@devlusoft/devix 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +216 -0
  3. package/bin/devix.js +2 -0
  4. package/dist/cli/build.d.ts +1 -0
  5. package/dist/cli/build.js +286 -0
  6. package/dist/cli/build.js.map +7 -0
  7. package/dist/cli/dev.d.ts +1 -0
  8. package/dist/cli/dev.js +361 -0
  9. package/dist/cli/dev.js.map +7 -0
  10. package/dist/cli/generate.d.ts +1 -0
  11. package/dist/cli/generate.js +389 -0
  12. package/dist/cli/generate.js.map +7 -0
  13. package/dist/cli/index.d.ts +2 -0
  14. package/dist/cli/index.js +649 -0
  15. package/dist/cli/index.js.map +7 -0
  16. package/dist/cli/start.d.ts +1 -0
  17. package/dist/cli/start.js +78 -0
  18. package/dist/cli/start.js.map +7 -0
  19. package/dist/config.d.ts +21 -0
  20. package/dist/config.js +17 -0
  21. package/dist/config.js.map +7 -0
  22. package/dist/runtime/api-context.d.ts +20 -0
  23. package/dist/runtime/api-context.js +18 -0
  24. package/dist/runtime/api-context.js.map +7 -0
  25. package/dist/runtime/client-router.d.ts +13 -0
  26. package/dist/runtime/client-router.js +59 -0
  27. package/dist/runtime/client-router.js.map +7 -0
  28. package/dist/runtime/context.d.ts +27 -0
  29. package/dist/runtime/context.js +15 -0
  30. package/dist/runtime/context.js.map +7 -0
  31. package/dist/runtime/error-boundary.d.ts +19 -0
  32. package/dist/runtime/error-boundary.js +37 -0
  33. package/dist/runtime/error-boundary.js.map +7 -0
  34. package/dist/runtime/head.d.ts +3 -0
  35. package/dist/runtime/head.js +69 -0
  36. package/dist/runtime/head.js.map +7 -0
  37. package/dist/runtime/index.d.ts +5 -0
  38. package/dist/runtime/index.js +300 -0
  39. package/dist/runtime/index.js.map +7 -0
  40. package/dist/runtime/link.d.ts +8 -0
  41. package/dist/runtime/link.js +43 -0
  42. package/dist/runtime/link.js.map +7 -0
  43. package/dist/runtime/metadata.d.ts +10 -0
  44. package/dist/runtime/metadata.js +22 -0
  45. package/dist/runtime/metadata.js.map +7 -0
  46. package/dist/runtime/router-provider.d.ts +22 -0
  47. package/dist/runtime/router-provider.js +259 -0
  48. package/dist/runtime/router-provider.js.map +7 -0
  49. package/dist/server/api-router.d.ts +21 -0
  50. package/dist/server/api-router.js +64 -0
  51. package/dist/server/api-router.js.map +7 -0
  52. package/dist/server/api.d.ts +2 -0
  53. package/dist/server/api.js +123 -0
  54. package/dist/server/api.js.map +7 -0
  55. package/dist/server/collect-css.d.ts +2 -0
  56. package/dist/server/collect-css.js +15 -0
  57. package/dist/server/collect-css.js.map +7 -0
  58. package/dist/server/index.d.ts +6 -0
  59. package/dist/server/index.js +133 -0
  60. package/dist/server/index.js.map +7 -0
  61. package/dist/server/pages-router.d.ts +21 -0
  62. package/dist/server/pages-router.js +64 -0
  63. package/dist/server/pages-router.js.map +7 -0
  64. package/dist/server/render.d.ts +34 -0
  65. package/dist/server/render.js +306 -0
  66. package/dist/server/render.js.map +7 -0
  67. package/dist/server/routes.d.ts +11 -0
  68. package/dist/server/routes.js +42 -0
  69. package/dist/server/routes.js.map +7 -0
  70. package/dist/server/types.d.ts +49 -0
  71. package/dist/server/types.js +1 -0
  72. package/dist/server/types.js.map +7 -0
  73. package/dist/types.d.ts +35 -0
  74. package/dist/types.js +1 -0
  75. package/dist/types.js.map +7 -0
  76. package/dist/utils/async.d.ts +1 -0
  77. package/dist/utils/async.js +14 -0
  78. package/dist/utils/async.js.map +7 -0
  79. package/dist/utils/banner.d.ts +1 -0
  80. package/dist/utils/banner.js +34 -0
  81. package/dist/utils/banner.js.map +7 -0
  82. package/dist/utils/duration.d.ts +1 -0
  83. package/dist/utils/duration.js +22 -0
  84. package/dist/utils/duration.js.map +7 -0
  85. package/dist/utils/html.d.ts +2 -0
  86. package/dist/utils/html.js +12 -0
  87. package/dist/utils/html.js.map +7 -0
  88. package/dist/utils/patterns.d.ts +1 -0
  89. package/dist/utils/patterns.js +8 -0
  90. package/dist/utils/patterns.js.map +7 -0
  91. package/dist/vite/codegen/api.d.ts +6 -0
  92. package/dist/vite/codegen/api.js +23 -0
  93. package/dist/vite/codegen/api.js.map +7 -0
  94. package/dist/vite/codegen/client-routes.d.ts +6 -0
  95. package/dist/vite/codegen/client-routes.js +36 -0
  96. package/dist/vite/codegen/client-routes.js.map +7 -0
  97. package/dist/vite/codegen/context.d.ts +1 -0
  98. package/dist/vite/codegen/context.js +10 -0
  99. package/dist/vite/codegen/context.js.map +7 -0
  100. package/dist/vite/codegen/entry-client.d.ts +5 -0
  101. package/dist/vite/codegen/entry-client.js +64 -0
  102. package/dist/vite/codegen/entry-client.js.map +7 -0
  103. package/dist/vite/codegen/render.d.ts +6 -0
  104. package/dist/vite/codegen/render.js +31 -0
  105. package/dist/vite/codegen/render.js.map +7 -0
  106. package/dist/vite/index.d.ts +3 -0
  107. package/dist/vite/index.js +225 -0
  108. package/dist/vite/index.js.map +7 -0
  109. package/package.json +77 -0
