@koda-sl/baker-cli 0.204.0 → 0.209.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 +56 -3
- package/dist/{chunk-UZYVSPCA.js → chunk-3E33AGNR.js} +2 -2
- package/dist/{chunk-SVHFT7AN.js → chunk-3WODSPNZ.js} +4 -4
- package/dist/{chunk-2R2DEPG7.js → chunk-CRXSLYEX.js} +3 -3
- package/dist/{chunk-5MPIOGRO.js → chunk-E2WYWHJJ.js} +370 -221
- package/dist/chunk-E2WYWHJJ.js.map +1 -0
- package/dist/{chunk-KHHD26LZ.js → chunk-FUSTYLP5.js} +3 -3
- package/dist/{chunk-6NZG2TCM.js → chunk-IRNM3T6B.js} +6 -1
- package/dist/{chunk-6NZG2TCM.js.map → chunk-IRNM3T6B.js.map} +1 -1
- package/dist/cli.js +732 -121
- package/dist/cli.js.map +1 -1
- package/dist/client-CF6ZORCO.js +15 -0
- package/dist/engine/index.js +3 -3
- package/dist/{env-FF7ZB5W5.js → env-7SCX7HAH.js} +4 -2
- package/dist/{output-YGPWST77.js → output-4YIOSWOG.js} +5 -5
- package/dist/{shared-CRWB5B7P.js → shared-EBF5DEDJ.js} +6 -6
- package/package.json +1 -1
- package/dist/chunk-5MPIOGRO.js.map +0 -1
- package/dist/client-MP7BKFKM.js +0 -15
- /package/dist/{chunk-UZYVSPCA.js.map → chunk-3E33AGNR.js.map} +0 -0
- /package/dist/{chunk-SVHFT7AN.js.map → chunk-3WODSPNZ.js.map} +0 -0
- /package/dist/{chunk-2R2DEPG7.js.map → chunk-CRXSLYEX.js.map} +0 -0
- /package/dist/{chunk-KHHD26LZ.js.map → chunk-FUSTYLP5.js.map} +0 -0
- /package/dist/{client-MP7BKFKM.js.map → client-CF6ZORCO.js.map} +0 -0
- /package/dist/{env-FF7ZB5W5.js.map → env-7SCX7HAH.js.map} +0 -0
- /package/dist/{output-YGPWST77.js.map → output-4YIOSWOG.js.map} +0 -0
- /package/dist/{shared-CRWB5B7P.js.map → shared-EBF5DEDJ.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
debugLogHttp,
|
|
3
3
|
readBodyForLog
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-3E33AGNR.js";
|
|
5
5
|
import {
|
|
6
6
|
__commonJS,
|
|
7
7
|
__toESM,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
plannedRoutes,
|
|
14
14
|
refuseNonPublicUrl,
|
|
15
15
|
shouldEscalate
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-IRNM3T6B.js";
|
|
17
17
|
|
|
18
18
|
// ../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/index.js
|
|
19
19
|
var require_safe_stable_stringify = __commonJS({
|
|
@@ -612,7 +612,7 @@ ${originalIndentation}`;
|
|
|
612
612
|
});
|
|
613
613
|
|
|
614
614
|
// src/engine/index.ts
|
|
615
|
-
import
|
|
615
|
+
import path18 from "path";
|
|
616
616
|
|
|
617
617
|
// src/engine/client/http.ts
|
|
618
618
|
var CONTENT_POLICY_CODE = "content_policy_blocked";
|
|
@@ -661,17 +661,17 @@ var HttpClient = class {
|
|
|
661
661
|
this.fetchFn = opts.fetchFn ?? fetch;
|
|
662
662
|
this.sleepFn = opts.sleepFn ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
663
663
|
}
|
|
664
|
-
async postJson(
|
|
665
|
-
return await this.requestJson("POST",
|
|
664
|
+
async postJson(path19, body, signal) {
|
|
665
|
+
return await this.requestJson("POST", path19, body, signal);
|
|
666
666
|
}
|
|
667
|
-
async putJson(
|
|
668
|
-
return await this.requestJson("PUT",
|
|
667
|
+
async putJson(path19, body, signal) {
|
|
668
|
+
return await this.requestJson("PUT", path19, body, signal);
|
|
669
669
|
}
|
|
670
|
-
async getJson(
|
|
671
|
-
return await this.requestJson("GET",
|
|
670
|
+
async getJson(path19, signal) {
|
|
671
|
+
return await this.requestJson("GET", path19, void 0, signal);
|
|
672
672
|
}
|
|
673
|
-
async requestJson(method,
|
|
674
|
-
const url = `${this.baseUrl}${
|
|
673
|
+
async requestJson(method, path19, body, signal) {
|
|
674
|
+
const url = `${this.baseUrl}${path19.startsWith("/") ? path19 : `/${path19}`}`;
|
|
675
675
|
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
676
676
|
const outcome = await this.attempt(method, url, body, attempt, signal);
|
|
677
677
|
if (outcome.kind === "value") return outcome.value;
|
|
@@ -834,12 +834,12 @@ var BackendClient = class {
|
|
|
834
834
|
}
|
|
835
835
|
async pollJob(jobId, signal) {
|
|
836
836
|
const deadline = Date.now() + JOB_POLL_MAX_MS;
|
|
837
|
-
const
|
|
837
|
+
const path19 = `/api/canvas/jobs/${encodeURIComponent(jobId)}`;
|
|
838
838
|
for (let attempt = 0; ; attempt++) {
|
|
839
839
|
if (signal?.aborted) {
|
|
840
840
|
throw new BackendHttpError({ kind: "network", cause: signal.reason ?? new Error("aborted") });
|
|
841
841
|
}
|
|
842
|
-
const job = await this.http.getJson(
|
|
842
|
+
const job = await this.http.getJson(path19, signal);
|
|
843
843
|
if (job.status === "completed") return job.result;
|
|
844
844
|
if (job.status === "failed") throw failedJobError(job.error);
|
|
845
845
|
if (Date.now() > deadline) {
|
|
@@ -918,8 +918,8 @@ var BackendClient = class {
|
|
|
918
918
|
await this.http.postJson("/api/creatives/definition", payload, signal);
|
|
919
919
|
}
|
|
920
920
|
getArtifact(kind, name, version, signal) {
|
|
921
|
-
const
|
|
922
|
-
return this.http.getJson(
|
|
921
|
+
const path19 = version ? `/api/canvas/artifacts/${encodeURIComponent(kind)}/${encodeURIComponent(name)}/${encodeURIComponent(version)}` : `/api/canvas/artifacts/${encodeURIComponent(kind)}/${encodeURIComponent(name)}`;
|
|
922
|
+
return this.http.getJson(path19, signal);
|
|
923
923
|
}
|
|
924
924
|
};
|
|
925
925
|
|
|
@@ -4289,9 +4289,9 @@ function checkOutputRef(ctx) {
|
|
|
4289
4289
|
function pushZodIssues(issues, err, pathPrefix, code, nodeId, nodeType) {
|
|
4290
4290
|
for (const issue of err.issues) {
|
|
4291
4291
|
const tail2 = pathToString(issue.path);
|
|
4292
|
-
const
|
|
4292
|
+
const path19 = pathPrefix ? tail2 ? `${pathPrefix}.${tail2}` : pathPrefix : tail2;
|
|
4293
4293
|
issues.push({
|
|
4294
|
-
path:
|
|
4294
|
+
path: path19,
|
|
4295
4295
|
code,
|
|
4296
4296
|
message: issue.message,
|
|
4297
4297
|
received: issue.code === "invalid_type" ? issue.received : void 0,
|
|
@@ -4300,8 +4300,8 @@ function pushZodIssues(issues, err, pathPrefix, code, nodeId, nodeType) {
|
|
|
4300
4300
|
});
|
|
4301
4301
|
}
|
|
4302
4302
|
}
|
|
4303
|
-
function pathToString(
|
|
4304
|
-
return
|
|
4303
|
+
function pathToString(path19) {
|
|
4304
|
+
return path19.map((p) => typeof p === "number" ? `[${p}]` : `.${String(p)}`).join("").replace(/^\./, "");
|
|
4305
4305
|
}
|
|
4306
4306
|
function buildDepGraph(canvas) {
|
|
4307
4307
|
const graph = /* @__PURE__ */ new Map();
|
|
@@ -4910,10 +4910,10 @@ var NodeRegistry = class {
|
|
|
4910
4910
|
};
|
|
4911
4911
|
|
|
4912
4912
|
// src/engine/nodes/ingest.ts
|
|
4913
|
-
import { execFile as execFileCb
|
|
4914
|
-
import { mkdtemp,
|
|
4913
|
+
import { execFile as execFileCb } from "child_process";
|
|
4914
|
+
import { mkdtemp, readFile as readFile4, rm, stat as stat2 } from "fs/promises";
|
|
4915
4915
|
import { tmpdir } from "os";
|
|
4916
|
-
import
|
|
4916
|
+
import path6 from "path";
|
|
4917
4917
|
import { promisify } from "util";
|
|
4918
4918
|
import { z as z5 } from "zod";
|
|
4919
4919
|
|
|
@@ -5231,6 +5231,248 @@ async function fetchExternalBytes(url, options) {
|
|
|
5231
5231
|
return { buffer, contentType: response.headers.get("content-type") };
|
|
5232
5232
|
}
|
|
5233
5233
|
|
|
5234
|
+
// src/lib/ytDlp.ts
|
|
5235
|
+
import { spawn } from "child_process";
|
|
5236
|
+
import { readdir, readFile as readFile3 } from "fs/promises";
|
|
5237
|
+
import path5 from "path";
|
|
5238
|
+
|
|
5239
|
+
// src/lib/vimeoCookie.ts
|
|
5240
|
+
import { writeFile as writeFile3 } from "fs/promises";
|
|
5241
|
+
import path4 from "path";
|
|
5242
|
+
var COOKIE_NAME = "vimeo";
|
|
5243
|
+
async function writeVimeoCookieFile(cookie, workDir) {
|
|
5244
|
+
const value = cookie?.trim();
|
|
5245
|
+
if (!value) return void 0;
|
|
5246
|
+
const expires = Math.floor(Date.now() / 1e3) + 365 * 24 * 60 * 60;
|
|
5247
|
+
const jar = [
|
|
5248
|
+
"# Netscape HTTP Cookie File",
|
|
5249
|
+
[".vimeo.com", "TRUE", "/", "TRUE", String(expires), COOKIE_NAME, value].join(" "),
|
|
5250
|
+
""
|
|
5251
|
+
].join("\n");
|
|
5252
|
+
const filePath = path4.join(workDir, "vimeo-cookies.txt");
|
|
5253
|
+
await writeFile3(filePath, jar, { mode: 384 });
|
|
5254
|
+
return filePath;
|
|
5255
|
+
}
|
|
5256
|
+
var VIMEO_AUTH_SIGNATURES = ["oauth token", "only works when logged-in", "unable to fetch new oauth tokens"];
|
|
5257
|
+
function isVimeoAuthFailure(detail) {
|
|
5258
|
+
const haystack = detail.toLowerCase();
|
|
5259
|
+
if (!haystack.includes("[vimeo]")) return false;
|
|
5260
|
+
return VIMEO_AUTH_SIGNATURES.some((signature) => haystack.includes(signature));
|
|
5261
|
+
}
|
|
5262
|
+
|
|
5263
|
+
// src/lib/ytDlpSignal.ts
|
|
5264
|
+
var HTTP_ERROR = /HTTP Error (\d{3})/i;
|
|
5265
|
+
var CHALLENGE_PHRASES = [
|
|
5266
|
+
"sign in to confirm you're not a bot",
|
|
5267
|
+
"sign in to confirm you\u2019re not a bot",
|
|
5268
|
+
"confirm you are not a bot",
|
|
5269
|
+
"unusual traffic",
|
|
5270
|
+
"captcha"
|
|
5271
|
+
];
|
|
5272
|
+
var NOT_A_BLOCK_PHRASES = ["private video", "video unavailable", "members-only", "this video is available to"];
|
|
5273
|
+
var PROXY_TUNNEL_FAILURE = /tunnel connection failed: 407|proxy authentication required|http error 407/i;
|
|
5274
|
+
function ytDlpBlockSignal(stderr) {
|
|
5275
|
+
const text = stderr.toLowerCase();
|
|
5276
|
+
if (PROXY_TUNNEL_FAILURE.test(stderr)) return { status: 407 };
|
|
5277
|
+
if (NOT_A_BLOCK_PHRASES.some((phrase) => text.includes(phrase))) return {};
|
|
5278
|
+
const status = HTTP_ERROR.exec(stderr);
|
|
5279
|
+
if (status) return { status: Number(status[1]) };
|
|
5280
|
+
if (CHALLENGE_PHRASES.some((phrase) => text.includes(phrase))) return { challenge: true };
|
|
5281
|
+
return {};
|
|
5282
|
+
}
|
|
5283
|
+
|
|
5284
|
+
// src/lib/ytDlp.ts
|
|
5285
|
+
var YT_DLP_BIN = "yt-dlp";
|
|
5286
|
+
var YT_DLP_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
5287
|
+
var YT_DLP_PROBE_TIMEOUT_MS = 60 * 1e3;
|
|
5288
|
+
var YtDlpError = class extends Error {
|
|
5289
|
+
constructor(message2, stderrTail) {
|
|
5290
|
+
super(message2);
|
|
5291
|
+
this.stderrTail = stderrTail;
|
|
5292
|
+
this.name = "YtDlpError";
|
|
5293
|
+
}
|
|
5294
|
+
stderrTail;
|
|
5295
|
+
};
|
|
5296
|
+
function tail(text, maxLines) {
|
|
5297
|
+
return text.split("\n").slice(-maxLines).join("\n");
|
|
5298
|
+
}
|
|
5299
|
+
async function runYtDlp(args) {
|
|
5300
|
+
const outTemplate = path5.join(args.workDir, "out.%(ext)s");
|
|
5301
|
+
const infoPath = path5.join(args.workDir, "out.info.json");
|
|
5302
|
+
const argv = [
|
|
5303
|
+
args.url,
|
|
5304
|
+
"--no-playlist",
|
|
5305
|
+
"--no-progress",
|
|
5306
|
+
"--no-warnings",
|
|
5307
|
+
"--quiet",
|
|
5308
|
+
"--write-info-json",
|
|
5309
|
+
"-o",
|
|
5310
|
+
outTemplate
|
|
5311
|
+
];
|
|
5312
|
+
if (args.audioOnly) {
|
|
5313
|
+
argv.push("-x", "--audio-format", "m4a");
|
|
5314
|
+
} else {
|
|
5315
|
+
const format = args.maxHeight ? `bv*[height<=${args.maxHeight}]+ba/b[height<=${args.maxHeight}]/bv*+ba/b` : "bv*+ba/b";
|
|
5316
|
+
argv.push("--format", format, "--merge-output-format", "mp4");
|
|
5317
|
+
}
|
|
5318
|
+
const cookieFile = await writeVimeoCookieFile(args.vimeoCookie, args.workDir);
|
|
5319
|
+
if (cookieFile) argv.push("--cookies", cookieFile);
|
|
5320
|
+
args.onLog?.(`yt-dlp: downloading ${args.audioOnly ? "audio" : "video"}`);
|
|
5321
|
+
const { code, stderr, tier } = await runViaLadder({
|
|
5322
|
+
url: args.url,
|
|
5323
|
+
argv,
|
|
5324
|
+
timeoutMs: args.timeoutMs ?? YT_DLP_TIMEOUT_MS,
|
|
5325
|
+
onLog: args.onLog,
|
|
5326
|
+
country: args.country
|
|
5327
|
+
});
|
|
5328
|
+
if (code !== 0) {
|
|
5329
|
+
throw new YtDlpError(`yt-dlp exited ${code}`, tail(stderr, 40));
|
|
5330
|
+
}
|
|
5331
|
+
const files = await readdir(args.workDir);
|
|
5332
|
+
const wanted = args.audioOnly ? ".m4a" : ".mp4";
|
|
5333
|
+
const downloaded = files.find((f) => f.startsWith("out.") && f.endsWith(wanted));
|
|
5334
|
+
if (!downloaded) {
|
|
5335
|
+
throw new YtDlpError(`yt-dlp finished but produced no ${wanted} file (saw: ${files.join(", ")})`, "");
|
|
5336
|
+
}
|
|
5337
|
+
let info = {};
|
|
5338
|
+
try {
|
|
5339
|
+
info = JSON.parse(await readFile3(infoPath, "utf-8"));
|
|
5340
|
+
} catch {
|
|
5341
|
+
}
|
|
5342
|
+
return { filePath: path5.join(args.workDir, downloaded), info, tier };
|
|
5343
|
+
}
|
|
5344
|
+
async function probeYtDlp(args) {
|
|
5345
|
+
const argv = [
|
|
5346
|
+
args.url,
|
|
5347
|
+
"--no-playlist",
|
|
5348
|
+
"--no-warnings",
|
|
5349
|
+
"--dump-single-json",
|
|
5350
|
+
"--skip-download",
|
|
5351
|
+
// Without this the probe fails wherever the *download* would fail, which
|
|
5352
|
+
// defeats the point of probing: yt-dlp still runs format selection for
|
|
5353
|
+
// `--dump-single-json`, so a binary too old to read a site's format list
|
|
5354
|
+
// exits "Requested format is not available" and we learn nothing — measured
|
|
5355
|
+
// against a stale build, which returned full metadata the moment this flag
|
|
5356
|
+
// was added. Duration and id come from the page, not from a format.
|
|
5357
|
+
"--ignore-no-formats-error"
|
|
5358
|
+
];
|
|
5359
|
+
if (args.cookieDir) {
|
|
5360
|
+
const cookieFile = await writeVimeoCookieFile(args.vimeoCookie, args.cookieDir);
|
|
5361
|
+
if (cookieFile) argv.push("--cookies", cookieFile);
|
|
5362
|
+
}
|
|
5363
|
+
let stdout = "";
|
|
5364
|
+
const { code, stderr, tier } = await walkYtDlpLadder({
|
|
5365
|
+
url: args.url,
|
|
5366
|
+
credentials: captureProxyCredentials(),
|
|
5367
|
+
attempt: async (route) => {
|
|
5368
|
+
const result = await spawnAndWait(YT_DLP_BIN, argv, args.timeoutMs ?? YT_DLP_PROBE_TIMEOUT_MS, {
|
|
5369
|
+
extraEnv: proxyEnvFor(route),
|
|
5370
|
+
captureStdout: true
|
|
5371
|
+
});
|
|
5372
|
+
stdout = result.stdout ?? "";
|
|
5373
|
+
return result;
|
|
5374
|
+
},
|
|
5375
|
+
onLog: args.onLog,
|
|
5376
|
+
country: args.country
|
|
5377
|
+
});
|
|
5378
|
+
if (code !== 0) {
|
|
5379
|
+
throw new YtDlpError(`yt-dlp exited ${code}`, tail(stderr, 40));
|
|
5380
|
+
}
|
|
5381
|
+
let parsed;
|
|
5382
|
+
try {
|
|
5383
|
+
parsed = JSON.parse(stdout);
|
|
5384
|
+
} catch {
|
|
5385
|
+
throw new YtDlpError("yt-dlp returned metadata that could not be read", tail(stdout, 5));
|
|
5386
|
+
}
|
|
5387
|
+
if (parsed === null || typeof parsed !== "object") {
|
|
5388
|
+
throw new YtDlpError("yt-dlp returned no metadata for that link", tail(stderr, 40));
|
|
5389
|
+
}
|
|
5390
|
+
return { info: parsed, tier };
|
|
5391
|
+
}
|
|
5392
|
+
function isAudioOnly(info) {
|
|
5393
|
+
return info.vcodec === "none";
|
|
5394
|
+
}
|
|
5395
|
+
function durationSeconds(info) {
|
|
5396
|
+
const raw = info.duration;
|
|
5397
|
+
return typeof raw === "number" && Number.isFinite(raw) && raw > 0 ? raw : void 0;
|
|
5398
|
+
}
|
|
5399
|
+
function canonicalUrl(info) {
|
|
5400
|
+
const raw = info.webpage_url;
|
|
5401
|
+
return typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
5402
|
+
}
|
|
5403
|
+
async function walkYtDlpLadder(args) {
|
|
5404
|
+
const routes = plannedRoutes(args.url, args.credentials, args.country ? { country: args.country } : void 0);
|
|
5405
|
+
let last = { code: -1, stderr: "", tier: "direct" };
|
|
5406
|
+
for (const route of routes) {
|
|
5407
|
+
const tier = route.kind === "proxy" ? route.tier : "direct";
|
|
5408
|
+
if (route.kind === "proxy") args.onLog?.(`yt-dlp: retrying via ${tier}`);
|
|
5409
|
+
last = { ...await args.attempt(route), tier };
|
|
5410
|
+
if (last.code === 0) return last;
|
|
5411
|
+
if (!shouldEscalate(ytDlpBlockSignal(last.stderr))) return last;
|
|
5412
|
+
}
|
|
5413
|
+
return last;
|
|
5414
|
+
}
|
|
5415
|
+
function runViaLadder(args) {
|
|
5416
|
+
return walkYtDlpLadder({
|
|
5417
|
+
url: args.url,
|
|
5418
|
+
credentials: captureProxyCredentials(),
|
|
5419
|
+
attempt: (route) => spawnAndWait(YT_DLP_BIN, args.argv, args.timeoutMs, { extraEnv: proxyEnvFor(route) }),
|
|
5420
|
+
onLog: args.onLog,
|
|
5421
|
+
country: args.country
|
|
5422
|
+
});
|
|
5423
|
+
}
|
|
5424
|
+
function proxyEnvFor(route) {
|
|
5425
|
+
if (route.kind !== "proxy") return void 0;
|
|
5426
|
+
const uri = new URL(route.server);
|
|
5427
|
+
uri.username = encodeURIComponent(route.username);
|
|
5428
|
+
uri.password = encodeURIComponent(route.password);
|
|
5429
|
+
return {
|
|
5430
|
+
HTTP_PROXY: uri.toString(),
|
|
5431
|
+
HTTPS_PROXY: uri.toString(),
|
|
5432
|
+
http_proxy: uri.toString(),
|
|
5433
|
+
https_proxy: uri.toString()
|
|
5434
|
+
};
|
|
5435
|
+
}
|
|
5436
|
+
function spawnAndWait(bin, argv, timeoutMs, options = {}) {
|
|
5437
|
+
return new Promise((resolve, reject) => {
|
|
5438
|
+
const child = spawn(bin, argv, {
|
|
5439
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
5440
|
+
// `childEnvWith` because `spawn`'s `env` REPLACES the environment rather
|
|
5441
|
+
// than extending it — passing only the additions would strip PATH.
|
|
5442
|
+
...options.extraEnv ? { env: childEnvWith(options.extraEnv) } : {}
|
|
5443
|
+
});
|
|
5444
|
+
const stderrChunks = [];
|
|
5445
|
+
const stdoutChunks = [];
|
|
5446
|
+
let stderrLen = 0;
|
|
5447
|
+
const MAX_STDERR = 256 * 1024;
|
|
5448
|
+
child.stdout.on("data", (chunk) => {
|
|
5449
|
+
if (options.captureStdout) stdoutChunks.push(chunk);
|
|
5450
|
+
});
|
|
5451
|
+
child.stderr.on("data", (chunk) => {
|
|
5452
|
+
if (stderrLen < MAX_STDERR) {
|
|
5453
|
+
stderrChunks.push(chunk);
|
|
5454
|
+
stderrLen += chunk.length;
|
|
5455
|
+
}
|
|
5456
|
+
});
|
|
5457
|
+
const timer = setTimeout(() => {
|
|
5458
|
+
child.kill("SIGKILL");
|
|
5459
|
+
reject(new Error(`${bin} timed out after ${timeoutMs}ms`));
|
|
5460
|
+
}, timeoutMs);
|
|
5461
|
+
child.on("error", (err) => {
|
|
5462
|
+
clearTimeout(timer);
|
|
5463
|
+
reject(new Error(`failed to spawn "${bin}" \u2014 ${err.message}`));
|
|
5464
|
+
});
|
|
5465
|
+
child.on("close", (code) => {
|
|
5466
|
+
clearTimeout(timer);
|
|
5467
|
+
resolve({
|
|
5468
|
+
code: code ?? -1,
|
|
5469
|
+
stderr: Buffer.concat(stderrChunks).toString("utf-8"),
|
|
5470
|
+
...options.captureStdout ? { stdout: Buffer.concat(stdoutChunks).toString("utf-8") } : {}
|
|
5471
|
+
});
|
|
5472
|
+
});
|
|
5473
|
+
});
|
|
5474
|
+
}
|
|
5475
|
+
|
|
5234
5476
|
// src/engine/schema/refs.ts
|
|
5235
5477
|
import { z as z4 } from "zod";
|
|
5236
5478
|
|
|
@@ -5426,25 +5668,6 @@ function mapClientError(ctx, e) {
|
|
|
5426
5668
|
return new NodeExecutionError(ctx.nodeId, ctx.nodeType, { kind: "local", cause: e });
|
|
5427
5669
|
}
|
|
5428
5670
|
|
|
5429
|
-
// src/engine/nodes/ytDlpSignal.ts
|
|
5430
|
-
var HTTP_ERROR = /HTTP Error (\d{3})/i;
|
|
5431
|
-
var CHALLENGE_PHRASES = [
|
|
5432
|
-
"sign in to confirm you're not a bot",
|
|
5433
|
-
"sign in to confirm you\u2019re not a bot",
|
|
5434
|
-
"confirm you are not a bot",
|
|
5435
|
-
"unusual traffic",
|
|
5436
|
-
"captcha"
|
|
5437
|
-
];
|
|
5438
|
-
var NOT_A_BLOCK_PHRASES = ["private video", "video unavailable", "members-only", "this video is available to"];
|
|
5439
|
-
function ytDlpBlockSignal(stderr) {
|
|
5440
|
-
const text = stderr.toLowerCase();
|
|
5441
|
-
if (NOT_A_BLOCK_PHRASES.some((phrase) => text.includes(phrase))) return {};
|
|
5442
|
-
const status = HTTP_ERROR.exec(stderr);
|
|
5443
|
-
if (status) return { status: Number(status[1]) };
|
|
5444
|
-
if (CHALLENGE_PHRASES.some((phrase) => text.includes(phrase))) return { challenge: true };
|
|
5445
|
-
return {};
|
|
5446
|
-
}
|
|
5447
|
-
|
|
5448
5671
|
// src/engine/nodes/ingest.ts
|
|
5449
5672
|
var execFile = promisify(execFileCb);
|
|
5450
5673
|
var ExpectEnum = z5.enum(["image", "video", "audio", "text", "json", "font"]);
|
|
@@ -5617,8 +5840,8 @@ function resolveLocalPath(input) {
|
|
|
5617
5840
|
`ingest: ~ path expansion is not supported (got "${input}"). Use an absolute path or a cwd-relative path.`
|
|
5618
5841
|
);
|
|
5619
5842
|
}
|
|
5620
|
-
if (
|
|
5621
|
-
return
|
|
5843
|
+
if (path6.isAbsolute(input)) return input;
|
|
5844
|
+
return path6.resolve(process.cwd(), input);
|
|
5622
5845
|
}
|
|
5623
5846
|
var EXT_TO_MIME = {
|
|
5624
5847
|
png: "image/png",
|
|
@@ -5666,7 +5889,7 @@ function sniffSvg(buf) {
|
|
|
5666
5889
|
return head.startsWith("<?xml") ? head.includes("<svg") : head.startsWith("<svg");
|
|
5667
5890
|
}
|
|
5668
5891
|
function inferMimeFromPath(absPath, sniffBytes) {
|
|
5669
|
-
const ext =
|
|
5892
|
+
const ext = path6.extname(absPath).slice(1).toLowerCase();
|
|
5670
5893
|
const fromExt = EXT_TO_MIME[ext];
|
|
5671
5894
|
if (fromExt) return fromExt;
|
|
5672
5895
|
const fromBytes = sniffImageMime(sniffBytes);
|
|
@@ -5805,7 +6028,7 @@ async function execLocalFile(params, ctx) {
|
|
|
5805
6028
|
}
|
|
5806
6029
|
let bytes;
|
|
5807
6030
|
try {
|
|
5808
|
-
bytes = await
|
|
6031
|
+
bytes = await readFile4(absPath);
|
|
5809
6032
|
} catch (e) {
|
|
5810
6033
|
if (e.code === "EACCES") {
|
|
5811
6034
|
throw localExecError(ctx, `permission_denied: ${absPath}`);
|
|
@@ -5856,7 +6079,7 @@ function localFileMetadata(args) {
|
|
|
5856
6079
|
strategy: "local_file",
|
|
5857
6080
|
ingested_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5858
6081
|
file_size: args.fileSize,
|
|
5859
|
-
original_filename:
|
|
6082
|
+
original_filename: path6.basename(args.absPath),
|
|
5860
6083
|
...args.rasterizedFrom ? { rasterized_from: args.rasterizedFrom } : {},
|
|
5861
6084
|
...args.durationMs !== void 0 ? { duration_ms: args.durationMs } : {}
|
|
5862
6085
|
};
|
|
@@ -5865,8 +6088,6 @@ function withProbedDuration(ref, durationMs) {
|
|
|
5865
6088
|
if (durationMs === void 0 || ref.kind !== "video") return ref;
|
|
5866
6089
|
return { ...ref, duration_ms: durationMs };
|
|
5867
6090
|
}
|
|
5868
|
-
var YT_DLP_BIN = "yt-dlp";
|
|
5869
|
-
var YT_DLP_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
5870
6091
|
var YT_DLP_MIME = {
|
|
5871
6092
|
image: "image/png",
|
|
5872
6093
|
video: "video/mp4",
|
|
@@ -5880,9 +6101,9 @@ async function execYtDlp(params, ctx) {
|
|
|
5880
6101
|
if (params.expect !== "video" && params.expect !== "audio") {
|
|
5881
6102
|
throw new Error(`ingest: yt_dlp only handles video/audio, got expect=${params.expect}`);
|
|
5882
6103
|
}
|
|
5883
|
-
const workDir = await mkdtemp(
|
|
6104
|
+
const workDir = await mkdtemp(path6.join(tmpdir(), "ingest-yt-"));
|
|
5884
6105
|
try {
|
|
5885
|
-
const { filePath, info } = await
|
|
6106
|
+
const { filePath, info } = await runYtDlpForNode({
|
|
5886
6107
|
url: params.url,
|
|
5887
6108
|
audioOnly: params.expect === "audio",
|
|
5888
6109
|
workDir,
|
|
@@ -5895,7 +6116,7 @@ async function execYtDlp(params, ctx) {
|
|
|
5895
6116
|
`file_too_large: yt-dlp output for ${params.url} is ${downloadedStats.size} bytes (limit ${MAX_ASSET_BYTES})`
|
|
5896
6117
|
);
|
|
5897
6118
|
}
|
|
5898
|
-
const bytes = await
|
|
6119
|
+
const bytes = await readFile4(filePath);
|
|
5899
6120
|
const kind = params.expect;
|
|
5900
6121
|
const mime = YT_DLP_MIME[kind];
|
|
5901
6122
|
const metadata = buildYtDlpMetadata(params.url, info);
|
|
@@ -5906,69 +6127,24 @@ async function execYtDlp(params, ctx) {
|
|
|
5906
6127
|
});
|
|
5907
6128
|
}
|
|
5908
6129
|
}
|
|
5909
|
-
async function
|
|
5910
|
-
const routes = plannedRoutes(url, captureProxyCredentials());
|
|
5911
|
-
let last = { code: -1, stderr: "" };
|
|
5912
|
-
for (const route of routes) {
|
|
5913
|
-
if (route.kind === "proxy") ctx.log(`ingest: retrying yt-dlp via ${route.tier}`);
|
|
5914
|
-
last = await spawnAndWait(YT_DLP_BIN, argv, YT_DLP_TIMEOUT_MS, proxyEnvFor(route));
|
|
5915
|
-
if (last.code === 0) return last;
|
|
5916
|
-
if (!shouldEscalate(ytDlpBlockSignal(last.stderr))) return last;
|
|
5917
|
-
}
|
|
5918
|
-
return last;
|
|
5919
|
-
}
|
|
5920
|
-
function proxyEnvFor(route) {
|
|
5921
|
-
if (route.kind !== "proxy") return void 0;
|
|
5922
|
-
const uri = new URL(route.server);
|
|
5923
|
-
uri.username = encodeURIComponent(route.username);
|
|
5924
|
-
uri.password = encodeURIComponent(route.password);
|
|
5925
|
-
return {
|
|
5926
|
-
HTTP_PROXY: uri.toString(),
|
|
5927
|
-
HTTPS_PROXY: uri.toString(),
|
|
5928
|
-
http_proxy: uri.toString(),
|
|
5929
|
-
https_proxy: uri.toString()
|
|
5930
|
-
};
|
|
5931
|
-
}
|
|
5932
|
-
async function runYtDlp(args) {
|
|
5933
|
-
const outTemplate = path4.join(args.workDir, "out.%(ext)s");
|
|
5934
|
-
const infoPath = path4.join(args.workDir, "out.info.json");
|
|
5935
|
-
const argv = [
|
|
5936
|
-
args.url,
|
|
5937
|
-
"--no-playlist",
|
|
5938
|
-
"--no-progress",
|
|
5939
|
-
"--no-warnings",
|
|
5940
|
-
"--quiet",
|
|
5941
|
-
"--write-info-json",
|
|
5942
|
-
"-o",
|
|
5943
|
-
outTemplate
|
|
5944
|
-
];
|
|
5945
|
-
if (args.audioOnly) {
|
|
5946
|
-
argv.push("-x", "--audio-format", "m4a");
|
|
5947
|
-
} else {
|
|
5948
|
-
argv.push("--format", "bv*+ba/b", "--merge-output-format", "mp4");
|
|
5949
|
-
}
|
|
5950
|
-
args.ctx.log(`ingest: spawning yt-dlp (${args.audioOnly ? "audio" : "video"})`);
|
|
5951
|
-
const { code, stderr } = await runYtDlpViaLadder(args.url, argv, args.ctx);
|
|
5952
|
-
if (code !== 0) {
|
|
5953
|
-
throw new NodeExecutionError(args.ctx.nodeId, args.ctx.nodeType, {
|
|
5954
|
-
kind: "local",
|
|
5955
|
-
cause: new Error(`yt-dlp exited ${code}
|
|
5956
|
-
${tail(stderr, 40)}`)
|
|
5957
|
-
});
|
|
5958
|
-
}
|
|
5959
|
-
const files = await readdir(args.workDir);
|
|
5960
|
-
const wanted = args.audioOnly ? ".m4a" : ".mp4";
|
|
5961
|
-
const downloaded = files.find((f) => f.startsWith("out.") && f.endsWith(wanted));
|
|
5962
|
-
if (!downloaded) {
|
|
5963
|
-
throw new Error(`ingest: yt-dlp finished but no ${wanted} file in ${args.workDir} (saw: ${files.join(", ")})`);
|
|
5964
|
-
}
|
|
5965
|
-
let info = {};
|
|
6130
|
+
async function runYtDlpForNode(args) {
|
|
5966
6131
|
try {
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
6132
|
+
return await runYtDlp({
|
|
6133
|
+
url: args.url,
|
|
6134
|
+
audioOnly: args.audioOnly,
|
|
6135
|
+
workDir: args.workDir,
|
|
6136
|
+
onLog: (message2) => args.ctx.log(`ingest: ${message2}`)
|
|
6137
|
+
});
|
|
6138
|
+
} catch (error) {
|
|
6139
|
+
if (error instanceof YtDlpError) {
|
|
6140
|
+
throw new NodeExecutionError(args.ctx.nodeId, args.ctx.nodeType, {
|
|
6141
|
+
kind: "local",
|
|
6142
|
+
cause: new Error(error.stderrTail ? `${error.message}
|
|
6143
|
+
${error.stderrTail}` : error.message)
|
|
6144
|
+
});
|
|
6145
|
+
}
|
|
6146
|
+
throw error;
|
|
5970
6147
|
}
|
|
5971
|
-
return { filePath: path4.join(args.workDir, downloaded), info };
|
|
5972
6148
|
}
|
|
5973
6149
|
function buildYtDlpMetadata(sourceUrl, info) {
|
|
5974
6150
|
const out = {
|
|
@@ -5995,41 +6171,6 @@ async function uploadAndIngest(args) {
|
|
|
5995
6171
|
});
|
|
5996
6172
|
return { ...localRef, url: publicUrl };
|
|
5997
6173
|
}
|
|
5998
|
-
function spawnAndWait(bin, argv, timeoutMs, env) {
|
|
5999
|
-
return new Promise((resolve, reject) => {
|
|
6000
|
-
const child = spawn(bin, argv, {
|
|
6001
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
6002
|
-
...env ? { env: childEnvWith(env) } : {}
|
|
6003
|
-
});
|
|
6004
|
-
const stderrChunks = [];
|
|
6005
|
-
let stderrLen = 0;
|
|
6006
|
-
const MAX_STDERR = 256 * 1024;
|
|
6007
|
-
child.stdout.on("data", () => {
|
|
6008
|
-
});
|
|
6009
|
-
child.stderr.on("data", (chunk) => {
|
|
6010
|
-
if (stderrLen < MAX_STDERR) {
|
|
6011
|
-
stderrChunks.push(chunk);
|
|
6012
|
-
stderrLen += chunk.length;
|
|
6013
|
-
}
|
|
6014
|
-
});
|
|
6015
|
-
const timer = setTimeout(() => {
|
|
6016
|
-
child.kill("SIGKILL");
|
|
6017
|
-
reject(new Error(`ingest: ${bin} timed out after ${timeoutMs}ms`));
|
|
6018
|
-
}, timeoutMs);
|
|
6019
|
-
child.on("error", (err) => {
|
|
6020
|
-
clearTimeout(timer);
|
|
6021
|
-
reject(new Error(`ingest: failed to spawn "${bin}" \u2014 ${err.message}`));
|
|
6022
|
-
});
|
|
6023
|
-
child.on("close", (code) => {
|
|
6024
|
-
clearTimeout(timer);
|
|
6025
|
-
resolve({ code: code ?? -1, stderr: Buffer.concat(stderrChunks).toString("utf8") });
|
|
6026
|
-
});
|
|
6027
|
-
});
|
|
6028
|
-
}
|
|
6029
|
-
function tail(text, maxLines) {
|
|
6030
|
-
const lines = text.split("\n");
|
|
6031
|
-
return lines.slice(-maxLines).join("\n");
|
|
6032
|
-
}
|
|
6033
6174
|
async function ytDlpVersion() {
|
|
6034
6175
|
try {
|
|
6035
6176
|
const { stdout } = await execFile(YT_DLP_BIN, ["--version"], { encoding: "utf-8", maxBuffer: 64 * 1024 });
|
|
@@ -6060,9 +6201,9 @@ import { z as z6 } from "zod";
|
|
|
6060
6201
|
|
|
6061
6202
|
// src/engine/nodes/local/lib/cli-runner.ts
|
|
6062
6203
|
import { execFile as execFileCb2, spawn as spawn2 } from "child_process";
|
|
6063
|
-
import { copyFile as copyFile2, mkdtemp as mkdtemp2, readFile as
|
|
6204
|
+
import { copyFile as copyFile2, mkdtemp as mkdtemp2, readFile as readFile5, rm as rm2, stat as stat3 } from "fs/promises";
|
|
6064
6205
|
import { tmpdir as tmpdir2 } from "os";
|
|
6065
|
-
import
|
|
6206
|
+
import path7 from "path";
|
|
6066
6207
|
import { promisify as promisify2 } from "util";
|
|
6067
6208
|
var execFile2 = promisify2(execFileCb2);
|
|
6068
6209
|
var DEFAULT_TIMEOUT_MS2 = 10 * 60 * 1e3;
|
|
@@ -6089,7 +6230,7 @@ function mimeForExt(ext) {
|
|
|
6089
6230
|
}
|
|
6090
6231
|
function extForAssetRef(ref) {
|
|
6091
6232
|
if (ref.path) {
|
|
6092
|
-
const e =
|
|
6233
|
+
const e = path7.extname(ref.path);
|
|
6093
6234
|
if (e) return e;
|
|
6094
6235
|
}
|
|
6095
6236
|
const reverse = {
|
|
@@ -6111,14 +6252,14 @@ function planArrayInputSlot(tmpDir, slot, values, lookup, stagedInputs) {
|
|
|
6111
6252
|
const ref = values[i];
|
|
6112
6253
|
if (!ref) continue;
|
|
6113
6254
|
if (!ref.path) throw new Error(`cli-runner: inputs.${slot}[${i}] has no local path`);
|
|
6114
|
-
const dest =
|
|
6255
|
+
const dest = path7.join(tmpDir, `in_${slot}_${i}${extForAssetRef(ref)}`);
|
|
6115
6256
|
stagedInputs.push({ srcPath: ref.path, destPath: dest });
|
|
6116
6257
|
lookup.set(`in.${slot}.${i}`, dest);
|
|
6117
6258
|
}
|
|
6118
6259
|
}
|
|
6119
6260
|
function planSingleInputSlot(tmpDir, slot, ref, lookup, stagedInputs) {
|
|
6120
6261
|
if (!ref.path) throw new Error(`cli-runner: inputs.${slot} has no local path`);
|
|
6121
|
-
const dest =
|
|
6262
|
+
const dest = path7.join(tmpDir, `in_${slot}${extForAssetRef(ref)}`);
|
|
6122
6263
|
stagedInputs.push({ srcPath: ref.path, destPath: dest });
|
|
6123
6264
|
lookup.set(`in.${slot}`, dest);
|
|
6124
6265
|
}
|
|
@@ -6126,7 +6267,7 @@ function planOutputs(tmpDir, outputs, lookup) {
|
|
|
6126
6267
|
const outputPaths = [];
|
|
6127
6268
|
for (const [name, spec] of Object.entries(outputs)) {
|
|
6128
6269
|
const ext = spec.ext.startsWith(".") ? spec.ext : `.${spec.ext}`;
|
|
6129
|
-
const absPath =
|
|
6270
|
+
const absPath = path7.join(tmpDir, `out_${name}${ext}`);
|
|
6130
6271
|
outputPaths.push({ name, absPath, spec });
|
|
6131
6272
|
lookup.set(`out.${name}`, absPath);
|
|
6132
6273
|
}
|
|
@@ -6167,8 +6308,8 @@ function rejectRawPaths(substituted, original, stagingDir) {
|
|
|
6167
6308
|
throw new Error(`cli-runner: home-relative path "${original}" not allowed in args.`);
|
|
6168
6309
|
}
|
|
6169
6310
|
if (substituted.startsWith("/")) {
|
|
6170
|
-
const resolved =
|
|
6171
|
-
if (!resolved.startsWith(`${stagingDir}${
|
|
6311
|
+
const resolved = path7.resolve(substituted);
|
|
6312
|
+
if (!resolved.startsWith(`${stagingDir}${path7.sep}`) && resolved !== stagingDir) {
|
|
6172
6313
|
throw new Error(
|
|
6173
6314
|
`cli-runner: raw filesystem path "${original}" not allowed \u2014 declare an input slot and use {{in.<slot>}} instead.`
|
|
6174
6315
|
);
|
|
@@ -6218,7 +6359,7 @@ function tailLines(text, maxLines) {
|
|
|
6218
6359
|
}
|
|
6219
6360
|
async function runCli(opts) {
|
|
6220
6361
|
const { bin, args, inputs, outputs, ctx, timeoutMs = DEFAULT_TIMEOUT_MS2 } = opts;
|
|
6221
|
-
const tmpDir = await mkdtemp2(
|
|
6362
|
+
const tmpDir = await mkdtemp2(path7.join(tmpdir2(), `cli-${bin.replace(/[^a-z0-9]/gi, "")}-`));
|
|
6222
6363
|
try {
|
|
6223
6364
|
const { lookup, stagedInputs, outputPaths } = planPlaceholders(tmpDir, inputs, outputs);
|
|
6224
6365
|
await stageInputs(stagedInputs);
|
|
@@ -6240,7 +6381,7 @@ ${tailLines(stderr, 40)}`);
|
|
|
6240
6381
|
if (!s?.isFile() || s.size === 0) {
|
|
6241
6382
|
throw new Error(`cli-runner: declared output "${name}" missing or empty at ${absPath}`);
|
|
6242
6383
|
}
|
|
6243
|
-
const bytes = await
|
|
6384
|
+
const bytes = await readFile5(absPath);
|
|
6244
6385
|
const ref = await ctx.assets.ingestBytes({
|
|
6245
6386
|
bytes: Buffer.from(bytes),
|
|
6246
6387
|
kind: spec.kind,
|
|
@@ -6495,20 +6636,20 @@ var ffmpegNode = defineNode({
|
|
|
6495
6636
|
});
|
|
6496
6637
|
|
|
6497
6638
|
// src/engine/nodes/local/fontSpecimen.ts
|
|
6498
|
-
import { mkdtemp as mkdtemp3, rm as rm3, writeFile as
|
|
6639
|
+
import { mkdtemp as mkdtemp3, rm as rm3, writeFile as writeFile4 } from "fs/promises";
|
|
6499
6640
|
import { createRequire } from "module";
|
|
6500
6641
|
import { tmpdir as tmpdir3 } from "os";
|
|
6501
|
-
import
|
|
6642
|
+
import path9 from "path";
|
|
6502
6643
|
import { z as z9 } from "zod";
|
|
6503
6644
|
|
|
6504
6645
|
// src/engine/nodes/local/lib/assets.ts
|
|
6505
|
-
import { copyFile as copyFile3, readFile as
|
|
6506
|
-
import
|
|
6646
|
+
import { copyFile as copyFile3, readFile as readFile6 } from "fs/promises";
|
|
6647
|
+
import path8 from "path";
|
|
6507
6648
|
async function stageAsset(ref, destDir, filename) {
|
|
6508
6649
|
if (!ref.path) {
|
|
6509
6650
|
throw new Error(`stageAsset: ref (${ref.kind}/${ref.mime}) has no local path`);
|
|
6510
6651
|
}
|
|
6511
|
-
const dest =
|
|
6652
|
+
const dest = path8.join(destDir, filename);
|
|
6512
6653
|
await copyFile3(ref.path, dest);
|
|
6513
6654
|
return dest;
|
|
6514
6655
|
}
|
|
@@ -6517,7 +6658,7 @@ async function refToUrl(ref) {
|
|
|
6517
6658
|
if (!ref.path) {
|
|
6518
6659
|
throw new Error("refToUrl: AssetRef has neither url nor path");
|
|
6519
6660
|
}
|
|
6520
|
-
const bytes = await
|
|
6661
|
+
const bytes = await readFile6(ref.path);
|
|
6521
6662
|
return `data:${ref.mime};base64,${bytes.toString("base64")}`;
|
|
6522
6663
|
}
|
|
6523
6664
|
var ASSET_KINDS = /* @__PURE__ */ new Set(["image", "video", "audio", "json", "text", "font"]);
|
|
@@ -6591,12 +6732,12 @@ var fontSpecimenNode = defineNode({
|
|
|
6591
6732
|
outputKinds: { image: "image" },
|
|
6592
6733
|
cost: () => ({ credits: 0, seconds_estimate: 5 }),
|
|
6593
6734
|
async execute({ inputs, params, ctx }) {
|
|
6594
|
-
const tmp = await mkdtemp3(
|
|
6735
|
+
const tmp = await mkdtemp3(path9.join(tmpdir3(), "font-specimen-"));
|
|
6595
6736
|
try {
|
|
6596
6737
|
const fontFilename = `font.${extForMime(inputs.font.mime)}`;
|
|
6597
6738
|
await stageAsset(inputs.font, tmp, fontFilename);
|
|
6598
|
-
const entryPath =
|
|
6599
|
-
await
|
|
6739
|
+
const entryPath = path9.join(tmp, "index.html");
|
|
6740
|
+
await writeFile4(entryPath, buildSpecimenHtml(params, fontFilename), "utf-8");
|
|
6600
6741
|
ctx.log(`rendering specimen (${params.font_size}px, ${params.text.split("\n").length} lines)`);
|
|
6601
6742
|
const pwSpecifier = ["play", "wright"].join("");
|
|
6602
6743
|
const playwright = require_(pwSpecifier);
|
|
@@ -6679,16 +6820,16 @@ var fontSpecimenNode = defineNode({
|
|
|
6679
6820
|
|
|
6680
6821
|
// src/engine/nodes/local/hyperframe.ts
|
|
6681
6822
|
import { execFile as execFile4 } from "child_process";
|
|
6682
|
-
import { copyFile as copyFile4, mkdtemp as mkdtemp4, readFile as
|
|
6823
|
+
import { copyFile as copyFile4, mkdtemp as mkdtemp4, readFile as readFile10, rm as rm4, stat as stat5, writeFile as writeFile6 } from "fs/promises";
|
|
6683
6824
|
import { createRequire as createRequire2 } from "module";
|
|
6684
6825
|
import { cpus, tmpdir as tmpdir4 } from "os";
|
|
6685
|
-
import
|
|
6826
|
+
import path14 from "path";
|
|
6686
6827
|
import { promisify as promisify4 } from "util";
|
|
6687
6828
|
import { z as z11 } from "zod";
|
|
6688
6829
|
|
|
6689
6830
|
// src/engine/engine/composition-hash.ts
|
|
6690
|
-
import { readdir as readdir2, readFile as
|
|
6691
|
-
import
|
|
6831
|
+
import { readdir as readdir2, readFile as readFile7, stat as stat4 } from "fs/promises";
|
|
6832
|
+
import path10 from "path";
|
|
6692
6833
|
var SKIP_DIRS = /* @__PURE__ */ new Set([".cache", ".git", "node_modules", "dist", "build", ".next", ".turbo"]);
|
|
6693
6834
|
function isSkippedName(name) {
|
|
6694
6835
|
if (name.startsWith(".")) return true;
|
|
@@ -6705,23 +6846,23 @@ async function collectFiles(root, current) {
|
|
|
6705
6846
|
const names = await readdir2(current);
|
|
6706
6847
|
for (const name of names) {
|
|
6707
6848
|
if (isSkippedName(name)) continue;
|
|
6708
|
-
const abs =
|
|
6849
|
+
const abs = path10.join(current, name);
|
|
6709
6850
|
const s = await stat4(abs);
|
|
6710
6851
|
if (s.isDirectory()) {
|
|
6711
6852
|
out.push(...await collectFiles(root, abs));
|
|
6712
6853
|
continue;
|
|
6713
6854
|
}
|
|
6714
6855
|
if (!s.isFile()) continue;
|
|
6715
|
-
const bytes = await
|
|
6716
|
-
const relPath =
|
|
6856
|
+
const bytes = await readFile7(abs);
|
|
6857
|
+
const relPath = path10.relative(root, abs).split(path10.sep).join("/");
|
|
6717
6858
|
out.push({ relPath, contentSha: sha256Hex(bytes) });
|
|
6718
6859
|
}
|
|
6719
6860
|
return out;
|
|
6720
6861
|
}
|
|
6721
6862
|
|
|
6722
6863
|
// src/engine/engine/composition-meta.ts
|
|
6723
|
-
import { readFile as
|
|
6724
|
-
import
|
|
6864
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
6865
|
+
import path11 from "path";
|
|
6725
6866
|
import { z as z10 } from "zod";
|
|
6726
6867
|
var InputKind = z10.enum(["video", "image", "audio", "json"]);
|
|
6727
6868
|
var InputSpec = z10.object({
|
|
@@ -6798,10 +6939,10 @@ var CompositionMetaSchema = z10.object({
|
|
|
6798
6939
|
params: z10.record(z10.string(), ParamSpec).default({})
|
|
6799
6940
|
}).strict();
|
|
6800
6941
|
async function loadCompositionMeta(compositionDir) {
|
|
6801
|
-
const metaPath =
|
|
6942
|
+
const metaPath = path11.join(compositionDir, "meta.json");
|
|
6802
6943
|
let raw;
|
|
6803
6944
|
try {
|
|
6804
|
-
raw = await
|
|
6945
|
+
raw = await readFile8(metaPath, "utf-8");
|
|
6805
6946
|
} catch (e) {
|
|
6806
6947
|
throw new Error(`composition meta: cannot read ${metaPath} (${e.message})`);
|
|
6807
6948
|
}
|
|
@@ -6879,8 +7020,8 @@ function defaultFilenameForInput(key, kind) {
|
|
|
6879
7020
|
|
|
6880
7021
|
// src/engine/nodes/local/lib/hyperframe-check.ts
|
|
6881
7022
|
import { execFile as execFile3 } from "child_process";
|
|
6882
|
-
import { readFile as
|
|
6883
|
-
import
|
|
7023
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
7024
|
+
import path12 from "path";
|
|
6884
7025
|
import { promisify as promisify3 } from "util";
|
|
6885
7026
|
var execFileAsync = promisify3(execFile3);
|
|
6886
7027
|
var NEVER_BLOCK = [
|
|
@@ -7032,7 +7173,7 @@ ${detail}`);
|
|
|
7032
7173
|
}
|
|
7033
7174
|
let indexHtml = "";
|
|
7034
7175
|
try {
|
|
7035
|
-
indexHtml = await
|
|
7176
|
+
indexHtml = await readFile9(path12.join(dir, "index.html"), "utf-8");
|
|
7036
7177
|
} catch {
|
|
7037
7178
|
indexHtml = "";
|
|
7038
7179
|
}
|
|
@@ -7096,11 +7237,11 @@ ${stderr.slice(0, 1500)}`;
|
|
|
7096
7237
|
}
|
|
7097
7238
|
|
|
7098
7239
|
// src/engine/nodes/local/lib/hyperframe-meta.ts
|
|
7099
|
-
import { writeFile as
|
|
7100
|
-
import
|
|
7240
|
+
import { writeFile as writeFile5 } from "fs/promises";
|
|
7241
|
+
import path13 from "path";
|
|
7101
7242
|
async function ensureHyperframesMetaJson(tmp, nodeId, meta, duration) {
|
|
7102
|
-
const metaPath =
|
|
7103
|
-
await
|
|
7243
|
+
const metaPath = path13.join(tmp, "meta.json");
|
|
7244
|
+
await writeFile5(
|
|
7104
7245
|
metaPath,
|
|
7105
7246
|
JSON.stringify(
|
|
7106
7247
|
{
|
|
@@ -7199,7 +7340,7 @@ var hyperframeRenderNode = defineNode({
|
|
|
7199
7340
|
const compositionDir = await resolveCompositionDir(params.composition);
|
|
7200
7341
|
const meta = await loadCompositionMeta(compositionDir);
|
|
7201
7342
|
const compositionParams = validateAndParseDynamicParams(meta, params);
|
|
7202
|
-
const tmp = await mkdtemp4(
|
|
7343
|
+
const tmp = await mkdtemp4(path14.join(tmpdir4(), "hf-render-"));
|
|
7203
7344
|
try {
|
|
7204
7345
|
await copyComposition(compositionDir, tmp);
|
|
7205
7346
|
await vendorGsap(tmp, ctx);
|
|
@@ -7209,9 +7350,9 @@ var hyperframeRenderNode = defineNode({
|
|
|
7209
7350
|
await substituteCompositionFiles(tmp, substitutionValues);
|
|
7210
7351
|
await ensureHyperframesMetaJson(tmp, ctx.nodeId, meta, duration);
|
|
7211
7352
|
await runHyperframesCheck({ dir: tmp, nodeId: "hyperframe_render", ctx, timeoutMs: params.timeout_ms });
|
|
7212
|
-
const outputPath =
|
|
7353
|
+
const outputPath = path14.join(tmp, `output.${params.format}`);
|
|
7213
7354
|
await runRender({ tmp, outputPath, params, meta, ctx });
|
|
7214
|
-
const bytes = await
|
|
7355
|
+
const bytes = await readFile10(outputPath);
|
|
7215
7356
|
ctx.log(`rendered ${bytes.length} bytes`);
|
|
7216
7357
|
const ref = await ctx.assets.ingestBytes({
|
|
7217
7358
|
bytes: Buffer.from(bytes),
|
|
@@ -7233,10 +7374,10 @@ var hyperframeRenderNode = defineNode({
|
|
|
7233
7374
|
}
|
|
7234
7375
|
});
|
|
7235
7376
|
async function resolveCompositionDir(composition) {
|
|
7236
|
-
const compositionPath =
|
|
7377
|
+
const compositionPath = path14.isAbsolute(composition) ? composition : path14.resolve(process.cwd(), composition);
|
|
7237
7378
|
const s = await stat5(compositionPath);
|
|
7238
7379
|
if (s.isDirectory()) return compositionPath;
|
|
7239
|
-
return
|
|
7380
|
+
return path14.dirname(compositionPath);
|
|
7240
7381
|
}
|
|
7241
7382
|
async function validateComposition(rawParams) {
|
|
7242
7383
|
const issues = await validateCompositionParams(rawParams);
|
|
@@ -7318,7 +7459,7 @@ async function copyComposition(srcDir, destDir) {
|
|
|
7318
7459
|
await cp(srcDir, destDir, {
|
|
7319
7460
|
recursive: true,
|
|
7320
7461
|
filter: (src) => {
|
|
7321
|
-
const name =
|
|
7462
|
+
const name = path14.basename(src);
|
|
7322
7463
|
if (name === ".cache" || name === "node_modules" || name === ".git") return false;
|
|
7323
7464
|
return true;
|
|
7324
7465
|
}
|
|
@@ -7327,7 +7468,7 @@ async function copyComposition(srcDir, destDir) {
|
|
|
7327
7468
|
async function vendorGsap(tmp, ctx) {
|
|
7328
7469
|
try {
|
|
7329
7470
|
const gsapMin = require_2.resolve("gsap/dist/gsap.min.js");
|
|
7330
|
-
await copyFile4(gsapMin,
|
|
7471
|
+
await copyFile4(gsapMin, path14.join(tmp, "gsap.min.js"));
|
|
7331
7472
|
} catch (e) {
|
|
7332
7473
|
ctx.log(`warning: could not vendor gsap.min.js (${e.message}); compositions must self-supply`);
|
|
7333
7474
|
}
|
|
@@ -7342,7 +7483,7 @@ async function stageInputs2(tmp, inputs, meta, ctx) {
|
|
|
7342
7483
|
await stageAsset(ref, tmp, filename);
|
|
7343
7484
|
ctx.log(`staged ${spec.kind} \u2192 ${filename}`);
|
|
7344
7485
|
if (spec.kind === "video" && primaryDuration === null) {
|
|
7345
|
-
primaryDuration = await probeDurationSeconds(
|
|
7486
|
+
primaryDuration = await probeDurationSeconds(path14.join(tmp, filename));
|
|
7346
7487
|
}
|
|
7347
7488
|
}
|
|
7348
7489
|
return primaryDuration;
|
|
@@ -7388,15 +7529,15 @@ function coerceImageParam(value) {
|
|
|
7388
7529
|
throw new Error("hyperframe_render: image param must be a URL string or AssetRef");
|
|
7389
7530
|
}
|
|
7390
7531
|
async function substituteCompositionFiles(tmp, values) {
|
|
7391
|
-
const entryPath =
|
|
7392
|
-
const original = await
|
|
7532
|
+
const entryPath = path14.join(tmp, "index.html");
|
|
7533
|
+
const original = await readFile10(entryPath, "utf-8");
|
|
7393
7534
|
const { output, missing } = substituteVariables(original, values);
|
|
7394
7535
|
if (missing.length > 0) {
|
|
7395
7536
|
throw new Error(
|
|
7396
7537
|
`hyperframe_render: composition references undefined variables: ${missing.map((m) => `{{${m}}}`).join(", ")}. Add to params or to meta.json's params with a default.`
|
|
7397
7538
|
);
|
|
7398
7539
|
}
|
|
7399
|
-
await
|
|
7540
|
+
await writeFile6(entryPath, output, "utf-8");
|
|
7400
7541
|
}
|
|
7401
7542
|
var MAX_WORKERS = 4;
|
|
7402
7543
|
function workerCount() {
|
|
@@ -7405,7 +7546,7 @@ function workerCount() {
|
|
|
7405
7546
|
async function runRender(opts) {
|
|
7406
7547
|
const { tmp, outputPath, params, meta, ctx } = opts;
|
|
7407
7548
|
const args = buildRenderArgs(tmp, outputPath, meta, params.format);
|
|
7408
|
-
ctx.log(`rendering ${meta.width}x${meta.height}@${meta.fps}fps ${params.format} from ${
|
|
7549
|
+
ctx.log(`rendering ${meta.width}x${meta.height}@${meta.fps}fps ${params.format} from ${path14.basename(tmp)}`);
|
|
7409
7550
|
try {
|
|
7410
7551
|
await execFileAsync2("npx", args, { timeout: params.timeout_ms, maxBuffer: 64 * 1024 * 1024 });
|
|
7411
7552
|
} catch (e) {
|
|
@@ -7449,10 +7590,10 @@ async function probeDurationSeconds(filePath) {
|
|
|
7449
7590
|
|
|
7450
7591
|
// src/engine/nodes/local/hyperframe-snapshot.ts
|
|
7451
7592
|
import { execFile as execFile5 } from "child_process";
|
|
7452
|
-
import { copyFile as copyFile5, mkdtemp as mkdtemp5, readFile as
|
|
7593
|
+
import { copyFile as copyFile5, mkdtemp as mkdtemp5, readFile as readFile11, rm as rm5, writeFile as writeFile7 } from "fs/promises";
|
|
7453
7594
|
import { createRequire as createRequire3 } from "module";
|
|
7454
7595
|
import { tmpdir as tmpdir5 } from "os";
|
|
7455
|
-
import
|
|
7596
|
+
import path15 from "path";
|
|
7456
7597
|
import { promisify as promisify5 } from "util";
|
|
7457
7598
|
import { z as z12 } from "zod";
|
|
7458
7599
|
var _execFileAsync = promisify5(execFile5);
|
|
@@ -7499,7 +7640,7 @@ var hyperframeSnapshotNode = defineNode({
|
|
|
7499
7640
|
const compositionDir = await resolveCompositionDir(params.composition);
|
|
7500
7641
|
const meta = await loadCompositionMeta(compositionDir);
|
|
7501
7642
|
const compositionParams = validateAndParseDynamicParams2(meta, params);
|
|
7502
|
-
const tmp = await mkdtemp5(
|
|
7643
|
+
const tmp = await mkdtemp5(path15.join(tmpdir5(), "hf-snap-"));
|
|
7503
7644
|
try {
|
|
7504
7645
|
await copyComposition2(compositionDir, tmp);
|
|
7505
7646
|
await vendorGsap2(tmp, ctx);
|
|
@@ -7514,7 +7655,7 @@ var hyperframeSnapshotNode = defineNode({
|
|
|
7514
7655
|
timeoutMs: params.timeout_ms,
|
|
7515
7656
|
samples: 1
|
|
7516
7657
|
});
|
|
7517
|
-
const entryPath =
|
|
7658
|
+
const entryPath = path15.join(tmp, "index.html");
|
|
7518
7659
|
const entryUrl = `file://${entryPath}`;
|
|
7519
7660
|
ctx.log(`snapshotting ${meta.width}x${meta.height}@${DEVICE_SCALE_FACTOR2}x wait=${params.wait_for.kind}`);
|
|
7520
7661
|
const pwSpecifier = ["play", "wright"].join("");
|
|
@@ -7575,7 +7716,7 @@ async function copyComposition2(srcDir, destDir) {
|
|
|
7575
7716
|
await cp(srcDir, destDir, {
|
|
7576
7717
|
recursive: true,
|
|
7577
7718
|
filter: (src) => {
|
|
7578
|
-
const name =
|
|
7719
|
+
const name = path15.basename(src);
|
|
7579
7720
|
if (name === ".cache" || name === "node_modules" || name === ".git") return false;
|
|
7580
7721
|
return true;
|
|
7581
7722
|
}
|
|
@@ -7584,7 +7725,7 @@ async function copyComposition2(srcDir, destDir) {
|
|
|
7584
7725
|
async function vendorGsap2(tmp, ctx) {
|
|
7585
7726
|
try {
|
|
7586
7727
|
const gsapMin = require_3.resolve("gsap/dist/gsap.min.js");
|
|
7587
|
-
await copyFile5(gsapMin,
|
|
7728
|
+
await copyFile5(gsapMin, path15.join(tmp, "gsap.min.js"));
|
|
7588
7729
|
} catch (e) {
|
|
7589
7730
|
ctx.log(`warning: could not vendor gsap.min.js (${e.message}); compositions must self-supply`);
|
|
7590
7731
|
}
|
|
@@ -7618,15 +7759,15 @@ function coerceImageParam2(value) {
|
|
|
7618
7759
|
throw new Error("hyperframe_snapshot: image param must be a URL string or AssetRef");
|
|
7619
7760
|
}
|
|
7620
7761
|
async function substituteCompositionFiles2(tmp, values) {
|
|
7621
|
-
const entryPath =
|
|
7622
|
-
const original = await
|
|
7762
|
+
const entryPath = path15.join(tmp, "index.html");
|
|
7763
|
+
const original = await readFile11(entryPath, "utf-8");
|
|
7623
7764
|
const { output, missing } = substituteVariables(original, values);
|
|
7624
7765
|
if (missing.length > 0) {
|
|
7625
7766
|
throw new Error(
|
|
7626
7767
|
`hyperframe_snapshot: composition references undefined variables: ${missing.map((m) => `{{${m}}}`).join(", ")}.`
|
|
7627
7768
|
);
|
|
7628
7769
|
}
|
|
7629
|
-
await
|
|
7770
|
+
await writeFile7(entryPath, output, "utf-8");
|
|
7630
7771
|
}
|
|
7631
7772
|
async function waitForReady(page, waitFor, timeoutMs) {
|
|
7632
7773
|
switch (waitFor.kind) {
|
|
@@ -8368,9 +8509,9 @@ var videoLipsyncNode = delegated({
|
|
|
8368
8509
|
});
|
|
8369
8510
|
|
|
8370
8511
|
// src/engine/nodes/remote/videoTranscribe.ts
|
|
8371
|
-
import { mkdtemp as mkdtemp6, readFile as
|
|
8512
|
+
import { mkdtemp as mkdtemp6, readFile as readFile12, rm as rm6 } from "fs/promises";
|
|
8372
8513
|
import { tmpdir as tmpdir6 } from "os";
|
|
8373
|
-
import
|
|
8514
|
+
import path16 from "path";
|
|
8374
8515
|
import { z as z33 } from "zod";
|
|
8375
8516
|
|
|
8376
8517
|
// src/engine/nodes/local/lib/ffmpeg.ts
|
|
@@ -8505,14 +8646,14 @@ async function tryExtractAudio(inputs, ctx) {
|
|
|
8505
8646
|
ctx.log("video_transcribe: no audio track detected, sending full video");
|
|
8506
8647
|
return null;
|
|
8507
8648
|
}
|
|
8508
|
-
tmpDir = await mkdtemp6(
|
|
8509
|
-
const audioPath =
|
|
8649
|
+
tmpDir = await mkdtemp6(path16.join(tmpdir6(), "vtx-"));
|
|
8650
|
+
const audioPath = path16.join(tmpDir, "audio.mp3");
|
|
8510
8651
|
ctx.log("video_transcribe: extracting audio (mono 16kHz mp3)");
|
|
8511
8652
|
await runFfmpeg(
|
|
8512
8653
|
["-i", video.path, "-vn", "-ac", "1", "-ar", "16000", "-b:a", "64k", "-f", "mp3", "-y", audioPath],
|
|
8513
8654
|
{ timeout_ms: AUDIO_EXTRACT_TIMEOUT_MS }
|
|
8514
8655
|
);
|
|
8515
|
-
const bytes = await
|
|
8656
|
+
const bytes = await readFile12(audioPath);
|
|
8516
8657
|
if (bytes.byteLength === 0) {
|
|
8517
8658
|
ctx.log("video_transcribe: extracted audio is empty, sending full video");
|
|
8518
8659
|
return null;
|
|
@@ -8619,19 +8760,19 @@ function safeCost(def) {
|
|
|
8619
8760
|
|
|
8620
8761
|
// src/engine/storage/cache-store.ts
|
|
8621
8762
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
8622
|
-
import { mkdir as mkdir3, readFile as
|
|
8623
|
-
import
|
|
8763
|
+
import { mkdir as mkdir3, readFile as readFile13, rename as rename2, writeFile as writeFile8 } from "fs/promises";
|
|
8764
|
+
import path17 from "path";
|
|
8624
8765
|
var LocalCacheStore = class {
|
|
8625
8766
|
rootDir;
|
|
8626
8767
|
constructor(rootDir) {
|
|
8627
8768
|
this.rootDir = rootDir;
|
|
8628
8769
|
}
|
|
8629
8770
|
filePath(cacheKey) {
|
|
8630
|
-
return
|
|
8771
|
+
return path17.join(this.rootDir, `${cacheKey}.json`);
|
|
8631
8772
|
}
|
|
8632
8773
|
async get(cacheKey) {
|
|
8633
8774
|
try {
|
|
8634
|
-
const buf = await
|
|
8775
|
+
const buf = await readFile13(this.filePath(cacheKey), "utf8");
|
|
8635
8776
|
return JSON.parse(buf);
|
|
8636
8777
|
} catch (e) {
|
|
8637
8778
|
if (e.code === "ENOENT") return null;
|
|
@@ -8640,9 +8781,9 @@ var LocalCacheStore = class {
|
|
|
8640
8781
|
}
|
|
8641
8782
|
async put(entry) {
|
|
8642
8783
|
const dest = this.filePath(entry.cacheKey);
|
|
8643
|
-
await mkdir3(
|
|
8784
|
+
await mkdir3(path17.dirname(dest), { recursive: true });
|
|
8644
8785
|
const tmp = `${dest}.tmp-${process.pid}-${randomUUID2()}`;
|
|
8645
|
-
await
|
|
8786
|
+
await writeFile8(tmp, JSON.stringify(entry, null, 0));
|
|
8646
8787
|
await rename2(tmp, dest);
|
|
8647
8788
|
}
|
|
8648
8789
|
};
|
|
@@ -8696,12 +8837,12 @@ function defaultRegistry() {
|
|
|
8696
8837
|
}
|
|
8697
8838
|
function createEngineFromEnv(opts = {}) {
|
|
8698
8839
|
const cwd = opts.cwd ?? process.cwd();
|
|
8699
|
-
const cacheDir = opts.cacheDir ??
|
|
8700
|
-
const outputsDir = opts.outputsDir ??
|
|
8840
|
+
const cacheDir = opts.cacheDir ?? path18.join(cwd, "canvas", ".cache");
|
|
8841
|
+
const outputsDir = opts.outputsDir ?? path18.join(cwd, "canvas");
|
|
8701
8842
|
const creds = requireCredentialsFromEnv();
|
|
8702
8843
|
const client = new BackendClient({ baseUrl: creds.url, apiKey: creds.apiKey });
|
|
8703
|
-
const assets = new LocalAssetStore(
|
|
8704
|
-
const localCache = new LocalCacheStore(
|
|
8844
|
+
const assets = new LocalAssetStore(path18.join(cacheDir, "assets"));
|
|
8845
|
+
const localCache = new LocalCacheStore(path18.join(cacheDir, "index"));
|
|
8705
8846
|
const remoteCacheEnabled = opts.remoteCache ?? remoteCacheEnabledFromEnv();
|
|
8706
8847
|
const cache = remoteCacheEnabled ? new LayeredCacheStore({
|
|
8707
8848
|
local: localCache,
|
|
@@ -8764,6 +8905,14 @@ export {
|
|
|
8764
8905
|
elementMentionKeywords,
|
|
8765
8906
|
MAX_REMOTE_IMAGE_BYTES,
|
|
8766
8907
|
fetchExternalBytes,
|
|
8908
|
+
isVimeoAuthFailure,
|
|
8909
|
+
ytDlpBlockSignal,
|
|
8910
|
+
YtDlpError,
|
|
8911
|
+
runYtDlp,
|
|
8912
|
+
probeYtDlp,
|
|
8913
|
+
isAudioOnly,
|
|
8914
|
+
durationSeconds,
|
|
8915
|
+
canonicalUrl,
|
|
8767
8916
|
toModelSafeImage,
|
|
8768
8917
|
BackendClient2,
|
|
8769
8918
|
Engine2 as Engine,
|
|
@@ -8775,4 +8924,4 @@ export {
|
|
|
8775
8924
|
defaultRegistry,
|
|
8776
8925
|
createEngineFromEnv
|
|
8777
8926
|
};
|
|
8778
|
-
//# sourceMappingURL=chunk-
|
|
8927
|
+
//# sourceMappingURL=chunk-E2WYWHJJ.js.map
|