@field/cascade 0.10.0 → 0.11.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.
Files changed (133) hide show
  1. package/AGENTS.md +20 -3
  2. package/CHANGELOG.md +187 -0
  3. package/README.md +1 -2
  4. package/dist/assets/{CodeEditor-DSZBpzdy.js → CodeEditor-4O7FQ__R.js} +4 -4
  5. package/dist/assets/{CodePanel-sY6sZa7B.js → CodePanel-BU-autmq.js} +2 -2
  6. package/dist/assets/{CustomNodeDialog-BRKnqZhp.js → CustomNodeDialog-BRpDfOV5.js} +1 -1
  7. package/dist/assets/{OpenGraphDialog-ZsgL-iwr.js → OpenGraphDialog-BwbRlC0X.js} +1 -1
  8. package/dist/assets/{ProjectSettings-Cw17nNe3.js → ProjectSettings-DLOXLxcA.js} +1 -1
  9. package/dist/assets/{SettingsDialog-p25h3tj0.js → SettingsDialog-Bz8JxISH.js} +1 -1
  10. package/dist/assets/{VersionHistoryDialog-DzFLUotB.js → VersionHistoryDialog-D5Z0nJ5-.js} +1 -1
  11. package/dist/assets/{core-BICrEt0y.js → core-Yqi0Ni61.js} +1 -1
  12. package/dist/assets/{cssMode-D7TXw57I.js → cssMode-CxUzc4vp.js} +1 -1
  13. package/dist/assets/{definitions-DUww2Rt-.js → definitions-GreEMXLV.js} +1 -1
  14. package/dist/assets/{editor.api-DvkCO3ko.js → editor.api-BNFb25CX.js} +1 -1
  15. package/dist/assets/{freemarker2-BA2lxsF1.js → freemarker2-0ZDnWxeM.js} +1 -1
  16. package/dist/assets/geo-u0n_jOsD.js +1 -0
  17. package/dist/assets/{handlebars-BcDiyPd8.js → handlebars-Cy18MoaD.js} +1 -1
  18. package/dist/assets/{html-C7O4TyhX.js → html-B-2Dx7nq.js} +1 -1
  19. package/dist/assets/{htmlMode-WaDGF3Mj.js → htmlMode-DUAc9B_d.js} +1 -1
  20. package/dist/assets/{image-DR4oeyJB.js → image-cAa-Nu9O.js} +1 -1
  21. package/dist/assets/{index-DyvDC6pS.js → index-5ZtDAuy2.js} +42 -41
  22. package/dist/assets/{javascript-CpIEK7tz.js → javascript-DeQsDqP5.js} +1 -1
  23. package/dist/assets/{jsonMode-B2FltW0f.js → jsonMode-BiFwJVSk.js} +1 -1
  24. package/dist/assets/{liquid-CN6FFHZE.js → liquid-BLoHH68a.js} +1 -1
  25. package/dist/assets/{lspLanguageFeatures-QhV9XtLp.js → lspLanguageFeatures-CNFgm1Kv.js} +1 -1
  26. package/dist/assets/math-DioIQOPJ.js +1 -0
  27. package/dist/assets/{mdx-DE89pafV.js → mdx-CFXQVNfs.js} +1 -1
  28. package/dist/assets/pop-DC_TQG9t.js +1 -0
  29. package/dist/assets/{python-ByTllTB9.js → python-Cf7S7p2E.js} +1 -1
  30. package/dist/assets/{razor-USYlRRCE.js → razor-CMHBcG-g.js} +1 -1
  31. package/dist/assets/{tsMode-BXpN8iUz.js → tsMode-BEih0GDj.js} +1 -1
  32. package/dist/assets/{typescript-DInBjGQQ.js → typescript-COEKYD_a.js} +1 -1
  33. package/dist/assets/{workers-C7Ye5B7D.js → workers-BNhAhMNn.js} +1 -1
  34. package/dist/assets/{xml-U0Uocqyo.js → xml-CPiMpnPl.js} +1 -1
  35. package/dist/assets/{yaml-CBw_PVYf.js → yaml-Bgp1ITLR.js} +1 -1
  36. package/dist/cli/Graph-ZJJTRQTM.js +2975 -0
  37. package/dist/cli/chunk-2KXB47FP.js +115 -0
  38. package/dist/cli/chunk-35D343DC.js +1447 -0
  39. package/dist/cli/chunk-36P5K3SD.js +631 -0
  40. package/dist/cli/chunk-3JTJHF7V.js +267 -0
  41. package/dist/cli/chunk-4UDFYF5G.js +132 -0
  42. package/dist/cli/chunk-53NMBJAO.js +35 -0
  43. package/dist/cli/chunk-A4OAMOZH.js +310 -0
  44. package/dist/cli/chunk-BIJO5OFS.js +71 -0
  45. package/dist/cli/chunk-BP7UKF36.js +376 -0
  46. package/dist/cli/chunk-CKS2FQWR.js +89 -0
  47. package/dist/cli/chunk-CO22XHNG.js +593 -0
  48. package/dist/cli/chunk-D6BUOHUG.js +634 -0
  49. package/dist/cli/chunk-DEQSEFFH.js +153 -0
  50. package/dist/cli/chunk-DUIUFVU2.js +241 -0
  51. package/dist/cli/chunk-EUIAC3U3.js +3006 -0
  52. package/dist/cli/chunk-FCDXJ6EK.js +2208 -0
  53. package/dist/cli/chunk-GFS6W3C5.js +308 -0
  54. package/dist/cli/chunk-HFHWYL55.js +139 -0
  55. package/dist/cli/chunk-HSNOBIVT.js +263 -0
  56. package/dist/cli/chunk-ICH7N5PN.js +26 -0
  57. package/dist/cli/chunk-J7VK7MNF.js +418 -0
  58. package/dist/cli/chunk-KH4MUBKF.js +1551 -0
  59. package/dist/cli/chunk-MIIEFUGG.js +623 -0
  60. package/dist/cli/chunk-OWWBYLHC.js +136 -0
  61. package/dist/cli/chunk-POMMNKZZ.js +672 -0
  62. package/dist/cli/chunk-QBBKRNGY.js +90 -0
  63. package/dist/cli/chunk-QQPA2ZHH.js +273 -0
  64. package/dist/cli/chunk-RLCAJBVR.js +224 -0
  65. package/dist/cli/chunk-RQZ4SX5R.js +1125 -0
  66. package/dist/cli/chunk-RUU3YWXU.js +99 -0
  67. package/dist/cli/chunk-RXDIMXN5.js +98 -0
  68. package/dist/cli/chunk-SGJAET2L.js +197 -0
  69. package/dist/cli/chunk-VVGSLVSF.js +151 -0
  70. package/dist/cli/chunk-WD62JIP6.js +118 -0
  71. package/dist/cli/chunk-WIH4TEIS.js +104 -0
  72. package/dist/cli/chunk-WKURDVYP.js +1119 -0
  73. package/dist/cli/chunk-WPUDGEFY.js +20 -0
  74. package/dist/cli/chunk-XWDSKG7N.js +1301 -0
  75. package/dist/cli/chunk-YAT5Y25U.js +121 -0
  76. package/dist/cli/chunk-ZL4JTG5Y.js +37 -0
  77. package/dist/cli/cliCommands-UQSFGEHF.js +3464 -0
  78. package/dist/cli/commands-Q23M26QM.js +1292 -0
  79. package/dist/cli/definitions-HF2FTHYU.js +32 -0
  80. package/dist/cli/doctorCommand-B7BPQMN6.js +76 -0
  81. package/dist/cli/edit-JHCPTQFT.js +28 -0
  82. package/dist/cli/editorCommands-UIF5YLUZ.js +33 -0
  83. package/dist/cli/encode-HAUZTYPT.js +17 -0
  84. package/dist/cli/frames-LSAUBZ7P.js +16 -0
  85. package/dist/cli/geo-4RNK3QVN.js +43 -0
  86. package/dist/cli/graphCommands-6BMAKGQE.js +184 -0
  87. package/dist/cli/graphFile-VPSQ6OF7.js +286 -0
  88. package/dist/cli/headlessCanvas-5XLDZT7M.js +15 -0
  89. package/dist/cli/helpCommand-3VRE3KVO.js +155 -0
  90. package/dist/cli/index.js +100 -30756
  91. package/dist/cli/math-ZRMLKU2D.js +16 -0
  92. package/dist/cli/mcp-ZLEEMYGF.js +164 -0
  93. package/dist/cli/parity-KINARCP2.js +328 -0
  94. package/dist/cli/pop-USDFXB7H.js +39 -0
  95. package/dist/cli/project-K3QIPGBB.js +12 -0
  96. package/dist/cli/projectDefinition-DMTZN2NE.js +27 -0
  97. package/dist/cli/projectRuntime-Z5PJOH47.js +48 -0
  98. package/dist/cli/registerStandardNodes-S2VLMF6J.js +3081 -0
  99. package/dist/cli/render-YURAPPUI.js +436 -0
  100. package/dist/cli/runCommand-PI764LEG.js +198 -0
  101. package/dist/cli/runner-RA6UKEPD.js +37 -0
  102. package/dist/cli/runtime/image.ts +277 -0
  103. package/dist/cli/runtime/stage.ts +4 -1
  104. package/dist/cli/stageRunner-KUNIMQKJ.js +10 -0
  105. package/dist/cli/verbs-RCE3K4VQ.js +19 -0
  106. package/dist/cli/webBuild-GT4PZT5D.js +37 -0
  107. package/dist/contracts/documentFormat.d.ts +18 -0
  108. package/dist/contracts/documentFormat.js +195 -0
  109. package/dist/contracts/editor.d.ts +17 -0
  110. package/dist/contracts/index.d.ts +1 -0
  111. package/dist/contracts/index.js +1 -0
  112. package/dist/image.d.ts +141 -0
  113. package/dist/image.js +127 -0
  114. package/dist/index.html +1 -1
  115. package/dist/player/editor.js +32 -7
  116. package/dist/runtime/builtins/headless.d.ts +19 -0
  117. package/dist/runtime/builtins/headless.js +25 -0
  118. package/dist/runtime/builtins/image/contactSheet.d.ts +226 -0
  119. package/dist/runtime/builtins/image/contactSheet.js +199 -0
  120. package/dist/runtime/builtins/image/index.d.ts +9 -0
  121. package/dist/runtime/builtins/image/index.js +9 -0
  122. package/dist/stage.js +4 -1
  123. package/doc/CLI_REFERENCE.md +73 -2
  124. package/doc/EDITOR_EXPORT.md +11 -2
  125. package/doc/MCP.md +17 -1
  126. package/doc/NODE_REFERENCE.md +142 -0
  127. package/doc/PROJECT_AUTHORING.md +127 -7
  128. package/package.json +16 -4
  129. package/dist/assets/ExportDialog-C7dVGua4.js +0 -358
  130. package/dist/assets/ExportDialog-DOq0flMV.css +0 -1
  131. package/dist/assets/geo-C2RHOVHC.js +0 -1
  132. package/dist/assets/math-DaDEgWKt.js +0 -1
  133. package/dist/assets/pop-pLH7L0PW.js +0 -1