@@ -0,0 +1,649 @@
1
+ #!/usr/bin/env node
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+
7
+ // src/vite/codegen/entry-client.ts
8
+ function generateEntryClient({ cssUrls }) {
9
+ const cssImports = cssUrls.map((u) => `import '${u}'`).join("\n");
10
+ return `
11
+ ${cssImports}
12
+ import "@vitejs/plugin-react/preamble"
13
+ import React from "react"
14
+ import {hydrateRoot, createRoot} from 'react-dom/client'
15
+ import {matchClientRoute, loadErrorPage, getDefaultErrorPage} from 'virtual:devix/client-routes'
16
+ import {RouterProvider} from '@devlusoft/devix'
17
+
18
+ const root = document.getElementById('devix-root')
19
+
20
+ if (!window.__DEVIX__) {
21
+ const ErrorPage = getDefaultErrorPage()
22
+ createRoot(root).render(React.createElement(ErrorPage, {statusCode: 500, message: 'Server error'}))
23
+ } else {
24
+ const {metadata, viewport, clientEntry} = window.__DEVIX__
25
+ const loaderData = window.__LOADER_DATA__
26
+ const layoutsData = window.__LAYOUTS_DATA__ ?? []
27
+
28
+ const matched = matchClientRoute(window.location.pathname)
29
+
30
+ if (matched) {
31
+ const [pageMod, ...layoutMods] = await Promise.all([
32
+ matched.load(),
33
+ ...matched.loadLayouts.map(l => l()),
34
+ ])
35
+ hydrateRoot(
36
+ root,
37
+ React.createElement(RouterProvider, {
38
+ clientEntry,
39
+ initialData: loaderData,
40
+ initialParams: matched.params,
41
+ initialPage: pageMod.default,
42
+ initialLayouts: layoutMods.map(m => m.default),
43
+ initialLayoutsData: layoutsData,
44
+ initialMeta: metadata,
45
+ initialViewport: viewport,
46
+ })
47
+ )
48
+ } else {
49
+ const ErrorPage = await loadErrorPage() ?? getDefaultErrorPage()
50
+ createRoot(root).render(
51
+ React.createElement(RouterProvider, {
52
+ clientEntry,
53
+ initialData: null,
54
+ initialParams: {},
55
+ initialPage: () => null,
56
+ initialLayouts: [],
57
+ initialLayoutsData: [],
58
+ initialMeta: null,
59
+ initialError: {statusCode: 404, message: 'Not found'},
60
+ initialErrorPage: ErrorPage,
61
+ })
62
+ )
63
+ }
64
+ }
65
+ `;
66
+ }
67
+ var init_entry_client = __esm({
68
+ "src/vite/codegen/entry-client.ts"() {
69
+ "use strict";
70
+ }
71
+ });
72
+
73
+ // src/vite/codegen/client-routes.ts
74
+ function generateClientRoutes({ pagesDir, matcherPath }) {
75
+ return `
76
+ import React from 'react'
77
+ import { createMatcher } from '${matcherPath}'
78
+ const pageFiles = import.meta.glob(['/${pagesDir}/**/*.tsx', '!**/error.tsx', '!**/layout.tsx'])
79
+ const layoutFiles = import.meta.glob('/${pagesDir}/**/layout.tsx')
80
+ const errorFiles = import.meta.glob('/${pagesDir}/**/error.tsx')
81
+
82
+ export const matchClientRoute = createMatcher(pageFiles, layoutFiles)
83
+
84
+ export async function loadErrorPage() {
85
+ const key = Object.keys(errorFiles)[0]
86
+ if (!key) return null
87
+ const mod = await errorFiles[key]()
88
+ return mod?.default ?? null
89
+ }
90
+
91
+ export function getDefaultErrorPage() {
92
+ return function DefaultError({ statusCode, message }) {
93
+ return React.createElement('main', {
94
+ style: { minHeight: '100dvh', display: 'flex', flexDirection: 'column',
95
+ alignItems: 'center', justifyContent: 'center', gap: '8px',
96
+ fontFamily: 'system-ui, sans-serif' }
97
+ },
98
+ React.createElement('h1', {style: {fontSize: '4rem', fontWeight: 700}}, statusCode),
99
+ React.createElement('p', {style: {color: '#666'}}, message ?? 'An unexpected error occurred'),
100
+ )
101
+ }
102
+ }
103
+ `;
104
+ }
105
+ var init_client_routes = __esm({
106
+ "src/vite/codegen/client-routes.ts"() {
107
+ "use strict";
108
+ }
109
+ });
110
+
111
+ // src/vite/codegen/render.ts
112
+ function generateRender({ pagesDir, renderPath }) {
113
+ return `
114
+ import { render as _render, runLoader as _runLoader, getStaticRoutes as _getStaticRoutes } from '${renderPath}'
115
+
116
+ const _pages = import.meta.glob(['/${pagesDir}/**/*.tsx', '!**/error.tsx', '!**/layout.tsx'])
117
+ const _layouts = import.meta.glob('/${pagesDir}/**/layout.tsx')
118
+
119
+ const _glob = {
120
+ pages: _pages,
121
+ layouts: _layouts,
122
+ pagesDir: '/${pagesDir}',
123
+ }
124
+
125
+ export function render(url, request, options) {
126
+ return _render(url, request, _glob, options)
127
+ }
128
+
129
+ export function runLoader(url, request, options) {
130
+ return _runLoader(url, request, _glob, options)
131
+ }
132
+
133
+ export function getStaticRoutes() {
134
+ return _getStaticRoutes(_glob)
135
+ }
136
+ `;
137
+ }
138
+ var init_render = __esm({
139
+ "src/vite/codegen/render.ts"() {
140
+ "use strict";
141
+ }
142
+ });
143
+
144
+ // src/vite/codegen/api.ts
145
+ function generateApi({ apiPath, appDir }) {
146
+ return `
147
+ import { handleApiRequest as _handleApiRequest } from '${apiPath}'
148
+
149
+ const _routes = import.meta.glob(['/${appDir}/api/**/*.ts', '!**/middleware.ts'])
150
+ const _middlewares = import.meta.glob('/${appDir}/api/**/middleware.ts')
151
+
152
+ const _glob = {
153
+ routes: _routes,
154
+ middlewares: _middlewares,
155
+ apiDir: '/${appDir}/api',
156
+ }
157
+
158
+ export function handleApiRequest(url, request) {
159
+ return _handleApiRequest(url, request, _glob)
160
+ }
161
+ `;
162
+ }
163
+ var init_api = __esm({
164
+ "src/vite/codegen/api.ts"() {
165
+ "use strict";
166
+ }
167
+ });
168
+
169
+ // src/utils/patterns.ts
170
+ var init_patterns = __esm({
171
+ "src/utils/patterns.ts"() {
172
+ "use strict";
173
+ }
174
+ });
175
+
176
+ // src/server/pages-router.ts
177
+ function invalidatePagesCache() {
178
+ cache = null;
179
+ }
180
+ var cache;
181
+ var init_pages_router = __esm({
182
+ "src/server/pages-router.ts"() {
183
+ "use strict";
184
+ init_patterns();
185
+ cache = null;
186
+ }
187
+ });
188
+
189
+ // src/server/api-router.ts
190
+ function invalidateApiCache() {
191
+ cache2 = null;
192
+ }
193
+ var cache2;
194
+ var init_api_router = __esm({
195
+ "src/server/api-router.ts"() {
196
+ "use strict";
197
+ init_patterns();
198
+ cache2 = null;
199
+ }
200
+ });
201
+
202
+ // src/vite/codegen/context.ts
203
+ function generateContext() {
204
+ return `
205
+ export {RouterContext} from '@devlusoft/devix/runtime/context'
206
+ `;
207
+ }
208
+ var init_context = __esm({
209
+ "src/vite/codegen/context.ts"() {
210
+ "use strict";
211
+ }
212
+ });
213
+
214
+ // src/vite/index.ts
215
+ import { mergeConfig } from "vite";
216
+ import react from "@vitejs/plugin-react";
217
+ import { fileURLToPath } from "node:url";
218
+ import { dirname, resolve } from "node:path";
219
+ function devix(config3) {
220
+ const appDir = config3.appDir ?? "app";
221
+ const pagesDir = `${appDir}/pages`;
222
+ const cssUrls = (config3.css ?? []).map((u) => u.startsWith("/") ? u : `/${u.replace(/^\.\//, "")}`);
223
+ const renderPath = resolve(__dirname, "../server/render.js").replace(/\\/g, "/");
224
+ const apiPath = resolve(__dirname, "../server/api.js").replace(/\\/g, "/");
225
+ const matcherPath = resolve(__dirname, "../runtime/client-router.js").replace(/\\/g, "/");
226
+ const virtualPlugin = {
227
+ name: "devix",
228
+ enforce: "pre",
229
+ resolveId(id) {
230
+ if (id === VIRTUAL_ENTRY_CLIENT) return `\0${VIRTUAL_ENTRY_CLIENT}`;
231
+ if (id === VIRTUAL_CLIENT_ROUTES) return `\0${VIRTUAL_CLIENT_ROUTES}`;
232
+ if (id === VIRTUAL_RENDER) return `\0${VIRTUAL_RENDER}`;
233
+ if (id === VIRTUAL_API) return `\0${VIRTUAL_API}`;
234
+ if (id === VIRTUAL_CONTEXT) return `\0${VIRTUAL_CONTEXT}`;
235
+ },
236
+ load(id) {
237
+ if (id === `\0${VIRTUAL_ENTRY_CLIENT}`)
238
+ return generateEntryClient({ cssUrls });
239
+ if (id === `\0${VIRTUAL_CLIENT_ROUTES}`)
240
+ return generateClientRoutes({ pagesDir, matcherPath });
241
+ if (id === `\0${VIRTUAL_RENDER}`)
242
+ return generateRender({ pagesDir, renderPath });
243
+ if (id === `\0${VIRTUAL_API}`)
244
+ return generateApi({ apiPath, appDir });
245
+ if (id === `\0${VIRTUAL_CONTEXT}`)
246
+ return generateContext();
247
+ },
248
+ configureServer(server) {
249
+ server.watcher.on("add", (file) => {
250
+ if (file.startsWith(resolve(process.cwd(), pagesDir))) invalidatePagesCache();
251
+ if (file.includes(`${appDir}/api`)) invalidateApiCache();
252
+ });
253
+ server.watcher.on("unlink", (file) => {
254
+ if (file.startsWith(resolve(process.cwd(), pagesDir))) invalidatePagesCache();
255
+ if (file.includes(`${appDir}/api`)) invalidateApiCache();
256
+ });
257
+ }
258
+ };
259
+ const base = {
260
+ plugins: [react(), virtualPlugin],
261
+ ssr: { noExternal: ["@devlusoft/devix"] },
262
+ ...config3.envPrefix ? { envPrefix: config3.envPrefix } : {}
263
+ };
264
+ return mergeConfig(base, config3.vite ?? {});
265
+ }
266
+ var __dirname, VIRTUAL_ENTRY_CLIENT, VIRTUAL_CLIENT_ROUTES, VIRTUAL_RENDER, VIRTUAL_API, VIRTUAL_CONTEXT;
267
+ var init_vite = __esm({
268
+ "src/vite/index.ts"() {
269
+ "use strict";
270
+ init_entry_client();
271
+ init_client_routes();
272
+ init_render();
273
+ init_api();
274
+ init_pages_router();
275
+ init_api_router();
276
+ init_context();
277
+ __dirname = dirname(fileURLToPath(import.meta.url));
278
+ VIRTUAL_ENTRY_CLIENT = "virtual:devix/entry-client";
279
+ VIRTUAL_CLIENT_ROUTES = "virtual:devix/client-routes";
280
+ VIRTUAL_RENDER = "virtual:devix/render";
281
+ VIRTUAL_API = "virtual:devix/api";
282
+ VIRTUAL_CONTEXT = "virtual:devix/context";
283
+ }
284
+ });
285
+
286
+ // src/server/routes.ts
287
+ function registerApiRoutes(app3, { apiModule: apiModule3, renderModule: renderModule4, loaderTimeout }) {
288
+ app3.all("/api/*", async (c) => {
289
+ try {
290
+ return await apiModule3.handleApiRequest(c.req.url, c.req.raw);
291
+ } catch (e) {
292
+ console.error(e);
293
+ return c.json({ error: "internal error" }, 500);
294
+ }
295
+ });
296
+ app3.get("/_data/*", async (c) => {
297
+ try {
298
+ const { pathname, search } = new URL(c.req.url, "http://localhost");
299
+ const url = pathname.replace(/^\/_data/, "") + search;
300
+ const data = await renderModule4.runLoader(url, c.req.raw, { loaderTimeout });
301
+ return c.json(data);
302
+ } catch (e) {
303
+ console.error(e);
304
+ return c.json({ error: "internal error" }, 500);
305
+ }
306
+ });
307
+ }
308
+ function registerSsrRoute(app3, { renderModule: renderModule4, manifest: manifest3, loaderTimeout }) {
309
+ app3.get("*", async (c) => {
310
+ try {
311
+ const { html, statusCode, headers } = await renderModule4.render(c.req.url, c.req.raw, { manifest: manifest3, loaderTimeout });
312
+ const res = c.html(`<!DOCTYPE html>${html}`, statusCode);
313
+ for (const [key, value] of Object.entries(headers)) {
314
+ res.headers.set(key, value);
315
+ }
316
+ return res;
317
+ } catch (e) {
318
+ console.error(e);
319
+ return c.text("Internal Server Error", 500);
320
+ }
321
+ });
322
+ }
323
+ var init_routes = __esm({
324
+ "src/server/routes.ts"() {
325
+ "use strict";
326
+ }
327
+ });
328
+
329
+ // src/utils/banner.ts
330
+ import pc from "picocolors";
331
+ import { networkInterfaces } from "node:os";
332
+ import { createRequire } from "node:module";
333
+ function getNetworkUrl(port3) {
334
+ const nets = networkInterfaces();
335
+ for (const interfaces of Object.values(nets)) {
336
+ for (const net of interfaces ?? []) {
337
+ if (net.family === "IPv4" && !net.internal) {
338
+ return `http://${net.address}:${port3}/`;
339
+ }
340
+ }
341
+ }
342
+ return null;
343
+ }
344
+ function printDevBanner(port3) {
345
+ const req = createRequire(import.meta.url);
346
+ const version = req("../../package.json").version;
347
+ const networkUrl = getNetworkUrl(port3);
348
+ console.log();
349
+ console.log(` ${pc.bold(pc.yellow("devix"))} ${pc.dim(`v${version}`)}`);
350
+ console.log();
351
+ console.log(` ${pc.green("\u279C")} ${pc.bold("Local:")} ${pc.cyan(`http://localhost:${port3}/`)}`);
352
+ if (networkUrl) {
353
+ console.log(` ${pc.green("\u279C")} ${pc.bold("Network:")} ${pc.cyan(networkUrl)}`);
354
+ } else {
355
+ console.log(` ${pc.green("\u279C")} ${pc.bold("Network:")} ${pc.dim("use --host to expose")}`);
356
+ }
357
+ console.log();
358
+ }
359
+ var init_banner = __esm({
360
+ "src/utils/banner.ts"() {
361
+ "use strict";
362
+ }
363
+ });
364
+
365
+ // src/server/collect-css.ts
366
+ async function collectCss(vite2) {
367
+ const cssUrls = /* @__PURE__ */ new Set();
368
+ for (const [, mod] of vite2.moduleGraph.idToModuleMap) {
369
+ if (!mod.id) continue;
370
+ if (mod.id.endsWith(".css") || mod.id.includes(".css?")) {
371
+ cssUrls.add(mod.url);
372
+ }
373
+ }
374
+ return [...cssUrls];
375
+ }
376
+ var init_collect_css = __esm({
377
+ "src/server/collect-css.ts"() {
378
+ "use strict";
379
+ }
380
+ });
381
+
382
+ // src/utils/duration.ts
383
+ function parseDuration(value) {
384
+ if (typeof value === "number") return value;
385
+ const match = value.trim().match(/^(\d+(?:\.\d+)?)\s*(ms|s|m|h)?$/);
386
+ if (!match) throw new Error(`[devix] Invalid duration: "${value}". Use a number (ms) or a string like "5s", "2m", "500ms".`);
387
+ const n = parseFloat(match[1]);
388
+ switch (match[2]) {
389
+ case "h":
390
+ return n * 36e5;
391
+ case "m":
392
+ return n * 6e4;
393
+ case "s":
394
+ return n * 1e3;
395
+ case "ms":
396
+ default:
397
+ return n;
398
+ }
399
+ }
400
+ var init_duration = __esm({
401
+ "src/utils/duration.ts"() {
402
+ "use strict";
403
+ }
404
+ });
405
+
406
+ // src/cli/dev.ts
407
+ var dev_exports = {};
408
+ import { createServer } from "node:http";
409
+ import { createServer as createViteServer } from "vite";
410
+ import { getRequestListener } from "@hono/node-server";
411
+ import { Hono } from "hono";
412
+ var VIRTUAL_RENDER2, VIRTUAL_API2, config, port, host, vite, renderModule, apiModule, app, honoHandler;
413
+ var init_dev = __esm({
414
+ async "src/cli/dev.ts"() {
415
+ "use strict";
416
+ init_vite();
417
+ init_routes();
418
+ init_banner();
419
+ init_collect_css();
420
+ init_duration();
421
+ VIRTUAL_RENDER2 = "virtual:devix/render";
422
+ VIRTUAL_API2 = "virtual:devix/api";
423
+ config = (await import(`${process.cwd()}/devix.config.ts`)).default;
424
+ port = Number(process.env.PORT) || config.port || 3e3;
425
+ host = typeof config.host === "string" ? config.host : config.host ? "0.0.0.0" : "localhost";
426
+ vite = await createViteServer({
427
+ ...devix(config),
428
+ configFile: false,
429
+ appType: "custom",
430
+ server: { middlewareMode: true }
431
+ });
432
+ renderModule = {
433
+ render: async (...args) => (await vite.ssrLoadModule(VIRTUAL_RENDER2)).render(...args),
434
+ runLoader: async (...args) => (await vite.ssrLoadModule(VIRTUAL_RENDER2)).runLoader(...args)
435
+ };
436
+ apiModule = {
437
+ handleApiRequest: async (...args) => (await vite.ssrLoadModule(VIRTUAL_API2)).handleApiRequest(...args)
438
+ };
439
+ app = new Hono();
440
+ registerApiRoutes(app, { renderModule, apiModule });
441
+ app.get("*", async (c) => {
442
+ try {
443
+ const { html, statusCode, headers } = await renderModule.render(c.req.url, c.req.raw, { loaderTimeout: parseDuration(config.loaderTimeout ?? 1e4) });
444
+ const cssUrls = await collectCss(vite);
445
+ const cssLinks = cssUrls.map((url) => `<link rel="stylesheet" href="${url}">`).join("\n");
446
+ const htmlWithCss = cssLinks ? html.replace("</head>", `${cssLinks}
447
+ </head>`) : html;
448
+ const transformed = await vite.transformIndexHtml(c.req.url, `<!DOCTYPE html>${htmlWithCss}`);
449
+ const res = c.html(transformed, statusCode);
450
+ for (const [key, value] of Object.entries(headers)) {
451
+ res.headers.set(key, value);
452
+ }
453
+ return res;
454
+ } catch (e) {
455
+ vite.ssrFixStacktrace(e);
456
+ console.error(e);
457
+ return c.text("Internal Server Error", 500);
458
+ }
459
+ });
460
+ honoHandler = getRequestListener(app.fetch);
461
+ createServer(async (req, res) => {
462
+ await new Promise((resolve5) => vite.middlewares(req, res, resolve5));
463
+ if (!res.writableEnded) await honoHandler(req, res);
464
+ }).listen(port, host, () => {
465
+ printDevBanner(port);
466
+ });
467
+ }
468
+ });
469
+
470
+ // src/cli/build.ts
471
+ var build_exports = {};
472
+ import { writeFileSync } from "node:fs";
473
+ import { resolve as resolve2 } from "node:path";
474
+ import { build } from "vite";
475
+ var config2, baseConfig, runtimeConfig;
476
+ var init_build = __esm({
477
+ async "src/cli/build.ts"() {
478
+ "use strict";
479
+ init_vite();
480
+ init_duration();
481
+ config2 = (await import(`${process.cwd()}/devix.config.ts`)).default;
482
+ baseConfig = devix(config2);
483
+ await build({
484
+ ...baseConfig,
485
+ configFile: false,
486
+ build: {
487
+ outDir: "dist/client",
488
+ manifest: true,
489
+ rolldownOptions: {
490
+ input: "virtual:devix/entry-client"
491
+ }
492
+ }
493
+ });
494
+ await build({
495
+ ...baseConfig,
496
+ configFile: false,
497
+ build: {
498
+ ssr: true,
499
+ outDir: "dist/server",
500
+ rolldownOptions: {
501
+ input: {
502
+ render: "virtual:devix/render",
503
+ api: "virtual:devix/api"
504
+ }
505
+ }
506
+ }
507
+ });
508
+ runtimeConfig = {
509
+ port: config2.port ?? 3e3,
510
+ host: config2.host ?? false,
511
+ loaderTimeout: parseDuration(config2.loaderTimeout ?? 1e4),
512
+ output: config2.output ?? "server"
513
+ };
514
+ writeFileSync(
515
+ resolve2(process.cwd(), "dist/devix.config.json"),
516
+ JSON.stringify(runtimeConfig, null, 2),
517
+ "utf-8"
518
+ );
519
+ }
520
+ });
521
+
522
+ // src/cli/generate.ts
523
+ var generate_exports = {};
524
+ import { readFileSync, mkdirSync, writeFileSync as writeFileSync2 } from "node:fs";
525
+ import { resolve as resolve3, join } from "node:path";
526
+ var userConfig, t, renderModule2, manifest, urls;
527
+ var init_generate = __esm({
528
+ async "src/cli/generate.ts"() {
529
+ "use strict";
530
+ userConfig = (await import(`${process.cwd()}/devix.config.ts`)).default;
531
+ if (userConfig.output !== "static") {
532
+ console.warn('[devix] Tip: set output: "static" in devix.config.ts to skip the SSR server at runtime.');
533
+ }
534
+ await init_build().then(() => build_exports);
535
+ t = Date.now();
536
+ renderModule2 = await import(resolve3(process.cwd(), "dist/server/render.js") + `?t=${t}`);
537
+ manifest = JSON.parse(
538
+ readFileSync(resolve3(process.cwd(), "dist/client/.vite/manifest.json"), "utf-8")
539
+ );
540
+ urls = await renderModule2.getStaticRoutes();
541
+ console.log(`[devix] Generating ${urls.length} static page${urls.length === 1 ? "" : "s"}...`);
542
+ for (const url of urls) {
543
+ const fullUrl = `http://localhost${url}`;
544
+ const { html, statusCode } = await renderModule2.render(fullUrl, new Request(fullUrl), { manifest });
545
+ if (statusCode !== 200) {
546
+ console.warn(`[devix] Skipping ${url} \u2014 status ${statusCode}`);
547
+ continue;
548
+ }
549
+ const outPath = url === "/" ? join(process.cwd(), "dist/client/index.html") : join(process.cwd(), "dist/client", url, "index.html");
550
+ mkdirSync(join(outPath, ".."), { recursive: true });
551
+ writeFileSync2(outPath, `<!DOCTYPE html>${html}`, "utf-8");
552
+ console.log(` \u2713 ${url}`);
553
+ }
554
+ console.log("[devix] Generation complete.");
555
+ }
556
+ });
557
+
558
+ // src/cli/start.ts
559
+ var start_exports = {};
560
+ import { readFileSync as readFileSync2 } from "node:fs";
561
+ import { serve } from "@hono/node-server";
562
+ import { serveStatic } from "@hono/node-server/serve-static";
563
+ import { Hono as Hono2 } from "hono";
564
+ import { resolve as resolve4 } from "node:path";
565
+ var renderModule3, apiModule2, manifest2, runtimeConfig2, port2, host2, app2;
566
+ var init_start = __esm({
567
+ async "src/cli/start.ts"() {
568
+ "use strict";
569
+ init_routes();
570
+ try {
571
+ runtimeConfig2 = JSON.parse(readFileSync2(resolve4(process.cwd(), "dist/devix.config.json"), "utf-8"));
572
+ if (runtimeConfig2.output !== "static") {
573
+ renderModule3 = await import(resolve4(process.cwd(), "dist/server/render.js"));
574
+ apiModule2 = await import(resolve4(process.cwd(), "dist/server/api.js"));
575
+ }
576
+ manifest2 = JSON.parse(readFileSync2(resolve4(process.cwd(), "dist/client/.vite/manifest.json"), "utf-8"));
577
+ } catch {
578
+ console.error('[devix] Build not found. Run "devix build" first.');
579
+ process.exit(1);
580
+ }
581
+ port2 = Number(process.env.PORT) || runtimeConfig2.port || 3e3;
582
+ host2 = typeof runtimeConfig2.host === "string" ? runtimeConfig2.host : runtimeConfig2.host ? "0.0.0.0" : process.env.HOST || "0.0.0.0";
583
+ app2 = new Hono2();
584
+ app2.use("/*", serveStatic({
585
+ root: "./dist/client",
586
+ onFound: (_path, c) => {
587
+ c.header("Cache-Control", _path.includes("/assets/") ? "public, immutable, max-age=31536000" : "no-cache");
588
+ }
589
+ }));
590
+ if (runtimeConfig2.output === "static") {
591
+ console.log("[devix] Static mode \u2014 serving pre-generated files from dist/client");
592
+ } else {
593
+ registerApiRoutes(app2, { renderModule: renderModule3, apiModule: apiModule2, manifest: manifest2 });
594
+ registerSsrRoute(app2, { renderModule: renderModule3, apiModule: apiModule2, manifest: manifest2, loaderTimeout: runtimeConfig2.loaderTimeout });
595
+ }
596
+ serve({ fetch: app2.fetch, port: port2, hostname: host2 }, (info) => console.log(`http://${info.address}:${info.port}`));
597
+ }
598
+ });
599
+
600
+ // src/cli/index.ts
601
+ import { readFileSync as readFileSync3 } from "node:fs";
602
+ import { join as join2, dirname as dirname2 } from "node:path";
603
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
604
+ var command = process.argv[2];
605
+ switch (command) {
606
+ case "dev":
607
+ await init_dev().then(() => dev_exports);
608
+ break;
609
+ case "build":
610
+ await init_build().then(() => build_exports);
611
+ break;
612
+ case "generate":
613
+ await init_generate().then(() => generate_exports);
614
+ break;
615
+ case "start":
616
+ await init_start().then(() => start_exports);
617
+ break;
618
+ case "--version":
619
+ case "-v": {
620
+ const pkg = JSON.parse(readFileSync3(join2(dirname2(fileURLToPath2(import.meta.url)), "../../package.json"), "utf-8"));
621
+ console.log(pkg.version);
622
+ break;
623
+ }
624
+ case "--help":
625
+ case "-h":
626
+ console.log(`
627
+ devix \u2014 a lightweight SSR framework
628
+
629
+ Usage:
630
+ devix dev Start development server
631
+ devix build Build for production
632
+ devix generate Build and generate static HTML (SSG)
633
+ devix start Start production server
634
+
635
+ Options:
636
+ -v, --version Show version
637
+ -h, --help Show this help
638
+
639
+ Output modes (set in devix.config.ts):
640
+ output: "server" SSR mode \u2014 devix start handles requests dynamically (default)
641
+ output: "static" SSG mode \u2014 devix generate pre-renders all pages; devix start serves static files only
642
+ `.trim());
643
+ break;
644
+ default:
645
+ console.error(`Unknown command: ${command}`);
646
+ console.error("Usage: devix <dev|build|generate|start>");
647
+ process.exit(1);
648
+ }
649
+ //# sourceMappingURL=index.js.map