reroute-js 0.7.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/README.md +1 -0
  2. package/_/basic/package.json +2 -1
  3. package/_/blog/package.json +2 -1
  4. package/_/store/package.json +2 -1
  5. package/cli/bin.d.ts +1 -1
  6. package/cli/bin.js +3456 -87
  7. package/cli/bin.js.map +34 -7
  8. package/cli/index.d.ts +1 -1
  9. package/cli/index.js +75 -34
  10. package/cli/index.js.map +6 -4
  11. package/cli/src/cli.d.ts +1 -1
  12. package/cli/src/commands/analyze.d.ts +19 -0
  13. package/cli/src/commands/analyze.d.ts.map +1 -0
  14. package/cli/src/commands/boot.d.ts +1 -1
  15. package/cli/src/commands/build.d.ts +1 -1
  16. package/cli/src/commands/build.d.ts.map +1 -1
  17. package/cli/src/commands/dev.d.ts +1 -1
  18. package/cli/src/commands/gen.d.ts +1 -1
  19. package/cli/src/commands/gen.d.ts.map +1 -1
  20. package/cli/src/commands/init.d.ts +1 -1
  21. package/cli/src/commands/start.d.ts +1 -1
  22. package/cli/src/libs/command.d.ts +15 -0
  23. package/cli/src/libs/command.d.ts.map +1 -0
  24. package/cli/src/libs/index.d.ts +3 -1
  25. package/cli/src/libs/index.d.ts.map +1 -1
  26. package/cli/src/libs/log.d.ts +1 -1
  27. package/cli/src/libs/log.d.ts.map +1 -1
  28. package/cli/src/libs/markdown-processor.d.ts +1 -1
  29. package/cli/src/libs/markdown.d.ts +1 -1
  30. package/cli/src/libs/production.d.ts +34 -0
  31. package/cli/src/libs/production.d.ts.map +1 -0
  32. package/cli/src/libs/tailwind.d.ts +1 -1
  33. package/cli/src/libs/version.d.ts +1 -1
  34. package/core/index.d.ts +1 -1
  35. package/core/index.js +2 -2
  36. package/core/index.js.map +1 -1
  37. package/core/src/bundler/hash.d.ts +1 -1
  38. package/core/src/bundler/index.d.ts +1 -1
  39. package/core/src/bundler/transpile.d.ts +1 -1
  40. package/core/src/content/discovery.d.ts +1 -1
  41. package/core/src/content/index.d.ts +1 -1
  42. package/core/src/content/metadata.d.ts +1 -1
  43. package/core/src/content/registry.d.ts +1 -1
  44. package/core/src/index.d.ts +1 -1
  45. package/core/src/ssr/data.d.ts +1 -1
  46. package/core/src/ssr/index.d.ts +1 -1
  47. package/core/src/ssr/modules.d.ts +1 -1
  48. package/core/src/ssr/render.d.ts +1 -1
  49. package/core/src/ssr/seed.d.ts +1 -1
  50. package/core/src/template/html.d.ts +1 -1
  51. package/core/src/template/index.d.ts +1 -1
  52. package/core/src/types.d.ts +1 -1
  53. package/core/src/utils/cache.d.ts +1 -1
  54. package/core/src/utils/compression.d.ts +1 -1
  55. package/core/src/utils/index.d.ts +1 -1
  56. package/core/src/utils/mime.d.ts +1 -1
  57. package/core/src/utils/path.d.ts +1 -1
  58. package/elysia/index.d.ts +1 -1
  59. package/elysia/index.js +2 -2
  60. package/elysia/index.js.map +1 -1
  61. package/elysia/src/index.d.ts +1 -1
  62. package/elysia/src/libs/http.d.ts +1 -1
  63. package/elysia/src/libs/image.d.ts +1 -1
  64. package/elysia/src/plugin.d.ts +1 -1
  65. package/elysia/src/routes/artifacts.d.ts +1 -1
  66. package/elysia/src/routes/content.d.ts +1 -1
  67. package/elysia/src/routes/dev.d.ts +1 -1
  68. package/elysia/src/routes/image.d.ts +1 -1
  69. package/elysia/src/routes/ssr.d.ts +1 -1
  70. package/elysia/src/routes/static.d.ts +1 -1
  71. package/elysia/src/types.d.ts +1 -1
  72. package/package.json +3 -2
  73. package/react/index.d.ts +1 -1
  74. package/react/index.js +2 -2
  75. package/react/index.js.map +1 -1
  76. package/react/src/components/ClientOnly.d.ts +1 -1
  77. package/react/src/components/ContentRoute.d.ts +1 -1
  78. package/react/src/components/Image.d.ts +1 -1
  79. package/react/src/components/Link.d.ts +1 -1
  80. package/react/src/components/Markdown.d.ts +1 -1
  81. package/react/src/components/Outlet.d.ts +1 -1
  82. package/react/src/components/index.d.ts +1 -1
  83. package/react/src/hooks/index.d.ts +1 -1
  84. package/react/src/hooks/useContent.d.ts +1 -1
  85. package/react/src/hooks/useData.d.ts +1 -1
  86. package/react/src/hooks/useNavigate.d.ts +1 -1
  87. package/react/src/hooks/useParams.d.ts +1 -1
  88. package/react/src/hooks/useRouter.d.ts +1 -1
  89. package/react/src/hooks/useSearchParams.d.ts +1 -1
  90. package/react/src/index.d.ts +1 -1
  91. package/react/src/providers/ContentProvider.d.ts +1 -1
  92. package/react/src/providers/RerouteProvider.d.ts +1 -1
  93. package/react/src/providers/RouterProvider.d.ts +1 -1
  94. package/react/src/providers/index.d.ts +1 -1
  95. package/react/src/types/any.d.ts +1 -1
  96. package/react/src/types/index.d.ts +1 -1
  97. package/react/src/types/router.d.ts +1 -1
  98. package/react/src/utils/content.d.ts +1 -1
  99. package/react/src/utils/head.d.ts +1 -1
  100. package/react/src/utils/index.d.ts +1 -1
