netlify-cli 17.3.2 → 17.4.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 (210) hide show
  1. package/README.md +2 -138
  2. package/npm-shrinkwrap.json +76 -76
  3. package/package.json +16 -15
  4. package/src/commands/addons/addons-auth.mjs +27 -30
  5. package/src/commands/addons/addons-config.mjs +145 -154
  6. package/src/commands/addons/addons-create.mjs +94 -108
  7. package/src/commands/addons/addons-delete.mjs +36 -41
  8. package/src/commands/addons/addons-list.mjs +38 -42
  9. package/src/commands/addons/addons.mjs +26 -28
  10. package/src/commands/addons/index.mjs +1 -1
  11. package/src/commands/api/api.mjs +45 -53
  12. package/src/commands/api/index.mjs +1 -1
  13. package/src/commands/base-command.mjs +597 -684
  14. package/src/commands/blobs/blobs-delete.mjs +35 -0
  15. package/src/commands/blobs/blobs-get.mjs +44 -0
  16. package/src/commands/blobs/blobs-list.mjs +48 -0
  17. package/src/commands/blobs/blobs-set.mjs +54 -0
  18. package/src/commands/blobs/blobs.mjs +32 -0
  19. package/src/commands/blobs/index.mjs +1 -0
  20. package/src/commands/build/build.mjs +55 -67
  21. package/src/commands/build/index.mjs +1 -1
  22. package/src/commands/completion/completion.mjs +41 -46
  23. package/src/commands/completion/index.mjs +1 -1
  24. package/src/commands/deploy/deploy.mjs +675 -710
  25. package/src/commands/deploy/index.mjs +1 -1
  26. package/src/commands/dev/dev-exec.mjs +20 -32
  27. package/src/commands/dev/dev.mjs +217 -302
  28. package/src/commands/dev/index.mjs +1 -1
  29. package/src/commands/dev/types.d.ts +30 -0
  30. package/src/commands/env/env-clone.mjs +157 -184
  31. package/src/commands/env/env-get.mjs +49 -68
  32. package/src/commands/env/env-import.mjs +100 -119
  33. package/src/commands/env/env-list.mjs +104 -129
  34. package/src/commands/env/env-set.mjs +160 -185
  35. package/src/commands/env/env-unset.mjs +104 -122
  36. package/src/commands/env/env.mjs +28 -30
  37. package/src/commands/env/index.mjs +1 -1
  38. package/src/commands/functions/functions-build.mjs +29 -41
  39. package/src/commands/functions/functions-create.mjs +533 -601
  40. package/src/commands/functions/functions-invoke.mjs +193 -216
  41. package/src/commands/functions/functions-list.mjs +45 -55
  42. package/src/commands/functions/functions-serve.mjs +51 -61
  43. package/src/commands/functions/functions.mjs +26 -32
  44. package/src/commands/functions/index.mjs +1 -1
  45. package/src/commands/index.mjs +2 -2
  46. package/src/commands/init/index.mjs +1 -1
  47. package/src/commands/init/init.mjs +138 -167
  48. package/src/commands/integration/deploy.mjs +337 -399
  49. package/src/commands/integration/index.mjs +12 -13
  50. package/src/commands/link/index.mjs +1 -1
  51. package/src/commands/link/link.mjs +298 -317
  52. package/src/commands/lm/index.mjs +1 -1
  53. package/src/commands/lm/lm-info.mjs +23 -31
  54. package/src/commands/lm/lm-install.mjs +13 -17
  55. package/src/commands/lm/lm-setup.mjs +80 -84
  56. package/src/commands/lm/lm-uninstall.mjs +7 -12
  57. package/src/commands/lm/lm.mjs +18 -22
  58. package/src/commands/login/index.mjs +1 -1
  59. package/src/commands/login/login.mjs +35 -41
  60. package/src/commands/logout/index.mjs +1 -1
  61. package/src/commands/logout/logout.mjs +25 -31
  62. package/src/commands/main.mjs +166 -201
  63. package/src/commands/open/index.mjs +1 -1
  64. package/src/commands/open/open-admin.mjs +15 -18
  65. package/src/commands/open/open-site.mjs +16 -19
  66. package/src/commands/open/open.mjs +24 -27
  67. package/src/commands/recipes/common.mjs +23 -34
  68. package/src/commands/recipes/index.mjs +1 -1
  69. package/src/commands/recipes/recipes-list.mjs +13 -20
  70. package/src/commands/recipes/recipes.mjs +59 -72
  71. package/src/commands/serve/index.mjs +1 -1
  72. package/src/commands/serve/serve.mjs +142 -189
  73. package/src/commands/sites/index.mjs +2 -2
  74. package/src/commands/sites/sites-create-template.mjs +214 -236
  75. package/src/commands/sites/sites-create.mjs +145 -157
  76. package/src/commands/sites/sites-delete.mjs +75 -81
  77. package/src/commands/sites/sites-list.mjs +63 -66
  78. package/src/commands/sites/sites.mjs +18 -20
  79. package/src/commands/status/index.mjs +1 -1
  80. package/src/commands/status/status-hooks.mjs +32 -34
  81. package/src/commands/status/status.mjs +99 -106
  82. package/src/commands/switch/index.mjs +1 -1
  83. package/src/commands/switch/switch.mjs +32 -37
  84. package/src/commands/types.d.ts +31 -0
  85. package/src/commands/unlink/index.mjs +1 -1
  86. package/src/commands/unlink/unlink.mjs +23 -29
  87. package/src/commands/watch/index.mjs +1 -1
  88. package/src/commands/watch/watch.mjs +91 -105
  89. package/src/functions-templates/javascript/hello/{{name}}.js +2 -3
  90. package/src/lib/account.mjs +4 -5
  91. package/src/lib/api.mjs +22 -20
  92. package/src/lib/blobs/blobs.mjs +36 -45
  93. package/src/lib/build.mjs +82 -85
  94. package/src/lib/completion/constants.mjs +2 -4
  95. package/src/lib/completion/generate-autocompletion.mjs +33 -36
  96. package/src/lib/completion/get-autocompletion.mjs +31 -35
  97. package/src/lib/completion/index.mjs +1 -1
  98. package/src/lib/completion/script.mjs +12 -19
  99. package/src/lib/edge-functions/bootstrap.mjs +3 -5
  100. package/src/lib/edge-functions/consts.mjs +9 -10
  101. package/src/lib/edge-functions/deploy.mjs +28 -34
  102. package/src/lib/edge-functions/editor-helper.mjs +29 -42
  103. package/src/lib/edge-functions/headers.mjs +24 -26
  104. package/src/lib/edge-functions/internal.mjs +38 -44
  105. package/src/lib/edge-functions/proxy.mjs +229 -228
  106. package/src/lib/edge-functions/registry.mjs +473 -574
  107. package/src/lib/exec-fetcher.mjs +115 -122
  108. package/src/lib/fs.mjs +28 -27
  109. package/src/lib/functions/background.mjs +16 -20
  110. package/src/lib/functions/config.mjs +12 -9
  111. package/src/lib/functions/form-submissions-handler.mjs +143 -149
  112. package/src/lib/functions/local-proxy.mjs +40 -44
  113. package/src/lib/functions/memoized-build.mjs +19 -21
  114. package/src/lib/functions/netlify-function.mjs +269 -249
  115. package/src/lib/functions/registry.mjs +509 -568
  116. package/src/lib/functions/runtimes/go/index.mjs +62 -71
  117. package/src/lib/functions/runtimes/index.mjs +8 -15
  118. package/src/lib/functions/runtimes/js/builders/netlify-lambda.mjs +55 -64
  119. package/src/lib/functions/runtimes/js/builders/zisi.mjs +135 -154
  120. package/src/lib/functions/runtimes/js/constants.mjs +1 -1
  121. package/src/lib/functions/runtimes/js/index.mjs +92 -109
  122. package/src/lib/functions/runtimes/js/worker.mjs +43 -45
  123. package/src/lib/functions/runtimes/rust/index.mjs +64 -73
  124. package/src/lib/functions/scheduled.mjs +70 -88
  125. package/src/lib/functions/server.mjs +269 -327
  126. package/src/lib/functions/synchronous.mjs +118 -147
  127. package/src/lib/functions/utils.mjs +38 -46
  128. package/src/lib/geo-location.mjs +69 -81
  129. package/src/lib/http-agent.mjs +87 -90
  130. package/src/lib/images/proxy.mjs +97 -99
  131. package/src/lib/log.mjs +6 -9
  132. package/src/lib/path.mjs +2 -1
  133. package/src/lib/render-error-template.mjs +19 -20
  134. package/src/lib/settings.mjs +17 -19
  135. package/src/lib/spinner.mjs +21 -23
  136. package/src/lib/string.mjs +4 -2
  137. package/src/recipes/vscode/index.mjs +69 -85
  138. package/src/recipes/vscode/settings.mjs +53 -58
  139. package/src/utils/addons/compare.mjs +31 -32
  140. package/src/utils/addons/diffs/index.mjs +16 -17
  141. package/src/utils/addons/diffs/options.mjs +99 -101
  142. package/src/utils/addons/prepare.mjs +100 -97
  143. package/src/utils/addons/prompts.mjs +73 -76
  144. package/src/utils/addons/render.mjs +33 -36
  145. package/src/utils/addons/validation.mjs +19 -15
  146. package/src/utils/banner.mjs +11 -16
  147. package/src/utils/build-info.mjs +65 -66
  148. package/src/utils/command-helpers.mjs +185 -199
  149. package/src/utils/create-deferred.mjs +9 -12
  150. package/src/utils/create-stream-promise.mjs +54 -47
  151. package/src/utils/deploy/constants.mjs +9 -11
  152. package/src/utils/deploy/deploy-site.mjs +162 -182
  153. package/src/utils/deploy/hash-config.mjs +21 -21
  154. package/src/utils/deploy/hash-files.mjs +34 -38
  155. package/src/utils/deploy/hash-fns.mjs +149 -154
  156. package/src/utils/deploy/hasher-segments.mjs +58 -52
  157. package/src/utils/deploy/upload-files.mjs +99 -113
  158. package/src/utils/deploy/util.mjs +85 -91
  159. package/src/utils/detect-server-settings.mjs +236 -268
  160. package/src/utils/dev.mjs +163 -178
  161. package/src/utils/dot-env.mjs +37 -42
  162. package/src/utils/env/index.mjs +148 -148
  163. package/src/utils/execa.mjs +9 -13
  164. package/src/utils/feature-flags.mjs +6 -5
  165. package/src/utils/framework-server.mjs +43 -52
  166. package/src/utils/functions/constants.mjs +1 -1
  167. package/src/utils/functions/functions.mjs +30 -40
  168. package/src/utils/functions/get-functions.mjs +28 -29
  169. package/src/utils/functions/index.mjs +3 -3
  170. package/src/utils/get-global-config.mjs +33 -36
  171. package/src/utils/get-package-json.mjs +14 -15
  172. package/src/utils/get-repo-data.mjs +54 -64
  173. package/src/utils/get-site.mjs +14 -14
  174. package/src/utils/gh-auth.mjs +79 -100
  175. package/src/utils/gitignore.mjs +37 -40
  176. package/src/utils/headers.mjs +33 -35
  177. package/src/utils/hooks/requires-site-info.mjs +26 -22
  178. package/src/utils/init/config-github.mjs +207 -219
  179. package/src/utils/init/config-manual.mjs +83 -100
  180. package/src/utils/init/config.mjs +25 -26
  181. package/src/utils/init/node-version.mjs +23 -30
  182. package/src/utils/init/plugins.mjs +12 -8
  183. package/src/utils/init/utils.mjs +152 -172
  184. package/src/utils/live-tunnel.mjs +118 -141
  185. package/src/utils/lm/install.mjs +220 -259
  186. package/src/utils/lm/requirements.mjs +54 -63
  187. package/src/utils/lm/steps.mjs +31 -31
  188. package/src/utils/lm/ui.mjs +13 -20
  189. package/src/utils/open-browser.mjs +31 -32
  190. package/src/utils/parse-raw-flags.mjs +39 -35
  191. package/src/utils/proxy-server.mjs +84 -71
  192. package/src/utils/proxy.mjs +696 -750
  193. package/src/utils/read-repo-url.mjs +48 -47
  194. package/src/utils/redirects.mjs +49 -49
  195. package/src/utils/request-id.mjs +2 -4
  196. package/src/utils/rules-proxy.mjs +96 -100
  197. package/src/utils/run-build.mjs +109 -132
  198. package/src/utils/shell.mjs +99 -106
  199. package/src/utils/sign-redirect.mjs +14 -14
  200. package/src/utils/sites/utils.mjs +48 -55
  201. package/src/utils/state-config.mjs +101 -101
  202. package/src/utils/static-server.mjs +28 -34
  203. package/src/utils/telemetry/index.mjs +2 -2
  204. package/src/utils/telemetry/report-error.mjs +45 -49
  205. package/src/utils/telemetry/request.mjs +36 -43
  206. package/src/utils/telemetry/telemetry.mjs +90 -105
  207. package/src/utils/telemetry/utils.mjs +5 -6
  208. package/src/utils/telemetry/validation.mjs +55 -53
  209. package/src/utils/types.d.ts +46 -0
  210. package/src/utils/validation.mjs +10 -13
