@nuraly/lumenjs 0.1.2 → 0.1.4
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 +76 -235
- 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 +73 -0
- package/dist/auth/native-auth.js +293 -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 +98 -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/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 +110 -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 +17 -0
- package/dist/build/build-server.js +98 -0
- package/dist/build/build.js +52 -120
- package/dist/build/scan.d.ts +19 -0
- package/dist/build/scan.js +77 -6
- package/dist/build/serve-api.js +8 -2
- package/dist/build/serve-loaders.d.ts +4 -2
- package/dist/build/serve-loaders.js +128 -10
- package/dist/build/serve-ssr.js +38 -11
- package/dist/build/serve-static.js +3 -3
- package/dist/build/serve.js +229 -14
- 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/context.d.ts +2 -0
- package/dist/db/context.js +9 -0
- package/dist/db/index.d.ts +23 -0
- package/dist/db/index.js +258 -0
- 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 +14 -0
- package/dist/dev-server/config.js +26 -9
- package/dist/dev-server/index-html.d.ts +3 -0
- package/dist/dev-server/index-html.js +18 -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.d.ts +0 -1
- package/dist/dev-server/plugins/vite-plugin-loaders.js +311 -42
- package/dist/dev-server/plugins/vite-plugin-routes.js +18 -6
- 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 +111 -2
- package/dist/dev-server/server.js +128 -12
- package/dist/dev-server/ssr-render.d.ts +2 -1
- package/dist/dev-server/ssr-render.js +107 -48
- package/dist/editor/ai/backend.d.ts +20 -0
- package/dist/editor/ai/backend.js +104 -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/opencode-client.d.ts +14 -0
- package/dist/editor/ai/opencode-client.js +125 -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 +587 -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 +17 -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 +75 -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.js +163 -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/router-data.d.ts +5 -0
- package/dist/runtime/router-data.js +121 -16
- package/dist/runtime/router-hydration.js +25 -0
- package/dist/runtime/router.d.ts +21 -1
- package/dist/runtime/router.js +221 -39
- package/dist/runtime/socket-client.d.ts +2 -0
- package/dist/runtime/socket-client.js +30 -0
- package/dist/runtime/webrtc.d.ts +47 -0
- package/dist/runtime/webrtc.js +178 -0
- 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 +16 -0
- package/dist/shared/utils.d.ts +37 -7
- package/dist/shared/utils.js +175 -26
- 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 +116 -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 +20 -1
- package/templates/blog/api/posts.ts +6 -0
- package/templates/blog/data/migrations/001_init.sql +13 -0
- 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 +65 -0
- package/templates/blog/pages/posts/[slug].ts +60 -0
- package/templates/blog/pages/tag/[tag].ts +44 -0
- package/templates/dashboard/api/stats.ts +10 -0
- package/templates/dashboard/data/migrations/001_init.sql +13 -0
- 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 +72 -0
- 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/templates/social/api/posts/[id].ts +14 -0
- package/templates/social/api/posts.ts +11 -0
- package/templates/social/api/profile/[username].ts +10 -0
- package/templates/social/api/upload.ts +19 -0
- package/templates/social/data/migrations/001_init.sql +78 -0
- package/templates/social/data/migrations/002_add_image_url.sql +1 -0
- package/templates/social/data/migrations/003_auth.sql +7 -0
- package/templates/social/docs/architecture.md +76 -0
- package/templates/social/docs/components.md +100 -0
- package/templates/social/docs/data.md +89 -0
- package/templates/social/docs/pages.md +96 -0
- package/templates/social/docs/theming.md +52 -0
- package/templates/social/lib/media.ts +130 -0
- package/templates/social/lumenjs.auth.ts +21 -0
- package/templates/social/lumenjs.config.ts +3 -0
- package/templates/social/package.json +5 -0
- package/templates/social/pages/_layout.ts +239 -0
- package/templates/social/pages/apps/[id].ts +173 -0
- package/templates/social/pages/apps/index.ts +116 -0
- package/templates/social/pages/auth/login.ts +92 -0
- package/templates/social/pages/bookmarks.ts +57 -0
- package/templates/social/pages/explore.ts +73 -0
- package/templates/social/pages/index.ts +351 -0
- package/templates/social/pages/messages.ts +298 -0
- package/templates/social/pages/new.ts +77 -0
- package/templates/social/pages/notifications.ts +73 -0
- package/templates/social/pages/post/[id].ts +124 -0
- package/templates/social/pages/profile/[username].ts +100 -0
- package/templates/social/pages/settings/accessibility.ts +153 -0
- package/templates/social/pages/settings/account.ts +260 -0
- package/templates/social/pages/settings/help.ts +141 -0
- package/templates/social/pages/settings/language.ts +103 -0
- package/templates/social/pages/settings/privacy.ts +183 -0
- package/templates/social/pages/settings/security.ts +133 -0
- package/templates/social/pages/settings.ts +185 -0
|
@@ -15,7 +15,6 @@ import { installDomShims } from '../../shared/dom-shims.js';
|
|
|
15
15
|
* return { item: data, timestamp: Date.now() };
|
|
16
16
|
* }
|
|
17
17
|
*
|
|
18
|
-
* @customElement('page-item')
|
|
19
18
|
* export class PageItem extends LitElement {
|
|
20
19
|
* @property({ type: Object }) loaderData = {};
|
|
21
20
|
* render() {
|
|
@@ -31,6 +30,83 @@ export function lumenLoadersPlugin(pagesDir) {
|
|
|
31
30
|
return {
|
|
32
31
|
name: 'lumenjs-loaders',
|
|
33
32
|
configureServer(server) {
|
|
33
|
+
// SSE subscribe middleware
|
|
34
|
+
server.middlewares.use(async (req, res, next) => {
|
|
35
|
+
if (!req.url?.startsWith('/__nk_subscribe/')) {
|
|
36
|
+
return next();
|
|
37
|
+
}
|
|
38
|
+
const [pathname, queryString] = req.url.split('?');
|
|
39
|
+
// Parse query params
|
|
40
|
+
const query = {};
|
|
41
|
+
if (queryString) {
|
|
42
|
+
for (const pair of queryString.split('&')) {
|
|
43
|
+
const [key, val] = pair.split('=');
|
|
44
|
+
query[decodeURIComponent(key)] = decodeURIComponent(val || '');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Handle layout subscribe: /__nk_subscribe/__layout/?__dir=<dir>
|
|
48
|
+
if (pathname === '/__nk_subscribe/__layout/' || pathname === '/__nk_subscribe/__layout') {
|
|
49
|
+
const dir = query.__dir || '';
|
|
50
|
+
await handleLayoutSubscribe(server, pagesDir, dir, query, req, res);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const pagePath = pathname.replace('/__nk_subscribe', '') || '/';
|
|
54
|
+
// Parse URL params
|
|
55
|
+
let params = {};
|
|
56
|
+
if (query.__params) {
|
|
57
|
+
try {
|
|
58
|
+
params = JSON.parse(query.__params);
|
|
59
|
+
}
|
|
60
|
+
catch { /* ignore */ }
|
|
61
|
+
delete query.__params;
|
|
62
|
+
}
|
|
63
|
+
const filePath = resolvePageFile(pagesDir, pagePath);
|
|
64
|
+
if (filePath && !filePath.startsWith(path.resolve(pagesDir) + path.sep)) {
|
|
65
|
+
res.statusCode = 400;
|
|
66
|
+
res.end();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (!filePath) {
|
|
70
|
+
res.statusCode = 404;
|
|
71
|
+
res.end();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (Object.keys(params).length === 0) {
|
|
75
|
+
Object.assign(params, extractRouteParams(pagesDir, pagePath, filePath));
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
installDomShims();
|
|
79
|
+
const mod = await server.ssrLoadModule(filePath);
|
|
80
|
+
if (!mod.subscribe || typeof mod.subscribe !== 'function') {
|
|
81
|
+
res.statusCode = 204;
|
|
82
|
+
res.end();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
// Set SSE headers
|
|
86
|
+
res.writeHead(200, {
|
|
87
|
+
'Content-Type': 'text/event-stream',
|
|
88
|
+
'Cache-Control': 'no-cache',
|
|
89
|
+
'Connection': 'keep-alive',
|
|
90
|
+
});
|
|
91
|
+
const locale = query.__locale;
|
|
92
|
+
const push = (data) => {
|
|
93
|
+
res.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
94
|
+
};
|
|
95
|
+
const cleanup = mod.subscribe({ params, push, headers: req.headers, locale, user: req.nkAuth?.user ?? null });
|
|
96
|
+
res.on('close', () => {
|
|
97
|
+
if (typeof cleanup === 'function')
|
|
98
|
+
cleanup();
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
console.error(`[LumenJS] Subscribe error for ${pagePath}:`, err);
|
|
103
|
+
if (!res.headersSent) {
|
|
104
|
+
res.statusCode = 500;
|
|
105
|
+
res.end();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
// Loader middleware
|
|
34
110
|
server.middlewares.use(async (req, res, next) => {
|
|
35
111
|
if (!req.url?.startsWith('/__nk_loader/')) {
|
|
36
112
|
return next();
|
|
@@ -60,8 +136,14 @@ export function lumenLoadersPlugin(pagesDir) {
|
|
|
60
136
|
catch { /* ignore */ }
|
|
61
137
|
delete query.__params;
|
|
62
138
|
}
|
|
63
|
-
// Find the page file
|
|
139
|
+
// Find the page file (validate path stays within pagesDir)
|
|
64
140
|
const filePath = resolvePageFile(pagesDir, pagePath);
|
|
141
|
+
if (filePath && !filePath.startsWith(path.resolve(pagesDir) + path.sep)) {
|
|
142
|
+
res.statusCode = 400;
|
|
143
|
+
res.setHeader('Content-Type', 'application/json');
|
|
144
|
+
res.end(JSON.stringify({ error: 'Invalid page path' }));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
65
147
|
if (!filePath) {
|
|
66
148
|
res.statusCode = 404;
|
|
67
149
|
res.setHeader('Content-Type', 'application/json');
|
|
@@ -86,7 +168,43 @@ export function lumenLoadersPlugin(pagesDir) {
|
|
|
86
168
|
// Extract locale from query if provided by the client router
|
|
87
169
|
const locale = query.__locale;
|
|
88
170
|
delete query.__locale;
|
|
89
|
-
|
|
171
|
+
// If auth middleware hasn't populated nkAuth, try to parse from cookie or bearer token
|
|
172
|
+
let user = req.nkAuth?.user ?? null;
|
|
173
|
+
if (!user) {
|
|
174
|
+
try {
|
|
175
|
+
const authConfigPath = path.join(pagesDir, '..', 'lumenjs.auth.ts');
|
|
176
|
+
if (fs.existsSync(authConfigPath)) {
|
|
177
|
+
const { loadAuthConfig } = await import('../../auth/config.js');
|
|
178
|
+
const authCfg = await loadAuthConfig(path.join(pagesDir, '..'), server.ssrLoadModule.bind(server));
|
|
179
|
+
if (authCfg) {
|
|
180
|
+
// Try bearer token first
|
|
181
|
+
const authHeader = req.headers.authorization;
|
|
182
|
+
if (authHeader?.startsWith('Bearer ')) {
|
|
183
|
+
const { verifyAccessToken } = await import('../../auth/token.js');
|
|
184
|
+
const tokenUser = verifyAccessToken(authHeader.slice(7), authCfg.session.secret);
|
|
185
|
+
if (tokenUser) {
|
|
186
|
+
user = tokenUser;
|
|
187
|
+
req.nkAuth = { user: tokenUser, session: { accessToken: authHeader.slice(7), expiresAt: 0, user: tokenUser } };
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// Fall back to cookie
|
|
191
|
+
if (!user && req.headers.cookie) {
|
|
192
|
+
const { parseSessionCookie, decryptSession } = await import('../../auth/session.js');
|
|
193
|
+
const cookieVal = parseSessionCookie(req.headers.cookie, authCfg.session.cookieName);
|
|
194
|
+
if (cookieVal) {
|
|
195
|
+
const session = await decryptSession(cookieVal, authCfg.session.secret);
|
|
196
|
+
if (session?.user) {
|
|
197
|
+
user = session.user;
|
|
198
|
+
req.nkAuth = { user: session.user, session };
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch { }
|
|
206
|
+
}
|
|
207
|
+
const result = await mod.loader({ params, query, url: pagePath, headers: req.headers, locale, user });
|
|
90
208
|
if (isRedirectResponse(result)) {
|
|
91
209
|
res.statusCode = result.status || 302;
|
|
92
210
|
res.setHeader('Location', result.location);
|
|
@@ -126,45 +244,26 @@ export function lumenLoadersPlugin(pagesDir) {
|
|
|
126
244
|
// Apply to page files and layout files within the pages directory
|
|
127
245
|
if (!id.startsWith(pagesDir) || !id.endsWith('.ts'))
|
|
128
246
|
return;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (!hasLoader)
|
|
247
|
+
const hasLoader = hasTopLevelServerFunction(code, 'loader');
|
|
248
|
+
const hasSubscribe = hasTopLevelServerFunction(code, 'subscribe');
|
|
249
|
+
if (!hasLoader && !hasSubscribe)
|
|
133
250
|
return;
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (
|
|
137
|
-
|
|
138
|
-
const startIdx = match.index;
|
|
139
|
-
// Skip past the function signature's closing parenthesis (handles nested braces in type annotations)
|
|
140
|
-
let parenDepth = 1;
|
|
141
|
-
let sigIdx = startIdx + match[0].length;
|
|
142
|
-
while (sigIdx < code.length && parenDepth > 0) {
|
|
143
|
-
if (code[sigIdx] === '(')
|
|
144
|
-
parenDepth++;
|
|
145
|
-
else if (code[sigIdx] === ')')
|
|
146
|
-
parenDepth--;
|
|
147
|
-
sigIdx++;
|
|
251
|
+
let result = code;
|
|
252
|
+
// Strip loader function
|
|
253
|
+
if (hasLoader) {
|
|
254
|
+
result = stripServerFunction(result, 'loader');
|
|
148
255
|
}
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
return;
|
|
153
|
-
let depth = 1;
|
|
154
|
-
let i = braceStart + 1;
|
|
155
|
-
while (i < code.length && depth > 0) {
|
|
156
|
-
if (code[i] === '{')
|
|
157
|
-
depth++;
|
|
158
|
-
else if (code[i] === '}')
|
|
159
|
-
depth--;
|
|
160
|
-
i++;
|
|
256
|
+
// Strip subscribe function
|
|
257
|
+
if (hasSubscribe) {
|
|
258
|
+
result = stripServerFunction(result, 'subscribe');
|
|
161
259
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
260
|
+
if (hasLoader) {
|
|
261
|
+
result += '\nexport const __nk_has_loader = true;\n';
|
|
262
|
+
}
|
|
263
|
+
if (hasSubscribe) {
|
|
264
|
+
result += '\nexport const __nk_has_subscribe = true;\n';
|
|
265
|
+
}
|
|
266
|
+
return { code: result, map: null };
|
|
168
267
|
},
|
|
169
268
|
};
|
|
170
269
|
}
|
|
@@ -173,8 +272,13 @@ export function lumenLoadersPlugin(pagesDir) {
|
|
|
173
272
|
* GET /__nk_loader/__layout/?__dir=dashboard
|
|
174
273
|
*/
|
|
175
274
|
async function handleLayoutLoader(server, pagesDir, dir, req, res) {
|
|
176
|
-
// Resolve the layout file from the directory
|
|
177
|
-
const layoutDir = path.
|
|
275
|
+
// Resolve the layout file from the directory (validate path stays within pagesDir)
|
|
276
|
+
const layoutDir = path.resolve(pagesDir, dir);
|
|
277
|
+
if (!layoutDir.startsWith(path.resolve(pagesDir) + path.sep) && layoutDir !== path.resolve(pagesDir)) {
|
|
278
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
279
|
+
res.end(JSON.stringify({ error: 'Invalid layout directory' }));
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
178
282
|
let layoutFile = null;
|
|
179
283
|
for (const ext of ['.ts', '.js']) {
|
|
180
284
|
const p = path.join(layoutDir, `_layout${ext}`);
|
|
@@ -209,7 +313,37 @@ async function handleLayoutLoader(server, pagesDir, dir, req, res) {
|
|
|
209
313
|
}
|
|
210
314
|
}
|
|
211
315
|
const locale = query.__locale;
|
|
212
|
-
|
|
316
|
+
// If auth middleware hasn't populated nkAuth, try to parse from cookie or bearer token
|
|
317
|
+
let user = req.nkAuth?.user ?? null;
|
|
318
|
+
if (!user) {
|
|
319
|
+
try {
|
|
320
|
+
const authConfigPath = path.join(pagesDir, '..', 'lumenjs.auth.ts');
|
|
321
|
+
if (fs.existsSync(authConfigPath)) {
|
|
322
|
+
const { loadAuthConfig } = await import('../../auth/config.js');
|
|
323
|
+
const authCfg = await loadAuthConfig(path.join(pagesDir, '..'), server.ssrLoadModule.bind(server));
|
|
324
|
+
if (authCfg) {
|
|
325
|
+
const authHeader = req.headers.authorization;
|
|
326
|
+
if (authHeader?.startsWith('Bearer ')) {
|
|
327
|
+
const { verifyAccessToken } = await import('../../auth/token.js');
|
|
328
|
+
const tokenUser = verifyAccessToken(authHeader.slice(7), authCfg.session.secret);
|
|
329
|
+
if (tokenUser)
|
|
330
|
+
user = tokenUser;
|
|
331
|
+
}
|
|
332
|
+
if (!user && req.headers.cookie) {
|
|
333
|
+
const { parseSessionCookie, decryptSession } = await import('../../auth/session.js');
|
|
334
|
+
const cookieVal = parseSessionCookie(req.headers.cookie, authCfg.session.cookieName);
|
|
335
|
+
if (cookieVal) {
|
|
336
|
+
const session = await decryptSession(cookieVal, authCfg.session.secret);
|
|
337
|
+
if (session?.user)
|
|
338
|
+
user = session.user;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
catch { }
|
|
345
|
+
}
|
|
346
|
+
const result = await mod.loader({ params: {}, query: {}, url: `/__layout/${dir}`, headers: req.headers, locale, user });
|
|
213
347
|
if (isRedirectResponse(result)) {
|
|
214
348
|
res.statusCode = result.status || 302;
|
|
215
349
|
res.setHeader('Location', result.location);
|
|
@@ -310,6 +444,141 @@ function findDynamicPage(baseDir, segments) {
|
|
|
310
444
|
}
|
|
311
445
|
return null;
|
|
312
446
|
}
|
|
447
|
+
/**
|
|
448
|
+
* Check if code has a top-level export of a server function (before the class definition).
|
|
449
|
+
* In LumenJS, loader/subscribe are always declared before `export class`.
|
|
450
|
+
*/
|
|
451
|
+
function hasTopLevelServerFunction(code, fnName) {
|
|
452
|
+
const classStart = code.search(/export\s+class\s+\w+/);
|
|
453
|
+
const fnRegex = new RegExp(`export\\s+(async\\s+)?function\\s+${fnName}\\s*\\(`);
|
|
454
|
+
const match = fnRegex.exec(code);
|
|
455
|
+
if (!match)
|
|
456
|
+
return false;
|
|
457
|
+
// Real server functions appear before the class; code examples appear inside the class
|
|
458
|
+
if (classStart >= 0 && match.index > classStart)
|
|
459
|
+
return false;
|
|
460
|
+
return true;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Strip a named server-side function (loader/subscribe) from client code using brace-depth tracking.
|
|
464
|
+
*/
|
|
465
|
+
function stripServerFunction(code, fnName) {
|
|
466
|
+
const classStart = code.search(/export\s+class\s+\w+/);
|
|
467
|
+
const regex = new RegExp(`export\\s+(async\\s+)?function\\s+${fnName}\\s*\\(`);
|
|
468
|
+
const match = regex.exec(code);
|
|
469
|
+
if (!match)
|
|
470
|
+
return code;
|
|
471
|
+
// Only strip if the match is before the class (top-level, not inside a code example)
|
|
472
|
+
if (classStart >= 0 && match.index > classStart)
|
|
473
|
+
return code;
|
|
474
|
+
const startIdx = match.index;
|
|
475
|
+
let parenDepth = 1;
|
|
476
|
+
let sigIdx = startIdx + match[0].length;
|
|
477
|
+
while (sigIdx < code.length && parenDepth > 0) {
|
|
478
|
+
if (code[sigIdx] === '(')
|
|
479
|
+
parenDepth++;
|
|
480
|
+
else if (code[sigIdx] === ')')
|
|
481
|
+
parenDepth--;
|
|
482
|
+
sigIdx++;
|
|
483
|
+
}
|
|
484
|
+
let braceStart = code.indexOf('{', sigIdx);
|
|
485
|
+
if (braceStart === -1)
|
|
486
|
+
return code;
|
|
487
|
+
let depth = 1;
|
|
488
|
+
let i = braceStart + 1;
|
|
489
|
+
while (i < code.length && depth > 0) {
|
|
490
|
+
const ch = code[i];
|
|
491
|
+
// Skip string literals and template literals to avoid counting braces inside them
|
|
492
|
+
if (ch === "'" || ch === '"' || ch === '`') {
|
|
493
|
+
const quote = ch;
|
|
494
|
+
i++;
|
|
495
|
+
while (i < code.length && code[i] !== quote) {
|
|
496
|
+
if (code[i] === '\\')
|
|
497
|
+
i++; // skip escaped char
|
|
498
|
+
i++;
|
|
499
|
+
}
|
|
500
|
+
i++; // skip closing quote
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
// Skip single-line comments
|
|
504
|
+
if (ch === '/' && code[i + 1] === '/') {
|
|
505
|
+
while (i < code.length && code[i] !== '\n')
|
|
506
|
+
i++;
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
// Skip multi-line comments
|
|
510
|
+
if (ch === '/' && code[i + 1] === '*') {
|
|
511
|
+
i += 2;
|
|
512
|
+
while (i < code.length - 1 && !(code[i] === '*' && code[i + 1] === '/'))
|
|
513
|
+
i++;
|
|
514
|
+
i += 2;
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
if (ch === '{')
|
|
518
|
+
depth++;
|
|
519
|
+
else if (ch === '}')
|
|
520
|
+
depth--;
|
|
521
|
+
i++;
|
|
522
|
+
}
|
|
523
|
+
return code.substring(0, startIdx)
|
|
524
|
+
+ `// ${fnName}() — runs server-side only`
|
|
525
|
+
+ code.substring(i);
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Handle layout subscribe requests in dev mode.
|
|
529
|
+
* GET /__nk_subscribe/__layout/?__dir=<dir>
|
|
530
|
+
*/
|
|
531
|
+
async function handleLayoutSubscribe(server, pagesDir, dir, query, req, res) {
|
|
532
|
+
const layoutDir = path.resolve(pagesDir, dir);
|
|
533
|
+
if (!layoutDir.startsWith(path.resolve(pagesDir) + path.sep) && layoutDir !== path.resolve(pagesDir)) {
|
|
534
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
535
|
+
res.end(JSON.stringify({ error: 'Invalid layout directory' }));
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
let layoutFile = null;
|
|
539
|
+
for (const ext of ['.ts', '.js']) {
|
|
540
|
+
const p = path.join(layoutDir, `_layout${ext}`);
|
|
541
|
+
if (fs.existsSync(p)) {
|
|
542
|
+
layoutFile = p;
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
if (!layoutFile) {
|
|
547
|
+
res.statusCode = 204;
|
|
548
|
+
res.end();
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
try {
|
|
552
|
+
installDomShims();
|
|
553
|
+
const mod = await server.ssrLoadModule(layoutFile);
|
|
554
|
+
if (!mod.subscribe || typeof mod.subscribe !== 'function') {
|
|
555
|
+
res.statusCode = 204;
|
|
556
|
+
res.end();
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
res.writeHead(200, {
|
|
560
|
+
'Content-Type': 'text/event-stream',
|
|
561
|
+
'Cache-Control': 'no-cache',
|
|
562
|
+
'Connection': 'keep-alive',
|
|
563
|
+
});
|
|
564
|
+
const locale = query.__locale;
|
|
565
|
+
const push = (data) => {
|
|
566
|
+
res.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
567
|
+
};
|
|
568
|
+
const cleanup = mod.subscribe({ params: {}, push, headers: req.headers, locale, user: req.nkAuth?.user ?? null });
|
|
569
|
+
res.on('close', () => {
|
|
570
|
+
if (typeof cleanup === 'function')
|
|
571
|
+
cleanup();
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
catch (err) {
|
|
575
|
+
console.error(`[LumenJS] Layout subscribe error for dir=${dir}:`, err);
|
|
576
|
+
if (!res.headersSent) {
|
|
577
|
+
res.statusCode = 500;
|
|
578
|
+
res.end();
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
313
582
|
/**
|
|
314
583
|
* Extract dynamic route params by comparing URL segments against [param] file path segments.
|
|
315
584
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { dirToLayoutTagName, fileHasLoader, filePathToRoute, filePathToTagName } from '../../shared/utils.js';
|
|
3
|
+
import { dirToLayoutTagName, fileHasLoader, fileHasSubscribe, fileHasAuth, fileHasMeta, fileHasStandalone, filePathToRoute, filePathToTagName } from '../../shared/utils.js';
|
|
4
4
|
const VIRTUAL_MODULE_ID = 'virtual:lumenjs-routes';
|
|
5
5
|
const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID;
|
|
6
6
|
/**
|
|
@@ -28,7 +28,7 @@ export function lumenRoutesPlugin(pagesDir) {
|
|
|
28
28
|
const tagName = dirToLayoutTagName(relativePath);
|
|
29
29
|
layouts.push({ dir: relativePath.replace(/\\/g, '/'), filePath, tagName });
|
|
30
30
|
}
|
|
31
|
-
if (entry.isDirectory()) {
|
|
31
|
+
if (entry.isDirectory() && !entry.name.startsWith('_')) {
|
|
32
32
|
walkForLayouts(baseDir, path.join(relativePath, entry.name), layouts);
|
|
33
33
|
}
|
|
34
34
|
}
|
|
@@ -55,12 +55,18 @@ export function lumenRoutesPlugin(pagesDir) {
|
|
|
55
55
|
function walkDir(baseDir, relativePath, routes) {
|
|
56
56
|
const fullDir = path.join(baseDir, relativePath);
|
|
57
57
|
const entries = fs.readdirSync(fullDir, { withFileTypes: true });
|
|
58
|
+
// Check if this subdirectory contains an index file (folder route)
|
|
59
|
+
// Only applies to subdirectories, not the root pages directory
|
|
60
|
+
const hasIndex = relativePath !== '' && entries.some(e => e.isFile() && /^index\.(ts|js)$/.test(e.name));
|
|
58
61
|
for (const entry of entries) {
|
|
59
62
|
const entryRelative = path.join(relativePath, entry.name);
|
|
60
|
-
if (entry.isDirectory()) {
|
|
63
|
+
if (entry.isDirectory() && !entry.name.startsWith('_')) {
|
|
61
64
|
walkDir(baseDir, entryRelative, routes);
|
|
62
65
|
}
|
|
63
66
|
else if (entry.isFile() && /\.(ts|js)$/.test(entry.name) && !entry.name.startsWith('_')) {
|
|
67
|
+
// In a folder route (has index file), only register the index file and dynamic param files
|
|
68
|
+
if (hasIndex && !/^index\.(ts|js)$/.test(entry.name) && !entry.name.startsWith('['))
|
|
69
|
+
continue;
|
|
64
70
|
const routePath = filePathToRoute(entryRelative);
|
|
65
71
|
const componentPath = path.join(pagesDir, entryRelative);
|
|
66
72
|
const tagName = filePathToTagName(entryRelative);
|
|
@@ -98,18 +104,24 @@ export function lumenRoutesPlugin(pagesDir) {
|
|
|
98
104
|
const routeArray = routes
|
|
99
105
|
.map(r => {
|
|
100
106
|
const hasLoader = fileHasLoader(r.componentPath);
|
|
107
|
+
const hasSubscribe = fileHasSubscribe(r.componentPath);
|
|
108
|
+
const hasAuth = fileHasAuth(r.componentPath);
|
|
109
|
+
const hasMeta = fileHasMeta(r.componentPath);
|
|
110
|
+
const isStandalone = fileHasStandalone(r.componentPath);
|
|
101
111
|
const componentPath = r.componentPath.replace(/\\/g, '/');
|
|
102
|
-
|
|
112
|
+
// Standalone pages skip all layouts
|
|
113
|
+
const chain = isStandalone ? [] : getLayoutChain(r.componentPath, layouts);
|
|
103
114
|
let layoutsStr = '';
|
|
104
115
|
if (chain.length > 0) {
|
|
105
116
|
const items = chain.map(l => {
|
|
106
117
|
const lHasLoader = fileHasLoader(l.filePath);
|
|
118
|
+
const lHasSubscribe = fileHasSubscribe(l.filePath);
|
|
107
119
|
const lPath = l.filePath.replace(/\\/g, '/');
|
|
108
|
-
return `{ tagName: ${JSON.stringify(l.tagName)}, loaderPath: ${JSON.stringify(l.dir)}${lHasLoader ? ', hasLoader: true' : ''}, load: () => import('${lPath}') }`;
|
|
120
|
+
return `{ tagName: ${JSON.stringify(l.tagName)}, loaderPath: ${JSON.stringify(l.dir)}${lHasLoader ? ', hasLoader: true' : ''}${lHasSubscribe ? ', hasSubscribe: true' : ''}, load: () => import('${lPath}') }`;
|
|
109
121
|
});
|
|
110
122
|
layoutsStr = `, layouts: [${items.join(', ')}]`;
|
|
111
123
|
}
|
|
112
|
-
return ` { path: ${JSON.stringify(r.path)}, tagName: ${JSON.stringify(r.tagName)}${hasLoader ? ', hasLoader: true' : ''}, load: () => import('${componentPath}')${layoutsStr} }`;
|
|
124
|
+
return ` { path: ${JSON.stringify(r.path)}, tagName: ${JSON.stringify(r.tagName)}${hasLoader ? ', hasLoader: true' : ''}${hasSubscribe ? ', hasSubscribe: true' : ''}${hasMeta ? ', hasMeta: true' : ''}${hasAuth ? ', __nk_has_auth: true' : ''}, load: () => import('${componentPath}')${layoutsStr} }`;
|
|
113
125
|
})
|
|
114
126
|
.join(',\n');
|
|
115
127
|
return `export const routes = [\n${routeArray}\n];\n`;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileHasSocket, filePathToRoute } from '../../shared/utils.js';
|
|
4
|
+
export function lumenSocketIOPlugin(pagesDir) {
|
|
5
|
+
return {
|
|
6
|
+
name: 'lumenjs-socketio',
|
|
7
|
+
configureServer(server) {
|
|
8
|
+
if (!server.httpServer)
|
|
9
|
+
return;
|
|
10
|
+
import('../../shared/socket-io-setup.js').then(({ setupSocketIO }) => {
|
|
11
|
+
const routes = scanSocketRoutes(pagesDir);
|
|
12
|
+
setupSocketIO({
|
|
13
|
+
httpServer: server.httpServer,
|
|
14
|
+
loadModule: (fp) => server.ssrLoadModule(fp),
|
|
15
|
+
routes,
|
|
16
|
+
projectDir: path.dirname(pagesDir),
|
|
17
|
+
}).catch((err) => {
|
|
18
|
+
console.warn('[LumenJS] Socket.IO setup failed:', err.message);
|
|
19
|
+
console.warn('[LumenJS] Make sure socket.io is installed: lumenjs add socketio');
|
|
20
|
+
});
|
|
21
|
+
}).catch((err) => {
|
|
22
|
+
console.warn('[LumenJS] Socket.IO plugin load failed:', err.message);
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function scanSocketRoutes(pagesDir) {
|
|
28
|
+
const routes = [];
|
|
29
|
+
if (!fs.existsSync(pagesDir))
|
|
30
|
+
return routes;
|
|
31
|
+
walkDir(pagesDir, '', routes, pagesDir);
|
|
32
|
+
return routes;
|
|
33
|
+
}
|
|
34
|
+
function walkDir(baseDir, relativePath, routes, pagesDir) {
|
|
35
|
+
const fullDir = path.join(baseDir, relativePath);
|
|
36
|
+
const entries = fs.readdirSync(fullDir, { withFileTypes: true });
|
|
37
|
+
for (const entry of entries) {
|
|
38
|
+
const entryRelative = path.join(relativePath, entry.name);
|
|
39
|
+
if (entry.isDirectory()) {
|
|
40
|
+
walkDir(baseDir, entryRelative, routes, pagesDir);
|
|
41
|
+
}
|
|
42
|
+
else if (entry.isFile() && /\.(ts|js)$/.test(entry.name) && !entry.name.startsWith('_')) {
|
|
43
|
+
const filePath = path.join(pagesDir, entryRelative);
|
|
44
|
+
const hasSocket = fileHasSocket(filePath);
|
|
45
|
+
if (hasSocket) {
|
|
46
|
+
const routePath = filePathToRoute(entryRelative);
|
|
47
|
+
routes.push({ path: routePath, hasSocket: true, filePath });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Plugin } from 'vite';
|
|
2
2
|
/**
|
|
3
3
|
* In editor mode, inject data-nk-source attributes into html`` template literals.
|
|
4
|
+
* Reads the original source file from disk to compute correct line numbers,
|
|
5
|
+
* since Vite's transform hook receives esbuild-compiled code with shifted lines.
|
|
4
6
|
*/
|
|
5
7
|
export declare function sourceAnnotatorPlugin(projectDir: string): Plugin;
|
|
@@ -1,21 +1,44 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
+
import fs from 'fs';
|
|
2
3
|
/**
|
|
3
4
|
* In editor mode, inject data-nk-source attributes into html`` template literals.
|
|
5
|
+
* Reads the original source file from disk to compute correct line numbers,
|
|
6
|
+
* since Vite's transform hook receives esbuild-compiled code with shifted lines.
|
|
4
7
|
*/
|
|
5
8
|
export function sourceAnnotatorPlugin(projectDir) {
|
|
6
9
|
return {
|
|
7
10
|
name: 'lumenjs-source-annotator',
|
|
11
|
+
enforce: 'pre',
|
|
8
12
|
transform(code, id) {
|
|
9
13
|
if (!id.startsWith(projectDir) || !id.endsWith('.ts'))
|
|
10
14
|
return;
|
|
11
15
|
if (!code.includes('html`'))
|
|
12
16
|
return;
|
|
17
|
+
// Read the original source to get correct line numbers
|
|
18
|
+
let originalSource;
|
|
19
|
+
try {
|
|
20
|
+
originalSource = fs.readFileSync(id, 'utf-8');
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
originalSource = code;
|
|
24
|
+
}
|
|
13
25
|
const relativePath = path.relative(projectDir, id);
|
|
26
|
+
// Build ordered list of html` base lines from original source
|
|
27
|
+
const originalBaseLines = [];
|
|
28
|
+
const origRegex = /html`/g;
|
|
29
|
+
let origMatch;
|
|
30
|
+
while ((origMatch = origRegex.exec(originalSource)) !== null) {
|
|
31
|
+
const before = originalSource.substring(0, origMatch.index + 5); // include 'html`'
|
|
32
|
+
originalBaseLines.push(before.split('\n').length);
|
|
33
|
+
}
|
|
34
|
+
let templateIndex = 0;
|
|
14
35
|
const transformed = code.replace(/html`([\s\S]*?)`/g, (match, templateContent) => {
|
|
15
36
|
let offset = 0;
|
|
16
|
-
|
|
17
|
-
const baseLine =
|
|
18
|
-
|
|
37
|
+
// Use original source line for this Nth html` template
|
|
38
|
+
const baseLine = originalBaseLines[templateIndex] ?? code.substring(0, code.indexOf(match)).split('\n').length;
|
|
39
|
+
templateIndex++;
|
|
40
|
+
// Annotate both custom elements (tags with hyphens) and standard HTML elements
|
|
41
|
+
const annotated = templateContent.replace(/<((?:[a-z][a-z0-9]*-[a-z0-9-]*)|(?:div|section|article|aside|main|nav|header|footer|h[1-6]|p|span|a|ul|ol|li|button|form|input|textarea|select|label|img|table|tr|td|th|thead|tbody))([\s>])/gi, (tagMatch, tagName, after) => {
|
|
19
42
|
const beforeTag = templateContent.substring(0, templateContent.indexOf(tagMatch, offset));
|
|
20
43
|
const lineInTemplate = beforeTag.split('\n').length - 1;
|
|
21
44
|
offset = templateContent.indexOf(tagMatch, offset) + tagMatch.length;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
/**
|
|
3
|
+
* LumenJS storage plugin (dev mode).
|
|
4
|
+
*
|
|
5
|
+
* - Creates a LocalStorageAdapter pointing to `{projectDir}/uploads`
|
|
6
|
+
* - Registers it as the global storage singleton (`useStorage()`)
|
|
7
|
+
* - Serves uploaded files at `/uploads/*`
|
|
8
|
+
* - Handles presigned PUT requests at `/__nk_storage/upload/:token`
|
|
9
|
+
*/
|
|
10
|
+
export declare function lumenStoragePlugin(projectDir: string): Plugin;
|