@heroiclands/package-build 23.1.1 → 23.2.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 (246) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/bin/content-commands.mjs +68 -149
  3. package/bin/package-build.mjs +56 -10
  4. package/ci/check-banners.mjs +42 -6
  5. package/ci/confirm-npm-version.mjs +135 -0
  6. package/ci/content-format-reference.mjs +14 -5
  7. package/config.mjs +1 -0
  8. package/container.mjs +185 -4
  9. package/content-config.mjs +333 -210
  10. package/deploy.mjs +116 -10
  11. package/docs/api.md +238 -246
  12. package/docs/authoring/assets.md +11 -5
  13. package/docs/authoring/frontmatter.md +1 -1
  14. package/docs/authoring/links-and-markup.md +87 -0
  15. package/docs/commands.md +138 -99
  16. package/docs/configuration.md +277 -125
  17. package/docs/content-format.md +2 -0
  18. package/docs/diagnostics.md +48 -13
  19. package/docs/guides/book.md +341 -21
  20. package/docs/guides/e2e.md +13 -2
  21. package/docs/guides/icons.md +14 -4
  22. package/docs/guides/module-project.md +18 -8
  23. package/docs/guides/release-and-deploy.md +32 -0
  24. package/docs/guides/site.md +5 -2
  25. package/docs/project-setup.md +30 -10
  26. package/docs/reference/format-details.md +373 -170
  27. package/docs/reference/note-types.md +128 -109
  28. package/docs/reference/theme-pages.md +31 -6
  29. package/e2e.mjs +140 -28
  30. package/engine/actor-compiler.mjs +22 -31
  31. package/engine/address-charset.mjs +4 -17
  32. package/engine/address-fields.mjs +1 -1
  33. package/engine/address-render.mjs +12 -1
  34. package/engine/address-values.mjs +6 -0
  35. package/engine/address.mjs +46 -33
  36. package/engine/art-fields.mjs +30 -5
  37. package/engine/art-slots.mjs +10 -1
  38. package/engine/asset-bindings.mjs +12 -1
  39. package/engine/base-compiler.mjs +34 -65
  40. package/engine/being-age.mjs +2 -7
  41. package/engine/being-measurements.mjs +12 -1
  42. package/engine/calendar-artifacts.mjs +6 -0
  43. package/engine/calendar-choice.mjs +12 -1
  44. package/engine/calendar-format.mjs +12 -1
  45. package/engine/calendar-human.mjs +12 -1
  46. package/engine/compile-corpus.mjs +3 -1
  47. package/engine/content-address.mjs +9 -12
  48. package/engine/content-alerts.mjs +13 -1
  49. package/engine/content-blocks.mjs +14 -3
  50. package/engine/content-embeds.mjs +11 -3
  51. package/engine/content-figures.mjs +9 -0
  52. package/engine/content-footnotes.mjs +12 -1
  53. package/engine/content-format-check.mjs +11 -262
  54. package/engine/content-format.yaml +5 -2
  55. package/engine/content-images.mjs +3 -0
  56. package/engine/content-index.mjs +443 -20
  57. package/engine/content-links.mjs +29 -35
  58. package/engine/content-lint.mjs +2 -3
  59. package/engine/content-poetry.mjs +12 -1
  60. package/engine/content-slug.mjs +2 -14
  61. package/engine/content-spans.mjs +13 -1
  62. package/engine/content-tables.mjs +12 -1
  63. package/engine/culture-choice.mjs +210 -13
  64. package/engine/data-keys.mjs +19 -16
  65. package/engine/date-conversion.mjs +12 -1
  66. package/engine/declaration-check.mjs +9 -0
  67. package/engine/derived-fields.mjs +6 -7
  68. package/engine/diagnostics.mjs +34 -4
  69. package/engine/docs-checks.mjs +9 -0
  70. package/engine/document-subtypes.mjs +47 -34
  71. package/engine/event-views.mjs +139 -14
  72. package/engine/exported-scene.mjs +92 -28
  73. package/engine/extension-attributes.mjs +12 -1
  74. package/engine/field-spec.mjs +11 -102
  75. package/engine/folder-notes.mjs +0 -64
  76. package/engine/foreign-catalog.mjs +2 -1
  77. package/engine/format-generated.mjs +9 -0
  78. package/engine/foundry-entries.mjs +10 -28
  79. package/engine/frontmatter-lint.mjs +83 -392
  80. package/engine/frontmatter.mjs +20 -70
  81. package/engine/generate.mjs +10 -15
  82. package/engine/heading-attributes.mjs +12 -1
  83. package/engine/helpers.mjs +41 -102
  84. package/engine/holdings.mjs +1 -1
  85. package/engine/homepage.mjs +3 -10
  86. package/engine/ids.mjs +1 -167
  87. package/engine/index-records.mjs +6 -4
  88. package/engine/index.mjs +0 -3
  89. package/engine/infobox-render.mjs +15 -3
  90. package/engine/infobox.mjs +13 -14
  91. package/engine/item-compiler.mjs +4 -10
  92. package/engine/item-docs.mjs +3 -5
  93. package/engine/item-registry.mjs +2 -10
  94. package/engine/journals.mjs +1 -5
  95. package/engine/literature-notes.mjs +4 -12
  96. package/engine/macros.mjs +1 -2
  97. package/engine/map-graphviz.mjs +12 -1
  98. package/engine/markdown-expressions.mjs +12 -1
  99. package/engine/markdownlint-run.mjs +567 -0
  100. package/engine/metadata-index.mjs +42 -12
  101. package/engine/note-addresses.mjs +143 -35
  102. package/engine/note-claims.mjs +14 -28
  103. package/engine/note-dates.mjs +3 -3
  104. package/engine/note-frontmatter.mjs +6 -0
  105. package/engine/note-ids.mjs +8 -14
  106. package/engine/note-package.mjs +8 -25
  107. package/engine/note-state.mjs +1 -3
  108. package/engine/note-vocabulary.mjs +148 -84
  109. package/engine/number-words.mjs +12 -1
  110. package/engine/pack-config.mjs +15 -25
  111. package/engine/pack-router.mjs +10 -16
  112. package/engine/pathnames.mjs +8 -22
  113. package/engine/pdf-build.mjs +773 -121
  114. package/engine/pdf-coverage.mjs +188 -0
  115. package/engine/pdf-index.mjs +54 -7
  116. package/engine/pdf-pages.mjs +247 -0
  117. package/engine/pdf-render.mjs +114 -4
  118. package/engine/pdf-toc.mjs +29 -32
  119. package/engine/project-init.mjs +46 -6
  120. package/engine/prose-config.mjs +1 -1
  121. package/engine/prose-lint.mjs +52 -88
  122. package/engine/readability-lint.mjs +9 -0
  123. package/engine/reckoning-markers.mjs +12 -1
  124. package/engine/reference-targets.mjs +7 -0
  125. package/engine/runtime-only-fields.mjs +5 -8
  126. package/engine/scene-levels.mjs +2 -2
  127. package/engine/scenes.mjs +1 -1
  128. package/engine/shortcode-references.mjs +7 -0
  129. package/engine/site-build.mjs +30 -16
  130. package/engine/site-index.mjs +10 -16
  131. package/engine/site-preview.mjs +6 -0
  132. package/engine/site-root.mjs +8 -1
  133. package/engine/social-tie-terms.mjs +12 -1
  134. package/engine/social-ties.mjs +3 -0
  135. package/engine/sql-tables.mjs +56 -20
  136. package/engine/standing-terms.mjs +11 -1
  137. package/engine/standings.mjs +11 -27
  138. package/engine/system-block.mjs +39 -141
  139. package/engine/system-vocabulary.mjs +6 -0
  140. package/engine/walk-files.mjs +69 -0
  141. package/engine/web-wikilinks.mjs +23 -3
  142. package/engine/wikilink-syntax.mjs +32 -21
  143. package/engine/wikilinks.mjs +27 -16
  144. package/githooks/attribution.mjs +8 -0
  145. package/githooks/commit-message.mjs +9 -0
  146. package/hm3/actors.mjs +9 -48
  147. package/hm3/default-item-art.mjs +9 -9
  148. package/hm3/item-fields.mjs +2 -9
  149. package/hm3/template-priority.mjs +6 -7
  150. package/hugo-theme/data/banner-kinds.yaml +78 -0
  151. package/hugo-theme/data/banners.yaml +2 -3
  152. package/hugo-theme/layouts/404.html +3 -3
  153. package/hugo-theme/layouts/partials/banner-url.html +31 -7
  154. package/hugo-theme/layouts/partials/hero-banner.html +13 -14
  155. package/hugo-theme/layouts/partials/related.html +1 -1
  156. package/hugo-theme/layouts/partials/warn-missing-banner.html +1 -1
  157. package/hugo-theme/static/css/style.css +1 -1
  158. package/manifest.mjs +11 -4
  159. package/markdownlint-config.mjs +1 -1
  160. package/package.json +6 -3
  161. package/release.mjs +21 -14
  162. package/sohl/actors.mjs +16 -27
  163. package/sohl/default-item-art.mjs +3 -3
  164. package/sohl/document-subtypes.mjs +1 -16
  165. package/sohl/infobox.mjs +2 -3
  166. package/sohl/item-builders.mjs +5 -8
  167. package/sohl/item-fields.mjs +5 -35
  168. package/sohl/items.mjs +0 -4
  169. package/sohl/kb-passes.mjs +3 -3
  170. package/types/container.d.mts +116 -5
  171. package/types/content-config.d.mts +83 -35
  172. package/types/deploy.d.mts +42 -6
  173. package/types/e2e.d.mts +35 -3
  174. package/types/engine/address-charset.d.mts +4 -17
  175. package/types/engine/address.d.mts +17 -8
  176. package/types/engine/art-fields.d.mts +7 -4
  177. package/types/engine/art-slots.d.mts +6 -0
  178. package/types/engine/base-compiler.d.mts +15 -17
  179. package/types/engine/content-address.d.mts +9 -12
  180. package/types/engine/content-embeds.d.mts +4 -2
  181. package/types/engine/content-format-check.d.mts +0 -64
  182. package/types/engine/content-index.d.mts +82 -3
  183. package/types/engine/content-links.d.mts +2 -4
  184. package/types/engine/culture-choice.d.mts +51 -8
  185. package/types/engine/data-keys.d.mts +2 -2
  186. package/types/engine/derived-fields.d.mts +1 -2
  187. package/types/engine/diagnostics.d.mts +27 -4
  188. package/types/engine/document-subtypes.d.mts +12 -31
  189. package/types/engine/event-views.d.mts +14 -2
  190. package/types/engine/exported-scene.d.mts +3 -2
  191. package/types/engine/field-spec.d.mts +13 -122
  192. package/types/engine/folder-notes.d.mts +0 -39
  193. package/types/engine/foreign-catalog.d.mts +2 -1
  194. package/types/engine/foundry-entries.d.mts +4 -5
  195. package/types/engine/frontmatter-lint.d.mts +2 -2
  196. package/types/engine/frontmatter.d.mts +19 -45
  197. package/types/engine/generate.d.mts +1 -1
  198. package/types/engine/helpers.d.mts +25 -42
  199. package/types/engine/homepage.d.mts +3 -10
  200. package/types/engine/ids.d.mts +0 -114
  201. package/types/engine/index-records.d.mts +6 -4
  202. package/types/engine/index.d.mts +0 -1
  203. package/types/engine/infobox-render.d.mts +7 -1
  204. package/types/engine/infobox.d.mts +3 -2
  205. package/types/engine/item-docs.d.mts +1 -1
  206. package/types/engine/item-registry.d.mts +2 -4
  207. package/types/engine/literature-notes.d.mts +1 -1
  208. package/types/engine/markdownlint-run.d.mts +40 -0
  209. package/types/engine/metadata-index.d.mts +9 -1
  210. package/types/engine/note-addresses.d.mts +27 -2
  211. package/types/engine/note-claims.d.mts +3 -5
  212. package/types/engine/note-ids.d.mts +2 -4
  213. package/types/engine/note-package.d.mts +1 -1
  214. package/types/engine/note-state.d.mts +1 -3
  215. package/types/engine/note-vocabulary.d.mts +28 -21
  216. package/types/engine/pack-config.d.mts +2 -6
  217. package/types/engine/pack-router.d.mts +4 -4
  218. package/types/engine/pathnames.d.mts +3 -3
  219. package/types/engine/pdf-build.d.mts +98 -12
  220. package/types/engine/pdf-coverage.d.mts +60 -0
  221. package/types/engine/pdf-index.d.mts +34 -5
  222. package/types/engine/pdf-pages.d.mts +143 -0
  223. package/types/engine/pdf-render.d.mts +45 -1
  224. package/types/engine/pdf-toc.d.mts +12 -14
  225. package/types/engine/prose-config.d.mts +1 -1
  226. package/types/engine/prose-lint.d.mts +11 -10
  227. package/types/engine/runtime-only-fields.d.mts +2 -2
  228. package/types/engine/site-build.d.mts +9 -5
  229. package/types/engine/site-index.d.mts +4 -5
  230. package/types/engine/site-root.d.mts +1 -1
  231. package/types/engine/sql-tables.d.mts +31 -2
  232. package/types/engine/standing-terms.d.mts +2 -1
  233. package/types/engine/standings.d.mts +7 -9
  234. package/types/engine/system-block.d.mts +10 -44
  235. package/types/engine/walk-files.d.mts +15 -0
  236. package/types/engine/wikilink-syntax.d.mts +18 -19
  237. package/types/hm3/actors.d.mts +1 -2
  238. package/types/hm3/item-fields.d.mts +2 -4
  239. package/types/hm3/template-priority.d.mts +6 -7
  240. package/types/manifest.d.mts +3 -2
  241. package/types/release.d.mts +8 -7
  242. package/types/sohl/actors.d.mts +2 -1
  243. package/types/sohl/document-subtypes.d.mts +1 -2
  244. package/types/sohl/item-fields.d.mts +3 -7
  245. package/engine/retired-fields.mjs +0 -631
  246. package/types/engine/retired-fields.d.mts +0 -354
