@jasonshimmy/vite-plugin-cer-app 0.23.1 → 0.24.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 (397) hide show
  1. package/dist/cli/checks/lighthouse.d.ts +26 -0
  2. package/dist/cli/checks/lighthouse.d.ts.map +1 -0
  3. package/dist/cli/checks/lighthouse.js +121 -0
  4. package/dist/cli/checks/lighthouse.js.map +1 -0
  5. package/dist/cli/checks/links.d.ts +12 -0
  6. package/dist/cli/checks/links.d.ts.map +1 -0
  7. package/dist/cli/checks/links.js +89 -0
  8. package/dist/cli/checks/links.js.map +1 -0
  9. package/dist/cli/checks/performance.d.ts +17 -0
  10. package/dist/cli/checks/performance.d.ts.map +1 -0
  11. package/dist/cli/checks/performance.js +61 -0
  12. package/dist/cli/checks/performance.js.map +1 -0
  13. package/dist/cli/commands/check.d.ts +3 -0
  14. package/dist/cli/commands/check.d.ts.map +1 -0
  15. package/dist/cli/commands/check.js +85 -0
  16. package/dist/cli/commands/check.js.map +1 -0
  17. package/dist/cli/commands/preview.d.ts.map +1 -1
  18. package/dist/cli/commands/preview.js +37 -4
  19. package/dist/cli/commands/preview.js.map +1 -1
  20. package/dist/cli/create/index.d.ts +11 -1
  21. package/dist/cli/create/index.d.ts.map +1 -1
  22. package/dist/cli/create/index.js +135 -65
  23. package/dist/cli/create/index.js.map +1 -1
  24. package/dist/cli/create/templates/spa/package.json.tpl +5 -3
  25. package/dist/cli/create/templates/ssg/cer.config.ts.tpl +2 -0
  26. package/dist/cli/create/templates/ssg/package.json.tpl +5 -3
  27. package/dist/cli/create/templates/ssr/package.json.tpl +5 -3
  28. package/dist/cli/index.js +2 -0
  29. package/dist/cli/index.js.map +1 -1
  30. package/dist/index.d.ts +3 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/plugin/build-ssg.d.ts +22 -4
  33. package/dist/plugin/build-ssg.d.ts.map +1 -1
  34. package/dist/plugin/build-ssg.js +273 -13
  35. package/dist/plugin/build-ssg.js.map +1 -1
  36. package/dist/plugin/content/parser.d.ts.map +1 -1
  37. package/dist/plugin/content/parser.js +54 -10
  38. package/dist/plugin/content/parser.js.map +1 -1
  39. package/dist/plugin/dev-server.d.ts +6 -1
  40. package/dist/plugin/dev-server.d.ts.map +1 -1
  41. package/dist/plugin/dev-server.js.map +1 -1
  42. package/dist/plugin/dts-generator.d.ts.map +1 -1
  43. package/dist/plugin/dts-generator.js +50 -8
  44. package/dist/plugin/dts-generator.js.map +1 -1
  45. package/dist/plugin/generated-dir.d.ts +2 -0
  46. package/dist/plugin/generated-dir.d.ts.map +1 -1
  47. package/dist/plugin/generated-dir.js +17 -6
  48. package/dist/plugin/generated-dir.js.map +1 -1
  49. package/dist/plugin/html-post-process.d.ts +35 -0
  50. package/dist/plugin/html-post-process.d.ts.map +1 -0
  51. package/dist/plugin/html-post-process.js +112 -0
  52. package/dist/plugin/html-post-process.js.map +1 -0
  53. package/dist/plugin/index.d.ts +9 -0
  54. package/dist/plugin/index.d.ts.map +1 -1
  55. package/dist/plugin/index.js +89 -14
  56. package/dist/plugin/index.js.map +1 -1
  57. package/dist/plugin/transforms/auto-import.d.ts.map +1 -1
  58. package/dist/plugin/transforms/auto-import.js +7 -0
  59. package/dist/plugin/transforms/auto-import.js.map +1 -1
  60. package/dist/plugin/transforms/prune-runtime-css.d.ts +8 -0
  61. package/dist/plugin/transforms/prune-runtime-css.d.ts.map +1 -0
  62. package/dist/plugin/transforms/prune-runtime-css.js +40 -0
  63. package/dist/plugin/transforms/prune-runtime-css.js.map +1 -0
  64. package/dist/runtime/app-template.d.ts +1 -4
  65. package/dist/runtime/app-template.d.ts.map +1 -1
  66. package/dist/runtime/app-template.js +211 -40
  67. package/dist/runtime/app-template.js.map +1 -1
  68. package/dist/runtime/composables/index.d.ts +4 -0
  69. package/dist/runtime/composables/index.d.ts.map +1 -1
  70. package/dist/runtime/composables/index.js +2 -0
  71. package/dist/runtime/composables/index.js.map +1 -1
  72. package/dist/runtime/composables/use-active-headings.d.ts +24 -0
  73. package/dist/runtime/composables/use-active-headings.d.ts.map +1 -0
  74. package/dist/runtime/composables/use-active-headings.js +112 -0
  75. package/dist/runtime/composables/use-active-headings.js.map +1 -0
  76. package/dist/runtime/composables/use-content-page.d.ts +45 -0
  77. package/dist/runtime/composables/use-content-page.d.ts.map +1 -0
  78. package/dist/runtime/composables/use-content-page.js +179 -0
  79. package/dist/runtime/composables/use-content-page.js.map +1 -0
  80. package/dist/runtime/composables/use-content-search.d.ts.map +1 -1
  81. package/dist/runtime/composables/use-content-search.js +1 -5
  82. package/dist/runtime/composables/use-content-search.js.map +1 -1
  83. package/dist/runtime/composables/use-fetch.d.ts +2 -1
  84. package/dist/runtime/composables/use-fetch.d.ts.map +1 -1
  85. package/dist/runtime/composables/use-fetch.js +112 -43
  86. package/dist/runtime/composables/use-fetch.js.map +1 -1
  87. package/dist/runtime/composables/use-head.d.ts.map +1 -1
  88. package/dist/runtime/composables/use-head.js +30 -6
  89. package/dist/runtime/composables/use-head.js.map +1 -1
  90. package/dist/runtime/composables/use-route.d.ts.map +1 -1
  91. package/dist/runtime/composables/use-route.js +39 -11
  92. package/dist/runtime/composables/use-route.js.map +1 -1
  93. package/dist/runtime/content/client.js +2 -2
  94. package/dist/runtime/content/client.js.map +1 -1
  95. package/dist/runtime/entry-server-template.d.ts +16 -1
  96. package/dist/runtime/entry-server-template.d.ts.map +1 -1
  97. package/dist/runtime/entry-server-template.js +120 -13
  98. package/dist/runtime/entry-server-template.js.map +1 -1
  99. package/dist/types/config.d.ts +53 -4
  100. package/dist/types/config.d.ts.map +1 -1
  101. package/dist/types/config.js.map +1 -1
  102. package/dist/types/middleware.d.ts +2 -2
  103. package/dist/types/middleware.d.ts.map +1 -1
  104. package/package.json +22 -19
  105. package/.github/copilot-instructions.md +0 -169
  106. package/.github/workflows/publish.yml +0 -257
  107. package/.nvmrc +0 -1
  108. package/CHANGELOG.md +0 -232
  109. package/CLAUDE.md +0 -113
  110. package/commits.txt +0 -1
  111. package/cypress.config.ts +0 -16
  112. package/docs/authentication.md +0 -285
  113. package/docs/cli.md +0 -324
  114. package/docs/components.md +0 -237
  115. package/docs/composables.md +0 -907
  116. package/docs/configuration.md +0 -667
  117. package/docs/content.md +0 -570
  118. package/docs/data-loading.md +0 -336
  119. package/docs/getting-started.md +0 -237
  120. package/docs/head-management.md +0 -212
  121. package/docs/i18n.md +0 -159
  122. package/docs/layouts.md +0 -224
  123. package/docs/middleware.md +0 -234
  124. package/docs/plugins.md +0 -170
  125. package/docs/rendering-modes.md +0 -377
  126. package/docs/routing.md +0 -485
  127. package/docs/server-api.md +0 -253
  128. package/docs/testing.md +0 -455
  129. package/docs/use-fetch.md +0 -183
  130. package/e2e/cypress/e2e/api.cy.ts +0 -123
  131. package/e2e/cypress/e2e/async-component.cy.ts +0 -27
  132. package/e2e/cypress/e2e/component-splitting.cy.ts +0 -50
  133. package/e2e/cypress/e2e/content.cy.ts +0 -425
  134. package/e2e/cypress/e2e/cookie.cy.ts +0 -85
  135. package/e2e/cypress/e2e/data.cy.ts +0 -158
  136. package/e2e/cypress/e2e/error-boundary.cy.ts +0 -88
  137. package/e2e/cypress/e2e/fouc.cy.ts +0 -254
  138. package/e2e/cypress/e2e/group-meta.cy.ts +0 -36
  139. package/e2e/cypress/e2e/head.cy.ts +0 -89
  140. package/e2e/cypress/e2e/i18n.cy.ts +0 -79
  141. package/e2e/cypress/e2e/interactive.cy.ts +0 -137
  142. package/e2e/cypress/e2e/isr-nested-runtime.cy.ts +0 -102
  143. package/e2e/cypress/e2e/jit-css-custom-colors.cy.ts +0 -43
  144. package/e2e/cypress/e2e/middleware-chain.cy.ts +0 -89
  145. package/e2e/cypress/e2e/middleware-status.cy.ts +0 -30
  146. package/e2e/cypress/e2e/middleware.cy.ts +0 -45
  147. package/e2e/cypress/e2e/navigate.cy.ts +0 -39
  148. package/e2e/cypress/e2e/observability.cy.ts +0 -77
  149. package/e2e/cypress/e2e/per-route-error.cy.ts +0 -42
  150. package/e2e/cypress/e2e/per-route-render.cy.ts +0 -70
  151. package/e2e/cypress/e2e/preview-hardening.cy.ts +0 -88
  152. package/e2e/cypress/e2e/routes.cy.ts +0 -128
  153. package/e2e/cypress/e2e/seo-meta.cy.ts +0 -108
  154. package/e2e/cypress/e2e/server-middleware.cy.ts +0 -46
  155. package/e2e/cypress/e2e/session-rotation.cy.ts +0 -25
  156. package/e2e/cypress/e2e/session.cy.ts +0 -73
  157. package/e2e/cypress/e2e/ssr-render-error.cy.ts +0 -68
  158. package/e2e/cypress/e2e/synthetic-404.cy.ts +0 -45
  159. package/e2e/cypress/e2e/use-page-data.cy.ts +0 -122
  160. package/e2e/cypress/e2e/use-state.cy.ts +0 -138
  161. package/e2e/cypress/support/commands.ts +0 -120
  162. package/e2e/cypress/support/e2e.ts +0 -10
  163. package/e2e/cypress/tsconfig.json +0 -14
  164. package/e2e/kitchen-sink/app/components/ks-badge.ts +0 -8
  165. package/e2e/kitchen-sink/app/composables/useKsCounter.ts +0 -9
  166. package/e2e/kitchen-sink/app/error.ts +0 -18
  167. package/e2e/kitchen-sink/app/layouts/admin.ts +0 -13
  168. package/e2e/kitchen-sink/app/layouts/default.ts +0 -25
  169. package/e2e/kitchen-sink/app/layouts/group.ts +0 -9
  170. package/e2e/kitchen-sink/app/layouts/minimal.ts +0 -7
  171. package/e2e/kitchen-sink/app/loading.ts +0 -9
  172. package/e2e/kitchen-sink/app/middleware/auth.ts +0 -9
  173. package/e2e/kitchen-sink/app/middleware/custom-log.ts +0 -4
  174. package/e2e/kitchen-sink/app/middleware/group-auth.ts +0 -7
  175. package/e2e/kitchen-sink/app/middleware/logger.ts +0 -14
  176. package/e2e/kitchen-sink/app/pages/(auth)/login.ts +0 -13
  177. package/e2e/kitchen-sink/app/pages/(auth)/protected.ts +0 -16
  178. package/e2e/kitchen-sink/app/pages/404.ts +0 -9
  179. package/e2e/kitchen-sink/app/pages/about.ts +0 -17
  180. package/e2e/kitchen-sink/app/pages/admin/_layout.ts +0 -1
  181. package/e2e/kitchen-sink/app/pages/admin/dashboard.ts +0 -11
  182. package/e2e/kitchen-sink/app/pages/async-component-error-test.ts +0 -18
  183. package/e2e/kitchen-sink/app/pages/async-component-test.ts +0 -18
  184. package/e2e/kitchen-sink/app/pages/blog/[slug].ts +0 -59
  185. package/e2e/kitchen-sink/app/pages/blog/index.ts +0 -51
  186. package/e2e/kitchen-sink/app/pages/content-blog.ts +0 -37
  187. package/e2e/kitchen-sink/app/pages/content-doc.ts +0 -42
  188. package/e2e/kitchen-sink/app/pages/content-fallback.ts +0 -36
  189. package/e2e/kitchen-sink/app/pages/content-guides.ts +0 -35
  190. package/e2e/kitchen-sink/app/pages/content-index.ts +0 -39
  191. package/e2e/kitchen-sink/app/pages/content-search.ts +0 -36
  192. package/e2e/kitchen-sink/app/pages/cookie-test.ts +0 -22
  193. package/e2e/kitchen-sink/app/pages/counter.ts +0 -17
  194. package/e2e/kitchen-sink/app/pages/group-auth-blocked.ts +0 -9
  195. package/e2e/kitchen-sink/app/pages/group-meta-test/_layout.ts +0 -6
  196. package/e2e/kitchen-sink/app/pages/group-meta-test/custom-mw.ts +0 -14
  197. package/e2e/kitchen-sink/app/pages/group-meta-test/page.ts +0 -9
  198. package/e2e/kitchen-sink/app/pages/group-meta-test/protected.ts +0 -8
  199. package/e2e/kitchen-sink/app/pages/head.ts +0 -20
  200. package/e2e/kitchen-sink/app/pages/i18n-test.ts +0 -14
  201. package/e2e/kitchen-sink/app/pages/index.ts +0 -27
  202. package/e2e/kitchen-sink/app/pages/isr-test.ts +0 -17
  203. package/e2e/kitchen-sink/app/pages/items/[id].ts +0 -20
  204. package/e2e/kitchen-sink/app/pages/jit-css-custom-colors-test.ts +0 -14
  205. package/e2e/kitchen-sink/app/pages/loader-error-test.ts +0 -13
  206. package/e2e/kitchen-sink/app/pages/loader-props-test.ts +0 -19
  207. package/e2e/kitchen-sink/app/pages/loader-response-error-test.ts +0 -13
  208. package/e2e/kitchen-sink/app/pages/middleware-chain-test.ts +0 -12
  209. package/e2e/kitchen-sink/app/pages/middleware-status-test.ts +0 -9
  210. package/e2e/kitchen-sink/app/pages/navigate-test.ts +0 -16
  211. package/e2e/kitchen-sink/app/pages/observability-test.ts +0 -25
  212. package/e2e/kitchen-sink/app/pages/per-route-error-test.error.ts +0 -13
  213. package/e2e/kitchen-sink/app/pages/per-route-error-test.ts +0 -15
  214. package/e2e/kitchen-sink/app/pages/plugin-dsd-test.ts +0 -10
  215. package/e2e/kitchen-sink/app/pages/render-error-test.ts +0 -12
  216. package/e2e/kitchen-sink/app/pages/render-server-test.ts +0 -12
  217. package/e2e/kitchen-sink/app/pages/render-spa-test.ts +0 -12
  218. package/e2e/kitchen-sink/app/pages/route-info.ts +0 -14
  219. package/e2e/kitchen-sink/app/pages/seo-test.ts +0 -23
  220. package/e2e/kitchen-sink/app/pages/session-rotation-test.ts +0 -25
  221. package/e2e/kitchen-sink/app/pages/use-state-test.ts +0 -37
  222. package/e2e/kitchen-sink/app/plugins/01.setup.ts +0 -7
  223. package/e2e/kitchen-sink/app/plugins/02.ks-plugin-components.ts +0 -27
  224. package/e2e/kitchen-sink/cer-auto-imports.d.ts +0 -51
  225. package/e2e/kitchen-sink/cer-env.d.ts +0 -35
  226. package/e2e/kitchen-sink/cer-tsconfig.json +0 -30
  227. package/e2e/kitchen-sink/cer.config.ts +0 -43
  228. package/e2e/kitchen-sink/content/01.guides/01.index.md +0 -8
  229. package/e2e/kitchen-sink/content/01.guides/02.intro.md +0 -8
  230. package/e2e/kitchen-sink/content/01.guides/10.advanced.md +0 -8
  231. package/e2e/kitchen-sink/content/blog/2026-04-01-hello.md +0 -26
  232. package/e2e/kitchen-sink/content/blog/2026-04-02-draft.md +0 -10
  233. package/e2e/kitchen-sink/content/blog/index.md +0 -8
  234. package/e2e/kitchen-sink/content/blog/no-frontmatter.md +0 -7
  235. package/e2e/kitchen-sink/content/docs/getting-started.md +0 -48
  236. package/e2e/kitchen-sink/content/index.md +0 -16
  237. package/e2e/kitchen-sink/server/api/echo.ts +0 -12
  238. package/e2e/kitchen-sink/server/api/health.ts +0 -3
  239. package/e2e/kitchen-sink/server/api/posts/[slug].ts +0 -11
  240. package/e2e/kitchen-sink/server/api/posts/index.ts +0 -5
  241. package/e2e/kitchen-sink/server/api/session.ts +0 -23
  242. package/e2e/kitchen-sink/server/data/posts.ts +0 -21
  243. package/e2e/kitchen-sink/server/middleware/01-headers.ts +0 -6
  244. package/e2e/kitchen-sink/server/middleware/02-status-test.ts +0 -10
  245. package/e2e/kitchen-sink/tsconfig.json +0 -3
  246. package/e2e/scripts/clean.mjs +0 -14
  247. package/eslint.config.ts +0 -22
  248. package/src/__tests__/cli/adapters/cloudflare-worker.integration.test.ts +0 -132
  249. package/src/__tests__/cli/adapters/cloudflare.test.ts +0 -332
  250. package/src/__tests__/cli/adapters/netlify-bridge.integration.test.ts +0 -139
  251. package/src/__tests__/cli/adapters/netlify.test.ts +0 -286
  252. package/src/__tests__/cli/adapters/vercel-launcher.integration.test.ts +0 -129
  253. package/src/__tests__/cli/adapters/vercel.test.ts +0 -263
  254. package/src/__tests__/cli/preview-hardening.test.ts +0 -265
  255. package/src/__tests__/cli/preview-isr.test.ts +0 -320
  256. package/src/__tests__/index.test.ts +0 -21
  257. package/src/__tests__/plugin/app-template.test.ts +0 -112
  258. package/src/__tests__/plugin/build-ssg-render.test.ts +0 -155
  259. package/src/__tests__/plugin/build-ssg.test.ts +0 -598
  260. package/src/__tests__/plugin/build-ssr.test.ts +0 -192
  261. package/src/__tests__/plugin/cer-app-plugin.test.ts +0 -552
  262. package/src/__tests__/plugin/content/emitter.test.ts +0 -117
  263. package/src/__tests__/plugin/content/loader.test.ts +0 -154
  264. package/src/__tests__/plugin/content/parser.test.ts +0 -381
  265. package/src/__tests__/plugin/content/path-utils.test.ts +0 -69
  266. package/src/__tests__/plugin/content/search.test.ts +0 -119
  267. package/src/__tests__/plugin/dev-server.test.ts +0 -813
  268. package/src/__tests__/plugin/dts-generator.test.ts +0 -394
  269. package/src/__tests__/plugin/entry-server-template.test.ts +0 -385
  270. package/src/__tests__/plugin/generated-dir.test.ts +0 -144
  271. package/src/__tests__/plugin/path-utils.test.ts +0 -411
  272. package/src/__tests__/plugin/resolve-config.test.ts +0 -200
  273. package/src/__tests__/plugin/scanner.test.ts +0 -186
  274. package/src/__tests__/plugin/transforms/auto-import.test.ts +0 -510
  275. package/src/__tests__/plugin/transforms/head-inject.test.ts +0 -178
  276. package/src/__tests__/plugin/virtual/composables.test.ts +0 -57
  277. package/src/__tests__/plugin/virtual/content-components.test.ts +0 -66
  278. package/src/__tests__/plugin/virtual/error.test.ts +0 -71
  279. package/src/__tests__/plugin/virtual/layouts.test.ts +0 -70
  280. package/src/__tests__/plugin/virtual/loading.test.ts +0 -72
  281. package/src/__tests__/plugin/virtual/middleware.test.ts +0 -83
  282. package/src/__tests__/plugin/virtual/plugins.test.ts +0 -129
  283. package/src/__tests__/plugin/virtual/routes.test.ts +0 -884
  284. package/src/__tests__/plugin/virtual/server-api.test.ts +0 -164
  285. package/src/__tests__/plugin/virtual/server-middleware.test.ts +0 -102
  286. package/src/__tests__/runtime/app-template.test.ts +0 -198
  287. package/src/__tests__/runtime/define-middleware.test.ts +0 -64
  288. package/src/__tests__/runtime/define-server-middleware.test.ts +0 -54
  289. package/src/__tests__/runtime/entry-client-template.test.ts +0 -61
  290. package/src/__tests__/runtime/isr-handler.test.ts +0 -338
  291. package/src/__tests__/runtime/oauth/handler.test.ts +0 -358
  292. package/src/__tests__/runtime/oauth/providers.test.ts +0 -47
  293. package/src/__tests__/runtime/oauth-index.test.ts +0 -43
  294. package/src/__tests__/runtime/use-auth.test.ts +0 -169
  295. package/src/__tests__/runtime/use-content-search-composable.test.ts +0 -405
  296. package/src/__tests__/runtime/use-content-search.test.ts +0 -163
  297. package/src/__tests__/runtime/use-content.test.ts +0 -226
  298. package/src/__tests__/runtime/use-cookie.test.ts +0 -277
  299. package/src/__tests__/runtime/use-fetch-component.test.ts +0 -214
  300. package/src/__tests__/runtime/use-fetch.test.ts +0 -377
  301. package/src/__tests__/runtime/use-head.test.ts +0 -266
  302. package/src/__tests__/runtime/use-inject-client.test.ts +0 -67
  303. package/src/__tests__/runtime/use-inject.test.ts +0 -66
  304. package/src/__tests__/runtime/use-locale.test.ts +0 -152
  305. package/src/__tests__/runtime/use-navigate.test.ts +0 -121
  306. package/src/__tests__/runtime/use-page-data.test.ts +0 -298
  307. package/src/__tests__/runtime/use-route.test.ts +0 -156
  308. package/src/__tests__/runtime/use-runtime-config.test.ts +0 -182
  309. package/src/__tests__/runtime/use-seo-meta.test.ts +0 -109
  310. package/src/__tests__/runtime/use-session.test.ts +0 -320
  311. package/src/__tests__/runtime/use-state.test.ts +0 -340
  312. package/src/__tests__/types/config.test.ts +0 -18
  313. package/src/cli/adapters/cloudflare.ts +0 -335
  314. package/src/cli/adapters/netlify.ts +0 -326
  315. package/src/cli/adapters/vercel.ts +0 -311
  316. package/src/cli/commands/adapt.ts +0 -84
  317. package/src/cli/commands/build.ts +0 -165
  318. package/src/cli/commands/dev.ts +0 -125
  319. package/src/cli/commands/generate.ts +0 -91
  320. package/src/cli/commands/preview-isr.ts +0 -172
  321. package/src/cli/commands/preview.ts +0 -489
  322. package/src/cli/create/index.ts +0 -275
  323. package/src/cli/create/templates/shared/.gitignore.tpl +0 -25
  324. package/src/cli/create/templates/shared/app/layouts/default.ts.tpl +0 -15
  325. package/src/cli/create/templates/shared/app/pages/index.ts.tpl +0 -8
  326. package/src/cli/create/templates/shared/index.html.tpl +0 -12
  327. package/src/cli/create/templates/shared/tsconfig.json.tpl +0 -6
  328. package/src/cli/create/templates/spa/cer.config.ts.tpl +0 -6
  329. package/src/cli/create/templates/spa/package.json.tpl +0 -18
  330. package/src/cli/create/templates/ssg/cer.config.ts.tpl +0 -10
  331. package/src/cli/create/templates/ssg/package.json.tpl +0 -19
  332. package/src/cli/create/templates/ssr/cer.config.ts.tpl +0 -6
  333. package/src/cli/create/templates/ssr/package.json.tpl +0 -18
  334. package/src/cli/index.ts +0 -22
  335. package/src/index.ts +0 -20
  336. package/src/plugin/build-ssg.ts +0 -336
  337. package/src/plugin/build-ssr.ts +0 -134
  338. package/src/plugin/content/emitter.ts +0 -50
  339. package/src/plugin/content/index.ts +0 -233
  340. package/src/plugin/content/parser.ts +0 -259
  341. package/src/plugin/content/path-utils.ts +0 -55
  342. package/src/plugin/content/scanner.ts +0 -26
  343. package/src/plugin/content/search.ts +0 -28
  344. package/src/plugin/dev-server.ts +0 -377
  345. package/src/plugin/dts-generator.ts +0 -273
  346. package/src/plugin/generated-dir.ts +0 -121
  347. package/src/plugin/index.ts +0 -553
  348. package/src/plugin/path-utils.ts +0 -186
  349. package/src/plugin/scanner.ts +0 -65
  350. package/src/plugin/transforms/auto-import.ts +0 -206
  351. package/src/plugin/transforms/head-inject.ts +0 -161
  352. package/src/plugin/virtual/composables.ts +0 -28
  353. package/src/plugin/virtual/content-components.ts +0 -83
  354. package/src/plugin/virtual/error.ts +0 -34
  355. package/src/plugin/virtual/layouts.ts +0 -41
  356. package/src/plugin/virtual/loading.ts +0 -32
  357. package/src/plugin/virtual/middleware.ts +0 -45
  358. package/src/plugin/virtual/plugins.ts +0 -46
  359. package/src/plugin/virtual/routes.ts +0 -514
  360. package/src/plugin/virtual/server-api.ts +0 -85
  361. package/src/plugin/virtual/server-middleware.ts +0 -44
  362. package/src/runtime/app-template.ts +0 -380
  363. package/src/runtime/composables/define-middleware.ts +0 -17
  364. package/src/runtime/composables/define-server-middleware.ts +0 -18
  365. package/src/runtime/composables/index.ts +0 -27
  366. package/src/runtime/composables/use-auth.ts +0 -125
  367. package/src/runtime/composables/use-content-search.ts +0 -205
  368. package/src/runtime/composables/use-content.ts +0 -146
  369. package/src/runtime/composables/use-cookie.ts +0 -135
  370. package/src/runtime/composables/use-fetch.ts +0 -438
  371. package/src/runtime/composables/use-head.ts +0 -212
  372. package/src/runtime/composables/use-inject.ts +0 -49
  373. package/src/runtime/composables/use-locale.ts +0 -109
  374. package/src/runtime/composables/use-navigate.ts +0 -58
  375. package/src/runtime/composables/use-page-data.ts +0 -93
  376. package/src/runtime/composables/use-route.ts +0 -71
  377. package/src/runtime/composables/use-runtime-config.ts +0 -123
  378. package/src/runtime/composables/use-seo-meta.ts +0 -80
  379. package/src/runtime/composables/use-session.ts +0 -186
  380. package/src/runtime/composables/use-state.ts +0 -92
  381. package/src/runtime/content/client.ts +0 -168
  382. package/src/runtime/entry-client-template.ts +0 -39
  383. package/src/runtime/entry-server-template.ts +0 -493
  384. package/src/runtime/isr-handler.ts +0 -184
  385. package/src/runtime/oauth/handler.ts +0 -407
  386. package/src/runtime/oauth/index.ts +0 -49
  387. package/src/runtime/oauth/providers.ts +0 -38
  388. package/src/types/api.ts +0 -40
  389. package/src/types/config.ts +0 -371
  390. package/src/types/content.ts +0 -66
  391. package/src/types/index.ts +0 -7
  392. package/src/types/middleware.ts +0 -63
  393. package/src/types/page.ts +0 -117
  394. package/src/types/plugin.ts +0 -34
  395. package/tsconfig.build.json +0 -10
  396. package/tsconfig.json +0 -19
  397. package/vitest.config.ts +0 -33
