netlify-cli 16.7.0 → 16.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/README.md +10 -0
  2. package/npm-shrinkwrap.json +16123 -10639
  3. package/package.json +6 -4
  4. package/src/commands/build/build.mjs +4 -2
  5. package/src/commands/deploy/deploy.mjs +11 -3
  6. package/src/commands/integration/deploy.mjs +397 -0
  7. package/src/commands/integration/index.mjs +25 -0
  8. package/src/commands/main.mjs +2 -0
  9. package/src/lib/build.mjs +3 -1
  10. package/src/lib/edge-functions/consts.mjs +1 -0
  11. package/src/lib/edge-functions/proxy.mjs +1 -2
  12. package/src/lib/edge-functions/registry.mjs +15 -19
  13. package/src/lib/functions/registry.mjs +31 -4
  14. package/src/lib/functions/server.mjs +15 -0
  15. package/src/functions-templates/javascript/apollo-graphql/.netlify-function-template.mjs +0 -5
  16. package/src/functions-templates/javascript/apollo-graphql/package.json +0 -21
  17. package/src/functions-templates/javascript/apollo-graphql/{{name}}.js +0 -42
  18. package/src/functions-templates/javascript/apollo-graphql-rest/.netlify-function-template.mjs +0 -5
  19. package/src/functions-templates/javascript/apollo-graphql-rest/package.json +0 -22
  20. package/src/functions-templates/javascript/apollo-graphql-rest/random-user.js +0 -23
  21. package/src/functions-templates/javascript/apollo-graphql-rest/{{name}}.js +0 -68
  22. package/src/functions-templates/javascript/auth-fetch/.netlify-function-template.mjs +0 -11
  23. package/src/functions-templates/javascript/auth-fetch/package-lock.json +0 -83
  24. package/src/functions-templates/javascript/auth-fetch/package.json +0 -21
  25. package/src/functions-templates/javascript/auth-fetch/{{name}}.js +0 -39
  26. package/src/functions-templates/javascript/create-user/.netlify-function-template.mjs +0 -11
  27. package/src/functions-templates/javascript/create-user/package.json +0 -21
  28. package/src/functions-templates/javascript/create-user/{{name}}.js +0 -36
  29. package/src/functions-templates/javascript/fauna-crud/.netlify-function-template.mjs +0 -17
  30. package/src/functions-templates/javascript/fauna-crud/create-schema.js +0 -36
  31. package/src/functions-templates/javascript/fauna-crud/create.js +0 -37
  32. package/src/functions-templates/javascript/fauna-crud/delete.js +0 -29
  33. package/src/functions-templates/javascript/fauna-crud/package.json +0 -20
  34. package/src/functions-templates/javascript/fauna-crud/read-all.js +0 -33
  35. package/src/functions-templates/javascript/fauna-crud/read.js +0 -30
  36. package/src/functions-templates/javascript/fauna-crud/update.js +0 -30
  37. package/src/functions-templates/javascript/fauna-crud/{{name}}.js +0 -62
  38. package/src/functions-templates/javascript/fauna-graphql/.netlify-function-template.mjs +0 -17
  39. package/src/functions-templates/javascript/fauna-graphql/package.json +0 -26
  40. package/src/functions-templates/javascript/fauna-graphql/schema.graphql +0 -8
  41. package/src/functions-templates/javascript/fauna-graphql/sync-schema.js +0 -38
  42. package/src/functions-templates/javascript/fauna-graphql/{{name}}.js +0 -46
  43. package/src/functions-templates/javascript/google-analytics/.netlify-function-template.mjs +0 -5
  44. package/src/functions-templates/javascript/google-analytics/package-lock.json +0 -100
  45. package/src/functions-templates/javascript/google-analytics/package.json +0 -25
  46. package/src/functions-templates/javascript/google-analytics/{{name}}.js +0 -114
  47. package/src/functions-templates/javascript/graphql-gateway/.netlify-function-template.mjs +0 -5
  48. package/src/functions-templates/javascript/graphql-gateway/example-sibling-function-graphql-1.js +0 -42
  49. package/src/functions-templates/javascript/graphql-gateway/example-sibling-function-graphql-2.js +0 -80
  50. package/src/functions-templates/javascript/graphql-gateway/package.json +0 -24
  51. package/src/functions-templates/javascript/graphql-gateway/{{name}}.js +0 -75
  52. package/src/functions-templates/javascript/hasura-event-triggered/.netlify-function-template.mjs +0 -5
  53. package/src/functions-templates/javascript/hasura-event-triggered/package.json +0 -21
  54. package/src/functions-templates/javascript/hasura-event-triggered/{{name}}.js +0 -40
  55. package/src/functions-templates/javascript/node-fetch/.netlify-function-template.mjs +0 -5
  56. package/src/functions-templates/javascript/node-fetch/package.json +0 -19
  57. package/src/functions-templates/javascript/node-fetch/{{name}}.js +0 -29
  58. package/src/functions-templates/javascript/oauth-passport/.netlify-function-template.mjs +0 -5
  59. package/src/functions-templates/javascript/oauth-passport/package.json +0 -25
  60. package/src/functions-templates/javascript/oauth-passport/utils/auth.js +0 -65
  61. package/src/functions-templates/javascript/oauth-passport/utils/config.js +0 -24
  62. package/src/functions-templates/javascript/oauth-passport/{{name}}.js +0 -37
  63. package/src/functions-templates/javascript/protected-function/.netlify-function-template.mjs +0 -5
  64. package/src/functions-templates/javascript/protected-function/{{name}}.js +0 -25
  65. package/src/functions-templates/javascript/send-email/.netlify-function-template.mjs +0 -5
  66. package/src/functions-templates/javascript/send-email/package.json +0 -21
  67. package/src/functions-templates/javascript/send-email/validations.js +0 -38
  68. package/src/functions-templates/javascript/send-email/{{name}}.js +0 -68
  69. package/src/functions-templates/javascript/serverless-ssr/.netlify-function-template.mjs +0 -5
  70. package/src/functions-templates/javascript/serverless-ssr/app/index.js +0 -116
  71. package/src/functions-templates/javascript/serverless-ssr/package.json +0 -24
  72. package/src/functions-templates/javascript/serverless-ssr/serverless-http.js +0 -15
  73. package/src/functions-templates/javascript/serverless-ssr/{{name}}.js +0 -15
  74. package/src/functions-templates/javascript/set-cookie/.netlify-function-template.mjs +0 -5
  75. package/src/functions-templates/javascript/set-cookie/package.json +0 -19
  76. package/src/functions-templates/javascript/set-cookie/{{name}}.js +0 -44
  77. package/src/functions-templates/javascript/slack-rate-limit/.netlify-function-template.mjs +0 -5
  78. package/src/functions-templates/javascript/slack-rate-limit/package.json +0 -20
  79. package/src/functions-templates/javascript/slack-rate-limit/{{name}}.js +0 -115
  80. package/src/functions-templates/javascript/stripe-charge/.netlify-function-template.mjs +0 -28
  81. package/src/functions-templates/javascript/stripe-charge/package-lock.json +0 -196
  82. package/src/functions-templates/javascript/stripe-charge/package.json +0 -21
  83. package/src/functions-templates/javascript/stripe-charge/{{name}}.js +0 -56
  84. package/src/functions-templates/javascript/stripe-subscription/.netlify-function-template.mjs +0 -28
  85. package/src/functions-templates/javascript/stripe-subscription/package-lock.json +0 -196
  86. package/src/functions-templates/javascript/stripe-subscription/package.json +0 -21
  87. package/src/functions-templates/javascript/stripe-subscription/{{name}}.js +0 -52
  88. package/src/functions-templates/javascript/token-hider/.netlify-function-template.mjs +0 -29
  89. package/src/functions-templates/javascript/token-hider/package-lock.json +0 -317
  90. package/src/functions-templates/javascript/token-hider/package.json +0 -21
  91. package/src/functions-templates/javascript/token-hider/{{name}}.js +0 -37
  92. package/src/functions-templates/javascript/url-shortener/.netlify-function-template.mjs +0 -29
  93. package/src/functions-templates/javascript/url-shortener/generate-route.js +0 -53
  94. package/src/functions-templates/javascript/url-shortener/get-route.js +0 -32
  95. package/src/functions-templates/javascript/url-shortener/package-lock.json +0 -126
  96. package/src/functions-templates/javascript/url-shortener/package.json +0 -22
  97. package/src/functions-templates/javascript/url-shortener/{{name}}.js +0 -30
  98. package/src/functions-templates/javascript/using-middleware/.netlify-function-template.mjs +0 -5
  99. package/src/functions-templates/javascript/using-middleware/package.json +0 -19
  100. package/src/functions-templates/javascript/using-middleware/{{name}}.js +0 -60
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "netlify-cli",
3
3
  "description": "Netlify command line tool",
