netlify-cli 27.11.2 → 27.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/base-command.js +2 -2
- package/dist/commands/deploy/deploy.js +28 -59
- package/dist/commands/dev/dev.js +1 -2
- package/dist/commands/env/env-clone.js +1 -1
- package/dist/commands/env/env-set.js +1 -1
- package/dist/commands/env/env-unset.js +1 -1
- package/dist/commands/functions/functions-create.js +3 -93
- package/dist/commands/init/index.js +6 -1
- package/dist/commands/init/init.d.ts +3 -1
- package/dist/commands/init/init.js +22 -2
- package/dist/commands/serve/serve.js +1 -2
- package/dist/commands/sites/sites-list.js +1 -1
- package/dist/commands/unlink/unlink.js +1 -1
- package/dist/lib/edge-functions/registry.js +0 -1
- package/dist/lib/exec-fetcher.js +1 -1
- package/dist/lib/functions/form-submissions-handler.js +2 -1
- package/dist/lib/spinner.d.ts +2 -2
- package/dist/lib/spinner.js +1 -1
- package/dist/recipes/ai-context/context.js +2 -2
- package/dist/utils/deploy/deploy-site.d.ts +32 -12
- package/dist/utils/deploy/deploy-site.js +18 -40
- package/dist/utils/deploy/drop-api.d.ts +7 -1
- package/dist/utils/deploy/drop-api.js +3 -4
- package/dist/utils/deploy/file.d.ts +4 -2
- package/dist/utils/deploy/hash-config.d.ts +5 -6
- package/dist/utils/deploy/hash-config.js +1 -4
- package/dist/utils/deploy/hash-files.d.ts +9 -8
- package/dist/utils/deploy/hash-fns.d.ts +45 -10
- package/dist/utils/deploy/hasher-segments.d.ts +10 -6
- package/dist/utils/deploy/hasher-segments.js +0 -1
- package/dist/utils/deploy/process-files.d.ts +1 -1
- package/dist/utils/deploy/process-files.js +1 -0
- package/dist/utils/deploy/types.d.ts +17 -0
- package/dist/utils/deploy/types.js +2 -0
- package/dist/utils/deploy/upload-files.d.ts +8 -3
- package/dist/utils/deploy/upload-files.js +4 -3
- package/dist/utils/deploy/upload-source-zip.js +4 -7
- package/dist/utils/deploy/util.d.ts +6 -3
- package/dist/utils/deploy/util.js +4 -16
- package/dist/utils/dev.d.ts +0 -1
- package/dist/utils/dev.js +1 -27
- package/dist/utils/env/index.js +1 -3
- package/dist/utils/get-repo-data.js +1 -1
- package/dist/utils/init/agent-skills.d.ts +96 -0
- package/dist/utils/init/agent-skills.js +653 -0
- package/dist/utils/parse-raw-flags.js +1 -1
- package/dist/utils/proxy-server.d.ts +1 -2
- package/dist/utils/proxy-server.js +1 -2
- package/dist/utils/proxy.d.ts +1 -1
- package/dist/utils/proxy.js +7 -35
- package/dist/utils/types.d.ts +1 -1
- package/functions-templates/javascript/scheduled-function/package.json +1 -1
- package/functions-templates/typescript/database/package-lock.json +8 -8
- package/functions-templates/typescript/database/package.json +1 -1
- package/functions-templates/typescript/database-drizzle/package-lock.json +8 -8
- package/functions-templates/typescript/database-drizzle/package.json +1 -1
- package/functions-templates/typescript/hello-world/package-lock.json +15 -17
- package/functions-templates/typescript/hello-world/package.json +1 -1
- package/functions-templates/typescript/scheduled-function/package.json +1 -1
- package/package.json +10 -11
- package/dist/utils/addons/prepare.d.ts +0 -13
- package/dist/utils/addons/prepare.js +0 -26
|
@@ -248,7 +248,7 @@ export default class BaseCommand extends Command {
|
|
|
248
248
|
};
|
|
249
249
|
help.longestSubcommandTermLength = (command) => getCommands(command).reduce((max, cmd) => Math.max(max, cmd.name().length), 0);
|
|
250
250
|
/** override the longestOptionTermLength to react on hide options flag */
|
|
251
|
-
help.longestOptionTermLength = (command, helper) => (command.noBaseOptions
|
|
251
|
+
help.longestOptionTermLength = (command, helper) => (!command.noBaseOptions &&
|
|
252
252
|
helper.visibleOptions(command).reduce((max, option) => Math.max(max, helper.optionTerm(option).length), 0)) ||
|
|
253
253
|
0;
|
|
254
254
|
help.formatHelp = (command, helper) => {
|
|
@@ -284,7 +284,7 @@ export default class BaseCommand extends Command {
|
|
|
284
284
|
if (argumentList.length !== 0) {
|
|
285
285
|
output = [...output, chalk.bold('ARGUMENTS'), formatHelpList(argumentList), ''];
|
|
286
286
|
}
|
|
287
|
-
if (command.#noBaseOptions
|
|
287
|
+
if (!command.#noBaseOptions) {
|
|
288
288
|
// Options
|
|
289
289
|
const optionList = helper
|
|
290
290
|
.visibleOptions(command)
|
|
@@ -166,13 +166,6 @@ const validateFolders = async ({ deployFolder, functionsFolder, }) => {
|
|
|
166
166
|
const functionsFolderStat = await validateFunctionsFolder(functionsFolder);
|
|
167
167
|
return { deployFolderStat, functionsFolderStat };
|
|
168
168
|
};
|
|
169
|
-
/**
|
|
170
|
-
* @param {object} config
|
|
171
|
-
* @param {string} config.deployFolder
|
|
172
|
-
* @param {*} config.site
|
|
173
|
-
* @returns
|
|
174
|
-
*/
|
|
175
|
-
// @ts-expect-error TS(7031) FIXME: Binding element 'deployFolder' implicitly has an '... Remove this comment to see the full error message
|
|
176
169
|
const getDeployFilesFilter = ({ deployFolder, site }) => {
|
|
177
170
|
// site.root === deployFolder can happen when users run `netlify deploy --dir .`
|
|
178
171
|
// in that specific case we don't want to publish the repo node_modules
|
|
@@ -258,9 +251,9 @@ const generateDeployCommand = (options, availableTeams, command) => {
|
|
|
258
251
|
}
|
|
259
252
|
return parts.join(' ');
|
|
260
253
|
};
|
|
261
|
-
|
|
262
|
-
const prepareProductionDeploy = async ({ api, siteData, options, command }) => {
|
|
254
|
+
const prepareProductionDeploy = async ({ api, siteData, options, command, }) => {
|
|
263
255
|
if (typeof siteData.published_deploy === 'object' &&
|
|
256
|
+
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain -- FIXME: a non-object `published_deploy` must not count as locked
|
|
264
257
|
siteData.published_deploy !== null &&
|
|
265
258
|
siteData.published_deploy.locked) {
|
|
266
259
|
log(`\n${NETLIFYDEVERR} Deployments are "locked" for production context of this project\n`);
|
|
@@ -337,6 +330,7 @@ const deployProgressCb = function () {
|
|
|
337
330
|
return;
|
|
338
331
|
case 'stop':
|
|
339
332
|
default: {
|
|
333
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- FIXME: a `stop` event is assumed to follow a `start` event of the same type
|
|
340
334
|
spinnersByType[event.type].success(event.msg);
|
|
341
335
|
delete spinnersByType[event.type];
|
|
342
336
|
}
|
|
@@ -357,7 +351,7 @@ const uploadDeployBlobs = async ({ cachedConfig, deployId, options, packagePath,
|
|
|
357
351
|
// We log our own progress so we don't want this as well. Plus, this logs much of the same
|
|
358
352
|
// information as the build that (likely) came before this as part of the deploy build.
|
|
359
353
|
quiet: options.debug ?? true,
|
|
360
|
-
// @ts-expect-error(
|
|
354
|
+
// @ts-expect-error FIXME(@netlify/build): the `cachedConfig` flag is typed `Record<string, unknown>`, rejecting `CachedConfig`
|
|
361
355
|
cachedConfig,
|
|
362
356
|
packagePath,
|
|
363
357
|
deployId,
|
|
@@ -378,37 +372,7 @@ const uploadDeployBlobs = async ({ cachedConfig, deployId, options, packagePath,
|
|
|
378
372
|
phase: 'stop',
|
|
379
373
|
});
|
|
380
374
|
};
|
|
381
|
-
const runDeploy = async ({
|
|
382
|
-
// @ts-expect-error TS(7031) FIXME: Binding element 'alias' implicitly has an 'any' ty... Remove this comment to see the full error message
|
|
383
|
-
alias,
|
|
384
|
-
// @ts-expect-error TS(7031) FIXME: Binding element 'api' implicitly has an 'any' type... Remove this comment to see the full error message
|
|
385
|
-
api, command,
|
|
386
|
-
// @ts-expect-error TS(7031) FIXME: Binding element 'config' implicitly has an 'any' t... Remove this comment to see the full error message
|
|
387
|
-
config,
|
|
388
|
-
// @ts-expect-error TS(7031) FIXME: Binding element 'deployFolder' implicitly has an '... Remove this comment to see the full error message
|
|
389
|
-
deployFolder,
|
|
390
|
-
// @ts-expect-error TS(7031) FIXME: Binding element 'deployTimeout' implicitly has an ... Remove this comment to see the full error message
|
|
391
|
-
deployTimeout,
|
|
392
|
-
// @ts-expect-error TS(7031) FIXME: Binding element 'deployToProduction' implicitly ha... Remove this comment to see the full error message
|
|
393
|
-
deployToProduction,
|
|
394
|
-
// @ts-expect-error TS(7031) FIXME: Binding element 'functionsConfig' implicitly has a... Remove this comment to see the full error message
|
|
395
|
-
functionsConfig, functionsFolder,
|
|
396
|
-
// @ts-expect-error TS(7031) FIXME: Binding element 'options' implicitly has an 'a... Remove this comment to see the full error message
|
|
397
|
-
options,
|
|
398
|
-
// @ts-expect-error TS(7031) FIXME: Binding element 'packagePath' implicitly has an 'a... Remove this comment to see the full error message
|
|
399
|
-
packagePath,
|
|
400
|
-
// @ts-expect-error TS(7031) FIXME: Binding element 'silent' implicitly has an 'any' t... Remove this comment to see the full error message
|
|
401
|
-
silent,
|
|
402
|
-
// @ts-expect-error TS(7031) FIXME: Binding element 'site' implicitly has an 'any' typ... Remove this comment to see the full error message
|
|
403
|
-
site,
|
|
404
|
-
// @ts-expect-error TS(7031) FIXME: Binding element 'siteData' implicitly has an 'any'... Remove this comment to see the full error message
|
|
405
|
-
siteData,
|
|
406
|
-
// @ts-expect-error TS(7031) FIXME: Binding element 'siteId' implicitly has an 'any' t... Remove this comment to see the full error message
|
|
407
|
-
siteId,
|
|
408
|
-
// @ts-expect-error TS(7031) FIXME: Binding element 'skipFunctionsCache' implicitly ha... Remove this comment to see the full error message
|
|
409
|
-
skipFunctionsCache,
|
|
410
|
-
// @ts-expect-error TS(7031) FIXME: Binding element 'title' implicitly has an 'any' ty... Remove this comment to see the full error message
|
|
411
|
-
title, deployId: existingDeployId, }) => {
|
|
375
|
+
const runDeploy = async ({ alias, api, command, config, deployFolder, deployTimeout, deployToProduction, functionsConfig, functionsFolder, options, packagePath, silent, site, siteData, siteId, skipFunctionsCache, title, deployId: existingDeployId, }) => {
|
|
412
376
|
let results;
|
|
413
377
|
let deployId = existingDeployId;
|
|
414
378
|
let uploadSourceZipResult;
|
|
@@ -426,7 +390,11 @@ title, deployId: existingDeployId, }) => {
|
|
|
426
390
|
include_upload_url: options.uploadSourceZip,
|
|
427
391
|
...getDeploySourceFields(),
|
|
428
392
|
};
|
|
429
|
-
const createDeployResponse = await api.createSiteDeploy({
|
|
393
|
+
const createDeployResponse = (await api.createSiteDeploy({
|
|
394
|
+
siteId,
|
|
395
|
+
title,
|
|
396
|
+
body: createDeployBody,
|
|
397
|
+
}));
|
|
430
398
|
deployId = createDeployResponse.id;
|
|
431
399
|
if (options.uploadSourceZip &&
|
|
432
400
|
createDeployResponse.source_zip_upload_url &&
|
|
@@ -449,17 +417,21 @@ title, deployId: existingDeployId, }) => {
|
|
|
449
417
|
command.netlify.frameworksAPIPaths.functions.path,
|
|
450
418
|
functionsFolder,
|
|
451
419
|
].filter((folder) => Boolean(folder));
|
|
452
|
-
const manifestPath = skipFunctionsCache
|
|
420
|
+
const manifestPath = skipFunctionsCache
|
|
421
|
+
? undefined
|
|
422
|
+
: await getFunctionsManifestPath({ base: site.root, packagePath });
|
|
453
423
|
const serverManifestPath = await getServerManifestPath({ base: site.root, packagePath });
|
|
454
424
|
const redirectsPath = `${deployFolder}/_redirects`;
|
|
455
425
|
const headersPath = `${deployFolder}/_headers`;
|
|
456
426
|
const { redirects } = await parseAllRedirects({
|
|
427
|
+
// @ts-expect-error FIXME(@netlify/redirect-parser): `configRedirects` is typed `string[]` but takes redirect objects
|
|
457
428
|
configRedirects: config.redirects,
|
|
458
429
|
redirectsFiles: [redirectsPath],
|
|
459
430
|
minimal: true,
|
|
460
431
|
});
|
|
461
432
|
config.redirects = redirects;
|
|
462
433
|
const { headers } = await parseAllHeaders({
|
|
434
|
+
// @ts-expect-error FIXME(@netlify/headers-parser): `MinimalHeader` rejects the array values `NetlifyConfig['headers']` allows
|
|
463
435
|
configHeaders: config.headers,
|
|
464
436
|
headersFiles: [headersPath],
|
|
465
437
|
minimal: true,
|
|
@@ -474,7 +446,6 @@ title, deployId: existingDeployId, }) => {
|
|
|
474
446
|
packagePath: command.workspacePackage,
|
|
475
447
|
});
|
|
476
448
|
results = await deploySite(command, api, siteId, deployFolder, {
|
|
477
|
-
// @ts-expect-error FIXME
|
|
478
449
|
config,
|
|
479
450
|
fnDir: functionDirectories,
|
|
480
451
|
functionsConfig,
|
|
@@ -485,7 +456,7 @@ title, deployId: existingDeployId, }) => {
|
|
|
485
456
|
deployId,
|
|
486
457
|
filter: getDeployFilesFilter({ site, deployFolder }),
|
|
487
458
|
workingDir: command.workingDir,
|
|
488
|
-
manifestPath,
|
|
459
|
+
manifestPath: manifestPath ?? undefined,
|
|
489
460
|
packagePath,
|
|
490
461
|
serverEnabled: Boolean(siteData?.feature_flags?.netlify_build_server_standalone),
|
|
491
462
|
serverManifestPath: serverManifestPath ?? undefined,
|
|
@@ -581,10 +552,8 @@ const bundleEdgeFunctions = async (options, command) => {
|
|
|
581
552
|
// We log our own progress so we don't want this as well. Plus, this logs much of the same
|
|
582
553
|
// information as the build that (likely) came before this as part of the deploy build.
|
|
583
554
|
quiet: options.debug ?? true,
|
|
584
|
-
// (cachedConfig type error hides this one, but it still is valid) @ts-expect-error FIXME(serhalp): This is missing from the `runCoreSteps` type in @netlify/build
|
|
585
555
|
edgeFunctionsBootstrapURL: await getBootstrapURL(),
|
|
586
|
-
// @ts-expect-error
|
|
587
|
-
// Index signature for type 'string' is missing in type 'CachedConfig'.
|
|
556
|
+
// @ts-expect-error FIXME(@netlify/build): `cachedConfig` is typed `Record<string, unknown>` and `edgeFunctionsBootstrapURL` is missing from the flags
|
|
588
557
|
cachedConfig: command.netlify.cachedConfig,
|
|
589
558
|
});
|
|
590
559
|
if (!success) {
|
|
@@ -722,7 +691,6 @@ const prepAndRunDeploy = async ({ api, command, config, deployToProduction, opti
|
|
|
722
691
|
siteEnv,
|
|
723
692
|
});
|
|
724
693
|
const results = await runDeploy({
|
|
725
|
-
// @ts-expect-error FIXME
|
|
726
694
|
alias,
|
|
727
695
|
api,
|
|
728
696
|
command,
|
|
@@ -735,7 +703,7 @@ const prepAndRunDeploy = async ({ api, command, config, deployToProduction, opti
|
|
|
735
703
|
functionsFolder: functionsFolderStat && functionsFolder,
|
|
736
704
|
options,
|
|
737
705
|
packagePath: command.workspacePackage,
|
|
738
|
-
silent: options.json || options.silent,
|
|
706
|
+
silent: options.json || Boolean(options.silent),
|
|
739
707
|
site,
|
|
740
708
|
siteData,
|
|
741
709
|
siteId,
|
|
@@ -768,10 +736,11 @@ const createSiteWithFlags = async (options, command, site) => {
|
|
|
768
736
|
throw new Error('Team must be specified to create a site');
|
|
769
737
|
}
|
|
770
738
|
try {
|
|
771
|
-
|
|
739
|
+
// FIXME(@netlify/api): the `site` response type makes every field optional, unlike `SiteInfo`
|
|
740
|
+
const siteData = (await api.createSiteInTeam({
|
|
772
741
|
accountSlug: options.team,
|
|
773
742
|
body,
|
|
774
|
-
});
|
|
743
|
+
}));
|
|
775
744
|
site.id = siteData.id;
|
|
776
745
|
return siteData;
|
|
777
746
|
}
|
|
@@ -781,10 +750,11 @@ const createSiteWithFlags = async (options, command, site) => {
|
|
|
781
750
|
const suffixedName = `${siteName.trim()}-${suffix}`;
|
|
782
751
|
log(`Site name "${siteName}" is taken. Retrying with "${suffixedName}"...`);
|
|
783
752
|
try {
|
|
784
|
-
|
|
753
|
+
// FIXME(@netlify/api): the `site` response type makes every field optional, unlike `SiteInfo`
|
|
754
|
+
const siteData = (await api.createSiteInTeam({
|
|
785
755
|
accountSlug: options.team,
|
|
786
756
|
body: { name: suffixedName },
|
|
787
|
-
});
|
|
757
|
+
}));
|
|
788
758
|
site.id = siteData.id;
|
|
789
759
|
return siteData;
|
|
790
760
|
}
|
|
@@ -901,7 +871,7 @@ const anonymousDeploy = async (options, command) => {
|
|
|
901
871
|
const filter = getDeployFilesFilter({ site, deployFolder });
|
|
902
872
|
const { files, filesShaMap } = await hashFiles({
|
|
903
873
|
concurrentHash: DEFAULT_CONCURRENT_HASH,
|
|
904
|
-
directories: [deployFolder, edgeFunctionsDistPath].filter(Boolean),
|
|
874
|
+
directories: [deployFolder, edgeFunctionsDistPath].filter((dir) => Boolean(dir)),
|
|
905
875
|
filter,
|
|
906
876
|
normalizer: deployFileNormalizer.bind(null, workingDir),
|
|
907
877
|
statusCb: options.json ? () => { } : deployProgressCb(),
|
|
@@ -941,7 +911,7 @@ const anonymousDeploy = async (options, command) => {
|
|
|
941
911
|
}
|
|
942
912
|
const deploy = await waitForDropDeploy(dropApiOptions, deployInfo.id, deployInfo.deploy_id, options.timeout ? options.timeout * 1000 : DEFAULT_DEPLOY_TIMEOUT);
|
|
943
913
|
site.id = deployInfo.id;
|
|
944
|
-
const siteUrl =
|
|
914
|
+
const siteUrl = deploy.ssl_url || deploy.url || `https://${deployInfo.subdomain}.netlify.app`;
|
|
945
915
|
const isPasswordProtected = !options.createdVia || options.createdVia === 'drop';
|
|
946
916
|
const claimUrl = `https://app.netlify.com/drop/${deployInfo.subdomain}#drop_token=${dropToken}`;
|
|
947
917
|
if (options.json) {
|
|
@@ -1023,7 +993,6 @@ export const deploy = async (options, command) => {
|
|
|
1023
993
|
include_upload_url: options.uploadSourceZip,
|
|
1024
994
|
...getDeploySourceFields(),
|
|
1025
995
|
};
|
|
1026
|
-
// TODO: Type this properly in `@netlify/api`.
|
|
1027
996
|
const deployMetadata = (await api.createSiteDeploy({
|
|
1028
997
|
siteId,
|
|
1029
998
|
title: options.message,
|
|
@@ -1058,7 +1027,7 @@ export const deploy = async (options, command) => {
|
|
|
1058
1027
|
options,
|
|
1059
1028
|
workingDir,
|
|
1060
1029
|
api,
|
|
1061
|
-
site,
|
|
1030
|
+
site: site,
|
|
1062
1031
|
config: netlifyConfig,
|
|
1063
1032
|
siteData,
|
|
1064
1033
|
siteId,
|
|
@@ -1087,7 +1056,7 @@ export const deploy = async (options, command) => {
|
|
|
1087
1056
|
options,
|
|
1088
1057
|
workingDir,
|
|
1089
1058
|
api,
|
|
1090
|
-
site,
|
|
1059
|
+
site: site,
|
|
1091
1060
|
config: command.netlify.config,
|
|
1092
1061
|
siteData,
|
|
1093
1062
|
siteId,
|
package/dist/commands/dev/dev.js
CHANGED
|
@@ -91,7 +91,7 @@ export const dev = async (options, command) => {
|
|
|
91
91
|
log(`${NETLIFYDEVLOG} Injecting environment variable values for ${chalk.yellow('all scopes')}`);
|
|
92
92
|
}
|
|
93
93
|
env = await getDotEnvVariables({ devConfig, env, site });
|
|
94
|
-
const { accountId,
|
|
94
|
+
const { accountId, capabilities, siteUrl, timeouts } = await getSiteInformation({
|
|
95
95
|
// inherited from base command --offline
|
|
96
96
|
offline: options.offline,
|
|
97
97
|
api,
|
|
@@ -217,7 +217,6 @@ export const dev = async (options, command) => {
|
|
|
217
217
|
};
|
|
218
218
|
const inspectSettings = generateInspectSettings(options.edgeInspect, options.edgeInspectBrk);
|
|
219
219
|
await startProxyServer({
|
|
220
|
-
addonsUrls,
|
|
221
220
|
aiGatewayContext,
|
|
222
221
|
api,
|
|
223
222
|
blobsContext,
|
|
@@ -30,7 +30,7 @@ const cloneEnvVars = async ({ api, force, siteFrom, siteTo }) => {
|
|
|
30
30
|
const siteId = siteTo.id;
|
|
31
31
|
// @ts-expect-error TS(7031) FIXME: Binding element 'key' implicitly has an 'any' type... Remove this comment to see the full error message
|
|
32
32
|
const envVarsToDelete = envelopeTo.filter(({ key }) => keysFrom.includes(key));
|
|
33
|
-
if (envVarsToDelete.length !== 0 &&
|
|
33
|
+
if (envVarsToDelete.length !== 0 && !force) {
|
|
34
34
|
await promptEnvCloneOverwrite(siteTo.id, envVarsToDelete);
|
|
35
35
|
}
|
|
36
36
|
// delete marked env vars in parallel
|
|
@@ -34,7 +34,7 @@ const setInEnvelope = async ({ api, context, force, key, scope, secret, siteInfo
|
|
|
34
34
|
// @ts-expect-error TS(7006) FIXME: Parameter 'envVar' implicitly has an 'any' type.
|
|
35
35
|
const existing = envelopeVariables.find((envVar) => envVar.key === key);
|
|
36
36
|
// Checks if --force is passed and if it is an existing variaible, then we need to prompt the user
|
|
37
|
-
if (
|
|
37
|
+
if (!force && existing) {
|
|
38
38
|
await promptOverwriteEnvVariable(key);
|
|
39
39
|
}
|
|
40
40
|
const params = { accountId, siteId, key };
|
|
@@ -21,7 +21,7 @@ const unsetInEnvelope = async ({ api, context, force, key, siteInfo }) => {
|
|
|
21
21
|
// if not, no need to call delete; return early
|
|
22
22
|
return env;
|
|
23
23
|
}
|
|
24
|
-
if (
|
|
24
|
+
if (!force) {
|
|
25
25
|
await promptOverwriteEnvVariable(key);
|
|
26
26
|
}
|
|
27
27
|
const params = { accountId, siteId, key };
|
|
@@ -5,13 +5,12 @@ import { createRequire } from 'module';
|
|
|
5
5
|
import path, { dirname, join, relative } from 'path';
|
|
6
6
|
import process from 'process';
|
|
7
7
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
8
|
-
import {
|
|
8
|
+
import { input, search, select, Separator } from '@inquirer/prompts';
|
|
9
9
|
import { findUp } from 'find-up';
|
|
10
10
|
import fuzzy from 'fuzzy';
|
|
11
11
|
import fetch from 'node-fetch';
|
|
12
12
|
import { createSpinner } from 'nanospinner';
|
|
13
13
|
import { fileExistsAsync } from '../../lib/fs.js';
|
|
14
|
-
import { getAddons, getCurrentAddon, getSiteData } from '../../utils/addons/prepare.js';
|
|
15
14
|
import { NETLIFYDEVERR, NETLIFYDEVLOG, NETLIFYDEVWARN, chalk, logAndThrowError, log, } from '../../utils/command-helpers.js';
|
|
16
15
|
import { copyTemplateDir } from '../../utils/copy-template-dir/copy-template-dir.js';
|
|
17
16
|
import { getDotEnvVariables, injectEnvVariables } from '../../utils/dev.js';
|
|
@@ -313,8 +312,7 @@ const downloadFromURL = async function (command, options, argumentName, function
|
|
|
313
312
|
// read, execute, and delete function template file if exists
|
|
314
313
|
const fnTemplateFile = path.join(fnFolder, '.netlify-function-template.mjs');
|
|
315
314
|
if (await fileExistsAsync(fnTemplateFile)) {
|
|
316
|
-
const { default: {
|
|
317
|
-
await installAddons(command, addons, path.resolve(fnFolder));
|
|
315
|
+
const { default: { onComplete }, } = await import(pathToFileURL(fnTemplateFile).href);
|
|
318
316
|
await handleOnComplete({ command, onComplete });
|
|
319
317
|
// delete
|
|
320
318
|
await unlink(fnTemplateFile);
|
|
@@ -400,7 +398,7 @@ const scaffoldFromTemplate = async function (command, options, argumentName, fun
|
|
|
400
398
|
log(`${NETLIFYDEVLOG} Open in browser: https://github.com/netlify/cli/issues/new`);
|
|
401
399
|
}
|
|
402
400
|
else {
|
|
403
|
-
const {
|
|
401
|
+
const { lang, name: templateName, onComplete } = chosenTemplate;
|
|
404
402
|
const pathToTemplate = path.join(templatesDir, lang, templateName);
|
|
405
403
|
if (!fs.existsSync(pathToTemplate)) {
|
|
406
404
|
throw new Error(`There isn't a corresponding directory to the selected name. Template '${templateName}' is misconfigured`);
|
|
@@ -435,33 +433,12 @@ const scaffoldFromTemplate = async function (command, options, argumentName, fun
|
|
|
435
433
|
if (funcType === 'edge') {
|
|
436
434
|
await registerEFInToml(name, command.netlify);
|
|
437
435
|
}
|
|
438
|
-
await installAddons(command, addons, path.resolve(functionPath));
|
|
439
436
|
await handleOnComplete({ command, onComplete });
|
|
440
437
|
log();
|
|
441
438
|
log(chalk.greenBright(`Function created!`));
|
|
442
439
|
}
|
|
443
440
|
};
|
|
444
441
|
const TEMPLATE_PERMISSIONS = 0o777;
|
|
445
|
-
// @ts-expect-error TS(7031) FIXME: Binding element 'addonName' implicitly has an 'any... Remove this comment to see the full error message
|
|
446
|
-
const createFunctionAddon = async function ({ addonName, addons, api, siteData, siteId }) {
|
|
447
|
-
try {
|
|
448
|
-
const addon = getCurrentAddon({ addons, addonName });
|
|
449
|
-
if (addon && addon.id) {
|
|
450
|
-
log(`The "${addonName} add-on" already exists for ${siteData.name}`);
|
|
451
|
-
return false;
|
|
452
|
-
}
|
|
453
|
-
await api.createServiceInstance({
|
|
454
|
-
siteId,
|
|
455
|
-
addon: addonName,
|
|
456
|
-
body: { config: {} },
|
|
457
|
-
});
|
|
458
|
-
log(`Add-on "${addonName}" created for ${siteData.name}`);
|
|
459
|
-
return true;
|
|
460
|
-
}
|
|
461
|
-
catch (error_) {
|
|
462
|
-
return logAndThrowError(error_.message);
|
|
463
|
-
}
|
|
464
|
-
};
|
|
465
442
|
/**
|
|
466
443
|
*
|
|
467
444
|
* @param {object} config
|
|
@@ -481,73 +458,6 @@ const handleOnComplete = async ({ command, onComplete }) => {
|
|
|
481
458
|
await onComplete.call(command);
|
|
482
459
|
}
|
|
483
460
|
};
|
|
484
|
-
/**
|
|
485
|
-
*
|
|
486
|
-
* @param {object} config
|
|
487
|
-
* @param {*} config.addonCreated
|
|
488
|
-
* @param {*} config.addonDidInstall
|
|
489
|
-
* @param {import('../base-command.js').default} config.command
|
|
490
|
-
* @param {string} config.fnPath
|
|
491
|
-
*/
|
|
492
|
-
// @ts-expect-error TS(7031) FIXME: Binding element 'addonCreated' implicitly has an '... Remove this comment to see the full error message
|
|
493
|
-
const handleAddonDidInstall = async ({ addonCreated, addonDidInstall, command, fnPath }) => {
|
|
494
|
-
const { config } = command.netlify;
|
|
495
|
-
if (!addonCreated || !addonDidInstall) {
|
|
496
|
-
return;
|
|
497
|
-
}
|
|
498
|
-
const confirmPostInstall = await confirm({
|
|
499
|
-
message: `This template has an optional setup script that runs after addon install. This can be helpful for first time users to try out templates. Run the script?`,
|
|
500
|
-
default: false,
|
|
501
|
-
});
|
|
502
|
-
if (!confirmPostInstall) {
|
|
503
|
-
return;
|
|
504
|
-
}
|
|
505
|
-
await injectEnvVariables({
|
|
506
|
-
devConfig: { ...config.dev },
|
|
507
|
-
env: command.netlify.cachedConfig.env,
|
|
508
|
-
site: command.netlify.site,
|
|
509
|
-
});
|
|
510
|
-
addonDidInstall(fnPath);
|
|
511
|
-
};
|
|
512
|
-
/**
|
|
513
|
-
*
|
|
514
|
-
* @param {import('../base-command.js').default} command
|
|
515
|
-
* @param {*} functionAddons
|
|
516
|
-
* @param {*} fnPath
|
|
517
|
-
* @returns
|
|
518
|
-
*/
|
|
519
|
-
// @ts-expect-error TS(7006) FIXME: Parameter 'command' implicitly has an 'any' type.
|
|
520
|
-
const installAddons = async function (command, functionAddons, fnPath) {
|
|
521
|
-
if (functionAddons.length === 0) {
|
|
522
|
-
return;
|
|
523
|
-
}
|
|
524
|
-
const { api, site } = command.netlify;
|
|
525
|
-
const siteId = site.id;
|
|
526
|
-
if (!siteId) {
|
|
527
|
-
log('No project id found, please run inside a project directory or `netlify link`');
|
|
528
|
-
return false;
|
|
529
|
-
}
|
|
530
|
-
log(`${NETLIFYDEVLOG} checking Netlify APIs...`);
|
|
531
|
-
const [siteData, siteAddons] = await Promise.all([getSiteData({ api, siteId }), getAddons({ api, siteId })]);
|
|
532
|
-
// @ts-expect-error TS(7031) FIXME: Binding element 'addonDidInstall' implicitly has a... Remove this comment to see the full error message
|
|
533
|
-
const arr = functionAddons.map(async ({ addonDidInstall, addonName }) => {
|
|
534
|
-
log(`${NETLIFYDEVLOG} installing addon: ${chalk.yellow.inverse(addonName)}`);
|
|
535
|
-
try {
|
|
536
|
-
const addonCreated = await createFunctionAddon({
|
|
537
|
-
api,
|
|
538
|
-
addons: siteAddons,
|
|
539
|
-
siteId,
|
|
540
|
-
addonName,
|
|
541
|
-
siteData,
|
|
542
|
-
});
|
|
543
|
-
await handleAddonDidInstall({ addonCreated, addonDidInstall, command, fnPath });
|
|
544
|
-
}
|
|
545
|
-
catch (error_) {
|
|
546
|
-
return logAndThrowError(`${NETLIFYDEVERR} Error installing addon: ${error_}`);
|
|
547
|
-
}
|
|
548
|
-
});
|
|
549
|
-
return Promise.all(arr);
|
|
550
|
-
};
|
|
551
461
|
/**
|
|
552
462
|
*
|
|
553
463
|
* @param {string} funcName
|
|
@@ -4,6 +4,8 @@ export const createInitCommand = (program) => program
|
|
|
4
4
|
.description('Configure continuous deployment for a new or existing project. To create a new project without continuous deployment, use `netlify sites:create`')
|
|
5
5
|
.option('-m, --manual', 'Manually configure a git remote for CI')
|
|
6
6
|
.option('--git-remote-name <name>', 'Name of Git remote to use. e.g. "origin"')
|
|
7
|
+
.option('--skip-agent-setup', 'Skip installing Netlify skills for AI coding agents into the project')
|
|
8
|
+
.option('--reset-context', 'Replace locally edited Netlify skills with the latest release, and migrate or delete edited copies under renamed or deprecated names')
|
|
7
9
|
.addHelpText('after', () => {
|
|
8
10
|
const docsUrl = 'https://docs.netlify.com/cli/get-started/';
|
|
9
11
|
return `
|
|
@@ -12,6 +14,9 @@ For more information about getting started with Netlify CLI, see ${terminalLink(
|
|
|
12
14
|
})
|
|
13
15
|
.action(async (options, command) => {
|
|
14
16
|
const { init } = await import('./init.js');
|
|
15
|
-
await init(options, command
|
|
17
|
+
await init(options, command, {
|
|
18
|
+
setupAgentSkills: !options.skipAgentSetup,
|
|
19
|
+
resetContext: Boolean(options.resetContext),
|
|
20
|
+
});
|
|
16
21
|
});
|
|
17
22
|
//# sourceMappingURL=index.js.map
|
|
@@ -6,7 +6,9 @@ type InitExitMessageCustomizer = (code: InitExitCode, defaultMessage: string) =>
|
|
|
6
6
|
type InitExtraOptions = {
|
|
7
7
|
customizeExitMessage?: InitExitMessageCustomizer | undefined;
|
|
8
8
|
exitAfterConfiguringRepo?: boolean | undefined;
|
|
9
|
+
setupAgentSkills?: boolean | undefined;
|
|
10
|
+
resetContext?: boolean | undefined;
|
|
9
11
|
};
|
|
10
|
-
export declare const init: (options: OptionValues, command: BaseCommand, { customizeExitMessage, exitAfterConfiguringRepo }?: InitExtraOptions) => Promise<SiteInfo>;
|
|
12
|
+
export declare const init: (options: OptionValues, command: BaseCommand, { customizeExitMessage, exitAfterConfiguringRepo, setupAgentSkills: shouldSetupAgentSkills, resetContext, }?: InitExtraOptions) => Promise<SiteInfo>;
|
|
11
13
|
export {};
|
|
12
14
|
//# sourceMappingURL=init.d.ts.map
|
|
@@ -7,6 +7,7 @@ import { configureRepo } from '../../utils/init/config.js';
|
|
|
7
7
|
import { track } from '../../utils/telemetry/index.js';
|
|
8
8
|
import { link } from '../link/link.js';
|
|
9
9
|
import { sitesCreate } from '../sites/sites-create.js';
|
|
10
|
+
import { setupAgentSkills } from '../../utils/init/agent-skills.js';
|
|
10
11
|
import { getBuildSettings, saveNetlifyToml } from '../../utils/init/utils.js';
|
|
11
12
|
import { LINKED_EXISTING_SITE_EXIT_CODE, LINKED_NEW_SITE_EXIT_CODE } from './constants.js';
|
|
12
13
|
const persistState = ({ siteInfo, state }) => {
|
|
@@ -147,14 +148,33 @@ const logExistingRepoSetupAndExit = ({ repoUrl, siteName, customizeExitMessage,
|
|
|
147
148
|
// TODO add support for changing GitHub repo in site:config command
|
|
148
149
|
exit();
|
|
149
150
|
};
|
|
150
|
-
|
|
151
|
-
|
|
151
|
+
const installAgentSkills = async (command, reset) => {
|
|
152
|
+
log();
|
|
153
|
+
const result = await setupAgentSkills({ workingDir: command.netlify.repositoryRoot, reset });
|
|
154
|
+
await track('sites_agentSkillsSetup', {
|
|
155
|
+
installed: result.installed,
|
|
156
|
+
directories: result.directories,
|
|
157
|
+
skillsVersion: result.skillsVersion,
|
|
158
|
+
resetContext: reset,
|
|
159
|
+
...result.summary,
|
|
160
|
+
});
|
|
161
|
+
};
|
|
162
|
+
export const init = async (options, command, { customizeExitMessage, exitAfterConfiguringRepo = false, setupAgentSkills: shouldSetupAgentSkills = false, resetContext = false, } = {}) => {
|
|
163
|
+
command.setAnalyticsPayload({
|
|
164
|
+
manual: options.manual,
|
|
165
|
+
force: options.force,
|
|
166
|
+
skipAgentSetup: options.skipAgentSetup,
|
|
167
|
+
resetContext: options.resetContext,
|
|
168
|
+
});
|
|
152
169
|
const { repositoryRoot, state } = command.netlify;
|
|
153
170
|
const { siteInfo: existingSiteInfo } = command.netlify;
|
|
154
171
|
// Check logged in status
|
|
155
172
|
await command.authenticate();
|
|
156
173
|
// Add .netlify to .gitignore file
|
|
157
174
|
await ensureNetlifyIgnore(repositoryRoot);
|
|
175
|
+
if (shouldSetupAgentSkills) {
|
|
176
|
+
await installAgentSkills(command, resetContext);
|
|
177
|
+
}
|
|
158
178
|
const repoUrl = getRepoUrl(existingSiteInfo);
|
|
159
179
|
if (repoUrl && !options.force) {
|
|
160
180
|
logExistingAndExit({ siteInfo: existingSiteInfo });
|
|
@@ -33,7 +33,7 @@ export const serve = async (options, command) => {
|
|
|
33
33
|
log(`${NETLIFYDEVLOG} Injecting environment variable values for ${chalk.yellow('all scopes')}`);
|
|
34
34
|
}
|
|
35
35
|
env = await getDotEnvVariables({ devConfig, env, site });
|
|
36
|
-
const { accountId,
|
|
36
|
+
const { accountId, capabilities, siteUrl, timeouts } = await getSiteInformation({
|
|
37
37
|
// inherited from base command --offline
|
|
38
38
|
offline: options.offline,
|
|
39
39
|
api,
|
|
@@ -145,7 +145,6 @@ export const serve = async (options, command) => {
|
|
|
145
145
|
};
|
|
146
146
|
const inspectSettings = generateInspectSettings(options.edgeInspect, options.edgeInspectBrk);
|
|
147
147
|
const url = await startProxyServer({
|
|
148
|
-
addonsUrls,
|
|
149
148
|
blobsContext: runtimeBlobsContext,
|
|
150
149
|
command,
|
|
151
150
|
config: mergedConfig,
|
|
@@ -20,7 +20,7 @@ export const sitesList = async (options, command) => {
|
|
|
20
20
|
ssl_url: site.ssl_url,
|
|
21
21
|
account_name: site.account_name,
|
|
22
22
|
};
|
|
23
|
-
if (site.build_settings
|
|
23
|
+
if (site.build_settings?.repo_url) {
|
|
24
24
|
siteInfo.repo_url = site.build_settings.repo_url;
|
|
25
25
|
}
|
|
26
26
|
return siteInfo;
|
|
@@ -13,7 +13,7 @@ export const unlink = async (_options, command) => {
|
|
|
13
13
|
await track('sites_unlinked', {
|
|
14
14
|
siteId: siteData.id || siteId,
|
|
15
15
|
});
|
|
16
|
-
if (site
|
|
16
|
+
if (site?.configPath) {
|
|
17
17
|
log(`Unlinked ${site.configPath} from ${siteData ? siteData.name : siteId}`);
|
|
18
18
|
}
|
|
19
19
|
else {
|
|
@@ -235,7 +235,6 @@ export class EdgeFunctionsRegistryImpl {
|
|
|
235
235
|
Object.entries(envConfig).forEach(([key, variable]) => {
|
|
236
236
|
if (variable.sources.includes('ui') ||
|
|
237
237
|
variable.sources.includes('account') ||
|
|
238
|
-
variable.sources.includes('addons') ||
|
|
239
238
|
variable.sources.includes('internal') ||
|
|
240
239
|
variable.sources.some((source) => source.startsWith('.env'))) {
|
|
241
240
|
env[key] = variable.value;
|
package/dist/lib/exec-fetcher.js
CHANGED
|
@@ -105,7 +105,7 @@ export const shouldFetchLatestVersion = async ({ binPath, execArgs, execName, la
|
|
|
105
105
|
if (!stdout) {
|
|
106
106
|
return false;
|
|
107
107
|
}
|
|
108
|
-
const match =
|
|
108
|
+
const match = new RegExp(pattern).exec(stdout);
|
|
109
109
|
if (!match) {
|
|
110
110
|
return false;
|
|
111
111
|
}
|
|
@@ -46,7 +46,8 @@ export const createFormSubmissionHandler = function ({ functionsRegistry, siteUr
|
|
|
46
46
|
}
|
|
47
47
|
const originalUrl = new URL(req.url, 'http://localhost');
|
|
48
48
|
req.url = `/.netlify/functions/${handlerName}${originalUrl.search}`;
|
|
49
|
-
|
|
49
|
+
// A missing header parses to an empty type and takes the unsupported-type branch below.
|
|
50
|
+
const ct = parseContentType(req.headers['content-type'] ?? '');
|
|
50
51
|
let fields = {};
|
|
51
52
|
let files = {};
|
|
52
53
|
if (ct.type.endsWith('/x-www-form-urlencoded')) {
|
package/dist/lib/spinner.d.ts
CHANGED
|
@@ -8,9 +8,9 @@ export declare const startSpinner: ({ text }: {
|
|
|
8
8
|
/**
|
|
9
9
|
* Stops the spinner with the following text
|
|
10
10
|
*/
|
|
11
|
-
export declare const stopSpinner: ({ error, spinner, text }: {
|
|
11
|
+
export declare const stopSpinner: ({ error, spinner, text, }: {
|
|
12
12
|
error?: boolean;
|
|
13
|
-
spinner: Spinner;
|
|
13
|
+
spinner: Spinner | undefined;
|
|
14
14
|
text?: string;
|
|
15
15
|
}) => void;
|
|
16
16
|
/**
|
package/dist/lib/spinner.js
CHANGED
|
@@ -33,7 +33,7 @@ export const startSpinner = ({ text }) => {
|
|
|
33
33
|
/**
|
|
34
34
|
* Stops the spinner with the following text
|
|
35
35
|
*/
|
|
36
|
-
export const stopSpinner = ({ error, spinner, text }) => {
|
|
36
|
+
export const stopSpinner = ({ error, spinner, text, }) => {
|
|
37
37
|
if (!spinner) {
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
@@ -73,7 +73,7 @@ export const parseContextFile = (contents) => {
|
|
|
73
73
|
const result = {
|
|
74
74
|
contents,
|
|
75
75
|
};
|
|
76
|
-
const providerContext =
|
|
76
|
+
const providerContext = PROVIDER_CONTEXT_REGEX.exec(contents);
|
|
77
77
|
if (providerContext) {
|
|
78
78
|
const [, attributes, innerContents] = providerContext;
|
|
79
79
|
result.innerContents = innerContents;
|
|
@@ -90,7 +90,7 @@ export const parseContextFile = (contents) => {
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
|
-
const contextOverrides =
|
|
93
|
+
const contextOverrides = PROVIDER_CONTEXT_OVERRIDES_REGEX.exec(contents);
|
|
94
94
|
if (contextOverrides) {
|
|
95
95
|
const [overrideContents, , innerContents] = contextOverrides;
|
|
96
96
|
result.overrides = {
|