@vinext/cloudflare 1.0.0-beta.1 → 1.0.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -1
- package/dist/cache/cdn-adapter-config.d.ts +30 -0
- package/dist/cache/cdn-adapter-config.js +87 -0
- package/dist/cache/cdn-adapter.d.ts +47 -15
- package/dist/cache/cdn-adapter.js +49 -13
- package/dist/cache/cdn-adapter.runtime.d.ts +26 -4
- package/dist/cache/cdn-adapter.runtime.js +150 -37
- package/dist/cache/cdn-adapter.worker.d.ts +31 -0
- package/dist/cache/cdn-adapter.worker.js +373 -0
- package/dist/cache/cdn-build-id.d.ts +6 -0
- package/dist/cache/cdn-build-id.js +9 -0
- package/dist/cache/kv-data-adapter.d.ts +32 -8
- package/dist/cache/kv-data-adapter.js +5 -1
- package/dist/cache/kv-data-adapter.runtime.d.ts +45 -13
- package/dist/cache/kv-data-adapter.runtime.js +132 -52
- package/dist/cache/kv-key.d.ts +15 -0
- package/dist/cache/kv-key.js +43 -0
- package/dist/cache/response-store-adapter.d.ts +73 -0
- package/dist/cache/response-store-adapter.js +64 -0
- package/dist/cache/response-store-adapter.self-contained.worker.d.ts +18 -0
- package/dist/cache/response-store-adapter.self-contained.worker.js +12 -0
- package/dist/cache/response-store-adapter.service-binding.worker.d.ts +16 -0
- package/dist/cache/response-store-adapter.service-binding.worker.js +11 -0
- package/dist/cache/response-store-adapter.worker.d.ts +14 -0
- package/dist/cache/response-store-adapter.worker.js +278 -0
- package/dist/cache/response-store-cdn.runtime.d.ts +5 -0
- package/dist/cache/response-store-cdn.runtime.js +41 -0
- package/dist/cache/response-store-data.runtime.d.ts +34 -0
- package/dist/cache/response-store-data.runtime.js +300 -0
- package/dist/cacheability-artifact.d.ts +9 -0
- package/dist/cacheability-artifact.js +191 -0
- package/dist/cacheability-manifest-limits.d.ts +4 -0
- package/dist/cacheability-manifest-limits.js +7 -0
- package/dist/cacheability-probe.d.ts +49 -0
- package/dist/cacheability-probe.js +660 -0
- package/dist/cdn-warm.d.ts +110 -12
- package/dist/cdn-warm.js +880 -82
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +15 -1
- package/dist/deploy-config.d.ts +13 -11
- package/dist/deploy-config.js +21 -3
- package/dist/deploy-help.d.ts +3 -4
- package/dist/deploy-help.js +61 -18
- package/dist/deploy.d.ts +121 -41
- package/dist/deploy.js +762 -104
- package/dist/images/images-optimizer.d.ts +5 -5
- package/dist/images/images-optimizer.runtime.d.ts +1 -5
- package/dist/prerender-kv-populate.d.ts +3 -4
- package/dist/prerender-kv-populate.js +51 -18
- package/dist/tpr.d.ts +32 -70
- package/dist/tpr.js +45 -602
- package/dist/utils/cache-control-metadata.d.ts +3 -4
- package/dist/version-deploy.d.ts +19 -17
- package/dist/version-deploy.js +32 -13
- package/dist/version-headers.d.ts +2 -0
- package/dist/version-headers.js +2 -0
- package/dist/worker-deployment-url.d.ts +9 -0
- package/dist/worker-deployment-url.js +45 -0
- package/dist/workers-dev-url.d.ts +2 -3
- package/dist/wrangler-config.d.ts +19 -0
- package/dist/wrangler-config.js +294 -0
- package/dist/wrangler-version-metadata.d.ts +7 -0
- package/dist/wrangler-version-metadata.js +9 -0
- package/package.json +14 -11
package/dist/deploy.js
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { writeCacheabilityManifestArtifact } from "./cacheability-artifact.js";
|
|
2
|
+
import { DEFAULT_CACHEABILITY_PROBE_RETRY_DELAY_MS, probeStagedWorkerCacheability, readPrerenderSecret } from "./cacheability-probe.js";
|
|
3
|
+
import { CdnOperationProgress, createCdnWarmTargets, createPrerenderWarmPlan, waitForCdnWarmTargetReadiness, warmCdnCache } from "./cdn-warm.js";
|
|
4
|
+
import { parseWranglerConfig } from "./wrangler-config.js";
|
|
5
|
+
import { resolveTPRRoutes, selectRoutes } from "./tpr.js";
|
|
6
|
+
import { VINEXT_EXPECTED_WORKER_VERSION_HEADER } from "./version-headers.js";
|
|
7
|
+
import { formatImageOptimizationHint, formatMissingCacheAdapterError, resolveCdnAdapterConfig, resolveKvDataAdapterConfig, viteConfigHasCacheAdapter, viteConfigHasCloudflarePlugin, viteConfigHasImageAdapter, workerEntryHasCacheHandler } from "./deploy-config.js";
|
|
8
|
+
import { assertCdnVersionMetadataConfig } from "./wrangler-version-metadata.js";
|
|
9
|
+
import { parseWorkerDeploymentUrl } from "./worker-deployment-url.js";
|
|
5
10
|
import { runWranglerDeploymentStatus, runWranglerTriggersDeploy, runWranglerVersionDeploy, runWranglerVersionUpload } from "./version-deploy.js";
|
|
6
11
|
import { buildPrerenderKVPairs } from "./prerender-kv-populate.js";
|
|
7
12
|
import { createRequire } from "node:module";
|
|
8
|
-
import path from "node:path";
|
|
9
13
|
import fs from "node:fs";
|
|
10
|
-
import
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import { cacheabilityRoutePathname } from "vinext/internal/server/cacheability-manifest";
|
|
16
|
+
import { setTimeout } from "node:timers/promises";
|
|
17
|
+
import { DEFAULT_REMOTE_PATH_DISCOVERY_PHASE_TIMEOUT_MS, DEFAULT_REMOTE_PATH_DISCOVERY_RETRY_DELAY_MS, discoverPrerenderPathManifest, emitPrerenderPathManifest } from "vinext/internal/build/prerender-paths";
|
|
18
|
+
import { normalizePathTrailingSlash } from "vinext/shims/url-utils";
|
|
11
19
|
import os from "node:os";
|
|
12
20
|
import { spawn } from "node:child_process";
|
|
13
21
|
import { parseArgs } from "node:util";
|
|
@@ -15,7 +23,7 @@ import { pathToFileURL } from "node:url";
|
|
|
15
23
|
import { runPrerender } from "vinext/internal/build/run-prerender";
|
|
16
24
|
import { loadDotenv } from "vinext/internal/config/dotenv";
|
|
17
25
|
import { findVinextNextConfigInPlugins, loadNextConfig, resolveNextConfig, resolveNextConfigInput } from "vinext/internal/config/next-config";
|
|
18
|
-
import { findVinextCacheConfigInPlugins, findVinextPrerenderConfigInPlugins, findVinextRouteRootConfigInPlugins, formatVinextPrerenderLabel, resolveVinextPrerenderDecision } from "vinext/internal/config/prerender";
|
|
26
|
+
import { cacheWarmupStatusSource, findVinextCacheConfigInPlugins, findVinextPrerenderConfigInPlugins, findVinextRouteRootConfigInPlugins, formatVinextPrerenderLabel, hasBuildIdentityResponseHeader, hasUncachedRequestRouting, hasVerbatimResponseVary, isConfiguredCdnResponsePolicyHeader, requiresRouteCacheabilityProbeManifest, resolveVinextPrerenderDecision, supportsCanonicalRscWarmup } from "vinext/internal/config/prerender";
|
|
19
27
|
import { detectProject, findInNodeModules, formatMissingCloudflarePluginError, getMissingDeps } from "vinext/internal/utils/project";
|
|
20
28
|
import { PHASE_PRODUCTION_BUILD } from "vinext/shims/constants";
|
|
21
29
|
//#region src/deploy.ts
|
|
@@ -28,6 +36,7 @@ import { PHASE_PRODUCTION_BUILD } from "vinext/shims/constants";
|
|
|
28
36
|
* 2. Runs the Vite build
|
|
29
37
|
* 3. Deploys to Cloudflare Workers via Wrangler
|
|
30
38
|
*/
|
|
39
|
+
const DEFAULT_CDN_WARM_PROMOTION_DELAY_MS = 15e3;
|
|
31
40
|
function parsePositiveIntegerArg(raw, flag) {
|
|
32
41
|
if (raw === "") throw new Error(`${flag} requires a value, but none was provided.`);
|
|
33
42
|
const parsed = Number(raw);
|
|
@@ -40,10 +49,31 @@ function parseNonNegativeIntegerArg(raw, flag) {
|
|
|
40
49
|
if (!Number.isInteger(parsed) || parsed < 0) throw new Error(`${flag} expects a non-negative integer, but got "${raw}".`);
|
|
41
50
|
return parsed;
|
|
42
51
|
}
|
|
52
|
+
const MAX_NODE_TIMER_DELAY_MS = 2147483647;
|
|
53
|
+
function validateTimerDelay(value, flag, raw = String(value)) {
|
|
54
|
+
if (!Number.isInteger(value) || value < 0) throw new Error(`${flag} expects a non-negative integer, but got "${raw}".`);
|
|
55
|
+
if (value > MAX_NODE_TIMER_DELAY_MS) throw new Error(`${flag} must not exceed ${MAX_NODE_TIMER_DELAY_MS} milliseconds, but got "${raw}".`);
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
function validatePromotionDelay(value, raw = String(value)) {
|
|
59
|
+
return validateTimerDelay(value, "--warm-cdn-promotion-delay", raw);
|
|
60
|
+
}
|
|
61
|
+
function validateCdnWarmTarget(raw) {
|
|
62
|
+
const value = raw.trim();
|
|
63
|
+
let url;
|
|
64
|
+
try {
|
|
65
|
+
url = new URL(value);
|
|
66
|
+
} catch {
|
|
67
|
+
throw new Error(`--warm-cdn-target expects an HTTPS origin, but got "${raw}".`);
|
|
68
|
+
}
|
|
69
|
+
if (url.protocol !== "https:" || url.username !== "" || url.password !== "" || url.port !== "" || url.pathname !== "/" || url.search !== "" || url.hash !== "") throw new Error(`--warm-cdn-target expects an HTTPS origin without a path, query, credentials, or port, but got "${raw}".`);
|
|
70
|
+
return url.origin;
|
|
71
|
+
}
|
|
43
72
|
function formatUnknownError(error) {
|
|
44
73
|
if (error instanceof Error && error.message) return error.message;
|
|
45
74
|
return String(error);
|
|
46
75
|
}
|
|
76
|
+
var StagedWarmupError = class extends Error {};
|
|
47
77
|
/** Deploy command flag definitions for util.parseArgs. */
|
|
48
78
|
const deployArgOptions = {
|
|
49
79
|
help: {
|
|
@@ -66,6 +96,10 @@ const deployArgOptions = {
|
|
|
66
96
|
type: "boolean",
|
|
67
97
|
default: false
|
|
68
98
|
},
|
|
99
|
+
verbose: {
|
|
100
|
+
type: "boolean",
|
|
101
|
+
default: false
|
|
102
|
+
},
|
|
69
103
|
"prerender-all": {
|
|
70
104
|
type: "boolean",
|
|
71
105
|
default: false
|
|
@@ -75,17 +109,46 @@ const deployArgOptions = {
|
|
|
75
109
|
type: "boolean",
|
|
76
110
|
default: false
|
|
77
111
|
},
|
|
112
|
+
"warm-cdn-target": { type: "string" },
|
|
78
113
|
"warm-cdn-concurrency": { type: "string" },
|
|
79
114
|
"warm-cdn-timeout": { type: "string" },
|
|
80
115
|
"warm-cdn-retries": { type: "string" },
|
|
81
|
-
"warm-cdn-
|
|
116
|
+
"warm-cdn-discovery-timeout": { type: "string" },
|
|
117
|
+
"warm-cdn-discovery-retries": { type: "string" },
|
|
118
|
+
"warm-cdn-probe-timeout": { type: "string" },
|
|
119
|
+
"warm-cdn-probe-retries": { type: "string" },
|
|
120
|
+
"warm-cdn-certify": {
|
|
121
|
+
type: "boolean",
|
|
122
|
+
default: false
|
|
123
|
+
},
|
|
124
|
+
"warm-cdn-readiness-timeout": { type: "string" },
|
|
125
|
+
"warm-cdn-readiness-retries": { type: "string" },
|
|
126
|
+
"warm-cdn-readiness-probes": { type: "string" },
|
|
127
|
+
"warm-cdn-readiness-probe-delay": { type: "string" },
|
|
128
|
+
"dangerously-promote-on-cdn-warm-error": {
|
|
129
|
+
type: "boolean",
|
|
130
|
+
default: false
|
|
131
|
+
},
|
|
132
|
+
"no-promote": {
|
|
82
133
|
type: "boolean",
|
|
83
134
|
default: false
|
|
84
135
|
},
|
|
136
|
+
"warm-cdn-no-promote": {
|
|
137
|
+
type: "boolean",
|
|
138
|
+
default: false
|
|
139
|
+
},
|
|
140
|
+
"warm-cdn-promotion-delay": { type: "string" },
|
|
85
141
|
"warm-cdn-include-fallbacks": {
|
|
86
142
|
type: "boolean",
|
|
87
143
|
default: false
|
|
88
144
|
},
|
|
145
|
+
"experimental-traffic-aware-warm-cache": {
|
|
146
|
+
type: "boolean",
|
|
147
|
+
default: false
|
|
148
|
+
},
|
|
149
|
+
"traffic-aware-coverage": { type: "string" },
|
|
150
|
+
"traffic-aware-limit": { type: "string" },
|
|
151
|
+
"traffic-aware-window": { type: "string" },
|
|
89
152
|
"experimental-tpr": {
|
|
90
153
|
type: "boolean",
|
|
91
154
|
default: false
|
|
@@ -100,6 +163,8 @@ function parseDeployArgs(args) {
|
|
|
100
163
|
options: deployArgOptions,
|
|
101
164
|
strict: true
|
|
102
165
|
});
|
|
166
|
+
if (values["warm-cdn-certify"] && !values["experimental-warm-cdn-cache"]) throw new Error("--warm-cdn-certify requires --experimental-warm-cdn-cache.");
|
|
167
|
+
if (values["warm-cdn-target"] && !values["experimental-warm-cdn-cache"]) throw new Error("--warm-cdn-target requires --experimental-warm-cdn-cache.");
|
|
103
168
|
function parseIntArg(name, raw) {
|
|
104
169
|
if (!raw) return void 0;
|
|
105
170
|
const n = parseInt(raw, 10);
|
|
@@ -117,18 +182,31 @@ function parseDeployArgs(args) {
|
|
|
117
182
|
config: values.config?.trim() || void 0,
|
|
118
183
|
skipBuild: values["skip-build"],
|
|
119
184
|
dryRun: values["dry-run"],
|
|
185
|
+
verbose: values.verbose,
|
|
120
186
|
prerenderAll: values["prerender-all"],
|
|
121
187
|
prerenderConcurrency: values["prerender-concurrency"] === void 0 ? void 0 : parsePositiveIntegerArg(values["prerender-concurrency"], "--prerender-concurrency"),
|
|
122
188
|
warmCdnCache: values["experimental-warm-cdn-cache"],
|
|
189
|
+
warmCdnTarget: values["warm-cdn-target"] === void 0 ? void 0 : validateCdnWarmTarget(values["warm-cdn-target"]),
|
|
123
190
|
warmCdnConcurrency: values["warm-cdn-concurrency"] === void 0 ? void 0 : parsePositiveIntegerArg(values["warm-cdn-concurrency"], "--warm-cdn-concurrency"),
|
|
124
191
|
warmCdnTimeout: values["warm-cdn-timeout"] === void 0 ? void 0 : parsePositiveIntegerArg(values["warm-cdn-timeout"], "--warm-cdn-timeout"),
|
|
125
192
|
warmCdnRetries: values["warm-cdn-retries"] === void 0 ? void 0 : parseNonNegativeIntegerArg(values["warm-cdn-retries"], "--warm-cdn-retries"),
|
|
126
|
-
|
|
193
|
+
warmCdnDiscoveryTimeout: values["warm-cdn-discovery-timeout"] === void 0 ? void 0 : parsePositiveIntegerArg(values["warm-cdn-discovery-timeout"], "--warm-cdn-discovery-timeout"),
|
|
194
|
+
warmCdnDiscoveryRetries: values["warm-cdn-discovery-retries"] === void 0 ? void 0 : parseNonNegativeIntegerArg(values["warm-cdn-discovery-retries"], "--warm-cdn-discovery-retries"),
|
|
195
|
+
warmCdnProbeTimeout: values["warm-cdn-probe-timeout"] === void 0 ? void 0 : parsePositiveIntegerArg(values["warm-cdn-probe-timeout"], "--warm-cdn-probe-timeout"),
|
|
196
|
+
warmCdnProbeRetries: values["warm-cdn-probe-retries"] === void 0 ? void 0 : parseNonNegativeIntegerArg(values["warm-cdn-probe-retries"], "--warm-cdn-probe-retries"),
|
|
197
|
+
warmCdnCertify: values["warm-cdn-certify"],
|
|
198
|
+
warmCdnReadinessTimeout: values["warm-cdn-readiness-timeout"] === void 0 ? void 0 : parsePositiveIntegerArg(values["warm-cdn-readiness-timeout"], "--warm-cdn-readiness-timeout"),
|
|
199
|
+
warmCdnReadinessRetries: values["warm-cdn-readiness-retries"] === void 0 ? void 0 : parseNonNegativeIntegerArg(values["warm-cdn-readiness-retries"], "--warm-cdn-readiness-retries"),
|
|
200
|
+
warmCdnReadinessProbes: values["warm-cdn-readiness-probes"] === void 0 ? void 0 : parsePositiveIntegerArg(values["warm-cdn-readiness-probes"], "--warm-cdn-readiness-probes"),
|
|
201
|
+
warmCdnReadinessProbeDelay: values["warm-cdn-readiness-probe-delay"] === void 0 ? void 0 : validateTimerDelay(parseNonNegativeIntegerArg(values["warm-cdn-readiness-probe-delay"], "--warm-cdn-readiness-probe-delay"), "--warm-cdn-readiness-probe-delay", values["warm-cdn-readiness-probe-delay"]),
|
|
202
|
+
dangerouslyPromoteOnCdnWarmError: values["dangerously-promote-on-cdn-warm-error"],
|
|
203
|
+
warmCdnPromote: !values["no-promote"] && !values["warm-cdn-no-promote"],
|
|
204
|
+
warmCdnPromotionDelay: values["warm-cdn-promotion-delay"] === void 0 ? void 0 : validatePromotionDelay(parseNonNegativeIntegerArg(values["warm-cdn-promotion-delay"], "--warm-cdn-promotion-delay"), values["warm-cdn-promotion-delay"]),
|
|
127
205
|
warmCdnIncludeFallbacks: values["warm-cdn-include-fallbacks"],
|
|
128
|
-
experimentalTPR: values["experimental-tpr"],
|
|
129
|
-
tprCoverage: parseIntArg("
|
|
130
|
-
tprLimit: parseIntArg("
|
|
131
|
-
tprWindow: parseIntArg("
|
|
206
|
+
experimentalTPR: values["experimental-traffic-aware-warm-cache"] || values["experimental-tpr"],
|
|
207
|
+
tprCoverage: parseIntArg("traffic-aware-coverage", values["traffic-aware-coverage"] ?? values["tpr-coverage"]),
|
|
208
|
+
tprLimit: parseIntArg("traffic-aware-limit", values["traffic-aware-limit"] ?? values["tpr-limit"]),
|
|
209
|
+
tprWindow: parseIntArg("traffic-aware-window", values["traffic-aware-window"] ?? values["tpr-window"])
|
|
132
210
|
};
|
|
133
211
|
}
|
|
134
212
|
/**
|
|
@@ -164,13 +242,27 @@ async function loadProjectViteApi(root) {
|
|
|
164
242
|
}
|
|
165
243
|
return await (vitePath === "vite" ? import(vitePath) : import(pathToFileURL(vitePath).href));
|
|
166
244
|
}
|
|
245
|
+
async function loadProjectWranglerApi(root) {
|
|
246
|
+
const req = createRequire(path.join(root, "package.json"));
|
|
247
|
+
let wranglerPath;
|
|
248
|
+
try {
|
|
249
|
+
wranglerPath = req.resolve("wrangler");
|
|
250
|
+
} catch {
|
|
251
|
+
const cloudflarePluginPath = req.resolve("@cloudflare/vite-plugin");
|
|
252
|
+
wranglerPath = createRequire(cloudflarePluginPath).resolve("wrangler");
|
|
253
|
+
}
|
|
254
|
+
return await import(
|
|
255
|
+
/* @vite-ignore */
|
|
256
|
+
pathToFileURL(wranglerPath).href
|
|
257
|
+
);
|
|
258
|
+
}
|
|
167
259
|
async function loadDeployViteConfigMetadata(root) {
|
|
168
260
|
const plugins = (await (await loadProjectViteApi(root)).loadConfigFromFile({
|
|
169
261
|
command: "build",
|
|
170
262
|
mode: "production"
|
|
171
263
|
}, void 0, root))?.config.plugins;
|
|
172
264
|
return {
|
|
173
|
-
cacheConfig:
|
|
265
|
+
cacheConfig: await findVinextCacheConfigInPlugins(plugins),
|
|
174
266
|
nextConfig: await findVinextNextConfigInPlugins(plugins),
|
|
175
267
|
prerenderConfig: await findVinextPrerenderConfigInPlugins(plugins),
|
|
176
268
|
routeRootConfig: await findVinextRouteRootConfigInPlugins(plugins)
|
|
@@ -309,6 +401,7 @@ async function runWranglerKVBulkPut(root, options, execute = spawn, nodeExecutab
|
|
|
309
401
|
}
|
|
310
402
|
}
|
|
311
403
|
async function runWranglerDeploy(root, options, execute = spawn) {
|
|
404
|
+
if (options.promote === false) return runWranglerVersionUpload(root, options).previewUrl ?? "(Preview URL not detected in wrangler output)";
|
|
312
405
|
const spawnOptions = {
|
|
313
406
|
cwd: root,
|
|
314
407
|
stdio: [
|
|
@@ -344,90 +437,548 @@ async function runWranglerDeploy(root, options, execute = spawn) {
|
|
|
344
437
|
reject(/* @__PURE__ */ new Error(`Wrangler deploy failed with ${exitReason}.`));
|
|
345
438
|
});
|
|
346
439
|
});
|
|
347
|
-
return
|
|
440
|
+
return parseWorkerDeploymentUrl(output) ?? "(URL not detected in wrangler output)";
|
|
441
|
+
}
|
|
442
|
+
function hasCdnWarmRequests(plan) {
|
|
443
|
+
return plan.paths.length + (plan.pagesDataPaths?.length ?? 0) + (plan.routeHandlerPaths?.length ?? 0) + plan.rscPaths.length + plan.loadingShellPaths.length > 0;
|
|
444
|
+
}
|
|
445
|
+
function selectTPRWarmPlan(plan, traffic, coverage, limit) {
|
|
446
|
+
const canonical = (pathname) => normalizePathTrailingSlash(pathname, false);
|
|
447
|
+
const resolved = new Set(Object.entries(plan.routePatterns ?? {}).map(([pathname, route]) => canonical(route.cacheabilityProbe?.concretePathname ?? pathname)));
|
|
448
|
+
const requestsByPath = /* @__PURE__ */ new Map();
|
|
449
|
+
for (const { path, requests } of traffic) {
|
|
450
|
+
const pathname = canonical(path);
|
|
451
|
+
if (!resolved.has(pathname)) continue;
|
|
452
|
+
requestsByPath.set(pathname, (requestsByPath.get(pathname) ?? 0) + requests);
|
|
453
|
+
}
|
|
454
|
+
const selected = new Set(selectRoutes(Array.from(requestsByPath, ([path, requests]) => ({
|
|
455
|
+
path,
|
|
456
|
+
requests
|
|
457
|
+
})).sort((a, b) => b.requests - a.requests), coverage, limit).routes.map(({ path }) => path));
|
|
458
|
+
const includes = (pathname) => selected.has(canonical(plan.routePatterns?.[pathname]?.cacheabilityProbe?.concretePathname ?? pathname));
|
|
459
|
+
const filter = (pathnames) => pathnames?.filter(includes);
|
|
460
|
+
return {
|
|
461
|
+
...plan,
|
|
462
|
+
appPaths: filter(plan.appPaths),
|
|
463
|
+
loadingShellPaths: filter(plan.loadingShellPaths) ?? [],
|
|
464
|
+
pagesDataPaths: filter(plan.pagesDataPaths),
|
|
465
|
+
pagesPaths: filter(plan.pagesPaths),
|
|
466
|
+
paths: filter(plan.paths) ?? [],
|
|
467
|
+
routeHandlerPaths: filter(plan.routeHandlerPaths),
|
|
468
|
+
routePatterns: plan.routePatterns ? Object.fromEntries(Object.entries(plan.routePatterns).filter(([pathname]) => includes(pathname))) : void 0,
|
|
469
|
+
rscPaths: filter(plan.rscPaths) ?? []
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
function projectRequiresRouteCacheabilityProbeManifest(project, cacheConfig) {
|
|
473
|
+
return (project.isAppRouter || project.isPagesRouter) && requiresRouteCacheabilityProbeManifest(cacheConfig);
|
|
474
|
+
}
|
|
475
|
+
function cdnWarmTargetKey(target) {
|
|
476
|
+
return `${target.kind}\0${target.sourcePathname}`;
|
|
477
|
+
}
|
|
478
|
+
function requiredCdnWarmTargetKeys(plan, optionalTargetKeys) {
|
|
479
|
+
const keys = /* @__PURE__ */ new Set([
|
|
480
|
+
...plan.paths.map((pathname) => `html\0${pathname}`),
|
|
481
|
+
...plan.pagesDataPaths.map((pathname) => `pages-data\0${pathname}`),
|
|
482
|
+
...(plan.routeHandlerPaths ?? []).map((pathname) => `app-route\0${pathname}`),
|
|
483
|
+
...plan.rscPaths.map((pathname) => `rsc-full\0${pathname}`),
|
|
484
|
+
...plan.loadingShellPaths.map((pathname) => `rsc-loading-shell\0${pathname}`)
|
|
485
|
+
]);
|
|
486
|
+
if (optionalTargetKeys) for (const key of optionalTargetKeys) keys.delete(key);
|
|
487
|
+
return keys;
|
|
348
488
|
}
|
|
349
489
|
async function deployWithCdnWarmup(root, paths, options) {
|
|
350
|
-
|
|
490
|
+
if (options.warmCdnTarget !== void 0) options = {
|
|
491
|
+
...options,
|
|
492
|
+
warmCdnTarget: validateCdnWarmTarget(options.warmCdnTarget)
|
|
493
|
+
};
|
|
494
|
+
if (options.warmCdnDiscoveryTimeout !== void 0) parsePositiveIntegerArg(String(options.warmCdnDiscoveryTimeout), "--warm-cdn-discovery-timeout");
|
|
495
|
+
if (options.warmCdnDiscoveryRetries !== void 0) parseNonNegativeIntegerArg(String(options.warmCdnDiscoveryRetries), "--warm-cdn-discovery-retries");
|
|
496
|
+
if (options.warmCdnProbeTimeout !== void 0) parsePositiveIntegerArg(String(options.warmCdnProbeTimeout), "--warm-cdn-probe-timeout");
|
|
497
|
+
if (options.warmCdnProbeRetries !== void 0) parseNonNegativeIntegerArg(String(options.warmCdnProbeRetries), "--warm-cdn-probe-retries");
|
|
498
|
+
if (options.warmCdnReadinessTimeout !== void 0) parsePositiveIntegerArg(String(options.warmCdnReadinessTimeout), "--warm-cdn-readiness-timeout");
|
|
499
|
+
if (options.warmCdnReadinessRetries !== void 0) parseNonNegativeIntegerArg(String(options.warmCdnReadinessRetries), "--warm-cdn-readiness-retries");
|
|
500
|
+
if (options.warmCdnReadinessProbes !== void 0) parsePositiveIntegerArg(String(options.warmCdnReadinessProbes), "--warm-cdn-readiness-probes");
|
|
501
|
+
if (options.warmCdnReadinessProbeDelay !== void 0) validateTimerDelay(options.warmCdnReadinessProbeDelay, "--warm-cdn-readiness-probe-delay");
|
|
502
|
+
if (options.warmCdnPromotionDelay !== void 0) validatePromotionDelay(options.warmCdnPromotionDelay);
|
|
503
|
+
if (options.cacheabilityProbe) return deployWithCacheabilityProbe(root, options);
|
|
504
|
+
return deployUploadedVersionWithCdnWarmup(root, paths, options);
|
|
505
|
+
}
|
|
506
|
+
async function deployUploadedVersionWithCdnWarmup(root, paths, options) {
|
|
507
|
+
const allowUnverifiedPromotion = options.dangerouslyPromoteOnCdnWarmError === true && options.warmCdnCertify !== true;
|
|
508
|
+
let deploymentId = options.deploymentId;
|
|
509
|
+
let expectedBuildId = options.expectedBuildId;
|
|
510
|
+
let expectedRscBuildId = options.expectedRscBuildId;
|
|
511
|
+
const hasPreparedWarmPlan = options.uploadedVersion !== void 0;
|
|
512
|
+
let warmPlanDiscovered = hasPreparedWarmPlan || options.discoverWarmPlan === void 0;
|
|
513
|
+
let remainingWarmPlan = {
|
|
514
|
+
loadingShellPaths: [...options.loadingShellPaths ?? []],
|
|
515
|
+
pagesDataPaths: [...options.pagesDataPaths ?? []],
|
|
516
|
+
paths: [...paths],
|
|
517
|
+
routeHandlerPaths: [...options.routeHandlerPaths ?? []],
|
|
518
|
+
routePatterns: options.routePatterns ? { ...options.routePatterns } : void 0,
|
|
519
|
+
rscPaths: [...options.rscPaths ?? []]
|
|
520
|
+
};
|
|
521
|
+
let discoveredWarmRequests = remainingWarmPlan.paths.length + remainingWarmPlan.pagesDataPaths.length + (remainingWarmPlan.routeHandlerPaths?.length ?? 0) + remainingWarmPlan.rscPaths.length + remainingWarmPlan.loadingShellPaths.length;
|
|
522
|
+
const prepareWarmPlan = (plan) => {
|
|
523
|
+
if (plan.paths.length === 0 && plan.pagesDataPaths.length === 0 && (plan.routeHandlerPaths?.length ?? 0) === 0 || expectedBuildId !== void 0) return plan;
|
|
524
|
+
if (!allowUnverifiedPromotion) {
|
|
525
|
+
const warmupKind = plan.paths.length > 0 ? "CDN HTML warmup" : plan.pagesDataPaths.length > 0 ? "CDN Pages data warmup" : "CDN Route Handler warmup";
|
|
526
|
+
throw new Error(`${warmupKind} requires a CDN adapter that declares build-identity response headers. Configure that adapter capability or deploy without --experimental-warm-cdn-cache.`);
|
|
527
|
+
}
|
|
528
|
+
console.warn(` CDN warmup: skipping ${plan.paths.length} HTML, ${plan.pagesDataPaths.length} Pages data, and ${plan.routeHandlerPaths?.length ?? 0} Route Handler request(s) because the CDN adapter does not declare build-identity response headers.`);
|
|
529
|
+
return {
|
|
530
|
+
...plan,
|
|
531
|
+
pagesDataPaths: [],
|
|
532
|
+
paths: [],
|
|
533
|
+
routeHandlerPaths: []
|
|
534
|
+
};
|
|
535
|
+
};
|
|
536
|
+
const discoverWarmPlan = async (targetUrl, headers) => {
|
|
537
|
+
if (!options.discoverWarmPlan) return;
|
|
538
|
+
const discovered = await options.discoverWarmPlan({
|
|
539
|
+
headers,
|
|
540
|
+
targetUrl
|
|
541
|
+
});
|
|
542
|
+
const plan = options.selectWarmPlan?.(discovered) ?? discovered;
|
|
543
|
+
deploymentId = plan.deploymentId;
|
|
544
|
+
expectedBuildId = plan.buildIdentity;
|
|
545
|
+
expectedRscBuildId = plan.rscBuildId;
|
|
546
|
+
remainingWarmPlan = {
|
|
547
|
+
loadingShellPaths: [...plan.loadingShellPaths],
|
|
548
|
+
pagesDataPaths: [...plan.pagesDataPaths ?? []],
|
|
549
|
+
paths: [...plan.paths],
|
|
550
|
+
routeHandlerPaths: [...plan.routeHandlerPaths ?? []],
|
|
551
|
+
routePatterns: plan.routePatterns ? { ...plan.routePatterns } : void 0,
|
|
552
|
+
rscPaths: [...plan.rscPaths]
|
|
553
|
+
};
|
|
554
|
+
discoveredWarmRequests = remainingWarmPlan.paths.length + remainingWarmPlan.pagesDataPaths.length + (remainingWarmPlan.routeHandlerPaths?.length ?? 0) + remainingWarmPlan.rscPaths.length + remainingWarmPlan.loadingShellPaths.length;
|
|
555
|
+
warmPlanDiscovered = true;
|
|
556
|
+
};
|
|
557
|
+
if (!options.discoverWarmPlan || hasPreparedWarmPlan) remainingWarmPlan = prepareWarmPlan(remainingWarmPlan);
|
|
558
|
+
const upload = options.uploadedVersion ?? runWranglerVersionUpload(root, options);
|
|
559
|
+
const warmUploadedVersion = (targetUrl, headers, propagatingTarget = false, plan = {
|
|
560
|
+
loadingShellPaths: remainingWarmPlan.loadingShellPaths,
|
|
561
|
+
pagesDataPaths: remainingWarmPlan.pagesDataPaths,
|
|
562
|
+
paths: remainingWarmPlan.paths,
|
|
563
|
+
routeHandlerPaths: remainingWarmPlan.routeHandlerPaths,
|
|
564
|
+
routePatterns: remainingWarmPlan.routePatterns,
|
|
565
|
+
rscPaths: remainingWarmPlan.rscPaths
|
|
566
|
+
}, requireCacheHit = false) => warmCdnCache({
|
|
567
|
+
targetUrl,
|
|
568
|
+
paths: plan.paths,
|
|
569
|
+
headers,
|
|
570
|
+
propagatingTarget,
|
|
571
|
+
deploymentId,
|
|
572
|
+
expectedBuildId,
|
|
573
|
+
expectedRscBuildId,
|
|
574
|
+
loadingShellPaths: plan.loadingShellPaths,
|
|
575
|
+
pagesDataPaths: plan.pagesDataPaths,
|
|
576
|
+
routeHandlerPaths: plan.routeHandlerPaths,
|
|
577
|
+
routePatterns: plan.routePatterns,
|
|
578
|
+
rscPaths: plan.rscPaths,
|
|
579
|
+
concurrency: options.warmCdnConcurrency,
|
|
580
|
+
timeoutMs: options.warmCdnTimeout,
|
|
581
|
+
retries: options.warmCdnRetries,
|
|
582
|
+
retrySkippedTargetKeys: propagatingTarget && hasPreparedWarmPlan ? requiredCdnWarmTargetKeys(plan, options.optionalWarmTargetKeys) : void 0,
|
|
583
|
+
requireCacheHit,
|
|
584
|
+
strict: requireCacheHit || !allowUnverifiedPromotion,
|
|
585
|
+
statusSource: options.statusSource
|
|
586
|
+
});
|
|
351
587
|
const wranglerConfig = parseWranglerConfig(root, options.config);
|
|
352
|
-
|
|
588
|
+
let deploymentStatus;
|
|
589
|
+
try {
|
|
590
|
+
deploymentStatus = runWranglerDeploymentStatus(root, options);
|
|
591
|
+
} catch (error) {
|
|
592
|
+
if (!hasPreparedWarmPlan) throw error;
|
|
593
|
+
throw new StagedWarmupError(formatUnknownError(error), { cause: error });
|
|
594
|
+
}
|
|
595
|
+
if (options.expectedDeploymentState && !deploymentStateEquals(deploymentStatus, options.expectedDeploymentState)) throw new StagedWarmupError("Two-stage CDN warming stopped because Worker deployment traffic or deployment identity changed before the final version could be staged. No final version was promoted.");
|
|
596
|
+
const stagingTraffic = getZeroPercentStagingTraffic(deploymentStatus, upload.versionId);
|
|
597
|
+
if (hasPreparedWarmPlan && !stagingTraffic) throw new StagedWarmupError("Two-stage CDN warming stopped because Worker deployment traffic changed before the final version could be staged. No final version was promoted.");
|
|
353
598
|
let staged = null;
|
|
354
|
-
let triggersDeployedUrl = null;
|
|
355
|
-
let
|
|
356
|
-
let
|
|
599
|
+
let triggersDeployedUrl = options.triggersDeployedUrl ?? null;
|
|
600
|
+
let stagedCacheFilled = false;
|
|
601
|
+
let stagedDeploymentState = null;
|
|
602
|
+
const initialWarmRequests = options.discoverWarmPlan === void 0 || hasPreparedWarmPlan ? remainingWarmPlan.paths.length + remainingWarmPlan.pagesDataPaths.length + (remainingWarmPlan.routeHandlerPaths?.length ?? 0) + remainingWarmPlan.rscPaths.length + remainingWarmPlan.loadingShellPaths.length : 1;
|
|
603
|
+
let triggersApplied = options.triggersAlreadyApplied === true;
|
|
357
604
|
function applyTriggers() {
|
|
358
605
|
if (triggersApplied) return;
|
|
359
606
|
triggersDeployedUrl = runWranglerTriggersDeploy(root, options).deployedUrl;
|
|
360
607
|
triggersApplied = true;
|
|
361
608
|
}
|
|
362
609
|
if (stagingTraffic) {
|
|
363
|
-
staged = runWranglerVersionDeploy(root, stagingTraffic, options, "stage");
|
|
364
610
|
try {
|
|
611
|
+
staged = runWranglerVersionDeploy(root, stagingTraffic, options, "stage");
|
|
612
|
+
} catch (error) {
|
|
613
|
+
throw reconcileVersionDeployFailure(root, options, error, {
|
|
614
|
+
desiredTraffic: stagingTraffic,
|
|
615
|
+
desiredDescription: "The uploaded version is staged at 0% with the previous version still serving 100% traffic; Worker triggers/routes were not changed.",
|
|
616
|
+
priorState: deploymentStatus,
|
|
617
|
+
priorDescription: "Worker deployment traffic was not changed and Worker triggers/routes were not changed."
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
try {
|
|
621
|
+
if (hasPreparedWarmPlan) {
|
|
622
|
+
stagedDeploymentState = runWranglerDeploymentStatus(root, options);
|
|
623
|
+
if (!deploymentTrafficEquals(stagedDeploymentState.versions, stagingTraffic)) throw new Error("Two-stage CDN warming stopped because Worker deployment traffic changed before production triggers could be applied. No final version was promoted.");
|
|
624
|
+
}
|
|
365
625
|
applyTriggers();
|
|
366
626
|
} catch (error) {
|
|
367
627
|
throw withStagedVersionCleanupNote(error);
|
|
368
628
|
}
|
|
369
|
-
const targetUrl = resolveCdnWarmupTargetUrl(root,
|
|
370
|
-
const headers = buildVersionOverrideHeaders(resolveWorkerNameForVersionOverride(wranglerConfig, options), upload.versionId);
|
|
371
|
-
if (targetUrl && headers) {
|
|
372
|
-
|
|
373
|
-
|
|
629
|
+
const targetUrl = resolveCdnWarmupTargetUrl(root, triggersDeployedUrl, options) ?? staged.deployedUrl;
|
|
630
|
+
const headers = buildVersionOverrideHeaders(options.name ?? upload.workerName ?? resolveWorkerNameForVersionOverride(wranglerConfig, options), upload.versionId);
|
|
631
|
+
if (targetUrl && headers) try {
|
|
632
|
+
if (!warmPlanDiscovered) {
|
|
633
|
+
console.log(" CDN warmup: discovering paths from the staged Worker version...");
|
|
634
|
+
await discoverWarmPlan(targetUrl, headers);
|
|
635
|
+
remainingWarmPlan = prepareWarmPlan(remainingWarmPlan);
|
|
636
|
+
console.log(` CDN warmup: discovered ${remainingWarmPlan.paths.length} HTML, ${remainingWarmPlan.pagesDataPaths.length} Pages data, ${remainingWarmPlan.routeHandlerPaths?.length ?? 0} Route Handler, ${remainingWarmPlan.rscPaths.length} RSC, and ${remainingWarmPlan.loadingShellPaths.length} loading-shell request(s).`);
|
|
637
|
+
}
|
|
638
|
+
const stagedWarmPlan = {
|
|
639
|
+
loadingShellPaths: remainingWarmPlan.loadingShellPaths,
|
|
640
|
+
pagesDataPaths: remainingWarmPlan.pagesDataPaths,
|
|
641
|
+
paths: remainingWarmPlan.paths,
|
|
642
|
+
routeHandlerPaths: remainingWarmPlan.routeHandlerPaths,
|
|
643
|
+
routePatterns: remainingWarmPlan.routePatterns,
|
|
644
|
+
rscPaths: remainingWarmPlan.rscPaths
|
|
645
|
+
};
|
|
646
|
+
const stagedWarmRequests = stagedWarmPlan.paths.length + stagedWarmPlan.pagesDataPaths.length + (stagedWarmPlan.routeHandlerPaths?.length ?? 0) + stagedWarmPlan.rscPaths.length + stagedWarmPlan.loadingShellPaths.length;
|
|
647
|
+
if (stagedWarmRequests > 0) {
|
|
648
|
+
console.log(" CDN warmup: waiting for the staged Worker version to become stable...");
|
|
649
|
+
const readiness = await waitForCdnWarmTargetReadiness({
|
|
374
650
|
targetUrl,
|
|
375
|
-
paths,
|
|
376
651
|
headers,
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
652
|
+
plan: stagedWarmPlan,
|
|
653
|
+
prerenderSecret: options.prerenderSecret,
|
|
654
|
+
deploymentId,
|
|
655
|
+
expectedBuildId,
|
|
656
|
+
expectedRscBuildId,
|
|
657
|
+
phaseTimeoutMs: options.warmCdnReadinessTimeout,
|
|
658
|
+
probeIntervalMs: options.warmCdnReadinessProbeDelay,
|
|
659
|
+
requiredConsecutiveSuccesses: options.warmCdnReadinessProbes,
|
|
660
|
+
retries: options.warmCdnReadinessRetries ?? options.warmCdnRetries,
|
|
661
|
+
timeoutMs: options.warmCdnTimeout
|
|
381
662
|
});
|
|
663
|
+
if (!readiness.ready) {
|
|
664
|
+
const message = `CDN warmup could not verify staged Worker readiness: ${readiness.error}.`;
|
|
665
|
+
const noPromoteNote = options.warmCdnPromote === false ? " CDN warmup cannot continue because promotion is disabled and the staged version was not warmed." : "";
|
|
666
|
+
if (!allowUnverifiedPromotion || options.warmCdnPromote === false) throw new Error(`${message}${noPromoteNote}`);
|
|
667
|
+
console.warn(` ${message} Promoting because the dangerous override is enabled.`);
|
|
668
|
+
} else {
|
|
669
|
+
console.log(" CDN warmup: staged Worker version is stable.");
|
|
670
|
+
const warmResult = await warmUploadedVersion(targetUrl, headers, true, stagedWarmPlan);
|
|
671
|
+
const optionalSkipped = options.optionalWarmTargetKeys ? warmResult.skippedTargets.filter((target) => options.optionalWarmTargetKeys.has(cdnWarmTargetKey(target))).length : 0;
|
|
672
|
+
if (hasPreparedWarmPlan && options.warmCdnCertify) {
|
|
673
|
+
if (warmResult.warmed + optionalSkipped !== stagedWarmRequests) throw new Error(`CDN warmup cannot certify the staged cache because only ${warmResult.warmed}/${stagedWarmRequests - optionalSkipped} cacheable entries completed their initial fill.`);
|
|
674
|
+
}
|
|
675
|
+
const requiredSkipped = warmResult.skipped - optionalSkipped;
|
|
676
|
+
if (hasPreparedWarmPlan && requiredSkipped > 0) {
|
|
677
|
+
const message = `Two-stage CDN warming could not fill ${requiredSkipped}/${warmResult.total} planned cache entries because Cloudflare refused cache admission.`;
|
|
678
|
+
if (!allowUnverifiedPromotion) throw new Error(message);
|
|
679
|
+
console.warn(` ${message} Promoting because the dangerous override is enabled.`);
|
|
680
|
+
}
|
|
681
|
+
if (optionalSkipped > 0) console.log(` CDN warmup: ${optionalSkipped} paired representation${optionalSkipped === 1 ? " remained" : "s remained"} private after ${optionalSkipped === 1 ? "its" : "their"} final render and will not be cached.`);
|
|
682
|
+
remainingWarmPlan = {
|
|
683
|
+
loadingShellPaths: warmResult.retryPlan.loadingShellPaths,
|
|
684
|
+
pagesDataPaths: warmResult.retryPlan.pagesDataPaths,
|
|
685
|
+
paths: warmResult.retryPlan.paths,
|
|
686
|
+
routeHandlerPaths: warmResult.retryPlan.routeHandlerPaths,
|
|
687
|
+
rscPaths: warmResult.retryPlan.rscPaths
|
|
688
|
+
};
|
|
689
|
+
if (options.warmCdnCertify && warmResult.warmed > 0) {
|
|
690
|
+
console.log(` CDN warmup: certifying ${warmResult.warmed} staged cache entr${warmResult.warmed === 1 ? "y" : "ies"} before promotion...`);
|
|
691
|
+
const certification = await warmUploadedVersion(targetUrl, headers, true, warmResult.warmedPlan, true);
|
|
692
|
+
if (certification.warmed !== warmResult.warmed) throw new Error(`CDN warmup certified ${certification.warmed}/${warmResult.warmed} staged cache entries as reusable.`);
|
|
693
|
+
stagedCacheFilled = true;
|
|
694
|
+
} else stagedCacheFilled = warmResult.warmed > 0;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
} catch (error) {
|
|
698
|
+
throw withStagedVersionCleanupNote(error);
|
|
699
|
+
}
|
|
700
|
+
else if (initialWarmRequests > 0) {
|
|
701
|
+
const message = "CDN warmup failed: pre-traffic warmup needs a production URL and Worker name for version overrides. Configure a route/custom domain and Worker name, or deploy without --experimental-warm-cdn-cache.";
|
|
702
|
+
if (!allowUnverifiedPromotion) throw new StagedWarmupError(`${message} ${getStagedVersionCleanupNote()}`);
|
|
703
|
+
console.warn(` ${message} Promoting because the dangerous override is enabled.`);
|
|
704
|
+
}
|
|
705
|
+
} else {
|
|
706
|
+
if (initialWarmRequests > 0) {
|
|
707
|
+
const message = "CDN warmup cannot stage the uploaded Worker at 0% because the current deployment is not exactly one version serving 100% traffic.";
|
|
708
|
+
if (!allowUnverifiedPromotion) throw new Error(`${message} No traffic or triggers were changed.`);
|
|
709
|
+
console.warn(` ${message} Promoting because the dangerous override is enabled.`);
|
|
710
|
+
}
|
|
711
|
+
console.warn(" CDN warmup: pre-traffic version override skipped because the current deployment is not one version serving 100% traffic.");
|
|
712
|
+
}
|
|
713
|
+
const countRemainingWarmRequests = () => remainingWarmPlan.paths.length + remainingWarmPlan.pagesDataPaths.length + (remainingWarmPlan.routeHandlerPaths?.length ?? 0) + remainingWarmPlan.rscPaths.length + remainingWarmPlan.loadingShellPaths.length;
|
|
714
|
+
if (options.warmCdnPromote === false) {
|
|
715
|
+
if (!staged) throw new Error("CDN warmup cannot skip promotion because the uploaded Worker version could not be staged at 0% traffic. The current deployment must have exactly one version serving 100% traffic.");
|
|
716
|
+
const remainingWarmRequests = countRemainingWarmRequests();
|
|
717
|
+
if (!options.allowEmptyWarmPlan && !hasPreparedWarmPlan && options.discoverWarmPlan && discoveredWarmRequests === 0) throw withStagedVersionCleanupNote(/* @__PURE__ */ new Error("CDN warmup cannot skip promotion because no build-discovered requests were found to warm."));
|
|
718
|
+
if (remainingWarmRequests > 0) throw withStagedVersionCleanupNote(/* @__PURE__ */ new Error(`CDN warmup cannot skip promotion because ${remainingWarmRequests} request(s) remain unwarmed.`));
|
|
719
|
+
if (hasPreparedWarmPlan && stagedDeploymentState) {
|
|
720
|
+
let currentDeployment;
|
|
721
|
+
try {
|
|
722
|
+
currentDeployment = runWranglerDeploymentStatus(root, options);
|
|
382
723
|
} catch (error) {
|
|
383
|
-
throw
|
|
724
|
+
throw new StagedWarmupError(`${error instanceof Error ? error.message : String(error)} CDN warmup cannot confirm that the uploaded Worker remains staged at 0% traffic; production Worker triggers/routes remain applied. Inspect \`wrangler deployments status\` before continuing.`, { cause: error });
|
|
384
725
|
}
|
|
385
|
-
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
|
|
726
|
+
if (!deploymentStateEquals(currentDeployment, stagedDeploymentState)) throw new StagedWarmupError("Two-stage CDN warming stopped because Worker deployment traffic or deployment identity changed before the no-promote handoff. The uploaded Worker is not confirmed staged at 0% traffic; production Worker triggers/routes remain applied.");
|
|
727
|
+
}
|
|
728
|
+
console.log(` CDN warmup: promotion disabled; uploaded Worker version ${upload.versionId} remains staged at 0% traffic and production Worker triggers/routes remain applied.`);
|
|
729
|
+
return staged.deployedUrl ?? triggersDeployedUrl ?? upload.previewUrl ?? "(URL not detected in wrangler output)";
|
|
730
|
+
}
|
|
731
|
+
let deployed;
|
|
732
|
+
let prePromotionState = null;
|
|
733
|
+
const promotionTraffic = [{
|
|
389
734
|
versionId: upload.versionId,
|
|
390
735
|
percentage: 100
|
|
391
|
-
}]
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
736
|
+
}];
|
|
737
|
+
let promotionAttempted = false;
|
|
738
|
+
try {
|
|
739
|
+
if (stagedCacheFilled && options.statusSource !== "vinext") {
|
|
740
|
+
const promotionDelay = options.warmCdnPromotionDelay ?? 15e3;
|
|
741
|
+
if (promotionDelay > 0) {
|
|
742
|
+
console.log(` CDN warmup: waiting ${promotionDelay / 1e3} seconds for cache propagation before promotion...`);
|
|
743
|
+
await setTimeout(promotionDelay);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
if (hasPreparedWarmPlan && stagingTraffic) {
|
|
747
|
+
prePromotionState = runWranglerDeploymentStatus(root, options);
|
|
748
|
+
if (!deploymentTrafficEquals(prePromotionState.versions, stagingTraffic) || stagedDeploymentState && !deploymentStateEquals(prePromotionState, stagedDeploymentState)) throw new Error("Two-stage CDN warming stopped because Worker deployment traffic or deployment identity changed before the final version could be promoted. No final version was promoted.");
|
|
397
749
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
750
|
+
promotionAttempted = true;
|
|
751
|
+
deployed = runWranglerVersionDeploy(root, promotionTraffic, options, stagedCacheFilled ? "promote-warmed" : "promote-uploaded");
|
|
752
|
+
} catch (error) {
|
|
753
|
+
if (promotionAttempted) throw reconcileVersionDeployFailure(root, options, error, {
|
|
754
|
+
desiredTraffic: promotionTraffic,
|
|
755
|
+
desiredDescription: "The uploaded version is already promoted to 100%; Worker triggers/routes may already have changed.",
|
|
756
|
+
priorState: prePromotionState ?? (stagingTraffic ? {
|
|
757
|
+
deploymentId: null,
|
|
758
|
+
output: "",
|
|
759
|
+
versions: stagingTraffic
|
|
760
|
+
} : deploymentStatus),
|
|
761
|
+
priorDescription: "The uploaded version remains staged at 0% with the previous version still serving 100% traffic; Worker triggers/routes may already have changed."
|
|
406
762
|
});
|
|
407
|
-
|
|
408
|
-
|
|
763
|
+
throw staged ? withStagedVersionCleanupNote(error) : error;
|
|
764
|
+
}
|
|
765
|
+
try {
|
|
766
|
+
applyTriggers();
|
|
767
|
+
} catch (error) {
|
|
768
|
+
throw withPromotedVersionTriggerNote(error);
|
|
769
|
+
}
|
|
770
|
+
let postPromotionTargetUrl = resolveCdnWarmupTargetUrl(root, triggersDeployedUrl, options) ?? deployed.deployedUrl;
|
|
771
|
+
if (!warmPlanDiscovered && postPromotionTargetUrl) try {
|
|
772
|
+
console.log(" CDN warmup: discovering paths from the promoted Worker version...");
|
|
773
|
+
await discoverWarmPlan(postPromotionTargetUrl);
|
|
774
|
+
remainingWarmPlan = prepareWarmPlan(remainingWarmPlan);
|
|
775
|
+
} catch (error) {
|
|
776
|
+
throw withPromotedVersionWarmupNote(error);
|
|
777
|
+
}
|
|
778
|
+
if (countRemainingWarmRequests() > 0) {
|
|
779
|
+
const targetUrl = postPromotionTargetUrl;
|
|
780
|
+
if (targetUrl) try {
|
|
781
|
+
await warmUploadedVersion(targetUrl, void 0, true, remainingWarmPlan);
|
|
782
|
+
} catch (error) {
|
|
783
|
+
throw withPromotedVersionWarmupNote(error);
|
|
784
|
+
}
|
|
785
|
+
else if (!allowUnverifiedPromotion) throw withPromotedVersionWarmupNote(/* @__PURE__ */ new Error("CDN warmup failed: no production URL could be inferred from wrangler config or output. Configure a route/custom domain, ensure Wrangler prints a workers.dev URL, or deploy without --experimental-warm-cdn-cache."));
|
|
786
|
+
else console.warn(" CDN warmup skipped: no production URL could be inferred after promotion; the dangerous override was enabled.");
|
|
409
787
|
}
|
|
410
788
|
return deployed.deployedUrl ?? triggersDeployedUrl ?? staged?.deployedUrl ?? upload.previewUrl ?? "(URL not detected in wrangler output)";
|
|
411
789
|
}
|
|
412
|
-
function
|
|
413
|
-
const
|
|
414
|
-
const
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
790
|
+
function deploymentTrafficEquals(actual, expected) {
|
|
791
|
+
const normalize = (traffic) => [...traffic].sort((a, b) => a.versionId.localeCompare(b.versionId));
|
|
792
|
+
const normalizedActual = normalize(actual);
|
|
793
|
+
const normalizedExpected = normalize(expected);
|
|
794
|
+
return normalizedActual.length === normalizedExpected.length && normalizedActual.every((version, index) => version.versionId === normalizedExpected[index]?.versionId && version.percentage === normalizedExpected[index]?.percentage);
|
|
795
|
+
}
|
|
796
|
+
function deploymentStateEquals(actual, expected) {
|
|
797
|
+
if (expected.deploymentId !== null && actual.deploymentId !== expected.deploymentId) return false;
|
|
798
|
+
return deploymentTrafficEquals(actual.versions, expected.versions);
|
|
418
799
|
}
|
|
419
|
-
function
|
|
800
|
+
function reconcileVersionDeployFailure(root, options, error, expected) {
|
|
801
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
802
|
+
let reconciliation;
|
|
803
|
+
let mayNeedCleanup = true;
|
|
420
804
|
try {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
805
|
+
const current = runWranglerDeploymentStatus(root, options);
|
|
806
|
+
if (deploymentTrafficEquals(current.versions, expected.desiredTraffic)) reconciliation = expected.desiredDescription;
|
|
807
|
+
else if (deploymentStateEquals(current, expected.priorState)) {
|
|
808
|
+
reconciliation = expected.priorDescription;
|
|
809
|
+
mayNeedCleanup = false;
|
|
810
|
+
} else reconciliation = "Worker deployment state differs from both the requested and prior state, so the deployment outcome is unknown or another deployment changed it.";
|
|
811
|
+
} catch (reconciliationError) {
|
|
812
|
+
reconciliation = `Worker deployment status could not be read after the command failed, so the deployment outcome is unknown (${reconciliationError instanceof Error ? reconciliationError.message : String(reconciliationError)}).`;
|
|
424
813
|
}
|
|
814
|
+
const detail = `${message} ${reconciliation}`;
|
|
815
|
+
return mayNeedCleanup ? new StagedWarmupError(detail, { cause: error }) : new Error(detail, { cause: error });
|
|
816
|
+
}
|
|
817
|
+
function assertDeploymentStateUnchanged(root, options, expected, message) {
|
|
818
|
+
if (!deploymentStateEquals(runWranglerDeploymentStatus(root, options), expected)) throw new Error(message);
|
|
819
|
+
}
|
|
820
|
+
async function deployWithCacheabilityProbe(root, options) {
|
|
821
|
+
if (!options.discoverWarmPlan) throw new Error("Two-stage CDN warming requires staged Worker route discovery before cacheability probing.");
|
|
822
|
+
const prerenderSecret = readPrerenderSecret(root);
|
|
823
|
+
const probeUpload = runWranglerVersionUpload(root, options);
|
|
824
|
+
const initialDeployment = runWranglerDeploymentStatus(root, options);
|
|
825
|
+
const probeTraffic = getZeroPercentStagingTraffic(initialDeployment, probeUpload.versionId);
|
|
826
|
+
if (!probeTraffic) throw new Error("Two-stage CDN warming cannot stage the probe Worker at 0% because the current deployment is not exactly one version serving 100% traffic. No traffic or triggers were changed.");
|
|
827
|
+
let stagedProbe;
|
|
828
|
+
try {
|
|
829
|
+
stagedProbe = runWranglerVersionDeploy(root, probeTraffic, options, "stage");
|
|
830
|
+
} catch (error) {
|
|
831
|
+
throw reconcileVersionDeployFailure(root, options, error, {
|
|
832
|
+
desiredTraffic: probeTraffic,
|
|
833
|
+
desiredDescription: "The probe version is staged at 0% with the previous version still serving 100% traffic; production Worker triggers/routes were not changed.",
|
|
834
|
+
priorState: initialDeployment,
|
|
835
|
+
priorDescription: "Worker deployment traffic was not changed and production Worker triggers/routes were not changed."
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
let stagedProbeDeployment;
|
|
839
|
+
try {
|
|
840
|
+
stagedProbeDeployment = runWranglerDeploymentStatus(root, options);
|
|
841
|
+
if (!deploymentTrafficEquals(stagedProbeDeployment.versions, probeTraffic)) throw new Error("Two-stage CDN warming stopped because Worker deployment traffic changed immediately after the probe version was staged.");
|
|
842
|
+
} catch (error) {
|
|
843
|
+
throw new StagedWarmupError(`${error instanceof Error ? error.message : String(error)} The probe staging command completed, but its current deployment state could not be confirmed; production Worker triggers/routes were not changed. Inspect \`wrangler deployments status\` and remove the staged probe version if necessary.`, { cause: error });
|
|
844
|
+
}
|
|
845
|
+
let prepared;
|
|
846
|
+
try {
|
|
847
|
+
const targetUrl = resolveCdnWarmupTargetUrl(root, stagedProbe.deployedUrl ?? probeUpload.previewUrl, options);
|
|
848
|
+
const wranglerConfig = parseWranglerConfig(root, options.config);
|
|
849
|
+
const headers = buildVersionOverrideHeaders(options.name ?? probeUpload.workerName ?? resolveWorkerNameForVersionOverride(wranglerConfig, options), probeUpload.versionId);
|
|
850
|
+
if (!targetUrl || !headers) throw new Error("Two-stage CDN warming needs a production URL and Worker name for version-overridden cacheability probes.");
|
|
851
|
+
console.log(" CDN warmup: discovering paths from the staged probe Worker...");
|
|
852
|
+
const discovered = await options.discoverWarmPlan({
|
|
853
|
+
headers,
|
|
854
|
+
targetUrl
|
|
855
|
+
});
|
|
856
|
+
if (!discovered.buildId) throw new Error("Two-stage CDN warming requires the staged discovery result to match dist/server/BUILD_ID.");
|
|
857
|
+
if (!discovered.buildIdentity) throw new Error("Two-stage CDN warming requires a CDN adapter that exposes the application build identity.");
|
|
858
|
+
const plan = {
|
|
859
|
+
...discovered,
|
|
860
|
+
appPaths: discovered.appPaths ? [...discovered.appPaths] : void 0,
|
|
861
|
+
fallbackRoutePatterns: discovered.fallbackRoutePatterns ? [...discovered.fallbackRoutePatterns] : void 0,
|
|
862
|
+
loadingShellPaths: [...discovered.loadingShellPaths],
|
|
863
|
+
pagesDataPaths: [...discovered.pagesDataPaths ?? []],
|
|
864
|
+
pagesPaths: discovered.pagesPaths ? [...discovered.pagesPaths] : void 0,
|
|
865
|
+
paths: [...discovered.paths],
|
|
866
|
+
routeHandlerPaths: [...discovered.routeHandlerPaths ?? []],
|
|
867
|
+
routePatterns: discovered.routePatterns ? { ...discovered.routePatterns } : void 0,
|
|
868
|
+
rscPaths: [...discovered.rscPaths]
|
|
869
|
+
};
|
|
870
|
+
if (!plan.appPaths && !plan.pagesPaths) throw new Error("Two-stage CDN warming requires staged discovery to report App or Pages route ownership.");
|
|
871
|
+
const ownedHtmlPaths = /* @__PURE__ */ new Set([...plan.appPaths ?? [], ...plan.pagesPaths ?? []]);
|
|
872
|
+
plan.paths = plan.paths.filter((pathname) => ownedHtmlPaths.has(pathname));
|
|
873
|
+
const targets = await createCdnWarmTargets({
|
|
874
|
+
deploymentId: plan.deploymentId,
|
|
875
|
+
headers,
|
|
876
|
+
loadingShellPaths: plan.loadingShellPaths,
|
|
877
|
+
pagesDataPaths: plan.pagesDataPaths,
|
|
878
|
+
paths: plan.paths,
|
|
879
|
+
routeHandlerPaths: plan.routeHandlerPaths,
|
|
880
|
+
routePatterns: plan.routePatterns,
|
|
881
|
+
rscPaths: plan.rscPaths
|
|
882
|
+
});
|
|
883
|
+
const routePatternCount = new Set(targets.flatMap((target) => target.route ? [`${target.route.kind}\0${target.route.pattern}`] : [])).size;
|
|
884
|
+
const concreteRoutePathCount = new Set(targets.flatMap((target) => target.route ? [`${target.route.kind}\0${target.route.pattern}\0${target.route.cacheabilityProbe?.concretePathname ?? cacheabilityRoutePathname(target.pathname, target.kind)}`] : [])).size;
|
|
885
|
+
if (targets.length > 0) {
|
|
886
|
+
console.log(" CDN warmup: waiting for the staged probe Worker to become stable...");
|
|
887
|
+
const readiness = await waitForCdnWarmTargetReadiness({
|
|
888
|
+
targetUrl,
|
|
889
|
+
headers,
|
|
890
|
+
plan,
|
|
891
|
+
prerenderSecret,
|
|
892
|
+
deploymentId: plan.deploymentId,
|
|
893
|
+
expectedBuildId: plan.buildIdentity,
|
|
894
|
+
expectedRscBuildId: plan.rscBuildId,
|
|
895
|
+
phaseTimeoutMs: options.warmCdnReadinessTimeout,
|
|
896
|
+
probeIntervalMs: options.warmCdnReadinessProbeDelay,
|
|
897
|
+
requiredConsecutiveSuccesses: options.warmCdnReadinessProbes,
|
|
898
|
+
retries: options.warmCdnReadinessRetries ?? options.warmCdnRetries,
|
|
899
|
+
timeoutMs: options.warmCdnTimeout
|
|
900
|
+
});
|
|
901
|
+
if (!readiness.ready) throw new Error(`Two-stage CDN warming could not verify staged probe Worker readiness: ${readiness.error}.`);
|
|
902
|
+
console.log(` CDN warmup: probing ${concreteRoutePathCount} concrete route path${concreteRoutePathCount === 1 ? "" : "s"} once across ${routePatternCount} pattern${routePatternCount === 1 ? "" : "s"}; filtering ${targets.length} candidate warm request identit${targets.length === 1 ? "y" : "ies"}...`);
|
|
903
|
+
} else {
|
|
904
|
+
const fallbackPatternCount = plan.fallbackRoutePatterns?.length ?? 0;
|
|
905
|
+
console.log(fallbackPatternCount > 0 ? ` CDN warmup: embedding ${fallbackPatternCount} on-demand static route pattern${fallbackPatternCount === 1 ? "" : "s"} without a speculative render.` : " CDN warmup: no page request identities were discovered; embedding an empty fail-closed cacheability manifest.");
|
|
906
|
+
}
|
|
907
|
+
const probeProgress = new CdnOperationProgress();
|
|
908
|
+
let probe;
|
|
909
|
+
try {
|
|
910
|
+
probe = await probeStagedWorkerCacheability({
|
|
911
|
+
buildId: discovered.buildId,
|
|
912
|
+
concurrency: options.warmCdnConcurrency,
|
|
913
|
+
expectedResponseBuildId: plan.buildIdentity,
|
|
914
|
+
fallbackRoutePatterns: plan.fallbackRoutePatterns,
|
|
915
|
+
phaseTimeoutMs: options.warmCdnProbeTimeout ?? 12e4,
|
|
916
|
+
retries: options.warmCdnProbeRetries ?? options.warmCdnRetries ?? 2,
|
|
917
|
+
retryDelayMs: DEFAULT_CACHEABILITY_PROBE_RETRY_DELAY_MS,
|
|
918
|
+
root,
|
|
919
|
+
targets,
|
|
920
|
+
targetUrl,
|
|
921
|
+
timeoutMs: options.warmCdnTimeout,
|
|
922
|
+
onProgress(progress) {
|
|
923
|
+
probeProgress.update(progress.completed, progress.total, `${progress.static} static, ${progress.dynamic} dynamic, ${progress.skipped} skipped`, "Probing route cacheability");
|
|
924
|
+
}
|
|
925
|
+
});
|
|
926
|
+
} finally {
|
|
927
|
+
probeProgress.finish();
|
|
928
|
+
}
|
|
929
|
+
console.log(` CDN warmup: classified ${probe.classified} route pattern${probe.classified === 1 ? "" : "s"} with ${probe.probed} render probe${probe.probed === 1 ? "" : "s"}; ${probe.dynamic} observed dynamic and ${probe.skipped} excluded by pattern-wide proof.`);
|
|
930
|
+
if (probe.failures.length > 0) throw new Error(`Two-stage CDN warming failed to classify ${probe.failures.length}/${concreteRoutePathCount} concrete route paths after ${probe.probed} render probe(s). First failure: ${probe.failures[0]}`);
|
|
931
|
+
const finalPlan = {
|
|
932
|
+
...plan,
|
|
933
|
+
loadingShellPaths: probe.cacheableTargets.filter((target) => target.kind === "rsc-loading-shell").map((target) => target.sourcePathname),
|
|
934
|
+
pagesDataPaths: probe.cacheableTargets.filter((target) => target.kind === "pages-data").map((target) => target.sourcePathname),
|
|
935
|
+
paths: probe.cacheableTargets.filter((target) => target.kind === "html").map((target) => target.sourcePathname),
|
|
936
|
+
routeHandlerPaths: probe.cacheableTargets.filter((target) => target.kind === "app-route").map((target) => target.sourcePathname),
|
|
937
|
+
rscPaths: probe.cacheableTargets.filter((target) => target.kind === "rsc-full").map((target) => target.sourcePathname)
|
|
938
|
+
};
|
|
939
|
+
const selectedFinalPlan = options.selectWarmPlan?.(finalPlan) ?? finalPlan;
|
|
940
|
+
assertDeploymentStateUnchanged(root, options, stagedProbeDeployment, "Two-stage CDN warming stopped because Worker deployment traffic or deployment identity changed while cacheability was being probed. No final version was promoted.");
|
|
941
|
+
const finalConfig = writeCacheabilityManifestArtifact(root, options.config, probe.manifest);
|
|
942
|
+
const finalUpload = runWranglerVersionUpload(root, {
|
|
943
|
+
config: finalConfig,
|
|
944
|
+
env: options.env,
|
|
945
|
+
name: options.name,
|
|
946
|
+
preview: options.preview,
|
|
947
|
+
verbose: options.verbose
|
|
948
|
+
});
|
|
949
|
+
prepared = {
|
|
950
|
+
optionalWarmTargetKeys: new Set(probe.speculativeTargets.map(cdnWarmTargetKey)),
|
|
951
|
+
prerenderSecret,
|
|
952
|
+
plan: selectedFinalPlan,
|
|
953
|
+
upload: finalUpload
|
|
954
|
+
};
|
|
955
|
+
} catch (error) {
|
|
956
|
+
throw withStagedProbeVersionCleanupNote(error);
|
|
957
|
+
}
|
|
958
|
+
return deployUploadedVersionWithCdnWarmup(root, prepared.plan.paths, {
|
|
959
|
+
...options,
|
|
960
|
+
deploymentId: prepared.plan.deploymentId,
|
|
961
|
+
expectedBuildId: prepared.plan.buildIdentity,
|
|
962
|
+
expectedRscBuildId: prepared.plan.rscBuildId,
|
|
963
|
+
expectedDeploymentState: stagedProbeDeployment,
|
|
964
|
+
loadingShellPaths: prepared.plan.loadingShellPaths,
|
|
965
|
+
optionalWarmTargetKeys: prepared.optionalWarmTargetKeys,
|
|
966
|
+
pagesDataPaths: prepared.plan.pagesDataPaths,
|
|
967
|
+
prerenderSecret: prepared.prerenderSecret,
|
|
968
|
+
routeHandlerPaths: prepared.plan.routeHandlerPaths,
|
|
969
|
+
routePatterns: prepared.plan.routePatterns,
|
|
970
|
+
rscPaths: prepared.plan.rscPaths,
|
|
971
|
+
uploadedVersion: prepared.upload
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
function resolveCdnWarmupTargetUrl(_root, deployedUrl, options) {
|
|
975
|
+
return options?.warmCdnTarget ?? deployedUrl;
|
|
425
976
|
}
|
|
426
977
|
function getZeroPercentStagingTraffic(deployment, versionId) {
|
|
427
|
-
const
|
|
428
|
-
if (
|
|
429
|
-
if (
|
|
430
|
-
return [
|
|
978
|
+
const serving = (deployment?.versions ?? []).filter((version) => version.percentage > 0);
|
|
979
|
+
if (serving.length !== 1 || serving[0].percentage !== 100) return null;
|
|
980
|
+
if (serving[0].versionId === versionId) return null;
|
|
981
|
+
return [serving[0], {
|
|
431
982
|
versionId,
|
|
432
983
|
percentage: 0
|
|
433
984
|
}];
|
|
@@ -447,20 +998,31 @@ function quoteStructuredHeaderString(value) {
|
|
|
447
998
|
}
|
|
448
999
|
function buildVersionOverrideHeaders(workerName, versionId) {
|
|
449
1000
|
if (!workerName) return void 0;
|
|
450
|
-
return {
|
|
1001
|
+
return {
|
|
1002
|
+
"Cloudflare-Workers-Version-Overrides": `${workerName}=${quoteStructuredHeaderString(versionId)}`,
|
|
1003
|
+
[VINEXT_EXPECTED_WORKER_VERSION_HEADER]: versionId
|
|
1004
|
+
};
|
|
451
1005
|
}
|
|
452
1006
|
function withStagedVersionCleanupNote(error) {
|
|
453
|
-
|
|
454
|
-
|
|
1007
|
+
return new StagedWarmupError(`${error instanceof Error ? error.message : String(error)} ${getStagedVersionCleanupNote()}`, { cause: error });
|
|
1008
|
+
}
|
|
1009
|
+
function withStagedProbeVersionCleanupNote(error) {
|
|
1010
|
+
return new StagedWarmupError(`${error instanceof Error ? error.message : String(error)} The probe version may remain staged at 0% with the previous version still serving 100% traffic; production Worker triggers/routes were not changed.`, { cause: error });
|
|
455
1011
|
}
|
|
456
1012
|
function getStagedVersionCleanupNote() {
|
|
457
|
-
return "The uploaded version may remain staged at 0% with the previous version still serving 100% traffic;
|
|
1013
|
+
return "The uploaded version may remain staged at 0% with the previous version still serving 100% traffic; Worker triggers/routes may also have changed because trigger deployment runs before warming. Rerun deploy to promote it or use `wrangler versions deploy` to choose the desired version split.";
|
|
458
1014
|
}
|
|
459
1015
|
function withPromotedVersionTriggerNote(error) {
|
|
460
1016
|
const message = error instanceof Error ? error.message : String(error);
|
|
461
1017
|
return new Error(`${message} The uploaded version may already be promoted to 100%, but Worker triggers/routes may not be updated; rerun deploy or \`wrangler triggers deploy\` after fixing the trigger error.`, { cause: error });
|
|
462
1018
|
}
|
|
1019
|
+
function withPromotedVersionWarmupNote(error) {
|
|
1020
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1021
|
+
return new Error(`${message} The uploaded version is already promoted to 100% and its Worker triggers/routes were updated; rerun deploy to retry cache warming or roll back with \`wrangler versions deploy\`.`, { cause: error });
|
|
1022
|
+
}
|
|
463
1023
|
async function deploy(options) {
|
|
1024
|
+
if (options.warmCdnTarget !== void 0 && !options.warmCdnCache) throw new Error("--warm-cdn-target requires --experimental-warm-cdn-cache.");
|
|
1025
|
+
const warmCdnTarget = options.warmCdnTarget === void 0 ? void 0 : validateCdnWarmTarget(options.warmCdnTarget);
|
|
464
1026
|
const deployEnv = validateWranglerEnvName(options.env || (options.preview ? "preview" : "production"));
|
|
465
1027
|
const root = path.resolve(options.root);
|
|
466
1028
|
loadDotenv({
|
|
@@ -495,9 +1057,11 @@ async function deploy(options) {
|
|
|
495
1057
|
}
|
|
496
1058
|
const buildEnv = deployEnv === "production" && !options.env ? void 0 : deployEnv;
|
|
497
1059
|
const viteConfigMetadata = await withCloudflareEnv(buildEnv, () => loadDeployViteConfigMetadata(info.root));
|
|
1060
|
+
const cdnAdapterConfig = resolveCdnAdapterConfig(viteConfigMetadata.cacheConfig);
|
|
498
1061
|
const nextConfig = await withCloudflareEnv(buildEnv, async () => {
|
|
499
1062
|
const inlineNextConfig = viteConfigMetadata.nextConfig;
|
|
500
|
-
|
|
1063
|
+
const rawNextConfig = inlineNextConfig ? await resolveNextConfigInput(inlineNextConfig, PHASE_PRODUCTION_BUILD) : await loadNextConfig(info.root, PHASE_PRODUCTION_BUILD);
|
|
1064
|
+
return resolveNextConfig(rawNextConfig, info.root);
|
|
501
1065
|
});
|
|
502
1066
|
const vinextPrerenderConfig = !options.prerenderAll && nextConfig.output !== "export" ? viteConfigMetadata.prerenderConfig : null;
|
|
503
1067
|
const prerenderDecision = resolveVinextPrerenderDecision({
|
|
@@ -505,16 +1069,79 @@ async function deploy(options) {
|
|
|
505
1069
|
vinextPrerenderConfig,
|
|
506
1070
|
nextOutput: nextConfig.output
|
|
507
1071
|
});
|
|
508
|
-
const
|
|
1072
|
+
const hasStrictResponseVary = hasVerbatimResponseVary(viteConfigMetadata.cacheConfig);
|
|
1073
|
+
const warmupStatusSource = cacheWarmupStatusSource(viteConfigMetadata.cacheConfig);
|
|
1074
|
+
const hasStagedRequestRouting = hasUncachedRequestRouting(viteConfigMetadata.cacheConfig) || warmupStatusSource === "data-cache";
|
|
1075
|
+
const hasBuildIdentityHeader = hasBuildIdentityResponseHeader(viteConfigMetadata.cacheConfig);
|
|
1076
|
+
const hasCanonicalRscWarmup = supportsCanonicalRscWarmup(viteConfigMetadata.cacheConfig);
|
|
1077
|
+
const needsCacheabilityProbeManifest = projectRequiresRouteCacheabilityProbeManifest(info, viteConfigMetadata.cacheConfig);
|
|
1078
|
+
const shouldEmitPrerenderPathManifest = !options.skipBuild && prerenderDecision;
|
|
509
1079
|
if (!options.skipBuild) await runBuild(info, buildEnv);
|
|
510
1080
|
else console.log("\n Skipping build (--skip-build)");
|
|
1081
|
+
const canWarmTpr = options.experimentalTPR && !prerenderDecision && hasBuildIdentityHeader;
|
|
1082
|
+
if (options.experimentalTPR && prerenderDecision) console.log(" TPR: Skipping route selection (all-route prerendering configured)");
|
|
1083
|
+
else if (options.experimentalTPR && !hasBuildIdentityHeader) console.log(workerEntryHasCacheHandler(root) ? " TPR: Skipping pre-warm (legacy imperative cache handlers must migrate to declarative vinext({ cache }) for standard warming)" : " TPR: Skipping pre-warm (configured cache does not expose build identity)");
|
|
1084
|
+
const tpr = canWarmTpr ? await resolveTPRRoutes({
|
|
1085
|
+
root,
|
|
1086
|
+
config: options.config,
|
|
1087
|
+
env: buildEnv,
|
|
1088
|
+
hostname: warmCdnTarget ? new URL(warmCdnTarget).hostname : void 0,
|
|
1089
|
+
window: Math.max(1, options.tprWindow ?? 24)
|
|
1090
|
+
}) : null;
|
|
1091
|
+
if (tpr?.skipped) console.log(` TPR: Skipped (${tpr.skipped})`);
|
|
1092
|
+
const tprRoutes = tpr?.routes ?? [];
|
|
1093
|
+
const wranglerOptions = {
|
|
1094
|
+
env: deployEnv === "production" && !options.env ? void 0 : deployEnv,
|
|
1095
|
+
name: options.name,
|
|
1096
|
+
config: options.config,
|
|
1097
|
+
verbose: options.verbose
|
|
1098
|
+
};
|
|
1099
|
+
let shouldWarmTpr = tprRoutes.length > 0;
|
|
1100
|
+
if (shouldWarmTpr && !options.warmCdnCache) {
|
|
1101
|
+
try {
|
|
1102
|
+
shouldWarmTpr = getZeroPercentStagingTraffic(runWranglerDeploymentStatus(root, wranglerOptions), "tpr-preflight") !== null;
|
|
1103
|
+
} catch {
|
|
1104
|
+
shouldWarmTpr = false;
|
|
1105
|
+
}
|
|
1106
|
+
if (!shouldWarmTpr) console.log(" TPR: Skipping pre-warm (current deployment cannot be staged safely)");
|
|
1107
|
+
}
|
|
1108
|
+
const shouldWarmCdnCache = options.warmCdnCache || shouldWarmTpr;
|
|
1109
|
+
const shouldSelectTpr = shouldWarmTpr && !options.warmCdnCache;
|
|
1110
|
+
const candidatePathsOnly = shouldSelectTpr && !needsCacheabilityProbeManifest;
|
|
1111
|
+
const shouldPrerenderLocally = prerenderDecision && (!shouldWarmCdnCache || prerenderDecision.reason === "next-export");
|
|
1112
|
+
if (shouldWarmCdnCache && cdnAdapterConfig) {
|
|
1113
|
+
const wrangler = await loadProjectWranglerApi(info.root);
|
|
1114
|
+
const previousCwd = process.cwd();
|
|
1115
|
+
try {
|
|
1116
|
+
process.chdir(info.root);
|
|
1117
|
+
const config = wrangler.unstable_readConfig({
|
|
1118
|
+
config: options.config,
|
|
1119
|
+
env: buildEnv
|
|
1120
|
+
}, {
|
|
1121
|
+
hideWarnings: true,
|
|
1122
|
+
preserveOriginalMain: true,
|
|
1123
|
+
useRedirectIfAvailable: true
|
|
1124
|
+
});
|
|
1125
|
+
assertCdnVersionMetadataConfig({
|
|
1126
|
+
binding: cdnAdapterConfig.versionMetadataBinding,
|
|
1127
|
+
configuredBinding: config.version_metadata?.binding,
|
|
1128
|
+
configPath: config.configPath
|
|
1129
|
+
});
|
|
1130
|
+
} finally {
|
|
1131
|
+
process.chdir(previousCwd);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
511
1134
|
if (shouldEmitPrerenderPathManifest) await emitPrerenderPathManifest({
|
|
512
1135
|
root: info.root,
|
|
513
1136
|
nextConfig,
|
|
1137
|
+
buildIdentity: hasBuildIdentityHeader ? "response-header" : void 0,
|
|
1138
|
+
requestRouting: hasStagedRequestRouting ? "uncached-stage" : void 0,
|
|
1139
|
+
responseVary: hasStrictResponseVary ? "verbatim" : void 0,
|
|
1140
|
+
isResponsePolicyHeader: (name) => isConfiguredCdnResponsePolicyHeader(viteConfigMetadata.cacheConfig, name),
|
|
514
1141
|
routeRootConfig: viteConfigMetadata.routeRootConfig
|
|
515
1142
|
});
|
|
516
1143
|
let ranPrerender = false;
|
|
517
|
-
if (
|
|
1144
|
+
if (shouldPrerenderLocally) {
|
|
518
1145
|
console.log(`\n ${formatVinextPrerenderLabel(prerenderDecision)}`);
|
|
519
1146
|
if (nextConfig.enablePrerenderSourceMaps) {
|
|
520
1147
|
process.setSourceMapsEnabled(true);
|
|
@@ -523,7 +1150,8 @@ async function deploy(options) {
|
|
|
523
1150
|
await runPrerender({
|
|
524
1151
|
root: info.root,
|
|
525
1152
|
concurrency: options.prerenderConcurrency,
|
|
526
|
-
nextConfig
|
|
1153
|
+
nextConfig,
|
|
1154
|
+
routeRootConfig: viteConfigMetadata.routeRootConfig
|
|
527
1155
|
});
|
|
528
1156
|
ranPrerender = true;
|
|
529
1157
|
}
|
|
@@ -532,43 +1160,73 @@ async function deploy(options) {
|
|
|
532
1160
|
} catch (error) {
|
|
533
1161
|
console.log(` KV cache: Skipping prerender upload (${formatUnknownError(error)}). Continuing with deploy.`);
|
|
534
1162
|
}
|
|
535
|
-
if (options.experimentalTPR) {
|
|
536
|
-
console.log();
|
|
537
|
-
const tprResult = await runTPR({
|
|
538
|
-
root,
|
|
539
|
-
config: options.config,
|
|
540
|
-
coverage: Math.max(1, Math.min(100, options.tprCoverage ?? 90)),
|
|
541
|
-
limit: Math.max(1, options.tprLimit ?? 1e3),
|
|
542
|
-
window: Math.max(1, options.tprWindow ?? 24)
|
|
543
|
-
});
|
|
544
|
-
if (tprResult.skipped) console.log(` TPR: Skipped (${tprResult.skipped})`);
|
|
545
|
-
}
|
|
546
|
-
const wranglerOptions = {
|
|
547
|
-
env: deployEnv === "production" && !options.env ? void 0 : deployEnv,
|
|
548
|
-
name: options.name,
|
|
549
|
-
config: options.config
|
|
550
|
-
};
|
|
551
1163
|
let url;
|
|
552
|
-
if (
|
|
553
|
-
|
|
554
|
-
includeFallbackShells: options.warmCdnIncludeFallbacks,
|
|
555
|
-
strict: options.warmCdnStrict
|
|
556
|
-
});
|
|
557
|
-
if (warmPaths.length > 0) url = await deployWithCdnWarmup(root, warmPaths, {
|
|
1164
|
+
if (shouldWarmCdnCache) try {
|
|
1165
|
+
url = await deployWithCdnWarmup(root, [], {
|
|
558
1166
|
...wranglerOptions,
|
|
1167
|
+
allowEmptyWarmPlan: tprRoutes.length > 0 && !options.warmCdnCache,
|
|
1168
|
+
cacheabilityProbe: needsCacheabilityProbeManifest,
|
|
1169
|
+
discoverWarmPlan: async ({ headers, targetUrl }) => {
|
|
1170
|
+
const discovery = await discoverPrerenderPathManifest({
|
|
1171
|
+
root: info.root,
|
|
1172
|
+
candidatePaths: tprRoutes.map(({ path }) => path),
|
|
1173
|
+
candidatePathsOnly,
|
|
1174
|
+
nextConfig,
|
|
1175
|
+
buildIdentity: hasBuildIdentityHeader ? "response-header" : void 0,
|
|
1176
|
+
includeCanonicalRsc: hasCanonicalRscWarmup,
|
|
1177
|
+
requestRouting: hasStagedRequestRouting ? "uncached-stage" : void 0,
|
|
1178
|
+
responseVary: hasStrictResponseVary ? "verbatim" : void 0,
|
|
1179
|
+
isResponsePolicyHeader: (name) => isConfiguredCdnResponsePolicyHeader(viteConfigMetadata.cacheConfig, name),
|
|
1180
|
+
routeRootConfig: viteConfigMetadata.routeRootConfig,
|
|
1181
|
+
pathDiscoveryTarget: {
|
|
1182
|
+
baseUrl: targetUrl,
|
|
1183
|
+
headers,
|
|
1184
|
+
phaseTimeoutMs: options.warmCdnDiscoveryTimeout ?? DEFAULT_REMOTE_PATH_DISCOVERY_PHASE_TIMEOUT_MS,
|
|
1185
|
+
retries: options.warmCdnDiscoveryRetries,
|
|
1186
|
+
retryDelayMs: DEFAULT_REMOTE_PATH_DISCOVERY_RETRY_DELAY_MS
|
|
1187
|
+
}
|
|
1188
|
+
});
|
|
1189
|
+
if (!discovery) return {
|
|
1190
|
+
loadingShellPaths: [],
|
|
1191
|
+
paths: [],
|
|
1192
|
+
rscPaths: []
|
|
1193
|
+
};
|
|
1194
|
+
return createPrerenderWarmPlan(root, discovery, {
|
|
1195
|
+
includeCanonicalRsc: hasCanonicalRscWarmup,
|
|
1196
|
+
includeFallbackShells: options.warmCdnIncludeFallbacks,
|
|
1197
|
+
strict: options.warmCdnCertify === true || !options.dangerouslyPromoteOnCdnWarmError
|
|
1198
|
+
});
|
|
1199
|
+
},
|
|
1200
|
+
selectWarmPlan: shouldSelectTpr ? (plan) => selectTPRWarmPlan(plan, tprRoutes, Math.max(1, Math.min(100, options.tprCoverage ?? 90)), Math.max(1, options.tprLimit ?? 1e3)) : void 0,
|
|
1201
|
+
statusSource: warmupStatusSource,
|
|
559
1202
|
warmCdnConcurrency: options.warmCdnConcurrency,
|
|
1203
|
+
warmCdnTarget,
|
|
560
1204
|
warmCdnTimeout: options.warmCdnTimeout,
|
|
561
1205
|
warmCdnRetries: options.warmCdnRetries,
|
|
562
|
-
|
|
1206
|
+
warmCdnDiscoveryTimeout: options.warmCdnDiscoveryTimeout,
|
|
1207
|
+
warmCdnDiscoveryRetries: options.warmCdnDiscoveryRetries,
|
|
1208
|
+
warmCdnProbeTimeout: options.warmCdnProbeTimeout,
|
|
1209
|
+
warmCdnProbeRetries: options.warmCdnProbeRetries,
|
|
1210
|
+
warmCdnCertify: options.warmCdnCertify,
|
|
1211
|
+
warmCdnReadinessTimeout: options.warmCdnReadinessTimeout,
|
|
1212
|
+
warmCdnReadinessRetries: options.warmCdnReadinessRetries,
|
|
1213
|
+
warmCdnReadinessProbes: options.warmCdnReadinessProbes,
|
|
1214
|
+
warmCdnReadinessProbeDelay: options.warmCdnReadinessProbeDelay,
|
|
1215
|
+
dangerouslyPromoteOnCdnWarmError: options.dangerouslyPromoteOnCdnWarmError,
|
|
1216
|
+
warmCdnPromote: options.warmCdnPromote,
|
|
1217
|
+
warmCdnPromotionDelay: options.warmCdnPromotionDelay
|
|
563
1218
|
});
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
1219
|
+
} catch (error) {
|
|
1220
|
+
if (options.warmCdnCache || options.warmCdnPromote === false && error instanceof StagedWarmupError) throw error;
|
|
1221
|
+
console.log(` TPR: Skipping pre-warm (${formatUnknownError(error)}). Continuing with deploy.`);
|
|
1222
|
+
}
|
|
1223
|
+
if (url === void 0) url = await runWranglerDeploy(root, {
|
|
1224
|
+
...wranglerOptions,
|
|
1225
|
+
promote: options.warmCdnPromote
|
|
1226
|
+
});
|
|
569
1227
|
console.log("\n ─────────────────────────────────────────");
|
|
570
|
-
console.log(` Deployed to: ${url}`);
|
|
1228
|
+
console.log(` ${options.warmCdnPromote === false ? "Version URL" : "Deployed to"}: ${url}`);
|
|
571
1229
|
console.log(" ─────────────────────────────────────────\n");
|
|
572
1230
|
}
|
|
573
1231
|
//#endregion
|
|
574
|
-
export { buildNodeCliInvocation, buildVersionOverrideHeaders, buildWranglerDeployArgs, buildWranglerInvocation, buildWranglerKVBulkPutArgs, deploy, deployWithCdnWarmup, getZeroPercentStagingTraffic, parseDeployArgs, resolveCdnWarmupTargetUrl, resolveWorkerNameForVersionOverride, resolveWranglerBin, runWranglerDeploy, runWranglerKVBulkPut, validateWranglerEnvName, withCloudflareEnv };
|
|
1232
|
+
export { DEFAULT_CDN_WARM_PROMOTION_DELAY_MS, buildNodeCliInvocation, buildVersionOverrideHeaders, buildWranglerDeployArgs, buildWranglerInvocation, buildWranglerKVBulkPutArgs, deploy, deployWithCdnWarmup, getZeroPercentStagingTraffic, hasCdnWarmRequests, parseDeployArgs, projectRequiresRouteCacheabilityProbeManifest, resolveCdnWarmupTargetUrl, resolveWorkerNameForVersionOverride, resolveWranglerBin, runWranglerDeploy, runWranglerKVBulkPut, selectTPRWarmPlan, validateWranglerEnvName, withCloudflareEnv };
|