@void/deploy-cloudflare 1.2.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +48 -3
- package/dist/index.mjs +136 -29
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -10,7 +10,7 @@ type WorkerTarget = ConfigTarget & {
|
|
|
10
10
|
type CloudflareOperation = ({
|
|
11
11
|
type: "auth.whoami" | "auth.login" | "auth.logout" | "auth.token";
|
|
12
12
|
} & ConfigTarget) | ({
|
|
13
|
-
type: "d1.list" | "kv.namespace.list" | "containers.list";
|
|
13
|
+
type: "d1.list" | "kv.namespace.list" | "containers.list" | "workers.subdomain.get";
|
|
14
14
|
} & ConfigTarget) | ({
|
|
15
15
|
type: "d1.info";
|
|
16
16
|
name: string;
|
|
@@ -87,6 +87,7 @@ type CloudflareOperation = ({
|
|
|
87
87
|
percentage: number;
|
|
88
88
|
}>;
|
|
89
89
|
message?: string;
|
|
90
|
+
bypassDeploymentChecks?: boolean;
|
|
90
91
|
} & WorkerTarget) | ({
|
|
91
92
|
type: "versions.upload" | "worker.deploy";
|
|
92
93
|
message?: string;
|
|
@@ -267,6 +268,7 @@ type CloudflareStaticDeployConfig = Readonly<{
|
|
|
267
268
|
/** Generate the deterministic thin Worker entry bundled by Wrangler. */
|
|
268
269
|
export declare function renderCloudflareStaticEntry(options: {
|
|
269
270
|
runtimeImport?: string;
|
|
271
|
+
alwaysUseHttps?: boolean;
|
|
270
272
|
headerRules?: ReadonlyArray<HeaderRule>;
|
|
271
273
|
redirectRules?: ReadonlyArray<RedirectRule>;
|
|
272
274
|
fallbackRules?: ReadonlyArray<RedirectRule>;
|
|
@@ -310,6 +312,8 @@ type CloudflareTriggerConfig = Readonly<{
|
|
|
310
312
|
settingsHash?: string;
|
|
311
313
|
/** Explicit Worker-wide observability settings, published after version activation. */
|
|
312
314
|
observability?: Readonly<Record<string, unknown>>;
|
|
315
|
+
/** Explicit Worker-wide Logpush setting, published after version activation. */
|
|
316
|
+
logpush?: boolean;
|
|
313
317
|
workers_dev: boolean;
|
|
314
318
|
preview_urls: boolean;
|
|
315
319
|
routes: ReadonlyArray<unknown>;
|
|
@@ -317,7 +321,7 @@ type CloudflareTriggerConfig = Readonly<{
|
|
|
317
321
|
queueConsumers: ReadonlyArray<unknown>;
|
|
318
322
|
workflows: ReadonlyArray<unknown>;
|
|
319
323
|
/**
|
|
320
|
-
* Email Routing addresses (`addresses` in Wrangler config).
|
|
324
|
+
* Email Routing addresses (`addresses` in Wrangler config). The Cloudflare CLI applies its
|
|
321
325
|
* Email Routing plan inside `triggers deploy` — `undefined` skips the plan entirely, while
|
|
322
326
|
* `[]` is a destructive plan that deletes every rule this Worker owns — so the two must stay
|
|
323
327
|
* distinct through the snapshot. Absent from annotations written before this field existed.
|
|
@@ -325,6 +329,17 @@ type CloudflareTriggerConfig = Readonly<{
|
|
|
325
329
|
addresses?: ReadonlyArray<string>;
|
|
326
330
|
}>;
|
|
327
331
|
type CloudflareTrafficConfig = Pick<CloudflareTriggerConfig, "workers_dev" | "preview_urls" | "routes">;
|
|
332
|
+
/**
|
|
333
|
+
* Result of an account workers.dev subdomain lookup. `uploader-changed` means the lookup refused
|
|
334
|
+
* to run a changed uploader with the session credentials.
|
|
335
|
+
*/
|
|
336
|
+
type CloudflareWorkersDevSubdomainLookup = {
|
|
337
|
+
ok: true;
|
|
338
|
+
subdomain: string;
|
|
339
|
+
} | {
|
|
340
|
+
ok: false;
|
|
341
|
+
reason: "unavailable" | "uploader-changed";
|
|
342
|
+
};
|
|
328
343
|
type CloudflareVersionActivation = {
|
|
329
344
|
ok: true;
|
|
330
345
|
versionId: string;
|
|
@@ -345,10 +360,21 @@ type CloudflareVersionActivation = {
|
|
|
345
360
|
versionId?: string;
|
|
346
361
|
previousVersionId?: string | null;
|
|
347
362
|
rollback?: "not-needed" | "succeeded" | "failed" | "unavailable" | "unsafe";
|
|
363
|
+
/**
|
|
364
|
+
* Cloudflare refused to restore the previous version because a secret
|
|
365
|
+
* changed with this deploy, so Void forced it. The previous version runs
|
|
366
|
+
* with its earlier secret values.
|
|
367
|
+
*/
|
|
368
|
+
restoredOlderSecrets?: true;
|
|
348
369
|
/** Version that owned production after a concurrent operation superseded this command. */
|
|
349
370
|
activeVersionId?: string | null;
|
|
350
371
|
/** Whether Void restored the active version's immutable trigger snapshot. */
|
|
351
372
|
triggerRepair?: "succeeded" | "failed" | "unavailable";
|
|
373
|
+
/**
|
|
374
|
+
* Access denied a readiness probe to this custom-route origin. The probe carried no Access
|
|
375
|
+
* credentials because `readinessHeadersOrigin` did not name the origin.
|
|
376
|
+
*/
|
|
377
|
+
customRouteReadinessOrigin?: string;
|
|
352
378
|
};
|
|
353
379
|
/** Encode migration state into Cloudflare's version annotation for safe rollback diagnostics. */
|
|
354
380
|
export declare function encodeCloudflareVersionMessage(migrations: ReadonlyArray<string>, triggerConfig?: CloudflareTriggerConfig, contentHash?: string): string;
|
|
@@ -423,11 +449,23 @@ type CloudflareVersionActivationOptions = {
|
|
|
423
449
|
readinessHeaders?: Readonly<Record<string, string>>;
|
|
424
450
|
/** Account workers.dev subdomain used when this Worker cannot expose version preview URLs. */
|
|
425
451
|
workersDevSubdomain?: string;
|
|
452
|
+
/**
|
|
453
|
+
* Look up the account workers.dev subdomain when a first publish reports no URL and
|
|
454
|
+
* `workersDevSubdomain` is not set. The subdomain includes the compliance-region label, for
|
|
455
|
+
* example `team.fed` for FedRAMP High. A thrown error counts as `unavailable`.
|
|
456
|
+
*/
|
|
457
|
+
resolveWorkersDevSubdomain?: () => Promise<CloudflareWorkersDevSubdomainLookup>;
|
|
426
458
|
/**
|
|
427
459
|
* Operator-provided workers.dev subdomain that may receive readiness credentials even when
|
|
428
460
|
* Wrangler cannot report a preview URL. Cached/project-controlled values must not be used here.
|
|
429
461
|
*/
|
|
430
462
|
readinessHeadersWorkersDevSubdomain?: string;
|
|
463
|
+
/**
|
|
464
|
+
* Operator-provided HTTPS origin that may receive readiness credentials when the probe uses a
|
|
465
|
+
* configured custom route. A route hostname is not guaranteed to reach this Worker, so only an
|
|
466
|
+
* exact origin match receives them. Project-controlled values must not be used here.
|
|
467
|
+
*/
|
|
468
|
+
readinessHeadersOrigin?: string;
|
|
431
469
|
message: string;
|
|
432
470
|
/** Direct empty-domain publisher for the Wrangler reconciliation gap. */
|
|
433
471
|
publishEmptyCustomDomains?: () => Promise<CloudflareCommandResult>;
|
|
@@ -467,6 +505,12 @@ type CloudflareVersionActivationOptions = {
|
|
|
467
505
|
};
|
|
468
506
|
/** Upload, probe, and atomically activate a verified Worker Version. */
|
|
469
507
|
export declare function activateCloudflareVersion(options: CloudflareVersionActivationOptions): Promise<CloudflareVersionActivation>;
|
|
508
|
+
/**
|
|
509
|
+
* Normalize an operator-selected readiness origin. Returns `undefined` unless the value is one
|
|
510
|
+
* exact `https:` origin without a wildcard, credentials, a path, a query, or a fragment. A probe
|
|
511
|
+
* URL never has a wildcard host, so a wildcard value could never match.
|
|
512
|
+
*/
|
|
513
|
+
export declare function normalizeReadinessAccessOrigin(value: string | undefined): string | undefined;
|
|
470
514
|
/** Change version traffic without synchronizing Worker settings from local config. */
|
|
471
515
|
export declare function deployCloudflareVersionTraffic(options: {
|
|
472
516
|
runWrangler: CloudflareCommandRunner;
|
|
@@ -474,6 +518,7 @@ export declare function deployCloudflareVersionTraffic(options: {
|
|
|
474
518
|
workerName: string;
|
|
475
519
|
percentages: Array<string>;
|
|
476
520
|
message: string;
|
|
521
|
+
bypassDeploymentChecks?: boolean;
|
|
477
522
|
}): Promise<CloudflareCommandResult>;
|
|
478
523
|
/** Normalize an account workers.dev subdomain supplied by config or the environment. */
|
|
479
524
|
export declare function normalizeWorkersDevSubdomain(value: string | undefined): string | undefined;
|
|
@@ -506,4 +551,4 @@ export declare function cloudflareCommandDiagnostic(result: CloudflareCommandRes
|
|
|
506
551
|
//#region src/secret-draft.d.ts
|
|
507
552
|
export declare const VOID_SECRET_DRAFT_SOURCE = "export default { fetch() { return new Response(\"\"); } };\n";
|
|
508
553
|
//#endregion
|
|
509
|
-
export type { ApprovedCloudflareKvBinding, CloudflareCommandResult, CloudflareCommandRunner, CloudflareDeployExecution, CloudflareFrameworkDeployConfig, CloudflareOperation, CloudflareProviderError, CloudflareStaticDeployConfig, CloudflareTrafficConfig, CloudflareTriggerConfig, CloudflareTriggerStabilization, CloudflareUploadResult, CloudflareVersionActivation, CloudflareVersionActivationOptions, CloudflareVersionMetadata, CloudflareWorkerMigrationTagOptions, ExistingCloudflareWorker };
|
|
554
|
+
export type { ApprovedCloudflareKvBinding, CloudflareCommandResult, CloudflareCommandRunner, CloudflareDeployExecution, CloudflareFrameworkDeployConfig, CloudflareOperation, CloudflareProviderError, CloudflareStaticDeployConfig, CloudflareTrafficConfig, CloudflareTriggerConfig, CloudflareTriggerStabilization, CloudflareUploadResult, CloudflareVersionActivation, CloudflareVersionActivationOptions, CloudflareVersionMetadata, CloudflareWorkerMigrationTagOptions, CloudflareWorkersDevSubdomainLookup, ExistingCloudflareWorker };
|
package/dist/index.mjs
CHANGED
|
@@ -645,6 +645,7 @@ function writeCloudflareFrameworkDeployConfig(options) {
|
|
|
645
645
|
/** Generate the deterministic thin Worker entry bundled by Wrangler. */
|
|
646
646
|
function renderCloudflareStaticEntry(options) {
|
|
647
647
|
const config = JSON.stringify({
|
|
648
|
+
...options.alwaysUseHttps !== void 0 ? { alwaysUseHttps: options.alwaysUseHttps } : {},
|
|
648
649
|
...options.headerRules?.length ? { headerRules: options.headerRules } : {},
|
|
649
650
|
...options.redirectRules?.length ? { redirectRules: options.redirectRules } : {},
|
|
650
651
|
...options.fallbackRules?.length ? { fallbackRules: options.fallbackRules } : {},
|
|
@@ -799,6 +800,7 @@ function cloudflareCommandDiagnostic(result) {
|
|
|
799
800
|
//#endregion
|
|
800
801
|
//#region src/version.ts
|
|
801
802
|
const DURABLE_MIGRATION_VERSION_ERROR = 10211;
|
|
803
|
+
const CLOUDFLARE_SECRET_CHANGED_ERROR = 10220;
|
|
802
804
|
/** Encode migration state into Cloudflare's version annotation for safe rollback diagnostics. */
|
|
803
805
|
function encodeCloudflareVersionMessage(migrations, triggerConfig, contentHash) {
|
|
804
806
|
const metadata = triggerConfig ? {
|
|
@@ -843,7 +845,8 @@ function readCloudflareTriggerConfig(configPath, sourceConfigPath) {
|
|
|
843
845
|
const source = sourceConfigPath ? parseRootWranglerConfig(sourceConfigPath) : config;
|
|
844
846
|
const result = triggerConfigFromWrangler({
|
|
845
847
|
...config,
|
|
846
|
-
observability: source.observability
|
|
848
|
+
observability: source.observability,
|
|
849
|
+
logpush: source.logpush
|
|
847
850
|
});
|
|
848
851
|
if (!sourceConfigPath) return result;
|
|
849
852
|
return {
|
|
@@ -1084,6 +1087,7 @@ function triggerConfigFromWrangler(config) {
|
|
|
1084
1087
|
...config.preview_urls === void 0 ? { preservePreviewUrls: true } : {},
|
|
1085
1088
|
settingsHash: nonVersionedSettingsHash(config),
|
|
1086
1089
|
...isRecord(config.observability) ? { observability: config.observability } : {},
|
|
1090
|
+
...typeof config.logpush === "boolean" ? { logpush: config.logpush } : {},
|
|
1087
1091
|
workers_dev: workersDev,
|
|
1088
1092
|
preview_urls: typeof config.preview_urls === "boolean" ? config.preview_urls : workersDev,
|
|
1089
1093
|
routes,
|
|
@@ -1117,7 +1121,8 @@ function parseTriggerConfig(value) {
|
|
|
1117
1121
|
"preserve",
|
|
1118
1122
|
"preserveRoutes",
|
|
1119
1123
|
"preserveWorkersDev",
|
|
1120
|
-
"preservePreviewUrls"
|
|
1124
|
+
"preservePreviewUrls",
|
|
1125
|
+
"logpush"
|
|
1121
1126
|
].some((key) => value[key] !== void 0 && typeof value[key] !== "boolean") || value.settingsHash !== void 0 && typeof value.settingsHash !== "string" || value.observability !== void 0 && !isRecord(value.observability) || typeof value.workers_dev !== "boolean" || typeof value.preview_urls !== "boolean" || !Array.isArray(value.routes) || !Array.isArray(value.crons) || !Array.isArray(value.queueConsumers) || !Array.isArray(value.workflows) || value.addresses !== void 0 && readEmailAddresses(value.addresses) === void 0) return null;
|
|
1122
1127
|
return {
|
|
1123
1128
|
...value.preserve === true ? { preserve: true } : {},
|
|
@@ -1126,6 +1131,7 @@ function parseTriggerConfig(value) {
|
|
|
1126
1131
|
...value.preservePreviewUrls === true ? { preservePreviewUrls: true } : {},
|
|
1127
1132
|
...typeof value.settingsHash === "string" ? { settingsHash: value.settingsHash } : {},
|
|
1128
1133
|
...isRecord(value.observability) ? { observability: value.observability } : {},
|
|
1134
|
+
...typeof value.logpush === "boolean" ? { logpush: value.logpush } : {},
|
|
1129
1135
|
workers_dev: value.workers_dev,
|
|
1130
1136
|
preview_urls: value.preview_urls,
|
|
1131
1137
|
routes: value.routes,
|
|
@@ -1143,6 +1149,7 @@ function triggerWranglerConfig(base, triggerConfig, manageRoutes = true) {
|
|
|
1143
1149
|
const config = {
|
|
1144
1150
|
...base,
|
|
1145
1151
|
...triggerConfig.observability === void 0 ? {} : { observability: triggerConfig.observability },
|
|
1152
|
+
...triggerConfig.logpush === void 0 ? {} : { logpush: triggerConfig.logpush },
|
|
1146
1153
|
triggers,
|
|
1147
1154
|
queues,
|
|
1148
1155
|
workflows: [...triggerConfig.workflows],
|
|
@@ -1162,6 +1169,7 @@ function resolvePreservedTraffic(desired, previous, workerExists) {
|
|
|
1162
1169
|
...desired.preserve ? { preserve: true } : {},
|
|
1163
1170
|
...desired.settingsHash ? { settingsHash: desired.settingsHash } : {},
|
|
1164
1171
|
...desired.observability === void 0 ? {} : { observability: desired.observability },
|
|
1172
|
+
...desired.logpush === void 0 ? {} : { logpush: desired.logpush },
|
|
1165
1173
|
workers_dev: desired.preserveWorkersDev ? previous?.workers_dev ?? desired.workers_dev : desired.workers_dev,
|
|
1166
1174
|
preview_urls: desired.preservePreviewUrls ? previous?.preview_urls ?? desired.preview_urls : desired.preview_urls,
|
|
1167
1175
|
routes: desired.preserveRoutes ? previous?.routes ?? desired.routes : desired.routes,
|
|
@@ -1506,7 +1514,9 @@ async function activateCloudflareVersionWithSecretsFile(options) {
|
|
|
1506
1514
|
if (stagingFailure) return stagingFailure;
|
|
1507
1515
|
stagedVersionOverride = true;
|
|
1508
1516
|
}
|
|
1509
|
-
const
|
|
1517
|
+
const trustedReadinessOrigin = normalizeReadinessAccessOrigin(options.readinessHeadersOrigin);
|
|
1518
|
+
const withholdsCustomRouteHeaders = (url) => readinessUsesCustomRoute && !isTrustedReadinessOrigin(url, trustedReadinessOrigin);
|
|
1519
|
+
const probe = (url) => probeReadiness(`${url.replace(/\/$/, "")}/__void/ready`, options.workerName, withholdsCustomRouteHeaders(url) ? void 0 : options.readinessHeaders, authenticatedWorkersDevSubdomain, readinessUsesCustomRoute ? trustedReadinessOrigin : void 0, stagedVersionOverride ? versionId : void 0, options.fetch ?? globalThis.fetch, options.sleep ?? setTimeout, options.readinessAttempts ?? 6, Number.POSITIVE_INFINITY, options.requireVersionMetadata ? versionId : void 0);
|
|
1510
1520
|
options.onProgress?.("Waiting for the Worker to become ready...");
|
|
1511
1521
|
let ready = await probe(readinessUrl);
|
|
1512
1522
|
if (ready === "access-denied" && !stagedVersionOverride && previous.versionId !== null && !options.requirePreviewForReadiness) {
|
|
@@ -1526,14 +1536,15 @@ async function activateCloudflareVersionWithSecretsFile(options) {
|
|
|
1526
1536
|
reason: ready === "access-denied" ? "readiness-access-denied" : "readiness-failed",
|
|
1527
1537
|
versionId,
|
|
1528
1538
|
previousVersionId: previous.versionId,
|
|
1529
|
-
|
|
1539
|
+
...ready === "access-denied" && withholdsCustomRouteHeaders(readinessUrl) ? customRouteReadinessOriginField(readinessUrl) : {},
|
|
1540
|
+
...stagedVersionOverride ? await restorePreviousVersion(options, previous.versionId, "readiness failed", [stagedVersionPercentages(previous.versionId, versionId)]) : { rollback: "not-needed" }
|
|
1530
1541
|
};
|
|
1531
1542
|
if (!options.verifyUploader()) return {
|
|
1532
1543
|
ok: false,
|
|
1533
1544
|
reason: "uploader-changed",
|
|
1534
1545
|
versionId,
|
|
1535
1546
|
previousVersionId: previous.versionId,
|
|
1536
|
-
|
|
1547
|
+
...stagedVersionOverride ? await restorePreviousVersion(options, previous.versionId, "uploader verification failed", [stagedVersionPercentages(previous.versionId, versionId)]) : { rollback: "not-needed" }
|
|
1537
1548
|
};
|
|
1538
1549
|
const ownerBeforeActivation = await readCurrentProductionVersion(options);
|
|
1539
1550
|
if (ownerBeforeActivation.kind === "failed") return {
|
|
@@ -1570,10 +1581,10 @@ async function activateCloudflareVersionWithSecretsFile(options) {
|
|
|
1570
1581
|
status: activate.status,
|
|
1571
1582
|
versionId,
|
|
1572
1583
|
previousVersionId: previous.versionId,
|
|
1573
|
-
|
|
1584
|
+
...stagedVersionOverride ? await restorePreviousVersion(options, previous.versionId, "version activation failed", [stagedVersionPercentages(previous.versionId, versionId), [{
|
|
1574
1585
|
versionId,
|
|
1575
1586
|
percentage: 100
|
|
1576
|
-
}]]) : "not-needed"
|
|
1587
|
+
}]]) : { rollback: "not-needed" }
|
|
1577
1588
|
};
|
|
1578
1589
|
if (!options.verifyUploader()) return {
|
|
1579
1590
|
ok: false,
|
|
@@ -1666,14 +1677,14 @@ async function activateCloudflareVersionWithSecretsFile(options) {
|
|
|
1666
1677
|
triggerRepair: triggerRepairOutcome(repair)
|
|
1667
1678
|
};
|
|
1668
1679
|
}
|
|
1669
|
-
const
|
|
1680
|
+
const restore = await restorePreviousVersionAndTriggers(options, previous.versionId, "trigger activation failed", desiredTriggerConfig, versionId, versionedPreviousTriggerConfigWithTraffic);
|
|
1670
1681
|
return {
|
|
1671
1682
|
ok: false,
|
|
1672
1683
|
reason: "triggers-failed",
|
|
1673
1684
|
status: triggers.status,
|
|
1674
1685
|
versionId,
|
|
1675
1686
|
previousVersionId: previous.versionId,
|
|
1676
|
-
|
|
1687
|
+
...restore
|
|
1677
1688
|
};
|
|
1678
1689
|
}
|
|
1679
1690
|
if (!triggers.targetOwnsTraffic) return {
|
|
@@ -1704,16 +1715,28 @@ async function restorePreviousVersionAndTriggers(options, previousVersionId, cau
|
|
|
1704
1715
|
versionId: failedVersionId,
|
|
1705
1716
|
percentage: 100
|
|
1706
1717
|
}]]);
|
|
1707
|
-
if (traffic !== "succeeded" || !previousVersionId) return traffic;
|
|
1708
|
-
if (!options.verifyUploader()) return
|
|
1718
|
+
if (traffic.rollback !== "succeeded" || !previousVersionId) return traffic;
|
|
1719
|
+
if (!options.verifyUploader()) return {
|
|
1720
|
+
...traffic,
|
|
1721
|
+
rollback: "unsafe"
|
|
1722
|
+
};
|
|
1709
1723
|
let targetTriggerConfig = capturedTriggerConfig;
|
|
1710
1724
|
if (!targetTriggerConfig) {
|
|
1711
1725
|
const metadata = await readCloudflareVersionMetadata(options, previousVersionId);
|
|
1712
|
-
if (metadata?.schema !== 2) return
|
|
1726
|
+
if (metadata?.schema !== 2) return {
|
|
1727
|
+
...traffic,
|
|
1728
|
+
rollback: "unavailable"
|
|
1729
|
+
};
|
|
1713
1730
|
targetTriggerConfig = metadata.triggerConfig;
|
|
1714
1731
|
}
|
|
1715
|
-
if (targetTriggerConfig.preserve) return
|
|
1716
|
-
|
|
1732
|
+
if (targetTriggerConfig.preserve) return {
|
|
1733
|
+
...traffic,
|
|
1734
|
+
rollback: "unavailable"
|
|
1735
|
+
};
|
|
1736
|
+
if (!options.verifyUploader()) return {
|
|
1737
|
+
...traffic,
|
|
1738
|
+
rollback: "unsafe"
|
|
1739
|
+
};
|
|
1717
1740
|
const triggers = await stabilizeCloudflareVersionTriggers({
|
|
1718
1741
|
runWrangler: options.runWrangler,
|
|
1719
1742
|
workerName: options.workerName,
|
|
@@ -1725,7 +1748,10 @@ async function restorePreviousVersionAndTriggers(options, previousVersionId, cau
|
|
|
1725
1748
|
publishEmptyCustomDomains: options.publishEmptyCustomDomains,
|
|
1726
1749
|
publishEmptyCrons: options.publishEmptyCrons
|
|
1727
1750
|
});
|
|
1728
|
-
return
|
|
1751
|
+
return {
|
|
1752
|
+
...traffic,
|
|
1753
|
+
rollback: triggers.ok && triggers.targetOwnsTraffic ? "succeeded" : "failed"
|
|
1754
|
+
};
|
|
1729
1755
|
}
|
|
1730
1756
|
async function readCloudflareVersionMetadata(options, versionId) {
|
|
1731
1757
|
return (await readCloudflareVersionInspection(options, versionId))?.metadata ?? null;
|
|
@@ -1879,7 +1905,33 @@ async function bootstrapFirstDeployment(options, previousVersionId, atomic = fal
|
|
|
1879
1905
|
const published = readUploadResult(bootstrap);
|
|
1880
1906
|
const versionId = published?.versionId;
|
|
1881
1907
|
const desiredTriggerConfig = desiredTriggerConfigFor(parseCloudflareVersionMessage(options.message), options.configPath);
|
|
1882
|
-
|
|
1908
|
+
let reportedOrConfiguredUrl = published?.previewUrl ?? published?.workersDevUrl ?? (desiredTriggerConfig.workers_dev ? workersDevReadinessUrl(options.workerName, options.workersDevSubdomain) : customRouteReadinessUrl(desiredTriggerConfig.routes));
|
|
1909
|
+
if (versionId && !reportedOrConfiguredUrl && desiredTriggerConfig.workers_dev && !desiredTriggerConfig.preserve && !desiredTriggerConfig.preserveWorkersDev && !options.preserveTriggers && options.resolveWorkersDevSubdomain) {
|
|
1910
|
+
if (!options.verifyUploader()) return {
|
|
1911
|
+
ok: false,
|
|
1912
|
+
reason: "uploader-changed",
|
|
1913
|
+
versionId,
|
|
1914
|
+
previousVersionId,
|
|
1915
|
+
rollback: "unsafe"
|
|
1916
|
+
};
|
|
1917
|
+
let lookup;
|
|
1918
|
+
try {
|
|
1919
|
+
lookup = await options.resolveWorkersDevSubdomain();
|
|
1920
|
+
} catch {
|
|
1921
|
+
lookup = {
|
|
1922
|
+
ok: false,
|
|
1923
|
+
reason: "unavailable"
|
|
1924
|
+
};
|
|
1925
|
+
}
|
|
1926
|
+
if (!lookup.ok && lookup.reason === "uploader-changed") return {
|
|
1927
|
+
ok: false,
|
|
1928
|
+
reason: "uploader-changed",
|
|
1929
|
+
versionId,
|
|
1930
|
+
previousVersionId,
|
|
1931
|
+
rollback: "unsafe"
|
|
1932
|
+
};
|
|
1933
|
+
reportedOrConfiguredUrl = lookup.ok ? workersDevReadinessUrl(options.workerName, lookup.subdomain) : void 0;
|
|
1934
|
+
}
|
|
1883
1935
|
if (!versionId || !reportedOrConfiguredUrl) return {
|
|
1884
1936
|
ok: false,
|
|
1885
1937
|
reason: atomic ? "atomic-output-invalid" : "bootstrap-output-invalid",
|
|
@@ -1984,14 +2036,18 @@ async function bootstrapFirstDeployment(options, previousVersionId, atomic = fal
|
|
|
1984
2036
|
const usesCustomDomain = !desiredTriggerConfig.workers_dev && desiredTriggerConfig.routes.some((route) => isRecord(route) && route.custom_domain === true);
|
|
1985
2037
|
const customDomainBootstrap = !atomic && usesCustomDomain;
|
|
1986
2038
|
const readinessTimeoutMs = customDomainBootstrap ? 3e5 : 6e4;
|
|
2039
|
+
const previewSubdomain = extractWorkersDevSubdomain(previewUrl);
|
|
2040
|
+
const trustedReadinessOrigin = previewSubdomain ? void 0 : normalizeReadinessAccessOrigin(options.readinessHeadersOrigin);
|
|
2041
|
+
const withholdsCustomRouteHeaders = !previewSubdomain && !isTrustedReadinessOrigin(previewUrl, trustedReadinessOrigin);
|
|
1987
2042
|
options.onProgress?.("Waiting for the Worker to become ready...");
|
|
1988
|
-
const ready = await probeReadiness(`${previewUrl.replace(/\/$/, "")}/__void/ready`, options.workerName,
|
|
2043
|
+
const ready = await probeReadiness(`${previewUrl.replace(/\/$/, "")}/__void/ready`, options.workerName, withholdsCustomRouteHeaders ? void 0 : options.readinessHeaders, previewSubdomain, trustedReadinessOrigin, void 0, options.fetch ?? globalThis.fetch, options.sleep ?? setTimeout, options.readinessAttempts ?? (customDomainBootstrap ? 150 : 30), readinessTimeoutMs, options.requireVersionMetadata ? versionId : void 0);
|
|
1989
2044
|
if (ready !== "ready") return {
|
|
1990
2045
|
ok: false,
|
|
1991
2046
|
reason: ready === "access-denied" ? atomic ? "atomic-readiness-access-denied" : "bootstrap-readiness-access-denied" : atomic ? "atomic-readiness-failed" : "bootstrap-readiness-failed",
|
|
1992
2047
|
versionId,
|
|
1993
2048
|
previousVersionId,
|
|
1994
2049
|
rollback: "unavailable",
|
|
2050
|
+
...ready === "access-denied" && withholdsCustomRouteHeaders ? customRouteReadinessOriginField(previewUrl) : {},
|
|
1995
2051
|
...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." } : {}
|
|
1996
2052
|
};
|
|
1997
2053
|
if (!options.verifyUploader()) return {
|
|
@@ -2167,8 +2223,8 @@ async function verifyReadinessStagingOwner(options, previousVersionId, versionId
|
|
|
2167
2223
|
};
|
|
2168
2224
|
return null;
|
|
2169
2225
|
}
|
|
2170
|
-
async function probeReadiness(url, workerName, readinessHeaders, authenticatedWorkersDevSubdomain, versionOverride, fetcher, sleep, attempts, timeoutMs = Number.POSITIVE_INFINITY, expectedVersionId) {
|
|
2171
|
-
const headers = guardedReadinessHeaders(url, workerName, readinessHeaders, authenticatedWorkersDevSubdomain);
|
|
2226
|
+
async function probeReadiness(url, workerName, readinessHeaders, authenticatedWorkersDevSubdomain, trustedReadinessOrigin, versionOverride, fetcher, sleep, attempts, timeoutMs = Number.POSITIVE_INFINITY, expectedVersionId) {
|
|
2227
|
+
const headers = guardedReadinessHeaders(url, workerName, readinessHeaders, authenticatedWorkersDevSubdomain, trustedReadinessOrigin);
|
|
2172
2228
|
if (headers === null) return "failed";
|
|
2173
2229
|
const deadline = Date.now() + timeoutMs;
|
|
2174
2230
|
for (let attempt = 0; attempt < Math.max(1, attempts); attempt++) {
|
|
@@ -2199,9 +2255,13 @@ async function probeReadiness(url, workerName, readinessHeaders, authenticatedWo
|
|
|
2199
2255
|
}
|
|
2200
2256
|
return "failed";
|
|
2201
2257
|
}
|
|
2202
|
-
/**
|
|
2203
|
-
|
|
2258
|
+
/**
|
|
2259
|
+
* Never forward Access headers to a hostname that cannot be this Worker, unless the operator
|
|
2260
|
+
* named that exact origin.
|
|
2261
|
+
*/
|
|
2262
|
+
function guardedReadinessHeaders(url, workerName, headers, authenticatedWorkersDevSubdomain, trustedReadinessOrigin) {
|
|
2204
2263
|
if (!headers || Object.keys(headers).length === 0) return {};
|
|
2264
|
+
if (isTrustedReadinessOrigin(url, trustedReadinessOrigin)) return headers;
|
|
2205
2265
|
try {
|
|
2206
2266
|
const parsed = new URL(url);
|
|
2207
2267
|
const firstLabel = parsed.hostname.toLowerCase().split(".")[0] ?? "";
|
|
@@ -2213,6 +2273,36 @@ function guardedReadinessHeaders(url, workerName, headers, authenticatedWorkersD
|
|
|
2213
2273
|
return null;
|
|
2214
2274
|
}
|
|
2215
2275
|
}
|
|
2276
|
+
/**
|
|
2277
|
+
* Normalize an operator-selected readiness origin. Returns `undefined` unless the value is one
|
|
2278
|
+
* exact `https:` origin without a wildcard, credentials, a path, a query, or a fragment. A probe
|
|
2279
|
+
* URL never has a wildcard host, so a wildcard value could never match.
|
|
2280
|
+
*/
|
|
2281
|
+
function normalizeReadinessAccessOrigin(value) {
|
|
2282
|
+
if (!value) return;
|
|
2283
|
+
try {
|
|
2284
|
+
const url = new URL(value);
|
|
2285
|
+
return url.protocol === "https:" && !url.hostname.includes("*") && !url.username && !url.password && url.pathname === "/" && !url.search && !url.hash ? url.origin : void 0;
|
|
2286
|
+
} catch {
|
|
2287
|
+
return;
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
function isTrustedReadinessOrigin(url, trustedOrigin) {
|
|
2291
|
+
if (trustedOrigin === void 0) return false;
|
|
2292
|
+
try {
|
|
2293
|
+
const parsed = new URL(url);
|
|
2294
|
+
return parsed.protocol === "https:" && !parsed.username && !parsed.password && parsed.origin === trustedOrigin;
|
|
2295
|
+
} catch {
|
|
2296
|
+
return false;
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
function customRouteReadinessOriginField(url) {
|
|
2300
|
+
try {
|
|
2301
|
+
return { customRouteReadinessOrigin: new URL(url).origin };
|
|
2302
|
+
} catch {
|
|
2303
|
+
return {};
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2216
2306
|
async function isCloudflareAccessDenial(response) {
|
|
2217
2307
|
if (response.status >= 300 && response.status < 400 && response.headers.get("location")?.includes("/cdn-cgi/access/")) return true;
|
|
2218
2308
|
if (response.status !== 403) return false;
|
|
@@ -2236,7 +2326,8 @@ async function deployVersionPercentages(options, percentages, message) {
|
|
|
2236
2326
|
versions: versionPercentages(percentages),
|
|
2237
2327
|
configPath: options.configPath,
|
|
2238
2328
|
workerName: options.workerName,
|
|
2239
|
-
message
|
|
2329
|
+
message,
|
|
2330
|
+
...options.bypassDeploymentChecks ? { bypassDeploymentChecks: true } : {}
|
|
2240
2331
|
}, { capture: false });
|
|
2241
2332
|
}
|
|
2242
2333
|
/** Change version traffic without synchronizing Worker settings from local config. */
|
|
@@ -2251,7 +2342,8 @@ async function deployCloudflareVersionTraffic(options) {
|
|
|
2251
2342
|
versions: versionPercentages(options.percentages),
|
|
2252
2343
|
configPath: path,
|
|
2253
2344
|
workerName: options.workerName,
|
|
2254
|
-
message: options.message
|
|
2345
|
+
message: options.message,
|
|
2346
|
+
...options.bypassDeploymentChecks ? { bypassDeploymentChecks: true } : {}
|
|
2255
2347
|
}, { capture: false });
|
|
2256
2348
|
} finally {
|
|
2257
2349
|
rmSync(directory, {
|
|
@@ -2261,11 +2353,26 @@ async function deployCloudflareVersionTraffic(options) {
|
|
|
2261
2353
|
}
|
|
2262
2354
|
}
|
|
2263
2355
|
async function restorePreviousVersion(options, previousVersionId, cause, expectedOwnedTraffic) {
|
|
2264
|
-
if (!previousVersionId) return "unavailable";
|
|
2265
|
-
if (!options.verifyUploader()) return "unsafe";
|
|
2266
|
-
const
|
|
2267
|
-
|
|
2268
|
-
|
|
2356
|
+
if (!previousVersionId) return { rollback: "unavailable" };
|
|
2357
|
+
if (!options.verifyUploader()) return { rollback: "unsafe" };
|
|
2358
|
+
const ownsExpectedTraffic = async () => {
|
|
2359
|
+
const owner = await readCurrentProductionVersion(options);
|
|
2360
|
+
return owner.kind === "ok" && expectedOwnedTraffic.some((expected) => productionVersionPercentagesEqual(owner.versions, expected));
|
|
2361
|
+
};
|
|
2362
|
+
if (!await ownsExpectedTraffic()) return { rollback: "unsafe" };
|
|
2363
|
+
const restore = (bypassDeploymentChecks) => deployVersionPercentages({
|
|
2364
|
+
...options,
|
|
2365
|
+
bypassDeploymentChecks
|
|
2366
|
+
}, [`${previousVersionId}@100`], `Void automatic rollback after ${cause}.`);
|
|
2367
|
+
const result = await restore(false);
|
|
2368
|
+
if (result.errorCode === CLOUDFLARE_SECRET_CHANGED_ERROR) {
|
|
2369
|
+
if (!await ownsExpectedTraffic()) return { rollback: "unsafe" };
|
|
2370
|
+
return isCleanCommandExit(await restore(true)) ? {
|
|
2371
|
+
rollback: "succeeded",
|
|
2372
|
+
restoredOlderSecrets: true
|
|
2373
|
+
} : { rollback: "failed" };
|
|
2374
|
+
}
|
|
2375
|
+
return { rollback: isCleanCommandExit(result) ? "succeeded" : "failed" };
|
|
2269
2376
|
}
|
|
2270
2377
|
function workersDevReadinessUrl(workerName, value) {
|
|
2271
2378
|
const subdomain = normalizeWorkersDevSubdomain(value);
|
|
@@ -2391,4 +2498,4 @@ function createCloudflareMetadataRunner(options) {
|
|
|
2391
2498
|
};
|
|
2392
2499
|
}
|
|
2393
2500
|
//#endregion
|
|
2394
|
-
export { CloudflareWorkerInspectionError, VOID_SECRET_DRAFT_SOURCE, activateCloudflareVersion, assertCloudflareBindingsPreserved, assertCloudflareDurableObjectLifecycleSupported, assertExistingCloudflareConfig, cloudflareCommandDiagnostic, createCloudflareMetadataRunner, deployCloudflareVersionTraffic, deployCloudflareVersionTriggers, encodeCloudflareVersionMessage, executeCloudflareDeploy, extractWorkersDevSubdomain, normalizeWorkersDevSubdomain, parseCloudflareVersionMessage, parseRootWranglerConfig, readBindingFieldPresence, readCloudflareTriggerConfig, readCloudflareVersionMigrationTag, readCloudflareWorkerMigrationTag, readConfigEntryString, readExistingCloudflareWorker, readStoredResourceName, readWranglerTopLevelString, renderCloudflareStaticEntry, resolveD1MigrateTarget, stabilizeCloudflareVersionTriggers, stageCloudflareStaticAssets, writeCloudflareFrameworkDeployConfig, writeCloudflareStaticDeployConfig };
|
|
2501
|
+
export { CloudflareWorkerInspectionError, VOID_SECRET_DRAFT_SOURCE, activateCloudflareVersion, assertCloudflareBindingsPreserved, assertCloudflareDurableObjectLifecycleSupported, assertExistingCloudflareConfig, cloudflareCommandDiagnostic, createCloudflareMetadataRunner, deployCloudflareVersionTraffic, deployCloudflareVersionTriggers, encodeCloudflareVersionMessage, executeCloudflareDeploy, extractWorkersDevSubdomain, normalizeReadinessAccessOrigin, 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": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Cloudflare deployment primitives for Void runtimes and deployment adapters.",
|
|
5
5
|
"homepage": "https://void.cloud",
|
|
6
6
|
"bugs": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"access": "public"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@void/deploy-core": "1.
|
|
29
|
+
"@void/deploy-core": "1.4.0",
|
|
30
30
|
"jsonc-parser": "^3.3.1"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|