netlify-cli 15.9.0 → 15.9.1-rc.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.
Files changed (44) hide show
  1. package/npm-shrinkwrap.json +749 -147
  2. package/package.json +6 -7
  3. package/scripts/postinstall.mjs +8 -8
  4. package/src/commands/base-command.mjs +195 -59
  5. package/src/commands/deploy/deploy.mjs +21 -9
  6. package/src/commands/dev/dev.mjs +21 -15
  7. package/src/commands/functions/functions-build.mjs +2 -2
  8. package/src/commands/functions/functions-create.mjs +0 -3
  9. package/src/commands/functions/functions-invoke.mjs +8 -5
  10. package/src/commands/init/init.mjs +1 -1
  11. package/src/commands/link/link.mjs +5 -5
  12. package/src/commands/main.mjs +1 -1
  13. package/src/commands/serve/serve.mjs +11 -5
  14. package/src/commands/sites/sites-create-template.mjs +1 -1
  15. package/src/commands/sites/sites-create.mjs +1 -1
  16. package/src/lib/completion/generate-autocompletion.mjs +4 -4
  17. package/src/lib/edge-functions/bootstrap.mjs +1 -1
  18. package/src/lib/edge-functions/internal.mjs +5 -3
  19. package/src/lib/edge-functions/proxy.mjs +30 -5
  20. package/src/lib/functions/registry.mjs +1 -3
  21. package/src/lib/functions/runtimes/js/builders/zisi.mjs +3 -8
  22. package/src/lib/functions/server.mjs +6 -6
  23. package/src/lib/spinner.mjs +1 -1
  24. package/src/recipes/vscode/index.mjs +1 -1
  25. package/src/utils/build-info.mjs +19 -0
  26. package/src/utils/command-helpers.mjs +16 -7
  27. package/src/utils/deploy/hash-fns.mjs +1 -2
  28. package/src/utils/detect-server-settings.mjs +148 -200
  29. package/src/utils/execa.mjs +4 -2
  30. package/src/utils/framework-server.mjs +2 -2
  31. package/src/utils/functions/functions.mjs +7 -0
  32. package/src/utils/functions/get-functions.mjs +2 -2
  33. package/src/utils/get-repo-data.mjs +5 -6
  34. package/src/utils/init/config-github.mjs +2 -2
  35. package/src/utils/init/config-manual.mjs +24 -7
  36. package/src/utils/init/utils.mjs +62 -63
  37. package/src/utils/proxy-server.mjs +7 -4
  38. package/src/utils/proxy.mjs +4 -0
  39. package/src/utils/run-build.mjs +58 -7
  40. package/src/utils/shell.mjs +14 -3
  41. package/src/utils/state-config.mjs +5 -1
  42. package/src/utils/static-server.mjs +4 -0
  43. package/src/utils/telemetry/report-error.mjs +8 -4
  44. package/src/utils/init/frameworks.mjs +0 -23
@@ -1,6 +1,8 @@
1
1
  // @ts-check
2
2
  import { mkdir } from 'fs/promises'
3
3
 
4
+ import { zipFunctions } from '@netlify/zip-it-and-ship-it'
5
+
4
6
  import { NETLIFYDEVERR, NETLIFYDEVLOG, exit, log } from '../../utils/command-helpers.mjs'
5
7
  import { getFunctionsDir } from '../../utils/functions/index.mjs'
6
8
 
@@ -36,8 +38,6 @@ const functionsBuild = async (options, command) => {
36
38
 
37
39
  log(`${NETLIFYDEVLOG} Building functions`)
38
40
 
39
- const { zipFunctions } = await import('@netlify/zip-it-and-ship-it')
40
-
41
41
  zipFunctions(src, dst, { skipGo: true })
42
42
  log(`${NETLIFYDEVLOG} Functions built to `, dst)
43
43
  }
@@ -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
 
@@ -172,8 +171,6 @@ const pickTemplate = async function ({ language: languageFromFlag }, funcType) {
172
171
  language = languageFromPrompt
173
172
  }
174
173
 
175
- inquirer.registerPrompt('autocomplete', inquirerAutocompletePrompt)
176
-
177
174
  let templatesForLanguage
178
175
 
