@kensio/colophon 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (357) hide show
  1. package/README.md +608 -34
  2. package/dist/cli/args.d.ts +14 -0
  3. package/dist/cli/args.d.ts.map +1 -0
  4. package/dist/cli/args.js +67 -0
  5. package/dist/cli/args.js.map +1 -0
  6. package/dist/cli/config.d.ts +12 -0
  7. package/dist/cli/config.d.ts.map +1 -0
  8. package/dist/cli/config.js +43 -0
  9. package/dist/cli/config.js.map +1 -0
  10. package/dist/cli/index.d.ts +3 -0
  11. package/dist/cli/index.d.ts.map +1 -0
  12. package/dist/cli/index.js +35 -0
  13. package/dist/cli/index.js.map +1 -0
  14. package/dist/cli.js +34 -6
  15. package/dist/cli.js.map +1 -1
  16. package/dist/config/defaults.d.ts +64 -0
  17. package/dist/config/defaults.d.ts.map +1 -0
  18. package/dist/config/defaults.js +64 -0
  19. package/dist/config/defaults.js.map +1 -0
  20. package/dist/config/index.d.ts +22 -0
  21. package/dist/config/index.d.ts.map +1 -0
  22. package/dist/config/index.js +52 -0
  23. package/dist/config/index.js.map +1 -0
  24. package/dist/config/resolve.d.ts +24 -0
  25. package/dist/config/resolve.d.ts.map +1 -0
  26. package/dist/config/resolve.js +66 -0
  27. package/dist/config/resolve.js.map +1 -0
  28. package/dist/config/size.d.ts +20 -0
  29. package/dist/config/size.d.ts.map +1 -0
  30. package/dist/config/size.js +46 -0
  31. package/dist/config/size.js.map +1 -0
  32. package/dist/config.d.ts +33 -2
  33. package/dist/config.d.ts.map +1 -1
  34. package/dist/config.js +94 -4
  35. package/dist/config.js.map +1 -1
  36. package/dist/content/index.d.ts +5 -51
  37. package/dist/content/index.d.ts.map +1 -1
  38. package/dist/content/index.js +3 -115
  39. package/dist/content/index.js.map +1 -1
  40. package/dist/content/props.d.ts +15 -0
  41. package/dist/content/props.d.ts.map +1 -0
  42. package/dist/content/props.js +71 -0
  43. package/dist/content/props.js.map +1 -0
  44. package/dist/content/slug.d.ts +23 -0
  45. package/dist/content/slug.d.ts.map +1 -0
  46. package/dist/content/slug.js +52 -0
  47. package/dist/content/slug.js.map +1 -0
  48. package/dist/content/walk.d.ts +17 -0
  49. package/dist/content/walk.d.ts.map +1 -0
  50. package/dist/content/walk.js +57 -0
  51. package/dist/content/walk.js.map +1 -0
  52. package/dist/fonts/index.d.ts +10 -0
  53. package/dist/fonts/index.d.ts.map +1 -0
  54. package/dist/fonts/index.js +11 -0
  55. package/dist/fonts/index.js.map +1 -0
  56. package/dist/fonts/materialise.d.ts +7 -0
  57. package/dist/fonts/materialise.d.ts.map +1 -0
  58. package/dist/fonts/materialise.js +47 -0
  59. package/dist/fonts/materialise.js.map +1 -0
  60. package/dist/fonts/resolve.d.ts +7 -0
  61. package/dist/fonts/resolve.d.ts.map +1 -0
  62. package/dist/fonts/resolve.js +50 -0
  63. package/dist/fonts/resolve.js.map +1 -0
  64. package/dist/fonts.d.ts +18 -0
  65. package/dist/fonts.d.ts.map +1 -0
  66. package/dist/fonts.js +102 -0
  67. package/dist/fonts.js.map +1 -0
  68. package/dist/generate/extra.d.ts +16 -0
  69. package/dist/generate/extra.d.ts.map +1 -0
  70. package/dist/generate/extra.js +60 -0
  71. package/dist/generate/extra.js.map +1 -0
  72. package/dist/generate/index.d.ts +19 -0
  73. package/dist/generate/index.d.ts.map +1 -0
  74. package/dist/generate/index.js +46 -0
  75. package/dist/generate/index.js.map +1 -0
  76. package/dist/generate/job.d.ts +35 -0
  77. package/dist/generate/job.d.ts.map +1 -0
  78. package/dist/generate/job.js +2 -0
  79. package/dist/generate/job.js.map +1 -0
  80. package/dist/generate/options.d.ts +56 -0
  81. package/dist/generate/options.d.ts.map +1 -0
  82. package/dist/generate/options.js +17 -0
  83. package/dist/generate/options.js.map +1 -0
  84. package/dist/generate/output-path.d.ts +12 -0
  85. package/dist/generate/output-path.d.ts.map +1 -0
  86. package/dist/generate/output-path.js +18 -0
  87. package/dist/generate/output-path.js.map +1 -0
  88. package/dist/generate/outputs.d.ts +28 -0
  89. package/dist/generate/outputs.d.ts.map +1 -0
  90. package/dist/generate/outputs.js +71 -0
  91. package/dist/generate/outputs.js.map +1 -0
  92. package/dist/generate/plan.d.ts +21 -0
  93. package/dist/generate/plan.d.ts.map +1 -0
  94. package/dist/generate/plan.js +72 -0
  95. package/dist/generate/plan.js.map +1 -0
  96. package/dist/generate/render-image.d.ts +11 -0
  97. package/dist/generate/render-image.d.ts.map +1 -0
  98. package/dist/generate/render-image.js +27 -0
  99. package/dist/generate/render-image.js.map +1 -0
  100. package/dist/generate.d.ts +27 -6
  101. package/dist/generate.d.ts.map +1 -1
  102. package/dist/generate.js +77 -15
  103. package/dist/generate.js.map +1 -1
  104. package/dist/highlight/color.d.ts +9 -0
  105. package/dist/highlight/color.d.ts.map +1 -0
  106. package/dist/highlight/color.js +15 -0
  107. package/dist/highlight/color.js.map +1 -0
  108. package/dist/highlight/grid.d.ts +14 -0
  109. package/dist/highlight/grid.d.ts.map +1 -0
  110. package/dist/highlight/grid.js +36 -0
  111. package/dist/highlight/grid.js.map +1 -0
  112. package/dist/highlight/index.d.ts +12 -0
  113. package/dist/highlight/index.d.ts.map +1 -0
  114. package/dist/highlight/index.js +31 -0
  115. package/dist/highlight/index.js.map +1 -0
  116. package/dist/highlight/language.d.ts +14 -0
  117. package/dist/highlight/language.d.ts.map +1 -0
  118. package/dist/highlight/language.js +42 -0
  119. package/dist/highlight/language.js.map +1 -0
  120. package/dist/highlight/normalise.d.ts +17 -0
  121. package/dist/highlight/normalise.d.ts.map +1 -0
  122. package/dist/highlight/normalise.js +37 -0
  123. package/dist/highlight/normalise.js.map +1 -0
  124. package/dist/highlight/types.d.ts +40 -0
  125. package/dist/highlight/types.d.ts.map +1 -0
  126. package/dist/highlight/types.js +2 -0
  127. package/dist/highlight/types.js.map +1 -0
  128. package/dist/highlight.d.ts +10 -0
  129. package/dist/highlight.d.ts.map +1 -1
  130. package/dist/highlight.js +25 -2
  131. package/dist/highlight.js.map +1 -1
  132. package/dist/index.d.ts +15 -9
  133. package/dist/index.d.ts.map +1 -1
  134. package/dist/index.js +9 -5
  135. package/dist/index.js.map +1 -1
  136. package/dist/manifest/build.d.ts +14 -0
  137. package/dist/manifest/build.d.ts.map +1 -0
  138. package/dist/manifest/build.js +43 -0
  139. package/dist/manifest/build.js.map +1 -0
  140. package/dist/manifest/index.d.ts +27 -0
  141. package/dist/manifest/index.d.ts.map +1 -0
  142. package/dist/manifest/index.js +31 -0
  143. package/dist/manifest/index.js.map +1 -0
  144. package/dist/manifest/order.d.ts +12 -0
  145. package/dist/manifest/order.d.ts.map +1 -0
  146. package/dist/manifest/order.js +17 -0
  147. package/dist/manifest/order.js.map +1 -0
  148. package/dist/manifest/page.d.ts +24 -0
  149. package/dist/manifest/page.d.ts.map +1 -0
  150. package/dist/manifest/page.js +55 -0
  151. package/dist/manifest/page.js.map +1 -0
  152. package/dist/meta/html.d.ts +14 -0
  153. package/dist/meta/html.d.ts.map +1 -0
  154. package/dist/meta/html.js +24 -0
  155. package/dist/meta/html.js.map +1 -0
  156. package/dist/meta/index.d.ts +11 -0
  157. package/dist/meta/index.d.ts.map +1 -0
  158. package/dist/meta/index.js +11 -0
  159. package/dist/meta/index.js.map +1 -0
  160. package/dist/meta/tags.d.ts +21 -0
  161. package/dist/meta/tags.d.ts.map +1 -0
  162. package/dist/meta/tags.js +72 -0
  163. package/dist/meta/tags.js.map +1 -0
  164. package/dist/meta/url.d.ts +15 -0
  165. package/dist/meta/url.d.ts.map +1 -0
  166. package/dist/meta/url.js +34 -0
  167. package/dist/meta/url.js.map +1 -0
  168. package/dist/placement/check.d.ts +16 -0
  169. package/dist/placement/check.d.ts.map +1 -0
  170. package/dist/placement/check.js +40 -0
  171. package/dist/placement/check.js.map +1 -0
  172. package/dist/placement/index.d.ts +29 -0
  173. package/dist/placement/index.d.ts.map +1 -0
  174. package/dist/placement/index.js +61 -0
  175. package/dist/placement/index.js.map +1 -0
  176. package/dist/placement/relative.d.ts +31 -0
  177. package/dist/placement/relative.d.ts.map +1 -0
  178. package/dist/placement/relative.js +53 -0
  179. package/dist/placement/relative.js.map +1 -0
  180. package/dist/pool.d.ts +18 -0
  181. package/dist/pool.d.ts.map +1 -0
  182. package/dist/pool.js +56 -0
  183. package/dist/pool.js.map +1 -0
  184. package/dist/render/index.d.ts +10 -0
  185. package/dist/render/index.d.ts.map +1 -0
  186. package/dist/render/index.js +27 -0
  187. package/dist/render/index.js.map +1 -0
  188. package/dist/render/png.d.ts +10 -0
  189. package/dist/render/png.d.ts.map +1 -0
  190. package/dist/render/png.js +30 -0
  191. package/dist/render/png.js.map +1 -0
  192. package/dist/render/svg.d.ts +10 -0
  193. package/dist/render/svg.d.ts.map +1 -0
  194. package/dist/render/svg.js +20 -0
  195. package/dist/render/svg.js.map +1 -0
  196. package/dist/render/template.d.ts +9 -0
  197. package/dist/render/template.d.ts.map +1 -0
  198. package/dist/render/template.js +17 -0
  199. package/dist/render/template.js.map +1 -0
  200. package/dist/render.d.ts +6 -2
  201. package/dist/render.d.ts.map +1 -1
  202. package/dist/render.js +31 -10
  203. package/dist/render.js.map +1 -1
  204. package/dist/stamp/chunk.d.ts +16 -0
  205. package/dist/stamp/chunk.d.ts.map +1 -0
  206. package/dist/stamp/chunk.js +46 -0
  207. package/dist/stamp/chunk.js.map +1 -0
  208. package/dist/stamp/config-digest.d.ts +11 -0
  209. package/dist/stamp/config-digest.d.ts.map +1 -0
  210. package/dist/stamp/config-digest.js +43 -0
  211. package/dist/stamp/config-digest.js.map +1 -0
  212. package/dist/stamp/digest.d.ts +8 -0
  213. package/dist/stamp/digest.d.ts.map +1 -0
  214. package/dist/stamp/digest.js +20 -0
  215. package/dist/stamp/digest.js.map +1 -0
  216. package/dist/stamp/index.d.ts +20 -0
  217. package/dist/stamp/index.d.ts.map +1 -0
  218. package/dist/stamp/index.js +40 -0
  219. package/dist/stamp/index.js.map +1 -0
  220. package/dist/stamp/read.d.ts +7 -0
  221. package/dist/stamp/read.d.ts.map +1 -0
  222. package/dist/stamp/read.js +62 -0
  223. package/dist/stamp/read.js.map +1 -0
  224. package/dist/stamp.d.ts +29 -0
  225. package/dist/stamp.d.ts.map +1 -0
  226. package/dist/stamp.js +198 -0
  227. package/dist/stamp.js.map +1 -0
  228. package/dist/templates/banner/badge.d.ts +7 -0
  229. package/dist/templates/banner/badge.d.ts.map +1 -0
  230. package/dist/templates/banner/badge.js +24 -0
  231. package/dist/templates/banner/badge.js.map +1 -0
  232. package/dist/templates/banner/index.d.ts +9 -0
  233. package/dist/templates/banner/index.d.ts.map +1 -0
  234. package/dist/templates/banner/index.js +66 -0
  235. package/dist/templates/banner/index.js.map +1 -0
  236. package/dist/templates/banner/lines.d.ts +27 -0
  237. package/dist/templates/banner/lines.d.ts.map +1 -0
  238. package/dist/templates/banner/lines.js +46 -0
  239. package/dist/templates/banner/lines.js.map +1 -0
  240. package/dist/templates/card/index.d.ts +7 -0
  241. package/dist/templates/card/index.d.ts.map +1 -0
  242. package/dist/templates/card/index.js +43 -0
  243. package/dist/templates/card/index.js.map +1 -0
  244. package/dist/templates/card/lines.d.ts +17 -0
  245. package/dist/templates/card/lines.d.ts.map +1 -0
  246. package/dist/templates/card/lines.js +32 -0
  247. package/dist/templates/card/lines.js.map +1 -0
  248. package/dist/templates/code/chrome.d.ts +9 -0
  249. package/dist/templates/code/chrome.d.ts.map +1 -0
  250. package/dist/templates/code/chrome.js +32 -0
  251. package/dist/templates/code/chrome.js.map +1 -0
  252. package/dist/templates/code/clip.d.ts +18 -0
  253. package/dist/templates/code/clip.d.ts.map +1 -0
  254. package/dist/templates/code/clip.js +44 -0
  255. package/dist/templates/code/clip.js.map +1 -0
  256. package/dist/templates/code/ellipsis.d.ts +3 -0
  257. package/dist/templates/code/ellipsis.d.ts.map +1 -0
  258. package/dist/templates/code/ellipsis.js +3 -0
  259. package/dist/templates/code/ellipsis.js.map +1 -0
  260. package/dist/templates/code/fit.d.ts +27 -0
  261. package/dist/templates/code/fit.d.ts.map +1 -0
  262. package/dist/templates/code/fit.js +59 -0
  263. package/dist/templates/code/fit.js.map +1 -0
  264. package/dist/templates/code/index.d.ts +12 -0
  265. package/dist/templates/code/index.d.ts.map +1 -0
  266. package/dist/templates/code/index.js +64 -0
  267. package/dist/templates/code/index.js.map +1 -0
  268. package/dist/templates/code/layout.d.ts +21 -0
  269. package/dist/templates/code/layout.d.ts.map +1 -0
  270. package/dist/templates/code/layout.js +21 -0
  271. package/dist/templates/code/layout.js.map +1 -0
  272. package/dist/templates/code/panel.d.ts +17 -0
  273. package/dist/templates/code/panel.d.ts.map +1 -0
  274. package/dist/templates/code/panel.js +20 -0
  275. package/dist/templates/code/panel.js.map +1 -0
  276. package/dist/templates/code/plate.d.ts +15 -0
  277. package/dist/templates/code/plate.d.ts.map +1 -0
  278. package/dist/templates/code/plate.js +32 -0
  279. package/dist/templates/code/plate.js.map +1 -0
  280. package/dist/templates/code/spans.d.ts +19 -0
  281. package/dist/templates/code/spans.d.ts.map +1 -0
  282. package/dist/templates/code/spans.js +36 -0
  283. package/dist/templates/code/spans.js.map +1 -0
  284. package/dist/templates/code/warn.d.ts +11 -0
  285. package/dist/templates/code/warn.d.ts.map +1 -0
  286. package/dist/templates/code/warn.js +24 -0
  287. package/dist/templates/code/warn.js.map +1 -0
  288. package/dist/templates/code.d.ts.map +1 -1
  289. package/dist/templates/code.js +101 -29
  290. package/dist/templates/code.js.map +1 -1
  291. package/dist/templates/footer.d.ts +21 -0
  292. package/dist/templates/footer.d.ts.map +1 -0
  293. package/dist/templates/footer.js +25 -0
  294. package/dist/templates/footer.js.map +1 -0
  295. package/dist/templates/index.d.ts +3 -3
  296. package/dist/templates/index.d.ts.map +1 -1
  297. package/dist/templates/index.js +6 -6
  298. package/dist/templates/index.js.map +1 -1
  299. package/dist/text/element.d.ts +18 -0
  300. package/dist/text/element.d.ts.map +1 -0
  301. package/dist/text/element.js +19 -0
  302. package/dist/text/element.js.map +1 -0
  303. package/dist/text/escape.d.ts +5 -0
  304. package/dist/text/escape.d.ts.map +1 -0
  305. package/dist/text/escape.js +12 -0
  306. package/dist/text/escape.js.map +1 -0
  307. package/dist/text/index.d.ts +7 -0
  308. package/dist/text/index.d.ts.map +1 -0
  309. package/dist/text/index.js +5 -0
  310. package/dist/text/index.js.map +1 -0
  311. package/dist/text/stack.d.ts +24 -0
  312. package/dist/text/stack.d.ts.map +1 -0
  313. package/dist/text/stack.js +18 -0
  314. package/dist/text/stack.js.map +1 -0
  315. package/dist/text/wrap.d.ts +12 -0
  316. package/dist/text/wrap.d.ts.map +1 -0
  317. package/dist/text/wrap.js +33 -0
  318. package/dist/text/wrap.js.map +1 -0
  319. package/dist/types.d.ts +345 -6
  320. package/dist/types.d.ts.map +1 -1
  321. package/dist/validate/check.d.ts +13 -0
  322. package/dist/validate/check.d.ts.map +1 -0
  323. package/dist/validate/check.js +34 -0
  324. package/dist/validate/check.js.map +1 -0
  325. package/dist/validate/index.d.ts +15 -0
  326. package/dist/validate/index.d.ts.map +1 -0
  327. package/dist/validate/index.js +41 -0
  328. package/dist/validate/index.js.map +1 -0
  329. package/dist/validate/keys.d.ts +58 -0
  330. package/dist/validate/keys.d.ts.map +1 -0
  331. package/dist/validate/keys.js +166 -0
  332. package/dist/validate/keys.js.map +1 -0
  333. package/dist/validate/overrides.d.ts +5 -0
  334. package/dist/validate/overrides.d.ts.map +1 -0
  335. package/dist/validate/overrides.js +43 -0
  336. package/dist/validate/overrides.js.map +1 -0
  337. package/dist/validate/placement.d.ts +10 -0
  338. package/dist/validate/placement.d.ts.map +1 -0
  339. package/dist/validate/placement.js +38 -0
  340. package/dist/validate/placement.js.map +1 -0
  341. package/dist/validate/sizes.d.ts +9 -0
  342. package/dist/validate/sizes.d.ts.map +1 -0
  343. package/dist/validate/sizes.js +27 -0
  344. package/dist/validate/sizes.js.map +1 -0
  345. package/dist/validate/suggest.d.ts +10 -0
  346. package/dist/validate/suggest.d.ts.map +1 -0
  347. package/dist/validate/suggest.js +55 -0
  348. package/dist/validate/suggest.js.map +1 -0
  349. package/dist/validate/values.d.ts +21 -0
  350. package/dist/validate/values.d.ts.map +1 -0
  351. package/dist/validate/values.js +57 -0
  352. package/dist/validate/values.js.map +1 -0
  353. package/dist/validate.d.ts +15 -0
  354. package/dist/validate.d.ts.map +1 -0
  355. package/dist/validate.js +300 -0
  356. package/dist/validate.js.map +1 -0
  357. package/package.json +14 -7
