@vinext/cloudflare 1.0.0-beta.10 → 1.0.0-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +105 -6
- package/dist/cache/cdn-adapter-config.d.ts +2 -1
- package/dist/cache/cdn-adapter-config.js +10 -2
- package/dist/cache/cdn-adapter.d.ts +8 -7
- package/dist/cache/cdn-adapter.js +8 -7
- 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/cacheability-artifact.d.ts +2 -1
- package/dist/cacheability-artifact.js +34 -10
- package/dist/deploy-config.d.ts +2 -1
- package/dist/deploy-config.js +3 -2
- package/dist/deploy-help.js +5 -5
- package/dist/deploy.d.ts +19 -2
- package/dist/deploy.js +245 -89
- package/dist/tpr.d.ts +2 -0
- package/dist/tpr.js +8 -4
- package/dist/version-deploy.d.ts +27 -0
- package/dist/version-deploy.js +121 -3
- package/package.json +10 -3
package/README.md
CHANGED
|
@@ -43,10 +43,9 @@ export default defineConfig({
|
|
|
43
43
|
`cdnAdapter()` is optional. Configuring it asks the Cloudflare build for two
|
|
44
44
|
Worker entrypoints: the default entrypoint runs middleware and request-time
|
|
45
45
|
routing with caching disabled, while `VinextCachedResponse` lazily loads the
|
|
46
|
-
render stage with Workers Cache enabled.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
the version metadata binding used for staged warmup.
|
|
46
|
+
render stage with Workers Cache enabled. Legacy Cloudflare Vite plugin builds
|
|
47
|
+
write these settings and the version metadata binding to the generated
|
|
48
|
+
`dist/server/wrangler.json`.
|
|
50
49
|
|
|
51
50
|
```ts
|
|
52
51
|
import { cdnAdapter } from "@vinext/cloudflare/cache/cdn-adapter";
|
|
@@ -54,10 +53,30 @@ import { cdnAdapter } from "@vinext/cloudflare/cache/cdn-adapter";
|
|
|
54
53
|
vinext({ cache: { cdn: cdnAdapter() } });
|
|
55
54
|
```
|
|
56
55
|
|
|
56
|
+
Cloudflare Vite plugin v2 uses Build Output and treats `cloudflare.config.ts`
|
|
57
|
+
as the deployment source of truth. Declare the equivalent policies there:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { defineWorker } from "@cloudflare/vite-plugin/experimental-config";
|
|
61
|
+
import { createWorkersCacheConfig } from "@vinext/cloudflare/cache/config";
|
|
62
|
+
|
|
63
|
+
const workersCache = await createWorkersCacheConfig();
|
|
64
|
+
|
|
65
|
+
export default defineWorker({
|
|
66
|
+
// ...
|
|
67
|
+
...workersCache,
|
|
68
|
+
env: {
|
|
69
|
+
...workersCache.env,
|
|
70
|
+
// Other application bindings...
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
57
75
|
The generated version metadata binding lets staged warmup prove that every
|
|
58
76
|
discovery, probe, and fill request reached the uploaded Worker version. Pass
|
|
59
|
-
`versionMetadataBinding` to `cdnAdapter()`
|
|
60
|
-
custom binding
|
|
77
|
+
the same `versionMetadataBinding` to `cdnAdapter()` and
|
|
78
|
+
`createWorkersCacheConfig()` only when the deployment needs a custom binding
|
|
79
|
+
name.
|
|
61
80
|
|
|
62
81
|
Use `--experimental-warm-cdn-cache` for the two-stage deploy. The default flow
|
|
63
82
|
makes one final fill request per admitted identity. Add `--warm-cdn-certify`
|
|
@@ -91,6 +110,61 @@ npx @vinext/cloudflare deploy
|
|
|
91
110
|
`vinext-cloudflare deploy` never creates, rewrites, or deploys the Response
|
|
92
111
|
Store Worker.
|
|
93
112
|
|
|
113
|
+
Cloudflare Vite plugin v2 projects can define the same service-binding setup in
|
|
114
|
+
`cloudflare.config.ts` without a second Wrangler config:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { defineConfig, defineWorker } from "@cloudflare/vite-plugin/experimental-config";
|
|
118
|
+
import { createWorkersResponseStoreServiceBindingConfig } from "@vinext/cloudflare/cache/config";
|
|
119
|
+
|
|
120
|
+
const responseStore = await createWorkersResponseStoreServiceBindingConfig({
|
|
121
|
+
accountId: "<your-account-id>",
|
|
122
|
+
worker: {
|
|
123
|
+
name: "example-response-store",
|
|
124
|
+
compatibilityDate: "2026-09-15",
|
|
125
|
+
compatibilityFlags: ["nodejs_compat"],
|
|
126
|
+
observability: {
|
|
127
|
+
enabled: true,
|
|
128
|
+
headSamplingRate: 1,
|
|
129
|
+
logs: { enabled: true, invocationLogs: true },
|
|
130
|
+
traces: { enabled: true, headSamplingRate: 0.1 },
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
bucket: "example-response-store-cache-bodies",
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
export const responseStoreServiceBinding = responseStore.serviceBindingWorker;
|
|
137
|
+
|
|
138
|
+
export default defineConfig({
|
|
139
|
+
accountId: responseStore.accountId,
|
|
140
|
+
worker: defineWorker({
|
|
141
|
+
...responseStore.applicationWorker,
|
|
142
|
+
name: "example",
|
|
143
|
+
entrypoint: "./worker.ts",
|
|
144
|
+
}),
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`worker.observability` accepts the full observability configuration supported by
|
|
149
|
+
your installed Cloudflare Vite plugin, including logs and traces settings.
|
|
150
|
+
The optional `accountId` is returned at the top level: pass it to `defineConfig`,
|
|
151
|
+
not to either Worker. It applies to both Workers in the project.
|
|
152
|
+
|
|
153
|
+
The async config helpers import bindings and exports from your installed
|
|
154
|
+
`@cloudflare/vite-plugin/experimental-config`. The plugin is an optional peer
|
|
155
|
+
dependency of `@vinext/cloudflare`; these helpers require v2, but Wrangler-only
|
|
156
|
+
projects do not need it.
|
|
157
|
+
|
|
158
|
+
Register the exported auxiliary Worker with the Cloudflare Vite plugin so it
|
|
159
|
+
is built alongside the application:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
import { cloudflare } from "@cloudflare/vite-plugin";
|
|
163
|
+
import { responseStoreServiceBinding } from "./cloudflare.config.ts";
|
|
164
|
+
|
|
165
|
+
cloudflare({ auxiliaryWorkers: [{ config: responseStoreServiceBinding }] });
|
|
166
|
+
```
|
|
167
|
+
|
|
94
168
|
Metadata sharding is opt-in and works in either deployment mode:
|
|
95
169
|
|
|
96
170
|
```ts
|
|
@@ -121,6 +195,9 @@ import { responseStoreAdapter } from "@vinext/cloudflare/cache/response-store-ad
|
|
|
121
195
|
vinext({ cache: responseStoreAdapter({ mode: "self-contained" }) });
|
|
122
196
|
```
|
|
123
197
|
|
|
198
|
+
The corresponding typed config helper is
|
|
199
|
+
`createWorkersResponseStoreSelfContainedConfig`.
|
|
200
|
+
|
|
124
201
|
In this mode `vinext init` places the required R2, SQLite Durable Object,
|
|
125
202
|
Workers Cache entrypoint, and version-metadata configuration in
|
|
126
203
|
`wrangler.jsonc`; no second Wrangler config is required.
|
|
@@ -133,5 +210,27 @@ Deploy Cloudflare Workers projects with the package CLI:
|
|
|
133
210
|
npx @vinext/cloudflare deploy
|
|
134
211
|
```
|
|
135
212
|
|
|
213
|
+
Projects with `cloudflare.config.ts` opt into the experimental Cloudflare Vite
|
|
214
|
+
plugin v2 path and deploy their generated Build Output with `cf`. Existing
|
|
215
|
+
Wrangler-configured projects continue to use Wrangler. A normal typed-config
|
|
216
|
+
deploy needs neither `wrangler.jsonc` nor the Wrangler package.
|
|
217
|
+
|
|
218
|
+
`vinext-cloudflare deploy` only deploys the entry Worker. It never deploys named
|
|
219
|
+
auxiliary Workers, including during staged warming or `--no-promote` uploads.
|
|
220
|
+
Deploy a configured Response Store Worker explicitly after building, when first
|
|
221
|
+
setting it up or when its code/config changes:
|
|
222
|
+
|
|
223
|
+
```sh
|
|
224
|
+
cf deploy --prebuilt --mode production --worker example-response-store
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Use the mode and Worker name from your build. `cloudflare.config.ts` remains the
|
|
228
|
+
source of truth; no auxiliary Wrangler config is required.
|
|
229
|
+
|
|
230
|
+
Experimental staged CDN warming uses `cf` to upload a version, read deployment
|
|
231
|
+
status, stage and promote traffic, and apply triggers. Typed-config projects do
|
|
232
|
+
not need an equivalent Wrangler config. Existing Wrangler-configured projects
|
|
233
|
+
continue to use Wrangler for this flow.
|
|
234
|
+
|
|
136
235
|
With Vite+, use `vpx @vinext/cloudflare deploy`, or
|
|
137
236
|
`vp exec vinext-cloudflare deploy` when running the locally installed bin.
|
|
@@ -21,7 +21,8 @@ export declare function configureCdnVersionMetadata(config: WranglerOutputConfig
|
|
|
21
21
|
/** Add the per-entrypoint Workers Cache policy to an emitted Wrangler config. */
|
|
22
22
|
export declare function configureWorkersCacheEntrypoints(config: WranglerOutputConfig): WranglerOutputConfig;
|
|
23
23
|
/** Apply the complete CDN adapter policy to the primary generated config. */
|
|
24
|
-
export declare function finalizeCdnAdapterBuildOutput({ outDir, isPrimaryServerOutput, binding, bindingIsExplicit }: {
|
|
24
|
+
export declare function finalizeCdnAdapterBuildOutput({ root, outDir, isPrimaryServerOutput, binding, bindingIsExplicit }: {
|
|
25
|
+
root: string;
|
|
25
26
|
outDir: string;
|
|
26
27
|
isPrimaryServerOutput: boolean;
|
|
27
28
|
binding: string;
|
|
@@ -72,11 +72,19 @@ async function readGeneratedConfig(generatedConfigPath, allowMissing) {
|
|
|
72
72
|
async function writeGeneratedConfig(generatedConfigPath, config) {
|
|
73
73
|
await fs.writeFile(generatedConfigPath, `${JSON.stringify(config, null, 2)}\n`);
|
|
74
74
|
}
|
|
75
|
+
async function usesCloudflareBuildOutput(root) {
|
|
76
|
+
try {
|
|
77
|
+
return (await fs.stat(path.resolve(root, "cloudflare.config.ts"))).isFile();
|
|
78
|
+
} catch (cause) {
|
|
79
|
+
if (cause !== null && typeof cause === "object" && "code" in cause && cause.code === "ENOENT") return false;
|
|
80
|
+
throw cause;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
75
83
|
/** Apply the complete CDN adapter policy to the primary generated config. */
|
|
76
|
-
async function finalizeCdnAdapterBuildOutput({ outDir, isPrimaryServerOutput, binding, bindingIsExplicit }) {
|
|
84
|
+
async function finalizeCdnAdapterBuildOutput({ root, outDir, isPrimaryServerOutput, binding, bindingIsExplicit }) {
|
|
77
85
|
if (!isPrimaryServerOutput) return;
|
|
78
86
|
const generatedConfigPath = path.resolve(outDir, "wrangler.json");
|
|
79
|
-
const generatedConfig = await readGeneratedConfig(generatedConfigPath,
|
|
87
|
+
const generatedConfig = await readGeneratedConfig(generatedConfigPath, await usesCloudflareBuildOutput(root));
|
|
80
88
|
if (!generatedConfig) return;
|
|
81
89
|
await writeGeneratedConfig(generatedConfigPath, configureWorkersCacheEntrypoints(configureCdnVersionMetadata(generatedConfig, {
|
|
82
90
|
binding,
|
|
@@ -15,12 +15,12 @@ export type CdnAdapterOptions = {
|
|
|
15
15
|
* request-time routing before dispatching to cached or uncached response-stage
|
|
16
16
|
* entrypoints.
|
|
17
17
|
*
|
|
18
|
-
* The
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
18
|
+
* The deployment configuration enables Workers Cache only for that response-stage
|
|
19
|
+
* export, so cache hits do not start the application stage. Legacy Cloudflare
|
|
20
|
+
* builds receive this policy in their generated Wrangler config. Cloudflare Vite
|
|
21
|
+
* plugin v2 builds declare the same entrypoint policy in `cloudflare.config.ts`.
|
|
22
|
+
* The uncached response entrypoint keeps bypass and probe renders out of the
|
|
23
|
+
* gateway without enabling Workers Cache for them.
|
|
24
24
|
*
|
|
25
25
|
* The adapter adds a transport-only URL digest so distinct response-stage
|
|
26
26
|
* identities cannot collide. Workers Cache owns this key independently of
|
|
@@ -40,7 +40,8 @@ export declare function cdnAdapter(options?: CdnAdapterOptions): {
|
|
|
40
40
|
code: string;
|
|
41
41
|
id: string;
|
|
42
42
|
}): string | null;
|
|
43
|
-
finalizeBuildOutput({ outDir, isPrimaryServerOutput }: {
|
|
43
|
+
finalizeBuildOutput({ root, outDir, isPrimaryServerOutput }: {
|
|
44
|
+
root: string;
|
|
44
45
|
outDir: string;
|
|
45
46
|
isPrimaryServerOutput: boolean;
|
|
46
47
|
}): Promise<void>;
|
|
@@ -13,12 +13,12 @@ const CLOUDFLARE_WORKER_ENTRY_ID = "virtual:cloudflare/worker-entry";
|
|
|
13
13
|
* request-time routing before dispatching to cached or uncached response-stage
|
|
14
14
|
* entrypoints.
|
|
15
15
|
*
|
|
16
|
-
* The
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
16
|
+
* The deployment configuration enables Workers Cache only for that response-stage
|
|
17
|
+
* export, so cache hits do not start the application stage. Legacy Cloudflare
|
|
18
|
+
* builds receive this policy in their generated Wrangler config. Cloudflare Vite
|
|
19
|
+
* plugin v2 builds declare the same entrypoint policy in `cloudflare.config.ts`.
|
|
20
|
+
* The uncached response entrypoint keeps bypass and probe renders out of the
|
|
21
|
+
* gateway without enabling Workers Cache for them.
|
|
22
22
|
*
|
|
23
23
|
* The adapter adds a transport-only URL digest so distinct response-stage
|
|
24
24
|
* identities cannot collide. Workers Cache owns this key independently of
|
|
@@ -41,8 +41,9 @@ function cdnAdapter(options) {
|
|
|
41
41
|
if ((id.charCodeAt(0) === 0 ? id.slice(1) : id) !== CLOUDFLARE_WORKER_ENTRY_ID) return null;
|
|
42
42
|
return `${code}\nexport { VinextCachedResponse, VinextUncachedResponse } from ${JSON.stringify(workerEntry)};\n`;
|
|
43
43
|
},
|
|
44
|
-
finalizeBuildOutput({ outDir, isPrimaryServerOutput }) {
|
|
44
|
+
finalizeBuildOutput({ root, outDir, isPrimaryServerOutput }) {
|
|
45
45
|
return finalizeCdnAdapterBuildOutput({
|
|
46
|
+
root,
|
|
46
47
|
outDir,
|
|
47
48
|
isPrimaryServerOutput,
|
|
48
49
|
binding: versionMetadataBinding,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//#region src/cache/cdn-build-id.d.ts
|
|
2
2
|
/** Build identity stamped by the Cloudflare CDN adapter on page responses. */
|
|
3
3
|
export declare const VINEXT_CDN_BUILD_ID_HEADER = "X-Vinext-Build-Id";
|
|
4
|
-
/** Opaque identity shared by every server entry emitted by one
|
|
4
|
+
/** Opaque identity shared by every server entry emitted by one vite build. */
|
|
5
5
|
export declare function getVinextCdnBuildIdentity(): string | null;
|
|
6
6
|
//#endregion
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
//#region src/cache/cdn-build-id.ts
|
|
2
2
|
/** Build identity stamped by the Cloudflare CDN adapter on page responses. */
|
|
3
3
|
const VINEXT_CDN_BUILD_ID_HEADER = "X-Vinext-Build-Id";
|
|
4
|
-
/** Opaque identity shared by every server entry emitted by one
|
|
4
|
+
/** Opaque identity shared by every server entry emitted by one vite build. */
|
|
5
5
|
function getVinextCdnBuildIdentity() {
|
|
6
6
|
return process.env.__VINEXT_RSC_BUILD_IDENTITY || process.env.__VINEXT_BUILD_ID || null;
|
|
7
7
|
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { CloudflareConfig, WorkerConfig } from "@cloudflare/vite-plugin/experimental-config";
|
|
2
|
+
//#region src/cache/config.d.ts
|
|
3
|
+
type ResponseStoreStorageOptions = {
|
|
4
|
+
worker: string;
|
|
5
|
+
bucket: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function createWorkersResponseStoreSelfContainedConfig(options: ResponseStoreStorageOptions): Promise<{
|
|
8
|
+
cache: {
|
|
9
|
+
enabled: true;
|
|
10
|
+
};
|
|
11
|
+
exports: {
|
|
12
|
+
CacheMetadata: {
|
|
13
|
+
state?: "created";
|
|
14
|
+
type: "durable-object";
|
|
15
|
+
storage: "sqlite";
|
|
16
|
+
container: undefined;
|
|
17
|
+
} | {
|
|
18
|
+
state?: "created";
|
|
19
|
+
type: "durable-object";
|
|
20
|
+
storage: "sqlite";
|
|
21
|
+
container: undefined;
|
|
22
|
+
};
|
|
23
|
+
ResponseStoreBinding: import("@cloudflare/vite-plugin/experimental-config").WorkerEntrypointExport;
|
|
24
|
+
};
|
|
25
|
+
env: {
|
|
26
|
+
CACHE_BODIES: import("@cloudflare/vite-plugin/experimental-config").R2Binding;
|
|
27
|
+
CACHE_METADATA: {
|
|
28
|
+
type: "durable-object";
|
|
29
|
+
worker: string;
|
|
30
|
+
exportName: string;
|
|
31
|
+
};
|
|
32
|
+
CF_VERSION_METADATA: import("@cloudflare/vite-plugin/experimental-config").VersionMetadataBinding;
|
|
33
|
+
};
|
|
34
|
+
}>;
|
|
35
|
+
export declare function createWorkersResponseStoreServiceBindingConfig(options: {
|
|
36
|
+
accountId?: CloudflareConfig["accountId"];
|
|
37
|
+
worker: {
|
|
38
|
+
name: string;
|
|
39
|
+
compatibilityDate: string;
|
|
40
|
+
compatibilityFlags?: string[];
|
|
41
|
+
observability?: WorkerConfig["observability"];
|
|
42
|
+
};
|
|
43
|
+
bucket: string;
|
|
44
|
+
}): Promise<{
|
|
45
|
+
accountId: string | undefined;
|
|
46
|
+
serviceBindingWorker: {
|
|
47
|
+
cache: {
|
|
48
|
+
enabled: true;
|
|
49
|
+
};
|
|
50
|
+
exports: {
|
|
51
|
+
CacheMetadata: {
|
|
52
|
+
state?: "created";
|
|
53
|
+
type: "durable-object";
|
|
54
|
+
storage: "sqlite";
|
|
55
|
+
container: undefined;
|
|
56
|
+
} | {
|
|
57
|
+
state?: "created";
|
|
58
|
+
type: "durable-object";
|
|
59
|
+
storage: "sqlite";
|
|
60
|
+
container: undefined;
|
|
61
|
+
};
|
|
62
|
+
ResponseStoreBinding: import("@cloudflare/vite-plugin/experimental-config").WorkerEntrypointExport;
|
|
63
|
+
};
|
|
64
|
+
env: {
|
|
65
|
+
CACHE_BODIES: import("@cloudflare/vite-plugin/experimental-config").R2Binding;
|
|
66
|
+
CACHE_METADATA: {
|
|
67
|
+
type: "durable-object";
|
|
68
|
+
worker: string;
|
|
69
|
+
exportName: string;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
name: string;
|
|
73
|
+
compatibilityDate: string;
|
|
74
|
+
compatibilityFlags?: string[];
|
|
75
|
+
observability?: WorkerConfig["observability"];
|
|
76
|
+
entrypoint: string;
|
|
77
|
+
workersDev: false;
|
|
78
|
+
previewUrls: false;
|
|
79
|
+
};
|
|
80
|
+
applicationWorker: {
|
|
81
|
+
cache: {
|
|
82
|
+
enabled: false;
|
|
83
|
+
};
|
|
84
|
+
env: {
|
|
85
|
+
RESPONSE_STORE: import("@cloudflare/vite-plugin/experimental-config").WorkerBinding<{
|
|
86
|
+
cache: {
|
|
87
|
+
enabled: true;
|
|
88
|
+
};
|
|
89
|
+
exports: {
|
|
90
|
+
CacheMetadata: {
|
|
91
|
+
state?: "created";
|
|
92
|
+
type: "durable-object";
|
|
93
|
+
storage: "sqlite";
|
|
94
|
+
container: undefined;
|
|
95
|
+
} | {
|
|
96
|
+
state?: "created";
|
|
97
|
+
type: "durable-object";
|
|
98
|
+
storage: "sqlite";
|
|
99
|
+
container: undefined;
|
|
100
|
+
};
|
|
101
|
+
ResponseStoreBinding: import("@cloudflare/vite-plugin/experimental-config").WorkerEntrypointExport;
|
|
102
|
+
};
|
|
103
|
+
env: {
|
|
104
|
+
CACHE_BODIES: import("@cloudflare/vite-plugin/experimental-config").R2Binding;
|
|
105
|
+
CACHE_METADATA: {
|
|
106
|
+
type: "durable-object";
|
|
107
|
+
worker: string;
|
|
108
|
+
exportName: string;
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
name: string;
|
|
112
|
+
compatibilityDate: string;
|
|
113
|
+
compatibilityFlags?: string[];
|
|
114
|
+
observability?: WorkerConfig["observability"];
|
|
115
|
+
entrypoint: string;
|
|
116
|
+
workersDev: false;
|
|
117
|
+
previewUrls: false;
|
|
118
|
+
}, "ResponseStoreService">;
|
|
119
|
+
CF_VERSION_METADATA: import("@cloudflare/vite-plugin/experimental-config").VersionMetadataBinding;
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
}>;
|
|
123
|
+
export declare function createWorkersCacheConfig({ versionMetadataBinding }?: {
|
|
124
|
+
versionMetadataBinding?: string;
|
|
125
|
+
}): Promise<{
|
|
126
|
+
cache: {
|
|
127
|
+
enabled: false;
|
|
128
|
+
};
|
|
129
|
+
env: {
|
|
130
|
+
[versionMetadataBinding]: import("@cloudflare/vite-plugin/experimental-config").VersionMetadataBinding;
|
|
131
|
+
};
|
|
132
|
+
exports: {
|
|
133
|
+
VinextCachedResponse: import("@cloudflare/vite-plugin/experimental-config").WorkerEntrypointExport;
|
|
134
|
+
VinextUncachedResponse: import("@cloudflare/vite-plugin/experimental-config").WorkerEntrypointExport;
|
|
135
|
+
};
|
|
136
|
+
}>;
|
|
137
|
+
//#endregion
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
//#region src/cache/config.ts
|
|
2
|
+
const RESPONSE_STORE_BINDING = "RESPONSE_STORE";
|
|
3
|
+
const RESPONSE_STORE_ENTRYPOINT = "ResponseStoreService";
|
|
4
|
+
const DEFAULT_VERSION_METADATA_BINDING = "CF_VERSION_METADATA";
|
|
5
|
+
const CACHE_BODIES_BINDING = "CACHE_BODIES";
|
|
6
|
+
const CACHE_METADATA_BINDING = "CACHE_METADATA";
|
|
7
|
+
const CACHE_METADATA_CLASS = "CacheMetadata";
|
|
8
|
+
const RESPONSE_STORE_CACHE_ENTRYPOINT = "ResponseStoreBinding";
|
|
9
|
+
const RESPONSE_STORE_SERVICE_ENTRYPOINT = "@cloudflare/workers-response-store/service";
|
|
10
|
+
async function createResponseStoreWorkerConfig(options) {
|
|
11
|
+
const { bindings, exports } = await import("@cloudflare/vite-plugin/experimental-config");
|
|
12
|
+
return {
|
|
13
|
+
cache: { enabled: true },
|
|
14
|
+
exports: {
|
|
15
|
+
[CACHE_METADATA_CLASS]: {
|
|
16
|
+
...exports.durableObject({ storage: "sqlite" }),
|
|
17
|
+
storage: "sqlite",
|
|
18
|
+
container: void 0
|
|
19
|
+
},
|
|
20
|
+
[RESPONSE_STORE_CACHE_ENTRYPOINT]: exports.worker({ cache: { enabled: true } })
|
|
21
|
+
},
|
|
22
|
+
env: {
|
|
23
|
+
[CACHE_BODIES_BINDING]: bindings.r2({ name: options.bucket }),
|
|
24
|
+
[CACHE_METADATA_BINDING]: {
|
|
25
|
+
...bindings.durableObject({
|
|
26
|
+
worker: options.worker,
|
|
27
|
+
exportName: CACHE_METADATA_CLASS
|
|
28
|
+
}),
|
|
29
|
+
worker: options.worker,
|
|
30
|
+
exportName: CACHE_METADATA_CLASS
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
async function createWorkersResponseStoreSelfContainedConfig(options) {
|
|
36
|
+
const { bindings } = await import("@cloudflare/vite-plugin/experimental-config");
|
|
37
|
+
const storage = await createResponseStoreWorkerConfig(options);
|
|
38
|
+
return {
|
|
39
|
+
...storage,
|
|
40
|
+
env: {
|
|
41
|
+
...storage.env,
|
|
42
|
+
[DEFAULT_VERSION_METADATA_BINDING]: bindings.versionMetadata()
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
async function createWorkersResponseStoreServiceBindingConfig(options) {
|
|
47
|
+
const { bindings } = await import("@cloudflare/vite-plugin/experimental-config");
|
|
48
|
+
const storage = await createResponseStoreWorkerConfig({
|
|
49
|
+
worker: options.worker.name,
|
|
50
|
+
bucket: options.bucket
|
|
51
|
+
});
|
|
52
|
+
const serviceBindingWorker = {
|
|
53
|
+
...options.worker,
|
|
54
|
+
entrypoint: RESPONSE_STORE_SERVICE_ENTRYPOINT,
|
|
55
|
+
workersDev: false,
|
|
56
|
+
previewUrls: false,
|
|
57
|
+
...storage
|
|
58
|
+
};
|
|
59
|
+
return {
|
|
60
|
+
accountId: options.accountId,
|
|
61
|
+
serviceBindingWorker,
|
|
62
|
+
applicationWorker: {
|
|
63
|
+
cache: { enabled: false },
|
|
64
|
+
env: {
|
|
65
|
+
[RESPONSE_STORE_BINDING]: bindings.worker({
|
|
66
|
+
worker: serviceBindingWorker,
|
|
67
|
+
exportName: RESPONSE_STORE_ENTRYPOINT
|
|
68
|
+
}),
|
|
69
|
+
[DEFAULT_VERSION_METADATA_BINDING]: bindings.versionMetadata()
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
async function createWorkersCacheConfig({ versionMetadataBinding = DEFAULT_VERSION_METADATA_BINDING } = {}) {
|
|
75
|
+
if (versionMetadataBinding.length === 0) throw new TypeError("versionMetadataBinding must be a non-empty string");
|
|
76
|
+
const { bindings, exports } = await import("@cloudflare/vite-plugin/experimental-config");
|
|
77
|
+
return {
|
|
78
|
+
cache: { enabled: false },
|
|
79
|
+
env: { [versionMetadataBinding]: bindings.versionMetadata() },
|
|
80
|
+
exports: {
|
|
81
|
+
VinextCachedResponse: exports.worker({ cache: { enabled: true } }),
|
|
82
|
+
VinextUncachedResponse: exports.worker({ cache: { enabled: false } })
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
export { createWorkersCacheConfig, createWorkersResponseStoreSelfContainedConfig, createWorkersResponseStoreServiceBindingConfig };
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { CacheabilityManifest } from "vinext/internal/server/cacheability-manifest";
|
|
2
2
|
//#region src/cacheability-artifact.d.ts
|
|
3
|
+
export type CloudflareDeploymentTool = "cf" | "wrangler";
|
|
3
4
|
/**
|
|
4
5
|
* Write the version-specific manifest into the built Worker artifact.
|
|
5
6
|
* The application build already imports this stable module asset, so the
|
|
6
7
|
* completed dist directory remains the exact input to the final upload.
|
|
7
8
|
*/
|
|
8
|
-
export declare function writeCacheabilityManifestArtifact(root: string, configuredPath: string | undefined, manifest: CacheabilityManifest): string;
|
|
9
|
+
export declare function writeCacheabilityManifestArtifact(root: string, configuredPath: string | undefined, manifest: CacheabilityManifest, deploymentTool?: CloudflareDeploymentTool): string;
|
|
9
10
|
//#endregion
|
|
@@ -115,16 +115,12 @@ function hasStaticModuleSpecifier(source, expected) {
|
|
|
115
115
|
}
|
|
116
116
|
return false;
|
|
117
117
|
}
|
|
118
|
-
function
|
|
118
|
+
function resolveGeneratedWranglerArtifact(root, configuredPath) {
|
|
119
119
|
const distDirectory = path.join(root, "dist");
|
|
120
120
|
const configPath = path.resolve(root, configuredPath ?? "dist/server/wrangler.json");
|
|
121
121
|
const relativeConfigPath = path.relative(distDirectory, configPath);
|
|
122
122
|
if (relativeConfigPath.startsWith("..") || path.isAbsolute(relativeConfigPath)) throw new Error("Two-stage CDN warming requires a generated Wrangler config under dist/. Pass --config dist/server/wrangler.json (or another generated dist config).");
|
|
123
123
|
if (!fs.existsSync(configPath) || !fs.lstatSync(configPath).isFile()) throw new Error(`Two-stage CDN warming requires the generated Wrangler config at ${path.relative(root, configPath)}. Rebuild the app before deploying.`);
|
|
124
|
-
return configPath;
|
|
125
|
-
}
|
|
126
|
-
function assertManifestModuleReachable(configPath) {
|
|
127
|
-
const serverDirectory = path.dirname(configPath);
|
|
128
124
|
let config;
|
|
129
125
|
try {
|
|
130
126
|
config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
@@ -133,8 +129,36 @@ function assertManifestModuleReachable(configPath) {
|
|
|
133
129
|
}
|
|
134
130
|
const main = config && typeof config === "object" && !Array.isArray(config) ? config.main : void 0;
|
|
135
131
|
if (typeof main !== "string" || main.length === 0) throw new Error("Two-stage CDN warming requires a generated Wrangler main module.");
|
|
132
|
+
return {
|
|
133
|
+
configPath,
|
|
134
|
+
main,
|
|
135
|
+
serverDirectory: path.dirname(configPath)
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function resolveBuildOutputArtifact(root) {
|
|
139
|
+
const outputDirectory = path.join(root, ".cloudflare", "output");
|
|
140
|
+
const configPaths = fs.existsSync(outputDirectory) ? fs.readdirSync(outputDirectory, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => path.join(outputDirectory, entry.name, "workers", "default", "worker.config.json")).filter((candidate) => fs.existsSync(candidate) && fs.lstatSync(candidate).isFile()) : [];
|
|
141
|
+
if (configPaths.length !== 1) throw new Error("Two-stage CDN warming requires one default Worker Build Output config under .cloudflare/output/. Rebuild the app before deploying.");
|
|
142
|
+
const configPath = configPaths[0];
|
|
143
|
+
let config;
|
|
144
|
+
try {
|
|
145
|
+
config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
146
|
+
} catch (cause) {
|
|
147
|
+
throw new Error("Two-stage CDN warming could not parse the generated Build Output config.", { cause });
|
|
148
|
+
}
|
|
149
|
+
const manifest = config && typeof config === "object" && !Array.isArray(config) ? config.manifest : void 0;
|
|
150
|
+
const main = manifest && typeof manifest === "object" && !Array.isArray(manifest) ? manifest.mainModule : void 0;
|
|
151
|
+
if (typeof main !== "string" || main.length === 0) throw new Error("Two-stage CDN warming requires a generated Build Output main module.");
|
|
152
|
+
return {
|
|
153
|
+
configPath,
|
|
154
|
+
main,
|
|
155
|
+
serverDirectory: path.join(path.dirname(configPath), "bundle")
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function assertManifestModuleReachable(artifact) {
|
|
159
|
+
const { main, serverDirectory } = artifact;
|
|
136
160
|
const mainPath = path.resolve(serverDirectory, main);
|
|
137
|
-
if (!fs.existsSync(mainPath) || !fs.lstatSync(mainPath).isFile()) throw new Error("Two-stage CDN warming could not find the generated
|
|
161
|
+
if (!fs.existsSync(mainPath) || !fs.lstatSync(mainPath).isFile()) throw new Error("Two-stage CDN warming could not find the generated Worker main module.");
|
|
138
162
|
const viteManifestPath = path.join(serverDirectory, ".vite", "manifest.json");
|
|
139
163
|
let viteManifest;
|
|
140
164
|
try {
|
|
@@ -168,10 +192,10 @@ function assertManifestModuleReachable(configPath) {
|
|
|
168
192
|
* The application build already imports this stable module asset, so the
|
|
169
193
|
* completed dist directory remains the exact input to the final upload.
|
|
170
194
|
*/
|
|
171
|
-
function writeCacheabilityManifestArtifact(root, configuredPath, manifest) {
|
|
172
|
-
const
|
|
173
|
-
assertManifestModuleReachable(
|
|
174
|
-
const serverDirectory =
|
|
195
|
+
function writeCacheabilityManifestArtifact(root, configuredPath, manifest, deploymentTool = "wrangler") {
|
|
196
|
+
const artifact = deploymentTool === "cf" ? resolveBuildOutputArtifact(root) : resolveGeneratedWranglerArtifact(root, configuredPath);
|
|
197
|
+
assertManifestModuleReachable(artifact);
|
|
198
|
+
const { configPath, serverDirectory } = artifact;
|
|
175
199
|
const manifestPath = path.join(serverDirectory, CACHEABILITY_MANIFEST_MODULE);
|
|
176
200
|
if (!fs.existsSync(manifestPath) || !fs.lstatSync(manifestPath).isFile()) throw new Error(`Two-stage CDN warming requires ${CACHEABILITY_MANIFEST_MODULE} in the generated Worker artifact. Rebuild the app before deploying.`);
|
|
177
201
|
const serializedManifest = JSON.stringify(manifest);
|
package/dist/deploy-config.d.ts
CHANGED
|
@@ -52,6 +52,7 @@ export declare function workerEntryHasCacheHandler(root: string): boolean;
|
|
|
52
52
|
*/
|
|
53
53
|
export declare function formatMissingCacheAdapterError(options: {
|
|
54
54
|
configFile?: string;
|
|
55
|
+
typedConfig?: boolean;
|
|
55
56
|
}): string;
|
|
56
|
-
export declare function formatImageOptimizationHint(): string;
|
|
57
|
+
export declare function formatImageOptimizationHint(typedConfig?: boolean): string;
|
|
57
58
|
//#endregion
|
package/dist/deploy-config.js
CHANGED
|
@@ -178,9 +178,10 @@ function workerEntryHasCacheHandler(root) {
|
|
|
178
178
|
*/
|
|
179
179
|
function formatMissingCacheAdapterError(options) {
|
|
180
180
|
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
|
|
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` + (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
182
|
}
|
|
183
|
-
function formatImageOptimizationHint() {
|
|
183
|
+
function formatImageOptimizationHint(typedConfig = false) {
|
|
184
|
+
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
185
|
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
186
|
}
|
|
186
187
|
//#endregion
|
package/dist/deploy-help.js
CHANGED
|
@@ -9,16 +9,16 @@ 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
|
|
21
|
+
--verbose Print raw output from internal Cloudflare CLI commands
|
|
22
22
|
--no-promote Do not promote the uploaded Worker version to 100%
|
|
23
23
|
traffic
|
|
24
24
|
--prerender-all Pre-render discovered routes after building (future
|
|
@@ -91,7 +91,7 @@ function formatDeployHelp() {
|
|
|
91
91
|
vpx @vinext/cloudflare deploy Build and deploy with Vite+
|
|
92
92
|
vp exec vinext-cloudflare deploy Run the locally installed Vite+ bin
|
|
93
93
|
vinext-cloudflare deploy --preview Deploy to a preview URL
|
|
94
|
-
vinext-cloudflare deploy --env staging Deploy using
|
|
94
|
+
vinext-cloudflare deploy --env staging Deploy using Cloudflare mode/environment staging
|
|
95
95
|
vinext-cloudflare deploy --config dist/server/wrangler.json Deploy using a generated Wrangler config
|
|
96
96
|
vinext-cloudflare deploy --dry-run Validate setup without building or deploying
|
|
97
97
|
vinext-cloudflare deploy --name my-app Deploy with a custom Worker name
|