@softure-ai/blog 0.0.0-stage → 0.1.6

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 (419) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/LICENSE +21 -0
  3. package/README.md +604 -2
  4. package/dist/cli/bin.d.ts +3 -0
  5. package/dist/cli/bin.d.ts.map +1 -0
  6. package/dist/cli/bin.js +5 -0
  7. package/dist/cli/bin.js.map +1 -0
  8. package/dist/cli/command.d.ts +9 -0
  9. package/dist/cli/command.d.ts.map +1 -0
  10. package/dist/cli/command.js +40 -0
  11. package/dist/cli/command.js.map +1 -0
  12. package/dist/cli/index.d.ts +4 -0
  13. package/dist/cli/index.d.ts.map +1 -0
  14. package/dist/cli/index.js +5 -0
  15. package/dist/cli/index.js.map +1 -0
  16. package/dist/cli/run.d.ts +62 -0
  17. package/dist/cli/run.d.ts.map +1 -0
  18. package/dist/cli/run.js +555 -0
  19. package/dist/cli/run.js.map +1 -0
  20. package/dist/cli/skill.d.ts +47 -0
  21. package/dist/cli/skill.d.ts.map +1 -0
  22. package/dist/cli/skill.js +221 -0
  23. package/dist/cli/skill.js.map +1 -0
  24. package/dist/content/article-file.d.ts +27 -0
  25. package/dist/content/article-file.d.ts.map +1 -0
  26. package/dist/content/article-file.js +179 -0
  27. package/dist/content/article-file.js.map +1 -0
  28. package/dist/contract.d.ts +77 -0
  29. package/dist/contract.d.ts.map +1 -0
  30. package/dist/contract.js +5 -0
  31. package/dist/contract.js.map +1 -0
  32. package/dist/db/articles.d.ts +30 -0
  33. package/dist/db/articles.d.ts.map +1 -0
  34. package/dist/db/articles.js +138 -0
  35. package/dist/db/articles.js.map +1 -0
  36. package/dist/db/publish-run.d.ts +50 -0
  37. package/dist/db/publish-run.d.ts.map +1 -0
  38. package/dist/db/publish-run.js +168 -0
  39. package/dist/db/publish-run.js.map +1 -0
  40. package/dist/db/schema.d.ts +403 -0
  41. package/dist/db/schema.d.ts.map +1 -0
  42. package/dist/db/schema.js +33 -0
  43. package/dist/db/schema.js.map +1 -0
  44. package/dist/discovery/dates.d.ts +9 -0
  45. package/dist/discovery/dates.d.ts.map +1 -0
  46. package/dist/discovery/dates.js +19 -0
  47. package/dist/discovery/dates.js.map +1 -0
  48. package/dist/discovery/index.d.ts +8 -0
  49. package/dist/discovery/index.d.ts.map +1 -0
  50. package/dist/discovery/index.js +11 -0
  51. package/dist/discovery/index.js.map +1 -0
  52. package/dist/discovery/indexnow.d.ts +11 -0
  53. package/dist/discovery/indexnow.d.ts.map +1 -0
  54. package/dist/discovery/indexnow.js +23 -0
  55. package/dist/discovery/indexnow.js.map +1 -0
  56. package/dist/discovery/refresh.d.ts +49 -0
  57. package/dist/discovery/refresh.d.ts.map +1 -0
  58. package/dist/discovery/refresh.js +55 -0
  59. package/dist/discovery/refresh.js.map +1 -0
  60. package/dist/discovery/related.d.ts +17 -0
  61. package/dist/discovery/related.d.ts.map +1 -0
  62. package/dist/discovery/related.js +28 -0
  63. package/dist/discovery/related.js.map +1 -0
  64. package/dist/discovery/rss.d.ts +30 -0
  65. package/dist/discovery/rss.d.ts.map +1 -0
  66. package/dist/discovery/rss.js +48 -0
  67. package/dist/discovery/rss.js.map +1 -0
  68. package/dist/discovery/sitemap.d.ts +25 -0
  69. package/dist/discovery/sitemap.d.ts.map +1 -0
  70. package/dist/discovery/sitemap.js +31 -0
  71. package/dist/discovery/sitemap.js.map +1 -0
  72. package/dist/discovery/submit.d.ts +44 -0
  73. package/dist/discovery/submit.d.ts.map +1 -0
  74. package/dist/discovery/submit.js +46 -0
  75. package/dist/discovery/submit.js.map +1 -0
  76. package/dist/index.d.ts +371 -0
  77. package/dist/index.d.ts.map +1 -0
  78. package/dist/index.js +69 -0
  79. package/dist/index.js.map +1 -0
  80. package/dist/messages/en.d.ts +83 -0
  81. package/dist/messages/en.d.ts.map +1 -0
  82. package/dist/messages/en.js +83 -0
  83. package/dist/messages/en.js.map +1 -0
  84. package/dist/messages/index.d.ts +158 -0
  85. package/dist/messages/index.d.ts.map +1 -0
  86. package/dist/messages/index.js +5 -0
  87. package/dist/messages/index.js.map +1 -0
  88. package/dist/messages/pl.d.ts +78 -0
  89. package/dist/messages/pl.d.ts.map +1 -0
  90. package/dist/messages/pl.js +78 -0
  91. package/dist/messages/pl.js.map +1 -0
  92. package/dist/next/context.d.ts +6 -0
  93. package/dist/next/context.d.ts.map +1 -0
  94. package/dist/next/context.js +27 -0
  95. package/dist/next/context.js.map +1 -0
  96. package/dist/next/data.d.ts +11 -0
  97. package/dist/next/data.d.ts.map +1 -0
  98. package/dist/next/data.js +40 -0
  99. package/dist/next/data.js.map +1 -0
  100. package/dist/next/discovery.d.ts +6 -0
  101. package/dist/next/discovery.d.ts.map +1 -0
  102. package/dist/next/discovery.js +49 -0
  103. package/dist/next/discovery.js.map +1 -0
  104. package/dist/next/index.d.ts +9 -0
  105. package/dist/next/index.d.ts.map +1 -0
  106. package/dist/next/index.js +10 -0
  107. package/dist/next/index.js.map +1 -0
  108. package/dist/next/og-fonts.d.ts +6 -0
  109. package/dist/next/og-fonts.d.ts.map +1 -0
  110. package/dist/next/og-fonts.js +8 -0
  111. package/dist/next/og-fonts.js.map +1 -0
  112. package/dist/next/og-image.d.ts +36 -0
  113. package/dist/next/og-image.d.ts.map +1 -0
  114. package/dist/next/og-image.js +77 -0
  115. package/dist/next/og-image.js.map +1 -0
  116. package/dist/next/pages.d.ts +45 -0
  117. package/dist/next/pages.d.ts.map +1 -0
  118. package/dist/next/pages.js +189 -0
  119. package/dist/next/pages.js.map +1 -0
  120. package/dist/next/refresh.d.ts +9 -0
  121. package/dist/next/refresh.d.ts.map +1 -0
  122. package/dist/next/refresh.js +81 -0
  123. package/dist/next/refresh.js.map +1 -0
  124. package/dist/options.d.ts +320 -0
  125. package/dist/options.d.ts.map +1 -0
  126. package/dist/options.js +184 -0
  127. package/dist/options.js.map +1 -0
  128. package/dist/pages/body.d.ts +18 -0
  129. package/dist/pages/body.d.ts.map +1 -0
  130. package/dist/pages/body.js +21 -0
  131. package/dist/pages/body.js.map +1 -0
  132. package/dist/pages/dates.d.ts +21 -0
  133. package/dist/pages/dates.d.ts.map +1 -0
  134. package/dist/pages/dates.js +24 -0
  135. package/dist/pages/dates.js.map +1 -0
  136. package/dist/pages/index.d.ts +7 -0
  137. package/dist/pages/index.d.ts.map +1 -0
  138. package/dist/pages/index.js +9 -0
  139. package/dist/pages/index.js.map +1 -0
  140. package/dist/pages/json-ld.d.ts +33 -0
  141. package/dist/pages/json-ld.d.ts.map +1 -0
  142. package/dist/pages/json-ld.js +95 -0
  143. package/dist/pages/json-ld.js.map +1 -0
  144. package/dist/pages/listing.d.ts +46 -0
  145. package/dist/pages/listing.d.ts.map +1 -0
  146. package/dist/pages/listing.js +57 -0
  147. package/dist/pages/listing.js.map +1 -0
  148. package/dist/pages/paths.d.ts +37 -0
  149. package/dist/pages/paths.d.ts.map +1 -0
  150. package/dist/pages/paths.js +61 -0
  151. package/dist/pages/paths.js.map +1 -0
  152. package/dist/pages/redirects.d.ts +57 -0
  153. package/dist/pages/redirects.d.ts.map +1 -0
  154. package/dist/pages/redirects.js +78 -0
  155. package/dist/pages/redirects.js.map +1 -0
  156. package/dist/proxy/index.d.ts +15 -0
  157. package/dist/proxy/index.d.ts.map +1 -0
  158. package/dist/proxy/index.js +60 -0
  159. package/dist/proxy/index.js.map +1 -0
  160. package/dist/quality/blocks.d.ts +21 -0
  161. package/dist/quality/blocks.d.ts.map +1 -0
  162. package/dist/quality/blocks.js +113 -0
  163. package/dist/quality/blocks.js.map +1 -0
  164. package/dist/quality/catalog.d.ts +10 -0
  165. package/dist/quality/catalog.d.ts.map +1 -0
  166. package/dist/quality/catalog.js +71 -0
  167. package/dist/quality/catalog.js.map +1 -0
  168. package/dist/quality/check-article.d.ts +34 -0
  169. package/dist/quality/check-article.d.ts.map +1 -0
  170. package/dist/quality/check-article.js +74 -0
  171. package/dist/quality/check-article.js.map +1 -0
  172. package/dist/quality/check-files.d.ts +23 -0
  173. package/dist/quality/check-files.d.ts.map +1 -0
  174. package/dist/quality/check-files.js +44 -0
  175. package/dist/quality/check-files.js.map +1 -0
  176. package/dist/quality/external-links.d.ts +19 -0
  177. package/dist/quality/external-links.d.ts.map +1 -0
  178. package/dist/quality/external-links.js +30 -0
  179. package/dist/quality/external-links.js.map +1 -0
  180. package/dist/quality/finding.d.ts +17 -0
  181. package/dist/quality/finding.d.ts.map +1 -0
  182. package/dist/quality/finding.js +15 -0
  183. package/dist/quality/finding.js.map +1 -0
  184. package/dist/quality/gate.d.ts +5 -0
  185. package/dist/quality/gate.d.ts.map +1 -0
  186. package/dist/quality/gate.js +10 -0
  187. package/dist/quality/gate.js.map +1 -0
  188. package/dist/quality/index.d.ts +15 -0
  189. package/dist/quality/index.d.ts.map +1 -0
  190. package/dist/quality/index.js +16 -0
  191. package/dist/quality/index.js.map +1 -0
  192. package/dist/quality/link-targets.d.ts +44 -0
  193. package/dist/quality/link-targets.d.ts.map +1 -0
  194. package/dist/quality/link-targets.js +91 -0
  195. package/dist/quality/link-targets.js.map +1 -0
  196. package/dist/quality/options.d.ts +113 -0
  197. package/dist/quality/options.d.ts.map +1 -0
  198. package/dist/quality/options.js +93 -0
  199. package/dist/quality/options.js.map +1 -0
  200. package/dist/quality/plugin.d.ts +34 -0
  201. package/dist/quality/plugin.d.ts.map +1 -0
  202. package/dist/quality/plugin.js +7 -0
  203. package/dist/quality/plugin.js.map +1 -0
  204. package/dist/quality/rules/blocks.d.ts +5 -0
  205. package/dist/quality/rules/blocks.d.ts.map +1 -0
  206. package/dist/quality/rules/blocks.js +35 -0
  207. package/dist/quality/rules/blocks.js.map +1 -0
  208. package/dist/quality/rules/images.d.ts +4 -0
  209. package/dist/quality/rules/images.d.ts.map +1 -0
  210. package/dist/quality/rules/images.js +33 -0
  211. package/dist/quality/rules/images.js.map +1 -0
  212. package/dist/quality/rules/input.d.ts +11 -0
  213. package/dist/quality/rules/input.d.ts.map +1 -0
  214. package/dist/quality/rules/input.js +2 -0
  215. package/dist/quality/rules/input.js.map +1 -0
  216. package/dist/quality/rules/links.d.ts +17 -0
  217. package/dist/quality/rules/links.d.ts.map +1 -0
  218. package/dist/quality/rules/links.js +46 -0
  219. package/dist/quality/rules/links.js.map +1 -0
  220. package/dist/quality/rules/structure.d.ts +13 -0
  221. package/dist/quality/rules/structure.d.ts.map +1 -0
  222. package/dist/quality/rules/structure.js +139 -0
  223. package/dist/quality/rules/structure.js.map +1 -0
  224. package/dist/quality/rules/style.d.ts +11 -0
  225. package/dist/quality/rules/style.d.ts.map +1 -0
  226. package/dist/quality/rules/style.js +129 -0
  227. package/dist/quality/rules/style.js.map +1 -0
  228. package/dist/quality/rules/ymyl.d.ts +5 -0
  229. package/dist/quality/rules/ymyl.d.ts.map +1 -0
  230. package/dist/quality/rules/ymyl.js +51 -0
  231. package/dist/quality/rules/ymyl.js.map +1 -0
  232. package/dist/quality/rulesets/en/ruleset.d.ts +3 -0
  233. package/dist/quality/rulesets/en/ruleset.d.ts.map +1 -0
  234. package/dist/quality/rulesets/en/ruleset.js +106 -0
  235. package/dist/quality/rulesets/en/ruleset.js.map +1 -0
  236. package/dist/quality/rulesets/index.d.ts +7 -0
  237. package/dist/quality/rulesets/index.d.ts.map +1 -0
  238. package/dist/quality/rulesets/index.js +7 -0
  239. package/dist/quality/rulesets/index.js.map +1 -0
  240. package/dist/quality/rulesets/pl/ruleset.d.ts +3 -0
  241. package/dist/quality/rulesets/pl/ruleset.d.ts.map +1 -0
  242. package/dist/quality/rulesets/pl/ruleset.js +114 -0
  243. package/dist/quality/rulesets/pl/ruleset.js.map +1 -0
  244. package/dist/quality/rulesets/types.d.ts +28 -0
  245. package/dist/quality/rulesets/types.d.ts.map +1 -0
  246. package/dist/quality/rulesets/types.js +2 -0
  247. package/dist/quality/rulesets/types.js.map +1 -0
  248. package/dist/quality/settings.d.ts +26 -0
  249. package/dist/quality/settings.d.ts.map +1 -0
  250. package/dist/quality/settings.js +32 -0
  251. package/dist/quality/settings.js.map +1 -0
  252. package/dist/quality/text.d.ts +43 -0
  253. package/dist/quality/text.d.ts.map +1 -0
  254. package/dist/quality/text.js +85 -0
  255. package/dist/quality/text.js.map +1 -0
  256. package/dist/render/glossary.d.ts +27 -0
  257. package/dist/render/glossary.d.ts.map +1 -0
  258. package/dist/render/glossary.js +71 -0
  259. package/dist/render/glossary.js.map +1 -0
  260. package/dist/render/images.d.ts +42 -0
  261. package/dist/render/images.d.ts.map +1 -0
  262. package/dist/render/images.js +84 -0
  263. package/dist/render/images.js.map +1 -0
  264. package/dist/render/index.d.ts +6 -0
  265. package/dist/render/index.d.ts.map +1 -0
  266. package/dist/render/index.js +8 -0
  267. package/dist/render/index.js.map +1 -0
  268. package/dist/render/reading-time.d.ts +8 -0
  269. package/dist/render/reading-time.d.ts.map +1 -0
  270. package/dist/render/reading-time.js +17 -0
  271. package/dist/render/reading-time.js.map +1 -0
  272. package/dist/render/render-article.d.ts +97 -0
  273. package/dist/render/render-article.d.ts.map +1 -0
  274. package/dist/render/render-article.js +373 -0
  275. package/dist/render/render-article.js.map +1 -0
  276. package/dist/render/slugify-heading.d.ts +2 -0
  277. package/dist/render/slugify-heading.d.ts.map +1 -0
  278. package/dist/render/slugify-heading.js +21 -0
  279. package/dist/render/slugify-heading.js.map +1 -0
  280. package/dist/server/health.d.ts +3 -0
  281. package/dist/server/health.d.ts.map +1 -0
  282. package/dist/server/health.js +11 -0
  283. package/dist/server/health.js.map +1 -0
  284. package/dist/server/index.d.ts +10 -0
  285. package/dist/server/index.d.ts.map +1 -0
  286. package/dist/server/index.js +13 -0
  287. package/dist/server/index.js.map +1 -0
  288. package/dist/server/og-fonts.d.ts +24 -0
  289. package/dist/server/og-fonts.d.ts.map +1 -0
  290. package/dist/server/og-fonts.js +85 -0
  291. package/dist/server/og-fonts.js.map +1 -0
  292. package/dist/server/options.d.ts +17 -0
  293. package/dist/server/options.d.ts.map +1 -0
  294. package/dist/server/options.js +56 -0
  295. package/dist/server/options.js.map +1 -0
  296. package/dist/sitemap.d.ts +11 -0
  297. package/dist/sitemap.d.ts.map +1 -0
  298. package/dist/sitemap.js +35 -0
  299. package/dist/sitemap.js.map +1 -0
  300. package/dist/ui/blog-article.d.ts +49 -0
  301. package/dist/ui/blog-article.d.ts.map +1 -0
  302. package/dist/ui/blog-article.js +45 -0
  303. package/dist/ui/blog-article.js.map +1 -0
  304. package/dist/ui/blog-glossary.d.ts +27 -0
  305. package/dist/ui/blog-glossary.d.ts.map +1 -0
  306. package/dist/ui/blog-glossary.js +20 -0
  307. package/dist/ui/blog-glossary.js.map +1 -0
  308. package/dist/ui/blog-layout.d.ts +31 -0
  309. package/dist/ui/blog-layout.d.ts.map +1 -0
  310. package/dist/ui/blog-layout.js +25 -0
  311. package/dist/ui/blog-layout.js.map +1 -0
  312. package/dist/ui/blog-listing.d.ts +15 -0
  313. package/dist/ui/blog-listing.d.ts.map +1 -0
  314. package/dist/ui/blog-listing.js +29 -0
  315. package/dist/ui/blog-listing.js.map +1 -0
  316. package/dist/ui/blog-method.d.ts +5 -0
  317. package/dist/ui/blog-method.d.ts.map +1 -0
  318. package/dist/ui/blog-method.js +21 -0
  319. package/dist/ui/blog-method.js.map +1 -0
  320. package/dist/ui/index.d.ts +7 -0
  321. package/dist/ui/index.d.ts.map +1 -0
  322. package/dist/ui/index.js +8 -0
  323. package/dist/ui/index.js.map +1 -0
  324. package/dist/ui/page-context.d.ts +15 -0
  325. package/dist/ui/page-context.d.ts.map +1 -0
  326. package/dist/ui/page-context.js +2 -0
  327. package/dist/ui/page-context.js.map +1 -0
  328. package/migrations/0001_create_articles.sql +67 -0
  329. package/migrations/README.md +8 -0
  330. package/module.json +28 -0
  331. package/package.json +102 -4
  332. package/skill/SKILL.md +72 -0
  333. package/skill/references/reviewer.md +39 -0
  334. package/skill/references/rules.md +122 -0
  335. package/skill/references/structure.md +69 -0
  336. package/skill/references/template.md +86 -0
  337. package/src/cli/bin.ts +5 -0
  338. package/src/cli/command.ts +49 -0
  339. package/src/cli/index.ts +20 -0
  340. package/src/cli/run.ts +591 -0
  341. package/src/cli/skill.ts +242 -0
  342. package/src/content/article-file.ts +184 -0
  343. package/src/contract.ts +88 -0
  344. package/src/db/articles.ts +160 -0
  345. package/src/db/publish-run.ts +224 -0
  346. package/src/db/schema.ts +36 -0
  347. package/src/discovery/dates.ts +27 -0
  348. package/src/discovery/index.ts +18 -0
  349. package/src/discovery/indexnow.ts +25 -0
  350. package/src/discovery/refresh.ts +79 -0
  351. package/src/discovery/related.ts +35 -0
  352. package/src/discovery/rss.ts +77 -0
  353. package/src/discovery/sitemap.ts +59 -0
  354. package/src/discovery/submit.ts +69 -0
  355. package/src/index.ts +99 -0
  356. package/src/messages/en.ts +82 -0
  357. package/src/messages/index.ts +7 -0
  358. package/src/messages/pl.ts +77 -0
  359. package/src/next/context.ts +30 -0
  360. package/src/next/data.ts +55 -0
  361. package/src/next/discovery.ts +49 -0
  362. package/src/next/index.ts +25 -0
  363. package/src/next/next-modules.d.ts +15 -0
  364. package/src/next/og-fonts.ts +14 -0
  365. package/src/next/og-image.tsx +108 -0
  366. package/src/next/pages.tsx +251 -0
  367. package/src/next/refresh.ts +85 -0
  368. package/src/options.ts +224 -0
  369. package/src/pages/body.ts +38 -0
  370. package/src/pages/dates.ts +39 -0
  371. package/src/pages/index.ts +37 -0
  372. package/src/pages/json-ld.ts +120 -0
  373. package/src/pages/listing.ts +95 -0
  374. package/src/pages/paths.ts +84 -0
  375. package/src/pages/redirects.ts +120 -0
  376. package/src/proxy/index.ts +66 -0
  377. package/src/quality/blocks.ts +130 -0
  378. package/src/quality/catalog.ts +86 -0
  379. package/src/quality/check-article.ts +112 -0
  380. package/src/quality/check-files.ts +63 -0
  381. package/src/quality/external-links.ts +37 -0
  382. package/src/quality/finding.ts +29 -0
  383. package/src/quality/gate.ts +15 -0
  384. package/src/quality/index.ts +28 -0
  385. package/src/quality/link-targets.ts +107 -0
  386. package/src/quality/options.ts +105 -0
  387. package/src/quality/plugin.ts +43 -0
  388. package/src/quality/rules/blocks.ts +43 -0
  389. package/src/quality/rules/images.ts +34 -0
  390. package/src/quality/rules/input.ts +12 -0
  391. package/src/quality/rules/links.ts +56 -0
  392. package/src/quality/rules/structure.ts +137 -0
  393. package/src/quality/rules/style.ts +137 -0
  394. package/src/quality/rules/ymyl.ts +57 -0
  395. package/src/quality/rulesets/en/ruleset.ts +117 -0
  396. package/src/quality/rulesets/index.ts +9 -0
  397. package/src/quality/rulesets/pl/ruleset.ts +126 -0
  398. package/src/quality/rulesets/types.ts +31 -0
  399. package/src/quality/settings.ts +60 -0
  400. package/src/quality/text.ts +113 -0
  401. package/src/render/glossary.ts +103 -0
  402. package/src/render/images.ts +110 -0
  403. package/src/render/index.ts +29 -0
  404. package/src/render/reading-time.ts +18 -0
  405. package/src/render/render-article.ts +487 -0
  406. package/src/render/slugify-heading.ts +22 -0
  407. package/src/server/health.ts +12 -0
  408. package/src/server/index.ts +28 -0
  409. package/src/server/og-fonts.ts +102 -0
  410. package/src/server/options.ts +62 -0
  411. package/src/sitemap.ts +36 -0
  412. package/src/ui/blog-article.tsx +186 -0
  413. package/src/ui/blog-glossary.tsx +104 -0
  414. package/src/ui/blog-layout.tsx +89 -0
  415. package/src/ui/blog-listing.tsx +95 -0
  416. package/src/ui/blog-method.tsx +34 -0
  417. package/src/ui/index.ts +8 -0
  418. package/src/ui/page-context.ts +17 -0
  419. package/styles.css +498 -0
