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,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
|
|
@@ -128,14 +129,15 @@ const validateDeployFolder = async ({ deployFolder }) => {
|
|
|
128
129
|
* @param {import('commander').OptionValues} config.options
|
|
129
130
|
* @param {object} config.site
|
|
130
131
|
* @param {object} config.siteData
|
|
132
|
+
* @param {string} config.workingDir // The process working directory
|
|
131
133
|
* @returns {string}
|
|
132
134
|
*/
|
|
133
|
-
const getFunctionsFolder = ({ config, options, site, siteData }) => {
|
|
135
|
+
const getFunctionsFolder = ({ config, options, site, siteData, workingDir }) => {
|
|
134
136
|
let functionsFolder
|
|
135
137
|
// Support "functions" and "Functions"
|
|
136
138
|
const funcConfig = config.functionsDirectory
|
|
137
139
|
if (options.functions) {
|
|
138
|
-
functionsFolder = resolve(
|
|
140
|
+
functionsFolder = resolve(workingDir, options.functions)
|
|
139
141
|
} else if (funcConfig) {
|
|
140
142
|
functionsFolder = resolve(site.root, funcConfig)
|
|
141
143
|
} else if (siteData?.build_settings?.functions_dir) {
|
|
@@ -178,12 +180,21 @@ const validateFolders = async ({ deployFolder, functionsFolder }) => {
|
|
|
178
180
|
return { deployFolderStat, functionsFolderStat }
|
|
179
181
|
}
|
|
180
182
|
|
|
183
|
+
/**
|
|
184
|
+
* @param {object} config
|
|
185
|
+
* @param {string} config.deployFolder
|
|
186
|
+
* @param {*} config.site
|
|
187
|
+
* @returns
|
|
188
|
+
*/
|
|
181
189
|
const getDeployFilesFilter = ({ deployFolder, site }) => {
|
|
182
190
|
// site.root === deployFolder can happen when users run `netlify deploy --dir .`
|
|
183
191
|
// in that specific case we don't want to publish the repo node_modules
|
|
184
192
|
// when site.root !== deployFolder the behaviour matches our buildbot
|
|
185
193
|
const skipNodeModules = site.root === deployFolder
|
|
186
194
|
|
|
195
|
+
/**
|
|
196
|
+
* @param {string} filename
|
|
197
|
+
*/
|
|
187
198
|
return (filename) => {
|
|
188
199
|
if (filename == null) {
|
|
189
200
|
return false
|
|
@@ -496,6 +507,7 @@ const printResults = ({ deployToProduction, json, results, runBuildCommand }) =>
|
|
|
496
507
|
* @param {import('../base-command.mjs').default} command
|
|
497
508
|
*/
|
|
498
509
|
const deploy = async (options, command) => {
|
|
510
|
+
const { workingDir } = command
|
|
499
511
|
const { api, site, siteInfo } = command.netlify
|
|
500
512
|
const alias = options.alias || options.branch
|
|
501
513
|
|
|
@@ -560,16 +572,18 @@ const deploy = async (options, command) => {
|
|
|
560
572
|
siteInfo: siteData,
|
|
561
573
|
})
|
|
562
574
|
}
|
|
575
|
+
|
|
563
576
|
const { configMutations = [], newConfig } = await handleBuild({
|
|
564
577
|
cachedConfig: command.netlify.cachedConfig,
|
|
565
578
|
options,
|
|
566
579
|
})
|
|
567
580
|
const config = newConfig || command.netlify.config
|
|
568
581
|
|
|
569
|
-
const deployFolder = await getDeployFolder({ options, config, site, siteData })
|
|
570
|
-
const functionsFolder = getFunctionsFolder({ options, config, site, siteData })
|
|
582
|
+
const deployFolder = await getDeployFolder({ workingDir, options, config, site, siteData })
|
|
583
|
+
const functionsFolder = getFunctionsFolder({ workingDir, options, config, site, siteData })
|
|
571
584
|
const { configPath } = site
|
|
572
585
|
const edgeFunctionsConfig = command.netlify.config.edge_functions
|
|
586
|
+
console.log({ functionsFolder, edgeFunctionsConfig })
|
|
573
587
|
|
|
574
588
|
// build flag wasn't used and edge functions exist
|
|
575
589
|
if (!options.build && edgeFunctionsConfig && edgeFunctionsConfig.length !== 0) {
|
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
|
*/
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import fs from 'fs'
|
|
3
3
|
import { createRequire } from 'module'
|
|
4
4
|
import path from 'path'
|
|
5
|
-
import process from 'process'
|
|
6
5
|
|
|
7
6
|
import inquirer from 'inquirer'
|
|
8
7
|
import fetch from 'node-fetch'
|
|
@@ -56,14 +55,18 @@ const formatQstring = function (querystring) {
|
|
|
56
55
|
return ''
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
/**
|
|
60
|
-
|
|
58
|
+
/**
|
|
59
|
+
* process payloads from flag
|
|
60
|
+
* @param {string} payloadString
|
|
61
|
+
* @param {string} workingDir
|
|
62
|
+
*/
|
|
63
|
+
const processPayloadFromFlag = function (payloadString, workingDir) {
|
|
61
64
|
if (payloadString) {
|
|
62
65
|
// case 1: jsonstring
|
|
63
66
|
let payload = tryParseJSON(payloadString)
|
|
64
67
|
if (payload) return payload
|
|
65
68
|
// case 2: jsonpath
|
|
66
|
-
const payloadpath = path.join(
|
|
69
|
+
const payloadpath = path.join(workingDir, payloadString)
|
|
67
70
|
const pathexists = fs.existsSync(payloadpath)
|
|
68
71
|
if (pathexists) {
|
|
69
72
|
try {
|
|
@@ -141,11 +144,11 @@ const getFunctionToTrigger = function (options, argumentName) {
|
|
|
141
144
|
* @param {import('../base-command.mjs').default} command
|
|
142
145
|
*/
|
|
143
146
|
const functionsInvoke = async (nameArgument, options, command) => {
|
|
144
|
-
const { config } = command.netlify
|
|
147
|
+
const { config, relConfigFilePath } = command.netlify
|
|
145
148
|
|
|
146
149
|
const functionsDir = options.functions || (config.dev && config.dev.functions) || config.functionsDirectory
|
|
147
150
|
if (typeof functionsDir === 'undefined') {
|
|
148
|
-
error(
|
|
151
|
+
error(`functions directory is undefined, did you forget to set it in ${relConfigFilePath}?`)
|
|
149
152
|
}
|
|
150
153
|
|
|
151
154
|
if (!options.port)
|
|
@@ -210,7 +213,7 @@ const functionsInvoke = async (nameArgument, options, command) => {
|
|
|
210
213
|
// }
|
|
211
214
|
}
|
|
212
215
|
}
|
|
213
|
-
const payload = processPayloadFromFlag(options.payload)
|
|
216
|
+
const payload = processPayloadFromFlag(options.payload, command.workingDir)
|
|
214
217
|
body = { ...body, ...payload }
|
|
215
218
|
|
|
216
219
|
try {
|
|
@@ -16,7 +16,7 @@ const normalizeFunction = function (deployedFunctions, { name, urlPath: url }) {
|
|
|
16
16
|
* @param {import('../base-command.mjs').default} command
|
|
17
17
|
*/
|
|
18
18
|
const functionsList = async (options, command) => {
|
|
19
|
-
const { config, siteInfo } = command.netlify
|
|
19
|
+
const { config, relConfigFilePath, siteInfo } = command.netlify
|
|
20
20
|
|
|
21
21
|
const deploy = siteInfo.published_deploy || {}
|
|
22
22
|
const deployedFunctions = deploy.available_functions || []
|
|
@@ -25,7 +25,7 @@ const functionsList = async (options, command) => {
|
|
|
25
25
|
|
|
26
26
|
if (typeof functionsDir === 'undefined') {
|
|
27
27
|
log('Functions directory is undefined')
|
|
28
|
-
log(
|
|
28
|
+
log(`Please verify functions.directory is set in your Netlify configuration file ${relConfigFilePath}`)
|
|
29
29
|
log('See https://docs.netlify.com/configure-builds/file-based-configuration/ for more information')
|
|
30
30
|
exit(1)
|
|
31
31
|
}
|
|
@@ -196,7 +196,7 @@ export const init = async (options, command) => {
|
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
// Look for local repo
|
|
199
|
-
const repoData = await getRepoData({ remoteName: options.gitRemoteName })
|
|
199
|
+
const repoData = await getRepoData({ workingDir: command.workingDir, remoteName: options.gitRemoteName })
|
|
200
200
|
if (repoData.error) {
|
|
201
201
|
await handleNoGitRemoteAndExit({ command, error: repoData.error, state })
|
|
202
202
|
}
|