1
- const childProcess = require ( 'child_process' )
2
1
const path = require ( 'path' )
3
2
const process = require ( 'process' )
4
3
const { promisify } = require ( 'util' )
5
4
6
5
const { flags : flagsLib } = require ( '@oclif/command' )
7
6
const boxen = require ( 'boxen' )
8
7
const chalk = require ( 'chalk' )
8
+ const execa = require ( 'execa' )
9
9
const StaticServer = require ( 'static-server' )
10
10
const stripAnsiCc = require ( 'strip-ansi-control-characters' )
11
11
const waitPort = require ( 'wait-port' )
12
- const which = require ( 'which' )
13
12
14
13
const { startFunctionsServer } = require ( '../../lib/functions/server' )
15
14
const Command = require ( '../../utils/command' )
@@ -35,47 +34,61 @@ const startStaticServer = async ({ settings, log }) => {
35
34
log ( `\n${ NETLIFYDEVLOG } Server listening to` , settings . frameworkPort )
36
35
}
37
36
37
+ const isNonExistingCommandError = ( { command, error } ) => {
38
+ // `ENOENT` is only returned for non Windows systems
39
+ // See https://github.com/sindresorhus/execa/pull/447
40
+ if ( error . code === 'ENOENT' ) {
41
+ return true
42
+ }
43
+
44
+ // if the command is a package manager we let it report the error
45
+ if ( [ 'yarn' , 'npm' ] . includes ( command ) ) {
46
+ return false
47
+ }
48
+
49
+ // this only works on English versions of Windows
50
+ return (
51
+ typeof error . message === 'string' && error . message . includes ( 'is not recognized as an internal or external command' )
52
+ )
53
+ }
54
+
38
55
const startFrameworkServer = async function ( { settings, log, exit } ) {
39
56
if ( settings . noCmd ) {
40
57
return await startStaticServer ( { settings, log } )
41
58
}
42
59
43
60
log ( `${ NETLIFYDEVLOG } Starting Netlify Dev with ${ settings . framework || 'custom config' } ` )
44
- const commandBin = await which ( settings . command ) . catch ( ( error ) => {
45
- if ( error . code === 'ENOENT' ) {
46
- throw new Error (
47
- `"${ settings . command } " could not be found in your PATH. Please make sure that "${ settings . command } " is installed and available in your PATH` ,
48
- )
49
- }
50
- throw error
51
- } )
52
- const ps = childProcess . spawn ( commandBin , settings . args , {
53
- env : { ...process . env , ...settings . env , FORCE_COLOR : 'true' } ,
54
- stdio : 'pipe' ,
55
- } )
56
61
57
- ps . stdout . pipe ( stripAnsiCc . stream ( ) ) . pipe ( process . stdout )
58
- ps . stderr . pipe ( stripAnsiCc . stream ( ) ) . pipe ( process . stderr )
59
-
60
- process . stdin . pipe ( process . stdin )
62
+ // we use reject=false to avoid rejecting synchronously when the command doesn't exist
63
+ const frameworkProcess = execa ( settings . command , settings . args , { preferLocal : true , reject : false } )
64
+ frameworkProcess . stdout . pipe ( stripAnsiCc . stream ( ) ) . pipe ( process . stdout )
65
+ frameworkProcess . stderr . pipe ( stripAnsiCc . stream ( ) ) . pipe ( process . stderr )
66
+ process . stdin . pipe ( frameworkProcess . stdin )
67
+
68
+ // we can't try->await->catch since we don't want to block on the framework server which
69
+ // is a long running process
70
+ // eslint-disable-next-line promise/catch-or-return,promise/prefer-await-to-then
71
+ frameworkProcess . then ( async ( ) => {
72
+ const result = await frameworkProcess
73
+ // eslint-disable-next-line promise/always-return
74
+ if ( result . failed && isNonExistingCommandError ( { command : settings . command , error : result } ) ) {
75
+ log (
76
+ NETLIFYDEVERR ,
77
+ `Failed launching framework server. Please verify ${ chalk . magenta ( `'${ settings . command } '` ) } exists` ,
78
+ )
79
+ } else {
80
+ const commandWithArgs = `${ settings . command } ${ settings . args . join ( ' ' ) } `
81
+ const errorMessage = result . failed
82
+ ? `${ NETLIFYDEVERR } ${ result . shortMessage } `
83
+ : `${ NETLIFYDEVWARN } "${ commandWithArgs } " exited with code ${ result . exitCode } `
61
84
62
- const handleProcessExit = function ( code ) {
63
- log (
64
- code > 0 ? NETLIFYDEVERR : NETLIFYDEVWARN ,
65
- `"${ [ settings . command , ...settings . args ] . join ( ' ' ) } " exited with code ${ code } . Shutting down Netlify Dev server` ,
66
- )
85
+ log ( `${ errorMessage } . Shutting down Netlify Dev server` )
86
+ }
67
87
process . exit ( 1 )
68
- }
69
- ps . on ( 'close' , handleProcessExit )
70
- ps . on ( 'SIGINT' , handleProcessExit )
71
- ps . on ( 'SIGTERM' , handleProcessExit )
88
+ } )
72
89
; [ 'SIGINT' , 'SIGTERM' , 'SIGQUIT' , 'SIGHUP' , 'exit' ] . forEach ( ( signal ) => {
73
90
process . on ( signal , ( ) => {
74
- try {
75
- process . kill ( - ps . pid )
76
- } catch ( error ) {
77
- // Ignore
78
- }
91
+ frameworkProcess . kill ( 'SIGTERM' , { forceKillAfterTimeout : 500 } )
79
92
process . exit ( )
80
93
} )
81
94
} )
@@ -96,8 +109,6 @@ const startFrameworkServer = async function ({ settings, log, exit }) {
96
109
log ( NETLIFYDEVERR , `Please make sure your framework server is running on port ${ settings . frameworkPort } ` )
97
110
exit ( 1 )
98
111
}
99
-
100
- return ps
101
112
}
102
113
103
114
// 10 minutes
0 commit comments