package/deploy.mjs CHANGED
@@ -194,6 +194,65 @@ async function exists(p) {
194
194
  }
195
195
  }
196
196
 
197
+ /**
198
+ * A previous package tree a deploy could not remove after the swap.
199
+ *
200
+ * @typedef {object} LeftoverTree
201
+ * @property {string} path - The tree, still under its `….old-<pid>` name.
202
+ * @property {string} reason - Why the last attempt to remove it failed.
203
+ */
204
+
205
+ /**
206
+ * Removes a tree the host user cannot, typically by acting as the user that
207
+ * owns its files. Receives the tree and the stage's data root.
208
+ *
209
+ * @callback RemoveTree
210
+ * @param {string} tree - The tree to remove.
211
+ * @param {{dataRoot: string}} where - The data root the tree sits beneath.
212
+ * @returns {void|Promise<void>}
213
+ */
214
+
215
+ /**
216
+ * @param {unknown} err - What a removal threw.
217
+ * @returns {string} Its message on one line, so it fits in one diagnostic.
218
+ */
219
+ function reasonOf(err) {
220
+ return (err instanceof Error ? err.message : String(err)).replace(/\s+/g, " ").trim();
221
+ }
222
+
223
+ /**
224
+ * Remove the previous tree once the new one is in place.
225
+ *
226
+ * Foundry running in a container writes files owned by the container's user,
227
+ * which under a rootless runtime is a mapped uid the host user cannot unlink.
228
+ * A refusal is handed to `removeTree`, which can act as that user. The install
229
+ * is already complete by then, so a tree neither can remove is reported rather
230
+ * than thrown, and keeps its name so it is never mistaken for anything else.
231
+ *
232
+ * @param {string} old - The previous tree.
233
+ * @param {((tree: string) => void|Promise<void>)|undefined} removeTree - The fallback remover.
234
+ * @returns {Promise<LeftoverTree|null>} The tree left behind, or `null`.
235
+ */
236
+ async function removePrevious(old, removeTree) {
237
+ try {
238
+ await fs.rm(old, { recursive: true, force: true });
239
+ return null;
240
+ } catch (err) {
241
+ const code = /** @type {NodeJS.ErrnoException} */ (err).code;
242
+ if (!removeTree || (code !== "EACCES" && code !== "EPERM")) {
243
+ return { path: old, reason: reasonOf(err) };
244
+ }
245
+ }
246
+ try {
247
+ await removeTree(old);
248
+ } catch (err) {
249
+ return { path: old, reason: reasonOf(err) };
250
+ }
251
+ return (await exists(old)) ?
252
+ { path: old, reason: "the tree is still present after the fallback removal" }
253
+ : null;
254
+ }
255
+
197
256
  /**
198
257
  * Mirror the staged build into a local directory via a staged, atomic swap.
199
258
  *
@@ -202,9 +261,13 @@ async function exists(p) {
202
261
  *
203
262
  * @param {string} srcAbs - The staged tree.
204
263
  * @param {string} destDir - Where the package installs.
205
- * @returns {Promise<void>}
264
+ * @param {object} [opts]
265
+ * @param {(tree: string) => void|Promise<void>} [opts.removeTree] - Removes a
266
+ * previous tree the host user is refused permission to remove.
267
+ * @returns {Promise<{leftovers: LeftoverTree[]}>} The previous trees that
268
+ * could not be removed; the new one is installed either way.
206
269
  */
