@heroiclands/package-build 25.5.4 → 25.5.5

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 (57) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +7 -0
  3. package/bin/content-commands.mjs +32 -8
  4. package/docs/api.md +57 -16
  5. package/docs/authoring/links-and-markup.md +51 -3
  6. package/docs/commands.md +60 -17
  7. package/docs/configuration.md +3 -2
  8. package/docs/content-format.md +10 -0
  9. package/docs/diagnostics.md +17 -0
  10. package/docs/guides/module-project.md +11 -3
  11. package/docs/guides/site.md +12 -2
  12. package/docs/guides/writing-in-invented-scripts.md +5 -2
  13. package/docs/reference/format-details.md +61 -11
  14. package/docs/reference/note-types.md +32 -32
  15. package/e2e.mjs +1 -1
  16. package/engine/base-compiler.mjs +2 -0
  17. package/engine/compile-corpus.mjs +6 -2
  18. package/engine/content-charset.mjs +1 -1
  19. package/engine/content-render.mjs +14 -2
  20. package/engine/content-scripts.mjs +44 -6
  21. package/engine/content-session.mjs +11 -7
  22. package/engine/content-spans.mjs +3 -4
  23. package/engine/content-verse.mjs +52 -10
  24. package/engine/core-report.mjs +27 -18
  25. package/engine/language-names.mjs +53 -23
  26. package/engine/markdownlint-run.mjs +1 -1
  27. package/engine/markup-validation.mjs +2 -0
  28. package/engine/note-vocabulary.mjs +10 -5
  29. package/engine/npc-generate.mjs +52 -7
  30. package/engine/page-breaks.mjs +115 -0
  31. package/engine/pdf-build.mjs +12 -4
  32. package/engine/pdf-render.mjs +10 -1
  33. package/engine/preview-index.mjs +4 -2
  34. package/engine/site-build.mjs +12 -3
  35. package/engine/site-preview.mjs +3 -0
  36. package/engine/site-render.mjs +18 -4
  37. package/engine/site-root.mjs +10 -1
  38. package/engine/site-serve-http.mjs +151 -0
  39. package/engine/site-serve.mjs +196 -126
  40. package/engine/verse-metre.mjs +50 -5
  41. package/engine/verse-regions.mjs +16 -0
  42. package/package.json +3 -3
  43. package/types/e2e.d.mts +1 -1
  44. package/types/engine/content-scripts.d.mts +9 -1
  45. package/types/engine/content-verse.d.mts +11 -2
  46. package/types/engine/core-report.d.mts +6 -2
  47. package/types/engine/language-names.d.mts +18 -6
  48. package/types/engine/npc-generate.d.mts +3 -0
  49. package/types/engine/page-breaks.d.mts +30 -0
  50. package/types/engine/pdf-render.d.mts +2 -0
  51. package/types/engine/site-build.d.mts +3 -1
  52. package/types/engine/site-render.d.mts +6 -2
  53. package/types/engine/site-root.d.mts +3 -1
  54. package/types/engine/site-serve-http.d.mts +1 -0
  55. package/types/engine/site-serve.d.mts +12 -6
  56. package/types/engine/verse-metre.d.mts +2 -1
  57. package/types/engine/verse-regions.d.mts +8 -0
