@heroiclands/package-build 8.0.0 → 8.1.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 +119 -0
- package/engine/schema-extract.mjs +65 -12
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,124 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 8.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 333c340: Follow a spread of an imported schema function, and refuse a computed field name.
|
|
8
|
+
|
|
9
|
+
Two defects in `package-build schema`, both found by running it against real
|
|
10
|
+
content rather than by reading it.
|
|
11
|
+
|
|
12
|
+
**A shared base schema spread from another file was dropped, in silence.** A
|
|
13
|
+
concrete DataModel spreads a shared builder by name — `...defineSohlDataSchema()`
|
|
14
|
+
— and the resolver looked for that function only in the file doing the
|
|
15
|
+
spreading. When it was imported, the lookup found nothing and the spread
|
|
16
|
+
contributed nothing, with no error: a spread of a missing function read exactly
|
|
17
|
+
like a spread of an empty one.
|
|
18
|
+
|
|
19
|
+
The effect was not small. Every SoHL Item and Actor subtype lost `shortcode`,
|
|
20
|
+
`actionDefs`, `lastRun` and `scheduledActions` from the published schema. So
|
|
21
|
+
content correctly authoring `system.shortcode` — which SoHL requires to be
|
|
22
|
+
unique per `(type, shortcode)` on an actor, and which content therefore sets
|
|
23
|
+
deliberately — was reported as emitting a field no DataModel declares. The check
|
|
24
|
+
was accusing the content of the reader's own blind spot, which is worse than not
|
|
25
|
+
checking: it is a false accusation delivered with the same confidence as a true
|
|
26
|
+
one.
|
|
27
|
+
|
|
28
|
+
An imported spread is now resolved through the import, with a same-file
|
|
29
|
+
definition still taking precedence.
|
|
30
|
+
|
|
31
|
+
**A computed field name is now refused rather than published as source text.**
|
|
32
|
+
`[`${name}Date`]: worldTimeDateField()` takes its real name from an argument
|
|
33
|
+
this reader does not evaluate, and the previous behaviour handed back the source
|
|
34
|
+
text — putting a field called ``[`${name}Date`]`` into the schema. That field
|
|
35
|
+
matches nothing any builder could emit: absent for checking purposes while
|
|
36
|
+
looking present, and permanently reported as unemitted.
|
|
37
|
+
|
|
38
|
+
It now stops, naming the file and the key:
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
temporal-fields.ts declares a schema field with a computed name,
|
|
42
|
+
`[`${name}DurationFormula`]`, whose value depends on an argument this reader
|
|
43
|
+
does not evaluate. Write the keys out so the published schema can name them.
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Stopping is the same stance `compareFields` already takes on an artifact of the
|
|
47
|
+
wrong version: the schema is a contract other repositories read, so a contract
|
|
48
|
+
this cannot state is worth failing for rather than approximating. The fix
|
|
49
|
+
belongs at the source, where the names are actually decided.
|
|
50
|
+
|
|
51
|
+
**Bump**
|
|
52
|
+
|
|
53
|
+
_Minor._ Repositories whose schemas spread an imported builder will publish more
|
|
54
|
+
fields than before — which is the correction. A repository using computed field
|
|
55
|
+
names now fails where it previously produced a wrong artifact; none does today
|
|
56
|
+
except the one this was found on, and that is being fixed at the source.
|
|
57
|
+
|
|
58
|
+
### Patch Changes
|
|
59
|
+
|
|
60
|
+
- 2319a56: Bump `markdown-it` from 15.0.0 to 15.0.1 (#121).
|
|
61
|
+
|
|
62
|
+
The surface is unchanged from the 15.0.0 adoption: one constructor,
|
|
63
|
+
`markdownit({ html: true })` in `engine/helpers.mjs`, and three `md.render()`
|
|
64
|
+
call sites — `engine/helpers.mjs`, `engine/journals.mjs` and `sohl/actors.mjs`.
|
|
65
|
+
No plugin is installed, no renderer rule is overridden, and `linkify` is left at
|
|
66
|
+
its default `false`. That last fact decides most of this release.
|
|
67
|
+
|
|
68
|
+
**Two of the five changes cannot reach a build that never linkifies.**
|
|
69
|
+
|
|
70
|
+
| Release note | Reaches here? |
|
|
71
|
+
| --------------------------------------------------------- | ---------------------------------------------------------------- |
|
|
72
|
+
| _Security:_ quadratic complexity replacing fuzzy links | No — `linkify` is `false`, so the fuzzy-link replacer never runs |
|
|
73
|
+
| _Security:_ quadratic complexity in scheme backscan | No — same; the rule is the inline **linkify** rule |
|
|
74
|
+
| Preserve brackets around IPv6 literals in `normalizeLink` | Yes, in principle — no content in any consumer writes one |
|
|
75
|
+
| Preserve spaces in all-space code spans (#1180) | Yes, for runs of three or more spaces only |
|
|
76
|
+
| Code spans after unclosed link/image labels (#1201) | Yes, for an unclosed `[` followed by an odd-length backtick run |
|
|
77
|
+
|
|
78
|
+
Calling the first two "security" fixes is upstream's framing and the honest
|
|
79
|
+
reading of it here is that this package was never exposed: both are complexity
|
|
80
|
+
bounds on `linkify-it` code paths that are unreachable with the linkifier off.
|
|
81
|
+
Taking the bump is hygiene, not remediation.
|
|
82
|
+
|
|
83
|
+
**The three real fixes were measured, not assumed.** Both versions were rendered
|
|
84
|
+
through `markdownit({ html: true })` — this package's exact configuration — and
|
|
85
|
+
compared:
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
a ` ` b 15.0.0 → <p>a <code> </code> b</p> 15.0.1 → <p>a <code> </code> b</p>
|
|
89
|
+
[`a`b` 15.0.0 → <p>[`a`b`</p> 15.0.1 → <p>[<code>a</code>b`</p>
|
|
90
|
+
[x](http://[::1]/)
|
|
91
|
+
15.0.0 → href="http://%5B::1%5D/" 15.0.1 → href="http://[::1]/"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Each has a narrow trigger. The code-span fix moves output only at three spaces
|
|
95
|
+
or more — 15.0.0 stripped one space from each end of an all-space span, so one-
|
|
96
|
+
and two-space spans were already correct and stay byte-identical. The unclosed-
|
|
97
|
+
label fix needs the backtick run to be **odd**: `[Sword `hp`and`ac`` renders
|
|
98
|
+
the same on both, because its four backticks close. And the IPv6 fix only fires
|
|
99
|
+
on a bracketed address literal in a link, image or autolink destination, which
|
|
100
|
+
15.0.0 percent-encoded into a host no browser resolves.
|
|
101
|
+
|
|
102
|
+
**Verified against every consumer, and against a search rather than a guess.**
|
|
103
|
+
All 14,995 `.md` files across the six HeroicLands content repositories — `sohl`,
|
|
104
|
+
`hm3`, `thalorna`, `kethira`, `harnensemble` and `harnadventures` — were parsed
|
|
105
|
+
the way `parseMarkdownFile` parses them and rendered through both versions.
|
|
106
|
+
Every one is byte-identical. Scanning the same corpus explains why rather than
|
|
107
|
+
leaving it to luck: zero files contain an IPv6 URL, zero contain an all-space
|
|
108
|
+
code span of three or more spaces, and the 109 files that do use `` ` ` `` use
|
|
109
|
+
the one-space form the fix does not touch. Beyond the corpus, an exhaustive
|
|
110
|
+
render of all 488,280 strings up to eight characters over `[`, `]`, `` ` ``,
|
|
111
|
+
`x` and `!` — the alphabet the label-and-backtick fix is about — found the
|
|
112
|
+
divergence set to be exactly the shapes above and nothing else. The repository's
|
|
113
|
+
1,713 tests pass unchanged.
|
|
114
|
+
|
|
115
|
+
**Bump**
|
|
116
|
+
|
|
117
|
+
_Patch, not minor._ No export, option, or emitted document changes shape. The
|
|
118
|
+
only behaviour that moves is markdown that was rendered wrongly before, on
|
|
119
|
+
constructs no consumer writes, and the two changes advertised as security fixes
|
|
120
|
+
are unreachable from this configuration.
|
|
121
|
+
|
|
3
122
|
## 8.0.0
|
|
4
123
|
|
|
5
124
|
### Major Changes
|
|
@@ -95,6 +95,37 @@ function propName(name) {
|
|
|
95
95
|
return name.getText();
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
/**
|
|
99
|
+
* A *field* name, which unlike a property name elsewhere must be knowable.
|
|
100
|
+
*
|
|
101
|
+
* A computed key — `[`${name}Date`]: worldTimeDateField()` — depends on an
|
|
102
|
+
* argument this reader does not evaluate, so its real name is not in the file.
|
|
103
|
+
* {@link propName} would hand back the source text, and a field called
|
|
104
|
+
* ``[`${name}Date`]`` matches nothing a builder could ever emit: it is absent
|
|
105
|
+
* from the schema for checking purposes while *looking* present, and it shows
|
|
106
|
+
* up as permanently unemitted noise.
|
|
107
|
+
*
|
|
108
|
+
* So this refuses rather than guessing. The schema is a contract other
|
|
109
|
+
* repositories read, and a contract it cannot state is worth stopping for —
|
|
110
|
+
* the same reason `compareFields` refuses an artifact of the wrong version
|
|
111
|
+
* instead of resolving it anyway. Writing the keys out fixes it at the source,
|
|
112
|
+
* where the names are actually decided.
|
|
113
|
+
*
|
|
114
|
+
* @param {ts.PropertyName} name - The property name node.
|
|
115
|
+
* @param {string} file - For the message.
|
|
116
|
+
* @returns {string} The literal field name.
|
|
117
|
+
* @throws {Error} When the name is computed.
|
|
118
|
+
*/
|
|
119
|
+
function fieldName(name, file) {
|
|
120
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name)) return name.text;
|
|
121
|
+
throw new Error(
|
|
122
|
+
`${path.basename(file)} declares a schema field with a computed name, ` +
|
|
123
|
+
`\`${name.getText()}\`, whose value depends on an argument this ` +
|
|
124
|
+
`reader does not evaluate. Write the keys out so the published ` +
|
|
125
|
+
`schema can name them.`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
98
129
|
/**
|
|
99
130
|
* The `subtype: ClassName` entries of a registry object literal.
|
|
100
131
|
*
|
|
@@ -424,16 +455,16 @@ function isSchemaField(expr) {
|
|
|
424
455
|
}
|
|
425
456
|
|
|
426
457
|
/** The keys of a `new SchemaField({ … })`, recursively dotted. */
|
|
427
|
-
function nestedKeysOf(expr) {
|
|
458
|
+
function nestedKeysOf(expr, file) {
|
|
428
459
|
if (!isSchemaField(expr)) return [];
|
|
429
460
|
const arg = expr.arguments?.[0];
|
|
430
461
|
if (!arg || !ts.isObjectLiteralExpression(arg)) return [];
|
|
431
462
|
const out = [];
|
|
432
463
|
for (const p of arg.properties) {
|
|
433
464
|
if (!ts.isPropertyAssignment(p)) continue;
|
|
434
|
-
const key =
|
|
465
|
+
const key = fieldName(p.name, file);
|
|
435
466
|
out.push(key);
|
|
436
|
-
for (const child of nestedKeysOf(p.initializer)) {
|
|
467
|
+
for (const child of nestedKeysOf(p.initializer, file)) {
|
|
437
468
|
out.push(`${key}.${child}`);
|
|
438
469
|
}
|
|
439
470
|
}
|
|
@@ -445,7 +476,7 @@ function nestedKeysOf(expr) {
|
|
|
445
476
|
*
|
|
446
477
|
* @returns {{own: string[], edges: object[]}}
|
|
447
478
|
*/
|
|
448
|
-
function readLiteral(literal) {
|
|
479
|
+
function readLiteral(literal, file) {
|
|
449
480
|
const own = [];
|
|
450
481
|
const edges = [];
|
|
451
482
|
for (const p of literal.properties) {
|
|
@@ -455,9 +486,9 @@ function readLiteral(literal) {
|
|
|
455
486
|
continue;
|
|
456
487
|
}
|
|
457
488
|
if (!ts.isPropertyAssignment(p)) continue;
|
|
458
|
-
const key =
|
|
489
|
+
const key = fieldName(p.name, file);
|
|
459
490
|
own.push(key);
|
|
460
|
-
for (const child of nestedKeysOf(p.initializer)) {
|
|
491
|
+
for (const child of nestedKeysOf(p.initializer, file)) {
|
|
461
492
|
own.push(`${key}.${child}`);
|
|
462
493
|
}
|
|
463
494
|
}
|
|
@@ -505,7 +536,7 @@ export function fieldsForClass({ file, className, aliases, cache, rootDir }) {
|
|
|
505
536
|
|
|
506
537
|
/** Walk a schema literal, following the spreads inside it. */
|
|
507
538
|
const walkLiteral = (located, literal, into, superName) => {
|
|
508
|
-
const { own: fields, edges } = readLiteral(literal);
|
|
539
|
+
const { own: fields, edges } = readLiteral(literal, located.file);
|
|
509
540
|
into.push(...fields);
|
|
510
541
|
for (const edge of edges) followEdge(located, edge, superName);
|
|
511
542
|
};
|
|
@@ -516,12 +547,34 @@ export function fieldsForClass({ file, className, aliases, cache, rootDir }) {
|
|
|
516
547
|
const key = `local:${located.file}:${edge.name}`;
|
|
517
548
|
if (seen.has(key)) return;
|
|
518
549
|
seen.add(key);
|
|
519
|
-
// A
|
|
520
|
-
//
|
|
521
|
-
//
|
|
522
|
-
//
|
|
550
|
+
// A spread of a schema-building function is the parent's
|
|
551
|
+
// contribution — a shared `defineXDataSchema()` is where the common
|
|
552
|
+
// fields come from — so it lands in `inherited` whichever file it
|
|
553
|
+
// is written in.
|
|
523
554
|
const lit = literalReturnedBy(located.src, edge.name);
|
|
524
|
-
if (lit)
|
|
555
|
+
if (lit) {
|
|
556
|
+
walkLiteral(located, lit, inherited, superName);
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
// Not declared here, so it was imported. Following it matters more
|
|
560
|
+
// than it looks: the shared base schema is spread by name from the
|
|
561
|
+
// file that exports it, and resolving only same-file functions
|
|
562
|
+
// dropped it **entirely and in silence** — every SoHL subtype lost
|
|
563
|
+
// `shortcode` and `actionDefs`, so content correctly authoring
|
|
564
|
+
// `system.shortcode` was reported as undeclared. A spread that
|
|
565
|
+
// resolves to nothing must not read as a spread of nothing.
|
|
566
|
+
const from = importSourceOf(located.src, edge.name, aliases);
|
|
567
|
+
if (!from) return;
|
|
568
|
+
const importedSrc = parse(from, cache);
|
|
569
|
+
const importedLit = literalReturnedBy(importedSrc, edge.name);
|
|
570
|
+
if (importedLit) {
|
|
571
|
+
walkLiteral(
|
|
572
|
+
{ file: from, src: importedSrc },
|
|
573
|
+
importedLit,
|
|
574
|
+
inherited,
|
|
575
|
+
superName,
|
|
576
|
+
);
|
|
577
|
+
}
|
|
525
578
|
return;
|
|
526
579
|
}
|
|
527
580
|
const name = edge.kind === "super" ? superName : edge.name;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.1.0",
|
|
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",
|