@jasonshimmy/vite-plugin-cer-app 0.1.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 (299) hide show
  1. package/.github/copilot-instructions.md +130 -0
  2. package/.github/workflows/publish.yml +206 -0
  3. package/.nvmrc +1 -0
  4. package/CHANGELOG.md +10 -0
  5. package/IMPLEMENTATION_PLAN.md +391 -0
  6. package/README.md +231 -0
  7. package/VITE_PLUGIN_FRAMEWORK_PLAN.md +594 -0
  8. package/commits.txt +3 -0
  9. package/dist/__tests__/plugin/path-utils.test.d.ts +2 -0
  10. package/dist/__tests__/plugin/path-utils.test.d.ts.map +1 -0
  11. package/dist/__tests__/plugin/path-utils.test.js +305 -0
  12. package/dist/__tests__/plugin/path-utils.test.js.map +1 -0
  13. package/dist/__tests__/plugin/scanner.test.d.ts +2 -0
  14. package/dist/__tests__/plugin/scanner.test.d.ts.map +1 -0
  15. package/dist/__tests__/plugin/scanner.test.js +143 -0
  16. package/dist/__tests__/plugin/scanner.test.js.map +1 -0
  17. package/dist/__tests__/plugin/transforms/auto-import.test.d.ts +2 -0
  18. package/dist/__tests__/plugin/transforms/auto-import.test.d.ts.map +1 -0
  19. package/dist/__tests__/plugin/transforms/auto-import.test.js +151 -0
  20. package/dist/__tests__/plugin/transforms/auto-import.test.js.map +1 -0
  21. package/dist/__tests__/plugin/transforms/head-inject.test.d.ts +2 -0
  22. package/dist/__tests__/plugin/transforms/head-inject.test.d.ts.map +1 -0
  23. package/dist/__tests__/plugin/transforms/head-inject.test.js +151 -0
  24. package/dist/__tests__/plugin/transforms/head-inject.test.js.map +1 -0
  25. package/dist/__tests__/plugin/virtual/components.test.d.ts +2 -0
  26. package/dist/__tests__/plugin/virtual/components.test.d.ts.map +1 -0
  27. package/dist/__tests__/plugin/virtual/components.test.js +47 -0
  28. package/dist/__tests__/plugin/virtual/components.test.js.map +1 -0
  29. package/dist/__tests__/plugin/virtual/composables.test.d.ts +2 -0
  30. package/dist/__tests__/plugin/virtual/composables.test.d.ts.map +1 -0
  31. package/dist/__tests__/plugin/virtual/composables.test.js +48 -0
  32. package/dist/__tests__/plugin/virtual/composables.test.js.map +1 -0
  33. package/dist/__tests__/plugin/virtual/layouts.test.d.ts +2 -0
  34. package/dist/__tests__/plugin/virtual/layouts.test.d.ts.map +1 -0
  35. package/dist/__tests__/plugin/virtual/layouts.test.js +59 -0
  36. package/dist/__tests__/plugin/virtual/layouts.test.js.map +1 -0
  37. package/dist/__tests__/plugin/virtual/middleware.test.d.ts +2 -0
  38. package/dist/__tests__/plugin/virtual/middleware.test.d.ts.map +1 -0
  39. package/dist/__tests__/plugin/virtual/middleware.test.js +58 -0
  40. package/dist/__tests__/plugin/virtual/middleware.test.js.map +1 -0
  41. package/dist/__tests__/plugin/virtual/plugins.test.d.ts +2 -0
  42. package/dist/__tests__/plugin/virtual/plugins.test.d.ts.map +1 -0
  43. package/dist/__tests__/plugin/virtual/plugins.test.js +73 -0
  44. package/dist/__tests__/plugin/virtual/plugins.test.js.map +1 -0
  45. package/dist/__tests__/plugin/virtual/routes.test.d.ts +2 -0
  46. package/dist/__tests__/plugin/virtual/routes.test.d.ts.map +1 -0
  47. package/dist/__tests__/plugin/virtual/routes.test.js +167 -0
  48. package/dist/__tests__/plugin/virtual/routes.test.js.map +1 -0
  49. package/dist/__tests__/plugin/virtual/server-api.test.d.ts +2 -0
  50. package/dist/__tests__/plugin/virtual/server-api.test.d.ts.map +1 -0
  51. package/dist/__tests__/plugin/virtual/server-api.test.js +72 -0
  52. package/dist/__tests__/plugin/virtual/server-api.test.js.map +1 -0
  53. package/dist/__tests__/runtime/use-head.test.d.ts +2 -0
  54. package/dist/__tests__/runtime/use-head.test.d.ts.map +1 -0
  55. package/dist/__tests__/runtime/use-head.test.js +202 -0
  56. package/dist/__tests__/runtime/use-head.test.js.map +1 -0
  57. package/dist/__tests__/runtime/use-page-data.test.d.ts +2 -0
  58. package/dist/__tests__/runtime/use-page-data.test.d.ts.map +1 -0
  59. package/dist/__tests__/runtime/use-page-data.test.js +41 -0
  60. package/dist/__tests__/runtime/use-page-data.test.js.map +1 -0
  61. package/dist/cli/commands/build.d.ts +3 -0
  62. package/dist/cli/commands/build.d.ts.map +1 -0
  63. package/dist/cli/commands/build.js +103 -0
  64. package/dist/cli/commands/build.js.map +1 -0
  65. package/dist/cli/commands/dev.d.ts +3 -0
  66. package/dist/cli/commands/dev.d.ts.map +1 -0
  67. package/dist/cli/commands/dev.js +92 -0
  68. package/dist/cli/commands/dev.js.map +1 -0
  69. package/dist/cli/commands/generate.d.ts +7 -0
  70. package/dist/cli/commands/generate.d.ts.map +1 -0
  71. package/dist/cli/commands/generate.js +72 -0
  72. package/dist/cli/commands/generate.js.map +1 -0
  73. package/dist/cli/commands/preview.d.ts +3 -0
  74. package/dist/cli/commands/preview.d.ts.map +1 -0
  75. package/dist/cli/commands/preview.js +191 -0
  76. package/dist/cli/commands/preview.js.map +1 -0
  77. package/dist/cli/create/index.d.ts +3 -0
  78. package/dist/cli/create/index.d.ts.map +1 -0
  79. package/dist/cli/create/index.js +184 -0
  80. package/dist/cli/create/index.js.map +1 -0
  81. package/dist/cli/index.d.ts +3 -0
  82. package/dist/cli/index.d.ts.map +1 -0
  83. package/dist/cli/index.js +17 -0
  84. package/dist/cli/index.js.map +1 -0
  85. package/dist/index.d.ts +9 -0
  86. package/dist/index.d.ts.map +1 -0
  87. package/dist/index.js +4 -0
  88. package/dist/index.js.map +1 -0
  89. package/dist/plugin/build-ssg.d.ts +12 -0
  90. package/dist/plugin/build-ssg.d.ts.map +1 -0
  91. package/dist/plugin/build-ssg.js +212 -0
  92. package/dist/plugin/build-ssg.js.map +1 -0
  93. package/dist/plugin/build-ssr.d.ts +10 -0
  94. package/dist/plugin/build-ssr.d.ts.map +1 -0
  95. package/dist/plugin/build-ssr.js +139 -0
  96. package/dist/plugin/build-ssr.js.map +1 -0
  97. package/dist/plugin/dev-server.d.ts +46 -0
  98. package/dist/plugin/dev-server.d.ts.map +1 -0
  99. package/dist/plugin/dev-server.js +194 -0
  100. package/dist/plugin/dev-server.js.map +1 -0
  101. package/dist/plugin/dts-generator.d.ts +27 -0
  102. package/dist/plugin/dts-generator.d.ts.map +1 -0
  103. package/dist/plugin/dts-generator.js +180 -0
  104. package/dist/plugin/dts-generator.js.map +1 -0
  105. package/dist/plugin/index.d.ts +13 -0
  106. package/dist/plugin/index.d.ts.map +1 -0
  107. package/dist/plugin/index.js +298 -0
  108. package/dist/plugin/index.js.map +1 -0
  109. package/dist/plugin/path-utils.d.ts +57 -0
  110. package/dist/plugin/path-utils.d.ts.map +1 -0
  111. package/dist/plugin/path-utils.js +160 -0
  112. package/dist/plugin/path-utils.js.map +1 -0
  113. package/dist/plugin/scanner.d.ts +17 -0
  114. package/dist/plugin/scanner.d.ts.map +1 -0
  115. package/dist/plugin/scanner.js +54 -0
  116. package/dist/plugin/scanner.js.map +1 -0
  117. package/dist/plugin/transforms/auto-import.d.ts +14 -0
  118. package/dist/plugin/transforms/auto-import.d.ts.map +1 -0
  119. package/dist/plugin/transforms/auto-import.js +154 -0
  120. package/dist/plugin/transforms/auto-import.js.map +1 -0
  121. package/dist/plugin/transforms/head-inject.d.ts +29 -0
  122. package/dist/plugin/transforms/head-inject.d.ts.map +1 -0
  123. package/dist/plugin/transforms/head-inject.js +127 -0
  124. package/dist/plugin/transforms/head-inject.js.map +1 -0
  125. package/dist/plugin/virtual/components.d.ts +6 -0
  126. package/dist/plugin/virtual/components.d.ts.map +1 -0
  127. package/dist/plugin/virtual/components.js +22 -0
  128. package/dist/plugin/virtual/components.js.map +1 -0
  129. package/dist/plugin/virtual/composables.d.ts +6 -0
  130. package/dist/plugin/virtual/composables.d.ts.map +1 -0
  131. package/dist/plugin/virtual/composables.js +22 -0
  132. package/dist/plugin/virtual/composables.js.map +1 -0
  133. package/dist/plugin/virtual/error.d.ts +13 -0
  134. package/dist/plugin/virtual/error.d.ts.map +1 -0
  135. package/dist/plugin/virtual/error.js +32 -0
  136. package/dist/plugin/virtual/error.js.map +1 -0
  137. package/dist/plugin/virtual/layouts.d.ts +6 -0
  138. package/dist/plugin/virtual/layouts.d.ts.map +1 -0
  139. package/dist/plugin/virtual/layouts.js +33 -0
  140. package/dist/plugin/virtual/layouts.js.map +1 -0
  141. package/dist/plugin/virtual/loading.d.ts +11 -0
  142. package/dist/plugin/virtual/loading.d.ts.map +1 -0
  143. package/dist/plugin/virtual/loading.js +30 -0
  144. package/dist/plugin/virtual/loading.js.map +1 -0
  145. package/dist/plugin/virtual/middleware.d.ts +6 -0
  146. package/dist/plugin/virtual/middleware.d.ts.map +1 -0
  147. package/dist/plugin/virtual/middleware.js +36 -0
  148. package/dist/plugin/virtual/middleware.js.map +1 -0
  149. package/dist/plugin/virtual/plugins.d.ts +6 -0
  150. package/dist/plugin/virtual/plugins.d.ts.map +1 -0
  151. package/dist/plugin/virtual/plugins.js +30 -0
  152. package/dist/plugin/virtual/plugins.js.map +1 -0
  153. package/dist/plugin/virtual/routes.d.ts +16 -0
  154. package/dist/plugin/virtual/routes.d.ts.map +1 -0
  155. package/dist/plugin/virtual/routes.js +131 -0
  156. package/dist/plugin/virtual/routes.js.map +1 -0
  157. package/dist/plugin/virtual/server-api.d.ts +6 -0
  158. package/dist/plugin/virtual/server-api.d.ts.map +1 -0
  159. package/dist/plugin/virtual/server-api.js +41 -0
  160. package/dist/plugin/virtual/server-api.js.map +1 -0
  161. package/dist/plugin/virtual/server-middleware.d.ts +6 -0
  162. package/dist/plugin/virtual/server-middleware.d.ts.map +1 -0
  163. package/dist/plugin/virtual/server-middleware.js +36 -0
  164. package/dist/plugin/virtual/server-middleware.js.map +1 -0
  165. package/dist/runtime/app-template.d.ts +10 -0
  166. package/dist/runtime/app-template.d.ts.map +1 -0
  167. package/dist/runtime/app-template.js +143 -0
  168. package/dist/runtime/app-template.js.map +1 -0
  169. package/dist/runtime/composables/index.d.ts +4 -0
  170. package/dist/runtime/composables/index.d.ts.map +1 -0
  171. package/dist/runtime/composables/index.js +3 -0
  172. package/dist/runtime/composables/index.js.map +1 -0
  173. package/dist/runtime/composables/use-head.d.ts +30 -0
  174. package/dist/runtime/composables/use-head.d.ts.map +1 -0
  175. package/dist/runtime/composables/use-head.js +182 -0
  176. package/dist/runtime/composables/use-head.js.map +1 -0
  177. package/dist/runtime/composables/use-page-data.d.ts +32 -0
  178. package/dist/runtime/composables/use-page-data.d.ts.map +1 -0
  179. package/dist/runtime/composables/use-page-data.js +41 -0
  180. package/dist/runtime/composables/use-page-data.js.map +1 -0
  181. package/dist/runtime/entry-client-template.d.ts +8 -0
  182. package/dist/runtime/entry-client-template.d.ts.map +1 -0
  183. package/dist/runtime/entry-client-template.js +18 -0
  184. package/dist/runtime/entry-client-template.js.map +1 -0
  185. package/dist/runtime/entry-server-template.d.ts +9 -0
  186. package/dist/runtime/entry-server-template.d.ts.map +1 -0
  187. package/dist/runtime/entry-server-template.js +72 -0
  188. package/dist/runtime/entry-server-template.js.map +1 -0
  189. package/dist/types/api.d.ts +16 -0
  190. package/dist/types/api.d.ts.map +1 -0
  191. package/dist/types/api.js +2 -0
  192. package/dist/types/api.js.map +1 -0
  193. package/dist/types/config.d.ts +32 -0
  194. package/dist/types/config.d.ts.map +1 -0
  195. package/dist/types/config.js +4 -0
  196. package/dist/types/config.js.map +1 -0
  197. package/dist/types/index.d.ts +7 -0
  198. package/dist/types/index.d.ts.map +1 -0
  199. package/dist/types/index.js +2 -0
  200. package/dist/types/index.js.map +1 -0
  201. package/dist/types/middleware.d.ts +6 -0
  202. package/dist/types/middleware.d.ts.map +1 -0
  203. package/dist/types/middleware.js +2 -0
  204. package/dist/types/middleware.js.map +1 -0
  205. package/dist/types/page.d.ts +21 -0
  206. package/dist/types/page.d.ts.map +1 -0
  207. package/dist/types/page.js +2 -0
  208. package/dist/types/page.js.map +1 -0
  209. package/dist/types/plugin.d.ts +12 -0
  210. package/dist/types/plugin.d.ts.map +1 -0
  211. package/dist/types/plugin.js +2 -0
  212. package/dist/types/plugin.js.map +1 -0
  213. package/docs/cli.md +233 -0
  214. package/docs/components.md +114 -0
  215. package/docs/composables.md +99 -0
  216. package/docs/configuration.md +270 -0
  217. package/docs/data-loading.md +165 -0
  218. package/docs/getting-started.md +235 -0
  219. package/docs/head-management.md +206 -0
  220. package/docs/layouts.md +112 -0
  221. package/docs/middleware.md +140 -0
  222. package/docs/plugins.md +138 -0
  223. package/docs/rendering-modes.md +251 -0
  224. package/docs/routing.md +180 -0
  225. package/docs/server-api.md +172 -0
  226. package/docs/testing.md +462 -0
  227. package/package.json +75 -0
  228. package/src/__tests__/plugin/path-utils.test.ts +399 -0
  229. package/src/__tests__/plugin/scanner.test.ts +172 -0
  230. package/src/__tests__/plugin/transforms/auto-import.test.ts +229 -0
  231. package/src/__tests__/plugin/transforms/head-inject.test.ts +178 -0
  232. package/src/__tests__/plugin/virtual/components.test.ts +56 -0
  233. package/src/__tests__/plugin/virtual/composables.test.ts +57 -0
  234. package/src/__tests__/plugin/virtual/layouts.test.ts +70 -0
  235. package/src/__tests__/plugin/virtual/middleware.test.ts +68 -0
  236. package/src/__tests__/plugin/virtual/plugins.test.ts +84 -0
  237. package/src/__tests__/plugin/virtual/routes.test.ts +202 -0
  238. package/src/__tests__/plugin/virtual/server-api.test.ts +85 -0
  239. package/src/__tests__/runtime/use-head.test.ts +243 -0
  240. package/src/__tests__/runtime/use-page-data.test.ts +45 -0
  241. package/src/cli/commands/build.ts +114 -0
  242. package/src/cli/commands/dev.ts +101 -0
  243. package/src/cli/commands/generate.ts +81 -0
  244. package/src/cli/commands/preview.ts +218 -0
  245. package/src/cli/create/index.ts +250 -0
  246. package/src/cli/create/templates/spa/app/app.ts.tpl +74 -0
  247. package/src/cli/create/templates/spa/app/layouts/default.ts.tpl +15 -0
  248. package/src/cli/create/templates/spa/app/pages/index.ts.tpl +8 -0
  249. package/src/cli/create/templates/spa/cer.config.ts.tpl +6 -0
  250. package/src/cli/create/templates/spa/index.html.tpl +12 -0
  251. package/src/cli/create/templates/spa/package.json.tpl +18 -0
  252. package/src/cli/create/templates/ssg/app/app.ts.tpl +74 -0
  253. package/src/cli/create/templates/ssg/app/layouts/default.ts.tpl +15 -0
  254. package/src/cli/create/templates/ssg/app/pages/index.ts.tpl +17 -0
  255. package/src/cli/create/templates/ssg/cer.config.ts.tpl +13 -0
  256. package/src/cli/create/templates/ssg/index.html.tpl +12 -0
  257. package/src/cli/create/templates/ssg/package.json.tpl +19 -0
  258. package/src/cli/create/templates/ssr/app/app.ts.tpl +74 -0
  259. package/src/cli/create/templates/ssr/app/layouts/default.ts.tpl +15 -0
  260. package/src/cli/create/templates/ssr/app/pages/index.ts.tpl +8 -0
  261. package/src/cli/create/templates/ssr/cer.config.ts.tpl +10 -0
  262. package/src/cli/create/templates/ssr/index.html.tpl +12 -0
  263. package/src/cli/create/templates/ssr/package.json.tpl +18 -0
  264. package/src/cli/index.ts +20 -0
  265. package/src/index.ts +13 -0
  266. package/src/plugin/build-ssg.ts +259 -0
  267. package/src/plugin/build-ssr.ts +147 -0
  268. package/src/plugin/dev-server.ts +266 -0
  269. package/src/plugin/dts-generator.ts +214 -0
  270. package/src/plugin/index.ts +330 -0
  271. package/src/plugin/path-utils.ts +186 -0
  272. package/src/plugin/scanner.ts +65 -0
  273. package/src/plugin/transforms/auto-import.ts +190 -0
  274. package/src/plugin/transforms/head-inject.ts +161 -0
  275. package/src/plugin/virtual/components.ts +28 -0
  276. package/src/plugin/virtual/composables.ts +28 -0
  277. package/src/plugin/virtual/error.ts +34 -0
  278. package/src/plugin/virtual/layouts.ts +41 -0
  279. package/src/plugin/virtual/loading.ts +33 -0
  280. package/src/plugin/virtual/middleware.ts +45 -0
  281. package/src/plugin/virtual/plugins.ts +36 -0
  282. package/src/plugin/virtual/routes.ts +147 -0
  283. package/src/plugin/virtual/server-api.ts +52 -0
  284. package/src/plugin/virtual/server-middleware.ts +44 -0
  285. package/src/runtime/app-template.ts +142 -0
  286. package/src/runtime/composables/index.ts +3 -0
  287. package/src/runtime/composables/use-head.ts +204 -0
  288. package/src/runtime/composables/use-page-data.ts +39 -0
  289. package/src/runtime/entry-client-template.ts +17 -0
  290. package/src/runtime/entry-server-template.ts +71 -0
  291. package/src/types/api.ts +19 -0
  292. package/src/types/config.ts +39 -0
  293. package/src/types/index.ts +6 -0
  294. package/src/types/middleware.ts +16 -0
  295. package/src/types/page.ts +29 -0
  296. package/src/types/plugin.ts +13 -0
  297. package/tsconfig.build.json +10 -0
  298. package/tsconfig.json +19 -0
  299. package/vitest.config.ts +29 -0
