@vosjs/cli 0.59.0 → 0.60.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/assetManifest-MM5WGOWG.js +15 -0
- package/dist/chunk-36P5RREC.js +648 -0
- package/dist/chunk-36P5RREC.js.map +1 -0
- package/dist/{chunk-67L7FPJR.js → chunk-NE5MHC3D.js} +497 -924
- package/dist/chunk-NE5MHC3D.js.map +1 -0
- package/dist/{chunk-YC3LY4LW.js → chunk-OMDRILG7.js} +48 -539
- package/dist/chunk-OMDRILG7.js.map +1 -0
- package/dist/chunk-PUXY32AP.js +670 -0
- package/dist/chunk-PUXY32AP.js.map +1 -0
- package/dist/cli.js +67 -15
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +15 -11
- package/dist/{run-RJEBQFID.js → run-5DLEKBK2.js} +5 -3
- package/dist/run-5DLEKBK2.js.map +1 -0
- package/package.json +7 -7
- package/schema/doc.schema.json +28 -1
- package/dist/chunk-67L7FPJR.js.map +0 -1
- package/dist/chunk-YC3LY4LW.js.map +0 -1
- /package/dist/{run-RJEBQFID.js.map → assetManifest-MM5WGOWG.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-OMDRILG7.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-PUXY32AP.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-36P5RREC.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,
|
|
@@ -176,6 +204,7 @@ import {
|
|
|
176
204
|
pinCandidates,
|
|
177
205
|
pinRectOnScreen,
|
|
178
206
|
pinReferent,
|
|
207
|
+
parseEmphasis,
|
|
179
208
|
ratedSegments,
|
|
180
209
|
recommendedExportResolution,
|
|
181
210
|
resolvePins,
|
|
@@ -479,6 +508,23 @@ function lintDoc(docIn) {
|
|
|
479
508
|
if (z.focusMode !== void 0 && z.focusMode !== "manual" && z.focusMode !== "auto") {
|
|
480
509
|
problems.push(`${name}: focusMode must be 'manual' or 'auto'`);
|
|
481
510
|
}
|
|
511
|
+
if (z.screen !== void 0) {
|
|
512
|
+
const sp = z.screen;
|
|
513
|
+
const ok = !!sp && typeof sp === "object" && isNum(sp.x) && isNum(sp.y) && sp.x >= 0 && sp.x <= 1 && sp.y >= 0 && sp.y <= 1;
|
|
514
|
+
if (!ok) {
|
|
515
|
+
problems.push(
|
|
516
|
+
`${name}: screen must be { x, y } in 0..1 \u2014 where the target lands as FRACTIONS of the frame (0.35, 0.5 = a third across, centred), not pixels`
|
|
517
|
+
);
|
|
518
|
+
} else if (z.focusMode === "auto") {
|
|
519
|
+
warnings.push(
|
|
520
|
+
`${name}: screen is ignored on a span that follows the cursor (focusMode 'auto'); drop one or the other`
|
|
521
|
+
);
|
|
522
|
+
} else if (cameraModel(doc.frame) !== "stage") {
|
|
523
|
+
warnings.push(
|
|
524
|
+
`${name}: screen places the target under the stage camera only (frame.camera: 'stage'); the magnifier keeps the target where it is`
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
482
528
|
if (z.source !== void 0 && z.source !== "manual" && z.source !== "auto") {
|
|
483
529
|
problems.push(`${name}: source must be 'manual' or 'auto'`);
|
|
484
530
|
}
|
|
@@ -1000,6 +1046,23 @@ function lintDoc(docIn) {
|
|
|
1000
1046
|
`${name}.preset "${String(o.preset)}" is not a known preset (${PRESETS.join("|")}) \u2014 falls back to "title"`
|
|
1001
1047
|
);
|
|
1002
1048
|
}
|
|
1049
|
+
if (o.emphasis !== void 0) {
|
|
1050
|
+
const em = o.emphasis;
|
|
1051
|
+
if (!em || typeof em !== "object" || Array.isArray(em)) {
|
|
1052
|
+
problems.push(
|
|
1053
|
+
`${name}.emphasis must be { weight?, color? } ({} turns *markers* on)`
|
|
1054
|
+
);
|
|
1055
|
+
} else {
|
|
1056
|
+
if (em.weight !== void 0 && (!isNum(em.weight) || em.weight < 100 || em.weight > 900))
|
|
1057
|
+
problems.push(`${name}.emphasis.weight must be 100..900`);
|
|
1058
|
+
if (em.color !== void 0 && typeof em.color !== "string")
|
|
1059
|
+
problems.push(`${name}.emphasis.color must be a CSS colour`);
|
|
1060
|
+
}
|
|
1061
|
+
} else if (typeof o.text === "string" && parseEmphasis(o.text) !== null) {
|
|
1062
|
+
warnings.push(
|
|
1063
|
+
`${name}.text has *marked* words but no emphasis, so the asterisks show as typed; add emphasis: {} to set them bold (\\* keeps one literal)`
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1003
1066
|
if (o.size !== void 0 && (!isNum(o.size) || o.size < 12 || o.size > 200)) {
|
|
1004
1067
|
problems.push(`${name}.size must be design px in 12..200`);
|
|
1005
1068
|
}
|
|
@@ -1632,7 +1695,13 @@ function framingWarnings(docIn, doc, zoom, overlays, warnings) {
|
|
|
1632
1695
|
}
|
|
1633
1696
|
const rect = { x: x0, y: y0, w: x1 - x0, h: y1 - y0 };
|
|
1634
1697
|
if (!zoomCoversRect(
|
|
1635
|
-
{
|
|
1698
|
+
{
|
|
1699
|
+
level: z.level,
|
|
1700
|
+
cx: z.cx,
|
|
1701
|
+
cy: z.cy,
|
|
1702
|
+
screen: z.screen,
|
|
1703
|
+
focusMode: z.focusMode
|
|
1704
|
+
},
|
|
1636
1705
|
rect,
|
|
1637
1706
|
layout,
|
|
1638
1707
|
0.02,
|
|
@@ -1772,535 +1841,6 @@ function framingWarnings(docIn, doc, zoom, overlays, warnings) {
|
|
|
1772
1841
|
}
|
|
1773
1842
|
}
|
|
1774
1843
|
|
|
1775
|
-
// src/plugin/media.ts
|
|
1776
|
-
import { isKeyedOverlay } from "@vosjs/studio-core";
|
|
1777
|
-
import { createHash } from "crypto";
|
|
1778
|
-
import { createWriteStream, existsSync as existsSync2 } from "fs";
|
|
1779
|
-
import {
|
|
1780
|
-
mkdir as mkdir2,
|
|
1781
|
-
open,
|
|
1782
|
-
readFile as readFile2,
|
|
1783
|
-
readdir,
|
|
1784
|
-
rename as rename2,
|
|
1785
|
-
rm as rm2,
|
|
1786
|
-
stat,
|
|
1787
|
-
writeFile as writeFile2
|
|
1788
|
-
} from "fs/promises";
|
|
1789
|
-
import { basename as basename2, dirname, join as join2 } from "path";
|
|
1790
|
-
import { Readable } from "stream";
|
|
1791
|
-
import { pipeline } from "stream/promises";
|
|
1792
|
-
|
|
1793
|
-
// src/plugin/take.ts
|
|
1794
|
-
import { mkdir, readFile, rename, rm, writeFile } from "fs/promises";
|
|
1795
|
-
import { existsSync, readdirSync, statSync } from "fs";
|
|
1796
|
-
import { basename, join } from "path";
|
|
1797
|
-
var RECORDING_NAME = "recording.webm";
|
|
1798
|
-
function takeMediaFiles(dir, stem) {
|
|
1799
|
-
return TAKE_MEDIA_EXTENSIONS.map((ext) => join(dir, `${stem}${ext}`)).filter(
|
|
1800
|
-
(file) => existsSync(file)
|
|
1801
|
-
);
|
|
1802
|
-
}
|
|
1803
|
-
function takeMediaFile(dir, stem, fallback) {
|
|
1804
|
-
return takeMediaFiles(dir, stem)[0] ?? join(dir, fallback);
|
|
1805
|
-
}
|
|
1806
|
-
var PREV_DOC_NAME = "doc.prev.json";
|
|
1807
|
-
function takePaths(dir) {
|
|
1808
|
-
const recording = takeMediaFile(dir, "recording", RECORDING_NAME);
|
|
1809
|
-
return {
|
|
1810
|
-
dir,
|
|
1811
|
-
recording,
|
|
1812
|
-
recordingName: basename(recording),
|
|
1813
|
-
framesDir: join(dir, "frames"),
|
|
1814
|
-
framesIndex: join(dir, "frames.json"),
|
|
1815
|
-
cursor: join(dir, "cursor.json"),
|
|
1816
|
-
meta: join(dir, "meta.json"),
|
|
1817
|
-
actions: join(dir, "actions.json"),
|
|
1818
|
-
doc: join(dir, "doc.json")
|
|
1819
|
-
};
|
|
1820
|
-
}
|
|
1821
|
-
async function ensureTakeDir(dir) {
|
|
1822
|
-
const p = takePaths(dir);
|
|
1823
|
-
await mkdir(p.framesDir, { recursive: true });
|
|
1824
|
-
return p;
|
|
1825
|
-
}
|
|
1826
|
-
async function dropScreencastFrames(p) {
|
|
1827
|
-
if (!existsSync(p.recording) || statSync(p.recording).size === 0) return 0;
|
|
1828
|
-
if (!existsSync(p.framesDir)) return 0;
|
|
1829
|
-
let freed = 0;
|
|
1830
|
-
for (const name of readdirSync(p.framesDir)) {
|
|
1831
|
-
try {
|
|
1832
|
-
freed += statSync(join(p.framesDir, name)).size;
|
|
1833
|
-
} catch {
|
|
1834
|
-
}
|
|
1835
|
-
}
|
|
1836
|
-
await rm(p.framesDir, { recursive: true, force: true });
|
|
1837
|
-
await rm(p.framesIndex, { force: true });
|
|
1838
|
-
return freed;
|
|
1839
|
-
}
|
|
1840
|
-
async function prepareReRecord(dir) {
|
|
1841
|
-
const p = takePaths(dir);
|
|
1842
|
-
const kept = [];
|
|
1843
|
-
let prevDoc = false;
|
|
1844
|
-
if (existsSync(p.doc)) {
|
|
1845
|
-
await rename(p.doc, join(dir, PREV_DOC_NAME));
|
|
1846
|
-
prevDoc = true;
|
|
1847
|
-
kept.push(PREV_DOC_NAME);
|
|
1848
|
-
}
|
|
1849
|
-
if (existsSync(p.actions)) kept.push("actions.json");
|
|
1850
|
-
if (existsSync(join(dir, "vos.json"))) kept.push("vos.json");
|
|
1851
|
-
for (const stale of [
|
|
1852
|
-
// Every container, not just the one `record` writes: a pulled take's
|
|
1853
|
-
// footage may be mp4, and a survivor would outrank the new recording.
|
|
1854
|
-
...takeMediaFiles(dir, "recording"),
|
|
1855
|
-
...takeMediaFiles(dir, "mic"),
|
|
1856
|
-
...takeMediaFiles(dir, "cam"),
|
|
1857
|
-
p.cursor,
|
|
1858
|
-
p.meta,
|
|
1859
|
-
p.framesIndex,
|
|
1860
|
-
p.framesDir,
|
|
1861
|
-
join(dir, "stills"),
|
|
1862
|
-
join(dir, "digest")
|
|
1863
|
-
]) {
|
|
1864
|
-
await rm(stale, { recursive: true, force: true });
|
|
1865
|
-
}
|
|
1866
|
-
return { prevDoc, kept };
|
|
1867
|
-
}
|
|
1868
|
-
async function readJson(file) {
|
|
1869
|
-
return JSON.parse(await readFile(file, "utf8"));
|
|
1870
|
-
}
|
|
1871
|
-
async function writeJson(file, value, pretty = false) {
|
|
1872
|
-
await writeFile(
|
|
1873
|
-
file,
|
|
1874
|
-
pretty ? JSON.stringify(value, null, 2) : JSON.stringify(value)
|
|
1875
|
-
);
|
|
1876
|
-
}
|
|
1877
|
-
async function loadTake(dir) {
|
|
1878
|
-
const paths = takePaths(dir);
|
|
1879
|
-
if (!existsSync(paths.meta)) {
|
|
1880
|
-
throw new Error(
|
|
1881
|
-
`${dir} is not a take directory (missing meta.json) \u2014 run record first`
|
|
1882
|
-
);
|
|
1883
|
-
}
|
|
1884
|
-
const meta = await readJson(paths.meta);
|
|
1885
|
-
const cursor = existsSync(paths.cursor) ? await readJson(paths.cursor) : [];
|
|
1886
|
-
const actions = existsSync(paths.actions) ? await readJson(paths.actions) : null;
|
|
1887
|
-
const doc = existsSync(paths.doc) ? await readJson(paths.doc) : null;
|
|
1888
|
-
return { paths, cursor, meta, actions, doc };
|
|
1889
|
-
}
|
|
1890
|
-
|
|
1891
|
-
// src/plugin/uploadAsset.ts
|
|
1892
|
-
var SINGLE_SHOT_MAX_BYTES = 32 * 1024 * 1024;
|
|
1893
|
-
var PART_ATTEMPTS = 3;
|
|
1894
|
-
function planParts(totalBytes, partBytes) {
|
|
1895
|
-
if (totalBytes <= 0 || partBytes <= 0) return [];
|
|
1896
|
-
const parts = [];
|
|
1897
|
-
for (let start = 0; start < totalBytes; start += partBytes) {
|
|
1898
|
-
parts.push({
|
|
1899
|
-
partNumber: parts.length + 1,
|
|
1900
|
-
start,
|
|
1901
|
-
end: Math.min(start + partBytes, totalBytes)
|
|
1902
|
-
});
|
|
1903
|
-
}
|
|
1904
|
-
return parts;
|
|
1905
|
-
}
|
|
1906
|
-
function failed(what, r) {
|
|
1907
|
-
const detail = typeof r.body.error === "string" ? r.body.error : "";
|
|
1908
|
-
return new Error(`${what} (${r.status})${detail ? `: ${detail}` : ""}`);
|
|
1909
|
-
}
|
|
1910
|
-
function asAsset(body) {
|
|
1911
|
-
return {
|
|
1912
|
-
id: String(body.id),
|
|
1913
|
-
url: String(body.url),
|
|
1914
|
-
size: typeof body.size === "number" ? body.size : 0,
|
|
1915
|
-
reused: body.reused === true
|
|
1916
|
-
};
|
|
1917
|
-
}
|
|
1918
|
-
function transient(status) {
|
|
1919
|
-
return status >= 500 || status === 429;
|
|
1920
|
-
}
|
|
1921
|
-
var wait = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1922
|
-
async function uploadAsset(target, bytes, opts) {
|
|
1923
|
-
if (bytes.length > SINGLE_SHOT_MAX_BYTES && opts.folderId) {
|
|
1924
|
-
throw new Error(
|
|
1925
|
-
`${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`
|
|
1926
|
-
);
|
|
1927
|
-
}
|
|
1928
|
-
return bytes.length > SINGLE_SHOT_MAX_BYTES ? uploadInParts(target, bytes, opts) : uploadWhole(target, bytes, opts);
|
|
1929
|
-
}
|
|
1930
|
-
async function uploadWhole(target, bytes, opts) {
|
|
1931
|
-
const res = await apiJson(target.origin, "/api/assets/recording", {
|
|
1932
|
-
method: "POST",
|
|
1933
|
-
key: target.key,
|
|
1934
|
-
headers: {
|
|
1935
|
-
"Content-Type": opts.contentType,
|
|
1936
|
-
"Content-Length": String(bytes.length),
|
|
1937
|
-
"X-Filename": opts.filename,
|
|
1938
|
-
"X-Content-Hash": opts.contentHash,
|
|
1939
|
-
...opts.durationSeconds && opts.durationSeconds > 0 ? { "X-Content-Duration": opts.durationSeconds.toFixed(3) } : {},
|
|
1940
|
-
...opts.folderId ? { "X-Folder-Id": opts.folderId } : {}
|
|
1941
|
-
},
|
|
1942
|
-
raw: bytes
|
|
1943
|
-
});
|
|
1944
|
-
if (res.status !== 201 && res.status !== 200)
|
|
1945
|
-
throw failed("upload failed", res);
|
|
1946
|
-
return asAsset(res.body);
|
|
1947
|
-
}
|
|
1948
|
-
async function uploadInParts(target, bytes, opts) {
|
|
1949
|
-
const begun = await apiJson(
|
|
1950
|
-
target.origin,
|
|
1951
|
-
"/api/assets/recording/multipart",
|
|
1952
|
-
{
|
|
1953
|
-
method: "POST",
|
|
1954
|
-
key: target.key,
|
|
1955
|
-
body: {
|
|
1956
|
-
filename: opts.filename,
|
|
1957
|
-
contentType: opts.contentType,
|
|
1958
|
-
size: bytes.length,
|
|
1959
|
-
contentHash: opts.contentHash,
|
|
1960
|
-
...opts.durationSeconds && opts.durationSeconds > 0 ? { durationSeconds: opts.durationSeconds } : {}
|
|
1961
|
-
}
|
|
1962
|
-
}
|
|
1963
|
-
);
|
|
1964
|
-
if (begun.status !== 201 && begun.status !== 200) {
|
|
1965
|
-
throw failed("upload failed", begun);
|
|
1966
|
-
}
|
|
1967
|
-
if (begun.body.reused === true || typeof begun.body.url === "string") {
|
|
1968
|
-
return asAsset(begun.body);
|
|
1969
|
-
}
|
|
1970
|
-
const uploadId = String(begun.body.id);
|
|
1971
|
-
const partBytes = Number(begun.body.partBytes);
|
|
1972
|
-
if (!Number.isFinite(partBytes) || partBytes <= 0) {
|
|
1973
|
-
throw new Error("upload failed: the platform returned no part size");
|
|
1974
|
-
}
|
|
1975
|
-
const parts = planParts(bytes.length, partBytes);
|
|
1976
|
-
const etags = [];
|
|
1977
|
-
try {
|
|
1978
|
-
for (const part of parts) {
|
|
1979
|
-
const chunk2 = bytes.subarray(part.start, part.end);
|
|
1980
|
-
let sent = null;
|
|
1981
|
-
for (let attempt = 1; attempt <= PART_ATTEMPTS; attempt++) {
|
|
1982
|
-
try {
|
|
1983
|
-
sent = await apiJson(
|
|
1984
|
-
target.origin,
|
|
1985
|
-
`/api/assets/recording/multipart/${uploadId}/parts/${part.partNumber}`,
|
|
1986
|
-
{
|
|
1987
|
-
method: "PUT",
|
|
1988
|
-
key: target.key,
|
|
1989
|
-
headers: {
|
|
1990
|
-
"Content-Type": "application/octet-stream",
|
|
1991
|
-
"Content-Length": String(chunk2.length)
|
|
1992
|
-
},
|
|
1993
|
-
raw: chunk2
|
|
1994
|
-
}
|
|
1995
|
-
);
|
|
1996
|
-
} catch (networkError) {
|
|
1997
|
-
if (attempt === PART_ATTEMPTS) throw networkError;
|
|
1998
|
-
await wait(500 * 2 ** (attempt - 1));
|
|
1999
|
-
continue;
|
|
2000
|
-
}
|
|
2001
|
-
if (sent.status === 200) break;
|
|
2002
|
-
if (!transient(sent.status) || attempt === PART_ATTEMPTS) {
|
|
2003
|
-
throw failed(`part ${part.partNumber} failed`, sent);
|
|
2004
|
-
}
|
|
2005
|
-
await wait(500 * 2 ** (attempt - 1));
|
|
2006
|
-
}
|
|
2007
|
-
if (!sent || sent.status !== 200) {
|
|
2008
|
-
throw new Error(`part ${part.partNumber} failed`);
|
|
2009
|
-
}
|
|
2010
|
-
etags.push({
|
|
2011
|
-
partNumber: Number(sent.body.partNumber),
|
|
2012
|
-
etag: String(sent.body.etag)
|
|
2013
|
-
});
|
|
2014
|
-
opts.onPart?.(etags.length, parts.length);
|
|
2015
|
-
}
|
|
2016
|
-
const sealed = await apiJson(
|
|
2017
|
-
target.origin,
|
|
2018
|
-
`/api/assets/recording/multipart/${uploadId}/complete`,
|
|
2019
|
-
{ method: "POST", key: target.key, body: { parts: etags } }
|
|
2020
|
-
);
|
|
2021
|
-
if (sealed.status !== 201 && sealed.status !== 200) {
|
|
2022
|
-
throw failed("upload failed", sealed);
|
|
2023
|
-
}
|
|
2024
|
-
return asAsset(sealed.body);
|
|
2025
|
-
} catch (err) {
|
|
2026
|
-
await apiJson(
|
|
2027
|
-
target.origin,
|
|
2028
|
-
`/api/assets/recording/multipart/${uploadId}`,
|
|
2029
|
-
{ method: "DELETE", key: target.key }
|
|
2030
|
-
).catch(() => void 0);
|
|
2031
|
-
throw err;
|
|
2032
|
-
}
|
|
2033
|
-
}
|
|
2034
|
-
|
|
2035
|
-
// src/plugin/media.ts
|
|
2036
|
-
var SOURCE_MEDIA = [
|
|
2037
|
-
{ key: "videoKey", stem: "recording", fallbackExt: ".webm" },
|
|
2038
|
-
{ key: "micKey", stem: "mic", fallbackExt: ".webm" },
|
|
2039
|
-
{ key: "camKey", stem: "cam", fallbackExt: ".webm" }
|
|
2040
|
-
];
|
|
2041
|
-
function isTakeRelativeKey(key) {
|
|
2042
|
-
return !!key && !/^(https?:|blob:|data:|media:|\/\/)/.test(key) && !key.startsWith("/api/");
|
|
2043
|
-
}
|
|
2044
|
-
var takeRelativeFile = (key) => key.replace(/^\/+/, "");
|
|
2045
|
-
function sourceSidecarRefs(doc, keep = isTakeRelativeKey) {
|
|
2046
|
-
const out = [];
|
|
2047
|
-
for (const [field, where] of [
|
|
2048
|
-
["micKey", "mic track"],
|
|
2049
|
-
["camKey", "cam track"]
|
|
2050
|
-
]) {
|
|
2051
|
-
const key = doc.source[field];
|
|
2052
|
-
if (!key || !keep(key)) continue;
|
|
2053
|
-
out.push({
|
|
2054
|
-
where,
|
|
2055
|
-
key,
|
|
2056
|
-
set: (next) => {
|
|
2057
|
-
doc.source[field] = next;
|
|
2058
|
-
}
|
|
2059
|
-
});
|
|
2060
|
-
}
|
|
2061
|
-
return out;
|
|
2062
|
-
}
|
|
2063
|
-
function docObjectRefs(doc, keep = isTakeRelativeKey) {
|
|
2064
|
-
const out = [];
|
|
2065
|
-
for (const clip of doc.objects ?? []) {
|
|
2066
|
-
const asset = clip.asset;
|
|
2067
|
-
if (asset.kind !== "gltf" || !keep(asset.key)) continue;
|
|
2068
|
-
out.push({
|
|
2069
|
-
where: `object ${clip.id}`,
|
|
2070
|
-
key: asset.key,
|
|
2071
|
-
set: (next) => {
|
|
2072
|
-
asset.key = next;
|
|
2073
|
-
}
|
|
2074
|
-
});
|
|
2075
|
-
}
|
|
2076
|
-
return out;
|
|
2077
|
-
}
|
|
2078
|
-
function docOverlayRefs(doc, keep = isTakeRelativeKey) {
|
|
2079
|
-
const out = [];
|
|
2080
|
-
for (const clip of doc.overlays ?? []) {
|
|
2081
|
-
if (!isKeyedOverlay(clip) || !keep(clip.key)) continue;
|
|
2082
|
-
out.push({
|
|
2083
|
-
where: `overlay ${clip.id}`,
|
|
2084
|
-
key: clip.key,
|
|
2085
|
-
set: (next) => {
|
|
2086
|
-
clip.key = next;
|
|
2087
|
-
}
|
|
2088
|
-
});
|
|
2089
|
-
}
|
|
2090
|
-
return out;
|
|
2091
|
-
}
|
|
2092
|
-
function docLayerRefs(doc, keep = isTakeRelativeKey) {
|
|
2093
|
-
return [
|
|
2094
|
-
...docOverlayRefs(doc, keep),
|
|
2095
|
-
...docObjectRefs(doc, keep),
|
|
2096
|
-
...docAudioRefs(doc, keep)
|
|
2097
|
-
];
|
|
2098
|
-
}
|
|
2099
|
-
function docMediaRefs(doc, keep = isTakeRelativeKey) {
|
|
2100
|
-
const out = [];
|
|
2101
|
-
for (const m of doc.media ?? []) {
|
|
2102
|
-
if (keep(m.videoKey))
|
|
2103
|
-
out.push({
|
|
2104
|
-
where: `media ${m.id}`,
|
|
2105
|
-
key: m.videoKey,
|
|
2106
|
-
set: (next) => {
|
|
2107
|
-
m.videoKey = next;
|
|
2108
|
-
}
|
|
2109
|
-
});
|
|
2110
|
-
const mic = m.micKey;
|
|
2111
|
-
if (mic && keep(mic))
|
|
2112
|
-
out.push({
|
|
2113
|
-
where: `media ${m.id} mic`,
|
|
2114
|
-
key: mic,
|
|
2115
|
-
set: (next) => {
|
|
2116
|
-
m.micKey = next;
|
|
2117
|
-
}
|
|
2118
|
-
});
|
|
2119
|
-
const cam = m.camKey;
|
|
2120
|
-
if (cam && keep(cam))
|
|
2121
|
-
out.push({
|
|
2122
|
-
where: `media ${m.id} cam`,
|
|
2123
|
-
key: cam,
|
|
2124
|
-
set: (next) => {
|
|
2125
|
-
m.camKey = next;
|
|
2126
|
-
}
|
|
2127
|
-
});
|
|
2128
|
-
}
|
|
2129
|
-
out.push(...docOverlayRefs(doc, keep));
|
|
2130
|
-
out.push(...docObjectRefs(doc, keep));
|
|
2131
|
-
const mark = doc.endCard?.mark;
|
|
2132
|
-
if (mark && keep(mark.key))
|
|
2133
|
-
out.push({
|
|
2134
|
-
where: "end card mark",
|
|
2135
|
-
key: mark.key,
|
|
2136
|
-
set: (next) => {
|
|
2137
|
-
mark.key = next;
|
|
2138
|
-
}
|
|
2139
|
-
});
|
|
2140
|
-
const bg = doc.frame.backgroundMedia;
|
|
2141
|
-
if (bg && keep(bg.key))
|
|
2142
|
-
out.push({
|
|
2143
|
-
where: "background",
|
|
2144
|
-
key: bg.key,
|
|
2145
|
-
set: (next) => {
|
|
2146
|
-
bg.key = next;
|
|
2147
|
-
}
|
|
2148
|
-
});
|
|
2149
|
-
out.push(...docAudioRefs(doc, keep));
|
|
2150
|
-
return out;
|
|
2151
|
-
}
|
|
2152
|
-
function docAudioRefs(doc, keep = isTakeRelativeKey) {
|
|
2153
|
-
const out = [];
|
|
2154
|
-
for (const clip of doc.audio ?? []) {
|
|
2155
|
-
if (!keep(clip.key)) continue;
|
|
2156
|
-
out.push({
|
|
2157
|
-
where: `audio ${clip.id}`,
|
|
2158
|
-
key: clip.key,
|
|
2159
|
-
set: (next) => {
|
|
2160
|
-
clip.key = next;
|
|
2161
|
-
}
|
|
2162
|
-
});
|
|
2163
|
-
}
|
|
2164
|
-
return out;
|
|
2165
|
-
}
|
|
2166
|
-
async function uploadDocRefs(refs, dir, target, log) {
|
|
2167
|
-
for (const ref of refs) {
|
|
2168
|
-
const file = join2(dir, takeRelativeFile(ref.key));
|
|
2169
|
-
if (!existsSync2(file)) {
|
|
2170
|
-
log(` ${ref.where}: ${ref.key} is not beside the document \u2014 left as is`);
|
|
2171
|
-
continue;
|
|
2172
|
-
}
|
|
2173
|
-
const media = await readFile2(file);
|
|
2174
|
-
const mediaHash = createHash("sha256").update(media).digest("hex");
|
|
2175
|
-
const { type } = resolveMediaType({
|
|
2176
|
-
head: media.subarray(0, MEDIA_HEAD_BYTES),
|
|
2177
|
-
filename: file
|
|
2178
|
-
});
|
|
2179
|
-
const name = nameForType(basename2(file), type);
|
|
2180
|
-
if (name !== basename2(file)) {
|
|
2181
|
-
log(` ${ref.where}: ${basename2(file)} holds ${type} \u2014 sent as ${name}`);
|
|
2182
|
-
}
|
|
2183
|
-
let put;
|
|
2184
|
-
try {
|
|
2185
|
-
put = await uploadAsset(target, new Uint8Array(media), {
|
|
2186
|
-
filename: name,
|
|
2187
|
-
contentType: type,
|
|
2188
|
-
contentHash: mediaHash
|
|
2189
|
-
});
|
|
2190
|
-
} catch (e) {
|
|
2191
|
-
throw new Error(
|
|
2192
|
-
`${ref.where} upload failed: ${e instanceof Error ? e.message : String(e)}`
|
|
2193
|
-
);
|
|
2194
|
-
}
|
|
2195
|
-
ref.set(put.url);
|
|
2196
|
-
log(
|
|
2197
|
-
` ${ref.where}: ${ref.key} \u2192 asset ${put.id}${put.reused ? " (reused)" : ""}`
|
|
2198
|
-
);
|
|
2199
|
-
}
|
|
2200
|
-
}
|
|
2201
|
-
function assetIdOf(url) {
|
|
2202
|
-
if (!url) return null;
|
|
2203
|
-
const m = /\/api\/assets\/([A-Za-z0-9_-]+)\/file/.exec(url);
|
|
2204
|
-
return m ? m[1] : null;
|
|
2205
|
-
}
|
|
2206
|
-
async function readMediaHead(file) {
|
|
2207
|
-
const handle = await open(file, "r");
|
|
2208
|
-
try {
|
|
2209
|
-
const buf = Buffer.alloc(MEDIA_HEAD_BYTES);
|
|
2210
|
-
const { bytesRead } = await handle.read(buf, 0, MEDIA_HEAD_BYTES, 0);
|
|
2211
|
-
return new Uint8Array(buf.subarray(0, bytesRead));
|
|
2212
|
-
} finally {
|
|
2213
|
-
await handle.close();
|
|
2214
|
-
}
|
|
2215
|
-
}
|
|
2216
|
-
async function downloadMedia(ctx, url, target) {
|
|
2217
|
-
const abs = /^https?:/.test(url) ? url : `${ctx.origin}${url}`;
|
|
2218
|
-
const res = await fetch(abs, {
|
|
2219
|
-
headers: ctx.key ? { authorization: `Bearer ${ctx.key}` } : {}
|
|
2220
|
-
});
|
|
2221
|
-
if (!res.ok || !res.body) {
|
|
2222
|
-
throw new Error(
|
|
2223
|
-
`download of ${target.describe} failed (${res.status})${res.status === 404 && target.notFoundHint ? ` \u2014 ${target.notFoundHint}` : ""}`
|
|
2224
|
-
);
|
|
2225
|
-
}
|
|
2226
|
-
const into = target.subdir ? join2(target.dir, target.subdir) : target.dir;
|
|
2227
|
-
const partial = join2(into, `${target.stem}.part`);
|
|
2228
|
-
await mkdir2(dirname(partial), { recursive: true });
|
|
2229
|
-
try {
|
|
2230
|
-
await pipeline(
|
|
2231
|
-
Readable.fromWeb(res.body),
|
|
2232
|
-
createWriteStream(partial)
|
|
2233
|
-
);
|
|
2234
|
-
const { type } = resolveMediaType({
|
|
2235
|
-
head: await readMediaHead(partial),
|
|
2236
|
-
declared: res.headers.get("content-type"),
|
|
2237
|
-
filename: `${target.stem}${target.fallbackExt}`
|
|
2238
|
-
});
|
|
2239
|
-
const name = `${target.stem}${extensionFor(type) || target.fallbackExt}`;
|
|
2240
|
-
await rename2(partial, join2(into, name));
|
|
2241
|
-
const file = target.subdir ? `${target.subdir}/${name}` : name;
|
|
2242
|
-
return { file, bytes: (await stat(join2(target.dir, file))).size };
|
|
2243
|
-
} finally {
|
|
2244
|
-
await rm2(partial, { force: true });
|
|
2245
|
-
}
|
|
2246
|
-
}
|
|
2247
|
-
async function pullMedia(ctx, dir, doc, log) {
|
|
2248
|
-
const result = { downloaded: [], kept: [] };
|
|
2249
|
-
for (const { key, stem, fallbackExt } of SOURCE_MEDIA) {
|
|
2250
|
-
const url = doc.source[key];
|
|
2251
|
-
const assetId = assetIdOf(url);
|
|
2252
|
-
if (!assetId) continue;
|
|
2253
|
-
const present = takeMediaFiles(dir, stem)[0];
|
|
2254
|
-
if (present) {
|
|
2255
|
-
result.kept.push(basename2(present));
|
|
2256
|
-
doc.source[key] = basename2(present);
|
|
2257
|
-
continue;
|
|
2258
|
-
}
|
|
2259
|
-
const got = await downloadMedia(ctx, url, {
|
|
2260
|
-
dir,
|
|
2261
|
-
stem,
|
|
2262
|
-
fallbackExt,
|
|
2263
|
-
describe: stem,
|
|
2264
|
-
notFoundHint: "a private recording needs a content key of its owner"
|
|
2265
|
-
});
|
|
2266
|
-
result.downloaded.push({ file: got.file, assetId, bytes: got.bytes });
|
|
2267
|
-
log(` ${got.file} \u2190 asset ${assetId} (${Math.round(got.bytes / 1024)} kB)`);
|
|
2268
|
-
doc.source[key] = got.file;
|
|
2269
|
-
}
|
|
2270
|
-
for (const ref of docMediaRefs(doc, (k) => assetIdOf(k) !== null)) {
|
|
2271
|
-
const assetId = assetIdOf(ref.key);
|
|
2272
|
-
const existing = existsSync2(join2(dir, "media")) ? (await readdir(join2(dir, "media"))).find(
|
|
2273
|
-
(f) => f === assetId || f.startsWith(`${assetId}.`)
|
|
2274
|
-
) : void 0;
|
|
2275
|
-
if (existing) {
|
|
2276
|
-
const file = `media/${existing}`;
|
|
2277
|
-
result.kept.push(file);
|
|
2278
|
-
ref.set(file);
|
|
2279
|
-
continue;
|
|
2280
|
-
}
|
|
2281
|
-
const got = await downloadMedia(ctx, ref.key, {
|
|
2282
|
-
dir,
|
|
2283
|
-
subdir: "media",
|
|
2284
|
-
stem: assetId,
|
|
2285
|
-
// A type with no extension here (an svg served as such) keeps the bare
|
|
2286
|
-
// asset id, exactly as before.
|
|
2287
|
-
fallbackExt: "",
|
|
2288
|
-
describe: `${ref.where} (${ref.key})`
|
|
2289
|
-
});
|
|
2290
|
-
result.downloaded.push({ file: got.file, assetId, bytes: got.bytes });
|
|
2291
|
-
log(
|
|
2292
|
-
` ${got.file} \u2190 ${ref.where}, asset ${assetId} (${Math.round(got.bytes / 1024)} kB)`
|
|
2293
|
-
);
|
|
2294
|
-
ref.set(got.file);
|
|
2295
|
-
}
|
|
2296
|
-
const metaPath = join2(dir, "meta.json");
|
|
2297
|
-
if (!existsSync2(metaPath)) await writeJson(metaPath, doc.source.meta, true);
|
|
2298
|
-
const cursorPath = join2(dir, "cursor.json");
|
|
2299
|
-
if (!existsSync2(cursorPath)) await writeJson(cursorPath, doc.source.cursor);
|
|
2300
|
-
await writeFile2(join2(dir, "doc.json"), JSON.stringify(doc, null, 2));
|
|
2301
|
-
return result;
|
|
2302
|
-
}
|
|
2303
|
-
|
|
2304
1844
|
// src/plugin/mediaProbe.ts
|
|
2305
1845
|
var MEDIA_EXT = /\.(png|jpe?g|webp|gif|webm|mp4|mov|mp3|wav|m4a|ogg|glb|gltf|hdr|exr|woff2?)($|\?)/i;
|
|
2306
1846
|
var isRemote = (v) => typeof v === "string" && /^https?:\/\//i.test(v);
|
|
@@ -2386,9 +1926,9 @@ function mediaProbeLints(results) {
|
|
|
2386
1926
|
}
|
|
2387
1927
|
|
|
2388
1928
|
// src/plugin/framesTake.ts
|
|
2389
|
-
import { mkdir as
|
|
2390
|
-
import { existsSync as
|
|
2391
|
-
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";
|
|
2392
1932
|
import { compileVosConfig } from "@vosjs/core";
|
|
2393
1933
|
import {
|
|
2394
1934
|
docStillTime,
|
|
@@ -2402,9 +1942,9 @@ import {
|
|
|
2402
1942
|
|
|
2403
1943
|
// src/plugin/server.ts
|
|
2404
1944
|
import { createServer } from "http";
|
|
2405
|
-
import { existsSync
|
|
2406
|
-
import { writeFile
|
|
2407
|
-
import { extname, join
|
|
1945
|
+
import { existsSync, readFileSync, statSync } from "fs";
|
|
1946
|
+
import { writeFile } from "fs/promises";
|
|
1947
|
+
import { extname, join } from "path";
|
|
2408
1948
|
var MIME = {
|
|
2409
1949
|
".html": "text/html",
|
|
2410
1950
|
".js": "text/javascript",
|
|
@@ -2448,7 +1988,7 @@ function startTakeServer(rootDir, pages) {
|
|
|
2448
1988
|
const chunks = [];
|
|
2449
1989
|
req.on("data", (c) => chunks.push(c));
|
|
2450
1990
|
req.on("end", () => {
|
|
2451
|
-
void
|
|
1991
|
+
void writeFile(join(rootDir, name), Buffer.concat(chunks)).then(
|
|
2452
1992
|
() => res.writeHead(200).end("ok"),
|
|
2453
1993
|
(e) => res.writeHead(500).end(String(e))
|
|
2454
1994
|
);
|
|
@@ -2460,9 +2000,9 @@ function startTakeServer(rootDir, pages) {
|
|
|
2460
2000
|
res.end(pages[url.pathname]);
|
|
2461
2001
|
return;
|
|
2462
2002
|
}
|
|
2463
|
-
const file =
|
|
2464
|
-
if (
|
|
2465
|
-
const size =
|
|
2003
|
+
const file = join(rootDir, decodeURIComponent(url.pathname));
|
|
2004
|
+
if (existsSync(file) && statSync(file).isFile()) {
|
|
2005
|
+
const size = statSync(file).size;
|
|
2466
2006
|
const buf = readFileSync(file);
|
|
2467
2007
|
const type = sniffMediaType(buf.subarray(0, MEDIA_HEAD_BYTES)) ?? MIME[extname(file)] ?? "application/octet-stream";
|
|
2468
2008
|
const range = req.headers.range;
|
|
@@ -2527,7 +2067,7 @@ async function waitForPageDone(page, label, onProgress, timeoutMs) {
|
|
|
2527
2067
|
}
|
|
2528
2068
|
|
|
2529
2069
|
// src/plugin/renderAnimation.ts
|
|
2530
|
-
import { readFile
|
|
2070
|
+
import { readFile, rm } from "fs/promises";
|
|
2531
2071
|
import { tweenRuntimeCode } from "@vosjs/tween/bundle";
|
|
2532
2072
|
import { elementsBundleCode } from "@vosjs/elements/bundle";
|
|
2533
2073
|
import { concatEncodedVideo, planChunks } from "@vosjs/render-core";
|
|
@@ -2822,10 +2362,10 @@ async function renderAnimation(browser, opts) {
|
|
|
2822
2362
|
)
|
|
2823
2363
|
);
|
|
2824
2364
|
const files = await Promise.all(
|
|
2825
|
-
outNames.map((name) =>
|
|
2365
|
+
outNames.map((name) => readFile(`${opts.workDir}/${name}`))
|
|
2826
2366
|
);
|
|
2827
2367
|
await Promise.all(
|
|
2828
|
-
outNames.map((name) =>
|
|
2368
|
+
outNames.map((name) => rm(`${opts.workDir}/${name}`, { force: true }))
|
|
2829
2369
|
);
|
|
2830
2370
|
if (chunks.length === 1) {
|
|
2831
2371
|
return { bytes: new Uint8Array(files[0]), totalFrames, chunks: 1 };
|
|
@@ -3080,9 +2620,9 @@ function applyAndValidate(doc, o) {
|
|
|
3080
2620
|
}
|
|
3081
2621
|
|
|
3082
2622
|
// src/plugin/digestTake.ts
|
|
3083
|
-
import { mkdir
|
|
3084
|
-
import { existsSync as
|
|
3085
|
-
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";
|
|
3086
2626
|
import {
|
|
3087
2627
|
DIGEST_CROP_MAX,
|
|
3088
2628
|
DIGEST_FULL_MAX,
|
|
@@ -3304,8 +2844,8 @@ async function digestTake(browser, dir, opts = {}) {
|
|
|
3304
2844
|
const meta = doc.source.meta;
|
|
3305
2845
|
let plan = planForDigest(doc);
|
|
3306
2846
|
const transcript = opts.transcript ?? await discoverTranscript(dir);
|
|
3307
|
-
const outDir = resolve(opts.outDir ??
|
|
3308
|
-
await
|
|
2847
|
+
const outDir = resolve(opts.outDir ?? join2(dir, DIGEST_DIR));
|
|
2848
|
+
await mkdir(outDir, { recursive: true });
|
|
3309
2849
|
const fullMax = opts.full ?? DIGEST_FULL_MAX;
|
|
3310
2850
|
const cropMax = opts.crop ?? DIGEST_CROP_MAX;
|
|
3311
2851
|
const wantFrames = opts.frames !== false && browser !== null;
|
|
@@ -3321,7 +2861,7 @@ async function digestTake(browser, dir, opts = {}) {
|
|
|
3321
2861
|
kind: "video",
|
|
3322
2862
|
url: `/${take.paths.recordingName}`
|
|
3323
2863
|
};
|
|
3324
|
-
if (
|
|
2864
|
+
if (existsSync2(take.paths.framesIndex) && existsSync2(take.paths.framesDir)) {
|
|
3325
2865
|
const index = await readJson(
|
|
3326
2866
|
take.paths.framesIndex
|
|
3327
2867
|
);
|
|
@@ -3336,7 +2876,7 @@ async function digestTake(browser, dir, opts = {}) {
|
|
|
3336
2876
|
}
|
|
3337
2877
|
}
|
|
3338
2878
|
if (wantFrames) {
|
|
3339
|
-
if (source.kind === "video" && !
|
|
2879
|
+
if (source.kind === "video" && !existsSync2(take.paths.recording))
|
|
3340
2880
|
throw new Error(
|
|
3341
2881
|
`${dir} has no recording \u2014 pull it with \`vos pull --media\` first`
|
|
3342
2882
|
);
|
|
@@ -3409,9 +2949,9 @@ async function digestTake(browser, dir, opts = {}) {
|
|
|
3409
2949
|
);
|
|
3410
2950
|
sizes = f.sizes;
|
|
3411
2951
|
for (const name of Object.keys(sizes)) {
|
|
3412
|
-
const from =
|
|
3413
|
-
const to =
|
|
3414
|
-
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);
|
|
3415
2955
|
}
|
|
3416
2956
|
if (sizes["sheet.png"]) sheet = "sheet.png";
|
|
3417
2957
|
} finally {
|
|
@@ -3437,12 +2977,12 @@ async function digestTake(browser, dir, opts = {}) {
|
|
|
3437
2977
|
if (!p) continue;
|
|
3438
2978
|
for (const f of [p.full, p.crop]) {
|
|
3439
2979
|
if (!f) continue;
|
|
3440
|
-
const size = await pngSize(
|
|
2980
|
+
const size = await pngSize(join2(outDir, f));
|
|
3441
2981
|
if (size) sizes[f] = size;
|
|
3442
2982
|
}
|
|
3443
2983
|
if (p.box) boxes.set(m.id, p.box);
|
|
3444
2984
|
}
|
|
3445
|
-
if (prior.images.sheet &&
|
|
2985
|
+
if (prior.images.sheet && existsSync2(join2(outDir, prior.images.sheet)))
|
|
3446
2986
|
sheet = prior.images.sheet;
|
|
3447
2987
|
}
|
|
3448
2988
|
}
|
|
@@ -3471,16 +3011,16 @@ async function digestTake(browser, dir, opts = {}) {
|
|
|
3471
3011
|
style: opts.style ?? null,
|
|
3472
3012
|
transcript
|
|
3473
3013
|
});
|
|
3474
|
-
const file =
|
|
3014
|
+
const file = join2(outDir, "digest.json");
|
|
3475
3015
|
const body = JSON.stringify(digest, null, 2);
|
|
3476
|
-
await
|
|
3016
|
+
await writeFile2(file, body);
|
|
3477
3017
|
return { file, outDir, digest, bytes: Buffer.byteLength(body) };
|
|
3478
3018
|
}
|
|
3479
3019
|
async function discoverTranscript(dir) {
|
|
3480
|
-
const file =
|
|
3481
|
-
if (!
|
|
3020
|
+
const file = join2(dir, "transcript.json");
|
|
3021
|
+
if (!existsSync2(file)) return null;
|
|
3482
3022
|
try {
|
|
3483
|
-
return parseTranscript(JSON.parse(await
|
|
3023
|
+
return parseTranscript(JSON.parse(await readFile2(file, "utf8")));
|
|
3484
3024
|
} catch {
|
|
3485
3025
|
return null;
|
|
3486
3026
|
}
|
|
@@ -3488,7 +3028,7 @@ async function discoverTranscript(dir) {
|
|
|
3488
3028
|
async function readPriorDigest(outDir) {
|
|
3489
3029
|
try {
|
|
3490
3030
|
const raw = JSON.parse(
|
|
3491
|
-
await
|
|
3031
|
+
await readFile2(join2(outDir, "digest.json"), "utf8")
|
|
3492
3032
|
);
|
|
3493
3033
|
return raw.digestVersion === DIGEST_VERSION && Array.isArray(raw.moments) ? raw : null;
|
|
3494
3034
|
} catch {
|
|
@@ -3497,7 +3037,7 @@ async function readPriorDigest(outDir) {
|
|
|
3497
3037
|
}
|
|
3498
3038
|
async function pngSize(file) {
|
|
3499
3039
|
try {
|
|
3500
|
-
const buf = await
|
|
3040
|
+
const buf = await readFile2(file);
|
|
3501
3041
|
if (buf.length < 24 || buf.readUInt32BE(0) !== 2303741511) return null;
|
|
3502
3042
|
return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20) };
|
|
3503
3043
|
} catch {
|
|
@@ -3672,8 +3212,8 @@ async function framesTake(browser, dir, opts) {
|
|
|
3672
3212
|
throw new Error(
|
|
3673
3213
|
"no frame times \u2014 pass --times/--frame, --at-zooms, --at-moments or --at-still"
|
|
3674
3214
|
);
|
|
3675
|
-
const outDir = resolve2(opts.outDir ??
|
|
3676
|
-
await
|
|
3215
|
+
const outDir = resolve2(opts.outDir ?? join3(dir, "stills"));
|
|
3216
|
+
await mkdir2(outDir, { recursive: true });
|
|
3677
3217
|
const named = shots.map((s, i) => ({
|
|
3678
3218
|
...s,
|
|
3679
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`
|
|
@@ -3717,9 +3257,9 @@ async function framesTake(browser, dir, opts) {
|
|
|
3717
3257
|
);
|
|
3718
3258
|
const frames = [];
|
|
3719
3259
|
for (const n of named) {
|
|
3720
|
-
const from =
|
|
3721
|
-
const to =
|
|
3722
|
-
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);
|
|
3723
3263
|
frames.push({
|
|
3724
3264
|
file: to,
|
|
3725
3265
|
time: n.time,
|
|
@@ -3734,10 +3274,10 @@ async function framesTake(browser, dir, opts) {
|
|
|
3734
3274
|
}
|
|
3735
3275
|
}
|
|
3736
3276
|
async function momentsFor(dir, doc) {
|
|
3737
|
-
const file =
|
|
3738
|
-
if (
|
|
3277
|
+
const file = join3(dir, DIGEST_DIR, "digest.json");
|
|
3278
|
+
if (existsSync3(file)) {
|
|
3739
3279
|
try {
|
|
3740
|
-
const d = JSON.parse(await
|
|
3280
|
+
const d = JSON.parse(await readFile3(file, "utf8"));
|
|
3741
3281
|
if (Array.isArray(d.moments)) return d.moments;
|
|
3742
3282
|
} catch {
|
|
3743
3283
|
}
|
|
@@ -3757,8 +3297,8 @@ function parseTimes2(spec, duration) {
|
|
|
3757
3297
|
});
|
|
3758
3298
|
}
|
|
3759
3299
|
async function writeIndexJson(result) {
|
|
3760
|
-
const index =
|
|
3761
|
-
await
|
|
3300
|
+
const index = join3(result.outDir, "stills.json");
|
|
3301
|
+
await writeFile3(
|
|
3762
3302
|
index,
|
|
3763
3303
|
JSON.stringify(
|
|
3764
3304
|
{
|
|
@@ -3775,11 +3315,11 @@ async function writeIndexJson(result) {
|
|
|
3775
3315
|
}
|
|
3776
3316
|
|
|
3777
3317
|
// src/plugin/deliver.ts
|
|
3778
|
-
import { mkdir as
|
|
3318
|
+
import { mkdir as mkdir3, mkdtemp, rename as rename3, rm as rm2, stat, writeFile as writeFile5 } from "fs/promises";
|
|
3779
3319
|
import { tmpdir } from "os";
|
|
3780
|
-
import { join as
|
|
3781
|
-
import { existsSync as
|
|
3782
|
-
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";
|
|
3783
3323
|
import { parseFrontmatter } from "@vosjs/shared/frontmatter";
|
|
3784
3324
|
import {
|
|
3785
3325
|
CHANNEL_SPECS_VERIFIED,
|
|
@@ -3793,7 +3333,7 @@ import {
|
|
|
3793
3333
|
} from "@vosjs/studio-core";
|
|
3794
3334
|
|
|
3795
3335
|
// src/plugin/renderTake.ts
|
|
3796
|
-
import { writeFile as
|
|
3336
|
+
import { writeFile as writeFile4 } from "fs/promises";
|
|
3797
3337
|
import { compileVosConfig as compileVosConfig2 } from "@vosjs/core";
|
|
3798
3338
|
import {
|
|
3799
3339
|
audioProducerCode,
|
|
@@ -3882,7 +3422,7 @@ async function renderTake(browser, dir, outFile, opts) {
|
|
|
3882
3422
|
} : void 0,
|
|
3883
3423
|
onProgress: opts.onProgress
|
|
3884
3424
|
});
|
|
3885
|
-
await
|
|
3425
|
+
await writeFile4(outFile, result.bytes);
|
|
3886
3426
|
return {
|
|
3887
3427
|
out: outFile,
|
|
3888
3428
|
bytes: result.bytes.length,
|
|
@@ -4437,9 +3977,9 @@ function proposeTransitions(input, kind) {
|
|
|
4437
3977
|
for (const step of steps) {
|
|
4438
3978
|
if (!step.navigated || step.skipped) continue;
|
|
4439
3979
|
const name = step.id ?? String(step.step);
|
|
4440
|
-
const
|
|
4441
|
-
const outEnd = round3(
|
|
4442
|
-
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);
|
|
4443
3983
|
const segs = doc.segments;
|
|
4444
3984
|
if (segs.some(
|
|
4445
3985
|
(s) => !s.media && Math.abs(s.out - outEnd) < 0.05 && s !== segs.at(-1)
|
|
@@ -4758,9 +4298,9 @@ function isLightHexGround(hex2) {
|
|
|
4758
4298
|
}
|
|
4759
4299
|
|
|
4760
4300
|
// src/plugin/posterDoc.ts
|
|
4761
|
-
import { existsSync as
|
|
4762
|
-
import { readFile as
|
|
4763
|
-
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";
|
|
4764
4304
|
import {
|
|
4765
4305
|
computeCardLayout,
|
|
4766
4306
|
docStillTime as docStillTime2,
|
|
@@ -4787,16 +4327,16 @@ var roleFor = (cls) => `poster-${cls}`;
|
|
|
4787
4327
|
async function readPosterAt(cls, path, takeDir, from) {
|
|
4788
4328
|
let file = path;
|
|
4789
4329
|
let docDir = path;
|
|
4790
|
-
if (
|
|
4791
|
-
file =
|
|
4792
|
-
} 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)) {
|
|
4793
4333
|
return null;
|
|
4794
4334
|
} else {
|
|
4795
4335
|
docDir = resolve3(path, "..");
|
|
4796
4336
|
}
|
|
4797
4337
|
let raw;
|
|
4798
4338
|
try {
|
|
4799
|
-
raw = JSON.parse(await
|
|
4339
|
+
raw = JSON.parse(await readFile4(file, "utf8"));
|
|
4800
4340
|
} catch {
|
|
4801
4341
|
return null;
|
|
4802
4342
|
}
|
|
@@ -4806,7 +4346,7 @@ async function readPosterAt(cls, path, takeDir, from) {
|
|
|
4806
4346
|
const doc = migrateHostedDoc(
|
|
4807
4347
|
raw
|
|
4808
4348
|
);
|
|
4809
|
-
const ownFootage =
|
|
4349
|
+
const ownFootage = existsSync4(join4(docDir, "meta.json")) && existsSync4(join4(docDir, source.videoKey));
|
|
4810
4350
|
const sync = readSyncState(docDir);
|
|
4811
4351
|
return {
|
|
4812
4352
|
cls,
|
|
@@ -4829,10 +4369,10 @@ async function findPosterDocs(takeDir, launchRoles) {
|
|
|
4829
4369
|
candidates.push({ path: at2(named.trim()), from: `LAUNCH.md ${role}` });
|
|
4830
4370
|
}
|
|
4831
4371
|
candidates.push({
|
|
4832
|
-
path:
|
|
4372
|
+
path: join4(takeDir, "poster", cls),
|
|
4833
4373
|
from: `poster/${cls}`
|
|
4834
4374
|
});
|
|
4835
|
-
candidates.push({ path:
|
|
4375
|
+
candidates.push({ path: join4(takeDir, "poster"), from: "poster" });
|
|
4836
4376
|
for (const c of candidates) {
|
|
4837
4377
|
const ref = await readPosterAt(cls, c.path, takeDir, c.from);
|
|
4838
4378
|
if (ref) {
|
|
@@ -4928,19 +4468,19 @@ function resolveChannels(raw) {
|
|
|
4928
4468
|
return out;
|
|
4929
4469
|
}
|
|
4930
4470
|
async function readBrandBesideTake(dir, explicit) {
|
|
4931
|
-
const candidates = explicit ? [explicit] : [
|
|
4471
|
+
const candidates = explicit ? [explicit] : [join5(dir, "BRAND.md"), join5(dir, "..", "BRAND.md")];
|
|
4932
4472
|
for (const file of candidates) {
|
|
4933
|
-
if (!
|
|
4934
|
-
const roles = parseFrontmatter(await
|
|
4473
|
+
if (!existsSync5(file)) continue;
|
|
4474
|
+
const roles = parseFrontmatter(await readFile5(file, "utf8"));
|
|
4935
4475
|
return { file, roles };
|
|
4936
4476
|
}
|
|
4937
4477
|
return null;
|
|
4938
4478
|
}
|
|
4939
4479
|
async function readLaunchBesideTake(dir, explicit) {
|
|
4940
|
-
const candidates = explicit ? [explicit] : [
|
|
4480
|
+
const candidates = explicit ? [explicit] : [join5(dir, "LAUNCH.md"), join5(dir, "..", "LAUNCH.md")];
|
|
4941
4481
|
for (const file of candidates) {
|
|
4942
|
-
if (!
|
|
4943
|
-
return { file, roles: parseFrontmatter(await
|
|
4482
|
+
if (!existsSync5(file)) continue;
|
|
4483
|
+
return { file, roles: parseFrontmatter(await readFile5(file, "utf8")) };
|
|
4944
4484
|
}
|
|
4945
4485
|
return null;
|
|
4946
4486
|
}
|
|
@@ -5004,7 +4544,7 @@ async function pickStillTimes(browser, dir, doc, duration, opts) {
|
|
|
5004
4544
|
const meta = doc.source.meta;
|
|
5005
4545
|
const vw = meta.captureWidth ?? meta.width;
|
|
5006
4546
|
const vh = meta.captureHeight ?? meta.height;
|
|
5007
|
-
const probeDir = await mkdtemp(
|
|
4547
|
+
const probeDir = await mkdtemp(join5(tmpdir(), "vos-moments-"));
|
|
5008
4548
|
try {
|
|
5009
4549
|
const probe3 = await framesTake(browser, dir, {
|
|
5010
4550
|
times: candidates.map((c) => c.time),
|
|
@@ -5018,7 +4558,7 @@ async function pickStillTimes(browser, dir, doc, duration, opts) {
|
|
|
5018
4558
|
});
|
|
5019
4559
|
const byTime = /* @__PURE__ */ new Map();
|
|
5020
4560
|
for (const frame of probe3.frames) {
|
|
5021
|
-
const img = decodePng(new Uint8Array(await
|
|
4561
|
+
const img = decodePng(new Uint8Array(await readFile5(frame.file)));
|
|
5022
4562
|
if (!img) continue;
|
|
5023
4563
|
const whole = { x: 0, y: 0, w: img.w, h: img.h };
|
|
5024
4564
|
byTime.set(+frame.time.toFixed(3), {
|
|
@@ -5043,7 +4583,7 @@ async function pickStillTimes(browser, dir, doc, duration, opts) {
|
|
|
5043
4583
|
notes: [...notes, ...pick.dropped.map((d) => `moment: ${d}`)]
|
|
5044
4584
|
};
|
|
5045
4585
|
} finally {
|
|
5046
|
-
await
|
|
4586
|
+
await rm2(probeDir, { recursive: true, force: true });
|
|
5047
4587
|
}
|
|
5048
4588
|
}
|
|
5049
4589
|
var overCeiling = (bytes, maxBytes) => `${Math.ceil(bytes / 1024)} KB exceeds the ${Math.floor(maxBytes / 1024)} KB ceiling`;
|
|
@@ -5091,8 +4631,8 @@ async function deliverTake(browser, dir, opts) {
|
|
|
5091
4631
|
const doc = take.doc;
|
|
5092
4632
|
const duration = docOutputDuration2(doc);
|
|
5093
4633
|
const videoSeconds = opts.range ? Math.min(opts.range[1], duration) - Math.min(opts.range[0], duration) : duration;
|
|
5094
|
-
const outDir = resolve4(opts.outDir ??
|
|
5095
|
-
await
|
|
4634
|
+
const outDir = resolve4(opts.outDir ?? join5(dir, "kit"));
|
|
4635
|
+
await mkdir3(outDir, { recursive: true });
|
|
5096
4636
|
const destinations = opts.channels.flatMap((c) => destinationsForChannel(c));
|
|
5097
4637
|
const assets = [];
|
|
5098
4638
|
const skipped = [];
|
|
@@ -5152,7 +4692,7 @@ async function deliverTake(browser, dir, opts) {
|
|
|
5152
4692
|
opts.onPhase?.(
|
|
5153
4693
|
`${label} (${specWords(d)}) from ${ref.from}${ref.vosId ? ` ${ref.vosId}` : ""}, the rest at ${time.toFixed(2)}s`
|
|
5154
4694
|
);
|
|
5155
|
-
const shotDir = await mkdtemp(
|
|
4695
|
+
const shotDir = await mkdtemp(join5(tmpdir(), "vos-poster-"));
|
|
5156
4696
|
try {
|
|
5157
4697
|
const captured = await framesTake(browser, ref.takeDir, {
|
|
5158
4698
|
times: [time],
|
|
@@ -5162,9 +4702,9 @@ async function deliverTake(browser, dir, opts) {
|
|
|
5162
4702
|
doc: ref.doc,
|
|
5163
4703
|
overrides: opts.overrides
|
|
5164
4704
|
});
|
|
5165
|
-
const to =
|
|
5166
|
-
await
|
|
5167
|
-
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;
|
|
5168
4708
|
if (d.maxBytes !== void 0 && bytes > d.maxBytes) {
|
|
5169
4709
|
skipped.push(
|
|
5170
4710
|
`${label}: ${overCeiling(bytes, d.maxBytes)} (kept at ${to})`
|
|
@@ -5193,7 +4733,7 @@ async function deliverTake(browser, dir, opts) {
|
|
|
5193
4733
|
...shot.x < 0 && shot.x + shot.w > 1 ? { crop: true } : {}
|
|
5194
4734
|
});
|
|
5195
4735
|
} finally {
|
|
5196
|
-
await
|
|
4736
|
+
await rm2(shotDir, { recursive: true, force: true });
|
|
5197
4737
|
}
|
|
5198
4738
|
}
|
|
5199
4739
|
if (missing.size) {
|
|
@@ -5234,7 +4774,7 @@ async function deliverTake(browser, dir, opts) {
|
|
|
5234
4774
|
}
|
|
5235
4775
|
}
|
|
5236
4776
|
opts.onPhase?.(`${label} (${specWords(d)})`);
|
|
5237
|
-
const outFile =
|
|
4777
|
+
const outFile = join5(outDir, `${d.id}.${d.format}`);
|
|
5238
4778
|
const bitrate = d.maxBytes !== void 0 ? Math.min(
|
|
5239
4779
|
1e7,
|
|
5240
4780
|
Math.floor(d.maxBytes * 8 / seconds * 0.85)
|
|
@@ -5281,9 +4821,9 @@ async function deliverTake(browser, dir, opts) {
|
|
|
5281
4821
|
for (let i = 0; i < captured.frames.length; i++) {
|
|
5282
4822
|
const frame = captured.frames[i];
|
|
5283
4823
|
const name = captured.frames.length > 1 ? `${d.id}-${i + 1}.png` : `${d.id}.png`;
|
|
5284
|
-
const to =
|
|
5285
|
-
await
|
|
5286
|
-
const bytes = (await
|
|
4824
|
+
const to = join5(outDir, name);
|
|
4825
|
+
await rename3(frame.file, to);
|
|
4826
|
+
const bytes = (await stat(to)).size;
|
|
5287
4827
|
if (d.maxBytes !== void 0 && bytes > d.maxBytes) {
|
|
5288
4828
|
skipped.push(
|
|
5289
4829
|
`${label}: ${overCeiling(bytes, d.maxBytes)} (kept at ${to})`
|
|
@@ -5319,15 +4859,15 @@ async function deliverTake(browser, dir, opts) {
|
|
|
5319
4859
|
skipped,
|
|
5320
4860
|
assets
|
|
5321
4861
|
};
|
|
5322
|
-
const kitFile =
|
|
5323
|
-
await
|
|
4862
|
+
const kitFile = join5(outDir, "kit.json");
|
|
4863
|
+
await writeFile5(kitFile, JSON.stringify(kit, null, 2));
|
|
5324
4864
|
return { kit, kitFile, outDir };
|
|
5325
4865
|
}
|
|
5326
4866
|
|
|
5327
4867
|
// src/plugin/markAsset.ts
|
|
5328
|
-
import { mkdir as
|
|
5329
|
-
import { existsSync as
|
|
5330
|
-
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";
|
|
5331
4871
|
function markExtension(url) {
|
|
5332
4872
|
const m = /\.(svg|png|webp|jpe?g)(?:[?#]|$)/i.exec(url);
|
|
5333
4873
|
if (!m) return null;
|
|
@@ -5409,8 +4949,8 @@ async function fetchBrandMarks(takeDir, roles) {
|
|
|
5409
4949
|
bytes = new Uint8Array(await res.arrayBuffer());
|
|
5410
4950
|
} else {
|
|
5411
4951
|
const file = isAbsolute2(src) ? src : resolve5(takeDir, src);
|
|
5412
|
-
if (!
|
|
5413
|
-
bytes = new Uint8Array(await
|
|
4952
|
+
if (!existsSync6(file)) throw new Error("no such file");
|
|
4953
|
+
bytes = new Uint8Array(await readFile6(file));
|
|
5414
4954
|
}
|
|
5415
4955
|
} catch (e) {
|
|
5416
4956
|
out.notes.push(
|
|
@@ -5425,8 +4965,8 @@ async function fetchBrandMarks(takeDir, roles) {
|
|
|
5425
4965
|
);
|
|
5426
4966
|
return null;
|
|
5427
4967
|
}
|
|
5428
|
-
await
|
|
5429
|
-
await
|
|
4968
|
+
await mkdir4(join6(takeDir, "brand"), { recursive: true });
|
|
4969
|
+
await writeFile6(join6(takeDir, "brand", `${name}.${ext}`), bytes);
|
|
5430
4970
|
return { key: `/brand/${name}.${ext}`, aspect };
|
|
5431
4971
|
};
|
|
5432
4972
|
out.light = await one("logoUrl", "mark");
|
|
@@ -5462,9 +5002,9 @@ async function fetchMusicCatalog(origin) {
|
|
|
5462
5002
|
}
|
|
5463
5003
|
|
|
5464
5004
|
// src/plugin/judge.ts
|
|
5465
|
-
import { existsSync as
|
|
5466
|
-
import { mkdir as
|
|
5467
|
-
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";
|
|
5468
5008
|
import { DESTINATIONS as DESTINATIONS2 } from "@vosjs/studio-core";
|
|
5469
5009
|
|
|
5470
5010
|
// src/plugin/shotBake.ts
|
|
@@ -5635,28 +5175,28 @@ function composeSheet(left, right, height = 540, gutter = 32) {
|
|
|
5635
5175
|
return { w, h, data: out };
|
|
5636
5176
|
}
|
|
5637
5177
|
async function judgeKit(kitPath, manifestPath, outDir) {
|
|
5638
|
-
const kit = JSON.parse(await
|
|
5178
|
+
const kit = JSON.parse(await readFile7(kitPath, "utf8"));
|
|
5639
5179
|
const manifest = JSON.parse(
|
|
5640
|
-
await
|
|
5180
|
+
await readFile7(manifestPath, "utf8")
|
|
5641
5181
|
);
|
|
5642
|
-
const manifestDir =
|
|
5643
|
-
const kitDir =
|
|
5644
|
-
const out = resolve6(outDir ??
|
|
5645
|
-
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 });
|
|
5646
5186
|
const sheets = [];
|
|
5647
5187
|
const skipped = [];
|
|
5648
5188
|
const refCache = /* @__PURE__ */ new Map();
|
|
5649
5189
|
const loadRef = async (file) => {
|
|
5650
5190
|
if (refCache.has(file)) return refCache.get(file) ?? null;
|
|
5651
|
-
const p =
|
|
5652
|
-
const img =
|
|
5191
|
+
const p = join7(manifestDir, file);
|
|
5192
|
+
const img = existsSync7(p) ? decodePng(new Uint8Array(await readFile7(p))) : null;
|
|
5653
5193
|
refCache.set(file, img);
|
|
5654
5194
|
return img;
|
|
5655
5195
|
};
|
|
5656
5196
|
for (const a of kit.assets) {
|
|
5657
5197
|
if (!/\.png$/i.test(a.path)) continue;
|
|
5658
|
-
const file = isAbsolute3(a.path) &&
|
|
5659
|
-
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;
|
|
5660
5200
|
if (!img) {
|
|
5661
5201
|
skipped.push(`${a.destination ?? a.path}: unreadable`);
|
|
5662
5202
|
continue;
|
|
@@ -5687,8 +5227,8 @@ async function judgeKit(kitPath, manifestPath, outDir) {
|
|
|
5687
5227
|
const id = stem.replace(/[^A-Za-z0-9_-]+/g, "-") || a.destination || "asset";
|
|
5688
5228
|
const nameA = `${id}--A.png`;
|
|
5689
5229
|
const nameB = `${id}--B.png`;
|
|
5690
|
-
await
|
|
5691
|
-
await
|
|
5230
|
+
await writeFile7(join7(out, nameA), encodePng(composeSheet(img, refImg)));
|
|
5231
|
+
await writeFile7(join7(out, nameB), encodePng(composeSheet(refImg, img)));
|
|
5692
5232
|
const rubric = [
|
|
5693
5233
|
`# ${id} against ${ref.id} (${ref.file})`,
|
|
5694
5234
|
"",
|
|
@@ -5708,7 +5248,7 @@ async function judgeKit(kitPath, manifestPath, outDir) {
|
|
|
5708
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.",
|
|
5709
5249
|
""
|
|
5710
5250
|
].join("\n");
|
|
5711
|
-
await
|
|
5251
|
+
await writeFile7(join7(out, `${id}.md`), rubric);
|
|
5712
5252
|
sheets.push({
|
|
5713
5253
|
asset: id,
|
|
5714
5254
|
reference: ref.id,
|
|
@@ -5717,9 +5257,9 @@ async function judgeKit(kitPath, manifestPath, outDir) {
|
|
|
5717
5257
|
rubric: `${id}.md`
|
|
5718
5258
|
});
|
|
5719
5259
|
}
|
|
5720
|
-
const verdictFile =
|
|
5721
|
-
if (!
|
|
5722
|
-
await
|
|
5260
|
+
const verdictFile = join7(out, "judge.json");
|
|
5261
|
+
if (!existsSync7(verdictFile)) {
|
|
5262
|
+
await writeFile7(
|
|
5723
5263
|
verdictFile,
|
|
5724
5264
|
JSON.stringify(
|
|
5725
5265
|
{
|
|
@@ -5770,9 +5310,9 @@ function catalogFamily(family) {
|
|
|
5770
5310
|
}
|
|
5771
5311
|
|
|
5772
5312
|
// src/plugin/callout.ts
|
|
5773
|
-
import { existsSync as
|
|
5774
|
-
import { readFile as
|
|
5775
|
-
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";
|
|
5776
5316
|
import { lerpArray, sample } from "@vosjs/timeline";
|
|
5777
5317
|
import {
|
|
5778
5318
|
CALLOUT_DEFAULT_BODY_PX,
|
|
@@ -5969,13 +5509,13 @@ async function footageFrames(browser, dir, doc, times, framesTake2) {
|
|
|
5969
5509
|
const out = /* @__PURE__ */ new Map();
|
|
5970
5510
|
if (!times.length) return out;
|
|
5971
5511
|
const bare = { ...doc, overlays: [] };
|
|
5972
|
-
const outDir =
|
|
5512
|
+
const outDir = join8(dir, ".ground-check");
|
|
5973
5513
|
const res = await framesTake2(browser, dir, { times, outDir, doc: bare });
|
|
5974
5514
|
for (const f of res.frames) {
|
|
5975
|
-
if (!
|
|
5515
|
+
if (!existsSync8(f.file)) continue;
|
|
5976
5516
|
out.set(
|
|
5977
5517
|
+f.time.toFixed(3),
|
|
5978
|
-
decodePng(new Uint8Array(await
|
|
5518
|
+
decodePng(new Uint8Array(await readFile8(f.file)))
|
|
5979
5519
|
);
|
|
5980
5520
|
}
|
|
5981
5521
|
return out;
|
|
@@ -5984,7 +5524,7 @@ async function footageFrames(browser, dir, doc, times, framesTake2) {
|
|
|
5984
5524
|
// src/plugin/kitPicture.ts
|
|
5985
5525
|
import { execFile } from "child_process";
|
|
5986
5526
|
import { promisify } from "util";
|
|
5987
|
-
import { readFile as
|
|
5527
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
5988
5528
|
var execFileP = promisify(execFile);
|
|
5989
5529
|
var SUBJECT_BAND = { min: 0.6, max: 0.92 };
|
|
5990
5530
|
var BLANK_INK = 0.12;
|
|
@@ -6260,7 +5800,7 @@ async function pictureChecks(assets) {
|
|
|
6260
5800
|
let ffmpeg2 = null;
|
|
6261
5801
|
for (const a of assets) {
|
|
6262
5802
|
if (/\.png$/i.test(a.file)) {
|
|
6263
|
-
const img = decodePng(new Uint8Array(await
|
|
5803
|
+
const img = decodePng(new Uint8Array(await readFile9(a.file)));
|
|
6264
5804
|
if (!img) {
|
|
6265
5805
|
findings.push({
|
|
6266
5806
|
code: "unreadable",
|
|
@@ -6353,9 +5893,9 @@ function formatFinding(f) {
|
|
|
6353
5893
|
}
|
|
6354
5894
|
|
|
6355
5895
|
// src/plugin/validateKit.ts
|
|
6356
|
-
import { existsSync as
|
|
6357
|
-
import { readFile as
|
|
6358
|
-
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";
|
|
6359
5899
|
import { DESTINATIONS as DESTINATIONS3 } from "@vosjs/studio-core";
|
|
6360
5900
|
var PNG_SIG2 = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
6361
5901
|
function pngDimensions(bytes) {
|
|
@@ -6374,7 +5914,7 @@ async function probeVideo(path) {
|
|
|
6374
5914
|
const MB = await import("mediabunny");
|
|
6375
5915
|
const input = new MB.Input({
|
|
6376
5916
|
formats: MB.ALL_FORMATS,
|
|
6377
|
-
source: new MB.BufferSource(new Uint8Array(await
|
|
5917
|
+
source: new MB.BufferSource(new Uint8Array(await readFile10(path)))
|
|
6378
5918
|
});
|
|
6379
5919
|
try {
|
|
6380
5920
|
const track = await input.getPrimaryVideoTrack();
|
|
@@ -6395,7 +5935,7 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
6395
5935
|
const measured = [];
|
|
6396
5936
|
let kit;
|
|
6397
5937
|
try {
|
|
6398
|
-
kit = JSON.parse(await
|
|
5938
|
+
kit = JSON.parse(await readFile10(kitPath, "utf8"));
|
|
6399
5939
|
} catch (e) {
|
|
6400
5940
|
return {
|
|
6401
5941
|
valid: false,
|
|
@@ -6412,10 +5952,10 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
6412
5952
|
measured
|
|
6413
5953
|
};
|
|
6414
5954
|
}
|
|
6415
|
-
const base =
|
|
5955
|
+
const base = dirname2(kitPath);
|
|
6416
5956
|
const resolvePath = (p) => {
|
|
6417
|
-
if (isAbsolute4(p) &&
|
|
6418
|
-
const beside =
|
|
5957
|
+
if (isAbsolute4(p) && existsSync9(p)) return p;
|
|
5958
|
+
const beside = join9(base, p.split("/").pop() ?? p);
|
|
6419
5959
|
return beside;
|
|
6420
5960
|
};
|
|
6421
5961
|
const perDestination = /* @__PURE__ */ new Map();
|
|
@@ -6426,7 +5966,7 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
6426
5966
|
const file = resolvePath(a.path);
|
|
6427
5967
|
let bytes;
|
|
6428
5968
|
try {
|
|
6429
|
-
bytes = (await
|
|
5969
|
+
bytes = (await stat2(file)).size;
|
|
6430
5970
|
} catch {
|
|
6431
5971
|
problems.push(`${label}: ${a.path} is missing`);
|
|
6432
5972
|
continue;
|
|
@@ -6441,7 +5981,7 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
6441
5981
|
let h = null;
|
|
6442
5982
|
let seconds = null;
|
|
6443
5983
|
if (/\.(png|jpe?g|webp)$/i.test(file)) {
|
|
6444
|
-
const head = Buffer.from(await
|
|
5984
|
+
const head = Buffer.from(await readFile10(file));
|
|
6445
5985
|
const kind = sniffImage(head);
|
|
6446
5986
|
if (file.toLowerCase().endsWith(".png") && kind !== "png")
|
|
6447
5987
|
problems.push(
|
|
@@ -6551,7 +6091,7 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
6551
6091
|
|
|
6552
6092
|
// src/plugin/recorder.ts
|
|
6553
6093
|
import { writeFileSync } from "fs";
|
|
6554
|
-
import { join as
|
|
6094
|
+
import { join as join10 } from "path";
|
|
6555
6095
|
import { recordTake as recordTakeCore } from "@vosjs/render-core/record";
|
|
6556
6096
|
async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
6557
6097
|
const dry = opts.dryRun === true;
|
|
@@ -6561,7 +6101,7 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
6561
6101
|
actions,
|
|
6562
6102
|
{
|
|
6563
6103
|
frame: (file, bytes) => {
|
|
6564
|
-
writeFileSync(
|
|
6104
|
+
writeFileSync(join10(paths.framesDir, file), bytes);
|
|
6565
6105
|
}
|
|
6566
6106
|
},
|
|
6567
6107
|
log,
|
|
@@ -6613,11 +6153,11 @@ import {
|
|
|
6613
6153
|
|
|
6614
6154
|
// src/plugin/session.ts
|
|
6615
6155
|
import { spawn } from "child_process";
|
|
6616
|
-
import { existsSync as
|
|
6156
|
+
import { existsSync as existsSync10, mkdirSync, readdirSync, rmSync, statSync as statSync2 } from "fs";
|
|
6617
6157
|
import { homedir } from "os";
|
|
6618
|
-
import { join as
|
|
6158
|
+
import { join as join11 } from "path";
|
|
6619
6159
|
import { chromium } from "playwright";
|
|
6620
|
-
var SESSIONS_DIR =
|
|
6160
|
+
var SESSIONS_DIR = join11(homedir(), ".config", "vos", "sessions");
|
|
6621
6161
|
var NAME = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
|
|
6622
6162
|
function sessionNameVerdict(name) {
|
|
6623
6163
|
if (!name) return "a session needs a name: --name <app>";
|
|
@@ -6626,20 +6166,20 @@ function sessionNameVerdict(name) {
|
|
|
6626
6166
|
return null;
|
|
6627
6167
|
}
|
|
6628
6168
|
function sessionDir(name) {
|
|
6629
|
-
return
|
|
6169
|
+
return join11(SESSIONS_DIR, name);
|
|
6630
6170
|
}
|
|
6631
6171
|
function systemChromePath() {
|
|
6632
6172
|
const explicit = process.env.VOS_BROWSER_PATH;
|
|
6633
6173
|
if (explicit) return explicit;
|
|
6634
6174
|
const candidates = process.platform === "darwin" ? ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"] : process.platform === "win32" ? [
|
|
6635
|
-
|
|
6175
|
+
join11(
|
|
6636
6176
|
process.env["PROGRAMFILES"] ?? "C:\\Program Files",
|
|
6637
6177
|
"Google",
|
|
6638
6178
|
"Chrome",
|
|
6639
6179
|
"Application",
|
|
6640
6180
|
"chrome.exe"
|
|
6641
6181
|
),
|
|
6642
|
-
|
|
6182
|
+
join11(
|
|
6643
6183
|
process.env["LOCALAPPDATA"] ?? "",
|
|
6644
6184
|
"Google",
|
|
6645
6185
|
"Chrome",
|
|
@@ -6651,7 +6191,7 @@ function systemChromePath() {
|
|
|
6651
6191
|
"/usr/bin/google-chrome-stable",
|
|
6652
6192
|
"/opt/google/chrome/chrome"
|
|
6653
6193
|
];
|
|
6654
|
-
const found = candidates.find((c) =>
|
|
6194
|
+
const found = candidates.find((c) => existsSync10(c));
|
|
6655
6195
|
if (!found)
|
|
6656
6196
|
throw new Error(
|
|
6657
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"
|
|
@@ -6682,7 +6222,7 @@ async function openSession(name, url, opts = { log: () => {
|
|
|
6682
6222
|
}
|
|
6683
6223
|
async function withSession(name, fn, opts = {}) {
|
|
6684
6224
|
const dir = sessionDir(name);
|
|
6685
|
-
if (!
|
|
6225
|
+
if (!existsSync10(dir))
|
|
6686
6226
|
throw new Error(`no session "${name}" (vos session list)`);
|
|
6687
6227
|
const explicit = process.env.VOS_BROWSER_PATH;
|
|
6688
6228
|
const ctx = await chromium.launchPersistentContext(dir, {
|
|
@@ -6701,7 +6241,7 @@ async function withSession(name, fn, opts = {}) {
|
|
|
6701
6241
|
}
|
|
6702
6242
|
async function summarizeSession(name) {
|
|
6703
6243
|
const dir = sessionDir(name);
|
|
6704
|
-
const created =
|
|
6244
|
+
const created = statSync2(dir).birthtimeMs || statSync2(dir).mtimeMs;
|
|
6705
6245
|
return withSession(name, async (ctx) => {
|
|
6706
6246
|
const cookies = await ctx.cookies();
|
|
6707
6247
|
const byHost = /* @__PURE__ */ new Map();
|
|
@@ -6731,9 +6271,9 @@ function summaryLine(s) {
|
|
|
6731
6271
|
return `session "${s.name}" holds cookies for ${hosts}${more}; ${exp}`;
|
|
6732
6272
|
}
|
|
6733
6273
|
function listSessions() {
|
|
6734
|
-
if (!
|
|
6735
|
-
return
|
|
6736
|
-
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));
|
|
6737
6277
|
return {
|
|
6738
6278
|
name: d.name,
|
|
6739
6279
|
ageDays: Math.floor(
|
|
@@ -6744,7 +6284,7 @@ function listSessions() {
|
|
|
6744
6284
|
}
|
|
6745
6285
|
function removeSession(name) {
|
|
6746
6286
|
const dir = sessionDir(name);
|
|
6747
|
-
if (!
|
|
6287
|
+
if (!existsSync10(dir)) return false;
|
|
6748
6288
|
rmSync(dir, { recursive: true, force: true });
|
|
6749
6289
|
return true;
|
|
6750
6290
|
}
|
|
@@ -6809,9 +6349,9 @@ async function encodeRecording(browser, takeDir, onProgress) {
|
|
|
6809
6349
|
}
|
|
6810
6350
|
|
|
6811
6351
|
// src/plugin/plan.ts
|
|
6812
|
-
import { existsSync as
|
|
6813
|
-
import { readFile as
|
|
6814
|
-
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";
|
|
6815
6355
|
import {
|
|
6816
6356
|
DEFAULT_FRAME_STYLE,
|
|
6817
6357
|
STYLE_FIELDS,
|
|
@@ -7033,10 +6573,10 @@ function retimeCut(prev, newSteps, newDurationMs) {
|
|
|
7033
6573
|
// src/plugin/plan.ts
|
|
7034
6574
|
var overlaps = (a, b) => a.in < b.out && b.in < a.out;
|
|
7035
6575
|
async function readDigestActivity(dir) {
|
|
7036
|
-
const file =
|
|
7037
|
-
if (!
|
|
6576
|
+
const file = join12(dir, "digest", "digest.json");
|
|
6577
|
+
if (!existsSync11(file)) return null;
|
|
7038
6578
|
try {
|
|
7039
|
-
const d = JSON.parse(await
|
|
6579
|
+
const d = JSON.parse(await readFile11(file, "utf8"));
|
|
7040
6580
|
return Array.isArray(d.activity) && d.activity.every((v) => typeof v === "number") ? d.activity : null;
|
|
7041
6581
|
} catch {
|
|
7042
6582
|
return null;
|
|
@@ -7249,16 +6789,16 @@ async function planTake(dir, opts = {}) {
|
|
|
7249
6789
|
}
|
|
7250
6790
|
|
|
7251
6791
|
// src/plugin/sync.ts
|
|
7252
|
-
import { createHash
|
|
7253
|
-
import { existsSync as
|
|
7254
|
-
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";
|
|
7255
6795
|
import { createInterface } from "readline/promises";
|
|
7256
|
-
import { basename
|
|
6796
|
+
import { basename, join as join14 } from "path";
|
|
7257
6797
|
import { lowerToComposition as lowerToComposition3, migrateHostedDoc as migrateHostedDoc3 } from "@vosjs/studio-core";
|
|
7258
6798
|
|
|
7259
6799
|
// src/plugin/folder.ts
|
|
7260
|
-
import { mkdir as
|
|
7261
|
-
import { join as
|
|
6800
|
+
import { mkdir as mkdir6, writeFile as writeFile8 } from "fs/promises";
|
|
6801
|
+
import { join as join13 } from "path";
|
|
7262
6802
|
import { recipeHints } from "@vosjs/shared/frontmatter";
|
|
7263
6803
|
import { migrateHostedDoc as migrateHostedDoc2 } from "@vosjs/studio-core";
|
|
7264
6804
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["json", "help", "media"]);
|
|
@@ -7368,7 +6908,7 @@ async function cmdMove(argv) {
|
|
|
7368
6908
|
});
|
|
7369
6909
|
const key = requireCredential(strFlag(flags, "key"));
|
|
7370
6910
|
const folderId = toRef === "none" ? null : resolveFolder(await listFolders(origin, key), toRef).id;
|
|
7371
|
-
let
|
|
6911
|
+
let failed = 0;
|
|
7372
6912
|
const moved = [];
|
|
7373
6913
|
for (const raw of positionals) {
|
|
7374
6914
|
const id = parseVosId(raw);
|
|
@@ -7383,28 +6923,28 @@ async function cmdMove(argv) {
|
|
|
7383
6923
|
continue;
|
|
7384
6924
|
}
|
|
7385
6925
|
if (asVos.status === 404) {
|
|
7386
|
-
const
|
|
6926
|
+
const asAsset = await apiJson(origin, `/api/assets/${id}`, {
|
|
7387
6927
|
method: "PATCH",
|
|
7388
6928
|
key,
|
|
7389
6929
|
body: { folderId }
|
|
7390
6930
|
});
|
|
7391
|
-
if (
|
|
6931
|
+
if (asAsset.status === 200) {
|
|
7392
6932
|
moved.push({ id, kind: "asset" });
|
|
7393
6933
|
r.log(`moved asset ${id}`);
|
|
7394
6934
|
continue;
|
|
7395
6935
|
}
|
|
7396
|
-
|
|
7397
|
-
r.log(apiError(`move ${id}`,
|
|
6936
|
+
failed++;
|
|
6937
|
+
r.log(apiError(`move ${id}`, asAsset));
|
|
7398
6938
|
continue;
|
|
7399
6939
|
}
|
|
7400
|
-
|
|
6940
|
+
failed++;
|
|
7401
6941
|
r.log(apiError(`move ${id}`, asVos));
|
|
7402
6942
|
}
|
|
7403
6943
|
r.done(
|
|
7404
|
-
{ moved, failed
|
|
7405
|
-
`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` : ""}`
|
|
7406
6946
|
);
|
|
7407
|
-
return
|
|
6947
|
+
return failed ? EXIT_ERROR : EXIT_OK;
|
|
7408
6948
|
}
|
|
7409
6949
|
function recipeLine(filename, body) {
|
|
7410
6950
|
const h = recipeHints(body);
|
|
@@ -7434,25 +6974,25 @@ async function cmdPull(argv) {
|
|
|
7434
6974
|
throw new Error(apiError(`pull folder ${folder.slug}`, res));
|
|
7435
6975
|
const payload = res.body;
|
|
7436
6976
|
const out = strFlag(flags, "out") ?? folder.slug;
|
|
7437
|
-
await
|
|
6977
|
+
await mkdir6(join13(out, "recipes"), { recursive: true });
|
|
7438
6978
|
const lines = [];
|
|
7439
6979
|
const recipes = [];
|
|
7440
6980
|
for (const rec of payload.recipes) {
|
|
7441
|
-
const path =
|
|
7442
|
-
await
|
|
6981
|
+
const path = join13(out, "recipes", safeName(rec.filename));
|
|
6982
|
+
await writeFile8(path, rec.body ?? "");
|
|
7443
6983
|
recipes.push({ path, line: recipeLine(rec.filename, rec.body ?? "") });
|
|
7444
6984
|
}
|
|
7445
6985
|
for (const rec of payload.inheritedRecipes) {
|
|
7446
6986
|
const from = safeName(rec.folderSlug ?? "inherited");
|
|
7447
|
-
await
|
|
7448
|
-
const path =
|
|
6987
|
+
await mkdir6(join13(out, "recipes", "_inherited", from), { recursive: true });
|
|
6988
|
+
const path = join13(
|
|
7449
6989
|
out,
|
|
7450
6990
|
"recipes",
|
|
7451
6991
|
"_inherited",
|
|
7452
6992
|
from,
|
|
7453
6993
|
safeName(rec.filename)
|
|
7454
6994
|
);
|
|
7455
|
-
await
|
|
6995
|
+
await writeFile8(path, rec.body ?? "");
|
|
7456
6996
|
recipes.push({
|
|
7457
6997
|
path,
|
|
7458
6998
|
line: `${recipeLine(rec.filename, rec.body ?? "")} [inherited from ${rec.folderName ?? from}]`
|
|
@@ -7460,15 +7000,15 @@ async function cmdPull(argv) {
|
|
|
7460
7000
|
}
|
|
7461
7001
|
const members = [];
|
|
7462
7002
|
for (const v of payload.voses) {
|
|
7463
|
-
const dir =
|
|
7464
|
-
await
|
|
7003
|
+
const dir = join13(out, "members", safeName(v.slug || v.title || v.id));
|
|
7004
|
+
await mkdir6(dir, { recursive: true });
|
|
7465
7005
|
const cfg = await apiJson(origin, `/api/vos/${v.id}/config`, { key });
|
|
7466
7006
|
if (cfg.status !== 200) {
|
|
7467
7007
|
lines.push(`skipped ${v.title}: ${apiError("fetch config", cfg)}`);
|
|
7468
7008
|
continue;
|
|
7469
7009
|
}
|
|
7470
|
-
await
|
|
7471
|
-
|
|
7010
|
+
await writeFile8(
|
|
7011
|
+
join13(dir, "config.json"),
|
|
7472
7012
|
JSON.stringify(cfg.body.config, null, 2)
|
|
7473
7013
|
);
|
|
7474
7014
|
let take = false;
|
|
@@ -7479,8 +7019,8 @@ async function cmdPull(argv) {
|
|
|
7479
7019
|
{ key }
|
|
7480
7020
|
);
|
|
7481
7021
|
if (doc.status === 200) {
|
|
7482
|
-
await
|
|
7483
|
-
|
|
7022
|
+
await writeFile8(
|
|
7023
|
+
join13(dir, "doc.json"),
|
|
7484
7024
|
JSON.stringify(doc.body, null, 2)
|
|
7485
7025
|
);
|
|
7486
7026
|
take = true;
|
|
@@ -7612,12 +7152,12 @@ function programPushTarget(ask) {
|
|
|
7612
7152
|
return { kind: "version", vosId: tracked, tracked: true };
|
|
7613
7153
|
}
|
|
7614
7154
|
function storageStateInDir(dir) {
|
|
7615
|
-
for (const f of
|
|
7155
|
+
for (const f of readdirSync2(dir)) {
|
|
7616
7156
|
if (!f.endsWith(".json") || f === "doc.json" || f === "meta.json" || f === "actions.json" || f === "cursor.json" || f === "vos.json" || f === "doc.prev.json")
|
|
7617
7157
|
continue;
|
|
7618
7158
|
try {
|
|
7619
|
-
const v = JSON.parse(readFileSync2(
|
|
7620
|
-
if (looksLikeStorageState(v)) return
|
|
7159
|
+
const v = JSON.parse(readFileSync2(join14(dir, f), "utf8"));
|
|
7160
|
+
if (looksLikeStorageState(v)) return join14(dir, f);
|
|
7621
7161
|
} catch {
|
|
7622
7162
|
}
|
|
7623
7163
|
}
|
|
@@ -7638,7 +7178,7 @@ async function pushTake(dir, flags, r) {
|
|
|
7638
7178
|
throw new Error(`doc.json fails lint:
|
|
7639
7179
|
${lint.problems.join("\n ")}`);
|
|
7640
7180
|
}
|
|
7641
|
-
if (!
|
|
7181
|
+
if (!existsSync12(take.paths.recording)) {
|
|
7642
7182
|
throw new Error(
|
|
7643
7183
|
"no recording in this take \u2014 record it, or `vos pull --media` to bring it home"
|
|
7644
7184
|
);
|
|
@@ -7687,8 +7227,8 @@ async function pushTake(dir, flags, r) {
|
|
|
7687
7227
|
throw new Error("push cancelled");
|
|
7688
7228
|
}
|
|
7689
7229
|
}
|
|
7690
|
-
const bytes = await
|
|
7691
|
-
const hash =
|
|
7230
|
+
const bytes = await readFile12(take.paths.recording);
|
|
7231
|
+
const hash = createHash("sha256").update(bytes).digest("hex");
|
|
7692
7232
|
const { type: contentType } = resolveMediaType({
|
|
7693
7233
|
head: bytes.subarray(0, MEDIA_HEAD_BYTES),
|
|
7694
7234
|
filename: take.paths.recordingName
|
|
@@ -7735,7 +7275,7 @@ async function pushTake(dir, flags, r) {
|
|
|
7735
7275
|
const lowered = lowerToComposition3(docForPush);
|
|
7736
7276
|
const config = { ...lowered.config, data: lowered.data };
|
|
7737
7277
|
if (!state) {
|
|
7738
|
-
const title = flags.title ?? `Take ${
|
|
7278
|
+
const title = flags.title ?? `Take ${basename(dir)} ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
7739
7279
|
const slug = `${title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "take"}-${Date.now().toString(36)}`;
|
|
7740
7280
|
const created = await api(ctx, "/vos", {
|
|
7741
7281
|
method: "POST",
|
|
@@ -7847,9 +7387,9 @@ async function pullTake(dir, flags, r) {
|
|
|
7847
7387
|
};
|
|
7848
7388
|
}
|
|
7849
7389
|
let media2;
|
|
7850
|
-
if (flags.media &&
|
|
7390
|
+
if (flags.media && existsSync12(join14(dir, "doc.json"))) {
|
|
7851
7391
|
const local = JSON.parse(
|
|
7852
|
-
await
|
|
7392
|
+
await readFile12(join14(dir, "doc.json"), "utf8")
|
|
7853
7393
|
);
|
|
7854
7394
|
media2 = await pullMedia(ctx, dir, local, r.log);
|
|
7855
7395
|
}
|
|
@@ -7910,10 +7450,10 @@ async function pullTake(dir, flags, r) {
|
|
|
7910
7450
|
const hostedDoc = migrateHostedDoc3(docRes.json);
|
|
7911
7451
|
const doc = hostedDoc;
|
|
7912
7452
|
const localRecording = takeMediaFile(dir, "recording", RECORDING_NAME);
|
|
7913
|
-
if (
|
|
7914
|
-
doc.source.videoKey =
|
|
7453
|
+
if (existsSync12(localRecording)) {
|
|
7454
|
+
doc.source.videoKey = basename(localRecording);
|
|
7915
7455
|
}
|
|
7916
|
-
await writeJson(
|
|
7456
|
+
await writeJson(join14(dir, "doc.json"), doc, true);
|
|
7917
7457
|
const media = flags.media ? await pullMedia(ctx, dir, doc, r.log) : void 0;
|
|
7918
7458
|
const versions = await api(ctx, `/vos/${vosId}/versions`);
|
|
7919
7459
|
const headRow = (versions.json.versions ?? []).find((v) => v.id === head);
|
|
@@ -7932,10 +7472,10 @@ async function pullTake(dir, flags, r) {
|
|
|
7932
7472
|
}
|
|
7933
7473
|
|
|
7934
7474
|
// src/plugin/program.ts
|
|
7935
|
-
import { existsSync as
|
|
7936
|
-
import { mkdir as
|
|
7475
|
+
import { existsSync as existsSync13 } from "fs";
|
|
7476
|
+
import { mkdir as mkdir7, readFile as readFile13, writeFile as writeFile9 } from "fs/promises";
|
|
7937
7477
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
7938
|
-
import { basename as
|
|
7478
|
+
import { basename as basename2, dirname as dirname3, join as join15 } from "path";
|
|
7939
7479
|
import {
|
|
7940
7480
|
CURRENT_CONFIG_VERSION,
|
|
7941
7481
|
migrateConfig,
|
|
@@ -8141,8 +7681,8 @@ function preflightConfig(parsed) {
|
|
|
8141
7681
|
}
|
|
8142
7682
|
function resolveConfigPath(target) {
|
|
8143
7683
|
if (target.endsWith(".json")) return target;
|
|
8144
|
-
const inDir =
|
|
8145
|
-
if (
|
|
7684
|
+
const inDir = join15(target, "config.json");
|
|
7685
|
+
if (existsSync13(inDir)) return inDir;
|
|
8146
7686
|
throw new UsageError(
|
|
8147
7687
|
`${target} has no config.json (and is not a take \u2014 no doc.json)`
|
|
8148
7688
|
);
|
|
@@ -8168,9 +7708,9 @@ async function cmdFetch(argv) {
|
|
|
8168
7708
|
throw new Error(apiError(`fetch config for ${vosId}`, cfg));
|
|
8169
7709
|
const slug = typeof vosMeta.slug === "string" && vosMeta.slug ? vosMeta.slug : vosId;
|
|
8170
7710
|
const out = strFlag(flags, "out") ?? slug;
|
|
8171
|
-
await
|
|
8172
|
-
await
|
|
8173
|
-
|
|
7711
|
+
await mkdir7(out, { recursive: true });
|
|
7712
|
+
await writeFile9(
|
|
7713
|
+
join15(out, "config.json"),
|
|
8174
7714
|
JSON.stringify(cfg.body.config, null, 2)
|
|
8175
7715
|
);
|
|
8176
7716
|
const title = typeof vosMeta.title === "string" ? vosMeta.title : "";
|
|
@@ -8195,18 +7735,40 @@ async function cmdFetch(argv) {
|
|
|
8195
7735
|
const hosted = migrateHostedDoc4(doc.body);
|
|
8196
7736
|
const { config: own } = await writeProgramDoc(out, hosted);
|
|
8197
7737
|
if (own) {
|
|
8198
|
-
await
|
|
7738
|
+
await writeFile9(join15(out, "config.json"), JSON.stringify(own, null, 2));
|
|
8199
7739
|
}
|
|
8200
7740
|
} else if (doc.status === 200) {
|
|
8201
7741
|
take = true;
|
|
8202
7742
|
const hosted = migrateHostedDoc4(doc.body);
|
|
8203
|
-
await
|
|
7743
|
+
await writeFile9(join15(out, "doc.json"), JSON.stringify(hosted, null, 2));
|
|
8204
7744
|
if (flags.media === true) {
|
|
8205
7745
|
const media = await pullMedia({ origin, key }, out, hosted, r.log);
|
|
8206
7746
|
mediaLine = media.downloaded.length ? ` + ${media.downloaded.map((m) => m.file).join(", ")}` : "";
|
|
8207
7747
|
}
|
|
8208
7748
|
}
|
|
8209
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)
|
|
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
|
+
}
|
|
8210
7772
|
r.done(
|
|
8211
7773
|
{
|
|
8212
7774
|
out,
|
|
@@ -8217,7 +7779,7 @@ async function cmdFetch(argv) {
|
|
|
8217
7779
|
take
|
|
8218
7780
|
},
|
|
8219
7781
|
take ? `Wrote ${out}/doc.json + config.json + vos.json${mediaLine} (${title || id}, a take)
|
|
8220
|
-
` + (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}
|
|
8221
7783
|
Edit config.json, then: vos check ${out}/config.json && vos push ${out}/config.json`
|
|
8222
7784
|
);
|
|
8223
7785
|
return EXIT_OK;
|
|
@@ -8313,8 +7875,8 @@ async function cmdPushProgram(argv) {
|
|
|
8313
7875
|
origin: strFlag(flags, "origin"),
|
|
8314
7876
|
api: strFlag(flags, "api")
|
|
8315
7877
|
});
|
|
8316
|
-
const dir =
|
|
8317
|
-
const parsed = JSON.parse(await
|
|
7878
|
+
const dir = dirname3(source);
|
|
7879
|
+
const parsed = JSON.parse(await readFile13(source, "utf8"));
|
|
8318
7880
|
const pre = preflightConfig(parsed);
|
|
8319
7881
|
if (!pre.ok || !pre.config) {
|
|
8320
7882
|
for (const issue of pre.issues) r.log(`error ${issue}`);
|
|
@@ -8334,6 +7896,7 @@ async function cmdPushProgram(argv) {
|
|
|
8334
7896
|
await setProgramStill(dir, t);
|
|
8335
7897
|
r.log(`cover: ${t}s (doc.json "still")`);
|
|
8336
7898
|
}
|
|
7899
|
+
for (const w of hostedLiteralWarnings(config)) r.log(`warning ${w}`);
|
|
8337
7900
|
const programDoc = await readProgramDoc(dir, config);
|
|
8338
7901
|
if (programDoc) {
|
|
8339
7902
|
const lint = lintDoc(programDoc);
|
|
@@ -8349,7 +7912,7 @@ async function cmdPushProgram(argv) {
|
|
|
8349
7912
|
"--claimable creates a NEW claimable vos and cannot iterate (--vos). After the human claims it, iterate with their key: vos push --vos <id>"
|
|
8350
7913
|
);
|
|
8351
7914
|
}
|
|
8352
|
-
const fallback = state?.title ? `${state.title} remix` :
|
|
7915
|
+
const fallback = state?.title ? `${state.title} remix` : basename2(source).replace(/\.json$/i, "") || "vos program";
|
|
8353
7916
|
const title2 = (strFlag(flags, "title") ?? fallback).slice(0, 100);
|
|
8354
7917
|
let claimDoc = programDoc;
|
|
8355
7918
|
if (programDoc) {
|
|
@@ -8366,6 +7929,12 @@ async function cmdPushProgram(argv) {
|
|
|
8366
7929
|
);
|
|
8367
7930
|
}
|
|
8368
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
|
+
}
|
|
8369
7938
|
const body = {
|
|
8370
7939
|
title: title2,
|
|
8371
7940
|
config: storedProgramConfig(config, claimDoc)
|
|
@@ -8399,6 +7968,7 @@ with their key: vos push ${source} --vos ${String(created2.id ?? "<id>")}`
|
|
|
8399
7968
|
(l) => r.log(l)
|
|
8400
7969
|
);
|
|
8401
7970
|
}
|
|
7971
|
+
await uploadManifest(config, dir, { origin, key }, (l) => r.log(l));
|
|
8402
7972
|
const overrides = [
|
|
8403
7973
|
...multi.override ?? [],
|
|
8404
7974
|
...strFlag(flags, "overrides")?.split(",") ?? []
|
|
@@ -8498,7 +8068,7 @@ ${landedLines(landed2)}` : "")
|
|
|
8498
8068
|
return EXIT_OK;
|
|
8499
8069
|
}
|
|
8500
8070
|
const remixOfId = pushTarget.kind === "create" ? pushTarget.remixOfId : void 0;
|
|
8501
|
-
const fallbackTitle = state?.title ? `${state.title} remix` :
|
|
8071
|
+
const fallbackTitle = state?.title ? `${state.title} remix` : basename2(source).replace(/\.json$/i, "") || "vos remix";
|
|
8502
8072
|
const title = (strFlag(flags, "title") ?? fallbackTitle).slice(0, 100);
|
|
8503
8073
|
const folderId = folderRef ? resolveFolder(await listFolders(origin, key), folderRef).id : void 0;
|
|
8504
8074
|
const created = await createProgramVos({
|
|
@@ -8566,7 +8136,7 @@ Iterate with: vos push ${source} --vos ${created.id}`
|
|
|
8566
8136
|
async function cmdPullProgram(argv) {
|
|
8567
8137
|
const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS2);
|
|
8568
8138
|
const target = positionals[0] ?? ".";
|
|
8569
|
-
const dir = target.endsWith(".json") ?
|
|
8139
|
+
const dir = target.endsWith(".json") ? dirname3(target) : target;
|
|
8570
8140
|
const r = createReporter(flags.json === true);
|
|
8571
8141
|
const origin = platformOrigin({
|
|
8572
8142
|
origin: strFlag(flags, "origin"),
|
|
@@ -8630,10 +8200,10 @@ async function cmdPullProgram(argv) {
|
|
|
8630
8200
|
const cfg = await apiJson(origin, `/api/vos/${vosId}/config`, { key });
|
|
8631
8201
|
if (cfg.status !== 200)
|
|
8632
8202
|
throw new Error(apiError(`fetch head config for ${vosId}`, cfg));
|
|
8633
|
-
const configPath =
|
|
8203
|
+
const configPath = join15(dir, "config.json");
|
|
8634
8204
|
let backedUp = false;
|
|
8635
|
-
if (
|
|
8636
|
-
await
|
|
8205
|
+
if (existsSync13(configPath)) {
|
|
8206
|
+
await writeFile9(join15(dir, "config.backup.json"), await readFile13(configPath));
|
|
8637
8207
|
backedUp = true;
|
|
8638
8208
|
}
|
|
8639
8209
|
let headConfig = cfg.body.config;
|
|
@@ -8652,7 +8222,7 @@ async function cmdPullProgram(argv) {
|
|
|
8652
8222
|
if (own) headConfig = own;
|
|
8653
8223
|
}
|
|
8654
8224
|
}
|
|
8655
|
-
await
|
|
8225
|
+
await writeFile9(configPath, JSON.stringify(headConfig, null, 2));
|
|
8656
8226
|
writeSyncState(dir, {
|
|
8657
8227
|
vosId,
|
|
8658
8228
|
versionId: typeof head.id === "string" ? head.id : since
|
|
@@ -8666,7 +8236,7 @@ async function cmdPullProgram(argv) {
|
|
|
8666
8236
|
protected: protectedIds,
|
|
8667
8237
|
changes,
|
|
8668
8238
|
out: configPath,
|
|
8669
|
-
backup: backedUp ?
|
|
8239
|
+
backup: backedUp ? join15(dir, "config.backup.json") : null
|
|
8670
8240
|
},
|
|
8671
8241
|
`Pulled ${changes.length} version${changes.length === 1 ? "" : "s"} \u2192 ${configPath}` + (backedUp ? ` (previous copy: config.backup.json)` : "") + `
|
|
8672
8242
|
Re-apply your edit on the new head, then: vos push ${configPath} --vos ${vosId}`
|
|
@@ -8753,9 +8323,9 @@ function storedProgramConfig(config, programDoc) {
|
|
|
8753
8323
|
return lowerProgramDoc(doc).config;
|
|
8754
8324
|
}
|
|
8755
8325
|
async function readProgramDoc(dir, config) {
|
|
8756
|
-
const docPath =
|
|
8757
|
-
if (!
|
|
8758
|
-
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"));
|
|
8759
8329
|
if (typeof parsed !== "object" || parsed === null || "source" in parsed)
|
|
8760
8330
|
return null;
|
|
8761
8331
|
const raw = parsed;
|
|
@@ -8763,32 +8333,32 @@ async function readProgramDoc(dir, config) {
|
|
|
8763
8333
|
return { ...raw, program: { ...program, config } };
|
|
8764
8334
|
}
|
|
8765
8335
|
async function setProgramStill(dir, t) {
|
|
8766
|
-
const docPath =
|
|
8336
|
+
const docPath = join15(dir, "doc.json");
|
|
8767
8337
|
let doc = { program: {}, audio: [] };
|
|
8768
|
-
if (
|
|
8769
|
-
const parsed = JSON.parse(await
|
|
8338
|
+
if (existsSync13(docPath)) {
|
|
8339
|
+
const parsed = JSON.parse(await readFile13(docPath, "utf8"));
|
|
8770
8340
|
if (!parsed || typeof parsed !== "object" || "source" in parsed)
|
|
8771
8341
|
throw new UsageError(
|
|
8772
8342
|
`${docPath} is a take's document; a take's cover is its own "still" field`
|
|
8773
8343
|
);
|
|
8774
8344
|
doc = parsed;
|
|
8775
8345
|
}
|
|
8776
|
-
await
|
|
8346
|
+
await writeFile9(docPath, JSON.stringify({ ...doc, still: t }, null, 2) + "\n");
|
|
8777
8347
|
}
|
|
8778
8348
|
async function writeProgramDoc(dir, hosted) {
|
|
8779
8349
|
const program = hosted.program && typeof hosted.program === "object" ? hosted.program : {};
|
|
8780
8350
|
const { config, ...rest } = program;
|
|
8781
8351
|
const onDisk = { ...hosted, program: rest };
|
|
8782
|
-
await
|
|
8352
|
+
await writeFile9(join15(dir, "doc.json"), JSON.stringify(onDisk, null, 2));
|
|
8783
8353
|
return {
|
|
8784
8354
|
config: config && typeof config === "object" ? config : null
|
|
8785
8355
|
};
|
|
8786
8356
|
}
|
|
8787
8357
|
|
|
8788
8358
|
// src/plugin/asset.ts
|
|
8789
|
-
import { createHash as
|
|
8359
|
+
import { createHash as createHash2 } from "crypto";
|
|
8790
8360
|
import { readFileSync as readFileSync3 } from "fs";
|
|
8791
|
-
import { basename as
|
|
8361
|
+
import { basename as basename3, extname as extname2 } from "path";
|
|
8792
8362
|
var BOOLEAN_FLAGS3 = /* @__PURE__ */ new Set(["json", "help"]);
|
|
8793
8363
|
var MIME_BY_EXT = {
|
|
8794
8364
|
".png": "image/png",
|
|
@@ -8840,7 +8410,7 @@ async function cmdPush(argv) {
|
|
|
8840
8410
|
const uploaded = [];
|
|
8841
8411
|
let unfiledMedia = 0;
|
|
8842
8412
|
for (const file of positionals) {
|
|
8843
|
-
const name =
|
|
8413
|
+
const name = basename3(file);
|
|
8844
8414
|
const body = readFileSync3(file);
|
|
8845
8415
|
const media = resolveMediaType({
|
|
8846
8416
|
head: body.subarray(0, MEDIA_HEAD_BYTES),
|
|
@@ -8853,7 +8423,7 @@ async function cmdPush(argv) {
|
|
|
8853
8423
|
put = await uploadAsset({ origin, key }, new Uint8Array(body), {
|
|
8854
8424
|
filename: sent,
|
|
8855
8425
|
contentType: media.type,
|
|
8856
|
-
contentHash:
|
|
8426
|
+
contentHash: createHash2("sha256").update(body).digest("hex"),
|
|
8857
8427
|
...folder ? { folderId: folder.id } : {}
|
|
8858
8428
|
});
|
|
8859
8429
|
} catch (e) {
|
|
@@ -8924,8 +8494,8 @@ async function cmdAsset(argv) {
|
|
|
8924
8494
|
}
|
|
8925
8495
|
|
|
8926
8496
|
// src/plugin/remove.ts
|
|
8927
|
-
import { existsSync as
|
|
8928
|
-
import { join as
|
|
8497
|
+
import { existsSync as existsSync14, rmSync as rmSync2, statSync as statSync3 } from "fs";
|
|
8498
|
+
import { join as join16 } from "path";
|
|
8929
8499
|
import { createInterface as createInterface3 } from "readline/promises";
|
|
8930
8500
|
|
|
8931
8501
|
// src/plugin/trash.ts
|
|
@@ -9104,7 +8674,7 @@ async function cmdDelete(argv) {
|
|
|
9104
8674
|
api: strFlag(flags, "api")
|
|
9105
8675
|
}).replace(/\/+$/, "");
|
|
9106
8676
|
const key = requireCredential(strFlag(flags, "key"));
|
|
9107
|
-
const dir =
|
|
8677
|
+
const dir = existsSync14(target) && statSync3(target).isDirectory() ? target : null;
|
|
9108
8678
|
const vosId = dir ? readSyncState(dir)?.vosId : parseVosId(target);
|
|
9109
8679
|
if (!vosId)
|
|
9110
8680
|
throw new UsageError(
|
|
@@ -9146,7 +8716,7 @@ async function cmdDelete(argv) {
|
|
|
9146
8716
|
throw new Error(apiError(`delete ${vosId}`, res));
|
|
9147
8717
|
let unlinked = false;
|
|
9148
8718
|
if (dir) {
|
|
9149
|
-
rmSync2(
|
|
8719
|
+
rmSync2(join16(dir, SYNC_STATE_NAME), { force: true });
|
|
9150
8720
|
unlinked = true;
|
|
9151
8721
|
}
|
|
9152
8722
|
const trashed = res.body.trashed === true;
|
|
@@ -9172,7 +8742,7 @@ async function cmdDelete(argv) {
|
|
|
9172
8742
|
|
|
9173
8743
|
// src/plugin/buildProgram.ts
|
|
9174
8744
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
9175
|
-
import { basename as
|
|
8745
|
+
import { basename as basename4, dirname as dirname4, join as join17, resolve as resolve7 } from "path";
|
|
9176
8746
|
import { pathToFileURL } from "url";
|
|
9177
8747
|
import { parse } from "acorn";
|
|
9178
8748
|
import { fullAncestor } from "acorn-walk";
|
|
@@ -9380,7 +8950,7 @@ async function cmdBuild(argv) {
|
|
|
9380
8950
|
throw new UsageError(
|
|
9381
8951
|
`${source} exports no program: \`export default { version: 2, duration, camera, createContent, createTimeline, \u2026 }\``
|
|
9382
8952
|
);
|
|
9383
|
-
const built = buildProgram(program,
|
|
8953
|
+
const built = buildProgram(program, basename4(file), text);
|
|
9384
8954
|
for (const p of built.problems) r.log(`error ${p.fn}: ${p.message}`);
|
|
9385
8955
|
if (built.problems.length) {
|
|
9386
8956
|
r.done(
|
|
@@ -9390,7 +8960,7 @@ async function cmdBuild(argv) {
|
|
|
9390
8960
|
return EXIT_ERROR;
|
|
9391
8961
|
}
|
|
9392
8962
|
const out = resolve7(
|
|
9393
|
-
strFlag(flags, "out") ??
|
|
8963
|
+
strFlag(flags, "out") ?? join17(dirname4(file), "config.json")
|
|
9394
8964
|
);
|
|
9395
8965
|
writeFileSync2(out, JSON.stringify(built.config, null, 2) + "\n");
|
|
9396
8966
|
const sidecar = out.replace(/\.json$/, "") + ".sources.json";
|
|
@@ -9407,8 +8977,8 @@ async function cmdBuild(argv) {
|
|
|
9407
8977
|
|
|
9408
8978
|
// src/plugin/compare.ts
|
|
9409
8979
|
import { spawnSync } from "child_process";
|
|
9410
|
-
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
9411
|
-
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";
|
|
9412
8982
|
var BOOLEAN_FLAGS7 = /* @__PURE__ */ new Set(["json", "help"]);
|
|
9413
8983
|
function sampleTimes(duration, every) {
|
|
9414
8984
|
const step = every > 0 ? every : 1;
|
|
@@ -9474,6 +9044,12 @@ async function cmdCompare(argv) {
|
|
|
9474
9044
|
for (const w of loaded.warnings) r.log(`note: ${w}`);
|
|
9475
9045
|
const config = loaded.config;
|
|
9476
9046
|
const duration = configDuration(config) ?? 5;
|
|
9047
|
+
const assets = await programAssets(config, {
|
|
9048
|
+
baseDir: existsSync15(source) && statSync4(source).isDirectory() ? source : dirname5(source),
|
|
9049
|
+
origin: platformOrigin({}),
|
|
9050
|
+
key: resolveCredential(),
|
|
9051
|
+
log: (line) => r.log(line)
|
|
9052
|
+
});
|
|
9477
9053
|
const probed = probeSize(ref);
|
|
9478
9054
|
const half = probed && probed.width > 1280 ? 2 : 1;
|
|
9479
9055
|
const width = Number(
|
|
@@ -9492,9 +9068,9 @@ async function cmdCompare(argv) {
|
|
|
9492
9068
|
try {
|
|
9493
9069
|
for (const t of times) {
|
|
9494
9070
|
const tag = t.toFixed(2);
|
|
9495
|
-
const refPng =
|
|
9496
|
-
const vosPng =
|
|
9497
|
-
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`);
|
|
9498
9074
|
const grab = ffmpeg([
|
|
9499
9075
|
"-v",
|
|
9500
9076
|
"error",
|
|
@@ -9515,6 +9091,7 @@ async function cmdCompare(argv) {
|
|
|
9515
9091
|
);
|
|
9516
9092
|
const still = await renderStill(browser, {
|
|
9517
9093
|
config,
|
|
9094
|
+
assets,
|
|
9518
9095
|
width,
|
|
9519
9096
|
height,
|
|
9520
9097
|
time: t
|
|
@@ -9550,7 +9127,7 @@ async function cmdCompare(argv) {
|
|
|
9550
9127
|
} finally {
|
|
9551
9128
|
await browser.close();
|
|
9552
9129
|
}
|
|
9553
|
-
const overview =
|
|
9130
|
+
const overview = join18(outDir, "overview.png");
|
|
9554
9131
|
if (scores.length) {
|
|
9555
9132
|
const inputs = scores.flatMap((s) => ["-i", s.sheet]);
|
|
9556
9133
|
const scaled = scores.map((_, i) => `[${i}:v]scale=iw/3:-2[s${i}]`).join(";");
|
|
@@ -9576,7 +9153,7 @@ async function cmdCompare(argv) {
|
|
|
9576
9153
|
overview
|
|
9577
9154
|
};
|
|
9578
9155
|
writeFileSync3(
|
|
9579
|
-
|
|
9156
|
+
join18(outDir, "report.json"),
|
|
9580
9157
|
JSON.stringify(report, null, 2) + "\n"
|
|
9581
9158
|
);
|
|
9582
9159
|
r.done(
|
|
@@ -9587,28 +9164,28 @@ async function cmdCompare(argv) {
|
|
|
9587
9164
|
}
|
|
9588
9165
|
|
|
9589
9166
|
// src/plugin/port.ts
|
|
9590
|
-
import { existsSync as
|
|
9591
|
-
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";
|
|
9592
9169
|
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
9593
9170
|
|
|
9594
9171
|
// src/plugin/portInventory.ts
|
|
9595
9172
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
9596
9173
|
import {
|
|
9597
|
-
existsSync as
|
|
9174
|
+
existsSync as existsSync17,
|
|
9598
9175
|
mkdirSync as mkdirSync3,
|
|
9599
9176
|
readFileSync as readFileSync6,
|
|
9600
|
-
readdirSync as
|
|
9177
|
+
readdirSync as readdirSync4,
|
|
9601
9178
|
statSync as statSync6
|
|
9602
9179
|
} from "fs";
|
|
9603
|
-
import { dirname as dirname6, join as
|
|
9180
|
+
import { dirname as dirname6, join as join20, relative as relative2, resolve as resolve9 } from "path";
|
|
9604
9181
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
9605
9182
|
import { findFontFamily as findFontFamily3 } from "@vosjs/shared";
|
|
9606
9183
|
|
|
9607
9184
|
// src/plugin/portRemotion.ts
|
|
9608
|
-
import { existsSync as
|
|
9185
|
+
import { existsSync as existsSync16, readFileSync as readFileSync5, readdirSync as readdirSync3, statSync as statSync5 } from "fs";
|
|
9609
9186
|
import { createServer as createServer2 } from "http";
|
|
9610
9187
|
import { createRequire } from "module";
|
|
9611
|
-
import { basename as
|
|
9188
|
+
import { basename as basename5, extname as extname3, join as join19 } from "path";
|
|
9612
9189
|
function themeColors(sources) {
|
|
9613
9190
|
const out = {};
|
|
9614
9191
|
const re = /\bconst\s+([A-Za-z_$][\w$]*)\s*(?::\s*string\s*)?=\s*['"](#[0-9a-fA-F]{3,8})['"]/g;
|
|
@@ -9672,16 +9249,16 @@ function sourceTexts(dir) {
|
|
|
9672
9249
|
const out = [];
|
|
9673
9250
|
const walk = (d, depth) => {
|
|
9674
9251
|
if (depth > 4 || out.length > 200) return;
|
|
9675
|
-
for (const name of
|
|
9252
|
+
for (const name of readdirSync3(d)) {
|
|
9676
9253
|
if (name === "node_modules" || name.startsWith(".")) continue;
|
|
9677
|
-
const p =
|
|
9254
|
+
const p = join19(d, name);
|
|
9678
9255
|
if (statSync5(p).isDirectory()) walk(p, depth + 1);
|
|
9679
9256
|
else if (/\.(tsx?|jsx?|mjs)$/.test(name))
|
|
9680
9257
|
out.push(readFileSync5(p, "utf8"));
|
|
9681
9258
|
}
|
|
9682
9259
|
};
|
|
9683
|
-
const src =
|
|
9684
|
-
walk(
|
|
9260
|
+
const src = join19(dir, "src");
|
|
9261
|
+
walk(existsSync16(src) ? src : dir, 0);
|
|
9685
9262
|
return out;
|
|
9686
9263
|
}
|
|
9687
9264
|
function entryPoint(dir) {
|
|
@@ -9693,7 +9270,7 @@ function entryPoint(dir) {
|
|
|
9693
9270
|
"remotion/index.ts",
|
|
9694
9271
|
"remotion/index.tsx"
|
|
9695
9272
|
])
|
|
9696
|
-
if (
|
|
9273
|
+
if (existsSync16(join19(dir, p))) return join19(dir, p);
|
|
9697
9274
|
return null;
|
|
9698
9275
|
}
|
|
9699
9276
|
var TYPES = {
|
|
@@ -9768,7 +9345,7 @@ async function readRemotion(dir, log) {
|
|
|
9768
9345
|
const entry = entryPoint(dir);
|
|
9769
9346
|
let bundler;
|
|
9770
9347
|
try {
|
|
9771
|
-
bundler = createRequire(
|
|
9348
|
+
bundler = createRequire(join19(dir, "package.json"))("@remotion/bundler");
|
|
9772
9349
|
} catch {
|
|
9773
9350
|
log?.(
|
|
9774
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"
|
|
@@ -9785,9 +9362,9 @@ async function readRemotion(dir, log) {
|
|
|
9785
9362
|
} });
|
|
9786
9363
|
const server = createServer2((rq, rs) => {
|
|
9787
9364
|
const path = decodeURIComponent(new URL(rq.url ?? "/", "http://x").pathname);
|
|
9788
|
-
let f =
|
|
9789
|
-
if (!f.startsWith(out) || !
|
|
9790
|
-
f =
|
|
9365
|
+
let f = join19(out, path);
|
|
9366
|
+
if (!f.startsWith(out) || !existsSync16(f) || statSync5(f).isDirectory())
|
|
9367
|
+
f = join19(out, "index.html");
|
|
9791
9368
|
rs.writeHead(200, {
|
|
9792
9369
|
"content-type": TYPES[extname3(f)] ?? "application/octet-stream"
|
|
9793
9370
|
});
|
|
@@ -9886,10 +9463,10 @@ async function readRemotion(dir, log) {
|
|
|
9886
9463
|
perScene.push({ scene: s.name, texts: read.texts ?? [] });
|
|
9887
9464
|
}
|
|
9888
9465
|
const sources = sourceTexts(dir);
|
|
9889
|
-
const publicDir =
|
|
9466
|
+
const publicDir = join19(dir, "public");
|
|
9890
9467
|
const media = tree.audio.map((a) => {
|
|
9891
|
-
const name =
|
|
9892
|
-
const local =
|
|
9468
|
+
const name = basename5(a.src.split("?")[0]);
|
|
9469
|
+
const local = existsSync16(join19(publicDir, name)) ? `public/${name}` : a.src;
|
|
9893
9470
|
return {
|
|
9894
9471
|
kind: "audio",
|
|
9895
9472
|
src: local,
|
|
@@ -9930,9 +9507,9 @@ async function readRemotion(dir, log) {
|
|
|
9930
9507
|
|
|
9931
9508
|
// src/plugin/portInventory.ts
|
|
9932
9509
|
function detectEngine(dir) {
|
|
9933
|
-
if (
|
|
9934
|
-
const pkg =
|
|
9935
|
-
if (
|
|
9510
|
+
if (existsSync17(join20(dir, "hyperframes.json"))) return "hyperframes";
|
|
9511
|
+
const pkg = join20(dir, "package.json");
|
|
9512
|
+
if (existsSync17(pkg)) {
|
|
9936
9513
|
try {
|
|
9937
9514
|
const p = JSON.parse(readFileSync6(pkg, "utf8"));
|
|
9938
9515
|
const deps = { ...p.dependencies ?? {}, ...p.devDependencies ?? {} };
|
|
@@ -9946,14 +9523,14 @@ function detectEngine(dir) {
|
|
|
9946
9523
|
return "page";
|
|
9947
9524
|
}
|
|
9948
9525
|
function sourcePage(source) {
|
|
9949
|
-
if (!
|
|
9526
|
+
if (!existsSync17(source)) return null;
|
|
9950
9527
|
if (statSync6(source).isFile()) return /\.html?$/i.test(source) ? source : null;
|
|
9951
|
-
const index =
|
|
9952
|
-
return
|
|
9528
|
+
const index = join20(source, "index.html");
|
|
9529
|
+
return existsSync17(index) ? index : null;
|
|
9953
9530
|
}
|
|
9954
9531
|
function findRender(dir) {
|
|
9955
|
-
const videos = (d) =>
|
|
9956
|
-
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]) {
|
|
9957
9534
|
const found = videos(d);
|
|
9958
9535
|
if (found.length) return found[0];
|
|
9959
9536
|
}
|
|
@@ -10224,7 +9801,7 @@ var SEEK_AND_READ = `((args) => {
|
|
|
10224
9801
|
})`;
|
|
10225
9802
|
async function readInventory(source, opts) {
|
|
10226
9803
|
const abs = resolve9(source);
|
|
10227
|
-
const dir =
|
|
9804
|
+
const dir = existsSync17(abs) && statSync6(abs).isDirectory() ? abs : dirname6(abs);
|
|
10228
9805
|
const engine = detectEngine(dir);
|
|
10229
9806
|
const page = sourcePage(abs);
|
|
10230
9807
|
const render = opts.render === void 0 ? findRender(dir) : opts.render;
|
|
@@ -10327,7 +9904,7 @@ async function readInventory(source, opts) {
|
|
|
10327
9904
|
if (render && duration) {
|
|
10328
9905
|
const has = spawnSync2("ffmpeg", ["-version"]).status === 0;
|
|
10329
9906
|
if (has) {
|
|
10330
|
-
const refDir =
|
|
9907
|
+
const refDir = join20(opts.outDir, "ref");
|
|
10331
9908
|
mkdirSync3(refDir, { recursive: true });
|
|
10332
9909
|
const times = read.scenes.length >= 3 ? read.scenes.map(
|
|
10333
9910
|
(s) => s.start + (s.duration ?? 1) / 2
|
|
@@ -10336,7 +9913,7 @@ async function readInventory(source, opts) {
|
|
|
10336
9913
|
(_, i) => i + 0.5
|
|
10337
9914
|
);
|
|
10338
9915
|
for (const t of times.filter((t2) => t2 < duration)) {
|
|
10339
|
-
const file =
|
|
9916
|
+
const file = join20(refDir, `src-${t.toFixed(2)}.png`);
|
|
10340
9917
|
const ok = spawnSync2("ffmpeg", [
|
|
10341
9918
|
"-v",
|
|
10342
9919
|
"error",
|
|
@@ -10390,8 +9967,8 @@ async function readInventory(source, opts) {
|
|
|
10390
9967
|
|
|
10391
9968
|
// src/plugin/portScaffold.ts
|
|
10392
9969
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
10393
|
-
import { copyFileSync, existsSync as
|
|
10394
|
-
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";
|
|
10395
9972
|
import { findFontFamily as findFontFamily4, fontFaceUrl, nearestFontWeight } from "@vosjs/shared";
|
|
10396
9973
|
|
|
10397
9974
|
// src/plugin/painterLib.ts
|
|
@@ -10801,8 +10378,8 @@ function planScaffold(inv, opts) {
|
|
|
10801
10378
|
const copies = [];
|
|
10802
10379
|
inv.media.filter((m) => m.kind === "audio" && m.src).forEach((m, i) => {
|
|
10803
10380
|
const from = resolve10(opts.sourceDir, m.src);
|
|
10804
|
-
const name =
|
|
10805
|
-
if (!
|
|
10381
|
+
const name = basename6(from);
|
|
10382
|
+
if (!existsSync18(from)) {
|
|
10806
10383
|
report.push(
|
|
10807
10384
|
`- the audio ${m.src} is not in the source folder; add it to doc.json by hand`
|
|
10808
10385
|
);
|
|
@@ -10945,7 +10522,7 @@ ${sceneTodos}${springHint}
|
|
|
10945
10522
|
}
|
|
10946
10523
|
function reportText(inv, r) {
|
|
10947
10524
|
const lines = [
|
|
10948
|
-
`# Port report: ${inv.source === "." && inv.root ?
|
|
10525
|
+
`# Port report: ${inv.source === "." && inv.root ? basename6(inv.root) : inv.source}`,
|
|
10949
10526
|
"",
|
|
10950
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}\`.`,
|
|
10951
10528
|
"",
|
|
@@ -11000,13 +10577,13 @@ function reportText(inv, r) {
|
|
|
11000
10577
|
}
|
|
11001
10578
|
function writeScaffold(plan, outDir) {
|
|
11002
10579
|
mkdirSync4(outDir, { recursive: true });
|
|
11003
|
-
writeFileSync4(
|
|
10580
|
+
writeFileSync4(join21(outDir, "program.mjs"), plan.program);
|
|
11004
10581
|
writeFileSync4(
|
|
11005
|
-
|
|
10582
|
+
join21(outDir, "doc.json"),
|
|
11006
10583
|
JSON.stringify(plan.doc, null, 2) + "\n"
|
|
11007
10584
|
);
|
|
11008
|
-
writeFileSync4(
|
|
11009
|
-
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));
|
|
11010
10587
|
}
|
|
11011
10588
|
function probeAudioDuration(file) {
|
|
11012
10589
|
const res = spawnSync3(
|
|
@@ -11040,7 +10617,7 @@ async function inventory(argv) {
|
|
|
11040
10617
|
outDir,
|
|
11041
10618
|
log: (l) => r.log(`note: ${l}`)
|
|
11042
10619
|
});
|
|
11043
|
-
const file =
|
|
10620
|
+
const file = join22(outDir, "inventory.json");
|
|
11044
10621
|
writeFileSync5(file, JSON.stringify(inv, null, 2) + "\n");
|
|
11045
10622
|
const missing = inv.fonts.filter((f) => !f.catalog).map((f) => f.family);
|
|
11046
10623
|
r.log(
|
|
@@ -11072,32 +10649,32 @@ function summary(inv) {
|
|
|
11072
10649
|
async function scaffold(argv) {
|
|
11073
10650
|
const { flags } = parseArgs(argv, BOOLEAN_FLAGS8);
|
|
11074
10651
|
const r = createReporter(flags.json === true);
|
|
11075
|
-
const from = resolve11(strFlag(flags, "from") ??
|
|
11076
|
-
if (!
|
|
10652
|
+
const from = resolve11(strFlag(flags, "from") ?? join22("port", "inventory.json"));
|
|
10653
|
+
if (!existsSync19(from))
|
|
11077
10654
|
throw new UsageError(
|
|
11078
10655
|
`no inventory at ${from}: run npx vos port inventory <source> first`
|
|
11079
10656
|
);
|
|
11080
10657
|
const inv = JSON.parse(readFileSync7(from, "utf8"));
|
|
11081
10658
|
const outDir = resolve11(
|
|
11082
|
-
strFlag(flags, "out") ??
|
|
10659
|
+
strFlag(flags, "out") ?? join22(dirname8(from), "program")
|
|
11083
10660
|
);
|
|
11084
10661
|
const plan = planScaffold(inv, {
|
|
11085
10662
|
sourceDir: scaffoldSourceDir(inv),
|
|
11086
10663
|
probeDuration: probeAudioDuration
|
|
11087
10664
|
});
|
|
11088
10665
|
writeScaffold(plan, outDir);
|
|
11089
|
-
const programFile =
|
|
10666
|
+
const programFile = join22(outDir, "program.mjs");
|
|
11090
10667
|
const mod = await import(`${pathToFileURL3(programFile).href}?t=${Date.now()}`);
|
|
11091
10668
|
const built = buildProgram(mod.default, "program.mjs", plan.program);
|
|
11092
10669
|
for (const p of built.problems) r.log(`error ${p.fn}: ${p.message}`);
|
|
11093
10670
|
if (built.problems.length) return EXIT_ERROR;
|
|
11094
10671
|
writeFileSync5(
|
|
11095
|
-
|
|
10672
|
+
join22(outDir, "config.json"),
|
|
11096
10673
|
JSON.stringify(built.config, null, 2) + "\n"
|
|
11097
10674
|
);
|
|
11098
10675
|
r.done(
|
|
11099
|
-
{ program: outDir, report:
|
|
11100
|
-
`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>`
|
|
11101
10678
|
);
|
|
11102
10679
|
return EXIT_OK;
|
|
11103
10680
|
}
|
|
@@ -11108,11 +10685,11 @@ function scaffoldSourceDir(inv) {
|
|
|
11108
10685
|
|
|
11109
10686
|
// src/plugin/recipe.ts
|
|
11110
10687
|
import { readFileSync as readFileSync8 } from "fs";
|
|
11111
|
-
import { basename as
|
|
10688
|
+
import { basename as basename7 } from "path";
|
|
11112
10689
|
var BOOLEAN_FLAGS9 = /* @__PURE__ */ new Set(["json", "help"]);
|
|
11113
10690
|
var USAGE = "vos recipe push <file.md> (--folder <folderId|slug> | --asset <assetId>)";
|
|
11114
10691
|
function recipeFilename(file) {
|
|
11115
|
-
const name =
|
|
10692
|
+
const name = basename7(file);
|
|
11116
10693
|
if (!/\.md$/i.test(name)) {
|
|
11117
10694
|
throw new UsageError(`a recipe is a markdown file \u2014 ${name} is not .md`);
|
|
11118
10695
|
}
|
|
@@ -11199,7 +10776,7 @@ async function cmdRecipe(argv) {
|
|
|
11199
10776
|
}
|
|
11200
10777
|
|
|
11201
10778
|
// src/plugin/brand.ts
|
|
11202
|
-
import { writeFile as
|
|
10779
|
+
import { writeFile as writeFile10 } from "fs/promises";
|
|
11203
10780
|
import { resolve as resolve12 } from "path";
|
|
11204
10781
|
import { parseFrontmatter as parseFrontmatter2 } from "@vosjs/shared/frontmatter";
|
|
11205
10782
|
import { lookKindForGround } from "@vosjs/studio-core";
|
|
@@ -11553,7 +11130,7 @@ async function cmdBrand(argv) {
|
|
|
11553
11130
|
composition,
|
|
11554
11131
|
llms?.claim ?? design?.description ?? null
|
|
11555
11132
|
);
|
|
11556
|
-
await
|
|
11133
|
+
await writeFile10(out, md);
|
|
11557
11134
|
const k = composition.kit;
|
|
11558
11135
|
r.done(
|
|
11559
11136
|
{
|
|
@@ -11668,7 +11245,7 @@ async function recordHosted(input) {
|
|
|
11668
11245
|
}
|
|
11669
11246
|
|
|
11670
11247
|
// src/plugin/agentBrowser.ts
|
|
11671
|
-
import { writeFile as
|
|
11248
|
+
import { writeFile as writeFile11 } from "fs/promises";
|
|
11672
11249
|
import { resolve as resolve13 } from "path";
|
|
11673
11250
|
function parseAgentBrowserLog(text) {
|
|
11674
11251
|
const records = [];
|
|
@@ -12148,8 +11725,8 @@ async function cmdActions(argv) {
|
|
|
12148
11725
|
if (!m) throw new UsageError("--viewport expects WxH, e.g. 1280x720");
|
|
12149
11726
|
viewport = { width: Number(m[1]), height: Number(m[2]) };
|
|
12150
11727
|
}
|
|
12151
|
-
const { readFile:
|
|
12152
|
-
const text = await
|
|
11728
|
+
const { readFile: readFile16 } = await import("fs/promises");
|
|
11729
|
+
const text = await readFile16(resolve13(input), "utf8");
|
|
12153
11730
|
const { records, problems } = parseAgentBrowserLog(text);
|
|
12154
11731
|
for (const p of problems) r.log(` ${p}`);
|
|
12155
11732
|
const result = convertAgentBrowser(records, {
|
|
@@ -12166,7 +11743,7 @@ async function cmdActions(argv) {
|
|
|
12166
11743
|
);
|
|
12167
11744
|
return EXIT_ERROR;
|
|
12168
11745
|
}
|
|
12169
|
-
await
|
|
11746
|
+
await writeFile11(out, `${JSON.stringify(result.actions, null, 2)}
|
|
12170
11747
|
`);
|
|
12171
11748
|
r.done(
|
|
12172
11749
|
{ ok: true, out, url: result.actions.url ?? null, ...summary2(result) },
|
|
@@ -12186,9 +11763,9 @@ function summary2(result) {
|
|
|
12186
11763
|
}
|
|
12187
11764
|
async function cmdActionsScript(input, flags) {
|
|
12188
11765
|
const r = createReporter(flags.json === true);
|
|
12189
|
-
const { readFile:
|
|
11766
|
+
const { readFile: readFile16 } = await import("fs/promises");
|
|
12190
11767
|
const { shotList, formatShotList } = await import("./shotList-KH6FTX4L.js");
|
|
12191
|
-
const raw = JSON.parse(await
|
|
11768
|
+
const raw = JSON.parse(await readFile16(resolve13(input), "utf8"));
|
|
12192
11769
|
const errors = validateActions(raw);
|
|
12193
11770
|
if (errors.length) throw new UsageError(`${input}:
|
|
12194
11771
|
${errors.join("\n ")}`);
|
|
@@ -12198,9 +11775,9 @@ async function cmdActionsScript(input, flags) {
|
|
|
12198
11775
|
}
|
|
12199
11776
|
|
|
12200
11777
|
// src/plugin/ingest.ts
|
|
12201
|
-
import { existsSync as
|
|
12202
|
-
import { copyFile, mkdir as
|
|
12203
|
-
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";
|
|
12204
11781
|
import { inflateRawSync } from "zlib";
|
|
12205
11782
|
import {
|
|
12206
11783
|
cursorFromPlaywrightTrace,
|
|
@@ -12281,14 +11858,14 @@ async function copyRecording(src, dest, ext) {
|
|
|
12281
11858
|
}
|
|
12282
11859
|
}
|
|
12283
11860
|
async function readTraceFile(path, offsetMs) {
|
|
12284
|
-
const kind = traceKindOf(
|
|
11861
|
+
const kind = traceKindOf(basename8(path));
|
|
12285
11862
|
if (!kind) {
|
|
12286
11863
|
throw new UsageError(
|
|
12287
11864
|
`${path}: not a trace the recorder reads (trace.zip, .trace, .jsonl, .json or .csv)`
|
|
12288
11865
|
);
|
|
12289
11866
|
}
|
|
12290
11867
|
if (kind === "zip") {
|
|
12291
|
-
const bytes = new Uint8Array(await
|
|
11868
|
+
const bytes = new Uint8Array(await readFile14(path));
|
|
12292
11869
|
const entry = playwrightTraceEntry(zipEntries(bytes));
|
|
12293
11870
|
if (!entry) {
|
|
12294
11871
|
throw new UsageError(
|
|
@@ -12301,8 +11878,8 @@ async function readTraceFile(path, offsetMs) {
|
|
|
12301
11878
|
);
|
|
12302
11879
|
return cursorFromPlaywrightTrace(text2, { offsetMs });
|
|
12303
11880
|
}
|
|
12304
|
-
const text = await
|
|
12305
|
-
return cursorFromTrace(
|
|
11881
|
+
const text = await readFile14(path, "utf8");
|
|
11882
|
+
return cursorFromTrace(basename8(path), text, { offsetMs });
|
|
12306
11883
|
}
|
|
12307
11884
|
function parseViewport(v) {
|
|
12308
11885
|
if (!v) return null;
|
|
@@ -12318,7 +11895,7 @@ async function cmdIngest(argv) {
|
|
|
12318
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]"
|
|
12319
11896
|
);
|
|
12320
11897
|
const videoPath = resolve14(source);
|
|
12321
|
-
if (!
|
|
11898
|
+
if (!existsSync20(videoPath)) throw new UsageError(`${source}: no such file`);
|
|
12322
11899
|
const r = createReporter(flags.json === true);
|
|
12323
11900
|
const outDir = resolve14(strFlag(flags, "out") ?? "take");
|
|
12324
11901
|
const offsetMs = numFlag(flags, "offset", 0);
|
|
@@ -12333,7 +11910,7 @@ async function cmdIngest(argv) {
|
|
|
12333
11910
|
"note: finished footage opens bare, so --background is not used (pass --as take for the screen-recording card and its ground)"
|
|
12334
11911
|
);
|
|
12335
11912
|
const backdrop = footage ? null : await takeBackdrop(flags, r);
|
|
12336
|
-
r.log(`probing ${
|
|
11913
|
+
r.log(`probing ${basename8(videoPath)}\u2026`);
|
|
12337
11914
|
const probed = await probeVideoFile(videoPath);
|
|
12338
11915
|
if (!(probed.durationMs > 0)) {
|
|
12339
11916
|
throw new UsageError(
|
|
@@ -12345,15 +11922,15 @@ async function cmdIngest(argv) {
|
|
|
12345
11922
|
trace = await readTraceFile(resolve14(cursorPath), offsetMs);
|
|
12346
11923
|
for (const n of trace.notes) r.log(`note: ${n}`);
|
|
12347
11924
|
}
|
|
12348
|
-
if (
|
|
11925
|
+
if (existsSync20(join23(outDir, "meta.json"))) {
|
|
12349
11926
|
const { prevDoc, kept } = await prepareReRecord(outDir);
|
|
12350
11927
|
r.log(
|
|
12351
11928
|
`note: re-ingesting into ${outDir}; kept ${kept.length ? kept.join(", ") : "nothing"}` + (prevDoc ? `; apply the previous cut with: vos plan ${outDir} --reuse` : "")
|
|
12352
11929
|
);
|
|
12353
11930
|
}
|
|
12354
|
-
await
|
|
11931
|
+
await mkdir8(outDir, { recursive: true });
|
|
12355
11932
|
const paths = await ensureTakeDir(outDir);
|
|
12356
|
-
const recording =
|
|
11933
|
+
const recording = join23(outDir, `recording.${probed.ext}`);
|
|
12357
11934
|
r.log("copying the recording\u2026");
|
|
12358
11935
|
const remuxed = await copyRecording(videoPath, recording, probed.ext);
|
|
12359
11936
|
const viewport = viewportFlag ?? trace?.viewport ?? {
|
|
@@ -12361,7 +11938,7 @@ async function cmdIngest(argv) {
|
|
|
12361
11938
|
height: probed.height
|
|
12362
11939
|
};
|
|
12363
11940
|
const events = trace?.events ?? [];
|
|
12364
|
-
const fileStat = await
|
|
11941
|
+
const fileStat = await stat3(videoPath);
|
|
12365
11942
|
const meta = {
|
|
12366
11943
|
dpr: 1,
|
|
12367
11944
|
zoom: 1,
|
|
@@ -12382,13 +11959,13 @@ async function cmdIngest(argv) {
|
|
|
12382
11959
|
const clicks = events.filter((e) => e.type === "down").length;
|
|
12383
11960
|
const seconds = (probed.durationMs / 1e3).toFixed(1);
|
|
12384
11961
|
const facts = `${seconds}s, ${probed.width}\xD7${probed.height} @ ${meta.fps} fps${probed.hasAudio ? ", audio" : ", no audio"}${remuxed ? "" : ", copied as is"}`;
|
|
12385
|
-
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";
|
|
12386
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)`;
|
|
12387
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`;
|
|
12388
11965
|
r.done(
|
|
12389
11966
|
{
|
|
12390
11967
|
out: outDir,
|
|
12391
|
-
recording:
|
|
11968
|
+
recording: basename8(recording),
|
|
12392
11969
|
durationMs: probed.durationMs,
|
|
12393
11970
|
width: probed.width,
|
|
12394
11971
|
height: probed.height,
|
|
@@ -12397,7 +11974,7 @@ async function cmdIngest(argv) {
|
|
|
12397
11974
|
hasAudio: probed.hasAudio,
|
|
12398
11975
|
remuxed,
|
|
12399
11976
|
cursor: trace ? {
|
|
12400
|
-
file:
|
|
11977
|
+
file: basename8(cursorPath),
|
|
12401
11978
|
kind: trace.kind,
|
|
12402
11979
|
events: events.length,
|
|
12403
11980
|
clicks,
|
|
@@ -12409,7 +11986,7 @@ async function cmdIngest(argv) {
|
|
|
12409
11986
|
backdrop: plan.backdrop ?? null,
|
|
12410
11987
|
as: footage ? "footage" : "take"
|
|
12411
11988
|
},
|
|
12412
|
-
`Ingested ${
|
|
11989
|
+
`Ingested ${basename8(videoPath)} \u2192 ${outDir}/${basename8(recording)} (${facts})
|
|
12413
11990
|
${opened}
|
|
12414
11991
|
${cursorLine}
|
|
12415
11992
|
${next}`
|
|
@@ -12421,19 +11998,19 @@ async function cmdIngest(argv) {
|
|
|
12421
11998
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
12422
11999
|
import {
|
|
12423
12000
|
cpSync,
|
|
12424
|
-
existsSync as
|
|
12001
|
+
existsSync as existsSync22,
|
|
12425
12002
|
mkdirSync as mkdirSync6,
|
|
12426
12003
|
readFileSync as readFileSync10,
|
|
12427
12004
|
rmSync as rmSync3,
|
|
12428
12005
|
writeFileSync as writeFileSync6
|
|
12429
12006
|
} from "fs";
|
|
12430
12007
|
import { homedir as homedir2 } from "os";
|
|
12431
|
-
import { join as
|
|
12008
|
+
import { join as join25 } from "path";
|
|
12432
12009
|
import { chromium as chromium2 } from "playwright";
|
|
12433
12010
|
|
|
12434
12011
|
// src/plugin/machine.ts
|
|
12435
|
-
import { existsSync as
|
|
12436
|
-
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";
|
|
12437
12014
|
var AGENT_NAMES = [
|
|
12438
12015
|
"claude",
|
|
12439
12016
|
"cursor",
|
|
@@ -12453,17 +12030,17 @@ var HOME_DIRS = {
|
|
|
12453
12030
|
copilot: [".copilot"]
|
|
12454
12031
|
};
|
|
12455
12032
|
function detectAgents(root, opts = {}) {
|
|
12456
|
-
const exists = opts.exists ??
|
|
12033
|
+
const exists = opts.exists ?? existsSync21;
|
|
12457
12034
|
const out = [];
|
|
12458
12035
|
for (const agent of AGENT_NAMES) {
|
|
12459
12036
|
for (const rel of PROJECT_DIRS[agent]) {
|
|
12460
|
-
const dir =
|
|
12037
|
+
const dir = join24(root, rel);
|
|
12461
12038
|
if (exists(dir)) out.push({ agent, dir, scope: "project" });
|
|
12462
12039
|
}
|
|
12463
|
-
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"))) {
|
|
12464
12041
|
out.push({
|
|
12465
12042
|
agent,
|
|
12466
|
-
dir:
|
|
12043
|
+
dir: join24(root, ".github", "copilot"),
|
|
12467
12044
|
scope: "project"
|
|
12468
12045
|
});
|
|
12469
12046
|
}
|
|
@@ -12471,7 +12048,7 @@ function detectAgents(root, opts = {}) {
|
|
|
12471
12048
|
if (opts.global && opts.home) {
|
|
12472
12049
|
for (const agent of AGENT_NAMES) {
|
|
12473
12050
|
for (const rel of HOME_DIRS[agent]) {
|
|
12474
|
-
const dir =
|
|
12051
|
+
const dir = join24(opts.home, rel);
|
|
12475
12052
|
if (exists(dir)) out.push({ agent, dir, scope: "global" });
|
|
12476
12053
|
}
|
|
12477
12054
|
}
|
|
@@ -12489,26 +12066,26 @@ function agentsFromFlag(value, root) {
|
|
|
12489
12066
|
return {
|
|
12490
12067
|
homes: wanted.map((agent) => ({
|
|
12491
12068
|
agent,
|
|
12492
|
-
dir:
|
|
12069
|
+
dir: join24(root, PROJECT_DIRS[agent][0]),
|
|
12493
12070
|
scope: "project"
|
|
12494
12071
|
}))
|
|
12495
12072
|
};
|
|
12496
12073
|
}
|
|
12497
12074
|
function skillsDirFor(home) {
|
|
12498
|
-
return
|
|
12075
|
+
return join24(home.dir, "skills");
|
|
12499
12076
|
}
|
|
12500
12077
|
function fallbackSkillsHome(root) {
|
|
12501
|
-
return { agent: "codex", dir:
|
|
12078
|
+
return { agent: "codex", dir: join24(root, ".agents"), scope: "project" };
|
|
12502
12079
|
}
|
|
12503
|
-
function installedSkills(dir, exists =
|
|
12080
|
+
function installedSkills(dir, exists = existsSync21, list = (p) => {
|
|
12504
12081
|
try {
|
|
12505
|
-
return
|
|
12082
|
+
return readdirSync5(p);
|
|
12506
12083
|
} catch {
|
|
12507
12084
|
return [];
|
|
12508
12085
|
}
|
|
12509
12086
|
}) {
|
|
12510
12087
|
if (!exists(dir)) return [];
|
|
12511
|
-
return list(dir).filter((name) => exists(
|
|
12088
|
+
return list(dir).filter((name) => exists(join24(dir, name, "SKILL.md"))).sort();
|
|
12512
12089
|
}
|
|
12513
12090
|
var RULES_BEGIN = "<!-- vos:begin (written by vos setup; the block is replaced on the next run) -->";
|
|
12514
12091
|
var RULES_END = "<!-- vos:end -->";
|
|
@@ -12541,10 +12118,10 @@ ${RULES_BLOCK}
|
|
|
12541
12118
|
}
|
|
12542
12119
|
return { text, changed: text !== current };
|
|
12543
12120
|
}
|
|
12544
|
-
function rulesFileFor(root, exists =
|
|
12545
|
-
const agents =
|
|
12121
|
+
function rulesFileFor(root, exists = existsSync21) {
|
|
12122
|
+
const agents = join24(root, "AGENTS.md");
|
|
12546
12123
|
if (exists(agents)) return agents;
|
|
12547
|
-
const claude =
|
|
12124
|
+
const claude = join24(root, "CLAUDE.md");
|
|
12548
12125
|
if (exists(claude)) return claude;
|
|
12549
12126
|
return agents;
|
|
12550
12127
|
}
|
|
@@ -12553,14 +12130,14 @@ function chromeCandidates(platform, env) {
|
|
|
12553
12130
|
return ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"];
|
|
12554
12131
|
if (platform === "win32")
|
|
12555
12132
|
return [
|
|
12556
|
-
|
|
12133
|
+
join24(
|
|
12557
12134
|
env["PROGRAMFILES"] ?? "C:\\Program Files",
|
|
12558
12135
|
"Google",
|
|
12559
12136
|
"Chrome",
|
|
12560
12137
|
"Application",
|
|
12561
12138
|
"chrome.exe"
|
|
12562
12139
|
),
|
|
12563
|
-
|
|
12140
|
+
join24(
|
|
12564
12141
|
env["LOCALAPPDATA"] ?? "",
|
|
12565
12142
|
"Google",
|
|
12566
12143
|
"Chrome",
|
|
@@ -12647,13 +12224,13 @@ function doctorReport(facts) {
|
|
|
12647
12224
|
function bundledSkillsDir(from) {
|
|
12648
12225
|
let dir = dirname9(from);
|
|
12649
12226
|
for (let i = 0; i < 6; i++) {
|
|
12650
|
-
const pkg =
|
|
12651
|
-
if (
|
|
12227
|
+
const pkg = join24(dir, "package.json");
|
|
12228
|
+
if (existsSync21(pkg)) {
|
|
12652
12229
|
try {
|
|
12653
12230
|
const name = JSON.parse(readFileSync9(pkg, "utf8")).name;
|
|
12654
12231
|
if (name === "@vosjs/cli") {
|
|
12655
|
-
const skills =
|
|
12656
|
-
return
|
|
12232
|
+
const skills = join24(dir, "skills");
|
|
12233
|
+
return existsSync21(skills) && statSync7(skills).isDirectory() ? skills : null;
|
|
12657
12234
|
}
|
|
12658
12235
|
} catch {
|
|
12659
12236
|
}
|
|
@@ -12684,7 +12261,7 @@ function npx() {
|
|
|
12684
12261
|
function bundledChromium() {
|
|
12685
12262
|
try {
|
|
12686
12263
|
const p = chromium2.executablePath();
|
|
12687
|
-
return p &&
|
|
12264
|
+
return p && existsSync22(p) ? p : null;
|
|
12688
12265
|
} catch {
|
|
12689
12266
|
return null;
|
|
12690
12267
|
}
|
|
@@ -12693,7 +12270,7 @@ function probe2() {
|
|
|
12693
12270
|
return probeBrowser({
|
|
12694
12271
|
platform: process.platform,
|
|
12695
12272
|
env: process.env,
|
|
12696
|
-
exists:
|
|
12273
|
+
exists: existsSync22,
|
|
12697
12274
|
bundled: bundledChromium
|
|
12698
12275
|
});
|
|
12699
12276
|
}
|
|
@@ -12797,7 +12374,7 @@ async function cmdSetup(argv) {
|
|
|
12797
12374
|
const dest = skillsDirFor(home);
|
|
12798
12375
|
mkdirSync6(dest, { recursive: true });
|
|
12799
12376
|
for (const name of installedSkills(bundled)) {
|
|
12800
|
-
cpSync(
|
|
12377
|
+
cpSync(join25(bundled, name), join25(dest, name), { recursive: true });
|
|
12801
12378
|
}
|
|
12802
12379
|
}
|
|
12803
12380
|
skillsVia = "bundled";
|
|
@@ -12832,7 +12409,7 @@ async function cmdSetup(argv) {
|
|
|
12832
12409
|
let rules = null;
|
|
12833
12410
|
if (!noRules) {
|
|
12834
12411
|
const file = rulesFileFor(root);
|
|
12835
|
-
const existing =
|
|
12412
|
+
const existing = existsSync22(file) ? readFileSync10(file, "utf8") : null;
|
|
12836
12413
|
const { text, changed } = applyRulesBlock(existing);
|
|
12837
12414
|
if (changed) writeFileSync6(file, text);
|
|
12838
12415
|
rules = { file, changed };
|
|
@@ -12946,7 +12523,7 @@ async function cmdWhoami(argv) {
|
|
|
12946
12523
|
function cmdLogout(argv) {
|
|
12947
12524
|
const { flags } = parseArgs(argv, BOOLEAN_FLAGS11);
|
|
12948
12525
|
const r = reporterFor(flags);
|
|
12949
|
-
const had =
|
|
12526
|
+
const had = existsSync22(CREDENTIALS_PATH);
|
|
12950
12527
|
rmSync3(CREDENTIALS_PATH, { force: true });
|
|
12951
12528
|
const envKey = Boolean(process.env.VOS_API_KEY?.trim());
|
|
12952
12529
|
r.done(
|
|
@@ -13300,7 +12877,7 @@ lint-gated, so a bad override fails like a bad doc.json):
|
|
|
13300
12877
|
first ready loop (the house backdrop the studio opens on), or a flat ground offline
|
|
13301
12878
|
`;
|
|
13302
12879
|
async function loadActions(file) {
|
|
13303
|
-
const raw = JSON.parse(await
|
|
12880
|
+
const raw = JSON.parse(await readFile15(file, "utf8"));
|
|
13304
12881
|
const errors = validateActions(raw);
|
|
13305
12882
|
if (errors.length)
|
|
13306
12883
|
throw new UsageError(`invalid actions file:
|
|
@@ -13363,7 +12940,7 @@ function takeStorageState(flags) {
|
|
|
13363
12940
|
const raw = strFlag(flags, "storage-state");
|
|
13364
12941
|
if (!raw) return void 0;
|
|
13365
12942
|
const path = resolve15(raw);
|
|
13366
|
-
if (!
|
|
12943
|
+
if (!existsSync23(path))
|
|
13367
12944
|
throw new UsageError(`--storage-state: no such file: ${path}`);
|
|
13368
12945
|
return path;
|
|
13369
12946
|
}
|
|
@@ -13376,7 +12953,7 @@ function takeSessionName(flags, storageState) {
|
|
|
13376
12953
|
throw new UsageError(
|
|
13377
12954
|
"--session and --storage-state are two doors to one take: a persistent profile cannot take a storage state. Pass one"
|
|
13378
12955
|
);
|
|
13379
|
-
if (!
|
|
12956
|
+
if (!existsSync23(sessionDir(name)))
|
|
13380
12957
|
throw new UsageError(
|
|
13381
12958
|
`no session "${name}". Make one: vos session open <url> --name ${name} (a Chrome window opens; sign in and close it)`
|
|
13382
12959
|
);
|
|
@@ -13568,7 +13145,7 @@ async function cmdRecord(argv) {
|
|
|
13568
13145
|
const what = s.selector ? ` ${s.selector}` : "";
|
|
13569
13146
|
return ` ${s.skipped ? "\u2717" : "\u2713"} #${s.step}${s.id ? ` (${s.id})` : ""} ${s.do}${what}${s.skipped ? " NOT FOUND" : rect}${s.navigated ? " \u2192 navigated" : ""}`;
|
|
13570
13147
|
});
|
|
13571
|
-
const
|
|
13148
|
+
const failed = rec.skipped.length > 0 || rec.navTimeout || maskMisses(rec).length > 0;
|
|
13572
13149
|
const carried = [
|
|
13573
13150
|
...strFlag(flags, "storage-state") ? [`--storage-state ${strFlag(flags, "storage-state")}`] : [],
|
|
13574
13151
|
...browserArgs.map((a) => `--browser-arg=${a}`),
|
|
@@ -13580,7 +13157,7 @@ async function cmdRecord(argv) {
|
|
|
13580
13157
|
r.done(
|
|
13581
13158
|
{
|
|
13582
13159
|
dryRun: true,
|
|
13583
|
-
ok: !
|
|
13160
|
+
ok: !failed,
|
|
13584
13161
|
seconds: +((Date.now() - started) / 1e3).toFixed(1),
|
|
13585
13162
|
steps,
|
|
13586
13163
|
skipped: rec.skipped,
|
|
@@ -13588,24 +13165,24 @@ async function cmdRecord(argv) {
|
|
|
13588
13165
|
exposures: rec.exposures,
|
|
13589
13166
|
masks: rec.masks
|
|
13590
13167
|
},
|
|
13591
|
-
`${
|
|
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)" : ""}
|
|
13592
13169
|
${lines.join("\n")}
|
|
13593
|
-
${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)}`
|
|
13594
13171
|
);
|
|
13595
|
-
return
|
|
13172
|
+
return failed ? EXIT_USAGE : EXIT_OK;
|
|
13596
13173
|
} finally {
|
|
13597
13174
|
await browser2.close();
|
|
13598
13175
|
}
|
|
13599
13176
|
}
|
|
13600
13177
|
const paths = takePaths(outDir);
|
|
13601
13178
|
const prepare = async () => {
|
|
13602
|
-
if (
|
|
13179
|
+
if (existsSync23(join26(outDir, "meta.json"))) {
|
|
13603
13180
|
const { prevDoc, kept } = await prepareReRecord(outDir);
|
|
13604
13181
|
r.log(
|
|
13605
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" : "")
|
|
13606
13183
|
);
|
|
13607
13184
|
}
|
|
13608
|
-
await
|
|
13185
|
+
await mkdir9(outDir, { recursive: true });
|
|
13609
13186
|
await ensureTakeDir(outDir);
|
|
13610
13187
|
};
|
|
13611
13188
|
const browser = await launchBrowser(browserArgs);
|
|
@@ -13664,7 +13241,7 @@ ${lines.join("\n")}
|
|
|
13664
13241
|
},
|
|
13665
13242
|
strictFail ? `STRICT: take recorded but incomplete \u2014 ${strictReason(rec)}; fix the flow and re-record.${skippedNote}${exposureNote(rec)}` : `Take ready: ${outDir}
|
|
13666
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}
|
|
13667
|
-
Next: edit ${
|
|
13244
|
+
Next: edit ${join26(outDir, "doc.json")} (optional), then: vos render ${outDir}${exposureNote(rec)}`
|
|
13668
13245
|
);
|
|
13669
13246
|
return strictFail ? EXIT_USAGE : EXIT_OK;
|
|
13670
13247
|
} finally {
|
|
@@ -13690,7 +13267,7 @@ async function cmdCreate2(argv) {
|
|
|
13690
13267
|
throw new UsageError('no URL \u2014 set "url" in the actions file or pass --url');
|
|
13691
13268
|
const outDir = resolve15(strFlag(flags, "out") ?? "take");
|
|
13692
13269
|
const format = videoFormat(strFlag(flags, "format"), positionals[0]);
|
|
13693
|
-
const out = positionals[0] ??
|
|
13270
|
+
const out = positionals[0] ?? join26(outDir, `out.${format}`);
|
|
13694
13271
|
const parallel = numFlag(flags, "parallel", 1);
|
|
13695
13272
|
if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
|
|
13696
13273
|
throw new UsageError("--parallel expects an integer between 1 and 16");
|
|
@@ -13703,13 +13280,13 @@ async function cmdCreate2(argv) {
|
|
|
13703
13280
|
const headers = takeHeaders(multi);
|
|
13704
13281
|
const paths = takePaths(outDir);
|
|
13705
13282
|
const prepare = async () => {
|
|
13706
|
-
if (
|
|
13283
|
+
if (existsSync23(join26(outDir, "meta.json"))) {
|
|
13707
13284
|
const { prevDoc, kept } = await prepareReRecord(outDir);
|
|
13708
13285
|
r.log(
|
|
13709
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" : "")
|
|
13710
13287
|
);
|
|
13711
13288
|
}
|
|
13712
|
-
await
|
|
13289
|
+
await mkdir9(outDir, { recursive: true });
|
|
13713
13290
|
await ensureTakeDir(outDir);
|
|
13714
13291
|
};
|
|
13715
13292
|
const browser = await launchBrowser(browserArgs);
|
|
@@ -13810,24 +13387,24 @@ async function cmdPlan(argv) {
|
|
|
13810
13387
|
);
|
|
13811
13388
|
const r = createReporter(flags.json === true);
|
|
13812
13389
|
if (flags.fresh === true) {
|
|
13813
|
-
await
|
|
13390
|
+
await rm3(join26(dir, "doc.json"), { force: true });
|
|
13814
13391
|
r.log("note: --fresh discarded the existing doc.json");
|
|
13815
13392
|
}
|
|
13816
13393
|
let reuse;
|
|
13817
13394
|
if (flags.reuse === true) {
|
|
13818
|
-
const from = resolve15(strFlag(flags, "from") ??
|
|
13819
|
-
if (!
|
|
13395
|
+
const from = resolve15(strFlag(flags, "from") ?? join26(dir, PREV_DOC_NAME));
|
|
13396
|
+
if (!existsSync23(from)) {
|
|
13820
13397
|
throw new UsageError(
|
|
13821
13398
|
`--reuse: ${from} does not exist \u2014 a re-record into this take writes doc.prev.json, or pass --from <doc.json>`
|
|
13822
13399
|
);
|
|
13823
13400
|
}
|
|
13824
13401
|
reuse = {
|
|
13825
13402
|
from,
|
|
13826
|
-
doc: JSON.parse(await
|
|
13403
|
+
doc: JSON.parse(await readFile15(from, "utf8"))
|
|
13827
13404
|
};
|
|
13828
13405
|
}
|
|
13829
13406
|
const style = await resolveStyleRef(flags);
|
|
13830
|
-
const hasDoc =
|
|
13407
|
+
const hasDoc = existsSync23(join26(dir, "doc.json"));
|
|
13831
13408
|
const backdrop = hasDoc ? null : await takeBackdrop(flags, r);
|
|
13832
13409
|
const release = await releaseInputs(dir, flags, r, multi.with ?? []);
|
|
13833
13410
|
const motionWanted = !hasDoc || flags.motion === true || flags.fresh === true;
|
|
@@ -13885,7 +13462,7 @@ async function cmdPlan(argv) {
|
|
|
13885
13462
|
...s.motion ? { motion: s.motion } : {},
|
|
13886
13463
|
...s.reuse ? { reuse: s.reuse } : {}
|
|
13887
13464
|
},
|
|
13888
|
-
`${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}`
|
|
13889
13466
|
);
|
|
13890
13467
|
return EXIT_OK;
|
|
13891
13468
|
}
|
|
@@ -14008,7 +13585,7 @@ async function cmdRender(argv) {
|
|
|
14008
13585
|
);
|
|
14009
13586
|
const r = createReporter(flags.json === true);
|
|
14010
13587
|
const format = videoFormat(strFlag(flags, "format"), positionals[1]);
|
|
14011
|
-
const out = positionals[1] ??
|
|
13588
|
+
const out = positionals[1] ?? join26(dir, `out.${format}`);
|
|
14012
13589
|
const parallel = numFlag(flags, "parallel", 1);
|
|
14013
13590
|
if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
|
|
14014
13591
|
throw new UsageError("--parallel expects an integer between 1 and 16");
|
|
@@ -14255,15 +13832,15 @@ async function resolveStyleRef(flags) {
|
|
|
14255
13832
|
}
|
|
14256
13833
|
async function resolveDocRef(ref, flags, what) {
|
|
14257
13834
|
const styleRef = ref;
|
|
14258
|
-
const file =
|
|
13835
|
+
const file = existsSync23(styleRef) ? resolve15(
|
|
14259
13836
|
styleRef,
|
|
14260
|
-
|
|
13837
|
+
existsSync23(join26(styleRef, "doc.json")) ? "doc.json" : ""
|
|
14261
13838
|
) : null;
|
|
14262
|
-
if (file &&
|
|
13839
|
+
if (file && existsSync23(file)) {
|
|
14263
13840
|
return {
|
|
14264
13841
|
from: file,
|
|
14265
13842
|
doc: migrateHostedDoc5(
|
|
14266
|
-
JSON.parse(await
|
|
13843
|
+
JSON.parse(await readFile15(file, "utf8"))
|
|
14267
13844
|
)
|
|
14268
13845
|
};
|
|
14269
13846
|
}
|
|
@@ -14315,7 +13892,7 @@ async function cmdDigest(argv) {
|
|
|
14315
13892
|
const take = await loadTake(dir);
|
|
14316
13893
|
if (!take.doc) throw new UsageError(`${dir} has no doc.json \u2014 run plan first`);
|
|
14317
13894
|
const transcriptPath = strFlag(flags, "transcript");
|
|
14318
|
-
const transcript = transcriptPath ? parseTranscript(JSON.parse(await
|
|
13895
|
+
const transcript = transcriptPath ? parseTranscript(JSON.parse(await readFile15(transcriptPath, "utf8"))) : null;
|
|
14319
13896
|
const style = await resolveStyleRef(flags);
|
|
14320
13897
|
const noFrames = flags["no-frames"] === true;
|
|
14321
13898
|
let browser = null;
|
|
@@ -14346,7 +13923,7 @@ async function cmdDigest(argv) {
|
|
|
14346
13923
|
kinds,
|
|
14347
13924
|
frames: d.images.full,
|
|
14348
13925
|
crops: d.images.crop,
|
|
14349
|
-
sheet: d.images.sheet ?
|
|
13926
|
+
sheet: d.images.sheet ? join26(result.outDir, d.images.sheet) : null,
|
|
14350
13927
|
scenes: kinds.scene ?? 0,
|
|
14351
13928
|
sourceDuration: d.take.sourceDuration,
|
|
14352
13929
|
outputDuration: d.take.outputDuration,
|
|
@@ -14374,7 +13951,7 @@ async function cmdOpen(argv) {
|
|
|
14374
13951
|
if (!dir) throw new UsageError("vos open <take> [--studio <url>]");
|
|
14375
13952
|
const r = createReporter(flags.json === true);
|
|
14376
13953
|
const take = await loadTake(dir);
|
|
14377
|
-
if (!
|
|
13954
|
+
if (!existsSync23(take.paths.recording)) {
|
|
14378
13955
|
throw new UsageError(`${dir} has no recording \u2014 re-run record`);
|
|
14379
13956
|
}
|
|
14380
13957
|
const studio = (strFlag(flags, "studio") ?? platformOrigin()).replace(
|
|
@@ -14415,12 +13992,12 @@ async function cmdJudge(argv) {
|
|
|
14415
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)"
|
|
14416
13993
|
);
|
|
14417
13994
|
const r = createReporter(flags.json === true);
|
|
14418
|
-
const kitFile = target.endsWith("kit.json") ? target :
|
|
14419
|
-
if (!
|
|
14420
|
-
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))
|
|
14421
13998
|
throw new UsageError(`${against}: no reference manifest`);
|
|
14422
13999
|
const result = await judgeKit(kitFile, against, strFlag(flags, "out"));
|
|
14423
|
-
const verdicts = JSON.parse(await
|
|
14000
|
+
const verdicts = JSON.parse(await readFile15(result.verdictFile, "utf8")).verdicts;
|
|
14424
14001
|
const rate = winRate(verdicts);
|
|
14425
14002
|
const lines = result.sheets.map(
|
|
14426
14003
|
(s) => `${s.asset} vs ${s.reference}: ${s.sheetA}, ${s.sheetB}, ${s.rubric}`
|
|
@@ -14460,7 +14037,7 @@ async function cmdCallout(argv) {
|
|
|
14460
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`
|
|
14461
14038
|
);
|
|
14462
14039
|
const codeFile = strFlag(flags, "code-file");
|
|
14463
|
-
const code = codeFile ? await
|
|
14040
|
+
const code = codeFile ? await readFile15(codeFile, "utf8") : strFlag(flags, "code");
|
|
14464
14041
|
const words2 = {
|
|
14465
14042
|
kicker: strFlag(flags, "kicker"),
|
|
14466
14043
|
title: strFlag(flags, "title"),
|
|
@@ -14539,7 +14116,7 @@ async function cmdValidate(argv) {
|
|
|
14539
14116
|
const target = positionals[0];
|
|
14540
14117
|
if (!target) throw new UsageError("vos validate <actions.json|take>");
|
|
14541
14118
|
const r = createReporter(flags.json === true);
|
|
14542
|
-
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;
|
|
14543
14120
|
if (kitTarget) {
|
|
14544
14121
|
const picture = flags.picture === true;
|
|
14545
14122
|
const verdict = await validateKit(kitTarget, { picture });
|
|
@@ -14571,9 +14148,9 @@ async function cmdValidate(argv) {
|
|
|
14571
14148
|
r.done({ valid: true, target }, `${target}: valid actions file`);
|
|
14572
14149
|
return EXIT_OK;
|
|
14573
14150
|
}
|
|
14574
|
-
if (!isTakeDir(target) &&
|
|
14151
|
+
if (!isTakeDir(target) && existsSync23(join26(target, "config.json"))) {
|
|
14575
14152
|
const parsed = JSON.parse(
|
|
14576
|
-
await
|
|
14153
|
+
await readFile15(join26(target, "config.json"), "utf8")
|
|
14577
14154
|
);
|
|
14578
14155
|
const pre = preflightConfig(parsed);
|
|
14579
14156
|
const problems2 = pre.ok ? [] : pre.issues.map((i) => `config.json: ${i}`);
|
|
@@ -14614,7 +14191,7 @@ async function cmdValidate(argv) {
|
|
|
14614
14191
|
const take = await loadTake(target);
|
|
14615
14192
|
const problems = [];
|
|
14616
14193
|
const warnings = [];
|
|
14617
|
-
if (!
|
|
14194
|
+
if (!existsSync23(take.paths.recording)) {
|
|
14618
14195
|
problems.push("missing recording (re-run record)");
|
|
14619
14196
|
} else {
|
|
14620
14197
|
const mismatch = containerMismatch(
|
|
@@ -14966,7 +14543,7 @@ async function cmdSession(argv) {
|
|
|
14966
14543
|
if (!arg) throw new UsageError(usage);
|
|
14967
14544
|
const bad = sessionNameVerdict(arg);
|
|
14968
14545
|
if (bad) throw new UsageError(bad);
|
|
14969
|
-
if (!
|
|
14546
|
+
if (!existsSync23(sessionDir(arg)))
|
|
14970
14547
|
throw new UsageError(`no session "${arg}" (vos session list)`);
|
|
14971
14548
|
const summary3 = await summarizeSession(arg);
|
|
14972
14549
|
const url = strFlag(flags, "url");
|
|
@@ -15047,10 +14624,6 @@ async function cmdSession(argv) {
|
|
|
15047
14624
|
|
|
15048
14625
|
export {
|
|
15049
14626
|
validateActions,
|
|
15050
|
-
RECORDING_NAME,
|
|
15051
|
-
takePaths,
|
|
15052
|
-
loadTake,
|
|
15053
|
-
pullMedia,
|
|
15054
14627
|
startTakeServer,
|
|
15055
14628
|
waitForPageDone,
|
|
15056
14629
|
renderAnimation,
|
|
@@ -15070,4 +14643,4 @@ export {
|
|
|
15070
14643
|
verbHelp,
|
|
15071
14644
|
run
|
|
15072
14645
|
};
|
|
15073
|
-
//# sourceMappingURL=chunk-
|
|
14646
|
+
//# sourceMappingURL=chunk-NE5MHC3D.js.map
|