netlify-cli 12.6.0 → 12.7.0
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/README.md +5 -0
- package/npm-shrinkwrap.json +169 -54
- package/package.json +3 -2
- package/src/commands/base-command.mjs +16 -5
- package/src/commands/dev/dev.mjs +27 -524
- package/src/commands/functions/functions-serve.mjs +5 -1
- package/src/commands/main.mjs +2 -0
- package/src/commands/serve/serve.mjs +189 -0
- package/src/functions-templates/javascript/oauth-passport/package.json +1 -1
- package/src/lib/edge-functions/registry.mjs +2 -5
- package/src/lib/functions/registry.mjs +55 -33
- package/src/lib/functions/runtimes/js/builders/zisi.mjs +2 -1
- package/src/lib/functions/runtimes/rust/index.mjs +2 -1
- package/src/lib/functions/server.mjs +35 -18
- package/src/utils/banner.mjs +17 -0
- package/src/utils/detect-server-settings.mjs +35 -1
- package/src/utils/dev.mjs +8 -5
- package/src/utils/framework-server.mjs +66 -0
- package/src/utils/functions/functions.mjs +18 -2
- package/src/utils/graph.mjs +170 -0
- package/src/utils/proxy-server.mjs +90 -0
- package/src/utils/run-build.mjs +129 -0
- package/src/utils/shell.mjs +120 -0
- package/src/utils/static-server.mjs +34 -0
- package/src/utils/validation.mjs +15 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import process from 'process'
|
|
3
|
+
|
|
4
|
+
import { Option } from 'commander'
|
|
5
|
+
|
|
6
|
+
import { promptEditorHelper } from '../../lib/edge-functions/editor-helper.mjs'
|
|
7
|
+
import { startFunctionsServer } from '../../lib/functions/server.mjs'
|
|
8
|
+
import { printBanner } from '../../utils/banner.mjs'
|
|
9
|
+
import {
|
|
10
|
+
chalk,
|
|
11
|
+
exit,
|
|
12
|
+
log,
|
|
13
|
+
NETLIFYDEVERR,
|
|
14
|
+
NETLIFYDEVLOG,
|
|
15
|
+
NETLIFYDEVWARN,
|
|
16
|
+
normalizeConfig,
|
|
17
|
+
} from '../../utils/command-helpers.mjs'
|
|
18
|
+
import detectServerSettings, { getConfigWithPlugins } from '../../utils/detect-server-settings.mjs'
|
|
19
|
+
import { getSiteInformation, injectEnvVariables } from '../../utils/dev.mjs'
|
|
20
|
+
import { getEnvelopeEnv, normalizeContext } from '../../utils/env/index.mjs'
|
|
21
|
+
import { getInternalFunctionsDir } from '../../utils/functions/functions.mjs'
|
|
22
|
+
import { ensureNetlifyIgnore } from '../../utils/gitignore.mjs'
|
|
23
|
+
import openBrowser from '../../utils/open-browser.mjs'
|
|
24
|
+
import { generateInspectSettings, startProxyServer } from '../../utils/proxy-server.mjs'
|
|
25
|
+
import { runBuildTimeline } from '../../utils/run-build.mjs'
|
|
26
|
+
import { getGeoCountryArgParser } from '../../utils/validation.mjs'
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The serve command
|
|
30
|
+
* @param {import('commander').OptionValues} options
|
|
31
|
+
* @param {import('../base-command.mjs').default} command
|
|
32
|
+
*/
|
|
33
|
+
const serve = async (options, command) => {
|
|
34
|
+
const { api, cachedConfig, config, repositoryRoot, site, siteInfo, state } = command.netlify
|
|
35
|
+
config.dev = { ...config.dev }
|
|
36
|
+
config.build = { ...config.build }
|
|
37
|
+
/** @type {import('../dev/types').DevConfig} */
|
|
38
|
+
const devConfig = {
|
|
39
|
+
...(config.functionsDirectory && { functions: config.functionsDirectory }),
|
|
40
|
+
...(config.build.publish && { publish: config.build.publish }),
|
|
41
|
+
...config.dev,
|
|
42
|
+
...options,
|
|
43
|
+
// Override the `framework` value so that we start a static server and not
|
|
44
|
+
// the framework's development server.
|
|
45
|
+
framework: '#static',
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let { env } = cachedConfig
|
|
49
|
+
|
|
50
|
+
if (!options.offline && siteInfo.use_envelope) {
|
|
51
|
+
env = await getEnvelopeEnv({ api, context: options.context, env, siteInfo })
|
|
52
|
+
log(`${NETLIFYDEVLOG} Injecting environment variable values for ${chalk.yellow('all scopes')}`)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
await injectEnvVariables({ devConfig, env, site })
|
|
56
|
+
await promptEditorHelper({ chalk, config, log, NETLIFYDEVLOG, repositoryRoot, state })
|
|
57
|
+
|
|
58
|
+
const { addonsUrls, capabilities, siteUrl, timeouts } = await getSiteInformation({
|
|
59
|
+
// inherited from base command --offline
|
|
60
|
+
offline: options.offline,
|
|
61
|
+
api,
|
|
62
|
+
site,
|
|
63
|
+
siteInfo,
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
// Ensure the internal functions directory exists so that the functions
|
|
67
|
+
// server and registry are initialized, and any functions created by
|
|
68
|
+
// Netlify Build are loaded.
|
|
69
|
+
await getInternalFunctionsDir({ base: site.root, ensureExists: true })
|
|
70
|
+
|
|
71
|
+
/** @type {Partial<import('../../utils/types').ServerSettings>} */
|
|
72
|
+
let settings = {}
|
|
73
|
+
try {
|
|
74
|
+
settings = await detectServerSettings(devConfig, options, site.root)
|
|
75
|
+
|
|
76
|
+
cachedConfig.config = getConfigWithPlugins(cachedConfig.config, settings)
|
|
77
|
+
} catch (error_) {
|
|
78
|
+
log(NETLIFYDEVERR, error_.message)
|
|
79
|
+
exit(1)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
command.setAnalyticsPayload({ projectType: settings.framework || 'custom', live: options.live, graph: options.graph })
|
|
83
|
+
|
|
84
|
+
log(`${NETLIFYDEVLOG} Building site for production`)
|
|
85
|
+
log(
|
|
86
|
+
`${NETLIFYDEVWARN} Changes will not be hot-reloaded, so if you need to rebuild your site you must exit and run 'netlify serve' again`,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
const { configPath: configPathOverride } = await runBuildTimeline({ cachedConfig, options, settings, site })
|
|
90
|
+
|
|
91
|
+
await startFunctionsServer({
|
|
92
|
+
api,
|
|
93
|
+
command,
|
|
94
|
+
config,
|
|
95
|
+
debug: options.debug,
|
|
96
|
+
loadDistFunctions: true,
|
|
97
|
+
settings,
|
|
98
|
+
site,
|
|
99
|
+
siteInfo,
|
|
100
|
+
siteUrl,
|
|
101
|
+
capabilities,
|
|
102
|
+
timeouts,
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
// Try to add `.netlify` to `.gitignore`.
|
|
106
|
+
try {
|
|
107
|
+
await ensureNetlifyIgnore(repositoryRoot)
|
|
108
|
+
} catch {
|
|
109
|
+
// no-op
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// TODO: We should consolidate this with the existing config watcher.
|
|
113
|
+
const getUpdatedConfig = async () => {
|
|
114
|
+
const cwd = options.cwd || process.cwd()
|
|
115
|
+
const { config: newConfig } = await command.getConfig({ cwd, offline: true, state })
|
|
116
|
+
const normalizedNewConfig = normalizeConfig(newConfig)
|
|
117
|
+
|
|
118
|
+
return normalizedNewConfig
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const inspectSettings = generateInspectSettings(options.edgeInspect, options.edgeInspectBrk)
|
|
122
|
+
const url = await startProxyServer({
|
|
123
|
+
addonsUrls,
|
|
124
|
+
config,
|
|
125
|
+
configPath: configPathOverride,
|
|
126
|
+
env,
|
|
127
|
+
geolocationMode: options.geo,
|
|
128
|
+
geoCountry: options.country,
|
|
129
|
+
getUpdatedConfig,
|
|
130
|
+
inspectSettings,
|
|
131
|
+
offline: options.offline,
|
|
132
|
+
settings,
|
|
133
|
+
site,
|
|
134
|
+
siteInfo,
|
|
135
|
+
state,
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
if (devConfig.autoLaunch !== false) {
|
|
139
|
+
await openBrowser({ url, silentBrowserNoneError: true })
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
process.env.URL = url
|
|
143
|
+
process.env.DEPLOY_URL = url
|
|
144
|
+
|
|
145
|
+
printBanner({ url })
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Creates the `netlify serve` command
|
|
150
|
+
* @param {import('../base-command.mjs').default} program
|
|
151
|
+
* @returns
|
|
152
|
+
*/
|
|
153
|
+
export const createServeCommand = (program) =>
|
|
154
|
+
program
|
|
155
|
+
.command('serve')
|
|
156
|
+
.description(
|
|
157
|
+
'(Beta) Build the site for production and serve locally. This does not watch the code for changes, so if you need to rebuild your site then you must exit and run `serve` again.',
|
|
158
|
+
)
|
|
159
|
+
.option(
|
|
160
|
+
'--context <context>',
|
|
161
|
+
'Specify a deploy context or branch for environment variables (contexts: "production", "deploy-preview", "branch-deploy", "dev")',
|
|
162
|
+
normalizeContext,
|
|
163
|
+
)
|
|
164
|
+
.option('-p ,--port <port>', 'port of netlify dev', (value) => Number.parseInt(value))
|
|
165
|
+
.option('-d ,--dir <path>', 'dir with static files')
|
|
166
|
+
.option('-f ,--functions <folder>', 'specify a functions folder to serve')
|
|
167
|
+
.option('-o ,--offline', 'disables any features that require network access')
|
|
168
|
+
.option('--functionsPort <port>', 'port of functions server', (value) => Number.parseInt(value))
|
|
169
|
+
.addOption(
|
|
170
|
+
new Option(
|
|
171
|
+
'--geo <mode>',
|
|
172
|
+
'force geolocation data to be updated, use cached data from the last 24h if found, or use a mock location',
|
|
173
|
+
)
|
|
174
|
+
.choices(['cache', 'mock', 'update'])
|
|
175
|
+
.default('cache'),
|
|
176
|
+
)
|
|
177
|
+
.addOption(
|
|
178
|
+
new Option(
|
|
179
|
+
'--country <geoCountry>',
|
|
180
|
+
'Two-letter country code (https://ntl.fyi/country-codes) to use as mock geolocation (enables --geo=mock automatically)',
|
|
181
|
+
).argParser(getGeoCountryArgParser('netlify dev --geo=mock --country=FR')),
|
|
182
|
+
)
|
|
183
|
+
.addOption(
|
|
184
|
+
new Option('--staticServerPort <port>', 'port of the static app server used when no framework is detected')
|
|
185
|
+
.argParser((value) => Number.parseInt(value))
|
|
186
|
+
.hideHelp(),
|
|
187
|
+
)
|
|
188
|
+
.addExamples(['netlify serve', 'BROWSER=none netlify serve # disable browser auto opening'])
|
|
189
|
+
.action(serve)
|
|
@@ -213,17 +213,14 @@ export class EdgeFunctionsRegistry {
|
|
|
213
213
|
if (
|
|
214
214
|
variable.sources.includes('ui') ||
|
|
215
215
|
variable.sources.includes('account') ||
|
|
216
|
-
variable.sources.includes('addons')
|
|
216
|
+
variable.sources.includes('addons') ||
|
|
217
|
+
variable.sources.includes('internal')
|
|
217
218
|
) {
|
|
218
219
|
env[key] = variable.value
|
|
219
220
|
}
|
|
220
221
|
})
|
|
221
222
|
|
|
222
223
|
env.DENO_REGION = 'local'
|
|
223
|
-
env.NETLIFY_DEV = 'true'
|
|
224
|
-
// We use it in the bootstrap layer to detect whether we're running in production or not
|
|
225
|
-
// (see https://github.com/netlify/edge-functions-bootstrap/blob/main/src/bootstrap/environment.ts#L2)
|
|
226
|
-
// env.DENO_DEPLOYMENT_ID = 'xxx='
|
|
227
224
|
|
|
228
225
|
return env
|
|
229
226
|
}
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import { mkdir } from 'fs/promises'
|
|
3
|
-
import { extname, isAbsolute, join } from 'path'
|
|
3
|
+
import { extname, isAbsolute, join, resolve } from 'path'
|
|
4
4
|
import { env } from 'process'
|
|
5
5
|
|
|
6
|
+
import extractZip from 'extract-zip'
|
|
7
|
+
|
|
6
8
|
import {
|
|
7
9
|
chalk,
|
|
8
10
|
getTerminalLink,
|
|
9
11
|
log,
|
|
10
12
|
NETLIFYDEVERR,
|
|
11
13
|
NETLIFYDEVLOG,
|
|
12
|
-
NETLIFYDEVWARN,
|
|
13
14
|
warn,
|
|
14
15
|
watchDebounced,
|
|
15
16
|
} from '../../utils/command-helpers.mjs'
|
|
17
|
+
import { SERVE_FUNCTIONS_FOLDER } from '../../utils/functions/functions.mjs'
|
|
16
18
|
import { getLogMessage } from '../log.mjs'
|
|
19
|
+
import { getPathInProject } from '../settings.mjs'
|
|
17
20
|
|
|
18
21
|
import NetlifyFunction from './netlify-function.mjs'
|
|
19
22
|
import runtimes from './runtimes/index.mjs'
|
|
@@ -21,9 +24,10 @@ import runtimes from './runtimes/index.mjs'
|
|
|
21
24
|
const ZIP_EXTENSION = '.zip'
|
|
22
25
|
|
|
23
26
|
export class FunctionsRegistry {
|
|
24
|
-
constructor({ capabilities, config, isConnected = false, projectRoot, settings, timeouts }) {
|
|
27
|
+
constructor({ capabilities, config, debug = false, isConnected = false, projectRoot, settings, timeouts }) {
|
|
25
28
|
this.capabilities = capabilities
|
|
26
29
|
this.config = config
|
|
30
|
+
this.debug = debug
|
|
27
31
|
this.isConnected = isConnected
|
|
28
32
|
this.projectRoot = projectRoot
|
|
29
33
|
this.timeouts = timeouts
|
|
@@ -123,7 +127,7 @@ export class FunctionsRegistry {
|
|
|
123
127
|
return this.functions.get(name)
|
|
124
128
|
}
|
|
125
129
|
|
|
126
|
-
registerFunction(name, funcBeforeHook) {
|
|
130
|
+
async registerFunction(name, funcBeforeHook) {
|
|
127
131
|
const { runtime } = funcBeforeHook
|
|
128
132
|
|
|
129
133
|
// The `onRegister` hook allows runtimes to modify the function before it's
|
|
@@ -145,11 +149,16 @@ export class FunctionsRegistry {
|
|
|
145
149
|
)
|
|
146
150
|
}
|
|
147
151
|
|
|
148
|
-
//
|
|
149
|
-
//
|
|
152
|
+
// If the function file is a ZIP, we extract it and rewire its main file to
|
|
153
|
+
// the new location.
|
|
150
154
|
if (extname(func.mainFile) === ZIP_EXTENSION) {
|
|
151
|
-
|
|
152
|
-
|
|
155
|
+
const unzippedDirectory = await this.unzipFunction(func)
|
|
156
|
+
|
|
157
|
+
if (this.debug) {
|
|
158
|
+
log(`${NETLIFYDEVLOG} ${chalk.green('Extracted')} function ${chalk.yellow(name)} from ${func.mainFile}.`)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
func.mainFile = join(unzippedDirectory, `${func.name}.js`)
|
|
153
162
|
}
|
|
154
163
|
|
|
155
164
|
this.functions.set(name, func)
|
|
@@ -197,34 +206,36 @@ export class FunctionsRegistry {
|
|
|
197
206
|
|
|
198
207
|
await Promise.all(deletedFunctions.map((func) => this.unregisterFunction(func.name)))
|
|
199
208
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
// If there is no matching runtime, it means this function is not yet
|
|
204
|
-
// supported in Netlify Dev.
|
|
205
|
-
if (runtime === undefined) {
|
|
206
|
-
return
|
|
207
|
-
}
|
|
209
|
+
await Promise.all(
|
|
210
|
+
functions.map(async ({ mainFile, name, runtime: runtimeName }) => {
|
|
211
|
+
const runtime = runtimes[runtimeName]
|
|
208
212
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
+
// If there is no matching runtime, it means this function is not yet
|
|
214
|
+
// supported in Netlify Dev.
|
|
215
|
+
if (runtime === undefined) {
|
|
216
|
+
return
|
|
217
|
+
}
|
|
213
218
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
name,
|
|
219
|
-
projectRoot: this.projectRoot,
|
|
220
|
-
runtime,
|
|
221
|
-
timeoutBackground: this.timeouts.backgroundFunctions,
|
|
222
|
-
timeoutSynchronous: this.timeouts.syncFunctions,
|
|
223
|
-
settings: this.settings,
|
|
224
|
-
})
|
|
219
|
+
// If this function has already been registered, we skip it.
|
|
220
|
+
if (this.functions.has(name)) {
|
|
221
|
+
return
|
|
222
|
+
}
|
|
225
223
|
|
|
226
|
-
|
|
227
|
-
|
|
224
|
+
const func = new NetlifyFunction({
|
|
225
|
+
config: this.config,
|
|
226
|
+
directory: directories.find((directory) => mainFile.startsWith(directory)),
|
|
227
|
+
mainFile,
|
|
228
|
+
name,
|
|
229
|
+
projectRoot: this.projectRoot,
|
|
230
|
+
runtime,
|
|
231
|
+
timeoutBackground: this.timeouts.backgroundFunctions,
|
|
232
|
+
timeoutSynchronous: this.timeouts.syncFunctions,
|
|
233
|
+
settings: this.settings,
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
await this.registerFunction(name, func)
|
|
237
|
+
}),
|
|
238
|
+
)
|
|
228
239
|
|
|
229
240
|
await Promise.all(directories.map((path) => this.setupDirectoryWatcher(path)))
|
|
230
241
|
}
|
|
@@ -261,4 +272,15 @@ export class FunctionsRegistry {
|
|
|
261
272
|
await watcher.close()
|
|
262
273
|
}
|
|
263
274
|
}
|
|
275
|
+
|
|
276
|
+
async unzipFunction(func) {
|
|
277
|
+
const targetDirectory = resolve(
|
|
278
|
+
this.projectRoot,
|
|
279
|
+
getPathInProject([SERVE_FUNCTIONS_FOLDER, '.unzipped', func.name]),
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
await extractZip(func.mainFile, { dir: targetDirectory })
|
|
283
|
+
|
|
284
|
+
return targetDirectory
|
|
285
|
+
}
|
|
264
286
|
}
|
|
@@ -7,6 +7,7 @@ import readPkgUp from 'read-pkg-up'
|
|
|
7
7
|
import sourceMapSupport from 'source-map-support'
|
|
8
8
|
|
|
9
9
|
import { NETLIFYDEVERR } from '../../../../../utils/command-helpers.mjs'
|
|
10
|
+
import { SERVE_FUNCTIONS_FOLDER } from '../../../../../utils/functions/functions.mjs'
|
|
10
11
|
import { getPathInProject } from '../../../../settings.mjs'
|
|
11
12
|
import { normalizeFunctionsConfig } from '../../../config.mjs'
|
|
12
13
|
import { memoizedBuild } from '../../../memoized-build.mjs'
|
|
@@ -94,7 +95,7 @@ const clearFunctionsCache = (functionsPath) => {
|
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
const getTargetDirectory = async ({ errorExit }) => {
|
|
97
|
-
const targetDirectory = path.resolve(getPathInProject([
|
|
98
|
+
const targetDirectory = path.resolve(getPathInProject([SERVE_FUNCTIONS_FOLDER]))
|
|
98
99
|
|
|
99
100
|
try {
|
|
100
101
|
await mkdir(targetDirectory, { recursive: true })
|
|
@@ -7,6 +7,7 @@ import findUp from 'find-up'
|
|
|
7
7
|
import toml from 'toml'
|
|
8
8
|
|
|
9
9
|
import execa from '../../../../utils/execa.mjs'
|
|
10
|
+
import { SERVE_FUNCTIONS_FOLDER } from '../../../../utils/functions/functions.mjs'
|
|
10
11
|
import { getPathInProject } from '../../../settings.mjs'
|
|
11
12
|
import { runFunctionsProxy } from '../../local-proxy.mjs'
|
|
12
13
|
|
|
@@ -16,7 +17,7 @@ export const name = 'rs'
|
|
|
16
17
|
|
|
17
18
|
const build = async ({ func }) => {
|
|
18
19
|
const functionDirectory = dirname(func.mainFile)
|
|
19
|
-
const cacheDirectory = resolve(getPathInProject([
|
|
20
|
+
const cacheDirectory = resolve(getPathInProject([SERVE_FUNCTIONS_FOLDER]))
|
|
20
21
|
const targetDirectory = join(cacheDirectory, func.name)
|
|
21
22
|
const crateName = await getCrateName(functionDirectory)
|
|
22
23
|
const binaryName = `${crateName}${isWindows ? '.exe' : ''}`
|
|
@@ -4,7 +4,7 @@ import jwtDecode from 'jwt-decode'
|
|
|
4
4
|
|
|
5
5
|
import { NETLIFYDEVERR, NETLIFYDEVLOG, error as errorExit, log } from '../../utils/command-helpers.mjs'
|
|
6
6
|
import { generateNetlifyGraphJWT } from '../../utils/dev.mjs'
|
|
7
|
-
import { CLOCKWORK_USERAGENT, getInternalFunctionsDir } from '../../utils/functions/index.mjs'
|
|
7
|
+
import { CLOCKWORK_USERAGENT, getFunctionsDistPath, getInternalFunctionsDir } from '../../utils/functions/index.mjs'
|
|
8
8
|
|
|
9
9
|
import { handleBackgroundFunction, handleBackgroundFunctionResult } from './background.mjs'
|
|
10
10
|
import { createFormSubmissionHandler } from './form-submissions-handler.mjs'
|
|
@@ -208,29 +208,46 @@ const getFunctionsServer = async function (options) {
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
export const startFunctionsServer = async (options) => {
|
|
211
|
-
const { capabilities, config, settings, site, siteUrl, timeouts } = options
|
|
211
|
+
const { capabilities, config, debug, loadDistFunctions, settings, site, siteUrl, timeouts } = options
|
|
212
212
|
const internalFunctionsDir = await getInternalFunctionsDir({ base: site.root })
|
|
213
|
+
const functionsDirectories = []
|
|
213
214
|
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const functionsRegistry = new FunctionsRegistry({
|
|
220
|
-
capabilities,
|
|
221
|
-
config,
|
|
222
|
-
isConnected: Boolean(siteUrl),
|
|
223
|
-
projectRoot: site.root,
|
|
224
|
-
settings,
|
|
225
|
-
timeouts,
|
|
226
|
-
})
|
|
215
|
+
// If the `loadDistFunctions` parameter is sent, the functions server will
|
|
216
|
+
// use the built functions created by zip-it-and-ship-it rather than building
|
|
217
|
+
// them from source.
|
|
218
|
+
if (loadDistFunctions) {
|
|
219
|
+
const distPath = await getFunctionsDistPath({ base: site.root })
|
|
227
220
|
|
|
228
|
-
|
|
221
|
+
if (distPath) {
|
|
222
|
+
functionsDirectories.push(distPath)
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
// The order of the function directories matters. Leftmost directories take
|
|
226
|
+
// precedence.
|
|
227
|
+
const sourceDirectories = [settings.functions, internalFunctionsDir].filter(Boolean)
|
|
229
228
|
|
|
230
|
-
|
|
229
|
+
functionsDirectories.push(...sourceDirectories)
|
|
230
|
+
}
|
|
231
231
|
|
|
232
|
-
|
|
232
|
+
if (functionsDirectories.length === 0) {
|
|
233
|
+
return
|
|
233
234
|
}
|
|
235
|
+
|
|
236
|
+
const functionsRegistry = new FunctionsRegistry({
|
|
237
|
+
capabilities,
|
|
238
|
+
config,
|
|
239
|
+
debug,
|
|
240
|
+
isConnected: Boolean(siteUrl),
|
|
241
|
+
projectRoot: site.root,
|
|
242
|
+
settings,
|
|
243
|
+
timeouts,
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
await functionsRegistry.scan(functionsDirectories)
|
|
247
|
+
|
|
248
|
+
const server = await getFunctionsServer(Object.assign(options, { functionsRegistry }))
|
|
249
|
+
|
|
250
|
+
await startWebServer({ server, settings })
|
|
234
251
|
}
|
|
235
252
|
|
|
236
253
|
const startWebServer = async ({ server, settings }) => {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import boxen from 'boxen'
|
|
3
|
+
|
|
4
|
+
import { chalk, log, NETLIFYDEVLOG } from './command-helpers.mjs'
|
|
5
|
+
|
|
6
|
+
export const printBanner = ({ url }) => {
|
|
7
|
+
const banner = chalk.bold(`${NETLIFYDEVLOG} Server now ready on ${url}`)
|
|
8
|
+
|
|
9
|
+
log(
|
|
10
|
+
boxen(banner, {
|
|
11
|
+
padding: 1,
|
|
12
|
+
margin: 1,
|
|
13
|
+
align: 'center',
|
|
14
|
+
borderColor: '#00c7b7',
|
|
15
|
+
}),
|
|
16
|
+
)
|
|
17
|
+
}
|
|
@@ -11,7 +11,7 @@ import isPlainObject from 'is-plain-obj'
|
|
|
11
11
|
|
|
12
12
|
import { NETLIFYDEVWARN, chalk, log } from './command-helpers.mjs'
|
|
13
13
|
import { acquirePort } from './dev.mjs'
|
|
14
|
-
import { getInternalFunctionsDir } from './functions/
|
|
14
|
+
import { getInternalFunctionsDir } from './functions/functions.mjs'
|
|
15
15
|
|
|
16
16
|
const formatProperty = (str) => chalk.magenta(`'${str}'`)
|
|
17
17
|
const formatValue = (str) => chalk.green(`'${str}'`)
|
|
@@ -364,4 +364,38 @@ const formatSettingsArrForInquirer = function (frameworks) {
|
|
|
364
364
|
return formattedArr.flat()
|
|
365
365
|
}
|
|
366
366
|
|
|
367
|
+
/**
|
|
368
|
+
* Returns a copy of the provided config with any plugins provided by the
|
|
369
|
+
* server settings
|
|
370
|
+
* @param {*} config
|
|
371
|
+
* @param {Partial<import('./types').ServerSettings>} settings
|
|
372
|
+
* @returns {*} Modified config
|
|
373
|
+
*/
|
|
374
|
+
export const getConfigWithPlugins = (config, settings) => {
|
|
375
|
+
if (!settings.plugins) {
|
|
376
|
+
return config
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// If there are plugins that we should be running for this site, add them
|
|
380
|
+
// to the config as if they were declared in netlify.toml. We must check
|
|
381
|
+
// whether the plugin has already been added by another source (like the
|
|
382
|
+
// TOML file or the UI), as we don't want to run the same plugin twice.
|
|
383
|
+
const { plugins: existingPlugins = [] } = config
|
|
384
|
+
const existingPluginNames = new Set(existingPlugins.map((plugin) => plugin.package))
|
|
385
|
+
const newPlugins = settings.plugins
|
|
386
|
+
.map((pluginName) => {
|
|
387
|
+
if (existingPluginNames.has(pluginName)) {
|
|
388
|
+
return
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return { package: pluginName, origin: 'config', inputs: {} }
|
|
392
|
+
})
|
|
393
|
+
.filter(Boolean)
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
...config,
|
|
397
|
+
plugins: [...newPlugins, ...config.plugins],
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
367
401
|
export default detectServerSettings
|
package/src/utils/dev.mjs
CHANGED
|
@@ -148,6 +148,10 @@ export const injectEnvVariables = async ({ devConfig, env, site }) => {
|
|
|
148
148
|
const newSourceName = `${file} file`
|
|
149
149
|
const sources = environment.has(key) ? [newSourceName, ...environment.get(key).sources] : [newSourceName]
|
|
150
150
|
|
|
151
|
+
if (sources.includes('internal')) {
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
|
|
151
155
|
environment.set(key, {
|
|
152
156
|
sources,
|
|
153
157
|
value: fileEnv[key],
|
|
@@ -160,6 +164,7 @@ export const injectEnvVariables = async ({ devConfig, env, site }) => {
|
|
|
160
164
|
const existsInProcess = process.env[key] !== undefined
|
|
161
165
|
const [usedSource, ...overriddenSources] = existsInProcess ? ['process', ...variable.sources] : variable.sources
|
|
162
166
|
const usedSourceName = getEnvSourceName(usedSource)
|
|
167
|
+
const isInternal = variable.sources.includes('internal')
|
|
163
168
|
|
|
164
169
|
overriddenSources.forEach((source) => {
|
|
165
170
|
const sourceName = getEnvSourceName(source)
|
|
@@ -173,17 +178,15 @@ export const injectEnvVariables = async ({ devConfig, env, site }) => {
|
|
|
173
178
|
)
|
|
174
179
|
})
|
|
175
180
|
|
|
176
|
-
if (!existsInProcess) {
|
|
177
|
-
// Omitting `general` env vars to reduce noise in the logs.
|
|
178
|
-
if (usedSource !== 'general') {
|
|
181
|
+
if (!existsInProcess || isInternal) {
|
|
182
|
+
// Omitting `general` and `internal` env vars to reduce noise in the logs.
|
|
183
|
+
if (usedSource !== 'general' && !isInternal) {
|
|
179
184
|
log(`${NETLIFYDEVLOG} Injected ${usedSourceName} env var: ${chalk.yellow(key)}`)
|
|
180
185
|
}
|
|
181
186
|
|
|
182
187
|
process.env[key] = variable.value
|
|
183
188
|
}
|
|
184
189
|
}
|
|
185
|
-
|
|
186
|
-
process.env.NETLIFY_DEV = 'true'
|
|
187
190
|
}
|
|
188
191
|
|
|
189
192
|
export const acquirePort = async ({ configuredPort, defaultPort, errorMessage }) => {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import waitPort from 'wait-port'
|
|
3
|
+
|
|
4
|
+
import { startSpinner, stopSpinner } from '../lib/spinner.mjs'
|
|
5
|
+
|
|
6
|
+
import { error, exit, log, NETLIFYDEVERR, NETLIFYDEVLOG } from './command-helpers.mjs'
|
|
7
|
+
import { runCommand } from './shell.mjs'
|
|
8
|
+
import { startStaticServer } from './static-server.mjs'
|
|
9
|
+
|
|
10
|
+
// 10 minutes
|
|
11
|
+
const FRAMEWORK_PORT_TIMEOUT = 6e5
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef StartReturnObject
|
|
15
|
+
* @property {4 | 6 | undefined=} ipVersion The version the open port was found on
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Start a static server if the `useStaticServer` is provided or a framework specific server
|
|
20
|
+
* @param {object} config
|
|
21
|
+
* @param {Partial<import('./types').ServerSettings>} config.settings
|
|
22
|
+
* @returns {Promise<StartReturnObject>}
|
|
23
|
+
*/
|
|
24
|
+
export const startFrameworkServer = async function ({ settings }) {
|
|
25
|
+
if (settings.useStaticServer) {
|
|
26
|
+
if (settings.command) {
|
|
27
|
+
runCommand(settings.command, settings.env)
|
|
28
|
+
}
|
|
29
|
+
await startStaticServer({ settings })
|
|
30
|
+
|
|
31
|
+
return {}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
log(`${NETLIFYDEVLOG} Starting Netlify Dev with ${settings.framework || 'custom config'}`)
|
|
35
|
+
|
|
36
|
+
const spinner = startSpinner({
|
|
37
|
+
text: `Waiting for framework port ${settings.frameworkPort}. This can be configured using the 'targetPort' property in the netlify.toml`,
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
runCommand(settings.command, settings.env, spinner)
|
|
41
|
+
|
|
42
|
+
let port
|
|
43
|
+
try {
|
|
44
|
+
port = await waitPort({
|
|
45
|
+
port: settings.frameworkPort,
|
|
46
|
+
host: 'localhost',
|
|
47
|
+
output: 'silent',
|
|
48
|
+
timeout: FRAMEWORK_PORT_TIMEOUT,
|
|
49
|
+
...(settings.pollingStrategies.includes('HTTP') && { protocol: 'http' }),
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
if (!port.open) {
|
|
53
|
+
throw new Error(`Timed out waiting for port '${settings.frameworkPort}' to be open`)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
stopSpinner({ error: false, spinner })
|
|
57
|
+
} catch (error_) {
|
|
58
|
+
stopSpinner({ error: true, spinner })
|
|
59
|
+
log(NETLIFYDEVERR, `Netlify Dev could not start or connect to localhost:${settings.frameworkPort}.`)
|
|
60
|
+
log(NETLIFYDEVERR, `Please make sure your framework server is running on port ${settings.frameworkPort}`)
|
|
61
|
+
error(error_)
|
|
62
|
+
exit(1)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return { ipVersion: port?.ipVersion }
|
|
66
|
+
}
|