@fluixi/start 0.1.0-alpha.73 → 0.1.0-alpha.75

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 (123) hide show
  1. package/dist/adapter.cjs +750 -0
  2. package/dist/adapter.d.ts +9 -9
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/adapter.js +10 -10
  5. package/dist/adapter.mjs +719 -0
  6. package/dist/adapters/entry.cjs +122 -0
  7. package/dist/adapters/entry.js +1 -1
  8. package/dist/adapters/entry.mjs +95 -0
  9. package/dist/adapters/platforms.cjs +311 -0
  10. package/dist/adapters/platforms.d.ts +2 -2
  11. package/dist/adapters/platforms.js +8 -8
  12. package/dist/adapters/platforms.mjs +284 -0
  13. package/dist/api-HD7KU3BS.mjs +114 -0
  14. package/dist/api.cjs +145 -0
  15. package/dist/api.js +1 -1
  16. package/dist/api.mjs +116 -0
  17. package/dist/commands/api-HD7KU3BS.mjs +114 -0
  18. package/dist/commands/api-PBT4QZ34.mjs +16 -0
  19. package/dist/commands/api-PJL37LV3.mjs +17 -0
  20. package/dist/commands/build.cjs +877 -0
  21. package/dist/commands/build.js +6 -6
  22. package/dist/commands/build.mjs +717 -0
  23. package/dist/commands/chunk-GFQ4MUIC.mjs +117 -0
  24. package/dist/commands/chunk-MTM6M3TC.mjs +115 -0
  25. package/dist/commands/dev.cjs +670 -0
  26. package/dist/commands/dev.js +3 -3
  27. package/dist/commands/dev.mjs +512 -0
  28. package/dist/commands/index.cjs +1145 -0
  29. package/dist/commands/index.d.ts +1 -1
  30. package/dist/commands/index.js +1 -1
  31. package/dist/commands/index.mjs +982 -0
  32. package/dist/commands/prerender.cjs +404 -0
  33. package/dist/commands/prerender.d.ts +1 -1
  34. package/dist/commands/prerender.js +2 -2
  35. package/dist/commands/prerender.mjs +377 -0
  36. package/dist/commands/start.cjs +463 -0
  37. package/dist/commands/start.d.ts +1 -1
  38. package/dist/commands/start.js +2 -2
  39. package/dist/commands/start.mjs +428 -0
  40. package/dist/config.cjs +150 -0
  41. package/dist/config.d.ts +24 -7
  42. package/dist/config.d.ts.map +1 -1
  43. package/dist/config.js +8 -6
  44. package/dist/config.mjs +114 -0
  45. package/dist/define-config.cjs +32 -0
  46. package/dist/define-config.d.ts +12 -0
  47. package/dist/define-config.d.ts.map +1 -0
  48. package/dist/define-config.js +3 -0
  49. package/dist/define-config.mjs +7 -0
  50. package/dist/di.cjs +200 -0
  51. package/dist/di.d.ts +4 -4
  52. package/dist/di.d.ts.map +1 -1
  53. package/dist/di.js +9 -9
  54. package/dist/di.mjs +169 -0
  55. package/dist/document.cjs +115 -0
  56. package/dist/document.d.ts +1 -1
  57. package/dist/document.js +1 -1
  58. package/dist/document.mjs +86 -0
  59. package/dist/generated-api.cjs +43 -0
  60. package/dist/generated-api.js +1 -1
  61. package/dist/generated-api.mjs +18 -0
  62. package/dist/generated-server-fns.cjs +43 -0
  63. package/dist/generated-server-fns.mjs +18 -0
  64. package/dist/handler-core.cjs +211 -0
  65. package/dist/handler-core.d.ts +2 -2
  66. package/dist/handler-core.js +2 -2
  67. package/dist/handler-core.mjs +185 -0
  68. package/dist/handler.cjs +182 -0
  69. package/dist/handler.d.ts +3 -3
  70. package/dist/handler.js +3 -3
  71. package/dist/handler.mjs +152 -0
  72. package/dist/head.cjs +25 -0
  73. package/dist/head.d.ts +1 -1
  74. package/dist/head.js +1 -1
  75. package/dist/head.mjs +4 -0
  76. package/dist/image-loader.cjs +37 -0
  77. package/dist/image-loader.d.ts +25 -0
  78. package/dist/image-loader.d.ts.map +1 -0
  79. package/dist/image-loader.js +18 -0
  80. package/dist/image-loader.mjs +12 -0
  81. package/dist/image.cjs +151 -0
  82. package/dist/image.d.ts +2 -19
  83. package/dist/image.d.ts.map +1 -1
  84. package/dist/image.js +5 -21
  85. package/dist/image.mjs +126 -0
  86. package/dist/index.cjs +303 -0
  87. package/dist/index.d.ts +28 -8
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +33 -17
  90. package/dist/index.mjs +262 -0
  91. package/dist/interceptors.cjs +72 -0
  92. package/dist/interceptors.js +1 -1
  93. package/dist/interceptors.mjs +44 -0
  94. package/dist/internal.cjs +393 -0
  95. package/dist/internal.d.ts +6 -6
  96. package/dist/internal.d.ts.map +1 -1
  97. package/dist/internal.js +23 -15
  98. package/dist/internal.mjs +223 -0
  99. package/dist/middleware.cjs +58 -0
  100. package/dist/middleware.d.ts +3 -3
  101. package/dist/middleware.d.ts.map +1 -1
  102. package/dist/middleware.js +1 -1
  103. package/dist/middleware.mjs +31 -0
  104. package/dist/preload.cjs +42 -0
  105. package/dist/preload.js +1 -1
  106. package/dist/preload.mjs +18 -0
  107. package/dist/router.cjs +25 -0
  108. package/dist/router.d.ts +3 -3
  109. package/dist/router.js +3 -3
  110. package/dist/router.mjs +4 -0
  111. package/dist/server-fn-setup.cjs +46 -0
  112. package/dist/server-fn-setup.d.ts +1 -1
  113. package/dist/server-fn-setup.js +2 -2
  114. package/dist/server-fn-setup.mjs +22 -0
  115. package/dist/server-fn.cjs +109 -0
  116. package/dist/server-fn.d.ts +2 -2
  117. package/dist/server-fn.js +2 -2
  118. package/dist/server-fn.mjs +79 -0
  119. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  120. package/dist/ui.cjs +123 -0
  121. package/dist/ui.js +1 -1
  122. package/dist/ui.mjs +93 -0
  123. package/package.json +63 -25
