@heroiclands/package-build 22.0.0 → 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,29 @@
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
+
13
+ ## 22.0.1
14
+
15
+ ### Patch Changes
16
+
17
+ - d049b3e: **A provenance record may state `ai`.** It says whether the file is
18
+ machine-generated, and like every provenance key it is optional — a record that
19
+ omits it is unchanged, and nothing reads it yet. It exists so a package can
20
+ record the fact where the fact belongs, rather than in a key that means
21
+ something else.
22
+
23
+ **`attribution` is the person, not the tool.** It names whoever holds the rights
24
+ and is legally due the credit. Its description said a tool could go there, which
25
+ is what `ai` is for.
26
+
3
27
  ## 22.0.0
4
28
 
5
29
  ### Major 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
 
@@ -1465,8 +1465,9 @@ nothing about itself, compiles into no document, and publishes no page, so the
1465
1465
  | `asset` field | Source | What it says |
1466
1466
  | ------------- | ---------- | ------------------------------------------------------------------ |
1467
1467
  | `path` | the walk | Where the file sits inside the emitting package's asset directory. |
1468
- | `attribution` | provenance | Who made the file, or what tool generated it. |
1468
+ | `attribution` | provenance | The person holding the rights, to whom attribution is legally due. |
1469
1469
  | `source` | provenance | Where it came from — a URL, or a sentence. |
1470
+ | `ai` | provenance | Whether the file is machine-generated — `true` or `false`. |
1470
1471
  | `license` | provenance | The licence it is used under — an SPDX identifier, or terms. |
1471
1472
  | `notes` | provenance | Anything else a person reading the attribution needs. |
1472
1473
 
@@ -1490,10 +1491,10 @@ the two are distinct — they are equal in the system only by coincidence, and i
1490
1491
 
1491
1492
  ##### Where provenance comes from
1492
1493
 
1493
- A `provenance.yaml` states `attribution`, `source`, `license` and `notes`, and
1494
- nothing else: an unknown key is a finding rather than a silent drop, because
1495
- `licence` beside `license` is otherwise an attribution record that looks
1496
- complete and carries nothing.
1494
+ A `provenance.yaml` states the provenance fields in the table above, and nothing
1495
+ else: an unknown key is a finding rather than a silent drop, because `licence`
1496
+ beside `license` is otherwise an attribution record that looks complete and
1497
+ carries nothing.
1497
1498
 
1498
1499
  One address resolves its record in this order:
1499
1500
 
@@ -1507,6 +1508,17 @@ One address resolves its record in this order:
1507
1508
  3. Otherwise nothing, and the provenance fields are blank. A package that
1508
1509
  records no attribution is a fact to state, not a walk to fail.
1509
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
+
1510
1522
  #### A font is not an asset
1511
1523
 
