@patterkit/cli 0.2.6 → 0.3.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/cli.js +750 -350
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -192615,6 +192615,7 @@ function readDirSafe(dir2) {
|
|
|
192615
192615
|
function walkFiles(dir2, ext) {
|
|
192616
192616
|
const out = [];
|
|
192617
192617
|
for (const e of readDirSafe(dir2)) {
|
|
192618
|
+
if (e.name.startsWith(".")) continue;
|
|
192618
192619
|
const p = join(dir2, e.name);
|
|
192619
192620
|
if (e.isDirectory()) out.push(...walkFiles(p, ext));
|
|
192620
192621
|
else if (e.isFile() && e.name.endsWith(ext)) out.push(p);
|
|
@@ -192748,6 +192749,347 @@ import { dirname as dirname2 } from "path";
|
|
|
192748
192749
|
// ../ops/src/validate.ts
|
|
192749
192750
|
import { readFileSync as readFileSync2, statSync as statSync2 } from "fs";
|
|
192750
192751
|
|
|
192752
|
+
// ../ops/src/merge.ts
|
|
192753
|
+
var UnsupportedMergeError = class extends Error {
|
|
192754
|
+
};
|
|
192755
|
+
var eq = (a, b) => canonicalStringify(a) === canonicalStringify(b);
|
|
192756
|
+
var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
192757
|
+
var CONFLICT_SIDECAR = ".patterconflict";
|
|
192758
|
+
function sidecarIssues(sidecarPaths) {
|
|
192759
|
+
return sidecarPaths.map((file) => ({
|
|
192760
|
+
file,
|
|
192761
|
+
message: "unresolved merge conflict - resolve it and delete the .patterconflict sidecar before committing"
|
|
192762
|
+
}));
|
|
192763
|
+
}
|
|
192764
|
+
function detectMergeType(file) {
|
|
192765
|
+
const s = typeof file.schema === "string" ? file.schema : "";
|
|
192766
|
+
if (s.startsWith("patter/flow")) return "flow";
|
|
192767
|
+
if (s.startsWith("patter/strings")) return "loc";
|
|
192768
|
+
if (s.startsWith("patter/authoring")) return "authoring";
|
|
192769
|
+
if (s.startsWith("patter/project")) return "project";
|
|
192770
|
+
throw new UnsupportedMergeError(`cannot detect a Patter merge type from schema '${s}'`);
|
|
192771
|
+
}
|
|
192772
|
+
function runMerge(base, ours, theirs, opts) {
|
|
192773
|
+
const type = opts?.type ?? detectMergeType(ours);
|
|
192774
|
+
switch (type) {
|
|
192775
|
+
case "loc":
|
|
192776
|
+
return mergeLoc(base, ours, theirs);
|
|
192777
|
+
case "authoring":
|
|
192778
|
+
return mergeAuthoring(base, ours, theirs);
|
|
192779
|
+
case "flow":
|
|
192780
|
+
return mergeFlow(base, ours, theirs);
|
|
192781
|
+
case "project":
|
|
192782
|
+
return mergeProject(base, ours, theirs);
|
|
192783
|
+
}
|
|
192784
|
+
}
|
|
192785
|
+
function deletedKind(base, ours, theirs) {
|
|
192786
|
+
const deleted = base !== void 0 && (ours === void 0 || theirs === void 0);
|
|
192787
|
+
return deleted ? "delete-vs-edit" : "both-changed";
|
|
192788
|
+
}
|
|
192789
|
+
function merge3(base, ours, theirs, id, path, conflicts) {
|
|
192790
|
+
if (eq(ours, theirs)) return ours;
|
|
192791
|
+
if (eq(base, ours)) return theirs;
|
|
192792
|
+
if (eq(base, theirs)) return ours;
|
|
192793
|
+
conflicts.push({ id, path, base, ours, theirs, kind: deletedKind(base, ours, theirs) });
|
|
192794
|
+
return ours;
|
|
192795
|
+
}
|
|
192796
|
+
function mergeMap(base, ours, theirs, prefix, conflicts, resolve6) {
|
|
192797
|
+
const out = {};
|
|
192798
|
+
for (const k of /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(ours), ...Object.keys(theirs)])) {
|
|
192799
|
+
const b = base[k], o = ours[k], t = theirs[k];
|
|
192800
|
+
let v;
|
|
192801
|
+
if (eq(o, t)) v = o;
|
|
192802
|
+
else if (eq(b, o)) v = t;
|
|
192803
|
+
else if (eq(b, t)) v = o;
|
|
192804
|
+
else {
|
|
192805
|
+
const r = resolve6?.(k, b, o, t);
|
|
192806
|
+
if (r && r.resolved) v = r.value;
|
|
192807
|
+
else {
|
|
192808
|
+
conflicts.push({ id: k, path: `${prefix}.${k}`, base: b, ours: o, theirs: t, kind: r ? r.kind : deletedKind(b, o, t) });
|
|
192809
|
+
v = o;
|
|
192810
|
+
}
|
|
192811
|
+
}
|
|
192812
|
+
if (v !== void 0) out[k] = v;
|
|
192813
|
+
}
|
|
192814
|
+
return out;
|
|
192815
|
+
}
|
|
192816
|
+
var asMap = (v) => isObj(v) ? v : {};
|
|
192817
|
+
var asArr = (v) => Array.isArray(v) ? v : [];
|
|
192818
|
+
function setIf(obj2, key2, val) {
|
|
192819
|
+
if (Array.isArray(val) ? val.length > 0 : Object.keys(val).length > 0) obj2[key2] = val;
|
|
192820
|
+
}
|
|
192821
|
+
function mergeLoc(base, ours, theirs) {
|
|
192822
|
+
const conflicts = [];
|
|
192823
|
+
const merged = {};
|
|
192824
|
+
for (const f of ["schema", "scene", "locale", "default"]) {
|
|
192825
|
+
const v = merge3(base[f], ours[f], theirs[f], "", f, conflicts);
|
|
192826
|
+
if (v !== void 0) merged[f] = v;
|
|
192827
|
+
}
|
|
192828
|
+
merged.strings = mergeMap(asMap(base.strings), asMap(ours.strings), asMap(theirs.strings), "strings", conflicts);
|
|
192829
|
+
return { type: "loc", merged, conflicts, warnings: [] };
|
|
192830
|
+
}
|
|
192831
|
+
function mergeAuthoring(base, ours, theirs) {
|
|
192832
|
+
const conflicts = [];
|
|
192833
|
+
const merged = {};
|
|
192834
|
+
merged.schema = merge3(base.schema, ours.schema, theirs.schema, "", "schema", conflicts) ?? ours.schema;
|
|
192835
|
+
setIf(merged, "comments", mergeComments(asArr(base.comments), asArr(ours.comments), asArr(theirs.comments)));
|
|
192836
|
+
const edits = mergeEdits(asMap(base.edits), asMap(ours.edits), asMap(theirs.edits));
|
|
192837
|
+
setIf(merged, "edits", edits);
|
|
192838
|
+
const lww = lastWriterWins(asMap(ours.edits), asMap(theirs.edits));
|
|
192839
|
+
for (const f of ["writing", "recording", "audio"]) {
|
|
192840
|
+
setIf(merged, f, mergeMap(asMap(base[f]), asMap(ours[f]), asMap(theirs[f]), f, conflicts, lww));
|
|
192841
|
+
}
|
|
192842
|
+
setIf(merged, "documentation", mergeMap(asMap(base.documentation), asMap(ours.documentation), asMap(theirs.documentation), "documentation", conflicts));
|
|
192843
|
+
setIf(merged, "cut", mergeMap(asMap(base.cut), asMap(ours.cut), asMap(theirs.cut), "cut", conflicts, void 0));
|
|
192844
|
+
setIf(merged, "suggestions", mergeById(asArr(base.suggestions), asArr(ours.suggestions), asArr(theirs.suggestions), "suggestions", conflicts));
|
|
192845
|
+
setIf(merged, "rerecord", mergeMap(asMap(base.rerecord), asMap(ours.rerecord), asMap(theirs.rerecord), "rerecord", conflicts, void 0));
|
|
192846
|
+
for (const k of /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(ours), ...Object.keys(theirs)])) {
|
|
192847
|
+
if (k in merged || AUTHORING_HANDLED.has(k)) continue;
|
|
192848
|
+
const v = merge3(base[k], ours[k], theirs[k], "", k, conflicts);
|
|
192849
|
+
if (v !== void 0) merged[k] = v;
|
|
192850
|
+
}
|
|
192851
|
+
return { type: "authoring", merged, conflicts, warnings: [] };
|
|
192852
|
+
}
|
|
192853
|
+
var AUTHORING_HANDLED = /* @__PURE__ */ new Set(["schema", "comments", "edits", "writing", "recording", "audio", "documentation", "cut", "suggestions", "rerecord"]);
|
|
192854
|
+
function mergeById(base, ours, theirs, path, conflicts) {
|
|
192855
|
+
const index2 = (arr) => {
|
|
192856
|
+
const m = {};
|
|
192857
|
+
for (const r of arr) if (isObj(r) && typeof r.id === "string") m[r.id] = r;
|
|
192858
|
+
return m;
|
|
192859
|
+
};
|
|
192860
|
+
const merged = mergeMap(index2(base), index2(ours), index2(theirs), path, conflicts);
|
|
192861
|
+
return Object.values(merged).sort((a, b) => String(asMap(a).ts ?? "").localeCompare(String(asMap(b).ts ?? "")));
|
|
192862
|
+
}
|
|
192863
|
+
function mergeComments(base, ours, theirs) {
|
|
192864
|
+
const byId = /* @__PURE__ */ new Map();
|
|
192865
|
+
for (const c2 of [...base, ...ours, ...theirs]) {
|
|
192866
|
+
if (isObj(c2) && typeof c2.id === "string" && !byId.has(c2.id)) byId.set(c2.id, c2);
|
|
192867
|
+
}
|
|
192868
|
+
return [...byId.values()].sort((a, b) => String(a.ts ?? "").localeCompare(String(b.ts ?? "")));
|
|
192869
|
+
}
|
|
192870
|
+
function lastWriterWins(oursEdits, theirsEdits) {
|
|
192871
|
+
return (id, _b, o, t) => {
|
|
192872
|
+
const om = asMap(oursEdits[id]).modifiedAt, tm = asMap(theirsEdits[id]).modifiedAt;
|
|
192873
|
+
if (typeof om === "string" && typeof tm === "string") return { resolved: true, value: om >= tm ? o : t };
|
|
192874
|
+
return { resolved: false, kind: "no-timestamp" };
|
|
192875
|
+
};
|
|
192876
|
+
}
|
|
192877
|
+
function mergeEdits(base, ours, theirs) {
|
|
192878
|
+
const out = {};
|
|
192879
|
+
for (const id of /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(ours), ...Object.keys(theirs)])) {
|
|
192880
|
+
const o = ours[id], t = theirs[id];
|
|
192881
|
+
if (eq(o, t)) {
|
|
192882
|
+
if (o !== void 0) out[id] = o;
|
|
192883
|
+
continue;
|
|
192884
|
+
}
|
|
192885
|
+
if (o === void 0) {
|
|
192886
|
+
if (t !== void 0) out[id] = t;
|
|
192887
|
+
continue;
|
|
192888
|
+
}
|
|
192889
|
+
if (t === void 0) {
|
|
192890
|
+
out[id] = o;
|
|
192891
|
+
continue;
|
|
192892
|
+
}
|
|
192893
|
+
out[id] = mergeEditRecord(asMap(o), asMap(t));
|
|
192894
|
+
}
|
|
192895
|
+
return out;
|
|
192896
|
+
}
|
|
192897
|
+
function mergeEditRecord(o, t) {
|
|
192898
|
+
const om = typeof o.modifiedAt === "string" ? o.modifiedAt : "";
|
|
192899
|
+
const tm = typeof t.modifiedAt === "string" ? t.modifiedAt : "";
|
|
192900
|
+
const newer = om >= tm ? o : t;
|
|
192901
|
+
const ol = asMap(o.localisedAt), tl = asMap(t.localisedAt);
|
|
192902
|
+
const loc = {};
|
|
192903
|
+
for (const k of /* @__PURE__ */ new Set([...Object.keys(ol), ...Object.keys(tl)])) {
|
|
192904
|
+
const a = typeof ol[k] === "string" ? ol[k] : "";
|
|
192905
|
+
const b = typeof tl[k] === "string" ? tl[k] : "";
|
|
192906
|
+
loc[k] = a >= b ? ol[k] ?? tl[k] : tl[k] ?? ol[k];
|
|
192907
|
+
}
|
|
192908
|
+
const merged = { ...newer };
|
|
192909
|
+
if (Object.keys(loc).length > 0) merged.localisedAt = loc;
|
|
192910
|
+
return merged;
|
|
192911
|
+
}
|
|
192912
|
+
function mergeKeyedByName(base, ours, theirs, path, conflicts) {
|
|
192913
|
+
const byName = (arr) => {
|
|
192914
|
+
const m = {};
|
|
192915
|
+
for (const p of arr) if (isObj(p) && typeof p.name === "string") m[p.name] = p;
|
|
192916
|
+
return m;
|
|
192917
|
+
};
|
|
192918
|
+
const merged = mergeMap(byName(base), byName(ours), byName(theirs), path, conflicts);
|
|
192919
|
+
return Object.keys(merged).sort().map((name) => merged[name]);
|
|
192920
|
+
}
|
|
192921
|
+
function mergeProject(base, ours, theirs) {
|
|
192922
|
+
const conflicts = [];
|
|
192923
|
+
const merged = {};
|
|
192924
|
+
for (const k of /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(ours), ...Object.keys(theirs)])) {
|
|
192925
|
+
if (k === "properties" || k === "cast") {
|
|
192926
|
+
const v = mergeKeyedByName(asArr(base[k]), asArr(ours[k]), asArr(theirs[k]), k, conflicts);
|
|
192927
|
+
if (v.length > 0) merged[k] = v;
|
|
192928
|
+
} else if (k === "gameDataFields") {
|
|
192929
|
+
const v = mergeGameDataFields(asMap(base[k]), asMap(ours[k]), asMap(theirs[k]), conflicts);
|
|
192930
|
+
if (Object.keys(v).length > 0) merged[k] = v;
|
|
192931
|
+
} else if (k === "locales") {
|
|
192932
|
+
merged.locales = mergeLocales(asMap(base.locales), asMap(ours.locales), asMap(theirs.locales), conflicts);
|
|
192933
|
+
} else {
|
|
192934
|
+
const v = merge3(base[k], ours[k], theirs[k], "", k, conflicts);
|
|
192935
|
+
if (v !== void 0) merged[k] = v;
|
|
192936
|
+
}
|
|
192937
|
+
}
|
|
192938
|
+
return { type: "project", merged, conflicts, warnings: [] };
|
|
192939
|
+
}
|
|
192940
|
+
function mergeGameDataFields(b, o, t, conflicts) {
|
|
192941
|
+
const out = {};
|
|
192942
|
+
for (const kind of /* @__PURE__ */ new Set([...Object.keys(b), ...Object.keys(o), ...Object.keys(t)])) {
|
|
192943
|
+
const v = mergeKeyedByName(asArr(b[kind]), asArr(o[kind]), asArr(t[kind]), `gameDataFields.${kind}`, conflicts);
|
|
192944
|
+
if (v.length > 0) out[kind] = v;
|
|
192945
|
+
}
|
|
192946
|
+
return out;
|
|
192947
|
+
}
|
|
192948
|
+
function mergeLocales(b, o, t, conflicts) {
|
|
192949
|
+
const out = {};
|
|
192950
|
+
const def = merge3(b.default, o.default, t.default, "", "locales.default", conflicts);
|
|
192951
|
+
if (def !== void 0) out.default = def;
|
|
192952
|
+
const all = [];
|
|
192953
|
+
for (const arr of [asArr(b.all), asArr(o.all), asArr(t.all)]) {
|
|
192954
|
+
for (const x of arr) if (typeof x === "string" && !all.includes(x)) all.push(x);
|
|
192955
|
+
}
|
|
192956
|
+
out.all = all;
|
|
192957
|
+
return out;
|
|
192958
|
+
}
|
|
192959
|
+
var CHILD_KEYS = ["blocks", "children", "beats"];
|
|
192960
|
+
function toTree(raw) {
|
|
192961
|
+
const childKey = CHILD_KEYS.find((k) => Array.isArray(raw[k]));
|
|
192962
|
+
const fields = {};
|
|
192963
|
+
for (const [k, v] of Object.entries(raw)) if (k !== childKey) fields[k] = v;
|
|
192964
|
+
const children = childKey ? raw[childKey].filter(isObj).map(toTree) : [];
|
|
192965
|
+
return { id: typeof raw.id === "string" ? raw.id : "", fields, childKey, children };
|
|
192966
|
+
}
|
|
192967
|
+
function fromTree(n) {
|
|
192968
|
+
const out = { ...n.fields };
|
|
192969
|
+
if (n.childKey) out[n.childKey] = n.children.map(fromTree);
|
|
192970
|
+
return out;
|
|
192971
|
+
}
|
|
192972
|
+
function mergeFlow(base, ours, theirs) {
|
|
192973
|
+
const conflicts = [];
|
|
192974
|
+
const warnings = [];
|
|
192975
|
+
const merged = {};
|
|
192976
|
+
merged.schema = merge3(base.schema, ours.schema, theirs.schema, "", "schema", conflicts) ?? ours.schema;
|
|
192977
|
+
const bScene = isObj(base.scene) ? toTree(base.scene) : void 0;
|
|
192978
|
+
const oScene = toTree(asMap(ours.scene));
|
|
192979
|
+
const tScene = isObj(theirs.scene) ? toTree(theirs.scene) : void 0;
|
|
192980
|
+
const mergedScene = mergeNode(bScene, oScene, tScene, "scene", conflicts, warnings);
|
|
192981
|
+
merged.scene = fromTree(mergedScene);
|
|
192982
|
+
checkDuplicateIds(mergedScene, conflicts);
|
|
192983
|
+
return { type: "flow", merged, conflicts, warnings };
|
|
192984
|
+
}
|
|
192985
|
+
function mergeNode(b, o, t, path, conflicts, warnings) {
|
|
192986
|
+
const fields = mergeFields(b?.fields ?? {}, o.fields, t?.fields ?? {}, o.id, path, conflicts);
|
|
192987
|
+
const childKey = o.childKey ?? t?.childKey ?? b?.childKey;
|
|
192988
|
+
const children = childKey ? mergeChildren(b?.children ?? [], o.children, t?.children ?? [], `${path}.${childKey}`, conflicts, warnings) : [];
|
|
192989
|
+
return { id: o.id, fields, childKey, children };
|
|
192990
|
+
}
|
|
192991
|
+
function mergeFields(b, o, t, id, path, conflicts) {
|
|
192992
|
+
const out = {};
|
|
192993
|
+
for (const k of /* @__PURE__ */ new Set([...Object.keys(b), ...Object.keys(o), ...Object.keys(t)])) {
|
|
192994
|
+
const v = merge3(b[k], o[k], t[k], id, `${path}.${k}`, conflicts);
|
|
192995
|
+
if (v !== void 0) out[k] = v;
|
|
192996
|
+
}
|
|
192997
|
+
return out;
|
|
192998
|
+
}
|
|
192999
|
+
function mergeChildren(B, O, T, path, conflicts, warnings) {
|
|
193000
|
+
const Bm = idMap(B), Om = idMap(O), Tm = idMap(T);
|
|
193001
|
+
const Bset = new Set(Bm.keys()), Oset = new Set(Om.keys()), Tset = new Set(Tm.keys());
|
|
193002
|
+
const merged = /* @__PURE__ */ new Map();
|
|
193003
|
+
const here = (id) => `${path}[${id}]`;
|
|
193004
|
+
for (const id of /* @__PURE__ */ new Set([...Bm.keys(), ...Om.keys(), ...Tm.keys()])) {
|
|
193005
|
+
const b = Bm.get(id), o = Om.get(id), t = Tm.get(id);
|
|
193006
|
+
const inB = Bset.has(id), inO = Oset.has(id), inT = Tset.has(id);
|
|
193007
|
+
if (inO && inT) {
|
|
193008
|
+
if (!inB && !eq(fromTree(o), fromTree(t))) {
|
|
193009
|
+
conflicts.push({ id, path: here(id), base: void 0, ours: fromTree(o), theirs: fromTree(t), kind: "added-both" });
|
|
193010
|
+
merged.set(id, o);
|
|
193011
|
+
} else {
|
|
193012
|
+
merged.set(id, mergeNode(b, o, t, here(id), conflicts, warnings));
|
|
193013
|
+
}
|
|
193014
|
+
} else if (inO && !inT) {
|
|
193015
|
+
if (inB && !eq(fromTree(o), fromTree(b))) {
|
|
193016
|
+
conflicts.push({ id, path: here(id), base: fromTree(b), ours: fromTree(o), theirs: void 0, kind: "delete-vs-edit" });
|
|
193017
|
+
merged.set(id, o);
|
|
193018
|
+
} else if (!inB) {
|
|
193019
|
+
merged.set(id, o);
|
|
193020
|
+
}
|
|
193021
|
+
} else if (!inO && inT) {
|
|
193022
|
+
if (inB && !eq(fromTree(t), fromTree(b))) {
|
|
193023
|
+
conflicts.push({ id, path: here(id), base: fromTree(b), ours: void 0, theirs: fromTree(t), kind: "delete-vs-edit" });
|
|
193024
|
+
} else if (!inB) {
|
|
193025
|
+
merged.set(id, t);
|
|
193026
|
+
}
|
|
193027
|
+
}
|
|
193028
|
+
}
|
|
193029
|
+
return orderChildren(merged, B, O, T, path, conflicts, warnings);
|
|
193030
|
+
}
|
|
193031
|
+
function orderChildren(merged, B, O, T, path, conflicts, warnings) {
|
|
193032
|
+
const Bids = B.map((n) => n.id), Oids = O.map((n) => n.id), Tids = T.map((n) => n.id);
|
|
193033
|
+
const Oidset = new Set(Oids);
|
|
193034
|
+
const common = new Set(Bids.filter((id) => merged.has(id)));
|
|
193035
|
+
const restrict = (ids) => ids.filter((id) => common.has(id));
|
|
193036
|
+
const relB = restrict(Bids), relO = restrict(Oids), relT = restrict(Tids);
|
|
193037
|
+
const fullO = relO.length === common.size, fullT = relT.length === common.size;
|
|
193038
|
+
let commonOrder;
|
|
193039
|
+
if (!fullO && !fullT) commonOrder = relB;
|
|
193040
|
+
else if (!fullO) commonOrder = relT;
|
|
193041
|
+
else if (!fullT) commonOrder = relO;
|
|
193042
|
+
else if (arrEq(relO, relB)) commonOrder = relT;
|
|
193043
|
+
else if (arrEq(relT, relB)) commonOrder = relO;
|
|
193044
|
+
else if (arrEq(relO, relT)) commonOrder = relO;
|
|
193045
|
+
else {
|
|
193046
|
+
conflicts.push({ id: "", path, base: relB, ours: relO, theirs: relT, kind: "moved" });
|
|
193047
|
+
commonOrder = relO;
|
|
193048
|
+
}
|
|
193049
|
+
const anchorIn = (ids, id) => {
|
|
193050
|
+
for (let i = ids.indexOf(id) - 1; i >= 0; i--) if (common.has(ids[i])) return ids[i];
|
|
193051
|
+
return null;
|
|
193052
|
+
};
|
|
193053
|
+
const byAnchor = /* @__PURE__ */ new Map();
|
|
193054
|
+
const group = (a) => {
|
|
193055
|
+
let g = byAnchor.get(a);
|
|
193056
|
+
if (!g) {
|
|
193057
|
+
g = { ours: [], theirs: [] };
|
|
193058
|
+
byAnchor.set(a, g);
|
|
193059
|
+
}
|
|
193060
|
+
return g;
|
|
193061
|
+
};
|
|
193062
|
+
for (const n of O) if (merged.has(n.id) && !common.has(n.id)) group(anchorIn(Oids, n.id)).ours.push(n.id);
|
|
193063
|
+
for (const n of T) if (merged.has(n.id) && !common.has(n.id) && !Oidset.has(n.id)) group(anchorIn(Tids, n.id)).theirs.push(n.id);
|
|
193064
|
+
const out = [];
|
|
193065
|
+
const emit = (anchor) => {
|
|
193066
|
+
const g = byAnchor.get(anchor);
|
|
193067
|
+
if (!g) return;
|
|
193068
|
+
if (g.ours.length > 0 && g.theirs.length > 0) warnings.push({ id: anchor ?? "", path, message: "both sides inserted here; kept ours-then-theirs - review the order" });
|
|
193069
|
+
out.push(...g.ours, ...g.theirs);
|
|
193070
|
+
};
|
|
193071
|
+
emit(null);
|
|
193072
|
+
for (const id of commonOrder) {
|
|
193073
|
+
out.push(id);
|
|
193074
|
+
emit(id);
|
|
193075
|
+
}
|
|
193076
|
+
return out.map((id) => merged.get(id));
|
|
193077
|
+
}
|
|
193078
|
+
function checkDuplicateIds(root2, conflicts) {
|
|
193079
|
+
const seen = /* @__PURE__ */ new Set(), dup = /* @__PURE__ */ new Set();
|
|
193080
|
+
const walk2 = (n) => {
|
|
193081
|
+
if (n.id) {
|
|
193082
|
+
if (seen.has(n.id)) dup.add(n.id);
|
|
193083
|
+
else seen.add(n.id);
|
|
193084
|
+
}
|
|
193085
|
+
n.children.forEach(walk2);
|
|
193086
|
+
};
|
|
193087
|
+
walk2(root2);
|
|
193088
|
+
for (const id of dup) conflicts.push({ id, path: "scene", base: void 0, ours: void 0, theirs: void 0, kind: "structural" });
|
|
193089
|
+
}
|
|
193090
|
+
var idMap = (nodes) => new Map(nodes.filter((n) => n.id).map((n) => [n.id, n]));
|
|
193091
|
+
var arrEq = (a, b) => a.length === b.length && a.every((x, i) => x === b[i]);
|
|
193092
|
+
|
|
192751
193093
|
// ../../../expr/packages/expr/src/parser.ts
|
|
192752
193094
|
var ParseError = class extends Error {
|
|
192753
193095
|
constructor(message, pos2, source2) {
|
|
@@ -194286,6 +194628,201 @@ function exportBundle(input) {
|
|
|
194286
194628
|
};
|
|
194287
194629
|
}
|
|
194288
194630
|
|
|
194631
|
+
// ../ops/src/reachability.ts
|
|
194632
|
+
var normaliseRef = (ref) => ref.includes(".") ? ref : `@patter.${ref.slice(1)}`;
|
|
194633
|
+
var SEP = "\0";
|
|
194634
|
+
var keyOf = (ref, sceneId) => ref.startsWith("@scene.") ? `${sceneId}${SEP}${ref}` : ref;
|
|
194635
|
+
var shown = (key2) => {
|
|
194636
|
+
const bare = key2.includes(SEP) ? key2.slice(key2.indexOf(SEP) + 1) : key2;
|
|
194637
|
+
const at = bare.indexOf(":");
|
|
194638
|
+
return at < 0 ? bare : `${bare.slice(0, at)} +${bare.slice(at + 1)}`;
|
|
194639
|
+
};
|
|
194640
|
+
var svRef = (ast) => Array.isArray(ast) && ast[0] === "sv" ? `@${String(ast[1])}.${String(ast[2])}` : void 0;
|
|
194641
|
+
function latchOf(ast, sceneId) {
|
|
194642
|
+
const direct = svRef(ast);
|
|
194643
|
+
if (direct !== void 0) return keyOf(direct, sceneId);
|
|
194644
|
+
if (!Array.isArray(ast)) return void 0;
|
|
194645
|
+
if (ast[0] === "bin" && ast[1] === "==") {
|
|
194646
|
+
const [, , l2, r] = ast;
|
|
194647
|
+
for (const [a, b] of [[l2, r], [r, l2]]) {
|
|
194648
|
+
const ref = svRef(a);
|
|
194649
|
+
if (ref !== void 0 && Array.isArray(b) && b[0] === "b" && b[1] === true) return keyOf(ref, sceneId);
|
|
194650
|
+
}
|
|
194651
|
+
return void 0;
|
|
194652
|
+
}
|
|
194653
|
+
if (ast[0] === "call" && ast[1] === "check_flags") {
|
|
194654
|
+
const ref = svRef(ast[2]);
|
|
194655
|
+
const args = ast.slice(3);
|
|
194656
|
+
if (ref === void 0 || args.length !== 1) return void 0;
|
|
194657
|
+
const arg = args[0];
|
|
194658
|
+
if (Array.isArray(arg) && arg[0] === "fd" && arg[1] === "+") return `${keyOf(ref, sceneId)}:${String(arg[2])}`;
|
|
194659
|
+
}
|
|
194660
|
+
return void 0;
|
|
194661
|
+
}
|
|
194662
|
+
function disjuncts(ast) {
|
|
194663
|
+
if (Array.isArray(ast) && ast[0] === "bin" && ast[1] === "or") {
|
|
194664
|
+
const [, , l2, r] = ast;
|
|
194665
|
+
return [...disjuncts(l2), ...disjuncts(r)];
|
|
194666
|
+
}
|
|
194667
|
+
return [ast];
|
|
194668
|
+
}
|
|
194669
|
+
function terms(ast, sceneId, negated = false, into = []) {
|
|
194670
|
+
const latch = latchOf(ast, sceneId);
|
|
194671
|
+
if (latch !== void 0) {
|
|
194672
|
+
into.push({ key: latch, negated });
|
|
194673
|
+
return into;
|
|
194674
|
+
}
|
|
194675
|
+
if (!Array.isArray(ast)) return into;
|
|
194676
|
+
if (ast[0] === "u" && ast[1] === "not") return terms(ast[2], sceneId, !negated, into);
|
|
194677
|
+
if (ast[0] === "bin" && ast[1] === "and" && !negated) {
|
|
194678
|
+
terms(ast[2], sceneId, false, into);
|
|
194679
|
+
terms(ast[3], sceneId, false, into);
|
|
194680
|
+
}
|
|
194681
|
+
return into;
|
|
194682
|
+
}
|
|
194683
|
+
var requirementsOf = (expr, sceneId) => expr === void 0 ? [] : terms(expr.ast, sceneId);
|
|
194684
|
+
var asFlags = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
|
|
194685
|
+
function reachabilityIssues(loaded, compiled) {
|
|
194686
|
+
const bundle = compiled ?? exportBundle({ project: loaded.project, scenes: loaded.scenes, locales: loaded.locales });
|
|
194687
|
+
const hostTokens = new Set((loaded.project.scopeRegistry?.scopes ?? []).map((s) => s.token));
|
|
194688
|
+
const preset = /* @__PURE__ */ new Set();
|
|
194689
|
+
const notALatch = (decl, key2) => {
|
|
194690
|
+
if (decl.temporary) {
|
|
194691
|
+
preset.add(key2);
|
|
194692
|
+
for (const v of decl.values ?? []) preset.add(`${key2}:${v}`);
|
|
194693
|
+
return;
|
|
194694
|
+
}
|
|
194695
|
+
if (decl.type === "boolean" && decl.default === true) preset.add(key2);
|
|
194696
|
+
if (decl.type === "flags") for (const v of asFlags(decl.default)) preset.add(`${key2}:${v}`);
|
|
194697
|
+
};
|
|
194698
|
+
for (const decl of loaded.project.properties ?? []) notALatch(decl, `@patter.${decl.name}`);
|
|
194699
|
+
for (const scene of loaded.scenes) {
|
|
194700
|
+
for (const decl of scene.sceneProps ?? []) notALatch(decl, `${scene.id}${SEP}@scene.${decl.name}`);
|
|
194701
|
+
}
|
|
194702
|
+
const latched = /* @__PURE__ */ new Set();
|
|
194703
|
+
const broken = /* @__PURE__ */ new Set();
|
|
194704
|
+
const writers = /* @__PURE__ */ new Map();
|
|
194705
|
+
const noteWrite = (key2, requires) => {
|
|
194706
|
+
latched.add(key2);
|
|
194707
|
+
(writers.get(key2) ?? writers.set(key2, []).get(key2)).push({ requires });
|
|
194708
|
+
};
|
|
194709
|
+
const breakKey = (key2) => {
|
|
194710
|
+
broken.add(key2);
|
|
194711
|
+
for (const k of latched) if (k.startsWith(`${key2}:`)) broken.add(k);
|
|
194712
|
+
};
|
|
194713
|
+
const scanEffects = (effects, sceneId, need) => {
|
|
194714
|
+
for (const e of effects ?? []) {
|
|
194715
|
+
const ref = normaliseRef(e.target);
|
|
194716
|
+
const key2 = keyOf(ref, sceneId);
|
|
194717
|
+
const ast = e.value.ast;
|
|
194718
|
+
if (Array.isArray(ast) && ast[0] === "b" && ast[1] === true) {
|
|
194719
|
+
noteWrite(key2, need);
|
|
194720
|
+
continue;
|
|
194721
|
+
}
|
|
194722
|
+
if (Array.isArray(ast) && ast[0] === "call" && ast[1] === "set_flags" && svRef(ast[2]) === ref) {
|
|
194723
|
+
let clean3 = true;
|
|
194724
|
+
for (const arg of ast.slice(3)) {
|
|
194725
|
+
if (Array.isArray(arg) && arg[0] === "fd" && arg[1] === "+") noteWrite(`${key2}:${String(arg[2])}`, need);
|
|
194726
|
+
else clean3 = false;
|
|
194727
|
+
}
|
|
194728
|
+
if (clean3) continue;
|
|
194729
|
+
}
|
|
194730
|
+
breakKey(key2);
|
|
194731
|
+
}
|
|
194732
|
+
};
|
|
194733
|
+
const walk2 = (nodes, sceneId, gate) => {
|
|
194734
|
+
for (const node of nodes) {
|
|
194735
|
+
const here = [...gate, ...requirementsOf(node.condition, sceneId)];
|
|
194736
|
+
if (node.type === "group") walk2(node.children, sceneId, here);
|
|
194737
|
+
else {
|
|
194738
|
+
scanEffects(node.onEnter, sceneId, here);
|
|
194739
|
+
scanEffects(node.onExit, sceneId, here);
|
|
194740
|
+
}
|
|
194741
|
+
}
|
|
194742
|
+
};
|
|
194743
|
+
for (const scene of Object.values(bundle.scenes)) {
|
|
194744
|
+
scanEffects(scene.onEntry, scene.id, []);
|
|
194745
|
+
for (const block of scene.blocks) walk2(block.children, scene.id, []);
|
|
194746
|
+
}
|
|
194747
|
+
for (const key2 of [...latched]) {
|
|
194748
|
+
const at = key2.indexOf(":");
|
|
194749
|
+
if (at > 0 && broken.has(key2.slice(0, at))) broken.add(key2);
|
|
194750
|
+
}
|
|
194751
|
+
const hostDriven = (key2) => hostTokens.has(key2.slice(1, key2.indexOf(".")));
|
|
194752
|
+
const monotonic = (key2) => latched.has(key2) && !broken.has(key2) && !preset.has(key2) && !hostDriven(key2);
|
|
194753
|
+
const cache2 = /* @__PURE__ */ new Map();
|
|
194754
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
194755
|
+
const mustHold = (key2) => {
|
|
194756
|
+
const done = cache2.get(key2);
|
|
194757
|
+
if (done !== void 0) return { need: done, cut: false };
|
|
194758
|
+
if (inFlight.has(key2)) return { need: /* @__PURE__ */ new Set(), cut: true };
|
|
194759
|
+
const routes = writers.get(key2);
|
|
194760
|
+
if (routes === void 0 || routes.length === 0) return { need: /* @__PURE__ */ new Set(), cut: false };
|
|
194761
|
+
inFlight.add(key2);
|
|
194762
|
+
let shared;
|
|
194763
|
+
let cut = false;
|
|
194764
|
+
for (const route of routes) {
|
|
194765
|
+
const need = /* @__PURE__ */ new Set();
|
|
194766
|
+
for (const t of route.requires) {
|
|
194767
|
+
if (t.negated || !monotonic(t.key)) continue;
|
|
194768
|
+
need.add(t.key);
|
|
194769
|
+
const deeper = mustHold(t.key);
|
|
194770
|
+
cut ||= deeper.cut;
|
|
194771
|
+
for (const k of deeper.need) need.add(k);
|
|
194772
|
+
}
|
|
194773
|
+
shared = shared === void 0 ? need : new Set([...shared].filter((k) => need.has(k)));
|
|
194774
|
+
}
|
|
194775
|
+
inFlight.delete(key2);
|
|
194776
|
+
const result = shared ?? /* @__PURE__ */ new Set();
|
|
194777
|
+
if (!cut) cache2.set(key2, result);
|
|
194778
|
+
return { need: result, cut };
|
|
194779
|
+
};
|
|
194780
|
+
const refute = (gate) => {
|
|
194781
|
+
for (const no of gate.filter((t) => t.negated)) {
|
|
194782
|
+
if (!monotonic(no.key)) continue;
|
|
194783
|
+
if (gate.some((t) => !t.negated && t.key === no.key)) {
|
|
194784
|
+
return `it asks for ${shown(no.key)} to be both set and not set`;
|
|
194785
|
+
}
|
|
194786
|
+
for (const yes of gate.filter((t) => !t.negated && t.key !== no.key)) {
|
|
194787
|
+
if (!monotonic(yes.key)) continue;
|
|
194788
|
+
const chain = mustHold(yes.key);
|
|
194789
|
+
if (!chain.cut && chain.need.has(no.key)) {
|
|
194790
|
+
return `${shown(yes.key)} can only become true after ${shown(no.key)}, which nothing sets back, so this condition can never hold`;
|
|
194791
|
+
}
|
|
194792
|
+
}
|
|
194793
|
+
}
|
|
194794
|
+
return void 0;
|
|
194795
|
+
};
|
|
194796
|
+
const issues = [];
|
|
194797
|
+
const report = (nodes, sceneId, gate) => {
|
|
194798
|
+
for (const node of nodes) {
|
|
194799
|
+
const own = node.condition;
|
|
194800
|
+
const branches = own ? disjuncts(own.ast) : [void 0];
|
|
194801
|
+
let reason;
|
|
194802
|
+
const refuted = branches.every((b) => {
|
|
194803
|
+
const r = refute([...gate, ...b ? terms(b, sceneId) : []]);
|
|
194804
|
+
reason ??= r;
|
|
194805
|
+
return r !== void 0;
|
|
194806
|
+
});
|
|
194807
|
+
if (refuted && reason !== void 0 && own) {
|
|
194808
|
+
issues.push({
|
|
194809
|
+
nodeId: node.id,
|
|
194810
|
+
field: "condition",
|
|
194811
|
+
src: own.src,
|
|
194812
|
+
severity: "warning",
|
|
194813
|
+
message: `this can never run: ${reason}`
|
|
194814
|
+
});
|
|
194815
|
+
continue;
|
|
194816
|
+
}
|
|
194817
|
+
if (node.type === "group") report(node.children, sceneId, [...gate, ...requirementsOf(own, sceneId)]);
|
|
194818
|
+
}
|
|
194819
|
+
};
|
|
194820
|
+
for (const scene of Object.values(bundle.scenes)) {
|
|
194821
|
+
for (const block of scene.blocks) report(block.children, scene.id, []);
|
|
194822
|
+
}
|
|
194823
|
+
return issues;
|
|
194824
|
+
}
|
|
194825
|
+
|
|
194289
194826
|
// ../ops/src/validate.ts
|
|
194290
194827
|
function runValidate(loaded) {
|
|
194291
194828
|
const { project, scenes, locales } = loaded;
|
|
@@ -194295,19 +194832,42 @@ function runValidate(loaded) {
|
|
|
194295
194832
|
const interpolation = validateInterpolation({ project, scenes, locales }, { foreignScopes });
|
|
194296
194833
|
const hygiene = checkHygiene([loaded.projectFile, ...Object.values(loaded.sceneFiles), ...loaded.localeFiles, ...loaded.authoringFiles]);
|
|
194297
194834
|
const staleBundles = checkBundles(loaded);
|
|
194298
|
-
const unresolvedMerges = walkFiles(loaded.root,
|
|
194299
|
-
|
|
194300
|
-
|
|
194301
|
-
}));
|
|
194835
|
+
const unresolvedMerges = sidecarIssues(walkFiles(loaded.root, CONFLICT_SIDECAR));
|
|
194836
|
+
const orphans = orphanShards(loaded);
|
|
194837
|
+
const reachability = structural.length === 0 && conditions.length === 0 ? reachabilityIssues(loaded) : [];
|
|
194302
194838
|
return {
|
|
194303
194839
|
structural,
|
|
194304
194840
|
conditions,
|
|
194305
194841
|
interpolation,
|
|
194842
|
+
reachability,
|
|
194306
194843
|
hygiene,
|
|
194307
194844
|
staleBundles,
|
|
194308
194845
|
unresolvedMerges,
|
|
194309
|
-
|
|
194846
|
+
orphans,
|
|
194847
|
+
ok: structural.length === 0 && conditions.length === 0 && interpolation.length === 0 && hygiene.length === 0 && staleBundles.length === 0 && unresolvedMerges.length === 0 && orphans.length === 0
|
|
194848
|
+
};
|
|
194849
|
+
}
|
|
194850
|
+
function orphanShards(loaded) {
|
|
194851
|
+
const collected = /* @__PURE__ */ new Set([
|
|
194852
|
+
loaded.projectFile,
|
|
194853
|
+
...Object.values(loaded.sceneFiles),
|
|
194854
|
+
...loaded.localeFiles,
|
|
194855
|
+
...loaded.authoringFiles
|
|
194856
|
+
]);
|
|
194857
|
+
const kind = {
|
|
194858
|
+
".patterflow": "scene",
|
|
194859
|
+
".patterloc": "locale",
|
|
194860
|
+
".patterx": "authoring",
|
|
194861
|
+
".patterproj": "project"
|
|
194310
194862
|
};
|
|
194863
|
+
const out = [];
|
|
194864
|
+
for (const [ext, what] of Object.entries(kind)) {
|
|
194865
|
+
for (const file of walkFiles(loaded.root, ext)) {
|
|
194866
|
+
if (collected.has(file)) continue;
|
|
194867
|
+
out.push({ file, message: `a ${what} shard outside the project's layout - nothing loads it, so its content is not in the project (move it under the configured folder, or delete it)` });
|
|
194868
|
+
}
|
|
194869
|
+
}
|
|
194870
|
+
return out;
|
|
194311
194871
|
}
|
|
194312
194872
|
function checkBundles(loaded) {
|
|
194313
194873
|
const issues = [];
|
|
@@ -194388,7 +194948,16 @@ function compileScenes(loaded) {
|
|
|
194388
194948
|
blocks: s.blocks.map((b) => ({ ...b, children: b.children.map(clean3) }))
|
|
194389
194949
|
}));
|
|
194390
194950
|
}
|
|
194951
|
+
function refuseUnresolvedMerge(loaded) {
|
|
194952
|
+
const issues = sidecarIssues(walkFiles(loaded.root, CONFLICT_SIDECAR));
|
|
194953
|
+
if (!issues.length) return;
|
|
194954
|
+
throw new Error(
|
|
194955
|
+
`${issues.length} unresolved merge conflict(s) - resolve them and delete the .patterconflict sidecar(s) first:
|
|
194956
|
+
` + issues.map((i) => ` ${i.file}`).join("\n")
|
|
194957
|
+
);
|
|
194958
|
+
}
|
|
194391
194959
|
function runExport(loaded) {
|
|
194960
|
+
refuseUnresolvedMerge(loaded);
|
|
194392
194961
|
const { project, locales } = loaded;
|
|
194393
194962
|
const full = exportBundle({ project, scenes: compileScenes(loaded), locales });
|
|
194394
194963
|
const loc = project.export?.localisation;
|
|
@@ -194397,6 +194966,7 @@ function runExport(loaded) {
|
|
|
194397
194966
|
return { ...full, strings, localisation: { mode: "ids", ...loc.sourceDebug ? { sourceDebug: true } : {} } };
|
|
194398
194967
|
}
|
|
194399
194968
|
function runExportFull(loaded) {
|
|
194969
|
+
refuseUnresolvedMerge(loaded);
|
|
194400
194970
|
const { project, locales } = loaded;
|
|
194401
194971
|
return exportBundle({ project, scenes: compileScenes(loaded), locales });
|
|
194402
194972
|
}
|
|
@@ -196456,7 +197026,7 @@ function stringsByLocale(loaded) {
|
|
|
196456
197026
|
}
|
|
196457
197027
|
return byLocale;
|
|
196458
197028
|
}
|
|
196459
|
-
function
|
|
197029
|
+
function mergeAuthoring2(loaded) {
|
|
196460
197030
|
const writing = /* @__PURE__ */ new Map();
|
|
196461
197031
|
const recording = /* @__PURE__ */ new Map();
|
|
196462
197032
|
const cut = /* @__PURE__ */ new Set();
|
|
@@ -196586,40 +197156,113 @@ function targetHostRef(target, hostTokens) {
|
|
|
196586
197156
|
const m = /^@([A-Za-z_][\w]*)\.(.+)$/.exec(target);
|
|
196587
197157
|
return m && hostTokens.has(m[1]) ? `@${m[1]}.${m[2]}` : void 0;
|
|
196588
197158
|
}
|
|
197159
|
+
function flagKeys(node, hostTokens, fn) {
|
|
197160
|
+
if (node.kind !== "call" || node.name !== fn) return [];
|
|
197161
|
+
const subject = node.args[0];
|
|
197162
|
+
if (!subject || subject.kind !== "scopedvar" || !hostTokens.has(subject.scope)) return [];
|
|
197163
|
+
const ref = `@${subject.scope}.${subject.name}`;
|
|
197164
|
+
return node.args.slice(1).filter((a) => a.kind === "flagdelta").map((a) => `${ref}:${a.name}`);
|
|
197165
|
+
}
|
|
197166
|
+
function fineRefsIn(node, hostTokens, out) {
|
|
197167
|
+
for (const k of flagKeys(node, hostTokens, "check_flags")) out.add(k);
|
|
197168
|
+
switch (node.kind) {
|
|
197169
|
+
case "unary":
|
|
197170
|
+
fineRefsIn(node.operand, hostTokens, out);
|
|
197171
|
+
break;
|
|
197172
|
+
case "binary":
|
|
197173
|
+
fineRefsIn(node.left, hostTokens, out);
|
|
197174
|
+
fineRefsIn(node.right, hostTokens, out);
|
|
197175
|
+
break;
|
|
197176
|
+
case "call":
|
|
197177
|
+
for (const a of node.args) fineRefsIn(a, hostTokens, out);
|
|
197178
|
+
break;
|
|
197179
|
+
default:
|
|
197180
|
+
break;
|
|
197181
|
+
}
|
|
197182
|
+
}
|
|
196589
197183
|
function analyzeHostScopes(bundle, hostTokens) {
|
|
196590
197184
|
const written = /* @__PURE__ */ new Set();
|
|
196591
197185
|
const gatesByBeat = /* @__PURE__ */ new Map();
|
|
197186
|
+
const fineGatesByBeat = /* @__PURE__ */ new Map();
|
|
196592
197187
|
const proposals = /* @__PURE__ */ new Map();
|
|
196593
|
-
|
|
197188
|
+
const writerSites = /* @__PURE__ */ new Map();
|
|
197189
|
+
const opaqueWrites = /* @__PURE__ */ new Set();
|
|
197190
|
+
const empty = { written, gatesByBeat, proposals, fineGatesByBeat, writerSites, opaqueWrites };
|
|
197191
|
+
if (hostTokens.size === 0) return empty;
|
|
196594
197192
|
const refsIn = (expr) => {
|
|
196595
197193
|
const refs = /* @__PURE__ */ new Set();
|
|
196596
197194
|
if (expr) scanExpr(deserialiseAst(expr.ast), hostTokens, refs, proposals);
|
|
196597
197195
|
return refs;
|
|
196598
197196
|
};
|
|
196599
|
-
const
|
|
197197
|
+
const fineIn = (expr) => {
|
|
197198
|
+
const refs = /* @__PURE__ */ new Set();
|
|
197199
|
+
if (expr) fineRefsIn(deserialiseAst(expr.ast), hostTokens, refs);
|
|
197200
|
+
return refs;
|
|
197201
|
+
};
|
|
197202
|
+
const addSite = (key2, witnesses) => {
|
|
197203
|
+
(writerSites.get(key2) ?? writerSites.set(key2, []).get(key2)).push(witnesses);
|
|
197204
|
+
};
|
|
197205
|
+
const scanEffects = (effects, witnesses) => {
|
|
196600
197206
|
for (const e of effects ?? []) {
|
|
196601
|
-
const
|
|
196602
|
-
if (
|
|
197207
|
+
const target = targetHostRef(e.target, hostTokens);
|
|
197208
|
+
if (target) {
|
|
197209
|
+
written.add(target);
|
|
197210
|
+
addSite(target, witnesses);
|
|
197211
|
+
const flags = e.value ? flagKeys(deserialiseAst(e.value.ast), hostTokens, "set_flags") : [];
|
|
197212
|
+
if (flags.length) for (const k of flags) addSite(k, witnesses);
|
|
197213
|
+
else opaqueWrites.add(target);
|
|
197214
|
+
}
|
|
196603
197215
|
refsIn(e.value);
|
|
196604
197216
|
}
|
|
196605
197217
|
};
|
|
196606
|
-
const walk2 = (nodes, gate) => {
|
|
197218
|
+
const walk2 = (nodes, gate, fine) => {
|
|
196607
197219
|
for (const node of nodes) {
|
|
196608
197220
|
const here = /* @__PURE__ */ new Set([...gate, ...refsIn(node.condition)]);
|
|
197221
|
+
const hereFine = /* @__PURE__ */ new Set([...fine, ...fineIn(node.condition)]);
|
|
196609
197222
|
if (node.type === "group") {
|
|
196610
|
-
walk2(node.children, here);
|
|
197223
|
+
walk2(node.children, here, hereFine);
|
|
196611
197224
|
} else {
|
|
196612
|
-
|
|
196613
|
-
scanEffects(node.
|
|
196614
|
-
|
|
197225
|
+
const witnesses = (node.beats ?? []).map((b) => b.id);
|
|
197226
|
+
scanEffects(node.onEnter, witnesses);
|
|
197227
|
+
scanEffects(node.onExit, witnesses);
|
|
197228
|
+
for (const beat of node.beats ?? []) {
|
|
197229
|
+
gatesByBeat.set(beat.id, here);
|
|
197230
|
+
fineGatesByBeat.set(beat.id, hereFine);
|
|
197231
|
+
}
|
|
196615
197232
|
}
|
|
196616
197233
|
}
|
|
196617
197234
|
};
|
|
196618
197235
|
for (const scene of Object.values(bundle.scenes)) {
|
|
196619
|
-
|
|
196620
|
-
|
|
197236
|
+
const sceneBeats = [];
|
|
197237
|
+
const collect = (nodes) => {
|
|
197238
|
+
for (const n of nodes) {
|
|
197239
|
+
if (n.type === "group") collect(n.children);
|
|
197240
|
+
else for (const b of n.beats ?? []) sceneBeats.push(b.id);
|
|
197241
|
+
}
|
|
197242
|
+
};
|
|
197243
|
+
for (const block of scene.blocks) collect(block.children);
|
|
197244
|
+
scanEffects(scene.onEntry, sceneBeats);
|
|
197245
|
+
for (const block of scene.blocks) walk2(block.children, /* @__PURE__ */ new Set(), /* @__PURE__ */ new Set());
|
|
196621
197246
|
}
|
|
196622
|
-
return
|
|
197247
|
+
return empty;
|
|
197248
|
+
}
|
|
197249
|
+
function blockedGates(beatId, analysis, drivenRefs, reachedRuns) {
|
|
197250
|
+
const out = [];
|
|
197251
|
+
const gates = /* @__PURE__ */ new Set([...analysis.gatesByBeat.get(beatId) ?? [], ...analysis.fineGatesByBeat.get(beatId) ?? []]);
|
|
197252
|
+
for (const ref of [...gates].sort()) {
|
|
197253
|
+
const coarse = ref.includes(":") ? ref.slice(0, ref.indexOf(":")) : ref;
|
|
197254
|
+
if (drivenRefs.has(coarse)) continue;
|
|
197255
|
+
if (!analysis.written.has(coarse)) continue;
|
|
197256
|
+
if (ref !== coarse && analysis.opaqueWrites.has(coarse)) continue;
|
|
197257
|
+
if (ref === coarse && [...gates].some((g) => g !== ref && g.startsWith(`${ref}:`))) continue;
|
|
197258
|
+
const sites = analysis.writerSites.get(ref) ?? [];
|
|
197259
|
+
if (!sites.length) continue;
|
|
197260
|
+
if (sites.some((s) => !s.length || s.some((w) => !reachedRuns.has(w)))) continue;
|
|
197261
|
+
if (!sites.every((s) => s.every((w) => reachedRuns.get(w) === 0))) continue;
|
|
197262
|
+
const writers = [...new Set(sites.flat())].sort();
|
|
197263
|
+
out.push({ ref, writers });
|
|
197264
|
+
}
|
|
197265
|
+
return out;
|
|
196623
197266
|
}
|
|
196624
197267
|
function proposeCoverageDrivers(loaded) {
|
|
196625
197268
|
const hostTokens = new Set((loaded.project.scopeRegistry?.scopes ?? []).map((s) => s.token));
|
|
@@ -196752,12 +197395,15 @@ function* sweep(loaded, options = {}, hooks = {}) {
|
|
|
196752
197395
|
const m = meta.get(id);
|
|
196753
197396
|
const reached = reachedRuns.get(id);
|
|
196754
197397
|
let needsInput;
|
|
197398
|
+
let blockedBy;
|
|
196755
197399
|
if (reached === 0) {
|
|
196756
197400
|
const gates = [...analysis.gatesByBeat.get(id) ?? []].filter((r) => !analysis.written.has(r) && !drivenRefs.has(r));
|
|
196757
197401
|
if (gates.length) {
|
|
196758
197402
|
needsInput = gates;
|
|
196759
197403
|
for (const g of gates) unwrittenInputs.add(g);
|
|
196760
197404
|
}
|
|
197405
|
+
const blocked = blockedGates(id, analysis, drivenRefs, reachedRuns);
|
|
197406
|
+
if (blocked.length) blockedBy = blocked;
|
|
196761
197407
|
}
|
|
196762
197408
|
return {
|
|
196763
197409
|
id,
|
|
@@ -196768,7 +197414,8 @@ function* sweep(loaded, options = {}, hooks = {}) {
|
|
|
196768
197414
|
hits: hitCount.get(id),
|
|
196769
197415
|
reachedRuns: reached,
|
|
196770
197416
|
reachPct: executed ? reached / executed * 100 : 0,
|
|
196771
|
-
...needsInput ? { needsInput } : {}
|
|
197417
|
+
...needsInput ? { needsInput } : {},
|
|
197418
|
+
...blockedBy ? { blockedBy } : {}
|
|
196772
197419
|
};
|
|
196773
197420
|
});
|
|
196774
197421
|
const neverHit = beats.filter((b) => b.reachedRuns === 0).length;
|
|
@@ -196821,9 +197468,16 @@ function renderCoverageText(report, sceneName = (id) => id) {
|
|
|
196821
197468
|
out.push("");
|
|
196822
197469
|
out.push(`${sceneName(scene)}${dead ? ` (${dead} never reached)` : ""}`);
|
|
196823
197470
|
for (const b of beats) {
|
|
196824
|
-
const mark = b.reachedRuns === 0 ? b.needsInput ? "? " : "\u203C " : " ";
|
|
197471
|
+
const mark = b.reachedRuns === 0 ? b.needsInput || b.blockedBy ? "? " : "\u203C " : " ";
|
|
196825
197472
|
const label = b.character ? `${b.character}: ${clip(b.preview)}` : clip(b.preview || `(${b.kind})`);
|
|
196826
197473
|
out.push(` ${mark}${b.reachPct.toFixed(0).padStart(3)}% ${String(b.hits).padStart(6)} ${label}`);
|
|
197474
|
+
for (const bg of b.blockedBy ?? []) {
|
|
197475
|
+
const names = bg.writers.map((w) => {
|
|
197476
|
+
const target = report.beats.find((x) => x.id === w);
|
|
197477
|
+
return target ? clip(target.preview || target.id, 28) : w;
|
|
197478
|
+
});
|
|
197479
|
+
out.push(` gated on ${bg.ref}, written only by: ${names.join(", ")} (never played either)`);
|
|
197480
|
+
}
|
|
196827
197481
|
}
|
|
196828
197482
|
}
|
|
196829
197483
|
return out;
|
|
@@ -196875,7 +197529,7 @@ function runReport(loaded, recordingOverride) {
|
|
|
196875
197529
|
const thresholdIdx = estimatingOn ? writingIndex.get(est?.thresholdStatus ?? "") ?? 0 : -1;
|
|
196876
197530
|
const defaultLines = est?.defaultLines ?? 0;
|
|
196877
197531
|
const tagMap = new Map((est?.tagEstimates ?? []).map((t) => [t.tag, t.lines]));
|
|
196878
|
-
const { writing: writingOf, recording: manualRecordingOf, cut: cutSet, rerecord: rerecordSet, edits: editsOf } =
|
|
197532
|
+
const { writing: writingOf, recording: manualRecordingOf, cut: cutSet, rerecord: rerecordSet, edits: editsOf } = mergeAuthoring2(loaded);
|
|
196879
197533
|
const recordingBase = recordingOverride ?? manualRecordingOf;
|
|
196880
197534
|
const recordingOf = (id) => effectiveRecording(id, recordingBase, rerecordSet, recordingLadder[0]);
|
|
196881
197535
|
const byLocale = stringsByLocale(loaded);
|
|
@@ -197232,7 +197886,7 @@ function classesForChannel(classes, channel) {
|
|
|
197232
197886
|
function resolveDocumentation(loaded, channel) {
|
|
197233
197887
|
const classes = loaded.project.documentationClasses ?? DEFAULT_DOCUMENTATION_CLASSES;
|
|
197234
197888
|
const allowed = classesForChannel(classes, channel);
|
|
197235
|
-
const docsOf =
|
|
197889
|
+
const docsOf = mergeAuthoring2(loaded).documentation;
|
|
197236
197890
|
const own = (id) => (docsOf.get(id) ?? []).filter((l2) => l2.type !== void 0 && allowed.has(l2.type));
|
|
197237
197891
|
const out = /* @__PURE__ */ new Map();
|
|
197238
197892
|
const visit = (id, inherited) => {
|
|
@@ -197268,7 +197922,7 @@ function extractLoc(loaded, opts = {}) {
|
|
|
197268
197922
|
const targetLocale = isTemplate ? void 0 : opts.locale;
|
|
197269
197923
|
const source2 = tableFor(loaded, defaultLocale);
|
|
197270
197924
|
const target = targetLocale ? tableFor(loaded, targetLocale) : {};
|
|
197271
|
-
const edits =
|
|
197925
|
+
const edits = mergeAuthoring2(loaded).edits;
|
|
197272
197926
|
const docs = resolveDocumentation(loaded, "loc");
|
|
197273
197927
|
const commentsOf = (id) => (docs.get(id) ?? []).map((d) => d.text);
|
|
197274
197928
|
const staleFor = (id, translation) => {
|
|
@@ -197608,7 +198262,7 @@ function runVoiceScript(loaded, opts = {}) {
|
|
|
197608
198262
|
const stub = writingLadder[0];
|
|
197609
198263
|
const recordThreshold = ladderDecls.findIndex((s) => s.readyToRecord);
|
|
197610
198264
|
const writingIndex = new Map(writingLadder.map((name, i) => [name, i]));
|
|
197611
|
-
const { writing: writingOf, recording: manualRecordingOf, cut: cutSet, rerecord: rerecordSet, documentation: docsOf } =
|
|
198265
|
+
const { writing: writingOf, recording: manualRecordingOf, cut: cutSet, rerecord: rerecordSet, documentation: docsOf } = mergeAuthoring2(loaded);
|
|
197612
198266
|
const recordingBase = opts.recordingOverride ?? manualRecordingOf;
|
|
197613
198267
|
const recordingOf = (id) => effectiveRecording(id, recordingBase, rerecordSet, recordingLadder[0]);
|
|
197614
198268
|
const source2 = sourceStrings(loaded);
|
|
@@ -197804,7 +198458,7 @@ function sequenceLabel(node) {
|
|
|
197804
198458
|
function runScriptDoc(loaded) {
|
|
197805
198459
|
const { project } = loaded;
|
|
197806
198460
|
const source2 = sourceStrings(loaded);
|
|
197807
|
-
const { cut } =
|
|
198461
|
+
const { cut } = mergeAuthoring2(loaded);
|
|
197808
198462
|
const sceneOf = /* @__PURE__ */ new Map();
|
|
197809
198463
|
const blockTrail = /* @__PURE__ */ new Map();
|
|
197810
198464
|
const blockName = /* @__PURE__ */ new Map();
|
|
@@ -238165,6 +238819,13 @@ async function runPack(startPath) {
|
|
|
238165
238819
|
const projectFile = findProjectFile(startPath);
|
|
238166
238820
|
const root2 = dirname4(projectFile);
|
|
238167
238821
|
const project = parseSource(readFileSync4(projectFile, "utf8"));
|
|
238822
|
+
const unresolved = sidecarIssues(walkFiles(root2, CONFLICT_SIDECAR));
|
|
238823
|
+
if (unresolved.length) {
|
|
238824
|
+
throw new Error(
|
|
238825
|
+
`${unresolved.length} unresolved merge conflict(s) - resolve them before packing, or the recipient gets your side of a disagreement with no sign of it:
|
|
238826
|
+
` + unresolved.map((i) => ` ${i.file}`).join("\n")
|
|
238827
|
+
);
|
|
238828
|
+
}
|
|
238168
238829
|
const files = SHARD_EXTENSIONS.flatMap((ext) => walkFiles(root2, ext)).map((abs) => ({ abs, rel: relative(root2, abs).split(sep2).join("/") })).sort((a, b) => a.rel.localeCompare(b.rel));
|
|
238169
238830
|
const manifest = {
|
|
238170
238831
|
schema: "patter/document@0",
|
|
@@ -238181,325 +238842,6 @@ async function runPack(startPath) {
|
|
|
238181
238842
|
var import_jszip2 = __toESM(require_lib3(), 1);
|
|
238182
238843
|
import { join as join5, normalize, isAbsolute as isAbsolute2, resolve as resolve4, sep as sep3 } from "path";
|
|
238183
238844
|
import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
|
|
238184
|
-
|
|
238185
|
-
// ../ops/src/merge.ts
|
|
238186
|
-
var UnsupportedMergeError = class extends Error {
|
|
238187
|
-
};
|
|
238188
|
-
var eq = (a, b) => canonicalStringify(a) === canonicalStringify(b);
|
|
238189
|
-
var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
238190
|
-
function detectMergeType(file) {
|
|
238191
|
-
const s = typeof file.schema === "string" ? file.schema : "";
|
|
238192
|
-
if (s.startsWith("patter/flow")) return "flow";
|
|
238193
|
-
if (s.startsWith("patter/strings")) return "loc";
|
|
238194
|
-
if (s.startsWith("patter/authoring")) return "authoring";
|
|
238195
|
-
if (s.startsWith("patter/project")) return "project";
|
|
238196
|
-
throw new UnsupportedMergeError(`cannot detect a Patter merge type from schema '${s}'`);
|
|
238197
|
-
}
|
|
238198
|
-
function runMerge(base, ours, theirs, opts) {
|
|
238199
|
-
const type = opts?.type ?? detectMergeType(ours);
|
|
238200
|
-
switch (type) {
|
|
238201
|
-
case "loc":
|
|
238202
|
-
return mergeLoc(base, ours, theirs);
|
|
238203
|
-
case "authoring":
|
|
238204
|
-
return mergeAuthoring2(base, ours, theirs);
|
|
238205
|
-
case "flow":
|
|
238206
|
-
return mergeFlow(base, ours, theirs);
|
|
238207
|
-
case "project":
|
|
238208
|
-
return mergeProject(base, ours, theirs);
|
|
238209
|
-
}
|
|
238210
|
-
}
|
|
238211
|
-
function deletedKind(base, ours, theirs) {
|
|
238212
|
-
const deleted = base !== void 0 && (ours === void 0 || theirs === void 0);
|
|
238213
|
-
return deleted ? "delete-vs-edit" : "both-changed";
|
|
238214
|
-
}
|
|
238215
|
-
function merge3(base, ours, theirs, id, path, conflicts) {
|
|
238216
|
-
if (eq(ours, theirs)) return ours;
|
|
238217
|
-
if (eq(base, ours)) return theirs;
|
|
238218
|
-
if (eq(base, theirs)) return ours;
|
|
238219
|
-
conflicts.push({ id, path, base, ours, theirs, kind: deletedKind(base, ours, theirs) });
|
|
238220
|
-
return ours;
|
|
238221
|
-
}
|
|
238222
|
-
function mergeMap(base, ours, theirs, prefix, conflicts, resolve6) {
|
|
238223
|
-
const out = {};
|
|
238224
|
-
for (const k of /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(ours), ...Object.keys(theirs)])) {
|
|
238225
|
-
const b = base[k], o = ours[k], t = theirs[k];
|
|
238226
|
-
let v;
|
|
238227
|
-
if (eq(o, t)) v = o;
|
|
238228
|
-
else if (eq(b, o)) v = t;
|
|
238229
|
-
else if (eq(b, t)) v = o;
|
|
238230
|
-
else {
|
|
238231
|
-
const r = resolve6?.(k, b, o, t);
|
|
238232
|
-
if (r && r.resolved) v = r.value;
|
|
238233
|
-
else {
|
|
238234
|
-
conflicts.push({ id: k, path: `${prefix}.${k}`, base: b, ours: o, theirs: t, kind: r ? r.kind : deletedKind(b, o, t) });
|
|
238235
|
-
v = o;
|
|
238236
|
-
}
|
|
238237
|
-
}
|
|
238238
|
-
if (v !== void 0) out[k] = v;
|
|
238239
|
-
}
|
|
238240
|
-
return out;
|
|
238241
|
-
}
|
|
238242
|
-
var asMap = (v) => isObj(v) ? v : {};
|
|
238243
|
-
var asArr = (v) => Array.isArray(v) ? v : [];
|
|
238244
|
-
function setIf(obj2, key2, val) {
|
|
238245
|
-
if (Array.isArray(val) ? val.length > 0 : Object.keys(val).length > 0) obj2[key2] = val;
|
|
238246
|
-
}
|
|
238247
|
-
function mergeLoc(base, ours, theirs) {
|
|
238248
|
-
const conflicts = [];
|
|
238249
|
-
const merged = {};
|
|
238250
|
-
for (const f of ["schema", "scene", "locale", "default"]) {
|
|
238251
|
-
const v = merge3(base[f], ours[f], theirs[f], "", f, conflicts);
|
|
238252
|
-
if (v !== void 0) merged[f] = v;
|
|
238253
|
-
}
|
|
238254
|
-
merged.strings = mergeMap(asMap(base.strings), asMap(ours.strings), asMap(theirs.strings), "strings", conflicts);
|
|
238255
|
-
return { type: "loc", merged, conflicts, warnings: [] };
|
|
238256
|
-
}
|
|
238257
|
-
function mergeAuthoring2(base, ours, theirs) {
|
|
238258
|
-
const conflicts = [];
|
|
238259
|
-
const merged = {};
|
|
238260
|
-
merged.schema = merge3(base.schema, ours.schema, theirs.schema, "", "schema", conflicts) ?? ours.schema;
|
|
238261
|
-
setIf(merged, "comments", mergeComments(asArr(base.comments), asArr(ours.comments), asArr(theirs.comments)));
|
|
238262
|
-
const edits = mergeEdits(asMap(base.edits), asMap(ours.edits), asMap(theirs.edits));
|
|
238263
|
-
setIf(merged, "edits", edits);
|
|
238264
|
-
const lww = lastWriterWins(asMap(ours.edits), asMap(theirs.edits));
|
|
238265
|
-
for (const f of ["writing", "recording", "audio"]) {
|
|
238266
|
-
setIf(merged, f, mergeMap(asMap(base[f]), asMap(ours[f]), asMap(theirs[f]), f, conflicts, lww));
|
|
238267
|
-
}
|
|
238268
|
-
setIf(merged, "documentation", mergeMap(asMap(base.documentation), asMap(ours.documentation), asMap(theirs.documentation), "documentation", conflicts));
|
|
238269
|
-
setIf(merged, "cut", mergeMap(asMap(base.cut), asMap(ours.cut), asMap(theirs.cut), "cut", conflicts, void 0));
|
|
238270
|
-
return { type: "authoring", merged, conflicts, warnings: [] };
|
|
238271
|
-
}
|
|
238272
|
-
function mergeComments(base, ours, theirs) {
|
|
238273
|
-
const byId = /* @__PURE__ */ new Map();
|
|
238274
|
-
for (const c2 of [...base, ...ours, ...theirs]) {
|
|
238275
|
-
if (isObj(c2) && typeof c2.id === "string" && !byId.has(c2.id)) byId.set(c2.id, c2);
|
|
238276
|
-
}
|
|
238277
|
-
return [...byId.values()].sort((a, b) => String(a.ts ?? "").localeCompare(String(b.ts ?? "")));
|
|
238278
|
-
}
|
|
238279
|
-
function lastWriterWins(oursEdits, theirsEdits) {
|
|
238280
|
-
return (id, _b, o, t) => {
|
|
238281
|
-
const om = asMap(oursEdits[id]).modifiedAt, tm = asMap(theirsEdits[id]).modifiedAt;
|
|
238282
|
-
if (typeof om === "string" && typeof tm === "string") return { resolved: true, value: om >= tm ? o : t };
|
|
238283
|
-
return { resolved: false, kind: "no-timestamp" };
|
|
238284
|
-
};
|
|
238285
|
-
}
|
|
238286
|
-
function mergeEdits(base, ours, theirs) {
|
|
238287
|
-
const out = {};
|
|
238288
|
-
for (const id of /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(ours), ...Object.keys(theirs)])) {
|
|
238289
|
-
const o = ours[id], t = theirs[id];
|
|
238290
|
-
if (eq(o, t)) {
|
|
238291
|
-
if (o !== void 0) out[id] = o;
|
|
238292
|
-
continue;
|
|
238293
|
-
}
|
|
238294
|
-
if (o === void 0) {
|
|
238295
|
-
if (t !== void 0) out[id] = t;
|
|
238296
|
-
continue;
|
|
238297
|
-
}
|
|
238298
|
-
if (t === void 0) {
|
|
238299
|
-
out[id] = o;
|
|
238300
|
-
continue;
|
|
238301
|
-
}
|
|
238302
|
-
out[id] = mergeEditRecord(asMap(o), asMap(t));
|
|
238303
|
-
}
|
|
238304
|
-
return out;
|
|
238305
|
-
}
|
|
238306
|
-
function mergeEditRecord(o, t) {
|
|
238307
|
-
const om = typeof o.modifiedAt === "string" ? o.modifiedAt : "";
|
|
238308
|
-
const tm = typeof t.modifiedAt === "string" ? t.modifiedAt : "";
|
|
238309
|
-
const newer = om >= tm ? o : t;
|
|
238310
|
-
const ol = asMap(o.localisedAt), tl = asMap(t.localisedAt);
|
|
238311
|
-
const loc = {};
|
|
238312
|
-
for (const k of /* @__PURE__ */ new Set([...Object.keys(ol), ...Object.keys(tl)])) {
|
|
238313
|
-
const a = typeof ol[k] === "string" ? ol[k] : "";
|
|
238314
|
-
const b = typeof tl[k] === "string" ? tl[k] : "";
|
|
238315
|
-
loc[k] = a >= b ? ol[k] ?? tl[k] : tl[k] ?? ol[k];
|
|
238316
|
-
}
|
|
238317
|
-
const merged = { ...newer };
|
|
238318
|
-
if (Object.keys(loc).length > 0) merged.localisedAt = loc;
|
|
238319
|
-
return merged;
|
|
238320
|
-
}
|
|
238321
|
-
function mergeKeyedByName(base, ours, theirs, path, conflicts) {
|
|
238322
|
-
const byName = (arr) => {
|
|
238323
|
-
const m = {};
|
|
238324
|
-
for (const p of arr) if (isObj(p) && typeof p.name === "string") m[p.name] = p;
|
|
238325
|
-
return m;
|
|
238326
|
-
};
|
|
238327
|
-
const merged = mergeMap(byName(base), byName(ours), byName(theirs), path, conflicts);
|
|
238328
|
-
return Object.keys(merged).sort().map((name) => merged[name]);
|
|
238329
|
-
}
|
|
238330
|
-
function mergeProject(base, ours, theirs) {
|
|
238331
|
-
const conflicts = [];
|
|
238332
|
-
const merged = {};
|
|
238333
|
-
for (const k of /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(ours), ...Object.keys(theirs)])) {
|
|
238334
|
-
if (k === "properties" || k === "cast") {
|
|
238335
|
-
const v = mergeKeyedByName(asArr(base[k]), asArr(ours[k]), asArr(theirs[k]), k, conflicts);
|
|
238336
|
-
if (v.length > 0) merged[k] = v;
|
|
238337
|
-
} else if (k === "gameDataFields") {
|
|
238338
|
-
const v = mergeGameDataFields(asMap(base[k]), asMap(ours[k]), asMap(theirs[k]), conflicts);
|
|
238339
|
-
if (Object.keys(v).length > 0) merged[k] = v;
|
|
238340
|
-
} else if (k === "locales") {
|
|
238341
|
-
merged.locales = mergeLocales(asMap(base.locales), asMap(ours.locales), asMap(theirs.locales), conflicts);
|
|
238342
|
-
} else {
|
|
238343
|
-
const v = merge3(base[k], ours[k], theirs[k], "", k, conflicts);
|
|
238344
|
-
if (v !== void 0) merged[k] = v;
|
|
238345
|
-
}
|
|
238346
|
-
}
|
|
238347
|
-
return { type: "project", merged, conflicts, warnings: [] };
|
|
238348
|
-
}
|
|
238349
|
-
function mergeGameDataFields(b, o, t, conflicts) {
|
|
238350
|
-
const out = {};
|
|
238351
|
-
for (const kind of /* @__PURE__ */ new Set([...Object.keys(b), ...Object.keys(o), ...Object.keys(t)])) {
|
|
238352
|
-
const v = mergeKeyedByName(asArr(b[kind]), asArr(o[kind]), asArr(t[kind]), `gameDataFields.${kind}`, conflicts);
|
|
238353
|
-
if (v.length > 0) out[kind] = v;
|
|
238354
|
-
}
|
|
238355
|
-
return out;
|
|
238356
|
-
}
|
|
238357
|
-
function mergeLocales(b, o, t, conflicts) {
|
|
238358
|
-
const out = {};
|
|
238359
|
-
const def = merge3(b.default, o.default, t.default, "", "locales.default", conflicts);
|
|
238360
|
-
if (def !== void 0) out.default = def;
|
|
238361
|
-
const all = [];
|
|
238362
|
-
for (const arr of [asArr(b.all), asArr(o.all), asArr(t.all)]) {
|
|
238363
|
-
for (const x of arr) if (typeof x === "string" && !all.includes(x)) all.push(x);
|
|
238364
|
-
}
|
|
238365
|
-
out.all = all;
|
|
238366
|
-
return out;
|
|
238367
|
-
}
|
|
238368
|
-
var CHILD_KEYS = ["blocks", "children", "beats"];
|
|
238369
|
-
function toTree(raw) {
|
|
238370
|
-
const childKey = CHILD_KEYS.find((k) => Array.isArray(raw[k]));
|
|
238371
|
-
const fields = {};
|
|
238372
|
-
for (const [k, v] of Object.entries(raw)) if (k !== childKey) fields[k] = v;
|
|
238373
|
-
const children = childKey ? raw[childKey].filter(isObj).map(toTree) : [];
|
|
238374
|
-
return { id: typeof raw.id === "string" ? raw.id : "", fields, childKey, children };
|
|
238375
|
-
}
|
|
238376
|
-
function fromTree(n) {
|
|
238377
|
-
const out = { ...n.fields };
|
|
238378
|
-
if (n.childKey) out[n.childKey] = n.children.map(fromTree);
|
|
238379
|
-
return out;
|
|
238380
|
-
}
|
|
238381
|
-
function mergeFlow(base, ours, theirs) {
|
|
238382
|
-
const conflicts = [];
|
|
238383
|
-
const warnings = [];
|
|
238384
|
-
const merged = {};
|
|
238385
|
-
merged.schema = merge3(base.schema, ours.schema, theirs.schema, "", "schema", conflicts) ?? ours.schema;
|
|
238386
|
-
const bScene = isObj(base.scene) ? toTree(base.scene) : void 0;
|
|
238387
|
-
const oScene = toTree(asMap(ours.scene));
|
|
238388
|
-
const tScene = isObj(theirs.scene) ? toTree(theirs.scene) : void 0;
|
|
238389
|
-
const mergedScene = mergeNode(bScene, oScene, tScene, "scene", conflicts, warnings);
|
|
238390
|
-
merged.scene = fromTree(mergedScene);
|
|
238391
|
-
checkDuplicateIds(mergedScene, conflicts);
|
|
238392
|
-
return { type: "flow", merged, conflicts, warnings };
|
|
238393
|
-
}
|
|
238394
|
-
function mergeNode(b, o, t, path, conflicts, warnings) {
|
|
238395
|
-
const fields = mergeFields(b?.fields ?? {}, o.fields, t?.fields ?? {}, o.id, path, conflicts);
|
|
238396
|
-
const childKey = o.childKey ?? t?.childKey ?? b?.childKey;
|
|
238397
|
-
const children = childKey ? mergeChildren(b?.children ?? [], o.children, t?.children ?? [], `${path}.${childKey}`, conflicts, warnings) : [];
|
|
238398
|
-
return { id: o.id, fields, childKey, children };
|
|
238399
|
-
}
|
|
238400
|
-
function mergeFields(b, o, t, id, path, conflicts) {
|
|
238401
|
-
const out = {};
|
|
238402
|
-
for (const k of /* @__PURE__ */ new Set([...Object.keys(b), ...Object.keys(o), ...Object.keys(t)])) {
|
|
238403
|
-
const v = merge3(b[k], o[k], t[k], id, `${path}.${k}`, conflicts);
|
|
238404
|
-
if (v !== void 0) out[k] = v;
|
|
238405
|
-
}
|
|
238406
|
-
return out;
|
|
238407
|
-
}
|
|
238408
|
-
function mergeChildren(B, O, T, path, conflicts, warnings) {
|
|
238409
|
-
const Bm = idMap(B), Om = idMap(O), Tm = idMap(T);
|
|
238410
|
-
const Bset = new Set(Bm.keys()), Oset = new Set(Om.keys()), Tset = new Set(Tm.keys());
|
|
238411
|
-
const merged = /* @__PURE__ */ new Map();
|
|
238412
|
-
const here = (id) => `${path}[${id}]`;
|
|
238413
|
-
for (const id of /* @__PURE__ */ new Set([...Bm.keys(), ...Om.keys(), ...Tm.keys()])) {
|
|
238414
|
-
const b = Bm.get(id), o = Om.get(id), t = Tm.get(id);
|
|
238415
|
-
const inB = Bset.has(id), inO = Oset.has(id), inT = Tset.has(id);
|
|
238416
|
-
if (inO && inT) {
|
|
238417
|
-
if (!inB && !eq(fromTree(o), fromTree(t))) {
|
|
238418
|
-
conflicts.push({ id, path: here(id), base: void 0, ours: fromTree(o), theirs: fromTree(t), kind: "added-both" });
|
|
238419
|
-
merged.set(id, o);
|
|
238420
|
-
} else {
|
|
238421
|
-
merged.set(id, mergeNode(b, o, t, here(id), conflicts, warnings));
|
|
238422
|
-
}
|
|
238423
|
-
} else if (inO && !inT) {
|
|
238424
|
-
if (inB && !eq(fromTree(o), fromTree(b))) {
|
|
238425
|
-
conflicts.push({ id, path: here(id), base: fromTree(b), ours: fromTree(o), theirs: void 0, kind: "delete-vs-edit" });
|
|
238426
|
-
merged.set(id, o);
|
|
238427
|
-
} else if (!inB) {
|
|
238428
|
-
merged.set(id, o);
|
|
238429
|
-
}
|
|
238430
|
-
} else if (!inO && inT) {
|
|
238431
|
-
if (inB && !eq(fromTree(t), fromTree(b))) {
|
|
238432
|
-
conflicts.push({ id, path: here(id), base: fromTree(b), ours: void 0, theirs: fromTree(t), kind: "delete-vs-edit" });
|
|
238433
|
-
} else if (!inB) {
|
|
238434
|
-
merged.set(id, t);
|
|
238435
|
-
}
|
|
238436
|
-
}
|
|
238437
|
-
}
|
|
238438
|
-
return orderChildren(merged, B, O, T, path, conflicts, warnings);
|
|
238439
|
-
}
|
|
238440
|
-
function orderChildren(merged, B, O, T, path, conflicts, warnings) {
|
|
238441
|
-
const Bids = B.map((n) => n.id), Oids = O.map((n) => n.id), Tids = T.map((n) => n.id);
|
|
238442
|
-
const Oidset = new Set(Oids);
|
|
238443
|
-
const common = new Set(Bids.filter((id) => merged.has(id)));
|
|
238444
|
-
const restrict = (ids) => ids.filter((id) => common.has(id));
|
|
238445
|
-
const relB = restrict(Bids), relO = restrict(Oids), relT = restrict(Tids);
|
|
238446
|
-
const fullO = relO.length === common.size, fullT = relT.length === common.size;
|
|
238447
|
-
let commonOrder;
|
|
238448
|
-
if (!fullO && !fullT) commonOrder = relB;
|
|
238449
|
-
else if (!fullO) commonOrder = relT;
|
|
238450
|
-
else if (!fullT) commonOrder = relO;
|
|
238451
|
-
else if (arrEq(relO, relB)) commonOrder = relT;
|
|
238452
|
-
else if (arrEq(relT, relB)) commonOrder = relO;
|
|
238453
|
-
else if (arrEq(relO, relT)) commonOrder = relO;
|
|
238454
|
-
else {
|
|
238455
|
-
conflicts.push({ id: "", path, base: relB, ours: relO, theirs: relT, kind: "moved" });
|
|
238456
|
-
commonOrder = relO;
|
|
238457
|
-
}
|
|
238458
|
-
const anchorIn = (ids, id) => {
|
|
238459
|
-
for (let i = ids.indexOf(id) - 1; i >= 0; i--) if (common.has(ids[i])) return ids[i];
|
|
238460
|
-
return null;
|
|
238461
|
-
};
|
|
238462
|
-
const byAnchor = /* @__PURE__ */ new Map();
|
|
238463
|
-
const group = (a) => {
|
|
238464
|
-
let g = byAnchor.get(a);
|
|
238465
|
-
if (!g) {
|
|
238466
|
-
g = { ours: [], theirs: [] };
|
|
238467
|
-
byAnchor.set(a, g);
|
|
238468
|
-
}
|
|
238469
|
-
return g;
|
|
238470
|
-
};
|
|
238471
|
-
for (const n of O) if (merged.has(n.id) && !common.has(n.id)) group(anchorIn(Oids, n.id)).ours.push(n.id);
|
|
238472
|
-
for (const n of T) if (merged.has(n.id) && !common.has(n.id) && !Oidset.has(n.id)) group(anchorIn(Tids, n.id)).theirs.push(n.id);
|
|
238473
|
-
const out = [];
|
|
238474
|
-
const emit = (anchor) => {
|
|
238475
|
-
const g = byAnchor.get(anchor);
|
|
238476
|
-
if (!g) return;
|
|
238477
|
-
if (g.ours.length > 0 && g.theirs.length > 0) warnings.push({ id: anchor ?? "", path, message: "both sides inserted here; kept ours-then-theirs - review the order" });
|
|
238478
|
-
out.push(...g.ours, ...g.theirs);
|
|
238479
|
-
};
|
|
238480
|
-
emit(null);
|
|
238481
|
-
for (const id of commonOrder) {
|
|
238482
|
-
out.push(id);
|
|
238483
|
-
emit(id);
|
|
238484
|
-
}
|
|
238485
|
-
return out.map((id) => merged.get(id));
|
|
238486
|
-
}
|
|
238487
|
-
function checkDuplicateIds(root2, conflicts) {
|
|
238488
|
-
const seen = /* @__PURE__ */ new Set(), dup = /* @__PURE__ */ new Set();
|
|
238489
|
-
const walk2 = (n) => {
|
|
238490
|
-
if (n.id) {
|
|
238491
|
-
if (seen.has(n.id)) dup.add(n.id);
|
|
238492
|
-
else seen.add(n.id);
|
|
238493
|
-
}
|
|
238494
|
-
n.children.forEach(walk2);
|
|
238495
|
-
};
|
|
238496
|
-
walk2(root2);
|
|
238497
|
-
for (const id of dup) conflicts.push({ id, path: "scene", base: void 0, ours: void 0, theirs: void 0, kind: "structural" });
|
|
238498
|
-
}
|
|
238499
|
-
var idMap = (nodes) => new Map(nodes.filter((n) => n.id).map((n) => [n.id, n]));
|
|
238500
|
-
var arrEq = (a, b) => a.length === b.length && a.every((x, i) => x === b[i]);
|
|
238501
|
-
|
|
238502
|
-
// ../ops/src/unpack.ts
|
|
238503
238845
|
var MANIFEST = "patter.manifest.json";
|
|
238504
238846
|
var UnsafeEntryError = class extends Error {
|
|
238505
238847
|
};
|
|
@@ -238562,10 +238904,17 @@ async function runUnpackMerge(returnedBytes, baseBytes, projectDir) {
|
|
|
238562
238904
|
shards.push({ path: rel, added: true });
|
|
238563
238905
|
continue;
|
|
238564
238906
|
}
|
|
238565
|
-
const
|
|
238566
|
-
|
|
238907
|
+
const readSide = (side, text) => {
|
|
238908
|
+
try {
|
|
238909
|
+
return parseSource(text);
|
|
238910
|
+
} catch (e) {
|
|
238911
|
+
throw new Error(`${rel}: the ${side} copy is not readable Patter source - ${e instanceof Error ? e.message : String(e)}`);
|
|
238912
|
+
}
|
|
238913
|
+
};
|
|
238914
|
+
const oursObj = readSide("ours", readFileSync5(outPath, "utf8"));
|
|
238915
|
+
const theirsObj = readSide("theirs", theirText);
|
|
238567
238916
|
const baseText = base.get(rel);
|
|
238568
|
-
const baseObj = baseText !== void 0 ?
|
|
238917
|
+
const baseObj = baseText !== void 0 ? readSide("base", baseText) : {};
|
|
238569
238918
|
const result = runMerge(baseObj, oursObj, theirsObj);
|
|
238570
238919
|
writes.push({ path: outPath, content: canonicalStringify(result.merged) });
|
|
238571
238920
|
if (result.conflicts.length > 0) {
|
|
@@ -240467,6 +240816,44 @@ function writeTextFiles(files, encoding = "utf8") {
|
|
|
240467
240816
|
return { success: results.every((r) => r.success), results };
|
|
240468
240817
|
}
|
|
240469
240818
|
|
|
240819
|
+
// package.json
|
|
240820
|
+
var package_default = {
|
|
240821
|
+
name: "@patterkit/cli",
|
|
240822
|
+
version: "0.3.0",
|
|
240823
|
+
description: "The `patter` CLI: validate, format, export, and play a Patter project.",
|
|
240824
|
+
type: "module",
|
|
240825
|
+
license: "MIT",
|
|
240826
|
+
author: "Ian Thomas",
|
|
240827
|
+
homepage: "https://patterkit.dev",
|
|
240828
|
+
repository: {
|
|
240829
|
+
type: "git",
|
|
240830
|
+
url: "git+https://github.com/patterkit/patter.git",
|
|
240831
|
+
directory: "packages/cli"
|
|
240832
|
+
},
|
|
240833
|
+
bugs: "https://github.com/patterkit/patter/issues",
|
|
240834
|
+
publishConfig: {
|
|
240835
|
+
access: "public"
|
|
240836
|
+
},
|
|
240837
|
+
bin: {
|
|
240838
|
+
patter: "./dist/cli.js"
|
|
240839
|
+
},
|
|
240840
|
+
files: [
|
|
240841
|
+
"dist",
|
|
240842
|
+
"README.md"
|
|
240843
|
+
],
|
|
240844
|
+
scripts: {
|
|
240845
|
+
build: "tsup",
|
|
240846
|
+
"build:standalone": "node scripts/build-standalone.mjs",
|
|
240847
|
+
"build:standalone:mac": "node scripts/build-standalone.mjs --targets=darwin-arm64,darwin-x64",
|
|
240848
|
+
"build:standalone:others": "node scripts/build-standalone.mjs --targets=linux-x64,linux-arm64,windows-x64"
|
|
240849
|
+
},
|
|
240850
|
+
dependencies: {
|
|
240851
|
+
"@patterkit/core": "0.2.1",
|
|
240852
|
+
"@patterkit/ops": "0.5.0",
|
|
240853
|
+
"@wildwinter/simple-vc-lib": "^0.4.1"
|
|
240854
|
+
}
|
|
240855
|
+
};
|
|
240856
|
+
|
|
240470
240857
|
// src/main.ts
|
|
240471
240858
|
var isPatterSource = (path) => SHARD_EXTENSIONS.some((ext) => path.endsWith(ext));
|
|
240472
240859
|
function writeMergeResult(result, out, announce) {
|
|
@@ -240486,6 +240873,8 @@ function writeMergeResult(result, out, announce) {
|
|
|
240486
240873
|
var USAGE = `patter - Patter CLI
|
|
240487
240874
|
|
|
240488
240875
|
Usage:
|
|
240876
|
+
patter --version Print the version (also -v / version)
|
|
240877
|
+
patter --help Print this usage (also -h / help)
|
|
240489
240878
|
patter init [dir] Scaffold a new project (the <dir>.patter folder, starter scene, VCS config)
|
|
240490
240879
|
[--name X] [--vcs git|perforce|plastic|svn] [--bundle commit|ignore]
|
|
240491
240880
|
patter validate [path] Validate a project (structural + expressions + encoding + bundle)
|
|
@@ -240603,6 +240992,14 @@ async function main(argv) {
|
|
|
240603
240992
|
console.log(USAGE);
|
|
240604
240993
|
return 0;
|
|
240605
240994
|
}
|
|
240995
|
+
if (cmd === "--version" || cmd === "-v" || cmd === "version") {
|
|
240996
|
+
console.log(package_default.version);
|
|
240997
|
+
return 0;
|
|
240998
|
+
}
|
|
240999
|
+
if (cmd === "--help" || cmd === "-h" || cmd === "help") {
|
|
241000
|
+
console.log(USAGE);
|
|
241001
|
+
return 0;
|
|
241002
|
+
}
|
|
240606
241003
|
const canonical = cmd === "fmt" ? "format" : cmd === "stats" ? "report" : cmd;
|
|
240607
241004
|
if (!(canonical in FLAGS)) {
|
|
240608
241005
|
console.log(USAGE);
|
|
@@ -240689,15 +241086,18 @@ async function run(cmd, positionals, flags) {
|
|
|
240689
241086
|
}
|
|
240690
241087
|
case "validate": {
|
|
240691
241088
|
const loaded = loadProject(positionals[0] ?? ".");
|
|
240692
|
-
const { structural, conditions, interpolation, hygiene, staleBundles, unresolvedMerges, ok } = runValidate(loaded);
|
|
241089
|
+
const { structural, conditions, interpolation, hygiene, staleBundles, unresolvedMerges, orphans, reachability, ok } = runValidate(loaded);
|
|
240693
241090
|
for (const i of structural) console.error(` [${i.code}] ${i.message}`);
|
|
240694
241091
|
for (const i of conditions) console.error(` [${i.field}] ${i.nodeId}: ${i.message} (${i.src})`);
|
|
240695
241092
|
for (const i of interpolation) console.error(` [${i.field}] ${i.nodeId}: ${i.message} (${i.src})`);
|
|
240696
241093
|
for (const i of hygiene) console.error(` [hygiene] ${i.file}: ${i.message}`);
|
|
240697
241094
|
for (const i of staleBundles) console.error(` [stale-bundle] ${i.file}: ${i.message}`);
|
|
240698
241095
|
for (const i of unresolvedMerges) console.error(` [unresolved-merge] ${i.file}: ${i.message}`);
|
|
240699
|
-
const
|
|
240700
|
-
|
|
241096
|
+
for (const i of orphans) console.error(` [not-in-project] ${i.file}: ${i.message}`);
|
|
241097
|
+
for (const i of reachability) console.error(` [unreachable] ${i.nodeId}: ${i.message} (${i.src})`);
|
|
241098
|
+
const count = structural.length + conditions.length + interpolation.length + hygiene.length + staleBundles.length + unresolvedMerges.length + orphans.length;
|
|
241099
|
+
const advisory = reachability.length ? `, ${reachability.length} warning(s) above` : "";
|
|
241100
|
+
if (ok) console.log(`ok - ${loaded.scenes.length} scene(s), no issues${advisory}`);
|
|
240701
241101
|
else console.error(`
|
|
240702
241102
|
${count} issue(s)`);
|
|
240703
241103
|
return ok ? 0 : 1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@patterkit/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "The `patter` CLI: validate, format, export, and play a Patter project.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@patterkit/core": "0.2.1",
|
|
33
|
-
"@patterkit/ops": "0.
|
|
33
|
+
"@patterkit/ops": "0.5.0",
|
|
34
34
|
"@wildwinter/simple-vc-lib": "^0.4.1"
|
|
35
35
|
}
|
|
36
36
|
}
|