@heroiclands/package-build 22.15.0 → 22.16.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.
- package/CHANGELOG.md +103 -0
- package/README.md +39 -678
- package/bin/{content-build.mjs → content-commands.mjs} +197 -118
- package/bin/package-build.mjs +303 -42
- package/bin/report.mjs +1 -1
- package/ci/ci-docker.mjs +6 -3
- package/ci/ci-steps.mjs +6 -4
- package/ci/content-format-reference.mjs +106 -0
- package/config.mjs +22 -2
- package/content-config.mjs +74 -134
- package/docs/api.md +294 -234
- package/docs/authoring/assets.md +31 -0
- package/docs/authoring/dates-and-calendars.md +33 -0
- package/docs/authoring/first-note.md +48 -0
- package/docs/authoring/frontmatter.md +40 -0
- package/docs/authoring/links-and-markup.md +137 -0
- package/docs/commands.md +631 -162
- package/docs/configuration.md +176 -140
- package/docs/content-format.md +50 -4229
- package/docs/design.md +32 -0
- package/docs/diagnostics.md +29 -22
- package/docs/getting-started.md +21 -877
- package/docs/guides/book.md +153 -0
- package/docs/guides/e2e.md +143 -0
- package/docs/guides/icons.md +126 -0
- package/docs/guides/localization.md +120 -0
- package/docs/guides/module-project.md +915 -0
- package/docs/guides/packs.md +167 -0
- package/docs/guides/reference-pages.md +148 -0
- package/docs/guides/release-and-deploy.md +114 -0
- package/docs/guides/site.md +152 -0
- package/docs/guides/system-project.md +45 -0
- package/docs/index.md +16 -0
- package/docs/project-setup.md +54 -35
- package/docs/reference/format-details.md +4420 -0
- package/docs/reference/note-types.md +531 -0
- package/engine/address-render.mjs +26 -0
- package/engine/address.mjs +3 -20
- package/engine/anchors.mjs +15 -6
- package/engine/asset-bindings.mjs +6 -2
- package/engine/base-compiler.mjs +47 -11
- package/engine/being-measurements.mjs +70 -0
- package/engine/calendar-artifacts.mjs +1 -1
- package/engine/calendar-choice.mjs +32 -0
- package/engine/calendar-human.mjs +71 -0
- package/engine/calendar-notes.mjs +41 -3
- package/engine/calendars.mjs +8 -1
- package/engine/changelog-lint.mjs +2 -2
- package/engine/code-fences.mjs +23 -97
- package/engine/compile-corpus.mjs +6 -7
- package/engine/content-admonitions.mjs +114 -0
- package/engine/content-captions.mjs +181 -0
- package/engine/content-embeds.mjs +2 -2
- package/engine/content-footnotes.mjs +34 -0
- package/engine/content-format-check.mjs +2 -2
- package/engine/content-format.mjs +127 -73
- package/engine/content-format.yaml +745 -0
- package/engine/content-html.mjs +1 -1
- package/engine/content-icons.mjs +45 -92
- package/engine/content-images.mjs +22 -74
- package/engine/content-index.mjs +12 -3
- package/engine/content-links.mjs +21 -26
- package/engine/content-lint.mjs +1 -5
- package/engine/content-secrets.mjs +3 -1
- package/engine/content-tables.mjs +47 -1257
- package/engine/culture-choice.mjs +32 -0
- package/engine/date-conversion.mjs +47 -0
- package/engine/declaration-check.mjs +87 -0
- package/engine/docs-checks.mjs +140 -0
- package/engine/documentation-links.mjs +31 -0
- package/engine/exported-scene.mjs +1 -1
- package/engine/extension-attributes.mjs +74 -0
- package/engine/field-reference.mjs +1 -1
- package/engine/field-spec.mjs +4 -0
- package/engine/foreign-catalog.mjs +5 -5
- package/engine/format-generated.mjs +15 -0
- package/engine/foundry-entries.mjs +3 -1
- package/engine/frontmatter-lint.mjs +5 -1
- package/engine/frontmatter.mjs +10 -0
- package/engine/generate.mjs +2 -2
- package/engine/helpers.mjs +71 -117
- package/engine/homepage.mjs +2 -3
- package/engine/index-records.mjs +1 -0
- package/engine/index.mjs +18 -3
- package/engine/infobox-registry.mjs +3 -1
- package/engine/infobox.mjs +38 -20
- package/engine/item-compiler.mjs +10 -8
- package/engine/item-docs.mjs +7 -6
- package/engine/itinerary-scenes.mjs +4 -2
- package/engine/journals.mjs +51 -10
- package/engine/macros.mjs +1 -1
- package/engine/map-build.mjs +1 -1
- package/engine/map-dot.mjs +1 -1
- package/engine/map-raster.mjs +4 -2
- package/engine/markdown-expressions.mjs +117 -0
- package/engine/metadata-index.mjs +7 -3
- package/engine/note-addresses.mjs +1 -0
- package/engine/note-dates.mjs +283 -61
- package/engine/note-package.mjs +5 -25
- package/engine/note-state.mjs +6 -2
- package/engine/note-vocabulary.mjs +74 -19
- package/engine/number-words.mjs +75 -0
- package/engine/office-holders.mjs +188 -0
- package/engine/pack-config.mjs +2 -15
- package/engine/pack-router.mjs +24 -5
- package/engine/packages.mjs +3 -2
- package/engine/pathnames.mjs +21 -24
- package/engine/pdf-build.mjs +217 -55
- package/engine/pdf-fonts.mjs +2 -2
- package/engine/pdf-images.mjs +82 -0
- package/engine/pdf-render.mjs +174 -22
- package/engine/populations.mjs +3 -2
- package/engine/project-init.mjs +499 -0
- package/engine/prose-config.mjs +1 -1
- package/engine/prose-lint.mjs +7 -6
- package/engine/readability-lint.mjs +86 -0
- package/engine/reckoning-markers.mjs +205 -93
- package/engine/scenes.mjs +5 -2
- package/engine/schema-check.mjs +3 -3
- package/engine/site-build.mjs +87 -37
- package/engine/site-config.mjs +8 -8
- package/engine/site-index.mjs +4 -0
- package/engine/site-maps.mjs +1 -1
- package/engine/site-preview.mjs +19 -8
- package/engine/social-ties.mjs +5 -1
- package/engine/sql-tables.mjs +92 -22
- package/engine/web-wikilinks.mjs +15 -31
- package/engine/wikilink-syntax.mjs +4 -1
- package/engine/wikilinks.mjs +12 -14
- package/hm3/item-fields.mjs +1 -1
- package/hm3/items.mjs +12 -45
- package/index.mjs +2 -14
- package/manifest.mjs +31 -4
- package/markdownlint-config.mjs +1 -1
- package/package.json +32 -11
- package/prettier-config.mjs +1 -1
- package/sohl/being-info.mjs +3 -145
- package/sohl/index.mjs +1 -1
- package/sohl/infobox.mjs +2 -1
- package/sohl/item-builders.mjs +3 -27
- package/sohl/item-fields.mjs +61 -22
- package/sohl/kb-passes.mjs +2 -2
- package/sohl/note-schemas.mjs +0 -8
- package/stage.mjs +19 -0
- package/types/config.d.mts +12 -3
- package/types/content-config.d.mts +15 -81
- package/types/engine/address-render.d.mts +13 -0
- package/types/engine/address.d.mts +2 -11
- package/types/engine/anchors.d.mts +3 -4
- package/types/engine/asset-bindings.d.mts +9 -2
- package/types/engine/base-compiler.d.mts +1 -2
- package/types/engine/being-measurements.d.mts +16 -0
- package/types/engine/calendar-choice.d.mts +14 -0
- package/types/engine/calendar-human.d.mts +4 -0
- package/types/engine/calendar-notes.d.mts +2 -0
- package/types/engine/calendars.d.mts +5 -3
- package/types/engine/code-fences.d.mts +6 -72
- package/types/engine/compile-corpus.d.mts +1 -3
- package/types/engine/content-admonitions.d.mts +24 -0
- package/types/engine/content-captions.d.mts +45 -0
- package/types/engine/content-footnotes.d.mts +10 -0
- package/types/engine/content-format-check.d.mts +17 -4
- package/types/engine/content-format.d.mts +24 -11
- package/types/engine/content-icons.d.mts +14 -23
- package/types/engine/content-images.d.mts +2 -2
- package/types/engine/content-index.d.mts +9 -2
- package/types/engine/content-links.d.mts +3 -5
- package/types/engine/content-tables.d.mts +11 -140
- package/types/engine/culture-choice.d.mts +14 -0
- package/types/engine/date-conversion.d.mts +4 -0
- package/types/engine/declaration-check.d.mts +24 -0
- package/types/engine/docs-checks.d.mts +28 -0
- package/types/engine/documentation-links.d.mts +7 -0
- package/types/engine/exported-scene.d.mts +6 -2
- package/types/engine/extension-attributes.d.mts +14 -0
- package/types/engine/field-spec.d.mts +9 -0
- package/types/engine/format-generated.d.mts +7 -0
- package/types/engine/frontmatter.d.mts +14 -4
- package/types/engine/generate.d.mts +1 -1
- package/types/engine/helpers.d.mts +37 -92
- package/types/engine/homepage.d.mts +2 -3
- package/types/engine/index.d.mts +5 -0
- package/types/engine/infobox-registry.d.mts +3 -1
- package/types/engine/infobox.d.mts +17 -6
- package/types/engine/item-compiler.d.mts +4 -4
- package/types/engine/item-docs.d.mts +4 -3
- package/types/engine/journals.d.mts +10 -7
- package/types/engine/map-graphviz.d.mts +1 -1
- package/types/engine/markdown-expressions.d.mts +17 -0
- package/types/engine/note-dates.d.mts +47 -1
- package/types/engine/note-package.d.mts +3 -19
- package/types/engine/note-state.d.mts +2 -1
- package/types/engine/note-vocabulary.d.mts +9 -0
- package/types/engine/number-words.d.mts +4 -0
- package/types/engine/office-holders.d.mts +14 -0
- package/types/engine/pack-config.d.mts +1 -1
- package/types/engine/packages.d.mts +3 -2
- package/types/engine/pdf-images.d.mts +38 -0
- package/types/engine/populations.d.mts +2 -2
- package/types/engine/project-init.d.mts +17 -0
- package/types/engine/prose-config.d.mts +1 -1
- package/types/engine/prose-lint.d.mts +3 -2
- package/types/engine/readability-lint.d.mts +22 -0
- package/types/engine/reckoning-markers.d.mts +4 -2
- package/types/engine/schema-check.d.mts +3 -3
- package/types/engine/site-build.d.mts +38 -28
- package/types/engine/site-config.d.mts +2 -2
- package/types/engine/site-index.d.mts +4 -0
- package/types/engine/site-preview.d.mts +1 -1
- package/types/engine/social-ties.d.mts +8 -2
- package/types/engine/sql-tables.d.mts +13 -2
- package/types/engine/web-wikilinks.d.mts +2 -1
- package/types/engine/wikilinks.d.mts +7 -1
- package/types/hm3/items.d.mts +5 -6
- package/types/labels.d.mts +40 -0
- package/types/manifest.d.mts +3 -3
- package/types/sohl/actors.d.mts +2 -2
- package/types/sohl/being-info.d.mts +3 -43
- package/types/sohl/index.d.mts +1 -1
- package/types/sohl/infobox.d.mts +5 -2
- package/types/sohl/item-fields.d.mts +4 -13
- package/types/stage.d.mts +9 -0
- package/CONTENT.md +0 -3239
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
|
|
11
11
|
import { positionOfFrontmatterPath } from "./diagnostics.mjs";
|
|
12
12
|
import { canonicalDayOffset, daysInMonth } from "./calendars.mjs";
|
|
13
|
-
import { parseNoteDate } from "./note-dates.mjs";
|
|
13
|
+
import { DATEFROM_PATTERN, parseNoteDate } from "./note-dates.mjs";
|
|
14
|
+
import { eraNames } from "./calendar-human.mjs";
|
|
15
|
+
import { addressSuffixes, renderAddress } from "./address-render.mjs";
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
18
|
* Resolve every declared reckoning marker against its calendar and era start.
|
|
@@ -18,126 +20,205 @@ import { parseNoteDate } from "./note-dates.mjs";
|
|
|
18
20
|
*
|
|
19
21
|
* @param {{notes?: readonly object[]}} index - The corpus link index.
|
|
20
22
|
* @param {number} daysPerYear - The world's year length.
|
|
21
|
-
* @returns {{markers: Map<string, object>, findings: object[]}} Resolved markers and errors.
|
|
23
|
+
* @returns {{eras: Map<string, object>, markers: Map<string, object>, findings: object[]}} Resolved eras, markers, and errors.
|
|
22
24
|
*/
|
|
23
25
|
export function resolveReckoningMarkers(index, daysPerYear) {
|
|
24
26
|
const declared = new Map();
|
|
27
|
+
const qualifiers = new Map();
|
|
28
|
+
const markerKeys = new Map();
|
|
29
|
+
const eras = new Map();
|
|
25
30
|
const markers = new Map();
|
|
26
31
|
const findings = [];
|
|
32
|
+
const failed = new Set();
|
|
33
|
+
const visiting = [];
|
|
34
|
+
|
|
35
|
+
const location = (row, key) => ({
|
|
36
|
+
...(row.note.file === undefined ? {} : { file: row.note.file }),
|
|
37
|
+
...(row.note.raw === undefined ?
|
|
38
|
+
{}
|
|
39
|
+
: positionOfFrontmatterPath(row.note.raw, ["data", "eras", row.position, key])),
|
|
40
|
+
});
|
|
41
|
+
const finding = (row, key, message) =>
|
|
42
|
+
findings.push({
|
|
43
|
+
...location(row, key),
|
|
44
|
+
severity: "error",
|
|
45
|
+
message,
|
|
46
|
+
});
|
|
47
|
+
const alias = (qualifier, key) => {
|
|
48
|
+
if (qualifiers.has(qualifier) && qualifiers.get(qualifier) !== key)
|
|
49
|
+
qualifiers.set(qualifier, null);
|
|
50
|
+
else qualifiers.set(qualifier, key);
|
|
51
|
+
};
|
|
27
52
|
|
|
28
53
|
for (const note of index?.notes ?? []) {
|
|
29
54
|
const fm = note?.fm ?? note?.frontmatter ?? note;
|
|
30
55
|
if (fm?.type !== "lore" || fm?.subType !== "calendar") continue;
|
|
31
|
-
const
|
|
32
|
-
if (!Array.isArray(
|
|
33
|
-
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
56
|
+
const rows = fm.data?.eras;
|
|
57
|
+
if (!Array.isArray(rows)) continue;
|
|
58
|
+
rows.forEach((era, position) => {
|
|
59
|
+
if (
|
|
60
|
+
!era ||
|
|
61
|
+
typeof era.shortcode !== "string" ||
|
|
62
|
+
!era.shortcode ||
|
|
63
|
+
typeof fm.shortcode !== "string" ||
|
|
64
|
+
!fm.shortcode
|
|
65
|
+
)
|
|
66
|
+
return;
|
|
67
|
+
const pkg = fm.package ?? note.package ?? index?.contentPackage;
|
|
68
|
+
const short = `${fm.shortcode}.${era.shortcode}`;
|
|
69
|
+
const key =
|
|
70
|
+
pkg ?
|
|
71
|
+
renderAddress({ package: pkg, system: "note", type: "lore", shortcode: short })
|
|
72
|
+
: short;
|
|
73
|
+
const row = {
|
|
74
|
+
key,
|
|
75
|
+
era: short,
|
|
76
|
+
calendar: fm.data,
|
|
77
|
+
marker: era.marker,
|
|
78
|
+
calendarShortcode: fm.shortcode,
|
|
79
|
+
name: era.name,
|
|
80
|
+
abbreviation: era.abbreviation,
|
|
81
|
+
label: era.label,
|
|
82
|
+
start: era.start,
|
|
83
|
+
end: era.end,
|
|
84
|
+
note,
|
|
85
|
+
position,
|
|
41
86
|
};
|
|
42
|
-
if (
|
|
43
|
-
findings.push({
|
|
44
|
-
...at,
|
|
45
|
-
severity: "error",
|
|
46
|
-
message:
|
|
47
|
-
"an era marker is uppercase letters and digits, starting with a letter",
|
|
48
|
-
});
|
|
87
|
+
if (declared.has(key)) {
|
|
49
88
|
return;
|
|
50
89
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
90
|
+
declared.set(key, row);
|
|
91
|
+
alias(short, key);
|
|
92
|
+
if (pkg) {
|
|
93
|
+
alias(
|
|
94
|
+
renderAddress({ package: pkg, system: "note", type: "lore", shortcode: short }),
|
|
95
|
+
key,
|
|
96
|
+
);
|
|
97
|
+
alias(`note-lore-${short}`, key);
|
|
98
|
+
alias(`lore-${short}`, key);
|
|
99
|
+
}
|
|
100
|
+
if (era.marker === undefined) return;
|
|
101
|
+
if (typeof era.marker !== "string" || !/^[A-Z][A-Z0-9]*$/.test(era.marker)) {
|
|
102
|
+
finding(
|
|
103
|
+
row,
|
|
104
|
+
"marker",
|
|
105
|
+
"an era marker is uppercase letters and digits, starting with a letter",
|
|
106
|
+
);
|
|
57
107
|
return;
|
|
58
108
|
}
|
|
59
|
-
|
|
60
|
-
marker,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
end: row.end,
|
|
65
|
-
note,
|
|
66
|
-
position,
|
|
67
|
-
});
|
|
109
|
+
if (markerKeys.has(era.marker)) {
|
|
110
|
+
finding(row, "marker", `reckoning marker ${era.marker} is declared more than once`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
markerKeys.set(era.marker, key);
|
|
68
114
|
});
|
|
69
115
|
}
|
|
70
116
|
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (failed.has(
|
|
76
|
-
const row = declared.get(
|
|
77
|
-
if (
|
|
78
|
-
if (visiting.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
failed.add(marker);
|
|
117
|
+
for (const [qualifier, key] of qualifiers)
|
|
118
|
+
if (key === null) eras.set(qualifier, { ambiguous: true });
|
|
119
|
+
|
|
120
|
+
function resolve(key) {
|
|
121
|
+
if (key === null || !declared.has(key) || failed.has(key)) return null;
|
|
122
|
+
const row = declared.get(key);
|
|
123
|
+
if (eras.has(key)) return eras.get(key);
|
|
124
|
+
if (visiting.includes(key)) {
|
|
125
|
+
const members = [...visiting.slice(visiting.indexOf(key)), key].map(
|
|
126
|
+
(member) => declared.get(member).era,
|
|
127
|
+
);
|
|
128
|
+
finding(row, "start", `calendar era cycle includes ${members.join(" → ")}`);
|
|
129
|
+
for (const member of visiting.slice(visiting.indexOf(key))) failed.add(member);
|
|
85
130
|
return null;
|
|
86
131
|
}
|
|
87
|
-
visiting.
|
|
132
|
+
visiting.push(key);
|
|
88
133
|
if (row.start === undefined || row.start === null) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
severity: "error",
|
|
92
|
-
message: `reckoning marker ${marker} needs an era start`,
|
|
93
|
-
});
|
|
94
|
-
failed.add(marker);
|
|
134
|
+
finding(row, "start", `calendar era ${row.era} needs a start`);
|
|
135
|
+
failed.add(key);
|
|
95
136
|
}
|
|
137
|
+
const source = typeof row.start === "string" ? row.start.trim() : "";
|
|
138
|
+
const marker = /^([A-Z][A-Z0-9]*)\(/.exec(source)?.[1];
|
|
139
|
+
const qualifier = /\s+([^\s]+\.[^\s]+)$/.exec(source)?.[1];
|
|
140
|
+
const conversion = DATEFROM_PATTERN.exec(source);
|
|
141
|
+
const convertedRef = conversion?.[1];
|
|
142
|
+
const convertedDate = conversion?.[2];
|
|
143
|
+
const convertedRows =
|
|
144
|
+
convertedRef ?
|
|
145
|
+
[...declared.values()].filter((candidate) => {
|
|
146
|
+
const fm = candidate.note.fm ?? candidate.note.frontmatter ?? candidate.note;
|
|
147
|
+
const pkg = fm.package ?? candidate.note.package ?? index?.contentPackage;
|
|
148
|
+
if (!pkg) return convertedRef === candidate.calendarShortcode;
|
|
149
|
+
return addressSuffixes({
|
|
150
|
+
package: pkg,
|
|
151
|
+
system: "note",
|
|
152
|
+
type: "lore",
|
|
153
|
+
shortcode: candidate.calendarShortcode,
|
|
154
|
+
}).includes(convertedRef);
|
|
155
|
+
})
|
|
156
|
+
: [];
|
|
157
|
+
const convertedTargets = convertedRows.filter((candidate) =>
|
|
158
|
+
eraNames(candidate).some((name) =>
|
|
159
|
+
convertedDate?.toLowerCase().endsWith(` ${name.toLowerCase()}`),
|
|
160
|
+
),
|
|
161
|
+
);
|
|
162
|
+
if (convertedTargets.length === 0 && convertedRows.length === 1)
|
|
163
|
+
convertedTargets.push(convertedRows[0]);
|
|
96
164
|
const dependency =
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
165
|
+
marker ? markerKeys.get(marker)
|
|
166
|
+
: qualifier ? qualifiers.get(qualifier)
|
|
167
|
+
: convertedTargets.length === 1 ? convertedTargets[0].key
|
|
168
|
+
: null;
|
|
169
|
+
if (dependency && !failed.has(key)) resolve(dependency);
|
|
170
|
+
if (!failed.has(key)) {
|
|
100
171
|
const parsed = parseNoteDate(row.start, {
|
|
101
172
|
markers,
|
|
173
|
+
eras,
|
|
102
174
|
daysPerYear,
|
|
103
175
|
field: "data.eras.start",
|
|
104
176
|
allowUnknown: false,
|
|
105
|
-
|
|
177
|
+
...location(row, "start"),
|
|
106
178
|
raw: row.note.raw,
|
|
107
179
|
keyPath: ["data", "eras", row.position, "start"],
|
|
108
180
|
});
|
|
109
181
|
findings.push(...parsed.findings);
|
|
110
182
|
if (
|
|
111
|
-
parsed.date?.
|
|
112
|
-
|
|
113
|
-
parsed.findings.every((f) => f.severity !== "error")
|
|
183
|
+
Number.isSafeInteger(parsed.date?.canonicalYear) &&
|
|
184
|
+
parsed.findings.every((item) => item.severity !== "error")
|
|
114
185
|
) {
|
|
115
186
|
const entry = {
|
|
116
187
|
era: row.era,
|
|
188
|
+
qualifier: row.key,
|
|
189
|
+
marker: row.marker,
|
|
190
|
+
firstEra: row.position === 0,
|
|
117
191
|
calendar: row.calendar,
|
|
192
|
+
calendarShortcode: row.calendarShortcode,
|
|
193
|
+
name: row.name,
|
|
194
|
+
abbreviation: row.abbreviation,
|
|
195
|
+
label: row.label,
|
|
118
196
|
epochYear: parsed.date.canonicalYear,
|
|
119
197
|
epochDay: parsed.date.canonicalDay ?? 1,
|
|
120
198
|
};
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
199
|
+
if (Number.isSafeInteger(daysPerYear) && daysPerYear > 0)
|
|
200
|
+
entry.startOffset = canonicalDayOffset(
|
|
201
|
+
entry.epochYear,
|
|
202
|
+
entry.epochDay,
|
|
203
|
+
1,
|
|
204
|
+
daysPerYear,
|
|
205
|
+
);
|
|
206
|
+
eras.set(key, entry);
|
|
207
|
+
for (const [name, owner] of qualifiers) if (owner === key) eras.set(name, entry);
|
|
208
|
+
if (row.marker && markerKeys.get(row.marker) === key)
|
|
209
|
+
markers.set(row.marker, entry);
|
|
210
|
+
} else failed.add(key);
|
|
131
211
|
}
|
|
132
|
-
visiting.
|
|
133
|
-
return
|
|
212
|
+
visiting.pop();
|
|
213
|
+
return eras.get(key) ?? null;
|
|
134
214
|
}
|
|
135
|
-
for (const
|
|
136
|
-
for (const
|
|
137
|
-
const entry =
|
|
215
|
+
for (const key of declared.keys()) resolve(key);
|
|
216
|
+
for (const row of declared.values()) {
|
|
217
|
+
const entry = eras.get(row.key);
|
|
138
218
|
if (!entry || row.end === undefined || row.end === null) continue;
|
|
139
219
|
const parsed = parseNoteDate(row.end, {
|
|
140
220
|
markers,
|
|
221
|
+
eras,
|
|
141
222
|
daysPerYear,
|
|
142
223
|
field: "data.eras.end",
|
|
143
224
|
allowUnknown: false,
|
|
@@ -146,13 +227,13 @@ export function resolveReckoningMarkers(index, daysPerYear) {
|
|
|
146
227
|
keyPath: ["data", "eras", row.position, "end"],
|
|
147
228
|
});
|
|
148
229
|
findings.push(...parsed.findings);
|
|
149
|
-
if (!Number.isSafeInteger(parsed.date?.canonicalYear))
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
230
|
+
if (!Number.isSafeInteger(parsed.date?.canonicalYear)) continue;
|
|
231
|
+
if (!Number.isSafeInteger(daysPerYear) || daysPerYear < 1) {
|
|
232
|
+
finding(
|
|
233
|
+
row,
|
|
234
|
+
"end",
|
|
235
|
+
`calendar era ${row.era} needs the world's year length to bound its end`,
|
|
236
|
+
);
|
|
156
237
|
continue;
|
|
157
238
|
}
|
|
158
239
|
const firstEndDay = canonicalDayOffset(
|
|
@@ -164,21 +245,52 @@ export function resolveReckoningMarkers(index, daysPerYear) {
|
|
|
164
245
|
const extraDays =
|
|
165
246
|
parsed.date.precision === "year" ? daysPerYear - 1
|
|
166
247
|
: parsed.date.precision === "month" ?
|
|
167
|
-
(daysInMonth(
|
|
248
|
+
(daysInMonth(
|
|
249
|
+
markers.get(parsed.date.marker)?.calendar ??
|
|
250
|
+
eras.get(parsed.date.qualifier)?.calendar,
|
|
251
|
+
parsed.date.month,
|
|
252
|
+
) ?? 1) - 1
|
|
168
253
|
: 0;
|
|
169
254
|
const endOffset = firstEndDay + extraDays;
|
|
170
255
|
const startOffset = canonicalDayOffset(entry.epochYear, entry.epochDay, 1, daysPerYear);
|
|
171
256
|
if (endOffset < startOffset) {
|
|
172
|
-
|
|
173
|
-
...(row.note.file === undefined ? {} : { file: row.note.file }),
|
|
174
|
-
severity: "error",
|
|
175
|
-
message: `reckoning marker ${marker} ends before its era starts`,
|
|
176
|
-
});
|
|
257
|
+
finding(row, "end", `calendar era ${row.era} ends before its start`);
|
|
177
258
|
continue;
|
|
178
259
|
}
|
|
179
260
|
entry.endOffset = endOffset;
|
|
180
261
|
}
|
|
181
|
-
|
|
262
|
+
const calendars = new Map();
|
|
263
|
+
for (const row of declared.values()) {
|
|
264
|
+
const rows = calendars.get(row.note) ?? [];
|
|
265
|
+
rows.push(row);
|
|
266
|
+
calendars.set(row.note, rows);
|
|
267
|
+
}
|
|
268
|
+
for (const rows of calendars.values()) {
|
|
269
|
+
for (let position = 0; position < rows.length; position++) {
|
|
270
|
+
const row = rows[position];
|
|
271
|
+
const entry = eras.get(row.key);
|
|
272
|
+
if (!entry || !Number.isSafeInteger(entry.startOffset)) continue;
|
|
273
|
+
entry.firstEra = position === 0;
|
|
274
|
+
const next = rows[position + 1];
|
|
275
|
+
if (!next) continue;
|
|
276
|
+
const following = eras.get(next.key);
|
|
277
|
+
if (!following || !Number.isSafeInteger(following.startOffset)) continue;
|
|
278
|
+
if (following.startOffset <= entry.startOffset) {
|
|
279
|
+
finding(
|
|
280
|
+
next,
|
|
281
|
+
"start",
|
|
282
|
+
`calendar eras ${row.era} and ${next.era} must begin in increasing order`,
|
|
283
|
+
);
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
if (entry.endOffset !== undefined && entry.endOffset >= following.startOffset) {
|
|
287
|
+
finding(next, "start", `calendar eras ${row.era} and ${next.era} overlap`);
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if (entry.endOffset === undefined) entry.endOffset = following.startOffset - 1;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return { eras, markers, findings };
|
|
182
294
|
}
|
|
183
295
|
|
|
184
296
|
/** One resolved date context per corpus index, shared by its note checks. */
|
|
@@ -188,11 +300,11 @@ const contexts = new WeakMap();
|
|
|
188
300
|
* Read the world's year length and its era markers from one corpus index.
|
|
189
301
|
*
|
|
190
302
|
* @param {{notes?: readonly object[]}|undefined} index - The corpus index.
|
|
191
|
-
* @returns {{markers: Map<string, object>, daysPerYear: number|undefined, findings: object[]}}
|
|
303
|
+
* @returns {{eras: Map<string, object>, markers: Map<string, object>, daysPerYear: number|undefined, findings: object[]}}
|
|
192
304
|
*/
|
|
193
305
|
export function reckoningContext(index) {
|
|
194
306
|
if (!index || typeof index !== "object")
|
|
195
|
-
return { markers: new Map(), daysPerYear: undefined, findings: [] };
|
|
307
|
+
return { eras: new Map(), markers: new Map(), daysPerYear: undefined, findings: [] };
|
|
196
308
|
const cached = contexts.get(index);
|
|
197
309
|
if (cached) return cached;
|
|
198
310
|
const world = (index.notes ?? []).find((note) => {
|
package/engine/scenes.mjs
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
* rather than stored. It is the same vocabulary the link manifest uses.
|
|
40
40
|
*
|
|
41
41
|
* Not a standalone script — exports the `Scenes` compiler class, imported and
|
|
42
|
-
* driven by `packages/
|
|
42
|
+
* driven by `packages/package-build/engine/generate.mjs` (via `npm run build:compiledb`).
|
|
43
43
|
*
|
|
44
44
|
* The walk itself — filtering by type, expanding tables, converting
|
|
45
45
|
* wikilinks, writing the JSON and counting errors — belongs to {@link sohl.utils.packs.BasePackCompiler}; this module
|
|
@@ -499,7 +499,10 @@ export class Scenes extends BasePackCompiler {
|
|
|
499
499
|
// default JournalEntry pack, not in whichever Scene pack the map
|
|
500
500
|
// itself was routed to.
|
|
501
501
|
// This compile's router, as everywhere else in this pass.
|
|
502
|
-
journalPack:
|
|
502
|
+
journalPack:
|
|
503
|
+
hasBody ?
|
|
504
|
+
this.#packRouter.resolve(fm, "JournalEntry")
|
|
505
|
+
: this.#packRouter.defaultOf("JournalEntry"),
|
|
503
506
|
pageIds: hasBody ? this.#pageIds(markdown, entryId, name) : new Map(),
|
|
504
507
|
knownActions: this.knownActions,
|
|
505
508
|
warnings,
|
package/engine/schema-check.mjs
CHANGED
|
@@ -468,7 +468,7 @@ export function compareEmittedSystem({
|
|
|
468
468
|
*
|
|
469
469
|
* - **A system**: its own `schema.json`, generated from its `src/` into
|
|
470
470
|
* `build/` by `package-build schema`.
|
|
471
|
-
* - **A module**: the copy cached by `
|
|
471
|
+
* - **A module**: the copy cached by `package-build deps fetch`, from the
|
|
472
472
|
* archive of the version it pins — which is what makes the comparison happen
|
|
473
473
|
* at `verified` rather than against whatever the system's `main` holds today.
|
|
474
474
|
* That distinction is the whole of the `affiliation.subType` case.
|
|
@@ -640,7 +640,7 @@ function schemaFor(config, system = undefined) {
|
|
|
640
640
|
* version released before the artifact existed, or a subtype the artifact does
|
|
641
641
|
* not name, produces no findings — the same stance `compareFields` takes, where
|
|
642
642
|
* an unknown subtype is a routing question rather than a field one. The
|
|
643
|
-
* whole-build check in `
|
|
643
|
+
* whole-build check in `package-build lint` is where a missing artifact is said
|
|
644
644
|
* out loud, once, instead of per note.
|
|
645
645
|
*
|
|
646
646
|
* @param {object} fm - The note's frontmatter.
|
|
@@ -690,7 +690,7 @@ export function checkAuthoredSystemData(
|
|
|
690
690
|
* **Silent where there is nothing to check against**, exactly as its two
|
|
691
691
|
* siblings are: a module pinning a system version released before the artifact
|
|
692
692
|
* existed, or a subtype the artifact does not name, produces no findings.
|
|
693
|
-
* `
|
|
693
|
+
* `package-build lint` is where a missing artifact is said out loud, once.
|
|
694
694
|
*
|
|
695
695
|
* @param {object} emitted - The `system` block the compiler produced.
|
|
696
696
|
* @param {object} opts
|