@cloudflare/worker-bundler 0.0.0 → 0.0.3

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 ADDED
@@ -0,0 +1,1681 @@
1
+ import * as esbuild from "esbuild-wasm/lib/browser.js";
2
+ import esbuildWasm from "./esbuild.wasm";
3
+ import { parse } from "es-module-lexer/js";
4
+ import * as resolveExports from "resolve.exports";
5
+ import { parse as parse$1 } from "smol-toml";
6
+ import * as semver from "semver";
7
+ import { transform } from "sucrase";
8
+ //#region src/resolver.ts
9
+ const DEFAULT_EXTENSIONS = [
10
+ ".ts",
11
+ ".tsx",
12
+ ".js",
13
+ ".jsx",
14
+ ".mts",
15
+ ".mjs",
16
+ ".json"
17
+ ];
18
+ /**
19
+ * Resolve a module specifier to a file path in the virtual file system.
20
+ *
21
+ * Handles:
22
+ * - Relative imports (./foo, ../bar)
23
+ * - Package imports (lodash, @scope/pkg)
24
+ * - Package.json exports field
25
+ * - Extension resolution (.ts, .tsx, .js, etc.)
26
+ * - Index file resolution (foo/index.ts)
27
+ *
28
+ * @param specifier - The import specifier (e.g., './utils', 'lodash')
29
+ * @param options - Resolution options
30
+ * @returns Resolved path or external marker
31
+ */
32
+ function resolveModule(specifier, options) {
33
+ const { files, importer = "", conditions = ["import", "browser"], extensions = DEFAULT_EXTENSIONS } = options;
34
+ if (specifier.startsWith(".") || specifier.startsWith("/")) {
35
+ const resolved = resolveRelative(specifier, importer, files, extensions);
36
+ if (resolved) return {
37
+ path: resolved,
38
+ external: false
39
+ };
40
+ throw new Error(`Cannot resolve relative import '${specifier}' from '${importer}'`);
41
+ }
42
+ return resolvePackage(specifier, files, conditions, extensions);
43
+ }
44
+ /**
45
+ * Resolve a relative import
46
+ */
47
+ function resolveRelative(specifier, importer, files, extensions) {
48
+ return resolveWithExtensions(joinPaths(getDirectory$1(importer), specifier), files, extensions);
49
+ }
50
+ /**
51
+ * Resolve a package specifier
52
+ */
53
+ function resolvePackage(specifier, files, conditions, extensions) {
54
+ const { packageName, subpath } = parsePackageSpecifier(specifier);
55
+ const packageJson = files[`node_modules/${packageName}/package.json`];
56
+ if (!packageJson) return {
57
+ path: specifier,
58
+ external: true
59
+ };
60
+ let pkg;
61
+ try {
62
+ pkg = JSON.parse(packageJson);
63
+ } catch {
64
+ throw new Error(`Invalid package.json for ${packageName}`);
65
+ }
66
+ const entrySubpath = subpath ? `./${subpath}` : ".";
67
+ try {
68
+ const resolved = resolveExports.resolve(pkg, entrySubpath, { conditions });
69
+ if (resolved && resolved.length > 0) {
70
+ const resolvedPath = resolved[0];
71
+ if (resolvedPath) {
72
+ const fullPath = `node_modules/${packageName}/${normalizeRelativePath(resolvedPath)}`;
73
+ if (fullPath in files) return {
74
+ path: fullPath,
75
+ external: false
76
+ };
77
+ }
78
+ }
79
+ } catch {}
80
+ const legacyEntry = resolveExports.legacy(pkg, { fields: ["module", "main"] });
81
+ if (legacyEntry && typeof legacyEntry === "string") {
82
+ const fullPath = `node_modules/${packageName}/${normalizeRelativePath(legacyEntry)}`;
83
+ if (fullPath in files) return {
84
+ path: fullPath,
85
+ external: false
86
+ };
87
+ }
88
+ const indexPath = resolveWithExtensions(`node_modules/${packageName}${subpath ? `/${subpath}` : ""}`, files, extensions);
89
+ if (indexPath) return {
90
+ path: indexPath,
91
+ external: false
92
+ };
93
+ return {
94
+ path: specifier,
95
+ external: true
96
+ };
97
+ }
98
+ /**
99
+ * Try to resolve a path with various extensions and index files
100
+ */
101
+ function resolveWithExtensions(path, files, extensions) {
102
+ const normalized = normalizePath(path);
103
+ if (normalized in files) return normalized;
104
+ for (const ext of extensions) {
105
+ const withExt = normalized + ext;
106
+ if (withExt in files) return withExt;
107
+ }
108
+ for (const ext of extensions) {
109
+ const indexPath = `${normalized}/index${ext}`;
110
+ if (indexPath in files) return indexPath;
111
+ }
112
+ }
113
+ /**
114
+ * Parse a package specifier into package name and subpath
115
+ */
116
+ function parsePackageSpecifier(specifier) {
117
+ if (specifier.startsWith("@")) {
118
+ const parts = specifier.split("/");
119
+ if (parts.length >= 2) return {
120
+ packageName: `${parts[0]}/${parts[1]}`,
121
+ subpath: parts.slice(2).join("/") || void 0
122
+ };
123
+ }
124
+ const slashIndex = specifier.indexOf("/");
125
+ if (slashIndex === -1) return {
126
+ packageName: specifier,
127
+ subpath: void 0
128
+ };
129
+ return {
130
+ packageName: specifier.slice(0, slashIndex),
131
+ subpath: specifier.slice(slashIndex + 1)
132
+ };
133
+ }
134
+ /**
135
+ * Get the directory of a file path
136
+ */
137
+ function getDirectory$1(filePath) {
138
+ const lastSlash = filePath.lastIndexOf("/");
139
+ if (lastSlash === -1) return "";
140
+ return filePath.slice(0, lastSlash);
141
+ }
142
+ /**
143
+ * Join two paths
144
+ */
145
+ function joinPaths(base, relative) {
146
+ if (relative.startsWith("/")) return relative.slice(1);
147
+ const baseParts = base ? base.split("/") : [];
148
+ const relativeParts = relative.split("/");
149
+ for (const part of relativeParts) if (part === "..") baseParts.pop();
150
+ else if (part !== ".") baseParts.push(part);
151
+ return baseParts.join("/");
152
+ }
153
+ /**
154
+ * Normalize a path (remove ./ prefix, handle multiple slashes)
155
+ */
156
+ function normalizePath(path) {
157
+ return path.replace(/^\.\//, "").replace(/\/+/g, "/").replace(/\/$/, "");
158
+ }
159
+ /**
160
+ * Normalize a relative path from package.json
161
+ */
162
+ function normalizeRelativePath(path) {
163
+ if (path.startsWith("./")) return path.slice(2);
164
+ if (path.startsWith("/")) return path.slice(1);
165
+ return path;
166
+ }
167
+ /**
168
+ * Parse imports from a JavaScript/TypeScript source file.
169
+ *
170
+ * Uses es-module-lexer for accurate parsing of ES module syntax.
171
+ * Falls back to regex for JSX files since es-module-lexer doesn't
172
+ * handle JSX syntax (e.g., `<div>` is not valid JavaScript).
173
+ */
174
+ function parseImports(code) {
175
+ try {
176
+ const [imports] = parse(code);
177
+ const specifiers = [];
178
+ for (const imp of imports) if (imp.n !== void 0) specifiers.push(imp.n);
179
+ return [...new Set(specifiers)];
180
+ } catch {
181
+ return parseImportsRegex(code);
182
+ }
183
+ }
184
+ /**
185
+ * Regex-based fallback for parsing imports.
186
+ * Used when es-module-lexer fails (e.g., on JSX/TSX files).
187
+ */
188
+ function parseImportsRegex(code) {
189
+ const imports = [];
190
+ for (const match of code.matchAll(/import\s+(?:(?:[\w*{}\s,]+)\s+from\s+)?['"]([^'"]+)['"]/g)) {
191
+ const specifier = match[1];
192
+ if (specifier) imports.push(specifier);
193
+ }
194
+ for (const match of code.matchAll(/import\s*\(\s*['"]([^'"]+)['"]\s*\)/g)) {
195
+ const specifier = match[1];
196
+ if (specifier) imports.push(specifier);
197
+ }
198
+ for (const match of code.matchAll(/export\s+(?:[\w*{}\s,]+\s+)?from\s+['"]([^'"]+)['"]/g)) {
199
+ const specifier = match[1];
200
+ if (specifier) imports.push(specifier);
201
+ }
202
+ return [...new Set(imports)];
203
+ }
204
+ //#endregion
205
+ //#region src/bundler.ts
206
+ /**
207
+ * esbuild-wasm bundling functionality.
208
+ */
209
+ /**
210
+ * Bundle files using esbuild-wasm
211
+ */
212
+ async function bundleWithEsbuild(files, entryPoint, externals, target, minify, sourcemap, nodejsCompat) {
213
+ await initializeEsbuild();
214
+ const result = await esbuild.build({
215
+ entryPoints: [entryPoint],
216
+ bundle: true,
217
+ write: false,
218
+ format: "esm",
219
+ platform: nodejsCompat ? "node" : "browser",
220
+ target,
221
+ minify,
222
+ sourcemap: sourcemap ? "inline" : false,
223
+ plugins: [{
224
+ name: "virtual-fs",
225
+ setup(build) {
226
+ build.onResolve({ filter: /.*/ }, (args) => {
227
+ if (args.kind === "entry-point") return {
228
+ path: args.path,
229
+ namespace: "virtual"
230
+ };
231
+ if (args.path.startsWith(".")) {
232
+ const resolved = resolveRelativePath(args.resolveDir, args.path, files);
233
+ if (resolved) return {
234
+ path: resolved,
235
+ namespace: "virtual"
236
+ };
237
+ }
238
+ if (!args.path.startsWith("/") && !args.path.startsWith(".")) {
239
+ if (externals.includes(args.path) || externals.some((e) => args.path.startsWith(`${e}/`) || args.path.startsWith(e))) return {
240
+ path: args.path,
241
+ external: true
242
+ };
243
+ try {
244
+ const result = resolveModule(args.path, { files });
245
+ if (!result.external) return {
246
+ path: result.path,
247
+ namespace: "virtual"
248
+ };
249
+ } catch {}
250
+ return {
251
+ path: args.path,
252
+ external: true
253
+ };
254
+ }
255
+ const normalizedPath = args.path.startsWith("/") ? args.path.slice(1) : args.path;
256
+ if (normalizedPath in files) return {
257
+ path: normalizedPath,
258
+ namespace: "virtual"
259
+ };
260
+ return {
261
+ path: args.path,
262
+ external: true
263
+ };
264
+ });
265
+ build.onLoad({
266
+ filter: /.*/,
267
+ namespace: "virtual"
268
+ }, (args) => {
269
+ const content = files[args.path];
270
+ if (content === void 0) return { errors: [{ text: `File not found: ${args.path}` }] };
271
+ const loader = getLoader(args.path);
272
+ const lastSlash = args.path.lastIndexOf("/");
273
+ return {
274
+ contents: content,
275
+ loader,
276
+ resolveDir: lastSlash >= 0 ? args.path.slice(0, lastSlash) : ""
277
+ };
278
+ });
279
+ }
280
+ }],
281
+ outfile: "bundle.js"
282
+ });
283
+ const output = result.outputFiles?.[0];
284
+ if (!output) throw new Error("No output generated from esbuild");
285
+ const modules = { "bundle.js": output.text };
286
+ const warnings = result.warnings.map((w) => w.text);
287
+ if (warnings.length > 0) return {
288
+ mainModule: "bundle.js",
289
+ modules,
290
+ warnings
291
+ };
292
+ return {
293
+ mainModule: "bundle.js",
294
+ modules
295
+ };
296
+ }
297
+ /**
298
+ * Resolve a relative path against a directory within the virtual filesystem.
299
+ */
300
+ function resolveRelativePath(resolveDir, relativePath, files) {
301
+ const dir = resolveDir.replace(/^\//, "");
302
+ const parts = dir ? dir.split("/") : [];
303
+ const relParts = relativePath.split("/");
304
+ for (const part of relParts) if (part === "..") parts.pop();
305
+ else if (part !== ".") parts.push(part);
306
+ const resolved = parts.join("/");
307
+ if (resolved in files) return resolved;
308
+ const extensions = [
309
+ ".ts",
310
+ ".tsx",
311
+ ".js",
312
+ ".jsx",
313
+ ".mts",
314
+ ".mjs"
315
+ ];
316
+ for (const ext of extensions) if (resolved + ext in files) return resolved + ext;
317
+ for (const ext of extensions) {
318
+ const indexPath = `${resolved}/index${ext}`;
319
+ if (indexPath in files) return indexPath;
320
+ }
321
+ }
322
+ function getLoader(path) {
323
+ if (path.endsWith(".ts") || path.endsWith(".mts")) return "ts";
324
+ if (path.endsWith(".tsx")) return "tsx";
325
+ if (path.endsWith(".jsx")) return "jsx";
326
+ if (path.endsWith(".json")) return "json";
327
+ if (path.endsWith(".css")) return "css";
328
+ return "js";
329
+ }
330
+ let esbuildInitialized = false;
331
+ let esbuildInitializePromise = null;
332
+ /**
333
+ * Initialize the esbuild bundler.
334
+ * This is called automatically when needed.
335
+ */
336
+ async function initializeEsbuild() {
337
+ if (esbuildInitialized) return;
338
+ if (esbuildInitializePromise) return esbuildInitializePromise;
339
+ esbuildInitializePromise = (async () => {
340
+ try {
341
+ await esbuild.initialize({
342
+ wasmModule: esbuildWasm,
343
+ worker: false
344
+ });
345
+ esbuildInitialized = true;
346
+ } catch (error) {
347
+ if (error instanceof Error && error.message.includes("Cannot call \"initialize\" more than once")) {
348
+ esbuildInitialized = true;
349
+ return;
350
+ }
351
+ throw error;
352
+ }
353
+ })();
354
+ try {
355
+ await esbuildInitializePromise;
356
+ } catch (error) {
357
+ esbuildInitializePromise = null;
358
+ throw error;
359
+ }
360
+ }
361
+ //#endregion
362
+ //#region src/config.ts
363
+ /**
364
+ * Wrangler configuration parsing.
365
+ *
366
+ * Parses wrangler.toml, wrangler.json, and wrangler.jsonc files
367
+ * to extract compatibility settings.
368
+ */
369
+ /**
370
+ * Parse wrangler configuration from files.
371
+ *
372
+ * Looks for wrangler.toml, wrangler.json, or wrangler.jsonc in the files
373
+ * and extracts compatibility_date and compatibility_flags.
374
+ *
375
+ * @param files - Virtual file system
376
+ * @returns Parsed wrangler config, or undefined if no config file found
377
+ */
378
+ function parseWranglerConfig(files) {
379
+ const tomlContent = files["wrangler.toml"];
380
+ if (tomlContent) return parseWranglerToml(tomlContent);
381
+ const jsonContent = files["wrangler.json"];
382
+ if (jsonContent) return parseWranglerJson(jsonContent);
383
+ const jsoncContent = files["wrangler.jsonc"];
384
+ if (jsoncContent) return parseWranglerJsonc(jsoncContent);
385
+ }
386
+ /**
387
+ * Parse wrangler.toml content
388
+ */
389
+ function parseWranglerToml(content) {
390
+ try {
391
+ return extractWranglerConfig(parse$1(content));
392
+ } catch {
393
+ return {};
394
+ }
395
+ }
396
+ /**
397
+ * Parse wrangler.json content
398
+ */
399
+ function parseWranglerJson(content) {
400
+ try {
401
+ return extractWranglerConfig(JSON.parse(content));
402
+ } catch {
403
+ return {};
404
+ }
405
+ }
406
+ /**
407
+ * Parse wrangler.jsonc content (JSON with comments)
408
+ */
409
+ function parseWranglerJsonc(content) {
410
+ try {
411
+ const jsonContent = stripJsonComments(content);
412
+ return extractWranglerConfig(JSON.parse(jsonContent));
413
+ } catch {
414
+ return {};
415
+ }
416
+ }
417
+ /**
418
+ * Extract wrangler config fields from parsed config object.
419
+ * Handles both snake_case (toml) and camelCase (json) formats.
420
+ */
421
+ function extractWranglerConfig(config) {
422
+ const result = {};
423
+ const main = config["main"];
424
+ if (typeof main === "string") result.main = main;
425
+ const date = config["compatibility_date"] ?? config["compatibilityDate"];
426
+ if (typeof date === "string") result.compatibilityDate = date;
427
+ const flags = config["compatibility_flags"] ?? config["compatibilityFlags"];
428
+ if (Array.isArray(flags) && flags.every((f) => typeof f === "string")) result.compatibilityFlags = flags;
429
+ return result;
430
+ }
431
+ /**
432
+ * Strip comments from JSONC content.
433
+ * Handles both single-line (//) and multi-line comments.
434
+ */
435
+ function stripJsonComments(content) {
436
+ let result = "";
437
+ let i = 0;
438
+ let inString = false;
439
+ let stringChar = "";
440
+ while (i < content.length) {
441
+ const char = content[i];
442
+ const nextChar = content[i + 1];
443
+ if ((char === "\"" || char === "'") && (i === 0 || content[i - 1] !== "\\")) {
444
+ if (!inString) {
445
+ inString = true;
446
+ stringChar = char;
447
+ } else if (char === stringChar) inString = false;
448
+ result += char;
449
+ i++;
450
+ continue;
451
+ }
452
+ if (inString) {
453
+ result += char;
454
+ i++;
455
+ continue;
456
+ }
457
+ if (char === "/" && nextChar === "/") {
458
+ while (i < content.length && content[i] !== "\n") i++;
459
+ continue;
460
+ }
461
+ if (char === "/" && nextChar === "*") {
462
+ i += 2;
463
+ while (i < content.length - 1 && !(content[i] === "*" && content[i + 1] === "/")) i++;
464
+ i += 2;
465
+ continue;
466
+ }
467
+ result += char;
468
+ i++;
469
+ }
470
+ return result;
471
+ }
472
+ /**
473
+ * Check if nodejs_compat flag is enabled in the config.
474
+ */
475
+ function hasNodejsCompat(config) {
476
+ return config?.compatibilityFlags?.includes("nodejs_compat") ?? false;
477
+ }
478
+ //#endregion
479
+ //#region src/installer.ts
480
+ /**
481
+ * NPM package installer for virtual file systems.
482
+ *
483
+ * This module fetches packages from the npm registry and populates
484
+ * a virtual node_modules directory structure.
485
+ */
486
+ const NPM_REGISTRY = "https://registry.npmjs.org";
487
+ const DEFAULT_TIMEOUT_MS = 3e4;
488
+ /**
489
+ * Fetch with a timeout.
490
+ * Throws an error if the request takes longer than the specified timeout.
491
+ */
492
+ async function fetchWithTimeout(url, options = {}, timeoutMs = DEFAULT_TIMEOUT_MS) {
493
+ const controller = new AbortController();
494
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
495
+ try {
496
+ return await fetch(url, {
497
+ ...options,
498
+ signal: controller.signal
499
+ });
500
+ } catch (error) {
501
+ if (error instanceof Error && error.name === "AbortError") throw new Error(`Request timed out after ${timeoutMs}ms: ${url}`);
502
+ throw error;
503
+ } finally {
504
+ clearTimeout(timeoutId);
505
+ }
506
+ }
507
+ /**
508
+ * Install npm dependencies into a virtual file system.
509
+ *
510
+ * Reads the package.json from the files, resolves all dependencies,
511
+ * and populates node_modules with the package contents.
512
+ *
513
+ * @param files - Virtual file system containing package.json
514
+ * @param options - Installation options
515
+ * @returns Files with node_modules populated
516
+ */
517
+ async function installDependencies(files, options = {}) {
518
+ const { dev = false, registry = NPM_REGISTRY } = options;
519
+ const result = {
520
+ files: { ...files },
521
+ installed: [],
522
+ warnings: []
523
+ };
524
+ const packageJsonContent = files["package.json"];
525
+ if (!packageJsonContent) return result;
526
+ let packageJson;
527
+ try {
528
+ packageJson = JSON.parse(packageJsonContent);
529
+ } catch {
530
+ result.warnings.push("Failed to parse package.json");
531
+ return result;
532
+ }
533
+ const depsToInstall = {
534
+ ...packageJson.dependencies,
535
+ ...dev ? packageJson.devDependencies : {}
536
+ };
537
+ if (Object.keys(depsToInstall).length === 0) return result;
538
+ const installedPackages = /* @__PURE__ */ new Map();
539
+ const inProgress = /* @__PURE__ */ new Map();
540
+ await Promise.all(Object.entries(depsToInstall).map(([name, versionRange]) => installPackage(name, versionRange, result, installedPackages, inProgress, registry)));
541
+ return result;
542
+ }
543
+ /**
544
+ * Install a single package and its dependencies recursively.
545
+ */
546
+ async function installPackage(name, versionRange, result, installedPackages, inProgress, registry) {
547
+ if (installedPackages.has(name)) return;
548
+ const existing = inProgress.get(name);
549
+ if (existing) return existing;
550
+ const installPromise = (async () => {
551
+ try {
552
+ const metadata = await fetchPackageMetadata(name, registry);
553
+ const version = resolveVersion(versionRange, metadata);
554
+ if (!version) {
555
+ result.warnings.push(`Could not resolve version for ${name}@${versionRange}`);
556
+ return;
557
+ }
558
+ const versionMetadata = metadata.versions[version];
559
+ if (!versionMetadata) {
560
+ result.warnings.push(`Version ${version} not found for ${name}`);
561
+ return;
562
+ }
563
+ installedPackages.set(name, version);
564
+ result.installed.push(`${name}@${version}`);
565
+ const packageFiles = await fetchPackageFiles(name, versionMetadata);
566
+ for (const [filePath, content] of Object.entries(packageFiles)) result.files[`node_modules/${name}/${filePath}`] = content;
567
+ const deps = versionMetadata.dependencies ?? {};
568
+ await Promise.all(Object.entries(deps).map(([depName, depVersion]) => installPackage(depName, depVersion, result, installedPackages, inProgress, registry)));
569
+ } catch (error) {
570
+ const message = error instanceof Error ? error.message : String(error);
571
+ result.warnings.push(`Failed to install ${name}: ${message}`);
572
+ }
573
+ })();
574
+ inProgress.set(name, installPromise);
575
+ try {
576
+ await installPromise;
577
+ } finally {
578
+ inProgress.delete(name);
579
+ }
580
+ }
581
+ /**
582
+ * Fetch package metadata from npm registry.
583
+ */
584
+ async function fetchPackageMetadata(name, registry) {
585
+ const response = await fetchWithTimeout(`${registry}/${name.startsWith("@") ? `@${encodeURIComponent(name.slice(1))}` : name}`, { headers: { Accept: "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8" } });
586
+ if (!response.ok) throw new Error(`Failed to fetch package metadata: ${response.status}`);
587
+ return await response.json();
588
+ }
589
+ /**
590
+ * Resolve a semver range to a specific version.
591
+ */
592
+ function resolveVersion(range, metadata) {
593
+ if (range === "latest" || range === "*") return metadata["dist-tags"]["latest"];
594
+ if (metadata.versions[range]) return range;
595
+ if (metadata["dist-tags"][range]) return metadata["dist-tags"][range];
596
+ const versions = Object.keys(metadata.versions);
597
+ return semver.maxSatisfying(versions, range) ?? void 0;
598
+ }
599
+ /**
600
+ * Fetch and extract package files from npm tarball.
601
+ */
602
+ async function fetchPackageFiles(name, metadata) {
603
+ const tarballUrl = metadata.dist?.tarball;
604
+ if (!tarballUrl) throw new Error(`No tarball URL for ${name}`);
605
+ const response = await fetchWithTimeout(tarballUrl, {}, DEFAULT_TIMEOUT_MS * 2);
606
+ if (!response.ok) throw new Error(`Failed to fetch tarball: ${response.status}`);
607
+ const buffer = await response.arrayBuffer();
608
+ return extractTarball(new Uint8Array(buffer));
609
+ }
610
+ /**
611
+ * Extract files from a gzipped tarball.
612
+ *
613
+ * npm packages are distributed as .tgz files (gzipped tar).
614
+ * The contents are in a "package/" directory.
615
+ */
616
+ async function extractTarball(data) {
617
+ return parseTar(await decompress(data));
618
+ }
619
+ /**
620
+ * Decompress gzip data using DecompressionStream.
621
+ */
622
+ async function decompress(data) {
623
+ const ds = new DecompressionStream("gzip");
624
+ const writer = ds.writable.getWriter();
625
+ const reader = ds.readable.getReader();
626
+ writer.write(data).catch(() => {});
627
+ writer.close().catch(() => {});
628
+ const chunks = [];
629
+ let totalLength = 0;
630
+ while (true) {
631
+ const { done, value } = await reader.read();
632
+ if (done) break;
633
+ chunks.push(value);
634
+ totalLength += value.length;
635
+ }
636
+ const result = new Uint8Array(totalLength);
637
+ let offset = 0;
638
+ for (const chunk of chunks) {
639
+ result.set(chunk, offset);
640
+ offset += chunk.length;
641
+ }
642
+ return result;
643
+ }
644
+ /**
645
+ * Parse a tar archive and extract text files.
646
+ *
647
+ * TAR format:
648
+ * - 512-byte header blocks
649
+ * - File content (padded to 512 bytes)
650
+ * - Two empty blocks at the end
651
+ */
652
+ function parseTar(data) {
653
+ const files = {};
654
+ const textDecoder = new TextDecoder();
655
+ let offset = 0;
656
+ while (offset < data.length - 512) {
657
+ const header = data.slice(offset, offset + 512);
658
+ if (header.every((b) => b === 0)) break;
659
+ const name = readString(header, 0, 100);
660
+ const sizeStr = readString(header, 124, 12);
661
+ const typeFlag = header[156];
662
+ const size = parseInt(sizeStr.trim(), 8) || 0;
663
+ offset += 512;
664
+ if ((typeFlag === 48 || typeFlag === 0) && size > 0) {
665
+ const content = data.slice(offset, offset + size);
666
+ let filePath = name;
667
+ if (filePath.startsWith("package/")) filePath = filePath.slice(8);
668
+ if (isTextFile(filePath)) try {
669
+ files[filePath] = textDecoder.decode(content);
670
+ } catch {}
671
+ }
672
+ offset += Math.ceil(size / 512) * 512;
673
+ }
674
+ return files;
675
+ }
676
+ /**
677
+ * Read a null-terminated string from a buffer.
678
+ */
679
+ function readString(buffer, offset, length) {
680
+ const bytes = buffer.slice(offset, offset + length);
681
+ const nullIndex = bytes.indexOf(0);
682
+ const relevantBytes = nullIndex >= 0 ? bytes.slice(0, nullIndex) : bytes;
683
+ return new TextDecoder().decode(relevantBytes);
684
+ }
685
+ /**
686
+ * Check if a file path is likely a text file.
687
+ */
688
+ function isTextFile(path) {
689
+ const textExtensions = [
690
+ ".js",
691
+ ".mjs",
692
+ ".cjs",
693
+ ".ts",
694
+ ".mts",
695
+ ".cts",
696
+ ".tsx",
697
+ ".jsx",
698
+ ".json",
699
+ ".md",
700
+ ".txt",
701
+ ".css",
702
+ ".html",
703
+ ".yml",
704
+ ".yaml",
705
+ ".toml",
706
+ ".xml",
707
+ ".svg",
708
+ ".map",
709
+ ".d.ts",
710
+ ".d.mts",
711
+ ".d.cts"
712
+ ];
713
+ const configFiles = [
714
+ "LICENSE",
715
+ "README",
716
+ "CHANGELOG",
717
+ "package.json",
718
+ "tsconfig.json",
719
+ ".npmignore",
720
+ ".gitignore"
721
+ ];
722
+ const fileName = path.split("/").pop() ?? "";
723
+ if (configFiles.some((f) => fileName.toUpperCase().startsWith(f.toUpperCase()))) return true;
724
+ return textExtensions.some((ext) => path.toLowerCase().endsWith(ext));
725
+ }
726
+ /**
727
+ * Check if files contain a package.json with dependencies that need installing.
728
+ */
729
+ function hasDependencies(files) {
730
+ const packageJson = files["package.json"];
731
+ if (!packageJson) return false;
732
+ try {
733
+ const deps = JSON.parse(packageJson).dependencies ?? {};
734
+ return Object.keys(deps).length > 0;
735
+ } catch {
736
+ return false;
737
+ }
738
+ }
739
+ //#endregion
740
+ //#region src/transformer.ts
741
+ /**
742
+ * Transform TypeScript/JSX code to JavaScript using Sucrase.
743
+ *
744
+ * Sucrase is a super-fast TypeScript transformer that:
745
+ * - Strips type annotations
746
+ * - Transforms JSX
747
+ * - Is ~20x faster than Babel
748
+ * - Works in any JS environment (no WASM needed)
749
+ *
750
+ * @param code - Source code to transform
751
+ * @param options - Transform options
752
+ * @returns Transformed code
753
+ */
754
+ function transformCode(code, options) {
755
+ const { filePath, sourceMap = false, jsxRuntime = "automatic", jsxImportSource = "react", production = false } = options;
756
+ const transforms = [];
757
+ if (isTypeScriptFile(filePath)) transforms.push("typescript");
758
+ if (isJsxFile(filePath)) {
759
+ if (jsxRuntime !== "preserve") transforms.push("jsx");
760
+ }
761
+ if (transforms.length === 0) return { code };
762
+ const transformOptions = {
763
+ transforms,
764
+ filePath,
765
+ jsxRuntime,
766
+ jsxImportSource,
767
+ production,
768
+ preserveDynamicImport: true,
769
+ disableESTransforms: true
770
+ };
771
+ if (sourceMap) transformOptions.sourceMapOptions = { compiledFilename: filePath.replace(/\.(tsx?|mts)$/, ".js") };
772
+ const result = transform(code, transformOptions);
773
+ if (result.sourceMap) return {
774
+ code: result.code,
775
+ sourceMap: JSON.stringify(result.sourceMap)
776
+ };
777
+ return { code: result.code };
778
+ }
779
+ /**
780
+ * Check if a file path is a TypeScript file
781
+ */
782
+ function isTypeScriptFile(filePath) {
783
+ return /\.(ts|tsx|mts)$/.test(filePath);
784
+ }
785
+ /**
786
+ * Check if a file path is a JSX file
787
+ */
788
+ function isJsxFile(filePath) {
789
+ return /\.(jsx|tsx)$/.test(filePath);
790
+ }
791
+ /**
792
+ * Check if a file path is any JavaScript/TypeScript file
793
+ */
794
+ function isJavaScriptFile(filePath) {
795
+ return /\.(js|jsx|ts|tsx|mjs|mts)$/.test(filePath);
796
+ }
797
+ /**
798
+ * Get the output path for a transformed file
799
+ */
800
+ function getOutputPath(filePath) {
801
+ return filePath.replace(/\.tsx?$/, ".js").replace(/\.mts$/, ".mjs");
802
+ }
803
+ /**
804
+ * Transform all files and resolve their dependencies.
805
+ * This produces multiple modules instead of a single bundle.
806
+ */
807
+ async function transformAndResolve(files, entryPoint, externals) {
808
+ const modules = {};
809
+ const warnings = [];
810
+ const processed = /* @__PURE__ */ new Set();
811
+ const toProcess = [entryPoint];
812
+ const pathMap = /* @__PURE__ */ new Map();
813
+ while (toProcess.length > 0) {
814
+ const filePath = toProcess.pop();
815
+ if (!filePath || processed.has(filePath)) continue;
816
+ processed.add(filePath);
817
+ const content = files[filePath];
818
+ if (content === void 0) {
819
+ warnings.push(`File not found: ${filePath}`);
820
+ continue;
821
+ }
822
+ const outputPath = isTypeScriptFile(filePath) ? getOutputPath(filePath) : filePath;
823
+ pathMap.set(filePath, outputPath);
824
+ if (!isJavaScriptFile(filePath)) {
825
+ if (filePath.endsWith(".json")) try {
826
+ modules[filePath] = { json: JSON.parse(content) };
827
+ } catch {
828
+ warnings.push(`Failed to parse JSON file: ${filePath}`);
829
+ }
830
+ else modules[filePath] = { text: content };
831
+ continue;
832
+ }
833
+ const imports = parseImports(content);
834
+ for (const specifier of imports) {
835
+ if (externals.includes(specifier) || externals.some((e) => specifier.startsWith(`${e}/`) || specifier.startsWith(e))) continue;
836
+ try {
837
+ const resolved = resolveModule(specifier, {
838
+ files,
839
+ importer: filePath
840
+ });
841
+ if (!resolved.external && !processed.has(resolved.path)) toProcess.push(resolved.path);
842
+ } catch (error) {
843
+ warnings.push(`Failed to resolve '${specifier}' from ${filePath}: ${error instanceof Error ? error.message : error}`);
844
+ }
845
+ }
846
+ }
847
+ for (const [sourcePath, outputPath] of pathMap) {
848
+ const content = files[sourcePath];
849
+ if (content === void 0 || !isJavaScriptFile(sourcePath)) continue;
850
+ let transformedCode;
851
+ if (isTypeScriptFile(sourcePath)) try {
852
+ transformedCode = transformCode(content, { filePath: sourcePath }).code;
853
+ } catch (error) {
854
+ warnings.push(`Failed to transform ${sourcePath}: ${error instanceof Error ? error.message : error}`);
855
+ continue;
856
+ }
857
+ else transformedCode = content;
858
+ transformedCode = rewriteImports(transformedCode, sourcePath, files, pathMap, externals);
859
+ modules[outputPath] = transformedCode;
860
+ }
861
+ const mainModule = isTypeScriptFile(entryPoint) ? getOutputPath(entryPoint) : entryPoint;
862
+ if (warnings.length > 0) return {
863
+ mainModule,
864
+ modules,
865
+ warnings
866
+ };
867
+ return {
868
+ mainModule,
869
+ modules
870
+ };
871
+ }
872
+ /**
873
+ * Rewrite import specifiers to use full output paths.
874
+ * This is necessary because the Worker Loader expects imports to match registered module names.
875
+ */
876
+ function rewriteImports(code, importer, files, pathMap, externals) {
877
+ const importExportRegex = /(import\s+(?:[\w*{}\s,]+\s+from\s+)?|export\s+(?:[\w*{}\s,]+\s+)?from\s+)(['"])([^'"]+)\2/g;
878
+ const importerOutputPath = pathMap.get(importer) ?? importer;
879
+ return code.replace(importExportRegex, (match, prefix, quote, specifier) => {
880
+ if (externals.includes(specifier) || externals.some((e) => specifier.startsWith(`${e}/`) || specifier.startsWith(e))) return match;
881
+ if (!specifier.startsWith(".") && !specifier.startsWith("/")) try {
882
+ const resolved = resolveModule(specifier, {
883
+ files,
884
+ importer
885
+ });
886
+ if (resolved.external) return match;
887
+ const resolvedOutputPath = pathMap.get(resolved.path) ?? resolved.path;
888
+ if (resolved.path.startsWith("node_modules/")) return `${prefix}${quote}/${resolvedOutputPath}${quote}`;
889
+ return `${prefix}${quote}${calculateRelativePath(importerOutputPath, resolvedOutputPath)}${quote}`;
890
+ } catch {
891
+ return match;
892
+ }
893
+ try {
894
+ const resolved = resolveModule(specifier, {
895
+ files,
896
+ importer
897
+ });
898
+ if (resolved.external) return match;
899
+ return `${prefix}${quote}${calculateRelativePath(importerOutputPath, pathMap.get(resolved.path) ?? resolved.path)}${quote}`;
900
+ } catch {
901
+ return match;
902
+ }
903
+ });
904
+ }
905
+ /**
906
+ * Calculate relative path from one file to another.
907
+ */
908
+ function calculateRelativePath(from, to) {
909
+ const fromDir = getDirectory(from);
910
+ const toDir = getDirectory(to);
911
+ const toFile = to.split("/").pop() ?? to;
912
+ if (fromDir === toDir) return `./${toFile}`;
913
+ const fromParts = fromDir ? fromDir.split("/") : [];
914
+ const toParts = toDir ? toDir.split("/") : [];
915
+ let commonLength = 0;
916
+ while (commonLength < fromParts.length && commonLength < toParts.length && fromParts[commonLength] === toParts[commonLength]) commonLength++;
917
+ const upCount = fromParts.length - commonLength;
918
+ const downParts = toParts.slice(commonLength);
919
+ let relativePath = "";
920
+ if (upCount === 0) relativePath = "./";
921
+ else relativePath = "../".repeat(upCount);
922
+ if (downParts.length > 0) relativePath += `${downParts.join("/")}/`;
923
+ return relativePath + toFile;
924
+ }
925
+ function getDirectory(filePath) {
926
+ const lastSlash = filePath.lastIndexOf("/");
927
+ if (lastSlash === -1) return "";
928
+ return filePath.slice(0, lastSlash);
929
+ }
930
+ //#endregion
931
+ //#region src/utils.ts
932
+ /**
933
+ * Detect entry point from wrangler config, package.json, or use defaults.
934
+ * Priority: wrangler main > package.json exports/module/main > default paths
935
+ */
936
+ function detectEntryPoint(files, wranglerConfig) {
937
+ if (wranglerConfig?.main) return normalizeEntryPath(wranglerConfig.main);
938
+ const packageJsonContent = files["package.json"];
939
+ if (packageJsonContent) try {
940
+ const pkg = JSON.parse(packageJsonContent);
941
+ if (pkg.exports) {
942
+ if (typeof pkg.exports === "string") return normalizeEntryPath(pkg.exports);
943
+ const dotExport = pkg.exports["."];
944
+ if (dotExport) {
945
+ if (typeof dotExport === "string") return normalizeEntryPath(dotExport);
946
+ if (typeof dotExport === "object" && dotExport !== null) {
947
+ const exp = dotExport;
948
+ const entry = exp["import"] ?? exp["default"] ?? exp["module"];
949
+ if (typeof entry === "string") return normalizeEntryPath(entry);
950
+ }
951
+ }
952
+ }
953
+ if (pkg.module) return normalizeEntryPath(pkg.module);
954
+ if (pkg.main) return normalizeEntryPath(pkg.main);
955
+ } catch {}
956
+ for (const entry of [
957
+ "src/index.ts",
958
+ "src/index.js",
959
+ "src/index.mts",
960
+ "src/index.mjs",
961
+ "index.ts",
962
+ "index.js",
963
+ "src/worker.ts",
964
+ "src/worker.js"
965
+ ]) if (entry in files) return entry;
966
+ }
967
+ function normalizeEntryPath(path) {
968
+ if (path.startsWith("./")) return path.slice(2);
969
+ return path;
970
+ }
971
+ //#endregion
972
+ //#region src/experimental.ts
973
+ let warningShown = false;
974
+ function showExperimentalWarning(fn) {
975
+ if (!warningShown) {
976
+ warningShown = true;
977
+ console.warn(`[worker-bundler] ${fn}(): This package is experimental and its API may change without notice.`);
978
+ }
979
+ }
980
+ //#endregion
981
+ //#region src/mime.ts
982
+ /**
983
+ * MIME type inference from file extensions.
984
+ */
985
+ const MIME_TYPES = {
986
+ ".html": "text/html; charset=utf-8",
987
+ ".htm": "text/html; charset=utf-8",
988
+ ".js": "application/javascript; charset=utf-8",
989
+ ".mjs": "application/javascript; charset=utf-8",
990
+ ".css": "text/css; charset=utf-8",
991
+ ".json": "application/json; charset=utf-8",
992
+ ".png": "image/png",
993
+ ".jpg": "image/jpeg",
994
+ ".jpeg": "image/jpeg",
995
+ ".gif": "image/gif",
996
+ ".svg": "image/svg+xml",
997
+ ".ico": "image/x-icon",
998
+ ".webp": "image/webp",
999
+ ".avif": "image/avif",
1000
+ ".woff": "font/woff",
1001
+ ".woff2": "font/woff2",
1002
+ ".ttf": "font/ttf",
1003
+ ".otf": "font/otf",
1004
+ ".eot": "application/vnd.ms-fontobject",
1005
+ ".mp3": "audio/mpeg",
1006
+ ".mp4": "video/mp4",
1007
+ ".webm": "video/webm",
1008
+ ".ogg": "audio/ogg",
1009
+ ".wav": "audio/wav",
1010
+ ".pdf": "application/pdf",
1011
+ ".xml": "application/xml",
1012
+ ".txt": "text/plain; charset=utf-8",
1013
+ ".csv": "text/csv; charset=utf-8",
1014
+ ".zip": "application/zip",
1015
+ ".gz": "application/gzip",
1016
+ ".tar": "application/x-tar",
1017
+ ".wasm": "application/wasm",
1018
+ ".webmanifest": "application/manifest+json",
1019
+ ".map": "application/json"
1020
+ };
1021
+ /**
1022
+ * Get the file extension from a path (including the dot).
1023
+ */
1024
+ function getExtension(path) {
1025
+ const lastDot = path.lastIndexOf(".");
1026
+ if (lastDot === -1) return "";
1027
+ if (lastDot < path.lastIndexOf("/")) return "";
1028
+ return path.slice(lastDot).toLowerCase();
1029
+ }
1030
+ /**
1031
+ * Infer MIME type from a file path.
1032
+ * Returns undefined if the type is unknown.
1033
+ */
1034
+ function inferContentType(path) {
1035
+ return MIME_TYPES[getExtension(path)];
1036
+ }
1037
+ /**
1038
+ * Whether a content type represents a text-based format
1039
+ * (used to decide text vs binary module storage).
1040
+ */
1041
+ function isTextContentType(contentType) {
1042
+ return contentType.startsWith("text/") || contentType.includes("json") || contentType.includes("xml") || contentType.includes("javascript") || contentType.includes("svg");
1043
+ }
1044
+ //#endregion
1045
+ //#region src/asset-handler.ts
1046
+ /**
1047
+ * Asset request handler for serving static assets.
1048
+ *
1049
+ * Key design: the manifest (routing metadata) is separated from the
1050
+ * storage (content retrieval). This lets you plug in any backend —
1051
+ * in-memory, KV, R2, Workspace, etc.
1052
+ *
1053
+ * Inspired by Cloudflare's Workers Static Assets behavior and
1054
+ * cloudflare-asset-worker by Timo Wilhelm.
1055
+ */
1056
+ /**
1057
+ * Create an in-memory storage backend from a pathname->content map.
1058
+ * This is the zero-config default for small asset sets.
1059
+ */
1060
+ function createMemoryStorage(assets) {
1061
+ const map = new Map(Object.entries(assets));
1062
+ return { get(pathname) {
1063
+ return Promise.resolve(map.get(pathname) ?? null);
1064
+ } };
1065
+ }
1066
+ /**
1067
+ * Normalize user config with defaults.
1068
+ */
1069
+ function normalizeConfig(config) {
1070
+ const staticRedirects = {};
1071
+ if (config?.redirects?.static) {
1072
+ let lineNumber = 1;
1073
+ for (const [path, rule] of Object.entries(config.redirects.static)) staticRedirects[path] = {
1074
+ ...rule,
1075
+ lineNumber: lineNumber++
1076
+ };
1077
+ }
1078
+ return {
1079
+ html_handling: config?.html_handling ?? "auto-trailing-slash",
1080
+ not_found_handling: config?.not_found_handling ?? "none",
1081
+ redirects: {
1082
+ static: staticRedirects,
1083
+ dynamic: config?.redirects?.dynamic ?? {}
1084
+ },
1085
+ headers: config?.headers ?? {}
1086
+ };
1087
+ }
1088
+ /**
1089
+ * Compute a simple hash for ETag generation.
1090
+ * Uses a fast string hash (FNV-1a) for text, or SHA-256 for binary.
1091
+ */
1092
+ async function computeETag(content) {
1093
+ if (typeof content === "string") {
1094
+ let hash = 2166136261;
1095
+ for (let i = 0; i < content.length; i++) {
1096
+ hash ^= content.charCodeAt(i);
1097
+ hash = hash * 16777619 >>> 0;
1098
+ }
1099
+ return hash.toString(16).padStart(8, "0");
1100
+ }
1101
+ const hashBuffer = await crypto.subtle.digest("SHA-256", content);
1102
+ return [...new Uint8Array(hashBuffer).slice(0, 8)].map((b) => b.toString(16).padStart(2, "0")).join("");
1103
+ }
1104
+ /**
1105
+ * Build an AssetManifest from a pathname->content mapping.
1106
+ * Only computes metadata (content types, ETags) — doesn't store content.
1107
+ */
1108
+ async function buildAssetManifest(assets) {
1109
+ const manifest = /* @__PURE__ */ new Map();
1110
+ const entries = Object.entries(assets);
1111
+ await Promise.all(entries.map(async ([pathname, content]) => {
1112
+ const contentType = inferContentType(pathname);
1113
+ const etag = await computeETag(content);
1114
+ manifest.set(pathname, {
1115
+ contentType,
1116
+ etag
1117
+ });
1118
+ }));
1119
+ return manifest;
1120
+ }
1121
+ /**
1122
+ * Convenience: build both a manifest and an in-memory storage from assets.
1123
+ */
1124
+ async function buildAssets(assets) {
1125
+ return {
1126
+ manifest: await buildAssetManifest(assets),
1127
+ storage: createMemoryStorage(assets)
1128
+ };
1129
+ }
1130
+ /**
1131
+ * Check if a pathname exists in the manifest.
1132
+ */
1133
+ function exists(manifest, pathname) {
1134
+ return manifest.get(pathname);
1135
+ }
1136
+ const ESCAPE_REGEX_CHARACTERS = /[-/\\^$*+?.()|[\]{}]/g;
1137
+ const escapeRegex = (s) => s.replaceAll(ESCAPE_REGEX_CHARACTERS, String.raw`\$&`);
1138
+ const PLACEHOLDER_REGEX = /:([A-Za-z]\w*)/g;
1139
+ function replacer(str, replacements) {
1140
+ for (const [key, value] of Object.entries(replacements)) str = str.replaceAll(`:${key}`, value);
1141
+ return str;
1142
+ }
1143
+ function generateRuleRegExp(rule) {
1144
+ rule = rule.split("*").map((s) => escapeRegex(s)).join("(?<splat>.*)");
1145
+ const matches = rule.matchAll(PLACEHOLDER_REGEX);
1146
+ for (const match of matches) rule = rule.split(match[0]).join(`(?<${match[1]}>[^/]+)`);
1147
+ return new RegExp("^" + rule + "$");
1148
+ }
1149
+ function matchStaticRedirects(config, host, pathname) {
1150
+ const withHost = config.redirects.static[`https://${host}${pathname}`];
1151
+ const withoutHost = config.redirects.static[pathname];
1152
+ if (withHost && withoutHost) return withHost.lineNumber < withoutHost.lineNumber ? withHost : withoutHost;
1153
+ return withHost || withoutHost;
1154
+ }
1155
+ function matchDynamicRedirects(config, request) {
1156
+ const { pathname } = new URL(request.url);
1157
+ for (const [pattern, rule] of Object.entries(config.redirects.dynamic)) try {
1158
+ const result = generateRuleRegExp(pattern).exec(pathname);
1159
+ if (result) {
1160
+ const target = replacer(rule.to, result.groups || {}).trim();
1161
+ return {
1162
+ status: rule.status,
1163
+ to: target
1164
+ };
1165
+ }
1166
+ } catch {}
1167
+ }
1168
+ function handleRedirects(request, config) {
1169
+ const url = new URL(request.url);
1170
+ const { search, host } = url;
1171
+ let { pathname } = url;
1172
+ const staticMatch = matchStaticRedirects(config, host, pathname);
1173
+ const dynamicMatch = staticMatch ? void 0 : matchDynamicRedirects(config, request);
1174
+ const match = staticMatch ?? dynamicMatch;
1175
+ let proxied = false;
1176
+ if (match) if (match.status === 200) {
1177
+ pathname = new URL(match.to, request.url).pathname;
1178
+ proxied = true;
1179
+ } else {
1180
+ const destination = new URL(match.to, request.url);
1181
+ const location = destination.origin === url.origin ? `${destination.pathname}${destination.search || search}${destination.hash}` : `${destination.href}`;
1182
+ return new Response(null, {
1183
+ status: match.status,
1184
+ headers: { Location: location }
1185
+ });
1186
+ }
1187
+ return {
1188
+ proxied,
1189
+ pathname
1190
+ };
1191
+ }
1192
+ function generateGlobRegExp(pattern) {
1193
+ const escaped = pattern.split("*").map((s) => escapeRegex(s)).join(".*");
1194
+ return new RegExp("^" + escaped + "$");
1195
+ }
1196
+ function attachCustomHeaders(request, response, config) {
1197
+ if (Object.keys(config.headers).length === 0) return response;
1198
+ const { pathname } = new URL(request.url);
1199
+ const setMap = /* @__PURE__ */ new Set();
1200
+ for (const [pattern, rules] of Object.entries(config.headers)) {
1201
+ try {
1202
+ if (!generateGlobRegExp(pattern).test(pathname)) continue;
1203
+ } catch {
1204
+ continue;
1205
+ }
1206
+ if (rules.unset) for (const key of rules.unset) response.headers.delete(key);
1207
+ if (rules.set) for (const [key, value] of Object.entries(rules.set)) if (setMap.has(key.toLowerCase())) response.headers.append(key, value);
1208
+ else {
1209
+ response.headers.set(key, value);
1210
+ setMap.add(key.toLowerCase());
1211
+ }
1212
+ }
1213
+ return response;
1214
+ }
1215
+ function decodePath(pathname) {
1216
+ return pathname.split("/").map((segment) => {
1217
+ try {
1218
+ return decodeURIComponent(segment);
1219
+ } catch {
1220
+ return segment;
1221
+ }
1222
+ }).join("/").replaceAll(/\/+/g, "/");
1223
+ }
1224
+ function encodePath(pathname) {
1225
+ return pathname.split("/").map((segment) => {
1226
+ try {
1227
+ return encodeURIComponent(segment);
1228
+ } catch {
1229
+ return segment;
1230
+ }
1231
+ }).join("/");
1232
+ }
1233
+ function getIntent(pathname, manifest, config, skipRedirects = false, acceptsHtml = true) {
1234
+ switch (config.html_handling) {
1235
+ case "auto-trailing-slash": return htmlAutoTrailingSlash(pathname, manifest, config, skipRedirects, acceptsHtml);
1236
+ case "force-trailing-slash": return htmlForceTrailingSlash(pathname, manifest, config, skipRedirects, acceptsHtml);
1237
+ case "drop-trailing-slash": return htmlDropTrailingSlash(pathname, manifest, config, skipRedirects, acceptsHtml);
1238
+ case "none": return htmlNone(pathname, manifest, config, acceptsHtml);
1239
+ }
1240
+ }
1241
+ function assetIntent(pathname, meta, status = 200) {
1242
+ return {
1243
+ type: "asset",
1244
+ pathname,
1245
+ meta,
1246
+ status
1247
+ };
1248
+ }
1249
+ function redirectIntent(to) {
1250
+ return {
1251
+ type: "redirect",
1252
+ to
1253
+ };
1254
+ }
1255
+ /**
1256
+ * Safe redirect: only redirect if the file exists and the destination
1257
+ * itself resolves to the same asset (avoids redirect loops).
1258
+ */
1259
+ function safeRedirect(file, destination, manifest, config, skip) {
1260
+ if (skip) return void 0;
1261
+ if (!exists(manifest, destination)) {
1262
+ const intent = getIntent(destination, manifest, config, true);
1263
+ if (intent?.type === "asset" && intent.meta.etag === exists(manifest, file)?.etag) return redirectIntent(destination);
1264
+ }
1265
+ }
1266
+ function htmlAutoTrailingSlash(pathname, manifest, config, skipRedirects, acceptsHtml) {
1267
+ let meta;
1268
+ let redirect;
1269
+ const exactMeta = exists(manifest, pathname);
1270
+ if (pathname.endsWith("/index")) {
1271
+ if (exactMeta) return assetIntent(pathname, exactMeta);
1272
+ if (redirect = safeRedirect(`${pathname}.html`, pathname.slice(0, -5), manifest, config, skipRedirects)) return redirect;
1273
+ if (redirect = safeRedirect(`${pathname.slice(0, -6)}.html`, pathname.slice(0, -6), manifest, config, skipRedirects)) return redirect;
1274
+ } else if (pathname.endsWith("/index.html")) {
1275
+ if (redirect = safeRedirect(pathname, pathname.slice(0, -10), manifest, config, skipRedirects)) return redirect;
1276
+ if (redirect = safeRedirect(`${pathname.slice(0, -11)}.html`, pathname.slice(0, -11), manifest, config, skipRedirects)) return redirect;
1277
+ } else if (pathname.endsWith("/")) {
1278
+ const indexPath = `${pathname}index.html`;
1279
+ if (meta = exists(manifest, indexPath)) return assetIntent(indexPath, meta);
1280
+ if (redirect = safeRedirect(`${pathname.slice(0, -1)}.html`, pathname.slice(0, -1), manifest, config, skipRedirects)) return redirect;
1281
+ } else if (pathname.endsWith(".html")) {
1282
+ if (redirect = safeRedirect(pathname, pathname.slice(0, -5), manifest, config, skipRedirects)) return redirect;
1283
+ if (redirect = safeRedirect(`${pathname.slice(0, -5)}/index.html`, `${pathname.slice(0, -5)}/`, manifest, config, skipRedirects)) return redirect;
1284
+ }
1285
+ if (exactMeta) return assetIntent(pathname, exactMeta);
1286
+ const htmlPath = `${pathname}.html`;
1287
+ if (meta = exists(manifest, htmlPath)) return assetIntent(htmlPath, meta);
1288
+ if (redirect = safeRedirect(`${pathname}/index.html`, `${pathname}/`, manifest, config, skipRedirects)) return redirect;
1289
+ return notFound(pathname, manifest, config, acceptsHtml);
1290
+ }
1291
+ function htmlForceTrailingSlash(pathname, manifest, config, skipRedirects, acceptsHtml) {
1292
+ let meta;
1293
+ let redirect;
1294
+ const exactMeta = exists(manifest, pathname);
1295
+ if (pathname.endsWith("/index")) {
1296
+ if (exactMeta) return assetIntent(pathname, exactMeta);
1297
+ if (redirect = safeRedirect(`${pathname}.html`, pathname.slice(0, -5), manifest, config, skipRedirects)) return redirect;
1298
+ if (redirect = safeRedirect(`${pathname.slice(0, -6)}.html`, pathname.slice(0, -5), manifest, config, skipRedirects)) return redirect;
1299
+ } else if (pathname.endsWith("/index.html")) {
1300
+ if (redirect = safeRedirect(pathname, pathname.slice(0, -10), manifest, config, skipRedirects)) return redirect;
1301
+ if (redirect = safeRedirect(`${pathname.slice(0, -11)}.html`, pathname.slice(0, -10), manifest, config, skipRedirects)) return redirect;
1302
+ } else if (pathname.endsWith("/")) {
1303
+ let p = `${pathname}index.html`;
1304
+ if (meta = exists(manifest, p)) return assetIntent(p, meta);
1305
+ p = `${pathname.slice(0, -1)}.html`;
1306
+ if (meta = exists(manifest, p)) return assetIntent(p, meta);
1307
+ } else if (pathname.endsWith(".html")) {
1308
+ if (redirect = safeRedirect(pathname, `${pathname.slice(0, -5)}/`, manifest, config, skipRedirects)) return redirect;
1309
+ if (exactMeta) return assetIntent(pathname, exactMeta);
1310
+ if (redirect = safeRedirect(`${pathname.slice(0, -5)}/index.html`, `${pathname.slice(0, -5)}/`, manifest, config, skipRedirects)) return redirect;
1311
+ }
1312
+ if (exactMeta) return assetIntent(pathname, exactMeta);
1313
+ if (redirect = safeRedirect(`${pathname}.html`, `${pathname}/`, manifest, config, skipRedirects)) return redirect;
1314
+ if (redirect = safeRedirect(`${pathname}/index.html`, `${pathname}/`, manifest, config, skipRedirects)) return redirect;
1315
+ return notFound(pathname, manifest, config, acceptsHtml);
1316
+ }
1317
+ function htmlDropTrailingSlash(pathname, manifest, config, skipRedirects, acceptsHtml) {
1318
+ let meta;
1319
+ let redirect;
1320
+ const exactMeta = exists(manifest, pathname);
1321
+ if (pathname.endsWith("/index")) {
1322
+ if (exactMeta) return assetIntent(pathname, exactMeta);
1323
+ if (pathname === "/index") {
1324
+ if (redirect = safeRedirect("/index.html", "/", manifest, config, skipRedirects)) return redirect;
1325
+ } else {
1326
+ if (redirect = safeRedirect(`${pathname.slice(0, -6)}.html`, pathname.slice(0, -6), manifest, config, skipRedirects)) return redirect;
1327
+ if (redirect = safeRedirect(`${pathname}.html`, pathname.slice(0, -6), manifest, config, skipRedirects)) return redirect;
1328
+ }
1329
+ } else if (pathname.endsWith("/index.html")) if (pathname === "/index.html") {
1330
+ if (redirect = safeRedirect("/index.html", "/", manifest, config, skipRedirects)) return redirect;
1331
+ } else {
1332
+ if (redirect = safeRedirect(pathname, pathname.slice(0, -11), manifest, config, skipRedirects)) return redirect;
1333
+ if (exactMeta) return assetIntent(pathname, exactMeta);
1334
+ if (redirect = safeRedirect(`${pathname.slice(0, -11)}.html`, pathname.slice(0, -11), manifest, config, skipRedirects)) return redirect;
1335
+ }
1336
+ else if (pathname.endsWith("/")) if (pathname === "/") {
1337
+ if (meta = exists(manifest, "/index.html")) return assetIntent("/index.html", meta);
1338
+ } else {
1339
+ if (redirect = safeRedirect(`${pathname.slice(0, -1)}.html`, pathname.slice(0, -1), manifest, config, skipRedirects)) return redirect;
1340
+ if (redirect = safeRedirect(`${pathname.slice(0, -1)}/index.html`, pathname.slice(0, -1), manifest, config, skipRedirects)) return redirect;
1341
+ }
1342
+ else if (pathname.endsWith(".html")) {
1343
+ if (redirect = safeRedirect(pathname, pathname.slice(0, -5), manifest, config, skipRedirects)) return redirect;
1344
+ if (redirect = safeRedirect(`${pathname.slice(0, -5)}/index.html`, pathname.slice(0, -5), manifest, config, skipRedirects)) return redirect;
1345
+ }
1346
+ if (exactMeta) return assetIntent(pathname, exactMeta);
1347
+ let p = `${pathname}.html`;
1348
+ if (meta = exists(manifest, p)) return assetIntent(p, meta);
1349
+ p = `${pathname}/index.html`;
1350
+ if (meta = exists(manifest, p)) return assetIntent(p, meta);
1351
+ return notFound(pathname, manifest, config, acceptsHtml);
1352
+ }
1353
+ function htmlNone(pathname, manifest, config, acceptsHtml) {
1354
+ const meta = exists(manifest, pathname);
1355
+ return meta ? assetIntent(pathname, meta) : notFound(pathname, manifest, config, acceptsHtml);
1356
+ }
1357
+ function notFound(pathname, manifest, config, acceptsHtml = true) {
1358
+ switch (config.not_found_handling) {
1359
+ case "single-page-application": {
1360
+ if (!acceptsHtml) return void 0;
1361
+ const meta = exists(manifest, "/index.html");
1362
+ if (meta) return assetIntent("/index.html", meta, 200);
1363
+ return;
1364
+ }
1365
+ case "404-page": {
1366
+ let cwd = pathname;
1367
+ while (cwd) {
1368
+ cwd = cwd.slice(0, cwd.lastIndexOf("/"));
1369
+ const p = `${cwd}/404.html`;
1370
+ const meta = exists(manifest, p);
1371
+ if (meta) return assetIntent(p, meta, 404);
1372
+ }
1373
+ return;
1374
+ }
1375
+ default: return;
1376
+ }
1377
+ }
1378
+ const CACHE_CONTROL_REVALIDATE = "public, max-age=0, must-revalidate";
1379
+ const CACHE_CONTROL_IMMUTABLE = "public, max-age=31536000, immutable";
1380
+ function getCacheControl(pathname) {
1381
+ if (/\.[a-f0-9]{8,}\.\w+$/.test(pathname)) return CACHE_CONTROL_IMMUTABLE;
1382
+ return CACHE_CONTROL_REVALIDATE;
1383
+ }
1384
+ /**
1385
+ * Handle an asset request. Returns a Response if an asset matches,
1386
+ * or null if the request should fall through to the user's Worker.
1387
+ *
1388
+ * @param request - The incoming HTTP request
1389
+ * @param manifest - Asset manifest (pathname -> metadata)
1390
+ * @param storage - Storage backend for fetching content
1391
+ * @param config - Asset serving configuration
1392
+ */
1393
+ async function handleAssetRequest(request, manifest, storage, config) {
1394
+ const normalized = normalizeConfig(config);
1395
+ const method = request.method.toUpperCase();
1396
+ if (!["GET", "HEAD"].includes(method)) return null;
1397
+ const redirectResult = handleRedirects(request, normalized);
1398
+ if (redirectResult instanceof Response) return attachCustomHeaders(request, redirectResult, normalized);
1399
+ const { pathname } = redirectResult;
1400
+ const decodedPathname = decodePath(pathname);
1401
+ const intent = getIntent(decodedPathname, manifest, normalized, false, (request.headers.get("Accept") || "").includes("text/html"));
1402
+ if (!intent) return null;
1403
+ if (intent.type === "redirect") {
1404
+ const url = new URL(request.url);
1405
+ const encodedDest = encodePath(intent.to);
1406
+ return attachCustomHeaders(request, new Response(null, {
1407
+ status: 307,
1408
+ headers: { Location: encodedDest + url.search }
1409
+ }), normalized);
1410
+ }
1411
+ const encodedPathname = encodePath(decodedPathname);
1412
+ if (encodedPathname !== pathname) {
1413
+ const url = new URL(request.url);
1414
+ return attachCustomHeaders(request, new Response(null, {
1415
+ status: 307,
1416
+ headers: { Location: encodedPathname + url.search }
1417
+ }), normalized);
1418
+ }
1419
+ const { pathname: assetPath, meta, status } = intent;
1420
+ const strongETag = `"${meta.etag}"`;
1421
+ const weakETag = `W/${strongETag}`;
1422
+ const ifNoneMatch = request.headers.get("If-None-Match") || "";
1423
+ const eTags = new Set(ifNoneMatch.split(",").map((t) => t.trim()));
1424
+ const headers = new Headers();
1425
+ headers.set("ETag", strongETag);
1426
+ if (meta.contentType) headers.set("Content-Type", meta.contentType);
1427
+ headers.set("Cache-Control", getCacheControl(decodedPathname));
1428
+ if (eTags.has(weakETag) || eTags.has(strongETag)) return attachCustomHeaders(request, new Response(null, {
1429
+ status: 304,
1430
+ headers
1431
+ }), normalized);
1432
+ let body = null;
1433
+ if (method !== "HEAD") body = await storage.get(assetPath);
1434
+ return attachCustomHeaders(request, new Response(body, {
1435
+ status,
1436
+ headers
1437
+ }), normalized);
1438
+ }
1439
+ //#endregion
1440
+ //#region src/_asset-runtime-code.ts
1441
+ const ASSET_RUNTIME_CODE = "var L={\".html\":\"text/html; charset=utf-8\",\".htm\":\"text/html; charset=utf-8\",\".js\":\"application/javascript; charset=utf-8\",\".mjs\":\"application/javascript; charset=utf-8\",\".css\":\"text/css; charset=utf-8\",\".json\":\"application/json; charset=utf-8\",\".png\":\"image/png\",\".jpg\":\"image/jpeg\",\".jpeg\":\"image/jpeg\",\".gif\":\"image/gif\",\".svg\":\"image/svg+xml\",\".ico\":\"image/x-icon\",\".webp\":\"image/webp\",\".avif\":\"image/avif\",\".woff\":\"font/woff\",\".woff2\":\"font/woff2\",\".ttf\":\"font/ttf\",\".otf\":\"font/otf\",\".eot\":\"application/vnd.ms-fontobject\",\".mp3\":\"audio/mpeg\",\".mp4\":\"video/mp4\",\".webm\":\"video/webm\",\".ogg\":\"audio/ogg\",\".wav\":\"audio/wav\",\".pdf\":\"application/pdf\",\".xml\":\"application/xml\",\".txt\":\"text/plain; charset=utf-8\",\".csv\":\"text/csv; charset=utf-8\",\".zip\":\"application/zip\",\".gz\":\"application/gzip\",\".tar\":\"application/x-tar\",\".wasm\":\"application/wasm\",\".webmanifest\":\"application/manifest+json\",\".map\":\"application/json\"};function P(t){let e=t.lastIndexOf(\".\");if(e===-1)return\"\";let n=t.lastIndexOf(\"/\");return e<n?\"\":t.slice(e).toLowerCase()}function M(t){let e=P(t);return L[e]}function O(t){let e=new Map(Object.entries(t));return{get(n){return Promise.resolve(e.get(n)??null)}}}function W(t){let e={};if(t?.redirects?.static){let n=1;for(let[s,i]of Object.entries(t.redirects.static))e[s]={...i,lineNumber:n++}}return{html_handling:t?.html_handling??\"auto-trailing-slash\",not_found_handling:t?.not_found_handling??\"none\",redirects:{static:e,dynamic:t?.redirects?.dynamic??{}},headers:t?.headers??{}}}async function U(t){if(typeof t==\"string\"){let s=2166136261;for(let i=0;i<t.length;i++)s^=t.charCodeAt(i),s=s*16777619>>>0;return s.toString(16).padStart(8,\"0\")}let e=await crypto.subtle.digest(\"SHA-256\",t);return[...new Uint8Array(e).slice(0,8)].map(s=>s.toString(16).padStart(2,\"0\")).join(\"\")}async function H(t){let e=new Map,n=Object.entries(t);return await Promise.all(n.map(async([s,i])=>{let l=M(s),r=await U(i);e.set(s,{contentType:l,etag:r})})),e}async function lt(t){let e=await H(t),n=O(t);return{manifest:e,storage:n}}function d(t,e){return t.get(e)}var B=/[-/\\\\^$*+?.()|[\\]{}]/g,j=t=>t.replaceAll(B,String.raw`\\$&`),D=/:([A-Za-z]\\w*)/g;function G(t,e){for(let[n,s]of Object.entries(e))t=t.replaceAll(`:${n}`,s);return t}function F(t){t=t.split(\"*\").map(n=>j(n)).join(\"(?<splat>.*)\");let e=t.matchAll(D);for(let n of e)t=t.split(n[0]).join(`(?<${n[1]}>[^/]+)`);return new RegExp(\"^\"+t+\"$\")}function X(t,e,n){let s=t.redirects.static[`https://${e}${n}`],i=t.redirects.static[n];return s&&i?s.lineNumber<i.lineNumber?s:i:s||i}function V(t,e){let{pathname:n}=new URL(e.url);for(let[s,i]of Object.entries(t.redirects.dynamic))try{let r=F(s).exec(n);if(r){let o=G(i.to,r.groups||{}).trim();return{status:i.status,to:o}}}catch{}}function Y(t,e){let n=new URL(t.url),{search:s,host:i}=n,{pathname:l}=n,r=X(e,i,l),o=r?void 0:V(e,t),c=r??o,h=!1;if(c)if(c.status===200)l=new URL(c.to,t.url).pathname,h=!0;else{let g=new URL(c.to,t.url),x=g.origin===n.origin?`${g.pathname}${g.search||s}${g.hash}`:`${g.href}`;return new Response(null,{status:c.status,headers:{Location:x}})}return{proxied:h,pathname:l}}function Z(t){let e=t.split(\"*\").map(n=>j(n)).join(\".*\");return new RegExp(\"^\"+e+\"$\")}function A(t,e,n){if(Object.keys(n.headers).length===0)return e;let{pathname:s}=new URL(t.url),i=new Set;for(let[l,r]of Object.entries(n.headers)){try{if(!Z(l).test(s))continue}catch{continue}if(r.unset)for(let o of r.unset)e.headers.delete(o);if(r.set)for(let[o,c]of Object.entries(r.set))i.has(o.toLowerCase())?e.headers.append(o,c):(e.headers.set(o,c),i.add(o.toLowerCase()))}return e}function J(t){return t.split(\"/\").map(e=>{try{return decodeURIComponent(e)}catch{return e}}).join(\"/\").replaceAll(/\\/+/g,\"/\")}function E(t){return t.split(\"/\").map(e=>{try{return encodeURIComponent(e)}catch{return e}}).join(\"/\")}function I(t,e,n,s=!1,i=!0){switch(n.html_handling){case\"auto-trailing-slash\":return Q(t,e,n,s,i);case\"force-trailing-slash\":return q(t,e,n,s,i);case\"drop-trailing-slash\":return k(t,e,n,s,i);case\"none\":return tt(t,e,n,i)}}function a(t,e,n=200){return{type:\"asset\",pathname:t,meta:e,status:n}}function K(t){return{type:\"redirect\",to:t}}function u(t,e,n,s,i){if(!i&&!d(n,e)){let l=I(e,n,s,!0);if(l?.type===\"asset\"&&l.meta.etag===d(n,t)?.etag)return K(e)}}function Q(t,e,n,s,i){let l,r,o=d(e,t);if(t.endsWith(\"/index\")){if(o)return a(t,o);if((r=u(`${t}.html`,t.slice(0,-5),e,n,s))||(r=u(`${t.slice(0,-6)}.html`,t.slice(0,-6),e,n,s)))return r}else if(t.endsWith(\"/index.html\")){if((r=u(t,t.slice(0,-10),e,n,s))||(r=u(`${t.slice(0,-11)}.html`,t.slice(0,-11),e,n,s)))return r}else if(t.endsWith(\"/\")){let h=`${t}index.html`;if(l=d(e,h))return a(h,l);if(r=u(`${t.slice(0,-1)}.html`,t.slice(0,-1),e,n,s))return r}else if(t.endsWith(\".html\")&&((r=u(t,t.slice(0,-5),e,n,s))||(r=u(`${t.slice(0,-5)}/index.html`,`${t.slice(0,-5)}/`,e,n,s))))return r;if(o)return a(t,o);let c=`${t}.html`;return(l=d(e,c))?a(c,l):(r=u(`${t}/index.html`,`${t}/`,e,n,s))?r:b(t,e,n,i)}function q(t,e,n,s,i){let l,r,o=d(e,t);if(t.endsWith(\"/index\")){if(o)return a(t,o);if((r=u(`${t}.html`,t.slice(0,-5),e,n,s))||(r=u(`${t.slice(0,-6)}.html`,t.slice(0,-5),e,n,s)))return r}else if(t.endsWith(\"/index.html\")){if((r=u(t,t.slice(0,-10),e,n,s))||(r=u(`${t.slice(0,-11)}.html`,t.slice(0,-10),e,n,s)))return r}else if(t.endsWith(\"/\")){let c=`${t}index.html`;if((l=d(e,c))||(c=`${t.slice(0,-1)}.html`,l=d(e,c)))return a(c,l)}else if(t.endsWith(\".html\")){if(r=u(t,`${t.slice(0,-5)}/`,e,n,s))return r;if(o)return a(t,o);if(r=u(`${t.slice(0,-5)}/index.html`,`${t.slice(0,-5)}/`,e,n,s))return r}return o?a(t,o):(r=u(`${t}.html`,`${t}/`,e,n,s))||(r=u(`${t}/index.html`,`${t}/`,e,n,s))?r:b(t,e,n,i)}function k(t,e,n,s,i){let l,r,o=d(e,t);if(t.endsWith(\"/index\")){if(o)return a(t,o);if(t===\"/index\"){if(r=u(\"/index.html\",\"/\",e,n,s))return r}else if((r=u(`${t.slice(0,-6)}.html`,t.slice(0,-6),e,n,s))||(r=u(`${t}.html`,t.slice(0,-6),e,n,s)))return r}else if(t.endsWith(\"/index.html\"))if(t===\"/index.html\"){if(r=u(\"/index.html\",\"/\",e,n,s))return r}else{if(r=u(t,t.slice(0,-11),e,n,s))return r;if(o)return a(t,o);if(r=u(`${t.slice(0,-11)}.html`,t.slice(0,-11),e,n,s))return r}else if(t.endsWith(\"/\")){if(t===\"/\"){if(l=d(e,\"/index.html\"))return a(\"/index.html\",l)}else if((r=u(`${t.slice(0,-1)}.html`,t.slice(0,-1),e,n,s))||(r=u(`${t.slice(0,-1)}/index.html`,t.slice(0,-1),e,n,s)))return r}else if(t.endsWith(\".html\")&&((r=u(t,t.slice(0,-5),e,n,s))||(r=u(`${t.slice(0,-5)}/index.html`,t.slice(0,-5),e,n,s))))return r;if(o)return a(t,o);let c=`${t}.html`;return(l=d(e,c))||(c=`${t}/index.html`,l=d(e,c))?a(c,l):b(t,e,n,i)}function tt(t,e,n,s){let i=d(e,t);return i?a(t,i):b(t,e,n,s)}function b(t,e,n,s=!0){switch(n.not_found_handling){case\"single-page-application\":{if(!s)return;let i=d(e,\"/index.html\");return i?a(\"/index.html\",i,200):void 0}case\"404-page\":{let i=t;for(;i;){i=i.slice(0,i.lastIndexOf(\"/\"));let l=`${i}/404.html`,r=d(e,l);if(r)return a(l,r,404)}return}default:return}}var et=\"public, max-age=0, must-revalidate\",nt=\"public, max-age=31536000, immutable\";function rt(t){return/\\.[a-f0-9]{8,}\\.\\w+$/.test(t)?nt:et}async function ot(t,e,n,s){let i=W(s),l=t.method.toUpperCase();if(![\"GET\",\"HEAD\"].includes(l))return null;let r=Y(t,i);if(r instanceof Response)return A(t,r,i);let{pathname:o}=r,c=J(o),g=(t.headers.get(\"Accept\")||\"\").includes(\"text/html\"),x=I(c,e,i,!1,g);if(!x)return null;if(x.type===\"redirect\"){let f=new URL(t.url),y=E(x.to),v=new Response(null,{status:307,headers:{Location:y+f.search}});return A(t,v,i)}let $=E(c);if($!==o){let f=new URL(t.url),y=new Response(null,{status:307,headers:{Location:$+f.search}});return A(t,y,i)}let{pathname:N,meta:p,status:S}=x,w=`\"${p.etag}\"`,_=`W/${w}`,z=t.headers.get(\"If-None-Match\")||\"\",C=new Set(z.split(\",\").map(f=>f.trim())),m=new Headers;if(m.set(\"ETag\",w),p.contentType&&m.set(\"Content-Type\",p.contentType),m.set(\"Cache-Control\",rt(c)),C.has(_)||C.has(w)){let f=new Response(null,{status:304,headers:m});return A(t,f,i)}let R=null;l!==\"HEAD\"&&(R=await n.get(N));let T=new Response(R,{status:S,headers:m});return A(t,T,i)}export{H as buildAssetManifest,lt as buildAssets,U as computeETag,O as createMemoryStorage,ot as handleAssetRequest,W as normalizeConfig};\n";
1442
+ //#endregion
1443
+ //#region src/app.ts
1444
+ /**
1445
+ * App bundler: builds a full-stack app (server Worker + client bundle + static assets)
1446
+ * for the Worker Loader binding.
1447
+ */
1448
+ /**
1449
+ * Creates a full-stack app bundle from source files.
1450
+ *
1451
+ * This function:
1452
+ * 1. Bundles client entry point(s) for the browser (if provided)
1453
+ * 2. Collects static assets
1454
+ * 3. Bundles the server Worker
1455
+ * 4. Generates a server wrapper that serves assets and falls through to user code
1456
+ * 5. Returns everything ready for the Worker Loader
1457
+ */
1458
+ async function createApp(options) {
1459
+ showExperimentalWarning("createApp");
1460
+ let { files, bundle = true, externals = [], target = "es2022", minify = false, sourcemap = false, registry } = options;
1461
+ externals = ["cloudflare:", ...externals];
1462
+ const wranglerConfig = parseWranglerConfig(files);
1463
+ const nodejsCompat = hasNodejsCompat(wranglerConfig);
1464
+ const installWarnings = [];
1465
+ if (hasDependencies(files)) {
1466
+ const installResult = await installDependencies(files, registry ? { registry } : {});
1467
+ files = installResult.files;
1468
+ installWarnings.push(...installResult.warnings);
1469
+ }
1470
+ const clientEntries = options.client ? Array.isArray(options.client) ? options.client : [options.client] : [];
1471
+ const clientOutputs = {};
1472
+ const clientBundles = [];
1473
+ for (const clientEntry of clientEntries) {
1474
+ if (!(clientEntry in files)) throw new Error(`Client entry point "${clientEntry}" not found in files.`);
1475
+ const bundleModule = (await bundleWithEsbuild(files, clientEntry, externals, "es2022", minify, sourcemap, false)).modules["bundle.js"];
1476
+ if (typeof bundleModule === "string") {
1477
+ const outputPath = `/${clientEntry.replace(/^src\//, "").replace(/\.(tsx?|jsx?)$/, ".js")}`;
1478
+ clientOutputs[outputPath] = bundleModule;
1479
+ clientBundles.push(outputPath);
1480
+ }
1481
+ }
1482
+ const allAssets = {};
1483
+ if (options.assets) for (const [pathname, content] of Object.entries(options.assets)) {
1484
+ const normalizedPath = pathname.startsWith("/") ? pathname : `/${pathname}`;
1485
+ allAssets[normalizedPath] = content;
1486
+ }
1487
+ for (const [pathname, content] of Object.entries(clientOutputs)) allAssets[pathname] = content;
1488
+ const assetManifest = await buildAssetManifest(allAssets);
1489
+ const serverEntry = options.server ?? detectEntryPoint(files, wranglerConfig);
1490
+ if (!serverEntry) throw new Error("Could not determine server entry point. Specify the 'server' option.");
1491
+ if (!(serverEntry in files)) throw new Error(`Server entry point "${serverEntry}" not found in files.`);
1492
+ let serverResult;
1493
+ if (bundle) serverResult = await bundleWithEsbuild(files, serverEntry, externals, target, minify, sourcemap, nodejsCompat);
1494
+ else serverResult = await transformAndResolve(files, serverEntry, externals);
1495
+ const modules = { ...serverResult.modules };
1496
+ for (const [pathname, content] of Object.entries(allAssets)) {
1497
+ const moduleName = `__assets${pathname}`;
1498
+ if (typeof content === "string") modules[moduleName] = { text: content };
1499
+ else modules[moduleName] = { data: content };
1500
+ }
1501
+ const manifestJson = {};
1502
+ for (const [pathname, meta] of assetManifest) manifestJson[pathname] = {
1503
+ contentType: meta.contentType,
1504
+ etag: meta.etag
1505
+ };
1506
+ modules["__asset-manifest.json"] = { json: manifestJson };
1507
+ const assetPathnames = [...assetManifest.keys()];
1508
+ const doOption = options.durableObject;
1509
+ const doClassName = doOption ? typeof doOption === "object" && doOption.className ? doOption.className : "App" : void 0;
1510
+ modules["__app-wrapper.js"] = doClassName ? generateDOAppWrapper(serverResult.mainModule, assetPathnames, doClassName, options.assetConfig) : generateAppWrapper(serverResult.mainModule, assetPathnames, options.assetConfig);
1511
+ modules["__asset-runtime.js"] = ASSET_RUNTIME_CODE;
1512
+ const result = {
1513
+ mainModule: "__app-wrapper.js",
1514
+ modules,
1515
+ assetManifest,
1516
+ assetConfig: options.assetConfig,
1517
+ clientBundles: clientBundles.length > 0 ? clientBundles : void 0,
1518
+ durableObjectClassName: doClassName
1519
+ };
1520
+ if (wranglerConfig !== void 0) result.wranglerConfig = wranglerConfig;
1521
+ if (installWarnings.length > 0) result.warnings = [...serverResult.warnings ?? [], ...installWarnings];
1522
+ else if (serverResult.warnings) result.warnings = serverResult.warnings;
1523
+ return result;
1524
+ }
1525
+ /**
1526
+ * Generate the asset imports + initialization preamble shared by both wrappers.
1527
+ * Returns the import statements and the initialization code that creates
1528
+ * the manifest Map, memory storage, and ASSET_CONFIG for handleAssetRequest.
1529
+ */
1530
+ function generateAssetPreamble(assetPathnames, assetConfig) {
1531
+ const configJson = JSON.stringify(assetConfig ?? {});
1532
+ const imports = [];
1533
+ const mapEntries = [];
1534
+ for (let i = 0; i < assetPathnames.length; i++) {
1535
+ const pathname = assetPathnames[i];
1536
+ const moduleName = `__assets${pathname}`;
1537
+ const varName = `__asset_${i}`;
1538
+ imports.push(`import ${varName} from "./${moduleName}";`);
1539
+ mapEntries.push(` ${JSON.stringify(pathname)}: ${varName}`);
1540
+ }
1541
+ return {
1542
+ importsBlock: [
1543
+ "import { handleAssetRequest, createMemoryStorage } from \"./__asset-runtime.js\";",
1544
+ "import manifestJson from \"./__asset-manifest.json\";",
1545
+ ...imports
1546
+ ].join("\n"),
1547
+ initBlock: `
1548
+ const ASSET_CONFIG = ${configJson};
1549
+ ${`const ASSET_CONTENT = {\n${mapEntries.join(",\n")}\n};`}
1550
+
1551
+ // Build manifest Map and storage at module init time
1552
+ const manifest = new Map(Object.entries(manifestJson));
1553
+ const storage = createMemoryStorage(ASSET_CONTENT);
1554
+ `.trimStart()
1555
+ };
1556
+ }
1557
+ /**
1558
+ * Generate the app wrapper module source.
1559
+ * This Worker serves assets first, then falls through to the user's server.
1560
+ *
1561
+ * Uses the pre-built __asset-runtime.js module for full asset handling
1562
+ * (all HTML modes, redirects, custom headers, ETag caching, etc.)
1563
+ */
1564
+ function generateAppWrapper(userServerModule, assetPathnames, assetConfig) {
1565
+ const { importsBlock, initBlock } = generateAssetPreamble(assetPathnames, assetConfig);
1566
+ return `
1567
+ import userWorker from "./${userServerModule}";
1568
+ ${importsBlock}
1569
+
1570
+ ${initBlock}
1571
+ export default {
1572
+ async fetch(request, env, ctx) {
1573
+ const assetResponse = await handleAssetRequest(request, manifest, storage, ASSET_CONFIG);
1574
+ if (assetResponse) return assetResponse;
1575
+
1576
+ // Fall through to user's Worker
1577
+ if (typeof userWorker === "object" && userWorker !== null && typeof userWorker.fetch === "function") {
1578
+ return userWorker.fetch(request, env, ctx);
1579
+ }
1580
+ if (typeof userWorker === "function") {
1581
+ return userWorker(request, env, ctx);
1582
+ }
1583
+
1584
+ return new Response("Not Found", { status: 404 });
1585
+ }
1586
+ };
1587
+ `.trim();
1588
+ }
1589
+ /**
1590
+ * Generate a Durable Object class wrapper module source.
1591
+ * Exports a named class that serves assets first, then delegates to the
1592
+ * user's server code. If the user's default export is a class (DurableObject
1593
+ * subclass), the wrapper extends it so `this.ctx.storage` works naturally.
1594
+ * Otherwise, it wraps the fetch handler in a DurableObject.
1595
+ *
1596
+ * Uses the pre-built __asset-runtime.js module for full asset handling.
1597
+ */
1598
+ function generateDOAppWrapper(userServerModule, assetPathnames, className, assetConfig) {
1599
+ const { importsBlock, initBlock } = generateAssetPreamble(assetPathnames, assetConfig);
1600
+ return `
1601
+ import { DurableObject } from "cloudflare:workers";
1602
+ import userExport from "./${userServerModule}";
1603
+ ${importsBlock}
1604
+
1605
+ ${initBlock}
1606
+ // Determine base class: if user exported a DurableObject subclass, extend it
1607
+ // so this.ctx.storage works naturally. Regular functions and plain objects are
1608
+ // wrapped in a minimal DurableObject that delegates fetch().
1609
+ // NOTE: This check uses prototype presence — regular (non-arrow) functions also
1610
+ // have .prototype, but the system prompt instructs class exports for DO mode.
1611
+ const BaseClass = (typeof userExport === "function" && userExport.prototype)
1612
+ ? userExport
1613
+ : class extends DurableObject {
1614
+ async fetch(request) {
1615
+ if (typeof userExport === "object" && userExport !== null && typeof userExport.fetch === "function") {
1616
+ return userExport.fetch(request, this.env, this.ctx);
1617
+ }
1618
+ return new Response("Not Found", { status: 404 });
1619
+ }
1620
+ };
1621
+
1622
+ export class ${className} extends BaseClass {
1623
+ async fetch(request) {
1624
+ const assetResponse = await handleAssetRequest(request, manifest, storage, ASSET_CONFIG);
1625
+ if (assetResponse) return assetResponse;
1626
+ return super.fetch(request);
1627
+ }
1628
+ }
1629
+ `.trim();
1630
+ }
1631
+ //#endregion
1632
+ //#region src/index.ts
1633
+ /**
1634
+ * Dynamic Worker Bundler
1635
+ *
1636
+ * Creates worker bundles from source files for Cloudflare's Worker Loader binding.
1637
+ */
1638
+ /**
1639
+ * Creates a worker bundle from source files.
1640
+ *
1641
+ * This function performs:
1642
+ * 1. Entry point detection (from package.json or defaults)
1643
+ * 2. Auto-installation of npm dependencies (if package.json has dependencies)
1644
+ * 3. TypeScript/JSX transformation (via Sucrase)
1645
+ * 4. Module resolution (handling imports/exports)
1646
+ * 5. Optional bundling (combining all modules into one)
1647
+ *
1648
+ * @param options - Configuration options
1649
+ * @returns The main module path and all modules
1650
+ */
1651
+ async function createWorker(options) {
1652
+ showExperimentalWarning("createWorker");
1653
+ let { files, bundle = true, externals = [], target = "es2022", minify = false, sourcemap = false, registry } = options;
1654
+ externals = ["cloudflare:", ...externals];
1655
+ const wranglerConfig = parseWranglerConfig(files);
1656
+ const nodejsCompat = hasNodejsCompat(wranglerConfig);
1657
+ const installWarnings = [];
1658
+ if (hasDependencies(files)) {
1659
+ const installResult = await installDependencies(files, registry ? { registry } : {});
1660
+ files = installResult.files;
1661
+ installWarnings.push(...installResult.warnings);
1662
+ }
1663
+ const entryPoint = options.entryPoint ?? detectEntryPoint(files, wranglerConfig);
1664
+ if (!entryPoint) throw new Error("Could not determine entry point. Please specify entryPoint option.");
1665
+ if (!(entryPoint in files)) throw new Error(`Entry point "${entryPoint}" not found in files.`);
1666
+ if (bundle) {
1667
+ const result = await bundleWithEsbuild(files, entryPoint, externals, target, minify, sourcemap, nodejsCompat);
1668
+ if (wranglerConfig !== void 0) result.wranglerConfig = wranglerConfig;
1669
+ if (installWarnings.length > 0) result.warnings = [...result.warnings ?? [], ...installWarnings];
1670
+ return result;
1671
+ } else {
1672
+ const result = await transformAndResolve(files, entryPoint, externals);
1673
+ if (wranglerConfig !== void 0) result.wranglerConfig = wranglerConfig;
1674
+ if (installWarnings.length > 0) result.warnings = [...result.warnings ?? [], ...installWarnings];
1675
+ return result;
1676
+ }
1677
+ }
1678
+ //#endregion
1679
+ export { buildAssetManifest, buildAssets, createApp, createMemoryStorage, createWorker, handleAssetRequest, inferContentType, isTextContentType };
1680
+
1681
+ //# sourceMappingURL=index.js.map