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.
Files changed (44) hide show
  1. package/bin/run.mjs +6 -5
  2. package/npm-shrinkwrap.json +628 -42
  3. package/package.json +4 -5
  4. package/src/commands/base-command.mjs +295 -118
  5. package/src/commands/build/build.mjs +9 -1
  6. package/src/commands/deploy/deploy.mjs +42 -18
  7. package/src/commands/dev/dev.mjs +22 -17
  8. package/src/commands/functions/functions-create.mjs +118 -89
  9. package/src/commands/functions/functions-invoke.mjs +10 -7
  10. package/src/commands/functions/functions-list.mjs +3 -3
  11. package/src/commands/functions/functions-serve.mjs +1 -0
  12. package/src/commands/init/init.mjs +1 -1
  13. package/src/commands/link/link.mjs +5 -5
  14. package/src/commands/serve/serve.mjs +10 -6
  15. package/src/commands/sites/sites-create-template.mjs +1 -1
  16. package/src/commands/sites/sites-create.mjs +1 -1
  17. package/src/functions-templates/javascript/google-analytics/package.json +1 -1
  18. package/src/functions-templates/typescript/scheduled-function/package.json +1 -1
  19. package/src/lib/edge-functions/deploy.mjs +11 -4
  20. package/src/lib/edge-functions/internal.mjs +5 -3
  21. package/src/lib/edge-functions/proxy.mjs +29 -5
  22. package/src/lib/functions/runtimes/js/builders/zisi.mjs +20 -3
  23. package/src/lib/functions/server.mjs +3 -2
  24. package/src/lib/spinner.mjs +1 -1
  25. package/src/recipes/vscode/index.mjs +24 -6
  26. package/src/utils/build-info.mjs +100 -0
  27. package/src/utils/command-helpers.mjs +16 -7
  28. package/src/utils/deploy/deploy-site.mjs +4 -4
  29. package/src/utils/deploy/hash-fns.mjs +2 -2
  30. package/src/utils/detect-server-settings.mjs +133 -245
  31. package/src/utils/framework-server.mjs +6 -5
  32. package/src/utils/functions/functions.mjs +8 -5
  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 +68 -68
  37. package/src/utils/proxy-server.mjs +7 -4
  38. package/src/utils/proxy.mjs +4 -3
  39. package/src/utils/read-repo-url.mjs +4 -0
  40. package/src/utils/run-build.mjs +58 -32
  41. package/src/utils/shell.mjs +24 -7
  42. package/src/utils/state-config.mjs +5 -1
  43. package/src/utils/static-server.mjs +4 -0
  44. package/src/utils/init/frameworks.mjs +0 -23
@@ -5,7 +5,12 @@ import { exit, log } from '../command-helpers.mjs'
5
5
 
6
6
  import { createDeployKey, getBuildSettings, saveNetlifyToml, setupSite } from './utils.mjs'
7
7
 
