@vinext/cloudflare 1.0.0-beta.7 → 1.0.0-beta.9
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 +64 -22
- package/dist/cache/cdn-adapter-config.d.ts +16 -8
- package/dist/cache/cdn-adapter-config.js +57 -17
- package/dist/cache/cdn-adapter.d.ts +25 -24
- package/dist/cache/cdn-adapter.js +24 -20
- package/dist/cache/cdn-adapter.runtime.d.ts +9 -3
- package/dist/cache/cdn-adapter.runtime.js +63 -40
- 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 +4 -3
- package/dist/cache/cdn-build-id.js +5 -1
- package/dist/cache/kv-data-adapter.d.ts +25 -5
- package/dist/cache/kv-data-adapter.js +5 -1
- package/dist/cache/kv-data-adapter.runtime.d.ts +38 -4
- package/dist/cache/kv-data-adapter.runtime.js +115 -41
- package/dist/cache/kv-key.d.ts +4 -5
- package/dist/cache/response-store-adapter.d.ts +70 -0
- package/dist/cache/response-store-adapter.js +45 -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 +264 -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 +289 -0
- package/dist/cacheability-artifact.d.ts +2 -3
- package/dist/cacheability-artifact.js +27 -1
- package/dist/cacheability-manifest-limits.d.ts +3 -4
- package/dist/cacheability-manifest-limits.js +1 -1
- package/dist/cacheability-probe.d.ts +8 -9
- package/dist/cacheability-probe.js +255 -91
- package/dist/cdn-warm.d.ts +31 -25
- package/dist/cdn-warm.js +228 -54
- package/dist/deploy-config.d.ts +11 -12
- package/dist/deploy-config.js +8 -3
- package/dist/deploy-help.d.ts +3 -4
- package/dist/deploy-help.js +20 -14
- package/dist/deploy.d.ts +35 -28
- package/dist/deploy.js +204 -95
- package/dist/images/images-optimizer.d.ts +3 -4
- package/dist/prerender-kv-populate.d.ts +3 -4
- package/dist/prerender-kv-populate.js +3 -2
- package/dist/tpr.d.ts +26 -74
- package/dist/tpr.js +45 -603
- package/dist/utils/cache-control-metadata.d.ts +3 -4
- package/dist/version-deploy.d.ts +17 -17
- package/dist/version-deploy.js +10 -5
- package/dist/worker-deployment-url.d.ts +3 -4
- 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 +2 -3
- package/package.json +13 -11
package/dist/cdn-warm.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { PrerenderRoutePattern } from "vinext/internal/build/prerender-paths";
|
|
1
|
+
import { PrerenderPathManifest, PrerenderRoutePattern } from "vinext/internal/build/prerender-paths";
|
|
2
2
|
import { PrerenderManifest, PrerenderedPathSelectionOptions } from "vinext/internal/server/prerender-manifest";
|
|
3
3
|
//#region src/cdn-warm.d.ts
|
|
4
|
-
type CdnWarmOptions = {
|
|
4
|
+
export type CdnWarmOptions = {
|
|
5
5
|
targetUrl: string;
|
|
6
6
|
paths: readonly string[];
|
|
7
7
|
/** Pages Router JSON data identities used by client navigation. */
|
|
@@ -27,23 +27,27 @@ type CdnWarmOptions = {
|
|
|
27
27
|
phaseTimeoutMs?: number;
|
|
28
28
|
/** Retry a newly staged version or preview alias until its routing has propagated. */
|
|
29
29
|
propagatingTarget?: boolean;
|
|
30
|
+
/** @internal Matching skipped targets that may be transient while a staged version propagates. */
|
|
31
|
+
retrySkippedTargetKeys?: ReadonlySet<string>;
|
|
30
32
|
/** Require the response to come from a reusable cache entry, not merely an eligible MISS. */
|
|
31
33
|
requireCacheHit?: boolean;
|
|
32
34
|
strict?: boolean;
|
|
35
|
+
/** Cache-admission signal exposed by the deployed adapter. */
|
|
36
|
+
statusSource?: "cloudflare" | "data-cache" | "vinext";
|
|
33
37
|
fetchImpl?: typeof fetch;
|
|
34
38
|
};
|
|
35
|
-
declare const DEFAULT_CDN_WARM_CONCURRENCY = 25;
|
|
36
|
-
declare const DEFAULT_CDN_WARM_TIMEOUT_MS = 10000;
|
|
37
|
-
declare const DEFAULT_STAGED_READINESS_RETRIES = 60;
|
|
38
|
-
declare const DEFAULT_STAGED_READINESS_INTERVAL_MS = 1000;
|
|
39
|
-
declare const DEFAULT_STAGED_READINESS_PHASE_TIMEOUT_MS = 120000;
|
|
40
|
-
type PrerenderCdnWarmOptions = Omit<CdnWarmOptions, "paths"> & {
|
|
39
|
+
export declare const DEFAULT_CDN_WARM_CONCURRENCY = 25;
|
|
40
|
+
export declare const DEFAULT_CDN_WARM_TIMEOUT_MS = 10000;
|
|
41
|
+
export declare const DEFAULT_STAGED_READINESS_RETRIES = 60;
|
|
42
|
+
export declare const DEFAULT_STAGED_READINESS_INTERVAL_MS = 1000;
|
|
43
|
+
export declare const DEFAULT_STAGED_READINESS_PHASE_TIMEOUT_MS = 120000;
|
|
44
|
+
export type PrerenderCdnWarmOptions = Omit<CdnWarmOptions, "paths"> & {
|
|
41
45
|
root: string;
|
|
42
46
|
includeFallbackShells?: boolean;
|
|
43
47
|
/** Use the manifest build identity unless the configured adapter cannot expose it. */
|
|
44
48
|
validateBuildIdentity?: boolean;
|
|
45
49
|
};
|
|
46
|
-
type CdnWarmResult = {
|
|
50
|
+
export type CdnWarmResult = {
|
|
47
51
|
total: number;
|
|
48
52
|
warmed: number;
|
|
49
53
|
skipped: number;
|
|
@@ -56,7 +60,7 @@ type CdnWarmResult = {
|
|
|
56
60
|
warmedPlan: CdnWarmRequestPlan;
|
|
57
61
|
retryPlan: CdnWarmRequestPlan;
|
|
58
62
|
};
|
|
59
|
-
type CdnWarmRequestPlan = {
|
|
63
|
+
export type CdnWarmRequestPlan = {
|
|
60
64
|
loadingShellPaths: string[];
|
|
61
65
|
pagesDataPaths: string[];
|
|
62
66
|
paths: string[];
|
|
@@ -64,13 +68,13 @@ type CdnWarmRequestPlan = {
|
|
|
64
68
|
routeHandlerPaths?: string[];
|
|
65
69
|
routePatterns?: Record<string, PrerenderRoutePattern>;
|
|
66
70
|
};
|
|
67
|
-
type CdnWarmReadinessResult = {
|
|
71
|
+
export type CdnWarmReadinessResult = {
|
|
68
72
|
ready: true;
|
|
69
73
|
} | {
|
|
70
74
|
error: string;
|
|
71
75
|
ready: false;
|
|
72
76
|
};
|
|
73
|
-
type PrerenderWarmPlan = {
|
|
77
|
+
export type PrerenderWarmPlan = {
|
|
74
78
|
appPaths?: string[];
|
|
75
79
|
buildId?: string;
|
|
76
80
|
buildIdentity?: string;
|
|
@@ -85,17 +89,20 @@ type PrerenderWarmPlan = {
|
|
|
85
89
|
rscBuildId?: string;
|
|
86
90
|
rscPaths: string[];
|
|
87
91
|
};
|
|
88
|
-
|
|
92
|
+
type PrerenderWarmPlanOptions = {
|
|
93
|
+
includeCanonicalRsc?: boolean;
|
|
89
94
|
includeFallbackShells?: boolean;
|
|
90
95
|
strict?: boolean;
|
|
91
|
-
}
|
|
92
|
-
declare function
|
|
96
|
+
};
|
|
97
|
+
export declare function createPrerenderWarmPlan(root: string, manifest: PrerenderPathManifest, options?: PrerenderWarmPlanOptions): PrerenderWarmPlan;
|
|
98
|
+
export declare function readPrerenderWarmPlan(root: string, options?: PrerenderWarmPlanOptions): PrerenderWarmPlan;
|
|
99
|
+
export declare function readPrerenderWarmPaths(root: string, options?: {
|
|
93
100
|
includeFallbackShells?: boolean;
|
|
94
101
|
strict?: boolean;
|
|
95
102
|
}): string[];
|
|
96
|
-
declare function getWarmPathsFromPrerenderManifest(manifest: PrerenderManifest, options?: PrerenderedPathSelectionOptions): string[];
|
|
97
|
-
declare function buildWarmupUrl(targetUrl: string, pathname: string): URL;
|
|
98
|
-
type CdnWarmTarget = {
|
|
103
|
+
export declare function getWarmPathsFromPrerenderManifest(manifest: PrerenderManifest, options?: PrerenderedPathSelectionOptions): string[];
|
|
104
|
+
export declare function buildWarmupUrl(targetUrl: string, pathname: string): URL;
|
|
105
|
+
export type CdnWarmTarget = {
|
|
99
106
|
headers?: HeadersInit;
|
|
100
107
|
kind: "app-route" | "html" | "pages-data" | "rsc-full" | "rsc-loading-shell";
|
|
101
108
|
label: string;
|
|
@@ -103,8 +110,8 @@ type CdnWarmTarget = {
|
|
|
103
110
|
sourcePathname: string;
|
|
104
111
|
route?: PrerenderRoutePattern;
|
|
105
112
|
};
|
|
106
|
-
declare function createCdnWarmTargets(options: Pick<CdnWarmOptions, "deploymentId" | "headers" | "loadingShellPaths" | "pagesDataPaths" | "paths" | "routeHandlerPaths" | "routePatterns" | "rscPaths">): Promise<CdnWarmTarget[]>;
|
|
107
|
-
declare class CdnOperationProgress {
|
|
113
|
+
export declare function createCdnWarmTargets(options: Pick<CdnWarmOptions, "deploymentId" | "headers" | "loadingShellPaths" | "pagesDataPaths" | "paths" | "routeHandlerPaths" | "routePatterns" | "rscPaths">): Promise<CdnWarmTarget[]>;
|
|
114
|
+
export declare class CdnOperationProgress {
|
|
108
115
|
private readonly isTTY;
|
|
109
116
|
private lastLineLength;
|
|
110
117
|
update(completed: number, total: number, label: string, phase?: string): void;
|
|
@@ -115,7 +122,7 @@ declare class CdnOperationProgress {
|
|
|
115
122
|
* before any real cache key is filled. Every probe has a unique query key, so
|
|
116
123
|
* an early response cannot make a later readiness attempt pass from cache.
|
|
117
124
|
*/
|
|
118
|
-
declare function waitForCdnWarmTargetReadiness(options: Pick<CdnWarmOptions, "deploymentId" | "expectedBuildId" | "expectedRscBuildId" | "fetchImpl" | "headers" | "retries" | "targetUrl" | "timeoutMs"> & {
|
|
125
|
+
export declare function waitForCdnWarmTargetReadiness(options: Pick<CdnWarmOptions, "deploymentId" | "expectedBuildId" | "expectedRscBuildId" | "fetchImpl" | "headers" | "retries" | "targetUrl" | "timeoutMs"> & {
|
|
119
126
|
plan: CdnWarmRequestPlan;
|
|
120
127
|
maxAttempts?: number;
|
|
121
128
|
phaseTimeoutMs?: number;
|
|
@@ -123,7 +130,6 @@ declare function waitForCdnWarmTargetReadiness(options: Pick<CdnWarmOptions, "de
|
|
|
123
130
|
probeIntervalMs?: number;
|
|
124
131
|
requiredConsecutiveSuccesses?: number;
|
|
125
132
|
}): Promise<CdnWarmReadinessResult>;
|
|
126
|
-
declare function warmCdnCache(options: CdnWarmOptions): Promise<CdnWarmResult>;
|
|
127
|
-
declare function warmCdnCacheFromPrerender(options: PrerenderCdnWarmOptions): Promise<CdnWarmResult>;
|
|
128
|
-
//#endregion
|
|
129
|
-
export { CdnOperationProgress, CdnWarmOptions, CdnWarmReadinessResult, CdnWarmRequestPlan, CdnWarmResult, CdnWarmTarget, DEFAULT_CDN_WARM_CONCURRENCY, DEFAULT_CDN_WARM_TIMEOUT_MS, DEFAULT_STAGED_READINESS_INTERVAL_MS, DEFAULT_STAGED_READINESS_PHASE_TIMEOUT_MS, DEFAULT_STAGED_READINESS_RETRIES, PrerenderCdnWarmOptions, PrerenderWarmPlan, buildWarmupUrl, createCdnWarmTargets, getWarmPathsFromPrerenderManifest, readPrerenderWarmPaths, readPrerenderWarmPlan, waitForCdnWarmTargetReadiness, warmCdnCache, warmCdnCacheFromPrerender };
|
|
133
|
+
export declare function warmCdnCache(options: CdnWarmOptions): Promise<CdnWarmResult>;
|
|
134
|
+
export declare function warmCdnCacheFromPrerender(options: PrerenderCdnWarmOptions): Promise<CdnWarmResult>;
|
|
135
|
+
//#endregion
|
package/dist/cdn-warm.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { VINEXT_CDN_BUILD_ID_HEADER } from "./cache/cdn-build-id.js";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { cacheabilityRoutePathname } from "vinext/internal/server/cacheability-manifest";
|
|
4
5
|
import { randomUUID } from "node:crypto";
|
|
5
6
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
6
|
-
import { VINEXT_PRERENDER_READINESS_HEADER, VINEXT_PRERENDER_READINESS_PATH, VINEXT_PRERENDER_SECRET_HEADER } from "vinext/internal/server/headers";
|
|
7
|
+
import { VINEXT_CACHE_HEADER, VINEXT_PRERENDER_READINESS_HEADER, VINEXT_PRERENDER_READINESS_PATH, VINEXT_PRERENDER_SECRET_HEADER } from "vinext/internal/server/headers";
|
|
7
8
|
import { PRERENDER_PATHS_MANIFEST } from "vinext/internal/build/prerender-paths";
|
|
8
9
|
import { getPrerenderedConcretePaths, readPrerenderManifest } from "vinext/internal/server/prerender-manifest";
|
|
9
|
-
import { VINEXT_RSC_BUILD_ID_HEADER, VINEXT_RSC_CONTENT_TYPE, VINEXT_RSC_VARY_HEADER, createCanonicalLoadingShellRscRequestHeaders, createCanonicalRscRequestHeaders, createCanonicalRscRequestUrl, createRscRequestUrl } from "vinext/internal/server/app-rsc-cache-busting";
|
|
10
|
+
import { VINEXT_RSC_BUILD_ID_HEADER, VINEXT_RSC_CONTENT_TYPE, VINEXT_RSC_VARY_HEADER as VINEXT_RSC_VARY_HEADER$1, createCanonicalLoadingShellRscRequestHeaders, createCanonicalRscRequestHeaders, createCanonicalRscRequestUrl, createRscRequestUrl } from "vinext/internal/server/app-rsc-cache-busting";
|
|
10
11
|
import { isNonCacheableCacheControl } from "vinext/shims/cdn-cache";
|
|
11
12
|
import { normalizePathTrailingSlash } from "vinext/shims/url-utils";
|
|
12
13
|
//#region src/cdn-warm.ts
|
|
@@ -18,7 +19,8 @@ const DEFAULT_STAGED_READINESS_PHASE_TIMEOUT_MS = 12e4;
|
|
|
18
19
|
const DEFAULT_STAGED_READINESS_SUCCESSES = 6;
|
|
19
20
|
const STAGED_READINESS_QUERY_PARAM = "__vinext_cdn_warm_readiness";
|
|
20
21
|
function applyWarmPathConfig(pathname, config) {
|
|
21
|
-
const
|
|
22
|
+
const withBasePath = config.basePath ? pathname === "/" ? config.basePath : `${config.basePath}${pathname}` : pathname;
|
|
23
|
+
const normalized = normalizePathTrailingSlash(withBasePath, config.trailingSlash === true);
|
|
22
24
|
return new URL(normalized, "http://vinext.local").pathname;
|
|
23
25
|
}
|
|
24
26
|
function readBuiltBuildId(root) {
|
|
@@ -36,16 +38,14 @@ function readPrerenderPathManifest(manifestPath) {
|
|
|
36
38
|
const parsed = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
37
39
|
if (!parsed || typeof parsed !== "object") return null;
|
|
38
40
|
const manifest = parsed;
|
|
39
|
-
if (!Array.isArray(manifest.paths) || !manifest.paths.every((pathname) => typeof pathname === "string") || manifest.appPaths !== void 0 && (!Array.isArray(manifest.appPaths) || !manifest.appPaths.every((pathname) => typeof pathname === "string")) || manifest.pagesPaths !== void 0 && (!Array.isArray(manifest.pagesPaths) || !manifest.pagesPaths.every((pathname) => typeof pathname === "string")) || manifest.pagesDataPaths !== void 0 && (!Array.isArray(manifest.pagesDataPaths) || !manifest.pagesDataPaths.every((pathname) => typeof pathname === "string")) || manifest.excludedWarmPaths !== void 0 && (!Array.isArray(manifest.excludedWarmPaths) || !manifest.excludedWarmPaths.every((pathname) => typeof pathname === "string")) || manifest.fallbackRoutePatterns !== void 0 && (!Array.isArray(manifest.fallbackRoutePatterns) || !manifest.fallbackRoutePatterns.every((route) => route !== null && typeof route === "object" && !Array.isArray(route) && (route.kind === "app-page" || route.kind === "app-route" || route.kind === "pages-page") && typeof route.pattern === "string" && route.pattern.startsWith("/"))) || manifest.rscPaths !== void 0 && (!Array.isArray(manifest.rscPaths) || !manifest.rscPaths.every((pathname) => typeof pathname === "string")) || manifest.routeHandlerPaths !== void 0 && (!Array.isArray(manifest.routeHandlerPaths) || !manifest.routeHandlerPaths.every((pathname) => typeof pathname === "string")) || manifest.routePatterns !== void 0 && (!manifest.routePatterns || typeof manifest.routePatterns !== "object" || Array.isArray(manifest.routePatterns) || !Object.entries(manifest.routePatterns).every(([pathname, route]) => pathname.startsWith("/") && route !== null && typeof route === "object" && !Array.isArray(route) && (route.kind === "app-page" || route.kind === "app-route" || route.kind === "pages-page") && typeof route.pattern === "string" && route.pattern.startsWith("/") && (route.cacheabilityProbe === void 0 || route.cacheabilityProbe !== null && typeof route.cacheabilityProbe === "object" && !Array.isArray(route.cacheabilityProbe) && typeof route.cacheabilityProbe.canPrunePattern === "boolean" && (route.cacheabilityProbe.concretePathname === void 0 || typeof route.cacheabilityProbe.concretePathname === "string" && route.cacheabilityProbe.concretePathname.startsWith("/"))))) || manifest.loadingShellPaths !== void 0 && (!Array.isArray(manifest.loadingShellPaths) || !manifest.loadingShellPaths.every((pathname) => typeof pathname === "string")) || manifest.basePath !== void 0 && typeof manifest.basePath !== "string" || manifest.buildIdentity !== void 0 && typeof manifest.buildIdentity !== "string" || manifest.deploymentId !== void 0 && typeof manifest.deploymentId !== "string" || manifest.rscBuildId !== void 0 && typeof manifest.rscBuildId !== "string" || manifest.trailingSlash !== void 0 && typeof manifest.trailingSlash !== "boolean" || manifest.responseVary !== void 0 && manifest.responseVary !== "verbatim") return null;
|
|
41
|
+
if (!Array.isArray(manifest.paths) || !manifest.paths.every((pathname) => typeof pathname === "string") || manifest.appPaths !== void 0 && (!Array.isArray(manifest.appPaths) || !manifest.appPaths.every((pathname) => typeof pathname === "string")) || manifest.pagesPaths !== void 0 && (!Array.isArray(manifest.pagesPaths) || !manifest.pagesPaths.every((pathname) => typeof pathname === "string")) || manifest.pagesDataPaths !== void 0 && (!Array.isArray(manifest.pagesDataPaths) || !manifest.pagesDataPaths.every((pathname) => typeof pathname === "string")) || manifest.excludedWarmPaths !== void 0 && (!Array.isArray(manifest.excludedWarmPaths) || !manifest.excludedWarmPaths.every((pathname) => typeof pathname === "string")) || manifest.fallbackRoutePatterns !== void 0 && (!Array.isArray(manifest.fallbackRoutePatterns) || !manifest.fallbackRoutePatterns.every((route) => route !== null && typeof route === "object" && !Array.isArray(route) && (route.kind === "app-page" || route.kind === "app-route" || route.kind === "pages-page") && typeof route.pattern === "string" && route.pattern.startsWith("/"))) || manifest.rscPaths !== void 0 && (!Array.isArray(manifest.rscPaths) || !manifest.rscPaths.every((pathname) => typeof pathname === "string")) || manifest.routeHandlerPaths !== void 0 && (!Array.isArray(manifest.routeHandlerPaths) || !manifest.routeHandlerPaths.every((pathname) => typeof pathname === "string")) || manifest.routePatterns !== void 0 && (!manifest.routePatterns || typeof manifest.routePatterns !== "object" || Array.isArray(manifest.routePatterns) || !Object.entries(manifest.routePatterns).every(([pathname, route]) => pathname.startsWith("/") && route !== null && typeof route === "object" && !Array.isArray(route) && (route.kind === "app-page" || route.kind === "app-route" || route.kind === "pages-page") && typeof route.pattern === "string" && route.pattern.startsWith("/") && (route.cacheabilityProbe === void 0 || route.cacheabilityProbe !== null && typeof route.cacheabilityProbe === "object" && !Array.isArray(route.cacheabilityProbe) && typeof route.cacheabilityProbe.canPrunePattern === "boolean" && (route.cacheabilityProbe.concretePathname === void 0 || typeof route.cacheabilityProbe.concretePathname === "string" && route.cacheabilityProbe.concretePathname.startsWith("/")) && (route.cacheabilityProbe.routeMayResolve === void 0 || typeof route.cacheabilityProbe.routeMayResolve === "boolean") && (route.cacheabilityProbe.requestStageMayTerminate === void 0 || typeof route.cacheabilityProbe.requestStageMayTerminate === "boolean")))) || manifest.loadingShellPaths !== void 0 && (!Array.isArray(manifest.loadingShellPaths) || !manifest.loadingShellPaths.every((pathname) => typeof pathname === "string")) || manifest.basePath !== void 0 && typeof manifest.basePath !== "string" || manifest.buildIdentity !== void 0 && typeof manifest.buildIdentity !== "string" || manifest.deploymentId !== void 0 && typeof manifest.deploymentId !== "string" || manifest.rscBuildId !== void 0 && typeof manifest.rscBuildId !== "string" || manifest.trailingSlash !== void 0 && typeof manifest.trailingSlash !== "boolean" || manifest.responseVary !== void 0 && manifest.responseVary !== "verbatim") return null;
|
|
40
42
|
return manifest;
|
|
41
43
|
} catch (error) {
|
|
42
44
|
console.warn(`[vinext] Failed to read prerender path manifest at ${manifestPath}:`, error);
|
|
43
45
|
return null;
|
|
44
46
|
}
|
|
45
47
|
}
|
|
46
|
-
function
|
|
47
|
-
const manifest = readPrerenderPathManifest(path.join(root, "dist", "server", PRERENDER_PATHS_MANIFEST));
|
|
48
|
-
if (!manifest) return null;
|
|
48
|
+
function createPrerenderPathWarmPaths(root, manifest, options) {
|
|
49
49
|
const builtBuildId = readBuiltBuildId(root);
|
|
50
50
|
if (!manifest.buildId || !builtBuildId || manifest.buildId !== builtBuildId) {
|
|
51
51
|
const message = "[vinext] CDN warmup skipped: prerender path manifest buildId does not match dist/server/BUILD_ID.";
|
|
@@ -59,10 +59,10 @@ function readPrerenderPathWarmPaths(root, options) {
|
|
|
59
59
|
paths: manifest.paths.filter((pathname) => pathname.startsWith("/")).filter((pathname) => !manifest.excludedWarmPaths?.includes(pathname)).map((pathname) => applyWarmPathConfig(pathname, manifest))
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
|
-
function
|
|
63
|
-
const pathPlan =
|
|
62
|
+
function createPrerenderWarmPlan(root, manifest, options) {
|
|
63
|
+
const pathPlan = createPrerenderPathWarmPaths(root, manifest, options);
|
|
64
64
|
if (!pathPlan) {
|
|
65
|
-
const message = "[vinext] CDN warmup skipped: prerender path
|
|
65
|
+
const message = "[vinext] CDN warmup skipped: prerender path discovery is invalid.";
|
|
66
66
|
if (options?.strict) throw new Error(message);
|
|
67
67
|
return {
|
|
68
68
|
loadingShellPaths: [],
|
|
@@ -70,8 +70,7 @@ function readPrerenderWarmPlan(root, options) {
|
|
|
70
70
|
rscPaths: []
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
|
-
const
|
|
74
|
-
const supportsCanonicalRsc = manifest.responseVary === "verbatim" && manifest.rscPaths !== void 0 && manifest.rscBuildId !== void 0;
|
|
73
|
+
const supportsCanonicalRsc = (manifest.responseVary === "verbatim" || options?.includeCanonicalRsc === true) && manifest.rscPaths !== void 0 && manifest.rscBuildId !== void 0;
|
|
75
74
|
const applyConfig = (pathname) => applyWarmPathConfig(pathname, manifest);
|
|
76
75
|
const routePatterns = manifest.routePatterns ? Object.fromEntries(Object.entries(manifest.routePatterns).map(([pathname, route]) => [pathname.includes("/_next/data/") ? pathname : applyConfig(pathname), route])) : void 0;
|
|
77
76
|
let htmlPaths = pathPlan.paths;
|
|
@@ -101,6 +100,19 @@ function readPrerenderWarmPlan(root, options) {
|
|
|
101
100
|
...routePatterns ? { routePatterns } : {}
|
|
102
101
|
};
|
|
103
102
|
}
|
|
103
|
+
function readPrerenderWarmPlan(root, options) {
|
|
104
|
+
const manifest = readPrerenderPathManifest(path.join(root, "dist", "server", PRERENDER_PATHS_MANIFEST));
|
|
105
|
+
if (!manifest) {
|
|
106
|
+
const message = "[vinext] CDN warmup skipped: prerender path manifest not found.";
|
|
107
|
+
if (options?.strict) throw new Error(message);
|
|
108
|
+
return {
|
|
109
|
+
loadingShellPaths: [],
|
|
110
|
+
paths: [],
|
|
111
|
+
rscPaths: []
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return createPrerenderWarmPlan(root, manifest, options);
|
|
115
|
+
}
|
|
104
116
|
function readPrerenderWarmPaths(root, options) {
|
|
105
117
|
return readPrerenderWarmPlan(root, options).paths;
|
|
106
118
|
}
|
|
@@ -169,6 +181,89 @@ async function fetchHeadersWithTimeout(fetchImpl, url, timeoutMs, headers, metho
|
|
|
169
181
|
if (timer !== void 0) clearTimeout(timer);
|
|
170
182
|
}
|
|
171
183
|
}
|
|
184
|
+
const MAX_CDN_WARM_REPORT_ROUTES = 10;
|
|
185
|
+
const MAX_CDN_WARM_REPORT_PATTERN_WIDTH = 48;
|
|
186
|
+
const CDN_WARM_ROUTE_KIND_LABELS = {
|
|
187
|
+
"app-page": "App page",
|
|
188
|
+
"app-route": "Route Handler",
|
|
189
|
+
"pages-page": "Pages page"
|
|
190
|
+
};
|
|
191
|
+
function formatCount(count, singular, plural = `${singular}s`) {
|
|
192
|
+
return `${count.toLocaleString("en-US")} ${count === 1 ? singular : plural}`;
|
|
193
|
+
}
|
|
194
|
+
function formatNumber(count) {
|
|
195
|
+
return count.toLocaleString("en-US");
|
|
196
|
+
}
|
|
197
|
+
function truncateMiddle(value, maxLength) {
|
|
198
|
+
if (value.length <= maxLength) return value;
|
|
199
|
+
const remaining = maxLength - 1;
|
|
200
|
+
const startLength = Math.ceil(remaining / 2);
|
|
201
|
+
return `${value.slice(0, startLength)}…${value.slice(-(remaining - startLength))}`;
|
|
202
|
+
}
|
|
203
|
+
function printCdnWarmTable(headings, rows, rightAlignedFrom) {
|
|
204
|
+
const widths = headings.map((heading, index) => Math.max(heading.length, ...rows.map((row) => row[index]?.length ?? 0)));
|
|
205
|
+
for (const row of [headings, ...rows]) console.log(` ${row.map((value, index) => index >= rightAlignedFrom ? value.padStart(widths[index]) : value.padEnd(widths[index])).join(" ")}`);
|
|
206
|
+
}
|
|
207
|
+
function createCdnWarmRouteReport(targets, outcomes) {
|
|
208
|
+
const routes = /* @__PURE__ */ new Map();
|
|
209
|
+
for (const [index, target] of targets.entries()) {
|
|
210
|
+
const key = target.route ? `${target.route.kind}\0${target.route.pattern}` : "unmatched";
|
|
211
|
+
const route = routes.get(key) ?? {
|
|
212
|
+
failed: 0,
|
|
213
|
+
kind: target.route ? CDN_WARM_ROUTE_KIND_LABELS[target.route.kind] : "Other",
|
|
214
|
+
key,
|
|
215
|
+
pattern: target.route?.pattern ?? "Other discovered requests",
|
|
216
|
+
paths: /* @__PURE__ */ new Set(),
|
|
217
|
+
skipped: 0,
|
|
218
|
+
total: 0,
|
|
219
|
+
warmed: 0
|
|
220
|
+
};
|
|
221
|
+
route.paths.add(target.route?.cacheabilityProbe?.concretePathname ?? cacheabilityRoutePathname(target.pathname, target.kind));
|
|
222
|
+
route.total++;
|
|
223
|
+
const outcome = outcomes?.[index];
|
|
224
|
+
if (outcome) route[outcome]++;
|
|
225
|
+
routes.set(key, route);
|
|
226
|
+
}
|
|
227
|
+
return [...routes.values()].sort((left, right) => right.total - left.total || left.key.localeCompare(right.key));
|
|
228
|
+
}
|
|
229
|
+
function printCdnWarmRouteReport(targets, outcomes) {
|
|
230
|
+
if (!targets.some((target) => target.route)) return;
|
|
231
|
+
const routes = createCdnWarmRouteReport(targets, outcomes);
|
|
232
|
+
if (routes.length === 0) return;
|
|
233
|
+
console.log(` CDN warmup ${outcomes ? "result" : "plan"} by route:`);
|
|
234
|
+
const visible = routes.slice(0, MAX_CDN_WARM_REPORT_ROUTES);
|
|
235
|
+
const routeColumns = (route) => [truncateMiddle(route.pattern, MAX_CDN_WARM_REPORT_PATTERN_WIDTH), route.kind];
|
|
236
|
+
if (outcomes) printCdnWarmTable([
|
|
237
|
+
"Route pattern",
|
|
238
|
+
"Kind",
|
|
239
|
+
"Warmed",
|
|
240
|
+
"Skipped",
|
|
241
|
+
"Failed"
|
|
242
|
+
], visible.map((route) => [
|
|
243
|
+
...routeColumns(route),
|
|
244
|
+
`${formatNumber(route.warmed)}/${formatNumber(route.total)}`,
|
|
245
|
+
formatNumber(route.skipped),
|
|
246
|
+
formatNumber(route.failed)
|
|
247
|
+
]), 2);
|
|
248
|
+
else printCdnWarmTable([
|
|
249
|
+
"Route pattern",
|
|
250
|
+
"Kind",
|
|
251
|
+
"Paths",
|
|
252
|
+
"Entries"
|
|
253
|
+
], visible.map((route) => [
|
|
254
|
+
...routeColumns(route),
|
|
255
|
+
formatNumber(route.paths.size),
|
|
256
|
+
formatNumber(route.total)
|
|
257
|
+
]), 2);
|
|
258
|
+
const omitted = routes.slice(MAX_CDN_WARM_REPORT_ROUTES);
|
|
259
|
+
if (omitted.length > 0) {
|
|
260
|
+
const omittedEntries = omitted.reduce((total, route) => total + route.total, 0);
|
|
261
|
+
console.log(` ... ${formatCount(omitted.length, "additional route pattern")} omitted (${formatCount(omittedEntries, "cache entry", "cache entries")})`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function cdnWarmTargetKey(target) {
|
|
265
|
+
return `${target.kind}\0${target.sourcePathname}`;
|
|
266
|
+
}
|
|
172
267
|
async function createCdnWarmTargets(options) {
|
|
173
268
|
const requests = [];
|
|
174
269
|
const fullRscPaths = new Set(options.rscPaths ?? []);
|
|
@@ -257,7 +352,7 @@ var CdnOperationProgress = class {
|
|
|
257
352
|
this.lastLineLength = 0;
|
|
258
353
|
}
|
|
259
354
|
};
|
|
260
|
-
const REQUIRED_RSC_VARY_HEADERS = VINEXT_RSC_VARY_HEADER.split(",").map((name) => name.trim().toLowerCase());
|
|
355
|
+
const REQUIRED_RSC_VARY_HEADERS = VINEXT_RSC_VARY_HEADER$1.split(",").map((name) => name.trim().toLowerCase());
|
|
261
356
|
const ADMITTED_CF_CACHE_STATUSES = /* @__PURE__ */ new Set([
|
|
262
357
|
"HIT",
|
|
263
358
|
"MISS",
|
|
@@ -270,6 +365,7 @@ const REUSABLE_CF_CACHE_STATUSES = /* @__PURE__ */ new Set([
|
|
|
270
365
|
"REVALIDATED",
|
|
271
366
|
"UPDATING"
|
|
272
367
|
]);
|
|
368
|
+
const TRANSITIONAL_VINEXT_CACHE_STATUSES = /* @__PURE__ */ new Set(["MISS", "UPDATING"]);
|
|
273
369
|
const NON_CACHEABLE_CF_CACHE_STATUSES = /* @__PURE__ */ new Set(["BYPASS"]);
|
|
274
370
|
const CDN_CACHE_POLICY_HEADERS = [
|
|
275
371
|
"Cloudflare-CDN-Cache-Control",
|
|
@@ -350,17 +446,44 @@ function validateCachePolicy(response, requireCacheStatus, requireCacheHit = fal
|
|
|
350
446
|
error: `CF-Cache-Status is ${cacheStatus}`
|
|
351
447
|
};
|
|
352
448
|
}
|
|
353
|
-
function
|
|
354
|
-
|
|
449
|
+
function validateVinextCacheStatus(response, missingStatus = "failed", requireCacheHit = false) {
|
|
450
|
+
const status = response.headers.get(VINEXT_CACHE_HEADER)?.trim().toUpperCase();
|
|
451
|
+
if (requireCacheHit && status !== "HIT") return {
|
|
355
452
|
outcome: "failed",
|
|
356
|
-
error:
|
|
453
|
+
error: `${VINEXT_CACHE_HEADER} is ${status ?? "missing"}; the cache fill is not reusable`
|
|
357
454
|
};
|
|
455
|
+
if (status === "MISS" || status === "HIT" || status === "UPDATING") return { outcome: "warmed" };
|
|
456
|
+
if (status === "BYPASS" || status === "DYNAMIC") return {
|
|
457
|
+
outcome: "skipped",
|
|
458
|
+
reason: `${VINEXT_CACHE_HEADER} is ${status}`
|
|
459
|
+
};
|
|
460
|
+
return missingStatus === "skipped" ? {
|
|
461
|
+
outcome: "skipped",
|
|
462
|
+
reason: `response is missing ${VINEXT_CACHE_HEADER}`
|
|
463
|
+
} : {
|
|
464
|
+
outcome: "failed",
|
|
465
|
+
error: `response is missing ${VINEXT_CACHE_HEADER}`
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
function validateWarmAdmission(response, statusSource, requireCacheHit) {
|
|
469
|
+
if (statusSource === "vinext") return validateVinextCacheStatus(response, "failed", requireCacheHit);
|
|
470
|
+
if (statusSource === "data-cache") return validateVinextCacheStatus(response, "skipped", requireCacheHit);
|
|
471
|
+
return validateCachePolicy(response, true, requireCacheHit);
|
|
472
|
+
}
|
|
473
|
+
function validateBuildIdentity(response, expectedBuildId) {
|
|
474
|
+
if (expectedBuildId !== void 0 && response.headers.get("X-Vinext-Build-Id") !== expectedBuildId) {
|
|
475
|
+
const actualBuildId = response.headers.get("X-Vinext-Build-Id") ?? "missing";
|
|
476
|
+
return {
|
|
477
|
+
outcome: "failed",
|
|
478
|
+
error: `response ${VINEXT_CDN_BUILD_ID_HEADER} does not match build ${expectedBuildId} (received ${actualBuildId})`
|
|
479
|
+
};
|
|
480
|
+
}
|
|
358
481
|
return null;
|
|
359
482
|
}
|
|
360
483
|
function isExpectedTerminalStatus(status) {
|
|
361
484
|
return status >= 300 && status < 400 || status === 404;
|
|
362
485
|
}
|
|
363
|
-
function validateRscWarmResponse(response, expectedBuildId, expectedRscBuildId, requireCacheHit = false) {
|
|
486
|
+
function validateRscWarmResponse(response, expectedBuildId, expectedRscBuildId, requireCacheHit = false, statusSource = "cloudflare") {
|
|
364
487
|
const buildIdentityValidation = validateBuildIdentity(response, expectedBuildId);
|
|
365
488
|
if (buildIdentityValidation) return buildIdentityValidation;
|
|
366
489
|
if (expectedRscBuildId !== void 0 && response.headers.get(VINEXT_RSC_BUILD_ID_HEADER) !== expectedRscBuildId) return {
|
|
@@ -381,7 +504,7 @@ function validateRscWarmResponse(response, expectedBuildId, expectedRscBuildId,
|
|
|
381
504
|
outcome: "failed",
|
|
382
505
|
error: `expected ${VINEXT_RSC_CONTENT_TYPE} response`
|
|
383
506
|
};
|
|
384
|
-
const cachePolicyValidation =
|
|
507
|
+
const cachePolicyValidation = validateWarmAdmission(response, statusSource, requireCacheHit);
|
|
385
508
|
if (cachePolicyValidation.outcome !== "warmed") return cachePolicyValidation;
|
|
386
509
|
if (terminalResponse && !response.headers.get("Content-Type")?.toLowerCase().startsWith(VINEXT_RSC_CONTENT_TYPE)) return {
|
|
387
510
|
outcome: "failed",
|
|
@@ -395,7 +518,7 @@ function validateRscWarmResponse(response, expectedBuildId, expectedRscBuildId,
|
|
|
395
518
|
};
|
|
396
519
|
return { outcome: "warmed" };
|
|
397
520
|
}
|
|
398
|
-
function validateHtmlWarmResponse(response, expectedBuildId, requireCacheHit = false) {
|
|
521
|
+
function validateHtmlWarmResponse(response, expectedBuildId, requireCacheHit = false, statusSource = "cloudflare") {
|
|
399
522
|
const buildIdentityValidation = validateBuildIdentity(response, expectedBuildId);
|
|
400
523
|
if (buildIdentityValidation) return buildIdentityValidation;
|
|
401
524
|
if (response.redirected) return {
|
|
@@ -408,12 +531,12 @@ function validateHtmlWarmResponse(response, expectedBuildId, requireCacheHit = f
|
|
|
408
531
|
error: `HTTP ${response.status}`
|
|
409
532
|
};
|
|
410
533
|
}
|
|
411
|
-
const cachePolicyValidation =
|
|
534
|
+
const cachePolicyValidation = validateWarmAdmission(response, statusSource, requireCacheHit);
|
|
412
535
|
if (cachePolicyValidation.outcome !== "warmed") return cachePolicyValidation;
|
|
413
536
|
return { outcome: "warmed" };
|
|
414
537
|
}
|
|
415
|
-
function validatePagesDataWarmResponse(response, expectedBuildId, requireCacheHit = false) {
|
|
416
|
-
const validation = validateHtmlWarmResponse(response, expectedBuildId, requireCacheHit);
|
|
538
|
+
function validatePagesDataWarmResponse(response, expectedBuildId, requireCacheHit = false, statusSource = "cloudflare") {
|
|
539
|
+
const validation = validateHtmlWarmResponse(response, expectedBuildId, requireCacheHit, statusSource);
|
|
417
540
|
if (validation.outcome !== "warmed") return validation;
|
|
418
541
|
if (!response.headers.get("Content-Type")?.toLowerCase().startsWith("application/json")) return {
|
|
419
542
|
outcome: "failed",
|
|
@@ -532,10 +655,19 @@ function shouldRetryValidationFailure(response, target, options) {
|
|
|
532
655
|
if (expectedIdentities.some((matches) => !matches)) return true;
|
|
533
656
|
return isRetryableStatus(response.status, options.retryNotFound);
|
|
534
657
|
}
|
|
658
|
+
function isRetryableCertificationFailure(response, statusSource, retryPropagationFailures) {
|
|
659
|
+
if (statusSource === "cloudflare") {
|
|
660
|
+
const status = response.headers.get("CF-Cache-Status")?.trim().toUpperCase();
|
|
661
|
+
return !REUSABLE_CF_CACHE_STATUSES.has(status ?? "") && (retryPropagationFailures || ADMITTED_CF_CACHE_STATUSES.has(status ?? ""));
|
|
662
|
+
}
|
|
663
|
+
const status = response.headers.get(VINEXT_CACHE_HEADER)?.trim().toUpperCase();
|
|
664
|
+
return TRANSITIONAL_VINEXT_CACHE_STATUSES.has(status ?? "");
|
|
665
|
+
}
|
|
535
666
|
async function warmOnePath(target, options) {
|
|
536
667
|
const url = buildWarmupUrl(options.targetUrl, target.pathname);
|
|
537
668
|
let lastError = "request failed before the first attempt";
|
|
538
669
|
let lastRetryable = true;
|
|
670
|
+
let lastSkippedReason = null;
|
|
539
671
|
const phaseDeadlineError = () => `CDN warmup exceeded its ${options.phaseTimeoutMs}ms phase deadline`;
|
|
540
672
|
const remainingPhaseMs = () => options.deadlineAt === void 0 ? options.timeoutMs : options.deadlineAt - Date.now();
|
|
541
673
|
const canRetry = (attempt) => attempt < options.retries;
|
|
@@ -555,6 +687,7 @@ async function warmOnePath(target, options) {
|
|
|
555
687
|
retryable: false
|
|
556
688
|
};
|
|
557
689
|
const attemptTimeoutMs = Math.min(options.timeoutMs, remainingMs);
|
|
690
|
+
const phaseLimitedAttempt = options.deadlineAt !== void 0 && remainingMs <= options.timeoutMs;
|
|
558
691
|
try {
|
|
559
692
|
const response = options.requireCacheHit ? await fetchHeadersWithTimeout(options.fetchImpl, url, attemptTimeoutMs, target.headers ?? options.headers) : (await fetchWithTimeout(options.fetchImpl, url, attemptTimeoutMs, target.headers ?? options.headers, "manual")).response;
|
|
560
693
|
if (options.deadlineAt !== void 0 && Date.now() >= options.deadlineAt) return {
|
|
@@ -566,21 +699,33 @@ async function warmOnePath(target, options) {
|
|
|
566
699
|
if (options.requireCacheHit) response.body?.cancel().catch(() => {});
|
|
567
700
|
if (process.env.VINEXT_CDN_WARM_DEBUG === "1") console.log(` CDN warm debug ${target.label} attempt ${attempt + 1}: ${url.pathname}${url.search} HTTP ${response.status} cache=${response.headers.get("CF-Cache-Status") ?? "missing"} ray=${response.headers.get("CF-Ray") ?? "missing"} encoding=${response.headers.get("Content-Encoding") ?? "identity"} rscBuild=${response.headers.get(VINEXT_RSC_BUILD_ID_HEADER) ?? "missing"}`);
|
|
568
701
|
if (target.kind.startsWith("rsc-")) {
|
|
569
|
-
const validation = validateRscWarmResponse(response, options.expectedBuildId, options.expectedRscBuildId, options.requireCacheHit);
|
|
702
|
+
const validation = validateRscWarmResponse(response, options.expectedBuildId, options.expectedRscBuildId, options.requireCacheHit, options.statusSource);
|
|
570
703
|
if (validation.outcome === "warmed") return {
|
|
571
704
|
path: target.label,
|
|
572
705
|
ok: true,
|
|
573
706
|
skipped: false
|
|
574
707
|
};
|
|
575
|
-
if (validation.outcome === "skipped")
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
708
|
+
if (validation.outcome === "skipped") {
|
|
709
|
+
if (!options.retrySkipped) return {
|
|
710
|
+
path: target.label,
|
|
711
|
+
ok: true,
|
|
712
|
+
skipped: true,
|
|
713
|
+
reason: validation.reason
|
|
714
|
+
};
|
|
715
|
+
lastSkippedReason = validation.reason;
|
|
716
|
+
lastRetryable = true;
|
|
717
|
+
if (!canRetry(attempt)) break;
|
|
718
|
+
if (!await waitBeforeRetry()) return {
|
|
719
|
+
path: target.label,
|
|
720
|
+
ok: false,
|
|
721
|
+
error: phaseDeadlineError(),
|
|
722
|
+
retryable: false
|
|
723
|
+
};
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
lastSkippedReason = null;
|
|
581
727
|
lastError = validation.error;
|
|
582
|
-
|
|
583
|
-
lastRetryable = options.requireCacheHit && ADMITTED_CF_CACHE_STATUSES.has(cacheStatus ?? "") && !REUSABLE_CF_CACHE_STATUSES.has(cacheStatus ?? "") || shouldRetryValidationFailure(response, target, options);
|
|
728
|
+
lastRetryable = options.requireCacheHit && isRetryableCertificationFailure(response, options.statusSource, options.retryPropagationFailures) || shouldRetryValidationFailure(response, target, options);
|
|
584
729
|
if (!lastRetryable) break;
|
|
585
730
|
if (!canRetry(attempt)) break;
|
|
586
731
|
if (!await waitBeforeRetry()) return {
|
|
@@ -591,26 +736,46 @@ async function warmOnePath(target, options) {
|
|
|
591
736
|
};
|
|
592
737
|
continue;
|
|
593
738
|
}
|
|
594
|
-
const validation = target.kind === "pages-data" ? validatePagesDataWarmResponse(response, options.expectedBuildId, options.requireCacheHit) : validateHtmlWarmResponse(response, options.expectedBuildId, options.requireCacheHit);
|
|
739
|
+
const validation = target.kind === "pages-data" ? validatePagesDataWarmResponse(response, options.expectedBuildId, options.requireCacheHit, options.statusSource) : validateHtmlWarmResponse(response, options.expectedBuildId, options.requireCacheHit, options.statusSource);
|
|
595
740
|
if (validation.outcome === "warmed") return {
|
|
596
741
|
path: target.label,
|
|
597
742
|
ok: true,
|
|
598
743
|
skipped: false
|
|
599
744
|
};
|
|
600
|
-
if (validation.outcome === "skipped")
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
745
|
+
if (validation.outcome === "skipped") {
|
|
746
|
+
if (!options.retrySkipped) return {
|
|
747
|
+
path: target.label,
|
|
748
|
+
ok: true,
|
|
749
|
+
skipped: true,
|
|
750
|
+
reason: validation.reason
|
|
751
|
+
};
|
|
752
|
+
lastSkippedReason = validation.reason;
|
|
753
|
+
lastRetryable = true;
|
|
754
|
+
if (!canRetry(attempt)) break;
|
|
755
|
+
if (!await waitBeforeRetry()) return {
|
|
756
|
+
path: target.label,
|
|
757
|
+
ok: false,
|
|
758
|
+
error: phaseDeadlineError(),
|
|
759
|
+
retryable: false
|
|
760
|
+
};
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
lastSkippedReason = null;
|
|
606
764
|
lastError = validation.error;
|
|
607
|
-
|
|
608
|
-
lastRetryable = options.requireCacheHit && ADMITTED_CF_CACHE_STATUSES.has(cacheStatus ?? "") && !REUSABLE_CF_CACHE_STATUSES.has(cacheStatus ?? "") || shouldRetryValidationFailure(response, target, options);
|
|
765
|
+
lastRetryable = options.requireCacheHit && isRetryableCertificationFailure(response, options.statusSource, options.retryPropagationFailures) || shouldRetryValidationFailure(response, target, options);
|
|
609
766
|
if (!lastRetryable) break;
|
|
610
767
|
} catch (error) {
|
|
768
|
+
lastSkippedReason = null;
|
|
611
769
|
lastRetryable = true;
|
|
612
|
-
if (error instanceof DOMException && error.name === "AbortError")
|
|
613
|
-
|
|
770
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
771
|
+
if (phaseLimitedAttempt) return {
|
|
772
|
+
path: target.label,
|
|
773
|
+
ok: false,
|
|
774
|
+
error: phaseDeadlineError(),
|
|
775
|
+
retryable: false
|
|
776
|
+
};
|
|
777
|
+
lastError = `timed out after ${attemptTimeoutMs}ms`;
|
|
778
|
+
} else lastError = error instanceof Error ? error.message : String(error);
|
|
614
779
|
}
|
|
615
780
|
if (!canRetry(attempt)) break;
|
|
616
781
|
if (!await waitBeforeRetry()) return {
|
|
@@ -620,11 +785,16 @@ async function warmOnePath(target, options) {
|
|
|
620
785
|
retryable: false
|
|
621
786
|
};
|
|
622
787
|
}
|
|
623
|
-
return {
|
|
788
|
+
return lastSkippedReason === null ? {
|
|
624
789
|
path: target.label,
|
|
625
790
|
ok: false,
|
|
626
791
|
error: lastError,
|
|
627
792
|
retryable: lastRetryable
|
|
793
|
+
} : {
|
|
794
|
+
path: target.label,
|
|
795
|
+
ok: true,
|
|
796
|
+
skipped: true,
|
|
797
|
+
reason: lastSkippedReason
|
|
628
798
|
};
|
|
629
799
|
}
|
|
630
800
|
async function runWithConcurrency(items, concurrency, fn) {
|
|
@@ -677,7 +847,8 @@ async function warmCdnCache(options) {
|
|
|
677
847
|
rscPaths: []
|
|
678
848
|
}
|
|
679
849
|
};
|
|
680
|
-
console.log(`\n Warming
|
|
850
|
+
console.log(`\n Warming ${formatCount(requests.length, "CDN cache entry", "CDN cache entries")}...`);
|
|
851
|
+
printCdnWarmRouteReport(requests);
|
|
681
852
|
const progress = new CdnOperationProgress();
|
|
682
853
|
let completedRequests = 0;
|
|
683
854
|
progress.update(0, requests.length, "starting warmup");
|
|
@@ -697,7 +868,9 @@ async function warmCdnCache(options) {
|
|
|
697
868
|
retryDelayMs: isPropagationRequest ? propagationRetryDelayMs : normalRetryDelayMs,
|
|
698
869
|
retryNotFound: isPropagationRequest,
|
|
699
870
|
phaseTimeoutMs,
|
|
700
|
-
requireCacheHit: options.requireCacheHit === true
|
|
871
|
+
requireCacheHit: options.requireCacheHit === true,
|
|
872
|
+
statusSource: options.statusSource ?? "cloudflare",
|
|
873
|
+
retrySkipped: isPropagationRequest && options.retrySkippedTargetKeys?.has(cdnWarmTargetKey(target)) === true
|
|
701
874
|
});
|
|
702
875
|
};
|
|
703
876
|
const warmRequest = async (target, retryMode = "normal") => {
|
|
@@ -708,24 +881,24 @@ async function warmCdnCache(options) {
|
|
|
708
881
|
};
|
|
709
882
|
const warmPropagatingPass = async (targets) => {
|
|
710
883
|
const results = await runWithConcurrency(targets, concurrency, (target) => warmRequest(target, "propagation-pass"));
|
|
711
|
-
const
|
|
884
|
+
const retryable = targets.map((target, index) => ({
|
|
712
885
|
index,
|
|
713
886
|
result: results[index],
|
|
714
887
|
target
|
|
715
|
-
})).filter((
|
|
716
|
-
if (
|
|
888
|
+
})).filter(({ result, target }) => result.ok ? result.skipped && options.retrySkippedTargetKeys?.has(cdnWarmTargetKey(target)) === true : result.retryable);
|
|
889
|
+
if (retryable.length === 0 || propagationRetries === 0) return results;
|
|
717
890
|
progress.finish();
|
|
718
|
-
console.log(` CDN warmup: retrying ${
|
|
891
|
+
console.log(` CDN warmup: retrying ${retryable.length} unresolved request(s) after completing the initial pass...`);
|
|
719
892
|
let completedRetries = 0;
|
|
720
|
-
progress.update(0,
|
|
721
|
-
const retried = await runWithConcurrency(
|
|
893
|
+
progress.update(0, retryable.length, "starting retry pass", "Retrying CDN cache");
|
|
894
|
+
const retried = await runWithConcurrency(retryable.map(({ target }) => target), concurrency, async (target) => {
|
|
722
895
|
const result = await warmTarget(target, "propagation-retry");
|
|
723
896
|
completedRetries++;
|
|
724
|
-
progress.update(completedRetries,
|
|
897
|
+
progress.update(completedRetries, retryable.length, target.label, "Retrying CDN cache");
|
|
725
898
|
return result;
|
|
726
899
|
});
|
|
727
900
|
progress.finish();
|
|
728
|
-
for (const [retryIndex, { index }] of
|
|
901
|
+
for (const [retryIndex, { index }] of retryable.entries()) results[index] = retried[retryIndex];
|
|
729
902
|
return results;
|
|
730
903
|
};
|
|
731
904
|
let results;
|
|
@@ -752,6 +925,7 @@ async function warmCdnCache(options) {
|
|
|
752
925
|
const warmed = results.length - failures.length - skippedResults.length;
|
|
753
926
|
const warmedRouteHandlerPaths = warmedRequests.filter((target) => target.kind === "app-route").map((target) => target.sourcePathname);
|
|
754
927
|
const failedRouteHandlerPaths = failedRequests.filter(({ target }) => target.kind === "app-route").map(({ target }) => target.sourcePathname);
|
|
928
|
+
printCdnWarmRouteReport(requests, results.map((result) => !result.ok ? "failed" : result.skipped ? "skipped" : "warmed"));
|
|
755
929
|
console.log(` CDN warmup: ${warmed} warmed, ${skippedResults.length} skipped, ${failures.length} failed.`);
|
|
756
930
|
for (const skipped of skippedResults.slice(0, 5)) console.log(` CDN warmup skipped ${skipped.path}: ${skipped.reason}`);
|
|
757
931
|
if (failures.length > 0) {
|
|
@@ -804,4 +978,4 @@ async function warmCdnCacheFromPrerender(options) {
|
|
|
804
978
|
});
|
|
805
979
|
}
|
|
806
980
|
//#endregion
|
|
807
|
-
export { CdnOperationProgress, DEFAULT_CDN_WARM_CONCURRENCY, DEFAULT_CDN_WARM_TIMEOUT_MS, DEFAULT_STAGED_READINESS_INTERVAL_MS, DEFAULT_STAGED_READINESS_PHASE_TIMEOUT_MS, DEFAULT_STAGED_READINESS_RETRIES, buildWarmupUrl, createCdnWarmTargets, getWarmPathsFromPrerenderManifest, readPrerenderWarmPaths, readPrerenderWarmPlan, waitForCdnWarmTargetReadiness, warmCdnCache, warmCdnCacheFromPrerender };
|
|
981
|
+
export { CdnOperationProgress, DEFAULT_CDN_WARM_CONCURRENCY, DEFAULT_CDN_WARM_TIMEOUT_MS, DEFAULT_STAGED_READINESS_INTERVAL_MS, DEFAULT_STAGED_READINESS_PHASE_TIMEOUT_MS, DEFAULT_STAGED_READINESS_RETRIES, buildWarmupUrl, createCdnWarmTargets, createPrerenderWarmPlan, getWarmPathsFromPrerenderManifest, readPrerenderWarmPaths, readPrerenderWarmPlan, waitForCdnWarmTargetReadiness, warmCdnCache, warmCdnCacheFromPrerender };
|