@fluixi/start 0.1.0-alpha.72 → 0.1.0-alpha.74

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 (64) hide show
  1. package/dist/adapter.cjs +748 -0
  2. package/dist/adapter.mjs +717 -0
  3. package/dist/adapters/entry.cjs +122 -0
  4. package/dist/adapters/entry.mjs +95 -0
  5. package/dist/adapters/platforms.cjs +311 -0
  6. package/dist/adapters/platforms.mjs +284 -0
  7. package/dist/api-RH6E5UTH.mjs +114 -0
  8. package/dist/api.cjs +145 -0
  9. package/dist/api.mjs +116 -0
  10. package/dist/commands/api-23MJFSYL.mjs +17 -0
  11. package/dist/commands/api-4IYNQRKG.mjs +16 -0
  12. package/dist/commands/api-RH6E5UTH.mjs +114 -0
  13. package/dist/commands/build.cjs +870 -0
  14. package/dist/commands/build.mjs +710 -0
  15. package/dist/commands/chunk-NS5GR2GX.mjs +115 -0
  16. package/dist/commands/chunk-OCPCK4ZJ.mjs +117 -0
  17. package/dist/commands/dev.cjs +663 -0
  18. package/dist/commands/dev.mjs +505 -0
  19. package/dist/commands/index.cjs +1138 -0
  20. package/dist/commands/index.mjs +975 -0
  21. package/dist/commands/prerender.cjs +402 -0
  22. package/dist/commands/prerender.mjs +375 -0
  23. package/dist/commands/start.cjs +461 -0
  24. package/dist/commands/start.mjs +426 -0
  25. package/dist/config.cjs +144 -0
  26. package/dist/config.mjs +108 -0
  27. package/dist/di.cjs +200 -0
  28. package/dist/di.mjs +169 -0
  29. package/dist/document.cjs +115 -0
  30. package/dist/document.mjs +86 -0
  31. package/dist/generated-api.cjs +43 -0
  32. package/dist/generated-api.mjs +18 -0
  33. package/dist/generated-server-fns.cjs +43 -0
  34. package/dist/generated-server-fns.mjs +18 -0
  35. package/dist/handler-core.cjs +211 -0
  36. package/dist/handler-core.mjs +185 -0
  37. package/dist/handler.cjs +181 -0
  38. package/dist/handler.mjs +150 -0
  39. package/dist/head.cjs +25 -0
  40. package/dist/head.mjs +4 -0
  41. package/dist/image.cjs +146 -0
  42. package/dist/image.mjs +120 -0
  43. package/dist/index.cjs +1036 -0
  44. package/dist/index.mjs +968 -0
  45. package/dist/interceptors.cjs +71 -0
  46. package/dist/interceptors.mjs +42 -0
  47. package/dist/internal.cjs +388 -0
  48. package/dist/internal.d.ts.map +1 -1
  49. package/dist/internal.js +6 -2
  50. package/dist/internal.mjs +218 -0
  51. package/dist/middleware.cjs +58 -0
  52. package/dist/middleware.mjs +31 -0
  53. package/dist/preload.cjs +42 -0
  54. package/dist/preload.mjs +18 -0
  55. package/dist/router.cjs +25 -0
  56. package/dist/router.mjs +4 -0
  57. package/dist/server-fn-setup.cjs +46 -0
  58. package/dist/server-fn-setup.mjs +22 -0
  59. package/dist/server-fn.cjs +109 -0
  60. package/dist/server-fn.mjs +79 -0
  61. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  62. package/dist/ui.cjs +123 -0
  63. package/dist/ui.mjs +93 -0
  64. package/package.json +45 -25
