@void/deploy-cloudflare 0.22.0 → 0.23.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/index.d.mts +115 -2
- package/dist/index.mjs +309 -274
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -1,13 +1,120 @@
|
|
|
1
1
|
import { AssetConfig, HeaderRule, RedirectRule } from "@void/deploy-core";
|
|
2
|
+
//#region src/operations.d.ts
|
|
3
|
+
/** Typed provider operations shared by app deployment and platform installation. */
|
|
4
|
+
type ConfigTarget = {
|
|
5
|
+
configPath?: string;
|
|
6
|
+
};
|
|
7
|
+
type WorkerTarget = ConfigTarget & {
|
|
8
|
+
workerName?: string;
|
|
9
|
+
};
|
|
10
|
+
type CloudflareOperation = ({
|
|
11
|
+
type: "auth.whoami" | "auth.login" | "auth.logout" | "auth.token";
|
|
12
|
+
} & ConfigTarget) | ({
|
|
13
|
+
type: "d1.list" | "kv.namespace.list" | "containers.list";
|
|
14
|
+
} & ConfigTarget) | ({
|
|
15
|
+
type: "d1.info";
|
|
16
|
+
name: string;
|
|
17
|
+
} & ConfigTarget) | {
|
|
18
|
+
type: "d1.create";
|
|
19
|
+
name: string;
|
|
20
|
+
binding: string;
|
|
21
|
+
configPath: string;
|
|
22
|
+
} | {
|
|
23
|
+
type: "d1.execute";
|
|
24
|
+
database: string;
|
|
25
|
+
sql: string;
|
|
26
|
+
configPath: string;
|
|
27
|
+
} | {
|
|
28
|
+
type: "d1.migrations.list" | "d1.migrations.apply";
|
|
29
|
+
database: string;
|
|
30
|
+
configPath: string;
|
|
31
|
+
} | {
|
|
32
|
+
type: "kv.namespace.create";
|
|
33
|
+
name: string;
|
|
34
|
+
binding: string;
|
|
35
|
+
configPath: string;
|
|
36
|
+
} | ({
|
|
37
|
+
type: "r2.bucket.info";
|
|
38
|
+
name: string;
|
|
39
|
+
} & ConfigTarget) | {
|
|
40
|
+
type: "r2.bucket.create";
|
|
41
|
+
name: string;
|
|
42
|
+
binding: string;
|
|
43
|
+
configPath: string;
|
|
44
|
+
} | {
|
|
45
|
+
type: "hyperdrive.create";
|
|
46
|
+
name: string;
|
|
47
|
+
binding: string;
|
|
48
|
+
configPath: string;
|
|
49
|
+
} | ({
|
|
50
|
+
type: "queues.info" | "queues.create";
|
|
51
|
+
name: string;
|
|
52
|
+
} & ConfigTarget) | ({
|
|
53
|
+
type: "queues.consumer.remove";
|
|
54
|
+
queueName: string;
|
|
55
|
+
workerName: string;
|
|
56
|
+
} & ConfigTarget) | ({
|
|
57
|
+
type: "email.routing.settings" | "email.routing.enable" | "email.routing.rules.list" | "email.sending.settings" | "email.sending.enable";
|
|
58
|
+
domain: string;
|
|
59
|
+
} & ConfigTarget) | ({
|
|
60
|
+
type: "secret.list" | "secret.bulk";
|
|
61
|
+
} & WorkerTarget) | ({
|
|
62
|
+
type: "secret.put" | "secret.delete";
|
|
63
|
+
name: string;
|
|
64
|
+
} & WorkerTarget) | ({
|
|
65
|
+
type: "deployments.status" | "deployments.list" | "versions.list";
|
|
66
|
+
} & WorkerTarget) | ({
|
|
67
|
+
type: "versions.view";
|
|
68
|
+
versionId: string;
|
|
69
|
+
} & WorkerTarget) | ({
|
|
70
|
+
type: "versions.deploy";
|
|
71
|
+
versions: Array<{
|
|
72
|
+
versionId: string;
|
|
73
|
+
percentage: number;
|
|
74
|
+
}>;
|
|
75
|
+
message?: string;
|
|
76
|
+
} & WorkerTarget) | ({
|
|
77
|
+
type: "versions.upload" | "worker.deploy";
|
|
78
|
+
message?: string;
|
|
79
|
+
secretsFilePath?: string;
|
|
80
|
+
assetsDirectory?: string;
|
|
81
|
+
previewAlias?: string;
|
|
82
|
+
noBundle?: boolean;
|
|
83
|
+
} & WorkerTarget) | ({
|
|
84
|
+
type: "triggers.deploy";
|
|
85
|
+
assets?: boolean;
|
|
86
|
+
} & WorkerTarget) | ({
|
|
87
|
+
type: "triggers.clear-crons" | "triggers.clear-domains";
|
|
88
|
+
} & WorkerTarget) | ({
|
|
89
|
+
type: "tail";
|
|
90
|
+
search?: string;
|
|
91
|
+
versionId?: string;
|
|
92
|
+
status?: "error";
|
|
93
|
+
} & WorkerTarget);
|
|
94
|
+
type CloudflareUploadResult = {
|
|
95
|
+
versionId: string;
|
|
96
|
+
previewUrl?: string;
|
|
97
|
+
workersDevUrl?: string;
|
|
98
|
+
};
|
|
99
|
+
type CloudflareProviderError = {
|
|
100
|
+
code?: number;
|
|
101
|
+
message: string;
|
|
102
|
+
};
|
|
103
|
+
//#endregion
|
|
2
104
|
//#region src/execute.d.ts
|
|
3
105
|
type CloudflareCommandResult = {
|
|
106
|
+
/** Structured provider response. stdout is diagnostics only. */
|
|
107
|
+
data?: unknown;
|
|
108
|
+
errorCode?: number;
|
|
109
|
+
failure?: "not-found" | "durable-migration" | "containers-unavailable";
|
|
110
|
+
errors?: Array<CloudflareProviderError>;
|
|
4
111
|
stdout: string;
|
|
5
112
|
stderr: string;
|
|
6
113
|
status: number;
|
|
7
114
|
signal?: NodeJS.Signals | null;
|
|
8
115
|
error?: string | null;
|
|
9
116
|
};
|
|
10
|
-
type CloudflareCommandRunner = (
|
|
117
|
+
type CloudflareCommandRunner = (operation: CloudflareOperation, options: {
|
|
11
118
|
capture: boolean;
|
|
12
119
|
}) => CloudflareCommandResult | Promise<CloudflareCommandResult>;
|
|
13
120
|
type CloudflareDeployExecution = {
|
|
@@ -112,6 +219,7 @@ type CloudflareFrameworkDeployConfig = Readonly<{
|
|
|
112
219
|
assetsIgnore?: ReadonlyArray<string>;
|
|
113
220
|
crons?: ReadonlyArray<string>;
|
|
114
221
|
requiredSecrets?: ReadonlyArray<string>;
|
|
222
|
+
versionMetadataBinding?: string;
|
|
115
223
|
ai?: Readonly<{
|
|
116
224
|
binding: string;
|
|
117
225
|
gateway?: string;
|
|
@@ -142,6 +250,7 @@ export declare function renderCloudflareStaticEntry(options: {
|
|
|
142
250
|
headerRules?: ReadonlyArray<HeaderRule>;
|
|
143
251
|
redirectRules?: ReadonlyArray<RedirectRule>;
|
|
144
252
|
fallbackRules?: ReadonlyArray<RedirectRule>;
|
|
253
|
+
versionMetadataBinding?: string;
|
|
145
254
|
}): string;
|
|
146
255
|
/**
|
|
147
256
|
* Copy static assets into Void's deploy area without Cloudflare's reserved routing files.
|
|
@@ -314,6 +423,10 @@ type CloudflareVersionActivationOptions = {
|
|
|
314
423
|
fetch?: typeof globalThis.fetch;
|
|
315
424
|
sleep?: (milliseconds: number) => Promise<void>;
|
|
316
425
|
readinessAttempts?: number;
|
|
426
|
+
/** Require readiness to report the uploaded Worker's actual version UUID. */
|
|
427
|
+
requireVersionMetadata?: boolean;
|
|
428
|
+
/** A binding-preserving handoff without metadata may use only a genuine version preview. */
|
|
429
|
+
requirePreviewForReadiness?: boolean;
|
|
317
430
|
/** Verify that omitted remote secrets come from the active Worker Version before uploading. */
|
|
318
431
|
verifyInheritedSecrets?: boolean;
|
|
319
432
|
/** Pin opaque secret bindings to the live version when an unpublished upload is newer. */
|
|
@@ -364,4 +477,4 @@ export declare function createCloudflareMetadataRunner(options: {
|
|
|
364
477
|
env?: Readonly<Record<string, string | undefined>>;
|
|
365
478
|
}): CloudflareCommandRunner;
|
|
366
479
|
//#endregion
|
|
367
|
-
export type { ApprovedCloudflareKvBinding, CloudflareCommandResult, CloudflareCommandRunner, CloudflareDeployExecution, CloudflareFrameworkDeployConfig, CloudflareStaticDeployConfig, CloudflareTrafficConfig, CloudflareTriggerConfig, CloudflareTriggerStabilization, CloudflareVersionActivation, CloudflareVersionActivationOptions, CloudflareVersionMetadata, CloudflareWorkerMigrationTagOptions, ExistingCloudflareWorker };
|
|
480
|
+
export type { ApprovedCloudflareKvBinding, CloudflareCommandResult, CloudflareCommandRunner, CloudflareDeployExecution, CloudflareFrameworkDeployConfig, CloudflareOperation, CloudflareProviderError, CloudflareStaticDeployConfig, CloudflareTrafficConfig, CloudflareTriggerConfig, CloudflareTriggerStabilization, CloudflareUploadResult, CloudflareVersionActivation, CloudflareVersionActivationOptions, CloudflareVersionMetadata, CloudflareWorkerMigrationTagOptions, ExistingCloudflareWorker };
|
package/dist/index.mjs
CHANGED
|
@@ -115,25 +115,18 @@ async function readExistingCloudflareWorker(options) {
|
|
|
115
115
|
return worker.snapshot;
|
|
116
116
|
}
|
|
117
117
|
async function readCloudflareWorker(options, allowMissing) {
|
|
118
|
-
const target =
|
|
119
|
-
|
|
120
|
-
options.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
options.
|
|
124
|
-
];
|
|
125
|
-
const read = async (args, missingIsNull = false) => {
|
|
126
|
-
const result = await options.runWrangler([
|
|
127
|
-
...args,
|
|
128
|
-
...target,
|
|
129
|
-
"--json"
|
|
130
|
-
], { capture: true });
|
|
118
|
+
const target = {
|
|
119
|
+
configPath: options.configPath,
|
|
120
|
+
workerName: options.workerName
|
|
121
|
+
};
|
|
122
|
+
const read = async (operation, missingIsNull = false) => {
|
|
123
|
+
const result = await options.runWrangler(operation, { capture: true });
|
|
131
124
|
if (result.status !== 0 || result.signal || result.error) {
|
|
132
|
-
if (missingIsNull &&
|
|
125
|
+
if (missingIsNull && Number.isSafeInteger(result.status) && result.status >= 0 && result.signal == null && (result.error == null || result.error === "") && (result.failure === "not-found" || result.errorCode === 10007)) return null;
|
|
133
126
|
throw new CloudflareWorkerInspectionError(`Could not verify existing Cloudflare Worker "${options.workerName}". Check the account, Worker name, and Cloudflare permissions before retrying.`);
|
|
134
127
|
}
|
|
135
128
|
try {
|
|
136
|
-
const value =
|
|
129
|
+
const value = result.data;
|
|
137
130
|
if (record$1(value)) return value;
|
|
138
131
|
} catch {}
|
|
139
132
|
throw new Error("Cloudflare returned an invalid existing Worker response.");
|
|
@@ -141,21 +134,27 @@ async function readCloudflareWorker(options, allowMissing) {
|
|
|
141
134
|
const deployment = options.versionId ? { versions: [{
|
|
142
135
|
version_id: options.versionId,
|
|
143
136
|
percentage: 100
|
|
144
|
-
}] } : await read(
|
|
137
|
+
}] } : await read({
|
|
138
|
+
type: "deployments.status",
|
|
139
|
+
...target
|
|
140
|
+
}, allowMissing);
|
|
145
141
|
if (deployment === null) return null;
|
|
146
142
|
const versions = deployment.versions;
|
|
147
143
|
if (allowMissing && Array.isArray(versions) && versions.length === 0) return null;
|
|
148
144
|
if (!Array.isArray(versions) || versions.length !== 1 || !record$1(versions[0]) || versions[0].percentage !== 100 || typeof versions[0].version_id !== "string") throw new Error("The existing Worker must have one deployed version receiving all traffic. Finish any gradual rollout before linking.");
|
|
149
145
|
const versionId = versions[0].version_id;
|
|
150
|
-
const version = await read(
|
|
151
|
-
"versions",
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
146
|
+
const version = await read({
|
|
147
|
+
type: "versions.view",
|
|
148
|
+
versionId,
|
|
149
|
+
...target
|
|
150
|
+
});
|
|
155
151
|
if (version === null) throw new Error("Cloudflare did not return the existing Worker configuration.");
|
|
156
152
|
const resources = version.resources;
|
|
157
153
|
if (version.id !== versionId || !record$1(resources) || !Array.isArray(resources.bindings) || !resources.bindings.every(record$1) || !record$1(resources.script_runtime) || !record$1(resources.script)) throw new Error("Cloudflare did not return the existing Worker configuration.");
|
|
158
|
-
const after = options.versionId ? deployment : await read(
|
|
154
|
+
const after = options.versionId ? deployment : await read({
|
|
155
|
+
type: "deployments.status",
|
|
156
|
+
...target
|
|
157
|
+
});
|
|
159
158
|
if (after === null || !isDeepStrictEqual(deployment.versions, after.versions)) throw new Error("Cloudflare production changed while linking. Retry from the current deployment.");
|
|
160
159
|
return {
|
|
161
160
|
snapshot: {
|
|
@@ -207,15 +206,11 @@ async function readCloudflareWorkerMigrationTag(options) {
|
|
|
207
206
|
return worker.migration_tag;
|
|
208
207
|
}
|
|
209
208
|
async function readCloudflareApiAuthentication(runWrangler) {
|
|
210
|
-
const result = await runWrangler(
|
|
211
|
-
"auth",
|
|
212
|
-
"token",
|
|
213
|
-
"--json"
|
|
214
|
-
], { capture: true });
|
|
209
|
+
const result = await runWrangler({ type: "auth.token" }, { capture: true });
|
|
215
210
|
if (result.status !== 0 || result.signal || result.error) throw new Error("Could not read the authenticated Cloudflare session.");
|
|
216
211
|
let value;
|
|
217
212
|
try {
|
|
218
|
-
value =
|
|
213
|
+
value = result.data;
|
|
219
214
|
} catch {
|
|
220
215
|
throw new Error("Cloudflare returned an invalid authentication response.");
|
|
221
216
|
}
|
|
@@ -309,9 +304,6 @@ function declarativeClassesMatch(migrations, actualValue) {
|
|
|
309
304
|
return record$1(current) && current.type === "durable-object" && current.storage === storage;
|
|
310
305
|
});
|
|
311
306
|
}
|
|
312
|
-
function isMissingWorkerOutput$1(output) {
|
|
313
|
-
return /\[code:\s*10007\]/i.test(output) || /no deployments|\bworker\b.*\b(?:does not exist|not found)\b/i.test(output);
|
|
314
|
-
}
|
|
315
307
|
function assertCloudflareBindingsPreserved(before, after, approvedKv = [], options = {}) {
|
|
316
308
|
const staticAssetsBinding = isAssetsOnlyWorker(before) ? options.staticAssetsBinding : void 0;
|
|
317
309
|
const addsAssetsBinding = staticAssetsBinding !== void 0 && !before.bindings.some((binding) => binding.name === staticAssetsBinding);
|
|
@@ -381,6 +373,9 @@ function assertExistingCloudflareConfig(existing, config, built = false, approve
|
|
|
381
373
|
if ((built || config[field] !== void 0) && !equal) throw new Error(`Keep the existing Worker's ${field} when linking. Update it in a later deployment.`);
|
|
382
374
|
}
|
|
383
375
|
const vars = record$1(config.vars) ? config.vars : {};
|
|
376
|
+
const metadataBindings = existing.bindings.filter((binding) => binding.type === "version_metadata");
|
|
377
|
+
const metadata = record$1(config.version_metadata) ? config.version_metadata : void 0;
|
|
378
|
+
if (metadataBindings.length > 1 || metadataBindings.length === 1 && metadata?.binding !== metadataBindings[0].name || metadataBindings.length === 0 && config.version_metadata !== void 0) throw new Error("Keep the existing Worker version_metadata binding when linking; linking cannot add or replace it.");
|
|
384
379
|
const remote = new Map(existing.bindings.map((binding) => [binding.name, binding]));
|
|
385
380
|
for (const [name, value] of Object.entries(vars)) {
|
|
386
381
|
const binding = remote.get(name);
|
|
@@ -423,6 +418,7 @@ function assertExistingCloudflareConfig(existing, config, built = false, approve
|
|
|
423
418
|
"secret_text",
|
|
424
419
|
"secret_key"
|
|
425
420
|
].includes(String(binding.type))) continue;
|
|
421
|
+
if (binding.type === "version_metadata") continue;
|
|
426
422
|
if (binding.type === "assets") {
|
|
427
423
|
if (built && (!record$1(config.assets) || config.assets.binding !== binding.name)) throw new Error(`Keep the existing assets binding ${binding.name} when linking.`);
|
|
428
424
|
continue;
|
|
@@ -478,14 +474,10 @@ async function executeCloudflareDeploy(options) {
|
|
|
478
474
|
ok: false,
|
|
479
475
|
reason: "uploader-changed"
|
|
480
476
|
};
|
|
481
|
-
const result = await Promise.resolve(options.runWrangler(
|
|
482
|
-
"deploy",
|
|
483
|
-
...options.configPath ?
|
|
484
|
-
|
|
485
|
-
"--experimental-provision=false",
|
|
486
|
-
"--experimental-auto-create=false",
|
|
487
|
-
"--autoconfig=false"
|
|
488
|
-
], { capture: false }));
|
|
477
|
+
const result = await Promise.resolve(options.runWrangler({
|
|
478
|
+
type: "worker.deploy",
|
|
479
|
+
...options.configPath ? { configPath: options.configPath } : {}
|
|
480
|
+
}, { capture: false }));
|
|
489
481
|
if (!isCleanCommandExit$1(result)) return {
|
|
490
482
|
ok: false,
|
|
491
483
|
reason: "deploy-failed",
|
|
@@ -558,6 +550,7 @@ function writeCloudflareFrameworkDeployConfig(options) {
|
|
|
558
550
|
upsert(["account_id"], options.accountId);
|
|
559
551
|
upsert(["main"], options.main);
|
|
560
552
|
upsert(["compatibility_date"], options.compatibilityDate);
|
|
553
|
+
if (options.versionMetadataBinding && parse(content).version_metadata === void 0) upsert(["version_metadata"], { binding: options.versionMetadataBinding });
|
|
561
554
|
if (options.compatibilityFlags && options.compatibilityFlags.length > 0) {
|
|
562
555
|
const config = parse(content);
|
|
563
556
|
const current = Array.isArray(config.compatibility_flags) ? config.compatibility_flags.filter((flag) => typeof flag === "string") : [];
|
|
@@ -615,7 +608,8 @@ function renderCloudflareStaticEntry(options) {
|
|
|
615
608
|
const config = JSON.stringify({
|
|
616
609
|
...options.headerRules?.length ? { headerRules: options.headerRules } : {},
|
|
617
610
|
...options.redirectRules?.length ? { redirectRules: options.redirectRules } : {},
|
|
618
|
-
...options.fallbackRules?.length ? { fallbackRules: options.fallbackRules } : {}
|
|
611
|
+
...options.fallbackRules?.length ? { fallbackRules: options.fallbackRules } : {},
|
|
612
|
+
...options.versionMetadataBinding ? { versionMetadataBinding: options.versionMetadataBinding } : {}
|
|
619
613
|
});
|
|
620
614
|
return [
|
|
621
615
|
`import { createStaticEdgeHandler } from ${JSON.stringify(options.runtimeImport ?? "@void/edge")};`,
|
|
@@ -762,10 +756,6 @@ function cloudflareCommandDiagnostic(result) {
|
|
|
762
756
|
}
|
|
763
757
|
//#endregion
|
|
764
758
|
//#region src/version.ts
|
|
765
|
-
const VERSION_ID_RE = /(?:Worker|Current) Version ID:\s*([0-9a-f]{8}-[0-9a-f-]{27})/i;
|
|
766
|
-
const PREVIEW_URL_RE = /Version Preview(?: Alias)? URL:\s*(https:\/\/\S+)/i;
|
|
767
|
-
const WORKERS_DEV_URL_RE = /(https:\/\/[a-z0-9.-]+\.workers\.dev(?:\/[^\s]*)?)/i;
|
|
768
|
-
const CLOUDFLARE_ERROR_CODE_RE = /\[code:\s*(\d+)\]/i;
|
|
769
759
|
const DURABLE_MIGRATION_VERSION_ERROR = 10211;
|
|
770
760
|
/** Encode migration state into Cloudflare's version annotation for safe rollback diagnostics. */
|
|
771
761
|
function encodeCloudflareVersionMessage(migrations, triggerConfig, contentHash) {
|
|
@@ -830,31 +820,23 @@ async function deployCloudflareVersionTriggers(options) {
|
|
|
830
820
|
const configPath = join(directory, "wrangler.json");
|
|
831
821
|
try {
|
|
832
822
|
writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, { mode: 384 });
|
|
833
|
-
const deployed = await options.runWrangler(
|
|
834
|
-
"triggers",
|
|
835
|
-
"deploy",
|
|
836
|
-
"-c",
|
|
823
|
+
const deployed = await options.runWrangler({
|
|
824
|
+
type: "triggers.deploy",
|
|
837
825
|
configPath,
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
options.workerName
|
|
841
|
-
], { capture: false });
|
|
826
|
+
workerName: options.workerName
|
|
827
|
+
}, { capture: false });
|
|
842
828
|
if (!isCleanCommandExit(deployed)) return deployed;
|
|
843
829
|
if (options.triggerConfig.crons.length === 0 && options.publishEmptyCrons) {
|
|
844
830
|
const published = await options.publishEmptyCrons();
|
|
845
831
|
if (!isCleanCommandExit(published)) return published;
|
|
846
832
|
}
|
|
847
833
|
for (const queue of removedQueueConsumers(options.triggerConfig, options.previousTriggerConfig)) {
|
|
848
|
-
const removed = await options.runWrangler(
|
|
849
|
-
"queues",
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
"-c",
|
|
855
|
-
configPath,
|
|
856
|
-
"--env="
|
|
857
|
-
], { capture: false });
|
|
834
|
+
const removed = await options.runWrangler({
|
|
835
|
+
type: "queues.consumer.remove",
|
|
836
|
+
queueName: queue,
|
|
837
|
+
workerName: options.workerName,
|
|
838
|
+
configPath
|
|
839
|
+
}, { capture: false });
|
|
858
840
|
if (!isCleanCommandExit(removed)) return removed;
|
|
859
841
|
}
|
|
860
842
|
if (options.manageRoutes !== false && !options.triggerConfig.preserveRoutes && hasCustomDomains(options.previousTriggerConfig?.routes) && !hasCustomDomains(options.triggerConfig.routes)) {
|
|
@@ -894,11 +876,11 @@ function pickWranglerTriggerContext(source) {
|
|
|
894
876
|
* schema-2 snapshot. The bounded loop fails closed if traffic never stabilizes.
|
|
895
877
|
*/
|
|
896
878
|
async function stabilizeCloudflareVersionTriggers(options) {
|
|
897
|
-
const
|
|
879
|
+
const configPath = options.configPath;
|
|
898
880
|
let cleanupBaseline = mergeTriggerCleanupBaselines(options.previousTriggerConfig, options.targetTriggerConfig);
|
|
899
881
|
const attempts = Math.max(1, options.attempts ?? 4);
|
|
900
882
|
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
901
|
-
const owner = await readCurrentProductionVersion(options,
|
|
883
|
+
const owner = await readCurrentProductionVersion(options, configPath);
|
|
902
884
|
if (owner.kind === "failed") return {
|
|
903
885
|
ok: false,
|
|
904
886
|
reason: "status-failed",
|
|
@@ -916,7 +898,7 @@ async function stabilizeCloudflareVersionTriggers(options) {
|
|
|
916
898
|
const activeVersionId = owner.versionId;
|
|
917
899
|
let activeTriggerConfig = activeVersionId === options.targetVersionId ? options.targetTriggerConfig : options.knownTriggerConfigs?.[activeVersionId];
|
|
918
900
|
if (!activeTriggerConfig && activeVersionId !== options.targetVersionId) {
|
|
919
|
-
const metadata = await readCloudflareVersionMetadata(options,
|
|
901
|
+
const metadata = await readCloudflareVersionMetadata(options, configPath, activeVersionId);
|
|
920
902
|
activeTriggerConfig = metadata?.schema === 2 ? metadata.triggerConfig : void 0;
|
|
921
903
|
}
|
|
922
904
|
if (!activeTriggerConfig) return {
|
|
@@ -941,7 +923,7 @@ async function stabilizeCloudflareVersionTriggers(options) {
|
|
|
941
923
|
activeVersionId
|
|
942
924
|
};
|
|
943
925
|
cleanupBaseline = mergeTriggerCleanupBaselines(cleanupBaseline, activeTriggerConfig);
|
|
944
|
-
const after = await readCurrentProductionVersion(options,
|
|
926
|
+
const after = await readCurrentProductionVersion(options, configPath);
|
|
945
927
|
if (after.kind === "failed") return {
|
|
946
928
|
ok: false,
|
|
947
929
|
reason: "status-failed",
|
|
@@ -1139,6 +1121,15 @@ function resolvePreservedTraffic(desired, previous, workerExists) {
|
|
|
1139
1121
|
function isRecord(value) {
|
|
1140
1122
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1141
1123
|
}
|
|
1124
|
+
function isCandidatePreviewUrl(value, workerName, versionId, alias) {
|
|
1125
|
+
try {
|
|
1126
|
+
const url = new URL(value);
|
|
1127
|
+
const label = url.hostname.split(".")[0];
|
|
1128
|
+
return url.protocol === "https:" && !url.username && !url.password && extractWorkersDevSubdomain(value) !== void 0 && (label === `${versionId.slice(0, 8)}-${workerName}` || label === `${alias}-${workerName}`);
|
|
1129
|
+
} catch {
|
|
1130
|
+
return false;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1142
1133
|
/** Upload, probe, and atomically activate a verified Worker Version. */
|
|
1143
1134
|
async function activateCloudflareVersion(options) {
|
|
1144
1135
|
const candidateSecrets = Object.entries(options.candidateSecrets ?? {});
|
|
@@ -1159,13 +1150,13 @@ async function activateCloudflareVersion(options) {
|
|
|
1159
1150
|
}
|
|
1160
1151
|
}
|
|
1161
1152
|
async function activateCloudflareVersionWithSecretsFile(options) {
|
|
1162
|
-
const
|
|
1153
|
+
const configPath = options.configPath;
|
|
1163
1154
|
if (!options.verifyUploader()) return {
|
|
1164
1155
|
ok: false,
|
|
1165
1156
|
reason: "uploader-changed"
|
|
1166
1157
|
};
|
|
1167
1158
|
options.onProgress?.("Checking the current Worker deployment...");
|
|
1168
|
-
const previous = await readCurrentProductionVersion(options,
|
|
1159
|
+
const previous = await readCurrentProductionVersion(options, configPath);
|
|
1169
1160
|
if (previous.kind === "failed") return {
|
|
1170
1161
|
ok: false,
|
|
1171
1162
|
reason: "status-failed",
|
|
@@ -1186,12 +1177,19 @@ async function activateCloudflareVersionWithSecretsFile(options) {
|
|
|
1186
1177
|
reason: "uploader-changed"
|
|
1187
1178
|
};
|
|
1188
1179
|
const previousVersionId = previous.versionId;
|
|
1180
|
+
if (options.atomic && options.requirePreviewForReadiness) return {
|
|
1181
|
+
ok: false,
|
|
1182
|
+
reason: "preview-unavailable",
|
|
1183
|
+
previousVersionId,
|
|
1184
|
+
rollback: "not-needed",
|
|
1185
|
+
diagnostic: "This binding-preserving handoff requires a candidate preview before activation. Configure version metadata on the existing Worker or retry without atomic publication; production traffic is unchanged."
|
|
1186
|
+
};
|
|
1189
1187
|
const trustedInheritanceVersionIds = previousVersionId && options.trustedInheritanceVersionIds?.at(-1) === previousVersionId ? [] : options.trustedInheritanceVersionIds;
|
|
1190
1188
|
let inheritanceBaseline;
|
|
1191
1189
|
let trustedCandidateChain = false;
|
|
1192
1190
|
let activeSecretsPinned = false;
|
|
1193
1191
|
if (options.expectedPreviousVersionId !== void 0 || options.verifyInheritedSecrets === true) {
|
|
1194
|
-
const recent = await readRecentCloudflareVersionIds(options,
|
|
1192
|
+
const recent = await readRecentCloudflareVersionIds(options, configPath);
|
|
1195
1193
|
if (recent === null) return {
|
|
1196
1194
|
ok: false,
|
|
1197
1195
|
reason: "inheritance-source-unavailable"
|
|
@@ -1210,13 +1208,13 @@ async function activateCloudflareVersionWithSecretsFile(options) {
|
|
|
1210
1208
|
};
|
|
1211
1209
|
}
|
|
1212
1210
|
if (options.verifyInheritedSecrets === true && previous.versionId !== latestVersionId && !trustedCandidateChain) {
|
|
1213
|
-
const replacesAllRemoteSecrets = await candidateReplacesAllRemoteSecrets(options,
|
|
1211
|
+
const replacesAllRemoteSecrets = await candidateReplacesAllRemoteSecrets(options, configPath, previous.versionId, latestVersionId);
|
|
1214
1212
|
if (replacesAllRemoteSecrets === null) return {
|
|
1215
1213
|
ok: false,
|
|
1216
1214
|
reason: "inheritance-source-unavailable"
|
|
1217
1215
|
};
|
|
1218
1216
|
if (!replacesAllRemoteSecrets) {
|
|
1219
|
-
const activeSecrets = previous.versionId ? await readCloudflareVersionSecretBindings(options,
|
|
1217
|
+
const activeSecrets = previous.versionId ? await readCloudflareVersionSecretBindings(options, configPath, previous.versionId) : null;
|
|
1220
1218
|
if (!activeSecrets || !options.pinActiveSecrets || !await options.pinActiveSecrets(previous.versionId, activeSecrets)) return {
|
|
1221
1219
|
ok: false,
|
|
1222
1220
|
reason: "inheritance-source-changed",
|
|
@@ -1230,10 +1228,10 @@ async function activateCloudflareVersionWithSecretsFile(options) {
|
|
|
1230
1228
|
}
|
|
1231
1229
|
const desiredMetadata = parseCloudflareVersionMessage(options.message);
|
|
1232
1230
|
let desiredTriggerConfig = desiredMetadata?.schema === 2 ? desiredMetadata.triggerConfig : options.configPath ? readCloudflareTriggerConfig(options.configPath) : triggerConfigFromWrangler({});
|
|
1233
|
-
let previousVersion = previousVersionId && desiredMetadata?.schema === 2 ? await readCloudflareVersionInspection(options,
|
|
1231
|
+
let previousVersion = previousVersionId && desiredMetadata?.schema === 2 ? await readCloudflareVersionInspection(options, configPath, previousVersionId, inheritanceBaseline?.map((version) => version.inspection)) : null;
|
|
1234
1232
|
async function previousIsSecretOnlyDraft() {
|
|
1235
1233
|
if (!previousVersionId) return false;
|
|
1236
|
-
previousVersion ??= await readCloudflareVersionInspection(options,
|
|
1234
|
+
previousVersion ??= await readCloudflareVersionInspection(options, configPath, previousVersionId, inheritanceBaseline?.map((version) => version.inspection));
|
|
1237
1235
|
return previousVersion?.isSecretOnlyDraft === true;
|
|
1238
1236
|
}
|
|
1239
1237
|
const previousMetadata = desiredMetadata?.schema === 2 ? previousVersion?.metadata : null;
|
|
@@ -1267,57 +1265,49 @@ async function activateCloudflareVersionWithSecretsFile(options) {
|
|
|
1267
1265
|
const alias = `void-ready-${Date.now().toString(36)}`;
|
|
1268
1266
|
if (options.atomic && previous.versionId !== null) {
|
|
1269
1267
|
if (!activeSecretsPinned && options.pinActiveSecrets) {
|
|
1270
|
-
const activeSecrets = await readCloudflareVersionSecretBindings(options,
|
|
1268
|
+
const activeSecrets = await readCloudflareVersionSecretBindings(options, configPath, previous.versionId);
|
|
1271
1269
|
if (activeSecrets?.length) await options.pinActiveSecrets(previous.versionId, activeSecrets);
|
|
1272
1270
|
}
|
|
1273
1271
|
return bootstrapFirstDeployment({
|
|
1274
1272
|
...options,
|
|
1275
1273
|
message
|
|
1276
|
-
},
|
|
1274
|
+
}, configPath, previous.versionId, true, previousTriggerConfig);
|
|
1277
1275
|
}
|
|
1278
1276
|
options.onProgress?.("Uploading the Worker and assets...");
|
|
1279
|
-
const upload = await Promise.resolve(options.runWrangler(
|
|
1280
|
-
"versions",
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
"--name",
|
|
1284
|
-
options.workerName,
|
|
1285
|
-
"--message",
|
|
1277
|
+
const upload = await Promise.resolve(options.runWrangler({
|
|
1278
|
+
type: "versions.upload",
|
|
1279
|
+
configPath,
|
|
1280
|
+
workerName: options.workerName,
|
|
1286
1281
|
message,
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
"--strict",
|
|
1292
|
-
"--experimental-provision=false",
|
|
1293
|
-
"--experimental-auto-create=false"
|
|
1294
|
-
], { capture: true }));
|
|
1282
|
+
secretsFilePath: options.candidateSecretsFilePath,
|
|
1283
|
+
assetsDirectory: options.assetsDirectoryOverride,
|
|
1284
|
+
previewAlias: alias
|
|
1285
|
+
}, { capture: true }));
|
|
1295
1286
|
if (!isCleanCommandExit(upload)) {
|
|
1296
|
-
const
|
|
1297
|
-
const
|
|
1298
|
-
const isBootstrapError = cloudflareErrorCode === DURABLE_MIGRATION_VERSION_ERROR || /This\s+Worker\s+has\s+a\s+pending\s+Durable\s+Object\s+migration,\s+which\s+cannot\s+be\s+applied\s+by\s+`wrangler\s+versions\s+upload`/i.test(output) || /cannot upload a new version of a worker that does not yet exist/i.test(output);
|
|
1287
|
+
const cloudflareErrorCode = upload.errorCode;
|
|
1288
|
+
const isBootstrapError = isCompletedCommand(upload) && (upload.failure === "durable-migration" || upload.failure === "not-found" || cloudflareErrorCode === DURABLE_MIGRATION_VERSION_ERROR || cloudflareErrorCode === 10007);
|
|
1299
1289
|
if (!options.preserveTriggers && isBootstrapError && (previous.versionId === null || options.allowFirstDeploymentBootstrap === true || await previousIsSecretOnlyDraft())) return bootstrapFirstDeployment({
|
|
1300
1290
|
...options,
|
|
1301
1291
|
message
|
|
1302
|
-
},
|
|
1292
|
+
}, configPath, previous.versionId, false, previousTriggerConfig);
|
|
1303
1293
|
return {
|
|
1304
1294
|
ok: false,
|
|
1305
1295
|
reason: "upload-failed",
|
|
1306
1296
|
status: upload.status,
|
|
1307
1297
|
...commandDiagnosticFields(upload),
|
|
1308
1298
|
...cloudflareErrorCode === void 0 ? {} : { cloudflareErrorCode },
|
|
1309
|
-
...isContainersUnavailable(
|
|
1299
|
+
...isContainersUnavailable(upload) ? { containersUnavailable: true } : {}
|
|
1310
1300
|
};
|
|
1311
1301
|
}
|
|
1312
|
-
const
|
|
1313
|
-
const versionId =
|
|
1302
|
+
const uploaded = readUploadResult(upload);
|
|
1303
|
+
const versionId = uploaded?.versionId;
|
|
1314
1304
|
if (!versionId) return {
|
|
1315
1305
|
ok: false,
|
|
1316
1306
|
reason: "upload-output-invalid"
|
|
1317
1307
|
};
|
|
1318
1308
|
options.onProgress?.("Verifying the uploaded Worker...");
|
|
1319
1309
|
if (inheritanceBaseline) {
|
|
1320
|
-
const recent = await readRecentCloudflareVersionIds(options,
|
|
1310
|
+
const recent = await readRecentCloudflareVersionIds(options, configPath);
|
|
1321
1311
|
const previousLatest = inheritanceBaseline.at(-1);
|
|
1322
1312
|
const expected = [...inheritanceBaseline.map((version) => version.id), versionId].slice(-10);
|
|
1323
1313
|
if (recent === null) return {
|
|
@@ -1342,7 +1332,15 @@ async function activateCloudflareVersionWithSecretsFile(options) {
|
|
|
1342
1332
|
if (!options.validateUploadedVersion && !inheritanceBaseline) throw new Error("A Worker candidate cannot be recorded without validating its upload and version sequence.");
|
|
1343
1333
|
await options.recordVerifiedCandidate(versionId, previous.versionId);
|
|
1344
1334
|
}
|
|
1345
|
-
const reportedPreviewUrl =
|
|
1335
|
+
const reportedPreviewUrl = desiredTriggerConfig.preview_urls || desiredTriggerConfig.preservePreviewUrls || desiredTriggerConfig.preserve ? uploaded?.previewUrl : void 0;
|
|
1336
|
+
if (options.requirePreviewForReadiness && (!reportedPreviewUrl || !isCandidatePreviewUrl(reportedPreviewUrl, options.workerName, versionId, alias))) return {
|
|
1337
|
+
ok: false,
|
|
1338
|
+
reason: "preview-unavailable",
|
|
1339
|
+
versionId,
|
|
1340
|
+
previousVersionId,
|
|
1341
|
+
rollback: "not-needed",
|
|
1342
|
+
diagnostic: "This existing Worker has no version metadata binding and no genuine candidate preview URL. Keep its bindings unchanged; configure version metadata on the existing Worker or use an admitted version preview, then retry the handoff. Production traffic is unchanged."
|
|
1343
|
+
};
|
|
1346
1344
|
let authenticatedWorkersDevSubdomain = reportedPreviewUrl ? extractWorkersDevSubdomain(reportedPreviewUrl) : void 0;
|
|
1347
1345
|
let readinessUrl = reportedPreviewUrl;
|
|
1348
1346
|
let readinessUsesCustomRoute = false;
|
|
@@ -1351,7 +1349,7 @@ async function activateCloudflareVersionWithSecretsFile(options) {
|
|
|
1351
1349
|
if (!options.preserveTriggers && (previous.versionId === null || options.allowFirstDeploymentBootstrap === true || await previousIsSecretOnlyDraft())) return bootstrapFirstDeployment({
|
|
1352
1350
|
...options,
|
|
1353
1351
|
message
|
|
1354
|
-
},
|
|
1352
|
+
}, configPath, previous.versionId, false, previousTriggerConfig);
|
|
1355
1353
|
readinessUrl = desiredTriggerConfig.workers_dev ? workersDevReadinessUrl(options.workerName, options.workersDevSubdomain) : customRouteReadinessUrl(desiredTriggerConfig.routes);
|
|
1356
1354
|
readinessUsesCustomRoute = !desiredTriggerConfig.workers_dev && readinessUrl !== void 0;
|
|
1357
1355
|
if (!readinessUrl) return {
|
|
@@ -1369,11 +1367,11 @@ async function activateCloudflareVersionWithSecretsFile(options) {
|
|
|
1369
1367
|
previousVersionId: previous.versionId,
|
|
1370
1368
|
rollback: "not-needed"
|
|
1371
1369
|
};
|
|
1372
|
-
const stagingOwnerFailure = await verifyReadinessStagingOwner(options,
|
|
1370
|
+
const stagingOwnerFailure = await verifyReadinessStagingOwner(options, configPath, previous.versionId, versionId);
|
|
1373
1371
|
if (stagingOwnerFailure) return stagingOwnerFailure;
|
|
1374
|
-
const staged = await deployVersionPercentages(options,
|
|
1372
|
+
const staged = await deployVersionPercentages(options, configPath, [`${previous.versionId}@100`, `${versionId}@0`], message);
|
|
1375
1373
|
if (!isCleanCommandExit(staged)) {
|
|
1376
|
-
const cloudflareErrorCode =
|
|
1374
|
+
const cloudflareErrorCode = staged.errorCode;
|
|
1377
1375
|
return {
|
|
1378
1376
|
ok: false,
|
|
1379
1377
|
reason: "readiness-staging-failed",
|
|
@@ -1388,8 +1386,8 @@ async function activateCloudflareVersionWithSecretsFile(options) {
|
|
|
1388
1386
|
stagedVersionOverride = true;
|
|
1389
1387
|
}
|
|
1390
1388
|
options.onProgress?.("Waiting for the Worker to become ready...");
|
|
1391
|
-
let ready = await probeReadiness(`${readinessUrl.replace(/\/$/, "")}/__void/ready`, options.workerName, readinessUsesCustomRoute ? void 0 : options.readinessHeaders, authenticatedWorkersDevSubdomain, stagedVersionOverride ? versionId : void 0, options.fetch ?? globalThis.fetch, options.sleep ?? defaultSleep, options.readinessAttempts ?? 6);
|
|
1392
|
-
if (ready === "access-denied" && !stagedVersionOverride && previous.versionId !== null) {
|
|
1389
|
+
let ready = await probeReadiness(`${readinessUrl.replace(/\/$/, "")}/__void/ready`, options.workerName, readinessUsesCustomRoute ? void 0 : options.readinessHeaders, authenticatedWorkersDevSubdomain, stagedVersionOverride ? versionId : void 0, options.fetch ?? globalThis.fetch, options.sleep ?? defaultSleep, options.readinessAttempts ?? 6, Number.POSITIVE_INFINITY, options.requireVersionMetadata ? versionId : void 0);
|
|
1390
|
+
if (ready === "access-denied" && !stagedVersionOverride && previous.versionId !== null && !options.requirePreviewForReadiness) {
|
|
1393
1391
|
const stableReadinessUrl = desiredTriggerConfig.workers_dev ? workersDevReadinessUrl(options.workerName, extractWorkersDevSubdomain(readinessUrl) ?? options.workersDevSubdomain) : customRouteReadinessUrl(desiredTriggerConfig.routes);
|
|
1394
1392
|
if (stableReadinessUrl && stableReadinessUrl !== readinessUrl) {
|
|
1395
1393
|
if (!options.verifyUploader()) return {
|
|
@@ -1399,11 +1397,11 @@ async function activateCloudflareVersionWithSecretsFile(options) {
|
|
|
1399
1397
|
previousVersionId: previous.versionId,
|
|
1400
1398
|
rollback: "not-needed"
|
|
1401
1399
|
};
|
|
1402
|
-
const stagingOwnerFailure = await verifyReadinessStagingOwner(options,
|
|
1400
|
+
const stagingOwnerFailure = await verifyReadinessStagingOwner(options, configPath, previous.versionId, versionId);
|
|
1403
1401
|
if (stagingOwnerFailure) return stagingOwnerFailure;
|
|
1404
|
-
const staged = await deployVersionPercentages(options,
|
|
1402
|
+
const staged = await deployVersionPercentages(options, configPath, [`${previous.versionId}@100`, `${versionId}@0`], message);
|
|
1405
1403
|
if (!isCleanCommandExit(staged)) {
|
|
1406
|
-
const cloudflareErrorCode =
|
|
1404
|
+
const cloudflareErrorCode = staged.errorCode;
|
|
1407
1405
|
return {
|
|
1408
1406
|
ok: false,
|
|
1409
1407
|
reason: "readiness-staging-failed",
|
|
@@ -1419,7 +1417,7 @@ async function activateCloudflareVersionWithSecretsFile(options) {
|
|
|
1419
1417
|
authenticatedWorkersDevSubdomain = extractWorkersDevSubdomain(stableReadinessUrl);
|
|
1420
1418
|
readinessUsesCustomRoute = !desiredTriggerConfig.workers_dev;
|
|
1421
1419
|
stagedVersionOverride = true;
|
|
1422
|
-
ready = await probeReadiness(`${readinessUrl.replace(/\/$/, "")}/__void/ready`, options.workerName, readinessUsesCustomRoute ? void 0 : options.readinessHeaders, authenticatedWorkersDevSubdomain, versionId, options.fetch ?? globalThis.fetch, options.sleep ?? defaultSleep, options.readinessAttempts ?? 6);
|
|
1420
|
+
ready = await probeReadiness(`${readinessUrl.replace(/\/$/, "")}/__void/ready`, options.workerName, readinessUsesCustomRoute ? void 0 : options.readinessHeaders, authenticatedWorkersDevSubdomain, versionId, options.fetch ?? globalThis.fetch, options.sleep ?? defaultSleep, options.readinessAttempts ?? 6, Number.POSITIVE_INFINITY, options.requireVersionMetadata ? versionId : void 0);
|
|
1423
1421
|
}
|
|
1424
1422
|
}
|
|
1425
1423
|
if (ready !== "ready") return {
|
|
@@ -1427,16 +1425,16 @@ async function activateCloudflareVersionWithSecretsFile(options) {
|
|
|
1427
1425
|
reason: ready === "access-denied" ? "readiness-access-denied" : "readiness-failed",
|
|
1428
1426
|
versionId,
|
|
1429
1427
|
previousVersionId: previous.versionId,
|
|
1430
|
-
rollback: stagedVersionOverride ? await restorePreviousVersion(options,
|
|
1428
|
+
rollback: stagedVersionOverride ? await restorePreviousVersion(options, configPath, previous.versionId, "readiness failed", [stagedVersionPercentages(previous.versionId, versionId)]) : "not-needed"
|
|
1431
1429
|
};
|
|
1432
1430
|
if (!options.verifyUploader()) return {
|
|
1433
1431
|
ok: false,
|
|
1434
1432
|
reason: "uploader-changed",
|
|
1435
1433
|
versionId,
|
|
1436
1434
|
previousVersionId: previous.versionId,
|
|
1437
|
-
rollback: stagedVersionOverride ? await restorePreviousVersion(options,
|
|
1435
|
+
rollback: stagedVersionOverride ? await restorePreviousVersion(options, configPath, previous.versionId, "uploader verification failed", [stagedVersionPercentages(previous.versionId, versionId)]) : "not-needed"
|
|
1438
1436
|
};
|
|
1439
|
-
const ownerBeforeActivation = await readCurrentProductionVersion(options,
|
|
1437
|
+
const ownerBeforeActivation = await readCurrentProductionVersion(options, configPath);
|
|
1440
1438
|
if (ownerBeforeActivation.kind === "failed") return {
|
|
1441
1439
|
ok: false,
|
|
1442
1440
|
reason: "status-failed",
|
|
@@ -1464,14 +1462,14 @@ async function activateCloudflareVersionWithSecretsFile(options) {
|
|
|
1464
1462
|
triggerRepair: "succeeded"
|
|
1465
1463
|
};
|
|
1466
1464
|
options.onProgress?.("Activating the Worker...");
|
|
1467
|
-
const activate = await deployVersionPercentages(options,
|
|
1465
|
+
const activate = await deployVersionPercentages(options, configPath, [`${versionId}@100`], message);
|
|
1468
1466
|
if (!isCleanCommandExit(activate)) return {
|
|
1469
1467
|
ok: false,
|
|
1470
1468
|
reason: "activation-failed",
|
|
1471
1469
|
status: activate.status,
|
|
1472
1470
|
versionId,
|
|
1473
1471
|
previousVersionId: previous.versionId,
|
|
1474
|
-
rollback: stagedVersionOverride ? await restorePreviousVersion(options,
|
|
1472
|
+
rollback: stagedVersionOverride ? await restorePreviousVersion(options, configPath, previous.versionId, "version activation failed", [stagedVersionPercentages(previous.versionId, versionId), [{
|
|
1475
1473
|
versionId,
|
|
1476
1474
|
percentage: 100
|
|
1477
1475
|
}]]) : "not-needed"
|
|
@@ -1484,7 +1482,7 @@ async function activateCloudflareVersionWithSecretsFile(options) {
|
|
|
1484
1482
|
rollback: previous.versionId ? "unsafe" : "unavailable"
|
|
1485
1483
|
};
|
|
1486
1484
|
if (options.preserveTriggers) {
|
|
1487
|
-
const owner = await readCurrentProductionVersion(options,
|
|
1485
|
+
const owner = await readCurrentProductionVersion(options, configPath);
|
|
1488
1486
|
if (owner.kind !== "ok" || owner.versionId !== versionId) return {
|
|
1489
1487
|
ok: false,
|
|
1490
1488
|
reason: "deployment-changed",
|
|
@@ -1535,7 +1533,7 @@ async function activateCloudflareVersionWithSecretsFile(options) {
|
|
|
1535
1533
|
previousVersionId: previous.versionId,
|
|
1536
1534
|
rollback: "unsafe"
|
|
1537
1535
|
};
|
|
1538
|
-
const ownerBeforeCompensation = await readCurrentProductionVersion(options,
|
|
1536
|
+
const ownerBeforeCompensation = await readCurrentProductionVersion(options, configPath);
|
|
1539
1537
|
if (ownerBeforeCompensation.kind !== "ok" || ownerBeforeCompensation.versionId === null) return {
|
|
1540
1538
|
ok: false,
|
|
1541
1539
|
reason: "triggers-failed",
|
|
@@ -1567,7 +1565,7 @@ async function activateCloudflareVersionWithSecretsFile(options) {
|
|
|
1567
1565
|
triggerRepair: repair.ok ? "succeeded" : repair.reason === "metadata-unavailable" ? "unavailable" : "failed"
|
|
1568
1566
|
};
|
|
1569
1567
|
}
|
|
1570
|
-
const rollback = await restorePreviousVersionAndTriggers(options,
|
|
1568
|
+
const rollback = await restorePreviousVersionAndTriggers(options, configPath, previous.versionId, "trigger activation failed", desiredTriggerConfig, versionId, versionedPreviousTriggerConfigWithTraffic);
|
|
1571
1569
|
return {
|
|
1572
1570
|
ok: false,
|
|
1573
1571
|
reason: "triggers-failed",
|
|
@@ -1600,8 +1598,8 @@ async function activateCloudflareVersionWithSecretsFile(options) {
|
|
|
1600
1598
|
...workersDevSubdomainResult(readinessUrl)
|
|
1601
1599
|
};
|
|
1602
1600
|
}
|
|
1603
|
-
async function restorePreviousVersionAndTriggers(options,
|
|
1604
|
-
const traffic = await restorePreviousVersion(options,
|
|
1601
|
+
async function restorePreviousVersionAndTriggers(options, configPath, previousVersionId, cause, failedTriggerConfig, failedVersionId, capturedTriggerConfig) {
|
|
1602
|
+
const traffic = await restorePreviousVersion(options, configPath, previousVersionId, cause, [[{
|
|
1605
1603
|
versionId: failedVersionId,
|
|
1606
1604
|
percentage: 100
|
|
1607
1605
|
}]]);
|
|
@@ -1609,7 +1607,7 @@ async function restorePreviousVersionAndTriggers(options, commonArgs, previousVe
|
|
|
1609
1607
|
if (!options.verifyUploader()) return "unsafe";
|
|
1610
1608
|
let targetTriggerConfig = capturedTriggerConfig;
|
|
1611
1609
|
if (!targetTriggerConfig) {
|
|
1612
|
-
const metadata = await readCloudflareVersionMetadata(options,
|
|
1610
|
+
const metadata = await readCloudflareVersionMetadata(options, configPath, previousVersionId);
|
|
1613
1611
|
if (metadata?.schema !== 2) return "unavailable";
|
|
1614
1612
|
targetTriggerConfig = metadata.triggerConfig;
|
|
1615
1613
|
}
|
|
@@ -1628,23 +1626,20 @@ async function restorePreviousVersionAndTriggers(options, commonArgs, previousVe
|
|
|
1628
1626
|
});
|
|
1629
1627
|
return triggers.ok && triggers.targetOwnsTraffic ? "succeeded" : "failed";
|
|
1630
1628
|
}
|
|
1631
|
-
async function readCloudflareVersionMetadata(options,
|
|
1632
|
-
return (await readCloudflareVersionInspection(options,
|
|
1629
|
+
async function readCloudflareVersionMetadata(options, configPath, versionId) {
|
|
1630
|
+
return (await readCloudflareVersionInspection(options, configPath, versionId))?.metadata ?? null;
|
|
1633
1631
|
}
|
|
1634
|
-
async function readCloudflareVersionInspection(options,
|
|
1632
|
+
async function readCloudflareVersionInspection(options, configPath, versionId, knownVersions) {
|
|
1635
1633
|
try {
|
|
1636
1634
|
let versions = knownVersions;
|
|
1637
1635
|
if (versions === void 0) {
|
|
1638
|
-
const result = await options.runWrangler(
|
|
1639
|
-
"versions",
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
options.workerName,
|
|
1644
|
-
"--json"
|
|
1645
|
-
], { capture: true });
|
|
1636
|
+
const result = await options.runWrangler({
|
|
1637
|
+
type: "versions.list",
|
|
1638
|
+
configPath,
|
|
1639
|
+
workerName: options.workerName
|
|
1640
|
+
}, { capture: true });
|
|
1646
1641
|
if (!isCleanCommandExit(result)) return null;
|
|
1647
|
-
versions =
|
|
1642
|
+
versions = result.data;
|
|
1648
1643
|
}
|
|
1649
1644
|
if (!Array.isArray(versions)) return null;
|
|
1650
1645
|
for (const entry of versions) {
|
|
@@ -1653,17 +1648,14 @@ async function readCloudflareVersionInspection(options, commonArgs, versionId, k
|
|
|
1653
1648
|
const message = annotations?.["workers/message"];
|
|
1654
1649
|
let draft = entry;
|
|
1655
1650
|
if (annotations?.["workers/triggered_by"] === "secret" && Object.keys(annotations).length === 1 && !isRecord(entry.resources)) {
|
|
1656
|
-
const inspection = await Promise.resolve(options.runWrangler(
|
|
1657
|
-
"versions",
|
|
1658
|
-
"view",
|
|
1651
|
+
const inspection = await Promise.resolve(options.runWrangler({
|
|
1652
|
+
type: "versions.view",
|
|
1659
1653
|
versionId,
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
"--json"
|
|
1664
|
-
], { capture: true }));
|
|
1654
|
+
configPath,
|
|
1655
|
+
workerName: options.workerName
|
|
1656
|
+
}, { capture: true }));
|
|
1665
1657
|
if (isCleanCommandExit(inspection)) {
|
|
1666
|
-
const inspected =
|
|
1658
|
+
const inspected = inspection.data;
|
|
1667
1659
|
if (isRecord(inspected) && inspected.id === versionId) draft = inspected;
|
|
1668
1660
|
}
|
|
1669
1661
|
}
|
|
@@ -1677,21 +1669,18 @@ async function readCloudflareVersionInspection(options, commonArgs, versionId, k
|
|
|
1677
1669
|
}
|
|
1678
1670
|
return null;
|
|
1679
1671
|
}
|
|
1680
|
-
async function readRecentCloudflareVersionIds(options,
|
|
1681
|
-
const result = await Promise.resolve(options.runWrangler(
|
|
1682
|
-
"versions",
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
options.workerName,
|
|
1687
|
-
"--json"
|
|
1688
|
-
], { capture: true }));
|
|
1672
|
+
async function readRecentCloudflareVersionIds(options, configPath) {
|
|
1673
|
+
const result = await Promise.resolve(options.runWrangler({
|
|
1674
|
+
type: "versions.list",
|
|
1675
|
+
configPath,
|
|
1676
|
+
workerName: options.workerName
|
|
1677
|
+
}, { capture: true }));
|
|
1689
1678
|
if (!isCleanCommandExit(result)) {
|
|
1690
|
-
if (
|
|
1679
|
+
if (isCompletedCommand(result) && (result.failure === "not-found" || result.errorCode === 10007)) return [];
|
|
1691
1680
|
return null;
|
|
1692
1681
|
}
|
|
1693
1682
|
try {
|
|
1694
|
-
const versions =
|
|
1683
|
+
const versions = result.data;
|
|
1695
1684
|
if (!Array.isArray(versions) || !versions.every((version) => isRecord(version) && typeof version.id === "string" && typeof version.number === "number" && Number.isSafeInteger(version.number))) return null;
|
|
1696
1685
|
return versions.map((version) => ({
|
|
1697
1686
|
id: version.id,
|
|
@@ -1705,29 +1694,26 @@ async function readRecentCloudflareVersionIds(options, commonArgs) {
|
|
|
1705
1694
|
function arraysEqual(left, right) {
|
|
1706
1695
|
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
1707
1696
|
}
|
|
1708
|
-
async function candidateReplacesAllRemoteSecrets(options,
|
|
1697
|
+
async function candidateReplacesAllRemoteSecrets(options, configPath, activeVersionId, latestVersionId) {
|
|
1709
1698
|
const versionIds = [...new Set([activeVersionId, latestVersionId].filter(Boolean))];
|
|
1710
1699
|
const secretBindings = [];
|
|
1711
1700
|
for (const versionId of versionIds) {
|
|
1712
|
-
const bindings = await readCloudflareVersionSecretBindings(options,
|
|
1701
|
+
const bindings = await readCloudflareVersionSecretBindings(options, configPath, versionId);
|
|
1713
1702
|
if (bindings === null) return null;
|
|
1714
1703
|
secretBindings.push(bindings);
|
|
1715
1704
|
}
|
|
1716
1705
|
return secretBindings.every((bindings) => bindings.every((binding) => binding.type === "secret_text" && Object.hasOwn(options.candidateSecrets ?? {}, binding.name)));
|
|
1717
1706
|
}
|
|
1718
|
-
async function readCloudflareVersionSecretBindings(options,
|
|
1719
|
-
const result = await Promise.resolve(options.runWrangler(
|
|
1720
|
-
"versions",
|
|
1721
|
-
"view",
|
|
1707
|
+
async function readCloudflareVersionSecretBindings(options, configPath, versionId) {
|
|
1708
|
+
const result = await Promise.resolve(options.runWrangler({
|
|
1709
|
+
type: "versions.view",
|
|
1722
1710
|
versionId,
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
"--json"
|
|
1727
|
-
], { capture: true }));
|
|
1711
|
+
configPath,
|
|
1712
|
+
workerName: options.workerName
|
|
1713
|
+
}, { capture: true }));
|
|
1728
1714
|
if (!isCleanCommandExit(result)) return null;
|
|
1729
1715
|
try {
|
|
1730
|
-
const version =
|
|
1716
|
+
const version = result.data;
|
|
1731
1717
|
if (!isRecord(version) || version.id !== versionId || !isRecord(version.resources)) return null;
|
|
1732
1718
|
const bindings = version.resources.bindings;
|
|
1733
1719
|
if (!Array.isArray(bindings) || !bindings.every(isRecord)) return null;
|
|
@@ -1751,13 +1737,13 @@ async function readCloudflareVersionSecretBindings(options, commonArgs, versionI
|
|
|
1751
1737
|
* brand-new Worker can therefore need one ordinary deploy before later releases use preview-first
|
|
1752
1738
|
* version activation. This fallback requires prior proof that no application had been deployed.
|
|
1753
1739
|
*/
|
|
1754
|
-
async function bootstrapFirstDeployment(options,
|
|
1740
|
+
async function bootstrapFirstDeployment(options, configPath, previousVersionId, atomic = false, previousTriggerConfig) {
|
|
1755
1741
|
if (!options.verifyUploader()) return {
|
|
1756
1742
|
ok: false,
|
|
1757
1743
|
reason: "uploader-changed"
|
|
1758
1744
|
};
|
|
1759
1745
|
if (atomic) {
|
|
1760
|
-
const current = await readCurrentProductionVersion(options,
|
|
1746
|
+
const current = await readCurrentProductionVersion(options, configPath);
|
|
1761
1747
|
if (current.kind === "failed") return {
|
|
1762
1748
|
ok: false,
|
|
1763
1749
|
reason: "status-failed",
|
|
@@ -1771,53 +1757,114 @@ async function bootstrapFirstDeployment(options, commonArgs, previousVersionId,
|
|
|
1771
1757
|
};
|
|
1772
1758
|
}
|
|
1773
1759
|
options.onProgress?.(atomic ? "Publishing the Worker atomically..." : "Publishing the first Worker deployment...");
|
|
1774
|
-
const bootstrap = await Promise.resolve(options.runWrangler(
|
|
1775
|
-
"deploy",
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
options.
|
|
1779
|
-
|
|
1780
|
-
options.
|
|
1781
|
-
|
|
1782
|
-
...options.assetsDirectoryOverride ? ["--assets", options.assetsDirectoryOverride] : [],
|
|
1783
|
-
"--strict",
|
|
1784
|
-
"--experimental-provision=false",
|
|
1785
|
-
"--experimental-auto-create=false",
|
|
1786
|
-
"--autoconfig=false"
|
|
1787
|
-
], { capture: true }));
|
|
1788
|
-
const output = `${bootstrap.stdout}\n${bootstrap.stderr}`;
|
|
1760
|
+
const bootstrap = await Promise.resolve(options.runWrangler({
|
|
1761
|
+
type: "worker.deploy",
|
|
1762
|
+
configPath,
|
|
1763
|
+
workerName: options.workerName,
|
|
1764
|
+
message: options.message,
|
|
1765
|
+
secretsFilePath: options.candidateSecretsFilePath,
|
|
1766
|
+
assetsDirectory: options.assetsDirectoryOverride
|
|
1767
|
+
}, { capture: true }));
|
|
1789
1768
|
if (!isCleanCommandExit(bootstrap)) {
|
|
1790
|
-
const cloudflareErrorCode =
|
|
1769
|
+
const cloudflareErrorCode = bootstrap.errorCode;
|
|
1791
1770
|
return {
|
|
1792
1771
|
ok: false,
|
|
1793
1772
|
reason: atomic ? "atomic-failed" : "bootstrap-failed",
|
|
1794
1773
|
status: bootstrap.status,
|
|
1795
1774
|
...commandDiagnosticFields(bootstrap),
|
|
1796
1775
|
...cloudflareErrorCode === void 0 ? {} : { cloudflareErrorCode },
|
|
1797
|
-
...isContainersUnavailable(
|
|
1776
|
+
...isContainersUnavailable(bootstrap) ? { containersUnavailable: true } : {}
|
|
1798
1777
|
};
|
|
1799
1778
|
}
|
|
1800
|
-
const
|
|
1779
|
+
const published = readUploadResult(bootstrap);
|
|
1780
|
+
const versionId = published?.versionId;
|
|
1801
1781
|
const desiredMetadata = parseCloudflareVersionMessage(options.message);
|
|
1802
1782
|
const desiredTriggerConfig = desiredMetadata?.schema === 2 ? desiredMetadata.triggerConfig : options.configPath ? readCloudflareTriggerConfig(options.configPath) : triggerConfigFromWrangler({});
|
|
1803
|
-
const
|
|
1804
|
-
const reportedOrConfiguredUrl = reportedUrl ?? (desiredTriggerConfig.workers_dev ? workersDevReadinessUrl(options.workerName, options.workersDevSubdomain) : customRouteReadinessUrl(desiredTriggerConfig.routes));
|
|
1783
|
+
const reportedOrConfiguredUrl = published?.previewUrl ?? published?.workersDevUrl ?? (desiredTriggerConfig.workers_dev ? workersDevReadinessUrl(options.workerName, options.workersDevSubdomain) : customRouteReadinessUrl(desiredTriggerConfig.routes));
|
|
1805
1784
|
if (!versionId || !reportedOrConfiguredUrl) return {
|
|
1806
1785
|
ok: false,
|
|
1807
1786
|
reason: atomic ? "atomic-output-invalid" : "bootstrap-output-invalid",
|
|
1808
1787
|
previousVersionId,
|
|
1809
1788
|
...versionId ? { versionId } : {}
|
|
1810
1789
|
};
|
|
1790
|
+
if (!options.verifyUploader()) return {
|
|
1791
|
+
ok: false,
|
|
1792
|
+
reason: "uploader-changed",
|
|
1793
|
+
versionId,
|
|
1794
|
+
previousVersionId,
|
|
1795
|
+
rollback: "unsafe"
|
|
1796
|
+
};
|
|
1797
|
+
options.onProgress?.("Updating routes and triggers...");
|
|
1798
|
+
const triggerOptions = {
|
|
1799
|
+
runWrangler: options.runWrangler,
|
|
1800
|
+
workerName: options.workerName,
|
|
1801
|
+
configPath: options.configPath,
|
|
1802
|
+
targetVersionId: versionId,
|
|
1803
|
+
targetTriggerConfig: options.preserveTriggers ? {
|
|
1804
|
+
...desiredTriggerConfig,
|
|
1805
|
+
preserve: true
|
|
1806
|
+
} : desiredTriggerConfig,
|
|
1807
|
+
previousTriggerConfig,
|
|
1808
|
+
...previousVersionId && previousTriggerConfig ? { knownTriggerConfigs: { [previousVersionId]: previousTriggerConfig } } : {},
|
|
1809
|
+
publishEmptyCustomDomains: options.publishEmptyCustomDomains,
|
|
1810
|
+
publishEmptyCrons: options.publishEmptyCrons
|
|
1811
|
+
};
|
|
1812
|
+
const triggers = await stabilizeCloudflareVersionTriggers(triggerOptions);
|
|
1813
|
+
if (!triggers.ok || !triggers.targetOwnsTraffic) {
|
|
1814
|
+
const superseded = triggers.activeVersionId !== void 0 && triggers.activeVersionId !== null && triggers.activeVersionId !== versionId;
|
|
1815
|
+
return {
|
|
1816
|
+
ok: false,
|
|
1817
|
+
reason: superseded ? "deployment-changed" : "triggers-failed",
|
|
1818
|
+
...!triggers.ok ? { status: triggers.status } : {},
|
|
1819
|
+
versionId,
|
|
1820
|
+
previousVersionId,
|
|
1821
|
+
activeVersionId: triggers.activeVersionId,
|
|
1822
|
+
rollback: superseded ? "not-needed" : triggers.activeVersionId ? "unavailable" : "unsafe",
|
|
1823
|
+
...superseded ? { triggerRepair: triggers.ok ? "succeeded" : triggers.reason === "metadata-unavailable" ? "unavailable" : "failed" } : {}
|
|
1824
|
+
};
|
|
1825
|
+
}
|
|
1811
1826
|
const previewUrl = publishedCloudflareUrl(options.workerName, desiredTriggerConfig, reportedOrConfiguredUrl);
|
|
1827
|
+
const usesCustomDomain = !desiredTriggerConfig.workers_dev && desiredTriggerConfig.routes.some((route) => isRecord(route) && route.custom_domain === true);
|
|
1828
|
+
const customDomainBootstrap = !atomic && usesCustomDomain;
|
|
1829
|
+
const readinessTimeoutMs = customDomainBootstrap ? 3e5 : 6e4;
|
|
1812
1830
|
options.onProgress?.("Waiting for the Worker to become ready...");
|
|
1813
|
-
const ready = await probeReadiness(`${previewUrl.replace(/\/$/, "")}/__void/ready`, options.workerName,
|
|
1831
|
+
const ready = await probeReadiness(`${previewUrl.replace(/\/$/, "")}/__void/ready`, options.workerName, extractWorkersDevSubdomain(previewUrl) ? options.readinessHeaders : void 0, extractWorkersDevSubdomain(previewUrl), void 0, options.fetch ?? globalThis.fetch, options.sleep ?? defaultSleep, options.readinessAttempts ?? (customDomainBootstrap ? 150 : 30), readinessTimeoutMs, options.requireVersionMetadata ? versionId : void 0);
|
|
1814
1832
|
if (ready !== "ready") return {
|
|
1815
1833
|
ok: false,
|
|
1816
1834
|
reason: ready === "access-denied" ? atomic ? "atomic-readiness-access-denied" : "bootstrap-readiness-access-denied" : atomic ? "atomic-readiness-failed" : "bootstrap-readiness-failed",
|
|
1817
1835
|
versionId,
|
|
1818
1836
|
previousVersionId,
|
|
1819
|
-
rollback: "unavailable"
|
|
1837
|
+
rollback: "unavailable",
|
|
1838
|
+
...ready === "failed" && usesCustomDomain ? { diagnostic: "The Custom Domain may still be propagating DNS or its TLS certificate. Check the configured domain in Cloudflare, then retry `void deploy --platform cloudflare` once its /__void/ready endpoint is reachable." } : {}
|
|
1820
1839
|
};
|
|
1840
|
+
if (!options.verifyUploader()) return {
|
|
1841
|
+
ok: false,
|
|
1842
|
+
reason: "uploader-changed",
|
|
1843
|
+
versionId,
|
|
1844
|
+
previousVersionId,
|
|
1845
|
+
rollback: "unsafe"
|
|
1846
|
+
};
|
|
1847
|
+
const ownerAfterReadiness = await readCurrentProductionVersion(options, configPath);
|
|
1848
|
+
if (ownerAfterReadiness.kind === "failed") return {
|
|
1849
|
+
ok: false,
|
|
1850
|
+
reason: "status-failed",
|
|
1851
|
+
status: ownerAfterReadiness.status,
|
|
1852
|
+
versionId,
|
|
1853
|
+
previousVersionId,
|
|
1854
|
+
rollback: "unsafe"
|
|
1855
|
+
};
|
|
1856
|
+
if (ownerAfterReadiness.kind !== "ok" || ownerAfterReadiness.versionId !== versionId) {
|
|
1857
|
+
const repair = await stabilizeCloudflareVersionTriggers(triggerOptions);
|
|
1858
|
+
return {
|
|
1859
|
+
ok: false,
|
|
1860
|
+
reason: "deployment-changed",
|
|
1861
|
+
versionId,
|
|
1862
|
+
previousVersionId,
|
|
1863
|
+
activeVersionId: repair.activeVersionId,
|
|
1864
|
+
rollback: "not-needed",
|
|
1865
|
+
triggerRepair: repair.ok ? "succeeded" : repair.reason === "metadata-unavailable" ? "unavailable" : "failed"
|
|
1866
|
+
};
|
|
1867
|
+
}
|
|
1821
1868
|
return {
|
|
1822
1869
|
ok: true,
|
|
1823
1870
|
versionId,
|
|
@@ -1831,32 +1878,36 @@ async function bootstrapFirstDeployment(options, commonArgs, previousVersionId,
|
|
|
1831
1878
|
...workersDevSubdomainResult(previewUrl)
|
|
1832
1879
|
};
|
|
1833
1880
|
}
|
|
1834
|
-
function
|
|
1835
|
-
const
|
|
1836
|
-
|
|
1881
|
+
function readUploadResult(result) {
|
|
1882
|
+
const data = result.data;
|
|
1883
|
+
if (!isRecord(data) || typeof data.versionId !== "string" || !/^[0-9a-f]{8}-[0-9a-f-]{27}$/i.test(data.versionId)) return null;
|
|
1884
|
+
if (data.previewUrl !== void 0 && typeof data.previewUrl !== "string" || data.workersDevUrl !== void 0 && typeof data.workersDevUrl !== "string") return null;
|
|
1885
|
+
return data;
|
|
1886
|
+
}
|
|
1887
|
+
function versionPercentages(values) {
|
|
1888
|
+
return values.map((value) => {
|
|
1889
|
+
const [versionId, percentage] = value.split("@");
|
|
1890
|
+
return {
|
|
1891
|
+
versionId,
|
|
1892
|
+
percentage: Number(percentage)
|
|
1893
|
+
};
|
|
1894
|
+
});
|
|
1837
1895
|
}
|
|
1838
1896
|
function commandDiagnosticFields(result) {
|
|
1839
1897
|
const diagnostic = cloudflareCommandDiagnostic(result);
|
|
1840
1898
|
return diagnostic ? { diagnostic } : {};
|
|
1841
1899
|
}
|
|
1842
|
-
function
|
|
1843
|
-
return
|
|
1844
|
-
}
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
"deployments",
|
|
1852
|
-
"status",
|
|
1853
|
-
...commonArgs,
|
|
1854
|
-
"--name",
|
|
1855
|
-
options.workerName,
|
|
1856
|
-
"--json"
|
|
1857
|
-
], { capture: true }));
|
|
1900
|
+
function isContainersUnavailable(result) {
|
|
1901
|
+
return result.failure === "containers-unavailable" || (result.errors?.some(({ message }) => /Cloudflare Containers[^\n]*(?:Workers Paid|not (?:enabled|available)|subscription)/i.test(message) || /\/containers\/me(?:\b|\?)/i.test(message) && /\b403\b/.test(message)) ?? false);
|
|
1902
|
+
}
|
|
1903
|
+
async function readCurrentProductionVersion(options, configPath) {
|
|
1904
|
+
const result = await Promise.resolve(options.runWrangler({
|
|
1905
|
+
type: "deployments.status",
|
|
1906
|
+
configPath,
|
|
1907
|
+
workerName: options.workerName
|
|
1908
|
+
}, { capture: true }));
|
|
1858
1909
|
if (!isCleanCommandExit(result)) {
|
|
1859
|
-
if (
|
|
1910
|
+
if (isCompletedCommand(result) && (result.failure === "not-found" || result.errorCode === 10007)) return {
|
|
1860
1911
|
kind: "ok",
|
|
1861
1912
|
versionId: null,
|
|
1862
1913
|
versions: []
|
|
@@ -1867,7 +1918,7 @@ async function readCurrentProductionVersion(options, commonArgs) {
|
|
|
1867
1918
|
};
|
|
1868
1919
|
}
|
|
1869
1920
|
try {
|
|
1870
|
-
const versions = (
|
|
1921
|
+
const versions = (result.data.versions ?? []).map((version) => ({
|
|
1871
1922
|
versionId: typeof version.version_id === "string" ? version.version_id : "",
|
|
1872
1923
|
percentage: Number(version.percentage)
|
|
1873
1924
|
}));
|
|
@@ -1901,8 +1952,8 @@ function stagedVersionPercentages(previousVersionId, versionId) {
|
|
|
1901
1952
|
percentage: 0
|
|
1902
1953
|
}] : [];
|
|
1903
1954
|
}
|
|
1904
|
-
async function verifyReadinessStagingOwner(options,
|
|
1905
|
-
const owner = await readCurrentProductionVersion(options,
|
|
1955
|
+
async function verifyReadinessStagingOwner(options, configPath, previousVersionId, versionId) {
|
|
1956
|
+
const owner = await readCurrentProductionVersion(options, configPath);
|
|
1906
1957
|
if (owner.kind === "failed") return {
|
|
1907
1958
|
ok: false,
|
|
1908
1959
|
reason: "status-failed",
|
|
@@ -1932,7 +1983,7 @@ async function verifyReadinessStagingOwner(options, commonArgs, previousVersionI
|
|
|
1932
1983
|
};
|
|
1933
1984
|
return null;
|
|
1934
1985
|
}
|
|
1935
|
-
async function probeReadiness(url, workerName, readinessHeaders, authenticatedWorkersDevSubdomain, versionOverride, fetcher, sleep, attempts, timeoutMs = Number.POSITIVE_INFINITY) {
|
|
1986
|
+
async function probeReadiness(url, workerName, readinessHeaders, authenticatedWorkersDevSubdomain, versionOverride, fetcher, sleep, attempts, timeoutMs = Number.POSITIVE_INFINITY, expectedVersionId) {
|
|
1936
1987
|
const headers = guardedReadinessHeaders(url, workerName, readinessHeaders, authenticatedWorkersDevSubdomain);
|
|
1937
1988
|
if (headers === null) return "failed";
|
|
1938
1989
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -1951,7 +2002,8 @@ async function probeReadiness(url, workerName, readinessHeaders, authenticatedWo
|
|
|
1951
2002
|
signal: AbortSignal.timeout(Math.max(1, Math.min(1e4, remaining)))
|
|
1952
2003
|
});
|
|
1953
2004
|
if (response.ok) {
|
|
1954
|
-
|
|
2005
|
+
const body = await response.json();
|
|
2006
|
+
if (body.ready === true && (expectedVersionId === void 0 || body.versionId === expectedVersionId)) return "ready";
|
|
1955
2007
|
} else if (await isCloudflareAccessDenial(response)) return "access-denied";
|
|
1956
2008
|
} catch {} finally {
|
|
1957
2009
|
if (response && !response.bodyUsed) await response.body?.cancel().catch(() => {});
|
|
@@ -1987,7 +2039,7 @@ async function isCloudflareAccessDenial(response) {
|
|
|
1987
2039
|
return false;
|
|
1988
2040
|
}
|
|
1989
2041
|
}
|
|
1990
|
-
async function deployVersionPercentages(options,
|
|
2042
|
+
async function deployVersionPercentages(options, configPath, percentages, message) {
|
|
1991
2043
|
const metadata = parseCloudflareVersionMessage(message);
|
|
1992
2044
|
if ((options.preserveTriggers || metadata?.schema === 2 && metadata.triggerConfig.preserve) && options.configPath) return deployCloudflareVersionTraffic({
|
|
1993
2045
|
...options,
|
|
@@ -1995,17 +2047,13 @@ async function deployVersionPercentages(options, commonArgs, percentages, messag
|
|
|
1995
2047
|
percentages,
|
|
1996
2048
|
message
|
|
1997
2049
|
});
|
|
1998
|
-
return options.runWrangler(
|
|
1999
|
-
"versions",
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
"--message",
|
|
2006
|
-
message,
|
|
2007
|
-
"--yes"
|
|
2008
|
-
], { capture: false });
|
|
2050
|
+
return options.runWrangler({
|
|
2051
|
+
type: "versions.deploy",
|
|
2052
|
+
versions: versionPercentages(percentages),
|
|
2053
|
+
configPath,
|
|
2054
|
+
workerName: options.workerName,
|
|
2055
|
+
message
|
|
2056
|
+
}, { capture: false });
|
|
2009
2057
|
}
|
|
2010
2058
|
/** Change version traffic without synchronizing Worker settings from local config. */
|
|
2011
2059
|
async function deployCloudflareVersionTraffic(options) {
|
|
@@ -2014,19 +2062,13 @@ async function deployCloudflareVersionTraffic(options) {
|
|
|
2014
2062
|
const path = join(directory, "wrangler.json");
|
|
2015
2063
|
try {
|
|
2016
2064
|
writeFileSync(path, JSON.stringify(config), { mode: 384 });
|
|
2017
|
-
return await options.runWrangler(
|
|
2018
|
-
"versions",
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
"--name",
|
|
2025
|
-
options.workerName,
|
|
2026
|
-
"--message",
|
|
2027
|
-
options.message,
|
|
2028
|
-
"--yes"
|
|
2029
|
-
], { capture: false });
|
|
2065
|
+
return await options.runWrangler({
|
|
2066
|
+
type: "versions.deploy",
|
|
2067
|
+
versions: versionPercentages(options.percentages),
|
|
2068
|
+
configPath: path,
|
|
2069
|
+
workerName: options.workerName,
|
|
2070
|
+
message: options.message
|
|
2071
|
+
}, { capture: false });
|
|
2030
2072
|
} finally {
|
|
2031
2073
|
rmSync(directory, {
|
|
2032
2074
|
recursive: true,
|
|
@@ -2034,12 +2076,12 @@ async function deployCloudflareVersionTraffic(options) {
|
|
|
2034
2076
|
});
|
|
2035
2077
|
}
|
|
2036
2078
|
}
|
|
2037
|
-
async function restorePreviousVersion(options,
|
|
2079
|
+
async function restorePreviousVersion(options, configPath, previousVersionId, cause, expectedOwnedTraffic) {
|
|
2038
2080
|
if (!previousVersionId) return "unavailable";
|
|
2039
2081
|
if (!options.verifyUploader()) return "unsafe";
|
|
2040
|
-
const owner = await readCurrentProductionVersion(options,
|
|
2082
|
+
const owner = await readCurrentProductionVersion(options, configPath);
|
|
2041
2083
|
if (owner.kind !== "ok" || !expectedOwnedTraffic.some((expected) => productionVersionPercentagesEqual(owner.versions, expected))) return "unsafe";
|
|
2042
|
-
return isCleanCommandExit(await deployVersionPercentages(options,
|
|
2084
|
+
return isCleanCommandExit(await deployVersionPercentages(options, configPath, [`${previousVersionId}@100`], `Void automatic rollback after ${cause}.`)) ? "succeeded" : "failed";
|
|
2043
2085
|
}
|
|
2044
2086
|
function workersDevReadinessUrl(workerName, value) {
|
|
2045
2087
|
const subdomain = normalizeWorkersDevSubdomain(value);
|
|
@@ -2073,8 +2115,11 @@ function workersDevSubdomainResult(url) {
|
|
|
2073
2115
|
const workersDevSubdomain = extractWorkersDevSubdomain(url);
|
|
2074
2116
|
return workersDevSubdomain ? { workersDevSubdomain } : {};
|
|
2075
2117
|
}
|
|
2118
|
+
function isCompletedCommand(result) {
|
|
2119
|
+
return Number.isSafeInteger(result.status) && result.status >= 0 && result.signal == null && (result.error == null || result.error === "");
|
|
2120
|
+
}
|
|
2076
2121
|
function isCleanCommandExit(result) {
|
|
2077
|
-
return result.status === 0 &&
|
|
2122
|
+
return result.status === 0 && isCompletedCommand(result);
|
|
2078
2123
|
}
|
|
2079
2124
|
function defaultSleep(milliseconds) {
|
|
2080
2125
|
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
@@ -2088,13 +2133,6 @@ function defaultSleep(milliseconds) {
|
|
|
2088
2133
|
*/
|
|
2089
2134
|
function createCloudflareMetadataRunner(options) {
|
|
2090
2135
|
let tokenPromise;
|
|
2091
|
-
const common = [
|
|
2092
|
-
...options.configPath ? ["-c", options.configPath] : [],
|
|
2093
|
-
"--env=",
|
|
2094
|
-
"--name",
|
|
2095
|
-
options.workerName,
|
|
2096
|
-
"--json"
|
|
2097
|
-
];
|
|
2098
2136
|
const prefix = `${options.apiBaseUrl.replace(/\/$/, "")}/accounts/${encodeURIComponent(options.accountId)}/workers/scripts/${encodeURIComponent(options.workerName)}`;
|
|
2099
2137
|
const fetcher = options.fetch ?? globalThis.fetch;
|
|
2100
2138
|
const proxyEnv = options.env ?? process.env;
|
|
@@ -2105,26 +2143,22 @@ function createCloudflareMetadataRunner(options) {
|
|
|
2105
2143
|
stderr: "Cloudflare uploader integrity check failed.",
|
|
2106
2144
|
error: "uploader-changed"
|
|
2107
2145
|
};
|
|
2108
|
-
const fallback = (
|
|
2109
|
-
return async (
|
|
2146
|
+
const fallback = (operation, runOptions) => options.verifyUploader() ? options.runWrangler(operation, runOptions) : refused;
|
|
2147
|
+
return async (operation, runOptions) => {
|
|
2110
2148
|
let kind;
|
|
2111
2149
|
let resource;
|
|
2112
|
-
|
|
2113
|
-
if (args[0] === "deployments" && args[1] === "status") {
|
|
2150
|
+
if (operation.type === "deployments.status") {
|
|
2114
2151
|
kind = "deployment";
|
|
2115
2152
|
resource = "/deployments";
|
|
2116
|
-
|
|
2117
|
-
} else if (args[0] === "versions" && args[1] === "list") {
|
|
2153
|
+
} else if (operation.type === "versions.list") {
|
|
2118
2154
|
kind = "versions";
|
|
2119
2155
|
resource = "/versions?deployable=true";
|
|
2120
|
-
|
|
2121
|
-
} else if (args[0] === "versions" && args[1] === "view" && /^[0-9a-f-]{36}$/i.test(args[2] ?? "")) {
|
|
2156
|
+
} else if (operation.type === "versions.view" && /^[0-9a-f-]{36}$/i.test(operation.versionId)) {
|
|
2122
2157
|
kind = "version";
|
|
2123
|
-
resource =
|
|
2124
|
-
rest = args.slice(3);
|
|
2158
|
+
resource = "/versions/" + encodeURIComponent(operation.versionId);
|
|
2125
2159
|
}
|
|
2126
|
-
if (!kind || !runOptions.capture ||
|
|
2127
|
-
if (requiresProxyTransport) return fallback(
|
|
2160
|
+
if (!kind || !runOptions.capture || !("workerName" in operation) || operation.workerName !== options.workerName || operation.configPath !== options.configPath) return fallback(operation, runOptions);
|
|
2161
|
+
if (requiresProxyTransport) return fallback(operation, runOptions);
|
|
2128
2162
|
let response;
|
|
2129
2163
|
try {
|
|
2130
2164
|
if (!tokenPromise && !options.verifyUploader()) return refused;
|
|
@@ -2145,29 +2179,30 @@ function createCloudflareMetadataRunner(options) {
|
|
|
2145
2179
|
if (result.deployments.length === 0) return {
|
|
2146
2180
|
status: 1,
|
|
2147
2181
|
stdout: "",
|
|
2148
|
-
stderr: `The Worker ${options.workerName} has no deployments
|
|
2182
|
+
stderr: `The Worker ${options.workerName} has no deployments.`,
|
|
2183
|
+
errorCode: 10007
|
|
2149
2184
|
};
|
|
2150
2185
|
if (record(latest) && Array.isArray(latest.versions)) return {
|
|
2151
2186
|
status: 0,
|
|
2152
|
-
stdout:
|
|
2187
|
+
stdout: "",
|
|
2188
|
+
data: latest,
|
|
2153
2189
|
stderr: ""
|
|
2154
2190
|
};
|
|
2155
2191
|
}
|
|
2156
|
-
if (
|
|
2192
|
+
if (operation.type === "versions.view" && result.id === operation.versionId) return {
|
|
2157
2193
|
status: 0,
|
|
2158
|
-
stdout:
|
|
2194
|
+
stdout: "",
|
|
2195
|
+
data: result,
|
|
2159
2196
|
stderr: ""
|
|
2160
2197
|
};
|
|
2161
2198
|
if (kind === "versions" && Array.isArray(result.items)) {
|
|
2162
2199
|
const items = result.items;
|
|
2163
|
-
if (items.every((item) => record(item) && record(item.metadata) && typeof item.metadata.created_on === "string")) {
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
};
|
|
2170
|
-
}
|
|
2200
|
+
if (items.every((item) => record(item) && record(item.metadata) && typeof item.metadata.created_on === "string")) return {
|
|
2201
|
+
status: 0,
|
|
2202
|
+
stdout: "",
|
|
2203
|
+
data: items.sort((a, b) => a.metadata.created_on.localeCompare(b.metadata.created_on)).slice(-10),
|
|
2204
|
+
stderr: ""
|
|
2205
|
+
};
|
|
2171
2206
|
}
|
|
2172
2207
|
}
|
|
2173
2208
|
}
|
|
@@ -2177,7 +2212,7 @@ function createCloudflareMetadataRunner(options) {
|
|
|
2177
2212
|
} finally {
|
|
2178
2213
|
if (response && !response.bodyUsed) await response.body?.cancel().catch(() => {});
|
|
2179
2214
|
}
|
|
2180
|
-
return fallback(
|
|
2215
|
+
return fallback(operation, runOptions);
|
|
2181
2216
|
};
|
|
2182
2217
|
}
|
|
2183
2218
|
function record(value) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@void/deploy-cloudflare",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/voidzero-dev/void.git",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"access": "public"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@void/deploy-core": "0.
|
|
23
|
+
"@void/deploy-core": "0.23.0",
|
|
24
24
|
"jsonc-parser": "3.3.1"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|