@heroiclands/package-build 22.16.0 → 22.17.1
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 +24 -0
- package/bin/content-commands.mjs +93 -14
- package/bin/package-build.mjs +14 -2
- package/config.mjs +54 -3
- package/docs/api.md +22 -22
- package/docs/authoring/dates-and-calendars.md +63 -11
- package/docs/authoring/links-and-markup.md +1 -1
- package/docs/commands.md +78 -40
- package/docs/configuration.md +49 -22
- package/docs/reference/format-details.md +107 -165
- package/docs/reference/note-types.md +11 -10
- package/engine/calendar-format.mjs +441 -0
- package/engine/calendar-human.mjs +65 -3
- package/engine/calendar-notes.mjs +277 -96
- package/engine/content-format.yaml +2 -1
- package/engine/date-conversion.mjs +4 -4
- package/engine/markdown-expressions.mjs +2 -1
- package/engine/note-dates.mjs +50 -22
- package/engine/readability-lint.mjs +196 -18
- package/engine/reckoning-markers.mjs +154 -256
- package/package.json +5 -1
- package/types/config.d.mts +14 -2
- package/types/engine/calendar-format.d.mts +33 -0
- package/types/engine/calendar-notes.d.mts +3 -3
- package/types/engine/date-conversion.d.mts +1 -1
- package/types/engine/note-dates.d.mts +2 -2
- package/types/engine/readability-lint.d.mts +11 -5
- package/types/engine/reckoning-markers.d.mts +13 -28
|
@@ -1,70 +1,64 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
|
|
3
|
-
* Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
|
|
4
|
-
*
|
|
5
|
-
* This work is licensed under the GNU General Public License v3.0 (GPLv3).
|
|
6
|
-
* You may copy, modify, and distribute it under the terms of that license.
|
|
7
|
-
*
|
|
8
|
-
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
9
|
-
*/
|
|
1
|
+
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
|
10
2
|
|
|
11
3
|
import { positionOfFrontmatterPath } from "./diagnostics.mjs";
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
4
|
+
import {
|
|
5
|
+
canonicalDateFromOffset,
|
|
6
|
+
canonicalDayOffset,
|
|
7
|
+
canonicalYear,
|
|
8
|
+
parseCanonicalDate,
|
|
9
|
+
} from "./calendars.mjs";
|
|
10
|
+
import { renderAddress } from "./address-render.mjs";
|
|
16
11
|
|
|
17
|
-
/**
|
|
18
|
-
* Resolve every declared reckoning marker against its calendar and era start.
|
|
19
|
-
* The marker names one era, while the calendar supplies the month layout.
|
|
20
|
-
*
|
|
21
|
-
* @param {{notes?: readonly object[]}} index - The corpus link index.
|
|
22
|
-
* @param {number} daysPerYear - The world's year length.
|
|
23
|
-
* @returns {{eras: Map<string, object>, markers: Map<string, object>, findings: object[]}} Resolved eras, markers, and errors.
|
|
24
|
-
*/
|
|
12
|
+
/** Resolve calendar eras on the canonical day axis. */
|
|
25
13
|
export function resolveReckoningMarkers(index, daysPerYear) {
|
|
26
|
-
const declared = new Map();
|
|
27
|
-
const qualifiers = new Map();
|
|
28
|
-
const markerKeys = new Map();
|
|
29
14
|
const eras = new Map();
|
|
30
15
|
const markers = new Map();
|
|
31
16
|
const findings = [];
|
|
32
|
-
const
|
|
33
|
-
const
|
|
17
|
+
const qualifiers = new Map();
|
|
18
|
+
const markerOwners = new Map();
|
|
19
|
+
const calendarRows = [];
|
|
34
20
|
|
|
35
|
-
const location = (
|
|
36
|
-
...(
|
|
37
|
-
...(
|
|
21
|
+
const location = (note, position, key) => ({
|
|
22
|
+
...(note.file === undefined ? {} : { file: note.file }),
|
|
23
|
+
...(note.raw === undefined ?
|
|
38
24
|
{}
|
|
39
|
-
: positionOfFrontmatterPath(
|
|
25
|
+
: positionOfFrontmatterPath(note.raw, ["data", "eras", position, key])),
|
|
40
26
|
});
|
|
41
|
-
const
|
|
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
|
+
const error = (note, position, key, message) =>
|
|
28
|
+
findings.push({ ...location(note, position, key), severity: "error", message });
|
|
52
29
|
|
|
53
30
|
for (const note of index?.notes ?? []) {
|
|
54
31
|
const fm = note?.fm ?? note?.frontmatter ?? note;
|
|
55
32
|
if (fm?.type !== "lore" || fm?.subType !== "calendar") continue;
|
|
56
|
-
const
|
|
33
|
+
const data = fm.data ?? {};
|
|
34
|
+
const rows = data.eras;
|
|
57
35
|
if (!Array.isArray(rows)) continue;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
36
|
+
if (!Number.isSafeInteger(daysPerYear) || daysPerYear < 1) continue;
|
|
37
|
+
const epochText = String(data.epoch ?? "");
|
|
38
|
+
const epoch = parseCanonicalDate(epochText, daysPerYear);
|
|
39
|
+
if (!epoch || epoch.year === 0 || epoch.seconds !== undefined) {
|
|
40
|
+
findings.push({
|
|
41
|
+
...(note.file === undefined ? {} : { file: note.file }),
|
|
42
|
+
...(note.raw === undefined ?
|
|
43
|
+
{}
|
|
44
|
+
: positionOfFrontmatterPath(note.raw, ["data", "epoch"])),
|
|
45
|
+
severity: "error",
|
|
46
|
+
message:
|
|
47
|
+
"data.epoch must be a canonical <year>.<day> anchoring calendar year 1, day 1",
|
|
48
|
+
});
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const epochOffset = canonicalDayOffset(
|
|
52
|
+
canonicalYear(epoch.year),
|
|
53
|
+
epoch.day,
|
|
54
|
+
1,
|
|
55
|
+
daysPerYear,
|
|
56
|
+
);
|
|
57
|
+
const pkg = fm.package ?? note.package ?? index?.contentPackage;
|
|
58
|
+
const declared = [];
|
|
59
|
+
for (const [position, era] of rows.entries()) {
|
|
60
|
+
if (!era || typeof era.shortcode !== "string" || !era.shortcode || !fm.shortcode)
|
|
61
|
+
continue;
|
|
68
62
|
const short = `${fm.shortcode}.${era.shortcode}`;
|
|
69
63
|
const key =
|
|
70
64
|
pkg ?
|
|
@@ -73,235 +67,139 @@ export function resolveReckoningMarkers(index, daysPerYear) {
|
|
|
73
67
|
const row = {
|
|
74
68
|
key,
|
|
75
69
|
era: short,
|
|
76
|
-
calendar:
|
|
77
|
-
marker: era.marker,
|
|
70
|
+
calendar: data,
|
|
78
71
|
calendarShortcode: fm.shortcode,
|
|
72
|
+
note,
|
|
73
|
+
position,
|
|
74
|
+
start: era.start,
|
|
79
75
|
name: era.name,
|
|
80
76
|
abbreviation: era.abbreviation,
|
|
77
|
+
marker: era.marker,
|
|
81
78
|
label: era.label,
|
|
82
|
-
start: era.start,
|
|
83
|
-
end: era.end,
|
|
84
|
-
note,
|
|
85
|
-
position,
|
|
86
79
|
};
|
|
87
|
-
|
|
88
|
-
|
|
80
|
+
declared.push(row);
|
|
81
|
+
const aliases = [short, `lore-${short}`, `note-lore-${short}`];
|
|
82
|
+
for (const alias of aliases) {
|
|
83
|
+
if (qualifiers.has(alias) && qualifiers.get(alias) !== key)
|
|
84
|
+
qualifiers.set(alias, null);
|
|
85
|
+
else qualifiers.set(alias, key);
|
|
89
86
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
87
|
+
if (era.marker !== undefined) {
|
|
88
|
+
if (typeof era.marker !== "string" || !/^[A-Z][A-Z0-9]*$/.test(era.marker))
|
|
89
|
+
error(
|
|
90
|
+
note,
|
|
91
|
+
position,
|
|
92
|
+
"marker",
|
|
93
|
+
"an era marker needs uppercase letters and digits",
|
|
94
|
+
);
|
|
95
|
+
else if (markerOwners.has(era.marker))
|
|
96
|
+
error(
|
|
97
|
+
note,
|
|
98
|
+
position,
|
|
99
|
+
"marker",
|
|
100
|
+
`reckoning marker ${era.marker} is declared more than once`,
|
|
101
|
+
);
|
|
102
|
+
else markerOwners.set(era.marker, key);
|
|
99
103
|
}
|
|
100
|
-
if (era.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
"
|
|
105
|
-
"an era
|
|
104
|
+
if (era.end !== undefined)
|
|
105
|
+
error(
|
|
106
|
+
note,
|
|
107
|
+
position,
|
|
108
|
+
"end",
|
|
109
|
+
"an era ends when the next calendar year era begins; omit end",
|
|
106
110
|
);
|
|
107
|
-
|
|
108
|
-
|
|
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);
|
|
114
|
-
});
|
|
111
|
+
}
|
|
112
|
+
calendarRows.push({ note, declared, epochOffset });
|
|
115
113
|
}
|
|
116
114
|
|
|
117
|
-
for (const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
(member) => declared.get(member).era,
|
|
115
|
+
for (const { note, declared, epochOffset } of calendarRows) {
|
|
116
|
+
const nullRows = declared.filter((row) => row.start === null);
|
|
117
|
+
const firstRows = declared.filter((row) => row.start === 1);
|
|
118
|
+
if (nullRows.length !== 1)
|
|
119
|
+
error(
|
|
120
|
+
note,
|
|
121
|
+
nullRows[0]?.position ?? 0,
|
|
122
|
+
"start",
|
|
123
|
+
"a calendar needs exactly one start: null era before year 1",
|
|
127
124
|
);
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
finding(row, "start", `calendar era ${row.era} needs a start`);
|
|
135
|
-
failed.add(key);
|
|
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]);
|
|
164
|
-
const dependency =
|
|
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)) {
|
|
171
|
-
const parsed = parseNoteDate(row.start, {
|
|
172
|
-
markers,
|
|
173
|
-
eras,
|
|
174
|
-
daysPerYear,
|
|
175
|
-
field: "data.eras.start",
|
|
176
|
-
allowUnknown: false,
|
|
177
|
-
...location(row, "start"),
|
|
178
|
-
raw: row.note.raw,
|
|
179
|
-
keyPath: ["data", "eras", row.position, "start"],
|
|
180
|
-
});
|
|
181
|
-
findings.push(...parsed.findings);
|
|
182
|
-
if (
|
|
183
|
-
Number.isSafeInteger(parsed.date?.canonicalYear) &&
|
|
184
|
-
parsed.findings.every((item) => item.severity !== "error")
|
|
185
|
-
) {
|
|
186
|
-
const entry = {
|
|
187
|
-
era: row.era,
|
|
188
|
-
qualifier: row.key,
|
|
189
|
-
marker: row.marker,
|
|
190
|
-
firstEra: row.position === 0,
|
|
191
|
-
calendar: row.calendar,
|
|
192
|
-
calendarShortcode: row.calendarShortcode,
|
|
193
|
-
name: row.name,
|
|
194
|
-
abbreviation: row.abbreviation,
|
|
195
|
-
label: row.label,
|
|
196
|
-
epochYear: parsed.date.canonicalYear,
|
|
197
|
-
epochDay: parsed.date.canonicalDay ?? 1,
|
|
198
|
-
};
|
|
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);
|
|
211
|
-
}
|
|
212
|
-
visiting.pop();
|
|
213
|
-
return eras.get(key) ?? null;
|
|
214
|
-
}
|
|
215
|
-
for (const key of declared.keys()) resolve(key);
|
|
216
|
-
for (const row of declared.values()) {
|
|
217
|
-
const entry = eras.get(row.key);
|
|
218
|
-
if (!entry || row.end === undefined || row.end === null) continue;
|
|
219
|
-
const parsed = parseNoteDate(row.end, {
|
|
220
|
-
markers,
|
|
221
|
-
eras,
|
|
222
|
-
daysPerYear,
|
|
223
|
-
field: "data.eras.end",
|
|
224
|
-
allowUnknown: false,
|
|
225
|
-
file: row.note.file,
|
|
226
|
-
raw: row.note.raw,
|
|
227
|
-
keyPath: ["data", "eras", row.position, "end"],
|
|
228
|
-
});
|
|
229
|
-
findings.push(...parsed.findings);
|
|
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`,
|
|
125
|
+
if (firstRows.length !== 1)
|
|
126
|
+
error(
|
|
127
|
+
note,
|
|
128
|
+
firstRows[0]?.position ?? 0,
|
|
129
|
+
"start",
|
|
130
|
+
"a calendar needs exactly one era with start: 1",
|
|
236
131
|
);
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
daysPerYear,
|
|
244
|
-
);
|
|
245
|
-
const extraDays =
|
|
246
|
-
parsed.date.precision === "year" ? daysPerYear - 1
|
|
247
|
-
: parsed.date.precision === "month" ?
|
|
248
|
-
(daysInMonth(
|
|
249
|
-
markers.get(parsed.date.marker)?.calendar ??
|
|
250
|
-
eras.get(parsed.date.qualifier)?.calendar,
|
|
251
|
-
parsed.date.month,
|
|
252
|
-
) ?? 1) - 1
|
|
253
|
-
: 0;
|
|
254
|
-
const endOffset = firstEndDay + extraDays;
|
|
255
|
-
const startOffset = canonicalDayOffset(entry.epochYear, entry.epochDay, 1, daysPerYear);
|
|
256
|
-
if (endOffset < startOffset) {
|
|
257
|
-
finding(row, "end", `calendar era ${row.era} ends before its start`);
|
|
258
|
-
continue;
|
|
259
|
-
}
|
|
260
|
-
entry.endOffset = endOffset;
|
|
261
|
-
}
|
|
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,
|
|
132
|
+
const byStart = new Map();
|
|
133
|
+
for (const row of declared) {
|
|
134
|
+
if (row.start !== null && (!Number.isSafeInteger(row.start) || row.start < 1)) {
|
|
135
|
+
error(
|
|
136
|
+
note,
|
|
137
|
+
row.position,
|
|
281
138
|
"start",
|
|
282
|
-
|
|
139
|
+
"an era start is null or a positive integer calendar year",
|
|
283
140
|
);
|
|
284
141
|
continue;
|
|
285
142
|
}
|
|
286
|
-
if (
|
|
287
|
-
|
|
143
|
+
if (byStart.has(row.start)) {
|
|
144
|
+
error(note, row.position, "start", `calendar eras cannot share start ${row.start}`);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
byStart.set(row.start, row);
|
|
148
|
+
}
|
|
149
|
+
const ordered = [...byStart.values()].sort((a, b) =>
|
|
150
|
+
a.start === null ? -1
|
|
151
|
+
: b.start === null ? 1
|
|
152
|
+
: a.start - b.start,
|
|
153
|
+
);
|
|
154
|
+
for (const [position, row] of ordered.entries()) {
|
|
155
|
+
const startOffset =
|
|
156
|
+
row.start === null ? epochOffset : epochOffset + (row.start - 1) * daysPerYear;
|
|
157
|
+
if (!Number.isSafeInteger(startOffset)) {
|
|
158
|
+
error(
|
|
159
|
+
note,
|
|
160
|
+
row.position,
|
|
161
|
+
"start",
|
|
162
|
+
"era start lies outside the supported day range",
|
|
163
|
+
);
|
|
288
164
|
continue;
|
|
289
165
|
}
|
|
290
|
-
|
|
166
|
+
const startDate = canonicalDateFromOffset(startOffset, 1, daysPerYear);
|
|
167
|
+
const next = ordered[position + 1];
|
|
168
|
+
const endOffset = next ? epochOffset + (next.start - 1) * daysPerYear - 1 : undefined;
|
|
169
|
+
const entry = {
|
|
170
|
+
era: row.era,
|
|
171
|
+
qualifier: row.key,
|
|
172
|
+
marker: row.marker,
|
|
173
|
+
firstEra: row.start === null,
|
|
174
|
+
beforeEra: row.start === null,
|
|
175
|
+
startYear: row.start,
|
|
176
|
+
calendar: row.calendar,
|
|
177
|
+
calendarShortcode: row.calendarShortcode,
|
|
178
|
+
name: row.name,
|
|
179
|
+
abbreviation: row.abbreviation,
|
|
180
|
+
label: row.label,
|
|
181
|
+
epochYear: startDate.year,
|
|
182
|
+
epochDay: startDate.day,
|
|
183
|
+
calendarEpochOffset: epochOffset,
|
|
184
|
+
startOffset,
|
|
185
|
+
...(endOffset === undefined ? {} : { endOffset }),
|
|
186
|
+
};
|
|
187
|
+
eras.set(row.key, entry);
|
|
188
|
+
if (row.marker && markerOwners.get(row.marker) === row.key)
|
|
189
|
+
markers.set(row.marker, entry);
|
|
291
190
|
}
|
|
292
191
|
}
|
|
192
|
+
for (const [alias, key] of qualifiers) {
|
|
193
|
+
if (key === null) eras.set(alias, { ambiguous: true });
|
|
194
|
+
else if (eras.has(key)) eras.set(alias, eras.get(key));
|
|
195
|
+
}
|
|
293
196
|
return { eras, markers, findings };
|
|
294
197
|
}
|
|
295
198
|
|
|
296
|
-
/** One resolved date context per corpus index
|
|
199
|
+
/** One resolved date context per corpus index. */
|
|
297
200
|
const contexts = new WeakMap();
|
|
298
201
|
|
|
299
|
-
/**
|
|
300
|
-
* Read the world's year length and its era markers from one corpus index.
|
|
301
|
-
*
|
|
302
|
-
* @param {{notes?: readonly object[]}|undefined} index - The corpus index.
|
|
303
|
-
* @returns {{eras: Map<string, object>, markers: Map<string, object>, daysPerYear: number|undefined, findings: object[]}}
|
|
304
|
-
*/
|
|
202
|
+
/** Read the world's year length and resolve every calendar era in one corpus. */
|
|
305
203
|
export function reckoningContext(index) {
|
|
306
204
|
if (!index || typeof index !== "object")
|
|
307
205
|
return { eras: new Map(), markers: new Map(), daysPerYear: undefined, findings: [] };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "22.
|
|
3
|
+
"version": "22.17.1",
|
|
4
4
|
"description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package — content compilation, manifest, localization, staging, bundle, release and deployment.",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -137,10 +137,12 @@
|
|
|
137
137
|
"acorn": "^8.18.0",
|
|
138
138
|
"archiver": "^8.0.0",
|
|
139
139
|
"classic-level": "^3.0.0",
|
|
140
|
+
"dale-chall": "^2.0.1",
|
|
140
141
|
"dotenv": "^18.0.0",
|
|
141
142
|
"eslint": "^10.10.0",
|
|
142
143
|
"eslint-plugin-yml": "^3.8.1",
|
|
143
144
|
"fflate": "^0.8.3",
|
|
145
|
+
"flesch": "^2.0.1",
|
|
144
146
|
"glob": "^13.0.6",
|
|
145
147
|
"gray-matter": "^4.0.3",
|
|
146
148
|
"handlebars": "^4.7.9",
|
|
@@ -153,6 +155,7 @@
|
|
|
153
155
|
"pagefind": "^1.5.2",
|
|
154
156
|
"prettier": "^3.9.6",
|
|
155
157
|
"remark-frontmatter": "^5.0.0",
|
|
158
|
+
"remark-gfm": "^4.0.1",
|
|
156
159
|
"remark-parse": "^11.0.0",
|
|
157
160
|
"remark-retext": "^6.0.1",
|
|
158
161
|
"retext-english": "^5.0.0",
|
|
@@ -162,6 +165,7 @@
|
|
|
162
165
|
"sharp": "^0.35.4",
|
|
163
166
|
"smol-toml": "^1.8.0",
|
|
164
167
|
"ssh2-sftp-client": "^12.1.1",
|
|
168
|
+
"syllable": "^5.0.1",
|
|
165
169
|
"typescript": "^6.0.3",
|
|
166
170
|
"unidecode": "^1.1.0",
|
|
167
171
|
"unified": "^11.0.5",
|
package/types/config.d.mts
CHANGED
|
@@ -94,7 +94,8 @@ export const DERIVED_MANIFEST_KEYS: Readonly<Record<string, string>>;
|
|
|
94
94
|
*
|
|
95
95
|
* @typedef {object} PackageBuildConfig
|
|
96
96
|
* @property {string} rootDir The repository root, from package-build.
|
|
97
|
-
* @property {{age: number, threshold: number, minWords: number}} proseLint Optional prose
|
|
97
|
+
* @property {{age: number, threshold: number, minWords: number, rules: string}} proseLint Optional prose lint settings.
|
|
98
|
+
* @property {{minWords: number, bands: Record<string, {min?: number, max?: number}>}} proseScore Optional note score settings.
|
|
98
99
|
* @property {string} packageKind `systems` or `modules`.
|
|
99
100
|
* @property {string} packageId The Foundry package id.
|
|
100
101
|
* @property {string} artifact Derived: `system` or `module`.
|
|
@@ -198,12 +199,23 @@ export type PackageBuildConfig = {
|
|
|
198
199
|
*/
|
|
199
200
|
rootDir: string;
|
|
200
201
|
/**
|
|
201
|
-
* Optional prose
|
|
202
|
+
* Optional prose lint settings.
|
|
202
203
|
*/
|
|
203
204
|
proseLint: {
|
|
204
205
|
age: number;
|
|
205
206
|
threshold: number;
|
|
206
207
|
minWords: number;
|
|
208
|
+
rules: string;
|
|
209
|
+
};
|
|
210
|
+
/**
|
|
211
|
+
* Optional note score settings.
|
|
212
|
+
*/
|
|
213
|
+
proseScore: {
|
|
214
|
+
minWords: number;
|
|
215
|
+
bands: Record<string, {
|
|
216
|
+
min?: number;
|
|
217
|
+
max?: number;
|
|
218
|
+
}>;
|
|
207
219
|
};
|
|
208
220
|
/**
|
|
209
221
|
* `systems` or `modules`.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Brackets and braces must close; `[[]` is a literal left bracket. */
|
|
2
|
+
export function calendarFormatSyntaxError(pattern: any): string | null;
|
|
3
|
+
/** Split a Calendaria pattern into tokens and literal text. */
|
|
4
|
+
export function calendarFormatSegments(pattern: any): {
|
|
5
|
+
token: boolean;
|
|
6
|
+
value: any;
|
|
7
|
+
}[];
|
|
8
|
+
/** A standard format must identify one calendar day and be readable without context. */
|
|
9
|
+
export function readableCalendarFormatError(pattern: any): string | null;
|
|
10
|
+
/** Whether a selected pattern prints clock components. */
|
|
11
|
+
export function calendarFormatHasClock(pattern: any): boolean;
|
|
12
|
+
/** Keep the fields named by an authored date's precision. */
|
|
13
|
+
export function calendarPatternSegmentsForPrecision(pattern: any, precision?: string, includeClock?: boolean): {
|
|
14
|
+
token: boolean;
|
|
15
|
+
value: any;
|
|
16
|
+
}[];
|
|
17
|
+
/** Choose an authored named format, with `std` as the preferred default. */
|
|
18
|
+
export function selectCalendarFormat(calendar: any, name: any): {
|
|
19
|
+
name: any;
|
|
20
|
+
pattern: any;
|
|
21
|
+
};
|
|
22
|
+
/** Format calendar date components with Calendaria's documented tokens. */
|
|
23
|
+
export function formatCalendarPattern(pattern: any, parts: any, precision?: string, includeClock?: boolean): string;
|
|
24
|
+
/** Read the invertible date fields of one Calendaria pattern. */
|
|
25
|
+
export function parseCalendarPattern(pattern: any, text: any, calendar: any): {
|
|
26
|
+
hour: number;
|
|
27
|
+
};
|
|
28
|
+
/** Convert a calendar month/day into its one-based day of year. */
|
|
29
|
+
export function calendarOrdinal(calendar: any, month: any, day: any): number;
|
|
30
|
+
/** Calendaria's bare date, time, week, season, and zone tokens. */
|
|
31
|
+
export const CALENDAR_FORMAT_TOKENS: Readonly<Set<string>>;
|
|
32
|
+
/** Calendaria's named fields inside brackets or braces. */
|
|
33
|
+
export const CALENDAR_CUSTOM_TOKENS: Readonly<Set<string>>;
|
|
@@ -18,7 +18,7 @@ export function worldInvariants(index?: object): {
|
|
|
18
18
|
yearNote: object | null;
|
|
19
19
|
moonNote: object | null;
|
|
20
20
|
};
|
|
21
|
-
/** Check each
|
|
21
|
+
/** Check each named pattern and the standard pattern's readable fields. */
|
|
22
22
|
export function checkCalendarDateFormats(note: any): {
|
|
23
23
|
severity: any;
|
|
24
24
|
message: any;
|
|
@@ -162,8 +162,7 @@ export const CALENDAR_SUBTYPE: "calendar";
|
|
|
162
162
|
export const INVARIANT_SUBTYPES: readonly string[];
|
|
163
163
|
/** Calendaria's display locations that a calendar note can format. */
|
|
164
164
|
export const CALENDAR_DATE_FORMAT_KEYS: readonly string[];
|
|
165
|
-
|
|
166
|
-
export const CALENDAR_FORMAT_TOKENS: Readonly<Set<string>>;
|
|
165
|
+
export { CALENDAR_FORMAT_TOKENS };
|
|
167
166
|
/**
|
|
168
167
|
* The `data:` keys a calendar note may write — the closed list, in authored
|
|
169
168
|
* order.
|
|
@@ -185,3 +184,4 @@ export const CALENDAR_FIELDS: readonly object[];
|
|
|
185
184
|
* @type {readonly object[]}
|
|
186
185
|
*/
|
|
187
186
|
export const INVARIANT_FIELDS: readonly object[];
|
|
187
|
+
import { CALENDAR_FORMAT_TOKENS } from "./calendar-format.mjs";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** Convert a day in an addressed calendar to its canonical spelling. */
|
|
2
2
|
export function dateFromCalendar(reference: any, value: any, context: any): string;
|
|
3
3
|
/** Convert a canonical day to the date in the covering era of a calendar. */
|
|
4
|
-
export function dateToCalendar(reference: any, value: any, context: any): any;
|
|
4
|
+
export function dateToCalendar(reference: any, value: any, context: any, formatName: any): any;
|
|
@@ -60,7 +60,7 @@ export function eraCovering(date: any, eraRows: any, daysPerYear: any): any;
|
|
|
60
60
|
/** Resolve a calendar or one of its eras from an Address or shortcode. */
|
|
61
61
|
export function calendarEras(reference: any, context: any): any;
|
|
62
62
|
/** Print a resolved date using the era active in one addressed calendar. */
|
|
63
|
-
export function formatDateInCalendar(date: any, reference: any, context: any): {
|
|
63
|
+
export function formatDateInCalendar(date: any, reference: any, context: any, formatName: any): {
|
|
64
64
|
era: null;
|
|
65
65
|
year: number;
|
|
66
66
|
text: any;
|
|
@@ -76,7 +76,7 @@ export function formatDateInCalendar(date: any, reference: any, context: any): {
|
|
|
76
76
|
prose: string | null;
|
|
77
77
|
} | null;
|
|
78
78
|
/** Print a resolved date in one era, retaining its authored precision. */
|
|
79
|
-
export function formatNoteDate(date: any, era: any, daysPerYear: any): {
|
|
79
|
+
export function formatNoteDate(date: any, era: any, daysPerYear: any, formatName: any): {
|
|
80
80
|
era: null;
|
|
81
81
|
year: number;
|
|
82
82
|
text: any;
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
/** Files in a content tree, excluding configured directory names. */
|
|
2
2
|
export function proseFiles(root: any, skipDirectories?: any[]): any[];
|
|
3
3
|
/** Source-aligned findings for one Markdown note. */
|
|
4
|
-
export function analyzeProse(file: any, source: any, {
|
|
5
|
-
age?: number | undefined;
|
|
6
|
-
threshold?: number | undefined;
|
|
7
|
-
minWords?: number | undefined;
|
|
8
|
-
}): Promise<{
|
|
4
|
+
export function analyzeProse(file: any, source: any, options?: {}): Promise<{
|
|
9
5
|
file: any;
|
|
10
6
|
line: any;
|
|
11
7
|
column: any;
|
|
@@ -20,3 +16,13 @@ export function lintProse(root: any, options: any, skipDirectories?: any[]): Pro
|
|
|
20
16
|
severity: string;
|
|
21
17
|
message: string;
|
|
22
18
|
}[]>;
|
|
19
|
+
/** Score the complete prose paragraphs in one note. */
|
|
20
|
+
export function scoreProse(file: any, source: any, { minWords, bands }?: {
|
|
21
|
+
minWords?: number | undefined;
|
|
22
|
+
bands?: {} | undefined;
|
|
23
|
+
}): Promise<any>;
|
|
24
|
+
/** Score a note or content tree and pool counts for its aggregate. */
|
|
25
|
+
export function scoreProseTree(root: any, options: any, skipDirectories?: any[]): Promise<{
|
|
26
|
+
notes: any[];
|
|
27
|
+
summary: any;
|
|
28
|
+
}>;
|