package/cli/bin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
3
  /**
4
- * reroute-js v0.7.2
4
+ * reroute-js v0.8.0
5
5
  *
6
6
  * @license MIT
7
7
  * @copyright 2025 stewones <hi@stewan.io>
@@ -47,7 +47,7 @@ async function getVersionString() {
47
47
  }
48
48
  async function getVersion() {
49
49
  if (true) {
50
- return "0.7.2";
50
+ return "0.8.0";
51
51
  }
52
52
  const possiblePaths = [
53
53
  path.join(import.meta.dir, "../../../../package.json"),
@@ -65,7 +65,7 @@ async function getVersion() {
65
65
  }
66
66
  async function getCommit() {
67
67
  if (true) {
68
- return "993e6df";
68
+ return "b31f8e3";
69
69
  }
70
70
  return "dev";
71
71
  }
@@ -649,6 +649,11 @@ function isMarkdownFile(filePath) {
649
649
  }
650
650
  var init_markdown_processor = () => {};
651
651
 
652
+ // packages/cli/src/libs/production.ts
653
+ function isProductionMode(args = []) {
654
+ return true;
655
+ }
656
+
652
657
  // packages/cli/src/libs/tailwind.ts
653
658
  import { spawn } from "node:child_process";
654
659
  import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
@@ -1030,15 +1035,16 @@ async function buildContentChunks(cwd) {
1030
1035
  let code = "";
1031
1036
  try {
1032
1037
  console.log(`[reroute/content] build ${buildSrc}`);
1038
+ const isProd = isProductionMode();
1033
1039
  const result = await Bun.build({
1034
1040
  entrypoints: [buildSrc],
1035
1041
  target: "browser",
1036
1042
  format: "esm",
1037
1043
  splitting: false,
1038
- sourcemap: Bun.env.NODE_ENV === "production" ? "linked" : "none",
1039
- minify: Bun.env.NODE_ENV === "production",
1044
+ sourcemap: isProd ? "linked" : "none",
1045
+ minify: isProd,
1040
1046
  define: {
1041
- "process.env.NODE_ENV": Bun.env.NODE_ENV === "production" ? '"production"' : '"development"'
1047
+ "process.env.NODE_ENV": isProd ? '"production"' : '"development"'
1042
1048
  }
1043
1049
  });
1044
1050
  if (!result.success)
@@ -1445,6 +1451,7 @@ var init_log = __esm(() => {
1445
1451
  "[reroute/dev]": "{green-fg}",
1446
1452
  "[reroute/start]": "{bright-cyan-fg}",
1447
1453
  "[reroute/build]": "{bright-magenta-fg}",
1454
+ "[reroute/analyze]": "{bright-green-fg}",
1448
1455
  "[api]": "{blue-fg}",
1449
1456
  "[app]": "{bright-green-fg}"
1450
1457
  };
@@ -2037,7 +2044,7 @@ async function build(args) {
2037
2044
  `));
2038
2045
  process.stdout.write(`${colorizeLogPrefixAnsi(`[reroute/build] Building binary to ${outputPath}...`)}
2039
2046
  `);
2040
- const isProduction = true;
2047
+ const isProduction = isProductionMode(args) || args.includes("--minify");
2041
2048
  const buildArgs = ["build", "src/index.ts", "--target", "bun", "--compile"];
2042
2049
  if (isProduction) {
2043
2050
  buildArgs.push("--minify");
@@ -2048,7 +2055,7 @@ async function build(args) {
2048
2055
  return false;
2049
2056
  }
2050
2057
  }
2051
- return arg !== "--outfile" && arg !== "-o" && arg !== "--minify" && arg !== "--no-minify";
2058
+ return arg !== "--outfile" && arg !== "-o" && arg !== "--minify" && arg !== "--no-minify" && arg !== "--prod";
2052
2059
  });
2053
2060
  buildArgs.push(...filteredArgs, "--outfile", outputPath);
2054
2061
  await new Promise((resolve, reject) => {
@@ -2078,86 +2085,3448 @@ var init_build = __esm(() => {
2078
2085
  init_log();
2079
2086
  });
2080
2087
 
2081
- // packages/cli/src/cli.ts
2082
- import path2 from "node:path";
2083
- var commands = {
2084
- init: () => Promise.resolve().then(() => (init_init(), exports_init)),
2085
- gen: () => Promise.resolve().then(() => (init_gen(), exports_gen)),
2086
- boot: () => Promise.resolve().then(() => (init_boot(), exports_boot)),
2087
- dev: () => Promise.resolve().then(() => (init_dev(), exports_dev)),
2088
- start: () => Promise.resolve().then(() => (init_start(), exports_start)),
2089
- build: () => Promise.resolve().then(() => (init_build(), exports_build))
2090
- };
2091
- async function main() {
2092
- const args = process.argv.slice(2);
2093
- if (args[0] === "--version" || args[0] === "-v") {
2094
- const version = await getVersionString2();
2095
- console.log(version);
2096
- process.exit(0);
2088
+ // packages/core/src/bundler/hash.ts
2089
+ async function generateContentHash(content) {
2090
+ const data = new TextEncoder().encode(content);
2091
+ const buf = await crypto.subtle.digest("SHA-256", data);
2092
+ let hex = "";
2093
+ for (const b of new Uint8Array(buf))
2094
+ hex += b.toString(16).padStart(2, "0");
2095
+ return hex.slice(0, 8);
2096
+ }
2097
+
2098
+ // packages/cli/src/libs/command.ts
2099
+ var exports_command = {};
2100
+ __export(exports_command, {
2101
+ getRerouteCommand: () => getRerouteCommand4
2102
+ });
2103
+ import { existsSync as existsSync8 } from "node:fs";
2104
+ import { join as join10 } from "node:path";
2105
+ function getRerouteCommand4() {
2106
+ const binPath = join10(import.meta.dir, "..", "..", "bin.ts");
2107
+ const isBunDev = existsSync8(binPath);
2108
+ if (isBunDev) {
2109
+ return `bun ${binPath}`;
2097
2110
  }
2098
- if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
2099
- await printHelp2();
2100
- process.exit(0);
2111
+ const cwd = process.cwd();
2112
+ const nodeModulesPaths = [
2113
+ join10(cwd, "node_modules", ".bin", "reroute"),
2114
+ join10(cwd, "..", "node_modules", ".bin", "reroute"),
2115
+ join10(cwd, "..", "..", "node_modules", ".bin", "reroute"),
2116
+ join10(cwd, "..", "..", "..", "node_modules", ".bin", "reroute")
2117
+ ];
2118
+ for (const binPath2 of nodeModulesPaths) {
2119
+ if (existsSync8(binPath2)) {
2120
+ return binPath2;
2121
+ }
2101
2122
  }
2102
- const command = args[0];
2103
- if (!commands[command]) {
2104
- console.error(`Unknown command: ${command}`);
2105
- console.error("");
2106
- await printHelp2();
2107
- process.exit(1);
2123
+ return "reroute";
2124
+ }
2125
+ var init_command = () => {};
2126
+
2127
+ // packages/cli/src/libs/index.ts
2128
+ var init_libs = __esm(() => {
2129
+ init_command();
2130
+ init_log();
2131
+ init_markdown();
2132
+ init_markdown_processor();
2133
+ init_tailwind();
2134
+ });
2135
+
2136
+ // packages/cli/index.ts
2137
+ var init_cli = __esm(() => {
2138
+ init_libs();
2139
+ });
2140
+
2141
+ // packages/core/src/utils/path.ts
2142
+ var exports_path = {};
2143
+ __export(exports_path, {
2144
+ stripStart: () => stripStart,
2145
+ stripEnd: () => stripEnd,
2146
+ join: () => join11,
2147
+ extname: () => extname2,
2148
+ basename: () => basename2
2149
+ });
2150
+ function join11(...parts) {
2151
+ return parts.join("/").replace(/\/+/g, "/");
2152
+ }
2153
+ function extname2(p) {
2154
+ const i = p.lastIndexOf(".");
2155
+ return i >= 0 ? p.slice(i) : "";
2156
+ }
2157
+ function basename2(p, ext) {
2158
+ const name = p.replace(/\\/g, "/").split("/").pop() || p;
2159
+ return ext && name.endsWith(ext) ? name.slice(0, -ext.length) : name;
2160
+ }
2161
+ function stripStart(p, ch) {
2162
+ return p.startsWith(ch) ? p.slice(ch.length) : p;
2163
+ }
2164
+ function stripEnd(p, ch) {
2165
+ return p.endsWith(ch) ? p.slice(0, -ch.length) : p;
2166
+ }
2167
+
2168
+ // packages/core/src/bundler/transpile.ts
2169
+ function isWatchMode() {
2170
+ try {
2171
+ return Array.isArray(process.execArgv) && process.execArgv.includes("--watch") || Array.isArray(process.argv) && process.argv.includes("--watch");
2172
+ } catch {
2173
+ return false;
2174
+ }
2175
+ }
2176
+ async function transpileFile(filePath, originalPath, options, bundleCache) {
2177
+ const src = await Bun.file(filePath).text();
2178
+ const cacheKey = `${filePath}-${await generateContentHash(src)}`;
2179
+ if (bundleCache.has(cacheKey)) {
2180
+ return bundleCache.get(cacheKey);
2181
+ }
2182
+ console.log(`[reroute] Building ${originalPath}${options.minify ? " (minified)" : ""}...`);
2183
+ try {
2184
+ if (isWatchMode()) {
2185
+ const cwd = typeof process !== "undefined" && process.cwd ? process.cwd() : "";
2186
+ if (cwd) {
2187
+ await buildTailwindIfConfigured(cwd);
2188
+ }
2189
+ }
2190
+ } catch (e) {
2191
+ console.warn("[reroute/tailwind] rebuild failed:", e);
2192
+ }
2193
+ async function doBuild(sm) {
2194
+ return await Bun.build({
2195
+ entrypoints: [filePath],
2196
+ target: "browser",
2197
+ format: "esm",
2198
+ minify: options.minify,
2199
+ splitting: false,
2200
+ sourcemap: sm,
2201
+ define: {
2202
+ "process.env.NODE_ENV": options.minify ? '"production"' : '"development"'
2203
+ }
2204
+ });
2108
2205
  }
2206
+ let result;
2109
2207
  try {
2110
- const module = await commands[command]();
2111
- await module.default(args.slice(1));
2208
+ result = await doBuild(options.sourcemap ? "external" : "none");
2112
2209
  } catch (error) {
2113
- console.error("Error executing command:", error);
2114
- process.exit(1);
2210
+ console.warn(`[reroute] Build errored for ${originalPath} (sourcemap external). Retrying without sourcemap...`, error);
2211
+ result = await doBuild("none");
2115
2212
  }
2213
+ if (!result.success) {
2214
+ console.error(`[reroute] Failed to build ${filePath}`);
2215
+ for (const log2 of result.logs)
2216
+ console.error(log2);
2217
+ throw new Error(`Failed to transpile ${filePath}`);
2218
+ }
2219
+ const output = result.outputs[0];
2220
+ const code = await output.text();
2221
+ let sourceMap;
2222
+ if (options.sourcemap && result.outputs.length > 1) {
2223
+ const mapOutput = result.outputs.find((o) => o.path.endsWith(".map"));
2224
+ if (mapOutput) {
2225
+ sourceMap = await mapOutput.text();
2226
+ }
2227
+ }
2228
+ const contentHash = await generateContentHash(code);
2229
+ const bundleInfo = {
2230
+ hash: contentHash,
2231
+ code,
2232
+ sourceMap
2233
+ };
2234
+ bundleCache.set(cacheKey, bundleInfo);
2235
+ const sizeKB = (code.length / 1024).toFixed(2);
2236
+ const gzippedSize = Bun.gzipSync(new TextEncoder().encode(code)).length;
2237
+ const gzippedKB = (gzippedSize / 1024).toFixed(2);
2238
+ console.log(`[reroute] Built ${originalPath} -> ${sizeKB} KB (${gzippedKB} KB gzipped)`);
2239
+ return bundleInfo;
2116
2240
  }
2117
- async function printHelp2() {
2118
- console.log("");
2119
- console.log(await getVersionString2());
2120
- console.log("");
2121
- console.log("");
2122
- console.log("Usage:");
2123
- console.log("");
2124
- console.log(" reroute <command> [options]");
2125
- console.log("");
2126
- console.log("");
2127
- console.log("Commands:");
2128
- console.log("");
2129
- console.log(" init Scaffold a new Reroute project");
2130
- console.log(" boot Initialize .reroute directory with stub files");
2131
- console.log(" gen Generate content registry and static assets");
2132
- console.log(" dev Start development environment");
2133
- console.log(" start Run production-like server locally");
2134
- console.log(" build Build production binary");
2135
- console.log("");
2136
- console.log("");
2137
- console.log("Options:");
2138
- console.log("");
2139
- console.log(" -h, --help Show help");
2140
- console.log(" -v, --version Show version");
2141
- console.log("");
2142
- console.log("");
2143
- console.log("Examples:");
2144
- console.log("");
2145
- console.log(" reroute init my-app");
2146
- console.log(" reroute boot");
2147
- console.log("");
2148
- console.log(" reroute gen");
2149
- console.log(" reroute gen -w");
2150
- console.log(" reroute gen --watch");
2151
- console.log("");
2152
- console.log(" reroute dev");
2153
- console.log(" reroute dev -s");
2154
- console.log(" reroute dev --single");
2155
- console.log("");
2156
- console.log(" reroute start");
2157
- console.log("");
2158
- console.log(" reroute build");
2159
- console.log(" reroute build -o ./dist/myapp");
2160
- console.log(" reroute build --outfile ./dist/myapp");
2241
+ async function getBundleUrlsFor(modules, clientDir, prefix, options, bundleCache) {
2242
+ const mods = Array.isArray(modules) ? modules : [modules];
2243
+ const urls = [];
2244
+ const { join: join12 } = await Promise.resolve().then(() => exports_path);
2245
+ for (const mod of mods) {
2246
+ const rel = mod.replace(/^\.\//, "");
2247
+ const fullPath = join12(clientDir, rel);
2248
+ try {
2249
+ const bundleInfo = await transpileFile(fullPath, rel, options, bundleCache);
2250
+ const base = basename2(rel, extname2(rel));
2251
+ urls.push(`${prefix}/${base}.${bundleInfo.hash}.js`);
2252
+ } catch (error) {
2253
+ console.error(`[reroute] Error getting bundle URL for ${rel}:`, error);
2254
+ urls.push(`${prefix}/${rel}`);
2255
+ }
2256
+ }
2257
+ return urls;
2258
+ }
2259
+ var init_transpile = __esm(() => {
2260
+ init_cli();
2261
+ });
2262
+
2263
+ // packages/core/src/bundler/index.ts
2264
+ var init_bundler = __esm(() => {
2265
+ init_transpile();
2266
+ });
2267
+
2268
+ // packages/core/src/content/metadata.ts
2269
+ import { stat } from "node:fs/promises";
2270
+ import { pathToFileURL as pathToFileURL2 } from "node:url";
2271
+ async function getContentMeta(absPath, isWatchMode2) {
2272
+ try {
2273
+ const url = pathToFileURL2(absPath).href;
2274
+ const mod = await import(`${url}${isWatchMode2 ? `?t=${Date.now()}` : ""}`);
2275
+ const meta = mod.meta || mod.frontmatter || {};
2276
+ if (meta && typeof meta === "object")
2277
+ return meta;
2278
+ return {};
2279
+ } catch {
2280
+ try {
2281
+ const s = await stat(absPath);
2282
+ return { date: new Date(s.mtimeMs).toISOString() };
2283
+ } catch {
2284
+ return {};
2285
+ }
2286
+ }
2287
+ }
2288
+ function buildHeadFromMeta(meta) {
2289
+ if (!meta || typeof meta !== "object")
2290
+ return "";
2291
+ const parts = [];
2292
+ const title = typeof meta.title === "string" ? meta.title : undefined;
2293
+ const description = typeof meta.description === "string" ? meta.description : typeof meta.excerpt === "string" ? meta.excerpt : undefined;
2294
+ if (title)
2295
+ parts.push(`<title>${escapeHtml(title)}</title>`);
2296
+ if (description)
2297
+ parts.push(`<meta name="description" content="${escapeHtml(description)}" />`);
2298
+ return parts.length ? `
2299
+ ${parts.join(`
2300
+ `)}` : "";
2301
+ }
2302
+ function escapeHtml(input) {
2303
+ return input.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
2304
+ }
2305
+ var init_metadata = () => {};
2306
+
2307
+ // packages/core/src/content/discovery.ts
2308
+ import { readdir as readdir3, stat as stat2 } from "node:fs/promises";
2309
+ async function listContentFiles2(dir, baseRel) {
2310
+ const out = [];
2311
+ try {
2312
+ const entries = await readdir3(dir, { withFileTypes: true });
2313
+ for (const entry of entries) {
2314
+ const full = join11(dir, entry.name);
2315
+ const rel = join11(baseRel, entry.name);
2316
+ if (entry.isDirectory()) {
2317
+ const nested = await listContentFiles2(full, rel);
2318
+ out.push(...nested);
2319
+ } else if (entry.isFile()) {
2320
+ if (/\.(tsx|ts)$/.test(entry.name) && !entry.name.startsWith("_")) {
2321
+ out.push(rel);
2322
+ }
2323
+ }
2324
+ }
2325
+ } catch {}
2326
+ return out;
2327
+ }
2328
+ async function buildContentDTOs(collectionPath, clientDir, prefix, isWatchMode2) {
2329
+ const normalized = stripStart(stripEnd(collectionPath, "/"), "/");
2330
+ const contentRelDir = join11("routes", normalized, "content");
2331
+ const contentAbsDir = join11(clientDir, contentRelDir);
2332
+ const files = await listContentFiles2(contentAbsDir, "");
2333
+ const items = [];
2334
+ for (const rel of files) {
2335
+ const fullRelPath = join11(contentRelDir, rel);
2336
+ const absPath = join11(clientDir, fullRelPath);
2337
+ const noExt = rel.replace(/\.(tsx|ts)$/, "");
2338
+ const name = basename2(noExt);
2339
+ const moduleUrl = `${prefix}/${fullRelPath}`.replace(/\/+/g, "/");
2340
+ const meta = await getContentMeta(absPath, isWatchMode2);
2341
+ const href = `/${normalized}/${name}`.replace(/\\+/g, "/");
2342
+ items.push({
2343
+ slug: noExt,
2344
+ name,
2345
+ path: fullRelPath,
2346
+ module: moduleUrl,
2347
+ meta,
2348
+ href
2349
+ });
2350
+ }
2351
+ return items;
2352
+ }
2353
+ async function discoverCollections(clientDir) {
2354
+ const root = join11(clientDir, "routes");
2355
+ const collections = new Set;
2356
+ try {
2357
+ const entries = await readdir3(root, { withFileTypes: true });
2358
+ for (const entry of entries) {
2359
+ if (entry.isDirectory()) {
2360
+ const contentDir = join11(root, entry.name, "content");
2361
+ try {
2362
+ await stat2(contentDir);
2363
+ collections.add(entry.name);
2364
+ } catch {}
2365
+ }
2366
+ }
2367
+ } catch {}
2368
+ return Array.from(collections);
2369
+ }
2370
+ var init_discovery = __esm(() => {
2371
+ init_metadata();
2372
+ });
2373
+
2374
+ // packages/core/src/content/registry.ts
2375
+ async function generateContentRegistry(cwd) {
2376
+ try {
2377
+ const p = join11(cwd, ".reroute", "content.ts");
2378
+ const exists = await Bun.file(p).exists();
2379
+ console.log(exists ? "[reroute] Content registry up-to-date" : "[reroute] Warning: .reroute/content.ts not found");
2380
+ } catch {}
2381
+ }
2382
+ var init_registry = () => {};
2383
+
2384
+ // packages/core/src/content/index.ts
2385
+ var init_content = __esm(() => {
2386
+ init_discovery();
2387
+ init_metadata();
2388
+ init_registry();
2389
+ });
2390
+
2391
+ // packages/core/src/ssr/modules.ts
2392
+ import { readdir as readdir4, stat as stat3 } from "node:fs/promises";
2393
+ import { pathToFileURL as pathToFileURL3 } from "node:url";
2394
+ async function importContentModuleForPath(pathname, clientDir, cwd, isWatchMode2) {
2395
+ try {
2396
+ const parts = pathname.split("/").filter(Boolean);
2397
+ if (parts.length < 2)
2398
+ return null;
2399
+ const collection = parts[0];
2400
+ const name = parts[1];
2401
+ try {
2402
+ const registryPath = join11(cwd, ".reroute", "content.ts");
2403
+ const reg = await import(pathToFileURL3(registryPath).href + (isWatchMode2 ? `?t=${Date.now()}` : ""));
2404
+ const get = reg?.getContentEntry;
2405
+ const entry = typeof get === "function" ? get(collection, name) : undefined;
2406
+ const moduleUrl = entry?.module;
2407
+ if (moduleUrl?.endsWith(".js")) {
2408
+ const abs = join11(cwd, moduleUrl.replace(/^\//, ""));
2409
+ const href = pathToFileURL3(abs).href + (isWatchMode2 ? `?t=${Date.now()}` : "");
2410
+ const mod = await import(href);
2411
+ return mod;
2412
+ }
2413
+ } catch {}
2414
+ try {
2415
+ const chunkDir = join11(cwd, ".reroute", "chunks", collection);
2416
+ const files = await readdir4(chunkDir);
2417
+ const matches = files.filter((n) => n.startsWith(`${name}.`) && n.endsWith(".js"));
2418
+ if (matches.length) {
2419
+ let latest = matches[0];
2420
+ let latestM = 0;
2421
+ for (const f of matches) {
2422
+ try {
2423
+ const s = await stat3(join11(chunkDir, f));
2424
+ if (s.mtimeMs >= latestM) {
2425
+ latestM = s.mtimeMs;
2426
+ latest = f;
2427
+ }
2428
+ } catch {}
2429
+ }
2430
+ const absChunk = join11(chunkDir, latest);
2431
+ const href = pathToFileURL3(absChunk).href + (isWatchMode2 ? `?t=${Date.now()}` : "");
2432
+ const mod = await import(href);
2433
+ return mod;
2434
+ }
2435
+ } catch {}
2436
+ try {
2437
+ const srcTsx = join11(clientDir, "routes", collection, "content", `${name}.tsx`);
2438
+ const srcTs = join11(clientDir, "routes", collection, "content", `${name}.ts`);
2439
+ let absSrc = null;
2440
+ if (await Bun.file(srcTsx).exists())
2441
+ absSrc = srcTsx;
2442
+ else if (await Bun.file(srcTs).exists())
2443
+ absSrc = srcTs;
2444
+ if (absSrc) {
2445
+ const href = pathToFileURL3(absSrc).href;
2446
+ const mod = await (isWatchMode2 ? import(`${href}?t=${Date.now()}`) : import(href));
2447
+ return mod;
2448
+ }
2449
+ } catch {}
2450
+ } catch {}
2451
+ return null;
2452
+ }
2453
+ var init_modules = () => {};
2454
+
2455
+ // packages/core/src/ssr/seed.ts
2456
+ async function seedSSRModuleForPath(pathname, clientDir, cwd, isWatchMode2) {
2457
+ try {
2458
+ const parts = pathname.split("/").filter(Boolean);
2459
+ if (parts.length < 2)
2460
+ return;
2461
+ const collection = parts[0];
2462
+ const name = parts[1];
2463
+ const key = `${collection}:${name}`;
2464
+ const mod = await importContentModuleForPath(pathname, clientDir, cwd, isWatchMode2);
2465
+ if (!mod)
2466
+ return;
2467
+ const C = mod.default || mod;
2468
+ globalThis.__REROUTE_SSR_MODULES__ = globalThis.__REROUTE_SSR_MODULES__ || {};
2469
+ globalThis.__REROUTE_SSR_MODULES__[key] = C;
2470
+ try {
2471
+ const pageMeta = mod.meta || mod.frontmatter || {};
2472
+ const pageSSR = mod.ssr || {};
2473
+ globalThis.__REROUTE_SSR_EXPORTS__ = globalThis.__REROUTE_SSR_EXPORTS__ || {};
2474
+ globalThis.__REROUTE_SSR_EXPORTS__[key] = {
2475
+ meta: pageMeta,
2476
+ ssr: pageSSR
2477
+ };
2478
+ } catch {}
2479
+ } catch {}
2480
+ }
2481
+ var init_seed = __esm(() => {
2482
+ init_modules();
2483
+ });
2484
+
2485
+ // packages/core/src/ssr/data.ts
2486
+ import { pathToFileURL as pathToFileURL4 } from "node:url";
2487
+ async function computeSSRDataForPath(params) {
2488
+ const { pathname, clientDir, cwd, isWatchMode: isWatchMode2 } = params;
2489
+ try {
2490
+ await seedSSRModuleForPath(pathname, clientDir, cwd, isWatchMode2);
2491
+ } catch {}
2492
+ try {
2493
+ const parts = pathname.split("/").filter(Boolean);
2494
+ if (parts.length >= 2) {
2495
+ const key = `${parts[0]}:${parts[1]}`;
2496
+ const g = globalThis;
2497
+ const exp = g.__REROUTE_SSR_EXPORTS__?.[key];
2498
+ const dataFn = exp?.ssr?.data;
2499
+ if (typeof dataFn === "function") {
2500
+ return await dataFn({ pathname, params: { name: parts[1] } });
2501
+ }
2502
+ }
2503
+ } catch {}
2504
+ try {
2505
+ const routesPath = join11(cwd, ".reroute", "routes.ts");
2506
+ const m = await import(pathToFileURL4(routesPath).href + (isWatchMode2 ? `?t=${Date.now()}` : ""));
2507
+ const match = typeof m.matchRoute === "function" ? m.matchRoute(pathname) : null;
2508
+ const r = match?.route;
2509
+ const paramsValue = match?.params || {};
2510
+ if (r && typeof r.path === "string") {
2511
+ try {
2512
+ const abs = join11(clientDir, "routes", String(r.path));
2513
+ const mod = await import(pathToFileURL4(abs).href + (isWatchMode2 ? `?t=${Date.now()}` : ""));
2514
+ const ssr = mod?.ssr;
2515
+ const dataFn = ssr?.data;
2516
+ if (typeof dataFn === "function") {
2517
+ return await dataFn({ pathname, params: paramsValue });
2518
+ }
2519
+ } catch {}
2520
+ }
2521
+ } catch {}
2522
+ return null;
2523
+ }
2524
+ var init_data = __esm(() => {
2525
+ init_seed();
2526
+ });
2527
+
2528
+ // node_modules/dedent/dist/dedent.mjs
2529
+ function ownKeys(object, enumerableOnly) {
2530
+ var keys = Object.keys(object);
2531
+ if (Object.getOwnPropertySymbols) {
2532
+ var symbols = Object.getOwnPropertySymbols(object);
2533
+ enumerableOnly && (symbols = symbols.filter(function(sym) {
2534
+ return Object.getOwnPropertyDescriptor(object, sym).enumerable;
2535
+ })), keys.push.apply(keys, symbols);
2536
+ }
2537
+ return keys;
2538
+ }
2539
+ function _objectSpread(target) {
2540
+ for (var i = 1;i < arguments.length; i++) {
2541
+ var source = arguments[i] != null ? arguments[i] : {};
2542
+ i % 2 ? ownKeys(Object(source), true).forEach(function(key) {
2543
+ _defineProperty(target, key, source[key]);
2544
+ }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function(key) {
2545
+ Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
2546
+ });
2547
+ }
2548
+ return target;
2549
+ }
2550
+ function _defineProperty(obj, key, value) {
2551
+ key = _toPropertyKey(key);
2552
+ if (key in obj) {
2553
+ Object.defineProperty(obj, key, { value, enumerable: true, configurable: true, writable: true });
2554
+ } else {
2555
+ obj[key] = value;
2556
+ }
2557
+ return obj;
2558
+ }
2559
+ function _toPropertyKey(arg) {
2560
+ var key = _toPrimitive(arg, "string");
2561
+ return typeof key === "symbol" ? key : String(key);
2562
+ }
2563
+ function _toPrimitive(input, hint) {
2564
+ if (typeof input !== "object" || input === null)
2565
+ return input;
2566
+ var prim = input[Symbol.toPrimitive];
2567
+ if (prim !== undefined) {
2568
+ var res = prim.call(input, hint || "default");
2569
+ if (typeof res !== "object")
2570
+ return res;
2571
+ throw new TypeError("@@toPrimitive must return a primitive value.");
2572
+ }
2573
+ return (hint === "string" ? String : Number)(input);
2574
+ }
2575
+ function createDedent(options) {
2576
+ dedent2.withOptions = (newOptions) => createDedent(_objectSpread(_objectSpread({}, options), newOptions));
2577
+ return dedent2;
2578
+ function dedent2(strings, ...values) {
2579
+ const raw = typeof strings === "string" ? [strings] : strings.raw;
2580
+ const {
2581
+ alignValues = false,
2582
+ escapeSpecialCharacters = Array.isArray(strings),
2583
+ trimWhitespace = true
2584
+ } = options;
2585
+ let result = "";
2586
+ for (let i = 0;i < raw.length; i++) {
2587
+ let next = raw[i];
2588
+ if (escapeSpecialCharacters) {
2589
+ next = next.replace(/\\\n[ \t]*/g, "").replace(/\\`/g, "`").replace(/\\\$/g, "$").replace(/\\\{/g, "{");
2590
+ }
2591
+ result += next;
2592
+ if (i < values.length) {
2593
+ const value = alignValues ? alignValue(values[i], result) : values[i];
2594
+ result += value;
2595
+ }
2596
+ }
2597
+ const lines = result.split(`
2598
+ `);
2599
+ let mindent = null;
2600
+ for (const l of lines) {
2601
+ const m = l.match(/^(\s+)\S+/);
2602
+ if (m) {
2603
+ const indent = m[1].length;
2604
+ if (!mindent) {
2605
+ mindent = indent;
2606
+ } else {
2607
+ mindent = Math.min(mindent, indent);
2608
+ }
2609
+ }
2610
+ }
2611
+ if (mindent !== null) {
2612
+ const m = mindent;
2613
+ result = lines.map((l) => l[0] === " " || l[0] === "\t" ? l.slice(m) : l).join(`
2614
+ `);
2615
+ }
2616
+ if (trimWhitespace) {
2617
+ result = result.trim();
2618
+ }
2619
+ if (escapeSpecialCharacters) {
2620
+ result = result.replace(/\\n/g, `
2621
+ `);
2622
+ }
2623
+ return result;
2624
+ }
2625
+ }
2626
+ function alignValue(value, precedingText) {
2627
+ if (typeof value !== "string" || !value.includes(`
2628
+ `)) {
2629
+ return value;
2630
+ }
2631
+ const currentLine = precedingText.slice(precedingText.lastIndexOf(`
2632
+ `) + 1);
2633
+ const indentMatch = currentLine.match(/^(\s+)/);
2634
+ if (indentMatch) {
2635
+ const indent = indentMatch[1];
2636
+ return value.replace(/\n/g, `
2637
+ ${indent}`);
2638
+ }
2639
+ return value;
2640
+ }
2641
+ var dedent, dedent_default;
2642
+ var init_dedent = __esm(() => {
2643
+ dedent = createDedent({});
2644
+ dedent_default = dedent;
2645
+ });
2646
+
2647
+ // packages/core/src/template/html.ts
2648
+ async function loadIndexHtml(clientDir) {
2649
+ const templatePath = join11(clientDir, "index.html");
2650
+ try {
2651
+ const content = await Bun.file(templatePath).text();
2652
+ return content;
2653
+ } catch (error) {
2654
+ console.error(`[reroute] Failed to load template from ${templatePath}:`, error);
2655
+ return `<!doctype html>
2656
+ <html lang="en">
2657
+ <head>
2658
+ <meta charset="UTF-8" />
2659
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
2660
+ <title>Reroute</title>
2661
+ </head>
2662
+ <body>
2663
+ <div id="root"></div>
2664
+ </body>
2665
+ </html>`;
2666
+ }
2667
+ }
2668
+ function applyIndexTemplate(templateHtml, appHtml, {
2669
+ head = "",
2670
+ hydrationScript = "",
2671
+ lang = "en",
2672
+ appId = "root"
2673
+ }) {
2674
+ let html = templateHtml;
2675
+ html = html.replace(/<html([^>]*)>/i, (_m, attrs) => {
2676
+ const hasLang = /(^|\s)lang\s*=/.test(attrs);
2677
+ const newAttrs = hasLang ? attrs.replace(/lang\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/i, `lang="${lang}"`) : `${attrs} lang="${lang}"`;
2678
+ return `<html${newAttrs}>`;
2679
+ });
2680
+ let headToInject = head || "";
2681
+ try {
2682
+ const titleMatch = headToInject.match(/<title[\s\S]*?<\/title>/i);
2683
+ if (titleMatch) {
2684
+ const titleTag = titleMatch[0];
2685
+ if (/<title[\s\S]*?<\/title>/i.test(html)) {
2686
+ html = html.replace(/<title[\s\S]*?<\/title>/i, titleTag);
2687
+ } else {}
2688
+ headToInject = headToInject.replace(titleTag, "");
2689
+ }
2690
+ const descRe = /<meta\s+name\s*=\s*['"]description['"][^>]*>/i;
2691
+ const descMatch = headToInject.match(descRe);
2692
+ if (descMatch) {
2693
+ const descTag = descMatch[0];
2694
+ if (descRe.test(html)) {
2695
+ html = html.replace(descRe, descTag);
2696
+ }
2697
+ headToInject = headToInject.replace(descTag, "");
2698
+ }
2699
+ } catch {}
2700
+ if (headToInject) {
2701
+ html = html.replace(/<\/head>/i, `${headToInject}
2702
+ </head>`);
2703
+ }
2704
+ html = html.replace(/<script\b[^>]*src\s*=\s*["'][^"']+\.(ts|tsx)(?:[?#][^"']*)?["'][^>]*>\s*<\/script>/gi, "");
2705
+ const appIdEscaped = appId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2706
+ const rootDivRegex = new RegExp(`(<div[^>]*\\bid=(\\"|')${appIdEscaped}\\2[^>]*>)([\\s\\S]*?)(<\\/div>)`, "i");
2707
+ if (rootDivRegex.test(html)) {
2708
+ html = html.replace(rootDivRegex, `$1${appHtml}$4`);
2709
+ } else {
2710
+ html = html.replace(/<\/body>/i, ` <div id="${appId}">${appHtml}</div>
2711
+ </body>`);
2712
+ }
2713
+ if (hydrationScript) {
2714
+ html = html.replace(/<\/body>/i, ` ${hydrationScript}
2715
+ </body>`);
2716
+ }
2717
+ return html;
2718
+ }
2719
+ var init_html = () => {};
2720
+
2721
+ // packages/core/src/template/index.ts
2722
+ var init_template = __esm(() => {
2723
+ init_html();
2724
+ });
2725
+
2726
+ // packages/core/src/ssr/render.ts
2727
+ import { readdir as readdir5, stat as stat4 } from "node:fs/promises";
2728
+ import { pathToFileURL as pathToFileURL5 } from "node:url";
2729
+ import { cloneElement } from "react";
2730
+ import { renderToString } from "react-dom/server";
2731
+ async function renderSSRDocument(options) {
2732
+ const {
2733
+ pathname,
2734
+ rootComponent,
2735
+ clientDir,
2736
+ cwd,
2737
+ isWatchMode: isWatchMode2,
2738
+ bundleUrl,
2739
+ head = "",
2740
+ lang = "en",
2741
+ appId = "root",
2742
+ minify = false
2743
+ } = options;
2744
+ const scripts = [bundleUrl];
2745
+ let hydrationScript = "";
2746
+ let extraHead = "";
2747
+ let statusOverride;
2748
+ try {
2749
+ globalThis.__REROUTE_SSR_ACCESSED__ = {};
2750
+ } catch {}
2751
+ try {
2752
+ const parts = pathname.split("/").filter(Boolean);
2753
+ if (parts.length >= 2) {
2754
+ const collection = parts[0];
2755
+ const name = parts[1];
2756
+ let modulePath = "";
2757
+ let isContentCollection = false;
2758
+ try {
2759
+ const maybeDir = join11(clientDir, "routes", collection, "content");
2760
+ const s = await stat4(maybeDir);
2761
+ isContentCollection = typeof s?.isDirectory === "function" ? s.isDirectory() : true;
2762
+ } catch {
2763
+ isContentCollection = false;
2764
+ }
2765
+ if (!isContentCollection) {
2766
+ throw new Error("skip-content-preload");
2767
+ }
2768
+ try {
2769
+ const registryPath = join11(cwd, ".reroute", "content.ts");
2770
+ const reg = await import(pathToFileURL5(registryPath).href + (isWatchMode2 ? `?t=${Date.now()}` : ""));
2771
+ const get = reg?.getContentEntry;
2772
+ const entry = typeof get === "function" ? get(collection, name) : undefined;
2773
+ const moduleUrl = entry?.module;
2774
+ if (moduleUrl?.endsWith(".js")) {
2775
+ modulePath = moduleUrl;
2776
+ }
2777
+ } catch {}
2778
+ if (!modulePath) {
2779
+ try {
2780
+ const chunkDir = join11(cwd, ".reroute", "chunks", collection);
2781
+ const files = await readdir5(chunkDir);
2782
+ const candidates = files.filter((n) => n.startsWith(`${name}.`) && n.endsWith(".js"));
2783
+ if (candidates.length) {
2784
+ let latest = candidates[0];
2785
+ let latestM = 0;
2786
+ for (const candidateName of candidates) {
2787
+ try {
2788
+ const s = await stat4(join11(chunkDir, candidateName));
2789
+ if (s.mtimeMs >= latestM) {
2790
+ latestM = s.mtimeMs;
2791
+ latest = candidateName;
2792
+ }
2793
+ } catch {}
2794
+ }
2795
+ modulePath = `/${join11(".reroute", "chunks", collection, latest).replace(/\\+/g, "/")}`;
2796
+ }
2797
+ } catch {}
2798
+ }
2799
+ if (!modulePath) {
2800
+ const tsx = join11(clientDir, "routes", collection, "content", `${name}.tsx`);
2801
+ const ts = join11(clientDir, "routes", collection, "content", `${name}.ts`);
2802
+ let srcUrl = "";
2803
+ if (await Bun.file(tsx).exists()) {
2804
+ srcUrl = `/${join11("routes", collection, "content", `${name}.tsx`).replace(/\\+/g, "/")}`;
2805
+ } else if (await Bun.file(ts).exists()) {
2806
+ srcUrl = `/${join11("routes", collection, "content", `${name}.ts`).replace(/\\+/g, "/")}`;
2807
+ }
2808
+ if (srcUrl) {
2809
+ modulePath = srcUrl;
2810
+ }
2811
+ }
2812
+ if (!modulePath) {
2813
+ statusOverride = 404;
2814
+ }
2815
+ if (modulePath) {
2816
+ const key = `${collection}:${name}`;
2817
+ extraHead += `
2818
+ <link rel="modulepreload" href="${modulePath}" />`;
2819
+ const qs = `src=${encodeURIComponent(modulePath)}&key=${encodeURIComponent(key)}`;
2820
+ hydrationScript += `<script type="module" src="/__reroute_preload?${qs}"></script>`;
2821
+ }
2822
+ }
2823
+ } catch {}
2824
+ await seedSSRModuleForPath(pathname, clientDir, cwd, isWatchMode2);
2825
+ const __SSR_DATA__ = {};
2826
+ try {
2827
+ try {
2828
+ const parts = pathname.split("/").filter(Boolean);
2829
+ if (parts.length >= 2) {
2830
+ const g = globalThis;
2831
+ const key = `${parts[0]}:${parts[1]}`;
2832
+ const exp = g.__REROUTE_SSR_EXPORTS__?.[key];
2833
+ const dataFn = exp?.ssr?.data;
2834
+ if (typeof dataFn === "function") {
2835
+ const out = await dataFn({
2836
+ pathname,
2837
+ params: { name: parts[1] }
2838
+ });
2839
+ __SSR_DATA__[pathname] = out;
2840
+ }
2841
+ }
2842
+ } catch {}
2843
+ try {
2844
+ const routesPath = join11(cwd, ".reroute", "routes.ts");
2845
+ const m = await import(pathToFileURL5(routesPath).href + (isWatchMode2 ? `?t=${Date.now()}` : ""));
2846
+ const match = typeof m.matchRoute === "function" ? m.matchRoute(pathname) : null;
2847
+ const r = match?.route;
2848
+ const params = match?.params || {};
2849
+ if (r && typeof r.path === "string") {
2850
+ try {
2851
+ const abs = join11(clientDir, "routes", String(r.path));
2852
+ const mod = await import(pathToFileURL5(abs).href + (isWatchMode2 ? `?t=${Date.now()}` : ""));
2853
+ const ssr = mod?.ssr;
2854
+ const dataFn = ssr?.data;
2855
+ if (typeof dataFn === "function") {
2856
+ const out = await dataFn({ pathname, params });
2857
+ __SSR_DATA__[pathname] = out;
2858
+ }
2859
+ } catch {}
2860
+ }
2861
+ } catch {}
2862
+ } catch {}
2863
+ try {
2864
+ globalThis.__REROUTE_DATA__ = __SSR_DATA__;
2865
+ } catch {}
2866
+ let __byCollectionForSSR = {};
2867
+ try {
2868
+ const p = join11(cwd, ".reroute", "content.ts");
2869
+ const mod = await import(pathToFileURL5(p).href + (isWatchMode2 ? `?t=${Date.now()}` : ""));
2870
+ __byCollectionForSSR = mod?.byCollection || {};
2871
+ try {
2872
+ globalThis.__REROUTE_COLLECTIONS__ = __byCollectionForSSR;
2873
+ } catch {}
2874
+ } catch {}
2875
+ const componentWithPathname = cloneElement(rootComponent, {
2876
+ pathname
2877
+ });
2878
+ const appHtml = renderToString(componentWithPathname);
2879
+ const baseTemplate = await loadIndexHtml(clientDir);
2880
+ let inlineStyleTag = "";
2881
+ try {
2882
+ const candidates = [
2883
+ join11(clientDir, "..", ".reroute", "theme.css"),
2884
+ join11(clientDir, "..", "..", ".reroute", "theme.css"),
2885
+ join11(clientDir, "..", "..", "..", ".reroute", "theme.css"),
2886
+ join11(clientDir, "..", "..", "..", "..", ".reroute", "theme.css")
2887
+ ];
2888
+ let cssPath = "";
2889
+ for (const p of candidates) {
2890
+ if (await Bun.file(p).exists()) {
2891
+ cssPath = p;
2892
+ break;
2893
+ }
2894
+ }
2895
+ if (cssPath) {
2896
+ let css = await Bun.file(cssPath).text();
2897
+ if (minify) {
2898
+ css = css.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s+/g, " ").replace(/\s*([{}:;,>+~])\s*/g, "$1").replace(/;}/g, "}").trim();
2899
+ }
2900
+ inlineStyleTag = `<style data-reroute="tailwind">${css}</style>`;
2901
+ }
2902
+ } catch {}
2903
+ try {
2904
+ const g = globalThis;
2905
+ const acc = g.__REROUTE_SSR_ACCESSED__;
2906
+ const sortByDate = (order) => (a, b) => {
2907
+ const da = a?.meta?.date ? Date.parse(String(a.meta.date)) : 0;
2908
+ const db = b?.meta?.date ? Date.parse(String(b.meta.date)) : 0;
2909
+ return order === "asc" ? da - db : db - da;
2910
+ };
2911
+ const usage = {};
2912
+ if (acc && typeof acc.forEach === "function") {
2913
+ acc.forEach((c) => {
2914
+ if (typeof c === "string")
2915
+ usage[c] = { limit: Number.POSITIVE_INFINITY, sort: "custom" };
2916
+ });
2917
+ } else if (acc && typeof acc === "object") {
2918
+ for (const [k, v] of Object.entries(acc)) {
2919
+ const lim = typeof v?.limit === "number" ? v.limit : Number.POSITIVE_INFINITY;
2920
+ const s = typeof v?.sort === "string" ? v.sort : "custom";
2921
+ usage[k] = { limit: lim, sort: s };
2922
+ }
2923
+ }
2924
+ const collections = Object.keys(usage);
2925
+ if (collections.length) {
2926
+ const subset = {};
2927
+ const partial = {};
2928
+ for (const c of collections) {
2929
+ const conf = usage[c];
2930
+ const full = __byCollectionForSSR?.[c] || [];
2931
+ let arr = full;
2932
+ if (Number.isFinite(conf.limit) && conf.limit > 0 && conf.sort !== "custom") {
2933
+ if (conf.sort === "date-desc") {
2934
+ arr = [...full].sort(sortByDate("desc")).slice(0, conf.limit);
2935
+ } else if (conf.sort === "date-asc") {
2936
+ arr = [...full].sort(sortByDate("asc")).slice(0, conf.limit);
2937
+ } else if (conf.sort === "none") {
2938
+ arr = full.slice(0, conf.limit);
2939
+ }
2940
+ }
2941
+ subset[c] = arr;
2942
+ partial[c] = Array.isArray(arr) && Array.isArray(full) ? arr.length < full.length : false;
2943
+ }
2944
+ const subsetJson = JSON.stringify(subset);
2945
+ const partialJson = JSON.stringify(partial);
2946
+ hydrationScript += `
2947
+ <script>
2948
+ (function(){ try {
2949
+ var w = (typeof window!== 'undefined'? window : globalThis);
2950
+ w.__REROUTE_COLLECTIONS__ = w.__REROUTE_COLLECTIONS__ || {};
2951
+ Object.assign(w.__REROUTE_COLLECTIONS__, ${subsetJson});
2952
+ w.__REROUTE_COLLECTIONS_PARTIAL__ = w.__REROUTE_COLLECTIONS_PARTIAL__ || {};
2953
+ Object.assign(w.__REROUTE_COLLECTIONS_PARTIAL__, ${partialJson});
2954
+ } catch(e){} })();
2955
+ </script>`;
2956
+ }
2957
+ } catch {}
2958
+ try {
2959
+ const seededJson = JSON.stringify(__SSR_DATA__);
2960
+ hydrationScript += `
2961
+ <script>
2962
+ (function(){ try {
2963
+ var w = (typeof window!== 'undefined'? window : globalThis);
2964
+ w.__REROUTE_DATA__ = Object.assign({}, w.__REROUTE_DATA__ || {}, ${seededJson});
2965
+ } catch(e){} })();
2966
+ </script>`;
2967
+ } catch {}
2968
+ hydrationScript += scripts.map((src) => `<script type="module" src="${src}"></script>`).join("");
2969
+ if (isWatchMode2) {
2970
+ hydrationScript += `<script type="module" src="/__reroute_watch.js"></script>`;
2971
+ }
2972
+ let perPageHead = "";
2973
+ let pageLang;
2974
+ try {
2975
+ const parts = pathname.split("/").filter(Boolean);
2976
+ if (parts.length >= 2) {
2977
+ const key = `${parts[0]}:${parts[1]}`;
2978
+ const g = globalThis;
2979
+ const exp = g.__REROUTE_SSR_EXPORTS__?.[key];
2980
+ const meta = exp?.meta;
2981
+ const ssr = exp?.ssr;
2982
+ perPageHead += buildHeadFromMeta(meta);
2983
+ const ssrHead = typeof ssr?.head === "string" ? ssr.head : Array.isArray(ssr?.head) ? String(ssr.head.join(`
2984
+ `)) : "";
2985
+ if (ssrHead)
2986
+ perPageHead += `
2987
+ ${ssrHead}`;
2988
+ if (typeof ssr?.lang === "string" && ssr.lang.trim()) {
2989
+ pageLang = ssr.lang.trim();
2990
+ }
2991
+ }
2992
+ } catch {}
2993
+ try {
2994
+ const routesPath = join11(cwd, ".reroute", "routes.ts");
2995
+ const m = await import(pathToFileURL5(routesPath).href + (isWatchMode2 ? `?t=${Date.now()}` : ""));
2996
+ const match = typeof m.matchRoute === "function" ? m.matchRoute(pathname) : null;
2997
+ const r = match?.route;
2998
+ if (!r) {
2999
+ statusOverride = statusOverride || 404;
3000
+ }
3001
+ if (r && typeof r.path === "string") {
3002
+ try {
3003
+ const abs = join11(clientDir, "routes", String(r.path));
3004
+ const mod = await import(pathToFileURL5(abs).href + (isWatchMode2 ? `?t=${Date.now()}` : ""));
3005
+ const meta = mod?.meta;
3006
+ const ssr = mod?.ssr;
3007
+ if (meta)
3008
+ perPageHead += buildHeadFromMeta(meta);
3009
+ if (ssr) {
3010
+ const ssrHead = typeof ssr.head === "string" ? ssr.head : Array.isArray(ssr.head) ? String(ssr.head.join(`
3011
+ `)) : "";
3012
+ if (ssrHead)
3013
+ perPageHead += `
3014
+ ${ssrHead}`;
3015
+ if (typeof ssr.lang === "string" && ssr.lang.trim()) {
3016
+ pageLang = ssr.lang.trim();
3017
+ }
3018
+ }
3019
+ } catch {}
3020
+ } else {
3021
+ try {
3022
+ const list = m?.notFoundRoutes;
3023
+ if (Array.isArray(list)) {
3024
+ let chosen;
3025
+ let bestLen = -1;
3026
+ for (const nf of list) {
3027
+ const pat = String(nf?.pattern || "/");
3028
+ if (!pathname.startsWith(pat))
3029
+ continue;
3030
+ const len = pat.split("/").filter(Boolean).length;
3031
+ if (len >= bestLen) {
3032
+ bestLen = len;
3033
+ chosen = nf;
3034
+ }
3035
+ }
3036
+ if (chosen && typeof chosen.path === "string") {
3037
+ try {
3038
+ const abs = join11(clientDir, "routes", String(chosen.path));
3039
+ const mod = await import(pathToFileURL5(abs).href + (isWatchMode2 ? `?t=${Date.now()}` : ""));
3040
+ const meta = mod?.meta;
3041
+ const ssr = mod?.ssr;
3042
+ if (meta)
3043
+ perPageHead += buildHeadFromMeta(meta);
3044
+ const ssrHead = typeof ssr?.head === "string" ? ssr.head : Array.isArray(ssr?.head) ? String(ssr.head.join(`
3045
+ `)) : "";
3046
+ if (ssrHead)
3047
+ perPageHead += `
3048
+ ${ssrHead}`;
3049
+ if (typeof ssr?.lang === "string" && ssr.lang?.trim()) {
3050
+ pageLang = String(ssr.lang).trim();
3051
+ }
3052
+ } catch {}
3053
+ }
3054
+ }
3055
+ } catch {}
3056
+ }
3057
+ } catch {}
3058
+ const combinedHead = dedent_default([dedent_default(head) || "", dedent_default(extraHead), dedent_default(perPageHead)].filter(Boolean).join(`
3059
+ `));
3060
+ const html2 = applyIndexTemplate(baseTemplate, appHtml, {
3061
+ head: [inlineStyleTag, combinedHead].filter(Boolean).join(`
3062
+ `),
3063
+ hydrationScript,
3064
+ lang: pageLang || lang,
3065
+ appId
3066
+ });
3067
+ return {
3068
+ html: html2,
3069
+ status: statusOverride || 200
3070
+ };
3071
+ }
3072
+ var init_render = __esm(() => {
3073
+ init_dedent();
3074
+ init_metadata();
3075
+ init_template();
3076
+ init_seed();
3077
+ });
3078
+
3079
+ // packages/core/src/ssr/index.ts
3080
+ var init_ssr = __esm(() => {
3081
+ init_data();
3082
+ init_render();
3083
+ init_seed();
3084
+ });
3085
+ // packages/core/src/utils/cache.ts
3086
+ class LRUCache {
3087
+ cache;
3088
+ maxSize;
3089
+ constructor(maxSize = 100) {
3090
+ this.cache = new Map;
3091
+ this.maxSize = maxSize;
3092
+ }
3093
+ get(key) {
3094
+ if (!this.cache.has(key)) {
3095
+ return;
3096
+ }
3097
+ const value = this.cache.get(key);
3098
+ this.cache.delete(key);
3099
+ this.cache.set(key, value);
3100
+ return value;
3101
+ }
3102
+ set(key, value) {
3103
+ if (this.cache.has(key)) {
3104
+ this.cache.delete(key);
3105
+ }
3106
+ this.cache.set(key, value);
3107
+ if (this.cache.size > this.maxSize) {
3108
+ const firstKey = this.cache.keys().next().value;
3109
+ this.cache.delete(firstKey);
3110
+ }
3111
+ }
3112
+ has(key) {
3113
+ return this.cache.has(key);
3114
+ }
3115
+ clear() {
3116
+ this.cache.clear();
3117
+ }
3118
+ get size() {
3119
+ return this.cache.size;
3120
+ }
3121
+ }
3122
+ var init_cache = () => {};
3123
+
3124
+ // packages/core/src/utils/mime.ts
3125
+ function getMimeType(filePath) {
3126
+ const ext = filePath.split(".").pop()?.toLowerCase();
3127
+ const mimeTypes = {
3128
+ html: "text/html",
3129
+ css: "text/css",
3130
+ js: "application/javascript",
3131
+ ts: "application/javascript",
3132
+ json: "application/json",
3133
+ png: "image/png",
3134
+ jpg: "image/jpeg",
3135
+ jpeg: "image/jpeg",
3136
+ gif: "image/gif",
3137
+ svg: "image/svg+xml",
3138
+ ico: "image/x-icon",
3139
+ woff: "font/woff",
3140
+ woff2: "font/woff2",
3141
+ ttf: "font/ttf"
3142
+ };
3143
+ return mimeTypes[ext || ""] || "application/octet-stream";
3144
+ }
3145
+ function isCompressible(contentType) {
3146
+ if (!contentType)
3147
+ return false;
3148
+ const ct = contentType.split(";")[0].trim();
3149
+ return ct.startsWith("text/") || ct === "application/javascript" || ct === "application/json" || ct === "application/xml" || ct === "image/svg+xml";
3150
+ }
3151
+
3152
+ // packages/core/src/utils/compression.ts
3153
+ function acceptsGzip(acceptEncoding) {
3154
+ return Boolean(acceptEncoding && /gzip/i.test(acceptEncoding));
3155
+ }
3156
+ function toBytes(input) {
3157
+ return typeof input === "string" ? new TextEncoder().encode(input) : input;
3158
+ }
3159
+ function gzipIfAccepted(body, contentType, acceptEncoding) {
3160
+ const extraHeaders = {};
3161
+ if (acceptsGzip(acceptEncoding) && isCompressible(contentType)) {
3162
+ try {
3163
+ const compressed = Bun.gzipSync(toBytes(body));
3164
+ extraHeaders["Content-Encoding"] = "gzip";
3165
+ extraHeaders.Vary = "Accept-Encoding";
3166
+ return { body: compressed, extraHeaders };
3167
+ } catch {}
3168
+ }
3169
+ return { body, extraHeaders };
3170
+ }
3171
+ var init_compression = () => {};
3172
+
3173
+ // packages/core/src/utils/index.ts
3174
+ var init_utils = __esm(() => {
3175
+ init_cache();
3176
+ init_compression();
3177
+ });
3178
+
3179
+ // packages/core/src/index.ts
3180
+ var init_src = __esm(() => {
3181
+ init_bundler();
3182
+ init_content();
3183
+ init_ssr();
3184
+ init_template();
3185
+ init_utils();
3186
+ });
3187
+
3188
+ // packages/core/index.ts
3189
+ var exports_core = {};
3190
+ __export(exports_core, {
3191
+ transpileFile: () => transpileFile,
3192
+ toBytes: () => toBytes,
3193
+ stripStart: () => stripStart,
3194
+ stripEnd: () => stripEnd,
3195
+ seedSSRModuleForPath: () => seedSSRModuleForPath,
3196
+ renderSSRDocument: () => renderSSRDocument,
3197
+ loadIndexHtml: () => loadIndexHtml,
3198
+ listContentFiles: () => listContentFiles2,
3199
+ join: () => join11,
3200
+ isCompressible: () => isCompressible,
3201
+ gzipIfAccepted: () => gzipIfAccepted,
3202
+ getMimeType: () => getMimeType,
3203
+ getContentMeta: () => getContentMeta,
3204
+ getBundleUrlsFor: () => getBundleUrlsFor,
3205
+ generateContentRegistry: () => generateContentRegistry,
3206
+ generateContentHash: () => generateContentHash,
3207
+ extname: () => extname2,
3208
+ discoverCollections: () => discoverCollections,
3209
+ computeSSRDataForPath: () => computeSSRDataForPath,
3210
+ buildHeadFromMeta: () => buildHeadFromMeta,
3211
+ buildContentDTOs: () => buildContentDTOs,
3212
+ basename: () => basename2,
3213
+ applyIndexTemplate: () => applyIndexTemplate,
3214
+ acceptsGzip: () => acceptsGzip,
3215
+ LRUCache: () => LRUCache
3216
+ });
3217
+ var init_core = __esm(() => {
3218
+ init_src();
3219
+ });
3220
+
3221
+ // packages/cli/src/commands/analyze.ts
3222
+ var exports_analyze = {};
3223
+ __export(exports_analyze, {
3224
+ default: () => analyze
3225
+ });
3226
+ import { existsSync as existsSync9, statSync } from "node:fs";
3227
+ import { readdir as readdir6, readFile as readFile2, stat as stat5, writeFile as writeFile4 } from "node:fs/promises";
3228
+ import { basename as basename3, join as join12, relative } from "node:path";
3229
+ import Table from "cli-table3";
3230
+ async function findBundles(cwd) {
3231
+ const bundles = [];
3232
+ const chunksDir = join12(cwd, ".reroute", "chunks");
3233
+ if (existsSync9(chunksDir)) {
3234
+ const collections = await readdir6(chunksDir, { withFileTypes: true });
3235
+ for (const collection of collections) {
3236
+ if (!collection.isDirectory())
3237
+ continue;
3238
+ const collectionDir = join12(chunksDir, collection.name);
3239
+ const files = await readdir6(collectionDir);
3240
+ for (const file of files) {
3241
+ if (!file.endsWith(".js"))
3242
+ continue;
3243
+ const filePath = join12(collectionDir, file);
3244
+ const stats = await stat5(filePath);
3245
+ const content2 = await readFile2(filePath);
3246
+ const gzippedSize = Bun.gzipSync(content2).length;
3247
+ bundles.push({
3248
+ path: filePath,
3249
+ name: file,
3250
+ size: stats.size,
3251
+ gzippedSize,
3252
+ relativePath: relative(cwd, filePath),
3253
+ type: "chunk"
3254
+ });
3255
+ }
3256
+ }
3257
+ }
3258
+ const distDir = join12(cwd, "dist");
3259
+ if (existsSync9(distDir)) {
3260
+ const files = await readdir6(distDir);
3261
+ for (const file of files) {
3262
+ const filePath = join12(distDir, file);
3263
+ const stats = statSync(filePath);
3264
+ if (stats.isFile() && !file.endsWith(".map") && !file.endsWith(".d.ts")) {
3265
+ const content2 = await readFile2(filePath);
3266
+ const gzippedSize = Bun.gzipSync(content2).length;
3267
+ bundles.push({
3268
+ path: filePath,
3269
+ name: file,
3270
+ size: stats.size,
3271
+ gzippedSize,
3272
+ relativePath: relative(cwd, filePath),
3273
+ type: "binary"
3274
+ });
3275
+ }
3276
+ }
3277
+ }
3278
+ return bundles;
3279
+ }
3280
+ async function findBrowserEntry(cwd) {
3281
+ try {
3282
+ const clientDir = join12(cwd, "src", "client");
3283
+ const tsxEntry = join12(clientDir, "index.tsx");
3284
+ const tsEntry = join12(clientDir, "index.ts");
3285
+ let sourceEntry = null;
3286
+ if (existsSync9(tsxEntry)) {
3287
+ sourceEntry = tsxEntry;
3288
+ } else if (existsSync9(tsEntry)) {
3289
+ sourceEntry = tsEntry;
3290
+ }
3291
+ if (!sourceEntry)
3292
+ return null;
3293
+ const { generateContentHash: generateContentHash2 } = await Promise.resolve().then(() => (init_core(), exports_core));
3294
+ const isProd = isProductionMode();
3295
+ const result = await Bun.build({
3296
+ entrypoints: [sourceEntry],
3297
+ target: "browser",
3298
+ format: "esm",
3299
+ minify: isProd,
3300
+ sourcemap: "linked",
3301
+ external: [],
3302
+ define: {
3303
+ "process.env.NODE_ENV": isProd ? '"production"' : '"development"'
3304
+ }
3305
+ });
3306
+ if (!result.success) {
3307
+ if (result.logs && result.logs.length > 0) {
3308
+ console.warn("[reroute/analyze] Browser build errors:", result.logs.map((log2) => log2.message).join(`
3309
+ `));
3310
+ }
3311
+ return null;
3312
+ }
3313
+ if (result.outputs.length === 0) {
3314
+ console.warn("[reroute/analyze] Browser build produced no outputs");
3315
+ return null;
3316
+ }
3317
+ const tempDir = join12(cwd, ".reroute", "browser");
3318
+ await import("node:fs/promises").then((fs) => fs.mkdir(tempDir, { recursive: true }));
3319
+ const output = result.outputs[0];
3320
+ const code = await output.text();
3321
+ const actualHash = await generateContentHash2(code);
3322
+ const bundlePath = join12(tempDir, `index.${actualHash}.js`);
3323
+ await writeFile4(bundlePath, code, "utf-8");
3324
+ const mapOutput = result.outputs.find((o) => o.path.endsWith(".map"));
3325
+ if (mapOutput) {
3326
+ const mapContent = await mapOutput.text();
3327
+ await writeFile4(`${bundlePath}.map`, mapContent, "utf-8");
3328
+ }
3329
+ return bundlePath;
3330
+ } catch (error) {
3331
+ console.warn("[reroute/analyze] Could not build browser entry:", error);
3332
+ return null;
3333
+ }
3334
+ }
3335
+ function formatBytes(bytes) {
3336
+ if (bytes === 0)
3337
+ return "0 B";
3338
+ const k = 1024;
3339
+ const sizes = ["B", "KB", "MB", "GB"];
3340
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
3341
+ return `${(bytes / k ** i).toFixed(2)} ${sizes[i]}`;
3342
+ }
3343
+ function calculateStats(bundles) {
3344
+ const byCollection = {};
3345
+ let totalSize = 0;
3346
+ let totalGzippedSize = 0;
3347
+ for (const bundle of bundles) {
3348
+ totalSize += bundle.size;
3349
+ totalGzippedSize += bundle.gzippedSize;
3350
+ if (bundle.type === "chunk") {
3351
+ const match = bundle.relativePath.match(/\.reroute\/chunks\/([^/]+)/);
3352
+ const collection = match ? match[1] : "unknown";
3353
+ if (!byCollection[collection]) {
3354
+ byCollection[collection] = [];
3355
+ }
3356
+ byCollection[collection].push(bundle);
3357
+ } else {
3358
+ if (!byCollection.binaries) {
3359
+ byCollection.binaries = [];
3360
+ }
3361
+ byCollection.binaries.push(bundle);
3362
+ }
3363
+ }
3364
+ return {
3365
+ totalBundles: bundles.length,
3366
+ totalSize,
3367
+ totalGzippedSize,
3368
+ bundles,
3369
+ byCollection
3370
+ };
3371
+ }
3372
+ async function getInstalledPackageSizes(cwd) {
3373
+ const packageSizes = new Map;
3374
+ const nodeModulesDir = join12(cwd, "node_modules");
3375
+ if (!existsSync9(nodeModulesDir))
3376
+ return packageSizes;
3377
+ try {
3378
+ const { execSync } = await import("node:child_process");
3379
+ const output = execSync(`du -sk ${nodeModulesDir}/* 2>/dev/null || true`, {
3380
+ encoding: "utf8",
3381
+ maxBuffer: 10485760
3382
+ });
3383
+ const lines = output.trim().split(`
3384
+ `);
3385
+ for (const line of lines) {
3386
+ const [sizeKB, path3] = line.split("\t");
3387
+ if (!path3)
3388
+ continue;
3389
+ const pkgName = basename3(path3);
3390
+ if (pkgName.startsWith("@")) {
3391
+ const scopeDir = path3;
3392
+ const scopedOutput = execSync(`du -sk ${scopeDir}/* 2>/dev/null || true`, {
3393
+ encoding: "utf8"
3394
+ });
3395
+ const scopedLines = scopedOutput.trim().split(`
3396
+ `);
3397
+ for (const scopedLine of scopedLines) {
3398
+ const [scopedSizeKB, scopedPath] = scopedLine.split("\t");
3399
+ if (!scopedPath)
3400
+ continue;
3401
+ const fullPkgName = `${pkgName}/${basename3(scopedPath)}`;
3402
+ packageSizes.set(fullPkgName, Number.parseInt(scopedSizeKB, 10) * 1024);
3403
+ }
3404
+ } else {
3405
+ packageSizes.set(pkgName, Number.parseInt(sizeKB, 10) * 1024);
3406
+ }
3407
+ }
3408
+ } catch (_error) {}
3409
+ return packageSizes;
3410
+ }
3411
+ async function buildDependencyTree(entryPath, cwd) {
3412
+ try {
3413
+ if (!existsSync9(entryPath))
3414
+ return null;
3415
+ const root = {
3416
+ name: basename3(entryPath),
3417
+ path: entryPath,
3418
+ size: 0,
3419
+ children: []
3420
+ };
3421
+ const fromMap = await buildTreeFromSourceMap(entryPath, cwd);
3422
+ if (fromMap)
3423
+ return fromMap;
3424
+ try {
3425
+ let place = function(filePath, size) {
3426
+ if (filePath.includes("node_modules")) {
3427
+ let pkgName = filePath.slice(filePath.indexOf("node_modules") + "node_modules".length + 1);
3428
+ pkgName = pkgName.startsWith("@") ? pkgName.split("/").slice(0, 2).join("/") : pkgName.split("/")[0];
3429
+ let pkgNode = nodeModulesNode.children?.find((c) => c.name === pkgName);
3430
+ if (!pkgNode) {
3431
+ pkgNode = {
3432
+ name: pkgName || "unknown",
3433
+ path: join12(cwd, "node_modules", pkgName || ""),
3434
+ size: 0,
3435
+ children: []
3436
+ };
3437
+ nodeModulesNode.children?.push(pkgNode);
3438
+ }
3439
+ pkgNode.children?.push({
3440
+ name: basename3(filePath),
3441
+ path: filePath,
3442
+ size
3443
+ });
3444
+ } else {
3445
+ const rel = relative(cwd, filePath);
3446
+ const parts = rel.split("/").filter(Boolean);
3447
+ let current = srcNode;
3448
+ for (let i = 0;i < parts.length - 1; i++) {
3449
+ const part = parts[i];
3450
+ let child = current.children?.find((c) => c.name === part);
3451
+ if (!child) {
3452
+ child = {
3453
+ name: part,
3454
+ path: join12(cwd, ...parts.slice(0, i + 1)),
3455
+ size: 0,
3456
+ children: []
3457
+ };
3458
+ current.children?.push(child);
3459
+ }
3460
+ current = child;
3461
+ }
3462
+ current.children?.push({
3463
+ name: parts[parts.length - 1] || basename3(filePath),
3464
+ path: filePath,
3465
+ size
3466
+ });
3467
+ }
3468
+ }, compute = function(node) {
3469
+ if (node.children && node.children.length > 0) {
3470
+ node.size = node.children.reduce((s, c) => s + compute(c), 0);
3471
+ }
3472
+ return node.size;
3473
+ };
3474
+ const content2 = await readFile2(entryPath, "utf-8");
3475
+ const modulePattern = /\/\/\s*(.+?\.(?:ts|tsx|js|jsx|mjs|cjs))\s*$/gm;
3476
+ const nodeModulesNode = {
3477
+ name: "node_modules",
3478
+ path: join12(cwd, "node_modules"),
3479
+ size: 0,
3480
+ children: []
3481
+ };
3482
+ const srcNode = {
3483
+ name: "src",
3484
+ path: cwd,
3485
+ size: 0,
3486
+ children: []
3487
+ };
3488
+ root.children?.push(nodeModulesNode, srcNode);
3489
+ let match;
3490
+ let lastPath = "";
3491
+ let lastPos = 0;
3492
+ match = modulePattern.exec(content2);
3493
+ while (match !== null) {
3494
+ if (lastPath) {
3495
+ const size = match.index - lastPos;
3496
+ if (size > 0)
3497
+ place(lastPath, size);
3498
+ }
3499
+ lastPath = match[1];
3500
+ lastPos = match.index + match[0].length;
3501
+ match = modulePattern.exec(content2);
3502
+ }
3503
+ if (lastPath) {
3504
+ const size = content2.length - lastPos;
3505
+ if (size > 0)
3506
+ place(lastPath, size);
3507
+ }
3508
+ compute(root);
3509
+ return {
3510
+ entry: entryPath,
3511
+ totalSize: root.size,
3512
+ root
3513
+ };
3514
+ } catch {
3515
+ return null;
3516
+ }
3517
+ } catch {
3518
+ return null;
3519
+ }
3520
+ }
3521
+ async function buildTreeFromSourceMap(bundlePath, cwd) {
3522
+ try {
3523
+ let decodeVLQ = function(str, indexRef) {
3524
+ let result = 0;
3525
+ let shift = 0;
3526
+ let continuation;
3527
+ do {
3528
+ const ch = str[indexRef.i++];
3529
+ if (ch == null)
3530
+ return 0;
3531
+ const val = charToInt.get(ch) ?? 0;
3532
+ continuation = (val & 32) !== 0;
3533
+ const digit = val & 31;
3534
+ result += digit << shift;
3535
+ shift += 5;
3536
+ } while (continuation);
3537
+ const negate = result & 1;
3538
+ result >>= 1;
3539
+ return negate ? -result : result;
3540
+ }, absFor = function(sourcePath) {
3541
+ if (sourcePath.startsWith("/") || sourcePath.match(/^[A-Za-z]:\\/))
3542
+ return sourcePath;
3543
+ const bundleDir = bundlePath.substring(0, bundlePath.lastIndexOf("/"));
3544
+ return join12(bundleDir, sourceRoot || "", sourcePath);
3545
+ }, computeSizes = function(node) {
3546
+ if (node.children && node.children.length > 0) {
3547
+ node.size = node.children.reduce((s, c) => s + computeSizes(c), 0);
3548
+ }
3549
+ return node.size;
3550
+ };
3551
+ const mapPath = `${bundlePath}.map`;
3552
+ if (!existsSync9(mapPath))
3553
+ return null;
3554
+ const [bundle, mapJson] = await Promise.all([
3555
+ readFile2(bundlePath, "utf-8"),
3556
+ readFile2(mapPath, "utf-8")
3557
+ ]);
3558
+ const map = JSON.parse(mapJson);
3559
+ const sourceRoot = map.sourceRoot || "";
3560
+ const genLines = bundle.split(`
3561
+ `).map((l) => l.length);
3562
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
3563
+ const charToInt = new Map;
3564
+ for (let i = 0;i < chars.length; i++)
3565
+ charToInt.set(chars[i], i);
3566
+ const perSource = new Array(map.sources.length).fill(0);
3567
+ const lines = map.mappings.split(";");
3568
+ for (let line = 0;line < lines.length; line++) {
3569
+ const segs = lines[line];
3570
+ if (!segs)
3571
+ continue;
3572
+ let genCol = 0;
3573
+ let src2 = 0;
3574
+ let _srcLine = 0;
3575
+ let _srcCol = 0;
3576
+ const decoded = [];
3577
+ const idx = { i: 0 };
3578
+ while (idx.i < segs.length) {
3579
+ const start2 = idx.i;
3580
+ genCol += decodeVLQ(segs, idx);
3581
+ let srcIndex = null;
3582
+ if (idx.i < segs.length && segs[idx.i] !== ",") {
3583
+ src2 += decodeVLQ(segs, idx);
3584
+ _srcLine += decodeVLQ(segs, idx);
3585
+ _srcCol += decodeVLQ(segs, idx);
3586
+ if (idx.i < segs.length && segs[idx.i] !== ",") {
3587
+ decodeVLQ(segs, idx);
3588
+ }
3589
+ srcIndex = src2;
3590
+ }
3591
+ decoded.push({ gen: genCol, src: srcIndex });
3592
+ if (segs[idx.i] === ",")
3593
+ idx.i++;
3594
+ if (idx.i === start2)
3595
+ idx.i++;
3596
+ }
3597
+ if (decoded.length === 0)
3598
+ continue;
3599
+ for (let i = 0;i < decoded.length; i++) {
3600
+ const curr = decoded[i];
3601
+ const nextGen = i + 1 < decoded.length ? decoded[i + 1].gen : genLines[line] ?? 0;
3602
+ const span = Math.max(0, nextGen - curr.gen);
3603
+ if (span > 0 && curr.src != null && perSource[curr.src] != null) {
3604
+ perSource[curr.src] += span;
3605
+ }
3606
+ }
3607
+ }
3608
+ const root = {
3609
+ name: basename3(bundlePath),
3610
+ path: bundlePath,
3611
+ size: 0,
3612
+ children: []
3613
+ };
3614
+ const nodeModulesNode = {
3615
+ name: "node_modules",
3616
+ path: join12(cwd, "node_modules"),
3617
+ size: 0,
3618
+ children: []
3619
+ };
3620
+ const srcNode = {
3621
+ name: "src",
3622
+ path: cwd,
3623
+ size: 0,
3624
+ children: []
3625
+ };
3626
+ root.children?.push(nodeModulesNode, srcNode);
3627
+ for (let i = 0;i < map.sources.length; i++) {
3628
+ const size = perSource[i];
3629
+ if (size <= 0)
3630
+ continue;
3631
+ const srcPath = absFor(map.sources[i]);
3632
+ if (srcPath.includes("node_modules")) {
3633
+ let pkgName;
3634
+ if (srcPath.includes(".bun/")) {
3635
+ const bunMatch = srcPath.match(/\.bun\/([^/]+)@[^/]+\/node_modules\/([^/]+)\//);
3636
+ if (bunMatch) {
3637
+ const innerPkg = bunMatch[2];
3638
+ if (innerPkg === "@borewit" || innerPkg.startsWith("@")) {
3639
+ const scopeMatch = srcPath.match(/\.bun\/[^/]+@[^/]+\/node_modules\/((@[^/]+\/[^/]+))/);
3640
+ pkgName = scopeMatch ? scopeMatch[1] : innerPkg;
3641
+ } else {
3642
+ pkgName = innerPkg;
3643
+ }
3644
+ } else {
3645
+ const fallbackMatch = srcPath.match(/\.bun\/([^@/]+)/);
3646
+ pkgName = fallbackMatch ? fallbackMatch[1] : "unknown";
3647
+ }
3648
+ } else {
3649
+ const afterNm = srcPath.slice(srcPath.lastIndexOf("node_modules") + "node_modules".length + 1);
3650
+ pkgName = afterNm.startsWith("@") ? afterNm.split("/").slice(0, 2).join("/") : afterNm.split("/")[0];
3651
+ }
3652
+ let pkgNode = nodeModulesNode.children?.find((c) => c.name === pkgName);
3653
+ if (!pkgNode) {
3654
+ pkgNode = {
3655
+ name: pkgName || "unknown",
3656
+ path: join12(cwd, "node_modules", pkgName || ""),
3657
+ size: 0,
3658
+ children: []
3659
+ };
3660
+ nodeModulesNode.children?.push(pkgNode);
3661
+ }
3662
+ pkgNode.children?.push({
3663
+ name: basename3(srcPath),
3664
+ path: srcPath,
3665
+ size
3666
+ });
3667
+ } else {
3668
+ const rel = relative(cwd, srcPath);
3669
+ const parts = rel.split("/").filter(Boolean);
3670
+ let current = srcNode;
3671
+ for (let i2 = 0;i2 < parts.length - 1; i2++) {
3672
+ const part = parts[i2];
3673
+ let child = current.children?.find((c) => c.name === part);
3674
+ if (!child) {
3675
+ child = {
3676
+ name: part,
3677
+ path: join12(cwd, ...parts.slice(0, i2 + 1)),
3678
+ size: 0,
3679
+ children: []
3680
+ };
3681
+ current.children?.push(child);
3682
+ }
3683
+ current = child;
3684
+ }
3685
+ current.children?.push({
3686
+ name: parts[parts.length - 1] || basename3(srcPath),
3687
+ path: srcPath,
3688
+ size
3689
+ });
3690
+ }
3691
+ }
3692
+ computeSizes(root);
3693
+ return {
3694
+ entry: bundlePath,
3695
+ totalSize: root.size,
3696
+ root
3697
+ };
3698
+ } catch {
3699
+ return null;
3700
+ }
3701
+ }
3702
+ async function generateHTMLReport(stats, depTree, mainInfo, browserDepTree, browserInfo, initialSearch) {
3703
+ const bundles = stats.bundles.sort((a, b) => b.size - a.size);
3704
+ let actualServerBinarySize = null;
3705
+ let actualServerBinaryGzipped = null;
3706
+ try {
3707
+ const distDir = join12(process.cwd(), "dist");
3708
+ if (existsSync9(distDir)) {
3709
+ const files = await readdir6(distDir);
3710
+ for (const file of files) {
3711
+ const filePath = join12(distDir, file);
3712
+ const stats2 = statSync(filePath);
3713
+ if (stats2.isFile() && !file.includes(".")) {
3714
+ actualServerBinarySize = stats2.size;
3715
+ const content2 = await readFile2(filePath);
3716
+ actualServerBinaryGzipped = Bun.gzipSync(content2).length;
3717
+ break;
3718
+ }
3719
+ }
3720
+ }
3721
+ } catch {}
3722
+ const icon = (name, color = "#0f0", size = 16) => {
3723
+ const icons = {
3724
+ chart: '<path d="M3 3v18h18"/><path d="M7 12l4-4 4 4 6-6"/>',
3725
+ world: '<path d="M21 12a9 9 0 1 1-6.219-8.56"/><path d="M3 9v6a6 6 0 0 0 6 6h6"/><path d="M21 9v6a6 6 0 0 1-6 6H9"/>',
3726
+ search: '<circle cx="10" cy="10" r="7"/><path d="m21 21-6-6"/>',
3727
+ treemap: '<path d="M3 3v18h18"/><path d="M3 9h8v12H3z"/><path d="M13 9h8v6h-8z"/><path d="M13 17h8v4h-8z"/>',
3728
+ package: '<path d="M12 3v20"/><path d="M2 7h20v14H2z"/><path d="M2 7l10-4 10 4"/>',
3729
+ server: '<path d="M4 5h16"/><path d="M4 9h16"/><path d="M4 13h16"/><path d="M4 17h16"/><path d="M6 3v18"/><path d="M18 3v18"/>'
3730
+ };
3731
+ const path3 = icons[name] || icons.chart;
3732
+ return `<svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline-block; vertical-align: middle; margin-right: 0.4rem;">${path3}</svg>`;
3733
+ };
3734
+ const html2 = `<!DOCTYPE html>
3735
+ <html lang="en">
3736
+ <head>
3737
+ <meta charset="UTF-8">
3738
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
3739
+ <title>Reroute Bundle Analysis</title>
3740
+ <script src="https://d3js.org/d3.v7.min.js"></script>
3741
+ <style>
3742
+ * {
3743
+ margin: 0;
3744
+ padding: 0;
3745
+ box-sizing: border-box;
3746
+ }
3747
+ body {
3748
+ font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', 'Droid Sans Mono', 'Source Code Pro', monospace;
3749
+ background: #000;
3750
+ color: #0f0;
3751
+ padding: 1.5rem;
3752
+ line-height: 1.5;
3753
+ font-size: 13px;
3754
+ }
3755
+ .container {
3756
+ max-width: 1600px;
3757
+ margin: 0 auto;
3758
+ }
3759
+ h1 {
3760
+ color: #0f0;
3761
+ margin-bottom: 0.5rem;
3762
+ font-size: 1.25rem;
3763
+ font-weight: 600;
3764
+ letter-spacing: 0.5px;
3765
+ }
3766
+ h1::before {
3767
+ content: '$ ';
3768
+ color: #666;
3769
+ }
3770
+ .sticky-search {
3771
+ position: sticky;
3772
+ top: 0;
3773
+ background: #000;
3774
+ z-index: 100;
3775
+ padding: 1rem 0;
3776
+ margin: 0;
3777
+ }
3778
+ .search-row {
3779
+ display: grid;
3780
+ grid-template-columns: 1fr auto;
3781
+ gap: 0.75rem;
3782
+ align-items: start;
3783
+ height: 33px;
3784
+ }
3785
+ .search-container {
3786
+ position: relative;
3787
+ display: flex;
3788
+ flex-direction: column;
3789
+ gap: 0.5rem;
3790
+ }
3791
+ .search-input-wrapper {
3792
+ position: relative;
3793
+ }
3794
+ .search-clear {
3795
+ position: absolute;
3796
+ right: 0.5rem;
3797
+ top: 50%;
3798
+ transform: translateY(-50%);
3799
+ background: none;
3800
+ border: none;
3801
+ color: #666;
3802
+ cursor: pointer;
3803
+ padding: 0.25rem;
3804
+ display: none;
3805
+ align-items: center;
3806
+ justify-content: center;
3807
+ font-size: 17px;
3808
+ line-height: 1;
3809
+ }
3810
+ .search-clear:hover {
3811
+ color: #0f0;
3812
+ }
3813
+ .search-clear.visible {
3814
+ display: flex;
3815
+ }
3816
+ .search-input {
3817
+ width: 100%;
3818
+ padding: 0.5rem 2rem 0.5rem 0.75rem;
3819
+ background: #111;
3820
+ border: 1px solid #333;
3821
+ color: #0f0;
3822
+ font-size: 13px;
3823
+ font-family: inherit;
3824
+ }
3825
+ .note {
3826
+ color: #666;
3827
+ font-size: 12px;
3828
+ padding: 0.75rem;
3829
+ background: #111;
3830
+ border-left: 2px solid #333;
3831
+ }
3832
+ .note strong {
3833
+ color: #0f0;
3834
+ }
3835
+ @media (max-width: 640px) {
3836
+ .search-row {
3837
+ grid-template-columns: 1fr;
3838
+ gap: 0.5rem;
3839
+ }
3840
+ }
3841
+ .search-input:focus {
3842
+ outline: none;
3843
+ border-color: #0f0;
3844
+ }
3845
+ .search-input::placeholder {
3846
+ color: #666;
3847
+ }
3848
+ .search-stats {
3849
+ margin-bottom: 1rem;
3850
+ font-size: 12px;
3851
+ color: #666;
3852
+ }
3853
+ .search-actions {
3854
+ display: flex;
3855
+ gap: 0.5rem;
3856
+ align-items: stretch;
3857
+ }
3858
+ @media (max-width: 640px) {
3859
+ .search-actions {
3860
+ width: 100%;
3861
+ }
3862
+ }
3863
+ .checkbox-label {
3864
+ padding: 0.5rem 0.75rem;
3865
+ background: #111;
3866
+ border: 1px solid #333;
3867
+ color: #0f0;
3868
+ font-size: 13px;
3869
+ font-family: inherit;
3870
+ cursor: pointer;
3871
+ white-space: nowrap;
3872
+ display: flex;
3873
+ align-items: center;
3874
+ gap: 0.5rem;
3875
+ user-select: none;
3876
+ }
3877
+ .checkbox-label:hover {
3878
+ background: #1a1a1a;
3879
+ border-color: #0f0;
3880
+ }
3881
+ .checkbox-label input[type="checkbox"] {
3882
+ cursor: pointer;
3883
+ width: 14px;
3884
+ height: 14px;
3885
+ accent-color: #0f0;
3886
+ }
3887
+ @media (max-width: 640px) {
3888
+ .checkbox-label {
3889
+ flex: 1;
3890
+ }
3891
+ }
3892
+ .btn-toggle {
3893
+ padding: 0.5rem 0.75rem;
3894
+ background: #111;
3895
+ border: 1px solid #333;
3896
+ color: #0f0;
3897
+ font-size: 13px;
3898
+ font-family: inherit;
3899
+ cursor: pointer;
3900
+ white-space: nowrap;
3901
+ display: flex;
3902
+ align-items: center;
3903
+ box-sizing: border-box;
3904
+ }
3905
+ .btn-toggle:hover {
3906
+ background: #1a1a1a;
3907
+ border-color: #0f0;
3908
+ }
3909
+ @media (max-width: 640px) {
3910
+ .btn-toggle {
3911
+ flex: 1;
3912
+ }
3913
+ }
3914
+ .hidden {
3915
+ display: none !important;
3916
+ }
3917
+ .section {
3918
+ margin-bottom: 1.5rem;
3919
+ border: 1px solid #333;
3920
+ background: #0a0a0a;
3921
+ }
3922
+ .section-header {
3923
+ padding: 0.75rem 1rem;
3924
+ background: #111;
3925
+ border-bottom: 1px solid #333;
3926
+ cursor: pointer;
3927
+ user-select: none;
3928
+ display: flex;
3929
+ justify-content: space-between;
3930
+ align-items: center;
3931
+ }
3932
+ .section-header:hover {
3933
+ background: #151515;
3934
+ }
3935
+ .section-title {
3936
+ font-size: 14px;
3937
+ font-weight: 600;
3938
+ color: #0f0;
3939
+ }
3940
+ .section-title.server {
3941
+ color: #666;
3942
+ }
3943
+ .section-meta {
3944
+ color: #666;
3945
+ font-size: 12px;
3946
+ }
3947
+ .section-toggle {
3948
+ color: #666;
3949
+ font-size: 12px;
3950
+ }
3951
+ .section-content {
3952
+ padding: 1rem;
3953
+ }
3954
+ .section.collapsed .section-content {
3955
+ display: none;
3956
+ }
3957
+ .section.collapsed .section-toggle::before {
3958
+ content: '▶ ';
3959
+ }
3960
+ .section-toggle::before {
3961
+ content: '▼ ';
3962
+ }
3963
+ .stats-grid {
3964
+ display: grid;
3965
+ grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
3966
+ gap: 0.5rem;
3967
+ margin-bottom: 1rem;
3968
+ }
3969
+ .stat {
3970
+ background: #111;
3971
+ padding: 0.5rem 0.75rem;
3972
+ border: 1px solid #222;
3973
+ }
3974
+ .stat-label {
3975
+ font-size: 11px;
3976
+ color: #666;
3977
+ margin-bottom: 0.25rem;
3978
+ }
3979
+ .stat-value {
3980
+ font-size: 16px;
3981
+ font-weight: 600;
3982
+ color: #0f0;
3983
+ }
3984
+ .stat-value.server {
3985
+ color: #666;
3986
+ }
3987
+ .dep-list {
3988
+ list-style: none;
3989
+ margin: 0;
3990
+ padding: 0;
3991
+ }
3992
+ .dep-item {
3993
+ padding: 0.4rem 0;
3994
+ border-bottom: 1px solid #1a1a1a;
3995
+ display: flex;
3996
+ justify-content: space-between;
3997
+ align-items: center;
3998
+ font-size: 12px;
3999
+ }
4000
+ .dep-item:last-child {
4001
+ border-bottom: none;
4002
+ }
4003
+ .dep-name {
4004
+ color: #0f0;
4005
+ font-weight: 500;
4006
+ }
4007
+ .dep-name.server {
4008
+ color: #666;
4009
+ }
4010
+ .dep-size {
4011
+ color: #666;
4012
+ font-size: 11px;
4013
+ }
4014
+ .dep-children {
4015
+ margin-left: 1.5rem;
4016
+ margin-top: 0.25rem;
4017
+ }
4018
+ .dep-child {
4019
+ color: #666;
4020
+ font-size: 11px;
4021
+ padding: 0.2rem 0;
4022
+ }
4023
+ .load-more {
4024
+ margin-top: 0.75rem;
4025
+ padding: 0.4rem 0.75rem;
4026
+ background: #111;
4027
+ border: 1px solid #333;
4028
+ color: #0f0;
4029
+ cursor: pointer;
4030
+ font-family: inherit;
4031
+ font-size: 12px;
4032
+ width: 100%;
4033
+ }
4034
+ .load-more:hover {
4035
+ background: #1a1a1a;
4036
+ border-color: #0f0;
4037
+ }
4038
+ .treemap-container {
4039
+ width: 100%;
4040
+ overflow: visible;
4041
+ }
4042
+ .empty {
4043
+ text-align: center;
4044
+ padding: 2rem;
4045
+ color: #666;
4046
+ font-size: 12px;
4047
+ }
4048
+ .treemap-cell {
4049
+ stroke: #111;
4050
+ stroke-width: 1px;
4051
+ cursor: pointer;
4052
+ transition: all 0.2s;
4053
+ }
4054
+ .treemap-cell:hover {
4055
+ stroke: #0f0;
4056
+ stroke-width: 2px;
4057
+ }
4058
+ .treemap-label {
4059
+ fill: #0f0;
4060
+ font-size: 10px;
4061
+ pointer-events: none;
4062
+ text-anchor: middle;
4063
+ font-weight: 500;
4064
+ }
4065
+ .treemap-tooltip {
4066
+ position: absolute;
4067
+ background: #000;
4068
+ border: 1px solid #0f0;
4069
+ padding: 0.5rem 0.75rem;
4070
+ pointer-events: none;
4071
+ opacity: 0;
4072
+ transition: opacity 0.15s;
4073
+ z-index: 1000;
4074
+ font-size: 11px;
4075
+ }
4076
+ .treemap-tooltip.visible {
4077
+ opacity: 1;
4078
+ }
4079
+ .tooltip-name {
4080
+ font-weight: 600;
4081
+ color: #0f0;
4082
+ margin-bottom: 0.25rem;
4083
+ }
4084
+ .tooltip-path {
4085
+ color: #666;
4086
+ font-size: 10px;
4087
+ margin-bottom: 0.25rem;
4088
+ word-break: break-all;
4089
+ }
4090
+ .tooltip-size {
4091
+ color: #0f0;
4092
+ font-size: 11px;
4093
+ }
4094
+ </style>
4095
+ </head>
4096
+ <body>
4097
+ <div class="container">
4098
+ <h1>reroute analyze</h1>
4099
+
4100
+ <div class="sticky-search">
4101
+ <div class="search-row">
4102
+ <div class="search-container">
4103
+ <div class="search-input-wrapper">
4104
+ <input
4105
+ type="text"
4106
+ id="search-input"
4107
+ class="search-input"
4108
+ placeholder="search dependencies (e.g., 'react', 'scheduler', 'sharp')..."
4109
+ />
4110
+ <button class="search-clear" id="search-clear" type="button" aria-label="Clear search">×</button>
4111
+ </div>
4112
+ </div>
4113
+ <div class="search-actions">
4114
+ <button class="btn-toggle" id="expand-all">expand</button>
4115
+ <button class="btn-toggle" id="collapse-all">collapse</button>
4116
+ </div>
4117
+ </div>
4118
+ </div>
4119
+
4120
+ <div class="section">
4121
+ <div class="section-header">
4122
+ <div>
4123
+ <div class="section-title">${icon("chart")}Summary</div>
4124
+ </div>
4125
+ </div>
4126
+ <div class="section-content">
4127
+ <div class="stats-grid">
4128
+ <div class="stat">
4129
+ <div class="stat-label">browser dependencies</div>
4130
+ <div class="stat-value">${(() => {
4131
+ const nodeModules = browserDepTree?.root.children?.find((c) => c.name === "node_modules");
4132
+ return nodeModules?.children?.length || 0;
4133
+ })()}</div>
4134
+ </div>
4135
+ <div class="stat">
4136
+ <div class="stat-label">browser bundle</div>
4137
+ <div class="stat-value">${browserInfo ? formatBytes(browserInfo.size) : "N/A"}</div>
4138
+ </div>
4139
+ <div class="stat">
4140
+ <div class="stat-label">server dependencies</div>
4141
+ <div class="stat-value">${(() => {
4142
+ const nodeModules = depTree?.root.children?.find((c) => c.name === "node_modules");
4143
+ return nodeModules?.children?.length || 0;
4144
+ })()}</div>
4145
+ </div>
4146
+ <div class="stat">
4147
+ <div class="stat-label">final server binary</div>
4148
+ <div class="stat-value">${actualServerBinarySize ? formatBytes(actualServerBinarySize) : "N/A"}</div>
4149
+ </div>
4150
+ </div>
4151
+ </div>
4152
+ </div>
4153
+
4154
+ <!-- ═══════════════════════════════════════════════════════════ -->
4155
+ <!-- BROWSER SECTION (what users download) -->
4156
+ <!-- ═══════════════════════════════════════════════════════════ -->
4157
+
4158
+ ${browserInfo ? `
4159
+ <div class="section">
4160
+ <div class="section-header" onclick="this.parentElement.classList.toggle('collapsed')">
4161
+ <div>
4162
+ <div class="section-title">${icon("world")}browser bundle</div>
4163
+ <div class="section-meta">${browserInfo.name} &middot; ${formatBytes(browserInfo.size)} raw &middot; ${formatBytes(browserInfo.gzippedSize)} gzipped</div>
4164
+ </div>
4165
+ <div class="section-toggle"></div>
4166
+ </div>
4167
+ <div class="section-content">
4168
+ <div class="stats-grid">
4169
+ <div class="stat">
4170
+ <div class="stat-label">raw size</div>
4171
+ <div class="stat-value">${formatBytes(browserInfo.size)}</div>
4172
+ </div>
4173
+ <div class="stat">
4174
+ <div class="stat-label">gzipped</div>
4175
+ <div class="stat-value">${formatBytes(browserInfo.gzippedSize)}</div>
4176
+ </div>
4177
+ <div class="stat">
4178
+ <div class="stat-label">compression</div>
4179
+ <div class="stat-value">${((1 - browserInfo.gzippedSize / browserInfo.size) * 100).toFixed(1)}%</div>
4180
+ </div>
4181
+ <div class="stat">
4182
+ <div class="stat-label">3G transfer</div>
4183
+ <div class="stat-value">${(() => {
4184
+ const t = browserInfo.gzippedSize / 1024 / 204.8;
4185
+ return t >= 1 ? `${t.toFixed(1)}s` : `${(t * 1000).toFixed(0)}ms`;
4186
+ })()}</div>
4187
+ </div>
4188
+ <div class="stat">
4189
+ <div class="stat-label">4G transfer</div>
4190
+ <div class="stat-value">${(() => {
4191
+ const t = browserInfo.gzippedSize / 1024 / 1152;
4192
+ return t >= 1 ? `${t.toFixed(1)}s` : `${(t * 1000).toFixed(0)}ms`;
4193
+ })()}</div>
4194
+ </div>
4195
+ <div class="stat">
4196
+ <div class="stat-label">5G transfer</div>
4197
+ <div class="stat-value">${(() => {
4198
+ const t = browserInfo.gzippedSize / 1024 / 6400;
4199
+ return t >= 1 ? `${t.toFixed(1)}s` : `${(t * 1000).toFixed(0)}ms`;
4200
+ })()}</div>
4201
+ </div>
4202
+ </div>
4203
+ </div>
4204
+ </div>
4205
+ ` : ""}
4206
+ ${browserDepTree ? `
4207
+ <div class="section collapsed">
4208
+ <div class="section-header" onclick="this.parentElement.classList.toggle('collapsed')">
4209
+ <div>
4210
+ <div class="section-title">${icon("search")}browser dependencies</div>
4211
+ <div class="section-meta">dependency breakdown</div>
4212
+ </div>
4213
+ <div class="section-toggle"></div>
4214
+ </div>
4215
+ <div class="section-content">
4216
+ <div id="browser-deps-list"></div>
4217
+ </div>
4218
+ </div>
4219
+
4220
+ <div class="section collapsed">
4221
+ <div class="section-header" onclick="this.parentElement.classList.toggle('collapsed')">
4222
+ <div>
4223
+ <div class="section-title">${icon("treemap")}browser treemap</div>
4224
+ <div class="section-meta">visual dependency breakdown</div>
4225
+ </div>
4226
+ <div class="section-toggle"></div>
4227
+ </div>
4228
+ <div class="section-content">
4229
+ <div class="treemap-container" id="browser-treemap"></div>
4230
+ </div>
4231
+ </div>
4232
+ ` : ""}
4233
+
4234
+ ${Object.entries(stats.byCollection).filter(([collection]) => collection !== "binaries").map(([collection, collectionBundles]) => {
4235
+ const collectionTotal = collectionBundles.reduce((sum, b) => sum + b.size, 0);
4236
+ const collectionGzipped = collectionBundles.reduce((sum, b) => sum + b.gzippedSize, 0);
4237
+ return `
4238
+ <div class="section collapsed">
4239
+ <div class="section-header" onclick="this.parentElement.classList.toggle('collapsed')">
4240
+ <div>
4241
+ <div class="section-title">${icon("package")}${collection} chunks</div>
4242
+ <div class="section-meta">${collectionBundles.length} files &middot; ${formatBytes(collectionTotal)} raw &middot; ${formatBytes(collectionGzipped)} gzipped</div>
4243
+ </div>
4244
+ <div class="section-toggle"></div>
4245
+ </div>
4246
+ <div class="section-content">
4247
+ <ul class="dep-list" id="${collection}-chunks-list">
4248
+ ${collectionBundles.sort((a, b) => b.size - a.size).slice(0, 10).map((bundle) => `
4249
+ <li class="dep-item">
4250
+ <span class="dep-name">${bundle.name}</span>
4251
+ <span class="dep-size">${formatBytes(bundle.size)} &middot; ${formatBytes(bundle.gzippedSize)} gz</span>
4252
+ </li>
4253
+ `).join("")}
4254
+ </ul>
4255
+ ${collectionBundles.length > 10 ? `
4256
+ <button class="load-more" onclick="loadMore${collection}()">load ${collectionBundles.length - 10} more...</button>
4257
+ <script>
4258
+ let ${collection}Loaded = false;
4259
+ function loadMore${collection}() {
4260
+ if (${collection}Loaded) return;
4261
+ ${collection}Loaded = true;
4262
+ const list = document.getElementById('${collection}-chunks-list');
4263
+ const remaining = ${JSON.stringify(collectionBundles.slice(10).map((b) => ({ name: b.name, size: b.size, gzippedSize: b.gzippedSize })))};
4264
+ for (const bundle of remaining) {
4265
+ const li = document.createElement('li');
4266
+ li.className = 'dep-item';
4267
+ li.innerHTML = '<span class="dep-name">' + bundle.name + '</span><span class="dep-size">' + formatBytes(bundle.size) + ' &middot; ' + formatBytes(bundle.gzippedSize) + ' gz</span>';
4268
+ list.appendChild(li);
4269
+ }
4270
+ event.target.remove();
4271
+ }
4272
+ </script>
4273
+ ` : ""}
4274
+ </div>
4275
+ </div>
4276
+ `;
4277
+ }).join("")}
4278
+
4279
+ <!-- ═══════════════════════════════════════════════════════════ -->
4280
+ <!-- SERVER SECTION (not shipped to browser) -->
4281
+ <!-- ═══════════════════════════════════════════════════════════ -->
4282
+
4283
+ ${actualServerBinarySize ? `
4284
+ <div class="section collapsed">
4285
+ <div class="section-header" onclick="this.parentElement.classList.toggle('collapsed')">
4286
+ <div>
4287
+ <div class="section-title server">${icon("server", "#666")}final server binary</div>
4288
+ <div class="section-meta">standalone executable &middot; ${formatBytes(actualServerBinarySize)} &middot; not shipped to browser</div>
4289
+ </div>
4290
+ <div class="section-toggle"></div>
4291
+ </div>
4292
+ <div class="section-content">
4293
+ <div class="stats-grid">
4294
+ <div class="stat">
4295
+ <div class="stat-label">binary size</div>
4296
+ <div class="stat-value server">${formatBytes(actualServerBinarySize)}</div>
4297
+ </div>
4298
+ ${actualServerBinaryGzipped ? `
4299
+ <div class="stat">
4300
+ <div class="stat-label">gzipped</div>
4301
+ <div class="stat-value server">${formatBytes(actualServerBinaryGzipped)}</div>
4302
+ </div>
4303
+ <div class="stat">
4304
+ <div class="stat-label">compression</div>
4305
+ <div class="stat-value server">${((1 - actualServerBinaryGzipped / actualServerBinarySize) * 100).toFixed(1)}%</div>
4306
+ </div>
4307
+ ` : ""}
4308
+ </div>
4309
+ </div>
4310
+ </div>
4311
+ ` : ""}
4312
+ ${depTree ? `
4313
+ <div class="section collapsed">
4314
+ <div class="section-header" onclick="this.parentElement.classList.toggle('collapsed')">
4315
+ <div>
4316
+ <div class="section-title server">${icon("search", "#666")}server dependencies</div>
4317
+ <div class="section-meta">server-side only &middot; not shipped to browser</div>
4318
+ </div>
4319
+ <div class="section-toggle"></div>
4320
+ </div>
4321
+ <div class="section-content">
4322
+ <div id="server-deps-list"></div>
4323
+ </div>
4324
+ </div>
4325
+
4326
+ <div class="section collapsed">
4327
+ <div class="section-header" onclick="this.parentElement.classList.toggle('collapsed')">
4328
+ <div>
4329
+ <div class="section-title server">${icon("treemap", "#666")}server treemap</div>
4330
+ <div class="section-meta">visual dependency breakdown</div>
4331
+ </div>
4332
+ <div class="section-toggle"></div>
4333
+ </div>
4334
+ <div class="section-content">
4335
+ <div class="treemap-container" id="server-treemap"></div>
4336
+ </div>
4337
+ </div>
4338
+ ` : ""}
4339
+
4340
+ ${bundles.length === 0 ? '<div class="empty">no bundles found. run <code>reroute gen</code> or <code>reroute build</code> first.</div>' : ""}
4341
+ </div>
4342
+ <div class="treemap-tooltip" id="tooltip">
4343
+ <div class="tooltip-name" id="tooltip-name"></div>
4344
+ <div class="tooltip-path" id="tooltip-path"></div>
4345
+ <div class="tooltip-size" id="tooltip-size"></div>
4346
+ </div>
4347
+ ${browserDepTree || depTree ? `
4348
+ <script>
4349
+ const browserDepTree = ${browserDepTree ? JSON.stringify(browserDepTree) : "null"};
4350
+ const serverDepTree = ${depTree ? JSON.stringify(depTree) : "null"};
4351
+ const formatBytes = ${formatBytes.toString()};
4352
+
4353
+ function cleanPath(path) {
4354
+ if (!path) return '';
4355
+ const nodeModulesIdx = path.indexOf('node_modules');
4356
+ if (nodeModulesIdx === -1) return path;
4357
+ return path.substring(nodeModulesIdx);
4358
+ }
4359
+
4360
+ function convertNode(node) {
4361
+ return {
4362
+ name: node.name,
4363
+ path: node.path,
4364
+ size: node.size,
4365
+ children: node.children && node.children.length > 0 ? node.children.map(convertNode) : null
4366
+ };
4367
+ }
4368
+
4369
+ function renderDepTreemap(tree, containerId) {
4370
+ const container = d3.select(containerId);
4371
+ container.selectAll("*").remove();
4372
+ const width = container.node()?.clientWidth || 1200;
4373
+
4374
+ const tooltip = d3.select("#tooltip");
4375
+
4376
+ const root = d3.hierarchy(convertNode(tree.root))
4377
+ .sum(d => d.size || 0)
4378
+ .sort((a, b) => (b.value || 0) - (a.value || 0));
4379
+
4380
+ // Calculate height based on data - use aspect ratio that works well for treemaps
4381
+ // More items = taller treemap, but cap at reasonable max
4382
+ const leafCount = root.leaves().length;
4383
+ const baseHeight = Math.max(400, Math.min(1200, width * 0.6));
4384
+ const height = Math.min(baseHeight + (leafCount * 2), width * 1.2);
4385
+
4386
+ const svg = container.append("svg")
4387
+ .attr("width", width)
4388
+ .attr("height", height);
4389
+
4390
+ const treemap = d3.treemap()
4391
+ .size([width, height])
4392
+ .padding(2)
4393
+ .round(true);
4394
+
4395
+ treemap(root);
4396
+
4397
+ function hashString(str) {
4398
+ let h = 0;
4399
+ for (let i = 0; i < str.length; i++) {
4400
+ h = ((h << 5) - h + str.charCodeAt(i)) | 0;
4401
+ }
4402
+ return Math.abs(h);
4403
+ }
4404
+ function getPackageName(d) {
4405
+ const p = d.data.path || '';
4406
+ if (!p.includes('node_modules')) return null;
4407
+ const idx = p.lastIndexOf('node_modules/');
4408
+ if (idx === -1) return null;
4409
+ const after = p.slice(idx + 'node_modules/'.length);
4410
+ // Handle @scope/package or package
4411
+ if (after.startsWith('@')) {
4412
+ const parts = after.split('/');
4413
+ return parts.length >= 2 ? parts[0] + '/' + parts[1] : after;
4414
+ }
4415
+ return after.split('/')[0];
4416
+ }
4417
+
4418
+ // Terminal-style colors
4419
+ const colorCache = {};
4420
+ function colorFor(d) {
4421
+ const p = d.data.path || '';
4422
+ if (p.includes('node_modules')) {
4423
+ const pkg = getPackageName(d) || 'unknown';
4424
+
4425
+ if (!colorCache[pkg]) {
4426
+ const hash = hashString(pkg);
4427
+ // Shades of green for terminal aesthetic
4428
+ const brightness = 40 + (hash % 40);
4429
+ colorCache[pkg] = 'rgb(0, ' + brightness + ', 0)';
4430
+ }
4431
+
4432
+ return colorCache[pkg];
4433
+ }
4434
+ // Brighter green for local src files
4435
+ const depth = Math.min(d.depth, 4);
4436
+ const brightness = 80 + depth * 30;
4437
+ return 'rgb(0, ' + Math.min(255, brightness) + ', 0)';
4438
+ }
4439
+
4440
+ const cell = svg.selectAll("g")
4441
+ .data(root.leaves())
4442
+ .enter()
4443
+ .append("g")
4444
+ .attr("transform", d => \`translate(\${d.x0},\${d.y0})\`);
4445
+
4446
+ cell.append("rect")
4447
+ .attr("class", "treemap-cell")
4448
+ .attr("width", d => d.x1 - d.x0)
4449
+ .attr("height", d => d.y1 - d.y0)
4450
+ .attr("fill", colorFor)
4451
+ .on("mouseover", function(event, d) {
4452
+ d3.select(this).attr("stroke", "#0f0").attr("stroke-width", 2);
4453
+ tooltip.select("#tooltip-name").text(d.data.name);
4454
+ tooltip.select("#tooltip-path").text(cleanPath(d.data.path || ''));
4455
+ tooltip.select("#tooltip-size").text(formatBytes(d.value));
4456
+ tooltip.classed("visible", true);
4457
+ })
4458
+ .on("mousemove", function(event) {
4459
+ tooltip.style("left", (event.pageX + 10) + "px")
4460
+ .style("top", (event.pageY - 10) + "px");
4461
+ })
4462
+ .on("mouseout", function() {
4463
+ d3.select(this).attr("stroke", "#111").attr("stroke-width", 1);
4464
+ tooltip.classed("visible", false);
4465
+ });
4466
+
4467
+ // Labels on cells - show first letter for small cells, truncated text for larger ones
4468
+ const labels = cell.filter(d => {
4469
+ const w = d.x1 - d.x0;
4470
+ const h = d.y1 - d.y0;
4471
+ // Show label if cell is at least 10px wide and 10px tall (enough for first letter)
4472
+ return w > 10 && h > 10;
4473
+ })
4474
+ .append("text")
4475
+ .attr("class", "treemap-label")
4476
+ .attr("x", d => (d.x1 - d.x0) / 2)
4477
+ .attr("y", d => (d.y1 - d.y0) / 2)
4478
+ .style("text-anchor", "middle")
4479
+ .style("dominant-baseline", "middle");
4480
+
4481
+ labels.each(function(d) {
4482
+ const name = d.data.name;
4483
+ const width = d.x1 - d.x0;
4484
+ const height = d.y1 - d.y0;
4485
+ const text = d3.select(this);
4486
+
4487
+ // Estimate character width: ~6px per character for 10px monospace font
4488
+ const charWidth = 6;
4489
+ const maxChars = Math.floor((width - 8) / charWidth); // 8px padding
4490
+ const minSize = Math.min(width, height);
4491
+
4492
+ let labelText = '';
4493
+ let fontSize = 10;
4494
+
4495
+ if (width < 20 || height < 14 || maxChars < 1) {
4496
+ // Very small cell - show first letter only, ensure it's readable
4497
+ labelText = name.charAt(0).toUpperCase();
4498
+ // Scale font to fit but ensure minimum 8px for readability
4499
+ fontSize = Math.max(8, Math.min(12, minSize * 0.65));
4500
+ } else if (width < 30 || maxChars < 2) {
4501
+ // Small cell - show first letter (or 2 chars if fits)
4502
+ labelText = name.substring(0, Math.min(1, name.length)).toUpperCase();
4503
+ fontSize = Math.max(8, Math.min(10, minSize * 0.5));
4504
+ } else if (width < 40 || maxChars < 3) {
4505
+ // Small-medium cell - show first 2-3 characters
4506
+ labelText = name.substring(0, Math.min(2, name.length));
4507
+ fontSize = 9;
4508
+ } else if (maxChars >= name.length) {
4509
+ // Large enough - show full name
4510
+ labelText = name;
4511
+ fontSize = 10;
4512
+ } else {
4513
+ // Medium cell - truncate based on available width
4514
+ const truncateLength = Math.max(3, maxChars - 3); // Leave room for "..."
4515
+ labelText = name.substring(0, truncateLength) + '...';
4516
+ fontSize = 10;
4517
+ }
4518
+
4519
+ text.style("font-size", fontSize + "px")
4520
+ .text(labelText);
4521
+
4522
+ // Show size for larger cells
4523
+ if (width > 90 && height > 32) {
4524
+ text.append("tspan")
4525
+ .attr("x", width / 2)
4526
+ .attr("dy", "14")
4527
+ .text(formatBytes(d.value));
4528
+ }
4529
+ });
4530
+ }
4531
+
4532
+ let currentSearchTerm = '';
4533
+
4534
+ function updateSearchStats(visibleCount, totalCount) {
4535
+ const stats = document.getElementById('search-stats');
4536
+ if (stats) {
4537
+ if (currentSearchTerm) {
4538
+ stats.textContent = 'Showing ' + visibleCount + ' of ' + totalCount + ' items matching "' + currentSearchTerm + '"';
4539
+ } else {
4540
+ stats.textContent = '';
4541
+ }
4542
+ }
4543
+ }
4544
+
4545
+ function filterTreemap(searchTerm) {
4546
+ currentSearchTerm = searchTerm.toLowerCase();
4547
+ if (!searchTerm) {
4548
+ if (browserDepTree) renderDepTreemap(browserDepTree, "#browser-treemap");
4549
+ if (serverDepTree) renderDepTreemap(serverDepTree, "#server-treemap");
4550
+ updateSearchStats(0, 0);
4551
+
4552
+ // Show all elements
4553
+ document.querySelectorAll('.section').forEach(el => el.classList.remove('hidden'));
4554
+ document.querySelectorAll('tbody tr').forEach(el => el.classList.remove('hidden'));
4555
+ document.querySelectorAll('.dep-item').forEach(el => el.classList.remove('hidden'));
4556
+ document.querySelectorAll('.stat').forEach(el => el.classList.remove('hidden'));
4557
+ return;
4558
+ }
4559
+
4560
+ let totalMatches = 0;
4561
+ let totalItems = 0;
4562
+
4563
+ // Filter treemap data
4564
+ function filterNode(node) {
4565
+ // Only match against the package/file name, not the full path
4566
+ // to avoid matching build directories like .reroute/
4567
+ const matches = node.name.toLowerCase().includes(searchTerm);
4568
+
4569
+ if (!node.children || node.children.length === 0) {
4570
+ return matches ? { ...node } : null;
4571
+ }
4572
+
4573
+ const filteredChildren = node.children
4574
+ .map(child => filterNode(child))
4575
+ .filter(child => child !== null);
4576
+
4577
+ if (filteredChildren.length > 0 || matches) {
4578
+ return {
4579
+ ...node,
4580
+ children: filteredChildren.length > 0 ? filteredChildren : undefined
4581
+ };
4582
+ }
4583
+
4584
+ return null;
4585
+ }
4586
+
4587
+ function countLeaves(node) {
4588
+ if (!node.children || node.children.length === 0) return 1;
4589
+ return node.children.reduce((sum, child) => sum + countLeaves(child), 0);
4590
+ }
4591
+
4592
+ // Filter browser treemap
4593
+ if (browserDepTree) {
4594
+ const filtered = filterNode(browserDepTree.root);
4595
+ if (filtered) {
4596
+ const totalLeaves = countLeaves(browserDepTree.root);
4597
+ const visibleLeaves = countLeaves(filtered);
4598
+ totalItems += totalLeaves;
4599
+ totalMatches += visibleLeaves;
4600
+ renderDepTreemap({ ...browserDepTree, root: filtered }, "#browser-treemap");
4601
+ } else {
4602
+ const elem = document.getElementById('browser-treemap');
4603
+ if (elem) elem.innerHTML = '<div class="empty">No matches found</div>';
4604
+ }
4605
+ }
4606
+
4607
+ // Filter server treemap
4608
+ if (serverDepTree) {
4609
+ const filtered = filterNode(serverDepTree.root);
4610
+ if (filtered) {
4611
+ renderDepTreemap({ ...serverDepTree, root: filtered }, "#server-treemap");
4612
+ } else {
4613
+ const elem = document.getElementById('server-treemap');
4614
+ if (elem) elem.innerHTML = '<div class="empty">No matches found</div>';
4615
+ }
4616
+ }
4617
+
4618
+ // Render matching items that aren't loaded yet
4619
+ if (searchTerm) {
4620
+ renderMatchingDeps('browser-deps-list', searchTerm);
4621
+ renderMatchingDeps('server-deps-list', searchTerm);
4622
+ }
4623
+
4624
+ // Filter dependency lists
4625
+ document.querySelectorAll('.dep-item').forEach(item => {
4626
+ const packageName = item.getAttribute('data-package-name') || '';
4627
+ if (packageName.includes(searchTerm)) {
4628
+ item.classList.remove('hidden');
4629
+ totalMatches++;
4630
+ } else {
4631
+ item.classList.add('hidden');
4632
+ }
4633
+ totalItems++;
4634
+ });
4635
+
4636
+ // Filter bundle/chunk tables
4637
+ document.querySelectorAll('tbody tr').forEach(row => {
4638
+ const text = row.textContent.toLowerCase();
4639
+ if (text.includes(searchTerm)) {
4640
+ row.classList.remove('hidden');
4641
+ totalMatches++;
4642
+ } else {
4643
+ row.classList.add('hidden');
4644
+ }
4645
+ totalItems++;
4646
+ });
4647
+
4648
+ // Filter section headers and content
4649
+ document.querySelectorAll('.section').forEach(section => {
4650
+ // Check if section has visible content
4651
+ const tbody = section.querySelector('tbody');
4652
+ const depList = section.querySelector('.dep-list');
4653
+ const treemapContainer = section.querySelector('.treemap-container');
4654
+ const stats = section.querySelectorAll('.stat');
4655
+ const sectionTitle = section.querySelector('.section-title');
4656
+
4657
+ let hasVisibleContent = false;
4658
+
4659
+ if (tbody) {
4660
+ const visibleRows = tbody.querySelectorAll('tr:not(.hidden)');
4661
+ hasVisibleContent = visibleRows.length > 0;
4662
+ } else if (depList) {
4663
+ const visibleItems = depList.querySelectorAll('.dep-item:not(.hidden)');
4664
+ hasVisibleContent = visibleItems.length > 0;
4665
+ } else if (treemapContainer) {
4666
+ hasVisibleContent = !treemapContainer.querySelector('.empty');
4667
+ } else if (stats.length > 0) {
4668
+ // Stat sections: show if any stat matches
4669
+ const statMatches = Array.from(stats).some(stat =>
4670
+ stat.textContent.toLowerCase().includes(searchTerm)
4671
+ );
4672
+ hasVisibleContent = statMatches;
4673
+ } else if (sectionTitle && sectionTitle.textContent.toLowerCase().includes(searchTerm)) {
4674
+ // Section title matches
4675
+ hasVisibleContent = true;
4676
+ }
4677
+
4678
+ if (hasVisibleContent) {
4679
+ section.classList.remove('hidden');
4680
+ } else {
4681
+ section.classList.add('hidden');
4682
+ }
4683
+ });
4684
+
4685
+ updateSearchStats(totalMatches, totalItems);
4686
+ }
4687
+
4688
+ // Setup search
4689
+ const searchInput = document.getElementById('search-input');
4690
+ const searchClear = document.getElementById('search-clear');
4691
+
4692
+ function updateClearButton() {
4693
+ if (searchClear && searchInput) {
4694
+ if (searchInput.value.trim().length > 0) {
4695
+ searchClear.classList.add('visible');
4696
+ } else {
4697
+ searchClear.classList.remove('visible');
4698
+ }
4699
+ }
4700
+ }
4701
+
4702
+ if (searchInput) {
4703
+ searchInput.addEventListener('input', (e) => {
4704
+ filterTreemap(e.target.value);
4705
+ updateClearButton();
4706
+ });
4707
+ // Check initial state
4708
+ updateClearButton();
4709
+ }
4710
+
4711
+ if (searchClear) {
4712
+ searchClear.addEventListener('click', () => {
4713
+ if (searchInput) {
4714
+ searchInput.value = '';
4715
+ searchInput.focus();
4716
+ filterTreemap('');
4717
+ updateClearButton();
4718
+ }
4719
+ });
4720
+ }
4721
+
4722
+ // Expand/collapse all handlers
4723
+ const expandAllBtn = document.getElementById('expand-all');
4724
+ const collapseAllBtn = document.getElementById('collapse-all');
4725
+
4726
+ if (expandAllBtn) {
4727
+ expandAllBtn.addEventListener('click', () => {
4728
+ document.querySelectorAll('.section.collapsed').forEach(section => {
4729
+ section.classList.remove('collapsed');
4730
+ });
4731
+ });
4732
+ }
4733
+
4734
+ if (collapseAllBtn) {
4735
+ collapseAllBtn.addEventListener('click', () => {
4736
+ document.querySelectorAll('.section:not(.collapsed)').forEach(section => {
4737
+ // Don't collapse the Summary section
4738
+ const title = section.querySelector('.section-title');
4739
+ if (title && !title.textContent.includes('Summary')) {
4740
+ section.classList.add('collapsed');
4741
+ }
4742
+ });
4743
+ });
4744
+ }
4745
+
4746
+ // Store full packages data for search
4747
+ const depListData = new Map();
4748
+
4749
+ // Helper to create a dep item element
4750
+ function createDepItem(pkg, isServer, totalSize, bundleInfo) {
4751
+ const li = document.createElement('li');
4752
+ li.className = 'dep-item';
4753
+ li.setAttribute('data-package-name', pkg.name.toLowerCase());
4754
+
4755
+ const nameSpan = document.createElement('span');
4756
+ nameSpan.className = isServer ? 'dep-name server' : 'dep-name';
4757
+ nameSpan.textContent = pkg.name;
4758
+
4759
+ const sizeSpan = document.createElement('span');
4760
+ sizeSpan.className = 'dep-size';
4761
+
4762
+ // Estimate gzipped size using compression ratio
4763
+ let sizeText = formatBytes(pkg.size);
4764
+ if (bundleInfo && bundleInfo.size > 0) {
4765
+ const compressionRatio = bundleInfo.gzippedSize / bundleInfo.size;
4766
+ const estimatedGz = Math.round(pkg.size * compressionRatio);
4767
+ sizeText += ' (' + formatBytes(estimatedGz) + ' gz)';
4768
+ }
4769
+ sizeText += ' · ' + ((pkg.size / totalSize) * 100).toFixed(1) + '%';
4770
+ sizeSpan.textContent = sizeText;
4771
+
4772
+ li.appendChild(nameSpan);
4773
+ li.appendChild(sizeSpan);
4774
+ return li;
4775
+ }
4776
+
4777
+ // Populate dependency lists
4778
+ function populateDepList(tree, containerId, isServer, bundleInfo) {
4779
+ const container = document.getElementById(containerId);
4780
+ if (!container || !tree) return;
4781
+
4782
+ const nodeModules = tree.root.children?.find(c => c.name === 'node_modules');
4783
+ if (!nodeModules || !nodeModules.children) return;
4784
+
4785
+ const packages = nodeModules.children
4786
+ .filter(pkg => pkg.size > 0)
4787
+ .sort((a, b) => b.size - a.size);
4788
+
4789
+ // Store full packages data for this container
4790
+ depListData.set(containerId, {
4791
+ packages: packages,
4792
+ isServer: isServer,
4793
+ totalSize: tree.totalSize,
4794
+ bundleInfo: bundleInfo,
4795
+ loadedCount: 0
4796
+ });
4797
+
4798
+ const ul = document.createElement('ul');
4799
+ ul.className = 'dep-list';
4800
+
4801
+ const initialCount = 10;
4802
+ for (let i = 0; i < Math.min(initialCount, packages.length); i++) {
4803
+ const li = createDepItem(packages[i], isServer, tree.totalSize, bundleInfo);
4804
+ ul.appendChild(li);
4805
+ }
4806
+
4807
+ depListData.get(containerId).loadedCount = Math.min(initialCount, packages.length);
4808
+
4809
+ container.appendChild(ul);
4810
+
4811
+ if (packages.length > initialCount) {
4812
+ const btn = document.createElement('button');
4813
+ btn.className = 'load-more';
4814
+ btn.textContent = 'load ' + (packages.length - initialCount) + ' more...';
4815
+ btn.onclick = function() {
4816
+ const data = depListData.get(containerId);
4817
+ const existingItems = new Set();
4818
+ ul.querySelectorAll('.dep-item').forEach(item => {
4819
+ existingItems.add(item.getAttribute('data-package-name'));
4820
+ });
4821
+
4822
+ let loaded = 0;
4823
+ for (let i = 0; i < packages.length; i++) {
4824
+ const pkg = packages[i];
4825
+ const nameLower = pkg.name.toLowerCase();
4826
+ if (!existingItems.has(nameLower)) {
4827
+ const li = createDepItem(pkg, isServer, tree.totalSize, bundleInfo);
4828
+ ul.appendChild(li);
4829
+ loaded++;
4830
+ }
4831
+ }
4832
+ data.loadedCount = packages.length;
4833
+ btn.remove();
4834
+ };
4835
+ container.appendChild(btn);
4836
+ }
4837
+ }
4838
+
4839
+ // Render matching items that aren't loaded yet
4840
+ function renderMatchingDeps(containerId, searchTerm) {
4841
+ const data = depListData.get(containerId);
4842
+ if (!data || !searchTerm) return;
4843
+
4844
+ const container = document.getElementById(containerId);
4845
+ if (!container) return;
4846
+
4847
+ const ul = container.querySelector('.dep-list');
4848
+ if (!ul) return;
4849
+
4850
+ const searchLower = searchTerm.toLowerCase();
4851
+ const existingItems = new Set();
4852
+ ul.querySelectorAll('.dep-item').forEach(item => {
4853
+ existingItems.add(item.getAttribute('data-package-name'));
4854
+ });
4855
+
4856
+ // Find matching packages that aren't rendered yet
4857
+ const matchingPackages = data.packages.filter((pkg) => {
4858
+ const nameLower = pkg.name.toLowerCase();
4859
+ const matches = nameLower.includes(searchLower);
4860
+ const notRendered = !existingItems.has(nameLower);
4861
+ return matches && notRendered;
4862
+ });
4863
+
4864
+ // Render matching items
4865
+ matchingPackages.forEach(pkg => {
4866
+ const li = createDepItem(pkg, data.isServer, data.totalSize, data.bundleInfo);
4867
+ ul.appendChild(li);
4868
+ });
4869
+ }
4870
+
4871
+ // Initial render
4872
+ const browserInfo = ${JSON.stringify(browserInfo)};
4873
+ const serverInfo = ${JSON.stringify(mainInfo)};
4874
+
4875
+ if (browserDepTree) {
4876
+ populateDepList(browserDepTree, 'browser-deps-list', false, browserInfo);
4877
+ renderDepTreemap(browserDepTree, "#browser-treemap");
4878
+ }
4879
+ if (serverDepTree) {
4880
+ populateDepList(serverDepTree, 'server-deps-list', true, serverInfo);
4881
+ renderDepTreemap(serverDepTree, "#server-treemap");
4882
+ }
4883
+
4884
+ window.addEventListener("resize", () => {
4885
+ if (currentSearchTerm) {
4886
+ filterTreemap(currentSearchTerm);
4887
+ } else {
4888
+ if (browserDepTree) renderDepTreemap(browserDepTree, "#browser-treemap");
4889
+ if (serverDepTree) renderDepTreemap(serverDepTree, "#server-treemap");
4890
+ }
4891
+ });
4892
+
4893
+ // Apply initial search if provided
4894
+ const initialSearchTerm = ${JSON.stringify(initialSearch || "")};
4895
+ if (initialSearchTerm && searchInput) {
4896
+ searchInput.value = initialSearchTerm;
4897
+ filterTreemap(initialSearchTerm);
4898
+ updateClearButton();
4899
+ }
4900
+ </script>
4901
+ ` : ""}
4902
+
4903
+ <div class="footer">
4904
+ <div class="search-stats" id="search-stats"></div>
4905
+ <div class="note">
4906
+ <strong>Browser</strong> bundles are shipped to users. <strong>Server</strong> binaries run on your server only.
4907
+ </div>
4908
+ ${mainInfo ? `
4909
+ <div class="note" style="margin-top: 0.5rem;">
4910
+ <strong>Final Server Binary</strong> is a standalone executable that includes the Bun runtime, your application code, and all dependencies. This allows it to run without requiring Node.js or Bun to be installed on the deployment server.
4911
+ </div>
4912
+ ` : ""}
4913
+ </div>
4914
+ </div>
4915
+ </body>
4916
+ </html>`;
4917
+ return html2;
4918
+ }
4919
+ async function printConsoleReport(stats, depTree, mainInfo, browserDepTree, browserInfo, fullOutput = false) {
4920
+ console.log(colorizeLogPrefixAnsi(`[reroute/analyze] Bundle Analysis Report
4921
+ `));
4922
+ if (mainInfo || browserInfo) {
4923
+ console.log(`\x1B[90mNote: Browser bundles are shipped to users. Server binaries run on your server only.\x1B[0m
4924
+ `);
4925
+ }
4926
+ if (browserInfo) {
4927
+ console.log(`\x1B[1m\x1B[36m${String.fromCharCode(9473).repeat(1)}\x1B[0m`);
4928
+ console.log(`\x1B[1m\x1B[36mBrowser Bundle: ${browserInfo.name}\x1B[0m \x1B[32m(shipped to browser)\x1B[0m`);
4929
+ console.log(`\x1B[1m\x1B[36m${String.fromCharCode(9473).repeat(1)}\x1B[0m
4930
+ `);
4931
+ const browserTable = new Table({
4932
+ head: ["Metric", "Value"],
4933
+ colWidths: [25, 35],
4934
+ style: {
4935
+ head: ["cyan", "bold"],
4936
+ border: ["gray"]
4937
+ },
4938
+ chars: ASCII_TABLE_CHARS
4939
+ });
4940
+ browserTable.push(["Raw Size", formatBytes(browserInfo.size)], ["Gzipped", formatBytes(browserInfo.gzippedSize)], [
4941
+ "Compression Ratio",
4942
+ `${((1 - browserInfo.gzippedSize / browserInfo.size) * 100).toFixed(1)}%`
4943
+ ]);
4944
+ console.log(browserTable.toString());
4945
+ console.log("");
4946
+ const gzipKB = browserInfo.gzippedSize / 1024;
4947
+ const time3G = gzipKB / 204.8;
4948
+ const time4G = gzipKB / 1152;
4949
+ const time5G = gzipKB / 6400;
4950
+ const timeWiFi = gzipKB / 12800;
4951
+ const transferTable = new Table({
4952
+ head: ["Network", "Throughput", "Transfer Time"],
4953
+ colWidths: [15, 15, 20],
4954
+ style: {
4955
+ head: ["cyan", "bold"],
4956
+ border: ["gray"]
4957
+ },
4958
+ chars: ASCII_TABLE_CHARS
4959
+ });
4960
+ transferTable.push([
4961
+ "3G Fast",
4962
+ "1.6 Mbps",
4963
+ time3G >= 1 ? `~${time3G.toFixed(2)}s` : `~${(time3G * 1000).toFixed(0)}ms`
4964
+ ], [
4965
+ "4G",
4966
+ "9 Mbps",
4967
+ time4G >= 1 ? `~${time4G.toFixed(2)}s` : `~${(time4G * 1000).toFixed(0)}ms`
4968
+ ], [
4969
+ "5G",
4970
+ "50 Mbps",
4971
+ time5G >= 1 ? `~${time5G.toFixed(2)}s` : `~${(time5G * 1000).toFixed(0)}ms`
4972
+ ], [
4973
+ "Wi-Fi 100",
4974
+ "100 Mbps",
4975
+ timeWiFi >= 1 ? `~${timeWiFi.toFixed(2)}s` : `~${(timeWiFi * 1000).toFixed(0)}ms`
4976
+ ]);
4977
+ console.log(transferTable.toString());
4978
+ console.log(`\x1B[90m Note: Estimates use gzipped size, ignore latency/HTTP overhead\x1B[0m
4979
+ `);
4980
+ }
4981
+ if (browserDepTree) {
4982
+ const nodeModules = browserDepTree.root.children?.find((c) => c.name === "node_modules");
4983
+ const src2 = browserDepTree.root.children?.find((c) => c.name === "src");
4984
+ if (nodeModules || src2) {
4985
+ console.log(`\x1B[1m\x1B[36m${String.fromCharCode(9473).repeat(1)}\x1B[0m`);
4986
+ console.log("\x1B[1m\x1B[36mBrowser Dependency Breakdown\x1B[0m \x1B[32m(shipped to browser)\x1B[0m");
4987
+ console.log(`\x1B[1m\x1B[36m${String.fromCharCode(9473).repeat(1)}\x1B[0m
4988
+ `);
4989
+ if (nodeModules && nodeModules.size > 0) {
4990
+ const nmPercent = (nodeModules.size / browserDepTree.totalSize * 100).toFixed(1);
4991
+ console.log(`\x1B[1mnode_modules: ${formatBytes(nodeModules.size)} (${nmPercent}%)\x1B[0m
4992
+ `);
4993
+ const packages = (nodeModules.children || []).filter((pkg) => pkg.size > 0).sort((a, b) => b.size - a.size);
4994
+ if (packages.length > 0) {
4995
+ const pkgTable = new Table({
4996
+ head: ["Package / File", "Raw Size", "Gzipped", "% of Total"],
4997
+ colWidths: [40, 15, 15, 12],
4998
+ style: {
4999
+ head: ["green", "bold"],
5000
+ border: ["gray"]
5001
+ },
5002
+ chars: ASCII_TABLE_CHARS
5003
+ });
5004
+ const packagesToShow = fullOutput ? packages : packages.filter((pkg) => pkg.size / browserDepTree.totalSize >= 0.01);
5005
+ for (const pkg of packagesToShow) {
5006
+ const percentage = (pkg.size / browserDepTree.totalSize * 100).toFixed(1);
5007
+ const pkgEstimatedGz = Math.round(pkg.size * (browserInfo?.gzippedSize || 0) / (browserInfo?.size || 1));
5008
+ pkgTable.push([
5009
+ `\x1B[1m${pkg.name}\x1B[0m`,
5010
+ formatBytes(pkg.size),
5011
+ formatBytes(pkgEstimatedGz),
5012
+ `${percentage}%`
5013
+ ]);
5014
+ const files = (pkg.children || []).filter((f) => f.size > 0).sort((a, b) => b.size - a.size);
5015
+ const filesToShow = fullOutput ? files : files.slice(0, 10);
5016
+ for (const file of filesToShow) {
5017
+ const filePercent = (file.size / browserDepTree.totalSize * 100).toFixed(1);
5018
+ const estimatedGz = Math.round(file.size * (browserInfo?.gzippedSize || 0) / (browserInfo?.size || 1));
5019
+ pkgTable.push([
5020
+ ` ${String.fromCharCode(8627)} ${file.name}`,
5021
+ formatBytes(file.size),
5022
+ formatBytes(estimatedGz),
5023
+ `${filePercent}%`
5024
+ ]);
5025
+ }
5026
+ if (!fullOutput && files.length > 10) {
5027
+ pkgTable.push([
5028
+ ` ... ${files.length - 10} more files`,
5029
+ "",
5030
+ "",
5031
+ ""
5032
+ ]);
5033
+ }
5034
+ }
5035
+ console.log(pkgTable.toString());
5036
+ console.log("");
5037
+ }
5038
+ }
5039
+ if (src2 && src2.size > 0) {
5040
+ const srcPercent = (src2.size / browserDepTree.totalSize * 100).toFixed(1);
5041
+ console.log(`\x1B[1msrc: ${formatBytes(src2.size)} (${srcPercent}%)\x1B[0m
5042
+ `);
5043
+ }
5044
+ }
5045
+ }
5046
+ const browserChunks = Object.entries(stats.byCollection).filter(([collection]) => collection !== "binaries");
5047
+ if (browserChunks.length > 0) {
5048
+ for (const [collection, bundles] of browserChunks) {
5049
+ const collectionTotal = bundles.reduce((sum, b) => sum + b.size, 0);
5050
+ const collectionGzipped = bundles.reduce((sum, b) => sum + b.gzippedSize, 0);
5051
+ console.log(`\x1B[1mBrowser Chunks: ${collection}\x1B[0m \x1B[32m(shipped to browser)\x1B[0m`);
5052
+ console.log(` Total: ${formatBytes(collectionTotal)} (${formatBytes(collectionGzipped)} gzipped)
5053
+ `);
5054
+ const sorted = bundles.sort((a, b) => b.size - a.size);
5055
+ const bundleTable = new Table({
5056
+ head: ["Bundle", "Size", "Gzipped", "% of Total"],
5057
+ colWidths: [40, 12, 12, 12],
5058
+ style: {
5059
+ head: ["green", "bold"],
5060
+ border: ["gray"]
5061
+ },
5062
+ chars: ASCII_TABLE_CHARS
5063
+ });
5064
+ for (const bundle of sorted) {
5065
+ const percentage = (bundle.size / stats.totalSize * 100).toFixed(1);
5066
+ bundleTable.push([
5067
+ bundle.name,
5068
+ formatBytes(bundle.size),
5069
+ formatBytes(bundle.gzippedSize),
5070
+ `${percentage}%`
5071
+ ]);
5072
+ }
5073
+ console.log(bundleTable.toString());
5074
+ console.log("");
5075
+ }
5076
+ }
5077
+ if (mainInfo) {
5078
+ console.log(`\x1B[1m\x1B[36m${String.fromCharCode(9473).repeat(1)}\x1B[0m`);
5079
+ console.log(`\x1B[1m\x1B[36mServer Binary: ${mainInfo.name}\x1B[0m \x1B[90m(not shipped to browser)\x1B[0m`);
5080
+ console.log(`\x1B[1m\x1B[36m${String.fromCharCode(9473).repeat(1)}\x1B[0m
5081
+ `);
5082
+ const mainTable = new Table({
5083
+ head: ["Metric", "Value"],
5084
+ colWidths: [25, 35],
5085
+ style: {
5086
+ head: ["cyan", "bold"],
5087
+ border: ["gray"]
5088
+ },
5089
+ chars: ASCII_TABLE_CHARS
5090
+ });
5091
+ mainTable.push(["Binary Size", formatBytes(mainInfo.size)]);
5092
+ console.log(mainTable.toString());
5093
+ console.log("");
5094
+ }
5095
+ if (depTree) {
5096
+ const nodeModules = depTree.root.children?.find((c) => c.name === "node_modules");
5097
+ const src2 = depTree.root.children?.find((c) => c.name === "src");
5098
+ if (nodeModules || src2) {
5099
+ console.log(`\x1B[1m\x1B[36m${String.fromCharCode(9473).repeat(1)}\x1B[0m`);
5100
+ console.log("\x1B[1m\x1B[36mServer Dependency Breakdown\x1B[0m \x1B[90m(server-side only)\x1B[0m");
5101
+ console.log(`\x1B[1m\x1B[36m${String.fromCharCode(9473).repeat(1)}\x1B[0m
5102
+ `);
5103
+ if (nodeModules && nodeModules.size > 0) {
5104
+ const nmPercent = (nodeModules.size / depTree.totalSize * 100).toFixed(1);
5105
+ console.log(`\x1B[1mnode_modules: ${formatBytes(nodeModules.size)} (${nmPercent}%)\x1B[0m
5106
+ `);
5107
+ const packages = (nodeModules.children || []).filter((pkg) => pkg.size > 0).sort((a, b) => b.size - a.size);
5108
+ if (packages.length > 0) {
5109
+ const pkgTable = new Table({
5110
+ head: ["Package / File", "Size", "% of Total"],
5111
+ colWidths: [50, 20, 12],
5112
+ style: {
5113
+ head: ["red", "bold"],
5114
+ border: ["gray"]
5115
+ },
5116
+ chars: ASCII_TABLE_CHARS
5117
+ });
5118
+ const packagesToShow = fullOutput ? packages : packages.filter((pkg) => pkg.size / depTree.totalSize >= 0.01);
5119
+ for (const pkg of packagesToShow) {
5120
+ const percentage = (pkg.size / depTree.totalSize * 100).toFixed(1);
5121
+ pkgTable.push([
5122
+ `\x1B[1m${pkg.name}\x1B[0m`,
5123
+ formatBytes(pkg.size),
5124
+ `${percentage}%`
5125
+ ]);
5126
+ const files = (pkg.children || []).filter((f) => f.size > 0).sort((a, b) => b.size - a.size);
5127
+ const filesToShow = fullOutput ? files : files.slice(0, 10);
5128
+ for (const file of filesToShow) {
5129
+ const filePercent = (file.size / depTree.totalSize * 100).toFixed(1);
5130
+ pkgTable.push([
5131
+ ` ${String.fromCharCode(8627)} ${file.name}`,
5132
+ formatBytes(file.size),
5133
+ `${filePercent}%`
5134
+ ]);
5135
+ }
5136
+ if (!fullOutput && files.length > 10) {
5137
+ pkgTable.push([` ... ${files.length - 10} more files`, "", ""]);
5138
+ }
5139
+ }
5140
+ console.log(pkgTable.toString());
5141
+ console.log("");
5142
+ }
5143
+ }
5144
+ if (src2 && src2.size > 0) {
5145
+ const srcPercent = (src2.size / depTree.totalSize * 100).toFixed(1);
5146
+ console.log(`\x1B[1msrc: ${formatBytes(src2.size)} (${srcPercent}%)\x1B[0m
5147
+ `);
5148
+ const srcItems = (src2.children || []).filter((item) => item.size > 0).sort((a, b) => b.size - a.size);
5149
+ if (srcItems.length > 0) {
5150
+ const srcTable = new Table({
5151
+ head: ["Source", "Size", "% of Total"],
5152
+ colWidths: [45, 12, 12],
5153
+ style: {
5154
+ head: ["green", "bold"],
5155
+ border: ["gray"]
5156
+ },
5157
+ chars: ASCII_TABLE_CHARS
5158
+ });
5159
+ for (const item of srcItems) {
5160
+ const percentage = (item.size / depTree.totalSize * 100).toFixed(1);
5161
+ srcTable.push([
5162
+ item.name,
5163
+ formatBytes(item.size),
5164
+ `${percentage}%`
5165
+ ]);
5166
+ }
5167
+ console.log(srcTable.toString());
5168
+ console.log("");
5169
+ }
5170
+ }
5171
+ }
5172
+ }
5173
+ console.log(`\x1B[1m\x1B[36m${String.fromCharCode(9473).repeat(1)}\x1B[0m`);
5174
+ console.log("\x1B[1m\x1B[36mSummary\x1B[0m");
5175
+ console.log(`\x1B[1m\x1B[36m${String.fromCharCode(9473).repeat(1)}\x1B[0m
5176
+ `);
5177
+ const summaryTable = new Table({
5178
+ head: ["Metric", "Value"],
5179
+ colWidths: [25, 35],
5180
+ style: {
5181
+ head: ["cyan", "bold"],
5182
+ border: ["gray"]
5183
+ },
5184
+ chars: ASCII_TABLE_CHARS
5185
+ });
5186
+ const browserNodeModules = browserDepTree?.root.children?.find((c) => c.name === "node_modules");
5187
+ const browserDepCount = browserNodeModules?.children?.length || 0;
5188
+ const serverNodeModules = depTree?.root.children?.find((c) => c.name === "node_modules");
5189
+ const serverDepCount = serverNodeModules?.children?.length || 0;
5190
+ let actualServerBinarySize = null;
5191
+ let actualServerBinaryGzipped = null;
5192
+ try {
5193
+ const distDir = join12(process.cwd(), "dist");
5194
+ if (existsSync9(distDir)) {
5195
+ const files = await readdir6(distDir);
5196
+ for (const file of files) {
5197
+ const filePath = join12(distDir, file);
5198
+ const stats2 = statSync(filePath);
5199
+ if (stats2.isFile() && !file.includes(".")) {
5200
+ actualServerBinarySize = stats2.size;
5201
+ const content2 = await readFile2(filePath);
5202
+ actualServerBinaryGzipped = Bun.gzipSync(content2).length;
5203
+ break;
5204
+ }
5205
+ }
5206
+ }
5207
+ } catch {}
5208
+ const browserBundleDisplay = browserInfo ? `${formatBytes(browserInfo.size)} (${formatBytes(browserInfo.gzippedSize)} gz)` : "N/A";
5209
+ const serverBinaryDisplay = actualServerBinarySize ? `${formatBytes(actualServerBinarySize)}${actualServerBinaryGzipped ? ` (${formatBytes(actualServerBinaryGzipped)} gz)` : ""}` : mainInfo ? formatBytes(mainInfo.size) : "N/A";
5210
+ summaryTable.push(["Browser Dependencies", browserDepCount.toString()], ["Browser Bundle", browserBundleDisplay], ["Server Dependencies", serverDepCount.toString()], ["Final Server Binary", serverBinaryDisplay]);
5211
+ console.log(summaryTable.toString());
5212
+ console.log("");
5213
+ if (actualServerBinarySize) {
5214
+ console.log("\x1B[90mNote: Final Server Binary is a standalone executable that includes the Bun runtime,\x1B[0m");
5215
+ console.log("\x1B[90m your application code, and all dependencies. This allows it to run without\x1B[0m");
5216
+ console.log("\x1B[90m requiring Node.js or Bun to be installed on the deployment server.\x1B[0m");
5217
+ console.log("");
5218
+ }
5219
+ if (stats.bundles.length === 0) {
5220
+ console.log(colorizeLogPrefixAnsi("[reroute/analyze] No bundles found. Run `reroute build` first. For production builds, use NODE_ENV=production or --prod flag.\n"));
5221
+ }
5222
+ }
5223
+ async function analyze(args) {
5224
+ try {
5225
+ const cwd = process.cwd();
5226
+ const outputHtml = args.includes("--html");
5227
+ const openBrowser = args.includes("--open") || args.includes("-o");
5228
+ const isProd = isProductionMode(args);
5229
+ const searchIndex = args.indexOf("--search");
5230
+ const initialSearch = searchIndex !== -1 && args[searchIndex + 1] ? args[searchIndex + 1] : "";
5231
+ const rerouteIndexPath = join12(cwd, ".reroute", "index.ts");
5232
+ if (!existsSync9(rerouteIndexPath)) {
5233
+ console.log(colorizeLogPrefixAnsi(`[reroute/analyze] .reroute artifacts not found, running build...
5234
+ `));
5235
+ const { spawn: spawn5 } = await import("node:child_process");
5236
+ const { getRerouteCommand: getRerouteCommand5 } = await Promise.resolve().then(() => (init_command(), exports_command));
5237
+ const rerouteCmd = getRerouteCommand5();
5238
+ const useShell = rerouteCmd.includes(" ");
5239
+ const buildCommand = useShell ? `${rerouteCmd} build` : rerouteCmd;
5240
+ const buildArgs = useShell ? [] : ["build"];
5241
+ await new Promise((resolve, reject) => {
5242
+ const buildProcess = spawn5(buildCommand, buildArgs, {
5243
+ stdio: "inherit",
5244
+ shell: useShell
5245
+ });
5246
+ buildProcess.on("exit", (code) => {
5247
+ if (code !== null && code !== 0) {
5248
+ reject(new Error(`Build exited with code ${code}`));
5249
+ } else {
5250
+ resolve();
5251
+ }
5252
+ });
5253
+ buildProcess.on("error", (error) => {
5254
+ reject(error);
5255
+ });
5256
+ });
5257
+ console.log(colorizeLogPrefixAnsi(`[reroute/analyze] Build complete
5258
+ `));
5259
+ }
5260
+ console.log(colorizeLogPrefixAnsi(`[reroute/analyze] Scanning for bundles...
5261
+ `));
5262
+ const bundles = await findBundles(cwd);
5263
+ const stats = calculateStats(bundles);
5264
+ console.log(colorizeLogPrefixAnsi(`[reroute/analyze] Building browser entrypoint for analysis...
5265
+ `));
5266
+ const browserEntry = await findBrowserEntry(cwd);
5267
+ let browserDepTree = null;
5268
+ let browserInfo = null;
5269
+ if (browserEntry) {
5270
+ console.log(colorizeLogPrefixAnsi(`[reroute/analyze] Analyzing browser bundle: ${relative(cwd, browserEntry)}
5271
+ `));
5272
+ browserDepTree = await buildDependencyTree(browserEntry, cwd);
5273
+ if (!browserDepTree) {
5274
+ console.log(colorizeLogPrefixAnsi(`[reroute/analyze] Could not build dependency tree from browser bundle. Sourcemap may be missing or invalid.
5275
+ `));
5276
+ }
5277
+ try {
5278
+ const browserStat = await stat5(browserEntry);
5279
+ const browserContent = await readFile2(browserEntry);
5280
+ const browserGzipped = Bun.gzipSync(browserContent).length;
5281
+ browserInfo = {
5282
+ name: basename3(browserEntry),
5283
+ size: browserStat.size,
5284
+ gzippedSize: browserGzipped
5285
+ };
5286
+ } catch {}
5287
+ } else {
5288
+ console.log(colorizeLogPrefixAnsi(`[reroute/analyze] Could not build browser entrypoint for analysis. Browser dependency breakdown will not be available.
5289
+ `));
5290
+ }
5291
+ let entry = null;
5292
+ let depTree = null;
5293
+ let mainInfo = null;
5294
+ const srcEntry = join12(cwd, "src", "index.ts");
5295
+ if (existsSync9(srcEntry)) {
5296
+ console.log(colorizeLogPrefixAnsi(`[reroute/analyze] Building server binary for analysis...
5297
+ `));
5298
+ const { mkdir: mkdir4 } = await import("node:fs/promises");
5299
+ const serverAnalysisDir = join12(cwd, ".reroute", "server");
5300
+ await mkdir4(serverAnalysisDir, { recursive: true });
5301
+ const result = await Bun.build({
5302
+ entrypoints: [srcEntry],
5303
+ target: "bun",
5304
+ minify: isProd,
5305
+ sourcemap: "linked",
5306
+ external: [],
5307
+ splitting: false
5308
+ });
5309
+ if (result.success && result.outputs.length > 0) {
5310
+ const output = result.outputs[0];
5311
+ const code = await output.text();
5312
+ const analysisEntry = join12(serverAnalysisDir, "index.js");
5313
+ await writeFile4(analysisEntry, code, "utf-8");
5314
+ const mapOutput = result.outputs.find((o) => o.path.endsWith(".map"));
5315
+ if (mapOutput) {
5316
+ const mapContent = await mapOutput.text();
5317
+ await writeFile4(`${analysisEntry}.map`, mapContent, "utf-8");
5318
+ }
5319
+ entry = analysisEntry;
5320
+ }
5321
+ }
5322
+ if (entry) {
5323
+ console.log(colorizeLogPrefixAnsi(`[reroute/analyze] Building dependency graph for ${relative(cwd, entry)}
5324
+ `));
5325
+ depTree = await buildDependencyTree(entry, cwd);
5326
+ console.log(colorizeLogPrefixAnsi(`[reroute/analyze] Checking installed package sizes...
5327
+ `));
5328
+ const installedSizes = await getInstalledPackageSizes(cwd);
5329
+ if (depTree) {
5330
+ const packageJsonPath = join12(cwd, "package.json");
5331
+ if (existsSync9(packageJsonPath)) {
5332
+ try {
5333
+ const packageJson = JSON.parse(await readFile2(packageJsonPath, "utf-8"));
5334
+ const runtimeDeps = packageJson.dependencies || {};
5335
+ const nodeModulesNode = depTree.root.children?.find((c) => c.name === "node_modules");
5336
+ if (nodeModulesNode) {
5337
+ const trackedPkgs = new Set(nodeModulesNode.children?.map((c) => c.name) || []);
5338
+ for (const pkgName of Object.keys(runtimeDeps)) {
5339
+ if (!trackedPkgs.has(pkgName) && installedSizes.has(pkgName)) {
5340
+ nodeModulesNode.children?.push({
5341
+ name: pkgName,
5342
+ path: join12(cwd, "node_modules", pkgName),
5343
+ size: installedSizes.get(pkgName) || 0
5344
+ });
5345
+ }
5346
+ }
5347
+ nodeModulesNode.size = nodeModulesNode.children?.reduce((sum, c) => {
5348
+ if (c.children && c.children.length > 0) {
5349
+ c.size = c.children.reduce((s, child) => s + child.size, 0);
5350
+ }
5351
+ return sum + c.size;
5352
+ }, 0) || 0;
5353
+ depTree.root.size = depTree.root.children?.reduce((sum, c) => sum + c.size, 0) || 0;
5354
+ depTree.totalSize = depTree.root.size;
5355
+ }
5356
+ } catch (_e) {}
5357
+ }
5358
+ }
5359
+ try {
5360
+ const entryStat = await stat5(entry);
5361
+ const entryContent = await readFile2(entry);
5362
+ const entryGzipped = Bun.gzipSync(entryContent).length;
5363
+ mainInfo = {
5364
+ name: basename3(entry),
5365
+ size: entryStat.size,
5366
+ gzippedSize: entryGzipped
5367
+ };
5368
+ } catch {}
5369
+ }
5370
+ const fullOutput = args.includes("--full");
5371
+ await printConsoleReport(stats, depTree, mainInfo, browserDepTree, browserInfo, fullOutput);
5372
+ if (outputHtml || openBrowser) {
5373
+ const htmlPath = join12(cwd, ".reroute", "analysis.html");
5374
+ const html2 = await generateHTMLReport(stats, depTree, mainInfo, browserDepTree, browserInfo, initialSearch);
5375
+ const { mkdir: mkdir4 } = await import("node:fs/promises");
5376
+ await mkdir4(join12(cwd, ".reroute"), { recursive: true });
5377
+ await writeFile4(htmlPath, html2, "utf-8");
5378
+ console.log(colorizeLogPrefixAnsi(`[reroute/analyze] HTML report generated: ${relative(cwd, htmlPath)}
5379
+ `));
5380
+ if (openBrowser) {
5381
+ try {
5382
+ const { spawn: spawn5 } = await import("node:child_process");
5383
+ const platform = process.platform;
5384
+ let command2;
5385
+ let commandArgs;
5386
+ if (platform === "darwin") {
5387
+ command2 = "open";
5388
+ commandArgs = [htmlPath];
5389
+ } else if (platform === "win32") {
5390
+ command2 = "cmd";
5391
+ commandArgs = ["/c", "start", "", htmlPath];
5392
+ } else {
5393
+ command2 = "xdg-open";
5394
+ commandArgs = [htmlPath];
5395
+ }
5396
+ spawn5(command2, commandArgs, { detached: true, stdio: "ignore" });
5397
+ console.log(colorizeLogPrefixAnsi(`[reroute/analyze] Opened report in browser
5398
+ `));
5399
+ } catch {
5400
+ console.warn(colorizeLogPrefixAnsi(`[reroute/analyze] Could not open browser automatically. Please open ${htmlPath} manually.
5401
+ `));
5402
+ }
5403
+ }
5404
+ }
5405
+ } catch (error) {
5406
+ console.error(colorizeLogPrefixAnsi(`[reroute/analyze] Error: ${String(error)}
5407
+ `));
5408
+ throw error;
5409
+ }
5410
+ }
5411
+ var ASCII_TABLE_CHARS;
5412
+ var init_analyze = __esm(() => {
5413
+ init_log();
5414
+ ASCII_TABLE_CHARS = {
5415
+ top: "-",
5416
+ "top-mid": "+",
5417
+ "top-left": "+",
5418
+ "top-right": "+",
5419
+ bottom: "-",
5420
+ "bottom-mid": "+",
5421
+ "bottom-left": "+",
5422
+ "bottom-right": "+",
5423
+ left: "|",
5424
+ "left-mid": "+",
5425
+ mid: "-",
5426
+ "mid-mid": "+",
5427
+ right: "|",
5428
+ "right-mid": "+",
5429
+ middle: "|"
5430
+ };
5431
+ });
5432
+
5433
+ // packages/cli/src/cli.ts
5434
+ import path3 from "node:path";
5435
+ var commands = {
5436
+ init: () => Promise.resolve().then(() => (init_init(), exports_init)),
5437
+ gen: () => Promise.resolve().then(() => (init_gen(), exports_gen)),
5438
+ boot: () => Promise.resolve().then(() => (init_boot(), exports_boot)),
5439
+ dev: () => Promise.resolve().then(() => (init_dev(), exports_dev)),
5440
+ start: () => Promise.resolve().then(() => (init_start(), exports_start)),
5441
+ build: () => Promise.resolve().then(() => (init_build(), exports_build)),
5442
+ analyze: () => Promise.resolve().then(() => (init_analyze(), exports_analyze))
5443
+ };
5444
+ async function main() {
5445
+ const args = process.argv.slice(2);
5446
+ if (args[0] === "--version" || args[0] === "-v") {
5447
+ const version = await getVersionString2();
5448
+ console.log(version);
5449
+ process.exit(0);
5450
+ }
5451
+ if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
5452
+ await printHelp2();
5453
+ process.exit(0);
5454
+ }
5455
+ const command2 = args[0];
5456
+ if (!commands[command2]) {
5457
+ console.error(`Unknown command: ${command2}`);
5458
+ console.error("");
5459
+ await printHelp2();
5460
+ process.exit(1);
5461
+ }
5462
+ try {
5463
+ const commandArgs = args.slice(1);
5464
+ if (commandArgs.includes("--prod")) {
5465
+ process.env.NODE_ENV = "production";
5466
+ if (typeof Bun !== "undefined") {
5467
+ Bun.env.NODE_ENV = "production";
5468
+ }
5469
+ }
5470
+ const module = await commands[command2]();
5471
+ await module.default(commandArgs);
5472
+ } catch (error) {
5473
+ console.error("Error executing command:", error);
5474
+ process.exit(1);
5475
+ }
5476
+ }
5477
+ async function printHelp2() {
5478
+ console.log("");
5479
+ console.log(await getVersionString2());
5480
+ console.log("");
5481
+ console.log("");
5482
+ console.log("Usage:");
5483
+ console.log("");
5484
+ console.log(" reroute <command> [options]");
5485
+ console.log("");
5486
+ console.log("");
5487
+ console.log("Commands:");
5488
+ console.log("");
5489
+ console.log(" init Scaffold a new Reroute project");
5490
+ console.log(" boot Initialize .reroute directory with stub files");
5491
+ console.log(" gen Generate content registry and static assets");
5492
+ console.log(" dev Start development environment");
5493
+ console.log(" start Run production-like server locally");
5494
+ console.log(" build Build production binary");
5495
+ console.log(" analyze Analyze bundle sizes and generate report");
5496
+ console.log("");
5497
+ console.log("");
5498
+ console.log("Options:");
5499
+ console.log("");
5500
+ console.log(" -h, --help Show help");
5501
+ console.log(" -v, --version Show version");
5502
+ console.log(" --prod Enable production mode (same as NODE_ENV=production)");
5503
+ console.log("");
5504
+ console.log("");
5505
+ console.log("Examples:");
5506
+ console.log("");
5507
+ console.log(" reroute init my-app");
5508
+ console.log(" reroute boot");
5509
+ console.log("");
5510
+ console.log(" reroute gen");
5511
+ console.log(" reroute gen -w");
5512
+ console.log(" reroute gen --watch");
5513
+ console.log("");
5514
+ console.log(" reroute dev");
5515
+ console.log(" reroute dev -s");
5516
+ console.log(" reroute dev --single");
5517
+ console.log("");
5518
+ console.log(" reroute start");
5519
+ console.log("");
5520
+ console.log(" reroute build");
5521
+ console.log(" reroute build --prod");
5522
+ console.log(" reroute build -o ./dist/myapp");
5523
+ console.log(" reroute build --outfile ./dist/myapp");
5524
+ console.log("");
5525
+ console.log(" reroute analyze");
5526
+ console.log(" reroute analyze --prod");
5527
+ console.log(" reroute analyze --full");
5528
+ console.log(" reroute analyze --html");
5529
+ console.log(" reroute analyze --open");
2161
5530
  }
2162
5531
  async function getVersionString2() {
2163
5532
  const version = await getVersion2();
@@ -2166,12 +5535,12 @@ async function getVersionString2() {
2166
5535
  }
2167
5536
  async function getVersion2() {
2168
5537
  if (true) {
2169
- return "0.7.2";
5538
+ return "0.8.0";
2170
5539
  }
2171
5540
  const possiblePaths = [
2172
- path2.join(import.meta.dir, "../../../package.json"),
2173
- path2.join(import.meta.dir, "../../package.json"),
2174
- path2.join(import.meta.dir, "../package.json")
5541
+ path3.join(import.meta.dir, "../../../package.json"),
5542
+ path3.join(import.meta.dir, "../../package.json"),
5543
+ path3.join(import.meta.dir, "../package.json")
2175
5544
  ];
2176
5545
  for (const pkgPath of possiblePaths) {
2177
5546
  try {
@@ -2183,10 +5552,10 @@ async function getVersion2() {
2183
5552
  }
2184
5553
  async function getCommit2() {
2185
5554
  if (true) {
2186
- return "993e6df";
5555
+ return "b31f8e3";
2187
5556
  }
2188
5557
  return "dev";
2189
5558
  }
2190
5559
  main();
2191
5560
 
2192
- //# debugId=27B92FF191E5405264756E2164756E21
5561
+ //# debugId=8F50A077EDCF534864756E2164756E21