@patterkit/core 0.4.1 → 0.6.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/dist/index.cjs +77 -0
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +78 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -167,6 +167,45 @@ function validateProject(input) {
|
|
|
167
167
|
const sceneGameIds = /* @__PURE__ */ new Map();
|
|
168
168
|
const jumps = [];
|
|
169
169
|
const castNames = new Set((project.cast ?? []).map((c) => c.name));
|
|
170
|
+
const qualifierIds = project.qualifiers === void 0 || Array.isArray(project.qualifiers) ? new Set((0, import_model.projectQualifiers)(project).flatMap((q) => isQualifierEntry(q) && typeof q.gameId === "string" ? [q.gameId] : [])) : null;
|
|
171
|
+
const checkPad = (value, what, id) => {
|
|
172
|
+
if (value === void 0) return;
|
|
173
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < import_model.PAD_AFTER_MIN || value > import_model.PAD_AFTER_MAX) {
|
|
174
|
+
issues.push({ code: "invalid-pad", message: `${what} pause '${String(value)}' must be a number of seconds from ${import_model.PAD_AFTER_MIN} to ${import_model.PAD_AFTER_MAX}`, id });
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
const checkSeamPad = (beats) => {
|
|
178
|
+
const last = (beats ?? []).at(-1);
|
|
179
|
+
if (!last || last.kind !== "line" && last.kind !== "text" || typeof last.padAfter !== "number" || last.padAfter >= 0) return;
|
|
180
|
+
issues.push({
|
|
181
|
+
code: "pad-overlaps-seam",
|
|
182
|
+
severity: "warning",
|
|
183
|
+
id: last.id,
|
|
184
|
+
message: `beat '${last.id}' is its snippet's last line, so its negative pause can't cut in on what follows; it plays as no pause`
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
const checkPromptPad = (option) => {
|
|
188
|
+
const p = option.prompt;
|
|
189
|
+
if (!p || p.kind !== "line" && p.kind !== "text" || typeof p.padAfter !== "number" || p.padAfter >= 0) return;
|
|
190
|
+
if ((option.selector ?? "run") !== "run" || !Array.isArray(option.children)) return;
|
|
191
|
+
const first = option.children[0];
|
|
192
|
+
if (first && (first.type !== "snippet" || first.condition || (first.beats?.length ?? 0) > 0)) return;
|
|
193
|
+
issues.push({
|
|
194
|
+
code: "prompt-pad-without-beat",
|
|
195
|
+
severity: "warning",
|
|
196
|
+
id: p.id,
|
|
197
|
+
message: `prompt '${p.id}' has a negative pause, but its option plays nothing before it moves on, so nothing cuts in on it`
|
|
198
|
+
});
|
|
199
|
+
};
|
|
200
|
+
const checkQualifier = (beat, what) => {
|
|
201
|
+
if (beat.kind !== "line" || beat.qualifier === void 0) return;
|
|
202
|
+
if (qualifierIds && !qualifierIds.has(beat.qualifier)) {
|
|
203
|
+
issues.push({ code: "unknown-qualifier", message: `${what} '${beat.id}' qualifier '${beat.qualifier}' is not in the project's qualifiers`, id: beat.id });
|
|
204
|
+
}
|
|
205
|
+
if (!beat.character) {
|
|
206
|
+
issues.push({ code: "qualifier-without-speaker", severity: "warning", message: `${what} '${beat.id}' has the qualifier '${beat.qualifier}' but no speaker`, id: beat.id });
|
|
207
|
+
}
|
|
208
|
+
};
|
|
170
209
|
const checkTags = (tags, where, id) => {
|
|
171
210
|
for (const t of tags ?? []) {
|
|
172
211
|
if (typeof t !== "string" || t.length === 0 || /[\s,]/.test(t)) {
|
|
@@ -191,6 +230,7 @@ function validateProject(input) {
|
|
|
191
230
|
validateProjectFile(project, issues);
|
|
192
231
|
for (const scene of scenes) {
|
|
193
232
|
seeId(scene.id, "scene", "project");
|
|
233
|
+
checkPad(scene.padAfterDefault, `scene '${scene.id}' default`, scene.id);
|
|
194
234
|
const where = `scene '${scene.id}'`;
|
|
195
235
|
if (typeof scene.id === "string") addressable.add(scene.id);
|
|
196
236
|
if (!scene.name?.trim()) {
|
|
@@ -226,6 +266,7 @@ function validateProject(input) {
|
|
|
226
266
|
}
|
|
227
267
|
for (const block of scene.blocks) {
|
|
228
268
|
seeId(block.id, "block", where);
|
|
269
|
+
checkPad(block.padAfterDefault, `block '${block.id}' default`, block.id);
|
|
229
270
|
checkTags(block.tags, `block '${block.id}'`, block.id);
|
|
230
271
|
if (typeof block.id === "string") addressable.add(block.id);
|
|
231
272
|
if (!block.name?.trim()) {
|
|
@@ -266,6 +307,12 @@ function validateProject(input) {
|
|
|
266
307
|
(0, import_model.walkNodes)(block.children, (node) => {
|
|
267
308
|
const inBlock = `block '${block.id}'`;
|
|
268
309
|
checkTags(node.tags, `${node.type} '${node.id}'`, node.id);
|
|
310
|
+
checkPad(node.padAfterDefault, `${node.type} '${node.id}' default`, node.id);
|
|
311
|
+
if (node.type === "group" && node.prompt) checkPad(node.prompt.padAfter, `prompt '${node.prompt.id}'`, node.prompt.id);
|
|
312
|
+
if (node.type === "snippet") {
|
|
313
|
+
for (const beat of node.beats ?? []) if (beat.kind !== "gameEvent") checkPad(beat.padAfter, `beat '${beat.id}'`, beat.id);
|
|
314
|
+
checkSeamPad(node.beats);
|
|
315
|
+
}
|
|
269
316
|
if (node.type === "group") {
|
|
270
317
|
seeId(node.id, "group", inBlock);
|
|
271
318
|
if (!Array.isArray(node.children)) {
|
|
@@ -290,6 +337,8 @@ function validateProject(input) {
|
|
|
290
337
|
if (p.kind === "line" && p.character && !castNames.has(p.character)) {
|
|
291
338
|
issues.push({ code: "unknown-character", message: `prompt '${p.id}' speaker '${p.character}' is not in the project cast`, id: p.id });
|
|
292
339
|
}
|
|
340
|
+
checkQualifier(p, "prompt");
|
|
341
|
+
checkPromptPad(opt);
|
|
293
342
|
}
|
|
294
343
|
}
|
|
295
344
|
if (fallbacks > 1) {
|
|
@@ -319,6 +368,7 @@ function validateProject(input) {
|
|
|
319
368
|
id: beat.id
|
|
320
369
|
});
|
|
321
370
|
}
|
|
371
|
+
checkQualifier(beat, "beat");
|
|
322
372
|
}
|
|
323
373
|
if (node.jump) jumps.push({ to: node.jump.to, from: node.id });
|
|
324
374
|
});
|
|
@@ -340,6 +390,9 @@ function validateProject(input) {
|
|
|
340
390
|
}
|
|
341
391
|
return issues;
|
|
342
392
|
}
|
|
393
|
+
function isQualifierEntry(q) {
|
|
394
|
+
return typeof q === "object" && q !== null && !Array.isArray(q);
|
|
395
|
+
}
|
|
343
396
|
function validateProjectFile(project, issues) {
|
|
344
397
|
checkDecls(project.properties, "project properties", issues);
|
|
345
398
|
for (const scope of project.scopeRegistry?.scopes ?? []) {
|
|
@@ -391,6 +444,30 @@ function validateProjectFile(project, issues) {
|
|
|
391
444
|
if (project.recordingStatuses) {
|
|
392
445
|
checkLadderNames(project.recordingStatuses.map((s) => s.name), "recordingStatuses", issues);
|
|
393
446
|
}
|
|
447
|
+
const pad = project.padAfterDefault;
|
|
448
|
+
if (pad !== void 0 && (typeof pad !== "number" || !Number.isFinite(pad) || pad < import_model.PAD_AFTER_MIN || pad > import_model.PAD_AFTER_MAX)) {
|
|
449
|
+
issues.push({ code: "invalid-pad", message: `the project default pause '${String(pad)}' must be a number of seconds from ${import_model.PAD_AFTER_MIN} to ${import_model.PAD_AFTER_MAX}` });
|
|
450
|
+
}
|
|
451
|
+
const seenQualifiers = /* @__PURE__ */ new Set();
|
|
452
|
+
const qualifiers = project.qualifiers;
|
|
453
|
+
if (qualifiers !== void 0 && !Array.isArray(qualifiers)) {
|
|
454
|
+
issues.push({ code: "invalid-qualifier", message: "qualifiers must be a list" });
|
|
455
|
+
}
|
|
456
|
+
for (const q of Array.isArray(qualifiers) ? qualifiers : []) {
|
|
457
|
+
if (!isQualifierEntry(q)) {
|
|
458
|
+
issues.push({ code: "invalid-qualifier", message: `qualifier '${String(q)}' is not a qualifier (a name and an address)` });
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (typeof q.name !== "string" || !q.name.trim()) {
|
|
462
|
+
issues.push({ code: "invalid-qualifier", message: `qualifier '${String(q.gameId)}' has no name` });
|
|
463
|
+
}
|
|
464
|
+
if (typeof q.gameId !== "string" || !(0, import_model2.isValidGameId)(q.gameId)) {
|
|
465
|
+
issues.push({ code: "invalid-qualifier", message: `qualifier '${String(q.name)}' address '${String(q.gameId)}' is invalid (lowercase letters, digits, hyphens; no leading/trailing hyphen)` });
|
|
466
|
+
} else if (seenQualifiers.has(q.gameId)) {
|
|
467
|
+
issues.push({ code: "invalid-qualifier", message: `qualifier address '${q.gameId}' is used more than once` });
|
|
468
|
+
}
|
|
469
|
+
if (typeof q.gameId === "string") seenQualifiers.add(q.gameId);
|
|
470
|
+
}
|
|
394
471
|
for (const [kind, fields] of Object.entries(project.gameDataFields ?? {})) {
|
|
395
472
|
const seen = /* @__PURE__ */ new Set();
|
|
396
473
|
for (const f of fields ?? []) {
|
package/dist/index.d.cts
CHANGED
|
@@ -26,7 +26,7 @@ interface StringifyOptions {
|
|
|
26
26
|
declare function canonicalStringify(value: unknown, opts?: StringifyOptions): string;
|
|
27
27
|
|
|
28
28
|
interface ValidationIssue {
|
|
29
|
-
code: "duplicate-id" | "missing-id" | "malformed-node" | "missing-name" | "empty-scene" | "empty-snippet" | "empty-container" | "dangling-jump" | "jump-into-non-addressable" | "invalid-gameid" | "duplicate-gameid" | "unknown-character" | "invalid-temporary" | "invalid-declaration" | "invalid-status-ladder" | "invalid-locales" | "invalid-captions" | "missing-prompt" | "invalid-prompt" | "multiple-fallbacks" | "choice-can-empty" | "invalid-status-value" | "unknown-doc-class" | "invalid-tag" | "invalid-gamedata-field" | "jump-cycle";
|
|
29
|
+
code: "duplicate-id" | "missing-id" | "malformed-node" | "missing-name" | "empty-scene" | "empty-snippet" | "empty-container" | "dangling-jump" | "jump-into-non-addressable" | "invalid-gameid" | "duplicate-gameid" | "unknown-character" | "unknown-qualifier" | "invalid-qualifier" | "invalid-pad" | "pad-overlaps-seam" | "prompt-pad-without-beat" | "qualifier-without-speaker" | "invalid-temporary" | "invalid-declaration" | "invalid-status-ladder" | "invalid-locales" | "invalid-captions" | "missing-prompt" | "invalid-prompt" | "multiple-fallbacks" | "choice-can-empty" | "invalid-status-value" | "unknown-doc-class" | "invalid-tag" | "invalid-gamedata-field" | "jump-cycle";
|
|
30
30
|
message: string;
|
|
31
31
|
/** Id of the offending node/beat, where applicable. */
|
|
32
32
|
id?: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ interface StringifyOptions {
|
|
|
26
26
|
declare function canonicalStringify(value: unknown, opts?: StringifyOptions): string;
|
|
27
27
|
|
|
28
28
|
interface ValidationIssue {
|
|
29
|
-
code: "duplicate-id" | "missing-id" | "malformed-node" | "missing-name" | "empty-scene" | "empty-snippet" | "empty-container" | "dangling-jump" | "jump-into-non-addressable" | "invalid-gameid" | "duplicate-gameid" | "unknown-character" | "invalid-temporary" | "invalid-declaration" | "invalid-status-ladder" | "invalid-locales" | "invalid-captions" | "missing-prompt" | "invalid-prompt" | "multiple-fallbacks" | "choice-can-empty" | "invalid-status-value" | "unknown-doc-class" | "invalid-tag" | "invalid-gamedata-field" | "jump-cycle";
|
|
29
|
+
code: "duplicate-id" | "missing-id" | "malformed-node" | "missing-name" | "empty-scene" | "empty-snippet" | "empty-container" | "dangling-jump" | "jump-into-non-addressable" | "invalid-gameid" | "duplicate-gameid" | "unknown-character" | "unknown-qualifier" | "invalid-qualifier" | "invalid-pad" | "pad-overlaps-seam" | "prompt-pad-without-beat" | "qualifier-without-speaker" | "invalid-temporary" | "invalid-declaration" | "invalid-status-ladder" | "invalid-locales" | "invalid-captions" | "missing-prompt" | "invalid-prompt" | "multiple-fallbacks" | "choice-can-empty" | "invalid-status-value" | "unknown-doc-class" | "invalid-tag" | "invalid-gamedata-field" | "jump-cycle";
|
|
30
30
|
message: string;
|
|
31
31
|
/** Id of the offending node/beat, where applicable. */
|
|
32
32
|
id?: string;
|
package/dist/index.js
CHANGED
|
@@ -55,7 +55,7 @@ ${indent}}`;
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
// src/validate.ts
|
|
58
|
-
import { walkNodes, DEFAULT_WRITING_STATUSES, DEFAULT_RECORDING_STATUSES, DEFAULT_DOCUMENTATION_CLASSES } from "@patterkit/model";
|
|
58
|
+
import { walkNodes, DEFAULT_WRITING_STATUSES, DEFAULT_RECORDING_STATUSES, DEFAULT_DOCUMENTATION_CLASSES, projectQualifiers, PAD_AFTER_MIN, PAD_AFTER_MAX } from "@patterkit/model";
|
|
59
59
|
import { isValidGameId, effectiveGameId } from "@patterkit/model";
|
|
60
60
|
import { isValidPropertyName, propertyNameify, RESERVED_PROPERTY_NAMES } from "@patterkit/model";
|
|
61
61
|
function choiceLoopsBack(choice, ...targets) {
|
|
@@ -129,6 +129,45 @@ function validateProject(input) {
|
|
|
129
129
|
const sceneGameIds = /* @__PURE__ */ new Map();
|
|
130
130
|
const jumps = [];
|
|
131
131
|
const castNames = new Set((project.cast ?? []).map((c) => c.name));
|
|
132
|
+
const qualifierIds = project.qualifiers === void 0 || Array.isArray(project.qualifiers) ? new Set(projectQualifiers(project).flatMap((q) => isQualifierEntry(q) && typeof q.gameId === "string" ? [q.gameId] : [])) : null;
|
|
133
|
+
const checkPad = (value, what, id) => {
|
|
134
|
+
if (value === void 0) return;
|
|
135
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < PAD_AFTER_MIN || value > PAD_AFTER_MAX) {
|
|
136
|
+
issues.push({ code: "invalid-pad", message: `${what} pause '${String(value)}' must be a number of seconds from ${PAD_AFTER_MIN} to ${PAD_AFTER_MAX}`, id });
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
const checkSeamPad = (beats) => {
|
|
140
|
+
const last = (beats ?? []).at(-1);
|
|
141
|
+
if (!last || last.kind !== "line" && last.kind !== "text" || typeof last.padAfter !== "number" || last.padAfter >= 0) return;
|
|
142
|
+
issues.push({
|
|
143
|
+
code: "pad-overlaps-seam",
|
|
144
|
+
severity: "warning",
|
|
145
|
+
id: last.id,
|
|
146
|
+
message: `beat '${last.id}' is its snippet's last line, so its negative pause can't cut in on what follows; it plays as no pause`
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
const checkPromptPad = (option) => {
|
|
150
|
+
const p = option.prompt;
|
|
151
|
+
if (!p || p.kind !== "line" && p.kind !== "text" || typeof p.padAfter !== "number" || p.padAfter >= 0) return;
|
|
152
|
+
if ((option.selector ?? "run") !== "run" || !Array.isArray(option.children)) return;
|
|
153
|
+
const first = option.children[0];
|
|
154
|
+
if (first && (first.type !== "snippet" || first.condition || (first.beats?.length ?? 0) > 0)) return;
|
|
155
|
+
issues.push({
|
|
156
|
+
code: "prompt-pad-without-beat",
|
|
157
|
+
severity: "warning",
|
|
158
|
+
id: p.id,
|
|
159
|
+
message: `prompt '${p.id}' has a negative pause, but its option plays nothing before it moves on, so nothing cuts in on it`
|
|
160
|
+
});
|
|
161
|
+
};
|
|
162
|
+
const checkQualifier = (beat, what) => {
|
|
163
|
+
if (beat.kind !== "line" || beat.qualifier === void 0) return;
|
|
164
|
+
if (qualifierIds && !qualifierIds.has(beat.qualifier)) {
|
|
165
|
+
issues.push({ code: "unknown-qualifier", message: `${what} '${beat.id}' qualifier '${beat.qualifier}' is not in the project's qualifiers`, id: beat.id });
|
|
166
|
+
}
|
|
167
|
+
if (!beat.character) {
|
|
168
|
+
issues.push({ code: "qualifier-without-speaker", severity: "warning", message: `${what} '${beat.id}' has the qualifier '${beat.qualifier}' but no speaker`, id: beat.id });
|
|
169
|
+
}
|
|
170
|
+
};
|
|
132
171
|
const checkTags = (tags, where, id) => {
|
|
133
172
|
for (const t of tags ?? []) {
|
|
134
173
|
if (typeof t !== "string" || t.length === 0 || /[\s,]/.test(t)) {
|
|
@@ -153,6 +192,7 @@ function validateProject(input) {
|
|
|
153
192
|
validateProjectFile(project, issues);
|
|
154
193
|
for (const scene of scenes) {
|
|
155
194
|
seeId(scene.id, "scene", "project");
|
|
195
|
+
checkPad(scene.padAfterDefault, `scene '${scene.id}' default`, scene.id);
|
|
156
196
|
const where = `scene '${scene.id}'`;
|
|
157
197
|
if (typeof scene.id === "string") addressable.add(scene.id);
|
|
158
198
|
if (!scene.name?.trim()) {
|
|
@@ -188,6 +228,7 @@ function validateProject(input) {
|
|
|
188
228
|
}
|
|
189
229
|
for (const block of scene.blocks) {
|
|
190
230
|
seeId(block.id, "block", where);
|
|
231
|
+
checkPad(block.padAfterDefault, `block '${block.id}' default`, block.id);
|
|
191
232
|
checkTags(block.tags, `block '${block.id}'`, block.id);
|
|
192
233
|
if (typeof block.id === "string") addressable.add(block.id);
|
|
193
234
|
if (!block.name?.trim()) {
|
|
@@ -228,6 +269,12 @@ function validateProject(input) {
|
|
|
228
269
|
walkNodes(block.children, (node) => {
|
|
229
270
|
const inBlock = `block '${block.id}'`;
|
|
230
271
|
checkTags(node.tags, `${node.type} '${node.id}'`, node.id);
|
|
272
|
+
checkPad(node.padAfterDefault, `${node.type} '${node.id}' default`, node.id);
|
|
273
|
+
if (node.type === "group" && node.prompt) checkPad(node.prompt.padAfter, `prompt '${node.prompt.id}'`, node.prompt.id);
|
|
274
|
+
if (node.type === "snippet") {
|
|
275
|
+
for (const beat of node.beats ?? []) if (beat.kind !== "gameEvent") checkPad(beat.padAfter, `beat '${beat.id}'`, beat.id);
|
|
276
|
+
checkSeamPad(node.beats);
|
|
277
|
+
}
|
|
231
278
|
if (node.type === "group") {
|
|
232
279
|
seeId(node.id, "group", inBlock);
|
|
233
280
|
if (!Array.isArray(node.children)) {
|
|
@@ -252,6 +299,8 @@ function validateProject(input) {
|
|
|
252
299
|
if (p.kind === "line" && p.character && !castNames.has(p.character)) {
|
|
253
300
|
issues.push({ code: "unknown-character", message: `prompt '${p.id}' speaker '${p.character}' is not in the project cast`, id: p.id });
|
|
254
301
|
}
|
|
302
|
+
checkQualifier(p, "prompt");
|
|
303
|
+
checkPromptPad(opt);
|
|
255
304
|
}
|
|
256
305
|
}
|
|
257
306
|
if (fallbacks > 1) {
|
|
@@ -281,6 +330,7 @@ function validateProject(input) {
|
|
|
281
330
|
id: beat.id
|
|
282
331
|
});
|
|
283
332
|
}
|
|
333
|
+
checkQualifier(beat, "beat");
|
|
284
334
|
}
|
|
285
335
|
if (node.jump) jumps.push({ to: node.jump.to, from: node.id });
|
|
286
336
|
});
|
|
@@ -302,6 +352,9 @@ function validateProject(input) {
|
|
|
302
352
|
}
|
|
303
353
|
return issues;
|
|
304
354
|
}
|
|
355
|
+
function isQualifierEntry(q) {
|
|
356
|
+
return typeof q === "object" && q !== null && !Array.isArray(q);
|
|
357
|
+
}
|
|
305
358
|
function validateProjectFile(project, issues) {
|
|
306
359
|
checkDecls(project.properties, "project properties", issues);
|
|
307
360
|
for (const scope of project.scopeRegistry?.scopes ?? []) {
|
|
@@ -353,6 +406,30 @@ function validateProjectFile(project, issues) {
|
|
|
353
406
|
if (project.recordingStatuses) {
|
|
354
407
|
checkLadderNames(project.recordingStatuses.map((s) => s.name), "recordingStatuses", issues);
|
|
355
408
|
}
|
|
409
|
+
const pad = project.padAfterDefault;
|
|
410
|
+
if (pad !== void 0 && (typeof pad !== "number" || !Number.isFinite(pad) || pad < PAD_AFTER_MIN || pad > PAD_AFTER_MAX)) {
|
|
411
|
+
issues.push({ code: "invalid-pad", message: `the project default pause '${String(pad)}' must be a number of seconds from ${PAD_AFTER_MIN} to ${PAD_AFTER_MAX}` });
|
|
412
|
+
}
|
|
413
|
+
const seenQualifiers = /* @__PURE__ */ new Set();
|
|
414
|
+
const qualifiers = project.qualifiers;
|
|
415
|
+
if (qualifiers !== void 0 && !Array.isArray(qualifiers)) {
|
|
416
|
+
issues.push({ code: "invalid-qualifier", message: "qualifiers must be a list" });
|
|
417
|
+
}
|
|
418
|
+
for (const q of Array.isArray(qualifiers) ? qualifiers : []) {
|
|
419
|
+
if (!isQualifierEntry(q)) {
|
|
420
|
+
issues.push({ code: "invalid-qualifier", message: `qualifier '${String(q)}' is not a qualifier (a name and an address)` });
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
if (typeof q.name !== "string" || !q.name.trim()) {
|
|
424
|
+
issues.push({ code: "invalid-qualifier", message: `qualifier '${String(q.gameId)}' has no name` });
|
|
425
|
+
}
|
|
426
|
+
if (typeof q.gameId !== "string" || !isValidGameId(q.gameId)) {
|
|
427
|
+
issues.push({ code: "invalid-qualifier", message: `qualifier '${String(q.name)}' address '${String(q.gameId)}' is invalid (lowercase letters, digits, hyphens; no leading/trailing hyphen)` });
|
|
428
|
+
} else if (seenQualifiers.has(q.gameId)) {
|
|
429
|
+
issues.push({ code: "invalid-qualifier", message: `qualifier address '${q.gameId}' is used more than once` });
|
|
430
|
+
}
|
|
431
|
+
if (typeof q.gameId === "string") seenQualifiers.add(q.gameId);
|
|
432
|
+
}
|
|
356
433
|
for (const [kind, fields] of Object.entries(project.gameDataFields ?? {})) {
|
|
357
434
|
const seen = /* @__PURE__ */ new Set();
|
|
358
435
|
for (const f of fields ?? []) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@patterkit/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Patter core: stable id generation, readable-handle computation, canonical (de)serialisation, and the structural validator.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"build": "tsup src/index.ts --format esm,cjs --dts --clean"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@patterkit/model": "0.
|
|
37
|
+
"@patterkit/model": "0.12.0",
|
|
38
38
|
"@wildwinter/toolkit": "^0.1.0",
|
|
39
39
|
"json5": "^2.2.3"
|
|
40
40
|
}
|