@@ -0,0 +1,266 @@
1
+ import type { ViteDevServer } from 'vite'
2
+ import type { IncomingMessage, ServerResponse } from 'node:http'
3
+ import { resolve } from 'pathe'
4
+
5
+ export interface ResolvedCerConfig {
6
+ mode: 'spa' | 'ssr' | 'ssg'
7
+ srcDir: string
8
+ root: string
9
+ pagesDir: string
10
+ layoutsDir: string
11
+ componentsDir: string
12
+ composablesDir: string
13
+ pluginsDir: string
14
+ middlewareDir: string
15
+ serverApiDir: string
16
+ serverMiddlewareDir: string
17
+ port: number
18
+ ssr: { dsd: boolean; streaming: boolean }
19
+ ssg: { routes: 'auto' | string[]; concurrency: number; fallback: boolean }
20
+ router: { base?: string; scrollToFragment?: boolean | object }
21
+ jitCss: { content: string[]; extendedColors: boolean }
22
+ autoImports: { components: boolean; composables: boolean; directives: boolean; runtime: boolean }
23
+ }
24
+
25
+ /**
26
+ * Reads the raw body from an IncomingMessage as a Buffer.
27
+ */
28
+ function readBody(req: IncomingMessage): Promise<Buffer> {
29
+ return new Promise((resolve, reject) => {
30
+ const chunks: Buffer[] = []
31
+ req.on('data', (chunk: Buffer) => chunks.push(chunk))
32
+ req.on('end', () => resolve(Buffer.concat(chunks)))
33
+ req.on('error', reject)
34
+ })
35
+ }
36
+
37
+ /**
38
+ * Parses the request body for JSON content types.
39
+ * Attaches the parsed body (or raw buffer) to `(req as any).body`.
40
+ */
41
+ async function parseBody(req: IncomingMessage): Promise<unknown> {
42
+ const contentType = req.headers['content-type'] ?? ''
43
+ const method = req.method?.toUpperCase() ?? 'GET'
44
+
45
+ if (!['POST', 'PUT', 'PATCH'].includes(method)) {
46
+ return undefined
47
+ }
48
+
49
+ const buf = await readBody(req)
50
+
51
+ if (contentType.includes('application/json')) {
52
+ try {
53
+ return JSON.parse(buf.toString('utf-8'))
54
+ } catch {
55
+ return undefined
56
+ }
57
+ }
58
+
59
+ return buf
60
+ }
61
+
62
+ /**
63
+ * Extracts route params from a path pattern.
64
+ * e.g. pattern="/api/users/:id", path="/api/users/42" -> { id: "42" }
65
+ */
66
+ function matchApiPath(
67
+ pattern: string,
68
+ urlPath: string,
69
+ ): Record<string, string> | null {
70
+ const patternParts = pattern.split('/')
71
+ const urlParts = urlPath.split('?')[0].split('/')
72
+
73
+ if (patternParts.length !== urlParts.length) return null
74
+
75
+ const params: Record<string, string> = {}
76
+ for (let i = 0; i < patternParts.length; i++) {
77
+ const p = patternParts[i]
78
+ const u = urlParts[i]
79
+ if (p.startsWith(':')) {
80
+ params[p.slice(1)] = decodeURIComponent(u)
81
+ } else if (p !== u) {
82
+ return null
83
+ }
84
+ }
85
+
86
+ return params
87
+ }
88
+
89
+ /**
90
+ * Parses URL query string into a plain object.
91
+ */
92
+ function parseQuery(url: string): Record<string, string> {
93
+ const qIndex = url.indexOf('?')
94
+ if (qIndex === -1) return {}
95
+ const qs = url.slice(qIndex + 1)
96
+ const result: Record<string, string> = {}
97
+ for (const part of qs.split('&')) {
98
+ if (!part) continue
99
+ const eqIdx = part.indexOf('=')
100
+ if (eqIdx === -1) {
101
+ result[decodeURIComponent(part)] = ''
102
+ } else {
103
+ result[decodeURIComponent(part.slice(0, eqIdx))] = decodeURIComponent(
104
+ part.slice(eqIdx + 1),
105
+ )
106
+ }
107
+ }
108
+ return result
109
+ }
110
+
111
+ /**
112
+ * Configures the Vite dev server with:
113
+ * 1. API route handlers from server/api/
114
+ * 2. Server middleware from server/middleware/
115
+ * 3. SSR HTML rendering (when mode is 'ssr')
116
+ */
117
+ export function configureCerDevServer(
118
+ server: ViteDevServer,
119
+ config: ResolvedCerConfig,
120
+ ): void {
121
+ server.middlewares.use(async (req: IncomingMessage, res: ServerResponse, next: () => void) => {
122
+ const url = req.url ?? '/'
123
+ const method = req.method?.toUpperCase() ?? 'GET'
124
+
125
+ // 1. Server middleware from server/middleware/ runs first (CORS, auth, logging, etc.)
126
+ try {
127
+ const smMod = await server.ssrLoadModule('virtual:cer-server-middleware')
128
+ const serverMiddleware = (smMod.serverMiddleware ?? smMod.default ?? []) as Array<{
129
+ name: string
130
+ handler: (req: IncomingMessage, res: ServerResponse, next: () => void) => void | Promise<void>
131
+ }>
132
+
133
+ for (const { handler } of serverMiddleware) {
134
+ if (typeof handler === 'function') {
135
+ let calledNext = false
136
+ await handler(req, res, () => {
137
+ calledNext = true
138
+ })
139
+ if (!calledNext) return
140
+ }
141
+ }
142
+ } catch {
143
+ // middleware module not ready — continue
144
+ }
145
+
146
+ // 2. API route handlers from server/api/
147
+ try {
148
+ const mod = await server.ssrLoadModule('virtual:cer-server-api')
149
+ const apiRoutes: Array<{ path: string; handlers: Record<string, unknown> }> =
150
+ mod.apiRoutes ?? mod.default?.apiRoutes ?? []
151
+
152
+ for (const route of apiRoutes) {
153
+ const params = matchApiPath(route.path, url.split('?')[0])
154
+ if (params === null) continue
155
+
156
+ const query = parseQuery(url)
157
+ const body = await parseBody(req)
158
+
159
+ // Augment request
160
+ const augmentedReq = req as IncomingMessage & {
161
+ params: Record<string, string>
162
+ query: Record<string, string>
163
+ body: unknown
164
+ }
165
+ augmentedReq.params = params
166
+ augmentedReq.query = query
167
+ augmentedReq.body = body
168
+
169
+ // Augment response with json() and status() helpers
170
+ const augmentedRes = res as ServerResponse & {
171
+ json(data: unknown): void
172
+ status(code: number): typeof augmentedRes
173
+ _statusCode?: number
174
+ }
175
+
176
+ augmentedRes.json = function (data: unknown) {
177
+ const json = JSON.stringify(data)
178
+ this.setHeader('Content-Type', 'application/json')
179
+ this.end(json)
180
+ }
181
+
182
+ augmentedRes.status = function (code: number) {
183
+ this.statusCode = code
184
+ return this
185
+ }
186
+
187
+ // Try to find a handler for the HTTP method. Exports may be GET/POST (uppercase)
188
+ // or get/post (lowercase); try both plus a 'default' fallback.
189
+ const handlerKey = method.toLowerCase()
190
+ const handler =
191
+ (route.handlers[handlerKey] as Function | undefined) ??
192
+ (route.handlers[method.toUpperCase()] as Function | undefined) ??
193
+ (route.handlers['default'] as Function | undefined)
194
+
195
+ if (typeof handler === 'function') {
196
+ try {
197
+ await handler(augmentedReq, augmentedRes)
198
+ } catch (err) {
199
+ server.ssrFixStacktrace(err as Error)
200
+ console.error(`[cer-app] API handler error at ${route.path}:`, err)
201
+ res.statusCode = 500
202
+ res.setHeader('Content-Type', 'application/json')
203
+ res.end(JSON.stringify({ error: 'Internal Server Error' }))
204
+ }
205
+ return
206
+ }
207
+ }
208
+ } catch {
209
+ // virtual:cer-server-api not yet ready or empty — continue
210
+ }
211
+
212
+ // 3. SSR mode: intercept HTML requests
213
+ if (config.mode === 'ssr') {
214
+ const acceptsHtml =
215
+ (req.headers['accept'] ?? '').includes('text/html') ||
216
+ url === '/' ||
217
+ (!url.includes('.') && !url.startsWith('/api/'))
218
+
219
+ if (acceptsHtml) {
220
+ try {
221
+ // Load the SSR entry module
222
+ const ssrEntry = await server.ssrLoadModule(
223
+ resolve(config.srcDir, 'entry-server.ts'),
224
+ )
225
+
226
+ const handler =
227
+ ssrEntry.handler ?? ssrEntry.default?.handler
228
+
229
+ if (typeof handler === 'function') {
230
+ await handler(req, res)
231
+ return
232
+ }
233
+
234
+ // Fallback: render using template + ssrEntry render function
235
+ const renderFn = ssrEntry.render ?? ssrEntry.default?.render
236
+
237
+ if (typeof renderFn === 'function') {
238
+ const template = await server.transformIndexHtml(
239
+ url,
240
+ `<!DOCTYPE html><html><head></head><body><div id="app"></div></body></html>`,
241
+ )
242
+
243
+ const result = await renderFn({ url, req })
244
+ const rendered = template.replace(
245
+ '<div id="app"></div>',
246
+ `<div id="app">${result.html ?? ''}</div>`,
247
+ )
248
+
249
+ res.setHeader('Content-Type', 'text/html; charset=utf-8')
250
+ res.end(rendered)
251
+ return
252
+ }
253
+ } catch (err) {
254
+ server.ssrFixStacktrace(err as Error)
255
+ console.error('[cer-app] SSR render error:', err)
256
+ res.statusCode = 500
257
+ res.setHeader('Content-Type', 'text/plain')
258
+ res.end('SSR Error: ' + String(err))
259
+ return
260
+ }
261
+ }
262
+ }
263
+
264
+ next()
265
+ })
266
+ }
@@ -0,0 +1,214 @@
1
+ import { writeFileSync } from 'node:fs'
2
+ import { readFileSync, existsSync } from 'node:fs'
3
+ import { join, relative } from 'pathe'
4
+ import { scanDirectory } from './scanner.js'
5
+
6
+ /**
7
+ * Writes `cer-tsconfig.json` to the project root containing path aliases for
8
+ * the `~/` prefix. Users extend it from their `tsconfig.json`:
9
+ * { "extends": "./cer-tsconfig.json" }
10
+ */
11
+ export function writeTsconfigPaths(root: string, srcDir: string): void {
12
+ const rel = './' + relative(root, srcDir).replace(/\\/g, '/')
13
+ const paths: Record<string, string[]> = {
14
+ '~/*': [`${rel}/*`],
15
+ '~/pages/*': [`${rel}/pages/*`],
16
+ '~/layouts/*': [`${rel}/layouts/*`],
17
+ '~/components/*': [`${rel}/components/*`],
18
+ '~/composables/*': [`${rel}/composables/*`],
19
+ '~/plugins/*': [`${rel}/plugins/*`],
20
+ '~/middleware/*': [`${rel}/middleware/*`],
21
+ '~/assets/*': [`${rel}/assets/*`],
22
+ }
23
+ const content = JSON.stringify({ compilerOptions: { paths } }, null, 2) + '\n'
24
+ writeFileSync(join(root, 'cer-tsconfig.json'), content, 'utf-8')
25
+ }
26
+
27
+ const RUNTIME_GLOBALS = [
28
+ 'component',
29
+ 'html',
30
+ 'css',
31
+ 'ref',
32
+ 'computed',
33
+ 'watch',
34
+ 'watchEffect',
35
+ 'useProps',
36
+ 'useEmit',
37
+ 'useOnConnected',
38
+ 'useOnDisconnected',
39
+ 'useOnAttributeChanged',
40
+ 'useOnError',
41
+ 'useStyle',
42
+ 'useDesignTokens',
43
+ 'useGlobalStyle',
44
+ 'useExpose',
45
+ 'useSlots',
46
+ 'provide',
47
+ 'inject',
48
+ 'createComposable',
49
+ 'nextTick',
50
+ 'defineModel',
51
+ 'getCurrentComponentContext',
52
+ 'isReactiveState',
53
+ 'unsafeHTML',
54
+ 'decodeEntities',
55
+ 'useTeleport',
56
+ ]
57
+
58
+ const DIRECTIVE_GLOBALS = ['when', 'each', 'match', 'anchorBlock']
59
+
60
+ const FRAMEWORK_GLOBALS = ['useHead', 'usePageData']
61
+
62
+ /**
63
+ * Scans a composables directory and returns a map of export name → file path.
64
+ * Uses a simple regex to find exported identifiers.
65
+ */
66
+ export async function scanComposableExports(composablesDir: string): Promise<Map<string, string>> {
67
+ const result = new Map<string, string>()
68
+ if (!existsSync(composablesDir)) return result
69
+
70
+ const files = await scanDirectory('**/*.ts', composablesDir)
71
+ const exportPattern = /export\s+(?:async\s+)?(?:function|const|let|var|class)\s+(\w+)/g
72
+
73
+ for (const file of files) {
74
+ const code = readFileSync(file, 'utf-8')
75
+ exportPattern.lastIndex = 0
76
+ let match
77
+ while ((match = exportPattern.exec(code)) !== null) {
78
+ result.set(match[1], file)
79
+ }
80
+ }
81
+
82
+ return result
83
+ }
84
+
85
+ /**
86
+ * Generates the content of `cer-auto-imports.d.ts`.
87
+ * @param root - project root (where the .d.ts file will be written)
88
+ */
89
+ export async function generateAutoImportDts(
90
+ root: string,
91
+ composablesDir: string,
92
+ composableExports?: Map<string, string>,
93
+ ): Promise<string> {
94
+ const exports = composableExports ?? await scanComposableExports(composablesDir)
95
+
96
+ const lines: string[] = [
97
+ '// AUTO-GENERATED by vite-plugin-cer-app',
98
+ '// Do not edit — regenerated automatically on dev server start and build.',
99
+ '',
100
+ 'export {}',
101
+ '',
102
+ 'declare global {',
103
+ ]
104
+
105
+ for (const name of RUNTIME_GLOBALS) {
106
+ lines.push(` const ${name}: typeof import('@jasonshimmy/custom-elements-runtime')['${name}']`)
107
+ }
108
+
109
+ lines.push('')
110
+
111
+ for (const name of DIRECTIVE_GLOBALS) {
112
+ lines.push(` const ${name}: typeof import('@jasonshimmy/custom-elements-runtime/directives')['${name}']`)
113
+ }
114
+
115
+ lines.push('')
116
+
117
+ for (const name of FRAMEWORK_GLOBALS) {
118
+ lines.push(` const ${name}: typeof import('vite-plugin-cer-app/composables')['${name}']`)
119
+ }
120
+
121
+ if (exports.size > 0) {
122
+ lines.push('')
123
+ for (const [name, filePath] of exports) {
124
+ // Use a path relative to the project root so the .d.ts is portable
125
+ const rel = './' + relative(root, filePath).replace(/\.ts$/, '')
126
+ lines.push(` const ${name}: typeof import('${rel}')['${name}']`)
127
+ }
128
+ }
129
+
130
+ lines.push('')
131
+ lines.push(' // SSR loader data injected as window.__CER_DATA__ by the server.')
132
+ lines.push(' // Consumed once by usePageData() during client hydration.')
133
+ lines.push(' // eslint-disable-next-line @typescript-eslint/no-explicit-any')
134
+ lines.push(' var __CER_DATA__: any')
135
+ lines.push('}')
136
+ lines.push('')
137
+
138
+ return lines.join('\n')
139
+ }
140
+
141
+ /**
142
+ * Generates the content of `cer-env.d.ts` — a script (non-module) file
143
+ * containing ambient `declare module` declarations for all virtual modules.
144
+ * Must NOT contain `export {}` or any top-level imports/exports.
145
+ */
146
+ export async function generateVirtualModuleDts(
147
+ root: string,
148
+ composablesDir: string,
149
+ composableExports?: Map<string, string>,
150
+ ): Promise<string> {
151
+ const exports = composableExports ?? await scanComposableExports(composablesDir)
152
+
153
+ const lines: string[] = [
154
+ '// AUTO-GENERATED by vite-plugin-cer-app',
155
+ '// Do not edit — regenerated automatically on dev server start and build.',
156
+ '',
157
+ `declare module 'virtual:cer-routes' {`,
158
+ ` import type { Route } from '@jasonshimmy/custom-elements-runtime/router'`,
159
+ ` const routes: Route[]`,
160
+ ` export default routes`,
161
+ `}`,
162
+ '',
163
+ `declare module 'virtual:cer-layouts' {}`,
164
+ `declare module 'virtual:cer-components' {}`,
165
+ '',
166
+ `declare module 'virtual:cer-plugins' {`,
167
+ ` import type { AppPlugin } from 'vite-plugin-cer-app/types'`,
168
+ ` const plugins: AppPlugin[]`,
169
+ ` export default plugins`,
170
+ `}`,
171
+ '',
172
+ `declare module 'virtual:cer-composables' {`,
173
+ ]
174
+
175
+ for (const [name, filePath] of exports) {
176
+ const rel = './' + relative(root, filePath).replace(/\.ts$/, '')
177
+ lines.push(` export { ${name} } from '${rel}'`)
178
+ }
179
+
180
+ lines.push(`}`)
181
+ lines.push('')
182
+ lines.push(`declare module 'virtual:cer-middleware' {`)
183
+ lines.push(` const middleware: Record<string, Function>`)
184
+ lines.push(` export { middleware }`)
185
+ lines.push(`}`)
186
+ lines.push('')
187
+ lines.push(`declare module 'virtual:cer-loading' {`)
188
+ lines.push(` export const hasLoading: boolean`)
189
+ lines.push(` export const loadingTag: string | null`)
190
+ lines.push(`}`)
191
+ lines.push('')
192
+ lines.push(`declare module 'virtual:cer-error' {`)
193
+ lines.push(` export const hasError: boolean`)
194
+ lines.push(` export const errorTag: string | null`)
195
+ lines.push(`}`)
196
+ lines.push('')
197
+
198
+ return lines.join('\n')
199
+ }
200
+
201
+ /**
202
+ * Writes both `cer-auto-imports.d.ts` and `cer-env.d.ts` to the project root.
203
+ */
204
+ export async function writeAutoImportDts(
205
+ root: string,
206
+ composablesDir: string,
207
+ composableExports?: Map<string, string>,
208
+ ): Promise<void> {
209
+ const scanned = composableExports ?? await scanComposableExports(composablesDir)
210
+ const autoImportsContent = await generateAutoImportDts(root, composablesDir, scanned)
211
+ const envContent = await generateVirtualModuleDts(root, composablesDir, scanned)
212
+ writeFileSync(join(root, 'cer-auto-imports.d.ts'), autoImportsContent, 'utf-8')
213
+ writeFileSync(join(root, 'cer-env.d.ts'), envContent, 'utf-8')
214
+ }