@nuraly/lumenjs 0.1.3 → 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 +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 +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 +48 -120
- package/dist/build/scan.d.ts +17 -0
- package/dist/build/scan.js +76 -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 +218 -15
- 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 +23 -20
- 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.js +146 -13
- package/dist/dev-server/plugins/vite-plugin-routes.js +15 -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 +111 -2
- package/dist/dev-server/server.js +127 -13
- 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 +3 -0
- package/dist/runtime/router-data.js +102 -17
- package/dist/runtime/router-hydration.js +25 -0
- package/dist/runtime/router.d.ts +16 -1
- package/dist/runtime/router.js +188 -42
- 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 +14 -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 +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 +19 -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/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
|
@@ -47,10 +47,77 @@ export function i18nPlugin(projectDir, config) {
|
|
|
47
47
|
res.end(JSON.stringify({ error: 'Invalid locale file' }));
|
|
48
48
|
}
|
|
49
49
|
});
|
|
50
|
-
// Watch locale
|
|
50
|
+
// Watch locale files for changes and send HMR event.
|
|
51
|
+
// Uses fs.watchFile (stat-based polling) instead of inotify/chokidar so
|
|
52
|
+
// it works reliably inside Docker containers where inotify misses changes.
|
|
51
53
|
if (fs.existsSync(localesDir)) {
|
|
52
|
-
|
|
54
|
+
for (const locale of config.locales) {
|
|
55
|
+
const filePath = path.join(localesDir, `${locale}.json`);
|
|
56
|
+
if (!fs.existsSync(filePath))
|
|
57
|
+
continue;
|
|
58
|
+
fs.watchFile(filePath, { interval: 500 }, (curr, prev) => {
|
|
59
|
+
if (curr.mtimeMs === prev.mtimeMs)
|
|
60
|
+
return;
|
|
61
|
+
server.ws.send({
|
|
62
|
+
type: 'custom',
|
|
63
|
+
event: 'lumenjs:i18n-update',
|
|
64
|
+
data: { locale },
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
server.httpServer?.on('close', () => {
|
|
69
|
+
for (const locale of config.locales) {
|
|
70
|
+
fs.unwatchFile(path.join(localesDir, `${locale}.json`));
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
transformIndexHtml() {
|
|
76
|
+
return [
|
|
77
|
+
{
|
|
78
|
+
tag: 'script',
|
|
79
|
+
attrs: { type: 'module' },
|
|
80
|
+
children: `
|
|
81
|
+
// i18n HMR: listen on Vite's WebSocket for locale file changes.
|
|
82
|
+
// Uses window.__lumenjs_i18n_reload (set by the i18n runtime) to update
|
|
83
|
+
// translations in the correct module instance — avoids the duplicate-module
|
|
84
|
+
// problem caused by Vite's cache-busting query strings on /@fs/ imports.
|
|
85
|
+
import { createHotContext } from '/@vite/client';
|
|
86
|
+
const hot = createHotContext('/__nk_i18n_hmr');
|
|
87
|
+
hot.on('lumenjs:i18n-update', async ({ locale }) => {
|
|
88
|
+
const reload = window.__lumenjs_i18n_reload;
|
|
89
|
+
if (!reload) return;
|
|
90
|
+
const updated = await reload(locale);
|
|
91
|
+
if (!updated) return;
|
|
92
|
+
function __updateAll(root) {
|
|
93
|
+
for (const el of root.querySelectorAll('*')) {
|
|
94
|
+
if (el.requestUpdate) {
|
|
95
|
+
// Clear Lit's template cache to force a full re-render.
|
|
96
|
+
// Deleting _$litPart$ forces Lit to re-create the template from scratch.
|
|
97
|
+
if (el.renderRoot) {
|
|
98
|
+
if ('_$litPart$' in el.renderRoot) {
|
|
99
|
+
delete el.renderRoot['_$litPart$'];
|
|
100
|
+
} else {
|
|
101
|
+
for (const s of Object.getOwnPropertySymbols(el.renderRoot)) {
|
|
102
|
+
const v = el.renderRoot[s];
|
|
103
|
+
if (v && typeof v === 'object' && '_$committedValue' in v) {
|
|
104
|
+
v._$committedValue = undefined;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
53
107
|
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
el.requestUpdate();
|
|
111
|
+
}
|
|
112
|
+
if (el.shadowRoot) __updateAll(el.shadowRoot);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
__updateAll(document);
|
|
116
|
+
});
|
|
117
|
+
`,
|
|
118
|
+
injectTo: 'body',
|
|
119
|
+
},
|
|
120
|
+
];
|
|
54
121
|
},
|
|
55
122
|
};
|
|
56
123
|
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { Plugin } from 'vite';
|
|
2
2
|
/**
|
|
3
3
|
* Force ALL lit imports to resolve to lumenjs's single lit copy.
|
|
4
|
+
*
|
|
5
|
+
* Instead of returning absolute paths directly (which bypasses Vite's ?v= cache
|
|
6
|
+
* busting and causes module duplication), we resolve the correct entry point
|
|
7
|
+
* from package.json exports, then delegate to Vite's resolver using a fake
|
|
8
|
+
* importer within lumenjs node_modules. This ensures consistent ?v= hashing
|
|
9
|
+
* across all import chains.
|
|
4
10
|
*/
|
|
5
11
|
export declare function litDedupPlugin(lumenNodeModules: string, isDev: boolean): Plugin;
|
|
@@ -2,12 +2,76 @@ import path from 'path';
|
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
/**
|
|
4
4
|
* Force ALL lit imports to resolve to lumenjs's single lit copy.
|
|
5
|
+
*
|
|
6
|
+
* Instead of returning absolute paths directly (which bypasses Vite's ?v= cache
|
|
7
|
+
* busting and causes module duplication), we resolve the correct entry point
|
|
8
|
+
* from package.json exports, then delegate to Vite's resolver using a fake
|
|
9
|
+
* importer within lumenjs node_modules. This ensures consistent ?v= hashing
|
|
10
|
+
* across all import chains.
|
|
5
11
|
*/
|
|
6
12
|
export function litDedupPlugin(lumenNodeModules, isDev) {
|
|
13
|
+
// Cache resolved export paths per (pkgName, subpath) to avoid repeated pkg.json reads
|
|
14
|
+
const exportCache = new Map();
|
|
15
|
+
function resolveExport(pkgName, subpath, ssr) {
|
|
16
|
+
const cacheKey = `${pkgName}:${subpath}:${ssr}`;
|
|
17
|
+
if (exportCache.has(cacheKey))
|
|
18
|
+
return exportCache.get(cacheKey);
|
|
19
|
+
const pkgDir = path.join(lumenNodeModules, pkgName);
|
|
20
|
+
if (!fs.existsSync(pkgDir)) {
|
|
21
|
+
exportCache.set(cacheKey, null);
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
let resolved = null;
|
|
25
|
+
try {
|
|
26
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf-8'));
|
|
27
|
+
const exports = pkg.exports;
|
|
28
|
+
if (exports) {
|
|
29
|
+
const exportKey = subpath ? './' + subpath : '.';
|
|
30
|
+
const entry = exports[exportKey];
|
|
31
|
+
if (entry) {
|
|
32
|
+
let entryPath;
|
|
33
|
+
if (ssr) {
|
|
34
|
+
entryPath = isDev
|
|
35
|
+
? (entry?.node?.development || entry?.node?.default || entry?.node || entry?.development || entry?.default || entry)
|
|
36
|
+
: (entry?.node?.default || entry?.node || entry?.default || entry);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
entryPath = isDev
|
|
40
|
+
? (entry?.browser?.development || entry?.development || entry?.browser?.default || entry?.default || entry)
|
|
41
|
+
: (entry?.browser?.default || entry?.browser || entry?.default || entry);
|
|
42
|
+
}
|
|
43
|
+
if (typeof entryPath === 'string') {
|
|
44
|
+
let finalPath = entryPath;
|
|
45
|
+
// In dev mode, prefer development/ builds to avoid mixing prod/dev modules
|
|
46
|
+
if (isDev && !entryPath.includes('/development/')) {
|
|
47
|
+
const devEntryPath = entryPath.replace(/^\.\//, '');
|
|
48
|
+
const devFullPath = path.join(pkgDir, 'development', devEntryPath);
|
|
49
|
+
if (fs.existsSync(devFullPath)) {
|
|
50
|
+
finalPath = './development/' + devEntryPath;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
resolved = finalPath;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (!resolved && subpath) {
|
|
58
|
+
resolved = './' + subpath;
|
|
59
|
+
}
|
|
60
|
+
if (!resolved) {
|
|
61
|
+
const entry = pkg.module || pkg.main || 'index.js';
|
|
62
|
+
resolved = './' + entry;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
resolved = subpath ? './' + subpath : null;
|
|
67
|
+
}
|
|
68
|
+
exportCache.set(cacheKey, resolved);
|
|
69
|
+
return resolved;
|
|
70
|
+
}
|
|
7
71
|
return {
|
|
8
72
|
name: 'lumenjs-lit-dedup',
|
|
9
73
|
enforce: 'pre',
|
|
10
|
-
resolveId(source, importer, options) {
|
|
74
|
+
async resolveId(source, importer, options) {
|
|
11
75
|
if (!importer)
|
|
12
76
|
return;
|
|
13
77
|
const isLitImport = source === 'lit' || source.startsWith('lit/')
|
|
@@ -23,40 +87,20 @@ export function litDedupPlugin(lumenNodeModules, isDev) {
|
|
|
23
87
|
const pkgName = source.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0];
|
|
24
88
|
const subpath = source.startsWith('@') ? parts.slice(2).join('/') : parts.slice(1).join('/');
|
|
25
89
|
const pkgDir = path.join(lumenNodeModules, pkgName);
|
|
26
|
-
|
|
90
|
+
const resolved = resolveExport(pkgName, subpath, !!options?.ssr);
|
|
91
|
+
if (!resolved)
|
|
27
92
|
return;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
: (entry?.node?.default || entry?.node || entry?.default || entry);
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
resolved = isDev
|
|
43
|
-
? (entry?.browser?.development || entry?.development || entry?.browser?.default || entry?.default || entry)
|
|
44
|
-
: (entry?.browser?.default || entry?.browser || entry?.default || entry);
|
|
45
|
-
}
|
|
46
|
-
if (typeof resolved === 'string') {
|
|
47
|
-
return path.join(pkgDir, resolved);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
if (subpath) {
|
|
52
|
-
return path.join(pkgDir, subpath);
|
|
53
|
-
}
|
|
54
|
-
const entry = pkg.module || pkg.main || 'index.js';
|
|
55
|
-
return path.join(pkgDir, entry);
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
return subpath ? path.join(pkgDir, subpath) : pkgDir;
|
|
59
|
-
}
|
|
93
|
+
// Compute the absolute path for the resolved entry
|
|
94
|
+
const absolutePath = path.join(pkgDir, resolved);
|
|
95
|
+
// Delegate to Vite's resolver using a fake importer inside lumenjs node_modules.
|
|
96
|
+
// This ensures Vite's importAnalysis adds consistent ?v= cache busting,
|
|
97
|
+
// preventing module duplication between different import chains.
|
|
98
|
+
const fakeImporter = path.join(lumenNodeModules, '_lumenjs_dedup_anchor.js');
|
|
99
|
+
const result = await this.resolve(absolutePath, fakeImporter, { skipSelf: true });
|
|
100
|
+
if (result)
|
|
101
|
+
return result;
|
|
102
|
+
// Fallback to absolute path if Vite's resolver fails
|
|
103
|
+
return absolutePath;
|
|
60
104
|
}
|
|
61
105
|
};
|
|
62
106
|
}
|
|
@@ -26,15 +26,57 @@ if (import.meta.hot) {
|
|
|
26
26
|
if (key === "constructor") continue;
|
|
27
27
|
Object.defineProperty(OldClass.prototype, key, desc);
|
|
28
28
|
}
|
|
29
|
+
const newCssText = NewClass.styles?.cssText || '';
|
|
29
30
|
if (NewClass.styles) {
|
|
30
31
|
OldClass.styles = NewClass.styles;
|
|
31
32
|
OldClass.elementStyles = undefined;
|
|
32
|
-
OldClass.
|
|
33
|
+
OldClass.finalized = false;
|
|
34
|
+
try { OldClass.finalizeStyles(); } catch {}
|
|
33
35
|
}
|
|
34
36
|
if (NewClass.properties) {
|
|
35
37
|
OldClass.properties = NewClass.properties;
|
|
36
38
|
}
|
|
37
|
-
|
|
39
|
+
function __queryShadowAll(root, sel) {
|
|
40
|
+
const results = [...root.querySelectorAll(sel)];
|
|
41
|
+
for (const el of root.querySelectorAll('*')) {
|
|
42
|
+
if (el.shadowRoot) results.push(...__queryShadowAll(el.shadowRoot, sel));
|
|
43
|
+
}
|
|
44
|
+
return results;
|
|
45
|
+
}
|
|
46
|
+
__queryShadowAll(document, "${tagName}").forEach((el) => {
|
|
47
|
+
if (el.renderRoot) {
|
|
48
|
+
// Update styles: try adoptedStyleSheets first, fall back to <style> tag
|
|
49
|
+
if (OldClass.elementStyles && OldClass.elementStyles.length > 0) {
|
|
50
|
+
const sheets = OldClass.elementStyles
|
|
51
|
+
.filter(s => s instanceof CSSStyleSheet || (s && s.styleSheet))
|
|
52
|
+
.map(s => s instanceof CSSStyleSheet ? s : s.styleSheet);
|
|
53
|
+
if (sheets.length && el.renderRoot.adoptedStyleSheets !== undefined) {
|
|
54
|
+
el.renderRoot.adoptedStyleSheets = sheets;
|
|
55
|
+
}
|
|
56
|
+
} else if (newCssText) {
|
|
57
|
+
const styleEl = el.renderRoot.querySelector('style');
|
|
58
|
+
if (styleEl) styleEl.textContent = newCssText;
|
|
59
|
+
}
|
|
60
|
+
// Clear stale SSR inline styles from child elements
|
|
61
|
+
el.renderRoot.querySelectorAll('[style]').forEach((child) => {
|
|
62
|
+
child.removeAttribute('style');
|
|
63
|
+
});
|
|
64
|
+
// Clear Lit's template cache to force a full re-render.
|
|
65
|
+
// Deleting the ChildPart forces Lit to re-create the entire template
|
|
66
|
+
// from scratch instead of diffing against stale cached values.
|
|
67
|
+
if ('_$litPart$' in el.renderRoot) {
|
|
68
|
+
delete el.renderRoot['_$litPart$'];
|
|
69
|
+
} else {
|
|
70
|
+
// Lit 2.x fallback: clear via symbols
|
|
71
|
+
for (const s of Object.getOwnPropertySymbols(el.renderRoot)) {
|
|
72
|
+
const v = el.renderRoot[s];
|
|
73
|
+
if (v && typeof v === 'object' && '_$committedValue' in v) {
|
|
74
|
+
v._$committedValue = undefined;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
38
80
|
if (el.requestUpdate) el.requestUpdate();
|
|
39
81
|
});
|
|
40
82
|
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { scanPages, scanApiRoutes } from '../../build/scan.js';
|
|
4
|
+
import { readProjectConfig } from '../config.js';
|
|
5
|
+
import { fileGetApiMethods } from '../../shared/utils.js';
|
|
6
|
+
import { generateLlmsTxt, resolveDynamicEntries } from '../../llms/generate.js';
|
|
7
|
+
export function lumenLlmsPlugin(projectDir) {
|
|
8
|
+
const pagesDir = path.join(projectDir, 'pages');
|
|
9
|
+
const apiDir = path.join(projectDir, 'api');
|
|
10
|
+
return {
|
|
11
|
+
name: 'lumenjs-llms',
|
|
12
|
+
configureServer(server) {
|
|
13
|
+
server.middlewares.use(async (req, res, next) => {
|
|
14
|
+
const url = (req.url || '').split('?')[0];
|
|
15
|
+
if (url !== '/llms.txt')
|
|
16
|
+
return next();
|
|
17
|
+
// Check for user override in public/
|
|
18
|
+
const publicOverride = path.join(projectDir, 'public', 'llms.txt');
|
|
19
|
+
if (fs.existsSync(publicOverride)) {
|
|
20
|
+
// Let Vite's static serving handle it
|
|
21
|
+
return next();
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const config = readProjectConfig(projectDir);
|
|
25
|
+
const pages = scanPages(pagesDir);
|
|
26
|
+
const apiEntries = scanApiRoutes(apiDir);
|
|
27
|
+
// Build API routes with methods
|
|
28
|
+
const apiRoutes = apiEntries.map(entry => ({
|
|
29
|
+
path: entry.routePath,
|
|
30
|
+
methods: fileGetApiMethods(entry.filePath),
|
|
31
|
+
})).filter(r => r.methods.length > 0);
|
|
32
|
+
// Build page data with loader resolution
|
|
33
|
+
const llmsPages = [];
|
|
34
|
+
for (const page of pages) {
|
|
35
|
+
const llmsPage = {
|
|
36
|
+
path: page.routePath,
|
|
37
|
+
hasLoader: page.hasLoader,
|
|
38
|
+
hasSubscribe: page.hasSubscribe,
|
|
39
|
+
};
|
|
40
|
+
if (page.hasLoader) {
|
|
41
|
+
const isDynamic = page.routePath.includes(':');
|
|
42
|
+
if (isDynamic) {
|
|
43
|
+
// Extract param name from route like /blog/:slug
|
|
44
|
+
const paramMatch = page.routePath.match(/:([^/]+)/);
|
|
45
|
+
const paramName = paramMatch ? paramMatch[1] : '';
|
|
46
|
+
if (paramName) {
|
|
47
|
+
const entries = await resolveDynamicEntries({ path: page.routePath, paramName }, (filePath) => server.ssrLoadModule(filePath), pages.map(p => ({ path: p.routePath, filePath: p.filePath, hasLoader: p.hasLoader })));
|
|
48
|
+
if (entries) {
|
|
49
|
+
llmsPage.dynamicEntries = entries;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
// Static page — call loader directly
|
|
55
|
+
try {
|
|
56
|
+
const mod = await server.ssrLoadModule(page.filePath);
|
|
57
|
+
if (mod?.loader) {
|
|
58
|
+
const data = await mod.loader({ params: {}, query: {}, url: page.routePath, headers: {} });
|
|
59
|
+
if (data && !data.__nk_redirect) {
|
|
60
|
+
llmsPage.loaderData = data;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Skip loader errors
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
llmsPages.push(llmsPage);
|
|
70
|
+
}
|
|
71
|
+
const content = generateLlmsTxt({
|
|
72
|
+
title: config.title,
|
|
73
|
+
pages: llmsPages,
|
|
74
|
+
apiRoutes,
|
|
75
|
+
integrations: config.integrations,
|
|
76
|
+
i18n: config.i18n ? { locales: config.i18n.locales, defaultLocale: config.i18n.defaultLocale } : undefined,
|
|
77
|
+
db: config.db,
|
|
78
|
+
});
|
|
79
|
+
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
80
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
81
|
+
res.statusCode = 200;
|
|
82
|
+
res.end(content);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
console.error('[LumenJS] Error generating llms.txt:', err);
|
|
86
|
+
res.statusCode = 500;
|
|
87
|
+
res.end('Error generating llms.txt');
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -61,6 +61,11 @@ export function lumenLoadersPlugin(pagesDir) {
|
|
|
61
61
|
delete query.__params;
|
|
62
62
|
}
|
|
63
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
|
+
}
|
|
64
69
|
if (!filePath) {
|
|
65
70
|
res.statusCode = 404;
|
|
66
71
|
res.end();
|
|
@@ -87,7 +92,7 @@ export function lumenLoadersPlugin(pagesDir) {
|
|
|
87
92
|
const push = (data) => {
|
|
88
93
|
res.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
89
94
|
};
|
|
90
|
-
const cleanup = mod.subscribe({ params, push, headers: req.headers, locale });
|
|
95
|
+
const cleanup = mod.subscribe({ params, push, headers: req.headers, locale, user: req.nkAuth?.user ?? null });
|
|
91
96
|
res.on('close', () => {
|
|
92
97
|
if (typeof cleanup === 'function')
|
|
93
98
|
cleanup();
|
|
@@ -131,8 +136,14 @@ export function lumenLoadersPlugin(pagesDir) {
|
|
|
131
136
|
catch { /* ignore */ }
|
|
132
137
|
delete query.__params;
|
|
133
138
|
}
|
|
134
|
-
// Find the page file
|
|
139
|
+
// Find the page file (validate path stays within pagesDir)
|
|
135
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
|
+
}
|
|
136
147
|
if (!filePath) {
|
|
137
148
|
res.statusCode = 404;
|
|
138
149
|
res.setHeader('Content-Type', 'application/json');
|
|
@@ -157,7 +168,43 @@ export function lumenLoadersPlugin(pagesDir) {
|
|
|
157
168
|
// Extract locale from query if provided by the client router
|
|
158
169
|
const locale = query.__locale;
|
|
159
170
|
delete query.__locale;
|
|
160
|
-
|
|
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 });
|
|
161
208
|
if (isRedirectResponse(result)) {
|
|
162
209
|
res.statusCode = result.status || 302;
|
|
163
210
|
res.setHeader('Location', result.location);
|
|
@@ -197,8 +244,8 @@ export function lumenLoadersPlugin(pagesDir) {
|
|
|
197
244
|
// Apply to page files and layout files within the pages directory
|
|
198
245
|
if (!id.startsWith(pagesDir) || !id.endsWith('.ts'))
|
|
199
246
|
return;
|
|
200
|
-
const hasLoader = code
|
|
201
|
-
const hasSubscribe = code
|
|
247
|
+
const hasLoader = hasTopLevelServerFunction(code, 'loader');
|
|
248
|
+
const hasSubscribe = hasTopLevelServerFunction(code, 'subscribe');
|
|
202
249
|
if (!hasLoader && !hasSubscribe)
|
|
203
250
|
return;
|
|
204
251
|
let result = code;
|
|
@@ -225,8 +272,13 @@ export function lumenLoadersPlugin(pagesDir) {
|
|
|
225
272
|
* GET /__nk_loader/__layout/?__dir=dashboard
|
|
226
273
|
*/
|
|
227
274
|
async function handleLayoutLoader(server, pagesDir, dir, req, res) {
|
|
228
|
-
// Resolve the layout file from the directory
|
|
229
|
-
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
|
+
}
|
|
230
282
|
let layoutFile = null;
|
|
231
283
|
for (const ext of ['.ts', '.js']) {
|
|
232
284
|
const p = path.join(layoutDir, `_layout${ext}`);
|
|
@@ -261,7 +313,37 @@ async function handleLayoutLoader(server, pagesDir, dir, req, res) {
|
|
|
261
313
|
}
|
|
262
314
|
}
|
|
263
315
|
const locale = query.__locale;
|
|
264
|
-
|
|
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 });
|
|
265
347
|
if (isRedirectResponse(result)) {
|
|
266
348
|
res.statusCode = result.status || 302;
|
|
267
349
|
res.setHeader('Location', result.location);
|
|
@@ -362,14 +444,33 @@ function findDynamicPage(baseDir, segments) {
|
|
|
362
444
|
}
|
|
363
445
|
return null;
|
|
364
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
|
+
}
|
|
365
462
|
/**
|
|
366
463
|
* Strip a named server-side function (loader/subscribe) from client code using brace-depth tracking.
|
|
367
464
|
*/
|
|
368
465
|
function stripServerFunction(code, fnName) {
|
|
466
|
+
const classStart = code.search(/export\s+class\s+\w+/);
|
|
369
467
|
const regex = new RegExp(`export\\s+(async\\s+)?function\\s+${fnName}\\s*\\(`);
|
|
370
|
-
const match =
|
|
468
|
+
const match = regex.exec(code);
|
|
371
469
|
if (!match)
|
|
372
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;
|
|
373
474
|
const startIdx = match.index;
|
|
374
475
|
let parenDepth = 1;
|
|
375
476
|
let sigIdx = startIdx + match[0].length;
|
|
@@ -386,9 +487,36 @@ function stripServerFunction(code, fnName) {
|
|
|
386
487
|
let depth = 1;
|
|
387
488
|
let i = braceStart + 1;
|
|
388
489
|
while (i < code.length && depth > 0) {
|
|
389
|
-
|
|
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 === '{')
|
|
390
518
|
depth++;
|
|
391
|
-
else if (
|
|
519
|
+
else if (ch === '}')
|
|
392
520
|
depth--;
|
|
393
521
|
i++;
|
|
394
522
|
}
|
|
@@ -401,7 +529,12 @@ function stripServerFunction(code, fnName) {
|
|
|
401
529
|
* GET /__nk_subscribe/__layout/?__dir=<dir>
|
|
402
530
|
*/
|
|
403
531
|
async function handleLayoutSubscribe(server, pagesDir, dir, query, req, res) {
|
|
404
|
-
const layoutDir = path.
|
|
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
|
+
}
|
|
405
538
|
let layoutFile = null;
|
|
406
539
|
for (const ext of ['.ts', '.js']) {
|
|
407
540
|
const p = path.join(layoutDir, `_layout${ext}`);
|
|
@@ -432,7 +565,7 @@ async function handleLayoutSubscribe(server, pagesDir, dir, query, req, res) {
|
|
|
432
565
|
const push = (data) => {
|
|
433
566
|
res.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
434
567
|
};
|
|
435
|
-
const cleanup = mod.subscribe({ params: {}, push, headers: req.headers, locale });
|
|
568
|
+
const cleanup = mod.subscribe({ params: {}, push, headers: req.headers, locale, user: req.nkAuth?.user ?? null });
|
|
436
569
|
res.on('close', () => {
|
|
437
570
|
if (typeof cleanup === 'function')
|
|
438
571
|
cleanup();
|