@@ -0,0 +1,35 @@
1
+ import type { MetaImageProps, OutputSize, ResolvedConfig } from "../types.js";
2
+ /**
3
+ * One image to render: what to draw, how big, where it goes, and the config it
4
+ * is drawn with. A content file becomes one job per size; a config `extra`
5
+ * becomes a single job of its own.
6
+ *
7
+ * Working the paths and configs out while planning is what lets the two
8
+ * sources meet: by the time anything is rendered there are only jobs, and
9
+ * nothing downstream has to ask which kind of image it is holding.
10
+ */
11
+ export interface RenderJob {
12
+ /**
13
+ * The content file this image came from, relative to the content root.
14
+ * `undefined` for an extra image, which has no content file.
15
+ */
16
+ readonly contentPath: string | undefined;
17
+ /**
18
+ * The slug the page is addressed by, and the key it appears under in the
19
+ * manifest. `undefined` for an extra image, which is not a page.
20
+ */
21
+ readonly slug: string | undefined;
22
+ readonly props: MetaImageProps;
23
+ readonly size: OutputSize;
24
+ readonly outputPath: string;
25
+ /** Where the image is served, if the placement knows. */
26
+ readonly url: string | undefined;
27
+ /**
28
+ * The digest of everything this image is drawn from. An image on disk
29
+ * carrying the same one is already up to date, and a placement that hashes
30
+ * filenames names the file after it.
31
+ */
32
+ readonly stamp: string;
33
+ readonly config: ResolvedConfig;
34
+ }
35
+ //# sourceMappingURL=job.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"job.d.ts","sourceRoot":"","sources":["../../src/generate/job.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE9E;;;;;;;;GAQG;AACH,MAAM,WAAW,SAAS;IACxB;;;OAGG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,yDAAyD;IACzD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;CACjC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=job.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"job.js","sourceRoot":"","sources":["../../src/generate/job.ts"],"names":[],"mappings":""}
@@ -0,0 +1,56 @@
1
+ import type { ContentFile, WalkOptions } from "../content/index.js";
2
+ import type { ColophonConfig, OutputSize } from "../types.js";
3
+ /**
4
+ * Outcome for one generated (or skipped) image.
5
+ */
6
+ export interface GeneratedImage {
7
+ /**
8
+ * The content file this image came from, relative to the content root.
9
+ * `undefined` for a config `extra` image: there is no post behind it, and
10
+ * naming one — the output path, say — would quietly break anything grouping
11
+ * results by the post they belong to.
12
+ */
13
+ readonly contentPath: string | undefined;
14
+ readonly size: OutputSize;
15
+ readonly outputPath: string;
16
+ /**
17
+ * Where the image is served, from the configured placement. `undefined`
18
+ * when nothing says: no `urlBase`, an image placed by `outputPath`, or an
19
+ * `extra` that named its own path.
20
+ */
21
+ readonly url: string | undefined;
22
+ /** True when an up-to-date image was left in place (no `overwrite`). */
23
+ readonly skipped: boolean;
24
+ }
25
+ /**
26
+ * Options for `generate`.
27
+ */
28
+ export interface GenerateOptions {
29
+ /** Root content directory to walk. */
30
+ readonly contentDir: string;
31
+ readonly config?: ColophonConfig;
32
+ /** Extra walk options (props key, template field, slug field, extensions). */
33
+ readonly walk?: Omit<WalkOptions, "dir">;
34
+ /** Override where each image is written. */
35
+ readonly outputPath?: (file: ContentFile, size: OutputSize) => string;
36
+ /**
37
+ * Re-render every image, ignoring the stamps that would otherwise mark them
38
+ * as up to date. Default `false`.
39
+ */
40
+ readonly overwrite?: boolean;
41
+ /**
42
+ * How many images to render at once. Defaults to the number of CPUs the
43
+ * process can use. Must be a positive integer.
44
+ */
45
+ readonly concurrency?: number;
46
+ /** Called after each image is written or skipped. */
47
+ readonly onResult?: (result: GeneratedImage) => void;
48
+ }
49
+ /**
50
+ * How many images to render at once when nothing is configured: one per CPU
51
+ * the process can actually use. Rasterising is CPU-bound, so starting more
52
+ * than that only queues the work up while holding every pending image in
53
+ * memory.
54
+ */
55
+ export declare function resolveConcurrency(concurrency: number | undefined): number;
56
+ //# sourceMappingURL=options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/generate/options.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9D;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,wEAAwE;IACxE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,sCAAsC;IACtC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;IACjC,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IACzC,4CAA4C;IAC5C,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,KAAK,MAAM,CAAC;IACtE;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,qDAAqD;IACrD,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;CACtD;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAY1E"}
@@ -0,0 +1,17 @@
1
+ import { availableParallelism } from "node:os";
2
+ /**
3
+ * How many images to render at once when nothing is configured: one per CPU
4
+ * the process can actually use. Rasterising is CPU-bound, so starting more
5
+ * than that only queues the work up while holding every pending image in
6
+ * memory.
7
+ */
8
+ export function resolveConcurrency(concurrency) {
9
+ if (concurrency === undefined) {
10
+ return availableParallelism();
11
+ }
12
+ if (!Number.isSafeInteger(concurrency) || concurrency < 1) {
13
+ throw new Error(`Invalid concurrency ${String(concurrency)}; expected a positive integer.`);
14
+ }
15
+ return concurrency;
16
+ }
17
+ //# sourceMappingURL=options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.js","sourceRoot":"","sources":["../../src/generate/options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAqD/C;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,WAA+B;IAChE,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO,oBAAoB,EAAE,CAAC;IAChC,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,WAAW,CAAC,IAAI,WAAW,GAAG,CAAC,EAAE,CAAC;QAC1D,MAAM,IAAI,KAAK,CACb,uBAAuB,MAAM,CAAC,WAAW,CAAC,gCAAgC,CAC3E,CAAC;IACJ,CAAC;IAED,OAAO,WAAW,CAAC;AACrB,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { ContentFile, OutputSize } from "../types.js";
2
+ /**
3
+ * Default output path: named `<slug>-<size>.png`, alongside the content file.
4
+ *
5
+ * This is the `beside-content` placement reached without a content root to
6
+ * place from, so it takes the root off the file itself. It stays its own
7
+ * export because it is what `generate`'s `outputPath` callback replaces, and
8
+ * a caller wrapping the default has to be able to call it. It never hashes:
9
+ * a hash names one rendering of an image, and this has no way to know which.
10
+ */
11
+ export declare function defaultOutputPath(file: ContentFile, size: OutputSize): string;
12
+ //# sourceMappingURL=output-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-path.d.ts","sourceRoot":"","sources":["../../src/generate/output-path.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE3D;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CAS7E"}
@@ -0,0 +1,18 @@
1
+ import path from "node:path";
2
+ import { besideContent } from "../placement/index.js";
3
+ /**
4
+ * Default output path: named `<slug>-<size>.png`, alongside the content file.
5
+ *
6
+ * This is the `beside-content` placement reached without a content root to
7
+ * place from, so it takes the root off the file itself. It stays its own
8
+ * export because it is what `generate`'s `outputPath` callback replaces, and
9
+ * a caller wrapping the default has to be able to call it. It never hashes:
10
+ * a hash names one rendering of an image, and this has no way to know which.
11
+ */
12
+ export function defaultOutputPath(file, size) {
13
+ // `absolutePath` ends with `contentPath` by construction, so dropping it
14
+ // leaves the directory the walk started from.
15
+ const root = file.absolutePath.slice(0, file.absolutePath.length - file.contentPath.length);
16
+ return path.join(root, ...besideContent(file, size, undefined).split("/"));
17
+ }
18
+ //# sourceMappingURL=output-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-path.js","sourceRoot":"","sources":["../../src/generate/output-path.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAGtD;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAiB,EAAE,IAAgB;IACnE,yEAAyE;IACzE,8CAA8C;IAC9C,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,KAAK,CAClC,CAAC,EACD,IAAI,CAAC,YAAY,CAAC,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,CACnD,CAAC;IAEF,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;AAC7E,CAAC"}
@@ -0,0 +1,28 @@
1
+ import type { RenderJob } from "./job.js";
2
+ /**
3
+ * Reject a manifest written over one of the images it describes.
4
+ *
5
+ * The manifest is written last, so it wins: the image is replaced by JSON, and
6
+ * — its stamp gone with it — rendered again on every build afterwards. It is
7
+ * the same collision as two images sharing a path, so it is answered here,
8
+ * where a path is already compared the way the filesystem would.
9
+ */
10
+ export declare function assertManifestIsNotAnImage(manifestPath: string | undefined, jobs: readonly RenderJob[]): void;
11
+ /**
12
+ * Reject a build in which two images would be written to one path.
13
+ *
14
+ * They do not merely lose an image between them: each stamps the file with its
15
+ * own digest, so every later build finds a stamp that does not match and
16
+ * renders both again, for ever.
17
+ *
18
+ * Every job is checked, posts included. That used to be older ground worth
19
+ * leaving alone — two posts collided only if they shared a directory and a
20
+ * slug — but `public-dir` gathers a whole tree into one directory, where every
21
+ * post sharing a basename with another lands on it. Naming both sides is what
22
+ * makes that worth reporting: the path alone does not say which two posts.
23
+ *
24
+ * Paths are compared resolved, since a placement names an absolute path and an
25
+ * extra names whatever the config wrote.
26
+ */
27
+ export declare function assertDistinctOutputs(jobs: readonly RenderJob[]): void;
28
+ //# sourceMappingURL=outputs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outputs.d.ts","sourceRoot":"","sources":["../../src/generate/outputs.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AA0B1C;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,IAAI,EAAE,SAAS,SAAS,EAAE,GACzB,IAAI,CAeN;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,SAAS,SAAS,EAAE,GAAG,IAAI,CAiBtE"}
@@ -0,0 +1,71 @@
1
+ import path from "node:path";
2
+ /**
3
+ * Whether the filesystem underneath treats `Card.png` and `card.png` as one
4
+ * file. Read off the platform rather than probed: a case-sensitive volume on
5
+ * macOS would have two images it could have kept told to rename one of them,
6
+ * which is a rare config and an obvious fix, where missing a real collision
7
+ * leaves a build re-rendering for ever.
8
+ */
9
+ const isCaseInsensitive = process.platform === "darwin" || process.platform === "win32";
10
+ /**
11
+ * How a path is compared, which is not always how it is written: where case
12
+ * does not distinguish two files, it must not distinguish two jobs either.
13
+ */
14
+ function comparable(outputPath) {
15
+ const absolute = path.resolve(outputPath);
16
+ return isCaseInsensitive ? absolute.toLowerCase() : absolute;
17
+ }
18
+ /** What to call a job in a message: the post it came from, or its path. */
19
+ function describe(job) {
20
+ return job.contentPath ?? `extra "${job.outputPath}"`;
21
+ }
22
+ /**
23
+ * Reject a manifest written over one of the images it describes.
24
+ *
25
+ * The manifest is written last, so it wins: the image is replaced by JSON, and
26
+ * — its stamp gone with it — rendered again on every build afterwards. It is
27
+ * the same collision as two images sharing a path, so it is answered here,
28
+ * where a path is already compared the way the filesystem would.
29
+ */
30
+ export function assertManifestIsNotAnImage(manifestPath, jobs) {
31
+ if (manifestPath === undefined) {
32
+ return;
33
+ }
34
+ const key = comparable(manifestPath);
35
+ const clash = jobs.find((job) => comparable(job.outputPath) === key);
36
+ if (clash !== undefined) {
37
+ throw new Error(`The manifest path "${manifestPath}" is where ${describe(clash)} is` +
38
+ ` written. The manifest is written last, so it would replace the` +
39
+ ` image; give one of them a path of its own.`);
40
+ }
41
+ }
42
+ /**
43
+ * Reject a build in which two images would be written to one path.
44
+ *
45
+ * They do not merely lose an image between them: each stamps the file with its
46
+ * own digest, so every later build finds a stamp that does not match and
47
+ * renders both again, for ever.
48
+ *
49
+ * Every job is checked, posts included. That used to be older ground worth
50
+ * leaving alone — two posts collided only if they shared a directory and a
51
+ * slug — but `public-dir` gathers a whole tree into one directory, where every
52
+ * post sharing a basename with another lands on it. Naming both sides is what
53
+ * makes that worth reporting: the path alone does not say which two posts.
54
+ *
55
+ * Paths are compared resolved, since a placement names an absolute path and an
56
+ * extra names whatever the config wrote.
57
+ */
58
+ export function assertDistinctOutputs(jobs) {
59
+ const taken = new Map();
60
+ for (const job of jobs) {
61
+ const key = comparable(job.outputPath);
62
+ const first = taken.get(key);
63
+ if (first !== undefined) {
64
+ throw new Error(`Two images would be written to "${job.outputPath}":` +
65
+ ` ${describe(first)} and ${describe(job)}. Each image needs a path` +
66
+ ` of its own, or one is lost and both re-render on every build.`);
67
+ }
68
+ taken.set(key, job);
69
+ }
70
+ }
71
+ //# sourceMappingURL=outputs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outputs.js","sourceRoot":"","sources":["../../src/generate/outputs.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAI7B;;;;;;GAMG;AACH,MAAM,iBAAiB,GACrB,OAAO,CAAC,QAAQ,KAAK,QAAQ,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC;AAEhE;;;GAGG;AACH,SAAS,UAAU,CAAC,UAAkB;IACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC1C,OAAO,iBAAiB,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC/D,CAAC;AAED,2EAA2E;AAC3E,SAAS,QAAQ,CAAC,GAAc;IAC9B,OAAO,GAAG,CAAC,WAAW,IAAI,UAAU,GAAG,CAAC,UAAU,GAAG,CAAC;AACxD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,0BAA0B,CACxC,YAAgC,EAChC,IAA0B;IAE1B,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QAC/B,OAAO;IACT,CAAC;IAED,MAAM,GAAG,GAAG,UAAU,CAAC,YAAY,CAAC,CAAC;IACrC,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,GAAG,CAAC,CAAC;IAErE,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,sBAAsB,YAAY,cAAc,QAAQ,CAAC,KAAK,CAAC,KAAK;YAClE,iEAAiE;YACjE,6CAA6C,CAChD,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAA0B;IAC9D,MAAM,KAAK,GAAG,IAAI,GAAG,EAAqB,CAAC;IAE3C,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACvC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAE7B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CACb,mCAAmC,GAAG,CAAC,UAAU,IAAI;gBACnD,IAAI,QAAQ,CAAC,KAAK,CAAC,QAAQ,QAAQ,CAAC,GAAG,CAAC,2BAA2B;gBACnE,gEAAgE,CACnE,CAAC;QACJ,CAAC;QAED,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACtB,CAAC;AACH,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type { PlannedManifest } from "../manifest/index.js";
2
+ import type { Stamper } from "../stamp/index.js";
3
+ import type { RenderJob } from "./job.js";
4
+ import type { GenerateOptions } from "./options.js";
5
+ /**
6
+ * Everything a build needs before it starts rendering: one job per image, and
7
+ * the stamper that says which of them can be skipped.
8
+ */
9
+ export interface BuildPlan {
10
+ readonly jobs: readonly RenderJob[];
11
+ readonly stamper: Stamper;
12
+ /** What to write down about the build, where a config asked for it. */
13
+ readonly manifest: PlannedManifest | undefined;
14
+ }
15
+ /**
16
+ * Work out what a build has to render. The per-size configs are resolved once
17
+ * rather than once per image: the overrides are the same for every post, and
18
+ * resolving re-reads the configured font files.
19
+ */
20
+ export declare function planBuild(options: GenerateOptions): Promise<BuildPlan>;
21
+ //# sourceMappingURL=plan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../src/generate/plan.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE5D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAKjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAMpD;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,SAAS,SAAS,EAAE,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,uEAAuE;IACvE,QAAQ,CAAC,QAAQ,EAAE,eAAe,GAAG,SAAS,CAAC;CAChD;AAqBD;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,SAAS,CAAC,CAmD5E"}
@@ -0,0 +1,72 @@
1
+ import { resolveConfig } from "../config/index.js";
2
+ import { resolveConfigForSize } from "../config/size.js";
3
+ import { walkContent } from "../content/index.js";
4
+ import { planManifest } from "../manifest/index.js";
5
+ import { createStamper } from "../stamp/index.js";
6
+ import { createPlacer } from "../placement/index.js";
7
+ import { extraJobs } from "./extra.js";
8
+ import { assertDistinctOutputs, assertManifestIsNotAnImage, } from "./outputs.js";
9
+ /**
10
+ * How this build places its images: the configured placement, or `outputPath`
11
+ * where a caller passed one.
12
+ *
13
+ * `outputPath` is the programmatic equivalent of a `custom` placement and wins
14
+ * for the same reason `walk` beats `config.content`. The URL then comes from
15
+ * nowhere: the placement no longer describes where the file went, and the one
16
+ * it would have named is not where the image is.
17
+ */
18
+ function placer(options) {
19
+ const override = options.outputPath;
20
+ if (override === undefined) {
21
+ return createPlacer(options.config?.placement, options.contentDir);
22
+ }
23
+ return (file, size) => ({ path: override(file, size), url: undefined });
24
+ }
25
+ /**
26
+ * Work out what a build has to render. The per-size configs are resolved once
27
+ * rather than once per image: the overrides are the same for every post, and
28
+ * resolving re-reads the configured font files.
29
+ */
30
+ export async function planBuild(options) {
31
+ const resolved = resolveConfig(options.config);
32
+ const place = placer(options);
33
+ const configBySize = new Map(resolved.sizes.map((size) => [
34
+ size.name,
35
+ resolveConfigForSize(options.config, size),
36
+ ]));
37
+ const [stamper, files] = await Promise.all([
38
+ createStamper(resolved),
39
+ // Content options can come from the config module, which is the only route
40
+ // a CLI user has to them; `walk` is the programmatic override and wins.
41
+ walkContent({
42
+ dir: options.contentDir,
43
+ ...options.config?.content,
44
+ ...options.walk,
45
+ }),
46
+ ]);
47
+ const jobs = files.flatMap((file) => resolved.sizes.map((size) => {
48
+ // Stamped before it is placed, not after: a hashed filename is built
49
+ // from the stamp, so the name cannot be known before the digest is.
50
+ const stamp = stamper.stamp(file.props, size);
51
+ const placed = place(file, size, stamp);
52
+ return {
53
+ contentPath: file.contentPath,
54
+ slug: file.slug,
55
+ props: file.props,
56
+ size,
57
+ outputPath: placed.path,
58
+ url: placed.url,
59
+ stamp,
60
+ config: configBySize.get(size.name) ?? resolved,
61
+ };
62
+ }));
63
+ const all = [...jobs, ...extraJobs(options.config, resolved, stamper)];
64
+ assertDistinctOutputs(all);
65
+ assertManifestIsNotAnImage(options.config?.manifest, all);
66
+ return {
67
+ jobs: all,
68
+ stamper,
69
+ manifest: planManifest(options.config?.manifest, all),
70
+ };
71
+ }
72
+ //# sourceMappingURL=plan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.js","sourceRoot":"","sources":["../../src/generate/plan.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGvC,OAAO,EACL,qBAAqB,EACrB,0BAA0B,GAC3B,MAAM,cAAc,CAAC;AAatB;;;;;;;;GAQG;AACH,SAAS,MAAM,CAAC,OAAwB;IACtC,MAAM,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC;IAEpC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IACrE,CAAC;IAED,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC;AAC1E,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,OAAwB;IACtD,MAAM,QAAQ,GAAG,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/C,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAE9B,MAAM,YAAY,GAAG,IAAI,GAAG,CAC1B,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QAC3B,IAAI,CAAC,IAAI;QACT,oBAAoB,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC;KAC3C,CAAC,CACH,CAAC;IAEF,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACzC,aAAa,CAAC,QAAQ,CAAC;QACvB,2EAA2E;QAC3E,wEAAwE;QACxE,WAAW,CAAC;YACV,GAAG,EAAE,OAAO,CAAC,UAAU;YACvB,GAAG,OAAO,CAAC,MAAM,EAAE,OAAO;YAC1B,GAAG,OAAO,CAAC,IAAI;SAChB,CAAC;KACH,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAClC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QAC1B,qEAAqE;QACrE,oEAAoE;QACpE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAC9C,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;QAExC,OAAO;YACL,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,IAAI;YACJ,UAAU,EAAE,MAAM,CAAC,IAAI;YACvB,GAAG,EAAE,MAAM,CAAC,GAAG;YACf,KAAK;YACL,MAAM,EAAE,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ;SAChD,CAAC;IACJ,CAAC,CAAC,CACH,CAAC;IAEF,MAAM,GAAG,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;IACvE,qBAAqB,CAAC,GAAG,CAAC,CAAC;IAC3B,0BAA0B,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;IAE1D,OAAO;QACL,IAAI,EAAE,GAAG;QACT,OAAO;QACP,QAAQ,EAAE,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,CAAC;KACtD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { RenderJob } from "./job.js";
2
+ /**
3
+ * Render one image and write it, stamped, to its output path, creating
4
+ * whatever directories that path needs.
5
+ *
6
+ * Warnings name the image they came from: a build renders many, and "shorten
7
+ * the sample" is no use without knowing which sample. An extra image has no
8
+ * content file to name, so it is named by where it is being written.
9
+ */
10
+ export declare function renderImage(job: RenderJob, stamp: string): Promise<void>;
11
+ //# sourceMappingURL=render-image.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-image.d.ts","sourceRoot":"","sources":["../../src/generate/render-image.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE1C;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,SAAS,EACd,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAef"}
@@ -0,0 +1,27 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { buildSvg, renderSvgToPng } from "../render/index.js";
4
+ import { stampPng } from "../stamp/index.js";
5
+ /**
6
+ * Render one image and write it, stamped, to its output path, creating
7
+ * whatever directories that path needs.
8
+ *
9
+ * Warnings name the image they came from: a build renders many, and "shorten
10
+ * the sample" is no use without knowing which sample. An extra image has no
11
+ * content file to name, so it is named by where it is being written.
12
+ */
13
+ export async function renderImage(job, stamp) {
14
+ const dimensions = { width: job.size.width, height: job.size.height };
15
+ const source = job.contentPath ?? job.outputPath;
16
+ const config = {
17
+ ...job.config,
18
+ onWarning: (message) => {
19
+ job.config.onWarning(`${source}: ${message}`);
20
+ },
21
+ };
22
+ const svg = await buildSvg(job.props, config, dimensions);
23
+ const png = await renderSvgToPng(svg, dimensions, config);
24
+ await mkdir(path.dirname(job.outputPath), { recursive: true });
25
+ await writeFile(job.outputPath, stampPng(png, stamp));
26
+ }
27
+ //# sourceMappingURL=render-image.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-image.js","sourceRoot":"","sources":["../../src/generate/render-image.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAI7C;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,GAAc,EACd,KAAa;IAEb,MAAM,UAAU,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;IACtE,MAAM,MAAM,GAAG,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,UAAU,CAAC;IACjD,MAAM,MAAM,GAAmB;QAC7B,GAAG,GAAG,CAAC,MAAM;QACb,SAAS,EAAE,CAAC,OAAe,EAAQ,EAAE;YACnC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,MAAM,KAAK,OAAO,EAAE,CAAC,CAAC;QAChD,CAAC;KACF,CAAC;IAEF,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;IAC1D,MAAM,GAAG,GAAG,MAAM,cAAc,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;IAE1D,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/D,MAAM,SAAS,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;AACxD,CAAC"}
@@ -7,7 +7,7 @@ export interface GeneratedImage {
7
7
  readonly contentPath: string;
8
8
  readonly size: OutputSize;
9
9
  readonly outputPath: string;
10
- /** True when an existing file was left in place (no `overwrite`). */
10
+ /** True when an up-to-date image was left in place (no `overwrite`). */
11
11
  readonly skipped: boolean;
12
12
  }
