netlify-cli 15.11.0 → 16.0.0-alpha.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/bin/run.mjs +6 -5
- package/npm-shrinkwrap.json +628 -42
- package/package.json +4 -5
- package/src/commands/base-command.mjs +295 -118
- package/src/commands/build/build.mjs +9 -1
- package/src/commands/deploy/deploy.mjs +42 -18
- 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 +3 -3
- package/src/commands/functions/functions-serve.mjs +1 -0
- 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/javascript/google-analytics/package.json +1 -1
- package/src/functions-templates/typescript/scheduled-function/package.json +1 -1
- package/src/lib/edge-functions/deploy.mjs +11 -4
- package/src/lib/edge-functions/internal.mjs +5 -3
- package/src/lib/edge-functions/proxy.mjs +29 -5
- package/src/lib/functions/runtimes/js/builders/zisi.mjs +20 -3
- 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/deploy/deploy-site.mjs +4 -4
- package/src/utils/deploy/hash-fns.mjs +2 -2
- 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 +68 -68
- 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/init/frameworks.mjs +0 -23
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import { stat } from 'fs/promises'
|
|
3
3
|
import { basename, resolve } from 'path'
|
|
4
|
-
import {
|
|
4
|
+
import { env } from 'process'
|
|
5
5
|
|
|
6
6
|
import { runCoreSteps } from '@netlify/build'
|
|
7
7
|
import { restoreConfig, updateConfig } from '@netlify/config'
|
|
@@ -64,16 +64,18 @@ const triggerDeploy = async ({ api, options, siteData, siteId }) => {
|
|
|
64
64
|
/**
|
|
65
65
|
* g
|
|
66
66
|
* @param {object} config
|
|
67
|
+
* @param {string} config.workingDir The process working directory
|
|
67
68
|
* @param {object} config.config
|
|
68
69
|
* @param {import('commander').OptionValues} config.options
|
|
69
70
|
* @param {object} config.site
|
|
70
71
|
* @param {object} config.siteData
|
|
71
72
|
* @returns {Promise<string>}
|
|
72
73
|
*/
|
|
73
|
-
const getDeployFolder = async ({ config, options, site, siteData }) => {
|
|
74
|
+
const getDeployFolder = async ({ config, options, site, siteData, workingDir }) => {
|
|
75
|
+
console.log()
|
|
74
76
|
let deployFolder
|
|
75
77
|
if (options.dir) {
|
|
76
|
-
deployFolder = resolve(
|
|
78
|
+
deployFolder = resolve(site.root, options.dir)
|
|
77
79
|
} else if (config?.build?.publish) {
|
|
78
80
|
deployFolder = resolve(site.root, config.build.publish)
|
|
79
81
|
} else if (siteData?.build_settings?.dir) {
|
|
@@ -82,14 +84,13 @@ const getDeployFolder = async ({ config, options, site, siteData }) => {
|
|
|
82
84
|
|
|
83
85
|
if (!deployFolder) {
|
|
84
86
|
log('Please provide a publish directory (e.g. "public" or "dist" or "."):')
|
|
85
|
-
log(cwd())
|
|
86
87
|
const { promptPath } = await inquirer.prompt([
|
|
87
88
|
{
|
|
88
89
|
type: 'input',
|
|
89
90
|
name: 'promptPath',
|
|
90
91
|
message: 'Publish directory',
|
|
91
92
|
default: '.',
|
|
92
|
-
filter: (input) => resolve(
|
|
93
|
+
filter: (input) => resolve(workingDir, input),
|
|
93
94
|
},
|
|
94
95
|
])
|
|
95
96
|
deployFolder = promptPath
|
|
@@ -98,7 +99,10 @@ const getDeployFolder = async ({ config, options, site, siteData }) => {
|
|
|
98
99
|
return deployFolder
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
|
|
102
|
+
/**
|
|
103
|
+
* @param {string} deployFolder
|
|
104
|
+
*/
|
|
105
|
+
const validateDeployFolder = async (deployFolder) => {
|
|
102
106
|
/** @type {import('fs').Stats} */
|
|
103
107
|
let stats
|
|
104
108
|
try {
|
|
@@ -128,14 +132,15 @@ const validateDeployFolder = async ({ deployFolder }) => {
|
|
|
128
132
|
* @param {import('commander').OptionValues} config.options
|
|
129
133
|
* @param {object} config.site
|
|
130
134
|
* @param {object} config.siteData
|
|
131
|
-
* @
|
|
135
|
+
* @param {string} config.workingDir // The process working directory
|
|
136
|
+
* @returns {string|undefined}
|
|
132
137
|
*/
|
|
133
|
-
const getFunctionsFolder = ({ config, options, site, siteData }) => {
|
|
138
|
+
const getFunctionsFolder = ({ config, options, site, siteData, workingDir }) => {
|
|
134
139
|
let functionsFolder
|
|
135
140
|
// Support "functions" and "Functions"
|
|
136
141
|
const funcConfig = config.functionsDirectory
|
|
137
142
|
if (options.functions) {
|
|
138
|
-
functionsFolder = resolve(
|
|
143
|
+
functionsFolder = resolve(workingDir, options.functions)
|
|
139
144
|
} else if (funcConfig) {
|
|
140
145
|
functionsFolder = resolve(site.root, funcConfig)
|
|
141
146
|
} else if (siteData?.build_settings?.functions_dir) {
|
|
@@ -144,8 +149,12 @@ const getFunctionsFolder = ({ config, options, site, siteData }) => {
|
|
|
144
149
|
return functionsFolder
|
|
145
150
|
}
|
|
146
151
|
|
|
147
|
-
|
|
148
|
-
|
|
152
|
+
/**
|
|
153
|
+
*
|
|
154
|
+
* @param {string|undefined} functionsFolder
|
|
155
|
+
*/
|
|
156
|
+
const validateFunctionsFolder = async (functionsFolder) => {
|
|
157
|
+
/** @type {import('fs').Stats|undefined} */
|
|
149
158
|
let stats
|
|
150
159
|
if (functionsFolder) {
|
|
151
160
|
// we used to hard error if functions folder is specified but doesn't exist
|
|
@@ -173,17 +182,26 @@ const validateFunctionsFolder = async ({ functionsFolder }) => {
|
|
|
173
182
|
}
|
|
174
183
|
|
|
175
184
|
const validateFolders = async ({ deployFolder, functionsFolder }) => {
|
|
176
|
-
const deployFolderStat = await validateDeployFolder(
|
|
177
|
-
const functionsFolderStat = await validateFunctionsFolder(
|
|
185
|
+
const deployFolderStat = await validateDeployFolder(deployFolder)
|
|
186
|
+
const functionsFolderStat = await validateFunctionsFolder(functionsFolder)
|
|
178
187
|
return { deployFolderStat, functionsFolderStat }
|
|
179
188
|
}
|
|
180
189
|
|
|
190
|
+
/**
|
|
191
|
+
* @param {object} config
|
|
192
|
+
* @param {string} config.deployFolder
|
|
193
|
+
* @param {*} config.site
|
|
194
|
+
* @returns
|
|
195
|
+
*/
|
|
181
196
|
const getDeployFilesFilter = ({ deployFolder, site }) => {
|
|
182
197
|
// site.root === deployFolder can happen when users run `netlify deploy --dir .`
|
|
183
198
|
// in that specific case we don't want to publish the repo node_modules
|
|
184
199
|
// when site.root !== deployFolder the behaviour matches our buildbot
|
|
185
200
|
const skipNodeModules = site.root === deployFolder
|
|
186
201
|
|
|
202
|
+
/**
|
|
203
|
+
* @param {string} filename
|
|
204
|
+
*/
|
|
187
205
|
return (filename) => {
|
|
188
206
|
if (filename == null) {
|
|
189
207
|
return false
|
|
@@ -298,6 +316,7 @@ const deployProgressCb = function () {
|
|
|
298
316
|
const runDeploy = async ({
|
|
299
317
|
alias,
|
|
300
318
|
api,
|
|
319
|
+
command,
|
|
301
320
|
configPath,
|
|
302
321
|
deployFolder,
|
|
303
322
|
deployTimeout,
|
|
@@ -344,7 +363,7 @@ const runDeploy = async ({
|
|
|
344
363
|
// pass an existing deployId to update
|
|
345
364
|
deployId,
|
|
346
365
|
filter: getDeployFilesFilter({ site, deployFolder }),
|
|
347
|
-
|
|
366
|
+
workingDir: command.workingDir,
|
|
348
367
|
manifestPath,
|
|
349
368
|
skipFunctionsCache,
|
|
350
369
|
})
|
|
@@ -403,9 +422,10 @@ const handleBuild = async ({ cachedConfig, options }) => {
|
|
|
403
422
|
/**
|
|
404
423
|
*
|
|
405
424
|
* @param {object} options Bundling options
|
|
425
|
+
* @param {import('..//base-command.mjs').default} command
|
|
406
426
|
* @returns
|
|
407
427
|
*/
|
|
408
|
-
const bundleEdgeFunctions = async (options) => {
|
|
428
|
+
const bundleEdgeFunctions = async (options, command) => {
|
|
409
429
|
const statusCb = options.silent ? () => {} : deployProgressCb()
|
|
410
430
|
|
|
411
431
|
statusCb({
|
|
@@ -416,6 +436,7 @@ const bundleEdgeFunctions = async (options) => {
|
|
|
416
436
|
|
|
417
437
|
const { severityCode, success } = await runCoreSteps(['edge_functions_bundling'], {
|
|
418
438
|
...options,
|
|
439
|
+
packagePath: command.workspacePackage,
|
|
419
440
|
buffer: true,
|
|
420
441
|
featureFlags: edgeFunctionsFeatureFlags,
|
|
421
442
|
})
|
|
@@ -496,6 +517,7 @@ const printResults = ({ deployToProduction, json, results, runBuildCommand }) =>
|
|
|
496
517
|
* @param {import('../base-command.mjs').default} command
|
|
497
518
|
*/
|
|
498
519
|
const deploy = async (options, command) => {
|
|
520
|
+
const { workingDir } = command
|
|
499
521
|
const { api, site, siteInfo } = command.netlify
|
|
500
522
|
const alias = options.alias || options.branch
|
|
501
523
|
|
|
@@ -560,20 +582,21 @@ const deploy = async (options, command) => {
|
|
|
560
582
|
siteInfo: siteData,
|
|
561
583
|
})
|
|
562
584
|
}
|
|
585
|
+
|
|
563
586
|
const { configMutations = [], newConfig } = await handleBuild({
|
|
564
587
|
cachedConfig: command.netlify.cachedConfig,
|
|
565
588
|
options,
|
|
566
589
|
})
|
|
567
590
|
const config = newConfig || command.netlify.config
|
|
568
591
|
|
|
569
|
-
const deployFolder = await getDeployFolder({ options, config, site, siteData })
|
|
570
|
-
const functionsFolder = getFunctionsFolder({ options, config, site, siteData })
|
|
592
|
+
const deployFolder = await getDeployFolder({ workingDir, options, config, site, siteData })
|
|
593
|
+
const functionsFolder = getFunctionsFolder({ workingDir, options, config, site, siteData })
|
|
571
594
|
const { configPath } = site
|
|
572
595
|
const edgeFunctionsConfig = command.netlify.config.edge_functions
|
|
573
596
|
|
|
574
597
|
// build flag wasn't used and edge functions exist
|
|
575
598
|
if (!options.build && edgeFunctionsConfig && edgeFunctionsConfig.length !== 0) {
|
|
576
|
-
await bundleEdgeFunctions(options)
|
|
599
|
+
await bundleEdgeFunctions(options, command)
|
|
577
600
|
}
|
|
578
601
|
|
|
579
602
|
log(
|
|
@@ -618,6 +641,7 @@ const deploy = async (options, command) => {
|
|
|
618
641
|
const results = await runDeploy({
|
|
619
642
|
alias,
|
|
620
643
|
api,
|
|
644
|
+
command,
|
|
621
645
|
configPath,
|
|
622
646
|
deployFolder,
|
|
623
647
|
deployTimeout: options.timeout * SEC_TO_MILLISEC || DEFAULT_DEPLOY_TIMEOUT,
|
package/src/commands/dev/dev.mjs
CHANGED
|
@@ -9,7 +9,6 @@ import { printBanner } from '../../utils/banner.mjs'
|
|
|
9
9
|
import {
|
|
10
10
|
BANG,
|
|
11
11
|
chalk,
|
|
12
|
-
exit,
|
|
13
12
|
log,
|
|
14
13
|
NETLIFYDEV,
|
|
15
14
|
NETLIFYDEVERR,
|
|
@@ -35,7 +34,7 @@ import { createDevExecCommand } from './dev-exec.mjs'
|
|
|
35
34
|
* @param {object} config
|
|
36
35
|
* @param {*} config.api
|
|
37
36
|
* @param {import('commander').OptionValues} config.options
|
|
38
|
-
* @param {
|
|
37
|
+
* @param {import('../../utils/types.js').ServerSettings} config.settings
|
|
39
38
|
* @param {*} config.site
|
|
40
39
|
* @param {*} config.state
|
|
41
40
|
* @returns
|
|
@@ -68,6 +67,9 @@ const handleLiveTunnel = async ({ api, options, settings, site, state }) => {
|
|
|
68
67
|
}
|
|
69
68
|
}
|
|
70
69
|
|
|
70
|
+
/**
|
|
71
|
+
* @param {string} args
|
|
72
|
+
*/
|
|
71
73
|
const validateShortFlagArgs = (args) => {
|
|
72
74
|
if (args.startsWith('=')) {
|
|
73
75
|
throw new Error(
|
|
@@ -94,11 +96,13 @@ const dev = async (options, command) => {
|
|
|
94
96
|
const { api, cachedConfig, config, repositoryRoot, site, siteInfo, state } = command.netlify
|
|
95
97
|
config.dev = { ...config.dev }
|
|
96
98
|
config.build = { ...config.build }
|
|
97
|
-
/** @type {import('./types').DevConfig} */
|
|
99
|
+
/** @type {import('./types.js').DevConfig} */
|
|
98
100
|
const devConfig = {
|
|
99
101
|
framework: '#auto',
|
|
102
|
+
autoLaunch: Boolean(options.open),
|
|
100
103
|
...(config.functionsDirectory && { functions: config.functionsDirectory }),
|
|
101
104
|
...(config.build.publish && { publish: config.build.publish }),
|
|
105
|
+
...(config.build.base && { base: config.build.base }),
|
|
102
106
|
...config.dev,
|
|
103
107
|
...options,
|
|
104
108
|
}
|
|
@@ -124,20 +128,17 @@ const dev = async (options, command) => {
|
|
|
124
128
|
siteInfo,
|
|
125
129
|
})
|
|
126
130
|
|
|
127
|
-
/** @type {
|
|
128
|
-
let settings
|
|
131
|
+
/** @type {import('../../utils/types.js').ServerSettings} */
|
|
132
|
+
let settings
|
|
129
133
|
try {
|
|
130
|
-
settings = await detectServerSettings(devConfig, options,
|
|
131
|
-
site: {
|
|
132
|
-
id: site.id,
|
|
133
|
-
url: siteUrl,
|
|
134
|
-
},
|
|
135
|
-
})
|
|
134
|
+
settings = await detectServerSettings(devConfig, options, command)
|
|
136
135
|
|
|
137
136
|
cachedConfig.config = getConfigWithPlugins(cachedConfig.config, settings)
|
|
138
137
|
} catch (error_) {
|
|
139
|
-
|
|
140
|
-
|
|
138
|
+
if (error_ && typeof error_ === 'object' && 'message' in error_) {
|
|
139
|
+
log(NETLIFYDEVERR, error_.message)
|
|
140
|
+
}
|
|
141
|
+
process.exit(1)
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
command.setAnalyticsPayload({ live: options.live })
|
|
@@ -151,10 +152,9 @@ const dev = async (options, command) => {
|
|
|
151
152
|
log(`${NETLIFYDEVWARN} Setting up local development server`)
|
|
152
153
|
|
|
153
154
|
const { configPath: configPathOverride } = await runDevTimeline({
|
|
154
|
-
|
|
155
|
+
command,
|
|
155
156
|
options,
|
|
156
157
|
settings,
|
|
157
|
-
site,
|
|
158
158
|
env: {
|
|
159
159
|
URL: url,
|
|
160
160
|
DEPLOY_URL: url,
|
|
@@ -188,8 +188,11 @@ const dev = async (options, command) => {
|
|
|
188
188
|
|
|
189
189
|
// TODO: We should consolidate this with the existing config watcher.
|
|
190
190
|
const getUpdatedConfig = async () => {
|
|
191
|
-
const
|
|
192
|
-
|
|
191
|
+
const { config: newConfig } = await command.getConfig({
|
|
192
|
+
cwd: command.workingDir,
|
|
193
|
+
offline: true,
|
|
194
|
+
state,
|
|
195
|
+
})
|
|
193
196
|
const normalizedNewConfig = normalizeConfig(newConfig)
|
|
194
197
|
|
|
195
198
|
return normalizedNewConfig
|
|
@@ -202,6 +205,7 @@ const dev = async (options, command) => {
|
|
|
202
205
|
config,
|
|
203
206
|
configPath: configPathOverride,
|
|
204
207
|
debug: options.debug,
|
|
208
|
+
projectDir: command.workingDir,
|
|
205
209
|
env,
|
|
206
210
|
getUpdatedConfig,
|
|
207
211
|
inspectSettings,
|
|
@@ -248,6 +252,7 @@ export const createDevCommand = (program) => {
|
|
|
248
252
|
.argParser((value) => Number.parseInt(value))
|
|
249
253
|
.hideHelp(true),
|
|
250
254
|
)
|
|
255
|
+
.addOption(new Option('--no-open', 'disables the automatic opening of a browser window'))
|
|
251
256
|
.option('--target-port <port>', 'port of target app server', (value) => Number.parseInt(value))
|
|
252
257
|
.option('--framework <name>', 'framework to use. Defaults to #auto which automatically detects a framework')
|
|
253
258
|
.option('-d ,--dir <path>', 'dir with static files')
|
|
@@ -3,7 +3,7 @@ import cp from 'child_process'
|
|
|
3
3
|
import fs from 'fs'
|
|
4
4
|
import { mkdir, readdir, unlink } from 'fs/promises'
|
|
5
5
|
import { createRequire } from 'module'
|
|
6
|
-
import path, { dirname } from 'path'
|
|
6
|
+
import path, { dirname, join, relative } from 'path'
|
|
7
7
|
import process from 'process'
|
|
8
8
|
import { fileURLToPath, pathToFileURL } from 'url'
|
|
9
9
|
import { promisify } from 'util'
|
|
@@ -12,7 +12,6 @@ import copyTemplateDirOriginal from 'copy-template-dir'
|
|
|
12
12
|
import { findUp } from 'find-up'
|
|
13
13
|
import fuzzy from 'fuzzy'
|
|
14
14
|
import inquirer from 'inquirer'
|
|
15
|
-
import inquirerAutocompletePrompt from 'inquirer-autocomplete-prompt'
|
|
16
15
|
import fetch from 'node-fetch'
|
|
17
16
|
import ora from 'ora'
|
|
18
17
|
|
|
@@ -31,8 +30,10 @@ const templatesDir = path.resolve(dirname(fileURLToPath(import.meta.url)), '../.
|
|
|
31
30
|
|
|
32
31
|
const showRustTemplates = process.env.NETLIFY_EXPERIMENTAL_BUILD_RUST_SOURCE === 'true'
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Ensure that there's a sub-directory in `src/functions-templates` named after
|
|
35
|
+
* each `value` property in this list.
|
|
36
|
+
*/
|
|
36
37
|
const languages = [
|
|
37
38
|
{ name: 'JavaScript', value: 'javascript' },
|
|
38
39
|
{ name: 'TypeScript', value: 'typescript' },
|
|
@@ -91,23 +92,28 @@ const filterRegistry = function (registry, input) {
|
|
|
91
92
|
})
|
|
92
93
|
}
|
|
93
94
|
|
|
95
|
+
/**
|
|
96
|
+
* @param {string} lang
|
|
97
|
+
* @param {'edge' | 'serverless'} funcType
|
|
98
|
+
*/
|
|
94
99
|
const formatRegistryArrayForInquirer = async function (lang, funcType) {
|
|
95
|
-
const
|
|
100
|
+
const folders = await readdir(path.join(templatesDir, lang), { withFileTypes: true })
|
|
96
101
|
|
|
97
102
|
const imports = await Promise.all(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
folders
|
|
104
|
+
.filter((folder) => Boolean(folder?.isDirectory()))
|
|
105
|
+
.map(async ({ name }) => {
|
|
106
|
+
try {
|
|
107
|
+
const templatePath = path.join(templatesDir, lang, name, '.netlify-function-template.mjs')
|
|
108
|
+
const template = await import(pathToFileURL(templatePath))
|
|
109
|
+
return template.default
|
|
110
|
+
} catch {
|
|
111
|
+
// noop if import fails we don't break the whole inquirer
|
|
112
|
+
}
|
|
106
113
|
}),
|
|
107
114
|
)
|
|
108
|
-
|
|
109
115
|
const registry = imports
|
|
110
|
-
.filter((template) => template
|
|
116
|
+
.filter((template) => template?.functionType === funcType)
|
|
111
117
|
.sort((templateA, templateB) => {
|
|
112
118
|
const priorityDiff = (templateA.priority || DEFAULT_PRIORITY) - (templateB.priority || DEFAULT_PRIORITY)
|
|
113
119
|
|
|
@@ -136,7 +142,7 @@ const formatRegistryArrayForInquirer = async function (lang, funcType) {
|
|
|
136
142
|
/**
|
|
137
143
|
* pick template from our existing templates
|
|
138
144
|
* @param {import('commander').OptionValues} config
|
|
139
|
-
*
|
|
145
|
+
* @param {'edge' | 'serverless'} funcType
|
|
140
146
|
*/
|
|
141
147
|
const pickTemplate = async function ({ language: languageFromFlag }, funcType) {
|
|
142
148
|
const specialCommands = [
|
|
@@ -172,8 +178,6 @@ const pickTemplate = async function ({ language: languageFromFlag }, funcType) {
|
|
|
172
178
|
language = languageFromPrompt
|
|
173
179
|
}
|
|
174
180
|
|
|
175
|
-
inquirer.registerPrompt('autocomplete', inquirerAutocompletePrompt)
|
|
176
|
-
|
|
177
181
|
let templatesForLanguage
|
|
178
182
|
|
|
179
183
|
try {
|
|
@@ -207,6 +211,7 @@ const pickTemplate = async function ({ language: languageFromFlag }, funcType) {
|
|
|
207
211
|
|
|
208
212
|
const DEFAULT_PRIORITY = 999
|
|
209
213
|
|
|
214
|
+
/** @returns {Promise<'edge' | 'serverless'>} */
|
|
210
215
|
const selectTypeOfFunc = async () => {
|
|
211
216
|
const functionTypes = [
|
|
212
217
|
{ name: 'Edge function (Deno)', value: 'edge' },
|
|
@@ -224,92 +229,100 @@ const selectTypeOfFunc = async () => {
|
|
|
224
229
|
return functionType
|
|
225
230
|
}
|
|
226
231
|
|
|
232
|
+
/**
|
|
233
|
+
* @param {import('../base-command.mjs').default} command
|
|
234
|
+
*/
|
|
227
235
|
const ensureEdgeFuncDirExists = function (command) {
|
|
228
236
|
const { config, site } = command.netlify
|
|
229
237
|
const siteId = site.id
|
|
230
|
-
let functionsDirHolder = config.build.edge_functions
|
|
231
238
|
|
|
232
239
|
if (!siteId) {
|
|
233
240
|
error(`${NETLIFYDEVERR} No site id found, please run inside a site directory or \`netlify link\``)
|
|
234
241
|
}
|
|
235
242
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
243
|
+
const functionsDir = config.build?.edge_functions ?? join(command.workingDir, 'netlify/edge-functions')
|
|
244
|
+
const relFunctionsDir = relative(command.workingDir, functionsDir)
|
|
239
245
|
|
|
240
|
-
if (!fs.existsSync(
|
|
246
|
+
if (!fs.existsSync(functionsDir)) {
|
|
241
247
|
log(
|
|
242
248
|
`${NETLIFYDEVLOG} Edge Functions directory ${chalk.magenta.inverse(
|
|
243
|
-
|
|
249
|
+
relFunctionsDir,
|
|
244
250
|
)} does not exist yet, creating it...`,
|
|
245
251
|
)
|
|
246
252
|
|
|
247
|
-
fs.mkdirSync(
|
|
253
|
+
fs.mkdirSync(functionsDir, { recursive: true })
|
|
248
254
|
|
|
249
|
-
log(`${NETLIFYDEVLOG} Edge Functions directory ${chalk.magenta.inverse(
|
|
255
|
+
log(`${NETLIFYDEVLOG} Edge Functions directory ${chalk.magenta.inverse(relFunctionsDir)} created.`)
|
|
250
256
|
}
|
|
251
|
-
|
|
257
|
+
|
|
258
|
+
return functionsDir
|
|
252
259
|
}
|
|
253
260
|
|
|
254
261
|
/**
|
|
255
|
-
*
|
|
262
|
+
* Prompts the user to choose a functions directory
|
|
256
263
|
* @param {import('../base-command.mjs').default} command
|
|
257
|
-
* @returns {Promise<string
|
|
264
|
+
* @returns {Promise<string>} - functions directory or throws an error
|
|
258
265
|
*/
|
|
259
|
-
const
|
|
260
|
-
const { api,
|
|
261
|
-
|
|
262
|
-
let functionsDirHolder = config.functionsDirectory
|
|
263
|
-
|
|
264
|
-
if (!functionsDirHolder) {
|
|
265
|
-
log(`${NETLIFYDEVLOG} functions directory not specified in netlify.toml or UI settings`)
|
|
266
|
-
|
|
267
|
-
if (!siteId) {
|
|
268
|
-
error(`${NETLIFYDEVERR} No site id found, please run inside a site directory or \`netlify link\``)
|
|
269
|
-
}
|
|
266
|
+
const promptFunctionsDirectory = async (command) => {
|
|
267
|
+
const { api, relConfigFilePath, site } = command.netlify
|
|
268
|
+
log(`\n${NETLIFYDEVLOG} functions directory not specified in ${relConfigFilePath} or UI settings`)
|
|
270
269
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
name: 'functionsDir',
|
|
275
|
-
message:
|
|
276
|
-
'Enter the path, relative to your site’s base directory in your repository, where your functions should live:',
|
|
277
|
-
default: 'netlify/functions',
|
|
278
|
-
},
|
|
279
|
-
])
|
|
270
|
+
if (!site.id) {
|
|
271
|
+
error(`${NETLIFYDEVERR} No site id found, please run inside a site directory or \`netlify link\``)
|
|
272
|
+
}
|
|
280
273
|
|
|
281
|
-
|
|
274
|
+
const { functionsDir } = await inquirer.prompt([
|
|
275
|
+
{
|
|
276
|
+
type: 'input',
|
|
277
|
+
name: 'functionsDir',
|
|
278
|
+
message: 'Enter the path, relative to your site, where your functions should live:',
|
|
279
|
+
default: 'netlify/functions',
|
|
280
|
+
},
|
|
281
|
+
])
|
|
282
282
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
},
|
|
283
|
+
try {
|
|
284
|
+
log(`${NETLIFYDEVLOG} updating site settings with ${chalk.magenta.inverse(functionsDir)}`)
|
|
285
|
+
|
|
286
|
+
// @ts-ignore Typings of API are not correct
|
|
287
|
+
await api.updateSite({
|
|
288
|
+
siteId: site.id,
|
|
289
|
+
body: {
|
|
290
|
+
build_settings: {
|
|
291
|
+
functions_dir: functionsDir,
|
|
293
292
|
},
|
|
294
|
-
}
|
|
293
|
+
},
|
|
294
|
+
})
|
|
295
295
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
}
|
|
296
|
+
log(`${NETLIFYDEVLOG} functions directory ${chalk.magenta.inverse(functionsDir)} updated in site settings`)
|
|
297
|
+
} catch {
|
|
298
|
+
throw error('Error updating site settings')
|
|
300
299
|
}
|
|
300
|
+
return functionsDir
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Get functions directory (and make it if necessary)
|
|
305
|
+
* @param {import('../base-command.mjs').default} command
|
|
306
|
+
* @returns {Promise<string>} - functions directory or throws an error
|
|
307
|
+
*/
|
|
308
|
+
const ensureFunctionDirExists = async function (command) {
|
|
309
|
+
const { config } = command.netlify
|
|
310
|
+
const functionsDirHolder =
|
|
311
|
+
config.functionsDirectory || join(command.workingDir, await promptFunctionsDirectory(command))
|
|
312
|
+
const relFunctionsDirHolder = relative(command.workingDir, functionsDirHolder)
|
|
301
313
|
|
|
302
|
-
if (!(
|
|
314
|
+
if (!fs.existsSync(functionsDirHolder)) {
|
|
303
315
|
log(
|
|
304
316
|
`${NETLIFYDEVLOG} functions directory ${chalk.magenta.inverse(
|
|
305
|
-
|
|
317
|
+
relFunctionsDirHolder,
|
|
306
318
|
)} does not exist yet, creating it...`,
|
|
307
319
|
)
|
|
308
320
|
|
|
309
321
|
await mkdir(functionsDirHolder, { recursive: true })
|
|
310
322
|
|
|
311
|
-
log(`${NETLIFYDEVLOG} functions directory ${chalk.magenta.inverse(
|
|
323
|
+
log(`${NETLIFYDEVLOG} functions directory ${chalk.magenta.inverse(relFunctionsDirHolder)} created`)
|
|
312
324
|
}
|
|
325
|
+
|
|
313
326
|
return functionsDirHolder
|
|
314
327
|
}
|
|
315
328
|
|
|
@@ -370,20 +383,24 @@ const downloadFromURL = async function (command, options, argumentName, function
|
|
|
370
383
|
}
|
|
371
384
|
}
|
|
372
385
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
386
|
+
/**
|
|
387
|
+
* Takes a list of existing packages and a list of packages required by a
|
|
388
|
+
* function, and returns the packages from the latter that aren't present
|
|
389
|
+
* in the former. The packages are returned as an array of strings with the
|
|
390
|
+
* name and version range (e.g. '@netlify/functions@0.1.0').
|
|
391
|
+
*/
|
|
377
392
|
const getNpmInstallPackages = (existingPackages = {}, neededPackages = {}) =>
|
|
378
393
|
Object.entries(neededPackages)
|
|
379
394
|
.filter(([name]) => existingPackages[name] === undefined)
|
|
380
395
|
.map(([name, version]) => `${name}@${version}`)
|
|
381
396
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
397
|
+
/**
|
|
398
|
+
* When installing a function's dependencies, we first try to find a site-level
|
|
399
|
+
* `package.json` file. If we do, we look for any dependencies of the function
|
|
400
|
+
* that aren't already listed as dependencies of the site and install them. If
|
|
401
|
+
* we don't do this check, we may be upgrading the version of a module used in
|
|
402
|
+
* another part of the project, which we don't want to do.
|
|
403
|
+
*/
|
|
387
404
|
const installDeps = async ({ functionPackageJson, functionPath, functionsDir }) => {
|
|
388
405
|
const { dependencies: functionDependencies, devDependencies: functionDevDependencies } = require(functionPackageJson)
|
|
389
406
|
const sitePackageJson = await findUp('package.json', { cwd: functionsDir })
|
|
@@ -430,8 +447,8 @@ const installDeps = async ({ functionPackageJson, functionPath, functionsDir })
|
|
|
430
447
|
* @param {import('../base-command.mjs').default} command
|
|
431
448
|
* @param {import('commander').OptionValues} options
|
|
432
449
|
* @param {string} argumentName
|
|
433
|
-
* @param {string} functionsDir
|
|
434
|
-
* @param {
|
|
450
|
+
* @param {string} functionsDir Absolute path of the functions directory
|
|
451
|
+
* @param {'edge' | 'serverless'} funcType
|
|
435
452
|
*/
|
|
436
453
|
// eslint-disable-next-line max-params
|
|
437
454
|
const scaffoldFromTemplate = async function (command, options, argumentName, functionsDir, funcType) {
|
|
@@ -443,7 +460,7 @@ const scaffoldFromTemplate = async function (command, options, argumentName, fun
|
|
|
443
460
|
name: 'chosenUrl',
|
|
444
461
|
message: 'URL to clone: ',
|
|
445
462
|
type: 'input',
|
|
446
|
-
validate: (val) => Boolean(validateRepoURL(val)),
|
|
463
|
+
validate: (/** @type {string} */ val) => Boolean(validateRepoURL(val)),
|
|
447
464
|
// make sure it is not undefined and is a valid filename.
|
|
448
465
|
// this has some nuance i have ignored, eg crossenv and i18n concerns
|
|
449
466
|
},
|
|
@@ -506,7 +523,7 @@ const scaffoldFromTemplate = async function (command, options, argumentName, fun
|
|
|
506
523
|
}
|
|
507
524
|
|
|
508
525
|
if (funcType === 'edge') {
|
|
509
|
-
registerEFInToml(name)
|
|
526
|
+
registerEFInToml(name, command.netlify)
|
|
510
527
|
}
|
|
511
528
|
|
|
512
529
|
await installAddons(command, addons, path.resolve(functionPath))
|
|
@@ -631,9 +648,15 @@ const installAddons = async function (command, functionAddons, fnPath) {
|
|
|
631
648
|
return Promise.all(arr)
|
|
632
649
|
}
|
|
633
650
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
651
|
+
/**
|
|
652
|
+
*
|
|
653
|
+
* @param {string} funcName
|
|
654
|
+
* @param {import('../types.js').NetlifyOptions} options
|
|
655
|
+
*/
|
|
656
|
+
const registerEFInToml = async (funcName, options) => {
|
|
657
|
+
const { configFilePath, relConfigFilePath } = options
|
|
658
|
+
if (!fs.existsSync(configFilePath)) {
|
|
659
|
+
log(`${NETLIFYDEVLOG} \`${relConfigFilePath}\` file does not exist yet. Creating it...`)
|
|
637
660
|
}
|
|
638
661
|
|
|
639
662
|
let { funcPath } = await inquirer.prompt([
|
|
@@ -656,17 +679,22 @@ const registerEFInToml = async (funcName) => {
|
|
|
656
679
|
const functionRegister = `\n\n[[edge_functions]]\nfunction = "${funcName}"\npath = "${funcPath}"`
|
|
657
680
|
|
|
658
681
|
try {
|
|
659
|
-
fs.promises.appendFile(
|
|
682
|
+
fs.promises.appendFile(configFilePath, functionRegister)
|
|
660
683
|
log(
|
|
661
|
-
`${NETLIFYDEVLOG} Function '${funcName}' registered for route \`${funcPath}\`. To change, edit your \`
|
|
684
|
+
`${NETLIFYDEVLOG} Function '${funcName}' registered for route \`${funcPath}\`. To change, edit your \`${relConfigFilePath}\` file.`,
|
|
662
685
|
)
|
|
663
686
|
} catch {
|
|
664
|
-
error(`${NETLIFYDEVERR} Unable to register function. Please check your \`
|
|
687
|
+
error(`${NETLIFYDEVERR} Unable to register function. Please check your \`${relConfigFilePath}\` file.`)
|
|
665
688
|
}
|
|
666
689
|
}
|
|
667
690
|
|
|
668
|
-
|
|
669
|
-
|
|
691
|
+
/**
|
|
692
|
+
* we used to allow for a --dir command,
|
|
693
|
+
* but have retired that to force every scaffolded function to be a directory
|
|
694
|
+
* @param {string} functionsDir
|
|
695
|
+
* @param {string} name
|
|
696
|
+
* @returns
|
|
697
|
+
*/
|
|
670
698
|
const ensureFunctionPathIsOk = function (functionsDir, name) {
|
|
671
699
|
const functionPath = path.join(functionsDir, name)
|
|
672
700
|
if (fs.existsSync(functionPath)) {
|
|
@@ -678,6 +706,7 @@ const ensureFunctionPathIsOk = function (functionsDir, name) {
|
|
|
678
706
|
|
|
679
707
|
/**
|
|
680
708
|
* The functions:create command
|
|
709
|
+
* @param {string} name
|
|
681
710
|
* @param {import('commander').OptionValues} options
|
|
682
711
|
* @param {import('../base-command.mjs').default} command
|
|
683
712
|
*/
|