@vinext/cloudflare 1.0.0-beta.1 → 1.0.0-beta.11
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 +193 -1
- package/dist/cache/cdn-adapter-config.d.ts +31 -0
- package/dist/cache/cdn-adapter-config.js +95 -0
- package/dist/cache/cdn-adapter.d.ts +48 -15
- package/dist/cache/cdn-adapter.js +50 -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/config.d.ts +137 -0
- package/dist/cache/config.js +87 -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 +10 -0
- package/dist/cacheability-artifact.js +215 -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 +14 -11
- package/dist/deploy-config.js +24 -5
- package/dist/deploy-help.d.ts +3 -4
- package/dist/deploy-help.js +65 -22
- package/dist/deploy.d.ts +139 -42
- package/dist/deploy.js +946 -132
- 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 +34 -70
- package/dist/tpr.js +50 -603
- package/dist/utils/cache-control-metadata.d.ts +3 -4
- package/dist/version-deploy.d.ts +46 -17
- package/dist/version-deploy.js +151 -14
- 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 +21 -11
package/dist/deploy.js
CHANGED
|
@@ -1,21 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
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";
|
|
10
|
+
import { readBuildOutputWorkerName, runCfDeploymentStatus, runCfTriggersDeploy, runCfVersionDeploy, runCfVersionUpload, 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";
|
|
14
22
|
import { pathToFileURL } from "node:url";
|
|
23
|
+
import { VINEXT_BUILD_LIFECYCLE_CONFIG } from "vinext/internal/build/lifecycle";
|
|
15
24
|
import { runPrerender } from "vinext/internal/build/run-prerender";
|
|
25
|
+
import { printBuildReport } from "vinext/internal/build/report";
|
|
16
26
|
import { loadDotenv } from "vinext/internal/config/dotenv";
|
|
17
27
|
import { findVinextNextConfigInPlugins, loadNextConfig, resolveNextConfig, resolveNextConfigInput } from "vinext/internal/config/next-config";
|
|
18
|
-
import { findVinextCacheConfigInPlugins, findVinextPrerenderConfigInPlugins, findVinextRouteRootConfigInPlugins, formatVinextPrerenderLabel, resolveVinextPrerenderDecision } from "vinext/internal/config/prerender";
|
|
28
|
+
import { cacheWarmupStatusSource, findVinextCacheConfigInPlugins, findVinextPrerenderConfigInPlugins, findVinextRouteRootConfigInPlugins, formatVinextPrerenderLabel, hasBuildIdentityResponseHeader, hasUncachedRequestRouting, hasVerbatimResponseVary, isConfiguredCdnResponsePolicyHeader, requiresRouteCacheabilityProbeManifest, resolveVinextPrerenderDecision, supportsCanonicalRscWarmup } from "vinext/internal/config/prerender";
|
|
19
29
|
import { detectProject, findInNodeModules, formatMissingCloudflarePluginError, getMissingDeps } from "vinext/internal/utils/project";
|
|
20
30
|
import { PHASE_PRODUCTION_BUILD } from "vinext/shims/constants";
|
|
21
31
|
//#region src/deploy.ts
|
|
@@ -28,6 +38,7 @@ import { PHASE_PRODUCTION_BUILD } from "vinext/shims/constants";
|
|
|
28
38
|
* 2. Runs the Vite build
|
|
29
39
|
* 3. Deploys to Cloudflare Workers via Wrangler
|
|
30
40
|
*/
|
|
41
|
+
const DEFAULT_CDN_WARM_PROMOTION_DELAY_MS = 15e3;
|
|
31
42
|
function parsePositiveIntegerArg(raw, flag) {
|
|
32
43
|
if (raw === "") throw new Error(`${flag} requires a value, but none was provided.`);
|
|
33
44
|
const parsed = Number(raw);
|
|
@@ -40,10 +51,31 @@ function parseNonNegativeIntegerArg(raw, flag) {
|
|
|
40
51
|
if (!Number.isInteger(parsed) || parsed < 0) throw new Error(`${flag} expects a non-negative integer, but got "${raw}".`);
|
|
41
52
|
return parsed;
|
|
42
53
|
}
|
|
54
|
+
const MAX_NODE_TIMER_DELAY_MS = 2147483647;
|
|
55
|
+
function validateTimerDelay(value, flag, raw = String(value)) {
|
|
56
|
+
if (!Number.isInteger(value) || value < 0) throw new Error(`${flag} expects a non-negative integer, but got "${raw}".`);
|
|
57
|
+
if (value > MAX_NODE_TIMER_DELAY_MS) throw new Error(`${flag} must not exceed ${MAX_NODE_TIMER_DELAY_MS} milliseconds, but got "${raw}".`);
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
function validatePromotionDelay(value, raw = String(value)) {
|
|
61
|
+
return validateTimerDelay(value, "--warm-cdn-promotion-delay", raw);
|
|
62
|
+
}
|
|
63
|
+
function validateCdnWarmTarget(raw) {
|
|
64
|
+
const value = raw.trim();
|
|
65
|
+
let url;
|
|
66
|
+
try {
|
|
67
|
+
url = new URL(value);
|
|
68
|
+
} catch {
|
|
69
|
+
throw new Error(`--warm-cdn-target expects an HTTPS origin, but got "${raw}".`);
|
|
70
|
+
}
|
|
71
|
+
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}".`);
|
|
72
|
+
return url.origin;
|
|
73
|
+
}
|
|
43
74
|
function formatUnknownError(error) {
|
|
44
75
|
if (error instanceof Error && error.message) return error.message;
|
|
45
76
|
return String(error);
|
|
46
77
|
}
|
|
78
|
+
var StagedWarmupError = class extends Error {};
|
|
47
79
|
/** Deploy command flag definitions for util.parseArgs. */
|
|
48
80
|
const deployArgOptions = {
|
|
49
81
|
help: {
|
|
@@ -66,6 +98,10 @@ const deployArgOptions = {
|
|
|
66
98
|
type: "boolean",
|
|
67
99
|
default: false
|
|
68
100
|
},
|
|
101
|
+
verbose: {
|
|
102
|
+
type: "boolean",
|
|
103
|
+
default: false
|
|
104
|
+
},
|
|
69
105
|
"prerender-all": {
|
|
70
106
|
type: "boolean",
|
|
71
107
|
default: false
|
|
@@ -75,17 +111,46 @@ const deployArgOptions = {
|
|
|
75
111
|
type: "boolean",
|
|
76
112
|
default: false
|
|
77
113
|
},
|
|
114
|
+
"warm-cdn-target": { type: "string" },
|
|
78
115
|
"warm-cdn-concurrency": { type: "string" },
|
|
79
116
|
"warm-cdn-timeout": { type: "string" },
|
|
80
117
|
"warm-cdn-retries": { type: "string" },
|
|
81
|
-
"warm-cdn-
|
|
118
|
+
"warm-cdn-discovery-timeout": { type: "string" },
|
|
119
|
+
"warm-cdn-discovery-retries": { type: "string" },
|
|
120
|
+
"warm-cdn-probe-timeout": { type: "string" },
|
|
121
|
+
"warm-cdn-probe-retries": { type: "string" },
|
|
122
|
+
"warm-cdn-certify": {
|
|
82
123
|
type: "boolean",
|
|
83
124
|
default: false
|
|
84
125
|
},
|
|
126
|
+
"warm-cdn-readiness-timeout": { type: "string" },
|
|
127
|
+
"warm-cdn-readiness-retries": { type: "string" },
|
|
128
|
+
"warm-cdn-readiness-probes": { type: "string" },
|
|
129
|
+
"warm-cdn-readiness-probe-delay": { type: "string" },
|
|
130
|
+
"dangerously-promote-on-cdn-warm-error": {
|
|
131
|
+
type: "boolean",
|
|
132
|
+
default: false
|
|
133
|
+
},
|
|
134
|
+
"no-promote": {
|
|
135
|
+
type: "boolean",
|
|
136
|
+
default: false
|
|
137
|
+
},
|
|
138
|
+
"warm-cdn-no-promote": {
|
|
139
|
+
type: "boolean",
|
|
140
|
+
default: false
|
|
141
|
+
},
|
|
142
|
+
"warm-cdn-promotion-delay": { type: "string" },
|
|
85
143
|
"warm-cdn-include-fallbacks": {
|
|
86
144
|
type: "boolean",
|
|
87
145
|
default: false
|
|
88
146
|
},
|
|
147
|
+
"experimental-traffic-aware-warm-cache": {
|
|
148
|
+
type: "boolean",
|
|
149
|
+
default: false
|
|
150
|
+
},
|
|
151
|
+
"traffic-aware-coverage": { type: "string" },
|
|
152
|
+
"traffic-aware-limit": { type: "string" },
|
|
153
|
+
"traffic-aware-window": { type: "string" },
|
|
89
154
|
"experimental-tpr": {
|
|
90
155
|
type: "boolean",
|
|
91
156
|
default: false
|
|
@@ -100,6 +165,8 @@ function parseDeployArgs(args) {
|
|
|
100
165
|
options: deployArgOptions,
|
|
101
166
|
strict: true
|
|
102
167
|
});
|
|
168
|
+
if (values["warm-cdn-certify"] && !values["experimental-warm-cdn-cache"]) throw new Error("--warm-cdn-certify requires --experimental-warm-cdn-cache.");
|
|
169
|
+
if (values["warm-cdn-target"] && !values["experimental-warm-cdn-cache"]) throw new Error("--warm-cdn-target requires --experimental-warm-cdn-cache.");
|
|
103
170
|
function parseIntArg(name, raw) {
|
|
104
171
|
if (!raw) return void 0;
|
|
105
172
|
const n = parseInt(raw, 10);
|
|
@@ -117,18 +184,31 @@ function parseDeployArgs(args) {
|
|
|
117
184
|
config: values.config?.trim() || void 0,
|
|
118
185
|
skipBuild: values["skip-build"],
|
|
119
186
|
dryRun: values["dry-run"],
|
|
187
|
+
verbose: values.verbose,
|
|
120
188
|
prerenderAll: values["prerender-all"],
|
|
121
189
|
prerenderConcurrency: values["prerender-concurrency"] === void 0 ? void 0 : parsePositiveIntegerArg(values["prerender-concurrency"], "--prerender-concurrency"),
|
|
122
190
|
warmCdnCache: values["experimental-warm-cdn-cache"],
|
|
191
|
+
warmCdnTarget: values["warm-cdn-target"] === void 0 ? void 0 : validateCdnWarmTarget(values["warm-cdn-target"]),
|
|
123
192
|
warmCdnConcurrency: values["warm-cdn-concurrency"] === void 0 ? void 0 : parsePositiveIntegerArg(values["warm-cdn-concurrency"], "--warm-cdn-concurrency"),
|
|
124
193
|
warmCdnTimeout: values["warm-cdn-timeout"] === void 0 ? void 0 : parsePositiveIntegerArg(values["warm-cdn-timeout"], "--warm-cdn-timeout"),
|
|
125
194
|
warmCdnRetries: values["warm-cdn-retries"] === void 0 ? void 0 : parseNonNegativeIntegerArg(values["warm-cdn-retries"], "--warm-cdn-retries"),
|
|
126
|
-
|
|
195
|
+
warmCdnDiscoveryTimeout: values["warm-cdn-discovery-timeout"] === void 0 ? void 0 : parsePositiveIntegerArg(values["warm-cdn-discovery-timeout"], "--warm-cdn-discovery-timeout"),
|
|
196
|
+
warmCdnDiscoveryRetries: values["warm-cdn-discovery-retries"] === void 0 ? void 0 : parseNonNegativeIntegerArg(values["warm-cdn-discovery-retries"], "--warm-cdn-discovery-retries"),
|
|
197
|
+
warmCdnProbeTimeout: values["warm-cdn-probe-timeout"] === void 0 ? void 0 : parsePositiveIntegerArg(values["warm-cdn-probe-timeout"], "--warm-cdn-probe-timeout"),
|
|
198
|
+
warmCdnProbeRetries: values["warm-cdn-probe-retries"] === void 0 ? void 0 : parseNonNegativeIntegerArg(values["warm-cdn-probe-retries"], "--warm-cdn-probe-retries"),
|
|
199
|
+
warmCdnCertify: values["warm-cdn-certify"],
|
|
200
|
+
warmCdnReadinessTimeout: values["warm-cdn-readiness-timeout"] === void 0 ? void 0 : parsePositiveIntegerArg(values["warm-cdn-readiness-timeout"], "--warm-cdn-readiness-timeout"),
|
|
201
|
+
warmCdnReadinessRetries: values["warm-cdn-readiness-retries"] === void 0 ? void 0 : parseNonNegativeIntegerArg(values["warm-cdn-readiness-retries"], "--warm-cdn-readiness-retries"),
|
|
202
|
+
warmCdnReadinessProbes: values["warm-cdn-readiness-probes"] === void 0 ? void 0 : parsePositiveIntegerArg(values["warm-cdn-readiness-probes"], "--warm-cdn-readiness-probes"),
|
|
203
|
+
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"]),
|
|
204
|
+
dangerouslyPromoteOnCdnWarmError: values["dangerously-promote-on-cdn-warm-error"],
|
|
205
|
+
warmCdnPromote: !values["no-promote"] && !values["warm-cdn-no-promote"],
|
|
206
|
+
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
207
|
warmCdnIncludeFallbacks: values["warm-cdn-include-fallbacks"],
|
|
128
|
-
experimentalTPR: values["experimental-tpr"],
|
|
129
|
-
tprCoverage: parseIntArg("
|
|
130
|
-
tprLimit: parseIntArg("
|
|
131
|
-
tprWindow: parseIntArg("
|
|
208
|
+
experimentalTPR: values["experimental-traffic-aware-warm-cache"] || values["experimental-tpr"],
|
|
209
|
+
tprCoverage: parseIntArg("traffic-aware-coverage", values["traffic-aware-coverage"] ?? values["tpr-coverage"]),
|
|
210
|
+
tprLimit: parseIntArg("traffic-aware-limit", values["traffic-aware-limit"] ?? values["tpr-limit"]),
|
|
211
|
+
tprWindow: parseIntArg("traffic-aware-window", values["traffic-aware-window"] ?? values["tpr-window"])
|
|
132
212
|
};
|
|
133
213
|
}
|
|
134
214
|
/**
|
|
@@ -164,26 +244,52 @@ async function loadProjectViteApi(root) {
|
|
|
164
244
|
}
|
|
165
245
|
return await (vitePath === "vite" ? import(vitePath) : import(pathToFileURL(vitePath).href));
|
|
166
246
|
}
|
|
167
|
-
async function
|
|
247
|
+
async function loadProjectWranglerApi(root) {
|
|
248
|
+
const req = createRequire(path.join(root, "package.json"));
|
|
249
|
+
let wranglerPath;
|
|
250
|
+
try {
|
|
251
|
+
wranglerPath = req.resolve("wrangler");
|
|
252
|
+
} catch {
|
|
253
|
+
const cloudflarePluginPath = req.resolve("@cloudflare/vite-plugin");
|
|
254
|
+
wranglerPath = createRequire(cloudflarePluginPath).resolve("wrangler");
|
|
255
|
+
}
|
|
256
|
+
return await import(
|
|
257
|
+
/* @vite-ignore */
|
|
258
|
+
pathToFileURL(wranglerPath).href
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
function resolveViteBuildMode(deploymentTool, env) {
|
|
262
|
+
return deploymentTool === "cf" ? env ?? "production" : "production";
|
|
263
|
+
}
|
|
264
|
+
async function loadDeployViteConfigMetadata(root, mode) {
|
|
168
265
|
const plugins = (await (await loadProjectViteApi(root)).loadConfigFromFile({
|
|
169
266
|
command: "build",
|
|
170
|
-
mode
|
|
267
|
+
mode
|
|
171
268
|
}, void 0, root))?.config.plugins;
|
|
172
269
|
return {
|
|
173
|
-
cacheConfig:
|
|
270
|
+
cacheConfig: await findVinextCacheConfigInPlugins(plugins),
|
|
174
271
|
nextConfig: await findVinextNextConfigInPlugins(plugins),
|
|
175
272
|
prerenderConfig: await findVinextPrerenderConfigInPlugins(plugins),
|
|
176
273
|
routeRootConfig: await findVinextRouteRootConfigInPlugins(plugins)
|
|
177
274
|
};
|
|
178
275
|
}
|
|
179
|
-
async function runBuild(info, env) {
|
|
276
|
+
async function runBuild(info, env, mode) {
|
|
180
277
|
console.log("\n Building for Cloudflare Workers...\n");
|
|
181
278
|
const { createBuilder } = await loadProjectViteApi(info.root);
|
|
279
|
+
let completed = false;
|
|
182
280
|
await withCloudflareEnv(env, async () => {
|
|
183
|
-
|
|
281
|
+
const invocation = { onComplete() {
|
|
282
|
+
completed = true;
|
|
283
|
+
} };
|
|
284
|
+
await (await createBuilder({
|
|
285
|
+
root: info.root,
|
|
286
|
+
mode,
|
|
287
|
+
[VINEXT_BUILD_LIFECYCLE_CONFIG]: invocation
|
|
288
|
+
})).buildApp();
|
|
184
289
|
});
|
|
290
|
+
if (!completed) throw new Error("[vinext] The Cloudflare build lifecycle did not complete.");
|
|
185
291
|
}
|
|
186
|
-
async function populateKVCacheFromPrerenderedArtifacts(root,
|
|
292
|
+
async function populateKVCacheFromPrerenderedArtifacts(root, deploymentTool, env, cacheConfig) {
|
|
187
293
|
const kvConfig = resolveKvDataAdapterConfig(cacheConfig);
|
|
188
294
|
if (!kvConfig) return;
|
|
189
295
|
const { routeCount, pairs } = buildPrerenderKVPairs(path.join(root, "dist", "server"), {
|
|
@@ -194,9 +300,10 @@ async function populateKVCacheFromPrerenderedArtifacts(root, wranglerEnv, cacheC
|
|
|
194
300
|
console.log(" KV cache: Skipping prerender upload (no App Router prerendered cache entries found).");
|
|
195
301
|
return;
|
|
196
302
|
}
|
|
197
|
-
await
|
|
303
|
+
await runKVBulkPut(root, {
|
|
198
304
|
binding: kvConfig.binding,
|
|
199
|
-
|
|
305
|
+
deploymentTool,
|
|
306
|
+
env,
|
|
200
307
|
pairs
|
|
201
308
|
});
|
|
202
309
|
console.log(` KV cache: Uploaded ${pairs.length} entr${pairs.length === 1 ? "y" : "ies"} for ${routeCount} prerendered route${routeCount === 1 ? "" : "s"}.`);
|
|
@@ -217,6 +324,27 @@ function buildWranglerDeployArgs(options) {
|
|
|
217
324
|
env
|
|
218
325
|
};
|
|
219
326
|
}
|
|
327
|
+
function resolveDeploymentTool(root) {
|
|
328
|
+
return fs.existsSync(path.join(root, "cloudflare.config.ts")) ? "cf" : "wrangler";
|
|
329
|
+
}
|
|
330
|
+
function isCfCliInstalled(root, resolvePackageJson = (projectRoot) => {
|
|
331
|
+
try {
|
|
332
|
+
return createRequire(path.join(projectRoot, "package.json")).resolve("cf/package.json");
|
|
333
|
+
} catch {
|
|
334
|
+
return findInNodeModules(projectRoot, "cf/package.json");
|
|
335
|
+
}
|
|
336
|
+
}) {
|
|
337
|
+
return resolvePackageJson(root) !== null;
|
|
338
|
+
}
|
|
339
|
+
function buildCfDeployArgs(options) {
|
|
340
|
+
const mode = options.env || (options.preview ? "preview" : void 0);
|
|
341
|
+
const args = ["deploy", "--prebuilt"];
|
|
342
|
+
if (mode) args.push("--mode", validateWranglerEnvName(mode));
|
|
343
|
+
return {
|
|
344
|
+
args,
|
|
345
|
+
mode
|
|
346
|
+
};
|
|
347
|
+
}
|
|
220
348
|
function buildWranglerKVBulkPutArgs(options) {
|
|
221
349
|
const env = options.env || void 0;
|
|
222
350
|
const args = [
|
|
@@ -255,6 +383,21 @@ function resolveWranglerBin(root, resolvePackageJson = (projectRoot) => {
|
|
|
255
383
|
}
|
|
256
384
|
return path.join(root, "node_modules", "wrangler", "bin", "wrangler.js");
|
|
257
385
|
}
|
|
386
|
+
function resolveCfBin(root, resolvePackageJson = (projectRoot) => {
|
|
387
|
+
try {
|
|
388
|
+
return createRequire(path.join(projectRoot, "package.json")).resolve("cf/package.json");
|
|
389
|
+
} catch {
|
|
390
|
+
return findInNodeModules(projectRoot, "cf/package.json");
|
|
391
|
+
}
|
|
392
|
+
}) {
|
|
393
|
+
const packageJsonPath = resolvePackageJson(root);
|
|
394
|
+
if (packageJsonPath) {
|
|
395
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
396
|
+
const bin = typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin?.cf;
|
|
397
|
+
if (bin) return path.resolve(path.dirname(packageJsonPath), bin);
|
|
398
|
+
}
|
|
399
|
+
return path.join(root, "node_modules", "cf", "bin", "cf");
|
|
400
|
+
}
|
|
258
401
|
function buildNodeCliInvocation(scriptPath, args, nodeExecutable = process.execPath) {
|
|
259
402
|
return {
|
|
260
403
|
file: nodeExecutable,
|
|
@@ -269,21 +412,35 @@ function buildWranglerInvocation(root, options, nodeExecutable = process.execPat
|
|
|
269
412
|
env
|
|
270
413
|
};
|
|
271
414
|
}
|
|
272
|
-
async function
|
|
415
|
+
async function runKVBulkPut(root, options, execute = spawn, nodeExecutable = process.execPath) {
|
|
416
|
+
const cfNamespaceId = (() => {
|
|
417
|
+
if (options.deploymentTool !== "cf") return void 0;
|
|
418
|
+
const configPath = path.join(root, ".cloudflare/output/v0/workers/default/worker.config.json");
|
|
419
|
+
const binding = JSON.parse(fs.readFileSync(configPath, "utf8")).env?.[options.binding];
|
|
420
|
+
if (binding?.type !== "kv" || typeof binding.id !== "string" || !binding.id) throw new Error(`[vinext] Generated Cloudflare Build Output does not declare KV binding ${JSON.stringify(options.binding)} with a namespace ID.`);
|
|
421
|
+
return binding.id;
|
|
422
|
+
})();
|
|
273
423
|
const tempDir = fs.mkdtempSync(path.join(options.tempDir ?? os.tmpdir(), "vinext-kv-bulk-"));
|
|
274
424
|
try {
|
|
275
|
-
const
|
|
425
|
+
const cliBin = cfNamespaceId ? resolveCfBin(root) : resolveWranglerBin(root);
|
|
276
426
|
const totalChunks = Math.ceil(options.pairs.length / KV_BULK_PUT_CHUNK_SIZE);
|
|
277
427
|
for (let i = 0; i < totalChunks; i++) {
|
|
278
428
|
const filePath = path.join(tempDir, `prerender-kv-${i}.json`);
|
|
279
429
|
const chunk = options.pairs.slice(i * KV_BULK_PUT_CHUNK_SIZE, (i + 1) * KV_BULK_PUT_CHUNK_SIZE);
|
|
280
430
|
fs.writeFileSync(filePath, JSON.stringify(chunk), "utf-8");
|
|
281
|
-
const
|
|
431
|
+
const invocation = buildNodeCliInvocation(cliBin, cfNamespaceId ? [
|
|
432
|
+
"kv",
|
|
433
|
+
"bulk",
|
|
434
|
+
"update",
|
|
435
|
+
cfNamespaceId,
|
|
436
|
+
"--body",
|
|
437
|
+
`@${filePath}`,
|
|
438
|
+
...options.env ? ["--mode", validateWranglerEnvName(options.env)] : []
|
|
439
|
+
] : buildWranglerKVBulkPutArgs({
|
|
282
440
|
binding: options.binding,
|
|
283
441
|
env: options.env,
|
|
284
442
|
filePath
|
|
285
|
-
});
|
|
286
|
-
const invocation = buildNodeCliInvocation(wranglerBin, args, nodeExecutable);
|
|
443
|
+
}).args, nodeExecutable);
|
|
287
444
|
const child = execute(invocation.file, invocation.args, {
|
|
288
445
|
cwd: root,
|
|
289
446
|
stdio: "inherit",
|
|
@@ -297,7 +454,7 @@ async function runWranglerKVBulkPut(root, options, execute = spawn, nodeExecutab
|
|
|
297
454
|
return;
|
|
298
455
|
}
|
|
299
456
|
const exitReason = signal ? `signal ${signal}` : `exit code ${code ?? "unknown"}`;
|
|
300
|
-
reject(/* @__PURE__ */ new Error(
|
|
457
|
+
reject(/* @__PURE__ */ new Error(`${cfNamespaceId ? "cf" : "Wrangler"} KV bulk upload failed with ${exitReason}.`));
|
|
301
458
|
});
|
|
302
459
|
});
|
|
303
460
|
}
|
|
@@ -309,6 +466,7 @@ async function runWranglerKVBulkPut(root, options, execute = spawn, nodeExecutab
|
|
|
309
466
|
}
|
|
310
467
|
}
|
|
311
468
|
async function runWranglerDeploy(root, options, execute = spawn) {
|
|
469
|
+
if (options.promote === false) return runWranglerVersionUpload(root, options).previewUrl ?? "(Preview URL not detected in wrangler output)";
|
|
312
470
|
const spawnOptions = {
|
|
313
471
|
cwd: root,
|
|
314
472
|
stdio: [
|
|
@@ -344,90 +502,603 @@ async function runWranglerDeploy(root, options, execute = spawn) {
|
|
|
344
502
|
reject(/* @__PURE__ */ new Error(`Wrangler deploy failed with ${exitReason}.`));
|
|
345
503
|
});
|
|
346
504
|
});
|
|
347
|
-
return
|
|
505
|
+
return parseWorkerDeploymentUrl(output) ?? "(URL not detected in wrangler output)";
|
|
506
|
+
}
|
|
507
|
+
async function runCfDeploy(root, options, execute = spawn) {
|
|
508
|
+
const { args, mode } = buildCfDeployArgs(options);
|
|
509
|
+
console.log(mode ? `\n Deploying Build Output in mode: ${mode}...` : "\n Deploying Build Output...");
|
|
510
|
+
const child = execute(process.execPath, [resolveCfBin(root), ...args], {
|
|
511
|
+
cwd: root,
|
|
512
|
+
stdio: [
|
|
513
|
+
"inherit",
|
|
514
|
+
"pipe",
|
|
515
|
+
"pipe"
|
|
516
|
+
],
|
|
517
|
+
shell: false
|
|
518
|
+
});
|
|
519
|
+
let output = "";
|
|
520
|
+
child.stdout?.on("data", (chunk) => {
|
|
521
|
+
const text = chunk.toString();
|
|
522
|
+
output += text;
|
|
523
|
+
process.stdout.write(text);
|
|
524
|
+
});
|
|
525
|
+
child.stderr?.on("data", (chunk) => {
|
|
526
|
+
const text = chunk.toString();
|
|
527
|
+
output += text;
|
|
528
|
+
process.stderr.write(text);
|
|
529
|
+
});
|
|
530
|
+
await new Promise((resolve, reject) => {
|
|
531
|
+
child.once("error", reject);
|
|
532
|
+
child.once("close", (code, signal) => {
|
|
533
|
+
if (code === 0) resolve();
|
|
534
|
+
else reject(/* @__PURE__ */ new Error(`cf deploy failed with ${signal ? `signal ${signal}` : `exit code ${code ?? "unknown"}`}.`));
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
return parseWorkerDeploymentUrl(output) ?? "(URL not detected in cf output)";
|
|
538
|
+
}
|
|
539
|
+
function hasCdnWarmRequests(plan) {
|
|
540
|
+
return plan.paths.length + (plan.pagesDataPaths?.length ?? 0) + (plan.routeHandlerPaths?.length ?? 0) + plan.rscPaths.length + plan.loadingShellPaths.length > 0;
|
|
541
|
+
}
|
|
542
|
+
function selectTPRWarmPlan(plan, traffic, coverage, limit) {
|
|
543
|
+
const canonical = (pathname) => normalizePathTrailingSlash(pathname, false);
|
|
544
|
+
const resolved = new Set(Object.entries(plan.routePatterns ?? {}).map(([pathname, route]) => canonical(route.cacheabilityProbe?.concretePathname ?? pathname)));
|
|
545
|
+
const requestsByPath = /* @__PURE__ */ new Map();
|
|
546
|
+
for (const { path, requests } of traffic) {
|
|
547
|
+
const pathname = canonical(path);
|
|
548
|
+
if (!resolved.has(pathname)) continue;
|
|
549
|
+
requestsByPath.set(pathname, (requestsByPath.get(pathname) ?? 0) + requests);
|
|
550
|
+
}
|
|
551
|
+
const selected = new Set(selectRoutes(Array.from(requestsByPath, ([path, requests]) => ({
|
|
552
|
+
path,
|
|
553
|
+
requests
|
|
554
|
+
})).sort((a, b) => b.requests - a.requests), coverage, limit).routes.map(({ path }) => path));
|
|
555
|
+
const includes = (pathname) => selected.has(canonical(plan.routePatterns?.[pathname]?.cacheabilityProbe?.concretePathname ?? pathname));
|
|
556
|
+
const filter = (pathnames) => pathnames?.filter(includes);
|
|
557
|
+
return {
|
|
558
|
+
...plan,
|
|
559
|
+
appPaths: filter(plan.appPaths),
|
|
560
|
+
loadingShellPaths: filter(plan.loadingShellPaths) ?? [],
|
|
561
|
+
pagesDataPaths: filter(plan.pagesDataPaths),
|
|
562
|
+
pagesPaths: filter(plan.pagesPaths),
|
|
563
|
+
paths: filter(plan.paths) ?? [],
|
|
564
|
+
routeHandlerPaths: filter(plan.routeHandlerPaths),
|
|
565
|
+
routePatterns: plan.routePatterns ? Object.fromEntries(Object.entries(plan.routePatterns).filter(([pathname]) => includes(pathname))) : void 0,
|
|
566
|
+
rscPaths: filter(plan.rscPaths) ?? []
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
function projectRequiresRouteCacheabilityProbeManifest(project, cacheConfig) {
|
|
570
|
+
return (project.isAppRouter || project.isPagesRouter) && requiresRouteCacheabilityProbeManifest(cacheConfig);
|
|
571
|
+
}
|
|
572
|
+
function deploymentWorkerName(options) {
|
|
573
|
+
if (!options.name) throw new Error("Could not detect the Worker name for its deployment.");
|
|
574
|
+
return options.name;
|
|
575
|
+
}
|
|
576
|
+
function runDeploymentStatus(root, options) {
|
|
577
|
+
return options.deploymentTool === "cf" ? runCfDeploymentStatus(root, {
|
|
578
|
+
name: deploymentWorkerName(options),
|
|
579
|
+
env: options.env,
|
|
580
|
+
verbose: options.verbose
|
|
581
|
+
}) : runWranglerDeploymentStatus(root, options);
|
|
582
|
+
}
|
|
583
|
+
function runVersionDeploy(root, traffic, options, phase) {
|
|
584
|
+
return options.deploymentTool === "cf" ? runCfVersionDeploy(root, traffic, {
|
|
585
|
+
name: deploymentWorkerName(options),
|
|
586
|
+
env: options.env,
|
|
587
|
+
verbose: options.verbose
|
|
588
|
+
}, phase) : runWranglerVersionDeploy(root, traffic, options, phase);
|
|
589
|
+
}
|
|
590
|
+
function runTriggersDeploy(root, options) {
|
|
591
|
+
return options.deploymentTool === "cf" ? runCfTriggersDeploy(root, options) : runWranglerTriggersDeploy(root, options);
|
|
592
|
+
}
|
|
593
|
+
function runVersionUpload(root, options) {
|
|
594
|
+
return options.deploymentTool === "cf" ? runCfVersionUpload(root, options) : runWranglerVersionUpload(root, options);
|
|
595
|
+
}
|
|
596
|
+
function cdnWarmTargetKey(target) {
|
|
597
|
+
return `${target.kind}\0${target.sourcePathname}`;
|
|
598
|
+
}
|
|
599
|
+
function requiredCdnWarmTargetKeys(plan, optionalTargetKeys) {
|
|
600
|
+
const keys = /* @__PURE__ */ new Set([
|
|
601
|
+
...plan.paths.map((pathname) => `html\0${pathname}`),
|
|
602
|
+
...plan.pagesDataPaths.map((pathname) => `pages-data\0${pathname}`),
|
|
603
|
+
...(plan.routeHandlerPaths ?? []).map((pathname) => `app-route\0${pathname}`),
|
|
604
|
+
...plan.rscPaths.map((pathname) => `rsc-full\0${pathname}`),
|
|
605
|
+
...plan.loadingShellPaths.map((pathname) => `rsc-loading-shell\0${pathname}`)
|
|
606
|
+
]);
|
|
607
|
+
if (optionalTargetKeys) for (const key of optionalTargetKeys) keys.delete(key);
|
|
608
|
+
return keys;
|
|
348
609
|
}
|
|
349
610
|
async function deployWithCdnWarmup(root, paths, options) {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
611
|
+
if (options.warmCdnTarget !== void 0) options = {
|
|
612
|
+
...options,
|
|
613
|
+
warmCdnTarget: validateCdnWarmTarget(options.warmCdnTarget)
|
|
614
|
+
};
|
|
615
|
+
if (options.warmCdnDiscoveryTimeout !== void 0) parsePositiveIntegerArg(String(options.warmCdnDiscoveryTimeout), "--warm-cdn-discovery-timeout");
|
|
616
|
+
if (options.warmCdnDiscoveryRetries !== void 0) parseNonNegativeIntegerArg(String(options.warmCdnDiscoveryRetries), "--warm-cdn-discovery-retries");
|
|
617
|
+
if (options.warmCdnProbeTimeout !== void 0) parsePositiveIntegerArg(String(options.warmCdnProbeTimeout), "--warm-cdn-probe-timeout");
|
|
618
|
+
if (options.warmCdnProbeRetries !== void 0) parseNonNegativeIntegerArg(String(options.warmCdnProbeRetries), "--warm-cdn-probe-retries");
|
|
619
|
+
if (options.warmCdnReadinessTimeout !== void 0) parsePositiveIntegerArg(String(options.warmCdnReadinessTimeout), "--warm-cdn-readiness-timeout");
|
|
620
|
+
if (options.warmCdnReadinessRetries !== void 0) parseNonNegativeIntegerArg(String(options.warmCdnReadinessRetries), "--warm-cdn-readiness-retries");
|
|
621
|
+
if (options.warmCdnReadinessProbes !== void 0) parsePositiveIntegerArg(String(options.warmCdnReadinessProbes), "--warm-cdn-readiness-probes");
|
|
622
|
+
if (options.warmCdnReadinessProbeDelay !== void 0) validateTimerDelay(options.warmCdnReadinessProbeDelay, "--warm-cdn-readiness-probe-delay");
|
|
623
|
+
if (options.warmCdnPromotionDelay !== void 0) validatePromotionDelay(options.warmCdnPromotionDelay);
|
|
624
|
+
if (options.cacheabilityProbe) return deployWithCacheabilityProbe(root, options);
|
|
625
|
+
return deployUploadedVersionWithCdnWarmup(root, paths, options);
|
|
626
|
+
}
|
|
627
|
+
async function deployUploadedVersionWithCdnWarmup(root, paths, options) {
|
|
628
|
+
const allowUnverifiedPromotion = options.dangerouslyPromoteOnCdnWarmError === true && options.warmCdnCertify !== true;
|
|
629
|
+
let deploymentId = options.deploymentId;
|
|
630
|
+
let expectedBuildId = options.expectedBuildId;
|
|
631
|
+
let expectedRscBuildId = options.expectedRscBuildId;
|
|
632
|
+
const hasPreparedWarmPlan = options.uploadedVersion !== void 0;
|
|
633
|
+
let warmPlanDiscovered = hasPreparedWarmPlan || options.discoverWarmPlan === void 0;
|
|
634
|
+
let remainingWarmPlan = {
|
|
635
|
+
loadingShellPaths: [...options.loadingShellPaths ?? []],
|
|
636
|
+
pagesDataPaths: [...options.pagesDataPaths ?? []],
|
|
637
|
+
paths: [...paths],
|
|
638
|
+
routeHandlerPaths: [...options.routeHandlerPaths ?? []],
|
|
639
|
+
routePatterns: options.routePatterns ? { ...options.routePatterns } : void 0,
|
|
640
|
+
rscPaths: [...options.rscPaths ?? []]
|
|
641
|
+
};
|
|
642
|
+
let discoveredWarmRequests = remainingWarmPlan.paths.length + remainingWarmPlan.pagesDataPaths.length + (remainingWarmPlan.routeHandlerPaths?.length ?? 0) + remainingWarmPlan.rscPaths.length + remainingWarmPlan.loadingShellPaths.length;
|
|
643
|
+
const prepareWarmPlan = (plan) => {
|
|
644
|
+
if (plan.paths.length === 0 && plan.pagesDataPaths.length === 0 && (plan.routeHandlerPaths?.length ?? 0) === 0 || expectedBuildId !== void 0) return plan;
|
|
645
|
+
if (!allowUnverifiedPromotion) {
|
|
646
|
+
const warmupKind = plan.paths.length > 0 ? "CDN HTML warmup" : plan.pagesDataPaths.length > 0 ? "CDN Pages data warmup" : "CDN Route Handler warmup";
|
|
647
|
+
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.`);
|
|
648
|
+
}
|
|
649
|
+
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.`);
|
|
650
|
+
return {
|
|
651
|
+
...plan,
|
|
652
|
+
pagesDataPaths: [],
|
|
653
|
+
paths: [],
|
|
654
|
+
routeHandlerPaths: []
|
|
655
|
+
};
|
|
656
|
+
};
|
|
657
|
+
const discoverWarmPlan = async (targetUrl, headers) => {
|
|
658
|
+
if (!options.discoverWarmPlan) return;
|
|
659
|
+
const discovered = await options.discoverWarmPlan({
|
|
660
|
+
headers,
|
|
661
|
+
targetUrl
|
|
662
|
+
});
|
|
663
|
+
const plan = options.selectWarmPlan?.(discovered) ?? discovered;
|
|
664
|
+
deploymentId = plan.deploymentId;
|
|
665
|
+
expectedBuildId = plan.buildIdentity;
|
|
666
|
+
expectedRscBuildId = plan.rscBuildId;
|
|
667
|
+
remainingWarmPlan = {
|
|
668
|
+
loadingShellPaths: [...plan.loadingShellPaths],
|
|
669
|
+
pagesDataPaths: [...plan.pagesDataPaths ?? []],
|
|
670
|
+
paths: [...plan.paths],
|
|
671
|
+
routeHandlerPaths: [...plan.routeHandlerPaths ?? []],
|
|
672
|
+
routePatterns: plan.routePatterns ? { ...plan.routePatterns } : void 0,
|
|
673
|
+
rscPaths: [...plan.rscPaths]
|
|
674
|
+
};
|
|
675
|
+
discoveredWarmRequests = remainingWarmPlan.paths.length + remainingWarmPlan.pagesDataPaths.length + (remainingWarmPlan.routeHandlerPaths?.length ?? 0) + remainingWarmPlan.rscPaths.length + remainingWarmPlan.loadingShellPaths.length;
|
|
676
|
+
warmPlanDiscovered = true;
|
|
677
|
+
};
|
|
678
|
+
if (!options.discoverWarmPlan || hasPreparedWarmPlan) remainingWarmPlan = prepareWarmPlan(remainingWarmPlan);
|
|
679
|
+
const upload = options.uploadedVersion ?? runVersionUpload(root, options);
|
|
680
|
+
const wranglerOptions = resolveDeploymentControlPlaneOptions(options, upload);
|
|
681
|
+
const warmUploadedVersion = (targetUrl, headers, propagatingTarget = false, plan = {
|
|
682
|
+
loadingShellPaths: remainingWarmPlan.loadingShellPaths,
|
|
683
|
+
pagesDataPaths: remainingWarmPlan.pagesDataPaths,
|
|
684
|
+
paths: remainingWarmPlan.paths,
|
|
685
|
+
routeHandlerPaths: remainingWarmPlan.routeHandlerPaths,
|
|
686
|
+
routePatterns: remainingWarmPlan.routePatterns,
|
|
687
|
+
rscPaths: remainingWarmPlan.rscPaths
|
|
688
|
+
}, requireCacheHit = false) => warmCdnCache({
|
|
689
|
+
targetUrl,
|
|
690
|
+
paths: plan.paths,
|
|
691
|
+
headers,
|
|
692
|
+
propagatingTarget,
|
|
693
|
+
deploymentId,
|
|
694
|
+
expectedBuildId,
|
|
695
|
+
expectedRscBuildId,
|
|
696
|
+
loadingShellPaths: plan.loadingShellPaths,
|
|
697
|
+
pagesDataPaths: plan.pagesDataPaths,
|
|
698
|
+
routeHandlerPaths: plan.routeHandlerPaths,
|
|
699
|
+
routePatterns: plan.routePatterns,
|
|
700
|
+
rscPaths: plan.rscPaths,
|
|
701
|
+
concurrency: options.warmCdnConcurrency,
|
|
702
|
+
timeoutMs: options.warmCdnTimeout,
|
|
703
|
+
retries: options.warmCdnRetries,
|
|
704
|
+
retrySkippedTargetKeys: propagatingTarget && hasPreparedWarmPlan ? requiredCdnWarmTargetKeys(plan, options.optionalWarmTargetKeys) : void 0,
|
|
705
|
+
requireCacheHit,
|
|
706
|
+
strict: requireCacheHit || !allowUnverifiedPromotion,
|
|
707
|
+
statusSource: options.statusSource
|
|
708
|
+
});
|
|
709
|
+
const wranglerConfig = options.deploymentTool === "cf" ? null : parseWranglerConfig(root, options.config);
|
|
710
|
+
let deploymentStatus;
|
|
711
|
+
try {
|
|
712
|
+
deploymentStatus = runDeploymentStatus(root, wranglerOptions);
|
|
713
|
+
} catch (error) {
|
|
714
|
+
if (!hasPreparedWarmPlan) throw error;
|
|
715
|
+
throw new StagedWarmupError(formatUnknownError(error), { cause: error });
|
|
716
|
+
}
|
|
717
|
+
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.");
|
|
718
|
+
const stagingTraffic = getZeroPercentStagingTraffic(deploymentStatus, upload.versionId);
|
|
719
|
+
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
720
|
let staged = null;
|
|
354
|
-
let triggersDeployedUrl = null;
|
|
355
|
-
let
|
|
356
|
-
let
|
|
721
|
+
let triggersDeployedUrl = options.triggersDeployedUrl ?? null;
|
|
722
|
+
let stagedCacheFilled = false;
|
|
723
|
+
let stagedDeploymentState = null;
|
|
724
|
+
const initialWarmRequests = options.discoverWarmPlan === void 0 || hasPreparedWarmPlan ? remainingWarmPlan.paths.length + remainingWarmPlan.pagesDataPaths.length + (remainingWarmPlan.routeHandlerPaths?.length ?? 0) + remainingWarmPlan.rscPaths.length + remainingWarmPlan.loadingShellPaths.length : 1;
|
|
725
|
+
let triggersApplied = options.triggersAlreadyApplied === true;
|
|
357
726
|
function applyTriggers() {
|
|
358
727
|
if (triggersApplied) return;
|
|
359
|
-
triggersDeployedUrl =
|
|
728
|
+
triggersDeployedUrl = runTriggersDeploy(root, wranglerOptions).deployedUrl;
|
|
360
729
|
triggersApplied = true;
|
|
361
730
|
}
|
|
362
731
|
if (stagingTraffic) {
|
|
363
|
-
staged = runWranglerVersionDeploy(root, stagingTraffic, options, "stage");
|
|
364
732
|
try {
|
|
733
|
+
staged = runVersionDeploy(root, stagingTraffic, wranglerOptions, "stage");
|
|
734
|
+
} catch (error) {
|
|
735
|
+
throw reconcileVersionDeployFailure(root, wranglerOptions, error, {
|
|
736
|
+
desiredTraffic: stagingTraffic,
|
|
737
|
+
desiredDescription: "The uploaded version is staged at 0% with the previous version still serving 100% traffic; Worker triggers/routes were not changed.",
|
|
738
|
+
priorState: deploymentStatus,
|
|
739
|
+
priorDescription: "Worker deployment traffic was not changed and Worker triggers/routes were not changed."
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
try {
|
|
743
|
+
if (hasPreparedWarmPlan) {
|
|
744
|
+
stagedDeploymentState = runDeploymentStatus(root, wranglerOptions);
|
|
745
|
+
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.");
|
|
746
|
+
}
|
|
365
747
|
applyTriggers();
|
|
366
748
|
} catch (error) {
|
|
367
749
|
throw withStagedVersionCleanupNote(error);
|
|
368
750
|
}
|
|
369
|
-
const targetUrl = resolveCdnWarmupTargetUrl(root,
|
|
370
|
-
const headers = buildVersionOverrideHeaders(resolveWorkerNameForVersionOverride(wranglerConfig,
|
|
371
|
-
if (targetUrl && headers) {
|
|
372
|
-
|
|
373
|
-
|
|
751
|
+
const targetUrl = resolveCdnWarmupTargetUrl(root, triggersDeployedUrl, options) ?? staged.deployedUrl;
|
|
752
|
+
const headers = buildVersionOverrideHeaders(options.name ?? upload.workerName ?? resolveWorkerNameForVersionOverride(wranglerConfig, wranglerOptions), upload.versionId);
|
|
753
|
+
if (targetUrl && headers) try {
|
|
754
|
+
if (!warmPlanDiscovered) {
|
|
755
|
+
console.log(" CDN warmup: discovering paths from the staged Worker version...");
|
|
756
|
+
await discoverWarmPlan(targetUrl, headers);
|
|
757
|
+
remainingWarmPlan = prepareWarmPlan(remainingWarmPlan);
|
|
758
|
+
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).`);
|
|
759
|
+
}
|
|
760
|
+
const stagedWarmPlan = {
|
|
761
|
+
loadingShellPaths: remainingWarmPlan.loadingShellPaths,
|
|
762
|
+
pagesDataPaths: remainingWarmPlan.pagesDataPaths,
|
|
763
|
+
paths: remainingWarmPlan.paths,
|
|
764
|
+
routeHandlerPaths: remainingWarmPlan.routeHandlerPaths,
|
|
765
|
+
routePatterns: remainingWarmPlan.routePatterns,
|
|
766
|
+
rscPaths: remainingWarmPlan.rscPaths
|
|
767
|
+
};
|
|
768
|
+
const stagedWarmRequests = stagedWarmPlan.paths.length + stagedWarmPlan.pagesDataPaths.length + (stagedWarmPlan.routeHandlerPaths?.length ?? 0) + stagedWarmPlan.rscPaths.length + stagedWarmPlan.loadingShellPaths.length;
|
|
769
|
+
if (stagedWarmRequests > 0) {
|
|
770
|
+
console.log(" CDN warmup: waiting for the staged Worker version to become stable...");
|
|
771
|
+
const readiness = await waitForCdnWarmTargetReadiness({
|
|
374
772
|
targetUrl,
|
|
375
|
-
paths,
|
|
376
773
|
headers,
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
774
|
+
plan: stagedWarmPlan,
|
|
775
|
+
prerenderSecret: options.prerenderSecret,
|
|
776
|
+
deploymentId,
|
|
777
|
+
expectedBuildId,
|
|
778
|
+
expectedRscBuildId,
|
|
779
|
+
phaseTimeoutMs: options.warmCdnReadinessTimeout,
|
|
780
|
+
probeIntervalMs: options.warmCdnReadinessProbeDelay,
|
|
781
|
+
requiredConsecutiveSuccesses: options.warmCdnReadinessProbes,
|
|
782
|
+
retries: options.warmCdnReadinessRetries ?? options.warmCdnRetries,
|
|
783
|
+
timeoutMs: options.warmCdnTimeout
|
|
381
784
|
});
|
|
785
|
+
if (!readiness.ready) {
|
|
786
|
+
const message = `CDN warmup could not verify staged Worker readiness: ${readiness.error}.`;
|
|
787
|
+
const noPromoteNote = options.warmCdnPromote === false ? " CDN warmup cannot continue because promotion is disabled and the staged version was not warmed." : "";
|
|
788
|
+
if (!allowUnverifiedPromotion || options.warmCdnPromote === false) throw new Error(`${message}${noPromoteNote}`);
|
|
789
|
+
console.warn(` ${message} Promoting because the dangerous override is enabled.`);
|
|
790
|
+
} else {
|
|
791
|
+
console.log(" CDN warmup: staged Worker version is stable.");
|
|
792
|
+
const warmResult = await warmUploadedVersion(targetUrl, headers, true, stagedWarmPlan);
|
|
793
|
+
const optionalSkipped = options.optionalWarmTargetKeys ? warmResult.skippedTargets.filter((target) => options.optionalWarmTargetKeys.has(cdnWarmTargetKey(target))).length : 0;
|
|
794
|
+
if (hasPreparedWarmPlan && options.warmCdnCertify) {
|
|
795
|
+
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.`);
|
|
796
|
+
}
|
|
797
|
+
const requiredSkipped = warmResult.skipped - optionalSkipped;
|
|
798
|
+
if (hasPreparedWarmPlan && requiredSkipped > 0) {
|
|
799
|
+
const message = `Two-stage CDN warming could not fill ${requiredSkipped}/${warmResult.total} planned cache entries because Cloudflare refused cache admission.`;
|
|
800
|
+
if (!allowUnverifiedPromotion) throw new Error(message);
|
|
801
|
+
console.warn(` ${message} Promoting because the dangerous override is enabled.`);
|
|
802
|
+
}
|
|
803
|
+
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.`);
|
|
804
|
+
remainingWarmPlan = {
|
|
805
|
+
loadingShellPaths: warmResult.retryPlan.loadingShellPaths,
|
|
806
|
+
pagesDataPaths: warmResult.retryPlan.pagesDataPaths,
|
|
807
|
+
paths: warmResult.retryPlan.paths,
|
|
808
|
+
routeHandlerPaths: warmResult.retryPlan.routeHandlerPaths,
|
|
809
|
+
rscPaths: warmResult.retryPlan.rscPaths
|
|
810
|
+
};
|
|
811
|
+
if (options.warmCdnCertify && warmResult.warmed > 0) {
|
|
812
|
+
console.log(` CDN warmup: certifying ${warmResult.warmed} staged cache entr${warmResult.warmed === 1 ? "y" : "ies"} before promotion...`);
|
|
813
|
+
const certification = await warmUploadedVersion(targetUrl, headers, true, warmResult.warmedPlan, true);
|
|
814
|
+
if (certification.warmed !== warmResult.warmed) throw new Error(`CDN warmup certified ${certification.warmed}/${warmResult.warmed} staged cache entries as reusable.`);
|
|
815
|
+
stagedCacheFilled = true;
|
|
816
|
+
} else stagedCacheFilled = warmResult.warmed > 0;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
} catch (error) {
|
|
820
|
+
throw withStagedVersionCleanupNote(error);
|
|
821
|
+
}
|
|
822
|
+
else if (initialWarmRequests > 0) {
|
|
823
|
+
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.";
|
|
824
|
+
if (!allowUnverifiedPromotion) throw new StagedWarmupError(`${message} ${getStagedVersionCleanupNote()}`);
|
|
825
|
+
console.warn(` ${message} Promoting because the dangerous override is enabled.`);
|
|
826
|
+
}
|
|
827
|
+
} else {
|
|
828
|
+
if (initialWarmRequests > 0) {
|
|
829
|
+
const message = "CDN warmup cannot stage the uploaded Worker at 0% because the current deployment is not exactly one version serving 100% traffic.";
|
|
830
|
+
if (!allowUnverifiedPromotion) throw new Error(`${message} No traffic or triggers were changed.`);
|
|
831
|
+
console.warn(` ${message} Promoting because the dangerous override is enabled.`);
|
|
832
|
+
}
|
|
833
|
+
console.warn(" CDN warmup: pre-traffic version override skipped because the current deployment is not one version serving 100% traffic.");
|
|
834
|
+
}
|
|
835
|
+
const countRemainingWarmRequests = () => remainingWarmPlan.paths.length + remainingWarmPlan.pagesDataPaths.length + (remainingWarmPlan.routeHandlerPaths?.length ?? 0) + remainingWarmPlan.rscPaths.length + remainingWarmPlan.loadingShellPaths.length;
|
|
836
|
+
if (options.warmCdnPromote === false) {
|
|
837
|
+
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.");
|
|
838
|
+
const remainingWarmRequests = countRemainingWarmRequests();
|
|
839
|
+
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."));
|
|
840
|
+
if (remainingWarmRequests > 0) throw withStagedVersionCleanupNote(/* @__PURE__ */ new Error(`CDN warmup cannot skip promotion because ${remainingWarmRequests} request(s) remain unwarmed.`));
|
|
841
|
+
if (hasPreparedWarmPlan && stagedDeploymentState) {
|
|
842
|
+
let currentDeployment;
|
|
843
|
+
try {
|
|
844
|
+
currentDeployment = runDeploymentStatus(root, wranglerOptions);
|
|
382
845
|
} catch (error) {
|
|
383
|
-
throw
|
|
846
|
+
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 the active Worker deployment before continuing.`, { cause: error });
|
|
384
847
|
}
|
|
385
|
-
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
|
|
848
|
+
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.");
|
|
849
|
+
}
|
|
850
|
+
console.log(` CDN warmup: promotion disabled; uploaded Worker version ${upload.versionId} remains staged at 0% traffic and production Worker triggers/routes remain applied.`);
|
|
851
|
+
return staged.deployedUrl ?? triggersDeployedUrl ?? upload.previewUrl ?? "(URL not detected in wrangler output)";
|
|
852
|
+
}
|
|
853
|
+
let deployed;
|
|
854
|
+
let prePromotionState = null;
|
|
855
|
+
const promotionTraffic = [{
|
|
389
856
|
versionId: upload.versionId,
|
|
390
857
|
percentage: 100
|
|
391
|
-
}]
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
858
|
+
}];
|
|
859
|
+
let promotionAttempted = false;
|
|
860
|
+
try {
|
|
861
|
+
if (stagedCacheFilled && options.statusSource !== "vinext") {
|
|
862
|
+
const promotionDelay = options.warmCdnPromotionDelay ?? 15e3;
|
|
863
|
+
if (promotionDelay > 0) {
|
|
864
|
+
console.log(` CDN warmup: waiting ${promotionDelay / 1e3} seconds for cache propagation before promotion...`);
|
|
865
|
+
await setTimeout(promotionDelay);
|
|
866
|
+
}
|
|
397
867
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
868
|
+
if (hasPreparedWarmPlan && stagingTraffic) {
|
|
869
|
+
prePromotionState = runDeploymentStatus(root, wranglerOptions);
|
|
870
|
+
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.");
|
|
871
|
+
}
|
|
872
|
+
promotionAttempted = true;
|
|
873
|
+
deployed = runVersionDeploy(root, promotionTraffic, wranglerOptions, stagedCacheFilled ? "promote-warmed" : "promote-uploaded");
|
|
874
|
+
} catch (error) {
|
|
875
|
+
if (promotionAttempted) throw reconcileVersionDeployFailure(root, wranglerOptions, error, {
|
|
876
|
+
desiredTraffic: promotionTraffic,
|
|
877
|
+
desiredDescription: "The uploaded version is already promoted to 100%; Worker triggers/routes may already have changed.",
|
|
878
|
+
priorState: prePromotionState ?? (stagingTraffic ? {
|
|
879
|
+
deploymentId: null,
|
|
880
|
+
output: "",
|
|
881
|
+
versions: stagingTraffic
|
|
882
|
+
} : deploymentStatus),
|
|
883
|
+
priorDescription: "The uploaded version remains staged at 0% with the previous version still serving 100% traffic; Worker triggers/routes may already have changed."
|
|
406
884
|
});
|
|
407
|
-
|
|
408
|
-
|
|
885
|
+
throw staged ? withStagedVersionCleanupNote(error) : error;
|
|
886
|
+
}
|
|
887
|
+
try {
|
|
888
|
+
applyTriggers();
|
|
889
|
+
} catch (error) {
|
|
890
|
+
throw withPromotedVersionTriggerNote(error);
|
|
891
|
+
}
|
|
892
|
+
let postPromotionTargetUrl = resolveCdnWarmupTargetUrl(root, triggersDeployedUrl, options) ?? deployed.deployedUrl;
|
|
893
|
+
if (!warmPlanDiscovered && postPromotionTargetUrl) try {
|
|
894
|
+
console.log(" CDN warmup: discovering paths from the promoted Worker version...");
|
|
895
|
+
await discoverWarmPlan(postPromotionTargetUrl);
|
|
896
|
+
remainingWarmPlan = prepareWarmPlan(remainingWarmPlan);
|
|
897
|
+
} catch (error) {
|
|
898
|
+
throw withPromotedVersionWarmupNote(error);
|
|
899
|
+
}
|
|
900
|
+
if (countRemainingWarmRequests() > 0) {
|
|
901
|
+
const targetUrl = postPromotionTargetUrl;
|
|
902
|
+
if (targetUrl) try {
|
|
903
|
+
await warmUploadedVersion(targetUrl, void 0, true, remainingWarmPlan);
|
|
904
|
+
} catch (error) {
|
|
905
|
+
throw withPromotedVersionWarmupNote(error);
|
|
906
|
+
}
|
|
907
|
+
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."));
|
|
908
|
+
else console.warn(" CDN warmup skipped: no production URL could be inferred after promotion; the dangerous override was enabled.");
|
|
409
909
|
}
|
|
410
910
|
return deployed.deployedUrl ?? triggersDeployedUrl ?? staged?.deployedUrl ?? upload.previewUrl ?? "(URL not detected in wrangler output)";
|
|
411
911
|
}
|
|
412
|
-
function
|
|
413
|
-
const
|
|
414
|
-
const
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
return deployedUrl;
|
|
912
|
+
function deploymentTrafficEquals(actual, expected) {
|
|
913
|
+
const normalize = (traffic) => [...traffic].sort((a, b) => a.versionId.localeCompare(b.versionId));
|
|
914
|
+
const normalizedActual = normalize(actual);
|
|
915
|
+
const normalizedExpected = normalize(expected);
|
|
916
|
+
return normalizedActual.length === normalizedExpected.length && normalizedActual.every((version, index) => version.versionId === normalizedExpected[index]?.versionId && version.percentage === normalizedExpected[index]?.percentage);
|
|
418
917
|
}
|
|
419
|
-
function
|
|
918
|
+
function deploymentStateEquals(actual, expected) {
|
|
919
|
+
if (expected.deploymentId !== null && actual.deploymentId !== expected.deploymentId) return false;
|
|
920
|
+
return deploymentTrafficEquals(actual.versions, expected.versions);
|
|
921
|
+
}
|
|
922
|
+
function reconcileVersionDeployFailure(root, options, error, expected) {
|
|
923
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
924
|
+
let reconciliation;
|
|
925
|
+
let mayNeedCleanup = true;
|
|
420
926
|
try {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
927
|
+
const current = runDeploymentStatus(root, options);
|
|
928
|
+
if (deploymentTrafficEquals(current.versions, expected.desiredTraffic)) reconciliation = expected.desiredDescription;
|
|
929
|
+
else if (deploymentStateEquals(current, expected.priorState)) {
|
|
930
|
+
reconciliation = expected.priorDescription;
|
|
931
|
+
mayNeedCleanup = false;
|
|
932
|
+
} else reconciliation = "Worker deployment state differs from both the requested and prior state, so the deployment outcome is unknown or another deployment changed it.";
|
|
933
|
+
} catch (reconciliationError) {
|
|
934
|
+
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
935
|
}
|
|
936
|
+
const detail = `${message} ${reconciliation}`;
|
|
937
|
+
return mayNeedCleanup ? new StagedWarmupError(detail, { cause: error }) : new Error(detail, { cause: error });
|
|
938
|
+
}
|
|
939
|
+
function assertDeploymentStateUnchanged(root, options, expected, message) {
|
|
940
|
+
if (!deploymentStateEquals(runDeploymentStatus(root, options), expected)) throw new Error(message);
|
|
941
|
+
}
|
|
942
|
+
async function deployWithCacheabilityProbe(root, options) {
|
|
943
|
+
if (!options.discoverWarmPlan) throw new Error("Two-stage CDN warming requires staged Worker route discovery before cacheability probing.");
|
|
944
|
+
const prerenderSecret = readPrerenderSecret(root);
|
|
945
|
+
const probeUpload = runVersionUpload(root, options);
|
|
946
|
+
const wranglerOptions = resolveDeploymentControlPlaneOptions(options, probeUpload);
|
|
947
|
+
const initialDeployment = runDeploymentStatus(root, wranglerOptions);
|
|
948
|
+
const probeTraffic = getZeroPercentStagingTraffic(initialDeployment, probeUpload.versionId);
|
|
949
|
+
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.");
|
|
950
|
+
let stagedProbe;
|
|
951
|
+
try {
|
|
952
|
+
stagedProbe = runVersionDeploy(root, probeTraffic, wranglerOptions, "stage");
|
|
953
|
+
} catch (error) {
|
|
954
|
+
throw reconcileVersionDeployFailure(root, wranglerOptions, error, {
|
|
955
|
+
desiredTraffic: probeTraffic,
|
|
956
|
+
desiredDescription: "The probe version is staged at 0% with the previous version still serving 100% traffic; production Worker triggers/routes were not changed.",
|
|
957
|
+
priorState: initialDeployment,
|
|
958
|
+
priorDescription: "Worker deployment traffic was not changed and production Worker triggers/routes were not changed."
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
let stagedProbeDeployment;
|
|
962
|
+
try {
|
|
963
|
+
stagedProbeDeployment = runDeploymentStatus(root, wranglerOptions);
|
|
964
|
+
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.");
|
|
965
|
+
} catch (error) {
|
|
966
|
+
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 the active Worker deployment and remove the staged probe version if necessary.`, { cause: error });
|
|
967
|
+
}
|
|
968
|
+
let prepared;
|
|
969
|
+
try {
|
|
970
|
+
const targetUrl = resolveCdnWarmupTargetUrl(root, stagedProbe.deployedUrl ?? probeUpload.previewUrl, options);
|
|
971
|
+
const wranglerConfig = options.deploymentTool === "cf" ? null : parseWranglerConfig(root, wranglerOptions.config);
|
|
972
|
+
const headers = buildVersionOverrideHeaders(options.name ?? probeUpload.workerName ?? resolveWorkerNameForVersionOverride(wranglerConfig, wranglerOptions), probeUpload.versionId);
|
|
973
|
+
if (!targetUrl || !headers) throw new Error("Two-stage CDN warming needs a production URL and Worker name for version-overridden cacheability probes.");
|
|
974
|
+
console.log(" CDN warmup: discovering paths from the staged probe Worker...");
|
|
975
|
+
const discovered = await options.discoverWarmPlan({
|
|
976
|
+
headers,
|
|
977
|
+
targetUrl
|
|
978
|
+
});
|
|
979
|
+
if (!discovered.buildId) throw new Error("Two-stage CDN warming requires the staged discovery result to match dist/server/BUILD_ID.");
|
|
980
|
+
if (!discovered.buildIdentity) throw new Error("Two-stage CDN warming requires a CDN adapter that exposes the application build identity.");
|
|
981
|
+
const plan = {
|
|
982
|
+
...discovered,
|
|
983
|
+
appPaths: discovered.appPaths ? [...discovered.appPaths] : void 0,
|
|
984
|
+
fallbackRoutePatterns: discovered.fallbackRoutePatterns ? [...discovered.fallbackRoutePatterns] : void 0,
|
|
985
|
+
loadingShellPaths: [...discovered.loadingShellPaths],
|
|
986
|
+
pagesDataPaths: [...discovered.pagesDataPaths ?? []],
|
|
987
|
+
pagesPaths: discovered.pagesPaths ? [...discovered.pagesPaths] : void 0,
|
|
988
|
+
paths: [...discovered.paths],
|
|
989
|
+
routeHandlerPaths: [...discovered.routeHandlerPaths ?? []],
|
|
990
|
+
routePatterns: discovered.routePatterns ? { ...discovered.routePatterns } : void 0,
|
|
991
|
+
rscPaths: [...discovered.rscPaths]
|
|
992
|
+
};
|
|
993
|
+
if (!plan.appPaths && !plan.pagesPaths) throw new Error("Two-stage CDN warming requires staged discovery to report App or Pages route ownership.");
|
|
994
|
+
const ownedHtmlPaths = /* @__PURE__ */ new Set([...plan.appPaths ?? [], ...plan.pagesPaths ?? []]);
|
|
995
|
+
plan.paths = plan.paths.filter((pathname) => ownedHtmlPaths.has(pathname));
|
|
996
|
+
const targets = await createCdnWarmTargets({
|
|
997
|
+
deploymentId: plan.deploymentId,
|
|
998
|
+
headers,
|
|
999
|
+
loadingShellPaths: plan.loadingShellPaths,
|
|
1000
|
+
pagesDataPaths: plan.pagesDataPaths,
|
|
1001
|
+
paths: plan.paths,
|
|
1002
|
+
routeHandlerPaths: plan.routeHandlerPaths,
|
|
1003
|
+
routePatterns: plan.routePatterns,
|
|
1004
|
+
rscPaths: plan.rscPaths
|
|
1005
|
+
});
|
|
1006
|
+
const routePatternCount = new Set(targets.flatMap((target) => target.route ? [`${target.route.kind}\0${target.route.pattern}`] : [])).size;
|
|
1007
|
+
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;
|
|
1008
|
+
if (targets.length > 0) {
|
|
1009
|
+
console.log(" CDN warmup: waiting for the staged probe Worker to become stable...");
|
|
1010
|
+
const readiness = await waitForCdnWarmTargetReadiness({
|
|
1011
|
+
targetUrl,
|
|
1012
|
+
headers,
|
|
1013
|
+
plan,
|
|
1014
|
+
prerenderSecret,
|
|
1015
|
+
deploymentId: plan.deploymentId,
|
|
1016
|
+
expectedBuildId: plan.buildIdentity,
|
|
1017
|
+
expectedRscBuildId: plan.rscBuildId,
|
|
1018
|
+
phaseTimeoutMs: options.warmCdnReadinessTimeout,
|
|
1019
|
+
probeIntervalMs: options.warmCdnReadinessProbeDelay,
|
|
1020
|
+
requiredConsecutiveSuccesses: options.warmCdnReadinessProbes,
|
|
1021
|
+
retries: options.warmCdnReadinessRetries ?? options.warmCdnRetries,
|
|
1022
|
+
timeoutMs: options.warmCdnTimeout
|
|
1023
|
+
});
|
|
1024
|
+
if (!readiness.ready) throw new Error(`Two-stage CDN warming could not verify staged probe Worker readiness: ${readiness.error}.`);
|
|
1025
|
+
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"}...`);
|
|
1026
|
+
} else {
|
|
1027
|
+
const fallbackPatternCount = plan.fallbackRoutePatterns?.length ?? 0;
|
|
1028
|
+
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.");
|
|
1029
|
+
}
|
|
1030
|
+
const probeProgress = new CdnOperationProgress();
|
|
1031
|
+
let probe;
|
|
1032
|
+
try {
|
|
1033
|
+
probe = await probeStagedWorkerCacheability({
|
|
1034
|
+
buildId: discovered.buildId,
|
|
1035
|
+
concurrency: options.warmCdnConcurrency,
|
|
1036
|
+
expectedResponseBuildId: plan.buildIdentity,
|
|
1037
|
+
fallbackRoutePatterns: plan.fallbackRoutePatterns,
|
|
1038
|
+
phaseTimeoutMs: options.warmCdnProbeTimeout ?? 12e4,
|
|
1039
|
+
retries: options.warmCdnProbeRetries ?? options.warmCdnRetries ?? 2,
|
|
1040
|
+
retryDelayMs: DEFAULT_CACHEABILITY_PROBE_RETRY_DELAY_MS,
|
|
1041
|
+
root,
|
|
1042
|
+
targets,
|
|
1043
|
+
targetUrl,
|
|
1044
|
+
timeoutMs: options.warmCdnTimeout,
|
|
1045
|
+
onProgress(progress) {
|
|
1046
|
+
probeProgress.update(progress.completed, progress.total, `${progress.static} static, ${progress.dynamic} dynamic, ${progress.skipped} skipped`, "Probing route cacheability");
|
|
1047
|
+
}
|
|
1048
|
+
});
|
|
1049
|
+
} finally {
|
|
1050
|
+
probeProgress.finish();
|
|
1051
|
+
}
|
|
1052
|
+
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.`);
|
|
1053
|
+
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]}`);
|
|
1054
|
+
const finalPlan = {
|
|
1055
|
+
...plan,
|
|
1056
|
+
loadingShellPaths: probe.cacheableTargets.filter((target) => target.kind === "rsc-loading-shell").map((target) => target.sourcePathname),
|
|
1057
|
+
pagesDataPaths: probe.cacheableTargets.filter((target) => target.kind === "pages-data").map((target) => target.sourcePathname),
|
|
1058
|
+
paths: probe.cacheableTargets.filter((target) => target.kind === "html").map((target) => target.sourcePathname),
|
|
1059
|
+
routeHandlerPaths: probe.cacheableTargets.filter((target) => target.kind === "app-route").map((target) => target.sourcePathname),
|
|
1060
|
+
rscPaths: probe.cacheableTargets.filter((target) => target.kind === "rsc-full").map((target) => target.sourcePathname)
|
|
1061
|
+
};
|
|
1062
|
+
const selectedFinalPlan = options.selectWarmPlan?.(finalPlan) ?? finalPlan;
|
|
1063
|
+
assertDeploymentStateUnchanged(root, wranglerOptions, stagedProbeDeployment, "Two-stage CDN warming stopped because Worker deployment traffic or deployment identity changed while cacheability was being probed. No final version was promoted.");
|
|
1064
|
+
const finalConfig = writeCacheabilityManifestArtifact(root, options.config, probe.manifest, options.deploymentTool);
|
|
1065
|
+
const finalUpload = runVersionUpload(root, {
|
|
1066
|
+
...options,
|
|
1067
|
+
config: options.deploymentTool === "cf" ? options.config : finalConfig
|
|
1068
|
+
});
|
|
1069
|
+
prepared = {
|
|
1070
|
+
optionalWarmTargetKeys: new Set(probe.speculativeTargets.map(cdnWarmTargetKey)),
|
|
1071
|
+
prerenderSecret,
|
|
1072
|
+
plan: selectedFinalPlan,
|
|
1073
|
+
upload: finalUpload
|
|
1074
|
+
};
|
|
1075
|
+
} catch (error) {
|
|
1076
|
+
throw withStagedProbeVersionCleanupNote(error);
|
|
1077
|
+
}
|
|
1078
|
+
return deployUploadedVersionWithCdnWarmup(root, prepared.plan.paths, {
|
|
1079
|
+
...options,
|
|
1080
|
+
deploymentId: prepared.plan.deploymentId,
|
|
1081
|
+
expectedBuildId: prepared.plan.buildIdentity,
|
|
1082
|
+
expectedRscBuildId: prepared.plan.rscBuildId,
|
|
1083
|
+
expectedDeploymentState: stagedProbeDeployment,
|
|
1084
|
+
loadingShellPaths: prepared.plan.loadingShellPaths,
|
|
1085
|
+
optionalWarmTargetKeys: prepared.optionalWarmTargetKeys,
|
|
1086
|
+
pagesDataPaths: prepared.plan.pagesDataPaths,
|
|
1087
|
+
prerenderSecret: prepared.prerenderSecret,
|
|
1088
|
+
routeHandlerPaths: prepared.plan.routeHandlerPaths,
|
|
1089
|
+
routePatterns: prepared.plan.routePatterns,
|
|
1090
|
+
rscPaths: prepared.plan.rscPaths,
|
|
1091
|
+
uploadedVersion: prepared.upload
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
function resolveCdnWarmupTargetUrl(_root, deployedUrl, options) {
|
|
1095
|
+
return options?.warmCdnTarget ?? deployedUrl;
|
|
425
1096
|
}
|
|
426
1097
|
function getZeroPercentStagingTraffic(deployment, versionId) {
|
|
427
|
-
const
|
|
428
|
-
if (
|
|
429
|
-
if (
|
|
430
|
-
return [
|
|
1098
|
+
const serving = (deployment?.versions ?? []).filter((version) => version.percentage > 0);
|
|
1099
|
+
if (serving.length !== 1 || serving[0].percentage !== 100) return null;
|
|
1100
|
+
if (serving[0].versionId === versionId) return null;
|
|
1101
|
+
return [serving[0], {
|
|
431
1102
|
versionId,
|
|
432
1103
|
percentage: 0
|
|
433
1104
|
}];
|
|
@@ -435,6 +1106,15 @@ function getZeroPercentStagingTraffic(deployment, versionId) {
|
|
|
435
1106
|
function getWranglerTargetEnv(options) {
|
|
436
1107
|
return options.env || (options.preview ? "preview" : void 0);
|
|
437
1108
|
}
|
|
1109
|
+
function resolveDeploymentControlPlaneOptions(options, upload) {
|
|
1110
|
+
if (options.deploymentTool !== "cf") return options;
|
|
1111
|
+
const name = upload.workerName ?? options.name;
|
|
1112
|
+
if (!name) throw new Error("Could not detect the uploaded Worker name needed for staged CDN warming.");
|
|
1113
|
+
return {
|
|
1114
|
+
...options,
|
|
1115
|
+
name
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
438
1118
|
function resolveWorkerNameForVersionOverride(config, options) {
|
|
439
1119
|
if (options.name) return options.name;
|
|
440
1120
|
const env = getWranglerTargetEnv(options);
|
|
@@ -447,25 +1127,41 @@ function quoteStructuredHeaderString(value) {
|
|
|
447
1127
|
}
|
|
448
1128
|
function buildVersionOverrideHeaders(workerName, versionId) {
|
|
449
1129
|
if (!workerName) return void 0;
|
|
450
|
-
return {
|
|
1130
|
+
return {
|
|
1131
|
+
"Cloudflare-Workers-Version-Overrides": `${workerName}=${quoteStructuredHeaderString(versionId)}`,
|
|
1132
|
+
[VINEXT_EXPECTED_WORKER_VERSION_HEADER]: versionId
|
|
1133
|
+
};
|
|
451
1134
|
}
|
|
452
1135
|
function withStagedVersionCleanupNote(error) {
|
|
453
|
-
|
|
454
|
-
|
|
1136
|
+
return new StagedWarmupError(`${error instanceof Error ? error.message : String(error)} ${getStagedVersionCleanupNote()}`, { cause: error });
|
|
1137
|
+
}
|
|
1138
|
+
function withStagedProbeVersionCleanupNote(error) {
|
|
1139
|
+
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
1140
|
}
|
|
456
1141
|
function getStagedVersionCleanupNote() {
|
|
457
|
-
return "The uploaded version may remain staged at 0% with the previous version still serving 100% traffic;
|
|
1142
|
+
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 create a deployment with the desired version split.";
|
|
458
1143
|
}
|
|
459
1144
|
function withPromotedVersionTriggerNote(error) {
|
|
460
1145
|
const message = error instanceof Error ? error.message : String(error);
|
|
461
|
-
return new Error(`${message} The uploaded version may already be promoted to 100%, but Worker triggers/routes may not be updated; rerun deploy or
|
|
1146
|
+
return new Error(`${message} The uploaded version may already be promoted to 100%, but Worker triggers/routes may not be updated; rerun deploy or apply the Worker triggers after fixing the trigger error.`, { cause: error });
|
|
1147
|
+
}
|
|
1148
|
+
function withPromotedVersionWarmupNote(error) {
|
|
1149
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1150
|
+
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 create a deployment for the previous version.`, { cause: error });
|
|
462
1151
|
}
|
|
463
1152
|
async function deploy(options) {
|
|
464
|
-
|
|
1153
|
+
if (options.warmCdnTarget !== void 0 && !options.warmCdnCache) throw new Error("--warm-cdn-target requires --experimental-warm-cdn-cache.");
|
|
1154
|
+
const warmCdnTarget = options.warmCdnTarget === void 0 ? void 0 : validateCdnWarmTarget(options.warmCdnTarget);
|
|
1155
|
+
const deployEnv = options.env || (options.preview ? "preview" : void 0);
|
|
1156
|
+
if (deployEnv) validateWranglerEnvName(deployEnv);
|
|
465
1157
|
const root = path.resolve(options.root);
|
|
1158
|
+
const deploymentTool = resolveDeploymentTool(root);
|
|
1159
|
+
if (deploymentTool === "cf" && options.name) throw new Error("--name is not supported for cloudflare.config.ts projects. Set `name` in cloudflare.config.ts instead.");
|
|
1160
|
+
const viteMode = resolveViteBuildMode(deploymentTool, deployEnv);
|
|
1161
|
+
const wranglerFallbackEnv = deploymentTool === "cf" ? void 0 : deployEnv;
|
|
466
1162
|
loadDotenv({
|
|
467
1163
|
root,
|
|
468
|
-
mode:
|
|
1164
|
+
mode: viteMode
|
|
469
1165
|
});
|
|
470
1166
|
console.log("\n vinext-cloudflare deploy\n");
|
|
471
1167
|
const info = detectProject(root);
|
|
@@ -483,21 +1179,23 @@ async function deploy(options) {
|
|
|
483
1179
|
if (missingScaffolding.length > 0) throw new Error(`Missing Cloudflare deployment setup: ${missingScaffolding.join(", ")}. Run \`vinext init --platform=cloudflare\` first.`);
|
|
484
1180
|
const missingDeps = getMissingDeps(info);
|
|
485
1181
|
if (missingDeps.length > 0) throw new Error(`Missing deployment dependencies: ${missingDeps.map((dependency) => dependency.name).join(", ")}. Run \`vinext init --platform=cloudflare\` first.`);
|
|
1182
|
+
if (deploymentTool === "cf" && !isCfCliInstalled(root)) throw new Error("Missing deployment dependencies: cf. Run `vinext init --platform=cloudflare` first.");
|
|
486
1183
|
if (info.hasViteConfig && !viteConfigHasCloudflarePlugin(root)) throw new Error(formatMissingCloudflarePluginError({ isAppRouter: info.isAppRouter }));
|
|
487
|
-
if (info.hasISR && !viteConfigHasCacheAdapter(root) && !workerEntryHasCacheHandler(root)) throw new Error(formatMissingCacheAdapterError({}));
|
|
1184
|
+
if (info.hasISR && !viteConfigHasCacheAdapter(root) && !workerEntryHasCacheHandler(root)) throw new Error(formatMissingCacheAdapterError({ typedConfig: deploymentTool === "cf" }));
|
|
488
1185
|
if (!viteConfigHasImageAdapter(root)) {
|
|
489
1186
|
console.log();
|
|
490
|
-
console.log(formatImageOptimizationHint());
|
|
1187
|
+
console.log(formatImageOptimizationHint(deploymentTool === "cf"));
|
|
491
1188
|
}
|
|
492
1189
|
if (options.dryRun) {
|
|
493
1190
|
console.log("\n Dry run complete. No build or deploy performed.\n");
|
|
494
1191
|
return;
|
|
495
1192
|
}
|
|
496
|
-
const
|
|
497
|
-
const
|
|
498
|
-
const nextConfig = await withCloudflareEnv(
|
|
1193
|
+
const viteConfigMetadata = await withCloudflareEnv(deployEnv, () => loadDeployViteConfigMetadata(info.root, viteMode));
|
|
1194
|
+
const cdnAdapterConfig = resolveCdnAdapterConfig(viteConfigMetadata.cacheConfig);
|
|
1195
|
+
const nextConfig = await withCloudflareEnv(deployEnv, async () => {
|
|
499
1196
|
const inlineNextConfig = viteConfigMetadata.nextConfig;
|
|
500
|
-
|
|
1197
|
+
const rawNextConfig = inlineNextConfig ? await resolveNextConfigInput(inlineNextConfig, PHASE_PRODUCTION_BUILD) : await loadNextConfig(info.root, PHASE_PRODUCTION_BUILD);
|
|
1198
|
+
return resolveNextConfig(rawNextConfig, info.root);
|
|
501
1199
|
});
|
|
502
1200
|
const vinextPrerenderConfig = !options.prerenderAll && nextConfig.output !== "export" ? viteConfigMetadata.prerenderConfig : null;
|
|
503
1201
|
const prerenderDecision = resolveVinextPrerenderDecision({
|
|
@@ -505,70 +1203,186 @@ async function deploy(options) {
|
|
|
505
1203
|
vinextPrerenderConfig,
|
|
506
1204
|
nextOutput: nextConfig.output
|
|
507
1205
|
});
|
|
508
|
-
const
|
|
509
|
-
|
|
1206
|
+
const hasStrictResponseVary = hasVerbatimResponseVary(viteConfigMetadata.cacheConfig);
|
|
1207
|
+
const warmupStatusSource = cacheWarmupStatusSource(viteConfigMetadata.cacheConfig);
|
|
1208
|
+
const hasStagedRequestRouting = hasUncachedRequestRouting(viteConfigMetadata.cacheConfig) || warmupStatusSource === "data-cache";
|
|
1209
|
+
const hasBuildIdentityHeader = hasBuildIdentityResponseHeader(viteConfigMetadata.cacheConfig);
|
|
1210
|
+
const hasCanonicalRscWarmup = supportsCanonicalRscWarmup(viteConfigMetadata.cacheConfig);
|
|
1211
|
+
const needsCacheabilityProbeManifest = projectRequiresRouteCacheabilityProbeManifest(info, viteConfigMetadata.cacheConfig);
|
|
1212
|
+
const shouldEmitPrerenderPathManifest = !options.skipBuild && prerenderDecision;
|
|
1213
|
+
if (!options.skipBuild) await runBuild(info, deployEnv, viteMode);
|
|
510
1214
|
else console.log("\n Skipping build (--skip-build)");
|
|
1215
|
+
const canWarmTpr = options.experimentalTPR && !prerenderDecision && hasBuildIdentityHeader;
|
|
1216
|
+
if (options.experimentalTPR && prerenderDecision) console.log(" TPR: Skipping route selection (all-route prerendering configured)");
|
|
1217
|
+
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)");
|
|
1218
|
+
const tpr = canWarmTpr ? await resolveTPRRoutes({
|
|
1219
|
+
root,
|
|
1220
|
+
config: options.config,
|
|
1221
|
+
env: wranglerFallbackEnv,
|
|
1222
|
+
hostname: warmCdnTarget ? new URL(warmCdnTarget).hostname : void 0,
|
|
1223
|
+
typedConfig: deploymentTool === "cf",
|
|
1224
|
+
window: Math.max(1, options.tprWindow ?? 24)
|
|
1225
|
+
}) : null;
|
|
1226
|
+
if (tpr?.skipped) console.log(` TPR: Skipped (${tpr.skipped})`);
|
|
1227
|
+
const tprRoutes = tpr?.routes ?? [];
|
|
1228
|
+
const wranglerOptions = {
|
|
1229
|
+
env: deploymentTool === "cf" ? viteMode : deployEnv,
|
|
1230
|
+
name: deploymentTool === "cf" ? readBuildOutputWorkerName(root) : options.name,
|
|
1231
|
+
config: options.config,
|
|
1232
|
+
verbose: options.verbose,
|
|
1233
|
+
deploymentTool
|
|
1234
|
+
};
|
|
1235
|
+
let shouldWarmTpr = tprRoutes.length > 0;
|
|
1236
|
+
if (shouldWarmTpr && !options.warmCdnCache) {
|
|
1237
|
+
try {
|
|
1238
|
+
shouldWarmTpr = getZeroPercentStagingTraffic(runDeploymentStatus(root, wranglerOptions), "tpr-preflight") !== null;
|
|
1239
|
+
} catch {
|
|
1240
|
+
shouldWarmTpr = false;
|
|
1241
|
+
}
|
|
1242
|
+
if (!shouldWarmTpr) console.log(" TPR: Skipping pre-warm (current deployment cannot be staged safely)");
|
|
1243
|
+
}
|
|
1244
|
+
const shouldWarmCdnCache = options.warmCdnCache || shouldWarmTpr;
|
|
1245
|
+
const shouldSelectTpr = shouldWarmTpr && !options.warmCdnCache;
|
|
1246
|
+
const candidatePathsOnly = shouldSelectTpr && !needsCacheabilityProbeManifest;
|
|
1247
|
+
const shouldPrerenderLocally = prerenderDecision && (!shouldWarmCdnCache || prerenderDecision.reason === "next-export");
|
|
1248
|
+
if (shouldWarmCdnCache && cdnAdapterConfig) {
|
|
1249
|
+
if (deploymentTool === "cf") {
|
|
1250
|
+
const configPath = path.join(root, ".cloudflare/output/v0/workers/default/worker.config.json");
|
|
1251
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
1252
|
+
const binding = cdnAdapterConfig.versionMetadataBinding;
|
|
1253
|
+
if (config.env?.[binding]?.type !== "version-metadata") throw new Error(`[vinext] Cloudflare CDN warmup requires version metadata binding ${JSON.stringify(binding)} in the generated Build Output Worker config ${configPath}.`);
|
|
1254
|
+
} else {
|
|
1255
|
+
const wrangler = await loadProjectWranglerApi(info.root);
|
|
1256
|
+
const previousCwd = process.cwd();
|
|
1257
|
+
try {
|
|
1258
|
+
process.chdir(info.root);
|
|
1259
|
+
const config = wrangler.unstable_readConfig({
|
|
1260
|
+
config: options.config,
|
|
1261
|
+
env: wranglerFallbackEnv
|
|
1262
|
+
}, {
|
|
1263
|
+
hideWarnings: true,
|
|
1264
|
+
preserveOriginalMain: true,
|
|
1265
|
+
useRedirectIfAvailable: true
|
|
1266
|
+
});
|
|
1267
|
+
assertCdnVersionMetadataConfig({
|
|
1268
|
+
binding: cdnAdapterConfig.versionMetadataBinding,
|
|
1269
|
+
configuredBinding: config.version_metadata?.binding,
|
|
1270
|
+
configPath: config.configPath
|
|
1271
|
+
});
|
|
1272
|
+
} finally {
|
|
1273
|
+
process.chdir(previousCwd);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
511
1277
|
if (shouldEmitPrerenderPathManifest) await emitPrerenderPathManifest({
|
|
512
1278
|
root: info.root,
|
|
513
1279
|
nextConfig,
|
|
1280
|
+
buildIdentity: hasBuildIdentityHeader ? "response-header" : void 0,
|
|
1281
|
+
requestRouting: hasStagedRequestRouting ? "uncached-stage" : void 0,
|
|
1282
|
+
responseVary: hasStrictResponseVary ? "verbatim" : void 0,
|
|
1283
|
+
isResponsePolicyHeader: (name) => isConfiguredCdnResponsePolicyHeader(viteConfigMetadata.cacheConfig, name),
|
|
514
1284
|
routeRootConfig: viteConfigMetadata.routeRootConfig
|
|
515
1285
|
});
|
|
516
1286
|
let ranPrerender = false;
|
|
517
|
-
|
|
1287
|
+
let prerenderResult = void 0;
|
|
1288
|
+
if (shouldPrerenderLocally && !ranPrerender) {
|
|
518
1289
|
console.log(`\n ${formatVinextPrerenderLabel(prerenderDecision)}`);
|
|
519
1290
|
if (nextConfig.enablePrerenderSourceMaps) {
|
|
520
1291
|
process.setSourceMapsEnabled(true);
|
|
521
1292
|
Error.stackTraceLimit = Math.max(Error.stackTraceLimit, 50);
|
|
522
1293
|
}
|
|
523
|
-
await runPrerender({
|
|
1294
|
+
prerenderResult = await runPrerender({
|
|
524
1295
|
root: info.root,
|
|
525
|
-
concurrency: options.prerenderConcurrency,
|
|
526
|
-
nextConfig
|
|
1296
|
+
concurrency: options.prerenderConcurrency ?? viteConfigMetadata.prerenderConfig?.concurrency,
|
|
1297
|
+
nextConfig,
|
|
1298
|
+
routeRootConfig: viteConfigMetadata.routeRootConfig
|
|
527
1299
|
});
|
|
528
1300
|
ranPrerender = true;
|
|
529
1301
|
}
|
|
1302
|
+
if (!options.skipBuild) {
|
|
1303
|
+
await printBuildReport({
|
|
1304
|
+
root: info.root,
|
|
1305
|
+
pageExtensions: nextConfig.pageExtensions,
|
|
1306
|
+
prerenderResult: prerenderResult ?? void 0
|
|
1307
|
+
});
|
|
1308
|
+
console.log("\n Build complete.\n");
|
|
1309
|
+
}
|
|
530
1310
|
if (ranPrerender) try {
|
|
531
|
-
await populateKVCacheFromPrerenderedArtifacts(root,
|
|
1311
|
+
await populateKVCacheFromPrerenderedArtifacts(root, deploymentTool, deploymentTool === "cf" ? viteMode : wranglerFallbackEnv, viteConfigMetadata.cacheConfig);
|
|
532
1312
|
} catch (error) {
|
|
533
1313
|
console.log(` KV cache: Skipping prerender upload (${formatUnknownError(error)}). Continuing with deploy.`);
|
|
534
1314
|
}
|
|
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
1315
|
let url;
|
|
552
|
-
if (
|
|
553
|
-
|
|
554
|
-
includeFallbackShells: options.warmCdnIncludeFallbacks,
|
|
555
|
-
strict: options.warmCdnStrict
|
|
556
|
-
});
|
|
557
|
-
if (warmPaths.length > 0) url = await deployWithCdnWarmup(root, warmPaths, {
|
|
1316
|
+
if (shouldWarmCdnCache) try {
|
|
1317
|
+
url = await deployWithCdnWarmup(root, [], {
|
|
558
1318
|
...wranglerOptions,
|
|
1319
|
+
allowEmptyWarmPlan: tprRoutes.length > 0 && !options.warmCdnCache,
|
|
1320
|
+
cacheabilityProbe: needsCacheabilityProbeManifest,
|
|
1321
|
+
discoverWarmPlan: async ({ headers, targetUrl }) => {
|
|
1322
|
+
const discovery = await discoverPrerenderPathManifest({
|
|
1323
|
+
root: info.root,
|
|
1324
|
+
candidatePaths: tprRoutes.map(({ path }) => path),
|
|
1325
|
+
candidatePathsOnly,
|
|
1326
|
+
nextConfig,
|
|
1327
|
+
buildIdentity: hasBuildIdentityHeader ? "response-header" : void 0,
|
|
1328
|
+
includeCanonicalRsc: hasCanonicalRscWarmup,
|
|
1329
|
+
requestRouting: hasStagedRequestRouting ? "uncached-stage" : void 0,
|
|
1330
|
+
responseVary: hasStrictResponseVary ? "verbatim" : void 0,
|
|
1331
|
+
isResponsePolicyHeader: (name) => isConfiguredCdnResponsePolicyHeader(viteConfigMetadata.cacheConfig, name),
|
|
1332
|
+
routeRootConfig: viteConfigMetadata.routeRootConfig,
|
|
1333
|
+
pathDiscoveryTarget: {
|
|
1334
|
+
baseUrl: targetUrl,
|
|
1335
|
+
headers,
|
|
1336
|
+
phaseTimeoutMs: options.warmCdnDiscoveryTimeout ?? DEFAULT_REMOTE_PATH_DISCOVERY_PHASE_TIMEOUT_MS,
|
|
1337
|
+
retries: options.warmCdnDiscoveryRetries,
|
|
1338
|
+
retryDelayMs: DEFAULT_REMOTE_PATH_DISCOVERY_RETRY_DELAY_MS
|
|
1339
|
+
}
|
|
1340
|
+
});
|
|
1341
|
+
if (!discovery) return {
|
|
1342
|
+
loadingShellPaths: [],
|
|
1343
|
+
paths: [],
|
|
1344
|
+
rscPaths: []
|
|
1345
|
+
};
|
|
1346
|
+
return createPrerenderWarmPlan(root, discovery, {
|
|
1347
|
+
includeCanonicalRsc: hasCanonicalRscWarmup,
|
|
1348
|
+
includeFallbackShells: options.warmCdnIncludeFallbacks,
|
|
1349
|
+
strict: options.warmCdnCertify === true || !options.dangerouslyPromoteOnCdnWarmError
|
|
1350
|
+
});
|
|
1351
|
+
},
|
|
1352
|
+
selectWarmPlan: shouldSelectTpr ? (plan) => selectTPRWarmPlan(plan, tprRoutes, Math.max(1, Math.min(100, options.tprCoverage ?? 90)), Math.max(1, options.tprLimit ?? 1e3)) : void 0,
|
|
1353
|
+
statusSource: warmupStatusSource,
|
|
559
1354
|
warmCdnConcurrency: options.warmCdnConcurrency,
|
|
1355
|
+
warmCdnTarget,
|
|
560
1356
|
warmCdnTimeout: options.warmCdnTimeout,
|
|
561
1357
|
warmCdnRetries: options.warmCdnRetries,
|
|
562
|
-
|
|
1358
|
+
warmCdnDiscoveryTimeout: options.warmCdnDiscoveryTimeout,
|
|
1359
|
+
warmCdnDiscoveryRetries: options.warmCdnDiscoveryRetries,
|
|
1360
|
+
warmCdnProbeTimeout: options.warmCdnProbeTimeout,
|
|
1361
|
+
warmCdnProbeRetries: options.warmCdnProbeRetries,
|
|
1362
|
+
warmCdnCertify: options.warmCdnCertify,
|
|
1363
|
+
warmCdnReadinessTimeout: options.warmCdnReadinessTimeout,
|
|
1364
|
+
warmCdnReadinessRetries: options.warmCdnReadinessRetries,
|
|
1365
|
+
warmCdnReadinessProbes: options.warmCdnReadinessProbes,
|
|
1366
|
+
warmCdnReadinessProbeDelay: options.warmCdnReadinessProbeDelay,
|
|
1367
|
+
dangerouslyPromoteOnCdnWarmError: options.dangerouslyPromoteOnCdnWarmError,
|
|
1368
|
+
warmCdnPromote: options.warmCdnPromote,
|
|
1369
|
+
warmCdnPromotionDelay: options.warmCdnPromotionDelay
|
|
563
1370
|
});
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
1371
|
+
} catch (error) {
|
|
1372
|
+
if (options.warmCdnCache || options.warmCdnPromote === false && error instanceof StagedWarmupError) throw error;
|
|
1373
|
+
console.log(` TPR: Skipping pre-warm (${formatUnknownError(error)}). Continuing with deploy.`);
|
|
1374
|
+
}
|
|
1375
|
+
if (url === void 0) {
|
|
1376
|
+
if (deploymentTool === "cf" && options.warmCdnPromote === false) url = runCfVersionUpload(root, wranglerOptions).previewUrl ?? "(Preview URL not detected in cf output)";
|
|
1377
|
+
else if (deploymentTool === "cf") url = await runCfDeploy(root, wranglerOptions);
|
|
1378
|
+
else url = await runWranglerDeploy(root, {
|
|
1379
|
+
...wranglerOptions,
|
|
1380
|
+
promote: options.warmCdnPromote
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
569
1383
|
console.log("\n ─────────────────────────────────────────");
|
|
570
|
-
console.log(` Deployed to: ${url}`);
|
|
1384
|
+
console.log(` ${options.warmCdnPromote === false ? "Version URL" : "Deployed to"}: ${url}`);
|
|
571
1385
|
console.log(" ─────────────────────────────────────────\n");
|
|
572
1386
|
}
|
|
573
1387
|
//#endregion
|
|
574
|
-
export { buildNodeCliInvocation, buildVersionOverrideHeaders, buildWranglerDeployArgs, buildWranglerInvocation, buildWranglerKVBulkPutArgs, deploy, deployWithCdnWarmup, getZeroPercentStagingTraffic, parseDeployArgs, resolveCdnWarmupTargetUrl, resolveWorkerNameForVersionOverride, resolveWranglerBin, runWranglerDeploy,
|
|
1388
|
+
export { DEFAULT_CDN_WARM_PROMOTION_DELAY_MS, buildCfDeployArgs, buildNodeCliInvocation, buildVersionOverrideHeaders, buildWranglerDeployArgs, buildWranglerInvocation, buildWranglerKVBulkPutArgs, deploy, deployWithCdnWarmup, getZeroPercentStagingTraffic, hasCdnWarmRequests, isCfCliInstalled, parseDeployArgs, projectRequiresRouteCacheabilityProbeManifest, resolveCdnWarmupTargetUrl, resolveCfBin, resolveDeploymentControlPlaneOptions, resolveDeploymentTool, resolveViteBuildMode, resolveWorkerNameForVersionOverride, resolveWranglerBin, runCfDeploy, runKVBulkPut, runWranglerDeploy, selectTPRWarmPlan, validateWranglerEnvName, withCloudflareEnv };
|