13
13
  /**
@@ -21,22 +21,43 @@ export interface GenerateOptions {
21
21
  readonly walk?: Omit<WalkOptions, "dir">;
22
22
  /** Override where each image is written. */
23
23
  readonly outputPath?: (file: ContentFile, size: OutputSize) => string;
24
- /** Re-render even when the output file already exists. Default `false`. */
24
+ /**
25
+ * Re-render every image, ignoring the stamps that would otherwise mark them
26
+ * as up to date. Default `false`.
27
+ */
25
28
  readonly overwrite?: boolean;
29
+ /**
30
+ * How many images to render at once. Defaults to the number of CPUs the
31
+ * process can use. Must be a positive integer.
32
+ */
33
+ readonly concurrency?: number;
26
34
  /** Called after each image is written or skipped. */
27
35
  readonly onResult?: (result: GeneratedImage) => void;
28
36
  }
29
37
  /**
30
- * Default output path: alongside the content file, named `<slug>-<size>.png`.
38
+ * Default output path: named `<slug>-<size>.png`, alongside the content file.
31
39
  * The slug carries the post's keywords into the filename and the size name
32
40
  * keeps every image distinct (e.g. `my-post-og.png`, `my-post-square.png`).
41
+ *
42
+ * A slug carrying directories of its own — what the `route` strategy produces,
43
+ * and what a frontmatter `slug` of `docs/intro` means — is written from the
44
+ * content root instead, so its path mirrors the route. Resolving it beside the
45
+ * file would repeat the directories that are already in the slug, putting
46
+ * `services/iam` under `content/services/iam/`.
33
47
  */
