@nuraly/lumenjs 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (306) hide show
  1. package/README.md +62 -282
  2. package/dist/auth/config.d.ts +23 -0
  3. package/dist/auth/config.js +115 -0
  4. package/dist/auth/guard.d.ts +12 -0
  5. package/dist/auth/guard.js +28 -0
  6. package/dist/auth/index.d.ts +3 -0
  7. package/dist/auth/index.js +1 -0
  8. package/dist/auth/middleware.d.ts +23 -0
  9. package/dist/auth/middleware.js +89 -0
  10. package/dist/auth/native-auth.d.ts +82 -0
  11. package/dist/auth/native-auth.js +340 -0
  12. package/dist/auth/oidc-client.d.ts +17 -0
  13. package/dist/auth/oidc-client.js +123 -0
  14. package/dist/auth/providers/google.d.ts +23 -0
  15. package/dist/auth/providers/google.js +25 -0
  16. package/dist/auth/providers/index.d.ts +2 -0
  17. package/dist/auth/providers/index.js +1 -0
  18. package/dist/auth/routes/login.d.ts +8 -0
  19. package/dist/auth/routes/login.js +121 -0
  20. package/dist/auth/routes/logout.d.ts +4 -0
  21. package/dist/auth/routes/logout.js +79 -0
  22. package/dist/auth/routes/oidc-callback.d.ts +3 -0
  23. package/dist/auth/routes/oidc-callback.js +70 -0
  24. package/dist/auth/routes/password.d.ts +5 -0
  25. package/dist/auth/routes/password.js +149 -0
  26. package/dist/auth/routes/signup.d.ts +3 -0
  27. package/dist/auth/routes/signup.js +81 -0
  28. package/dist/auth/routes/token.d.ts +4 -0
  29. package/dist/auth/routes/token.js +70 -0
  30. package/dist/auth/routes/totp.d.ts +22 -0
  31. package/dist/auth/routes/totp.js +232 -0
  32. package/dist/auth/routes/utils.d.ts +7 -0
  33. package/dist/auth/routes/utils.js +35 -0
  34. package/dist/auth/routes/verify.d.ts +3 -0
  35. package/dist/auth/routes/verify.js +26 -0
  36. package/dist/auth/routes.d.ts +8 -0
  37. package/dist/auth/routes.js +124 -0
  38. package/dist/auth/session.d.ts +8 -0
  39. package/dist/auth/session.js +54 -0
  40. package/dist/auth/token.d.ts +33 -0
  41. package/dist/auth/token.js +90 -0
  42. package/dist/auth/types.d.ts +156 -0
  43. package/dist/auth/types.js +2 -0
  44. package/dist/build/build-client.d.ts +15 -0
  45. package/dist/build/build-client.js +45 -0
  46. package/dist/build/build-prerender.d.ts +11 -0
  47. package/dist/build/build-prerender.js +159 -0
  48. package/dist/build/build-server.d.ts +18 -0
  49. package/dist/build/build-server.js +107 -0
  50. package/dist/build/build.js +60 -123
  51. package/dist/build/scan.d.ts +18 -0
  52. package/dist/build/scan.js +77 -6
  53. package/dist/build/serve-api.js +8 -2
  54. package/dist/build/serve-loaders.d.ts +4 -4
  55. package/dist/build/serve-loaders.js +26 -18
  56. package/dist/build/serve-ssr.js +38 -11
  57. package/dist/build/serve-static.js +3 -3
  58. package/dist/build/serve.js +341 -18
  59. package/dist/cli.js +37 -6
  60. package/dist/communication/encryption.d.ts +35 -0
  61. package/dist/communication/encryption.js +90 -0
  62. package/dist/communication/handlers/context.d.ts +27 -0
  63. package/dist/communication/handlers/context.js +1 -0
  64. package/dist/communication/handlers/conversation.d.ts +24 -0
  65. package/dist/communication/handlers/conversation.js +113 -0
  66. package/dist/communication/handlers/file-upload.d.ts +17 -0
  67. package/dist/communication/handlers/file-upload.js +62 -0
  68. package/dist/communication/handlers/messaging.d.ts +30 -0
  69. package/dist/communication/handlers/messaging.js +237 -0
  70. package/dist/communication/handlers/presence.d.ts +15 -0
  71. package/dist/communication/handlers/presence.js +76 -0
  72. package/dist/communication/handlers.d.ts +5 -0
  73. package/dist/communication/handlers.js +5 -0
  74. package/dist/communication/index.d.ts +9 -0
  75. package/dist/communication/index.js +7 -0
  76. package/dist/communication/link-preview.d.ts +18 -0
  77. package/dist/communication/link-preview.js +115 -0
  78. package/dist/communication/schema.d.ts +10 -0
  79. package/dist/communication/schema.js +101 -0
  80. package/dist/communication/server.d.ts +86 -0
  81. package/dist/communication/server.js +212 -0
  82. package/dist/communication/signaling.d.ts +43 -0
  83. package/dist/communication/signaling.js +271 -0
  84. package/dist/communication/store.d.ts +71 -0
  85. package/dist/communication/store.js +289 -0
  86. package/dist/communication/types.d.ts +454 -0
  87. package/dist/communication/types.js +1 -0
  88. package/dist/create.d.ts +1 -0
  89. package/dist/create.js +55 -0
  90. package/dist/db/auto-migrate.d.ts +3 -0
  91. package/dist/db/auto-migrate.js +100 -0
  92. package/dist/db/client.d.ts +3 -0
  93. package/dist/db/client.js +18 -0
  94. package/dist/db/index.d.ts +17 -13
  95. package/dist/db/index.js +205 -26
  96. package/dist/db/seed.d.ts +12 -0
  97. package/dist/db/seed.js +88 -0
  98. package/dist/db/table.d.ts +10 -0
  99. package/dist/db/table.js +12 -0
  100. package/dist/dev-server/config.d.ts +11 -0
  101. package/dist/dev-server/config.js +40 -20
  102. package/dist/dev-server/index-html.d.ts +4 -0
  103. package/dist/dev-server/index-html.js +21 -6
  104. package/dist/dev-server/nuralyui-aliases.d.ts +0 -4
  105. package/dist/dev-server/nuralyui-aliases.js +115 -94
  106. package/dist/dev-server/plugins/vite-plugin-api-routes.js +29 -5
  107. package/dist/dev-server/plugins/vite-plugin-auth.d.ts +6 -0
  108. package/dist/dev-server/plugins/vite-plugin-auth.js +223 -0
  109. package/dist/dev-server/plugins/vite-plugin-auto-define.d.ts +16 -0
  110. package/dist/dev-server/plugins/vite-plugin-auto-define.js +111 -0
  111. package/dist/dev-server/plugins/vite-plugin-communication.d.ts +6 -0
  112. package/dist/dev-server/plugins/vite-plugin-communication.js +205 -0
  113. package/dist/dev-server/plugins/vite-plugin-editor-api.d.ts +6 -0
  114. package/dist/dev-server/plugins/vite-plugin-editor-api.js +318 -0
  115. package/dist/dev-server/plugins/vite-plugin-i18n.js +69 -2
  116. package/dist/dev-server/plugins/vite-plugin-lit-dedup.d.ts +6 -0
  117. package/dist/dev-server/plugins/vite-plugin-lit-dedup.js +78 -34
  118. package/dist/dev-server/plugins/vite-plugin-lit-hmr.js +44 -2
  119. package/dist/dev-server/plugins/vite-plugin-llms.d.ts +2 -0
  120. package/dist/dev-server/plugins/vite-plugin-llms.js +92 -0
  121. package/dist/dev-server/plugins/vite-plugin-loaders.js +146 -13
  122. package/dist/dev-server/plugins/vite-plugin-routes.js +16 -5
  123. package/dist/dev-server/plugins/vite-plugin-socketio.d.ts +2 -0
  124. package/dist/dev-server/plugins/vite-plugin-socketio.js +51 -0
  125. package/dist/dev-server/plugins/vite-plugin-source-annotator.d.ts +2 -0
  126. package/dist/dev-server/plugins/vite-plugin-source-annotator.js +26 -3
  127. package/dist/dev-server/plugins/vite-plugin-storage.d.ts +10 -0
  128. package/dist/dev-server/plugins/vite-plugin-storage.js +126 -0
  129. package/dist/dev-server/plugins/vite-plugin-virtual-modules.js +140 -3
  130. package/dist/dev-server/server.js +242 -70
  131. package/dist/dev-server/ssr-render.d.ts +2 -1
  132. package/dist/dev-server/ssr-render.js +117 -50
  133. package/dist/editor/ai/backend.d.ts +20 -0
  134. package/dist/editor/ai/backend.js +113 -0
  135. package/dist/editor/ai/claude-code-client.d.ts +20 -0
  136. package/dist/editor/ai/claude-code-client.js +145 -0
  137. package/dist/editor/ai/deepseek-client.d.ts +7 -0
  138. package/dist/editor/ai/deepseek-client.js +113 -0
  139. package/dist/editor/ai/opencode-client.d.ts +14 -0
  140. package/dist/editor/ai/opencode-client.js +99 -0
  141. package/dist/editor/ai/snapshot-store.d.ts +22 -0
  142. package/dist/editor/ai/snapshot-store.js +35 -0
  143. package/dist/editor/ai/types.d.ts +30 -0
  144. package/dist/editor/ai/types.js +136 -0
  145. package/dist/editor/ai-chat-panel.d.ts +13 -0
  146. package/dist/editor/ai-chat-panel.js +613 -0
  147. package/dist/editor/ai-markdown.d.ts +10 -0
  148. package/dist/editor/ai-markdown.js +70 -0
  149. package/dist/editor/ai-project-panel.d.ts +11 -0
  150. package/dist/editor/ai-project-panel.js +332 -0
  151. package/dist/editor/ast-modification.d.ts +11 -0
  152. package/dist/editor/ast-modification.js +1 -0
  153. package/dist/editor/ast-service.d.ts +30 -0
  154. package/dist/editor/ast-service.js +180 -0
  155. package/dist/editor/css-rules.d.ts +54 -0
  156. package/dist/editor/css-rules.js +423 -0
  157. package/dist/editor/editor-api-client.d.ts +51 -0
  158. package/dist/editor/editor-api-client.js +162 -0
  159. package/dist/editor/editor-bridge.d.ts +1 -0
  160. package/dist/editor/editor-bridge.js +18 -8
  161. package/dist/editor/editor-toolbar.d.ts +14 -0
  162. package/dist/editor/editor-toolbar.js +115 -0
  163. package/dist/editor/file-editor.d.ts +9 -0
  164. package/dist/editor/file-editor.js +236 -0
  165. package/dist/editor/file-service.d.ts +16 -0
  166. package/dist/editor/file-service.js +52 -0
  167. package/dist/editor/i18n-key-gen.d.ts +1 -0
  168. package/dist/editor/i18n-key-gen.js +7 -0
  169. package/dist/editor/inline-text-edit.d.ts +5 -0
  170. package/dist/editor/inline-text-edit.js +173 -92
  171. package/dist/editor/overlay-events.d.ts +5 -0
  172. package/dist/editor/overlay-events.js +364 -0
  173. package/dist/editor/overlay-hmr.d.ts +2 -0
  174. package/dist/editor/overlay-hmr.js +76 -0
  175. package/dist/editor/overlay-selection.d.ts +29 -0
  176. package/dist/editor/overlay-selection.js +148 -0
  177. package/dist/editor/overlay-utils.d.ts +12 -0
  178. package/dist/editor/overlay-utils.js +59 -0
  179. package/dist/editor/properties-panel-persist.d.ts +14 -0
  180. package/dist/editor/properties-panel-persist.js +70 -0
  181. package/dist/editor/properties-panel-rows.d.ts +10 -0
  182. package/dist/editor/properties-panel-rows.js +349 -0
  183. package/dist/editor/properties-panel-styles.d.ts +4 -0
  184. package/dist/editor/properties-panel-styles.js +174 -0
  185. package/dist/editor/properties-panel.d.ts +4 -0
  186. package/dist/editor/properties-panel.js +148 -0
  187. package/dist/editor/property-registry.d.ts +16 -0
  188. package/dist/editor/property-registry.js +303 -0
  189. package/dist/editor/standalone-file-panel.d.ts +0 -0
  190. package/dist/editor/standalone-file-panel.js +1 -0
  191. package/dist/editor/standalone-overlay-dom.d.ts +0 -0
  192. package/dist/editor/standalone-overlay-dom.js +1 -0
  193. package/dist/editor/standalone-overlay-styles.d.ts +0 -0
  194. package/dist/editor/standalone-overlay-styles.js +1 -0
  195. package/dist/editor/standalone-overlay.d.ts +1 -0
  196. package/dist/editor/standalone-overlay.js +76 -0
  197. package/dist/editor/syntax-highlighter.d.ts +4 -0
  198. package/dist/editor/syntax-highlighter.js +81 -0
  199. package/dist/editor/text-toolbar.d.ts +11 -0
  200. package/dist/editor/text-toolbar.js +327 -0
  201. package/dist/editor/toolbar-styles.d.ts +4 -0
  202. package/dist/editor/toolbar-styles.js +198 -0
  203. package/dist/email/index.d.ts +32 -0
  204. package/dist/email/index.js +154 -0
  205. package/dist/email/providers/resend.d.ts +2 -0
  206. package/dist/email/providers/resend.js +24 -0
  207. package/dist/email/providers/sendgrid.d.ts +2 -0
  208. package/dist/email/providers/sendgrid.js +31 -0
  209. package/dist/email/providers/smtp.d.ts +13 -0
  210. package/dist/email/providers/smtp.js +125 -0
  211. package/dist/email/template-engine.d.ts +18 -0
  212. package/dist/email/template-engine.js +116 -0
  213. package/dist/email/templates/base.d.ts +9 -0
  214. package/dist/email/templates/base.js +65 -0
  215. package/dist/email/templates/password-reset.d.ts +5 -0
  216. package/dist/email/templates/password-reset.js +15 -0
  217. package/dist/email/templates/verify-email.d.ts +5 -0
  218. package/dist/email/templates/verify-email.js +15 -0
  219. package/dist/email/templates/welcome.d.ts +5 -0
  220. package/dist/email/templates/welcome.js +13 -0
  221. package/dist/email/types.d.ts +49 -0
  222. package/dist/email/types.js +1 -0
  223. package/dist/llms/generate.d.ts +46 -0
  224. package/dist/llms/generate.js +185 -0
  225. package/dist/permissions/guard.d.ts +28 -0
  226. package/dist/permissions/guard.js +30 -0
  227. package/dist/permissions/index.d.ts +6 -0
  228. package/dist/permissions/index.js +3 -0
  229. package/dist/permissions/service.d.ts +80 -0
  230. package/dist/permissions/service.js +210 -0
  231. package/dist/permissions/tables.d.ts +5 -0
  232. package/dist/permissions/tables.js +68 -0
  233. package/dist/permissions/types.d.ts +33 -0
  234. package/dist/permissions/types.js +1 -0
  235. package/dist/runtime/app-shell.d.ts +1 -1
  236. package/dist/runtime/app-shell.js +164 -0
  237. package/dist/runtime/auth.d.ts +10 -0
  238. package/dist/runtime/auth.js +30 -0
  239. package/dist/runtime/communication.d.ts +137 -0
  240. package/dist/runtime/communication.js +228 -0
  241. package/dist/runtime/error-boundary.d.ts +23 -0
  242. package/dist/runtime/error-boundary.js +120 -0
  243. package/dist/runtime/i18n.d.ts +6 -1
  244. package/dist/runtime/i18n.js +42 -21
  245. package/dist/runtime/island.d.ts +16 -0
  246. package/dist/runtime/island.js +80 -0
  247. package/dist/runtime/router-data.d.ts +3 -0
  248. package/dist/runtime/router-data.js +102 -17
  249. package/dist/runtime/router-hydration.js +34 -2
  250. package/dist/runtime/router.d.ts +19 -2
  251. package/dist/runtime/router.js +237 -43
  252. package/dist/runtime/socket-client.d.ts +2 -0
  253. package/dist/runtime/socket-client.js +30 -0
  254. package/dist/runtime/webrtc.d.ts +91 -0
  255. package/dist/runtime/webrtc.js +428 -0
  256. package/dist/shared/dom-shims.js +4 -2
  257. package/dist/shared/graceful-shutdown.d.ts +8 -0
  258. package/dist/shared/graceful-shutdown.js +36 -0
  259. package/dist/shared/health.d.ts +8 -0
  260. package/dist/shared/health.js +25 -0
  261. package/dist/shared/llms-txt.d.ts +31 -0
  262. package/dist/shared/llms-txt.js +85 -0
  263. package/dist/shared/logger.d.ts +32 -0
  264. package/dist/shared/logger.js +93 -0
  265. package/dist/shared/meta.d.ts +27 -0
  266. package/dist/shared/meta.js +71 -0
  267. package/dist/shared/middleware-runner.d.ts +9 -0
  268. package/dist/shared/middleware-runner.js +29 -0
  269. package/dist/shared/rate-limit.d.ts +18 -0
  270. package/dist/shared/rate-limit.js +71 -0
  271. package/dist/shared/request-id.d.ts +5 -0
  272. package/dist/shared/request-id.js +18 -0
  273. package/dist/shared/route-matching.js +16 -1
  274. package/dist/shared/security-headers.d.ts +18 -0
  275. package/dist/shared/security-headers.js +38 -0
  276. package/dist/shared/socket-io-setup.d.ts +11 -0
  277. package/dist/shared/socket-io-setup.js +51 -0
  278. package/dist/shared/types.d.ts +15 -0
  279. package/dist/shared/utils.d.ts +33 -7
  280. package/dist/shared/utils.js +164 -27
  281. package/dist/storage/adapters/local.d.ts +44 -0
  282. package/dist/storage/adapters/local.js +85 -0
  283. package/dist/storage/adapters/s3.d.ts +32 -0
  284. package/dist/storage/adapters/s3.js +119 -0
  285. package/dist/storage/adapters/types.d.ts +53 -0
  286. package/dist/storage/adapters/types.js +1 -0
  287. package/dist/storage/index.d.ts +76 -0
  288. package/dist/storage/index.js +83 -0
  289. package/package.json +45 -7
  290. package/templates/blog/api/posts.ts +4 -18
  291. package/templates/blog/data/migrations/001_init.sql +6 -5
  292. package/templates/blog/lumenjs.config.ts +3 -0
  293. package/templates/blog/package.json +14 -0
  294. package/templates/blog/pages/_layout.ts +25 -0
  295. package/templates/blog/pages/index.ts +48 -22
  296. package/templates/blog/pages/posts/[slug].ts +45 -20
  297. package/templates/blog/pages/tag/[tag].ts +44 -0
  298. package/templates/dashboard/api/stats.ts +8 -5
  299. package/templates/dashboard/lumenjs.config.ts +3 -0
  300. package/templates/dashboard/package.json +14 -0
  301. package/templates/dashboard/pages/_layout.ts +25 -0
  302. package/templates/dashboard/pages/index.ts +54 -23
  303. package/templates/dashboard/pages/settings/index.ts +29 -0
  304. package/templates/default/lumenjs.config.ts +3 -0
  305. package/templates/default/package.json +14 -0
  306. package/templates/default/pages/index.ts +24 -0
