@vosjs/cli 0.41.1 → 0.43.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/README.md +27 -1
- package/dist/{chunk-4QVEV3HX.js → chunk-6PJPGBKH.js} +441 -19
- package/dist/chunk-6PJPGBKH.js.map +1 -0
- package/dist/cli.js +3 -3
- package/dist/index.d.ts +96 -0
- package/dist/index.js +1 -1
- package/dist/{run-ZIOIU3SD.js → run-ZYROWQ3T.js} +2 -2
- package/package.json +5 -5
- package/schema/actions.schema.json +67 -0
- package/schema/digest.schema.json +22 -0
- package/dist/chunk-4QVEV3HX.js.map +0 -1
- /package/dist/{run-ZIOIU3SD.js.map → run-ZYROWQ3T.js.map} +0 -0
|
@@ -101,8 +101,137 @@ function createReporter(json) {
|
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
// src/plugin/setup.ts
|
|
105
|
+
var VERBS = /* @__PURE__ */ new Set(["wait", "click", "type", "press", "goto"]);
|
|
106
|
+
function validateSetup(value) {
|
|
107
|
+
const errors = [];
|
|
108
|
+
if (!Array.isArray(value)) return ["setup must be an array of steps"];
|
|
109
|
+
value.forEach((raw, i) => {
|
|
110
|
+
const at2 = `setup[${i}]`;
|
|
111
|
+
if (typeof raw !== "object" || raw === null) {
|
|
112
|
+
errors.push(`${at2}: must be an object`);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const s = raw;
|
|
116
|
+
if (typeof s.do !== "string" || !VERBS.has(s.do)) {
|
|
117
|
+
errors.push(`${at2}: "do" must be one of ${[...VERBS].join(", ")}`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if ((s.do === "click" || s.do === "type") && typeof s.selector !== "string")
|
|
121
|
+
errors.push(`${at2}: ${s.do} needs a selector`);
|
|
122
|
+
if (s.do === "wait" && typeof s.ms !== "number")
|
|
123
|
+
errors.push(`${at2}: wait needs ms`);
|
|
124
|
+
if (s.do === "press" && typeof s.key !== "string")
|
|
125
|
+
errors.push(`${at2}: press needs a key`);
|
|
126
|
+
if (s.do === "goto" && typeof s.url !== "string")
|
|
127
|
+
errors.push(`${at2}: goto needs a url`);
|
|
128
|
+
if (s.do === "type") {
|
|
129
|
+
const t = s.text;
|
|
130
|
+
const isEnv = typeof t === "object" && t !== null && typeof t.env === "string" && Object.keys(t).length === 1;
|
|
131
|
+
if (typeof t !== "string" && !isEnv)
|
|
132
|
+
errors.push(
|
|
133
|
+
`${at2}: type needs text, a string or { "env": "NAME" } read at run time`
|
|
134
|
+
);
|
|
135
|
+
if (typeof t === "string" && /passw|secret|token/i.test(String(s.selector)) && t.length > 0)
|
|
136
|
+
errors.push(
|
|
137
|
+
`${at2}: a literal value typed into ${String(s.selector)} would live in actions.json, which is committed and pushed. Use { "env": "NAME" }`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
return errors;
|
|
142
|
+
}
|
|
143
|
+
function resolveSetupText(t, env = process.env) {
|
|
144
|
+
if (typeof t === "string") return { value: t, secret: false };
|
|
145
|
+
const v = env[t.env];
|
|
146
|
+
if (v === void 0 || v === "")
|
|
147
|
+
throw new SetupEnvError(
|
|
148
|
+
`setup: the environment variable ${t.env} is not set, and a type step reads it. Export it in the shell that runs vos record; it is never written to a file`
|
|
149
|
+
);
|
|
150
|
+
return { value: v, secret: true };
|
|
151
|
+
}
|
|
152
|
+
async function runSetup(page, steps, opts) {
|
|
153
|
+
const lines = [];
|
|
154
|
+
for (const [i, step] of steps.entries()) {
|
|
155
|
+
const settle = "ms" in step && typeof step.ms === "number" ? step.ms : 150;
|
|
156
|
+
try {
|
|
157
|
+
switch (step.do) {
|
|
158
|
+
case "wait":
|
|
159
|
+
await opts.sleep(step.ms);
|
|
160
|
+
lines.push(`setup #${i} wait ${step.ms}ms`);
|
|
161
|
+
break;
|
|
162
|
+
case "goto":
|
|
163
|
+
await page.goto(step.url, { waitUntil: "networkidle", timeout: 45e3 }).catch(() => {
|
|
164
|
+
});
|
|
165
|
+
lines.push(`setup #${i} goto ${step.url}`);
|
|
166
|
+
break;
|
|
167
|
+
case "press":
|
|
168
|
+
await page.keyboard.press(step.key);
|
|
169
|
+
await opts.sleep(settle);
|
|
170
|
+
lines.push(`setup #${i} press ${step.key}`);
|
|
171
|
+
break;
|
|
172
|
+
case "click": {
|
|
173
|
+
const loc = page.locator(step.selector).first();
|
|
174
|
+
await loc.waitFor({ state: "visible", timeout: 8e3 });
|
|
175
|
+
await loc.click();
|
|
176
|
+
await opts.sleep(settle);
|
|
177
|
+
lines.push(`setup #${i} click ${step.selector}`);
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
case "type": {
|
|
181
|
+
const loc = page.locator(step.selector).first();
|
|
182
|
+
await loc.waitFor({ state: "visible", timeout: 8e3 });
|
|
183
|
+
const { value, secret } = resolveSetupText(step.text, opts.env);
|
|
184
|
+
await loc.fill(value);
|
|
185
|
+
await opts.sleep(settle);
|
|
186
|
+
lines.push(
|
|
187
|
+
`setup #${i} type ${secret ? `\${${step.text.env}}` : `${value.length} chars`} into ${step.selector}`
|
|
188
|
+
);
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
} catch (e) {
|
|
193
|
+
if (e instanceof SetupEnvError) throw e;
|
|
194
|
+
return {
|
|
195
|
+
ran: i,
|
|
196
|
+
lines,
|
|
197
|
+
failed: {
|
|
198
|
+
step: i,
|
|
199
|
+
do: step.do,
|
|
200
|
+
..."selector" in step ? { selector: step.selector } : {}
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return { ran: steps.length, lines };
|
|
206
|
+
}
|
|
207
|
+
var SetupEnvError = class extends Error {
|
|
208
|
+
constructor(message) {
|
|
209
|
+
super(message);
|
|
210
|
+
this.name = "SetupEnvError";
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
var SetupError = class extends Error {
|
|
214
|
+
constructor(failed2) {
|
|
215
|
+
super(
|
|
216
|
+
`setup #${failed2.step} ${failed2.do}${failed2.selector ? ` ${failed2.selector}` : ""}: the selector never appeared, so the take would begin at a half-finished setup. Nothing was recorded. Fix the setup step (rehearse with --dry-run), then record.`
|
|
217
|
+
);
|
|
218
|
+
this.failed = failed2;
|
|
219
|
+
this.name = "SetupError";
|
|
220
|
+
}
|
|
221
|
+
failed;
|
|
222
|
+
};
|
|
223
|
+
function parseHeaders(given) {
|
|
224
|
+
const out = {};
|
|
225
|
+
for (const h of given ?? []) {
|
|
226
|
+
const i = h.indexOf("=");
|
|
227
|
+
if (i <= 0) throw new Error(`--header wants name=value, got "${h}"`);
|
|
228
|
+
out[h.slice(0, i).trim()] = h.slice(i + 1);
|
|
229
|
+
}
|
|
230
|
+
return out;
|
|
231
|
+
}
|
|
232
|
+
|
|
104
233
|
// src/plugin/actions.ts
|
|
105
|
-
var
|
|
234
|
+
var VERBS2 = /* @__PURE__ */ new Set([
|
|
106
235
|
"wait",
|
|
107
236
|
"hover",
|
|
108
237
|
"click",
|
|
@@ -129,6 +258,25 @@ function validateActions(value) {
|
|
|
129
258
|
}
|
|
130
259
|
}
|
|
131
260
|
}
|
|
261
|
+
if (obj.setup !== void 0) errors.push(...validateSetup(obj.setup));
|
|
262
|
+
if (obj.mask !== void 0) {
|
|
263
|
+
if (!Array.isArray(obj.mask)) {
|
|
264
|
+
errors.push("mask must be an array of { selector, as?, text? }");
|
|
265
|
+
} else {
|
|
266
|
+
obj.mask.forEach((raw, i) => {
|
|
267
|
+
const at2 = `mask[${i}]`;
|
|
268
|
+
const m = raw ?? {};
|
|
269
|
+
if (typeof m.selector !== "string" || !m.selector.trim())
|
|
270
|
+
errors.push(`${at2}: needs a selector`);
|
|
271
|
+
if (m.as !== void 0 && m.as !== "blur" && m.as !== "text")
|
|
272
|
+
errors.push(`${at2}: as must be "blur" or "text"`);
|
|
273
|
+
if (m.as === "text" && (typeof m.text !== "string" || !m.text))
|
|
274
|
+
errors.push(`${at2}: as "text" needs the text to show instead`);
|
|
275
|
+
if (m.as !== "text" && m.text !== void 0)
|
|
276
|
+
errors.push(`${at2}: text is only read with as "text"`);
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
132
280
|
if (!Array.isArray(obj.steps) || obj.steps.length === 0) {
|
|
133
281
|
errors.push("steps must be a non-empty array");
|
|
134
282
|
return errors;
|
|
@@ -141,8 +289,8 @@ function validateActions(value) {
|
|
|
141
289
|
return;
|
|
142
290
|
}
|
|
143
291
|
const s = raw;
|
|
144
|
-
if (typeof s.do !== "string" || !
|
|
145
|
-
errors.push(`${at2}: "do" must be one of ${[...
|
|
292
|
+
if (typeof s.do !== "string" || !VERBS2.has(s.do)) {
|
|
293
|
+
errors.push(`${at2}: "do" must be one of ${[...VERBS2].join(", ")}`);
|
|
146
294
|
return;
|
|
147
295
|
}
|
|
148
296
|
if (s.id !== void 0) {
|
|
@@ -6758,6 +6906,199 @@ var WALL_PROBE = `(() => {
|
|
|
6758
6906
|
}
|
|
6759
6907
|
})()`;
|
|
6760
6908
|
|
|
6909
|
+
// src/plugin/exposure.ts
|
|
6910
|
+
var EXPOSURE_MATCHERS_SRC = `(() => {
|
|
6911
|
+
const EMAIL = /[A-Za-z0-9._%+-]+@((?:[A-Za-z0-9-]+\\.)+[A-Za-z]{2,})/g
|
|
6912
|
+
const SAFE_HOST = /(^|\\.)example\\.(com|org|net)$|\\.(test|example|invalid|localhost)$/i
|
|
6913
|
+
const KEY = new RegExp([
|
|
6914
|
+
'\\\\b[srp]k_(?:live|test)_[A-Za-z0-9]{8,}',
|
|
6915
|
+
'\\\\bgh[pousr]_[A-Za-z0-9]{20,}',
|
|
6916
|
+
'\\\\bgithub_pat_[A-Za-z0-9_]{20,}',
|
|
6917
|
+
'\\\\bxox[abprs]-[A-Za-z0-9-]{10,}',
|
|
6918
|
+
'\\\\bAKIA[0-9A-Z]{16}\\\\b',
|
|
6919
|
+
'\\\\bAIza[0-9A-Za-z_-]{30,}',
|
|
6920
|
+
'\\\\bvos_(?:sk|rg|ho)_[A-Za-z0-9]{8,}',
|
|
6921
|
+
'\\\\beyJ[A-Za-z0-9_-]{8,}\\\\.eyJ[A-Za-z0-9_-]{8,}\\\\.[A-Za-z0-9_-]{8,}',
|
|
6922
|
+
].join('|'))
|
|
6923
|
+
const CARD = /(?:^|[^0-9])((?:[0-9][ -]?){12,18}[0-9])(?![0-9])/g
|
|
6924
|
+
const TAIL = /[\\u2022\\u00b7*xX]{2,}[ -]?[0-9]{4}(?![0-9])/
|
|
6925
|
+
const luhn = (digits) => {
|
|
6926
|
+
let sum = 0, alt = false
|
|
6927
|
+
for (let i = digits.length - 1; i >= 0; i--) {
|
|
6928
|
+
let d = digits.charCodeAt(i) - 48
|
|
6929
|
+
if (alt) { d *= 2; if (d > 9) d -= 9 }
|
|
6930
|
+
sum += d; alt = !alt
|
|
6931
|
+
}
|
|
6932
|
+
return sum % 10 === 0
|
|
6933
|
+
}
|
|
6934
|
+
const kindsIn = (text) => {
|
|
6935
|
+
const kinds = []
|
|
6936
|
+
if (!text || text.length < 6) return kinds
|
|
6937
|
+
EMAIL.lastIndex = 0
|
|
6938
|
+
for (let m; (m = EMAIL.exec(text)); ) {
|
|
6939
|
+
if (!SAFE_HOST.test(m[1])) { kinds.push('email'); break }
|
|
6940
|
+
}
|
|
6941
|
+
if (KEY.test(text)) kinds.push('key')
|
|
6942
|
+
CARD.lastIndex = 0
|
|
6943
|
+
for (let m; (m = CARD.exec(text)); ) {
|
|
6944
|
+
const digits = m[1].replace(/[ -]/g, '')
|
|
6945
|
+
if (digits.length >= 13 && digits.length <= 19 && !/^0+$/.test(digits) && luhn(digits)) { kinds.push('card'); break }
|
|
6946
|
+
}
|
|
6947
|
+
if (TAIL.test(text)) kinds.push('card-tail')
|
|
6948
|
+
return kinds
|
|
6949
|
+
}
|
|
6950
|
+
return { kindsIn }
|
|
6951
|
+
})()`;
|
|
6952
|
+
var EXPOSURE_PROBE = `(() => {
|
|
6953
|
+
const { kindsIn } = ${EXPOSURE_MATCHERS_SRC}
|
|
6954
|
+
const vw = innerWidth, vh = innerHeight
|
|
6955
|
+
// A selector that reaches THIS element and no other, so it can be pasted
|
|
6956
|
+
// into "mask" as it is: climb until the path is unique, and say which
|
|
6957
|
+
// sibling wherever a tag-and-class alone would match several.
|
|
6958
|
+
const segment = (el) => {
|
|
6959
|
+
const tid = el.getAttribute('data-testid')
|
|
6960
|
+
if (tid) return el.tagName.toLowerCase() + '[data-testid="' + tid + '"]'
|
|
6961
|
+
if (el.id && /^[A-Za-z_][\\w-]*$/.test(el.id)) return el.tagName.toLowerCase() + '#' + el.id
|
|
6962
|
+
const cls = [...el.classList].filter((c) => /^[A-Za-z_-][\\w-]*$/.test(c)).slice(0, 2)
|
|
6963
|
+
let seg = el.tagName.toLowerCase() + (cls.length ? '.' + cls.join('.') : '')
|
|
6964
|
+
const p = el.parentElement
|
|
6965
|
+
if (p && [...p.children].filter((c) => c.matches(seg)).length > 1)
|
|
6966
|
+
seg += ':nth-child(' + ([...p.children].indexOf(el) + 1) + ')'
|
|
6967
|
+
return seg
|
|
6968
|
+
}
|
|
6969
|
+
const name = (el) => {
|
|
6970
|
+
const parts = []
|
|
6971
|
+
for (let cur = el; cur && cur !== document.body && cur !== document.documentElement; cur = cur.parentElement) {
|
|
6972
|
+
parts.unshift(segment(cur))
|
|
6973
|
+
let n = 2
|
|
6974
|
+
try { n = document.querySelectorAll(parts.join(' > ')).length } catch {}
|
|
6975
|
+
// a bare tag is unique HERE and nowhere else: give it its parent too
|
|
6976
|
+
const bare = parts.length === 1 && !/[#.\\[]/.test(parts[0])
|
|
6977
|
+
if ((n === 1 && !bare) || parts.length >= 5) break
|
|
6978
|
+
}
|
|
6979
|
+
return parts.join(' > ')
|
|
6980
|
+
}
|
|
6981
|
+
const seen = (el) => {
|
|
6982
|
+
if (el.closest('[data-vos-masked]')) return null
|
|
6983
|
+
const r = el.getBoundingClientRect()
|
|
6984
|
+
if (r.width < 2 || r.height < 2) return null
|
|
6985
|
+
if (r.bottom <= 0 || r.right <= 0 || r.top >= vh || r.left >= vw) return null
|
|
6986
|
+
const s = getComputedStyle(el)
|
|
6987
|
+
if (s.visibility === 'hidden' || s.display === 'none' || +s.opacity === 0) return null
|
|
6988
|
+
return r
|
|
6989
|
+
}
|
|
6990
|
+
const out = [], keys = new Set()
|
|
6991
|
+
const add = (el, text) => {
|
|
6992
|
+
const kinds = kindsIn(text)
|
|
6993
|
+
if (!kinds.length) return
|
|
6994
|
+
const r = seen(el)
|
|
6995
|
+
if (!r) return
|
|
6996
|
+
const selector = name(el)
|
|
6997
|
+
for (const kind of kinds) {
|
|
6998
|
+
const k = kind + '|' + selector
|
|
6999
|
+
if (keys.has(k) || out.length >= 40) continue
|
|
7000
|
+
keys.add(k)
|
|
7001
|
+
out.push({ kind, selector, rect: { x: Math.round(r.left), y: Math.round(r.top), w: Math.round(r.width), h: Math.round(r.height) } })
|
|
7002
|
+
}
|
|
7003
|
+
}
|
|
7004
|
+
const walker = document.createTreeWalker(document.body || document.documentElement, NodeFilter.SHOW_TEXT)
|
|
7005
|
+
for (let n; (n = walker.nextNode()); ) {
|
|
7006
|
+
const el = n.parentElement
|
|
7007
|
+
if (!el || /^(SCRIPT|STYLE|NOSCRIPT|TEMPLATE)$/.test(el.tagName)) continue
|
|
7008
|
+
add(el, n.nodeValue)
|
|
7009
|
+
}
|
|
7010
|
+
for (const el of document.querySelectorAll('input, textarea')) {
|
|
7011
|
+
if (/^(password|hidden|file|checkbox|radio)$/.test(el.type)) continue
|
|
7012
|
+
add(el, el.value)
|
|
7013
|
+
}
|
|
7014
|
+
const hits = {}
|
|
7015
|
+
for (const [sel, n] of Object.entries(window.__vosMaskHits || {})) hits[sel] = n
|
|
7016
|
+
return { exposures: out, maskHits: hits }
|
|
7017
|
+
})()`;
|
|
7018
|
+
function maskInitScript(masks) {
|
|
7019
|
+
const rules = masks.map((m) => ({
|
|
7020
|
+
selector: m.selector,
|
|
7021
|
+
as: m.as === "text" ? "text" : "blur",
|
|
7022
|
+
text: m.text ?? ""
|
|
7023
|
+
}));
|
|
7024
|
+
return `(() => {
|
|
7025
|
+
const RULES = ${JSON.stringify(rules)}
|
|
7026
|
+
const hits = (window.__vosMaskHits = window.__vosMaskHits || {})
|
|
7027
|
+
for (const r of RULES) hits[r.selector] = hits[r.selector] || 0
|
|
7028
|
+
const css = RULES.map((r) => r.selector + '[data-vos-masked="blur"]{filter:blur(7px) !important;user-select:none !important}').join('\\n')
|
|
7029
|
+
const paint = () => {
|
|
7030
|
+
const root = document.head || document.documentElement
|
|
7031
|
+
if (!root || document.getElementById('__vos-mask-style')) return
|
|
7032
|
+
const style = document.createElement('style')
|
|
7033
|
+
style.id = '__vos-mask-style'
|
|
7034
|
+
style.textContent = css
|
|
7035
|
+
root.appendChild(style)
|
|
7036
|
+
}
|
|
7037
|
+
const apply = () => {
|
|
7038
|
+
paint()
|
|
7039
|
+
for (const r of RULES) {
|
|
7040
|
+
let found
|
|
7041
|
+
try { found = document.querySelectorAll(r.selector) } catch { continue }
|
|
7042
|
+
if (found.length > hits[r.selector]) hits[r.selector] = found.length
|
|
7043
|
+
for (const el of found) {
|
|
7044
|
+
const form = /^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName)
|
|
7045
|
+
const mode = r.as === 'text' && !form ? 'text' : 'blur'
|
|
7046
|
+
if (el.getAttribute('data-vos-masked') !== mode) el.setAttribute('data-vos-masked', mode)
|
|
7047
|
+
if (mode !== 'text') continue
|
|
7048
|
+
const texts = []
|
|
7049
|
+
const w = document.createTreeWalker(el, NodeFilter.SHOW_TEXT)
|
|
7050
|
+
for (let n; (n = w.nextNode()); ) texts.push(n)
|
|
7051
|
+
if (!texts.length) { if (el.textContent !== r.text) el.textContent = r.text; continue }
|
|
7052
|
+
texts.forEach((n, i) => { const want = i === 0 ? r.text : ''; if (n.nodeValue !== want) n.nodeValue = want })
|
|
7053
|
+
}
|
|
7054
|
+
}
|
|
7055
|
+
}
|
|
7056
|
+
new MutationObserver(apply).observe(document, { childList: true, subtree: true, characterData: true })
|
|
7057
|
+
document.addEventListener('DOMContentLoaded', apply)
|
|
7058
|
+
apply()
|
|
7059
|
+
})()`;
|
|
7060
|
+
}
|
|
7061
|
+
var ExposureLog = class {
|
|
7062
|
+
byKey = /* @__PURE__ */ new Map();
|
|
7063
|
+
maskHits = /* @__PURE__ */ new Map();
|
|
7064
|
+
constructor(masks = []) {
|
|
7065
|
+
for (const m of masks) this.maskHits.set(m.selector, 0);
|
|
7066
|
+
}
|
|
7067
|
+
add(step, scan) {
|
|
7068
|
+
if (!scan) return;
|
|
7069
|
+
for (const e of scan.exposures ?? []) {
|
|
7070
|
+
const key = `${e.kind}|${e.selector}`;
|
|
7071
|
+
const had = this.byKey.get(key);
|
|
7072
|
+
if (had) had.seen += 1;
|
|
7073
|
+
else this.byKey.set(key, { step, ...e, seen: 1 });
|
|
7074
|
+
}
|
|
7075
|
+
for (const [sel, n] of Object.entries(scan.maskHits ?? {})) {
|
|
7076
|
+
if (n > (this.maskHits.get(sel) ?? 0)) this.maskHits.set(sel, n);
|
|
7077
|
+
}
|
|
7078
|
+
}
|
|
7079
|
+
exposures() {
|
|
7080
|
+
return [...this.byKey.values()];
|
|
7081
|
+
}
|
|
7082
|
+
masks(rules) {
|
|
7083
|
+
return rules.map((m) => ({
|
|
7084
|
+
selector: m.selector,
|
|
7085
|
+
as: m.as === "text" ? "text" : "blur",
|
|
7086
|
+
hits: this.maskHits.get(m.selector) ?? 0
|
|
7087
|
+
}));
|
|
7088
|
+
}
|
|
7089
|
+
};
|
|
7090
|
+
var KIND_WORDS = {
|
|
7091
|
+
email: "an email address",
|
|
7092
|
+
key: "something shaped like an API key or token",
|
|
7093
|
+
card: "a card number",
|
|
7094
|
+
"card-tail": "a card's last digits"
|
|
7095
|
+
};
|
|
7096
|
+
function exposureLine(e) {
|
|
7097
|
+
const when = e.step < 0 ? "as the page opened" : `at step #${e.step}`;
|
|
7098
|
+
return `${KIND_WORDS[e.kind]} in ${e.selector} (${when}, ${e.rect.x},${e.rect.y} ${e.rect.w}\xD7${e.rect.h})`;
|
|
7099
|
+
}
|
|
7100
|
+
var EXPOSURE_ADVICE = 'The recording shows these. Record from a demo account, or hide them before the camera rolls with "mask" in actions.json: { "selector": "\u2026", "as": "text", "text": "demo@acme.test" } swaps the words, "as": "blur" blurs. Then re-record.';
|
|
7101
|
+
|
|
6761
7102
|
// src/plugin/recorder.ts
|
|
6762
7103
|
var realSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
6763
7104
|
var DRY_PAUSE_MS = 120;
|
|
@@ -6783,20 +7124,42 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
6783
7124
|
const context = await browser.newContext({
|
|
6784
7125
|
viewport: { width: vw, height: vh },
|
|
6785
7126
|
deviceScaleFactor: 1,
|
|
6786
|
-
...opts.storageState ? { storageState: opts.storageState } : {}
|
|
7127
|
+
...opts.storageState ? { storageState: opts.storageState } : {},
|
|
7128
|
+
...opts.headers && Object.keys(opts.headers).length ? { extraHTTPHeaders: opts.headers } : {}
|
|
6787
7129
|
});
|
|
7130
|
+
const masks = actions.mask ?? [];
|
|
7131
|
+
if (masks.length) await context.addInitScript(maskInitScript(masks));
|
|
7132
|
+
const exposureLog = new ExposureLog(masks);
|
|
6788
7133
|
const page = await context.newPage();
|
|
7134
|
+
const scanExposures = async (step) => {
|
|
7135
|
+
const scan = await page.evaluate(EXPOSURE_PROBE).catch(() => null);
|
|
7136
|
+
exposureLog.add(step, scan);
|
|
7137
|
+
};
|
|
6789
7138
|
page.on("console", (m) => {
|
|
6790
7139
|
if (m.type() === "error") log(` [page error] ${m.text()}`);
|
|
6791
7140
|
});
|
|
6792
7141
|
log(`goto ${url}`);
|
|
6793
7142
|
let navTimeout = false;
|
|
6794
|
-
|
|
7143
|
+
let response = await page.goto(url, { waitUntil: "networkidle", timeout: 45e3 }).catch(() => {
|
|
6795
7144
|
navTimeout = true;
|
|
6796
7145
|
log(" (networkidle timeout \u2014 continuing)");
|
|
6797
7146
|
return null;
|
|
6798
7147
|
});
|
|
6799
7148
|
await sleep(800);
|
|
7149
|
+
const setup = actions.setup ?? [];
|
|
7150
|
+
if (setup.length) {
|
|
7151
|
+
log(`setup: ${setup.length} step(s), off camera`);
|
|
7152
|
+
const result = await runSetup(page, setup, { sleep: realSleep });
|
|
7153
|
+
for (const line of result.lines) log(` ${line}`);
|
|
7154
|
+
if (result.failed) {
|
|
7155
|
+
await context.close().catch(() => {
|
|
7156
|
+
});
|
|
7157
|
+
throw new SetupError(result.failed);
|
|
7158
|
+
}
|
|
7159
|
+
log(`goto ${url} (after setup)`);
|
|
7160
|
+
response = await page.goto(url, { waitUntil: "networkidle", timeout: 45e3 }).catch(() => null);
|
|
7161
|
+
await sleep(800);
|
|
7162
|
+
}
|
|
6800
7163
|
let wall = null;
|
|
6801
7164
|
if (opts.onArrival) {
|
|
6802
7165
|
const seen = await page.evaluate(WALL_PROBE).catch(() => null);
|
|
@@ -6815,6 +7178,7 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
6815
7178
|
throw e;
|
|
6816
7179
|
}
|
|
6817
7180
|
}
|
|
7181
|
+
await scanExposures(-1);
|
|
6818
7182
|
const cdp = await context.newCDPSession(page);
|
|
6819
7183
|
const frames = [];
|
|
6820
7184
|
let frameIdx = 0;
|
|
@@ -7074,6 +7438,7 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
7074
7438
|
gestureMs,
|
|
7075
7439
|
wallMs: now() - stepStart
|
|
7076
7440
|
});
|
|
7441
|
+
await scanExposures(stepIdx);
|
|
7077
7442
|
}
|
|
7078
7443
|
if (!capped) await sleep(TRAILING_HOLD_MS);
|
|
7079
7444
|
if (capped || capReached(now(), maxSeconds)) {
|
|
@@ -7104,7 +7469,9 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
7104
7469
|
platform: process.platform === "darwin" ? "mac" : process.platform === "win32" ? "windows" : "linux",
|
|
7105
7470
|
producer: "cli",
|
|
7106
7471
|
steps: stepSpans,
|
|
7107
|
-
...wall ? { wall: { kind: wall.kind, asked: wall.asked, landed: wall.landed } } : {}
|
|
7472
|
+
...wall ? { wall: { kind: wall.kind, asked: wall.asked, landed: wall.landed } } : {},
|
|
7473
|
+
...exposureLog.exposures().length ? { exposures: exposureLog.exposures() } : {},
|
|
7474
|
+
...masks.length ? { masks: exposureLog.masks(masks) } : {}
|
|
7108
7475
|
};
|
|
7109
7476
|
const freezes = [];
|
|
7110
7477
|
for (let i = 1; i < frames.length; i++) {
|
|
@@ -7140,6 +7507,8 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
7140
7507
|
skipped,
|
|
7141
7508
|
navTimeout,
|
|
7142
7509
|
wall,
|
|
7510
|
+
exposures: exposureLog.exposures(),
|
|
7511
|
+
masks: exposureLog.masks(masks),
|
|
7143
7512
|
freezes,
|
|
7144
7513
|
freezePct,
|
|
7145
7514
|
capped
|
|
@@ -10294,12 +10663,12 @@ var BOOLEAN_FLAGS5 = /* @__PURE__ */ new Set([
|
|
|
10294
10663
|
"dry-run",
|
|
10295
10664
|
"allow-wall"
|
|
10296
10665
|
]);
|
|
10297
|
-
var MULTI_FLAGS2 = /* @__PURE__ */ new Set(["set", "override", "browser-arg"]);
|
|
10666
|
+
var MULTI_FLAGS2 = /* @__PURE__ */ new Set(["set", "override", "browser-arg", "header"]);
|
|
10298
10667
|
var HELP = `vos \u2014 record a browser flow, plan effects, render a product video; sync with vos.so
|
|
10299
10668
|
|
|
10300
10669
|
Take pipeline
|
|
10301
|
-
vos create --actions actions.json [--url <url>] [--out take] [out.webm] [--strict] [--allow-wall] [--keep-frames] [--max-duration <s>] [--storage-state <file>] [--browser-arg=<switch>]... [--background <slug|url|none>] [render flags] [--json]
|
|
10302
|
-
vos record --actions actions.json [--url <url>] [--out take] [--strict] [--dry-run] [--allow-wall] [--keep-frames] [--max-duration <s>] [--storage-state <file>] [--browser-arg=<switch>]... [--background <slug|url|none>] [--json]
|
|
10670
|
+
vos create --actions actions.json [--url <url>] [--out take] [out.webm] [--strict] [--allow-wall] [--keep-frames] [--max-duration <s>] [--storage-state <file>] [--header name=value]... [--browser-arg=<switch>]... [--background <slug|url|none>] [render flags] [--json]
|
|
10671
|
+
vos record --actions actions.json [--url <url>] [--out take] [--strict] [--dry-run] [--allow-wall] [--keep-frames] [--max-duration <s>] [--storage-state <file>] [--header name=value]... [--browser-arg=<switch>]... [--background <slug|url|none>] [--json]
|
|
10303
10672
|
vos plan <take> [--fresh] [--reuse [--from <doc.json>]] [--style <doc.json|vosId>] [--with <doc.json|vosId>[@end|@start|@step:<id>|@<s>]]... [--background <slug|url|none>] [--motion] [--headline "\u2026"] [--kicker "\u2026"] [--launch LAUNCH.md] [--brand BRAND.md] [--music <slug|mood|none>] [--entrance tilt-in|pull-out|rise|fade|slide|none] [--transitions slide|fade|scale|none] [--end-card on|none|<doc.json|vosId>] [--captions none] [--clicks none] [--still <t>] [--release v2.1] [--json]
|
|
10304
10673
|
vos render <take> [out.webm] [--width] [--height] [--fps] [--format webm|mp4] [--parallel N] [--range a..b] [--draft] [--frame <kind>] [--background <url|slug>] [--set <path=value>]... [--json]
|
|
10305
10674
|
vos frames <take> [--times 0,25%,50%,75%,100%] [--frame <t>] [--at-zooms] [--at-moments] [--at-still] [--size WxH] [--out dir] [--background <url|slug>] [--set <path=value>]... [--json]
|
|
@@ -10575,8 +10944,30 @@ function strictReason(rec) {
|
|
|
10575
10944
|
const parts = [`${rec.skipped.length} skipped step(s)`];
|
|
10576
10945
|
if (rec.navTimeout) parts.push("networkidle timeout");
|
|
10577
10946
|
if (rec.capped) parts.push("stopped at --max-duration");
|
|
10947
|
+
const missed = maskMisses(rec);
|
|
10948
|
+
if (missed.length)
|
|
10949
|
+
parts.push(
|
|
10950
|
+
`${missed.length} mask(s) that hid nothing (${missed.join(", ")}): whatever they were for may be showing`
|
|
10951
|
+
);
|
|
10578
10952
|
return parts.join(" + ");
|
|
10579
10953
|
}
|
|
10954
|
+
function maskMisses(rec) {
|
|
10955
|
+
return (rec.masks ?? []).filter((m) => m.hits === 0).map((m) => m.selector);
|
|
10956
|
+
}
|
|
10957
|
+
function exposureNote(rec) {
|
|
10958
|
+
if (!rec.exposures.length) return "";
|
|
10959
|
+
return `
|
|
10960
|
+
EXPOSED in the frame (${rec.exposures.length}):
|
|
10961
|
+
${rec.exposures.map((e) => ` - ${exposureLine(e)}`).join("\n")}
|
|
10962
|
+
${EXPOSURE_ADVICE}`;
|
|
10963
|
+
}
|
|
10964
|
+
function takeHeaders(multi) {
|
|
10965
|
+
try {
|
|
10966
|
+
return parseHeaders(multi.header);
|
|
10967
|
+
} catch (e) {
|
|
10968
|
+
throw new UsageError(e instanceof Error ? e.message : String(e));
|
|
10969
|
+
}
|
|
10970
|
+
}
|
|
10580
10971
|
function takeBrowserArgs(multi) {
|
|
10581
10972
|
const given = multi["browser-arg"];
|
|
10582
10973
|
return (given ?? []).filter(Boolean);
|
|
@@ -10647,6 +11038,7 @@ async function cmdRecord(argv) {
|
|
|
10647
11038
|
const maxDurationSeconds = await maxDuration(flags, r);
|
|
10648
11039
|
const storageState = takeStorageState(flags);
|
|
10649
11040
|
const browserArgs = takeBrowserArgs(multi);
|
|
11041
|
+
const headers = takeHeaders(multi);
|
|
10650
11042
|
if (flags["dry-run"] === true) {
|
|
10651
11043
|
void flags.strict;
|
|
10652
11044
|
void flags["keep-frames"];
|
|
@@ -10663,6 +11055,7 @@ async function cmdRecord(argv) {
|
|
|
10663
11055
|
r.log,
|
|
10664
11056
|
{
|
|
10665
11057
|
storageState,
|
|
11058
|
+
headers,
|
|
10666
11059
|
dryRun: true,
|
|
10667
11060
|
// A rehearsal is always strict, and it touches no directory.
|
|
10668
11061
|
onArrival: wallGate(flags, r, { strict: true })
|
|
@@ -10674,10 +11067,11 @@ async function cmdRecord(argv) {
|
|
|
10674
11067
|
const what = s.selector ? ` ${s.selector}` : "";
|
|
10675
11068
|
return ` ${s.skipped ? "\u2717" : "\u2713"} #${s.step}${s.id ? ` (${s.id})` : ""} ${s.do}${what}${s.skipped ? " NOT FOUND" : rect}${s.navigated ? " \u2192 navigated" : ""}`;
|
|
10676
11069
|
});
|
|
10677
|
-
const failed2 = rec.skipped.length > 0 || rec.navTimeout;
|
|
11070
|
+
const failed2 = rec.skipped.length > 0 || rec.navTimeout || maskMisses(rec).length > 0;
|
|
10678
11071
|
const carried = [
|
|
10679
11072
|
...strFlag(flags, "storage-state") ? [`--storage-state ${strFlag(flags, "storage-state")}`] : [],
|
|
10680
11073
|
...browserArgs.map((a) => `--browser-arg=${a}`),
|
|
11074
|
+
...Object.entries(headers).map(([k, v]) => `--header ${k}=${v}`),
|
|
10681
11075
|
...flags["allow-wall"] === true ? ["--allow-wall"] : []
|
|
10682
11076
|
].join(" ");
|
|
10683
11077
|
const nextOut = strFlag(flags, "out") ?? "take";
|
|
@@ -10688,11 +11082,13 @@ async function cmdRecord(argv) {
|
|
|
10688
11082
|
seconds: +((Date.now() - started) / 1e3).toFixed(1),
|
|
10689
11083
|
steps,
|
|
10690
11084
|
skipped: rec.skipped,
|
|
10691
|
-
navTimeout: rec.navTimeout
|
|
11085
|
+
navTimeout: rec.navTimeout,
|
|
11086
|
+
exposures: rec.exposures,
|
|
11087
|
+
masks: rec.masks
|
|
10692
11088
|
},
|
|
10693
11089
|
`${failed2 ? "REHEARSAL FAILED" : "Rehearsal passed"}: ${steps.length - rec.skipped.length}/${steps.length} steps resolved in ${((Date.now() - started) / 1e3).toFixed(1)}s${rec.navTimeout ? " (the first load never reached networkidle)" : ""}
|
|
10694
11090
|
${lines.join("\n")}
|
|
10695
|
-
${failed2 ? "Fix the script, rehearse again, then record." : `Rects are capture px (the step rects a pin or a callout reads). Next: vos record --actions ${actionsPath} --out ${nextOut}${carried ? ` ${carried}` : ""} --strict`}`
|
|
11091
|
+
${maskMisses(rec).length ? `MASK hid nothing: ${maskMisses(rec).join(", ")} (the selector reached no element). ` : ""}${failed2 ? "Fix the script, rehearse again, then record." : `Rects are capture px (the step rects a pin or a callout reads). Next: vos record --actions ${actionsPath} --out ${nextOut}${carried ? ` ${carried}` : ""} --strict`}${exposureNote(rec)}`
|
|
10696
11092
|
);
|
|
10697
11093
|
return failed2 ? EXIT_USAGE : EXIT_OK;
|
|
10698
11094
|
} finally {
|
|
@@ -10717,6 +11113,7 @@ ${lines.join("\n")}
|
|
|
10717
11113
|
const rec = await recordTake(browser, url, actions, paths, r.log, {
|
|
10718
11114
|
maxDurationSeconds,
|
|
10719
11115
|
storageState,
|
|
11116
|
+
headers,
|
|
10720
11117
|
onArrival: wallGate(flags, r, {
|
|
10721
11118
|
strict: flags.strict === true,
|
|
10722
11119
|
prepare
|
|
@@ -10734,7 +11131,7 @@ ${lines.join("\n")}
|
|
|
10734
11131
|
const plan = await planTake(outDir, { backdrop });
|
|
10735
11132
|
const clicks = rec.events.filter((e) => e.type === "down").length;
|
|
10736
11133
|
const strict = flags.strict === true;
|
|
10737
|
-
const strictFail = strict && (rec.skipped.length > 0 || rec.navTimeout || rec.capped);
|
|
11134
|
+
const strictFail = strict && (rec.skipped.length > 0 || rec.navTimeout || rec.capped || maskMisses(rec).length > 0);
|
|
10738
11135
|
const skippedNote = rec.skipped.length ? `
|
|
10739
11136
|
SKIPPED ${rec.skipped.length} step(s): ${rec.skipped.map((s) => `#${s.step} ${s.do} ${s.selector}`).join(", ")}` : "";
|
|
10740
11137
|
r.done(
|
|
@@ -10753,11 +11150,13 @@ ${lines.join("\n")}
|
|
|
10753
11150
|
capped: rec.capped,
|
|
10754
11151
|
pace: rec.pace,
|
|
10755
11152
|
...rec.wall ? { wall: rec.wall } : {},
|
|
11153
|
+
exposures: rec.exposures,
|
|
11154
|
+
...rec.masks.length ? { masks: rec.masks } : {},
|
|
10756
11155
|
...strictFail ? { strictFailed: true } : {}
|
|
10757
11156
|
},
|
|
10758
|
-
strictFail ? `STRICT: take recorded but incomplete \u2014 ${strictReason(rec)}; fix the flow and re-record.${skippedNote}` : `Take ready: ${outDir}
|
|
11157
|
+
strictFail ? `STRICT: take recorded but incomplete \u2014 ${strictReason(rec)}; fix the flow and re-record.${skippedNote}${exposureNote(rec)}` : `Take ready: ${outDir}
|
|
10759
11158
|
${(rec.meta.durationMs / 1e3).toFixed(1)}s \xB7 ${rec.frames.length} frames \xB7 ${rec.events.length} cursor events \xB7 ${clicks} clicks \xB7 ${plan.doc.zoom.length} zoom spans planned \xB7 ${rec.freezePct}% frozen${rec.freezePct >= 25 ? " \u26A0 keep motion in frame or trim" : ""}${skippedNote}
|
|
10760
|
-
Next: edit ${join18(outDir, "doc.json")} (optional), then: vos render ${outDir}`
|
|
11159
|
+
Next: edit ${join18(outDir, "doc.json")} (optional), then: vos render ${outDir}${exposureNote(rec)}`
|
|
10761
11160
|
);
|
|
10762
11161
|
return strictFail ? EXIT_USAGE : EXIT_OK;
|
|
10763
11162
|
} finally {
|
|
@@ -10795,6 +11194,7 @@ async function cmdCreate2(argv) {
|
|
|
10795
11194
|
const maxDurationSeconds = await maxDuration(flags, r);
|
|
10796
11195
|
const storageState = takeStorageState(flags);
|
|
10797
11196
|
const browserArgs = takeBrowserArgs(multi);
|
|
11197
|
+
const headers = takeHeaders(multi);
|
|
10798
11198
|
const paths = takePaths(outDir);
|
|
10799
11199
|
const prepare = async () => {
|
|
10800
11200
|
if (existsSync15(join18(outDir, "meta.json"))) {
|
|
@@ -10813,6 +11213,7 @@ async function cmdCreate2(argv) {
|
|
|
10813
11213
|
const rec = await recordTake(browser, url, actions, paths, r.log, {
|
|
10814
11214
|
maxDurationSeconds,
|
|
10815
11215
|
storageState,
|
|
11216
|
+
headers,
|
|
10816
11217
|
onArrival: wallGate(flags, r, {
|
|
10817
11218
|
strict: flags.strict === true,
|
|
10818
11219
|
prepare
|
|
@@ -10828,7 +11229,7 @@ async function cmdCreate2(argv) {
|
|
|
10828
11229
|
await releaseFrames(paths, flags, r.log);
|
|
10829
11230
|
r.event({ event: "phase", phase: "plan" });
|
|
10830
11231
|
await planTake(outDir, { backdrop });
|
|
10831
|
-
if (flags.strict === true && (rec.skipped.length > 0 || rec.navTimeout || rec.capped)) {
|
|
11232
|
+
if (flags.strict === true && (rec.skipped.length > 0 || rec.navTimeout || rec.capped || maskMisses(rec).length > 0)) {
|
|
10832
11233
|
r.done(
|
|
10833
11234
|
{
|
|
10834
11235
|
take: outDir,
|
|
@@ -10872,10 +11273,12 @@ async function cmdCreate2(argv) {
|
|
|
10872
11273
|
navTimeout: rec.navTimeout,
|
|
10873
11274
|
freezes: rec.freezes,
|
|
10874
11275
|
freezePct: rec.freezePct,
|
|
11276
|
+
exposures: rec.exposures,
|
|
11277
|
+
...rec.masks.length ? { masks: rec.masks } : {},
|
|
10875
11278
|
...flags.draft === true ? { draft: true } : {}
|
|
10876
11279
|
},
|
|
10877
11280
|
`Created ${result.out} (${(result.bytes / 1024).toFixed(0)} KB, ${result.width}x${result.height}@${result.fps}, ${result.duration.toFixed(1)}s, ${result.zoomSpans} zoom spans, ${result.clicks} click effects${result.audio ? ", audio" : ""}${flags.draft === true ? ", DRAFT" : ""})
|
|
10878
|
-
take: ${outDir} \u2014 inspect with: vos frames ${outDir} --at-zooms; edit doc.json and re-render with: vos render ${outDir}`
|
|
11281
|
+
take: ${outDir} \u2014 inspect with: vos frames ${outDir} --at-zooms; edit doc.json and re-render with: vos render ${outDir}${exposureNote(rec)}`
|
|
10879
11282
|
);
|
|
10880
11283
|
return EXIT_OK;
|
|
10881
11284
|
} finally {
|
|
@@ -11440,12 +11843,14 @@ async function cmdDigest(argv) {
|
|
|
11440
11843
|
outputDuration: d.take.outputDuration,
|
|
11441
11844
|
hasCursor: d.take.hasCursor,
|
|
11442
11845
|
...d.take.wall ? { wall: d.take.wall } : {},
|
|
11846
|
+
...d.take.exposures.length ? { exposures: d.take.exposures } : {},
|
|
11443
11847
|
tokensEstimate: d.images.tokensEstimate,
|
|
11444
11848
|
bytes: result.bytes
|
|
11445
11849
|
},
|
|
11446
11850
|
`Wrote ${result.file} \u2014 ${d.moments.length} moments (${Object.entries(
|
|
11447
11851
|
kinds
|
|
11448
|
-
).map(([k, n]) => `${n} ${k}`).join(", ")}), ${d.images.full} frames + ${d.images.crop} crops` + (d.images.sheet ? `, sheet.png` : "") + ` \u2014 ~${d.images.tokensEstimate} image tokens to read them all` + (d.take.hasCursor ? "" : "\n no cursor track: moments are head/tail/scenes only \u2014 pace by activity, zoom only where the ask names a place") + (d.take.
|
|
11852
|
+
).map(([k, n]) => `${n} ${k}`).join(", ")}), ${d.images.full} frames + ${d.images.crop} crops` + (d.images.sheet ? `, sheet.png` : "") + ` \u2014 ~${d.images.tokensEstimate} image tokens to read them all` + (d.take.hasCursor ? "" : "\n no cursor track: moments are head/tail/scenes only \u2014 pace by activity, zoom only where the ask names a place") + (d.take.exposures.length ? `
|
|
11853
|
+
EXPOSED in the footage: ${d.take.exposures.map((e) => `${e.kind} in ${e.selector}`).join("; ")} \u2014 look at those frames before this take goes anywhere` : "") + (d.take.wall ? `
|
|
11449
11854
|
WALL: asked for ${d.take.wall.asked}, recorded ${d.take.wall.landed} \u2014 this footage may be the wrong page; re-record with a session before cutting it` : "") + `
|
|
11450
11855
|
Read digest.json, then the sheet, then a crop only where you must decide.`
|
|
11451
11856
|
);
|
|
@@ -11709,6 +12114,18 @@ async function cmdValidate(argv) {
|
|
|
11709
12114
|
);
|
|
11710
12115
|
if (mismatch) warnings.push(mismatch);
|
|
11711
12116
|
}
|
|
12117
|
+
for (const e of take.meta.exposures ?? [])
|
|
12118
|
+
warnings.push(`the recording shows ${exposureLine(e)}`);
|
|
12119
|
+
if (take.meta.exposures?.length) warnings.push(EXPOSURE_ADVICE);
|
|
12120
|
+
for (const m of take.meta.masks ?? [])
|
|
12121
|
+
if (m.hits === 0)
|
|
12122
|
+
warnings.push(
|
|
12123
|
+
`mask "${m.selector}" reached no element, so it hid nothing: whatever it was for may be showing`
|
|
12124
|
+
);
|
|
12125
|
+
if (take.meta.wall)
|
|
12126
|
+
warnings.push(
|
|
12127
|
+
`recorded past a wall: asked for ${take.meta.wall.asked}, landed on ${take.meta.wall.landed}. This footage may be the wrong page`
|
|
12128
|
+
);
|
|
11712
12129
|
if (!take.doc) problems.push("missing doc.json (run plan)");
|
|
11713
12130
|
if (take.doc) {
|
|
11714
12131
|
const lint = lintDoc(take.doc);
|
|
@@ -11937,6 +12354,11 @@ async function run(argv) {
|
|
|
11937
12354
|
} catch (e) {
|
|
11938
12355
|
if (e instanceof UsageError) {
|
|
11939
12356
|
process.stderr.write(`usage error: ${e.message}
|
|
12357
|
+
`);
|
|
12358
|
+
return EXIT_USAGE;
|
|
12359
|
+
}
|
|
12360
|
+
if (e instanceof SetupError || e instanceof SetupEnvError) {
|
|
12361
|
+
process.stderr.write(`${e.message}
|
|
11940
12362
|
`);
|
|
11941
12363
|
return EXIT_USAGE;
|
|
11942
12364
|
}
|
|
@@ -11989,4 +12411,4 @@ export {
|
|
|
11989
12411
|
verbHelp,
|
|
11990
12412
|
run
|
|
11991
12413
|
};
|
|
11992
|
-
//# sourceMappingURL=chunk-
|
|
12414
|
+
//# sourceMappingURL=chunk-6PJPGBKH.js.map
|