@@ -1,82 +1,64 @@
1
- // @ts-check
2
- import { existsSync } from 'fs'
3
- import { join, relative, resolve } from 'path'
4
- import process from 'process'
5
- import { format } from 'util'
6
-
7
- import { DefaultLogger, Project } from '@netlify/build-info'
1
+ import { existsSync } from 'fs';
2
+ import { join, relative, resolve } from 'path';
3
+ import process from 'process';
4
+ import { format } from 'util';
5
+ import { DefaultLogger, Project } from '@netlify/build-info';
8
6
  // eslint-disable-next-line import/extensions, n/no-missing-import
9
- import { NodeFS, NoopLogger } from '@netlify/build-info/node'
10
- import { resolveConfig } from '@netlify/config'
11
- import { Command, Option } from 'commander'
12
- import debug from 'debug'
13
- import { findUp } from 'find-up'
14
- import inquirer from 'inquirer'
15
- import inquirerAutocompletePrompt from 'inquirer-autocomplete-prompt'
16
- import merge from 'lodash/merge.js'
17
- import { NetlifyAPI } from 'netlify'
18
-
19
- import { getAgent } from '../lib/http-agent.mjs'
20
- import {
21
- NETLIFY_CYAN,
22
- USER_AGENT,
23
- chalk,
24
- error,
25
- exit,
26
- getToken,
27
- log,
28
- normalizeConfig,
29
- padLeft,
30
- pollForToken,
31
- sortOptions,
32
- warn,
33
- } from '../utils/command-helpers.mjs'
34
- import getGlobalConfig from '../utils/get-global-config.mjs'
35
- import { getSiteByName } from '../utils/get-site.mjs'
36
- import openBrowser from '../utils/open-browser.mjs'
37
- import StateConfig from '../utils/state-config.mjs'
38
- import { identify, reportError, track } from '../utils/telemetry/index.mjs'
39
-
7
+ import { NodeFS, NoopLogger } from '@netlify/build-info/node';
8
+ // @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module '@net... Remove this comment to see the full error message
9
+ import { resolveConfig } from '@netlify/config';
10
+ import { Command, Option } from 'commander';
11
+ // @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'debu... Remove this comment to see the full error message
12
+ import debug from 'debug';
13
+ import { findUp } from 'find-up';
14
+ import inquirer from 'inquirer';
15
+ // @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'inqu... Remove this comment to see the full error message
16
+ import inquirerAutocompletePrompt from 'inquirer-autocomplete-prompt';
17
+ import merge from 'lodash/merge.js';
18
+ import { NetlifyAPI } from 'netlify';
19
+ import { getAgent } from '../lib/http-agent.mjs';
20
+ import { NETLIFY_CYAN, USER_AGENT, chalk, error, exit, getToken, log, normalizeConfig, padLeft, pollForToken, sortOptions, warn, } from '../utils/command-helpers.mjs';
21
+ import getGlobalConfig from '../utils/get-global-config.mjs';
22
+ import { getSiteByName } from '../utils/get-site.mjs';
23
+ import openBrowser from '../utils/open-browser.mjs';
24
+ import StateConfig from '../utils/state-config.mjs';
25
+ import { identify, reportError, track } from '../utils/telemetry/index.mjs';
40
26
  // load the autocomplete plugin
41
- inquirer.registerPrompt('autocomplete', inquirerAutocompletePrompt)
27
+ inquirer.registerPrompt('autocomplete', inquirerAutocompletePrompt);
42
28
  /** Netlify CLI client id. Lives in bot@netlify.com */
43
29
  // TODO: setup client for multiple environments
44
- const CLIENT_ID = 'd6f37de6614df7ae58664cfca524744d73807a377f5ee71f1a254f78412e3750'
45
-
46
- const NANO_SECS_TO_MSECS = 1e6
30
+ const CLIENT_ID = 'd6f37de6614df7ae58664cfca524744d73807a377f5ee71f1a254f78412e3750';
31
+ const NANO_SECS_TO_MSECS = 1e6;
47
32
  /** The fallback width for the help terminal */
48
- const FALLBACK_HELP_CMD_WIDTH = 80
49
-
50
- const HELP_$ = NETLIFY_CYAN('$')
33
+ const FALLBACK_HELP_CMD_WIDTH = 80;
34
+ const HELP_$ = NETLIFY_CYAN('$');
51
35
  /** indent on commands or description on the help page */
52
- const HELP_INDENT_WIDTH = 2
36
+ const HELP_INDENT_WIDTH = 2;
53
37
  /** separator width between term and description */
54
- const HELP_SEPARATOR_WIDTH = 5
55
-
38
+ const HELP_SEPARATOR_WIDTH = 5;
56
39
  /**
57
40
  * A list of commands where we don't have to perform the workspace selection at.
58
41
  * Those commands work with the system or are not writing any config files that need to be
59
42
  * workspace aware.
60
43
  */
61
- const COMMANDS_WITHOUT_WORKSPACE_OPTIONS = new Set(['api', 'recipes', 'completion', 'status', 'switch', 'login', 'lm'])
62
-
44
+ const COMMANDS_WITHOUT_WORKSPACE_OPTIONS = new Set(['api', 'recipes', 'completion', 'status', 'switch', 'login', 'lm']);
63
45
  /**
64
46
  * Formats a help list correctly with the correct indent
65
47
  * @param {string[]} textArray
66
48
  * @returns
67
49
  */
