@junejs/server 0.1.0-dev.4 → 0.1.0-dev.7

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 (105) hide show
  1. package/dist/adapter.d.ts +56 -0
  2. package/dist/adapter.js +211 -0
  3. package/dist/agent-discover.d.ts +5 -0
  4. package/dist/agent-discover.js +75 -0
  5. package/dist/agent-durable.d.ts +72 -0
  6. package/dist/agent-durable.js +141 -0
  7. package/dist/agent-native.d.ts +40 -0
  8. package/dist/agent-native.js +179 -0
  9. package/dist/app.d.ts +15 -0
  10. package/dist/app.js +252 -0
  11. package/dist/blob.d.ts +23 -0
  12. package/dist/blob.js +78 -0
  13. package/dist/build.d.ts +40 -0
  14. package/dist/build.js +582 -0
  15. package/dist/client-bundle.d.ts +15 -0
  16. package/dist/client-bundle.js +91 -0
  17. package/dist/config-loader.d.ts +9 -0
  18. package/dist/config-loader.js +25 -0
  19. package/dist/content.d.ts +39 -0
  20. package/dist/content.js +201 -0
  21. package/dist/css-modules-ambient.d.ts +10 -0
  22. package/dist/css-modules.d.ts +24 -0
  23. package/dist/css-modules.js +127 -0
  24. package/dist/css.d.ts +14 -0
  25. package/dist/css.js +114 -0
  26. package/dist/d1-BkdUg9kg.d.ts +24 -0
  27. package/dist/d1-migrate.d.ts +31 -0
  28. package/dist/d1-migrate.js +156 -0
  29. package/dist/d1.d.ts +2 -0
  30. package/dist/d1.js +32 -0
  31. package/dist/db.d.ts +11 -0
  32. package/dist/db.js +20 -0
  33. package/dist/deploy.d.ts +25 -0
  34. package/dist/deploy.js +158 -0
  35. package/dist/dev-reload.d.ts +5 -0
  36. package/dist/dev-reload.js +123 -0
  37. package/dist/dev.d.ts +13 -0
  38. package/dist/dev.js +55 -0
  39. package/dist/host.d.ts +25 -0
  40. package/dist/host.js +107 -0
  41. package/dist/index.d.ts +26 -0
  42. package/dist/index.js +26 -0
  43. package/dist/instrumentation.d.ts +4 -0
  44. package/dist/instrumentation.js +15 -0
  45. package/dist/island-registry.d.ts +8 -0
  46. package/dist/island-registry.js +117 -0
  47. package/dist/kv.d.ts +8 -0
  48. package/dist/kv.js +35 -0
  49. package/dist/migrate-BbWt-PuB.d.ts +32 -0
  50. package/dist/migrate.d.ts +2 -0
  51. package/dist/migrate.js +115 -0
  52. package/dist/mysql-driver-BsrWVqbp.d.ts +12 -0
  53. package/dist/mysql-driver.d.ts +2 -0
  54. package/dist/mysql-driver.js +52 -0
  55. package/dist/negotiate.d.ts +11 -0
  56. package/dist/negotiate.js +39 -0
  57. package/dist/pipeline-B2V6tKfO.d.ts +49 -0
  58. package/dist/pipeline.d.ts +2 -0
  59. package/dist/pipeline.js +235 -0
  60. package/dist/postgres-driver-DkkBFikA.d.ts +15 -0
  61. package/dist/postgres-driver.d.ts +2 -0
  62. package/dist/postgres-driver.js +50 -0
  63. package/dist/resources.d.ts +15 -0
  64. package/dist/resources.js +31 -0
  65. package/dist/router.d.ts +32 -0
  66. package/dist/router.js +183 -0
  67. package/dist/rsc-build.d.ts +17 -0
  68. package/dist/rsc-build.js +68 -0
  69. package/dist/rsc-bundle.d.ts +8 -0
  70. package/dist/rsc-bundle.js +39 -0
  71. package/dist/rsc-manifest.d.ts +8 -0
  72. package/dist/rsc-manifest.js +67 -0
  73. package/dist/rsc-runtime/flight-render-routes.d.ts +5 -0
  74. package/dist/rsc-runtime/flight-render-routes.js +26 -0
  75. package/dist/rsc-runtime/flight-render.d.ts +4 -0
  76. package/dist/rsc-runtime/flight-render.js +21 -0
  77. package/dist/rsc-runtime/flight-to-html.d.ts +4 -0
  78. package/dist/rsc-runtime/flight-to-html.js +32 -0
  79. package/dist/rsc-runtime/rsc-worker.d.ts +6 -0
  80. package/dist/rsc-runtime/rsc-worker.js +46 -0
  81. package/dist/rsc-runtime/rsdw-shims.d.ts +42 -0
  82. package/dist/segment.d.ts +14 -0
  83. package/dist/segment.js +33 -0
  84. package/dist/sqlite-driver.d.ts +25 -0
  85. package/dist/sqlite-driver.js +102 -0
  86. package/dist/static-files.d.ts +6 -0
  87. package/dist/static-files.js +56 -0
  88. package/dist/tsconfig-jsx.d.ts +15 -0
  89. package/dist/tsconfig-jsx.js +35 -0
  90. package/dist/turso-driver-C1wEigMZ.d.ts +22 -0
  91. package/dist/turso-driver.d.ts +2 -0
  92. package/dist/turso-driver.js +68 -0
  93. package/dist/worker.d.ts +55 -0
  94. package/dist/worker.js +211 -0
  95. package/package.json +115 -23
  96. package/src/adapter.ts +20 -7
  97. package/src/agent-discover.ts +5 -2
  98. package/src/agent-durable.ts +108 -4
  99. package/src/app.ts +74 -2
  100. package/src/build.ts +52 -3
  101. package/src/client-bundle.ts +3 -1
  102. package/src/index.ts +4 -0
  103. package/src/rsc-bundle.ts +3 -2
  104. package/src/static-files.ts +84 -0
  105. package/src/worker.ts +46 -29
