@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,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
- }