1512
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,16 +123,23 @@ export const ASSET_RECORD_FIELDS = Object.freeze([
119
123
  Object.freeze({
120
124
  name: "attribution",
121
125
  from: "provenance",
122
- describe: "Who made the file, or what tool generated it.",
126
+ required: true,
127
+ describe: "The person holding the rights, to whom attribution is legally due.",
123
128
  }),
124
129
  Object.freeze({
125
130
  name: "source",
126
131
  from: "provenance",
127
132
  describe: "Where it came from — a URL, or a sentence.",
128
133
  }),
134
+ Object.freeze({
135
+ name: "ai",
136
+ from: "provenance",
137
+ describe: "Whether the file is machine-generated — `true` or `false`.",
138
+ }),
129
139
  Object.freeze({
130
140
  name: "license",
131
141
  from: "provenance",
142
+ required: true,
132
143
  describe: "The licence it is used under — an SPDX identifier, or terms.",
133
144
  }),
134
145
  Object.freeze({
@@ -154,6 +165,22 @@ export const PROVENANCE_KEYS = Object.freeze(
154
165
  ),
155
166
  );
156
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
+
157
184
  /**
158
185
  * Read one provenance file, reporting every key that is not a provenance key.
159
186
  *
@@ -213,6 +240,25 @@ function readProvenanceFile(file, findings) {
213
240
  }
214
241
  out[key] = value == null ? "" : String(value);
215
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
+ }
216
262
  return out;
217
263
  }
218
264
 
@@ -234,6 +280,9 @@ function inheritedProvenance(dir, root, cache, findings) {
234
280
  const own = path.join(dir, PROVENANCE_FILE);
235
281
  let answer;
236
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.
237
286
  answer = readProvenanceFile(own, findings);
238
287
  } else if (path.resolve(dir) === path.resolve(root)) {
239
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.0",
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",
@@ -1,13 +1,13 @@
1
- {"address":{"canonical":"packagebuild-none-image-afflictionbnr"},"asset":{"attribution":"Tom Rodriguez","license":"CC-BY-SA-4.0","notes":"","path":"images/banners/afflictionbnr.webp","source":""},"package":"packagebuild","shortcode":"afflictionbnr","type":"image"}
2
- {"address":{"canonical":"packagebuild-none-image-armorclothingbnr"},"asset":{"attribution":"Tom Rodriguez","license":"CC-BY-SA-4.0","notes":"","path":"images/banners/armorclothingbnr.webp","source":""},"package":"packagebuild","shortcode":"armorclothingbnr","type":"image"}
3
- {"address":{"canonical":"packagebuild-none-image-attributebnr"},"asset":{"attribution":"Tom Rodriguez","license":"CC-BY-SA-4.0","notes":"","path":"images/banners/attributebnr.webp","source":""},"package":"packagebuild","shortcode":"attributebnr","type":"image"}
4
- {"address":{"canonical":"packagebuild-none-image-containerbnr"},"asset":{"attribution":"Tom Rodriguez","license":"CC-BY-SA-4.0","notes":"","path":"images/banners/containerbnr.webp","source":""},"package":"packagebuild","shortcode":"containerbnr","type":"image"}
5
- {"address":{"canonical":"packagebuild-none-image-devdocsbnr"},"asset":{"attribution":"Tom Rodriguez","license":"CC-BY-SA-4.0","notes":"","path":"images/banners/devdocsbnr.webp","source":""},"package":"packagebuild","shortcode":"devdocsbnr","type":"image"}
6
- {"address":{"canonical":"packagebuild-none-image-miscgearbnr"},"asset":{"attribution":"Tom Rodriguez","license":"CC-BY-SA-4.0","notes":"","path":"images/banners/miscgearbnr.webp","source":""},"package":"packagebuild","shortcode":"miscgearbnr","type":"image"}
7
- {"address":{"canonical":"packagebuild-none-image-mysticalabilitybnr"},"asset":{"attribution":"Tom Rodriguez","license":"CC-BY-SA-4.0","notes":"","path":"images/banners/mysticalabilitybnr.webp","source":""},"package":"packagebuild","shortcode":"mysticalabilitybnr","type":"image"}
8
- {"address":{"canonical":"packagebuild-none-image-projectilebnr"},"asset":{"attribution":"Tom Rodriguez","license":"CC-BY-SA-4.0","notes":"","path":"images/banners/projectilebnr.webp","source":""},"package":"packagebuild","shortcode":"projectilebnr","type":"image"}
9
- {"address":{"canonical":"packagebuild-none-image-rulesbnr"},"asset":{"attribution":"Tom Rodriguez","license":"CC-BY-SA-4.0","notes":"","path":"images/banners/rulesbnr.webp","source":""},"package":"packagebuild","shortcode":"rulesbnr","type":"image"}
10
- {"address":{"canonical":"packagebuild-none-image-skillbnr"},"asset":{"attribution":"Tom Rodriguez","license":"CC-BY-SA-4.0","notes":"","path":"images/banners/skillbnr.webp","source":""},"package":"packagebuild","shortcode":"skillbnr","type":"image"}
11
- {"address":{"canonical":"packagebuild-none-image-traumabnr"},"asset":{"attribution":"Tom Rodriguez","license":"CC-BY-SA-4.0","notes":"","path":"images/banners/traumabnr.webp","source":""},"package":"packagebuild","shortcode":"traumabnr","type":"image"}
12
- {"address":{"canonical":"packagebuild-none-image-userguidebnr"},"asset":{"attribution":"Tom Rodriguez","license":"CC-BY-SA-4.0","notes":"","path":"images/banners/userguidebnr.webp","source":""},"package":"packagebuild","shortcode":"userguidebnr","type":"image"}
13
- {"address":{"canonical":"packagebuild-none-image-weapongearbnr"},"asset":{"attribution":"Tom Rodriguez","license":"CC-BY-SA-4.0","notes":"","path":"images/banners/weapongearbnr.webp","source":""},"package":"packagebuild","shortcode":"weapongearbnr","type":"image"}
1
+ {"address":{"canonical":"packagebuild-none-image-afflictionbnr"},"asset":{"ai":"","attribution":"Tom Rodriguez","license":"CC-BY-SA-4.0","notes":"","path":"images/banners/afflictionbnr.webp","source":""},"package":"packagebuild","shortcode":"afflictionbnr","type":"image"}
2
+ {"address":{"canonical":"packagebuild-none-image-armorclothingbnr"},"asset":{"ai":"","attribution":"Tom Rodriguez","license":"CC-BY-SA-4.0","notes":"","path":"images/banners/armorclothingbnr.webp","source":""},"package":"packagebuild","shortcode":"armorclothingbnr","type":"image"}
3
+ {"address":{"canonical":"packagebuild-none-image-attributebnr"},"asset":{"ai":"","attribution":"Tom Rodriguez","license":"CC-BY-SA-4.0","notes":"","path":"images/banners/attributebnr.webp","source":""},"package":"packagebuild","shortcode":"attributebnr","type":"image"}
4
+ {"address":{"canonical":"packagebuild-none-image-containerbnr"},"asset":{"ai":"","attribution":"Tom Rodriguez","license":"CC-BY-SA-4.0","notes":"","path":"images/banners/containerbnr.webp","source":""},"package":"packagebuild","shortcode":"containerbnr","type":"image"}
5
+ {"address":{"canonical":"packagebuild-none-image-devdocsbnr"},"asset":{"ai":"","attribution":"Tom Rodriguez","license":"CC-BY-SA-4.0","notes":"","path":"images/banners/devdocsbnr.webp","source":""},"package":"packagebuild","shortcode":"devdocsbnr","type":"image"}
6
+ {"address":{"canonical":"packagebuild-none-image-miscgearbnr"},"asset":{"ai":"","attribution":"Tom Rodriguez","license":"CC-BY-SA-4.0","notes":"","path":"images/banners/miscgearbnr.webp","source":""},"package":"packagebuild","shortcode":"miscgearbnr","type":"image"}
7
+ {"address":{"canonical":"packagebuild-none-image-mysticalabilitybnr"},"asset":{"ai":"","attribution":"Tom Rodriguez","license":"CC-BY-SA-4.0","notes":"","path":"images/banners/mysticalabilitybnr.webp","source":""},"package":"packagebuild","shortcode":"mysticalabilitybnr","type":"image"}
8
+ {"address":{"canonical":"packagebuild-none-image-projectilebnr"},"asset":{"ai":"","attribution":"Tom Rodriguez","license":"CC-BY-SA-4.0","notes":"","path":"images/banners/projectilebnr.webp","source":""},"package":"packagebuild","shortcode":"projectilebnr","type":"image"}
9
+ {"address":{"canonical":"packagebuild-none-image-rulesbnr"},"asset":{"ai":"","attribution":"Tom Rodriguez","license":"CC-BY-SA-4.0","notes":"","path":"images/banners/rulesbnr.webp","source":""},"package":"packagebuild","shortcode":"rulesbnr","type":"image"}
10
+ {"address":{"canonical":"packagebuild-none-image-skillbnr"},"asset":{"ai":"","attribution":"Tom Rodriguez","license":"CC-BY-SA-4.0","notes":"","path":"images/banners/skillbnr.webp","source":""},"package":"packagebuild","shortcode":"skillbnr","type":"image"}
11
+ {"address":{"canonical":"packagebuild-none-image-traumabnr"},"asset":{"ai":"","attribution":"Tom Rodriguez","license":"CC-BY-SA-4.0","notes":"","path":"images/banners/traumabnr.webp","source":""},"package":"packagebuild","shortcode":"traumabnr","type":"image"}
12
+ {"address":{"canonical":"packagebuild-none-image-userguidebnr"},"asset":{"ai":"","attribution":"Tom Rodriguez","license":"CC-BY-SA-4.0","notes":"","path":"images/banners/userguidebnr.webp","source":""},"package":"packagebuild","shortcode":"userguidebnr","type":"image"}
13
+ {"address":{"canonical":"packagebuild-none-image-weapongearbnr"},"asset":{"ai":"","attribution":"Tom Rodriguez","license":"CC-BY-SA-4.0","notes":"","path":"images/banners/weapongearbnr.webp","source":""},"package":"packagebuild","shortcode":"weapongearbnr","type":"image"}
@@ -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
  */