@heroiclands/package-build 22.0.1 → 22.0.2

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 CHANGED
@@ -1,5 +1,15 @@
1
1
  # @heroiclands/package-build
2
2
 
3
+ ## 22.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 83fa58e: **A record states `attribution` and `license`, or it is a finding.** The nearest
8
+ provenance record is the whole answer and is never merged with the one above it,
9
+ so a key left out is not inherited — it is absent, and a record stating neither
10
+ would leave every file beneath it with no rights holder and no terms. `source`,
11
+ `ai` and `notes` stay optional, because blank is a truthful answer for each.
12
+
3
13
  ## 22.0.1
4
14
 
5
15
  ### Patch Changes
package/docs/api.md CHANGED
@@ -567,13 +567,14 @@ The asset types — `icon`, `image` and `audio` — and the three roots they are
567
567
 
568
568
  The asset record: one line of the content index per addressable file. The record is emitted by the package holding the bytes, so its `path` is that package's path and each consumer joins its own root onto it — Foundry its data directory, the website its CDN prefix, the book its asset base.
569
569
 
570
- | Export | Signature | Returns | Use it when |
571
- | --------------------------- | --------------------------------------------------------------- | ----------------------------- | --------------------------------------------------------------------------------- |
572
- | `ASSET_RECORD_FIELDS` | `const ASSET_RECORD_FIELDS` | `readonly AssetRecordField[]` | reading what an `asset` block carries, and where each field comes from |
573
- | `PROVENANCE_KEYS` | `const PROVENANCE_KEYS` | `ReadonlySet<string>` | validating a provenance file, whose keys are derived from the record's own fields |
574
- | `PROVENANCE_FILE` | `const PROVENANCE_FILE` | `string` `provenance.yaml` | naming the file a directory records provenance for its subtree in |
575
- | `PROVENANCE_SIDECAR_SUFFIX` | `const PROVENANCE_SIDECAR_SUFFIX` | `string` — `.yaml` | naming a per-file record, which replaces an inherited one wholesale |
576
- | `collectAssetRecords` | `collectAssetRecords(assetsBase, { contentPackage, problems })` | `Array<Record<string, any>>` | reading a package's asset roots into index records, without walking its notes |
570
+ | Export | Signature | Returns | Use it when |
571
+ | --------------------------- | --------------------------------------------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
572
+ | `ASSET_RECORD_FIELDS` | `const ASSET_RECORD_FIELDS` | `readonly AssetRecordField[]` | reading what an `asset` block carries, and where each field comes from |
573
+ | `PROVENANCE_KEYS` | `const PROVENANCE_KEYS` | `ReadonlySet<string>` | validating a provenance file, whose keys are derived from the record's own fields |
574
+ | `REQUIRED_PROVENANCE_KEYS` | `const REQUIRED_PROVENANCE_KEYS` | `ReadonlySet<string>` | the keys a provenance file must state — a record resolves wholesale, so one omitting them leaves every file it covers with no rights holder or no terms |
575
+ | `PROVENANCE_FILE` | `const PROVENANCE_FILE` | `string` — `provenance.yaml` | naming the file a directory records provenance for its subtree in |
576
+ | `PROVENANCE_SIDECAR_SUFFIX` | `const PROVENANCE_SIDECAR_SUFFIX` | `string` — `.yaml` | naming a per-file record, which replaces an inherited one wholesale |
577
+ | `collectAssetRecords` | `collectAssetRecords(assetsBase, { contentPackage, problems })` | `Array<Record<string, any>>` | reading a package's asset roots into index records, without walking its notes |
577
578
 
578
579
  ### `engine.packages`
579
580
 
@@ -1508,6 +1508,17 @@ One address resolves its record in this order:
1508
1508
  3. Otherwise nothing, and the provenance fields are blank. A package that
1509
1509
  records no attribution is a fact to state, not a walk to fail.
1510
1510
 
1511
+ **The nearest record is the whole answer.** It is never merged with the one
1512
+ above it, so a record states every key it means to claim — which is what lets a
1513
+ reader open one file and know what the tree beneath it carries, instead of
1514
+ resolving a chain by hand.
1515
+
1516
+ That is also why `attribution` and `license` are **required in any record that
1517
+ exists**: a key left out is not inherited from above, it is simply absent, so a
1518
+ record stating neither leaves every file it covers with no rights holder and no
1519
+ terms. Omitting one is a finding. `source`, `ai` and `notes` stay optional,
1520
+ because a blank is a truthful answer for each of them.
1521
+
1511
1522
  #### A font is not an asset
1512
1523
 
1513
1524
  `assets/fonts` is not a root, and a font has no address.
@@ -88,6 +88,10 @@ export const PROVENANCE_SIDECAR_SUFFIX = ".yaml";
88
88
  * @property {string} name - The key inside `asset`.
89
89
  * @property {"walk"|"provenance"} from - Where the value comes from: the walk
90
90
  * itself, or the provenance record resolved for the address.
91
+ * @property {boolean} [required] - Whether a provenance record that exists must
92
+ * state this key. Omitting one is a finding rather than a blank, because a
93
+ * record resolves wholesale: the nearest one is the whole answer, so a key it
94
+ * leaves out is not inherited from above but simply absent.
91
95
  * @property {string} describe - One line, for the author-facing reference.
92
96
  */
93
97
 