34
48
  export declare function defaultOutputPath(file: ContentFile, size: OutputSize): string;
35
49
  /**
36
50
  * Walk a content tree and render meta images for every file that declares
37
- * props, writing PNGs to disk. Existing files are skipped unless `overwrite`
38
- * is set (matching the original script's behaviour), and skipped sizes are
39
- * never rendered.
51
+ * props, writing PNGs to disk.
52
+ *
53
+ * Each image is stamped with a digest of the props, config and size it came
54
+ * from, so a rebuild renders only what has actually changed: an image whose
55
+ * stamp still matches is left alone, and one whose title, colours or template
56
+ * has moved on is rendered again. `overwrite` ignores the stamps and renders
57
+ * everything. Sizes with no image to render are never rasterised.
58
+ *
59
+ * Rendering runs `concurrency` images at a time rather than all of them, so a
60
+ * large tree does not start hundreds of rasterisations at once.
40
61
  */
41
62
  export declare function generate(options: GenerateOptions): Promise<GeneratedImage[]>;
42
63
  //# sourceMappingURL=generate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGnE,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7D;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,sCAAsC;IACtC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;IACjC,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IACzC,4CAA4C;IAC5C,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,KAAK,MAAM,CAAC;IACtE,2EAA2E;IAC3E,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,qDAAqD;IACrD,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;CACtD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CAG7E;AAED;;;;;GAKG;AACH,wBAAsB,QAAQ,CAC5B,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,cAAc,EAAE,CAAC,CAgC3B"}
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAKnE,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7D;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,wEAAwE;IACxE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,sCAAsC;IACtC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;IACjC,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IACzC,4CAA4C;IAC5C,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,KAAK,MAAM,CAAC;IACtE;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,qDAAqD;IACrD,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;CACtD;AAsBD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CAc7E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,QAAQ,CAC5B,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,cAAc,EAAE,CAAC,CA2D3B"}
package/dist/generate.js CHANGED
@@ -1,38 +1,100 @@
1
- import { existsSync } from "node:fs";
2
1
  import { mkdir, writeFile } from "node:fs/promises";
