@void/deploy-cloudflare 0.20.0 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +3 -1
- package/dist/index.mjs +72 -32
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -70,7 +70,9 @@ export declare function assertCloudflareDurableObjectLifecycleSupported(options:
|
|
|
70
70
|
apiBaseUrl: string;
|
|
71
71
|
readMigrationTag?: (options: CloudflareWorkerMigrationTagOptions) => Promise<string | null>;
|
|
72
72
|
}): Promise<void>;
|
|
73
|
-
export declare function assertCloudflareBindingsPreserved(before: ExistingCloudflareWorker, after: ExistingCloudflareWorker, approvedKv?: ReadonlyArray<ApprovedCloudflareKvBinding
|
|
73
|
+
export declare function assertCloudflareBindingsPreserved(before: ExistingCloudflareWorker, after: ExistingCloudflareWorker, approvedKv?: ReadonlyArray<ApprovedCloudflareKvBinding>, options?: {
|
|
74
|
+
staticAssetsBinding?: string;
|
|
75
|
+
}): void;
|
|
74
76
|
/** Preserve the existing Worker, allowing only explicitly approved KV additions. */
|
|
75
77
|
export declare function assertExistingCloudflareConfig(existing: ExistingCloudflareWorker, config: Record<string, unknown>, built?: boolean, approvedKv?: ReadonlyArray<ApprovedCloudflareKvBinding>): void;
|
|
76
78
|
//#endregion
|
package/dist/index.mjs
CHANGED
|
@@ -81,12 +81,34 @@ function resolveD1MigrateTarget(config, binding) {
|
|
|
81
81
|
};
|
|
82
82
|
}
|
|
83
83
|
//#endregion
|
|
84
|
+
//#region src/secret-draft.ts
|
|
85
|
+
/**
|
|
86
|
+
* `wrangler secret put` creates a fetch-only draft before attaching a first secret to a Worker.
|
|
87
|
+
* Accept that draft as first-deployment proof only when its complete deployable shape contains no
|
|
88
|
+
* application code configuration or non-secret resource that an ordinary deploy could own.
|
|
89
|
+
*/
|
|
90
|
+
function isWranglerSecretOnlyDraft(entry) {
|
|
91
|
+
if (!isRecord$1(entry.annotations)) return false;
|
|
92
|
+
const annotationKeys = Object.keys(entry.annotations);
|
|
93
|
+
if (entry.annotations["workers/triggered_by"] !== "secret" || annotationKeys.some((key) => key !== "workers/triggered_by")) return false;
|
|
94
|
+
if (!isRecord$1(entry.resources)) return false;
|
|
95
|
+
const { bindings, script, script_runtime: scriptRuntime } = entry.resources;
|
|
96
|
+
if (!Array.isArray(bindings) || bindings.length === 0 || !bindings.every((binding) => isRecord$1(binding) && typeof binding.name === "string" && binding.name.length > 0 && binding.type === "secret_text" && Object.keys(binding).every((key) => key === "name" || key === "type"))) return false;
|
|
97
|
+
if (!isRecord$1(script) || !Array.isArray(script.handlers) || script.handlers.length !== 1 || script.handlers[0] !== "fetch" || script.placement_mode !== void 0) return false;
|
|
98
|
+
if (!isRecord$1(scriptRuntime)) return false;
|
|
99
|
+
const runtimeKeys = Object.keys(scriptRuntime);
|
|
100
|
+
return scriptRuntime.usage_model === "standard" && (scriptRuntime.limits === void 0 || isRecord$1(scriptRuntime.limits) && Object.keys(scriptRuntime.limits).length === 0) && runtimeKeys.every((key) => key === "usage_model" || key === "limits");
|
|
101
|
+
}
|
|
102
|
+
function isRecord$1(value) {
|
|
103
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
84
106
|
//#region src/existing.ts
|
|
85
107
|
/** Read the deployed version, not the most recently uploaded candidate. */
|
|
86
108
|
async function readExistingCloudflareWorker(options) {
|
|
87
109
|
const worker = await readCloudflareWorker(options, false);
|
|
88
110
|
if (!worker) throw new Error(`Cloudflare Worker "${options.workerName}" does not have an active version.`);
|
|
89
|
-
return worker;
|
|
111
|
+
return worker.snapshot;
|
|
90
112
|
}
|
|
91
113
|
async function readCloudflareWorker(options, allowMissing) {
|
|
92
114
|
const target = [
|
|
@@ -132,10 +154,13 @@ async function readCloudflareWorker(options, allowMissing) {
|
|
|
132
154
|
const after = options.versionId ? deployment : await read(["deployments", "status"]);
|
|
133
155
|
if (after === null || !isDeepStrictEqual(deployment.versions, after.versions)) throw new Error("Cloudflare production changed while linking. Retry from the current deployment.");
|
|
134
156
|
return {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
157
|
+
snapshot: {
|
|
158
|
+
versionId,
|
|
159
|
+
bindings: resources.bindings,
|
|
160
|
+
runtime: resources.script_runtime,
|
|
161
|
+
script: resources.script
|
|
162
|
+
},
|
|
163
|
+
isSecretOnlyDraft: isWranglerSecretOnlyDraft(version)
|
|
139
164
|
};
|
|
140
165
|
}
|
|
141
166
|
/** Parse legacy version-detail shapes; this is not authoritative Worker migration state. */
|
|
@@ -219,7 +244,8 @@ async function assertCloudflareDurableObjectLifecycleSupported(options) {
|
|
|
219
244
|
const activeTag = await readMigrationTag(migrationOptions);
|
|
220
245
|
const after = await readCloudflareWorker(options, false);
|
|
221
246
|
const afterTag = await readMigrationTag(migrationOptions);
|
|
222
|
-
if (!after || after.versionId !== existing.versionId || afterTag !== activeTag) throw new Error("Cloudflare production or Durable Object migration state changed during verification. Retry from the current deployment.");
|
|
247
|
+
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.");
|
|
248
|
+
if (activeTag === null && existing.isSecretOnlyDraft && after.isSecretOnlyDraft) return;
|
|
223
249
|
const activeIndex = activeTag === null ? -1 : migrations.findIndex((migration) => migration.tag === activeTag);
|
|
224
250
|
const pending = migrations.slice(activeIndex + 1).filter((migration) => migration.changesClasses);
|
|
225
251
|
if (pending.length === 0) return;
|
|
@@ -251,7 +277,9 @@ function parseDurableObjectMigrations(value) {
|
|
|
251
277
|
function isMissingWorkerOutput$1(output) {
|
|
252
278
|
return /\[code:\s*10007\]/i.test(output) || /no deployments|\bworker\b.*\b(?:does not exist|not found)\b/i.test(output);
|
|
253
279
|
}
|
|
254
|
-
function assertCloudflareBindingsPreserved(before, after, approvedKv = []) {
|
|
280
|
+
function assertCloudflareBindingsPreserved(before, after, approvedKv = [], options = {}) {
|
|
281
|
+
const staticAssetsBinding = isAssetsOnlyWorker(before) ? options.staticAssetsBinding : void 0;
|
|
282
|
+
const addsAssetsBinding = staticAssetsBinding !== void 0 && !before.bindings.some((binding) => binding.name === staticAssetsBinding);
|
|
255
283
|
const additions = newApprovedKvBindings(before, approvedKv);
|
|
256
284
|
for (const addition of additions) {
|
|
257
285
|
const matches = after.bindings.filter((binding) => binding.name === addition.binding);
|
|
@@ -259,12 +287,24 @@ function assertCloudflareBindingsPreserved(before, after, approvedKv = []) {
|
|
|
259
287
|
}
|
|
260
288
|
const preservedBindings = after.bindings.filter((binding) => !additions.some((addition) => addition.binding === binding.name));
|
|
261
289
|
const sorted = (bindings) => [...bindings].sort((a, b) => String(a.name).localeCompare(String(b.name)));
|
|
262
|
-
|
|
290
|
+
const expectedBindings = addsAssetsBinding ? [...before.bindings, {
|
|
291
|
+
type: "assets",
|
|
292
|
+
name: staticAssetsBinding
|
|
293
|
+
}] : before.bindings;
|
|
294
|
+
if (staticAssetsBinding !== void 0 && !after.bindings.some((binding) => binding.type === "assets" && binding.name === staticAssetsBinding) || !isDeepStrictEqual(sorted(expectedBindings), sorted(preservedBindings))) throw new Error("The uploaded version did not preserve the existing Worker bindings and variables. It has not been activated.");
|
|
263
295
|
const handlers = (worker) => {
|
|
264
|
-
const value = worker
|
|
296
|
+
const value = existingWorkerHandlers(worker);
|
|
265
297
|
return Array.isArray(value) && value.every((handler) => typeof handler === "string") ? [...new Set(value)].sort() : null;
|
|
266
298
|
};
|
|
267
|
-
|
|
299
|
+
const expectedHandlers = staticAssetsBinding !== void 0 ? ["fetch"] : handlers(before);
|
|
300
|
+
if (!isDeepStrictEqual(expectedHandlers, handlers(after))) throw new Error("The uploaded version did not preserve the existing Worker event handlers. It has not been activated.");
|
|
301
|
+
}
|
|
302
|
+
function isAssetsOnlyWorker(worker) {
|
|
303
|
+
const handlers = worker.script.handlers;
|
|
304
|
+
return record(worker.runtime.assets) && (handlers === null || Array.isArray(handlers) && handlers.length === 0);
|
|
305
|
+
}
|
|
306
|
+
function existingWorkerHandlers(worker) {
|
|
307
|
+
return isAssetsOnlyWorker(worker) ? [] : worker.script.handlers;
|
|
268
308
|
}
|
|
269
309
|
/** Preserve the existing Worker, allowing only explicitly approved KV additions. */
|
|
270
310
|
function assertExistingCloudflareConfig(existing, config, built = false, approvedKv = []) {
|
|
@@ -280,7 +320,7 @@ function assertExistingCloudflareConfig(existing, config, built = false, approve
|
|
|
280
320
|
kv_namespaces: entries.filter((entry) => !record(entry) || !additions.some((addition) => addition.binding === entry.binding))
|
|
281
321
|
};
|
|
282
322
|
}
|
|
283
|
-
const handlers = existing
|
|
323
|
+
const handlers = existingWorkerHandlers(existing);
|
|
284
324
|
if (!Array.isArray(handlers) || handlers.some((handler) => handler !== "fetch" && handler !== "scheduled")) throw new Error("Linking an existing Worker currently supports fetch and scheduled handlers only. Queue and Durable Object handlers need a separate migration.");
|
|
285
325
|
for (const field of [
|
|
286
326
|
"durable_objects",
|
|
@@ -1178,9 +1218,9 @@ async function activateCloudflareVersionWithSecretsFile(options) {
|
|
|
1178
1218
|
"--experimental-auto-create=false"
|
|
1179
1219
|
], { capture: true }));
|
|
1180
1220
|
if (!isCleanCommandExit(upload)) {
|
|
1181
|
-
const output = `${upload.stdout}\n${upload.stderr}
|
|
1221
|
+
const output = stripVTControlCharacters(`${upload.stdout}\n${upload.stderr}`);
|
|
1182
1222
|
const cloudflareErrorCode = parseCloudflareErrorCode(output);
|
|
1183
|
-
const isBootstrapError = cloudflareErrorCode === DURABLE_MIGRATION_VERSION_ERROR || /cannot upload a new version of a worker that does not yet exist/i.test(output);
|
|
1223
|
+
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);
|
|
1184
1224
|
if (!options.preserveTriggers && isBootstrapError && (previous.versionId === null || options.allowFirstDeploymentBootstrap === true || await previousIsSecretOnlyDraft())) return bootstrapFirstDeployment({
|
|
1185
1225
|
...options,
|
|
1186
1226
|
message
|
|
@@ -1514,10 +1554,27 @@ async function readCloudflareVersionInspection(options, commonArgs, versionId) {
|
|
|
1514
1554
|
if (!Array.isArray(versions)) return null;
|
|
1515
1555
|
for (const entry of versions) {
|
|
1516
1556
|
if (!isRecord(entry) || entry.id !== versionId) continue;
|
|
1517
|
-
const
|
|
1557
|
+
const annotations = isRecord(entry.annotations) ? entry.annotations : void 0;
|
|
1558
|
+
const message = annotations?.["workers/message"];
|
|
1559
|
+
let draft = entry;
|
|
1560
|
+
if (annotations?.["workers/triggered_by"] === "secret" && Object.keys(annotations).length === 1 && !isRecord(entry.resources)) {
|
|
1561
|
+
const inspection = await Promise.resolve(options.runWrangler([
|
|
1562
|
+
"versions",
|
|
1563
|
+
"view",
|
|
1564
|
+
versionId,
|
|
1565
|
+
...commonArgs,
|
|
1566
|
+
"--name",
|
|
1567
|
+
options.workerName,
|
|
1568
|
+
"--json"
|
|
1569
|
+
], { capture: true }));
|
|
1570
|
+
if (isCleanCommandExit(inspection)) {
|
|
1571
|
+
const inspected = JSON.parse(inspection.stdout);
|
|
1572
|
+
if (isRecord(inspected) && inspected.id === versionId) draft = inspected;
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1518
1575
|
return {
|
|
1519
1576
|
metadata: typeof message === "string" ? parseCloudflareVersionMessage(message) : null,
|
|
1520
|
-
isSecretOnlyDraft: isWranglerSecretOnlyDraft(
|
|
1577
|
+
isSecretOnlyDraft: isWranglerSecretOnlyDraft(draft)
|
|
1521
1578
|
};
|
|
1522
1579
|
}
|
|
1523
1580
|
} catch {
|
|
@@ -1593,23 +1650,6 @@ async function readCloudflareVersionSecretBindings(options, commonArgs, versionI
|
|
|
1593
1650
|
}
|
|
1594
1651
|
}
|
|
1595
1652
|
/**
|
|
1596
|
-
* `wrangler secret put` creates a fetch-only draft before attaching a first secret to a Worker.
|
|
1597
|
-
* Accept that draft as first-deployment proof only when its complete deployable shape contains no
|
|
1598
|
-
* application code configuration or non-secret resource that an ordinary deploy could own.
|
|
1599
|
-
*/
|
|
1600
|
-
function isWranglerSecretOnlyDraft(entry) {
|
|
1601
|
-
if (!isRecord(entry.annotations)) return false;
|
|
1602
|
-
const annotationKeys = Object.keys(entry.annotations);
|
|
1603
|
-
if (entry.annotations["workers/triggered_by"] !== "secret" || annotationKeys.some((key) => key !== "workers/triggered_by")) return false;
|
|
1604
|
-
if (!isRecord(entry.resources)) return false;
|
|
1605
|
-
const { bindings, script, script_runtime: scriptRuntime } = entry.resources;
|
|
1606
|
-
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;
|
|
1607
|
-
if (!isRecord(script) || !Array.isArray(script.handlers) || script.handlers.length !== 1 || script.handlers[0] !== "fetch" || script.placement_mode !== void 0) return false;
|
|
1608
|
-
if (!isRecord(scriptRuntime)) return false;
|
|
1609
|
-
const runtimeKeys = Object.keys(scriptRuntime);
|
|
1610
|
-
return scriptRuntime.usage_model === "standard" && isRecord(scriptRuntime.limits) && Object.keys(scriptRuntime.limits).length === 0 && runtimeKeys.every((key) => key === "usage_model" || key === "limits");
|
|
1611
|
-
}
|
|
1612
|
-
/**
|
|
1613
1653
|
* The Versions API cannot create a Worker or its initial Durable Object migration history, and
|
|
1614
1654
|
* some accounts do not expose a version preview until their first ordinary deployment. A
|
|
1615
1655
|
* brand-new Worker can therefore need one ordinary deploy before later releases use preview-first
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@void/deploy-cloudflare",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.1",
|
|
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.20.
|
|
23
|
+
"@void/deploy-core": "0.20.1",
|
|
24
24
|
"jsonc-parser": "3.3.1"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|