@@ -0,0 +1,320 @@
1
+ import { z } from "zod";
2
+ import type { BlockPlugin } from "./render/render-article.js";
3
+ /** Where the app keeps its article files unless a command names a path. */
4
+ export declare const DEFAULT_CONTENT_DIR = "content/blog";
5
+ /** The frontmatter keys of the module; the app's `fields` may not reuse them. */
6
+ export declare const FRONTMATTER_KEYS: readonly ["id", "slug", "kind", "cluster", "pillar", "title", "description", "summary", "status", "current_as_of", "published_at", "sources", "faq", "forms"];
7
+ /** What a schema's `safeParse` reports; zod 3 and 4 both fit. */
8
+ export type BlogFieldsParseResult = {
9
+ readonly success: true;
10
+ readonly data: unknown;
11
+ } | {
12
+ readonly success: false;
13
+ readonly error: {
14
+ readonly issues: readonly {
15
+ readonly path: readonly PropertyKey[];
16
+ readonly message: string;
17
+ }[];
18
+ };
19
+ };
20
+ /**
21
+ * The app's own frontmatter fields: an object schema from the app's `zod`, e.g.
22
+ * `z.object({ scenario: z.string().optional() })`. Its keys join the frontmatter; the parsed value
23
+ * is stored with the article and enters its content hash. It must parse to plain JSON values.
24
+ */
25
+ export interface BlogFieldsSchema {
26
+ readonly shape: Readonly<Record<string, unknown>>;
27
+ readonly safeParse: (value: unknown) => BlogFieldsParseResult;
28
+ }
29
+ /** How long the pages cache their reads by default, in seconds; the app's `revalidate` should match. */
30
+ export declare const DEFAULT_REVALIDATE_SECONDS = 300;
31
+ /** Text the app writes per locale; `en` is the fallback for a locale it leaves out. */
32
+ declare const localizedTextSchema: z.ZodObject<{
33
+ en: z.ZodString;
34
+ pl: z.ZodOptional<z.ZodString>;
35
+ }, z.core.$strict>;
36
+ export type LocalizedText = z.output<typeof localizedTextSchema>;
37
+ /** The weights Satori (`next/og`) draws: static fonts only, in steps of 100. */
38
+ export declare const OG_FONT_WEIGHTS: readonly [100, 200, 300, 400, 500, 600, 700, 800, 900];
39
+ export type OgFontWeight = (typeof OG_FONT_WEIGHTS)[number];
40
+ declare const ogFontSchema: z.ZodObject<{
41
+ name: z.ZodString;
42
+ weight: z.ZodDefault<z.ZodCustom<200 | 600 | 700 | 300 | 100 | 400 | 500 | 800 | 900, 200 | 600 | 700 | 300 | 100 | 400 | 500 | 800 | 900>>;
43
+ style: z.ZodDefault<z.ZodEnum<{
44
+ normal: "normal";
45
+ italic: "italic";
46
+ }>>;
47
+ src: z.ZodString;
48
+ }, z.core.$strict>;
49
+ export type OgFontSource = z.output<typeof ogFontSchema>;
50
+ /** One section of the app's own in the writing skill: `## <title>` and the Markdown body, verbatim. */
51
+ declare const skillSectionSchema: z.ZodObject<{
52
+ title: z.ZodString;
53
+ body: z.ZodString;
54
+ }, z.core.$strict>;
55
+ export type BlogSkillSection = z.output<typeof skillSectionSchema>;
56
+ export declare const blogOptionsSchema: z.ZodObject<{
57
+ contentDir: z.ZodDefault<z.ZodString>;
58
+ reservedSlugs: z.ZodDefault<z.ZodArray<z.ZodString>>;
59
+ fields: z.ZodOptional<z.ZodCustom<BlogFieldsSchema, BlogFieldsSchema>>;
60
+ brand: z.ZodOptional<z.ZodObject<{
61
+ name: z.ZodString;
62
+ colors: z.ZodOptional<z.ZodObject<{
63
+ background: z.ZodOptional<z.ZodString>;
64
+ foreground: z.ZodOptional<z.ZodString>;
65
+ accent: z.ZodOptional<z.ZodString>;
66
+ }, z.core.$strict>>;
67
+ fonts: z.ZodOptional<z.ZodArray<z.ZodObject<{
68
+ name: z.ZodString;
69
+ weight: z.ZodDefault<z.ZodCustom<200 | 600 | 700 | 300 | 100 | 400 | 500 | 800 | 900, 200 | 600 | 700 | 300 | 100 | 400 | 500 | 800 | 900>>;
70
+ style: z.ZodDefault<z.ZodEnum<{
71
+ normal: "normal";
72
+ italic: "italic";
73
+ }>>;
74
+ src: z.ZodString;
75
+ }, z.core.$strict>>>;
76
+ }, z.core.$strict>>;
77
+ methodPage: z.ZodDefault<z.ZodBoolean>;
78
+ disclaimer: z.ZodOptional<z.ZodObject<{
79
+ en: z.ZodString;
80
+ pl: z.ZodOptional<z.ZodString>;
81
+ }, z.core.$strict>>;
82
+ clusters: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
83
+ en: z.ZodString;
84
+ pl: z.ZodOptional<z.ZodString>;
85
+ }, z.core.$strict>>>;
86
+ blocks: z.ZodDefault<z.ZodArray<z.ZodCustom<BlockPlugin<unknown>, BlockPlugin<unknown>>>>;
87
+ siteHosts: z.ZodDefault<z.ZodArray<z.ZodString>>;
88
+ images: z.ZodOptional<z.ZodObject<{
89
+ hosts: z.ZodDefault<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
90
+ dimensions: z.ZodCustom<(src: string) => import("./render/images.js").ImageDimensions | null, (src: string) => import("./render/images.js").ImageDimensions | null>;
91
+ }, z.core.$strict>>;
92
+ revalidateSeconds: z.ZodDefault<z.ZodNumber>;
93
+ quality: z.ZodDefault<z.ZodType<false | {
94
+ language: "en" | "pl";
95
+ ymyl: {
96
+ ownCalculationMark: string | null;
97
+ } | null;
98
+ voice: {
99
+ forbidFirstPersonSingular: boolean;
100
+ phrases: {
101
+ id: string;
102
+ pattern: RegExp;
103
+ message: string;
104
+ severity: "error" | "warning";
105
+ }[];
106
+ };
107
+ limits: {
108
+ words: {
109
+ article: {
110
+ min: number;
111
+ max: number;
112
+ };
113
+ term: {
114
+ min: number;
115
+ max: number;
116
+ };
117
+ };
118
+ leadWords: {
119
+ article: number;
120
+ term: number;
121
+ };
122
+ answerWords: number;
123
+ internalLinks: {
124
+ article: number;
125
+ term: number;
126
+ };
127
+ staleAfterDays: number;
128
+ titleChars: number;
129
+ descriptionChars: {
130
+ min: number;
131
+ max: number;
132
+ };
133
+ wordsPerDash: number;
134
+ wordsPerBold: number;
135
+ sentenceWords: number;
136
+ };
137
+ severity: Record<string, "error" | "warning" | "off">;
138
+ paths: {
139
+ articles: string;
140
+ terms: string;
141
+ };
142
+ ownOrigins: string[];
143
+ privateRouteSegments: string[];
144
+ plugins: import("./quality/plugin.js").QualityPlugin[];
145
+ blocks: BlockPlugin<unknown>[];
146
+ appDir?: string | undefined;
147
+ }, false | {
148
+ language?: "en" | "pl" | undefined;
149
+ ymyl?: boolean | {
150
+ ownCalculationMark?: string | undefined;
151
+ } | undefined;
152
+ voice?: {
153
+ forbidFirstPersonSingular?: boolean | undefined;
154
+ phrases?: {
155
+ id: string;
156
+ pattern: RegExp;
157
+ message: string;
158
+ severity?: "error" | "warning" | undefined;
159
+ }[] | undefined;
160
+ } | undefined;
161
+ limits?: {
162
+ words?: {
163
+ article?: {
164
+ min?: number | undefined;
165
+ max?: number | undefined;
166
+ } | undefined;
167
+ term?: {
168
+ min?: number | undefined;
169
+ max?: number | undefined;
170
+ } | undefined;
171
+ } | undefined;
172
+ leadWords?: {
173
+ article?: number | undefined;
174
+ term?: number | undefined;
175
+ } | undefined;
176
+ answerWords?: number | undefined;
177
+ internalLinks?: {
178
+ article?: number | undefined;
179
+ term?: number | undefined;
180
+ } | undefined;
181
+ staleAfterDays?: number | undefined;
182
+ titleChars?: number | undefined;
183
+ descriptionChars?: {
184
+ min?: number | undefined;
185
+ max?: number | undefined;
186
+ } | undefined;
187
+ wordsPerDash?: number | undefined;
188
+ wordsPerBold?: number | undefined;
189
+ sentenceWords?: number | undefined;
190
+ } | undefined;
191
+ severity?: Record<string, "error" | "warning" | "off"> | undefined;
192
+ paths?: {
193
+ articles?: string | undefined;
194
+ terms?: string | undefined;
195
+ } | undefined;
196
+ ownOrigins?: string[] | undefined;
197
+ appDir?: string | undefined;
198
+ privateRouteSegments?: string[] | undefined;
199
+ plugins?: import("./quality/plugin.js").QualityPlugin[] | undefined;
200
+ blocks?: BlockPlugin<unknown>[] | undefined;
201
+ }, z.core.$ZodTypeInternals<false | {
202
+ language: "en" | "pl";
203
+ ymyl: {
204
+ ownCalculationMark: string | null;
205
+ } | null;
206
+ voice: {
207
+ forbidFirstPersonSingular: boolean;
208
+ phrases: {
209
+ id: string;
210
+ pattern: RegExp;
211
+ message: string;
212
+ severity: "error" | "warning";
213
+ }[];
214
+ };
215
+ limits: {
216
+ words: {
217
+ article: {
218
+ min: number;
219
+ max: number;
220
+ };
221
+ term: {
222
+ min: number;
223
+ max: number;
224
+ };
225
+ };
226
+ leadWords: {
227
+ article: number;
228
+ term: number;
229
+ };
230
+ answerWords: number;
231
+ internalLinks: {
232
+ article: number;
233
+ term: number;
234
+ };
235
+ staleAfterDays: number;
236
+ titleChars: number;
237
+ descriptionChars: {
238
+ min: number;
239
+ max: number;
240
+ };
241
+ wordsPerDash: number;
242
+ wordsPerBold: number;
243
+ sentenceWords: number;
244
+ };
245
+ severity: Record<string, "error" | "warning" | "off">;
246
+ paths: {
247
+ articles: string;
248
+ terms: string;
249
+ };
250
+ ownOrigins: string[];
251
+ privateRouteSegments: string[];
252
+ plugins: import("./quality/plugin.js").QualityPlugin[];
253
+ blocks: BlockPlugin<unknown>[];
254
+ appDir?: string | undefined;
255
+ }, false | {
256
+ language?: "en" | "pl" | undefined;
257
+ ymyl?: boolean | {
258
+ ownCalculationMark?: string | undefined;
259
+ } | undefined;
260
+ voice?: {
261
+ forbidFirstPersonSingular?: boolean | undefined;
262
+ phrases?: {
263
+ id: string;
264
+ pattern: RegExp;
265
+ message: string;
266
+ severity?: "error" | "warning" | undefined;
267
+ }[] | undefined;
268
+ } | undefined;
269
+ limits?: {
270
+ words?: {
271
+ article?: {
272
+ min?: number | undefined;
273
+ max?: number | undefined;
274
+ } | undefined;
275
+ term?: {
276
+ min?: number | undefined;
277
+ max?: number | undefined;
278
+ } | undefined;
279
+ } | undefined;
280
+ leadWords?: {
281
+ article?: number | undefined;
282
+ term?: number | undefined;
283
+ } | undefined;
284
+ answerWords?: number | undefined;
285
+ internalLinks?: {
286
+ article?: number | undefined;
287
+ term?: number | undefined;
288
+ } | undefined;
289
+ staleAfterDays?: number | undefined;
290
+ titleChars?: number | undefined;
291
+ descriptionChars?: {
292
+ min?: number | undefined;
293
+ max?: number | undefined;
294
+ } | undefined;
295
+ wordsPerDash?: number | undefined;
296
+ wordsPerBold?: number | undefined;
297
+ sentenceWords?: number | undefined;
298
+ } | undefined;
299
+ severity?: Record<string, "error" | "warning" | "off"> | undefined;
300
+ paths?: {
301
+ articles?: string | undefined;
302
+ terms?: string | undefined;
303
+ } | undefined;
304
+ ownOrigins?: string[] | undefined;
305
+ appDir?: string | undefined;
306
+ privateRouteSegments?: string[] | undefined;
307
+ plugins?: import("./quality/plugin.js").QualityPlugin[] | undefined;
308
+ blocks?: BlockPlugin<unknown>[] | undefined;
309
+ }>>>;
310
+ skill: z.ZodDefault<z.ZodObject<{
311
+ sections: z.ZodDefault<z.ZodArray<z.ZodObject<{
312
+ title: z.ZodString;
313
+ body: z.ZodString;
314
+ }, z.core.$strict>>>;
315
+ }, z.core.$strict>>;
316
+ }, z.core.$strict>;
317
+ export type BlogOptionsInput = z.input<typeof blogOptionsSchema>;
318
+ export type BlogOptions = z.output<typeof blogOptionsSchema>;
319
+ export {};
320
+ //# sourceMappingURL=options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAE9D,2EAA2E;AAC3E,eAAO,MAAM,mBAAmB,iBAAiB,CAAC;AAElD,iFAAiF;AACjF,eAAO,MAAM,gBAAgB,+JAenB,CAAC;AAEX,iEAAiE;AACjE,MAAM,MAAM,qBAAqB,GAC7B;IAAE,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAClD;IAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,SAAS;YAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,WAAW,EAAE,CAAC;YAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;SAAE,EAAE,CAAA;KAAE,CAAA;CAAE,CAAC;AAErJ;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAClD,QAAQ,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,qBAAqB,CAAC;CAC/D;AAgBD,wGAAwG;AACxG,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAE9C,uFAAuF;AACvF,QAAA,MAAM,mBAAmB;;;kBAGvB,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAMjE,gFAAgF;AAChF,eAAO,MAAM,eAAe,wDAAyD,CAAC;AAEtF,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAc5D,QAAA,MAAM,YAAY;;;;;;;;kBAchB,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,YAAY,CAAC,CAAC;AAyCzD,uGAAuG;AACvG,QAAA,MAAM,kBAAkB;;;kBAYtB,CAAC;AAaH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,kBAAkB,CAAC,CAAC;AASnE,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAwC1B,CAAC;AAEL,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AACjE,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,iBAAiB,CAAC,CAAC"}
@@ -0,0 +1,184 @@
1
+ // The options an app passes to `blog({ ... })` in softure.config.ts, parsed at startup.
2
+ import { z } from "zod";
3
+ import { qualitySettingSchema } from "./quality/options.js";
4
+ /** Where the app keeps its article files unless a command names a path. */
5
+ export const DEFAULT_CONTENT_DIR = "content/blog";
6
+ /** The frontmatter keys of the module; the app's `fields` may not reuse them. */
7
+ export const FRONTMATTER_KEYS = [
8
+ "id",
9
+ "slug",
10
+ "kind",
11
+ "cluster",
12
+ "pillar",
13
+ "title",
14
+ "description",
15
+ "summary",
16
+ "status",
17
+ "current_as_of",
18
+ "published_at",
19
+ "sources",
20
+ "faq",
21
+ "forms",
22
+ ];
23
+ const KEBAB = /^[a-z0-9]+(-[a-z0-9]+)*$/;
24
+ function isFieldsSchema(value) {
25
+ if (typeof value !== "object" || value === null)
26
+ return false;
27
+ const candidate = value;
28
+ return typeof candidate.shape === "object" && candidate.shape !== null && typeof candidate.safeParse === "function";
29
+ }
30
+ function isBlockPlugin(value) {
31
+ if (typeof value !== "object" || value === null)
32
+ return false;
33
+ const candidate = value;
34
+ return typeof candidate.type === "string" && KEBAB.test(candidate.type) && typeof candidate.render === "function";
35
+ }
36
+ /** How long the pages cache their reads by default, in seconds; the app's `revalidate` should match. */
37
+ export const DEFAULT_REVALIDATE_SECONDS = 300;
38
+ /** Text the app writes per locale; `en` is the fallback for a locale it leaves out. */
39
+ const localizedTextSchema = z.strictObject({
40
+ en: z.string().trim().min(1),
41
+ pl: z.string().trim().min(1).optional(),
42
+ });
43
+ const HEX_COLOR = /^#[0-9a-fA-F]{6}$/;
44
+ const colorSchema = z.string().regex(HEX_COLOR, "must be a six-digit hex colour, e.g. #0c0c0d");
45
+ const HOSTNAME = /^(?=.{1,253}$)[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/;
46
+ /** The weights Satori (`next/og`) draws: static fonts only, in steps of 100. */
47
+ export const OG_FONT_WEIGHTS = [100, 200, 300, 400, 500, 600, 700, 800, 900];
48
+ const WOFF2 = /\.woff2$/i;
49
+ /** The part of a font source that names the file: a URL's path, or the path as written. */
50
+ function getSourceFileName(src) {
51
+ return src.startsWith("https://") ? new URL(src).pathname : src;
52
+ }
53
+ function isFontSource(src) {
54
+ if (src.startsWith("https://"))
55
+ return URL.canParse(src);
56
+ return !/^[a-z][a-z0-9+.-]*:\/\//i.test(src);
57
+ }
58
+ const ogFontSchema = z.strictObject({
59
+ /** The family name the card writes in; a second file of one weight and style needs its own name. */
60
+ name: z.string().trim().min(1).max(80),
61
+ weight: z
62
+ .custom((value) => OG_FONT_WEIGHTS.some((weight) => weight === value), "must be a weight from 100 to 900 in steps of 100")
63
+ .default(400),
64
+ style: z.enum(["normal", "italic"]).default("normal"),
65
+ /** A .ttf, .otf or .woff file: an https URL, an absolute path, or a path from the app's root. */
66
+ src: z
67
+ .string()
68
+ .trim()
69
+ .min(1)
70
+ .refine(isFontSource, "must be an https URL or a file path, e.g. fonts/inter-700.woff")
71
+ .refine((src) => !isFontSource(src) || !WOFF2.test(getSourceFileName(src)), "must be a .ttf, .otf or .woff file; the card cannot read .woff2"),
72
+ });
73
+ const brandSchema = z.strictObject({
74
+ /** The site's name: in page titles, as the author and publisher in JSON-LD, on the OG card. */
75
+ name: z.string().trim().min(1).max(80),
76
+ /** The OG card's colours; each defaults to the dark scheme of @softure-ai/ui's default theme. */
77
+ colors: z
78
+ .strictObject({ background: colorSchema.optional(), foreground: colorSchema.optional(), accent: colorSchema.optional() })
79
+ .optional(),
80
+ /**
81
+ * The OG card's fonts, read by the card's route on its first render and kept for the life of the
82
+ * process; without them the card uses `next/og`'s default font.
83
+ */
84
+ fonts: z
85
+ .array(ogFontSchema)
86
+ .min(1, "must list at least one font")
87
+ .superRefine((fonts, ctx) => {
88
+ const seen = new Set();
89
+ for (const [index, font] of fonts.entries()) {
90
+ const key = `"${font.name}" ${font.weight} ${font.style}`;
91
+ if (seen.has(key)) {
92
+ ctx.addIssue({ code: "custom", path: [index], message: `${key} is listed twice; give a second file its own name, e.g. "${font.name} Ext"` });
93
+ }
94
+ seen.add(key);
95
+ }
96
+ })
97
+ .optional(),
98
+ });
99
+ /** Whether a Markdown body holds a `#` or `##` heading outside fenced code. */
100
+ function hasTopHeading(body) {
101
+ let fence = null;
102
+ for (const line of body.split(/\r?\n/)) {
103
+ const trimmed = line.trimStart();
104
+ const marker = trimmed.startsWith("```") ? "```" : trimmed.startsWith("~~~") ? "~~~" : null;
105
+ if (marker !== null)
106
+ fence = fence === null ? marker : fence === marker ? null : fence;
107
+ else if (fence === null && /^ {0,3}#{1,2}(?:[ \t]|$)/.test(line))
108
+ return true;
109
+ }
110
+ return false;
111
+ }
112
+ /** One section of the app's own in the writing skill: `## <title>` and the Markdown body, verbatim. */
113
+ const skillSectionSchema = z.strictObject({
114
+ title: z
115
+ .string()
116
+ .trim()
117
+ .min(1)
118
+ .max(80)
119
+ .refine((title) => !/[\r\n]/.test(title), "must be one line"),
120
+ body: z
121
+ .string()
122
+ .trim()
123
+ .min(1)
124
+ .refine((body) => !hasTopHeading(body), "must not hold a # or ## heading; use ### and deeper (the title is the section's ## heading)"),
125
+ });
126
+ const skillSchema = z.strictObject({
127
+ /** The app's own sections (its numbers, block plugins, fields), written to `references/app.md` of the skill. */
128
+ sections: z.array(skillSectionSchema).superRefine((sections, ctx) => {
129
+ const seen = new Set();
130
+ for (const [index, section] of sections.entries()) {
131
+ if (seen.has(section.title))
132
+ ctx.addIssue({ code: "custom", path: [index, "title"], message: `"${section.title}" is the title of another section` });
133
+ seen.add(section.title);
134
+ }
135
+ }).default([]),
136
+ });
137
+ const imagePolicySchema = z.strictObject({
138
+ /** Hosts whose https images are allowed besides the site's own paths (subdomains included). */
139
+ hosts: z.array(z.string().regex(HOSTNAME, "must be a host name, e.g. cdn.example.com")).readonly().default([]),
140
+ /** `(src) => ({ width, height })` in pixels, or `null` for an unknown image. */
141
+ dimensions: z.custom((value) => typeof value === "function", "must be a function: (src) => ({ width, height }) or null"),
142
+ });
143
+ export const blogOptionsSchema = z
144
+ .strictObject({
145
+ /** The folder with the article files, relative to the app's root. */
146
+ contentDir: z.string().trim().min(1).default(DEFAULT_CONTENT_DIR),
147
+ /**
148
+ * Slugs taken by static pages under the blog's path (a glossary index, a method page). A static
149
+ * route wins over the article route, so an article with such a slug would be unreachable.
150
+ */
151
+ reservedSlugs: z.array(z.string().max(100).regex(KEBAB, "must be kebab-case, e.g. how-we-write")).default([]),
152
+ fields: z.custom(isFieldsSchema, "must be an object schema, e.g. z.object({ scenario: z.string() })").optional(),
153
+ /** The site's brand for titles, JSON-LD and the OG card; without it pages carry no brand. */
154
+ brand: brandSchema.optional(),
155
+ /** Whether the app mounts the "how our texts are made" page (`BlogMethodPage` at `routes.method`). */
156
+ methodPage: z.boolean().default(false),
157
+ /** A note under every text (not advice, not a recommendation…); none by default. */
158
+ disclaimer: localizedTextSchema.optional(),
159
+ /** Display names of clusters by key; a cluster without one shows its key with spaces. */
160
+ clusters: z.record(z.string().regex(KEBAB, "must be kebab-case, e.g. investing-basics"), localizedTextSchema).default({}),
161
+ /** Block plugins for the app's fenced blocks (`renderArticle({ blocks })`), used by the pages. */
162
+ blocks: z.array(z.custom(isBlockPlugin, "must be a block plugin: { type: \"chart\", render(block) }")).default([]),
163
+ /** Hosts besides those of `appOrigin` and the canonical site origin (`getSiteUrls`) whose links are not marked external (subdomains included). */
164
+ siteHosts: z.array(z.string().regex(HOSTNAME, "must be a host name, e.g. example.com")).default([]),
165
+ /**
166
+ * Which images article bodies may show (`renderArticle({ images })`), used by the pages and the
167
+ * quality gate; without it every image renders as its alt text and the gate refuses it.
168
+ */
169
+ images: imagePolicySchema.optional(),
170
+ /** How long the listing and glossary cache their reads; keep it equal to the pages' `revalidate`. */
171
+ revalidateSeconds: z.number().int().min(1).default(DEFAULT_REVALIDATE_SECONDS),
172
+ /** The text quality gate (`softure-blog check`, and every publish); `false` turns it off. */
173
+ quality: qualitySettingSchema,
174
+ /** What `softure-blog skill install` adds to the generated writing skill. */
175
+ skill: skillSchema.default({ sections: [] }),
176
+ })
177
+ .superRefine((options, ctx) => {
178
+ for (const key of Object.keys(options.fields?.shape ?? {})) {
179
+ if (FRONTMATTER_KEYS.includes(key)) {
180
+ ctx.addIssue({ code: "custom", path: ["fields", key], message: `"${key}" is a frontmatter key of the module` });
181
+ }
182
+ }
183
+ });
184
+ //# sourceMappingURL=options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.js","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { BlogArticle } from "../contract.js";
2
+ import type { BlogMessages } from "../messages/index.js";
3
+ import type { BlogOptions } from "../options.js";
4
+ import type { GlossaryTerm } from "../render/glossary.js";
5
+ import { type RenderedArticle } from "../render/render-article.js";
6
+ import { type BlogRoutes } from "./paths.js";
7
+ export interface RenderPageBodyOptions {
8
+ readonly glossary: readonly GlossaryTerm[];
9
+ readonly routes: BlogRoutes;
10
+ readonly options: Pick<BlogOptions, "blocks" | "images" | "siteHosts">;
11
+ /** The app's own origins (`appOrigin` and the canonical site origin); their hosts are the site's (links to them are not external). */
12
+ readonly origins: readonly string[];
13
+ readonly messages: BlogMessages;
14
+ }
15
+ export declare function renderPageBody<TNode = unknown>(text: BlogArticle, input: RenderPageBodyOptions): RenderedArticle<TNode>;
16
+ /** Published articles whose body links the term, by hand or by its first mention: the same rule as the links. */
17
+ export declare function findArticlesLinkingTerm(articles: readonly BlogArticle[], termSlug: string, input: RenderPageBodyOptions): BlogArticle[];
18
+ //# sourceMappingURL=body.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"body.d.ts","sourceRoot":"","sources":["../../src/pages/body.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAmC,KAAK,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACpG,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAE1D,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;IAC3C,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC,CAAC;IACvE,sIAAsI;IACtI,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;CACjC;AAED,wBAAgB,cAAc,CAAC,KAAK,GAAG,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,qBAAqB,GAAG,eAAe,CAAC,KAAK,CAAC,CAavH;AAED,iHAAiH;AACjH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,qBAAqB,GAAG,WAAW,EAAE,CAEvI"}
@@ -0,0 +1,21 @@
1
+ import { renderArticle } from "../render/render-article.js";
2
+ import { getTermPath } from "./paths.js";
3
+ export function renderPageBody(text, input) {
4
+ return renderArticle(text.bodyMarkdown, {
5
+ glossary: input.glossary,
6
+ ...(text.kind === "term" ? { selfSlug: text.slug } : {}),
7
+ termHref: (slug) => getTermPath(input.routes, slug),
8
+ siteHosts: [...input.origins.map((origin) => new URL(origin).hostname), ...input.options.siteHosts],
9
+ ...(input.options.images === undefined ? {} : { images: input.options.images }),
10
+ // The options keep plugins untyped (`unknown` nodes); the pages render React nodes, the type the
11
+ // app's plugins return.
12
+ blocks: input.options.blocks,
13
+ article: { currentAsOf: text.currentAsOf, fields: text.fields },
14
+ messages: input.messages.render,
15
+ });
16
+ }
17
+ /** Published articles whose body links the term, by hand or by its first mention: the same rule as the links. */
18
+ export function findArticlesLinkingTerm(articles, termSlug, input) {
19
+ return articles.filter((article) => renderPageBody(article, input).linkedTerms.includes(termSlug));
20
+ }
21
+ //# sourceMappingURL=body.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"body.js","sourceRoot":"","sources":["../../src/pages/body.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,aAAa,EAA0C,MAAM,6BAA6B,CAAC;AACpG,OAAO,EAAE,WAAW,EAAmB,MAAM,YAAY,CAAC;AAW1D,MAAM,UAAU,cAAc,CAAkB,IAAiB,EAAE,KAA4B;IAC7F,OAAO,aAAa,CAAQ,IAAI,CAAC,YAAY,EAAE;QAC7C,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACxD,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC;QACnD,SAAS,EAAE,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC;QACnG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;QAC/E,iGAAiG;QACjG,wBAAwB;QACxB,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,MAAuC;QAC7D,OAAO,EAAE,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE;QAC/D,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,MAAM;KAChC,CAAC,CAAC;AACL,CAAC;AAED,iHAAiH;AACjH,MAAM,UAAU,uBAAuB,CAAC,QAAgC,EAAE,QAAgB,EAAE,KAA4B;IACtH,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,cAAc,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;AACrG,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type { Locale } from "@softure-ai/core";
2
+ import type { BlogArticle } from "../contract.js";
3
+ export interface ArticleDates {
4
+ /** The day of first publication, `YYYY-MM-DD`. */
5
+ readonly published: string;
6
+ /** The day of the last content change; `null` without one, or when it falls on the publication day. */
7
+ readonly updated: string | null;
8
+ /** The day the facts were checked, `YYYY-MM-DD`. */
9
+ readonly currentAsOf: string;
10
+ }
11
+ /** The calendar day of a moment in a time zone, `YYYY-MM-DD`. */
12
+ export declare function getDayInZone(moment: Date, timezone: string): string;
13
+ /**
14
+ * The visible dates of a published text. Two equal days one under the other read like a mistake, so
15
+ * "updated" disappears when it falls on the publication day. A text without a publication date has
16
+ * no page: calling this for one is a bug.
17
+ */
18
+ export declare function getArticleDates(article: Pick<BlogArticle, "slug" | "publishedAt" | "updatedAt" | "currentAsOf">, timezone: string): ArticleDates;
19
+ /** A `YYYY-MM-DD` day as the locale writes it in full (en: "October 4, 2026"). */
20
+ export declare function formatDay(day: string, locale: Locale): string;
21
+ //# sourceMappingURL=dates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dates.d.ts","sourceRoot":"","sources":["../../src/pages/dates.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,MAAM,WAAW,YAAY;IAC3B,kDAAkD;IAClD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,uGAAuG;IACvG,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,oDAAoD;IACpD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,iEAAiE;AACjE,wBAAgB,YAAY,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGnE;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,GAAG,WAAW,GAAG,aAAa,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,YAAY,CAOhJ;AAED,kFAAkF;AAClF,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAG7D"}
@@ -0,0 +1,24 @@
1
+ /** The calendar day of a moment in a time zone, `YYYY-MM-DD`. */
2
+ export function getDayInZone(moment, timezone) {
3
+ // `en-CA` formats a date as YYYY-MM-DD.
4
+ return new Intl.DateTimeFormat("en-CA", { timeZone: timezone, year: "numeric", month: "2-digit", day: "2-digit" }).format(moment);
5
+ }
6
+ /**
7
+ * The visible dates of a published text. Two equal days one under the other read like a mistake, so
8
+ * "updated" disappears when it falls on the publication day. A text without a publication date has
9
+ * no page: calling this for one is a bug.
10
+ */
11
+ export function getArticleDates(article, timezone) {
12
+ if (article.publishedAt === null) {
13
+ throw new Error(`getArticleDates: article ${article.slug} has no published_at; pages show only published texts`);
14
+ }
15
+ const published = getDayInZone(article.publishedAt, timezone);
16
+ const updated = article.updatedAt === null ? null : getDayInZone(article.updatedAt, timezone);
17
+ return { published, updated: updated === published ? null : updated, currentAsOf: article.currentAsOf };
18
+ }
19
+ /** A `YYYY-MM-DD` day as the locale writes it in full (en: "October 4, 2026"). */
20
+ export function formatDay(day, locale) {
21
+ // The day is already a calendar day: format it at UTC midnight in UTC, so no zone shifts it.
22
+ return new Intl.DateTimeFormat(locale, { dateStyle: "long", timeZone: "UTC" }).format(new Date(`${day}T00:00:00Z`));
23
+ }
24
+ //# sourceMappingURL=dates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dates.js","sourceRoot":"","sources":["../../src/pages/dates.ts"],"names":[],"mappings":"AAcA,iEAAiE;AACjE,MAAM,UAAU,YAAY,CAAC,MAAY,EAAE,QAAgB;IACzD,wCAAwC;IACxC,OAAO,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AACpI,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,OAAgF,EAAE,QAAgB;IAChI,IAAI,OAAO,CAAC,WAAW,KAAK,IAAI,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,4BAA4B,OAAO,CAAC,IAAI,uDAAuD,CAAC,CAAC;IACnH,CAAC;IACD,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IAC9D,MAAM,OAAO,GAAG,OAAO,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IAC9F,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC;AAC1G,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,SAAS,CAAC,GAAW,EAAE,MAAc;IACnD,6FAA6F;IAC7F,OAAO,IAAI,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,GAAG,GAAG,YAAY,CAAC,CAAC,CAAC;AACtH,CAAC"}
@@ -0,0 +1,7 @@
1
+ export { findArticlesLinkingTerm, renderPageBody, type RenderPageBodyOptions } from "./body.js";
2
+ export { formatDay, getArticleDates, getDayInZone, type ArticleDates } from "./dates.js";
3
+ export { getArticleImageUrl, getArticleJsonLd, getGlossaryJsonLd, getTermJsonLd, serializeJsonLd, type JsonLdContext } from "./json-ld.js";
4
+ export { getArticleCrumbs, getClusterAnchor, getClusterLabel, getTermCrumbs, groupByCluster, sortTerms, splitClusterLead, type ClusterGroup, type Crumb, type CrumbLabels, } from "./listing.js";
5
+ export { getArticlePath, getReservedSlugs, getTermPath, getTextPath, matchBlogPath, normalizeRoute, type BlogPathKind, type BlogPathMatch, type BlogRoutes, } from "./paths.js";
6
+ export { buildGonePage, createCachedBlogPathDecider, decideBlogPath, type BlogPathDecision, type BlogPathLookup, type CachedDeciderOptions, type GonePageCopy, } from "./redirects.js";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pages/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,uBAAuB,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAChG,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AACzF,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,aAAa,EAAE,eAAe,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAC3I,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,cAAc,EACd,SAAS,EACT,gBAAgB,EAChB,KAAK,YAAY,EACjB,KAAK,KAAK,EACV,KAAK,WAAW,GACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,WAAW,EACX,aAAa,EACb,cAAc,EACd,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,UAAU,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,aAAa,EACb,2BAA2B,EAC3B,cAAc,EACd,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,YAAY,GAClB,MAAM,gBAAgB,CAAC"}