68
- const formatHelpList = (textArray) => textArray.join('\n').replace(/^/gm, ' '.repeat(HELP_INDENT_WIDTH))
69
-
50
+ // @ts-expect-error TS(7006) FIXME: Parameter 'textArray' implicitly has an 'any' type... Remove this comment to see the full error message
51
+ const formatHelpList = (textArray) => textArray.join('\n').replace(/^/gm, ' '.repeat(HELP_INDENT_WIDTH));
70
52
  /**
71
53
  * Get the duration between a start time and the current time
72
54
  * @param {bigint} startTime
73
55
  * @returns
74
56
  */
57
+ // @ts-expect-error TS(7006) FIXME: Parameter 'startTime' implicitly has an 'any' type... Remove this comment to see the full error message
75
58
  const getDuration = function (startTime) {
76
- const durationNs = process.hrtime.bigint() - startTime
77
- return Math.round(Number(durationNs / BigInt(NANO_SECS_TO_MSECS)))
78
- }
79
-
59
+ const durationNs = process.hrtime.bigint() - startTime;
60
+ return Math.round(Number(durationNs / BigInt(NANO_SECS_TO_MSECS)));
61
+ };
80
62
  /**
81
63
  * Retrieves a workspace package based of the filter flag that is provided.
82
64
  * If the filter flag does not match a workspace package or is not defined then it will prompt with an autocomplete to select a package
@@ -84,664 +66,595 @@ const getDuration = function (startTime) {
84
66
  * @param {string=} filter
85
67
  * @returns {Promise<string>}
86
68
  */
