netlify-cli 9.16.7 → 10.1.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 +10 -1
- package/npm-shrinkwrap.json +671 -3362
- package/package.json +8 -5
- package/src/commands/base-command.js +1 -2
- package/src/commands/deploy/deploy.js +0 -7
- package/src/commands/dev/dev.js +65 -52
- package/src/commands/main.js +3 -0
- package/src/commands/recipes/common.js +33 -0
- package/src/commands/recipes/index.js +8 -0
- package/src/commands/recipes/recipes-list.js +34 -0
- package/src/commands/recipes/recipes.js +85 -0
- package/src/lib/api.js +1 -72
- package/src/lib/edge-functions/consts.js +21 -0
- package/src/lib/edge-functions/deploy.js +41 -0
- package/src/lib/edge-functions/editor-helper.js +41 -0
- package/src/lib/edge-functions/headers.js +7 -0
- package/src/lib/edge-functions/index.js +7 -0
- package/src/lib/edge-functions/internal.js +75 -0
- package/src/lib/edge-functions/proxy.js +150 -0
- package/src/lib/edge-functions/registry.js +354 -0
- package/src/lib/functions/registry.js +1 -1
- package/src/lib/geo-location.js +99 -0
- package/src/recipes/vscode/index.js +61 -0
- package/src/recipes/vscode/settings.js +66 -0
- package/src/utils/command-helpers.js +10 -0
- package/src/utils/deploy/deploy-site.js +26 -4
- package/src/utils/deploy/hash-files.js +12 -7
- package/src/utils/deploy/hasher-segments.js +10 -2
- package/src/utils/functions/index.js +3 -2
- package/src/utils/index.js +0 -2
- package/src/utils/proxy.js +99 -12
- package/src/commands/dev/dev-trace.js +0 -47
- package/src/utils/functions/edge-handlers.js +0 -88
- package/src/utils/traffic-mesh.js +0 -219
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "netlify-cli",
|
|
3
3
|
"description": "Netlify command line tool",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "10.1.0",
|
|
5
5
|
"author": "Netlify Inc.",
|
|
6
6
|
"contributors": [
|
|
7
7
|
"Abraham Schilling <AbrahamSchilling@gmail.com> (https://gitlab.com/n4bb12)",
|
|
@@ -103,6 +103,7 @@
|
|
|
103
103
|
"Sam Holmes <samholmes1337@gmail.com> (https://samholmes.net)",
|
|
104
104
|
"Sander de Groot (https://degroot.dev)",
|
|
105
105
|
"Sarah Drasner <sarah.drasner@gmail.com> (https://twitter.com/sarah_edo)",
|
|
106
|
+
"Sarah Etter <sarah@sarahetter.com> (http://www.sarahetter.com)",
|
|
106
107
|
"Scott Spence <spences10apps@gmail.com> (https://twitter.com/spences10)",
|
|
107
108
|
"Sean Grove <sean@bushi.do> (https://twitter.com/sgrove)",
|
|
108
109
|
"Sebastian Smolorz",
|
|
@@ -206,13 +207,12 @@
|
|
|
206
207
|
"prettier": "--ignore-path .gitignore --loglevel=warn \"{src,tools,scripts,site,tests,.github}/**/*.{mjs,cjs,js,md,yml,json,html}\" \"*.{mjs,cjs,js,yml,json,html}\" \".*.{mjs,cjs,js,yml,json,html}\" \"!CHANGELOG.md\" \"!npm-shrinkwrap.json\" \"!**/*/package-lock.json\" \"!.github/**/*.md\""
|
|
207
208
|
},
|
|
208
209
|
"dependencies": {
|
|
209
|
-
"@netlify/build": "^
|
|
210
|
-
"@netlify/config": "^
|
|
210
|
+
"@netlify/build": "^27.0.1",
|
|
211
|
+
"@netlify/config": "^18.0.0",
|
|
212
|
+
"@netlify/edge-bundler": "^0.12.0",
|
|
211
213
|
"@netlify/framework-info": "^9.0.2",
|
|
212
214
|
"@netlify/local-functions-proxy": "^1.1.1",
|
|
213
|
-
"@netlify/plugin-edge-handlers": "^3.0.7",
|
|
214
215
|
"@netlify/plugins-list": "^6.19.0",
|
|
215
|
-
"@netlify/routing-local-proxy": "^0.34.1",
|
|
216
216
|
"@netlify/zip-it-and-ship-it": "^5.9.0",
|
|
217
217
|
"@octokit/rest": "^18.0.0",
|
|
218
218
|
"@sindresorhus/slugify": "^1.1.0",
|
|
@@ -241,6 +241,7 @@
|
|
|
241
241
|
"dotenv": "^16.0.0",
|
|
242
242
|
"env-paths": "^2.2.0",
|
|
243
243
|
"envinfo": "^7.3.1",
|
|
244
|
+
"etag": "^1.8.1",
|
|
244
245
|
"execa": "^5.0.0",
|
|
245
246
|
"express": "^4.17.1",
|
|
246
247
|
"express-logging": "^1.1.1",
|
|
@@ -310,6 +311,7 @@
|
|
|
310
311
|
"through2-map": "^3.0.0",
|
|
311
312
|
"to-readable-stream": "^2.1.0",
|
|
312
313
|
"toml": "^3.0.0",
|
|
314
|
+
"unixify": "^1.0.0",
|
|
313
315
|
"update-notifier": "^5.0.0",
|
|
314
316
|
"uuid": "^8.0.0",
|
|
315
317
|
"wait-port": "^0.2.2",
|
|
@@ -329,6 +331,7 @@
|
|
|
329
331
|
"husky": "^7.0.4",
|
|
330
332
|
"ini": "^2.0.0",
|
|
331
333
|
"mock-fs": "^5.1.2",
|
|
334
|
+
"nock": "^13.2.4",
|
|
332
335
|
"p-timeout": "^4.0.0",
|
|
333
336
|
"rewiremock": "^3.14.3",
|
|
334
337
|
"seedrandom": "^3.0.5",
|
|
@@ -421,7 +421,6 @@ class BaseCommand extends Command {
|
|
|
421
421
|
const cachedConfig = await actionCommand.getConfig({ cwd, state, token, ...apiUrlOpts })
|
|
422
422
|
const { buildDir, config, configPath, repositoryRoot, siteInfo } = cachedConfig
|
|
423
423
|
const normalizedConfig = normalizeConfig(config)
|
|
424
|
-
|
|
425
424
|
const agent = await getAgent({
|
|
426
425
|
httpProxy: options.httpProxy,
|
|
427
426
|
certificateFile: options.httpProxyCertificateFilename,
|
|
@@ -478,7 +477,7 @@ class BaseCommand extends Command {
|
|
|
478
477
|
options.context ||
|
|
479
478
|
process.env.CONTEXT ||
|
|
480
479
|
// Dev commands have a default context of `dev`, otherwise we let netlify/config handle default behavior
|
|
481
|
-
(['dev', 'dev:exec'
|
|
480
|
+
(['dev', 'dev:exec'].includes(this.name()) ? 'dev' : undefined),
|
|
482
481
|
debug: this.opts().debug,
|
|
483
482
|
siteId: options.siteId || (typeof options.site === 'string' && options.site) || state.get('siteId'),
|
|
484
483
|
token,
|
|
@@ -20,7 +20,6 @@ const {
|
|
|
20
20
|
NETLIFYDEVERR,
|
|
21
21
|
NETLIFYDEVLOG,
|
|
22
22
|
chalk,
|
|
23
|
-
deployEdgeHandlers,
|
|
24
23
|
deploySite,
|
|
25
24
|
error,
|
|
26
25
|
exit,
|
|
@@ -314,12 +313,6 @@ const runDeploy = async ({
|
|
|
314
313
|
results = await api.createSiteDeploy({ siteId, title, body: { draft, branch: alias } })
|
|
315
314
|
deployId = results.id
|
|
316
315
|
|
|
317
|
-
await deployEdgeHandlers({
|
|
318
|
-
site,
|
|
319
|
-
deployId,
|
|
320
|
-
api,
|
|
321
|
-
silent,
|
|
322
|
-
})
|
|
323
316
|
const internalFunctionsFolder = await getInternalFunctionsDir({ base: site.root })
|
|
324
317
|
|
|
325
318
|
// The order of the directories matter: zip-it-and-ship-it will prioritize
|
package/src/commands/dev/dev.js
CHANGED
|
@@ -11,6 +11,7 @@ const StaticServer = require('static-server')
|
|
|
11
11
|
const stripAnsiCc = require('strip-ansi-control-characters')
|
|
12
12
|
const waitPort = require('wait-port')
|
|
13
13
|
|
|
14
|
+
const { promptEditorHelper } = require('../../lib/edge-functions')
|
|
14
15
|
const { startFunctionsServer } = require('../../lib/functions/server')
|
|
15
16
|
const {
|
|
16
17
|
OneGraphCliClient,
|
|
@@ -42,7 +43,6 @@ const {
|
|
|
42
43
|
normalizeConfig,
|
|
43
44
|
openBrowser,
|
|
44
45
|
processOnExit,
|
|
45
|
-
startForwardProxy,
|
|
46
46
|
startLiveTunnel,
|
|
47
47
|
startProxy,
|
|
48
48
|
warn,
|
|
@@ -50,7 +50,6 @@ const {
|
|
|
50
50
|
} = require('../../utils')
|
|
51
51
|
|
|
52
52
|
const { createDevExecCommand } = require('./dev-exec')
|
|
53
|
-
const { createDevTraceCommand } = require('./dev-trace')
|
|
54
53
|
|
|
55
54
|
const startStaticServer = async ({ settings }) => {
|
|
56
55
|
const server = new StaticServer({
|
|
@@ -198,37 +197,44 @@ const FRAMEWORK_PORT_TIMEOUT = 6e5
|
|
|
198
197
|
|
|
199
198
|
/**
|
|
200
199
|
*
|
|
201
|
-
* @param {object}
|
|
202
|
-
* @param {*}
|
|
203
|
-
* @param {import('
|
|
204
|
-
* @param {
|
|
205
|
-
* @param {
|
|
200
|
+
* @param {object} params
|
|
201
|
+
* @param {*} params.addonsUrls
|
|
202
|
+
* @param {import('../base-command').NetlifyOptions["config"]} params.config
|
|
203
|
+
* @param {() => Promise<object>} params.getUpdatedConfig
|
|
204
|
+
* @param {string} params.geolocationMode
|
|
205
|
+
* @param {*} params.settings
|
|
206
|
+
* @param {boolean} params.offline
|
|
207
|
+
* @param {*} params.site
|
|
208
|
+
* @param {import('../../utils/state-config').StateConfig} params.state
|
|
206
209
|
* @returns
|
|
207
210
|
*/
|
|
208
|
-
const startProxyServer = async ({
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
211
|
+
const startProxyServer = async ({
|
|
212
|
+
addonsUrls,
|
|
213
|
+
config,
|
|
214
|
+
geolocationMode,
|
|
215
|
+
getUpdatedConfig,
|
|
216
|
+
offline,
|
|
217
|
+
settings,
|
|
218
|
+
site,
|
|
219
|
+
state,
|
|
220
|
+
}) => {
|
|
221
|
+
const url = await startProxy({
|
|
222
|
+
addonsUrls,
|
|
223
|
+
config,
|
|
224
|
+
configPath: site.configPath,
|
|
225
|
+
geolocationMode,
|
|
226
|
+
getUpdatedConfig,
|
|
227
|
+
offline,
|
|
228
|
+
projectDir: site.root,
|
|
229
|
+
settings,
|
|
230
|
+
state,
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
if (!url) {
|
|
234
|
+
log(NETLIFYDEVERR, `Unable to start proxy server on port '${settings.port}'`)
|
|
235
|
+
exit(1)
|
|
231
236
|
}
|
|
237
|
+
|
|
232
238
|
return url
|
|
233
239
|
}
|
|
234
240
|
|
|
@@ -313,7 +319,7 @@ const startPollingForAPIAuthentication = async function (options) {
|
|
|
313
319
|
*/
|
|
314
320
|
const dev = async (options, command) => {
|
|
315
321
|
log(`${NETLIFYDEV}`)
|
|
316
|
-
const { api, config, site, siteInfo, state } = command.netlify
|
|
322
|
+
const { api, config, repositoryRoot, site, siteInfo, state } = command.netlify
|
|
317
323
|
config.dev = { ...config.dev }
|
|
318
324
|
config.build = { ...config.build }
|
|
319
325
|
/** @type {import('./types').DevConfig} */
|
|
@@ -325,13 +331,8 @@ const dev = async (options, command) => {
|
|
|
325
331
|
...options,
|
|
326
332
|
}
|
|
327
333
|
|
|
328
|
-
if (options.trafficMesh) {
|
|
329
|
-
warn(
|
|
330
|
-
'--trafficMesh and -t are deprecated and will be removed in the near future. Please use --edgeHandlers or -e instead.',
|
|
331
|
-
)
|
|
332
|
-
}
|
|
333
|
-
|
|
334
334
|
await injectEnvVariables({ devConfig, env: command.netlify.cachedConfig.env, site })
|
|
335
|
+
await promptEditorHelper({ chalk, config, log, NETLIFYDEVLOG, repositoryRoot, state })
|
|
335
336
|
|
|
336
337
|
const { addonsUrls, capabilities, siteUrl, timeouts } = await getSiteInformation({
|
|
337
338
|
// inherited from base command --offline
|
|
@@ -370,7 +371,25 @@ const dev = async (options, command) => {
|
|
|
370
371
|
})
|
|
371
372
|
await startFrameworkServer({ settings })
|
|
372
373
|
|
|
373
|
-
|
|
374
|
+
// TODO: We should consolidate this with the existing config watcher.
|
|
375
|
+
const getUpdatedConfig = async () => {
|
|
376
|
+
const cwd = options.cwd || process.cwd()
|
|
377
|
+
const { config: newConfig } = await command.getConfig({ cwd, offline: true, state })
|
|
378
|
+
const normalizedNewConfig = normalizeConfig(newConfig)
|
|
379
|
+
|
|
380
|
+
return normalizedNewConfig
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
let url = await startProxyServer({
|
|
384
|
+
addonsUrls,
|
|
385
|
+
config,
|
|
386
|
+
geolocationMode: options.geo,
|
|
387
|
+
getUpdatedConfig,
|
|
388
|
+
offline: options.offline,
|
|
389
|
+
settings,
|
|
390
|
+
site,
|
|
391
|
+
state,
|
|
392
|
+
})
|
|
374
393
|
|
|
375
394
|
const liveTunnelUrl = await handleLiveTunnel({ options, site, api, settings })
|
|
376
395
|
url = liveTunnelUrl || url
|
|
@@ -474,7 +493,6 @@ const dev = async (options, command) => {
|
|
|
474
493
|
*/
|
|
475
494
|
const createDevCommand = (program) => {
|
|
476
495
|
createDevExecCommand(program)
|
|
477
|
-
createDevTraceCommand(program)
|
|
478
496
|
|
|
479
497
|
return program
|
|
480
498
|
.command('dev')
|
|
@@ -490,23 +508,18 @@ const createDevCommand = (program) => {
|
|
|
490
508
|
.option('-o ,--offline', 'disables any features that require network access')
|
|
491
509
|
.option('-l, --live', 'start a public live session', false)
|
|
492
510
|
.option('--functionsPort <port>', 'port of functions server', (value) => Number.parseInt(value))
|
|
493
|
-
.addOption(
|
|
494
|
-
new Option('--staticServerPort <port>', 'port of the static app server used when no framework is detected')
|
|
495
|
-
.argParser((value) => Number.parseInt(value))
|
|
496
|
-
.hideHelp(),
|
|
497
|
-
)
|
|
498
|
-
.addOption(new Option('-e ,--edgeHandlers', 'activates the Edge Handlers runtime').hideHelp())
|
|
499
511
|
.addOption(
|
|
500
512
|
new Option(
|
|
501
|
-
'
|
|
502
|
-
'
|
|
503
|
-
)
|
|
513
|
+
'--geo <mode>',
|
|
514
|
+
'force geolocation data to be updated, use cached data from the last 24h if found, or use a mock location',
|
|
515
|
+
)
|
|
516
|
+
.choices(['cache', 'mock', 'update'])
|
|
517
|
+
.default('cache'),
|
|
504
518
|
)
|
|
505
519
|
.addOption(
|
|
506
|
-
new Option(
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
).hideHelp(),
|
|
520
|
+
new Option('--staticServerPort <port>', 'port of the static app server used when no framework is detected')
|
|
521
|
+
.argParser((value) => Number.parseInt(value))
|
|
522
|
+
.hideHelp(),
|
|
510
523
|
)
|
|
511
524
|
.addOption(new Option('--graph', 'enable Netlify Graph support').hideHelp())
|
|
512
525
|
.addExamples([
|
package/src/commands/main.js
CHANGED
|
@@ -36,6 +36,7 @@ const { createLmCommand } = require('./lm')
|
|
|
36
36
|
const { createLoginCommand } = require('./login')
|
|
37
37
|
const { createLogoutCommand } = require('./logout')
|
|
38
38
|
const { createOpenCommand } = require('./open')
|
|
39
|
+
const { createRecipesCommand, createRecipesListCommand } = require('./recipes')
|
|
39
40
|
const { createSitesCommand } = require('./sites')
|
|
40
41
|
const { createStatusCommand } = require('./status')
|
|
41
42
|
const { createSwitchCommand } = require('./switch')
|
|
@@ -171,6 +172,8 @@ const createMainCommand = () => {
|
|
|
171
172
|
createDevCommand(program)
|
|
172
173
|
createEnvCommand(program)
|
|
173
174
|
createFunctionsCommand(program)
|
|
175
|
+
createRecipesCommand(program)
|
|
176
|
+
createRecipesListCommand(program)
|
|
174
177
|
createGraphCommand(program)
|
|
175
178
|
createInitCommand(program)
|
|
176
179
|
createLinkCommand(program)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const { promises: fs } = require('fs')
|
|
2
|
+
const { join, resolve } = require('path')
|
|
3
|
+
|
|
4
|
+
const getRecipe = (name) => {
|
|
5
|
+
const recipePath = resolve(__dirname, '../../recipes', name)
|
|
6
|
+
|
|
7
|
+
// eslint-disable-next-line import/no-dynamic-require, n/global-require
|
|
8
|
+
const recipe = require(recipePath)
|
|
9
|
+
|
|
10
|
+
return recipe
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const listRecipes = async () => {
|
|
14
|
+
const recipesPath = resolve(__dirname, '../../recipes')
|
|
15
|
+
const recipeNames = await fs.readdir(recipesPath)
|
|
16
|
+
const recipes = await Promise.all(
|
|
17
|
+
recipeNames.map((name) => {
|
|
18
|
+
const recipePath = join(recipesPath, name)
|
|
19
|
+
|
|
20
|
+
// eslint-disable-next-line import/no-dynamic-require, n/global-require
|
|
21
|
+
const recipe = require(recipePath)
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
...recipe,
|
|
25
|
+
name,
|
|
26
|
+
}
|
|
27
|
+
}),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
return recipes
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = { getRecipe, listRecipes }
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
const AsciiTable = require('ascii-table')
|
|
3
|
+
|
|
4
|
+
const { listRecipes } = require('./common')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The recipes:list command
|
|
8
|
+
*/
|
|
9
|
+
const recipesListCommand = async () => {
|
|
10
|
+
const recipes = await listRecipes()
|
|
11
|
+
const table = new AsciiTable(`Usage: netlify recipes <name>`)
|
|
12
|
+
|
|
13
|
+
table.setHeading('Name', 'Description')
|
|
14
|
+
|
|
15
|
+
recipes.forEach(({ description, name }) => {
|
|
16
|
+
table.addRow(name, description)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
console.log(table.toString())
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Creates the `netlify recipes:list` command
|
|
24
|
+
* @param {import('../base-command').BaseCommand} program
|
|
25
|
+
* @returns
|
|
26
|
+
*/
|
|
27
|
+
const createRecipesListCommand = (program) =>
|
|
28
|
+
program
|
|
29
|
+
.command('recipes:list')
|
|
30
|
+
.description(`(Beta) List the recipes available to create and modify files in a project`)
|
|
31
|
+
.addExamples(['netlify recipes:list'])
|
|
32
|
+
.action(recipesListCommand)
|
|
33
|
+
|
|
34
|
+
module.exports = { createRecipesListCommand }
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
const { basename } = require('path')
|
|
3
|
+
|
|
4
|
+
const inquirer = require('inquirer')
|
|
5
|
+
const { findBestMatch } = require('string-similarity')
|
|
6
|
+
|
|
7
|
+
const utils = require('../../utils/command-helpers')
|
|
8
|
+
|
|
9
|
+
const { getRecipe, listRecipes } = require('./common')
|
|
10
|
+
|
|
11
|
+
const SUGGESTION_TIMEOUT = 1e4
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The recipes command
|
|
15
|
+
* @param {string} recipeName
|
|
16
|
+
* @param {import('commander').OptionValues} options
|
|
17
|
+
* @param {import('../base-command').BaseCommand} command
|
|
18
|
+
*/
|
|
19
|
+
const recipesCommand = async (recipeName, options, command) => {
|
|
20
|
+
const { config, repositoryRoot } = command.netlify
|
|
21
|
+
const sanitizedRecipeName = basename(recipeName || '').toLowerCase()
|
|
22
|
+
|
|
23
|
+
if (sanitizedRecipeName.length === 0) {
|
|
24
|
+
return command.help()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
return await runRecipe({ config, recipeName: sanitizedRecipeName, repositoryRoot })
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if (error.code !== 'MODULE_NOT_FOUND') {
|
|
31
|
+
throw error
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
utils.log(`${utils.NETLIFYDEVERR} ${utils.chalk.yellow(recipeName)} is not a valid recipe name.`)
|
|
35
|
+
|
|
36
|
+
const recipes = await listRecipes()
|
|
37
|
+
const recipeNames = recipes.map(({ name }) => name)
|
|
38
|
+
const {
|
|
39
|
+
bestMatch: { target: suggestion },
|
|
40
|
+
} = findBestMatch(recipeName, recipeNames)
|
|
41
|
+
const applySuggestion = await new Promise((resolve) => {
|
|
42
|
+
const prompt = inquirer.prompt({
|
|
43
|
+
type: 'confirm',
|
|
44
|
+
name: 'suggestion',
|
|
45
|
+
message: `Did you mean ${utils.chalk.blue(suggestion)}`,
|
|
46
|
+
default: false,
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
setTimeout(() => {
|
|
50
|
+
// @ts-ignore
|
|
51
|
+
prompt.ui.close()
|
|
52
|
+
resolve(false)
|
|
53
|
+
}, SUGGESTION_TIMEOUT)
|
|
54
|
+
|
|
55
|
+
// eslint-disable-next-line promise/catch-or-return
|
|
56
|
+
prompt.then((value) => resolve(value.suggestion))
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
if (applySuggestion) {
|
|
60
|
+
return recipesCommand(suggestion, options, command)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const runRecipe = ({ config, recipeName, repositoryRoot }) => {
|
|
66
|
+
const recipe = getRecipe(recipeName)
|
|
67
|
+
|
|
68
|
+
return recipe.run({ config, repositoryRoot })
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Creates the `netlify recipes` command
|
|
73
|
+
* @param {import('../base-command').BaseCommand} program
|
|
74
|
+
* @returns
|
|
75
|
+
*/
|
|
76
|
+
const createRecipesCommand = (program) =>
|
|
77
|
+
program
|
|
78
|
+
.command('recipes')
|
|
79
|
+
.argument('[name]', 'name of the recipe')
|
|
80
|
+
.description(`(Beta) Create and modify files in a project using pre-defined recipes`)
|
|
81
|
+
.option('-n, --name <name>', 'recipe name to use')
|
|
82
|
+
.addExamples(['netlify recipes my-recipe', 'netlify recipes --name my-recipe'])
|
|
83
|
+
.action(recipesCommand)
|
|
84
|
+
|
|
85
|
+
module.exports = { createRecipesCommand, runRecipe }
|
package/src/lib/api.js
CHANGED
|
@@ -1,76 +1,5 @@
|
|
|
1
|
-
const fetch = require('node-fetch')
|
|
2
|
-
|
|
3
1
|
const { warn } = require('../utils/command-helpers')
|
|
4
2
|
|
|
5
|
-
const getHeaders = ({ token }) => ({
|
|
6
|
-
'Content-Type': 'application/json',
|
|
7
|
-
Authorization: `Bearer ${token}`,
|
|
8
|
-
})
|
|
9
|
-
|
|
10
|
-
const getErrorMessage = async ({ response }) => {
|
|
11
|
-
const contentType = response.headers.get('content-type')
|
|
12
|
-
if (contentType && contentType.includes('application/json')) {
|
|
13
|
-
const json = await response.json()
|
|
14
|
-
return json.message
|
|
15
|
-
}
|
|
16
|
-
const text = await response.text()
|
|
17
|
-
return text
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const checkResponse = async ({ response }) => {
|
|
21
|
-
if (!response.ok) {
|
|
22
|
-
const message = await getErrorMessage({ response }).catch(() => {})
|
|
23
|
-
const errorPostfix = message ? ` and message '${message}'` : ''
|
|
24
|
-
throw new Error(`Request failed with status '${response.status}'${errorPostfix}`)
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const getApiUrl = ({ api }) => `${api.scheme}://${api.host}${api.pathPrefix}`
|
|
29
|
-
|
|
30
|
-
const apiPost = async ({ api, data, path }) => {
|
|
31
|
-
const apiUrl = getApiUrl({ api })
|
|
32
|
-
const response = await fetch(`${apiUrl}/${path}`, {
|
|
33
|
-
method: 'POST',
|
|
34
|
-
body: JSON.stringify(data),
|
|
35
|
-
headers: getHeaders({ token: api.accessToken }),
|
|
36
|
-
agent: api.agent,
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
await checkResponse({ response })
|
|
40
|
-
|
|
41
|
-
return response
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const uploadEdgeHandlers = async ({ api, bundleBuffer, deployId, manifest }) => {
|
|
45
|
-
// TODO: use open-api spec via api when it is exposed
|
|
46
|
-
const response = await apiPost({ api, path: `deploys/${deployId}/edge_handlers`, data: manifest })
|
|
47
|
-
const { error, exists, upload_url: uploadUrl } = await response.json()
|
|
48
|
-
if (error) {
|
|
49
|
-
throw new Error(error)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (exists) {
|
|
53
|
-
return false
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (!uploadUrl) {
|
|
57
|
-
throw new Error('Missing upload URL')
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const putResponse = await fetch(uploadUrl, {
|
|
61
|
-
method: 'PUT',
|
|
62
|
-
body: bundleBuffer,
|
|
63
|
-
headers: {
|
|
64
|
-
'Content-Type': 'application/javascript',
|
|
65
|
-
},
|
|
66
|
-
agent: api.agent,
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
await checkResponse({ response: putResponse })
|
|
70
|
-
|
|
71
|
-
return true
|
|
72
|
-
}
|
|
73
|
-
|
|
74
3
|
const cancelDeploy = async ({ api, deployId }) => {
|
|
75
4
|
try {
|
|
76
5
|
await api.cancelSiteDeploy({ deploy_id: deployId })
|
|
@@ -92,4 +21,4 @@ const listSites = async ({ api, options }) => {
|
|
|
92
21
|
return sites
|
|
93
22
|
}
|
|
94
23
|
|
|
95
|
-
module.exports = {
|
|
24
|
+
module.exports = { cancelDeploy, listSites }
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const DEFAULT_SRC_DIR = 'netlify/edge-functions'
|
|
2
|
+
const DIST_IMPORT_MAP_PATH = 'edge-functions-import-map.json'
|
|
3
|
+
const INTERNAL_EDGE_FUNCTIONS_FOLDER = 'edge-functions'
|
|
4
|
+
const EDGE_FUNCTIONS_FOLDER = 'edge-functions-dist'
|
|
5
|
+
const PUBLIC_URL_PATH = '.netlify/internal/edge-functions'
|
|
6
|
+
|
|
7
|
+
// 1 second
|
|
8
|
+
const SERVER_POLL_INTERNAL = 1e3
|
|
9
|
+
|
|
10
|
+
// 10 seconds
|
|
11
|
+
const SERVER_POLL_TIMEOUT = 1e4
|
|
12
|
+
|
|
13
|
+
module.exports = {
|
|
14
|
+
DEFAULT_SRC_DIR,
|
|
15
|
+
DIST_IMPORT_MAP_PATH,
|
|
16
|
+
INTERNAL_EDGE_FUNCTIONS_FOLDER,
|
|
17
|
+
EDGE_FUNCTIONS_FOLDER,
|
|
18
|
+
PUBLIC_URL_PATH,
|
|
19
|
+
SERVER_POLL_INTERNAL,
|
|
20
|
+
SERVER_POLL_TIMEOUT,
|
|
21
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
const { stat } = require('fs').promises
|
|
3
|
+
const path = require('path')
|
|
4
|
+
|
|
5
|
+
const { getPathInProject } = require('../settings')
|
|
6
|
+
|
|
7
|
+
const { EDGE_FUNCTIONS_FOLDER, PUBLIC_URL_PATH } = require('./consts')
|
|
8
|
+
|
|
9
|
+
const distPath = getPathInProject([EDGE_FUNCTIONS_FOLDER])
|
|
10
|
+
|
|
11
|
+
const deployFileNormalizer = (file) => {
|
|
12
|
+
const isEdgeFunction = file.root === distPath
|
|
13
|
+
const normalizedPath = isEdgeFunction ? path.join(PUBLIC_URL_PATH, file.normalizedPath) : file.normalizedPath
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
...file,
|
|
17
|
+
normalizedPath,
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const getDistPathIfExists = async () => {
|
|
22
|
+
try {
|
|
23
|
+
const stats = await stat(distPath)
|
|
24
|
+
|
|
25
|
+
if (!stats.isDirectory()) {
|
|
26
|
+
throw new Error(`Path ${distPath} must be a directory.`)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return distPath
|
|
30
|
+
} catch {
|
|
31
|
+
// no-op
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const isEdgeFunctionFile = (filePath) => filePath.startsWith(`${PUBLIC_URL_PATH}${path.sep}`)
|
|
36
|
+
|
|
37
|
+
module.exports = {
|
|
38
|
+
deployFileNormalizer,
|
|
39
|
+
getDistPathIfExists,
|
|
40
|
+
isEdgeFunctionFile,
|
|
41
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const { env } = require('process')
|
|
2
|
+
|
|
3
|
+
const inquirer = require('inquirer')
|
|
4
|
+
|
|
5
|
+
const { runRecipe } = require('../../commands/recipes')
|
|
6
|
+
|
|
7
|
+
const STATE_PROMPT_PROPERTY = 'promptVSCodeSettings'
|
|
8
|
+
|
|
9
|
+
const promptEditorHelper = async ({ NETLIFYDEVLOG, chalk, config, log, repositoryRoot, state }) => {
|
|
10
|
+
const isVSCode = env.TERM_PROGRAM === 'vscode'
|
|
11
|
+
const hasShownPrompt = Boolean(state.get(STATE_PROMPT_PROPERTY))
|
|
12
|
+
const hasEdgeFunctions = Boolean(config.edge_functions && config.edge_functions.length !== 0)
|
|
13
|
+
|
|
14
|
+
if (!isVSCode || hasShownPrompt || !hasEdgeFunctions) {
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
state.set(STATE_PROMPT_PROPERTY, true)
|
|
19
|
+
|
|
20
|
+
const message = 'Would you like to configure VS Code to use Edge Functions?'
|
|
21
|
+
const { confirm } = await inquirer.prompt({
|
|
22
|
+
type: 'confirm',
|
|
23
|
+
name: 'confirm',
|
|
24
|
+
message,
|
|
25
|
+
default: true,
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
if (!confirm) {
|
|
29
|
+
log(
|
|
30
|
+
`${NETLIFYDEVLOG} You can start this configuration manually by running ${chalk.magenta.bold(
|
|
31
|
+
'netlify recipes vscode',
|
|
32
|
+
)}.`,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
await runRecipe({ config, recipeName: 'vscode', repositoryRoot })
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = { promptEditorHelper }
|