netlify-cli 27.12.1 → 27.14.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.
- package/dist/commands/agents/agents-stop.js +2 -2
- package/dist/commands/base-command.js +7 -3
- package/dist/commands/deploy/deploy.js +29 -44
- package/dist/commands/sites/sites-create.js +3 -2
- package/dist/utils/deploy/deploy-site.d.ts +1 -2
- package/dist/utils/deploy/deploy-site.js +2 -2
- package/dist/utils/deploy/drop-api.js +25 -39
- package/dist/utils/deploy/hash-config.d.ts +1 -1
- package/dist/utils/deploy/hash-config.js +0 -2
- package/dist/utils/deploy/hash-files.d.ts +2 -3
- package/dist/utils/deploy/hash-files.js +2 -4
- package/dist/utils/deploy/hash-fns.d.ts +41 -36
- package/dist/utils/deploy/hash-fns.js +29 -25
- package/dist/utils/deploy/hasher-segments.d.ts +1 -2
- package/dist/utils/deploy/hasher-segments.js +2 -2
- package/dist/utils/deploy/types.d.ts +1 -0
- package/dist/utils/deploy/upload-files.d.ts +1 -1
- package/dist/utils/deploy/upload-files.js +11 -9
- package/dist/utils/deploy/util.d.ts +4 -0
- package/dist/utils/deploy/util.js +25 -62
- package/dist/utils/init/agent-skills.js +3 -2
- package/dist/utils/team.d.ts +1 -0
- package/dist/utils/team.js +10 -5
- package/dist/utils/telemetry/report-error.js +2 -3
- package/dist/utils/telemetry/telemetry.js +9 -5
- package/dist/utils/telemetry/utils.d.ts +1 -0
- package/dist/utils/telemetry/utils.js +4 -0
- package/package.json +9 -9
|
@@ -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: '
|
|
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
|
-
|
|
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
|
|
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
|
|
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) {
|
|
@@ -172,9 +172,6 @@ const getDeployFilesFilter = ({ deployFolder, site }) => {
|
|
|
172
172
|
// when site.root !== deployFolder the behaviour matches our buildbot
|
|
173
173
|
const skipNodeModules = site.root === deployFolder;
|
|
174
174
|
return (filename) => {
|
|
175
|
-
if (filename == null) {
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
175
|
if (filename === deployFolder) {
|
|
179
176
|
return true;
|
|
180
177
|
}
|
|
@@ -252,10 +249,7 @@ const generateDeployCommand = (options, availableTeams, command) => {
|
|
|
252
249
|
return parts.join(' ');
|
|
253
250
|
};
|
|
254
251
|
const prepareProductionDeploy = async ({ api, siteData, options, command, }) => {
|
|
255
|
-
if (
|
|
256
|
-
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain -- FIXME: a non-object `published_deploy` must not count as locked
|
|
257
|
-
siteData.published_deploy !== null &&
|
|
258
|
-
siteData.published_deploy.locked) {
|
|
252
|
+
if (siteData.published_deploy?.locked) {
|
|
259
253
|
log(`\n${NETLIFYDEVERR} Deployments are "locked" for production context of this project\n`);
|
|
260
254
|
const overrideCommand = generateDeployCommand({ ...options, prodIfUnlocked: true, prod: false }, [], command);
|
|
261
255
|
if (!isInteractive()) {
|
|
@@ -275,6 +269,19 @@ const prepareProductionDeploy = async ({ api, siteData, options, command, }) =>
|
|
|
275
269
|
log(`\n${NETLIFYDEVLOG} "Auto publishing" has been enabled for production context\n`);
|
|
276
270
|
}
|
|
277
271
|
};
|
|
272
|
+
const createDeploy = async ({ alias, api, command, deployToProduction, options, siteData, siteId, }) => {
|
|
273
|
+
if (deployToProduction) {
|
|
274
|
+
await prepareProductionDeploy({ siteData, api, options, command });
|
|
275
|
+
}
|
|
276
|
+
const draft = options.draft || (!deployToProduction && !alias);
|
|
277
|
+
const createDeployBody = {
|
|
278
|
+
draft,
|
|
279
|
+
branch: alias,
|
|
280
|
+
include_upload_url: options.uploadSourceZip,
|
|
281
|
+
...getDeploySourceFields(),
|
|
282
|
+
};
|
|
283
|
+
return (await api.createSiteDeploy({ siteId, title: options.message, body: createDeployBody }));
|
|
284
|
+
};
|
|
278
285
|
const hasErrorMessage = (actual, expected) => {
|
|
279
286
|
if (typeof actual === 'string') {
|
|
280
287
|
return actual.includes(expected);
|
|
@@ -372,7 +379,7 @@ const uploadDeployBlobs = async ({ cachedConfig, deployId, options, packagePath,
|
|
|
372
379
|
phase: 'stop',
|
|
373
380
|
});
|
|
374
381
|
};
|
|
375
|
-
const runDeploy = async ({ alias, api, command, config, deployFolder, deployTimeout, deployToProduction, functionsConfig, functionsFolder, options, packagePath, silent, site, siteData, siteId, skipFunctionsCache,
|
|
382
|
+
const runDeploy = async ({ alias, api, command, config, deployFolder, deployTimeout, deployToProduction, functionsConfig, functionsFolder, options, packagePath, silent, site, siteData, siteId, skipFunctionsCache, deployId: existingDeployId, }) => {
|
|
376
383
|
let results;
|
|
377
384
|
let deployId = existingDeployId;
|
|
378
385
|
let uploadSourceZipResult;
|
|
@@ -380,21 +387,15 @@ const runDeploy = async ({ alias, api, command, config, deployFolder, deployTime
|
|
|
380
387
|
// We won't have a deploy ID if we run the command with `--no-build`.
|
|
381
388
|
// In this case, we must create the deploy.
|
|
382
389
|
if (!deployId) {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
include_upload_url: options.uploadSourceZip,
|
|
391
|
-
...getDeploySourceFields(),
|
|
392
|
-
};
|
|
393
|
-
const createDeployResponse = (await api.createSiteDeploy({
|
|
390
|
+
const createDeployResponse = await createDeploy({
|
|
391
|
+
alias,
|
|
392
|
+
api,
|
|
393
|
+
command,
|
|
394
|
+
deployToProduction,
|
|
395
|
+
options,
|
|
396
|
+
siteData,
|
|
394
397
|
siteId,
|
|
395
|
-
|
|
396
|
-
body: createDeployBody,
|
|
397
|
-
}));
|
|
398
|
+
});
|
|
398
399
|
deployId = createDeployResponse.id;
|
|
399
400
|
if (options.uploadSourceZip &&
|
|
400
401
|
createDeployResponse.source_zip_upload_url &&
|
|
@@ -458,7 +459,7 @@ const runDeploy = async ({ alias, api, command, config, deployFolder, deployTime
|
|
|
458
459
|
workingDir: command.workingDir,
|
|
459
460
|
manifestPath: manifestPath ?? undefined,
|
|
460
461
|
packagePath,
|
|
461
|
-
serverEnabled: Boolean(siteData
|
|
462
|
+
serverEnabled: Boolean(siteData.feature_flags?.netlify_build_server_standalone),
|
|
462
463
|
serverManifestPath: serverManifestPath ?? undefined,
|
|
463
464
|
skipFunctionsCache,
|
|
464
465
|
siteRoot: site.root,
|
|
@@ -708,7 +709,6 @@ const prepAndRunDeploy = async ({ api, command, config, deployToProduction, opti
|
|
|
708
709
|
siteData,
|
|
709
710
|
siteId,
|
|
710
711
|
skipFunctionsCache: options.skipFunctionsCache,
|
|
711
|
-
title: options.message,
|
|
712
712
|
deployId,
|
|
713
713
|
});
|
|
714
714
|
return results;
|
|
@@ -831,7 +831,7 @@ const anonymousDeploy = async (options, command) => {
|
|
|
831
831
|
}
|
|
832
832
|
};
|
|
833
833
|
const checkForFunctions = async () => {
|
|
834
|
-
const functionsFolder = getFunctionsFolder({ config, options, site,
|
|
834
|
+
const functionsFolder = getFunctionsFolder({ config, options, site, workingDir });
|
|
835
835
|
const internalFunctionsDir = await getInternalFunctionsDir({ base: site.root });
|
|
836
836
|
const frameworksFunctionsDir = command.netlify.frameworksAPIPaths.functions.path;
|
|
837
837
|
const hasFunctions = (await dirHasFiles(functionsFolder)) ||
|
|
@@ -861,10 +861,9 @@ const anonymousDeploy = async (options, command) => {
|
|
|
861
861
|
}
|
|
862
862
|
const deployFolder = await getDeployFolder({
|
|
863
863
|
command,
|
|
864
|
-
config
|
|
864
|
+
config,
|
|
865
865
|
options,
|
|
866
866
|
site,
|
|
867
|
-
siteData: {},
|
|
868
867
|
});
|
|
869
868
|
await validateDeployFolder(deployFolder);
|
|
870
869
|
const edgeFunctionsDistPath = await getEdgeFunctionsDistPathIfExists(workingDir);
|
|
@@ -983,22 +982,8 @@ export const deploy = async (options, command) => {
|
|
|
983
982
|
const deployToProduction = !options.draft && (options.prod || (options.prodIfUnlocked && !(siteData.published_deploy?.locked ?? false)));
|
|
984
983
|
let results = {};
|
|
985
984
|
if (options.build) {
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
}
|
|
989
|
-
const draft = options.draft || (!deployToProduction && !alias);
|
|
990
|
-
const createDeployBody = {
|
|
991
|
-
draft,
|
|
992
|
-
branch: alias,
|
|
993
|
-
include_upload_url: options.uploadSourceZip,
|
|
994
|
-
...getDeploySourceFields(),
|
|
995
|
-
};
|
|
996
|
-
const deployMetadata = (await api.createSiteDeploy({
|
|
997
|
-
siteId,
|
|
998
|
-
title: options.message,
|
|
999
|
-
body: createDeployBody,
|
|
1000
|
-
}));
|
|
1001
|
-
const deployId = deployMetadata.id || '';
|
|
985
|
+
const deployMetadata = await createDeploy({ alias, api, command, deployToProduction, options, siteData, siteId });
|
|
986
|
+
const deployId = deployMetadata.id;
|
|
1002
987
|
const skewProtectionToken = deployMetadata.skew_protection_token;
|
|
1003
988
|
let sourceZipFileName;
|
|
1004
989
|
if (options.uploadSourceZip &&
|
|
@@ -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:
|
|
35
|
+
choices: teams.map((account) => ({
|
|
35
36
|
value: account.slug,
|
|
36
37
|
name: account.name,
|
|
37
38
|
})),
|
|
@@ -7,7 +7,6 @@ import type { DeployConfig } from './types.js';
|
|
|
7
7
|
import type { DeployEnvironmentVariable } from '../env/deploy-env-vars.js';
|
|
8
8
|
export type { DeployEvent };
|
|
9
9
|
export interface DeploySiteOptions {
|
|
10
|
-
assetType?: 'file' | undefined;
|
|
11
10
|
branch?: string;
|
|
12
11
|
concurrentHash?: number;
|
|
13
12
|
concurrentUpload?: number;
|
|
@@ -33,7 +32,7 @@ export interface DeploySiteOptions {
|
|
|
33
32
|
tmpDir?: string;
|
|
34
33
|
workingDir: string;
|
|
35
34
|
}
|
|
36
|
-
export declare const deploySite: (command: BaseCommand, api: NetlifyAPI, siteId: string, dir: string, {
|
|
35
|
+
export declare const deploySite: (command: BaseCommand, api: NetlifyAPI, siteId: string, dir: string, { 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
36
|
deployId: string;
|
|
38
37
|
deploy: import("./util.js").ReadyDeploy;
|
|
39
38
|
uploadList: (import("./upload-files.js").EdgeFunctionUploadFile | import("./upload-files.js").ServerUploadFile | File | (import("./upload-files.js").FunctionUploadFile & {
|
|
@@ -18,7 +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, siteId, dir, {
|
|
21
|
+
export const deploySite = async (command, api, siteId, dir, { 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 = () => {
|
|
22
22
|
/* default to noop */
|
|
23
23
|
}, syncFileLimit = DEFAULT_SYNC_LIMIT, tmpDir = temporaryDirectory(), workingDir, }) => {
|
|
24
24
|
statusCb({
|
|
@@ -31,7 +31,6 @@ export const deploySite = async (command, api, siteId, dir, { assetType, branch,
|
|
|
31
31
|
const dbMigrationsDistPath = await getDbMigrationsDistPathIfExists(workingDir);
|
|
32
32
|
const [{ files: staticFiles, filesShaMap: staticShaMap }, { fnConfig, fnShaMap, functionSchedules, functions, functionsWithNativeModules, server, serverShaMap }, configFile, { edgeFunctions, edgeFnShaMap },] = await Promise.all([
|
|
33
33
|
hashFiles({
|
|
34
|
-
assetType,
|
|
35
34
|
concurrentHash,
|
|
36
35
|
directories: [dir, edgeFunctionsDistPath, deployConfigPath, dbMigrationsDistPath].filter((d) => Boolean(d)),
|
|
37
36
|
filter,
|
|
@@ -47,6 +46,7 @@ export const deploySite = async (command, api, siteId, dir, { assetType, branch,
|
|
|
47
46
|
statusCb,
|
|
48
47
|
manifestPath,
|
|
49
48
|
packagePath,
|
|
49
|
+
serverConfig: config.server,
|
|
50
50
|
serverEnabled,
|
|
51
51
|
serverManifestPath,
|
|
52
52
|
skipFunctionsCache,
|
|
@@ -62,37 +62,29 @@ export const uploadDropFile = async ({ apiBase, userAgent }, deployId, filePath,
|
|
|
62
62
|
}
|
|
63
63
|
};
|
|
64
64
|
// TODO: Migrate to @netlify/api when Drop endpoints are in the OpenAPI spec.
|
|
65
|
-
export const waitForDropDeploy = async ({ apiBase, userAgent }, siteId, deployId, timeout = DEFAULT_DEPLOY_TIMEOUT) => {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const response = await fetch(`${apiBase}/sites/${siteId}/deploys/${deployId}`, {
|
|
69
|
-
headers: makeHeaders(userAgent),
|
|
70
|
-
});
|
|
71
|
-
if (!response.ok) {
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
const data = (await response.json());
|
|
75
|
-
if (data.state === 'ready') {
|
|
76
|
-
deploy = data;
|
|
77
|
-
return true;
|
|
78
|
-
}
|
|
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
|
|
81
|
-
throw new Error(data.error_message || `Deploy ${deployId} had an error`);
|
|
82
|
-
}
|
|
83
|
-
return false;
|
|
84
|
-
};
|
|
85
|
-
await pWaitFor(checkDeploy, {
|
|
86
|
-
interval: DEPLOY_POLL,
|
|
87
|
-
timeout: {
|
|
88
|
-
milliseconds: timeout,
|
|
89
|
-
message: 'Timeout while waiting for deploy',
|
|
90
|
-
},
|
|
65
|
+
export const waitForDropDeploy = async ({ apiBase, userAgent }, siteId, deployId, timeout = DEFAULT_DEPLOY_TIMEOUT) => await pWaitFor(async () => {
|
|
66
|
+
const response = await fetch(`${apiBase}/sites/${siteId}/deploys/${deployId}`, {
|
|
67
|
+
headers: makeHeaders(userAgent),
|
|
91
68
|
});
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
const data = (await response.json());
|
|
73
|
+
if (data.state === 'ready') {
|
|
74
|
+
return pWaitFor.resolveWith(data);
|
|
75
|
+
}
|
|
76
|
+
if (data.state === 'error') {
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- an empty error message falls back to the generic one
|
|
78
|
+
throw new Error(data.error_message || `Deploy ${deployId} had an error`);
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}, {
|
|
82
|
+
interval: DEPLOY_POLL,
|
|
83
|
+
timeout: {
|
|
84
|
+
milliseconds: timeout,
|
|
85
|
+
message: 'Timeout while waiting for deploy',
|
|
86
|
+
},
|
|
87
|
+
});
|
|
96
88
|
// TODO: Migrate to @netlify/api when Drop endpoints are in the OpenAPI spec.
|
|
97
89
|
export const claimDropSite = async ({ apiBase, userAgent }, siteId, dropToken, authToken) => {
|
|
98
90
|
const response = await fetch(`${apiBase}/drop/claim`, {
|
|
@@ -123,7 +115,6 @@ export const uploadDropFiles = async (apiOptions, deployId, uploadList, token, {
|
|
|
123
115
|
msg: `(${String(index + 1)}/${String(uploadList.length)}) Uploading ${fileObj.normalizedPath}...`,
|
|
124
116
|
phase: 'progress',
|
|
125
117
|
});
|
|
126
|
-
let lastError;
|
|
127
118
|
for (let attempt = 0; attempt <= maxRetry; attempt++) {
|
|
128
119
|
try {
|
|
129
120
|
const body = fs.createReadStream(fileObj.filepath);
|
|
@@ -131,18 +122,13 @@ export const uploadDropFiles = async (apiOptions, deployId, uploadList, token, {
|
|
|
131
122
|
return;
|
|
132
123
|
}
|
|
133
124
|
catch (error) {
|
|
134
|
-
|
|
135
|
-
if (
|
|
125
|
+
const isPermanentFailure = error instanceof Error && 'status' in error && (error.status === 400 || error.status === 422);
|
|
126
|
+
if (isPermanentFailure || attempt + 1 > maxRetry) {
|
|
136
127
|
throw error;
|
|
137
128
|
}
|
|
138
|
-
|
|
139
|
-
await new Promise((resolve) => setTimeout(resolve, 1000 * (attempt + 1)));
|
|
140
|
-
}
|
|
129
|
+
await new Promise((resolve) => setTimeout(resolve, 1000 * (attempt + 1)));
|
|
141
130
|
}
|
|
142
131
|
}
|
|
143
|
-
if (lastError) {
|
|
144
|
-
throw lastError;
|
|
145
|
-
}
|
|
146
132
|
};
|
|
147
133
|
await pMap(uploadList, uploadFile, { concurrency: concurrentUpload });
|
|
148
134
|
statusCb({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { DeployConfig } from './types.js';
|
|
2
2
|
import type { InlineUploadFile } from './upload-files.js';
|
|
3
3
|
export declare const hashConfig: ({ config }: {
|
|
4
|
-
config: DeployConfig
|
|
4
|
+
config: DeployConfig;
|
|
5
5
|
}) => InlineUploadFile & {
|
|
6
6
|
hash: string;
|
|
7
7
|
};
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import tomlify from 'tomlify-j0.4';
|
|
3
3
|
export const hashConfig = ({ config }) => {
|
|
4
|
-
if (!config)
|
|
5
|
-
throw new Error('Missing config option');
|
|
6
4
|
const configString = serializeToml(config);
|
|
7
5
|
const hash = createHash('sha1').update(configString).digest('hex');
|
|
8
6
|
return {
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import type { File } from './file.js';
|
|
2
2
|
import type { StatusCallback } from './status-cb.js';
|
|
3
|
-
declare const hashFiles: ({
|
|
4
|
-
assetType?: 'file' | undefined;
|
|
3
|
+
declare const hashFiles: ({ concurrentHash, directories, filter, hashAlgorithm, normalizer, statusCb, }: {
|
|
5
4
|
concurrentHash: number;
|
|
6
5
|
directories: string[];
|
|
7
|
-
filter: (
|
|
6
|
+
filter: (filename: string) => boolean;
|
|
8
7
|
hashAlgorithm?: string | undefined;
|
|
9
8
|
normalizer?: (file: File) => File;
|
|
10
9
|
statusCb: StatusCallback;
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { pipeline } from 'stream/promises';
|
|
2
2
|
import walker from 'folder-walker';
|
|
3
3
|
import { fileFilterCtor, fileNormalizerCtor, hasherCtor, manifestCollectorCtor } from './hasher-segments.js';
|
|
4
|
-
const hashFiles = async ({
|
|
5
|
-
if (!filter)
|
|
6
|
-
throw new Error('Missing filter function option');
|
|
4
|
+
const hashFiles = async ({ concurrentHash, directories, filter, hashAlgorithm = 'sha1', normalizer, statusCb, }) => {
|
|
7
5
|
const fileStream = walker(directories, { filter });
|
|
8
6
|
const fileFilter = fileFilterCtor();
|
|
9
7
|
const hasher = hasherCtor({ concurrentHash, hashAlgorithm });
|
|
10
|
-
const fileNormalizer = fileNormalizerCtor({
|
|
8
|
+
const fileNormalizer = fileNormalizerCtor({ normalizer });
|
|
11
9
|
// Written to by manifestCollector
|
|
12
10
|
// normalizedPath: hash (wanted by deploy API)
|
|
13
11
|
const files = {};
|
|
@@ -1,53 +1,62 @@
|
|
|
1
|
-
import { type Config as FunctionsConfig, type FunctionResult, type
|
|
1
|
+
import { type Config as FunctionsConfig, type FunctionResult, type NetlifyServerConfig, type ServerResult } from '@netlify/zip-it-and-ship-it';
|
|
2
2
|
import type BaseCommand from '../../commands/base-command.js';
|
|
3
3
|
import type { StatusCallback } from './status-cb.js';
|
|
4
4
|
import type { FunctionUploadFile, ServerUploadFile } from './upload-files.js';
|
|
5
|
+
interface ServerPayload {
|
|
6
|
+
sha: string;
|
|
7
|
+
region?: string;
|
|
8
|
+
instance_size?: ServerResult['instanceSize'];
|
|
9
|
+
routes?: ServerResult['routes'];
|
|
10
|
+
excluded_routes?: ServerResult['excludedRoutes'];
|
|
11
|
+
runtime_version?: ServerResult['runtimeVersion'];
|
|
12
|
+
}
|
|
5
13
|
type HashedFunctionFile = FunctionUploadFile & {
|
|
6
14
|
hash: string;
|
|
7
15
|
relname: string;
|
|
8
16
|
};
|
|
9
|
-
declare const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
declare const toFunctionConfigPayload: (func: FunctionResult) => {
|
|
18
|
+
display_name: string | undefined;
|
|
19
|
+
excluded_routes: import("@netlify/zip-it-and-ship-it").Route[] | undefined;
|
|
20
|
+
generator: string | undefined;
|
|
21
|
+
memory: number | undefined;
|
|
22
|
+
region: string | undefined;
|
|
23
|
+
routes: import("@netlify/zip-it-and-ship-it").ExtendedRoute[] | undefined;
|
|
24
|
+
build_data: import("@netlify/zip-it-and-ship-it/dist/utils/format_result.js").BuildData | undefined;
|
|
25
|
+
priority: number | undefined;
|
|
26
|
+
traffic_rules: {
|
|
27
|
+
action: {
|
|
28
|
+
type: string;
|
|
29
|
+
config: {
|
|
30
|
+
rate_limit_config: {
|
|
31
|
+
algorithm: string;
|
|
32
|
+
window_size: number;
|
|
33
|
+
window_limit: number;
|
|
34
|
+
};
|
|
35
|
+
aggregate: {
|
|
36
|
+
keys: {
|
|
37
|
+
type: string;
|
|
38
|
+
}[];
|
|
39
|
+
};
|
|
40
|
+
to: string | undefined;
|
|
22
41
|
};
|
|
23
|
-
to: string | undefined;
|
|
24
42
|
};
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
generator: FunctionResult['generator'];
|
|
31
|
-
memory: FunctionResult['memory'];
|
|
32
|
-
region: FunctionResult['region'];
|
|
33
|
-
routes: FunctionResult['routes'];
|
|
34
|
-
build_data: FunctionResult['buildData'];
|
|
35
|
-
priority: FunctionResult['priority'];
|
|
36
|
-
traffic_rules: ReturnType<typeof trafficRulesConfig>;
|
|
37
|
-
vcpu: FunctionResult['vcpu'];
|
|
38
|
-
}
|
|
39
|
-
declare const hashFns: (command: BaseCommand, directories: string[], { concurrentHash, functionsConfig, hashAlgorithm, manifestPath, packagePath, rootDir, serverEnabled, serverManifestPath, skipFunctionsCache, statusCb, tmpDir, }: {
|
|
43
|
+
} | undefined;
|
|
44
|
+
vcpu: number | undefined;
|
|
45
|
+
};
|
|
46
|
+
type FunctionConfigPayload = ReturnType<typeof toFunctionConfigPayload>;
|
|
47
|
+
declare const hashFns: (command: BaseCommand, directories: string[], { concurrentHash, functionsConfig, hashAlgorithm, manifestPath, packagePath, rootDir, serverConfig, serverEnabled, serverManifestPath, skipFunctionsCache, statusCb, tmpDir, }: {
|
|
40
48
|
concurrentHash?: number;
|
|
41
49
|
functionsConfig?: FunctionsConfig | undefined;
|
|
42
50
|
hashAlgorithm?: string | undefined;
|
|
43
51
|
manifestPath?: string | undefined;
|
|
44
52
|
packagePath?: string | undefined;
|
|
45
53
|
rootDir?: string | undefined;
|
|
54
|
+
serverConfig?: NetlifyServerConfig | undefined;
|
|
46
55
|
serverEnabled?: boolean | undefined;
|
|
47
56
|
serverManifestPath?: string | undefined;
|
|
48
57
|
skipFunctionsCache?: boolean | undefined;
|
|
49
58
|
statusCb: StatusCallback;
|
|
50
|
-
tmpDir
|
|
59
|
+
tmpDir: string;
|
|
51
60
|
}) => Promise<{
|
|
52
61
|
functionSchedules?: {
|
|
53
62
|
name: string;
|
|
@@ -55,13 +64,9 @@ declare const hashFns: (command: BaseCommand, directories: string[], { concurren
|
|
|
55
64
|
}[] | undefined;
|
|
56
65
|
functions: Record<string, string>;
|
|
57
66
|
functionsWithNativeModules: FunctionResult[];
|
|
58
|
-
shaMap?: Record<string, never> | undefined;
|
|
59
67
|
fnShaMap?: Record<string, HashedFunctionFile[]> | undefined;
|
|
60
68
|
fnConfig?: Record<string, FunctionConfigPayload> | undefined;
|
|
61
|
-
server?:
|
|
62
|
-
sha: string;
|
|
63
|
-
region?: string;
|
|
64
|
-
} | undefined;
|
|
69
|
+
server?: ServerPayload | undefined;
|
|
65
70
|
serverShaMap?: Record<string, ServerUploadFile[]> | undefined;
|
|
66
71
|
}>;
|
|
67
72
|
export default hashFns;
|
|
@@ -52,7 +52,7 @@ const getFunctionZips = async ({ command, directories, functionsConfig, manifest
|
|
|
52
52
|
config: functionsConfig,
|
|
53
53
|
});
|
|
54
54
|
};
|
|
55
|
-
const getServerBundle = async ({ packagePath, rootDir, serverEnabled, serverManifestPath, tmpDir, }) => {
|
|
55
|
+
const getServerBundle = async ({ packagePath, rootDir, serverConfig, serverEnabled, serverManifestPath, tmpDir, }) => {
|
|
56
56
|
if (!serverEnabled) {
|
|
57
57
|
return undefined;
|
|
58
58
|
}
|
|
@@ -72,7 +72,7 @@ const getServerBundle = async ({ packagePath, rootDir, serverEnabled, serverMani
|
|
|
72
72
|
if (entryPath === undefined) {
|
|
73
73
|
return undefined;
|
|
74
74
|
}
|
|
75
|
-
return await zipServer(entryPath, join(tmpDir, 'server'), { basePath: rootDir });
|
|
75
|
+
return await zipServer(entryPath, join(tmpDir, 'server'), { basePath: rootDir, config: serverConfig });
|
|
76
76
|
};
|
|
77
77
|
const trafficRulesConfig = (trafficRules) => {
|
|
78
78
|
if (!trafficRules) {
|
|
@@ -93,14 +93,23 @@ const trafficRulesConfig = (trafficRules) => {
|
|
|
93
93
|
},
|
|
94
94
|
};
|
|
95
95
|
};
|
|
96
|
-
const
|
|
96
|
+
const toFunctionConfigPayload = (func) => ({
|
|
97
|
+
display_name: func.displayName,
|
|
98
|
+
excluded_routes: func.excludedRoutes,
|
|
99
|
+
generator: func.generator,
|
|
100
|
+
memory: func.memory,
|
|
101
|
+
region: func.region,
|
|
102
|
+
routes: func.routes,
|
|
103
|
+
build_data: func.buildData,
|
|
104
|
+
priority: func.priority,
|
|
105
|
+
traffic_rules: trafficRulesConfig(func.trafficRules),
|
|
106
|
+
vcpu: func.vcpu,
|
|
107
|
+
});
|
|
108
|
+
const hashFns = async (command, directories, { concurrentHash, functionsConfig, hashAlgorithm = 'sha256', manifestPath, packagePath, rootDir, serverConfig, serverEnabled, serverManifestPath, skipFunctionsCache, statusCb, tmpDir, }) => {
|
|
97
109
|
// Exit early if there is nothing to bundle. A site can have a server without
|
|
98
110
|
// any functions directory.
|
|
99
111
|
if (directories.length === 0 && !serverEnabled) {
|
|
100
|
-
return { functions: {}, functionsWithNativeModules: []
|
|
101
|
-
}
|
|
102
|
-
if (!tmpDir) {
|
|
103
|
-
throw new Error('Missing tmpDir directory for zipping files');
|
|
112
|
+
return { functions: {}, functionsWithNativeModules: [] };
|
|
104
113
|
}
|
|
105
114
|
const [functionZips, serverBundle] = await Promise.all([
|
|
106
115
|
directories.length === 0
|
|
@@ -115,7 +124,7 @@ const hashFns = async (command, directories, { concurrentHash, functionsConfig,
|
|
|
115
124
|
statusCb,
|
|
116
125
|
tmpDir,
|
|
117
126
|
}),
|
|
118
|
-
getServerBundle({ packagePath, rootDir, serverEnabled, serverManifestPath, tmpDir }),
|
|
127
|
+
getServerBundle({ packagePath, rootDir, serverConfig, serverEnabled, serverManifestPath, tmpDir }),
|
|
119
128
|
]);
|
|
120
129
|
// ZISI's in-memory FunctionResult only nests bootstrap/runtime version into
|
|
121
130
|
// buildData when writing the manifest cache. Reconstruct it for direct-zip paths.
|
|
@@ -145,7 +154,7 @@ const hashFns = async (command, directories, { concurrentHash, functionsConfig,
|
|
|
145
154
|
priority,
|
|
146
155
|
trafficRules,
|
|
147
156
|
}));
|
|
148
|
-
const fnConfig = functionZips
|
|
157
|
+
const fnConfig = Object.fromEntries(functionZips
|
|
149
158
|
.filter((func) => Boolean(func.displayName ||
|
|
150
159
|
func.generator ||
|
|
151
160
|
func.routes ||
|
|
@@ -155,21 +164,7 @@ const hashFns = async (command, directories, { concurrentHash, functionsConfig,
|
|
|
155
164
|
func.region ||
|
|
156
165
|
func.memory ||
|
|
157
166
|
func.vcpu))
|
|
158
|
-
.
|
|
159
|
-
...funcs,
|
|
160
|
-
[curr.name]: {
|
|
161
|
-
display_name: curr.displayName,
|
|
162
|
-
excluded_routes: curr.excludedRoutes,
|
|
163
|
-
generator: curr.generator,
|
|
164
|
-
memory: curr.memory,
|
|
165
|
-
region: curr.region,
|
|
166
|
-
routes: curr.routes,
|
|
167
|
-
build_data: curr.buildData,
|
|
168
|
-
priority: curr.priority,
|
|
169
|
-
traffic_rules: trafficRulesConfig(curr.trafficRules),
|
|
170
|
-
vcpu: curr.vcpu,
|
|
171
|
-
},
|
|
172
|
-
}), {});
|
|
167
|
+
.map((func) => [func.name, toFunctionConfigPayload(func)]));
|
|
173
168
|
const functionSchedules = functionZips
|
|
174
169
|
.map(({ name, schedule }) => schedule && { name, cron: schedule })
|
|
175
170
|
.filter((schedule) => schedule !== '' && schedule !== undefined);
|
|
@@ -210,7 +205,16 @@ const hashServer = async (serverBundle, { concurrentHash, hashAlgorithm, statusC
|
|
|
210
205
|
hasherCtor({ concurrentHash, hashAlgorithm }),
|
|
211
206
|
manifestCollectorCtor(servers, serverShaMap, { statusCb }),
|
|
212
207
|
]);
|
|
213
|
-
|
|
208
|
+
const { excludedRoutes, instanceSize, region, routes, runtimeVersion } = serverBundle;
|
|
209
|
+
const server = {
|
|
210
|
+
sha: Object.values(servers)[0],
|
|
211
|
+
...(region === undefined ? {} : { region }),
|
|
212
|
+
...(instanceSize === undefined ? {} : { instance_size: instanceSize }),
|
|
213
|
+
...(routes === undefined ? {} : { routes }),
|
|
214
|
+
...(excludedRoutes === undefined ? {} : { excluded_routes: excludedRoutes }),
|
|
215
|
+
...(runtimeVersion === undefined ? {} : { runtime_version: runtimeVersion }),
|
|
216
|
+
};
|
|
217
|
+
return { server, serverShaMap };
|
|
214
218
|
};
|
|
215
219
|
export default hashFns;
|
|
216
220
|
//# sourceMappingURL=hash-fns.js.map
|
|
@@ -5,8 +5,7 @@ export declare const hasherCtor: ({ concurrentHash, hashAlgorithm }: {
|
|
|
5
5
|
concurrentHash?: number;
|
|
6
6
|
hashAlgorithm: string;
|
|
7
7
|
}) => Transform;
|
|
8
|
-
export declare const fileNormalizerCtor: ({
|
|
9
|
-
assetType: 'file';
|
|
8
|
+
export declare const fileNormalizerCtor: ({ normalizer: normalizeFunction }: {
|
|
10
9
|
normalizer?: (file: File) => File;
|
|
11
10
|
}) => Transform;
|
|
12
11
|
export declare const manifestCollectorCtor: <T extends {
|
|
@@ -28,11 +28,11 @@ export const hasherCtor = ({ concurrentHash, hashAlgorithm }) => {
|
|
|
28
28
|
});
|
|
29
29
|
};
|
|
30
30
|
// Inject normalized file names into normalizedPath and assetType
|
|
31
|
-
export const fileNormalizerCtor = ({
|
|
31
|
+
export const fileNormalizerCtor = ({ normalizer: normalizeFunction }) => {
|
|
32
32
|
return new Transform({
|
|
33
33
|
objectMode: true,
|
|
34
34
|
transform(fileObj, _, callback) {
|
|
35
|
-
const normalizedFile = { ...fileObj, assetType, normalizedPath: normalizePath(fileObj.relname) };
|
|
35
|
+
const normalizedFile = { ...fileObj, assetType: 'file', normalizedPath: normalizePath(fileObj.relname) };
|
|
36
36
|
const result = normalizeFunction !== undefined ? normalizeFunction(normalizedFile) : normalizedFile;
|
|
37
37
|
this.push(result);
|
|
38
38
|
callback();
|
|
@@ -4,10 +4,10 @@ export type UploadApi = Pick<NetlifyAPI, 'uploadDeployFile' | 'uploadDeployFunct
|
|
|
4
4
|
interface UploadFileBase {
|
|
5
5
|
filepath: string;
|
|
6
6
|
normalizedPath: string;
|
|
7
|
-
body?: undefined;
|
|
8
7
|
}
|
|
9
8
|
export interface StaticUploadFile extends UploadFileBase {
|
|
10
9
|
assetType: 'file';
|
|
10
|
+
body?: undefined;
|
|
11
11
|
}
|
|
12
12
|
export interface InlineUploadFile {
|
|
13
13
|
assetType: 'file';
|
|
@@ -22,9 +22,7 @@ const uploadFiles = async (api, deployId, uploadList, { concurrentUpload, maxRet
|
|
|
22
22
|
phase: 'start',
|
|
23
23
|
});
|
|
24
24
|
const uploadFile = async (fileObj, index) => {
|
|
25
|
-
const {
|
|
26
|
-
// FIXME(@netlify/api): octet-stream `body` is typed as `ReadStream` only, but an inline string body works too
|
|
27
|
-
const readStreamCtor = () => (body ?? fs.createReadStream(filepath));
|
|
25
|
+
const { normalizedPath } = fileObj;
|
|
28
26
|
statusCb({
|
|
29
27
|
type: 'upload',
|
|
30
28
|
msg: `(${index}/${uploadList.length}) Uploading ${normalizedPath}...`,
|
|
@@ -32,8 +30,11 @@ const uploadFiles = async (api, deployId, uploadList, { concurrentUpload, maxRet
|
|
|
32
30
|
});
|
|
33
31
|
switch (fileObj.assetType) {
|
|
34
32
|
case 'file': {
|
|
33
|
+
const { body, filepath } = fileObj;
|
|
35
34
|
return await retryUpload(() => api.uploadDeployFile({
|
|
36
|
-
|
|
35
|
+
// FIXME(@netlify/api): `types.d.ts` hardcodes every `application/octet-stream` body as `ReadStream | (() => ReadStream)`,
|
|
36
|
+
// ignoring the spec's `string` type; a string body works at runtime
|
|
37
|
+
body: () => (body ?? fs.createReadStream(filepath)),
|
|
37
38
|
deployId,
|
|
38
39
|
path: encodeURI(normalizedPath),
|
|
39
40
|
}), maxRetry);
|
|
@@ -42,7 +43,7 @@ const uploadFiles = async (api, deployId, uploadList, { concurrentUpload, maxRet
|
|
|
42
43
|
const { invocationMode, runtime, timeout } = fileObj;
|
|
43
44
|
return await retryUpload((retryCount) => {
|
|
44
45
|
const params = {
|
|
45
|
-
body:
|
|
46
|
+
body: () => fs.createReadStream(fileObj.filepath),
|
|
46
47
|
deployId,
|
|
47
48
|
invocationMode,
|
|
48
49
|
timeout,
|
|
@@ -58,7 +59,7 @@ const uploadFiles = async (api, deployId, uploadList, { concurrentUpload, maxRet
|
|
|
58
59
|
case 'edge-function': {
|
|
59
60
|
return await retryUpload((retryCount) => {
|
|
60
61
|
const params = {
|
|
61
|
-
body:
|
|
62
|
+
body: () => fs.createReadStream(fileObj.filepath),
|
|
62
63
|
deployId,
|
|
63
64
|
codeSha: normalizedPath,
|
|
64
65
|
};
|
|
@@ -71,7 +72,7 @@ const uploadFiles = async (api, deployId, uploadList, { concurrentUpload, maxRet
|
|
|
71
72
|
case 'server': {
|
|
72
73
|
return await retryUpload((retryCount) => {
|
|
73
74
|
const params = {
|
|
74
|
-
body:
|
|
75
|
+
body: () => fs.createReadStream(fileObj.filepath),
|
|
75
76
|
deployId,
|
|
76
77
|
codeSha: fileObj.hash,
|
|
77
78
|
};
|
|
@@ -132,8 +133,9 @@ const retryUpload = (uploadFn, maxRetry) => new Promise((resolve, reject) => {
|
|
|
132
133
|
// Do something when backoff starts, e.g. show to the
|
|
133
134
|
// user the delay before next reconnection attempt.
|
|
134
135
|
});
|
|
135
|
-
|
|
136
|
-
|
|
136
|
+
fibonacciBackoff.on('ready', (retryIndex) => {
|
|
137
|
+
void tryUpload(retryIndex);
|
|
138
|
+
});
|
|
137
139
|
fibonacciBackoff.on('fail', () => {
|
|
138
140
|
reject(lastError);
|
|
139
141
|
});
|
|
@@ -2,6 +2,10 @@ import type { NetlifyAPI } from '@netlify/api';
|
|
|
2
2
|
export declare const pluralize: (amount: number, noun: string) => string;
|
|
3
3
|
export type Deploy = Awaited<ReturnType<NetlifyAPI['getSiteDeploy']>>;
|
|
4
4
|
export type ReadyDeploy = Deploy & Required<Pick<Deploy, 'id' | 'site_id' | 'name' | 'admin_url' | 'url' | 'ssl_url' | 'deploy_url' | 'deploy_ssl_url'>>;
|
|
5
|
+
export declare class DeployStateError extends Error {
|
|
6
|
+
readonly deploy: Deploy;
|
|
7
|
+
constructor(message: string, deploy: Deploy);
|
|
8
|
+
}
|
|
5
9
|
export declare const normalizePath: (relname: string) => string;
|
|
6
10
|
export declare const waitForDiff: (api: Pick<NetlifyAPI, 'getSiteDeploy'>, deployId: string, siteId: string, timeout: number) => Promise<Deploy>;
|
|
7
11
|
export declare const waitForDeploy: (api: Pick<NetlifyAPI, 'getSiteDeploy'>, deployId: string, siteId: string, timeout: number) => Promise<ReadyDeploy>;
|
|
@@ -2,6 +2,13 @@ import { sep } from 'path';
|
|
|
2
2
|
import pWaitFor from 'p-wait-for';
|
|
3
3
|
import { DEPLOY_POLL } from './constants.js';
|
|
4
4
|
export const pluralize = (amount, noun) => `${amount} ${noun}${amount === 1 ? '' : 's'}`;
|
|
5
|
+
export class DeployStateError extends Error {
|
|
6
|
+
deploy;
|
|
7
|
+
constructor(message, deploy) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.deploy = deploy;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
5
12
|
// normalize windows paths to unix paths
|
|
6
13
|
export const normalizePath = (relname) => {
|
|
7
14
|
if (relname.includes('#') || relname.includes('?')) {
|
|
@@ -9,70 +16,26 @@ export const normalizePath = (relname) => {
|
|
|
9
16
|
}
|
|
10
17
|
return relname.split(sep).join('/');
|
|
11
18
|
};
|
|
19
|
+
// https://github.com/netlify/bitballoon/blob/master/app/models/deploy.rb#L21-L33
|
|
20
|
+
const DIFFED_STATES = new Set(['prepared', 'uploading', 'uploaded', 'ready']);
|
|
21
|
+
const READY_STATES = new Set(['ready']);
|
|
22
|
+
const pollDeployUntil = async (api, deployId, siteId, timeout, targetStates) => await pWaitFor(async () => {
|
|
23
|
+
const siteDeploy = await api.getSiteDeploy({ siteId, deployId });
|
|
24
|
+
if (siteDeploy.state === 'error') {
|
|
25
|
+
throw new DeployStateError(siteDeploy.error_message || `Deploy ${deployId} had an error`, siteDeploy);
|
|
26
|
+
}
|
|
27
|
+
return siteDeploy.state !== undefined && targetStates.has(siteDeploy.state) && pWaitFor.resolveWith(siteDeploy);
|
|
28
|
+
}, {
|
|
29
|
+
interval: DEPLOY_POLL,
|
|
30
|
+
timeout: {
|
|
31
|
+
milliseconds: timeout,
|
|
32
|
+
message: 'Timeout while waiting for deploy',
|
|
33
|
+
},
|
|
34
|
+
});
|
|
12
35
|
// poll an async deployId until its done diffing
|
|
13
|
-
export const waitForDiff = async (api, deployId, siteId, timeout) =>
|
|
14
|
-
const loadDeploy = async () => {
|
|
15
|
-
const siteDeploy = await api.getSiteDeploy({ siteId, deployId });
|
|
16
|
-
switch (siteDeploy.state) {
|
|
17
|
-
// https://github.com/netlify/bitballoon/blob/master/app/models/deploy.rb#L21-L33
|
|
18
|
-
case 'error': {
|
|
19
|
-
const deployError = new Error(siteDeploy.error_message || `Deploy ${deployId} had an error`);
|
|
20
|
-
deployError.deploy = siteDeploy;
|
|
21
|
-
throw deployError;
|
|
22
|
-
}
|
|
23
|
-
case 'prepared':
|
|
24
|
-
case 'uploading':
|
|
25
|
-
case 'uploaded':
|
|
26
|
-
case 'ready': {
|
|
27
|
-
return pWaitFor.resolveWith(siteDeploy);
|
|
28
|
-
}
|
|
29
|
-
case 'preparing':
|
|
30
|
-
default: {
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
const deploy = await pWaitFor(loadDeploy, {
|
|
36
|
-
interval: DEPLOY_POLL,
|
|
37
|
-
timeout: {
|
|
38
|
-
milliseconds: timeout,
|
|
39
|
-
message: 'Timeout while waiting for deploy',
|
|
40
|
-
},
|
|
41
|
-
});
|
|
42
|
-
return deploy;
|
|
43
|
-
};
|
|
36
|
+
export const waitForDiff = async (api, deployId, siteId, timeout) => await pollDeployUntil(api, deployId, siteId, timeout, DIFFED_STATES);
|
|
44
37
|
// Poll a deployId until its ready
|
|
45
|
-
export const waitForDeploy = async (api, deployId, siteId, timeout) =>
|
|
46
|
-
const loadDeploy = async () => {
|
|
47
|
-
const siteDeploy = await api.getSiteDeploy({ siteId, deployId });
|
|
48
|
-
switch (siteDeploy.state) {
|
|
49
|
-
// https://github.com/netlify/bitballoon/blob/master/app/models/deploy.rb#L21-L33
|
|
50
|
-
case 'error': {
|
|
51
|
-
const deployError = new Error(siteDeploy.error_message || `Deploy ${deployId} had an error`);
|
|
52
|
-
deployError.deploy = siteDeploy;
|
|
53
|
-
throw deployError;
|
|
54
|
-
}
|
|
55
|
-
case 'ready': {
|
|
56
|
-
return pWaitFor.resolveWith(siteDeploy);
|
|
57
|
-
}
|
|
58
|
-
case 'preparing':
|
|
59
|
-
case 'prepared':
|
|
60
|
-
case 'uploaded':
|
|
61
|
-
case 'uploading':
|
|
62
|
-
default: {
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
const deploy = await pWaitFor(loadDeploy, {
|
|
68
|
-
interval: DEPLOY_POLL,
|
|
69
|
-
timeout: {
|
|
70
|
-
milliseconds: timeout,
|
|
71
|
-
message: 'Timeout while waiting for deploy',
|
|
72
|
-
},
|
|
73
|
-
});
|
|
74
|
-
return deploy;
|
|
75
|
-
};
|
|
38
|
+
export const waitForDeploy = async (api, deployId, siteId, timeout) => (await pollDeployUntil(api, deployId, siteId, timeout, READY_STATES));
|
|
76
39
|
// Transform the fileShaMap and fnShaMap into a generic shaMap that file-uploader.js can use
|
|
77
40
|
export const getUploadList = (required, shaMap) => {
|
|
78
41
|
if (!required || !shaMap)
|
|
@@ -199,8 +199,9 @@ const isOlderThan = async (file, ageMs) => {
|
|
|
199
199
|
};
|
|
200
200
|
const isSameEntry = async (a, b) => {
|
|
201
201
|
try {
|
|
202
|
-
|
|
203
|
-
|
|
202
|
+
// As numbers, Windows file IDs lose precision and two directories can share one.
|
|
203
|
+
const [statA, statB] = await Promise.all([fs.lstat(a, { bigint: true }), fs.lstat(b, { bigint: true })]);
|
|
204
|
+
return statA.ino !== 0n && statA.ino === statB.ino && statA.dev === statB.dev;
|
|
204
205
|
}
|
|
205
206
|
catch {
|
|
206
207
|
return false;
|
package/dist/utils/team.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { MinimalAccount } from './types.js';
|
|
2
|
+
export declare const requireTeams: (accounts: MinimalAccount[] | undefined) => MinimalAccount[];
|
|
2
3
|
export declare const resolveTeam: (accounts: MinimalAccount[]) => MinimalAccount | undefined;
|
|
3
4
|
export declare const resolveTeamForNonInteractive: (accounts: MinimalAccount[], commandExample: string) => MinimalAccount;
|
|
4
5
|
//# sourceMappingURL=team.d.ts.map
|
package/dist/utils/team.js
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { logAndThrowError } from './command-helpers.js';
|
|
2
|
+
export const requireTeams = (accounts) => {
|
|
3
|
+
const teams = accounts ?? [];
|
|
4
|
+
if (teams.length === 0) {
|
|
5
|
+
return logAndThrowError('No teams available. Please ensure you have access to at least one team.');
|
|
6
|
+
}
|
|
7
|
+
return teams;
|
|
8
|
+
};
|
|
2
9
|
export const resolveTeam = (accounts) => {
|
|
3
10
|
if (accounts.length === 1) {
|
|
4
11
|
return accounts[0];
|
|
@@ -6,14 +13,12 @@ export const resolveTeam = (accounts) => {
|
|
|
6
13
|
return accounts.find((acc) => acc.default);
|
|
7
14
|
};
|
|
8
15
|
export const resolveTeamForNonInteractive = (accounts, commandExample) => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
const team = resolveTeam(accounts);
|
|
16
|
+
const teams = requireTeams(accounts);
|
|
17
|
+
const team = resolveTeam(teams);
|
|
13
18
|
if (team) {
|
|
14
19
|
return team;
|
|
15
20
|
}
|
|
16
|
-
const availableTeams =
|
|
21
|
+
const availableTeams = teams.map((t) => t.slug).join(', ');
|
|
17
22
|
return logAndThrowError(`Multiple teams available. Please specify which team to use.\n` +
|
|
18
23
|
`Available teams: ${availableTeams}\n\n` +
|
|
19
24
|
`Example: ${commandExample}\n\n` +
|
|
@@ -3,9 +3,8 @@ import { dirname, join } from 'path';
|
|
|
3
3
|
import process, { version as nodejsVersion } from 'process';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { getGlobalConfigStore } from '@netlify/dev-utils';
|
|
6
|
-
import { isCI } from 'ci-info';
|
|
7
6
|
import execa from '../execa.js';
|
|
8
|
-
import { cliVersion } from './utils.js';
|
|
7
|
+
import { cliVersion, isTelemetryDisabledByEnv } from './utils.js';
|
|
9
8
|
const dirPath = dirname(fileURLToPath(import.meta.url));
|
|
10
9
|
let currentCommand;
|
|
11
10
|
export const setCommandForErrorReporting = (command) => {
|
|
@@ -21,7 +20,7 @@ export const setCommandForErrorReporting = (command) => {
|
|
|
21
20
|
*/
|
|
22
21
|
// @ts-expect-error TS(7006) FIXME: Parameter 'error' implicitly has an 'any' type.
|
|
23
22
|
export const reportError = async function (error, config = {}) {
|
|
24
|
-
if (
|
|
23
|
+
if (isTelemetryDisabledByEnv()) {
|
|
25
24
|
return;
|
|
26
25
|
}
|
|
27
26
|
// convert a NotifiableError to an error class
|
|
@@ -2,10 +2,10 @@ import { dirname, join } from 'path';
|
|
|
2
2
|
import process, { version as nodejsVersion } from 'process';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { getGlobalConfigStore } from '@netlify/dev-utils';
|
|
5
|
-
import {
|
|
5
|
+
import { id as ciVendor, isPR } from 'ci-info';
|
|
6
6
|
import { getDrivingAgent } from '../agent-detection.js';
|
|
7
7
|
import execa from '../execa.js';
|
|
8
|
-
import { isTelemetryDisabled, cliVersion } from './utils.js';
|
|
8
|
+
import { isTelemetryDisabled, isTelemetryDisabledByEnv, cliVersion } from './utils.js';
|
|
9
9
|
import isValidEventName from './validation.js';
|
|
10
10
|
const dirPath = dirname(fileURLToPath(import.meta.url));
|
|
11
11
|
function send(type, payload) {
|
|
@@ -49,11 +49,15 @@ const getAgentProperties = () => {
|
|
|
49
49
|
agent_other_value: agent?.otherValue,
|
|
50
50
|
};
|
|
51
51
|
};
|
|
52
|
+
const getCIProperties = () => ({
|
|
53
|
+
ci_vendor: ciVendor ?? undefined,
|
|
54
|
+
ci_is_pr: isPR ?? undefined,
|
|
55
|
+
});
|
|
52
56
|
/**
|
|
53
57
|
* Tracks a custom event with the provided payload
|
|
54
58
|
*/
|
|
55
59
|
export async function track(eventName, payload = {}) {
|
|
56
|
-
if (
|
|
60
|
+
if (isTelemetryDisabledByEnv()) {
|
|
57
61
|
return;
|
|
58
62
|
}
|
|
59
63
|
const globalConfig = await getGlobalConfigStore();
|
|
@@ -78,12 +82,12 @@ export async function track(eventName, payload = {}) {
|
|
|
78
82
|
anonymousId: cliId,
|
|
79
83
|
duration,
|
|
80
84
|
status,
|
|
81
|
-
properties: { ...properties, nodejsVersion, cliVersion, ...getAgentProperties() },
|
|
85
|
+
properties: { ...properties, nodejsVersion, cliVersion, ...getAgentProperties(), ...getCIProperties() },
|
|
82
86
|
};
|
|
83
87
|
return send('track', defaultData);
|
|
84
88
|
}
|
|
85
89
|
export async function identify(payload) {
|
|
86
|
-
if (
|
|
90
|
+
if (isTelemetryDisabledByEnv()) {
|
|
87
91
|
return;
|
|
88
92
|
}
|
|
89
93
|
const globalConfig = await getGlobalConfigStore();
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import process from 'process';
|
|
1
2
|
import getCLIPackageJson from '../get-cli-package-json.js';
|
|
2
3
|
export const { version: cliVersion } = await getCLIPackageJson();
|
|
3
4
|
// @ts-expect-error TS(7006) FIXME: Parameter 'config' implicitly has an 'any' type.
|
|
4
5
|
export const isTelemetryDisabled = function (config) {
|
|
5
6
|
return config.get('telemetryDisabled');
|
|
6
7
|
};
|
|
8
|
+
const isEnabledFlag = (value) => value !== undefined && !['', '0', 'false'].includes(value);
|
|
9
|
+
// An env var opt-out matters in CI, where the global config written by `--telemetry-disable` rarely persists.
|
|
10
|
+
export const isTelemetryDisabledByEnv = () => isEnabledFlag(process.env.NETLIFY_TELEMETRY_DISABLED) || isEnabledFlag(process.env.DO_NOT_TRACK);
|
|
7
11
|
//# sourceMappingURL=utils.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "netlify-cli",
|
|
3
|
-
"version": "27.
|
|
3
|
+
"version": "27.14.0",
|
|
4
4
|
"description": "Netlify command line tool",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"api",
|
|
@@ -55,11 +55,11 @@
|
|
|
55
55
|
"@fastify/static": "^10.0.0",
|
|
56
56
|
"@inquirer/prompts": "^8.7.2",
|
|
57
57
|
"@netlify/ai": "^1.0.1",
|
|
58
|
-
"@netlify/api": "^15.1.
|
|
58
|
+
"@netlify/api": "^15.1.3",
|
|
59
59
|
"@netlify/blobs": "^11.1.3",
|
|
60
|
-
"@netlify/build": "^37.
|
|
60
|
+
"@netlify/build": "^37.5.0",
|
|
61
61
|
"@netlify/build-info": "^11.3.0",
|
|
62
|
-
"@netlify/config": "^25.
|
|
62
|
+
"@netlify/config": "^25.3.0",
|
|
63
63
|
"@netlify/dev": "^5.1.6",
|
|
64
64
|
"@netlify/dev-utils": "^6.0.3",
|
|
65
65
|
"@netlify/edge-bundler": "^16.1.2",
|
|
@@ -71,14 +71,14 @@
|
|
|
71
71
|
"@netlify/redirect-matcher": "^0.4.2",
|
|
72
72
|
"@netlify/redirect-parser": "^16.1.1",
|
|
73
73
|
"@netlify/server-dev": "^0.1.3",
|
|
74
|
-
"@netlify/zip-it-and-ship-it": "^16.
|
|
74
|
+
"@netlify/zip-it-and-ship-it": "^16.4.0",
|
|
75
75
|
"@opentelemetry/api": "~1.9.0",
|
|
76
76
|
"@pnpm/tabtab": "^0.5.4",
|
|
77
77
|
"ansi-escapes": "^7.3.0",
|
|
78
78
|
"ansi-to-html": "^0.7.2",
|
|
79
79
|
"ascii-table": "^0.0.9",
|
|
80
80
|
"backoff": "^2.5.0",
|
|
81
|
-
"boxen": "^
|
|
81
|
+
"boxen": "^9.0.0",
|
|
82
82
|
"chalk": "^6.0.0",
|
|
83
83
|
"chokidar": "^5.0.0",
|
|
84
84
|
"ci-info": "^4.4.0",
|
|
@@ -182,7 +182,7 @@
|
|
|
182
182
|
"@types/write-file-atomic": "^4.0.3",
|
|
183
183
|
"@types/ws": "^8.18.1",
|
|
184
184
|
"@types/yauzl": "^2.10.3",
|
|
185
|
-
"@vitest/coverage-v8": "^
|
|
185
|
+
"@vitest/coverage-v8": "^5.0.0",
|
|
186
186
|
"@vitest/eslint-plugin": "1.3.13",
|
|
187
187
|
"c8": "^10.1.3",
|
|
188
188
|
"cheerio": "^1.1.2",
|
|
@@ -196,7 +196,7 @@
|
|
|
196
196
|
"memfs": "^4.56.10",
|
|
197
197
|
"nock": "^14.0.10",
|
|
198
198
|
"npm-run-all2": "^8.0.4",
|
|
199
|
-
"oxfmt": "0.
|
|
199
|
+
"oxfmt": "0.73.0",
|
|
200
200
|
"p-timeout": "^7.0.0",
|
|
201
201
|
"picomatch": "^4.0.4",
|
|
202
202
|
"pkg-pr-new": "^0.0.86",
|
|
@@ -210,7 +210,7 @@
|
|
|
210
210
|
"typescript-eslint": "~8.65.0",
|
|
211
211
|
"typescript-native": "npm:typescript@~7.0.2",
|
|
212
212
|
"verdaccio": "^6.3.2",
|
|
213
|
-
"vitest": "^
|
|
213
|
+
"vitest": "^5.0.0"
|
|
214
214
|
},
|
|
215
215
|
"engines": {
|
|
216
216
|
"node": ">=22.13.0"
|