69
+ // @ts-expect-error TS(7006) FIXME: Parameter 'project' implicitly has an 'any' type.
87
70
  async function selectWorkspace(project, filter) {
88
- const selected = project.workspace?.packages.find((pkg) => {
89
- if (
90
- project.relativeBaseDirectory &&
91
- project.relativeBaseDirectory.length !== 0 &&
92
- pkg.path.startsWith(project.relativeBaseDirectory)
93
- ) {
94
- return true
71
+ // @ts-expect-error TS(7006) FIXME: Parameter 'pkg' implicitly has an 'any' type.
72
+ const selected = project.workspace?.packages.find((pkg) => {
73
+ if (project.relativeBaseDirectory &&
74
+ project.relativeBaseDirectory.length !== 0 &&
75
+ pkg.path.startsWith(project.relativeBaseDirectory)) {
76
+ return true;
77
+ }
78
+ return (pkg.name && pkg.name === filter) || pkg.path === filter;
79
+ });
80
+ if (!selected) {
81
+ log();
82
+ log(chalk.cyan(`We've detected multiple sites inside your repository`));
83
+ const { result } = await inquirer.prompt({
84
+ name: 'result',
85
+ // @ts-expect-error TS(2769) FIXME: No overload matches this call.
86
+ type: 'autocomplete',
87
+ message: 'Select the site you want to work with',
88
+ // @ts-expect-error TS(7006) FIXME: Parameter '_' implicitly has an 'any' type.
89
+ source: (/** @type {string} */ _, input = '') => (project.workspace?.packages || [])
90
+ // @ts-expect-error TS(7006) FIXME: Parameter 'pkg' implicitly has an 'any' type.
91
+ .filter((pkg) => pkg.path.includes(input))
92
+ // @ts-expect-error TS(7006) FIXME: Parameter 'pkg' implicitly has an 'any' type.
93
+ .map((pkg) => ({
94
+ name: `${pkg.name ? `${chalk.bold(pkg.name)} ` : ''}${pkg.path} ${chalk.dim(`--filter ${pkg.name || pkg.path}`)}`,
95
+ value: pkg.path,
96
+ })),
97
+ });
98
+ return result;
95
99
  }
96
- return (pkg.name && pkg.name === filter) || pkg.path === filter
97
- })
98
-
99
- if (!selected) {
100
- log()
101
- log(chalk.cyan(`We've detected multiple sites inside your repository`))
102
-
103
- const { result } = await inquirer.prompt({
104
- name: 'result',
105
- type: 'autocomplete',
106
- message: 'Select the site you want to work with',
107
- source: (/** @type {string} */ _, input = '') =>
108
- (project.workspace?.packages || [])
109
- .filter((pkg) => pkg.path.includes(input))
110
- .map((pkg) => ({
111
- name: `${pkg.name ? `${chalk.bold(pkg.name)} ` : ''}${pkg.path} ${chalk.dim(
112
- `--filter ${pkg.name || pkg.path}`,
113
- )}`,
114
- value: pkg.path,
115
- })),
116
- })
117
-
118
- return result
119
- }
120
- return selected.path
100
+ return selected.path;
121
101
  }
122
-
123
102
  /** Base command class that provides tracking and config initialization */
124
103
  export default class BaseCommand extends Command {
125
- /**
126
- * The netlify object inside each command with the state
127
- * @type {import('./types.js').NetlifyOptions}
128
- */
129
- netlify
130
-
131
- /** @type {{ startTime: bigint, payload?: any}} */
132
- analytics = { startTime: process.hrtime.bigint() }
133
-
134
- /** @type {Project} */
135
- project
136
-
137
- /**
138
- * The working directory that is used for reading the `netlify.toml` file and storing the state.
139
- * In a monorepo context this must not be the process working directory and can be an absolute path to the
140
- * Package/Site that should be worked in.
141
- */
142
- // here we actually want to disable the lint rule as its value is set
143
- // eslint-disable-next-line workspace/no-process-cwd
144
- workingDir = process.cwd()
145
-
146
- /**
147
- * The workspace root if inside a mono repository.
148
- * Must not be the repository root!
149
- * @type {string|undefined}
150
- */
151
- jsWorkspaceRoot
152
- /**
153
- * The current workspace package we should execute the commands in
154
- * @type {string|undefined}
155
- */
156
- workspacePackage
157
-
158
- /**
159
- * IMPORTANT this function will be called for each command!
160
- * Don't do anything expensive in there.
161
- * @param {string} name The command name
162
- * @returns
163
- */
164
- createCommand(name) {
165
- const base = new BaseCommand(name)
166
- // If --silent or --json flag passed disable logger
167
- .addOption(new Option('--json', 'Output return values as JSON').hideHelp(true))
168
- .addOption(new Option('--silent', 'Silence CLI output').hideHelp(true))
169
- .addOption(new Option('--cwd <cwd>').hideHelp(true))
170
- .addOption(new Option('-o, --offline').hideHelp(true))
171
- .addOption(new Option('--auth <token>', 'Netlify auth token').hideHelp(true))
172
- .addOption(
173
- new Option('--httpProxy [address]', 'Old, prefer --http-proxy. Proxy server address to route requests through.')
174
- .default(process.env.HTTP_PROXY || process.env.HTTPS_PROXY)
175
- .hideHelp(true),
176
- )
177
- .addOption(
178
- new Option(
179
- '--httpProxyCertificateFilename [file]',
180
- 'Old, prefer --http-proxy-certificate-filename. Certificate file to use when connecting using a proxy server.',
181
- )
182
- .default(process.env.NETLIFY_PROXY_CERTIFICATE_FILENAME)
183
- .hideHelp(true),
184
- )
185
- .addOption(
186
- new Option(
187
- '--http-proxy-certificate-filename [file]',
188
- 'Certificate file to use when connecting using a proxy server',
189
- )
190
- .default(process.env.NETLIFY_PROXY_CERTIFICATE_FILENAME)
191
- .hideHelp(true),
192
- )
193
- .addOption(
194
- new Option('--httpProxy [address]', 'Proxy server address to route requests through.')
195
- .default(process.env.HTTP_PROXY || process.env.HTTPS_PROXY)
196
- .hideHelp(true),
197
- )
198
- .option('--debug', 'Print debugging information')
199
-
200
- // only add the `--filter` option to commands that are workspace aware
201
- if (!COMMANDS_WITHOUT_WORKSPACE_OPTIONS.has(name)) {
202
- base.option('--filter <app>', 'For monorepos, specify the name of the application to run the command in')
104
+ constructor() {
105
+ super(...arguments);
106
+ /** @type {{ startTime: bigint, payload?: any}} */
107
+ this.analytics = { startTime: process.hrtime.bigint() };
108
+ /**
109
+ * The working directory that is used for reading the `netlify.toml` file and storing the state.
110
+ * In a monorepo context this must not be the process working directory and can be an absolute path to the
111
+ * Package/Site that should be worked in.
112
+ */
113
+ // here we actually want to disable the lint rule as its value is set
114
+ // eslint-disable-next-line workspace/no-process-cwd
115
+ this.workingDir = process.cwd();
116
+ /** @private */
117
+ this.noBaseOptions = false;
118
+ /** @type {string[]} The examples list for the command (used inside doc generation and help page) */
119
+ this.examples = [];
203
120
  }
204
-
205
- return base.hook('preAction', async (_parentCommand, actionCommand) => {
206
- if (actionCommand.opts()?.debug) {
207
- process.env.DEBUG = '*'
208
- }
209
- debug(`${name}:preAction`)('start')
210
- this.analytics = { startTime: process.hrtime.bigint() }
211
- // @ts-ignore cannot type actionCommand as BaseCommand
212
- await this.init(actionCommand)
213
- debug(`${name}:preAction`)('end')
214
- })
215
- }
216
-
217
- /** @private */
218
- noBaseOptions = false
219
-
220
- /** don't show help options on command overview (mostly used on top commands like `addons` where options only apply on children) */
221
- noHelpOptions() {
222
- this.noBaseOptions = true
223
- return this
224
- }
225
-
226
- /** @type {string[]} The examples list for the command (used inside doc generation and help page) */
227
- examples = []
228
-
229
- /**
230
- * Set examples for the command
231
- * @param {string[]} examples
232
- */
233
- addExamples(examples) {
234
- this.examples = examples
235
- return this
236
- }
237
-
238
- /**
239
- * Overrides the help output of commander with custom styling
240
- * @returns {import('commander').Help}
241
- */
242
- createHelp() {
243
- const help = super.createHelp()
244
-
245
- help.commandUsage = (command) => {
246
- const term =
247
- this.name() === 'netlify'
248
- ? `${HELP_$} ${command.name()} [COMMAND]`
249
- : `${HELP_$} ${command.parent?.name()} ${command.name()} ${command.usage()}`
250
-
251
- return padLeft(term, HELP_INDENT_WIDTH)
252
- }
253
-
254
121
  /**
255
- * @param {BaseCommand} command
122
+ * IMPORTANT this function will be called for each command!
123
+ * Don't do anything expensive in there.
124
+ * @param {string} name The command name
125
+ * @returns
256
126
  */
257
- const getCommands = (command) => {
258
- const parentCommand = this.name() === 'netlify' ? command : command.parent
259
- return (
260
- parentCommand?.commands.filter((cmd) => {
261
- if (cmd._hidden) return false
262
- // the root command
263
- if (this.name() === 'netlify') {
264
- // don't include subcommands on the main page
265
- return !cmd.name().includes(':')
266
- }
267
- return cmd.name().startsWith(`${command.name()}:`)
268
- }) || []
269
- )
127
+ // @ts-expect-error TS(7006) FIXME: Parameter 'name' implicitly has an 'any' type.
128
+ createCommand(name) {
129
+ const base = new BaseCommand(name)
130
+ // If --silent or --json flag passed disable logger
131
+ .addOption(new Option('--json', 'Output return values as JSON').hideHelp(true))
132
+ .addOption(new Option('--silent', 'Silence CLI output').hideHelp(true))
133
+ .addOption(new Option('--cwd <cwd>').hideHelp(true))
134
+ .addOption(new Option('-o, --offline').hideHelp(true))
135
+ .addOption(new Option('--auth <token>', 'Netlify auth token').hideHelp(true))
136
+ .addOption(new Option('--httpProxy [address]', 'Old, prefer --http-proxy. Proxy server address to route requests through.')
137
+ .default(process.env.HTTP_PROXY || process.env.HTTPS_PROXY)
138
+ .hideHelp(true))
139
+ .addOption(new Option('--httpProxyCertificateFilename [file]', 'Old, prefer --http-proxy-certificate-filename. Certificate file to use when connecting using a proxy server.')
140
+ .default(process.env.NETLIFY_PROXY_CERTIFICATE_FILENAME)
141
+ .hideHelp(true))
142
+ .addOption(new Option('--http-proxy-certificate-filename [file]', 'Certificate file to use when connecting using a proxy server')
143
+ .default(process.env.NETLIFY_PROXY_CERTIFICATE_FILENAME)
144
+ .hideHelp(true))
145
+ .addOption(new Option('--httpProxy [address]', 'Proxy server address to route requests through.')
146
+ .default(process.env.HTTP_PROXY || process.env.HTTPS_PROXY)
147
+ .hideHelp(true))
148
+ .option('--debug', 'Print debugging information');
149
+ // only add the `--filter` option to commands that are workspace aware
150
+ if (!COMMANDS_WITHOUT_WORKSPACE_OPTIONS.has(name)) {
151
+ base.option('--filter <app>', 'For monorepos, specify the name of the application to run the command in');
152
+ }
153
+ return base.hook('preAction', async (_parentCommand, actionCommand) => {
154
+ if (actionCommand.opts()?.debug) {
155
+ process.env.DEBUG = '*';
156
+ }
157
+ debug(`${name}:preAction`)('start');
158
+ this.analytics = { startTime: process.hrtime.bigint() };
159
+ await this.init(actionCommand);
160
+ debug(`${name}:preAction`)('end');
161
+ });
162
+ }
163
+ /** don't show help options on command overview (mostly used on top commands like `addons` where options only apply on children) */
164
+ noHelpOptions() {
165
+ this.noBaseOptions = true;
166
+ return this;
270
167
  }
271
-
272
168
  /**
273
- * override the longestSubcommandTermLength
274
- * @param {BaseCommand} command
275
- * @returns {number}
169
+ * Set examples for the command
170
+ * @param {string[]} examples
276
171
  */
277
- help.longestSubcommandTermLength = (command) =>
278
- getCommands(command).reduce((max, cmd) => Math.max(max, cmd.name().length), 0)
279
-
172
+ // @ts-expect-error TS(7006) FIXME: Parameter 'examples' implicitly has an 'any' type.
173
+ addExamples(examples) {
174
+ this.examples = examples;
175
+ return this;
176
+ }
280
177
  /**
281
- * override the longestOptionTermLength to react on hide options flag
282
- * @param {BaseCommand} command
283
- * @param {import('commander').Help} helper
284
- * @returns {number}
178
+ * Overrides the help output of commander with custom styling
179
+ * @returns {import('commander').Help}
285
180
  */
286
- help.longestOptionTermLength = (command, helper) =>
287
- (command.noBaseOptions === false &&
288
- helper.visibleOptions(command).reduce((max, option) => Math.max(max, helper.optionTerm(option).length), 0)) ||
289
- 0
290
-
181
+ createHelp() {
182
+ const help = super.createHelp();
183
+ help.commandUsage = (command) => {
184
+ const term = this.name() === 'netlify'
185
+ ? `${HELP_$} ${command.name()} [COMMAND]`
186
+ : `${HELP_$} ${command.parent?.name()} ${command.name()} ${command.usage()}`;
187
+ return padLeft(term, HELP_INDENT_WIDTH);
188
+ };
189
+ /**
190
+ * @param {BaseCommand} command
191
+ */
192
+ // @ts-expect-error TS(7006) FIXME: Parameter 'command' implicitly has an 'any' type.
193
+ const getCommands = (command) => {
194
+ const parentCommand = this.name() === 'netlify' ? command : command.parent;
195
+ return (
196
+ // @ts-expect-error TS(7006) FIXME: Parameter 'cmd' implicitly has an 'any' type.
197
+ parentCommand?.commands.filter((cmd) => {
198
+ if (cmd._hidden)
199
+ return false;
200
+ // the root command
201
+ if (this.name() === 'netlify') {
202
+ // don't include subcommands on the main page
203
+ return !cmd.name().includes(':');
204
+ }
205
+ return cmd.name().startsWith(`${command.name()}:`);
206
+ }) || []);
207
+ };
208
+ /**
209
+ * override the longestSubcommandTermLength
210
+ * @param {BaseCommand} command
211
+ * @returns {number}
212
+ */
213
+ help.longestSubcommandTermLength = (command) =>
214
+ // @ts-expect-error TS(7006) FIXME: Parameter 'max' implicitly has an 'any' type.
215
+ getCommands(command).reduce((max, cmd) => Math.max(max, cmd.name().length), 0);
216
+ /**
217
+ * override the longestOptionTermLength to react on hide options flag
218
+ * @param {BaseCommand} command
219
+ * @param {import('commander').Help} helper
220
+ * @returns {number}
221
+ */
222
+ help.longestOptionTermLength = (command, helper) =>
223
+ // @ts-expect-error TS(2551) FIXME: Property 'noBaseOptions' does not exist on type 'C... Remove this comment to see the full error message
224
+ (command.noBaseOptions === false &&
225
+ helper.visibleOptions(command).reduce((max, option) => Math.max(max, helper.optionTerm(option).length), 0)) ||
226
+ 0;
227
+ /**
228
+ * override the format help function to style it correctly
229
+ * @param {BaseCommand} command
230
+ * @param {import('commander').Help} helper
231
+ * @returns {string}
232
+ */
233
+ help.formatHelp = (command, helper) => {
234
+ const parentCommand = this.name() === 'netlify' ? command : command.parent;
235
+ const termWidth = helper.padWidth(command, helper);
236
+ const helpWidth = helper.helpWidth || FALLBACK_HELP_CMD_WIDTH;
237
+ /**
238
+ * formats a term correctly
239
+ * @param {string} term
240
+ * @param {string} [description]
241
+ * @param {boolean} [isCommand]
242
+ * @returns {string}
243
+ */
244
+ // @ts-expect-error TS(7006) FIXME: Parameter 'term' implicitly has an 'any' type.
245
+ const formatItem = (term, description, isCommand = false) => {
246
+ const bang = isCommand ? `${HELP_$} ` : '';
247
+ if (description) {
248
+ const pad = termWidth + HELP_SEPARATOR_WIDTH;
249
+ const fullText = `${bang}${term.padEnd(pad - (isCommand ? 2 : 0))}${chalk.grey(description)}`;
250
+ return helper.wrap(fullText, helpWidth - HELP_INDENT_WIDTH, pad);
251
+ }
252
+ return `${bang}${term}`;
253
+ };
254
+ /** @type {string[]} */
255
+ // @ts-expect-error TS(7034) FIXME: Variable 'output' implicitly has type 'any[]' in s... Remove this comment to see the full error message
256
+ let output = [];
257
+ // Description
258
+ const [topDescription, ...commandDescription] = (helper.commandDescription(command) || '').split('\n');
259
+ if (topDescription.length !== 0) {
260
+ // @ts-expect-error TS(7005) FIXME: Variable 'output' implicitly has an 'any[]' type.
261
+ output = [...output, topDescription, ''];
262
+ }
263
+ // on the parent help command the version should be displayed
264
+ if (this.name() === 'netlify') {
265
+ // @ts-expect-error TS(7005) FIXME: Variable 'output' implicitly has an 'any[]' type.
266
+ output = [...output, chalk.bold('VERSION'), formatHelpList([formatItem(USER_AGENT)]), ''];
267
+ }
268
+ // Usage
269
+ // @ts-expect-error TS(7005) FIXME: Variable 'output' implicitly has an 'any[]' type.
270
+ output = [...output, chalk.bold('USAGE'), helper.commandUsage(command), ''];
271
+ // Arguments
272
+ const argumentList = helper
273
+ .visibleArguments(command)
274
+ .map((argument) => formatItem(helper.argumentTerm(argument), helper.argumentDescription(argument)));
275
+ if (argumentList.length !== 0) {
276
+ output = [...output, chalk.bold('ARGUMENTS'), formatHelpList(argumentList), ''];
277
+ }
278
+ // @ts-expect-error TS(2551) FIXME: Property 'noBaseOptions' does not exist on type 'C... Remove this comment to see the full error message
279
+ if (command.noBaseOptions === false) {
280
+ // Options
281
+ const optionList = helper
282
+ .visibleOptions(command)
283
+ .sort(sortOptions)
284
+ .map((option) => formatItem(helper.optionTerm(option), helper.optionDescription(option)));
285
+ if (optionList.length !== 0) {
286
+ output = [...output, chalk.bold('OPTIONS'), formatHelpList(optionList), ''];
287
+ }
288
+ }
289
+ // Description
290
+ if (commandDescription.length !== 0) {
291
+ output = [...output, chalk.bold('DESCRIPTION'), formatHelpList(commandDescription), ''];
292
+ }
293
+ // Aliases
294
+ // @ts-expect-error TS(2551) FIXME: Property '_aliases' does not exist on type 'Comman... Remove this comment to see the full error message
295
+ if (command._aliases.length !== 0) {
296
+ // @ts-expect-error TS(2551) FIXME: Property '_aliases' does not exist on type 'Comman... Remove this comment to see the full error message
297
+ const aliases = command._aliases.map((alias) => formatItem(`${parentCommand.name()} ${alias}`, null, true));
298
+ output = [...output, chalk.bold('ALIASES'), formatHelpList(aliases), ''];
299
+ }
300
+ // @ts-expect-error TS(2339) FIXME: Property 'examples' does not exist on type 'Comman... Remove this comment to see the full error message
301
+ if (command.examples.length !== 0) {
302
+ output = [
303
+ ...output,
304
+ chalk.bold('EXAMPLES'),
305
+ // @ts-expect-error TS(2339) FIXME: Property 'examples' does not exist on type 'Comman... Remove this comment to see the full error message
306
+ formatHelpList(command.examples.map((example) => `${HELP_$} ${example}`)),
307
+ '',
308
+ ];
309
+ }
310
+ // @ts-expect-error TS(7006) FIXME: Parameter 'cmd' implicitly has an 'any' type.
311
+ const commandList = getCommands(command).map((cmd) => formatItem(cmd.name(), helper.subcommandDescription(cmd).split('\n')[0], true));
312
+ if (commandList.length !== 0) {
313
+ output = [...output, chalk.bold('COMMANDS'), formatHelpList(commandList), ''];
314
+ }
315
+ return [...output, ''].join('\n');
316
+ };
317
+ return help;
318
+ }
291
319
  /**
292
- * override the format help function to style it correctly
293
- * @param {BaseCommand} command
294
- * @param {import('commander').Help} helper
295
- * @returns {string}
320
+ * Will be called on the end of an action to track the metrics
321
+ * @param {*} [error_]
296
322
  */
297
- help.formatHelp = (command, helper) => {
298
- const parentCommand = this.name() === 'netlify' ? command : command.parent
299
- const termWidth = helper.padWidth(command, helper)
300
- const helpWidth = helper.helpWidth || FALLBACK_HELP_CMD_WIDTH
301
- /**
302
- * formats a term correctly
303
- * @param {string} term
304
- * @param {string} [description]
305
- * @param {boolean} [isCommand]
306
- * @returns {string}
307
- */
308
- const formatItem = (term, description, isCommand = false) => {
309
- const bang = isCommand ? `${HELP_$} ` : ''
310
-
311
- if (description) {
312
- const pad = termWidth + HELP_SEPARATOR_WIDTH
313
- const fullText = `${bang}${term.padEnd(pad - (isCommand ? 2 : 0))}${chalk.grey(description)}`
314
- return helper.wrap(fullText, helpWidth - HELP_INDENT_WIDTH, pad)
323
+ // @ts-expect-error TS(7006) FIXME: Parameter 'error_' implicitly has an 'any' type.
324
+ async onEnd(error_) {
325
+ // @ts-expect-error TS(2339) FIXME: Property 'payload' does not exist on type '{ start... Remove this comment to see the full error message
326
+ const { payload, startTime } = this.analytics;
327
+ const duration = getDuration(startTime);
328
+ const status = error_ === undefined ? 'success' : 'error';
329
+ const command = Array.isArray(this.args) ? this.args[0] : this.name();
330
+ debug(`${this.name()}:onEnd`)(`Command: ${command}. Status: ${status}. Duration: ${duration}ms`);
331
+ try {
332
+ await track('command', {
333
+ ...payload,
334
+ command,
335
+ duration,
336
+ status,
337
+ });
315
338
  }
316
-
317
- return `${bang}${term}`
318
- }
319
-
320
- /** @type {string[]} */
321
- let output = []
322
-
323
- // Description
324
- const [topDescription, ...commandDescription] = (helper.commandDescription(command) || '').split('\n')
325
- if (topDescription.length !== 0) {
326
- output = [...output, topDescription, '']
327
- }
328
-
329
- // on the parent help command the version should be displayed
330
- if (this.name() === 'netlify') {
331
- output = [...output, chalk.bold('VERSION'), formatHelpList([formatItem(USER_AGENT)]), '']
332
- }
333
-
334
- // Usage
335
- output = [...output, chalk.bold('USAGE'), helper.commandUsage(command), '']
336
-
337
- // Arguments
338
- const argumentList = helper
339
- .visibleArguments(command)
340
- .map((argument) => formatItem(helper.argumentTerm(argument), helper.argumentDescription(argument)))
341
- if (argumentList.length !== 0) {
342
- output = [...output, chalk.bold('ARGUMENTS'), formatHelpList(argumentList), '']
343
- }
344
-
345
- if (command.noBaseOptions === false) {
346
- // Options
347
- const optionList = helper
348
- .visibleOptions(command)
349
- .sort(sortOptions)
350
- .map((option) => formatItem(helper.optionTerm(option), helper.optionDescription(option)))
351
- if (optionList.length !== 0) {
352
- output = [...output, chalk.bold('OPTIONS'), formatHelpList(optionList), '']
339
+ catch { }
340
+ if (error_ !== undefined) {
341
+ // @ts-expect-error TS(2345) FIXME: Argument of type 'string | Error' is not assignabl... Remove this comment to see the full error message
342
+ error(error_ instanceof Error ? error_ : format(error_), { exit: false });
343
+ exit(1);
353
344
  }
354
- }
355
-
356
- // Description
357
- if (commandDescription.length !== 0) {
358
- output = [...output, chalk.bold('DESCRIPTION'), formatHelpList(commandDescription), '']
359
- }
360
-
361
- // Aliases
362
-
363
- if (command._aliases.length !== 0) {
364
- const aliases = command._aliases.map((alias) => formatItem(`${parentCommand.name()} ${alias}`, null, true))
365
- output = [...output, chalk.bold('ALIASES'), formatHelpList(aliases), '']
366
- }
367
-
368
- if (command.examples.length !== 0) {
369
- output = [
370
- ...output,
371
- chalk.bold('EXAMPLES'),
372
- formatHelpList(command.examples.map((example) => `${HELP_$} ${example}`)),
373
- '',
374
- ]
375
- }
376
-
377
- const commandList = getCommands(command).map((cmd) =>
378
- formatItem(cmd.name(), helper.subcommandDescription(cmd).split('\n')[0], true),
379
- )
380
- if (commandList.length !== 0) {
381
- output = [...output, chalk.bold('COMMANDS'), formatHelpList(commandList), '']
382
- }
383
-
384
- return [...output, ''].join('\n')
385
- }
386
- return help
387
- }
388
-
389
- /**
390
- * Will be called on the end of an action to track the metrics
391
- * @param {*} [error_]
392
- */
393
- async onEnd(error_) {
394
- const { payload, startTime } = this.analytics
395
- const duration = getDuration(startTime)
396
- const status = error_ === undefined ? 'success' : 'error'
397
-
398
- const command = Array.isArray(this.args) ? this.args[0] : this.name()
399
-
400
- debug(`${this.name()}:onEnd`)(`Command: ${command}. Status: ${status}. Duration: ${duration}ms`)
401
-
402
- try {
403
- await track('command', {
404
- ...payload,
405
- command,
406
- duration,
407
- status,
408
- })
409
- } catch {}
410
-
411
- if (error_ !== undefined) {
412
- error(error_ instanceof Error ? error_ : format(error_), { exit: false })
413
- exit(1)
414
345
  }
415
- }
416
-
417
- /**
418
- *
419
- * @param {string|undefined} tokenFromFlag
420
- * @returns
421
- */
422
- async authenticate(tokenFromFlag) {
423
- const [token] = await getToken(tokenFromFlag)
424
- if (token) {
425
- return token
426
- }
427
- return this.expensivelyAuthenticate()
428
- }
429
-
430
- async expensivelyAuthenticate() {
431
- const webUI = process.env.NETLIFY_WEB_UI || 'https://app.netlify.com'
432
- log(`Logging into your Netlify account...`)
433
-
434
- // Create ticket for auth
435
- // @ts-ignore Types from api are wrong and they don't recognize `createTicket`
436
- const ticket = await this.netlify.api.createTicket({
437
- clientId: CLIENT_ID,
438
- })
439
-
440
- // Open browser for authentication
441
- const authLink = `${webUI}/authorize?response_type=ticket&ticket=${ticket.id}`
442
-
443
- log(`Opening ${authLink}`)
444
- await openBrowser({ url: authLink })
445
-
446
- const accessToken = await pollForToken({
447
- api: this.netlify.api,
448
- ticket,
449
- })
450
-
451
- // @ts-ignore Types from api are wrong and they don't recognize `getCurrentUser`
452
- const { email, full_name: name, id: userId } = await this.netlify.api.getCurrentUser()
453
-
454
- const userData = merge(this.netlify.globalConfig.get(`users.${userId}`), {
455
- id: userId,
456
- name,
457
- email,
458
- auth: {
459
- token: accessToken,
460
- github: {
461
- user: undefined,
462
- token: undefined,
463
- },
464
- },
465
- })
466
- // Set current userId
467
- this.netlify.globalConfig.set('userId', userId)
468
- // Set user data
469
- this.netlify.globalConfig.set(`users.${userId}`, userData)
470
-
471
- await identify({
472
- name,
473
- email,
474
- userId,
475
- })
476
- await track('user_login', {
477
- email,
478
- })
479
-
480
- // Log success
481
- log()
482
- log(`${chalk.greenBright('You are now logged into your Netlify account!')}`)
483
- log()
484
- log(`Run ${chalk.cyanBright('netlify status')} for account details`)
485
- log()
486
- log(`To see all available commands run: ${chalk.cyanBright('netlify help')}`)
487
- log()
488
- return accessToken
489
- }
490
-
491
- /**
492
- * Adds some data to the analytics payload
493
- * @param {Record<string, unknown>} payload
494
- */
495
- setAnalyticsPayload(payload) {
496
- const newPayload = { ...this.analytics.payload, ...payload }
497
- this.analytics = { ...this.analytics, payload: newPayload }
498
- }
499
-
500
- /**
501
- * Initializes the options and parses the configuration needs to be called on start of a command function
502
- * @param {BaseCommand} actionCommand The command of the action that is run (`this.` gets the parent command)
503
- * @private
504
- */
505
- async init(actionCommand) {
506
- debug(`${actionCommand.name()}:init`)('start')
507
- const flags = actionCommand.opts()
508
- // here we actually want to use the process.cwd as we are setting the workingDir
509
- // eslint-disable-next-line workspace/no-process-cwd
510
- this.workingDir = flags.cwd ? resolve(flags.cwd) : process.cwd()
511
-
512
- // ==================================================
513
- // Create a Project and run the Heuristics to detect
514
- // if we are running inside a monorepo or not.
515
- // ==================================================
516
-
517
- // retrieve the repository root
518
- const rootDir = await getRepositoryRoot()
519
- // Get framework, add to analytics payload for every command, if a framework is set
520
- const fs = new NodeFS()
521
- // disable logging inside the project and FS if not in debug mode
522
- fs.logger = actionCommand.opts()?.debug ? new DefaultLogger('debug') : new NoopLogger()
523
- this.project = new Project(fs, this.workingDir, rootDir)
524
- .setEnvironment(process.env)
525
- .setNodeVersion(process.version)
526
- // eslint-disable-next-line promise/prefer-await-to-callbacks
527
- .setReportFn((err, reportConfig) => {
528
- reportError(err, {
529
- severity: reportConfig?.severity || 'error',
530
- metadata: reportConfig?.metadata,
531
- })
532
- })
533
- const frameworks = await this.project.detectFrameworks()
534
- /** @type { string|undefined} */
535
- let packageConfig = flags.config ? resolve(flags.config) : undefined
536
- // check if we have detected multiple projects inside which one we have to perform our operations.
537
- // only ask to select one if on the workspace root
538
- if (
539
- !COMMANDS_WITHOUT_WORKSPACE_OPTIONS.has(actionCommand.name()) &&
540
- this.project.workspace?.packages.length &&
541
- this.project.workspace.isRoot
542
- ) {
543
- this.workspacePackage = await selectWorkspace(this.project, actionCommand.opts().filter)
544
- this.workingDir = join(this.project.jsWorkspaceRoot, this.workspacePackage)
545
- }
546
-
547
- this.jsWorkspaceRoot = this.project.jsWorkspaceRoot
548
- // detect if a toml exists in this package.
549
- const tomlFile = join(this.workingDir, 'netlify.toml')
550
- if (!packageConfig && existsSync(tomlFile)) {
551
- packageConfig = tomlFile
552
- }
553
-
554
- // ==================================================
555
- // Retrieve Site id and build state from the state.json
556
- // ==================================================
557
- const state = new StateConfig(this.workingDir)
558
- const [token] = await getToken(flags.auth)
559
-
560
- const apiUrlOpts = {
561
- userAgent: USER_AGENT,
562
- }
563
-
564
- if (process.env.NETLIFY_API_URL) {
565
- const apiUrl = new URL(process.env.NETLIFY_API_URL)
566
- apiUrlOpts.scheme = apiUrl.protocol.slice(0, -1)
567
- apiUrlOpts.host = apiUrl.host
568
- apiUrlOpts.pathPrefix =
569
- process.env.NETLIFY_API_URL === `${apiUrl.protocol}//${apiUrl.host}` ? '/api/v1' : apiUrl.pathname
346
+ /**
347
+ *
348
+ * @param {string|undefined} tokenFromFlag
349
+ * @returns
350
+ */
351
+ // @ts-expect-error TS(7006) FIXME: Parameter 'tokenFromFlag' implicitly has an 'any' ... Remove this comment to see the full error message
352
+ async authenticate(tokenFromFlag) {
353
+ const [token] = await getToken(tokenFromFlag);
354
+ if (token) {
355
+ return token;
356
+ }
357
+ return this.expensivelyAuthenticate();
570
358
  }
571
-
572
- // ==================================================
573
- // Start retrieving the configuration through the
574
- // configuration file and the API
575
- // ==================================================
576
- const cachedConfig = await actionCommand.getConfig({
577
- cwd: flags.cwd ? this.workingDir : this.jsWorkspaceRoot || this.workingDir,
578
- repositoryRoot: rootDir,
579
- packagePath: this.workspacePackage,
580
- // The config flag needs to be resolved from the actual process working directory
581
- configFilePath: packageConfig,
582
- state,
583
- token,
584
- ...apiUrlOpts,
585
- })
586
- const { buildDir, config, configPath, repositoryRoot, siteInfo } = cachedConfig
587
- const normalizedConfig = normalizeConfig(config)
588
- const agent = await getAgent({
589
- httpProxy: flags.httpProxy,
590
- certificateFile: flags.httpProxyCertificateFilename,
591
- })
592
- const apiOpts = { ...apiUrlOpts, agent }
593
- const api = new NetlifyAPI(token || '', apiOpts)
594
-
595
- // If a user passes a site name as an option instead of a site ID to options.site, the siteInfo object
596
- // will only have the property siteInfo.id. Checking for one of the other properties ensures that we can do
597
- // a re-call of the api.getSite() that is done in @netlify/config so we have the proper site object in all
598
- // commands.
599
- // options.site as a site name (and not just site id) was introduced for the deploy command, so users could
600
- // deploy by name along with by id
601
- let siteData = siteInfo
602
- if (!siteData.url && flags.site) {
603
- siteData = await getSiteByName(api, flags.site)
359
+ async expensivelyAuthenticate() {
360
+ const webUI = process.env.NETLIFY_WEB_UI || 'https://app.netlify.com';
361
+ log(`Logging into your Netlify account...`);
362
+ // Create ticket for auth
363
+ const ticket = await this.netlify.api.createTicket({
364
+ clientId: CLIENT_ID,
365
+ });
366
+ // Open browser for authentication
367
+ const authLink = `${webUI}/authorize?response_type=ticket&ticket=${ticket.id}`;
368
+ log(`Opening ${authLink}`);
369
+ // @ts-expect-error TS(2345) FIXME: Argument of type '{ url: string; }' is not assigna... Remove this comment to see the full error message
370
+ await openBrowser({ url: authLink });
371
+ const accessToken = await pollForToken({
372
+ api: this.netlify.api,
373
+ ticket,
374
+ });
375
+ const { email, full_name: name, id: userId } = await this.netlify.api.getCurrentUser();
376
+ const userData = merge(this.netlify.globalConfig.get(`users.${userId}`), {
377
+ id: userId,
378
+ name,
379
+ email,
380
+ auth: {
381
+ token: accessToken,
382
+ github: {
383
+ user: undefined,
384
+ token: undefined,
385
+ },
386
+ },
387
+ });
388
+ // Set current userId
389
+ this.netlify.globalConfig.set('userId', userId);
390
+ // Set user data
391
+ this.netlify.globalConfig.set(`users.${userId}`, userData);
392
+ await identify({
393
+ name,
394
+ email,
395
+ userId,
396
+ });
397
+ await track('user_login', {
398
+ email,
399
+ });
400
+ // Log success
401
+ log();
402
+ log(`${chalk.greenBright('You are now logged into your Netlify account!')}`);
403
+ log();
404
+ log(`Run ${chalk.cyanBright('netlify status')} for account details`);
405
+ log();
406
+ log(`To see all available commands run: ${chalk.cyanBright('netlify help')}`);
407
+ log();
408
+ return accessToken;
604
409
  }
605
-
606
- const globalConfig = await getGlobalConfig()
607
-
608
- // ==================================================
609
- // Perform analytics reporting
610
- // ==================================================
611
- const frameworkIDs = frameworks?.map((framework) => framework.id)
612
- if (frameworkIDs?.length !== 0) {
613
- this.setAnalyticsPayload({ frameworks: frameworkIDs })
410
+ /**
411
+ * Adds some data to the analytics payload
412
+ * @param {Record<string, unknown>} payload
413
+ */
414
+ // @ts-expect-error TS(7006) FIXME: Parameter 'payload' implicitly has an 'any' type.
415
+ setAnalyticsPayload(payload) {
416
+ // @ts-expect-error TS(2339) FIXME: Property 'payload' does not exist on type '{ start... Remove this comment to see the full error message
417
+ const newPayload = { ...this.analytics.payload, ...payload };
418
+ // @ts-expect-error TS(2322) FIXME: Type '{ payload: any; startTime: bigint; }' is not... Remove this comment to see the full error message
419
+ this.analytics = { ...this.analytics, payload: newPayload };
614
420
  }
615
- this.setAnalyticsPayload({
616
- monorepo: Boolean(this.project.workspace),
617
- packageManager: this.project.packageManager?.name,
618
- buildSystem: this.project.buildSystems.map(({ id }) => id),
619
- })
620
-
621
- // set the project and the netlify api object on the command,
622
- // to be accessible inside each command.
623
- actionCommand.project = this.project
624
- actionCommand.workingDir = this.workingDir
625
- actionCommand.workspacePackage = this.workspacePackage
626
- actionCommand.jsWorkspaceRoot = this.jsWorkspaceRoot
627
-
628
- // Either an existing configuration file from `@netlify/config` or a file path
629
- // that should be used for creating it.
630
- const configFilePath = configPath || join(this.workingDir, 'netlify.toml')
631
-
632
- actionCommand.netlify = {
633
- // api methods
634
- api,
635
- apiOpts,
636
- // The absolute repository root (detected through @netlify/config)
637
- repositoryRoot,
638
- configFilePath,
639
- relConfigFilePath: relative(repositoryRoot, configFilePath),
640
- // current site context
641
- site: {
642
- root: buildDir,
643
- configPath,
644
- get id() {
645
- return state.get('siteId')
646
- },
647
- set id(id) {
648
- state.set('siteId', id)
649
- },
650
- },
651
- // Site information retrieved using the API (api.getSite())
652
- siteInfo: siteData,
653
- // Configuration from netlify.[toml/yml]
654
- config: normalizedConfig,
655
- // Used to avoid calling @netlify/config again
656
- cachedConfig,
657
- // global cli config
658
- globalConfig,
659
- // state of current site dir
660
- state,
421
+ /**
422
+ * Initializes the options and parses the configuration needs to be called on start of a command function
423
+ * @param {BaseCommand} actionCommand The command of the action that is run (`this.` gets the parent command)
424
+ * @private
425
+ */
426
+ // @ts-expect-error TS(7006) FIXME: Parameter 'actionCommand' implicitly has an 'any' ... Remove this comment to see the full error message
427
+ async init(actionCommand) {
428
+ debug(`${actionCommand.name()}:init`)('start');
429
+ const flags = actionCommand.opts();
430
+ // here we actually want to use the process.cwd as we are setting the workingDir
431
+ // eslint-disable-next-line workspace/no-process-cwd
432
+ this.workingDir = flags.cwd ? resolve(flags.cwd) : process.cwd();
433
+ // ==================================================
434
+ // Create a Project and run the Heuristics to detect
435
+ // if we are running inside a monorepo or not.
436
+ // ==================================================
437
+ // retrieve the repository root
438
+ // @ts-expect-error TS(2554) FIXME: Expected 1 arguments, but got 0.
439
+ const rootDir = await getRepositoryRoot();
440
+ // Get framework, add to analytics payload for every command, if a framework is set
441
+ const fs = new NodeFS();
442
+ // disable logging inside the project and FS if not in debug mode
443
+ fs.logger = actionCommand.opts()?.debug ? new DefaultLogger('debug') : new NoopLogger();
444
+ this.project = new Project(fs, this.workingDir, rootDir)
445
+ .setEnvironment(process.env)
446
+ .setNodeVersion(process.version)
447
+ // eslint-disable-next-line promise/prefer-await-to-callbacks
448
+ .setReportFn((err, reportConfig) => {
449
+ reportError(err, {
450
+ severity: reportConfig?.severity || 'error',
451
+ metadata: reportConfig?.metadata,
452
+ });
453
+ });
454
+ const frameworks = await this.project.detectFrameworks();
455
+ /** @type { string|undefined} */
456
+ let packageConfig = flags.config ? resolve(flags.config) : undefined;
457
+ // check if we have detected multiple projects inside which one we have to perform our operations.
458
+ // only ask to select one if on the workspace root
459
+ if (!COMMANDS_WITHOUT_WORKSPACE_OPTIONS.has(actionCommand.name()) &&
460
+ this.project.workspace?.packages.length &&
461
+ this.project.workspace.isRoot) {
462
+ this.workspacePackage = await selectWorkspace(this.project, actionCommand.opts().filter);
463
+ this.workingDir = join(this.project.jsWorkspaceRoot, this.workspacePackage);
464
+ }
465
+ this.jsWorkspaceRoot = this.project.jsWorkspaceRoot;
466
+ // detect if a toml exists in this package.
467
+ const tomlFile = join(this.workingDir, 'netlify.toml');
468
+ if (!packageConfig && existsSync(tomlFile)) {
469
+ packageConfig = tomlFile;
470
+ }
471
+ // ==================================================
472
+ // Retrieve Site id and build state from the state.json
473
+ // ==================================================
474
+ const state = new StateConfig(this.workingDir);
475
+ const [token] = await getToken(flags.auth);
476
+ const apiUrlOpts = {
477
+ userAgent: USER_AGENT,
478
+ };
479
+ if (process.env.NETLIFY_API_URL) {
480
+ const apiUrl = new URL(process.env.NETLIFY_API_URL);
481
+ // @ts-expect-error TS(2339) FIXME: Property 'scheme' does not exist on type '{ userAg... Remove this comment to see the full error message
482
+ apiUrlOpts.scheme = apiUrl.protocol.slice(0, -1);
483
+ // @ts-expect-error TS(2339) FIXME: Property 'host' does not exist on type '{ userAgen... Remove this comment to see the full error message
484
+ apiUrlOpts.host = apiUrl.host;
485
+ // @ts-expect-error TS(2339) FIXME: Property 'pathPrefix' does not exist on type '{ us... Remove this comment to see the full error message
486
+ apiUrlOpts.pathPrefix =
487
+ process.env.NETLIFY_API_URL === `${apiUrl.protocol}//${apiUrl.host}` ? '/api/v1' : apiUrl.pathname;
488
+ }
489
+ // ==================================================
490
+ // Start retrieving the configuration through the
491
+ // configuration file and the API
492
+ // ==================================================
493
+ const cachedConfig = await actionCommand.getConfig({
494
+ cwd: flags.cwd ? this.workingDir : this.jsWorkspaceRoot || this.workingDir,
495
+ repositoryRoot: rootDir,
496
+ packagePath: this.workspacePackage,
497
+ // The config flag needs to be resolved from the actual process working directory
498
+ configFilePath: packageConfig,
499
+ state,
500
+ token,
501
+ ...apiUrlOpts,
502
+ });
503
+ const { buildDir, config, configPath, repositoryRoot, siteInfo } = cachedConfig;
504
+ const normalizedConfig = normalizeConfig(config);
505
+ const agent = await getAgent({
506
+ httpProxy: flags.httpProxy,
507
+ certificateFile: flags.httpProxyCertificateFilename,
508
+ });
509
+ const apiOpts = { ...apiUrlOpts, agent };
510
+ const api = new NetlifyAPI(token || '', apiOpts);
511
+ // If a user passes a site name as an option instead of a site ID to options.site, the siteInfo object
512
+ // will only have the property siteInfo.id. Checking for one of the other properties ensures that we can do
513
+ // a re-call of the api.getSite() that is done in @netlify/config so we have the proper site object in all
514
+ // commands.
515
+ // options.site as a site name (and not just site id) was introduced for the deploy command, so users could
516
+ // deploy by name along with by id
517
+ let siteData = siteInfo;
518
+ if (!siteData.url && flags.site) {
519
+ siteData = await getSiteByName(api, flags.site);
520
+ }
521
+ const globalConfig = await getGlobalConfig();
522
+ // ==================================================
523
+ // Perform analytics reporting
524
+ // ==================================================
525
+ // @ts-expect-error TS(7006) FIXME: Parameter 'framework' implicitly has an 'any' type... Remove this comment to see the full error message
526
+ const frameworkIDs = frameworks?.map((framework) => framework.id);
527
+ if (frameworkIDs?.length !== 0) {
528
+ this.setAnalyticsPayload({ frameworks: frameworkIDs });
529
+ }
530
+ this.setAnalyticsPayload({
531
+ monorepo: Boolean(this.project.workspace),
532
+ packageManager: this.project.packageManager?.name,
533
+ // @ts-expect-error TS(7031) FIXME: Binding element 'id' implicitly has an 'any' type.
534
+ buildSystem: this.project.buildSystems.map(({ id }) => id),
535
+ });
536
+ // set the project and the netlify api object on the command,
537
+ // to be accessible inside each command.
538
+ actionCommand.project = this.project;
539
+ actionCommand.workingDir = this.workingDir;
540
+ actionCommand.workspacePackage = this.workspacePackage;
541
+ actionCommand.jsWorkspaceRoot = this.jsWorkspaceRoot;
542
+ // Either an existing configuration file from `@netlify/config` or a file path
543
+ // that should be used for creating it.
544
+ const configFilePath = configPath || join(this.workingDir, 'netlify.toml');
545
+ actionCommand.netlify = {
546
+ // api methods
547
+ api,
548
+ apiOpts,
549
+ // The absolute repository root (detected through @netlify/config)
550
+ repositoryRoot,
551
+ configFilePath,
552
+ relConfigFilePath: relative(repositoryRoot, configFilePath),
553
+ // current site context
554
+ site: {
555
+ root: buildDir,
556
+ configPath,
557
+ get id() {
558
+ return state.get('siteId');
559
+ },
560
+ set id(id) {
561
+ state.set('siteId', id);
562
+ },
563
+ },
564
+ // Site information retrieved using the API (api.getSite())
565
+ siteInfo: siteData,
566
+ // Configuration from netlify.[toml/yml]
567
+ config: normalizedConfig,
568
+ // Used to avoid calling @netlify/config again
569
+ cachedConfig,
570
+ // global cli config
571
+ globalConfig,
572
+ // state of current site dir
573
+ state,
574
+ };
575
+ debug(`${this.name()}:init`)('end');
661
576
  }
662
- debug(`${this.name()}:init`)('end')
663
- }
664
-
665
- /**
666
- * Find and resolve the Netlify configuration
667
- * @param {object} config
668
- * @param {string} config.cwd
669
- * @param {string|null=} config.token
670
- * @param {*} config.state
671
- * @param {boolean=} config.offline
672
- * @param {string=} config.configFilePath An optional path to the netlify configuration file e.g. netlify.toml
673
- * @param {string=} config.packagePath
674
- * @param {string=} config.repositoryRoot
675
- * @param {string=} config.host
676
- * @param {string=} config.pathPrefix
677
- * @param {string=} config.scheme
678
- * @returns {ReturnType<typeof resolveConfig>}
679
- */
680
- async getConfig(config) {
681
- // the flags that are passed to the command like `--debug` or `--offline`
682
- const flags = this.opts()
683
-
684
- try {
685
- return await resolveConfig({
686
- config: config.configFilePath,
687
- packagePath: config.packagePath,
688
- repositoryRoot: config.repositoryRoot,
689
- cwd: config.cwd,
690
- context: flags.context || process.env.CONTEXT || this.getDefaultContext(),
691
- debug: flags.debug,
692
- siteId: flags.siteId || (typeof flags.site === 'string' && flags.site) || config.state.get('siteId'),
693
- token: config.token,
694
- mode: 'cli',
695
- host: config.host,
696
- pathPrefix: config.pathPrefix,
697
- scheme: config.scheme,
698
- offline: config.offline ?? flags.offline,
699
- siteFeatureFlagPrefix: 'cli',
700
- })
701
- } catch (error_) {
702
- const isUserError = error_.customErrorInfo !== undefined && error_.customErrorInfo.type === 'resolveConfig'
703
-
704
- // If we're failing due to an error thrown by us, it might be because the token we're using is invalid.
705
- // To account for that, we try to retrieve the config again, this time without a token, to avoid making
706
- // any API calls.
707
- //
708
- // @todo Replace this with a mechanism for calling `resolveConfig` with more granularity (i.e. having
709
- // the option to say that we don't need API data.)
710
- if (isUserError && !config.offline && config.token) {
711
- if (flags.debug) {
712
- error(error_, { exit: false })
713
- warn('Failed to resolve config, falling back to offline resolution')
577
+ /**
578
+ * Find and resolve the Netlify configuration
579
+ * @param {object} config
580
+ * @param {string} config.cwd
581
+ * @param {string|null=} config.token
582
+ * @param {*} config.state
583
+ * @param {boolean=} config.offline
584
+ * @param {string=} config.configFilePath An optional path to the netlify configuration file e.g. netlify.toml
585
+ * @param {string=} config.packagePath
586
+ * @param {string=} config.repositoryRoot
587
+ * @param {string=} config.host
588
+ * @param {string=} config.pathPrefix
589
+ * @param {string=} config.scheme
590
+ * @returns {ReturnType<typeof resolveConfig>}
591
+ */
592
+ // @ts-expect-error TS(7023) FIXME: 'getConfig' implicitly has return type 'any' becau... Remove this comment to see the full error message
593
+ async getConfig(config) {
594
+ // the flags that are passed to the command like `--debug` or `--offline`
595
+ const flags = this.opts();
596
+ try {
597
+ return await resolveConfig({
598
+ config: config.configFilePath,
599
+ packagePath: config.packagePath,
600
+ repositoryRoot: config.repositoryRoot,
601
+ cwd: config.cwd,
602
+ context: flags.context || process.env.CONTEXT || this.getDefaultContext(),
603
+ debug: flags.debug,
604
+ siteId: flags.siteId || (typeof flags.site === 'string' && flags.site) || config.state.get('siteId'),
605
+ token: config.token,
606
+ mode: 'cli',
607
+ host: config.host,
608
+ pathPrefix: config.pathPrefix,
609
+ scheme: config.scheme,
610
+ offline: config.offline ?? flags.offline,
611
+ siteFeatureFlagPrefix: 'cli',
612
+ });
613
+ }
614
+ catch (error_) {
615
+ // @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'.
616
+ const isUserError = error_.customErrorInfo !== undefined && error_.customErrorInfo.type === 'resolveConfig';
617
+ // If we're failing due to an error thrown by us, it might be because the token we're using is invalid.
618
+ // To account for that, we try to retrieve the config again, this time without a token, to avoid making
619
+ // any API calls.
620
+ //
621
+ // @todo Replace this with a mechanism for calling `resolveConfig` with more granularity (i.e. having
622
+ // the option to say that we don't need API data.)
623
+ if (isUserError && !config.offline && config.token) {
624
+ if (flags.debug) {
625
+ // @ts-expect-error TS(2345) FIXME: Argument of type 'unknown' is not assignable to pa... Remove this comment to see the full error message
626
+ error(error_, { exit: false });
627
+ warn('Failed to resolve config, falling back to offline resolution');
628
+ }
629
+ // recursive call with trying to resolve offline
630
+ return this.getConfig({ ...config, offline: true });
631
+ }
632
+ // @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'.
633
+ const message = isUserError ? error_.message : error_.stack;
634
+ error(message, { exit: true });
714
635
  }
715
- // recursive call with trying to resolve offline
716
- return this.getConfig({ ...config, offline: true })
717
- }
718
-
719
- const message = isUserError ? error_.message : error_.stack
720
- error(message, { exit: true })
721
636
  }
722
- }
723
-
724
- /**
725
- * Returns the context that should be used in case one hasn't been explicitly
726
- * set. The default context is `dev` most of the time, but some commands may
727
- * wish to override that.
728
- *
729
- * @returns {'production' | 'dev'}
730
- */
731
- getDefaultContext() {
732
- return this.name() === 'serve' ? 'production' : 'dev'
733
- }
637
+ /**
638
+ * Returns the context that should be used in case one hasn't been explicitly
639
+ * set. The default context is `dev` most of the time, but some commands may
640
+ * wish to override that.
641
+ *
642
+ * @returns {'production' | 'dev'}
643
+ */
644
+ getDefaultContext() {
645
+ return this.name() === 'serve' ? 'production' : 'dev';
646
+ }
734
647
  }
735
-
736
648
  /**
737
649
  * Retrieves the repository root through a git command.
738
650
  * Returns undefined if not a git project.
739
651
  * @param {string} [cwd] The optional current working directory
740
652
  * @returns {Promise<string|undefined>}
741
653
  */
654
+ // @ts-expect-error TS(7006) FIXME: Parameter 'cwd' implicitly has an 'any' type.
742
655
  async function getRepositoryRoot(cwd) {
743
- const res = await findUp('.git', { cwd, type: 'directory' })
744
- if (res) {
745
- return join(res, '..')
746
- }
656
+ const res = await findUp('.git', { cwd, type: 'directory' });
657
+ if (res) {
658
+ return join(res, '..');
659
+ }
747
660
  }