@nuraly/lumenjs 0.1.3 → 0.2.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 (306) hide show
  1. package/README.md +62 -282
  2. package/dist/auth/config.d.ts +23 -0
  3. package/dist/auth/config.js +115 -0
  4. package/dist/auth/guard.d.ts +12 -0
  5. package/dist/auth/guard.js +28 -0
  6. package/dist/auth/index.d.ts +3 -0
  7. package/dist/auth/index.js +1 -0
  8. package/dist/auth/middleware.d.ts +23 -0
  9. package/dist/auth/middleware.js +89 -0
  10. package/dist/auth/native-auth.d.ts +82 -0
  11. package/dist/auth/native-auth.js +340 -0
  12. package/dist/auth/oidc-client.d.ts +17 -0
  13. package/dist/auth/oidc-client.js +123 -0
  14. package/dist/auth/providers/google.d.ts +23 -0
  15. package/dist/auth/providers/google.js +25 -0
  16. package/dist/auth/providers/index.d.ts +2 -0
  17. package/dist/auth/providers/index.js +1 -0
  18. package/dist/auth/routes/login.d.ts +8 -0
  19. package/dist/auth/routes/login.js +121 -0
  20. package/dist/auth/routes/logout.d.ts +4 -0
  21. package/dist/auth/routes/logout.js +79 -0
  22. package/dist/auth/routes/oidc-callback.d.ts +3 -0
  23. package/dist/auth/routes/oidc-callback.js +70 -0
  24. package/dist/auth/routes/password.d.ts +5 -0
  25. package/dist/auth/routes/password.js +149 -0
  26. package/dist/auth/routes/signup.d.ts +3 -0
  27. package/dist/auth/routes/signup.js +81 -0
  28. package/dist/auth/routes/token.d.ts +4 -0
  29. package/dist/auth/routes/token.js +70 -0
  30. package/dist/auth/routes/totp.d.ts +22 -0
  31. package/dist/auth/routes/totp.js +232 -0
  32. package/dist/auth/routes/utils.d.ts +7 -0
  33. package/dist/auth/routes/utils.js +35 -0
  34. package/dist/auth/routes/verify.d.ts +3 -0
  35. package/dist/auth/routes/verify.js +26 -0
  36. package/dist/auth/routes.d.ts +8 -0
  37. package/dist/auth/routes.js +124 -0
  38. package/dist/auth/session.d.ts +8 -0
  39. package/dist/auth/session.js +54 -0
  40. package/dist/auth/token.d.ts +33 -0
  41. package/dist/auth/token.js +90 -0
  42. package/dist/auth/types.d.ts +156 -0
  43. package/dist/auth/types.js +2 -0
  44. package/dist/build/build-client.d.ts +15 -0
  45. package/dist/build/build-client.js +45 -0
  46. package/dist/build/build-prerender.d.ts +11 -0
  47. package/dist/build/build-prerender.js +159 -0
  48. package/dist/build/build-server.d.ts +18 -0
  49. package/dist/build/build-server.js +107 -0
  50. package/dist/build/build.js +60 -123
  51. package/dist/build/scan.d.ts +18 -0
  52. package/dist/build/scan.js +77 -6
  53. package/dist/build/serve-api.js +8 -2
  54. package/dist/build/serve-loaders.d.ts +4 -4
  55. package/dist/build/serve-loaders.js +26 -18
  56. package/dist/build/serve-ssr.js +38 -11
  57. package/dist/build/serve-static.js +3 -3
  58. package/dist/build/serve.js +341 -18
  59. package/dist/cli.js +37 -6
  60. package/dist/communication/encryption.d.ts +35 -0
  61. package/dist/communication/encryption.js +90 -0
  62. package/dist/communication/handlers/context.d.ts +27 -0
  63. package/dist/communication/handlers/context.js +1 -0
  64. package/dist/communication/handlers/conversation.d.ts +24 -0
  65. package/dist/communication/handlers/conversation.js +113 -0
  66. package/dist/communication/handlers/file-upload.d.ts +17 -0
  67. package/dist/communication/handlers/file-upload.js +62 -0
  68. package/dist/communication/handlers/messaging.d.ts +30 -0
  69. package/dist/communication/handlers/messaging.js +237 -0
  70. package/dist/communication/handlers/presence.d.ts +15 -0
  71. package/dist/communication/handlers/presence.js +76 -0
  72. package/dist/communication/handlers.d.ts +5 -0
  73. package/dist/communication/handlers.js +5 -0
  74. package/dist/communication/index.d.ts +9 -0
  75. package/dist/communication/index.js +7 -0
  76. package/dist/communication/link-preview.d.ts +18 -0
  77. package/dist/communication/link-preview.js +115 -0
  78. package/dist/communication/schema.d.ts +10 -0
  79. package/dist/communication/schema.js +101 -0
  80. package/dist/communication/server.d.ts +86 -0
  81. package/dist/communication/server.js +212 -0
  82. package/dist/communication/signaling.d.ts +43 -0
  83. package/dist/communication/signaling.js +271 -0
  84. package/dist/communication/store.d.ts +71 -0
  85. package/dist/communication/store.js +289 -0
  86. package/dist/communication/types.d.ts +454 -0
  87. package/dist/communication/types.js +1 -0
  88. package/dist/create.d.ts +1 -0
  89. package/dist/create.js +55 -0
  90. package/dist/db/auto-migrate.d.ts +3 -0
  91. package/dist/db/auto-migrate.js +100 -0
  92. package/dist/db/client.d.ts +3 -0
  93. package/dist/db/client.js +18 -0
  94. package/dist/db/index.d.ts +17 -13
  95. package/dist/db/index.js +205 -26
  96. package/dist/db/seed.d.ts +12 -0
  97. package/dist/db/seed.js +88 -0
  98. package/dist/db/table.d.ts +10 -0
  99. package/dist/db/table.js +12 -0
  100. package/dist/dev-server/config.d.ts +11 -0
  101. package/dist/dev-server/config.js +40 -20
  102. package/dist/dev-server/index-html.d.ts +4 -0
  103. package/dist/dev-server/index-html.js +21 -6
  104. package/dist/dev-server/nuralyui-aliases.d.ts +0 -4
  105. package/dist/dev-server/nuralyui-aliases.js +115 -94
  106. package/dist/dev-server/plugins/vite-plugin-api-routes.js +29 -5
  107. package/dist/dev-server/plugins/vite-plugin-auth.d.ts +6 -0
  108. package/dist/dev-server/plugins/vite-plugin-auth.js +223 -0
  109. package/dist/dev-server/plugins/vite-plugin-auto-define.d.ts +16 -0
  110. package/dist/dev-server/plugins/vite-plugin-auto-define.js +111 -0
  111. package/dist/dev-server/plugins/vite-plugin-communication.d.ts +6 -0
  112. package/dist/dev-server/plugins/vite-plugin-communication.js +205 -0
  113. package/dist/dev-server/plugins/vite-plugin-editor-api.d.ts +6 -0
  114. package/dist/dev-server/plugins/vite-plugin-editor-api.js +318 -0
  115. package/dist/dev-server/plugins/vite-plugin-i18n.js +69 -2
  116. package/dist/dev-server/plugins/vite-plugin-lit-dedup.d.ts +6 -0
  117. package/dist/dev-server/plugins/vite-plugin-lit-dedup.js +78 -34
  118. package/dist/dev-server/plugins/vite-plugin-lit-hmr.js +44 -2
  119. package/dist/dev-server/plugins/vite-plugin-llms.d.ts +2 -0
  120. package/dist/dev-server/plugins/vite-plugin-llms.js +92 -0
  121. package/dist/dev-server/plugins/vite-plugin-loaders.js +146 -13
  122. package/dist/dev-server/plugins/vite-plugin-routes.js +16 -5
  123. package/dist/dev-server/plugins/vite-plugin-socketio.d.ts +2 -0
  124. package/dist/dev-server/plugins/vite-plugin-socketio.js +51 -0
  125. package/dist/dev-server/plugins/vite-plugin-source-annotator.d.ts +2 -0
  126. package/dist/dev-server/plugins/vite-plugin-source-annotator.js +26 -3
  127. package/dist/dev-server/plugins/vite-plugin-storage.d.ts +10 -0
  128. package/dist/dev-server/plugins/vite-plugin-storage.js +126 -0
  129. package/dist/dev-server/plugins/vite-plugin-virtual-modules.js +140 -3
  130. package/dist/dev-server/server.js +242 -70
  131. package/dist/dev-server/ssr-render.d.ts +2 -1
  132. package/dist/dev-server/ssr-render.js +117 -50
  133. package/dist/editor/ai/backend.d.ts +20 -0
  134. package/dist/editor/ai/backend.js +113 -0
  135. package/dist/editor/ai/claude-code-client.d.ts +20 -0
  136. package/dist/editor/ai/claude-code-client.js +145 -0
  137. package/dist/editor/ai/deepseek-client.d.ts +7 -0
  138. package/dist/editor/ai/deepseek-client.js +113 -0
  139. package/dist/editor/ai/opencode-client.d.ts +14 -0
  140. package/dist/editor/ai/opencode-client.js +99 -0
  141. package/dist/editor/ai/snapshot-store.d.ts +22 -0
  142. package/dist/editor/ai/snapshot-store.js +35 -0
  143. package/dist/editor/ai/types.d.ts +30 -0
  144. package/dist/editor/ai/types.js +136 -0
  145. package/dist/editor/ai-chat-panel.d.ts +13 -0
  146. package/dist/editor/ai-chat-panel.js +613 -0
  147. package/dist/editor/ai-markdown.d.ts +10 -0
  148. package/dist/editor/ai-markdown.js +70 -0
  149. package/dist/editor/ai-project-panel.d.ts +11 -0
  150. package/dist/editor/ai-project-panel.js +332 -0
  151. package/dist/editor/ast-modification.d.ts +11 -0
  152. package/dist/editor/ast-modification.js +1 -0
  153. package/dist/editor/ast-service.d.ts +30 -0
  154. package/dist/editor/ast-service.js +180 -0
  155. package/dist/editor/css-rules.d.ts +54 -0
  156. package/dist/editor/css-rules.js +423 -0
  157. package/dist/editor/editor-api-client.d.ts +51 -0
  158. package/dist/editor/editor-api-client.js +162 -0
  159. package/dist/editor/editor-bridge.d.ts +1 -0
  160. package/dist/editor/editor-bridge.js +18 -8
  161. package/dist/editor/editor-toolbar.d.ts +14 -0
  162. package/dist/editor/editor-toolbar.js +115 -0
  163. package/dist/editor/file-editor.d.ts +9 -0
  164. package/dist/editor/file-editor.js +236 -0
  165. package/dist/editor/file-service.d.ts +16 -0
  166. package/dist/editor/file-service.js +52 -0
  167. package/dist/editor/i18n-key-gen.d.ts +1 -0
  168. package/dist/editor/i18n-key-gen.js +7 -0
  169. package/dist/editor/inline-text-edit.d.ts +5 -0
  170. package/dist/editor/inline-text-edit.js +173 -92
  171. package/dist/editor/overlay-events.d.ts +5 -0
  172. package/dist/editor/overlay-events.js +364 -0
  173. package/dist/editor/overlay-hmr.d.ts +2 -0
  174. package/dist/editor/overlay-hmr.js +76 -0
  175. package/dist/editor/overlay-selection.d.ts +29 -0
  176. package/dist/editor/overlay-selection.js +148 -0
  177. package/dist/editor/overlay-utils.d.ts +12 -0
  178. package/dist/editor/overlay-utils.js +59 -0
  179. package/dist/editor/properties-panel-persist.d.ts +14 -0
  180. package/dist/editor/properties-panel-persist.js +70 -0
  181. package/dist/editor/properties-panel-rows.d.ts +10 -0
  182. package/dist/editor/properties-panel-rows.js +349 -0
  183. package/dist/editor/properties-panel-styles.d.ts +4 -0
  184. package/dist/editor/properties-panel-styles.js +174 -0
  185. package/dist/editor/properties-panel.d.ts +4 -0
  186. package/dist/editor/properties-panel.js +148 -0
  187. package/dist/editor/property-registry.d.ts +16 -0
  188. package/dist/editor/property-registry.js +303 -0
  189. package/dist/editor/standalone-file-panel.d.ts +0 -0
  190. package/dist/editor/standalone-file-panel.js +1 -0
  191. package/dist/editor/standalone-overlay-dom.d.ts +0 -0
  192. package/dist/editor/standalone-overlay-dom.js +1 -0
  193. package/dist/editor/standalone-overlay-styles.d.ts +0 -0
  194. package/dist/editor/standalone-overlay-styles.js +1 -0
  195. package/dist/editor/standalone-overlay.d.ts +1 -0
  196. package/dist/editor/standalone-overlay.js +76 -0
  197. package/dist/editor/syntax-highlighter.d.ts +4 -0
  198. package/dist/editor/syntax-highlighter.js +81 -0
  199. package/dist/editor/text-toolbar.d.ts +11 -0
  200. package/dist/editor/text-toolbar.js +327 -0
  201. package/dist/editor/toolbar-styles.d.ts +4 -0
  202. package/dist/editor/toolbar-styles.js +198 -0
  203. package/dist/email/index.d.ts +32 -0
  204. package/dist/email/index.js +154 -0
  205. package/dist/email/providers/resend.d.ts +2 -0
  206. package/dist/email/providers/resend.js +24 -0
  207. package/dist/email/providers/sendgrid.d.ts +2 -0
  208. package/dist/email/providers/sendgrid.js +31 -0
  209. package/dist/email/providers/smtp.d.ts +13 -0
  210. package/dist/email/providers/smtp.js +125 -0
  211. package/dist/email/template-engine.d.ts +18 -0
  212. package/dist/email/template-engine.js +116 -0
  213. package/dist/email/templates/base.d.ts +9 -0
  214. package/dist/email/templates/base.js +65 -0
  215. package/dist/email/templates/password-reset.d.ts +5 -0
  216. package/dist/email/templates/password-reset.js +15 -0
  217. package/dist/email/templates/verify-email.d.ts +5 -0
  218. package/dist/email/templates/verify-email.js +15 -0
  219. package/dist/email/templates/welcome.d.ts +5 -0
  220. package/dist/email/templates/welcome.js +13 -0
  221. package/dist/email/types.d.ts +49 -0
  222. package/dist/email/types.js +1 -0
  223. package/dist/llms/generate.d.ts +46 -0
  224. package/dist/llms/generate.js +185 -0
  225. package/dist/permissions/guard.d.ts +28 -0
  226. package/dist/permissions/guard.js +30 -0
  227. package/dist/permissions/index.d.ts +6 -0
  228. package/dist/permissions/index.js +3 -0
  229. package/dist/permissions/service.d.ts +80 -0
  230. package/dist/permissions/service.js +210 -0
  231. package/dist/permissions/tables.d.ts +5 -0
  232. package/dist/permissions/tables.js +68 -0
  233. package/dist/permissions/types.d.ts +33 -0
  234. package/dist/permissions/types.js +1 -0
  235. package/dist/runtime/app-shell.d.ts +1 -1
  236. package/dist/runtime/app-shell.js +164 -0
  237. package/dist/runtime/auth.d.ts +10 -0
  238. package/dist/runtime/auth.js +30 -0
  239. package/dist/runtime/communication.d.ts +137 -0
  240. package/dist/runtime/communication.js +228 -0
  241. package/dist/runtime/error-boundary.d.ts +23 -0
  242. package/dist/runtime/error-boundary.js +120 -0
  243. package/dist/runtime/i18n.d.ts +6 -1
  244. package/dist/runtime/i18n.js +42 -21
  245. package/dist/runtime/island.d.ts +16 -0
  246. package/dist/runtime/island.js +80 -0
  247. package/dist/runtime/router-data.d.ts +3 -0
  248. package/dist/runtime/router-data.js +102 -17
  249. package/dist/runtime/router-hydration.js +34 -2
  250. package/dist/runtime/router.d.ts +19 -2
  251. package/dist/runtime/router.js +237 -43
  252. package/dist/runtime/socket-client.d.ts +2 -0
  253. package/dist/runtime/socket-client.js +30 -0
  254. package/dist/runtime/webrtc.d.ts +91 -0
  255. package/dist/runtime/webrtc.js +428 -0
  256. package/dist/shared/dom-shims.js +4 -2
  257. package/dist/shared/graceful-shutdown.d.ts +8 -0
  258. package/dist/shared/graceful-shutdown.js +36 -0
  259. package/dist/shared/health.d.ts +8 -0
  260. package/dist/shared/health.js +25 -0
  261. package/dist/shared/llms-txt.d.ts +31 -0
  262. package/dist/shared/llms-txt.js +85 -0
  263. package/dist/shared/logger.d.ts +32 -0
  264. package/dist/shared/logger.js +93 -0
  265. package/dist/shared/meta.d.ts +27 -0
  266. package/dist/shared/meta.js +71 -0
  267. package/dist/shared/middleware-runner.d.ts +9 -0
  268. package/dist/shared/middleware-runner.js +29 -0
  269. package/dist/shared/rate-limit.d.ts +18 -0
  270. package/dist/shared/rate-limit.js +71 -0
  271. package/dist/shared/request-id.d.ts +5 -0
  272. package/dist/shared/request-id.js +18 -0
  273. package/dist/shared/route-matching.js +16 -1
  274. package/dist/shared/security-headers.d.ts +18 -0
  275. package/dist/shared/security-headers.js +38 -0
  276. package/dist/shared/socket-io-setup.d.ts +11 -0
  277. package/dist/shared/socket-io-setup.js +51 -0
  278. package/dist/shared/types.d.ts +15 -0
  279. package/dist/shared/utils.d.ts +33 -7
  280. package/dist/shared/utils.js +164 -27
  281. package/dist/storage/adapters/local.d.ts +44 -0
  282. package/dist/storage/adapters/local.js +85 -0
  283. package/dist/storage/adapters/s3.d.ts +32 -0
  284. package/dist/storage/adapters/s3.js +119 -0
  285. package/dist/storage/adapters/types.d.ts +53 -0
  286. package/dist/storage/adapters/types.js +1 -0
  287. package/dist/storage/index.d.ts +76 -0
  288. package/dist/storage/index.js +83 -0
  289. package/package.json +45 -7
  290. package/templates/blog/api/posts.ts +4 -18
  291. package/templates/blog/data/migrations/001_init.sql +6 -5
  292. package/templates/blog/lumenjs.config.ts +3 -0
  293. package/templates/blog/package.json +14 -0
  294. package/templates/blog/pages/_layout.ts +25 -0
  295. package/templates/blog/pages/index.ts +48 -22
  296. package/templates/blog/pages/posts/[slug].ts +45 -20
  297. package/templates/blog/pages/tag/[tag].ts +44 -0
  298. package/templates/dashboard/api/stats.ts +8 -5
  299. package/templates/dashboard/lumenjs.config.ts +3 -0
  300. package/templates/dashboard/package.json +14 -0
  301. package/templates/dashboard/pages/_layout.ts +25 -0
  302. package/templates/dashboard/pages/index.ts +54 -23
  303. package/templates/dashboard/pages/settings/index.ts +29 -0
  304. package/templates/default/lumenjs.config.ts +3 -0
  305. package/templates/default/package.json +14 -0
  306. package/templates/default/pages/index.ts +24 -0
