@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.
- package/README.md +62 -282
- package/dist/auth/config.d.ts +23 -0
- package/dist/auth/config.js +115 -0
- package/dist/auth/guard.d.ts +12 -0
- package/dist/auth/guard.js +28 -0
- package/dist/auth/index.d.ts +3 -0
- package/dist/auth/index.js +1 -0
- package/dist/auth/middleware.d.ts +23 -0
- package/dist/auth/middleware.js +89 -0
- package/dist/auth/native-auth.d.ts +82 -0
- package/dist/auth/native-auth.js +340 -0
- package/dist/auth/oidc-client.d.ts +17 -0
- package/dist/auth/oidc-client.js +123 -0
- package/dist/auth/providers/google.d.ts +23 -0
- package/dist/auth/providers/google.js +25 -0
- package/dist/auth/providers/index.d.ts +2 -0
- package/dist/auth/providers/index.js +1 -0
- package/dist/auth/routes/login.d.ts +8 -0
- package/dist/auth/routes/login.js +121 -0
- package/dist/auth/routes/logout.d.ts +4 -0
- package/dist/auth/routes/logout.js +79 -0
- package/dist/auth/routes/oidc-callback.d.ts +3 -0
- package/dist/auth/routes/oidc-callback.js +70 -0
- package/dist/auth/routes/password.d.ts +5 -0
- package/dist/auth/routes/password.js +149 -0
- package/dist/auth/routes/signup.d.ts +3 -0
- package/dist/auth/routes/signup.js +81 -0
- package/dist/auth/routes/token.d.ts +4 -0
- package/dist/auth/routes/token.js +70 -0
- package/dist/auth/routes/totp.d.ts +22 -0
- package/dist/auth/routes/totp.js +232 -0
- package/dist/auth/routes/utils.d.ts +7 -0
- package/dist/auth/routes/utils.js +35 -0
- package/dist/auth/routes/verify.d.ts +3 -0
- package/dist/auth/routes/verify.js +26 -0
- package/dist/auth/routes.d.ts +8 -0
- package/dist/auth/routes.js +124 -0
- package/dist/auth/session.d.ts +8 -0
- package/dist/auth/session.js +54 -0
- package/dist/auth/token.d.ts +33 -0
- package/dist/auth/token.js +90 -0
- package/dist/auth/types.d.ts +156 -0
- package/dist/auth/types.js +2 -0
- package/dist/build/build-client.d.ts +15 -0
- package/dist/build/build-client.js +45 -0
- package/dist/build/build-prerender.d.ts +11 -0
- package/dist/build/build-prerender.js +159 -0
- package/dist/build/build-server.d.ts +18 -0
- package/dist/build/build-server.js +107 -0
- package/dist/build/build.js +60 -123
- package/dist/build/scan.d.ts +18 -0
- package/dist/build/scan.js +77 -6
- package/dist/build/serve-api.js +8 -2
- package/dist/build/serve-loaders.d.ts +4 -4
- package/dist/build/serve-loaders.js +26 -18
- package/dist/build/serve-ssr.js +38 -11
- package/dist/build/serve-static.js +3 -3
- package/dist/build/serve.js +341 -18
- package/dist/cli.js +37 -6
- package/dist/communication/encryption.d.ts +35 -0
- package/dist/communication/encryption.js +90 -0
- package/dist/communication/handlers/context.d.ts +27 -0
- package/dist/communication/handlers/context.js +1 -0
- package/dist/communication/handlers/conversation.d.ts +24 -0
- package/dist/communication/handlers/conversation.js +113 -0
- package/dist/communication/handlers/file-upload.d.ts +17 -0
- package/dist/communication/handlers/file-upload.js +62 -0
- package/dist/communication/handlers/messaging.d.ts +30 -0
- package/dist/communication/handlers/messaging.js +237 -0
- package/dist/communication/handlers/presence.d.ts +15 -0
- package/dist/communication/handlers/presence.js +76 -0
- package/dist/communication/handlers.d.ts +5 -0
- package/dist/communication/handlers.js +5 -0
- package/dist/communication/index.d.ts +9 -0
- package/dist/communication/index.js +7 -0
- package/dist/communication/link-preview.d.ts +18 -0
- package/dist/communication/link-preview.js +115 -0
- package/dist/communication/schema.d.ts +10 -0
- package/dist/communication/schema.js +101 -0
- package/dist/communication/server.d.ts +86 -0
- package/dist/communication/server.js +212 -0
- package/dist/communication/signaling.d.ts +43 -0
- package/dist/communication/signaling.js +271 -0
- package/dist/communication/store.d.ts +71 -0
- package/dist/communication/store.js +289 -0
- package/dist/communication/types.d.ts +454 -0
- package/dist/communication/types.js +1 -0
- package/dist/create.d.ts +1 -0
- package/dist/create.js +55 -0
- package/dist/db/auto-migrate.d.ts +3 -0
- package/dist/db/auto-migrate.js +100 -0
- package/dist/db/client.d.ts +3 -0
- package/dist/db/client.js +18 -0
- package/dist/db/index.d.ts +17 -13
- package/dist/db/index.js +205 -26
- package/dist/db/seed.d.ts +12 -0
- package/dist/db/seed.js +88 -0
- package/dist/db/table.d.ts +10 -0
- package/dist/db/table.js +12 -0
- package/dist/dev-server/config.d.ts +11 -0
- package/dist/dev-server/config.js +40 -20
- package/dist/dev-server/index-html.d.ts +4 -0
- package/dist/dev-server/index-html.js +21 -6
- package/dist/dev-server/nuralyui-aliases.d.ts +0 -4
- package/dist/dev-server/nuralyui-aliases.js +115 -94
- package/dist/dev-server/plugins/vite-plugin-api-routes.js +29 -5
- package/dist/dev-server/plugins/vite-plugin-auth.d.ts +6 -0
- package/dist/dev-server/plugins/vite-plugin-auth.js +223 -0
- package/dist/dev-server/plugins/vite-plugin-auto-define.d.ts +16 -0
- package/dist/dev-server/plugins/vite-plugin-auto-define.js +111 -0
- package/dist/dev-server/plugins/vite-plugin-communication.d.ts +6 -0
- package/dist/dev-server/plugins/vite-plugin-communication.js +205 -0
- package/dist/dev-server/plugins/vite-plugin-editor-api.d.ts +6 -0
- package/dist/dev-server/plugins/vite-plugin-editor-api.js +318 -0
- package/dist/dev-server/plugins/vite-plugin-i18n.js +69 -2
- package/dist/dev-server/plugins/vite-plugin-lit-dedup.d.ts +6 -0
- package/dist/dev-server/plugins/vite-plugin-lit-dedup.js +78 -34
- package/dist/dev-server/plugins/vite-plugin-lit-hmr.js +44 -2
- package/dist/dev-server/plugins/vite-plugin-llms.d.ts +2 -0
- package/dist/dev-server/plugins/vite-plugin-llms.js +92 -0
- package/dist/dev-server/plugins/vite-plugin-loaders.js +146 -13
- package/dist/dev-server/plugins/vite-plugin-routes.js +16 -5
- package/dist/dev-server/plugins/vite-plugin-socketio.d.ts +2 -0
- package/dist/dev-server/plugins/vite-plugin-socketio.js +51 -0
- package/dist/dev-server/plugins/vite-plugin-source-annotator.d.ts +2 -0
- package/dist/dev-server/plugins/vite-plugin-source-annotator.js +26 -3
- package/dist/dev-server/plugins/vite-plugin-storage.d.ts +10 -0
- package/dist/dev-server/plugins/vite-plugin-storage.js +126 -0
- package/dist/dev-server/plugins/vite-plugin-virtual-modules.js +140 -3
- package/dist/dev-server/server.js +242 -70
- package/dist/dev-server/ssr-render.d.ts +2 -1
- package/dist/dev-server/ssr-render.js +117 -50
- package/dist/editor/ai/backend.d.ts +20 -0
- package/dist/editor/ai/backend.js +113 -0
- package/dist/editor/ai/claude-code-client.d.ts +20 -0
- package/dist/editor/ai/claude-code-client.js +145 -0
- package/dist/editor/ai/deepseek-client.d.ts +7 -0
- package/dist/editor/ai/deepseek-client.js +113 -0
- package/dist/editor/ai/opencode-client.d.ts +14 -0
- package/dist/editor/ai/opencode-client.js +99 -0
- package/dist/editor/ai/snapshot-store.d.ts +22 -0
- package/dist/editor/ai/snapshot-store.js +35 -0
- package/dist/editor/ai/types.d.ts +30 -0
- package/dist/editor/ai/types.js +136 -0
- package/dist/editor/ai-chat-panel.d.ts +13 -0
- package/dist/editor/ai-chat-panel.js +613 -0
- package/dist/editor/ai-markdown.d.ts +10 -0
- package/dist/editor/ai-markdown.js +70 -0
- package/dist/editor/ai-project-panel.d.ts +11 -0
- package/dist/editor/ai-project-panel.js +332 -0
- package/dist/editor/ast-modification.d.ts +11 -0
- package/dist/editor/ast-modification.js +1 -0
- package/dist/editor/ast-service.d.ts +30 -0
- package/dist/editor/ast-service.js +180 -0
- package/dist/editor/css-rules.d.ts +54 -0
- package/dist/editor/css-rules.js +423 -0
- package/dist/editor/editor-api-client.d.ts +51 -0
- package/dist/editor/editor-api-client.js +162 -0
- package/dist/editor/editor-bridge.d.ts +1 -0
- package/dist/editor/editor-bridge.js +18 -8
- package/dist/editor/editor-toolbar.d.ts +14 -0
- package/dist/editor/editor-toolbar.js +115 -0
- package/dist/editor/file-editor.d.ts +9 -0
- package/dist/editor/file-editor.js +236 -0
- package/dist/editor/file-service.d.ts +16 -0
- package/dist/editor/file-service.js +52 -0
- package/dist/editor/i18n-key-gen.d.ts +1 -0
- package/dist/editor/i18n-key-gen.js +7 -0
- package/dist/editor/inline-text-edit.d.ts +5 -0
- package/dist/editor/inline-text-edit.js +173 -92
- package/dist/editor/overlay-events.d.ts +5 -0
- package/dist/editor/overlay-events.js +364 -0
- package/dist/editor/overlay-hmr.d.ts +2 -0
- package/dist/editor/overlay-hmr.js +76 -0
- package/dist/editor/overlay-selection.d.ts +29 -0
- package/dist/editor/overlay-selection.js +148 -0
- package/dist/editor/overlay-utils.d.ts +12 -0
- package/dist/editor/overlay-utils.js +59 -0
- package/dist/editor/properties-panel-persist.d.ts +14 -0
- package/dist/editor/properties-panel-persist.js +70 -0
- package/dist/editor/properties-panel-rows.d.ts +10 -0
- package/dist/editor/properties-panel-rows.js +349 -0
- package/dist/editor/properties-panel-styles.d.ts +4 -0
- package/dist/editor/properties-panel-styles.js +174 -0
- package/dist/editor/properties-panel.d.ts +4 -0
- package/dist/editor/properties-panel.js +148 -0
- package/dist/editor/property-registry.d.ts +16 -0
- package/dist/editor/property-registry.js +303 -0
- package/dist/editor/standalone-file-panel.d.ts +0 -0
- package/dist/editor/standalone-file-panel.js +1 -0
- package/dist/editor/standalone-overlay-dom.d.ts +0 -0
- package/dist/editor/standalone-overlay-dom.js +1 -0
- package/dist/editor/standalone-overlay-styles.d.ts +0 -0
- package/dist/editor/standalone-overlay-styles.js +1 -0
- package/dist/editor/standalone-overlay.d.ts +1 -0
- package/dist/editor/standalone-overlay.js +76 -0
- package/dist/editor/syntax-highlighter.d.ts +4 -0
- package/dist/editor/syntax-highlighter.js +81 -0
- package/dist/editor/text-toolbar.d.ts +11 -0
- package/dist/editor/text-toolbar.js +327 -0
- package/dist/editor/toolbar-styles.d.ts +4 -0
- package/dist/editor/toolbar-styles.js +198 -0
- package/dist/email/index.d.ts +32 -0
- package/dist/email/index.js +154 -0
- package/dist/email/providers/resend.d.ts +2 -0
- package/dist/email/providers/resend.js +24 -0
- package/dist/email/providers/sendgrid.d.ts +2 -0
- package/dist/email/providers/sendgrid.js +31 -0
- package/dist/email/providers/smtp.d.ts +13 -0
- package/dist/email/providers/smtp.js +125 -0
- package/dist/email/template-engine.d.ts +18 -0
- package/dist/email/template-engine.js +116 -0
- package/dist/email/templates/base.d.ts +9 -0
- package/dist/email/templates/base.js +65 -0
- package/dist/email/templates/password-reset.d.ts +5 -0
- package/dist/email/templates/password-reset.js +15 -0
- package/dist/email/templates/verify-email.d.ts +5 -0
- package/dist/email/templates/verify-email.js +15 -0
- package/dist/email/templates/welcome.d.ts +5 -0
- package/dist/email/templates/welcome.js +13 -0
- package/dist/email/types.d.ts +49 -0
- package/dist/email/types.js +1 -0
- package/dist/llms/generate.d.ts +46 -0
- package/dist/llms/generate.js +185 -0
- package/dist/permissions/guard.d.ts +28 -0
- package/dist/permissions/guard.js +30 -0
- package/dist/permissions/index.d.ts +6 -0
- package/dist/permissions/index.js +3 -0
- package/dist/permissions/service.d.ts +80 -0
- package/dist/permissions/service.js +210 -0
- package/dist/permissions/tables.d.ts +5 -0
- package/dist/permissions/tables.js +68 -0
- package/dist/permissions/types.d.ts +33 -0
- package/dist/permissions/types.js +1 -0
- package/dist/runtime/app-shell.d.ts +1 -1
- package/dist/runtime/app-shell.js +164 -0
- package/dist/runtime/auth.d.ts +10 -0
- package/dist/runtime/auth.js +30 -0
- package/dist/runtime/communication.d.ts +137 -0
- package/dist/runtime/communication.js +228 -0
- package/dist/runtime/error-boundary.d.ts +23 -0
- package/dist/runtime/error-boundary.js +120 -0
- package/dist/runtime/i18n.d.ts +6 -1
- package/dist/runtime/i18n.js +42 -21
- package/dist/runtime/island.d.ts +16 -0
- package/dist/runtime/island.js +80 -0
- package/dist/runtime/router-data.d.ts +3 -0
- package/dist/runtime/router-data.js +102 -17
- package/dist/runtime/router-hydration.js +34 -2
- package/dist/runtime/router.d.ts +19 -2
- package/dist/runtime/router.js +237 -43
- package/dist/runtime/socket-client.d.ts +2 -0
- package/dist/runtime/socket-client.js +30 -0
- package/dist/runtime/webrtc.d.ts +91 -0
- package/dist/runtime/webrtc.js +428 -0
- package/dist/shared/dom-shims.js +4 -2
- package/dist/shared/graceful-shutdown.d.ts +8 -0
- package/dist/shared/graceful-shutdown.js +36 -0
- package/dist/shared/health.d.ts +8 -0
- package/dist/shared/health.js +25 -0
- package/dist/shared/llms-txt.d.ts +31 -0
- package/dist/shared/llms-txt.js +85 -0
- package/dist/shared/logger.d.ts +32 -0
- package/dist/shared/logger.js +93 -0
- package/dist/shared/meta.d.ts +27 -0
- package/dist/shared/meta.js +71 -0
- package/dist/shared/middleware-runner.d.ts +9 -0
- package/dist/shared/middleware-runner.js +29 -0
- package/dist/shared/rate-limit.d.ts +18 -0
- package/dist/shared/rate-limit.js +71 -0
- package/dist/shared/request-id.d.ts +5 -0
- package/dist/shared/request-id.js +18 -0
- package/dist/shared/route-matching.js +16 -1
- package/dist/shared/security-headers.d.ts +18 -0
- package/dist/shared/security-headers.js +38 -0
- package/dist/shared/socket-io-setup.d.ts +11 -0
- package/dist/shared/socket-io-setup.js +51 -0
- package/dist/shared/types.d.ts +15 -0
- package/dist/shared/utils.d.ts +33 -7
- package/dist/shared/utils.js +164 -27
- package/dist/storage/adapters/local.d.ts +44 -0
- package/dist/storage/adapters/local.js +85 -0
- package/dist/storage/adapters/s3.d.ts +32 -0
- package/dist/storage/adapters/s3.js +119 -0
- package/dist/storage/adapters/types.d.ts +53 -0
- package/dist/storage/adapters/types.js +1 -0
- package/dist/storage/index.d.ts +76 -0
- package/dist/storage/index.js +83 -0
- package/package.json +45 -7
- package/templates/blog/api/posts.ts +4 -18
- package/templates/blog/data/migrations/001_init.sql +6 -5
- package/templates/blog/lumenjs.config.ts +3 -0
- package/templates/blog/package.json +14 -0
- package/templates/blog/pages/_layout.ts +25 -0
- package/templates/blog/pages/index.ts +48 -22
- package/templates/blog/pages/posts/[slug].ts +45 -20
- package/templates/blog/pages/tag/[tag].ts +44 -0
- package/templates/dashboard/api/stats.ts +8 -5
- package/templates/dashboard/lumenjs.config.ts +3 -0
- package/templates/dashboard/package.json +14 -0
- package/templates/dashboard/pages/_layout.ts +25 -0
- package/templates/dashboard/pages/index.ts +54 -23
- package/templates/dashboard/pages/settings/index.ts +29 -0
- package/templates/default/lumenjs.config.ts +3 -0
- package/templates/default/package.json +14 -0
- package/templates/default/pages/index.ts +24 -0
package/dist/build/serve-ssr.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
134
|
+
logger.error('Page handler error', { error: err?.message });
|
|
116
135
|
}
|
|
117
136
|
}
|
|
118
137
|
}
|
|
119
|
-
// SPA fallback — serve the built index.html
|
|
120
|
-
|
|
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
|
|
56
|
-
const filePath = path.
|
|
57
|
-
if (!filePath.startsWith(
|
|
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()) {
|
package/dist/build/serve.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
53
|
+
logger.fatal('No index.html found in build output.');
|
|
33
54
|
process.exit(1);
|
|
34
55
|
}
|
|
35
|
-
|
|
36
|
-
//
|
|
37
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
13
|
-
lumenjs
|
|
14
|
-
lumenjs
|
|
15
|
-
lumenjs
|
|
16
|
-
|
|
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 === '
|
|
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);
|