2
+ import { availableParallelism } from "node:os";
3
3
  import path from "node:path";
4
- import { resolveConfig } from "./config.js";
4
+ import { resolveConfig, resolveConfigForSize } from "./config.js";
5
5
  import { walkContent } from "./content/index.js";
6
+ import { mapConcurrent } from "./pool.js";
6
7
  import { buildSvg, renderSvgToPng } from "./render.js";
8
+ import { createStamper, readPngStamp, stampPng } from "./stamp.js";
7
9
  /**
8
- * Default output path: alongside the content file, named `<slug>-<size>.png`.
10
+ * How many images to render at once when nothing is configured: one per CPU
11
+ * the process can actually use. Rasterising is CPU-bound, so starting more
12
+ * than that only queues the work up while holding every pending image in
13
+ * memory.
14
+ */
15
+ function resolveConcurrency(concurrency) {
16
+ if (concurrency === undefined) {
17
+ return availableParallelism();
18
+ }
19
+ if (!Number.isSafeInteger(concurrency) || concurrency < 1) {
20
+ throw new Error(`Invalid concurrency ${String(concurrency)}; expected a positive integer.`);
21
+ }
22
+ return concurrency;
23
+ }
24
+ /**
25
+ * Default output path: named `<slug>-<size>.png`, alongside the content file.
9
26
  * The slug carries the post's keywords into the filename and the size name
10
27
  * keeps every image distinct (e.g. `my-post-og.png`, `my-post-square.png`).
28
+ *
29
+ * A slug carrying directories of its own — what the `route` strategy produces,
30
+ * and what a frontmatter `slug` of `docs/intro` means — is written from the
31
+ * content root instead, so its path mirrors the route. Resolving it beside the
32
+ * file would repeat the directories that are already in the slug, putting
33
+ * `services/iam` under `content/services/iam/`.
11
34
  */
