netlify-cli 15.9.1 → 15.10.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/run.mjs +6 -5
- package/npm-shrinkwrap.json +369 -266
- package/package.json +8 -9
- package/src/commands/base-command.mjs +295 -116
- package/src/commands/build/build.mjs +9 -1
- package/src/commands/deploy/deploy.mjs +23 -9
- package/src/commands/dev/dev.mjs +22 -17
- package/src/commands/functions/functions-create.mjs +118 -89
- package/src/commands/functions/functions-invoke.mjs +10 -7
- package/src/commands/functions/functions-list.mjs +2 -2
- package/src/commands/init/init.mjs +1 -1
- package/src/commands/link/link.mjs +5 -5
- package/src/commands/serve/serve.mjs +10 -6
- package/src/commands/sites/sites-create-template.mjs +1 -1
- package/src/commands/sites/sites-create.mjs +1 -1
- package/src/functions-templates/typescript/hello-world/package-lock.json +6 -6
- package/src/lib/edge-functions/bootstrap.mjs +1 -1
- package/src/lib/edge-functions/headers.mjs +1 -0
- package/src/lib/edge-functions/internal.mjs +5 -3
- package/src/lib/edge-functions/proxy.mjs +29 -4
- package/src/lib/functions/runtimes/js/index.mjs +1 -1
- package/src/lib/functions/runtimes/js/worker.mjs +1 -1
- package/src/lib/functions/server.mjs +3 -2
- package/src/lib/spinner.mjs +1 -1
- package/src/recipes/vscode/index.mjs +24 -6
- package/src/utils/build-info.mjs +100 -0
- package/src/utils/command-helpers.mjs +16 -7
- package/src/utils/detect-server-settings.mjs +133 -245
- package/src/utils/framework-server.mjs +6 -5
- package/src/utils/functions/functions.mjs +8 -5
- package/src/utils/get-repo-data.mjs +5 -6
- package/src/utils/init/config-github.mjs +2 -2
- package/src/utils/init/config-manual.mjs +24 -7
- package/src/utils/init/utils.mjs +62 -63
- package/src/utils/proxy-server.mjs +7 -4
- package/src/utils/proxy.mjs +4 -3
- package/src/utils/read-repo-url.mjs +4 -0
- package/src/utils/run-build.mjs +58 -32
- package/src/utils/shell.mjs +24 -7
- package/src/utils/state-config.mjs +5 -1
- package/src/utils/static-server.mjs +4 -0
- package/src/utils/telemetry/report-error.mjs +8 -4
- package/src/utils/init/frameworks.mjs +0 -23
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import { readFile } from 'fs/promises'
|
|
3
3
|
import { EOL } from 'os'
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import { Project } from '@netlify/build-info'
|
|
8
|
-
// eslint-disable-next-line import/extensions, n/no-missing-import
|
|
9
|
-
import { NodeFS } from '@netlify/build-info/node'
|
|
10
|
-
import { getFramework, listFrameworks } from '@netlify/framework-info'
|
|
11
|
-
import fuzzy from 'fuzzy'
|
|
4
|
+
import { dirname, relative, resolve } from 'path'
|
|
5
|
+
|
|
6
|
+
import { getFramework, getSettings } from '@netlify/build-info'
|
|
12
7
|
import getPort from 'get-port'
|
|
13
|
-
import inquirer from 'inquirer'
|
|
14
|
-
import inquirerAutocompletePrompt from 'inquirer-autocomplete-prompt'
|
|
15
8
|
|
|
9
|
+
import { detectFrameworkSettings } from './build-info.mjs'
|
|
16
10
|
import { NETLIFYDEVWARN, chalk, log } from './command-helpers.mjs'
|
|
17
11
|
import { acquirePort } from './dev.mjs'
|
|
18
12
|
import { getInternalFunctionsDir } from './functions/functions.mjs'
|
|
19
|
-
import {
|
|
13
|
+
import { getPluginsToAutoInstall } from './init/utils.mjs'
|
|
20
14
|
|
|
15
|
+
/** @param {string} str */
|
|
21
16
|
const formatProperty = (str) => chalk.magenta(`'${str}'`)
|
|
17
|
+
/** @param {string} str */
|
|
22
18
|
const formatValue = (str) => chalk.green(`'${str}'`)
|
|
23
19
|
|
|
20
|
+
/**
|
|
21
|
+
* @param {object} options
|
|
22
|
+
* @param {string} options.keyFile
|
|
23
|
+
* @param {string} options.certFile
|
|
24
|
+
* @returns {Promise<{ key: string, cert: string, keyFilePath: string, certFilePath: string }>}
|
|
25
|
+
*/
|
|
24
26
|
const readHttpsSettings = async (options) => {
|
|
25
27
|
if (typeof options !== 'object' || !options.keyFile || !options.certFile) {
|
|
26
28
|
throw new TypeError(
|
|
@@ -38,43 +40,43 @@ const readHttpsSettings = async (options) => {
|
|
|
38
40
|
throw new TypeError(`Certificate file configuration should be a string`)
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
const [
|
|
42
|
-
readFile(keyFile, 'utf-8'),
|
|
43
|
-
readFile(certFile, 'utf-8'),
|
|
44
|
-
])
|
|
43
|
+
const [key, cert] = await Promise.allSettled([readFile(keyFile, 'utf-8'), readFile(certFile, 'utf-8')])
|
|
45
44
|
|
|
46
|
-
if (
|
|
47
|
-
throw new Error(`Error reading private key file: ${
|
|
45
|
+
if (key.status === 'rejected') {
|
|
46
|
+
throw new Error(`Error reading private key file: ${key.reason}`)
|
|
48
47
|
}
|
|
49
|
-
if (
|
|
50
|
-
throw new Error(`Error reading certificate file: ${
|
|
48
|
+
if (cert.status === 'rejected') {
|
|
49
|
+
throw new Error(`Error reading certificate file: ${cert.reason}`)
|
|
51
50
|
}
|
|
52
51
|
|
|
53
|
-
return { key, cert, keyFilePath:
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const validateStringProperty = ({ devConfig, property }) => {
|
|
57
|
-
if (devConfig[property] && typeof devConfig[property] !== 'string') {
|
|
58
|
-
const formattedProperty = formatProperty(property)
|
|
59
|
-
throw new TypeError(
|
|
60
|
-
`Invalid ${formattedProperty} option provided in config. The value of ${formattedProperty} option must be a string`,
|
|
61
|
-
)
|
|
62
|
-
}
|
|
52
|
+
return { key: key.value, cert: cert.value, keyFilePath: resolve(keyFile), certFilePath: resolve(certFile) }
|
|
63
53
|
}
|
|
64
54
|
|
|
65
|
-
|
|
66
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Validates a property inside the devConfig to be of a given type
|
|
57
|
+
* @param {import('../commands/dev/types.js').DevConfig} devConfig The devConfig
|
|
58
|
+
* @param {keyof import('../commands/dev/types.js').DevConfig} property The property to validate
|
|
59
|
+
* @param {'string' | 'number'} type The type it should have
|
|
60
|
+
*/
|
|
61
|
+
function validateProperty(devConfig, property, type) {
|
|
62
|
+
// eslint-disable-next-line valid-typeof
|
|
63
|
+
if (devConfig[property] && typeof devConfig[property] !== type) {
|
|
67
64
|
const formattedProperty = formatProperty(property)
|
|
68
65
|
throw new TypeError(
|
|
69
|
-
`Invalid ${formattedProperty} option provided in config. The value of ${formattedProperty} option must be
|
|
66
|
+
`Invalid ${formattedProperty} option provided in config. The value of ${formattedProperty} option must be of type ${type}`,
|
|
70
67
|
)
|
|
71
68
|
}
|
|
72
69
|
}
|
|
73
70
|
|
|
71
|
+
/**
|
|
72
|
+
*
|
|
73
|
+
* @param {object} config
|
|
74
|
+
* @param {import('../commands/dev/types.js').DevConfig} config.devConfig
|
|
75
|
+
*/
|
|
74
76
|
const validateFrameworkConfig = ({ devConfig }) => {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
validateProperty(devConfig, 'command', 'string')
|
|
78
|
+
validateProperty(devConfig, 'port', 'number')
|
|
79
|
+
validateProperty(devConfig, 'targetPort', 'number')
|
|
78
80
|
|
|
79
81
|
if (devConfig.targetPort && devConfig.targetPort === devConfig.port) {
|
|
80
82
|
throw new Error(
|
|
@@ -85,6 +87,11 @@ const validateFrameworkConfig = ({ devConfig }) => {
|
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
89
|
|
|
90
|
+
/**
|
|
91
|
+
* @param {object} config
|
|
92
|
+
* @param {import('../commands/dev/types.js').DevConfig} config.devConfig
|
|
93
|
+
* @param {number=} config.detectedPort
|
|
94
|
+
*/
|
|
88
95
|
const validateConfiguredPort = ({ detectedPort, devConfig }) => {
|
|
89
96
|
if (devConfig.port && devConfig.port === detectedPort) {
|
|
90
97
|
const formattedPort = formatProperty('port')
|
|
@@ -97,13 +104,22 @@ const validateConfiguredPort = ({ detectedPort, devConfig }) => {
|
|
|
97
104
|
const DEFAULT_PORT = 8888
|
|
98
105
|
const DEFAULT_STATIC_PORT = 3999
|
|
99
106
|
|
|
100
|
-
|
|
107
|
+
/**
|
|
108
|
+
* Logs a message that it was unable to determine the dist directory and falls back to the workingDir
|
|
109
|
+
* @param {string} workingDir
|
|
110
|
+
*/
|
|
111
|
+
const getDefaultDist = (workingDir) => {
|
|
101
112
|
log(`${NETLIFYDEVWARN} Unable to determine public folder to serve files from. Using current working directory`)
|
|
102
113
|
log(`${NETLIFYDEVWARN} Setup a netlify.toml file with a [dev] section to specify your dev server settings.`)
|
|
103
114
|
log(`${NETLIFYDEVWARN} See docs at: https://cli.netlify.com/netlify-dev#project-detection`)
|
|
104
|
-
return
|
|
115
|
+
return workingDir
|
|
105
116
|
}
|
|
106
117
|
|
|
118
|
+
/**
|
|
119
|
+
* @param {object} config
|
|
120
|
+
* @param {import('../commands/dev/types.js').DevConfig} config.devConfig
|
|
121
|
+
* @returns {Promise<number>}
|
|
122
|
+
*/
|
|
107
123
|
const getStaticServerPort = async ({ devConfig }) => {
|
|
108
124
|
const port = await acquirePort({
|
|
109
125
|
configuredPort: devConfig.staticServerPort,
|
|
@@ -116,16 +132,16 @@ const getStaticServerPort = async ({ devConfig }) => {
|
|
|
116
132
|
|
|
117
133
|
/**
|
|
118
134
|
*
|
|
119
|
-
* @param {object}
|
|
120
|
-
* @param {import('../commands/dev/types.js').DevConfig}
|
|
121
|
-
* @param {import('commander').OptionValues}
|
|
122
|
-
* @param {string}
|
|
123
|
-
* @returns {Promise<import('./types.js').BaseServerSettings>}
|
|
135
|
+
* @param {object} config
|
|
136
|
+
* @param {import('../commands/dev/types.js').DevConfig} config.devConfig
|
|
137
|
+
* @param {import('commander').OptionValues} config.flags
|
|
138
|
+
* @param {string} config.workingDir
|
|
139
|
+
* @returns {Promise<Omit<import('./types.js').BaseServerSettings, 'command'> & {command?: string}>}
|
|
124
140
|
*/
|
|
125
|
-
const handleStaticServer = async ({ devConfig,
|
|
126
|
-
|
|
141
|
+
const handleStaticServer = async ({ devConfig, flags, workingDir }) => {
|
|
142
|
+
validateProperty(devConfig, 'staticServerPort', 'number')
|
|
127
143
|
|
|
128
|
-
if (
|
|
144
|
+
if (flags.dir) {
|
|
129
145
|
log(`${NETLIFYDEVWARN} Using simple static server because ${formatProperty('--dir')} flag was specified`)
|
|
130
146
|
} else if (devConfig.framework === '#static') {
|
|
131
147
|
log(
|
|
@@ -143,8 +159,8 @@ const handleStaticServer = async ({ devConfig, options, projectDir }) => {
|
|
|
143
159
|
)
|
|
144
160
|
}
|
|
145
161
|
|
|
146
|
-
const dist =
|
|
147
|
-
log(`${NETLIFYDEVWARN} Running static server from "${
|
|
162
|
+
const dist = flags.dir || devConfig.publish || getDefaultDist(workingDir)
|
|
163
|
+
log(`${NETLIFYDEVWARN} Running static server from "${relative(dirname(workingDir), dist)}"`)
|
|
148
164
|
|
|
149
165
|
const frameworkPort = await getStaticServerPort({ devConfig })
|
|
150
166
|
return {
|
|
@@ -157,144 +173,39 @@ const handleStaticServer = async ({ devConfig, options, projectDir }) => {
|
|
|
157
173
|
|
|
158
174
|
/**
|
|
159
175
|
* Retrieves the settings from a framework
|
|
160
|
-
* @param {import('
|
|
161
|
-
* @returns {import('./types.js').BaseServerSettings}
|
|
176
|
+
* @param {import('@netlify/build-info').Settings} [settings]
|
|
177
|
+
* @returns {import('./types.js').BaseServerSettings | undefined}
|
|
162
178
|
*/
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
commands: [command],
|
|
168
|
-
pollingStrategies = [],
|
|
169
|
-
port: frameworkPort,
|
|
170
|
-
},
|
|
171
|
-
env = {},
|
|
172
|
-
name: frameworkName,
|
|
173
|
-
plugins,
|
|
174
|
-
staticAssetsDirectory: staticDir,
|
|
175
|
-
} = framework
|
|
176
|
-
|
|
179
|
+
const getSettingsFromDetectedSettings = (settings) => {
|
|
180
|
+
if (!settings) {
|
|
181
|
+
return
|
|
182
|
+
}
|
|
177
183
|
return {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
184
|
+
baseDirectory: settings.baseDirectory,
|
|
185
|
+
command: settings.devCommand,
|
|
186
|
+
frameworkPort: settings.frameworkPort,
|
|
187
|
+
dist: settings.dist,
|
|
188
|
+
framework: settings.framework.name,
|
|
189
|
+
env: settings.env,
|
|
190
|
+
pollingStrategies: settings.pollingStrategies,
|
|
191
|
+
plugins: getPluginsToAutoInstall(settings.plugins_from_config_file, settings.plugins_recommended),
|
|
185
192
|
}
|
|
186
193
|
}
|
|
187
194
|
|
|
188
|
-
const hasDevCommand = (framework) => Array.isArray(framework.dev.commands) && framework.dev.commands.length !== 0
|
|
189
|
-
|
|
190
195
|
/**
|
|
191
|
-
*
|
|
192
|
-
* @param {string} projectDir
|
|
196
|
+
* @param {import('../commands/dev/types.js').DevConfig} devConfig
|
|
193
197
|
*/
|
|
194
|
-
const
|
|
195
|
-
const fs = new NodeFS()
|
|
196
|
-
const project = new Project(fs, projectDir)
|
|
197
|
-
|
|
198
|
-
return await project.getBuildSettings()
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
*
|
|
203
|
-
* @param {import('./types.js').BaseServerSettings | undefined} frameworkSettings
|
|
204
|
-
* @param {import('@netlify/build-info').Settings[]} newSettings
|
|
205
|
-
* @param {Record<string, Record<string, any>>} [metadata]
|
|
206
|
-
*/
|
|
207
|
-
const detectChangesInNewSettings = (frameworkSettings, newSettings, metadata) => {
|
|
208
|
-
/** @type {string[]} */
|
|
209
|
-
const message = ['']
|
|
210
|
-
const [setting] = newSettings
|
|
211
|
-
|
|
212
|
-
if (frameworkSettings?.framework !== setting?.framework.name) {
|
|
213
|
-
message.push(
|
|
214
|
-
`- Framework does not match:`,
|
|
215
|
-
` [old]: ${frameworkSettings?.framework}`,
|
|
216
|
-
` [new]: ${setting?.framework.name}`,
|
|
217
|
-
'',
|
|
218
|
-
)
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if (frameworkSettings?.command !== setting?.devCommand) {
|
|
222
|
-
message.push(
|
|
223
|
-
`- command does not match:`,
|
|
224
|
-
` [old]: ${frameworkSettings?.command}`,
|
|
225
|
-
` [new]: ${setting?.devCommand}`,
|
|
226
|
-
'',
|
|
227
|
-
)
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if (frameworkSettings?.dist !== setting?.dist) {
|
|
231
|
-
message.push(`- dist does not match:`, ` [old]: ${frameworkSettings?.dist}`, ` [new]: ${setting?.dist}`, '')
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (frameworkSettings?.frameworkPort !== setting?.frameworkPort) {
|
|
235
|
-
message.push(
|
|
236
|
-
`- frameworkPort does not match:`,
|
|
237
|
-
` [old]: ${frameworkSettings?.frameworkPort}`,
|
|
238
|
-
` [new]: ${setting?.frameworkPort}`,
|
|
239
|
-
'',
|
|
240
|
-
)
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (message.length !== 0) {
|
|
244
|
-
reportError(
|
|
245
|
-
{
|
|
246
|
-
name: 'NewSettingsDetectionMismatch',
|
|
247
|
-
errorMessage: 'New Settings detection does not match old one',
|
|
248
|
-
message: message.join('\n'),
|
|
249
|
-
},
|
|
250
|
-
{ severity: 'info', metadata },
|
|
251
|
-
)
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const detectFrameworkSettings = async ({ projectDir }) => {
|
|
256
|
-
const projectFrameworks = await listFrameworks({ projectDir })
|
|
257
|
-
const frameworks = projectFrameworks.filter((framework) => hasDevCommand(framework))
|
|
258
|
-
|
|
259
|
-
if (frameworks.length === 1) {
|
|
260
|
-
return getSettingsFromFramework(frameworks[0])
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
if (frameworks.length > 1) {
|
|
264
|
-
/** multiple matching detectors, make the user choose */
|
|
265
|
-
inquirer.registerPrompt('autocomplete', inquirerAutocompletePrompt)
|
|
266
|
-
const scriptInquirerOptions = formatSettingsArrForInquirer(frameworks)
|
|
267
|
-
const { chosenFramework } = await inquirer.prompt({
|
|
268
|
-
name: 'chosenFramework',
|
|
269
|
-
message: `Multiple possible start commands found`,
|
|
270
|
-
type: 'autocomplete',
|
|
271
|
-
source(_, input) {
|
|
272
|
-
if (!input || input === '') {
|
|
273
|
-
return scriptInquirerOptions
|
|
274
|
-
}
|
|
275
|
-
// only show filtered results
|
|
276
|
-
return filterSettings(scriptInquirerOptions, input)
|
|
277
|
-
},
|
|
278
|
-
})
|
|
279
|
-
log(
|
|
280
|
-
`Add ${formatProperty(
|
|
281
|
-
`framework = "${chosenFramework.id}"`,
|
|
282
|
-
)} to the [dev] section of your netlify.toml to avoid this selection prompt next time`,
|
|
283
|
-
)
|
|
284
|
-
|
|
285
|
-
return getSettingsFromFramework(chosenFramework)
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const hasCommandAndTargetPort = ({ devConfig }) => devConfig.command && devConfig.targetPort
|
|
198
|
+
const hasCommandAndTargetPort = (devConfig) => devConfig.command && devConfig.targetPort
|
|
290
199
|
|
|
291
200
|
/**
|
|
292
201
|
* Creates settings for the custom framework
|
|
293
|
-
* @param {
|
|
202
|
+
* @param {object} config
|
|
203
|
+
* @param {import('../commands/dev/types.js').DevConfig} config.devConfig
|
|
204
|
+
* @param {string} config.workingDir
|
|
294
205
|
* @returns {import('./types.js').BaseServerSettings}
|
|
295
206
|
*/
|
|
296
|
-
const handleCustomFramework = ({ devConfig }) => {
|
|
297
|
-
if (!hasCommandAndTargetPort(
|
|
207
|
+
const handleCustomFramework = ({ devConfig, workingDir }) => {
|
|
208
|
+
if (!hasCommandAndTargetPort(devConfig)) {
|
|
298
209
|
throw new Error(
|
|
299
210
|
`${formatProperty('command')} and ${formatProperty('targetPort')} properties are required when ${formatProperty(
|
|
300
211
|
'framework',
|
|
@@ -304,101 +215,100 @@ const handleCustomFramework = ({ devConfig }) => {
|
|
|
304
215
|
return {
|
|
305
216
|
command: devConfig.command,
|
|
306
217
|
frameworkPort: devConfig.targetPort,
|
|
307
|
-
dist: devConfig.publish || getDefaultDist(),
|
|
218
|
+
dist: devConfig.publish || getDefaultDist(workingDir),
|
|
308
219
|
framework: '#custom',
|
|
309
220
|
pollingStrategies: devConfig.pollingStrategies || [],
|
|
310
221
|
}
|
|
311
222
|
}
|
|
312
223
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const command = devConfig.command || frameworkCommand
|
|
324
|
-
const frameworkPort = devConfig.targetPort || frameworkDetectedPort
|
|
224
|
+
/**
|
|
225
|
+
* Merges the framework settings with the devConfig
|
|
226
|
+
* @param {object} config
|
|
227
|
+
* @param {import('../commands/dev/types.js').DevConfig} config.devConfig
|
|
228
|
+
* @param {string} config.workingDir
|
|
229
|
+
* @param {Partial<import('./types.js').BaseServerSettings>=} config.frameworkSettings
|
|
230
|
+
*/
|
|
231
|
+
const mergeSettings = async ({ devConfig, frameworkSettings = {}, workingDir }) => {
|
|
232
|
+
const command = devConfig.command || frameworkSettings.command
|
|
233
|
+
const frameworkPort = devConfig.targetPort || frameworkSettings.frameworkPort
|
|
325
234
|
// if the framework doesn't start a server, we use a static one
|
|
326
235
|
const useStaticServer = !(command && frameworkPort)
|
|
327
236
|
return {
|
|
237
|
+
baseDirectory: devConfig.base || frameworkSettings.baseDirectory,
|
|
328
238
|
command,
|
|
329
239
|
frameworkPort: useStaticServer ? await getStaticServerPort({ devConfig }) : frameworkPort,
|
|
330
|
-
dist: devConfig.publish || dist || getDefaultDist(),
|
|
331
|
-
framework,
|
|
332
|
-
env,
|
|
333
|
-
pollingStrategies,
|
|
240
|
+
dist: devConfig.publish || frameworkSettings.dist || getDefaultDist(workingDir),
|
|
241
|
+
framework: frameworkSettings.framework,
|
|
242
|
+
env: frameworkSettings.env,
|
|
243
|
+
pollingStrategies: frameworkSettings.pollingStrategies || [],
|
|
334
244
|
useStaticServer,
|
|
335
245
|
}
|
|
336
246
|
}
|
|
337
247
|
|
|
338
248
|
/**
|
|
339
249
|
* Handles a forced framework and retrieves the settings for it
|
|
340
|
-
* @param {
|
|
250
|
+
* @param {object} config
|
|
251
|
+
* @param {import('../commands/dev/types.js').DevConfig} config.devConfig
|
|
252
|
+
* @param {import('@netlify/build-info').Project} config.project
|
|
253
|
+
* @param {string} config.workingDir
|
|
254
|
+
* @param {string=} config.workspacePackage
|
|
341
255
|
* @returns {Promise<import('./types.js').BaseServerSettings>}
|
|
342
256
|
*/
|
|
343
|
-
const handleForcedFramework = async ({ devConfig,
|
|
257
|
+
const handleForcedFramework = async ({ devConfig, project, workingDir, workspacePackage }) => {
|
|
344
258
|
// this throws if `devConfig.framework` is not a supported framework
|
|
345
|
-
const
|
|
346
|
-
|
|
259
|
+
const framework = await getFramework(devConfig.framework, project)
|
|
260
|
+
const settings = await getSettings(framework, project, workspacePackage || '')
|
|
261
|
+
const frameworkSettings = getSettingsFromDetectedSettings(settings)
|
|
262
|
+
return mergeSettings({ devConfig, workingDir, frameworkSettings })
|
|
347
263
|
}
|
|
348
264
|
|
|
349
265
|
/**
|
|
350
266
|
* Get the server settings based on the flags and the devConfig
|
|
351
267
|
* @param {import('../commands/dev/types.js').DevConfig} devConfig
|
|
352
|
-
* @param {import('commander').OptionValues}
|
|
353
|
-
* @param {
|
|
354
|
-
* @param {Record<string, Record<string, any>>} [metadata]
|
|
268
|
+
* @param {import('commander').OptionValues} flags
|
|
269
|
+
* @param {import('../commands/base-command.mjs').default} command
|
|
355
270
|
* @returns {Promise<import('./types.js').ServerSettings>}
|
|
356
271
|
*/
|
|
357
|
-
|
|
358
|
-
|
|
272
|
+
|
|
273
|
+
const detectServerSettings = async (devConfig, flags, command) => {
|
|
274
|
+
validateProperty(devConfig, 'framework', 'string')
|
|
359
275
|
|
|
360
276
|
/** @type {Partial<import('./types.js').BaseServerSettings>} */
|
|
361
277
|
let settings = {}
|
|
362
278
|
|
|
363
|
-
if (
|
|
279
|
+
if (flags.dir || devConfig.framework === '#static') {
|
|
364
280
|
// serving files statically without a framework server
|
|
365
|
-
settings = await handleStaticServer({
|
|
281
|
+
settings = await handleStaticServer({ flags, devConfig, workingDir: command.workingDir })
|
|
366
282
|
} else if (devConfig.framework === '#auto') {
|
|
367
283
|
// this is the default CLI behavior
|
|
368
284
|
|
|
369
|
-
const runDetection = !hasCommandAndTargetPort(
|
|
370
|
-
const frameworkSettings = runDetection
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
// just report differences in the settings
|
|
374
|
-
detectChangesInNewSettings(frameworkSettings, newSettings || [], {
|
|
375
|
-
...metadata,
|
|
376
|
-
settings: {
|
|
377
|
-
projectDir,
|
|
378
|
-
devConfig,
|
|
379
|
-
options,
|
|
380
|
-
old: frameworkSettings,
|
|
381
|
-
settings: newSettings,
|
|
382
|
-
},
|
|
383
|
-
})
|
|
384
|
-
|
|
285
|
+
const runDetection = !hasCommandAndTargetPort(devConfig)
|
|
286
|
+
const frameworkSettings = runDetection
|
|
287
|
+
? getSettingsFromDetectedSettings(await detectFrameworkSettings(command, 'dev'))
|
|
288
|
+
: undefined
|
|
385
289
|
if (frameworkSettings === undefined && runDetection) {
|
|
386
290
|
log(`${NETLIFYDEVWARN} No app server detected. Using simple static server`)
|
|
387
|
-
settings = await handleStaticServer({
|
|
291
|
+
settings = await handleStaticServer({ flags, devConfig, workingDir: command.workingDir })
|
|
388
292
|
} else {
|
|
389
293
|
validateFrameworkConfig({ devConfig })
|
|
390
|
-
|
|
294
|
+
|
|
295
|
+
settings = await mergeSettings({ devConfig, frameworkSettings, workingDir: command.workingDir })
|
|
391
296
|
}
|
|
392
297
|
|
|
393
|
-
settings.plugins = frameworkSettings
|
|
298
|
+
settings.plugins = frameworkSettings?.plugins
|
|
394
299
|
} else if (devConfig.framework === '#custom') {
|
|
395
300
|
validateFrameworkConfig({ devConfig })
|
|
396
301
|
// when the users wants to configure `command` and `targetPort`
|
|
397
|
-
settings = handleCustomFramework({ devConfig })
|
|
302
|
+
settings = handleCustomFramework({ devConfig, workingDir: command.workingDir })
|
|
398
303
|
} else if (devConfig.framework) {
|
|
399
304
|
validateFrameworkConfig({ devConfig })
|
|
400
305
|
// this is when the user explicitly configures a framework, e.g. `framework = "gatsby"`
|
|
401
|
-
settings = await handleForcedFramework({
|
|
306
|
+
settings = await handleForcedFramework({
|
|
307
|
+
devConfig,
|
|
308
|
+
project: command.project,
|
|
309
|
+
workingDir: command.workingDir,
|
|
310
|
+
workspacePackage: command.workspacePackage,
|
|
311
|
+
})
|
|
402
312
|
}
|
|
403
313
|
|
|
404
314
|
validateConfiguredPort({ devConfig, detectedPort: settings.frameworkPort })
|
|
@@ -409,7 +319,7 @@ const detectServerSettings = async (devConfig, options, projectDir, metadata) =>
|
|
|
409
319
|
errorMessage: `Could not acquire required ${formatProperty('port')}`,
|
|
410
320
|
})
|
|
411
321
|
const functionsDir = devConfig.functions || settings.functions
|
|
412
|
-
const internalFunctionsDir = await getInternalFunctionsDir({ base:
|
|
322
|
+
const internalFunctionsDir = await getInternalFunctionsDir({ base: command.workingDir })
|
|
413
323
|
const shouldStartFunctionsServer = Boolean(functionsDir || internalFunctionsDir)
|
|
414
324
|
|
|
415
325
|
return {
|
|
@@ -423,28 +333,6 @@ const detectServerSettings = async (devConfig, options, projectDir, metadata) =>
|
|
|
423
333
|
}
|
|
424
334
|
}
|
|
425
335
|
|
|
426
|
-
const filterSettings = function (scriptInquirerOptions, input) {
|
|
427
|
-
const filterOptions = scriptInquirerOptions.map((scriptInquirerOption) => scriptInquirerOption.name)
|
|
428
|
-
// TODO: remove once https://github.com/sindresorhus/eslint-plugin-unicorn/issues/1394 is fixed
|
|
429
|
-
// eslint-disable-next-line unicorn/no-array-method-this-argument
|
|
430
|
-
const filteredSettings = fuzzy.filter(input, filterOptions)
|
|
431
|
-
const filteredSettingNames = new Set(
|
|
432
|
-
filteredSettings.map((filteredSetting) => (input ? filteredSetting.string : filteredSetting)),
|
|
433
|
-
)
|
|
434
|
-
return scriptInquirerOptions.filter((t) => filteredSettingNames.has(t.name))
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
const formatSettingsArrForInquirer = function (frameworks) {
|
|
438
|
-
const formattedArr = frameworks.map((framework) =>
|
|
439
|
-
framework.dev.commands.map((command) => ({
|
|
440
|
-
name: `[${chalk.yellow(framework.name)}] '${command}'`,
|
|
441
|
-
value: { ...framework, commands: [command] },
|
|
442
|
-
short: `${framework.name}-${command}`,
|
|
443
|
-
})),
|
|
444
|
-
)
|
|
445
|
-
return formattedArr.flat()
|
|
446
|
-
}
|
|
447
|
-
|
|
448
336
|
/**
|
|
449
337
|
* Returns a copy of the provided config with any plugins provided by the
|
|
450
338
|
* server settings
|
|
@@ -18,13 +18,14 @@ const FRAMEWORK_PORT_TIMEOUT = 6e5
|
|
|
18
18
|
/**
|
|
19
19
|
* Start a static server if the `useStaticServer` is provided or a framework specific server
|
|
20
20
|
* @param {object} config
|
|
21
|
-
* @param {
|
|
21
|
+
* @param {import('./types.js').ServerSettings} config.settings
|
|
22
|
+
* @param {string} config.cwd
|
|
22
23
|
* @returns {Promise<StartReturnObject>}
|
|
23
24
|
*/
|
|
24
|
-
export const startFrameworkServer = async function ({ settings }) {
|
|
25
|
+
export const startFrameworkServer = async function ({ cwd, settings }) {
|
|
25
26
|
if (settings.useStaticServer) {
|
|
26
27
|
if (settings.command) {
|
|
27
|
-
runCommand(settings.command, settings.env)
|
|
28
|
+
runCommand(settings.command, { env: settings.env, cwd })
|
|
28
29
|
}
|
|
29
30
|
await startStaticServer({ settings })
|
|
30
31
|
|
|
@@ -37,7 +38,7 @@ export const startFrameworkServer = async function ({ settings }) {
|
|
|
37
38
|
text: `Waiting for framework port ${settings.frameworkPort}. This can be configured using the 'targetPort' property in the netlify.toml`,
|
|
38
39
|
})
|
|
39
40
|
|
|
40
|
-
runCommand(settings.command, settings.env, spinner)
|
|
41
|
+
runCommand(settings.command, { env: settings.env, spinner, cwd })
|
|
41
42
|
|
|
42
43
|
let port
|
|
43
44
|
try {
|
|
@@ -46,7 +47,7 @@ export const startFrameworkServer = async function ({ settings }) {
|
|
|
46
47
|
host: 'localhost',
|
|
47
48
|
output: 'silent',
|
|
48
49
|
timeout: FRAMEWORK_PORT_TIMEOUT,
|
|
49
|
-
...(settings.pollingStrategies
|
|
50
|
+
...(settings.pollingStrategies?.includes('HTTP') && { protocol: 'http' }),
|
|
50
51
|
})
|
|
51
52
|
|
|
52
53
|
if (!port.open) {
|
|
@@ -17,11 +17,7 @@ export const SERVE_FUNCTIONS_FOLDER = 'functions-serve'
|
|
|
17
17
|
* @returns {string}
|
|
18
18
|
*/
|
|
19
19
|
export const getFunctionsDir = ({ config, options }, defaultValue) =>
|
|
20
|
-
options.functions ||
|
|
21
|
-
(config.dev && config.dev.functions) ||
|
|
22
|
-
config.functionsDirectory ||
|
|
23
|
-
(config.dev && config.dev.Functions) ||
|
|
24
|
-
defaultValue
|
|
20
|
+
options.functions || config.dev?.functions || config.functionsDirectory || config.dev?.Functions || defaultValue
|
|
25
21
|
|
|
26
22
|
export const getFunctionsManifestPath = async ({ base }) => {
|
|
27
23
|
const path = resolve(base, getPathInProject(['functions', 'manifest.json']))
|
|
@@ -37,6 +33,13 @@ export const getFunctionsDistPath = async ({ base }) => {
|
|
|
37
33
|
return isDirectory ? path : null
|
|
38
34
|
}
|
|
39
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Retrieves the internal functions directory and creates it if ensureExists is provided
|
|
38
|
+
* @param {object} config
|
|
39
|
+
* @param {string} config.base
|
|
40
|
+
* @param {boolean=} config.ensureExists
|
|
41
|
+
* @returns
|
|
42
|
+
*/
|
|
40
43
|
export const getInternalFunctionsDir = async ({ base, ensureExists }) => {
|
|
41
44
|
const path = resolve(base, getPathInProject([INTERNAL_FUNCTIONS_FOLDER]))
|
|
42
45
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import { dirname } from 'path'
|
|
3
|
-
import process from 'process'
|
|
4
3
|
import util from 'util'
|
|
5
4
|
|
|
6
5
|
import { findUp } from 'find-up'
|
|
@@ -14,14 +13,14 @@ import { log } from './command-helpers.mjs'
|
|
|
14
13
|
*
|
|
15
14
|
* @param {object} config
|
|
16
15
|
* @param {string} [config.remoteName]
|
|
16
|
+
* @param {string} config.workingDir
|
|
17
17
|
* @returns
|
|
18
18
|
*/
|
|
19
|
-
const getRepoData = async function ({ remoteName
|
|
19
|
+
const getRepoData = async function ({ remoteName, workingDir }) {
|
|
20
20
|
try {
|
|
21
|
-
const cwd = process.cwd()
|
|
22
21
|
const [gitConfig, gitDirectory] = await Promise.all([
|
|
23
|
-
util.promisify(gitconfiglocal)(
|
|
24
|
-
findUp('.git', { cwd, type: 'directory' }),
|
|
22
|
+
util.promisify(gitconfiglocal)(workingDir),
|
|
23
|
+
findUp('.git', { cwd: workingDir, type: 'directory' }),
|
|
25
24
|
])
|
|
26
25
|
|
|
27
26
|
if (!gitDirectory || !gitConfig || !gitConfig.remote || Object.keys(gitConfig.remote).length === 0) {
|
|
@@ -30,7 +29,7 @@ const getRepoData = async function ({ remoteName } = {}) {
|
|
|
30
29
|
|
|
31
30
|
const baseGitPath = dirname(gitDirectory)
|
|
32
31
|
|
|
33
|
-
if (
|
|
32
|
+
if (workingDir !== baseGitPath) {
|
|
34
33
|
log(`Git directory located in ${baseGitPath}`)
|
|
35
34
|
}
|
|
36
35
|
|
|
@@ -207,7 +207,7 @@ export const configGithub = async ({ command, repoName, repoOwner, siteId }) =>
|
|
|
207
207
|
const { netlify } = command
|
|
208
208
|
const {
|
|
209
209
|
api,
|
|
210
|
-
cachedConfig: { configPath
|
|
210
|
+
cachedConfig: { configPath },
|
|
211
211
|
config,
|
|
212
212
|
globalConfig,
|
|
213
213
|
repositoryRoot,
|
|
@@ -220,7 +220,7 @@ export const configGithub = async ({ command, repoName, repoOwner, siteId }) =>
|
|
|
220
220
|
repositoryRoot,
|
|
221
221
|
siteRoot,
|
|
222
222
|
config,
|
|
223
|
-
|
|
223
|
+
command,
|
|
224
224
|
})
|
|
225
225
|
await saveNetlifyToml({ repositoryRoot, config, configPath, baseDir, buildCmd, buildDir, functionsDir })
|
|
226
226
|
|