@patterkit/core 0.3.2 → 0.4.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/dist/index.cjs +73 -13
- package/dist/index.d.cts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +69 -9
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -21,12 +21,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
canonicalStringify: () => canonicalStringify,
|
|
24
|
-
effectiveGameId: () =>
|
|
25
|
-
gameIdify: () =>
|
|
24
|
+
effectiveGameId: () => import_model5.effectiveGameId,
|
|
25
|
+
gameIdify: () => import_model5.gameIdify,
|
|
26
26
|
hash32: () => hash32,
|
|
27
27
|
hash4: () => hash4,
|
|
28
28
|
humanizeNodeRefs: () => humanizeNodeRefs,
|
|
29
|
-
isValidGameId: () =>
|
|
29
|
+
isValidGameId: () => import_model5.isValidGameId,
|
|
30
30
|
newId: () => import_toolkit.newId,
|
|
31
31
|
parseSource: () => import_toolkit3.parseSource,
|
|
32
32
|
planProject: () => planProject,
|
|
@@ -53,7 +53,7 @@ function hash32(input) {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
// src/index.ts
|
|
56
|
-
var
|
|
56
|
+
var import_model5 = require("@patterkit/model");
|
|
57
57
|
|
|
58
58
|
// src/noderefs.ts
|
|
59
59
|
var VISIT_FN_RE = /\b(patter_visits|patter_seen|visits|seen)\s*\(\s*(?:(['"])(.*?)\2|([A-Za-z_]\w*))\s*\)/g;
|
|
@@ -106,6 +106,59 @@ function isContentlessNode(node) {
|
|
|
106
106
|
if (node.prompt) return false;
|
|
107
107
|
return Array.isArray(node.children) && node.children.every(isContentlessNode);
|
|
108
108
|
}
|
|
109
|
+
function contentlessJumpCycles(scenes) {
|
|
110
|
+
const firstBlock = /* @__PURE__ */ new Map();
|
|
111
|
+
for (const scene of scenes) {
|
|
112
|
+
const first = Array.isArray(scene.blocks) ? scene.blocks[0] : void 0;
|
|
113
|
+
if (typeof scene.id === "string" && typeof first?.id === "string") firstBlock.set(scene.id, first.id);
|
|
114
|
+
}
|
|
115
|
+
const leads = (children) => {
|
|
116
|
+
if (!Array.isArray(children)) return void 0;
|
|
117
|
+
for (const n of children) {
|
|
118
|
+
if (!n || typeof n !== "object" || n.condition) return void 0;
|
|
119
|
+
if (n.type === "snippet") {
|
|
120
|
+
if ((n.beats?.length ?? 0) > 0) return void 0;
|
|
121
|
+
if (!n.jump) continue;
|
|
122
|
+
if (n.jump.to === "END") return void 0;
|
|
123
|
+
const to = firstBlock.get(n.jump.to) ?? n.jump.to;
|
|
124
|
+
return { via: n.id, to };
|
|
125
|
+
}
|
|
126
|
+
if (n.type !== "group" || n.prompt || n.selector !== void 0 && n.selector !== "run") return void 0;
|
|
127
|
+
const inner = leads(n.children);
|
|
128
|
+
if (inner !== "through") return inner;
|
|
129
|
+
}
|
|
130
|
+
return "through";
|
|
131
|
+
};
|
|
132
|
+
const next = /* @__PURE__ */ new Map();
|
|
133
|
+
for (const scene of scenes) {
|
|
134
|
+
for (const block of Array.isArray(scene.blocks) ? scene.blocks : []) {
|
|
135
|
+
const l = leads(block.children);
|
|
136
|
+
if (l && l !== "through" && typeof block.id === "string") next.set(block.id, l);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const issues = [];
|
|
140
|
+
const settled = /* @__PURE__ */ new Set();
|
|
141
|
+
for (const startId of next.keys()) {
|
|
142
|
+
const path = [];
|
|
143
|
+
let at = startId;
|
|
144
|
+
while (at !== void 0 && !settled.has(at) && !path.includes(at)) {
|
|
145
|
+
path.push(at);
|
|
146
|
+
at = next.get(at)?.to;
|
|
147
|
+
}
|
|
148
|
+
if (at !== void 0 && path.includes(at)) {
|
|
149
|
+
const loop = path.slice(path.indexOf(at));
|
|
150
|
+
const via = loop.map((b) => next.get(b).via);
|
|
151
|
+
issues.push({
|
|
152
|
+
code: "jump-cycle",
|
|
153
|
+
severity: "warning",
|
|
154
|
+
id: via[0],
|
|
155
|
+
message: `${via.length === 1 ? `snippet '${via[0]}' jumps` : `snippets ${via.map((v) => `'${v}'`).join(", ")} jump`} round a loop with nothing to deliver (blocks ${[...loop, at].map((b) => `'${b}'`).join(" -> ")}); play reaching it never gets out`
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
for (const b of path) settled.add(b);
|
|
159
|
+
}
|
|
160
|
+
return issues;
|
|
161
|
+
}
|
|
109
162
|
function validateProject(input) {
|
|
110
163
|
const { project, scenes } = input;
|
|
111
164
|
const issues = [];
|
|
@@ -121,6 +174,7 @@ function validateProject(input) {
|
|
|
121
174
|
}
|
|
122
175
|
}
|
|
123
176
|
};
|
|
177
|
+
const addressOf = (node) => (0, import_model2.effectiveGameId)({ gameId: node.gameId, name: typeof node.name === "string" ? node.name : "" });
|
|
124
178
|
const seeId = (id, kind, where) => {
|
|
125
179
|
if (typeof id !== "string" || id.length === 0) {
|
|
126
180
|
issues.push({ code: "missing-id", message: `${kind} in ${where} has no id` });
|
|
@@ -145,7 +199,7 @@ function validateProject(input) {
|
|
|
145
199
|
if (scene.gameId && !(0, import_model2.isValidGameId)(scene.gameId)) {
|
|
146
200
|
issues.push({ code: "invalid-gameid", message: `${where} address '${scene.gameId}' is invalid (lowercase letters, digits, hyphens; no leading/trailing hyphen)`, id: scene.id });
|
|
147
201
|
}
|
|
148
|
-
const sgid = (
|
|
202
|
+
const sgid = addressOf(scene);
|
|
149
203
|
if (sgid) {
|
|
150
204
|
const prev = sceneGameIds.get(sgid);
|
|
151
205
|
if (prev && prev !== scene.id) issues.push({ code: "duplicate-gameid", message: `${where} address '${sgid}' is already used by scene '${prev}'`, id: scene.id });
|
|
@@ -180,7 +234,7 @@ function validateProject(input) {
|
|
|
180
234
|
if (block.gameId && !(0, import_model2.isValidGameId)(block.gameId)) {
|
|
181
235
|
issues.push({ code: "invalid-gameid", message: `block '${block.id}' address '${block.gameId}' is invalid (lowercase letters, digits, hyphens; no leading/trailing hyphen)`, id: block.id });
|
|
182
236
|
}
|
|
183
|
-
const bgid = (
|
|
237
|
+
const bgid = addressOf(block);
|
|
184
238
|
if (bgid) {
|
|
185
239
|
const prev = blockGameIds.get(bgid);
|
|
186
240
|
if (prev && prev !== block.id) issues.push({ code: "duplicate-gameid", message: `block '${block.id}' address '${bgid}' is already used by block '${prev}' in ${where}`, id: block.id });
|
|
@@ -243,9 +297,9 @@ function validateProject(input) {
|
|
|
243
297
|
}
|
|
244
298
|
const hasUnconditionalEscape = (node.children ?? []).some((opt) => !opt.condition);
|
|
245
299
|
if ((node.children?.length ?? 0) > 0 && !hasUnconditionalEscape) {
|
|
246
|
-
issues.push({ code: "choice-can-empty", message: `choice '${node.id}' in ${inBlock} has no unconditional option or fallback - it runs dry (falls through) if every condition fails`, id: node.id });
|
|
300
|
+
issues.push({ code: "choice-can-empty", message: `choice '${node.id}' in ${inBlock} has no unconditional option or fallback - it runs dry (falls through) if every condition fails`, id: node.id, severity: "warning" });
|
|
247
301
|
} else if (!hasSticky && fallbacks === 0 && choiceLoopsBack(node, block.id, bgid, scene.id, sgid)) {
|
|
248
|
-
issues.push({ code: "choice-can-empty", message: `choice '${node.id}' in ${inBlock} can be re-entered but every option is once-only with no fallback - it will run dry`, id: node.id });
|
|
302
|
+
issues.push({ code: "choice-can-empty", message: `choice '${node.id}' in ${inBlock} can be re-entered but every option is once-only with no fallback - it will run dry`, id: node.id, severity: "warning" });
|
|
249
303
|
}
|
|
250
304
|
}
|
|
251
305
|
return;
|
|
@@ -254,6 +308,10 @@ function validateProject(input) {
|
|
|
254
308
|
for (const beat of node.beats ?? []) {
|
|
255
309
|
seeId(beat.id, "beat", `snippet '${node.id}'`);
|
|
256
310
|
checkTags(beat.tags, `beat '${beat.id}'`, beat.id);
|
|
311
|
+
const kind = beat.kind;
|
|
312
|
+
if (kind !== "line" && kind !== "text" && kind !== "gameEvent") {
|
|
313
|
+
issues.push({ code: "malformed-node", message: `beat '${beat.id}' has an unknown kind '${kind}' (line, text, or gameEvent)`, id: beat.id });
|
|
314
|
+
}
|
|
257
315
|
if (beat.kind === "line" && beat.character && !castNames.has(beat.character)) {
|
|
258
316
|
issues.push({
|
|
259
317
|
code: "unknown-character",
|
|
@@ -267,6 +325,7 @@ function validateProject(input) {
|
|
|
267
325
|
}
|
|
268
326
|
}
|
|
269
327
|
if (input.authoring) validateAuthoring(input.authoring, project, allIds, issues);
|
|
328
|
+
issues.push(...contentlessJumpCycles(scenes));
|
|
270
329
|
for (const d of jumps) {
|
|
271
330
|
if (d.to === "END") continue;
|
|
272
331
|
if (!allIds.has(d.to)) {
|
|
@@ -476,11 +535,12 @@ function defaultMatchesType(value, decl) {
|
|
|
476
535
|
}
|
|
477
536
|
|
|
478
537
|
// src/scene-scaffold.ts
|
|
538
|
+
var import_model4 = require("@patterkit/model");
|
|
479
539
|
var trimSlash = (p) => p.replace(/\/+$/, "");
|
|
480
540
|
function planProject(opts) {
|
|
481
541
|
const locale = opts.locale ?? "en";
|
|
482
542
|
const project = {
|
|
483
|
-
schema:
|
|
543
|
+
schema: import_model4.PROJECT_SCHEMA,
|
|
484
544
|
project: { id: (0, import_toolkit.newId)("proj"), name: opts.name },
|
|
485
545
|
locales: { default: locale, all: [locale] },
|
|
486
546
|
voiced: true
|
|
@@ -532,10 +592,10 @@ function planScene(target, scaffold) {
|
|
|
532
592
|
gameId: scaffold.gameId,
|
|
533
593
|
blocks: [{ id: (0, import_toolkit.newId)("blk"), type: "block", name: "Main", children }]
|
|
534
594
|
};
|
|
535
|
-
const flow = { schema:
|
|
536
|
-
const locale = { schema:
|
|
537
|
-
const flowDir = trimSlash(target.
|
|
538
|
-
const stringsDir = trimSlash(target.
|
|
595
|
+
const flow = { schema: import_model4.FLOW_SCHEMA, scene };
|
|
596
|
+
const locale = { schema: import_model4.STRINGS_SCHEMA, scene: sceneId, locale: target.locale, default: true, strings };
|
|
597
|
+
const flowDir = trimSlash((0, import_model4.projectLayout)(target).flow);
|
|
598
|
+
const stringsDir = trimSlash((0, import_model4.projectLayout)(target).strings);
|
|
539
599
|
return {
|
|
540
600
|
sceneId,
|
|
541
601
|
writes: [
|
package/dist/index.d.cts
CHANGED
|
@@ -26,10 +26,13 @@ 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";
|
|
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";
|
|
30
30
|
message: string;
|
|
31
31
|
/** Id of the offending node/beat, where applicable. */
|
|
32
32
|
id?: string;
|
|
33
|
+
/** Absent means an error: the project should not be built. A warning still compiles and plays (a choice
|
|
34
|
+
* that can run dry gathers or falls through at runtime), and every front end shows it as one. */
|
|
35
|
+
severity?: "warning";
|
|
33
36
|
}
|
|
34
37
|
interface ProjectInput {
|
|
35
38
|
project: ProjectFile;
|
package/dist/index.d.ts
CHANGED
|
@@ -26,10 +26,13 @@ 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";
|
|
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";
|
|
30
30
|
message: string;
|
|
31
31
|
/** Id of the offending node/beat, where applicable. */
|
|
32
32
|
id?: string;
|
|
33
|
+
/** Absent means an error: the project should not be built. A warning still compiles and plays (a choice
|
|
34
|
+
* that can run dry gathers or falls through at runtime), and every front end shows it as one. */
|
|
35
|
+
severity?: "warning";
|
|
33
36
|
}
|
|
34
37
|
interface ProjectInput {
|
|
35
38
|
project: ProjectFile;
|
package/dist/index.js
CHANGED
|
@@ -68,6 +68,59 @@ function isContentlessNode(node) {
|
|
|
68
68
|
if (node.prompt) return false;
|
|
69
69
|
return Array.isArray(node.children) && node.children.every(isContentlessNode);
|
|
70
70
|
}
|
|
71
|
+
function contentlessJumpCycles(scenes) {
|
|
72
|
+
const firstBlock = /* @__PURE__ */ new Map();
|
|
73
|
+
for (const scene of scenes) {
|
|
74
|
+
const first = Array.isArray(scene.blocks) ? scene.blocks[0] : void 0;
|
|
75
|
+
if (typeof scene.id === "string" && typeof first?.id === "string") firstBlock.set(scene.id, first.id);
|
|
76
|
+
}
|
|
77
|
+
const leads = (children) => {
|
|
78
|
+
if (!Array.isArray(children)) return void 0;
|
|
79
|
+
for (const n of children) {
|
|
80
|
+
if (!n || typeof n !== "object" || n.condition) return void 0;
|
|
81
|
+
if (n.type === "snippet") {
|
|
82
|
+
if ((n.beats?.length ?? 0) > 0) return void 0;
|
|
83
|
+
if (!n.jump) continue;
|
|
84
|
+
if (n.jump.to === "END") return void 0;
|
|
85
|
+
const to = firstBlock.get(n.jump.to) ?? n.jump.to;
|
|
86
|
+
return { via: n.id, to };
|
|
87
|
+
}
|
|
88
|
+
if (n.type !== "group" || n.prompt || n.selector !== void 0 && n.selector !== "run") return void 0;
|
|
89
|
+
const inner = leads(n.children);
|
|
90
|
+
if (inner !== "through") return inner;
|
|
91
|
+
}
|
|
92
|
+
return "through";
|
|
93
|
+
};
|
|
94
|
+
const next = /* @__PURE__ */ new Map();
|
|
95
|
+
for (const scene of scenes) {
|
|
96
|
+
for (const block of Array.isArray(scene.blocks) ? scene.blocks : []) {
|
|
97
|
+
const l = leads(block.children);
|
|
98
|
+
if (l && l !== "through" && typeof block.id === "string") next.set(block.id, l);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const issues = [];
|
|
102
|
+
const settled = /* @__PURE__ */ new Set();
|
|
103
|
+
for (const startId of next.keys()) {
|
|
104
|
+
const path = [];
|
|
105
|
+
let at = startId;
|
|
106
|
+
while (at !== void 0 && !settled.has(at) && !path.includes(at)) {
|
|
107
|
+
path.push(at);
|
|
108
|
+
at = next.get(at)?.to;
|
|
109
|
+
}
|
|
110
|
+
if (at !== void 0 && path.includes(at)) {
|
|
111
|
+
const loop = path.slice(path.indexOf(at));
|
|
112
|
+
const via = loop.map((b) => next.get(b).via);
|
|
113
|
+
issues.push({
|
|
114
|
+
code: "jump-cycle",
|
|
115
|
+
severity: "warning",
|
|
116
|
+
id: via[0],
|
|
117
|
+
message: `${via.length === 1 ? `snippet '${via[0]}' jumps` : `snippets ${via.map((v) => `'${v}'`).join(", ")} jump`} round a loop with nothing to deliver (blocks ${[...loop, at].map((b) => `'${b}'`).join(" -> ")}); play reaching it never gets out`
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
for (const b of path) settled.add(b);
|
|
121
|
+
}
|
|
122
|
+
return issues;
|
|
123
|
+
}
|
|
71
124
|
function validateProject(input) {
|
|
72
125
|
const { project, scenes } = input;
|
|
73
126
|
const issues = [];
|
|
@@ -83,6 +136,7 @@ function validateProject(input) {
|
|
|
83
136
|
}
|
|
84
137
|
}
|
|
85
138
|
};
|
|
139
|
+
const addressOf = (node) => effectiveGameId({ gameId: node.gameId, name: typeof node.name === "string" ? node.name : "" });
|
|
86
140
|
const seeId = (id, kind, where) => {
|
|
87
141
|
if (typeof id !== "string" || id.length === 0) {
|
|
88
142
|
issues.push({ code: "missing-id", message: `${kind} in ${where} has no id` });
|
|
@@ -107,7 +161,7 @@ function validateProject(input) {
|
|
|
107
161
|
if (scene.gameId && !isValidGameId(scene.gameId)) {
|
|
108
162
|
issues.push({ code: "invalid-gameid", message: `${where} address '${scene.gameId}' is invalid (lowercase letters, digits, hyphens; no leading/trailing hyphen)`, id: scene.id });
|
|
109
163
|
}
|
|
110
|
-
const sgid =
|
|
164
|
+
const sgid = addressOf(scene);
|
|
111
165
|
if (sgid) {
|
|
112
166
|
const prev = sceneGameIds.get(sgid);
|
|
113
167
|
if (prev && prev !== scene.id) issues.push({ code: "duplicate-gameid", message: `${where} address '${sgid}' is already used by scene '${prev}'`, id: scene.id });
|
|
@@ -142,7 +196,7 @@ function validateProject(input) {
|
|
|
142
196
|
if (block.gameId && !isValidGameId(block.gameId)) {
|
|
143
197
|
issues.push({ code: "invalid-gameid", message: `block '${block.id}' address '${block.gameId}' is invalid (lowercase letters, digits, hyphens; no leading/trailing hyphen)`, id: block.id });
|
|
144
198
|
}
|
|
145
|
-
const bgid =
|
|
199
|
+
const bgid = addressOf(block);
|
|
146
200
|
if (bgid) {
|
|
147
201
|
const prev = blockGameIds.get(bgid);
|
|
148
202
|
if (prev && prev !== block.id) issues.push({ code: "duplicate-gameid", message: `block '${block.id}' address '${bgid}' is already used by block '${prev}' in ${where}`, id: block.id });
|
|
@@ -205,9 +259,9 @@ function validateProject(input) {
|
|
|
205
259
|
}
|
|
206
260
|
const hasUnconditionalEscape = (node.children ?? []).some((opt) => !opt.condition);
|
|
207
261
|
if ((node.children?.length ?? 0) > 0 && !hasUnconditionalEscape) {
|
|
208
|
-
issues.push({ code: "choice-can-empty", message: `choice '${node.id}' in ${inBlock} has no unconditional option or fallback - it runs dry (falls through) if every condition fails`, id: node.id });
|
|
262
|
+
issues.push({ code: "choice-can-empty", message: `choice '${node.id}' in ${inBlock} has no unconditional option or fallback - it runs dry (falls through) if every condition fails`, id: node.id, severity: "warning" });
|
|
209
263
|
} else if (!hasSticky && fallbacks === 0 && choiceLoopsBack(node, block.id, bgid, scene.id, sgid)) {
|
|
210
|
-
issues.push({ code: "choice-can-empty", message: `choice '${node.id}' in ${inBlock} can be re-entered but every option is once-only with no fallback - it will run dry`, id: node.id });
|
|
264
|
+
issues.push({ code: "choice-can-empty", message: `choice '${node.id}' in ${inBlock} can be re-entered but every option is once-only with no fallback - it will run dry`, id: node.id, severity: "warning" });
|
|
211
265
|
}
|
|
212
266
|
}
|
|
213
267
|
return;
|
|
@@ -216,6 +270,10 @@ function validateProject(input) {
|
|
|
216
270
|
for (const beat of node.beats ?? []) {
|
|
217
271
|
seeId(beat.id, "beat", `snippet '${node.id}'`);
|
|
218
272
|
checkTags(beat.tags, `beat '${beat.id}'`, beat.id);
|
|
273
|
+
const kind = beat.kind;
|
|
274
|
+
if (kind !== "line" && kind !== "text" && kind !== "gameEvent") {
|
|
275
|
+
issues.push({ code: "malformed-node", message: `beat '${beat.id}' has an unknown kind '${kind}' (line, text, or gameEvent)`, id: beat.id });
|
|
276
|
+
}
|
|
219
277
|
if (beat.kind === "line" && beat.character && !castNames.has(beat.character)) {
|
|
220
278
|
issues.push({
|
|
221
279
|
code: "unknown-character",
|
|
@@ -229,6 +287,7 @@ function validateProject(input) {
|
|
|
229
287
|
}
|
|
230
288
|
}
|
|
231
289
|
if (input.authoring) validateAuthoring(input.authoring, project, allIds, issues);
|
|
290
|
+
issues.push(...contentlessJumpCycles(scenes));
|
|
232
291
|
for (const d of jumps) {
|
|
233
292
|
if (d.to === "END") continue;
|
|
234
293
|
if (!allIds.has(d.to)) {
|
|
@@ -438,11 +497,12 @@ function defaultMatchesType(value, decl) {
|
|
|
438
497
|
}
|
|
439
498
|
|
|
440
499
|
// src/scene-scaffold.ts
|
|
500
|
+
import { projectLayout, FLOW_SCHEMA, PROJECT_SCHEMA, STRINGS_SCHEMA } from "@patterkit/model";
|
|
441
501
|
var trimSlash = (p) => p.replace(/\/+$/, "");
|
|
442
502
|
function planProject(opts) {
|
|
443
503
|
const locale = opts.locale ?? "en";
|
|
444
504
|
const project = {
|
|
445
|
-
schema:
|
|
505
|
+
schema: PROJECT_SCHEMA,
|
|
446
506
|
project: { id: newId("proj"), name: opts.name },
|
|
447
507
|
locales: { default: locale, all: [locale] },
|
|
448
508
|
voiced: true
|
|
@@ -494,10 +554,10 @@ function planScene(target, scaffold) {
|
|
|
494
554
|
gameId: scaffold.gameId,
|
|
495
555
|
blocks: [{ id: newId("blk"), type: "block", name: "Main", children }]
|
|
496
556
|
};
|
|
497
|
-
const flow = { schema:
|
|
498
|
-
const locale = { schema:
|
|
499
|
-
const flowDir = trimSlash(target.
|
|
500
|
-
const stringsDir = trimSlash(target.
|
|
557
|
+
const flow = { schema: FLOW_SCHEMA, scene };
|
|
558
|
+
const locale = { schema: STRINGS_SCHEMA, scene: sceneId, locale: target.locale, default: true, strings };
|
|
559
|
+
const flowDir = trimSlash(projectLayout(target).flow);
|
|
560
|
+
const stringsDir = trimSlash(projectLayout(target).strings);
|
|
501
561
|
return {
|
|
502
562
|
sceneId,
|
|
503
563
|
writes: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@patterkit/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
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.10.0",
|
|
38
38
|
"@wildwinter/toolkit": "^0.1.0",
|
|
39
39
|
"json5": "^2.2.3"
|
|
40
40
|
}
|