@void/deploy-cloudflare 0.20.0 → 0.20.2
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 +22 -1
- package/dist/index.mjs +218 -70
- 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
|
|
@@ -319,4 +321,23 @@ export declare function normalizeWorkersDevSubdomain(value: string | undefined):
|
|
|
319
321
|
/** Extract the account subdomain from a Wrangler-reported workers.dev URL. */
|
|
320
322
|
export declare function extractWorkersDevSubdomain(url: string): string | undefined;
|
|
321
323
|
//#endregion
|
|
324
|
+
//#region src/metadata.d.ts
|
|
325
|
+
/**
|
|
326
|
+
* Share one authenticated transport for the activator's read-only metadata calls.
|
|
327
|
+
* Writes and unsupported commands retain the pinned Wrangler runner. Responses
|
|
328
|
+
* keep its JSON contract so activation and concurrency checks remain unchanged.
|
|
329
|
+
*/
|
|
330
|
+
export declare function createCloudflareMetadataRunner(options: {
|
|
331
|
+
runWrangler: CloudflareCommandRunner;
|
|
332
|
+
accountId: string;
|
|
333
|
+
apiBaseUrl: string;
|
|
334
|
+
workerName: string;
|
|
335
|
+
configPath?: string;
|
|
336
|
+
getToken: () => Promise<string | null>;
|
|
337
|
+
verifyUploader: () => boolean;
|
|
338
|
+
fetch?: typeof globalThis.fetch;
|
|
339
|
+
/** Environment used to decide whether Wrangler's proxy-aware transport is required. */
|
|
340
|
+
env?: Readonly<Record<string, string | undefined>>;
|
|
341
|
+
}): CloudflareCommandRunner;
|
|
342
|
+
//#endregion
|
|
322
343
|
export type { ApprovedCloudflareKvBinding, CloudflareCommandResult, CloudflareCommandRunner, CloudflareDeployExecution, CloudflareFrameworkDeployConfig, CloudflareStaticDeployConfig, CloudflareTrafficConfig, CloudflareTriggerConfig, CloudflareTriggerStabilization, CloudflareVersionActivation, CloudflareVersionActivationOptions, CloudflareVersionMetadata, CloudflareWorkerMigrationTagOptions, ExistingCloudflareWorker };
|
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 = [
|
|
@@ -108,7 +130,7 @@ async function readCloudflareWorker(options, allowMissing) {
|
|
|
108
130
|
}
|
|
109
131
|
try {
|
|
110
132
|
const value = JSON.parse(result.stdout);
|
|
111
|
-
if (record(value)) return value;
|
|
133
|
+
if (record$1(value)) return value;
|
|
112
134
|
} catch {}
|
|
113
135
|
throw new Error("Cloudflare returned an invalid existing Worker response.");
|
|
114
136
|
};
|
|
@@ -119,7 +141,7 @@ async function readCloudflareWorker(options, allowMissing) {
|
|
|
119
141
|
if (deployment === null) return null;
|
|
120
142
|
const versions = deployment.versions;
|
|
121
143
|
if (allowMissing && Array.isArray(versions) && versions.length === 0) return null;
|
|
122
|
-
if (!Array.isArray(versions) || versions.length !== 1 || !record(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.");
|
|
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.");
|
|
123
145
|
const versionId = versions[0].version_id;
|
|
124
146
|
const version = await read([
|
|
125
147
|
"versions",
|
|
@@ -128,24 +150,27 @@ async function readCloudflareWorker(options, allowMissing) {
|
|
|
128
150
|
]);
|
|
129
151
|
if (version === null) throw new Error("Cloudflare did not return the existing Worker configuration.");
|
|
130
152
|
const resources = version.resources;
|
|
131
|
-
if (version.id !== versionId || !record(resources) || !Array.isArray(resources.bindings) || !resources.bindings.every(record) || !record(resources.script_runtime) || !record(resources.script)) throw new Error("Cloudflare did not return the existing Worker configuration.");
|
|
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.");
|
|
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. */
|
|
142
167
|
function readCloudflareVersionMigrationTag(version) {
|
|
143
|
-
if (!record(version)) return null;
|
|
144
|
-
const resources = record(version.resources) ? version.resources : void 0;
|
|
168
|
+
if (!record$1(version)) return null;
|
|
169
|
+
const resources = record$1(version.resources) ? version.resources : void 0;
|
|
145
170
|
const tag = [
|
|
146
|
-
resources && record(resources.script_runtime) ? resources.script_runtime.migration_tag : null,
|
|
147
|
-
resources && record(resources.script) ? resources.script.migration_tag : null,
|
|
148
|
-
record(version.script) ? version.script.migration_tag : null,
|
|
171
|
+
resources && record$1(resources.script_runtime) ? resources.script_runtime.migration_tag : null,
|
|
172
|
+
resources && record$1(resources.script) ? resources.script.migration_tag : null,
|
|
173
|
+
record$1(version.script) ? version.script.migration_tag : null,
|
|
149
174
|
version.migration_tag
|
|
150
175
|
].find((candidate) => typeof candidate === "string");
|
|
151
176
|
return typeof tag === "string" && tag.length > 0 ? tag : null;
|
|
@@ -170,9 +195,9 @@ async function readCloudflareWorkerMigrationTag(options) {
|
|
|
170
195
|
} catch {
|
|
171
196
|
throw new Error(`Cloudflare returned an invalid Durable Object migration response for Worker "${options.workerName}".`);
|
|
172
197
|
}
|
|
173
|
-
if (!response.ok || !record(body) || body.success !== true || !Array.isArray(body.result)) throw new Error(`Could not verify Durable Object migration state for Cloudflare Worker "${options.workerName}".`);
|
|
174
|
-
const worker = body.result.find((candidate) => record(candidate) && candidate.id === options.workerName);
|
|
175
|
-
if (!record(worker)) throw new Error(`Cloudflare did not return migration state for Worker "${options.workerName}".`);
|
|
198
|
+
if (!response.ok || !record$1(body) || body.success !== true || !Array.isArray(body.result)) throw new Error(`Could not verify Durable Object migration state for Cloudflare Worker "${options.workerName}".`);
|
|
199
|
+
const worker = body.result.find((candidate) => record$1(candidate) && candidate.id === options.workerName);
|
|
200
|
+
if (!record$1(worker)) throw new Error(`Cloudflare did not return migration state for Worker "${options.workerName}".`);
|
|
176
201
|
if (worker.migration_tag === void 0 || worker.migration_tag === null) return null;
|
|
177
202
|
if (typeof worker.migration_tag !== "string" || worker.migration_tag.length === 0) throw new Error(`Cloudflare returned an invalid Durable Object migration response for Worker "${options.workerName}".`);
|
|
178
203
|
return worker.migration_tag;
|
|
@@ -190,7 +215,7 @@ async function readCloudflareApiAuthentication(runWrangler) {
|
|
|
190
215
|
} catch {
|
|
191
216
|
throw new Error("Cloudflare returned an invalid authentication response.");
|
|
192
217
|
}
|
|
193
|
-
if (!record(value)) throw new Error("Cloudflare returned an invalid authentication response.");
|
|
218
|
+
if (!record$1(value)) throw new Error("Cloudflare returned an invalid authentication response.");
|
|
194
219
|
if ((value.type === "oauth" || value.type === "api_token") && typeof value.token === "string" && value.token.length > 0) return { Authorization: `Bearer ${value.token}` };
|
|
195
220
|
if (value.type === "api_key" && typeof value.key === "string" && value.key.length > 0 && typeof value.email === "string" && value.email.length > 0) return {
|
|
196
221
|
"X-Auth-Key": value.key,
|
|
@@ -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;
|
|
@@ -234,7 +260,7 @@ function parseDurableObjectMigrations(value) {
|
|
|
234
260
|
if (value === void 0) return [];
|
|
235
261
|
if (!Array.isArray(value)) throw new Error("deploy: generated Cloudflare config has a malformed migrations field.");
|
|
236
262
|
return value.map((migration) => {
|
|
237
|
-
if (!record(migration) || typeof migration.tag !== "string") throw new Error("deploy: generated Cloudflare config has a malformed class migration.");
|
|
263
|
+
if (!record$1(migration) || typeof migration.tag !== "string") throw new Error("deploy: generated Cloudflare config has a malformed class migration.");
|
|
238
264
|
const fields = [
|
|
239
265
|
migration.new_classes,
|
|
240
266
|
migration.new_sqlite_classes,
|
|
@@ -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$1(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 = []) {
|
|
@@ -272,15 +312,15 @@ function assertExistingCloudflareConfig(existing, config, built = false, approve
|
|
|
272
312
|
if (additions.length > 0) {
|
|
273
313
|
const entries = config.kv_namespaces;
|
|
274
314
|
for (const addition of additions) {
|
|
275
|
-
const matches = Array.isArray(entries) ? entries.filter((entry) => record(entry) && entry.binding === addition.binding) : [];
|
|
315
|
+
const matches = Array.isArray(entries) ? entries.filter((entry) => record$1(entry) && entry.binding === addition.binding) : [];
|
|
276
316
|
if ((built || matches.length > 0) && (matches.length !== 1 || matches[0].id !== addition.id)) throw new Error(`Binding ${addition.binding} must reference its approved KV namespace when linking.`);
|
|
277
317
|
}
|
|
278
318
|
if (Array.isArray(entries)) config = {
|
|
279
319
|
...config,
|
|
280
|
-
kv_namespaces: entries.filter((entry) => !record(entry) || !additions.some((addition) => addition.binding === entry.binding))
|
|
320
|
+
kv_namespaces: entries.filter((entry) => !record$1(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",
|
|
@@ -294,7 +334,7 @@ function assertExistingCloudflareConfig(existing, config, built = false, approve
|
|
|
294
334
|
const value = config[field];
|
|
295
335
|
if (hasContent(value)) throw new Error(`The existing Worker handoff cannot change ${field}. Keep this deployment limited to the existing site.`);
|
|
296
336
|
}
|
|
297
|
-
if (record(existing.runtime.exports) && Object.values(existing.runtime.exports).some((value) => record(value) && value.type !== "worker")) throw new Error("The existing Worker exports stateful classes. Migrate those classes separately before linking.");
|
|
337
|
+
if (record$1(existing.runtime.exports) && Object.values(existing.runtime.exports).some((value) => record$1(value) && value.type !== "worker")) throw new Error("The existing Worker exports stateful classes. Migrate those classes separately before linking.");
|
|
298
338
|
for (const field of [
|
|
299
339
|
"compatibility_date",
|
|
300
340
|
"compatibility_flags",
|
|
@@ -305,7 +345,7 @@ function assertExistingCloudflareConfig(existing, config, built = false, approve
|
|
|
305
345
|
const equal = field === "compatibility_flags" ? stringSetEqual(actual, expected) : isDeepStrictEqual(actual, expected);
|
|
306
346
|
if ((built || config[field] !== void 0) && !equal) throw new Error(`Keep the existing Worker's ${field} when linking. Update it in a later deployment.`);
|
|
307
347
|
}
|
|
308
|
-
const vars = record(config.vars) ? config.vars : {};
|
|
348
|
+
const vars = record$1(config.vars) ? config.vars : {};
|
|
309
349
|
const remote = new Map(existing.bindings.map((binding) => [binding.name, binding]));
|
|
310
350
|
for (const [name, value] of Object.entries(vars)) {
|
|
311
351
|
const binding = remote.get(name);
|
|
@@ -349,21 +389,21 @@ function assertExistingCloudflareConfig(existing, config, built = false, approve
|
|
|
349
389
|
"secret_key"
|
|
350
390
|
].includes(String(binding.type))) continue;
|
|
351
391
|
if (binding.type === "assets") {
|
|
352
|
-
if (built && (!record(config.assets) || config.assets.binding !== binding.name)) throw new Error(`Keep the existing assets binding ${binding.name} when linking.`);
|
|
392
|
+
if (built && (!record$1(config.assets) || config.assets.binding !== binding.name)) throw new Error(`Keep the existing assets binding ${binding.name} when linking.`);
|
|
353
393
|
continue;
|
|
354
394
|
}
|
|
355
395
|
const mapping = supported[String(binding.type)];
|
|
356
396
|
if (!mapping) throw new Error(`The existing binding ${binding.name} (${String(binding.type)}) is not yet supported when linking.`);
|
|
357
397
|
const [field, localId, remoteId] = mapping;
|
|
358
398
|
const entries = config[field];
|
|
359
|
-
const matches = Array.isArray(entries) ? entries.filter((entry) => record(entry) && entry.binding === binding.name) : [];
|
|
399
|
+
const matches = Array.isArray(entries) ? entries.filter((entry) => record$1(entry) && entry.binding === binding.name) : [];
|
|
360
400
|
const entry = matches[0];
|
|
361
|
-
if (matches.length !== 1 || !record(entry) || typeof binding[remoteId] !== "string" || entry[localId] !== binding[remoteId] || binding.type === "service" && ((entry.environment ?? "production") !== (binding.environment ?? "production") || entry.entrypoint !== binding.entrypoint) || binding.type === "r2_bucket" && (entry.jurisdiction ?? "default") !== (binding.jurisdiction ?? "default")) throw new Error(`Binding ${binding.name} must reference the existing Cloudflare resource when linking.`);
|
|
401
|
+
if (matches.length !== 1 || !record$1(entry) || typeof binding[remoteId] !== "string" || entry[localId] !== binding[remoteId] || binding.type === "service" && ((entry.environment ?? "production") !== (binding.environment ?? "production") || entry.entrypoint !== binding.entrypoint) || binding.type === "r2_bucket" && (entry.jurisdiction ?? "default") !== (binding.jurisdiction ?? "default")) throw new Error(`Binding ${binding.name} must reference the existing Cloudflare resource when linking.`);
|
|
362
402
|
found.add(binding.name);
|
|
363
403
|
}
|
|
364
404
|
for (const [field] of Object.values(supported)) {
|
|
365
405
|
const entries = config[field];
|
|
366
|
-
if (Array.isArray(entries) && entries.some((entry) => !record(entry) || !found.has(String(entry.binding)))) throw new Error(`Linking cannot add or replace ${field}. Use the existing resource bindings.`);
|
|
406
|
+
if (Array.isArray(entries) && entries.some((entry) => !record$1(entry) || !found.has(String(entry.binding)))) throw new Error(`Linking cannot add or replace ${field}. Use the existing resource bindings.`);
|
|
367
407
|
}
|
|
368
408
|
}
|
|
369
409
|
function newApprovedKvBindings(existing, approved) {
|
|
@@ -386,13 +426,13 @@ function stringSetEqual(left, right) {
|
|
|
386
426
|
if (!Array.isArray(left) || !left.every((value) => typeof value === "string") || !Array.isArray(right) || !right.every((value) => typeof value === "string")) return false;
|
|
387
427
|
return isDeepStrictEqual([...new Set(left)].sort(), [...new Set(right)].sort());
|
|
388
428
|
}
|
|
389
|
-
function record(value) {
|
|
429
|
+
function record$1(value) {
|
|
390
430
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
391
431
|
}
|
|
392
432
|
function hasContent(value) {
|
|
393
433
|
if (value === void 0 || value === null) return false;
|
|
394
434
|
if (Array.isArray(value)) return value.length > 0;
|
|
395
|
-
if (record(value)) return Object.values(value).some(hasContent);
|
|
435
|
+
if (record$1(value)) return Object.values(value).some(hasContent);
|
|
396
436
|
return true;
|
|
397
437
|
}
|
|
398
438
|
//#endregion
|
|
@@ -1126,10 +1166,10 @@ async function activateCloudflareVersionWithSecretsFile(options) {
|
|
|
1126
1166
|
}
|
|
1127
1167
|
const desiredMetadata = parseCloudflareVersionMessage(options.message);
|
|
1128
1168
|
let desiredTriggerConfig = desiredMetadata?.schema === 2 ? desiredMetadata.triggerConfig : options.configPath ? readCloudflareTriggerConfig(options.configPath) : triggerConfigFromWrangler({});
|
|
1129
|
-
let previousVersion = previousVersionId && desiredMetadata?.schema === 2 ? await readCloudflareVersionInspection(options, commonArgs, previousVersionId) : null;
|
|
1169
|
+
let previousVersion = previousVersionId && desiredMetadata?.schema === 2 ? await readCloudflareVersionInspection(options, commonArgs, previousVersionId, inheritanceBaseline?.map((version) => version.inspection)) : null;
|
|
1130
1170
|
async function previousIsSecretOnlyDraft() {
|
|
1131
1171
|
if (!previousVersionId) return false;
|
|
1132
|
-
previousVersion ??= await readCloudflareVersionInspection(options, commonArgs, previousVersionId);
|
|
1172
|
+
previousVersion ??= await readCloudflareVersionInspection(options, commonArgs, previousVersionId, inheritanceBaseline?.map((version) => version.inspection));
|
|
1133
1173
|
return previousVersion?.isSecretOnlyDraft === true;
|
|
1134
1174
|
}
|
|
1135
1175
|
const previousMetadata = desiredMetadata?.schema === 2 ? previousVersion?.metadata : null;
|
|
@@ -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
|
|
@@ -1499,25 +1539,45 @@ async function restorePreviousVersionAndTriggers(options, commonArgs, previousVe
|
|
|
1499
1539
|
async function readCloudflareVersionMetadata(options, commonArgs, versionId) {
|
|
1500
1540
|
return (await readCloudflareVersionInspection(options, commonArgs, versionId))?.metadata ?? null;
|
|
1501
1541
|
}
|
|
1502
|
-
async function readCloudflareVersionInspection(options, commonArgs, versionId) {
|
|
1503
|
-
const result = await Promise.resolve(options.runWrangler([
|
|
1504
|
-
"versions",
|
|
1505
|
-
"list",
|
|
1506
|
-
...commonArgs,
|
|
1507
|
-
"--name",
|
|
1508
|
-
options.workerName,
|
|
1509
|
-
"--json"
|
|
1510
|
-
], { capture: true }));
|
|
1511
|
-
if (!isCleanCommandExit(result)) return null;
|
|
1542
|
+
async function readCloudflareVersionInspection(options, commonArgs, versionId, knownVersions) {
|
|
1512
1543
|
try {
|
|
1513
|
-
|
|
1544
|
+
let versions = knownVersions;
|
|
1545
|
+
if (versions === void 0) {
|
|
1546
|
+
const result = await options.runWrangler([
|
|
1547
|
+
"versions",
|
|
1548
|
+
"list",
|
|
1549
|
+
...commonArgs,
|
|
1550
|
+
"--name",
|
|
1551
|
+
options.workerName,
|
|
1552
|
+
"--json"
|
|
1553
|
+
], { capture: true });
|
|
1554
|
+
if (!isCleanCommandExit(result)) return null;
|
|
1555
|
+
versions = JSON.parse(result.stdout);
|
|
1556
|
+
}
|
|
1514
1557
|
if (!Array.isArray(versions)) return null;
|
|
1515
1558
|
for (const entry of versions) {
|
|
1516
1559
|
if (!isRecord(entry) || entry.id !== versionId) continue;
|
|
1517
|
-
const
|
|
1560
|
+
const annotations = isRecord(entry.annotations) ? entry.annotations : void 0;
|
|
1561
|
+
const message = annotations?.["workers/message"];
|
|
1562
|
+
let draft = entry;
|
|
1563
|
+
if (annotations?.["workers/triggered_by"] === "secret" && Object.keys(annotations).length === 1 && !isRecord(entry.resources)) {
|
|
1564
|
+
const inspection = await Promise.resolve(options.runWrangler([
|
|
1565
|
+
"versions",
|
|
1566
|
+
"view",
|
|
1567
|
+
versionId,
|
|
1568
|
+
...commonArgs,
|
|
1569
|
+
"--name",
|
|
1570
|
+
options.workerName,
|
|
1571
|
+
"--json"
|
|
1572
|
+
], { capture: true }));
|
|
1573
|
+
if (isCleanCommandExit(inspection)) {
|
|
1574
|
+
const inspected = JSON.parse(inspection.stdout);
|
|
1575
|
+
if (isRecord(inspected) && inspected.id === versionId) draft = inspected;
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1518
1578
|
return {
|
|
1519
1579
|
metadata: typeof message === "string" ? parseCloudflareVersionMessage(message) : null,
|
|
1520
|
-
isSecretOnlyDraft: isWranglerSecretOnlyDraft(
|
|
1580
|
+
isSecretOnlyDraft: isWranglerSecretOnlyDraft(draft)
|
|
1521
1581
|
};
|
|
1522
1582
|
}
|
|
1523
1583
|
} catch {
|
|
@@ -1543,7 +1603,8 @@ async function readRecentCloudflareVersionIds(options, commonArgs) {
|
|
|
1543
1603
|
if (!Array.isArray(versions) || !versions.every((version) => isRecord(version) && typeof version.id === "string" && typeof version.number === "number" && Number.isSafeInteger(version.number))) return null;
|
|
1544
1604
|
return versions.map((version) => ({
|
|
1545
1605
|
id: version.id,
|
|
1546
|
-
number: version.number
|
|
1606
|
+
number: version.number,
|
|
1607
|
+
inspection: version
|
|
1547
1608
|
}));
|
|
1548
1609
|
} catch {
|
|
1549
1610
|
return null;
|
|
@@ -1593,23 +1654,6 @@ async function readCloudflareVersionSecretBindings(options, commonArgs, versionI
|
|
|
1593
1654
|
}
|
|
1594
1655
|
}
|
|
1595
1656
|
/**
|
|
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
1657
|
* The Versions API cannot create a Worker or its initial Durable Object migration history, and
|
|
1614
1658
|
* some accounts do not expose a version preview until their first ordinary deployment. A
|
|
1615
1659
|
* brand-new Worker can therefore need one ordinary deploy before later releases use preview-first
|
|
@@ -1923,4 +1967,108 @@ function defaultSleep(milliseconds) {
|
|
|
1923
1967
|
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
1924
1968
|
}
|
|
1925
1969
|
//#endregion
|
|
1926
|
-
|
|
1970
|
+
//#region src/metadata.ts
|
|
1971
|
+
/**
|
|
1972
|
+
* Share one authenticated transport for the activator's read-only metadata calls.
|
|
1973
|
+
* Writes and unsupported commands retain the pinned Wrangler runner. Responses
|
|
1974
|
+
* keep its JSON contract so activation and concurrency checks remain unchanged.
|
|
1975
|
+
*/
|
|
1976
|
+
function createCloudflareMetadataRunner(options) {
|
|
1977
|
+
let tokenPromise;
|
|
1978
|
+
const common = [
|
|
1979
|
+
...options.configPath ? ["-c", options.configPath] : [],
|
|
1980
|
+
"--env=",
|
|
1981
|
+
"--name",
|
|
1982
|
+
options.workerName,
|
|
1983
|
+
"--json"
|
|
1984
|
+
];
|
|
1985
|
+
const prefix = `${options.apiBaseUrl.replace(/\/$/, "")}/accounts/${encodeURIComponent(options.accountId)}/workers/scripts/${encodeURIComponent(options.workerName)}`;
|
|
1986
|
+
const fetcher = options.fetch ?? globalThis.fetch;
|
|
1987
|
+
const proxyEnv = options.env ?? process.env;
|
|
1988
|
+
const requiresWranglerProxy = Boolean(proxyEnv.https_proxy || proxyEnv.HTTPS_PROXY || proxyEnv.http_proxy || proxyEnv.HTTP_PROXY);
|
|
1989
|
+
const refused = {
|
|
1990
|
+
status: 1,
|
|
1991
|
+
stdout: "",
|
|
1992
|
+
stderr: "Cloudflare uploader integrity check failed.",
|
|
1993
|
+
error: "uploader-changed"
|
|
1994
|
+
};
|
|
1995
|
+
const fallback = (args, runOptions) => options.verifyUploader() ? options.runWrangler(args, runOptions) : refused;
|
|
1996
|
+
return async (args, runOptions) => {
|
|
1997
|
+
let kind;
|
|
1998
|
+
let resource;
|
|
1999
|
+
let rest = [];
|
|
2000
|
+
if (args[0] === "deployments" && args[1] === "status") {
|
|
2001
|
+
kind = "deployment";
|
|
2002
|
+
resource = "/deployments";
|
|
2003
|
+
rest = args.slice(2);
|
|
2004
|
+
} else if (args[0] === "versions" && args[1] === "list") {
|
|
2005
|
+
kind = "versions";
|
|
2006
|
+
resource = "/versions?deployable=true";
|
|
2007
|
+
rest = args.slice(2);
|
|
2008
|
+
} else if (args[0] === "versions" && args[1] === "view" && /^[0-9a-f-]{36}$/i.test(args[2] ?? "")) {
|
|
2009
|
+
kind = "version";
|
|
2010
|
+
resource = `/versions/${encodeURIComponent(args[2])}`;
|
|
2011
|
+
rest = args.slice(3);
|
|
2012
|
+
}
|
|
2013
|
+
if (!kind || !runOptions.capture || rest.length !== common.length || rest.some((value, i) => value !== common[i])) return fallback(args, runOptions);
|
|
2014
|
+
if (requiresWranglerProxy) return fallback(args, runOptions);
|
|
2015
|
+
let response;
|
|
2016
|
+
try {
|
|
2017
|
+
if (!tokenPromise && !options.verifyUploader()) return refused;
|
|
2018
|
+
const token = await (tokenPromise ??= options.getToken());
|
|
2019
|
+
if (token) {
|
|
2020
|
+
response = await fetcher(prefix + resource, {
|
|
2021
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
2022
|
+
redirect: "manual",
|
|
2023
|
+
signal: AbortSignal.timeout(1e4)
|
|
2024
|
+
});
|
|
2025
|
+
if (response.status === 401 || response.status === 403) tokenPromise = void 0;
|
|
2026
|
+
if (response.ok) {
|
|
2027
|
+
const body = await response.json();
|
|
2028
|
+
if (record(body) && body.success === true && record(body.result)) {
|
|
2029
|
+
const result = body.result;
|
|
2030
|
+
if (kind === "deployment" && Array.isArray(result.deployments)) {
|
|
2031
|
+
const latest = result.deployments[0];
|
|
2032
|
+
if (result.deployments.length === 0) return {
|
|
2033
|
+
status: 1,
|
|
2034
|
+
stdout: "",
|
|
2035
|
+
stderr: `The Worker ${options.workerName} has no deployments.`
|
|
2036
|
+
};
|
|
2037
|
+
if (record(latest) && Array.isArray(latest.versions)) return {
|
|
2038
|
+
status: 0,
|
|
2039
|
+
stdout: JSON.stringify(latest),
|
|
2040
|
+
stderr: ""
|
|
2041
|
+
};
|
|
2042
|
+
}
|
|
2043
|
+
if (kind === "version" && result.id === args[2]) return {
|
|
2044
|
+
status: 0,
|
|
2045
|
+
stdout: JSON.stringify(result),
|
|
2046
|
+
stderr: ""
|
|
2047
|
+
};
|
|
2048
|
+
if (kind === "versions" && Array.isArray(result.items)) {
|
|
2049
|
+
const items = result.items;
|
|
2050
|
+
if (items.every((item) => record(item) && record(item.metadata) && typeof item.metadata.created_on === "string")) {
|
|
2051
|
+
const sorted = items.sort((a, b) => a.metadata.created_on.localeCompare(b.metadata.created_on)).slice(-10);
|
|
2052
|
+
return {
|
|
2053
|
+
status: 0,
|
|
2054
|
+
stdout: JSON.stringify(sorted),
|
|
2055
|
+
stderr: ""
|
|
2056
|
+
};
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
} catch {
|
|
2063
|
+
tokenPromise = void 0;
|
|
2064
|
+
} finally {
|
|
2065
|
+
if (response && !response.bodyUsed) await response.body?.cancel().catch(() => {});
|
|
2066
|
+
}
|
|
2067
|
+
return fallback(args, runOptions);
|
|
2068
|
+
};
|
|
2069
|
+
}
|
|
2070
|
+
function record(value) {
|
|
2071
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2072
|
+
}
|
|
2073
|
+
//#endregion
|
|
2074
|
+
export { 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.20.
|
|
3
|
+
"version": "0.20.2",
|
|
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.2",
|
|
24
24
|
"jsonc-parser": "3.3.1"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|