@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 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/proxy/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ export type BlockKind = "heading" | "paragraph" | "listItem" | "table" | "quote" | "footnote" | "directive";
2
+ export interface Block {
3
+ readonly kind: BlockKind;
4
+ /** The text without the block marker (`#`, `-`, `>`, `[^id]:`); lines of a paragraph joined by spaces. */
5
+ text: string;
6
+ /** The line of the file, from 1. */
7
+ readonly line: number;
8
+ /** Heading level, 1–6. */
9
+ readonly level?: number;
10
+ /** Footnote id of a definition. */
11
+ readonly footnoteId?: string;
12
+ }
13
+ /** The body of an article file and the file line it starts on, or `null` without a frontmatter. */
14
+ export declare function splitArticleBody(text: string): {
15
+ readonly body: string;
16
+ readonly bodyStartLine: number;
17
+ } | null;
18
+ export declare function splitBlocks(body: string, bodyStartLine?: number): Block[];
19
+ /** The blocks a reader reads as text: no footnote definitions, no directives. */
20
+ export declare function getProseBlocks(blocks: readonly Block[]): Block[];
21
+ //# sourceMappingURL=blocks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blocks.d.ts","sourceRoot":"","sources":["../../src/quality/blocks.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG,UAAU,GAAG,WAAW,CAAC;AAE5G,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,0GAA0G;IAC1G,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0BAA0B;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,mCAAmC;IACnC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,mGAAmG;AACnG,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAM/G;AAaD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,SAAI,GAAG,KAAK,EAAE,CAmFpE;AAID,iFAAiF;AACjF,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,GAAG,KAAK,EAAE,CAEhE"}
@@ -0,0 +1,113 @@
1
+ // The body of an article cut into blocks with file lines (FIRE_TRACKER `src/lib/blog/quality/parse.ts`,
2
+ // `splitBlocks`). Line-level rules need no Markdown tree: headings, paragraphs, list items, tables,
3
+ // quotes, footnote definitions and `::` directives are enough. Fenced code and HTML comments are
4
+ // skipped: the gate judges prose, not examples.
5
+ /** The body of an article file and the file line it starts on, or `null` without a frontmatter. */
6
+ export function splitArticleBody(text) {
7
+ const lines = text.replace(/^\uFEFF/, "").replace(/\r\n?/g, "\n").split("\n");
8
+ if (lines[0] !== "---")
9
+ return null;
10
+ const end = lines.indexOf("---", 1);
11
+ if (end === -1)
12
+ return null;
13
+ return { body: lines.slice(end + 1).join("\n"), bodyStartLine: end + 2 };
14
+ }
15
+ const HEADING = /^(#{1,6})\s+(.*)$/;
16
+ const LIST_ITEM = /^\s*(?:[-*+]|\d+[.)])\s+(.*)$/;
17
+ const FOOTNOTE_DEF = /^\[\^([^\]]+)\]:\s*(.*)$/;
18
+ const FENCE = /^\s*(```|~~~)/;
19
+ const CONTINUATION = /^\s{2,}\S/;
20
+ export function splitBlocks(body, bodyStartLine = 1) {
21
+ const blocks = [];
22
+ let paragraph = null;
23
+ let table = null;
24
+ let inFence = false;
25
+ let inComment = false;
26
+ const flush = () => {
27
+ if (paragraph !== null)
28
+ blocks.push({ kind: "paragraph", text: paragraph.lines.join(" "), line: paragraph.line });
29
+ if (table !== null)
30
+ blocks.push({ kind: "table", text: table.lines.join("\n"), line: table.line });
31
+ paragraph = null;
32
+ table = null;
33
+ };
34
+ for (const [index, raw] of body.split("\n").entries()) {
35
+ const lineNo = bodyStartLine + index;
36
+ const line = raw.trimEnd();
37
+ const trimmed = line.trim();
38
+ if (FENCE.test(line)) {
39
+ flush();
40
+ inFence = !inFence;
41
+ continue;
42
+ }
43
+ if (inFence)
44
+ continue;
45
+ if (inComment) {
46
+ if (line.includes("-->"))
47
+ inComment = false;
48
+ continue;
49
+ }
50
+ if (trimmed.startsWith("<!--")) {
51
+ flush();
52
+ inComment = !line.includes("-->");
53
+ continue;
54
+ }
55
+ if (trimmed === "") {
56
+ flush();
57
+ continue;
58
+ }
59
+ const heading = HEADING.exec(line);
60
+ if (heading !== null) {
61
+ flush();
62
+ blocks.push({ kind: "heading", text: (heading[2] ?? "").trim(), line: lineNo, level: (heading[1] ?? "#").length });
63
+ continue;
64
+ }
65
+ const footnote = FOOTNOTE_DEF.exec(line);
66
+ if (footnote !== null) {
67
+ flush();
68
+ blocks.push({ kind: "footnote", text: (footnote[2] ?? "").trim(), line: lineNo, footnoteId: footnote[1] ?? "" });
69
+ continue;
70
+ }
71
+ if (trimmed.startsWith("::")) {
72
+ flush();
73
+ blocks.push({ kind: "directive", text: trimmed, line: lineNo });
74
+ continue;
75
+ }
76
+ if (trimmed.startsWith("|")) {
77
+ if (paragraph !== null)
78
+ flush();
79
+ table ??= { lines: [], line: lineNo };
80
+ table.lines.push(trimmed);
81
+ continue;
82
+ }
83
+ const listItem = LIST_ITEM.exec(line);
84
+ if (listItem !== null) {
85
+ flush();
86
+ blocks.push({ kind: "listItem", text: (listItem[1] ?? "").trim(), line: lineNo });
87
+ continue;
88
+ }
89
+ if (trimmed.startsWith(">")) {
90
+ flush();
91
+ blocks.push({ kind: "quote", text: trimmed.replace(/^>\s?/, ""), line: lineNo });
92
+ continue;
93
+ }
94
+ // An indented line continues the list item or footnote above it.
95
+ const previous = blocks.at(-1);
96
+ if (paragraph === null && table === null && CONTINUATION.test(raw) && (previous?.kind === "listItem" || previous?.kind === "footnote")) {
97
+ previous.text = `${previous.text} ${trimmed}`;
98
+ continue;
99
+ }
100
+ if (table !== null)
101
+ flush();
102
+ paragraph ??= { lines: [], line: lineNo };
103
+ paragraph.lines.push(trimmed);
104
+ }
105
+ flush();
106
+ return blocks;
107
+ }
108
+ const PROSE_KINDS = new Set(["paragraph", "listItem", "table", "quote", "heading"]);
109
+ /** The blocks a reader reads as text: no footnote definitions, no directives. */
110
+ export function getProseBlocks(blocks) {
111
+ return blocks.filter((block) => PROSE_KINDS.has(block.kind));
112
+ }
113
+ //# sourceMappingURL=blocks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blocks.js","sourceRoot":"","sources":["../../src/quality/blocks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { QualityRuleInfo } from "./plugin.js";
2
+ import { type QualitySettings } from "./settings.js";
3
+ export declare const QUALITY_RULE_GROUPS: readonly ["file", "structure", "links", "images", "ymyl", "style", "voice", "plugin"];
4
+ export type QualityRuleGroup = (typeof QUALITY_RULE_GROUPS)[number];
5
+ export interface QualityCatalogRule extends QualityRuleInfo {
6
+ readonly group: QualityRuleGroup;
7
+ }
8
+ /** Every rule the gate can report with these settings, with the effective severity; rules switched off are left out. */
9
+ export declare function listQualityRules(settings: QualitySettings): QualityCatalogRule[];
10
+ //# sourceMappingURL=catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/quality/catalog.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAE3E,eAAO,MAAM,mBAAmB,uFAAwF,CAAC;AACzH,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpE,MAAM,WAAW,kBAAmB,SAAQ,eAAe;IACzD,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;CAClC;AAwDD,wHAAwH;AACxH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,eAAe,GAAG,kBAAkB,EAAE,CAgBhF"}
@@ -0,0 +1,71 @@
1
+ import { getEffectiveSeverity } from "./settings.js";
2
+ export const QUALITY_RULE_GROUPS = ["file", "structure", "links", "images", "ymyl", "style", "voice", "plugin"];
3
+ const rule = (group, id, severity, description) => ({ group, id, severity, description });
4
+ /** Rules that do not depend on the ruleset, the voice or plugins. */
5
+ const CORE_RULES = [
6
+ rule("file", "file", "error", "the file parses: frontmatter keys and values, slug equal to the file name"),
7
+ rule("structure", "title-length", "warning", "the title fits in search results (limits.titleChars)"),
8
+ rule("structure", "description-length", "warning", "the description is between limits.descriptionChars.min and max characters"),
9
+ rule("structure", "as-of-future", "error", "current_as_of is not in the future"),
10
+ rule("structure", "stale", "warning", "current_as_of is not older than limits.staleAfterDays"),
11
+ rule("structure", "summary-missing", "error", "an article has a summary"),
12
+ rule("structure", "lead", "error", "the text opens with a paragraph that answers"),
13
+ rule("structure", "lead-length", "warning", "the first paragraph fits in limits.leadWords"),
14
+ rule("structure", "lead-number", "error", "an article has a concrete number in its first two paragraphs"),
15
+ rule("structure", "heading-h1", "error", "no # heading in the body"),
16
+ rule("structure", "heading-order", "error", "headings do not skip a level"),
17
+ rule("structure", "sections", "error", "an article has at least two ## sections"),
18
+ rule("structure", "section-question", "error", "at least one ## heading of an article is a question"),
19
+ rule("structure", "section-answer", "error", "a question heading is followed by a paragraph"),
20
+ rule("structure", "section-answer-length", "warning", "the answer under a question fits in limits.answerWords"),
21
+ rule("structure", "length", "error", "the word count is within limits.words (a warning above the maximum)"),
22
+ rule("structure", "footnote-undefined", "error", "every footnote reference has a definition"),
23
+ rule("structure", "footnote-unused", "warning", "every footnote definition is referenced"),
24
+ rule("links", "internal-links", "error", "at least limits.internalLinks internal links (a warning for a term)"),
25
+ rule("links", "internal-link-target", "error", "every internal link leads to a page, article or glossary term"),
26
+ rule("links", "external-link-https", "warning", "external links use https"),
27
+ rule("links", "external-link-dead", "error", "external links answer 2xx (only with --external)"),
28
+ rule("links", "term-form-conflict", "error", "a glossary form belongs to one published term (only in check; publish refuses it always)"),
29
+ rule("images", "image-source", "error", "an image comes from the site or a host of blog({ images: { hosts } })"),
30
+ rule("images", "image-alt", "error", "an image has alt text"),
31
+ rule("images", "image-dimensions", "error", "the app knows an image's width and height (blog({ images: { dimensions } }))"),
32
+ rule("style", "dashes", "error", "at most one dash per limits.wordsPerDash words"),
33
+ rule("style", "dashes-paragraph", "warning", "at most one dash per paragraph"),
34
+ rule("style", "bold-density", "warning", "at most one bold phrase per limits.wordsPerBold words"),
35
+ rule("style", "bold-labels", "warning", "fewer than three list items start with a bold label"),
36
+ rule("style", "triads", "warning", "at most three lists of three"),
37
+ rule("style", "long-sentences", "warning", "sentences fit in limits.sentenceWords"),
38
+ rule("style", "monotone-rhythm", "warning", "sentence lengths vary"),
39
+ rule("style", "repeated-openings", "warning", "no three sentences in a row start with the same word"),
40
+ ];
41
+ const YMYL_RULES = [
42
+ rule("ymyl", "sources-missing", "error", "an article lists its sources"),
43
+ rule("ymyl", "source-https", "error", "every source address uses https"),
44
+ rule("ymyl", "number-source", "error", "every significant number (amount, percentage, 1000 or more) carries a footnote"),
45
+ rule("ymyl", "footnote-source", "error", "every footnote has a source address or the app's own calculation mark"),
46
+ rule("ymyl", "footnote-not-in-sources", "error", "every address in a footnote is a listed source"),
47
+ rule("ymyl", "profit-promise", "error", "no promise of profit and no order to buy"),
48
+ ];
49
+ const PLUGIN_RULES = [
50
+ rule("plugin", "plugin-failed", "error", "a rule plugin ran without throwing"),
51
+ rule("plugin", "plugin-rule-undeclared", "error", "a rule plugin reports only the rules it declares"),
52
+ ];
53
+ /** Every rule the gate can report with these settings, with the effective severity; rules switched off are left out. */
54
+ export function listQualityRules(settings) {
55
+ const { ruleset, options } = settings;
56
+ const rules = [
57
+ ...CORE_RULES,
58
+ ...(ruleset.flagsTitleCaseHeadings ? [rule("style", "title-case-heading", "warning", "headings use sentence case")] : []),
59
+ ...ruleset.patterns.map((pattern) => rule("style", pattern.id, pattern.severity, pattern.message)),
60
+ ...settings.voicePatterns.map((pattern) => rule("voice", pattern.id, pattern.severity, pattern.message)),
61
+ ...(options.ymyl === null ? [] : YMYL_RULES),
62
+ ...(options.blocks.length === 0 ? [] : [rule("structure", "block-requires", "error", "a block plugin's fenced block has the frontmatter keys it requires")]),
63
+ ...(options.plugins.length === 0 ? [] : PLUGIN_RULES),
64
+ ...options.plugins.flatMap((plugin) => plugin.rules.map((info) => rule("plugin", info.id, info.severity, info.description))),
65
+ ];
66
+ return rules.flatMap((entry) => {
67
+ const severity = getEffectiveSeverity(settings, entry.id, entry.severity);
68
+ return severity === null ? [] : [{ ...entry, severity }];
69
+ });
70
+ }
71
+ //# sourceMappingURL=catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../src/quality/catalog.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import type { BlogArticleInput } from "../contract.js";
2
+ import { type ParseArticleFileOptions } from "../content/article-file.js";
3
+ import { type QualityFinding } from "./finding.js";
4
+ import { type InternalLinkResolver } from "./rules/links.js";
5
+ import { type QualitySettings } from "./settings.js";
6
+ export interface QualityCheckResult {
7
+ readonly findings: readonly QualityFinding[];
8
+ /** External addresses for the network check (`--external`). */
9
+ readonly externalLinks: readonly {
10
+ readonly url: string;
11
+ readonly line: number;
12
+ }[];
13
+ }
14
+ export interface CheckArticleInput {
15
+ /** The parsed file. */
16
+ readonly article: BlogArticleInput;
17
+ /** The whole file text, for the body's line numbers. */
18
+ readonly text: string;
19
+ readonly settings: QualitySettings;
20
+ /** `YYYY-MM-DD` in the app's time zone. */
21
+ readonly today: string;
22
+ /** Default: every internal link exists (the publish gate; `check` resolves them). */
23
+ readonly resolveInternalLink?: InternalLinkResolver;
24
+ }
25
+ export declare function checkArticle(input: CheckArticleInput): QualityCheckResult;
26
+ export interface CheckArticleTextInput extends Omit<CheckArticleInput, "article"> {
27
+ /** A path or a bare name; it must read `<slug>.md`. */
28
+ readonly fileName: string;
29
+ /** The app's `fields` and reserved slugs, as the publish run applies them. */
30
+ readonly parse?: ParseArticleFileOptions;
31
+ }
32
+ /** Parses the file first; parse errors are `file` findings and stop the check. */
33
+ export declare function checkArticleText(input: CheckArticleTextInput): QualityCheckResult;
34
+ //# sourceMappingURL=check-article.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-article.d.ts","sourceRoot":"","sources":["../../src/quality/check-article.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAGvD,OAAO,EAAoB,KAAK,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAE5F,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAKjE,OAAO,EAA4B,KAAK,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAIvF,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAE3E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAC;IAC7C,+DAA+D;IAC/D,QAAQ,CAAC,aAAa,EAAE,SAAS;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACpF;AAED,MAAM,WAAW,iBAAiB;IAChC,uBAAuB;IACvB,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,wDAAwD;IACxD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,2CAA2C;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,qFAAqF;IACrF,QAAQ,CAAC,mBAAmB,CAAC,EAAE,oBAAoB,CAAC;CACrD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,kBAAkB,CA2BzE;AAED,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CAAC,iBAAiB,EAAE,SAAS,CAAC;IAC/E,uDAAuD;IACvD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,CAAC,EAAE,uBAAuB,CAAC;CAC1C;AAED,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,kBAAkB,CAMjF"}
@@ -0,0 +1,74 @@
1
+ import { findArticleImages } from "../render/images.js";
2
+ import { findArticleBlocks } from "../render/render-article.js";
3
+ import { parseArticleFile } from "../content/article-file.js";
4
+ import { splitArticleBody, splitBlocks } from "./blocks.js";
5
+ import { sortFindings } from "./finding.js";
6
+ import { checkBlockRequires } from "./rules/blocks.js";
7
+ import { checkImages } from "./rules/images.js";
8
+ import { checkLinks, collectLinks } from "./rules/links.js";
9
+ import { checkFootnotes, checkLead, checkLength, checkMetadata, checkSections } from "./rules/structure.js";
10
+ import { checkMetadataStyle, checkRhythm, checkStyle } from "./rules/style.js";
11
+ import { checkNumberSources, checkSources } from "./rules/ymyl.js";
12
+ import { getEffectiveSeverity } from "./settings.js";
13
+ export function checkArticle(input) {
14
+ const { article, settings, today } = input;
15
+ const split = splitArticleBody(input.text);
16
+ const blocks = split === null ? splitBlocks(article.bodyMarkdown) : splitBlocks(split.body, split.bodyStartLine);
17
+ const body = split?.body ?? article.bodyMarkdown;
18
+ const bodyStartLine = split?.bodyStartLine ?? 1;
19
+ const pluginBlocks = findPluginBlocks(body, bodyStartLine, settings);
20
+ const images = findArticleImages(body).map((image) => ({ ...image, line: image.line + bodyStartLine - 1 }));
21
+ const ruleInput = { article, blocks, settings, today };
22
+ const links = collectLinks(ruleInput);
23
+ const builtIn = [
24
+ ...checkMetadata(ruleInput),
25
+ ...checkMetadataStyle(ruleInput),
26
+ ...checkLead(ruleInput),
27
+ ...checkSections(ruleInput),
28
+ ...checkLength(ruleInput),
29
+ ...checkFootnotes(ruleInput),
30
+ ...(settings.options.ymyl === null ? [] : [...checkSources(ruleInput), ...checkNumberSources(ruleInput)]),
31
+ ...checkLinks(ruleInput, links, input.resolveInternalLink ?? (() => true)),
32
+ ...checkStyle(ruleInput),
33
+ ...checkRhythm(ruleInput),
34
+ ...checkBlockRequires(article, pluginBlocks),
35
+ ...checkImages(images, settings.images),
36
+ ];
37
+ const fromPlugins = settings.options.plugins.flatMap((plugin) => runPlugin(plugin, { article, blocks, pluginBlocks, today, ruleset: settings.ruleset }));
38
+ return { findings: applySeverity(settings, [...builtIn, ...fromPlugins]), externalLinks: links.external };
39
+ }
40
+ /** Parses the file first; parse errors are `file` findings and stop the check. */
41
+ export function checkArticleText(input) {
42
+ const parsed = parseArticleFile(input.text, input.fileName, input.parse);
43
+ if (!parsed.ok) {
44
+ return { findings: applySeverity(input.settings, parsed.errors.map((message) => ({ rule: "file", severity: "error", message }))), externalLinks: [] };
45
+ }
46
+ return checkArticle({ ...input, article: parsed.article });
47
+ }
48
+ /** The app's plugin blocks with file lines; none without `quality.blocks`. */
49
+ function findPluginBlocks(body, bodyStartLine, settings) {
50
+ if (settings.options.blocks.length === 0)
51
+ return [];
52
+ return findArticleBlocks(body, settings.options.blocks).map((block) => ({ ...block, line: block.line + bodyStartLine - 1 }));
53
+ }
54
+ function runPlugin(plugin, context) {
55
+ let findings;
56
+ try {
57
+ findings = plugin.check(context);
58
+ }
59
+ catch (error) {
60
+ // A plugin bug must not crash a publish with a stack trace; it refuses the file instead.
61
+ return [{ rule: "plugin-failed", severity: "error", message: `plugin ${plugin.name} failed: ${error instanceof Error ? error.message : String(error)}` }];
62
+ }
63
+ const declared = new Set(plugin.rules.map((info) => info.id));
64
+ return findings.map((finding) => declared.has(finding.rule)
65
+ ? finding
66
+ : { rule: "plugin-rule-undeclared", severity: "error", message: `plugin ${plugin.name} reported rule ${finding.rule}, which it does not declare`, ...(finding.line === undefined ? {} : { line: finding.line }) });
67
+ }
68
+ function applySeverity(settings, findings) {
69
+ return sortFindings(findings.flatMap((finding) => {
70
+ const severity = getEffectiveSeverity(settings, finding.rule, finding.severity);
71
+ return severity === null ? [] : [{ ...finding, severity }];
72
+ }));
73
+ }
74
+ //# sourceMappingURL=check-article.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-article.js","sourceRoot":"","sources":["../../src/quality/check-article.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { ArticleFile } from "../db/publish-run.js";
2
+ import { type ParseArticleFileOptions } from "../content/article-file.js";
3
+ import { type GlossaryTerm } from "../render/glossary.js";
4
+ import { type FetchLike } from "./external-links.js";
5
+ import { type QualityFinding } from "./finding.js";
6
+ import type { InternalLinkResolver } from "./rules/links.js";
7
+ import { type QualitySettings } from "./settings.js";
8
+ export interface CheckArticleFilesOptions {
9
+ readonly settings: QualitySettings;
10
+ readonly today: string;
11
+ readonly resolveInternalLink: InternalLinkResolver;
12
+ readonly parse?: ParseArticleFileOptions;
13
+ /** When given, external links are requested over the network. */
14
+ readonly fetch?: FetchLike;
15
+ /** The site's published terms (`readGlossaryTerms`); a checked term that shares a form with another is reported. */
16
+ readonly glossary?: readonly GlossaryTerm[];
17
+ }
18
+ export interface FileCheckResult {
19
+ readonly file: string;
20
+ readonly findings: readonly QualityFinding[];
21
+ }
22
+ export declare function checkArticleFiles(files: readonly ArticleFile[], options: CheckArticleFilesOptions): Promise<FileCheckResult[]>;
23
+ //# sourceMappingURL=check-files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-files.d.ts","sourceRoot":"","sources":["../../src/quality/check-files.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAoB,KAAK,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAC5F,OAAO,EAAyB,KAAK,YAAY,EAAyB,MAAM,uBAAuB,CAAC;AAExG,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACzE,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAE3E,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,mBAAmB,EAAE,oBAAoB,CAAC;IACnD,QAAQ,CAAC,KAAK,CAAC,EAAE,uBAAuB,CAAC;IACzC,iEAAiE;IACjE,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC;IAC3B,oHAAoH;IACpH,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;CAC7C;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAC;CAC9C;AAED,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,EAAE,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAiBpI"}
@@ -0,0 +1,44 @@
1
+ import { parseArticleFile } from "../content/article-file.js";
2
+ import { findTermFormConflicts } from "../render/glossary.js";
3
+ import { checkArticleText } from "./check-article.js";
4
+ import { checkExternalLinks } from "./external-links.js";
5
+ import { sortFindings } from "./finding.js";
6
+ import { getEffectiveSeverity } from "./settings.js";
7
+ export async function checkArticleFiles(files, options) {
8
+ const results = [];
9
+ const conflicts = findTermFormConflicts(options.glossary ?? []);
10
+ for (const file of files) {
11
+ const result = checkArticleText({
12
+ text: file.text,
13
+ fileName: file.name,
14
+ settings: options.settings,
15
+ today: options.today,
16
+ resolveInternalLink: options.resolveInternalLink,
17
+ ...(options.parse === undefined ? {} : { parse: options.parse }),
18
+ });
19
+ const external = options.fetch === undefined ? [] : await checkExternalLinks(result.externalLinks, options.fetch);
20
+ const forms = checkTermForms(file, conflicts, options);
21
+ results.push({ file: file.name, findings: sortFindings([...result.findings, ...external, ...forms]) });
22
+ }
23
+ return results;
24
+ }
25
+ /** `term-form-conflict` for a published term file whose forms another term claims too. */
26
+ function checkTermForms(file, conflicts, options) {
27
+ if (conflicts.length === 0)
28
+ return [];
29
+ const parsed = parseArticleFile(file.text, file.name, options.parse);
30
+ if (!parsed.ok || parsed.article.kind !== "term" || parsed.article.status !== "published")
31
+ return [];
32
+ const { slug } = parsed.article;
33
+ const severity = getEffectiveSeverity(options.settings, "term-form-conflict", "error");
34
+ if (severity === null)
35
+ return [];
36
+ return conflicts
37
+ .filter((conflict) => conflict.slugs.includes(slug))
38
+ .map((conflict) => ({
39
+ rule: "term-form-conflict",
40
+ severity,
41
+ message: `form "${conflict.form}" is also a form of term ${conflict.slugs.filter((other) => other !== slug).join(", ")}; a form belongs to one term, so remove it from all but one`,
42
+ }));
43
+ }
44
+ //# sourceMappingURL=check-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-files.js","sourceRoot":"","sources":["../../src/quality/check-files.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { QualityFinding } from "./finding.js";
2
+ export type FetchLike = (url: string, init: {
3
+ readonly method: string;
4
+ readonly redirect: "follow";
5
+ readonly signal: AbortSignal;
6
+ readonly headers: Record<string, string>;
7
+ }) => Promise<{
8
+ readonly status: number;
9
+ }>;
10
+ export declare function checkExternalUrl(url: string, fetchImpl: FetchLike): Promise<{
11
+ ok: boolean;
12
+ detail: string;
13
+ }>;
14
+ /** Each address once, at its first line. */
15
+ export declare function checkExternalLinks(links: readonly {
16
+ readonly url: string;
17
+ readonly line: number;
18
+ }[], fetchImpl: FetchLike): Promise<QualityFinding[]>;
19
+ //# sourceMappingURL=external-links.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"external-links.d.ts","sourceRoot":"","sources":["../../src/quality/external-links.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,MAAM,MAAM,SAAS,GAAG,CACtB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,KACnI,OAAO,CAAC;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAQ1C,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAUlH;AAED,4CAA4C;AAC5C,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,SAAS;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAO3J"}
@@ -0,0 +1,30 @@
1
+ /** Some public-sector servers refuse requests that do not introduce themselves. */
2
+ const USER_AGENT = "softure-blog-link-check/1.0";
3
+ /** Servers without HEAD support answer with these; then the check asks with GET. */
4
+ const RETRY_WITH_GET = new Set([400, 403, 404, 405, 501]);
5
+ const TIMEOUT_MS = 15_000;
6
+ export async function checkExternalUrl(url, fetchImpl) {
7
+ const request = (method) => fetchImpl(url, { method, redirect: "follow", signal: AbortSignal.timeout(TIMEOUT_MS), headers: { "user-agent": USER_AGENT } });
8
+ try {
9
+ let response = await request("HEAD");
10
+ if (RETRY_WITH_GET.has(response.status))
11
+ response = await request("GET");
12
+ return { ok: response.status >= 200 && response.status < 300, detail: `HTTP ${String(response.status)}` };
13
+ }
14
+ catch (error) {
15
+ // A network failure is the finding itself: its message tells the editor what happened.
16
+ return { ok: false, detail: error instanceof Error ? error.message : String(error) };
17
+ }
18
+ }
19
+ /** Each address once, at its first line. */
20
+ export async function checkExternalLinks(links, fetchImpl) {
21
+ const unique = new Map();
22
+ for (const link of links)
23
+ if (!unique.has(link.url))
24
+ unique.set(link.url, link.line);
25
+ const results = await Promise.all([...unique].map(async ([url, line]) => ({ url, line, ...(await checkExternalUrl(url, fetchImpl)) })));
26
+ return results
27
+ .filter((result) => !result.ok)
28
+ .map((result) => ({ rule: "external-link-dead", severity: "error", message: `an external link does not answer 2xx (${result.detail}): ${result.url}`, line: result.line }));
29
+ }
30
+ //# sourceMappingURL=external-links.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"external-links.js","sourceRoot":"","sources":["../../src/quality/external-links.ts"],"names":[],"mappings":"AAUA,mFAAmF;AACnF,MAAM,UAAU,GAAG,6BAA6B,CAAC;AACjD,oFAAoF;AACpF,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;AAC1D,MAAM,UAAU,GAAG,MAAM,CAAC;AAE1B,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,GAAW,EAAE,SAAoB;IACtE,MAAM,OAAO,GAAG,CAAC,MAAc,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,EAAE,YAAY,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC;IACnK,IAAI,CAAC;QACH,IAAI,QAAQ,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;QACrC,IAAI,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,QAAQ,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC;QACzE,OAAO,EAAE,EAAE,EAAE,QAAQ,CAAC,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,EAAE,MAAM,EAAE,QAAQ,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;IAC5G,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,uFAAuF;QACvF,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;IACvF,CAAC;AACH,CAAC;AAED,4CAA4C;AAC5C,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,KAAiE,EAAE,SAAoB;IAC9H,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACrF,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,gBAAgB,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACxI,OAAO,OAAO;SACX,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;SAC9B,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,oBAAoB,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,yCAAyC,MAAM,CAAC,MAAM,MAAM,MAAM,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AAChL,CAAC"}
@@ -0,0 +1,17 @@
1
+ export declare const QUALITY_SEVERITIES: readonly ["error", "warning"];
2
+ export type QualitySeverity = (typeof QUALITY_SEVERITIES)[number];
3
+ export interface QualityFinding {
4
+ /** The stable id of the rule, used for severity overrides and by the writing skill. */
5
+ readonly rule: string;
6
+ readonly severity: QualitySeverity;
7
+ /** What is wrong and what to do instead, in English. */
8
+ readonly message: string;
9
+ /** The line of the file, from 1, when the rule points at one. */
10
+ readonly line?: number;
11
+ }
12
+ export declare function hasQualityErrors(findings: readonly QualityFinding[]): boolean;
13
+ /** Errors first, then by line; findings without a line go first within their severity. */
14
+ export declare function sortFindings(findings: readonly QualityFinding[]): QualityFinding[];
15
+ /** `[rule] line N: message`, the form the publish command prints. */
16
+ export declare function formatFinding(finding: QualityFinding): string;
17
+ //# sourceMappingURL=finding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding.d.ts","sourceRoot":"","sources":["../../src/quality/finding.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,kBAAkB,+BAAgC,CAAC;AAChE,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAElE,MAAM,WAAW,cAAc;IAC7B,uFAAuF;IACvF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,wDAAwD;IACxD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,GAAG,OAAO,CAE7E;AAED,0FAA0F;AAC1F,wBAAgB,YAAY,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,GAAG,cAAc,EAAE,CAElF;AAED,qEAAqE;AACrE,wBAAgB,aAAa,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,CAE7D"}
@@ -0,0 +1,15 @@
1
+ // A finding of the quality gate (FIRE_TRACKER `src/lib/blog/quality/finding.ts`). An error refuses a
2
+ // publish and fails `softure-blog check`; a warning only informs.
3
+ export const QUALITY_SEVERITIES = ["error", "warning"];
4
+ export function hasQualityErrors(findings) {
5
+ return findings.some((finding) => finding.severity === "error");
6
+ }
7
+ /** Errors first, then by line; findings without a line go first within their severity. */
8
+ export function sortFindings(findings) {
9
+ return [...findings].sort((a, b) => (a.severity === b.severity ? (a.line ?? 0) - (b.line ?? 0) : a.severity === "error" ? -1 : 1));
10
+ }
11
+ /** `[rule] line N: message`, the form the publish command prints. */
12
+ export function formatFinding(finding) {
13
+ return `[${finding.rule}]${finding.line === undefined ? "" : ` line ${String(finding.line)}`}: ${finding.message}`;
14
+ }
15
+ //# sourceMappingURL=finding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding.js","sourceRoot":"","sources":["../../src/quality/finding.ts"],"names":[],"mappings":"AAAA,qGAAqG;AACrG,kEAAkE;AAElE,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,SAAS,CAAU,CAAC;AAahE,MAAM,UAAU,gBAAgB,CAAC,QAAmC;IAClE,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC;AAClE,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,YAAY,CAAC,QAAmC;IAC9D,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACrI,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,aAAa,CAAC,OAAuB;IACnD,OAAO,IAAI,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC;AACrH,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { Clock } from "@softure-ai/core";
2
+ import type { PublishGate } from "../db/publish-run.js";
3
+ import { type QualitySettings } from "./settings.js";
4
+ export declare function createQualityGate(settings: QualitySettings, clock: Clock): PublishGate;
5
+ //# sourceMappingURL=gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../src/quality/gate.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGxD,OAAO,EAAgB,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAEnE,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,eAAe,EAAE,KAAK,EAAE,KAAK,GAAG,WAAW,CAKtF"}
@@ -0,0 +1,10 @@
1
+ import { checkArticle } from "./check-article.js";
2
+ import { formatFinding } from "./finding.js";
3
+ import { getLocalDate } from "./settings.js";
4
+ export function createQualityGate(settings, clock) {
5
+ return (file, article) => {
6
+ const { findings } = checkArticle({ article, text: file.text, settings, today: getLocalDate(clock.now(), settings.timeZone) });
7
+ return findings.filter((finding) => finding.severity === "error").map(formatFinding);
8
+ };
9
+ }
10
+ //# sourceMappingURL=gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate.js","sourceRoot":"","sources":["../../src/quality/gate.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAwB,MAAM,eAAe,CAAC;AAEnE,MAAM,UAAU,iBAAiB,CAAC,QAAyB,EAAE,KAAY;IACvE,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE;QACvB,MAAM,EAAE,QAAQ,EAAE,GAAG,YAAY,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC/H,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IACvF,CAAC,CAAC;AACJ,CAAC"}