@patterkit/cli 0.2.7 → 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 +347 -16
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -194628,6 +194628,201 @@ function exportBundle(input) {
|
|
|
194628
194628
|
};
|
|
194629
194629
|
}
|
|
194630
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
|
+
|
|
194631
194826
|
// ../ops/src/validate.ts
|
|
194632
194827
|
function runValidate(loaded) {
|
|
194633
194828
|
const { project, scenes, locales } = loaded;
|
|
@@ -194639,10 +194834,12 @@ function runValidate(loaded) {
|
|
|
194639
194834
|
const staleBundles = checkBundles(loaded);
|
|
194640
194835
|
const unresolvedMerges = sidecarIssues(walkFiles(loaded.root, CONFLICT_SIDECAR));
|
|
194641
194836
|
const orphans = orphanShards(loaded);
|
|
194837
|
+
const reachability = structural.length === 0 && conditions.length === 0 ? reachabilityIssues(loaded) : [];
|
|
194642
194838
|
return {
|
|
194643
194839
|
structural,
|
|
194644
194840
|
conditions,
|
|
194645
194841
|
interpolation,
|
|
194842
|
+
reachability,
|
|
194646
194843
|
hygiene,
|
|
194647
194844
|
staleBundles,
|
|
194648
194845
|
unresolvedMerges,
|
|
@@ -196959,40 +197156,113 @@ function targetHostRef(target, hostTokens) {
|
|
|
196959
197156
|
const m = /^@([A-Za-z_][\w]*)\.(.+)$/.exec(target);
|
|
196960
197157
|
return m && hostTokens.has(m[1]) ? `@${m[1]}.${m[2]}` : void 0;
|
|
196961
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
|
+
}
|
|
196962
197183
|
function analyzeHostScopes(bundle, hostTokens) {
|
|
196963
197184
|
const written = /* @__PURE__ */ new Set();
|
|
196964
197185
|
const gatesByBeat = /* @__PURE__ */ new Map();
|
|
197186
|
+
const fineGatesByBeat = /* @__PURE__ */ new Map();
|
|
196965
197187
|
const proposals = /* @__PURE__ */ new Map();
|
|
196966
|
-
|
|
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;
|
|
196967
197192
|
const refsIn = (expr) => {
|
|
196968
197193
|
const refs = /* @__PURE__ */ new Set();
|
|
196969
197194
|
if (expr) scanExpr(deserialiseAst(expr.ast), hostTokens, refs, proposals);
|
|
196970
197195
|
return refs;
|
|
196971
197196
|
};
|
|
196972
|
-
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) => {
|
|
196973
197206
|
for (const e of effects ?? []) {
|
|
196974
|
-
const
|
|
196975
|
-
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
|
+
}
|
|
196976
197215
|
refsIn(e.value);
|
|
196977
197216
|
}
|
|
196978
197217
|
};
|
|
196979
|
-
const walk2 = (nodes, gate) => {
|
|
197218
|
+
const walk2 = (nodes, gate, fine) => {
|
|
196980
197219
|
for (const node of nodes) {
|
|
196981
197220
|
const here = /* @__PURE__ */ new Set([...gate, ...refsIn(node.condition)]);
|
|
197221
|
+
const hereFine = /* @__PURE__ */ new Set([...fine, ...fineIn(node.condition)]);
|
|
196982
197222
|
if (node.type === "group") {
|
|
196983
|
-
walk2(node.children, here);
|
|
197223
|
+
walk2(node.children, here, hereFine);
|
|
196984
197224
|
} else {
|
|
196985
|
-
|
|
196986
|
-
scanEffects(node.
|
|
196987
|
-
|
|
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
|
+
}
|
|
196988
197232
|
}
|
|
196989
197233
|
}
|
|
196990
197234
|
};
|
|
196991
197235
|
for (const scene of Object.values(bundle.scenes)) {
|
|
196992
|
-
|
|
196993
|
-
|
|
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());
|
|
197246
|
+
}
|
|
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 });
|
|
196994
197264
|
}
|
|
196995
|
-
return
|
|
197265
|
+
return out;
|
|
196996
197266
|
}
|
|
196997
197267
|
function proposeCoverageDrivers(loaded) {
|
|
196998
197268
|
const hostTokens = new Set((loaded.project.scopeRegistry?.scopes ?? []).map((s) => s.token));
|
|
@@ -197125,12 +197395,15 @@ function* sweep(loaded, options = {}, hooks = {}) {
|
|
|
197125
197395
|
const m = meta.get(id);
|
|
197126
197396
|
const reached = reachedRuns.get(id);
|
|
197127
197397
|
let needsInput;
|
|
197398
|
+
let blockedBy;
|
|
197128
197399
|
if (reached === 0) {
|
|
197129
197400
|
const gates = [...analysis.gatesByBeat.get(id) ?? []].filter((r) => !analysis.written.has(r) && !drivenRefs.has(r));
|
|
197130
197401
|
if (gates.length) {
|
|
197131
197402
|
needsInput = gates;
|
|
197132
197403
|
for (const g of gates) unwrittenInputs.add(g);
|
|
197133
197404
|
}
|
|
197405
|
+
const blocked = blockedGates(id, analysis, drivenRefs, reachedRuns);
|
|
197406
|
+
if (blocked.length) blockedBy = blocked;
|
|
197134
197407
|
}
|
|
197135
197408
|
return {
|
|
197136
197409
|
id,
|
|
@@ -197141,7 +197414,8 @@ function* sweep(loaded, options = {}, hooks = {}) {
|
|
|
197141
197414
|
hits: hitCount.get(id),
|
|
197142
197415
|
reachedRuns: reached,
|
|
197143
197416
|
reachPct: executed ? reached / executed * 100 : 0,
|
|
197144
|
-
...needsInput ? { needsInput } : {}
|
|
197417
|
+
...needsInput ? { needsInput } : {},
|
|
197418
|
+
...blockedBy ? { blockedBy } : {}
|
|
197145
197419
|
};
|
|
197146
197420
|
});
|
|
197147
197421
|
const neverHit = beats.filter((b) => b.reachedRuns === 0).length;
|
|
@@ -197194,9 +197468,16 @@ function renderCoverageText(report, sceneName = (id) => id) {
|
|
|
197194
197468
|
out.push("");
|
|
197195
197469
|
out.push(`${sceneName(scene)}${dead ? ` (${dead} never reached)` : ""}`);
|
|
197196
197470
|
for (const b of beats) {
|
|
197197
|
-
const mark = b.reachedRuns === 0 ? b.needsInput ? "? " : "\u203C " : " ";
|
|
197471
|
+
const mark = b.reachedRuns === 0 ? b.needsInput || b.blockedBy ? "? " : "\u203C " : " ";
|
|
197198
197472
|
const label = b.character ? `${b.character}: ${clip(b.preview)}` : clip(b.preview || `(${b.kind})`);
|
|
197199
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
|
+
}
|
|
197200
197481
|
}
|
|
197201
197482
|
}
|
|
197202
197483
|
return out;
|
|
@@ -240535,6 +240816,44 @@ function writeTextFiles(files, encoding = "utf8") {
|
|
|
240535
240816
|
return { success: results.every((r) => r.success), results };
|
|
240536
240817
|
}
|
|
240537
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
|
+
|
|
240538
240857
|
// src/main.ts
|
|
240539
240858
|
var isPatterSource = (path) => SHARD_EXTENSIONS.some((ext) => path.endsWith(ext));
|
|
240540
240859
|
function writeMergeResult(result, out, announce) {
|
|
@@ -240554,6 +240873,8 @@ function writeMergeResult(result, out, announce) {
|
|
|
240554
240873
|
var USAGE = `patter - Patter CLI
|
|
240555
240874
|
|
|
240556
240875
|
Usage:
|
|
240876
|
+
patter --version Print the version (also -v / version)
|
|
240877
|
+
patter --help Print this usage (also -h / help)
|
|
240557
240878
|
patter init [dir] Scaffold a new project (the <dir>.patter folder, starter scene, VCS config)
|
|
240558
240879
|
[--name X] [--vcs git|perforce|plastic|svn] [--bundle commit|ignore]
|
|
240559
240880
|
patter validate [path] Validate a project (structural + expressions + encoding + bundle)
|
|
@@ -240671,6 +240992,14 @@ async function main(argv) {
|
|
|
240671
240992
|
console.log(USAGE);
|
|
240672
240993
|
return 0;
|
|
240673
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
|
+
}
|
|
240674
241003
|
const canonical = cmd === "fmt" ? "format" : cmd === "stats" ? "report" : cmd;
|
|
240675
241004
|
if (!(canonical in FLAGS)) {
|
|
240676
241005
|
console.log(USAGE);
|
|
@@ -240757,7 +241086,7 @@ async function run(cmd, positionals, flags) {
|
|
|
240757
241086
|
}
|
|
240758
241087
|
case "validate": {
|
|
240759
241088
|
const loaded = loadProject(positionals[0] ?? ".");
|
|
240760
|
-
const { structural, conditions, interpolation, hygiene, staleBundles, unresolvedMerges, orphans, ok } = runValidate(loaded);
|
|
241089
|
+
const { structural, conditions, interpolation, hygiene, staleBundles, unresolvedMerges, orphans, reachability, ok } = runValidate(loaded);
|
|
240761
241090
|
for (const i of structural) console.error(` [${i.code}] ${i.message}`);
|
|
240762
241091
|
for (const i of conditions) console.error(` [${i.field}] ${i.nodeId}: ${i.message} (${i.src})`);
|
|
240763
241092
|
for (const i of interpolation) console.error(` [${i.field}] ${i.nodeId}: ${i.message} (${i.src})`);
|
|
@@ -240765,8 +241094,10 @@ async function run(cmd, positionals, flags) {
|
|
|
240765
241094
|
for (const i of staleBundles) console.error(` [stale-bundle] ${i.file}: ${i.message}`);
|
|
240766
241095
|
for (const i of unresolvedMerges) console.error(` [unresolved-merge] ${i.file}: ${i.message}`);
|
|
240767
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})`);
|
|
240768
241098
|
const count = structural.length + conditions.length + interpolation.length + hygiene.length + staleBundles.length + unresolvedMerges.length + orphans.length;
|
|
240769
|
-
|
|
241099
|
+
const advisory = reachability.length ? `, ${reachability.length} warning(s) above` : "";
|
|
241100
|
+
if (ok) console.log(`ok - ${loaded.scenes.length} scene(s), no issues${advisory}`);
|
|
240770
241101
|
else console.error(`
|
|
240771
241102
|
${count} issue(s)`);
|
|
240772
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
|
}
|