@@ -1,22 +1,27 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { stripOuterLitMarkers, dirToLayoutTagName, isRedirectResponse } from '../shared/utils.js';
3
+ import { stripOuterLitMarkers, dirToLayoutTagName, isRedirectResponse, patchLoaderDataSpread } from '../shared/utils.js';
4
4
  import { matchRoute } from '../shared/route-matching.js';
5
5
  import { sendCompressed } from './serve-static.js';
6
+ import { logger } from '../shared/logger.js';
6
7
  export async function handlePageRoute(manifest, serverDir, pagesDir, pathname, queryString, indexHtmlShell, title, ssrRuntime, req, res) {
7
8
  // Find matching route (any route, not just those with loaders)
8
9
  const allMatched = matchRoute(manifest.routes, pathname);
9
10
  // Try SSR for routes with loaders
10
11
  const matched = matchRoute(manifest.routes.filter(r => r.hasLoader), pathname);
11
12
  if (matched && matched.route.module) {
12
- const modulePath = path.join(serverDir, matched.route.module);
13
+ // Rollup sanitizes brackets in filenames: [...path] → _...path_
14
+ let modulePath = path.join(serverDir, matched.route.module);
15
+ if (!fs.existsSync(modulePath)) {
16
+ modulePath = path.join(serverDir, matched.route.module.replace(/\[/g, '_').replace(/\]/g, '_'));
17
+ }
13
18
  if (fs.existsSync(modulePath)) {
14
19
  try {
15
20
  const mod = await import(modulePath);
16
21
  // Run loader
17
22
  let loaderData = undefined;
18
23
  if (mod.loader && typeof mod.loader === 'function') {
19
- loaderData = await mod.loader({ params: matched.params, query: {}, url: pathname, headers: req.headers });
24
+ loaderData = await mod.loader({ params: matched.params, query: {}, url: pathname, headers: req.headers, user: req.nkAuth?.user ?? null });
20
25
  if (isRedirectResponse(loaderData)) {
21
26
  res.writeHead(loaderData.status || 302, { Location: loaderData.location });
22
27
  res.end();
@@ -39,7 +44,7 @@ export async function handlePageRoute(manifest, serverDir, pagesDir, pathname, q
39
44
  if (fs.existsSync(layoutModulePath)) {
40
45
  const layoutMod = await import(layoutModulePath);
41
46
  if (layoutMod.loader && typeof layoutMod.loader === 'function') {
42
- layoutLoaderData = await layoutMod.loader({ params: {}, query: {}, url: pathname, headers: req.headers });
47
+ layoutLoaderData = await layoutMod.loader({ params: {}, query: {}, url: pathname, headers: req.headers, user: req.nkAuth?.user ?? null });
43
48
  if (isRedirectResponse(layoutLoaderData)) {
44
49
  res.writeHead(layoutLoaderData.status || 302, { Location: layoutLoaderData.location });
45
50
  res.end();
@@ -52,6 +57,12 @@ export async function handlePageRoute(manifest, serverDir, pagesDir, pathname, q
52
57
  layoutsData.push({ loaderPath: dir, data: layoutLoaderData });
53
58
  layoutModules.push({ tagName: layoutTagName, loaderData: layoutLoaderData });
54
59
  }
60
+ // Patch element classes to spread loaderData into individual properties
61
+ for (const lm of layoutModules) {
62
+ patchLoaderDataSpread(lm.tagName);
63
+ }
64
+ if (tagName)
65
+ patchLoaderDataSpread(tagName);
55
66
  if (tagName && ssrRuntime) {
56
67
  // SSR render with the bundled @lit-labs/ssr runtime
57
68
  try {
@@ -90,14 +101,18 @@ export async function handlePageRoute(manifest, serverDir, pagesDir, pathname, q
90
101
  const loaderDataScript = ssrDataObj !== undefined
91
102
  ? `<script type="application/json" id="__nk_ssr_data__">${JSON.stringify(ssrDataObj).replace(/</g, '\\u003c')}</script>`
92
103
  : '';
93
- const hydrateScript = `<script type="module">import '@lit-labs/ssr-client/lit-element-hydrate-support.js';</script>`;
104
+ // Auth: inline user data for client hydration
105
+ const authUser = req.nkAuth?.user ?? null;
106
+ const authScript = authUser
107
+ ? `<script type="application/json" id="__nk_auth__">${JSON.stringify(authUser).replace(/</g, '\\u003c')}</script>`
108
+ : '';
94
109
  let html_out = indexHtmlShell;
95
- html_out = html_out.replace(/<nk-app><\/nk-app>/, `${loaderDataScript}<nk-app data-nk-ssr><div id="nk-router-outlet">${ssrHtml}</div></nk-app>${hydrateScript}`);
110
+ html_out = html_out.replace(/<nk-app><\/nk-app>/, `${authScript}${loaderDataScript}<nk-app data-nk-ssr><div id="nk-router-outlet">${ssrHtml}</div></nk-app>`);
96
111
  sendCompressed(req, res, 200, 'text/html; charset=utf-8', html_out);
97
112
  return;
98
113
  }
99
114
  catch (ssrErr) {
100
- console.error('[LumenJS] SSR render failed, falling back to CSR:', ssrErr);
115
+ logger.warn('SSR render failed, falling back to CSR', { error: ssrErr?.message });
101
116
  }
102
117
  }
103
118
  // Fallback: inject loader data without SSR HTML
@@ -106,16 +121,28 @@ export async function handlePageRoute(manifest, serverDir, pagesDir, pathname, q
106
121
  ? { page: loaderData, layouts: layoutsData }
107
122
  : loaderData;
108
123
  const loaderDataScript = `<script type="application/json" id="__nk_ssr_data__">${JSON.stringify(ssrDataObj).replace(/</g, '\\u003c')}</script>`;
109
- let html_out = indexHtmlShell.replace('<nk-app>', `${loaderDataScript}<nk-app>`);
124
+ const authUserFb = req.nkAuth?.user ?? null;
125
+ const authScriptFb = authUserFb
126
+ ? `<script type="application/json" id="__nk_auth__">${JSON.stringify(authUserFb).replace(/</g, '\\u003c')}</script>`
127
+ : '';
128
+ let html_out = indexHtmlShell.replace('<nk-app>', `${authScriptFb}${loaderDataScript}<nk-app>`);
110
129
  sendCompressed(req, res, 200, 'text/html; charset=utf-8', html_out);
111
130
  return;
112
131
  }
113
132
  }
114
133
  catch (err) {
115
- console.error('[LumenJS] Page handler error:', err);
134
+ logger.error('Page handler error', { error: err?.message });
116
135
  }
117
136
  }
118
137
  }
119
- // SPA fallback — serve the built index.html
120
- sendCompressed(req, res, 200, 'text/html; charset=utf-8', indexHtmlShell);
138
+ // SPA fallback — serve the built index.html with auth data injected
139
+ const fallbackUser = req.nkAuth?.user ?? null;
140
+ if (fallbackUser) {
141
+ const authTag = `<script type="application/json" id="__nk_auth__">${JSON.stringify(fallbackUser).replace(/</g, '\\u003c')}</script>`;
142
+ const html = indexHtmlShell.replace('<nk-app>', `${authTag}<nk-app>`);
143
+ sendCompressed(req, res, 200, 'text/html; charset=utf-8', html);
144
+ }
145
+ else {
146
+ sendCompressed(req, res, 200, 'text/html; charset=utf-8', indexHtmlShell);
147
+ }
121
148
  }
@@ -52,9 +52,9 @@ export function sendCompressed(req, res, statusCode, contentType, body) {
52
52
  }
53
53
  export function serveStaticFile(clientDir, pathname, req, res) {
54
54
  // Prevent directory traversal
55
- const safePath = path.normalize(pathname).replace(/^(\.\.[/\\])+/, '');
56
- const filePath = path.join(clientDir, safePath);
57
- if (!filePath.startsWith(clientDir)) {
55
+ const resolvedClientDir = path.resolve(clientDir);
56
+ const filePath = path.resolve(resolvedClientDir, pathname.replace(/^\/+/, ''));
57
+ if (!filePath.startsWith(resolvedClientDir + path.sep) && filePath !== resolvedClientDir) {
58
58
  return false;
59
59
  }
60
60
  if (!fs.existsSync(filePath) || fs.statSync(filePath).isDirectory()) {
@@ -10,50 +10,329 @@ import { handlePageRoute } from './serve-ssr.js';
10
10
  import { renderErrorPage } from './error-page.js';
11
11
  import { handleI18nRequest } from './serve-i18n.js';
12
12
  import { resolveLocale } from '../dev-server/middleware/locale.js';
13
- import { setProjectDir } from '../db/context.js';
13
+ import { runMiddlewareChain, extractMiddleware } from '../shared/middleware-runner.js';
14
+ import { getMiddlewareDirsForPathname } from './scan.js';
15
+ import { createAuthMiddleware } from '../auth/middleware.js';
16
+ import { handleAuthRoutes } from '../auth/routes.js';
17
+ import { loadAuthConfigProd } from '../auth/config.js';
18
+ import { initLogger, logger } from '../shared/logger.js';
19
+ import { createSecurityHeadersMiddleware } from '../shared/security-headers.js';
20
+ import { createRateLimiter, createAuthRateLimiter } from '../shared/rate-limit.js';
21
+ import { createHealthCheckHandler } from '../shared/health.js';
22
+ import { createRequestIdMiddleware } from '../shared/request-id.js';
23
+ import { getRequestId } from '../shared/request-id.js';
24
+ import { setupGracefulShutdown } from '../shared/graceful-shutdown.js';
25
+ import { setupSocketIO } from '../shared/socket-io-setup.js';
26
+ import crypto from 'crypto';
14
27
  export async function serveProject(options) {
15
28
  const { projectDir } = options;
16
- setProjectDir(projectDir);
17
29
  const port = options.port || 3000;
18
30
  const outDir = path.join(projectDir, '.lumenjs');
19
31
  const clientDir = path.join(outDir, 'client');
20
32
  const serverDir = path.join(outDir, 'server');
21
33
  const manifestPath = path.join(outDir, 'manifest.json');
34
+ // Initialize structured logging
35
+ initLogger();
22
36
  if (!fs.existsSync(manifestPath)) {
23
- console.error('[LumenJS] No build found. Run `lumenjs build` first.');
37
+ logger.fatal('No build found. Run `lumenjs build` first.');
24
38
  process.exit(1);
25
39
  }
26
40
  const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
27
- const { title } = readProjectConfig(projectDir);
41
+ const config = readProjectConfig(projectDir);
42
+ const { title } = config;
28
43
  const localesDir = path.join(outDir, 'locales');
44
+ // Production middleware stack
45
+ const requestIdMiddleware = createRequestIdMiddleware();
46
+ const securityHeaders = createSecurityHeadersMiddleware(config.securityHeaders);
47
+ const rateLimiter = createRateLimiter(config.rateLimit);
48
+ const authRateLimiter = createAuthRateLimiter();
49
+ const healthCheck = createHealthCheckHandler({ version: config.version });
29
50
  // Read the built index.html shell
30
51
  const indexHtmlPath = path.join(clientDir, 'index.html');
31
52
  if (!fs.existsSync(indexHtmlPath)) {
32
- console.error('[LumenJS] No index.html found in build output.');
53
+ logger.fatal('No index.html found in build output.');
33
54
  process.exit(1);
34
55
  }
35
- const indexHtmlShell = fs.readFileSync(indexHtmlPath, 'utf-8');
36
- // Load bundled SSR runtime first — it installs @lit-labs/ssr DOM shim
37
- // which must be in place before any Lit class is instantiated.
56
+ let indexHtmlShell = fs.readFileSync(indexHtmlPath, 'utf-8');
57
+ // Substitute env var placeholders (e.g. __UMAMI_WEBSITE_ID__)
58
+ indexHtmlShell = indexHtmlShell.replace(/__([A-Z0-9_]+)__/g, (_, key) => process.env[key] || '');
59
+ // Load bundled SSR runtime first — its install-global-dom-shim sets up
60
+ // the proper HTMLElement/window/document shims that @lit-labs/ssr needs.
61
+ // The lit-shared chunk handles missing HTMLElement via a fallback to its own shim,
62
+ // so no pre-installation is needed.
38
63
  const ssrRuntimePath = path.join(serverDir, 'ssr-runtime.js');
39
64
  let ssrRuntime = null;
40
65
  if (fs.existsSync(ssrRuntimePath)) {
41
66
  ssrRuntime = await import(ssrRuntimePath);
42
67
  }
43
68
  // Install additional DOM shims that NuralyUI components may need
69
+ // (must run AFTER SSR runtime so we don't block its window/HTMLElement setup)
44
70
  installDomShims();
45
71
  const pagesDir = path.join(projectDir, 'pages');
72
+ // Load bundled middleware at startup
73
+ const middlewareModules = new Map();
74
+ if (manifest.middlewares) {
75
+ for (const entry of manifest.middlewares) {
76
+ const modPath = path.join(serverDir, entry.module);
77
+ if (fs.existsSync(modPath)) {
78
+ try {
79
+ const mod = await import(modPath);
80
+ middlewareModules.set(entry.dir, extractMiddleware(mod));
81
+ }
82
+ catch (err) {
83
+ logger.error(`Failed to load middleware (${entry.dir || 'root'})`, { error: err?.message });
84
+ }
85
+ }
86
+ }
87
+ }
88
+ const middlewareEntries = manifest.middlewares
89
+ ? manifest.middlewares.map(e => ({ dir: e.dir, filePath: '' }))
90
+ : [];
91
+ // Load auth config if present
92
+ let authConfig = null;
93
+ let authMiddleware = null;
94
+ let authDb = null;
95
+ if (manifest.auth) {
96
+ try {
97
+ authConfig = await loadAuthConfigProd(serverDir, manifest.auth.configModule);
98
+ // Initialize DB for native auth
99
+ const { hasNativeAuth } = await import('../auth/config.js');
100
+ if (hasNativeAuth(authConfig)) {
101
+ try {
102
+ const { setProjectDir } = await import('../db/context.js');
103
+ const { useDb, waitForMigrations } = await import('../db/index.js');
104
+ const { ensureUsersTable } = await import('../auth/native-auth.js');
105
+ setProjectDir(projectDir);
106
+ authDb = useDb();
107
+ await waitForMigrations();
108
+ await ensureUsersTable(authDb);
109
+ // Run seed if not yet applied (SQLite and PG)
110
+ {
111
+ const seedModule = path.join(serverDir, 'seed.js');
112
+ if (fs.existsSync(seedModule)) {
113
+ try {
114
+ if (authDb.isPg) {
115
+ await authDb.exec(`CREATE TABLE IF NOT EXISTS _lumen_seed_applied (
116
+ name TEXT PRIMARY KEY,
117
+ applied_at TIMESTAMP NOT NULL DEFAULT NOW()
118
+ )`);
119
+ }
120
+ else {
121
+ await authDb.exec(`CREATE TABLE IF NOT EXISTS _lumen_seed_applied (
122
+ name TEXT PRIMARY KEY,
123
+ applied_at TEXT NOT NULL DEFAULT (datetime('now'))
124
+ )`);
125
+ }
126
+ const seedRow = authDb.isPg
127
+ ? await authDb.get('SELECT 1 FROM _lumen_seed_applied WHERE name = $1', 'data/seed.ts')
128
+ : await authDb.get('SELECT 1 FROM _lumen_seed_applied WHERE name = ?', 'data/seed.ts');
129
+ if (!seedRow) {
130
+ if (authDb.isPg) {
131
+ await authDb.run('INSERT INTO _lumen_seed_applied (name) VALUES ($1) ON CONFLICT DO NOTHING', 'data/seed.ts');
132
+ }
133
+ else {
134
+ await authDb.run('INSERT OR IGNORE INTO _lumen_seed_applied (name) VALUES (?)', 'data/seed.ts');
135
+ }
136
+ logger.info('Running seed file (prod)...');
137
+ const { default: seedFn } = await import(seedModule);
138
+ if (typeof seedFn === 'function')
139
+ await seedFn();
140
+ logger.info('Seed applied (prod).');
141
+ }
142
+ }
143
+ catch (seedErr) {
144
+ try {
145
+ if (authDb.isPg) {
146
+ await authDb.run('DELETE FROM _lumen_seed_applied WHERE name = $1', 'data/seed.ts');
147
+ }
148
+ else {
149
+ await authDb.run('DELETE FROM _lumen_seed_applied WHERE name = ?', 'data/seed.ts');
150
+ }
151
+ }
152
+ catch { }
153
+ logger.warn('Seed failed', { error: seedErr?.message });
154
+ }
155
+ }
156
+ }
157
+ }
158
+ catch (dbErr) {
159
+ logger.warn('Native auth DB init failed', { error: dbErr?.message });
160
+ }
161
+ }
162
+ authMiddleware = createAuthMiddleware(authConfig, authDb);
163
+ logger.info('Auth middleware loaded.');
164
+ }
165
+ catch (err) {
166
+ logger.error('Failed to load auth config', { error: err?.message });
167
+ }
168
+ }
169
+ const runMiddleware = (mw, req, res) => new Promise((resolve, reject) => mw(req, res, (err) => err ? reject(err) : resolve()));
46
170
  const server = http.createServer(async (req, res) => {
171
+ const startTime = Date.now();
47
172
  const url = req.url || '/';
48
173
  const [pathname, queryString] = url.split('?');
49
174
  const method = req.method || 'GET';
50
175
  try {
51
- // 1. API routes
176
+ // --- Production middleware pipeline ---
177
+ // Request ID (always first)
178
+ await runMiddleware(requestIdMiddleware, req, res);
179
+ // Health check (bypass everything else)
180
+ let healthHandled = false;
181
+ await new Promise(resolve => healthCheck(req, res, () => { resolve(); }));
182
+ if (res.writableEnded)
183
+ return;
184
+ // Security headers
185
+ await runMiddleware(securityHeaders, req, res);
186
+ // Rate limiting (stricter for auth routes)
187
+ if (pathname.startsWith('/__nk_auth/')) {
188
+ await runMiddleware(authRateLimiter, req, res);
189
+ }
190
+ else {
191
+ await runMiddleware(rateLimiter, req, res);
192
+ }
193
+ if (res.writableEnded)
194
+ return;
195
+ // --- Original request handling ---
196
+ // -2. Auth session middleware (attach req.nkAuth — must run before auth routes and user middleware)
197
+ if (authMiddleware && !pathname.includes('.') && !pathname.startsWith('/@')) {
198
+ await new Promise(resolve => authMiddleware(req, res, resolve));
199
+ if (res.writableEnded)
200
+ return;
201
+ }
202
+ // 0. Run user middleware chain (runs before auth routes so middleware can gate signup etc.)
203
+ if (middlewareModules.size > 0 && !pathname.includes('.') && (!pathname.startsWith('/__nk_') || pathname.startsWith('/__nk_auth/'))) {
204
+ const matching = getMiddlewareDirsForPathname(pathname, middlewareEntries);
205
+ const allMw = [];
206
+ for (const entry of matching) {
207
+ const mws = middlewareModules.get(entry.dir);
208
+ if (mws)
209
+ allMw.push(...mws);
210
+ }
211
+ if (allMw.length > 0) {
212
+ const err = await new Promise(resolve => runMiddlewareChain(allMw, req, res, resolve));
213
+ if (err) {
214
+ res.statusCode = 500;
215
+ res.end('Internal Server Error');
216
+ return;
217
+ }
218
+ if (res.writableEnded)
219
+ return;
220
+ }
221
+ }
222
+ // 1. Auth routes (login, logout, me, signup, etc. — runs after user middleware so invite gate can intercept)
223
+ if (authConfig && pathname.startsWith('/__nk_auth/')) {
224
+ const handled = await handleAuthRoutes(authConfig, req, res, authDb);
225
+ if (handled)
226
+ return;
227
+ }
228
+ // 2. API routes
52
229
  if (pathname.startsWith('/api/')) {
53
230
  await handleApiRoute(manifest, serverDir, pathname, queryString, method, req, res);
54
231
  return;
55
232
  }
56
- // 2. Static assets — try to serve from client dir
233
+ // 2b. Communication file upload
234
+ if (pathname === '/__nk_comm/upload' && method === 'POST') {
235
+ const userId = req.nkAuth?.user?.sub;
236
+ if (!userId) {
237
+ res.statusCode = 401;
238
+ res.end(JSON.stringify({ error: 'Unauthorized' }));
239
+ return;
240
+ }
241
+ const MAX_UPLOAD_SIZE = 10 * 1024 * 1024;
242
+ const chunks = [];
243
+ let uploadSize = 0;
244
+ let aborted = false;
245
+ req.on('data', (c) => {
246
+ uploadSize += c.length;
247
+ if (uploadSize > MAX_UPLOAD_SIZE) {
248
+ aborted = true;
249
+ req.destroy();
250
+ res.statusCode = 413;
251
+ res.end(JSON.stringify({ error: 'File too large' }));
252
+ return;
253
+ }
254
+ chunks.push(c);
255
+ });
256
+ req.on('end', async () => {
257
+ if (aborted)
258
+ return;
259
+ try {
260
+ const body = Buffer.concat(chunks);
261
+ const id = crypto.randomUUID();
262
+ const fileName = req.headers['x-filename'] || `file-${id}`;
263
+ const mimeType = req.headers['content-type'] || 'application/octet-stream';
264
+ const ext = fileName.includes('.') ? `.${fileName.split('.').pop()}` : '';
265
+ const key = `chat-uploads/${id}${ext}`;
266
+ // Use R2/S3 if configured, otherwise fall back to local disk
267
+ if (process.env.R2_BUCKET && process.env.R2_ENDPOINT) {
268
+ const { S3StorageAdapter } = await import('../storage/adapters/s3.js');
269
+ const s3 = new S3StorageAdapter({
270
+ bucket: process.env.R2_BUCKET,
271
+ region: 'auto',
272
+ accessKeyId: process.env.LUMENJS_S3_ACCESS_KEY || '',
273
+ secretAccessKey: process.env.LUMENJS_S3_SECRET_KEY || '',
274
+ endpoint: process.env.R2_ENDPOINT,
275
+ publicBaseUrl: process.env.R2_PUBLIC_URL,
276
+ });
277
+ const stored = await s3.put(body, { key, mimeType, fileName });
278
+ res.statusCode = 201;
279
+ res.setHeader('Content-Type', 'application/json');
280
+ res.end(JSON.stringify({ id, url: stored.url, size: body.length }));
281
+ }
282
+ else {
283
+ // Local fallback
284
+ const uploadDir = path.join(projectDir, 'data', 'uploads');
285
+ if (!fs.existsSync(uploadDir))
286
+ fs.mkdirSync(uploadDir, { recursive: true });
287
+ fs.writeFileSync(path.join(uploadDir, `${id}.bin`), body);
288
+ fs.writeFileSync(path.join(uploadDir, `${id}.meta.json`), JSON.stringify({ id, filename: fileName, mimetype: mimeType, size: body.length }));
289
+ res.statusCode = 201;
290
+ res.setHeader('Content-Type', 'application/json');
291
+ res.end(JSON.stringify({ id, url: `/__nk_comm/files/${id}`, size: body.length }));
292
+ }
293
+ }
294
+ catch (err) {
295
+ logger.error('Upload failed', { error: err?.message });
296
+ res.statusCode = 500;
297
+ res.end(JSON.stringify({ error: 'Upload failed' }));
298
+ }
299
+ });
300
+ return;
301
+ }
302
+ // Serve locally stored files (fallback when R2 is not configured)
303
+ if (pathname.startsWith('/__nk_comm/files/') && method === 'GET') {
304
+ const fileId = pathname.slice('/__nk_comm/files/'.length);
305
+ if (!/^[a-zA-Z0-9._-]+$/.test(fileId)) {
306
+ res.statusCode = 400;
307
+ res.end('Invalid file ID');
308
+ return;
309
+ }
310
+ const uploadDir = path.join(projectDir, 'data', 'uploads');
311
+ const filePath = path.resolve(uploadDir, `${fileId}.bin`);
312
+ if (!filePath.startsWith(path.resolve(uploadDir))) {
313
+ res.statusCode = 400;
314
+ res.end('Invalid file ID');
315
+ return;
316
+ }
317
+ if (!fs.existsSync(filePath)) {
318
+ res.statusCode = 404;
319
+ res.end('File not found');
320
+ return;
321
+ }
322
+ let contentType = 'application/octet-stream';
323
+ try {
324
+ const meta = JSON.parse(fs.readFileSync(path.resolve(uploadDir, `${fileId}.meta.json`), 'utf-8'));
325
+ contentType = meta.mimetype || contentType;
326
+ }
327
+ catch { }
328
+ const stat = fs.statSync(filePath);
329
+ res.setHeader('Content-Type', contentType);
330
+ res.setHeader('Content-Length', stat.size);
331
+ res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
332
+ fs.createReadStream(filePath).pipe(res);
333
+ return;
334
+ }
335
+ // 3. Static assets — try to serve from client dir
57
336
  if (pathname.includes('.')) {
58
337
  const served = serveStaticFile(clientDir, pathname, req, res);
59
338
  if (served)
@@ -66,25 +345,26 @@ export async function serveProject(options) {
66
345
  }
67
346
  // 4. Layout subscribe endpoint (SSE)
68
347
  if (pathname === '/__nk_subscribe/__layout/' || pathname === '/__nk_subscribe/__layout') {
69
- await handleLayoutSubscribeRequest(manifest, serverDir, queryString, req.headers, res);
348
+ const authUser = req.nkAuth?.user ?? undefined;
349
+ await handleLayoutSubscribeRequest(manifest, serverDir, queryString, req.headers, res, authUser);
70
350
  return;
71
351
  }
72
352
  // 5. Subscribe endpoint (SSE)
73
353
  if (pathname.startsWith('/__nk_subscribe/')) {
74
- await handleSubscribeRequest(manifest, serverDir, pagesDir, pathname, queryString, req.headers, res);
354
+ await handleSubscribeRequest(manifest, serverDir, pagesDir, pathname, queryString, req.headers, res, req.nkAuth?.user ?? undefined);
75
355
  return;
76
356
  }
77
357
  // 6. Layout loader endpoint
78
358
  if (pathname === '/__nk_loader/__layout/' || pathname === '/__nk_loader/__layout') {
79
- await handleLayoutLoaderRequest(manifest, serverDir, queryString, req.headers, res);
359
+ await handleLayoutLoaderRequest(manifest, serverDir, queryString, req.headers, res, req.nkAuth?.user ?? undefined);
80
360
  return;
81
361
  }
82
362
  // 7. Loader endpoint for client-side navigation
83
363
  if (pathname.startsWith('/__nk_loader/')) {
84
- await handleLoaderRequest(manifest, serverDir, pagesDir, pathname, queryString, req.headers, res);
364
+ await handleLoaderRequest(manifest, serverDir, pagesDir, pathname, queryString, req.headers, res, req.nkAuth?.user ?? undefined);
85
365
  return;
86
366
  }
87
- // 6. Resolve locale and strip prefix for page routing
367
+ // 8. Resolve locale and strip prefix for page routing
88
368
  let resolvedPathname = pathname;
89
369
  let locale;
90
370
  if (manifest.i18n) {
@@ -92,16 +372,59 @@ export async function serveProject(options) {
92
372
  resolvedPathname = result.pathname;
93
373
  locale = result.locale;
94
374
  }
95
- // 7. Page routes SSR render
375
+ // 9. Check for pre-rendered HTML file
376
+ const prerenderFile = path.join(clientDir, resolvedPathname === '/' ? '' : resolvedPathname, 'index.html');
377
+ if (resolvedPathname !== '/' && fs.existsSync(prerenderFile)) {
378
+ const prerenderHtml = fs.readFileSync(prerenderFile, 'utf-8');
379
+ sendCompressed(req, res, 200, 'text/html; charset=utf-8', prerenderHtml);
380
+ return;
381
+ }
382
+ // Check if the root index.html is a pre-rendered page (has data-nk-ssr attribute)
383
+ if (resolvedPathname === '/') {
384
+ const rootRoute = manifest.routes.find(r => r.path === '/');
385
+ if (rootRoute?.prerender) {
386
+ sendCompressed(req, res, 200, 'text/html; charset=utf-8', indexHtmlShell);
387
+ return;
388
+ }
389
+ }
390
+ // 10. Page routes — SSR render
96
391
  await handlePageRoute(manifest, serverDir, pagesDir, resolvedPathname, queryString, indexHtmlShell, title, ssrRuntime, req, res);
97
392
  }
98
393
  catch (err) {
99
- console.error('[LumenJS] Request error:', err);
394
+ logger.error('Request error', {
395
+ method, url: pathname, error: err?.message, stack: err?.stack,
396
+ requestId: getRequestId(req),
397
+ });
100
398
  const html = renderErrorPage(500, 'Something went wrong', 'An unexpected error occurred while processing your request.', process.env.NODE_ENV !== 'production' ? err?.stack || err?.message : undefined);
101
399
  sendCompressed(req, res, 500, 'text/html; charset=utf-8', html);
102
400
  }
401
+ finally {
402
+ // Log request completion
403
+ const duration = Date.now() - startTime;
404
+ logger.request(req, res.statusCode, duration, { requestId: getRequestId(req) });
405
+ }
406
+ });
407
+ // Socket.IO setup (attach before listen so it shares the HTTP server)
408
+ const socketRoutes = manifest.routes
409
+ .filter(r => r.hasSocket && r.module)
410
+ .map(r => ({ path: r.path, hasSocket: true, filePath: path.join(serverDir, r.module) }));
411
+ if (socketRoutes.length > 0) {
412
+ setupSocketIO({
413
+ httpServer: server,
414
+ loadModule: (fp) => import(fp),
415
+ routes: socketRoutes,
416
+ projectDir,
417
+ }).catch((err) => {
418
+ logger.warn('Socket.IO setup failed', { error: err?.message });
419
+ });
420
+ }
421
+ // Graceful shutdown
422
+ setupGracefulShutdown(server, {
423
+ onShutdown: async () => {
424
+ logger.info('Cleaning up resources...');
425
+ },
103
426
  });
104
427
  server.listen(port, () => {
105
- console.log(`[LumenJS] Production server running at http://localhost:${port}`);
428
+ logger.info(`Production server running at http://localhost:${port}`, { port });
106
429
  });
107
430
  }
package/dist/cli.js CHANGED
@@ -9,19 +9,32 @@ function getArg(name) {
9
9
  return undefined;
10
10
  }
11
11
  const USAGE = `Usage:
12
- lumenjs dev [--project <dir>] [--port <port>] [--base <path>] [--editor-mode]
13
- lumenjs build [--project <dir>] [--out <dir>]
14
- lumenjs serve [--project <dir>] [--port <port>]
15
- lumenjs add <integration>`;
16
- if (!command || !['dev', 'build', 'serve', 'add'].includes(command)) {
12
+ lumenjs create <name> [--template <default|blog|dashboard|social>]
13
+ lumenjs dev [--project <dir>] [--port <port>] [--base <path>] [--editor-mode]
14
+ lumenjs build [--project <dir>] [--out <dir>]
15
+ lumenjs serve [--project <dir>] [--port <port>]
16
+ lumenjs add <integration>
17
+ lumenjs db seed [--force] [--project <dir>]`;
18
+ if (!command || !['create', 'dev', 'build', 'serve', 'add', 'db'].includes(command)) {
17
19
  console.error(USAGE);
18
20
  process.exit(1);
19
21
  }
20
22
  const projectDir = path.resolve(getArg('project') || '.');
21
23
  async function main() {
22
- if (command === 'dev') {
24
+ if (command === 'create') {
25
+ const { createProject } = await import('./create.js');
26
+ const name = args[1];
27
+ const template = getArg('template') || 'default';
28
+ await createProject(name, template);
29
+ return;
30
+ }
31
+ else if (command === 'dev') {
23
32
  const { createDevServer } = await import('./dev-server/server.js');
24
33
  const port = parseInt(getArg('port') || '3000', 10);
34
+ if (isNaN(port) || port < 1 || port > 65535) {
35
+ console.error(`Invalid port number. Must be between 1 and 65535.`);
36
+ process.exit(1);
37
+ }
25
38
  const editorMode = args.includes('--editor-mode');
26
39
  const base = getArg('base') || '/';
27
40
  console.log(`[LumenJS] Starting dev server...`);
@@ -53,11 +66,29 @@ async function main() {
53
66
  else if (command === 'serve') {
54
67
  const { serveProject } = await import('./build/serve.js');
55
68
  const port = parseInt(getArg('port') || '3000', 10);
69
+ if (isNaN(port) || port < 1 || port > 65535) {
70
+ console.error(`Invalid port number. Must be between 1 and 65535.`);
71
+ process.exit(1);
72
+ }
56
73
  console.log(`[LumenJS] Starting production server...`);
57
74
  console.log(` Project: ${projectDir}`);
58
75
  console.log(` Port: ${port}`);
59
76
  await serveProject({ projectDir, port });
60
77
  }
78
+ else if (command === 'db') {
79
+ const subcommand = args[1];
80
+ if (subcommand === 'seed') {
81
+ const { setProjectDir } = await import('./db/context.js');
82
+ const { runSeed } = await import('./db/seed.js');
83
+ setProjectDir(projectDir);
84
+ const force = args.includes('--force');
85
+ await runSeed(projectDir, force);
86
+ }
87
+ else {
88
+ console.error(`Unknown db subcommand: ${subcommand}\n\n${USAGE}`);
89
+ process.exit(1);
90
+ }
91
+ }
61
92
  }
62
93
  main().catch(err => {
63
94
  console.error('[LumenJS] Failed to start:', err);