@vinext/cloudflare 1.0.0-beta.8 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +174 -37
- package/dist/cache/cdn-adapter-config.d.ts +2 -1
- package/dist/cache/cdn-adapter-config.js +13 -5
- package/dist/cache/cdn-adapter.d.ts +5 -56
- package/dist/cache/cdn-adapter.js +3 -61
- package/dist/cache/cdn-adapter.runtime.d.ts +1 -0
- package/dist/cache/cdn-adapter.runtime.js +17 -3
- package/dist/cache/cdn-adapter.worker.d.ts +1 -0
- package/dist/cache/cdn-adapter.worker.js +6 -4
- package/dist/cache/cdn-build-id.d.ts +1 -1
- package/dist/cache/cdn-build-id.js +1 -1
- package/dist/cache/config.d.ts +137 -0
- package/dist/cache/config.js +87 -0
- package/dist/cache/kv-data-adapter.d.ts +5 -1
- package/dist/cache/kv-data-adapter.js +6 -2
- package/dist/cache/kv-data-adapter.runtime.js +13 -9
- package/dist/cache/kv-key.d.ts +7 -4
- package/dist/cache/kv-key.js +16 -5
- package/dist/cache/response-store-adapter.d.ts +36 -0
- package/dist/cache/response-store-adapter.js +26 -1
- package/dist/cache/response-store-adapter.self-contained.worker.d.ts +1 -0
- package/dist/cache/response-store-adapter.self-contained.worker.js +3 -1
- package/dist/cache/response-store-adapter.service-binding.worker.d.ts +1 -0
- package/dist/cache/response-store-adapter.service-binding.worker.js +3 -1
- package/dist/cache/response-store-adapter.worker.d.ts +1 -1
- package/dist/cache/response-store-adapter.worker.js +148 -81
- package/dist/cache/response-store-cdn.runtime.js +1 -0
- package/dist/cache/response-store-data.runtime.js +53 -31
- package/dist/cache/static-assets-adapter.build.d.ts +6 -0
- package/dist/cache/static-assets-adapter.build.js +65 -0
- package/dist/cache/static-assets-adapter.d.ts +27 -0
- package/dist/cache/static-assets-adapter.js +26 -0
- package/dist/cache/static-assets-adapter.runtime.d.ts +26 -0
- package/dist/cache/static-assets-adapter.runtime.js +77 -0
- package/dist/cache/static-assets-adapter.shared.d.ts +12 -0
- package/dist/cache/static-assets-adapter.shared.js +4 -0
- package/dist/cache/workers-cache-cdn-adapter.d.ts +58 -0
- package/dist/cache/workers-cache-cdn-adapter.js +65 -0
- package/dist/cacheability-artifact.d.ts +7 -1
- package/dist/cacheability-artifact.js +60 -21
- package/dist/cacheability-probe.d.ts +2 -0
- package/dist/cacheability-probe.js +107 -13
- package/dist/cdn-warm.d.ts +2 -1
- package/dist/cdn-warm.js +25 -8
- package/dist/deploy-config.d.ts +3 -8
- package/dist/deploy-config.js +5 -23
- package/dist/deploy-help.js +58 -47
- package/dist/deploy.d.ts +31 -21
- package/dist/deploy.js +501 -286
- package/dist/tpr.d.ts +29 -72
- package/dist/tpr.js +52 -601
- package/dist/version-deploy.d.ts +27 -0
- package/dist/version-deploy.js +125 -5
- package/dist/wrangler-config.d.ts +19 -0
- package/dist/wrangler-config.js +294 -0
- package/dist/wrangler-version-metadata.js +1 -1
- package/package.json +16 -9
- package/dist/prerender-kv-populate.d.ts +0 -24
- package/dist/prerender-kv-populate.js +0 -153
package/dist/cdn-warm.js
CHANGED
|
@@ -38,7 +38,7 @@ function readPrerenderPathManifest(manifestPath) {
|
|
|
38
38
|
const parsed = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
39
39
|
if (!parsed || typeof parsed !== "object") return null;
|
|
40
40
|
const manifest = parsed;
|
|
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;
|
|
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") && (route.cacheabilityProbe.unlisted === void 0 || typeof route.cacheabilityProbe.unlisted === "boolean")))) || manifest.loadingShellPaths !== void 0 && (!Array.isArray(manifest.loadingShellPaths) || !manifest.loadingShellPaths.every((pathname) => typeof pathname === "string")) || manifest.loadingBoundaryRoutePatterns !== void 0 && (!Array.isArray(manifest.loadingBoundaryRoutePatterns) || !manifest.loadingBoundaryRoutePatterns.every((pattern) => typeof pattern === "string" && pattern.startsWith("/"))) || 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;
|
|
42
42
|
return manifest;
|
|
43
43
|
} catch (error) {
|
|
44
44
|
console.warn(`[vinext] Failed to read prerender path manifest at ${manifestPath}:`, error);
|
|
@@ -90,6 +90,7 @@ function createPrerenderWarmPlan(root, manifest, options) {
|
|
|
90
90
|
...manifest.buildIdentity ? { buildIdentity: manifest.buildIdentity } : {},
|
|
91
91
|
...manifest.deploymentId ? { deploymentId: manifest.deploymentId } : {},
|
|
92
92
|
...manifest.fallbackRoutePatterns ? { fallbackRoutePatterns: manifest.fallbackRoutePatterns } : {},
|
|
93
|
+
...manifest.loadingBoundaryRoutePatterns ? { loadingBoundaryRoutePatterns: manifest.loadingBoundaryRoutePatterns } : {},
|
|
93
94
|
loadingShellPaths: supportsCanonicalRsc ? (manifest.loadingShellPaths ?? []).map(applyConfig) : [],
|
|
94
95
|
...manifest.pagesDataPaths ? { pagesDataPaths: manifest.pagesDataPaths } : {},
|
|
95
96
|
...manifest.pagesPaths ? { pagesPaths: manifest.pagesPaths.map(applyConfig) } : {},
|
|
@@ -365,6 +366,7 @@ const REUSABLE_CF_CACHE_STATUSES = /* @__PURE__ */ new Set([
|
|
|
365
366
|
"REVALIDATED",
|
|
366
367
|
"UPDATING"
|
|
367
368
|
]);
|
|
369
|
+
const TRANSITIONAL_VINEXT_CACHE_STATUSES = /* @__PURE__ */ new Set(["MISS", "UPDATING"]);
|
|
368
370
|
const NON_CACHEABLE_CF_CACHE_STATUSES = /* @__PURE__ */ new Set(["BYPASS"]);
|
|
369
371
|
const CDN_CACHE_POLICY_HEADERS = [
|
|
370
372
|
"Cloudflare-CDN-Cache-Control",
|
|
@@ -445,20 +447,29 @@ function validateCachePolicy(response, requireCacheStatus, requireCacheHit = fal
|
|
|
445
447
|
error: `CF-Cache-Status is ${cacheStatus}`
|
|
446
448
|
};
|
|
447
449
|
}
|
|
448
|
-
function validateVinextCacheStatus(response) {
|
|
450
|
+
function validateVinextCacheStatus(response, missingStatus = "failed", requireCacheHit = false) {
|
|
449
451
|
const status = response.headers.get(VINEXT_CACHE_HEADER)?.trim().toUpperCase();
|
|
452
|
+
if (requireCacheHit && status !== "HIT") return {
|
|
453
|
+
outcome: "failed",
|
|
454
|
+
error: `${VINEXT_CACHE_HEADER} is ${status ?? "missing"}; the cache fill is not reusable`
|
|
455
|
+
};
|
|
450
456
|
if (status === "MISS" || status === "HIT" || status === "UPDATING") return { outcome: "warmed" };
|
|
451
457
|
if (status === "BYPASS" || status === "DYNAMIC") return {
|
|
452
458
|
outcome: "skipped",
|
|
453
459
|
reason: `${VINEXT_CACHE_HEADER} is ${status}`
|
|
454
460
|
};
|
|
455
|
-
return {
|
|
461
|
+
return missingStatus === "skipped" ? {
|
|
462
|
+
outcome: "skipped",
|
|
463
|
+
reason: `response is missing ${VINEXT_CACHE_HEADER}`
|
|
464
|
+
} : {
|
|
456
465
|
outcome: "failed",
|
|
457
466
|
error: `response is missing ${VINEXT_CACHE_HEADER}`
|
|
458
467
|
};
|
|
459
468
|
}
|
|
460
469
|
function validateWarmAdmission(response, statusSource, requireCacheHit) {
|
|
461
|
-
|
|
470
|
+
if (statusSource === "vinext") return validateVinextCacheStatus(response, "failed", requireCacheHit);
|
|
471
|
+
if (statusSource === "data-cache") return validateVinextCacheStatus(response, "skipped", requireCacheHit);
|
|
472
|
+
return validateCachePolicy(response, true, requireCacheHit);
|
|
462
473
|
}
|
|
463
474
|
function validateBuildIdentity(response, expectedBuildId) {
|
|
464
475
|
if (expectedBuildId !== void 0 && response.headers.get("X-Vinext-Build-Id") !== expectedBuildId) {
|
|
@@ -645,6 +656,14 @@ function shouldRetryValidationFailure(response, target, options) {
|
|
|
645
656
|
if (expectedIdentities.some((matches) => !matches)) return true;
|
|
646
657
|
return isRetryableStatus(response.status, options.retryNotFound);
|
|
647
658
|
}
|
|
659
|
+
function isRetryableCertificationFailure(response, statusSource, retryPropagationFailures) {
|
|
660
|
+
if (statusSource === "cloudflare") {
|
|
661
|
+
const status = response.headers.get("CF-Cache-Status")?.trim().toUpperCase();
|
|
662
|
+
return !REUSABLE_CF_CACHE_STATUSES.has(status ?? "") && (retryPropagationFailures || ADMITTED_CF_CACHE_STATUSES.has(status ?? ""));
|
|
663
|
+
}
|
|
664
|
+
const status = response.headers.get(VINEXT_CACHE_HEADER)?.trim().toUpperCase();
|
|
665
|
+
return TRANSITIONAL_VINEXT_CACHE_STATUSES.has(status ?? "");
|
|
666
|
+
}
|
|
648
667
|
async function warmOnePath(target, options) {
|
|
649
668
|
const url = buildWarmupUrl(options.targetUrl, target.pathname);
|
|
650
669
|
let lastError = "request failed before the first attempt";
|
|
@@ -707,8 +726,7 @@ async function warmOnePath(target, options) {
|
|
|
707
726
|
}
|
|
708
727
|
lastSkippedReason = null;
|
|
709
728
|
lastError = validation.error;
|
|
710
|
-
|
|
711
|
-
lastRetryable = options.requireCacheHit && ADMITTED_CF_CACHE_STATUSES.has(cacheStatus ?? "") && !REUSABLE_CF_CACHE_STATUSES.has(cacheStatus ?? "") || shouldRetryValidationFailure(response, target, options);
|
|
729
|
+
lastRetryable = options.requireCacheHit && isRetryableCertificationFailure(response, options.statusSource, options.retryPropagationFailures) || shouldRetryValidationFailure(response, target, options);
|
|
712
730
|
if (!lastRetryable) break;
|
|
713
731
|
if (!canRetry(attempt)) break;
|
|
714
732
|
if (!await waitBeforeRetry()) return {
|
|
@@ -745,8 +763,7 @@ async function warmOnePath(target, options) {
|
|
|
745
763
|
}
|
|
746
764
|
lastSkippedReason = null;
|
|
747
765
|
lastError = validation.error;
|
|
748
|
-
|
|
749
|
-
lastRetryable = options.requireCacheHit && ADMITTED_CF_CACHE_STATUSES.has(cacheStatus ?? "") && !REUSABLE_CF_CACHE_STATUSES.has(cacheStatus ?? "") || shouldRetryValidationFailure(response, target, options);
|
|
766
|
+
lastRetryable = options.requireCacheHit && isRetryableCertificationFailure(response, options.statusSource, options.retryPropagationFailures) || shouldRetryValidationFailure(response, target, options);
|
|
750
767
|
if (!lastRetryable) break;
|
|
751
768
|
} catch (error) {
|
|
752
769
|
lastSkippedReason = null;
|
package/dist/deploy-config.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { VinextCacheConfig } from "vinext/internal/
|
|
1
|
+
import { VinextCacheConfig } from "vinext/internal/config/prerender";
|
|
2
2
|
//#region src/deploy-config.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* Check whether an existing vite.config file already imports and uses the
|
|
@@ -18,16 +18,10 @@ export declare function viteConfigHasCloudflarePlugin(root: string): boolean;
|
|
|
18
18
|
* blocked on a false negative.
|
|
19
19
|
*/
|
|
20
20
|
export declare function viteConfigHasCacheAdapter(root: string): boolean;
|
|
21
|
-
export type ResolvedKvDataAdapterConfig = {
|
|
22
|
-
binding: string;
|
|
23
|
-
appPrefix?: string;
|
|
24
|
-
ttlSeconds?: number;
|
|
25
|
-
};
|
|
26
21
|
export type ResolvedCdnAdapterConfig = {
|
|
27
22
|
versionMetadataBinding: string;
|
|
28
23
|
};
|
|
29
24
|
export declare function resolveCdnAdapterConfig(cache: VinextCacheConfig | null | undefined): ResolvedCdnAdapterConfig | null;
|
|
30
|
-
export declare function resolveKvDataAdapterConfig(cache: VinextCacheConfig | null | undefined): ResolvedKvDataAdapterConfig | null;
|
|
31
25
|
export declare function viteConfigHasImageAdapter(root: string): boolean;
|
|
32
26
|
/**
|
|
33
27
|
* Detect whether an existing user-authored Worker entry wires up a cache
|
|
@@ -52,6 +46,7 @@ export declare function workerEntryHasCacheHandler(root: string): boolean;
|
|
|
52
46
|
*/
|
|
53
47
|
export declare function formatMissingCacheAdapterError(options: {
|
|
54
48
|
configFile?: string;
|
|
49
|
+
typedConfig?: boolean;
|
|
55
50
|
}): string;
|
|
56
|
-
export declare function formatImageOptimizationHint(): string;
|
|
51
|
+
export declare function formatImageOptimizationHint(typedConfig?: boolean): string;
|
|
57
52
|
//#endregion
|
package/dist/deploy-config.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { DEFAULT_CDN_VERSION_METADATA_BINDING } from "./cache/cdn-adapter.js";
|
|
1
|
+
import { DEFAULT_CDN_VERSION_METADATA_BINDING } from "./cache/workers-cache-cdn-adapter.js";
|
|
3
2
|
import fs from "node:fs";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
import { fileURLToPath } from "node:url";
|
|
@@ -105,24 +104,6 @@ function resolveCdnAdapterConfig(cache) {
|
|
|
105
104
|
const options = cdn.options;
|
|
106
105
|
return { versionMetadataBinding: typeof options?.versionMetadataBinding === "string" && options.versionMetadataBinding.length > 0 ? options.versionMetadataBinding : DEFAULT_CDN_VERSION_METADATA_BINDING };
|
|
107
106
|
}
|
|
108
|
-
function isCloudflareKvDataAdapterPath(adapter) {
|
|
109
|
-
const normalized = adapter.replace(/\\/g, "/");
|
|
110
|
-
return normalized === "@vinext/cloudflare/cache/kv-data-adapter.runtime" || normalized === "@vinext/cloudflare/cache/kv-data-adapter.runtime.js" || normalized.endsWith("/cache/kv-data-adapter.runtime.js");
|
|
111
|
-
}
|
|
112
|
-
function readPositiveNumberOption(options, field) {
|
|
113
|
-
const value = options?.[field];
|
|
114
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
115
|
-
}
|
|
116
|
-
function resolveKvDataAdapterConfig(cache) {
|
|
117
|
-
const data = cache?.data;
|
|
118
|
-
if (!data?.adapter || !isCloudflareKvDataAdapterPath(data.adapter)) return null;
|
|
119
|
-
const options = data.options;
|
|
120
|
-
return {
|
|
121
|
-
binding: typeof options?.binding === "string" && options.binding.length > 0 ? options.binding : DEFAULT_KV_DATA_CACHE_BINDING,
|
|
122
|
-
...typeof options?.appPrefix === "string" && options.appPrefix.length > 0 ? { appPrefix: options.appPrefix } : {},
|
|
123
|
-
...readPositiveNumberOption(options, "ttlSeconds") !== void 0 ? { ttlSeconds: readPositiveNumberOption(options, "ttlSeconds") } : {}
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
107
|
function viteConfigHasImageAdapter(root) {
|
|
127
108
|
const configPath = findViteConfigPath(root);
|
|
128
109
|
if (!configPath) return true;
|
|
@@ -178,10 +159,11 @@ function workerEntryHasCacheHandler(root) {
|
|
|
178
159
|
*/
|
|
179
160
|
function formatMissingCacheAdapterError(options) {
|
|
180
161
|
const configRef = options.configFile ? options.configFile : "your Vite config";
|
|
181
|
-
return `[vinext] This app uses ISR / caching but no cache adapter is configured in ${configRef}.\n\n Production deployments need a persistent cache backend. Declare one on the\n vinext() plugin in ${configRef}:\n\n import { kvDataAdapter } from "@vinext/cloudflare/cache/kv-data-adapter";\n\n export default defineConfig({\n plugins: [\n vinext({\n cache: {\n data: kvDataAdapter(), // KV-backed data cache (binding: VINEXT_KV_CACHE)\n },\n }),\n cloudflare(),\n ],\n });\n\n The VINEXT_KV_CACHE namespace binding is added to wrangler.jsonc for you.\n Create the namespace with:\n\n npx wrangler kv namespace create VINEXT_KV_CACHE
|
|
162
|
+
return `[vinext] This app uses ISR / caching but no cache adapter is configured in ${configRef}.\n\n Production deployments need a persistent cache backend. Declare one on the\n vinext() plugin in ${configRef}:\n\n import { kvDataAdapter } from "@vinext/cloudflare/cache/kv-data-adapter";\n\n export default defineConfig({\n plugins: [\n vinext({\n cache: {\n data: kvDataAdapter(), // KV-backed data cache (binding: VINEXT_KV_CACHE)\n },\n }),\n cloudflare(),\n ],\n });\n\n` + (options.typedConfig ? ` Configure the VINEXT_KV_CACHE namespace binding in cloudflare.config.ts.` : " The VINEXT_KV_CACHE namespace binding is added to wrangler.jsonc for you.\n Create the namespace with:\n\n npx wrangler kv namespace create VINEXT_KV_CACHE");
|
|
182
163
|
}
|
|
183
|
-
function formatImageOptimizationHint() {
|
|
164
|
+
function formatImageOptimizationHint(typedConfig = false) {
|
|
165
|
+
if (typedConfig) return " [vinext] next/image is served unoptimized. To enable edge image\n optimization via Cloudflare Images, configure imagesOptimizer() in your\n Vite config and the IMAGES binding in cloudflare.config.ts.";
|
|
184
166
|
return " [vinext] next/image is served unoptimized. To enable edge image\n optimization via Cloudflare Images, run:\n\n vinext init --platform=cloudflare --image-optimization=cloudflare-images\n\n This adds the imagesOptimizer() option to your Vite config and the matching\n IMAGES binding to your Wrangler config without replacing existing settings.";
|
|
185
167
|
}
|
|
186
168
|
//#endregion
|
|
187
|
-
export { formatImageOptimizationHint, formatMissingCacheAdapterError, resolveCdnAdapterConfig,
|
|
169
|
+
export { formatImageOptimizationHint, formatMissingCacheAdapterError, resolveCdnAdapterConfig, viteConfigHasCacheAdapter, viteConfigHasCloudflarePlugin, viteConfigHasImageAdapter, workerEntryHasCacheHandler };
|
package/dist/deploy-help.js
CHANGED
|
@@ -9,76 +9,81 @@ function formatDeployHelp() {
|
|
|
9
9
|
- Detects App Router or Pages Router
|
|
10
10
|
- Validates setup from vinext init --platform=cloudflare
|
|
11
11
|
- Builds the project with Vite
|
|
12
|
-
- Deploys
|
|
12
|
+
- Deploys with cf for typed Cloudflare config projects, or Wrangler otherwise
|
|
13
13
|
|
|
14
14
|
Options:
|
|
15
15
|
--preview Deploy to preview environment (same as --env preview)
|
|
16
|
-
--env <name>
|
|
17
|
-
--name <name> Custom Worker name
|
|
16
|
+
--env <name> Cloudflare mode for typed config; Wrangler environment otherwise
|
|
17
|
+
--name <name> Custom Worker name for Wrangler config projects
|
|
18
18
|
--config <path> Wrangler config path (default: wrangler.jsonc/json/toml)
|
|
19
19
|
--skip-build Skip the build step (use existing dist/)
|
|
20
20
|
--dry-run Validate setup without building or deploying
|
|
21
|
-
--verbose Print raw output from internal
|
|
22
|
-
--
|
|
23
|
-
|
|
21
|
+
--verbose Print raw output from internal Cloudflare CLI commands
|
|
22
|
+
--no-promote Do not promote the uploaded Worker version to 100%
|
|
23
|
+
traffic
|
|
24
|
+
--prerender-all Deprecated for Worker deployments; use
|
|
25
|
+
--warm-cache instead (still honored for static
|
|
26
|
+
export and cache adapters that package local
|
|
27
|
+
prerender output)
|
|
24
28
|
--prerender-concurrency <count>
|
|
25
|
-
Maximum
|
|
26
|
-
--
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
--warm-cdn-target <origin>
|
|
29
|
+
Maximum parallel routes for local prerendering
|
|
30
|
+
--warm-cache Upload a Worker version, warm build-discovered paths
|
|
31
|
+
through the production URL, then promote it
|
|
32
|
+
--warm-cache-target <origin>
|
|
30
33
|
HTTPS origin to use for discovery, probing, and warming
|
|
31
34
|
(overrides URLs inferred from Wrangler output)
|
|
32
|
-
|
|
35
|
+
Also supported with traffic-aware warming
|
|
36
|
+
--warm-cache-concurrency <count>
|
|
33
37
|
Maximum number of CDN warmup requests in parallel (default: 25)
|
|
34
|
-
--warm-
|
|
35
|
-
--warm-
|
|
38
|
+
--warm-cache-timeout <ms> Per-request CDN warmup timeout (default: 10000)
|
|
39
|
+
--warm-cache-retries <n> Retries per failed CDN warmup request (default: 1;
|
|
36
40
|
staged-version propagation default: 60)
|
|
37
|
-
--warm-
|
|
41
|
+
--warm-cache-discovery-timeout <ms>
|
|
38
42
|
Total staged path-discovery deadline (default: 120000)
|
|
39
|
-
--warm-
|
|
43
|
+
--warm-cache-discovery-retries <n>
|
|
40
44
|
Optional staged path-discovery retry limit
|
|
41
45
|
(default: derived from the discovery deadline)
|
|
42
|
-
--warm-
|
|
46
|
+
--warm-cache-probe-timeout <ms>
|
|
43
47
|
Abort when cacheability probing makes no progress for
|
|
44
48
|
this duration (default: 120000)
|
|
45
|
-
--warm-
|
|
49
|
+
--warm-cache-probe-retries <n>
|
|
46
50
|
Cacheability-probe retries (default: 2)
|
|
47
|
-
--warm-
|
|
48
|
-
entries using headers only and require
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
--warm-cache-certify With either warming mode, re-request warmed
|
|
52
|
+
entries using headers only and require reusable hits.
|
|
53
|
+
Traffic-aware warming requires every selected entry
|
|
54
|
+
to be reusable before promotion
|
|
55
|
+
--warm-cache-readiness-timeout <ms>
|
|
51
56
|
Explicit total staged-readiness deadline (default:
|
|
52
57
|
120000)
|
|
53
|
-
--warm-
|
|
58
|
+
--warm-cache-readiness-retries <n>
|
|
54
59
|
Staged-readiness retries (default: 60)
|
|
55
|
-
--warm-
|
|
60
|
+
--warm-cache-readiness-probes <count>
|
|
56
61
|
Consecutive successful staged-readiness probes
|
|
57
62
|
required before warming (default: 6)
|
|
58
|
-
--warm-
|
|
63
|
+
--warm-cache-readiness-probe-delay <ms>
|
|
59
64
|
Delay between staged-readiness probes (default: 1000)
|
|
60
|
-
--dangerously-promote-on-
|
|
65
|
+
--dangerously-promote-on-warm-cache-error
|
|
61
66
|
Promote even when ordinary staged warmup cannot be
|
|
62
|
-
verified (never bypasses --warm-
|
|
63
|
-
--warm-
|
|
64
|
-
production triggers are still applied before warming
|
|
65
|
-
--warm-cdn-promotion-delay <ms>
|
|
67
|
+
verified (never bypasses --warm-cache-certify)
|
|
68
|
+
--warm-cache-promotion-delay <ms>
|
|
66
69
|
Delay before promotion after warmup (default: 15000)
|
|
67
|
-
--warm-
|
|
70
|
+
--warm-cache-include-fallbacks
|
|
68
71
|
Also warm PPR fallback-shell placeholder paths
|
|
69
72
|
-h, --help Show this help
|
|
70
73
|
|
|
71
|
-
|
|
72
|
-
--
|
|
73
|
-
--
|
|
74
|
-
--
|
|
75
|
-
--
|
|
74
|
+
Traffic-aware warming:
|
|
75
|
+
--traffic-aware-warm-cache Select cache warming routes from traffic
|
|
76
|
+
--traffic-aware-coverage <pct> Traffic coverage target, 0-100 (default: 90)
|
|
77
|
+
--traffic-aware-limit <count> Hard cap on selected routes (default: 1000)
|
|
78
|
+
--traffic-aware-window <hours> Analytics lookback window in hours (default: 24)
|
|
76
79
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
a
|
|
81
|
-
|
|
80
|
+
Traffic-aware warming uses Cloudflare zone analytics to select the
|
|
81
|
+
highest-traffic routes, then feeds those routes into the same staged CDN
|
|
82
|
+
pre-warming flow used by --warm-cache. It requires a custom
|
|
83
|
+
domain and a CLOUDFLARE_API_TOKEN with Zone > Analytics > Read and
|
|
84
|
+
Zone > Zone > Read permissions for that zone.
|
|
85
|
+
Use --warm-cache-target <origin> to provide the production HTTPS origin
|
|
86
|
+
manually for traffic-aware analytics selection, discovery, probing, and warming.
|
|
82
87
|
|
|
83
88
|
Workers Cache automatically uses tiered caching. Warmed entries can therefore
|
|
84
89
|
be reused outside the data center reached by the warmup request after cache
|
|
@@ -89,16 +94,22 @@ function formatDeployHelp() {
|
|
|
89
94
|
vpx @vinext/cloudflare deploy Build and deploy with Vite+
|
|
90
95
|
vp exec vinext-cloudflare deploy Run the locally installed Vite+ bin
|
|
91
96
|
vinext-cloudflare deploy --preview Deploy to a preview URL
|
|
92
|
-
vinext-cloudflare deploy --env staging Deploy using
|
|
97
|
+
vinext-cloudflare deploy --env staging Deploy using Cloudflare mode/environment staging
|
|
93
98
|
vinext-cloudflare deploy --config dist/server/wrangler.json Deploy using a generated Wrangler config
|
|
94
99
|
vinext-cloudflare deploy --dry-run Validate setup without building or deploying
|
|
95
100
|
vinext-cloudflare deploy --name my-app Deploy with a custom Worker name
|
|
96
|
-
vinext-cloudflare deploy --
|
|
97
|
-
vinext-cloudflare deploy --
|
|
101
|
+
vinext-cloudflare deploy --no-promote Upload a version without changing deployment traffic
|
|
102
|
+
vinext-cloudflare deploy --warm-cache Warm build-discovered paths during version deploy
|
|
103
|
+
vinext-cloudflare deploy --warm-cache --warm-cache-target https://example.com
|
|
98
104
|
Warm an explicit production origin
|
|
99
|
-
vinext-cloudflare deploy --
|
|
100
|
-
|
|
101
|
-
vinext-cloudflare deploy --
|
|
105
|
+
vinext-cloudflare deploy --traffic-aware-warm-cache
|
|
106
|
+
Enable traffic-aware warming
|
|
107
|
+
vinext-cloudflare deploy --traffic-aware-warm-cache --traffic-aware-coverage 95
|
|
108
|
+
Cover 95% of traffic
|
|
109
|
+
vinext-cloudflare deploy --traffic-aware-warm-cache --traffic-aware-limit 500
|
|
110
|
+
Cap at 500 routes
|
|
111
|
+
vinext-cloudflare deploy --traffic-aware-warm-cache --warm-cache-target https://example.com
|
|
112
|
+
Select and warm traffic routes for an explicit origin
|
|
102
113
|
`;
|
|
103
114
|
}
|
|
104
115
|
function printDeployHelp() {
|
package/dist/deploy.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { CdnWarmOptions, CdnWarmRequestPlan, PrerenderWarmPlan } from "./cdn-warm.js";
|
|
2
|
-
import { parseWranglerConfig } from "./
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { parseWranglerConfig } from "./wrangler-config.js";
|
|
3
|
+
import { TrafficEntry } from "./tpr.js";
|
|
4
|
+
import { WranglerDeploymentStatus, WranglerVersionTraffic, WranglerVersionUploadResult } from "./version-deploy.js";
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
|
-
import { VinextCacheConfig } from "vinext/internal/config/prerender";
|
|
6
|
+
import { VinextCacheConfig, VinextRouteRootConfig } from "vinext/internal/config/prerender";
|
|
7
7
|
import { ProjectInfo } from "vinext/internal/utils/project";
|
|
8
8
|
//#region src/deploy.d.ts
|
|
9
9
|
export declare const DEFAULT_CDN_WARM_PROMOTION_DELAY_MS = 15000;
|
|
@@ -58,21 +58,24 @@ export type DeployOptions = {
|
|
|
58
58
|
warmCdnReadinessProbeDelay?: number;
|
|
59
59
|
/** Promote even when staged CDN warmup cannot be completed */
|
|
60
60
|
dangerouslyPromoteOnCdnWarmError?: boolean;
|
|
61
|
-
/** Promote the
|
|
61
|
+
/** Promote the uploaded Worker version to 100% traffic (default: true) */
|
|
62
62
|
warmCdnPromote?: boolean;
|
|
63
63
|
/** Delay between successful warmup and promotion in milliseconds */
|
|
64
64
|
warmCdnPromotionDelay?: number;
|
|
65
65
|
/** Include PPR fallback-shell placeholder paths during CDN warmup */
|
|
66
66
|
warmCdnIncludeFallbacks?: boolean;
|
|
67
|
-
/**
|
|
67
|
+
/** Select CDN pre-warm routes using traffic analytics */
|
|
68
68
|
experimentalTPR?: boolean;
|
|
69
69
|
/** TPR: traffic coverage percentage target (0–100, default: 90) */
|
|
70
70
|
tprCoverage?: number;
|
|
71
|
-
/** TPR: hard cap on
|
|
71
|
+
/** TPR: hard cap on selected routes (default: 1000) */
|
|
72
72
|
tprLimit?: number;
|
|
73
73
|
/** TPR: analytics lookback window in hours (default: 24) */
|
|
74
74
|
tprWindow?: number;
|
|
75
75
|
};
|
|
76
|
+
export type DeploymentTool = "cf" | "wrangler";
|
|
77
|
+
/** The typed cf Vite plugin emits Worker and asset bundles into Build Output. */
|
|
78
|
+
export declare function resolvePrerenderOutputDirs(root: string, deploymentTool: DeploymentTool, configured: VinextRouteRootConfig | null): VinextRouteRootConfig | null;
|
|
76
79
|
export declare function parseDeployArgs(args: string[]): {
|
|
77
80
|
help: boolean;
|
|
78
81
|
preview: boolean;
|
|
@@ -120,21 +123,20 @@ export declare function parseDeployArgs(args: string[]): {
|
|
|
120
123
|
* Passing `undefined` is a no-op; the callback runs with `process.env` untouched.
|
|
121
124
|
*/
|
|
122
125
|
export declare function withCloudflareEnv<T>(env: string | undefined, fn: () => Promise<T>): Promise<T>;
|
|
126
|
+
export declare function resolveViteBuildMode(deploymentTool: DeploymentTool, env: string | undefined): string;
|
|
123
127
|
type WranglerDeployArgs = {
|
|
124
128
|
args: string[];
|
|
125
129
|
env: string | undefined;
|
|
126
130
|
};
|
|
127
|
-
type
|
|
131
|
+
type CfDeployArgs = {
|
|
128
132
|
args: string[];
|
|
129
|
-
|
|
133
|
+
mode: string | undefined;
|
|
130
134
|
};
|
|
131
135
|
export declare function validateWranglerEnvName(env: string): string;
|
|
132
136
|
export declare function buildWranglerDeployArgs(options: Pick<DeployOptions, "preview" | "env" | "name" | "config">): WranglerDeployArgs;
|
|
133
|
-
export declare function
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
filePath: string;
|
|
137
|
-
}): WranglerKVBulkPutArgs;
|
|
137
|
+
export declare function resolveDeploymentTool(root: string): DeploymentTool;
|
|
138
|
+
export declare function isCfCliInstalled(root: string, resolvePackageJson?: (root: string) => string | null): boolean;
|
|
139
|
+
export declare function buildCfDeployArgs(options: Pick<DeployOptions, "preview" | "env">): CfDeployArgs;
|
|
138
140
|
/**
|
|
139
141
|
* Resolve Wrangler's JavaScript CLI entrypoint in node_modules.
|
|
140
142
|
*
|
|
@@ -142,6 +144,7 @@ export declare function buildWranglerKVBulkPutArgs(options: {
|
|
|
142
144
|
* shim and command shell that package managers create on Windows.
|
|
143
145
|
*/
|
|
144
146
|
export declare function resolveWranglerBin(root: string, resolvePackageJson?: (root: string) => string | null): string;
|
|
147
|
+
export declare function resolveCfBin(root: string, resolvePackageJson?: (root: string) => string | null): string;
|
|
145
148
|
export declare function buildNodeCliInvocation(scriptPath: string, args: string[], nodeExecutable?: string): {
|
|
146
149
|
file: string;
|
|
147
150
|
args: string[];
|
|
@@ -151,30 +154,37 @@ export declare function buildWranglerInvocation(root: string, options: Pick<Depl
|
|
|
151
154
|
args: string[];
|
|
152
155
|
env: string | undefined;
|
|
153
156
|
};
|
|
154
|
-
export declare function
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
tempDir?: string;
|
|
159
|
-
}, execute?: typeof spawn, nodeExecutable?: string): Promise<void>;
|
|
160
|
-
export declare function runWranglerDeploy(root: string, options: Pick<DeployOptions, "preview" | "env" | "name" | "config">, execute?: typeof spawn): Promise<string>;
|
|
157
|
+
export declare function runWranglerDeploy(root: string, options: Pick<DeployOptions, "preview" | "env" | "name" | "config" | "verbose"> & {
|
|
158
|
+
promote?: boolean;
|
|
159
|
+
}, execute?: typeof spawn): Promise<string>;
|
|
160
|
+
export declare function runCfDeploy(root: string, options: Pick<DeployOptions, "preview" | "env">, execute?: typeof spawn): Promise<string>;
|
|
161
161
|
export declare function hasCdnWarmRequests(plan: Omit<CdnWarmRequestPlan, "pagesDataPaths"> & {
|
|
162
162
|
pagesDataPaths?: readonly string[];
|
|
163
163
|
}): boolean;
|
|
164
|
+
export declare function selectTPRWarmPlan(plan: PrerenderWarmPlan, traffic: readonly TrafficEntry[], coverage: number, limit: number): PrerenderWarmPlan;
|
|
164
165
|
export declare function projectRequiresRouteCacheabilityProbeManifest(project: Pick<ProjectInfo, "isAppRouter" | "isPagesRouter">, cacheConfig: VinextCacheConfig | null): boolean;
|
|
165
166
|
type CdnWarmDeployOptions = Pick<DeployOptions, "preview" | "env" | "name" | "config" | "verbose" | "warmCdnTarget" | "warmCdnConcurrency" | "warmCdnTimeout" | "warmCdnRetries" | "warmCdnDiscoveryTimeout" | "warmCdnDiscoveryRetries" | "warmCdnProbeTimeout" | "warmCdnProbeRetries" | "warmCdnCertify" | "warmCdnReadinessTimeout" | "warmCdnReadinessRetries" | "warmCdnReadinessProbes" | "warmCdnReadinessProbeDelay" | "dangerouslyPromoteOnCdnWarmError" | "warmCdnPromote" | "warmCdnPromotionDelay"> & Pick<CdnWarmOptions, "deploymentId" | "expectedBuildId" | "expectedRscBuildId" | "loadingShellPaths" | "pagesDataPaths" | "routeHandlerPaths" | "routePatterns" | "rscPaths" | "statusSource"> & {
|
|
167
|
+
deploymentTool?: DeploymentTool;
|
|
168
|
+
/** Allow optional route selection to discover no warmable requests. */
|
|
169
|
+
allowEmptyWarmPlan?: boolean;
|
|
166
170
|
/** Probe a staged Worker and upload the resulting manifest as a second version. */
|
|
167
171
|
cacheabilityProbe?: boolean;
|
|
168
172
|
discoverWarmPlan?: (target: {
|
|
169
173
|
headers?: HeadersInit;
|
|
170
174
|
targetUrl: string;
|
|
171
175
|
}) => Promise<PrerenderWarmPlan>;
|
|
176
|
+
/** Narrow the final warm requests without changing discovery or probe metadata. */
|
|
177
|
+
selectWarmPlan?: (plan: PrerenderWarmPlan) => PrerenderWarmPlan;
|
|
178
|
+
};
|
|
179
|
+
type DeploymentControlPlaneOptions = Pick<DeployOptions, "preview" | "env" | "name" | "config" | "verbose"> & {
|
|
180
|
+
deploymentTool?: DeploymentTool;
|
|
172
181
|
};
|
|
173
182
|
export declare function deployWithCdnWarmup(root: string, paths: readonly string[], options: CdnWarmDeployOptions): Promise<string>;
|
|
174
183
|
export declare function resolveCdnWarmupTargetUrl(root: string, deployedUrl: string | null): string | null;
|
|
175
184
|
export declare function resolveCdnWarmupTargetUrl(root: string, deployedUrl: string | null, options: Pick<DeployOptions, "preview" | "env" | "config" | "warmCdnTarget">): string | null;
|
|
176
185
|
export declare function getZeroPercentStagingTraffic(deployment: WranglerDeploymentStatus | null, versionId: string): WranglerVersionTraffic[] | null;
|
|
177
186
|
type ParsedWranglerConfig = NonNullable<ReturnType<typeof parseWranglerConfig>>;
|
|
187
|
+
export declare function resolveDeploymentControlPlaneOptions(options: DeploymentControlPlaneOptions, upload: Pick<WranglerVersionUploadResult, "workerName">): DeploymentControlPlaneOptions;
|
|
178
188
|
export declare function resolveWorkerNameForVersionOverride(config: ParsedWranglerConfig | null, options: Pick<DeployOptions, "preview" | "env" | "name">): string | undefined;
|
|
179
189
|
export declare function buildVersionOverrideHeaders(workerName: string | undefined, versionId: string): HeadersInit | undefined;
|
|
180
190
|
export declare function deploy(options: DeployOptions): Promise<void>;
|