davaux 0.8.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 (333) hide show
  1. package/BASELINE.md +169 -0
  2. package/CLAUDE.md +518 -0
  3. package/LICENSE +21 -0
  4. package/README.md +36 -0
  5. package/ROADMAP.md +198 -0
  6. package/build.mjs +101 -0
  7. package/client/control.ts +247 -0
  8. package/client/hydrate.ts +37 -0
  9. package/client/index.ts +19 -0
  10. package/client/jsx-runtime.ts +209 -0
  11. package/client/resource.ts +122 -0
  12. package/client/signal.ts +211 -0
  13. package/client/store.ts +110 -0
  14. package/client/useHead.ts +63 -0
  15. package/dist/build/config.d.ts +3 -0
  16. package/dist/build/config.d.ts.map +1 -0
  17. package/dist/build/config.js +38 -0
  18. package/dist/build/config.js.map +7 -0
  19. package/dist/build/index.d.ts +2 -0
  20. package/dist/build/index.d.ts.map +1 -0
  21. package/dist/build/index.js +13 -0
  22. package/dist/build/index.js.map +7 -0
  23. package/dist/build/plugins.d.ts +7 -0
  24. package/dist/build/plugins.d.ts.map +1 -0
  25. package/dist/build/plugins.js +85 -0
  26. package/dist/build/plugins.js.map +7 -0
  27. package/dist/cli.d.ts +2 -0
  28. package/dist/cli.d.ts.map +1 -0
  29. package/dist/cli.js +427 -0
  30. package/dist/cli.js.map +7 -0
  31. package/dist/client/control.d.ts +49 -0
  32. package/dist/client/control.d.ts.map +1 -0
  33. package/dist/client/control.js +154 -0
  34. package/dist/client/control.js.map +7 -0
  35. package/dist/client/hydrate.d.ts +7 -0
  36. package/dist/client/hydrate.d.ts.map +1 -0
  37. package/dist/client/hydrate.js +23 -0
  38. package/dist/client/hydrate.js.map +7 -0
  39. package/dist/client/index.d.ts +12 -0
  40. package/dist/client/index.d.ts.map +1 -0
  41. package/dist/client/index.js +32 -0
  42. package/dist/client/index.js.map +7 -0
  43. package/dist/client/jsx-runtime.d.ts +40 -0
  44. package/dist/client/jsx-runtime.d.ts.map +1 -0
  45. package/dist/client/jsx-runtime.js +139 -0
  46. package/dist/client/jsx-runtime.js.map +7 -0
  47. package/dist/client/resource.d.ts +31 -0
  48. package/dist/client/resource.d.ts.map +1 -0
  49. package/dist/client/resource.js +64 -0
  50. package/dist/client/resource.js.map +7 -0
  51. package/dist/client/signal.d.ts +90 -0
  52. package/dist/client/signal.d.ts.map +1 -0
  53. package/dist/client/signal.js +115 -0
  54. package/dist/client/signal.js.map +7 -0
  55. package/dist/client/store.d.ts +26 -0
  56. package/dist/client/store.d.ts.map +1 -0
  57. package/dist/client/store.js +63 -0
  58. package/dist/client/store.js.map +7 -0
  59. package/dist/client/useHead.d.ts +28 -0
  60. package/dist/client/useHead.d.ts.map +1 -0
  61. package/dist/client/useHead.js +33 -0
  62. package/dist/client/useHead.js.map +7 -0
  63. package/dist/config.d.ts +182 -0
  64. package/dist/config.d.ts.map +1 -0
  65. package/dist/config.js +21 -0
  66. package/dist/config.js.map +7 -0
  67. package/dist/create-multisite.d.ts +2 -0
  68. package/dist/create-multisite.d.ts.map +1 -0
  69. package/dist/create-multisite.js +291 -0
  70. package/dist/create-multisite.js.map +7 -0
  71. package/dist/create.d.ts +2 -0
  72. package/dist/create.d.ts.map +1 -0
  73. package/dist/create.js +179 -0
  74. package/dist/create.js.map +7 -0
  75. package/dist/dev/blueprints.d.ts +11 -0
  76. package/dist/dev/blueprints.d.ts.map +1 -0
  77. package/dist/dev/blueprints.js +65 -0
  78. package/dist/dev/blueprints.js.map +7 -0
  79. package/dist/dev/components.d.ts +19 -0
  80. package/dist/dev/components.d.ts.map +1 -0
  81. package/dist/dev/components.js +87 -0
  82. package/dist/dev/components.js.map +7 -0
  83. package/dist/dev/insert.d.ts +11 -0
  84. package/dist/dev/insert.d.ts.map +1 -0
  85. package/dist/dev/insert.js +160 -0
  86. package/dist/dev/insert.js.map +7 -0
  87. package/dist/dev/remove.d.ts +53 -0
  88. package/dist/dev/remove.d.ts.map +1 -0
  89. package/dist/dev/remove.js +518 -0
  90. package/dist/dev/remove.js.map +7 -0
  91. package/dist/dev/watch.d.ts +26 -0
  92. package/dist/dev/watch.d.ts.map +1 -0
  93. package/dist/dev/watch.js +2905 -0
  94. package/dist/dev/watch.js.map +7 -0
  95. package/dist/errors.d.ts +6 -0
  96. package/dist/errors.d.ts.map +1 -0
  97. package/dist/errors.js +63 -0
  98. package/dist/errors.js.map +7 -0
  99. package/dist/generate.d.ts +2 -0
  100. package/dist/generate.d.ts.map +1 -0
  101. package/dist/generate.js +191 -0
  102. package/dist/generate.js.map +7 -0
  103. package/dist/index.d.ts +9 -0
  104. package/dist/index.d.ts.map +1 -0
  105. package/dist/index.js +57 -0
  106. package/dist/index.js.map +7 -0
  107. package/dist/island.d.ts +24 -0
  108. package/dist/island.d.ts.map +1 -0
  109. package/dist/island.js +15 -0
  110. package/dist/island.js.map +7 -0
  111. package/dist/jsx-runtime.d.ts +406 -0
  112. package/dist/jsx-runtime.d.ts.map +1 -0
  113. package/dist/jsx-runtime.js +90 -0
  114. package/dist/jsx-runtime.js.map +7 -0
  115. package/dist/link.d.ts +27 -0
  116. package/dist/link.d.ts.map +1 -0
  117. package/dist/link.js +29 -0
  118. package/dist/link.js.map +7 -0
  119. package/dist/oml/fragment.d.ts +16 -0
  120. package/dist/oml/fragment.d.ts.map +1 -0
  121. package/dist/oml/fragment.js +26 -0
  122. package/dist/oml/fragment.js.map +7 -0
  123. package/dist/oml/index.d.ts +11 -0
  124. package/dist/oml/index.d.ts.map +1 -0
  125. package/dist/oml/index.js +21 -0
  126. package/dist/oml/index.js.map +7 -0
  127. package/dist/oml/jsx-runtime.d.ts +34 -0
  128. package/dist/oml/jsx-runtime.d.ts.map +1 -0
  129. package/dist/oml/jsx-runtime.js +59 -0
  130. package/dist/oml/jsx-runtime.js.map +7 -0
  131. package/dist/oml/jsx.d.ts +14 -0
  132. package/dist/oml/jsx.d.ts.map +1 -0
  133. package/dist/oml/jsx.js +96 -0
  134. package/dist/oml/jsx.js.map +7 -0
  135. package/dist/oml/page.d.ts +7 -0
  136. package/dist/oml/page.d.ts.map +1 -0
  137. package/dist/oml/page.js +6 -0
  138. package/dist/oml/page.js.map +7 -0
  139. package/dist/oml/render.d.ts +13 -0
  140. package/dist/oml/render.d.ts.map +1 -0
  141. package/dist/oml/render.js +117 -0
  142. package/dist/oml/render.js.map +7 -0
  143. package/dist/oml/types.d.ts +79 -0
  144. package/dist/oml/types.d.ts.map +1 -0
  145. package/dist/oml/types.js +64 -0
  146. package/dist/oml/types.js.map +7 -0
  147. package/dist/router/handler.d.ts +53 -0
  148. package/dist/router/handler.d.ts.map +1 -0
  149. package/dist/router/handler.js +342 -0
  150. package/dist/router/handler.js.map +7 -0
  151. package/dist/router/matcher.d.ts +21 -0
  152. package/dist/router/matcher.d.ts.map +1 -0
  153. package/dist/router/matcher.js +28 -0
  154. package/dist/router/matcher.js.map +7 -0
  155. package/dist/router/scanner.d.ts +17 -0
  156. package/dist/router/scanner.d.ts.map +1 -0
  157. package/dist/router/scanner.js +197 -0
  158. package/dist/router/scanner.js.map +7 -0
  159. package/dist/server/index.d.ts +23 -0
  160. package/dist/server/index.d.ts.map +1 -0
  161. package/dist/server/index.js +29 -0
  162. package/dist/server/index.js.map +7 -0
  163. package/dist/signal.d.ts +15 -0
  164. package/dist/signal.d.ts.map +1 -0
  165. package/dist/signal.js +29 -0
  166. package/dist/signal.js.map +7 -0
  167. package/dist/ssg.d.ts +45 -0
  168. package/dist/ssg.d.ts.map +1 -0
  169. package/dist/ssg.js +175 -0
  170. package/dist/ssg.js.map +7 -0
  171. package/dist/test/actions.test.d.ts +2 -0
  172. package/dist/test/actions.test.d.ts.map +1 -0
  173. package/dist/test/body-limits.test.d.ts +2 -0
  174. package/dist/test/body-limits.test.d.ts.map +1 -0
  175. package/dist/test/errors.test.d.ts +2 -0
  176. package/dist/test/errors.test.d.ts.map +1 -0
  177. package/dist/test/fixtures/routes/[id].page.d.ts +4 -0
  178. package/dist/test/fixtures/routes/[id].page.d.ts.map +1 -0
  179. package/dist/test/fixtures/routes/_error.d.ts +3 -0
  180. package/dist/test/fixtures/routes/_error.d.ts.map +1 -0
  181. package/dist/test/fixtures/routes/_global.d.ts +3 -0
  182. package/dist/test/fixtures/routes/_global.d.ts.map +1 -0
  183. package/dist/test/fixtures/routes/_layout-template.d.ts +3 -0
  184. package/dist/test/fixtures/routes/_layout-template.d.ts.map +1 -0
  185. package/dist/test/fixtures/routes/_layout.d.ts +3 -0
  186. package/dist/test/fixtures/routes/_layout.d.ts.map +1 -0
  187. package/dist/test/fixtures/routes/_layout_scripts.d.ts +3 -0
  188. package/dist/test/fixtures/routes/_layout_scripts.d.ts.map +1 -0
  189. package/dist/test/fixtures/routes/_middleware.d.ts +3 -0
  190. package/dist/test/fixtures/routes/_middleware.d.ts.map +1 -0
  191. package/dist/test/fixtures/routes/_redirect301_mw.d.ts +3 -0
  192. package/dist/test/fixtures/routes/_redirect301_mw.d.ts.map +1 -0
  193. package/dist/test/fixtures/routes/_redirect_mw.d.ts +3 -0
  194. package/dist/test/fixtures/routes/_redirect_mw.d.ts.map +1 -0
  195. package/dist/test/fixtures/routes/about.page.d.ts +3 -0
  196. package/dist/test/fixtures/routes/about.page.d.ts.map +1 -0
  197. package/dist/test/fixtures/routes/action.page.d.ts +6 -0
  198. package/dist/test/fixtures/routes/action.page.d.ts.map +1 -0
  199. package/dist/test/fixtures/routes/api/form-all.post.d.ts +3 -0
  200. package/dist/test/fixtures/routes/api/form-all.post.d.ts.map +1 -0
  201. package/dist/test/fixtures/routes/api/form-limited.post.d.ts +6 -0
  202. package/dist/test/fixtures/routes/api/form-limited.post.d.ts.map +1 -0
  203. package/dist/test/fixtures/routes/api/response-obj.get.d.ts +3 -0
  204. package/dist/test/fixtures/routes/api/response-obj.get.d.ts.map +1 -0
  205. package/dist/test/fixtures/routes/api/upload.post.d.ts +12 -0
  206. package/dist/test/fixtures/routes/api/upload.post.d.ts.map +1 -0
  207. package/dist/test/fixtures/routes/api/users.get.d.ts +6 -0
  208. package/dist/test/fixtures/routes/api/users.get.d.ts.map +1 -0
  209. package/dist/test/fixtures/routes/api/xml.get.d.ts +3 -0
  210. package/dist/test/fixtures/routes/api/xml.get.d.ts.map +1 -0
  211. package/dist/test/fixtures/routes/auth/_middleware.d.ts +3 -0
  212. package/dist/test/fixtures/routes/auth/_middleware.d.ts.map +1 -0
  213. package/dist/test/fixtures/routes/auth/protected.page.d.ts +3 -0
  214. package/dist/test/fixtures/routes/auth/protected.page.d.ts.map +1 -0
  215. package/dist/test/fixtures/routes/index.page.d.ts +3 -0
  216. package/dist/test/fixtures/routes/index.page.d.ts.map +1 -0
  217. package/dist/test/fixtures/routes/oml.page.d.ts +3 -0
  218. package/dist/test/fixtures/routes/oml.page.d.ts.map +1 -0
  219. package/dist/test/fixtures/routes/redirect.page.d.ts +3 -0
  220. package/dist/test/fixtures/routes/redirect.page.d.ts.map +1 -0
  221. package/dist/test/fixtures/routes/ssg/[slug].page.d.ts +5 -0
  222. package/dist/test/fixtures/routes/ssg/[slug].page.d.ts.map +1 -0
  223. package/dist/test/fixtures/routes/ssg/server.page.d.ts +4 -0
  224. package/dist/test/fixtures/routes/ssg/server.page.d.ts.map +1 -0
  225. package/dist/test/fixtures/routes/state.page.d.ts +3 -0
  226. package/dist/test/fixtures/routes/state.page.d.ts.map +1 -0
  227. package/dist/test/fixtures/routes/throw.page.d.ts +3 -0
  228. package/dist/test/fixtures/routes/throw.page.d.ts.map +1 -0
  229. package/dist/test/fixtures/routes/wiki/[...slug].page.d.ts +3 -0
  230. package/dist/test/fixtures/routes/wiki/[...slug].page.d.ts.map +1 -0
  231. package/dist/test/helpers.d.ts +37 -0
  232. package/dist/test/helpers.d.ts.map +1 -0
  233. package/dist/test/layouts.test.d.ts +2 -0
  234. package/dist/test/layouts.test.d.ts.map +1 -0
  235. package/dist/test/middleware.test.d.ts +2 -0
  236. package/dist/test/middleware.test.d.ts.map +1 -0
  237. package/dist/test/multipart.test.d.ts +2 -0
  238. package/dist/test/multipart.test.d.ts.map +1 -0
  239. package/dist/test/oml-routing.test.d.ts +2 -0
  240. package/dist/test/oml-routing.test.d.ts.map +1 -0
  241. package/dist/test/oml.test.d.ts +2 -0
  242. package/dist/test/oml.test.d.ts.map +1 -0
  243. package/dist/test/redirects.test.d.ts +2 -0
  244. package/dist/test/redirects.test.d.ts.map +1 -0
  245. package/dist/test/routing.test.d.ts +2 -0
  246. package/dist/test/routing.test.d.ts.map +1 -0
  247. package/dist/test/ssg.test.d.ts +2 -0
  248. package/dist/test/ssg.test.d.ts.map +1 -0
  249. package/dist/test/web-response.test.d.ts +2 -0
  250. package/dist/test/web-response.test.d.ts.map +1 -0
  251. package/dist/types.d.ts +314 -0
  252. package/dist/types.d.ts.map +1 -0
  253. package/dist/types.js +292 -0
  254. package/dist/types.js.map +7 -0
  255. package/package.json +103 -0
  256. package/pka.config.json +32 -0
  257. package/src/build/config.ts +42 -0
  258. package/src/build/index.ts +6 -0
  259. package/src/build/plugins.ts +118 -0
  260. package/src/cli.ts +502 -0
  261. package/src/config.ts +197 -0
  262. package/src/create-multisite.ts +310 -0
  263. package/src/create.ts +194 -0
  264. package/src/dev/blueprints.ts +75 -0
  265. package/src/dev/components.ts +108 -0
  266. package/src/dev/insert.ts +221 -0
  267. package/src/dev/remove.ts +677 -0
  268. package/src/dev/watch.ts +3098 -0
  269. package/src/env.d.ts +5 -0
  270. package/src/errors.ts +64 -0
  271. package/src/generate.ts +228 -0
  272. package/src/index.ts +67 -0
  273. package/src/island.ts +47 -0
  274. package/src/jsx-runtime.d.ts +408 -0
  275. package/src/jsx-runtime.d.ts.map +1 -0
  276. package/src/jsx-runtime.ts +536 -0
  277. package/src/link.ts +49 -0
  278. package/src/oml/fragment.ts +54 -0
  279. package/src/oml/index.ts +21 -0
  280. package/src/oml/jsx-runtime.ts +121 -0
  281. package/src/oml/jsx.ts +151 -0
  282. package/src/oml/page.ts +13 -0
  283. package/src/oml/render.ts +181 -0
  284. package/src/oml/types.ts +159 -0
  285. package/src/router/handler.ts +515 -0
  286. package/src/router/matcher.ts +52 -0
  287. package/src/router/scanner.ts +272 -0
  288. package/src/server/index.ts +49 -0
  289. package/src/signal.ts +39 -0
  290. package/src/ssg.ts +253 -0
  291. package/src/test/actions.test.ts +40 -0
  292. package/src/test/body-limits.test.ts +83 -0
  293. package/src/test/errors.test.ts +53 -0
  294. package/src/test/fixtures/routes/[id].page.ts +3 -0
  295. package/src/test/fixtures/routes/_error.ts +6 -0
  296. package/src/test/fixtures/routes/_global.ts +8 -0
  297. package/src/test/fixtures/routes/_layout-template.ts +7 -0
  298. package/src/test/fixtures/routes/_layout.ts +7 -0
  299. package/src/test/fixtures/routes/_layout_scripts.ts +8 -0
  300. package/src/test/fixtures/routes/_middleware.ts +8 -0
  301. package/src/test/fixtures/routes/_redirect301_mw.ts +5 -0
  302. package/src/test/fixtures/routes/_redirect_mw.ts +5 -0
  303. package/src/test/fixtures/routes/about.page.ts +6 -0
  304. package/src/test/fixtures/routes/action.page.ts +11 -0
  305. package/src/test/fixtures/routes/api/form-all.post.ts +5 -0
  306. package/src/test/fixtures/routes/api/form-limited.post.ts +6 -0
  307. package/src/test/fixtures/routes/api/response-obj.get.ts +17 -0
  308. package/src/test/fixtures/routes/api/upload.post.ts +14 -0
  309. package/src/test/fixtures/routes/api/users.get.ts +3 -0
  310. package/src/test/fixtures/routes/api/xml.get.ts +5 -0
  311. package/src/test/fixtures/routes/auth/_middleware.ts +11 -0
  312. package/src/test/fixtures/routes/auth/protected.page.ts +3 -0
  313. package/src/test/fixtures/routes/index.page.ts +3 -0
  314. package/src/test/fixtures/routes/oml.page.ts +7 -0
  315. package/src/test/fixtures/routes/redirect.page.ts +3 -0
  316. package/src/test/fixtures/routes/ssg/[slug].page.ts +8 -0
  317. package/src/test/fixtures/routes/ssg/server.page.ts +5 -0
  318. package/src/test/fixtures/routes/state.page.ts +4 -0
  319. package/src/test/fixtures/routes/throw.page.ts +5 -0
  320. package/src/test/fixtures/routes/wiki/[...slug].page.ts +3 -0
  321. package/src/test/helpers.ts +132 -0
  322. package/src/test/layouts.test.ts +76 -0
  323. package/src/test/middleware.test.ts +69 -0
  324. package/src/test/multipart.test.ts +91 -0
  325. package/src/test/oml-routing.test.ts +59 -0
  326. package/src/test/oml.test.ts +429 -0
  327. package/src/test/redirects.test.ts +32 -0
  328. package/src/test/routing.test.ts +118 -0
  329. package/src/test/ssg.test.ts +273 -0
  330. package/src/test/web-response.test.ts +33 -0
  331. package/src/types.ts +670 -0
  332. package/tsconfig.client.json +17 -0
  333. package/tsconfig.json +20 -0