@@ -1,6 +1,6 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { dirToLayoutTagName, fileHasLoader, fileHasSubscribe, filePathToRoute, filePathToTagName } from '../../shared/utils.js';
3
+ import { dirToLayoutTagName, fileHasLoader, fileHasSubscribe, fileHasSocket, 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);
@@ -99,8 +105,13 @@ export function lumenRoutesPlugin(pagesDir) {
99
105
  .map(r => {
100
106
  const hasLoader = fileHasLoader(r.componentPath);
101
107
  const hasSubscribe = fileHasSubscribe(r.componentPath);
108
+ const hasSocketFlag = fileHasSocket(r.componentPath);
109
+ const hasAuth = fileHasAuth(r.componentPath);
110
+ const hasMeta = fileHasMeta(r.componentPath);
111
+ const isStandalone = fileHasStandalone(r.componentPath);
102
112
  const componentPath = r.componentPath.replace(/\\/g, '/');
103
- const chain = getLayoutChain(r.componentPath, layouts);
113
+ // Standalone pages skip all layouts
114
+ const chain = isStandalone ? [] : getLayoutChain(r.componentPath, layouts);
104
115
  let layoutsStr = '';
105
116
  if (chain.length > 0) {
106
117
  const items = chain.map(l => {
@@ -111,7 +122,7 @@ export function lumenRoutesPlugin(pagesDir) {
111
122
  });
112
123
  layoutsStr = `, layouts: [${items.join(', ')}]`;
113
124
  }
114
- return ` { path: ${JSON.stringify(r.path)}, tagName: ${JSON.stringify(r.tagName)}${hasLoader ? ', hasLoader: true' : ''}${hasSubscribe ? ', hasSubscribe: true' : ''}, load: () => import('${componentPath}')${layoutsStr} }`;
125
+ return ` { path: ${JSON.stringify(r.path)}, tagName: ${JSON.stringify(r.tagName)}${hasLoader ? ', hasLoader: true' : ''}${hasSubscribe ? ', hasSubscribe: true' : ''}${hasSocketFlag ? ', hasSocket: true' : ''}${hasMeta ? ', hasMeta: true' : ''}${hasAuth ? ', __nk_has_auth: true' : ''}, load: () => import('${componentPath}')${layoutsStr} }`;
115
126
  })
116
127
  .join(',\n');
117
128
  return `export const routes = [\n${routeArray}\n];\n`;
@@ -0,0 +1,2 @@
1
+ import { Plugin } from 'vite';
2
+ export declare function lumenSocketIOPlugin(pagesDir: string): Plugin;
@@ -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
- const beforeTemplate = code.substring(0, code.indexOf(match));
17
- const baseLine = beforeTemplate.split('\n').length;
18
- const annotated = templateContent.replace(/<([a-z][a-z0-9]*-[a-z0-9-]*)([\s>])/gi, (tagMatch, tagName, after) => {
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;
@@ -0,0 +1,126 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+ import { LocalStorageAdapter } from '../../storage/adapters/local.js';
4
+ import { setStorage } from '../../storage/index.js';
5
+ /**
6
+ * LumenJS storage plugin (dev mode).
7
+ *
8
+ * - Creates a LocalStorageAdapter pointing to `{projectDir}/uploads`
9
+ * - Registers it as the global storage singleton (`useStorage()`)
10
+ * - Serves uploaded files at `/uploads/*`
11
+ * - Handles presigned PUT requests at `/__nk_storage/upload/:token`
12
+ */
13
+ export function lumenStoragePlugin(projectDir) {
14
+ const uploadDir = path.join(projectDir, 'uploads');
15
+ const adapter = new LocalStorageAdapter({ uploadDir, publicPath: '/uploads' });
16
+ // Register global singleton so API routes and communication handlers can use it
17
+ setStorage(adapter);
18
+ return {
19
+ name: 'lumenjs-storage',
20
+ configureServer(server) {
21
+ // ── Presigned upload endpoint ──────────────────────────────
22
+ // Client sends a PUT to /__nk_storage/upload/:token with the (possibly
23
+ // encrypted) file body. The token was issued via adapter.presignPut().
24
+ server.middlewares.use('/__nk_storage/upload', async (req, res, next) => {
25
+ if (req.method !== 'PUT')
26
+ return next();
27
+ const token = req.url?.replace(/^\//, '').split('?')[0];
28
+ if (!token) {
29
+ res.statusCode = 400;
30
+ res.end(JSON.stringify({ error: 'missing token' }));
31
+ return;
32
+ }
33
+ const pending = adapter.consumeUpload(token);
34
+ if (!pending) {
35
+ res.statusCode = 410; // Gone — expired or unknown token
36
+ res.setHeader('Content-Type', 'application/json');
37
+ res.end(JSON.stringify({ error: 'upload token expired or invalid' }));
38
+ return;
39
+ }
40
+ // Read raw body with streaming size enforcement
41
+ const MAX_UPLOAD = pending.maxSize || 50 * 1024 * 1024; // 50MB default cap
42
+ const chunks = [];
43
+ let uploadSize = 0;
44
+ let aborted = false;
45
+ req.on('data', (c) => {
46
+ uploadSize += c.length;
47
+ if (uploadSize > MAX_UPLOAD) {
48
+ aborted = true;
49
+ req.destroy();
50
+ res.statusCode = 413;
51
+ res.setHeader('Content-Type', 'application/json');
52
+ res.end(JSON.stringify({ error: 'file_too_large', maxSize: MAX_UPLOAD, received: uploadSize }));
53
+ return;
54
+ }
55
+ chunks.push(c);
56
+ });
57
+ await new Promise((resolve, reject) => {
58
+ req.on('end', resolve);
59
+ req.on('error', reject);
60
+ });
61
+ if (aborted)
62
+ return;
63
+ const body = Buffer.concat(chunks);
64
+ // Enforce maxSize if specified (redundant safety check)
65
+ if (pending.maxSize && body.length > pending.maxSize) {
66
+ res.statusCode = 413;
67
+ res.setHeader('Content-Type', 'application/json');
68
+ res.end(JSON.stringify({
69
+ error: `file_too_large`,
70
+ maxSize: pending.maxSize,
71
+ received: body.length,
72
+ }));
73
+ return;
74
+ }
75
+ // Write to disk (validate path stays within uploadDir)
76
+ const filePath = path.resolve(uploadDir, pending.key);
77
+ if (!filePath.startsWith(uploadDir + path.sep) && filePath !== uploadDir) {
78
+ res.statusCode = 400;
79
+ res.setHeader('Content-Type', 'application/json');
80
+ res.end(JSON.stringify({ error: 'invalid storage key' }));
81
+ return;
82
+ }
83
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
84
+ fs.writeFileSync(filePath, body);
85
+ res.statusCode = 200;
86
+ res.setHeader('Content-Type', 'application/json');
87
+ res.end(JSON.stringify({
88
+ key: pending.key,
89
+ url: adapter.publicUrl(pending.key),
90
+ size: body.length,
91
+ }));
92
+ });
93
+ // ── Static file serving ────────────────────────────────────
94
+ // Serve files from uploadDir at /uploads/*
95
+ server.middlewares.use('/uploads', (req, res, next) => {
96
+ if (req.method !== 'GET' && req.method !== 'HEAD')
97
+ return next();
98
+ const filePath = path.resolve(uploadDir, (req.url?.split('?')[0] ?? '/').replace(/^\/+/, ''));
99
+ if (!filePath.startsWith(uploadDir + path.sep) && filePath !== uploadDir) {
100
+ return next();
101
+ }
102
+ if (!fs.existsSync(filePath) || fs.statSync(filePath).isDirectory()) {
103
+ return next();
104
+ }
105
+ const ext = path.extname(filePath).toLowerCase();
106
+ const mimeTypes = {
107
+ '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png',
108
+ '.gif': 'image/gif', '.webp': 'image/webp', '.svg': 'image/svg+xml',
109
+ '.mp4': 'video/mp4', '.webm': 'video/webm', '.mp3': 'audio/mpeg',
110
+ '.pdf': 'application/pdf', '.json': 'application/json',
111
+ '.txt': 'text/plain', '.bin': 'application/octet-stream',
112
+ };
113
+ const contentType = mimeTypes[ext] ?? 'application/octet-stream';
114
+ const stat = fs.statSync(filePath);
115
+ res.setHeader('Content-Type', contentType);
116
+ res.setHeader('Content-Length', stat.size);
117
+ res.setHeader('Cache-Control', 'no-cache');
118
+ if (req.method === 'HEAD') {
119
+ res.end();
120
+ return;
121
+ }
122
+ fs.createReadStream(filePath).pipe(res);
123
+ });
124
+ },
125
+ };
126
+ }
@@ -15,16 +15,49 @@ export function virtualModulesPlugin(runtimeDir, editorDir) {
15
15
  'router-data': 'router-data.js',
16
16
  'router-hydration': 'router-hydration.js',
17
17
  'i18n': 'i18n.js',
18
+ 'auth': 'auth.js',
19
+ 'communication': 'communication.js',
20
+ 'webrtc': 'webrtc.js',
21
+ 'error-boundary': 'error-boundary.js',
22
+ 'island': 'island.js',
23
+ 'hydrate-support': '__virtual__',
18
24
  };
19
25
  // Modules resolved via resolve.alias instead of virtual module.
20
26
  // They still appear in the map so relative import rewrites work.
21
- const aliasedModules = new Set(['i18n']);
27
+ const aliasedModules = new Set(['i18n', 'auth', 'communication', 'webrtc']);
22
28
  const editorModules = {
23
29
  'editor-bridge': 'editor-bridge.js',
24
30
  'element-annotator': 'element-annotator.js',
25
31
  'click-select': 'click-select.js',
26
32
  'hover-detect': 'hover-detect.js',
27
33
  'inline-text-edit': 'inline-text-edit.js',
34
+ 'editor-api-client': 'editor-api-client.js',
35
+ 'standalone-overlay': 'standalone-overlay.js',
36
+ 'standalone-overlay-dom': 'standalone-overlay-dom.js',
37
+ 'standalone-overlay-styles': 'standalone-overlay-styles.js',
38
+ 'standalone-file-panel': 'standalone-file-panel.js',
39
+ 'overlay-utils': 'overlay-utils.js',
40
+ 'overlay-events': 'overlay-events.js',
41
+ 'overlay-hmr': 'overlay-hmr.js',
42
+ 'overlay-selection': 'overlay-selection.js',
43
+ 'text-toolbar': 'text-toolbar.js',
44
+ 'toolbar-styles': 'toolbar-styles.js',
45
+ 'editor-toolbar': 'editor-toolbar.js',
46
+ 'css-rules': 'css-rules.js',
47
+ 'ast-modification': 'ast-modification.js',
48
+ 'ast-service': 'ast-service.js',
49
+ 'file-service': 'file-service.js',
50
+ 'file-editor': 'file-editor.js',
51
+ 'syntax-highlighter': 'syntax-highlighter.js',
52
+ 'property-registry': 'property-registry.js',
53
+ 'properties-panel': 'properties-panel.js',
54
+ 'properties-panel-persist': 'properties-panel-persist.js',
55
+ 'properties-panel-rows': 'properties-panel-rows.js',
56
+ 'properties-panel-styles': 'properties-panel-styles.js',
57
+ 'i18n-key-gen': 'i18n-key-gen.js',
58
+ 'ai-chat-panel': 'ai-chat-panel.js',
59
+ 'ai-project-panel': 'ai-project-panel.js',
60
+ 'ai-markdown': 'ai-markdown.js',
28
61
  };
29
62
  function rewriteRelativeImports(code, modules) {
30
63
  for (const name of Object.keys(modules)) {
@@ -32,14 +65,26 @@ export function virtualModulesPlugin(runtimeDir, editorDir) {
32
65
  // Aliased modules use @lumenjs/name (resolved by Vite alias).
33
66
  // Virtual modules use /@lumenjs/name (resolved by this plugin).
34
67
  const prefix = aliasedModules.has(name) ? '@lumenjs' : '/@lumenjs';
35
- code = code.replace(new RegExp(`from\\s+['"]\\.\\/${file.replace('.', '\\.')}['"]`, 'g'), `from '${prefix}/${name}'`);
68
+ const escaped = file.replace('.', '\\.');
69
+ // Rewrite `from './file.js'`
70
+ code = code.replace(new RegExp(`from\\s+['"]\\.\\/${escaped}['"]`, 'g'), `from '${prefix}/${name}'`);
71
+ // Rewrite side-effect `import './file.js'`
72
+ code = code.replace(new RegExp(`import\\s+['"]\\.\\/${escaped}['"]`, 'g'), `import '${prefix}/${name}'`);
36
73
  }
37
74
  return code;
38
75
  }
76
+ let viteBase = '/';
39
77
  return {
40
78
  name: 'lumenjs-virtual-modules',
41
79
  enforce: 'pre',
80
+ configResolved(config) {
81
+ viteBase = config.base || '/';
82
+ },
42
83
  resolveId(id) {
84
+ // Strip Vite base prefix if present (e.g. /__app_dev/{id}/@lumenjs/foo → /@lumenjs/foo)
85
+ if (viteBase !== '/' && id.startsWith(viteBase)) {
86
+ id = '/' + id.slice(viteBase.length);
87
+ }
43
88
  const match = id.match(/^\/@lumenjs\/(.+)$/);
44
89
  if (!match)
45
90
  return;
@@ -55,8 +100,100 @@ export function virtualModulesPlugin(runtimeDir, editorDir) {
55
100
  if (!id.startsWith('\0lumenjs:'))
56
101
  return;
57
102
  const name = id.slice('\0lumenjs:'.length);
103
+ if (name === 'hydrate-support') {
104
+ // Custom hydrate support that catches digest mismatch errors and falls
105
+ // back to CSR instead of leaving double-rendered content.
106
+ // The stock @lit-labs/ssr-client/lit-element-hydrate-support.js throws
107
+ // on digest mismatch, sets _$AG=false before throwing, so the next
108
+ // update() appends fresh render alongside stale SSR content.
109
+ return `
110
+ import { render } from 'lit-html';
111
+ import { hydrate } from '@lit-labs/ssr-client';
112
+
113
+ globalThis.litElementHydrateSupport = ({LitElement}) => {
114
+ const observedGet = Object.getOwnPropertyDescriptor(
115
+ Object.getPrototypeOf(LitElement), 'observedAttributes'
116
+ ).get;
117
+ Object.defineProperty(LitElement, 'observedAttributes', {
118
+ get() { return [...observedGet.call(this), 'defer-hydration']; }
119
+ });
120
+
121
+ const origAttrChanged = LitElement.prototype.attributeChangedCallback;
122
+ LitElement.prototype.attributeChangedCallback = function(name, old, value) {
123
+ if (name === 'defer-hydration' && value === null) {
124
+ origConnected.call(this);
125
+ }
126
+ origAttrChanged.call(this, name, old, value);
127
+ };
128
+
129
+ const origConnected = LitElement.prototype.connectedCallback;
130
+ LitElement.prototype.connectedCallback = function() {
131
+ if (!this.hasAttribute('defer-hydration')) origConnected.call(this);
132
+ };
133
+
134
+ function adoptElementStyles(el) {
135
+ const styles = el.constructor.elementStyles;
136
+ if (styles?.length && el.renderRoot instanceof ShadowRoot) {
137
+ el.renderRoot.adoptedStyleSheets = styles.map(
138
+ s => s instanceof CSSStyleSheet ? s : s.styleSheet
139
+ );
140
+ }
141
+ }
142
+
143
+ const origCreateRoot = LitElement.prototype.createRenderRoot;
144
+ LitElement.prototype.createRenderRoot = function() {
145
+ if (this.shadowRoot) {
146
+ this._$AG = true;
147
+ // Adopt styles that SSR declarative shadow roots don't include
148
+ adoptElementStyles(this);
149
+ return this.shadowRoot;
150
+ }
151
+ return origCreateRoot.call(this);
152
+ };
153
+
154
+ const superUpdate = Object.getPrototypeOf(LitElement.prototype).update;
155
+ LitElement.prototype.update = function(changedProps) {
156
+ const value = this.render();
157
+ superUpdate.call(this, changedProps);
158
+ if (this._$AG) {
159
+ this._$AG = false;
160
+ for (const attr of this.getAttributeNames()) {
161
+ if (attr.startsWith('hydrate-internals-')) {
162
+ this.removeAttribute(attr.slice(18));
163
+ this.removeAttribute(attr);
164
+ }
165
+ }
166
+ try {
167
+ hydrate(value, this.renderRoot, this.renderOptions);
168
+ } catch (err) {
169
+ // Digest mismatch — re-render fresh but avoid visible flash
170
+ console.warn('[LumenJS] Hydration mismatch for <' + this.localName + '>, falling back to CSR');
171
+ const root = this.renderRoot;
172
+ // Preserve adopted styles so content is never unstyled
173
+ adoptElementStyles(this);
174
+ // Remove only non-style children to keep styles applied during re-render
175
+ const toRemove = [];
176
+ for (let c = root.firstChild; c; c = c.nextSibling) {
177
+ if (c.nodeName !== 'STYLE') toRemove.push(c);
178
+ }
179
+ toRemove.forEach(c => root.removeChild(c));
180
+ delete root._$litPart$;
181
+ render(value, root, this.renderOptions);
182
+ }
183
+ } else {
184
+ render(value, this.renderRoot, this.renderOptions);
185
+ }
186
+ };
187
+ };
188
+ `;
189
+ }
58
190
  if (runtimeModules[name]) {
59
- const code = fs.readFileSync(path.join(runtimeDir, runtimeModules[name]), 'utf-8');
191
+ let code = fs.readFileSync(path.join(runtimeDir, runtimeModules[name]), 'utf-8');
192
+ // Prepend hydrate support to app-shell so all Lit imports share one module graph.
193
+ // Uses our custom hydrate-support virtual module (with CSR fallback) instead of stock.
194
+ if (name === 'app-shell') {
195
+ code = `import '/@lumenjs/hydrate-support';\n` + code;
196
+ }
60
197
  return rewriteRelativeImports(code, runtimeModules);
61
198
  }
62
199
  if (editorModules[name]) {