8
- const addDeployKey = async ({ deployKey }) => {
8
+ /**
9
+ * Prompts for granting the netlify ssh public key access to your repo
10
+ * @param {object} deployKey
11
+ * @param {string} deployKey.public_key
12
+ */
13
+ const addDeployKey = async (deployKey) => {
9
14
  log('\nGive this Netlify SSH public key access to your repository:\n')
10
15
  log(`\n${deployKey.public_key}\n\n`)
11
16
 
@@ -23,6 +28,11 @@ const addDeployKey = async ({ deployKey }) => {
23
28
  }
24
29
  }
25
30
 
31
+ /**
32
+ * @param {object} config
33
+ * @param {Awaited<ReturnType<import('../../utils/get-repo-data.mjs').default>>} config.repoData
34
+ * @returns {Promise<string>}
35
+ */
26
36
  const getRepoPath = async ({ repoData }) => {
27
37
  const { repoPath } = await inquirer.prompt([
28
38
  {
@@ -30,6 +40,9 @@ const getRepoPath = async ({ repoData }) => {
30
40
  name: 'repoPath',
31
41
  message: 'The SSH URL of the remote git repo:',
32
42
  default: repoData.url,
43
+ /**
44
+ * @param {string} url
45
+ */
33
46
  validate: (url) => SSH_URL_REGEXP.test(url) || 'The URL provided does not use the SSH protocol',
34
47
  },
35
48
  ])
@@ -37,7 +50,11 @@ const getRepoPath = async ({ repoData }) => {
37
50
  return repoPath
38
51
  }
39
52
 
40
- const addDeployHook = async ({ deployHook }) => {
53
+ /**
54
+ * @param {string} deployHook
55
+ * @returns
56
+ */
57
+ const addDeployHook = async (deployHook) => {
41
58
  log('\nConfigure the following webhook for your repository:\n')
42
59
  log(`\n${deployHook}\n\n`)
43
60
  const { deployHookAdded } = await inquirer.prompt([
@@ -55,14 +72,14 @@ const addDeployHook = async ({ deployHook }) => {
55
72
  /**
56
73
  * @param {object} config
57
74
  * @param {import('../../commands/base-command.mjs').default} config.command
58
- * @param {*} config.repoData
75
+ * @param {Awaited<ReturnType<import('../../utils/get-repo-data.mjs').default>>} config.repoData
59
76
  * @param {string} config.siteId
60
77
  */
61
78
  export default async function configManual({ command, repoData, siteId }) {
62
79
  const { netlify } = command
63
80
  const {
64
81
  api,
65
- cachedConfig: { configPath, env },
82
+ cachedConfig: { configPath },
66
83
  config,
67
84
  repositoryRoot,
68
85
  site: { root: siteRoot },
@@ -72,12 +89,12 @@ export default async function configManual({ command, repoData, siteId }) {
72
89
  repositoryRoot,
73
90
  siteRoot,
74
91
  config,
75
- env,
92
+ command,
76
93
  })
77
94
  await saveNetlifyToml({ repositoryRoot, config, configPath, baseDir, buildCmd, buildDir, functionsDir })
78
95
 
79
96
  const deployKey = await createDeployKey({ api })
80
- await addDeployKey({ deployKey })
97
+ await addDeployKey(deployKey)
81
98
 
82
99
  const repoPath = await getRepoPath({ repoData })
83
100
  const repo = {
@@ -99,7 +116,7 @@ export default async function configManual({ command, repoData, siteId }) {
99
116
  configPlugins: config.plugins,
100
117
  pluginsToInstall,
101
118
  })
102
- const deployHookAdded = await addDeployHook({ deployHook: updatedSite.deploy_hook })
119
+ const deployHookAdded = await addDeployHook(updatedSite.deploy_hook)
103
120
  if (!deployHookAdded) {
104
121
  exit()
105
122
  }
@@ -1,66 +1,75 @@
1
1
  // @ts-check
2
2
  import { writeFile } from 'fs/promises'
3
3
  import path from 'path'
4
- import process from 'process'
5
4
 
6
5
  import cleanDeep from 'clean-deep'
7
6
  import inquirer from 'inquirer'
8
7
 
9
8
  import { fileExistsAsync } from '../../lib/fs.mjs'
10
9
  import { normalizeBackslash } from '../../lib/path.mjs'
10
+ import { detectBuildSettings } from '../build-info.mjs'
11
11
  import { chalk, error as failAndExit, log, warn } from '../command-helpers.mjs'
12
12
 
13
- import { getFrameworkInfo } from './frameworks.mjs'
14
- import { detectNodeVersion } from './node-version.mjs'
15
13
  import { getRecommendPlugins, getUIPlugins } from './plugins.mjs'
16
14
 
17
- const normalizeDir = ({ baseDirectory, defaultValue, dir }) => {
18
- if (dir === undefined) {
19
- return defaultValue
20
- }
21
-
22
- const relativeDir = path.relative(baseDirectory, dir)
23
- return relativeDir || defaultValue
24
- }
25
-
26
- const getDefaultBase = ({ baseDirectory, repositoryRoot }) => {
27
- if (baseDirectory !== repositoryRoot && baseDirectory.startsWith(repositoryRoot)) {
28
- return path.relative(repositoryRoot, baseDirectory)
29
- }
30
- }
15
+ // these plugins represent runtimes that are
16
+ // expected to be "automatically" installed. Even though
17
+ // they can be installed on package/toml, we always
18
+ // want them installed in the site settings. When installed
19
+ // there our build will automatically install the latest without
20
+ // user management of the versioning.
21
+ const pluginsToAlwaysInstall = new Set(['@netlify/plugin-nextjs'])
22
+
23
+ /**
24
+ * Retrieve a list of plugins to auto install
25
+ * @param {string[]=} pluginsInstalled
26
+ * @param {string[]=} pluginsRecommended
27
+ * @returns
28
+ */
29
+ export const getPluginsToAutoInstall = (pluginsInstalled = [], pluginsRecommended = []) =>
30
+ pluginsRecommended.reduce(
31
+ (acc, plugin) =>
32
+ pluginsInstalled.includes(plugin) && !pluginsToAlwaysInstall.has(plugin) ? acc : [...acc, plugin],
33
+
34
+ /** @type {string[]} */ ([]),
35
+ )
31
36
 
32
- const getDefaultSettings = ({
33
- baseDirectory,
34
- config,
35
- frameworkBuildCommand,
36
- frameworkBuildDir,
37
- frameworkPlugins,
38
- repositoryRoot,
39
- }) => {
40
- const recommendedPlugins = getRecommendPlugins(frameworkPlugins, config)
41
- const {
42
- command: defaultBuildCmd = frameworkBuildCommand,
43
- functions: defaultFunctionsDir,
44
- publish: defaultBuildDir = frameworkBuildDir,
45
- } = config.build
37
+ /**
38
+ *
39
+ * @param {Partial<import('@netlify/build-info').Settings>} settings
40
+ * @param {*} config
41
+ * @param {import('../../commands/base-command.mjs').default} command
42
+ */
43
+ const normalizeSettings = (settings, config, command) => {
44
+ const plugins = getPluginsToAutoInstall(settings.plugins_from_config_file, settings.plugins_recommended)
45
+ const recommendedPlugins = getRecommendPlugins(plugins, config)
46
46
 
47
47
  return {
48
- defaultBaseDir: getDefaultBase({ repositoryRoot, baseDirectory }),
49
- defaultBuildCmd,
50
- defaultBuildDir: normalizeDir({ baseDirectory, dir: defaultBuildDir, defaultValue: '.' }),
51
- defaultFunctionsDir: normalizeDir({ baseDirectory, dir: defaultFunctionsDir, defaultValue: 'netlify/functions' }),
48
+ defaultBaseDir: settings.baseDirectory ?? command.project.relativeBaseDirectory ?? '',
49
+ defaultBuildCmd: config.build.command || settings.buildCommand,
50
+ defaultBuildDir: settings.dist,
51
+ defaultFunctionsDir: config.build.functions || 'netlify/functions',
52
52
  recommendedPlugins,
53
53
  }
54
54
  }
55
55
 
56
+ /**
57
+ *
58
+ * @param {object} param0
59
+ * @param {string} param0.defaultBaseDir
60
+ * @param {string} param0.defaultBuildCmd
61
+ * @param {string=} param0.defaultBuildDir
62
+ * @returns
63
+ */
56
64
  const getPromptInputs = ({ defaultBaseDir, defaultBuildCmd, defaultBuildDir }) => {
57
65
  const inputs = [
58
- defaultBaseDir !== undefined && {
59
- type: 'input',
60
- name: 'baseDir',
61
- message: 'Base directory (e.g. projects/frontend):',
62
- default: defaultBaseDir,
63
- },
66
+ defaultBaseDir !== undefined &&
67
+ defaultBaseDir !== '' && {
68
+ type: 'input',
69
+ name: 'baseDir',
70
+ message: 'Base directory `(blank for current dir):',
71
+ default: defaultBaseDir,
72
+ },
64
73
  {
65
74
  type: 'input',
66
75
  name: 'buildCmd',
@@ -79,34 +88,22 @@ const getPromptInputs = ({ defaultBaseDir, defaultBuildCmd, defaultBuildDir }) =
79
88
  return inputs.filter(Boolean)
80
89
  }
81
90
 
82
- // `repositoryRoot === siteRoot` means the base directory wasn't detected by @netlify/config, so we use cwd()
83
- const getBaseDirectory = ({ repositoryRoot, siteRoot }) =>
84
- path.normalize(repositoryRoot) === path.normalize(siteRoot) ? process.cwd() : siteRoot
85
-
86
- export const getBuildSettings = async ({ config, env, repositoryRoot, siteRoot }) => {
87
- const baseDirectory = getBaseDirectory({ repositoryRoot, siteRoot })
88
- const nodeVersion = await detectNodeVersion({ baseDirectory, env })
89
- const {
90
- frameworkBuildCommand,
91
- frameworkBuildDir,
92
- frameworkName,
93
- frameworkPlugins = [],
94
- } = await getFrameworkInfo({
95
- baseDirectory,
96
- nodeVersion,
97
- })
91
+ /**
92
+ * @param {object} param0
93
+ * @param {*} param0.config
94
+ * @param {import('../../commands/base-command.mjs').default} param0.command
95
+ */
96
+ export const getBuildSettings = async ({ command, config }) => {
97
+ const settings = await detectBuildSettings(command)
98
+ // TODO: add prompt for asking to choose the build command
99
+ /** @type {Partial<import('@netlify/build-info').Settings>} */
100
+ // eslint-disable-next-line unicorn/explicit-length-check
101
+ const setting = settings.length > 0 ? settings[0] : {}
98
102
  const { defaultBaseDir, defaultBuildCmd, defaultBuildDir, defaultFunctionsDir, recommendedPlugins } =
99
- await getDefaultSettings({
100
- repositoryRoot,
101
- config,
102
- baseDirectory,
103
- frameworkBuildCommand,
104
- frameworkBuildDir,
105
- frameworkPlugins,
106
- })
107
-
108
- if (recommendedPlugins.length !== 0) {
109
- log(`Configuring ${formatTitle(frameworkName)} runtime...`)
103
+ await normalizeSettings(setting, config, command)
104
+
105
+ if (recommendedPlugins.length !== 0 && setting.framework?.name) {
106
+ log(`Configuring ${formatTitle(setting.framework?.name)} runtime...`)
110
107
  log()
111
108
  }
112
109
 
@@ -199,6 +196,9 @@ export const formatErrorMessage = ({ error, message }) => {
199
196
  return `${message} with error: ${chalk.red(errorMessage)}`
200
197
  }
201
198
 
199
+ /**
200
+ * @param {string} title
201
+ */
202
202
  const formatTitle = (title) => chalk.cyan(title)
203
203
 
204
204
  export const createDeployKey = async ({ api }) => {
@@ -36,19 +36,21 @@ export const generateInspectSettings = (edgeInspect, edgeInspectBrk) => {
36
36
  /**
37
37
  *
38
38
  * @param {object} params
39
+ * @param {string=} params.accountId
39
40
  * @param {*} params.addonsUrls
40
- * @param {import('../commands/base-command.mjs').NetlifyOptions["config"]} params.config
41
+ * @param {import('../commands/types.js').NetlifyOptions["config"]} params.config
41
42
  * @param {string} [params.configPath] An override for the Netlify config path
42
43
  * @param {boolean} params.debug
43
- * @param {import('../commands/base-command.mjs').NetlifyOptions["cachedConfig"]['env']} params.env
44
+ * @param {import('../commands/types.js').NetlifyOptions["cachedConfig"]['env']} params.env
44
45
  * @param {InspectSettings} params.inspectSettings
45
46
  * @param {() => Promise<object>} params.getUpdatedConfig
46
47
  * @param {string} params.geolocationMode
47
48
  * @param {string} params.geoCountry
48
49
  * @param {*} params.settings
49
50
  * @param {boolean} params.offline
50
- * @param {*} params.site
51
+ * @param {object} params.site
51
52
  * @param {*} params.siteInfo
53
+ * @param {string} params.projectDir
52
54
  * @param {import('./state-config.mjs').default} params.state
53
55
  * @returns
54
56
  */
@@ -64,6 +66,7 @@ export const startProxyServer = async ({
64
66
  getUpdatedConfig,
65
67
  inspectSettings,
66
68
  offline,
69
+ projectDir,
67
70
  settings,
68
71
  site,
69
72
  siteInfo,
@@ -80,7 +83,7 @@ export const startProxyServer = async ({
80
83
  getUpdatedConfig,
81
84
  inspectSettings,
82
85
  offline,
83
- projectDir: site.root,
86
+ projectDir,
84
87
  settings,
85
88
  state,
86
89
  siteInfo,
@@ -375,7 +375,6 @@ const initializeProxy = async function ({ configPath, distDir, env, host, port,
375
375
  proxy.before('web', 'stream', (req) => {
376
376
  // See https://github.com/http-party/node-http-proxy/issues/1219#issuecomment-511110375
377
377
  if (req.headers.expect) {
378
- // eslint-disable-next-line no-underscore-dangle
379
378
  req.__expectHeader = req.headers.expect
380
379
  delete req.headers.expect
381
380
  }
@@ -402,9 +401,7 @@ const initializeProxy = async function ({ configPath, distDir, env, host, port,
402
401
  handleProxyRequest(req, proxyReq)
403
402
  }
404
403
 
405
- // eslint-disable-next-line no-underscore-dangle
406
404
  if (req.__expectHeader) {
407
- // eslint-disable-next-line no-underscore-dangle
408
405
  proxyReq.setHeader('Expect', req.__expectHeader)
409
406
  }
410
407
  if (req.originalBody) {
@@ -599,6 +596,10 @@ const onRequest = async (
599
596
  proxy.web(req, res, options)
600
597
  }
601
598
 
599
+ /**
600
+ * @param {import('./types.js').ServerSettings} settings
601
+ * @returns
602
+ */
602
603
  export const getProxyUrl = function (settings) {
603
604
  const scheme = settings.https ? 'https' : 'http'
604
605
  return `${scheme}://localhost:${settings.port}`
@@ -7,6 +7,7 @@ import fetch from 'node-fetch'
7
7
  const GITHUB = 'GitHub'
8
8
 
9
9
  /**
10
+ * @param {string} _url
10
11
  * Takes a url like https://github.com/netlify-labs/all-the-functions/tree/master/functions/9-using-middleware
11
12
  * and returns https://api.github.com/repos/netlify-labs/all-the-functions/contents/functions/9-using-middleware
12
13
  */
@@ -36,6 +37,9 @@ const getRepoURLContents = async function (repoHost, ownerAndRepo, contentsPath)
36
37
  throw new Error('unsupported host ', repoHost)
37
38
  }
38
39
 
40
+ /**
41
+ * @param {string} _url
42
+ */
39
43
  export const validateRepoURL = function (_url) {
40
44
  // TODO: use `url.URL()` instead
41
45
  // eslint-disable-next-line n/no-deprecated-api
@@ -1,7 +1,6 @@
1
1
  // @ts-check
2
2
  import { promises as fs } from 'fs'
3
- import path from 'path'
4
- import process from 'process'
3
+ import path, { join } from 'path'
5
4
 
6
5
  import { INTERNAL_EDGE_FUNCTIONS_FOLDER } from '../lib/edge-functions/consts.mjs'
7
6
  import { getPathInProject } from '../lib/settings.mjs'
@@ -12,10 +11,14 @@ import { INTERNAL_FUNCTIONS_FOLDER } from './functions/index.mjs'
12
11
 
13
12
  const netlifyBuildPromise = import('@netlify/build')
14
13
 
15
- // Copies `netlify.toml`, if one is defined, into the `.netlify` internal
16
- // directory and returns the path to its new location.
17
- const copyConfig = async ({ configPath, siteRoot }) => {
18
- const newConfigPath = path.resolve(siteRoot, getPathInProject(['netlify.toml']))
14
+ /**
15
+ * Copies `netlify.toml`, if one is defined, into the `.netlify` internal
16
+ * directory and returns the path to its new location.
17
+ * @param {string} configPath
18
+ * @param {string} destinationFolder The folder where it should be copied to. Either the root of the repo or a package inside a monorepo
19
+ */
20
+ const copyConfig = async (configPath, destinationFolder) => {
21
+ const newConfigPath = path.resolve(destinationFolder, getPathInProject(['netlify.toml']))
19
22
 
20
23
  try {
21
24
  await fs.copyFile(configPath, newConfigPath)
@@ -26,6 +29,9 @@ const copyConfig = async ({ configPath, siteRoot }) => {
26
29
  return newConfigPath
27
30
  }
28
31
 
32
+ /**
33
+ * @param {string} basePath
34
+ */
29
35
  const cleanInternalDirectory = async (basePath) => {
30
36
  const ops = [INTERNAL_FUNCTIONS_FOLDER, INTERNAL_EDGE_FUNCTIONS_FOLDER, 'netlify.toml'].map((name) => {
31
37
  const fullPath = path.resolve(basePath, getPathInProject([name]))
@@ -36,38 +42,52 @@ const cleanInternalDirectory = async (basePath) => {
36
42
  await Promise.all(ops)
37
43
  }
38
44
 
39
- const getBuildOptions = ({
40
- cachedConfig,
41
- options: { configPath, context, cwd = process.cwd(), debug, dry, offline, quiet, saveConfig },
42
- }) => ({
43
- cachedConfig,
44
- configPath,
45
- siteId: cachedConfig.siteInfo.id,
46
- token: cachedConfig.token,
47
- dry,
48
- debug,
49
- context,
50
- mode: 'cli',
51
- telemetry: false,
52
- buffer: false,
53
- offline,
54
- cwd,
55
- quiet,
56
- saveConfig,
57
- })
58
-
59
- const runNetlifyBuild = async ({ cachedConfig, env, options, settings, site, timeline = 'build' }) => {
45
+ /**
46
+ * @param {object} params
47
+ * @param {import('../commands/base-command.mjs').default} params.command
48
+ * @param {import('../commands/base-command.mjs').default} params.command
49
+ * @param {*} params.options The flags of the command
50
+ * @param {import('./types.js').ServerSettings} params.settings
51
+ * @param {NodeJS.ProcessEnv} [params.env]
52
+ * @param {'build' | 'dev'} [params.timeline]
53
+ * @returns
54
+ */
55
+ export const runNetlifyBuild = async ({ command, env = {}, options, settings, timeline = 'build' }) => {
56
+ const { cachedConfig, site } = command.netlify
57
+
60
58
  const { default: buildSite, startDev } = await netlifyBuildPromise
61
- const sharedOptions = getBuildOptions({
59
+
60
+ const sharedOptions = {
62
61
  cachedConfig,
63
- options,
64
- })
62
+ configPath: cachedConfig.configPath,
63
+ siteId: cachedConfig.siteInfo.id,
64
+ token: cachedConfig.token,
65
+ dry: options.dry,
66
+ debug: options.debug,
67
+ context: options.context,
68
+ mode: 'cli',
69
+ telemetry: false,
70
+ buffer: false,
71
+ offline: options.offline,
72
+ packagePath: command.workspacePackage,
73
+ cwd: cachedConfig.buildDir,
74
+ quiet: options.quiet,
75
+ saveConfig: options.saveConfig,
76
+ }
77
+
65
78
  const devCommand = async (settingsOverrides = {}) => {
79
+ let cwd = command.workingDir
80
+
81
+ if (command.project.workspace?.packages.length) {
82
+ cwd = join(command.project.jsWorkspaceRoot, settings.baseDirectory || '')
83
+ }
84
+
66
85
  const { ipVersion } = await startFrameworkServer({
67
86
  settings: {
68
87
  ...settings,
69
88
  ...settingsOverrides,
70
89
  },
90
+ cwd,
71
91
  })
72
92
 
73
93
  settings.frameworkHost = ipVersion === 6 ? '::1' : '127.0.0.1'
@@ -80,7 +100,7 @@ const runNetlifyBuild = async ({ cachedConfig, env, options, settings, site, tim
80
100
 
81
101
  // Copy `netlify.toml` into the internal directory. This will be the new
82
102
  // location of the config file for the duration of the command.
83
- const tempConfigPath = await copyConfig({ configPath: cachedConfig.configPath, siteRoot: site.root })
103
+ const tempConfigPath = await copyConfig(cachedConfig.configPath, command.workingDir)
84
104
  const buildSiteOptions = {
85
105
  ...sharedOptions,
86
106
  outputConfigPath: tempConfigPath,
@@ -118,13 +138,19 @@ const runNetlifyBuild = async ({ cachedConfig, env, options, settings, site, tim
118
138
  // Run Netlify Build using the `startDev` entry point.
119
139
  const { error: startDevError, success } = await startDev(devCommand, startDevOptions)
120
140
 
121
- if (!success) {
141
+ if (!success && startDevError) {
122
142
  error(`Could not start local development server\n\n${startDevError.message}\n\n${startDevError.stack}`)
123
143
  }
124
144
 
125
145
  return {}
126
146
  }
127
147
 
148
+ /**
149
+ * @param {Omit<Parameters<typeof runNetlifyBuild>[0], 'timeline'>} options
150
+ */
128
151
  export const runDevTimeline = (options) => runNetlifyBuild({ ...options, timeline: 'dev' })
129
152
 
153
+ /**
154
+ * @param {Omit<Parameters<typeof runNetlifyBuild>[0], 'timeline'>} options
155
+ */
130
156
  export const runBuildTimeline = (options) => runNetlifyBuild({ ...options, timeline: 'build' })
@@ -40,17 +40,26 @@ const cleanupBeforeExit = async ({ exitCode }) => {
40
40
  /**
41
41
  * Run a command and pipe stdout, stderr and stdin
42
42
  * @param {string} command
43
- * @param {NodeJS.ProcessEnv} env
43
+ * @param {object} options
44
+ * @param {import('ora').Ora|null} [options.spinner]
45
+ * @param {NodeJS.ProcessEnv} [options.env]
46
+ * @param {string} [options.cwd]
44
47
  * @returns {execa.ExecaChildProcess<string>}
45
48
  */
46
- export const runCommand = (command, env = {}, spinner = null) => {
49
+ export const runCommand = (command, options = {}) => {
50
+ const { cwd, env = {}, spinner = null } = options
47
51
  const commandProcess = execa.command(command, {
48
52
  preferLocal: true,
49
53
  // we use reject=false to avoid rejecting synchronously when the command doesn't exist
50
54
  reject: false,
51
- env,
55
+ env: {
56
+ // we want always colorful terminal outputs
57
+ FORCE_COLOR: 'true',
58
+ ...env,
59
+ },
52
60
  // windowsHide needs to be false for child process to terminate properly on Windows
53
61
  windowsHide: false,
62
+ cwd,
54
63
  })
55
64
 
56
65
  // This ensures that an active spinner stays at the bottom of the commandline
@@ -82,15 +91,16 @@ export const runCommand = (command, env = {}, spinner = null) => {
82
91
  const [commandWithoutArgs] = command.split(' ')
83
92
  if (result.failed && isNonExistingCommandError({ command: commandWithoutArgs, error: result })) {
84
93
  log(
85
- NETLIFYDEVERR,
86
- `Failed running command: ${command}. Please verify ${chalk.magenta(`'${commandWithoutArgs}'`)} exists`,
94
+ `\n\n${NETLIFYDEVERR} Failed running command: ${command}. Please verify ${chalk.magenta(
95
+ `'${commandWithoutArgs}'`,
96
+ )} exists`,
87
97
  )
88
98
  } else {
89
99
  const errorMessage = result.failed
90
100
  ? `${NETLIFYDEVERR} ${result.shortMessage}`
91
101
  : `${NETLIFYDEVWARN} "${command}" exited with code ${result.exitCode}`
92
102
 
93
- log(`${errorMessage}. Shutting down Netlify Dev server`)
103
+ log(`\n\n${errorMessage}. Shutting down Netlify Dev server`)
94
104
  }
95
105
 
96
106
  return await cleanupBeforeExit({ exitCode: 1 })
@@ -100,6 +110,13 @@ export const runCommand = (command, env = {}, spinner = null) => {
100
110
  return commandProcess
101
111
  }
102
112
 
113
+ /**
114
+ *
115
+ * @param {object} config
116
+ * @param {string} config.command
117
+ * @param {*} config.error
118
+ * @returns
119
+ */
103
120
  const isNonExistingCommandError = ({ command, error: commandError }) => {
104
121
  // `ENOENT` is only returned for non Windows systems
105
122
  // See https://github.com/sindresorhus/execa/pull/447
@@ -108,7 +125,7 @@ const isNonExistingCommandError = ({ command, error: commandError }) => {
108
125
  }
109
126
 
110
127
  // if the command is a package manager we let it report the error
111
- if (['yarn', 'npm'].includes(command)) {
128
+ if (['yarn', 'npm', 'pnpm'].includes(command)) {
112
129
  return false
113
130
  }
114
131
 
@@ -11,7 +11,11 @@ import { getPathInProject } from '../lib/settings.mjs'
11
11
  const STATE_PATH = getPathInProject(['state.json'])
12
12
  const permissionError = "You don't have access to this file."
13
13
 
14
- // Finds location of `.netlify/state.json`
14
+ /**
15
+ * Finds location of `.netlify/state.json`
16
+ * @param {string} cwd
17
+ * @returns {string}
18
+ */
15
19
  const findStatePath = (cwd) => {
16
20
  const statePath = findUpSync([STATE_PATH], { cwd })
17
21
 
@@ -6,6 +6,10 @@ import Fastify from 'fastify'
6
6
 
7
7
  import { log, NETLIFYDEVLOG } from './command-helpers.mjs'
8
8
 
9
+ /**
10
+ * @param {object} config
11
+ * @param {import('./types.js').ServerSettings} config.settings
12
+ */
9
13
  export const startStaticServer = async ({ settings }) => {
10
14
  const server = Fastify()
11
15
  const rootPath = path.resolve(settings.dist)
@@ -1,23 +0,0 @@
1
- // @ts-check
2
- import { listFrameworks } from '@netlify/framework-info'
3
-
4
- export const getFrameworkInfo = async ({ baseDirectory, nodeVersion }) => {
5
- const frameworks = await listFrameworks({ projectDir: baseDirectory, nodeVersion })
6
- // several frameworks can be detected - first one has highest priority
7
- if (frameworks.length !== 0) {
8
- const [
9
- {
10
- build: { commands, directory },
11
- name,
12
- plugins,
13
- },
14
- ] = frameworks
15
- return {
16
- frameworkName: name,
17
- frameworkBuildCommand: commands[0],
18
- frameworkBuildDir: directory,
19
- frameworkPlugins: plugins,
20
- }
21
- }
22
- return {}
23
- }