netlify-cli 27.12.0 → 27.13.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 (41) hide show
  1. package/dist/commands/agents/agents-stop.js +2 -2
  2. package/dist/commands/base-command.js +7 -3
  3. package/dist/commands/deploy/deploy.js +51 -94
  4. package/dist/commands/sites/sites-create.js +3 -2
  5. package/dist/lib/spinner.d.ts +2 -2
  6. package/dist/lib/spinner.js +1 -1
  7. package/dist/utils/deploy/deploy-site.d.ts +32 -12
  8. package/dist/utils/deploy/deploy-site.js +18 -40
  9. package/dist/utils/deploy/drop-api.d.ts +7 -1
  10. package/dist/utils/deploy/drop-api.js +3 -4
  11. package/dist/utils/deploy/file.d.ts +4 -2
  12. package/dist/utils/deploy/hash-config.d.ts +5 -6
  13. package/dist/utils/deploy/hash-config.js +1 -4
  14. package/dist/utils/deploy/hash-files.d.ts +9 -8
  15. package/dist/utils/deploy/hash-fns.d.ts +45 -10
  16. package/dist/utils/deploy/hasher-segments.d.ts +10 -6
  17. package/dist/utils/deploy/hasher-segments.js +0 -1
  18. package/dist/utils/deploy/process-files.d.ts +1 -1
  19. package/dist/utils/deploy/process-files.js +1 -0
  20. package/dist/utils/deploy/types.d.ts +17 -0
  21. package/dist/utils/deploy/types.js +2 -0
  22. package/dist/utils/deploy/upload-files.d.ts +8 -3
  23. package/dist/utils/deploy/upload-files.js +4 -3
  24. package/dist/utils/deploy/upload-source-zip.js +4 -7
  25. package/dist/utils/deploy/util.d.ts +6 -3
  26. package/dist/utils/deploy/util.js +4 -16
  27. package/dist/utils/team.d.ts +1 -0
  28. package/dist/utils/team.js +10 -5
  29. package/dist/utils/telemetry/report-error.js +2 -3
  30. package/dist/utils/telemetry/telemetry.js +9 -5
  31. package/dist/utils/telemetry/utils.d.ts +1 -0
  32. package/dist/utils/telemetry/utils.js +4 -0
  33. package/functions-templates/javascript/scheduled-function/package.json +1 -1
  34. package/functions-templates/typescript/database/package-lock.json +8 -8
  35. package/functions-templates/typescript/database/package.json +1 -1
  36. package/functions-templates/typescript/database-drizzle/package-lock.json +8 -8
  37. package/functions-templates/typescript/database-drizzle/package.json +1 -1
  38. package/functions-templates/typescript/hello-world/package-lock.json +15 -17
  39. package/functions-templates/typescript/hello-world/package.json +1 -1
  40. package/functions-templates/typescript/scheduled-function/package.json +1 -1
  41. package/package.json +4 -4
@@ -38,8 +38,8 @@ export const agentsStop = async (id, options, command) => {
38
38
  }
39
39
  // Stop the agent task
40
40
  const stopSpinnerInstance = startSpinner({ text: 'Stopping agent task...' });
