@jasonshimmy/vite-plugin-cer-app 0.23.2 → 0.25.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 (404) hide show
  1. package/dist/cli/adapters/netlify.js +4 -2
  2. package/dist/cli/adapters/netlify.js.map +1 -1
  3. package/dist/cli/checks/lighthouse.d.ts +26 -0
  4. package/dist/cli/checks/lighthouse.d.ts.map +1 -0
  5. package/dist/cli/checks/lighthouse.js +121 -0
  6. package/dist/cli/checks/lighthouse.js.map +1 -0
  7. package/dist/cli/checks/links.d.ts +12 -0
  8. package/dist/cli/checks/links.d.ts.map +1 -0
  9. package/dist/cli/checks/links.js +89 -0
  10. package/dist/cli/checks/links.js.map +1 -0
  11. package/dist/cli/checks/performance.d.ts +17 -0
  12. package/dist/cli/checks/performance.d.ts.map +1 -0
  13. package/dist/cli/checks/performance.js +61 -0
  14. package/dist/cli/checks/performance.js.map +1 -0
  15. package/dist/cli/commands/check.d.ts +3 -0
  16. package/dist/cli/commands/check.d.ts.map +1 -0
  17. package/dist/cli/commands/check.js +85 -0
  18. package/dist/cli/commands/check.js.map +1 -0
  19. package/dist/cli/commands/preview.d.ts.map +1 -1
  20. package/dist/cli/commands/preview.js +4 -2
  21. package/dist/cli/commands/preview.js.map +1 -1
  22. package/dist/cli/create/index.d.ts +11 -1
  23. package/dist/cli/create/index.d.ts.map +1 -1
  24. package/dist/cli/create/index.js +135 -65
  25. package/dist/cli/create/index.js.map +1 -1
  26. package/dist/cli/create/templates/spa/package.json.tpl +5 -3
  27. package/dist/cli/create/templates/ssg/cer.config.ts.tpl +2 -0
  28. package/dist/cli/create/templates/ssg/package.json.tpl +5 -3
  29. package/dist/cli/create/templates/ssr/package.json.tpl +5 -3
  30. package/dist/cli/index.js +2 -0
  31. package/dist/cli/index.js.map +1 -1
  32. package/dist/index.d.ts +4 -2
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/plugin/build-ssg.d.ts +22 -6
  35. package/dist/plugin/build-ssg.d.ts.map +1 -1
  36. package/dist/plugin/build-ssg.js +226 -17
  37. package/dist/plugin/build-ssg.js.map +1 -1
  38. package/dist/plugin/content/index.d.ts +1 -1
  39. package/dist/plugin/content/index.d.ts.map +1 -1
  40. package/dist/plugin/content/index.js +8 -8
  41. package/dist/plugin/content/index.js.map +1 -1
  42. package/dist/plugin/content/parser.d.ts +3 -3
  43. package/dist/plugin/content/parser.d.ts.map +1 -1
  44. package/dist/plugin/content/parser.js +103 -24
  45. package/dist/plugin/content/parser.js.map +1 -1
  46. package/dist/plugin/dev-server.d.ts +4 -1
  47. package/dist/plugin/dev-server.d.ts.map +1 -1
  48. package/dist/plugin/dev-server.js.map +1 -1
  49. package/dist/plugin/dts-generator.d.ts.map +1 -1
  50. package/dist/plugin/dts-generator.js +50 -8
  51. package/dist/plugin/dts-generator.js.map +1 -1
  52. package/dist/plugin/generated-dir.d.ts +2 -0
  53. package/dist/plugin/generated-dir.d.ts.map +1 -1
  54. package/dist/plugin/generated-dir.js +17 -6
  55. package/dist/plugin/generated-dir.js.map +1 -1
  56. package/dist/plugin/html-post-process.d.ts +7 -0
  57. package/dist/plugin/html-post-process.d.ts.map +1 -1
  58. package/dist/plugin/html-post-process.js +26 -0
  59. package/dist/plugin/html-post-process.js.map +1 -1
  60. package/dist/plugin/index.d.ts.map +1 -1
  61. package/dist/plugin/index.js +60 -13
  62. package/dist/plugin/index.js.map +1 -1
  63. package/dist/plugin/transforms/auto-import.d.ts.map +1 -1
  64. package/dist/plugin/transforms/auto-import.js +7 -0
  65. package/dist/plugin/transforms/auto-import.js.map +1 -1
  66. package/dist/plugin/transforms/prune-runtime-css.d.ts +8 -0
  67. package/dist/plugin/transforms/prune-runtime-css.d.ts.map +1 -0
  68. package/dist/plugin/transforms/prune-runtime-css.js +40 -0
  69. package/dist/plugin/transforms/prune-runtime-css.js.map +1 -0
  70. package/dist/runtime/app-template.d.ts +1 -1
  71. package/dist/runtime/app-template.d.ts.map +1 -1
  72. package/dist/runtime/app-template.js +206 -36
  73. package/dist/runtime/app-template.js.map +1 -1
  74. package/dist/runtime/composables/index.d.ts +4 -0
  75. package/dist/runtime/composables/index.d.ts.map +1 -1
  76. package/dist/runtime/composables/index.js +2 -0
  77. package/dist/runtime/composables/index.js.map +1 -1
  78. package/dist/runtime/composables/use-active-headings.d.ts +24 -0
  79. package/dist/runtime/composables/use-active-headings.d.ts.map +1 -0
  80. package/dist/runtime/composables/use-active-headings.js +112 -0
  81. package/dist/runtime/composables/use-active-headings.js.map +1 -0
  82. package/dist/runtime/composables/use-content-page.d.ts +45 -0
  83. package/dist/runtime/composables/use-content-page.d.ts.map +1 -0
  84. package/dist/runtime/composables/use-content-page.js +181 -0
  85. package/dist/runtime/composables/use-content-page.js.map +1 -0
  86. package/dist/runtime/composables/use-content-search.d.ts.map +1 -1
  87. package/dist/runtime/composables/use-content-search.js +1 -5
  88. package/dist/runtime/composables/use-content-search.js.map +1 -1
  89. package/dist/runtime/composables/use-fetch.d.ts +2 -1
  90. package/dist/runtime/composables/use-fetch.d.ts.map +1 -1
  91. package/dist/runtime/composables/use-fetch.js +112 -43
  92. package/dist/runtime/composables/use-fetch.js.map +1 -1
  93. package/dist/runtime/composables/use-route.d.ts.map +1 -1
  94. package/dist/runtime/composables/use-route.js +39 -11
  95. package/dist/runtime/composables/use-route.js.map +1 -1
  96. package/dist/runtime/content/client.js +2 -2
  97. package/dist/runtime/content/client.js.map +1 -1
  98. package/dist/runtime/entry-server-template.d.ts +16 -1
  99. package/dist/runtime/entry-server-template.d.ts.map +1 -1
  100. package/dist/runtime/entry-server-template.js +120 -13
  101. package/dist/runtime/entry-server-template.js.map +1 -1
  102. package/dist/types/config.d.ts +45 -4
  103. package/dist/types/config.d.ts.map +1 -1
  104. package/dist/types/config.js.map +1 -1
  105. package/dist/types/content.d.ts +18 -1
  106. package/dist/types/content.d.ts.map +1 -1
  107. package/dist/types/middleware.d.ts +2 -2
  108. package/dist/types/middleware.d.ts.map +1 -1
  109. package/package.json +22 -19
  110. package/.github/copilot-instructions.md +0 -169
  111. package/.github/workflows/publish.yml +0 -257
  112. package/.nvmrc +0 -1
  113. package/CHANGELOG.md +0 -236
  114. package/CLAUDE.md +0 -113
  115. package/commits.txt +0 -1
  116. package/cypress.config.ts +0 -16
  117. package/docs/authentication.md +0 -285
  118. package/docs/cli.md +0 -324
  119. package/docs/components.md +0 -237
  120. package/docs/composables.md +0 -907
  121. package/docs/configuration.md +0 -696
  122. package/docs/content.md +0 -570
  123. package/docs/data-loading.md +0 -336
  124. package/docs/getting-started.md +0 -237
  125. package/docs/head-management.md +0 -212
  126. package/docs/i18n.md +0 -159
  127. package/docs/layouts.md +0 -224
  128. package/docs/middleware.md +0 -234
  129. package/docs/plugins.md +0 -170
  130. package/docs/rendering-modes.md +0 -377
  131. package/docs/routing.md +0 -485
  132. package/docs/server-api.md +0 -253
  133. package/docs/testing.md +0 -455
  134. package/docs/use-fetch.md +0 -183
  135. package/e2e/cypress/e2e/api.cy.ts +0 -123
  136. package/e2e/cypress/e2e/async-component.cy.ts +0 -27
  137. package/e2e/cypress/e2e/component-splitting.cy.ts +0 -50
  138. package/e2e/cypress/e2e/content.cy.ts +0 -425
  139. package/e2e/cypress/e2e/cookie.cy.ts +0 -85
  140. package/e2e/cypress/e2e/data.cy.ts +0 -158
  141. package/e2e/cypress/e2e/error-boundary.cy.ts +0 -88
  142. package/e2e/cypress/e2e/fouc.cy.ts +0 -254
  143. package/e2e/cypress/e2e/group-meta.cy.ts +0 -36
  144. package/e2e/cypress/e2e/head.cy.ts +0 -89
  145. package/e2e/cypress/e2e/i18n.cy.ts +0 -79
  146. package/e2e/cypress/e2e/interactive.cy.ts +0 -137
  147. package/e2e/cypress/e2e/isr-nested-runtime.cy.ts +0 -102
  148. package/e2e/cypress/e2e/jit-css-custom-colors.cy.ts +0 -43
  149. package/e2e/cypress/e2e/middleware-chain.cy.ts +0 -89
  150. package/e2e/cypress/e2e/middleware-status.cy.ts +0 -30
  151. package/e2e/cypress/e2e/middleware.cy.ts +0 -45
  152. package/e2e/cypress/e2e/navigate.cy.ts +0 -39
  153. package/e2e/cypress/e2e/observability.cy.ts +0 -77
  154. package/e2e/cypress/e2e/per-route-error.cy.ts +0 -42
  155. package/e2e/cypress/e2e/per-route-render.cy.ts +0 -70
  156. package/e2e/cypress/e2e/preview-hardening.cy.ts +0 -88
  157. package/e2e/cypress/e2e/routes.cy.ts +0 -128
  158. package/e2e/cypress/e2e/seo-meta.cy.ts +0 -108
  159. package/e2e/cypress/e2e/server-middleware.cy.ts +0 -46
  160. package/e2e/cypress/e2e/session-rotation.cy.ts +0 -25
  161. package/e2e/cypress/e2e/session.cy.ts +0 -73
  162. package/e2e/cypress/e2e/ssr-render-error.cy.ts +0 -68
  163. package/e2e/cypress/e2e/synthetic-404.cy.ts +0 -45
  164. package/e2e/cypress/e2e/use-page-data.cy.ts +0 -122
  165. package/e2e/cypress/e2e/use-state.cy.ts +0 -138
  166. package/e2e/cypress/support/commands.ts +0 -120
  167. package/e2e/cypress/support/e2e.ts +0 -10
  168. package/e2e/cypress/tsconfig.json +0 -14
  169. package/e2e/kitchen-sink/app/components/ks-badge.ts +0 -8
  170. package/e2e/kitchen-sink/app/composables/useKsCounter.ts +0 -9
  171. package/e2e/kitchen-sink/app/error.ts +0 -18
  172. package/e2e/kitchen-sink/app/layouts/admin.ts +0 -13
  173. package/e2e/kitchen-sink/app/layouts/default.ts +0 -25
  174. package/e2e/kitchen-sink/app/layouts/group.ts +0 -9
  175. package/e2e/kitchen-sink/app/layouts/minimal.ts +0 -7
  176. package/e2e/kitchen-sink/app/loading.ts +0 -9
  177. package/e2e/kitchen-sink/app/middleware/auth.ts +0 -9
  178. package/e2e/kitchen-sink/app/middleware/custom-log.ts +0 -4
  179. package/e2e/kitchen-sink/app/middleware/group-auth.ts +0 -7
  180. package/e2e/kitchen-sink/app/middleware/logger.ts +0 -14
  181. package/e2e/kitchen-sink/app/pages/(auth)/login.ts +0 -13
  182. package/e2e/kitchen-sink/app/pages/(auth)/protected.ts +0 -16
  183. package/e2e/kitchen-sink/app/pages/404.ts +0 -9
  184. package/e2e/kitchen-sink/app/pages/about.ts +0 -17
  185. package/e2e/kitchen-sink/app/pages/admin/_layout.ts +0 -1
  186. package/e2e/kitchen-sink/app/pages/admin/dashboard.ts +0 -11
  187. package/e2e/kitchen-sink/app/pages/async-component-error-test.ts +0 -18
  188. package/e2e/kitchen-sink/app/pages/async-component-test.ts +0 -18
  189. package/e2e/kitchen-sink/app/pages/blog/[slug].ts +0 -59
  190. package/e2e/kitchen-sink/app/pages/blog/index.ts +0 -51
  191. package/e2e/kitchen-sink/app/pages/content-blog.ts +0 -37
  192. package/e2e/kitchen-sink/app/pages/content-doc.ts +0 -42
  193. package/e2e/kitchen-sink/app/pages/content-fallback.ts +0 -36
  194. package/e2e/kitchen-sink/app/pages/content-guides.ts +0 -35
  195. package/e2e/kitchen-sink/app/pages/content-index.ts +0 -39
  196. package/e2e/kitchen-sink/app/pages/content-search.ts +0 -36
  197. package/e2e/kitchen-sink/app/pages/cookie-test.ts +0 -22
  198. package/e2e/kitchen-sink/app/pages/counter.ts +0 -17
  199. package/e2e/kitchen-sink/app/pages/group-auth-blocked.ts +0 -9
  200. package/e2e/kitchen-sink/app/pages/group-meta-test/_layout.ts +0 -6
  201. package/e2e/kitchen-sink/app/pages/group-meta-test/custom-mw.ts +0 -14
  202. package/e2e/kitchen-sink/app/pages/group-meta-test/page.ts +0 -9
  203. package/e2e/kitchen-sink/app/pages/group-meta-test/protected.ts +0 -8
  204. package/e2e/kitchen-sink/app/pages/head.ts +0 -20
  205. package/e2e/kitchen-sink/app/pages/i18n-test.ts +0 -14
  206. package/e2e/kitchen-sink/app/pages/index.ts +0 -27
  207. package/e2e/kitchen-sink/app/pages/isr-test.ts +0 -17
  208. package/e2e/kitchen-sink/app/pages/items/[id].ts +0 -20
  209. package/e2e/kitchen-sink/app/pages/jit-css-custom-colors-test.ts +0 -14
  210. package/e2e/kitchen-sink/app/pages/loader-error-test.ts +0 -13
  211. package/e2e/kitchen-sink/app/pages/loader-props-test.ts +0 -19
  212. package/e2e/kitchen-sink/app/pages/loader-response-error-test.ts +0 -13
  213. package/e2e/kitchen-sink/app/pages/middleware-chain-test.ts +0 -12
  214. package/e2e/kitchen-sink/app/pages/middleware-status-test.ts +0 -9
  215. package/e2e/kitchen-sink/app/pages/navigate-test.ts +0 -16
  216. package/e2e/kitchen-sink/app/pages/observability-test.ts +0 -25
  217. package/e2e/kitchen-sink/app/pages/per-route-error-test.error.ts +0 -13
  218. package/e2e/kitchen-sink/app/pages/per-route-error-test.ts +0 -15
  219. package/e2e/kitchen-sink/app/pages/plugin-dsd-test.ts +0 -10
  220. package/e2e/kitchen-sink/app/pages/render-error-test.ts +0 -12
  221. package/e2e/kitchen-sink/app/pages/render-server-test.ts +0 -12
  222. package/e2e/kitchen-sink/app/pages/render-spa-test.ts +0 -12
  223. package/e2e/kitchen-sink/app/pages/route-info.ts +0 -14
  224. package/e2e/kitchen-sink/app/pages/seo-test.ts +0 -23
  225. package/e2e/kitchen-sink/app/pages/session-rotation-test.ts +0 -25
  226. package/e2e/kitchen-sink/app/pages/use-state-test.ts +0 -37
  227. package/e2e/kitchen-sink/app/plugins/01.setup.ts +0 -7
  228. package/e2e/kitchen-sink/app/plugins/02.ks-plugin-components.ts +0 -27
  229. package/e2e/kitchen-sink/cer-auto-imports.d.ts +0 -51
  230. package/e2e/kitchen-sink/cer-env.d.ts +0 -35
  231. package/e2e/kitchen-sink/cer-tsconfig.json +0 -30
  232. package/e2e/kitchen-sink/cer.config.ts +0 -43
  233. package/e2e/kitchen-sink/content/01.guides/01.index.md +0 -8
  234. package/e2e/kitchen-sink/content/01.guides/02.intro.md +0 -8
  235. package/e2e/kitchen-sink/content/01.guides/10.advanced.md +0 -8
  236. package/e2e/kitchen-sink/content/blog/2026-04-01-hello.md +0 -26
  237. package/e2e/kitchen-sink/content/blog/2026-04-02-draft.md +0 -10
  238. package/e2e/kitchen-sink/content/blog/index.md +0 -8
  239. package/e2e/kitchen-sink/content/blog/no-frontmatter.md +0 -7
  240. package/e2e/kitchen-sink/content/docs/getting-started.md +0 -48
  241. package/e2e/kitchen-sink/content/index.md +0 -16
  242. package/e2e/kitchen-sink/server/api/echo.ts +0 -12
  243. package/e2e/kitchen-sink/server/api/health.ts +0 -3
  244. package/e2e/kitchen-sink/server/api/posts/[slug].ts +0 -11
  245. package/e2e/kitchen-sink/server/api/posts/index.ts +0 -5
  246. package/e2e/kitchen-sink/server/api/session.ts +0 -23
  247. package/e2e/kitchen-sink/server/data/posts.ts +0 -21
  248. package/e2e/kitchen-sink/server/middleware/01-headers.ts +0 -6
  249. package/e2e/kitchen-sink/server/middleware/02-status-test.ts +0 -10
  250. package/e2e/kitchen-sink/tsconfig.json +0 -3
  251. package/e2e/scripts/clean.mjs +0 -14
  252. package/eslint.config.ts +0 -22
  253. package/src/__tests__/cli/adapters/cloudflare-worker.integration.test.ts +0 -132
  254. package/src/__tests__/cli/adapters/cloudflare.test.ts +0 -332
  255. package/src/__tests__/cli/adapters/netlify-bridge.integration.test.ts +0 -139
  256. package/src/__tests__/cli/adapters/netlify.test.ts +0 -286
  257. package/src/__tests__/cli/adapters/vercel-launcher.integration.test.ts +0 -129
  258. package/src/__tests__/cli/adapters/vercel.test.ts +0 -263
  259. package/src/__tests__/cli/preview-hardening.test.ts +0 -265
  260. package/src/__tests__/cli/preview-isr.test.ts +0 -320
  261. package/src/__tests__/index.test.ts +0 -21
  262. package/src/__tests__/plugin/app-template.test.ts +0 -166
  263. package/src/__tests__/plugin/build-ssg-render.test.ts +0 -155
  264. package/src/__tests__/plugin/build-ssg.test.ts +0 -598
  265. package/src/__tests__/plugin/build-ssr.test.ts +0 -192
  266. package/src/__tests__/plugin/cer-app-plugin.test.ts +0 -552
  267. package/src/__tests__/plugin/content/emitter.test.ts +0 -117
  268. package/src/__tests__/plugin/content/loader.test.ts +0 -154
  269. package/src/__tests__/plugin/content/parser.test.ts +0 -381
  270. package/src/__tests__/plugin/content/path-utils.test.ts +0 -69
  271. package/src/__tests__/plugin/content/search.test.ts +0 -119
  272. package/src/__tests__/plugin/dev-server.test.ts +0 -813
  273. package/src/__tests__/plugin/dts-generator.test.ts +0 -394
  274. package/src/__tests__/plugin/entry-server-template.test.ts +0 -385
  275. package/src/__tests__/plugin/generated-dir.test.ts +0 -144
  276. package/src/__tests__/plugin/html-post-process.test.ts +0 -146
  277. package/src/__tests__/plugin/path-utils.test.ts +0 -411
  278. package/src/__tests__/plugin/resolve-config.test.ts +0 -233
  279. package/src/__tests__/plugin/scanner.test.ts +0 -186
  280. package/src/__tests__/plugin/transforms/auto-import.test.ts +0 -510
  281. package/src/__tests__/plugin/transforms/head-inject.test.ts +0 -178
  282. package/src/__tests__/plugin/virtual/composables.test.ts +0 -57
  283. package/src/__tests__/plugin/virtual/content-components.test.ts +0 -66
  284. package/src/__tests__/plugin/virtual/error.test.ts +0 -71
  285. package/src/__tests__/plugin/virtual/layouts.test.ts +0 -70
  286. package/src/__tests__/plugin/virtual/loading.test.ts +0 -72
  287. package/src/__tests__/plugin/virtual/middleware.test.ts +0 -83
  288. package/src/__tests__/plugin/virtual/plugins.test.ts +0 -129
  289. package/src/__tests__/plugin/virtual/routes.test.ts +0 -884
  290. package/src/__tests__/plugin/virtual/server-api.test.ts +0 -164
  291. package/src/__tests__/plugin/virtual/server-middleware.test.ts +0 -102
  292. package/src/__tests__/runtime/app-template.test.ts +0 -208
  293. package/src/__tests__/runtime/define-middleware.test.ts +0 -64
  294. package/src/__tests__/runtime/define-server-middleware.test.ts +0 -54
  295. package/src/__tests__/runtime/entry-client-template.test.ts +0 -61
  296. package/src/__tests__/runtime/isr-handler.test.ts +0 -338
  297. package/src/__tests__/runtime/oauth/handler.test.ts +0 -358
  298. package/src/__tests__/runtime/oauth/providers.test.ts +0 -47
  299. package/src/__tests__/runtime/oauth-index.test.ts +0 -43
  300. package/src/__tests__/runtime/use-auth.test.ts +0 -169
  301. package/src/__tests__/runtime/use-content-search-composable.test.ts +0 -405
  302. package/src/__tests__/runtime/use-content-search.test.ts +0 -163
  303. package/src/__tests__/runtime/use-content.test.ts +0 -226
  304. package/src/__tests__/runtime/use-cookie.test.ts +0 -277
  305. package/src/__tests__/runtime/use-fetch-component.test.ts +0 -214
  306. package/src/__tests__/runtime/use-fetch.test.ts +0 -377
  307. package/src/__tests__/runtime/use-head.test.ts +0 -311
  308. package/src/__tests__/runtime/use-inject-client.test.ts +0 -67
  309. package/src/__tests__/runtime/use-inject.test.ts +0 -66
  310. package/src/__tests__/runtime/use-locale.test.ts +0 -152
  311. package/src/__tests__/runtime/use-navigate.test.ts +0 -121
  312. package/src/__tests__/runtime/use-page-data.test.ts +0 -298
  313. package/src/__tests__/runtime/use-route.test.ts +0 -156
  314. package/src/__tests__/runtime/use-runtime-config.test.ts +0 -182
  315. package/src/__tests__/runtime/use-seo-meta.test.ts +0 -109
  316. package/src/__tests__/runtime/use-session.test.ts +0 -320
  317. package/src/__tests__/runtime/use-state.test.ts +0 -340
  318. package/src/__tests__/types/config.test.ts +0 -18
  319. package/src/cli/adapters/cloudflare.ts +0 -335
  320. package/src/cli/adapters/netlify.ts +0 -326
  321. package/src/cli/adapters/vercel.ts +0 -311
  322. package/src/cli/commands/adapt.ts +0 -84
  323. package/src/cli/commands/build.ts +0 -165
  324. package/src/cli/commands/dev.ts +0 -125
  325. package/src/cli/commands/generate.ts +0 -91
  326. package/src/cli/commands/preview-isr.ts +0 -172
  327. package/src/cli/commands/preview.ts +0 -523
  328. package/src/cli/create/index.ts +0 -275
  329. package/src/cli/create/templates/shared/.gitignore.tpl +0 -25
  330. package/src/cli/create/templates/shared/app/layouts/default.ts.tpl +0 -15
  331. package/src/cli/create/templates/shared/app/pages/index.ts.tpl +0 -8
  332. package/src/cli/create/templates/shared/index.html.tpl +0 -12
  333. package/src/cli/create/templates/shared/tsconfig.json.tpl +0 -6
  334. package/src/cli/create/templates/spa/cer.config.ts.tpl +0 -6
  335. package/src/cli/create/templates/spa/package.json.tpl +0 -18
  336. package/src/cli/create/templates/ssg/cer.config.ts.tpl +0 -10
  337. package/src/cli/create/templates/ssg/package.json.tpl +0 -19
  338. package/src/cli/create/templates/ssr/cer.config.ts.tpl +0 -6
  339. package/src/cli/create/templates/ssr/package.json.tpl +0 -18
  340. package/src/cli/index.ts +0 -22
  341. package/src/index.ts +0 -20
  342. package/src/plugin/build-ssg.ts +0 -403
  343. package/src/plugin/build-ssr.ts +0 -134
  344. package/src/plugin/content/emitter.ts +0 -50
  345. package/src/plugin/content/index.ts +0 -233
  346. package/src/plugin/content/parser.ts +0 -259
  347. package/src/plugin/content/path-utils.ts +0 -55
  348. package/src/plugin/content/scanner.ts +0 -26
  349. package/src/plugin/content/search.ts +0 -28
  350. package/src/plugin/dev-server.ts +0 -379
  351. package/src/plugin/dts-generator.ts +0 -273
  352. package/src/plugin/generated-dir.ts +0 -121
  353. package/src/plugin/html-post-process.ts +0 -96
  354. package/src/plugin/index.ts +0 -584
  355. package/src/plugin/path-utils.ts +0 -186
  356. package/src/plugin/scanner.ts +0 -65
  357. package/src/plugin/transforms/auto-import.ts +0 -206
  358. package/src/plugin/transforms/head-inject.ts +0 -161
  359. package/src/plugin/virtual/composables.ts +0 -28
  360. package/src/plugin/virtual/content-components.ts +0 -83
  361. package/src/plugin/virtual/error.ts +0 -34
  362. package/src/plugin/virtual/layouts.ts +0 -41
  363. package/src/plugin/virtual/loading.ts +0 -32
  364. package/src/plugin/virtual/middleware.ts +0 -45
  365. package/src/plugin/virtual/plugins.ts +0 -46
  366. package/src/plugin/virtual/routes.ts +0 -514
  367. package/src/plugin/virtual/server-api.ts +0 -85
  368. package/src/plugin/virtual/server-middleware.ts +0 -44
  369. package/src/runtime/app-template.ts +0 -377
  370. package/src/runtime/composables/define-middleware.ts +0 -17
  371. package/src/runtime/composables/define-server-middleware.ts +0 -18
  372. package/src/runtime/composables/index.ts +0 -27
  373. package/src/runtime/composables/use-auth.ts +0 -125
  374. package/src/runtime/composables/use-content-search.ts +0 -205
  375. package/src/runtime/composables/use-content.ts +0 -146
  376. package/src/runtime/composables/use-cookie.ts +0 -135
  377. package/src/runtime/composables/use-fetch.ts +0 -438
  378. package/src/runtime/composables/use-head.ts +0 -234
  379. package/src/runtime/composables/use-inject.ts +0 -49
  380. package/src/runtime/composables/use-locale.ts +0 -109
  381. package/src/runtime/composables/use-navigate.ts +0 -58
  382. package/src/runtime/composables/use-page-data.ts +0 -93
  383. package/src/runtime/composables/use-route.ts +0 -71
  384. package/src/runtime/composables/use-runtime-config.ts +0 -123
  385. package/src/runtime/composables/use-seo-meta.ts +0 -80
  386. package/src/runtime/composables/use-session.ts +0 -186
  387. package/src/runtime/composables/use-state.ts +0 -92
  388. package/src/runtime/content/client.ts +0 -168
  389. package/src/runtime/entry-client-template.ts +0 -39
  390. package/src/runtime/entry-server-template.ts +0 -493
  391. package/src/runtime/isr-handler.ts +0 -184
  392. package/src/runtime/oauth/handler.ts +0 -407
  393. package/src/runtime/oauth/index.ts +0 -49
  394. package/src/runtime/oauth/providers.ts +0 -38
  395. package/src/types/api.ts +0 -40
  396. package/src/types/config.ts +0 -379
  397. package/src/types/content.ts +0 -66
  398. package/src/types/index.ts +0 -7
  399. package/src/types/middleware.ts +0 -63
  400. package/src/types/page.ts +0 -117
  401. package/src/types/plugin.ts +0 -34
  402. package/tsconfig.build.json +0 -10
  403. package/tsconfig.json +0 -19
  404. package/vitest.config.ts +0 -33
