@patterkit/compiler 0.8.0 → 0.10.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 +312 -18
- package/dist/index.d.cts +77 -2
- package/dist/index.d.ts +77 -2
- package/dist/index.js +320 -15
- package/package.json +6 -5
package/dist/index.cjs
CHANGED
|
@@ -24,6 +24,8 @@ __export(index_exports, {
|
|
|
24
24
|
PATTER_SCOPE: () => PATTER_SCOPE,
|
|
25
25
|
compileExpression: () => compileExpression,
|
|
26
26
|
exportBundle: () => exportBundle,
|
|
27
|
+
extensionNodes: () => extensionNodes,
|
|
28
|
+
extensionPass: () => extensionPass,
|
|
27
29
|
externalGameScopes: () => externalGameScopes,
|
|
28
30
|
hostScopesToSpec: () => import_dialect4.hostScopesToSpec,
|
|
29
31
|
projectScopes: () => projectScopes,
|
|
@@ -53,14 +55,14 @@ function sameScope(a, b) {
|
|
|
53
55
|
return norm(a) === norm(b);
|
|
54
56
|
}
|
|
55
57
|
function projectScopes(project, merged) {
|
|
56
|
-
const
|
|
57
|
-
if (!merged) return { host:
|
|
58
|
+
const own2 = project.scopeRegistry;
|
|
59
|
+
if (!merged) return { host: own2, external: [], notes: [] };
|
|
58
60
|
const notes = [];
|
|
59
61
|
const hosted = [];
|
|
60
62
|
const bundled = /* @__PURE__ */ new Set();
|
|
61
63
|
let changed = false;
|
|
62
64
|
const shared = (token) => token === PATTER_SCOPE ? void 0 : merged.spec.scopes.find((s) => s.token === token);
|
|
63
|
-
for (const h of
|
|
65
|
+
for (const h of own2?.scopes ?? []) {
|
|
64
66
|
const s = shared(h.token);
|
|
65
67
|
const who = merged.owners.get(h.token);
|
|
66
68
|
if (!s || !who) {
|
|
@@ -96,7 +98,7 @@ function projectScopes(project, merged) {
|
|
|
96
98
|
changed = true;
|
|
97
99
|
}
|
|
98
100
|
const external = merged.spec.scopes.filter((s) => s.token !== PATTER_SCOPE && !bundled.has(s.token));
|
|
99
|
-
const host = !changed ?
|
|
101
|
+
const host = !changed ? own2 : hosted.length ? { version: own2?.version ?? 1, scopes: hosted } : void 0;
|
|
100
102
|
return { host, external, notes };
|
|
101
103
|
}
|
|
102
104
|
function externalGameScopes(strict, merged) {
|
|
@@ -349,11 +351,284 @@ var import_dialect4 = require("@patterkit/dialect");
|
|
|
349
351
|
var import_scoperegistry = require("@wildwinter/scoperegistry");
|
|
350
352
|
var import_core = require("@patterkit/core");
|
|
351
353
|
var import_dialect3 = require("@patterkit/dialect");
|
|
354
|
+
var import_model3 = require("@patterkit/model");
|
|
355
|
+
|
|
356
|
+
// src/extensions.ts
|
|
352
357
|
var import_model2 = require("@patterkit/model");
|
|
358
|
+
var import_extensions = require("@wildwinter/toolkit/extensions");
|
|
359
|
+
function extensionNodes(project, scenes) {
|
|
360
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
361
|
+
const add = (id, kind, scene, gameData) => {
|
|
362
|
+
if (!nodes.has(id)) nodes.set(id, { kind, ...scene !== void 0 ? { scene } : {}, ...gameData !== void 0 ? { gameData } : {} });
|
|
363
|
+
};
|
|
364
|
+
add(project.project.id, "project", void 0, void 0);
|
|
365
|
+
for (const scene of scenes) {
|
|
366
|
+
const s = scene.id;
|
|
367
|
+
add(s, "scene", s, scene.gameData);
|
|
368
|
+
const walk = (children, inChoice) => {
|
|
369
|
+
for (const node of children ?? []) {
|
|
370
|
+
if (node.type === "group") {
|
|
371
|
+
if (inChoice) {
|
|
372
|
+
add(node.id, "option", s, node.gameData);
|
|
373
|
+
if (node.prompt) add(node.prompt.id, node.prompt.kind, s, node.prompt.gameData);
|
|
374
|
+
}
|
|
375
|
+
walk(node.children ?? [], node.selector === "choice");
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
add(node.id, "snippet", s, node.gameData);
|
|
379
|
+
for (const beat of node.beats ?? []) add(beat.id, beat.kind, s, beat.gameData);
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
for (const block of scene.blocks ?? []) {
|
|
383
|
+
add(block.id, "block", s, block.gameData);
|
|
384
|
+
walk(block.children ?? [], false);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return nodes;
|
|
388
|
+
}
|
|
389
|
+
var issueOf = (file, p, prefix = "", node) => ({
|
|
390
|
+
severity: p.severity,
|
|
391
|
+
file,
|
|
392
|
+
code: p.code,
|
|
393
|
+
...node !== void 0 ? { node } : {},
|
|
394
|
+
message: p.path === "" ? `${prefix}${p.message}` : `${prefix}${p.path}: ${p.message}`
|
|
395
|
+
});
|
|
396
|
+
var folderOf = (path) => {
|
|
397
|
+
const parts = path.split(/[\\/]/);
|
|
398
|
+
return parts.length >= 2 ? parts[parts.length - 2] : void 0;
|
|
399
|
+
};
|
|
400
|
+
var own = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
|
|
401
|
+
function extensionPass(input) {
|
|
402
|
+
const { project, scenes } = input;
|
|
403
|
+
const issues = [];
|
|
404
|
+
const projectPath = input.projectPath ?? "";
|
|
405
|
+
const taken = project.extensions ?? {};
|
|
406
|
+
const manifests = /* @__PURE__ */ new Map();
|
|
407
|
+
for (const m of input.manifests) {
|
|
408
|
+
const problems = (0, import_extensions.manifestProblems)(m.manifest);
|
|
409
|
+
for (const p of problems) issues.push(issueOf(m.path, p, "manifest "));
|
|
410
|
+
let sound2 = !problems.some((p) => p.severity === "error");
|
|
411
|
+
const id = m.manifest?.id;
|
|
412
|
+
if (sound2 && id !== m.id) {
|
|
413
|
+
issues.push({
|
|
414
|
+
severity: "error",
|
|
415
|
+
file: m.path,
|
|
416
|
+
message: `the manifest's id is "${String(id)}", but its file is named for "${m.id}"; a manifest is called <id>${import_model2.MANIFEST_EXTENSION}`
|
|
417
|
+
});
|
|
418
|
+
sound2 = false;
|
|
419
|
+
}
|
|
420
|
+
if (sound2 && m.manifest.targets.patter === void 0) {
|
|
421
|
+
issues.push({
|
|
422
|
+
severity: "warning",
|
|
423
|
+
file: m.path,
|
|
424
|
+
message: `"${m.id}" isn't a Patter extension: its manifest targets no Patter node kind, so it adds nothing here`
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
manifests.set(m.id, { path: m.path, manifest: m.manifest, sound: sound2 });
|
|
428
|
+
}
|
|
429
|
+
const takenList = [];
|
|
430
|
+
for (const [id, entry] of Object.entries(taken)) {
|
|
431
|
+
const m = manifests.get(id);
|
|
432
|
+
const version = typeof entry?.version === "string" ? entry.version : "";
|
|
433
|
+
const settings = entry?.settings ?? {};
|
|
434
|
+
if (m === void 0) {
|
|
435
|
+
issues.push({
|
|
436
|
+
severity: "warning",
|
|
437
|
+
file: projectPath,
|
|
438
|
+
message: `the project takes the Patter extension "${id}", but has no manifest for it at ${import_model2.EXTENSIONS_DIR}/${id}${import_model2.MANIFEST_EXTENSION}; its data is left as it is, and not compiled`
|
|
439
|
+
});
|
|
440
|
+
takenList.push({ id, settings });
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
if (!m.sound) {
|
|
444
|
+
takenList.push({ id, settings });
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
const manifest = m.manifest;
|
|
448
|
+
if (version !== manifest.version) {
|
|
449
|
+
issues.push({
|
|
450
|
+
severity: "warning",
|
|
451
|
+
file: projectPath,
|
|
452
|
+
message: `the project takes "${id}" ${version === "" ? "with no version" : `at version ${version}`}, but its manifest is version ${manifest.version}; run \`patter format\` to take it (renamed stages are renamed in the data as it does)`
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
const known = new Set((manifest.settings ?? []).map((s) => s.name));
|
|
456
|
+
for (const [name, value] of Object.entries(settings)) {
|
|
457
|
+
if (!known.has(name)) {
|
|
458
|
+
issues.push({ severity: "warning", file: projectPath, message: `"${id}" has no setting "${name}"; it's ignored` });
|
|
459
|
+
} else if (typeof value !== "string" || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
|
|
460
|
+
issues.push({
|
|
461
|
+
severity: "error",
|
|
462
|
+
file: projectPath,
|
|
463
|
+
message: `"${id}"'s setting "${name}" names a gameData field, so it's a field name, not ${JSON.stringify(value)}`
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
takenList.push({ id, settings, manifest });
|
|
468
|
+
}
|
|
469
|
+
const sound = new Map(takenList.filter((t) => t.manifest !== void 0).map((t) => [t.id, t.manifest]));
|
|
470
|
+
const nodes = extensionNodes(project, scenes);
|
|
471
|
+
const claims = takenList.flatMap((t) => t.manifest ? (0, import_extensions.resolvedClaims)(t.manifest, "patter", t.settings) : []);
|
|
472
|
+
for (const p of (0, import_extensions.claimClashes)(claims)) issues.push({ severity: p.severity, file: projectPath, code: p.code, message: p.message });
|
|
473
|
+
issues.push(...claimIssues(input, nodes, claims));
|
|
474
|
+
const exists = (kind, id) => kind === "cast" ? (project.cast ?? []).some((c) => c.name === id) : nodes.get(id)?.kind === kind;
|
|
475
|
+
const sceneIds = new Set(scenes.map((s) => s.id));
|
|
476
|
+
const values = /* @__PURE__ */ new Map();
|
|
477
|
+
const projectValues = /* @__PURE__ */ new Map();
|
|
478
|
+
for (const { path, shard } of [...input.shards].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0)) {
|
|
479
|
+
const folder = folderOf(path);
|
|
480
|
+
if (folder !== void 0 && shard.extension !== folder) {
|
|
481
|
+
issues.push({
|
|
482
|
+
severity: "error",
|
|
483
|
+
file: path,
|
|
484
|
+
message: `this shard says it holds "${String(shard.extension)}", but it sits in the folder for "${folder}"`
|
|
485
|
+
});
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
const ext = shard.extension;
|
|
489
|
+
if (!own(taken, ext)) {
|
|
490
|
+
issues.push({
|
|
491
|
+
severity: "warning",
|
|
492
|
+
file: path,
|
|
493
|
+
message: `data for the Patter extension "${ext}", which the project doesn't take; it's left as it is, and not compiled`
|
|
494
|
+
});
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
const entries = shard.entries;
|
|
498
|
+
if (typeof entries !== "object" || entries === null || Array.isArray(entries)) {
|
|
499
|
+
issues.push({ severity: "error", file: path, message: "entries are an object, by node kind and then node id" });
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
const shardScene = shard.scene === import_model2.PROJECT_LOCALE_SCENE ? void 0 : shard.scene;
|
|
503
|
+
const sceneKnown = shardScene === void 0 || sceneIds.has(shardScene);
|
|
504
|
+
if (!sceneKnown) {
|
|
505
|
+
issues.push({
|
|
506
|
+
severity: "warning",
|
|
507
|
+
file: path,
|
|
508
|
+
message: `this shard is for scene "${shard.scene}", which the project doesn't have; its entries are compiled where their nodes are`
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
for (const p of (0, import_extensions.orphanedEntries)(shard, exists)) issues.push(issueOf(path, p));
|
|
512
|
+
const manifest = sound.get(ext);
|
|
513
|
+
if (manifest === void 0) continue;
|
|
514
|
+
for (const [kind, byNode] of Object.entries(entries)) {
|
|
515
|
+
if (!import_extensions.HOSTS.patter.kinds.includes(kind)) {
|
|
516
|
+
issues.push({
|
|
517
|
+
severity: "warning",
|
|
518
|
+
file: path,
|
|
519
|
+
message: `entries.${kind}: "${kind}" isn't a Patter node kind; these entries are kept, and not compiled`
|
|
520
|
+
});
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
const target = (0, import_extensions.targetOf)(manifest, "patter", kind);
|
|
524
|
+
if (target === void 0) {
|
|
525
|
+
issues.push({
|
|
526
|
+
severity: "warning",
|
|
527
|
+
file: path,
|
|
528
|
+
message: `entries.${kind}: "${ext}" declares nothing for a ${kind}; these entries are kept, and not compiled`
|
|
529
|
+
});
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
if (typeof byNode !== "object" || byNode === null || Array.isArray(byNode)) continue;
|
|
533
|
+
for (const [id, entry] of Object.entries(byNode)) {
|
|
534
|
+
const node = nodes.get(id);
|
|
535
|
+
if (node === void 0 || node.kind !== kind) continue;
|
|
536
|
+
if (sceneKnown && node.scene !== shardScene) {
|
|
537
|
+
issues.push({
|
|
538
|
+
severity: "warning",
|
|
539
|
+
file: path,
|
|
540
|
+
node: id,
|
|
541
|
+
message: `entries.${kind}.${id} belongs ${node.scene === void 0 ? "in the project's own shard" : `in scene "${node.scene}"'s shard`}; it's compiled from here all the same`
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
const problems = (0, import_extensions.entryProblems)(target, entry, { exists });
|
|
545
|
+
for (const p of problems) issues.push(issueOf(path, p, `entries.${kind}.${id}${p.path === "" ? ": " : "."}`, id));
|
|
546
|
+
if (problems.some((p) => p.severity === "error")) continue;
|
|
547
|
+
const compiled = (0, import_extensions.compileEntry)(target, entry);
|
|
548
|
+
if (compiled === void 0) continue;
|
|
549
|
+
if (kind === "project") {
|
|
550
|
+
projectValues.set(ext, compiled);
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
values.set(id, { ...values.get(id) ?? {}, [ext]: compiled });
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
const bundle = {};
|
|
558
|
+
const chosen = new Map(takenList.map((t) => [t.id, t.settings]));
|
|
559
|
+
for (const [id, manifest] of [...sound.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
|
|
560
|
+
const fields = {};
|
|
561
|
+
for (const kind of Object.keys(manifest.targets.patter ?? {}).sort()) {
|
|
562
|
+
const target = (0, import_extensions.targetOf)(manifest, "patter", kind);
|
|
563
|
+
if (target !== void 0) fields[kind] = (0, import_extensions.compiledTarget)(target);
|
|
564
|
+
}
|
|
565
|
+
const projectEntry = projectValues.get(id);
|
|
566
|
+
const settings = (manifest.settings ?? []).length > 0 ? sortRecord((0, import_extensions.settingValues)(manifest, chosen.get(id))) : void 0;
|
|
567
|
+
bundle[id] = {
|
|
568
|
+
version: manifest.version,
|
|
569
|
+
fields,
|
|
570
|
+
...projectEntry !== void 0 ? { project: projectEntry } : {},
|
|
571
|
+
...settings !== void 0 ? { settings } : {}
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
const sortedNodes = new Map([...values.entries()].map(([id, v]) => [id, sortRecord(v)]));
|
|
575
|
+
return { issues, compiled: { bundle, nodes: sortedNodes } };
|
|
576
|
+
}
|
|
577
|
+
function sortRecord(r) {
|
|
578
|
+
return Object.fromEntries(Object.keys(r).sort().map((k) => [k, r[k]]));
|
|
579
|
+
}
|
|
580
|
+
function claimIssues(input, nodes, claims) {
|
|
581
|
+
const issues = [];
|
|
582
|
+
const projectPath = input.projectPath ?? "";
|
|
583
|
+
const declared = input.project.gameDataFields ?? {};
|
|
584
|
+
for (const claim of claims) {
|
|
585
|
+
const decl = (declared[claim.kind] ?? []).find((d) => d.name === claim.field);
|
|
586
|
+
if (decl === void 0) {
|
|
587
|
+
issues.push({
|
|
588
|
+
severity: "warning",
|
|
589
|
+
file: projectPath,
|
|
590
|
+
message: `the Patter extension "${claim.extension}" claims the ${claim.kind} gameData field "${claim.field}", which the project doesn't declare`
|
|
591
|
+
});
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
if (decl.type !== claim.claim.type) {
|
|
595
|
+
issues.push({
|
|
596
|
+
severity: "error",
|
|
597
|
+
file: projectPath,
|
|
598
|
+
message: `the Patter extension "${claim.extension}" claims the ${claim.kind} gameData field "${claim.field}" as ${claim.claim.type}, but the project declares it as ${decl.type}`
|
|
599
|
+
});
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
const effective = (0, import_extensions.effectiveClaim)(claim.claim, decl);
|
|
603
|
+
for (const [id, node] of nodes) {
|
|
604
|
+
if (node.kind !== claim.kind || node.gameData === void 0 || !own(node.gameData, claim.field)) continue;
|
|
605
|
+
const file = (node.scene !== void 0 ? input.sceneFiles?.[node.scene] : void 0) ?? projectPath;
|
|
606
|
+
for (const p of (0, import_extensions.valueProblems)(effective, node.gameData[claim.field])) {
|
|
607
|
+
issues.push({
|
|
608
|
+
severity: p.severity,
|
|
609
|
+
file,
|
|
610
|
+
node: id,
|
|
611
|
+
code: p.code,
|
|
612
|
+
message: `${claim.kind} '${id}' gameData "${claim.field}", as the Patter extension "${claim.extension}" claims it: ${p.message}`
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
return issues;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// src/export.ts
|
|
621
|
+
function compileBeat(beat, ctx) {
|
|
622
|
+
const values = ctx.extensions.get(beat.id);
|
|
623
|
+
if (values === void 0 && !Object.prototype.hasOwnProperty.call(beat, "extensions")) return beat;
|
|
624
|
+
const { extensions: _source, ...rest } = beat;
|
|
625
|
+
return values !== void 0 ? { ...rest, extensions: values } : rest;
|
|
626
|
+
}
|
|
353
627
|
function compileEffect(e, foreign) {
|
|
354
628
|
return { kind: "set", target: e.target, value: compileExpression(e.value, foreign) };
|
|
355
629
|
}
|
|
356
|
-
function compileNode(node,
|
|
630
|
+
function compileNode(node, ctx) {
|
|
631
|
+
const { foreign } = ctx;
|
|
357
632
|
if (node.type === "group") {
|
|
358
633
|
return {
|
|
359
634
|
id: node.id,
|
|
@@ -362,13 +637,15 @@ function compileNode(node, foreign) {
|
|
|
362
637
|
selector: node.selector,
|
|
363
638
|
shared: node.shared,
|
|
364
639
|
options: node.options,
|
|
365
|
-
children: node.children.map((c) => compileNode(c,
|
|
640
|
+
children: node.children.map((c) => compileNode(c, ctx)),
|
|
366
641
|
padAfterDefault: node.padAfterDefault,
|
|
367
642
|
// line padding: the default for the beats inside
|
|
368
643
|
gameData: node.gameData,
|
|
644
|
+
extensions: ctx.extensions.get(node.id),
|
|
645
|
+
// a choice's option's extension values
|
|
369
646
|
tags: node.tags,
|
|
370
647
|
// author tags (#215), accumulated at runtime
|
|
371
|
-
prompt: node.prompt,
|
|
648
|
+
prompt: node.prompt && compileBeat(node.prompt, ctx),
|
|
372
649
|
// option-position only (spec §5)
|
|
373
650
|
secretUntilEligible: node.secretUntilEligible,
|
|
374
651
|
sticky: node.sticky,
|
|
@@ -379,12 +656,13 @@ function compileNode(node, foreign) {
|
|
|
379
656
|
id: node.id,
|
|
380
657
|
type: "snippet",
|
|
381
658
|
condition: node.condition !== void 0 ? compileExpression(node.condition, foreign) : void 0,
|
|
382
|
-
beats: node.beats,
|
|
659
|
+
beats: node.beats && node.beats.map((b) => compileBeat(b, ctx)),
|
|
383
660
|
onEnter: node.onEnter?.map((e) => compileEffect(e, foreign)),
|
|
384
661
|
onExit: node.onExit?.map((e) => compileEffect(e, foreign)),
|
|
385
662
|
padAfterDefault: node.padAfterDefault,
|
|
386
663
|
// line padding: the default for the beats inside
|
|
387
664
|
gameData: node.gameData,
|
|
665
|
+
extensions: ctx.extensions.get(node.id),
|
|
388
666
|
tags: node.tags,
|
|
389
667
|
// author tags (#215)
|
|
390
668
|
jump: node.jump,
|
|
@@ -393,22 +671,24 @@ function compileNode(node, foreign) {
|
|
|
393
671
|
fallback: node.fallback
|
|
394
672
|
};
|
|
395
673
|
}
|
|
396
|
-
function compileBlock(block,
|
|
674
|
+
function compileBlock(block, ctx) {
|
|
397
675
|
return {
|
|
398
676
|
id: block.id,
|
|
399
677
|
type: "block",
|
|
400
678
|
name: block.name,
|
|
401
679
|
...block.gameId ? { gameId: block.gameId } : {},
|
|
402
680
|
// host-facing address; runtime falls back to the name slug
|
|
403
|
-
children: block.children.map((c) => compileNode(c,
|
|
681
|
+
children: block.children.map((c) => compileNode(c, ctx)),
|
|
404
682
|
padAfterDefault: block.padAfterDefault,
|
|
405
683
|
// line padding: the default for the beats inside
|
|
406
684
|
gameData: block.gameData,
|
|
685
|
+
extensions: ctx.extensions.get(block.id),
|
|
407
686
|
tags: block.tags
|
|
408
687
|
// author tags (#215)
|
|
409
688
|
};
|
|
410
689
|
}
|
|
411
|
-
function compileScene(scene,
|
|
690
|
+
function compileScene(scene, ctx) {
|
|
691
|
+
const { foreign } = ctx;
|
|
412
692
|
return {
|
|
413
693
|
id: scene.id,
|
|
414
694
|
type: "scene",
|
|
@@ -418,11 +698,12 @@ function compileScene(scene, foreign) {
|
|
|
418
698
|
padAfterDefault: scene.padAfterDefault,
|
|
419
699
|
// line padding: the default for the beats inside
|
|
420
700
|
gameData: scene.gameData,
|
|
701
|
+
extensions: ctx.extensions.get(scene.id),
|
|
421
702
|
tags: scene.tags,
|
|
422
703
|
// author tags (#215)
|
|
423
704
|
onEntry: scene.onEntry?.map((e) => compileEffect(e, foreign)),
|
|
424
705
|
sceneProps: scene.sceneProps,
|
|
425
|
-
blocks: scene.blocks.map((b) => compileBlock(b,
|
|
706
|
+
blocks: scene.blocks.map((b) => compileBlock(b, ctx))
|
|
426
707
|
};
|
|
427
708
|
}
|
|
428
709
|
function externalScopesIn(scenes, external) {
|
|
@@ -456,12 +737,16 @@ function exportBundle(input) {
|
|
|
456
737
|
version: foreignScopes?.version ?? 1,
|
|
457
738
|
scopes: [...foreignScopes?.scopes ?? [], ...gameExternal.map((s) => ({ token: s.token }))]
|
|
458
739
|
});
|
|
740
|
+
const ext = input.extensions;
|
|
741
|
+
const compiledExt = project.extensions !== void 0 || ext !== void 0 && (ext.manifests.length > 0 || ext.shards.length > 0) ? extensionPass({ project, scenes, manifests: ext?.manifests ?? [], shards: ext?.shards ?? [] }).compiled : void 0;
|
|
742
|
+
const extensionSection = compiledExt !== void 0 && Object.keys(compiledExt.bundle).length > 0 ? compiledExt.bundle : void 0;
|
|
743
|
+
const ctx = { foreign: compileScopes, extensions: compiledExt?.nodes ?? /* @__PURE__ */ new Map() };
|
|
459
744
|
const scenesOut = {};
|
|
460
|
-
for (const scene of scenes) scenesOut[scene.id] = compileScene(scene,
|
|
745
|
+
for (const scene of scenes) scenesOut[scene.id] = compileScene(scene, ctx);
|
|
461
746
|
const externalScopes = externalScopesIn(scenesOut, /* @__PURE__ */ new Set([...import_dialect3.EXTERNAL_SCOPES, ...gameExternal.map((s) => s.token)]));
|
|
462
|
-
const qualifiers = (0,
|
|
463
|
-
const usedPairs = new Set((0,
|
|
464
|
-
const speakerPattern = qualifiers.length > 0 && project.speakerPattern !== void 0 && project.speakerPattern !==
|
|
747
|
+
const qualifiers = (0, import_model3.usedQualifiers)(project, scenes).map((q) => ({ gameId: q.gameId, name: q.name }));
|
|
748
|
+
const usedPairs = new Set((0, import_model3.usedSpeakerPairs)(scenes).map((p) => `${p.character}\0${p.qualifier}`));
|
|
749
|
+
const speakerPattern = qualifiers.length > 0 && project.speakerPattern !== void 0 && project.speakerPattern !== import_model3.DEFAULT_SPEAKER_PATTERN ? project.speakerPattern : void 0;
|
|
465
750
|
const strings = {};
|
|
466
751
|
for (const loc of locales) {
|
|
467
752
|
if (includeLocales && !includeLocales.includes(loc.locale)) continue;
|
|
@@ -488,7 +773,9 @@ function exportBundle(input) {
|
|
|
488
773
|
properties: project.properties ?? [],
|
|
489
774
|
...foreignScopes ? { scopeRegistry: foreignScopes } : {},
|
|
490
775
|
...qualifiers.length > 0 ? { qualifiers } : {},
|
|
491
|
-
...typeof project.padAfterDefault === "number" ? { padAfterDefault: project.padAfterDefault } : {}
|
|
776
|
+
...typeof project.padAfterDefault === "number" ? { padAfterDefault: project.padAfterDefault } : {},
|
|
777
|
+
// Each node's values are in `scenes`; the declarations and the project's own values are here.
|
|
778
|
+
...extensionSection !== void 0 ? { extensions: extensionSection } : {}
|
|
492
779
|
}, { trailingComma: false })),
|
|
493
780
|
// Structure-only fingerprint (live bundle refresh): the same hash with the string tables left
|
|
494
781
|
// out. Same structureHash + a different hash = a text-only edit, safe to swap in place with
|
|
@@ -499,7 +786,8 @@ function exportBundle(input) {
|
|
|
499
786
|
properties: project.properties ?? [],
|
|
500
787
|
...foreignScopes ? { scopeRegistry: foreignScopes } : {},
|
|
501
788
|
...qualifiers.length > 0 ? { qualifiers } : {},
|
|
502
|
-
...typeof project.padAfterDefault === "number" ? { padAfterDefault: project.padAfterDefault } : {}
|
|
789
|
+
...typeof project.padAfterDefault === "number" ? { padAfterDefault: project.padAfterDefault } : {},
|
|
790
|
+
...extensionSection !== void 0 ? { extensions: extensionSection } : {}
|
|
503
791
|
}, { trailingComma: false }))
|
|
504
792
|
},
|
|
505
793
|
voiced: project.voiced ?? false,
|
|
@@ -528,6 +816,10 @@ function exportBundle(input) {
|
|
|
528
816
|
gameDataFields: project.gameDataFields && Object.fromEntries(
|
|
529
817
|
Object.entries(project.gameDataFields).map(([kind, fields]) => [kind, resolved(fields)])
|
|
530
818
|
),
|
|
819
|
+
// The extensions the project takes: each one's version, its compiled declarations by node kind (every
|
|
820
|
+
// default resolved, editor-only fields and the editor's words left out), the project node's values, and
|
|
821
|
+
// its settings, each the project's value or else the default.
|
|
822
|
+
...extensionSection !== void 0 ? { extensions: extensionSection } : {},
|
|
531
823
|
// Closed-caption delimiters ride along only when the project pins a non-default pair (#214); the
|
|
532
824
|
// runtime falls back to `[` / `]` when absent, so default projects keep their exact prior bundle.
|
|
533
825
|
...project.closedCaptions ? { closedCaptions: project.closedCaptions } : {},
|
|
@@ -543,6 +835,8 @@ function exportBundle(input) {
|
|
|
543
835
|
PATTER_SCOPE,
|
|
544
836
|
compileExpression,
|
|
545
837
|
exportBundle,
|
|
838
|
+
extensionNodes,
|
|
839
|
+
extensionPass,
|
|
546
840
|
externalGameScopes,
|
|
547
841
|
hostScopesToSpec,
|
|
548
842
|
projectScopes,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ScopeRegistrySpec, ScopeSpec } from '@wildwinter/scoperegistry';
|
|
2
2
|
import { MergedScopes } from '@wildwinter/scoperegistry/scopes';
|
|
3
|
-
import { Expression, ProjectFile, Scene, LocaleFile, Bundle, HostScopeRegistry } from '@patterkit/model';
|
|
3
|
+
import { Expression, ProjectFile, Scene, LocaleFile, BundleExtension, NodeExtensions, ExtensionShard, GameData, Bundle, HostScopeRegistry } from '@patterkit/model';
|
|
4
4
|
export { EXTERNAL_SCOPES, hostScopesToSpec } from '@patterkit/dialect';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -62,6 +62,70 @@ declare function validateInterpolation(input: {
|
|
|
62
62
|
locales?: LocaleFile[];
|
|
63
63
|
}, options?: ValidateOptions): ConditionIssue[];
|
|
64
64
|
|
|
65
|
+
/** One extension's manifest, as a project keeps it (`extensions/<id>.extension.json`). */
|
|
66
|
+
interface ExtensionManifestFile {
|
|
67
|
+
/** Where it is, for the report. */
|
|
68
|
+
path: string;
|
|
69
|
+
/** The file's stem, which names the extension it should be. */
|
|
70
|
+
id: string;
|
|
71
|
+
/** The parsed JSON, unchecked: the pass says whether it is sound. */
|
|
72
|
+
manifest: unknown;
|
|
73
|
+
}
|
|
74
|
+
/** One extension data shard (`.patterextension`), with where it is. */
|
|
75
|
+
interface ExtensionShardFile {
|
|
76
|
+
/** `<layout.extensions>/<extension id>/<scene file stem>.patterextension`: the folder names the extension. */
|
|
77
|
+
path: string;
|
|
78
|
+
shard: ExtensionShard;
|
|
79
|
+
}
|
|
80
|
+
/** What the pass reads: the project, its scenes, and its extension files. */
|
|
81
|
+
interface ExtensionPassInput {
|
|
82
|
+
project: ProjectFile;
|
|
83
|
+
scenes: Scene[];
|
|
84
|
+
manifests: readonly ExtensionManifestFile[];
|
|
85
|
+
shards: readonly ExtensionShardFile[];
|
|
86
|
+
/** The project file's path, for the report (default `""`). */
|
|
87
|
+
projectPath?: string;
|
|
88
|
+
/** Each scene's file, by scene id, for the report. */
|
|
89
|
+
sceneFiles?: Readonly<Record<string, string>>;
|
|
90
|
+
}
|
|
91
|
+
/** One thing the pass found. `file` is the file to open; `node` the node id, when it is about one. */
|
|
92
|
+
interface ExtensionIssue {
|
|
93
|
+
severity: "error" | "warning";
|
|
94
|
+
file: string;
|
|
95
|
+
node?: string;
|
|
96
|
+
/** The toolkit's stable name for the problem, when it is one of the toolkit's. */
|
|
97
|
+
code?: string;
|
|
98
|
+
message: string;
|
|
99
|
+
}
|
|
100
|
+
/** What the pass hands the bundle. */
|
|
101
|
+
interface CompiledExtensions {
|
|
102
|
+
/** The bundle's `extensions` section, by extension id. Empty when the project takes none. */
|
|
103
|
+
readonly bundle: Record<string, BundleExtension>;
|
|
104
|
+
/** Each node's compiled values, by node id: what goes on the node as `extensions`. */
|
|
105
|
+
readonly nodes: ReadonlyMap<string, NodeExtensions>;
|
|
106
|
+
}
|
|
107
|
+
/** A node the project has: its kind, the scene it belongs to (none for the project's own), and its gameData. */
|
|
108
|
+
interface ExtensionNode {
|
|
109
|
+
readonly kind: string;
|
|
110
|
+
readonly scene?: string;
|
|
111
|
+
readonly gameData?: GameData;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Every node an extension can attach to, by id (ids are project-unique): the project, each scene, block and
|
|
115
|
+
* snippet, a choice's options (the groups directly inside a `choice` group, as gameData's `option` is), and
|
|
116
|
+
* every beat by its kind, an option's prompt beat included. A bare snippet in a choice stays a snippet.
|
|
117
|
+
*/
|
|
118
|
+
declare function extensionNodes(project: ProjectFile, scenes: readonly Scene[]): Map<string, ExtensionNode>;
|
|
119
|
+
/**
|
|
120
|
+
* Validate every extension, and compile what may be compiled. `issues` is the pass's report; `compiled` is
|
|
121
|
+
* what the bundle carries. Data whose manifest is missing or unsound is reported and left out of the bundle,
|
|
122
|
+
* never changed; an entry with an error is left out on its own.
|
|
123
|
+
*/
|
|
124
|
+
declare function extensionPass(input: ExtensionPassInput): {
|
|
125
|
+
issues: ExtensionIssue[];
|
|
126
|
+
compiled: CompiledExtensions;
|
|
127
|
+
};
|
|
128
|
+
|
|
65
129
|
interface ExportInput {
|
|
66
130
|
project: ProjectFile;
|
|
67
131
|
scenes: Scene[];
|
|
@@ -82,6 +146,17 @@ interface ExportInput {
|
|
|
82
146
|
* does: named in `externalScopes`, never baked in.
|
|
83
147
|
*/
|
|
84
148
|
gameScopes?: MergedScopes;
|
|
149
|
+
/**
|
|
150
|
+
* The project's Patter extensions: its manifests and its extension data shards. Each node's values are
|
|
151
|
+
* compiled inline under `extensions`, and the bundle's `extensions` section carries each extension the
|
|
152
|
+
* project takes. Absent or empty, with no extension taken, the bundle and its hashes are exactly what they
|
|
153
|
+
* were. What is wrong with the data is validate's to report (`extensionPass`); here an entry with an
|
|
154
|
+
* error is simply left out.
|
|
155
|
+
*/
|
|
156
|
+
extensions?: {
|
|
157
|
+
manifests: readonly ExtensionManifestFile[];
|
|
158
|
+
shards: readonly ExtensionShardFile[];
|
|
159
|
+
};
|
|
85
160
|
}
|
|
86
161
|
/** Compile a project's source into the runtime bundle. */
|
|
87
162
|
declare function exportBundle(input: ExportInput): Bundle;
|
|
@@ -116,4 +191,4 @@ declare function projectScopes(project: ProjectFile, merged?: MergedScopes): Pro
|
|
|
116
191
|
*/
|
|
117
192
|
declare function externalGameScopes(strict: ScopeRegistrySpec | undefined, merged?: MergedScopes): ScopeSpec[];
|
|
118
193
|
|
|
119
|
-
export { type ConditionIssue, type ExportInput, type HostScopeNote, PATTER_SCOPE, type ProjectScopes, type ValidateOptions, compileExpression, exportBundle, externalGameScopes, projectScopes, validateConditions, validateInterpolation };
|
|
194
|
+
export { type CompiledExtensions, type ConditionIssue, type ExportInput, type ExtensionIssue, type ExtensionManifestFile, type ExtensionNode, type ExtensionPassInput, type ExtensionShardFile, type HostScopeNote, PATTER_SCOPE, type ProjectScopes, type ValidateOptions, compileExpression, exportBundle, extensionNodes, extensionPass, externalGameScopes, projectScopes, validateConditions, validateInterpolation };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ScopeRegistrySpec, ScopeSpec } from '@wildwinter/scoperegistry';
|
|
2
2
|
import { MergedScopes } from '@wildwinter/scoperegistry/scopes';
|
|
3
|
-
import { Expression, ProjectFile, Scene, LocaleFile, Bundle, HostScopeRegistry } from '@patterkit/model';
|
|
3
|
+
import { Expression, ProjectFile, Scene, LocaleFile, BundleExtension, NodeExtensions, ExtensionShard, GameData, Bundle, HostScopeRegistry } from '@patterkit/model';
|
|
4
4
|
export { EXTERNAL_SCOPES, hostScopesToSpec } from '@patterkit/dialect';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -62,6 +62,70 @@ declare function validateInterpolation(input: {
|
|
|
62
62
|
locales?: LocaleFile[];
|
|
63
63
|
}, options?: ValidateOptions): ConditionIssue[];
|
|
64
64
|
|
|
65
|
+
/** One extension's manifest, as a project keeps it (`extensions/<id>.extension.json`). */
|
|
66
|
+
interface ExtensionManifestFile {
|
|
67
|
+
/** Where it is, for the report. */
|
|
68
|
+
path: string;
|
|
69
|
+
/** The file's stem, which names the extension it should be. */
|
|
70
|
+
id: string;
|
|
71
|
+
/** The parsed JSON, unchecked: the pass says whether it is sound. */
|
|
72
|
+
manifest: unknown;
|
|
73
|
+
}
|
|
74
|
+
/** One extension data shard (`.patterextension`), with where it is. */
|
|
75
|
+
interface ExtensionShardFile {
|
|
76
|
+
/** `<layout.extensions>/<extension id>/<scene file stem>.patterextension`: the folder names the extension. */
|
|
77
|
+
path: string;
|
|
78
|
+
shard: ExtensionShard;
|
|
79
|
+
}
|
|
80
|
+
/** What the pass reads: the project, its scenes, and its extension files. */
|
|
81
|
+
interface ExtensionPassInput {
|
|
82
|
+
project: ProjectFile;
|
|
83
|
+
scenes: Scene[];
|
|
84
|
+
manifests: readonly ExtensionManifestFile[];
|
|
85
|
+
shards: readonly ExtensionShardFile[];
|
|
86
|
+
/** The project file's path, for the report (default `""`). */
|
|
87
|
+
projectPath?: string;
|
|
88
|
+
/** Each scene's file, by scene id, for the report. */
|
|
89
|
+
sceneFiles?: Readonly<Record<string, string>>;
|
|
90
|
+
}
|
|
91
|
+
/** One thing the pass found. `file` is the file to open; `node` the node id, when it is about one. */
|
|
92
|
+
interface ExtensionIssue {
|
|
93
|
+
severity: "error" | "warning";
|
|
94
|
+
file: string;
|
|
95
|
+
node?: string;
|
|
96
|
+
/** The toolkit's stable name for the problem, when it is one of the toolkit's. */
|
|
97
|
+
code?: string;
|
|
98
|
+
message: string;
|
|
99
|
+
}
|
|
100
|
+
/** What the pass hands the bundle. */
|
|
101
|
+
interface CompiledExtensions {
|
|
102
|
+
/** The bundle's `extensions` section, by extension id. Empty when the project takes none. */
|
|
103
|
+
readonly bundle: Record<string, BundleExtension>;
|
|
104
|
+
/** Each node's compiled values, by node id: what goes on the node as `extensions`. */
|
|
105
|
+
readonly nodes: ReadonlyMap<string, NodeExtensions>;
|
|
106
|
+
}
|
|
107
|
+
/** A node the project has: its kind, the scene it belongs to (none for the project's own), and its gameData. */
|
|
108
|
+
interface ExtensionNode {
|
|
109
|
+
readonly kind: string;
|
|
110
|
+
readonly scene?: string;
|
|
111
|
+
readonly gameData?: GameData;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Every node an extension can attach to, by id (ids are project-unique): the project, each scene, block and
|
|
115
|
+
* snippet, a choice's options (the groups directly inside a `choice` group, as gameData's `option` is), and
|
|
116
|
+
* every beat by its kind, an option's prompt beat included. A bare snippet in a choice stays a snippet.
|
|
117
|
+
*/
|
|
118
|
+
declare function extensionNodes(project: ProjectFile, scenes: readonly Scene[]): Map<string, ExtensionNode>;
|
|
119
|
+
/**
|
|
120
|
+
* Validate every extension, and compile what may be compiled. `issues` is the pass's report; `compiled` is
|
|
121
|
+
* what the bundle carries. Data whose manifest is missing or unsound is reported and left out of the bundle,
|
|
122
|
+
* never changed; an entry with an error is left out on its own.
|
|
123
|
+
*/
|
|
124
|
+
declare function extensionPass(input: ExtensionPassInput): {
|
|
125
|
+
issues: ExtensionIssue[];
|
|
126
|
+
compiled: CompiledExtensions;
|
|
127
|
+
};
|
|
128
|
+
|
|
65
129
|
interface ExportInput {
|
|
66
130
|
project: ProjectFile;
|
|
67
131
|
scenes: Scene[];
|
|
@@ -82,6 +146,17 @@ interface ExportInput {
|
|
|
82
146
|
* does: named in `externalScopes`, never baked in.
|
|
83
147
|
*/
|
|
84
148
|
gameScopes?: MergedScopes;
|
|
149
|
+
/**
|
|
150
|
+
* The project's Patter extensions: its manifests and its extension data shards. Each node's values are
|
|
151
|
+
* compiled inline under `extensions`, and the bundle's `extensions` section carries each extension the
|
|
152
|
+
* project takes. Absent or empty, with no extension taken, the bundle and its hashes are exactly what they
|
|
153
|
+
* were. What is wrong with the data is validate's to report (`extensionPass`); here an entry with an
|
|
154
|
+
* error is simply left out.
|
|
155
|
+
*/
|
|
156
|
+
extensions?: {
|
|
157
|
+
manifests: readonly ExtensionManifestFile[];
|
|
158
|
+
shards: readonly ExtensionShardFile[];
|
|
159
|
+
};
|
|
85
160
|
}
|
|
86
161
|
/** Compile a project's source into the runtime bundle. */
|
|
87
162
|
declare function exportBundle(input: ExportInput): Bundle;
|
|
@@ -116,4 +191,4 @@ declare function projectScopes(project: ProjectFile, merged?: MergedScopes): Pro
|
|
|
116
191
|
*/
|
|
117
192
|
declare function externalGameScopes(strict: ScopeRegistrySpec | undefined, merged?: MergedScopes): ScopeSpec[];
|
|
118
193
|
|
|
119
|
-
export { type ConditionIssue, type ExportInput, type HostScopeNote, PATTER_SCOPE, type ProjectScopes, type ValidateOptions, compileExpression, exportBundle, externalGameScopes, projectScopes, validateConditions, validateInterpolation };
|
|
194
|
+
export { type CompiledExtensions, type ConditionIssue, type ExportInput, type ExtensionIssue, type ExtensionManifestFile, type ExtensionNode, type ExtensionPassInput, type ExtensionShardFile, type HostScopeNote, PATTER_SCOPE, type ProjectScopes, type ValidateOptions, compileExpression, exportBundle, extensionNodes, extensionPass, externalGameScopes, projectScopes, validateConditions, validateInterpolation };
|
package/dist/index.js
CHANGED
|
@@ -19,14 +19,14 @@ function sameScope(a, b) {
|
|
|
19
19
|
return norm(a) === norm(b);
|
|
20
20
|
}
|
|
21
21
|
function projectScopes(project, merged) {
|
|
22
|
-
const
|
|
23
|
-
if (!merged) return { host:
|
|
22
|
+
const own2 = project.scopeRegistry;
|
|
23
|
+
if (!merged) return { host: own2, external: [], notes: [] };
|
|
24
24
|
const notes = [];
|
|
25
25
|
const hosted = [];
|
|
26
26
|
const bundled = /* @__PURE__ */ new Set();
|
|
27
27
|
let changed = false;
|
|
28
28
|
const shared = (token) => token === PATTER_SCOPE ? void 0 : merged.spec.scopes.find((s) => s.token === token);
|
|
29
|
-
for (const h of
|
|
29
|
+
for (const h of own2?.scopes ?? []) {
|
|
30
30
|
const s = shared(h.token);
|
|
31
31
|
const who = merged.owners.get(h.token);
|
|
32
32
|
if (!s || !who) {
|
|
@@ -62,7 +62,7 @@ function projectScopes(project, merged) {
|
|
|
62
62
|
changed = true;
|
|
63
63
|
}
|
|
64
64
|
const external = merged.spec.scopes.filter((s) => s.token !== PATTER_SCOPE && !bundled.has(s.token));
|
|
65
|
-
const host = !changed ?
|
|
65
|
+
const host = !changed ? own2 : hosted.length ? { version: own2?.version ?? 1, scopes: hosted } : void 0;
|
|
66
66
|
return { host, external, notes };
|
|
67
67
|
}
|
|
68
68
|
function externalGameScopes(strict, merged) {
|
|
@@ -316,10 +316,296 @@ import { defaultFor } from "@wildwinter/scoperegistry";
|
|
|
316
316
|
import { canonicalStringify, hash32 } from "@patterkit/core";
|
|
317
317
|
import { hostScopesToSpec as hostScopesToSpec3, withEngineScopes as withEngineScopes2, EXTERNAL_SCOPES } from "@patterkit/dialect";
|
|
318
318
|
import { usedQualifiers, usedSpeakerPairs, DEFAULT_SPEAKER_PATTERN } from "@patterkit/model";
|
|
319
|
+
|
|
320
|
+
// src/extensions.ts
|
|
321
|
+
import { EXTENSIONS_DIR, MANIFEST_EXTENSION, PROJECT_LOCALE_SCENE } from "@patterkit/model";
|
|
322
|
+
import {
|
|
323
|
+
HOSTS,
|
|
324
|
+
claimClashes,
|
|
325
|
+
compileEntry,
|
|
326
|
+
compiledTarget,
|
|
327
|
+
effectiveClaim,
|
|
328
|
+
entryProblems,
|
|
329
|
+
manifestProblems,
|
|
330
|
+
orphanedEntries,
|
|
331
|
+
resolvedClaims,
|
|
332
|
+
settingValues,
|
|
333
|
+
targetOf,
|
|
334
|
+
valueProblems
|
|
335
|
+
} from "@wildwinter/toolkit/extensions";
|
|
336
|
+
function extensionNodes(project, scenes) {
|
|
337
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
338
|
+
const add = (id, kind, scene, gameData) => {
|
|
339
|
+
if (!nodes.has(id)) nodes.set(id, { kind, ...scene !== void 0 ? { scene } : {}, ...gameData !== void 0 ? { gameData } : {} });
|
|
340
|
+
};
|
|
341
|
+
add(project.project.id, "project", void 0, void 0);
|
|
342
|
+
for (const scene of scenes) {
|
|
343
|
+
const s = scene.id;
|
|
344
|
+
add(s, "scene", s, scene.gameData);
|
|
345
|
+
const walk = (children, inChoice) => {
|
|
346
|
+
for (const node of children ?? []) {
|
|
347
|
+
if (node.type === "group") {
|
|
348
|
+
if (inChoice) {
|
|
349
|
+
add(node.id, "option", s, node.gameData);
|
|
350
|
+
if (node.prompt) add(node.prompt.id, node.prompt.kind, s, node.prompt.gameData);
|
|
351
|
+
}
|
|
352
|
+
walk(node.children ?? [], node.selector === "choice");
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
add(node.id, "snippet", s, node.gameData);
|
|
356
|
+
for (const beat of node.beats ?? []) add(beat.id, beat.kind, s, beat.gameData);
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
for (const block of scene.blocks ?? []) {
|
|
360
|
+
add(block.id, "block", s, block.gameData);
|
|
361
|
+
walk(block.children ?? [], false);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return nodes;
|
|
365
|
+
}
|
|
366
|
+
var issueOf = (file, p, prefix = "", node) => ({
|
|
367
|
+
severity: p.severity,
|
|
368
|
+
file,
|
|
369
|
+
code: p.code,
|
|
370
|
+
...node !== void 0 ? { node } : {},
|
|
371
|
+
message: p.path === "" ? `${prefix}${p.message}` : `${prefix}${p.path}: ${p.message}`
|
|
372
|
+
});
|
|
373
|
+
var folderOf = (path) => {
|
|
374
|
+
const parts = path.split(/[\\/]/);
|
|
375
|
+
return parts.length >= 2 ? parts[parts.length - 2] : void 0;
|
|
376
|
+
};
|
|
377
|
+
var own = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
|
|
378
|
+
function extensionPass(input) {
|
|
379
|
+
const { project, scenes } = input;
|
|
380
|
+
const issues = [];
|
|
381
|
+
const projectPath = input.projectPath ?? "";
|
|
382
|
+
const taken = project.extensions ?? {};
|
|
383
|
+
const manifests = /* @__PURE__ */ new Map();
|
|
384
|
+
for (const m of input.manifests) {
|
|
385
|
+
const problems = manifestProblems(m.manifest);
|
|
386
|
+
for (const p of problems) issues.push(issueOf(m.path, p, "manifest "));
|
|
387
|
+
let sound2 = !problems.some((p) => p.severity === "error");
|
|
388
|
+
const id = m.manifest?.id;
|
|
389
|
+
if (sound2 && id !== m.id) {
|
|
390
|
+
issues.push({
|
|
391
|
+
severity: "error",
|
|
392
|
+
file: m.path,
|
|
393
|
+
message: `the manifest's id is "${String(id)}", but its file is named for "${m.id}"; a manifest is called <id>${MANIFEST_EXTENSION}`
|
|
394
|
+
});
|
|
395
|
+
sound2 = false;
|
|
396
|
+
}
|
|
397
|
+
if (sound2 && m.manifest.targets.patter === void 0) {
|
|
398
|
+
issues.push({
|
|
399
|
+
severity: "warning",
|
|
400
|
+
file: m.path,
|
|
401
|
+
message: `"${m.id}" isn't a Patter extension: its manifest targets no Patter node kind, so it adds nothing here`
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
manifests.set(m.id, { path: m.path, manifest: m.manifest, sound: sound2 });
|
|
405
|
+
}
|
|
406
|
+
const takenList = [];
|
|
407
|
+
for (const [id, entry] of Object.entries(taken)) {
|
|
408
|
+
const m = manifests.get(id);
|
|
409
|
+
const version = typeof entry?.version === "string" ? entry.version : "";
|
|
410
|
+
const settings = entry?.settings ?? {};
|
|
411
|
+
if (m === void 0) {
|
|
412
|
+
issues.push({
|
|
413
|
+
severity: "warning",
|
|
414
|
+
file: projectPath,
|
|
415
|
+
message: `the project takes the Patter extension "${id}", but has no manifest for it at ${EXTENSIONS_DIR}/${id}${MANIFEST_EXTENSION}; its data is left as it is, and not compiled`
|
|
416
|
+
});
|
|
417
|
+
takenList.push({ id, settings });
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
if (!m.sound) {
|
|
421
|
+
takenList.push({ id, settings });
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
const manifest = m.manifest;
|
|
425
|
+
if (version !== manifest.version) {
|
|
426
|
+
issues.push({
|
|
427
|
+
severity: "warning",
|
|
428
|
+
file: projectPath,
|
|
429
|
+
message: `the project takes "${id}" ${version === "" ? "with no version" : `at version ${version}`}, but its manifest is version ${manifest.version}; run \`patter format\` to take it (renamed stages are renamed in the data as it does)`
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
const known = new Set((manifest.settings ?? []).map((s) => s.name));
|
|
433
|
+
for (const [name, value] of Object.entries(settings)) {
|
|
434
|
+
if (!known.has(name)) {
|
|
435
|
+
issues.push({ severity: "warning", file: projectPath, message: `"${id}" has no setting "${name}"; it's ignored` });
|
|
436
|
+
} else if (typeof value !== "string" || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
|
|
437
|
+
issues.push({
|
|
438
|
+
severity: "error",
|
|
439
|
+
file: projectPath,
|
|
440
|
+
message: `"${id}"'s setting "${name}" names a gameData field, so it's a field name, not ${JSON.stringify(value)}`
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
takenList.push({ id, settings, manifest });
|
|
445
|
+
}
|
|
446
|
+
const sound = new Map(takenList.filter((t) => t.manifest !== void 0).map((t) => [t.id, t.manifest]));
|
|
447
|
+
const nodes = extensionNodes(project, scenes);
|
|
448
|
+
const claims = takenList.flatMap((t) => t.manifest ? resolvedClaims(t.manifest, "patter", t.settings) : []);
|
|
449
|
+
for (const p of claimClashes(claims)) issues.push({ severity: p.severity, file: projectPath, code: p.code, message: p.message });
|
|
450
|
+
issues.push(...claimIssues(input, nodes, claims));
|
|
451
|
+
const exists = (kind, id) => kind === "cast" ? (project.cast ?? []).some((c) => c.name === id) : nodes.get(id)?.kind === kind;
|
|
452
|
+
const sceneIds = new Set(scenes.map((s) => s.id));
|
|
453
|
+
const values = /* @__PURE__ */ new Map();
|
|
454
|
+
const projectValues = /* @__PURE__ */ new Map();
|
|
455
|
+
for (const { path, shard } of [...input.shards].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0)) {
|
|
456
|
+
const folder = folderOf(path);
|
|
457
|
+
if (folder !== void 0 && shard.extension !== folder) {
|
|
458
|
+
issues.push({
|
|
459
|
+
severity: "error",
|
|
460
|
+
file: path,
|
|
461
|
+
message: `this shard says it holds "${String(shard.extension)}", but it sits in the folder for "${folder}"`
|
|
462
|
+
});
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
const ext = shard.extension;
|
|
466
|
+
if (!own(taken, ext)) {
|
|
467
|
+
issues.push({
|
|
468
|
+
severity: "warning",
|
|
469
|
+
file: path,
|
|
470
|
+
message: `data for the Patter extension "${ext}", which the project doesn't take; it's left as it is, and not compiled`
|
|
471
|
+
});
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
const entries = shard.entries;
|
|
475
|
+
if (typeof entries !== "object" || entries === null || Array.isArray(entries)) {
|
|
476
|
+
issues.push({ severity: "error", file: path, message: "entries are an object, by node kind and then node id" });
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
const shardScene = shard.scene === PROJECT_LOCALE_SCENE ? void 0 : shard.scene;
|
|
480
|
+
const sceneKnown = shardScene === void 0 || sceneIds.has(shardScene);
|
|
481
|
+
if (!sceneKnown) {
|
|
482
|
+
issues.push({
|
|
483
|
+
severity: "warning",
|
|
484
|
+
file: path,
|
|
485
|
+
message: `this shard is for scene "${shard.scene}", which the project doesn't have; its entries are compiled where their nodes are`
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
for (const p of orphanedEntries(shard, exists)) issues.push(issueOf(path, p));
|
|
489
|
+
const manifest = sound.get(ext);
|
|
490
|
+
if (manifest === void 0) continue;
|
|
491
|
+
for (const [kind, byNode] of Object.entries(entries)) {
|
|
492
|
+
if (!HOSTS.patter.kinds.includes(kind)) {
|
|
493
|
+
issues.push({
|
|
494
|
+
severity: "warning",
|
|
495
|
+
file: path,
|
|
496
|
+
message: `entries.${kind}: "${kind}" isn't a Patter node kind; these entries are kept, and not compiled`
|
|
497
|
+
});
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
const target = targetOf(manifest, "patter", kind);
|
|
501
|
+
if (target === void 0) {
|
|
502
|
+
issues.push({
|
|
503
|
+
severity: "warning",
|
|
504
|
+
file: path,
|
|
505
|
+
message: `entries.${kind}: "${ext}" declares nothing for a ${kind}; these entries are kept, and not compiled`
|
|
506
|
+
});
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
if (typeof byNode !== "object" || byNode === null || Array.isArray(byNode)) continue;
|
|
510
|
+
for (const [id, entry] of Object.entries(byNode)) {
|
|
511
|
+
const node = nodes.get(id);
|
|
512
|
+
if (node === void 0 || node.kind !== kind) continue;
|
|
513
|
+
if (sceneKnown && node.scene !== shardScene) {
|
|
514
|
+
issues.push({
|
|
515
|
+
severity: "warning",
|
|
516
|
+
file: path,
|
|
517
|
+
node: id,
|
|
518
|
+
message: `entries.${kind}.${id} belongs ${node.scene === void 0 ? "in the project's own shard" : `in scene "${node.scene}"'s shard`}; it's compiled from here all the same`
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
const problems = entryProblems(target, entry, { exists });
|
|
522
|
+
for (const p of problems) issues.push(issueOf(path, p, `entries.${kind}.${id}${p.path === "" ? ": " : "."}`, id));
|
|
523
|
+
if (problems.some((p) => p.severity === "error")) continue;
|
|
524
|
+
const compiled = compileEntry(target, entry);
|
|
525
|
+
if (compiled === void 0) continue;
|
|
526
|
+
if (kind === "project") {
|
|
527
|
+
projectValues.set(ext, compiled);
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
values.set(id, { ...values.get(id) ?? {}, [ext]: compiled });
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
const bundle = {};
|
|
535
|
+
const chosen = new Map(takenList.map((t) => [t.id, t.settings]));
|
|
536
|
+
for (const [id, manifest] of [...sound.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
|
|
537
|
+
const fields = {};
|
|
538
|
+
for (const kind of Object.keys(manifest.targets.patter ?? {}).sort()) {
|
|
539
|
+
const target = targetOf(manifest, "patter", kind);
|
|
540
|
+
if (target !== void 0) fields[kind] = compiledTarget(target);
|
|
541
|
+
}
|
|
542
|
+
const projectEntry = projectValues.get(id);
|
|
543
|
+
const settings = (manifest.settings ?? []).length > 0 ? sortRecord(settingValues(manifest, chosen.get(id))) : void 0;
|
|
544
|
+
bundle[id] = {
|
|
545
|
+
version: manifest.version,
|
|
546
|
+
fields,
|
|
547
|
+
...projectEntry !== void 0 ? { project: projectEntry } : {},
|
|
548
|
+
...settings !== void 0 ? { settings } : {}
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
const sortedNodes = new Map([...values.entries()].map(([id, v]) => [id, sortRecord(v)]));
|
|
552
|
+
return { issues, compiled: { bundle, nodes: sortedNodes } };
|
|
553
|
+
}
|
|
554
|
+
function sortRecord(r) {
|
|
555
|
+
return Object.fromEntries(Object.keys(r).sort().map((k) => [k, r[k]]));
|
|
556
|
+
}
|
|
557
|
+
function claimIssues(input, nodes, claims) {
|
|
558
|
+
const issues = [];
|
|
559
|
+
const projectPath = input.projectPath ?? "";
|
|
560
|
+
const declared = input.project.gameDataFields ?? {};
|
|
561
|
+
for (const claim of claims) {
|
|
562
|
+
const decl = (declared[claim.kind] ?? []).find((d) => d.name === claim.field);
|
|
563
|
+
if (decl === void 0) {
|
|
564
|
+
issues.push({
|
|
565
|
+
severity: "warning",
|
|
566
|
+
file: projectPath,
|
|
567
|
+
message: `the Patter extension "${claim.extension}" claims the ${claim.kind} gameData field "${claim.field}", which the project doesn't declare`
|
|
568
|
+
});
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
if (decl.type !== claim.claim.type) {
|
|
572
|
+
issues.push({
|
|
573
|
+
severity: "error",
|
|
574
|
+
file: projectPath,
|
|
575
|
+
message: `the Patter extension "${claim.extension}" claims the ${claim.kind} gameData field "${claim.field}" as ${claim.claim.type}, but the project declares it as ${decl.type}`
|
|
576
|
+
});
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
const effective = effectiveClaim(claim.claim, decl);
|
|
580
|
+
for (const [id, node] of nodes) {
|
|
581
|
+
if (node.kind !== claim.kind || node.gameData === void 0 || !own(node.gameData, claim.field)) continue;
|
|
582
|
+
const file = (node.scene !== void 0 ? input.sceneFiles?.[node.scene] : void 0) ?? projectPath;
|
|
583
|
+
for (const p of valueProblems(effective, node.gameData[claim.field])) {
|
|
584
|
+
issues.push({
|
|
585
|
+
severity: p.severity,
|
|
586
|
+
file,
|
|
587
|
+
node: id,
|
|
588
|
+
code: p.code,
|
|
589
|
+
message: `${claim.kind} '${id}' gameData "${claim.field}", as the Patter extension "${claim.extension}" claims it: ${p.message}`
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return issues;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// src/export.ts
|
|
598
|
+
function compileBeat(beat, ctx) {
|
|
599
|
+
const values = ctx.extensions.get(beat.id);
|
|
600
|
+
if (values === void 0 && !Object.prototype.hasOwnProperty.call(beat, "extensions")) return beat;
|
|
601
|
+
const { extensions: _source, ...rest } = beat;
|
|
602
|
+
return values !== void 0 ? { ...rest, extensions: values } : rest;
|
|
603
|
+
}
|
|
319
604
|
function compileEffect(e, foreign) {
|
|
320
605
|
return { kind: "set", target: e.target, value: compileExpression(e.value, foreign) };
|
|
321
606
|
}
|
|
322
|
-
function compileNode(node,
|
|
607
|
+
function compileNode(node, ctx) {
|
|
608
|
+
const { foreign } = ctx;
|
|
323
609
|
if (node.type === "group") {
|
|
324
610
|
return {
|
|
325
611
|
id: node.id,
|
|
@@ -328,13 +614,15 @@ function compileNode(node, foreign) {
|
|
|
328
614
|
selector: node.selector,
|
|
329
615
|
shared: node.shared,
|
|
330
616
|
options: node.options,
|
|
331
|
-
children: node.children.map((c) => compileNode(c,
|
|
617
|
+
children: node.children.map((c) => compileNode(c, ctx)),
|
|
332
618
|
padAfterDefault: node.padAfterDefault,
|
|
333
619
|
// line padding: the default for the beats inside
|
|
334
620
|
gameData: node.gameData,
|
|
621
|
+
extensions: ctx.extensions.get(node.id),
|
|
622
|
+
// a choice's option's extension values
|
|
335
623
|
tags: node.tags,
|
|
336
624
|
// author tags (#215), accumulated at runtime
|
|
337
|
-
prompt: node.prompt,
|
|
625
|
+
prompt: node.prompt && compileBeat(node.prompt, ctx),
|
|
338
626
|
// option-position only (spec §5)
|
|
339
627
|
secretUntilEligible: node.secretUntilEligible,
|
|
340
628
|
sticky: node.sticky,
|
|
@@ -345,12 +633,13 @@ function compileNode(node, foreign) {
|
|
|
345
633
|
id: node.id,
|
|
346
634
|
type: "snippet",
|
|
347
635
|
condition: node.condition !== void 0 ? compileExpression(node.condition, foreign) : void 0,
|
|
348
|
-
beats: node.beats,
|
|
636
|
+
beats: node.beats && node.beats.map((b) => compileBeat(b, ctx)),
|
|
349
637
|
onEnter: node.onEnter?.map((e) => compileEffect(e, foreign)),
|
|
350
638
|
onExit: node.onExit?.map((e) => compileEffect(e, foreign)),
|
|
351
639
|
padAfterDefault: node.padAfterDefault,
|
|
352
640
|
// line padding: the default for the beats inside
|
|
353
641
|
gameData: node.gameData,
|
|
642
|
+
extensions: ctx.extensions.get(node.id),
|
|
354
643
|
tags: node.tags,
|
|
355
644
|
// author tags (#215)
|
|
356
645
|
jump: node.jump,
|
|
@@ -359,22 +648,24 @@ function compileNode(node, foreign) {
|
|
|
359
648
|
fallback: node.fallback
|
|
360
649
|
};
|
|
361
650
|
}
|
|
362
|
-
function compileBlock(block,
|
|
651
|
+
function compileBlock(block, ctx) {
|
|
363
652
|
return {
|
|
364
653
|
id: block.id,
|
|
365
654
|
type: "block",
|
|
366
655
|
name: block.name,
|
|
367
656
|
...block.gameId ? { gameId: block.gameId } : {},
|
|
368
657
|
// host-facing address; runtime falls back to the name slug
|
|
369
|
-
children: block.children.map((c) => compileNode(c,
|
|
658
|
+
children: block.children.map((c) => compileNode(c, ctx)),
|
|
370
659
|
padAfterDefault: block.padAfterDefault,
|
|
371
660
|
// line padding: the default for the beats inside
|
|
372
661
|
gameData: block.gameData,
|
|
662
|
+
extensions: ctx.extensions.get(block.id),
|
|
373
663
|
tags: block.tags
|
|
374
664
|
// author tags (#215)
|
|
375
665
|
};
|
|
376
666
|
}
|
|
377
|
-
function compileScene(scene,
|
|
667
|
+
function compileScene(scene, ctx) {
|
|
668
|
+
const { foreign } = ctx;
|
|
378
669
|
return {
|
|
379
670
|
id: scene.id,
|
|
380
671
|
type: "scene",
|
|
@@ -384,11 +675,12 @@ function compileScene(scene, foreign) {
|
|
|
384
675
|
padAfterDefault: scene.padAfterDefault,
|
|
385
676
|
// line padding: the default for the beats inside
|
|
386
677
|
gameData: scene.gameData,
|
|
678
|
+
extensions: ctx.extensions.get(scene.id),
|
|
387
679
|
tags: scene.tags,
|
|
388
680
|
// author tags (#215)
|
|
389
681
|
onEntry: scene.onEntry?.map((e) => compileEffect(e, foreign)),
|
|
390
682
|
sceneProps: scene.sceneProps,
|
|
391
|
-
blocks: scene.blocks.map((b) => compileBlock(b,
|
|
683
|
+
blocks: scene.blocks.map((b) => compileBlock(b, ctx))
|
|
392
684
|
};
|
|
393
685
|
}
|
|
394
686
|
function externalScopesIn(scenes, external) {
|
|
@@ -422,8 +714,12 @@ function exportBundle(input) {
|
|
|
422
714
|
version: foreignScopes?.version ?? 1,
|
|
423
715
|
scopes: [...foreignScopes?.scopes ?? [], ...gameExternal.map((s) => ({ token: s.token }))]
|
|
424
716
|
});
|
|
717
|
+
const ext = input.extensions;
|
|
718
|
+
const compiledExt = project.extensions !== void 0 || ext !== void 0 && (ext.manifests.length > 0 || ext.shards.length > 0) ? extensionPass({ project, scenes, manifests: ext?.manifests ?? [], shards: ext?.shards ?? [] }).compiled : void 0;
|
|
719
|
+
const extensionSection = compiledExt !== void 0 && Object.keys(compiledExt.bundle).length > 0 ? compiledExt.bundle : void 0;
|
|
720
|
+
const ctx = { foreign: compileScopes, extensions: compiledExt?.nodes ?? /* @__PURE__ */ new Map() };
|
|
425
721
|
const scenesOut = {};
|
|
426
|
-
for (const scene of scenes) scenesOut[scene.id] = compileScene(scene,
|
|
722
|
+
for (const scene of scenes) scenesOut[scene.id] = compileScene(scene, ctx);
|
|
427
723
|
const externalScopes = externalScopesIn(scenesOut, /* @__PURE__ */ new Set([...EXTERNAL_SCOPES, ...gameExternal.map((s) => s.token)]));
|
|
428
724
|
const qualifiers = usedQualifiers(project, scenes).map((q) => ({ gameId: q.gameId, name: q.name }));
|
|
429
725
|
const usedPairs = new Set(usedSpeakerPairs(scenes).map((p) => `${p.character}\0${p.qualifier}`));
|
|
@@ -454,7 +750,9 @@ function exportBundle(input) {
|
|
|
454
750
|
properties: project.properties ?? [],
|
|
455
751
|
...foreignScopes ? { scopeRegistry: foreignScopes } : {},
|
|
456
752
|
...qualifiers.length > 0 ? { qualifiers } : {},
|
|
457
|
-
...typeof project.padAfterDefault === "number" ? { padAfterDefault: project.padAfterDefault } : {}
|
|
753
|
+
...typeof project.padAfterDefault === "number" ? { padAfterDefault: project.padAfterDefault } : {},
|
|
754
|
+
// Each node's values are in `scenes`; the declarations and the project's own values are here.
|
|
755
|
+
...extensionSection !== void 0 ? { extensions: extensionSection } : {}
|
|
458
756
|
}, { trailingComma: false })),
|
|
459
757
|
// Structure-only fingerprint (live bundle refresh): the same hash with the string tables left
|
|
460
758
|
// out. Same structureHash + a different hash = a text-only edit, safe to swap in place with
|
|
@@ -465,7 +763,8 @@ function exportBundle(input) {
|
|
|
465
763
|
properties: project.properties ?? [],
|
|
466
764
|
...foreignScopes ? { scopeRegistry: foreignScopes } : {},
|
|
467
765
|
...qualifiers.length > 0 ? { qualifiers } : {},
|
|
468
|
-
...typeof project.padAfterDefault === "number" ? { padAfterDefault: project.padAfterDefault } : {}
|
|
766
|
+
...typeof project.padAfterDefault === "number" ? { padAfterDefault: project.padAfterDefault } : {},
|
|
767
|
+
...extensionSection !== void 0 ? { extensions: extensionSection } : {}
|
|
469
768
|
}, { trailingComma: false }))
|
|
470
769
|
},
|
|
471
770
|
voiced: project.voiced ?? false,
|
|
@@ -494,6 +793,10 @@ function exportBundle(input) {
|
|
|
494
793
|
gameDataFields: project.gameDataFields && Object.fromEntries(
|
|
495
794
|
Object.entries(project.gameDataFields).map(([kind, fields]) => [kind, resolved(fields)])
|
|
496
795
|
),
|
|
796
|
+
// The extensions the project takes: each one's version, its compiled declarations by node kind (every
|
|
797
|
+
// default resolved, editor-only fields and the editor's words left out), the project node's values, and
|
|
798
|
+
// its settings, each the project's value or else the default.
|
|
799
|
+
...extensionSection !== void 0 ? { extensions: extensionSection } : {},
|
|
497
800
|
// Closed-caption delimiters ride along only when the project pins a non-default pair (#214); the
|
|
498
801
|
// runtime falls back to `[` / `]` when absent, so default projects keep their exact prior bundle.
|
|
499
802
|
...project.closedCaptions ? { closedCaptions: project.closedCaptions } : {},
|
|
@@ -508,6 +811,8 @@ export {
|
|
|
508
811
|
PATTER_SCOPE,
|
|
509
812
|
compileExpression,
|
|
510
813
|
exportBundle,
|
|
814
|
+
extensionNodes,
|
|
815
|
+
extensionPass,
|
|
511
816
|
externalGameScopes,
|
|
512
817
|
hostScopesToSpec4 as hostScopesToSpec,
|
|
513
818
|
projectScopes,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@patterkit/compiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Patter compiler: compile condition/effect expressions, validate them, and export source to the runtime bundle.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,10 +34,11 @@
|
|
|
34
34
|
"build": "tsup src/index.ts --format esm,cjs --dts --clean"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@patterkit/model": "0.
|
|
38
|
-
"@patterkit/core": "0.
|
|
39
|
-
"@patterkit/dialect": "0.2.
|
|
40
|
-
"@wildwinter/expr": "^0.6.0"
|
|
37
|
+
"@patterkit/model": "0.17.0",
|
|
38
|
+
"@patterkit/core": "0.9.1",
|
|
39
|
+
"@patterkit/dialect": "0.2.14",
|
|
40
|
+
"@wildwinter/expr": "^0.6.0",
|
|
41
|
+
"@wildwinter/toolkit": "^0.2.1"
|
|
41
42
|
},
|
|
42
43
|
"peerDependencies": {
|
|
43
44
|
"@wildwinter/scoperegistry": ">=0.9.0 <1.0.0"
|