@vinext/cloudflare 1.2.1 → 1.3.1
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/dist/cache/app-page-parts.d.ts +7 -0
- package/dist/cache/app-page-parts.js +10 -0
- package/dist/cache/cdn-adapter.runtime.d.ts +1 -0
- package/dist/cache/cdn-adapter.runtime.js +1 -0
- package/dist/cache/cdn-adapter.worker.js +221 -34
- package/dist/cache/kv-data-adapter.runtime.js +24 -10
- package/dist/cache/response-store-adapter.worker.js +168 -55
- package/dist/cache/response-store-cdn.runtime.js +4 -1
- package/dist/cache/response-store-data.runtime.d.ts +14 -0
- package/dist/cache/response-store-data.runtime.js +44 -6
- package/dist/cli.js +2 -0
- package/dist/deploy-config.d.ts +4 -4
- package/dist/deploy-config.js +10 -7
- package/dist/deploy-help.js +5 -3
- package/dist/deploy.d.ts +3 -0
- package/dist/deploy.js +23 -10
- package/dist/previews-deploy.d.ts +1 -0
- package/dist/previews-deploy.js +4 -2
- package/dist/tpr.js +0 -1
- package/dist/utils/cache-control-metadata.d.ts +5 -0
- package/dist/utils/cache-control-metadata.js +9 -1
- package/package.json +4 -4
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { isCanonicalAppPage } from "./app-page-parts.js";
|
|
1
2
|
import { SHARED_RESPONSE_STAGE_HEADER, finalizeGatewayResponse } from "./browser-cache-policy.js";
|
|
2
|
-
import { captureResponseStoreDataRegeneration, runWithResponseStoreInvocation, setResponseStore } from "./response-store-data.runtime.js";
|
|
3
|
-
import { VINEXT_PARAMS_HEADER, VINEXT_RENDERED_PATH_AND_SEARCH_HEADER, VINEXT_SPECIAL_ERROR_STATUS_HEADER } from "vinext/internal/server/headers";
|
|
4
|
-
import { VINEXT_RSC_CONTENT_TYPE, VINEXT_RSC_VARY_HEADER as VINEXT_RSC_VARY_HEADER$1, applyRscCompatibilityIdHeader, applyRscDeploymentIdHeader, createCanonicalRscRequestHeaders, createCanonicalRscRequestUrl } from "vinext/internal/server/app-rsc-cache-busting";
|
|
3
|
+
import { captureResponseStoreDataRegeneration, runWithResponseStoreInvocation, setResponseStore, trackResponseStoreRegenerationWork } from "./response-store-data.runtime.js";
|
|
4
|
+
import { NEXTJS_DEPLOYMENT_ID_HEADER, VINEXT_PARAMS_HEADER, VINEXT_RENDERED_PATH_AND_SEARCH_HEADER, VINEXT_RSC_COMPLETION_METADATA_HEADER, VINEXT_SPECIAL_ERROR_STATUS_HEADER, VINEXT_STALE_TIME_PENDING_HEADER } from "vinext/internal/server/headers";
|
|
5
|
+
import { VINEXT_RSC_BUILD_ID_HEADER, VINEXT_RSC_COMPATIBILITY_ID_HEADER, VINEXT_RSC_CONTENT_TYPE, VINEXT_RSC_VARY_HEADER as VINEXT_RSC_VARY_HEADER$1, applyRscCompatibilityIdHeader, applyRscDeploymentIdHeader, createCanonicalRscRequestHeaders, createCanonicalRscRequestUrl, stripRscCacheBustingSearchParam } from "vinext/internal/server/app-rsc-cache-busting";
|
|
5
6
|
import { hasSharedCacheCredentials, isNonCacheableCacheControl } from "vinext/shims/cdn-cache";
|
|
6
7
|
import { loadVinextRequestStage } from "vinext/server/request-stage";
|
|
7
8
|
import { loadVinextResponseStage } from "vinext/server/response-stage";
|
|
@@ -9,6 +10,8 @@ import { traceCachedResponseStart } from "vinext/internal/server/response-start-
|
|
|
9
10
|
import { isPageNotModified } from "vinext/internal/server/http-conditional";
|
|
10
11
|
//#region src/cache/response-store-adapter.worker.ts
|
|
11
12
|
const ROUTE_REVALIDATOR_ID = "vinext:response";
|
|
13
|
+
/** Leads the revalidator args of a page part whose render stores both parts. */
|
|
14
|
+
const PAGE_PARTS_REVALIDATOR_ARG = "vinext:page-parts";
|
|
12
15
|
const RESPONSE_STORE_KEY_PARAM = "__workers_response_store";
|
|
13
16
|
const AGE_BASIS_HEADER = "X-Workers-Response-Store-Age-Basis";
|
|
14
17
|
const WARMUP_USER_AGENT = "vinext-cloudflare-cdn-warm";
|
|
@@ -22,7 +25,10 @@ function stageContext(ctx, env) {
|
|
|
22
25
|
hostRuntime: "worker",
|
|
23
26
|
passThroughOnException: () => ctx.passThroughOnException(),
|
|
24
27
|
...ctx.props === void 0 ? {} : { props: ctx.props },
|
|
25
|
-
waitUntil: (promise) =>
|
|
28
|
+
waitUntil: (promise) => {
|
|
29
|
+
trackResponseStoreRegenerationWork(promise);
|
|
30
|
+
ctx.waitUntil(promise);
|
|
31
|
+
}
|
|
26
32
|
});
|
|
27
33
|
}
|
|
28
34
|
function safeProps(props) {
|
|
@@ -58,6 +64,15 @@ function prepareInvocation(request, props) {
|
|
|
58
64
|
function serializeInvocation(request, props) {
|
|
59
65
|
return JSON.stringify(prepareInvocation(request, props));
|
|
60
66
|
}
|
|
67
|
+
function serializeDispatchInvocation(invocation) {
|
|
68
|
+
return JSON.stringify({
|
|
69
|
+
...invocation,
|
|
70
|
+
request: {
|
|
71
|
+
...invocation.request,
|
|
72
|
+
headers: [...invocation.request.headers, ["host", new URL(invocation.request.url).host]]
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
61
76
|
function parseInvocation(value) {
|
|
62
77
|
if (typeof value !== "string") throw new TypeError("Invalid vinext response-store invocation");
|
|
63
78
|
const invocation = JSON.parse(value);
|
|
@@ -95,16 +110,29 @@ function createVinextResponseStoreOptions(configuration) {
|
|
|
95
110
|
async regenerate(input, { env, ctx }) {
|
|
96
111
|
if (input.id === ROUTE_REVALIDATOR_ID) {
|
|
97
112
|
const invocation = parseInvocation(input.args.at(-1));
|
|
98
|
-
const
|
|
113
|
+
const request = restoreRequest(invocation);
|
|
114
|
+
const otherPart = input.args.length > 1 && input.args[0] === PAGE_PARTS_REVALIDATOR_ARG ? otherPagePart(request, invocation.props) : void 0;
|
|
115
|
+
const capture = otherPagePartCapture(otherPart);
|
|
116
|
+
const response = await invokeResponseStage(request, invocation.props, env, ctx, {
|
|
99
117
|
cache: "shared",
|
|
100
118
|
regeneratesStoredEntry: true,
|
|
101
119
|
renderWholeDocument: true
|
|
102
|
-
});
|
|
120
|
+
}, capture);
|
|
103
121
|
if (!isCacheable(response)) {
|
|
104
122
|
await response.body?.cancel().catch(() => {});
|
|
123
|
+
capture.rscData?.catch(() => {});
|
|
105
124
|
throw new Error("Vinext response-stage regeneration was not cacheable");
|
|
106
125
|
}
|
|
107
|
-
|
|
126
|
+
const stored = withoutRequestScopedHeaders(response, invocation.props);
|
|
127
|
+
try {
|
|
128
|
+
await putOtherPagePart(otherPart, capture, stored);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error(JSON.stringify({
|
|
131
|
+
message: "Vinext response-store regeneration failed to store the page's other part",
|
|
132
|
+
error: error instanceof Error ? error.message : String(error)
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
return stored;
|
|
108
136
|
}
|
|
109
137
|
if (input.id === "vinext:cache-function" && typeof input.args[0] === "string" && typeof input.args[1] === "string") {
|
|
110
138
|
const invocation = JSON.parse(input.args[1]);
|
|
@@ -137,7 +165,8 @@ async function cacheRequest(invocation) {
|
|
|
137
165
|
url.searchParams.set(RESPONSE_STORE_KEY_PARAM, `v1.${[...digest].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`);
|
|
138
166
|
return new Request(url, {
|
|
139
167
|
headers: CACHE_REQUEST_VARY_HEADERS,
|
|
140
|
-
method: "GET"
|
|
168
|
+
method: "GET",
|
|
169
|
+
redirect: "manual"
|
|
141
170
|
});
|
|
142
171
|
}
|
|
143
172
|
/**
|
|
@@ -157,6 +186,116 @@ function withoutRequestScopedHeaders(response, responseStageProps) {
|
|
|
157
186
|
statusText: response.statusText
|
|
158
187
|
});
|
|
159
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* The revalidator of a page part. One whose render stores the page's other
|
|
191
|
+
* part says so, as a public config policy's full-URL entry stores its own.
|
|
192
|
+
*/
|
|
193
|
+
function routeRevalidator(serializedInvocation, storesOtherPart) {
|
|
194
|
+
return {
|
|
195
|
+
id: ROUTE_REVALIDATOR_ID,
|
|
196
|
+
args: storesOtherPart ? [PAGE_PARTS_REVALIDATOR_ARG, serializedInvocation] : [serializedInvocation]
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/** The invocation an RSC navigation to an HTML invocation's page stores under. */
|
|
200
|
+
function rscPartInvocation(htmlRequest, htmlProps) {
|
|
201
|
+
return prepareInvocation(new Request(new URL(createCanonicalRscRequestUrl(htmlRequest.url), htmlRequest.url), { headers: createCanonicalRscRequestHeaders() }), {
|
|
202
|
+
...htmlProps,
|
|
203
|
+
isRscRequest: true,
|
|
204
|
+
renderMode: "navigation"
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
/** The invocation an HTML request for an RSC invocation's page stores under. */
|
|
208
|
+
function documentPartInvocation(rscRequest, rscProps) {
|
|
209
|
+
const url = new URL(rscRequest.url);
|
|
210
|
+
stripRscCacheBustingSearchParam(url);
|
|
211
|
+
return prepareInvocation(new Request(url), {
|
|
212
|
+
...rscProps,
|
|
213
|
+
isRscRequest: false
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* The page's RSC payload an HTML render produced, with its document's headers
|
|
218
|
+
* and status.
|
|
219
|
+
*/
|
|
220
|
+
function rscPartResponse(htmlResponse, rscData) {
|
|
221
|
+
const headers = new Headers(htmlResponse.headers);
|
|
222
|
+
headers.delete("Content-Length");
|
|
223
|
+
headers.delete("Link");
|
|
224
|
+
headers.delete("X-Vinext-Response-Store-Replayable");
|
|
225
|
+
headers.delete(VINEXT_PARAMS_HEADER);
|
|
226
|
+
headers.delete(VINEXT_RENDERED_PATH_AND_SEARCH_HEADER);
|
|
227
|
+
headers.set("Content-Type", VINEXT_RSC_CONTENT_TYPE);
|
|
228
|
+
headers.set("Vary", VINEXT_RSC_VARY_HEADER$1);
|
|
229
|
+
applyRscCompatibilityIdHeader(headers);
|
|
230
|
+
applyRscDeploymentIdHeader(headers);
|
|
231
|
+
if (isStoredErrorStatus(htmlResponse.status)) headers.set(VINEXT_SPECIAL_ERROR_STATUS_HEADER, String(htmlResponse.status));
|
|
232
|
+
return new Response(rscData, {
|
|
233
|
+
headers,
|
|
234
|
+
status: htmlResponse.status >= 300 && htmlResponse.status < 400 ? 200 : htmlResponse.status
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* The page's document an RSC render produced, with the headers an HTML request
|
|
239
|
+
* for the page stores: the RSC response's, without the payload's own.
|
|
240
|
+
*/
|
|
241
|
+
function documentPartResponse(rscResponse, document) {
|
|
242
|
+
const headers = new Headers(rscResponse.headers);
|
|
243
|
+
for (const name of [
|
|
244
|
+
"Content-Length",
|
|
245
|
+
NEXTJS_DEPLOYMENT_ID_HEADER,
|
|
246
|
+
VINEXT_RSC_BUILD_ID_HEADER,
|
|
247
|
+
VINEXT_RSC_COMPATIBILITY_ID_HEADER,
|
|
248
|
+
VINEXT_RSC_COMPLETION_METADATA_HEADER,
|
|
249
|
+
VINEXT_STALE_TIME_PENDING_HEADER
|
|
250
|
+
]) headers.delete(name);
|
|
251
|
+
headers.set("Content-Type", "text/html; charset=utf-8");
|
|
252
|
+
if (document.linkHeader) headers.append("Link", document.linkHeader);
|
|
253
|
+
return new Response(document.html, {
|
|
254
|
+
headers,
|
|
255
|
+
status: rscResponse.status
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* The other part of the page a canonical App page render stores. Next.js
|
|
260
|
+
* keeps a page's document and RSC payload in one entry, so a render that
|
|
261
|
+
* stores one part also stores the other, under the key a request for that
|
|
262
|
+
* part reads. A variant has no other part.
|
|
263
|
+
*/
|
|
264
|
+
function otherPagePart(identityRequest, identityProps) {
|
|
265
|
+
if (isCanonicalAppPage(identityProps, false)) return {
|
|
266
|
+
invocation: rscPartInvocation(identityRequest, identityProps),
|
|
267
|
+
kind: "rsc"
|
|
268
|
+
};
|
|
269
|
+
if (isCanonicalAppPage(identityProps, true)) return {
|
|
270
|
+
invocation: documentPartInvocation(identityRequest, identityProps),
|
|
271
|
+
kind: "document"
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
/** What a render captures for the other part of its page. */
|
|
275
|
+
function otherPagePartCapture(part) {
|
|
276
|
+
if (part?.kind === "rsc") return { captureRscData: true };
|
|
277
|
+
if (part?.kind === "document") return { captureDocument: true };
|
|
278
|
+
return {};
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Store the other part of the page from the render that stores this one.
|
|
282
|
+
* Admission settles the document before it completes the response. A miss
|
|
283
|
+
* stores it first, so a failed write leaves the render's own part missing too
|
|
284
|
+
* and the next miss retries both. A regeneration's other part stays stale
|
|
285
|
+
* when it fails, so the next read of that part regenerates it. Concurrent
|
|
286
|
+
* misses coalesce each key on its own, so their parts can come from different
|
|
287
|
+
* renders of the page.
|
|
288
|
+
*/
|
|
289
|
+
async function putOtherPagePart(part, capture, stored) {
|
|
290
|
+
let response;
|
|
291
|
+
if (part?.kind === "rsc" && capture.rscData) response = rscPartResponse(stored, await capture.rscData);
|
|
292
|
+
else if (part?.kind === "document" && capture.document) response = documentPartResponse(stored, capture.document);
|
|
293
|
+
else return;
|
|
294
|
+
if (!(await responseStore.put(await cacheRequest(part.invocation), response, {
|
|
295
|
+
coalesce: true,
|
|
296
|
+
revalidator: routeRevalidator(JSON.stringify(part.invocation), true)
|
|
297
|
+
})).backingStoreUpdated) throw new Error(`Vinext response-store did not publish the App page ${part.kind} part`);
|
|
298
|
+
}
|
|
160
299
|
function isStoredErrorStatus(status) {
|
|
161
300
|
return status === 401 || status === 403 || status === 404;
|
|
162
301
|
}
|
|
@@ -275,17 +414,9 @@ const handler = { async fetch(request, env, ctx) {
|
|
|
275
414
|
const identityRequest = options.cacheIdentity?.request ?? stageRequest;
|
|
276
415
|
const identityProps = options.cacheIdentity?.props ?? props;
|
|
277
416
|
const isWarmup = request.headers.get("user-agent") === WARMUP_USER_AGENT;
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
...identityProps,
|
|
281
|
-
isRscRequest: true,
|
|
282
|
-
renderMode: "navigation"
|
|
283
|
-
},
|
|
284
|
-
request: new Request(new URL(createCanonicalRscRequestUrl(identityRequest.url), identityRequest.url), { headers: createCanonicalRscRequestHeaders() })
|
|
285
|
-
} : void 0;
|
|
417
|
+
const otherPart = isWarmup || options.cacheIdentity?.queryFree ? otherPagePart(identityRequest, identityProps) : void 0;
|
|
418
|
+
const rscKey = isWarmup && otherPart?.kind === "rsc" ? await cacheRequest(otherPart.invocation) : void 0;
|
|
286
419
|
const invocation = prepareInvocation(identityRequest, identityProps);
|
|
287
|
-
const rscInvocation = rscSeed ? prepareInvocation(rscSeed.request, rscSeed.props) : void 0;
|
|
288
|
-
const rscKey = rscInvocation ? await cacheRequest(rscInvocation) : void 0;
|
|
289
420
|
const key = await cacheRequest(invocation);
|
|
290
421
|
const stored = await readStoredResponse(key);
|
|
291
422
|
if (stored) {
|
|
@@ -297,8 +428,12 @@ const handler = { async fetch(request, env, ctx) {
|
|
|
297
428
|
}
|
|
298
429
|
stored.body?.cancel().catch(() => {});
|
|
299
430
|
}
|
|
300
|
-
const capture =
|
|
431
|
+
const capture = {
|
|
432
|
+
...isWarmup ? {} : { streamResponse: true },
|
|
433
|
+
...otherPagePartCapture(otherPart)
|
|
434
|
+
};
|
|
301
435
|
const serializedInvocation = JSON.stringify(invocation);
|
|
436
|
+
const revalidatorInvocation = !options.cacheIdentity ? serializeDispatchInvocation(invocation) : options.cacheIdentity.regenerateFromDispatch ? serializeDispatchInvocation(prepareInvocation(stageRequest, props)) : serializedInvocation;
|
|
302
437
|
const rendered = await invokeResponseStage(stageRequest, props, env, ctx, isWarmup ? {
|
|
303
438
|
cache: "shared",
|
|
304
439
|
renderWholeDocument: true
|
|
@@ -311,14 +446,19 @@ const handler = { async fetch(request, env, ctx) {
|
|
|
311
446
|
ctx.waitUntil(capture.admittedResponse.then(async (admitted) => {
|
|
312
447
|
if (!isCacheable(admitted)) {
|
|
313
448
|
await admitted.body?.cancel().catch(() => {});
|
|
449
|
+
capture.rscData?.catch(() => {});
|
|
314
450
|
return;
|
|
315
451
|
}
|
|
316
|
-
|
|
452
|
+
const stored = withoutRequestScopedHeaders(admitted, props);
|
|
453
|
+
try {
|
|
454
|
+
await putOtherPagePart(otherPart, capture, stored);
|
|
455
|
+
} catch (error) {
|
|
456
|
+
await stored.body?.cancel().catch(() => {});
|
|
457
|
+
throw error;
|
|
458
|
+
}
|
|
459
|
+
await responseStore.put(key, stored, {
|
|
317
460
|
coalesce: true,
|
|
318
|
-
revalidator:
|
|
319
|
-
id: ROUTE_REVALIDATOR_ID,
|
|
320
|
-
args: [serializedInvocation]
|
|
321
|
-
}
|
|
461
|
+
revalidator: routeRevalidator(revalidatorInvocation, otherPart !== void 0)
|
|
322
462
|
});
|
|
323
463
|
}).catch((error) => {
|
|
324
464
|
console.error(JSON.stringify({
|
|
@@ -335,22 +475,20 @@ const handler = { async fetch(request, env, ctx) {
|
|
|
335
475
|
}), "MISS", props) : publicResponse(notModified, "MISS", props, true);
|
|
336
476
|
}
|
|
337
477
|
if (!isCacheable(rendered)) {
|
|
338
|
-
capture
|
|
478
|
+
capture.rscData?.catch(() => {});
|
|
339
479
|
return publicResponse(foreground(rendered), "BYPASS", props);
|
|
340
480
|
}
|
|
341
|
-
if (
|
|
481
|
+
if (rscKey && !capture.rscData) {
|
|
342
482
|
await rendered.body?.cancel();
|
|
343
483
|
throw new Error("Vinext response-store warmup did not capture the App page RSC payload");
|
|
344
484
|
}
|
|
345
485
|
const [foregroundBody, cacheBody] = rendered.body ? rendered.body.tee() : [null, null];
|
|
346
486
|
const cacheResponse = withoutRequestScopedHeaders(new Response(cacheBody, rendered), props);
|
|
347
487
|
try {
|
|
488
|
+
await putOtherPagePart(otherPart, capture, cacheResponse);
|
|
348
489
|
await responseStore.put(key, cacheResponse, {
|
|
349
490
|
coalesce: true,
|
|
350
|
-
revalidator:
|
|
351
|
-
id: ROUTE_REVALIDATOR_ID,
|
|
352
|
-
args: [serializedInvocation]
|
|
353
|
-
}
|
|
491
|
+
revalidator: routeRevalidator(revalidatorInvocation, otherPart !== void 0)
|
|
354
492
|
});
|
|
355
493
|
} catch (error) {
|
|
356
494
|
if (isWarmup) throw error;
|
|
@@ -360,31 +498,6 @@ const handler = { async fetch(request, env, ctx) {
|
|
|
360
498
|
error: error instanceof Error ? error.message : String(error)
|
|
361
499
|
}));
|
|
362
500
|
}
|
|
363
|
-
if (rscSeed && rscInvocation && rscKey && capture?.rscData) {
|
|
364
|
-
const rscData = await capture.rscData;
|
|
365
|
-
const rscHeaders = new Headers(rendered.headers);
|
|
366
|
-
rscHeaders.delete("Content-Length");
|
|
367
|
-
rscHeaders.delete("Link");
|
|
368
|
-
rscHeaders.delete("X-Vinext-Response-Store-Replayable");
|
|
369
|
-
rscHeaders.delete(VINEXT_PARAMS_HEADER);
|
|
370
|
-
rscHeaders.delete(VINEXT_RENDERED_PATH_AND_SEARCH_HEADER);
|
|
371
|
-
rscHeaders.set("Content-Type", VINEXT_RSC_CONTENT_TYPE);
|
|
372
|
-
rscHeaders.set("Vary", VINEXT_RSC_VARY_HEADER$1);
|
|
373
|
-
applyRscCompatibilityIdHeader(rscHeaders);
|
|
374
|
-
applyRscDeploymentIdHeader(rscHeaders);
|
|
375
|
-
if (isStoredErrorStatus(rendered.status)) rscHeaders.set(VINEXT_SPECIAL_ERROR_STATUS_HEADER, String(rendered.status));
|
|
376
|
-
const serializedRscInvocation = JSON.stringify(rscInvocation);
|
|
377
|
-
await responseStore.put(rscKey, new Response(rscData, {
|
|
378
|
-
headers: rscHeaders,
|
|
379
|
-
status: rendered.status >= 300 && rendered.status < 400 ? 200 : rendered.status
|
|
380
|
-
}), {
|
|
381
|
-
coalesce: true,
|
|
382
|
-
revalidator: {
|
|
383
|
-
id: ROUTE_REVALIDATOR_ID,
|
|
384
|
-
args: [serializedRscInvocation]
|
|
385
|
-
}
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
501
|
return publicResponse(foreground(new Response(foregroundBody, rendered)), "MISS", props);
|
|
389
502
|
};
|
|
390
503
|
const { handleRequestStage } = await loadVinextRequestStage();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import createCloudflareCdnCacheAdapter from "./cdn-adapter.runtime.js";
|
|
2
|
-
import { captureResponseStoreRscData, deferResponseStoreAdmission, deferResponseStoreNotModified } from "./response-store-data.runtime.js";
|
|
2
|
+
import { captureResponseStoreAppPageDocument, captureResponseStoreRscData, deferResponseStoreAdmission, deferResponseStoreNotModified } from "./response-store-data.runtime.js";
|
|
3
3
|
//#region src/cache/response-store-cdn.runtime.ts
|
|
4
4
|
/** Response Store owns page serving and SWR; vinext only emits admitted response policy. */
|
|
5
5
|
var ResponseStoreCdnCacheAdapter = class {
|
|
@@ -33,6 +33,9 @@ var ResponseStoreCdnCacheAdapter = class {
|
|
|
33
33
|
captureAppPageRscData(rscData) {
|
|
34
34
|
captureResponseStoreRscData(rscData);
|
|
35
35
|
}
|
|
36
|
+
captureAppPageDocument() {
|
|
37
|
+
return captureResponseStoreAppPageDocument();
|
|
38
|
+
}
|
|
36
39
|
deferNotModifiedResponse() {
|
|
37
40
|
return deferResponseStoreNotModified();
|
|
38
41
|
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { CdnAppPageDocument } from "vinext/shims/cdn-cache";
|
|
1
2
|
import { WorkersResponseStore } from "@cloudflare/workers-response-store";
|
|
2
3
|
import { CacheHandler, CacheHandlerValue, IncrementalCacheValue } from "vinext/shims/cache";
|
|
3
4
|
//#region src/cache/response-store-data.runtime.d.ts
|
|
4
5
|
export type ResponseStoreInvocationCapture = {
|
|
5
6
|
admittedResponse?: Promise<Response>;
|
|
7
|
+
/** Store the page's document beside an App page RSC response. */
|
|
8
|
+
captureDocument?: boolean;
|
|
6
9
|
captureRscData?: boolean;
|
|
10
|
+
/** The page's document, set before admission completes the response. */
|
|
11
|
+
document?: CdnAppPageDocument;
|
|
7
12
|
/** The render matched the request's If-None-Match; the adapter sends the 304. */
|
|
8
13
|
notModified?: boolean;
|
|
9
14
|
rscData?: Promise<ArrayBuffer>;
|
|
@@ -17,15 +22,24 @@ export declare function setResponseStore(store: WorkersResponseStore): void;
|
|
|
17
22
|
export declare function runWithResponseStoreInvocation<T>(serialized: string, replayable: boolean, callback: () => T, capture?: ResponseStoreInvocationCapture): T;
|
|
18
23
|
/** Retain the RSC side stream only when the outer response-store invocation requested it. */
|
|
19
24
|
export declare function captureResponseStoreRscData(rscData: Promise<ArrayBuffer>): void;
|
|
25
|
+
/**
|
|
26
|
+
* Retain the page's document only when the outer response-store invocation
|
|
27
|
+
* requested it. Admission settles it before it completes the response.
|
|
28
|
+
*/
|
|
29
|
+
export declare function captureResponseStoreAppPageDocument(): ((document: CdnAppPageDocument | null) => void) | undefined;
|
|
20
30
|
/** A shared invocation stores the page, so its adapter sends the page's 304. */
|
|
21
31
|
export declare function deferResponseStoreNotModified(): boolean;
|
|
22
32
|
/** Stream the foreground body while retaining an independent admission branch. */
|
|
23
33
|
export declare function deferResponseStoreAdmission(response: Response, complete: (response: Response) => Promise<Response>): Response | null;
|
|
34
|
+
/** Keeps work a regeneration hands to waitUntil inside the regeneration. */
|
|
35
|
+
export declare function trackResponseStoreRegenerationWork(work: Promise<unknown>): void;
|
|
24
36
|
/** Re-render an invocation and return the exact rewritten data-cache entry. */
|
|
25
37
|
export declare function captureResponseStoreDataRegeneration(key: string, callback: () => Promise<void>): Promise<Response>;
|
|
26
38
|
export declare class WorkersResponseStoreCacheHandler implements CacheHandler {
|
|
27
39
|
private readonly store;
|
|
28
40
|
private readonly tagExpirations;
|
|
41
|
+
/** The revision behind each entry returned stale by the Store, for the write refreshing it. */
|
|
42
|
+
private readonly staleRevisions;
|
|
29
43
|
constructor(store?: WorkersResponseStore);
|
|
30
44
|
get(key: string, context?: Record<string, unknown>): Promise<CacheHandlerValue | null>;
|
|
31
45
|
set(key: string, value: IncrementalCacheValue | null, context?: Record<string, unknown>): Promise<void>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isStaleTagInvalidation } from "./workers-cache-invalidation.js";
|
|
2
2
|
import { encodeCloudflareCacheTag } from "./cdn-adapter.runtime.js";
|
|
3
|
+
import { readCacheTimestamp } from "../utils/cache-control-metadata.js";
|
|
3
4
|
import { Buffer } from "node:buffer";
|
|
4
5
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
5
6
|
import { cacheForRequest } from "vinext/cache";
|
|
@@ -8,6 +9,8 @@ const ARRAY_BUFFER_MARKER = "$vinextArrayBuffer";
|
|
|
8
9
|
const CACHE_MAX_AGE_SECONDS = 31536e4;
|
|
9
10
|
const DATA_ENTRY_PATH = "__vinext_data";
|
|
10
11
|
const REPLAYABLE_HEADER = "X-Vinext-Response-Store-Replayable";
|
|
12
|
+
/** The Store's internal header for when a response's generation started. */
|
|
13
|
+
const GENERATED_AT_HEADER = "X-Workers-Response-Store-Generated-At";
|
|
11
14
|
const MAX_CACHE_TAG_HEADER_BYTES = 16384;
|
|
12
15
|
/** How long past its revalidate a current Store's hit can still be marked fresh: write latency. */
|
|
13
16
|
const STALE_FRESH_HIT_GRACE_MS = 5e3;
|
|
@@ -34,6 +37,17 @@ function captureResponseStoreRscData(rscData) {
|
|
|
34
37
|
if (capture?.captureRscData) capture.rscData = rscData;
|
|
35
38
|
else rscData.catch(() => {});
|
|
36
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Retain the page's document only when the outer response-store invocation
|
|
42
|
+
* requested it. Admission settles it before it completes the response.
|
|
43
|
+
*/
|
|
44
|
+
function captureResponseStoreAppPageDocument() {
|
|
45
|
+
const capture = invocationStorage.getStore()?.capture;
|
|
46
|
+
if (!capture?.captureDocument) return void 0;
|
|
47
|
+
return (document) => {
|
|
48
|
+
capture.document = document ?? void 0;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
37
51
|
/** A shared invocation stores the page, so its adapter sends the page's 304. */
|
|
38
52
|
function deferResponseStoreNotModified() {
|
|
39
53
|
const capture = invocationStorage.getStore()?.capture;
|
|
@@ -143,6 +157,10 @@ function deferResponseStoreAdmission(response, complete) {
|
|
|
143
157
|
writer.closed.catch((error) => finishAdmission({ reason: error }));
|
|
144
158
|
return new Response(foreground, response);
|
|
145
159
|
}
|
|
160
|
+
/** Keeps work a regeneration hands to waitUntil inside the regeneration. */
|
|
161
|
+
function trackResponseStoreRegenerationWork(work) {
|
|
162
|
+
regenerationStorage.getStore()?.sideWrites.push(work.then(() => void 0, () => void 0));
|
|
163
|
+
}
|
|
146
164
|
/** Re-render an invocation and return the exact rewritten data-cache entry. */
|
|
147
165
|
async function captureResponseStoreDataRegeneration(key, callback) {
|
|
148
166
|
const scope = {
|
|
@@ -202,6 +220,8 @@ function cachePolicy(revalidate, expire) {
|
|
|
202
220
|
var WorkersResponseStoreCacheHandler = class {
|
|
203
221
|
store;
|
|
204
222
|
tagExpirations = cacheForRequest(() => /* @__PURE__ */ new Map());
|
|
223
|
+
/** The revision behind each entry returned stale by the Store, for the write refreshing it. */
|
|
224
|
+
staleRevisions = /* @__PURE__ */ new WeakMap();
|
|
205
225
|
constructor(store = responseStore) {
|
|
206
226
|
this.store = store;
|
|
207
227
|
if (!store) throw new Error("[vinext] The Workers Response Store adapter must run through its generated Cloudflare worker entry.");
|
|
@@ -238,10 +258,15 @@ var WorkersResponseStoreCacheHandler = class {
|
|
|
238
258
|
await this.store.purge({ pathPrefixes: [new URL(request.url).pathname] });
|
|
239
259
|
return null;
|
|
240
260
|
}
|
|
261
|
+
let tagExpiration;
|
|
241
262
|
if (softTags.length) {
|
|
242
263
|
const expiration = eagerExpiration ? await eagerExpiration : { value: await getTagExpiration() };
|
|
243
264
|
if ("error" in expiration) throw expiration.error;
|
|
244
265
|
if (expiration.value >= entry.lastModified) return null;
|
|
266
|
+
tagExpiration = {
|
|
267
|
+
tags: softTags,
|
|
268
|
+
expiration: expiration.value
|
|
269
|
+
};
|
|
245
270
|
}
|
|
246
271
|
const age = Date.now() - entry.lastModified;
|
|
247
272
|
const requestedRevalidate = readCacheControlField(context, "revalidate");
|
|
@@ -252,12 +277,18 @@ var WorkersResponseStoreCacheHandler = class {
|
|
|
252
277
|
if (requestedStale && storeStatus !== "BLOB-STALE") cacheState = "stale";
|
|
253
278
|
} else if (typeof entry.cacheControl?.expire === "number" && age > entry.cacheControl.expire * 1e3) cacheState = "expired";
|
|
254
279
|
else if (storeStatus === "BLOB-STALE" || requestedStale && typeof requestedRevalidate === "number" && (typeof storedRevalidate !== "number" || requestedRevalidate < storedRevalidate) || typeof storedRevalidate === "number" && storedRevalidate > 0 && age > storedRevalidate * 1e3 + STALE_FRESH_HIT_GRACE_MS) cacheState = "stale";
|
|
255
|
-
|
|
280
|
+
const value = {
|
|
256
281
|
lastModified: entry.lastModified,
|
|
257
282
|
value: entry.value,
|
|
258
283
|
...cacheState ? { cacheState } : {},
|
|
259
284
|
...entry.cacheControl ? { cacheControl: entry.cacheControl } : {}
|
|
260
285
|
};
|
|
286
|
+
const revision = Number(response.headers.get("X-Workers-Response-Store-Revision"));
|
|
287
|
+
if (cacheState && storeStatus === "BLOB-STALE" && Number.isSafeInteger(revision) && revision > 0) this.staleRevisions.set(value, {
|
|
288
|
+
revision,
|
|
289
|
+
...tagExpiration ? { tagExpiration } : {}
|
|
290
|
+
});
|
|
291
|
+
return value;
|
|
261
292
|
} catch (error) {
|
|
262
293
|
console.error(JSON.stringify({
|
|
263
294
|
message: "Vinext response-store data lookup failed; treating as a cache miss",
|
|
@@ -268,8 +299,7 @@ var WorkersResponseStoreCacheHandler = class {
|
|
|
268
299
|
}
|
|
269
300
|
set(key, value, context) {
|
|
270
301
|
const write = this.write(key, value, context);
|
|
271
|
-
|
|
272
|
-
if (regeneration && regeneration.targetKey !== key) regeneration.sideWrites.push(write);
|
|
302
|
+
regenerationStorage.getStore()?.sideWrites.push(write);
|
|
273
303
|
return write;
|
|
274
304
|
}
|
|
275
305
|
async write(key, value, context) {
|
|
@@ -302,11 +332,13 @@ var WorkersResponseStoreCacheHandler = class {
|
|
|
302
332
|
id: DATA_REVALIDATOR_ID,
|
|
303
333
|
args: [key, invocation.serialized]
|
|
304
334
|
} : void 0;
|
|
335
|
+
const lastModified = readCacheTimestamp(context, Date.now());
|
|
305
336
|
const response = new Response(serialize({
|
|
306
337
|
...cacheControl ? { cacheControl } : {},
|
|
307
|
-
lastModified
|
|
338
|
+
lastModified,
|
|
308
339
|
value
|
|
309
340
|
}), { headers: {
|
|
341
|
+
...lastModified === context?.timestamp ? { [GENERATED_AT_HEADER]: String(lastModified) } : {},
|
|
310
342
|
"Cache-Control": cachePolicy(revalidate, expire),
|
|
311
343
|
...tagHeader ? { "Cache-Tag": tagHeader } : {},
|
|
312
344
|
"Content-Type": "application/json",
|
|
@@ -318,11 +350,17 @@ var WorkersResponseStoreCacheHandler = class {
|
|
|
318
350
|
return;
|
|
319
351
|
}
|
|
320
352
|
if (regeneration && !revalidator) return;
|
|
353
|
+
const staleEntry = context?.staleEntry;
|
|
354
|
+
const replaces = staleEntry && typeof staleEntry === "object" ? this.staleRevisions.get(staleEntry) : void 0;
|
|
321
355
|
await this.store.put(await cacheRequest(key), response, {
|
|
322
356
|
...revalidator ? { revalidator } : {},
|
|
323
357
|
...revalidator?.id !== "vinext:cache-function" ? { expiryBehavior: "miss" } : {},
|
|
324
358
|
coalesce: true,
|
|
325
|
-
purgeExisting: true
|
|
359
|
+
purgeExisting: true,
|
|
360
|
+
...replaces ? {
|
|
361
|
+
replacesRevision: replaces.revision,
|
|
362
|
+
...replaces.tagExpiration ? { replacesTagExpiration: replaces.tagExpiration } : {}
|
|
363
|
+
} : {}
|
|
326
364
|
});
|
|
327
365
|
}
|
|
328
366
|
async revalidateTag(tags, durations) {
|
|
@@ -339,4 +377,4 @@ function createResponseStoreDataCacheAdapter() {
|
|
|
339
377
|
return new WorkersResponseStoreCacheHandler();
|
|
340
378
|
}
|
|
341
379
|
//#endregion
|
|
342
|
-
export { CACHE_FUNCTION_REVALIDATOR_ID, DATA_REVALIDATOR_ID, WorkersResponseStoreCacheHandler, captureResponseStoreDataRegeneration, captureResponseStoreRscData, createResponseStoreDataCacheAdapter as default, deferResponseStoreAdmission, deferResponseStoreNotModified, runWithResponseStoreInvocation, setResponseStore };
|
|
380
|
+
export { CACHE_FUNCTION_REVALIDATOR_ID, DATA_REVALIDATOR_ID, WorkersResponseStoreCacheHandler, captureResponseStoreAppPageDocument, captureResponseStoreDataRegeneration, captureResponseStoreRscData, createResponseStoreDataCacheAdapter as default, deferResponseStoreAdmission, deferResponseStoreNotModified, runWithResponseStoreInvocation, setResponseStore, trackResponseStoreRegenerationWork };
|
package/dist/cli.js
CHANGED
|
@@ -45,6 +45,7 @@ async function deployCommand() {
|
|
|
45
45
|
preview: parsed.preview,
|
|
46
46
|
env: parsed.env,
|
|
47
47
|
config: parsed.config,
|
|
48
|
+
viteConfig: parsed.viteConfig,
|
|
48
49
|
skipBuild: parsed.skipBuild,
|
|
49
50
|
dryRun: parsed.dryRun,
|
|
50
51
|
verbose: parsed.verbose,
|
|
@@ -92,6 +93,7 @@ async function previewsCommand() {
|
|
|
92
93
|
root: process.cwd(),
|
|
93
94
|
env: options.mode,
|
|
94
95
|
skipBuild: options.skipBuild,
|
|
96
|
+
viteConfig: options.viteConfig,
|
|
95
97
|
target: {
|
|
96
98
|
type: "preview",
|
|
97
99
|
name: options.name
|
package/dist/deploy-config.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { VinextCacheConfig } from "vinext/internal/config/prerender";
|
|
|
8
8
|
* Returns true when the config both imports the package and invokes
|
|
9
9
|
* `cloudflare(...)` in the plugin list.
|
|
10
10
|
*/
|
|
11
|
-
export declare function viteConfigHasCloudflarePlugin(root: string): boolean;
|
|
11
|
+
export declare function viteConfigHasCloudflarePlugin(root: string, configFile?: 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,12 +17,12 @@ export 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
|
-
export declare function viteConfigHasCacheAdapter(root: string): boolean;
|
|
20
|
+
export declare function viteConfigHasCacheAdapter(root: string, configFile?: string): boolean;
|
|
21
21
|
export type ResolvedCdnAdapterConfig = {
|
|
22
22
|
versionMetadataBinding: string;
|
|
23
23
|
};
|
|
24
24
|
export declare function resolveCdnAdapterConfig(cache: VinextCacheConfig | null | undefined): ResolvedCdnAdapterConfig | null;
|
|
25
|
-
export declare function viteConfigHasImageAdapter(root: string): boolean;
|
|
25
|
+
export declare function viteConfigHasImageAdapter(root: string, configFile?: string): boolean;
|
|
26
26
|
/**
|
|
27
27
|
* Detect whether an existing user-authored Worker entry wires up a cache
|
|
28
28
|
* backend imperatively via one of the `setCacheHandler` / `setDataCacheHandler`
|
|
@@ -48,5 +48,5 @@ export declare function formatMissingCacheAdapterError(options: {
|
|
|
48
48
|
configFile?: string;
|
|
49
49
|
typedConfig?: boolean;
|
|
50
50
|
}): string;
|
|
51
|
-
export declare function formatImageOptimizationHint(typedConfig?: boolean): string;
|
|
51
|
+
export declare function formatImageOptimizationHint(typedConfig?: boolean, configFile?: string): string;
|
|
52
52
|
//#endregion
|
package/dist/deploy-config.js
CHANGED
|
@@ -15,8 +15,8 @@ function escapeRegExp(value) {
|
|
|
15
15
|
* Returns true when the config both imports the package and invokes
|
|
16
16
|
* `cloudflare(...)` in the plugin list.
|
|
17
17
|
*/
|
|
18
|
-
function viteConfigHasCloudflarePlugin(root) {
|
|
19
|
-
const configPath = findViteConfigPath(root);
|
|
18
|
+
function viteConfigHasCloudflarePlugin(root, configFile) {
|
|
19
|
+
const configPath = configFile ?? findViteConfigPath(root);
|
|
20
20
|
if (!configPath) return false;
|
|
21
21
|
try {
|
|
22
22
|
const content = fs.readFileSync(configPath, "utf-8").replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1");
|
|
@@ -75,8 +75,8 @@ function cacheFieldAssigned(cacheBlock, field) {
|
|
|
75
75
|
* an unreadable or absent config is treated as configured so a deploy is never
|
|
76
76
|
* blocked on a false negative.
|
|
77
77
|
*/
|
|
78
|
-
function viteConfigHasCacheAdapter(root) {
|
|
79
|
-
const configPath = findViteConfigPath(root);
|
|
78
|
+
function viteConfigHasCacheAdapter(root, configFile) {
|
|
79
|
+
const configPath = configFile ?? findViteConfigPath(root);
|
|
80
80
|
if (!configPath) return true;
|
|
81
81
|
let content;
|
|
82
82
|
try {
|
|
@@ -104,8 +104,8 @@ function resolveCdnAdapterConfig(cache) {
|
|
|
104
104
|
const options = cdn.options;
|
|
105
105
|
return { versionMetadataBinding: typeof options?.versionMetadataBinding === "string" && options.versionMetadataBinding.length > 0 ? options.versionMetadataBinding : DEFAULT_CDN_VERSION_METADATA_BINDING };
|
|
106
106
|
}
|
|
107
|
-
function viteConfigHasImageAdapter(root) {
|
|
108
|
-
const configPath = findViteConfigPath(root);
|
|
107
|
+
function viteConfigHasImageAdapter(root, configFile) {
|
|
108
|
+
const configPath = configFile ?? findViteConfigPath(root);
|
|
109
109
|
if (!configPath) return true;
|
|
110
110
|
try {
|
|
111
111
|
const content = fs.readFileSync(configPath, "utf-8");
|
|
@@ -161,7 +161,10 @@ function formatMissingCacheAdapterError(options) {
|
|
|
161
161
|
const configRef = options.configFile ? options.configFile : "your Vite config";
|
|
162
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");
|
|
163
163
|
}
|
|
164
|
-
function formatImageOptimizationHint(typedConfig = false) {
|
|
164
|
+
function formatImageOptimizationHint(typedConfig = false, configFile) {
|
|
165
|
+
if (configFile) return ` [vinext] next/image is served unoptimized. To enable edge image
|
|
166
|
+
optimization via Cloudflare Images, configure imagesOptimizer() in
|
|
167
|
+
${configFile} and the IMAGES binding in ${typedConfig ? "cloudflare.config.ts" : "your Wrangler config"}.`;
|
|
165
168
|
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.";
|
|
166
169
|
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.";
|
|
167
170
|
}
|
package/dist/deploy-help.js
CHANGED
|
@@ -16,6 +16,7 @@ function formatDeployHelp() {
|
|
|
16
16
|
--env <name> Cloudflare mode for typed config; Wrangler environment otherwise
|
|
17
17
|
--name <name> Custom Worker name for Wrangler config projects
|
|
18
18
|
--config <path> Wrangler config path (default: wrangler.jsonc/json/toml)
|
|
19
|
+
--vite-config <path> Vite config file to build with (default: vite.config.*)
|
|
19
20
|
--skip-build Skip the build step (use existing dist/)
|
|
20
21
|
--dry-run Validate setup without building or deploying
|
|
21
22
|
--verbose Print raw output from internal Cloudflare CLI commands
|
|
@@ -129,9 +130,10 @@ function formatPreviewsDeployHelp() {
|
|
|
129
130
|
preview-name Optional Preview name (cf uses the CI or Git branch if omitted)
|
|
130
131
|
|
|
131
132
|
Options:
|
|
132
|
-
--mode <mode>
|
|
133
|
-
--skip-build
|
|
134
|
-
-
|
|
133
|
+
--mode <mode> Vite mode to build and deploy in (default: production)
|
|
134
|
+
--skip-build Deploy existing Preview Build Output without rebuilding
|
|
135
|
+
--vite-config <path> Vite config file to build with (default: vite.config.*)
|
|
136
|
+
-h, --help Show this help
|
|
135
137
|
|
|
136
138
|
Examples:
|
|
137
139
|
vinext-cloudflare previews deploy
|
package/dist/deploy.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export type DeployOptions = {
|
|
|
23
23
|
name?: string;
|
|
24
24
|
/** Wrangler config path, relative to root unless absolute */
|
|
25
25
|
config?: string;
|
|
26
|
+
/** Vite config file used to build and read deploy metadata, relative to root unless absolute */
|
|
27
|
+
viteConfig?: string;
|
|
26
28
|
/** Skip the build step (assume already built) */
|
|
27
29
|
skipBuild?: boolean;
|
|
28
30
|
/** Dry run — validate setup but don't build or deploy */
|
|
@@ -85,6 +87,7 @@ export declare function parseDeployArgs(args: string[]): {
|
|
|
85
87
|
env: string | undefined;
|
|
86
88
|
name: string | undefined;
|
|
87
89
|
config: string | undefined;
|
|
90
|
+
viteConfig: string | undefined;
|
|
88
91
|
skipBuild: boolean;
|
|
89
92
|
dryRun: boolean;
|
|
90
93
|
verbose: boolean;
|