@heroiclands/package-build 22.15.1 → 22.17.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 +110 -0
- package/README.md +39 -678
- package/bin/{content-build.mjs → content-commands.mjs} +197 -118
- package/bin/package-build.mjs +315 -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 +296 -236
- package/docs/authoring/assets.md +31 -0
- package/docs/authoring/dates-and-calendars.md +85 -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 +633 -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 +4362 -0
- package/docs/reference/note-types.md +532 -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-format.mjs +441 -0
- package/engine/calendar-human.mjs +133 -0
- package/engine/calendar-notes.mjs +307 -88
- 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 +746 -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/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/markdown-expressions.mjs +118 -0
- package/engine/metadata-index.mjs +7 -3
- package/engine/note-addresses.mjs +1 -0
- package/engine/note-dates.mjs +314 -64
- 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 +176 -166
- 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-format.d.mts +33 -0
- package/types/engine/calendar-human.d.mts +4 -0
- package/types/engine/calendar-notes.d.mts +5 -3
- 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 +13 -26
- 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
|
@@ -64,7 +64,20 @@
|
|
|
64
64
|
* @module
|
|
65
65
|
*/
|
|
66
66
|
|
|
67
|
-
import {
|
|
67
|
+
import {
|
|
68
|
+
canonicalDayOffset,
|
|
69
|
+
canonicalYear,
|
|
70
|
+
dayOfYear,
|
|
71
|
+
daysInYear,
|
|
72
|
+
monthDayOfYear,
|
|
73
|
+
parseCanonicalDate,
|
|
74
|
+
} from "./calendars.mjs";
|
|
75
|
+
import {
|
|
76
|
+
CALENDAR_FORMAT_TOKENS,
|
|
77
|
+
calendarFormatSyntaxError,
|
|
78
|
+
readableCalendarFormatError,
|
|
79
|
+
selectCalendarFormat,
|
|
80
|
+
} from "./calendar-format.mjs";
|
|
68
81
|
import { positionInFrontmatter, positionOfFrontmatterPath } from "./diagnostics.mjs";
|
|
69
82
|
import { parseNoteDate } from "./note-dates.mjs";
|
|
70
83
|
import { reckoningContext } from "./reckoning-markers.mjs";
|
|
@@ -89,6 +102,7 @@ export const CALENDAR_DATE_FORMAT_KEYS = Object.freeze([
|
|
|
89
102
|
"long",
|
|
90
103
|
"full",
|
|
91
104
|
"time",
|
|
105
|
+
"time12",
|
|
92
106
|
"weekHeader",
|
|
93
107
|
"yearHeader",
|
|
94
108
|
"yearLabel",
|
|
@@ -96,13 +110,7 @@ export const CALENDAR_DATE_FORMAT_KEYS = Object.freeze([
|
|
|
96
110
|
]);
|
|
97
111
|
|
|
98
112
|
/** Bare date tokens Calendaria substitutes inside authored format strings. */
|
|
99
|
-
export
|
|
100
|
-
new Set(
|
|
101
|
-
"YYYY YY Y MMMM MMM MM Mo M EEEEE EEEE EEE EE E dddd ddd dd Do DDD DD D d e GGGG GGG GG G QQQQ QQQ QQ Q zzzz z ww w W HH H hh h mm m ss s A a".split(
|
|
102
|
-
" ",
|
|
103
|
-
),
|
|
104
|
-
),
|
|
105
|
-
);
|
|
113
|
+
export { CALENDAR_FORMAT_TOKENS };
|
|
106
114
|
|
|
107
115
|
/**
|
|
108
116
|
* The `data:` keys a calendar note may write — the closed list, in authored
|
|
@@ -117,9 +125,9 @@ export const CALENDAR_FORMAT_TOKENS = Object.freeze(
|
|
|
117
125
|
export const CALENDAR_FIELDS = Object.freeze([
|
|
118
126
|
{
|
|
119
127
|
name: "epoch",
|
|
120
|
-
shape: "a
|
|
128
|
+
shape: "a canonical `<year>.<day>`",
|
|
121
129
|
kind: "string",
|
|
122
|
-
describe: "
|
|
130
|
+
describe: "Canonical day when calendar year 1, day 1 begins — `1.1`.",
|
|
123
131
|
},
|
|
124
132
|
{
|
|
125
133
|
name: "months",
|
|
@@ -139,22 +147,28 @@ export const CALENDAR_FIELDS = Object.freeze([
|
|
|
139
147
|
},
|
|
140
148
|
{
|
|
141
149
|
name: "seasons",
|
|
142
|
-
shape: "list of `{ name, abbreviation?,
|
|
150
|
+
shape: "list of `{ name, abbreviation?, start }`",
|
|
151
|
+
kind: "list",
|
|
152
|
+
describe: "The seasons this calendar marks, starting on numbered days of the year.",
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: "namedDays",
|
|
156
|
+
shape: "list of `{ name, abbreviation?, day }`",
|
|
143
157
|
kind: "list",
|
|
144
|
-
describe: "
|
|
158
|
+
describe: "Names assigned to particular days of the year.",
|
|
145
159
|
},
|
|
146
160
|
{
|
|
147
161
|
name: "eras",
|
|
148
|
-
shape: "list of `{ shortcode, name, marker?, abbreviation?, proclaimedBy?, start,
|
|
162
|
+
shape: "list of `{ shortcode, name, marker?, abbreviation?, proclaimedBy?, start, label? }`",
|
|
149
163
|
kind: "list",
|
|
150
164
|
describe:
|
|
151
165
|
"The year-counts kept in this calendar. A marker names one era and uses these months.",
|
|
152
166
|
},
|
|
153
167
|
{
|
|
154
|
-
name: "
|
|
155
|
-
shape: "map of
|
|
168
|
+
name: "formats",
|
|
169
|
+
shape: "map of named Calendaria format strings",
|
|
156
170
|
kind: "map",
|
|
157
|
-
describe: "
|
|
171
|
+
describe: "Named patterns for reading and writing this calendar's dates.",
|
|
158
172
|
},
|
|
159
173
|
]);
|
|
160
174
|
|
|
@@ -248,7 +262,7 @@ export const INVARIANT_FIELDS = Object.freeze([
|
|
|
248
262
|
},
|
|
249
263
|
{
|
|
250
264
|
name: "moon.newOn",
|
|
251
|
-
shape: "a date",
|
|
265
|
+
shape: "a day-precision date",
|
|
252
266
|
kind: "string",
|
|
253
267
|
describe: "A day the body was new, written in the reference calendar.",
|
|
254
268
|
},
|
|
@@ -363,46 +377,132 @@ function atData(note, keyPath, severity, message) {
|
|
|
363
377
|
};
|
|
364
378
|
}
|
|
365
379
|
|
|
366
|
-
/** Check each
|
|
380
|
+
/** Check each named pattern and the standard pattern's readable fields. */
|
|
367
381
|
export function checkCalendarDateFormats(note) {
|
|
368
|
-
const formats = dataOf(note).
|
|
369
|
-
if (formats === undefined
|
|
370
|
-
if (typeof formats !== "object" || Array.isArray(formats)
|
|
382
|
+
const formats = dataOf(note).formats;
|
|
383
|
+
if (formats === undefined) return [];
|
|
384
|
+
if (typeof formats !== "object" || Array.isArray(formats) || !Object.keys(formats).length)
|
|
385
|
+
return [
|
|
386
|
+
atData(note, ["formats"], "error", "data.formats needs at least one named pattern"),
|
|
387
|
+
];
|
|
371
388
|
const findings = [];
|
|
372
389
|
for (const [slot, value] of Object.entries(formats)) {
|
|
373
|
-
if (
|
|
390
|
+
if (!/^[A-Za-z][A-Za-z0-9]*$/.test(slot)) {
|
|
374
391
|
findings.push(
|
|
375
392
|
atData(
|
|
376
393
|
note,
|
|
377
|
-
["
|
|
394
|
+
["formats", slot],
|
|
378
395
|
"error",
|
|
379
|
-
`data.
|
|
396
|
+
`data.formats.${slot} needs a name made of letters and digits`,
|
|
380
397
|
),
|
|
381
398
|
);
|
|
382
399
|
continue;
|
|
383
400
|
}
|
|
384
401
|
if (typeof value !== "string") {
|
|
385
402
|
findings.push(
|
|
386
|
-
atData(
|
|
387
|
-
note,
|
|
388
|
-
["dateFormats", slot],
|
|
389
|
-
"error",
|
|
390
|
-
`data.dateFormats.${slot} must be a string`,
|
|
391
|
-
),
|
|
403
|
+
atData(note, ["formats", slot], "error", `data.formats.${slot} must be a string`),
|
|
392
404
|
);
|
|
393
405
|
continue;
|
|
394
406
|
}
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
407
|
+
const syntaxError = calendarFormatSyntaxError(value);
|
|
408
|
+
if (syntaxError) findings.push(atData(note, ["formats", slot], "error", syntaxError));
|
|
409
|
+
}
|
|
410
|
+
const standard = Object.hasOwn(formats, "std") ? "std" : Object.keys(formats)[0];
|
|
411
|
+
if (typeof formats[standard] === "string") {
|
|
412
|
+
const error = readableCalendarFormatError(formats[standard]);
|
|
413
|
+
if (error) findings.push(atData(note, ["formats", standard], "error", error));
|
|
414
|
+
}
|
|
415
|
+
return findings;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/** Validate ordered, one-based season starts and named day positions. */
|
|
419
|
+
function checkCalendarDayNames(note, daysPerYear) {
|
|
420
|
+
const data = dataOf(note);
|
|
421
|
+
const findings = [];
|
|
422
|
+
const seasons = data.seasons;
|
|
423
|
+
if (seasons !== undefined && !Array.isArray(seasons))
|
|
424
|
+
findings.push(atData(note, ["seasons"], "error", "data.seasons must be a list"));
|
|
425
|
+
if (Array.isArray(seasons)) {
|
|
426
|
+
let prior = 0;
|
|
427
|
+
for (const [position, season] of seasons.entries()) {
|
|
428
|
+
const start = season?.start;
|
|
429
|
+
if (typeof season?.name !== "string" || !season.name.trim())
|
|
430
|
+
findings.push(
|
|
431
|
+
atData(note, ["seasons", position, "name"], "error", "a season needs a name"),
|
|
432
|
+
);
|
|
433
|
+
if (
|
|
434
|
+
!Number.isSafeInteger(start) ||
|
|
435
|
+
start <= prior ||
|
|
436
|
+
(Number.isSafeInteger(daysPerYear) && start > daysPerYear)
|
|
437
|
+
)
|
|
438
|
+
findings.push(
|
|
439
|
+
atData(
|
|
440
|
+
note,
|
|
441
|
+
["seasons", position, "start"],
|
|
442
|
+
"error",
|
|
443
|
+
"season starts must increase within the year",
|
|
444
|
+
),
|
|
445
|
+
);
|
|
446
|
+
if (
|
|
447
|
+
Object.keys(season ?? {}).some(
|
|
448
|
+
(key) => !["name", "abbreviation", "start"].includes(key),
|
|
449
|
+
)
|
|
450
|
+
)
|
|
451
|
+
findings.push(
|
|
452
|
+
atData(
|
|
453
|
+
note,
|
|
454
|
+
["seasons", position],
|
|
455
|
+
"error",
|
|
456
|
+
"a season uses only name, abbreviation, and start",
|
|
457
|
+
),
|
|
458
|
+
);
|
|
459
|
+
prior = start;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
const namedDays = data.namedDays;
|
|
463
|
+
if (namedDays !== undefined && !Array.isArray(namedDays))
|
|
464
|
+
findings.push(atData(note, ["namedDays"], "error", "data.namedDays must be a list"));
|
|
465
|
+
if (Array.isArray(namedDays)) {
|
|
466
|
+
const seen = new Set();
|
|
467
|
+
for (const [position, namedDay] of namedDays.entries()) {
|
|
468
|
+
if (typeof namedDay?.name !== "string" || !namedDay.name.trim())
|
|
469
|
+
findings.push(
|
|
470
|
+
atData(
|
|
471
|
+
note,
|
|
472
|
+
["namedDays", position, "name"],
|
|
473
|
+
"error",
|
|
474
|
+
"a named day needs a name",
|
|
475
|
+
),
|
|
476
|
+
);
|
|
477
|
+
const day = namedDay?.day;
|
|
478
|
+
if (
|
|
479
|
+
!Number.isSafeInteger(day) ||
|
|
480
|
+
day < 1 ||
|
|
481
|
+
(Number.isSafeInteger(daysPerYear) && day > daysPerYear) ||
|
|
482
|
+
seen.has(day)
|
|
483
|
+
)
|
|
484
|
+
findings.push(
|
|
485
|
+
atData(
|
|
486
|
+
note,
|
|
487
|
+
["namedDays", position, "day"],
|
|
488
|
+
"error",
|
|
489
|
+
"named days need distinct days within the year",
|
|
490
|
+
),
|
|
491
|
+
);
|
|
492
|
+
if (
|
|
493
|
+
Object.keys(namedDay ?? {}).some(
|
|
494
|
+
(key) => !["name", "abbreviation", "day"].includes(key),
|
|
495
|
+
)
|
|
496
|
+
)
|
|
497
|
+
findings.push(
|
|
498
|
+
atData(
|
|
499
|
+
note,
|
|
500
|
+
["namedDays", position],
|
|
501
|
+
"error",
|
|
502
|
+
"a named day uses only name, abbreviation, and day",
|
|
503
|
+
),
|
|
504
|
+
);
|
|
505
|
+
seen.add(day);
|
|
406
506
|
}
|
|
407
507
|
}
|
|
408
508
|
return findings;
|
|
@@ -466,16 +566,24 @@ export function checkWorldFacts(note, { index } = {}) {
|
|
|
466
566
|
findings.push(...dated);
|
|
467
567
|
}
|
|
468
568
|
if (written.includes("moon") && dataOf(note).moon?.newOn !== undefined) {
|
|
469
|
-
|
|
470
|
-
...
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
)
|
|
569
|
+
const parsed = parseNoteDate(dataOf(note).moon.newOn, {
|
|
570
|
+
...reckoningContext(index),
|
|
571
|
+
field: "data.moon.newOn",
|
|
572
|
+
allowUnknown: false,
|
|
573
|
+
file: note.file,
|
|
574
|
+
raw: note.raw,
|
|
575
|
+
keyPath: ["data", "moon", "newOn"],
|
|
576
|
+
});
|
|
577
|
+
findings.push(...parsed.findings);
|
|
578
|
+
if (parsed.date && parsed.date.precision !== "day")
|
|
579
|
+
findings.push(
|
|
580
|
+
atData(
|
|
581
|
+
note,
|
|
582
|
+
["moon", "newOn"],
|
|
583
|
+
"error",
|
|
584
|
+
"`data.moon.newOn` needs a specific day; write `<year>.<day>` or a day in a named calendar",
|
|
585
|
+
),
|
|
586
|
+
);
|
|
479
587
|
}
|
|
480
588
|
|
|
481
589
|
// The corpus questions, which need every note and so are asked only where
|
|
@@ -559,7 +667,7 @@ export function checkCalendarNote(note, { index } = {}) {
|
|
|
559
667
|
return findings;
|
|
560
668
|
}
|
|
561
669
|
|
|
562
|
-
for (const required of ["months", "epoch"]) {
|
|
670
|
+
for (const required of ["months", "epoch", "eras"]) {
|
|
563
671
|
if (written.includes(required)) continue;
|
|
564
672
|
findings.push({
|
|
565
673
|
file: note.file,
|
|
@@ -572,20 +680,53 @@ export function checkCalendarNote(note, { index } = {}) {
|
|
|
572
680
|
}
|
|
573
681
|
|
|
574
682
|
if (written.includes("epoch")) {
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
683
|
+
const epochText = String(dataOf(note).epoch);
|
|
684
|
+
if (!/^-?\d+\.\d+$/.test(epochText) || /^-?0+\./.test(epochText))
|
|
685
|
+
findings.push(
|
|
686
|
+
atData(
|
|
687
|
+
note,
|
|
688
|
+
["epoch"],
|
|
689
|
+
"error",
|
|
690
|
+
"data.epoch is a canonical <year>.<day> for calendar year 1, day 1",
|
|
691
|
+
),
|
|
692
|
+
);
|
|
693
|
+
const parsed = parseNoteDate(dataOf(note).epoch, {
|
|
694
|
+
...reckoningContext(index),
|
|
695
|
+
field: "data.epoch",
|
|
696
|
+
allowUnknown: false,
|
|
697
|
+
file: note.file,
|
|
698
|
+
raw: note.raw,
|
|
699
|
+
keyPath: ["data", "epoch"],
|
|
700
|
+
});
|
|
701
|
+
findings.push(...parsed.findings);
|
|
702
|
+
if (parsed.date && parsed.date.precision !== "day")
|
|
703
|
+
findings.push(
|
|
704
|
+
atData(
|
|
705
|
+
note,
|
|
706
|
+
["epoch"],
|
|
707
|
+
"error",
|
|
708
|
+
"`data.epoch` needs a specific day; write `<year>.<day>` or a day in a named calendar",
|
|
709
|
+
),
|
|
710
|
+
);
|
|
585
711
|
}
|
|
586
712
|
|
|
587
713
|
findings.push(...checkMonthSum(note, index));
|
|
714
|
+
findings.push(...checkCalendarDayNames(note, worldInvariants(index).year?.days));
|
|
588
715
|
findings.push(...checkEraShortcodes(note));
|
|
716
|
+
for (const [position, day] of (Array.isArray(dataOf(note).weekdays) ?
|
|
717
|
+
dataOf(note).weekdays
|
|
718
|
+
: []
|
|
719
|
+
).entries()) {
|
|
720
|
+
if (day?.ordinal !== undefined)
|
|
721
|
+
findings.push(
|
|
722
|
+
atData(
|
|
723
|
+
note,
|
|
724
|
+
["weekdays", position, "ordinal"],
|
|
725
|
+
"error",
|
|
726
|
+
"weekday order is its array position, starting at zero; omit ordinal",
|
|
727
|
+
),
|
|
728
|
+
);
|
|
729
|
+
}
|
|
589
730
|
findings.push(
|
|
590
731
|
...reckoningContext(index).findings.filter((finding) => finding.file === note.file),
|
|
591
732
|
);
|
|
@@ -669,6 +810,35 @@ function checkEraShortcodes(note) {
|
|
|
669
810
|
);
|
|
670
811
|
}
|
|
671
812
|
seen.add(shortcode);
|
|
813
|
+
if (era.label === undefined) return;
|
|
814
|
+
const label = era.label;
|
|
815
|
+
const forms = typeof label === "string" ? { after: label } : label;
|
|
816
|
+
if (!forms || typeof forms !== "object" || Array.isArray(forms)) {
|
|
817
|
+
findings.push(
|
|
818
|
+
atData(
|
|
819
|
+
note,
|
|
820
|
+
["eras", position, "label"],
|
|
821
|
+
"error",
|
|
822
|
+
"an era label is a string or an {after, before} map",
|
|
823
|
+
),
|
|
824
|
+
);
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
for (const [key, value] of Object.entries(forms)) {
|
|
828
|
+
if (
|
|
829
|
+
!["after", "before"].includes(key) ||
|
|
830
|
+
typeof value !== "string" ||
|
|
831
|
+
value.split("{date}").length !== 2
|
|
832
|
+
)
|
|
833
|
+
findings.push(
|
|
834
|
+
atData(
|
|
835
|
+
note,
|
|
836
|
+
["eras", position, "label", key],
|
|
837
|
+
"error",
|
|
838
|
+
"an era label uses after or before with exactly one {date} slot",
|
|
839
|
+
),
|
|
840
|
+
);
|
|
841
|
+
}
|
|
672
842
|
});
|
|
673
843
|
return findings;
|
|
674
844
|
}
|
|
@@ -720,7 +890,7 @@ function monthValues(months) {
|
|
|
720
890
|
);
|
|
721
891
|
}
|
|
722
892
|
|
|
723
|
-
/**
|
|
893
|
+
/** Calendaria stores a one-based ordinal; the authored order remains zero-based. */
|
|
724
894
|
function weekdayValues(weekdays) {
|
|
725
895
|
return (Array.isArray(weekdays) ? weekdays : []).map((day, position) =>
|
|
726
896
|
written({
|
|
@@ -731,34 +901,47 @@ function weekdayValues(weekdays) {
|
|
|
731
901
|
);
|
|
732
902
|
}
|
|
733
903
|
|
|
734
|
-
/** A season's
|
|
735
|
-
function seasonValues(seasons) {
|
|
736
|
-
return (Array.isArray(seasons) ? seasons : []).map((season) =>
|
|
904
|
+
/** A season's one-based start becomes an inclusive zero-based Calendaria range. */
|
|
905
|
+
function seasonValues(seasons, daysPerYear) {
|
|
906
|
+
return (Array.isArray(seasons) ? seasons : []).map((season, position, rows) =>
|
|
737
907
|
written({
|
|
738
908
|
name: String(season?.name ?? ""),
|
|
739
909
|
abbreviation:
|
|
740
910
|
season?.abbreviation === undefined ? undefined : String(season.abbreviation),
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
911
|
+
dayStart: season.start - 1,
|
|
912
|
+
dayEnd:
|
|
913
|
+
position + 1 < rows.length ? rows[position + 1].start - 2
|
|
914
|
+
: rows[0].start === 1 ? daysPerYear - 1
|
|
915
|
+
: rows[0].start - 2,
|
|
916
|
+
}),
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/** A named day uses Calendaria's one-based year day number. */
|
|
921
|
+
function namedDayValues(namedDays) {
|
|
922
|
+
return (Array.isArray(namedDays) ? namedDays : []).map((day) =>
|
|
923
|
+
written({
|
|
924
|
+
name: day.name,
|
|
925
|
+
abbreviation: day.abbreviation,
|
|
926
|
+
dayNumber: day.day,
|
|
745
927
|
}),
|
|
746
928
|
);
|
|
747
929
|
}
|
|
748
930
|
|
|
749
931
|
/** The era rows, keyed by shortcode, with the addressing segments dropped. */
|
|
750
|
-
function eraEntries(eras
|
|
932
|
+
function eraEntries(eras) {
|
|
751
933
|
const entries = {};
|
|
752
|
-
|
|
934
|
+
const dated = (Array.isArray(eras) ? eras : [])
|
|
935
|
+
.filter((era) => Number.isSafeInteger(era?.start) && era.start >= 1)
|
|
936
|
+
.sort((a, b) => a.start - b.start);
|
|
937
|
+
for (const [position, era] of dated.entries()) {
|
|
753
938
|
const shortcode = String(era?.shortcode ?? "");
|
|
754
939
|
if (!shortcode) continue;
|
|
755
|
-
const start = parseNoteDate(era?.start, { ...dateContext, allowUnknown: false }).date;
|
|
756
|
-
const end = parseNoteDate(era?.end, { ...dateContext, allowUnknown: false }).date;
|
|
757
940
|
entries[shortcode] = written({
|
|
758
941
|
name: String(era?.name ?? ""),
|
|
759
942
|
abbreviation: era?.abbreviation === undefined ? undefined : String(era.abbreviation),
|
|
760
|
-
|
|
761
|
-
|
|
943
|
+
startYear: era.start,
|
|
944
|
+
endYear: dated[position + 1] ? dated[position + 1].start - 1 : null,
|
|
762
945
|
});
|
|
763
946
|
}
|
|
764
947
|
return entries;
|
|
@@ -780,6 +963,8 @@ function eraEntries(eras, dateContext = {}) {
|
|
|
780
963
|
function moonEntries(moon, body, name, months, dateContext = {}) {
|
|
781
964
|
if (!moon || !name) return null;
|
|
782
965
|
const date = parseNoteDate(moon.newOn, { ...dateContext, allowUnknown: false }).date;
|
|
966
|
+
if (date && date.precision !== "day")
|
|
967
|
+
throw new RangeError("data.moon.newOn needs a specific day");
|
|
783
968
|
const calendarDay =
|
|
784
969
|
date?.canonicalDay && months.length ? monthDayOfYear(months, date.canonicalDay) : null;
|
|
785
970
|
return {
|
|
@@ -818,13 +1003,33 @@ function moonEntries(moon, body, name, months, dateContext = {}) {
|
|
|
818
1003
|
* @param {string} [opts.contentPackage] - The package shipping the note, which
|
|
819
1004
|
* is a fact about the tree rather than about the note: a note never declares
|
|
820
1005
|
* its own package.
|
|
1006
|
+
* @param {object} [opts.dateContext] - Calendar conversion context.
|
|
821
1007
|
* @returns {object} The definition.
|
|
822
1008
|
*/
|
|
823
1009
|
export function compileCalendar({ note, invariants, contentPackage, dateContext = {} }) {
|
|
824
1010
|
const data = dataOf(note);
|
|
825
1011
|
const year = invariants?.year ?? {};
|
|
826
1012
|
const months = Array.isArray(data.months) ? data.months : [];
|
|
827
|
-
const
|
|
1013
|
+
const dayNameError = checkCalendarDayNames(note, year.days)[0];
|
|
1014
|
+
if (dayNameError) throw new RangeError(dayNameError.message);
|
|
1015
|
+
const starts = Array.isArray(data.eras) ? data.eras.map((era) => era?.start) : [];
|
|
1016
|
+
if (
|
|
1017
|
+
starts.filter((start) => start === null).length !== 1 ||
|
|
1018
|
+
starts.filter((start) => start === 1).length !== 1 ||
|
|
1019
|
+
starts.some((start) => start !== null && (!Number.isSafeInteger(start) || start < 1)) ||
|
|
1020
|
+
new Set(starts).size !== starts.length
|
|
1021
|
+
)
|
|
1022
|
+
throw new RangeError(
|
|
1023
|
+
"data.eras needs one null start, one year-one start, and distinct positive year starts",
|
|
1024
|
+
);
|
|
1025
|
+
const formatError = checkCalendarDateFormats(note).find(
|
|
1026
|
+
(finding) => finding.severity === "error",
|
|
1027
|
+
);
|
|
1028
|
+
if (formatError) throw new RangeError(formatError.message);
|
|
1029
|
+
const epoch = parseCanonicalDate(String(data.epoch ?? ""), year.days);
|
|
1030
|
+
if (!epoch || epoch.year === 0 || epoch.seconds !== undefined)
|
|
1031
|
+
throw new RangeError("data.epoch needs a canonical <year>.<day> specific day");
|
|
1032
|
+
const epochOffset = canonicalDayOffset(canonicalYear(epoch.year), epoch.day, 1, year.days);
|
|
828
1033
|
const shortcode = String(note.fm?.shortcode ?? "");
|
|
829
1034
|
const name = String(note.fm?.name?.full ?? "");
|
|
830
1035
|
const description = String(note.fm?.description ?? "");
|
|
@@ -841,9 +1046,8 @@ export function compileCalendar({ note, invariants, contentPackage, dateContext
|
|
|
841
1046
|
name,
|
|
842
1047
|
description,
|
|
843
1048
|
years: {
|
|
844
|
-
//
|
|
845
|
-
|
|
846
|
-
yearZero: epoch?.year ?? 0,
|
|
1049
|
+
// Internal year zero displays as calendar year one at the epoch.
|
|
1050
|
+
yearZero: 1,
|
|
847
1051
|
// Required, non-nullable and with no initial, so it must be
|
|
848
1052
|
// written; with no weekdays it indexes nothing and 0 is the only
|
|
849
1053
|
// defensible value.
|
|
@@ -855,19 +1059,34 @@ export function compileCalendar({ note, invariants, contentPackage, dateContext
|
|
|
855
1059
|
months: { values: monthValues(months) },
|
|
856
1060
|
days: written({
|
|
857
1061
|
values: weekdayValues(data.weekdays),
|
|
1062
|
+
names: namedDayValues(data.namedDays),
|
|
858
1063
|
daysPerYear: year.days,
|
|
859
1064
|
hoursPerDay: year.hoursPerDay,
|
|
860
1065
|
minutesPerHour: year.minutesPerHour,
|
|
861
1066
|
secondsPerMinute: year.secondsPerMinute,
|
|
862
1067
|
}),
|
|
863
|
-
seasons: { values: seasonValues(data.seasons) },
|
|
864
|
-
//
|
|
865
|
-
epochDayOffset:
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
1068
|
+
seasons: { values: seasonValues(data.seasons, year.days) },
|
|
1069
|
+
// Shift world time so the authored epoch is calendar year one, day one.
|
|
1070
|
+
epochDayOffset: epochOffset === 0 ? 0 : -epochOffset,
|
|
1071
|
+
eras: eraEntries(data.eras),
|
|
1072
|
+
dateFormats:
|
|
1073
|
+
data.formats && Object.keys(data.formats).length ?
|
|
1074
|
+
written({
|
|
1075
|
+
short: selectCalendarFormat(data).pattern,
|
|
1076
|
+
long: data.formats.long ?? selectCalendarFormat(data).pattern,
|
|
1077
|
+
full:
|
|
1078
|
+
data.formats.full ??
|
|
1079
|
+
data.formats.long ??
|
|
1080
|
+
selectCalendarFormat(data).pattern,
|
|
1081
|
+
...Object.fromEntries(
|
|
1082
|
+
CALENDAR_DATE_FORMAT_KEYS.filter(
|
|
1083
|
+
(key) => !["short", "long", "full"].includes(key),
|
|
1084
|
+
)
|
|
1085
|
+
.filter((key) => data.formats[key] !== undefined)
|
|
1086
|
+
.map((key) => [key, data.formats[key]]),
|
|
1087
|
+
),
|
|
1088
|
+
})
|
|
1089
|
+
: undefined,
|
|
871
1090
|
moons: moons ?? undefined,
|
|
872
1091
|
metadata: written({
|
|
873
1092
|
id: shortcode || undefined,
|
package/engine/calendars.mjs
CHANGED
|
@@ -104,6 +104,13 @@ export function canonicalYear(year, epoch = CANONICAL_EPOCH) {
|
|
|
104
104
|
return epoch + (year > 0 ? year - 1 : year);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
/** The signed year a reckoning counts at a canonical year, without year zero. */
|
|
108
|
+
export function eraYear(canonical, epoch = CANONICAL_EPOCH) {
|
|
109
|
+
if (!Number.isSafeInteger(canonical) || !Number.isSafeInteger(epoch))
|
|
110
|
+
throw new RangeError("era conversion needs whole canonical and epoch years");
|
|
111
|
+
return canonical >= epoch ? canonical - epoch + 1 : canonical - epoch;
|
|
112
|
+
}
|
|
113
|
+
|
|
107
114
|
/**
|
|
108
115
|
* The total-order key a date sorts on.
|
|
109
116
|
*
|
|
@@ -267,7 +274,7 @@ export function calendarStructure(calendar) {
|
|
|
267
274
|
/**
|
|
268
275
|
* How long one month of a calendar is, or `null` where the calendar is silent.
|
|
269
276
|
*
|
|
270
|
-
* @param {{months?: readonly CalendarMonth[]|null}}
|
|
277
|
+
* @param {{months?: readonly CalendarMonth[]|null}|undefined} calendar - The calendar.
|
|
271
278
|
* @param {number} month - The month, numbered from 1.
|
|
272
279
|
* @returns {number|null} Its length in days, or `null`.
|
|
273
280
|
*/
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
|
|
41
41
|
import { codeRegions } from "./code-fences.mjs";
|
|
42
42
|
|
|
43
|
-
/** The generated scaffold `
|
|
43
|
+
/** The generated scaffold `package-build`/Changesets writes, never authored prose. */
|
|
44
44
|
const RELEASE_HEADING_RE = /^## /;
|
|
45
45
|
const CHANGES_HEADING_RE = /^### (?:Major|Minor|Patch) Changes\s*$/;
|
|
46
46
|
|
|
@@ -79,7 +79,7 @@ const SCOREBOARD_PATTERNS = [
|
|
|
79
79
|
|
|
80
80
|
/** A token that reads as code but sits outside any code span — warning only. */
|
|
81
81
|
const CODE_TOKEN_RE =
|
|
82
|
-
|
|
82
|
+
/(?<![\p{L}\p{N}\p{M}_])[a-z][a-zA-Z0-9]*[A-Z][a-zA-Z0-9]*(?:\(\))?(?![\p{L}\p{N}\p{M}_])|\b[\w-]+(?:\/[\w-]+)+\.[A-Za-z]{1,8}\b|\b[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+\b/gu;
|
|
83
83
|
|
|
84
84
|
/** Bullets over this many words read as a paragraph, not a release note. */
|
|
85
85
|
const MAX_BULLET_WORDS = 40;
|