12
35
  export function defaultOutputPath(file, size) {
13
- const directory = path.dirname(file.absolutePath);
14
- return path.join(directory, `${file.slug}-${size.name}.png`);
36
+ const name = `${file.slug}-${size.name}.png`;
37
+ if (!file.slug.includes("/")) {
38
+ return path.join(path.dirname(file.absolutePath), name);
39
+ }
40
+ // `absolutePath` ends with `contentPath` by construction, so dropping it
41
+ // leaves the directory the walk started from.
42
+ const root = file.absolutePath.slice(0, file.absolutePath.length - file.contentPath.length);
43
+ return path.join(root, name);
15
44
  }
16
45
  /**
17
46
  * Walk a content tree and render meta images for every file that declares
18
- * props, writing PNGs to disk. Existing files are skipped unless `overwrite`
19
- * is set (matching the original script's behaviour), and skipped sizes are
20
- * never rendered.
47
+ * props, writing PNGs to disk.
48
+ *
49
+ * Each image is stamped with a digest of the props, config and size it came
50
+ * from, so a rebuild renders only what has actually changed: an image whose
51
+ * stamp still matches is left alone, and one whose title, colours or template
52
+ * has moved on is rendered again. `overwrite` ignores the stamps and renders
53
+ * everything. Sizes with no image to render are never rasterised.
54
+ *
55
+ * Rendering runs `concurrency` images at a time rather than all of them, so a
56
+ * large tree does not start hundreds of rasterisations at once.
21
57
  */
