@vosjs/cli 0.59.1 → 0.60.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 +3 -1
- package/dist/assetManifest-Y5FYTZW6.js +15 -0
- package/dist/{chunk-YC3LY4LW.js → chunk-LWHVZ5WI.js} +54 -539
- package/dist/chunk-LWHVZ5WI.js.map +1 -0
- package/dist/{chunk-CYU7LVPB.js → chunk-QL25OPZB.js} +455 -923
- package/dist/chunk-QL25OPZB.js.map +1 -0
- package/dist/chunk-WCF6KIOK.js +770 -0
- package/dist/chunk-WCF6KIOK.js.map +1 -0
- package/dist/chunk-YMVRTZ4L.js +719 -0
- package/dist/chunk-YMVRTZ4L.js.map +1 -0
- package/dist/cli.js +72 -16
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +15 -11
- package/dist/{run-TVTM3QJW.js → run-BJ77A4G6.js} +5 -3
- package/dist/run-BJ77A4G6.js.map +1 -0
- package/package.json +4 -4
- package/skills/VERSION +1 -1
- package/skills/vos-authoring/references/schema-reference.md +40 -2
- package/skills/vos-port/SKILL.md +3 -1
- package/skills/vos-port/references/elements-and-context.md +5 -0
- package/skills/vos-port/references/hyperframes.md +1 -1
- package/skills/vos-port/references/remotion.md +1 -1
- package/dist/chunk-CYU7LVPB.js.map +0 -1
- package/dist/chunk-YC3LY4LW.js.map +0 -1
- /package/dist/{run-TVTM3QJW.js.map → assetManifest-Y5FYTZW6.js.map} +0 -0
|
@@ -1,42 +1,70 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
BrowserUnavailableError,
|
|
4
|
+
configDuration,
|
|
5
|
+
directoryKind,
|
|
6
|
+
launchBrowser,
|
|
7
|
+
loadVosConfig,
|
|
8
|
+
parseTimes,
|
|
9
|
+
reencodeStill,
|
|
10
|
+
renderStill,
|
|
11
|
+
videoFormat
|
|
12
|
+
} from "./chunk-LWHVZ5WI.js";
|
|
13
|
+
import {
|
|
14
|
+
PREV_DOC_NAME,
|
|
15
|
+
RECORDING_NAME,
|
|
16
|
+
docAudioRefs,
|
|
17
|
+
docLayerRefs,
|
|
18
|
+
docMediaRefs,
|
|
19
|
+
dropScreencastFrames,
|
|
20
|
+
ensureTakeDir,
|
|
21
|
+
hostedLiteralWarnings,
|
|
22
|
+
isTakeRelativeKey,
|
|
23
|
+
loadTake,
|
|
24
|
+
localManifestRefs,
|
|
25
|
+
prepareReRecord,
|
|
26
|
+
pullManifest,
|
|
27
|
+
pullMedia,
|
|
28
|
+
readJson,
|
|
29
|
+
readMediaHead,
|
|
30
|
+
sourceSidecarRefs,
|
|
31
|
+
takeMediaFile,
|
|
32
|
+
takePaths,
|
|
33
|
+
uploadAsset,
|
|
34
|
+
uploadDocRefs,
|
|
35
|
+
uploadManifest,
|
|
36
|
+
writeJson
|
|
37
|
+
} from "./chunk-YMVRTZ4L.js";
|
|
38
|
+
import {
|
|
4
39
|
CREDENTIALS_PATH,
|
|
5
40
|
MEDIA_HEAD_BYTES,
|
|
6
41
|
SYNC_STATE_NAME,
|
|
7
|
-
TAKE_MEDIA_EXTENSIONS,
|
|
8
42
|
UsageError,
|
|
9
43
|
apiError,
|
|
10
44
|
apiJson,
|
|
11
45
|
clientId,
|
|
12
|
-
configDuration,
|
|
13
46
|
containerMismatch,
|
|
14
47
|
deriveSlug,
|
|
15
|
-
directoryKind,
|
|
16
|
-
extensionFor,
|
|
17
48
|
formatChanges,
|
|
18
49
|
hasFlag,
|
|
19
|
-
|
|
20
|
-
|
|
50
|
+
hostedAssetId,
|
|
51
|
+
manifestRefs,
|
|
21
52
|
nameForType,
|
|
22
53
|
numFlag2 as numFlag,
|
|
23
54
|
parseArgs2 as parseArgs,
|
|
24
|
-
parseTimes,
|
|
25
55
|
parseVosId,
|
|
26
56
|
platformOrigin,
|
|
57
|
+
programAssets,
|
|
27
58
|
readSyncState,
|
|
28
|
-
reencodeStill,
|
|
29
|
-
renderStill,
|
|
30
59
|
requireCredential,
|
|
31
60
|
resolveCredential,
|
|
32
61
|
resolveMediaType,
|
|
33
62
|
serverWarnings,
|
|
34
63
|
sniffMediaType,
|
|
35
64
|
strFlag,
|
|
36
|
-
videoFormat,
|
|
37
65
|
writeCredential,
|
|
38
66
|
writeSyncState
|
|
39
|
-
} from "./chunk-
|
|
67
|
+
} from "./chunk-WCF6KIOK.js";
|
|
40
68
|
import {
|
|
41
69
|
deadLine,
|
|
42
70
|
deadWarns
|
|
@@ -98,9 +126,9 @@ function landedLines(l) {
|
|
|
98
126
|
}
|
|
99
127
|
|
|
100
128
|
// src/plugin/run.ts
|
|
101
|
-
import { mkdir as
|
|
102
|
-
import { existsSync as
|
|
103
|
-
import { join as
|
|
129
|
+
import { mkdir as mkdir9, readFile as readFile15, rm as rm3 } from "fs/promises";
|
|
130
|
+
import { existsSync as existsSync23 } from "fs";
|
|
131
|
+
import { join as join26, resolve as resolve15 } from "path";
|
|
104
132
|
import {
|
|
105
133
|
END_CARD_FROM as END_CARD_FROM2,
|
|
106
134
|
docOutputDuration as docOutputDuration3,
|
|
@@ -1813,535 +1841,6 @@ function framingWarnings(docIn, doc, zoom, overlays, warnings) {
|
|
|
1813
1841
|
}
|
|
1814
1842
|
}
|
|
1815
1843
|
|
|
1816
|
-
// src/plugin/media.ts
|
|
1817
|
-
import { isKeyedOverlay } from "@vosjs/studio-core";
|
|
1818
|
-
import { createHash } from "crypto";
|
|
1819
|
-
import { createWriteStream, existsSync as existsSync2 } from "fs";
|
|
1820
|
-
import {
|
|
1821
|
-
mkdir as mkdir2,
|
|
1822
|
-
open,
|
|
1823
|
-
readFile as readFile2,
|
|
1824
|
-
readdir,
|
|
1825
|
-
rename as rename2,
|
|
1826
|
-
rm as rm2,
|
|
1827
|
-
stat,
|
|
1828
|
-
writeFile as writeFile2
|
|
1829
|
-
} from "fs/promises";
|
|
1830
|
-
import { basename as basename2, dirname, join as join2 } from "path";
|
|
1831
|
-
import { Readable } from "stream";
|
|
1832
|
-
import { pipeline } from "stream/promises";
|
|
1833
|
-
|
|
1834
|
-
// src/plugin/take.ts
|
|
1835
|
-
import { mkdir, readFile, rename, rm, writeFile } from "fs/promises";
|
|
1836
|
-
import { existsSync, readdirSync, statSync } from "fs";
|
|
1837
|
-
import { basename, join } from "path";
|
|
1838
|
-
var RECORDING_NAME = "recording.webm";
|
|
1839
|
-
function takeMediaFiles(dir, stem) {
|
|
1840
|
-
return TAKE_MEDIA_EXTENSIONS.map((ext) => join(dir, `${stem}${ext}`)).filter(
|
|
1841
|
-
(file) => existsSync(file)
|
|
1842
|
-
);
|
|
1843
|
-
}
|
|
1844
|
-
function takeMediaFile(dir, stem, fallback) {
|
|
1845
|
-
return takeMediaFiles(dir, stem)[0] ?? join(dir, fallback);
|
|
1846
|
-
}
|
|
1847
|
-
var PREV_DOC_NAME = "doc.prev.json";
|
|
1848
|
-
function takePaths(dir) {
|
|
1849
|
-
const recording = takeMediaFile(dir, "recording", RECORDING_NAME);
|
|
1850
|
-
return {
|
|
1851
|
-
dir,
|
|
1852
|
-
recording,
|
|
1853
|
-
recordingName: basename(recording),
|
|
1854
|
-
framesDir: join(dir, "frames"),
|
|
1855
|
-
framesIndex: join(dir, "frames.json"),
|
|
1856
|
-
cursor: join(dir, "cursor.json"),
|
|
1857
|
-
meta: join(dir, "meta.json"),
|
|
1858
|
-
actions: join(dir, "actions.json"),
|
|
1859
|
-
doc: join(dir, "doc.json")
|
|
1860
|
-
};
|
|
1861
|
-
}
|
|
1862
|
-
async function ensureTakeDir(dir) {
|
|
1863
|
-
const p = takePaths(dir);
|
|
1864
|
-
await mkdir(p.framesDir, { recursive: true });
|
|
1865
|
-
return p;
|
|
1866
|
-
}
|
|
1867
|
-
async function dropScreencastFrames(p) {
|
|
1868
|
-
if (!existsSync(p.recording) || statSync(p.recording).size === 0) return 0;
|
|
1869
|
-
if (!existsSync(p.framesDir)) return 0;
|
|
1870
|
-
let freed = 0;
|
|
1871
|
-
for (const name of readdirSync(p.framesDir)) {
|
|
1872
|
-
try {
|
|
1873
|
-
freed += statSync(join(p.framesDir, name)).size;
|
|
1874
|
-
} catch {
|
|
1875
|
-
}
|
|
1876
|
-
}
|
|
1877
|
-
await rm(p.framesDir, { recursive: true, force: true });
|
|
1878
|
-
await rm(p.framesIndex, { force: true });
|
|
1879
|
-
return freed;
|
|
1880
|
-
}
|
|
1881
|
-
async function prepareReRecord(dir) {
|
|
1882
|
-
const p = takePaths(dir);
|
|
1883
|
-
const kept = [];
|
|
1884
|
-
let prevDoc = false;
|
|
1885
|
-
if (existsSync(p.doc)) {
|
|
1886
|
-
await rename(p.doc, join(dir, PREV_DOC_NAME));
|
|
1887
|
-
prevDoc = true;
|
|
1888
|
-
kept.push(PREV_DOC_NAME);
|
|
1889
|
-
}
|
|
1890
|
-
if (existsSync(p.actions)) kept.push("actions.json");
|
|
1891
|
-
if (existsSync(join(dir, "vos.json"))) kept.push("vos.json");
|
|
1892
|
-
for (const stale of [
|
|
1893
|
-
// Every container, not just the one `record` writes: a pulled take's
|
|
1894
|
-
// footage may be mp4, and a survivor would outrank the new recording.
|
|
1895
|
-
...takeMediaFiles(dir, "recording"),
|
|
1896
|
-
...takeMediaFiles(dir, "mic"),
|
|
1897
|
-
...takeMediaFiles(dir, "cam"),
|
|
1898
|
-
p.cursor,
|
|
1899
|
-
p.meta,
|
|
1900
|
-
p.framesIndex,
|
|
1901
|
-
p.framesDir,
|
|
1902
|
-
join(dir, "stills"),
|
|
1903
|
-
join(dir, "digest")
|
|
1904
|
-
]) {
|
|
1905
|
-
await rm(stale, { recursive: true, force: true });
|
|
1906
|
-
}
|
|
1907
|
-
return { prevDoc, kept };
|
|
1908
|
-
}
|
|
1909
|
-
async function readJson(file) {
|
|
1910
|
-
return JSON.parse(await readFile(file, "utf8"));
|
|
1911
|
-
}
|
|
1912
|
-
async function writeJson(file, value, pretty = false) {
|
|
1913
|
-
await writeFile(
|
|
1914
|
-
file,
|
|
1915
|
-
pretty ? JSON.stringify(value, null, 2) : JSON.stringify(value)
|
|
1916
|
-
);
|
|
1917
|
-
}
|
|
1918
|
-
async function loadTake(dir) {
|
|
1919
|
-
const paths = takePaths(dir);
|
|
1920
|
-
if (!existsSync(paths.meta)) {
|
|
1921
|
-
throw new Error(
|
|
1922
|
-
`${dir} is not a take directory (missing meta.json) \u2014 run record first`
|
|
1923
|
-
);
|
|
1924
|
-
}
|
|
1925
|
-
const meta = await readJson(paths.meta);
|
|
1926
|
-
const cursor = existsSync(paths.cursor) ? await readJson(paths.cursor) : [];
|
|
1927
|
-
const actions = existsSync(paths.actions) ? await readJson(paths.actions) : null;
|
|
1928
|
-
const doc = existsSync(paths.doc) ? await readJson(paths.doc) : null;
|
|
1929
|
-
return { paths, cursor, meta, actions, doc };
|
|
1930
|
-
}
|
|
1931
|
-
|
|
1932
|
-
// src/plugin/uploadAsset.ts
|
|
1933
|
-
var SINGLE_SHOT_MAX_BYTES = 32 * 1024 * 1024;
|
|
1934
|
-
var PART_ATTEMPTS = 3;
|
|
1935
|
-
function planParts(totalBytes, partBytes) {
|
|
1936
|
-
if (totalBytes <= 0 || partBytes <= 0) return [];
|
|
1937
|
-
const parts = [];
|
|
1938
|
-
for (let start = 0; start < totalBytes; start += partBytes) {
|
|
1939
|
-
parts.push({
|
|
1940
|
-
partNumber: parts.length + 1,
|
|
1941
|
-
start,
|
|
1942
|
-
end: Math.min(start + partBytes, totalBytes)
|
|
1943
|
-
});
|
|
1944
|
-
}
|
|
1945
|
-
return parts;
|
|
1946
|
-
}
|
|
1947
|
-
function failed(what, r) {
|
|
1948
|
-
const detail = typeof r.body.error === "string" ? r.body.error : "";
|
|
1949
|
-
return new Error(`${what} (${r.status})${detail ? `: ${detail}` : ""}`);
|
|
1950
|
-
}
|
|
1951
|
-
function asAsset(body) {
|
|
1952
|
-
return {
|
|
1953
|
-
id: String(body.id),
|
|
1954
|
-
url: String(body.url),
|
|
1955
|
-
size: typeof body.size === "number" ? body.size : 0,
|
|
1956
|
-
reused: body.reused === true
|
|
1957
|
-
};
|
|
1958
|
-
}
|
|
1959
|
-
function transient(status) {
|
|
1960
|
-
return status >= 500 || status === 429;
|
|
1961
|
-
}
|
|
1962
|
-
var wait = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1963
|
-
async function uploadAsset(target, bytes, opts) {
|
|
1964
|
-
if (bytes.length > SINGLE_SHOT_MAX_BYTES && opts.folderId) {
|
|
1965
|
-
throw new Error(
|
|
1966
|
-
`${opts.filename} is over ${SINGLE_SHOT_MAX_BYTES / 1024 / 1024} MB, and a file that large cannot be filed into a project on upload yet. Push it without --folder and reference it from a document, which keeps it`
|
|
1967
|
-
);
|
|
1968
|
-
}
|
|
1969
|
-
return bytes.length > SINGLE_SHOT_MAX_BYTES ? uploadInParts(target, bytes, opts) : uploadWhole(target, bytes, opts);
|
|
1970
|
-
}
|
|
1971
|
-
async function uploadWhole(target, bytes, opts) {
|
|
1972
|
-
const res = await apiJson(target.origin, "/api/assets/recording", {
|
|
1973
|
-
method: "POST",
|
|
1974
|
-
key: target.key,
|
|
1975
|
-
headers: {
|
|
1976
|
-
"Content-Type": opts.contentType,
|
|
1977
|
-
"Content-Length": String(bytes.length),
|
|
1978
|
-
"X-Filename": opts.filename,
|
|
1979
|
-
"X-Content-Hash": opts.contentHash,
|
|
1980
|
-
...opts.durationSeconds && opts.durationSeconds > 0 ? { "X-Content-Duration": opts.durationSeconds.toFixed(3) } : {},
|
|
1981
|
-
...opts.folderId ? { "X-Folder-Id": opts.folderId } : {}
|
|
1982
|
-
},
|
|
1983
|
-
raw: bytes
|
|
1984
|
-
});
|
|
1985
|
-
if (res.status !== 201 && res.status !== 200)
|
|
1986
|
-
throw failed("upload failed", res);
|
|
1987
|
-
return asAsset(res.body);
|
|
1988
|
-
}
|
|
1989
|
-
async function uploadInParts(target, bytes, opts) {
|
|
1990
|
-
const begun = await apiJson(
|
|
1991
|
-
target.origin,
|
|
1992
|
-
"/api/assets/recording/multipart",
|
|
1993
|
-
{
|
|
1994
|
-
method: "POST",
|
|
1995
|
-
key: target.key,
|
|
1996
|
-
body: {
|
|
1997
|
-
filename: opts.filename,
|
|
1998
|
-
contentType: opts.contentType,
|
|
1999
|
-
size: bytes.length,
|
|
2000
|
-
contentHash: opts.contentHash,
|
|
2001
|
-
...opts.durationSeconds && opts.durationSeconds > 0 ? { durationSeconds: opts.durationSeconds } : {}
|
|
2002
|
-
}
|
|
2003
|
-
}
|
|
2004
|
-
);
|
|
2005
|
-
if (begun.status !== 201 && begun.status !== 200) {
|
|
2006
|
-
throw failed("upload failed", begun);
|
|
2007
|
-
}
|
|
2008
|
-
if (begun.body.reused === true || typeof begun.body.url === "string") {
|
|
2009
|
-
return asAsset(begun.body);
|
|
2010
|
-
}
|
|
2011
|
-
const uploadId = String(begun.body.id);
|
|
2012
|
-
const partBytes = Number(begun.body.partBytes);
|
|
2013
|
-
if (!Number.isFinite(partBytes) || partBytes <= 0) {
|
|
2014
|
-
throw new Error("upload failed: the platform returned no part size");
|
|
2015
|
-
}
|
|
2016
|
-
const parts = planParts(bytes.length, partBytes);
|
|
2017
|
-
const etags = [];
|
|
2018
|
-
try {
|
|
2019
|
-
for (const part of parts) {
|
|
2020
|
-
const chunk2 = bytes.subarray(part.start, part.end);
|
|
2021
|
-
let sent = null;
|
|
2022
|
-
for (let attempt = 1; attempt <= PART_ATTEMPTS; attempt++) {
|
|
2023
|
-
try {
|
|
2024
|
-
sent = await apiJson(
|
|
2025
|
-
target.origin,
|
|
2026
|
-
`/api/assets/recording/multipart/${uploadId}/parts/${part.partNumber}`,
|
|
2027
|
-
{
|
|
2028
|
-
method: "PUT",
|
|
2029
|
-
key: target.key,
|
|
2030
|
-
headers: {
|
|
2031
|
-
"Content-Type": "application/octet-stream",
|
|
2032
|
-
"Content-Length": String(chunk2.length)
|
|
2033
|
-
},
|
|
2034
|
-
raw: chunk2
|
|
2035
|
-
}
|
|
2036
|
-
);
|
|
2037
|
-
} catch (networkError) {
|
|
2038
|
-
if (attempt === PART_ATTEMPTS) throw networkError;
|
|
2039
|
-
await wait(500 * 2 ** (attempt - 1));
|
|
2040
|
-
continue;
|
|
2041
|
-
}
|
|
2042
|
-
if (sent.status === 200) break;
|
|
2043
|
-
if (!transient(sent.status) || attempt === PART_ATTEMPTS) {
|
|
2044
|
-
throw failed(`part ${part.partNumber} failed`, sent);
|
|
2045
|
-
}
|
|
2046
|
-
await wait(500 * 2 ** (attempt - 1));
|
|
2047
|
-
}
|
|
2048
|
-
if (!sent || sent.status !== 200) {
|
|
2049
|
-
throw new Error(`part ${part.partNumber} failed`);
|
|
2050
|
-
}
|
|
2051
|
-
etags.push({
|
|
2052
|
-
partNumber: Number(sent.body.partNumber),
|
|
2053
|
-
etag: String(sent.body.etag)
|
|
2054
|
-
});
|
|
2055
|
-
opts.onPart?.(etags.length, parts.length);
|
|
2056
|
-
}
|
|
2057
|
-
const sealed = await apiJson(
|
|
2058
|
-
target.origin,
|
|
2059
|
-
`/api/assets/recording/multipart/${uploadId}/complete`,
|
|
2060
|
-
{ method: "POST", key: target.key, body: { parts: etags } }
|
|
2061
|
-
);
|
|
2062
|
-
if (sealed.status !== 201 && sealed.status !== 200) {
|
|
2063
|
-
throw failed("upload failed", sealed);
|
|
2064
|
-
}
|
|
2065
|
-
return asAsset(sealed.body);
|
|
2066
|
-
} catch (err) {
|
|
2067
|
-
await apiJson(
|
|
2068
|
-
target.origin,
|
|
2069
|
-
`/api/assets/recording/multipart/${uploadId}`,
|
|
2070
|
-
{ method: "DELETE", key: target.key }
|
|
2071
|
-
).catch(() => void 0);
|
|
2072
|
-
throw err;
|
|
2073
|
-
}
|
|
2074
|
-
}
|
|
2075
|
-
|
|
2076
|
-
// src/plugin/media.ts
|
|
2077
|
-
var SOURCE_MEDIA = [
|
|
2078
|
-
{ key: "videoKey", stem: "recording", fallbackExt: ".webm" },
|
|
2079
|
-
{ key: "micKey", stem: "mic", fallbackExt: ".webm" },
|
|
2080
|
-
{ key: "camKey", stem: "cam", fallbackExt: ".webm" }
|
|
2081
|
-
];
|
|
2082
|
-
function isTakeRelativeKey(key) {
|
|
2083
|
-
return !!key && !/^(https?:|blob:|data:|media:|\/\/)/.test(key) && !key.startsWith("/api/");
|
|
2084
|
-
}
|
|
2085
|
-
var takeRelativeFile = (key) => key.replace(/^\/+/, "");
|
|
2086
|
-
function sourceSidecarRefs(doc, keep = isTakeRelativeKey) {
|
|
2087
|
-
const out = [];
|
|
2088
|
-
for (const [field, where] of [
|
|
2089
|
-
["micKey", "mic track"],
|
|
2090
|
-
["camKey", "cam track"]
|
|
2091
|
-
]) {
|
|
2092
|
-
const key = doc.source[field];
|
|
2093
|
-
if (!key || !keep(key)) continue;
|
|
2094
|
-
out.push({
|
|
2095
|
-
where,
|
|
2096
|
-
key,
|
|
2097
|
-
set: (next) => {
|
|
2098
|
-
doc.source[field] = next;
|
|
2099
|
-
}
|
|
2100
|
-
});
|
|
2101
|
-
}
|
|
2102
|
-
return out;
|
|
2103
|
-
}
|
|
2104
|
-
function docObjectRefs(doc, keep = isTakeRelativeKey) {
|
|
2105
|
-
const out = [];
|
|
2106
|
-
for (const clip of doc.objects ?? []) {
|
|
2107
|
-
const asset = clip.asset;
|
|
2108
|
-
if (asset.kind !== "gltf" || !keep(asset.key)) continue;
|
|
2109
|
-
out.push({
|
|
2110
|
-
where: `object ${clip.id}`,
|
|
2111
|
-
key: asset.key,
|
|
2112
|
-
set: (next) => {
|
|
2113
|
-
asset.key = next;
|
|
2114
|
-
}
|
|
2115
|
-
});
|
|
2116
|
-
}
|
|
2117
|
-
return out;
|
|
2118
|
-
}
|
|
2119
|
-
function docOverlayRefs(doc, keep = isTakeRelativeKey) {
|
|
2120
|
-
const out = [];
|
|
2121
|
-
for (const clip of doc.overlays ?? []) {
|
|
2122
|
-
if (!isKeyedOverlay(clip) || !keep(clip.key)) continue;
|
|
2123
|
-
out.push({
|
|
2124
|
-
where: `overlay ${clip.id}`,
|
|
2125
|
-
key: clip.key,
|
|
2126
|
-
set: (next) => {
|
|
2127
|
-
clip.key = next;
|
|
2128
|
-
}
|
|
2129
|
-
});
|
|
2130
|
-
}
|
|
2131
|
-
return out;
|
|
2132
|
-
}
|
|
2133
|
-
function docLayerRefs(doc, keep = isTakeRelativeKey) {
|
|
2134
|
-
return [
|
|
2135
|
-
...docOverlayRefs(doc, keep),
|
|
2136
|
-
...docObjectRefs(doc, keep),
|
|
2137
|
-
...docAudioRefs(doc, keep)
|
|
2138
|
-
];
|
|
2139
|
-
}
|
|
2140
|
-
function docMediaRefs(doc, keep = isTakeRelativeKey) {
|
|
2141
|
-
const out = [];
|
|
2142
|
-
for (const m of doc.media ?? []) {
|
|
2143
|
-
if (keep(m.videoKey))
|
|
2144
|
-
out.push({
|
|
2145
|
-
where: `media ${m.id}`,
|
|
2146
|
-
key: m.videoKey,
|
|
2147
|
-
set: (next) => {
|
|
2148
|
-
m.videoKey = next;
|
|
2149
|
-
}
|
|
2150
|
-
});
|
|
2151
|
-
const mic = m.micKey;
|
|
2152
|
-
if (mic && keep(mic))
|
|
2153
|
-
out.push({
|
|
2154
|
-
where: `media ${m.id} mic`,
|
|
2155
|
-
key: mic,
|
|
2156
|
-
set: (next) => {
|
|
2157
|
-
m.micKey = next;
|
|
2158
|
-
}
|
|
2159
|
-
});
|
|
2160
|
-
const cam = m.camKey;
|
|
2161
|
-
if (cam && keep(cam))
|
|
2162
|
-
out.push({
|
|
2163
|
-
where: `media ${m.id} cam`,
|
|
2164
|
-
key: cam,
|
|
2165
|
-
set: (next) => {
|
|
2166
|
-
m.camKey = next;
|
|
2167
|
-
}
|
|
2168
|
-
});
|
|
2169
|
-
}
|
|
2170
|
-
out.push(...docOverlayRefs(doc, keep));
|
|
2171
|
-
out.push(...docObjectRefs(doc, keep));
|
|
2172
|
-
const mark = doc.endCard?.mark;
|
|
2173
|
-
if (mark && keep(mark.key))
|
|
2174
|
-
out.push({
|
|
2175
|
-
where: "end card mark",
|
|
2176
|
-
key: mark.key,
|
|
2177
|
-
set: (next) => {
|
|
2178
|
-
mark.key = next;
|
|
2179
|
-
}
|
|
2180
|
-
});
|
|
2181
|
-
const bg = doc.frame.backgroundMedia;
|
|
2182
|
-
if (bg && keep(bg.key))
|
|
2183
|
-
out.push({
|
|
2184
|
-
where: "background",
|
|
2185
|
-
key: bg.key,
|
|
2186
|
-
set: (next) => {
|
|
2187
|
-
bg.key = next;
|
|
2188
|
-
}
|
|
2189
|
-
});
|
|
2190
|
-
out.push(...docAudioRefs(doc, keep));
|
|
2191
|
-
return out;
|
|
2192
|
-
}
|
|
2193
|
-
function docAudioRefs(doc, keep = isTakeRelativeKey) {
|
|
2194
|
-
const out = [];
|
|
2195
|
-
for (const clip of doc.audio ?? []) {
|
|
2196
|
-
if (!keep(clip.key)) continue;
|
|
2197
|
-
out.push({
|
|
2198
|
-
where: `audio ${clip.id}`,
|
|
2199
|
-
key: clip.key,
|
|
2200
|
-
set: (next) => {
|
|
2201
|
-
clip.key = next;
|
|
2202
|
-
}
|
|
2203
|
-
});
|
|
2204
|
-
}
|
|
2205
|
-
return out;
|
|
2206
|
-
}
|
|
2207
|
-
async function uploadDocRefs(refs, dir, target, log) {
|
|
2208
|
-
for (const ref of refs) {
|
|
2209
|
-
const file = join2(dir, takeRelativeFile(ref.key));
|
|
2210
|
-
if (!existsSync2(file)) {
|
|
2211
|
-
log(` ${ref.where}: ${ref.key} is not beside the document \u2014 left as is`);
|
|
2212
|
-
continue;
|
|
2213
|
-
}
|
|
2214
|
-
const media = await readFile2(file);
|
|
2215
|
-
const mediaHash = createHash("sha256").update(media).digest("hex");
|
|
2216
|
-
const { type } = resolveMediaType({
|
|
2217
|
-
head: media.subarray(0, MEDIA_HEAD_BYTES),
|
|
2218
|
-
filename: file
|
|
2219
|
-
});
|
|
2220
|
-
const name = nameForType(basename2(file), type);
|
|
2221
|
-
if (name !== basename2(file)) {
|
|
2222
|
-
log(` ${ref.where}: ${basename2(file)} holds ${type} \u2014 sent as ${name}`);
|
|
2223
|
-
}
|
|
2224
|
-
let put;
|
|
2225
|
-
try {
|
|
2226
|
-
put = await uploadAsset(target, new Uint8Array(media), {
|
|
2227
|
-
filename: name,
|
|
2228
|
-
contentType: type,
|
|
2229
|
-
contentHash: mediaHash
|
|
2230
|
-
});
|
|
2231
|
-
} catch (e) {
|
|
2232
|
-
throw new Error(
|
|
2233
|
-
`${ref.where} upload failed: ${e instanceof Error ? e.message : String(e)}`
|
|
2234
|
-
);
|
|
2235
|
-
}
|
|
2236
|
-
ref.set(put.url);
|
|
2237
|
-
log(
|
|
2238
|
-
` ${ref.where}: ${ref.key} \u2192 asset ${put.id}${put.reused ? " (reused)" : ""}`
|
|
2239
|
-
);
|
|
2240
|
-
}
|
|
2241
|
-
}
|
|
2242
|
-
function assetIdOf(url) {
|
|
2243
|
-
if (!url) return null;
|
|
2244
|
-
const m = /\/api\/assets\/([A-Za-z0-9_-]+)\/file/.exec(url);
|
|
2245
|
-
return m ? m[1] : null;
|
|
2246
|
-
}
|
|
2247
|
-
async function readMediaHead(file) {
|
|
2248
|
-
const handle = await open(file, "r");
|
|
2249
|
-
try {
|
|
2250
|
-
const buf = Buffer.alloc(MEDIA_HEAD_BYTES);
|
|
2251
|
-
const { bytesRead } = await handle.read(buf, 0, MEDIA_HEAD_BYTES, 0);
|
|
2252
|
-
return new Uint8Array(buf.subarray(0, bytesRead));
|
|
2253
|
-
} finally {
|
|
2254
|
-
await handle.close();
|
|
2255
|
-
}
|
|
2256
|
-
}
|
|
2257
|
-
async function downloadMedia(ctx, url, target) {
|
|
2258
|
-
const abs = /^https?:/.test(url) ? url : `${ctx.origin}${url}`;
|
|
2259
|
-
const res = await fetch(abs, {
|
|
2260
|
-
headers: ctx.key ? { authorization: `Bearer ${ctx.key}` } : {}
|
|
2261
|
-
});
|
|
2262
|
-
if (!res.ok || !res.body) {
|
|
2263
|
-
throw new Error(
|
|
2264
|
-
`download of ${target.describe} failed (${res.status})${res.status === 404 && target.notFoundHint ? ` \u2014 ${target.notFoundHint}` : ""}`
|
|
2265
|
-
);
|
|
2266
|
-
}
|
|
2267
|
-
const into = target.subdir ? join2(target.dir, target.subdir) : target.dir;
|
|
2268
|
-
const partial = join2(into, `${target.stem}.part`);
|
|
2269
|
-
await mkdir2(dirname(partial), { recursive: true });
|
|
2270
|
-
try {
|
|
2271
|
-
await pipeline(
|
|
2272
|
-
Readable.fromWeb(res.body),
|
|
2273
|
-
createWriteStream(partial)
|
|
2274
|
-
);
|
|
2275
|
-
const { type } = resolveMediaType({
|
|
2276
|
-
head: await readMediaHead(partial),
|
|
2277
|
-
declared: res.headers.get("content-type"),
|
|
2278
|
-
filename: `${target.stem}${target.fallbackExt}`
|
|
2279
|
-
});
|
|
2280
|
-
const name = `${target.stem}${extensionFor(type) || target.fallbackExt}`;
|
|
2281
|
-
await rename2(partial, join2(into, name));
|
|
2282
|
-
const file = target.subdir ? `${target.subdir}/${name}` : name;
|
|
2283
|
-
return { file, bytes: (await stat(join2(target.dir, file))).size };
|
|
2284
|
-
} finally {
|
|
2285
|
-
await rm2(partial, { force: true });
|
|
2286
|
-
}
|
|
2287
|
-
}
|
|
2288
|
-
async function pullMedia(ctx, dir, doc, log) {
|
|
2289
|
-
const result = { downloaded: [], kept: [] };
|
|
2290
|
-
for (const { key, stem, fallbackExt } of SOURCE_MEDIA) {
|
|
2291
|
-
const url = doc.source[key];
|
|
2292
|
-
const assetId = assetIdOf(url);
|
|
2293
|
-
if (!assetId) continue;
|
|
2294
|
-
const present = takeMediaFiles(dir, stem)[0];
|
|
2295
|
-
if (present) {
|
|
2296
|
-
result.kept.push(basename2(present));
|
|
2297
|
-
doc.source[key] = basename2(present);
|
|
2298
|
-
continue;
|
|
2299
|
-
}
|
|
2300
|
-
const got = await downloadMedia(ctx, url, {
|
|
2301
|
-
dir,
|
|
2302
|
-
stem,
|
|
2303
|
-
fallbackExt,
|
|
2304
|
-
describe: stem,
|
|
2305
|
-
notFoundHint: "a private recording needs a content key of its owner"
|
|
2306
|
-
});
|
|
2307
|
-
result.downloaded.push({ file: got.file, assetId, bytes: got.bytes });
|
|
2308
|
-
log(` ${got.file} \u2190 asset ${assetId} (${Math.round(got.bytes / 1024)} kB)`);
|
|
2309
|
-
doc.source[key] = got.file;
|
|
2310
|
-
}
|
|
2311
|
-
for (const ref of docMediaRefs(doc, (k) => assetIdOf(k) !== null)) {
|
|
2312
|
-
const assetId = assetIdOf(ref.key);
|
|
2313
|
-
const existing = existsSync2(join2(dir, "media")) ? (await readdir(join2(dir, "media"))).find(
|
|
2314
|
-
(f) => f === assetId || f.startsWith(`${assetId}.`)
|
|
2315
|
-
) : void 0;
|
|
2316
|
-
if (existing) {
|
|
2317
|
-
const file = `media/${existing}`;
|
|
2318
|
-
result.kept.push(file);
|
|
2319
|
-
ref.set(file);
|
|
2320
|
-
continue;
|
|
2321
|
-
}
|
|
2322
|
-
const got = await downloadMedia(ctx, ref.key, {
|
|
2323
|
-
dir,
|
|
2324
|
-
subdir: "media",
|
|
2325
|
-
stem: assetId,
|
|
2326
|
-
// A type with no extension here (an svg served as such) keeps the bare
|
|
2327
|
-
// asset id, exactly as before.
|
|
2328
|
-
fallbackExt: "",
|
|
2329
|
-
describe: `${ref.where} (${ref.key})`
|
|
2330
|
-
});
|
|
2331
|
-
result.downloaded.push({ file: got.file, assetId, bytes: got.bytes });
|
|
2332
|
-
log(
|
|
2333
|
-
` ${got.file} \u2190 ${ref.where}, asset ${assetId} (${Math.round(got.bytes / 1024)} kB)`
|
|
2334
|
-
);
|
|
2335
|
-
ref.set(got.file);
|
|
2336
|
-
}
|
|
2337
|
-
const metaPath = join2(dir, "meta.json");
|
|
2338
|
-
if (!existsSync2(metaPath)) await writeJson(metaPath, doc.source.meta, true);
|
|
2339
|
-
const cursorPath = join2(dir, "cursor.json");
|
|
2340
|
-
if (!existsSync2(cursorPath)) await writeJson(cursorPath, doc.source.cursor);
|
|
2341
|
-
await writeFile2(join2(dir, "doc.json"), JSON.stringify(doc, null, 2));
|
|
2342
|
-
return result;
|
|
2343
|
-
}
|
|
2344
|
-
|
|
2345
1844
|
// src/plugin/mediaProbe.ts
|
|
2346
1845
|
var MEDIA_EXT = /\.(png|jpe?g|webp|gif|webm|mp4|mov|mp3|wav|m4a|ogg|glb|gltf|hdr|exr|woff2?)($|\?)/i;
|
|
2347
1846
|
var isRemote = (v) => typeof v === "string" && /^https?:\/\//i.test(v);
|
|
@@ -2427,9 +1926,9 @@ function mediaProbeLints(results) {
|
|
|
2427
1926
|
}
|
|
2428
1927
|
|
|
2429
1928
|
// src/plugin/framesTake.ts
|
|
2430
|
-
import { mkdir as
|
|
2431
|
-
import { existsSync as
|
|
2432
|
-
import { join as
|
|
1929
|
+
import { mkdir as mkdir2, readFile as readFile3, rename as rename2, writeFile as writeFile3 } from "fs/promises";
|
|
1930
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1931
|
+
import { join as join3, resolve as resolve2 } from "path";
|
|
2433
1932
|
import { compileVosConfig } from "@vosjs/core";
|
|
2434
1933
|
import {
|
|
2435
1934
|
docStillTime,
|
|
@@ -2443,9 +1942,9 @@ import {
|
|
|
2443
1942
|
|
|
2444
1943
|
// src/plugin/server.ts
|
|
2445
1944
|
import { createServer } from "http";
|
|
2446
|
-
import { existsSync
|
|
2447
|
-
import { writeFile
|
|
2448
|
-
import { extname, join
|
|
1945
|
+
import { existsSync, readFileSync, statSync } from "fs";
|
|
1946
|
+
import { writeFile } from "fs/promises";
|
|
1947
|
+
import { extname, join } from "path";
|
|
2449
1948
|
var MIME = {
|
|
2450
1949
|
".html": "text/html",
|
|
2451
1950
|
".js": "text/javascript",
|
|
@@ -2489,7 +1988,7 @@ function startTakeServer(rootDir, pages) {
|
|
|
2489
1988
|
const chunks = [];
|
|
2490
1989
|
req.on("data", (c) => chunks.push(c));
|
|
2491
1990
|
req.on("end", () => {
|
|
2492
|
-
void
|
|
1991
|
+
void writeFile(join(rootDir, name), Buffer.concat(chunks)).then(
|
|
2493
1992
|
() => res.writeHead(200).end("ok"),
|
|
2494
1993
|
(e) => res.writeHead(500).end(String(e))
|
|
2495
1994
|
);
|
|
@@ -2501,9 +2000,9 @@ function startTakeServer(rootDir, pages) {
|
|
|
2501
2000
|
res.end(pages[url.pathname]);
|
|
2502
2001
|
return;
|
|
2503
2002
|
}
|
|
2504
|
-
const file =
|
|
2505
|
-
if (
|
|
2506
|
-
const size =
|
|
2003
|
+
const file = join(rootDir, decodeURIComponent(url.pathname));
|
|
2004
|
+
if (existsSync(file) && statSync(file).isFile()) {
|
|
2005
|
+
const size = statSync(file).size;
|
|
2507
2006
|
const buf = readFileSync(file);
|
|
2508
2007
|
const type = sniffMediaType(buf.subarray(0, MEDIA_HEAD_BYTES)) ?? MIME[extname(file)] ?? "application/octet-stream";
|
|
2509
2008
|
const range = req.headers.range;
|
|
@@ -2568,7 +2067,7 @@ async function waitForPageDone(page, label, onProgress, timeoutMs) {
|
|
|
2568
2067
|
}
|
|
2569
2068
|
|
|
2570
2069
|
// src/plugin/renderAnimation.ts
|
|
2571
|
-
import { readFile
|
|
2070
|
+
import { readFile, rm } from "fs/promises";
|
|
2572
2071
|
import { tweenRuntimeCode } from "@vosjs/tween/bundle";
|
|
2573
2072
|
import { elementsBundleCode } from "@vosjs/elements/bundle";
|
|
2574
2073
|
import { concatEncodedVideo, planChunks } from "@vosjs/render-core";
|
|
@@ -2863,10 +2362,10 @@ async function renderAnimation(browser, opts) {
|
|
|
2863
2362
|
)
|
|
2864
2363
|
);
|
|
2865
2364
|
const files = await Promise.all(
|
|
2866
|
-
outNames.map((name) =>
|
|
2365
|
+
outNames.map((name) => readFile(`${opts.workDir}/${name}`))
|
|
2867
2366
|
);
|
|
2868
2367
|
await Promise.all(
|
|
2869
|
-
outNames.map((name) =>
|
|
2368
|
+
outNames.map((name) => rm(`${opts.workDir}/${name}`, { force: true }))
|
|
2870
2369
|
);
|
|
2871
2370
|
if (chunks.length === 1) {
|
|
2872
2371
|
return { bytes: new Uint8Array(files[0]), totalFrames, chunks: 1 };
|
|
@@ -3121,9 +2620,9 @@ function applyAndValidate(doc, o) {
|
|
|
3121
2620
|
}
|
|
3122
2621
|
|
|
3123
2622
|
// src/plugin/digestTake.ts
|
|
3124
|
-
import { mkdir
|
|
3125
|
-
import { existsSync as
|
|
3126
|
-
import { join as
|
|
2623
|
+
import { mkdir, readFile as readFile2, rename, writeFile as writeFile2 } from "fs/promises";
|
|
2624
|
+
import { existsSync as existsSync2 } from "fs";
|
|
2625
|
+
import { join as join2, resolve } from "path";
|
|
3127
2626
|
import {
|
|
3128
2627
|
DIGEST_CROP_MAX,
|
|
3129
2628
|
DIGEST_FULL_MAX,
|
|
@@ -3345,8 +2844,8 @@ async function digestTake(browser, dir, opts = {}) {
|
|
|
3345
2844
|
const meta = doc.source.meta;
|
|
3346
2845
|
let plan = planForDigest(doc);
|
|
3347
2846
|
const transcript = opts.transcript ?? await discoverTranscript(dir);
|
|
3348
|
-
const outDir = resolve(opts.outDir ??
|
|
3349
|
-
await
|
|
2847
|
+
const outDir = resolve(opts.outDir ?? join2(dir, DIGEST_DIR));
|
|
2848
|
+
await mkdir(outDir, { recursive: true });
|
|
3350
2849
|
const fullMax = opts.full ?? DIGEST_FULL_MAX;
|
|
3351
2850
|
const cropMax = opts.crop ?? DIGEST_CROP_MAX;
|
|
3352
2851
|
const wantFrames = opts.frames !== false && browser !== null;
|
|
@@ -3362,7 +2861,7 @@ async function digestTake(browser, dir, opts = {}) {
|
|
|
3362
2861
|
kind: "video",
|
|
3363
2862
|
url: `/${take.paths.recordingName}`
|
|
3364
2863
|
};
|
|
3365
|
-
if (
|
|
2864
|
+
if (existsSync2(take.paths.framesIndex) && existsSync2(take.paths.framesDir)) {
|
|
3366
2865
|
const index = await readJson(
|
|
3367
2866
|
take.paths.framesIndex
|
|
3368
2867
|
);
|
|
@@ -3377,7 +2876,7 @@ async function digestTake(browser, dir, opts = {}) {
|
|
|
3377
2876
|
}
|
|
3378
2877
|
}
|
|
3379
2878
|
if (wantFrames) {
|
|
3380
|
-
if (source.kind === "video" && !
|
|
2879
|
+
if (source.kind === "video" && !existsSync2(take.paths.recording))
|
|
3381
2880
|
throw new Error(
|
|
3382
2881
|
`${dir} has no recording \u2014 pull it with \`vos pull --media\` first`
|
|
3383
2882
|
);
|
|
@@ -3450,9 +2949,9 @@ async function digestTake(browser, dir, opts = {}) {
|
|
|
3450
2949
|
);
|
|
3451
2950
|
sizes = f.sizes;
|
|
3452
2951
|
for (const name of Object.keys(sizes)) {
|
|
3453
|
-
const from =
|
|
3454
|
-
const to =
|
|
3455
|
-
if (from !== to &&
|
|
2952
|
+
const from = join2(dir, name);
|
|
2953
|
+
const to = join2(outDir, name);
|
|
2954
|
+
if (from !== to && existsSync2(from)) await rename(from, to);
|
|
3456
2955
|
}
|
|
3457
2956
|
if (sizes["sheet.png"]) sheet = "sheet.png";
|
|
3458
2957
|
} finally {
|
|
@@ -3478,12 +2977,12 @@ async function digestTake(browser, dir, opts = {}) {
|
|
|
3478
2977
|
if (!p) continue;
|
|
3479
2978
|
for (const f of [p.full, p.crop]) {
|
|
3480
2979
|
if (!f) continue;
|
|
3481
|
-
const size = await pngSize(
|
|
2980
|
+
const size = await pngSize(join2(outDir, f));
|
|
3482
2981
|
if (size) sizes[f] = size;
|
|
3483
2982
|
}
|
|
3484
2983
|
if (p.box) boxes.set(m.id, p.box);
|
|
3485
2984
|
}
|
|
3486
|
-
if (prior.images.sheet &&
|
|
2985
|
+
if (prior.images.sheet && existsSync2(join2(outDir, prior.images.sheet)))
|
|
3487
2986
|
sheet = prior.images.sheet;
|
|
3488
2987
|
}
|
|
3489
2988
|
}
|
|
@@ -3512,16 +3011,16 @@ async function digestTake(browser, dir, opts = {}) {
|
|
|
3512
3011
|
style: opts.style ?? null,
|
|
3513
3012
|
transcript
|
|
3514
3013
|
});
|
|
3515
|
-
const file =
|
|
3014
|
+
const file = join2(outDir, "digest.json");
|
|
3516
3015
|
const body = JSON.stringify(digest, null, 2);
|
|
3517
|
-
await
|
|
3016
|
+
await writeFile2(file, body);
|
|
3518
3017
|
return { file, outDir, digest, bytes: Buffer.byteLength(body) };
|
|
3519
3018
|
}
|
|
3520
3019
|
async function discoverTranscript(dir) {
|
|
3521
|
-
const file =
|
|
3522
|
-
if (!
|
|
3020
|
+
const file = join2(dir, "transcript.json");
|
|
3021
|
+
if (!existsSync2(file)) return null;
|
|
3523
3022
|
try {
|
|
3524
|
-
return parseTranscript(JSON.parse(await
|
|
3023
|
+
return parseTranscript(JSON.parse(await readFile2(file, "utf8")));
|
|
3525
3024
|
} catch {
|
|
3526
3025
|
return null;
|
|
3527
3026
|
}
|
|
@@ -3529,7 +3028,7 @@ async function discoverTranscript(dir) {
|
|
|
3529
3028
|
async function readPriorDigest(outDir) {
|
|
3530
3029
|
try {
|
|
3531
3030
|
const raw = JSON.parse(
|
|
3532
|
-
await
|
|
3031
|
+
await readFile2(join2(outDir, "digest.json"), "utf8")
|
|
3533
3032
|
);
|
|
3534
3033
|
return raw.digestVersion === DIGEST_VERSION && Array.isArray(raw.moments) ? raw : null;
|
|
3535
3034
|
} catch {
|
|
@@ -3538,7 +3037,7 @@ async function readPriorDigest(outDir) {
|
|
|
3538
3037
|
}
|
|
3539
3038
|
async function pngSize(file) {
|
|
3540
3039
|
try {
|
|
3541
|
-
const buf = await
|
|
3040
|
+
const buf = await readFile2(file);
|
|
3542
3041
|
if (buf.length < 24 || buf.readUInt32BE(0) !== 2303741511) return null;
|
|
3543
3042
|
return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20) };
|
|
3544
3043
|
} catch {
|
|
@@ -3713,8 +3212,8 @@ async function framesTake(browser, dir, opts) {
|
|
|
3713
3212
|
throw new Error(
|
|
3714
3213
|
"no frame times \u2014 pass --times/--frame, --at-zooms, --at-moments or --at-still"
|
|
3715
3214
|
);
|
|
3716
|
-
const outDir = resolve2(opts.outDir ??
|
|
3717
|
-
await
|
|
3215
|
+
const outDir = resolve2(opts.outDir ?? join3(dir, "stills"));
|
|
3216
|
+
await mkdir2(outDir, { recursive: true });
|
|
3718
3217
|
const named = shots.map((s, i) => ({
|
|
3719
3218
|
...s,
|
|
3720
3219
|
name: s.kind === "moment" ? `moment-${s.momentId}-${s.time.toFixed(2)}s.png` : `${s.kind === "zoom" ? "zoom" : "frame"}-${String(i).padStart(2, "0")}-${s.time.toFixed(2)}s.png`
|
|
@@ -3758,9 +3257,9 @@ async function framesTake(browser, dir, opts) {
|
|
|
3758
3257
|
);
|
|
3759
3258
|
const frames = [];
|
|
3760
3259
|
for (const n of named) {
|
|
3761
|
-
const from =
|
|
3762
|
-
const to =
|
|
3763
|
-
if (from !== to) await
|
|
3260
|
+
const from = join3(dir, n.name);
|
|
3261
|
+
const to = join3(outDir, n.name);
|
|
3262
|
+
if (from !== to) await rename2(from, to);
|
|
3764
3263
|
frames.push({
|
|
3765
3264
|
file: to,
|
|
3766
3265
|
time: n.time,
|
|
@@ -3775,10 +3274,10 @@ async function framesTake(browser, dir, opts) {
|
|
|
3775
3274
|
}
|
|
3776
3275
|
}
|
|
3777
3276
|
async function momentsFor(dir, doc) {
|
|
3778
|
-
const file =
|
|
3779
|
-
if (
|
|
3277
|
+
const file = join3(dir, DIGEST_DIR, "digest.json");
|
|
3278
|
+
if (existsSync3(file)) {
|
|
3780
3279
|
try {
|
|
3781
|
-
const d = JSON.parse(await
|
|
3280
|
+
const d = JSON.parse(await readFile3(file, "utf8"));
|
|
3782
3281
|
if (Array.isArray(d.moments)) return d.moments;
|
|
3783
3282
|
} catch {
|
|
3784
3283
|
}
|
|
@@ -3798,8 +3297,8 @@ function parseTimes2(spec, duration) {
|
|
|
3798
3297
|
});
|
|
3799
3298
|
}
|
|
3800
3299
|
async function writeIndexJson(result) {
|
|
3801
|
-
const index =
|
|
3802
|
-
await
|
|
3300
|
+
const index = join3(result.outDir, "stills.json");
|
|
3301
|
+
await writeFile3(
|
|
3803
3302
|
index,
|
|
3804
3303
|
JSON.stringify(
|
|
3805
3304
|
{
|
|
@@ -3816,11 +3315,11 @@ async function writeIndexJson(result) {
|
|
|
3816
3315
|
}
|
|
3817
3316
|
|
|
3818
3317
|
// src/plugin/deliver.ts
|
|
3819
|
-
import { mkdir as
|
|
3318
|
+
import { mkdir as mkdir3, mkdtemp, rename as rename3, rm as rm2, stat, writeFile as writeFile5 } from "fs/promises";
|
|
3820
3319
|
import { tmpdir } from "os";
|
|
3821
|
-
import { join as
|
|
3822
|
-
import { existsSync as
|
|
3823
|
-
import { readFile as
|
|
3320
|
+
import { join as join5, relative, resolve as resolve4 } from "path";
|
|
3321
|
+
import { existsSync as existsSync5 } from "fs";
|
|
3322
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
3824
3323
|
import { parseFrontmatter } from "@vosjs/shared/frontmatter";
|
|
3825
3324
|
import {
|
|
3826
3325
|
CHANNEL_SPECS_VERIFIED,
|
|
@@ -3834,7 +3333,7 @@ import {
|
|
|
3834
3333
|
} from "@vosjs/studio-core";
|
|
3835
3334
|
|
|
3836
3335
|
// src/plugin/renderTake.ts
|
|
3837
|
-
import { writeFile as
|
|
3336
|
+
import { writeFile as writeFile4 } from "fs/promises";
|
|
3838
3337
|
import { compileVosConfig as compileVosConfig2 } from "@vosjs/core";
|
|
3839
3338
|
import {
|
|
3840
3339
|
audioProducerCode,
|
|
@@ -3923,7 +3422,7 @@ async function renderTake(browser, dir, outFile, opts) {
|
|
|
3923
3422
|
} : void 0,
|
|
3924
3423
|
onProgress: opts.onProgress
|
|
3925
3424
|
});
|
|
3926
|
-
await
|
|
3425
|
+
await writeFile4(outFile, result.bytes);
|
|
3927
3426
|
return {
|
|
3928
3427
|
out: outFile,
|
|
3929
3428
|
bytes: result.bytes.length,
|
|
@@ -4478,9 +3977,9 @@ function proposeTransitions(input, kind) {
|
|
|
4478
3977
|
for (const step of steps) {
|
|
4479
3978
|
if (!step.navigated || step.skipped) continue;
|
|
4480
3979
|
const name = step.id ?? String(step.step);
|
|
4481
|
-
const
|
|
4482
|
-
const outEnd = round3(
|
|
4483
|
-
const inStart = round3(
|
|
3980
|
+
const wait = step.do === "wait";
|
|
3981
|
+
const outEnd = round3(wait ? step.tStart : step.tEnd);
|
|
3982
|
+
const inStart = round3(wait ? step.tEnd : step.tEnd + STEP_SETTLE_SECONDS);
|
|
4484
3983
|
const segs = doc.segments;
|
|
4485
3984
|
if (segs.some(
|
|
4486
3985
|
(s) => !s.media && Math.abs(s.out - outEnd) < 0.05 && s !== segs.at(-1)
|
|
@@ -4799,9 +4298,9 @@ function isLightHexGround(hex2) {
|
|
|
4799
4298
|
}
|
|
4800
4299
|
|
|
4801
4300
|
// src/plugin/posterDoc.ts
|
|
4802
|
-
import { existsSync as
|
|
4803
|
-
import { readFile as
|
|
4804
|
-
import { isAbsolute, join as
|
|
4301
|
+
import { existsSync as existsSync4 } from "fs";
|
|
4302
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
4303
|
+
import { isAbsolute, join as join4, resolve as resolve3 } from "path";
|
|
4805
4304
|
import {
|
|
4806
4305
|
computeCardLayout,
|
|
4807
4306
|
docStillTime as docStillTime2,
|
|
@@ -4828,16 +4327,16 @@ var roleFor = (cls) => `poster-${cls}`;
|
|
|
4828
4327
|
async function readPosterAt(cls, path, takeDir, from) {
|
|
4829
4328
|
let file = path;
|
|
4830
4329
|
let docDir = path;
|
|
4831
|
-
if (
|
|
4832
|
-
file =
|
|
4833
|
-
} else if (!/\.json$/i.test(path) || !
|
|
4330
|
+
if (existsSync4(join4(path, "doc.json"))) {
|
|
4331
|
+
file = join4(path, "doc.json");
|
|
4332
|
+
} else if (!/\.json$/i.test(path) || !existsSync4(path)) {
|
|
4834
4333
|
return null;
|
|
4835
4334
|
} else {
|
|
4836
4335
|
docDir = resolve3(path, "..");
|
|
4837
4336
|
}
|
|
4838
4337
|
let raw;
|
|
4839
4338
|
try {
|
|
4840
|
-
raw = JSON.parse(await
|
|
4339
|
+
raw = JSON.parse(await readFile4(file, "utf8"));
|
|
4841
4340
|
} catch {
|
|
4842
4341
|
return null;
|
|
4843
4342
|
}
|
|
@@ -4847,7 +4346,7 @@ async function readPosterAt(cls, path, takeDir, from) {
|
|
|
4847
4346
|
const doc = migrateHostedDoc(
|
|
4848
4347
|
raw
|
|
4849
4348
|
);
|
|
4850
|
-
const ownFootage =
|
|
4349
|
+
const ownFootage = existsSync4(join4(docDir, "meta.json")) && existsSync4(join4(docDir, source.videoKey));
|
|
4851
4350
|
const sync = readSyncState(docDir);
|
|
4852
4351
|
return {
|
|
4853
4352
|
cls,
|
|
@@ -4870,10 +4369,10 @@ async function findPosterDocs(takeDir, launchRoles) {
|
|
|
4870
4369
|
candidates.push({ path: at2(named.trim()), from: `LAUNCH.md ${role}` });
|
|
4871
4370
|
}
|
|
4872
4371
|
candidates.push({
|
|
4873
|
-
path:
|
|
4372
|
+
path: join4(takeDir, "poster", cls),
|
|
4874
4373
|
from: `poster/${cls}`
|
|
4875
4374
|
});
|
|
4876
|
-
candidates.push({ path:
|
|
4375
|
+
candidates.push({ path: join4(takeDir, "poster"), from: "poster" });
|
|
4877
4376
|
for (const c of candidates) {
|
|
4878
4377
|
const ref = await readPosterAt(cls, c.path, takeDir, c.from);
|
|
4879
4378
|
if (ref) {
|
|
@@ -4969,19 +4468,19 @@ function resolveChannels(raw) {
|
|
|
4969
4468
|
return out;
|
|
4970
4469
|
}
|
|
4971
4470
|
async function readBrandBesideTake(dir, explicit) {
|
|
4972
|
-
const candidates = explicit ? [explicit] : [
|
|
4471
|
+
const candidates = explicit ? [explicit] : [join5(dir, "BRAND.md"), join5(dir, "..", "BRAND.md")];
|
|
4973
4472
|
for (const file of candidates) {
|
|
4974
|
-
if (!
|
|
4975
|
-
const roles = parseFrontmatter(await
|
|
4473
|
+
if (!existsSync5(file)) continue;
|
|
4474
|
+
const roles = parseFrontmatter(await readFile5(file, "utf8"));
|
|
4976
4475
|
return { file, roles };
|
|
4977
4476
|
}
|
|
4978
4477
|
return null;
|
|
4979
4478
|
}
|
|
4980
4479
|
async function readLaunchBesideTake(dir, explicit) {
|
|
4981
|
-
const candidates = explicit ? [explicit] : [
|
|
4480
|
+
const candidates = explicit ? [explicit] : [join5(dir, "LAUNCH.md"), join5(dir, "..", "LAUNCH.md")];
|
|
4982
4481
|
for (const file of candidates) {
|
|
4983
|
-
if (!
|
|
4984
|
-
return { file, roles: parseFrontmatter(await
|
|
4482
|
+
if (!existsSync5(file)) continue;
|
|
4483
|
+
return { file, roles: parseFrontmatter(await readFile5(file, "utf8")) };
|
|
4985
4484
|
}
|
|
4986
4485
|
return null;
|
|
4987
4486
|
}
|
|
@@ -5045,7 +4544,7 @@ async function pickStillTimes(browser, dir, doc, duration, opts) {
|
|
|
5045
4544
|
const meta = doc.source.meta;
|
|
5046
4545
|
const vw = meta.captureWidth ?? meta.width;
|
|
5047
4546
|
const vh = meta.captureHeight ?? meta.height;
|
|
5048
|
-
const probeDir = await mkdtemp(
|
|
4547
|
+
const probeDir = await mkdtemp(join5(tmpdir(), "vos-moments-"));
|
|
5049
4548
|
try {
|
|
5050
4549
|
const probe3 = await framesTake(browser, dir, {
|
|
5051
4550
|
times: candidates.map((c) => c.time),
|
|
@@ -5059,7 +4558,7 @@ async function pickStillTimes(browser, dir, doc, duration, opts) {
|
|
|
5059
4558
|
});
|
|
5060
4559
|
const byTime = /* @__PURE__ */ new Map();
|
|
5061
4560
|
for (const frame of probe3.frames) {
|
|
5062
|
-
const img = decodePng(new Uint8Array(await
|
|
4561
|
+
const img = decodePng(new Uint8Array(await readFile5(frame.file)));
|
|
5063
4562
|
if (!img) continue;
|
|
5064
4563
|
const whole = { x: 0, y: 0, w: img.w, h: img.h };
|
|
5065
4564
|
byTime.set(+frame.time.toFixed(3), {
|
|
@@ -5084,7 +4583,7 @@ async function pickStillTimes(browser, dir, doc, duration, opts) {
|
|
|
5084
4583
|
notes: [...notes, ...pick.dropped.map((d) => `moment: ${d}`)]
|
|
5085
4584
|
};
|
|
5086
4585
|
} finally {
|
|
5087
|
-
await
|
|
4586
|
+
await rm2(probeDir, { recursive: true, force: true });
|
|
5088
4587
|
}
|
|
5089
4588
|
}
|
|
5090
4589
|
var overCeiling = (bytes, maxBytes) => `${Math.ceil(bytes / 1024)} KB exceeds the ${Math.floor(maxBytes / 1024)} KB ceiling`;
|
|
@@ -5132,8 +4631,8 @@ async function deliverTake(browser, dir, opts) {
|
|
|
5132
4631
|
const doc = take.doc;
|
|
5133
4632
|
const duration = docOutputDuration2(doc);
|
|
5134
4633
|
const videoSeconds = opts.range ? Math.min(opts.range[1], duration) - Math.min(opts.range[0], duration) : duration;
|
|
5135
|
-
const outDir = resolve4(opts.outDir ??
|
|
5136
|
-
await
|
|
4634
|
+
const outDir = resolve4(opts.outDir ?? join5(dir, "kit"));
|
|
4635
|
+
await mkdir3(outDir, { recursive: true });
|
|
5137
4636
|
const destinations = opts.channels.flatMap((c) => destinationsForChannel(c));
|
|
5138
4637
|
const assets = [];
|
|
5139
4638
|
const skipped = [];
|
|
@@ -5193,7 +4692,7 @@ async function deliverTake(browser, dir, opts) {
|
|
|
5193
4692
|
opts.onPhase?.(
|
|
5194
4693
|
`${label} (${specWords(d)}) from ${ref.from}${ref.vosId ? ` ${ref.vosId}` : ""}, the rest at ${time.toFixed(2)}s`
|
|
5195
4694
|
);
|
|
5196
|
-
const shotDir = await mkdtemp(
|
|
4695
|
+
const shotDir = await mkdtemp(join5(tmpdir(), "vos-poster-"));
|
|
5197
4696
|
try {
|
|
5198
4697
|
const captured = await framesTake(browser, ref.takeDir, {
|
|
5199
4698
|
times: [time],
|
|
@@ -5203,9 +4702,9 @@ async function deliverTake(browser, dir, opts) {
|
|
|
5203
4702
|
doc: ref.doc,
|
|
5204
4703
|
overrides: opts.overrides
|
|
5205
4704
|
});
|
|
5206
|
-
const to =
|
|
5207
|
-
await
|
|
5208
|
-
const bytes = (await
|
|
4705
|
+
const to = join5(outDir, `${d.id}.png`);
|
|
4706
|
+
await rename3(captured.frames[0].file, to);
|
|
4707
|
+
const bytes = (await stat(to)).size;
|
|
5209
4708
|
if (d.maxBytes !== void 0 && bytes > d.maxBytes) {
|
|
5210
4709
|
skipped.push(
|
|
5211
4710
|
`${label}: ${overCeiling(bytes, d.maxBytes)} (kept at ${to})`
|
|
@@ -5234,7 +4733,7 @@ async function deliverTake(browser, dir, opts) {
|
|
|
5234
4733
|
...shot.x < 0 && shot.x + shot.w > 1 ? { crop: true } : {}
|
|
5235
4734
|
});
|
|
5236
4735
|
} finally {
|
|
5237
|
-
await
|
|
4736
|
+
await rm2(shotDir, { recursive: true, force: true });
|
|
5238
4737
|
}
|
|
5239
4738
|
}
|
|
5240
4739
|
if (missing.size) {
|
|
@@ -5275,7 +4774,7 @@ async function deliverTake(browser, dir, opts) {
|
|
|
5275
4774
|
}
|
|
5276
4775
|
}
|
|
5277
4776
|
opts.onPhase?.(`${label} (${specWords(d)})`);
|
|
5278
|
-
const outFile =
|
|
4777
|
+
const outFile = join5(outDir, `${d.id}.${d.format}`);
|
|
5279
4778
|
const bitrate = d.maxBytes !== void 0 ? Math.min(
|
|
5280
4779
|
1e7,
|
|
5281
4780
|
Math.floor(d.maxBytes * 8 / seconds * 0.85)
|
|
@@ -5322,9 +4821,9 @@ async function deliverTake(browser, dir, opts) {
|
|
|
5322
4821
|
for (let i = 0; i < captured.frames.length; i++) {
|
|
5323
4822
|
const frame = captured.frames[i];
|
|
5324
4823
|
const name = captured.frames.length > 1 ? `${d.id}-${i + 1}.png` : `${d.id}.png`;
|
|
5325
|
-
const to =
|
|
5326
|
-
await
|
|
5327
|
-
const bytes = (await
|
|
4824
|
+
const to = join5(outDir, name);
|
|
4825
|
+
await rename3(frame.file, to);
|
|
4826
|
+
const bytes = (await stat(to)).size;
|
|
5328
4827
|
if (d.maxBytes !== void 0 && bytes > d.maxBytes) {
|
|
5329
4828
|
skipped.push(
|
|
5330
4829
|
`${label}: ${overCeiling(bytes, d.maxBytes)} (kept at ${to})`
|
|
@@ -5360,15 +4859,15 @@ async function deliverTake(browser, dir, opts) {
|
|
|
5360
4859
|
skipped,
|
|
5361
4860
|
assets
|
|
5362
4861
|
};
|
|
5363
|
-
const kitFile =
|
|
5364
|
-
await
|
|
4862
|
+
const kitFile = join5(outDir, "kit.json");
|
|
4863
|
+
await writeFile5(kitFile, JSON.stringify(kit, null, 2));
|
|
5365
4864
|
return { kit, kitFile, outDir };
|
|
5366
4865
|
}
|
|
5367
4866
|
|
|
5368
4867
|
// src/plugin/markAsset.ts
|
|
5369
|
-
import { mkdir as
|
|
5370
|
-
import { existsSync as
|
|
5371
|
-
import { isAbsolute as isAbsolute2, join as
|
|
4868
|
+
import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
|
|
4869
|
+
import { existsSync as existsSync6 } from "fs";
|
|
4870
|
+
import { isAbsolute as isAbsolute2, join as join6, resolve as resolve5 } from "path";
|
|
5372
4871
|
function markExtension(url) {
|
|
5373
4872
|
const m = /\.(svg|png|webp|jpe?g)(?:[?#]|$)/i.exec(url);
|
|
5374
4873
|
if (!m) return null;
|
|
@@ -5450,8 +4949,8 @@ async function fetchBrandMarks(takeDir, roles) {
|
|
|
5450
4949
|
bytes = new Uint8Array(await res.arrayBuffer());
|
|
5451
4950
|
} else {
|
|
5452
4951
|
const file = isAbsolute2(src) ? src : resolve5(takeDir, src);
|
|
5453
|
-
if (!
|
|
5454
|
-
bytes = new Uint8Array(await
|
|
4952
|
+
if (!existsSync6(file)) throw new Error("no such file");
|
|
4953
|
+
bytes = new Uint8Array(await readFile6(file));
|
|
5455
4954
|
}
|
|
5456
4955
|
} catch (e) {
|
|
5457
4956
|
out.notes.push(
|
|
@@ -5466,8 +4965,8 @@ async function fetchBrandMarks(takeDir, roles) {
|
|
|
5466
4965
|
);
|
|
5467
4966
|
return null;
|
|
5468
4967
|
}
|
|
5469
|
-
await
|
|
5470
|
-
await
|
|
4968
|
+
await mkdir4(join6(takeDir, "brand"), { recursive: true });
|
|
4969
|
+
await writeFile6(join6(takeDir, "brand", `${name}.${ext}`), bytes);
|
|
5471
4970
|
return { key: `/brand/${name}.${ext}`, aspect };
|
|
5472
4971
|
};
|
|
5473
4972
|
out.light = await one("logoUrl", "mark");
|
|
@@ -5503,9 +5002,9 @@ async function fetchMusicCatalog(origin) {
|
|
|
5503
5002
|
}
|
|
5504
5003
|
|
|
5505
5004
|
// src/plugin/judge.ts
|
|
5506
|
-
import { existsSync as
|
|
5507
|
-
import { mkdir as
|
|
5508
|
-
import { dirname
|
|
5005
|
+
import { existsSync as existsSync7 } from "fs";
|
|
5006
|
+
import { mkdir as mkdir5, readFile as readFile7, writeFile as writeFile7 } from "fs/promises";
|
|
5007
|
+
import { dirname, isAbsolute as isAbsolute3, join as join7, resolve as resolve6 } from "path";
|
|
5509
5008
|
import { DESTINATIONS as DESTINATIONS2 } from "@vosjs/studio-core";
|
|
5510
5009
|
|
|
5511
5010
|
// src/plugin/shotBake.ts
|
|
@@ -5676,28 +5175,28 @@ function composeSheet(left, right, height = 540, gutter = 32) {
|
|
|
5676
5175
|
return { w, h, data: out };
|
|
5677
5176
|
}
|
|
5678
5177
|
async function judgeKit(kitPath, manifestPath, outDir) {
|
|
5679
|
-
const kit = JSON.parse(await
|
|
5178
|
+
const kit = JSON.parse(await readFile7(kitPath, "utf8"));
|
|
5680
5179
|
const manifest = JSON.parse(
|
|
5681
|
-
await
|
|
5180
|
+
await readFile7(manifestPath, "utf8")
|
|
5682
5181
|
);
|
|
5683
|
-
const manifestDir =
|
|
5684
|
-
const kitDir =
|
|
5685
|
-
const out = resolve6(outDir ??
|
|
5686
|
-
await
|
|
5182
|
+
const manifestDir = dirname(resolve6(manifestPath));
|
|
5183
|
+
const kitDir = dirname(resolve6(kitPath));
|
|
5184
|
+
const out = resolve6(outDir ?? join7(kitDir, "judge"));
|
|
5185
|
+
await mkdir5(out, { recursive: true });
|
|
5687
5186
|
const sheets = [];
|
|
5688
5187
|
const skipped = [];
|
|
5689
5188
|
const refCache = /* @__PURE__ */ new Map();
|
|
5690
5189
|
const loadRef = async (file) => {
|
|
5691
5190
|
if (refCache.has(file)) return refCache.get(file) ?? null;
|
|
5692
|
-
const p =
|
|
5693
|
-
const img =
|
|
5191
|
+
const p = join7(manifestDir, file);
|
|
5192
|
+
const img = existsSync7(p) ? decodePng(new Uint8Array(await readFile7(p))) : null;
|
|
5694
5193
|
refCache.set(file, img);
|
|
5695
5194
|
return img;
|
|
5696
5195
|
};
|
|
5697
5196
|
for (const a of kit.assets) {
|
|
5698
5197
|
if (!/\.png$/i.test(a.path)) continue;
|
|
5699
|
-
const file = isAbsolute3(a.path) &&
|
|
5700
|
-
const img =
|
|
5198
|
+
const file = isAbsolute3(a.path) && existsSync7(a.path) ? a.path : join7(kitDir, a.path.split("/").pop() ?? a.path);
|
|
5199
|
+
const img = existsSync7(file) ? decodePng(new Uint8Array(await readFile7(file))) : null;
|
|
5701
5200
|
if (!img) {
|
|
5702
5201
|
skipped.push(`${a.destination ?? a.path}: unreadable`);
|
|
5703
5202
|
continue;
|
|
@@ -5728,8 +5227,8 @@ async function judgeKit(kitPath, manifestPath, outDir) {
|
|
|
5728
5227
|
const id = stem.replace(/[^A-Za-z0-9_-]+/g, "-") || a.destination || "asset";
|
|
5729
5228
|
const nameA = `${id}--A.png`;
|
|
5730
5229
|
const nameB = `${id}--B.png`;
|
|
5731
|
-
await
|
|
5732
|
-
await
|
|
5230
|
+
await writeFile7(join7(out, nameA), encodePng(composeSheet(img, refImg)));
|
|
5231
|
+
await writeFile7(join7(out, nameB), encodePng(composeSheet(refImg, img)));
|
|
5733
5232
|
const rubric = [
|
|
5734
5233
|
`# ${id} against ${ref.id} (${ref.file})`,
|
|
5735
5234
|
"",
|
|
@@ -5749,7 +5248,7 @@ async function judgeKit(kitPath, manifestPath, outDir) {
|
|
|
5749
5248
|
"In judge.json: `win` true when the kit asset is at least as good as the reference on the rules that apply to its genre, false when it is worse, null for a tie; `reasons` names the rules by number.",
|
|
5750
5249
|
""
|
|
5751
5250
|
].join("\n");
|
|
5752
|
-
await
|
|
5251
|
+
await writeFile7(join7(out, `${id}.md`), rubric);
|
|
5753
5252
|
sheets.push({
|
|
5754
5253
|
asset: id,
|
|
5755
5254
|
reference: ref.id,
|
|
@@ -5758,9 +5257,9 @@ async function judgeKit(kitPath, manifestPath, outDir) {
|
|
|
5758
5257
|
rubric: `${id}.md`
|
|
5759
5258
|
});
|
|
5760
5259
|
}
|
|
5761
|
-
const verdictFile =
|
|
5762
|
-
if (!
|
|
5763
|
-
await
|
|
5260
|
+
const verdictFile = join7(out, "judge.json");
|
|
5261
|
+
if (!existsSync7(verdictFile)) {
|
|
5262
|
+
await writeFile7(
|
|
5764
5263
|
verdictFile,
|
|
5765
5264
|
JSON.stringify(
|
|
5766
5265
|
{
|
|
@@ -5811,9 +5310,9 @@ function catalogFamily(family) {
|
|
|
5811
5310
|
}
|
|
5812
5311
|
|
|
5813
5312
|
// src/plugin/callout.ts
|
|
5814
|
-
import { existsSync as
|
|
5815
|
-
import { readFile as
|
|
5816
|
-
import { join as
|
|
5313
|
+
import { existsSync as existsSync8 } from "fs";
|
|
5314
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
5315
|
+
import { join as join8 } from "path";
|
|
5817
5316
|
import { lerpArray, sample } from "@vosjs/timeline";
|
|
5818
5317
|
import {
|
|
5819
5318
|
CALLOUT_DEFAULT_BODY_PX,
|
|
@@ -6010,13 +5509,13 @@ async function footageFrames(browser, dir, doc, times, framesTake2) {
|
|
|
6010
5509
|
const out = /* @__PURE__ */ new Map();
|
|
6011
5510
|
if (!times.length) return out;
|
|
6012
5511
|
const bare = { ...doc, overlays: [] };
|
|
6013
|
-
const outDir =
|
|
5512
|
+
const outDir = join8(dir, ".ground-check");
|
|
6014
5513
|
const res = await framesTake2(browser, dir, { times, outDir, doc: bare });
|
|
6015
5514
|
for (const f of res.frames) {
|
|
6016
|
-
if (!
|
|
5515
|
+
if (!existsSync8(f.file)) continue;
|
|
6017
5516
|
out.set(
|
|
6018
5517
|
+f.time.toFixed(3),
|
|
6019
|
-
decodePng(new Uint8Array(await
|
|
5518
|
+
decodePng(new Uint8Array(await readFile8(f.file)))
|
|
6020
5519
|
);
|
|
6021
5520
|
}
|
|
6022
5521
|
return out;
|
|
@@ -6025,7 +5524,7 @@ async function footageFrames(browser, dir, doc, times, framesTake2) {
|
|
|
6025
5524
|
// src/plugin/kitPicture.ts
|
|
6026
5525
|
import { execFile } from "child_process";
|
|
6027
5526
|
import { promisify } from "util";
|
|
6028
|
-
import { readFile as
|
|
5527
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
6029
5528
|
var execFileP = promisify(execFile);
|
|
6030
5529
|
var SUBJECT_BAND = { min: 0.6, max: 0.92 };
|
|
6031
5530
|
var BLANK_INK = 0.12;
|
|
@@ -6301,7 +5800,7 @@ async function pictureChecks(assets) {
|
|
|
6301
5800
|
let ffmpeg2 = null;
|
|
6302
5801
|
for (const a of assets) {
|
|
6303
5802
|
if (/\.png$/i.test(a.file)) {
|
|
6304
|
-
const img = decodePng(new Uint8Array(await
|
|
5803
|
+
const img = decodePng(new Uint8Array(await readFile9(a.file)));
|
|
6305
5804
|
if (!img) {
|
|
6306
5805
|
findings.push({
|
|
6307
5806
|
code: "unreadable",
|
|
@@ -6394,9 +5893,9 @@ function formatFinding(f) {
|
|
|
6394
5893
|
}
|
|
6395
5894
|
|
|
6396
5895
|
// src/plugin/validateKit.ts
|
|
6397
|
-
import { existsSync as
|
|
6398
|
-
import { readFile as
|
|
6399
|
-
import { dirname as
|
|
5896
|
+
import { existsSync as existsSync9 } from "fs";
|
|
5897
|
+
import { readFile as readFile10, stat as stat2 } from "fs/promises";
|
|
5898
|
+
import { dirname as dirname2, isAbsolute as isAbsolute4, join as join9 } from "path";
|
|
6400
5899
|
import { DESTINATIONS as DESTINATIONS3 } from "@vosjs/studio-core";
|
|
6401
5900
|
var PNG_SIG2 = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
6402
5901
|
function pngDimensions(bytes) {
|
|
@@ -6415,7 +5914,7 @@ async function probeVideo(path) {
|
|
|
6415
5914
|
const MB = await import("mediabunny");
|
|
6416
5915
|
const input = new MB.Input({
|
|
6417
5916
|
formats: MB.ALL_FORMATS,
|
|
6418
|
-
source: new MB.BufferSource(new Uint8Array(await
|
|
5917
|
+
source: new MB.BufferSource(new Uint8Array(await readFile10(path)))
|
|
6419
5918
|
});
|
|
6420
5919
|
try {
|
|
6421
5920
|
const track = await input.getPrimaryVideoTrack();
|
|
@@ -6436,7 +5935,7 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
6436
5935
|
const measured = [];
|
|
6437
5936
|
let kit;
|
|
6438
5937
|
try {
|
|
6439
|
-
kit = JSON.parse(await
|
|
5938
|
+
kit = JSON.parse(await readFile10(kitPath, "utf8"));
|
|
6440
5939
|
} catch (e) {
|
|
6441
5940
|
return {
|
|
6442
5941
|
valid: false,
|
|
@@ -6453,10 +5952,10 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
6453
5952
|
measured
|
|
6454
5953
|
};
|
|
6455
5954
|
}
|
|
6456
|
-
const base =
|
|
5955
|
+
const base = dirname2(kitPath);
|
|
6457
5956
|
const resolvePath = (p) => {
|
|
6458
|
-
if (isAbsolute4(p) &&
|
|
6459
|
-
const beside =
|
|
5957
|
+
if (isAbsolute4(p) && existsSync9(p)) return p;
|
|
5958
|
+
const beside = join9(base, p.split("/").pop() ?? p);
|
|
6460
5959
|
return beside;
|
|
6461
5960
|
};
|
|
6462
5961
|
const perDestination = /* @__PURE__ */ new Map();
|
|
@@ -6467,7 +5966,7 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
6467
5966
|
const file = resolvePath(a.path);
|
|
6468
5967
|
let bytes;
|
|
6469
5968
|
try {
|
|
6470
|
-
bytes = (await
|
|
5969
|
+
bytes = (await stat2(file)).size;
|
|
6471
5970
|
} catch {
|
|
6472
5971
|
problems.push(`${label}: ${a.path} is missing`);
|
|
6473
5972
|
continue;
|
|
@@ -6482,7 +5981,7 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
6482
5981
|
let h = null;
|
|
6483
5982
|
let seconds = null;
|
|
6484
5983
|
if (/\.(png|jpe?g|webp)$/i.test(file)) {
|
|
6485
|
-
const head = Buffer.from(await
|
|
5984
|
+
const head = Buffer.from(await readFile10(file));
|
|
6486
5985
|
const kind = sniffImage(head);
|
|
6487
5986
|
if (file.toLowerCase().endsWith(".png") && kind !== "png")
|
|
6488
5987
|
problems.push(
|
|
@@ -6592,7 +6091,7 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
6592
6091
|
|
|
6593
6092
|
// src/plugin/recorder.ts
|
|
6594
6093
|
import { writeFileSync } from "fs";
|
|
6595
|
-
import { join as
|
|
6094
|
+
import { join as join10 } from "path";
|
|
6596
6095
|
import { recordTake as recordTakeCore } from "@vosjs/render-core/record";
|
|
6597
6096
|
async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
6598
6097
|
const dry = opts.dryRun === true;
|
|
@@ -6602,7 +6101,7 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
6602
6101
|
actions,
|
|
6603
6102
|
{
|
|
6604
6103
|
frame: (file, bytes) => {
|
|
6605
|
-
writeFileSync(
|
|
6104
|
+
writeFileSync(join10(paths.framesDir, file), bytes);
|
|
6606
6105
|
}
|
|
6607
6106
|
},
|
|
6608
6107
|
log,
|
|
@@ -6654,11 +6153,11 @@ import {
|
|
|
6654
6153
|
|
|
6655
6154
|
// src/plugin/session.ts
|
|
6656
6155
|
import { spawn } from "child_process";
|
|
6657
|
-
import { existsSync as
|
|
6156
|
+
import { existsSync as existsSync10, mkdirSync, readdirSync, rmSync, statSync as statSync2 } from "fs";
|
|
6658
6157
|
import { homedir } from "os";
|
|
6659
|
-
import { join as
|
|
6158
|
+
import { join as join11 } from "path";
|
|
6660
6159
|
import { chromium } from "playwright";
|
|
6661
|
-
var SESSIONS_DIR =
|
|
6160
|
+
var SESSIONS_DIR = join11(homedir(), ".config", "vos", "sessions");
|
|
6662
6161
|
var NAME = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
|
|
6663
6162
|
function sessionNameVerdict(name) {
|
|
6664
6163
|
if (!name) return "a session needs a name: --name <app>";
|
|
@@ -6667,20 +6166,20 @@ function sessionNameVerdict(name) {
|
|
|
6667
6166
|
return null;
|
|
6668
6167
|
}
|
|
6669
6168
|
function sessionDir(name) {
|
|
6670
|
-
return
|
|
6169
|
+
return join11(SESSIONS_DIR, name);
|
|
6671
6170
|
}
|
|
6672
6171
|
function systemChromePath() {
|
|
6673
6172
|
const explicit = process.env.VOS_BROWSER_PATH;
|
|
6674
6173
|
if (explicit) return explicit;
|
|
6675
6174
|
const candidates = process.platform === "darwin" ? ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"] : process.platform === "win32" ? [
|
|
6676
|
-
|
|
6175
|
+
join11(
|
|
6677
6176
|
process.env["PROGRAMFILES"] ?? "C:\\Program Files",
|
|
6678
6177
|
"Google",
|
|
6679
6178
|
"Chrome",
|
|
6680
6179
|
"Application",
|
|
6681
6180
|
"chrome.exe"
|
|
6682
6181
|
),
|
|
6683
|
-
|
|
6182
|
+
join11(
|
|
6684
6183
|
process.env["LOCALAPPDATA"] ?? "",
|
|
6685
6184
|
"Google",
|
|
6686
6185
|
"Chrome",
|
|
@@ -6692,7 +6191,7 @@ function systemChromePath() {
|
|
|
6692
6191
|
"/usr/bin/google-chrome-stable",
|
|
6693
6192
|
"/opt/google/chrome/chrome"
|
|
6694
6193
|
];
|
|
6695
|
-
const found = candidates.find((c) =>
|
|
6194
|
+
const found = candidates.find((c) => existsSync10(c));
|
|
6696
6195
|
if (!found)
|
|
6697
6196
|
throw new Error(
|
|
6698
6197
|
"no Google Chrome found: a session needs the system Chrome (the bundled Chromium cannot read a profile Chrome wrote). Install Chrome, or set VOS_BROWSER_PATH"
|
|
@@ -6723,7 +6222,7 @@ async function openSession(name, url, opts = { log: () => {
|
|
|
6723
6222
|
}
|
|
6724
6223
|
async function withSession(name, fn, opts = {}) {
|
|
6725
6224
|
const dir = sessionDir(name);
|
|
6726
|
-
if (!
|
|
6225
|
+
if (!existsSync10(dir))
|
|
6727
6226
|
throw new Error(`no session "${name}" (vos session list)`);
|
|
6728
6227
|
const explicit = process.env.VOS_BROWSER_PATH;
|
|
6729
6228
|
const ctx = await chromium.launchPersistentContext(dir, {
|
|
@@ -6742,7 +6241,7 @@ async function withSession(name, fn, opts = {}) {
|
|
|
6742
6241
|
}
|
|
6743
6242
|
async function summarizeSession(name) {
|
|
6744
6243
|
const dir = sessionDir(name);
|
|
6745
|
-
const created =
|
|
6244
|
+
const created = statSync2(dir).birthtimeMs || statSync2(dir).mtimeMs;
|
|
6746
6245
|
return withSession(name, async (ctx) => {
|
|
6747
6246
|
const cookies = await ctx.cookies();
|
|
6748
6247
|
const byHost = /* @__PURE__ */ new Map();
|
|
@@ -6772,9 +6271,9 @@ function summaryLine(s) {
|
|
|
6772
6271
|
return `session "${s.name}" holds cookies for ${hosts}${more}; ${exp}`;
|
|
6773
6272
|
}
|
|
6774
6273
|
function listSessions() {
|
|
6775
|
-
if (!
|
|
6776
|
-
return
|
|
6777
|
-
const st =
|
|
6274
|
+
if (!existsSync10(SESSIONS_DIR)) return [];
|
|
6275
|
+
return readdirSync(SESSIONS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && NAME.test(d.name)).map((d) => {
|
|
6276
|
+
const st = statSync2(join11(SESSIONS_DIR, d.name));
|
|
6778
6277
|
return {
|
|
6779
6278
|
name: d.name,
|
|
6780
6279
|
ageDays: Math.floor(
|
|
@@ -6785,7 +6284,7 @@ function listSessions() {
|
|
|
6785
6284
|
}
|
|
6786
6285
|
function removeSession(name) {
|
|
6787
6286
|
const dir = sessionDir(name);
|
|
6788
|
-
if (!
|
|
6287
|
+
if (!existsSync10(dir)) return false;
|
|
6789
6288
|
rmSync(dir, { recursive: true, force: true });
|
|
6790
6289
|
return true;
|
|
6791
6290
|
}
|
|
@@ -6850,9 +6349,9 @@ async function encodeRecording(browser, takeDir, onProgress) {
|
|
|
6850
6349
|
}
|
|
6851
6350
|
|
|
6852
6351
|
// src/plugin/plan.ts
|
|
6853
|
-
import { existsSync as
|
|
6854
|
-
import { readFile as
|
|
6855
|
-
import { join as
|
|
6352
|
+
import { existsSync as existsSync11 } from "fs";
|
|
6353
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
6354
|
+
import { join as join12 } from "path";
|
|
6856
6355
|
import {
|
|
6857
6356
|
DEFAULT_FRAME_STYLE,
|
|
6858
6357
|
STYLE_FIELDS,
|
|
@@ -7074,10 +6573,10 @@ function retimeCut(prev, newSteps, newDurationMs) {
|
|
|
7074
6573
|
// src/plugin/plan.ts
|
|
7075
6574
|
var overlaps = (a, b) => a.in < b.out && b.in < a.out;
|
|
7076
6575
|
async function readDigestActivity(dir) {
|
|
7077
|
-
const file =
|
|
7078
|
-
if (!
|
|
6576
|
+
const file = join12(dir, "digest", "digest.json");
|
|
6577
|
+
if (!existsSync11(file)) return null;
|
|
7079
6578
|
try {
|
|
7080
|
-
const d = JSON.parse(await
|
|
6579
|
+
const d = JSON.parse(await readFile11(file, "utf8"));
|
|
7081
6580
|
return Array.isArray(d.activity) && d.activity.every((v) => typeof v === "number") ? d.activity : null;
|
|
7082
6581
|
} catch {
|
|
7083
6582
|
return null;
|
|
@@ -7290,16 +6789,16 @@ async function planTake(dir, opts = {}) {
|
|
|
7290
6789
|
}
|
|
7291
6790
|
|
|
7292
6791
|
// src/plugin/sync.ts
|
|
7293
|
-
import { createHash
|
|
7294
|
-
import { existsSync as
|
|
7295
|
-
import { readFile as
|
|
6792
|
+
import { createHash } from "crypto";
|
|
6793
|
+
import { existsSync as existsSync12, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
|
|
6794
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
7296
6795
|
import { createInterface } from "readline/promises";
|
|
7297
|
-
import { basename
|
|
6796
|
+
import { basename, join as join14 } from "path";
|
|
7298
6797
|
import { lowerToComposition as lowerToComposition3, migrateHostedDoc as migrateHostedDoc3 } from "@vosjs/studio-core";
|
|
7299
6798
|
|
|
7300
6799
|
// src/plugin/folder.ts
|
|
7301
|
-
import { mkdir as
|
|
7302
|
-
import { join as
|
|
6800
|
+
import { mkdir as mkdir6, writeFile as writeFile8 } from "fs/promises";
|
|
6801
|
+
import { join as join13 } from "path";
|
|
7303
6802
|
import { recipeHints } from "@vosjs/shared/frontmatter";
|
|
7304
6803
|
import { migrateHostedDoc as migrateHostedDoc2 } from "@vosjs/studio-core";
|
|
7305
6804
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["json", "help", "media"]);
|
|
@@ -7409,7 +6908,7 @@ async function cmdMove(argv) {
|
|
|
7409
6908
|
});
|
|
7410
6909
|
const key = requireCredential(strFlag(flags, "key"));
|
|
7411
6910
|
const folderId = toRef === "none" ? null : resolveFolder(await listFolders(origin, key), toRef).id;
|
|
7412
|
-
let
|
|
6911
|
+
let failed = 0;
|
|
7413
6912
|
const moved = [];
|
|
7414
6913
|
for (const raw of positionals) {
|
|
7415
6914
|
const id = parseVosId(raw);
|
|
@@ -7424,28 +6923,28 @@ async function cmdMove(argv) {
|
|
|
7424
6923
|
continue;
|
|
7425
6924
|
}
|
|
7426
6925
|
if (asVos.status === 404) {
|
|
7427
|
-
const
|
|
6926
|
+
const asAsset = await apiJson(origin, `/api/assets/${id}`, {
|
|
7428
6927
|
method: "PATCH",
|
|
7429
6928
|
key,
|
|
7430
6929
|
body: { folderId }
|
|
7431
6930
|
});
|
|
7432
|
-
if (
|
|
6931
|
+
if (asAsset.status === 200) {
|
|
7433
6932
|
moved.push({ id, kind: "asset" });
|
|
7434
6933
|
r.log(`moved asset ${id}`);
|
|
7435
6934
|
continue;
|
|
7436
6935
|
}
|
|
7437
|
-
|
|
7438
|
-
r.log(apiError(`move ${id}`,
|
|
6936
|
+
failed++;
|
|
6937
|
+
r.log(apiError(`move ${id}`, asAsset));
|
|
7439
6938
|
continue;
|
|
7440
6939
|
}
|
|
7441
|
-
|
|
6940
|
+
failed++;
|
|
7442
6941
|
r.log(apiError(`move ${id}`, asVos));
|
|
7443
6942
|
}
|
|
7444
6943
|
r.done(
|
|
7445
|
-
{ moved, failed
|
|
7446
|
-
`moved ${moved.length}/${positionals.length} into ${toRef === "none" ? "the top level" : toRef}${
|
|
6944
|
+
{ moved, failed, folderId },
|
|
6945
|
+
`moved ${moved.length}/${positionals.length} into ${toRef === "none" ? "the top level" : toRef}${failed ? ` \u2014 ${failed} failed` : ""}`
|
|
7447
6946
|
);
|
|
7448
|
-
return
|
|
6947
|
+
return failed ? EXIT_ERROR : EXIT_OK;
|
|
7449
6948
|
}
|
|
7450
6949
|
function recipeLine(filename, body) {
|
|
7451
6950
|
const h = recipeHints(body);
|
|
@@ -7475,25 +6974,25 @@ async function cmdPull(argv) {
|
|
|
7475
6974
|
throw new Error(apiError(`pull folder ${folder.slug}`, res));
|
|
7476
6975
|
const payload = res.body;
|
|
7477
6976
|
const out = strFlag(flags, "out") ?? folder.slug;
|
|
7478
|
-
await
|
|
6977
|
+
await mkdir6(join13(out, "recipes"), { recursive: true });
|
|
7479
6978
|
const lines = [];
|
|
7480
6979
|
const recipes = [];
|
|
7481
6980
|
for (const rec of payload.recipes) {
|
|
7482
|
-
const path =
|
|
7483
|
-
await
|
|
6981
|
+
const path = join13(out, "recipes", safeName(rec.filename));
|
|
6982
|
+
await writeFile8(path, rec.body ?? "");
|
|
7484
6983
|
recipes.push({ path, line: recipeLine(rec.filename, rec.body ?? "") });
|
|
7485
6984
|
}
|
|
7486
6985
|
for (const rec of payload.inheritedRecipes) {
|
|
7487
6986
|
const from = safeName(rec.folderSlug ?? "inherited");
|
|
7488
|
-
await
|
|
7489
|
-
const path =
|
|
6987
|
+
await mkdir6(join13(out, "recipes", "_inherited", from), { recursive: true });
|
|
6988
|
+
const path = join13(
|
|
7490
6989
|
out,
|
|
7491
6990
|
"recipes",
|
|
7492
6991
|
"_inherited",
|
|
7493
6992
|
from,
|
|
7494
6993
|
safeName(rec.filename)
|
|
7495
6994
|
);
|
|
7496
|
-
await
|
|
6995
|
+
await writeFile8(path, rec.body ?? "");
|
|
7497
6996
|
recipes.push({
|
|
7498
6997
|
path,
|
|
7499
6998
|
line: `${recipeLine(rec.filename, rec.body ?? "")} [inherited from ${rec.folderName ?? from}]`
|
|
@@ -7501,15 +7000,15 @@ async function cmdPull(argv) {
|
|
|
7501
7000
|
}
|
|
7502
7001
|
const members = [];
|
|
7503
7002
|
for (const v of payload.voses) {
|
|
7504
|
-
const dir =
|
|
7505
|
-
await
|
|
7003
|
+
const dir = join13(out, "members", safeName(v.slug || v.title || v.id));
|
|
7004
|
+
await mkdir6(dir, { recursive: true });
|
|
7506
7005
|
const cfg = await apiJson(origin, `/api/vos/${v.id}/config`, { key });
|
|
7507
7006
|
if (cfg.status !== 200) {
|
|
7508
7007
|
lines.push(`skipped ${v.title}: ${apiError("fetch config", cfg)}`);
|
|
7509
7008
|
continue;
|
|
7510
7009
|
}
|
|
7511
|
-
await
|
|
7512
|
-
|
|
7010
|
+
await writeFile8(
|
|
7011
|
+
join13(dir, "config.json"),
|
|
7513
7012
|
JSON.stringify(cfg.body.config, null, 2)
|
|
7514
7013
|
);
|
|
7515
7014
|
let take = false;
|
|
@@ -7520,8 +7019,8 @@ async function cmdPull(argv) {
|
|
|
7520
7019
|
{ key }
|
|
7521
7020
|
);
|
|
7522
7021
|
if (doc.status === 200) {
|
|
7523
|
-
await
|
|
7524
|
-
|
|
7022
|
+
await writeFile8(
|
|
7023
|
+
join13(dir, "doc.json"),
|
|
7525
7024
|
JSON.stringify(doc.body, null, 2)
|
|
7526
7025
|
);
|
|
7527
7026
|
take = true;
|
|
@@ -7653,12 +7152,12 @@ function programPushTarget(ask) {
|
|
|
7653
7152
|
return { kind: "version", vosId: tracked, tracked: true };
|
|
7654
7153
|
}
|
|
7655
7154
|
function storageStateInDir(dir) {
|
|
7656
|
-
for (const f of
|
|
7155
|
+
for (const f of readdirSync2(dir)) {
|
|
7657
7156
|
if (!f.endsWith(".json") || f === "doc.json" || f === "meta.json" || f === "actions.json" || f === "cursor.json" || f === "vos.json" || f === "doc.prev.json")
|
|
7658
7157
|
continue;
|
|
7659
7158
|
try {
|
|
7660
|
-
const v = JSON.parse(readFileSync2(
|
|
7661
|
-
if (looksLikeStorageState(v)) return
|
|
7159
|
+
const v = JSON.parse(readFileSync2(join14(dir, f), "utf8"));
|
|
7160
|
+
if (looksLikeStorageState(v)) return join14(dir, f);
|
|
7662
7161
|
} catch {
|
|
7663
7162
|
}
|
|
7664
7163
|
}
|
|
@@ -7679,7 +7178,7 @@ async function pushTake(dir, flags, r) {
|
|
|
7679
7178
|
throw new Error(`doc.json fails lint:
|
|
7680
7179
|
${lint.problems.join("\n ")}`);
|
|
7681
7180
|
}
|
|
7682
|
-
if (!
|
|
7181
|
+
if (!existsSync12(take.paths.recording)) {
|
|
7683
7182
|
throw new Error(
|
|
7684
7183
|
"no recording in this take \u2014 record it, or `vos pull --media` to bring it home"
|
|
7685
7184
|
);
|
|
@@ -7728,8 +7227,8 @@ async function pushTake(dir, flags, r) {
|
|
|
7728
7227
|
throw new Error("push cancelled");
|
|
7729
7228
|
}
|
|
7730
7229
|
}
|
|
7731
|
-
const bytes = await
|
|
7732
|
-
const hash =
|
|
7230
|
+
const bytes = await readFile12(take.paths.recording);
|
|
7231
|
+
const hash = createHash("sha256").update(bytes).digest("hex");
|
|
7733
7232
|
const { type: contentType } = resolveMediaType({
|
|
7734
7233
|
head: bytes.subarray(0, MEDIA_HEAD_BYTES),
|
|
7735
7234
|
filename: take.paths.recordingName
|
|
@@ -7776,7 +7275,7 @@ async function pushTake(dir, flags, r) {
|
|
|
7776
7275
|
const lowered = lowerToComposition3(docForPush);
|
|
7777
7276
|
const config = { ...lowered.config, data: lowered.data };
|
|
7778
7277
|
if (!state) {
|
|
7779
|
-
const title = flags.title ?? `Take ${
|
|
7278
|
+
const title = flags.title ?? `Take ${basename(dir)} ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
7780
7279
|
const slug = `${title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "take"}-${Date.now().toString(36)}`;
|
|
7781
7280
|
const created = await api(ctx, "/vos", {
|
|
7782
7281
|
method: "POST",
|
|
@@ -7888,9 +7387,9 @@ async function pullTake(dir, flags, r) {
|
|
|
7888
7387
|
};
|
|
7889
7388
|
}
|
|
7890
7389
|
let media2;
|
|
7891
|
-
if (flags.media &&
|
|
7390
|
+
if (flags.media && existsSync12(join14(dir, "doc.json"))) {
|
|
7892
7391
|
const local = JSON.parse(
|
|
7893
|
-
await
|
|
7392
|
+
await readFile12(join14(dir, "doc.json"), "utf8")
|
|
7894
7393
|
);
|
|
7895
7394
|
media2 = await pullMedia(ctx, dir, local, r.log);
|
|
7896
7395
|
}
|
|
@@ -7951,10 +7450,10 @@ async function pullTake(dir, flags, r) {
|
|
|
7951
7450
|
const hostedDoc = migrateHostedDoc3(docRes.json);
|
|
7952
7451
|
const doc = hostedDoc;
|
|
7953
7452
|
const localRecording = takeMediaFile(dir, "recording", RECORDING_NAME);
|
|
7954
|
-
if (
|
|
7955
|
-
doc.source.videoKey =
|
|
7453
|
+
if (existsSync12(localRecording)) {
|
|
7454
|
+
doc.source.videoKey = basename(localRecording);
|
|
7956
7455
|
}
|
|
7957
|
-
await writeJson(
|
|
7456
|
+
await writeJson(join14(dir, "doc.json"), doc, true);
|
|
7958
7457
|
const media = flags.media ? await pullMedia(ctx, dir, doc, r.log) : void 0;
|
|
7959
7458
|
const versions = await api(ctx, `/vos/${vosId}/versions`);
|
|
7960
7459
|
const headRow = (versions.json.versions ?? []).find((v) => v.id === head);
|
|
@@ -7973,10 +7472,10 @@ async function pullTake(dir, flags, r) {
|
|
|
7973
7472
|
}
|
|
7974
7473
|
|
|
7975
7474
|
// src/plugin/program.ts
|
|
7976
|
-
import { existsSync as
|
|
7977
|
-
import { mkdir as
|
|
7475
|
+
import { existsSync as existsSync13 } from "fs";
|
|
7476
|
+
import { mkdir as mkdir7, readFile as readFile13, writeFile as writeFile9 } from "fs/promises";
|
|
7978
7477
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
7979
|
-
import { basename as
|
|
7478
|
+
import { basename as basename2, dirname as dirname3, join as join15 } from "path";
|
|
7980
7479
|
import {
|
|
7981
7480
|
CURRENT_CONFIG_VERSION,
|
|
7982
7481
|
migrateConfig,
|
|
@@ -8182,8 +7681,8 @@ function preflightConfig(parsed) {
|
|
|
8182
7681
|
}
|
|
8183
7682
|
function resolveConfigPath(target) {
|
|
8184
7683
|
if (target.endsWith(".json")) return target;
|
|
8185
|
-
const inDir =
|
|
8186
|
-
if (
|
|
7684
|
+
const inDir = join15(target, "config.json");
|
|
7685
|
+
if (existsSync13(inDir)) return inDir;
|
|
8187
7686
|
throw new UsageError(
|
|
8188
7687
|
`${target} has no config.json (and is not a take \u2014 no doc.json)`
|
|
8189
7688
|
);
|
|
@@ -8209,9 +7708,9 @@ async function cmdFetch(argv) {
|
|
|
8209
7708
|
throw new Error(apiError(`fetch config for ${vosId}`, cfg));
|
|
8210
7709
|
const slug = typeof vosMeta.slug === "string" && vosMeta.slug ? vosMeta.slug : vosId;
|
|
8211
7710
|
const out = strFlag(flags, "out") ?? slug;
|
|
8212
|
-
await
|
|
8213
|
-
await
|
|
8214
|
-
|
|
7711
|
+
await mkdir7(out, { recursive: true });
|
|
7712
|
+
await writeFile9(
|
|
7713
|
+
join15(out, "config.json"),
|
|
8215
7714
|
JSON.stringify(cfg.body.config, null, 2)
|
|
8216
7715
|
);
|
|
8217
7716
|
const title = typeof vosMeta.title === "string" ? vosMeta.title : "";
|
|
@@ -8236,18 +7735,40 @@ async function cmdFetch(argv) {
|
|
|
8236
7735
|
const hosted = migrateHostedDoc4(doc.body);
|
|
8237
7736
|
const { config: own } = await writeProgramDoc(out, hosted);
|
|
8238
7737
|
if (own) {
|
|
8239
|
-
await
|
|
7738
|
+
await writeFile9(join15(out, "config.json"), JSON.stringify(own, null, 2));
|
|
8240
7739
|
}
|
|
8241
7740
|
} else if (doc.status === 200) {
|
|
8242
7741
|
take = true;
|
|
8243
7742
|
const hosted = migrateHostedDoc4(doc.body);
|
|
8244
|
-
await
|
|
7743
|
+
await writeFile9(join15(out, "doc.json"), JSON.stringify(hosted, null, 2));
|
|
8245
7744
|
if (flags.media === true) {
|
|
8246
7745
|
const media = await pullMedia({ origin, key }, out, hosted, r.log);
|
|
8247
7746
|
mediaLine = media.downloaded.length ? ` + ${media.downloaded.map((m) => m.file).join(", ")}` : "";
|
|
8248
7747
|
}
|
|
8249
7748
|
}
|
|
8250
7749
|
}
|
|
7750
|
+
let assetsLine = "";
|
|
7751
|
+
if (!take) {
|
|
7752
|
+
const configPath = join15(out, "config.json");
|
|
7753
|
+
const written = JSON.parse(await readFile13(configPath, "utf8"));
|
|
7754
|
+
const hostedCount = manifestRefs(written).filter(
|
|
7755
|
+
({ ref }) => hostedAssetId(ref, [origin, "https://vos.so", "https://www.vos.so"])
|
|
7756
|
+
).length;
|
|
7757
|
+
if (hostedCount && flags.media === true) {
|
|
7758
|
+
const pulled = await pullManifest({ origin, key }, out, written, r.log);
|
|
7759
|
+
if (pulled) {
|
|
7760
|
+
await writeFile9(
|
|
7761
|
+
configPath,
|
|
7762
|
+
JSON.stringify({ ...written, assets: pulled.assets }, null, 2)
|
|
7763
|
+
);
|
|
7764
|
+
assetsLine = `
|
|
7765
|
+
${pulled.files.length} declared file${pulled.files.length === 1 ? "" : "s"} in ${out}/assets`;
|
|
7766
|
+
}
|
|
7767
|
+
} else if (hostedCount) {
|
|
7768
|
+
assetsLine = `
|
|
7769
|
+
${hostedCount} declared file${hostedCount === 1 ? " is" : "s are"} hosted (a render reads ${hostedCount === 1 ? "it" : "them"} with your key). Add --media to bring ${hostedCount === 1 ? "it" : "them"} home`;
|
|
7770
|
+
}
|
|
7771
|
+
}
|
|
8251
7772
|
r.done(
|
|
8252
7773
|
{
|
|
8253
7774
|
out,
|
|
@@ -8258,7 +7779,7 @@ async function cmdFetch(argv) {
|
|
|
8258
7779
|
take
|
|
8259
7780
|
},
|
|
8260
7781
|
take ? `Wrote ${out}/doc.json + config.json + vos.json${mediaLine} (${title || id}, a take)
|
|
8261
|
-
` + (flags.media === true ? `Then: vos digest ${out} \u2014 look before you cut; edit doc.json; vos validate ${out}; vos push ${out}` : `Add --media to bring the recording home (digest/frames/render need it); then edit doc.json and vos push ${out}`) : `Wrote ${out}/config.json + vos.json (${title || id})
|
|
7782
|
+
` + (flags.media === true ? `Then: vos digest ${out} \u2014 look before you cut; edit doc.json; vos validate ${out}; vos push ${out}` : `Add --media to bring the recording home (digest/frames/render need it); then edit doc.json and vos push ${out}`) : `Wrote ${out}/config.json + vos.json (${title || id})${assetsLine}
|
|
8262
7783
|
Edit config.json, then: vos check ${out}/config.json && vos push ${out}/config.json`
|
|
8263
7784
|
);
|
|
8264
7785
|
return EXIT_OK;
|
|
@@ -8354,8 +7875,8 @@ async function cmdPushProgram(argv) {
|
|
|
8354
7875
|
origin: strFlag(flags, "origin"),
|
|
8355
7876
|
api: strFlag(flags, "api")
|
|
8356
7877
|
});
|
|
8357
|
-
const dir =
|
|
8358
|
-
const parsed = JSON.parse(await
|
|
7878
|
+
const dir = dirname3(source);
|
|
7879
|
+
const parsed = JSON.parse(await readFile13(source, "utf8"));
|
|
8359
7880
|
const pre = preflightConfig(parsed);
|
|
8360
7881
|
if (!pre.ok || !pre.config) {
|
|
8361
7882
|
for (const issue of pre.issues) r.log(`error ${issue}`);
|
|
@@ -8375,6 +7896,7 @@ async function cmdPushProgram(argv) {
|
|
|
8375
7896
|
await setProgramStill(dir, t);
|
|
8376
7897
|
r.log(`cover: ${t}s (doc.json "still")`);
|
|
8377
7898
|
}
|
|
7899
|
+
for (const w of hostedLiteralWarnings(config)) r.log(`warning ${w}`);
|
|
8378
7900
|
const programDoc = await readProgramDoc(dir, config);
|
|
8379
7901
|
if (programDoc) {
|
|
8380
7902
|
const lint = lintDoc(programDoc);
|
|
@@ -8390,7 +7912,7 @@ async function cmdPushProgram(argv) {
|
|
|
8390
7912
|
"--claimable creates a NEW claimable vos and cannot iterate (--vos). After the human claims it, iterate with their key: vos push --vos <id>"
|
|
8391
7913
|
);
|
|
8392
7914
|
}
|
|
8393
|
-
const fallback = state?.title ? `${state.title} remix` :
|
|
7915
|
+
const fallback = state?.title ? `${state.title} remix` : basename2(source).replace(/\.json$/i, "") || "vos program";
|
|
8394
7916
|
const title2 = (strFlag(flags, "title") ?? fallback).slice(0, 100);
|
|
8395
7917
|
let claimDoc = programDoc;
|
|
8396
7918
|
if (programDoc) {
|
|
@@ -8407,6 +7929,12 @@ async function cmdPushProgram(argv) {
|
|
|
8407
7929
|
);
|
|
8408
7930
|
}
|
|
8409
7931
|
}
|
|
7932
|
+
const stay = localManifestRefs(config);
|
|
7933
|
+
if (stay.length) {
|
|
7934
|
+
throw new UsageError(
|
|
7935
|
+
`a claimable push carries no files, and this program declares ${stay.length} local file${stay.length === 1 ? "" : "s"} (${stay.join(", ")}). Push with a key instead (vos login, then vos push ${source}), which uploads ${stay.length === 1 ? "it" : "them"}`
|
|
7936
|
+
);
|
|
7937
|
+
}
|
|
8410
7938
|
const body = {
|
|
8411
7939
|
title: title2,
|
|
8412
7940
|
config: storedProgramConfig(config, claimDoc)
|
|
@@ -8440,6 +7968,7 @@ with their key: vos push ${source} --vos ${String(created2.id ?? "<id>")}`
|
|
|
8440
7968
|
(l) => r.log(l)
|
|
8441
7969
|
);
|
|
8442
7970
|
}
|
|
7971
|
+
await uploadManifest(config, dir, { origin, key }, (l) => r.log(l));
|
|
8443
7972
|
const overrides = [
|
|
8444
7973
|
...multi.override ?? [],
|
|
8445
7974
|
...strFlag(flags, "overrides")?.split(",") ?? []
|
|
@@ -8539,7 +8068,7 @@ ${landedLines(landed2)}` : "")
|
|
|
8539
8068
|
return EXIT_OK;
|
|
8540
8069
|
}
|
|
8541
8070
|
const remixOfId = pushTarget.kind === "create" ? pushTarget.remixOfId : void 0;
|
|
8542
|
-
const fallbackTitle = state?.title ? `${state.title} remix` :
|
|
8071
|
+
const fallbackTitle = state?.title ? `${state.title} remix` : basename2(source).replace(/\.json$/i, "") || "vos remix";
|
|
8543
8072
|
const title = (strFlag(flags, "title") ?? fallbackTitle).slice(0, 100);
|
|
8544
8073
|
const folderId = folderRef ? resolveFolder(await listFolders(origin, key), folderRef).id : void 0;
|
|
8545
8074
|
const created = await createProgramVos({
|
|
@@ -8607,7 +8136,7 @@ Iterate with: vos push ${source} --vos ${created.id}`
|
|
|
8607
8136
|
async function cmdPullProgram(argv) {
|
|
8608
8137
|
const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS2);
|
|
8609
8138
|
const target = positionals[0] ?? ".";
|
|
8610
|
-
const dir = target.endsWith(".json") ?
|
|
8139
|
+
const dir = target.endsWith(".json") ? dirname3(target) : target;
|
|
8611
8140
|
const r = createReporter(flags.json === true);
|
|
8612
8141
|
const origin = platformOrigin({
|
|
8613
8142
|
origin: strFlag(flags, "origin"),
|
|
@@ -8671,10 +8200,10 @@ async function cmdPullProgram(argv) {
|
|
|
8671
8200
|
const cfg = await apiJson(origin, `/api/vos/${vosId}/config`, { key });
|
|
8672
8201
|
if (cfg.status !== 200)
|
|
8673
8202
|
throw new Error(apiError(`fetch head config for ${vosId}`, cfg));
|
|
8674
|
-
const configPath =
|
|
8203
|
+
const configPath = join15(dir, "config.json");
|
|
8675
8204
|
let backedUp = false;
|
|
8676
|
-
if (
|
|
8677
|
-
await
|
|
8205
|
+
if (existsSync13(configPath)) {
|
|
8206
|
+
await writeFile9(join15(dir, "config.backup.json"), await readFile13(configPath));
|
|
8678
8207
|
backedUp = true;
|
|
8679
8208
|
}
|
|
8680
8209
|
let headConfig = cfg.body.config;
|
|
@@ -8693,7 +8222,7 @@ async function cmdPullProgram(argv) {
|
|
|
8693
8222
|
if (own) headConfig = own;
|
|
8694
8223
|
}
|
|
8695
8224
|
}
|
|
8696
|
-
await
|
|
8225
|
+
await writeFile9(configPath, JSON.stringify(headConfig, null, 2));
|
|
8697
8226
|
writeSyncState(dir, {
|
|
8698
8227
|
vosId,
|
|
8699
8228
|
versionId: typeof head.id === "string" ? head.id : since
|
|
@@ -8707,7 +8236,7 @@ async function cmdPullProgram(argv) {
|
|
|
8707
8236
|
protected: protectedIds,
|
|
8708
8237
|
changes,
|
|
8709
8238
|
out: configPath,
|
|
8710
|
-
backup: backedUp ?
|
|
8239
|
+
backup: backedUp ? join15(dir, "config.backup.json") : null
|
|
8711
8240
|
},
|
|
8712
8241
|
`Pulled ${changes.length} version${changes.length === 1 ? "" : "s"} \u2192 ${configPath}` + (backedUp ? ` (previous copy: config.backup.json)` : "") + `
|
|
8713
8242
|
Re-apply your edit on the new head, then: vos push ${configPath} --vos ${vosId}`
|
|
@@ -8794,9 +8323,9 @@ function storedProgramConfig(config, programDoc) {
|
|
|
8794
8323
|
return lowerProgramDoc(doc).config;
|
|
8795
8324
|
}
|
|
8796
8325
|
async function readProgramDoc(dir, config) {
|
|
8797
|
-
const docPath =
|
|
8798
|
-
if (!
|
|
8799
|
-
const parsed = JSON.parse(await
|
|
8326
|
+
const docPath = join15(dir, "doc.json");
|
|
8327
|
+
if (!existsSync13(docPath)) return null;
|
|
8328
|
+
const parsed = JSON.parse(await readFile13(docPath, "utf8"));
|
|
8800
8329
|
if (typeof parsed !== "object" || parsed === null || "source" in parsed)
|
|
8801
8330
|
return null;
|
|
8802
8331
|
const raw = parsed;
|
|
@@ -8804,32 +8333,32 @@ async function readProgramDoc(dir, config) {
|
|
|
8804
8333
|
return { ...raw, program: { ...program, config } };
|
|
8805
8334
|
}
|
|
8806
8335
|
async function setProgramStill(dir, t) {
|
|
8807
|
-
const docPath =
|
|
8336
|
+
const docPath = join15(dir, "doc.json");
|
|
8808
8337
|
let doc = { program: {}, audio: [] };
|
|
8809
|
-
if (
|
|
8810
|
-
const parsed = JSON.parse(await
|
|
8338
|
+
if (existsSync13(docPath)) {
|
|
8339
|
+
const parsed = JSON.parse(await readFile13(docPath, "utf8"));
|
|
8811
8340
|
if (!parsed || typeof parsed !== "object" || "source" in parsed)
|
|
8812
8341
|
throw new UsageError(
|
|
8813
8342
|
`${docPath} is a take's document; a take's cover is its own "still" field`
|
|
8814
8343
|
);
|
|
8815
8344
|
doc = parsed;
|
|
8816
8345
|
}
|
|
8817
|
-
await
|
|
8346
|
+
await writeFile9(docPath, JSON.stringify({ ...doc, still: t }, null, 2) + "\n");
|
|
8818
8347
|
}
|
|
8819
8348
|
async function writeProgramDoc(dir, hosted) {
|
|
8820
8349
|
const program = hosted.program && typeof hosted.program === "object" ? hosted.program : {};
|
|
8821
8350
|
const { config, ...rest } = program;
|
|
8822
8351
|
const onDisk = { ...hosted, program: rest };
|
|
8823
|
-
await
|
|
8352
|
+
await writeFile9(join15(dir, "doc.json"), JSON.stringify(onDisk, null, 2));
|
|
8824
8353
|
return {
|
|
8825
8354
|
config: config && typeof config === "object" ? config : null
|
|
8826
8355
|
};
|
|
8827
8356
|
}
|
|
8828
8357
|
|
|
8829
8358
|
// src/plugin/asset.ts
|
|
8830
|
-
import { createHash as
|
|
8359
|
+
import { createHash as createHash2 } from "crypto";
|
|
8831
8360
|
import { readFileSync as readFileSync3 } from "fs";
|
|
8832
|
-
import { basename as
|
|
8361
|
+
import { basename as basename3, extname as extname2 } from "path";
|
|
8833
8362
|
var BOOLEAN_FLAGS3 = /* @__PURE__ */ new Set(["json", "help"]);
|
|
8834
8363
|
var MIME_BY_EXT = {
|
|
8835
8364
|
".png": "image/png",
|
|
@@ -8881,7 +8410,7 @@ async function cmdPush(argv) {
|
|
|
8881
8410
|
const uploaded = [];
|
|
8882
8411
|
let unfiledMedia = 0;
|
|
8883
8412
|
for (const file of positionals) {
|
|
8884
|
-
const name =
|
|
8413
|
+
const name = basename3(file);
|
|
8885
8414
|
const body = readFileSync3(file);
|
|
8886
8415
|
const media = resolveMediaType({
|
|
8887
8416
|
head: body.subarray(0, MEDIA_HEAD_BYTES),
|
|
@@ -8894,7 +8423,7 @@ async function cmdPush(argv) {
|
|
|
8894
8423
|
put = await uploadAsset({ origin, key }, new Uint8Array(body), {
|
|
8895
8424
|
filename: sent,
|
|
8896
8425
|
contentType: media.type,
|
|
8897
|
-
contentHash:
|
|
8426
|
+
contentHash: createHash2("sha256").update(body).digest("hex"),
|
|
8898
8427
|
...folder ? { folderId: folder.id } : {}
|
|
8899
8428
|
});
|
|
8900
8429
|
} catch (e) {
|
|
@@ -8965,8 +8494,8 @@ async function cmdAsset(argv) {
|
|
|
8965
8494
|
}
|
|
8966
8495
|
|
|
8967
8496
|
// src/plugin/remove.ts
|
|
8968
|
-
import { existsSync as
|
|
8969
|
-
import { join as
|
|
8497
|
+
import { existsSync as existsSync14, rmSync as rmSync2, statSync as statSync3 } from "fs";
|
|
8498
|
+
import { join as join16 } from "path";
|
|
8970
8499
|
import { createInterface as createInterface3 } from "readline/promises";
|
|
8971
8500
|
|
|
8972
8501
|
// src/plugin/trash.ts
|
|
@@ -9145,7 +8674,7 @@ async function cmdDelete(argv) {
|
|
|
9145
8674
|
api: strFlag(flags, "api")
|
|
9146
8675
|
}).replace(/\/+$/, "");
|
|
9147
8676
|
const key = requireCredential(strFlag(flags, "key"));
|
|
9148
|
-
const dir =
|
|
8677
|
+
const dir = existsSync14(target) && statSync3(target).isDirectory() ? target : null;
|
|
9149
8678
|
const vosId = dir ? readSyncState(dir)?.vosId : parseVosId(target);
|
|
9150
8679
|
if (!vosId)
|
|
9151
8680
|
throw new UsageError(
|
|
@@ -9187,7 +8716,7 @@ async function cmdDelete(argv) {
|
|
|
9187
8716
|
throw new Error(apiError(`delete ${vosId}`, res));
|
|
9188
8717
|
let unlinked = false;
|
|
9189
8718
|
if (dir) {
|
|
9190
|
-
rmSync2(
|
|
8719
|
+
rmSync2(join16(dir, SYNC_STATE_NAME), { force: true });
|
|
9191
8720
|
unlinked = true;
|
|
9192
8721
|
}
|
|
9193
8722
|
const trashed = res.body.trashed === true;
|
|
@@ -9213,7 +8742,7 @@ async function cmdDelete(argv) {
|
|
|
9213
8742
|
|
|
9214
8743
|
// src/plugin/buildProgram.ts
|
|
9215
8744
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
9216
|
-
import { basename as
|
|
8745
|
+
import { basename as basename4, dirname as dirname4, join as join17, resolve as resolve7 } from "path";
|
|
9217
8746
|
import { pathToFileURL } from "url";
|
|
9218
8747
|
import { parse } from "acorn";
|
|
9219
8748
|
import { fullAncestor } from "acorn-walk";
|
|
@@ -9421,7 +8950,7 @@ async function cmdBuild(argv) {
|
|
|
9421
8950
|
throw new UsageError(
|
|
9422
8951
|
`${source} exports no program: \`export default { version: 2, duration, camera, createContent, createTimeline, \u2026 }\``
|
|
9423
8952
|
);
|
|
9424
|
-
const built = buildProgram(program,
|
|
8953
|
+
const built = buildProgram(program, basename4(file), text);
|
|
9425
8954
|
for (const p of built.problems) r.log(`error ${p.fn}: ${p.message}`);
|
|
9426
8955
|
if (built.problems.length) {
|
|
9427
8956
|
r.done(
|
|
@@ -9431,7 +8960,7 @@ async function cmdBuild(argv) {
|
|
|
9431
8960
|
return EXIT_ERROR;
|
|
9432
8961
|
}
|
|
9433
8962
|
const out = resolve7(
|
|
9434
|
-
strFlag(flags, "out") ??
|
|
8963
|
+
strFlag(flags, "out") ?? join17(dirname4(file), "config.json")
|
|
9435
8964
|
);
|
|
9436
8965
|
writeFileSync2(out, JSON.stringify(built.config, null, 2) + "\n");
|
|
9437
8966
|
const sidecar = out.replace(/\.json$/, "") + ".sources.json";
|
|
@@ -9448,8 +8977,8 @@ async function cmdBuild(argv) {
|
|
|
9448
8977
|
|
|
9449
8978
|
// src/plugin/compare.ts
|
|
9450
8979
|
import { spawnSync } from "child_process";
|
|
9451
|
-
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
9452
|
-
import { join as
|
|
8980
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync2, statSync as statSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
8981
|
+
import { dirname as dirname5, join as join18, resolve as resolve8 } from "path";
|
|
9453
8982
|
var BOOLEAN_FLAGS7 = /* @__PURE__ */ new Set(["json", "help"]);
|
|
9454
8983
|
function sampleTimes(duration, every) {
|
|
9455
8984
|
const step = every > 0 ? every : 1;
|
|
@@ -9515,6 +9044,12 @@ async function cmdCompare(argv) {
|
|
|
9515
9044
|
for (const w of loaded.warnings) r.log(`note: ${w}`);
|
|
9516
9045
|
const config = loaded.config;
|
|
9517
9046
|
const duration = configDuration(config) ?? 5;
|
|
9047
|
+
const assets = await programAssets(config, {
|
|
9048
|
+
baseDir: /^https?:\/\//.test(source) ? null : existsSync15(source) && statSync4(source).isDirectory() ? source : dirname5(source),
|
|
9049
|
+
origin: platformOrigin({}),
|
|
9050
|
+
key: resolveCredential(),
|
|
9051
|
+
log: (line) => r.log(line)
|
|
9052
|
+
});
|
|
9518
9053
|
const probed = probeSize(ref);
|
|
9519
9054
|
const half = probed && probed.width > 1280 ? 2 : 1;
|
|
9520
9055
|
const width = Number(
|
|
@@ -9533,9 +9068,9 @@ async function cmdCompare(argv) {
|
|
|
9533
9068
|
try {
|
|
9534
9069
|
for (const t of times) {
|
|
9535
9070
|
const tag = t.toFixed(2);
|
|
9536
|
-
const refPng =
|
|
9537
|
-
const vosPng =
|
|
9538
|
-
const sheet =
|
|
9071
|
+
const refPng = join18(outDir, `src-${tag}.png`);
|
|
9072
|
+
const vosPng = join18(outDir, `vos-${tag}.png`);
|
|
9073
|
+
const sheet = join18(outDir, `sheet-${tag}.png`);
|
|
9539
9074
|
const grab = ffmpeg([
|
|
9540
9075
|
"-v",
|
|
9541
9076
|
"error",
|
|
@@ -9556,6 +9091,7 @@ async function cmdCompare(argv) {
|
|
|
9556
9091
|
);
|
|
9557
9092
|
const still = await renderStill(browser, {
|
|
9558
9093
|
config,
|
|
9094
|
+
assets,
|
|
9559
9095
|
width,
|
|
9560
9096
|
height,
|
|
9561
9097
|
time: t
|
|
@@ -9591,7 +9127,7 @@ async function cmdCompare(argv) {
|
|
|
9591
9127
|
} finally {
|
|
9592
9128
|
await browser.close();
|
|
9593
9129
|
}
|
|
9594
|
-
const overview =
|
|
9130
|
+
const overview = join18(outDir, "overview.png");
|
|
9595
9131
|
if (scores.length) {
|
|
9596
9132
|
const inputs = scores.flatMap((s) => ["-i", s.sheet]);
|
|
9597
9133
|
const scaled = scores.map((_, i) => `[${i}:v]scale=iw/3:-2[s${i}]`).join(";");
|
|
@@ -9617,7 +9153,7 @@ async function cmdCompare(argv) {
|
|
|
9617
9153
|
overview
|
|
9618
9154
|
};
|
|
9619
9155
|
writeFileSync3(
|
|
9620
|
-
|
|
9156
|
+
join18(outDir, "report.json"),
|
|
9621
9157
|
JSON.stringify(report, null, 2) + "\n"
|
|
9622
9158
|
);
|
|
9623
9159
|
r.done(
|
|
@@ -9628,28 +9164,28 @@ async function cmdCompare(argv) {
|
|
|
9628
9164
|
}
|
|
9629
9165
|
|
|
9630
9166
|
// src/plugin/port.ts
|
|
9631
|
-
import { existsSync as
|
|
9632
|
-
import { dirname as dirname8, join as
|
|
9167
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
9168
|
+
import { dirname as dirname8, join as join22, resolve as resolve11 } from "path";
|
|
9633
9169
|
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
9634
9170
|
|
|
9635
9171
|
// src/plugin/portInventory.ts
|
|
9636
9172
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
9637
9173
|
import {
|
|
9638
|
-
existsSync as
|
|
9174
|
+
existsSync as existsSync17,
|
|
9639
9175
|
mkdirSync as mkdirSync3,
|
|
9640
9176
|
readFileSync as readFileSync6,
|
|
9641
|
-
readdirSync as
|
|
9177
|
+
readdirSync as readdirSync4,
|
|
9642
9178
|
statSync as statSync6
|
|
9643
9179
|
} from "fs";
|
|
9644
|
-
import { dirname as dirname6, join as
|
|
9180
|
+
import { dirname as dirname6, join as join20, relative as relative2, resolve as resolve9 } from "path";
|
|
9645
9181
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
9646
9182
|
import { findFontFamily as findFontFamily3 } from "@vosjs/shared";
|
|
9647
9183
|
|
|
9648
9184
|
// src/plugin/portRemotion.ts
|
|
9649
|
-
import { existsSync as
|
|
9185
|
+
import { existsSync as existsSync16, readFileSync as readFileSync5, readdirSync as readdirSync3, statSync as statSync5 } from "fs";
|
|
9650
9186
|
import { createServer as createServer2 } from "http";
|
|
9651
9187
|
import { createRequire } from "module";
|
|
9652
|
-
import { basename as
|
|
9188
|
+
import { basename as basename5, extname as extname3, join as join19 } from "path";
|
|
9653
9189
|
function themeColors(sources) {
|
|
9654
9190
|
const out = {};
|
|
9655
9191
|
const re = /\bconst\s+([A-Za-z_$][\w$]*)\s*(?::\s*string\s*)?=\s*['"](#[0-9a-fA-F]{3,8})['"]/g;
|
|
@@ -9713,16 +9249,16 @@ function sourceTexts(dir) {
|
|
|
9713
9249
|
const out = [];
|
|
9714
9250
|
const walk = (d, depth) => {
|
|
9715
9251
|
if (depth > 4 || out.length > 200) return;
|
|
9716
|
-
for (const name of
|
|
9252
|
+
for (const name of readdirSync3(d)) {
|
|
9717
9253
|
if (name === "node_modules" || name.startsWith(".")) continue;
|
|
9718
|
-
const p =
|
|
9254
|
+
const p = join19(d, name);
|
|
9719
9255
|
if (statSync5(p).isDirectory()) walk(p, depth + 1);
|
|
9720
9256
|
else if (/\.(tsx?|jsx?|mjs)$/.test(name))
|
|
9721
9257
|
out.push(readFileSync5(p, "utf8"));
|
|
9722
9258
|
}
|
|
9723
9259
|
};
|
|
9724
|
-
const src =
|
|
9725
|
-
walk(
|
|
9260
|
+
const src = join19(dir, "src");
|
|
9261
|
+
walk(existsSync16(src) ? src : dir, 0);
|
|
9726
9262
|
return out;
|
|
9727
9263
|
}
|
|
9728
9264
|
function entryPoint(dir) {
|
|
@@ -9734,7 +9270,7 @@ function entryPoint(dir) {
|
|
|
9734
9270
|
"remotion/index.ts",
|
|
9735
9271
|
"remotion/index.tsx"
|
|
9736
9272
|
])
|
|
9737
|
-
if (
|
|
9273
|
+
if (existsSync16(join19(dir, p))) return join19(dir, p);
|
|
9738
9274
|
return null;
|
|
9739
9275
|
}
|
|
9740
9276
|
var TYPES = {
|
|
@@ -9809,7 +9345,7 @@ async function readRemotion(dir, log) {
|
|
|
9809
9345
|
const entry = entryPoint(dir);
|
|
9810
9346
|
let bundler;
|
|
9811
9347
|
try {
|
|
9812
|
-
bundler = createRequire(
|
|
9348
|
+
bundler = createRequire(join19(dir, "package.json"))("@remotion/bundler");
|
|
9813
9349
|
} catch {
|
|
9814
9350
|
log?.(
|
|
9815
9351
|
"a Remotion project whose dependencies are not installed: run `npm install` in it so vos can read its compositions (words, scenes, faces); reading only the render"
|
|
@@ -9826,9 +9362,9 @@ async function readRemotion(dir, log) {
|
|
|
9826
9362
|
} });
|
|
9827
9363
|
const server = createServer2((rq, rs) => {
|
|
9828
9364
|
const path = decodeURIComponent(new URL(rq.url ?? "/", "http://x").pathname);
|
|
9829
|
-
let f =
|
|
9830
|
-
if (!f.startsWith(out) || !
|
|
9831
|
-
f =
|
|
9365
|
+
let f = join19(out, path);
|
|
9366
|
+
if (!f.startsWith(out) || !existsSync16(f) || statSync5(f).isDirectory())
|
|
9367
|
+
f = join19(out, "index.html");
|
|
9832
9368
|
rs.writeHead(200, {
|
|
9833
9369
|
"content-type": TYPES[extname3(f)] ?? "application/octet-stream"
|
|
9834
9370
|
});
|
|
@@ -9927,10 +9463,10 @@ async function readRemotion(dir, log) {
|
|
|
9927
9463
|
perScene.push({ scene: s.name, texts: read.texts ?? [] });
|
|
9928
9464
|
}
|
|
9929
9465
|
const sources = sourceTexts(dir);
|
|
9930
|
-
const publicDir =
|
|
9466
|
+
const publicDir = join19(dir, "public");
|
|
9931
9467
|
const media = tree.audio.map((a) => {
|
|
9932
|
-
const name =
|
|
9933
|
-
const local =
|
|
9468
|
+
const name = basename5(a.src.split("?")[0]);
|
|
9469
|
+
const local = existsSync16(join19(publicDir, name)) ? `public/${name}` : a.src;
|
|
9934
9470
|
return {
|
|
9935
9471
|
kind: "audio",
|
|
9936
9472
|
src: local,
|
|
@@ -9971,9 +9507,9 @@ async function readRemotion(dir, log) {
|
|
|
9971
9507
|
|
|
9972
9508
|
// src/plugin/portInventory.ts
|
|
9973
9509
|
function detectEngine(dir) {
|
|
9974
|
-
if (
|
|
9975
|
-
const pkg =
|
|
9976
|
-
if (
|
|
9510
|
+
if (existsSync17(join20(dir, "hyperframes.json"))) return "hyperframes";
|
|
9511
|
+
const pkg = join20(dir, "package.json");
|
|
9512
|
+
if (existsSync17(pkg)) {
|
|
9977
9513
|
try {
|
|
9978
9514
|
const p = JSON.parse(readFileSync6(pkg, "utf8"));
|
|
9979
9515
|
const deps = { ...p.dependencies ?? {}, ...p.devDependencies ?? {} };
|
|
@@ -9987,14 +9523,14 @@ function detectEngine(dir) {
|
|
|
9987
9523
|
return "page";
|
|
9988
9524
|
}
|
|
9989
9525
|
function sourcePage(source) {
|
|
9990
|
-
if (!
|
|
9526
|
+
if (!existsSync17(source)) return null;
|
|
9991
9527
|
if (statSync6(source).isFile()) return /\.html?$/i.test(source) ? source : null;
|
|
9992
|
-
const index =
|
|
9993
|
-
return
|
|
9528
|
+
const index = join20(source, "index.html");
|
|
9529
|
+
return existsSync17(index) ? index : null;
|
|
9994
9530
|
}
|
|
9995
9531
|
function findRender(dir) {
|
|
9996
|
-
const videos = (d) =>
|
|
9997
|
-
for (const d of [
|
|
9532
|
+
const videos = (d) => existsSync17(d) ? readdirSync4(d).filter((f) => /\.(mp4|webm|mov)$/i.test(f)).map((f) => join20(d, f)) : [];
|
|
9533
|
+
for (const d of [join20(dir, "renders"), join20(dir, "out"), dir]) {
|
|
9998
9534
|
const found = videos(d);
|
|
9999
9535
|
if (found.length) return found[0];
|
|
10000
9536
|
}
|
|
@@ -10265,7 +9801,7 @@ var SEEK_AND_READ = `((args) => {
|
|
|
10265
9801
|
})`;
|
|
10266
9802
|
async function readInventory(source, opts) {
|
|
10267
9803
|
const abs = resolve9(source);
|
|
10268
|
-
const dir =
|
|
9804
|
+
const dir = existsSync17(abs) && statSync6(abs).isDirectory() ? abs : dirname6(abs);
|
|
10269
9805
|
const engine = detectEngine(dir);
|
|
10270
9806
|
const page = sourcePage(abs);
|
|
10271
9807
|
const render = opts.render === void 0 ? findRender(dir) : opts.render;
|
|
@@ -10368,7 +9904,7 @@ async function readInventory(source, opts) {
|
|
|
10368
9904
|
if (render && duration) {
|
|
10369
9905
|
const has = spawnSync2("ffmpeg", ["-version"]).status === 0;
|
|
10370
9906
|
if (has) {
|
|
10371
|
-
const refDir =
|
|
9907
|
+
const refDir = join20(opts.outDir, "ref");
|
|
10372
9908
|
mkdirSync3(refDir, { recursive: true });
|
|
10373
9909
|
const times = read.scenes.length >= 3 ? read.scenes.map(
|
|
10374
9910
|
(s) => s.start + (s.duration ?? 1) / 2
|
|
@@ -10377,7 +9913,7 @@ async function readInventory(source, opts) {
|
|
|
10377
9913
|
(_, i) => i + 0.5
|
|
10378
9914
|
);
|
|
10379
9915
|
for (const t of times.filter((t2) => t2 < duration)) {
|
|
10380
|
-
const file =
|
|
9916
|
+
const file = join20(refDir, `src-${t.toFixed(2)}.png`);
|
|
10381
9917
|
const ok = spawnSync2("ffmpeg", [
|
|
10382
9918
|
"-v",
|
|
10383
9919
|
"error",
|
|
@@ -10431,8 +9967,8 @@ async function readInventory(source, opts) {
|
|
|
10431
9967
|
|
|
10432
9968
|
// src/plugin/portScaffold.ts
|
|
10433
9969
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
10434
|
-
import { copyFileSync, existsSync as
|
|
10435
|
-
import { basename as
|
|
9970
|
+
import { copyFileSync, existsSync as existsSync18, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
9971
|
+
import { basename as basename6, dirname as dirname7, join as join21, resolve as resolve10 } from "path";
|
|
10436
9972
|
import { findFontFamily as findFontFamily4, fontFaceUrl, nearestFontWeight } from "@vosjs/shared";
|
|
10437
9973
|
|
|
10438
9974
|
// src/plugin/painterLib.ts
|
|
@@ -10842,8 +10378,8 @@ function planScaffold(inv, opts) {
|
|
|
10842
10378
|
const copies = [];
|
|
10843
10379
|
inv.media.filter((m) => m.kind === "audio" && m.src).forEach((m, i) => {
|
|
10844
10380
|
const from = resolve10(opts.sourceDir, m.src);
|
|
10845
|
-
const name =
|
|
10846
|
-
if (!
|
|
10381
|
+
const name = basename6(from);
|
|
10382
|
+
if (!existsSync18(from)) {
|
|
10847
10383
|
report.push(
|
|
10848
10384
|
`- the audio ${m.src} is not in the source folder; add it to doc.json by hand`
|
|
10849
10385
|
);
|
|
@@ -10986,7 +10522,7 @@ ${sceneTodos}${springHint}
|
|
|
10986
10522
|
}
|
|
10987
10523
|
function reportText(inv, r) {
|
|
10988
10524
|
const lines = [
|
|
10989
|
-
`# Port report: ${inv.source === "." && inv.root ?
|
|
10525
|
+
`# Port report: ${inv.source === "." && inv.root ? basename6(inv.root) : inv.source}`,
|
|
10990
10526
|
"",
|
|
10991
10527
|
`${inv.engine} piece, ${inv.width}\xD7${inv.height}${inv.fps ? ` at ${inv.fps} fps` : ""}, ${inv.duration ?? "?"} s. A program has no size of its own: render it with \`--width ${inv.width} --height ${inv.height}\`.`,
|
|
10992
10528
|
"",
|
|
@@ -11041,13 +10577,13 @@ function reportText(inv, r) {
|
|
|
11041
10577
|
}
|
|
11042
10578
|
function writeScaffold(plan, outDir) {
|
|
11043
10579
|
mkdirSync4(outDir, { recursive: true });
|
|
11044
|
-
writeFileSync4(
|
|
10580
|
+
writeFileSync4(join21(outDir, "program.mjs"), plan.program);
|
|
11045
10581
|
writeFileSync4(
|
|
11046
|
-
|
|
10582
|
+
join21(outDir, "doc.json"),
|
|
11047
10583
|
JSON.stringify(plan.doc, null, 2) + "\n"
|
|
11048
10584
|
);
|
|
11049
|
-
writeFileSync4(
|
|
11050
|
-
for (const [from, name] of plan.copies) copyFileSync(from,
|
|
10585
|
+
writeFileSync4(join21(dirname7(outDir), "REPORT.md"), plan.report);
|
|
10586
|
+
for (const [from, name] of plan.copies) copyFileSync(from, join21(outDir, name));
|
|
11051
10587
|
}
|
|
11052
10588
|
function probeAudioDuration(file) {
|
|
11053
10589
|
const res = spawnSync3(
|
|
@@ -11081,7 +10617,7 @@ async function inventory(argv) {
|
|
|
11081
10617
|
outDir,
|
|
11082
10618
|
log: (l) => r.log(`note: ${l}`)
|
|
11083
10619
|
});
|
|
11084
|
-
const file =
|
|
10620
|
+
const file = join22(outDir, "inventory.json");
|
|
11085
10621
|
writeFileSync5(file, JSON.stringify(inv, null, 2) + "\n");
|
|
11086
10622
|
const missing = inv.fonts.filter((f) => !f.catalog).map((f) => f.family);
|
|
11087
10623
|
r.log(
|
|
@@ -11113,32 +10649,32 @@ function summary(inv) {
|
|
|
11113
10649
|
async function scaffold(argv) {
|
|
11114
10650
|
const { flags } = parseArgs(argv, BOOLEAN_FLAGS8);
|
|
11115
10651
|
const r = createReporter(flags.json === true);
|
|
11116
|
-
const from = resolve11(strFlag(flags, "from") ??
|
|
11117
|
-
if (!
|
|
10652
|
+
const from = resolve11(strFlag(flags, "from") ?? join22("port", "inventory.json"));
|
|
10653
|
+
if (!existsSync19(from))
|
|
11118
10654
|
throw new UsageError(
|
|
11119
10655
|
`no inventory at ${from}: run npx vos port inventory <source> first`
|
|
11120
10656
|
);
|
|
11121
10657
|
const inv = JSON.parse(readFileSync7(from, "utf8"));
|
|
11122
10658
|
const outDir = resolve11(
|
|
11123
|
-
strFlag(flags, "out") ??
|
|
10659
|
+
strFlag(flags, "out") ?? join22(dirname8(from), "program")
|
|
11124
10660
|
);
|
|
11125
10661
|
const plan = planScaffold(inv, {
|
|
11126
10662
|
sourceDir: scaffoldSourceDir(inv),
|
|
11127
10663
|
probeDuration: probeAudioDuration
|
|
11128
10664
|
});
|
|
11129
10665
|
writeScaffold(plan, outDir);
|
|
11130
|
-
const programFile =
|
|
10666
|
+
const programFile = join22(outDir, "program.mjs");
|
|
11131
10667
|
const mod = await import(`${pathToFileURL3(programFile).href}?t=${Date.now()}`);
|
|
11132
10668
|
const built = buildProgram(mod.default, "program.mjs", plan.program);
|
|
11133
10669
|
for (const p of built.problems) r.log(`error ${p.fn}: ${p.message}`);
|
|
11134
10670
|
if (built.problems.length) return EXIT_ERROR;
|
|
11135
10671
|
writeFileSync5(
|
|
11136
|
-
|
|
10672
|
+
join22(outDir, "config.json"),
|
|
11137
10673
|
JSON.stringify(built.config, null, 2) + "\n"
|
|
11138
10674
|
);
|
|
11139
10675
|
r.done(
|
|
11140
|
-
{ program: outDir, report:
|
|
11141
|
-
`Wrote ${outDir}/program.mjs, config.json and doc.json, and ${
|
|
10676
|
+
{ program: outDir, report: join22(dirname8(outDir), "REPORT.md") },
|
|
10677
|
+
`Wrote ${outDir}/program.mjs, config.json and doc.json, and ${join22(dirname8(outDir), "REPORT.md")}. Translate the motion where it says TODO, then: npx vos build program.mjs && npx vos compare . --against <their render>`
|
|
11142
10678
|
);
|
|
11143
10679
|
return EXIT_OK;
|
|
11144
10680
|
}
|
|
@@ -11149,11 +10685,11 @@ function scaffoldSourceDir(inv) {
|
|
|
11149
10685
|
|
|
11150
10686
|
// src/plugin/recipe.ts
|
|
11151
10687
|
import { readFileSync as readFileSync8 } from "fs";
|
|
11152
|
-
import { basename as
|
|
10688
|
+
import { basename as basename7 } from "path";
|
|
11153
10689
|
var BOOLEAN_FLAGS9 = /* @__PURE__ */ new Set(["json", "help"]);
|
|
11154
10690
|
var USAGE = "vos recipe push <file.md> (--folder <folderId|slug> | --asset <assetId>)";
|
|
11155
10691
|
function recipeFilename(file) {
|
|
11156
|
-
const name =
|
|
10692
|
+
const name = basename7(file);
|
|
11157
10693
|
if (!/\.md$/i.test(name)) {
|
|
11158
10694
|
throw new UsageError(`a recipe is a markdown file \u2014 ${name} is not .md`);
|
|
11159
10695
|
}
|
|
@@ -11240,7 +10776,7 @@ async function cmdRecipe(argv) {
|
|
|
11240
10776
|
}
|
|
11241
10777
|
|
|
11242
10778
|
// src/plugin/brand.ts
|
|
11243
|
-
import { writeFile as
|
|
10779
|
+
import { writeFile as writeFile10 } from "fs/promises";
|
|
11244
10780
|
import { resolve as resolve12 } from "path";
|
|
11245
10781
|
import { parseFrontmatter as parseFrontmatter2 } from "@vosjs/shared/frontmatter";
|
|
11246
10782
|
import { lookKindForGround } from "@vosjs/studio-core";
|
|
@@ -11594,7 +11130,7 @@ async function cmdBrand(argv) {
|
|
|
11594
11130
|
composition,
|
|
11595
11131
|
llms?.claim ?? design?.description ?? null
|
|
11596
11132
|
);
|
|
11597
|
-
await
|
|
11133
|
+
await writeFile10(out, md);
|
|
11598
11134
|
const k = composition.kit;
|
|
11599
11135
|
r.done(
|
|
11600
11136
|
{
|
|
@@ -11709,7 +11245,7 @@ async function recordHosted(input) {
|
|
|
11709
11245
|
}
|
|
11710
11246
|
|
|
11711
11247
|
// src/plugin/agentBrowser.ts
|
|
11712
|
-
import { writeFile as
|
|
11248
|
+
import { writeFile as writeFile11 } from "fs/promises";
|
|
11713
11249
|
import { resolve as resolve13 } from "path";
|
|
11714
11250
|
function parseAgentBrowserLog(text) {
|
|
11715
11251
|
const records = [];
|
|
@@ -12189,8 +11725,8 @@ async function cmdActions(argv) {
|
|
|
12189
11725
|
if (!m) throw new UsageError("--viewport expects WxH, e.g. 1280x720");
|
|
12190
11726
|
viewport = { width: Number(m[1]), height: Number(m[2]) };
|
|
12191
11727
|
}
|
|
12192
|
-
const { readFile:
|
|
12193
|
-
const text = await
|
|
11728
|
+
const { readFile: readFile16 } = await import("fs/promises");
|
|
11729
|
+
const text = await readFile16(resolve13(input), "utf8");
|
|
12194
11730
|
const { records, problems } = parseAgentBrowserLog(text);
|
|
12195
11731
|
for (const p of problems) r.log(` ${p}`);
|
|
12196
11732
|
const result = convertAgentBrowser(records, {
|
|
@@ -12207,7 +11743,7 @@ async function cmdActions(argv) {
|
|
|
12207
11743
|
);
|
|
12208
11744
|
return EXIT_ERROR;
|
|
12209
11745
|
}
|
|
12210
|
-
await
|
|
11746
|
+
await writeFile11(out, `${JSON.stringify(result.actions, null, 2)}
|
|
12211
11747
|
`);
|
|
12212
11748
|
r.done(
|
|
12213
11749
|
{ ok: true, out, url: result.actions.url ?? null, ...summary2(result) },
|
|
@@ -12227,9 +11763,9 @@ function summary2(result) {
|
|
|
12227
11763
|
}
|
|
12228
11764
|
async function cmdActionsScript(input, flags) {
|
|
12229
11765
|
const r = createReporter(flags.json === true);
|
|
12230
|
-
const { readFile:
|
|
11766
|
+
const { readFile: readFile16 } = await import("fs/promises");
|
|
12231
11767
|
const { shotList, formatShotList } = await import("./shotList-KH6FTX4L.js");
|
|
12232
|
-
const raw = JSON.parse(await
|
|
11768
|
+
const raw = JSON.parse(await readFile16(resolve13(input), "utf8"));
|
|
12233
11769
|
const errors = validateActions(raw);
|
|
12234
11770
|
if (errors.length) throw new UsageError(`${input}:
|
|
12235
11771
|
${errors.join("\n ")}`);
|
|
@@ -12239,9 +11775,9 @@ async function cmdActionsScript(input, flags) {
|
|
|
12239
11775
|
}
|
|
12240
11776
|
|
|
12241
11777
|
// src/plugin/ingest.ts
|
|
12242
|
-
import { existsSync as
|
|
12243
|
-
import { copyFile, mkdir as
|
|
12244
|
-
import { basename as
|
|
11778
|
+
import { existsSync as existsSync20 } from "fs";
|
|
11779
|
+
import { copyFile, mkdir as mkdir8, readFile as readFile14, stat as stat3 } from "fs/promises";
|
|
11780
|
+
import { basename as basename8, join as join23, resolve as resolve14 } from "path";
|
|
12245
11781
|
import { inflateRawSync } from "zlib";
|
|
12246
11782
|
import {
|
|
12247
11783
|
cursorFromPlaywrightTrace,
|
|
@@ -12322,14 +11858,14 @@ async function copyRecording(src, dest, ext) {
|
|
|
12322
11858
|
}
|
|
12323
11859
|
}
|
|
12324
11860
|
async function readTraceFile(path, offsetMs) {
|
|
12325
|
-
const kind = traceKindOf(
|
|
11861
|
+
const kind = traceKindOf(basename8(path));
|
|
12326
11862
|
if (!kind) {
|
|
12327
11863
|
throw new UsageError(
|
|
12328
11864
|
`${path}: not a trace the recorder reads (trace.zip, .trace, .jsonl, .json or .csv)`
|
|
12329
11865
|
);
|
|
12330
11866
|
}
|
|
12331
11867
|
if (kind === "zip") {
|
|
12332
|
-
const bytes = new Uint8Array(await
|
|
11868
|
+
const bytes = new Uint8Array(await readFile14(path));
|
|
12333
11869
|
const entry = playwrightTraceEntry(zipEntries(bytes));
|
|
12334
11870
|
if (!entry) {
|
|
12335
11871
|
throw new UsageError(
|
|
@@ -12342,8 +11878,8 @@ async function readTraceFile(path, offsetMs) {
|
|
|
12342
11878
|
);
|
|
12343
11879
|
return cursorFromPlaywrightTrace(text2, { offsetMs });
|
|
12344
11880
|
}
|
|
12345
|
-
const text = await
|
|
12346
|
-
return cursorFromTrace(
|
|
11881
|
+
const text = await readFile14(path, "utf8");
|
|
11882
|
+
return cursorFromTrace(basename8(path), text, { offsetMs });
|
|
12347
11883
|
}
|
|
12348
11884
|
function parseViewport(v) {
|
|
12349
11885
|
if (!v) return null;
|
|
@@ -12359,7 +11895,7 @@ async function cmdIngest(argv) {
|
|
|
12359
11895
|
"vos ingest <video.webm|mp4> [--cursor <trace.zip|steps.jsonl|cursor.csv>] [--as footage|take] [--out take] [--offset <ms>] [--viewport WxH] [--background <slug|url|none>] [--json]"
|
|
12360
11896
|
);
|
|
12361
11897
|
const videoPath = resolve14(source);
|
|
12362
|
-
if (!
|
|
11898
|
+
if (!existsSync20(videoPath)) throw new UsageError(`${source}: no such file`);
|
|
12363
11899
|
const r = createReporter(flags.json === true);
|
|
12364
11900
|
const outDir = resolve14(strFlag(flags, "out") ?? "take");
|
|
12365
11901
|
const offsetMs = numFlag(flags, "offset", 0);
|
|
@@ -12374,7 +11910,7 @@ async function cmdIngest(argv) {
|
|
|
12374
11910
|
"note: finished footage opens bare, so --background is not used (pass --as take for the screen-recording card and its ground)"
|
|
12375
11911
|
);
|
|
12376
11912
|
const backdrop = footage ? null : await takeBackdrop(flags, r);
|
|
12377
|
-
r.log(`probing ${
|
|
11913
|
+
r.log(`probing ${basename8(videoPath)}\u2026`);
|
|
12378
11914
|
const probed = await probeVideoFile(videoPath);
|
|
12379
11915
|
if (!(probed.durationMs > 0)) {
|
|
12380
11916
|
throw new UsageError(
|
|
@@ -12386,15 +11922,15 @@ async function cmdIngest(argv) {
|
|
|
12386
11922
|
trace = await readTraceFile(resolve14(cursorPath), offsetMs);
|
|
12387
11923
|
for (const n of trace.notes) r.log(`note: ${n}`);
|
|
12388
11924
|
}
|
|
12389
|
-
if (
|
|
11925
|
+
if (existsSync20(join23(outDir, "meta.json"))) {
|
|
12390
11926
|
const { prevDoc, kept } = await prepareReRecord(outDir);
|
|
12391
11927
|
r.log(
|
|
12392
11928
|
`note: re-ingesting into ${outDir}; kept ${kept.length ? kept.join(", ") : "nothing"}` + (prevDoc ? `; apply the previous cut with: vos plan ${outDir} --reuse` : "")
|
|
12393
11929
|
);
|
|
12394
11930
|
}
|
|
12395
|
-
await
|
|
11931
|
+
await mkdir8(outDir, { recursive: true });
|
|
12396
11932
|
const paths = await ensureTakeDir(outDir);
|
|
12397
|
-
const recording =
|
|
11933
|
+
const recording = join23(outDir, `recording.${probed.ext}`);
|
|
12398
11934
|
r.log("copying the recording\u2026");
|
|
12399
11935
|
const remuxed = await copyRecording(videoPath, recording, probed.ext);
|
|
12400
11936
|
const viewport = viewportFlag ?? trace?.viewport ?? {
|
|
@@ -12402,7 +11938,7 @@ async function cmdIngest(argv) {
|
|
|
12402
11938
|
height: probed.height
|
|
12403
11939
|
};
|
|
12404
11940
|
const events = trace?.events ?? [];
|
|
12405
|
-
const fileStat = await
|
|
11941
|
+
const fileStat = await stat3(videoPath);
|
|
12406
11942
|
const meta = {
|
|
12407
11943
|
dpr: 1,
|
|
12408
11944
|
zoom: 1,
|
|
@@ -12423,13 +11959,13 @@ async function cmdIngest(argv) {
|
|
|
12423
11959
|
const clicks = events.filter((e) => e.type === "down").length;
|
|
12424
11960
|
const seconds = (probed.durationMs / 1e3).toFixed(1);
|
|
12425
11961
|
const facts = `${seconds}s, ${probed.width}\xD7${probed.height} @ ${meta.fps} fps${probed.hasAudio ? ", audio" : ", no audio"}${remuxed ? "" : ", copied as is"}`;
|
|
12426
|
-
const cursorLine = trace ? `${events.length} cursor events (${clicks} clicks) from ${
|
|
11962
|
+
const cursorLine = trace ? `${events.length} cursor events (${clicks} clicks) from ${basename8(cursorPath)}${trace.dropped ? `, ${trace.dropped} not placed` : ""}; ${plan.zoomAuto} zoom${plan.zoomAuto === 1 ? "" : "s"} planned` : "no cursor track, so nothing was planned";
|
|
12427
11963
|
const opened = footage ? `opened as finished footage: no card, no browser bar, no drawn cursor, ${meta.fps > 30 ? 60 : 30} fps export (--as take for a screen recording's card)` : `opened as a screen recording: the card, its ground and the drawn cursor (--as footage to open it bare)`;
|
|
12428
11964
|
const next = footage && !trace ? `Next: vos push ${outDir} --wait \u2014 it lands as it is; trim or add to it in the studio` : trace ? `Next: vos digest ${outDir} \u2014 look before you cut; edit doc.json; vos push ${outDir}` : `Add --cursor <trace> recorded beside the video (a Playwright trace.zip, stamped JSON records, or a t,x,y,type CSV) to plan zooms from its clicks; else place them by eye: vos frames ${outDir} --times 0,25%,50%,75%,100%, then edit doc.json`;
|
|
12429
11965
|
r.done(
|
|
12430
11966
|
{
|
|
12431
11967
|
out: outDir,
|
|
12432
|
-
recording:
|
|
11968
|
+
recording: basename8(recording),
|
|
12433
11969
|
durationMs: probed.durationMs,
|
|
12434
11970
|
width: probed.width,
|
|
12435
11971
|
height: probed.height,
|
|
@@ -12438,7 +11974,7 @@ async function cmdIngest(argv) {
|
|
|
12438
11974
|
hasAudio: probed.hasAudio,
|
|
12439
11975
|
remuxed,
|
|
12440
11976
|
cursor: trace ? {
|
|
12441
|
-
file:
|
|
11977
|
+
file: basename8(cursorPath),
|
|
12442
11978
|
kind: trace.kind,
|
|
12443
11979
|
events: events.length,
|
|
12444
11980
|
clicks,
|
|
@@ -12450,7 +11986,7 @@ async function cmdIngest(argv) {
|
|
|
12450
11986
|
backdrop: plan.backdrop ?? null,
|
|
12451
11987
|
as: footage ? "footage" : "take"
|
|
12452
11988
|
},
|
|
12453
|
-
`Ingested ${
|
|
11989
|
+
`Ingested ${basename8(videoPath)} \u2192 ${outDir}/${basename8(recording)} (${facts})
|
|
12454
11990
|
${opened}
|
|
12455
11991
|
${cursorLine}
|
|
12456
11992
|
${next}`
|
|
@@ -12462,19 +11998,19 @@ async function cmdIngest(argv) {
|
|
|
12462
11998
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
12463
11999
|
import {
|
|
12464
12000
|
cpSync,
|
|
12465
|
-
existsSync as
|
|
12001
|
+
existsSync as existsSync22,
|
|
12466
12002
|
mkdirSync as mkdirSync6,
|
|
12467
12003
|
readFileSync as readFileSync10,
|
|
12468
12004
|
rmSync as rmSync3,
|
|
12469
12005
|
writeFileSync as writeFileSync6
|
|
12470
12006
|
} from "fs";
|
|
12471
12007
|
import { homedir as homedir2 } from "os";
|
|
12472
|
-
import { join as
|
|
12008
|
+
import { join as join25 } from "path";
|
|
12473
12009
|
import { chromium as chromium2 } from "playwright";
|
|
12474
12010
|
|
|
12475
12011
|
// src/plugin/machine.ts
|
|
12476
|
-
import { existsSync as
|
|
12477
|
-
import { dirname as dirname9, join as
|
|
12012
|
+
import { existsSync as existsSync21, readFileSync as readFileSync9, readdirSync as readdirSync5, statSync as statSync7 } from "fs";
|
|
12013
|
+
import { dirname as dirname9, join as join24 } from "path";
|
|
12478
12014
|
var AGENT_NAMES = [
|
|
12479
12015
|
"claude",
|
|
12480
12016
|
"cursor",
|
|
@@ -12494,17 +12030,17 @@ var HOME_DIRS = {
|
|
|
12494
12030
|
copilot: [".copilot"]
|
|
12495
12031
|
};
|
|
12496
12032
|
function detectAgents(root, opts = {}) {
|
|
12497
|
-
const exists = opts.exists ??
|
|
12033
|
+
const exists = opts.exists ?? existsSync21;
|
|
12498
12034
|
const out = [];
|
|
12499
12035
|
for (const agent of AGENT_NAMES) {
|
|
12500
12036
|
for (const rel of PROJECT_DIRS[agent]) {
|
|
12501
|
-
const dir =
|
|
12037
|
+
const dir = join24(root, rel);
|
|
12502
12038
|
if (exists(dir)) out.push({ agent, dir, scope: "project" });
|
|
12503
12039
|
}
|
|
12504
|
-
if (agent === "copilot" && !out.some((h) => h.agent === "copilot") && exists(
|
|
12040
|
+
if (agent === "copilot" && !out.some((h) => h.agent === "copilot") && exists(join24(root, ".github", "copilot-instructions.md"))) {
|
|
12505
12041
|
out.push({
|
|
12506
12042
|
agent,
|
|
12507
|
-
dir:
|
|
12043
|
+
dir: join24(root, ".github", "copilot"),
|
|
12508
12044
|
scope: "project"
|
|
12509
12045
|
});
|
|
12510
12046
|
}
|
|
@@ -12512,7 +12048,7 @@ function detectAgents(root, opts = {}) {
|
|
|
12512
12048
|
if (opts.global && opts.home) {
|
|
12513
12049
|
for (const agent of AGENT_NAMES) {
|
|
12514
12050
|
for (const rel of HOME_DIRS[agent]) {
|
|
12515
|
-
const dir =
|
|
12051
|
+
const dir = join24(opts.home, rel);
|
|
12516
12052
|
if (exists(dir)) out.push({ agent, dir, scope: "global" });
|
|
12517
12053
|
}
|
|
12518
12054
|
}
|
|
@@ -12530,26 +12066,26 @@ function agentsFromFlag(value, root) {
|
|
|
12530
12066
|
return {
|
|
12531
12067
|
homes: wanted.map((agent) => ({
|
|
12532
12068
|
agent,
|
|
12533
|
-
dir:
|
|
12069
|
+
dir: join24(root, PROJECT_DIRS[agent][0]),
|
|
12534
12070
|
scope: "project"
|
|
12535
12071
|
}))
|
|
12536
12072
|
};
|
|
12537
12073
|
}
|
|
12538
12074
|
function skillsDirFor(home) {
|
|
12539
|
-
return
|
|
12075
|
+
return join24(home.dir, "skills");
|
|
12540
12076
|
}
|
|
12541
12077
|
function fallbackSkillsHome(root) {
|
|
12542
|
-
return { agent: "codex", dir:
|
|
12078
|
+
return { agent: "codex", dir: join24(root, ".agents"), scope: "project" };
|
|
12543
12079
|
}
|
|
12544
|
-
function installedSkills(dir, exists =
|
|
12080
|
+
function installedSkills(dir, exists = existsSync21, list = (p) => {
|
|
12545
12081
|
try {
|
|
12546
|
-
return
|
|
12082
|
+
return readdirSync5(p);
|
|
12547
12083
|
} catch {
|
|
12548
12084
|
return [];
|
|
12549
12085
|
}
|
|
12550
12086
|
}) {
|
|
12551
12087
|
if (!exists(dir)) return [];
|
|
12552
|
-
return list(dir).filter((name) => exists(
|
|
12088
|
+
return list(dir).filter((name) => exists(join24(dir, name, "SKILL.md"))).sort();
|
|
12553
12089
|
}
|
|
12554
12090
|
var RULES_BEGIN = "<!-- vos:begin (written by vos setup; the block is replaced on the next run) -->";
|
|
12555
12091
|
var RULES_END = "<!-- vos:end -->";
|
|
@@ -12582,10 +12118,10 @@ ${RULES_BLOCK}
|
|
|
12582
12118
|
}
|
|
12583
12119
|
return { text, changed: text !== current };
|
|
12584
12120
|
}
|
|
12585
|
-
function rulesFileFor(root, exists =
|
|
12586
|
-
const agents =
|
|
12121
|
+
function rulesFileFor(root, exists = existsSync21) {
|
|
12122
|
+
const agents = join24(root, "AGENTS.md");
|
|
12587
12123
|
if (exists(agents)) return agents;
|
|
12588
|
-
const claude =
|
|
12124
|
+
const claude = join24(root, "CLAUDE.md");
|
|
12589
12125
|
if (exists(claude)) return claude;
|
|
12590
12126
|
return agents;
|
|
12591
12127
|
}
|
|
@@ -12594,14 +12130,14 @@ function chromeCandidates(platform, env) {
|
|
|
12594
12130
|
return ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"];
|
|
12595
12131
|
if (platform === "win32")
|
|
12596
12132
|
return [
|
|
12597
|
-
|
|
12133
|
+
join24(
|
|
12598
12134
|
env["PROGRAMFILES"] ?? "C:\\Program Files",
|
|
12599
12135
|
"Google",
|
|
12600
12136
|
"Chrome",
|
|
12601
12137
|
"Application",
|
|
12602
12138
|
"chrome.exe"
|
|
12603
12139
|
),
|
|
12604
|
-
|
|
12140
|
+
join24(
|
|
12605
12141
|
env["LOCALAPPDATA"] ?? "",
|
|
12606
12142
|
"Google",
|
|
12607
12143
|
"Chrome",
|
|
@@ -12688,13 +12224,13 @@ function doctorReport(facts) {
|
|
|
12688
12224
|
function bundledSkillsDir(from) {
|
|
12689
12225
|
let dir = dirname9(from);
|
|
12690
12226
|
for (let i = 0; i < 6; i++) {
|
|
12691
|
-
const pkg =
|
|
12692
|
-
if (
|
|
12227
|
+
const pkg = join24(dir, "package.json");
|
|
12228
|
+
if (existsSync21(pkg)) {
|
|
12693
12229
|
try {
|
|
12694
12230
|
const name = JSON.parse(readFileSync9(pkg, "utf8")).name;
|
|
12695
12231
|
if (name === "@vosjs/cli") {
|
|
12696
|
-
const skills =
|
|
12697
|
-
return
|
|
12232
|
+
const skills = join24(dir, "skills");
|
|
12233
|
+
return existsSync21(skills) && statSync7(skills).isDirectory() ? skills : null;
|
|
12698
12234
|
}
|
|
12699
12235
|
} catch {
|
|
12700
12236
|
}
|
|
@@ -12725,7 +12261,7 @@ function npx() {
|
|
|
12725
12261
|
function bundledChromium() {
|
|
12726
12262
|
try {
|
|
12727
12263
|
const p = chromium2.executablePath();
|
|
12728
|
-
return p &&
|
|
12264
|
+
return p && existsSync22(p) ? p : null;
|
|
12729
12265
|
} catch {
|
|
12730
12266
|
return null;
|
|
12731
12267
|
}
|
|
@@ -12734,7 +12270,7 @@ function probe2() {
|
|
|
12734
12270
|
return probeBrowser({
|
|
12735
12271
|
platform: process.platform,
|
|
12736
12272
|
env: process.env,
|
|
12737
|
-
exists:
|
|
12273
|
+
exists: existsSync22,
|
|
12738
12274
|
bundled: bundledChromium
|
|
12739
12275
|
});
|
|
12740
12276
|
}
|
|
@@ -12838,7 +12374,7 @@ async function cmdSetup(argv) {
|
|
|
12838
12374
|
const dest = skillsDirFor(home);
|
|
12839
12375
|
mkdirSync6(dest, { recursive: true });
|
|
12840
12376
|
for (const name of installedSkills(bundled)) {
|
|
12841
|
-
cpSync(
|
|
12377
|
+
cpSync(join25(bundled, name), join25(dest, name), { recursive: true });
|
|
12842
12378
|
}
|
|
12843
12379
|
}
|
|
12844
12380
|
skillsVia = "bundled";
|
|
@@ -12873,7 +12409,7 @@ async function cmdSetup(argv) {
|
|
|
12873
12409
|
let rules = null;
|
|
12874
12410
|
if (!noRules) {
|
|
12875
12411
|
const file = rulesFileFor(root);
|
|
12876
|
-
const existing =
|
|
12412
|
+
const existing = existsSync22(file) ? readFileSync10(file, "utf8") : null;
|
|
12877
12413
|
const { text, changed } = applyRulesBlock(existing);
|
|
12878
12414
|
if (changed) writeFileSync6(file, text);
|
|
12879
12415
|
rules = { file, changed };
|
|
@@ -12987,7 +12523,7 @@ async function cmdWhoami(argv) {
|
|
|
12987
12523
|
function cmdLogout(argv) {
|
|
12988
12524
|
const { flags } = parseArgs(argv, BOOLEAN_FLAGS11);
|
|
12989
12525
|
const r = reporterFor(flags);
|
|
12990
|
-
const had =
|
|
12526
|
+
const had = existsSync22(CREDENTIALS_PATH);
|
|
12991
12527
|
rmSync3(CREDENTIALS_PATH, { force: true });
|
|
12992
12528
|
const envKey = Boolean(process.env.VOS_API_KEY?.trim());
|
|
12993
12529
|
r.done(
|
|
@@ -13341,7 +12877,7 @@ lint-gated, so a bad override fails like a bad doc.json):
|
|
|
13341
12877
|
first ready loop (the house backdrop the studio opens on), or a flat ground offline
|
|
13342
12878
|
`;
|
|
13343
12879
|
async function loadActions(file) {
|
|
13344
|
-
const raw = JSON.parse(await
|
|
12880
|
+
const raw = JSON.parse(await readFile15(file, "utf8"));
|
|
13345
12881
|
const errors = validateActions(raw);
|
|
13346
12882
|
if (errors.length)
|
|
13347
12883
|
throw new UsageError(`invalid actions file:
|
|
@@ -13404,7 +12940,7 @@ function takeStorageState(flags) {
|
|
|
13404
12940
|
const raw = strFlag(flags, "storage-state");
|
|
13405
12941
|
if (!raw) return void 0;
|
|
13406
12942
|
const path = resolve15(raw);
|
|
13407
|
-
if (!
|
|
12943
|
+
if (!existsSync23(path))
|
|
13408
12944
|
throw new UsageError(`--storage-state: no such file: ${path}`);
|
|
13409
12945
|
return path;
|
|
13410
12946
|
}
|
|
@@ -13417,7 +12953,7 @@ function takeSessionName(flags, storageState) {
|
|
|
13417
12953
|
throw new UsageError(
|
|
13418
12954
|
"--session and --storage-state are two doors to one take: a persistent profile cannot take a storage state. Pass one"
|
|
13419
12955
|
);
|
|
13420
|
-
if (!
|
|
12956
|
+
if (!existsSync23(sessionDir(name)))
|
|
13421
12957
|
throw new UsageError(
|
|
13422
12958
|
`no session "${name}". Make one: vos session open <url> --name ${name} (a Chrome window opens; sign in and close it)`
|
|
13423
12959
|
);
|
|
@@ -13609,7 +13145,7 @@ async function cmdRecord(argv) {
|
|
|
13609
13145
|
const what = s.selector ? ` ${s.selector}` : "";
|
|
13610
13146
|
return ` ${s.skipped ? "\u2717" : "\u2713"} #${s.step}${s.id ? ` (${s.id})` : ""} ${s.do}${what}${s.skipped ? " NOT FOUND" : rect}${s.navigated ? " \u2192 navigated" : ""}`;
|
|
13611
13147
|
});
|
|
13612
|
-
const
|
|
13148
|
+
const failed = rec.skipped.length > 0 || rec.navTimeout || maskMisses(rec).length > 0;
|
|
13613
13149
|
const carried = [
|
|
13614
13150
|
...strFlag(flags, "storage-state") ? [`--storage-state ${strFlag(flags, "storage-state")}`] : [],
|
|
13615
13151
|
...browserArgs.map((a) => `--browser-arg=${a}`),
|
|
@@ -13621,7 +13157,7 @@ async function cmdRecord(argv) {
|
|
|
13621
13157
|
r.done(
|
|
13622
13158
|
{
|
|
13623
13159
|
dryRun: true,
|
|
13624
|
-
ok: !
|
|
13160
|
+
ok: !failed,
|
|
13625
13161
|
seconds: +((Date.now() - started) / 1e3).toFixed(1),
|
|
13626
13162
|
steps,
|
|
13627
13163
|
skipped: rec.skipped,
|
|
@@ -13629,24 +13165,24 @@ async function cmdRecord(argv) {
|
|
|
13629
13165
|
exposures: rec.exposures,
|
|
13630
13166
|
masks: rec.masks
|
|
13631
13167
|
},
|
|
13632
|
-
`${
|
|
13168
|
+
`${failed ? "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)" : ""}
|
|
13633
13169
|
${lines.join("\n")}
|
|
13634
|
-
${maskMisses(rec).length ? `MASK hid nothing: ${maskMisses(rec).join(", ")} (the selector reached no element). ` : ""}${
|
|
13170
|
+
${maskMisses(rec).length ? `MASK hid nothing: ${maskMisses(rec).join(", ")} (the selector reached no element). ` : ""}${failed ? "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)}`
|
|
13635
13171
|
);
|
|
13636
|
-
return
|
|
13172
|
+
return failed ? EXIT_USAGE : EXIT_OK;
|
|
13637
13173
|
} finally {
|
|
13638
13174
|
await browser2.close();
|
|
13639
13175
|
}
|
|
13640
13176
|
}
|
|
13641
13177
|
const paths = takePaths(outDir);
|
|
13642
13178
|
const prepare = async () => {
|
|
13643
|
-
if (
|
|
13179
|
+
if (existsSync23(join26(outDir, "meta.json"))) {
|
|
13644
13180
|
const { prevDoc, kept } = await prepareReRecord(outDir);
|
|
13645
13181
|
r.log(
|
|
13646
13182
|
`note: re-recording ${outDir} \u2014 kept ${kept.length ? kept.join(", ") : "nothing"}` + (prevDoc ? "; apply the previous cut to the new footage with: vos plan " + outDir + " --reuse" : "")
|
|
13647
13183
|
);
|
|
13648
13184
|
}
|
|
13649
|
-
await
|
|
13185
|
+
await mkdir9(outDir, { recursive: true });
|
|
13650
13186
|
await ensureTakeDir(outDir);
|
|
13651
13187
|
};
|
|
13652
13188
|
const browser = await launchBrowser(browserArgs);
|
|
@@ -13705,7 +13241,7 @@ ${lines.join("\n")}
|
|
|
13705
13241
|
},
|
|
13706
13242
|
strictFail ? `STRICT: take recorded but incomplete \u2014 ${strictReason(rec)}; fix the flow and re-record.${skippedNote}${exposureNote(rec)}` : `Take ready: ${outDir}
|
|
13707
13243
|
${(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 \xB7 ${rec.dead.pct}% dead${deadWarns(rec.dead) ? " \u26A0 " + deadLine(rec.dead) : ""}${skippedNote}
|
|
13708
|
-
Next: edit ${
|
|
13244
|
+
Next: edit ${join26(outDir, "doc.json")} (optional), then: vos render ${outDir}${exposureNote(rec)}`
|
|
13709
13245
|
);
|
|
13710
13246
|
return strictFail ? EXIT_USAGE : EXIT_OK;
|
|
13711
13247
|
} finally {
|
|
@@ -13731,7 +13267,7 @@ async function cmdCreate2(argv) {
|
|
|
13731
13267
|
throw new UsageError('no URL \u2014 set "url" in the actions file or pass --url');
|
|
13732
13268
|
const outDir = resolve15(strFlag(flags, "out") ?? "take");
|
|
13733
13269
|
const format = videoFormat(strFlag(flags, "format"), positionals[0]);
|
|
13734
|
-
const out = positionals[0] ??
|
|
13270
|
+
const out = positionals[0] ?? join26(outDir, `out.${format}`);
|
|
13735
13271
|
const parallel = numFlag(flags, "parallel", 1);
|
|
13736
13272
|
if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
|
|
13737
13273
|
throw new UsageError("--parallel expects an integer between 1 and 16");
|
|
@@ -13744,13 +13280,13 @@ async function cmdCreate2(argv) {
|
|
|
13744
13280
|
const headers = takeHeaders(multi);
|
|
13745
13281
|
const paths = takePaths(outDir);
|
|
13746
13282
|
const prepare = async () => {
|
|
13747
|
-
if (
|
|
13283
|
+
if (existsSync23(join26(outDir, "meta.json"))) {
|
|
13748
13284
|
const { prevDoc, kept } = await prepareReRecord(outDir);
|
|
13749
13285
|
r.log(
|
|
13750
13286
|
`note: re-recording ${outDir} \u2014 kept ${kept.length ? kept.join(", ") : "nothing"}` + (prevDoc ? "; apply the previous cut to the new footage with: vos plan " + outDir + " --reuse" : "")
|
|
13751
13287
|
);
|
|
13752
13288
|
}
|
|
13753
|
-
await
|
|
13289
|
+
await mkdir9(outDir, { recursive: true });
|
|
13754
13290
|
await ensureTakeDir(outDir);
|
|
13755
13291
|
};
|
|
13756
13292
|
const browser = await launchBrowser(browserArgs);
|
|
@@ -13851,24 +13387,24 @@ async function cmdPlan(argv) {
|
|
|
13851
13387
|
);
|
|
13852
13388
|
const r = createReporter(flags.json === true);
|
|
13853
13389
|
if (flags.fresh === true) {
|
|
13854
|
-
await
|
|
13390
|
+
await rm3(join26(dir, "doc.json"), { force: true });
|
|
13855
13391
|
r.log("note: --fresh discarded the existing doc.json");
|
|
13856
13392
|
}
|
|
13857
13393
|
let reuse;
|
|
13858
13394
|
if (flags.reuse === true) {
|
|
13859
|
-
const from = resolve15(strFlag(flags, "from") ??
|
|
13860
|
-
if (!
|
|
13395
|
+
const from = resolve15(strFlag(flags, "from") ?? join26(dir, PREV_DOC_NAME));
|
|
13396
|
+
if (!existsSync23(from)) {
|
|
13861
13397
|
throw new UsageError(
|
|
13862
13398
|
`--reuse: ${from} does not exist \u2014 a re-record into this take writes doc.prev.json, or pass --from <doc.json>`
|
|
13863
13399
|
);
|
|
13864
13400
|
}
|
|
13865
13401
|
reuse = {
|
|
13866
13402
|
from,
|
|
13867
|
-
doc: JSON.parse(await
|
|
13403
|
+
doc: JSON.parse(await readFile15(from, "utf8"))
|
|
13868
13404
|
};
|
|
13869
13405
|
}
|
|
13870
13406
|
const style = await resolveStyleRef(flags);
|
|
13871
|
-
const hasDoc =
|
|
13407
|
+
const hasDoc = existsSync23(join26(dir, "doc.json"));
|
|
13872
13408
|
const backdrop = hasDoc ? null : await takeBackdrop(flags, r);
|
|
13873
13409
|
const release = await releaseInputs(dir, flags, r, multi.with ?? []);
|
|
13874
13410
|
const motionWanted = !hasDoc || flags.motion === true || flags.fresh === true;
|
|
@@ -13926,7 +13462,7 @@ async function cmdPlan(argv) {
|
|
|
13926
13462
|
...s.motion ? { motion: s.motion } : {},
|
|
13927
13463
|
...s.reuse ? { reuse: s.reuse } : {}
|
|
13928
13464
|
},
|
|
13929
|
-
`${s.reuse ? "Reused" : s.fresh ? "Planned" : "Refreshed"} ${
|
|
13465
|
+
`${s.reuse ? "Reused" : s.fresh ? "Planned" : "Refreshed"} ${join26(dir, "doc.json")}: ${s.zoomAuto} auto + ${s.zoomManual} manual zoom spans${s.cursorKept ? "" : " (cursor track dropped)"}${layoutLines}${motionLines}${reuseLines}`
|
|
13930
13466
|
);
|
|
13931
13467
|
return EXIT_OK;
|
|
13932
13468
|
}
|
|
@@ -14049,7 +13585,7 @@ async function cmdRender(argv) {
|
|
|
14049
13585
|
);
|
|
14050
13586
|
const r = createReporter(flags.json === true);
|
|
14051
13587
|
const format = videoFormat(strFlag(flags, "format"), positionals[1]);
|
|
14052
|
-
const out = positionals[1] ??
|
|
13588
|
+
const out = positionals[1] ?? join26(dir, `out.${format}`);
|
|
14053
13589
|
const parallel = numFlag(flags, "parallel", 1);
|
|
14054
13590
|
if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
|
|
14055
13591
|
throw new UsageError("--parallel expects an integer between 1 and 16");
|
|
@@ -14296,15 +13832,15 @@ async function resolveStyleRef(flags) {
|
|
|
14296
13832
|
}
|
|
14297
13833
|
async function resolveDocRef(ref, flags, what) {
|
|
14298
13834
|
const styleRef = ref;
|
|
14299
|
-
const file =
|
|
13835
|
+
const file = existsSync23(styleRef) ? resolve15(
|
|
14300
13836
|
styleRef,
|
|
14301
|
-
|
|
13837
|
+
existsSync23(join26(styleRef, "doc.json")) ? "doc.json" : ""
|
|
14302
13838
|
) : null;
|
|
14303
|
-
if (file &&
|
|
13839
|
+
if (file && existsSync23(file)) {
|
|
14304
13840
|
return {
|
|
14305
13841
|
from: file,
|
|
14306
13842
|
doc: migrateHostedDoc5(
|
|
14307
|
-
JSON.parse(await
|
|
13843
|
+
JSON.parse(await readFile15(file, "utf8"))
|
|
14308
13844
|
)
|
|
14309
13845
|
};
|
|
14310
13846
|
}
|
|
@@ -14356,7 +13892,7 @@ async function cmdDigest(argv) {
|
|
|
14356
13892
|
const take = await loadTake(dir);
|
|
14357
13893
|
if (!take.doc) throw new UsageError(`${dir} has no doc.json \u2014 run plan first`);
|
|
14358
13894
|
const transcriptPath = strFlag(flags, "transcript");
|
|
14359
|
-
const transcript = transcriptPath ? parseTranscript(JSON.parse(await
|
|
13895
|
+
const transcript = transcriptPath ? parseTranscript(JSON.parse(await readFile15(transcriptPath, "utf8"))) : null;
|
|
14360
13896
|
const style = await resolveStyleRef(flags);
|
|
14361
13897
|
const noFrames = flags["no-frames"] === true;
|
|
14362
13898
|
let browser = null;
|
|
@@ -14387,7 +13923,7 @@ async function cmdDigest(argv) {
|
|
|
14387
13923
|
kinds,
|
|
14388
13924
|
frames: d.images.full,
|
|
14389
13925
|
crops: d.images.crop,
|
|
14390
|
-
sheet: d.images.sheet ?
|
|
13926
|
+
sheet: d.images.sheet ? join26(result.outDir, d.images.sheet) : null,
|
|
14391
13927
|
scenes: kinds.scene ?? 0,
|
|
14392
13928
|
sourceDuration: d.take.sourceDuration,
|
|
14393
13929
|
outputDuration: d.take.outputDuration,
|
|
@@ -14415,7 +13951,7 @@ async function cmdOpen(argv) {
|
|
|
14415
13951
|
if (!dir) throw new UsageError("vos open <take> [--studio <url>]");
|
|
14416
13952
|
const r = createReporter(flags.json === true);
|
|
14417
13953
|
const take = await loadTake(dir);
|
|
14418
|
-
if (!
|
|
13954
|
+
if (!existsSync23(take.paths.recording)) {
|
|
14419
13955
|
throw new UsageError(`${dir} has no recording \u2014 re-run record`);
|
|
14420
13956
|
}
|
|
14421
13957
|
const studio = (strFlag(flags, "studio") ?? platformOrigin()).replace(
|
|
@@ -14456,12 +13992,12 @@ async function cmdJudge(argv) {
|
|
|
14456
13992
|
"vos judge <kit.json> --against <MANIFEST.json> [--out dir] [--json]\n(the manifest names the reference set: id, file, role, layout, facts, rule per asset)"
|
|
14457
13993
|
);
|
|
14458
13994
|
const r = createReporter(flags.json === true);
|
|
14459
|
-
const kitFile = target.endsWith("kit.json") ? target :
|
|
14460
|
-
if (!
|
|
14461
|
-
if (!
|
|
13995
|
+
const kitFile = target.endsWith("kit.json") ? target : join26(target, "kit.json");
|
|
13996
|
+
if (!existsSync23(kitFile)) throw new UsageError(`${kitFile}: no kit manifest`);
|
|
13997
|
+
if (!existsSync23(against))
|
|
14462
13998
|
throw new UsageError(`${against}: no reference manifest`);
|
|
14463
13999
|
const result = await judgeKit(kitFile, against, strFlag(flags, "out"));
|
|
14464
|
-
const verdicts = JSON.parse(await
|
|
14000
|
+
const verdicts = JSON.parse(await readFile15(result.verdictFile, "utf8")).verdicts;
|
|
14465
14001
|
const rate = winRate(verdicts);
|
|
14466
14002
|
const lines = result.sheets.map(
|
|
14467
14003
|
(s) => `${s.asset} vs ${s.reference}: ${s.sheetA}, ${s.sheetB}, ${s.rubric}`
|
|
@@ -14501,7 +14037,7 @@ async function cmdCallout(argv) {
|
|
|
14501
14037
|
`no register: put a BRAND.md beside the take (vos brand <url> --out BRAND.md; its bgA and accent are the ground and the accent), or pass --ground #hex --accent #hex`
|
|
14502
14038
|
);
|
|
14503
14039
|
const codeFile = strFlag(flags, "code-file");
|
|
14504
|
-
const code = codeFile ? await
|
|
14040
|
+
const code = codeFile ? await readFile15(codeFile, "utf8") : strFlag(flags, "code");
|
|
14505
14041
|
const words2 = {
|
|
14506
14042
|
kicker: strFlag(flags, "kicker"),
|
|
14507
14043
|
title: strFlag(flags, "title"),
|
|
@@ -14580,7 +14116,7 @@ async function cmdValidate(argv) {
|
|
|
14580
14116
|
const target = positionals[0];
|
|
14581
14117
|
if (!target) throw new UsageError("vos validate <actions.json|take>");
|
|
14582
14118
|
const r = createReporter(flags.json === true);
|
|
14583
|
-
const kitTarget = target.endsWith("kit.json") ? target :
|
|
14119
|
+
const kitTarget = target.endsWith("kit.json") ? target : existsSync23(join26(target, "kit.json")) && !isTakeDir(target) ? join26(target, "kit.json") : null;
|
|
14584
14120
|
if (kitTarget) {
|
|
14585
14121
|
const picture = flags.picture === true;
|
|
14586
14122
|
const verdict = await validateKit(kitTarget, { picture });
|
|
@@ -14612,9 +14148,9 @@ async function cmdValidate(argv) {
|
|
|
14612
14148
|
r.done({ valid: true, target }, `${target}: valid actions file`);
|
|
14613
14149
|
return EXIT_OK;
|
|
14614
14150
|
}
|
|
14615
|
-
if (!isTakeDir(target) &&
|
|
14151
|
+
if (!isTakeDir(target) && existsSync23(join26(target, "config.json"))) {
|
|
14616
14152
|
const parsed = JSON.parse(
|
|
14617
|
-
await
|
|
14153
|
+
await readFile15(join26(target, "config.json"), "utf8")
|
|
14618
14154
|
);
|
|
14619
14155
|
const pre = preflightConfig(parsed);
|
|
14620
14156
|
const problems2 = pre.ok ? [] : pre.issues.map((i) => `config.json: ${i}`);
|
|
@@ -14655,7 +14191,7 @@ async function cmdValidate(argv) {
|
|
|
14655
14191
|
const take = await loadTake(target);
|
|
14656
14192
|
const problems = [];
|
|
14657
14193
|
const warnings = [];
|
|
14658
|
-
if (!
|
|
14194
|
+
if (!existsSync23(take.paths.recording)) {
|
|
14659
14195
|
problems.push("missing recording (re-run record)");
|
|
14660
14196
|
} else {
|
|
14661
14197
|
const mismatch = containerMismatch(
|
|
@@ -15007,7 +14543,7 @@ async function cmdSession(argv) {
|
|
|
15007
14543
|
if (!arg) throw new UsageError(usage);
|
|
15008
14544
|
const bad = sessionNameVerdict(arg);
|
|
15009
14545
|
if (bad) throw new UsageError(bad);
|
|
15010
|
-
if (!
|
|
14546
|
+
if (!existsSync23(sessionDir(arg)))
|
|
15011
14547
|
throw new UsageError(`no session "${arg}" (vos session list)`);
|
|
15012
14548
|
const summary3 = await summarizeSession(arg);
|
|
15013
14549
|
const url = strFlag(flags, "url");
|
|
@@ -15088,10 +14624,6 @@ async function cmdSession(argv) {
|
|
|
15088
14624
|
|
|
15089
14625
|
export {
|
|
15090
14626
|
validateActions,
|
|
15091
|
-
RECORDING_NAME,
|
|
15092
|
-
takePaths,
|
|
15093
|
-
loadTake,
|
|
15094
|
-
pullMedia,
|
|
15095
14627
|
startTakeServer,
|
|
15096
14628
|
waitForPageDone,
|
|
15097
14629
|
renderAnimation,
|
|
@@ -15111,4 +14643,4 @@ export {
|
|
|
15111
14643
|
verbHelp,
|
|
15112
14644
|
run
|
|
15113
14645
|
};
|
|
15114
|
-
//# sourceMappingURL=chunk-
|
|
14646
|
+
//# sourceMappingURL=chunk-QL25OPZB.js.map
|