@void/deploy-cloudflare 0.22.0 → 0.24.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.mjs CHANGED
@@ -99,6 +99,43 @@ function isWranglerSecretOnlyDraft(entry) {
99
99
  const runtimeKeys = Object.keys(scriptRuntime);
100
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
101
  }
102
+ const VOID_SECRET_DRAFT_SOURCE = "export default { fetch() { return new Response(\"\"); } };\n";
103
+ /** Prove a cf-created placeholder from the exact immutable version's source. */
104
+ function isVoidSecretOnlyDraft(entry) {
105
+ const metadataFields = /* @__PURE__ */ new Set([
106
+ "id",
107
+ "number",
108
+ "startup_time_ms",
109
+ "created_on",
110
+ "source",
111
+ "author_id",
112
+ "author_email",
113
+ "urls",
114
+ "annotations",
115
+ "compatibility_date",
116
+ "usage_model",
117
+ "main_module",
118
+ "modules",
119
+ "bindings",
120
+ "env",
121
+ "resources"
122
+ ]);
123
+ if (Object.keys(entry).some((key) => !metadataFields.has(key)) || entry.source !== "cf_cli" || !isRecord$1(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;
124
+ const module = entry.modules[0];
125
+ if (!isRecord$1(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;
126
+ const names = /* @__PURE__ */ new Set();
127
+ for (const binding of entry.bindings) {
128
+ if (!isRecord$1(binding) || Object.keys(binding).length !== 2 || typeof binding.name !== "string" || binding.name.length === 0 || names.has(binding.name) || binding.type !== "secret_text") return false;
129
+ names.add(binding.name);
130
+ }
131
+ if (entry.env !== void 0 && (!isRecord$1(entry.env) || Object.keys(entry.env).length !== names.size || Object.entries(entry.env).some(([name, binding]) => !names.has(name) || !isRecord$1(binding) || Object.keys(binding).length !== 1 || binding.type !== "secret_text"))) return false;
132
+ if (entry.resources !== void 0) {
133
+ if (!isRecord$1(entry.resources) || Object.keys(entry.resources).length !== 1) return false;
134
+ const bindings = entry.resources.bindings;
135
+ if (!Array.isArray(bindings) || !isDeepStrictEqual(bindings, entry.bindings)) return false;
136
+ }
137
+ return true;
138
+ }
102
139
  function isRecord$1(value) {
103
140
  return typeof value === "object" && value !== null && !Array.isArray(value);
104
141
  }
@@ -114,26 +151,19 @@ async function readExistingCloudflareWorker(options) {
114
151
  if (!worker) throw new Error(`Cloudflare Worker "${options.workerName}" does not have an active version.`);
115
152
  return worker.snapshot;
116
153
  }
117
- async function readCloudflareWorker(options, allowMissing) {
118
- const target = [
119
- "-c",
120
- options.configPath,
121
- "--env=",
122
- "--name",
123
- options.workerName
124
- ];
125
- const read = async (args, missingIsNull = false) => {
126
- const result = await options.runWrangler([
127
- ...args,
128
- ...target,
129
- "--json"
130
- ], { capture: true });
154
+ async function readCloudflareWorker(options, allowMissing, proveSecretDraft = false) {
155
+ const target = {
156
+ configPath: options.configPath,
157
+ workerName: options.workerName
158
+ };
159
+ const read = async (operation, missingIsNull = false) => {
160
+ const result = await options.runWrangler(operation, { capture: true });
131
161
  if (result.status !== 0 || result.signal || result.error) {
132
- if (missingIsNull && isMissingWorkerOutput$1(`${result.stdout}\n${result.stderr}`)) return null;
162
+ 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
163
  throw new CloudflareWorkerInspectionError(`Could not verify existing Cloudflare Worker "${options.workerName}". Check the account, Worker name, and Cloudflare permissions before retrying.`);
134
164
  }
135
165
  try {
136
- const value = JSON.parse(result.stdout);
166
+ const value = result.data;
137
167
  if (record$1(value)) return value;
138
168
  } catch {}
139
169
  throw new Error("Cloudflare returned an invalid existing Worker response.");
@@ -141,21 +171,37 @@ async function readCloudflareWorker(options, allowMissing) {
141
171
  const deployment = options.versionId ? { versions: [{
142
172
  version_id: options.versionId,
143
173
  percentage: 100
144
- }] } : await read(["deployments", "status"], allowMissing);
174
+ }] } : await read({
175
+ type: "deployments.status",
176
+ ...target
177
+ }, allowMissing);
145
178
  if (deployment === null) return null;
146
179
  const versions = deployment.versions;
147
180
  if (allowMissing && Array.isArray(versions) && versions.length === 0) return null;
148
181
  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
182
  const versionId = versions[0].version_id;
150
- const version = await read([
151
- "versions",
152
- "view",
153
- versionId
154
- ]);
183
+ const version = await read({
184
+ type: "versions.view",
185
+ versionId,
186
+ ...target
187
+ });
155
188
  if (version === null) throw new Error("Cloudflare did not return the existing Worker configuration.");
156
189
  const resources = version.resources;
157
190
  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(["deployments", "status"]);
191
+ let isSecretOnlyDraft = isWranglerSecretOnlyDraft(version);
192
+ if (proveSecretDraft && !isSecretOnlyDraft && record$1(version.annotations) && Object.keys(version.annotations).length === 1 && version.annotations["workers/triggered_by"] === "secret") {
193
+ const source = await read({
194
+ type: "versions.view",
195
+ versionId,
196
+ includeSource: true,
197
+ ...target
198
+ });
199
+ if (source?.id === versionId) isSecretOnlyDraft = isVoidSecretOnlyDraft(source);
200
+ }
201
+ const after = options.versionId ? deployment : await read({
202
+ type: "deployments.status",
203
+ ...target
204
+ });
159
205
  if (after === null || !isDeepStrictEqual(deployment.versions, after.versions)) throw new Error("Cloudflare production changed while linking. Retry from the current deployment.");
160
206
  return {
161
207
  snapshot: {
@@ -164,7 +210,7 @@ async function readCloudflareWorker(options, allowMissing) {
164
210
  runtime: resources.script_runtime,
165
211
  script: resources.script
166
212
  },
167
- isSecretOnlyDraft: isWranglerSecretOnlyDraft(version)
213
+ isSecretOnlyDraft
168
214
  };
169
215
  }
170
216
  /** Parse legacy version-detail shapes; this is not authoritative Worker migration state. */
@@ -207,15 +253,11 @@ async function readCloudflareWorkerMigrationTag(options) {
207
253
  return worker.migration_tag;
208
254
  }
209
255
  async function readCloudflareApiAuthentication(runWrangler) {
210
- const result = await runWrangler([
211
- "auth",
212
- "token",
213
- "--json"
214
- ], { capture: true });
256
+ const result = await runWrangler({ type: "auth.token" }, { capture: true });
215
257
  if (result.status !== 0 || result.signal || result.error) throw new Error("Could not read the authenticated Cloudflare session.");
216
258
  let value;
217
259
  try {
218
- value = JSON.parse(result.stdout);
260
+ value = result.data;
219
261
  } catch {
220
262
  throw new Error("Cloudflare returned an invalid authentication response.");
221
263
  }
@@ -236,7 +278,7 @@ async function readCloudflareApiAuthentication(runWrangler) {
236
278
  async function assertCloudflareDurableObjectLifecycleSupported(options) {
237
279
  const migrations = parseDurableObjectMigrations(parseRootWranglerConfig(options.configPath).migrations);
238
280
  if (!migrations.some((migration) => migration.changesClasses)) return;
239
- const existing = await readCloudflareWorker(options, true);
281
+ const existing = await readCloudflareWorker(options, true, true);
240
282
  if (!existing) return;
241
283
  const readMigrationTag = options.readMigrationTag ?? readCloudflareWorkerMigrationTag;
242
284
  const migrationOptions = {
@@ -246,7 +288,7 @@ async function assertCloudflareDurableObjectLifecycleSupported(options) {
246
288
  workerName: options.workerName
247
289
  };
248
290
  const activeTag = await readMigrationTag(migrationOptions);
249
- const after = await readCloudflareWorker(options, false);
291
+ const after = await readCloudflareWorker(options, false, true);
250
292
  const afterTag = await readMigrationTag(migrationOptions);
251
293
  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.");
252
294
  if (activeTag === null && existing.isSecretOnlyDraft && after.isSecretOnlyDraft) return;
@@ -309,9 +351,6 @@ function declarativeClassesMatch(migrations, actualValue) {
309
351
  return record$1(current) && current.type === "durable-object" && current.storage === storage;
310
352
  });
311
353
  }
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
354
  function assertCloudflareBindingsPreserved(before, after, approvedKv = [], options = {}) {
316
355
  const staticAssetsBinding = isAssetsOnlyWorker(before) ? options.staticAssetsBinding : void 0;
317
356
  const addsAssetsBinding = staticAssetsBinding !== void 0 && !before.bindings.some((binding) => binding.name === staticAssetsBinding);
@@ -381,6 +420,9 @@ function assertExistingCloudflareConfig(existing, config, built = false, approve
381
420
  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
421
  }
383
422
  const vars = record$1(config.vars) ? config.vars : {};
423
+ const metadataBindings = existing.bindings.filter((binding) => binding.type === "version_metadata");
424
+ const metadata = record$1(config.version_metadata) ? config.version_metadata : void 0;
425
+ 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
426
  const remote = new Map(existing.bindings.map((binding) => [binding.name, binding]));
385
427
  for (const [name, value] of Object.entries(vars)) {
386
428
  const binding = remote.get(name);
@@ -423,6 +465,7 @@ function assertExistingCloudflareConfig(existing, config, built = false, approve
423
465
  "secret_text",
424
466
  "secret_key"
425
467
  ].includes(String(binding.type))) continue;
468
+ if (binding.type === "version_metadata") continue;
426
469
  if (binding.type === "assets") {
427
470
  if (built && (!record$1(config.assets) || config.assets.binding !== binding.name)) throw new Error(`Keep the existing assets binding ${binding.name} when linking.`);
428
471
  continue;
@@ -478,14 +521,10 @@ async function executeCloudflareDeploy(options) {
478
521
  ok: false,
479
522
  reason: "uploader-changed"
480
523
  };
481
- const result = await Promise.resolve(options.runWrangler([
482
- "deploy",
483
- ...options.configPath ? ["-c", options.configPath] : [],
484
- "--env=",
485
- "--experimental-provision=false",
486
- "--experimental-auto-create=false",
487
- "--autoconfig=false"
488
- ], { capture: false }));
524
+ const result = await Promise.resolve(options.runWrangler({
525
+ type: "worker.deploy",
526
+ ...options.configPath ? { configPath: options.configPath } : {}
527
+ }, { capture: false }));
489
528
  if (!isCleanCommandExit$1(result)) return {
490
529
  ok: false,
491
530
  reason: "deploy-failed",
@@ -558,6 +597,7 @@ function writeCloudflareFrameworkDeployConfig(options) {
558
597
  upsert(["account_id"], options.accountId);
559
598
  upsert(["main"], options.main);
560
599
  upsert(["compatibility_date"], options.compatibilityDate);
600
+ if (options.versionMetadataBinding && parse(content).version_metadata === void 0) upsert(["version_metadata"], { binding: options.versionMetadataBinding });
561
601
  if (options.compatibilityFlags && options.compatibilityFlags.length > 0) {
562
602
  const config = parse(content);
563
603
  const current = Array.isArray(config.compatibility_flags) ? config.compatibility_flags.filter((flag) => typeof flag === "string") : [];
@@ -615,7 +655,8 @@ function renderCloudflareStaticEntry(options) {
615
655
  const config = JSON.stringify({
616
656
  ...options.headerRules?.length ? { headerRules: options.headerRules } : {},
617
657
  ...options.redirectRules?.length ? { redirectRules: options.redirectRules } : {},
618
- ...options.fallbackRules?.length ? { fallbackRules: options.fallbackRules } : {}
658
+ ...options.fallbackRules?.length ? { fallbackRules: options.fallbackRules } : {},
659
+ ...options.versionMetadataBinding ? { versionMetadataBinding: options.versionMetadataBinding } : {}
619
660
  });
620
661
  return [
621
662
  `import { createStaticEdgeHandler } from ${JSON.stringify(options.runtimeImport ?? "@void/edge")};`,
@@ -762,10 +803,6 @@ function cloudflareCommandDiagnostic(result) {
762
803
  }
763
804
  //#endregion
764
805
  //#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
806
  const DURABLE_MIGRATION_VERSION_ERROR = 10211;
770
807
  /** Encode migration state into Cloudflare's version annotation for safe rollback diagnostics. */
771
808
  function encodeCloudflareVersionMessage(migrations, triggerConfig, contentHash) {
@@ -830,31 +867,23 @@ async function deployCloudflareVersionTriggers(options) {
830
867
  const configPath = join(directory, "wrangler.json");
831
868
  try {
832
869
  writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, { mode: 384 });
833
- const deployed = await options.runWrangler([
834
- "triggers",
835
- "deploy",
836
- "-c",
870
+ const deployed = await options.runWrangler({
871
+ type: "triggers.deploy",
837
872
  configPath,
838
- "--env=",
839
- "--name",
840
- options.workerName
841
- ], { capture: false });
873
+ workerName: options.workerName
874
+ }, { capture: false });
842
875
  if (!isCleanCommandExit(deployed)) return deployed;
843
876
  if (options.triggerConfig.crons.length === 0 && options.publishEmptyCrons) {
844
877
  const published = await options.publishEmptyCrons();
845
878
  if (!isCleanCommandExit(published)) return published;
846
879
  }
847
880
  for (const queue of removedQueueConsumers(options.triggerConfig, options.previousTriggerConfig)) {
848
- const removed = await options.runWrangler([
849
- "queues",
850
- "consumer",
851
- "remove",
852
- queue,
853
- options.workerName,
854
- "-c",
855
- configPath,
856
- "--env="
857
- ], { capture: false });
881
+ const removed = await options.runWrangler({
882
+ type: "queues.consumer.remove",
883
+ queueName: queue,
884
+ workerName: options.workerName,
885
+ configPath
886
+ }, { capture: false });
858
887
  if (!isCleanCommandExit(removed)) return removed;
859
888
  }
860
889
  if (options.manageRoutes !== false && !options.triggerConfig.preserveRoutes && hasCustomDomains(options.previousTriggerConfig?.routes) && !hasCustomDomains(options.triggerConfig.routes)) {
@@ -894,11 +923,11 @@ function pickWranglerTriggerContext(source) {
894
923
  * schema-2 snapshot. The bounded loop fails closed if traffic never stabilizes.
895
924
  */
896
925
  async function stabilizeCloudflareVersionTriggers(options) {
897
- const commonArgs = [...options.configPath ? ["-c", options.configPath] : [], "--env="];
926
+ const configPath = options.configPath;
898
927
  let cleanupBaseline = mergeTriggerCleanupBaselines(options.previousTriggerConfig, options.targetTriggerConfig);
899
928
  const attempts = Math.max(1, options.attempts ?? 4);
900
929
  for (let attempt = 0; attempt < attempts; attempt++) {
901
- const owner = await readCurrentProductionVersion(options, commonArgs);
930
+ const owner = await readCurrentProductionVersion(options, configPath);
902
931
  if (owner.kind === "failed") return {
903
932
  ok: false,
904
933
  reason: "status-failed",
@@ -916,7 +945,7 @@ async function stabilizeCloudflareVersionTriggers(options) {
916
945
  const activeVersionId = owner.versionId;
917
946
  let activeTriggerConfig = activeVersionId === options.targetVersionId ? options.targetTriggerConfig : options.knownTriggerConfigs?.[activeVersionId];
918
947
  if (!activeTriggerConfig && activeVersionId !== options.targetVersionId) {
919
- const metadata = await readCloudflareVersionMetadata(options, commonArgs, activeVersionId);
948
+ const metadata = await readCloudflareVersionMetadata(options, configPath, activeVersionId);
920
949
  activeTriggerConfig = metadata?.schema === 2 ? metadata.triggerConfig : void 0;
921
950
  }
922
951
  if (!activeTriggerConfig) return {
@@ -941,7 +970,7 @@ async function stabilizeCloudflareVersionTriggers(options) {
941
970
  activeVersionId
942
971
  };
943
972
  cleanupBaseline = mergeTriggerCleanupBaselines(cleanupBaseline, activeTriggerConfig);
944
- const after = await readCurrentProductionVersion(options, commonArgs);
973
+ const after = await readCurrentProductionVersion(options, configPath);
945
974
  if (after.kind === "failed") return {
946
975
  ok: false,
947
976
  reason: "status-failed",
@@ -1139,6 +1168,15 @@ function resolvePreservedTraffic(desired, previous, workerExists) {
1139
1168
  function isRecord(value) {
1140
1169
  return typeof value === "object" && value !== null && !Array.isArray(value);
1141
1170
  }
1171
+ function isCandidatePreviewUrl(value, workerName, versionId, alias) {
1172
+ try {
1173
+ const url = new URL(value);
1174
+ const label = url.hostname.split(".")[0];
1175
+ return url.protocol === "https:" && !url.username && !url.password && extractWorkersDevSubdomain(value) !== void 0 && (label === `${versionId.slice(0, 8)}-${workerName}` || label === `${alias}-${workerName}`);
1176
+ } catch {
1177
+ return false;
1178
+ }
1179
+ }
1142
1180
  /** Upload, probe, and atomically activate a verified Worker Version. */
1143
1181
  async function activateCloudflareVersion(options) {
1144
1182
  const candidateSecrets = Object.entries(options.candidateSecrets ?? {});
@@ -1159,13 +1197,13 @@ async function activateCloudflareVersion(options) {
1159
1197
  }
1160
1198
  }
1161
1199
  async function activateCloudflareVersionWithSecretsFile(options) {
1162
- const commonArgs = [...options.configPath ? ["-c", options.configPath] : [], "--env="];
1200
+ const configPath = options.configPath;
1163
1201
  if (!options.verifyUploader()) return {
1164
1202
  ok: false,
1165
1203
  reason: "uploader-changed"
1166
1204
  };
1167
1205
  options.onProgress?.("Checking the current Worker deployment...");
1168
- const previous = await readCurrentProductionVersion(options, commonArgs);
1206
+ const previous = await readCurrentProductionVersion(options, configPath);
1169
1207
  if (previous.kind === "failed") return {
1170
1208
  ok: false,
1171
1209
  reason: "status-failed",
@@ -1186,12 +1224,20 @@ async function activateCloudflareVersionWithSecretsFile(options) {
1186
1224
  reason: "uploader-changed"
1187
1225
  };
1188
1226
  const previousVersionId = previous.versionId;
1227
+ if (options.atomic && options.requirePreviewForReadiness) return {
1228
+ ok: false,
1229
+ reason: "preview-unavailable",
1230
+ previousVersionId,
1231
+ rollback: "not-needed",
1232
+ 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."
1233
+ };
1189
1234
  const trustedInheritanceVersionIds = previousVersionId && options.trustedInheritanceVersionIds?.at(-1) === previousVersionId ? [] : options.trustedInheritanceVersionIds;
1190
1235
  let inheritanceBaseline;
1191
1236
  let trustedCandidateChain = false;
1192
1237
  let activeSecretsPinned = false;
1238
+ let pinnedActiveSecrets;
1193
1239
  if (options.expectedPreviousVersionId !== void 0 || options.verifyInheritedSecrets === true) {
1194
- const recent = await readRecentCloudflareVersionIds(options, commonArgs);
1240
+ const recent = await readRecentCloudflareVersionIds(options, configPath);
1195
1241
  if (recent === null) return {
1196
1242
  ok: false,
1197
1243
  reason: "inheritance-source-unavailable"
@@ -1210,13 +1256,13 @@ async function activateCloudflareVersionWithSecretsFile(options) {
1210
1256
  };
1211
1257
  }
1212
1258
  if (options.verifyInheritedSecrets === true && previous.versionId !== latestVersionId && !trustedCandidateChain) {
1213
- const replacesAllRemoteSecrets = await candidateReplacesAllRemoteSecrets(options, commonArgs, previous.versionId, latestVersionId);
1259
+ const replacesAllRemoteSecrets = await candidateReplacesAllRemoteSecrets(options, configPath, previous.versionId, latestVersionId);
1214
1260
  if (replacesAllRemoteSecrets === null) return {
1215
1261
  ok: false,
1216
1262
  reason: "inheritance-source-unavailable"
1217
1263
  };
1218
1264
  if (!replacesAllRemoteSecrets) {
1219
- const activeSecrets = previous.versionId ? await readCloudflareVersionSecretBindings(options, commonArgs, previous.versionId) : null;
1265
+ const activeSecrets = previous.versionId ? await readCloudflareVersionSecretBindings(options, configPath, previous.versionId) : null;
1220
1266
  if (!activeSecrets || !options.pinActiveSecrets || !await options.pinActiveSecrets(previous.versionId, activeSecrets)) return {
1221
1267
  ok: false,
1222
1268
  reason: "inheritance-source-changed",
@@ -1224,16 +1270,17 @@ async function activateCloudflareVersionWithSecretsFile(options) {
1224
1270
  rollback: "not-needed"
1225
1271
  };
1226
1272
  activeSecretsPinned = true;
1273
+ pinnedActiveSecrets = activeSecrets;
1227
1274
  }
1228
1275
  }
1229
1276
  inheritanceBaseline = recent;
1230
1277
  }
1231
1278
  const desiredMetadata = parseCloudflareVersionMessage(options.message);
1232
1279
  let desiredTriggerConfig = desiredMetadata?.schema === 2 ? desiredMetadata.triggerConfig : options.configPath ? readCloudflareTriggerConfig(options.configPath) : triggerConfigFromWrangler({});
1233
- let previousVersion = previousVersionId && desiredMetadata?.schema === 2 ? await readCloudflareVersionInspection(options, commonArgs, previousVersionId, inheritanceBaseline?.map((version) => version.inspection)) : null;
1280
+ let previousVersion = previousVersionId && desiredMetadata?.schema === 2 ? await readCloudflareVersionInspection(options, configPath, previousVersionId, inheritanceBaseline?.map((version) => version.inspection)) : null;
1234
1281
  async function previousIsSecretOnlyDraft() {
1235
1282
  if (!previousVersionId) return false;
1236
- previousVersion ??= await readCloudflareVersionInspection(options, commonArgs, previousVersionId, inheritanceBaseline?.map((version) => version.inspection));
1283
+ previousVersion ??= await readCloudflareVersionInspection(options, configPath, previousVersionId, inheritanceBaseline?.map((version) => version.inspection));
1237
1284
  return previousVersion?.isSecretOnlyDraft === true;
1238
1285
  }
1239
1286
  const previousMetadata = desiredMetadata?.schema === 2 ? previousVersion?.metadata : null;
@@ -1265,59 +1312,142 @@ async function activateCloudflareVersionWithSecretsFile(options) {
1265
1312
  } : versionedPreviousTriggerConfig;
1266
1313
  const previousTriggerConfig = triggerConfigWithQueueConsumerCleanup(versionedPreviousTriggerConfigWithTraffic, options.queueConsumerCleanup);
1267
1314
  const alias = `void-ready-${Date.now().toString(36)}`;
1268
- if (options.atomic && previous.versionId !== null) {
1269
- if (!activeSecretsPinned && options.pinActiveSecrets) {
1270
- const activeSecrets = await readCloudflareVersionSecretBindings(options, commonArgs, previous.versionId);
1271
- if (activeSecrets?.length) await options.pinActiveSecrets(previous.versionId, activeSecrets);
1315
+ const bootstrapVoidDraft = !options.preserveTriggers && !options.requirePreviewForReadiness && previousVersion?.isVoidSecretOnlyDraft === true;
1316
+ if (previous.versionId !== null && (options.atomic || bootstrapVoidDraft)) {
1317
+ if (bootstrapVoidDraft && !activeSecretsPinned && !options.pinActiveSecrets) return {
1318
+ ok: false,
1319
+ reason: "inheritance-source-unavailable"
1320
+ };
1321
+ if (bootstrapVoidDraft || !activeSecretsPinned && options.pinActiveSecrets) {
1322
+ let activeSecrets;
1323
+ try {
1324
+ activeSecrets = await readCloudflareVersionSecretBindings(options, configPath, previous.versionId);
1325
+ } catch {
1326
+ return {
1327
+ ok: false,
1328
+ reason: "inheritance-source-unavailable"
1329
+ };
1330
+ }
1331
+ if (activeSecrets === null) return {
1332
+ ok: false,
1333
+ reason: "inheritance-source-unavailable"
1334
+ };
1335
+ 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 {
1336
+ ok: false,
1337
+ reason: "inheritance-source-changed"
1338
+ };
1339
+ if (!activeSecretsPinned && activeSecrets.length) {
1340
+ let pinned;
1341
+ try {
1342
+ pinned = await options.pinActiveSecrets(previous.versionId, activeSecrets);
1343
+ } catch {
1344
+ return {
1345
+ ok: false,
1346
+ reason: "inheritance-source-unavailable"
1347
+ };
1348
+ }
1349
+ if (!pinned) return {
1350
+ ok: false,
1351
+ reason: "inheritance-source-changed"
1352
+ };
1353
+ }
1272
1354
  }
1273
1355
  return bootstrapFirstDeployment({
1274
1356
  ...options,
1275
1357
  message
1276
- }, commonArgs, previous.versionId, true);
1358
+ }, configPath, previous.versionId, options.atomic === true, previousTriggerConfig);
1359
+ }
1360
+ if (previous.versionId !== null && !activeSecretsPinned && options.pinActiveSecrets && options.candidateSecretsFilePath === void 0) {
1361
+ let activeSecrets;
1362
+ try {
1363
+ activeSecrets = await readCloudflareVersionSecretBindings(options, configPath, previous.versionId);
1364
+ } catch {
1365
+ return {
1366
+ ok: false,
1367
+ reason: "inheritance-source-unavailable"
1368
+ };
1369
+ }
1370
+ if (activeSecrets === null) return {
1371
+ ok: false,
1372
+ reason: "inheritance-source-unavailable"
1373
+ };
1374
+ if (activeSecrets.some((secret) => !secret.name.trim() || secret.name.trim() !== secret.name) || new Set(activeSecrets.map((secret) => secret.name)).size !== activeSecrets.length) return {
1375
+ ok: false,
1376
+ reason: "inheritance-source-changed"
1377
+ };
1378
+ if (activeSecrets.length) {
1379
+ let pinned;
1380
+ try {
1381
+ pinned = await options.pinActiveSecrets(previous.versionId, activeSecrets);
1382
+ } catch {
1383
+ return {
1384
+ ok: false,
1385
+ reason: "inheritance-source-unavailable"
1386
+ };
1387
+ }
1388
+ if (!pinned) return {
1389
+ ok: false,
1390
+ reason: "inheritance-source-changed"
1391
+ };
1392
+ activeSecretsPinned = true;
1393
+ }
1394
+ }
1395
+ if (activeSecretsPinned) {
1396
+ if (!options.verifyUploader()) return {
1397
+ ok: false,
1398
+ reason: "uploader-changed"
1399
+ };
1400
+ const owner = await readCurrentProductionVersion(options, configPath);
1401
+ if (owner.kind === "failed") return {
1402
+ ok: false,
1403
+ reason: "status-failed",
1404
+ status: owner.status
1405
+ };
1406
+ if (owner.kind !== "ok" || owner.versionId !== previous.versionId || !productionVersionPercentagesEqual(owner.versions, [{
1407
+ versionId: previous.versionId,
1408
+ percentage: 100
1409
+ }])) return {
1410
+ ok: false,
1411
+ reason: "deployment-changed",
1412
+ activeVersionId: owner.kind === "ok" ? owner.versionId : null,
1413
+ rollback: "not-needed"
1414
+ };
1277
1415
  }
1278
1416
  options.onProgress?.("Uploading the Worker and assets...");
1279
- const upload = await Promise.resolve(options.runWrangler([
1280
- "versions",
1281
- "upload",
1282
- ...commonArgs,
1283
- "--name",
1284
- options.workerName,
1285
- "--message",
1417
+ const upload = await Promise.resolve(options.runWrangler({
1418
+ type: "versions.upload",
1419
+ configPath,
1420
+ workerName: options.workerName,
1286
1421
  message,
1287
- ...options.candidateSecretsFilePath ? ["--secrets-file", options.candidateSecretsFilePath] : [],
1288
- ...options.assetsDirectoryOverride ? ["--assets", options.assetsDirectoryOverride] : [],
1289
- "--preview-alias",
1290
- alias,
1291
- "--strict",
1292
- "--experimental-provision=false",
1293
- "--experimental-auto-create=false"
1294
- ], { capture: true }));
1422
+ secretsFilePath: options.candidateSecretsFilePath,
1423
+ assetsDirectory: options.assetsDirectoryOverride,
1424
+ previewAlias: alias
1425
+ }, { capture: true }));
1295
1426
  if (!isCleanCommandExit(upload)) {
1296
- const output = stripVTControlCharacters(`${upload.stdout}\n${upload.stderr}`);
1297
- const cloudflareErrorCode = parseCloudflareErrorCode(output);
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);
1427
+ const cloudflareErrorCode = upload.errorCode;
1428
+ const isBootstrapError = isCompletedCommand(upload) && (upload.failure === "durable-migration" || upload.failure === "not-found" || cloudflareErrorCode === DURABLE_MIGRATION_VERSION_ERROR || cloudflareErrorCode === 10007);
1299
1429
  if (!options.preserveTriggers && isBootstrapError && (previous.versionId === null || options.allowFirstDeploymentBootstrap === true || await previousIsSecretOnlyDraft())) return bootstrapFirstDeployment({
1300
1430
  ...options,
1301
1431
  message
1302
- }, commonArgs, previous.versionId);
1432
+ }, configPath, previous.versionId, false, previousTriggerConfig);
1303
1433
  return {
1304
1434
  ok: false,
1305
1435
  reason: "upload-failed",
1306
1436
  status: upload.status,
1307
1437
  ...commandDiagnosticFields(upload),
1308
1438
  ...cloudflareErrorCode === void 0 ? {} : { cloudflareErrorCode },
1309
- ...isContainersUnavailable(output) ? { containersUnavailable: true } : {}
1439
+ ...isContainersUnavailable(upload) ? { containersUnavailable: true } : {}
1310
1440
  };
1311
1441
  }
1312
- const output = `${upload.stdout}\n${upload.stderr}`;
1313
- const versionId = output.match(VERSION_ID_RE)?.[1];
1442
+ const uploaded = readUploadResult(upload);
1443
+ const versionId = uploaded?.versionId;
1314
1444
  if (!versionId) return {
1315
1445
  ok: false,
1316
1446
  reason: "upload-output-invalid"
1317
1447
  };
1318
1448
  options.onProgress?.("Verifying the uploaded Worker...");
1319
1449
  if (inheritanceBaseline) {
1320
- const recent = await readRecentCloudflareVersionIds(options, commonArgs);
1450
+ const recent = await readRecentCloudflareVersionIds(options, configPath);
1321
1451
  const previousLatest = inheritanceBaseline.at(-1);
1322
1452
  const expected = [...inheritanceBaseline.map((version) => version.id), versionId].slice(-10);
1323
1453
  if (recent === null) return {
@@ -1342,7 +1472,15 @@ async function activateCloudflareVersionWithSecretsFile(options) {
1342
1472
  if (!options.validateUploadedVersion && !inheritanceBaseline) throw new Error("A Worker candidate cannot be recorded without validating its upload and version sequence.");
1343
1473
  await options.recordVerifiedCandidate(versionId, previous.versionId);
1344
1474
  }
1345
- const reportedPreviewUrl = output.match(PREVIEW_URL_RE)?.[1];
1475
+ const reportedPreviewUrl = desiredTriggerConfig.preview_urls || desiredTriggerConfig.preservePreviewUrls || desiredTriggerConfig.preserve ? uploaded?.previewUrl : void 0;
1476
+ if (options.requirePreviewForReadiness && (!reportedPreviewUrl || !isCandidatePreviewUrl(reportedPreviewUrl, options.workerName, versionId, alias))) return {
1477
+ ok: false,
1478
+ reason: "preview-unavailable",
1479
+ versionId,
1480
+ previousVersionId,
1481
+ rollback: "not-needed",
1482
+ 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."
1483
+ };
1346
1484
  let authenticatedWorkersDevSubdomain = reportedPreviewUrl ? extractWorkersDevSubdomain(reportedPreviewUrl) : void 0;
1347
1485
  let readinessUrl = reportedPreviewUrl;
1348
1486
  let readinessUsesCustomRoute = false;
@@ -1351,7 +1489,7 @@ async function activateCloudflareVersionWithSecretsFile(options) {
1351
1489
  if (!options.preserveTriggers && (previous.versionId === null || options.allowFirstDeploymentBootstrap === true || await previousIsSecretOnlyDraft())) return bootstrapFirstDeployment({
1352
1490
  ...options,
1353
1491
  message
1354
- }, commonArgs, previous.versionId);
1492
+ }, configPath, previous.versionId, false, previousTriggerConfig);
1355
1493
  readinessUrl = desiredTriggerConfig.workers_dev ? workersDevReadinessUrl(options.workerName, options.workersDevSubdomain) : customRouteReadinessUrl(desiredTriggerConfig.routes);
1356
1494
  readinessUsesCustomRoute = !desiredTriggerConfig.workers_dev && readinessUrl !== void 0;
1357
1495
  if (!readinessUrl) return {
@@ -1369,11 +1507,11 @@ async function activateCloudflareVersionWithSecretsFile(options) {
1369
1507
  previousVersionId: previous.versionId,
1370
1508
  rollback: "not-needed"
1371
1509
  };
1372
- const stagingOwnerFailure = await verifyReadinessStagingOwner(options, commonArgs, previous.versionId, versionId);
1510
+ const stagingOwnerFailure = await verifyReadinessStagingOwner(options, configPath, previous.versionId, versionId);
1373
1511
  if (stagingOwnerFailure) return stagingOwnerFailure;
1374
- const staged = await deployVersionPercentages(options, commonArgs, [`${previous.versionId}@100`, `${versionId}@0`], message);
1512
+ const staged = await deployVersionPercentages(options, configPath, [`${previous.versionId}@100`, `${versionId}@0`], message);
1375
1513
  if (!isCleanCommandExit(staged)) {
1376
- const cloudflareErrorCode = parseCloudflareErrorCode(`${staged.stdout}\n${staged.stderr}`);
1514
+ const cloudflareErrorCode = staged.errorCode;
1377
1515
  return {
1378
1516
  ok: false,
1379
1517
  reason: "readiness-staging-failed",
@@ -1388,8 +1526,8 @@ async function activateCloudflareVersionWithSecretsFile(options) {
1388
1526
  stagedVersionOverride = true;
1389
1527
  }
1390
1528
  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) {
1529
+ 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);
1530
+ if (ready === "access-denied" && !stagedVersionOverride && previous.versionId !== null && !options.requirePreviewForReadiness) {
1393
1531
  const stableReadinessUrl = desiredTriggerConfig.workers_dev ? workersDevReadinessUrl(options.workerName, extractWorkersDevSubdomain(readinessUrl) ?? options.workersDevSubdomain) : customRouteReadinessUrl(desiredTriggerConfig.routes);
1394
1532
  if (stableReadinessUrl && stableReadinessUrl !== readinessUrl) {
1395
1533
  if (!options.verifyUploader()) return {
@@ -1399,11 +1537,11 @@ async function activateCloudflareVersionWithSecretsFile(options) {
1399
1537
  previousVersionId: previous.versionId,
1400
1538
  rollback: "not-needed"
1401
1539
  };
1402
- const stagingOwnerFailure = await verifyReadinessStagingOwner(options, commonArgs, previous.versionId, versionId);
1540
+ const stagingOwnerFailure = await verifyReadinessStagingOwner(options, configPath, previous.versionId, versionId);
1403
1541
  if (stagingOwnerFailure) return stagingOwnerFailure;
1404
- const staged = await deployVersionPercentages(options, commonArgs, [`${previous.versionId}@100`, `${versionId}@0`], message);
1542
+ const staged = await deployVersionPercentages(options, configPath, [`${previous.versionId}@100`, `${versionId}@0`], message);
1405
1543
  if (!isCleanCommandExit(staged)) {
1406
- const cloudflareErrorCode = parseCloudflareErrorCode(`${staged.stdout}\n${staged.stderr}`);
1544
+ const cloudflareErrorCode = staged.errorCode;
1407
1545
  return {
1408
1546
  ok: false,
1409
1547
  reason: "readiness-staging-failed",
@@ -1419,7 +1557,7 @@ async function activateCloudflareVersionWithSecretsFile(options) {
1419
1557
  authenticatedWorkersDevSubdomain = extractWorkersDevSubdomain(stableReadinessUrl);
1420
1558
  readinessUsesCustomRoute = !desiredTriggerConfig.workers_dev;
1421
1559
  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);
1560
+ 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
1561
  }
1424
1562
  }
1425
1563
  if (ready !== "ready") return {
@@ -1427,16 +1565,16 @@ async function activateCloudflareVersionWithSecretsFile(options) {
1427
1565
  reason: ready === "access-denied" ? "readiness-access-denied" : "readiness-failed",
1428
1566
  versionId,
1429
1567
  previousVersionId: previous.versionId,
1430
- rollback: stagedVersionOverride ? await restorePreviousVersion(options, commonArgs, previous.versionId, "readiness failed", [stagedVersionPercentages(previous.versionId, versionId)]) : "not-needed"
1568
+ rollback: stagedVersionOverride ? await restorePreviousVersion(options, configPath, previous.versionId, "readiness failed", [stagedVersionPercentages(previous.versionId, versionId)]) : "not-needed"
1431
1569
  };
1432
1570
  if (!options.verifyUploader()) return {
1433
1571
  ok: false,
1434
1572
  reason: "uploader-changed",
1435
1573
  versionId,
1436
1574
  previousVersionId: previous.versionId,
1437
- rollback: stagedVersionOverride ? await restorePreviousVersion(options, commonArgs, previous.versionId, "uploader verification failed", [stagedVersionPercentages(previous.versionId, versionId)]) : "not-needed"
1575
+ rollback: stagedVersionOverride ? await restorePreviousVersion(options, configPath, previous.versionId, "uploader verification failed", [stagedVersionPercentages(previous.versionId, versionId)]) : "not-needed"
1438
1576
  };
1439
- const ownerBeforeActivation = await readCurrentProductionVersion(options, commonArgs);
1577
+ const ownerBeforeActivation = await readCurrentProductionVersion(options, configPath);
1440
1578
  if (ownerBeforeActivation.kind === "failed") return {
1441
1579
  ok: false,
1442
1580
  reason: "status-failed",
@@ -1464,14 +1602,14 @@ async function activateCloudflareVersionWithSecretsFile(options) {
1464
1602
  triggerRepair: "succeeded"
1465
1603
  };
1466
1604
  options.onProgress?.("Activating the Worker...");
1467
- const activate = await deployVersionPercentages(options, commonArgs, [`${versionId}@100`], message);
1605
+ const activate = await deployVersionPercentages(options, configPath, [`${versionId}@100`], message);
1468
1606
  if (!isCleanCommandExit(activate)) return {
1469
1607
  ok: false,
1470
1608
  reason: "activation-failed",
1471
1609
  status: activate.status,
1472
1610
  versionId,
1473
1611
  previousVersionId: previous.versionId,
1474
- rollback: stagedVersionOverride ? await restorePreviousVersion(options, commonArgs, previous.versionId, "version activation failed", [stagedVersionPercentages(previous.versionId, versionId), [{
1612
+ rollback: stagedVersionOverride ? await restorePreviousVersion(options, configPath, previous.versionId, "version activation failed", [stagedVersionPercentages(previous.versionId, versionId), [{
1475
1613
  versionId,
1476
1614
  percentage: 100
1477
1615
  }]]) : "not-needed"
@@ -1484,7 +1622,7 @@ async function activateCloudflareVersionWithSecretsFile(options) {
1484
1622
  rollback: previous.versionId ? "unsafe" : "unavailable"
1485
1623
  };
1486
1624
  if (options.preserveTriggers) {
1487
- const owner = await readCurrentProductionVersion(options, commonArgs);
1625
+ const owner = await readCurrentProductionVersion(options, configPath);
1488
1626
  if (owner.kind !== "ok" || owner.versionId !== versionId) return {
1489
1627
  ok: false,
1490
1628
  reason: "deployment-changed",
@@ -1535,7 +1673,7 @@ async function activateCloudflareVersionWithSecretsFile(options) {
1535
1673
  previousVersionId: previous.versionId,
1536
1674
  rollback: "unsafe"
1537
1675
  };
1538
- const ownerBeforeCompensation = await readCurrentProductionVersion(options, commonArgs);
1676
+ const ownerBeforeCompensation = await readCurrentProductionVersion(options, configPath);
1539
1677
  if (ownerBeforeCompensation.kind !== "ok" || ownerBeforeCompensation.versionId === null) return {
1540
1678
  ok: false,
1541
1679
  reason: "triggers-failed",
@@ -1567,7 +1705,7 @@ async function activateCloudflareVersionWithSecretsFile(options) {
1567
1705
  triggerRepair: repair.ok ? "succeeded" : repair.reason === "metadata-unavailable" ? "unavailable" : "failed"
1568
1706
  };
1569
1707
  }
1570
- const rollback = await restorePreviousVersionAndTriggers(options, commonArgs, previous.versionId, "trigger activation failed", desiredTriggerConfig, versionId, versionedPreviousTriggerConfigWithTraffic);
1708
+ const rollback = await restorePreviousVersionAndTriggers(options, configPath, previous.versionId, "trigger activation failed", desiredTriggerConfig, versionId, versionedPreviousTriggerConfigWithTraffic);
1571
1709
  return {
1572
1710
  ok: false,
1573
1711
  reason: "triggers-failed",
@@ -1600,8 +1738,8 @@ async function activateCloudflareVersionWithSecretsFile(options) {
1600
1738
  ...workersDevSubdomainResult(readinessUrl)
1601
1739
  };
1602
1740
  }
1603
- async function restorePreviousVersionAndTriggers(options, commonArgs, previousVersionId, cause, failedTriggerConfig, failedVersionId, capturedTriggerConfig) {
1604
- const traffic = await restorePreviousVersion(options, commonArgs, previousVersionId, cause, [[{
1741
+ async function restorePreviousVersionAndTriggers(options, configPath, previousVersionId, cause, failedTriggerConfig, failedVersionId, capturedTriggerConfig) {
1742
+ const traffic = await restorePreviousVersion(options, configPath, previousVersionId, cause, [[{
1605
1743
  versionId: failedVersionId,
1606
1744
  percentage: 100
1607
1745
  }]]);
@@ -1609,7 +1747,7 @@ async function restorePreviousVersionAndTriggers(options, commonArgs, previousVe
1609
1747
  if (!options.verifyUploader()) return "unsafe";
1610
1748
  let targetTriggerConfig = capturedTriggerConfig;
1611
1749
  if (!targetTriggerConfig) {
1612
- const metadata = await readCloudflareVersionMetadata(options, commonArgs, previousVersionId);
1750
+ const metadata = await readCloudflareVersionMetadata(options, configPath, previousVersionId);
1613
1751
  if (metadata?.schema !== 2) return "unavailable";
1614
1752
  targetTriggerConfig = metadata.triggerConfig;
1615
1753
  }
@@ -1628,23 +1766,20 @@ async function restorePreviousVersionAndTriggers(options, commonArgs, previousVe
1628
1766
  });
1629
1767
  return triggers.ok && triggers.targetOwnsTraffic ? "succeeded" : "failed";
1630
1768
  }
1631
- async function readCloudflareVersionMetadata(options, commonArgs, versionId) {
1632
- return (await readCloudflareVersionInspection(options, commonArgs, versionId))?.metadata ?? null;
1769
+ async function readCloudflareVersionMetadata(options, configPath, versionId) {
1770
+ return (await readCloudflareVersionInspection(options, configPath, versionId))?.metadata ?? null;
1633
1771
  }
1634
- async function readCloudflareVersionInspection(options, commonArgs, versionId, knownVersions) {
1772
+ async function readCloudflareVersionInspection(options, configPath, versionId, knownVersions) {
1635
1773
  try {
1636
1774
  let versions = knownVersions;
1637
1775
  if (versions === void 0) {
1638
- const result = await options.runWrangler([
1639
- "versions",
1640
- "list",
1641
- ...commonArgs,
1642
- "--name",
1643
- options.workerName,
1644
- "--json"
1645
- ], { capture: true });
1776
+ const result = await options.runWrangler({
1777
+ type: "versions.list",
1778
+ configPath,
1779
+ workerName: options.workerName
1780
+ }, { capture: true });
1646
1781
  if (!isCleanCommandExit(result)) return null;
1647
- versions = JSON.parse(result.stdout);
1782
+ versions = result.data;
1648
1783
  }
1649
1784
  if (!Array.isArray(versions)) return null;
1650
1785
  for (const entry of versions) {
@@ -1652,24 +1787,25 @@ async function readCloudflareVersionInspection(options, commonArgs, versionId, k
1652
1787
  const annotations = isRecord(entry.annotations) ? entry.annotations : void 0;
1653
1788
  const message = annotations?.["workers/message"];
1654
1789
  let draft = entry;
1655
- 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",
1790
+ if (annotations?.["workers/triggered_by"] === "secret" && Object.keys(annotations).length === 1 && !isWranglerSecretOnlyDraft(entry)) {
1791
+ const inspection = await Promise.resolve(options.runWrangler({
1792
+ type: "versions.view",
1659
1793
  versionId,
1660
- ...commonArgs,
1661
- "--name",
1662
- options.workerName,
1663
- "--json"
1664
- ], { capture: true }));
1794
+ configPath,
1795
+ workerName: options.workerName,
1796
+ includeSource: true
1797
+ }, { capture: true }));
1665
1798
  if (isCleanCommandExit(inspection)) {
1666
- const inspected = JSON.parse(inspection.stdout);
1799
+ const inspected = inspection.data;
1667
1800
  if (isRecord(inspected) && inspected.id === versionId) draft = inspected;
1668
1801
  }
1669
1802
  }
1803
+ const isVoidDraft = isVoidSecretOnlyDraft(draft);
1670
1804
  return {
1671
1805
  metadata: typeof message === "string" ? parseCloudflareVersionMessage(message) : null,
1672
- isSecretOnlyDraft: isWranglerSecretOnlyDraft(draft)
1806
+ isSecretOnlyDraft: isWranglerSecretOnlyDraft(draft) || isVoidSecretOnlyDraft(draft),
1807
+ isVoidSecretOnlyDraft: isVoidDraft,
1808
+ voidDraftSecretNames: isVoidDraft ? draft.bindings.map((binding) => binding.name) : null
1673
1809
  };
1674
1810
  }
1675
1811
  } catch {
@@ -1677,21 +1813,18 @@ async function readCloudflareVersionInspection(options, commonArgs, versionId, k
1677
1813
  }
1678
1814
  return null;
1679
1815
  }
1680
- async function readRecentCloudflareVersionIds(options, commonArgs) {
1681
- const result = await Promise.resolve(options.runWrangler([
1682
- "versions",
1683
- "list",
1684
- ...commonArgs,
1685
- "--name",
1686
- options.workerName,
1687
- "--json"
1688
- ], { capture: true }));
1816
+ async function readRecentCloudflareVersionIds(options, configPath) {
1817
+ const result = await Promise.resolve(options.runWrangler({
1818
+ type: "versions.list",
1819
+ configPath,
1820
+ workerName: options.workerName
1821
+ }, { capture: true }));
1689
1822
  if (!isCleanCommandExit(result)) {
1690
- if (isMissingWorkerOutput(`${result.stdout}\n${result.stderr}`)) return [];
1823
+ if (isCompletedCommand(result) && (result.failure === "not-found" || result.errorCode === 10007)) return [];
1691
1824
  return null;
1692
1825
  }
1693
1826
  try {
1694
- const versions = JSON.parse(result.stdout);
1827
+ const versions = result.data;
1695
1828
  if (!Array.isArray(versions) || !versions.every((version) => isRecord(version) && typeof version.id === "string" && typeof version.number === "number" && Number.isSafeInteger(version.number))) return null;
1696
1829
  return versions.map((version) => ({
1697
1830
  id: version.id,
@@ -1705,29 +1838,26 @@ async function readRecentCloudflareVersionIds(options, commonArgs) {
1705
1838
  function arraysEqual(left, right) {
1706
1839
  return left.length === right.length && left.every((value, index) => value === right[index]);
1707
1840
  }
1708
- async function candidateReplacesAllRemoteSecrets(options, commonArgs, activeVersionId, latestVersionId) {
1841
+ async function candidateReplacesAllRemoteSecrets(options, configPath, activeVersionId, latestVersionId) {
1709
1842
  const versionIds = [...new Set([activeVersionId, latestVersionId].filter(Boolean))];
1710
1843
  const secretBindings = [];
1711
1844
  for (const versionId of versionIds) {
1712
- const bindings = await readCloudflareVersionSecretBindings(options, commonArgs, versionId);
1845
+ const bindings = await readCloudflareVersionSecretBindings(options, configPath, versionId);
1713
1846
  if (bindings === null) return null;
1714
1847
  secretBindings.push(bindings);
1715
1848
  }
1716
1849
  return secretBindings.every((bindings) => bindings.every((binding) => binding.type === "secret_text" && Object.hasOwn(options.candidateSecrets ?? {}, binding.name)));
1717
1850
  }
1718
- async function readCloudflareVersionSecretBindings(options, commonArgs, versionId) {
1719
- const result = await Promise.resolve(options.runWrangler([
1720
- "versions",
1721
- "view",
1851
+ async function readCloudflareVersionSecretBindings(options, configPath, versionId) {
1852
+ const result = await Promise.resolve(options.runWrangler({
1853
+ type: "versions.view",
1722
1854
  versionId,
1723
- ...commonArgs,
1724
- "--name",
1725
- options.workerName,
1726
- "--json"
1727
- ], { capture: true }));
1855
+ configPath,
1856
+ workerName: options.workerName
1857
+ }, { capture: true }));
1728
1858
  if (!isCleanCommandExit(result)) return null;
1729
1859
  try {
1730
- const version = JSON.parse(result.stdout);
1860
+ const version = result.data;
1731
1861
  if (!isRecord(version) || version.id !== versionId || !isRecord(version.resources)) return null;
1732
1862
  const bindings = version.resources.bindings;
1733
1863
  if (!Array.isArray(bindings) || !bindings.every(isRecord)) return null;
@@ -1751,19 +1881,22 @@ async function readCloudflareVersionSecretBindings(options, commonArgs, versionI
1751
1881
  * brand-new Worker can therefore need one ordinary deploy before later releases use preview-first
1752
1882
  * version activation. This fallback requires prior proof that no application had been deployed.
1753
1883
  */
1754
- async function bootstrapFirstDeployment(options, commonArgs, previousVersionId, atomic = false) {
1884
+ async function bootstrapFirstDeployment(options, configPath, previousVersionId, atomic = false, previousTriggerConfig) {
1755
1885
  if (!options.verifyUploader()) return {
1756
1886
  ok: false,
1757
1887
  reason: "uploader-changed"
1758
1888
  };
1759
- if (atomic) {
1760
- const current = await readCurrentProductionVersion(options, commonArgs);
1889
+ {
1890
+ const current = await readCurrentProductionVersion(options, configPath);
1761
1891
  if (current.kind === "failed") return {
1762
1892
  ok: false,
1763
1893
  reason: "status-failed",
1764
1894
  status: current.status
1765
1895
  };
1766
- if (current.kind !== "ok" || current.versionId !== previousVersionId) return {
1896
+ if (current.kind !== "ok" || current.versionId !== previousVersionId || !productionVersionPercentagesEqual(current.versions, previousVersionId ? [{
1897
+ versionId: previousVersionId,
1898
+ percentage: 100
1899
+ }] : [])) return {
1767
1900
  ok: false,
1768
1901
  reason: "deployment-changed",
1769
1902
  activeVersionId: current.kind === "ok" ? current.versionId : null,
@@ -1771,53 +1904,172 @@ async function bootstrapFirstDeployment(options, commonArgs, previousVersionId,
1771
1904
  };
1772
1905
  }
1773
1906
  options.onProgress?.(atomic ? "Publishing the Worker atomically..." : "Publishing the first Worker deployment...");
1774
- const bootstrap = await Promise.resolve(options.runWrangler([
1775
- "deploy",
1776
- ...commonArgs,
1777
- "--name",
1778
- options.workerName,
1779
- "--message",
1780
- options.message,
1781
- ...options.candidateSecretsFilePath ? ["--secrets-file", options.candidateSecretsFilePath] : [],
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}`;
1907
+ const bootstrap = await Promise.resolve(options.runWrangler({
1908
+ type: "worker.deploy",
1909
+ configPath,
1910
+ workerName: options.workerName,
1911
+ message: options.message,
1912
+ secretsFilePath: options.candidateSecretsFilePath,
1913
+ assetsDirectory: options.assetsDirectoryOverride
1914
+ }, { capture: true }));
1789
1915
  if (!isCleanCommandExit(bootstrap)) {
1790
- const cloudflareErrorCode = parseCloudflareErrorCode(output);
1916
+ const cloudflareErrorCode = bootstrap.errorCode;
1791
1917
  return {
1792
1918
  ok: false,
1793
1919
  reason: atomic ? "atomic-failed" : "bootstrap-failed",
1794
1920
  status: bootstrap.status,
1795
1921
  ...commandDiagnosticFields(bootstrap),
1796
1922
  ...cloudflareErrorCode === void 0 ? {} : { cloudflareErrorCode },
1797
- ...isContainersUnavailable(output) ? { containersUnavailable: true } : {}
1923
+ ...isContainersUnavailable(bootstrap) ? { containersUnavailable: true } : {}
1798
1924
  };
1799
1925
  }
1800
- const versionId = output.match(VERSION_ID_RE)?.[1];
1926
+ const published = readUploadResult(bootstrap);
1927
+ const versionId = published?.versionId;
1801
1928
  const desiredMetadata = parseCloudflareVersionMessage(options.message);
1802
1929
  const desiredTriggerConfig = desiredMetadata?.schema === 2 ? desiredMetadata.triggerConfig : options.configPath ? readCloudflareTriggerConfig(options.configPath) : triggerConfigFromWrangler({});
1803
- const reportedUrl = output.match(PREVIEW_URL_RE)?.[1] ?? output.match(WORKERS_DEV_URL_RE)?.[1];
1804
- const reportedOrConfiguredUrl = reportedUrl ?? (desiredTriggerConfig.workers_dev ? workersDevReadinessUrl(options.workerName, options.workersDevSubdomain) : customRouteReadinessUrl(desiredTriggerConfig.routes));
1930
+ const reportedOrConfiguredUrl = published?.previewUrl ?? published?.workersDevUrl ?? (desiredTriggerConfig.workers_dev ? workersDevReadinessUrl(options.workerName, options.workersDevSubdomain) : customRouteReadinessUrl(desiredTriggerConfig.routes));
1805
1931
  if (!versionId || !reportedOrConfiguredUrl) return {
1806
1932
  ok: false,
1807
1933
  reason: atomic ? "atomic-output-invalid" : "bootstrap-output-invalid",
1808
1934
  previousVersionId,
1809
1935
  ...versionId ? { versionId } : {}
1810
1936
  };
1937
+ if (!options.verifyUploader()) return {
1938
+ ok: false,
1939
+ reason: "uploader-changed",
1940
+ versionId,
1941
+ previousVersionId,
1942
+ rollback: "unsafe"
1943
+ };
1944
+ const triggerOptions = {
1945
+ runWrangler: options.runWrangler,
1946
+ workerName: options.workerName,
1947
+ configPath: options.configPath,
1948
+ targetVersionId: versionId,
1949
+ targetTriggerConfig: options.preserveTriggers ? {
1950
+ ...desiredTriggerConfig,
1951
+ preserve: true
1952
+ } : desiredTriggerConfig,
1953
+ previousTriggerConfig,
1954
+ ...previousVersionId && previousTriggerConfig ? { knownTriggerConfigs: { [previousVersionId]: previousTriggerConfig } } : {},
1955
+ publishEmptyCustomDomains: options.publishEmptyCustomDomains,
1956
+ publishEmptyCrons: options.publishEmptyCrons
1957
+ };
1958
+ const verifyPublishedOwner = async () => {
1959
+ const owner = await readCurrentProductionVersion(options, configPath);
1960
+ if (owner.kind === "failed") return {
1961
+ ok: false,
1962
+ reason: "status-failed",
1963
+ status: owner.status,
1964
+ versionId,
1965
+ previousVersionId,
1966
+ rollback: "unsafe"
1967
+ };
1968
+ if (owner.kind !== "ok" || owner.versionId !== versionId || !productionVersionPercentagesEqual(owner.versions, [{
1969
+ versionId,
1970
+ percentage: 100
1971
+ }])) {
1972
+ const repair = owner.kind === "ok" && owner.versionId && owner.versionId !== versionId && options.verifyUploader() ? await stabilizeCloudflareVersionTriggers({
1973
+ ...triggerOptions,
1974
+ targetTriggerConfig: {
1975
+ ...desiredTriggerConfig,
1976
+ preserve: true
1977
+ }
1978
+ }) : null;
1979
+ return {
1980
+ ok: false,
1981
+ reason: "deployment-changed",
1982
+ versionId,
1983
+ previousVersionId,
1984
+ activeVersionId: repair?.activeVersionId ?? (owner.kind === "ok" ? owner.versionId : null),
1985
+ rollback: "not-needed",
1986
+ ...repair ? { triggerRepair: repair.ok && !repair.targetOwnsTraffic ? "succeeded" : !repair.ok && repair.reason !== "metadata-unavailable" ? "failed" : "unavailable" } : {}
1987
+ };
1988
+ }
1989
+ return null;
1990
+ };
1991
+ const publishedOwnerFailure = await verifyPublishedOwner();
1992
+ if (publishedOwnerFailure) return publishedOwnerFailure;
1993
+ if (options.validateUploadedVersion || options.recordVerifiedCandidate) {
1994
+ options.onProgress?.("Verifying the uploaded Worker...");
1995
+ try {
1996
+ await options.validateUploadedVersion?.(versionId);
1997
+ const validatedOwnerFailure = await verifyPublishedOwner();
1998
+ if (validatedOwnerFailure) return validatedOwnerFailure;
1999
+ if (options.recordVerifiedCandidate) {
2000
+ if (!options.validateUploadedVersion && !options.verifyInheritedSecrets && options.expectedPreviousVersionId === void 0) throw new Error("The bootstrap upload has no verified candidate checkpoint.");
2001
+ await options.recordVerifiedCandidate(versionId, previousVersionId);
2002
+ }
2003
+ } catch {
2004
+ return {
2005
+ ok: false,
2006
+ reason: atomic ? "atomic-failed" : "bootstrap-failed",
2007
+ versionId,
2008
+ previousVersionId,
2009
+ rollback: "unavailable",
2010
+ diagnostic: `Worker version ${versionId} was published, but its upload validation or candidate checkpoint failed. Inspect the current Worker deployment before retrying.`
2011
+ };
2012
+ }
2013
+ const checkpointOwnerFailure = await verifyPublishedOwner();
2014
+ if (checkpointOwnerFailure) return checkpointOwnerFailure;
2015
+ }
2016
+ options.onProgress?.("Updating routes and triggers...");
2017
+ const triggers = await stabilizeCloudflareVersionTriggers(triggerOptions);
2018
+ if (!triggers.ok || !triggers.targetOwnsTraffic) {
2019
+ const superseded = triggers.activeVersionId !== void 0 && triggers.activeVersionId !== null && triggers.activeVersionId !== versionId;
2020
+ return {
2021
+ ok: false,
2022
+ reason: superseded ? "deployment-changed" : "triggers-failed",
2023
+ ...!triggers.ok ? { status: triggers.status } : {},
2024
+ versionId,
2025
+ previousVersionId,
2026
+ activeVersionId: triggers.activeVersionId,
2027
+ rollback: superseded ? "not-needed" : triggers.activeVersionId ? "unavailable" : "unsafe",
2028
+ ...superseded ? { triggerRepair: triggers.ok ? "succeeded" : triggers.reason === "metadata-unavailable" ? "unavailable" : "failed" } : {}
2029
+ };
2030
+ }
1811
2031
  const previewUrl = publishedCloudflareUrl(options.workerName, desiredTriggerConfig, reportedOrConfiguredUrl);
2032
+ const usesCustomDomain = !desiredTriggerConfig.workers_dev && desiredTriggerConfig.routes.some((route) => isRecord(route) && route.custom_domain === true);
2033
+ const customDomainBootstrap = !atomic && usesCustomDomain;
2034
+ const readinessTimeoutMs = customDomainBootstrap ? 3e5 : 6e4;
1812
2035
  options.onProgress?.("Waiting for the Worker to become ready...");
1813
- const ready = await probeReadiness(`${previewUrl.replace(/\/$/, "")}/__void/ready`, options.workerName, reportedUrl || desiredTriggerConfig.workers_dev ? options.readinessHeaders : void 0, extractWorkersDevSubdomain(previewUrl), void 0, options.fetch ?? globalThis.fetch, options.sleep ?? defaultSleep, options.readinessAttempts ?? 30, 6e4);
2036
+ 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
2037
  if (ready !== "ready") return {
1815
2038
  ok: false,
1816
2039
  reason: ready === "access-denied" ? atomic ? "atomic-readiness-access-denied" : "bootstrap-readiness-access-denied" : atomic ? "atomic-readiness-failed" : "bootstrap-readiness-failed",
1817
2040
  versionId,
1818
2041
  previousVersionId,
1819
- rollback: "unavailable"
2042
+ rollback: "unavailable",
2043
+ ...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." } : {}
2044
+ };
2045
+ if (!options.verifyUploader()) return {
2046
+ ok: false,
2047
+ reason: "uploader-changed",
2048
+ versionId,
2049
+ previousVersionId,
2050
+ rollback: "unsafe"
1820
2051
  };
2052
+ const ownerAfterReadiness = await readCurrentProductionVersion(options, configPath);
2053
+ if (ownerAfterReadiness.kind === "failed") return {
2054
+ ok: false,
2055
+ reason: "status-failed",
2056
+ status: ownerAfterReadiness.status,
2057
+ versionId,
2058
+ previousVersionId,
2059
+ rollback: "unsafe"
2060
+ };
2061
+ if (ownerAfterReadiness.kind !== "ok" || ownerAfterReadiness.versionId !== versionId) {
2062
+ const repair = await stabilizeCloudflareVersionTriggers(triggerOptions);
2063
+ return {
2064
+ ok: false,
2065
+ reason: "deployment-changed",
2066
+ versionId,
2067
+ previousVersionId,
2068
+ activeVersionId: repair.activeVersionId,
2069
+ rollback: "not-needed",
2070
+ triggerRepair: repair.ok ? "succeeded" : repair.reason === "metadata-unavailable" ? "unavailable" : "failed"
2071
+ };
2072
+ }
1821
2073
  return {
1822
2074
  ok: true,
1823
2075
  versionId,
@@ -1831,32 +2083,36 @@ async function bootstrapFirstDeployment(options, commonArgs, previousVersionId,
1831
2083
  ...workersDevSubdomainResult(previewUrl)
1832
2084
  };
1833
2085
  }
1834
- function parseCloudflareErrorCode(output) {
1835
- const value = output.match(CLOUDFLARE_ERROR_CODE_RE)?.[1];
1836
- return value === void 0 ? void 0 : Number(value);
2086
+ function readUploadResult(result) {
2087
+ const data = result.data;
2088
+ if (!isRecord(data) || typeof data.versionId !== "string" || !/^[0-9a-f]{8}-[0-9a-f-]{27}$/i.test(data.versionId)) return null;
2089
+ if (data.previewUrl !== void 0 && typeof data.previewUrl !== "string" || data.workersDevUrl !== void 0 && typeof data.workersDevUrl !== "string") return null;
2090
+ return data;
2091
+ }
2092
+ function versionPercentages(values) {
2093
+ return values.map((value) => {
2094
+ const [versionId, percentage] = value.split("@");
2095
+ return {
2096
+ versionId,
2097
+ percentage: Number(percentage)
2098
+ };
2099
+ });
1837
2100
  }
1838
2101
  function commandDiagnosticFields(result) {
1839
2102
  const diagnostic = cloudflareCommandDiagnostic(result);
1840
2103
  return diagnostic ? { diagnostic } : {};
1841
2104
  }
1842
- function isMissingWorkerOutput(output) {
1843
- return parseCloudflareErrorCode(output) === 10007 || /no deployments|\bworker\b.*\b(?:does not exist|not found)\b/i.test(output);
1844
- }
1845
- /** Recognize the Containers account-entitlement failure Wrangler exposes during upload. */
1846
- function isContainersUnavailable(output) {
1847
- return /\/containers\/me(?:\b|\?)/i.test(output) && /\b403\b/.test(output) || /Cloudflare Containers[^\n]*(?:Workers Paid|not (?:enabled|available)|subscription)/i.test(output);
1848
- }
1849
- async function readCurrentProductionVersion(options, commonArgs) {
1850
- const result = await Promise.resolve(options.runWrangler([
1851
- "deployments",
1852
- "status",
1853
- ...commonArgs,
1854
- "--name",
1855
- options.workerName,
1856
- "--json"
1857
- ], { capture: true }));
2105
+ function isContainersUnavailable(result) {
2106
+ 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);
2107
+ }
2108
+ async function readCurrentProductionVersion(options, configPath) {
2109
+ const result = await Promise.resolve(options.runWrangler({
2110
+ type: "deployments.status",
2111
+ configPath,
2112
+ workerName: options.workerName
2113
+ }, { capture: true }));
1858
2114
  if (!isCleanCommandExit(result)) {
1859
- if (isMissingWorkerOutput(`${result.stdout}\n${result.stderr}`)) return {
2115
+ if (isCompletedCommand(result) && (result.failure === "not-found" || result.errorCode === 10007)) return {
1860
2116
  kind: "ok",
1861
2117
  versionId: null,
1862
2118
  versions: []
@@ -1867,7 +2123,7 @@ async function readCurrentProductionVersion(options, commonArgs) {
1867
2123
  };
1868
2124
  }
1869
2125
  try {
1870
- const versions = (JSON.parse(result.stdout).versions ?? []).map((version) => ({
2126
+ const versions = (result.data.versions ?? []).map((version) => ({
1871
2127
  versionId: typeof version.version_id === "string" ? version.version_id : "",
1872
2128
  percentage: Number(version.percentage)
1873
2129
  }));
@@ -1901,8 +2157,8 @@ function stagedVersionPercentages(previousVersionId, versionId) {
1901
2157
  percentage: 0
1902
2158
  }] : [];
1903
2159
  }
1904
- async function verifyReadinessStagingOwner(options, commonArgs, previousVersionId, versionId) {
1905
- const owner = await readCurrentProductionVersion(options, commonArgs);
2160
+ async function verifyReadinessStagingOwner(options, configPath, previousVersionId, versionId) {
2161
+ const owner = await readCurrentProductionVersion(options, configPath);
1906
2162
  if (owner.kind === "failed") return {
1907
2163
  ok: false,
1908
2164
  reason: "status-failed",
@@ -1932,7 +2188,7 @@ async function verifyReadinessStagingOwner(options, commonArgs, previousVersionI
1932
2188
  };
1933
2189
  return null;
1934
2190
  }
1935
- async function probeReadiness(url, workerName, readinessHeaders, authenticatedWorkersDevSubdomain, versionOverride, fetcher, sleep, attempts, timeoutMs = Number.POSITIVE_INFINITY) {
2191
+ async function probeReadiness(url, workerName, readinessHeaders, authenticatedWorkersDevSubdomain, versionOverride, fetcher, sleep, attempts, timeoutMs = Number.POSITIVE_INFINITY, expectedVersionId) {
1936
2192
  const headers = guardedReadinessHeaders(url, workerName, readinessHeaders, authenticatedWorkersDevSubdomain);
1937
2193
  if (headers === null) return "failed";
1938
2194
  const deadline = Date.now() + timeoutMs;
@@ -1951,7 +2207,8 @@ async function probeReadiness(url, workerName, readinessHeaders, authenticatedWo
1951
2207
  signal: AbortSignal.timeout(Math.max(1, Math.min(1e4, remaining)))
1952
2208
  });
1953
2209
  if (response.ok) {
1954
- if ((await response.json()).ready === true) return "ready";
2210
+ const body = await response.json();
2211
+ if (body.ready === true && (expectedVersionId === void 0 || body.versionId === expectedVersionId)) return "ready";
1955
2212
  } else if (await isCloudflareAccessDenial(response)) return "access-denied";
1956
2213
  } catch {} finally {
1957
2214
  if (response && !response.bodyUsed) await response.body?.cancel().catch(() => {});
@@ -1987,7 +2244,7 @@ async function isCloudflareAccessDenial(response) {
1987
2244
  return false;
1988
2245
  }
1989
2246
  }
1990
- async function deployVersionPercentages(options, commonArgs, percentages, message) {
2247
+ async function deployVersionPercentages(options, configPath, percentages, message) {
1991
2248
  const metadata = parseCloudflareVersionMessage(message);
1992
2249
  if ((options.preserveTriggers || metadata?.schema === 2 && metadata.triggerConfig.preserve) && options.configPath) return deployCloudflareVersionTraffic({
1993
2250
  ...options,
@@ -1995,17 +2252,13 @@ async function deployVersionPercentages(options, commonArgs, percentages, messag
1995
2252
  percentages,
1996
2253
  message
1997
2254
  });
1998
- return options.runWrangler([
1999
- "versions",
2000
- "deploy",
2001
- ...percentages,
2002
- ...commonArgs,
2003
- "--name",
2004
- options.workerName,
2005
- "--message",
2006
- message,
2007
- "--yes"
2008
- ], { capture: false });
2255
+ return options.runWrangler({
2256
+ type: "versions.deploy",
2257
+ versions: versionPercentages(percentages),
2258
+ configPath,
2259
+ workerName: options.workerName,
2260
+ message
2261
+ }, { capture: false });
2009
2262
  }
2010
2263
  /** Change version traffic without synchronizing Worker settings from local config. */
2011
2264
  async function deployCloudflareVersionTraffic(options) {
@@ -2014,19 +2267,13 @@ async function deployCloudflareVersionTraffic(options) {
2014
2267
  const path = join(directory, "wrangler.json");
2015
2268
  try {
2016
2269
  writeFileSync(path, JSON.stringify(config), { mode: 384 });
2017
- return await options.runWrangler([
2018
- "versions",
2019
- "deploy",
2020
- ...options.percentages,
2021
- "-c",
2022
- path,
2023
- "--env=",
2024
- "--name",
2025
- options.workerName,
2026
- "--message",
2027
- options.message,
2028
- "--yes"
2029
- ], { capture: false });
2270
+ return await options.runWrangler({
2271
+ type: "versions.deploy",
2272
+ versions: versionPercentages(options.percentages),
2273
+ configPath: path,
2274
+ workerName: options.workerName,
2275
+ message: options.message
2276
+ }, { capture: false });
2030
2277
  } finally {
2031
2278
  rmSync(directory, {
2032
2279
  recursive: true,
@@ -2034,12 +2281,12 @@ async function deployCloudflareVersionTraffic(options) {
2034
2281
  });
2035
2282
  }
2036
2283
  }
2037
- async function restorePreviousVersion(options, commonArgs, previousVersionId, cause, expectedOwnedTraffic) {
2284
+ async function restorePreviousVersion(options, configPath, previousVersionId, cause, expectedOwnedTraffic) {
2038
2285
  if (!previousVersionId) return "unavailable";
2039
2286
  if (!options.verifyUploader()) return "unsafe";
2040
- const owner = await readCurrentProductionVersion(options, commonArgs);
2287
+ const owner = await readCurrentProductionVersion(options, configPath);
2041
2288
  if (owner.kind !== "ok" || !expectedOwnedTraffic.some((expected) => productionVersionPercentagesEqual(owner.versions, expected))) return "unsafe";
2042
- return isCleanCommandExit(await deployVersionPercentages(options, commonArgs, [`${previousVersionId}@100`], `Void automatic rollback after ${cause}.`)) ? "succeeded" : "failed";
2289
+ return isCleanCommandExit(await deployVersionPercentages(options, configPath, [`${previousVersionId}@100`], `Void automatic rollback after ${cause}.`)) ? "succeeded" : "failed";
2043
2290
  }
2044
2291
  function workersDevReadinessUrl(workerName, value) {
2045
2292
  const subdomain = normalizeWorkersDevSubdomain(value);
@@ -2073,8 +2320,11 @@ function workersDevSubdomainResult(url) {
2073
2320
  const workersDevSubdomain = extractWorkersDevSubdomain(url);
2074
2321
  return workersDevSubdomain ? { workersDevSubdomain } : {};
2075
2322
  }
2323
+ function isCompletedCommand(result) {
2324
+ return Number.isSafeInteger(result.status) && result.status >= 0 && result.signal == null && (result.error == null || result.error === "");
2325
+ }
2076
2326
  function isCleanCommandExit(result) {
2077
- return result.status === 0 && result.signal == null && (result.error == null || result.error === "");
2327
+ return result.status === 0 && isCompletedCommand(result);
2078
2328
  }
2079
2329
  function defaultSleep(milliseconds) {
2080
2330
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
@@ -2088,13 +2338,6 @@ function defaultSleep(milliseconds) {
2088
2338
  */
2089
2339
  function createCloudflareMetadataRunner(options) {
2090
2340
  let tokenPromise;
2091
- const common = [
2092
- ...options.configPath ? ["-c", options.configPath] : [],
2093
- "--env=",
2094
- "--name",
2095
- options.workerName,
2096
- "--json"
2097
- ];
2098
2341
  const prefix = `${options.apiBaseUrl.replace(/\/$/, "")}/accounts/${encodeURIComponent(options.accountId)}/workers/scripts/${encodeURIComponent(options.workerName)}`;
2099
2342
  const fetcher = options.fetch ?? globalThis.fetch;
2100
2343
  const proxyEnv = options.env ?? process.env;
@@ -2105,26 +2348,22 @@ function createCloudflareMetadataRunner(options) {
2105
2348
  stderr: "Cloudflare uploader integrity check failed.",
2106
2349
  error: "uploader-changed"
2107
2350
  };
2108
- const fallback = (args, runOptions) => options.verifyUploader() ? options.runWrangler(args, runOptions) : refused;
2109
- return async (args, runOptions) => {
2351
+ const fallback = (operation, runOptions) => options.verifyUploader() ? options.runWrangler(operation, runOptions) : refused;
2352
+ return async (operation, runOptions) => {
2110
2353
  let kind;
2111
2354
  let resource;
2112
- let rest = [];
2113
- if (args[0] === "deployments" && args[1] === "status") {
2355
+ if (operation.type === "deployments.status") {
2114
2356
  kind = "deployment";
2115
2357
  resource = "/deployments";
2116
- rest = args.slice(2);
2117
- } else if (args[0] === "versions" && args[1] === "list") {
2358
+ } else if (operation.type === "versions.list") {
2118
2359
  kind = "versions";
2119
2360
  resource = "/versions?deployable=true";
2120
- rest = args.slice(2);
2121
- } else if (args[0] === "versions" && args[1] === "view" && /^[0-9a-f-]{36}$/i.test(args[2] ?? "")) {
2361
+ } else if (operation.type === "versions.view" && !operation.includeSource && /^[0-9a-f-]{36}$/i.test(operation.versionId)) {
2122
2362
  kind = "version";
2123
- resource = `/versions/${encodeURIComponent(args[2])}`;
2124
- rest = args.slice(3);
2363
+ resource = "/versions/" + encodeURIComponent(operation.versionId);
2125
2364
  }
2126
- if (!kind || !runOptions.capture || rest.length !== common.length || rest.some((value, i) => value !== common[i])) return fallback(args, runOptions);
2127
- if (requiresProxyTransport) return fallback(args, runOptions);
2365
+ if (!kind || !runOptions.capture || !("workerName" in operation) || operation.workerName !== options.workerName || operation.configPath !== options.configPath) return fallback(operation, runOptions);
2366
+ if (requiresProxyTransport) return fallback(operation, runOptions);
2128
2367
  let response;
2129
2368
  try {
2130
2369
  if (!tokenPromise && !options.verifyUploader()) return refused;
@@ -2145,29 +2384,30 @@ function createCloudflareMetadataRunner(options) {
2145
2384
  if (result.deployments.length === 0) return {
2146
2385
  status: 1,
2147
2386
  stdout: "",
2148
- stderr: `The Worker ${options.workerName} has no deployments.`
2387
+ stderr: `The Worker ${options.workerName} has no deployments.`,
2388
+ errorCode: 10007
2149
2389
  };
2150
2390
  if (record(latest) && Array.isArray(latest.versions)) return {
2151
2391
  status: 0,
2152
- stdout: JSON.stringify(latest),
2392
+ stdout: "",
2393
+ data: latest,
2153
2394
  stderr: ""
2154
2395
  };
2155
2396
  }
2156
- if (kind === "version" && result.id === args[2]) return {
2397
+ if (operation.type === "versions.view" && result.id === operation.versionId) return {
2157
2398
  status: 0,
2158
- stdout: JSON.stringify(result),
2399
+ stdout: "",
2400
+ data: result,
2159
2401
  stderr: ""
2160
2402
  };
2161
2403
  if (kind === "versions" && Array.isArray(result.items)) {
2162
2404
  const items = result.items;
2163
- if (items.every((item) => record(item) && record(item.metadata) && typeof item.metadata.created_on === "string")) {
2164
- const sorted = items.sort((a, b) => a.metadata.created_on.localeCompare(b.metadata.created_on)).slice(-10);
2165
- return {
2166
- status: 0,
2167
- stdout: JSON.stringify(sorted),
2168
- stderr: ""
2169
- };
2170
- }
2405
+ if (items.every((item) => record(item) && record(item.metadata) && typeof item.metadata.created_on === "string")) return {
2406
+ status: 0,
2407
+ stdout: "",
2408
+ data: items.sort((a, b) => a.metadata.created_on.localeCompare(b.metadata.created_on)).slice(-10),
2409
+ stderr: ""
2410
+ };
2171
2411
  }
2172
2412
  }
2173
2413
  }
@@ -2177,11 +2417,11 @@ function createCloudflareMetadataRunner(options) {
2177
2417
  } finally {
2178
2418
  if (response && !response.bodyUsed) await response.body?.cancel().catch(() => {});
2179
2419
  }
2180
- return fallback(args, runOptions);
2420
+ return fallback(operation, runOptions);
2181
2421
  };
2182
2422
  }
2183
2423
  function record(value) {
2184
2424
  return typeof value === "object" && value !== null && !Array.isArray(value);
2185
2425
  }
2186
2426
  //#endregion
2187
- 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 };
2427
+ 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 };