octwin-cli 0.7.2 → 0.8.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/CHANGELOG.md +91 -0
- package/README.md +13 -1
- package/dist/index.js +1413 -323
- package/dist/lib/render-check.js +64 -0
- package/package.json +1 -1
package/dist/lib/render-check.js
CHANGED
|
@@ -58,6 +58,23 @@ export function loadAllowedRenderKeys(packDir) {
|
|
|
58
58
|
}
|
|
59
59
|
return { lookup, keys: out, nested };
|
|
60
60
|
}
|
|
61
|
+
/** The tap verbs, in the order the platform's compiler picks them. */
|
|
62
|
+
const ON_SELECT_VERBS = ['invoke', 'resume', 'reply', 'agent'];
|
|
63
|
+
/**
|
|
64
|
+
* `{ if, then, else }` — mirrors `isIfFragment` in the platform's
|
|
65
|
+
* `interpreter/render-resolver.ts` and `schemas/render.ts`. Three copies is two too
|
|
66
|
+
* many, but this package is deliberately dependency-free from `src/platform`; the
|
|
67
|
+
* predicate is identical and `render-check.parity.test.ts` is what keeps it so.
|
|
68
|
+
*/
|
|
69
|
+
function isIfFragment(v) {
|
|
70
|
+
return !!v && typeof v === 'object' && !Array.isArray(v) &&
|
|
71
|
+
typeof v.if === 'string' &&
|
|
72
|
+
Object.keys(v).every(k => k === 'if' || k === 'then' || k === 'else');
|
|
73
|
+
}
|
|
74
|
+
/** The `on_select` NODE itself — the one place an if-fragment is not evaluated. */
|
|
75
|
+
function isOnSelectPath(at) {
|
|
76
|
+
return at === 'on_select' || at.endsWith('.on_select');
|
|
77
|
+
}
|
|
61
78
|
/**
|
|
62
79
|
* Walk parsed YAML for objects carrying `render_intent` and report keys outside
|
|
63
80
|
* that intent's allowed set. Walks the whole document rather than just `render:`
|
|
@@ -81,7 +98,50 @@ export function findRenderKeyViolations(doc, file, allowedByIntent, nestedByInte
|
|
|
81
98
|
if (!value || typeof value !== 'object')
|
|
82
99
|
return;
|
|
83
100
|
const obj = value;
|
|
101
|
+
// An if-fragment stands in for the value it selects, so check its branches at the SAME
|
|
102
|
+
// path. Mirrors the platform's `checkNested`: without this the wrapper's own keys were
|
|
103
|
+
// reported against the slot it wraps, which rejected the one form that actually works.
|
|
104
|
+
if (isIfFragment(obj) && !isOnSelectPath(at)) {
|
|
105
|
+
for (const b of ['then', 'else']) {
|
|
106
|
+
if (obj[b] !== undefined)
|
|
107
|
+
walkNested(obj[b], intent, at, sets, [...path, b]);
|
|
108
|
+
}
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
// …and at an `on_select` node it is the reported bug, because runtime does not evaluate
|
|
112
|
+
// it there. One sentence naming the two forms that do work.
|
|
113
|
+
if (isIfFragment(obj) && isOnSelectPath(at)) {
|
|
114
|
+
findings.push({
|
|
115
|
+
file, path: [...path], intent, keys: [], allowed: ON_SELECT_VERBS, nestedAt: at,
|
|
116
|
+
note: `render_intent '${intent}' at '${at}': '{ if, then, else }' is not evaluated inside ` +
|
|
117
|
+
`'on_select' — it fails at render time, not at validate. Wrap the WHOLE entry instead ` +
|
|
118
|
+
`({ if, then: { title, on_select }, else: {…} }), or give each row its own on_select in ` +
|
|
119
|
+
`data and point at it (on_select: '$item.on_select').`,
|
|
120
|
+
});
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
84
123
|
const allowed = sets[at];
|
|
124
|
+
// A missing verb is an absence, not an unknown key, so the key check below is blind to it —
|
|
125
|
+
// and the tap compiler throws on it at render. Only judged where the KB declares the slot,
|
|
126
|
+
// so a KB pulled before on_select was declared simply does not run this.
|
|
127
|
+
if (allowed && isOnSelectPath(at)) {
|
|
128
|
+
const present = ON_SELECT_VERBS.filter(v => obj[v] !== undefined);
|
|
129
|
+
if (present.length === 0) {
|
|
130
|
+
findings.push({
|
|
131
|
+
file, path: [...path], intent, keys: [], allowed: ON_SELECT_VERBS, nestedAt: at,
|
|
132
|
+
note: `render_intent '${intent}' at '${at}': 'on_select' needs exactly one of ` +
|
|
133
|
+
`${ON_SELECT_VERBS.join(' / ')} — it has none, so the tap has no target and ` +
|
|
134
|
+
`fails at render time.`,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
else if (present.length > 1) {
|
|
138
|
+
findings.push({
|
|
139
|
+
file, path: [...path], intent, keys: [], allowed: ON_SELECT_VERBS, nestedAt: at,
|
|
140
|
+
note: `render_intent '${intent}' at '${at}': 'on_select' has more than one verb ` +
|
|
141
|
+
`(${present.join(', ')}); only '${present[0]}' would run. Keep one.`,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
85
145
|
if (allowed) {
|
|
86
146
|
const bad = Object.keys(obj).filter(k => !allowed.includes(k));
|
|
87
147
|
if (bad.length)
|
|
@@ -126,6 +186,10 @@ export function findRenderKeyViolations(doc, file, allowedByIntent, nestedByInte
|
|
|
126
186
|
/** One-line human message per finding. */
|
|
127
187
|
export function describeRenderFinding(f) {
|
|
128
188
|
const at = f.line != null ? `:${f.line}` : (f.path.length ? ` (${f.path.join('.')})` : '');
|
|
189
|
+
// A pre-worded finding (absent verb, if-fragment on a tap payload) carries its own sentence;
|
|
190
|
+
// the key-list template below cannot say either one.
|
|
191
|
+
if (f.note)
|
|
192
|
+
return `${f.file}${at}: ${f.note}`;
|
|
129
193
|
if (f.keys.length === 0) {
|
|
130
194
|
return `${f.file}${at}: unknown render_intent '${f.intent}' — known intents: ${f.allowed.join(', ')}`;
|
|
131
195
|
}
|
package/package.json
CHANGED