@vinext/cloudflare 1.0.0-beta.7 → 1.0.0-beta.8
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 +55 -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 +30 -0
- package/dist/cache/cdn-adapter.worker.js +371 -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 +21 -5
- 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 +37 -0
- package/dist/cache/response-store-adapter.js +39 -0
- package/dist/cache/response-store-adapter.self-contained.worker.d.ts +17 -0
- package/dist/cache/response-store-adapter.self-contained.worker.js +10 -0
- package/dist/cache/response-store-adapter.service-binding.worker.d.ts +15 -0
- package/dist/cache/response-store-adapter.service-binding.worker.js +9 -0
- package/dist/cache/response-store-adapter.worker.d.ts +14 -0
- package/dist/cache/response-store-adapter.worker.js +254 -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 +208 -50
- 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.d.ts +23 -24
- package/dist/deploy.js +44 -11
- 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 +9 -10
- package/dist/tpr.js +1 -1
- package/dist/utils/cache-control-metadata.d.ts +3 -4
- package/dist/version-deploy.d.ts +17 -17
- package/dist/version-deploy.js +6 -3
- package/dist/worker-deployment-url.d.ts +3 -4
- package/dist/workers-dev-url.d.ts +2 -3
- package/dist/wrangler-version-metadata.d.ts +2 -3
- package/package.json +13 -7
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" | "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",
|
|
@@ -350,17 +445,35 @@ function validateCachePolicy(response, requireCacheStatus, requireCacheHit = fal
|
|
|
350
445
|
error: `CF-Cache-Status is ${cacheStatus}`
|
|
351
446
|
};
|
|
352
447
|
}
|
|
353
|
-
function
|
|
354
|
-
|
|
448
|
+
function validateVinextCacheStatus(response) {
|
|
449
|
+
const status = response.headers.get(VINEXT_CACHE_HEADER)?.trim().toUpperCase();
|
|
450
|
+
if (status === "MISS" || status === "HIT" || status === "UPDATING") return { outcome: "warmed" };
|
|
451
|
+
if (status === "BYPASS" || status === "DYNAMIC") return {
|
|
452
|
+
outcome: "skipped",
|
|
453
|
+
reason: `${VINEXT_CACHE_HEADER} is ${status}`
|
|
454
|
+
};
|
|
455
|
+
return {
|
|
355
456
|
outcome: "failed",
|
|
356
|
-
error: `response
|
|
457
|
+
error: `response is missing ${VINEXT_CACHE_HEADER}`
|
|
357
458
|
};
|
|
459
|
+
}
|
|
460
|
+
function validateWarmAdmission(response, statusSource, requireCacheHit) {
|
|
461
|
+
return statusSource === "vinext" ? validateVinextCacheStatus(response) : validateCachePolicy(response, true, requireCacheHit);
|
|
462
|
+
}
|
|
463
|
+
function validateBuildIdentity(response, expectedBuildId) {
|
|
464
|
+
if (expectedBuildId !== void 0 && response.headers.get("X-Vinext-Build-Id") !== expectedBuildId) {
|
|
465
|
+
const actualBuildId = response.headers.get("X-Vinext-Build-Id") ?? "missing";
|
|
466
|
+
return {
|
|
467
|
+
outcome: "failed",
|
|
468
|
+
error: `response ${VINEXT_CDN_BUILD_ID_HEADER} does not match build ${expectedBuildId} (received ${actualBuildId})`
|
|
469
|
+
};
|
|
470
|
+
}
|
|
358
471
|
return null;
|
|
359
472
|
}
|
|
360
473
|
function isExpectedTerminalStatus(status) {
|
|
361
474
|
return status >= 300 && status < 400 || status === 404;
|
|
362
475
|
}
|
|
363
|
-
function validateRscWarmResponse(response, expectedBuildId, expectedRscBuildId, requireCacheHit = false) {
|
|
476
|
+
function validateRscWarmResponse(response, expectedBuildId, expectedRscBuildId, requireCacheHit = false, statusSource = "cloudflare") {
|
|
364
477
|
const buildIdentityValidation = validateBuildIdentity(response, expectedBuildId);
|
|
365
478
|
if (buildIdentityValidation) return buildIdentityValidation;
|
|
366
479
|
if (expectedRscBuildId !== void 0 && response.headers.get(VINEXT_RSC_BUILD_ID_HEADER) !== expectedRscBuildId) return {
|
|
@@ -381,7 +494,7 @@ function validateRscWarmResponse(response, expectedBuildId, expectedRscBuildId,
|
|
|
381
494
|
outcome: "failed",
|
|
382
495
|
error: `expected ${VINEXT_RSC_CONTENT_TYPE} response`
|
|
383
496
|
};
|
|
384
|
-
const cachePolicyValidation =
|
|
497
|
+
const cachePolicyValidation = validateWarmAdmission(response, statusSource, requireCacheHit);
|
|
385
498
|
if (cachePolicyValidation.outcome !== "warmed") return cachePolicyValidation;
|
|
386
499
|
if (terminalResponse && !response.headers.get("Content-Type")?.toLowerCase().startsWith(VINEXT_RSC_CONTENT_TYPE)) return {
|
|
387
500
|
outcome: "failed",
|
|
@@ -395,7 +508,7 @@ function validateRscWarmResponse(response, expectedBuildId, expectedRscBuildId,
|
|
|
395
508
|
};
|
|
396
509
|
return { outcome: "warmed" };
|
|
397
510
|
}
|
|
398
|
-
function validateHtmlWarmResponse(response, expectedBuildId, requireCacheHit = false) {
|
|
511
|
+
function validateHtmlWarmResponse(response, expectedBuildId, requireCacheHit = false, statusSource = "cloudflare") {
|
|
399
512
|
const buildIdentityValidation = validateBuildIdentity(response, expectedBuildId);
|
|
400
513
|
if (buildIdentityValidation) return buildIdentityValidation;
|
|
401
514
|
if (response.redirected) return {
|
|
@@ -408,12 +521,12 @@ function validateHtmlWarmResponse(response, expectedBuildId, requireCacheHit = f
|
|
|
408
521
|
error: `HTTP ${response.status}`
|
|
409
522
|
};
|
|
410
523
|
}
|
|
411
|
-
const cachePolicyValidation =
|
|
524
|
+
const cachePolicyValidation = validateWarmAdmission(response, statusSource, requireCacheHit);
|
|
412
525
|
if (cachePolicyValidation.outcome !== "warmed") return cachePolicyValidation;
|
|
413
526
|
return { outcome: "warmed" };
|
|
414
527
|
}
|
|
415
|
-
function validatePagesDataWarmResponse(response, expectedBuildId, requireCacheHit = false) {
|
|
416
|
-
const validation = validateHtmlWarmResponse(response, expectedBuildId, requireCacheHit);
|
|
528
|
+
function validatePagesDataWarmResponse(response, expectedBuildId, requireCacheHit = false, statusSource = "cloudflare") {
|
|
529
|
+
const validation = validateHtmlWarmResponse(response, expectedBuildId, requireCacheHit, statusSource);
|
|
417
530
|
if (validation.outcome !== "warmed") return validation;
|
|
418
531
|
if (!response.headers.get("Content-Type")?.toLowerCase().startsWith("application/json")) return {
|
|
419
532
|
outcome: "failed",
|
|
@@ -536,6 +649,7 @@ async function warmOnePath(target, options) {
|
|
|
536
649
|
const url = buildWarmupUrl(options.targetUrl, target.pathname);
|
|
537
650
|
let lastError = "request failed before the first attempt";
|
|
538
651
|
let lastRetryable = true;
|
|
652
|
+
let lastSkippedReason = null;
|
|
539
653
|
const phaseDeadlineError = () => `CDN warmup exceeded its ${options.phaseTimeoutMs}ms phase deadline`;
|
|
540
654
|
const remainingPhaseMs = () => options.deadlineAt === void 0 ? options.timeoutMs : options.deadlineAt - Date.now();
|
|
541
655
|
const canRetry = (attempt) => attempt < options.retries;
|
|
@@ -555,6 +669,7 @@ async function warmOnePath(target, options) {
|
|
|
555
669
|
retryable: false
|
|
556
670
|
};
|
|
557
671
|
const attemptTimeoutMs = Math.min(options.timeoutMs, remainingMs);
|
|
672
|
+
const phaseLimitedAttempt = options.deadlineAt !== void 0 && remainingMs <= options.timeoutMs;
|
|
558
673
|
try {
|
|
559
674
|
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
675
|
if (options.deadlineAt !== void 0 && Date.now() >= options.deadlineAt) return {
|
|
@@ -566,18 +681,31 @@ async function warmOnePath(target, options) {
|
|
|
566
681
|
if (options.requireCacheHit) response.body?.cancel().catch(() => {});
|
|
567
682
|
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
683
|
if (target.kind.startsWith("rsc-")) {
|
|
569
|
-
const validation = validateRscWarmResponse(response, options.expectedBuildId, options.expectedRscBuildId, options.requireCacheHit);
|
|
684
|
+
const validation = validateRscWarmResponse(response, options.expectedBuildId, options.expectedRscBuildId, options.requireCacheHit, options.statusSource);
|
|
570
685
|
if (validation.outcome === "warmed") return {
|
|
571
686
|
path: target.label,
|
|
572
687
|
ok: true,
|
|
573
688
|
skipped: false
|
|
574
689
|
};
|
|
575
|
-
if (validation.outcome === "skipped")
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
690
|
+
if (validation.outcome === "skipped") {
|
|
691
|
+
if (!options.retrySkipped) return {
|
|
692
|
+
path: target.label,
|
|
693
|
+
ok: true,
|
|
694
|
+
skipped: true,
|
|
695
|
+
reason: validation.reason
|
|
696
|
+
};
|
|
697
|
+
lastSkippedReason = validation.reason;
|
|
698
|
+
lastRetryable = true;
|
|
699
|
+
if (!canRetry(attempt)) break;
|
|
700
|
+
if (!await waitBeforeRetry()) return {
|
|
701
|
+
path: target.label,
|
|
702
|
+
ok: false,
|
|
703
|
+
error: phaseDeadlineError(),
|
|
704
|
+
retryable: false
|
|
705
|
+
};
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
708
|
+
lastSkippedReason = null;
|
|
581
709
|
lastError = validation.error;
|
|
582
710
|
const cacheStatus = response.headers.get("CF-Cache-Status")?.trim().toUpperCase();
|
|
583
711
|
lastRetryable = options.requireCacheHit && ADMITTED_CF_CACHE_STATUSES.has(cacheStatus ?? "") && !REUSABLE_CF_CACHE_STATUSES.has(cacheStatus ?? "") || shouldRetryValidationFailure(response, target, options);
|
|
@@ -591,26 +719,47 @@ async function warmOnePath(target, options) {
|
|
|
591
719
|
};
|
|
592
720
|
continue;
|
|
593
721
|
}
|
|
594
|
-
const validation = target.kind === "pages-data" ? validatePagesDataWarmResponse(response, options.expectedBuildId, options.requireCacheHit) : validateHtmlWarmResponse(response, options.expectedBuildId, options.requireCacheHit);
|
|
722
|
+
const validation = target.kind === "pages-data" ? validatePagesDataWarmResponse(response, options.expectedBuildId, options.requireCacheHit, options.statusSource) : validateHtmlWarmResponse(response, options.expectedBuildId, options.requireCacheHit, options.statusSource);
|
|
595
723
|
if (validation.outcome === "warmed") return {
|
|
596
724
|
path: target.label,
|
|
597
725
|
ok: true,
|
|
598
726
|
skipped: false
|
|
599
727
|
};
|
|
600
|
-
if (validation.outcome === "skipped")
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
728
|
+
if (validation.outcome === "skipped") {
|
|
729
|
+
if (!options.retrySkipped) return {
|
|
730
|
+
path: target.label,
|
|
731
|
+
ok: true,
|
|
732
|
+
skipped: true,
|
|
733
|
+
reason: validation.reason
|
|
734
|
+
};
|
|
735
|
+
lastSkippedReason = validation.reason;
|
|
736
|
+
lastRetryable = true;
|
|
737
|
+
if (!canRetry(attempt)) break;
|
|
738
|
+
if (!await waitBeforeRetry()) return {
|
|
739
|
+
path: target.label,
|
|
740
|
+
ok: false,
|
|
741
|
+
error: phaseDeadlineError(),
|
|
742
|
+
retryable: false
|
|
743
|
+
};
|
|
744
|
+
continue;
|
|
745
|
+
}
|
|
746
|
+
lastSkippedReason = null;
|
|
606
747
|
lastError = validation.error;
|
|
607
748
|
const cacheStatus = response.headers.get("CF-Cache-Status")?.trim().toUpperCase();
|
|
608
749
|
lastRetryable = options.requireCacheHit && ADMITTED_CF_CACHE_STATUSES.has(cacheStatus ?? "") && !REUSABLE_CF_CACHE_STATUSES.has(cacheStatus ?? "") || shouldRetryValidationFailure(response, target, options);
|
|
609
750
|
if (!lastRetryable) break;
|
|
610
751
|
} catch (error) {
|
|
752
|
+
lastSkippedReason = null;
|
|
611
753
|
lastRetryable = true;
|
|
612
|
-
if (error instanceof DOMException && error.name === "AbortError")
|
|
613
|
-
|
|
754
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
755
|
+
if (phaseLimitedAttempt) return {
|
|
756
|
+
path: target.label,
|
|
757
|
+
ok: false,
|
|
758
|
+
error: phaseDeadlineError(),
|
|
759
|
+
retryable: false
|
|
760
|
+
};
|
|
761
|
+
lastError = `timed out after ${attemptTimeoutMs}ms`;
|
|
762
|
+
} else lastError = error instanceof Error ? error.message : String(error);
|
|
614
763
|
}
|
|
615
764
|
if (!canRetry(attempt)) break;
|
|
616
765
|
if (!await waitBeforeRetry()) return {
|
|
@@ -620,11 +769,16 @@ async function warmOnePath(target, options) {
|
|
|
620
769
|
retryable: false
|
|
621
770
|
};
|
|
622
771
|
}
|
|
623
|
-
return {
|
|
772
|
+
return lastSkippedReason === null ? {
|
|
624
773
|
path: target.label,
|
|
625
774
|
ok: false,
|
|
626
775
|
error: lastError,
|
|
627
776
|
retryable: lastRetryable
|
|
777
|
+
} : {
|
|
778
|
+
path: target.label,
|
|
779
|
+
ok: true,
|
|
780
|
+
skipped: true,
|
|
781
|
+
reason: lastSkippedReason
|
|
628
782
|
};
|
|
629
783
|
}
|
|
630
784
|
async function runWithConcurrency(items, concurrency, fn) {
|
|
@@ -677,7 +831,8 @@ async function warmCdnCache(options) {
|
|
|
677
831
|
rscPaths: []
|
|
678
832
|
}
|
|
679
833
|
};
|
|
680
|
-
console.log(`\n Warming
|
|
834
|
+
console.log(`\n Warming ${formatCount(requests.length, "CDN cache entry", "CDN cache entries")}...`);
|
|
835
|
+
printCdnWarmRouteReport(requests);
|
|
681
836
|
const progress = new CdnOperationProgress();
|
|
682
837
|
let completedRequests = 0;
|
|
683
838
|
progress.update(0, requests.length, "starting warmup");
|
|
@@ -697,7 +852,9 @@ async function warmCdnCache(options) {
|
|
|
697
852
|
retryDelayMs: isPropagationRequest ? propagationRetryDelayMs : normalRetryDelayMs,
|
|
698
853
|
retryNotFound: isPropagationRequest,
|
|
699
854
|
phaseTimeoutMs,
|
|
700
|
-
requireCacheHit: options.requireCacheHit === true
|
|
855
|
+
requireCacheHit: options.requireCacheHit === true,
|
|
856
|
+
statusSource: options.statusSource ?? "cloudflare",
|
|
857
|
+
retrySkipped: isPropagationRequest && options.retrySkippedTargetKeys?.has(cdnWarmTargetKey(target)) === true
|
|
701
858
|
});
|
|
702
859
|
};
|
|
703
860
|
const warmRequest = async (target, retryMode = "normal") => {
|
|
@@ -708,24 +865,24 @@ async function warmCdnCache(options) {
|
|
|
708
865
|
};
|
|
709
866
|
const warmPropagatingPass = async (targets) => {
|
|
710
867
|
const results = await runWithConcurrency(targets, concurrency, (target) => warmRequest(target, "propagation-pass"));
|
|
711
|
-
const
|
|
868
|
+
const retryable = targets.map((target, index) => ({
|
|
712
869
|
index,
|
|
713
870
|
result: results[index],
|
|
714
871
|
target
|
|
715
|
-
})).filter((
|
|
716
|
-
if (
|
|
872
|
+
})).filter(({ result, target }) => result.ok ? result.skipped && options.retrySkippedTargetKeys?.has(cdnWarmTargetKey(target)) === true : result.retryable);
|
|
873
|
+
if (retryable.length === 0 || propagationRetries === 0) return results;
|
|
717
874
|
progress.finish();
|
|
718
|
-
console.log(` CDN warmup: retrying ${
|
|
875
|
+
console.log(` CDN warmup: retrying ${retryable.length} unresolved request(s) after completing the initial pass...`);
|
|
719
876
|
let completedRetries = 0;
|
|
720
|
-
progress.update(0,
|
|
721
|
-
const retried = await runWithConcurrency(
|
|
877
|
+
progress.update(0, retryable.length, "starting retry pass", "Retrying CDN cache");
|
|
878
|
+
const retried = await runWithConcurrency(retryable.map(({ target }) => target), concurrency, async (target) => {
|
|
722
879
|
const result = await warmTarget(target, "propagation-retry");
|
|
723
880
|
completedRetries++;
|
|
724
|
-
progress.update(completedRetries,
|
|
881
|
+
progress.update(completedRetries, retryable.length, target.label, "Retrying CDN cache");
|
|
725
882
|
return result;
|
|
726
883
|
});
|
|
727
884
|
progress.finish();
|
|
728
|
-
for (const [retryIndex, { index }] of
|
|
885
|
+
for (const [retryIndex, { index }] of retryable.entries()) results[index] = retried[retryIndex];
|
|
729
886
|
return results;
|
|
730
887
|
};
|
|
731
888
|
let results;
|
|
@@ -752,6 +909,7 @@ async function warmCdnCache(options) {
|
|
|
752
909
|
const warmed = results.length - failures.length - skippedResults.length;
|
|
753
910
|
const warmedRouteHandlerPaths = warmedRequests.filter((target) => target.kind === "app-route").map((target) => target.sourcePathname);
|
|
754
911
|
const failedRouteHandlerPaths = failedRequests.filter(({ target }) => target.kind === "app-route").map(({ target }) => target.sourcePathname);
|
|
912
|
+
printCdnWarmRouteReport(requests, results.map((result) => !result.ok ? "failed" : result.skipped ? "skipped" : "warmed"));
|
|
755
913
|
console.log(` CDN warmup: ${warmed} warmed, ${skippedResults.length} skipped, ${failures.length} failed.`);
|
|
756
914
|
for (const skipped of skippedResults.slice(0, 5)) console.log(` CDN warmup skipped ${skipped.path}: ${skipped.reason}`);
|
|
757
915
|
if (failures.length > 0) {
|
|
@@ -804,4 +962,4 @@ async function warmCdnCacheFromPrerender(options) {
|
|
|
804
962
|
});
|
|
805
963
|
}
|
|
806
964
|
//#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 };
|
|
965
|
+
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 };
|
package/dist/deploy-config.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { VinextCacheConfig } from "vinext/internal/cache-adapters";
|
|
|
8
8
|
* Returns true when the config both imports the package and invokes
|
|
9
9
|
* `cloudflare(...)` in the plugin list.
|
|
10
10
|
*/
|
|
11
|
-
declare function viteConfigHasCloudflarePlugin(root: string): boolean;
|
|
11
|
+
export declare function viteConfigHasCloudflarePlugin(root: string): boolean;
|
|
12
12
|
/**
|
|
13
13
|
* Detect whether the Vite config assigns a CDN or data cache adapter — i.e. the
|
|
14
14
|
* `cdn` or `data` field of the `vinext({ cache })` option is given a value.
|
|
@@ -17,18 +17,18 @@ declare function viteConfigHasCloudflarePlugin(root: string): boolean;
|
|
|
17
17
|
* an unreadable or absent config is treated as configured so a deploy is never
|
|
18
18
|
* blocked on a false negative.
|
|
19
19
|
*/
|
|
20
|
-
declare function viteConfigHasCacheAdapter(root: string): boolean;
|
|
21
|
-
type ResolvedKvDataAdapterConfig = {
|
|
20
|
+
export declare function viteConfigHasCacheAdapter(root: string): boolean;
|
|
21
|
+
export type ResolvedKvDataAdapterConfig = {
|
|
22
22
|
binding: string;
|
|
23
23
|
appPrefix?: string;
|
|
24
24
|
ttlSeconds?: number;
|
|
25
25
|
};
|
|
26
|
-
type ResolvedCdnAdapterConfig = {
|
|
26
|
+
export type ResolvedCdnAdapterConfig = {
|
|
27
27
|
versionMetadataBinding: string;
|
|
28
28
|
};
|
|
29
|
-
declare function resolveCdnAdapterConfig(cache: VinextCacheConfig | null | undefined): ResolvedCdnAdapterConfig | null;
|
|
30
|
-
declare function resolveKvDataAdapterConfig(cache: VinextCacheConfig | null | undefined): ResolvedKvDataAdapterConfig | null;
|
|
31
|
-
declare function viteConfigHasImageAdapter(root: string): boolean;
|
|
29
|
+
export declare function resolveCdnAdapterConfig(cache: VinextCacheConfig | null | undefined): ResolvedCdnAdapterConfig | null;
|
|
30
|
+
export declare function resolveKvDataAdapterConfig(cache: VinextCacheConfig | null | undefined): ResolvedKvDataAdapterConfig | null;
|
|
31
|
+
export declare function viteConfigHasImageAdapter(root: string): boolean;
|
|
32
32
|
/**
|
|
33
33
|
* Detect whether an existing user-authored Worker entry wires up a cache
|
|
34
34
|
* backend imperatively via one of the `setCacheHandler` / `setDataCacheHandler`
|
|
@@ -43,16 +43,15 @@ declare function viteConfigHasImageAdapter(root: string): boolean;
|
|
|
43
43
|
* missing Worker entry returns false (nothing to inspect — defer to other
|
|
44
44
|
* checks).
|
|
45
45
|
*/
|
|
46
|
-
declare function workerEntryHasCacheHandler(root: string): boolean;
|
|
46
|
+
export declare function workerEntryHasCacheHandler(root: string): boolean;
|
|
47
47
|
/**
|
|
48
48
|
* Build the error thrown when an ISR/cached app is deployed without a cache
|
|
49
49
|
* adapter configured in the Vite config. Production deployments need a
|
|
50
50
|
* persistent cache backend; vinext no longer scaffolds one into the Worker
|
|
51
51
|
* entry, so it must be declared via `vinext({ cache })`.
|
|
52
52
|
*/
|
|
53
|
-
declare function formatMissingCacheAdapterError(options: {
|
|
53
|
+
export declare function formatMissingCacheAdapterError(options: {
|
|
54
54
|
configFile?: string;
|
|
55
55
|
}): string;
|
|
56
|
-
declare function formatImageOptimizationHint(): string;
|
|
57
|
-
//#endregion
|
|
58
|
-
export { ResolvedCdnAdapterConfig, ResolvedKvDataAdapterConfig, formatImageOptimizationHint, formatMissingCacheAdapterError, resolveCdnAdapterConfig, resolveKvDataAdapterConfig, viteConfigHasCacheAdapter, viteConfigHasCloudflarePlugin, viteConfigHasImageAdapter, workerEntryHasCacheHandler };
|
|
56
|
+
export declare function formatImageOptimizationHint(): string;
|
|
57
|
+
//#endregion
|