@void/deploy-cloudflare 0.23.0 → 0.25.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/README.md +2 -25
- package/dist/index.d.mts +5 -1
- package/dist/index.mjs +232 -32
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,28 +1,5 @@
|
|
|
1
1
|
# @void/deploy-cloudflare
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Cloudflare deployment primitives used by the Void CLI. Provides Worker configuration reads, uploads, activation, and deployment verification.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
Cloudflare upload boundary. Source inference and app builds remain in `void`;
|
|
7
|
-
provider-neutral contracts and plans remain in `@void/deploy-core`.
|
|
8
|
-
|
|
9
|
-
Existing Worker handoffs read the active version, validate resource identities,
|
|
10
|
-
and check the uploaded bindings before activation. Traffic-only activation uses
|
|
11
|
-
an isolated config to retain Worker settings and triggers. Trigger snapshots
|
|
12
|
-
record when configuration was preserved, so rollback can keep those settings
|
|
13
|
-
for versions originally deployed outside Void.
|
|
14
|
-
|
|
15
|
-
Handoff verifiers can accept an explicit list of new KV binding identities.
|
|
16
|
-
Only those exact namespaces may be added; existing bindings, variables, secrets,
|
|
17
|
-
and handlers must remain unchanged. New bindings cannot alias an existing KV
|
|
18
|
-
namespace. The Void CLI uses this for the migration's explicit ISR cache choice.
|
|
19
|
-
|
|
20
|
-
First-deployment readiness allows up to one minute for the new Worker's public
|
|
21
|
-
route to propagate, with bounded requests and backoff. A successful check still
|
|
22
|
-
requires the ready response, and Cloudflare Access denials fail immediately.
|
|
23
|
-
Subsequent version previews retain the shorter retry window.
|
|
24
|
-
|
|
25
|
-
Failed version uploads and first-deployment bootstraps return a bounded diagnostic
|
|
26
|
-
alongside the exit status and Cloudflare error code. It retains validation messages
|
|
27
|
-
and stack locations, strips terminal formatting, excludes stdout binding tables,
|
|
28
|
-
and redacts common credential formats before callers display or log it.
|
|
5
|
+
Applications normally deploy with `void deploy --platform cloudflare`. See the [Cloudflare guide](https://void.cloud/integrations/cloudflare) for configuration and credentials, or [Platform Development](https://void.cloud/guide/platform-development) for runtime and adapter work.
|
package/dist/index.d.mts
CHANGED
|
@@ -66,6 +66,7 @@ type CloudflareOperation = ({
|
|
|
66
66
|
} & WorkerTarget) | ({
|
|
67
67
|
type: "versions.view";
|
|
68
68
|
versionId: string;
|
|
69
|
+
includeSource?: boolean;
|
|
69
70
|
} & WorkerTarget) | ({
|
|
70
71
|
type: "versions.deploy";
|
|
71
72
|
versions: Array<{
|
|
@@ -429,7 +430,7 @@ type CloudflareVersionActivationOptions = {
|
|
|
429
430
|
requirePreviewForReadiness?: boolean;
|
|
430
431
|
/** Verify that omitted remote secrets come from the active Worker Version before uploading. */
|
|
431
432
|
verifyInheritedSecrets?: boolean;
|
|
432
|
-
/** Pin opaque secret bindings to the live version
|
|
433
|
+
/** Pin inherited opaque secret bindings to the inspected live version before uploading. */
|
|
433
434
|
pinActiveSecrets?: (activeVersionId: string, bindings: ReadonlyArray<{
|
|
434
435
|
name: string;
|
|
435
436
|
type: "secret_text" | "secret_key";
|
|
@@ -477,4 +478,7 @@ export declare function createCloudflareMetadataRunner(options: {
|
|
|
477
478
|
env?: Readonly<Record<string, string | undefined>>;
|
|
478
479
|
}): CloudflareCommandRunner;
|
|
479
480
|
//#endregion
|
|
481
|
+
//#region src/secret-draft.d.ts
|
|
482
|
+
export declare const VOID_SECRET_DRAFT_SOURCE = "export default { fetch() { return new Response(\"\"); } };\n";
|
|
483
|
+
//#endregion
|
|
480
484
|
export type { ApprovedCloudflareKvBinding, CloudflareCommandResult, CloudflareCommandRunner, CloudflareDeployExecution, CloudflareFrameworkDeployConfig, CloudflareOperation, CloudflareProviderError, CloudflareStaticDeployConfig, CloudflareTrafficConfig, CloudflareTriggerConfig, CloudflareTriggerStabilization, CloudflareUploadResult, CloudflareVersionActivation, CloudflareVersionActivationOptions, CloudflareVersionMetadata, CloudflareWorkerMigrationTagOptions, ExistingCloudflareWorker };
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isDeepStrictEqual, stripVTControlCharacters } from "node:util";
|
|
2
2
|
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { SyntaxKind, applyEdits, createScanner, modify, parse, parseTree } from "jsonc-parser";
|
|
4
|
+
import { isRecord } from "@void/deploy-core";
|
|
4
5
|
import nodePath from "node:path";
|
|
5
6
|
import { createHash } from "node:crypto";
|
|
6
7
|
import { tmpdir } from "node:os";
|
|
@@ -88,19 +89,53 @@ function resolveD1MigrateTarget(config, binding) {
|
|
|
88
89
|
* application code configuration or non-secret resource that an ordinary deploy could own.
|
|
89
90
|
*/
|
|
90
91
|
function isWranglerSecretOnlyDraft(entry) {
|
|
91
|
-
if (!isRecord
|
|
92
|
+
if (!isRecord(entry.annotations)) return false;
|
|
92
93
|
const annotationKeys = Object.keys(entry.annotations);
|
|
93
94
|
if (entry.annotations["workers/triggered_by"] !== "secret" || annotationKeys.some((key) => key !== "workers/triggered_by")) return false;
|
|
94
|
-
if (!isRecord
|
|
95
|
+
if (!isRecord(entry.resources)) return false;
|
|
95
96
|
const { bindings, script, script_runtime: scriptRuntime } = entry.resources;
|
|
96
|
-
if (!Array.isArray(bindings) || bindings.length === 0 || !bindings.every((binding) => isRecord
|
|
97
|
-
if (!isRecord
|
|
98
|
-
if (!isRecord
|
|
97
|
+
if (!Array.isArray(bindings) || bindings.length === 0 || !bindings.every((binding) => isRecord(binding) && typeof binding.name === "string" && binding.name.length > 0 && binding.type === "secret_text" && Object.keys(binding).every((key) => key === "name" || key === "type"))) return false;
|
|
98
|
+
if (!isRecord(script) || !Array.isArray(script.handlers) || script.handlers.length !== 1 || script.handlers[0] !== "fetch" || script.placement_mode !== void 0) return false;
|
|
99
|
+
if (!isRecord(scriptRuntime)) return false;
|
|
99
100
|
const runtimeKeys = Object.keys(scriptRuntime);
|
|
100
|
-
return scriptRuntime.usage_model === "standard" && (scriptRuntime.limits === void 0 || isRecord
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
return scriptRuntime.usage_model === "standard" && (scriptRuntime.limits === void 0 || isRecord(scriptRuntime.limits) && Object.keys(scriptRuntime.limits).length === 0) && runtimeKeys.every((key) => key === "usage_model" || key === "limits");
|
|
102
|
+
}
|
|
103
|
+
const VOID_SECRET_DRAFT_SOURCE = "export default { fetch() { return new Response(\"\"); } };\n";
|
|
104
|
+
/** Prove a cf-created placeholder from the exact immutable version's source. */
|
|
105
|
+
function isVoidSecretOnlyDraft(entry) {
|
|
106
|
+
const metadataFields = /* @__PURE__ */ new Set([
|
|
107
|
+
"id",
|
|
108
|
+
"number",
|
|
109
|
+
"startup_time_ms",
|
|
110
|
+
"created_on",
|
|
111
|
+
"source",
|
|
112
|
+
"author_id",
|
|
113
|
+
"author_email",
|
|
114
|
+
"urls",
|
|
115
|
+
"annotations",
|
|
116
|
+
"compatibility_date",
|
|
117
|
+
"usage_model",
|
|
118
|
+
"main_module",
|
|
119
|
+
"modules",
|
|
120
|
+
"bindings",
|
|
121
|
+
"env",
|
|
122
|
+
"resources"
|
|
123
|
+
]);
|
|
124
|
+
if (Object.keys(entry).some((key) => !metadataFields.has(key)) || entry.source !== "cf_cli" || !isRecord(entry.annotations) || Object.keys(entry.annotations).length !== 1 || entry.annotations["workers/triggered_by"] !== "secret" || entry.main_module !== "index.js" || !Array.isArray(entry.urls) || entry.urls.length !== 0 || !Array.isArray(entry.modules) || entry.modules.length !== 1 || !Array.isArray(entry.bindings) || entry.bindings.length === 0) return false;
|
|
125
|
+
const module = entry.modules[0];
|
|
126
|
+
if (!isRecord(module) || Object.keys(module).length !== 3 || module.name !== "index.js" || module.content_type !== "application/javascript+module" || module.content_base64 !== Buffer.from("export default { fetch() { return new Response(\"\"); } };\n").toString("base64")) return false;
|
|
127
|
+
const names = /* @__PURE__ */ new Set();
|
|
128
|
+
for (const binding of entry.bindings) {
|
|
129
|
+
if (!isRecord(binding) || Object.keys(binding).length !== 2 || typeof binding.name !== "string" || binding.name.length === 0 || names.has(binding.name) || binding.type !== "secret_text") return false;
|
|
130
|
+
names.add(binding.name);
|
|
131
|
+
}
|
|
132
|
+
if (entry.env !== void 0 && (!isRecord(entry.env) || Object.keys(entry.env).length !== names.size || Object.entries(entry.env).some(([name, binding]) => !names.has(name) || !isRecord(binding) || Object.keys(binding).length !== 1 || binding.type !== "secret_text"))) return false;
|
|
133
|
+
if (entry.resources !== void 0) {
|
|
134
|
+
if (!isRecord(entry.resources) || Object.keys(entry.resources).length !== 1) return false;
|
|
135
|
+
const bindings = entry.resources.bindings;
|
|
136
|
+
if (!Array.isArray(bindings) || !isDeepStrictEqual(bindings, entry.bindings)) return false;
|
|
137
|
+
}
|
|
138
|
+
return true;
|
|
104
139
|
}
|
|
105
140
|
//#endregion
|
|
106
141
|
//#region src/existing.ts
|
|
@@ -114,7 +149,7 @@ async function readExistingCloudflareWorker(options) {
|
|
|
114
149
|
if (!worker) throw new Error(`Cloudflare Worker "${options.workerName}" does not have an active version.`);
|
|
115
150
|
return worker.snapshot;
|
|
116
151
|
}
|
|
117
|
-
async function readCloudflareWorker(options, allowMissing) {
|
|
152
|
+
async function readCloudflareWorker(options, allowMissing, proveSecretDraft = false) {
|
|
118
153
|
const target = {
|
|
119
154
|
configPath: options.configPath,
|
|
120
155
|
workerName: options.workerName
|
|
@@ -151,6 +186,16 @@ async function readCloudflareWorker(options, allowMissing) {
|
|
|
151
186
|
if (version === null) throw new Error("Cloudflare did not return the existing Worker configuration.");
|
|
152
187
|
const resources = version.resources;
|
|
153
188
|
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.");
|
|
189
|
+
let isSecretOnlyDraft = isWranglerSecretOnlyDraft(version);
|
|
190
|
+
if (proveSecretDraft && !isSecretOnlyDraft && record$1(version.annotations) && Object.keys(version.annotations).length === 1 && version.annotations["workers/triggered_by"] === "secret") {
|
|
191
|
+
const source = await read({
|
|
192
|
+
type: "versions.view",
|
|
193
|
+
versionId,
|
|
194
|
+
includeSource: true,
|
|
195
|
+
...target
|
|
196
|
+
});
|
|
197
|
+
if (source?.id === versionId) isSecretOnlyDraft = isVoidSecretOnlyDraft(source);
|
|
198
|
+
}
|
|
154
199
|
const after = options.versionId ? deployment : await read({
|
|
155
200
|
type: "deployments.status",
|
|
156
201
|
...target
|
|
@@ -163,7 +208,7 @@ async function readCloudflareWorker(options, allowMissing) {
|
|
|
163
208
|
runtime: resources.script_runtime,
|
|
164
209
|
script: resources.script
|
|
165
210
|
},
|
|
166
|
-
isSecretOnlyDraft
|
|
211
|
+
isSecretOnlyDraft
|
|
167
212
|
};
|
|
168
213
|
}
|
|
169
214
|
/** Parse legacy version-detail shapes; this is not authoritative Worker migration state. */
|
|
@@ -231,7 +276,7 @@ async function readCloudflareApiAuthentication(runWrangler) {
|
|
|
231
276
|
async function assertCloudflareDurableObjectLifecycleSupported(options) {
|
|
232
277
|
const migrations = parseDurableObjectMigrations(parseRootWranglerConfig(options.configPath).migrations);
|
|
233
278
|
if (!migrations.some((migration) => migration.changesClasses)) return;
|
|
234
|
-
const existing = await readCloudflareWorker(options, true);
|
|
279
|
+
const existing = await readCloudflareWorker(options, true, true);
|
|
235
280
|
if (!existing) return;
|
|
236
281
|
const readMigrationTag = options.readMigrationTag ?? readCloudflareWorkerMigrationTag;
|
|
237
282
|
const migrationOptions = {
|
|
@@ -241,7 +286,7 @@ async function assertCloudflareDurableObjectLifecycleSupported(options) {
|
|
|
241
286
|
workerName: options.workerName
|
|
242
287
|
};
|
|
243
288
|
const activeTag = await readMigrationTag(migrationOptions);
|
|
244
|
-
const after = await readCloudflareWorker(options, false);
|
|
289
|
+
const after = await readCloudflareWorker(options, false, true);
|
|
245
290
|
const afterTag = await readMigrationTag(migrationOptions);
|
|
246
291
|
if (!after || after.snapshot.versionId !== existing.snapshot.versionId || afterTag !== activeTag) throw new Error("Cloudflare production or Durable Object migration state changed during verification. Retry from the current deployment.");
|
|
247
292
|
if (activeTag === null && existing.isSecretOnlyDraft && after.isSecretOnlyDraft) return;
|
|
@@ -1011,9 +1056,10 @@ function hasCustomDomains(routes) {
|
|
|
1011
1056
|
/** Select a configured HTTPS route that is guaranteed to cover /__void/ready. */
|
|
1012
1057
|
function customRouteReadinessUrl(routes) {
|
|
1013
1058
|
for (const route of routes) {
|
|
1014
|
-
const
|
|
1059
|
+
const record = isRecord(route) ? route : void 0;
|
|
1060
|
+
const pattern = typeof route === "string" ? route : typeof record?.pattern === "string" ? record.pattern : void 0;
|
|
1015
1061
|
if (!pattern) continue;
|
|
1016
|
-
const customDomain =
|
|
1062
|
+
const customDomain = record?.custom_domain === true;
|
|
1017
1063
|
const withoutScheme = pattern.replace(/^https?:\/\//i, "");
|
|
1018
1064
|
const slash = withoutScheme.indexOf("/");
|
|
1019
1065
|
const hostname = (slash < 0 ? withoutScheme : withoutScheme.slice(0, slash)).trim();
|
|
@@ -1061,7 +1107,6 @@ function stableJson(value) {
|
|
|
1061
1107
|
return JSON.stringify(value);
|
|
1062
1108
|
}
|
|
1063
1109
|
function parseTriggerConfig(value) {
|
|
1064
|
-
if (value === void 0) return null;
|
|
1065
1110
|
if (!isRecord(value)) return null;
|
|
1066
1111
|
if ([
|
|
1067
1112
|
"preserve",
|
|
@@ -1118,9 +1163,6 @@ function resolvePreservedTraffic(desired, previous, workerExists) {
|
|
|
1118
1163
|
...desired.addresses === void 0 ? {} : { addresses: desired.addresses }
|
|
1119
1164
|
};
|
|
1120
1165
|
}
|
|
1121
|
-
function isRecord(value) {
|
|
1122
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1123
|
-
}
|
|
1124
1166
|
function isCandidatePreviewUrl(value, workerName, versionId, alias) {
|
|
1125
1167
|
try {
|
|
1126
1168
|
const url = new URL(value);
|
|
@@ -1188,6 +1230,7 @@ async function activateCloudflareVersionWithSecretsFile(options) {
|
|
|
1188
1230
|
let inheritanceBaseline;
|
|
1189
1231
|
let trustedCandidateChain = false;
|
|
1190
1232
|
let activeSecretsPinned = false;
|
|
1233
|
+
let pinnedActiveSecrets;
|
|
1191
1234
|
if (options.expectedPreviousVersionId !== void 0 || options.verifyInheritedSecrets === true) {
|
|
1192
1235
|
const recent = await readRecentCloudflareVersionIds(options, configPath);
|
|
1193
1236
|
if (recent === null) return {
|
|
@@ -1222,6 +1265,7 @@ async function activateCloudflareVersionWithSecretsFile(options) {
|
|
|
1222
1265
|
rollback: "not-needed"
|
|
1223
1266
|
};
|
|
1224
1267
|
activeSecretsPinned = true;
|
|
1268
|
+
pinnedActiveSecrets = activeSecrets;
|
|
1225
1269
|
}
|
|
1226
1270
|
}
|
|
1227
1271
|
inheritanceBaseline = recent;
|
|
@@ -1263,15 +1307,106 @@ async function activateCloudflareVersionWithSecretsFile(options) {
|
|
|
1263
1307
|
} : versionedPreviousTriggerConfig;
|
|
1264
1308
|
const previousTriggerConfig = triggerConfigWithQueueConsumerCleanup(versionedPreviousTriggerConfigWithTraffic, options.queueConsumerCleanup);
|
|
1265
1309
|
const alias = `void-ready-${Date.now().toString(36)}`;
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1310
|
+
const bootstrapVoidDraft = !options.preserveTriggers && !options.requirePreviewForReadiness && previousVersion?.isVoidSecretOnlyDraft === true;
|
|
1311
|
+
if (previous.versionId !== null && (options.atomic || bootstrapVoidDraft)) {
|
|
1312
|
+
if (bootstrapVoidDraft && !activeSecretsPinned && !options.pinActiveSecrets) return {
|
|
1313
|
+
ok: false,
|
|
1314
|
+
reason: "inheritance-source-unavailable"
|
|
1315
|
+
};
|
|
1316
|
+
if (bootstrapVoidDraft || !activeSecretsPinned && options.pinActiveSecrets) {
|
|
1317
|
+
let activeSecrets;
|
|
1318
|
+
try {
|
|
1319
|
+
activeSecrets = await readCloudflareVersionSecretBindings(options, configPath, previous.versionId);
|
|
1320
|
+
} catch {
|
|
1321
|
+
return {
|
|
1322
|
+
ok: false,
|
|
1323
|
+
reason: "inheritance-source-unavailable"
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
if (activeSecrets === null) return {
|
|
1327
|
+
ok: false,
|
|
1328
|
+
reason: "inheritance-source-unavailable"
|
|
1329
|
+
};
|
|
1330
|
+
if (bootstrapVoidDraft && (activeSecrets.length === 0 || activeSecrets.some((secret) => secret.type !== "secret_text") || !arraysEqual(activeSecrets.map((secret) => secret.name).sort(), [...previousVersion.voidDraftSecretNames].sort()) || activeSecretsPinned && !isDeepStrictEqual([...activeSecrets].sort((a, b) => a.name.localeCompare(b.name)), [...pinnedActiveSecrets].sort((a, b) => a.name.localeCompare(b.name))))) return {
|
|
1331
|
+
ok: false,
|
|
1332
|
+
reason: "inheritance-source-changed"
|
|
1333
|
+
};
|
|
1334
|
+
if (!activeSecretsPinned && activeSecrets.length) {
|
|
1335
|
+
let pinned;
|
|
1336
|
+
try {
|
|
1337
|
+
pinned = await options.pinActiveSecrets(previous.versionId, activeSecrets);
|
|
1338
|
+
} catch {
|
|
1339
|
+
return {
|
|
1340
|
+
ok: false,
|
|
1341
|
+
reason: "inheritance-source-unavailable"
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
if (!pinned) return {
|
|
1345
|
+
ok: false,
|
|
1346
|
+
reason: "inheritance-source-changed"
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1270
1349
|
}
|
|
1271
1350
|
return bootstrapFirstDeployment({
|
|
1272
1351
|
...options,
|
|
1273
1352
|
message
|
|
1274
|
-
}, configPath, previous.versionId, true, previousTriggerConfig);
|
|
1353
|
+
}, configPath, previous.versionId, options.atomic === true, previousTriggerConfig);
|
|
1354
|
+
}
|
|
1355
|
+
if (previous.versionId !== null && !activeSecretsPinned && options.pinActiveSecrets && options.candidateSecretsFilePath === void 0) {
|
|
1356
|
+
let activeSecrets;
|
|
1357
|
+
try {
|
|
1358
|
+
activeSecrets = await readCloudflareVersionSecretBindings(options, configPath, previous.versionId);
|
|
1359
|
+
} catch {
|
|
1360
|
+
return {
|
|
1361
|
+
ok: false,
|
|
1362
|
+
reason: "inheritance-source-unavailable"
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
if (activeSecrets === null) return {
|
|
1366
|
+
ok: false,
|
|
1367
|
+
reason: "inheritance-source-unavailable"
|
|
1368
|
+
};
|
|
1369
|
+
if (activeSecrets.some((secret) => !secret.name.trim() || secret.name.trim() !== secret.name) || new Set(activeSecrets.map((secret) => secret.name)).size !== activeSecrets.length) return {
|
|
1370
|
+
ok: false,
|
|
1371
|
+
reason: "inheritance-source-changed"
|
|
1372
|
+
};
|
|
1373
|
+
if (activeSecrets.length) {
|
|
1374
|
+
let pinned;
|
|
1375
|
+
try {
|
|
1376
|
+
pinned = await options.pinActiveSecrets(previous.versionId, activeSecrets);
|
|
1377
|
+
} catch {
|
|
1378
|
+
return {
|
|
1379
|
+
ok: false,
|
|
1380
|
+
reason: "inheritance-source-unavailable"
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
if (!pinned) return {
|
|
1384
|
+
ok: false,
|
|
1385
|
+
reason: "inheritance-source-changed"
|
|
1386
|
+
};
|
|
1387
|
+
activeSecretsPinned = true;
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
if (activeSecretsPinned) {
|
|
1391
|
+
if (!options.verifyUploader()) return {
|
|
1392
|
+
ok: false,
|
|
1393
|
+
reason: "uploader-changed"
|
|
1394
|
+
};
|
|
1395
|
+
const owner = await readCurrentProductionVersion(options, configPath);
|
|
1396
|
+
if (owner.kind === "failed") return {
|
|
1397
|
+
ok: false,
|
|
1398
|
+
reason: "status-failed",
|
|
1399
|
+
status: owner.status
|
|
1400
|
+
};
|
|
1401
|
+
if (owner.kind !== "ok" || owner.versionId !== previous.versionId || !productionVersionPercentagesEqual(owner.versions, [{
|
|
1402
|
+
versionId: previous.versionId,
|
|
1403
|
+
percentage: 100
|
|
1404
|
+
}])) return {
|
|
1405
|
+
ok: false,
|
|
1406
|
+
reason: "deployment-changed",
|
|
1407
|
+
activeVersionId: owner.kind === "ok" ? owner.versionId : null,
|
|
1408
|
+
rollback: "not-needed"
|
|
1409
|
+
};
|
|
1275
1410
|
}
|
|
1276
1411
|
options.onProgress?.("Uploading the Worker and assets...");
|
|
1277
1412
|
const upload = await Promise.resolve(options.runWrangler({
|
|
@@ -1647,21 +1782,25 @@ async function readCloudflareVersionInspection(options, configPath, versionId, k
|
|
|
1647
1782
|
const annotations = isRecord(entry.annotations) ? entry.annotations : void 0;
|
|
1648
1783
|
const message = annotations?.["workers/message"];
|
|
1649
1784
|
let draft = entry;
|
|
1650
|
-
if (annotations?.["workers/triggered_by"] === "secret" && Object.keys(annotations).length === 1 && !
|
|
1785
|
+
if (annotations?.["workers/triggered_by"] === "secret" && Object.keys(annotations).length === 1 && !isWranglerSecretOnlyDraft(entry)) {
|
|
1651
1786
|
const inspection = await Promise.resolve(options.runWrangler({
|
|
1652
1787
|
type: "versions.view",
|
|
1653
1788
|
versionId,
|
|
1654
1789
|
configPath,
|
|
1655
|
-
workerName: options.workerName
|
|
1790
|
+
workerName: options.workerName,
|
|
1791
|
+
includeSource: true
|
|
1656
1792
|
}, { capture: true }));
|
|
1657
1793
|
if (isCleanCommandExit(inspection)) {
|
|
1658
1794
|
const inspected = inspection.data;
|
|
1659
1795
|
if (isRecord(inspected) && inspected.id === versionId) draft = inspected;
|
|
1660
1796
|
}
|
|
1661
1797
|
}
|
|
1798
|
+
const isVoidDraft = isVoidSecretOnlyDraft(draft);
|
|
1662
1799
|
return {
|
|
1663
1800
|
metadata: typeof message === "string" ? parseCloudflareVersionMessage(message) : null,
|
|
1664
|
-
isSecretOnlyDraft: isWranglerSecretOnlyDraft(draft)
|
|
1801
|
+
isSecretOnlyDraft: isWranglerSecretOnlyDraft(draft) || isVoidDraft,
|
|
1802
|
+
isVoidSecretOnlyDraft: isVoidDraft,
|
|
1803
|
+
voidDraftSecretNames: isVoidDraft ? draft.bindings.map((binding) => binding.name) : null
|
|
1665
1804
|
};
|
|
1666
1805
|
}
|
|
1667
1806
|
} catch {
|
|
@@ -1742,14 +1881,17 @@ async function bootstrapFirstDeployment(options, configPath, previousVersionId,
|
|
|
1742
1881
|
ok: false,
|
|
1743
1882
|
reason: "uploader-changed"
|
|
1744
1883
|
};
|
|
1745
|
-
|
|
1884
|
+
{
|
|
1746
1885
|
const current = await readCurrentProductionVersion(options, configPath);
|
|
1747
1886
|
if (current.kind === "failed") return {
|
|
1748
1887
|
ok: false,
|
|
1749
1888
|
reason: "status-failed",
|
|
1750
1889
|
status: current.status
|
|
1751
1890
|
};
|
|
1752
|
-
if (current.kind !== "ok" || current.versionId !== previousVersionId
|
|
1891
|
+
if (current.kind !== "ok" || current.versionId !== previousVersionId || !productionVersionPercentagesEqual(current.versions, previousVersionId ? [{
|
|
1892
|
+
versionId: previousVersionId,
|
|
1893
|
+
percentage: 100
|
|
1894
|
+
}] : [])) return {
|
|
1753
1895
|
ok: false,
|
|
1754
1896
|
reason: "deployment-changed",
|
|
1755
1897
|
activeVersionId: current.kind === "ok" ? current.versionId : null,
|
|
@@ -1794,7 +1936,6 @@ async function bootstrapFirstDeployment(options, configPath, previousVersionId,
|
|
|
1794
1936
|
previousVersionId,
|
|
1795
1937
|
rollback: "unsafe"
|
|
1796
1938
|
};
|
|
1797
|
-
options.onProgress?.("Updating routes and triggers...");
|
|
1798
1939
|
const triggerOptions = {
|
|
1799
1940
|
runWrangler: options.runWrangler,
|
|
1800
1941
|
workerName: options.workerName,
|
|
@@ -1809,6 +1950,65 @@ async function bootstrapFirstDeployment(options, configPath, previousVersionId,
|
|
|
1809
1950
|
publishEmptyCustomDomains: options.publishEmptyCustomDomains,
|
|
1810
1951
|
publishEmptyCrons: options.publishEmptyCrons
|
|
1811
1952
|
};
|
|
1953
|
+
const verifyPublishedOwner = async () => {
|
|
1954
|
+
const owner = await readCurrentProductionVersion(options, configPath);
|
|
1955
|
+
if (owner.kind === "failed") return {
|
|
1956
|
+
ok: false,
|
|
1957
|
+
reason: "status-failed",
|
|
1958
|
+
status: owner.status,
|
|
1959
|
+
versionId,
|
|
1960
|
+
previousVersionId,
|
|
1961
|
+
rollback: "unsafe"
|
|
1962
|
+
};
|
|
1963
|
+
if (owner.kind !== "ok" || owner.versionId !== versionId || !productionVersionPercentagesEqual(owner.versions, [{
|
|
1964
|
+
versionId,
|
|
1965
|
+
percentage: 100
|
|
1966
|
+
}])) {
|
|
1967
|
+
const repair = owner.kind === "ok" && owner.versionId && owner.versionId !== versionId && options.verifyUploader() ? await stabilizeCloudflareVersionTriggers({
|
|
1968
|
+
...triggerOptions,
|
|
1969
|
+
targetTriggerConfig: {
|
|
1970
|
+
...desiredTriggerConfig,
|
|
1971
|
+
preserve: true
|
|
1972
|
+
}
|
|
1973
|
+
}) : null;
|
|
1974
|
+
return {
|
|
1975
|
+
ok: false,
|
|
1976
|
+
reason: "deployment-changed",
|
|
1977
|
+
versionId,
|
|
1978
|
+
previousVersionId,
|
|
1979
|
+
activeVersionId: repair?.activeVersionId ?? (owner.kind === "ok" ? owner.versionId : null),
|
|
1980
|
+
rollback: "not-needed",
|
|
1981
|
+
...repair ? { triggerRepair: repair.ok && !repair.targetOwnsTraffic ? "succeeded" : !repair.ok && repair.reason !== "metadata-unavailable" ? "failed" : "unavailable" } : {}
|
|
1982
|
+
};
|
|
1983
|
+
}
|
|
1984
|
+
return null;
|
|
1985
|
+
};
|
|
1986
|
+
const publishedOwnerFailure = await verifyPublishedOwner();
|
|
1987
|
+
if (publishedOwnerFailure) return publishedOwnerFailure;
|
|
1988
|
+
if (options.validateUploadedVersion || options.recordVerifiedCandidate) {
|
|
1989
|
+
options.onProgress?.("Verifying the uploaded Worker...");
|
|
1990
|
+
try {
|
|
1991
|
+
await options.validateUploadedVersion?.(versionId);
|
|
1992
|
+
const validatedOwnerFailure = await verifyPublishedOwner();
|
|
1993
|
+
if (validatedOwnerFailure) return validatedOwnerFailure;
|
|
1994
|
+
if (options.recordVerifiedCandidate) {
|
|
1995
|
+
if (!options.validateUploadedVersion && !options.verifyInheritedSecrets && options.expectedPreviousVersionId === void 0) throw new Error("The bootstrap upload has no verified candidate checkpoint.");
|
|
1996
|
+
await options.recordVerifiedCandidate(versionId, previousVersionId);
|
|
1997
|
+
}
|
|
1998
|
+
} catch {
|
|
1999
|
+
return {
|
|
2000
|
+
ok: false,
|
|
2001
|
+
reason: atomic ? "atomic-failed" : "bootstrap-failed",
|
|
2002
|
+
versionId,
|
|
2003
|
+
previousVersionId,
|
|
2004
|
+
rollback: "unavailable",
|
|
2005
|
+
diagnostic: `Worker version ${versionId} was published, but its upload validation or candidate checkpoint failed. Inspect the current Worker deployment before retrying.`
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
2008
|
+
const checkpointOwnerFailure = await verifyPublishedOwner();
|
|
2009
|
+
if (checkpointOwnerFailure) return checkpointOwnerFailure;
|
|
2010
|
+
}
|
|
2011
|
+
options.onProgress?.("Updating routes and triggers...");
|
|
1812
2012
|
const triggers = await stabilizeCloudflareVersionTriggers(triggerOptions);
|
|
1813
2013
|
if (!triggers.ok || !triggers.targetOwnsTraffic) {
|
|
1814
2014
|
const superseded = triggers.activeVersionId !== void 0 && triggers.activeVersionId !== null && triggers.activeVersionId !== versionId;
|
|
@@ -2153,7 +2353,7 @@ function createCloudflareMetadataRunner(options) {
|
|
|
2153
2353
|
} else if (operation.type === "versions.list") {
|
|
2154
2354
|
kind = "versions";
|
|
2155
2355
|
resource = "/versions?deployable=true";
|
|
2156
|
-
} else if (operation.type === "versions.view" && /^[0-9a-f-]{36}$/i.test(operation.versionId)) {
|
|
2356
|
+
} else if (operation.type === "versions.view" && !operation.includeSource && /^[0-9a-f-]{36}$/i.test(operation.versionId)) {
|
|
2157
2357
|
kind = "version";
|
|
2158
2358
|
resource = "/versions/" + encodeURIComponent(operation.versionId);
|
|
2159
2359
|
}
|
|
@@ -2219,4 +2419,4 @@ function record(value) {
|
|
|
2219
2419
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2220
2420
|
}
|
|
2221
2421
|
//#endregion
|
|
2222
|
-
export { CloudflareWorkerInspectionError, activateCloudflareVersion, assertCloudflareBindingsPreserved, assertCloudflareDurableObjectLifecycleSupported, assertExistingCloudflareConfig, createCloudflareMetadataRunner, deployCloudflareVersionTraffic, deployCloudflareVersionTriggers, encodeCloudflareVersionMessage, executeCloudflareDeploy, extractWorkersDevSubdomain, normalizeWorkersDevSubdomain, parseCloudflareVersionMessage, parseRootWranglerConfig, readBindingFieldPresence, readCloudflareTriggerConfig, readCloudflareVersionMigrationTag, readCloudflareWorkerMigrationTag, readConfigEntryString, readExistingCloudflareWorker, readStoredResourceName, readWranglerTopLevelString, renderCloudflareStaticEntry, resolveD1MigrateTarget, stabilizeCloudflareVersionTriggers, stageCloudflareStaticAssets, writeCloudflareFrameworkDeployConfig, writeCloudflareStaticDeployConfig };
|
|
2422
|
+
export { CloudflareWorkerInspectionError, VOID_SECRET_DRAFT_SOURCE, activateCloudflareVersion, assertCloudflareBindingsPreserved, assertCloudflareDurableObjectLifecycleSupported, assertExistingCloudflareConfig, createCloudflareMetadataRunner, deployCloudflareVersionTraffic, deployCloudflareVersionTriggers, encodeCloudflareVersionMessage, executeCloudflareDeploy, extractWorkersDevSubdomain, normalizeWorkersDevSubdomain, parseCloudflareVersionMessage, parseRootWranglerConfig, readBindingFieldPresence, readCloudflareTriggerConfig, readCloudflareVersionMigrationTag, readCloudflareWorkerMigrationTag, readConfigEntryString, readExistingCloudflareWorker, readStoredResourceName, readWranglerTopLevelString, renderCloudflareStaticEntry, resolveD1MigrateTarget, stabilizeCloudflareVersionTriggers, stageCloudflareStaticAssets, writeCloudflareFrameworkDeployConfig, writeCloudflareStaticDeployConfig };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@void/deploy-cloudflare",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/voidzero-dev/void.git",
|
|
@@ -20,12 +20,12 @@
|
|
|
20
20
|
"access": "public"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@void/deploy-core": "0.
|
|
23
|
+
"@void/deploy-core": "0.25.0",
|
|
24
24
|
"jsonc-parser": "3.3.1"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@cloudflare/workers-types": "^4.20260702.1",
|
|
28
|
-
"@types/node": "^26.6.
|
|
28
|
+
"@types/node": "^26.6.4",
|
|
29
29
|
"pathslash": "^0.1.0",
|
|
30
30
|
"typescript": "7.0.2",
|
|
31
31
|
"vite-plus": "1.0.0"
|