@vosjs/cli 0.13.0 → 0.13.1
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 +171 -90
- package/dist/{chunk-5BFGODBI.js → chunk-EFI3WUQB.js} +5 -1
- package/dist/chunk-EFI3WUQB.js.map +1 -0
- package/dist/{chunk-X2BKHZ56.js → chunk-GCWE75OA.js} +1 -105
- package/dist/chunk-GCWE75OA.js.map +1 -0
- package/dist/chunk-NHKHTIDR.js +216 -0
- package/dist/chunk-NHKHTIDR.js.map +1 -0
- package/dist/{chunk-6TFKMNSG.js → chunk-YICRU4ER.js} +74 -28
- package/dist/chunk-YICRU4ER.js.map +1 -0
- package/dist/cli.js +32 -21
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +7 -7
- package/dist/manifest-UCS6OVWZ.js +8 -0
- package/dist/run-PCWIQ4GI.js +9 -0
- package/package.json +9 -9
- package/schema/doc.schema.json +1 -1
- package/dist/chunk-5BFGODBI.js.map +0 -1
- package/dist/chunk-6TFKMNSG.js.map +0 -1
- package/dist/chunk-A2VSDK3C.js +0 -42
- package/dist/chunk-A2VSDK3C.js.map +0 -1
- package/dist/chunk-X2BKHZ56.js.map +0 -1
- package/dist/manifest-3LIL5M3F.js +0 -8
- package/dist/run-VU6B3CXC.js +0 -9
- /package/dist/{manifest-3LIL5M3F.js.map → manifest-UCS6OVWZ.js.map} +0 -0
- /package/dist/{run-VU6B3CXC.js.map → run-PCWIQ4GI.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/render.ts"],"sourcesContent":["import { compileVosConfig } from '@vosjs/core'\nimport { generateRenderTemplate } from '@vosjs/core/runtime'\nimport { elementsBundleCode } from '@vosjs/elements/bundle'\nimport { tweenRuntimeCode } from '@vosjs/tween/bundle'\nimport type { Browser, Page } from 'playwright'\n\n/** Fake secure origin the render page is served from (WebCodecs needs one). */\nconst RENDER_ORIGIN = 'https://vos-cli.render'\n\nexport interface RenderCommonOptions {\n config: Record<string, unknown>\n width: number\n height: number\n /** Phase callback for progress reporting. */\n onPhase?: (phase: string) => void\n}\n\nexport interface RenderVideoOptions extends RenderCommonOptions {\n fps: number\n /** Output duration in seconds. */\n duration: number\n format: 'webm' | 'mp4'\n}\n\nexport interface RenderStillOptions extends RenderCommonOptions {\n /** Seek time for the captured frame, seconds. */\n time: number\n}\n\nexport interface RenderResult {\n bytes: Uint8Array\n mimeType: string\n}\n\ninterface RenderComplete {\n success: boolean\n data?: string\n error?: string\n}\n\nfunction compile(config: Record<string, unknown>): string {\n return compileVosConfig(config as never, { tweenEngine: 'vos' })\n}\n\nasync function runCapturePage(\n browser: Browser,\n html: string,\n opts: { width: number; height: number; timeoutMs: number },\n): Promise<RenderComplete> {\n const context = await browser.newContext({\n viewport: { width: opts.width, height: opts.height },\n })\n const page: Page = await context.newPage()\n const errors: string[] = []\n page.on('console', (m) => {\n if (m.type() === 'error') errors.push(m.text())\n })\n try {\n await page.route(`${RENDER_ORIGIN}/**`, (route) =>\n route.fulfill({ status: 200, contentType: 'text/html', body: html }),\n )\n await page.goto(`${RENDER_ORIGIN}/render`, {\n waitUntil: 'domcontentloaded',\n })\n const start = Date.now()\n for (;;) {\n const done = (await page.evaluate(\n 'window.__renderComplete ?? null',\n )) as RenderComplete | null\n if (done) return done\n if (Date.now() - start > opts.timeoutMs) {\n return {\n success: false,\n error: `render timed out after ${Math.round(opts.timeoutMs / 1000)}s${\n errors.length\n ? ` (page errors: ${errors.slice(0, 3).join(' | ')})`\n : ''\n }`,\n }\n }\n await new Promise((r) => setTimeout(r, 400))\n }\n } finally {\n await context.close()\n }\n}\n\nfunction decodeDataUrl(dataUrl: string): RenderResult {\n const m = /^data:([^;,]+);base64,(.*)$/s.exec(dataUrl)\n if (!m) throw new Error('capture page returned an unexpected payload')\n return { bytes: Uint8Array.from(Buffer.from(m[2], 'base64')), mimeType: m[1] }\n}\n\n/** Render a vos config to a video (WebM/MP4) in a headless browser. */\nexport async function renderVideo(\n browser: Browser,\n opts: RenderVideoOptions,\n): Promise<RenderResult> {\n opts.onPhase?.('compile')\n const code = compile(opts.config)\n const html = generateRenderTemplate(code, {\n mode: 'capture-video',\n capture: {\n width: opts.width,\n height: opts.height,\n duration: opts.duration,\n fps: opts.fps,\n format: opts.format,\n },\n elementsBundleCode,\n tweenEngine: 'vos',\n tweenBundleCode: tweenRuntimeCode,\n })\n opts.onPhase?.('render')\n // Generous ceiling: startup + CDN module fetch + ~4× realtime per frame batch.\n const timeoutMs = 90_000 + opts.duration * opts.fps * 400\n const done = await runCapturePage(browser, html, {\n width: opts.width,\n height: opts.height,\n timeoutMs,\n })\n if (!done.success || !done.data)\n throw new Error(done.error ?? 'render failed')\n return decodeDataUrl(done.data)\n}\n\n/** Render a single frame of a vos config to an image in a headless browser. */\nexport async function renderStill(\n browser: Browser,\n opts: RenderStillOptions,\n): Promise<RenderResult> {\n opts.onPhase?.('compile')\n const code = compile(opts.config)\n const html = generateRenderTemplate(code, {\n mode: 'capture-thumbnail',\n capture: {\n width: opts.width,\n height: opts.height,\n duration: Math.max(1, opts.time + 1),\n fps: 30,\n thumbnailTime: opts.time,\n },\n elementsBundleCode,\n tweenEngine: 'vos',\n tweenBundleCode: tweenRuntimeCode,\n })\n opts.onPhase?.('render')\n const done = await runCapturePage(browser, html, {\n width: opts.width,\n height: opts.height,\n timeoutMs: 120_000,\n })\n if (!done.success || !done.data)\n throw new Error(done.error ?? 'still render failed')\n return decodeDataUrl(done.data)\n}\n\nexport interface PreviewPages {\n /** Host page: iframes the player and drives it over the bridge. */\n hostHtml: string\n /** The engine's playback-mode player (waits for a LOAD postMessage). */\n playerHtml: string\n}\n\n/**\n * Pages for `vos preview`. The playback template is the iframe HALF of the\n * player bridge — it renders nothing until a host posts `LOAD`, so the CLI\n * serves a minimal host page that sends `LOAD { code, autoplay }` and shows\n * a transport line (time / duration, click to play-pause).\n */\nexport function previewPages(config: Record<string, unknown>): PreviewPages {\n const code = compile(config)\n const playerHtml = generateRenderTemplate(code, {\n mode: 'playback',\n elementsBundleCode,\n tweenEngine: 'vos',\n tweenBundleCode: tweenRuntimeCode,\n })\n const codeJson = JSON.stringify(code).replace(/<\\//g, '<\\\\/')\n const hostHtml = `<!doctype html><html><head><meta charset=\"utf-8\"><title>vos preview</title>\n<style>\n html,body{margin:0;height:100%;background:#000;overflow:hidden}\n iframe{position:absolute;inset:0;width:100%;height:100%;border:0}\n #cover{position:fixed;inset:0;cursor:pointer}\n #hud{position:fixed;left:12px;bottom:10px;color:#fff;opacity:.75;font:12px/1.4 ui-monospace,monospace;\n background:rgba(0,0,0,.45);padding:4px 8px;border-radius:6px;pointer-events:none}\n</style></head><body>\n<iframe id=\"p\" src=\"/player\" allow=\"autoplay\"></iframe>\n<div id=\"cover\"></div>\n<div id=\"hud\">loading…</div>\n<script>\n const code = ${codeJson}\n const frame = document.getElementById('p')\n const hud = document.getElementById('hud')\n let ready = false\n let playing = true\n const post = (msg) => frame.contentWindow && frame.contentWindow.postMessage(msg, '*')\n const load = () => post({ type: 'LOAD', code, autoplay: true })\n window.addEventListener('message', (e) => {\n const msg = e.data || {}\n if (msg.type === 'BRIDGE_READY') load()\n if (msg.type === 'READY') { ready = true; hud.textContent = '0.0 / ' + msg.duration.toFixed(1) + 's — click to pause' }\n if (msg.type === 'UPDATE') hud.textContent = msg.time.toFixed(1) + ' / ' + msg.duration.toFixed(1) + 's — click to ' + (playing ? 'pause' : 'play')\n if (msg.type === 'ERROR') hud.textContent = 'error: ' + msg.error\n })\n // Belt and braces: if BRIDGE_READY raced the host, retry LOAD until READY.\n let tries = 0\n const iv = setInterval(() => {\n if (ready || tries++ > 20) return clearInterval(iv)\n load()\n }, 400)\n // The iframe swallows clicks — capture them on an overlay above it.\n document.getElementById('cover').addEventListener('click', () => {\n if (!ready) return\n playing = !playing\n post({ type: playing ? 'PLAY' : 'PAUSE' })\n hud.textContent = hud.textContent.replace(/click to (pause|play)/, 'click to ' + (playing ? 'pause' : 'play'))\n })\n</script></body></html>`\n return { hostHtml, playerHtml }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/args.ts
|
|
4
|
+
var UsageError = class extends Error {
|
|
5
|
+
};
|
|
6
|
+
function parseArgs(argv, booleanFlags) {
|
|
7
|
+
const positionals = [];
|
|
8
|
+
const flags = {};
|
|
9
|
+
for (let i = 0; i < argv.length; i++) {
|
|
10
|
+
const arg = argv[i];
|
|
11
|
+
if (arg === "--") {
|
|
12
|
+
positionals.push(...argv.slice(i + 1));
|
|
13
|
+
break;
|
|
14
|
+
}
|
|
15
|
+
if (arg.startsWith("--")) {
|
|
16
|
+
const eq = arg.indexOf("=");
|
|
17
|
+
if (eq !== -1) {
|
|
18
|
+
flags[arg.slice(2, eq)] = arg.slice(eq + 1);
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const name = arg.slice(2);
|
|
22
|
+
if (booleanFlags.has(name)) {
|
|
23
|
+
flags[name] = true;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const next = argv[i + 1];
|
|
27
|
+
if (next === void 0 || next.startsWith("--")) {
|
|
28
|
+
throw new UsageError(`--${name} expects a value`);
|
|
29
|
+
}
|
|
30
|
+
flags[name] = next;
|
|
31
|
+
i++;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
positionals.push(arg);
|
|
35
|
+
}
|
|
36
|
+
return { positionals, flags };
|
|
37
|
+
}
|
|
38
|
+
function numFlag(flags, name, fallback) {
|
|
39
|
+
const v = flags[name];
|
|
40
|
+
if (v === void 0) return fallback;
|
|
41
|
+
const n = Number(v);
|
|
42
|
+
if (!Number.isFinite(n))
|
|
43
|
+
throw new UsageError(`--${name} expects a number, got "${String(v)}"`);
|
|
44
|
+
return n;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/loadConfig.ts
|
|
48
|
+
import { readFile } from "fs/promises";
|
|
49
|
+
import { existsSync, readFileSync, statSync } from "fs";
|
|
50
|
+
import { join } from "path";
|
|
51
|
+
import {
|
|
52
|
+
CURRENT_CONFIG_VERSION,
|
|
53
|
+
migrateConfig,
|
|
54
|
+
vosConfigJsonSchema
|
|
55
|
+
} from "@vosjs/core";
|
|
56
|
+
function directoryKind(target) {
|
|
57
|
+
try {
|
|
58
|
+
if (!statSync(target).isDirectory()) return "none";
|
|
59
|
+
} catch {
|
|
60
|
+
return "none";
|
|
61
|
+
}
|
|
62
|
+
const docPath = join(target, "doc.json");
|
|
63
|
+
if (existsSync(docPath)) {
|
|
64
|
+
try {
|
|
65
|
+
const doc = JSON.parse(readFileSync(docPath, "utf8"));
|
|
66
|
+
if (typeof doc === "object" && doc !== null && "source" in doc)
|
|
67
|
+
return "take";
|
|
68
|
+
} catch {
|
|
69
|
+
return "take";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return existsSync(join(target, "config.json")) ? "program" : "none";
|
|
73
|
+
}
|
|
74
|
+
async function loadProgramDirectory(dir, warnings) {
|
|
75
|
+
const config = JSON.parse(
|
|
76
|
+
await readFile(join(dir, "config.json"), "utf8")
|
|
77
|
+
);
|
|
78
|
+
const docPath = join(dir, "doc.json");
|
|
79
|
+
if (!existsSync(docPath)) return config;
|
|
80
|
+
const doc = JSON.parse(await readFile(docPath, "utf8"));
|
|
81
|
+
if (typeof doc !== "object" || doc === null || "source" in doc) return config;
|
|
82
|
+
if (typeof config !== "object" || config === null) {
|
|
83
|
+
throw new UsageError(`${dir}/config.json does not contain a JSON object`);
|
|
84
|
+
}
|
|
85
|
+
const raw = doc;
|
|
86
|
+
const program = raw.program && typeof raw.program === "object" ? raw.program : {};
|
|
87
|
+
const { lowerProgramDoc } = await import("@vosjs/studio-core");
|
|
88
|
+
const composed = lowerProgramDoc(
|
|
89
|
+
{ ...raw, program: { ...program, config } },
|
|
90
|
+
{ bake: true }
|
|
91
|
+
).config;
|
|
92
|
+
warnings.push(
|
|
93
|
+
"composed config.json with doc.json (a program document: its layers and tween edits ride the render)"
|
|
94
|
+
);
|
|
95
|
+
return composed;
|
|
96
|
+
}
|
|
97
|
+
async function readSourceText(source) {
|
|
98
|
+
if (/^https?:\/\//.test(source)) {
|
|
99
|
+
const res = await fetch(source);
|
|
100
|
+
if (!res.ok) throw new Error(`fetch ${source} \u2192 ${res.status}`);
|
|
101
|
+
return await res.text();
|
|
102
|
+
}
|
|
103
|
+
let isDir = false;
|
|
104
|
+
try {
|
|
105
|
+
isDir = statSync(source).isDirectory();
|
|
106
|
+
} catch {
|
|
107
|
+
}
|
|
108
|
+
if (isDir) {
|
|
109
|
+
throw new UsageError(
|
|
110
|
+
`${source} is a directory with no config.json and no doc.json \u2014 pass a config file, a URL, a program directory or a take`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
return await readFile(source, "utf8");
|
|
114
|
+
}
|
|
115
|
+
async function loadVosConfig(source) {
|
|
116
|
+
const warnings = [];
|
|
117
|
+
let parsed;
|
|
118
|
+
const kind = /^https?:\/\//.test(source) ? "none" : directoryKind(source);
|
|
119
|
+
if (kind === "take") {
|
|
120
|
+
throw new UsageError(
|
|
121
|
+
`${source} is a take (its doc.json is a recording document). Render it through the take pipeline: vos render ${source} [out]; vos frames ${source} for stills.`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
if (kind === "program") {
|
|
125
|
+
parsed = await loadProgramDirectory(source, warnings);
|
|
126
|
+
return finish(source, parsed, warnings);
|
|
127
|
+
}
|
|
128
|
+
const raw = await readSourceText(source);
|
|
129
|
+
try {
|
|
130
|
+
parsed = JSON.parse(raw);
|
|
131
|
+
} catch (e) {
|
|
132
|
+
throw new UsageError(`${source} is not valid JSON: ${e.message}`);
|
|
133
|
+
}
|
|
134
|
+
return finish(source, parsed, warnings);
|
|
135
|
+
}
|
|
136
|
+
function finish(source, parsed, warnings) {
|
|
137
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
138
|
+
throw new UsageError(`${source} does not contain a JSON object`);
|
|
139
|
+
}
|
|
140
|
+
let obj = parsed;
|
|
141
|
+
if (typeof obj.config === "object" && obj.config !== null && !("createTimeline" in obj)) {
|
|
142
|
+
obj = obj.config;
|
|
143
|
+
warnings.push("unwrapped { config } envelope");
|
|
144
|
+
}
|
|
145
|
+
const version = obj.version;
|
|
146
|
+
const migrated = migrateConfig(obj);
|
|
147
|
+
if (version === void 0) {
|
|
148
|
+
warnings.push(
|
|
149
|
+
`config declares no "version". Add "version": ${CURRENT_CONFIG_VERSION} before pushing it.`
|
|
150
|
+
);
|
|
151
|
+
} else if (version !== CURRENT_CONFIG_VERSION) {
|
|
152
|
+
warnings.push(
|
|
153
|
+
`migrated config v${String(version)} to v${CURRENT_CONFIG_VERSION}`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
const check = vosConfigJsonSchema.safeParse(migrated);
|
|
157
|
+
if (!check.success) {
|
|
158
|
+
const issues = check.error.issues.slice(0, 5).map((i) => ` ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
|
|
159
|
+
throw new UsageError(`invalid vos config:
|
|
160
|
+
${issues}`);
|
|
161
|
+
}
|
|
162
|
+
return { config: migrated, warnings };
|
|
163
|
+
}
|
|
164
|
+
function configDuration(config) {
|
|
165
|
+
const d = config.duration;
|
|
166
|
+
return typeof d === "number" && Number.isFinite(d) && d > 0 ? d : void 0;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/browser.ts
|
|
170
|
+
import { chromium } from "playwright";
|
|
171
|
+
var BrowserUnavailableError = class extends Error {
|
|
172
|
+
constructor(cause) {
|
|
173
|
+
super(
|
|
174
|
+
`Could not launch a Chromium-family browser (${cause}).
|
|
175
|
+
Fixes, in order of preference:
|
|
176
|
+
1. Install Google Chrome (used automatically), or
|
|
177
|
+
2. npx playwright install chromium, or
|
|
178
|
+
3. Set VOS_BROWSER_PATH to a Chrome/Chromium executable.`
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
async function launchBrowser() {
|
|
183
|
+
const explicit = process.env.VOS_BROWSER_PATH;
|
|
184
|
+
if (explicit) {
|
|
185
|
+
return chromium.launch({ executablePath: explicit }).catch((e) => {
|
|
186
|
+
throw new BrowserUnavailableError(
|
|
187
|
+
`VOS_BROWSER_PATH failed: ${e.message}`
|
|
188
|
+
);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
return await chromium.launch({ channel: "chrome" });
|
|
193
|
+
} catch {
|
|
194
|
+
try {
|
|
195
|
+
return await chromium.launch();
|
|
196
|
+
} catch (e) {
|
|
197
|
+
throw new BrowserUnavailableError(
|
|
198
|
+
e.message.split("\n")[0] ?? "unknown"
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export {
|
|
205
|
+
UsageError,
|
|
206
|
+
parseArgs,
|
|
207
|
+
numFlag,
|
|
208
|
+
directoryKind,
|
|
209
|
+
loadProgramDirectory,
|
|
210
|
+
readSourceText,
|
|
211
|
+
loadVosConfig,
|
|
212
|
+
configDuration,
|
|
213
|
+
BrowserUnavailableError,
|
|
214
|
+
launchBrowser
|
|
215
|
+
};
|
|
216
|
+
//# sourceMappingURL=chunk-NHKHTIDR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/args.ts","../src/loadConfig.ts","../src/browser.ts"],"sourcesContent":["/**\n * Minimal argv parser — positionals + `--flag value` / `--flag=value` /\n * boolean flags. Deliberately tiny: the CLI has a small, stable surface and\n * agents benefit from predictable, dependency-free parsing.\n */\nexport interface ParsedArgs {\n positionals: string[]\n flags: Record<string, string | true>\n}\n\nexport class UsageError extends Error {}\n\nexport function parseArgs(\n argv: string[],\n booleanFlags: ReadonlySet<string>,\n): ParsedArgs {\n const positionals: string[] = []\n const flags: Record<string, string | true> = {}\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i]\n if (arg === '--') {\n positionals.push(...argv.slice(i + 1))\n break\n }\n if (arg.startsWith('--')) {\n const eq = arg.indexOf('=')\n if (eq !== -1) {\n flags[arg.slice(2, eq)] = arg.slice(eq + 1)\n continue\n }\n const name = arg.slice(2)\n if (booleanFlags.has(name)) {\n flags[name] = true\n continue\n }\n const next = argv[i + 1]\n if (next === undefined || next.startsWith('--')) {\n throw new UsageError(`--${name} expects a value`)\n }\n flags[name] = next\n i++\n continue\n }\n positionals.push(arg)\n }\n return { positionals, flags }\n}\n\nexport function numFlag(\n flags: ParsedArgs['flags'],\n name: string,\n fallback: number,\n): number {\n const v = flags[name]\n if (v === undefined) return fallback\n const n = Number(v)\n if (!Number.isFinite(n))\n throw new UsageError(`--${name} expects a number, got \"${String(v)}\"`)\n return n\n}\n","import { readFile } from 'node:fs/promises'\nimport { existsSync, readFileSync, statSync } from 'node:fs'\nimport { join } from 'node:path'\nimport {\n CURRENT_CONFIG_VERSION,\n migrateConfig,\n vosConfigJsonSchema,\n} from '@vosjs/core'\nimport { UsageError } from './args'\n\nexport interface LoadedConfig {\n config: Record<string, unknown>\n warnings: string[]\n}\n\n/**\n * What a directory holds, by one deterministic sniff and never a flag:\n * a TAKE carries a recording document (`doc.json` with `source`); a PROGRAM\n * directory carries `config.json`, optionally beside a program document\n * (`doc.json` without `source`: the shared layers, the tween overlay, the\n * program's own length, with `program.config` omitted on disk). An unparsable\n * doc.json counts as a take so the take path reports the real error.\n */\nexport type DirectoryKind = 'take' | 'program' | 'none'\n\nexport function directoryKind(target: string): DirectoryKind {\n try {\n if (!statSync(target).isDirectory()) return 'none'\n } catch {\n return 'none'\n }\n const docPath = join(target, 'doc.json')\n if (existsSync(docPath)) {\n try {\n const doc: unknown = JSON.parse(readFileSync(docPath, 'utf8'))\n if (typeof doc === 'object' && doc !== null && 'source' in doc)\n return 'take'\n } catch {\n return 'take'\n }\n }\n return existsSync(join(target, 'config.json')) ? 'program' : 'none'\n}\n\n/**\n * A program directory's config: `config.json`, composed with the program\n * document beside it when there is one (the layers ride as the studio stack\n * entry, the tween overlay is baked into `createTimeline`), so a render of\n * the directory is a render of what the studio and vos.so play.\n */\nexport async function loadProgramDirectory(\n dir: string,\n warnings: string[],\n): Promise<unknown> {\n const config: unknown = JSON.parse(\n await readFile(join(dir, 'config.json'), 'utf8'),\n )\n const docPath = join(dir, 'doc.json')\n if (!existsSync(docPath)) return config\n const doc: unknown = JSON.parse(await readFile(docPath, 'utf8'))\n if (typeof doc !== 'object' || doc === null || 'source' in doc) return config\n if (typeof config !== 'object' || config === null) {\n throw new UsageError(`${dir}/config.json does not contain a JSON object`)\n }\n const raw = doc as Record<string, unknown>\n const program =\n raw.program && typeof raw.program === 'object'\n ? (raw.program as Record<string, unknown>)\n : {}\n const { lowerProgramDoc } = await import('@vosjs/studio-core')\n const composed = lowerProgramDoc(\n { ...raw, program: { ...program, config } } as never,\n { bake: true },\n ).config\n warnings.push(\n 'composed config.json with doc.json (a program document: its layers and tween edits ride the render)',\n )\n return composed\n}\n\n/**\n * The text of a config source: an http(s) URL or a file. A directory that is\n * neither a take nor a program directory is refused in words, since the raw\n * read would report a bare EISDIR.\n */\nexport async function readSourceText(source: string): Promise<string> {\n if (/^https?:\\/\\//.test(source)) {\n const res = await fetch(source)\n if (!res.ok) throw new Error(`fetch ${source} → ${res.status}`)\n return await res.text()\n }\n let isDir = false\n try {\n isDir = statSync(source).isDirectory()\n } catch {\n /* a missing path reads below and reports itself */\n }\n if (isDir) {\n throw new UsageError(\n `${source} is a directory with no config.json and no doc.json — pass a config file, a URL, a program directory or a take`,\n )\n }\n return await readFile(source, 'utf8')\n}\n\n/**\n * Load a VosConfigJson from a file path, an http(s) URL, or a program\n * directory (config.json, composed with a program document when one sits\n * beside it); unwrap API `{ config }` envelopes, migrate old versions, and\n * validate against the schema. A take directory is refused here in words:\n * takes render through the take pipeline (`vos render <take>`).\n */\nexport async function loadVosConfig(source: string): Promise<LoadedConfig> {\n const warnings: string[] = []\n let parsed: unknown\n const kind = /^https?:\\/\\//.test(source) ? 'none' : directoryKind(source)\n if (kind === 'take') {\n throw new UsageError(\n `${source} is a take (its doc.json is a recording document). Render it through the take pipeline: vos render ${source} [out]; vos frames ${source} for stills.`,\n )\n }\n if (kind === 'program') {\n parsed = await loadProgramDirectory(source, warnings)\n return finish(source, parsed, warnings)\n }\n const raw = await readSourceText(source)\n\n try {\n parsed = JSON.parse(raw)\n } catch (e) {\n throw new UsageError(`${source} is not valid JSON: ${(e as Error).message}`)\n }\n return finish(source, parsed, warnings)\n}\n\nfunction finish(\n source: string,\n parsed: unknown,\n warnings: string[],\n): LoadedConfig {\n if (typeof parsed !== 'object' || parsed === null) {\n throw new UsageError(`${source} does not contain a JSON object`)\n }\n\n // API endpoints wrap the config: { config: {...} }.\n let obj = parsed as Record<string, unknown>\n if (\n typeof obj.config === 'object' &&\n obj.config !== null &&\n !('createTimeline' in obj)\n ) {\n obj = obj.config as Record<string, unknown>\n warnings.push('unwrapped { config } envelope')\n }\n\n const version = obj.version\n const migrated = migrateConfig(obj)\n if (version === undefined) {\n // Plays fine (the stamp happens above), but a storer refuses it.\n warnings.push(\n `config declares no \"version\". Add \"version\": ${CURRENT_CONFIG_VERSION} before pushing it.`,\n )\n } else if (version !== CURRENT_CONFIG_VERSION) {\n warnings.push(\n `migrated config v${String(version)} to v${CURRENT_CONFIG_VERSION}`,\n )\n }\n\n const check = vosConfigJsonSchema.safeParse(migrated)\n if (!check.success) {\n const issues = check.error.issues\n .slice(0, 5)\n .map((i) => ` ${i.path.join('.') || '(root)'}: ${i.message}`)\n .join('\\n')\n throw new UsageError(`invalid vos config:\\n${issues}`)\n }\n\n return { config: migrated, warnings }\n}\n\n/** Best-effort duration from the config (seconds). */\nexport function configDuration(\n config: Record<string, unknown>,\n): number | undefined {\n const d = config.duration\n return typeof d === 'number' && Number.isFinite(d) && d > 0 ? d : undefined\n}\n","import { chromium } from 'playwright'\nimport type { Browser } from 'playwright'\n\nexport class BrowserUnavailableError extends Error {\n constructor(cause: string) {\n super(\n `Could not launch a Chromium-family browser (${cause}).\\n` +\n `Fixes, in order of preference:\\n` +\n ` 1. Install Google Chrome (used automatically), or\\n` +\n ` 2. npx playwright install chromium, or\\n` +\n ` 3. Set VOS_BROWSER_PATH to a Chrome/Chromium executable.`,\n )\n }\n}\n\n/**\n * Launch headless Chromium: explicit VOS_BROWSER_PATH → system Chrome\n * (no download needed) → Playwright's bundled Chromium.\n */\nexport async function launchBrowser(): Promise<Browser> {\n const explicit = process.env.VOS_BROWSER_PATH\n if (explicit) {\n return chromium.launch({ executablePath: explicit }).catch((e) => {\n throw new BrowserUnavailableError(\n `VOS_BROWSER_PATH failed: ${(e as Error).message}`,\n )\n })\n }\n try {\n return await chromium.launch({ channel: 'chrome' })\n } catch {\n try {\n return await chromium.launch()\n } catch (e) {\n throw new BrowserUnavailableError(\n (e as Error).message.split('\\n')[0] ?? 'unknown',\n )\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
BrowserUnavailableError,
|
|
4
|
+
directoryKind,
|
|
4
5
|
launchBrowser
|
|
5
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-NHKHTIDR.js";
|
|
6
7
|
|
|
7
8
|
// src/plugin/run.ts
|
|
8
9
|
import { mkdir as mkdir7, readFile as readFile9, rm as rm4 } from "fs/promises";
|
|
@@ -4635,6 +4636,7 @@ async function pullTake(dir, flags, r) {
|
|
|
4635
4636
|
if (!vosId) {
|
|
4636
4637
|
throw new Error("this take was never pushed \u2014 pass --vos <id> to link it");
|
|
4637
4638
|
}
|
|
4639
|
+
const base = flags.since ?? state?.versionId ?? null;
|
|
4638
4640
|
const meta = await api(ctx, `/vos/${vosId}`);
|
|
4639
4641
|
if (meta.status !== 200) {
|
|
4640
4642
|
throw new Error(
|
|
@@ -4645,8 +4647,22 @@ async function pullTake(dir, flags, r) {
|
|
|
4645
4647
|
meta.json.vos?.currentVersionId ?? ""
|
|
4646
4648
|
);
|
|
4647
4649
|
if (!head) throw new Error("this vos has no versions");
|
|
4648
|
-
if (
|
|
4649
|
-
r.log(
|
|
4650
|
+
if (base === head) {
|
|
4651
|
+
r.log(
|
|
4652
|
+
flags.since ? "up to date \u2014 --since names the head" : "up to date \u2014 HEAD is your last push"
|
|
4653
|
+
);
|
|
4654
|
+
if (flags.check) {
|
|
4655
|
+
return {
|
|
4656
|
+
vosId,
|
|
4657
|
+
versionId: head,
|
|
4658
|
+
versionNumber: null,
|
|
4659
|
+
changed: false,
|
|
4660
|
+
checked: true,
|
|
4661
|
+
behind: 0,
|
|
4662
|
+
changes: [],
|
|
4663
|
+
protected: []
|
|
4664
|
+
};
|
|
4665
|
+
}
|
|
4650
4666
|
let media2;
|
|
4651
4667
|
if (flags.media && existsSync8(join12(dir, "doc.json"))) {
|
|
4652
4668
|
const local = JSON.parse(
|
|
@@ -4662,15 +4678,45 @@ async function pullTake(dir, flags, r) {
|
|
|
4662
4678
|
media: media2
|
|
4663
4679
|
};
|
|
4664
4680
|
}
|
|
4665
|
-
|
|
4681
|
+
let walked = null;
|
|
4682
|
+
if (base) {
|
|
4666
4683
|
const changes = await api(
|
|
4667
4684
|
ctx,
|
|
4668
|
-
`/vos/${vosId}/changes?since=${
|
|
4685
|
+
`/vos/${vosId}/changes?since=${encodeURIComponent(base)}`
|
|
4669
4686
|
);
|
|
4670
4687
|
if (changes.status === 200) {
|
|
4671
|
-
r.log(
|
|
4688
|
+
r.log(
|
|
4689
|
+
flags.since ? `changes since ${base.slice(0, 8)}\u2026:` : "changes since your last push:"
|
|
4690
|
+
);
|
|
4672
4691
|
printChanges(r, changes.json.changes, changes.json.protected);
|
|
4692
|
+
walked = {
|
|
4693
|
+
changes: Array.isArray(changes.json.changes) ? changes.json.changes : [],
|
|
4694
|
+
protected: Array.isArray(changes.json.protected) ? changes.json.protected : []
|
|
4695
|
+
};
|
|
4696
|
+
if (changes.json.truncated === true) {
|
|
4697
|
+
r.log("walk truncated \u2014 more versions exist; pull again after syncing");
|
|
4698
|
+
}
|
|
4699
|
+
} else {
|
|
4700
|
+
r.log(
|
|
4701
|
+
`changelog unavailable (${changes.status}) \u2014 the head is v?; pass --since <versionId> you can read, or pull to sync`
|
|
4702
|
+
);
|
|
4673
4703
|
}
|
|
4704
|
+
} else {
|
|
4705
|
+
r.log(
|
|
4706
|
+
"no base to compare from (this take was linked with --vos) \u2014 pass --since <versionId> for the changelog"
|
|
4707
|
+
);
|
|
4708
|
+
}
|
|
4709
|
+
if (flags.check) {
|
|
4710
|
+
return {
|
|
4711
|
+
vosId,
|
|
4712
|
+
versionId: head,
|
|
4713
|
+
versionNumber: null,
|
|
4714
|
+
changed: false,
|
|
4715
|
+
checked: true,
|
|
4716
|
+
behind: walked ? walked.changes.length : null,
|
|
4717
|
+
changes: walked?.changes ?? [],
|
|
4718
|
+
protected: walked?.protected ?? []
|
|
4719
|
+
};
|
|
4674
4720
|
}
|
|
4675
4721
|
const docRes = await api(ctx, `/vos/${vosId}/versions/${head}/doc`);
|
|
4676
4722
|
if (docRes.status !== 200) {
|
|
@@ -4702,7 +4748,7 @@ async function pullTake(dir, flags, r) {
|
|
|
4702
4748
|
}
|
|
4703
4749
|
|
|
4704
4750
|
// src/plugin/program.ts
|
|
4705
|
-
import { existsSync as existsSync9
|
|
4751
|
+
import { existsSync as existsSync9 } from "fs";
|
|
4706
4752
|
import { mkdir as mkdir6, readFile as readFile8, writeFile as writeFile9 } from "fs/promises";
|
|
4707
4753
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
4708
4754
|
import { basename as basename2, dirname as dirname2, join as join13 } from "path";
|
|
@@ -5369,19 +5415,7 @@ async function storeKey(key, origin, r) {
|
|
|
5369
5415
|
return EXIT_OK;
|
|
5370
5416
|
}
|
|
5371
5417
|
function isTakeDir(target) {
|
|
5372
|
-
|
|
5373
|
-
if (!statSync2(target).isDirectory()) return false;
|
|
5374
|
-
const docPath = join13(target, "doc.json");
|
|
5375
|
-
if (!existsSync9(docPath)) return false;
|
|
5376
|
-
try {
|
|
5377
|
-
const doc = JSON.parse(readFileSync4(docPath, "utf8"));
|
|
5378
|
-
return typeof doc === "object" && doc !== null && "source" in doc;
|
|
5379
|
-
} catch {
|
|
5380
|
-
return true;
|
|
5381
|
-
}
|
|
5382
|
-
} catch {
|
|
5383
|
-
return false;
|
|
5384
|
-
}
|
|
5418
|
+
return directoryKind(target) === "take";
|
|
5385
5419
|
}
|
|
5386
5420
|
async function readProgramDoc(dir, config) {
|
|
5387
5421
|
const docPath = join13(dir, "doc.json");
|
|
@@ -5404,7 +5438,7 @@ async function writeProgramDoc(dir, hosted) {
|
|
|
5404
5438
|
}
|
|
5405
5439
|
|
|
5406
5440
|
// src/plugin/asset.ts
|
|
5407
|
-
import { readFileSync as
|
|
5441
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
5408
5442
|
import { basename as basename3, extname as extname2 } from "path";
|
|
5409
5443
|
var BOOLEAN_FLAGS3 = /* @__PURE__ */ new Set(["json", "help"]);
|
|
5410
5444
|
var MIME_BY_EXT = {
|
|
@@ -5458,7 +5492,7 @@ async function cmdPush(argv) {
|
|
|
5458
5492
|
for (const file of positionals) {
|
|
5459
5493
|
const name = basename3(file);
|
|
5460
5494
|
const mime = MIME_BY_EXT[extname2(name).toLowerCase()] ?? "application/octet-stream";
|
|
5461
|
-
const body =
|
|
5495
|
+
const body = readFileSync4(file);
|
|
5462
5496
|
const form = new FormData();
|
|
5463
5497
|
form.set("file", new Blob([body], { type: mime }), name);
|
|
5464
5498
|
if (folder) form.set("folderId", folder.id);
|
|
@@ -5512,7 +5546,7 @@ async function cmdAsset(argv) {
|
|
|
5512
5546
|
}
|
|
5513
5547
|
|
|
5514
5548
|
// src/plugin/recipe.ts
|
|
5515
|
-
import { readFileSync as
|
|
5549
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
5516
5550
|
import { basename as basename4 } from "path";
|
|
5517
5551
|
var BOOLEAN_FLAGS4 = /* @__PURE__ */ new Set(["json", "help"]);
|
|
5518
5552
|
var USAGE = "vos recipe push <file.md> (--folder <folderId|slug> | --asset <assetId>)";
|
|
@@ -5532,7 +5566,7 @@ async function cmdPush2(argv) {
|
|
|
5532
5566
|
throw new UsageError(USAGE);
|
|
5533
5567
|
}
|
|
5534
5568
|
const filename = recipeFilename(file);
|
|
5535
|
-
const body =
|
|
5569
|
+
const body = readFileSync5(file);
|
|
5536
5570
|
const r = createReporter(flags.json === true);
|
|
5537
5571
|
const origin = platformOrigin({
|
|
5538
5572
|
origin: strFlag(flags, "origin"),
|
|
@@ -6490,7 +6524,9 @@ var BOOLEAN_FLAGS5 = /* @__PURE__ */ new Set([
|
|
|
6490
6524
|
"no-frames",
|
|
6491
6525
|
"media",
|
|
6492
6526
|
"print",
|
|
6493
|
-
"yes"
|
|
6527
|
+
"yes",
|
|
6528
|
+
"composed",
|
|
6529
|
+
"check"
|
|
6494
6530
|
]);
|
|
6495
6531
|
var MULTI_FLAGS2 = /* @__PURE__ */ new Set(["set", "override"]);
|
|
6496
6532
|
var HELP = `vos \u2014 record a browser flow, plan effects, render a product video; sync with vos.so
|
|
@@ -6581,7 +6617,8 @@ actions.json so anchors survive script edits.
|
|
|
6581
6617
|
record --strict exits 2 when any selector was skipped, the page never
|
|
6582
6618
|
reached networkidle, or the take hit --max-duration (the done event lists
|
|
6583
6619
|
skipped[] and capped either way). --max-duration defaults to the hosted
|
|
6584
|
-
recording cap
|
|
6620
|
+
recording cap, read live from GET /api/limits (30 min when the origin is
|
|
6621
|
+
unreachable): the capture stops there and says so in one line.
|
|
6585
6622
|
frames captures PNG stills at OUTPUT times (seconds or % of duration);
|
|
6586
6623
|
--at-zooms adds every zoom span's apex, --at-moments every digest moment \u2014
|
|
6587
6624
|
the agent's eyes AND exact-size posters/screenshots (--size WxH). render
|
|
@@ -7493,10 +7530,19 @@ async function cmdPull2(argv) {
|
|
|
7493
7530
|
api: strFlag(flags, "api"),
|
|
7494
7531
|
origin: strFlag(flags, "origin"),
|
|
7495
7532
|
vos: strFlag(flags, "vos"),
|
|
7496
|
-
media: flags.media === true
|
|
7533
|
+
media: flags.media === true,
|
|
7534
|
+
since: strFlag(flags, "since"),
|
|
7535
|
+
check: flags.check === true
|
|
7497
7536
|
},
|
|
7498
7537
|
r
|
|
7499
7538
|
);
|
|
7539
|
+
if (result.checked) {
|
|
7540
|
+
r.done(
|
|
7541
|
+
{ ...result },
|
|
7542
|
+
result.behind === null ? `${result.vosId}: head is ${result.versionId.slice(0, 8)}\u2026 \u2014 no base to count from; run without --check to sync` : result.behind === 0 ? `${result.vosId}: up to date` : `${result.behind} version${result.behind === 1 ? "" : "s"} behind \u2014 run without --check to sync`
|
|
7543
|
+
);
|
|
7544
|
+
return EXIT_OK;
|
|
7545
|
+
}
|
|
7500
7546
|
const mediaLine = result.media ? result.media.downloaded.length ? ` + ${result.media.downloaded.map((m) => m.file).join(", ")} (footage home \u2014 digest/frames/render run here now)` : result.media.kept.length ? ` (footage already here: ${result.media.kept.join(", ")})` : " (no hosted media on this doc)" : "";
|
|
7501
7547
|
r.done(
|
|
7502
7548
|
{ ...result },
|
|
@@ -7611,4 +7657,4 @@ export {
|
|
|
7611
7657
|
convertAgentBrowser,
|
|
7612
7658
|
run
|
|
7613
7659
|
};
|
|
7614
|
-
//# sourceMappingURL=chunk-
|
|
7660
|
+
//# sourceMappingURL=chunk-YICRU4ER.js.map
|