@@ -0,0 +1,436 @@
1
+ import {
2
+ runGraph
3
+ } from "./chunk-DUIUFVU2.js";
4
+ import "./chunk-4UDFYF5G.js";
5
+ import "./chunk-QBBKRNGY.js";
6
+ import "./chunk-2KXB47FP.js";
7
+ import {
8
+ parseFrameSpec
9
+ } from "./chunk-BP7UKF36.js";
10
+ import {
11
+ encodeSequence,
12
+ frameSequences,
13
+ hasFfmpeg,
14
+ timelineFrameSpec
15
+ } from "./chunk-YAT5Y25U.js";
16
+ import {
17
+ parseEnvArgs
18
+ } from "./chunk-ZL4JTG5Y.js";
19
+ import "./chunk-RUU3YWXU.js";
20
+ import "./chunk-MIIEFUGG.js";
21
+ import "./chunk-WPUDGEFY.js";
22
+ import "./chunk-VVGSLVSF.js";
23
+ import "./chunk-WD62JIP6.js";
24
+ import "./chunk-EUIAC3U3.js";
25
+ import "./chunk-36P5K3SD.js";
26
+ import "./chunk-D6BUOHUG.js";
27
+ import "./chunk-RXDIMXN5.js";
28
+ import "./chunk-QQPA2ZHH.js";
29
+ import "./chunk-KH4MUBKF.js";
30
+ import "./chunk-35D343DC.js";
31
+ import "./chunk-DEQSEFFH.js";
32
+ import "./chunk-HSNOBIVT.js";
33
+ import "./chunk-ICH7N5PN.js";
34
+ import "./chunk-GFS6W3C5.js";
35
+ import "./chunk-HFHWYL55.js";
36
+ import {
37
+ DEFAULT_FPS,
38
+ frameRange
39
+ } from "./chunk-CO22XHNG.js";
40
+ import "./chunk-RQZ4SX5R.js";
41
+ import "./chunk-A4OAMOZH.js";
42
+ import "./chunk-J7VK7MNF.js";
43
+ import "./chunk-XWDSKG7N.js";
44
+ import "./chunk-BIJO5OFS.js";
45
+ import "./chunk-WIH4TEIS.js";
46
+
47
+ // src/cli/render.ts
48
+ import { fork, spawn } from "node:child_process";
49
+ import fs from "node:fs/promises";
50
+ import os from "node:os";
51
+ import path from "node:path";
52
+ var PREVIEW_SCALE = 0.5;
53
+ function defaultWorkers(cores = os.availableParallelism()) {
54
+ return Math.max(1, cores - Math.min(4, Math.max(1, Math.round(cores / 4))));
55
+ }
56
+ var RENDER_HELP = `
57
+ cascade render <graph-file> [options]
58
+
59
+ Render an animation across worker processes, then encode it. Frames are
60
+ written exactly as run --frames writes them, <out>/<node id>.<frame>.<ext>;
61
+ the video goes beside them.
62
+
63
+ Options:
64
+ --frames <range> 1-100, 1-100x2, 42, or timeline (default: timeline)
65
+ --node <id> Render only this node's output. Repeatable
66
+ --workers <n> Worker processes (default: cores minus a few, here ${defaultWorkers()}).
67
+ With more than one, the first two frames are rendered
68
+ alone first, and a node writing both to one fixed file
69
+ stops the render: parallel workers would overwrite it
70
+ --final Full-size H.264 <node>.mp4 instead of the half-size
71
+ <node>.preview.mp4
72
+ --prores Also a full-size ProRes master <node>.mov (4444 when the
73
+ frames have alpha, else 422 HQ); implies --final
74
+ --no-video Frames only
75
+ --crf <n> H.264 quality, 0-51, lower is better (default 18)
76
+ --out <dir> Sequence directory, project-relative (default: renders)
77
+ --fps <number> Frame rate to evaluate at and play at
78
+ --seed <n> Cook with this graph seed
79
+ --fresh Cook every node on every frame
80
+ --progress <n> A progress line every n frames (default: about 20 lines)
81
+ --job <id> Note progress on this Cairn job (default: $CAIRN_JOB),
82
+ with cairn jobs update <id> --note
83
+ --env NAME=value Set a variable in every worker and its Python stages. Repeatable
84
+ --json Print the result as JSON; progress goes to stderr
85
+ --verbose, -v Every frame, and the workers' own output
86
+ `;
87
+ function parseRenderArgs(args) {
88
+ const file = args[0];
89
+ if (!file || file.startsWith("-")) throw new Error("cascade render requires a graph file");
90
+ const options = { file };
91
+ const nodes = [];
92
+ const value = (index, flag) => {
93
+ const next = args[index + 1];
94
+ if (next === void 0 || next.startsWith("--")) throw new Error(`${flag} needs a value`);
95
+ return next;
96
+ };
97
+ const integer = (text, flag, min, max = Number.MAX_SAFE_INTEGER) => {
98
+ const number = Number(text);
99
+ if (!Number.isInteger(number) || number < min || number > max) throw new Error(`${flag} must be an integer from ${min}${max === Number.MAX_SAFE_INTEGER ? " up" : ` to ${max}`} (got "${text}")`);
100
+ return number;
101
+ };
102
+ for (let index = 1; index < args.length; index += 1) {
103
+ const flag = args[index];
104
+ switch (flag) {
105
+ case "--frames":
106
+ options.frames = value(index, flag);
107
+ index += 1;
108
+ break;
109
+ case "--node":
110
+ nodes.push(value(index, flag));
111
+ index += 1;
112
+ break;
113
+ case "--workers":
114
+ options.workers = integer(value(index, flag), flag, 1);
115
+ index += 1;
116
+ break;
117
+ case "--final":
118
+ options.final = true;
119
+ break;
120
+ case "--prores":
121
+ options.prores = true;
122
+ options.final = true;
123
+ break;
124
+ case "--no-video":
125
+ options.video = false;
126
+ break;
127
+ case "--crf":
128
+ options.crf = integer(value(index, flag), flag, 0, 51);
129
+ index += 1;
130
+ break;
131
+ case "--out":
132
+ options.out = value(index, flag);
133
+ index += 1;
134
+ break;
135
+ case "--fps": {
136
+ const fps = Number(value(index, flag));
137
+ if (!Number.isFinite(fps) || fps <= 0) throw new Error(`--fps must be a positive number (got "${args[index + 1]}")`);
138
+ options.fps = fps;
139
+ index += 1;
140
+ break;
141
+ }
142
+ case "--seed":
143
+ options.seed = integer(value(index, flag), flag, 0, 4294967295);
144
+ index += 1;
145
+ break;
146
+ case "--fresh":
147
+ options.fresh = true;
148
+ break;
149
+ case "--progress":
150
+ options.progressEvery = integer(value(index, flag), flag, 1);
151
+ index += 1;
152
+ break;
153
+ case "--job":
154
+ options.job = value(index, flag);
155
+ index += 1;
156
+ break;
157
+ case "--env":
158
+ options.env = { ...options.env, ...parseEnvArgs(["--env", value(index, flag)]) };
159
+ index += 1;
160
+ break;
161
+ case "--json":
162
+ options.json = true;
163
+ break;
164
+ case "--verbose":
165
+ case "-v":
166
+ options.verbose = true;
167
+ break;
168
+ default:
169
+ throw new Error(`Unknown render option: ${flag} (cascade render --help)`);
170
+ }
171
+ }
172
+ if (nodes.length) options.nodes = nodes;
173
+ if (options.video === false && (options.final || options.crf !== void 0)) {
174
+ throw new Error("--no-video leaves nothing for --final, --prores or --crf to apply to");
175
+ }
176
+ return options;
177
+ }
178
+ function sharedOutputPaths(a, b) {
179
+ const shared = [];
180
+ for (const output of a.outputs) {
181
+ if (b.outputs.some((other) => other.nodeId === output.nodeId && other.source === output.source)) {
182
+ shared.push({ nodeId: output.nodeId, source: output.source });
183
+ }
184
+ }
185
+ return shared;
186
+ }
187
+ function fixedPathMessage(shared, a, b) {
188
+ const nodes = shared.map(({ nodeId, source }) => `"${nodeId}" (${source})`).join(", ");
189
+ return `Cannot render in parallel: ${shared.length === 1 ? "node" : "nodes"} ${nodes} wrote frames ${a} and ${b} to the same file, so workers would overwrite each other's frames and the sequence would silently mix them up. Give the output a per-frame path: put the frame in the file name, or save through saveImage(canvas, cachePath(context.nodeId, ".png")) from cascade/io or the assets capability, which name files by their content. Or render with --workers 1.`;
190
+ }
191
+ function nextBatch(remaining, workers) {
192
+ const size = Math.max(1, Math.ceil(remaining.length / (2 * workers)));
193
+ return remaining.splice(0, size);
194
+ }
195
+ function clock(ms) {
196
+ const seconds = Math.max(0, Math.round(ms / 1e3));
197
+ const h = Math.floor(seconds / 3600), m = Math.floor(seconds % 3600 / 60), s = seconds % 60;
198
+ return h ? `${h}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}` : `${m}:${String(s).padStart(2, "0")}`;
199
+ }
200
+ function progressLine(done, total, elapsedMs) {
201
+ const rate = elapsedMs > 0 ? done / (elapsedMs / 1e3) : 0;
202
+ const eta = rate > 0 ? (total - done) / rate * 1e3 : NaN;
203
+ const percent = total ? Math.floor(done / total * 100) : 100;
204
+ return `${done}/${total} frames (${percent}%) \xB7 ${rate.toFixed(1)} fps \xB7 ${clock(elapsedMs)} elapsed \xB7 ` + (done >= total ? "done" : `ETA ${Number.isFinite(eta) ? clock(eta) : "?"}`);
205
+ }
206
+ function cairnNotifier(job, run, warn) {
207
+ let busy = null;
208
+ let warned = false;
209
+ const note = (text, force = false) => {
210
+ if (!job) return Promise.resolve();
211
+ if (busy && !force) return busy;
212
+ const attempt = (busy ?? Promise.resolve()).then(() => run(["jobs", "update", job, "--note", text])).catch((error) => {
213
+ if (!warned) warn(`Could not note progress on Cairn job ${job}: ${error instanceof Error ? error.message : error}`);
214
+ warned = true;
215
+ });
216
+ busy = attempt.finally(() => {
217
+ if (busy === attempt) busy = null;
218
+ });
219
+ return attempt;
220
+ };
221
+ return note;
222
+ }
223
+ function runCairn(args) {
224
+ return new Promise((resolve, reject) => {
225
+ const child = spawn("cairn", args, { stdio: ["ignore", "ignore", "pipe"] });
226
+ let stderr = "";
227
+ child.stderr.on("data", (chunk) => {
228
+ stderr += chunk;
229
+ });
230
+ child.on("error", reject);
231
+ child.on("exit", (code) => code === 0 ? resolve() : reject(new Error(stderr.trim() || `cairn exited ${code}`)));
232
+ });
233
+ }
234
+ async function renderGraph(options) {
235
+ const started = performance.now();
236
+ const log = options.log ?? (options.json ? (line) => {
237
+ process.stderr.write(`${line}
238
+ `);
239
+ } : (line) => console.log(line));
240
+ const file = path.resolve(options.file);
241
+ const root = path.dirname(file);
242
+ const document = JSON.parse(await fs.readFile(file, "utf8"));
243
+ const spec = !options.frames || options.frames === "timeline" ? timelineFrameSpec(document) : parseFrameSpec(options.frames);
244
+ const all = frameRange(spec.start, spec.end, spec.step ?? 1);
245
+ const out = options.out ?? "renders";
246
+ const video = options.video !== false && all.length > 1;
247
+ if (video && spec.step !== void 0 && spec.step !== 1) {
248
+ throw new Error("A video needs a contiguous range of whole frames; drop the step or pass --no-video");
249
+ }
250
+ if (video && !await hasFfmpeg()) {
251
+ throw new Error("Encoding the video needs ffmpeg on the PATH (brew install ffmpeg), or pass --no-video for frames only");
252
+ }
253
+ const workers = Math.max(1, Math.min(options.workers ?? defaultWorkers(), all.length));
254
+ const every = options.progressEvery ?? Math.max(1, Math.round(all.length / 20));
255
+ const job = options.job ?? process.env.CAIRN_JOB ?? void 0;
256
+ const note = cairnNotifier(job || void 0, options.cairn ?? runCairn, (line) => console.error(line));
257
+ const what = options.nodes?.length ? options.nodes.join(", ") : path.basename(file);
258
+ log(`Rendering ${all.length} frames of ${what} on ${workers} ${workers === 1 ? "worker" : "workers"} -> ${out}/`);
259
+ const workerSpec = {
260
+ file,
261
+ range: spec.step === void 0 ? `${spec.start}-${spec.end}` : `${spec.start}-${spec.end}x${spec.step}`,
262
+ out,
263
+ ...options.nodes?.length ? { nodes: options.nodes } : {},
264
+ ...options.fps === void 0 ? {} : { fps: options.fps },
265
+ ...options.fresh ? { fresh: true } : {},
266
+ ...options.seed === void 0 ? {} : { seed: options.seed }
267
+ };
268
+ const entry = options.workerEntry ?? { script: process.argv[1], execArgv: process.execArgv };
269
+ const remaining = [...all];
270
+ const rendered = /* @__PURE__ */ new Map();
271
+ const probeFrames = workers > 1 ? remaining.splice(0, 2) : [];
272
+ let probe = Promise.resolve();
273
+ let passProbe = () => {
274
+ };
275
+ if (probeFrames.length === 2) probe = new Promise((resolve) => {
276
+ passProbe = resolve;
277
+ });
278
+ const children = [];
279
+ let failure = null;
280
+ const fail = (error) => {
281
+ if (failure) return;
282
+ failure = error;
283
+ for (const child of children) child.kill("SIGTERM");
284
+ passProbe();
285
+ };
286
+ let lastLogged = 0;
287
+ const onFrame = (frame) => {
288
+ rendered.set(frame.frame, frame);
289
+ if (probeFrames.length === 2 && probeFrames.every((value) => rendered.has(value))) {
290
+ const shared = sharedOutputPaths(rendered.get(probeFrames[0]), rendered.get(probeFrames[1]));
291
+ if (shared.length) fail(new Error(fixedPathMessage(shared, probeFrames[0], probeFrames[1])));
292
+ passProbe();
293
+ }
294
+ if (failure) return;
295
+ if (options.verbose) log(` frame ${frame.frame} (${Math.round(frame.durationMs)} ms)`);
296
+ if (rendered.size - lastLogged >= every || rendered.size === all.length) {
297
+ lastLogged = rendered.size;
298
+ const line = progressLine(rendered.size, all.length, performance.now() - started);
299
+ log(line);
300
+ void note(`render ${what}: ${line}`);
301
+ }
302
+ };
303
+ const runWorker = (index) => new Promise((resolve) => {
304
+ const child = fork(entry.script, ["__render-worker", JSON.stringify(workerSpec)], {
305
+ execArgv: entry.execArgv,
306
+ stdio: ["ignore", "pipe", "pipe", "ipc"],
307
+ cwd: process.cwd(),
308
+ env: { ...process.env, ...options.env }
309
+ });
310
+ children.push(child);
311
+ let stderr = "";
312
+ child.stdout.on("data", (chunk) => {
313
+ if (options.verbose) process.stderr.write(chunk);
314
+ });
315
+ child.stderr.on("data", (chunk) => {
316
+ stderr = (stderr + chunk).slice(-4e3);
317
+ if (options.verbose) process.stderr.write(chunk);
318
+ });
319
+ let first = true;
320
+ const send = (message) => {
321
+ if (child.connected) child.send(message);
322
+ };
323
+ child.on("message", async (message) => {
324
+ if (message.type === "frame") onFrame(message);
325
+ else if (message.type === "error") fail(new Error(message.message));
326
+ else if (message.type === "next") {
327
+ if (index === 0 && first && probeFrames.length) {
328
+ first = false;
329
+ send({ type: "frames", frames: probeFrames });
330
+ return;
331
+ }
332
+ first = false;
333
+ await probe;
334
+ send({ type: "frames", frames: failure ? [] : nextBatch(remaining, workers) });
335
+ }
336
+ });
337
+ child.on("exit", (code, signal) => {
338
+ if (code !== 0 && !failure) {
339
+ const detail = stderr.trim().split("\n").slice(-6).join("\n");
340
+ fail(new Error(`render worker ${index + 1} ${signal ? `was killed (${signal})` : `exited ${code}`}${detail ? `:
341
+ ${detail}` : ""}`));
342
+ }
343
+ resolve();
344
+ });
345
+ });
346
+ await Promise.all(Array.from({ length: workers }, (_, index) => runWorker(index)));
347
+ if (failure) {
348
+ await note(`render ${what} failed: ${failure.message.split("\n")[0]}`, true);
349
+ throw failure;
350
+ }
351
+ const missing = all.filter((frame) => !rendered.has(frame));
352
+ if (missing.length) throw new Error(`render finished without ${missing.length} frames (first: ${missing[0]})`);
353
+ const frames = [...all];
354
+ const files = frames.flatMap((frame) => rendered.get(frame).outputs.map((output) => output.file));
355
+ const videos = [];
356
+ if (video) {
357
+ const saved = document?.timeline?.fps;
358
+ const fps = options.fps ?? (typeof saved === "number" && saved > 0 ? saved : DEFAULT_FPS);
359
+ for (const sequence of frameSequences(files)) {
360
+ const base = sequence.prefix;
361
+ const encodes = [
362
+ options.final || options.prores ? { file: `${base}.mp4`, codec: "h264", scale: 1 } : { file: `${base}.preview.mp4`, codec: "h264", scale: PREVIEW_SCALE },
363
+ ...options.prores ? [{ file: `${base}.mov`, codec: "prores", scale: 1 }] : []
364
+ ];
365
+ for (const encode of encodes) {
366
+ videos.push(await encodeSequence({
367
+ root,
368
+ sequence,
369
+ frames,
370
+ fps,
371
+ ...encode,
372
+ ...options.crf === void 0 ? {} : { crf: options.crf }
373
+ }));
374
+ }
375
+ }
376
+ }
377
+ const durationMs = performance.now() - started;
378
+ const summary = `Rendered ${frames.length} frames in ${clock(durationMs)} on ${workers} ${workers === 1 ? "worker" : "workers"}` + (videos.length ? `; ${videos.map((item) => item.file).join(", ")}` : "");
379
+ log(summary);
380
+ await note(`render ${what}: ${summary}`, true);
381
+ return { frames, files, workers, durationMs, videos };
382
+ }
383
+ async function runRenderWorker(argument) {
384
+ if (!process.send) throw new Error("__render-worker is started by cascade render, not by hand");
385
+ const spec = JSON.parse(argument ?? "");
386
+ const send = (message) => new Promise((resolve, reject) => {
387
+ process.send(message, void 0, {}, (error) => error ? reject(error) : resolve());
388
+ });
389
+ const request = () => new Promise((resolve) => {
390
+ const listener = (message) => {
391
+ if (message?.type !== "frames") return;
392
+ process.off("message", listener);
393
+ resolve(message.frames);
394
+ };
395
+ process.on("message", listener);
396
+ void send({ type: "next" });
397
+ });
398
+ async function* frames() {
399
+ for (; ; ) {
400
+ const batch = await request();
401
+ if (!batch.length) return;
402
+ yield* batch;
403
+ }
404
+ }
405
+ try {
406
+ await runGraph({
407
+ file: spec.file,
408
+ frames: spec.range,
409
+ out: spec.out,
410
+ ...spec.nodes ? { nodes: spec.nodes } : {},
411
+ ...spec.fps === void 0 ? {} : { fps: spec.fps },
412
+ ...spec.fresh ? { fresh: true } : {},
413
+ ...spec.seed === void 0 ? {} : { seed: spec.seed },
414
+ worker: { frameSource: frames(), onFrame: (frame) => send({ type: "frame", ...frame }) }
415
+ });
416
+ } catch (error) {
417
+ await send({ type: "error", message: error instanceof Error ? error.message : String(error) }).catch(() => {
418
+ });
419
+ process.exitCode = 1;
420
+ } finally {
421
+ process.disconnect?.();
422
+ }
423
+ }
424
+ export {
425
+ PREVIEW_SCALE,
426
+ RENDER_HELP,
427
+ defaultWorkers,
428
+ fixedPathMessage,
429
+ nextBatch,
430
+ parseRenderArgs,
431
+ progressLine,
432
+ renderGraph,
433
+ runRenderWorker,
434
+ sharedOutputPaths
435
+ };
436
+ //# sourceMappingURL=render-YURAPPUI.js.map
@@ -0,0 +1,198 @@
1
+ import {
2
+ parseEnvArgs
3
+ } from "./chunk-ZL4JTG5Y.js";
4
+
5
+ // src/cli/runCommand.ts
6
+ import * as fs from "fs/promises";
7
+
8
+ // src/cli/supervise.ts
9
+ import { spawn } from "node:child_process";
10
+ function superviseProcess(command, args, timeoutMs) {
11
+ return new Promise((resolve, reject) => {
12
+ const child = spawn(command, args, { stdio: "inherit", detached: process.platform !== "win32" });
13
+ let timedOut = false;
14
+ let cancelled = false;
15
+ let grace;
16
+ const kill = (signal) => {
17
+ if (!child.pid) return;
18
+ try {
19
+ if (process.platform === "win32") child.kill(signal);
20
+ else process.kill(-child.pid, signal);
21
+ } catch (error) {
22
+ if (error.code !== "ESRCH") throw error;
23
+ }
24
+ };
25
+ const cancel = () => {
26
+ cancelled = true;
27
+ kill("SIGTERM");
28
+ grace ??= setTimeout(() => kill("SIGKILL"), 1e3);
29
+ };
30
+ process.on("SIGINT", cancel);
31
+ process.on("SIGTERM", cancel);
32
+ const timeout = setTimeout(() => {
33
+ timedOut = true;
34
+ console.error(`Render timed out after ${timeoutMs} ms`);
35
+ kill("SIGKILL");
36
+ }, timeoutMs);
37
+ const cleanup = () => {
38
+ clearTimeout(timeout);
39
+ clearTimeout(grace);
40
+ process.off("SIGINT", cancel);
41
+ process.off("SIGTERM", cancel);
42
+ };
43
+ child.once("error", (error) => {
44
+ cleanup();
45
+ reject(error);
46
+ });
47
+ child.once("close", (code) => {
48
+ cleanup();
49
+ resolve(timedOut ? 124 : cancelled ? 130 : code ?? 1);
50
+ });
51
+ });
52
+ }
53
+
54
+ // src/cli/runCommand.ts
55
+ async function runCommand(args) {
56
+ const command = args[0];
57
+ const fileArg = args[1];
58
+ if (!fileArg) {
59
+ console.error("Error: Graph file path required");
60
+ console.error('Run "cascade --help" for usage information');
61
+ process.exit(1);
62
+ }
63
+ try {
64
+ await fs.access(fileArg);
65
+ } catch {
66
+ console.error(`Error: File not found: ${fileArg}`);
67
+ process.exit(1);
68
+ }
69
+ const options = {
70
+ file: fileArg,
71
+ validateOnly: command === "validate",
72
+ strict: args.includes("--strict"),
73
+ checkOnly: command === "check",
74
+ inspectOnly: command === "inspect"
75
+ };
76
+ const verboseIndex = args.indexOf("--verbose") !== -1 ? args.indexOf("--verbose") : args.indexOf("-v");
77
+ if (verboseIndex !== -1) {
78
+ options.verbose = true;
79
+ }
80
+ const named = [];
81
+ for (let index = 0; index < args.length; index += 1) {
82
+ if (args[index] === "--node" && args[index + 1]) named.push(args[index + 1]);
83
+ }
84
+ if (named.length) options.nodes = named;
85
+ const outNodes = [];
86
+ for (let index = 0; index < args.length; index += 1) {
87
+ if (args[index] !== "--out-node") continue;
88
+ const value = args[index + 1];
89
+ if (!value || value.startsWith("-")) {
90
+ console.error("Error: --out-node needs a node id");
91
+ process.exit(1);
92
+ }
93
+ outNodes.push(value);
94
+ }
95
+ if (outNodes.length) options.outNodes = outNodes;
96
+ const env = parseEnvArgs(args);
97
+ if (env) options.env = env;
98
+ const entryNodeIndex = args.indexOf("--entry-node");
99
+ if (entryNodeIndex !== -1 && args[entryNodeIndex + 1]) {
100
+ options.entryNode = args[entryNodeIndex + 1];
101
+ }
102
+ const valueOf = (flag) => {
103
+ const index = args.indexOf(flag);
104
+ if (index === -1) return void 0;
105
+ const value = args[index + 1];
106
+ if (!value || value.startsWith("-")) {
107
+ console.error(`Error: ${flag} needs a value`);
108
+ process.exit(1);
109
+ }
110
+ return value;
111
+ };
112
+ const frames = valueOf("--frames");
113
+ if (frames) options.frames = frames;
114
+ const fps = valueOf("--fps");
115
+ if (fps !== void 0) {
116
+ const rate = Number(fps);
117
+ if (!Number.isFinite(rate) || rate <= 0) {
118
+ console.error(`Error: --fps must be a positive number (got "${fps}")`);
119
+ process.exit(1);
120
+ }
121
+ options.fps = rate;
122
+ }
123
+ const out = valueOf("--out");
124
+ if (out) options.out = out;
125
+ const mp4 = valueOf("--mp4");
126
+ if (mp4) {
127
+ if (command !== "run" || !options.frames) throw new Error("--mp4 requires run --frames (try --frames timeline)");
128
+ options.mp4 = mp4;
129
+ }
130
+ const crf = valueOf("--crf");
131
+ if (crf !== void 0) {
132
+ const value = Number(crf);
133
+ if (!options.mp4 || !Number.isInteger(value) || value < 0 || value > 51) throw new Error("--crf needs --mp4 and an integer from 0 to 51");
134
+ options.crf = value;
135
+ }
136
+ if (args.includes("--json") && command === "run") options.json = true;
137
+ if (args.includes("--fresh")) options.fresh = true;
138
+ const seed = valueOf("--seed");
139
+ if (seed !== void 0) {
140
+ const value = Number(seed);
141
+ if (!Number.isInteger(value) || value < 0 || value > 4294967295) {
142
+ throw new Error(`--seed must be an integer from 0 to 4294967295 (got "${seed}")`);
143
+ }
144
+ options.seed = value;
145
+ }
146
+ const timeout = valueOf("--timeout");
147
+ if (timeout !== void 0) {
148
+ const ms = Number(timeout);
149
+ if (command !== "run" || !Number.isSafeInteger(ms) || ms <= 0 || ms > 2147483647) {
150
+ throw new Error("--timeout requires run and a positive integer number of milliseconds up to 2147483647");
151
+ }
152
+ const index = args.indexOf("--timeout");
153
+ const childArgs = [...args.slice(0, index), ...args.slice(index + 2)];
154
+ if (childArgs.includes("--timeout")) throw new Error("--timeout may only be specified once");
155
+ process.exitCode = await superviseProcess(process.execPath, [...process.execArgv, process.argv[1], ...childArgs], ms);
156
+ return;
157
+ }
158
+ const { runGraph } = await import("./runner-RA6UKEPD.js");
159
+ try {
160
+ if (options.json) console.log = console.error;
161
+ await runGraph(options);
162
+ } catch (error) {
163
+ console.error(`Error: ${error.message}`);
164
+ if (options.verbose && error.stack) {
165
+ console.error(error.stack);
166
+ }
167
+ process.exit(1);
168
+ }
169
+ }
170
+ async function buildCommand(args) {
171
+ const { buildPlayer } = await import("./webBuild-GT4PZT5D.js");
172
+ if (!args[1] || args[1].startsWith("-")) throw new Error("cascade build requires a graph file");
173
+ let out;
174
+ const assets = [];
175
+ for (let index = 2; index < args.length; index += 2) {
176
+ const flag = args[index];
177
+ if (flag !== "--out" && flag !== "--asset") throw new Error(`Unknown build option: ${flag}`);
178
+ const value = args[index + 1];
179
+ if (!value || value.startsWith("-")) throw new Error(`${flag} requires a value`);
180
+ if (flag === "--out") {
181
+ if (out !== void 0) throw new Error("--out may only be supplied once");
182
+ out = value;
183
+ } else assets.push(value);
184
+ }
185
+ if (!out) throw new Error("cascade build requires --out <new-directory>");
186
+ try {
187
+ const result = await buildPlayer(args[1], { out, assets });
188
+ console.log(`Browser player built: ${result.directory}`);
189
+ } catch (error) {
190
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
191
+ process.exitCode = 1;
192
+ }
193
+ }
194
+ export {
195
+ buildCommand,
196
+ runCommand
197
+ };
198
+ //# sourceMappingURL=runCommand-PI764LEG.js.map
@@ -0,0 +1,37 @@
1
+ import {
2
+ runGraph
3
+ } from "./chunk-DUIUFVU2.js";
4
+ import "./chunk-4UDFYF5G.js";
5
+ import "./chunk-QBBKRNGY.js";
6
+ import "./chunk-2KXB47FP.js";
7
+ import "./chunk-BP7UKF36.js";
8
+ import "./chunk-YAT5Y25U.js";
9
+ import "./chunk-ZL4JTG5Y.js";
10
+ import "./chunk-RUU3YWXU.js";
11
+ import "./chunk-MIIEFUGG.js";
12
+ import "./chunk-WPUDGEFY.js";
13
+ import "./chunk-VVGSLVSF.js";
14
+ import "./chunk-WD62JIP6.js";
15
+ import "./chunk-EUIAC3U3.js";
16
+ import "./chunk-36P5K3SD.js";
17
+ import "./chunk-D6BUOHUG.js";
18
+ import "./chunk-RXDIMXN5.js";
19
+ import "./chunk-QQPA2ZHH.js";
20
+ import "./chunk-KH4MUBKF.js";
21
+ import "./chunk-35D343DC.js";
22
+ import "./chunk-DEQSEFFH.js";
23
+ import "./chunk-HSNOBIVT.js";
24
+ import "./chunk-ICH7N5PN.js";
25
+ import "./chunk-GFS6W3C5.js";
26
+ import "./chunk-HFHWYL55.js";
27
+ import "./chunk-CO22XHNG.js";
28
+ import "./chunk-RQZ4SX5R.js";
29
+ import "./chunk-A4OAMOZH.js";
30
+ import "./chunk-J7VK7MNF.js";
31
+ import "./chunk-XWDSKG7N.js";
32
+ import "./chunk-BIJO5OFS.js";
33
+ import "./chunk-WIH4TEIS.js";
34
+ export {
35
+ runGraph
36
+ };
37
+ //# sourceMappingURL=runner-RA6UKEPD.js.map