22
58
  export async function generate(options) {
23
59
  const resolved = resolveConfig(options.config);
60
+ const concurrency = resolveConcurrency(options.concurrency);
24
61
  const toOutputPath = options.outputPath ?? defaultOutputPath;
25
- const files = await walkContent({ dir: options.contentDir, ...options.walk });
62
+ // Resolved once per size rather than once per image: the overrides are the
63
+ // same for every post, and resolving re-reads the configured font files.
64
+ const configBySize = new Map(resolved.sizes.map((size) => [
65
+ size.name,
66
+ resolveConfigForSize(options.config, size),
67
+ ]));
68
+ const [stamper, files] = await Promise.all([
69
+ createStamper(resolved),
70
+ // Content options can come from the config module, which is the only route
71
+ // a CLI user has to them; `walk` is the programmatic override and wins.
72
+ walkContent({
73
+ dir: options.contentDir,
74
+ ...options.config?.content,
75
+ ...options.walk,
76
+ }),
77
+ ]);
26
78
  const jobs = files.flatMap((file) => resolved.sizes.map((size) => ({ file, size })));
27
- return Promise.all(jobs.map(async ({ file, size }) => {
79
+ return mapConcurrent(jobs, concurrency, async ({ file, size }) => {
28
80
  const outputPath = toOutputPath(file, size);
29
- const isSkipped = existsSync(outputPath) && options.overwrite !== true;
81
+ const stamp = stamper.stamp(file.props, size);
82
+ const isSkipped = options.overwrite !== true && (await readPngStamp(outputPath)) === stamp;
30
83
  if (!isSkipped) {
31
84
  const dimensions = { width: size.width, height: size.height };
32
- const svg = await buildSvg(file.props, resolved, dimensions);
33
- const png = await renderSvgToPng(svg, dimensions);
85
+ const sizeConfig = configBySize.get(size.name) ?? resolved;
86
+ // Warnings name the post they came from: a build renders many images,
87
+ // and "shorten the sample" is no use without knowing which sample.
88
+ const config = {
89
+ ...sizeConfig,
90
+ onWarning: (message) => {
91
+ resolved.onWarning(`${file.contentPath}: ${message}`);
92
+ },
93
+ };
94
+ const svg = await buildSvg(file.props, config, dimensions);
95
+ const png = await renderSvgToPng(svg, dimensions, config);
34
96
  await mkdir(path.dirname(outputPath), { recursive: true });
35
- await writeFile(outputPath, png);
97
+ await writeFile(outputPath, stampPng(png, stamp));
36
98
  }
37
99
  const result = {
38
100
  contentPath: file.contentPath,
@@ -42,6 +104,6 @@ export async function generate(options) {
42
104
  };
43
105
  options.onResult?.(result);
44
106
  return result;
45
- }));
107
+ });
46
108
  }
47
109
  //# sourceMappingURL=generate.js.map