@vosjs/cli 0.78.0 → 0.80.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 +152 -31
- package/dist/{assetManifest-S6H5F2VE.js → assetManifest-ETT3C7ZA.js} +3 -3
- package/dist/{chunk-JYCW4EOA.js → chunk-B373NVJE.js} +7 -3
- package/dist/chunk-B373NVJE.js.map +1 -0
- package/dist/{chunk-B4U4URE3.js → chunk-J3KHIZCH.js} +466 -412
- package/dist/chunk-J3KHIZCH.js.map +1 -0
- package/dist/{chunk-RUDDBHQ7.js → chunk-OWYOJB7K.js} +5 -1
- package/dist/chunk-OWYOJB7K.js.map +1 -0
- package/dist/{chunk-ZSJEISKL.js → chunk-PJ6MEJZV.js} +213 -28
- package/dist/chunk-PJ6MEJZV.js.map +1 -0
- package/dist/{chunk-INRUYQ6J.js → chunk-SP5OHTVB.js} +4285 -1560
- package/dist/chunk-SP5OHTVB.js.map +1 -0
- package/dist/cli.js +17 -10
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +15 -6
- package/dist/index.js +5 -5
- package/dist/manifest-VTOIEUHJ.js +8 -0
- package/dist/{run-TIFM3ZHI.js → run-2ELHMPTB.js} +5 -5
- package/dist/{shotList-SCA234OS.js → shotList-MSNWLVDN.js} +63 -9
- package/dist/shotList-MSNWLVDN.js.map +1 -0
- package/package.json +3 -3
- package/schema/actions.schema.json +498 -17
- package/schema/doc.schema.json +46 -5
- package/skills/VERSION +1 -1
- package/skills/launch-kit/SKILL.md +12 -3
- package/skills/product-video/SKILL.md +34 -0
- package/skills/product-video/references/taste.md +7 -3
- package/skills/vos-authoring/SKILL.md +8 -1
- package/skills/vos-create/SKILL.md +5 -0
- package/skills/vos-cut/SKILL.md +25 -2
- package/skills/vos-footage/SKILL.md +4 -0
- package/skills/vos-migrate/SKILL.md +4 -0
- package/dist/chunk-B4U4URE3.js.map +0 -1
- package/dist/chunk-INRUYQ6J.js.map +0 -1
- package/dist/chunk-JYCW4EOA.js.map +0 -1
- package/dist/chunk-RUDDBHQ7.js.map +0 -1
- package/dist/chunk-ZSJEISKL.js.map +0 -1
- package/dist/manifest-6DJSPKVH.js +0 -8
- package/dist/shotList-SCA234OS.js.map +0 -1
- /package/dist/{assetManifest-S6H5F2VE.js.map → assetManifest-ETT3C7ZA.js.map} +0 -0
- /package/dist/{manifest-6DJSPKVH.js.map → manifest-VTOIEUHJ.js.map} +0 -0
- /package/dist/{run-TIFM3ZHI.js.map → run-2ELHMPTB.js.map} +0 -0
|
@@ -232,279 +232,9 @@ function containerMismatch(filename, head) {
|
|
|
232
232
|
return `${filename} is ${actual}, not the ${claimed} its name claims \u2014 rename it to ${nameForType(filename, actual)}`;
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
-
// src/programAssets.ts
|
|
236
|
-
import { createHash } from "crypto";
|
|
237
|
-
import {
|
|
238
|
-
closeSync,
|
|
239
|
-
existsSync,
|
|
240
|
-
mkdirSync,
|
|
241
|
-
openSync,
|
|
242
|
-
readSync,
|
|
243
|
-
readdirSync,
|
|
244
|
-
realpathSync,
|
|
245
|
-
renameSync,
|
|
246
|
-
rmSync,
|
|
247
|
-
statSync,
|
|
248
|
-
writeFileSync
|
|
249
|
-
} from "fs";
|
|
250
|
-
import { homedir } from "os";
|
|
251
|
-
import { extname, join, resolve, sep } from "path";
|
|
252
|
-
var ASSET_ROUTE = "/__assets/";
|
|
253
|
-
var UUID = "[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}";
|
|
254
|
-
var HOSTED_URI = new RegExp(`^asset:(${UUID})$`);
|
|
255
|
-
var HOSTED_FILE = new RegExp(
|
|
256
|
-
`^(https?://[^/?#]+)?/api/assets/(${UUID})/file(?:[?#].*)?$`
|
|
257
|
-
);
|
|
258
|
-
function hostedAssetId(ref, homeOrigins = []) {
|
|
259
|
-
const uri = HOSTED_URI.exec(ref);
|
|
260
|
-
if (uri) return uri[1];
|
|
261
|
-
const file = HOSTED_FILE.exec(ref);
|
|
262
|
-
if (!file) return null;
|
|
263
|
-
return file[1] === void 0 || homeOrigins.includes(file[1]) ? file[2] : null;
|
|
264
|
-
}
|
|
265
|
-
var ASSET_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
266
|
-
var SERVED_TYPES = {
|
|
267
|
-
".png": "image/png",
|
|
268
|
-
".jpg": "image/jpeg",
|
|
269
|
-
".jpeg": "image/jpeg",
|
|
270
|
-
".webp": "image/webp",
|
|
271
|
-
".gif": "image/gif",
|
|
272
|
-
".svg": "image/svg+xml",
|
|
273
|
-
".avif": "image/avif",
|
|
274
|
-
".mp4": "video/mp4",
|
|
275
|
-
".webm": "video/webm",
|
|
276
|
-
".mov": "video/quicktime",
|
|
277
|
-
".mp3": "audio/mpeg",
|
|
278
|
-
".ogg": "audio/ogg",
|
|
279
|
-
".wav": "audio/wav",
|
|
280
|
-
".m4a": "audio/mp4",
|
|
281
|
-
".glb": "model/gltf-binary",
|
|
282
|
-
".gltf": "model/gltf+json",
|
|
283
|
-
".woff2": "font/woff2",
|
|
284
|
-
".woff": "font/woff",
|
|
285
|
-
".ttf": "font/ttf",
|
|
286
|
-
".otf": "font/otf",
|
|
287
|
-
".json": "application/json"
|
|
288
|
-
};
|
|
289
|
-
function serveFile(file, range) {
|
|
290
|
-
const size = statSync(file).size;
|
|
291
|
-
const type = SERVED_TYPES[extname(file).toLowerCase()] ?? "application/octet-stream";
|
|
292
|
-
const base = { "content-type": type, "accept-ranges": "bytes" };
|
|
293
|
-
const read = (start2, end2) => {
|
|
294
|
-
const body = Buffer.alloc(Math.max(0, end2 - start2 + 1));
|
|
295
|
-
const fd = openSync(file, "r");
|
|
296
|
-
try {
|
|
297
|
-
readSync(fd, body, 0, body.length, start2);
|
|
298
|
-
} finally {
|
|
299
|
-
closeSync(fd);
|
|
300
|
-
}
|
|
301
|
-
return body;
|
|
302
|
-
};
|
|
303
|
-
const m = range ? /^bytes=(\d*)-(\d*)$/.exec(range.trim()) : null;
|
|
304
|
-
if (!m || m[1] === "" && m[2] === "") {
|
|
305
|
-
return {
|
|
306
|
-
status: 200,
|
|
307
|
-
headers: { ...base, "content-length": String(size) },
|
|
308
|
-
body: read(0, size - 1)
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
const start = m[1] === "" ? Math.max(0, size - Number(m[2])) : Number(m[1]);
|
|
312
|
-
const end = m[1] === "" || m[2] === "" ? size - 1 : Math.min(Number(m[2]), size - 1);
|
|
313
|
-
if (start >= size || start > end) {
|
|
314
|
-
return {
|
|
315
|
-
status: 416,
|
|
316
|
-
headers: { ...base, "content-range": `bytes */${size}` },
|
|
317
|
-
body: Buffer.alloc(0)
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
return {
|
|
321
|
-
status: 206,
|
|
322
|
-
headers: {
|
|
323
|
-
...base,
|
|
324
|
-
"content-range": `bytes ${start}-${end}/${size}`,
|
|
325
|
-
"content-length": String(end - start + 1)
|
|
326
|
-
},
|
|
327
|
-
body: read(start, end)
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
function localFile(ref, baseDir) {
|
|
331
|
-
if (baseDir === null) {
|
|
332
|
-
return {
|
|
333
|
-
refused: "a config loaded from a URL has no directory, so it reads no local file"
|
|
334
|
-
};
|
|
335
|
-
}
|
|
336
|
-
const file = resolve(baseDir, ref);
|
|
337
|
-
if (!existsSync(file) || !statSync(file).isFile()) {
|
|
338
|
-
return { refused: "which is not a file beside the config" };
|
|
339
|
-
}
|
|
340
|
-
const root = realpathSync(baseDir);
|
|
341
|
-
const real = realpathSync(file);
|
|
342
|
-
if (!real.startsWith(root + sep)) {
|
|
343
|
-
return {
|
|
344
|
-
refused: `which is outside the program's directory (${root}). A program reads only files in its own directory or below: copy the file there`
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
return { file: real };
|
|
348
|
-
}
|
|
349
|
-
function isUrl(ref) {
|
|
350
|
-
return /^(https?:|data:|blob:)/.test(ref);
|
|
351
|
-
}
|
|
352
|
-
function manifestOf(config) {
|
|
353
|
-
const assets = config.assets;
|
|
354
|
-
if (!assets || typeof assets !== "object" || Array.isArray(assets))
|
|
355
|
-
return null;
|
|
356
|
-
return Object.keys(assets).length > 0 ? assets : null;
|
|
357
|
-
}
|
|
358
|
-
function manifestRefs(config) {
|
|
359
|
-
const out = [];
|
|
360
|
-
for (const [name, decl] of Object.entries(manifestOf(config) ?? {})) {
|
|
361
|
-
if (!ASSET_NAME.test(name) || !decl || typeof decl !== "object") continue;
|
|
362
|
-
if (Array.isArray(decl.ref)) {
|
|
363
|
-
decl.ref.forEach((ref, index) => {
|
|
364
|
-
if (typeof ref === "string") out.push({ name, index, ref });
|
|
365
|
-
});
|
|
366
|
-
} else if (typeof decl.ref === "string") {
|
|
367
|
-
out.push({ name, index: null, ref: decl.ref });
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
return out;
|
|
371
|
-
}
|
|
372
|
-
async function fetchHosted(id, opts) {
|
|
373
|
-
const dir = opts.cacheDir ?? join(homedir(), ".cache", "vos", "assets");
|
|
374
|
-
const cached = existsSync(dir) ? readdirSync(dir).find(
|
|
375
|
-
(f) => (f === id || f.startsWith(`${id}.`)) && !f.endsWith(".part")
|
|
376
|
-
) : void 0;
|
|
377
|
-
if (cached) return join(dir, cached);
|
|
378
|
-
if (!opts.origin || !opts.key) {
|
|
379
|
-
opts.log(
|
|
380
|
-
`note: asset ${id} is hosted and no credential is set, so the render cannot load it (vos login, or keep the file beside the config and name its path)`
|
|
381
|
-
);
|
|
382
|
-
return null;
|
|
383
|
-
}
|
|
384
|
-
const res = await fetch(`${opts.origin}/api/assets/${id}/file`, {
|
|
385
|
-
headers: { authorization: `Bearer ${opts.key}` }
|
|
386
|
-
});
|
|
387
|
-
if (!res.ok) {
|
|
388
|
-
opts.log(
|
|
389
|
-
`note: asset ${id} could not be read (${res.status}), so the render cannot load it`
|
|
390
|
-
);
|
|
391
|
-
return null;
|
|
392
|
-
}
|
|
393
|
-
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
394
|
-
const type = (res.headers.get("content-type") ?? "").split(";")[0].trim();
|
|
395
|
-
const file = join(dir, `${id}${extensionFor(type)}`);
|
|
396
|
-
const partial = `${file}.part`;
|
|
397
|
-
try {
|
|
398
|
-
writeFileSync(partial, new Uint8Array(await res.arrayBuffer()), {
|
|
399
|
-
mode: 384
|
|
400
|
-
});
|
|
401
|
-
renameSync(partial, file);
|
|
402
|
-
} finally {
|
|
403
|
-
rmSync(partial, { force: true });
|
|
404
|
-
}
|
|
405
|
-
return file;
|
|
406
|
-
}
|
|
407
|
-
function htmlFaceRefs(config) {
|
|
408
|
-
const out = [];
|
|
409
|
-
const stack = Array.isArray(config.stack) ? config.stack : [];
|
|
410
|
-
for (const entry of stack) {
|
|
411
|
-
const data = entry?.data;
|
|
412
|
-
const overlays = Array.isArray(data?.overlays) ? data.overlays : [];
|
|
413
|
-
for (const clip of overlays) {
|
|
414
|
-
const html = clip?.html;
|
|
415
|
-
const faces = Array.isArray(html?.faces) ? html.faces : [];
|
|
416
|
-
for (const face of faces) {
|
|
417
|
-
if (typeof face?.url !== "string" || !face.url) continue;
|
|
418
|
-
out.push({
|
|
419
|
-
where: `overlay ${String(clip.id)} font ${String(face.family)}`,
|
|
420
|
-
url: face.url,
|
|
421
|
-
set: (next) => {
|
|
422
|
-
face.url = next;
|
|
423
|
-
},
|
|
424
|
-
drop: () => {
|
|
425
|
-
const at = faces.indexOf(face);
|
|
426
|
-
if (at >= 0) faces.splice(at, 1);
|
|
427
|
-
}
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
return out;
|
|
433
|
-
}
|
|
434
|
-
var FACE_HOST = /^https:\/\/assets\.vos\.so\//;
|
|
435
|
-
async function programAssets(config, opts) {
|
|
436
|
-
const refs = manifestRefs(config);
|
|
437
|
-
const faces = [];
|
|
438
|
-
for (const f of htmlFaceRefs(config)) {
|
|
439
|
-
const hosted = hostedAssetId(f.url, opts.origin ? [opts.origin] : []);
|
|
440
|
-
if (!isUrl(f.url) || hosted) {
|
|
441
|
-
faces.push(f);
|
|
442
|
-
} else if (!FACE_HOST.test(f.url)) {
|
|
443
|
-
opts.log(
|
|
444
|
-
`note: ${f.where} names ${f.url}, which is not on https://assets.vos.so, so the render does not fetch it (a face comes from the hosted catalog, a hosted file, or a .woff2 beside the document)`
|
|
445
|
-
);
|
|
446
|
-
f.drop();
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
if (refs.length === 0 && faces.length === 0) return null;
|
|
450
|
-
const files = {};
|
|
451
|
-
const served = /* @__PURE__ */ new Map();
|
|
452
|
-
const serve = (file) => {
|
|
453
|
-
const known = served.get(file);
|
|
454
|
-
if (known) return known;
|
|
455
|
-
const tag = createHash("sha256").update(file).digest("hex").slice(0, 16);
|
|
456
|
-
const ext = extname(file);
|
|
457
|
-
const path = `${ASSET_ROUTE}${tag}${/^\.[A-Za-z0-9]+$/.test(ext) ? ext.toLowerCase() : ""}`;
|
|
458
|
-
files[path] = file;
|
|
459
|
-
served.set(file, path);
|
|
460
|
-
return path;
|
|
461
|
-
};
|
|
462
|
-
const reach = async (ref, where) => {
|
|
463
|
-
const hosted = hostedAssetId(ref, opts.origin ? [opts.origin] : []);
|
|
464
|
-
if (hosted) {
|
|
465
|
-
const file = await fetchHosted(hosted, opts);
|
|
466
|
-
return file ? serve(file) : ref;
|
|
467
|
-
}
|
|
468
|
-
if (isUrl(ref)) return ref;
|
|
469
|
-
const local = localFile(ref, opts.baseDir);
|
|
470
|
-
if ("refused" in local) {
|
|
471
|
-
opts.log(
|
|
472
|
-
`note: ${where} names ${ref}, ${local.refused}, so the render cannot load it`
|
|
473
|
-
);
|
|
474
|
-
return ref;
|
|
475
|
-
}
|
|
476
|
-
return serve(local.file);
|
|
477
|
-
};
|
|
478
|
-
for (const face of faces) face.set(await reach(face.url, face.where));
|
|
479
|
-
const assets = {};
|
|
480
|
-
for (const { name, index, ref } of refs) {
|
|
481
|
-
const url = await reach(
|
|
482
|
-
ref,
|
|
483
|
-
index === null ? `assets.${name}` : `assets.${name}[${index}]`
|
|
484
|
-
);
|
|
485
|
-
if (index === null) assets[name] = url;
|
|
486
|
-
else {
|
|
487
|
-
const list = assets[name] ??= [];
|
|
488
|
-
list[index] = url;
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
return { assets, files };
|
|
492
|
-
}
|
|
493
|
-
function missingManifestFiles(config, baseDir) {
|
|
494
|
-
const out = [];
|
|
495
|
-
for (const { name, index, ref } of manifestRefs(config)) {
|
|
496
|
-
if (isUrl(ref) || hostedAssetId(ref)) continue;
|
|
497
|
-
const local = localFile(ref, baseDir);
|
|
498
|
-
if ("file" in local) continue;
|
|
499
|
-
const where = index === null ? `assets.${name}` : `assets.${name}[${index}]`;
|
|
500
|
-
out.push(`${where} names ${ref}, ${local.refused}`);
|
|
501
|
-
}
|
|
502
|
-
return out;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
235
|
// src/plugin/safeWrite.ts
|
|
506
236
|
import { lstat, writeFile } from "fs/promises";
|
|
507
|
-
import { lstatSync, writeFileSync
|
|
237
|
+
import { lstatSync, writeFileSync } from "fs";
|
|
508
238
|
async function writeFileSafe(path, data) {
|
|
509
239
|
await refuseSymlink(path);
|
|
510
240
|
await writeFile(path, data);
|
|
@@ -524,7 +254,7 @@ function writeFileSafeSync(path, data) {
|
|
|
524
254
|
} catch {
|
|
525
255
|
}
|
|
526
256
|
if (link) throw new Error(symlinkRefusal(path));
|
|
527
|
-
|
|
257
|
+
writeFileSync(path, data);
|
|
528
258
|
}
|
|
529
259
|
function symlinkRefusal(path) {
|
|
530
260
|
return `${path} is a symbolic link, and the CLI writes only a regular file there: remove the link and run again`;
|
|
@@ -586,12 +316,12 @@ function hasFlag(flags, name) {
|
|
|
586
316
|
}
|
|
587
317
|
|
|
588
318
|
// src/plugin/platform.ts
|
|
589
|
-
import { mkdirSync
|
|
590
|
-
import { homedir
|
|
591
|
-
import { join
|
|
319
|
+
import { mkdirSync, readFileSync, writeFileSync as writeFileSync2 } from "fs";
|
|
320
|
+
import { homedir } from "os";
|
|
321
|
+
import { dirname, join } from "path";
|
|
592
322
|
|
|
593
323
|
// src/plugin/hostAgent.ts
|
|
594
|
-
import { existsSync
|
|
324
|
+
import { existsSync } from "fs";
|
|
595
325
|
var SLUG = /^[a-z0-9][a-z0-9.-]{0,39}$/;
|
|
596
326
|
var MARKERS = [
|
|
597
327
|
["cursor", (e) => !!e.CURSOR_TRACE_ID],
|
|
@@ -621,7 +351,7 @@ function slugFromAiAgent(value) {
|
|
|
621
351
|
if (head === "github-copilot-cli") return "github-copilot";
|
|
622
352
|
return SLUG.test(head) ? head : null;
|
|
623
353
|
}
|
|
624
|
-
function detectHostAgent(env = process.env, exists =
|
|
354
|
+
function detectHostAgent(env = process.env, exists = existsSync) {
|
|
625
355
|
if (env.AI_AGENT) {
|
|
626
356
|
const slug = slugFromAiAgent(env.AI_AGENT);
|
|
627
357
|
if (slug) return slug;
|
|
@@ -667,157 +397,479 @@ function parseVosId(input) {
|
|
|
667
397
|
function deriveSlug(title) {
|
|
668
398
|
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 50).replace(/-+$/, "") || "remix";
|
|
669
399
|
}
|
|
670
|
-
var CREDENTIALS_PATH =
|
|
671
|
-
|
|
400
|
+
var CREDENTIALS_PATH = join(homedir(), ".config", "vos", "credentials");
|
|
401
|
+
var DEFAULT_ORIGIN = "https://vos.so";
|
|
402
|
+
function originOf(url) {
|
|
403
|
+
try {
|
|
404
|
+
return new URL(url).origin;
|
|
405
|
+
} catch {
|
|
406
|
+
return url.replace(/\/+$/, "");
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
function readStoredCredential(path = CREDENTIALS_PATH) {
|
|
410
|
+
let text;
|
|
411
|
+
try {
|
|
412
|
+
text = readFileSync(path, "utf8");
|
|
413
|
+
} catch {
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
const [first, ...rest] = text.split("\n");
|
|
417
|
+
const key = first.trim();
|
|
418
|
+
if (!key) return null;
|
|
419
|
+
const line = rest.map((l) => l.trim()).find((l) => l.startsWith("origin="));
|
|
420
|
+
const origin = line ? originOf(line.slice("origin=".length).trim()) : "";
|
|
421
|
+
return { key, origin: origin || DEFAULT_ORIGIN };
|
|
422
|
+
}
|
|
423
|
+
var storedFor = /* @__PURE__ */ new Map();
|
|
424
|
+
var warned = /* @__PURE__ */ new Set();
|
|
425
|
+
function resolveCredential(explicit, path = CREDENTIALS_PATH) {
|
|
672
426
|
const flag = explicit?.trim();
|
|
673
427
|
if (flag) return flag;
|
|
674
428
|
const env = process.env.VOS_API_KEY?.trim();
|
|
675
429
|
if (env) return env;
|
|
430
|
+
const stored = readStoredCredential(path);
|
|
431
|
+
if (!stored) return null;
|
|
432
|
+
storedFor.set(stored.key, stored.origin);
|
|
433
|
+
return stored.key;
|
|
434
|
+
}
|
|
435
|
+
function bearerFor(key, url) {
|
|
436
|
+
if (!key) return {};
|
|
437
|
+
const bound = storedFor.get(key);
|
|
438
|
+
const to = originOf(url);
|
|
439
|
+
if (bound && bound !== to) {
|
|
440
|
+
if (!warned.has(to)) {
|
|
441
|
+
warned.add(to);
|
|
442
|
+
process.stderr.write(
|
|
443
|
+
`note: the credential vos login stored is for ${bound}, so it is not sent to ${to}; for ${to}, run vos login --origin ${to}, or pass --key or VOS_API_KEY
|
|
444
|
+
`
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
return {};
|
|
448
|
+
}
|
|
449
|
+
return { authorization: `Bearer ${key}` };
|
|
450
|
+
}
|
|
451
|
+
var mintAt = (origin = DEFAULT_ORIGIN) => `${originOf(origin)}/app/api`;
|
|
452
|
+
function requireCredential(explicit, origin) {
|
|
453
|
+
const key = resolveCredential(explicit);
|
|
454
|
+
if (!key) {
|
|
455
|
+
throw new Error(
|
|
456
|
+
`no credential found \u2014 pass --key, set VOS_API_KEY, or run \`vos login\` (mint a content key at ${mintAt(origin ?? platformOrigin())}; a vos_rg_ remix grant works too)`
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
return key;
|
|
460
|
+
}
|
|
461
|
+
function writeCredential(key, origin = DEFAULT_ORIGIN, path = CREDENTIALS_PATH) {
|
|
462
|
+
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
463
|
+
writeFileSync2(path, `${key.trim()}
|
|
464
|
+
origin=${originOf(origin)}
|
|
465
|
+
`, {
|
|
466
|
+
mode: 384
|
|
467
|
+
});
|
|
468
|
+
return path;
|
|
469
|
+
}
|
|
470
|
+
function clientId(env = process.env, exists) {
|
|
471
|
+
const explicit = env.VOS_CLIENT?.trim();
|
|
472
|
+
if (explicit && explicit.length <= 60 && !/[\r\n]/.test(explicit))
|
|
473
|
+
return explicit;
|
|
474
|
+
const host = detectHostAgent(env, exists);
|
|
475
|
+
return host ? `${host} vos-cli` : "vos-cli";
|
|
476
|
+
}
|
|
477
|
+
function unreachable(origin, e) {
|
|
478
|
+
const err = e;
|
|
479
|
+
const why = [err?.message, err?.cause?.code].filter(Boolean).join(": ");
|
|
480
|
+
return `could not reach ${origin}${why ? ` (${why})` : ""}; is it the origin you meant? (--origin, or VOS_ORIGIN)`;
|
|
481
|
+
}
|
|
482
|
+
async function apiJson(origin, path, init = {}) {
|
|
483
|
+
const headers = {
|
|
484
|
+
accept: "application/json",
|
|
485
|
+
...init.headers
|
|
486
|
+
};
|
|
487
|
+
Object.assign(headers, bearerFor(init.key, origin));
|
|
488
|
+
if (init.body !== void 0) headers["content-type"] = "application/json";
|
|
489
|
+
const reqBody = init.raw !== void 0 ? init.raw : init.body === void 0 ? void 0 : JSON.stringify(init.body);
|
|
490
|
+
let res;
|
|
491
|
+
try {
|
|
492
|
+
res = await fetch(`${origin}${path}`, {
|
|
493
|
+
method: init.method ?? "GET",
|
|
494
|
+
headers,
|
|
495
|
+
body: reqBody
|
|
496
|
+
});
|
|
497
|
+
} catch (e) {
|
|
498
|
+
throw new Error(unreachable(origin, e));
|
|
499
|
+
}
|
|
500
|
+
let body = {};
|
|
501
|
+
try {
|
|
502
|
+
body = await res.json();
|
|
503
|
+
} catch {
|
|
504
|
+
}
|
|
505
|
+
return { status: res.status, body, origin };
|
|
506
|
+
}
|
|
507
|
+
function detailLine(body) {
|
|
508
|
+
const details = body.details;
|
|
509
|
+
if (!Array.isArray(details) || !details.length) return "";
|
|
510
|
+
const lines = details.map((d) => {
|
|
511
|
+
const path = Array.isArray(d.path) ? d.path.join(".") : "";
|
|
512
|
+
return `${path ? `${path}: ` : ""}${String(d.message ?? JSON.stringify(d))}`;
|
|
513
|
+
});
|
|
514
|
+
return `
|
|
515
|
+
${lines.join("\n ")}`;
|
|
516
|
+
}
|
|
517
|
+
function apiError(what, r) {
|
|
518
|
+
const detail = typeof r.body.error === "string" ? r.body.error + detailLine(r.body) : r.body.details !== void 0 ? JSON.stringify(r.body.details) : "";
|
|
519
|
+
const hint = r.status === 401 ? ` (run \`vos login\`, or set VOS_API_KEY \u2014 mint a key at ${mintAt(r.origin)})` : "";
|
|
520
|
+
return `${what} \u2192 ${r.status}${detail ? `: ${detail}` : ""}${hint}`;
|
|
521
|
+
}
|
|
522
|
+
function promptHint(target, vosId) {
|
|
523
|
+
return `vos push ${target} --vos ${vosId} --prompt "<the person's prompt, as they wrote it>"`;
|
|
524
|
+
}
|
|
525
|
+
var SYNC_STATE_NAME = "vos.json";
|
|
526
|
+
function hostedMediaOf(value) {
|
|
527
|
+
if (!value || typeof value !== "object") return void 0;
|
|
528
|
+
const out = {};
|
|
529
|
+
for (const [file, entry] of Object.entries(value)) {
|
|
530
|
+
const e = entry;
|
|
531
|
+
if (e && typeof e.ref === "string" && typeof e.sha256 === "string")
|
|
532
|
+
out[file] = { ref: e.ref, sha256: e.sha256 };
|
|
533
|
+
}
|
|
534
|
+
return Object.keys(out).length ? out : void 0;
|
|
535
|
+
}
|
|
536
|
+
function readJsonFile(file) {
|
|
676
537
|
try {
|
|
677
|
-
|
|
678
|
-
return first || null;
|
|
538
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
679
539
|
} catch {
|
|
680
540
|
return null;
|
|
681
541
|
}
|
|
682
|
-
}
|
|
683
|
-
function
|
|
684
|
-
const
|
|
685
|
-
if (
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
542
|
+
}
|
|
543
|
+
function readSyncState(dir) {
|
|
544
|
+
const own = readJsonFile(join(dir, SYNC_STATE_NAME));
|
|
545
|
+
if (own && typeof own.vosId === "string") {
|
|
546
|
+
return {
|
|
547
|
+
vosId: own.vosId,
|
|
548
|
+
versionId: typeof own.versionId === "string" ? own.versionId : null,
|
|
549
|
+
...typeof own.pushedAt === "string" ? { pushedAt: own.pushedAt } : {},
|
|
550
|
+
...typeof own.title === "string" ? { title: own.title } : {},
|
|
551
|
+
...typeof own.slug === "string" ? { slug: own.slug } : {},
|
|
552
|
+
...typeof own.remixOfId === "string" ? { remixOfId: own.remixOfId } : {},
|
|
553
|
+
...hostedMediaOf(own.hostedMedia) ? { hostedMedia: hostedMediaOf(own.hostedMedia) } : {}
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
const push = readJsonFile(join(dir, "push.json"));
|
|
557
|
+
if (push && typeof push.vosId === "string") {
|
|
558
|
+
return {
|
|
559
|
+
vosId: push.vosId,
|
|
560
|
+
versionId: typeof push.versionId === "string" ? push.versionId : null,
|
|
561
|
+
...typeof push.pushedAt === "string" ? { pushedAt: push.pushedAt } : {}
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
const meta = readJsonFile(join(dir, "meta.json"));
|
|
565
|
+
if (meta && typeof meta.id === "string") {
|
|
566
|
+
return {
|
|
567
|
+
vosId: meta.id,
|
|
568
|
+
versionId: typeof meta.currentVersionId === "string" ? meta.currentVersionId : null,
|
|
569
|
+
...typeof meta.title === "string" ? { title: meta.title } : {},
|
|
570
|
+
...typeof meta.slug === "string" ? { slug: meta.slug } : {},
|
|
571
|
+
...typeof meta.remixOfId === "string" ? { remixOfId: meta.remixOfId } : {}
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
576
|
+
function writeSyncState(dir, patch) {
|
|
577
|
+
const merged = {
|
|
578
|
+
...readSyncState(dir) ?? { versionId: null },
|
|
579
|
+
...patch
|
|
580
|
+
};
|
|
581
|
+
writeFileSafeSync(
|
|
582
|
+
join(dir, SYNC_STATE_NAME),
|
|
583
|
+
`${JSON.stringify(merged, null, 2)}
|
|
584
|
+
`
|
|
585
|
+
);
|
|
586
|
+
return merged;
|
|
587
|
+
}
|
|
588
|
+
function formatChanges(changes) {
|
|
589
|
+
const lines = [];
|
|
590
|
+
for (const ch of changes) {
|
|
591
|
+
const label = ch.label ? ` \xB7 ${ch.label}` : "";
|
|
592
|
+
const who = ch.origin === "studio" ? "human" : ch.origin ?? "unknown";
|
|
593
|
+
lines.push(
|
|
594
|
+
`v${String(ch.versionNumber ?? "?")} (${who}${label}): ${ch.summary ?? ""}`
|
|
595
|
+
);
|
|
596
|
+
if (ch.note) lines.push(` note: ${ch.note}`);
|
|
597
|
+
}
|
|
598
|
+
return lines;
|
|
599
|
+
}
|
|
600
|
+
function serverWarnings(body, log) {
|
|
601
|
+
const warnings = Array.isArray(body.warnings) ? body.warnings : [];
|
|
602
|
+
for (const w of warnings) if (typeof w === "string") log(`warning ${w}`);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// src/programAssets.ts
|
|
606
|
+
import { createHash } from "crypto";
|
|
607
|
+
import {
|
|
608
|
+
closeSync,
|
|
609
|
+
existsSync as existsSync2,
|
|
610
|
+
mkdirSync as mkdirSync2,
|
|
611
|
+
openSync,
|
|
612
|
+
readSync,
|
|
613
|
+
readdirSync,
|
|
614
|
+
realpathSync,
|
|
615
|
+
renameSync,
|
|
616
|
+
rmSync,
|
|
617
|
+
statSync,
|
|
618
|
+
writeFileSync as writeFileSync3
|
|
619
|
+
} from "fs";
|
|
620
|
+
import { homedir as homedir2 } from "os";
|
|
621
|
+
import { extname, join as join2, resolve, sep } from "path";
|
|
622
|
+
var ASSET_ROUTE = "/__assets/";
|
|
623
|
+
var UUID = "[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}";
|
|
624
|
+
var HOSTED_URI = new RegExp(`^asset:(${UUID})$`);
|
|
625
|
+
var HOSTED_FILE = new RegExp(
|
|
626
|
+
`^(https?://[^/?#]+)?/api/assets/(${UUID})/file(?:[?#].*)?$`
|
|
627
|
+
);
|
|
628
|
+
function hostedAssetId(ref, homeOrigins = []) {
|
|
629
|
+
const uri = HOSTED_URI.exec(ref);
|
|
630
|
+
if (uri) return uri[1];
|
|
631
|
+
const file = HOSTED_FILE.exec(ref);
|
|
632
|
+
if (!file) return null;
|
|
633
|
+
return file[1] === void 0 || homeOrigins.includes(file[1]) ? file[2] : null;
|
|
634
|
+
}
|
|
635
|
+
var ASSET_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
636
|
+
var SERVED_TYPES = {
|
|
637
|
+
".png": "image/png",
|
|
638
|
+
".jpg": "image/jpeg",
|
|
639
|
+
".jpeg": "image/jpeg",
|
|
640
|
+
".webp": "image/webp",
|
|
641
|
+
".gif": "image/gif",
|
|
642
|
+
".svg": "image/svg+xml",
|
|
643
|
+
".avif": "image/avif",
|
|
644
|
+
".mp4": "video/mp4",
|
|
645
|
+
".webm": "video/webm",
|
|
646
|
+
".mov": "video/quicktime",
|
|
647
|
+
".mp3": "audio/mpeg",
|
|
648
|
+
".ogg": "audio/ogg",
|
|
649
|
+
".wav": "audio/wav",
|
|
650
|
+
".m4a": "audio/mp4",
|
|
651
|
+
".glb": "model/gltf-binary",
|
|
652
|
+
".gltf": "model/gltf+json",
|
|
653
|
+
".woff2": "font/woff2",
|
|
654
|
+
".woff": "font/woff",
|
|
655
|
+
".ttf": "font/ttf",
|
|
656
|
+
".otf": "font/otf",
|
|
657
|
+
".json": "application/json"
|
|
658
|
+
};
|
|
659
|
+
function serveFile(file, range) {
|
|
660
|
+
const size = statSync(file).size;
|
|
661
|
+
const type = SERVED_TYPES[extname(file).toLowerCase()] ?? "application/octet-stream";
|
|
662
|
+
const base = { "content-type": type, "accept-ranges": "bytes" };
|
|
663
|
+
const read = (start2, end2) => {
|
|
664
|
+
const body = Buffer.alloc(Math.max(0, end2 - start2 + 1));
|
|
665
|
+
const fd = openSync(file, "r");
|
|
666
|
+
try {
|
|
667
|
+
readSync(fd, body, 0, body.length, start2);
|
|
668
|
+
} finally {
|
|
669
|
+
closeSync(fd);
|
|
670
|
+
}
|
|
671
|
+
return body;
|
|
672
|
+
};
|
|
673
|
+
const m = range ? /^bytes=(\d*)-(\d*)$/.exec(range.trim()) : null;
|
|
674
|
+
if (!m || m[1] === "" && m[2] === "") {
|
|
675
|
+
return {
|
|
676
|
+
status: 200,
|
|
677
|
+
headers: { ...base, "content-length": String(size) },
|
|
678
|
+
body: read(0, size - 1)
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
const start = m[1] === "" ? Math.max(0, size - Number(m[2])) : Number(m[1]);
|
|
682
|
+
const end = m[1] === "" || m[2] === "" ? size - 1 : Math.min(Number(m[2]), size - 1);
|
|
683
|
+
if (start >= size || start > end) {
|
|
684
|
+
return {
|
|
685
|
+
status: 416,
|
|
686
|
+
headers: { ...base, "content-range": `bytes */${size}` },
|
|
687
|
+
body: Buffer.alloc(0)
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
return {
|
|
691
|
+
status: 206,
|
|
692
|
+
headers: {
|
|
693
|
+
...base,
|
|
694
|
+
"content-range": `bytes ${start}-${end}/${size}`,
|
|
695
|
+
"content-length": String(end - start + 1)
|
|
696
|
+
},
|
|
697
|
+
body: read(start, end)
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
function localFile(ref, baseDir) {
|
|
701
|
+
if (baseDir === null) {
|
|
702
|
+
return {
|
|
703
|
+
refused: "a config loaded from a URL has no directory, so it reads no local file"
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
const file = resolve(baseDir, ref);
|
|
707
|
+
if (!existsSync2(file) || !statSync(file).isFile()) {
|
|
708
|
+
return { refused: "which is not a file beside the config" };
|
|
709
|
+
}
|
|
710
|
+
const root = realpathSync(baseDir);
|
|
711
|
+
const real = realpathSync(file);
|
|
712
|
+
if (!real.startsWith(root + sep)) {
|
|
713
|
+
return {
|
|
714
|
+
refused: `which is outside the program's directory (${root}). A program reads only files in its own directory or below: copy the file there`
|
|
715
|
+
};
|
|
689
716
|
}
|
|
690
|
-
return
|
|
717
|
+
return { file: real };
|
|
691
718
|
}
|
|
692
|
-
function
|
|
693
|
-
|
|
694
|
-
writeFileSync3(CREDENTIALS_PATH, `${key.trim()}
|
|
695
|
-
`, { mode: 384 });
|
|
696
|
-
return CREDENTIALS_PATH;
|
|
719
|
+
function isUrl(ref) {
|
|
720
|
+
return /^(https?:|data:|blob:)/.test(ref);
|
|
697
721
|
}
|
|
698
|
-
function
|
|
699
|
-
const
|
|
700
|
-
if (
|
|
701
|
-
return
|
|
702
|
-
|
|
703
|
-
return host ? `${host} vos-cli` : "vos-cli";
|
|
722
|
+
function manifestOf(config) {
|
|
723
|
+
const assets = config.assets;
|
|
724
|
+
if (!assets || typeof assets !== "object" || Array.isArray(assets))
|
|
725
|
+
return null;
|
|
726
|
+
return Object.keys(assets).length > 0 ? assets : null;
|
|
704
727
|
}
|
|
705
|
-
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
body: reqBody
|
|
717
|
-
});
|
|
718
|
-
let body = {};
|
|
719
|
-
try {
|
|
720
|
-
body = await res.json();
|
|
721
|
-
} catch {
|
|
728
|
+
function manifestRefs(config) {
|
|
729
|
+
const out = [];
|
|
730
|
+
for (const [name, decl] of Object.entries(manifestOf(config) ?? {})) {
|
|
731
|
+
if (!ASSET_NAME.test(name) || !decl || typeof decl !== "object") continue;
|
|
732
|
+
if (Array.isArray(decl.ref)) {
|
|
733
|
+
decl.ref.forEach((ref, index) => {
|
|
734
|
+
if (typeof ref === "string") out.push({ name, index, ref });
|
|
735
|
+
});
|
|
736
|
+
} else if (typeof decl.ref === "string") {
|
|
737
|
+
out.push({ name, index: null, ref: decl.ref });
|
|
738
|
+
}
|
|
722
739
|
}
|
|
723
|
-
return
|
|
740
|
+
return out;
|
|
724
741
|
}
|
|
725
|
-
function
|
|
726
|
-
const
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
742
|
+
async function fetchHosted(id, opts) {
|
|
743
|
+
const dir = opts.cacheDir ?? join2(homedir2(), ".cache", "vos", "assets");
|
|
744
|
+
const cached = existsSync2(dir) ? readdirSync(dir).find(
|
|
745
|
+
(f) => (f === id || f.startsWith(`${id}.`)) && !f.endsWith(".part")
|
|
746
|
+
) : void 0;
|
|
747
|
+
if (cached) return join2(dir, cached);
|
|
748
|
+
if (!opts.origin || !opts.key) {
|
|
749
|
+
opts.log(
|
|
750
|
+
`note: asset ${id} is hosted and no credential is set, so the render cannot load it (vos login, or keep the file beside the config and name its path)`
|
|
751
|
+
);
|
|
752
|
+
return null;
|
|
753
|
+
}
|
|
754
|
+
const res = await fetch(`${opts.origin}/api/assets/${id}/file`, {
|
|
755
|
+
headers: bearerFor(opts.key, opts.origin)
|
|
731
756
|
});
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
const hint = r.status === 401 ? " (run `vos login`, or set VOS_API_KEY \u2014 mint a key at https://vos.so/app/api)" : "";
|
|
738
|
-
return `${what} \u2192 ${r.status}${detail ? `: ${detail}` : ""}${hint}`;
|
|
739
|
-
}
|
|
740
|
-
function promptHint(target, vosId) {
|
|
741
|
-
return `vos push ${target} --vos ${vosId} --prompt "<the person's prompt, as they wrote it>"`;
|
|
742
|
-
}
|
|
743
|
-
var SYNC_STATE_NAME = "vos.json";
|
|
744
|
-
function hostedMediaOf(value) {
|
|
745
|
-
if (!value || typeof value !== "object") return void 0;
|
|
746
|
-
const out = {};
|
|
747
|
-
for (const [file, entry] of Object.entries(value)) {
|
|
748
|
-
const e = entry;
|
|
749
|
-
if (e && typeof e.ref === "string" && typeof e.sha256 === "string")
|
|
750
|
-
out[file] = { ref: e.ref, sha256: e.sha256 };
|
|
757
|
+
if (!res.ok) {
|
|
758
|
+
opts.log(
|
|
759
|
+
`note: asset ${id} could not be read (${res.status}), so the render cannot load it`
|
|
760
|
+
);
|
|
761
|
+
return null;
|
|
751
762
|
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
763
|
+
mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
764
|
+
const type = (res.headers.get("content-type") ?? "").split(";")[0].trim();
|
|
765
|
+
const file = join2(dir, `${id}${extensionFor(type)}`);
|
|
766
|
+
const partial = `${file}.part`;
|
|
755
767
|
try {
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
768
|
+
writeFileSync3(partial, new Uint8Array(await res.arrayBuffer()), {
|
|
769
|
+
mode: 384
|
|
770
|
+
});
|
|
771
|
+
renameSync(partial, file);
|
|
772
|
+
} finally {
|
|
773
|
+
rmSync(partial, { force: true });
|
|
759
774
|
}
|
|
775
|
+
return file;
|
|
760
776
|
}
|
|
761
|
-
function
|
|
762
|
-
const
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
vosId: meta.id,
|
|
786
|
-
versionId: typeof meta.currentVersionId === "string" ? meta.currentVersionId : null,
|
|
787
|
-
...typeof meta.title === "string" ? { title: meta.title } : {},
|
|
788
|
-
...typeof meta.slug === "string" ? { slug: meta.slug } : {},
|
|
789
|
-
...typeof meta.remixOfId === "string" ? { remixOfId: meta.remixOfId } : {}
|
|
790
|
-
};
|
|
777
|
+
function htmlFaceRefs(config) {
|
|
778
|
+
const out = [];
|
|
779
|
+
const stack = Array.isArray(config.stack) ? config.stack : [];
|
|
780
|
+
for (const entry of stack) {
|
|
781
|
+
const data = entry?.data;
|
|
782
|
+
const overlays = Array.isArray(data?.overlays) ? data.overlays : [];
|
|
783
|
+
for (const clip of overlays) {
|
|
784
|
+
const html = clip?.html;
|
|
785
|
+
const faces = Array.isArray(html?.faces) ? html.faces : [];
|
|
786
|
+
for (const face of faces) {
|
|
787
|
+
if (typeof face?.url !== "string" || !face.url) continue;
|
|
788
|
+
out.push({
|
|
789
|
+
where: `overlay ${String(clip.id)} font ${String(face.family)}`,
|
|
790
|
+
url: face.url,
|
|
791
|
+
set: (next) => {
|
|
792
|
+
face.url = next;
|
|
793
|
+
},
|
|
794
|
+
drop: () => {
|
|
795
|
+
const at = faces.indexOf(face);
|
|
796
|
+
if (at >= 0) faces.splice(at, 1);
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
}
|
|
791
801
|
}
|
|
792
|
-
return
|
|
802
|
+
return out;
|
|
793
803
|
}
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
804
|
+
var FACE_HOST = /^https:\/\/assets\.vos\.so\//;
|
|
805
|
+
async function programAssets(config, opts) {
|
|
806
|
+
const refs = manifestRefs(config);
|
|
807
|
+
const faces = [];
|
|
808
|
+
for (const f of htmlFaceRefs(config)) {
|
|
809
|
+
const hosted = hostedAssetId(f.url, opts.origin ? [opts.origin] : []);
|
|
810
|
+
if (!isUrl(f.url) || hosted) {
|
|
811
|
+
faces.push(f);
|
|
812
|
+
} else if (!FACE_HOST.test(f.url)) {
|
|
813
|
+
opts.log(
|
|
814
|
+
`note: ${f.where} names ${f.url}, which is not on https://assets.vos.so, so the render does not fetch it (a face comes from the hosted catalog, a hosted file, or a .woff2 beside the document)`
|
|
815
|
+
);
|
|
816
|
+
f.drop();
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
if (refs.length === 0 && faces.length === 0) return null;
|
|
820
|
+
const files = {};
|
|
821
|
+
const served = /* @__PURE__ */ new Map();
|
|
822
|
+
const serve = (file) => {
|
|
823
|
+
const known = served.get(file);
|
|
824
|
+
if (known) return known;
|
|
825
|
+
const tag = createHash("sha256").update(file).digest("hex").slice(0, 16);
|
|
826
|
+
const ext = extname(file);
|
|
827
|
+
const path = `${ASSET_ROUTE}${tag}${/^\.[A-Za-z0-9]+$/.test(ext) ? ext.toLowerCase() : ""}`;
|
|
828
|
+
files[path] = file;
|
|
829
|
+
served.set(file, path);
|
|
830
|
+
return path;
|
|
798
831
|
};
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
832
|
+
const reach = async (ref, where) => {
|
|
833
|
+
const hosted = hostedAssetId(ref, opts.origin ? [opts.origin] : []);
|
|
834
|
+
if (hosted) {
|
|
835
|
+
const file = await fetchHosted(hosted, opts);
|
|
836
|
+
return file ? serve(file) : ref;
|
|
837
|
+
}
|
|
838
|
+
if (isUrl(ref)) return ref;
|
|
839
|
+
const local = localFile(ref, opts.baseDir);
|
|
840
|
+
if ("refused" in local) {
|
|
841
|
+
opts.log(
|
|
842
|
+
`note: ${where} names ${ref}, ${local.refused}, so the render cannot load it`
|
|
843
|
+
);
|
|
844
|
+
return ref;
|
|
845
|
+
}
|
|
846
|
+
return serve(local.file);
|
|
847
|
+
};
|
|
848
|
+
for (const face of faces) face.set(await reach(face.url, face.where));
|
|
849
|
+
const assets = {};
|
|
850
|
+
for (const { name, index, ref } of refs) {
|
|
851
|
+
const url = await reach(
|
|
852
|
+
ref,
|
|
853
|
+
index === null ? `assets.${name}` : `assets.${name}[${index}]`
|
|
813
854
|
);
|
|
814
|
-
if (
|
|
855
|
+
if (index === null) assets[name] = url;
|
|
856
|
+
else {
|
|
857
|
+
const list = assets[name] ??= [];
|
|
858
|
+
list[index] = url;
|
|
859
|
+
}
|
|
815
860
|
}
|
|
816
|
-
return
|
|
861
|
+
return { assets, files };
|
|
817
862
|
}
|
|
818
|
-
function
|
|
819
|
-
const
|
|
820
|
-
for (const
|
|
863
|
+
function missingManifestFiles(config, baseDir) {
|
|
864
|
+
const out = [];
|
|
865
|
+
for (const { name, index, ref } of manifestRefs(config)) {
|
|
866
|
+
if (isUrl(ref) || hostedAssetId(ref)) continue;
|
|
867
|
+
const local = localFile(ref, baseDir);
|
|
868
|
+
if ("file" in local) continue;
|
|
869
|
+
const where = index === null ? `assets.${name}` : `assets.${name}[${index}]`;
|
|
870
|
+
out.push(`${where} names ${ref}, ${local.refused}`);
|
|
871
|
+
}
|
|
872
|
+
return out;
|
|
821
873
|
}
|
|
822
874
|
|
|
823
875
|
export {
|
|
@@ -833,13 +885,6 @@ export {
|
|
|
833
885
|
resolveMediaType,
|
|
834
886
|
nameForType,
|
|
835
887
|
containerMismatch,
|
|
836
|
-
ASSET_ROUTE,
|
|
837
|
-
hostedAssetId,
|
|
838
|
-
serveFile,
|
|
839
|
-
localFile,
|
|
840
|
-
manifestRefs,
|
|
841
|
-
programAssets,
|
|
842
|
-
missingManifestFiles,
|
|
843
888
|
writeFileSafe,
|
|
844
889
|
refuseSymlink,
|
|
845
890
|
writeFileSafeSync,
|
|
@@ -852,6 +897,8 @@ export {
|
|
|
852
897
|
deriveSlug,
|
|
853
898
|
CREDENTIALS_PATH,
|
|
854
899
|
resolveCredential,
|
|
900
|
+
bearerFor,
|
|
901
|
+
mintAt,
|
|
855
902
|
requireCredential,
|
|
856
903
|
writeCredential,
|
|
857
904
|
clientId,
|
|
@@ -863,6 +910,13 @@ export {
|
|
|
863
910
|
readSyncState,
|
|
864
911
|
writeSyncState,
|
|
865
912
|
formatChanges,
|
|
866
|
-
serverWarnings
|
|
913
|
+
serverWarnings,
|
|
914
|
+
ASSET_ROUTE,
|
|
915
|
+
hostedAssetId,
|
|
916
|
+
serveFile,
|
|
917
|
+
localFile,
|
|
918
|
+
manifestRefs,
|
|
919
|
+
programAssets,
|
|
920
|
+
missingManifestFiles
|
|
867
921
|
};
|
|
868
|
-
//# sourceMappingURL=chunk-
|
|
922
|
+
//# sourceMappingURL=chunk-J3KHIZCH.js.map
|