@@ -1,884 +0,0 @@
1
- import { vi, describe, it, expect, beforeEach } from 'vitest'
2
-
3
- vi.mock('node:fs', () => ({ existsSync: vi.fn() }))
4
- vi.mock('node:fs/promises', () => ({ readFile: vi.fn().mockResolvedValue('') }))
5
- vi.mock('../../../plugin/scanner.js', () => ({ scanDirectory: vi.fn() }))
6
-
7
- import { existsSync } from 'node:fs'
8
- import { readFile } from 'node:fs/promises'
9
- import { scanDirectory } from '../../../plugin/scanner.js'
10
- import { generateRoutesCode } from '../../../plugin/virtual/routes.js'
11
-
12
- const PAGES = '/project/app/pages'
13
-
14
- describe('generateRoutesCode', () => {
15
- beforeEach(() => {
16
- vi.mocked(existsSync).mockReturnValue(true)
17
- vi.mocked(scanDirectory).mockResolvedValue([])
18
- vi.mocked(readFile).mockResolvedValue('' as never)
19
- })
20
-
21
- it('returns empty routes when directory does not exist', async () => {
22
- vi.mocked(existsSync).mockReturnValue(false)
23
- const code = await generateRoutesCode(PAGES)
24
- expect(code).toContain('const routes = []')
25
- expect(code).toContain('export default routes')
26
- })
27
-
28
- it('returns empty routes when no files found', async () => {
29
- const code = await generateRoutesCode(PAGES)
30
- expect(code).toContain('const routes = []')
31
- expect(code).toContain('export default routes')
32
- })
33
-
34
- it('includes AUTO-GENERATED comment', async () => {
35
- const code = await generateRoutesCode(PAGES)
36
- expect(code).toContain('AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app')
37
- })
38
-
39
- it('generates a lazy load() route for a static page', async () => {
40
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
41
- const code = await generateRoutesCode(PAGES)
42
- expect(code).toContain('path: \"/about\"')
43
- expect(code).toContain('load:')
44
- expect(code).toContain(`import("${PAGES}/about.ts")`)
45
- expect(code).toContain('"page-about"')
46
- // No eager static imports
47
- expect(code).not.toContain('import * as')
48
- })
49
-
50
- it('load() also forwards the page module loader export', async () => {
51
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
52
- const code = await generateRoutesCode(PAGES)
53
- expect(code).toContain('loader: mod.loader')
54
- })
55
-
56
- it('generates a lazy load() route for index.ts → /', async () => {
57
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/index.ts`])
58
- const code = await generateRoutesCode(PAGES)
59
- expect(code).toContain('path: \"/\"')
60
- expect(code).toContain('load:')
61
- expect(code).toContain('"page-index"')
62
- })
63
-
64
- it('generates a lazy load() route for a dynamic route', async () => {
65
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/blog/[slug].ts`])
66
- const code = await generateRoutesCode(PAGES)
67
- expect(code).toContain('path: \"/blog/:slug\"')
68
- expect(code).toContain('load:')
69
- expect(code).toContain('"page-blog-slug"')
70
- })
71
-
72
- it('generates a lazy load() route for a catch-all route', async () => {
73
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/[...all].ts`])
74
- const code = await generateRoutesCode(PAGES)
75
- expect(code).toContain('path: \"/:all*\"')
76
- expect(code).toContain('load:')
77
- expect(code).toContain('"page-all"')
78
- })
79
-
80
- it('sorts routes: static before dynamic before catch-all', async () => {
81
- vi.mocked(scanDirectory).mockResolvedValue([
82
- `${PAGES}/[...all].ts`,
83
- `${PAGES}/[id].ts`,
84
- `${PAGES}/about.ts`,
85
- ])
86
- const code = await generateRoutesCode(PAGES)
87
- const aboutIdx = code.indexOf('\"/about\"')
88
- const idIdx = code.indexOf('\"/:id\"')
89
- const allIdx = code.indexOf('\"/:all*\"')
90
- expect(aboutIdx).toBeLessThan(idIdx)
91
- expect(idIdx).toBeLessThan(allIdx)
92
- })
93
-
94
- it('includes export default routes', async () => {
95
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
96
- const code = await generateRoutesCode(PAGES)
97
- expect(code).toContain('export default routes')
98
- })
99
-
100
- it('generates multiple lazy routes', async () => {
101
- vi.mocked(scanDirectory).mockResolvedValue([
102
- `${PAGES}/index.ts`,
103
- `${PAGES}/about.ts`,
104
- `${PAGES}/contact.ts`,
105
- ])
106
- const code = await generateRoutesCode(PAGES)
107
- expect(code).toContain('\"/about\"')
108
- expect(code).toContain('\"/contact\"')
109
- expect(code).toContain('\"/\"')
110
- expect(code).not.toContain('import * as')
111
- })
112
-
113
- it('inlines middleware names and adds beforeEnter when middleware is present', async () => {
114
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/dashboard.ts`])
115
- vi.mocked(readFile).mockResolvedValue(
116
- `component('page-dashboard', () => html\`<div>Dashboard</div>\`)\nexport const meta = { middleware: ['auth'] }` as never,
117
- )
118
- const code = await generateRoutesCode(PAGES)
119
- expect(code).toContain('beforeEnter')
120
- expect(code).toContain('"auth"')
121
- expect(code).toContain('virtual:cer-middleware')
122
- })
123
-
124
- it('omits beforeEnter when no middleware is declared', async () => {
125
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
126
- vi.mocked(readFile).mockResolvedValue(
127
- `component('page-about', () => html\`<h1>About</h1>\`)\nexport const meta = { layout: 'default' }` as never,
128
- )
129
- const code = await generateRoutesCode(PAGES)
130
- expect(code).not.toContain('beforeEnter')
131
- })
132
-
133
- it('uses return-value API (await handler) not callback-style (new Promise)', async () => {
134
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/dashboard.ts`])
135
- vi.mocked(readFile).mockResolvedValue(
136
- `component('page-dashboard', () => html\`<div/>\`)\nexport const meta = { middleware: ['auth'] }` as never,
137
- )
138
- const code = await generateRoutesCode(PAGES)
139
- expect(code).toContain('await handler(to, from, next)')
140
- expect(code).not.toContain('new Promise')
141
- })
142
-
143
- it('passes a next() function as third arg to each middleware handler', async () => {
144
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/dashboard.ts`])
145
- vi.mocked(readFile).mockResolvedValue(
146
- `component('page-dashboard', () => html\`<div/>\`)\nexport const meta = { middleware: ['auth'] }` as never,
147
- )
148
- const code = await generateRoutesCode(PAGES)
149
- expect(code).toContain('const next = async () =>')
150
- expect(code).toContain('_calledNext = true')
151
- })
152
-
153
- it('runs downstream middleware when next() is called', async () => {
154
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/dashboard.ts`])
155
- vi.mocked(readFile).mockResolvedValue(
156
- `component('page-dashboard', () => html\`<div/>\`)\nexport const meta = { middleware: ['logger', 'auth'] }` as never,
157
- )
158
- const code = await generateRoutesCode(PAGES)
159
- expect(code).toContain('_runNext')
160
- expect(code).toContain('"logger"')
161
- expect(code).toContain('"auth"')
162
- })
163
-
164
- it('wraps handler call in try-catch (blocks navigation on error)', async () => {
165
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/dashboard.ts`])
166
- vi.mocked(readFile).mockResolvedValue(
167
- `component('page-dashboard', () => html\`<div/>\`)\nexport const meta = { middleware: ['auth'] }` as never,
168
- )
169
- const code = await generateRoutesCode(PAGES)
170
- expect(code).toContain('try {')
171
- expect(code).toContain('} catch (err) {')
172
- // New chain pattern: sets _guardResult = false and returns from the inner _runNext
173
- expect(code).toContain('_guardResult = false')
174
- })
175
-
176
- it('logs the middleware name in the error message', async () => {
177
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/dashboard.ts`])
178
- vi.mocked(readFile).mockResolvedValue(
179
- `component('page-dashboard', () => html\`<div/>\`)\nexport const meta = { middleware: ['auth'] }` as never,
180
- )
181
- const code = await generateRoutesCode(PAGES)
182
- expect(code).toContain('console.error')
183
- expect(code).toContain('Middleware')
184
- expect(code).toContain('err)')
185
- })
186
- })
187
-
188
- // ─── meta.layout ─────────────────────────────────────────────────────────────
189
-
190
- describe('generateRoutesCode — meta.layout', () => {
191
- beforeEach(() => {
192
- // Return true for directory existence checks but false for .error.ts and
193
- // _error.ts so resolveRouteErrorTag doesn't inject spurious errorTag fields
194
- // into routes that aren't testing error boundaries.
195
- vi.mocked(existsSync).mockImplementation((p: unknown) => {
196
- const path = String(p)
197
- if (path.endsWith('.error.ts') || path.endsWith('/_error.ts') || path.endsWith('\\_error.ts')) return false
198
- return true
199
- })
200
- vi.mocked(scanDirectory).mockResolvedValue([])
201
- vi.mocked(readFile).mockResolvedValue('' as never)
202
- })
203
-
204
- it('emits meta.layout when layout is declared in page source', async () => {
205
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
206
- vi.mocked(readFile).mockResolvedValue(
207
- `component('page-about', () => html\`<h1>About</h1>\`)\nexport const meta = { layout: 'minimal' }` as never,
208
- )
209
- const code = await generateRoutesCode(PAGES)
210
- expect(code).toContain('meta: { layout: "minimal" }')
211
- })
212
-
213
- it('omits meta field when no layout is declared', async () => {
214
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
215
- vi.mocked(readFile).mockResolvedValue(
216
- `component('page-about', () => html\`<h1>About</h1>\`)` as never,
217
- )
218
- const code = await generateRoutesCode(PAGES)
219
- expect(code).toContain('path: "/about"')
220
- expect(code).not.toContain('path: "/about",\n load: () => import("/project/app/pages/about.ts").then(mod => ({ default: "page-about", loader: mod.loader ?? null })),\n meta:')
221
- })
222
-
223
- it('emits meta.layout alongside beforeEnter when both layout and middleware are present', async () => {
224
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/dashboard.ts`])
225
- vi.mocked(readFile).mockResolvedValue(
226
- `component('page-dashboard', () => html\`<div/>\`)\nexport const meta = { layout: 'admin', middleware: ['auth'] }` as never,
227
- )
228
- const code = await generateRoutesCode(PAGES)
229
- expect(code).toContain('meta: { layout: "admin" }')
230
- expect(code).toContain('beforeEnter')
231
- expect(code).toContain('"auth"')
232
- })
233
- })
234
-
235
- // ─── 404 page convention ──────────────────────────────────────────────────────
236
-
237
- describe('generateRoutesCode — 404.ts convention', () => {
238
- beforeEach(() => {
239
- vi.mocked(existsSync).mockReturnValue(true)
240
- vi.mocked(scanDirectory).mockResolvedValue([])
241
- vi.mocked(readFile).mockResolvedValue('' as never)
242
- })
243
-
244
- it('maps 404.ts to path /:all* (catch-all)', async () => {
245
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/404.ts`])
246
- const code = await generateRoutesCode(PAGES)
247
- expect(code).toContain('path: \"/:all*\"')
248
- expect(code).toContain('"page-404"')
249
- expect(code).toContain('_cerNotFound: true')
250
- })
251
-
252
- it('sorts 404.ts catch-all after all other routes', async () => {
253
- vi.mocked(scanDirectory).mockResolvedValue([
254
- `${PAGES}/404.ts`,
255
- `${PAGES}/about.ts`,
256
- `${PAGES}/[id].ts`,
257
- ])
258
- const code = await generateRoutesCode(PAGES)
259
- const aboutIdx = code.indexOf('\"/about\"')
260
- const idIdx = code.indexOf('\"/:id\"')
261
- const allIdx = code.indexOf('\"/:all*\"')
262
- expect(aboutIdx).toBeLessThan(idIdx)
263
- expect(idIdx).toBeLessThan(allIdx)
264
- })
265
- })
266
-
267
- // ─── _layout.ts filtering ─────────────────────────────────────────────────────
268
-
269
- describe('generateRoutesCode — _layout.ts filtering', () => {
270
- beforeEach(() => {
271
- vi.mocked(existsSync).mockReturnValue(true)
272
- vi.mocked(scanDirectory).mockResolvedValue([])
273
- vi.mocked(readFile).mockResolvedValue('' as never)
274
- })
275
-
276
- it('excludes _layout.ts files from the generated routes', async () => {
277
- vi.mocked(scanDirectory).mockResolvedValue([
278
- `${PAGES}/admin/index.ts`,
279
- `${PAGES}/admin/_layout.ts`,
280
- ])
281
- const code = await generateRoutesCode(PAGES)
282
- expect(code).not.toContain('_layout')
283
- expect(code).toContain('/admin')
284
- })
285
-
286
- it('generates a route for non-underscore files in the same directory', async () => {
287
- vi.mocked(scanDirectory).mockResolvedValue([
288
- `${PAGES}/admin/users.ts`,
289
- `${PAGES}/admin/_layout.ts`,
290
- ])
291
- const code = await generateRoutesCode(PAGES)
292
- expect(code).toContain('/admin/users')
293
- expect(code).not.toContain('_layout')
294
- })
295
- })
296
-
297
- // ─── meta.revalidate ─────────────────────────────────────────────────────────
298
-
299
- describe('generateRoutesCode — meta.ssg.revalidate', () => {
300
- beforeEach(() => {
301
- vi.mocked(existsSync).mockReturnValue(true)
302
- vi.mocked(scanDirectory).mockResolvedValue([])
303
- vi.mocked(readFile).mockResolvedValue('' as never)
304
- })
305
-
306
- it('emits meta.ssg.revalidate when declared in page source', async () => {
307
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/blog.ts`])
308
- vi.mocked(readFile).mockResolvedValue(
309
- `export const meta = { ssg: { revalidate: 60 } }` as never,
310
- )
311
- const code = await generateRoutesCode(PAGES)
312
- expect(code).toContain('ssg: { revalidate: 60 }')
313
- })
314
-
315
- it('emits larger revalidate values correctly', async () => {
316
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/products.ts`])
317
- vi.mocked(readFile).mockResolvedValue(
318
- `export const meta = { ssg: { revalidate: 3600 } }` as never,
319
- )
320
- const code = await generateRoutesCode(PAGES)
321
- expect(code).toContain('ssg: { revalidate: 3600 }')
322
- })
323
-
324
- it('omits ssg meta when no revalidate is declared', async () => {
325
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
326
- vi.mocked(readFile).mockResolvedValue(
327
- `component('page-about', () => html\`<h1>About</h1>\`)` as never,
328
- )
329
- const code = await generateRoutesCode(PAGES)
330
- expect(code).not.toContain('revalidate')
331
- })
332
-
333
- it('can combine revalidate with layout in meta', async () => {
334
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/blog.ts`])
335
- vi.mocked(readFile).mockResolvedValue(
336
- `export const meta = { layout: 'minimal', ssg: { revalidate: 120 } }` as never,
337
- )
338
- const code = await generateRoutesCode(PAGES)
339
- expect(code).toContain('layout: "minimal"')
340
- expect(code).toContain('ssg: { revalidate: 120 }')
341
- })
342
- })
343
-
344
- // ─── meta.transition ─────────────────────────────────────────────────────────
345
-
346
- describe('generateRoutesCode — meta.transition', () => {
347
- beforeEach(() => {
348
- vi.mocked(existsSync).mockReturnValue(true)
349
- vi.mocked(scanDirectory).mockResolvedValue([])
350
- vi.mocked(readFile).mockResolvedValue('' as never)
351
- })
352
-
353
- it('emits meta.transition string when declared', async () => {
354
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
355
- vi.mocked(readFile).mockResolvedValue(
356
- `export const meta = { transition: 'fade' }` as never,
357
- )
358
- const code = await generateRoutesCode(PAGES)
359
- expect(code).toContain('transition: "fade"')
360
- })
361
-
362
- it('emits meta.transition true (boolean) when declared', async () => {
363
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
364
- vi.mocked(readFile).mockResolvedValue(
365
- `export const meta = { transition: true }` as never,
366
- )
367
- const code = await generateRoutesCode(PAGES)
368
- expect(code).toContain('transition: true')
369
- })
370
-
371
- it('omits transition meta when not declared', async () => {
372
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
373
- vi.mocked(readFile).mockResolvedValue(
374
- `component('page-about', () => html\`<h1>About</h1>\`)` as never,
375
- )
376
- const code = await generateRoutesCode(PAGES)
377
- expect(code).not.toContain('transition')
378
- })
379
-
380
- it('emits meta.transition false (boolean) when declared', async () => {
381
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
382
- vi.mocked(readFile).mockResolvedValue(
383
- `export const meta = { transition: false }` as never,
384
- )
385
- const code = await generateRoutesCode(PAGES)
386
- expect(code).toContain('transition: false')
387
- })
388
-
389
- it('can combine transition with middleware', async () => {
390
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/dashboard.ts`])
391
- vi.mocked(readFile).mockResolvedValue(
392
- `export const meta = { transition: 'slide', middleware: ['auth'] }` as never,
393
- )
394
- const code = await generateRoutesCode(PAGES)
395
- expect(code).toContain('transition: "slide"')
396
- expect(code).toContain('beforeEnter')
397
- expect(code).toContain('"auth"')
398
- })
399
- })
400
-
401
- // ─── meta.render ──────────────────────────────────────────────────────────────
402
-
403
- describe('generateRoutesCode — meta.render', () => {
404
- beforeEach(() => {
405
- vi.mocked(existsSync).mockReturnValue(true)
406
- vi.mocked(scanDirectory).mockResolvedValue([])
407
- vi.mocked(readFile).mockResolvedValue('' as never)
408
- })
409
-
410
- it('emits meta.render "server" when declared', async () => {
411
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/dashboard.ts`])
412
- vi.mocked(readFile).mockResolvedValue(
413
- `export const meta = { render: 'server' }` as never,
414
- )
415
- const code = await generateRoutesCode(PAGES)
416
- expect(code).toContain('render: "server"')
417
- })
418
-
419
- it('emits meta.render "spa" when declared', async () => {
420
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/profile.ts`])
421
- vi.mocked(readFile).mockResolvedValue(
422
- `export const meta = { render: 'spa' }` as never,
423
- )
424
- const code = await generateRoutesCode(PAGES)
425
- expect(code).toContain('render: "spa"')
426
- })
427
-
428
- it('emits meta.render "static" when declared', async () => {
429
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
430
- vi.mocked(readFile).mockResolvedValue(
431
- `export const meta = { render: 'static' }` as never,
432
- )
433
- const code = await generateRoutesCode(PAGES)
434
- expect(code).toContain('render: "static"')
435
- })
436
-
437
- it('omits render meta when not declared', async () => {
438
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
439
- vi.mocked(readFile).mockResolvedValue(
440
- `component('page-about', () => html\`<h1>About</h1>\`)` as never,
441
- )
442
- const code = await generateRoutesCode(PAGES)
443
- expect(code).not.toContain('render:')
444
- })
445
-
446
- it('ignores unknown render values', async () => {
447
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
448
- vi.mocked(readFile).mockResolvedValue(
449
- `export const meta = { render: 'unknown' }` as never,
450
- )
451
- const code = await generateRoutesCode(PAGES)
452
- expect(code).not.toContain('render:')
453
- })
454
-
455
- it('can combine render with layout', async () => {
456
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/dashboard.ts`])
457
- vi.mocked(readFile).mockResolvedValue(
458
- `export const meta = { render: 'server', layout: 'admin' }` as never,
459
- )
460
- const code = await generateRoutesCode(PAGES)
461
- expect(code).toContain('render: "server"')
462
- expect(code).toContain('layout: "admin"')
463
- })
464
- })
465
-
466
- // ─── meta.hydrate ─────────────────────────────────────────────────────────────
467
-
468
- describe('generateRoutesCode — meta.hydrate', () => {
469
- beforeEach(() => {
470
- vi.mocked(existsSync).mockReturnValue(true)
471
- vi.mocked(scanDirectory).mockResolvedValue([])
472
- vi.mocked(readFile).mockResolvedValue('' as never)
473
- })
474
-
475
- it('emits meta.hydrate "idle" when declared', async () => {
476
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
477
- vi.mocked(readFile).mockResolvedValue(
478
- `export const meta = { hydrate: 'idle' }` as never,
479
- )
480
- const code = await generateRoutesCode(PAGES)
481
- expect(code).toContain('hydrate: "idle"')
482
- })
483
-
484
- it('emits meta.hydrate "visible" when declared', async () => {
485
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/hero.ts`])
486
- vi.mocked(readFile).mockResolvedValue(
487
- `export const meta = { hydrate: 'visible' }` as never,
488
- )
489
- const code = await generateRoutesCode(PAGES)
490
- expect(code).toContain('hydrate: "visible"')
491
- })
492
-
493
- it('emits meta.hydrate "none" when declared', async () => {
494
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/static-page.ts`])
495
- vi.mocked(readFile).mockResolvedValue(
496
- `export const meta = { hydrate: 'none' }` as never,
497
- )
498
- const code = await generateRoutesCode(PAGES)
499
- expect(code).toContain('hydrate: "none"')
500
- })
501
-
502
- it('does NOT emit meta.hydrate when "load" (default — no-op)', async () => {
503
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
504
- vi.mocked(readFile).mockResolvedValue(
505
- `export const meta = { hydrate: 'load' }` as never,
506
- )
507
- const code = await generateRoutesCode(PAGES)
508
- expect(code).not.toContain('hydrate:')
509
- })
510
-
511
- it('omits hydrate meta when not declared', async () => {
512
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
513
- vi.mocked(readFile).mockResolvedValue(
514
- `component('page-about', () => html\`<h1>About</h1>\`)` as never,
515
- )
516
- const code = await generateRoutesCode(PAGES)
517
- expect(code).not.toContain('hydrate:')
518
- })
519
-
520
- it('ignores unknown hydrate values', async () => {
521
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
522
- vi.mocked(readFile).mockResolvedValue(
523
- `export const meta = { hydrate: 'lazy' }` as never,
524
- )
525
- const code = await generateRoutesCode(PAGES)
526
- expect(code).not.toContain('hydrate:')
527
- })
528
-
529
- it('can combine hydrate with layout and render', async () => {
530
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/dashboard.ts`])
531
- vi.mocked(readFile).mockResolvedValue(
532
- `export const meta = { hydrate: 'idle', layout: 'minimal', render: 'server' }` as never,
533
- )
534
- const code = await generateRoutesCode(PAGES)
535
- expect(code).toContain('hydrate: "idle"')
536
- expect(code).toContain('layout: "minimal"')
537
- expect(code).toContain('render: "server"')
538
- })
539
- })
540
-
541
- // ─── meta.title ───────────────────────────────────────────────────────────────
542
-
543
- describe('generateRoutesCode — meta.title', () => {
544
- beforeEach(() => {
545
- vi.mocked(existsSync).mockReturnValue(true)
546
- vi.mocked(scanDirectory).mockResolvedValue([])
547
- vi.mocked(readFile).mockResolvedValue('' as never)
548
- })
549
-
550
- it('emits meta.title when declared in page source', async () => {
551
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
552
- vi.mocked(readFile).mockResolvedValue(
553
- `export const meta = { title: 'About Us' }` as never,
554
- )
555
- const code = await generateRoutesCode(PAGES)
556
- expect(code).toContain('title: "About Us"')
557
- })
558
-
559
- it('omits title meta when not declared', async () => {
560
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
561
- vi.mocked(readFile).mockResolvedValue(
562
- `component('page-about', () => html\`<h1>About</h1>\`)` as never,
563
- )
564
- const code = await generateRoutesCode(PAGES)
565
- expect(code).not.toContain('title:')
566
- })
567
-
568
- it('can combine title with layout', async () => {
569
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
570
- vi.mocked(readFile).mockResolvedValue(
571
- `export const meta = { layout: 'minimal', title: 'About Us' }` as never,
572
- )
573
- const code = await generateRoutesCode(PAGES)
574
- expect(code).toContain('layout: "minimal"')
575
- expect(code).toContain('title: "About Us"')
576
- })
577
- })
578
-
579
- // ─── nested layouts (layoutChain) ────────────────────────────────────────────
580
-
581
- describe('generateRoutesCode — layoutChain (nested layouts)', () => {
582
- beforeEach(() => {
583
- vi.mocked(existsSync).mockReturnValue(true)
584
- vi.mocked(scanDirectory).mockResolvedValue([])
585
- vi.mocked(readFile).mockResolvedValue('' as never)
586
- })
587
-
588
- it('emits meta.layoutChain when a _layout.ts exists in ancestor directory', async () => {
589
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/admin/users.ts`])
590
- vi.mocked(readFile).mockImplementation(async (path: unknown) => {
591
- if (String(path).endsWith('_layout.ts')) return 'export default \'minimal\''
592
- return ''
593
- })
594
- const code = await generateRoutesCode(PAGES)
595
- expect(code).toContain('layoutChain:')
596
- expect(code).toContain('"default"')
597
- expect(code).toContain('"minimal"')
598
- })
599
-
600
- it('uses outerLayout from page meta when layoutChain is emitted', async () => {
601
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/admin/users.ts`])
602
- vi.mocked(readFile).mockImplementation(async (path: unknown) => {
603
- const p = String(path)
604
- if (p.endsWith('_layout.ts')) return 'export default \'sidebar\''
605
- // page source has its own layout
606
- return `export const meta = { layout: 'clean' }`
607
- })
608
- const code = await generateRoutesCode(PAGES)
609
- expect(code).toContain('layoutChain:')
610
- expect(code).toContain('"clean"')
611
- expect(code).toContain('"sidebar"')
612
- // Should not emit a plain meta.layout — the chain replaces it
613
- expect(code).not.toContain('layout: "clean"')
614
- })
615
-
616
- it('omits layoutChain for top-level pages with no _layout.ts', async () => {
617
- vi.mocked(existsSync).mockImplementation((p: unknown) => {
618
- // Return false for any _layout.ts path so no chain is resolved
619
- if (String(p).endsWith('_layout.ts')) return false
620
- return true
621
- })
622
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
623
- vi.mocked(readFile).mockResolvedValue(
624
- `export const meta = { layout: 'default' }` as never,
625
- )
626
- const code = await generateRoutesCode(PAGES)
627
- expect(code).not.toContain('layoutChain')
628
- expect(code).toContain('layout: "default"')
629
- })
630
-
631
- it('omits layoutChain for top-level pages even when existsSync returns true', async () => {
632
- // top-level page has no subdirectory segments, so resolveLayoutChain returns null immediately
633
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/index.ts`])
634
- vi.mocked(readFile).mockResolvedValue('' as never)
635
- const code = await generateRoutesCode(PAGES)
636
- expect(code).not.toContain('layoutChain')
637
- })
638
- })
639
-
640
- // ─── i18n route prefixing ─────────────────────────────────────────────────────
641
-
642
- describe('generateRoutesCode — i18n route prefixing', () => {
643
- const i18nPrefix = { locales: ['en', 'fr'], defaultLocale: 'en', strategy: 'prefix' as const }
644
- const i18nPrefixExcept = { locales: ['en', 'fr'], defaultLocale: 'en', strategy: 'prefix_except_default' as const }
645
- const i18nNoPrefix = { locales: ['en', 'fr'], defaultLocale: 'en', strategy: 'no_prefix' as const }
646
-
647
- beforeEach(() => {
648
- vi.mocked(existsSync).mockReturnValue(true)
649
- vi.mocked(scanDirectory).mockResolvedValue([])
650
- vi.mocked(readFile).mockResolvedValue('' as never)
651
- })
652
-
653
- it('prefix strategy: generates /en/about and /fr/about for about.ts', async () => {
654
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
655
- const code = await generateRoutesCode(PAGES, i18nPrefix)
656
- expect(code).toContain('"/en/about"')
657
- expect(code).toContain('"/fr/about"')
658
- expect(code).not.toContain('path: "/about"')
659
- })
660
-
661
- it('prefix_except_default strategy: generates /about and /fr/about for about.ts', async () => {
662
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
663
- const code = await generateRoutesCode(PAGES, i18nPrefixExcept)
664
- expect(code).toContain('"/about"')
665
- expect(code).toContain('"/fr/about"')
666
- expect(code).not.toContain('"/en/about"')
667
- })
668
-
669
- it('no_prefix strategy: does not add locale prefixes', async () => {
670
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
671
- const code = await generateRoutesCode(PAGES, i18nNoPrefix)
672
- expect(code).toContain('"/about"')
673
- expect(code).not.toContain('"/en/about"')
674
- expect(code).not.toContain('"/fr/about"')
675
- })
676
-
677
- it('catch-all routes are never prefixed', async () => {
678
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/404.ts`])
679
- const code = await generateRoutesCode(PAGES, i18nPrefix)
680
- expect(code).toContain('"/:all*"')
681
- expect(code).not.toContain('"/en/:all*"')
682
- expect(code).not.toContain('"/fr/:all*"')
683
- })
684
-
685
- it('emits meta.locale for each generated locale route', async () => {
686
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
687
- const code = await generateRoutesCode(PAGES, i18nPrefix)
688
- expect(code).toContain('locale: "en"')
689
- expect(code).toContain('locale: "fr"')
690
- })
691
-
692
- it('without i18n config, generates a single route as before', async () => {
693
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
694
- const code = await generateRoutesCode(PAGES)
695
- expect(code).toContain('"/about"')
696
- expect(code).not.toContain('"/en/about"')
697
- expect(code).not.toContain('locale:')
698
- })
699
- })
700
-
701
- // ─── P1-1: Synthetic 404 catch-all ───────────────────────────────────────────
702
-
703
- describe('generateRoutesCode — synthetic 404 catch-all (P1-1)', () => {
704
- beforeEach(() => {
705
- vi.mocked(existsSync).mockReturnValue(true)
706
- vi.mocked(scanDirectory).mockResolvedValue([])
707
- vi.mocked(readFile).mockResolvedValue('' as never)
708
- })
709
-
710
- it('adds a synthetic /:all* route when no user-defined catch-all exists', async () => {
711
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
712
- const code = await generateRoutesCode(PAGES)
713
- // Synthetic route uses single quotes in the generated code
714
- expect(code).toContain("path: '/:all*'")
715
- // Synthetic route returns default: null
716
- expect(code).toContain('default: null')
717
- expect(code).toContain('meta: { _cerNotFound: true }')
718
- })
719
-
720
- it('does NOT add a synthetic catch-all when a user 404.ts already exists', async () => {
721
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/404.ts`, `${PAGES}/about.ts`])
722
- const code = await generateRoutesCode(PAGES)
723
- // Should have exactly one /:all* (the real 404 page, with double quotes)
724
- expect(code).toContain('"/:all*"')
725
- expect(code).not.toContain('default: null')
726
- })
727
-
728
- it('does NOT add a synthetic catch-all when a user [...all].ts already exists', async () => {
729
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/[...all].ts`])
730
- const code = await generateRoutesCode(PAGES)
731
- // Real catch-all uses double quotes
732
- expect(code).toContain('"/:all*"')
733
- expect(code).not.toContain('default: null')
734
- expect(code).not.toContain('_cerNotFound: true')
735
- })
736
-
737
- it('does not add a synthetic catch-all for an empty pages directory (no routes)', async () => {
738
- const code = await generateRoutesCode(PAGES)
739
- // Empty routes array — no synthetic catch-all needed when there are no routes
740
- expect(code).toContain('const routes = []')
741
- expect(code).not.toContain("path: '/:all*'")
742
- })
743
- })
744
-
745
- // ─── hydration middleware replay skip ───────────────────────────────────────
746
-
747
- describe('generateRoutesCode — hydration middleware replay skip', () => {
748
- beforeEach(() => {
749
- vi.mocked(existsSync).mockReturnValue(true)
750
- vi.mocked(scanDirectory).mockResolvedValue([])
751
- vi.mocked(readFile).mockResolvedValue('' as never)
752
- })
753
-
754
- it('emits a one-shot hydration replay skip before importing middleware', async () => {
755
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/dashboard.ts`])
756
- vi.mocked(readFile).mockResolvedValue(
757
- `export const meta = { middleware: ['auth'] }` as never,
758
- )
759
- const code = await generateRoutesCode(PAGES)
760
- expect(code).toContain(`const _hydrationEntry = (globalThis).__CER_HYDRATION_ENTRY__`)
761
- expect(code).toContain(`from.path === to.path`)
762
- expect(code).toContain(`_hydrationEntry.path === to.path`)
763
- expect(code).toContain(`delete (globalThis).__CER_HYDRATION_ENTRY__`)
764
- const hydrationIdx = code.indexOf(`const _hydrationEntry = (globalThis).__CER_HYDRATION_ENTRY__`)
765
- const importIdx = code.indexOf(`const { middleware } = await import('virtual:cer-middleware')`)
766
- expect(hydrationIdx).toBeGreaterThanOrEqual(0)
767
- expect(importIdx).toBeGreaterThan(hydrationIdx)
768
- })
769
- })
770
-
771
- // ─── P2-1: _layout.ts group meta inheritance ─────────────────────────────────
772
-
773
- describe('generateRoutesCode — group meta from _layout.ts (P2-1)', () => {
774
- beforeEach(() => {
775
- vi.mocked(existsSync).mockReturnValue(true)
776
- vi.mocked(scanDirectory).mockResolvedValue([])
777
- vi.mocked(readFile).mockResolvedValue('' as never)
778
- })
779
-
780
- it('inherits middleware from ancestor _layout.ts when page has none', async () => {
781
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/admin/dashboard.ts`])
782
- vi.mocked(readFile).mockImplementation(async (path: unknown) => {
783
- if (String(path).endsWith('_layout.ts')) {
784
- return `export const meta = { middleware: ['auth'] }`
785
- }
786
- return ''
787
- })
788
- const code = await generateRoutesCode(PAGES)
789
- expect(code).toContain('beforeEnter')
790
- expect(code).toContain('"auth"')
791
- })
792
-
793
- it('page-level middleware overrides group middleware from _layout.ts', async () => {
794
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/admin/profile.ts`])
795
- vi.mocked(readFile).mockImplementation(async (path: unknown) => {
796
- if (String(path).endsWith('_layout.ts')) {
797
- return `export const meta = { middleware: ['auth'] }`
798
- }
799
- // page declares its own middleware
800
- return `export const meta = { middleware: ['verified'] }`
801
- })
802
- const code = await generateRoutesCode(PAGES)
803
- expect(code).toContain('"verified"')
804
- // Page overrides group — group middleware should NOT appear
805
- expect(code).not.toContain('"auth"')
806
- })
807
-
808
- it('inherits layout from ancestor _layout.ts meta.layout when page has no explicit layout', async () => {
809
- vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/docs/intro.ts`])
810
- vi.mocked(readFile).mockImplementation(async (path: unknown) => {
811
- if (String(path).endsWith('_layout.ts')) {
812
- // _layout.ts exports meta.layout (group default layout)
813
- return `export const meta = { layout: 'docs' }`
814
- }
815
- return ''
816
- })
817
- const code = await generateRoutesCode(PAGES)
818
- // The layout from group meta should appear in the route
819
- expect(code).toContain('"docs"')
820
- })
821
- })
822
-
823
- // ─── P2-2: Per-route error tags ───────────────────────────────────────────────
824
-
825
- describe('generateRoutesCode — per-route error tags (P2-2)', () => {
826
- beforeEach(() => {
827
- vi.mocked(existsSync).mockReturnValue(true)
828
- vi.mocked(scanDirectory).mockResolvedValue([])
829
- vi.mocked(readFile).mockResolvedValue('' as never)
830
- })
831
-
832
- it('excludes *.error.ts files from generating their own routes', async () => {
833
- vi.mocked(scanDirectory).mockResolvedValue([
834
- `${PAGES}/about.ts`,
835
- `${PAGES}/about.error.ts`,
836
- ])
837
- const code = await generateRoutesCode(PAGES)
838
- // The about route should exist
839
- expect(code).toContain('"/about"')
840
- // The error file must NOT produce a separate route path like /about-error or /about.error
841
- expect(code).not.toContain('path: "/about-error"')
842
- expect(code).not.toContain('path: "/about.error"')
843
- })
844
-
845
- it('excludes _error.ts files from generated routes', async () => {
846
- vi.mocked(scanDirectory).mockResolvedValue([
847
- `${PAGES}/admin/users.ts`,
848
- `${PAGES}/admin/_error.ts`,
849
- ])
850
- const code = await generateRoutesCode(PAGES)
851
- expect(code).toContain('/admin/users')
852
- expect(code).not.toContain('_error')
853
- })
854
-
855
- it('load() includes co-located error file import when present', async () => {
856
- vi.mocked(scanDirectory).mockResolvedValue([
857
- `${PAGES}/dashboard.ts`,
858
- `${PAGES}/dashboard.error.ts`,
859
- ])
860
- vi.mocked(existsSync).mockImplementation((p: unknown) => {
861
- const path = String(p)
862
- // The co-located error file exists
863
- if (path.endsWith('dashboard.error.ts')) return true
864
- return true
865
- })
866
- const code = await generateRoutesCode(PAGES)
867
- // load() should import the error component alongside the page
868
- expect(code).toContain('dashboard.error.ts')
869
- expect(code).toContain('Promise.all')
870
- })
871
-
872
- it('emits meta.errorTag when co-located *.error.ts is present', async () => {
873
- vi.mocked(scanDirectory).mockResolvedValue([
874
- `${PAGES}/settings.ts`,
875
- `${PAGES}/settings.error.ts`,
876
- ])
877
- vi.mocked(existsSync).mockImplementation((p: unknown) => {
878
- if (String(p).endsWith('settings.error.ts')) return true
879
- return true
880
- })
881
- const code = await generateRoutesCode(PAGES)
882
- expect(code).toContain('errorTag')
883
- })
884
- })