@@ -37,12 +37,13 @@ export function scanVerse(source: string, { checkWorks }?: {
37
37
  * Offsets are UTF-16 positions relative to body. Delimiters and indentation are
38
38
  * layout ranges; source newlines inside a logical line contribute one space.
39
39
  * @param {string} body - Authored verse body.
40
- * @param {{inline?: boolean, footnotes?: ReadonlySet<string>}} [options] - Inline quotations have no cantos; footnotes are same-note definition labels.
40
+ * @param {{inline?: boolean, footnotes?: ReadonlySet<string>, sourceOffsets?: number[]}} [options] - Inline quotations have no cantos; footnotes are same-note definition labels; sourceOffsets maps projected body positions to authored offsets, including the end.
41
41
  * @returns {VerseModel} The shared structure with offsets relative to the body.
42
42
  */
43
- export function parseVerse(body: string, { inline, footnotes }?: {
43
+ export function parseVerse(body: string, { inline, footnotes, sourceOffsets }?: {
44
44
  inline?: boolean;
45
45
  footnotes?: ReadonlySet<string>;
46
+ sourceOffsets?: number[];
46
47
  }): VerseModel;
47
48
  /**
48
49
  * Validate a quoted verse span using its original source coordinates.
@@ -271,6 +272,14 @@ export type VerseBlock = {
271
272
  * - Authored verse body.
272
273
  */
273
274
  body: string;
275
+ /**
276
+ * - Body without Markdown container prefixes.
277
+ */
278
+ logicalBody: string;
279
+ /**
280
+ * - Logical UTF-16 offsets mapped to authored body offsets, including the end.
281
+ */
282
+ bodyOffsets: number[];
274
283
  /**
275
284
  * - Logical structure relative to the body.
276
285
  */
@@ -4,7 +4,7 @@
4
4
  * @param {Iterable<Record<string, any>>} records - Index records.
5
5
  * @param {{language?: string}} [options] - A language to report alone, by its
6
6
  * name, shortcode or reference slug whether or not it states `data.core`.
7
- * @returns {{concepts: number, languages: Array<{name: string, language: string, core: boolean, total: number, covered: number, missing: Array<{group: string, count: number, total: number, concepts: Array<{id: string, gloss: string}>}>}>}|{problem: string, about: string}}
7
+ * @returns {{concepts: number, notCovered: string[], languages: Array<{name: string, language: string, core: boolean, total: number, covered: number, missing: Array<{group: string, count: number, total: number, concepts: Array<{id: string, gloss: string}>}>}>}|{problem: string, about: string}}
8
8
  * The report, or the problem when there is no concept list or the language
9
9
  * asked for is none.
10
10
  */
@@ -12,6 +12,7 @@ export function coreReport(records: Iterable<Record<string, any>>, { language }?
12
12
  language?: string;
13
13
  }): {
14
14
  concepts: number;
15
+ notCovered: string[];
15
16
  languages: Array<{
16
17
  name: string;
17
18
  language: string;
@@ -36,10 +37,13 @@ export function coreReport(records: Iterable<Record<string, any>>, { language }?
36
37
  * A core report as text: one block per language, its missing concepts under
37
38
  * their groups with counts, each concept's id and gloss tab-separated.
38
39
  *
39
- * @param {{concepts: number, languages: Array<object>}} report - A report from {@link coreReport}.
40
+ * The default report ends with the names of languages it does not measure.
41
+ *
42
+ * @param {{concepts: number, notCovered: string[], languages: Array<object>}} report - A report from {@link coreReport}.
40
43
  * @returns {string} The text, ending in a newline.
41
44
  */
42
45
  export function coreReportText(report: {
43
46
  concepts: number;
47
+ notCovered: string[];
44
48
  languages: Array<object>;
45
49
  }): string;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The genders every `data.names.given` states a list for.
2
+ * The genders a mapped `data.names.given` states a list for.
3
3
  *
4
4
  * A function rather than a `const`: `note-vocabulary.mjs` reads it while the
5
5
  * module graph is still evaluating.
@@ -47,7 +47,7 @@ export function namesProblems(names: unknown, lookVocabulary: Readonly<Record<st
47
47
  * keys cannot: a pattern that does not parse, a placeholder naming no list, a
48
48
  * list no pattern names, a list name a placeholder cannot spell, an empty list
49
49
  * of patterns or of names, an empty name, a pattern or given list keyed by no
50
- * gender, and a missing `female` or `male` list. Each is located at the pattern, list, entry or key at fault.
50
+ * gender, and a missing `female` or `male` list in a gender map. Each is located at the pattern, list, entry or key at fault.
51
51
  *
52
52
  * @param {object} note - The note: `file`, `raw` and parsed `fm`.
53
53
  * @param {Readonly<Record<string, readonly {value: string}[]>>} lookVocabulary -
@@ -70,8 +70,8 @@ export function namesFindings(note: object, lookVocabulary: Readonly<Record<stri
70
70
  value: string;
71
71
  }[]>>): object[];
72
72
  /**
73
- * The given names a gender draws from: its own list, or every given list
74
- * together where it has none.
73
+ * The given names a gender draws from: the shared list, its own list, or
74
+ * every given list together where it has none.
75
75
  *
76
76
  * @param {object} names - Checked `data.names`.
77
77
  * @param {string} gender - A gender word.
@@ -79,8 +79,20 @@ export function namesFindings(note: object, lookVocabulary: Readonly<Record<stri
79
79
  */
80
80
  export function givenNames(names: object, gender: string): string[];
81
81
  /**
82
- * A name drawn from a language's lists: one of the gender's patterns, where it
83
- * has more than one, then each placeholder an even draw from its list.
82
+ * A name's selected literal and placeholder parts. One of the gender's
83
+ * patterns is selected first, then each placeholder is an even draw from its
84
+ * source list.
85
+ *
86
+ * @param {object} names - Checked `data.names`.
87
+ * @param {string} gender - A gender word.
88
+ * @param {() => number} next - A stream of numbers in `[0, 1)`.
89
+ * @returns {(PatternPart & {text: string})[]} The selected parts in pattern order.
90
+ */
91
+ export function drawNameParts(names: object, gender: string, next: () => number): (PatternPart & {
92
+ text: string;
93
+ })[];
94
+ /**
95
+ * A name drawn from a language's lists.
84
96
  *
85
97
  * @param {object} names - Checked `data.names`.
86
98
  * @param {string} gender - A gender word.
@@ -57,6 +57,8 @@ export function appearanceParagraph({ name, age, gender, height, frame, appearan
57
57
  * by `namesProblems`.
58
58
  * @param {string} [context.language] - The language's Address, for an error
59
59
  * about a name that does not fit.
60
+ * @param {string} [context.languageCode] - The language code under which a
61
+ * generated name's distinct source form is retained.
60
62
  * @param {string} [context.seed] - Text joined to the spec's own seed, to draw a
61
63
  * different person from the same spec.
62
64
  * @param {unknown} [context.currentDate] - The setting's neutral current date, defaulting to "720.1".
@@ -74,6 +76,7 @@ export function generateNpc(spec: object, context: {
74
76
  profile: object;
75
77
  names: object;
76
78
  language?: string | undefined;
79
+ languageCode?: string | undefined;
77
80
  seed?: string | undefined;
78
81
  currentDate?: unknown;
79
82
  dateContext?: object | undefined;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Read page-break markers before any surface rewrites their source.
3
+ * Code, HTML examples and entire verse divs remain literal. Parser ranges
4
+ * retain list and quote membership, including lazy continuation lines.
5
+ * @param {string} source - Authored Markdown body.
6
+ * @returns {{breaks: Array<{line: number, column: number}>, errors: Array<{line: number, column: number, message: string}>}}
7
+ */
8
+ export function scanPageBreaks(source: string): {
9
+ breaks: Array<{
10
+ line: number;
11
+ column: number;
12
+ }>;
13
+ errors: Array<{
14
+ line: number;
15
+ column: number;
16
+ message: string;
17
+ }>;
18
+ };
19
+ /**
20
+ * Omit valid page breaks on a screen, preserving the surrounding block boundaries.
21
+ * @param {string} source - Markdown body.
22
+ * @returns {string} Markdown without valid page-break lines.
23
+ */
24
+ export function omitPageBreaks(source: string): string;
25
+ /**
26
+ * Produce the book's page-break token, leaving ordinary thematic breaks alone.
27
+ * @param {import("markdown-it")} md - The book parser.
28
+ * @returns {void}
29
+ */
30
+ export function pageBreakPlugin(md: typeof import("markdown-it")): void;
@@ -98,6 +98,7 @@ export function stripHtmlComments(markdown: string): string;
98
98
  * @param {Array<{line: number, generated: boolean}>} [opts.lineMap] - From
99
99
  * table expansion, mapping each line handed in back to the line an author
100
100
  * wrote.
101
+ * @param {boolean} [opts.pageBreaks] - Internal: whether this slice remains at the note top level.
101
102
  * @param {boolean} [opts.prepared] - Whether this body has been through the
102
103
  * passes that resolve a wikilink, an embed and an inline expression. A note's
103
104
  * has; a front-matter file's and a prose file's have not, and markup of theirs
@@ -138,6 +139,7 @@ export function markdownToTypst(markdown: string, opts?: {
138
139
  line: number;
139
140
  generated: boolean;
140
141
  }[] | undefined;
142
+ pageBreaks?: boolean | undefined;
141
143
  prepared?: boolean | undefined;
142
144
  url?: string | undefined;
143
145
  elsewhere?: {
@@ -306,6 +306,7 @@ export function sitePageDecorator(config: any, index: any): (data: any, page: an
306
306
  * @param {object} [options] - Options.
307
307
  * @param {object} [options.config] - A resolved configuration; loaded when
308
308
  * omitted.
309
+ * @param {(message: string) => void} [options.progress] - Build phase summaries.
309
310
  * @param {Map<string, object[]>} [options.sqlTables] - Prepared `sql` results,
310
311
  * keyed by the note's absolute file, from
311
312
  * {@link module:engine/sql-tables.prepareSqlTables}. A page authoring an
@@ -322,8 +323,9 @@ export function sitePageDecorator(config: any, index: any): (data: any, page: an
322
323
  * against the rest of the build, or returns `null` when its address, name
323
324
  * or type changed and the whole site must be built again.
324
325
  */
325
- export function buildSite({ config, sqlTables }?: {
326
+ export function buildSite({ config, sqlTables, progress }?: {
326
327
  config?: object | undefined;
328
+ progress?: ((message: string) => void) | undefined;
327
329
  sqlTables?: Map<string, object[]> | undefined;
328
330
  }): {
329
331
  gates: object;
@@ -50,19 +50,21 @@ export function writeThemeFiles(out: string, staticDir?: string): string[];
50
50
  * @param {string} [options.staticDir] - The theme's own files, copied first.
51
51
  * @param {ReadonlySet<string>} [options.only] - The addresses of the pages to
52
52
  * draw again into a site already written. Nothing else is touched.
53
+ * @param {(message: string) => void} [options.progress] - Build phase summaries.
53
54
  * @returns {{files: string[], pages: string[], reachable: string[],
54
55
  * findings: object[]}} Every file written, the address of every page, the
55
56
  * address of each page reachable from the homepage, and the layouts' findings.
56
57
  * @throws {Error} When a page's address is outside the site, or a site served
57
58
  * under a path asks for a sitemap index.
58
59
  */
59
- export function writeSite({ site, pages, out, report, staticDir, only }: {
60
+ export function writeSite({ site, pages, out, report, staticDir, only, progress, }: {
60
61
  site: object;
61
62
  pages: readonly object[];
62
63
  out: string;
63
64
  report?: ((finding: import("./site-theme.mjs").Finding) => void) | undefined;
64
65
  staticDir?: string | undefined;
65
66
  only?: ReadonlySet<string> | undefined;
67
+ progress?: ((message: string) => void) | undefined;
66
68
  }): {
67
69
  files: string[];
68
70
  pages: string[];
@@ -147,6 +149,7 @@ export function deliverSiteFonts(config: object, out: string): {
147
149
  * @param {string} options.out - The directory the site is written to, which
148
150
  * is the package's own directory under the deployed root.
149
151
  * @param {number} [options.year] - The year the footer states.
152
+ * @param {(message: string) => void} [options.progress] - Build phase summaries.
150
153
  * @returns {Promise<{built: object, written: object|null, fonts: object|null,
151
154
  * site?: object, pages?: object[], pageOf?: (entry: object) => object}>}
152
155
  * What the build found, and, when it found nothing that stops it, what was
@@ -154,10 +157,11 @@ export function deliverSiteFonts(config: object, out: string): {
154
157
  * becomes its page. `written` is `null` when a gate fired.
155
158
  * @throws {Error} When the site's settings or its cached menu cannot be read.
156
159
  */
157
- export function buildPackageSite({ config, out, year }: {
160
+ export function buildPackageSite({ config, out, year, progress }: {
158
161
  config: object;
159
162
  out: string;
160
163
  year?: number | undefined;
164
+ progress?: ((message: string) => void) | undefined;
161
165
  }): Promise<{
162
166
  built: object;
163
167
  written: object | null;
@@ -73,6 +73,7 @@ export function indexSite(where: {
73
73
  * `site.search` in the configuration. Default `true`.
74
74
  * @param {SiteIndexer} [options.indexer] - What builds it. Default
75
75
  * {@link indexSite}.
76
+ * @param {(message: string) => void} [options.progress] - Build phase summaries.
76
77
  * @returns {Promise<{files: string[], search: {dir: string, pages: number}|null}>}
77
78
  * The files written, and the index — where it is and how many pages it
78
79
  * holds — or `null` when none was built.
@@ -81,12 +82,13 @@ export function indexSite(where: {
81
82
  * under the prefix; or, located at the rendered site, when the indexer
82
83
  * fails.
83
84
  */
84
- export function writeSiteRoot({ pkg, base, out, search, indexer }: {
85
+ export function writeSiteRoot({ pkg, base, out, search, indexer, progress, }: {
85
86
  pkg: string;
86
87
  base?: string | undefined;
87
88
  out: string;
88
89
  search?: boolean | undefined;
89
90
  indexer?: SiteIndexer | undefined;
91
+ progress?: ((message: string) => void) | undefined;
90
92
  }): Promise<{
91
93
  files: string[];
92
94
  search: {
@@ -0,0 +1 @@
1
+ export {};
@@ -9,16 +9,19 @@
9
9
  * directory above `out` by default; `out` itself for a site served at `/`.
10
10
  * @param {string} [options.host] - The address to listen on.
11
11
  * @param {number} [options.port] - The port; `0` takes any free one.
12
- * @param {boolean} [options.search] - Build the search index after the first
12
+ * @param {boolean} [options.search] - Build the search index with each whole
13
13
  * build, where the site has one.
14
14
  * @param {number} [options.year] - The year the footer states.
15
- * @param {(event: {kind: string, built?: object, files?: string[], findings?: object[], error?: unknown}) => void} [options.report] -
16
- * Told of each build: `built` for a whole build, `page` for one note's page,
15
+ * @param {(event: {kind: string, built?: object, files?: string[], findings?: object[], error?: unknown, message?: string, fatal?: boolean}) => void} [options.report] -
16
+ * Told of progress (`message`) and each build: `built` for a whole build, `page` for one note's page,
17
17
  * `theme` for the theme, `failed` with what the build found, `error` with
18
18
  * what it threw.
19
- * @returns {Promise<{url: string, close: () => Promise<void>, settled: () => Promise<void>}>}
20
- * The address of the site's home page, how to stop serving, and a promise
21
- * for the moment every change seen so far has been written.
19
+ * @returns {Promise<{url: string, close: () => Promise<void>, settled: () => Promise<void>, done: Promise<void>}>}
20
+ * Resolves as soon as HTTP listens, before the first build. The address of
21
+ * the site's home page, how to stop serving, and a promise for the initial
22
+ * build and every change seen so far to finish. Failed builds are reported
23
+ * through `report` and leave the last successful site in service. `done`
24
+ * resolves on close and rejects if HTTP stops unexpectedly.
22
25
  */
23
26
  export function serveSite({ config, out, root, host, port, search, year, report, }: {
24
27
  config: object;
@@ -34,11 +37,14 @@ export function serveSite({ config, out, root, host, port, search, year, report,
34
37
  files?: string[];
35
38
  findings?: object[];
36
39
  error?: unknown;
40
+ message?: string;
41
+ fatal?: boolean;
37
42
  }) => void) | undefined;
38
43
  }): Promise<{
39
44
  url: string;
40
45
  close: () => Promise<void>;
41
46
  settled: () => Promise<void>;
47
+ done: Promise<void>;
42
48
  }>;
43
49
  /** The address a served page listens on for the server's reloads. */
44
50
  export const RELOAD_PATH: "/__package-build/reload";
@@ -73,7 +73,7 @@ export function metreMessage({ min, max }: {
73
73
  * @param {ReadonlyArray<{start: number, end: number, indent: string, attributes: Record<string, string>, classes: string[]}>} blocks -
74
74
  * The divs, from {@link module:engine/content-verse.scanVerse}.
75
75
  * @param {string[]} lines - The source's lines.
76
- * @param {{languages?: Readonly<Record<string, {tag: string}>>, metres?: ReadonlyMap<string, import("./metre-declaration.mjs").Metre>, metreBooks?: ReadonlyMap<string, MetreBook>, lexicons?: ReadonlyMap<string, unknown>, attributeColumns: (line: string, key: string) => {key: number, value: number}}} ctx -
76
+ * @param {{languages?: Readonly<Record<string, {tag: string}>>, metres?: ReadonlyMap<string, import("./metre-declaration.mjs").Metre>, metreBooks?: ReadonlyMap<string, MetreBook>, lexicons?: ReadonlyMap<string, unknown>, referenceName?: (target: string) => string|undefined, attributeColumns: (line: string, key: string) => {key: number, value: number}}} ctx -
77
77
  * The metres and books, and how the div says its columns and where its
78
78
  * keys sit.
79
79
  * @returns {{errors: object[], warnings: object[]}} The findings at 1-based
@@ -92,6 +92,7 @@ export function metreFindings(blocks: ReadonlyArray<{
92
92
  metres?: ReadonlyMap<string, import("./metre-declaration.mjs").Metre>;
93
93
  metreBooks?: ReadonlyMap<string, MetreBook>;
94
94
  lexicons?: ReadonlyMap<string, unknown>;
95
+ referenceName?: (target: string) => string | undefined;
95
96
  attributeColumns: (line: string, key: string) => {
96
97
  key: number;
97
98
  value: number;
@@ -51,6 +51,14 @@ export type VerseRegion = {
51
51
  * - Authored body bytes.
52
52
  */
53
53
  body: string;
54
+ /**
55
+ * - Body without Markdown container prefixes.
56
+ */
57
+ logicalBody: string;
58
+ /**
59
+ * - Logical UTF-16 offsets mapped to authored body offsets, including the end.
60
+ */
61
+ bodyOffsets: number[];
54
62
  /**
55
63
  * - Authored values.
56
64
  */