179
176
  try {
@@ -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
- /** process payloads from flag */
60
- const processPayloadFromFlag = function (payloadString) {
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(process.cwd(), payloadString)
69
+ const payloadpath = path.join(workingDir, payloadString)
67
70
  const pathexists = fs.existsSync(payloadpath)
68
71
  if (pathexists) {
69
72
  try {
@@ -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 {
@@ -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
  }
@@ -11,11 +11,11 @@ import { track } from '../../utils/telemetry/index.mjs'
11
11
 
12
12
  /**
13
13
  *
14
- * @param {import('../base-command.mjs').NetlifyOptions} netlify
14
+ * @param {import('../base-command.mjs').default} command
15
15
  * @param {import('commander').OptionValues} options
16
16
  */
17
- const linkPrompt = async (netlify, options) => {
18
- const { api, state } = netlify
17
+ const linkPrompt = async (command, options) => {
18
+ const { api, state } = command.netlify
19
19
 
20
20
  const SITE_NAME_PROMPT = 'Search by full or partial site name'
21
21
  const SITE_LIST_PROMPT = 'Choose from a list of your recently updated sites'
@@ -24,7 +24,7 @@ const linkPrompt = async (netlify, options) => {
24
24
  let GIT_REMOTE_PROMPT = 'Use the current git remote origin URL'
25
25
  let site
26
26
  // Get git remote data if exists
27
- const repoData = await getRepoData({ remoteName: options.gitRemoteName })
27
+ const repoData = await getRepoData({ workingDir: command.workingDir, remoteName: options.gitRemoteName })
28
28
 
29
29
  let linkChoices = [SITE_NAME_PROMPT, SITE_LIST_PROMPT, SITE_ID_PROMPT]
30
30
 
@@ -326,7 +326,7 @@ export const link = async (options, command) => {
326
326
  kind: 'byName',
327
327
  })
328
328
  } else {
329
- siteData = await linkPrompt(command.netlify, options)
329
+ siteData = await linkPrompt(command, options)
330
330
  }
331
331
  return siteData
332
332
  }
@@ -2,6 +2,7 @@
2
2
  import process from 'process'
3
3
 
4
4
  import { Option } from 'commander'
5
+ import envinfo from 'envinfo'
5
6
  import { closest } from 'fastest-levenshtein'
6
7
  import inquirer from 'inquirer'
7
8
 
@@ -39,7 +40,6 @@ const SUGGESTION_TIMEOUT = 1e4
39
40
  const getVersionPage = async () => {
40
41
  // performance optimization - load envinfo on demand
41
42
 
42
- const envinfo = await import('envinfo')
43
43
  const data = await envinfo.run({
44
44
  System: ['OS', 'CPU'],
45
45
  Binaries: ['Node', 'Yarn', 'npm'],
@@ -69,10 +69,10 @@ const serve = async (options, command) => {
69
69
  // Netlify Build are loaded.
70
70
  await getInternalFunctionsDir({ base: site.root, ensureExists: true })
71
71
 
72
- /** @type {Partial<import('../../utils/types').ServerSettings>} */
72
+ /** @type {Partial<import('../../utils/types.js').ServerSettings>} */
73
73
  let settings = {}
74
74
  try {
75
- settings = await detectServerSettings(devConfig, options, site.root)
75
+ settings = await detectServerSettings(devConfig, options, command)
76
76
 
77
77
  cachedConfig.config = getConfigWithPlugins(cachedConfig.config, settings)
78
78
  } catch (error_) {
@@ -87,7 +87,13 @@ const serve = async (options, command) => {
87
87
  `${NETLIFYDEVWARN} Changes will not be hot-reloaded, so if you need to rebuild your site you must exit and run 'netlify serve' again`,
88
88
  )
89
89
 
90
- const { configPath: configPathOverride } = await runBuildTimeline({ cachedConfig, options, settings, site })
90
+ const { configPath: configPathOverride } = await runBuildTimeline({
91
+ cachedConfig,
92
+ options,
93
+ settings,
94
+ projectDir: command.workingDir,
95
+ site,
96
+ })
91
97
 
92
98
  await startFunctionsServer({
93
99
  api,
@@ -117,8 +123,7 @@ const serve = async (options, command) => {
117
123
 
118
124
  // TODO: We should consolidate this with the existing config watcher.
119
125
  const getUpdatedConfig = async () => {
120
- const cwd = options.cwd || process.cwd()
121
- const { config: newConfig } = await command.getConfig({ cwd, offline: true, state })
126
+ const { config: newConfig } = await command.getConfig({ cwd: command.workingDir, offline: true, state })
122
127
  const normalizedNewConfig = normalizeConfig(newConfig)
123
128
 
124
129
  return normalizedNewConfig
@@ -135,6 +140,7 @@ const serve = async (options, command) => {
135
140
  getUpdatedConfig,
136
141
  inspectSettings,
137
142
  offline: options.offline,
143
+ projectDir: command.workingDir,
138
144
  settings,
139
145
  site,
140
146
  siteInfo,
@@ -197,7 +197,7 @@ const sitesCreateTemplate = async (repository, options, command) => {
197
197
 
198
198
  if (options.withCi) {
199
199
  log('Configuring CI')
200
- const repoData = await getRepoData()
200
+ const repoData = await getRepoData({ workingDir: command.workingDir })
201
201
  await configureRepo({ command, siteId: site.id, repoData, manual: options.manual })
202
202
  }
203
203
 
@@ -102,7 +102,7 @@ export const sitesCreate = async (options, command) => {
102
102
 
103
103
  if (options.withCi) {
104
104
  log('Configuring CI')
105
- const repoData = await getRepoData()
105
+ const repoData = await getRepoData({ workingDir: command.workingDir })
106
106
  await configureRepo({ command, siteId: site.id, repoData, manual: options.manual })
107
107
  }
108
108
 
@@ -1,5 +1,5 @@
1
1
  // @ts-check
2
- import { existsSync, mkdirSync, writeFileSync } from 'fs'
2
+ import fs from 'fs'
3
3
  import { dirname } from 'path'
4
4
 
5
5
  import { sortOptions, warn } from '../../utils/command-helpers.mjs'
@@ -28,10 +28,10 @@ const generateAutocompletion = (program) => {
28
28
  {},
29
29
  )
30
30
 
31
- if (!existsSync(dirname(AUTOCOMPLETION_FILE))) {
32
- mkdirSync(dirname(AUTOCOMPLETION_FILE), { recursive: true })
31
+ if (!fs.existsSync(dirname(AUTOCOMPLETION_FILE))) {
32
+ fs.mkdirSync(dirname(AUTOCOMPLETION_FILE), { recursive: true })
33
33
  }
34
- writeFileSync(AUTOCOMPLETION_FILE, JSON.stringify(autocomplete), 'utf-8')
34
+ fs.writeFileSync(AUTOCOMPLETION_FILE, JSON.stringify(autocomplete), 'utf-8')
35
35
  } catch (error_) {
36
36
  // Sometimes it can happen that the autocomplete generation in the postinstall script lacks permissions
37
37
  // to write files to the home directory of the user. Therefore just warn with the error and don't break install.
@@ -1,5 +1,5 @@
1
1
  import { env } from 'process'
2
2
 
3
- const latestBootstrapURL = 'https://6494585a67d46e0008867e60--edge.netlify.com/bootstrap/index-combined.ts'
3
+ const latestBootstrapURL = 'https://64ae60d920fd0f000865bcfc--edge.netlify.com/bootstrap/index-combined.ts'
4
4
 
5
5
  export const getBootstrapURL = () => env.NETLIFY_EDGE_BOOTSTRAP || latestBootstrapURL
@@ -1,14 +1,16 @@
1
1
  // @ts-check
2
2
  import { readFile, stat } from 'fs/promises'
3
3
  import { dirname, join, resolve } from 'path'
4
- import { cwd } from 'process'
5
4
 
6
5
  import { getPathInProject } from '../settings.mjs'
7
6
 
8
7
  import { INTERNAL_EDGE_FUNCTIONS_FOLDER } from './consts.mjs'
9
8
 
10
- export const getInternalFunctions = async () => {
11
- const path = join(cwd(), getPathInProject([INTERNAL_EDGE_FUNCTIONS_FOLDER]))
9
+ /**
10
+ * @param {string} workingDir
11
+ */
12
+ export const getInternalFunctions = async (workingDir) => {
13
+ const path = join(workingDir, getPathInProject([INTERNAL_EDGE_FUNCTIONS_FOLDER]))
12
14
 
13
15
  try {
14
16
  const stats = await stat(path)
@@ -1,8 +1,10 @@
1
1
  // @ts-check
2
2
  import { Buffer } from 'buffer'
3
3
  import { relative } from 'path'
4
- import { cwd, env } from 'process'
4
+ import { env } from 'process'
5
5
 
6
+ // eslint-disable-next-line import/no-namespace
7
+ import * as bundler from '@netlify/edge-bundler'
6
8
  import getAvailablePort from 'get-port'
7
9
 
8
10
  import { NETLIFYDEVERR, NETLIFYDEVWARN, chalk, error as printError, log } from '../../utils/command-helpers.mjs'
@@ -60,6 +62,26 @@ export const createAccountInfoHeader = (accountInfo = {}) => {
60
62
  return Buffer.from(accountString).toString('base64')
61
63
  }
62
64
 
65
+ /**
66
+ *
67
+ * @param {object} config
68
+ * @param {*} config.accountId
69
+ * @param {*} config.config
70
+ * @param {*} config.configPath
71
+ * @param {*} config.debug
72
+ * @param {*} config.env
73
+ * @param {*} config.geoCountry
74
+ * @param {*} config.geolocationMode
75
+ * @param {*} config.getUpdatedConfig
76
+ * @param {*} config.inspectSettings
77
+ * @param {*} config.mainPort
78
+ * @param {boolean=} config.offline
79
+ * @param {*} config.passthroughPort
80
+ * @param {*} config.projectDir
81
+ * @param {*} config.siteInfo
82
+ * @param {*} config.state
83
+ * @returns
84
+ */
63
85
  export const initializeProxy = async ({
64
86
  accountId,
65
87
  config,
@@ -77,7 +99,11 @@ export const initializeProxy = async ({
77
99
  siteInfo,
78
100
  state,
79
101
  }) => {
80
- const { functions: internalFunctions, importMap, path: internalFunctionsPath } = await getInternalFunctions()
102
+ const {
103
+ functions: internalFunctions,
104
+ importMap,
105
+ path: internalFunctionsPath,
106
+ } = await getInternalFunctions(projectDir)
81
107
  const userFunctionsPath = config.build.edge_functions
82
108
  const isolatePort = await getAvailablePort()
83
109
 
@@ -112,7 +138,7 @@ export const initializeProxy = async ({
112
138
  if (!registry) return
113
139
 
114
140
  // Setting header with geolocation and site info.
115
- req.headers[headers.Geo] = JSON.stringify(geoLocation)
141
+ req.headers[headers.Geo] = Buffer.from(JSON.stringify(geoLocation)).toString('base64')
116
142
  req.headers[headers.Site] = createSiteInfoHeader(siteInfo)
117
143
  req.headers[headers.Account] = createAccountInfoHeader({ id: accountId })
118
144
 
@@ -130,7 +156,7 @@ export const initializeProxy = async ({
130
156
  )} matches declaration for edge function ${chalk.yellow(
131
157
  functionName,
132
158
  )}, but there's no matching function file in ${chalk.yellow(
133
- relative(cwd(), userFunctionsPath),
159
+ relative(projectDir, userFunctionsPath),
134
160
  )}. Please visit ${chalk.blue('https://ntl.fyi/edge-create')} for more information.`,
135
161
  )
136
162
  })
@@ -185,7 +211,6 @@ const prepareServer = async ({
185
211
  const importMapPaths = [...importMaps, config.functions['*'].deno_import_map]
186
212
 
187
213
  try {
188
- const bundler = await import('@netlify/edge-bundler')
189
214
  const distImportMapPath = getPathInProject([DIST_IMPORT_MAP_PATH])
190
215
  const runIsolate = await bundler.serve({
191
216
  ...getDownloadUpdateFunctions(),
@@ -3,6 +3,7 @@ import { mkdir } from 'fs/promises'
3
3
  import { extname, isAbsolute, join, resolve } from 'path'
4
4
  import { env } from 'process'
5
5
 
6
+ import { listFunctions } from '@netlify/zip-it-and-ship-it'
6
7
  import extractZip from 'extract-zip'
7
8
 
8
9
  import {
@@ -175,9 +176,6 @@ export class FunctionsRegistry {
175
176
  // This function is here so we can mock it in tests
176
177
  // eslint-disable-next-line class-methods-use-this
177
178
  async listFunctions(...args) {
178
- // Performance optimization: load '@netlify/zip-it-and-ship-it' on demand.
179
- const { listFunctions } = await import('@netlify/zip-it-and-ship-it')
180
-
181
179
  return await listFunctions(...args)
182
180
  }
183
181
 
@@ -2,6 +2,7 @@ import { mkdir, writeFile } from 'fs/promises'
2
2
  import { createRequire } from 'module'
3
3
  import path from 'path'
4
4
 
5
+ import { zipFunction, listFunction } from '@netlify/zip-it-and-ship-it'
5
6
  import decache from 'decache'
6
7
  import { readPackageUp } from 'read-pkg-up'
7
8
  import sourceMapSupport from 'source-map-support'
@@ -44,9 +45,6 @@ const buildFunction = async ({
44
45
  }
45
46
  const functionDirectory = path.dirname(func.mainFile)
46
47
 
47
- // performance
48
- const { zipFunction } = await import('@netlify/zip-it-and-ship-it')
49
-
50
48
  // If we have a function at `functions/my-func/index.js` and we pass
51
49
  // that path to `zipFunction`, it will lack the context of the whole
52
50
  // functions directory and will infer the name of the function to be
@@ -92,15 +90,12 @@ const buildFunction = async ({
92
90
  * @param {string} params.mainFile
93
91
  * @param {string} params.projectRoot
94
92
  */
95
- export const parseFunctionForMetadata = async ({ config, mainFile, projectRoot }) => {
96
- const { listFunction } = await import('@netlify/zip-it-and-ship-it')
97
-
98
- return await listFunction(mainFile, {
93
+ export const parseFunctionForMetadata = async ({ config, mainFile, projectRoot }) =>
94
+ await listFunction(mainFile, {
99
95
  config: netlifyConfigToZisiConfig({ config, projectRoot }),
100
96
  featureFlags: { zisi_functions_api_v2: true },
101
97
  parseISC: true,
102
98
  })
103
- }
104
99
 
105
100
  // Clears the cache for any files inside the directory from which functions are
106
101
  // served.
@@ -1,4 +1,8 @@
1
1
  // @ts-check
2
+ import { Buffer } from 'buffer'
3
+
4
+ import express from 'express'
5
+ import expressLogging from 'express-logging'
2
6
  import jwtDecode from 'jwt-decode'
3
7
 
4
8
  import { NETLIFYDEVERR, NETLIFYDEVLOG, error as errorExit, log } from '../../utils/command-helpers.mjs'
@@ -45,7 +49,6 @@ const hasBody = (req) =>
45
49
  // eslint-disable-next-line unicorn/prefer-number-properties
46
50
  (req.header('transfer-encoding') !== undefined || !isNaN(req.header('content-length'))) &&
47
51
  // we expect a string or a buffer, because we use the two bodyParsers(text, raw) from express
48
- // eslint-disable-next-line n/prefer-global/buffer
49
52
  (typeof req.body === 'string' || Buffer.isBuffer(req.body))
50
53
 
51
54
  export const createHandler = function (options) {
@@ -112,7 +115,7 @@ export const createHandler = function (options) {
112
115
  'client-ip': [remoteAddress],
113
116
  'x-nf-client-connection-ip': [remoteAddress],
114
117
  'x-nf-account-id': [options.accountId],
115
- [efHeaders.Geo]: JSON.stringify(geoLocation),
118
+ [efHeaders.Geo]: Buffer.from(JSON.stringify(geoLocation)).toString('base64'),
116
119
  }).reduce((prev, [key, value]) => ({ ...prev, [key]: Array.isArray(value) ? value : [value] }), {})
117
120
  const rawQuery = new URLSearchParams(requestQuery).toString()
118
121
  const protocol = options.config?.dev?.https ? 'https' : 'http'
@@ -185,11 +188,8 @@ export const createHandler = function (options) {
185
188
  }
186
189
  }
187
190
 
188
- const getFunctionsServer = async function (options) {
191
+ const getFunctionsServer = (options) => {
189
192
  const { buildersPrefix = '', functionsPrefix = '', functionsRegistry, siteUrl } = options
190
- // performance optimization, load express on demand
191
- const { default: express } = await import('express')
192
- const { default: expressLogging } = await import('express-logging')
193
193
  const app = express()
194
194
  const functionHandler = createHandler(options)
195
195
 
@@ -17,7 +17,7 @@ export const startSpinner = ({ text }) =>
17
17
  * Stops the spinner with the following text
18
18
  * @param {object} config
19
19
  * @param {ora.Ora} config.spinner
20
- * @param {object} [config.error]
20
+ * @param {boolean} [config.error]
21
21
  * @param {string} [config.text]
22
22
  * @returns {void}
23
23
  */
@@ -1,5 +1,6 @@
1
1
  import { join } from 'path'
2
2
 
3
+ import { DenoBridge } from '@netlify/edge-bundler'
3
4
  import execa from 'execa'
4
5
  import inquirer from 'inquirer'
5
6
 
@@ -49,7 +50,6 @@ const getDenoExtPrompt = () => {
49
50
  }
50
51
 
51
52
  export const run = async ({ config, repositoryRoot }) => {
52
- const { DenoBridge } = await import('@netlify/edge-bundler')
53
53
  const deno = new DenoBridge({
54
54
  onBeforeDownload: () =>
55
55
  log(`${NETLIFYDEVWARN} Setting up the Edge Functions environment. This may take a couple of minutes.`),
@@ -0,0 +1,19 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * Detects and filters the build setting for a project and a command
5
+ * @param {import('../commands/base-command.mjs').default} command
6
+ */
7
+ export const detectBuildSettings = async (command) => {
8
+ const { project, workspacePackage } = command
9
+ const buildSettings = await project.getBuildSettings(project.workspace ? workspacePackage : '')
10
+ return buildSettings
11
+ .filter((setting) => {
12
+ if (project.workspace && project.relativeBaseDirectory && setting.packagePath) {
13
+ return project.relativeBaseDirectory.startsWith(setting.packagePath)
14
+ }
15
+
16
+ return true
17
+ })
18
+ .filter((setting) => setting.devCommand)
19
+ }
@@ -24,7 +24,7 @@ const argv = process.argv.slice(2)
24
24
  * Chalk instance for CLI that can be initialized with no colors mode
25
25
  * needed for json outputs where we don't want to have colors
26
26
  * @param {boolean} noColors - disable chalk colors
27
- * @return {object} - default or custom chalk instance
27
+ * @return {import('chalk').ChalkInstance} - default or custom chalk instance
28
28
  */
29
29
  const safeChalk = function (noColors) {
30
30
  if (noColors) {
@@ -174,12 +174,18 @@ export const warn = (message = '') => {
174
174
 
175
175
  /**
176
176
  * throws an error or log it
177
- * @param {string|Error} message
177
+ * @param {unknown} message
178
178
  * @param {object} [options]
179
179
  * @param {boolean} [options.exit]
180
180
  */
181
181
  export const error = (message = '', options = {}) => {
182
- const err = message instanceof Error ? message : new Error(message)
182
+ const err =
183
+ message instanceof Error
184
+ ? message
185
+ : // eslint-disable-next-line unicorn/no-nested-ternary
186
+ typeof message === 'string'
187
+ ? new Error(message)
188
+ : /** @type {Error} */ ({ message, stack: undefined, name: 'Error' })
183
189
 
184
190
  if (options.exit === false) {
185
191
  const bang = chalk.red(BANG)
@@ -198,10 +204,13 @@ export const exit = (code = 0) => {
198
204
  process.exit(code)
199
205
  }
200
206
 
201
- // When `build.publish` is not set by the user, the CLI behavior differs in
202
- // several ways. It detects it by checking if `build.publish` is `undefined`.
203
- // However, `@netlify/config` adds a default value to `build.publish`.
204
- // This removes 'publish' and 'publishOrigin' in this case.
207
+ /**
208
+ * When `build.publish` is not set by the user, the CLI behavior differs in
209
+ * several ways. It detects it by checking if `build.publish` is `undefined`.
210
+ * However, `@netlify/config` adds a default value to `build.publish`.
211
+ * This removes 'publish' and 'publishOrigin' in this case.
212
+ * @param {*} config
213
+ */
205
214
  export const normalizeConfig = (config) => {
206
215
  // Unused var here is in order to omit 'publish' from build config
207
216
  // eslint-disable-next-line no-unused-vars
@@ -2,6 +2,7 @@ import { readFile } from 'fs/promises'
2
2
  import path from 'path'
3
3
  import { promisify } from 'util'
4
4
 
5
+ import { zipFunctions } from '@netlify/zip-it-and-ship-it'
5
6
  import fromArray from 'from2-array'
6
7
  import pumpModule from 'pump'
7
8
 
@@ -66,8 +67,6 @@ const getFunctionZips = async ({
66
67
  })
67
68
  }
68
69
 
69
- const { zipFunctions } = await import('@netlify/zip-it-and-ship-it')
70
-
71
70
  return await zipFunctions(directories, tmpDir, {
72
71
  basePath: rootDir,
73
72
  configFileDirectories: [getPathInProject([INTERNAL_FUNCTIONS_FOLDER])],