netlify-cli 27.13.0 → 27.14.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 -0
- package/dist/commands/deploy/deploy.js +0 -3
- 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/local-api-test-opts.d.ts +8 -0
- package/dist/utils/local-api-test-opts.js +14 -0
- package/package.json +17 -17
|
@@ -19,6 +19,7 @@ import { handleOptionError, isOptionError } from '../utils/command-error-handler
|
|
|
19
19
|
import { EXIT_CODES } from '../utils/exit-codes.js';
|
|
20
20
|
import { getFrameworksAPIPaths } from '../utils/frameworks-api.js';
|
|
21
21
|
import { getSiteByName } from '../utils/get-site.js';
|
|
22
|
+
import { getLocalApiTestOpts } from '../utils/local-api-test-opts.js';
|
|
22
23
|
import { buildAuthorizeUrl } from '../utils/login-url.js';
|
|
23
24
|
import openBrowser from '../utils/open-browser.js';
|
|
24
25
|
import { isInteractive } from '../utils/scripted-commands.js';
|
|
@@ -653,6 +654,7 @@ export default class BaseCommand extends Command {
|
|
|
653
654
|
host: host,
|
|
654
655
|
pathPrefix: pathPrefix,
|
|
655
656
|
scheme: scheme,
|
|
657
|
+
testOpts: getLocalApiTestOpts({ host, scheme }),
|
|
656
658
|
offline: offline ?? flags.offline,
|
|
657
659
|
siteFeatureFlagPrefix: 'cli',
|
|
658
660
|
featureFlags: this.featureFlags,
|
|
@@ -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
|
}
|
|
@@ -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;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const LOOPBACK_HOSTNAMES = new Set(['localhost', '127.0.0.1', '[::1]']);
|
|
2
|
+
export const getLocalApiTestOpts = ({ host, scheme, }) => {
|
|
3
|
+
if (host === undefined) {
|
|
4
|
+
return undefined;
|
|
5
|
+
}
|
|
6
|
+
try {
|
|
7
|
+
const { hostname } = new URL(`http://${host}`);
|
|
8
|
+
return LOOPBACK_HOSTNAMES.has(hostname) ? { host, scheme } : undefined;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=local-api-test-opts.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "netlify-cli",
|
|
3
|
-
"version": "27.
|
|
3
|
+
"version": "27.14.1",
|
|
4
4
|
"description": "Netlify command line tool",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"api",
|
|
@@ -54,31 +54,31 @@
|
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@fastify/static": "^10.0.0",
|
|
56
56
|
"@inquirer/prompts": "^8.7.2",
|
|
57
|
-
"@netlify/ai": "^1.0.
|
|
58
|
-
"@netlify/api": "^15.1.
|
|
59
|
-
"@netlify/blobs": "^11.1.
|
|
60
|
-
"@netlify/build": "^37.
|
|
57
|
+
"@netlify/ai": "^1.0.2",
|
|
58
|
+
"@netlify/api": "^15.1.3",
|
|
59
|
+
"@netlify/blobs": "^11.1.6",
|
|
60
|
+
"@netlify/build": "^37.5.1",
|
|
61
61
|
"@netlify/build-info": "^11.3.0",
|
|
62
|
-
"@netlify/config": "^25.
|
|
63
|
-
"@netlify/dev": "^5.1.
|
|
64
|
-
"@netlify/dev-utils": "^6.0.
|
|
65
|
-
"@netlify/edge-bundler": "^16.1.
|
|
66
|
-
"@netlify/edge-functions": "^4.0.
|
|
62
|
+
"@netlify/config": "^25.3.0",
|
|
63
|
+
"@netlify/dev": "^5.1.10",
|
|
64
|
+
"@netlify/dev-utils": "^6.0.4",
|
|
65
|
+
"@netlify/edge-bundler": "^16.1.3",
|
|
66
|
+
"@netlify/edge-functions": "^4.0.3",
|
|
67
67
|
"@netlify/edge-functions-bootstrap": "^3.2.0",
|
|
68
68
|
"@netlify/headers-parser": "^10.1.1",
|
|
69
|
-
"@netlify/images": "^2.0.
|
|
69
|
+
"@netlify/images": "^2.0.5",
|
|
70
70
|
"@netlify/local-functions-proxy": "^2.0.3",
|
|
71
71
|
"@netlify/redirect-matcher": "^0.4.2",
|
|
72
72
|
"@netlify/redirect-parser": "^16.1.1",
|
|
73
|
-
"@netlify/server-dev": "^0.1.
|
|
74
|
-
"@netlify/zip-it-and-ship-it": "^16.
|
|
73
|
+
"@netlify/server-dev": "^0.1.4",
|
|
74
|
+
"@netlify/zip-it-and-ship-it": "^16.4.1",
|
|
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",
|
|
@@ -152,8 +152,8 @@
|
|
|
152
152
|
"@bugsnag/js": "^8.6.0",
|
|
153
153
|
"@eslint/compat": "^1.4.1",
|
|
154
154
|
"@eslint/js": "^9.36.0",
|
|
155
|
-
"@netlify/functions": "^6.0.
|
|
156
|
-
"@netlify/types": "^3.
|
|
155
|
+
"@netlify/functions": "^6.0.3",
|
|
156
|
+
"@netlify/types": "^3.3.0",
|
|
157
157
|
"@sindresorhus/slugify": "^3.0.0",
|
|
158
158
|
"@tsconfig/node22": "22.0.5",
|
|
159
159
|
"@tsconfig/recommended": "^1.0.13",
|
|
@@ -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",
|