Skip to content

Commit cda8036

Browse files
committed
chore(dev): enable the usage of the command property without port
1 parent 5f59329 commit cda8036

File tree

4 files changed

+147
-31
lines changed

4 files changed

+147
-31
lines changed

src/commands/dev/index.js

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// @ts-check
12
const path = require('path')
23
const process = require('process')
34
const { promisify } = require('util')
@@ -53,53 +54,75 @@ const isNonExistingCommandError = ({ command, error }) => {
5354
)
5455
}
5556

56-
const startFrameworkServer = async function ({ settings }) {
57-
if (settings.useStaticServer) {
58-
return await startStaticServer({ settings })
59-
}
60-
61-
log(`${NETLIFYDEVLOG} Starting Netlify Dev with ${settings.framework || 'custom config'}`)
62-
63-
// we use reject=false to avoid rejecting synchronously when the command doesn't exist
64-
const frameworkProcess = execa.command(settings.command, {
57+
/**
58+
* Run a command and pipe stdout, stderr and stdin
59+
* @param {string} command
60+
* @param {NodeJS.ProcessEnv} env
61+
* @returns {execa.ExecaChildProcess<string>}
62+
*/
63+
const runCommand = (command, env = {}) => {
64+
const commandProcess = execa.command(command, {
6565
preferLocal: true,
66+
// we use reject=false to avoid rejecting synchronously when the command doesn't exist
6667
reject: false,
67-
env: settings.env,
68+
env,
6869
// windowsHide needs to be false for child process to terminate properly on Windows
6970
windowsHide: false,
7071
})
71-
frameworkProcess.stdout.pipe(stripAnsiCc.stream()).pipe(process.stdout)
72-
frameworkProcess.stderr.pipe(stripAnsiCc.stream()).pipe(process.stderr)
73-
process.stdin.pipe(frameworkProcess.stdin)
72+
73+
commandProcess.stdout.pipe(stripAnsiCc.stream()).pipe(process.stdout)
74+
commandProcess.stderr.pipe(stripAnsiCc.stream()).pipe(process.stderr)
75+
process.stdin.pipe(commandProcess.stdin)
7476

7577
// we can't try->await->catch since we don't want to block on the framework server which
7678
// is a long running process
7779
// eslint-disable-next-line promise/catch-or-return,promise/prefer-await-to-then
78-
frameworkProcess.then(async () => {
79-
const result = await frameworkProcess
80-
const [commandWithoutArgs] = settings.command.split(' ')
80+
commandProcess.then(async () => {
81+
const result = await commandProcess
82+
const [commandWithoutArgs] = command.split(' ')
8183
// eslint-disable-next-line promise/always-return
8284
if (result.failed && isNonExistingCommandError({ command: commandWithoutArgs, error: result })) {
8385
log(
8486
NETLIFYDEVERR,
85-
`Failed launching framework server. Please verify ${chalk.magenta(`'${commandWithoutArgs}'`)} exists`,
87+
`Failed running command: ${command}. Please verify ${chalk.magenta(`'${commandWithoutArgs}'`)} exists`,
8688
)
8789
} else {
8890
const errorMessage = result.failed
8991
? `${NETLIFYDEVERR} ${result.shortMessage}`
90-
: `${NETLIFYDEVWARN} "${settings.command}" exited with code ${result.exitCode}`
92+
: `${NETLIFYDEVWARN} "${command}" exited with code ${result.exitCode}`
9193

9294
log(`${errorMessage}. Shutting down Netlify Dev server`)
9395
}
9496
process.exit(1)
9597
})
9698
;['SIGINT', 'SIGTERM', 'SIGQUIT', 'SIGHUP', 'exit'].forEach((signal) => {
9799
process.on(signal, () => {
98-
frameworkProcess.kill('SIGTERM', { forceKillAfterTimeout: 500 })
100+
commandProcess.kill('SIGTERM', { forceKillAfterTimeout: 500 })
99101
process.exit()
100102
})
101103
})
102104

105+
return commandProcess
106+
}
107+
108+
/**
109+
* Start a static server if the `useStaticServer` is provided or a framework specific server
110+
* @param {object} config
111+
* @param {import('../../utils/types').ServerSettings} config.settings
112+
* @returns {Promise<void>}
113+
*/
114+
const startFrameworkServer = async function ({ settings }) {
115+
if (settings.useStaticServer) {
116+
if (settings.command) {
117+
runCommand(settings.command, settings.env)
118+
}
119+
return await startStaticServer({ settings })
120+
}
121+
122+
log(`${NETLIFYDEVLOG} Starting Netlify Dev with ${settings.framework || 'custom config'}`)
123+
124+
runCommand(settings.command, settings.env)
125+
103126
try {
104127
const open = await waitPort({
105128
port: settings.frameworkPort,
@@ -185,6 +208,7 @@ class DevCommand extends Command {
185208
const { api, config, site, siteInfo } = this.netlify
186209
config.dev = { ...config.dev }
187210
config.build = { ...config.build }
211+
/** @type {import('./types').DevConfig} */
188212
const devConfig = {
189213
framework: '#auto',
190214
...(config.functionsDirectory && { functions: config.functionsDirectory }),
@@ -207,6 +231,7 @@ class DevCommand extends Command {
207231
siteInfo,
208232
})
209233

234+
/** @type {Partial<import('../../utils/types').ServerSettings>} */
210235
let settings = {}
211236
try {
212237
settings = await detectServerSettings(devConfig, flags, site.root)

src/commands/dev/types.d.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { FrameworkNames } from '../../utils/types';
2+
3+
export type DevConfig = {
4+
framework: FrameworkNames
5+
/** Directory of the functions */
6+
functions: string
7+
publish: string
8+
/** Port to serve the functions */
9+
port: number
10+
live: boolean
11+
staticServerPort?: number
12+
targetPort?: number
13+
/** Command to run */
14+
command?: string
15+
functionsPort?: number
16+
autoLaunch?: boolean
17+
https?: {
18+
keyFile: string
19+
certFile: string
20+
}
21+
}

src/utils/detect-server-settings.js

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// @ts-check
12
const { EOL } = require('os')
23
const path = require('path')
34
const process = require('process')
@@ -101,6 +102,14 @@ const getDefaultDist = () => {
101102
return process.cwd()
102103
}
103104

105+
/**
106+
*
107+
* @param {object} param0
108+
* @param {import('../commands/dev/types').DevConfig} param0.devConfig
109+
* @param {Record<string, string>} param0.flags
110+
* @param {string} param0.projectDir
111+
* @returns {Promise<import('./types').BaseServerSettings>}
112+
*/
104113
const handleStaticServer = async ({ devConfig, flags, projectDir }) => {
105114
validateNumberProperty({ devConfig, property: 'staticServerPort' })
106115

@@ -114,14 +123,6 @@ const handleStaticServer = async ({ devConfig, flags, projectDir }) => {
114123
)
115124
}
116125

117-
if (devConfig.command) {
118-
log(
119-
`${NETLIFYDEVWARN} Ignoring command setting since using a simple static server. Configure ${formatProperty(
120-
'command',
121-
)} ${chalk.bold('and')} ${formatProperty('targetPort')} for a custom setup`,
122-
)
123-
}
124-
125126
if (devConfig.targetPort) {
126127
log(
127128
`${NETLIFYDEVWARN} Ignoring ${formatProperty(
@@ -139,12 +140,18 @@ const handleStaticServer = async ({ devConfig, flags, projectDir }) => {
139140
errorMessage: 'Could not acquire configured static server port',
140141
})
141142
return {
143+
...(devConfig.command && { command: devConfig.command }),
142144
useStaticServer: true,
143145
frameworkPort,
144146
dist,
145147
}
146148
}
147149

150+
/**
151+
* Retrieves the settings from a framework
152+
* @param {import('./types').FrameworkInfo} framework
153+
* @returns {import('./types').BaseServerSettings}
154+
*/
148155
const getSettingsFromFramework = (framework) => {
149156
const {
150157
build: { directory: dist },
@@ -208,8 +215,14 @@ const detectFrameworkSettings = async ({ projectDir }) => {
208215
}
209216
}
210217

218+
/** @deprecated remove it */
211219
const hasCommandAndTargetPort = ({ devConfig }) => devConfig.command && devConfig.targetPort
212220

221+
/**
222+
* Creates settings for the custom framework
223+
* @param {*} param0
224+
* @returns {import('./types').BaseServerSettings}
225+
*/
213226
const handleCustomFramework = ({ devConfig }) => {
214227
if (!hasCommandAndTargetPort({ devConfig })) {
215228
throw new Error(
@@ -227,6 +240,11 @@ const handleCustomFramework = ({ devConfig }) => {
227240
}
228241
}
229242

243+
/**
244+
* Handles a forced framework and retrieves the settings for it
245+
* @param {*} param0
246+
* @returns {Promise<import('./types').BaseServerSettings>}
247+
*/
230248
const handleForcedFramework = async ({ devConfig, projectDir }) => {
231249
// this throws if `devConfig.framework` is not a supported framework
232250
const { command, dist, env, framework, frameworkPort, pollingStrategies } = getSettingsFromFramework(
@@ -242,9 +260,17 @@ const handleForcedFramework = async ({ devConfig, projectDir }) => {
242260
}
243261
}
244262

263+
/**
264+
* Get the server settings based on the flags and the devConfig
265+
* @param {import('../commands/dev/types').DevConfig} devConfig
266+
* @param {Record<string, string>} flags
267+
* @param {string} projectDir
268+
* @returns {Promise<import('./types').ServerSettings>}
269+
*/
245270
const detectServerSettings = async (devConfig, flags, projectDir) => {
246271
validateStringProperty({ devConfig, property: 'framework' })
247272

273+
/** @type {Partial<import('./types').BaseServerSettings>} */
248274
let settings = {}
249275

250276
if (flags.dir || devConfig.framework === '#static') {
@@ -253,11 +279,11 @@ const detectServerSettings = async (devConfig, flags, projectDir) => {
253279
} else if (devConfig.framework === '#auto') {
254280
// this is the default CLI behavior
255281

256-
// we don't need to run the detection if both command and targetPort are configured
257-
const runDetection = !hasCommandAndTargetPort({ devConfig })
258-
const frameworkSettings = runDetection ? await detectFrameworkSettings({ projectDir }) : undefined
282+
// detect the framework settings
283+
const frameworkSettings = await detectFrameworkSettings({ projectDir })
259284

260-
if (frameworkSettings === undefined && runDetection) {
285+
console.log(devConfig.targetPort)
286+
if (frameworkSettings === undefined) {
261287
log(`${NETLIFYDEVWARN} No app server detected. Using simple static server`)
262288
settings = await handleStaticServer({ flags, devConfig, projectDir })
263289
} else {

src/utils/types.d.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
export type FrameworkNames = '#static' | '#auto' | '#custom'
2+
3+
export type FrameworkInfo = {
4+
build: {
5+
directory: string
6+
}
7+
dev: {
8+
commands: string[]
9+
port: number
10+
pollingStrategies: { name: string }[]
11+
}
12+
name: FrameworkNames
13+
staticAssetsDirectory: string
14+
env: NodeJS.ProcessEnv
15+
}
16+
17+
export type BaseServerSettings = {
18+
dist: string
19+
20+
// static serving
21+
useStaticServer?: true
22+
23+
// Framework specific part
24+
/** A port where a proxy can listen to it */
25+
frameworkPort?: number
26+
functions?: string
27+
/** The command that was provided for the dev config */
28+
command?: string
29+
/** The framework name ('Create React App') */
30+
framework?: string
31+
env?: NodeJS.ProcessEnv
32+
pollingStrategies?: string[]
33+
}
34+
35+
export type ServerSettings = BaseServerSettings & {
36+
/** default 'secret' */
37+
jwtSecret: string
38+
/** default 'app_metadata.authorization.roles' */
39+
jwtRolePath: string
40+
/** The port where the functions are running on */
41+
port: number
42+
/** The directory of the functions */
43+
functions: number
44+
}

0 commit comments

Comments
 (0)