@@ -119,6 +123,7 @@ export const ASSET_RECORD_FIELDS = Object.freeze([
119
123
  Object.freeze({
120
124
  name: "attribution",
121
125
  from: "provenance",
126
+ required: true,
122
127
  describe: "The person holding the rights, to whom attribution is legally due.",
123
128
  }),
124
129
  Object.freeze({
@@ -134,6 +139,7 @@ export const ASSET_RECORD_FIELDS = Object.freeze([
134
139
  Object.freeze({
135
140
  name: "license",
136
141
  from: "provenance",
142
+ required: true,
137
143
  describe: "The licence it is used under — an SPDX identifier, or terms.",
138
144
  }),
139
145
  Object.freeze({
@@ -159,6 +165,22 @@ export const PROVENANCE_KEYS = Object.freeze(
159
165
  ),
160
166
  );
161
167
 
168
+ /**
169
+ * The keys a provenance file must state.
170
+ *
171
+ * Derived from {@link ASSET_RECORD_FIELDS} for the same reason the key set is:
172
+ * the rule and the record cannot disagree about which keys are required.
173
+ *
174
+ * @type {ReadonlySet<string>}
175
+ */
176
+ export const REQUIRED_PROVENANCE_KEYS = Object.freeze(
177
+ new Set(
178
+ ASSET_RECORD_FIELDS.filter((field) => field.from === "provenance" && field.required).map(
179
+ (field) => field.name,
180
+ ),
181
+ ),
182
+ );
183
+
162
184
  /**
163
185
  * Read one provenance file, reporting every key that is not a provenance key.
164
186
  *
@@ -218,6 +240,25 @@ function readProvenanceFile(file, findings) {
218
240
  }
219
241
  out[key] = value == null ? "" : String(value);
220
242
  }
243
+
244
+ // A record resolves wholesale — the nearest one is the whole answer, and no
245
+ // ancestor fills a key it leaves out. So an omitted `attribution` or
246
+ // `license` is not a value inherited from above; it is a file with no stated
247
+ // rights holder and no stated terms, which is the one thing a provenance
248
+ // record exists to prevent. Reported against the file rather than a line,
249
+ // because the fault is an absence and has no position.
250
+ for (const key of REQUIRED_PROVENANCE_KEYS) {
251
+ if (out[key]) continue;
252
+ findings.push({
253
+ file,
254
+ severity: "error",
255
+ message:
256
+ `a provenance record states \`${key}\`, and this one does not. ` +
257
+ "A record replaces rather than extends the one above it, so every " +
258
+ "file it covers would carry no " +
259
+ (key === "license" ? "licence" : key),
260
+ });
261
+ }
221
262
  return out;
222
263
  }
223
264
 
@@ -239,6 +280,9 @@ function inheritedProvenance(dir, root, cache, findings) {
239
280
  const own = path.join(dir, PROVENANCE_FILE);
240
281
  let answer;
241
282
  if (fs.existsSync(own)) {
283
+ // The nearest record is the whole answer. It is not merged with the one
284
+ // above it, so a record states every key it means to claim and a reader
285
+ // needs only the file in front of them to know what a file carries.
242
286
  answer = readProvenanceFile(own, findings);
243
287
  } else if (path.resolve(dir) === path.resolve(root)) {
244
288
  // The search stops at the type root: `assets/` above it is the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heroiclands/package-build",
3
- "version": "22.0.1",
3
+ "version": "22.0.2",
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",
@@ -43,6 +43,10 @@ export const PROVENANCE_SIDECAR_SUFFIX: string;
43
43
  * @property {string} name - The key inside `asset`.
44
44
  * @property {"walk"|"provenance"} from - Where the value comes from: the walk
45
45
  * itself, or the provenance record resolved for the address.
46
+ * @property {boolean} [required] - Whether a provenance record that exists must
47
+ * state this key. Omitting one is a finding rather than a blank, because a
48
+ * record resolves wholesale: the nearest one is the whole answer, so a key it
49
+ * leaves out is not inherited from above but simply absent.
46
50
  * @property {string} describe - One line, for the author-facing reference.
47
51
  */
48
52
  /**
@@ -72,6 +76,15 @@ export const ASSET_RECORD_FIELDS: readonly AssetRecordField[];
72
76
  * @type {ReadonlySet<string>}
73
77
  */
74
78
  export const PROVENANCE_KEYS: ReadonlySet<string>;
79
+ /**
80
+ * The keys a provenance file must state.
81
+ *
82
+ * Derived from {@link ASSET_RECORD_FIELDS} for the same reason the key set is:
83
+ * the rule and the record cannot disagree about which keys are required.
84
+ *
85
+ * @type {ReadonlySet<string>}
86
+ */
87
+ export const REQUIRED_PROVENANCE_KEYS: ReadonlySet<string>;
75
88
  /**
76
89
  * One field of the `asset` block.
77
90
  */
@@ -85,6 +98,13 @@ export type AssetRecordField = {
85
98
  * itself, or the provenance record resolved for the address.
86
99
  */
87
100
  from: "walk" | "provenance";
101
+ /**
102
+ * - Whether a provenance record that exists must
103
+ * state this key. Omitting one is a finding rather than a blank, because a
104
+ * record resolves wholesale: the nearest one is the whole answer, so a key it
105
+ * leaves out is not inherited from above but simply absent.
106
+ */
107
+ required?: boolean | undefined;
88
108
  /**
89
109
  * - One line, for the author-facing reference.
90
110
  */