41
- const response = await fetch(`${apiOpts.scheme ?? 'https'}://${apiOpts.host ?? api.host}/api/v1/agent_runners/${id}`, {
42
- method: 'DELETE',
41
+ const response = await fetch(`${apiOpts.scheme ?? 'https'}://${apiOpts.host ?? api.host}/api/v1/agent_runners/${id}/stop`, {
42
+ method: 'POST',
43
43
  headers: {
44
44
  Authorization: `Bearer ${api.accessToken ?? ''}`,
45
45
  'User-Agent': apiOpts.userAgent,
@@ -234,6 +234,8 @@ export default class BaseCommand extends Command {
234
234
  const getCommands = (command) => {
235
235
  const parentCommand = this.name() === 'netlify' ? command : command.parent;
236
236
  return (parentCommand?.commands
237
+ // subcommands nested with `.command()` rather than named `parent:child`, e.g. `database connect`
238
+ .concat(this.name() === 'netlify' ? [] : command.commands)
237
239
  .filter((cmd) => {
238
240
  if (cmd._hidden)
239
241
  return false;
@@ -242,11 +244,13 @@ export default class BaseCommand extends Command {
242
244
  // don't include subcommands on the main page
243
245
  return !cmd.name().includes(':');
244
246
  }
245
- return cmd.name().startsWith(`${command.name()}:`);
247
+ return cmd.name().startsWith(`${command.name()}:`) || cmd.parent === command;
246
248
  })
247
249
  .sort((a, b) => a.name().localeCompare(b.name())) || []);
248
250
  };
249
- help.longestSubcommandTermLength = (command) => getCommands(command).reduce((max, cmd) => Math.max(max, cmd.name().length), 0);
251
+ /** nested subcommands are shown by their full path, e.g. `database migrations apply` */
252
+ const getCommandTerm = (cmd) => cmd.parent && cmd.parent.name() !== 'netlify' ? `${getCommandTerm(cmd.parent)} ${cmd.name()}` : cmd.name();
253
+ help.longestSubcommandTermLength = (command) => getCommands(command).reduce((max, cmd) => Math.max(max, getCommandTerm(cmd).length), 0);
250
254
  /** override the longestOptionTermLength to react on hide options flag */
251
255
  help.longestOptionTermLength = (command, helper) => (!command.noBaseOptions &&
252
256
  helper.visibleOptions(command).reduce((max, option) => Math.max(max, helper.optionTerm(option).length), 0)) ||
@@ -313,7 +317,7 @@ export default class BaseCommand extends Command {
313
317
  '',
314
318
  ];
315
319
  }
316
- const commandList = getCommands(command).map((cmd) => formatItem(cmd.name(), helper.subcommandDescription(cmd).split('\n')[0], true));
320
+ const commandList = getCommands(command).map((cmd) => formatItem(getCommandTerm(cmd), helper.subcommandDescription(cmd).split('\n')[0], true));
317
321
  if (commandList.length !== 0) {
318
322
  output = [...output, chalk.bold('COMMANDS'), formatHelpList(commandList), ''];
319
323
  }
@@ -72,7 +72,7 @@ const getDeployFolder = async ({ command, config, options, site, siteData, }) =>
72
72
  ? resolve(command.jsWorkspaceRoot || site.root, options.dir)
73
73
  : resolve(command.workingDir, options.dir);
74
74
  }
75
- else if (config?.build?.publish) {
75
+ else if (config.build.publish) {
76
76
  deployFolder = resolve(site.root, config.build.publish);
77
77
  }
78
78
  else if (siteData?.build_settings?.dir) {
@@ -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,11 +251,8 @@ const generateDeployCommand = (options, availableTeams, command) => {
258
251
  }
259
252
  return parts.join(' ');
260
253
  };
261
- // @ts-expect-error TS(7031) FIXME: Binding element 'api' implicitly has an 'any' type... Remove this comment to see the full error message
262
- const prepareProductionDeploy = async ({ api, siteData, options, command }) => {
263
- if (typeof siteData.published_deploy === 'object' &&
264
- siteData.published_deploy !== null &&
265
- siteData.published_deploy.locked) {
254
+ const prepareProductionDeploy = async ({ api, siteData, options, command, }) => {
255
+ if (siteData.published_deploy?.locked) {
266
256
  log(`\n${NETLIFYDEVERR} Deployments are "locked" for production context of this project\n`);
267
257
  const overrideCommand = generateDeployCommand({ ...options, prodIfUnlocked: true, prod: false }, [], command);
268
258
  if (!isInteractive()) {
@@ -282,6 +272,19 @@ const prepareProductionDeploy = async ({ api, siteData, options, command }) => {
282
272
  log(`\n${NETLIFYDEVLOG} "Auto publishing" has been enabled for production context\n`);
283
273
  }
284
274
  };
275
+ const createDeploy = async ({ alias, api, command, deployToProduction, options, siteData, siteId, }) => {
276
+ if (deployToProduction) {
277
+ await prepareProductionDeploy({ siteData, api, options, command });
278
+ }
279
+ const draft = options.draft || (!deployToProduction && !alias);
280
+ const createDeployBody = {
281
+ draft,
282
+ branch: alias,
283
+ include_upload_url: options.uploadSourceZip,
284
+ ...getDeploySourceFields(),
285
+ };
286
+ return (await api.createSiteDeploy({ siteId, title: options.message, body: createDeployBody }));
287
+ };
285
288
  const hasErrorMessage = (actual, expected) => {
286
289
  if (typeof actual === 'string') {
287
290
  return actual.includes(expected);
@@ -337,6 +340,7 @@ const deployProgressCb = function () {
337
340
  return;
338
341
  case 'stop':
339
342
  default: {
343
+ // 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
344
  spinnersByType[event.type].success(event.msg);
341
345
  delete spinnersByType[event.type];
342
346
  }
@@ -357,7 +361,7 @@ const uploadDeployBlobs = async ({ cachedConfig, deployId, options, packagePath,
357
361
  // We log our own progress so we don't want this as well. Plus, this logs much of the same
358
362
  // information as the build that (likely) came before this as part of the deploy build.
359
363
  quiet: options.debug ?? true,
360
- // @ts-expect-error(serhalp) -- Untyped in `@netlify/build`
364
+ // @ts-expect-error FIXME(@netlify/build): the `cachedConfig` flag is typed `Record<string, unknown>`, rejecting `CachedConfig`
361
365
  cachedConfig,
362
366
  packagePath,
363
367
  deployId,
@@ -378,37 +382,7 @@ const uploadDeployBlobs = async ({ cachedConfig, deployId, options, packagePath,
378
382
  phase: 'stop',
379
383
  });
380
384
  };
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, }) => {
385
+ const runDeploy = async ({ alias, api, command, config, deployFolder, deployTimeout, deployToProduction, functionsConfig, functionsFolder, options, packagePath, silent, site, siteData, siteId, skipFunctionsCache, deployId: existingDeployId, }) => {
412
386
  let results;
413
387
  let deployId = existingDeployId;
414
388
  let uploadSourceZipResult;
@@ -416,17 +390,15 @@ title, deployId: existingDeployId, }) => {
416
390
  // We won't have a deploy ID if we run the command with `--no-build`.
417
391
  // In this case, we must create the deploy.
418
392
  if (!deployId) {
419
- if (deployToProduction) {
420
- await prepareProductionDeploy({ siteData, api, options, command });
421
- }
422
- const draft = options.draft || (!deployToProduction && !alias);
423
- const createDeployBody = {
424
- draft,
425
- branch: alias,
426
- include_upload_url: options.uploadSourceZip,
427
- ...getDeploySourceFields(),
428
- };
429
- const createDeployResponse = await api.createSiteDeploy({ siteId, title, body: createDeployBody });
393
+ const createDeployResponse = await createDeploy({
394
+ alias,
395
+ api,
396
+ command,
397
+ deployToProduction,
398
+ options,
399
+ siteData,
400
+ siteId,
401
+ });
430
402
  deployId = createDeployResponse.id;
431
403
  if (options.uploadSourceZip &&
432
404
  createDeployResponse.source_zip_upload_url &&
@@ -449,17 +421,21 @@ title, deployId: existingDeployId, }) => {
449
421
  command.netlify.frameworksAPIPaths.functions.path,
450
422
  functionsFolder,
451
423
  ].filter((folder) => Boolean(folder));
452
- const manifestPath = skipFunctionsCache ? null : await getFunctionsManifestPath({ base: site.root, packagePath });
424
+ const manifestPath = skipFunctionsCache
425
+ ? undefined
426
+ : await getFunctionsManifestPath({ base: site.root, packagePath });
453
427
  const serverManifestPath = await getServerManifestPath({ base: site.root, packagePath });
454
428
  const redirectsPath = `${deployFolder}/_redirects`;
455
429
  const headersPath = `${deployFolder}/_headers`;
456
430
  const { redirects } = await parseAllRedirects({
431
+ // @ts-expect-error FIXME(@netlify/redirect-parser): `configRedirects` is typed `string[]` but takes redirect objects
457
432
  configRedirects: config.redirects,
458
433
  redirectsFiles: [redirectsPath],
459
434
  minimal: true,
460
435
  });
461
436
  config.redirects = redirects;
462
437
  const { headers } = await parseAllHeaders({
438
+ // @ts-expect-error FIXME(@netlify/headers-parser): `MinimalHeader` rejects the array values `NetlifyConfig['headers']` allows
463
439
  configHeaders: config.headers,
464
440
  headersFiles: [headersPath],
465
441
  minimal: true,
@@ -474,7 +450,6 @@ title, deployId: existingDeployId, }) => {
474
450
  packagePath: command.workspacePackage,
475
451
  });
476
452
  results = await deploySite(command, api, siteId, deployFolder, {
477
- // @ts-expect-error FIXME
478
453
  config,
479
454
  fnDir: functionDirectories,
480
455
  functionsConfig,
@@ -485,9 +460,9 @@ title, deployId: existingDeployId, }) => {
485
460
  deployId,
486
461
  filter: getDeployFilesFilter({ site, deployFolder }),
487
462
  workingDir: command.workingDir,
488
- manifestPath,
463
+ manifestPath: manifestPath ?? undefined,
489
464
  packagePath,
490
- serverEnabled: Boolean(siteData?.feature_flags?.netlify_build_server_standalone),
465
+ serverEnabled: Boolean(siteData.feature_flags?.netlify_build_server_standalone),
491
466
  serverManifestPath: serverManifestPath ?? undefined,
492
467
  skipFunctionsCache,
493
468
  siteRoot: site.root,
@@ -581,10 +556,8 @@ const bundleEdgeFunctions = async (options, command) => {
581
556
  // We log our own progress so we don't want this as well. Plus, this logs much of the same
582
557
  // information as the build that (likely) came before this as part of the deploy build.
583
558
  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
559
  edgeFunctionsBootstrapURL: await getBootstrapURL(),
586
- // @ts-expect-error 'CachedConfig' is not assignable to type 'Record<string, unknown>'.
587
- // Index signature for type 'string' is missing in type 'CachedConfig'.
560
+ // @ts-expect-error FIXME(@netlify/build): `cachedConfig` is typed `Record<string, unknown>` and `edgeFunctionsBootstrapURL` is missing from the flags
588
561
  cachedConfig: command.netlify.cachedConfig,
589
562
  });
590
563
  if (!success) {
@@ -722,7 +695,6 @@ const prepAndRunDeploy = async ({ api, command, config, deployToProduction, opti
722
695
  siteEnv,
723
696
  });
724
697
  const results = await runDeploy({
725
- // @ts-expect-error FIXME
726
698
  alias,
727
699
  api,
728
700
  command,
@@ -735,12 +707,11 @@ const prepAndRunDeploy = async ({ api, command, config, deployToProduction, opti
735
707
  functionsFolder: functionsFolderStat && functionsFolder,
736
708
  options,
737
709
  packagePath: command.workspacePackage,
738
- silent: options.json || options.silent,
710
+ silent: options.json || Boolean(options.silent),
739
711
  site,
740
712
  siteData,
741
713
  siteId,
742
714
  skipFunctionsCache: options.skipFunctionsCache,
743
- title: options.message,
744
715
  deployId,
745
716
  });
746
717
  return results;
@@ -768,10 +739,11 @@ const createSiteWithFlags = async (options, command, site) => {
768
739
  throw new Error('Team must be specified to create a site');
769
740
  }
770
741
  try {
771
- const siteData = await api.createSiteInTeam({
742
+ // FIXME(@netlify/api): the `site` response type makes every field optional, unlike `SiteInfo`
743
+ const siteData = (await api.createSiteInTeam({
772
744
  accountSlug: options.team,
773
745
  body,
774
- });
746
+ }));
775
747
  site.id = siteData.id;
776
748
  return siteData;
777
749
  }
@@ -781,10 +753,11 @@ const createSiteWithFlags = async (options, command, site) => {
781
753
  const suffixedName = `${siteName.trim()}-${suffix}`;
782
754
  log(`Site name "${siteName}" is taken. Retrying with "${suffixedName}"...`);
783
755
  try {
784
- const siteData = await api.createSiteInTeam({
756
+ // FIXME(@netlify/api): the `site` response type makes every field optional, unlike `SiteInfo`
757
+ const siteData = (await api.createSiteInTeam({
785
758
  accountSlug: options.team,
786
759
  body: { name: suffixedName },
787
- });
760
+ }));
788
761
  site.id = siteData.id;
789
762
  return siteData;
790
763
  }
@@ -861,7 +834,7 @@ const anonymousDeploy = async (options, command) => {
861
834
  }
862
835
  };
863
836
  const checkForFunctions = async () => {
864
- const functionsFolder = getFunctionsFolder({ config, options, site, siteData: {}, workingDir });
837
+ const functionsFolder = getFunctionsFolder({ config, options, site, workingDir });
865
838
  const internalFunctionsDir = await getInternalFunctionsDir({ base: site.root });
866
839
  const frameworksFunctionsDir = command.netlify.frameworksAPIPaths.functions.path;
867
840
  const hasFunctions = (await dirHasFiles(functionsFolder)) ||
@@ -891,17 +864,16 @@ const anonymousDeploy = async (options, command) => {
891
864
  }
892
865
  const deployFolder = await getDeployFolder({
893
866
  command,
894
- config: command.netlify.config,
867
+ config,
895
868
  options,
896
869
  site,
897
- siteData: {},
898
870
  });
899
871
  await validateDeployFolder(deployFolder);
900
872
  const edgeFunctionsDistPath = await getEdgeFunctionsDistPathIfExists(workingDir);
901
873
  const filter = getDeployFilesFilter({ site, deployFolder });
902
874
  const { files, filesShaMap } = await hashFiles({
903
875
  concurrentHash: DEFAULT_CONCURRENT_HASH,
904
- directories: [deployFolder, edgeFunctionsDistPath].filter(Boolean),
876
+ directories: [deployFolder, edgeFunctionsDistPath].filter((dir) => Boolean(dir)),
905
877
  filter,
906
878
  normalizer: deployFileNormalizer.bind(null, workingDir),
907
879
  statusCb: options.json ? () => { } : deployProgressCb(),
@@ -941,7 +913,7 @@ const anonymousDeploy = async (options, command) => {
941
913
  }
942
914
  const deploy = await waitForDropDeploy(dropApiOptions, deployInfo.id, deployInfo.deploy_id, options.timeout ? options.timeout * 1000 : DEFAULT_DEPLOY_TIMEOUT);
943
915
  site.id = deployInfo.id;
944
- const siteUrl = (deploy.ssl_url || deploy.url || `https://${deployInfo.subdomain}.netlify.app`);
916
+ const siteUrl = deploy.ssl_url || deploy.url || `https://${deployInfo.subdomain}.netlify.app`;
945
917
  const isPasswordProtected = !options.createdVia || options.createdVia === 'drop';
946
918
  const claimUrl = `https://app.netlify.com/drop/${deployInfo.subdomain}#drop_token=${dropToken}`;
947
919
  if (options.json) {
@@ -1013,23 +985,8 @@ export const deploy = async (options, command) => {
1013
985
  const deployToProduction = !options.draft && (options.prod || (options.prodIfUnlocked && !(siteData.published_deploy?.locked ?? false)));
1014
986
  let results = {};
1015
987
  if (options.build) {
1016
- if (deployToProduction) {
1017
- await prepareProductionDeploy({ siteData, api, options, command });
1018
- }
1019
- const draft = options.draft || (!deployToProduction && !alias);
1020
- const createDeployBody = {
1021
- draft,
1022
- branch: alias,
1023
- include_upload_url: options.uploadSourceZip,
1024
- ...getDeploySourceFields(),
1025
- };
1026
- // TODO: Type this properly in `@netlify/api`.
1027
- const deployMetadata = (await api.createSiteDeploy({
1028
- siteId,
1029
- title: options.message,
1030
- body: createDeployBody,
1031
- }));
1032
- const deployId = deployMetadata.id || '';
988
+ const deployMetadata = await createDeploy({ alias, api, command, deployToProduction, options, siteData, siteId });
989
+ const deployId = deployMetadata.id;
1033
990
  const skewProtectionToken = deployMetadata.skew_protection_token;
1034
991
  let sourceZipFileName;
1035
992
  if (options.uploadSourceZip &&
@@ -1058,7 +1015,7 @@ export const deploy = async (options, command) => {
1058
1015
  options,
1059
1016
  workingDir,
1060
1017
  api,
1061
- site,
1018
+ site: site,
1062
1019
  config: netlifyConfig,
1063
1020
  siteData,
1064
1021
  siteId,
@@ -1087,7 +1044,7 @@ export const deploy = async (options, command) => {
1087
1044
  options,
1088
1045
  workingDir,
1089
1046
  api,
1090
- site,
1047
+ site: site,
1091
1048
  config: command.netlify.config,
1092
1049
  siteData,
1093
1050
  siteId,
@@ -5,7 +5,7 @@ import { chalk, logAndThrowError, log, logJson, warn } from '../../utils/command
5
5
  import getRepoData from '../../utils/get-repo-data.js';
6
6
  import { configureRepo } from '../../utils/init/config.js';
7
7
  import { isInteractive } from '../../utils/scripted-commands.js';
8
- import { resolveTeamForNonInteractive } from '../../utils/team.js';
8
+ import { requireTeams, resolveTeamForNonInteractive } from '../../utils/team.js';
9
9
  import { track } from '../../utils/telemetry/index.js';
10
10
  import { MAX_SITE_NAME_LENGTH } from '../../utils/validation.js';
11
11
  import { link } from '../link/link.js';
@@ -29,9 +29,10 @@ export const sitesCreate = async (options, command) => {
29
29
  log(`Using team: ${team.name}`);
30
30
  }
31
31
  else {
32
+ const teams = requireTeams(accounts);
32
33
  accountSlug = await select({
33
34
  message: 'Team:',
34
- choices: accounts.map((account) => ({
35
+ choices: teams.map((account) => ({
35
36
  value: account.slug,
36
37
  name: account.name,
37
38
  })),
@@ -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
  /**
@@ -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
  }
@@ -1,26 +1,46 @@
1
- import BaseCommand from '../../commands/base-command.js';
2
- import { type $TSFixMe } from '../../commands/types.js';
3
- import type { DeployEvent } from './status-cb.js';
1
+ import type { NetlifyAPI } from '@netlify/api';
2
+ import type { Config as FunctionsConfig } from '@netlify/zip-it-and-ship-it';
3
+ import type BaseCommand from '../../commands/base-command.js';
4
+ import type { File } from './file.js';
5
+ import type { DeployEvent, StatusCallback } from './status-cb.js';
6
+ import type { DeployConfig } from './types.js';
4
7
  import type { DeployEnvironmentVariable } from '../env/deploy-env-vars.js';
5
8
  export type { DeployEvent };
6
- export declare const deploySite: (command: BaseCommand, api: $TSFixMe, siteId: any, dir: any, { assetType, branch, concurrentHash, concurrentUpload, config, deployId, deployTimeout, draft, environment, filter, fnDir, functionsConfig, hashAlgorithm, manifestPath, maxRetry, packagePath, serverEnabled, serverManifestPath, siteRoot, skipFunctionsCache, statusCb, syncFileLimit, tmpDir, workingDir, }: {
9
+ export interface DeploySiteOptions {
10
+ assetType?: 'file' | undefined;
11
+ branch?: string;
7
12
  concurrentHash?: number;
8
13
  concurrentUpload?: number;
14
+ /** The site configuration, uploaded as the deploy's `netlify.toml` */
15
+ config: DeployConfig;
16
+ deployId: string;
9
17
  deployTimeout?: number;
10
18
  draft?: boolean;
11
19
  environment?: DeployEnvironmentVariable[];
20
+ filter: (filename: string) => boolean;
21
+ fnDir?: string[];
22
+ functionsConfig?: FunctionsConfig | undefined;
23
+ hashAlgorithm?: string;
24
+ manifestPath?: string | undefined;
12
25
  maxRetry?: number;
13
- packagePath?: string;
26
+ packagePath?: string | undefined;
14
27
  serverEnabled?: boolean;
15
- serverManifestPath?: string;
16
- statusCb?: (status: DeployEvent) => void;
28
+ serverManifestPath?: string | undefined;
29
+ siteRoot?: string | undefined;
30
+ skipFunctionsCache?: boolean | undefined;
31
+ statusCb?: StatusCallback;
17
32
  syncFileLimit?: number;
18
33
  tmpDir?: string;
19
- fnDir?: string[];
20
34
  workingDir: string;
21
- }) => Promise<{
22
- deployId: any;
23
- deploy: any;
24
- uploadList: any[];
35
+ }
36
+ export declare const deploySite: (command: BaseCommand, api: NetlifyAPI, siteId: string, dir: string, { assetType, branch, concurrentHash, concurrentUpload, config, deployId, deployTimeout, draft, environment, filter, fnDir, functionsConfig, hashAlgorithm, manifestPath, maxRetry, packagePath, serverEnabled, serverManifestPath, siteRoot, skipFunctionsCache, statusCb, syncFileLimit, tmpDir, workingDir, }: DeploySiteOptions) => Promise<{
37
+ deployId: string;
38
+ deploy: import("./util.js").ReadyDeploy;
39
+ uploadList: (import("./upload-files.js").EdgeFunctionUploadFile | import("./upload-files.js").ServerUploadFile | File | (import("./upload-files.js").FunctionUploadFile & {
40
+ hash: string;
41
+ relname: string;
42
+ }) | (import("./upload-files.js").InlineUploadFile & {
43
+ hash: string;
44
+ }))[];
25
45
  }>;
26
46
  //# sourceMappingURL=deploy-site.d.ts.map
@@ -18,31 +18,7 @@ const buildStatsString = (possibleParts) => {
18
18
  }
19
19
  return `${parts.slice(0, -1).join(', ')} and ${parts[parts.length - 1]}`;
20
20
  };
21
- export const deploySite = async (command, api,
22
- // @ts-expect-error TS(7006) FIXME: Parameter 'siteId' implicitly has an 'any' type.
23
- siteId,
24
- // @ts-expect-error TS(7006) FIXME: Parameter 'dir' implicitly has an 'any' type.
25
- dir, {
26
- // @ts-expect-error TS(2525) FIXME: Initializer provides no value for this binding ele... Remove this comment to see the full error message
27
- assetType,
28
- // @ts-expect-error TS(2525) FIXME: Initializer provides no value for this binding ele... Remove this comment to see the full error message
29
- branch, concurrentHash = DEFAULT_CONCURRENT_HASH, concurrentUpload = DEFAULT_CONCURRENT_UPLOAD,
30
- // @ts-expect-error TS(2525) FIXME: Initializer provides no value for this binding ele... Remove this comment to see the full error message
31
- config,
32
- // @ts-expect-error TS(2525) FIXME: Initializer provides no value for this binding ele... Remove this comment to see the full error message
33
- deployId, deployTimeout = DEFAULT_DEPLOY_TIMEOUT, draft = false, environment,
34
- // @ts-expect-error TS(2525) FIXME: Initializer provides no value for this binding ele... Remove this comment to see the full error message
35
- filter, fnDir = [],
36
- // @ts-expect-error TS(2525) FIXME: Initializer provides no value for this binding ele... Remove this comment to see the full error message
37
- functionsConfig,
38
- // @ts-expect-error TS(2525) FIXME: Initializer provides no value for this binding ele... Remove this comment to see the full error message
39
- hashAlgorithm,
40
- // @ts-expect-error TS(2525) FIXME: Initializer provides no value for this binding ele... Remove this comment to see the full error message
41
- manifestPath, maxRetry = DEFAULT_MAX_RETRY, packagePath, serverEnabled, serverManifestPath,
42
- // @ts-expect-error TS(2525) FIXME: Initializer provides no value for this binding ele... Remove this comment to see the full error message
43
- siteRoot,
44
- // @ts-expect-error TS(2525) FIXME: Initializer provides no value for this binding ele... Remove this comment to see the full error message
45
- skipFunctionsCache, statusCb = () => {
21
+ export const deploySite = async (command, api, siteId, dir, { assetType, branch, concurrentHash = DEFAULT_CONCURRENT_HASH, concurrentUpload = DEFAULT_CONCURRENT_UPLOAD, config, deployId, deployTimeout = DEFAULT_DEPLOY_TIMEOUT, draft = false, environment, filter, fnDir = [], functionsConfig, hashAlgorithm, manifestPath, maxRetry = DEFAULT_MAX_RETRY, packagePath, serverEnabled, serverManifestPath, siteRoot, skipFunctionsCache, statusCb = () => {
46
22
  /* default to noop */
47
23
  }, syncFileLimit = DEFAULT_SYNC_LIMIT, tmpDir = temporaryDirectory(), workingDir, }) => {
48
24
  statusCb({
@@ -57,7 +33,7 @@ skipFunctionsCache, statusCb = () => {
57
33
  hashFiles({
58
34
  assetType,
59
35
  concurrentHash,
60
- directories: [dir, edgeFunctionsDistPath, deployConfigPath, dbMigrationsDistPath].filter(Boolean),
36
+ directories: [dir, edgeFunctionsDistPath, deployConfigPath, dbMigrationsDistPath].filter((d) => Boolean(d)),
61
37
  filter,
62
38
  hashAlgorithm,
63
39
  normalizer: deployFileNormalizer.bind(null, workingDir),
@@ -99,9 +75,7 @@ skipFunctionsCache, statusCb = () => {
99
75
  throw new Error('Nothing to deploy');
100
76
  }
101
77
  if (functionsWithNativeModules.length !== 0) {
102
- const functionsWithNativeModulesMessage = functionsWithNativeModules
103
- .map(({ name }) => `- ${name}`)
104
- .join('\n');
78
+ const functionsWithNativeModulesMessage = functionsWithNativeModules.map(({ name }) => `- ${name}`).join('\n');
105
79
  warn(`Modules with native dependencies\n
106
80
  ${functionsWithNativeModulesMessage}
107
81
 
@@ -120,8 +94,7 @@ For more information, visit https://ntl.fyi/cli-native-modules.`);
120
94
  });
121
95
  const packageFrameworks = command.project.frameworks.get(command.workspacePackage ?? '');
122
96
  const primaryFramework = packageFrameworks?.[0];
123
- // @ts-expect-error TS(2349) This expression is not callable
124
- const cleanedParams = cleanDeep({
97
+ const params = {
125
98
  siteId,
126
99
  deploy_id: deployId,
127
100
  body: {
@@ -138,21 +111,27 @@ For more information, visit https://ntl.fyi/cli-native-modules.`);
138
111
  framework_version: primaryFramework?.detected.package?.version?.toString() ?? 'unknown',
139
112
  build_version: getNetlifyBuildVersion(),
140
113
  },
141
- });
114
+ };
115
+ const cleanedParams =
116
+ // @ts-expect-error FIXME(clean-deep): typings declare an ES `export default` but the package is CommonJS
117
+ cleanDeep(params);
142
118
  // cleanDeep deeply strips keys with empty strings, but empty strings are valid environment
143
119
  // variable values--a user can use an empty string to e.g. unset a variable only for a deploy.
144
120
  // This would result in payloads with a missing `value` key, which the API would reject.
145
121
  const deployParams = environment?.length
146
122
  ? { ...cleanedParams, body: { ...cleanedParams.body, environment } }
147
123
  : cleanedParams;
148
- let deploy = await api.updateSiteDeploy(deployParams);
124
+ // FIXME(@netlify/api): `id` and `required` are optional on the updated deploy
125
+ let deploy = (await api.updateSiteDeploy(
126
+ // @ts-expect-error FIXME(@netlify/api): `functions_config` rejects zip-it-and-ship-it's `BuildData`, route `methods` and `traffic_rules` strings
127
+ deployParams));
149
128
  if (deployParams.body.async)
150
- deploy = await waitForDiff(api, deploy.id, siteId, deployTimeout);
129
+ deploy = (await waitForDiff(api, deploy.id, siteId, deployTimeout));
151
130
  const { required: requiredFiles, required_functions: requiredFns, required_edge_functions: requiredEdgeFns, required_server: requiredServer, } = deploy;
152
131
  const newStats = buildStatsString([
153
132
  requiredFiles.length > 0 && pluralize(requiredFiles.length, 'file'),
154
- (requiredFns?.length ?? 0) > 0 && pluralize(requiredFns.length, 'function'),
155
- (requiredEdgeFns?.length ?? 0) > 0 && pluralize(requiredEdgeFns.length, 'edge function'),
133
+ requiredFns != null && requiredFns.length > 0 && pluralize(requiredFns.length, 'function'),
134
+ requiredEdgeFns != null && requiredEdgeFns.length > 0 && pluralize(requiredEdgeFns.length, 'edge function'),
156
135
  (requiredServer?.length ?? 0) > 0 && 'a server',
157
136
  ]);
158
137
  statusCb({
@@ -171,18 +150,17 @@ For more information, visit https://ntl.fyi/cli-native-modules.`);
171
150
  msg: 'Waiting for deploy to go live...',
172
151
  phase: 'start',
173
152
  });
174
- deploy = await waitForDeploy(api, deployId, siteId, deployTimeout);
153
+ const readyDeploy = await waitForDeploy(api, deployId, siteId, deployTimeout);
175
154
  statusCb({
176
155
  type: 'wait-for-deploy',
177
156
  msg: draft ? 'Draft deploy is live!' : 'Deploy is live!',
178
157
  phase: 'stop',
179
158
  });
180
159
  await rm(tmpDir, { force: true, recursive: true });
181
- const deployManifest = {
160
+ return {
182
161
  deployId,
183
- deploy,
162
+ deploy: readyDeploy,
184
163
  uploadList,
185
164
  };
186
- return deployManifest;
187
165
  };
188
166
  //# sourceMappingURL=deploy-site.js.map
@@ -18,7 +18,13 @@ export interface DropApiError extends Error {
18
18
  export declare const getDropToken: ({ apiBase, userAgent }: DropApiOptions) => Promise<string>;
19
19
  export declare const createDropDeploy: ({ apiBase, userAgent }: DropApiOptions, files: Record<string, string>, token: string, createdVia?: string) => Promise<DropDeployInfo>;
20
20
  export declare const uploadDropFile: ({ apiBase, userAgent }: DropApiOptions, deployId: string, filePath: string, body: fs.ReadStream | Buffer, token: string) => Promise<void>;
21
- export declare const waitForDropDeploy: ({ apiBase, userAgent }: DropApiOptions, siteId: string, deployId: string, timeout?: number) => Promise<Record<string, unknown>>;
21
+ interface DropSiteDeploy {
22
+ state?: string;
23
+ error_message?: string;
24
+ ssl_url?: string;
25
+ url?: string;
26
+ }
27
+ export declare const waitForDropDeploy: ({ apiBase, userAgent }: DropApiOptions, siteId: string, deployId: string, timeout?: number) => Promise<DropSiteDeploy>;
22
28
  export declare const claimDropSite: ({ apiBase, userAgent }: DropApiOptions, siteId: string, dropToken: string, authToken: string) => Promise<void>;
23
29
  export interface UploadListItem {
24
30
  normalizedPath: string;
@@ -44,8 +44,6 @@ export const createDropDeploy = async ({ apiBase, userAgent }, files, token, cre
44
44
  };
45
45
  // TODO: Migrate to @netlify/api when Drop endpoints are in the OpenAPI spec.
46
46
  export const uploadDropFile = async ({ apiBase, userAgent }, deployId, filePath, body, token) => {
47
- // Node.js fetch needs `duplex: 'half'` for streaming bodies which isn't in standard RequestInit
48
- /* eslint-disable @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any */
49
47
  const normalizedFilePath = filePath.startsWith('/') ? filePath : `/${filePath}`;
50
48
  const response = await fetch(`${apiBase}/deploys/${deployId}/files${encodeURI(normalizedFilePath)}`, {
51
49
  method: 'PUT',
@@ -53,10 +51,10 @@ export const uploadDropFile = async ({ apiBase, userAgent }, deployId, filePath,
53
51
  'Content-Type': 'application/octet-stream',
54
52
  Authorization: `Bearer ${token}`,
55
53
  }),
56
- body: body,
54
+ body,
55
+ // Node.js fetch requires this for streaming bodies
57
56
  duplex: 'half',
58
57
  });
59
- /* eslint-enable @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any */
60
58
  if (!response.ok) {
61
59
  const error = new Error(`Failed to upload file ${filePath}: ${String(response.status)} ${response.statusText}`);
62
60
  error.status = response.status;
@@ -79,6 +77,7 @@ export const waitForDropDeploy = async ({ apiBase, userAgent }, siteId, deployId
79
77
  return true;
80
78
  }
81
79
  if (data.state === 'error') {
80
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- an empty error message falls back to the generic one
82
81
  throw new Error(data.error_message || `Deploy ${deployId} had an error`);
83
82
  }
84
83
  return false;