@@ -1,233 +0,0 @@
1
- import { join } from 'pathe'
2
- import { existsSync } from 'node:fs'
3
- import type { Plugin, ViteDevServer, ResolvedConfig } from 'vite'
4
- import type { CerContentConfig } from '../../types/content.js'
5
- import type { ContentItem } from '../../types/content.js'
6
- import { scanContentFiles } from './scanner.js'
7
- import { parseContentFileAsync, toContentMeta } from './parser.js'
8
- import { emitContentFiles } from './emitter.js'
9
- import { buildSearchIndex } from './search.js'
10
- import type { IncomingMessage, ServerResponse } from 'node:http'
11
-
12
- /** The globalThis key used to share the in-memory content store with ContentClient. */
13
- export const CONTENT_STORE_KEY = '__CER_CONTENT_STORE__'
14
-
15
- /**
16
- * Resolves the absolute content directory from the framework config.
17
- * `dir` is relative to the project root (not the app source directory),
18
- * so `content/` sits alongside `app/`, `server/`, and `public/`.
19
- */
20
- export function resolveContentDir(root: string, contentConfig?: CerContentConfig): string {
21
- const dir = contentConfig?.dir ?? 'content'
22
- return join(root, dir)
23
- }
24
-
25
- /**
26
- * Loads all content files from `contentDir`, parses them concurrently, and
27
- * returns the full `ContentItem[]`. Excludes drafts unless `includeDrafts: true`
28
- * is set in the content config. Uses async I/O + `Promise.all` for concurrent
29
- * disk reads, which is significantly faster than sequential `readFileSync` at
30
- * 10k+ pages.
31
- */
32
- export async function loadContentStore(
33
- contentDir: string,
34
- isDraft: boolean,
35
- ): Promise<ContentItem[]> {
36
- if (!existsSync(contentDir)) return []
37
-
38
- const files = await scanContentFiles(contentDir)
39
-
40
- const results = await Promise.all(
41
- files.map(async (file) => {
42
- try {
43
- const item = await parseContentFileAsync(file, contentDir)
44
- // Skip drafts unless the user explicitly opted in via drafts: true
45
- if (!isDraft && item.draft === true) return null
46
- return item
47
- } catch (err) {
48
- // Warn and skip unparseable / invalid files so one bad file does not
49
- // abort the entire build. Invalid JSON files produce a descriptive
50
- // error from the parser; other errors are also surfaced here.
51
- console.warn(
52
- `[cer-app] Skipping content file (parse error): ${file.filePath}\n ${(err as Error).message}`,
53
- )
54
- return null
55
- }
56
- }),
57
- )
58
-
59
- return results.filter((item): item is ContentItem => item !== null)
60
- }
61
-
62
- // ─── Dev server derived-data cache ───────────────────────────────────────────
63
- // Re-computing the manifest JSON and search index on every request is O(n) at
64
- // 10k+ pages. Cache them as serialised strings, keyed by the store array
65
- // reference. When refreshStore() replaces the store, the reference changes
66
- // and the cache is automatically invalidated on the next request.
67
-
68
- let _devCacheStoreRef: ContentItem[] | null = null
69
- let _devCacheManifestJson: string | null = null
70
- let _devCacheSearchIndexJson: string | null = null
71
-
72
- function invalidateDevCaches(): void {
73
- _devCacheStoreRef = null
74
- _devCacheManifestJson = null
75
- _devCacheSearchIndexJson = null
76
- }
77
-
78
- function ensureDevCache(store: ContentItem[]): void {
79
- if (_devCacheStoreRef === store) return
80
- _devCacheStoreRef = store
81
- _devCacheManifestJson = JSON.stringify(store.map(toContentMeta))
82
- _devCacheSearchIndexJson = buildSearchIndex(store)
83
- }
84
-
85
- /**
86
- * Registers the `/_content/*` dev middleware that serves content from the
87
- * in-memory store populated by `buildStart`.
88
- */
89
- function registerDevMiddleware(server: ViteDevServer, _contentDir: string): void {
90
- server.middlewares.use(async (req: IncomingMessage, res: ServerResponse, next: () => void) => {
91
- const url = (req as { url?: string }).url ?? ''
92
- if (!url.startsWith('/_content/')) {
93
- next()
94
- return
95
- }
96
-
97
- const g = globalThis as Record<string, unknown>
98
- const store = g[CONTENT_STORE_KEY] as ContentItem[] | undefined
99
-
100
- if (!store) {
101
- res.statusCode = 503
102
- res.end('Content store not ready')
103
- return
104
- }
105
-
106
- // Rebuild derived caches if the store reference has changed (post-HMR).
107
- ensureDevCache(store)
108
-
109
- const suffix = url.slice('/_content/'.length).split('?')[0]
110
-
111
- if (suffix === 'manifest.json') {
112
- res.setHeader('Content-Type', 'application/json')
113
- res.setHeader('Cache-Control', 'no-store')
114
- res.end(_devCacheManifestJson!)
115
- return
116
- }
117
-
118
- if (suffix === 'search-index.json') {
119
- res.setHeader('Content-Type', 'application/json')
120
- res.setHeader('Cache-Control', 'no-store')
121
- res.end(_devCacheSearchIndexJson!)
122
- return
123
- }
124
-
125
- // Individual document: suffix is like "blog/hello.json" or "index.json"
126
- if (suffix.endsWith('.json')) {
127
- const rawPath = suffix.slice(0, -'.json'.length) // strip .json
128
- // Reverse the contentPathToFile mapping:
129
- // "index" → "/" and "blog/hello" → "/blog/hello"
130
- const _path = rawPath === 'index' ? '/' : '/' + rawPath
131
- const item = store.find((i) => i._path === _path)
132
- if (item) {
133
- res.setHeader('Content-Type', 'application/json')
134
- res.setHeader('Cache-Control', 'no-store')
135
- res.end(JSON.stringify(item))
136
- return
137
- }
138
- }
139
-
140
- res.statusCode = 404
141
- res.end('Not found')
142
- })
143
- }
144
-
145
- /**
146
- * `cerContent()` — Vite sub-plugin that provides the file-based content layer.
147
- *
148
- * - `buildStart`: scans and parses the content directory, populates `globalThis.__CER_CONTENT_STORE__`
149
- * - `configureServer`: registers `/_content/*` dev middleware from the same store; watches for HMR
150
- * - `closeBundle`: emits `_content/*.json` into the Vite output directory for this build
151
- * (e.g. `dist/client/_content/` for SSR client, `dist/_content/` for SPA/SSG)
152
- *
153
- * The content directory is resolved relative to the **project root** (not the
154
- * app source directory), so a default config produces `{root}/content/` — at
155
- * the same level as `app/`, `server/`, and `public/`.
156
- */
157
- export function cerContent(
158
- contentConfig?: CerContentConfig,
159
- ): Plugin {
160
- const contentDirName = contentConfig?.dir ?? 'content'
161
- const includeDrafts = contentConfig?.drafts ?? false
162
- // Resolved in configResolved; empty until then.
163
- let _resolvedContentDir = ''
164
- let _resolvedOutDir = ''
165
- let _isSsr = false
166
-
167
- const plugin: Plugin = {
168
- name: '@jasonshimmy/vite-plugin-cer-app:content',
169
-
170
- configResolved(resolvedConfig: ResolvedConfig) {
171
- // Content lives at {root}/{dir} — parallel to app/, server/, public/.'
172
- _resolvedContentDir = join(resolvedConfig.root, contentDirName)
173
- _resolvedOutDir = resolvedConfig.build.outDir
174
- _isSsr = !!resolvedConfig.build.ssr
175
- },
176
-
177
- async buildStart() {
178
- const items = await loadContentStore(_resolvedContentDir, includeDrafts)
179
- const g = globalThis as Record<string, unknown>
180
- g[CONTENT_STORE_KEY] = items
181
- },
182
-
183
- configureServer(server: ViteDevServer) {
184
- registerDevMiddleware(server, _resolvedContentDir)
185
-
186
- // HMR: re-parse on content file changes
187
- server.watcher.add(_resolvedContentDir)
188
-
189
- server.watcher.on('add', async (file: string) => {
190
- if (!file.startsWith(_resolvedContentDir)) return
191
- await refreshStore(_resolvedContentDir, includeDrafts)
192
- server.ws.send({ type: 'full-reload' })
193
- })
194
-
195
- server.watcher.on('change', async (file: string) => {
196
- if (!file.startsWith(_resolvedContentDir)) return
197
- await refreshStore(_resolvedContentDir, includeDrafts)
198
- server.ws.send({ type: 'full-reload' })
199
- })
200
-
201
- server.watcher.on('unlink', async (file: string) => {
202
- if (!file.startsWith(_resolvedContentDir)) return
203
- await refreshStore(_resolvedContentDir, includeDrafts)
204
- server.ws.send({ type: 'full-reload' })
205
- })
206
- },
207
-
208
- closeBundle() {
209
- // Only emit for the client/SPA/SSG build pass, not the SSR server bundle pass.
210
- // This prevents double scanning, parsing, and writing at build time.
211
- // The SSR runtime reads from dist/client/_content/ (or dist/_content/) at runtime.
212
- if (_isSsr) return
213
- const g = globalThis as Record<string, unknown>
214
- // Use the populated store; fall back to [] so the client never receives a 404 on
215
- // /_content/manifest.json even if buildStart failed to populate the store.
216
- const store = (g[CONTENT_STORE_KEY] as ContentItem[] | undefined) ?? []
217
-
218
- const searchIndex = buildSearchIndex(store)
219
- emitContentFiles(store, _resolvedOutDir, searchIndex)
220
- },
221
- }
222
-
223
- return plugin
224
- }
225
-
226
- async function refreshStore(contentDir: string, includeDrafts: boolean): Promise<void> {
227
- const items = await loadContentStore(contentDir, includeDrafts)
228
- const g = globalThis as Record<string, unknown>
229
- g[CONTENT_STORE_KEY] = items
230
- // Invalidate the dev middleware caches so the next request rebuilds manifest
231
- // and search-index from the updated store.
232
- invalidateDevCaches()
233
- }
@@ -1,259 +0,0 @@
1
- import matter from 'gray-matter'
2
- import { marked, type Token } from 'marked'
3
- import { readFileSync } from 'node:fs'
4
- import { readFile } from 'node:fs/promises'
5
- import type { ContentHeading, ContentItem, ContentMeta } from '../../types/content.js'
6
- import type { ContentFile } from './scanner.js'
7
- import { fileToContentPath } from './path-utils.js'
8
- import { relative } from 'pathe'
9
-
10
- // ─── Heading extraction ───────────────────────────────────────────────────────
11
-
12
- /** Slugify a plain-text heading string into a URL-safe id. */
13
- function slugify(text: string): string {
14
- return text
15
- .toLowerCase()
16
- .replace(/[^\w\s-]/g, '')
17
- .trim()
18
- .replace(/[\s_]+/g, '-')
19
- .replace(/-+/g, '-')
20
- }
21
-
22
- /**
23
- * Walks a marked token list and collects heading tokens.
24
- * Mutates heading tokens in place to add `id` attributes to the HTML output.
25
- */
26
- function extractHeadings(tokens: Token[]): ContentHeading[] {
27
- const headings: ContentHeading[] = []
28
-
29
- const walk = (tokenList: Token[]) => {
30
- for (const token of tokenList) {
31
- if (token.type === 'heading') {
32
- const text = token.text
33
- const id = slugify(text)
34
- headings.push({
35
- depth: token.depth as ContentHeading['depth'],
36
- id,
37
- text,
38
- })
39
- }
40
- // Walk nested tokens (e.g. list items, blockquote)
41
- if ('tokens' in token && Array.isArray(token.tokens)) {
42
- walk(token.tokens)
43
- }
44
- }
45
- }
46
-
47
- walk(tokens)
48
- return headings
49
- }
50
-
51
- // ─── Fallback title / description extraction ─────────────────────────────────
52
-
53
- /**
54
- * Converts a list of inline marked tokens to plain text by recursing into
55
- * formatted tokens (strong, em, link, etc.) and collecting their leaf text.
56
- * Used to derive readable fallback values from body content.
57
- */
58
- function inlineToPlainText(tokens: Token[]): string {
59
- let result = ''
60
- for (const t of tokens) {
61
- const children = (t as { tokens?: Token[] }).tokens
62
- if (Array.isArray(children) && children.length > 0) {
63
- result += inlineToPlainText(children)
64
- } else if (t.type === 'br') {
65
- result += ' '
66
- } else if ('text' in t && typeof (t as { text: string }).text === 'string') {
67
- result += (t as { text: string }).text
68
- }
69
- }
70
- return result
71
- }
72
-
73
- const DESCRIPTION_MAX_LEN = 160
74
-
75
- /**
76
- * Scans the top-level token list for a fallback `title` (first depth-1 heading)
77
- * and `description` (first paragraph). Both are `undefined` when no matching
78
- * token is found.
79
- *
80
- * These are applied only when the corresponding frontmatter field is absent, so
81
- * frontmatter always wins.
82
- */
83
- function extractFallbacks(tokens: Token[]): { title?: string; description?: string } {
84
- let title: string | undefined
85
- let description: string | undefined
86
-
87
- for (const token of tokens) {
88
- if (title === undefined && token.type === 'heading' && token.depth === 1) {
89
- const text = inlineToPlainText((token as { tokens: Token[] }).tokens ?? []).trim()
90
- if (text) title = text
91
- }
92
- if (description === undefined && token.type === 'paragraph') {
93
- const text = inlineToPlainText((token as { tokens: Token[] }).tokens ?? []).trim()
94
- if (text) {
95
- description =
96
- text.length > DESCRIPTION_MAX_LEN
97
- ? text.slice(0, DESCRIPTION_MAX_LEN).trimEnd() + '…'
98
- : text
99
- }
100
- }
101
- if (title !== undefined && description !== undefined) break
102
- }
103
-
104
- return { title, description }
105
- }
106
-
107
- // ─── Custom renderer: add id to heading tags ─────────────────────────────────
108
-
109
- const renderer = new marked.Renderer()
110
-
111
- renderer.heading = function ({ tokens, depth }) {
112
- const text = tokens.map((t) => ('text' in t ? (t.text as string) : '')).join('')
113
- const id = slugify(text)
114
- const level = depth as ContentHeading['depth']
115
- const innerHtml = marked.parseInline(tokens.map((t) => ('raw' in t ? t.raw : '')).join(''))
116
- return `<h${level} id="${id}">${innerHtml}</h${level}>\n`
117
- }
118
-
119
- // ─── Parser ───────────────────────────────────────────────────────────────────
120
-
121
- /**
122
- * Core parse logic shared by both the sync and async variants.
123
- * Accepts pre-read `raw` content so the caller controls I/O scheduling.
124
- *
125
- * Date normalization: gray-matter parses bare YAML dates (e.g. `date: 2026-04-03`)
126
- * as JavaScript `Date` objects. This causes a type mismatch — the in-memory server
127
- * store contains `Date` objects while the client, which reads via JSON.stringify/parse,
128
- * always gets ISO strings. All `Date` values are normalised to `YYYY-MM-DD` strings
129
- * here so both paths are consistent.
130
- */
131
- function parseContentFileFromRaw(
132
- file: ContentFile,
133
- contentDir: string,
134
- raw: string,
135
- ): ContentItem {
136
- const _path = fileToContentPath(file.filePath, contentDir)
137
- const _file = relative(contentDir, file.filePath)
138
-
139
- if (file.ext === 'json') {
140
- // Validate JSON so users get a clear error at build time rather than a
141
- // silently broken body string that only surfaces at render time.
142
- try {
143
- JSON.parse(raw)
144
- } catch (err) {
145
- throw new Error(
146
- `Invalid JSON in content file "${file.filePath}": ${(err as Error).message}`,
147
- )
148
- }
149
- return {
150
- _path,
151
- _file,
152
- _type: 'json',
153
- body: raw,
154
- toc: [],
155
- }
156
- }
157
-
158
- // ── Markdown ─────────────────────────────────────────────────────────────
159
- const parsed = matter(raw)
160
- const frontmatter = parsed.data as ContentMeta
161
- const content = parsed.content
162
-
163
- // Split on <!-- more --> for excerpt.
164
- // The marker is stripped from bodySource so it does not appear as an HTML
165
- // comment in the rendered body — spec says body is "all content minus the
166
- // marker itself".
167
- const MORE_MARKER = '<!-- more -->'
168
- const moreIndex = content.indexOf(MORE_MARKER)
169
- const hasMore = moreIndex !== -1
170
- const bodySource = hasMore
171
- ? content.slice(0, moreIndex) + content.slice(moreIndex + MORE_MARKER.length)
172
- : content
173
- const excerptSource = hasMore ? content.slice(0, moreIndex).trim() : null
174
-
175
- // Tokenise once and extract headings
176
- const lexer = new marked.Lexer()
177
- const tokens = lexer.lex(bodySource)
178
- const toc = extractHeadings(tokens)
179
-
180
- // Render full body with custom renderer (adds id= to headings)
181
- const body = marked.parser(tokens, { renderer }) as string
182
-
183
- // Render excerpt if present
184
- const excerpt = excerptSource !== null
185
- ? (marked.parse(excerptSource, { renderer }) as string)
186
- : undefined
187
-
188
- // Derive fallback title / description from body tokens when frontmatter
189
- // does not provide them. Frontmatter always wins — these only fill gaps.
190
- const fallbacks = extractFallbacks(tokens)
191
-
192
- const item: ContentItem = {
193
- ...frontmatter,
194
- _path,
195
- _file,
196
- _type: 'markdown',
197
- body,
198
- toc,
199
- }
200
-
201
- if (item.title === undefined && fallbacks.title !== undefined) {
202
- item.title = fallbacks.title
203
- }
204
- if (item.description === undefined && fallbacks.description !== undefined) {
205
- item.description = fallbacks.description
206
- }
207
-
208
- if (excerpt !== undefined) {
209
- item.excerpt = excerpt
210
- }
211
-
212
- // Normalise any Date objects introduced by gray-matter's YAML parser to
213
- // YYYY-MM-DD strings. Without this, the server in-memory store holds Date
214
- // objects while the client (after JSON round-trip) holds strings, causing
215
- // date comparisons in .where() predicates to silently misbehave server-side.
216
- for (const key of Object.keys(item)) {
217
- if ((item as Record<string, unknown>)[key] instanceof Date) {
218
- ;(item as Record<string, unknown>)[key] = ((item as Record<string, unknown>)[key] as Date)
219
- .toISOString()
220
- .split('T')[0]
221
- }
222
- }
223
-
224
- return item
225
- }
226
-
227
- /**
228
- * Parses a single content file synchronously and returns a `ContentItem`.
229
- * Prefer `parseContentFileAsync` in batch contexts (e.g. `loadContentStore`)
230
- * to allow concurrent I/O.
231
- */
232
- export function parseContentFile(
233
- file: ContentFile,
234
- contentDir: string,
235
- ): ContentItem {
236
- const raw = readFileSync(file.filePath, 'utf-8')
237
- return parseContentFileFromRaw(file, contentDir, raw)
238
- }
239
-
240
- /**
241
- * Async variant of `parseContentFile`. Uses `fs/promises.readFile` so multiple
242
- * calls can be awaited concurrently via `Promise.all`, overlapping disk I/O.
243
- */
244
- export async function parseContentFileAsync(
245
- file: ContentFile,
246
- contentDir: string,
247
- ): Promise<ContentItem> {
248
- const raw = await readFile(file.filePath, 'utf-8')
249
- return parseContentFileFromRaw(file, contentDir, raw)
250
- }
251
-
252
- /**
253
- * Strips body-only fields to produce a lean `ContentMeta` for the manifest.
254
- */
255
- export function toContentMeta(item: ContentItem): ContentMeta {
256
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
257
- const { _file, body, toc, excerpt, ...meta } = item
258
- return meta as ContentMeta
259
- }
@@ -1,55 +0,0 @@
1
- import { relative } from 'pathe'
2
-
3
- function stripNumericPrefix(segment: string): string {
4
- return segment.replace(/^\d+\./, '')
5
- }
6
-
7
- /**
8
- * Maps a content file path to a `_path` URL path.
9
- *
10
- * Rules:
11
- * - Strip the content dir prefix
12
- * - Strip the file extension
13
- * - Strip `NN.` numeric ordering prefixes from all path segments
14
- * - Strip `/index` suffix (so blog/index.md → /blog)
15
- * - Strip `YYYY-MM-DD-` date prefix from the final slug segment
16
- *
17
- * Examples:
18
- * index.md → /
19
- * 01.about.md → /about
20
- * about.md → /about
21
- * 01.blog/02.hello.md → /blog/hello
22
- * blog/index.md → /blog
23
- * blog/2026-04-03-hello.md → /blog/hello
24
- * docs/getting-started.md → /docs/getting-started
25
- * data/products.json → /data/products
26
- */
27
- export function fileToContentPath(filePath: string, contentDir: string): string {
28
- // Get path relative to contentDir, strip extension
29
- let rel = relative(contentDir, filePath)
30
- rel = rel.replace(/\.(md|json)$/, '')
31
-
32
- // Split into segments
33
- const segments = rel.split('/').map(stripNumericPrefix)
34
-
35
- // Strip date prefix (YYYY-MM-DD-) from the last segment after removing any
36
- // numeric ordering prefix (for example 01.2026-04-03-hello.md → /hello).
37
- const last = segments[segments.length - 1]
38
- const stripped = last.replace(/^\d{4}-\d{2}-\d{2}-/, '')
39
- segments[segments.length - 1] = stripped
40
-
41
- // Strip trailing 'index' segment (but keep bare 'index' → '/')
42
- if (segments.length > 1 && segments[segments.length - 1] === 'index') {
43
- segments.pop()
44
- }
45
-
46
- // If the only segment was 'index', produce root path
47
- if (segments.length === 1 && segments[0] === 'index') {
48
- return '/'
49
- }
50
-
51
- const path = '/' + segments.join('/')
52
- return path.replace(/\/+/g, '/')
53
- }
54
-
55
-
@@ -1,26 +0,0 @@
1
- import fg from 'fast-glob'
2
-
3
- export interface ContentFile {
4
- /** Absolute file path */
5
- filePath: string
6
- /** Extension without dot: 'md' | 'json' */
7
- ext: 'md' | 'json'
8
- }
9
-
10
- /**
11
- * Scans the content directory for all Markdown and JSON files.
12
- * Returns absolute file paths sorted alphabetically.
13
- */
14
- export async function scanContentFiles(contentDir: string): Promise<ContentFile[]> {
15
- const files = await fg('**/*.{md,json}', {
16
- cwd: contentDir,
17
- absolute: true,
18
- onlyFiles: true,
19
- ignore: ['**/node_modules/**', '**/.git/**'],
20
- })
21
-
22
- return files.sort().map((filePath) => ({
23
- filePath,
24
- ext: filePath.endsWith('.json') ? 'json' : 'md',
25
- }))
26
- }
@@ -1,28 +0,0 @@
1
- import MiniSearch from 'minisearch'
2
- import type { ContentItem } from '../../types/content.js'
3
-
4
- /**
5
- * Builds and serialises a MiniSearch full-text search index over `title` and `description`.
6
- *
7
- * Stored fields: `_path`, `title`, `description` — match `ContentSearchResult` exactly.
8
- * Returns the serialised index as a JSON string ready to be written to `search-index.json`.
9
- */
10
- export function buildSearchIndex(items: ContentItem[]): string {
11
- const index = new MiniSearch({
12
- fields: ['title', 'description'],
13
- storeFields: ['_path', 'title', 'description'],
14
- idField: '_path',
15
- })
16
-
17
- const docs = items
18
- .filter((item) => item.title !== undefined)
19
- .map((item) => ({
20
- _path: item._path,
21
- title: (item.title as string) ?? '',
22
- description: (item.description as string) ?? '',
23
- }))
24
-
25
- index.addAll(docs)
26
-
27
- return JSON.stringify(index)
28
- }