4
- "version": "16.7.0",
4
+ "version": "16.8.1",
5
5
  "author": "Netlify Inc.",
6
6
  "type": "module",
7
7
  "engines": {
@@ -44,13 +44,14 @@
44
44
  "dependencies": {
45
45
  "@bugsnag/js": "7.20.2",
46
46
  "@fastify/static": "6.10.2",
47
- "@netlify/build": "29.22.5",
47
+ "@netlify/build": "29.23.1",
48
48
  "@netlify/build-info": "7.10.1",
49
49
  "@netlify/config": "20.9.0",
50
- "@netlify/edge-bundler": "9.1.0",
50
+ "@netlify/edge-bundler": "9.3.0",
51
51
  "@netlify/local-functions-proxy": "1.1.1",
52
+ "@netlify/sdk": "^1.1.5",
52
53
  "@netlify/serverless-functions-api": "1.9.1",
53
- "@netlify/zip-it-and-ship-it": "9.24.3",
54
+ "@netlify/zip-it-and-ship-it": "9.25.1",
54
55
  "@octokit/rest": "19.0.13",
55
56
  "ansi-escapes": "6.2.0",
56
57
  "ansi-styles": "6.2.1",
@@ -104,6 +105,7 @@
104
105
  "is-stream": "3.0.0",
105
106
  "is-wsl": "2.2.0",
106
107
  "isexe": "2.0.0",
108
+ "js-yaml": "^4.1.0",
107
109
  "jsonwebtoken": "9.0.1",
108
110
  "jwt-decode": "3.1.2",
109
111
  "lambda-local": "2.1.2",
@@ -9,9 +9,11 @@ import { getEnvelopeEnv, normalizeContext } from '../../utils/env/index.mjs'
9
9
  /**
10
10
  * @param {import('../../lib/build.mjs').BuildConfig} options
11
11
  */
12
- const checkOptions = ({ cachedConfig: { siteInfo = {} }, token }) => {
12
+ export const checkOptions = ({ cachedConfig: { siteInfo = {} }, token }) => {
13
13
  if (!siteInfo.id) {
14
- error('Could not find the site ID. Please run netlify link.')
14
+ error(
15
+ 'Could not find the site ID. If your site is not on Netlify, please run `netlify init` or `netlify deploy` first. If it is, please run `netlify link`.',
16
+ )
15
17
  }
16
18
 
17
19
  if (!token) {
@@ -475,12 +475,13 @@ const bundleEdgeFunctions = async (options, command) => {
475
475
  *
476
476
  * @param {object} config
477
477
  * @param {boolean} config.deployToProduction
478
+ * @param {boolean} config.isIntegrationDeploy If the user ran netlify integration:deploy instead of just netlify deploy
478
479
  * @param {boolean} config.json If the result should be printed as json message
479
480
  * @param {boolean} config.runBuildCommand If the build command should be run
480
481
  * @param {object} config.results
481
482
  * @returns {void}
482
483
  */
483
- const printResults = ({ deployToProduction, json, results, runBuildCommand }) => {
484
+ const printResults = ({ deployToProduction, isIntegrationDeploy, json, results, runBuildCommand }) => {
484
485
  const msgData = {
485
486
  'Build logs': results.logsUrl,
486
487
  'Function logs': results.functionLogsUrl,
@@ -518,7 +519,11 @@ const printResults = ({ deployToProduction, json, results, runBuildCommand }) =>
518
519
  if (!deployToProduction) {
519
520
  log()
520
521
  log('If everything looks good on your draft URL, deploy it to your main site URL with the --prod flag.')
521
- log(`${chalk.cyanBright.bold(`netlify deploy${runBuildCommand ? ' --build' : ''} --prod`)}`)
522
+ log(
523
+ `${chalk.cyanBright.bold(
524
+ `netlify ${isIntegrationDeploy ? 'integration:' : ''}deploy${runBuildCommand ? ' --build' : ''} --prod`,
525
+ )}`,
526
+ )
522
527
  log()
523
528
  }
524
529
  }
@@ -529,7 +534,7 @@ const printResults = ({ deployToProduction, json, results, runBuildCommand }) =>
529
534
  * @param {import('commander').OptionValues} options
530
535
  * @param {import('../base-command.mjs').default} command
531
536
  */
532
- const deploy = async (options, command) => {
537
+ export const deploy = async (options, command) => {
533
538
  const { workingDir } = command
534
539
  const { api, site, siteInfo } = command.netlify
535
540
  const alias = options.alias || options.branch
@@ -675,8 +680,11 @@ const deploy = async (options, command) => {
675
680
  // @ts-ignore
676
681
  await restoreConfig(configMutations, { buildDir: deployFolder, configPath, redirectsPath })
677
682
 
683
+ const isIntegrationDeploy = command.name() === 'integration:deploy'
684
+
678
685
  printResults({
679
686
  runBuildCommand: options.build,
687
+ isIntegrationDeploy,
680
688
  json: options.json,
681
689
  results,
682
690
  deployToProduction,
@@ -0,0 +1,397 @@
1
+ /* eslint-disable import/extensions */
2
+ import { resolve } from 'path'
3
+ import { exit, env } from 'process'
4
+
5
+ // eslint-disable-next-line n/no-missing-import
6
+ import { getConfiguration } from '@netlify/sdk/cli-utils'
7
+ // eslint-disable-next-line n/no-unpublished-import
8
+ import fs from 'fs-extra'
9
+ import inquirer from 'inquirer'
10
+ import yaml from 'js-yaml'
11
+ import fetch from 'node-fetch'
12
+
13
+ import { getBuildOptions } from '../../lib/build.mjs'
14
+ import { getToken, chalk, log } from '../../utils/command-helpers.mjs'
15
+ import { getSiteInformation } from '../../utils/dev.mjs'
16
+ import { checkOptions } from '../build/build.mjs'
17
+ import { deploy as siteDeploy } from '../deploy/deploy.mjs'
18
+
19
+ function getIntegrationAPIUrl() {
20
+ return env.INTEGRATION_URL || 'https://api.netlifysdk.com'
21
+ }
22
+
23
+ export function areScopesEqual(localScopes, remoteScopes) {
24
+ if (localScopes.length !== remoteScopes.length) {
25
+ return false
26
+ }
27
+
28
+ return localScopes.every((scope) => remoteScopes.includes(scope))
29
+ }
30
+
31
+ function logScopeConfirmationMessage(localScopes, remoteScopes) {
32
+ log(chalk.yellow(`This integration is already registered. The current required scopes are:`))
33
+ for (const scope of remoteScopes) {
34
+ log(chalk.green(`- ${scope}`))
35
+ }
36
+ log(chalk.yellow('and will be updated to:'))
37
+ for (const scope of localScopes) {
38
+ log(chalk.green(`- ${scope}`))
39
+ }
40
+ log(chalk.yellow('if you continue. This will only affect future installations of the integration.'))
41
+ }
42
+
43
+ function formatScopesToWrite(registeredIntegrationScopes) {
44
+ let scopesToWrite = {}
45
+
46
+ for (const scope of registeredIntegrationScopes) {
47
+ const [resource, permission] = scope.split(':')
48
+ if (resource === 'all') {
49
+ scopesToWrite = { all: true }
50
+ break
51
+ } else {
52
+ if (!scopesToWrite[resource]) {
53
+ scopesToWrite[resource] = []
54
+ }
55
+ scopesToWrite[resource].push(permission)
56
+ }
57
+ }
58
+ return scopesToWrite
59
+ }
60
+
61
+ function formatScopesForRemote(scopes) {
62
+ const scopesToWrite = []
63
+ if (scopes.all) {
64
+ scopesToWrite.push('all')
65
+ } else {
66
+ const scopeResources = Object.keys(scopes)
67
+ scopeResources.forEach((resource) => {
68
+ const permissionsRequested = scopes[resource]
69
+ permissionsRequested.forEach((permission) => {
70
+ scopesToWrite.push(`${resource}:${permission}`)
71
+ })
72
+ })
73
+ }
74
+ return scopesToWrite.join(',')
75
+ }
76
+
77
+ function verifyRequiredFieldsAreInConfig(name, description, scopes, integrationLevel) {
78
+ const missingFields = []
79
+
80
+ if (!name) {
81
+ missingFields.push('name')
82
+ }
83
+ if (!description) {
84
+ missingFields.push('description')
85
+ }
86
+ if (!scopes) {
87
+ missingFields.push('scopes')
88
+ }
89
+ if (!integrationLevel) {
90
+ missingFields.push('integrationLevel')
91
+ }
92
+ if (missingFields.length !== 0) {
93
+ log(
94
+ chalk.yellow(
95
+ `You are missing the following fields for the integration to be deployed: ${missingFields.join(
96
+ ', ',
97
+ )}. Please add a these fields as an entry to the integration.yaml file and try again.`,
98
+ ),
99
+ )
100
+ log(
101
+ chalk.yellow(
102
+ 'For more information on the required fields, please see the documentation: https://ntl.fyi/create-private-integration',
103
+ ),
104
+ )
105
+ return false
106
+ }
107
+ return true
108
+ }
109
+
110
+ // eslint-disable-next-line max-params
111
+ export async function registerIntegration(workingDir, siteId, accountId, localIntegrationConfig, token) {
112
+ const { description, integrationLevel, name, scopes, slug } = localIntegrationConfig
113
+ log(chalk.yellow(`An integration associated with the site ID ${siteId} is not registered.`))
114
+ const registerPrompt = await inquirer.prompt([
115
+ {
116
+ type: 'confirm',
117
+ name: 'registerIntegration',
118
+ message: `Would you like to register this site as a private integration now?`,
119
+ default: false,
120
+ },
121
+ ])
122
+
123
+ if (!registerPrompt.registerIntegration) {
124
+ log(
125
+ chalk.white(
126
+ "Cancelling deployment. Please run 'netlify int deploy' again when you are ready to register the integration.",
127
+ ),
128
+ )
129
+ log(
130
+ chalk.white(
131
+ "You can also register the integration through the Netlify UI on the 'Integrations' > 'Create private integration' page",
132
+ ),
133
+ )
134
+ exit(1)
135
+ }
136
+
137
+ if (!verifyRequiredFieldsAreInConfig(name, description, scopes, integrationLevel)) {
138
+ exit(1)
139
+ }
140
+
141
+ log(chalk.white('Registering the integration...'))
142
+
143
+ const { body, statusCode } = await fetch(`${getIntegrationAPIUrl()}/${accountId}/integrations`, {
144
+ method: 'POST',
145
+ headers: {
146
+ 'netlify-token': token,
147
+ },
148
+ body: JSON.stringify({
149
+ name,
150
+ slug,
151
+ description,
152
+ hostSiteId: siteId,
153
+ scopes: formatScopesForRemote(scopes),
154
+ integrationLevel,
155
+ }),
156
+ }).then(async (res) => {
157
+ const response = await res.json()
158
+ return { body: response, statusCode: res.status }
159
+ })
160
+
161
+ if (statusCode !== 201) {
162
+ log(chalk.red(`There was an error registering the integration:`))
163
+ log()
164
+ log(chalk.red(`-----------------------------------------------`))
165
+ log(chalk.red(body.msg))
166
+ log(chalk.red(`-----------------------------------------------`))
167
+ log()
168
+ log(chalk.red(`Please try again. If the problem persists, please contact support.`))
169
+ exit(1)
170
+ }
171
+
172
+ log(chalk.green(`Successfully registered the integration with the slug: ${body.slug}`))
173
+
174
+ const updatedIntegrationConfig = yaml.dump({
175
+ config: { name, description, slug: body.slug, scopes, integrationLevel },
176
+ })
177
+
178
+ const filePath = resolve(workingDir, 'integration.yaml')
179
+ await fs.writeFile(filePath, updatedIntegrationConfig)
180
+
181
+ log(chalk.yellow('Your integration.yaml file has been updated. Please commit and push these changes.'))
182
+ }
183
+
184
+ // eslint-disable-next-line max-params
185
+ export async function updateIntegration(
186
+ workingDir,
187
+ options,
188
+ siteId,
189
+ accountId,
190
+ localIntegrationConfig,
191
+ token,
192
+ registeredIntegration,
193
+ ) {
194
+ let { description, integrationLevel, name, scopes, slug } = localIntegrationConfig
195
+
196
+ let integrationSlug = slug
197
+ if (slug !== registeredIntegration.slug) {
198
+ // Update the project's integration.yaml file with the remote slug since that will
199
+ // be considered the source of truth and is a value that can't be edited by the user.
200
+ // Let the user know they need to commit and push the changes.
201
+ integrationSlug = registeredIntegration.slug
202
+ }
203
+
204
+ if (!name) {
205
+ // Disabling this lint rule because the destructuring was not assigning the variable correct and leading to a bug
206
+ // eslint-disable-next-line prefer-destructuring
207
+ name = registeredIntegration.name
208
+ }
209
+
210
+ if (!description) {
211
+ // eslint-disable-next-line prefer-destructuring
212
+ description = registeredIntegration.description
213
+ }
214
+
215
+ if (!integrationLevel) {
216
+ // eslint-disable-next-line prefer-destructuring
217
+ integrationLevel = registeredIntegration.integrationLevel
218
+ }
219
+
220
+ // This is returned as a comma separated string and will be easier to manage here as an array
221
+ const registeredIntegrationScopes = registeredIntegration.scopes.split(',')
222
+
223
+ const scopeResources = Object.keys(scopes)
224
+ let localScopes = []
225
+
226
+ if (scopeResources.includes('all')) {
227
+ localScopes = ['all']
228
+ } else {
229
+ scopeResources.forEach((resource) => {
230
+ const permissionsRequested = scopes[resource]
231
+ permissionsRequested.forEach((permission) => {
232
+ localScopes.push(`${resource}:${permission}`)
233
+ })
234
+ })
235
+ }
236
+
237
+ if (!areScopesEqual(localScopes, registeredIntegrationScopes)) {
238
+ logScopeConfirmationMessage(localScopes, registeredIntegrationScopes)
239
+
240
+ const scopePrompt = await inquirer.prompt([
241
+ {
242
+ type: 'confirm',
243
+ name: 'updateScopes',
244
+ message: `Do you want to update the scopes?`,
245
+ default: false,
246
+ },
247
+ ])
248
+
249
+ let scopesToWrite
250
+ if (scopePrompt.updateScopes) {
251
+ // Update the scopes in remote
252
+ scopesToWrite = scopes
253
+ const { statusCode, updateResponse } = await fetch(
254
+ `${getIntegrationAPIUrl()}/${accountId}/integrations/${integrationSlug}`,
255
+ {
256
+ method: 'PUT',
257
+ headers: {
258
+ 'netlify-token': token,
259
+ },
260
+ body: JSON.stringify({
261
+ name,
262
+ description,
263
+ hostSiteId: siteId,
264
+ scopes: localScopes.join(','),
265
+ integrationLevel,
266
+ }),
267
+ },
268
+ ).then(async (res) => {
269
+ const response = await res.json()
270
+ return { updateResponse: response, statusCode: res.status }
271
+ })
272
+
273
+ if (statusCode !== 200) {
274
+ log(
275
+ chalk.red(`There was an error updating the integration: ${updateResponse}`),
276
+ chalk.red('Please try again. If the problem persists, please contact support.'),
277
+ )
278
+ exit(1)
279
+ }
280
+ } else {
281
+ const useRegisteredScopesPrompt = await inquirer.prompt([
282
+ {
283
+ type: 'confirm',
284
+ name: 'useRegisteredScopes',
285
+ message: `Do you want to save the scopes registered for your integration in your local configuration file?`,
286
+ default: false,
287
+ },
288
+ ])
289
+
290
+ if (useRegisteredScopesPrompt.useRegisteredScopes) {
291
+ // Use the scopes that are already registered
292
+ log(chalk.white('Saving the currently registered scopes to the integration.yaml file.'))
293
+ scopesToWrite = formatScopesToWrite(registeredIntegrationScopes)
294
+ }
295
+
296
+ if (!useRegisteredScopesPrompt.useRegisteredScopes && options.prod) {
297
+ log(chalk.red('Unable to deploy your integration to production without updating the registered scopes.'))
298
+ exit(1)
299
+ }
300
+ }
301
+
302
+ const updatedIntegrationConfig = yaml.dump({
303
+ config: { name, description, slug: integrationSlug, scopes: scopesToWrite, integrationLevel },
304
+ })
305
+
306
+ const filePath = resolve(workingDir, 'integration.yaml')
307
+ await fs.writeFile(filePath, updatedIntegrationConfig)
308
+
309
+ log(chalk.yellow('Changes to the integration.yaml file are complete. Please commit and push these changes.'))
310
+ }
311
+ }
312
+
313
+ /**
314
+ * The deploy command for Netlify Integrations
315
+ * @param {import('commander').OptionValues} options
316
+ * * @param {import('../base-command.mjs').default} command
317
+ */
318
+ const deploy = async (options, command) => {
319
+ const { api, cachedConfig, site, siteInfo } = command.netlify
320
+ const { id: siteId } = site
321
+ const [token] = await getToken()
322
+ const workingDir = resolve(command.workingDir)
323
+ const buildOptions = await getBuildOptions({
324
+ cachedConfig,
325
+ packagePath: command.workspacePackage,
326
+ token,
327
+ options,
328
+ })
329
+
330
+ // Confirm that a site is linked and that the user is logged in
331
+ checkOptions(buildOptions)
332
+
333
+ const { description, integrationLevel, name, scopes, slug } = await getConfiguration()
334
+ const localIntegrationConfig = { name, description, scopes, slug, integrationLevel }
335
+
336
+ const { accountId } = await getSiteInformation({
337
+ api,
338
+ site,
339
+ siteInfo,
340
+ })
341
+
342
+ const { body: registeredIntegration, statusCode } = await fetch(
343
+ `${getIntegrationAPIUrl()}/${accountId}/integrations?site_id=${siteId}`,
344
+ {
345
+ headers: {
346
+ 'netlify-token': token,
347
+ },
348
+ },
349
+ ).then(async (res) => {
350
+ const body = await res.json()
351
+ return { body, statusCode: res.status }
352
+ })
353
+
354
+ // The integration is registered on the remote
355
+ statusCode === 200
356
+ ? await updateIntegration(
357
+ workingDir,
358
+ options,
359
+ siteId,
360
+ accountId,
361
+ localIntegrationConfig,
362
+ token,
363
+ registeredIntegration,
364
+ )
365
+ : await registerIntegration(workingDir, siteId, accountId, localIntegrationConfig, token)
366
+
367
+ // Set the prod flag to true if the integration is being initially registered because we don't want the user
368
+ // to be in a weird state where the card is appearing in the integrations list but there's no production
369
+ // version of the integration deployed
370
+ options = statusCode === 200 ? options : { ...options, prod: true }
371
+
372
+ // Deploy the integration to that site
373
+ await siteDeploy(options, command)
374
+
375
+ log(
376
+ `${chalk.cyanBright.bold(
377
+ `Your integration has been deployed. Next step is to enable it for a team or site.`,
378
+ )} https://ntl.fyi/create-private-integration`,
379
+ )
380
+ }
381
+
382
+ /**
383
+ * Creates the `netlify int deploy` command
384
+ * @param {import('../base-command.mjs').default} program
385
+ * @returns
386
+ */
387
+ export const createDeployCommand = (program) =>
388
+ program
389
+ .command('integration:deploy')
390
+ .alias('int:deploy')
391
+ .description('Register, build, and deploy a private integration on Netlify')
392
+ .option('-p, --prod', 'Deploy to production', false)
393
+ .option('-b, --build', 'Build the integration', false)
394
+ .option('-a, --auth <token>', 'Netlify auth token to deploy with', env.NETLIFY_AUTH_TOKEN)
395
+ .option('-s, --site <name-or-id>', 'A site name or ID to deploy to', env.NETLIFY_SITE_ID)
396
+ .action(deploy)
397
+ /* eslint-enable import/extensions */
@@ -0,0 +1,25 @@
1
+ import { createDeployCommand } from './deploy.mjs'
2
+
3
+ /**
4
+ * The int command
5
+ * @param {import('commander').OptionValues} options
6
+ * @param {import('../base-command.mjs').default} command
7
+ */
8
+ const integrations = (options, command) => {
9
+ command.help()
10
+ }
11
+
12
+ /**
13
+ * Creates the `netlify integration` command
14
+ * @param {import('../base-command.mjs').default} program
15
+ * @returns
16
+ */
17
+ export const createIntegrationCommand = (program) => {
18
+ createDeployCommand(program)
19
+
20
+ return program
21
+ .command('integration')
22
+ .alias('int')
23
+ .description('Manage Netlify Integrations built with the Netlify SDK')
24
+ .action(integrations)
25
+ }
@@ -22,6 +22,7 @@ import { createDevCommand } from './dev/index.mjs'
22
22
  import { createEnvCommand } from './env/index.mjs'
23
23
  import { createFunctionsCommand } from './functions/index.mjs'
24
24
  import { createInitCommand } from './init/index.mjs'
25
+ import { createIntegrationCommand } from './integration/index.mjs'
25
26
  import { createLinkCommand } from './link/index.mjs'
26
27
  import { createLmCommand } from './lm/index.mjs'
27
28
  import { createLoginCommand } from './login/index.mjs'
@@ -191,6 +192,7 @@ export const createMainCommand = () => {
191
192
  createFunctionsCommand(program)
192
193
  createRecipesCommand(program)
193
194
  createInitCommand(program)
195
+ createIntegrationCommand(program)
194
196
  createLinkCommand(program)
195
197
  createLmCommand(program)
196
198
  createLoginCommand(program)
package/src/lib/build.mjs CHANGED
@@ -3,6 +3,8 @@ import process from 'process'
3
3
 
4
4
  import build from '@netlify/build'
5
5
 
6
+ import { isFeatureFlagEnabled } from '../utils/feature-flags.mjs'
7
+
6
8
  import { getBootstrapURL } from './edge-functions/bootstrap.mjs'
7
9
  import { featureFlags as edgeFunctionsFeatureFlags } from './edge-functions/consts.mjs'
8
10
 
@@ -58,7 +60,7 @@ export const getBuildOptions = ({
58
60
  const getFeatureFlagsFromSiteInfo = (siteInfo) => ({
59
61
  ...siteInfo.feature_flags,
60
62
  // see https://github.com/netlify/pod-dev-foundations/issues/581#issuecomment-1731022753
61
- zisi_golang_use_al2: siteInfo.featureFlags?.cli_golang_use_al2,
63
+ zisi_golang_use_al2: isFeatureFlagEnabled('cli_golang_use_al2', siteInfo),
62
64
  })
63
65
 
64
66
  /**
@@ -8,5 +8,6 @@ export const PUBLIC_URL_PATH = '.netlify/internal/edge-functions'
8
8
  // Netlify Build.
9
9
  export const featureFlags = {
10
10
  edge_functions_config_export: true,
11
+ edge_functions_npm_modules: true,
11
12
  edge_functions_read_deno_config: true,
12
13
  }
@@ -8,7 +8,6 @@ import * as bundler from '@netlify/edge-bundler'
8
8
  import getAvailablePort from 'get-port'
9
9
 
10
10
  import { NETLIFYDEVERR, NETLIFYDEVWARN, chalk, error as printError, log } from '../../utils/command-helpers.mjs'
11
- import { isFeatureFlagEnabled } from '../../utils/feature-flags.mjs'
12
11
  import { getGeoLocation } from '../geo-location.mjs'
13
12
  import { getPathInProject } from '../settings.mjs'
14
13
  import { startSpinner, stopSpinner } from '../spinner.mjs'
@@ -110,7 +109,7 @@ export const initializeProxy = async ({
110
109
  const userFunctionsPath = config.build.edge_functions
111
110
  const isolatePort = await getAvailablePort()
112
111
  const buildFeatureFlags = {
113
- edge_functions_npm_modules: isFeatureFlagEnabled('edge_functions_npm_modules', siteInfo),
112
+ edge_functions_npm_modules: true,
114
113
  }
115
114
  const runtimeFeatureFlags = ['edge_functions_bootstrap_failure_mode']
116
115
 
@@ -40,9 +40,6 @@ export class EdgeFunctionsRegistry {
40
40
  /** @type {RunIsolate} */
41
41
  #runIsolate
42
42
 
43
- /** @type {boolean} */
44
- #hasShownNPMWarning = false
45
-
46
43
  /** @type {Error | null} */
47
44
  #buildError = null
48
45
 
@@ -167,22 +164,13 @@ export class EdgeFunctionsRegistry {
167
164
  */
168
165
  async #build() {
169
166
  try {
170
- const {
171
- features = {},
172
- functionsConfig,
173
- graph,
174
- success,
175
- } = await this.#runIsolate(this.#functions, this.#env, {
176
- getFunctionsConfig: true,
177
- })
178
-
179
- if (features.npmModules && !this.#hasShownNPMWarning) {
180
- log(
181
- `${NETLIFYDEVWARN} Support for npm modules in edge functions is an experimental feature. To learn more about the current state of this capability or to report a problem, refer to https://ntl.fyi/edge-functions-npm.`,
182
- )
183
-
184
- this.#hasShownNPMWarning = true
185
- }
167
+ const { functionsConfig, graph, npmSpecifiersWithExtraneousFiles, success } = await this.#runIsolate(
168
+ this.#functions,
169
+ this.#env,
170
+ {
171
+ getFunctionsConfig: true,
172
+ },
173
+ )
186
174
 
187
175
  if (!success) {
188
176
  throw new Error('Build error')
@@ -207,6 +195,14 @@ export class EdgeFunctionsRegistry {
207
195
  )
208
196
 
209
197
  this.#processGraph(graph)
198
+
199
+ if (npmSpecifiersWithExtraneousFiles.length !== 0) {
200
+ const modules = npmSpecifiersWithExtraneousFiles.map((name) => chalk.yellow(name)).join(', ')
201
+
202
+ log(
203
+ `${NETLIFYDEVWARN} The following npm modules, which are directly or indirectly imported by an edge function, may not be supported: ${modules}. For more information, visit https://ntl.fyi/edge-functions-npm.`,
204
+ )
205
+ }
210
206
  } catch (error) {
211
207
  this.#buildError = error
212
208