package/dist/build.js ADDED
@@ -0,0 +1,582 @@
1
+ import { deno, staticSite, vercel, workers } from "./adapter.js";
2
+ import { findMiddlewareFile } from "./router.js";
3
+ import { resolveBoundary } from "./segment.js";
4
+ import { buildModuleCss, registerCssModules, rolldownCssModulesPlugin } from "./css-modules.js";
5
+ import { jsxTransform } from "./tsconfig-jsx.js";
6
+ import { CLIENT_SCRIPT_URL, bundleClientToFile, findClientEntry } from "./client-bundle.js";
7
+ import { generateIslandRegistry } from "./island-registry.js";
8
+ import { STYLES_URL, cssTargets, findGlobalCss, globalCssUsesTailwind, minifyCss, processCss } from "./css.js";
9
+ import { RESERVED_PREFIX } from "./static-files.js";
10
+ import { findJuneConfigPath, loadJuneConfig } from "./config-loader.js";
11
+ import { generateContentModule } from "./content.js";
12
+ import { createWorker } from "./worker.js";
13
+ import { buildRsc, findRscRoutes } from "./rsc-build.js";
14
+ import { existsSync, lstatSync } from "node:fs";
15
+ import { copyFile, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
16
+ import { basename, dirname, join, relative, resolve, sep } from "node:path";
17
+ import { pathToFileURL } from "node:url";
18
+ import { routeFromModule } from "@junejs/core/route";
19
+ import { resolveAgent, resolveClientRouter, resolveSpeculationRules } from "@junejs/core/config";
20
+ import { buildLinkHeader } from "@junejs/core/discovery";
21
+ import { localeHref } from "@junejs/core/i18n";
22
+ import { createHash } from "node:crypto";
23
+ import { execFileSync } from "node:child_process";
24
+ //#region src/build.ts
25
+ const PAGE_BASENAMES = /* @__PURE__ */ new Set(["page", "index"]);
26
+ const ROUTE_EXTS = [
27
+ ".tsx",
28
+ ".jsx",
29
+ ".ts",
30
+ ".js"
31
+ ];
32
+ const isRouteGroup = (name) => /^\(.+\)$/.test(name);
33
+ const isBunSpecifier = (id) => id === "bun" || id.startsWith("bun:");
34
+ function resolveDeployAdapter(deploy) {
35
+ if (deploy?.adapter) return deploy.adapter;
36
+ switch (deploy?.target) {
37
+ case "static": return staticSite();
38
+ case "vercel": return vercel();
39
+ case "deno": return deno();
40
+ default: return workers({
41
+ name: deploy?.name,
42
+ domain: deploy?.domain
43
+ });
44
+ }
45
+ }
46
+ function segmentFile(dir, base) {
47
+ return ROUTE_EXTS.map((e) => join(dir, `${base}${e}`)).find(existsSync);
48
+ }
49
+ async function scanRoutes(appDir, dir = appDir, layouts = [], out = [], loading) {
50
+ const ownLayout = segmentFile(dir, "layout");
51
+ const chain = ownLayout ? [...layouts, ownLayout] : layouts;
52
+ const nearestLoading = segmentFile(dir, "loading") ?? loading;
53
+ const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
54
+ for (const e of entries) {
55
+ if (e.name.startsWith("_") || e.name.startsWith(".")) continue;
56
+ const full = join(dir, e.name);
57
+ if (e.isDirectory()) {
58
+ await scanRoutes(appDir, full, chain, out, nearestLoading);
59
+ continue;
60
+ }
61
+ const ext = e.name.match(/\.[^.]+$/)?.[0] ?? "";
62
+ if (!ROUTE_EXTS.includes(ext)) continue;
63
+ const base = basename(e.name, ext);
64
+ const resource = base === "route";
65
+ if (!PAGE_BASENAMES.has(base) && !resource) continue;
66
+ const relDir = relative(appDir, dir);
67
+ const path = "/" + (relDir === "" ? [] : relDir.split(sep).filter((s) => !isRouteGroup(s))).join("/");
68
+ out.push({
69
+ path: path === "/" ? "/" : path,
70
+ file: full,
71
+ dynamic: /\[.+\]/.test(path),
72
+ resource,
73
+ layouts: chain,
74
+ loading: nearestLoading
75
+ });
76
+ }
77
+ return out;
78
+ }
79
+ async function generateContent(appRoot) {
80
+ const contentDir = join(appRoot, "content");
81
+ await mkdir(join(appRoot, "app"), { recursive: true });
82
+ const emit = async (sources, knownLocales) => {
83
+ const { code, names } = generateContentModule(contentDir, knownLocales, sources);
84
+ await writeFile(join(appRoot, "app", "_content.ts"), code);
85
+ return names;
86
+ };
87
+ const resolveSources = (sources) => sources.map((s) => ({
88
+ ...s,
89
+ dir: resolve(appRoot, s.dir)
90
+ }));
91
+ const declaredLocales = (config) => config.i18n ? [.../* @__PURE__ */ new Set([config.i18n.defaultLocale, ...Object.keys(config.i18n.locales)])] : [];
92
+ let config = null;
93
+ try {
94
+ config = await loadJuneConfig(appRoot);
95
+ } catch {}
96
+ if (config) return emit(resolveSources(config.content?.sources ?? []), declaredLocales(config));
97
+ const names = await emit([], void 0);
98
+ await seedContentImports(appRoot);
99
+ const probed = probeConfigFresh(appRoot);
100
+ if (probed === null) {
101
+ console.warn("[june gen] june.config.ts failed to load — content.sources/i18n locales (if any) not applied");
102
+ return names;
103
+ }
104
+ return emit(resolveSources(probed.sources), probed.locales);
105
+ }
106
+ async function seedContentImports(appRoot) {
107
+ const cfgPath = findJuneConfigPath(appRoot);
108
+ if (!cfgPath) return;
109
+ const contentFile = join(appRoot, "app", "_content.ts");
110
+ const current = existsSync(contentFile) ? await readFile(contentFile, "utf8") : "";
111
+ const cfgSrc = await readFile(cfgPath, "utf8");
112
+ const isIdent = (s) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(s);
113
+ const seen = /* @__PURE__ */ new Set();
114
+ const stubs = [];
115
+ for (const m of cfgSrc.matchAll(/import\s*(?:type\s+)?\{([^}]*)\}\s*from\s*["'][^"']*app\/_content(?:\.\w+)?["']/g)) {
116
+ const inner = (m[1] ?? "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/[^\n]*/g, "");
117
+ for (const part of inner.split(",")) {
118
+ const name = part.replace(/\btype\b/g, "").split(/\s+as\s+/).pop()?.trim();
119
+ if (!name || !isIdent(name) || seen.has(name)) continue;
120
+ seen.add(name);
121
+ const reName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
122
+ if (!new RegExp(`export (?:const|function|type) ${reName}\\b`).test(current)) stubs.push(`export const ${name}: ContentEntry[] = [];`);
123
+ }
124
+ }
125
+ if (stubs.length) await writeFile(contentFile, current + stubs.join("\n") + "\n");
126
+ }
127
+ function probeConfigFresh(appRoot) {
128
+ const path = findJuneConfigPath(appRoot);
129
+ if (!path) return {
130
+ sources: [],
131
+ locales: []
132
+ };
133
+ const code = `import(${JSON.stringify(pathToFileURL(path).href)}).then((m) => { const c = m.default ?? {}; process.stdout.write("\\n__JUNE_CONTENT__" + JSON.stringify({ sources: c.content?.sources ?? [], locales: c.i18n ? [...new Set([c.i18n.defaultLocale, ...Object.keys(c.i18n.locales ?? {})])] : [] }) + "__END__"); }, () => process.exit(42));`;
134
+ try {
135
+ const m = execFileSync(process.execPath, [
136
+ ...process.execArgv,
137
+ "-e",
138
+ code
139
+ ], {
140
+ encoding: "utf8",
141
+ stdio: [
142
+ "ignore",
143
+ "pipe",
144
+ "ignore"
145
+ ]
146
+ }).match(/__JUNE_CONTENT__(.*?)__END__/s);
147
+ return m ? JSON.parse(m[1]) : null;
148
+ } catch {
149
+ return null;
150
+ }
151
+ }
152
+ async function freezeConfig(appRoot) {
153
+ const cfg = await loadJuneConfig(appRoot);
154
+ const hasClient = findClientEntry(join(appRoot, "app")) !== void 0 || findClientEntry(join(appRoot, ".june", "routes")) !== void 0;
155
+ const hasCss = findGlobalCss(join(appRoot, "app")) !== null;
156
+ return {
157
+ document: {
158
+ site: cfg.site ?? {},
159
+ speculationRules: resolveSpeculationRules(cfg.speculation ?? void 0),
160
+ speculationDelivery: "inline",
161
+ viewTransitions: cfg.viewTransitions ?? true,
162
+ cssReset: cfg.cssReset ?? !globalCssUsesTailwind(join(appRoot, "app")),
163
+ clientRouter: resolveClientRouter(cfg.clientRouter),
164
+ clientScript: hasClient ? CLIENT_SCRIPT_URL : null,
165
+ styles: hasCss ? STYLES_URL : null,
166
+ basePath: normalizeBase(cfg.basePath)
167
+ },
168
+ agent: resolveAgent(cfg.agent),
169
+ i18n: cfg.i18n,
170
+ earlyHints: cfg.earlyHints ?? [],
171
+ buildExternal: cfg.build?.external ?? []
172
+ };
173
+ }
174
+ function normalizeBase(base) {
175
+ if (!base) return "";
176
+ const b = base.startsWith("/") ? base : `/${base}`;
177
+ return b.endsWith("/") ? b.slice(0, -1) : b;
178
+ }
179
+ async function importLayout(file) {
180
+ const mod = await import(pathToFileURL(file).href);
181
+ return typeof mod.default === "function" ? {
182
+ component: mod.default,
183
+ boundary: mod.segmentBoundary === true
184
+ } : null;
185
+ }
186
+ function workerName(raw) {
187
+ return raw.replace(/^@[^/]+\//, "").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "") || "app";
188
+ }
189
+ async function buildManifest(appRoot) {
190
+ const appDir = join(appRoot, "app");
191
+ const juneRoutesDir = join(appRoot, ".june", "routes");
192
+ const frozen = await freezeConfig(appRoot);
193
+ const appRoutes = await scanRoutes(appDir);
194
+ const frameworkRoutes = existsSync(juneRoutesDir) ? await scanRoutes(juneRoutesDir) : [];
195
+ const appPaths = new Set(appRoutes.map((r) => r.path));
196
+ const scanned = [...appRoutes, ...frameworkRoutes.filter((r) => !appPaths.has(r.path))].sort((a, b) => a.path.localeCompare(b.path));
197
+ const layoutCache = /* @__PURE__ */ new Map();
198
+ const loadCached = async (f) => {
199
+ if (!layoutCache.has(f)) layoutCache.set(f, await importLayout(f));
200
+ return layoutCache.get(f) ?? null;
201
+ };
202
+ const componentsFor = async (files) => {
203
+ const items = [];
204
+ for (const f of files) {
205
+ const c = await loadCached(f);
206
+ items.push({
207
+ file: f,
208
+ entry: c?.component ?? null,
209
+ boundary: !!c?.boundary
210
+ });
211
+ }
212
+ return resolveBoundary(items);
213
+ };
214
+ const loadingCache = /* @__PURE__ */ new Map();
215
+ const loadingFor = async (file) => {
216
+ if (!file) return void 0;
217
+ if (!loadingCache.has(file)) {
218
+ const loaded = await importLayout(file);
219
+ loadingCache.set(file, loaded ? loaded.component : null);
220
+ }
221
+ return loadingCache.get(file) ?? void 0;
222
+ };
223
+ const routes = {};
224
+ const dynamicRoutes = [];
225
+ const resourceRoutes = [];
226
+ const layoutChains = {};
227
+ const layoutBoundaries = {};
228
+ const loadings = {};
229
+ for (const r of scanned) {
230
+ const mod = await import(pathToFileURL(r.file).href);
231
+ if (r.resource) {
232
+ const handler = mod.default;
233
+ if (typeof handler === "function") resourceRoutes.push({
234
+ pattern: r.path,
235
+ handler
236
+ });
237
+ continue;
238
+ }
239
+ const def = routeFromModule(mod);
240
+ if (!def) continue;
241
+ const { chain, boundaryIndex, key } = await componentsFor(r.layouts);
242
+ const loading = await loadingFor(r.loading);
243
+ if (r.dynamic) {
244
+ dynamicRoutes.push({
245
+ pattern: r.path,
246
+ def
247
+ });
248
+ layoutChains[r.path] = chain;
249
+ } else {
250
+ routes[r.path] = def;
251
+ layoutChains[r.path] = chain;
252
+ }
253
+ if (boundaryIndex !== null && key !== null) layoutBoundaries[r.path] = {
254
+ index: boundaryIndex,
255
+ key
256
+ };
257
+ if (loading) loadings[r.path] = loading;
258
+ }
259
+ let extra;
260
+ const extraFile = findMiddlewareFile(appDir);
261
+ if (extraFile) {
262
+ const mod = await import(pathToFileURL(extraFile).href);
263
+ if (typeof mod.default === "function") extra = mod.default;
264
+ }
265
+ return {
266
+ routes,
267
+ dynamicRoutes,
268
+ resourceRoutes,
269
+ layoutChains,
270
+ layoutBoundaries,
271
+ loadings,
272
+ document: frozen.document,
273
+ agent: frozen.agent,
274
+ i18n: frozen.i18n,
275
+ earlyHints: frozen.earlyHints,
276
+ extra
277
+ };
278
+ }
279
+ function importPath(fromDir, file) {
280
+ const p = relative(fromDir, file).split(sep).join("/").replace(/\.[^.]+$/, "");
281
+ return p.startsWith(".") ? p : `./${p}`;
282
+ }
283
+ async function collectFiles(dir, base = dir) {
284
+ const out = [];
285
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
286
+ const abs = join(dir, entry.name);
287
+ if (entry.isDirectory()) out.push(...await collectFiles(abs, base));
288
+ else if (entry.isFile()) out.push(relative(base, abs).split(sep).join("/"));
289
+ }
290
+ return out;
291
+ }
292
+ async function juneBuild(appRoot, options = {}) {
293
+ const appDir = join(appRoot, "app");
294
+ if (!existsSync(appDir)) throw new Error(`no app/ directory in ${appRoot} — is this a June app?`);
295
+ const genDir = join(appRoot, ".june");
296
+ const outDir = options.outDir ?? join(appRoot, "dist");
297
+ await mkdir(genDir, { recursive: true });
298
+ await rm(outDir, {
299
+ recursive: true,
300
+ force: true
301
+ });
302
+ const contentCollections = await generateContent(appRoot);
303
+ const juneRoutesDir2 = join(appRoot, ".june", "routes");
304
+ const appRoutes2 = await scanRoutes(appDir);
305
+ const frameworkRoutes2 = existsSync(juneRoutesDir2) ? await scanRoutes(juneRoutesDir2) : [];
306
+ const appPaths2 = new Set(appRoutes2.map((r) => r.path));
307
+ const routes = [...appRoutes2, ...frameworkRoutes2.filter((r) => !appPaths2.has(r.path))].sort((a, b) => a.path.localeCompare(b.path));
308
+ if (routes.length === 0) throw new Error(`no page.* routes found under ${appDir} or .june/routes/`);
309
+ const frozen = await freezeConfig(appRoot);
310
+ if (frozen.i18n?.resolveLocale) console.warn("[june build] i18n.resolveLocale is not yet wired into the built worker (URL-pinned + built-in negotiation work; the hook runs in dev only).");
311
+ const fullConfig = await loadJuneConfig(appRoot);
312
+ const adapter = resolveDeployAdapter(fullConfig.deploy);
313
+ adapter.validate?.({
314
+ plan: { db: fullConfig.resources?.db ? {
315
+ binding: "DB",
316
+ databaseName: ""
317
+ } : void 0 },
318
+ config: fullConfig
319
+ });
320
+ const cssOut = await processCss(appDir, { minify: true });
321
+ let cssAsset = null;
322
+ if (cssOut !== null) {
323
+ cssAsset = `_june/global.${createHash("sha256").update(cssOut).digest("hex").slice(0, 8)}.css`;
324
+ frozen.document.styles = `/${cssAsset}`;
325
+ }
326
+ const { maps: cssModuleMaps, css: rawModuleCss } = await buildModuleCss(appDir, appRoot);
327
+ const moduleCss = rawModuleCss === null ? null : await minifyCss(rawModuleCss, "modules.css", await cssTargets(appDir));
328
+ let moduleCssAsset = null;
329
+ if (moduleCss !== null) {
330
+ moduleCssAsset = `_june/modules.${createHash("sha256").update(moduleCss).digest("hex").slice(0, 8)}.css`;
331
+ frozen.document.moduleStyles = `/${moduleCssAsset}`;
332
+ }
333
+ const assetsDir = join(outDir, "assets");
334
+ const clientEntry = findClientEntry(appDir) ?? findClientEntry(join(appRoot, ".june", "routes"));
335
+ let clientAsset = null;
336
+ if (clientEntry) {
337
+ await generateIslandRegistry(appDir);
338
+ clientAsset = await bundleClientToFile(clientEntry, appRoot, assetsDir, cssModuleMaps);
339
+ frozen.document.clientScript = `/${clientAsset}`;
340
+ }
341
+ if (findRscRoutes(appDir).length > 0) await buildRsc(appRoot, outDir, frozen.document);
342
+ const resourcesCfg = fullConfig.resources;
343
+ const hasResources = !!(resourcesCfg?.db || resourcesCfg?.kv || resourcesCfg?.blob);
344
+ const imports = [`import { createWorker } from "@junejs/server/worker";`, `import { routeFromModule } from "@junejs/core/route";`];
345
+ const statics = [];
346
+ const dynamics = [];
347
+ const layoutIds = /* @__PURE__ */ new Map();
348
+ const layoutId = (file) => {
349
+ let id = layoutIds.get(file);
350
+ if (!id) {
351
+ id = `L${layoutIds.size}`;
352
+ layoutIds.set(file, id);
353
+ imports.push(`import ${id} from ${JSON.stringify(importPath(genDir, file))};`);
354
+ }
355
+ return id;
356
+ };
357
+ const loadingIds = /* @__PURE__ */ new Map();
358
+ const loadingId = (file) => {
359
+ let id = loadingIds.get(file);
360
+ if (!id) {
361
+ id = `Ld${loadingIds.size}`;
362
+ loadingIds.set(file, id);
363
+ imports.push(`import ${id} from ${JSON.stringify(importPath(genDir, file))};`);
364
+ }
365
+ return id;
366
+ };
367
+ const layoutInfo = /* @__PURE__ */ new Map();
368
+ for (const f of new Set(routes.flatMap((r) => r.resource ? [] : r.layouts))) layoutInfo.set(f, await importLayout(f));
369
+ const chains = [];
370
+ const boundaries = [];
371
+ const loadings = [];
372
+ const resources = [];
373
+ routes.forEach((r, i) => {
374
+ if (r.resource) {
375
+ imports.push(`import h${i} from ${JSON.stringify(importPath(genDir, r.file))};`);
376
+ resources.push(` { pattern: ${JSON.stringify(r.path)}, handler: h${i} },`);
377
+ return;
378
+ }
379
+ imports.push(`import * as r${i} from ${JSON.stringify(importPath(genDir, r.file))};`);
380
+ if (r.dynamic) dynamics.push(` { pattern: ${JSON.stringify(r.path)}, def: routeFromModule(r${i}) },`);
381
+ else statics.push(` ${JSON.stringify(r.path)}: routeFromModule(r${i}),`);
382
+ const { chain, boundaryIndex, key } = resolveBoundary(r.layouts.map((f) => {
383
+ const info = layoutInfo.get(f) ?? null;
384
+ return {
385
+ file: f,
386
+ entry: info ? layoutId(f) : null,
387
+ boundary: !!info?.boundary
388
+ };
389
+ }));
390
+ chains.push(` ${JSON.stringify(r.path)}: [${chain.join(", ")}],`);
391
+ if (boundaryIndex !== null && key !== null) boundaries.push(` ${JSON.stringify(r.path)}: { index: ${boundaryIndex}, key: ${JSON.stringify(key)} },`);
392
+ if (r.loading) loadings.push(` ${JSON.stringify(r.path)}: ${loadingId(r.loading)},`);
393
+ });
394
+ const resourceRoutesField = resources.length ? `\n resourceRoutes: [\n${resources.join("\n")}\n ],` : "";
395
+ const layoutBoundariesField = boundaries.length ? `\n layoutBoundaries: {\n${boundaries.join("\n")}\n },` : "";
396
+ const builtExtraFile = findMiddlewareFile(appDir);
397
+ if (builtExtraFile) imports.push(`import extra from ${JSON.stringify(importPath(genDir, builtExtraFile))};`);
398
+ const linkHeader = buildLinkHeader(frozen.agent);
399
+ const adapterEntry = adapter.entry({ linkHeader });
400
+ const resourceFlags = {
401
+ db: !!resourcesCfg?.db,
402
+ kv: !!resourcesCfg?.kv,
403
+ blob: !!resourcesCfg?.blob
404
+ };
405
+ const tursoDb = resourcesCfg?.db?.kind === "turso";
406
+ if (tursoDb) {
407
+ imports.push(`import { turso } from "@junejs/server/db";`);
408
+ imports.push(`import { memoizeResources } from "@junejs/server/resources";`);
409
+ } else if (hasResources) imports.push(`import { bindWorkerResources } from "@junejs/server/resources";`);
410
+ const resourcesField = tursoDb ? `\n resources: memoizeResources({ db: turso() }),` : hasResources ? `\n resources: bindWorkerResources(${JSON.stringify(resourceFlags)}),` : "";
411
+ const dataLayerModule = fullConfig.dataLayer?.module;
412
+ if (dataLayerModule) imports.push(`import { installDataLayer } from ${JSON.stringify(dataLayerModule)};`);
413
+ const dataLayerBoot = dataLayerModule ? "\ninstallDataLayer();\n" : "";
414
+ const entry = `// AUTO-GENERATED by \`june build\` — do not edit. Regenerate: june build .
415
+ ${adapterEntry.imports.join("\n")}
416
+ ${imports.join("\n")}
417
+ ${dataLayerBoot}
418
+ const pipeline = createWorker({
419
+ routes: {
420
+ ${statics.join("\n")}
421
+ },
422
+ dynamicRoutes: [
423
+ ${dynamics.join("\n")}
424
+ ],${resourceRoutesField}
425
+ layoutChains: {
426
+ ${chains.join("\n")}
427
+ },${layoutBoundariesField}
428
+ loadings: {
429
+ ${loadings.join("\n")}
430
+ },
431
+ document: ${JSON.stringify(frozen.document, null, 2).replace(/\n/g, "\n ")},
432
+ agent: ${JSON.stringify(frozen.agent)},${frozen.i18n ? `\n i18n: ${JSON.stringify(frozen.i18n)},` : ""}
433
+ earlyHints: ${JSON.stringify(frozen.earlyHints)},${builtExtraFile ? "\n extra," : ""}${resourcesField}
434
+ });
435
+
436
+ ${adapterEntry.wrap("pipeline")}
437
+ `;
438
+ const entryFile = join(genDir, "worker-entry.tsx");
439
+ await writeFile(entryFile, entry);
440
+ const { rolldown } = await import("rolldown");
441
+ const bundle = await rolldown({
442
+ input: entryFile,
443
+ cwd: appRoot,
444
+ platform: "browser",
445
+ transform: {
446
+ define: { "process.env.NODE_ENV": JSON.stringify("production") },
447
+ jsx: await jsxTransform(appRoot)
448
+ },
449
+ plugins: [rolldownCssModulesPlugin(cssModuleMaps)],
450
+ external: (id) => {
451
+ if (isBunSpecifier(id)) return true;
452
+ if (/\.(wasm|ttf|otf|woff2?|png|jpe?g|avif|webp)$/.test(id)) return true;
453
+ return [...adapter.buildExternal ?? [], ...options.external ?? frozen.buildExternal].some((e) => id === e || id.startsWith(`${e}/`));
454
+ },
455
+ resolve: { conditionNames: adapter.conditions }
456
+ });
457
+ const result = await bundle.write({
458
+ dir: outDir,
459
+ format: "esm",
460
+ entryFileNames: "worker.js"
461
+ });
462
+ await bundle.close();
463
+ const outFile = join(outDir, result.output.find((o) => o.type === "chunk" && o.isEntry)?.fileName ?? "worker.js");
464
+ const prerendered = [];
465
+ await registerCssModules(cssModuleMaps);
466
+ const manifest = await buildManifest(appRoot);
467
+ if (cssAsset) manifest.document.styles = `/${cssAsset}`;
468
+ if (clientAsset) manifest.document.clientScript = `/${clientAsset}`;
469
+ if (moduleCssAsset) manifest.document.moduleStyles = `/${moduleCssAsset}`;
470
+ const worker = createWorker(manifest);
471
+ let hasAssets = false;
472
+ const publicDir = join(appRoot, "public");
473
+ const publicFiles = [];
474
+ let publicIsDir = false;
475
+ try {
476
+ publicIsDir = lstatSync(publicDir).isDirectory();
477
+ } catch {}
478
+ if (existsSync(publicDir) && !publicIsDir) console.warn(`[june] public/ is not a real directory (symlink?) — skipped`);
479
+ if (publicIsDir) {
480
+ for (const rel of await collectFiles(publicDir)) {
481
+ if (rel.split("/")[0] === "_june") {
482
+ console.warn(`[june] public/${rel} ignored — ${RESERVED_PREFIX}/ is reserved for framework assets`);
483
+ continue;
484
+ }
485
+ const dest = join(assetsDir, rel);
486
+ await mkdir(dirname(dest), { recursive: true });
487
+ await copyFile(join(publicDir, ...rel.split("/")), dest);
488
+ publicFiles.push(rel);
489
+ }
490
+ if (publicFiles.length) hasAssets = true;
491
+ }
492
+ const isStatic = adapter.capabilities.runtime === "static";
493
+ const i18n = frozen.i18n;
494
+ const prerenderOne = async (reqPath, def) => {
495
+ const stem = reqPath === "/" ? "index" : reqPath.slice(1);
496
+ const htmlFile = isStatic ? reqPath === "/" ? "index.html" : `${stem}/index.html` : `${stem}.html`;
497
+ const isHome = reqPath === "/" || i18n != null && Object.keys(i18n.locales).some((l) => localeHref(i18n, "/", l) === reqPath);
498
+ const mdReq = reqPath === "/" ? "/index.md" : isHome ? `${reqPath}/index.md` : `${reqPath}.md`;
499
+ const jsonReq = reqPath === "/" ? "/index.json" : isHome ? `${reqPath}/index.json` : `${reqPath}.json`;
500
+ const mdFile = reqPath === "/" ? "index.md" : isHome ? `${stem}/index.md` : `${stem}.md`;
501
+ const jsonFile = reqPath === "/" ? "index.json" : isHome ? `${stem}/index.json` : `${stem}.json`;
502
+ const targets = [[reqPath, htmlFile]];
503
+ if (def.md !== false) targets.push([mdReq, mdFile]);
504
+ if (typeof def.json === "function") targets.push([jsonReq, jsonFile]);
505
+ for (const [rp, file] of targets) {
506
+ const res = await worker.fetch(new Request(`https://prerender.june${rp}`));
507
+ if (!res.ok) throw new Error(`prerender ${rp} → ${res.status}`);
508
+ const dest = join(assetsDir, file);
509
+ await mkdir(dirname(dest), { recursive: true });
510
+ await writeFile(dest, Buffer.from(await res.arrayBuffer()));
511
+ }
512
+ prerendered.push(reqPath);
513
+ hasAssets = true;
514
+ };
515
+ const localeVariants = (path) => isStatic && i18n ? [...new Set(Object.keys(i18n.locales).map((l) => localeHref(i18n, path, l)))] : [path];
516
+ for (const r of routes.filter((x) => !x.dynamic)) {
517
+ const def = manifest.routes[r.path];
518
+ if (!def) continue;
519
+ if (!isStatic && !def.prerender) continue;
520
+ for (const p of localeVariants(r.path)) await prerenderOne(p, def);
521
+ }
522
+ if (isStatic) {
523
+ for (const dyn of manifest.dynamicRoutes ?? []) {
524
+ const sp = dyn.def.staticPaths;
525
+ if (!sp) continue;
526
+ const paths = typeof sp === "function" ? await sp() : sp;
527
+ for (const p of paths) await prerenderOne(p, dyn.def);
528
+ }
529
+ for (const [reqPath, file] of [
530
+ ["/favicon.svg", "favicon.svg"],
531
+ ["/llms.txt", "llms.txt"],
532
+ ["/sitemap.xml", "sitemap.xml"]
533
+ ]) {
534
+ const res = await worker.fetch(new Request(`https://prerender.june${reqPath}`));
535
+ if (!res.ok) continue;
536
+ await writeFile(join(assetsDir, file), Buffer.from(await res.arrayBuffer()));
537
+ hasAssets = true;
538
+ }
539
+ const nf = await worker.fetch(new Request("https://prerender.june/__june_not_found__"));
540
+ await writeFile(join(assetsDir, "404.html"), Buffer.from(await nf.arrayBuffer()));
541
+ hasAssets = true;
542
+ }
543
+ if (clientAsset) hasAssets = true;
544
+ if (cssOut !== null && cssAsset) {
545
+ const dest = join(assetsDir, cssAsset);
546
+ await mkdir(dirname(dest), { recursive: true });
547
+ await writeFile(dest, cssOut);
548
+ hasAssets = true;
549
+ }
550
+ if (moduleCss !== null && moduleCssAsset) {
551
+ const dest = join(assetsDir, moduleCssAsset);
552
+ await mkdir(dirname(dest), { recursive: true });
553
+ await writeFile(dest, moduleCss);
554
+ hasAssets = true;
555
+ }
556
+ const pkgPath = join(appRoot, "package.json");
557
+ const defaultName = workerName((existsSync(pkgPath) ? JSON.parse(await Bun.file(pkgPath).text()).name : void 0) ?? basename(appRoot));
558
+ const plan = resourcesCfg?.db && resourcesCfg.db.kind !== "turso" ? { db: {
559
+ binding: "DB",
560
+ databaseName: `${defaultName}-db`
561
+ } } : {};
562
+ await adapter.emit({
563
+ appRoot,
564
+ outDir,
565
+ hasAssets,
566
+ linkHeader,
567
+ config: fullConfig,
568
+ plan,
569
+ defaultName,
570
+ publicFiles
571
+ });
572
+ if (plan.db) console.log(` ↳ d1 binding "${plan.db.binding}" emitted — set database_id in wrangler.jsonc (wrangler d1 create ${plan.db.databaseName})`);
573
+ return {
574
+ outFile,
575
+ routes: routes.filter((r) => !r.dynamic).map((r) => r.path),
576
+ dynamicRoutes: routes.filter((r) => r.dynamic).map((r) => r.path),
577
+ contentCollections,
578
+ prerendered
579
+ };
580
+ }
581
+ //#endregion
582
+ export { buildManifest, freezeConfig, generateContent, isBunSpecifier, juneBuild, normalizeBase, resolveDeployAdapter, scanRoutes, workerName };
@@ -0,0 +1,15 @@
1
+ import { ModuleMaps } from "./css-modules.js";
2
+ //#region src/client-bundle.d.ts
3
+ declare const CLIENT_SCRIPT_URL = "/_june/client.js";
4
+ declare function findClientEntry(appDir: string): string | undefined;
5
+ type BundleMode = "development" | "production";
6
+ declare function isExpectedClientLog(log: {
7
+ code?: string;
8
+ exporter?: string;
9
+ }): boolean;
10
+ type ClientChunks = Map<string, string>;
11
+ declare function bundleClientSplit(entryFile: string, cwd: string, mode: BundleMode, maps?: ModuleMaps): Promise<ClientChunks>;
12
+ declare function bundleClientToString(entryFile: string, cwd: string, maps?: ModuleMaps): Promise<string>;
13
+ declare function bundleClientToFile(entryFile: string, cwd: string, destDir: string, maps?: ModuleMaps): Promise<string>;
14
+ //#endregion
15
+ export { CLIENT_SCRIPT_URL, ClientChunks, bundleClientSplit, bundleClientToFile, bundleClientToString, findClientEntry, isExpectedClientLog };
@@ -0,0 +1,91 @@
1
+ import { rolldownCssModulesPlugin } from "./css-modules.js";
2
+ import { jsxTransform } from "./tsconfig-jsx.js";
3
+ import { existsSync } from "node:fs";
4
+ import { mkdir, writeFile } from "node:fs/promises";
5
+ import { dirname, join } from "node:path";
6
+ //#region src/client-bundle.ts
7
+ const CLIENT_SCRIPT_URL = "/_june/client.js";
8
+ const CLIENT_BASENAME = "_client";
9
+ const CLIENT_EXTS = [
10
+ ".tsx",
11
+ ".ts",
12
+ ".jsx",
13
+ ".js"
14
+ ];
15
+ function findClientEntry(appDir) {
16
+ for (const ext of CLIENT_EXTS) {
17
+ const file = join(appDir, CLIENT_BASENAME + ext);
18
+ if (existsSync(file)) return file;
19
+ }
20
+ }
21
+ function isExpectedClientLog(log) {
22
+ return log.code === "UNRESOLVED_IMPORT" && (log.exporter ?? "").startsWith("react-server-dom-webpack");
23
+ }
24
+ async function bundleClient(entryFile, cwd, mode, maps = {}) {
25
+ const { rolldown } = await import("rolldown");
26
+ return await rolldown({
27
+ input: entryFile,
28
+ cwd,
29
+ platform: "browser",
30
+ plugins: [rolldownCssModulesPlugin(maps)],
31
+ transform: {
32
+ define: { "process.env.NODE_ENV": JSON.stringify(mode) },
33
+ jsx: await jsxTransform(cwd)
34
+ },
35
+ resolve: { conditionNames: [
36
+ "source",
37
+ "browser",
38
+ "import",
39
+ "default"
40
+ ] },
41
+ onLog(level, log, handler) {
42
+ if (isExpectedClientLog(log)) return;
43
+ handler(level, log);
44
+ }
45
+ });
46
+ }
47
+ async function bundleClientSplit(entryFile, cwd, mode, maps = {}) {
48
+ const bundle = await bundleClient(entryFile, cwd, mode, maps);
49
+ const { output } = await bundle.generate({
50
+ format: "esm",
51
+ entryFileNames: "client.js",
52
+ chunkFileNames: "[name].js"
53
+ });
54
+ await bundle.close();
55
+ const chunks = /* @__PURE__ */ new Map();
56
+ for (const o of output) if (o.type === "chunk") chunks.set(o.fileName, o.code);
57
+ return chunks;
58
+ }
59
+ async function bundleClientToString(entryFile, cwd, maps = {}) {
60
+ const bundle = await bundleClient(entryFile, cwd, "development", maps);
61
+ const { output } = await bundle.generate({
62
+ format: "esm",
63
+ entryFileNames: "client.js"
64
+ });
65
+ await bundle.close();
66
+ const entry = output.find((o) => o.type === "chunk" && o.isEntry);
67
+ return entry && entry.type === "chunk" ? entry.code : "";
68
+ }
69
+ async function bundleClientToFile(entryFile, cwd, destDir, maps = {}) {
70
+ const bundle = await bundleClient(entryFile, cwd, "production", maps);
71
+ const { output } = await bundle.generate({
72
+ format: "esm",
73
+ hashCharacters: "hex",
74
+ entryFileNames: "_june/client.[hash].js",
75
+ chunkFileNames: "_june/[name]-[hash].js",
76
+ assetFileNames: "_june/[name]-[hash][extname]"
77
+ });
78
+ await bundle.close();
79
+ let entryFileName = "";
80
+ for (const o of output) {
81
+ const dest = join(destDir, o.fileName);
82
+ await mkdir(dirname(dest), { recursive: true });
83
+ if (o.type === "chunk") {
84
+ await writeFile(dest, o.code);
85
+ if (o.isEntry) entryFileName = o.fileName;
86
+ } else await writeFile(dest, o.source);
87
+ }
88
+ return entryFileName;
89
+ }
90
+ //#endregion
91
+ export { CLIENT_SCRIPT_URL, bundleClientSplit, bundleClientToFile, bundleClientToString, findClientEntry, isExpectedClientLog };