207
- export async function deployLocal(srcAbs, destDir) {
270
+ export async function deployLocal(srcAbs, destDir, { removeTree } = {}) {
208
271
  const staging = `${destDir}.staging-${process.pid}`;
209
272
  const old = `${destDir}.old-${process.pid}`;
210
273
 
@@ -220,7 +283,37 @@ export async function deployLocal(srcAbs, destDir) {
220
283
  const hadDest = await exists(destDir);
221
284
  if (hadDest) await fs.rename(destDir, old);
222
285
  await fs.rename(staging, destDir);
223
- if (hadDest) await fs.rm(old, { recursive: true, force: true });
286
+
287
+ // 3. Remove every previous tree beside it: this deployment's own, and any
288
+ // an earlier one could not remove. A leftover still carries a manifest,
289
+ // so Foundry reads it as a second copy of the package.
290
+ const leftovers = [];
291
+ for (const tree of await previousTrees(destDir)) {
292
+ const leftover = await removePrevious(tree, removeTree);
293
+ if (leftover) leftovers.push(leftover);
294
+ }
295
+ return { leftovers };
296
+ }
297
+
298
+ /**
299
+ * The previous trees a local deploy has set aside beside a package:
300
+ * `<id>.old-<pid>`, whichever process's pid it carries.
301
+ *
302
+ * @param {string} destDir - Where the package installs.
303
+ * @returns {Promise<string[]>} Their paths, sorted.
304
+ */
305
+ async function previousTrees(destDir) {
306
+ const prefix = `${path.basename(destDir)}.old-`;
307
+ const entries = await fs.readdir(path.dirname(destDir), { withFileTypes: true });
308
+ return entries
309
+ .filter(
310
+ (entry) =>
311
+ entry.isDirectory() &&
312
+ entry.name.startsWith(prefix) &&
313
+ /^\d+$/.test(entry.name.slice(prefix.length)),
314
+ )
315
+ .map((entry) => path.join(path.dirname(destDir), entry.name))
316
+ .sort();
224
317
  }
225
318
 
226
319
  /**
@@ -235,7 +328,8 @@ export async function deployLocal(srcAbs, destDir) {
235
328
  * @param {string} remoteDir - Where the package installs on the host.
236
329
  * @param {object} [opts]
237
330
  * @param {(uploaded: string) => void} [opts.onUpload] - Per-file progress.
238
- * @returns {Promise<void>}
331
+ * @returns {Promise<{leftovers: LeftoverTree[]}>} The previous tree when it
332
+ * could not be removed; the new one is installed either way.
239
333
  */
240
334
  export async function deployRemote(conn, srcAbs, remoteDir, { onUpload } = {}) {
241
335
  const { default: Client } = await import("ssh2-sftp-client");
@@ -255,7 +349,13 @@ export async function deployRemote(conn, srcAbs, remoteDir, { onUpload } = {}) {
255
349
  const hadDest = Boolean(await sftp.exists(remoteDir));
256
350
  if (hadDest) await sftp.rename(remoteDir, old);
257
351
  await sftp.rename(staging, remoteDir);
258
- if (hadDest) await sftp.rmdir(old, true);
352
+ if (!hadDest) return { leftovers: [] };
353
+ try {
354
+ await sftp.rmdir(old, true);
355
+ return { leftovers: [] };
356
+ } catch (err) {
357
+ return { leftovers: [{ path: old, reason: reasonOf(err) }] };
358
+ }
259
359
  } finally {
260
360
  await sftp.end();
261
361
  }
@@ -273,7 +373,10 @@ export async function deployRemote(conn, srcAbs, remoteDir, { onUpload } = {}) {
273
373
  * @param {NodeJS.ProcessEnv} [opts.env] - The environment to read.
274
374
  * @param {string} [opts.prefix] - Fallback variable prefix for SFTP overrides.
275
375
  * @param {(message: string) => void} [opts.log] - Progress reporting.
276
- * @returns {Promise<{stage: string, destination: string, remote: boolean}>}
376
+ * @param {RemoveTree} [opts.removeTree] - Removes a previous local tree the
377
+ * host user is refused permission to remove.
378
+ * @returns {Promise<{stage: string, destination: string, remote: boolean, leftovers: LeftoverTree[]}>}
379
+ * `leftovers` names each previous tree the deploy installed past but could not remove.
277
380
  * @throws {Error} On an unknown stage, or one with no destination configured.
278
381
  */
279
382
  export async function deployStage({
@@ -284,6 +387,7 @@ export async function deployStage({
284
387
  env = process.env,
285
388
  prefix = "SOHL",
286
389
  log = () => {},
390
+ removeTree,
287
391
  }) {
288
392
  const name = resolveStage(stage);
289
393
  const envVarName = STAGE_ENV_MAP[name];
@@ -312,14 +416,16 @@ export async function deployStage({
312
416
  prefix,
313
417
  });
314
418
  log(`Deploying ${source} → ${conn.username}@${conn.host}:${remoteDir} (sftp)`);
315
- await deployRemote(conn, source, remoteDir, {
419
+ const { leftovers } = await deployRemote(conn, source, remoteDir, {
316
420
  onUpload: (f) => log(` ${f}`),
317
421
  });
318
- return { stage: name, destination: remoteDir, remote: true };
422
+ return { stage: name, destination: remoteDir, remote: true, leftovers };
319
423
  }
320
424
 
321
425
  const destDir = path.join(dataRoot, ...segments);
322
426
  log(`Deploying ${source} → ${destDir} (local copy)`);
323
- await deployLocal(source, destDir);
324
- return { stage: name, destination: destDir, remote: false };
427
+ const { leftovers } = await deployLocal(source, destDir, {
428
+ removeTree: removeTree && ((tree) => removeTree(tree, { dataRoot })),
429
+ });
430
+ return { stage: name, destination: destDir, remote: false, leftovers };
325
431
  }