@heroiclands/package-build 19.0.0 → 20.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1100 -0
- package/CONTENT.md +264 -33
- package/README.md +43 -4
- package/bin/content-build.mjs +94 -3
- package/config.mjs +9 -1
- package/content-config.mjs +99 -19
- package/docs/content-format.md +394 -72
- package/e2e.mjs +297 -3
- package/engine/actor-compiler.mjs +197 -7
- package/engine/address-charset.mjs +23 -5
- package/engine/base-compiler.mjs +63 -2
- package/engine/bundles.mjs +9 -0
- package/engine/content-address.mjs +92 -1
- package/engine/content-charset.mjs +434 -0
- package/engine/content-format.mjs +102 -0
- package/engine/content-icons.mjs +388 -0
- package/engine/content-index.mjs +11 -8
- package/engine/content-links.mjs +37 -21
- package/engine/field-reference.mjs +57 -5
- package/engine/field-spec.mjs +214 -7
- package/engine/folder-notes.mjs +24 -1
- package/engine/foreign-catalog.mjs +112 -7
- package/engine/foundry-entries.mjs +14 -0
- package/engine/frontmatter-lint.mjs +377 -56
- package/engine/frontmatter.mjs +11 -11
- package/engine/generate.mjs +72 -12
- package/engine/helpers.mjs +96 -10
- package/engine/index.mjs +9 -0
- package/engine/item-compiler.mjs +37 -0
- package/engine/journals.mjs +21 -4
- package/engine/macros.mjs +8 -0
- package/engine/map-notes.mjs +7 -7
- package/engine/note-claims.mjs +208 -5
- package/engine/note-ids.mjs +25 -1
- package/engine/note-vocabulary.mjs +76 -9
- package/engine/pack-config.mjs +102 -12
- package/engine/pack-router.mjs +0 -0
- package/engine/prose-config.mjs +42 -0
- package/engine/prose-lint.mjs +126 -0
- package/engine/retired-fields.mjs +57 -16
- package/engine/runtime-only-fields.mjs +204 -0
- package/engine/scenes.mjs +12 -19
- package/engine/schema-check.mjs +23 -1
- package/engine/schema-extract.mjs +13 -0
- package/engine/site-index.mjs +17 -0
- package/engine/subtype-registry.mjs +30 -0
- package/engine/system-block.mjs +81 -3
- package/engine/web-wikilinks.mjs +33 -27
- package/engine/wikilink-syntax.mjs +7 -0
- package/engine/wikilinks.mjs +74 -16
- package/hm3/actors.mjs +63 -13
- package/package.json +2 -2
- package/sohl/actors.mjs +106 -7
- package/sohl/item-fields.mjs +203 -0
- package/sohl/note-schemas.mjs +6 -3
- package/types/config.d.mts +7 -0
- package/types/e2e.d.mts +130 -3
- package/types/engine/actor-compiler.d.mts +83 -3
- package/types/engine/address-charset.d.mts +22 -4
- package/types/engine/base-compiler.d.mts +54 -3
- package/types/engine/content-address.d.mts +64 -0
- package/types/engine/content-charset.d.mts +127 -0
- package/types/engine/content-format.d.mts +9 -0
- package/types/engine/content-icons.d.mts +151 -0
- package/types/engine/field-spec.d.mts +271 -3
- package/types/engine/folder-notes.d.mts +20 -0
- package/types/engine/foreign-catalog.d.mts +38 -2
- package/types/engine/foundry-entries.d.mts +6 -0
- package/types/engine/frontmatter-lint.d.mts +164 -30
- package/types/engine/frontmatter.d.mts +11 -11
- package/types/engine/generate.d.mts +27 -0
- package/types/engine/helpers.d.mts +45 -9
- package/types/engine/index.d.mts +3 -0
- package/types/engine/map-notes.d.mts +2 -2
- package/types/engine/note-claims.d.mts +67 -0
- package/types/engine/note-ids.d.mts +14 -0
- package/types/engine/pack-config.d.mts +35 -0
- package/types/engine/prose-config.d.mts +41 -0
- package/types/engine/prose-lint.d.mts +36 -0
- package/types/engine/retired-fields.d.mts +29 -13
- package/types/engine/runtime-only-fields.d.mts +102 -0
- package/types/engine/schema-check.d.mts +10 -1
- package/types/engine/subtype-registry.d.mts +21 -0
- package/types/engine/system-block.d.mts +28 -2
- package/types/sohl/actors.d.mts +3 -3
package/CONTENT.md
CHANGED
|
@@ -349,6 +349,21 @@ pack: mysteries
|
|
|
349
349
|
that type's default implicitly; a type with several designates one with
|
|
350
350
|
`default: true`. Where several exist and none is marked, a declaration is
|
|
351
351
|
**mandatory** and an undeclared note fails the build.
|
|
352
|
+
- **A default is per system, not merely per type** (#58). The rule above counts
|
|
353
|
+
every pack of a type together, so a tree shipping one Actor pack per system has
|
|
354
|
+
two and would need a flag — except that asked _per system_ the layout is
|
|
355
|
+
unambiguous, one pack each, for the same reason a single-pack type needs no
|
|
356
|
+
flag. And a system is never answered with another system's pack: a type-wide
|
|
357
|
+
default declaring `system: sohl` does not route the HM3 document, which
|
|
358
|
+
otherwise had that pass see a pack name that was not its own and skip every
|
|
359
|
+
note in the tree without a word.
|
|
360
|
+
- **A `pack:` naming another system's pack** is refused where the _block_
|
|
361
|
+
declares it — `hm3.pack` says where the HM3 document goes, so naming a SoHL
|
|
362
|
+
pack is a contradiction, reported naming the note and the pack. At the top
|
|
363
|
+
level it is no contradiction: the shared position is the value a note states
|
|
364
|
+
once for every system, and a system-specific pack cannot be that value, so it
|
|
365
|
+
simply does not answer for the other system, which falls through to its own
|
|
366
|
+
default.
|
|
352
367
|
- **A `pack:` naming no configured pack is a build error**, not a fall-through to
|
|
353
368
|
the default. A typo'd name that quietly landed content in the wrong compendium
|
|
354
369
|
would be silent partial compilation — the failure mode this toolchain's guards
|
|
@@ -357,12 +372,43 @@ pack: mysteries
|
|
|
357
372
|
- **A note's `pack:` names where its _own_ document goes.** Anything derived from
|
|
358
373
|
it — an item's or a macro's prose, which compiles into a JournalEntry of its
|
|
359
374
|
own — lands in the default pack of _that_ type.
|
|
360
|
-
|
|
361
|
-
**
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
375
|
+
- **Every document a note produces needs a pack, and one that has none is a
|
|
376
|
+
finding** (#152). A note produces more than one document as a matter of
|
|
377
|
+
course: an item note an Item and the JournalEntry its prose becomes, a map
|
|
378
|
+
note a Scene and a JournalEntry, an actor note an Actor and a JournalEntry
|
|
379
|
+
since #337. Where the configuration declares no pack for one of them, that
|
|
380
|
+
document used to be dropped while the rest of the note compiled into a pack
|
|
381
|
+
that does exist — a build that succeeds and ships half of what was written.
|
|
382
|
+
The finding names the note, the class with no pack, and the class that did
|
|
383
|
+
compile, which is what distinguishes it from a note **nothing** claims: that
|
|
384
|
+
one is a `type:` to correct, this one a pack to declare.
|
|
385
|
+
|
|
386
|
+
It is asked **per note, not per type**, because documentation is: `Journals`
|
|
387
|
+
declines a doc-carrying note whose body is empty — an item with no prose gets
|
|
388
|
+
no doc — so a tree of deliberately description-less items loses nothing by
|
|
389
|
+
having no JournalEntry pack, and is told nothing. And it names no system, so a
|
|
390
|
+
type one system maps and another does not stays silent for the system that
|
|
391
|
+
declines it, per #79.
|
|
392
|
+
|
|
393
|
+
**The configuration is found by walking up from the working directory, and from
|
|
394
|
+
the installed package only if that finds nothing.** `engine/pack-config.mjs`
|
|
395
|
+
climbs from `process.cwd()` first, so a build reads the tree it was run in —
|
|
396
|
+
from the repository root, from `packages/`, from anywhere below, since the walk
|
|
397
|
+
climbs. Climbing from the module itself is the fallback, for an invocation from
|
|
398
|
+
outside any repository.
|
|
399
|
+
|
|
400
|
+
The order matters in one shape: a git worktree nested under its parent checkout
|
|
401
|
+
with **no `node_modules` of its own** resolves `@heroiclands/package-build` out
|
|
402
|
+
of the parent's, because Node's resolution walks parent directories. Climbing
|
|
403
|
+
from the module then landed on the _parent's_ configuration, and the build
|
|
404
|
+
compiled the parent's content tree into the parent's `build/` and exited 0
|
|
405
|
+
(#364). When both walks find a configuration and they disagree, the working
|
|
406
|
+
directory's is read and the ignored one is named in a warning — that
|
|
407
|
+
disagreement is also the cheapest signal that this tree is building on another
|
|
408
|
+
checkout's `node_modules`. Run `npm ci` in the worktree to give it its own.
|
|
409
|
+
|
|
410
|
+
Set `PACKAGE_BUILD_CONFIG` to point at the file explicitly if a consumer keeps
|
|
411
|
+
it somewhere else; it skips both walks.
|
|
366
412
|
|
|
367
413
|
**The configuration is resolved on first read, never at import.** Every module
|
|
368
414
|
here can be imported — and `content-build --version` and `--help` answered — in a
|
|
@@ -388,6 +434,24 @@ exactly once, here, and read through `loadPackConfig()` everywhere. There is one
|
|
|
388
434
|
resolved set at runtime; the compilers and the link-manifest emitter cannot come
|
|
389
435
|
to disagree about which notes carry documentation.
|
|
390
436
|
|
|
437
|
+
**Every note compiling into a system-bearing document carries documentation, and
|
|
438
|
+
that includes actors** (#337). `docEntryTypes` is `itemTypes` plus the actor
|
|
439
|
+
types the shipped subtype maps declare (`ACTOR_TYPES`, derived from them rather
|
|
440
|
+
than listed again), plus `macro` and the map types. Only `doc` is outside it, for
|
|
441
|
+
the reason that actually applies to it: its single document _is_ the prose.
|
|
442
|
+
|
|
443
|
+
A being therefore has the same two addresses an item has — `<pkg>-<system>-being-<shortcode>`
|
|
444
|
+
for the Actor and `<pkg>-none-docbeing-<shortcode>` for the page — where before
|
|
445
|
+
it had only the first, and was the one system-bearing note a prose link could not
|
|
446
|
+
name.
|
|
447
|
+
|
|
448
|
+
**An actor keeps its prose inline as well, and that asymmetry is deliberate.** An
|
|
449
|
+
item's description is an `@UUID` pointer into its journal, because one item is
|
|
450
|
+
embedded across hundreds of beings and baking long prose into every copy bloats
|
|
451
|
+
the compendium by the length of the text times the number of carriers. An actor
|
|
452
|
+
is singular, so the same indirection costs a reader a click and saves nothing:
|
|
453
|
+
`system.appearance` and `system.dossier` stay as rendered prose.
|
|
454
|
+
|
|
391
455
|
The Item compiler **dispatches through that same resolved table**, via
|
|
392
456
|
`engine/item-registry.mjs` (`itemTypes()` and `itemBuilder(type)`), so the types a
|
|
393
457
|
consumer's notes are accepted for and the builders they compile with are one
|
|
@@ -438,12 +502,27 @@ systems:
|
|
|
438
502
|
sohl: { compatibility: { verified: "0.9.0" } }
|
|
439
503
|
hm3: { compatibility: { verified: "1.6.3" } }
|
|
440
504
|
packs:
|
|
441
|
-
- { name: items-sohl, type: Item, system: sohl
|
|
505
|
+
- { name: items-sohl, type: Item, system: sohl }
|
|
442
506
|
- { name: items-hm3, type: Item, system: hm3 }
|
|
443
|
-
- { name: actors-sohl, type: Actor, system: sohl
|
|
507
|
+
- { name: actors-sohl, type: Actor, system: sohl }
|
|
444
508
|
- { name: actors-hm3, type: Actor, system: hm3 }
|
|
445
509
|
```
|
|
446
510
|
|
|
511
|
+
**The `systems:` block is required here, not decorative.** A pack's `system:` is
|
|
512
|
+
what its documents are stamped `_stats.systemId` and `systemVersion` from, and
|
|
513
|
+
the version can only come from that block — or, for a package whose packs are
|
|
514
|
+
all for its own system, from the package-wide stats. A pack naming a system that
|
|
515
|
+
resolves to neither is refused at configuration time, naming the pack and the
|
|
516
|
+
entry to add. It used to fall through and stamp `null` for both, which is the
|
|
517
|
+
plausible lie #43 was about arriving by the one path the check did not cover:
|
|
518
|
+
`harn-ensemble` shipped 2,513 compiled actors that way, out of a pack whose
|
|
519
|
+
configuration says `system: sohl` on the line above.
|
|
520
|
+
|
|
521
|
+
No `default: true` anywhere, because each system has exactly one pack of each
|
|
522
|
+
type and a default is resolved per system. Marking one is still allowed and
|
|
523
|
+
still means what it says — it designates that _system's_ default where a system
|
|
524
|
+
has several packs of a type.
|
|
525
|
+
|
|
447
526
|
A note carrying both a `sohl:` and an `hm3:` block then compiles **one document
|
|
448
527
|
in each system**, each shaped by its own builders and stamped with its own
|
|
449
528
|
system version. A note carrying only one block compiles only that system's
|
|
@@ -534,9 +613,46 @@ Both spellings are equal; the difference is only whether the type brings art.
|
|
|
534
613
|
whitelist without a builder behind it.
|
|
535
614
|
|
|
536
615
|
**The path is spelled the way a note spells it.** Registry art goes through the
|
|
537
|
-
same `resolveImg` rule as a note's `img:`, so
|
|
538
|
-
|
|
539
|
-
|
|
616
|
+
same `resolveImg` rule as a note's `img:`, so one spelling means one thing
|
|
617
|
+
wherever it is written.
|
|
618
|
+
|
|
619
|
+
#### An asset path's first segment says which package owns it
|
|
620
|
+
|
|
621
|
+
Every authored asset path — a registry `img:`, a note's `img:`, an actor's
|
|
622
|
+
`portrait:` — answers "which package holds this file?" in its **first segment**,
|
|
623
|
+
and there are exactly three answers:
|
|
624
|
+
|
|
625
|
+
| Authored path starts with | Owner | Emitted |
|
|
626
|
+
| ------------------------- | --------------------- | -------------------- |
|
|
627
|
+
| `systems/` | a separate **system** | unchanged |
|
|
628
|
+
| `modules/` | a separate **module** | unchanged |
|
|
629
|
+
| anything else | **this package** | `<assetRoot>/<path>` |
|
|
630
|
+
|
|
631
|
+
`assetRoot` is derived, never authored: it is
|
|
632
|
+
`<packageKind>/<foundryPackage>/assets`, and it is the one place `systems/sohl`
|
|
633
|
+
(or `modules/sohl-thalorna`) is ever spelled. So `icons/relic.svg` in a module's
|
|
634
|
+
registry compiles to `modules/sohl-relics/assets/icons/relic.svg`, and the same
|
|
635
|
+
string in the system's compiles to `systems/sohl/assets/icons/relic.svg`. An
|
|
636
|
+
already-served `systems/sohl/assets/icons/…` passes through untouched — which is
|
|
637
|
+
what lets a module pair a SoHL default with one of its own types.
|
|
638
|
+
|
|
639
|
+
**"Anything else" is the rule, not a list of directories.** A package owns its
|
|
640
|
+
whole `assets/` tree, so a directory this toolchain has never heard of is still
|
|
641
|
+
that package's: art under `assets/artwork/` is addressed `artwork/deity.webp`
|
|
642
|
+
and rooted exactly as `icons/…` and `images/…` are. An address naming no package
|
|
643
|
+
at all — an absolute URL, a `data:` URI, a `/`-rooted path — passes through, on
|
|
644
|
+
the same rule rather than as an exception.
|
|
645
|
+
|
|
646
|
+
`worlds/` is deliberately not exempt: a package may not ship art out of a world,
|
|
647
|
+
so prefixing such a path produces a plainly broken one rather than a plausible
|
|
648
|
+
one that 404s in Foundry unreported.
|
|
649
|
+
|
|
650
|
+
**`banner:` is a path that does not follow this rule.** It reaches no compiled
|
|
651
|
+
document; it is a top-level key the Hugo theme reads, and the theme prefixes a
|
|
652
|
+
relative value with `images/` and joins it onto `params.cdnBaseURL`. The two
|
|
653
|
+
address different places — `img:` a file Foundry serves, `banner:` a file the
|
|
654
|
+
CDN serves — so they are stated apart rather than reconciled. See the
|
|
655
|
+
[content format specification](docs/content-format.md#banner-addresses-the-cdn-not-the-foundry-install).
|
|
540
656
|
|
|
541
657
|
#### "Names no art" and "wants no art" are different (#218)
|
|
542
658
|
|
|
@@ -570,12 +686,20 @@ rather than two.
|
|
|
570
686
|
> (`sohl/item-fields.mjs`, "the style of address the office carries"), so one
|
|
571
687
|
> authored key fed two unrelated destinations that disagreed about what empty
|
|
572
688
|
> means — and `title: null` stringified into the compiled document as the literal
|
|
573
|
-
> `"null"`.
|
|
574
|
-
>
|
|
689
|
+
> `"null"`. The field declares `topLevelMeans` now, so the top-level key is no
|
|
690
|
+
> longer a source for it (#218).
|
|
691
|
+
>
|
|
692
|
+
> So `title: null` is a note declining to state a heading, and the site emitter's
|
|
693
|
+
> `fm.title ?? name` falls back to `name.full`. `title: ""` publishes a
|
|
694
|
+
> deliberately blank heading and is warned about on its own account — as the
|
|
695
|
+
> page's heading, not as an art path.
|
|
575
696
|
>
|
|
576
|
-
>
|
|
577
|
-
>
|
|
578
|
-
>
|
|
697
|
+
> **That warning reads the note's top level only.** The two spellings still name
|
|
698
|
+
> unrelated quantities, so a `sohl.title` — the office's style of address — must
|
|
699
|
+
> not answer for the page's heading. Twenty-eight `sohl-kethira-basic`
|
|
700
|
+
> affiliations write `sohl.title: ""`, meaning an office with no style of
|
|
701
|
+
> address, and every one of them was reported as publishing a blank heading
|
|
702
|
+
> until the check honoured the declaration (#312).
|
|
579
703
|
|
|
580
704
|
Because `""` used to mean "unset", a note still carrying that spelling has
|
|
581
705
|
quietly changed meaning, and the frontmatter lint says so — for either art
|
|
@@ -712,6 +836,14 @@ state the same quantity, which is nearly everywhere: `subType` is the other
|
|
|
712
836
|
declared item field spelled like a note-level key, and there the two agree by
|
|
713
837
|
design.
|
|
714
838
|
|
|
839
|
+
**The statement is symmetric, and the frontmatter linter reads it from the other
|
|
840
|
+
side too.** If the two positions hold unrelated quantities, then the _in-block_
|
|
841
|
+
position is not the note-level field either — so a check about a note-level field
|
|
842
|
+
(the page's heading, an art path) reads past a block key the note's own type
|
|
843
|
+
claims for something else. Declaring `topLevelMeans` settles both directions at
|
|
844
|
+
once; it was read for the emitted field alone until #312, which is how an
|
|
845
|
+
affiliation's office style came to answer for its page heading.
|
|
846
|
+
|
|
715
847
|
**An exempted field is still authorable**, at the two positions that describe the
|
|
716
848
|
document rather than the note:
|
|
717
849
|
|
|
@@ -949,6 +1081,44 @@ key it was probably meant to be, drawn from that type's own vocabulary:
|
|
|
949
1081
|
assets/content/Gear/Axe.md:14:5: error: "wieght" is not a `data:` property of a weapongear; the container is closed, so unlike a top-level key it is not passed through to the page. Did you mean "weight"?
|
|
950
1082
|
```
|
|
951
1083
|
|
|
1084
|
+
**A system block is closed too, and which blocks exist is the configuration's
|
|
1085
|
+
answer** (#58). A package is held to the blocks named after the systems it
|
|
1086
|
+
declares it ships for, read from the three places that already declare them:
|
|
1087
|
+
`systems:`, a pack's own `system:`, and `stats.systemId` where neither is
|
|
1088
|
+
written. So a package shipping for HM3 has its `hm3:` block checked and a
|
|
1089
|
+
package shipping for SoHL its `sohl:`. It used to be a constant, and the
|
|
1090
|
+
constant was `sohl`: an `hm3:` block was never read at all, so every key in it
|
|
1091
|
+
was discarded at compile without a word, while the block that _was_ checked was
|
|
1092
|
+
named after a system the package does not ship for.
|
|
1093
|
+
|
|
1094
|
+
A pack's `system:` counts because it is already authoritative at compile — a
|
|
1095
|
+
note routed to a pack declaring one and carrying no such block fails the build —
|
|
1096
|
+
so a lint blind to it would refuse a note for want of a block it never checked.
|
|
1097
|
+
`harn-ensemble` declares its two systems that way and no other.
|
|
1098
|
+
|
|
1099
|
+
**Each block is checked against its own system's vocabulary**, and that has two
|
|
1100
|
+
sources. A system's **`itemBuilders` registry** covers its item types: `skill` is
|
|
1101
|
+
one name over two data models, so a key SoHL's `skill` declares is not thereby a
|
|
1102
|
+
key HM3's declares, and a block that borrowed its neighbour's field names would
|
|
1103
|
+
accept the one mistake this check exists to report. The **note schemas** cover
|
|
1104
|
+
the rest — `being` above all, which is an actor type and sits in no item
|
|
1105
|
+
registry — and they belong to one system, SoHL, because that is the vocabulary
|
|
1106
|
+
`content-build` is built with.
|
|
1107
|
+
|
|
1108
|
+
A type neither source names — `mysticalability` is SoHL's, `invocation` is
|
|
1109
|
+
HM3's — is a type that system says nothing about, and its block is left alone on
|
|
1110
|
+
such a note rather than reported wholesale. A package that names no system at all
|
|
1111
|
+
is system-agnostic on purpose: its packs are core document types carrying no
|
|
1112
|
+
system data, so it has no system block and none is invented for it.
|
|
1113
|
+
|
|
1114
|
+
**A block whose vocabulary nothing states is said out loud.** A package
|
|
1115
|
+
declaring a system other than SoHL and no `itemBuilders` registry for it has
|
|
1116
|
+
nothing that can say what that block may carry, so the block goes unchecked — and
|
|
1117
|
+
`content-build lint` reports that once, naming the system and the registry to
|
|
1118
|
+
declare, because a check that quietly does nothing is indistinguishable from one
|
|
1119
|
+
that passed. `harn-ensemble` is the tree that gets it today: its `sohl:` block is
|
|
1120
|
+
checked, and its `hm3:` waits on `itemBuilders: [hm3, sohl]`.
|
|
1121
|
+
|
|
952
1122
|
**`subType` stays at the top level**, and is closed in its own way: a type
|
|
953
1123
|
either declares a `subType` or does not, and a type that does declares its
|
|
954
1124
|
values. A `weapon` declares none — SoHL distinguishes a weapon's uses by strike
|
|
@@ -1342,6 +1512,28 @@ markdownlint honour them natively. The one exception is `CHANGELOG.md`, which
|
|
|
1342
1512
|
`changeset version` regenerates in every repository here: linting it reports on
|
|
1343
1513
|
the generator, so it is skipped by default.
|
|
1344
1514
|
|
|
1515
|
+
**A default that says when it is not in force.** Because a local config wins
|
|
1516
|
+
wholesale rather than merging, the conventions above otherwise hold by convention
|
|
1517
|
+
alone: a `prettier.config.mjs` that spreads `PRETTIER_BASE` without the `**/*.md`
|
|
1518
|
+
override reindents every note at 4, and a partial `.prettierrc` such as
|
|
1519
|
+
`{"tabWidth": 2}` silently discards `printWidth: 100`, `trailingComma` and the
|
|
1520
|
+
rest. So every `format` run first names, as warnings, each shared convention this
|
|
1521
|
+
repository resolves differently (#133):
|
|
1522
|
+
|
|
1523
|
+
```text
|
|
1524
|
+
prettier.config.mjs: warning: markdown `tabWidth` is 4 here; the shared configuration says 2
|
|
1525
|
+
.prettierrc: warning: `printWidth` is not set here, so Prettier's own default applies; the shared configuration says 100
|
|
1526
|
+
```
|
|
1527
|
+
|
|
1528
|
+
A repository with **no** Prettier config is warned too, and it is the sharper
|
|
1529
|
+
case: the shared conventions then reach this command and nothing else, so an
|
|
1530
|
+
editor's format-on-save and a bare `npx prettier --check .` apply Prettier's own
|
|
1531
|
+
defaults to the same tree and the two take turns rewriting the same lines. The
|
|
1532
|
+
fix is the one-line re-export below.
|
|
1533
|
+
|
|
1534
|
+
None of this fails a run. A deliberate local choice still wins — it just stops
|
|
1535
|
+
being silent.
|
|
1536
|
+
|
|
1345
1537
|
Neither tool's file discovery is reimplemented, so `content-build format --check`
|
|
1346
1538
|
and a bare `prettier --check .` report the same thing. A file Prettier cannot
|
|
1347
1539
|
parse is a **finding**, with its position — not a crash that costs the report on
|
|
@@ -1485,6 +1677,17 @@ asked for — a build that downloads silently is not reproducible and fails
|
|
|
1485
1677
|
strangely offline. The cache is keyed by version, so changing the pinned version
|
|
1486
1678
|
is a miss rather than a silent overwrite.
|
|
1487
1679
|
|
|
1680
|
+
**A fetched catalogue is read one system at a time.** A dependency may ship a
|
|
1681
|
+
pack per system, and the two hold the same `(type, shortcode)` addresses with
|
|
1682
|
+
different data models — `skill:awar` is a real address in both vocabularies and
|
|
1683
|
+
means two different documents. So a pack declaring `system: hm3` resolves its
|
|
1684
|
+
embedded items against the dependency's `hm3` packs and its system-neutral ones,
|
|
1685
|
+
never against another system's, exactly as it already does for this repository's
|
|
1686
|
+
own packs. What each cached pack is comes from the dependency's manifest at
|
|
1687
|
+
fetch time, so a cache filled before this rule existed is treated as incomplete
|
|
1688
|
+
and `deps fetch` refills it — the alternative is a lookup that answers with the
|
|
1689
|
+
wrong system's document and reports nothing.
|
|
1690
|
+
|
|
1488
1691
|
**`--from` is for two packages changing together.** It fills the cache from a
|
|
1489
1692
|
locally built artifact — a package zip or the directory it was built from — so a
|
|
1490
1693
|
consumer can be built against a dependency that has not shipped. Without it,
|
|
@@ -2247,32 +2450,60 @@ JSDoc.
|
|
|
2247
2450
|
|
|
2248
2451
|
## Tests
|
|
2249
2452
|
|
|
2250
|
-
The package carries its own suite
|
|
2251
|
-
|
|
2453
|
+
The package carries its own suite, so it is verifiable without any repository
|
|
2454
|
+
that consumes it:
|
|
2252
2455
|
|
|
2253
2456
|
```
|
|
2254
|
-
npm test
|
|
2255
|
-
|
|
2457
|
+
npm test # the whole suite
|
|
2458
|
+
npx vitest run tests/wikilinks.test.ts # one file
|
|
2256
2459
|
```
|
|
2257
2460
|
|
|
2258
|
-
The
|
|
2259
|
-
|
|
2260
|
-
|
|
2461
|
+
**The suite is configured from a fixture, not from this repository's root.**
|
|
2462
|
+
`vitest.config.ts` points `PACKAGE_BUILD_CONFIG` at
|
|
2463
|
+
`tests/fixtures/repo/package-build.config.yaml`, whose adjacent `package.json`
|
|
2464
|
+
is shaped like a consumer's. The Foundry package id and the system version are
|
|
2465
|
+
derived from the manifest beside the configuration, and at the root that
|
|
2466
|
+
manifest is this toolchain's own — `@heroiclands/package-build`, which is
|
|
2467
|
+
neither a Foundry package id nor a game system version, so the derivations
|
|
2468
|
+
would assert nothing.
|
|
2261
2469
|
|
|
2262
2470
|
The harness is deliberately austere: no global setup, no Foundry stubs, and no
|
|
2263
2471
|
alias onto a consuming repository's source. `tests/suite-is-self-contained.test.ts`
|
|
2264
|
-
enforces that — a test
|
|
2265
|
-
|
|
2472
|
+
enforces that — a test that reached for `globalThis.game`, imported `@src/…`, or
|
|
2473
|
+
resolved a path climbing out of the package would pass in situ and fail the
|
|
2474
|
+
moment the package was installed from npm. Before the extraction five files
|
|
2475
|
+
resolved `"../../.."` and asserted about whatever happened to be there, which
|
|
2476
|
+
was the system repository, because the package was vendored inside it (#1).
|
|
2266
2477
|
|
|
2267
2478
|
`tests/dependencies-are-declared.test.ts` guards the same failure from the
|
|
2268
|
-
shipping side.
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
install (#1557). The test
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
unimported.
|
|
2479
|
+
shipping side. This package spent its first six changes as a workspace inside
|
|
2480
|
+
the Song of Heroic Lands repository, where npm hoisted the root's
|
|
2481
|
+
`devDependencies` into the workspace root: an import this package never declared
|
|
2482
|
+
still resolved, and failed nowhere but a consumer's install (#1557). The test
|
|
2483
|
+
walks every module named by the `files` field and holds each bare specifier to
|
|
2484
|
+
one of three cases — a Node builtin, this package addressing itself, or a
|
|
2485
|
+
declared `dependency` — and checks the converse: nothing shipped may import a
|
|
2486
|
+
`devDependency`, and no declared dependency may go unimported.
|
|
2487
|
+
|
|
2488
|
+
`tests/import-needs-no-config.test.ts` is the third of the same family, and the
|
|
2489
|
+
one that keeps "resolved on first read, never at import" honest. It copies the
|
|
2490
|
+
files the package ships into a temporary directory outside this repository and
|
|
2491
|
+
imports each shipped module on its own, in a process whose environment has
|
|
2492
|
+
`PACKAGE_BUILD_CONFIG` deleted. Outside is load-bearing: the config walk climbs
|
|
2493
|
+
from the working directory and from the module's own directory, and the copy is
|
|
2494
|
+
both — so one left inside the tree could reach a configuration above it and
|
|
2495
|
+
prove nothing, which is why the first case asserts that none is reachable from
|
|
2496
|
+
the copy before the rest run. A module that hoisted a configured value to import
|
|
2497
|
+
time fails there, and only there.
|
|
2498
|
+
|
|
2499
|
+
`tests/config-from-working-directory.test.ts` describes the resolution order
|
|
2500
|
+
itself (#364). It builds the shape no unit test can fake — a repository with the
|
|
2501
|
+
toolchain installed under it, and a second checkout nested inside that
|
|
2502
|
+
repository with its own configuration and no `node_modules` — and asserts which
|
|
2503
|
+
configuration a build run in each place reads. The nested case is the one that
|
|
2504
|
+
was wrong, and it is untestable any other way: the tell of a wrong-tree build is
|
|
2505
|
+
normally an unexpected zero diff, and the sweeps that provoke this shape expect
|
|
2506
|
+
zero differences, so only "which file was read" separates the outcomes.
|
|
2276
2507
|
|
|
2277
2508
|
## Releasing
|
|
2278
2509
|
|
package/README.md
CHANGED
|
@@ -208,6 +208,11 @@ packageBuild:
|
|
|
208
208
|
run: [npx, cypress, run]
|
|
209
209
|
open: [npx, cypress, open]
|
|
210
210
|
|
|
211
|
+
# Where that suite writes its results. Optional, and what it buys is the
|
|
212
|
+
# difference between "the suite ran and passed" and "the suite did not
|
|
213
|
+
# run" — a headless run that writes nothing here is reported as a failure.
|
|
214
|
+
results: [cypress/results]
|
|
215
|
+
|
|
211
216
|
# What the fast loop can rebuild, in the order it must be built: the
|
|
212
217
|
# bundler empties the stage, so it goes first. `recreate` marks a target
|
|
213
218
|
# whose output Foundry reads once, at world launch.
|
|
@@ -483,6 +488,35 @@ repository can hold without rotting.
|
|
|
483
488
|
A green sweep is what licenses moving `compatibility.verified` to that build. A
|
|
484
489
|
red one is the early warning the sweep exists to produce.
|
|
485
490
|
|
|
491
|
+
### A run that executed nothing is not a pass
|
|
492
|
+
|
|
493
|
+
Because a sweep is evidence, the exit status of the suite is not enough on its
|
|
494
|
+
own to call a run green. Every way of stopping a runner before it starts — a
|
|
495
|
+
corrupt install, a missing browser, a killed process, an `npm ci` racing the
|
|
496
|
+
run and taking `node_modules` with it — produces a run that executed nothing,
|
|
497
|
+
and a harness that reports that as 0 makes the evidence unfalsifiable in the
|
|
498
|
+
one direction that matters (#153).
|
|
499
|
+
|
|
500
|
+
So the suite is bracketed rather than trusted:
|
|
501
|
+
|
|
502
|
+
| Check | What it catches |
|
|
503
|
+
| ----------------------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
504
|
+
| Before, the command resolves | The runner is not installed. The error names it, before a container is stood up and a world seeded. |
|
|
505
|
+
| A runner is read through | `npx cypress run` is checked for **`cypress`**, not `npx` — `npx` is never missing, so it answers nothing. |
|
|
506
|
+
| After, the command still does | The runner went away mid-run. That is a run that cannot have finished, whatever it exited with. |
|
|
507
|
+
| After, results are newer | The suite started and produced nothing. Requires `packageBuild.e2e.results`. |
|
|
508
|
+
|
|
509
|
+
`results` names one path or a list of them, relative to the repository root.
|
|
510
|
+
Existence is not the test — a directory the _previous_ run left behind exists,
|
|
511
|
+
and reading that as evidence would make the check agree with exactly what it
|
|
512
|
+
was built to catch. What counts is a file modified since the spawn.
|
|
513
|
+
|
|
514
|
+
Two things it deliberately does not do. It only ever makes a verdict **worse**:
|
|
515
|
+
a suite that failed keeps its own status, so there is no second path by which
|
|
516
|
+
the harness can report a result that did not happen. And it does not apply to
|
|
517
|
+
`open`, where a person decides what to execute and when to close the runner —
|
|
518
|
+
"it wrote no results" describes that session rather than faulting it.
|
|
519
|
+
|
|
486
520
|
### Waiting for a world, not for a port
|
|
487
521
|
|
|
488
522
|
Foundry answers on its port long before a world is serving. A suite started at
|
|
@@ -575,13 +609,18 @@ npm test
|
|
|
575
609
|
```
|
|
576
610
|
|
|
577
611
|
Plain `vitest`, no setup file and no aliases: everything here is ESM over Node
|
|
578
|
-
built-ins and
|
|
579
|
-
would let something reach for one.
|
|
612
|
+
built-ins and what `dependencies` declares, and a harness that offered a Foundry
|
|
613
|
+
global would let something reach for one. (This said "three dependencies" while
|
|
614
|
+
it described the packaging half alone; absorbing the content half at 3.0.0
|
|
615
|
+
brought the compendium, Markdown and SQL toolchains with it. The number is left
|
|
616
|
+
out rather than restated, because the next dependency would make a restated one
|
|
617
|
+
wrong again.)
|
|
580
618
|
|
|
581
619
|
`tests/dependencies-are-declared.test.ts` is the guard an extraction most needs
|
|
582
620
|
— every bare specifier in a shipped file must be a builtin, this package, or a
|
|
583
|
-
declared `dependency`. Inside a workspace
|
|
584
|
-
installed from npm it fails on
|
|
621
|
+
declared `dependency`. Inside a workspace, which this package was for its first
|
|
622
|
+
six changes, a missing declaration is invisible; installed from npm it fails on
|
|
623
|
+
the first import.
|
|
585
624
|
|
|
586
625
|
## Releasing
|
|
587
626
|
|
package/bin/content-build.mjs
CHANGED
|
@@ -79,7 +79,9 @@ import {
|
|
|
79
79
|
} from "../engine/metadata-index.mjs";
|
|
80
80
|
import { renderItemFieldReference } from "../engine/field-reference.mjs";
|
|
81
81
|
import { lintContentTree } from "../engine/content-lint.mjs";
|
|
82
|
-
import {
|
|
82
|
+
import { lintContentCharset } from "../engine/content-charset.mjs";
|
|
83
|
+
import { lintContentIcons } from "../engine/content-icons.mjs";
|
|
84
|
+
import { declaredSystems, lintFrontmatter, systemBlocksFor } from "../engine/frontmatter-lint.mjs";
|
|
83
85
|
import { loadContentFormat } from "../engine/content-format.mjs";
|
|
84
86
|
import {
|
|
85
87
|
checkDeclaredFields,
|
|
@@ -105,7 +107,7 @@ import { HM3_ITEM_FIELDS } from "../hm3/item-fields.mjs";
|
|
|
105
107
|
import { ENGINE_NOTE_SCHEMAS } from "../engine/note-schemas.mjs";
|
|
106
108
|
import { schemaSubtypeOf } from "../engine/subtype-registry.mjs";
|
|
107
109
|
import { NOTE_VOCABULARY } from "../engine/note-vocabulary.mjs";
|
|
108
|
-
import { checkFormatting, lintMarkdown } from "../engine/prose-lint.mjs";
|
|
110
|
+
import { checkFormatting, checkPrettierConventions, lintMarkdown } from "../engine/prose-lint.mjs";
|
|
109
111
|
import {
|
|
110
112
|
authoredFrontmatter,
|
|
111
113
|
emitContentIndex,
|
|
@@ -136,7 +138,7 @@ import {
|
|
|
136
138
|
locateAddressFinding,
|
|
137
139
|
addressFindingMessage,
|
|
138
140
|
} from "../engine/address-diff.mjs";
|
|
139
|
-
import { itemPackJsonDirs } from "../engine/generate.mjs";
|
|
141
|
+
import { emittedArtFor, itemPackJsonDirs } from "../engine/generate.mjs";
|
|
140
142
|
|
|
141
143
|
/**
|
|
142
144
|
* The packs `unpack` extracts.
|
|
@@ -787,6 +789,38 @@ function lintCommand() {
|
|
|
787
789
|
skipDirectories: config.skipDirectories,
|
|
788
790
|
}),
|
|
789
791
|
});
|
|
792
|
+
// Which system blocks this tree carries, derived from what it
|
|
793
|
+
// declares it ships for (#58). Read before the lint so the
|
|
794
|
+
// systems it will *not* check can be said out loud below.
|
|
795
|
+
// `schemaSystem` names whose vocabulary the `schemas` below
|
|
796
|
+
// are. They are `sohl/note-schemas.mjs`, imported here
|
|
797
|
+
// unconditionally, so this states a fact about this file rather
|
|
798
|
+
// than introducing one — and it is what lets a type no item
|
|
799
|
+
// registry declares, `being` above all, be checked at all.
|
|
800
|
+
const systemBlocks = systemBlocksFor(config, { schemaSystem: "sohl" });
|
|
801
|
+
const uncheckedSystems = declaredSystems(config).filter(
|
|
802
|
+
(system) => !(system in systemBlocks),
|
|
803
|
+
);
|
|
804
|
+
if (uncheckedSystems.length) {
|
|
805
|
+
// Said out loud for the reason the missing schema artifact
|
|
806
|
+
// is: a check that quietly does nothing is indistinguishable
|
|
807
|
+
// from one that passed, and the thing going unchecked here
|
|
808
|
+
// is a closed region whose unknown keys the compiler drops
|
|
809
|
+
// without a word. `harn-ensemble` is the tree this names —
|
|
810
|
+
// two systems declared through its packs and an
|
|
811
|
+
// `itemBuilders` registry for neither.
|
|
812
|
+
log.info(
|
|
813
|
+
`No \`itemBuilders\` registry declares the vocabulary of ` +
|
|
814
|
+
`${uncheckedSystems.map((s) => `\`${s}\``).join(" or ")}, so ` +
|
|
815
|
+
`${
|
|
816
|
+
uncheckedSystems.length === 1 ?
|
|
817
|
+
"that system's block is"
|
|
818
|
+
: "those systems' blocks are"
|
|
819
|
+
} unchecked — a key inside one is discarded at ` +
|
|
820
|
+
`compile with no warning. Declare ` +
|
|
821
|
+
`\`itemBuilders: [${declaredSystems(config).join(", ")}]\`.`,
|
|
822
|
+
);
|
|
823
|
+
}
|
|
790
824
|
const frontmatter = lintFrontmatter(index, {
|
|
791
825
|
schemas: { ...ENGINE_NOTE_SCHEMAS, ...NOTE_SCHEMAS },
|
|
792
826
|
// The closed frontmatter regions (#128). Passed in rather
|
|
@@ -799,6 +833,19 @@ function lintCommand() {
|
|
|
799
833
|
// the compile asks the map for whichever pack it is
|
|
800
834
|
// writing, and a companion is a pack it writes.
|
|
801
835
|
packs: config.packDirectories,
|
|
836
|
+
// What art each type actually reaches its document through,
|
|
837
|
+
// asked of the passes rather than listed here (#349). This
|
|
838
|
+
// is the one place that decides what a tree is held to, so
|
|
839
|
+
// it is where the derivation is handed over — the linter
|
|
840
|
+
// states no list of iconless types of its own.
|
|
841
|
+
emittedArt: emittedArtFor,
|
|
842
|
+
// Handed over for the same reason the vocabulary is: the
|
|
843
|
+
// linter checks the blocks it is given and names no system
|
|
844
|
+
// itself. Until this it was given none, so every tree was
|
|
845
|
+
// held to a `sohl:` whatever system it ships for — and an
|
|
846
|
+
// `hm3:` block went unread, which is a key discarded at
|
|
847
|
+
// compile in silence.
|
|
848
|
+
systems: systemBlocks,
|
|
802
849
|
references: argv.references,
|
|
803
850
|
});
|
|
804
851
|
|
|
@@ -877,10 +924,33 @@ function lintCommand() {
|
|
|
877
924
|
}
|
|
878
925
|
}
|
|
879
926
|
|
|
927
|
+
// The charset the tree is held to, so a book can pick its face
|
|
928
|
+
// (#377). Run over the raw files rather than the parsed notes:
|
|
929
|
+
// the subject is every character authored, including the ones
|
|
930
|
+
// that stopped a note parsing at all.
|
|
931
|
+
const charset = lintContentCharset(root, {
|
|
932
|
+
skipDirectories: config.skipDirectories,
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
// The names the charset check leaves room for (#378). An icon
|
|
936
|
+
// the registry does not declare renders as its own literal
|
|
937
|
+
// text, which is visible but easy to publish, so it is reported
|
|
938
|
+
// here rather than left for a reader to notice.
|
|
939
|
+
//
|
|
940
|
+
// The shipped registry, with no per-package override yet: an
|
|
941
|
+
// `icons:` configuration key is a change to the configuration
|
|
942
|
+
// contract and belongs with its own validation, rather than
|
|
943
|
+
// being read here before anything declares it.
|
|
944
|
+
const icons = lintContentIcons(root, {
|
|
945
|
+
skipDirectories: config.skipDirectories,
|
|
946
|
+
});
|
|
947
|
+
|
|
880
948
|
const findings = [
|
|
881
949
|
...addresses.findings,
|
|
882
950
|
...frontmatter.findings,
|
|
883
951
|
...schemaFindings,
|
|
952
|
+
...charset.findings,
|
|
953
|
+
...icons.findings,
|
|
884
954
|
];
|
|
885
955
|
// Only an **error** fails the run. Every finding was an error
|
|
886
956
|
// until #142, so this changed nothing on the day it landed —
|
|
@@ -928,6 +998,13 @@ function lintCommand() {
|
|
|
928
998
|
* which paths a repository skips is knowledge about that repository's layout,
|
|
929
999
|
* and it stays there.
|
|
930
1000
|
*
|
|
1001
|
+
* It is a default that **says when it is not in force**, though. Every run first
|
|
1002
|
+
* reports, as warnings, each shared convention the repository's own
|
|
1003
|
+
* configuration resolves differently — or that it declares no configuration at
|
|
1004
|
+
* all, which guarantees an editor and a bare `npx prettier` disagree with this
|
|
1005
|
+
* command about the same tree (#133). Nothing there fails the run: the point is
|
|
1006
|
+
* that a local choice is deliberate rather than silent.
|
|
1007
|
+
*
|
|
931
1008
|
* @returns {object} The yargs command module.
|
|
932
1009
|
*/
|
|
933
1010
|
// eslint-disable-next-line
|
|
@@ -961,6 +1038,20 @@ function formatCommand() {
|
|
|
961
1038
|
return;
|
|
962
1039
|
}
|
|
963
1040
|
const root = process.cwd();
|
|
1041
|
+
// Before the per-file report, because it is the context for it:
|
|
1042
|
+
// which rules this run applied, and whether anything else in
|
|
1043
|
+
// the repository applies the same ones (#133). Warnings only —
|
|
1044
|
+
// a consumer's config wins by design, so none of this touches
|
|
1045
|
+
// the exit code.
|
|
1046
|
+
const conventions = await checkPrettierConventions(root);
|
|
1047
|
+
for (const finding of conventions.findings) emitDiagnostic(finding);
|
|
1048
|
+
if (conventions.findings.length && conventions.configFile) {
|
|
1049
|
+
log.warn(
|
|
1050
|
+
`${conventions.findings.length} shared Prettier convention(s) are not ` +
|
|
1051
|
+
"what this repository resolves; a deliberate local choice wins on " +
|
|
1052
|
+
"purpose, so this is a warning and not a failure.",
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
964
1055
|
const { findings, checked, written } = await checkFormatting(root, {
|
|
965
1056
|
paths: argv.paths,
|
|
966
1057
|
write: argv.write,
|
package/config.mjs
CHANGED
|
@@ -126,7 +126,7 @@ const CONTAINER_KEYS = ["image", "name", "stages"];
|
|
|
126
126
|
*/
|
|
127
127
|
const CONTAINER_NAME = /^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/;
|
|
128
128
|
const CONTAINER_STAGE_KEYS = ["port", "world", "version"];
|
|
129
|
-
const E2E_KEYS = ["stage", "suite", "build", "world", "gm", "documents"];
|
|
129
|
+
const E2E_KEYS = ["stage", "suite", "results", "build", "world", "gm", "documents"];
|
|
130
130
|
const E2E_SUITE_KEYS = ["run", "open"];
|
|
131
131
|
const E2E_WORLD_KEYS = ["id", "title", "description"];
|
|
132
132
|
const E2E_GM_KEYS = ["name", "password"];
|
|
@@ -557,6 +557,8 @@ function normalizeExceptions(value, field, where) {
|
|
|
557
557
|
* @property {string} e2eStage Which stage the suite runs against.
|
|
558
558
|
* @property {Readonly<{run: readonly string[], open: readonly string[]|null}>|null} e2eSuite
|
|
559
559
|
* What to run against the served world; `null` when the repository has none.
|
|
560
|
+
* @property {readonly string[]} e2eResults Where the suite writes its results,
|
|
561
|
+
* so a run that wrote none is not mistaken for one that passed.
|
|
560
562
|
* @property {Readonly<Record<string, Readonly<{script: string, recreate: boolean}>>>} e2eBuild
|
|
561
563
|
* Build targets the fast loop can produce, in declaration order.
|
|
562
564
|
* @property {Readonly<Record<string, string>>} e2eWorld Declared world identity.
|
|
@@ -798,6 +800,12 @@ export function resolvePackageBuildConfig(shared) {
|
|
|
798
800
|
"test"
|
|
799
801
|
: requireNonEmptyString(e2eInput.stage, "packageBuild.e2e.stage"),
|
|
800
802
|
e2eSuite: normalizeE2ESuite(e2eInput.suite),
|
|
803
|
+
// Declaring nothing keeps the old contract — the suite's exit status is
|
|
804
|
+
// taken at its word. Declaring where results land is what lets the
|
|
805
|
+
// harness tell "the suite ran and passed" from "the suite did not run"
|
|
806
|
+
// (#153); a repository that wants that distinction has to say where to
|
|
807
|
+
// look for it, because the harness does not know what the suite is.
|
|
808
|
+
e2eResults: normalizeGlobs(e2eInput.results, [], "packageBuild.e2e.results"),
|
|
801
809
|
e2eBuild,
|
|
802
810
|
e2eWorld: normalizeStringMap(e2eInput.world, "packageBuild.e2e.world", E2E_WORLD_KEYS),
|
|
803
811
|
e2eGm: normalizeStringMap(e2eInput.gm, "packageBuild.e2e.gm", E2E_GM_KEYS),
|