@patterkit/core 0.7.1 → 0.9.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 +71 -18
- package/dist/index.d.cts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +72 -19
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -229,6 +229,7 @@ function validateProject(input) {
|
|
|
229
229
|
return true;
|
|
230
230
|
};
|
|
231
231
|
validateProjectFile(project, issues);
|
|
232
|
+
validateGameDataValues(project, scenes, issues);
|
|
232
233
|
for (const scene of scenes) {
|
|
233
234
|
seeId(scene.id, "scene", "project");
|
|
234
235
|
checkPad(scene.padAfterDefault, `scene '${scene.id}' default`, scene.id);
|
|
@@ -505,12 +506,80 @@ function validateProjectFile(project, issues) {
|
|
|
505
506
|
if (!f.name.trim()) issues.push({ code: "invalid-gamedata-field", message: `${kind} gameData field has no name` });
|
|
506
507
|
else if (seen.has(key)) issues.push({ code: "invalid-gamedata-field", message: `${kind} gameData field '${f.name}' is declared more than once` });
|
|
507
508
|
else seen.add(key);
|
|
509
|
+
const subject = `${kind} gameData field '${f.name}'`;
|
|
508
510
|
if (f.type === "enum" && !(f.values && f.values.length)) {
|
|
509
|
-
issues.push({ code: "invalid-gamedata-field", message: `${
|
|
511
|
+
issues.push({ code: "invalid-gamedata-field", message: `${subject} is an enum but lists no values` });
|
|
512
|
+
} else if (f.type === "flags" && !(f.values && f.values.length)) {
|
|
513
|
+
issues.push({ code: "invalid-gamedata-field", message: `${subject} is flags but lists no values` });
|
|
514
|
+
}
|
|
515
|
+
if (f.type === "quality") {
|
|
516
|
+
for (const problem of (0, import_expr.qualityDeclarationProblems)(f.stages, f.default)) {
|
|
517
|
+
issues.push({ code: "invalid-gamedata-field", message: `${subject} ${qualityProblemWords(problem)}` });
|
|
518
|
+
}
|
|
519
|
+
} else if (f.default !== void 0 && !(0, import_expr.valueFits)(f, f.default, { noneChosen: true })) {
|
|
520
|
+
issues.push({
|
|
521
|
+
code: "invalid-gamedata-field",
|
|
522
|
+
message: `${subject} default ${JSON.stringify(f.default)} does not match type '${f.type}'`
|
|
523
|
+
});
|
|
510
524
|
}
|
|
511
525
|
}
|
|
512
526
|
}
|
|
513
527
|
}
|
|
528
|
+
function qualityProblemWords(problem) {
|
|
529
|
+
switch (problem.kind) {
|
|
530
|
+
case "too-few-stages":
|
|
531
|
+
return `is a quality but declares ${problem.count === 0 ? "no stages" : "only one stage"} (a ladder needs at least two)`;
|
|
532
|
+
case "empty-stage":
|
|
533
|
+
return "has an empty stage name";
|
|
534
|
+
case "duplicate-stage":
|
|
535
|
+
return `declares stage '${problem.stage}' more than once`;
|
|
536
|
+
case "default-not-a-stage":
|
|
537
|
+
return `default ${JSON.stringify(problem.value)} is not one of its stages`;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
function validateGameDataValues(project, scenes, issues) {
|
|
541
|
+
const declared = project.gameDataFields ?? {};
|
|
542
|
+
const check = (kind, id, values) => {
|
|
543
|
+
if (!values) return;
|
|
544
|
+
const fields = declared[kind] ?? [];
|
|
545
|
+
if (fields.length === 0) return;
|
|
546
|
+
for (const [name, value] of Object.entries(values)) {
|
|
547
|
+
const f = fields.find((d) => d.name === name);
|
|
548
|
+
if (!f || (0, import_expr.valueFits)(f, value, { noneChosen: true })) continue;
|
|
549
|
+
const where = `${kind} '${id ?? "?"}'`;
|
|
550
|
+
if (f.type === "quality" && typeof value === "string") {
|
|
551
|
+
issues.push({
|
|
552
|
+
code: "invalid-gamedata-value",
|
|
553
|
+
id,
|
|
554
|
+
message: `${where} gameData '${name}' is '${value}', which is not one of its stages`,
|
|
555
|
+
offLadder: { field: name, bad: value, options: (0, import_expr.stagesEitherSide)(f, value) }
|
|
556
|
+
});
|
|
557
|
+
} else {
|
|
558
|
+
issues.push({
|
|
559
|
+
code: "invalid-gamedata-value",
|
|
560
|
+
id,
|
|
561
|
+
message: `${where} gameData '${name}' ${JSON.stringify(value)} does not match type '${f.type}'`
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
for (const scene of scenes) {
|
|
567
|
+
check("scene", scene.id, scene.gameData);
|
|
568
|
+
for (const block of scene.blocks ?? []) {
|
|
569
|
+
check("block", block.id, block.gameData);
|
|
570
|
+
(0, import_model.walkNodes)(block.children ?? [], (node) => {
|
|
571
|
+
if (node.type !== "snippet") {
|
|
572
|
+
if (node.selector === "choice") {
|
|
573
|
+
for (const option of node.children ?? []) if (option.type === "group") check("option", option.id, option.gameData);
|
|
574
|
+
}
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
check("snippet", node.id, node.gameData);
|
|
578
|
+
for (const beat of node.beats ?? []) check(beat.kind, beat.id, beat.gameData);
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
514
583
|
function validateAuthoring(files, project, allIds, issues) {
|
|
515
584
|
const writingLadder = new Set((project.writingStatuses ?? import_model.DEFAULT_WRITING_STATUSES).map((s) => s.name));
|
|
516
585
|
const recordingLadder = new Set((project.recordingStatuses ?? import_model.DEFAULT_RECORDING_STATUSES).map((s) => s.name));
|
|
@@ -619,7 +688,7 @@ function checkDecls(decls, where, issues) {
|
|
|
619
688
|
}
|
|
620
689
|
}
|
|
621
690
|
}
|
|
622
|
-
if (decl.default !== void 0 && !
|
|
691
|
+
if (decl.default !== void 0 && !(0, import_expr.valueFits)(decl, decl.default)) {
|
|
623
692
|
issues.push({
|
|
624
693
|
code: "invalid-declaration",
|
|
625
694
|
message: `${where}: '${decl.name}' default ${JSON.stringify(decl.default)} does not match type '${decl.type}'`
|
|
@@ -627,22 +696,6 @@ function checkDecls(decls, where, issues) {
|
|
|
627
696
|
}
|
|
628
697
|
}
|
|
629
698
|
}
|
|
630
|
-
function defaultMatchesType(value, decl) {
|
|
631
|
-
switch (decl.type) {
|
|
632
|
-
case "boolean":
|
|
633
|
-
return typeof value === "boolean";
|
|
634
|
-
case "number":
|
|
635
|
-
return typeof value === "number";
|
|
636
|
-
case "string":
|
|
637
|
-
return typeof value === "string";
|
|
638
|
-
case "enum":
|
|
639
|
-
return typeof value === "string" && (decl.values?.includes(value) ?? true);
|
|
640
|
-
case "flags":
|
|
641
|
-
return Array.isArray(value) && value.every((v) => typeof v === "string" && (decl.values?.includes(v) ?? true));
|
|
642
|
-
case "quality":
|
|
643
|
-
return typeof value === "string" && !(0, import_expr.qualityDeclarationProblems)(decl.stages, value).some((p) => p.kind === "default-not-a-stage");
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
699
|
|
|
647
700
|
// src/scene-scaffold.ts
|
|
648
701
|
var import_model4 = require("@patterkit/model");
|
package/dist/index.d.cts
CHANGED
|
@@ -26,13 +26,20 @@ 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" | "unknown-qualifier" | "invalid-qualifier" | "invalid-pad" | "pad-overlaps-seam" | "prompt-pad-without-beat" | "qualifier-without-speaker" | "invalid-speaker-label" | "unused-speaker-label" | "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-speaker-label" | "unused-speaker-label" | "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" | "invalid-gamedata-value" | "jump-cycle";
|
|
30
30
|
message: string;
|
|
31
31
|
/** Id of the offending node/beat, where applicable. */
|
|
32
32
|
id?: string;
|
|
33
33
|
/** Absent means an error: the project should not be built. A warning still compiles and plays (a choice
|
|
34
34
|
* that can run dry gathers or falls through at runtime), and every front end shows it as one. */
|
|
35
35
|
severity?: "warning";
|
|
36
|
+
/** A quality gameData value off its field's ladder (a deleted or renamed stage, most often): the field, the
|
|
37
|
+
* value, and the stages to offer in its place, in ladder order. An editor's quick fix reads it. */
|
|
38
|
+
offLadder?: {
|
|
39
|
+
field: string;
|
|
40
|
+
bad: string;
|
|
41
|
+
options: string[];
|
|
42
|
+
};
|
|
36
43
|
}
|
|
37
44
|
interface ProjectInput {
|
|
38
45
|
project: ProjectFile;
|
package/dist/index.d.ts
CHANGED
|
@@ -26,13 +26,20 @@ 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" | "unknown-qualifier" | "invalid-qualifier" | "invalid-pad" | "pad-overlaps-seam" | "prompt-pad-without-beat" | "qualifier-without-speaker" | "invalid-speaker-label" | "unused-speaker-label" | "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-speaker-label" | "unused-speaker-label" | "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" | "invalid-gamedata-value" | "jump-cycle";
|
|
30
30
|
message: string;
|
|
31
31
|
/** Id of the offending node/beat, where applicable. */
|
|
32
32
|
id?: string;
|
|
33
33
|
/** Absent means an error: the project should not be built. A warning still compiles and plays (a choice
|
|
34
34
|
* that can run dry gathers or falls through at runtime), and every front end shows it as one. */
|
|
35
35
|
severity?: "warning";
|
|
36
|
+
/** A quality gameData value off its field's ladder (a deleted or renamed stage, most often): the field, the
|
|
37
|
+
* value, and the stages to offer in its place, in ladder order. An editor's quick fix reads it. */
|
|
38
|
+
offLadder?: {
|
|
39
|
+
field: string;
|
|
40
|
+
bad: string;
|
|
41
|
+
options: string[];
|
|
42
|
+
};
|
|
36
43
|
}
|
|
37
44
|
interface ProjectInput {
|
|
38
45
|
project: ProjectFile;
|
package/dist/index.js
CHANGED
|
@@ -58,7 +58,7 @@ ${indent}}`;
|
|
|
58
58
|
import { walkNodes, DEFAULT_WRITING_STATUSES, DEFAULT_RECORDING_STATUSES, DEFAULT_DOCUMENTATION_CLASSES, projectQualifiers, usedSpeakerPairs, 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
|
-
import { qualityDeclarationProblems } from "@wildwinter/expr";
|
|
61
|
+
import { qualityDeclarationProblems, stagesEitherSide, valueFits } from "@wildwinter/expr";
|
|
62
62
|
function choiceLoopsBack(choice, ...targets) {
|
|
63
63
|
const back = new Set(targets.filter((s) => typeof s === "string" && s.length > 0));
|
|
64
64
|
const hits = (n) => n.type === "snippet" ? !!n.jump && back.has(n.jump.to) : (n.children ?? []).some(hits);
|
|
@@ -191,6 +191,7 @@ function validateProject(input) {
|
|
|
191
191
|
return true;
|
|
192
192
|
};
|
|
193
193
|
validateProjectFile(project, issues);
|
|
194
|
+
validateGameDataValues(project, scenes, issues);
|
|
194
195
|
for (const scene of scenes) {
|
|
195
196
|
seeId(scene.id, "scene", "project");
|
|
196
197
|
checkPad(scene.padAfterDefault, `scene '${scene.id}' default`, scene.id);
|
|
@@ -467,12 +468,80 @@ function validateProjectFile(project, issues) {
|
|
|
467
468
|
if (!f.name.trim()) issues.push({ code: "invalid-gamedata-field", message: `${kind} gameData field has no name` });
|
|
468
469
|
else if (seen.has(key)) issues.push({ code: "invalid-gamedata-field", message: `${kind} gameData field '${f.name}' is declared more than once` });
|
|
469
470
|
else seen.add(key);
|
|
471
|
+
const subject = `${kind} gameData field '${f.name}'`;
|
|
470
472
|
if (f.type === "enum" && !(f.values && f.values.length)) {
|
|
471
|
-
issues.push({ code: "invalid-gamedata-field", message: `${
|
|
473
|
+
issues.push({ code: "invalid-gamedata-field", message: `${subject} is an enum but lists no values` });
|
|
474
|
+
} else if (f.type === "flags" && !(f.values && f.values.length)) {
|
|
475
|
+
issues.push({ code: "invalid-gamedata-field", message: `${subject} is flags but lists no values` });
|
|
476
|
+
}
|
|
477
|
+
if (f.type === "quality") {
|
|
478
|
+
for (const problem of qualityDeclarationProblems(f.stages, f.default)) {
|
|
479
|
+
issues.push({ code: "invalid-gamedata-field", message: `${subject} ${qualityProblemWords(problem)}` });
|
|
480
|
+
}
|
|
481
|
+
} else if (f.default !== void 0 && !valueFits(f, f.default, { noneChosen: true })) {
|
|
482
|
+
issues.push({
|
|
483
|
+
code: "invalid-gamedata-field",
|
|
484
|
+
message: `${subject} default ${JSON.stringify(f.default)} does not match type '${f.type}'`
|
|
485
|
+
});
|
|
472
486
|
}
|
|
473
487
|
}
|
|
474
488
|
}
|
|
475
489
|
}
|
|
490
|
+
function qualityProblemWords(problem) {
|
|
491
|
+
switch (problem.kind) {
|
|
492
|
+
case "too-few-stages":
|
|
493
|
+
return `is a quality but declares ${problem.count === 0 ? "no stages" : "only one stage"} (a ladder needs at least two)`;
|
|
494
|
+
case "empty-stage":
|
|
495
|
+
return "has an empty stage name";
|
|
496
|
+
case "duplicate-stage":
|
|
497
|
+
return `declares stage '${problem.stage}' more than once`;
|
|
498
|
+
case "default-not-a-stage":
|
|
499
|
+
return `default ${JSON.stringify(problem.value)} is not one of its stages`;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
function validateGameDataValues(project, scenes, issues) {
|
|
503
|
+
const declared = project.gameDataFields ?? {};
|
|
504
|
+
const check = (kind, id, values) => {
|
|
505
|
+
if (!values) return;
|
|
506
|
+
const fields = declared[kind] ?? [];
|
|
507
|
+
if (fields.length === 0) return;
|
|
508
|
+
for (const [name, value] of Object.entries(values)) {
|
|
509
|
+
const f = fields.find((d) => d.name === name);
|
|
510
|
+
if (!f || valueFits(f, value, { noneChosen: true })) continue;
|
|
511
|
+
const where = `${kind} '${id ?? "?"}'`;
|
|
512
|
+
if (f.type === "quality" && typeof value === "string") {
|
|
513
|
+
issues.push({
|
|
514
|
+
code: "invalid-gamedata-value",
|
|
515
|
+
id,
|
|
516
|
+
message: `${where} gameData '${name}' is '${value}', which is not one of its stages`,
|
|
517
|
+
offLadder: { field: name, bad: value, options: stagesEitherSide(f, value) }
|
|
518
|
+
});
|
|
519
|
+
} else {
|
|
520
|
+
issues.push({
|
|
521
|
+
code: "invalid-gamedata-value",
|
|
522
|
+
id,
|
|
523
|
+
message: `${where} gameData '${name}' ${JSON.stringify(value)} does not match type '${f.type}'`
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
for (const scene of scenes) {
|
|
529
|
+
check("scene", scene.id, scene.gameData);
|
|
530
|
+
for (const block of scene.blocks ?? []) {
|
|
531
|
+
check("block", block.id, block.gameData);
|
|
532
|
+
walkNodes(block.children ?? [], (node) => {
|
|
533
|
+
if (node.type !== "snippet") {
|
|
534
|
+
if (node.selector === "choice") {
|
|
535
|
+
for (const option of node.children ?? []) if (option.type === "group") check("option", option.id, option.gameData);
|
|
536
|
+
}
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
check("snippet", node.id, node.gameData);
|
|
540
|
+
for (const beat of node.beats ?? []) check(beat.kind, beat.id, beat.gameData);
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
476
545
|
function validateAuthoring(files, project, allIds, issues) {
|
|
477
546
|
const writingLadder = new Set((project.writingStatuses ?? DEFAULT_WRITING_STATUSES).map((s) => s.name));
|
|
478
547
|
const recordingLadder = new Set((project.recordingStatuses ?? DEFAULT_RECORDING_STATUSES).map((s) => s.name));
|
|
@@ -581,7 +650,7 @@ function checkDecls(decls, where, issues) {
|
|
|
581
650
|
}
|
|
582
651
|
}
|
|
583
652
|
}
|
|
584
|
-
if (decl.default !== void 0 && !
|
|
653
|
+
if (decl.default !== void 0 && !valueFits(decl, decl.default)) {
|
|
585
654
|
issues.push({
|
|
586
655
|
code: "invalid-declaration",
|
|
587
656
|
message: `${where}: '${decl.name}' default ${JSON.stringify(decl.default)} does not match type '${decl.type}'`
|
|
@@ -589,22 +658,6 @@ function checkDecls(decls, where, issues) {
|
|
|
589
658
|
}
|
|
590
659
|
}
|
|
591
660
|
}
|
|
592
|
-
function defaultMatchesType(value, decl) {
|
|
593
|
-
switch (decl.type) {
|
|
594
|
-
case "boolean":
|
|
595
|
-
return typeof value === "boolean";
|
|
596
|
-
case "number":
|
|
597
|
-
return typeof value === "number";
|
|
598
|
-
case "string":
|
|
599
|
-
return typeof value === "string";
|
|
600
|
-
case "enum":
|
|
601
|
-
return typeof value === "string" && (decl.values?.includes(value) ?? true);
|
|
602
|
-
case "flags":
|
|
603
|
-
return Array.isArray(value) && value.every((v) => typeof v === "string" && (decl.values?.includes(v) ?? true));
|
|
604
|
-
case "quality":
|
|
605
|
-
return typeof value === "string" && !qualityDeclarationProblems(decl.stages, value).some((p) => p.kind === "default-not-a-stage");
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
661
|
|
|
609
662
|
// src/scene-scaffold.ts
|
|
610
663
|
import { projectLayout, FLOW_SCHEMA, PROJECT_SCHEMA, STRINGS_SCHEMA } from "@patterkit/model";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@patterkit/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.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,9 +34,9 @@
|
|
|
34
34
|
"build": "tsup src/index.ts --format esm,cjs --dts --clean"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@patterkit/model": "0.
|
|
38
|
-
"@wildwinter/expr": "^0.
|
|
39
|
-
"@wildwinter/toolkit": "^0.
|
|
37
|
+
"@patterkit/model": "0.16.0",
|
|
38
|
+
"@wildwinter/expr": "^0.6.0",
|
|
39
|
+
"@wildwinter/toolkit": "^0.2.0",
|
|
40
40
|
"json5": "^2.2.3"
|
|
41
41
|
}
|
|
42
42
|
}
|