package/src/cli.ts ADDED
@@ -0,0 +1,502 @@
1
+ import { createReadStream, existsSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'
2
+ import { createServer, type IncomingMessage, type ServerResponse } from 'node:http'
3
+ import { createRequire } from 'node:module'
4
+ import { extname, join, resolve } from 'node:path'
5
+ import { collectCss, generateIslandsEntry, islandServerPlugin } from './build/plugins.js'
6
+ import { collectEsbuildPlugins, collectScannerSuffixes, pathsToAlias } from './config.js'
7
+ import { handleBuildError } from './errors.js'
8
+
9
+ const [, , command, ...args] = process.argv
10
+ const cwd = process.cwd()
11
+
12
+ switch (command) {
13
+ case 'create':
14
+ await runCreate()
15
+ break
16
+ case 'create-multisite':
17
+ await runCreateMultisite()
18
+ break
19
+ case 'generate':
20
+ await runGenerate()
21
+ break
22
+ case 'dev':
23
+ await runDev()
24
+ break
25
+ case 'build':
26
+ await runBuild()
27
+ break
28
+ case 'static':
29
+ await runStatic()
30
+ break
31
+ case 'start':
32
+ await runStart()
33
+ break
34
+ case 'preview':
35
+ await runPreview()
36
+ break
37
+ default:
38
+ console.error(`davaux: unknown command "${command ?? ''}"`)
39
+ console.error(
40
+ ' Usage: davaux <create|create-multisite|generate|dev|build|start|static|preview> [options]',
41
+ )
42
+ process.exit(1)
43
+ }
44
+
45
+ async function runCreate() {
46
+ const { scaffold } = await import('./create.js')
47
+ await scaffold(args[0], cwd)
48
+ }
49
+
50
+ async function runCreateMultisite() {
51
+ const { scaffoldMultisite } = await import('./create-multisite.js')
52
+ await scaffoldMultisite(args[0], cwd)
53
+ }
54
+
55
+ async function runGenerate() {
56
+ const { generate } = await import('./generate.js')
57
+ generate(args, cwd)
58
+ }
59
+
60
+ async function runDev() {
61
+ loadEnv(cwd)
62
+ const serverSrc = resolve(cwd, 'server.ts')
63
+ if (existsSync(serverSrc)) {
64
+ console.warn('[davaux] server.ts detected — custom server entry is not used in dev mode')
65
+ console.warn(
66
+ ' Use "node --watch --import tsx/esm server.ts" to develop with your custom server',
67
+ )
68
+ }
69
+ const { startDev } = await import('./dev/watch.js')
70
+ const { loadConfig } = await import('./build/config.js')
71
+ const config = await loadConfig(cwd)
72
+ const middlewareSrc = resolve(cwd, 'src', 'middleware.ts')
73
+ await startDev({
74
+ cwd,
75
+ port: Number(getFlag('--port', args) ?? config.server?.port ?? 3000),
76
+ hostname: getFlag('--hostname', args) ?? config.server?.hostname ?? 'localhost',
77
+ paths: config.paths,
78
+ plugins: config.plugins ?? [],
79
+ external: config.external,
80
+ middlewareSrc: existsSync(middlewareSrc) ? middlewareSrc : undefined,
81
+ editor: config.editor,
82
+ })
83
+ }
84
+
85
+ // ─── Build ────────────────────────────────────────────────────────────────────
86
+
87
+ async function runBuild() {
88
+ rmSync(resolve(cwd, 'dist'), { recursive: true, force: true })
89
+
90
+ const { build } = await import('esbuild')
91
+ const { loadConfig } = await import('./build/config.js')
92
+ const config = await loadConfig(cwd)
93
+ const davauxPlugins = config.plugins ?? []
94
+ const extraPlugins = collectEsbuildPlugins(davauxPlugins)
95
+ const extraSuffixes = collectScannerSuffixes(davauxPlugins)
96
+ const userAlias = pathsToAlias(config.paths ?? {})
97
+ const routesDir = resolve(cwd, 'src', 'routes')
98
+ const islandsDir = resolve(cwd, 'src', 'islands')
99
+
100
+ if (!existsSync(routesDir)) {
101
+ console.error(`[davaux] No routes directory found at ${routesDir}`)
102
+ process.exit(1)
103
+ }
104
+
105
+ const { scanRoutes } = await import('./router/scanner.js')
106
+ const { scanIslands } = await import('./router/scanner.js')
107
+ const { routes, layouts, middlewares, errorPage } = await scanRoutes(routesDir, extraSuffixes)
108
+ const islands = await scanIslands(islandsDir)
109
+
110
+ const outDir = resolve(cwd, 'dist')
111
+ const serverEntries = [
112
+ ...routes.map((r) => r.filePath),
113
+ ...layouts.map((l) => l.filePath),
114
+ ...middlewares.map((m) => m.filePath),
115
+ ...(errorPage ? [errorPage] : []),
116
+ ]
117
+
118
+ const serverExternal = ['node:*', 'davaux', ...(config.external ?? [])]
119
+
120
+ if (serverEntries.length === 0) {
121
+ console.log('[davaux] No routes found — nothing to build.')
122
+ } else {
123
+ await build({
124
+ entryPoints: serverEntries,
125
+ outdir: outDir,
126
+ outbase: routesDir,
127
+ format: 'esm',
128
+ platform: 'node',
129
+ target: 'node22',
130
+ bundle: true,
131
+ jsx: 'automatic',
132
+ jsxImportSource: 'davaux',
133
+ external: serverExternal,
134
+ alias: { ...userAlias, 'davaux/client': 'davaux/signal' },
135
+ sourcemap: true,
136
+ plugins: [islandServerPlugin(islandsDir), ...extraPlugins],
137
+ }).catch((err) => handleBuildError('Server build', err))
138
+ console.log(
139
+ `[davaux] Built ${routes.length} route(s) and ${layouts.length} layout(s) to ${outDir}`,
140
+ )
141
+ }
142
+
143
+ // Build src/middleware.ts separately if it exists (not under routesDir, so needs its own outfile)
144
+ const middlewareSrc = resolve(cwd, 'src', 'middleware.ts')
145
+ if (existsSync(middlewareSrc)) {
146
+ await build({
147
+ entryPoints: [middlewareSrc],
148
+ outfile: resolve(outDir, 'middleware.js'),
149
+ format: 'esm',
150
+ platform: 'node',
151
+ target: 'node22',
152
+ bundle: true,
153
+ jsx: 'automatic',
154
+ jsxImportSource: 'davaux',
155
+ external: serverExternal,
156
+ alias: { ...userAlias, 'davaux/client': 'davaux/signal' },
157
+ sourcemap: true,
158
+ }).catch((err) => handleBuildError('Middleware build', err))
159
+ }
160
+
161
+ const clientScripts: string[] = []
162
+
163
+ // Islands client bundle (auto-generated from scanned islands)
164
+ if (islands.length > 0) {
165
+ const islandsOutFile = resolve(cwd, 'dist', '_davaux', 'islands.js')
166
+ await build({
167
+ stdin: {
168
+ contents: generateIslandsEntry(islands),
169
+ loader: 'ts',
170
+ resolveDir: cwd,
171
+ },
172
+ outfile: islandsOutFile,
173
+ format: 'esm',
174
+ platform: 'browser',
175
+ target: 'es2022',
176
+ bundle: true,
177
+ jsx: 'automatic',
178
+ jsxImportSource: 'davaux/client',
179
+ alias: userAlias,
180
+ tsconfigRaw: JSON.stringify({
181
+ compilerOptions: { jsx: 'react-jsx', jsxImportSource: 'davaux/client' },
182
+ }),
183
+ sourcemap: true,
184
+ plugins: [...extraPlugins],
185
+ }).catch((err) => handleBuildError('Islands build', err))
186
+ clientScripts.push('/_davaux/islands.js')
187
+ console.log(`[davaux] Built islands bundle (${islands.length} island(s))`)
188
+ }
189
+
190
+ // Optional user-authored client bundle
191
+ const clientEntry = resolve(cwd, 'src', 'client.ts')
192
+ if (existsSync(clientEntry)) {
193
+ const clientOutFile = resolve(cwd, 'dist', '_davaux', 'client.js')
194
+ await build({
195
+ entryPoints: [clientEntry],
196
+ outfile: clientOutFile,
197
+ format: 'esm',
198
+ platform: 'browser',
199
+ target: 'es2022',
200
+ bundle: true,
201
+ jsx: 'automatic',
202
+ jsxImportSource: 'davaux/client',
203
+ alias: userAlias,
204
+ tsconfigRaw: JSON.stringify({
205
+ compilerOptions: { jsx: 'react-jsx', jsxImportSource: 'davaux/client' },
206
+ }),
207
+ sourcemap: true,
208
+ plugins: [...extraPlugins],
209
+ }).catch((err) => handleBuildError('Client build', err))
210
+ clientScripts.push('/_davaux/client.js')
211
+ console.log(`[davaux] Built client bundle to ${clientOutFile}`)
212
+ }
213
+
214
+ // Collect CSS side-effect outputs from all builds into a single stylesheet
215
+ const stylesOutFile = resolve(cwd, 'dist', '_davaux', 'styles.css')
216
+ const hasCss = await collectCss(outDir, stylesOutFile, true)
217
+ if (hasCss) console.log('[davaux] Collected CSS into dist/_davaux/styles.css')
218
+
219
+ // Copy partial-updates polyfills (self-detecting — no-op when native support lands)
220
+ try {
221
+ const r = createRequire(import.meta.url)
222
+ const templateForCjs = r.resolve('template-for-polyfill')
223
+ const templateForJs = readFileSync(
224
+ templateForCjs.replace(/template-for-polyfill\.cjs$/, 'template-for-polyfill.js'),
225
+ 'utf-8',
226
+ )
227
+ const htmlSettersMain = r.resolve('html-setters-polyfill')
228
+ const htmlSettersJs = readFileSync(
229
+ htmlSettersMain.replace(/index\.js$/, 'index.min.js'),
230
+ 'utf-8',
231
+ )
232
+ writeFileSync(
233
+ resolve(cwd, 'dist', '_davaux', 'partial-updates.js'),
234
+ `${templateForJs}\n${htmlSettersJs}`,
235
+ )
236
+ } catch {
237
+ console.warn('[davaux] Could not copy partial-updates polyfills — install template-for-polyfill and html-setters-polyfill if you use ctx.defer()')
238
+ }
239
+
240
+ // Compile server.ts if present — enables custom server entry (HTTPS, clustering, etc.)
241
+ const serverSrc = resolve(cwd, 'server.ts')
242
+ if (existsSync(serverSrc)) {
243
+ await build({
244
+ entryPoints: [serverSrc],
245
+ outfile: resolve(outDir, 'server.js'),
246
+ format: 'esm',
247
+ platform: 'node',
248
+ target: 'node22',
249
+ bundle: true,
250
+ external: [...serverExternal, 'davaux/*'],
251
+ sourcemap: true,
252
+ }).catch((err) => handleBuildError('Server entry build', err))
253
+ console.log('[davaux] Built server.ts → dist/server.js')
254
+ }
255
+ }
256
+
257
+ // ─── Static ───────────────────────────────────────────────────────────────────
258
+
259
+ async function runStatic() {
260
+ loadEnv(cwd)
261
+ await runBuild()
262
+
263
+ const { generateStatic } = await import('./ssg.js')
264
+ const { scanRoutes } = await import('./router/scanner.js')
265
+ const { loadConfig } = await import('./build/config.js')
266
+ const config = await loadConfig(cwd)
267
+
268
+ const distDir = resolve(cwd, 'dist')
269
+ const publicDir = resolve(cwd, 'public')
270
+ const outDir = resolve(cwd, getFlag('--out', args) ?? config.ssg?.outDir ?? 'out')
271
+
272
+ const islandsFile = resolve(distDir, '_davaux', 'islands.js')
273
+ const clientFile = resolve(distDir, '_davaux', 'client.js')
274
+ const stylesFile = resolve(distDir, '_davaux', 'styles.css')
275
+
276
+ const clientScripts: string[] = []
277
+ if (existsSync(islandsFile)) clientScripts.push('/_davaux/islands.js')
278
+ if (existsSync(clientFile)) clientScripts.push('/_davaux/client.js')
279
+
280
+ const clientStylesheets: string[] = []
281
+ if (existsSync(stylesFile)) clientStylesheets.push('/_davaux/styles.css')
282
+
283
+ const scan = await scanRoutes(distDir)
284
+
285
+ const middlewareDist = resolve(distDir, 'middleware.js')
286
+ await generateStatic({
287
+ outDir,
288
+ distDir,
289
+ publicDir,
290
+ scan,
291
+ clientScripts,
292
+ clientStylesheets,
293
+ appMiddlewarePath: existsSync(middlewareDist) ? middlewareDist : undefined,
294
+ trailingSlash: config.ssg?.trailingSlash,
295
+ basePath: config.ssg?.basePath,
296
+ notFound: config.ssg?.notFound,
297
+ sitemap: config.ssg?.sitemap,
298
+ })
299
+ }
300
+
301
+ // ─── Preview ─────────────────────────────────────────────────────────────────
302
+
303
+ async function runPreview() {
304
+ const { loadConfig } = await import('./build/config.js')
305
+ const config = await loadConfig(cwd)
306
+ const outDir = resolve(cwd, getFlag('--dir', args) ?? config.ssg?.outDir ?? 'out')
307
+ const port = Number(getFlag('--port', args) ?? config.server?.port ?? 4173)
308
+ const hostname = getFlag('--hostname', args) ?? 'localhost'
309
+
310
+ if (!existsSync(outDir)) {
311
+ console.error(`[davaux] Preview directory not found: ${outDir}`)
312
+ console.error(' Run "davaux static" first to generate the output.')
313
+ process.exit(1)
314
+ }
315
+
316
+ function serveFile(
317
+ res: import('node:http').ServerResponse,
318
+ filePath: string,
319
+ status = 200,
320
+ ): boolean {
321
+ if (existsSync(filePath) && statSync(filePath).isFile()) {
322
+ res.writeHead(status, {
323
+ 'Content-Type': MIME[extname(filePath)] ?? 'application/octet-stream',
324
+ })
325
+ createReadStream(filePath).pipe(res)
326
+ return true
327
+ }
328
+ return false
329
+ }
330
+
331
+ const server = createServer((req, res) => {
332
+ const pathname = new URL(req.url ?? '/', 'http://x').pathname
333
+ const safePath = join(outDir, pathname)
334
+
335
+ if (!safePath.startsWith(`${outDir}/`) && safePath !== outDir) {
336
+ res.writeHead(403, { 'Content-Type': 'text/plain' })
337
+ res.end('Forbidden')
338
+ return
339
+ }
340
+
341
+ if (serveFile(res, safePath)) return
342
+ if (serveFile(res, join(safePath, 'index.html'))) return
343
+
344
+ const notFoundPage = join(outDir, '404.html')
345
+ if (serveFile(res, notFoundPage, 404)) return
346
+
347
+ res.writeHead(404, { 'Content-Type': 'text/plain' })
348
+ res.end('Not found')
349
+ })
350
+
351
+ server.listen(port, hostname, () => {
352
+ console.log(`\n davaux preview http://${hostname}:${port}`)
353
+ console.log(` Serving: ${outDir}\n`)
354
+ })
355
+ }
356
+
357
+ // ─── Start ────────────────────────────────────────────────────────────────────
358
+
359
+ const MIME: Record<string, string> = {
360
+ '.js': 'application/javascript',
361
+ '.mjs': 'application/javascript',
362
+ '.css': 'text/css',
363
+ '.html': 'text/html',
364
+ '.json': 'application/json',
365
+ '.png': 'image/png',
366
+ '.jpg': 'image/jpeg',
367
+ '.jpeg': 'image/jpeg',
368
+ '.gif': 'image/gif',
369
+ '.svg': 'image/svg+xml',
370
+ '.ico': 'image/x-icon',
371
+ '.woff': 'font/woff',
372
+ '.woff2': 'font/woff2',
373
+ }
374
+
375
+ async function runStart() {
376
+ loadEnv(cwd)
377
+ const distDir = resolve(cwd, 'dist')
378
+ const publicDir = resolve(cwd, 'public')
379
+ const islandsFile = resolve(cwd, 'dist', '_davaux', 'islands.js')
380
+ const clientFile = resolve(cwd, 'dist', '_davaux', 'client.js')
381
+ const polyfillFile = resolve(cwd, 'dist', '_davaux', 'partial-updates.js')
382
+
383
+ const { loadConfig } = await import('./build/config.js')
384
+ const { scanRoutes } = await import('./router/scanner.js')
385
+ const { buildApp } = await import('./router/handler.js')
386
+ const { startServer } = await import('./server/index.js')
387
+
388
+ const [config, scanResult] = await Promise.all([loadConfig(cwd), scanRoutes(distDir)])
389
+
390
+ const stylesFile = resolve(cwd, 'dist', '_davaux', 'styles.css')
391
+ const middlewareDist = resolve(distDir, 'middleware.js')
392
+ const clientScripts: string[] = []
393
+ if (existsSync(islandsFile)) clientScripts.push('/_davaux/islands.js')
394
+ if (existsSync(clientFile)) clientScripts.push('/_davaux/client.js')
395
+ const clientStylesheets: string[] = []
396
+ if (existsSync(stylesFile)) clientStylesheets.push('/_davaux/styles.css')
397
+
398
+ const app = buildApp(
399
+ scanResult,
400
+ false,
401
+ clientScripts,
402
+ clientStylesheets,
403
+ existsSync(middlewareDist) ? middlewareDist : undefined,
404
+ '',
405
+ false,
406
+ config.oml?.cache,
407
+ )
408
+
409
+ const serveStatics = (req: IncomingMessage, res: ServerResponse): Promise<boolean> => {
410
+ const path = req.url?.split('?')[0] ?? '/'
411
+
412
+ if (path === '/_davaux/styles.css' && existsSync(stylesFile)) {
413
+ res.writeHead(200, { 'Content-Type': 'text/css' })
414
+ createReadStream(stylesFile).pipe(res)
415
+ return Promise.resolve(true)
416
+ }
417
+
418
+ if (path === '/_davaux/islands.js' && existsSync(islandsFile)) {
419
+ res.writeHead(200, { 'Content-Type': 'application/javascript' })
420
+ createReadStream(islandsFile).pipe(res)
421
+ return Promise.resolve(true)
422
+ }
423
+
424
+ if (path === '/_davaux/client.js' && existsSync(clientFile)) {
425
+ res.writeHead(200, { 'Content-Type': 'application/javascript' })
426
+ createReadStream(clientFile).pipe(res)
427
+ return Promise.resolve(true)
428
+ }
429
+
430
+ if (path === '/_davaux/partial-updates.js' && existsSync(polyfillFile)) {
431
+ res.writeHead(200, { 'Content-Type': 'application/javascript' })
432
+ createReadStream(polyfillFile).pipe(res)
433
+ return Promise.resolve(true)
434
+ }
435
+
436
+ if (existsSync(publicDir)) {
437
+ const filePath = join(publicDir, new URL(path, 'http://x').pathname)
438
+ if (
439
+ (filePath.startsWith(`${publicDir}/`) || filePath === publicDir) &&
440
+ existsSync(filePath) &&
441
+ statSync(filePath).isFile()
442
+ ) {
443
+ res.writeHead(200, {
444
+ 'Content-Type': MIME[extname(filePath)] ?? 'application/octet-stream',
445
+ })
446
+ createReadStream(filePath).pipe(res)
447
+ return Promise.resolve(true)
448
+ }
449
+ }
450
+
451
+ return Promise.resolve(false)
452
+ }
453
+
454
+ const serverDist = resolve(distDir, 'server.js')
455
+ if (existsSync(serverDist)) {
456
+ const serverMod = await import(serverDist)
457
+ if (typeof serverMod.default !== 'function') {
458
+ console.error(
459
+ '[davaux] dist/server.js must export a default function: (app: CompiledApp, serveStatics) => void',
460
+ )
461
+ process.exit(1)
462
+ }
463
+ await serverMod.default(app, serveStatics)
464
+ } else {
465
+ startServer(app, {
466
+ port: Number(getFlag('--port', args) ?? config.server?.port ?? 3000),
467
+ hostname: getFlag('--hostname', args) ?? config.server?.hostname ?? 'localhost',
468
+ onRequest: serveStatics,
469
+ })
470
+ }
471
+ }
472
+
473
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
474
+
475
+ function getFlag(name: string, argv: string[]): string | undefined {
476
+ const idx = argv.indexOf(name)
477
+ return idx !== -1 ? argv[idx + 1] : undefined
478
+ }
479
+
480
+ function loadEnv(cwd: string): void {
481
+ for (const name of ['.env', '.env.local']) {
482
+ const filePath = resolve(cwd, name)
483
+ if (!existsSync(filePath)) continue
484
+ const content = readFileSync(filePath, 'utf-8')
485
+ for (const line of content.split('\n')) {
486
+ const trimmed = line.trim()
487
+ if (!trimmed || trimmed.startsWith('#')) continue
488
+ const eq = trimmed.indexOf('=')
489
+ if (eq === -1) continue
490
+ const key = trimmed.slice(0, eq).trim()
491
+ if (!key) continue
492
+ let value = trimmed.slice(eq + 1).trim()
493
+ if (
494
+ (value.startsWith('"') && value.endsWith('"')) ||
495
+ (value.startsWith("'") && value.endsWith("'"))
496
+ ) {
497
+ value = value.slice(1, -1)
498
+ }
499
+ if (!(key in process.env)) process.env[key] = value
500
+ }
501
+ }
502
+ }
package/src/config.ts ADDED
@@ -0,0 +1,197 @@
1
+ import type { Plugin } from 'esbuild'
2
+ import type { RouteType } from './types.js'
3
+
4
+ // ─── Plugin API ───────────────────────────────────────────────────────────────
5
+
6
+ /**
7
+ * A Davaux plugin — extends the framework with new route file types and/or
8
+ * esbuild transforms. Implement this interface and return it from a factory
9
+ * function, then pass it to `plugins` in `davaux.config.ts`.
10
+ *
11
+ * @example
12
+ * // packages/my-plugin/src/index.ts
13
+ * import type { DavauxPlugin } from 'davaux/config'
14
+ * export function myPlugin(): DavauxPlugin {
15
+ * return {
16
+ * name: 'my-plugin',
17
+ * esbuild: [myEsbuildPlugin()],
18
+ * scanner: { suffixes: [['.page.myext', 'page']] },
19
+ * }
20
+ * }
21
+ */
22
+ export interface DavauxPlugin {
23
+ name: string
24
+ /** esbuild plugins contributed to every build context (server routes, islands, client) */
25
+ esbuild?: Plugin[]
26
+ /** Additional route file suffixes the scanner should recognize */
27
+ scanner?: {
28
+ suffixes: [suffix: string, type: RouteType][]
29
+ }
30
+ }
31
+
32
+ /** Collect esbuild plugins from an array of DavauxPlugins. */
33
+ export function collectEsbuildPlugins(plugins: DavauxPlugin[] = []): Plugin[] {
34
+ return plugins.flatMap((p) => p.esbuild ?? [])
35
+ }
36
+
37
+ /** Collect scanner suffix entries from an array of DavauxPlugins. */
38
+ export function collectScannerSuffixes(plugins: DavauxPlugin[] = []): [string, RouteType][] {
39
+ return plugins.flatMap((p) => p.scanner?.suffixes ?? [])
40
+ }
41
+
42
+ // ─── User-facing config type ──────────────────────────────────────────────────
43
+
44
+ export interface EditorConfig {
45
+ /**
46
+ * Opt-in to the visual editor and inspector overlay.
47
+ * Must be explicitly set to `true` — disabled by default so the editor
48
+ * endpoints and inspector script are never exposed unless intended.
49
+ */
50
+ enabled?: boolean
51
+ /** Floating inspector/editor badge options. */
52
+ badge?: {
53
+ /** Corner of the viewport where the badge appears. Default: `'bottom-right'` */
54
+ position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
55
+ /** Custom label text shown on the badge. Default: `'Inspector'` */
56
+ label?: string
57
+ }
58
+ /**
59
+ * Force a specific colour scheme for the editor UI instead of auto-detecting
60
+ * from the inspected page's theme attribute.
61
+ */
62
+ theme?: 'auto' | 'dark' | 'light'
63
+ /**
64
+ * Raw CSS string injected into the editor frame's `<style>` tag.
65
+ * Use this to override colours, hide elements, or adjust layout.
66
+ */
67
+ css?: string
68
+ /**
69
+ * Path to a CSS file (relative to the project root) whose contents are
70
+ * injected into the editor frame. Merged with `css` if both are provided.
71
+ * The file is re-read on each request in dev mode, so changes apply without a restart.
72
+ */
73
+ cssFile?: string
74
+ }
75
+
76
+ export interface DavauxConfig {
77
+ /** Server defaults for `davaux dev`, `davaux start`, and `davaux preview`. CLI flags (--port, --hostname) take priority over these. */
78
+ server?: {
79
+ port?: number
80
+ hostname?: string
81
+ }
82
+ /**
83
+ * Import path aliases, using the same glob syntax as `tsconfig.json` `paths`.
84
+ * Each entry is forwarded to esbuild `alias` in every build context so TypeScript
85
+ * and the bundler stay in sync without any extra plugin.
86
+ *
87
+ * @example
88
+ * import { defineConfig } from 'davaux/config'
89
+ * export default defineConfig({
90
+ * paths: { '@/*': './src/*' },
91
+ * })
92
+ */
93
+ paths?: Record<string, string>
94
+ /**
95
+ * Davaux plugins — each plugin can contribute esbuild transforms and/or
96
+ * register new route file extensions with the scanner.
97
+ *
98
+ * @example
99
+ * import { defineConfig } from 'davaux/config'
100
+ * import { markdown } from '@davaux/markdown'
101
+ * export default defineConfig({
102
+ * plugins: [markdown()],
103
+ * })
104
+ */
105
+ plugins?: DavauxPlugin[]
106
+ /**
107
+ * Additional packages to exclude from esbuild bundling. Useful for packages
108
+ * that use native bindings or CJS `require()` that cannot be bundled into ESM.
109
+ * Each entry is added to the esbuild `external` list alongside `node:*` and `davaux`.
110
+ *
111
+ * @example
112
+ * import { defineConfig } from 'davaux/config'
113
+ * export default defineConfig({
114
+ * external: ['@libsql/client', 'libsql'],
115
+ * })
116
+ */
117
+ external?: string[]
118
+ /** Visual editor and inspector overlay configuration. Disabled by default — set `enabled: true` to opt in. */
119
+ editor?: EditorConfig
120
+ /**
121
+ * OML (Object Markup Language) options. OML is the intermediate representation
122
+ * used by the visual editor and production page caching.
123
+ */
124
+ oml?: {
125
+ /**
126
+ * Control which routes use OML tree construction and result caching in production.
127
+ * By default no routes are cached — this must be explicitly configured.
128
+ *
129
+ * Use `include` for apps that are mostly dynamic with a few cacheable routes,
130
+ * or `exclude` for apps that are mostly static with a few dynamic routes.
131
+ * Routes not covered by the cache config use the string renderer directly in
132
+ * production — no OML tree is built and no result is stored.
133
+ *
134
+ * @example
135
+ * // Whitelist — only these routes are cached
136
+ * oml: { cache: { include: ['/', '/blog/:slug', '/docs/*'] } }
137
+ *
138
+ * // Blacklist — all routes cached except these
139
+ * oml: { cache: { exclude: ['/dashboard', '/account/*'] } }
140
+ */
141
+ cache?: OmlCacheConfig
142
+ }
143
+ /** Options that only apply to `davaux static` (static site generation). */
144
+ ssg?: {
145
+ /**
146
+ * Override the default `out/` output directory.
147
+ * The `--out` CLI flag takes priority over this value.
148
+ */
149
+ outDir?: string
150
+ /**
151
+ * Trailing slash behaviour for generated output files.
152
+ * - `'always'` (default) — `/about` → `out/about/index.html` (served as `/about/`)
153
+ * - `'never'` — `/about` → `out/about.html` (served as `/about`)
154
+ */
155
+ trailingSlash?: 'always' | 'never'
156
+ /**
157
+ * Path prefix for subdirectory deployments (e.g. `'/docs'` for GitHub Pages
158
+ * at `user.github.io/my-project/`). Prepended to all injected `/_davaux/*`
159
+ * script and stylesheet URLs in the generated HTML.
160
+ */
161
+ basePath?: string
162
+ /**
163
+ * Generate a `404.html` at the root of `outDir` for static hosts
164
+ * (Netlify, GitHub Pages) that serve it on unmatched paths.
165
+ * Defaults to `true` when an `_error.tsx` exists in the routes directory.
166
+ */
167
+ notFound?: boolean
168
+ /**
169
+ * Auto-generate a `sitemap.xml` from all rendered routes.
170
+ * Pass `{ baseUrl: 'https://example.com' }` to enable.
171
+ * Pass `false` to explicitly disable.
172
+ */
173
+ sitemap?: false | { baseUrl: string }
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Convert tsconfig-style `paths` glob entries to esbuild `alias` entries.
179
+ * `'@/*': './src/*'` → `'@': './src'`
180
+ */
181
+ export function pathsToAlias(paths: Record<string, string>): Record<string, string> {
182
+ return Object.fromEntries(
183
+ Object.entries(paths).map(([k, v]) => [k.replace(/\/\*$/, ''), v.replace(/\/\*$/, '')]),
184
+ )
185
+ }
186
+
187
+ /**
188
+ * Controls which routes use OML tree construction and caching in production.
189
+ * Use `include` to whitelist specific URL patterns, or `exclude` to blacklist them.
190
+ * Patterns support `:param` (single dynamic segment) and `*` (any characters).
191
+ */
192
+ export type OmlCacheConfig = { include: string[] } | { exclude: string[] }
193
+
194
+ /** Wraps a config object for TypeScript inference — returns it unchanged. */
195
+ export function defineConfig(config: DavauxConfig): DavauxConfig {
196
+ return config
197
+ }