@wildwinter/toolkit 0.1.0 → 0.2.1
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 +54 -0
- package/README.md +17 -2
- package/conformance/extensions/defaults.json +69 -0
- package/conformance/extensions/manifest.json +1129 -0
- package/conformance/extensions/merge.json +154 -0
- package/conformance/extensions/values.json +741 -0
- package/dist/chunk-ONEAWPDZ.js +11 -0
- package/dist/chunk-ONEAWPDZ.js.map +1 -0
- package/dist/extensions.cjs +948 -0
- package/dist/extensions.cjs.map +1 -0
- package/dist/extensions.d.cts +324 -0
- package/dist/extensions.d.ts +324 -0
- package/dist/extensions.js +902 -0
- package/dist/extensions.js.map +1 -0
- package/dist/index.js +2 -6
- package/dist/index.js.map +1 -1
- package/extensions.md +192 -0
- package/package.json +29 -3
|
@@ -0,0 +1,902 @@
|
|
|
1
|
+
import { parseSource } from './chunk-ONEAWPDZ.js';
|
|
2
|
+
import { valueFits, stagesEitherSide, qualityDeclarationProblems } from '@wildwinter/expr';
|
|
3
|
+
import { defaultFor } from '@wildwinter/scoperegistry';
|
|
4
|
+
|
|
5
|
+
// src/extensions/types.ts
|
|
6
|
+
var MANIFEST_SCHEMA = "wildwinter/extension@0";
|
|
7
|
+
var MANIFEST_FILE_SUFFIX = ".extension.json";
|
|
8
|
+
var HOSTS = {
|
|
9
|
+
patter: {
|
|
10
|
+
kinds: ["scene", "block", "snippet", "line", "text", "gameEvent", "option", "project"],
|
|
11
|
+
references: ["cast", "scene", "block", "snippet", "line"],
|
|
12
|
+
claimable: ["scene", "block", "snippet", "line", "text", "gameEvent", "option"]
|
|
13
|
+
},
|
|
14
|
+
storylets: {
|
|
15
|
+
kinds: ["box", "tagGroup", "tag", "deck", "card", "outcome", "handTemplate", "hand", "project"],
|
|
16
|
+
references: ["box", "deck", "card", "outcome", "tag"],
|
|
17
|
+
claimable: ["card", "outcome"]
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var SCALAR_TYPES = ["text", "multiline", "number", "boolean", "enum", "flags", "quality"];
|
|
21
|
+
var VALUE_TYPES = [...SCALAR_TYPES, "reference", "record", "map", "list"];
|
|
22
|
+
|
|
23
|
+
// src/extensions/path.ts
|
|
24
|
+
var IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
25
|
+
var atKey = (base, key) => IDENTIFIER.test(key) ? base === "" ? key : `${base}.${key}` : `${base}[${JSON.stringify(key)}]`;
|
|
26
|
+
var atIndex = (base, i) => `${base}[${i}]`;
|
|
27
|
+
var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
28
|
+
var own = (record, name) => Object.prototype.hasOwnProperty.call(record, name);
|
|
29
|
+
var typeWord = (v) => Array.isArray(v) ? "a list" : v === null ? "null" : isObject(v) ? "an object" : `a ${typeof v}`;
|
|
30
|
+
var where = (path) => path === "" ? "the value" : `"${path}"`;
|
|
31
|
+
var err = (code, path, message, options) => ({ severity: "error", code, path, message, ...options !== void 0 ? { options } : {} });
|
|
32
|
+
function valueProblems(decl, value, ctx = {}, path = "") {
|
|
33
|
+
const out = [];
|
|
34
|
+
const wrong = (expected) => {
|
|
35
|
+
out.push(err("wrong-type", path, `${where(path)} should be ${expected}, not ${typeWord(value)}`));
|
|
36
|
+
};
|
|
37
|
+
switch (decl.type) {
|
|
38
|
+
case "text":
|
|
39
|
+
case "multiline":
|
|
40
|
+
if (typeof value !== "string") wrong("text");
|
|
41
|
+
break;
|
|
42
|
+
case "boolean":
|
|
43
|
+
if (typeof value !== "boolean") wrong("true or false");
|
|
44
|
+
break;
|
|
45
|
+
case "number":
|
|
46
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
47
|
+
wrong("a number");
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
if (typeof decl.min === "number" && value < decl.min) {
|
|
51
|
+
out.push(err("below-min", path, `${where(path)} is ${value}, below the least it may be, ${decl.min}`));
|
|
52
|
+
}
|
|
53
|
+
if (typeof decl.max === "number" && value > decl.max) {
|
|
54
|
+
out.push(err("above-max", path, `${where(path)} is ${value}, above the most it may be, ${decl.max}`));
|
|
55
|
+
}
|
|
56
|
+
break;
|
|
57
|
+
case "enum":
|
|
58
|
+
if (typeof value !== "string") {
|
|
59
|
+
wrong("one of its values");
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
if (!valueFits(decl, value, { noneChosen: true })) {
|
|
63
|
+
out.push(err("not-a-value", path, `${where(path)} is "${value}", which is not one of its values`, [...decl.values ?? []]));
|
|
64
|
+
}
|
|
65
|
+
break;
|
|
66
|
+
case "flags":
|
|
67
|
+
if (!Array.isArray(value) || !value.every((f) => typeof f === "string")) {
|
|
68
|
+
wrong("a list of its flags");
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
if (!valueFits(decl, value)) {
|
|
72
|
+
const odd = value.filter((f) => !(decl.values ?? []).includes(f));
|
|
73
|
+
out.push(err("not-a-value", path, `${where(path)} names ${odd.map((f) => `"${f}"`).join(", ")}, not among its flags`, [...decl.values ?? []]));
|
|
74
|
+
}
|
|
75
|
+
break;
|
|
76
|
+
case "quality": {
|
|
77
|
+
if (typeof value !== "string") {
|
|
78
|
+
wrong("one of its stages");
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
if (valueFits(decl, value)) break;
|
|
82
|
+
const renamed = renamedStage(decl, value);
|
|
83
|
+
if (renamed !== void 0) {
|
|
84
|
+
out.push({
|
|
85
|
+
severity: "warning",
|
|
86
|
+
code: "renamed-stage",
|
|
87
|
+
path,
|
|
88
|
+
options: [renamed],
|
|
89
|
+
message: `${where(path)} is "${value}", a stage this extension has since renamed "${renamed}"`
|
|
90
|
+
});
|
|
91
|
+
} else {
|
|
92
|
+
out.push(err("not-a-stage", path, `${where(path)} is "${value}", which is not one of its stages`, stagesEitherSide(decl, value)));
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
case "reference":
|
|
97
|
+
if (typeof value !== "string") {
|
|
98
|
+
wrong(`the id of a ${decl.to ?? "node"}`);
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
if (value !== "" && ctx.exists && decl.to !== void 0 && !ctx.exists(decl.to, value)) {
|
|
102
|
+
out.push(err("no-such-node", path, `${where(path)} names ${decl.to} "${value}", which does not exist`));
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
case "record":
|
|
106
|
+
if (!isObject(value)) {
|
|
107
|
+
wrong("an object");
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
out.push(...fieldsProblems(decl.fields ?? [], value, ctx, path));
|
|
111
|
+
break;
|
|
112
|
+
case "map": {
|
|
113
|
+
if (!isObject(value)) {
|
|
114
|
+
wrong("an object");
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
const key = decl.key;
|
|
118
|
+
for (const [k, v] of Object.entries(value)) {
|
|
119
|
+
const at = atKey(path, k);
|
|
120
|
+
if (key?.type === "reference" && ctx.exists && key.to !== void 0 && !ctx.exists(key.to, k)) {
|
|
121
|
+
out.push(err("no-such-node", at, `${where(at)} is keyed by ${key.to} "${k}", which does not exist`));
|
|
122
|
+
}
|
|
123
|
+
if (decl.of) out.push(...valueProblems(decl.of, v, ctx, at));
|
|
124
|
+
}
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
case "list": {
|
|
128
|
+
if (!Array.isArray(value)) {
|
|
129
|
+
wrong("a list");
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
const seen = /* @__PURE__ */ new Set();
|
|
133
|
+
value.forEach((item, i) => {
|
|
134
|
+
const at = atIndex(path, i);
|
|
135
|
+
if (decl.id !== void 0 && isObject(item)) {
|
|
136
|
+
const id = item[decl.id];
|
|
137
|
+
const idAt = atKey(at, decl.id);
|
|
138
|
+
if (typeof id !== "string" || id === "") {
|
|
139
|
+
out.push(err("missing-id", idAt, `${where(at)} has no "${decl.id}", which keys every item of this list`));
|
|
140
|
+
} else if (seen.has(id)) {
|
|
141
|
+
out.push(err("duplicate-id", idAt, `${where(at)} repeats the id "${id}"`));
|
|
142
|
+
} else {
|
|
143
|
+
seen.add(id);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (decl.of) out.push(...valueProblems(decl.of, item, ctx, at));
|
|
147
|
+
});
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return out;
|
|
152
|
+
}
|
|
153
|
+
function fieldsProblems(fields, value, ctx, path) {
|
|
154
|
+
const out = [];
|
|
155
|
+
const named = new Map(fields.map((f) => [f.name, f]));
|
|
156
|
+
for (const f of fields) {
|
|
157
|
+
if (f.required === true && !own(value, f.name)) {
|
|
158
|
+
const at = atKey(path, f.name);
|
|
159
|
+
out.push(err("required", at, `${where(at)} is required and not set`));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
for (const [k, v] of Object.entries(value)) {
|
|
163
|
+
const at = atKey(path, k);
|
|
164
|
+
const f = named.get(k);
|
|
165
|
+
if (f === void 0) {
|
|
166
|
+
out.push({
|
|
167
|
+
severity: "warning",
|
|
168
|
+
code: "unknown-field",
|
|
169
|
+
path: at,
|
|
170
|
+
message: `${where(at)} is not a field this extension declares; it is kept, and not compiled`
|
|
171
|
+
});
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
out.push(...valueProblems(f, v, ctx, at));
|
|
175
|
+
}
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
function entryProblems(target, entry, ctx = {}, path = "") {
|
|
179
|
+
if (!isObject(entry)) {
|
|
180
|
+
return [err("wrong-type", path, `${where(path)} should be an object of field values, not ${typeWord(entry)}`)];
|
|
181
|
+
}
|
|
182
|
+
return fieldsProblems(target.fields ?? [], entry, ctx, path);
|
|
183
|
+
}
|
|
184
|
+
function defaultOf(decl) {
|
|
185
|
+
if (decl.default !== void 0) return decl.default;
|
|
186
|
+
switch (decl.type) {
|
|
187
|
+
case "reference":
|
|
188
|
+
return "";
|
|
189
|
+
case "record":
|
|
190
|
+
return Object.fromEntries((decl.fields ?? []).map((f) => [f.name, defaultOf(f)]));
|
|
191
|
+
case "map":
|
|
192
|
+
return {};
|
|
193
|
+
case "list":
|
|
194
|
+
return [];
|
|
195
|
+
default:
|
|
196
|
+
return defaultFor({
|
|
197
|
+
type: decl.type,
|
|
198
|
+
...decl.values !== void 0 ? { values: decl.values } : {},
|
|
199
|
+
...decl.stages !== void 0 ? { stages: decl.stages } : {}
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
var EDITOR_KEYS = /* @__PURE__ */ new Set(["label", "help", "valueHelp", "group", "editorOnly", "renamedStages"]);
|
|
204
|
+
function compiledDecl(decl) {
|
|
205
|
+
const out = {};
|
|
206
|
+
for (const [k, v] of Object.entries(decl)) if (!EDITOR_KEYS.has(k)) out[k] = v;
|
|
207
|
+
if (decl.fields !== void 0) out["fields"] = decl.fields.map((f) => compiledDecl(f));
|
|
208
|
+
if (decl.of !== void 0) out["of"] = compiledDecl(decl.of);
|
|
209
|
+
out["default"] = defaultOf(decl);
|
|
210
|
+
return out;
|
|
211
|
+
}
|
|
212
|
+
function compiledTarget(target) {
|
|
213
|
+
return (target.fields ?? []).filter((f) => f.editorOnly !== true).map((f) => stripOfDefaults(compiledDecl(f)));
|
|
214
|
+
}
|
|
215
|
+
function stripOfDefaults(decl) {
|
|
216
|
+
const out = { ...decl };
|
|
217
|
+
if (decl.of !== void 0) {
|
|
218
|
+
const { default: _drop, ...rest } = decl.of;
|
|
219
|
+
out["of"] = stripOfDefaults(rest);
|
|
220
|
+
}
|
|
221
|
+
if (decl.fields !== void 0) out["fields"] = decl.fields.map((f) => stripOfDefaults(f));
|
|
222
|
+
return out;
|
|
223
|
+
}
|
|
224
|
+
function compileEntry(target, entry) {
|
|
225
|
+
const renamed = renameStages(target, entry).entry;
|
|
226
|
+
const out = {};
|
|
227
|
+
const named = new Map((target.fields ?? []).map((f) => [f.name, f]));
|
|
228
|
+
for (const k of Object.keys(renamed).sort()) {
|
|
229
|
+
const f = named.get(k);
|
|
230
|
+
if (f === void 0 || f.editorOnly === true) continue;
|
|
231
|
+
out[k] = renamed[k];
|
|
232
|
+
}
|
|
233
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
234
|
+
}
|
|
235
|
+
function renamedStage(decl, value) {
|
|
236
|
+
const map = decl.renamedStages;
|
|
237
|
+
if (map === void 0 || !own(map, value)) return void 0;
|
|
238
|
+
const to = map[value];
|
|
239
|
+
return typeof to === "string" && (decl.stages ?? []).includes(to) ? to : void 0;
|
|
240
|
+
}
|
|
241
|
+
function renameStages(target, entry) {
|
|
242
|
+
const renames = [];
|
|
243
|
+
const walk = (decl, value, path) => {
|
|
244
|
+
switch (decl.type) {
|
|
245
|
+
case "quality": {
|
|
246
|
+
if (typeof value !== "string" || (decl.stages ?? []).includes(value)) return value;
|
|
247
|
+
const to = renamedStage(decl, value);
|
|
248
|
+
if (to === void 0) return value;
|
|
249
|
+
renames.push({ path, from: value, to });
|
|
250
|
+
return to;
|
|
251
|
+
}
|
|
252
|
+
case "record":
|
|
253
|
+
return isObject(value) ? walkFields(decl.fields ?? [], value, path) : value;
|
|
254
|
+
case "map":
|
|
255
|
+
if (!isObject(value) || decl.of === void 0) return value;
|
|
256
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, walk(decl.of, v, atKey(path, k))]));
|
|
257
|
+
case "list":
|
|
258
|
+
if (!Array.isArray(value) || decl.of === void 0) return value;
|
|
259
|
+
return value.map((v, i) => walk(decl.of, v, atIndex(path, i)));
|
|
260
|
+
default:
|
|
261
|
+
return value;
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
const walkFields = (fields, value, path) => {
|
|
265
|
+
const named = new Map(fields.map((f) => [f.name, f]));
|
|
266
|
+
const out2 = {};
|
|
267
|
+
for (const [k, v] of Object.entries(value)) {
|
|
268
|
+
const f = named.get(k);
|
|
269
|
+
out2[k] = f === void 0 ? v : walk(f, v, atKey(path, k));
|
|
270
|
+
}
|
|
271
|
+
return out2;
|
|
272
|
+
};
|
|
273
|
+
const out = walkFields(target.fields ?? [], entry, "");
|
|
274
|
+
return { entry: renames.length > 0 ? out : { ...entry }, renames };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// src/extensions/manifest.ts
|
|
278
|
+
var ID = /^[a-z][a-z0-9-]*$/;
|
|
279
|
+
var VERSION = /^\d+\.\d+\.\d+$/;
|
|
280
|
+
var NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
281
|
+
var TYPE_KEYS = ["type", "values", "stages", "renamedStages", "to", "fields", "key", "of", "id", "min", "max", "valueHelp", "default"];
|
|
282
|
+
var FIELD_KEYS = /* @__PURE__ */ new Set([...TYPE_KEYS, "name", "label", "help", "required", "group", "editorOnly"]);
|
|
283
|
+
var CLAIM_KEYS = /* @__PURE__ */ new Set([...TYPE_KEYS, "setting", "label", "help", "required"]);
|
|
284
|
+
var BARE_KEYS = new Set(TYPE_KEYS);
|
|
285
|
+
var MANIFEST_KEYS = /* @__PURE__ */ new Set(["schema", "id", "name", "version", "docs", "settings", "targets"]);
|
|
286
|
+
var err2 = (code, path, message) => ({ severity: "error", code, path, message });
|
|
287
|
+
function parseManifest(text) {
|
|
288
|
+
return parseSource(text);
|
|
289
|
+
}
|
|
290
|
+
function manifestProblems(manifest) {
|
|
291
|
+
const out = [];
|
|
292
|
+
if (!isObject(manifest)) return [err2("not-an-object", "", "a manifest is a JSON object")];
|
|
293
|
+
const m = manifest;
|
|
294
|
+
unknownKeys(m, MANIFEST_KEYS, "", out);
|
|
295
|
+
if (m["schema"] !== MANIFEST_SCHEMA) {
|
|
296
|
+
out.push(err2("schema", "schema", `the schema tag is ${JSON.stringify(m["schema"])}, not "${MANIFEST_SCHEMA}"`));
|
|
297
|
+
}
|
|
298
|
+
if (typeof m["id"] !== "string" || !ID.test(m["id"])) {
|
|
299
|
+
out.push(err2("id", "id", "the id is lower case letters, digits and hyphens, starting with a letter"));
|
|
300
|
+
}
|
|
301
|
+
if (typeof m["name"] !== "string" || m["name"].trim() === "") out.push(err2("name", "name", "the manifest has no name"));
|
|
302
|
+
if (typeof m["version"] !== "string" || !VERSION.test(m["version"])) {
|
|
303
|
+
out.push(err2("version", "version", "the version is major.minor.patch, such as 1.0.0"));
|
|
304
|
+
}
|
|
305
|
+
if (m["docs"] !== void 0 && typeof m["docs"] !== "string") out.push(err2("docs", "docs", "docs is a link, as text"));
|
|
306
|
+
const settings = settingsProblems(m["settings"], out);
|
|
307
|
+
const targets = m["targets"];
|
|
308
|
+
if (!isObject(targets) || Object.keys(targets).length === 0) {
|
|
309
|
+
out.push(err2("no-targets", "targets", "the manifest targets no host"));
|
|
310
|
+
return out;
|
|
311
|
+
}
|
|
312
|
+
for (const [host, kinds] of Object.entries(targets)) {
|
|
313
|
+
const hostPath = atKey("targets", host);
|
|
314
|
+
const spec = own(HOSTS, host) ? HOSTS[host] : void 0;
|
|
315
|
+
if (spec === void 0) {
|
|
316
|
+
out.push(err2("unknown-host", hostPath, `"${host}" is not a host; a manifest targets "patter" or "storylets"`));
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
if (!isObject(kinds)) {
|
|
320
|
+
out.push(err2("not-an-object", hostPath, "a host's targets are an object, by node kind"));
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
for (const [kind, target] of Object.entries(kinds)) {
|
|
324
|
+
const kindPath = atKey(hostPath, kind);
|
|
325
|
+
if (!spec.kinds.includes(kind)) {
|
|
326
|
+
out.push(err2("unknown-kind", kindPath, `"${kind}" is not a node kind of ${host}; it has ${spec.kinds.join(", ")}`));
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
targetProblems(host, kind, target, kindPath, settings, out);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return out;
|
|
333
|
+
}
|
|
334
|
+
function unknownKeys(obj, known, path, out) {
|
|
335
|
+
for (const k of Object.keys(obj)) {
|
|
336
|
+
if (!known.has(k)) {
|
|
337
|
+
out.push({ severity: "warning", code: "unknown-key", path: atKey(path, k), message: `"${k}" means nothing here; it is ignored` });
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
function settingsProblems(settings, out) {
|
|
342
|
+
const names = /* @__PURE__ */ new Set();
|
|
343
|
+
if (settings === void 0) return names;
|
|
344
|
+
if (!Array.isArray(settings)) {
|
|
345
|
+
out.push(err2("not-a-list", "settings", "settings are a list"));
|
|
346
|
+
return names;
|
|
347
|
+
}
|
|
348
|
+
settings.forEach((s, i) => {
|
|
349
|
+
const at = atIndex("settings", i);
|
|
350
|
+
if (!isObject(s)) {
|
|
351
|
+
out.push(err2("not-an-object", at, "a setting is an object"));
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const name = s["name"];
|
|
355
|
+
if (typeof name !== "string" || !NAME.test(name)) {
|
|
356
|
+
out.push(err2("setting-name", atKey(at, "name"), "a setting's name is an identifier"));
|
|
357
|
+
} else if (names.has(name)) {
|
|
358
|
+
out.push(err2("setting-duplicate", atKey(at, "name"), `setting "${name}" is declared twice`));
|
|
359
|
+
} else {
|
|
360
|
+
names.add(name);
|
|
361
|
+
}
|
|
362
|
+
const dflt = s.default;
|
|
363
|
+
if (typeof dflt !== "string" || !NAME.test(dflt)) {
|
|
364
|
+
out.push(err2("setting-default", atKey(at, "default"), "a setting names a host field by default, so its default is a field name"));
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
return names;
|
|
368
|
+
}
|
|
369
|
+
function targetProblems(host, kind, target, path, settings, out) {
|
|
370
|
+
if (!isObject(target)) {
|
|
371
|
+
out.push(err2("not-an-object", path, "a target is an object"));
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
unknownKeys(target, /* @__PURE__ */ new Set(["fields", "claims"]), path, out);
|
|
375
|
+
const t = target;
|
|
376
|
+
if (t.fields !== void 0) fieldListProblems(host, t.fields, atKey(path, "fields"), true, out);
|
|
377
|
+
if (t.claims === void 0) return;
|
|
378
|
+
if (!Array.isArray(t.claims)) {
|
|
379
|
+
out.push(err2("not-a-list", atKey(path, "claims"), "claims are a list"));
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const claimable = HOSTS[host].claimable.includes(kind);
|
|
383
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
384
|
+
t.claims.forEach((c, i) => {
|
|
385
|
+
const at = atIndex(atKey(path, "claims"), i);
|
|
386
|
+
if (!claimable) {
|
|
387
|
+
out.push(err2("claim-kind", at, `a ${kind} has no host fields to claim; ${host} has them on ${HOSTS[host].claimable.join(", ")}`));
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
if (!isObject(c)) {
|
|
391
|
+
out.push(err2("not-an-object", at, "a claim is an object"));
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
const claim = c;
|
|
395
|
+
unknownKeys(c, CLAIM_KEYS, at, out);
|
|
396
|
+
if (!SCALAR_TYPES.includes(claim.type)) {
|
|
397
|
+
out.push(err2("claim-type", atKey(at, "type"), "a claim presents a host field, which is a scalar"));
|
|
398
|
+
} else {
|
|
399
|
+
typeProblems(host, claim, at, false, out, true);
|
|
400
|
+
}
|
|
401
|
+
if (typeof claim.setting !== "string" || !settings.has(claim.setting)) {
|
|
402
|
+
out.push(err2("claim-setting", atKey(at, "setting"), `a claim names its field through a setting, and "${String(claim.setting)}" is not one of the manifest's settings`));
|
|
403
|
+
} else if (claimed.has(claim.setting)) {
|
|
404
|
+
out.push(err2("claim-duplicate", atKey(at, "setting"), `setting "${claim.setting}" is claimed twice for one kind`));
|
|
405
|
+
} else {
|
|
406
|
+
claimed.add(claim.setting);
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
function fieldListProblems(host, fields, path, topLevel, out) {
|
|
411
|
+
if (!Array.isArray(fields)) {
|
|
412
|
+
out.push(err2("not-a-list", path, "fields are a list"));
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
const names = /* @__PURE__ */ new Set();
|
|
416
|
+
fields.forEach((f, i) => {
|
|
417
|
+
const at = atIndex(path, i);
|
|
418
|
+
if (!isObject(f)) {
|
|
419
|
+
out.push(err2("not-an-object", at, "a field is an object"));
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
const field = f;
|
|
423
|
+
unknownKeys(f, FIELD_KEYS, at, out);
|
|
424
|
+
if (typeof field.name !== "string" || !NAME.test(field.name)) {
|
|
425
|
+
out.push(err2("field-name", atKey(at, "name"), "a field's name is an identifier: letters, digits and underscores, not starting with a digit"));
|
|
426
|
+
} else if (names.has(field.name)) {
|
|
427
|
+
out.push(err2("duplicate-field", atKey(at, "name"), `field "${field.name}" is declared twice`));
|
|
428
|
+
} else {
|
|
429
|
+
names.add(field.name);
|
|
430
|
+
}
|
|
431
|
+
typeProblems(host, field, at, true, out);
|
|
432
|
+
if (field.editorOnly !== void 0 && !topLevel) {
|
|
433
|
+
out.push(err2("editor-only-nested", atKey(at, "editorOnly"), "only a target's own fields can be editor-only, not a record's"));
|
|
434
|
+
}
|
|
435
|
+
for (const flag of ["required", "editorOnly"]) {
|
|
436
|
+
if (field[flag] !== void 0 && typeof field[flag] !== "boolean") {
|
|
437
|
+
out.push(err2("not-a-boolean", atKey(at, flag), `${flag} is true or false`));
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
function typeProblems(host, decl, path, named, out, claim = false) {
|
|
443
|
+
const typeAt = atKey(path, "type");
|
|
444
|
+
if (decl.type === "string") {
|
|
445
|
+
out.push(err2("string-type", typeAt, 'the text type is spelled "text"'));
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
if (typeof decl.type !== "string" || !VALUE_TYPES.includes(decl.type)) {
|
|
449
|
+
out.push(err2("unknown-type", typeAt, `${JSON.stringify(decl.type)} is not a type; the types are ${VALUE_TYPES.join(", ")}`));
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
const at = (k) => atKey(path, k);
|
|
453
|
+
let sound = true;
|
|
454
|
+
switch (decl.type) {
|
|
455
|
+
case "enum":
|
|
456
|
+
case "flags": {
|
|
457
|
+
const values = decl.values;
|
|
458
|
+
if (claim && values === void 0) break;
|
|
459
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
460
|
+
out.push(err2("values-empty", at("values"), `an ${decl.type === "enum" ? "enum" : "flags field"} lists its values`));
|
|
461
|
+
sound = false;
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
const seen = /* @__PURE__ */ new Set();
|
|
465
|
+
values.forEach((v, i) => {
|
|
466
|
+
if (typeof v !== "string" || v.trim() === "") {
|
|
467
|
+
out.push(err2("value-empty", atIndex(at("values"), i), "a value has a name"));
|
|
468
|
+
sound = false;
|
|
469
|
+
} else if (seen.has(v)) {
|
|
470
|
+
out.push(err2("value-duplicate", atIndex(at("values"), i), `"${v}" is listed twice`));
|
|
471
|
+
sound = false;
|
|
472
|
+
} else seen.add(v);
|
|
473
|
+
});
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
case "quality": {
|
|
477
|
+
if (claim && decl.stages === void 0) break;
|
|
478
|
+
for (const p of qualityDeclarationProblems(decl.stages)) {
|
|
479
|
+
out.push(err2(
|
|
480
|
+
`quality-${p.kind === "default-not-a-stage" ? "default" : p.kind}`,
|
|
481
|
+
at("stages"),
|
|
482
|
+
p.kind === "too-few-stages" ? "a quality's ladder has at least two stages" : p.kind === "empty-stage" ? `stage ${p.index + 1} has no name` : p.kind === "duplicate-stage" ? `stage "${p.stage}" is listed twice` : "the default is not a stage"
|
|
483
|
+
));
|
|
484
|
+
sound = false;
|
|
485
|
+
}
|
|
486
|
+
break;
|
|
487
|
+
}
|
|
488
|
+
case "reference":
|
|
489
|
+
if (typeof decl.to !== "string" || !HOSTS[host].references.includes(decl.to)) {
|
|
490
|
+
out.push(err2("reference-to", at("to"), `a reference names the kind it points at, one of ${HOSTS[host].references.join(", ")}`));
|
|
491
|
+
sound = false;
|
|
492
|
+
}
|
|
493
|
+
break;
|
|
494
|
+
case "record":
|
|
495
|
+
if (!Array.isArray(decl.fields) || decl.fields.length === 0) {
|
|
496
|
+
out.push(err2("record-empty", at("fields"), "a record declares its fields"));
|
|
497
|
+
sound = false;
|
|
498
|
+
} else {
|
|
499
|
+
const before = out.length;
|
|
500
|
+
fieldListProblems(host, decl.fields, at("fields"), false, out);
|
|
501
|
+
if (out.slice(before).some((p) => p.severity === "error")) sound = false;
|
|
502
|
+
}
|
|
503
|
+
break;
|
|
504
|
+
case "map": {
|
|
505
|
+
const key = decl.key;
|
|
506
|
+
const keyOk = isObject(key) && (key["type"] === "text" || key["type"] === "reference" && typeof key["to"] === "string" && HOSTS[host].references.includes(key["to"]));
|
|
507
|
+
if (!keyOk) {
|
|
508
|
+
out.push(err2("map-key", at("key"), `a map is keyed by { type: "text" } or a reference, { type: "reference", to: "${HOSTS[host].references[0]}" }`));
|
|
509
|
+
sound = false;
|
|
510
|
+
}
|
|
511
|
+
if (!ofProblems(host, decl, at, out)) sound = false;
|
|
512
|
+
break;
|
|
513
|
+
}
|
|
514
|
+
case "list": {
|
|
515
|
+
if (decl.id !== void 0) {
|
|
516
|
+
const of = decl.of;
|
|
517
|
+
const idField = isObject(of) && of["type"] === "record" && Array.isArray(of["fields"]) ? of["fields"].find((f) => isObject(f) && f["name"] === decl.id) : void 0;
|
|
518
|
+
if (typeof decl.id !== "string" || idField === void 0 || idField.type !== "text") {
|
|
519
|
+
out.push(err2("list-id", at("id"), "a list with ids holds records, and its id names a text field of them"));
|
|
520
|
+
sound = false;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
if (!ofProblems(host, decl, at, out)) sound = false;
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if (decl.min !== void 0 || decl.max !== void 0) {
|
|
528
|
+
const which = decl.max !== void 0 ? "max" : "min";
|
|
529
|
+
if (decl.type !== "number") {
|
|
530
|
+
out.push(err2("range", at(which), "a range applies to a number"));
|
|
531
|
+
} else if (typeof decl.min === "number" && typeof decl.max === "number" && decl.min > decl.max) {
|
|
532
|
+
out.push(err2("range", at("max"), `the range runs from ${decl.min} down to ${decl.max}`));
|
|
533
|
+
sound = false;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
if (decl.renamedStages !== void 0) renamedStagesProblems(decl, at("renamedStages"), out);
|
|
537
|
+
if (decl.valueHelp !== void 0 && claim) {
|
|
538
|
+
if (!isObject(decl.valueHelp) || !["enum", "flags", "quality"].includes(decl.type)) {
|
|
539
|
+
out.push(err2("value-help", at("valueHelp"), "value help describes the values of an enum or flags field, or a quality's stages"));
|
|
540
|
+
}
|
|
541
|
+
} else if (decl.valueHelp !== void 0) {
|
|
542
|
+
const vocabulary = decl.type === "quality" ? decl.stages : decl.type === "enum" || decl.type === "flags" ? decl.values : void 0;
|
|
543
|
+
if (vocabulary === void 0 || !isObject(decl.valueHelp)) {
|
|
544
|
+
out.push(err2("value-help", at("valueHelp"), "value help describes the values of an enum or flags field, or a quality's stages"));
|
|
545
|
+
} else {
|
|
546
|
+
for (const k of Object.keys(decl.valueHelp)) {
|
|
547
|
+
if (!vocabulary.includes(k)) out.push(err2("value-help", atKey(at("valueHelp"), k), `"${k}" is not one of its values`));
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
if (named && decl.default !== void 0 && sound) {
|
|
552
|
+
const misfit = valueProblems(decl, decl.default).filter((p) => p.severity === "error");
|
|
553
|
+
if (misfit.length > 0) out.push(err2("default-misfit", at("default"), `the default does not fit: ${misfit[0].message}`));
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
function ofProblems(host, decl, at, out) {
|
|
557
|
+
if (!isObject(decl.of)) {
|
|
558
|
+
out.push(err2("missing-of", at("of"), `a ${decl.type} says what it holds, in "of"`));
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
const before = out.length;
|
|
562
|
+
unknownKeys(decl.of, BARE_KEYS, at("of"), out);
|
|
563
|
+
typeProblems(host, decl.of, at("of"), false, out);
|
|
564
|
+
return !out.slice(before).some((p) => p.severity === "error");
|
|
565
|
+
}
|
|
566
|
+
function renamedStagesProblems(decl, path, out) {
|
|
567
|
+
if (decl.type !== "quality" || !isObject(decl.renamedStages)) {
|
|
568
|
+
out.push(err2("renamed-stages", path, 'renamed stages belong to a quality, as { "old": "new" }'));
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
const stages = decl.stages ?? [];
|
|
572
|
+
for (const [from, to] of Object.entries(decl.renamedStages)) {
|
|
573
|
+
if (stages.includes(from)) {
|
|
574
|
+
out.push(err2("renamed-stages", atKey(path, from), `"${from}" is still a stage, so it cannot also be an old name`));
|
|
575
|
+
} else if (typeof to !== "string" || !stages.includes(to)) {
|
|
576
|
+
out.push(err2("renamed-stages", atKey(path, from), `"${from}" is renamed to ${JSON.stringify(to)}, which is not a stage`));
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
function settingValues(manifest, chosen) {
|
|
581
|
+
const out = {};
|
|
582
|
+
for (const s of manifest.settings ?? []) {
|
|
583
|
+
const v = chosen !== void 0 && own(chosen, s.name) ? chosen[s.name] : void 0;
|
|
584
|
+
out[s.name] = typeof v === "string" && v !== "" ? v : s.default;
|
|
585
|
+
}
|
|
586
|
+
return out;
|
|
587
|
+
}
|
|
588
|
+
function resolvedClaims(manifest, host, chosen) {
|
|
589
|
+
const settings = settingValues(manifest, chosen);
|
|
590
|
+
const out = [];
|
|
591
|
+
for (const [kind, target] of Object.entries(manifest.targets[host] ?? {})) {
|
|
592
|
+
for (const claim of target.claims ?? []) {
|
|
593
|
+
const field = settings[claim.setting];
|
|
594
|
+
if (field !== void 0) out.push({ extension: manifest.id, host, kind, field, claim });
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
return out;
|
|
598
|
+
}
|
|
599
|
+
function claimClashes(claims) {
|
|
600
|
+
const out = [];
|
|
601
|
+
const first = /* @__PURE__ */ new Map();
|
|
602
|
+
for (const c of claims) {
|
|
603
|
+
const key = `${c.host}\0${c.kind}\0${c.field}`;
|
|
604
|
+
const prior = first.get(key);
|
|
605
|
+
if (prior === void 0) {
|
|
606
|
+
first.set(key, c);
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
if (prior.extension === c.extension) continue;
|
|
610
|
+
out.push(err2(
|
|
611
|
+
"claim-clash",
|
|
612
|
+
`${c.kind}.${c.field}`,
|
|
613
|
+
`extensions "${prior.extension}" and "${c.extension}" both claim the ${c.kind} field "${c.field}"`
|
|
614
|
+
));
|
|
615
|
+
}
|
|
616
|
+
return out;
|
|
617
|
+
}
|
|
618
|
+
function targetOf(manifest, host, kind) {
|
|
619
|
+
const kinds = manifest?.targets[host];
|
|
620
|
+
return kinds !== void 0 && own(kinds, kind) ? kinds[kind] : void 0;
|
|
621
|
+
}
|
|
622
|
+
function effectiveClaim(claim, hostDecl) {
|
|
623
|
+
return {
|
|
624
|
+
...claim,
|
|
625
|
+
...claim.values === void 0 && hostDecl.values !== void 0 ? { values: hostDecl.values } : {},
|
|
626
|
+
...claim.stages === void 0 && hostDecl.stages !== void 0 ? { stages: hostDecl.stages } : {},
|
|
627
|
+
...claim.default === void 0 && hostDecl.default !== void 0 ? { default: hostDecl.default } : {}
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// src/extensions/merge.ts
|
|
632
|
+
var ATOMIC = { kind: "atomic" };
|
|
633
|
+
var SET = { kind: "set" };
|
|
634
|
+
var mapOf = (of) => ({ kind: "map", of });
|
|
635
|
+
var keyedBy = (key, of, opts) => ({
|
|
636
|
+
kind: "keyed",
|
|
637
|
+
key,
|
|
638
|
+
of,
|
|
639
|
+
...opts?.order !== void 0 ? { order: opts.order } : {},
|
|
640
|
+
...opts?.renameWarning !== void 0 ? { renameWarning: opts.renameWarning } : {}
|
|
641
|
+
});
|
|
642
|
+
var objectOf = (fields, other) => ({ kind: "object", fields, ...other !== void 0 ? { other } : {} });
|
|
643
|
+
function stableStringify(value) {
|
|
644
|
+
if (value === void 0) return "undefined";
|
|
645
|
+
if (Array.isArray(value)) return `[${value.map((v) => v === void 0 ? "null" : stableStringify(v)).join(",")}]`;
|
|
646
|
+
if (isObject(value)) {
|
|
647
|
+
const keys = Object.keys(value).filter((k) => value[k] !== void 0).sort();
|
|
648
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(value[k])}`).join(",")}}`;
|
|
649
|
+
}
|
|
650
|
+
return JSON.stringify(value);
|
|
651
|
+
}
|
|
652
|
+
var defaultEqual = (a, b) => stableStringify(a) === stableStringify(b);
|
|
653
|
+
var asArr = (v) => Array.isArray(v) ? v : [];
|
|
654
|
+
function mergeTree(strategy, base, ours, theirs, opts) {
|
|
655
|
+
const ctx = { eq: opts?.equal ?? defaultEqual, conflicts: [], warnings: [] };
|
|
656
|
+
const merged = mergeValue(strategy, base, ours, theirs, "", "", ctx);
|
|
657
|
+
return { merged, conflicts: ctx.conflicts, warnings: ctx.warnings };
|
|
658
|
+
}
|
|
659
|
+
function deletedKind(base, ours, theirs) {
|
|
660
|
+
if (base !== void 0 && (ours === void 0 || theirs === void 0)) return "delete-vs-edit";
|
|
661
|
+
return base === void 0 ? "added-both" : "both-changed";
|
|
662
|
+
}
|
|
663
|
+
function merge3(b, o, t, id, path, ctx) {
|
|
664
|
+
if (ctx.eq(o, t)) return o;
|
|
665
|
+
if (ctx.eq(b, o)) return t;
|
|
666
|
+
if (ctx.eq(b, t)) return o;
|
|
667
|
+
ctx.conflicts.push({ id, path, base: b, ours: o, theirs: t, kind: deletedKind(b, o, t) });
|
|
668
|
+
return o;
|
|
669
|
+
}
|
|
670
|
+
function hasShape(spec, v) {
|
|
671
|
+
switch (spec.kind) {
|
|
672
|
+
case "atomic":
|
|
673
|
+
return true;
|
|
674
|
+
case "set":
|
|
675
|
+
case "keyed":
|
|
676
|
+
return Array.isArray(v);
|
|
677
|
+
case "map":
|
|
678
|
+
case "object":
|
|
679
|
+
return isObject(v);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
function mergeValue(spec, b, o, t, id, path, ctx) {
|
|
683
|
+
if (spec.kind === "atomic" || o === void 0 || t === void 0 || !hasShape(spec, o) || !hasShape(spec, t)) {
|
|
684
|
+
return merge3(b, o, t, id, path, ctx);
|
|
685
|
+
}
|
|
686
|
+
switch (spec.kind) {
|
|
687
|
+
case "set": {
|
|
688
|
+
const strings = (v) => new Set(asArr(v).filter((x) => typeof x === "string"));
|
|
689
|
+
const B = strings(b), O = strings(o), T = strings(t);
|
|
690
|
+
const out = /* @__PURE__ */ new Set();
|
|
691
|
+
for (const x of [...O, ...T]) if (O.has(x) && T.has(x) || !B.has(x)) out.add(x);
|
|
692
|
+
return [...out].sort();
|
|
693
|
+
}
|
|
694
|
+
case "map": {
|
|
695
|
+
const B = isObject(b) ? b : {}, O = o, T = t;
|
|
696
|
+
const out = {};
|
|
697
|
+
for (const k of Object.keys({ ...B, ...O, ...T }).sort()) {
|
|
698
|
+
const v = mergeValue(spec.of, B[k], O[k], T[k], id || k, `${path}.${k}`, ctx);
|
|
699
|
+
if (v !== void 0) out[k] = v;
|
|
700
|
+
}
|
|
701
|
+
return out;
|
|
702
|
+
}
|
|
703
|
+
case "object": {
|
|
704
|
+
const B = isObject(b) ? b : {}, O = o, T = t;
|
|
705
|
+
const out = {};
|
|
706
|
+
for (const k of Object.keys({ ...B, ...O, ...T }).sort()) {
|
|
707
|
+
const field = Object.prototype.hasOwnProperty.call(spec.fields, k) ? spec.fields[k] : spec.other ?? ATOMIC;
|
|
708
|
+
const v = mergeValue(field, B[k], O[k], T[k], id, path ? `${path}.${k}` : k, ctx);
|
|
709
|
+
if (v !== void 0) out[k] = v;
|
|
710
|
+
}
|
|
711
|
+
return out;
|
|
712
|
+
}
|
|
713
|
+
case "keyed":
|
|
714
|
+
return mergeKeyed(spec, asArr(b), o, t, path, ctx);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
function mergeKeyed(spec, B, O, T, path, ctx) {
|
|
718
|
+
const keyOf = (v) => isObject(v) && typeof v[spec.key] === "string" ? v[spec.key] : void 0;
|
|
719
|
+
const keysOf = (arr) => arr.map(keyOf).filter((k) => k !== void 0);
|
|
720
|
+
const toMap = (arr) => {
|
|
721
|
+
const m = /* @__PURE__ */ new Map();
|
|
722
|
+
for (const v of arr) {
|
|
723
|
+
const k = keyOf(v);
|
|
724
|
+
if (k !== void 0) m.set(k, v);
|
|
725
|
+
}
|
|
726
|
+
return m;
|
|
727
|
+
};
|
|
728
|
+
const Bm = toMap(B), Om = toMap(O), Tm = toMap(T);
|
|
729
|
+
const out = /* @__PURE__ */ new Map();
|
|
730
|
+
const here = (k) => `${path}[${k}]`;
|
|
731
|
+
for (const k of /* @__PURE__ */ new Set([...Bm.keys(), ...Om.keys(), ...Tm.keys()])) {
|
|
732
|
+
const b = Bm.get(k), o = Om.get(k), t = Tm.get(k);
|
|
733
|
+
const inB = Bm.has(k), inO = Om.has(k), inT = Tm.has(k);
|
|
734
|
+
if (inO && inT) {
|
|
735
|
+
if (!inB && !ctx.eq(o, t)) {
|
|
736
|
+
ctx.conflicts.push({ id: k, path: here(k), base: void 0, ours: o, theirs: t, kind: "added-both" });
|
|
737
|
+
out.set(k, o);
|
|
738
|
+
} else {
|
|
739
|
+
out.set(k, mergeValue(spec.of, b, o, t, k, here(k), ctx));
|
|
740
|
+
}
|
|
741
|
+
} else if (inO && !inT) {
|
|
742
|
+
if (inB && !ctx.eq(o, b)) {
|
|
743
|
+
ctx.conflicts.push({ id: k, path: here(k), base: b, ours: o, theirs: void 0, kind: "delete-vs-edit" });
|
|
744
|
+
out.set(k, o);
|
|
745
|
+
} else if (!inB) {
|
|
746
|
+
out.set(k, o);
|
|
747
|
+
}
|
|
748
|
+
} else if (!inO && inT) {
|
|
749
|
+
if (inB && !ctx.eq(t, b)) {
|
|
750
|
+
ctx.conflicts.push({ id: k, path: here(k), base: b, ours: void 0, theirs: t, kind: "delete-vs-edit" });
|
|
751
|
+
} else if (!inB) {
|
|
752
|
+
out.set(k, t);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
if (spec.renameWarning !== void 0) {
|
|
757
|
+
for (const [k, v] of out) {
|
|
758
|
+
const b = Bm.get(k);
|
|
759
|
+
if (isObject(b) && isObject(v) && typeof b["gameId"] === "string" && typeof v["gameId"] === "string" && b["gameId"] !== v["gameId"]) {
|
|
760
|
+
ctx.warnings.push({ id: k, path: here(k), message: `${spec.renameWarning} ("${b["gameId"]}" -> "${v["gameId"]}")` });
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
if (spec.order !== "preserve") return [...out.keys()].sort().map((k) => out.get(k));
|
|
765
|
+
return preservedOrder(keysOf(B), keysOf(O), keysOf(T), out, path, ctx).map((k) => out.get(k));
|
|
766
|
+
}
|
|
767
|
+
function preservedOrder(B, O, T, survivors, path, ctx) {
|
|
768
|
+
const inAll = new Set(B.filter((k) => O.includes(k) && T.includes(k) && survivors.has(k)));
|
|
769
|
+
const common = (seq) => seq.filter((k) => inAll.has(k));
|
|
770
|
+
const same = (a, b) => a.length === b.length && a.every((x, i) => x === b[i]);
|
|
771
|
+
const oMoved = !same(common(O), common(B)), tMoved = !same(common(T), common(B));
|
|
772
|
+
if (oMoved && tMoved && !same(common(O), common(T))) {
|
|
773
|
+
ctx.conflicts.push({ id: "", path: `${path}:order`, base: common(B), ours: common(O), theirs: common(T), kind: "both-changed" });
|
|
774
|
+
}
|
|
775
|
+
const [skeleton, other] = tMoved && !oMoved ? [T, O] : [O, T];
|
|
776
|
+
const result = skeleton.filter((k) => survivors.has(k));
|
|
777
|
+
for (let i = 0; i < other.length; i++) {
|
|
778
|
+
const k = other[i];
|
|
779
|
+
if (!survivors.has(k) || result.includes(k)) continue;
|
|
780
|
+
let at = 0;
|
|
781
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
782
|
+
const p = result.indexOf(other[j]);
|
|
783
|
+
if (p >= 0) {
|
|
784
|
+
at = p + 1;
|
|
785
|
+
break;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
result.splice(at, 0, k);
|
|
789
|
+
}
|
|
790
|
+
for (const k of [...survivors.keys()].sort()) if (!result.includes(k)) result.push(k);
|
|
791
|
+
return result;
|
|
792
|
+
}
|
|
793
|
+
function strategyForType(decl) {
|
|
794
|
+
switch (decl.type) {
|
|
795
|
+
case "flags":
|
|
796
|
+
return SET;
|
|
797
|
+
case "record":
|
|
798
|
+
return strategyForFields(decl.fields ?? []);
|
|
799
|
+
case "map":
|
|
800
|
+
return mapOf(decl.of !== void 0 ? strategyForType(decl.of) : ATOMIC);
|
|
801
|
+
case "list":
|
|
802
|
+
return decl.id !== void 0 ? keyedBy(decl.id, decl.of !== void 0 ? strategyForType(decl.of) : ATOMIC, { order: "preserve" }) : ATOMIC;
|
|
803
|
+
default:
|
|
804
|
+
return ATOMIC;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
function strategyForFields(fields) {
|
|
808
|
+
return objectOf(Object.fromEntries(fields.map((f) => [f.name, strategyForType(f)])));
|
|
809
|
+
}
|
|
810
|
+
function strategyForTarget(target) {
|
|
811
|
+
return strategyForFields(target.fields ?? []);
|
|
812
|
+
}
|
|
813
|
+
function extensionDataStrategy(manifest, host) {
|
|
814
|
+
const perNode = mapOf(ATOMIC);
|
|
815
|
+
const kinds = {};
|
|
816
|
+
for (const kind of Object.keys(manifest?.targets[host] ?? {})) {
|
|
817
|
+
const target = targetOf(manifest, host, kind);
|
|
818
|
+
if (target !== void 0) kinds[kind] = mapOf(strategyForTarget(target));
|
|
819
|
+
}
|
|
820
|
+
return objectOf({ entries: objectOf(kinds, perNode) });
|
|
821
|
+
}
|
|
822
|
+
var UNRESOLVED_KEY = "unresolved";
|
|
823
|
+
function unresolvedOf(shard) {
|
|
824
|
+
if (!isObject(shard) || !Object.prototype.hasOwnProperty.call(shard, UNRESOLVED_KEY)) return void 0;
|
|
825
|
+
const mark = shard[UNRESOLVED_KEY];
|
|
826
|
+
return Array.isArray(mark) ? mark : [];
|
|
827
|
+
}
|
|
828
|
+
function withoutMark(shard) {
|
|
829
|
+
if (!isObject(shard) || !Object.prototype.hasOwnProperty.call(shard, UNRESOLVED_KEY)) return shard;
|
|
830
|
+
const { [UNRESOLVED_KEY]: _mark, ...rest } = shard;
|
|
831
|
+
return rest;
|
|
832
|
+
}
|
|
833
|
+
var entryOf = (c) => ({
|
|
834
|
+
path: c.path,
|
|
835
|
+
kind: c.kind,
|
|
836
|
+
...c.base !== void 0 ? { base: c.base } : {},
|
|
837
|
+
...c.ours !== void 0 ? { ours: c.ours } : {},
|
|
838
|
+
...c.theirs !== void 0 ? { theirs: c.theirs } : {}
|
|
839
|
+
});
|
|
840
|
+
function markUnresolved(merged, conflicts, sides) {
|
|
841
|
+
const byPath = (mark) => new Map((mark ?? []).filter((e) => isObject(e) && typeof e.path === "string").map((e) => [e.path, e]));
|
|
842
|
+
const B = byPath(unresolvedOf(sides?.base)), O = byPath(unresolvedOf(sides?.ours)), T = byPath(unresolvedOf(sides?.theirs));
|
|
843
|
+
const out = /* @__PURE__ */ new Map();
|
|
844
|
+
for (const [p, e] of [...O, ...T]) {
|
|
845
|
+
const kept = O.has(p) && T.has(p) || !B.has(p);
|
|
846
|
+
if (kept && !out.has(p)) out.set(p, e);
|
|
847
|
+
}
|
|
848
|
+
for (const c of conflicts) out.set(c.path, entryOf(c));
|
|
849
|
+
const result = withoutMark(merged);
|
|
850
|
+
if (out.size === 0) return result;
|
|
851
|
+
return { ...result, [UNRESOLVED_KEY]: [...out.keys()].sort().map((p) => out.get(p)) };
|
|
852
|
+
}
|
|
853
|
+
function unresolvedProblems(shard) {
|
|
854
|
+
const mark = unresolvedOf(shard);
|
|
855
|
+
if (mark === void 0) return [];
|
|
856
|
+
const message = 'unresolved merge: this shard holds our side of a clash; resolve it, then remove the entry from "unresolved"';
|
|
857
|
+
if (mark.length === 0) return [{ severity: "error", code: "unresolved", path: UNRESOLVED_KEY, message }];
|
|
858
|
+
return mark.map((e) => ({
|
|
859
|
+
severity: "error",
|
|
860
|
+
code: "unresolved",
|
|
861
|
+
path: isObject(e) && typeof e.path === "string" ? e.path : UNRESOLVED_KEY,
|
|
862
|
+
message: `${message} (${isObject(e) && typeof e.kind === "string" ? e.kind : "clash"})`
|
|
863
|
+
}));
|
|
864
|
+
}
|
|
865
|
+
function mergeExtensionData(base, ours, theirs, opts) {
|
|
866
|
+
const strategy = extensionDataStrategy(opts.manifest, opts.host);
|
|
867
|
+
const { merged, conflicts, warnings } = mergeTree(
|
|
868
|
+
strategy,
|
|
869
|
+
withoutMark(base),
|
|
870
|
+
withoutMark(ours),
|
|
871
|
+
withoutMark(theirs),
|
|
872
|
+
opts.equal !== void 0 ? { equal: opts.equal } : void 0
|
|
873
|
+
);
|
|
874
|
+
return {
|
|
875
|
+
merged: markUnresolved(merged, conflicts, { base, ours, theirs }),
|
|
876
|
+
conflicts,
|
|
877
|
+
warnings
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
function orphanedEntries(data, exists) {
|
|
881
|
+
const out = [];
|
|
882
|
+
const entries = isObject(data) ? data["entries"] : void 0;
|
|
883
|
+
if (!isObject(entries)) return out;
|
|
884
|
+
for (const [kind, nodes] of Object.entries(entries)) {
|
|
885
|
+
if (!isObject(nodes)) continue;
|
|
886
|
+
for (const id of Object.keys(nodes)) {
|
|
887
|
+
if (!exists(kind, id)) {
|
|
888
|
+
out.push({
|
|
889
|
+
severity: "warning",
|
|
890
|
+
code: "orphan",
|
|
891
|
+
path: `entries.${kind}.${id}`,
|
|
892
|
+
message: `an entry for ${kind} "${id}", which no longer exists; it is kept, and not compiled`
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
return out;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
export { ATOMIC, HOSTS, MANIFEST_FILE_SUFFIX, MANIFEST_SCHEMA, SCALAR_TYPES, SET, UNRESOLVED_KEY, VALUE_TYPES, atIndex, atKey, claimClashes, compileEntry, compiledTarget, defaultOf, effectiveClaim, entryProblems, extensionDataStrategy, keyedBy, manifestProblems, mapOf, markUnresolved, mergeExtensionData, mergeTree, objectOf, orphanedEntries, parseManifest, renameStages, resolvedClaims, settingValues, stableStringify, strategyForTarget, strategyForType, targetOf, unresolvedOf, unresolvedProblems, valueProblems, withoutMark };
|
|
901
|
+
//# sourceMappingURL=extensions.js.map
|
|
902
|
+
//# sourceMappingURL=extensions.js.map
|