@timber-js/app 0.1.1 → 0.1.2
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/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/plugins/entries.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/adapters/cloudflare.ts +325 -0
- package/src/adapters/nitro.ts +366 -0
- package/src/adapters/types.ts +63 -0
- package/src/cache/index.ts +91 -0
- package/src/cache/redis-handler.ts +91 -0
- package/src/cache/register-cached-function.ts +99 -0
- package/src/cache/singleflight.ts +26 -0
- package/src/cache/stable-stringify.ts +21 -0
- package/src/cache/timber-cache.ts +116 -0
- package/src/cli.ts +201 -0
- package/src/client/browser-entry.ts +663 -0
- package/src/client/error-boundary.tsx +209 -0
- package/src/client/form.tsx +200 -0
- package/src/client/head.ts +61 -0
- package/src/client/history.ts +46 -0
- package/src/client/index.ts +60 -0
- package/src/client/link-navigate-interceptor.tsx +62 -0
- package/src/client/link-status-provider.tsx +40 -0
- package/src/client/link.tsx +310 -0
- package/src/client/nuqs-adapter.tsx +117 -0
- package/src/client/router-ref.ts +25 -0
- package/src/client/router.ts +563 -0
- package/src/client/segment-cache.ts +194 -0
- package/src/client/segment-context.ts +57 -0
- package/src/client/ssr-data.ts +95 -0
- package/src/client/types.ts +4 -0
- package/src/client/unload-guard.ts +34 -0
- package/src/client/use-cookie.ts +122 -0
- package/src/client/use-link-status.ts +46 -0
- package/src/client/use-navigation-pending.ts +47 -0
- package/src/client/use-params.ts +71 -0
- package/src/client/use-pathname.ts +43 -0
- package/src/client/use-query-states.ts +133 -0
- package/src/client/use-router.ts +77 -0
- package/src/client/use-search-params.ts +74 -0
- package/src/client/use-selected-layout-segment.ts +110 -0
- package/src/content/index.ts +13 -0
- package/src/cookies/define-cookie.ts +137 -0
- package/src/cookies/index.ts +9 -0
- package/src/fonts/ast.ts +359 -0
- package/src/fonts/css.ts +68 -0
- package/src/fonts/fallbacks.ts +248 -0
- package/src/fonts/google.ts +332 -0
- package/src/fonts/local.ts +177 -0
- package/src/fonts/types.ts +88 -0
- package/src/index.ts +413 -0
- package/src/plugins/adapter-build.ts +118 -0
- package/src/plugins/build-manifest.ts +323 -0
- package/src/plugins/build-report.ts +353 -0
- package/src/plugins/cache-transform.ts +199 -0
- package/src/plugins/chunks.ts +90 -0
- package/src/plugins/content.ts +136 -0
- package/src/plugins/dev-error-overlay.ts +230 -0
- package/src/plugins/dev-logs.ts +280 -0
- package/src/plugins/dev-server.ts +389 -0
- package/src/plugins/dynamic-transform.ts +161 -0
- package/src/plugins/entries.ts +207 -0
- package/src/plugins/fonts.ts +581 -0
- package/src/plugins/mdx.ts +179 -0
- package/src/plugins/react-prod.ts +56 -0
- package/src/plugins/routing.ts +419 -0
- package/src/plugins/server-action-exports.ts +220 -0
- package/src/plugins/server-bundle.ts +113 -0
- package/src/plugins/shims.ts +168 -0
- package/src/plugins/static-build.ts +207 -0
- package/src/routing/codegen.ts +396 -0
- package/src/routing/index.ts +14 -0
- package/src/routing/interception.ts +173 -0
- package/src/routing/scanner.ts +487 -0
- package/src/routing/status-file-lint.ts +114 -0
- package/src/routing/types.ts +100 -0
- package/src/search-params/analyze.ts +192 -0
- package/src/search-params/codecs.ts +153 -0
- package/src/search-params/create.ts +314 -0
- package/src/search-params/index.ts +23 -0
- package/src/search-params/registry.ts +31 -0
- package/src/server/access-gate.tsx +142 -0
- package/src/server/action-client.ts +473 -0
- package/src/server/action-handler.ts +325 -0
- package/src/server/actions.ts +236 -0
- package/src/server/asset-headers.ts +81 -0
- package/src/server/body-limits.ts +102 -0
- package/src/server/build-manifest.ts +234 -0
- package/src/server/canonicalize.ts +90 -0
- package/src/server/client-module-map.ts +58 -0
- package/src/server/csrf.ts +79 -0
- package/src/server/deny-renderer.ts +302 -0
- package/src/server/dev-logger.ts +419 -0
- package/src/server/dev-span-processor.ts +78 -0
- package/src/server/dev-warnings.ts +282 -0
- package/src/server/early-hints-sender.ts +55 -0
- package/src/server/early-hints.ts +142 -0
- package/src/server/error-boundary-wrapper.ts +69 -0
- package/src/server/error-formatter.ts +184 -0
- package/src/server/flush.ts +182 -0
- package/src/server/form-data.ts +176 -0
- package/src/server/form-flash.ts +93 -0
- package/src/server/html-injectors.ts +445 -0
- package/src/server/index.ts +222 -0
- package/src/server/instrumentation.ts +136 -0
- package/src/server/logger.ts +145 -0
- package/src/server/manifest-status-resolver.ts +215 -0
- package/src/server/metadata-render.ts +527 -0
- package/src/server/metadata-routes.ts +189 -0
- package/src/server/metadata.ts +263 -0
- package/src/server/middleware-runner.ts +32 -0
- package/src/server/nuqs-ssr-provider.tsx +63 -0
- package/src/server/pipeline.ts +555 -0
- package/src/server/prerender.ts +139 -0
- package/src/server/primitives.ts +264 -0
- package/src/server/proxy.ts +43 -0
- package/src/server/request-context.ts +554 -0
- package/src/server/route-element-builder.ts +395 -0
- package/src/server/route-handler.ts +153 -0
- package/src/server/route-matcher.ts +316 -0
- package/src/server/rsc-entry/api-handler.ts +112 -0
- package/src/server/rsc-entry/error-renderer.ts +177 -0
- package/src/server/rsc-entry/helpers.ts +147 -0
- package/src/server/rsc-entry/index.ts +688 -0
- package/src/server/rsc-entry/ssr-bridge.ts +18 -0
- package/src/server/slot-resolver.ts +359 -0
- package/src/server/ssr-entry.ts +161 -0
- package/src/server/ssr-render.ts +200 -0
- package/src/server/status-code-resolver.ts +282 -0
- package/src/server/tracing.ts +281 -0
- package/src/server/tree-builder.ts +354 -0
- package/src/server/types.ts +150 -0
- package/src/shims/font-google.ts +67 -0
- package/src/shims/headers.ts +11 -0
- package/src/shims/image.ts +48 -0
- package/src/shims/link.ts +9 -0
- package/src/shims/navigation-client.ts +52 -0
- package/src/shims/navigation.ts +31 -0
- package/src/shims/server-only-noop.js +5 -0
- package/src/utils/directive-parser.ts +529 -0
- package/src/utils/format.ts +10 -0
- package/src/utils/startup-timer.ts +102 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entries.d.ts","sourceRoot":"","sources":["../../src/plugins/entries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAGnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"entries.d.ts","sourceRoot":"","sources":["../../src/plugins/entries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAGnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AA8FhD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CA2FxD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timber-js/app",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Vite-native React framework for Cloudflare Workers — correct HTTP semantics, real status codes, pages that work without JavaScript",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -77,6 +77,7 @@
|
|
|
77
77
|
},
|
|
78
78
|
"files": [
|
|
79
79
|
"dist",
|
|
80
|
+
"src",
|
|
80
81
|
"bin",
|
|
81
82
|
"README.md",
|
|
82
83
|
"LICENSE"
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
// Cloudflare Workers adapter
|
|
2
|
+
//
|
|
3
|
+
// Primary deployment target. Generates a Workers-compatible entry point
|
|
4
|
+
// and wrangler.jsonc configuration. See design/11-platform.md §"Cloudflare Workers".
|
|
5
|
+
|
|
6
|
+
import { writeFile, mkdir, cp } from 'node:fs/promises';
|
|
7
|
+
import { execFile } from 'node:child_process';
|
|
8
|
+
import { join, relative } from 'node:path';
|
|
9
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
10
|
+
import type { TimberPlatformAdapter, TimberConfig } from './types';
|
|
11
|
+
// Inlined from server/asset-headers.ts — adapters are loaded by Node at
|
|
12
|
+
// Vite startup time, before Vite's module resolver is available, so cross-
|
|
13
|
+
// directory .ts imports don't resolve.
|
|
14
|
+
const IMMUTABLE_CACHE = 'public, max-age=31536000, immutable';
|
|
15
|
+
const STATIC_CACHE = 'public, max-age=3600, must-revalidate';
|
|
16
|
+
|
|
17
|
+
function generateHeadersFile(): string {
|
|
18
|
+
return `# Auto-generated by @timber/app — static asset cache headers.
|
|
19
|
+
# See design/25-production-deployments.md §"CDN / Edge Cache"
|
|
20
|
+
|
|
21
|
+
/assets/*
|
|
22
|
+
Cache-Control: ${IMMUTABLE_CACHE}
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
Cache-Control: ${STATIC_CACHE}
|
|
26
|
+
`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ─── Bindings passthrough ─────────────────────────────────────────────────
|
|
30
|
+
// ALS stores the env object per-request so server components and middleware
|
|
31
|
+
// can access KV, D1, DO, R2, Queues, etc. via getCloudflareBindings().
|
|
32
|
+
// No global fallback — if called outside a request, it throws.
|
|
33
|
+
// See design/11-platform.md §"Platform Target" and design/25-production-deployments.md.
|
|
34
|
+
|
|
35
|
+
const bindingsAls = new AsyncLocalStorage<Record<string, unknown>>();
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get Cloudflare Worker bindings for the current request.
|
|
39
|
+
*
|
|
40
|
+
* Returns the `env` object passed to the Worker's `fetch` handler,
|
|
41
|
+
* giving direct access to KV, D1, Durable Objects, R2, Queues, and
|
|
42
|
+
* any other bindings configured in `wrangler.jsonc`.
|
|
43
|
+
*
|
|
44
|
+
* Must be called within a request context (server component, middleware,
|
|
45
|
+
* server action). Throws outside a request.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* import { getCloudflareBindings } from '@timber/app/adapters/cloudflare'
|
|
50
|
+
*
|
|
51
|
+
* export default async function Page() {
|
|
52
|
+
* const { MY_KV, MY_DB } = getCloudflareBindings()
|
|
53
|
+
* const data = await MY_KV.get('key')
|
|
54
|
+
* return <div>{data}</div>
|
|
55
|
+
* }
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export function getCloudflareBindings<
|
|
59
|
+
T extends Record<string, unknown> = Record<string, unknown>,
|
|
60
|
+
>(): T {
|
|
61
|
+
const env = bindingsAls.getStore();
|
|
62
|
+
if (!env) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
'getCloudflareBindings() called outside a Cloudflare Workers request context. ' +
|
|
65
|
+
'It can only be called from server components, middleware, or server actions ' +
|
|
66
|
+
'when running on the Cloudflare adapter.'
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return env as T;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Run a function with Cloudflare bindings available via getCloudflareBindings().
|
|
74
|
+
* @internal Used by wrapWithExecutionContext.
|
|
75
|
+
*/
|
|
76
|
+
export function runWithBindings<T>(env: Record<string, unknown>, fn: () => T): T {
|
|
77
|
+
return bindingsAls.run(env, fn);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Options for the Cloudflare Workers adapter. */
|
|
81
|
+
export interface CloudflareAdapterOptions {
|
|
82
|
+
/**
|
|
83
|
+
* Cloudflare compatibility date.
|
|
84
|
+
* @default Current date in YYYY-MM-DD format at build time.
|
|
85
|
+
*/
|
|
86
|
+
compatibilityDate?: string;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Additional compatibility flags.
|
|
90
|
+
* @default ['nodejs_compat']
|
|
91
|
+
*/
|
|
92
|
+
compatibilityFlags?: string[];
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Custom wrangler.jsonc fields to merge.
|
|
96
|
+
* Overrides generated values.
|
|
97
|
+
*/
|
|
98
|
+
wrangler?: Record<string, unknown>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Create a Cloudflare Workers adapter.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```ts
|
|
106
|
+
* import { cloudflare } from '@timber/app/adapters/cloudflare'
|
|
107
|
+
*
|
|
108
|
+
* export default {
|
|
109
|
+
* output: 'server',
|
|
110
|
+
* adapter: cloudflare(),
|
|
111
|
+
* }
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
export function cloudflare(options: CloudflareAdapterOptions = {}): TimberPlatformAdapter {
|
|
115
|
+
return {
|
|
116
|
+
name: 'cloudflare',
|
|
117
|
+
|
|
118
|
+
async buildOutput(config: TimberConfig, buildDir: string) {
|
|
119
|
+
const outDir = join(buildDir, 'cloudflare');
|
|
120
|
+
await mkdir(outDir, { recursive: true });
|
|
121
|
+
|
|
122
|
+
// Copy client assets to static output.
|
|
123
|
+
// When client JavaScript is disabled, skip .js files — only CSS,
|
|
124
|
+
// fonts, images, and other static assets are needed.
|
|
125
|
+
const clientDir = join(buildDir, 'client');
|
|
126
|
+
const staticDir = join(outDir, 'static');
|
|
127
|
+
await mkdir(staticDir, { recursive: true });
|
|
128
|
+
await cp(clientDir, staticDir, {
|
|
129
|
+
recursive: true,
|
|
130
|
+
filter: config.clientJavascriptDisabled ? (src: string) => !src.endsWith('.js') : undefined,
|
|
131
|
+
}).catch(() => {
|
|
132
|
+
// Client dir may not exist when client JavaScript is disabled
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Write _headers file for static asset cache control.
|
|
136
|
+
// Cloudflare Workers Static Assets reads this to set Cache-Control
|
|
137
|
+
// headers on responses. Hashed assets get immutable; others get 1h.
|
|
138
|
+
await writeFile(join(staticDir, '_headers'), generateHeadersFile());
|
|
139
|
+
|
|
140
|
+
// Copy server bundles (rsc + ssr) into the output directory.
|
|
141
|
+
// These are already fully bundled by Vite with resolve.noExternal: true.
|
|
142
|
+
const rscDir = join(buildDir, 'rsc');
|
|
143
|
+
const ssrDir = join(buildDir, 'ssr');
|
|
144
|
+
await cp(rscDir, join(outDir, 'rsc'), { recursive: true });
|
|
145
|
+
await cp(ssrDir, join(outDir, 'ssr'), { recursive: true });
|
|
146
|
+
|
|
147
|
+
// Write the build manifest init module (if manifest data was produced).
|
|
148
|
+
// This must be imported before the RSC handler so the global is set
|
|
149
|
+
// when virtual:timber-build-manifest evaluates.
|
|
150
|
+
if (config.manifestInit) {
|
|
151
|
+
await writeFile(join(outDir, '_timber-manifest-init.js'), config.manifestInit);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Generate the Workers entry point
|
|
155
|
+
const hasManifestInit = !!config.manifestInit;
|
|
156
|
+
const workerEntry = generateWorkerEntry(outDir, outDir, hasManifestInit);
|
|
157
|
+
await writeFile(join(outDir, '_worker.js'), workerEntry);
|
|
158
|
+
|
|
159
|
+
// Generate wrangler.jsonc
|
|
160
|
+
const wranglerConfig = generateWranglerConfig(config, options);
|
|
161
|
+
await writeFile(join(outDir, 'wrangler.jsonc'), JSON.stringify(wranglerConfig, null, 2));
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
async preview(_config: TimberConfig, buildDir: string) {
|
|
165
|
+
const cmd = generatePreviewCommand(buildDir);
|
|
166
|
+
await spawnPreviewProcess(cmd.command, cmd.args, cmd.cwd);
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
// Default no-op. wrapWithExecutionContext() replaces this per-request
|
|
170
|
+
// with a function that routes to ctx.waitUntil().
|
|
171
|
+
waitUntil(_promise: Promise<unknown>) {},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Wrap a timber request handler to bind the Cloudflare execution context
|
|
177
|
+
* for `waitUntil()` support and env bindings passthrough.
|
|
178
|
+
* Called from the generated worker entry.
|
|
179
|
+
*
|
|
180
|
+
* This function:
|
|
181
|
+
* 1. Binds `adapter.waitUntil()` to `ctx.waitUntil()` per-request
|
|
182
|
+
* 2. Makes `env` accessible via `getCloudflareBindings()` per-request via ALS
|
|
183
|
+
*/
|
|
184
|
+
export function wrapWithExecutionContext(
|
|
185
|
+
adapter: TimberPlatformAdapter,
|
|
186
|
+
handler: (req: Request) => Promise<Response>
|
|
187
|
+
): ExportedHandler<Record<string, unknown>> {
|
|
188
|
+
return {
|
|
189
|
+
async fetch(
|
|
190
|
+
request: Request,
|
|
191
|
+
env: Record<string, unknown>,
|
|
192
|
+
ctx: ExecutionContext
|
|
193
|
+
): Promise<Response> {
|
|
194
|
+
// Bind the adapter's waitUntil to the Workers execution context
|
|
195
|
+
const originalWaitUntil = adapter.waitUntil;
|
|
196
|
+
adapter.waitUntil = (promise: Promise<unknown>) => {
|
|
197
|
+
ctx.waitUntil(promise);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
// Run the handler within ALS so getCloudflareBindings() works
|
|
202
|
+
return await runWithBindings(env, () => handler(request));
|
|
203
|
+
} finally {
|
|
204
|
+
// Restore (in case adapter is reused across isolate resets)
|
|
205
|
+
adapter.waitUntil = originalWaitUntil;
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ─── Exported helpers (used by tests and build) ─────────────────────────────
|
|
212
|
+
|
|
213
|
+
/** @internal Exported for testing. */
|
|
214
|
+
export function generateWorkerEntry(
|
|
215
|
+
buildDir: string,
|
|
216
|
+
outDir: string,
|
|
217
|
+
hasManifestInit = false
|
|
218
|
+
): string {
|
|
219
|
+
// The RSC entry is the main request handler — it exports the fetch handler as default.
|
|
220
|
+
// The Vite RSC plugin outputs it to rsc/index.js.
|
|
221
|
+
let rscEntryRelative = relative(outDir, join(buildDir, 'rsc', 'index.js'));
|
|
222
|
+
// Ensure the import path starts with ./ for ESM compatibility
|
|
223
|
+
if (!rscEntryRelative.startsWith('.')) {
|
|
224
|
+
rscEntryRelative = './' + rscEntryRelative;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Build manifest init must be imported before the RSC handler so that
|
|
228
|
+
// globalThis.__TIMBER_BUILD_MANIFEST__ is set when the virtual module evaluates.
|
|
229
|
+
// ESM guarantees imports are evaluated in order.
|
|
230
|
+
const manifestImport = hasManifestInit ? "import './_timber-manifest-init.js'\n" : '';
|
|
231
|
+
|
|
232
|
+
return `// Generated by @timber/app/adapters/cloudflare
|
|
233
|
+
// Do not edit — this file is regenerated on each build.
|
|
234
|
+
|
|
235
|
+
${manifestImport}import handler from '${rscEntryRelative}'
|
|
236
|
+
|
|
237
|
+
// Set TIMBER_RUNTIME for instrumentation.ts conditional SDK initialization.
|
|
238
|
+
// See design/25-production-deployments.md §"TIMBER_RUNTIME".
|
|
239
|
+
globalThis.process ??= { env: {} }
|
|
240
|
+
process.env.TIMBER_RUNTIME = 'cloudflare'
|
|
241
|
+
|
|
242
|
+
export default { fetch: handler }
|
|
243
|
+
`;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** @internal Exported for testing. */
|
|
247
|
+
export function generateWranglerConfig(
|
|
248
|
+
config: TimberConfig,
|
|
249
|
+
options: CloudflareAdapterOptions
|
|
250
|
+
): Record<string, unknown> {
|
|
251
|
+
const compatDate = options.compatibilityDate ?? new Date().toISOString().slice(0, 10);
|
|
252
|
+
|
|
253
|
+
const flags = options.compatibilityFlags ?? ['nodejs_compat'];
|
|
254
|
+
|
|
255
|
+
const base: Record<string, unknown> = {
|
|
256
|
+
name: 'timber-app',
|
|
257
|
+
main: '_worker.js',
|
|
258
|
+
compatibility_date: compatDate,
|
|
259
|
+
compatibility_flags: flags,
|
|
260
|
+
// The build output is already fully bundled by Vite — skip wrangler's
|
|
261
|
+
// esbuild pass to avoid issues with top-level await and module format.
|
|
262
|
+
no_bundle: true,
|
|
263
|
+
find_additional_modules: true,
|
|
264
|
+
rules: [{ type: 'ESModule', globs: ['**/*.js'] }],
|
|
265
|
+
assets: {
|
|
266
|
+
directory: './static',
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
// Merge user overrides
|
|
271
|
+
if (options.wrangler) {
|
|
272
|
+
return { ...base, ...options.wrangler };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return base;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ─── Preview ─────────────────────────────────────────────────────────────────
|
|
279
|
+
|
|
280
|
+
/** Command descriptor for preview — testable without spawning a process. */
|
|
281
|
+
export interface PreviewCommand {
|
|
282
|
+
command: string;
|
|
283
|
+
args: string[];
|
|
284
|
+
cwd: string;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** @internal Exported for testing. */
|
|
288
|
+
export function generatePreviewCommand(buildDir: string): PreviewCommand {
|
|
289
|
+
const cfDir = join(buildDir, 'cloudflare');
|
|
290
|
+
return {
|
|
291
|
+
command: 'wrangler',
|
|
292
|
+
args: ['dev', '--local', '--config', join(cfDir, 'wrangler.jsonc')],
|
|
293
|
+
cwd: cfDir,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Spawn a long-running preview process and pipe stdio to the parent.
|
|
299
|
+
* Resolves when the process exits.
|
|
300
|
+
*/
|
|
301
|
+
function spawnPreviewProcess(command: string, args: string[], cwd: string): Promise<void> {
|
|
302
|
+
return new Promise<void>((resolve, reject) => {
|
|
303
|
+
const child = execFile(command, args, { cwd }, (err) => {
|
|
304
|
+
if (err) reject(err);
|
|
305
|
+
else resolve();
|
|
306
|
+
});
|
|
307
|
+
child.stdout?.pipe(process.stdout);
|
|
308
|
+
child.stderr?.pipe(process.stderr);
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// ─── Cloudflare Workers type stubs ───────────────────────────────────────────
|
|
313
|
+
// Minimal type declarations so this file compiles without @cloudflare/workers-types.
|
|
314
|
+
// In production builds, users install @cloudflare/workers-types themselves.
|
|
315
|
+
|
|
316
|
+
declare global {
|
|
317
|
+
interface ExecutionContext {
|
|
318
|
+
waitUntil(promise: Promise<unknown>): void;
|
|
319
|
+
passThroughOnException(): void;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
interface ExportedHandler<Env = Record<string, unknown>> {
|
|
323
|
+
fetch?(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> | Response;
|
|
324
|
+
}
|
|
325
|
+
}
|