octwin-cli 0.8.4 → 0.8.6

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.
@@ -0,0 +1,79 @@
1
+ /**
2
+ * `Outcome` — what one `octwin validate` check ACTUALLY did, as a value.
3
+ *
4
+ * ## Why this exists
5
+ *
6
+ * `cmdValidate` used to hand-write its own `✓` per check, inside the `if` that had the
7
+ * check's data, with a `skipped[]` array and a `skipReasons` Map maintained alongside. It
8
+ * was correct because someone was careful, and the next check added was one `if` away from
9
+ * lying again. It had lied before, twice, in the same shape:
10
+ *
11
+ * - the KB-dependent block printed ONE `✓` above six checks whose own `✓`s lived inside
12
+ * their `if`s, so a run with no pulled KB read as "one check, passed". An entire backlog
13
+ * batch reached production that way;
14
+ * - `yamlDocs()` parsed inside `try { … } catch { return [] }`, so a file that failed to
15
+ * parse was silently DROPPED and every later check skipped it — `validate` printed
16
+ * all-green over a pack the platform's importer then refused with the same message, from
17
+ * the same `yaml` version this CLI already had.
18
+ *
19
+ * **An unparseable file is not "no findings", it is "no idea", and those must never print the
20
+ * same.** So a check no longer prints; it RETURNS one of three things, and a single printer
21
+ * renders all of them. A check that returns nothing cannot reach the `passed` branch, which
22
+ * makes the false-`✓` unrepresentable rather than merely discouraged.
23
+ *
24
+ * ## Why `not-run` carries the lookup rather than a message
25
+ *
26
+ * `describeKbLookup` phrases it at print time, so the wording lives in the module that owns
27
+ * the three KB states and cannot drift into a doubled "SKIPPED — SKIPPED —". It also lets the
28
+ * printer GROUP by reason: when the KB is absent every check skips for the identical reason,
29
+ * and six copies of one sentence is how a reader learns to scroll past the ⚠ block — the same
30
+ * failure as not printing it at all.
31
+ */
32
+ import { describeKbLookup } from './kb-path.js';
33
+ /**
34
+ * Run every check in order and print exactly what each returned.
35
+ *
36
+ * Returns the labels that could not run, so the caller can decide what a partial pass means
37
+ * (`--require-kb` fails on any; an interactive run says which, last, where it is read).
38
+ *
39
+ * The first `findings` outcome terminates via `die`, matching the previous behaviour: an
40
+ * author fixes one class at a time, and printing six screens of unrelated findings buries the
41
+ * first. Order therefore matters and is the caller's to choose.
42
+ */
43
+ export function reportChecks(checks, io) {
44
+ const notRun = [];
45
+ for (const check of checks) {
46
+ const outcome = check.run();
47
+ switch (outcome.kind) {
48
+ case 'passed':
49
+ io.log(`✓ ${outcome.line}`);
50
+ break;
51
+ case 'findings': {
52
+ const n = outcome.lines.length;
53
+ io.err(`✗ ${n} ${outcome.noun}${n === 1 ? '' : 's'}:`);
54
+ for (const line of outcome.lines)
55
+ io.err(` ✗ ${line}`);
56
+ io.die(outcome.hint);
57
+ }
58
+ // eslint-disable-next-line no-fallthrough -- `die` returns never; the case above cannot exit.
59
+ case 'not-run':
60
+ notRun.push({ label: check.label, lookup: outcome.lookup });
61
+ break;
62
+ }
63
+ }
64
+ // One ⚠ per distinct reason, naming every check it cost. Grouped by the lookup's IDENTITY.
65
+ const byReason = new Map();
66
+ for (const { label, lookup } of notRun) {
67
+ const key = lookup.state === 'ok'
68
+ ? 'ok'
69
+ : `${lookup.state}|${'dir' in lookup ? lookup.dir : ''}|${'reason' in lookup ? lookup.reason : ''}`;
70
+ const bucket = byReason.get(key) ?? { lookup, labels: [] };
71
+ bucket.labels.push(label);
72
+ byReason.set(key, bucket);
73
+ }
74
+ for (const { lookup, labels } of byReason.values()) {
75
+ const what = labels.length === 1 ? labels[0] : `${labels.length} checks (${labels.join(', ')})`;
76
+ io.log(`⚠ ${describeKbLookup(lookup, what)}`);
77
+ }
78
+ return notRun.map(n => n.label);
79
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "octwin-cli",
3
- "version": "0.8.4",
3
+ "version": "0.8.6",
4
4
  "description": "Octwin external-pack developer CLI (by CEQUENS) — scaffold, validate, deploy, and check pure-YAML packs on your tenant.",
5
5
  "type": "module",
6
6
  "bin": {