@@ -0,0 +1,717 @@
1
+ // src/commands/build.ts
2
+ import { existsSync as existsSync3 } from "node:fs";
3
+ import { mkdir as mkdir2, writeFile as writeFile2, rm, copyFile, readFile as readFile2 } from "node:fs/promises";
4
+ import { resolve as resolve4, dirname as dirname3, join as join3, basename } from "node:path";
5
+
6
+ // src/config.ts
7
+ import { resolve, isAbsolute } from "node:path";
8
+ function resolveConfig(config = {}) {
9
+ const root = resolve(config.root ?? process.cwd());
10
+ return {
11
+ root,
12
+ ssr: config.ssr ?? true,
13
+ port: config.port ?? 3e3,
14
+ serverEntry: config.serverEntry ?? "src/entry-server.ts",
15
+ clientEntry: config.clientEntry ?? "src/entry-client.ts",
16
+ template: config.template ?? "index.html",
17
+ mountId: config.mountId ?? "root",
18
+ routesDir: config.routesDir ?? "src/routes",
19
+ apiDir: config.apiDir ?? "src/api",
20
+ middleware: config.middleware ?? "src/middleware.ts",
21
+ ssrNoExternal: config.ssrNoExternal ?? [],
22
+ resolve: config.resolve ?? [],
23
+ vitePlugin: config.vitePlugin ?? {},
24
+ plugins: config.plugins ?? [],
25
+ adapter: config.adapter,
26
+ i18n: resolveI18n(config.i18n),
27
+ prerender: resolvePrerender(config.prerender)
28
+ };
29
+ }
30
+ function resolvePrerender(prerender2) {
31
+ if (!prerender2) return false;
32
+ const opts = typeof prerender2 === "object" ? prerender2 : {};
33
+ return {
34
+ routes: opts.routes?.length ? opts.routes : ["/"],
35
+ crawl: opts.crawl ?? true
36
+ };
37
+ }
38
+ function resolveI18n(i18n) {
39
+ if (i18n === false) return false;
40
+ const opts = typeof i18n === "object" ? i18n : {};
41
+ return {
42
+ dir: opts.dir ?? "src/i18n",
43
+ defaultLocale: opts.defaultLocale,
44
+ locales: opts.locales
45
+ };
46
+ }
47
+
48
+ // src/internal.ts
49
+ import { readdirSync, readFileSync, writeFileSync, existsSync } from "node:fs";
50
+ import { resolve as resolvePath, relative as relativePath } from "node:path";
51
+
52
+ // src/document.ts
53
+ import { extractHeadMarker } from "@fluixi/head";
54
+ function injectApp(template, appHtml, mountId = "root") {
55
+ const mount = new RegExp(`<div id=["']${mountId}["']\\s*>\\s*</div>`);
56
+ if (mount.test(template)) {
57
+ return template.replace(mount, `<div id="${mountId}">${appHtml}</div>`);
58
+ }
59
+ if (template.includes("<!--ssr-outlet-->")) {
60
+ return template.replace("<!--ssr-outlet-->", appHtml);
61
+ }
62
+ return template.replace("</body>", `<div id="${mountId}">${appHtml}</div></body>`);
63
+ }
64
+ function splitTemplate(template, mountId = "root") {
65
+ const mount = new RegExp(`<div id=["']${mountId}["']\\s*>\\s*</div>`);
66
+ const m = template.match(mount);
67
+ if (m && m.index !== void 0) {
68
+ return {
69
+ head: template.slice(0, m.index) + `<div id="${mountId}">`,
70
+ tail: `</div>` + template.slice(m.index + m[0].length)
71
+ };
72
+ }
73
+ const outlet = template.indexOf("<!--ssr-outlet-->");
74
+ if (outlet !== -1) {
75
+ return {
76
+ head: template.slice(0, outlet),
77
+ tail: template.slice(outlet + "<!--ssr-outlet-->".length)
78
+ };
79
+ }
80
+ const body = template.indexOf("</body>");
81
+ if (body !== -1) {
82
+ return {
83
+ head: template.slice(0, body) + `<div id="${mountId}">`,
84
+ tail: `</div>` + template.slice(body)
85
+ };
86
+ }
87
+ return { head: template, tail: "" };
88
+ }
89
+ function injectPreload(template, files) {
90
+ const tags = files.filter((file) => !template.includes(file)).map(
91
+ (file) => file.endsWith(".css") ? `<link rel="stylesheet" href="${file}">` : (
92
+ // `modulepreload` also fetches the chunk's own static imports.
93
+ `<link rel="modulepreload" href="${file}">`
94
+ )
95
+ );
96
+ if (tags.length === 0) return template;
97
+ const head = template.indexOf("</head>");
98
+ if (head === -1) return template;
99
+ return template.slice(0, head) + tags.join("") + template.slice(head);
100
+ }
101
+ function setHtmlAttr(html, name, value) {
102
+ const existing = new RegExp(`(<html\\b[^>]*?)\\s${name}="[^"]*"`, "i");
103
+ if (existing.test(html)) return html.replace(existing, `$1 ${name}="${value}"`);
104
+ return html.replace(/<html\b/i, `<html ${name}="${value}"`);
105
+ }
106
+ function injectAppAndHead(template, rendered, mountId = "root") {
107
+ const { head, body } = extractHeadMarker(rendered);
108
+ let html = injectApp(template, body, mountId);
109
+ if (head) {
110
+ if (head.headHtml) {
111
+ if (/<title[\s>]/i.test(head.headHtml)) html = html.replace(/<title>[\s\S]*?<\/title>/i, "");
112
+ html = html.replace("</head>", `${head.headHtml}</head>`);
113
+ }
114
+ for (const [k, v] of Object.entries(head.htmlAttrs)) html = setHtmlAttr(html, k, v);
115
+ }
116
+ return html;
117
+ }
118
+ function guardHandler(handler, onError) {
119
+ return async (request) => {
120
+ try {
121
+ return await handler(request);
122
+ } catch (e) {
123
+ onError?.(e);
124
+ return new Response(String(e?.stack || e), {
125
+ status: 500,
126
+ headers: { "content-type": "text/plain; charset=utf-8" }
127
+ });
128
+ }
129
+ };
130
+ }
131
+
132
+ // src/internal.ts
133
+ var I18N_VMOD = "virtual:fluixi-i18n";
134
+ function i18nVitePlugin(opts) {
135
+ let root = process.cwd();
136
+ const id = "\0" + I18N_VMOD;
137
+ return {
138
+ name: "fluixi-i18n",
139
+ configResolved(config) {
140
+ root = config.root || root;
141
+ },
142
+ resolveId(source) {
143
+ return source === I18N_VMOD ? id : null;
144
+ },
145
+ load(source) {
146
+ if (source !== id) return null;
147
+ const dir = resolvePath(root, opts.dir);
148
+ let files = [];
149
+ try {
150
+ files = readdirSync(dir).filter((f) => f.endsWith(".json"));
151
+ } catch {
152
+ }
153
+ const discovered = files.map((f) => f.replace(/\.json$/, "")).sort();
154
+ const locales = opts.locales?.length ? opts.locales : discovered;
155
+ const defaultLocale = opts.defaultLocale ?? locales[0] ?? "en";
156
+ const imports = locales.map((l, i) => `import _${i} from ${JSON.stringify(resolvePath(dir, l + ".json"))};`).join("\n");
157
+ const messages = `export const messages = {${locales.map((l, i) => `${JSON.stringify(l)}:_${i}`).join(",")}};`;
158
+ return `${imports}
159
+ export const locales = ${JSON.stringify(locales)};
160
+ export const defaultLocale = ${JSON.stringify(defaultLocale)};
161
+ ${messages}
162
+ `;
163
+ }
164
+ };
165
+ }
166
+ async function loadServerEnv(root, mode) {
167
+ const { loadEnv } = await import("vite");
168
+ const env = loadEnv(mode, root, "");
169
+ for (const [k, v] of Object.entries(env)) {
170
+ if (process.env[k] === void 0) process.env[k] = v;
171
+ }
172
+ }
173
+ function i18nTypeBlock(root, i18n) {
174
+ const dir = resolvePath(root, i18n.dir);
175
+ let discovered = [];
176
+ try {
177
+ discovered = readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => f.replace(/\.json$/, "")).sort();
178
+ } catch {
179
+ }
180
+ const locales = i18n.locales?.length ? i18n.locales : discovered;
181
+ const defaultLocale = i18n.defaultLocale ?? locales[0] ?? "en";
182
+ const typed = locales.length > 0 && locales.every((l) => discovered.includes(l));
183
+ if (!typed) {
184
+ return [
185
+ `declare module 'virtual:fluixi-i18n' {`,
186
+ ` export const messages: Record<string, Record<string, unknown>>;`,
187
+ ` export const locales: string[];`,
188
+ ` export const defaultLocale: string;`,
189
+ `}`
190
+ ];
191
+ }
192
+ const srcDir = resolvePath(root, "src");
193
+ const toSpec = (l) => {
194
+ const rel = relativePath(srcDir, resolvePath(dir, l + ".json")).split(/[\\/]/).join("/");
195
+ return rel.startsWith(".") ? rel : "./" + rel;
196
+ };
197
+ const entries = locales.map((l) => ` ${JSON.stringify(l)}: (typeof import(${JSON.stringify(toSpec(l))}))['default'];`);
198
+ const tuple = locales.map((l) => JSON.stringify(l)).join(", ");
199
+ return [
200
+ `declare module 'virtual:fluixi-i18n' {`,
201
+ ` export const messages: {`,
202
+ ...entries,
203
+ ` };`,
204
+ ` export const locales: readonly [${tuple}];`,
205
+ ` export const defaultLocale: ${JSON.stringify(defaultLocale)};`,
206
+ `}`
207
+ ];
208
+ }
209
+ function startTypesPlugin(cfg) {
210
+ let root = process.cwd();
211
+ return {
212
+ name: "fluixi-start-types",
213
+ configResolved(config) {
214
+ root = config.root || root;
215
+ },
216
+ buildStart() {
217
+ const blocks = [];
218
+ if (cfg.i18n) blocks.push(...i18nTypeBlock(root, cfg.i18n));
219
+ if (blocks.length === 0) return;
220
+ const content = `// Auto-generated by @fluixi/start, do not edit.
221
+ ${blocks.join("\n")}
222
+ `;
223
+ const file = resolvePath(root, "src/fluixi-start-env.d.ts");
224
+ try {
225
+ if (!existsSync(file) || readFileSync(file, "utf8") !== content) writeFileSync(file, content);
226
+ } catch {
227
+ }
228
+ }
229
+ };
230
+ }
231
+ async function fluixiPlugins(cfg) {
232
+ const { fluixi, fluixiRoutesPlugin } = await import("@fluixi/core/plugins");
233
+ const { serverFunctionsVitePlugin } = await import("@fluixi/compiler/integrations");
234
+ const plugins = [
235
+ serverFunctionsVitePlugin(),
236
+ fluixiRoutesPlugin({ routesDir: cfg.routesDir, polyfills: false }),
237
+ // The app's component-resolution rules reach the compiler here; without this an SSR
238
+ // app has no way to say where an unimported tag comes from.
239
+ //
240
+ // `fluixi()` and not `createVitePlugin()`: the latter is the bare compiler integration,
241
+ // and the reactive graph: agent, source marks, /__fluixi/graph, is composed on top of
242
+ // it by the former. A start app got no graph at all until this changed.
243
+ // Everything the app declared for the compiler, with `resolve` still usable on its
244
+ // own because it is the one most apps set. A start app never writes
245
+ // `fluixi()` itself, so without this passthrough the plugin's other options
246
+ // (propsDestructure and the rest) had nowhere to be declared.
247
+ fluixi({ ...cfg.vitePlugin ?? {}, ...cfg.resolve ? { resolve: cfg.resolve } : {} })
248
+ ];
249
+ if (cfg.plugins?.length) plugins.push(...cfg.plugins);
250
+ if (cfg.i18n) plugins.push(i18nVitePlugin(cfg.i18n));
251
+ const { apiRoutesVitePlugin } = await import("./api-HD7KU3BS.mjs");
252
+ plugins.push(apiRoutesVitePlugin({ dir: cfg.apiDir }));
253
+ plugins.push(startTypesPlugin(cfg));
254
+ return plugins;
255
+ }
256
+
257
+ // src/adapter.ts
258
+ import { readFile } from "node:fs/promises";
259
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
260
+ import { resolve as resolve2, join, dirname } from "node:path";
261
+ import { pathToFileURL } from "node:url";
262
+ import { createRequire } from "node:module";
263
+
264
+ // src/handler.ts
265
+ function streamDocument(head, body, tail) {
266
+ const enc = new TextEncoder();
267
+ const stream = new ReadableStream({
268
+ async start(controller) {
269
+ try {
270
+ controller.enqueue(enc.encode(head));
271
+ const reader = body.getReader();
272
+ for (; ; ) {
273
+ const { done: done2, value } = await reader.read();
274
+ if (done2) break;
275
+ controller.enqueue(value);
276
+ }
277
+ controller.enqueue(enc.encode(tail));
278
+ controller.close();
279
+ } catch (err) {
280
+ controller.error(err);
281
+ }
282
+ }
283
+ });
284
+ return new Response(stream, {
285
+ status: 200,
286
+ headers: { "content-type": "text/html; charset=utf-8" }
287
+ });
288
+ }
289
+ function createRequestHandler(render) {
290
+ return async (request) => {
291
+ const url = new URL(request.url);
292
+ try {
293
+ const html = await render(url.pathname + url.search, request);
294
+ return new Response(html, {
295
+ status: 200,
296
+ headers: { "content-type": "text/html; charset=utf-8" }
297
+ });
298
+ } catch (e) {
299
+ return new Response(String(e?.stack || e), {
300
+ status: 500,
301
+ headers: { "content-type": "text/plain; charset=utf-8" }
302
+ });
303
+ }
304
+ };
305
+ }
306
+
307
+ // src/middleware.ts
308
+ function normalizeMiddleware(mod) {
309
+ const def = mod?.default ?? mod;
310
+ if (def == null) return [];
311
+ if (Array.isArray(def)) return def.filter((m) => typeof m === "function");
312
+ return typeof def === "function" ? [def] : [];
313
+ }
314
+ function composeMiddleware(middlewares, core) {
315
+ if (middlewares.length === 0) return core;
316
+ return (request) => {
317
+ let lastCalled = -1;
318
+ const dispatch = (i) => {
319
+ if (i <= lastCalled) {
320
+ return Promise.reject(new Error("middleware called next() more than once"));
321
+ }
322
+ lastCalled = i;
323
+ const mw = middlewares[i];
324
+ if (!mw) return Promise.resolve(core(request));
325
+ return Promise.resolve(mw(request, () => dispatch(i + 1)));
326
+ };
327
+ return dispatch(0);
328
+ };
329
+ }
330
+
331
+ // src/handler-core.ts
332
+ function createHandlerFrom({ template, mod, cfg, middleware, preload }) {
333
+ const withPreload = (html, pathname) => preload ? injectPreload(html, preload(pathname)) : html;
334
+ let core;
335
+ if (!cfg.ssr) {
336
+ core = createRequestHandler(async (url) => withPreload(template, url.split("?")[0]));
337
+ } else if (typeof mod.renderStream === "function") {
338
+ core = async (request) => {
339
+ const url = new URL(request.url);
340
+ const { head, tail } = splitTemplate(withPreload(template, url.pathname), cfg.mountId);
341
+ const body = await mod.renderStream(url.pathname + url.search, request);
342
+ return streamDocument(head, body, tail);
343
+ };
344
+ } else {
345
+ const render = mod.render ?? mod.default;
346
+ core = createRequestHandler(
347
+ async (url, request) => (
348
+ // `url` here is `pathname + search`; matching only ever wants the path.
349
+ injectAppAndHead(withPreload(template, url.split("?")[0]), await render(url, request), cfg.mountId)
350
+ )
351
+ );
352
+ }
353
+ if (typeof mod.handleServerFn === "function" && typeof mod.isServerFnRequest === "function") {
354
+ const render = core;
355
+ core = async (request) => mod.isServerFnRequest(request) ? mod.handleServerFn(request) : render(request);
356
+ }
357
+ if (typeof mod.handleApiRequest === "function" && typeof mod.isApiRequest === "function") {
358
+ const next = core;
359
+ core = async (request) => mod.isApiRequest(request) ? mod.handleApiRequest(request) : next(request);
360
+ }
361
+ if (!middleware) return guardHandler(core);
362
+ return guardHandler(composeMiddleware(normalizeMiddleware(middleware), core));
363
+ }
364
+
365
+ // src/preload.ts
366
+ import { matchRoutes } from "@fluixi/core/router";
367
+ function createPreloader(assets) {
368
+ const patterns = Object.keys(assets ?? {});
369
+ if (!patterns.length) return () => [];
370
+ const routes = patterns.map((path) => ({ path, meta: { path } }));
371
+ return (pathname) => {
372
+ const matched = matchRoutes(routes, pathname)?.matched;
373
+ const leaf = matched?.[matched.length - 1];
374
+ const pattern = leaf?.route.meta?.path;
375
+ return pattern ? assets[pattern] ?? [] : [];
376
+ };
377
+ }
378
+
379
+ // src/adapter.ts
380
+ async function createProdHandler(config = {}) {
381
+ const cfg = resolveConfig(config);
382
+ const clientDir = resolve2(cfg.root, "dist/client");
383
+ const template = await readTemplate(clientDir, resolve2(cfg.root, "dist/server"));
384
+ const serverEntry = resolve2(cfg.root, "dist/server", "entry-server.js");
385
+ const mod = existsSync2(serverEntry) ? await import(
386
+ /* @vite-ignore */
387
+ pathToFileURL(serverEntry).href
388
+ ) : {};
389
+ const mwFile = resolve2(cfg.root, "dist/server", "middleware.js");
390
+ const middleware = existsSync2(mwFile) ? await import(
391
+ /* @vite-ignore */
392
+ pathToFileURL(mwFile).href
393
+ ) : void 0;
394
+ return createHandlerFrom({ template, mod, cfg, middleware, preload: await loadPreloader(cfg) });
395
+ }
396
+ async function readTemplate(clientDir, serverDir) {
397
+ const stashed = join(serverDir, "template.html");
398
+ return readFile(existsSync2(stashed) ? stashed : join(clientDir, "index.html"), "utf-8");
399
+ }
400
+ async function loadPreloader(cfg) {
401
+ const file = resolve2(cfg.root, "dist/server", "route-assets.json");
402
+ if (!existsSync2(file)) return void 0;
403
+ try {
404
+ return createPreloader(JSON.parse(await readFile(file, "utf-8")));
405
+ } catch {
406
+ return void 0;
407
+ }
408
+ }
409
+ var GENERATED_SUBPATHS = /* @__PURE__ */ new Set(["./routes", "./server-fns", "./api-routes"]);
410
+ function ssrBuildOptions(cfg) {
411
+ const noExternal = cfg.ssrNoExternal.length ? cfg.ssrNoExternal : void 0;
412
+ const mode = cfg.adapter?.bundle;
413
+ if (mode === "inline") return { noExternal: true };
414
+ if (mode !== "external") return { noExternal };
415
+ const external = frameworkDeps(cfg.root).filter((name) => !cfg.ssrNoExternal.includes(name)).flatMap((name) => withSubpaths(cfg.root, name));
416
+ if (!external.length) {
417
+ console.warn(
418
+ ` fluixi: adapter "${cfg.adapter?.name}" builds an externalized server, but no fluixi or @fluixi/* dependency was found in ${join(cfg.root, "package.json")}, bundling everything.`
419
+ );
420
+ }
421
+ return { noExternal, external: external.length ? external : void 0 };
422
+ }
423
+ function frameworkDeps(root) {
424
+ const pkg = readPackage(join(root, "package.json"));
425
+ if (!pkg) return [];
426
+ const declared = { ...pkg.dependencies, ...pkg.peerDependencies, ...pkg.optionalDependencies };
427
+ return Object.keys(declared).filter((id) => id === "@fluixi/core" || id.startsWith("@fluixi/"));
428
+ }
429
+ function withSubpaths(root, name) {
430
+ const exports = resolvePackage(root, name)?.exports;
431
+ if (!exports || typeof exports !== "object") return [name];
432
+ const ids = [name];
433
+ for (const key of Object.keys(exports)) {
434
+ if (!key.startsWith("./") || key.includes("*")) continue;
435
+ if (key === "./package.json" || GENERATED_SUBPATHS.has(key)) continue;
436
+ ids.push(`${name}/${key.slice(2)}`);
437
+ }
438
+ return ids;
439
+ }
440
+ function resolvePackage(root, name) {
441
+ try {
442
+ return readPackage(createRequire(join(root, "index.js")).resolve(`${name}/package.json`));
443
+ } catch {
444
+ }
445
+ for (let dir = root; ; dir = dirname(dir)) {
446
+ const pkg = readPackage(join(dir, "node_modules", name, "package.json"));
447
+ if (pkg) return pkg;
448
+ if (dirname(dir) === dir) return void 0;
449
+ }
450
+ }
451
+ function readPackage(file) {
452
+ if (!existsSync2(file)) return void 0;
453
+ try {
454
+ return JSON.parse(readFileSync2(file, "utf-8"));
455
+ } catch {
456
+ return void 0;
457
+ }
458
+ }
459
+
460
+ // src/commands/prerender.ts
461
+ import { writeFile, mkdir } from "node:fs/promises";
462
+ import { resolve as resolve3, join as join2, dirname as dirname2 } from "node:path";
463
+
464
+ // src/ui.ts
465
+ var COLOR = process.env.NO_COLOR || process.env.FORCE_COLOR === "0" ? false : (!!process.stdout.isTTY || !!process.env.FORCE_COLOR) && process.env.TERM !== "dumb";
466
+ var sgr = (open, close) => (s) => COLOR ? `\x1B[${open}m${s}\x1B[${close}m` : `${s}`;
467
+ var c = {
468
+ bold: sgr(1, 22),
469
+ dim: sgr(2, 22),
470
+ italic: sgr(3, 23),
471
+ underline: sgr(4, 24),
472
+ red: sgr(31, 39),
473
+ green: sgr(32, 39),
474
+ yellow: sgr(33, 39),
475
+ blue: sgr(34, 39),
476
+ magenta: sgr(35, 39),
477
+ cyan: sgr(36, 39),
478
+ gray: sgr(90, 39)
479
+ };
480
+ var rgb = (r, g, b) => (s) => COLOR ? `\x1B[38;2;${r};${g};${b}m${s}\x1B[39m` : s;
481
+ var BRAND = [
482
+ [34, 211, 238],
483
+ // cyan-400
484
+ [56, 189, 248],
485
+ [129, 140, 248],
486
+ [167, 139, 250],
487
+ [192, 132, 252],
488
+ // violet-400
489
+ [217, 130, 245]
490
+ ];
491
+ function gradient(text) {
492
+ if (!COLOR) return text;
493
+ const chars = [...text];
494
+ return chars.map((ch, i) => {
495
+ const [r, g, b] = BRAND[Math.min(BRAND.length - 1, Math.floor(i / Math.max(1, chars.length - 1) * (BRAND.length - 1)))];
496
+ return rgb(r, g, b)(ch);
497
+ }).join("");
498
+ }
499
+ function wordmark(version) {
500
+ const mark = `${gradient("◆")} ${c.bold(gradient("fluixi"))}`;
501
+ return version ? `${mark} ${c.dim(`v${version}`)}` : mark;
502
+ }
503
+ var SYM = {
504
+ arrow: c.gray("➜"),
505
+ ok: c.green("✓"),
506
+ err: c.red("✗")
507
+ };
508
+ function step(text) {
509
+ process.stdout.write(` ${c.cyan("•")} ${text}
510
+ `);
511
+ }
512
+ function done(text) {
513
+ process.stdout.write(` ${SYM.ok} ${text}
514
+ `);
515
+ }
516
+
517
+ // src/commands/prerender.ts
518
+ function htmlPath(url) {
519
+ const clean = url.split(/[?#]/)[0].replace(/\/+$/, "");
520
+ return clean === "" ? "index.html" : `${clean.replace(/^\//, "")}/index.html`;
521
+ }
522
+ function extractLinks(html) {
523
+ const out = /* @__PURE__ */ new Set();
524
+ for (const m of html.matchAll(/href=["'](\/[^"'#?]*)["']/g)) {
525
+ const href = m[1];
526
+ if (/\.[a-z0-9]+$/i.test(href)) continue;
527
+ out.add(href.replace(/\/+$/, "") || "/");
528
+ }
529
+ return [...out];
530
+ }
531
+ var norm = (u) => {
532
+ const p = (u.startsWith("/") ? u : "/" + u).replace(/\/+$/, "");
533
+ return p || "/";
534
+ };
535
+ async function prerender(config = {}) {
536
+ const cfg = resolveConfig(config);
537
+ if (!cfg.prerender) return [];
538
+ if (!cfg.ssr) {
539
+ process.stdout.write(` ${c.yellow("!")} prerender needs ssr:true — skipped
540
+ `);
541
+ return [];
542
+ }
543
+ step("prerender");
544
+ const clientDir = resolve3(cfg.root, "dist/client");
545
+ const handler = await createProdHandler(config);
546
+ const { routes, crawl } = cfg.prerender;
547
+ const queue = routes.map(norm);
548
+ const seen = /* @__PURE__ */ new Set();
549
+ const written = [];
550
+ while (queue.length) {
551
+ const url = queue.shift();
552
+ if (seen.has(url)) continue;
553
+ seen.add(url);
554
+ let html;
555
+ try {
556
+ const res = await handler(new Request("http://prerender.local" + url));
557
+ if (!res.ok) {
558
+ process.stdout.write(` ${c.yellow("skip")} ${url} ${c.gray("→ " + res.status)}
559
+ `);
560
+ continue;
561
+ }
562
+ if (!(res.headers.get("content-type") || "").includes("html")) continue;
563
+ html = await res.text();
564
+ } catch (e) {
565
+ process.stdout.write(` ${c.yellow("skip")} ${url} ${c.gray("→ " + e.message)}
566
+ `);
567
+ continue;
568
+ }
569
+ const file = join2(clientDir, htmlPath(url));
570
+ await mkdir(dirname2(file), { recursive: true });
571
+ await writeFile(file, html);
572
+ written.push(url);
573
+ if (crawl) {
574
+ for (const link of extractLinks(html)) if (!seen.has(link)) queue.push(link);
575
+ }
576
+ }
577
+ done(`${c.bold("prerendered")} ${c.bold(String(written.length))} page(s) ${c.gray("→ dist/client")}
578
+ `);
579
+ return written;
580
+ }
581
+
582
+ // src/commands/build.ts
583
+ async function build(config = {}) {
584
+ const startedAt = Date.now();
585
+ const cfg = resolveConfig(config);
586
+ await loadServerEnv(cfg.root, "production");
587
+ const { build: viteBuild } = await import("vite");
588
+ const plugins = await fluixiPlugins(cfg);
589
+ const ssr = ssrBuildOptions(cfg);
590
+ process.stdout.write(`
591
+ ${wordmark()} ${c.gray("building for production")}
592
+
593
+ `);
594
+ step("client bundle");
595
+ await viteBuild({
596
+ root: cfg.root,
597
+ plugins,
598
+ logLevel: "warn",
599
+ build: { outDir: "dist/client", ssrManifest: true, emptyOutDir: true }
600
+ });
601
+ const serverEntrySrc = resolve4(cfg.root, cfg.serverEntry);
602
+ if (cfg.ssr || existsSync3(serverEntrySrc)) {
603
+ step(`server bundle${bundleNote(cfg)}`);
604
+ await viteBuild({
605
+ root: cfg.root,
606
+ plugins,
607
+ ssr,
608
+ build: {
609
+ outDir: "dist/server",
610
+ ssr: serverEntrySrc,
611
+ emptyOutDir: true
612
+ }
613
+ });
614
+ }
615
+ const middlewareSrc = resolve4(cfg.root, cfg.middleware);
616
+ if (existsSync3(middlewareSrc)) {
617
+ step("middleware");
618
+ await viteBuild({
619
+ root: cfg.root,
620
+ plugins,
621
+ logLevel: "warn",
622
+ ssr,
623
+ build: {
624
+ outDir: "dist/server",
625
+ ssr: middlewareSrc,
626
+ emptyOutDir: false
627
+ }
628
+ });
629
+ }
630
+ await collectRouteAssets(cfg);
631
+ if (cfg.prerender) {
632
+ await copyFile(
633
+ resolve4(cfg.root, "dist/client", "index.html"),
634
+ resolve4(cfg.root, "dist/server", "template.html")
635
+ );
636
+ process.stdout.write("\n");
637
+ await prerender(config);
638
+ }
639
+ if (cfg.adapter?.build) {
640
+ step(`${cfg.adapter.name} output`);
641
+ await cfg.adapter.build({
642
+ cfg,
643
+ root: cfg.root,
644
+ clientDir: resolve4(cfg.root, "dist/client"),
645
+ serverDir: resolve4(cfg.root, "dist/server"),
646
+ hasServer: existsSync3(resolve4(cfg.root, "dist/server", "entry-server.js")),
647
+ bundleEntry: (source, outFile) => bundleEntry(cfg, plugins, source, outFile)
648
+ });
649
+ }
650
+ const ms = Date.now() - startedAt;
651
+ process.stdout.write("\n");
652
+ done(`${c.bold("built")} in ${c.bold(String(ms))} ms ${c.gray("→ dist/client + dist/server")}
653
+ `);
654
+ }
655
+ async function collectRouteAssets(cfg) {
656
+ const clientDir = resolve4(cfg.root, "dist/client");
657
+ const manifestFile = join3(clientDir, ".vite", "ssr-manifest.json");
658
+ if (!existsSync3(manifestFile)) return;
659
+ const serverDir = resolve4(cfg.root, "dist/server");
660
+ const routesDir = resolve4(cfg.root, cfg.routesDir);
661
+ if (existsSync3(serverDir) && existsSync3(routesDir)) {
662
+ const manifest = JSON.parse(await readFile2(manifestFile, "utf-8"));
663
+ const { scanRoutes } = await import("@fluixi/core/plugins");
664
+ const base = cfg.routesDir.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
665
+ const assets = {};
666
+ const walk = (routes, inherited) => {
667
+ for (const route of routes) {
668
+ const files = [.../* @__PURE__ */ new Set([...inherited, ...manifest[`${base}/${route.filePath}`] ?? []])];
669
+ if (files.length) assets[route.urlPath] = [.../* @__PURE__ */ new Set([...assets[route.urlPath] ?? [], ...files])];
670
+ walk(route.children, files);
671
+ }
672
+ };
673
+ walk(scanRoutes(routesDir), []);
674
+ await writeFile2(join3(serverDir, "route-assets.json"), JSON.stringify(assets, null, 2) + "\n");
675
+ }
676
+ await rm(join3(clientDir, ".vite"), { recursive: true, force: true });
677
+ }
678
+ async function bundleEntry(cfg, plugins, source, outFile) {
679
+ const { build: viteBuild } = await import("vite");
680
+ const dir = dirname3(outFile);
681
+ const temp = join3(dir, `.fluixi-entry-${Date.now()}.js`);
682
+ await mkdir2(dir, { recursive: true });
683
+ await writeFile2(temp, source);
684
+ try {
685
+ await viteBuild({
686
+ root: cfg.root,
687
+ plugins,
688
+ logLevel: "warn",
689
+ // Nothing may be left to resolve: these run as an uploaded file, not an install.
690
+ ssr: { noExternal: true },
691
+ build: {
692
+ outDir: dir,
693
+ emptyOutDir: false,
694
+ ssr: temp,
695
+ rollupOptions: {
696
+ output: {
697
+ entryFileNames: basename(outFile),
698
+ // One file, no chunks. A lazy route would otherwise be split out beside
699
+ // the entry: and for Cloudflare that directory is the published one, so
700
+ // every server chunk would be a public download.
701
+ inlineDynamicImports: true
702
+ }
703
+ }
704
+ }
705
+ });
706
+ } finally {
707
+ await rm(temp, { force: true });
708
+ }
709
+ }
710
+ function bundleNote(cfg) {
711
+ const adapter = cfg.adapter;
712
+ if (!adapter?.bundle) return "";
713
+ return c.gray(` (${adapter.name} · ${adapter.bundle})`);
714
+ }
715
+ export {
716
+ build
717
+ };