@@ -0,0 +1,1138 @@
1
+ /*! @fluixi/start v0.1.0-alpha.74 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
16
+ var __copyProps = (to, from, except, desc) => {
17
+ if (from && typeof from === "object" || typeof from === "function") {
18
+ for (let key of __getOwnPropNames(from))
19
+ if (!__hasOwnProp.call(to, key) && key !== except)
20
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
21
+ }
22
+ return to;
23
+ };
24
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
25
+ // If the importer is in node compatibility mode or this is not an ESM
26
+ // file that has been converted to a CommonJS file using a Babel-
27
+ // compatible transform (i.e. "__esModule" has not been set), then set
28
+ // "default" to the CommonJS "module.exports" for node compatibility.
29
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
30
+ mod
31
+ ));
32
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
33
+
34
+ // src/api.ts
35
+ var api_exports = {};
36
+ __export(api_exports, {
37
+ API_VIRTUAL_MODULE: () => API_VIRTUAL_MODULE,
38
+ apiRoutesVitePlugin: () => apiRoutesVitePlugin,
39
+ derivePattern: () => derivePattern,
40
+ handleApi: () => handleApi,
41
+ isApiPath: () => isApiPath,
42
+ matchApiRoutes: () => matchApiRoutes
43
+ });
44
+ function derivePattern(rel, prefix = "/api") {
45
+ const segs = rel.split("/").filter(Boolean);
46
+ if (segs[segs.length - 1] === "index") segs.pop();
47
+ const keys = [];
48
+ let rank = 0;
49
+ const parts = segs.map((seg) => {
50
+ const catchAll = seg.match(/^\[\.\.\.(.+)\]$/);
51
+ if (catchAll) {
52
+ keys.push(catchAll[1]);
53
+ return "(.+)";
54
+ }
55
+ const param = seg.match(/^\[(.+)\]$/);
56
+ if (param) {
57
+ keys.push(param[1]);
58
+ rank += 1;
59
+ return "([^/]+)";
60
+ }
61
+ rank += 10;
62
+ return seg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
63
+ });
64
+ return { source: `^${prefix}${parts.length ? "/" + parts.join("/") : ""}/?$`, keys, rank };
65
+ }
66
+ function isApiPath(pathname, prefix = "/api") {
67
+ return pathname === prefix || pathname.startsWith(prefix + "/");
68
+ }
69
+ function matchApiRoutes(routes, pathname) {
70
+ for (const r of [...routes].sort((a, b) => b.rank - a.rank)) {
71
+ const m = new RegExp(r.source).exec(pathname);
72
+ if (!m) continue;
73
+ const params = {};
74
+ r.keys.forEach((k, i) => params[k] = decodeURIComponent(m[i + 1] ?? ""));
75
+ return { mod: r.mod, params };
76
+ }
77
+ return null;
78
+ }
79
+ async function handleApi(routes, request, prefix = "/api") {
80
+ const pathname = new URL(request.url).pathname;
81
+ const match = matchApiRoutes(routes, pathname);
82
+ if (!match) return new Response("Not Found", { status: 404 });
83
+ const fn = match.mod[request.method];
84
+ if (typeof fn !== "function") {
85
+ const allow = METHODS.filter((m) => typeof match.mod[m] === "function").join(", ");
86
+ return new Response("Method Not Allowed", { status: 405, headers: allow ? { allow } : void 0 });
87
+ }
88
+ const out = await fn(request, { params: match.params });
89
+ if (out instanceof Response) return out;
90
+ return new Response(JSON.stringify(out ?? null), { headers: { "content-type": "application/json; charset=utf-8" } });
91
+ }
92
+ function scanFiles(dir, base = dir) {
93
+ let out = [];
94
+ let entries = [];
95
+ try {
96
+ entries = (0, import_node_fs.readdirSync)(dir);
97
+ } catch {
98
+ return out;
99
+ }
100
+ for (const name of entries) {
101
+ const full = (0, import_node_path2.join)(dir, name);
102
+ if ((0, import_node_fs.statSync)(full).isDirectory()) out = out.concat(scanFiles(full, base));
103
+ else if (/\.(ts|tsx|js|mjs)$/.test(name) && !/\.(spec|test)\./.test(name)) out.push(full);
104
+ }
105
+ return out;
106
+ }
107
+ function apiRoutesVitePlugin(opts) {
108
+ let root = process.cwd();
109
+ const prefix = opts.prefix ?? "/api";
110
+ const id = "\0" + API_VMOD;
111
+ return {
112
+ name: "fluixi-api",
113
+ // Must beat node resolution: '@fluixi/start/api-routes' is a real file in this package,
114
+ // so without `pre` Vite resolves the typed stub and the generated table never loads —
115
+ // the app builds and every API route 404s.
116
+ enforce: "pre",
117
+ configResolved(config) {
118
+ root = config.root || root;
119
+ },
120
+ resolveId(source) {
121
+ return source === API_VMOD || source === "@fluixi/start/api-routes" ? id : null;
122
+ },
123
+ load(source) {
124
+ if (source !== id) return null;
125
+ const dir = (0, import_node_path2.resolve)(root, opts.dir);
126
+ const files = scanFiles(dir);
127
+ const imports = [];
128
+ const entries = [];
129
+ files.forEach((file, i) => {
130
+ const rel = (0, import_node_path2.relative)(dir, file).replace(/\.(ts|tsx|js|mjs)$/, "").split(/[\\/]/).join("/");
131
+ const { source: src, keys, rank } = derivePattern(rel, prefix);
132
+ imports.push(`import * as _${i} from ${JSON.stringify(file)};`);
133
+ entries.push(`{ source: ${JSON.stringify(src)}, keys: ${JSON.stringify(keys)}, rank: ${rank}, mod: _${i} }`);
134
+ });
135
+ return `import { isApiPath, handleApi } from '@fluixi/start/api';
136
+ ${imports.join("\n")}
137
+ const routes = [${entries.join(",")}];
138
+ export const isApiRequest = (request) => isApiPath(new URL(request.url).pathname, ${JSON.stringify(prefix)});
139
+ export const handleApiRequest = (request) => handleApi(routes, request, ${JSON.stringify(prefix)});
140
+ `;
141
+ }
142
+ };
143
+ }
144
+ var import_node_fs, import_node_path2, API_VMOD, METHODS, API_VIRTUAL_MODULE;
145
+ var init_api = __esm({
146
+ "src/api.ts"() {
147
+ "use strict";
148
+ import_node_fs = require("node:fs");
149
+ import_node_path2 = require("node:path");
150
+ API_VMOD = "virtual:fluixi-api";
151
+ METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"];
152
+ API_VIRTUAL_MODULE = API_VMOD;
153
+ }
154
+ });
155
+
156
+ // src/commands/index.ts
157
+ var index_exports = {};
158
+ __export(index_exports, {
159
+ build: () => build,
160
+ dev: () => dev,
161
+ prerender: () => prerender,
162
+ start: () => start
163
+ });
164
+ module.exports = __toCommonJS(index_exports);
165
+
166
+ // src/commands/dev.ts
167
+ var import_node_http2 = require("node:http");
168
+ var import_node_fs4 = require("node:fs");
169
+ var import_node_path5 = require("node:path");
170
+
171
+ // src/config.ts
172
+ var import_node_path = require("node:path");
173
+ function resolveConfig(config = {}) {
174
+ const root = (0, import_node_path.resolve)(config.root ?? process.cwd());
175
+ return {
176
+ root,
177
+ ssr: config.ssr ?? true,
178
+ port: config.port ?? 3e3,
179
+ serverEntry: config.serverEntry ?? "src/entry-server.ts",
180
+ clientEntry: config.clientEntry ?? "src/entry-client.ts",
181
+ template: config.template ?? "index.html",
182
+ mountId: config.mountId ?? "root",
183
+ routesDir: config.routesDir ?? "src/routes",
184
+ apiDir: config.apiDir ?? "src/api",
185
+ middleware: config.middleware ?? "src/middleware.ts",
186
+ ssrNoExternal: config.ssrNoExternal ?? [],
187
+ resolve: config.resolve ?? [],
188
+ adapter: config.adapter,
189
+ i18n: resolveI18n(config.i18n),
190
+ prerender: resolvePrerender(config.prerender)
191
+ };
192
+ }
193
+ function resolvePrerender(prerender2) {
194
+ if (!prerender2) return false;
195
+ const opts = typeof prerender2 === "object" ? prerender2 : {};
196
+ return {
197
+ routes: opts.routes?.length ? opts.routes : ["/"],
198
+ crawl: opts.crawl ?? true
199
+ };
200
+ }
201
+ function resolveI18n(i18n) {
202
+ if (i18n === false) return false;
203
+ const opts = typeof i18n === "object" ? i18n : {};
204
+ return {
205
+ dir: opts.dir ?? "src/i18n",
206
+ defaultLocale: opts.defaultLocale,
207
+ locales: opts.locales
208
+ };
209
+ }
210
+
211
+ // src/internal.ts
212
+ var import_node_fs2 = require("node:fs");
213
+ var import_node_path3 = require("node:path");
214
+
215
+ // src/document.ts
216
+ var import_head = require("@fluixi/head");
217
+ function injectApp(template, appHtml, mountId = "root") {
218
+ const mount = new RegExp(`<div id=["']${mountId}["']\\s*>\\s*</div>`);
219
+ if (mount.test(template)) {
220
+ return template.replace(mount, `<div id="${mountId}">${appHtml}</div>`);
221
+ }
222
+ if (template.includes("<!--ssr-outlet-->")) {
223
+ return template.replace("<!--ssr-outlet-->", appHtml);
224
+ }
225
+ return template.replace("</body>", `<div id="${mountId}">${appHtml}</div></body>`);
226
+ }
227
+ function splitTemplate(template, mountId = "root") {
228
+ const mount = new RegExp(`<div id=["']${mountId}["']\\s*>\\s*</div>`);
229
+ const m = template.match(mount);
230
+ if (m && m.index !== void 0) {
231
+ return {
232
+ head: template.slice(0, m.index) + `<div id="${mountId}">`,
233
+ tail: `</div>` + template.slice(m.index + m[0].length)
234
+ };
235
+ }
236
+ const outlet = template.indexOf("<!--ssr-outlet-->");
237
+ if (outlet !== -1) {
238
+ return {
239
+ head: template.slice(0, outlet),
240
+ tail: template.slice(outlet + "<!--ssr-outlet-->".length)
241
+ };
242
+ }
243
+ const body = template.indexOf("</body>");
244
+ if (body !== -1) {
245
+ return {
246
+ head: template.slice(0, body) + `<div id="${mountId}">`,
247
+ tail: `</div>` + template.slice(body)
248
+ };
249
+ }
250
+ return { head: template, tail: "" };
251
+ }
252
+ function injectPreload(template, files) {
253
+ const tags = files.filter((file) => !template.includes(file)).map(
254
+ (file) => file.endsWith(".css") ? `<link rel="stylesheet" href="${file}">` : (
255
+ // `modulepreload` also fetches the chunk's own static imports.
256
+ `<link rel="modulepreload" href="${file}">`
257
+ )
258
+ );
259
+ if (tags.length === 0) return template;
260
+ const head = template.indexOf("</head>");
261
+ if (head === -1) return template;
262
+ return template.slice(0, head) + tags.join("") + template.slice(head);
263
+ }
264
+ function setHtmlAttr(html, name, value) {
265
+ const existing = new RegExp(`(<html\\b[^>]*?)\\s${name}="[^"]*"`, "i");
266
+ if (existing.test(html)) return html.replace(existing, `$1 ${name}="${value}"`);
267
+ return html.replace(/<html\b/i, `<html ${name}="${value}"`);
268
+ }
269
+ function injectAppAndHead(template, rendered, mountId = "root") {
270
+ const { head, body } = (0, import_head.extractHeadMarker)(rendered);
271
+ let html = injectApp(template, body, mountId);
272
+ if (head) {
273
+ if (head.headHtml) {
274
+ if (/<title[\s>]/i.test(head.headHtml)) html = html.replace(/<title>[\s\S]*?<\/title>/i, "");
275
+ html = html.replace("</head>", `${head.headHtml}</head>`);
276
+ }
277
+ for (const [k, v] of Object.entries(head.htmlAttrs)) html = setHtmlAttr(html, k, v);
278
+ }
279
+ return html;
280
+ }
281
+ function guardHandler(handler, onError) {
282
+ return async (request) => {
283
+ try {
284
+ return await handler(request);
285
+ } catch (e) {
286
+ onError?.(e);
287
+ return new Response(String(e?.stack || e), {
288
+ status: 500,
289
+ headers: { "content-type": "text/plain; charset=utf-8" }
290
+ });
291
+ }
292
+ };
293
+ }
294
+
295
+ // src/internal.ts
296
+ var SERVER_FNS_VMOD = "virtual:fluixi-server-fns";
297
+ var I18N_VMOD = "virtual:fluixi-i18n";
298
+ function i18nVitePlugin(opts) {
299
+ let root = process.cwd();
300
+ const id = "\0" + I18N_VMOD;
301
+ return {
302
+ name: "fluixi-i18n",
303
+ configResolved(config) {
304
+ root = config.root || root;
305
+ },
306
+ resolveId(source) {
307
+ return source === I18N_VMOD ? id : null;
308
+ },
309
+ load(source) {
310
+ if (source !== id) return null;
311
+ const dir = (0, import_node_path3.resolve)(root, opts.dir);
312
+ let files = [];
313
+ try {
314
+ files = (0, import_node_fs2.readdirSync)(dir).filter((f) => f.endsWith(".json"));
315
+ } catch {
316
+ }
317
+ const discovered = files.map((f) => f.replace(/\.json$/, "")).sort();
318
+ const locales = opts.locales?.length ? opts.locales : discovered;
319
+ const defaultLocale = opts.defaultLocale ?? locales[0] ?? "en";
320
+ const imports = locales.map((l, i) => `import _${i} from ${JSON.stringify((0, import_node_path3.resolve)(dir, l + ".json"))};`).join("\n");
321
+ const messages = `export const messages = {${locales.map((l, i) => `${JSON.stringify(l)}:_${i}`).join(",")}};`;
322
+ return `${imports}
323
+ export const locales = ${JSON.stringify(locales)};
324
+ export const defaultLocale = ${JSON.stringify(defaultLocale)};
325
+ ${messages}
326
+ `;
327
+ }
328
+ };
329
+ }
330
+ async function loadServerEnv(root, mode) {
331
+ const { loadEnv } = await import("vite");
332
+ const env = loadEnv(mode, root, "");
333
+ for (const [k, v] of Object.entries(env)) {
334
+ if (process.env[k] === void 0) process.env[k] = v;
335
+ }
336
+ }
337
+ function i18nTypeBlock(root, i18n) {
338
+ const dir = (0, import_node_path3.resolve)(root, i18n.dir);
339
+ let discovered = [];
340
+ try {
341
+ discovered = (0, import_node_fs2.readdirSync)(dir).filter((f) => f.endsWith(".json")).map((f) => f.replace(/\.json$/, "")).sort();
342
+ } catch {
343
+ }
344
+ const locales = i18n.locales?.length ? i18n.locales : discovered;
345
+ const defaultLocale = i18n.defaultLocale ?? locales[0] ?? "en";
346
+ const typed = locales.length > 0 && locales.every((l) => discovered.includes(l));
347
+ if (!typed) {
348
+ return [
349
+ `declare module 'virtual:fluixi-i18n' {`,
350
+ ` export const messages: Record<string, Record<string, unknown>>;`,
351
+ ` export const locales: string[];`,
352
+ ` export const defaultLocale: string;`,
353
+ `}`
354
+ ];
355
+ }
356
+ const srcDir = (0, import_node_path3.resolve)(root, "src");
357
+ const toSpec = (l) => {
358
+ const rel = (0, import_node_path3.relative)(srcDir, (0, import_node_path3.resolve)(dir, l + ".json")).split(/[\\/]/).join("/");
359
+ return rel.startsWith(".") ? rel : "./" + rel;
360
+ };
361
+ const entries = locales.map((l) => ` ${JSON.stringify(l)}: (typeof import(${JSON.stringify(toSpec(l))}))['default'];`);
362
+ const tuple = locales.map((l) => JSON.stringify(l)).join(", ");
363
+ return [
364
+ `declare module 'virtual:fluixi-i18n' {`,
365
+ ` export const messages: {`,
366
+ ...entries,
367
+ ` };`,
368
+ ` export const locales: readonly [${tuple}];`,
369
+ ` export const defaultLocale: ${JSON.stringify(defaultLocale)};`,
370
+ `}`
371
+ ];
372
+ }
373
+ function startTypesPlugin(cfg) {
374
+ let root = process.cwd();
375
+ return {
376
+ name: "fluixi-start-types",
377
+ configResolved(config) {
378
+ root = config.root || root;
379
+ },
380
+ buildStart() {
381
+ const blocks = [];
382
+ if (cfg.i18n) blocks.push(...i18nTypeBlock(root, cfg.i18n));
383
+ if (blocks.length === 0) return;
384
+ const content = `// Auto-generated by @fluixi/start — do not edit.
385
+ ${blocks.join("\n")}
386
+ `;
387
+ const file = (0, import_node_path3.resolve)(root, "src/fluixi-start-env.d.ts");
388
+ try {
389
+ if (!(0, import_node_fs2.existsSync)(file) || (0, import_node_fs2.readFileSync)(file, "utf8") !== content) (0, import_node_fs2.writeFileSync)(file, content);
390
+ } catch {
391
+ }
392
+ }
393
+ };
394
+ }
395
+ async function fluixiPlugins(cfg) {
396
+ const { fluixi, fluixiRoutesPlugin } = await import("@fluixi/core/plugins");
397
+ const { serverFunctionsVitePlugin } = await import("@fluixi/compiler/integrations");
398
+ const plugins = [
399
+ serverFunctionsVitePlugin(),
400
+ fluixiRoutesPlugin({ routesDir: cfg.routesDir, polyfills: false }),
401
+ // The app's component-resolution rules reach the compiler here; without this an SSR
402
+ // app has no way to say where an unimported tag comes from.
403
+ //
404
+ // `fluixi()` and not `createVitePlugin()`: the latter is the bare compiler integration,
405
+ // and the reactive graph — agent, source marks, /__fluixi/graph — is composed on top of
406
+ // it by the former. A start app got no graph at all until this changed.
407
+ fluixi({ resolve: cfg.resolve })
408
+ ];
409
+ if (cfg.i18n) plugins.push(i18nVitePlugin(cfg.i18n));
410
+ const { apiRoutesVitePlugin: apiRoutesVitePlugin2 } = await Promise.resolve().then(() => (init_api(), api_exports));
411
+ plugins.push(apiRoutesVitePlugin2({ dir: cfg.apiDir }));
412
+ plugins.push(startTypesPlugin(cfg));
413
+ return plugins;
414
+ }
415
+ var _guardsInstalled = false;
416
+ function installCrashGuards() {
417
+ if (_guardsInstalled) return;
418
+ _guardsInstalled = true;
419
+ process.on(
420
+ "unhandledRejection",
421
+ (reason) => console.warn("[fluixi] unhandled rejection (backend likely down) —", reason?.message || reason)
422
+ );
423
+ process.on(
424
+ "uncaughtException",
425
+ (err) => console.warn("[fluixi] uncaught exception —", err?.message || err)
426
+ );
427
+ }
428
+
429
+ // src/handler.ts
430
+ var import_node_http = require("node:http");
431
+ var import_promises = require("node:fs/promises");
432
+ var import_node_fs3 = require("node:fs");
433
+ var import_node_stream = require("node:stream");
434
+ var import_node_path4 = require("node:path");
435
+ function streamDocument(head, body, tail) {
436
+ const enc = new TextEncoder();
437
+ const stream = new ReadableStream({
438
+ async start(controller) {
439
+ try {
440
+ controller.enqueue(enc.encode(head));
441
+ const reader = body.getReader();
442
+ for (; ; ) {
443
+ const { done: done2, value } = await reader.read();
444
+ if (done2) break;
445
+ controller.enqueue(value);
446
+ }
447
+ controller.enqueue(enc.encode(tail));
448
+ controller.close();
449
+ } catch (err) {
450
+ controller.error(err);
451
+ }
452
+ }
453
+ });
454
+ return new Response(stream, {
455
+ status: 200,
456
+ headers: { "content-type": "text/html; charset=utf-8" }
457
+ });
458
+ }
459
+ function createRequestHandler(render) {
460
+ return async (request) => {
461
+ const url = new URL(request.url);
462
+ try {
463
+ const html = await render(url.pathname + url.search, request);
464
+ return new Response(html, {
465
+ status: 200,
466
+ headers: { "content-type": "text/html; charset=utf-8" }
467
+ });
468
+ } catch (e) {
469
+ return new Response(String(e?.stack || e), {
470
+ status: 500,
471
+ headers: { "content-type": "text/plain; charset=utf-8" }
472
+ });
473
+ }
474
+ };
475
+ }
476
+ function nodeToRequest(req) {
477
+ const host = req.headers.host ?? "localhost";
478
+ const url = `http://${host}${req.url ?? "/"}`;
479
+ const headers = new Headers();
480
+ for (const [k, v] of Object.entries(req.headers)) {
481
+ if (Array.isArray(v)) v.forEach((x) => headers.append(k, x));
482
+ else if (v != null) headers.set(k, v);
483
+ }
484
+ const method = req.method ?? "GET";
485
+ const init = { method, headers };
486
+ if (method !== "GET" && method !== "HEAD") {
487
+ init.body = req;
488
+ init.duplex = "half";
489
+ }
490
+ return new Request(url, init);
491
+ }
492
+ async function sendResponse(res, response) {
493
+ res.statusCode = response.status;
494
+ response.headers.forEach((value, key) => res.setHeader(key, value));
495
+ const body = response.body;
496
+ if (!body) {
497
+ res.end(await response.text().catch(() => ""));
498
+ return;
499
+ }
500
+ const reader = body.getReader();
501
+ try {
502
+ for (; ; ) {
503
+ const { done: done2, value } = await reader.read();
504
+ if (done2) break;
505
+ res.write(value);
506
+ }
507
+ } finally {
508
+ res.end();
509
+ }
510
+ }
511
+ function toNodeHandler(handler) {
512
+ return async (req, res) => {
513
+ const response = await handler(nodeToRequest(req));
514
+ await sendResponse(res, response);
515
+ };
516
+ }
517
+ function serveNode(handler, opts = {}) {
518
+ const port = opts.port ?? 3e3;
519
+ const node = toNodeHandler(handler);
520
+ const server = (0, import_node_http.createServer)((req, res) => {
521
+ node(req, res).catch((e) => {
522
+ res.statusCode = 500;
523
+ res.end(String(e?.stack || e));
524
+ });
525
+ });
526
+ server.listen(
527
+ port,
528
+ () => opts.onListen ? opts.onListen(port) : console.log(` fluixi → http://localhost:${port}`)
529
+ );
530
+ return server;
531
+ }
532
+ var MIME = {
533
+ ".js": "text/javascript",
534
+ ".mjs": "text/javascript",
535
+ ".css": "text/css",
536
+ ".html": "text/html",
537
+ ".json": "application/json",
538
+ ".svg": "image/svg+xml",
539
+ ".png": "image/png",
540
+ ".jpg": "image/jpeg",
541
+ ".jpeg": "image/jpeg",
542
+ ".webp": "image/webp",
543
+ ".ico": "image/x-icon",
544
+ ".woff": "font/woff",
545
+ ".woff2": "font/woff2",
546
+ ".map": "application/json"
547
+ };
548
+ function withStaticFiles(handler, dir) {
549
+ return async (request) => {
550
+ const { pathname } = new URL(request.url);
551
+ if (pathname !== "/" && (0, import_node_path4.extname)(pathname)) {
552
+ const file = (0, import_node_path4.join)(dir, decodeURIComponent(pathname));
553
+ try {
554
+ const s = await (0, import_promises.stat)(file);
555
+ if (s.isFile()) {
556
+ const body = import_node_stream.Readable.toWeb((0, import_node_fs3.createReadStream)(file));
557
+ return new Response(body, {
558
+ headers: {
559
+ "content-type": MIME[(0, import_node_path4.extname)(file).toLowerCase()] ?? "application/octet-stream",
560
+ "content-length": String(s.size)
561
+ }
562
+ });
563
+ }
564
+ } catch {
565
+ }
566
+ }
567
+ return handler(request);
568
+ };
569
+ }
570
+
571
+ // src/middleware.ts
572
+ function normalizeMiddleware(mod) {
573
+ const def = mod?.default ?? mod;
574
+ if (def == null) return [];
575
+ if (Array.isArray(def)) return def.filter((m) => typeof m === "function");
576
+ return typeof def === "function" ? [def] : [];
577
+ }
578
+ function composeMiddleware(middlewares, core) {
579
+ if (middlewares.length === 0) return core;
580
+ return (request) => {
581
+ let lastCalled = -1;
582
+ const dispatch = (i) => {
583
+ if (i <= lastCalled) {
584
+ return Promise.reject(new Error("middleware called next() more than once"));
585
+ }
586
+ lastCalled = i;
587
+ const mw = middlewares[i];
588
+ if (!mw) return Promise.resolve(core(request));
589
+ return Promise.resolve(mw(request, () => dispatch(i + 1)));
590
+ };
591
+ return dispatch(0);
592
+ };
593
+ }
594
+
595
+ // src/commands/dev.ts
596
+ init_api();
597
+
598
+ // src/ui.ts
599
+ var import_node_os = require("node:os");
600
+ var COLOR = process.env.NO_COLOR || process.env.FORCE_COLOR === "0" ? false : (!!process.stdout.isTTY || !!process.env.FORCE_COLOR) && process.env.TERM !== "dumb";
601
+ var sgr = (open, close) => (s) => COLOR ? `\x1B[${open}m${s}\x1B[${close}m` : `${s}`;
602
+ var c = {
603
+ bold: sgr(1, 22),
604
+ dim: sgr(2, 22),
605
+ italic: sgr(3, 23),
606
+ underline: sgr(4, 24),
607
+ red: sgr(31, 39),
608
+ green: sgr(32, 39),
609
+ yellow: sgr(33, 39),
610
+ blue: sgr(34, 39),
611
+ magenta: sgr(35, 39),
612
+ cyan: sgr(36, 39),
613
+ gray: sgr(90, 39)
614
+ };
615
+ var rgb = (r, g, b) => (s) => COLOR ? `\x1B[38;2;${r};${g};${b}m${s}\x1B[39m` : s;
616
+ var BRAND = [
617
+ [34, 211, 238],
618
+ // cyan-400
619
+ [56, 189, 248],
620
+ [129, 140, 248],
621
+ [167, 139, 250],
622
+ [192, 132, 252],
623
+ // violet-400
624
+ [217, 130, 245]
625
+ ];
626
+ function gradient(text) {
627
+ if (!COLOR) return text;
628
+ const chars = [...text];
629
+ return chars.map((ch, i) => {
630
+ const [r, g, b] = BRAND[Math.min(BRAND.length - 1, Math.floor(i / Math.max(1, chars.length - 1) * (BRAND.length - 1)))];
631
+ return rgb(r, g, b)(ch);
632
+ }).join("");
633
+ }
634
+ function wordmark(version) {
635
+ const mark = `${gradient("◆")} ${c.bold(gradient("fluixi"))}`;
636
+ return version ? `${mark} ${c.dim(`v${version}`)}` : mark;
637
+ }
638
+ var SYM = {
639
+ arrow: c.gray("➜"),
640
+ ok: c.green("✓"),
641
+ err: c.red("✗")
642
+ };
643
+ function networkAddress() {
644
+ for (const list of Object.values((0, import_node_os.networkInterfaces)())) {
645
+ for (const net of list ?? []) {
646
+ const family = typeof net.family === "string" ? net.family : `IPv${net.family}`;
647
+ if (family === "IPv4" && !net.internal) return net.address;
648
+ }
649
+ }
650
+ return null;
651
+ }
652
+ function devReady(opts) {
653
+ const ms = Date.now() - opts.startedAt;
654
+ const local = `http://localhost:${opts.port}/`;
655
+ const net = networkAddress();
656
+ const link = (u) => c.cyan(c.underline(u));
657
+ const label = (s) => c.gray(s.padEnd(8));
658
+ const lines = [
659
+ "",
660
+ ` ${wordmark(opts.version)} ${c.gray(`dev ready in ${c.bold(String(ms))} ms`)}`,
661
+ "",
662
+ ` ${SYM.arrow} ${label("Local")} ${link(local)}`,
663
+ net ? ` ${SYM.arrow} ${label("Network")} ${link(`http://${net}:${opts.port}/`)}` : ` ${SYM.arrow} ${label("Network")} ${c.dim("not exposed")}`,
664
+ ""
665
+ ];
666
+ process.stdout.write(lines.join("\n") + "\n");
667
+ }
668
+ function step(text) {
669
+ process.stdout.write(` ${c.cyan("•")} ${text}
670
+ `);
671
+ }
672
+ function done(text) {
673
+ process.stdout.write(` ${SYM.ok} ${text}
674
+ `);
675
+ }
676
+
677
+ // src/commands/dev.ts
678
+ var STYLE_FILE = /\.(css|scss|sass|less|styl|stylus|pcss|postcss)(\?|$)/;
679
+ async function renderedStyles(vite, entry) {
680
+ const seen = /* @__PURE__ */ new Set();
681
+ const found = [];
682
+ const visit = (mod) => {
683
+ if (!mod || seen.has(mod.url)) return;
684
+ seen.add(mod.url);
685
+ if (STYLE_FILE.test(mod.url)) found.push(mod.url);
686
+ for (const dep of mod.importedModules ?? []) visit(dep);
687
+ };
688
+ try {
689
+ visit(await vite.moduleGraph.getModuleByUrl("/" + entry, true));
690
+ } catch {
691
+ return [];
692
+ }
693
+ return found;
694
+ }
695
+ function injectStyles(html, urls) {
696
+ if (urls.length === 0) return html;
697
+ const links = urls.map((url) => `<link rel="stylesheet" href="${url}${url.includes("?") ? "&" : "?"}direct">`).join("\n ");
698
+ return html.includes("</head>") ? html.replace("</head>", ` ${links}
699
+ </head>`) : html;
700
+ }
701
+ async function dev(config = {}) {
702
+ const startedAt = Date.now();
703
+ const cfg = resolveConfig(config);
704
+ installCrashGuards();
705
+ await loadServerEnv(cfg.root, "development");
706
+ const { createServer } = await import("vite");
707
+ const template = (0, import_node_fs4.readFileSync)((0, import_node_path5.resolve)(cfg.root, cfg.template), "utf-8");
708
+ let vite;
709
+ const renderHandler = guardHandler(async (request) => {
710
+ const url = new URL(request.url);
711
+ const path = url.pathname + url.search;
712
+ if (url.pathname === "/_server") {
713
+ const sf = await vite.ssrLoadModule(SERVER_FNS_VMOD);
714
+ if (sf.isServerFnRequest(request)) return sf.handleServerFn(request);
715
+ }
716
+ if (isApiPath(url.pathname)) {
717
+ const api = await vite.ssrLoadModule(API_VIRTUAL_MODULE);
718
+ if (api.isApiRequest(request)) return api.handleApiRequest(request);
719
+ }
720
+ if (!cfg.ssr) {
721
+ const html2 = await vite.transformIndexHtml(path, template);
722
+ return new Response(html2, { status: 200, headers: { "content-type": "text/html; charset=utf-8" } });
723
+ }
724
+ const mod = await vite.ssrLoadModule("/" + cfg.serverEntry);
725
+ if (typeof mod.renderStream === "function") {
726
+ const shell = await vite.transformIndexHtml(
727
+ path,
728
+ injectStyles(template, await renderedStyles(vite, cfg.serverEntry))
729
+ );
730
+ const { head, tail } = splitTemplate(shell, cfg.mountId);
731
+ return streamDocument(head, await mod.renderStream(path, request), tail);
732
+ }
733
+ const render = mod.render ?? mod.default;
734
+ const appHtml = await render(path, request);
735
+ const styled = injectStyles(
736
+ injectAppAndHead(template, appHtml, cfg.mountId),
737
+ await renderedStyles(vite, cfg.serverEntry)
738
+ );
739
+ const html = await vite.transformIndexHtml(path, styled);
740
+ return new Response(html, { status: 200, headers: { "content-type": "text/html; charset=utf-8" } });
741
+ }, (e) => vite.ssrFixStacktrace?.(e));
742
+ const hasMiddleware = (0, import_node_fs4.existsSync)((0, import_node_path5.resolve)(cfg.root, cfg.middleware));
743
+ const handler = hasMiddleware ? guardHandler(async (request) => {
744
+ const mwMod = await vite.ssrLoadModule("/" + cfg.middleware);
745
+ return composeMiddleware(normalizeMiddleware(mwMod), renderHandler)(request);
746
+ }, (e) => vite.ssrFixStacktrace?.(e)) : renderHandler;
747
+ const nodeHandler = toNodeHandler(handler);
748
+ const http = (0, import_node_http2.createServer)((req, res) => {
749
+ vite.middlewares(req, res, () => nodeHandler(req, res));
750
+ });
751
+ vite = await createServer({
752
+ root: cfg.root,
753
+ plugins: await fluixiPlugins(cfg),
754
+ // file routing + JSX/SSR transform (fluixi)
755
+ server: { middlewareMode: true, hmr: { server: http } },
756
+ appType: "custom",
757
+ ssr: cfg.ssrNoExternal.length ? { noExternal: cfg.ssrNoExternal } : void 0
758
+ });
759
+ http.listen(cfg.port, () => devReady({ port: cfg.port, startedAt }));
760
+ }
761
+
762
+ // src/commands/build.ts
763
+ var import_node_fs6 = require("node:fs");
764
+ var import_promises4 = require("node:fs/promises");
765
+ var import_node_path8 = require("node:path");
766
+
767
+ // src/adapter.ts
768
+ var import_promises2 = require("node:fs/promises");
769
+ var import_node_fs5 = require("node:fs");
770
+ var import_node_path6 = require("node:path");
771
+ var import_node_url = require("node:url");
772
+ var import_node_module = require("node:module");
773
+
774
+ // src/handler-core.ts
775
+ function createHandlerFrom({ template, mod, cfg, middleware, preload }) {
776
+ const withPreload = (html, pathname) => preload ? injectPreload(html, preload(pathname)) : html;
777
+ let core;
778
+ if (!cfg.ssr) {
779
+ core = createRequestHandler(async (url) => withPreload(template, url.split("?")[0]));
780
+ } else if (typeof mod.renderStream === "function") {
781
+ core = async (request) => {
782
+ const url = new URL(request.url);
783
+ const { head, tail } = splitTemplate(withPreload(template, url.pathname), cfg.mountId);
784
+ const body = await mod.renderStream(url.pathname + url.search, request);
785
+ return streamDocument(head, body, tail);
786
+ };
787
+ } else {
788
+ const render = mod.render ?? mod.default;
789
+ core = createRequestHandler(
790
+ async (url, request) => (
791
+ // `url` here is `pathname + search`; matching only ever wants the path.
792
+ injectAppAndHead(withPreload(template, url.split("?")[0]), await render(url, request), cfg.mountId)
793
+ )
794
+ );
795
+ }
796
+ if (typeof mod.handleServerFn === "function" && typeof mod.isServerFnRequest === "function") {
797
+ const render = core;
798
+ core = async (request) => mod.isServerFnRequest(request) ? mod.handleServerFn(request) : render(request);
799
+ }
800
+ if (typeof mod.handleApiRequest === "function" && typeof mod.isApiRequest === "function") {
801
+ const next = core;
802
+ core = async (request) => mod.isApiRequest(request) ? mod.handleApiRequest(request) : next(request);
803
+ }
804
+ if (!middleware) return guardHandler(core);
805
+ return guardHandler(composeMiddleware(normalizeMiddleware(middleware), core));
806
+ }
807
+
808
+ // src/preload.ts
809
+ var import_router = require("@fluixi/core/router");
810
+ function createPreloader(assets) {
811
+ const patterns = Object.keys(assets ?? {});
812
+ if (!patterns.length) return () => [];
813
+ const routes = patterns.map((path) => ({ path, meta: { path } }));
814
+ return (pathname) => {
815
+ const matched = (0, import_router.matchRoutes)(routes, pathname)?.matched;
816
+ const leaf = matched?.[matched.length - 1];
817
+ const pattern = leaf?.route.meta?.path;
818
+ return pattern ? assets[pattern] ?? [] : [];
819
+ };
820
+ }
821
+
822
+ // src/adapter.ts
823
+ async function createProdHandler(config = {}) {
824
+ const cfg = resolveConfig(config);
825
+ const clientDir = (0, import_node_path6.resolve)(cfg.root, "dist/client");
826
+ const template = await readTemplate(clientDir, (0, import_node_path6.resolve)(cfg.root, "dist/server"));
827
+ const serverEntry = (0, import_node_path6.resolve)(cfg.root, "dist/server", "entry-server.js");
828
+ const mod = (0, import_node_fs5.existsSync)(serverEntry) ? await import(
829
+ /* @vite-ignore */
830
+ (0, import_node_url.pathToFileURL)(serverEntry).href
831
+ ) : {};
832
+ const mwFile = (0, import_node_path6.resolve)(cfg.root, "dist/server", "middleware.js");
833
+ const middleware = (0, import_node_fs5.existsSync)(mwFile) ? await import(
834
+ /* @vite-ignore */
835
+ (0, import_node_url.pathToFileURL)(mwFile).href
836
+ ) : void 0;
837
+ return createHandlerFrom({ template, mod, cfg, middleware, preload: await loadPreloader(cfg) });
838
+ }
839
+ async function readTemplate(clientDir, serverDir) {
840
+ const stashed = (0, import_node_path6.join)(serverDir, "template.html");
841
+ return (0, import_promises2.readFile)((0, import_node_fs5.existsSync)(stashed) ? stashed : (0, import_node_path6.join)(clientDir, "index.html"), "utf-8");
842
+ }
843
+ async function loadPreloader(cfg) {
844
+ const file = (0, import_node_path6.resolve)(cfg.root, "dist/server", "route-assets.json");
845
+ if (!(0, import_node_fs5.existsSync)(file)) return void 0;
846
+ try {
847
+ return createPreloader(JSON.parse(await (0, import_promises2.readFile)(file, "utf-8")));
848
+ } catch {
849
+ return void 0;
850
+ }
851
+ }
852
+ var nodeAdapter = {
853
+ name: "node",
854
+ // A Node server runs from the app directory, so node_modules is right there: keep the
855
+ // framework as imports instead of copying it into the bundle.
856
+ bundle: "external",
857
+ async serve({ handler, cfg, clientDir }) {
858
+ serveNode(withStaticFiles(handler, clientDir), {
859
+ port: cfg.port,
860
+ onListen: (p) => console.log(` fluixi start (node) → http://localhost:${p}`)
861
+ });
862
+ }
863
+ };
864
+ var GENERATED_SUBPATHS = /* @__PURE__ */ new Set(["./routes", "./server-fns", "./api-routes"]);
865
+ function ssrBuildOptions(cfg) {
866
+ const noExternal = cfg.ssrNoExternal.length ? cfg.ssrNoExternal : void 0;
867
+ const mode = cfg.adapter?.bundle;
868
+ if (mode === "inline") return { noExternal: true };
869
+ if (mode !== "external") return { noExternal };
870
+ const external = frameworkDeps(cfg.root).filter((name) => !cfg.ssrNoExternal.includes(name)).flatMap((name) => withSubpaths(cfg.root, name));
871
+ if (!external.length) {
872
+ console.warn(
873
+ ` fluixi: adapter "${cfg.adapter?.name}" builds an externalized server, but no fluixi or @fluixi/* dependency was found in ${(0, import_node_path6.join)(cfg.root, "package.json")} — bundling everything.`
874
+ );
875
+ }
876
+ return { noExternal, external: external.length ? external : void 0 };
877
+ }
878
+ function frameworkDeps(root) {
879
+ const pkg = readPackage((0, import_node_path6.join)(root, "package.json"));
880
+ if (!pkg) return [];
881
+ const declared = { ...pkg.dependencies, ...pkg.peerDependencies, ...pkg.optionalDependencies };
882
+ return Object.keys(declared).filter((id) => id === "@fluixi/core" || id.startsWith("@fluixi/"));
883
+ }
884
+ function withSubpaths(root, name) {
885
+ const exports2 = resolvePackage(root, name)?.exports;
886
+ if (!exports2 || typeof exports2 !== "object") return [name];
887
+ const ids = [name];
888
+ for (const key of Object.keys(exports2)) {
889
+ if (!key.startsWith("./") || key.includes("*")) continue;
890
+ if (key === "./package.json" || GENERATED_SUBPATHS.has(key)) continue;
891
+ ids.push(`${name}/${key.slice(2)}`);
892
+ }
893
+ return ids;
894
+ }
895
+ function resolvePackage(root, name) {
896
+ try {
897
+ return readPackage((0, import_node_module.createRequire)((0, import_node_path6.join)(root, "index.js")).resolve(`${name}/package.json`));
898
+ } catch {
899
+ }
900
+ for (let dir = root; ; dir = (0, import_node_path6.dirname)(dir)) {
901
+ const pkg = readPackage((0, import_node_path6.join)(dir, "node_modules", name, "package.json"));
902
+ if (pkg) return pkg;
903
+ if ((0, import_node_path6.dirname)(dir) === dir) return void 0;
904
+ }
905
+ }
906
+ function readPackage(file) {
907
+ if (!(0, import_node_fs5.existsSync)(file)) return void 0;
908
+ try {
909
+ return JSON.parse((0, import_node_fs5.readFileSync)(file, "utf-8"));
910
+ } catch {
911
+ return void 0;
912
+ }
913
+ }
914
+
915
+ // src/commands/prerender.ts
916
+ var import_promises3 = require("node:fs/promises");
917
+ var import_node_path7 = require("node:path");
918
+ function htmlPath(url) {
919
+ const clean = url.split(/[?#]/)[0].replace(/\/+$/, "");
920
+ return clean === "" ? "index.html" : `${clean.replace(/^\//, "")}/index.html`;
921
+ }
922
+ function extractLinks(html) {
923
+ const out = /* @__PURE__ */ new Set();
924
+ for (const m of html.matchAll(/href=["'](\/[^"'#?]*)["']/g)) {
925
+ const href = m[1];
926
+ if (/\.[a-z0-9]+$/i.test(href)) continue;
927
+ out.add(href.replace(/\/+$/, "") || "/");
928
+ }
929
+ return [...out];
930
+ }
931
+ var norm = (u) => {
932
+ const p = (u.startsWith("/") ? u : "/" + u).replace(/\/+$/, "");
933
+ return p || "/";
934
+ };
935
+ async function prerender(config = {}) {
936
+ const cfg = resolveConfig(config);
937
+ if (!cfg.prerender) return [];
938
+ if (!cfg.ssr) {
939
+ process.stdout.write(` ${c.yellow("!")} prerender needs ssr:true — skipped
940
+ `);
941
+ return [];
942
+ }
943
+ step("prerender");
944
+ const clientDir = (0, import_node_path7.resolve)(cfg.root, "dist/client");
945
+ const handler = await createProdHandler(config);
946
+ const { routes, crawl } = cfg.prerender;
947
+ const queue = routes.map(norm);
948
+ const seen = /* @__PURE__ */ new Set();
949
+ const written = [];
950
+ while (queue.length) {
951
+ const url = queue.shift();
952
+ if (seen.has(url)) continue;
953
+ seen.add(url);
954
+ let html;
955
+ try {
956
+ const res = await handler(new Request("http://prerender.local" + url));
957
+ if (!res.ok) {
958
+ process.stdout.write(` ${c.yellow("skip")} ${url} ${c.gray("→ " + res.status)}
959
+ `);
960
+ continue;
961
+ }
962
+ if (!(res.headers.get("content-type") || "").includes("html")) continue;
963
+ html = await res.text();
964
+ } catch (e) {
965
+ process.stdout.write(` ${c.yellow("skip")} ${url} ${c.gray("→ " + e.message)}
966
+ `);
967
+ continue;
968
+ }
969
+ const file = (0, import_node_path7.join)(clientDir, htmlPath(url));
970
+ await (0, import_promises3.mkdir)((0, import_node_path7.dirname)(file), { recursive: true });
971
+ await (0, import_promises3.writeFile)(file, html);
972
+ written.push(url);
973
+ if (crawl) {
974
+ for (const link of extractLinks(html)) if (!seen.has(link)) queue.push(link);
975
+ }
976
+ }
977
+ done(`${c.bold("prerendered")} ${c.bold(String(written.length))} page(s) ${c.gray("→ dist/client")}
978
+ `);
979
+ return written;
980
+ }
981
+
982
+ // src/commands/build.ts
983
+ async function build(config = {}) {
984
+ const startedAt = Date.now();
985
+ const cfg = resolveConfig(config);
986
+ await loadServerEnv(cfg.root, "production");
987
+ const { build: viteBuild } = await import("vite");
988
+ const plugins = await fluixiPlugins(cfg);
989
+ const ssr = ssrBuildOptions(cfg);
990
+ process.stdout.write(`
991
+ ${wordmark()} ${c.gray("building for production")}
992
+
993
+ `);
994
+ step("client bundle");
995
+ await viteBuild({
996
+ root: cfg.root,
997
+ plugins,
998
+ logLevel: "warn",
999
+ build: { outDir: "dist/client", ssrManifest: true, emptyOutDir: true }
1000
+ });
1001
+ const serverEntrySrc = (0, import_node_path8.resolve)(cfg.root, cfg.serverEntry);
1002
+ if (cfg.ssr || (0, import_node_fs6.existsSync)(serverEntrySrc)) {
1003
+ step(`server bundle${bundleNote(cfg)}`);
1004
+ await viteBuild({
1005
+ root: cfg.root,
1006
+ plugins,
1007
+ ssr,
1008
+ build: {
1009
+ outDir: "dist/server",
1010
+ ssr: serverEntrySrc,
1011
+ emptyOutDir: true
1012
+ }
1013
+ });
1014
+ }
1015
+ const middlewareSrc = (0, import_node_path8.resolve)(cfg.root, cfg.middleware);
1016
+ if ((0, import_node_fs6.existsSync)(middlewareSrc)) {
1017
+ step("middleware");
1018
+ await viteBuild({
1019
+ root: cfg.root,
1020
+ plugins,
1021
+ logLevel: "warn",
1022
+ ssr,
1023
+ build: {
1024
+ outDir: "dist/server",
1025
+ ssr: middlewareSrc,
1026
+ emptyOutDir: false
1027
+ }
1028
+ });
1029
+ }
1030
+ await collectRouteAssets(cfg);
1031
+ if (cfg.prerender) {
1032
+ await (0, import_promises4.copyFile)(
1033
+ (0, import_node_path8.resolve)(cfg.root, "dist/client", "index.html"),
1034
+ (0, import_node_path8.resolve)(cfg.root, "dist/server", "template.html")
1035
+ );
1036
+ process.stdout.write("\n");
1037
+ await prerender(config);
1038
+ }
1039
+ if (cfg.adapter?.build) {
1040
+ step(`${cfg.adapter.name} output`);
1041
+ await cfg.adapter.build({
1042
+ cfg,
1043
+ root: cfg.root,
1044
+ clientDir: (0, import_node_path8.resolve)(cfg.root, "dist/client"),
1045
+ serverDir: (0, import_node_path8.resolve)(cfg.root, "dist/server"),
1046
+ hasServer: (0, import_node_fs6.existsSync)((0, import_node_path8.resolve)(cfg.root, "dist/server", "entry-server.js")),
1047
+ bundleEntry: (source, outFile) => bundleEntry(cfg, plugins, source, outFile)
1048
+ });
1049
+ }
1050
+ const ms = Date.now() - startedAt;
1051
+ process.stdout.write("\n");
1052
+ done(`${c.bold("built")} in ${c.bold(String(ms))} ms ${c.gray("→ dist/client + dist/server")}
1053
+ `);
1054
+ }
1055
+ async function collectRouteAssets(cfg) {
1056
+ const clientDir = (0, import_node_path8.resolve)(cfg.root, "dist/client");
1057
+ const manifestFile = (0, import_node_path8.join)(clientDir, ".vite", "ssr-manifest.json");
1058
+ if (!(0, import_node_fs6.existsSync)(manifestFile)) return;
1059
+ const serverDir = (0, import_node_path8.resolve)(cfg.root, "dist/server");
1060
+ const routesDir = (0, import_node_path8.resolve)(cfg.root, cfg.routesDir);
1061
+ if ((0, import_node_fs6.existsSync)(serverDir) && (0, import_node_fs6.existsSync)(routesDir)) {
1062
+ const manifest = JSON.parse(await (0, import_promises4.readFile)(manifestFile, "utf-8"));
1063
+ const { scanRoutes } = await import("@fluixi/core/plugins");
1064
+ const base = cfg.routesDir.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
1065
+ const assets = {};
1066
+ const walk = (routes, inherited) => {
1067
+ for (const route of routes) {
1068
+ const files = [.../* @__PURE__ */ new Set([...inherited, ...manifest[`${base}/${route.filePath}`] ?? []])];
1069
+ if (files.length) assets[route.urlPath] = [.../* @__PURE__ */ new Set([...assets[route.urlPath] ?? [], ...files])];
1070
+ walk(route.children, files);
1071
+ }
1072
+ };
1073
+ walk(scanRoutes(routesDir), []);
1074
+ await (0, import_promises4.writeFile)((0, import_node_path8.join)(serverDir, "route-assets.json"), JSON.stringify(assets, null, 2) + "\n");
1075
+ }
1076
+ await (0, import_promises4.rm)((0, import_node_path8.join)(clientDir, ".vite"), { recursive: true, force: true });
1077
+ }
1078
+ async function bundleEntry(cfg, plugins, source, outFile) {
1079
+ const { build: viteBuild } = await import("vite");
1080
+ const dir = (0, import_node_path8.dirname)(outFile);
1081
+ const temp = (0, import_node_path8.join)(dir, `.fluixi-entry-${Date.now()}.js`);
1082
+ await (0, import_promises4.mkdir)(dir, { recursive: true });
1083
+ await (0, import_promises4.writeFile)(temp, source);
1084
+ try {
1085
+ await viteBuild({
1086
+ root: cfg.root,
1087
+ plugins,
1088
+ logLevel: "warn",
1089
+ // Nothing may be left to resolve: these run as an uploaded file, not an install.
1090
+ ssr: { noExternal: true },
1091
+ build: {
1092
+ outDir: dir,
1093
+ emptyOutDir: false,
1094
+ ssr: temp,
1095
+ rollupOptions: {
1096
+ output: {
1097
+ entryFileNames: (0, import_node_path8.basename)(outFile),
1098
+ // One file, no chunks. A lazy route would otherwise be split out beside
1099
+ // the entry — and for Cloudflare that directory is the published one, so
1100
+ // every server chunk would be a public download.
1101
+ inlineDynamicImports: true
1102
+ }
1103
+ }
1104
+ }
1105
+ });
1106
+ } finally {
1107
+ await (0, import_promises4.rm)(temp, { force: true });
1108
+ }
1109
+ }
1110
+ function bundleNote(cfg) {
1111
+ const adapter = cfg.adapter;
1112
+ if (!adapter?.bundle) return "";
1113
+ return c.gray(` (${adapter.name} · ${adapter.bundle})`);
1114
+ }
1115
+
1116
+ // src/commands/start.ts
1117
+ var import_node_path9 = require("node:path");
1118
+ async function start(config = {}) {
1119
+ const cfg = resolveConfig(config);
1120
+ installCrashGuards();
1121
+ await loadServerEnv(cfg.root, "production");
1122
+ const clientDir = (0, import_node_path9.resolve)(cfg.root, "dist/client");
1123
+ const handler = await createProdHandler(cfg);
1124
+ const adapter = cfg.adapter ?? nodeAdapter;
1125
+ const served = await adapter.serve({ handler, cfg, clientDir });
1126
+ if (served && typeof served === "object" && "fetch" in served) {
1127
+ console.log(
1128
+ ` fluixi: the "${adapter.name}" adapter exports a fetch handler — there is no server to start. Deploy dist/ and let the platform invoke it, or run \`fluixi start\` with a filesystem adapter (nodeAdapter).`
1129
+ );
1130
+ }
1131
+ }
1132
+ // Annotate the CommonJS export names for ESM import in node:
1133
+ 0 && (module.exports = {
1134
+ build,
1135
+ dev,
1136
+ prerender,
1137
+ start
1138
+ });