@field/cascade 0.8.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 (195) hide show
  1. package/AGENTS.md +36 -3
  2. package/CHANGELOG.md +401 -0
  3. package/DESIGN.md +14 -0
  4. package/README.md +41 -26
  5. package/dist/assets/CodeEditor-4O7FQ__R.js +623 -0
  6. package/dist/assets/CodePanel-BU-autmq.js +2 -0
  7. package/dist/assets/{CustomNodeDialog-BQbEhIwL.js → CustomNodeDialog-BRpDfOV5.js} +1 -1
  8. package/dist/assets/OpenGraphDialog-BwbRlC0X.js +1 -0
  9. package/dist/assets/ProjectSettings-DLOXLxcA.js +2 -0
  10. package/dist/assets/SettingsDialog-Bz8JxISH.js +1 -0
  11. package/dist/assets/{VersionHistoryDialog-D6VBzSQ0.js → VersionHistoryDialog-D5Z0nJ5-.js} +2 -2
  12. package/dist/assets/{core-BInLG4gw.js → core-Yqi0Ni61.js} +8 -8
  13. package/dist/assets/{cssMode-DfR2qmYG.js → cssMode-CxUzc4vp.js} +1 -1
  14. package/dist/assets/definitions-GreEMXLV.js +1 -0
  15. package/dist/assets/{editor.api-Ck8_vgQV.js → editor.api-BNFb25CX.js} +1 -1
  16. package/dist/assets/{freemarker2-fVn10Wn3.js → freemarker2-0ZDnWxeM.js} +1 -1
  17. package/dist/assets/geo-u0n_jOsD.js +1 -0
  18. package/dist/assets/{handlebars-BCSchLT2.js → handlebars-Cy18MoaD.js} +1 -1
  19. package/dist/assets/{html-CVpSv_k9.js → html-B-2Dx7nq.js} +1 -1
  20. package/dist/assets/{htmlMode-Da4ESTv0.js → htmlMode-DUAc9B_d.js} +1 -1
  21. package/dist/assets/{image-BLn--oSF.js → image-cAa-Nu9O.js} +3 -3
  22. package/dist/assets/index-5ZtDAuy2.js +220 -0
  23. package/dist/assets/index-7vae6YFC.css +1 -0
  24. package/dist/assets/index-client-DqYmKstU.js +1 -0
  25. package/dist/assets/{javascript-BpX0Gvcf.js → javascript-DeQsDqP5.js} +1 -1
  26. package/dist/assets/{jsonMode-CsLqaou7.js → jsonMode-BiFwJVSk.js} +1 -1
  27. package/dist/assets/legacy-CTEeXeqF.js +2 -0
  28. package/dist/assets/{liquid-efUz51Wf.js → liquid-BLoHH68a.js} +1 -1
  29. package/dist/assets/{lspLanguageFeatures-DCi5-K0n.js → lspLanguageFeatures-CNFgm1Kv.js} +1 -1
  30. package/dist/assets/math-DioIQOPJ.js +1 -0
  31. package/dist/assets/{mdx-DP_sjGVy.js → mdx-CFXQVNfs.js} +1 -1
  32. package/dist/assets/pop-DC_TQG9t.js +1 -0
  33. package/dist/assets/{python-aAuP3LUi.js → python-Cf7S7p2E.js} +1 -1
  34. package/dist/assets/{razor-7M6sMmC5.js → razor-CMHBcG-g.js} +1 -1
  35. package/dist/assets/studioScenes-8sq9FfFg.js +1 -0
  36. package/dist/assets/{tsMode-Bf4CnoYK.js → tsMode-BEih0GDj.js} +1 -1
  37. package/dist/assets/{typescript-ddhYBGyT.js → typescript-COEKYD_a.js} +1 -1
  38. package/dist/assets/{workers-X5la8DDT.js → workers-BNhAhMNn.js} +1 -1
  39. package/dist/assets/{xml-BzbXa3Ps.js → xml-CPiMpnPl.js} +1 -1
  40. package/dist/assets/{yaml-CXs-VzxL.js → yaml-Bgp1ITLR.js} +1 -1
  41. package/dist/cli/Graph-ZJJTRQTM.js +2975 -0
  42. package/dist/cli/chunk-2KXB47FP.js +115 -0
  43. package/dist/cli/chunk-35D343DC.js +1447 -0
  44. package/dist/cli/chunk-36P5K3SD.js +631 -0
  45. package/dist/cli/chunk-3JTJHF7V.js +267 -0
  46. package/dist/cli/chunk-4UDFYF5G.js +132 -0
  47. package/dist/cli/chunk-53NMBJAO.js +35 -0
  48. package/dist/cli/chunk-A4OAMOZH.js +310 -0
  49. package/dist/cli/chunk-BIJO5OFS.js +71 -0
  50. package/dist/cli/chunk-BP7UKF36.js +376 -0
  51. package/dist/cli/chunk-CKS2FQWR.js +89 -0
  52. package/dist/cli/chunk-CO22XHNG.js +593 -0
  53. package/dist/cli/chunk-D6BUOHUG.js +634 -0
  54. package/dist/cli/chunk-DEQSEFFH.js +153 -0
  55. package/dist/cli/chunk-DUIUFVU2.js +241 -0
  56. package/dist/cli/chunk-EUIAC3U3.js +3006 -0
  57. package/dist/cli/chunk-FCDXJ6EK.js +2208 -0
  58. package/dist/cli/chunk-GFS6W3C5.js +308 -0
  59. package/dist/cli/chunk-HFHWYL55.js +139 -0
  60. package/dist/cli/chunk-HSNOBIVT.js +263 -0
  61. package/dist/cli/chunk-ICH7N5PN.js +26 -0
  62. package/dist/cli/chunk-J7VK7MNF.js +418 -0
  63. package/dist/cli/chunk-KH4MUBKF.js +1551 -0
  64. package/dist/cli/chunk-MIIEFUGG.js +623 -0
  65. package/dist/cli/chunk-OWWBYLHC.js +136 -0
  66. package/dist/cli/chunk-POMMNKZZ.js +672 -0
  67. package/dist/cli/chunk-QBBKRNGY.js +90 -0
  68. package/dist/cli/chunk-QQPA2ZHH.js +273 -0
  69. package/dist/cli/chunk-RLCAJBVR.js +224 -0
  70. package/dist/cli/chunk-RQZ4SX5R.js +1125 -0
  71. package/dist/cli/chunk-RUU3YWXU.js +99 -0
  72. package/dist/cli/chunk-RXDIMXN5.js +98 -0
  73. package/dist/cli/chunk-SGJAET2L.js +197 -0
  74. package/dist/cli/chunk-VVGSLVSF.js +151 -0
  75. package/dist/cli/chunk-WD62JIP6.js +118 -0
  76. package/dist/cli/chunk-WIH4TEIS.js +104 -0
  77. package/dist/cli/chunk-WKURDVYP.js +1119 -0
  78. package/dist/cli/chunk-WPUDGEFY.js +20 -0
  79. package/dist/cli/chunk-XWDSKG7N.js +1301 -0
  80. package/dist/cli/chunk-YAT5Y25U.js +121 -0
  81. package/dist/cli/chunk-ZL4JTG5Y.js +37 -0
  82. package/dist/cli/cliCommands-UQSFGEHF.js +3464 -0
  83. package/dist/cli/commands-Q23M26QM.js +1292 -0
  84. package/dist/cli/definitions-HF2FTHYU.js +32 -0
  85. package/dist/cli/doctorCommand-B7BPQMN6.js +76 -0
  86. package/dist/cli/edit-JHCPTQFT.js +28 -0
  87. package/dist/cli/editorCommands-UIF5YLUZ.js +33 -0
  88. package/dist/cli/encode-HAUZTYPT.js +17 -0
  89. package/dist/cli/frames-LSAUBZ7P.js +16 -0
  90. package/dist/cli/geo-4RNK3QVN.js +43 -0
  91. package/dist/cli/graphCommands-6BMAKGQE.js +184 -0
  92. package/dist/cli/graphFile-VPSQ6OF7.js +286 -0
  93. package/dist/cli/headlessCanvas-5XLDZT7M.js +15 -0
  94. package/dist/cli/helpCommand-3VRE3KVO.js +155 -0
  95. package/dist/cli/index.js +106 -14207
  96. package/dist/cli/math-ZRMLKU2D.js +16 -0
  97. package/dist/cli/mcp-ZLEEMYGF.js +164 -0
  98. package/dist/cli/parity-KINARCP2.js +328 -0
  99. package/dist/cli/pop-USDFXB7H.js +39 -0
  100. package/dist/cli/project-K3QIPGBB.js +12 -0
  101. package/dist/cli/projectDefinition-DMTZN2NE.js +27 -0
  102. package/dist/cli/projectRuntime-Z5PJOH47.js +48 -0
  103. package/dist/cli/registerStandardNodes-S2VLMF6J.js +3081 -0
  104. package/dist/cli/render-YURAPPUI.js +436 -0
  105. package/dist/cli/runCommand-PI764LEG.js +198 -0
  106. package/dist/cli/runner-RA6UKEPD.js +37 -0
  107. package/dist/cli/runtime/image.ts +277 -0
  108. package/dist/cli/runtime/stage.ts +74 -11
  109. package/dist/cli/stageRunner-KUNIMQKJ.js +10 -0
  110. package/dist/cli/verbs-RCE3K4VQ.js +19 -0
  111. package/dist/cli/webBuild-GT4PZT5D.js +37 -0
  112. package/dist/contracts/bundle.d.ts +75 -0
  113. package/dist/contracts/bundle.js +112 -0
  114. package/dist/contracts/definition.d.ts +67 -4
  115. package/dist/contracts/document.d.ts +29 -0
  116. package/dist/contracts/documentFormat.d.ts +18 -0
  117. package/dist/contracts/documentFormat.js +195 -0
  118. package/dist/contracts/editor.d.ts +189 -0
  119. package/dist/contracts/editor.js +2 -0
  120. package/dist/contracts/index.d.ts +3 -0
  121. package/dist/contracts/index.js +3 -0
  122. package/dist/contracts/validate.js +59 -3
  123. package/dist/contracts/values.d.ts +3 -3
  124. package/dist/contracts/values.js +1 -0
  125. package/dist/image.d.ts +141 -0
  126. package/dist/image.js +127 -0
  127. package/dist/index.html +4 -4
  128. package/dist/player/app.js +17 -5
  129. package/dist/player/editor.js +1919 -0
  130. package/dist/player/editorEngine.js +477 -0
  131. package/dist/runtime/builtins/core/bundle.d.ts +70 -0
  132. package/dist/runtime/builtins/core/bundle.js +81 -0
  133. package/dist/runtime/builtins/core/index.d.ts +3 -2
  134. package/dist/runtime/builtins/core/index.js +6 -2
  135. package/dist/runtime/builtins/core/routing.d.ts +27 -0
  136. package/dist/runtime/builtins/core/routing.js +28 -0
  137. package/dist/runtime/builtins/headless.d.ts +19 -0
  138. package/dist/runtime/builtins/headless.js +25 -0
  139. package/dist/runtime/builtins/image/contactSheet.d.ts +226 -0
  140. package/dist/runtime/builtins/image/contactSheet.js +199 -0
  141. package/dist/runtime/builtins/image/index.d.ts +9 -0
  142. package/dist/runtime/builtins/image/index.js +9 -0
  143. package/dist/runtime/edit/document.d.ts +94 -0
  144. package/dist/runtime/edit/document.js +194 -0
  145. package/dist/runtime/edit/graphEditor.d.ts +223 -0
  146. package/dist/runtime/edit/graphEditor.js +893 -0
  147. package/dist/runtime/edit/index.d.ts +12 -0
  148. package/dist/runtime/edit/index.js +6 -0
  149. package/dist/runtime/edit/layout.d.ts +50 -0
  150. package/dist/runtime/edit/layout.js +126 -0
  151. package/dist/runtime/edit/operations.d.ts +65 -0
  152. package/dist/runtime/edit/operations.js +57 -0
  153. package/dist/runtime/edit/parameters.d.ts +75 -0
  154. package/dist/runtime/edit/parameters.js +284 -0
  155. package/dist/runtime/edit/read.d.ts +35 -0
  156. package/dist/runtime/edit/read.js +65 -0
  157. package/dist/runtime/editor/index.d.ts +104 -0
  158. package/dist/runtime/editor/index.js +439 -0
  159. package/dist/runtime/expressions/bundlePaths.d.ts +37 -0
  160. package/dist/runtime/expressions/bundlePaths.js +55 -0
  161. package/dist/runtime/expressions/index.d.ts +2 -0
  162. package/dist/runtime/expressions/index.js +1 -0
  163. package/dist/runtime/index.d.ts +2 -0
  164. package/dist/runtime/index.js +2 -0
  165. package/dist/runtime/params/animator.d.ts +5 -0
  166. package/dist/runtime/params/animator.js +16 -0
  167. package/dist/runtime/runtime.js +238 -12
  168. package/dist/runtime/seed.d.ts +29 -0
  169. package/dist/runtime/seed.js +52 -0
  170. package/dist/runtime/types.d.ts +12 -0
  171. package/dist/stage.js +43 -9
  172. package/dist/studio/panel.d.ts +2 -0
  173. package/doc/CLI_REFERENCE.md +171 -0
  174. package/doc/EDITOR_EXPORT.md +235 -0
  175. package/doc/HEADLESS_GPU.md +27 -0
  176. package/doc/MCP.md +69 -0
  177. package/doc/NODE_REFERENCE.md +208 -0
  178. package/doc/PROJECT_AUTHORING.md +258 -1
  179. package/package.json +29 -8
  180. package/dist/assets/CodeEditor-BEk-kIBd.js +0 -623
  181. package/dist/assets/CodePanel-CeSdjXkg.js +0 -2
  182. package/dist/assets/ExportDialog-CjTbbTZJ.js +0 -358
  183. package/dist/assets/ExportDialog-DOq0flMV.css +0 -1
  184. package/dist/assets/OpenGraphDialog-DCwH9itM.js +0 -1
  185. package/dist/assets/ProjectSettings-Dg7M5xt9.js +0 -2
  186. package/dist/assets/SettingsDialog-e7GgsjB9.js +0 -1
  187. package/dist/assets/definitions-DZpOjWKR.js +0 -1
  188. package/dist/assets/geo-D2VPjAdt.js +0 -1
  189. package/dist/assets/index-CMiBdEuT.js +0 -212
  190. package/dist/assets/index-DMIuml9a.css +0 -1
  191. package/dist/assets/index-client-BI2H82FJ.js +0 -1
  192. package/dist/assets/legacy-ZA7textG.js +0 -2
  193. package/dist/assets/math-B6hpxPJO.js +0 -1
  194. package/dist/assets/pop-CYUIjT6x.js +0 -1
  195. package/dist/assets/projectSettingsApi-C1WvIwfP.js +0 -1
@@ -0,0 +1,376 @@
1
+ import {
2
+ expressionEngine
3
+ } from "./chunk-RUU3YWXU.js";
4
+ import {
5
+ imagePath,
6
+ sequenceFileName,
7
+ sequenceWidth
8
+ } from "./chunk-ICH7N5PN.js";
9
+ import {
10
+ TimeState,
11
+ runFrameRange
12
+ } from "./chunk-CO22XHNG.js";
13
+
14
+ // src/cli/frames.ts
15
+ import fs from "node:fs/promises";
16
+ import path from "node:path";
17
+
18
+ // src/engine/cascade.ts
19
+ var CascadeContext = class {
20
+ constructor() {
21
+ this._graph = null;
22
+ this._pwd = null;
23
+ /**
24
+ * Time state. Frames are stored as real numbers: `frame()` reports the
25
+ * integer frame ($F) and `fframe()` the fractional one ($FF), so sub-frame
26
+ * time exists rather than being floored away at the door.
27
+ */
28
+ this._time = new TimeState();
29
+ this._selectedNodes = /* @__PURE__ */ new Set();
30
+ }
31
+ /**
32
+ * Initialize with a graph reference
33
+ */
34
+ setGraph(graph) {
35
+ this._graph = graph;
36
+ expressionEngine.setGraph(graph);
37
+ }
38
+ /**
39
+ * Get the current graph
40
+ */
41
+ getGraph() {
42
+ return this._graph;
43
+ }
44
+ // ============ Node Access ============
45
+ /**
46
+ * Find a node by path (absolute or relative to pwd)
47
+ */
48
+ node(path2) {
49
+ if (!this._graph) return null;
50
+ if (path2.startsWith("/")) {
51
+ return this.resolveAbsolutePath(path2);
52
+ }
53
+ if (this._pwd) {
54
+ return this._pwd.node(path2);
55
+ }
56
+ return this.resolveAbsolutePath("/" + path2);
57
+ }
58
+ /**
59
+ * Find a parameter by path (e.g., "/node1/radius")
60
+ */
61
+ parm(path2) {
62
+ const lastSlash = path2.lastIndexOf("/");
63
+ if (lastSlash === -1) return null;
64
+ const nodePath = path2.substring(0, lastSlash) || "/";
65
+ const parmName = path2.substring(lastSlash + 1);
66
+ const node = this.node(nodePath);
67
+ return node?.parm(parmName) ?? null;
68
+ }
69
+ /**
70
+ * Get the root node (virtual - returns null, represents "/")
71
+ * In Cascade, root is not a real node but the graph itself
72
+ */
73
+ root() {
74
+ return null;
75
+ }
76
+ /**
77
+ * Get current working directory/network
78
+ */
79
+ pwd() {
80
+ return this._pwd;
81
+ }
82
+ /**
83
+ * Set current working directory/network
84
+ */
85
+ setPwd(node) {
86
+ this._pwd = node;
87
+ }
88
+ /**
89
+ * Navigate to a path (like cd command)
90
+ */
91
+ cd(path2) {
92
+ if (path2 === "/") {
93
+ this._pwd = null;
94
+ return;
95
+ }
96
+ const target = this.node(path2);
97
+ if (target && target.isNetwork()) {
98
+ this._pwd = target;
99
+ }
100
+ }
101
+ // ============ Selection ============
102
+ /**
103
+ * Get all selected nodes
104
+ */
105
+ selectedNodes() {
106
+ if (!this._graph) return [];
107
+ return Array.from(this._selectedNodes).map((id) => this._graph.getNode(id)).filter((n) => n !== null);
108
+ }
109
+ /**
110
+ * Get selected node IDs
111
+ */
112
+ selectedNodeIds() {
113
+ return Array.from(this._selectedNodes);
114
+ }
115
+ /**
116
+ * Set selection (internal use by editor)
117
+ */
118
+ setSelection(nodeIds) {
119
+ this._selectedNodes = new Set(nodeIds);
120
+ this.onSelectionChange?.(nodeIds);
121
+ }
122
+ /**
123
+ * Clear all selection
124
+ */
125
+ clearAllSelected() {
126
+ this._selectedNodes.clear();
127
+ this.onSelectionChange?.([]);
128
+ }
129
+ // ============ Time/Playback ============
130
+ /**
131
+ * Get current frame number (integer) — $F
132
+ */
133
+ frame() {
134
+ return this._time.frame;
135
+ }
136
+ /**
137
+ * Get current frame as a fractional value — $FF
138
+ */
139
+ fframe() {
140
+ return this._time.frameFraction;
141
+ }
142
+ /**
143
+ * Get current time in seconds — $T
144
+ */
145
+ time() {
146
+ return this._time.time;
147
+ }
148
+ /**
149
+ * Get frames per second — $FPS
150
+ */
151
+ fps() {
152
+ return this._time.fps;
153
+ }
154
+ /**
155
+ * Set current frame. Fractional frames are preserved; `frame()` still
156
+ * reports the floored integer frame.
157
+ */
158
+ setFrame(frame) {
159
+ this._time.setFrame(frame);
160
+ expressionEngine.setFrame(this._time.frameFraction);
161
+ }
162
+ /**
163
+ * Set current time in seconds, keeping the sub-frame remainder
164
+ */
165
+ setTime(time) {
166
+ this._time.setTime(time);
167
+ expressionEngine.setFrame(this._time.frameFraction);
168
+ }
169
+ /**
170
+ * Set FPS
171
+ */
172
+ setFps(fps) {
173
+ this._time.setFps(fps);
174
+ expressionEngine.setFps(this._time.fps);
175
+ }
176
+ /**
177
+ * The clock a frame-range loop drives. Nothing here reads a wall clock —
178
+ * the caller owns time, which is what keeps a cook deterministic.
179
+ */
180
+ clock() {
181
+ return {
182
+ setFrame: (frame) => this.setFrame(frame),
183
+ markTimeDependentDirty: () => this.markTimeDependentDirty(),
184
+ getFrame: () => this.frame(),
185
+ getFrameFraction: () => this.fframe(),
186
+ getTime: () => this.time(),
187
+ getFps: () => this.fps()
188
+ };
189
+ }
190
+ // ============ Channel Shortcuts ============
191
+ /**
192
+ * Get parameter value as number (shorthand)
193
+ */
194
+ ch(path2) {
195
+ const p = this.parm(path2);
196
+ return p?.evalAsFloat() ?? 0;
197
+ }
198
+ /**
199
+ * Get parameter value as string (shorthand)
200
+ */
201
+ chs(path2) {
202
+ const p = this.parm(path2);
203
+ return p?.evalAsString() ?? "";
204
+ }
205
+ /**
206
+ * Get parameter value as vector (shorthand)
207
+ */
208
+ chv(path2) {
209
+ const p = this.parm(path2);
210
+ const val = p?.eval();
211
+ if (Array.isArray(val)) return val;
212
+ if (typeof val === "number") return [val];
213
+ return [];
214
+ }
215
+ // ============ Time-Dependent Updates ============
216
+ /**
217
+ * Mark all time-dependent nodes as dirty
218
+ * Call this at the start of each frame in the animation loop
219
+ */
220
+ markTimeDependentDirty() {
221
+ if (!this._graph) return;
222
+ for (const node of this._graph.nodes) {
223
+ if (node.isTimeDependent) {
224
+ node.markDirty();
225
+ }
226
+ }
227
+ }
228
+ /**
229
+ * Get all time-dependent nodes in the graph
230
+ */
231
+ getTimeDependentNodes() {
232
+ if (!this._graph) return [];
233
+ return this._graph.nodes.filter((n) => n.isTimeDependent);
234
+ }
235
+ /**
236
+ * Advance the frame and mark time-dependent nodes dirty.
237
+ * Convenience method for animation loops; `step` may be fractional.
238
+ * Returns the new fractional frame.
239
+ */
240
+ nextFrame(step = 1) {
241
+ this.setFrame(this.fframe() + step);
242
+ this.markTimeDependentDirty();
243
+ return this.fframe();
244
+ }
245
+ // ============ Internal Helpers ============
246
+ resolveAbsolutePath(path2) {
247
+ if (!this._graph) return null;
248
+ const segments = path2.replace(/^\//, "").split("/").filter((s) => s.length > 0);
249
+ if (segments.length === 0) {
250
+ return null;
251
+ }
252
+ let current = null;
253
+ const rootNodes = this._graph.nodes.filter((n) => !n.parent);
254
+ for (let i = 0; i < segments.length; i++) {
255
+ const segment = segments[i];
256
+ const searchIn = current ? current.children() : rootNodes;
257
+ const found = searchIn.find((n) => n.id === segment);
258
+ if (!found) return null;
259
+ current = found;
260
+ }
261
+ return current;
262
+ }
263
+ };
264
+ var cascade = new CascadeContext();
265
+
266
+ // src/engine/animation/frameRange.ts
267
+ async function evaluateFrameRange(options) {
268
+ const { fps, context = cascade, ...rest } = options;
269
+ const previousFps = context.fps();
270
+ if (fps !== void 0) context.setFps(fps);
271
+ try {
272
+ return await runFrameRange(context.clock(), rest);
273
+ } finally {
274
+ if (fps !== void 0) context.setFps(previousFps);
275
+ }
276
+ }
277
+
278
+ // src/cli/frames.ts
279
+ function parseFrameSpec(spec) {
280
+ const text = String(spec ?? "").trim();
281
+ const match = /^(-?\d+(?:\.\d+)?)(?:\s*[-:]\s*(-?\d+(?:\.\d+)?))?(?:\s*x\s*(\d+(?:\.\d+)?))?$/i.exec(text);
282
+ if (!match) {
283
+ throw new Error(`--frames expects a range like 1-100, 1-100x2, or a single frame like 42 (got "${spec}")`);
284
+ }
285
+ const start = Number(match[1]);
286
+ const end = match[2] === void 0 ? start : Number(match[2]);
287
+ const step = match[3] === void 0 ? void 0 : Number(match[3]);
288
+ if (end < start) throw new Error(`--frames: end (${end}) must not be before start (${start})`);
289
+ if (step !== void 0 && !(step > 0)) throw new Error(`--frames: step must be greater than 0 (got ${step})`);
290
+ return step === void 0 ? { start, end } : { start, end, step };
291
+ }
292
+ function outputPorts(graph, entryNode) {
293
+ const nodes = entryNode ? [entryNode] : graph.nodes;
294
+ const found = [];
295
+ for (const node of nodes) {
296
+ for (const port of node.outputs) {
297
+ if (port.dataType !== "image") continue;
298
+ if (!entryNode && port.connections.length > 0) continue;
299
+ found.push({ node, port });
300
+ }
301
+ }
302
+ return found;
303
+ }
304
+ function outputSnapshot(graph) {
305
+ return JSON.stringify(graph.nodes.map((node) => [node.id, node.outputs.map((port) => port.value ?? null)]));
306
+ }
307
+ async function cookUntilSettled(graph, entryNode, options = {}) {
308
+ const { fixpoint = false, passes = 8 } = options;
309
+ const dirtyAll = () => {
310
+ for (const node of graph.nodes) node.markDirty?.();
311
+ };
312
+ let previous = fixpoint ? outputSnapshot(graph) : null;
313
+ for (let pass = 0; pass < passes; pass++) {
314
+ await graph.execute(entryNode);
315
+ await graph.scheduler?.whenIdle?.();
316
+ if (graph.nodes.some((node) => node.error !== null)) return;
317
+ const stale = graph.nodes.some((node) => node.cookState === "stale" || node.cookState === "queued");
318
+ if (!fixpoint) {
319
+ if (!stale) return;
320
+ continue;
321
+ }
322
+ const snapshot = outputSnapshot(graph);
323
+ const settled = !stale && snapshot === previous;
324
+ previous = snapshot;
325
+ if (settled) return;
326
+ dirtyAll();
327
+ }
328
+ throw new Error(`graph did not settle after ${passes} cook passes`);
329
+ }
330
+ async function renderFrameRange(options) {
331
+ const { graph, project, out, start, end, step, fps, entryNode, verbose, signal } = options;
332
+ cascade.setGraph(graph);
333
+ const outDirectory = project.resolve(out);
334
+ await fs.mkdir(outDirectory, { recursive: true });
335
+ const files = [];
336
+ const width = sequenceWidth(end);
337
+ const result = await evaluateFrameRange({
338
+ start,
339
+ end,
340
+ step,
341
+ fps,
342
+ signal,
343
+ onFrame: async ({ frame, index, total }) => {
344
+ if (index === 0) for (const node of graph.nodes) node.markDirty();
345
+ await cookUntilSettled(graph, entryNode, { fixpoint: index === 0 });
346
+ const failed = graph.nodes.filter((node) => node.error !== null);
347
+ if (failed.length) {
348
+ const detail = failed.map((node) => `${node.id}: ${node.error?.message ?? "unknown cook error"}`).join("; ");
349
+ throw new Error(`frame ${frame} failed \u2014 ${detail}`);
350
+ }
351
+ const ports = outputPorts(graph, entryNode);
352
+ if (!ports.length) {
353
+ throw new Error(
354
+ "No image output to save: the graph has no unconsumed image port. Name the node to render with --entry-node."
355
+ );
356
+ }
357
+ for (const { node, port } of ports) {
358
+ const source = imagePath(port.value);
359
+ if (!source) continue;
360
+ const name = sequenceFileName(node.id, frame, source, width);
361
+ await fs.copyFile(project.resolveMedia(source), path.join(outDirectory, name));
362
+ files.push(path.posix.join(out, name));
363
+ }
364
+ if (verbose) console.log(` frame ${frame} (${index + 1}/${total})`);
365
+ }
366
+ });
367
+ return { frames: result.frames, files, aborted: result.aborted };
368
+ }
369
+
370
+ export {
371
+ parseFrameSpec,
372
+ outputPorts,
373
+ cookUntilSettled,
374
+ renderFrameRange
375
+ };
376
+ //# sourceMappingURL=chunk-BP7UKF36.js.map
@@ -0,0 +1,89 @@
1
+ // src/cli/jsonEnvelope.ts
2
+ var ExitRequested = class extends Error {
3
+ constructor(code) {
4
+ super("");
5
+ this.code = code;
6
+ }
7
+ };
8
+ async function captureEnvelope(command) {
9
+ const logged = [];
10
+ const warnings = [];
11
+ const errors = [];
12
+ const original = {
13
+ log: console.log,
14
+ info: console.info,
15
+ warn: console.warn,
16
+ error: console.error,
17
+ write: process.stdout.write,
18
+ exit: process.exit
19
+ };
20
+ const text = (args) => args.map((arg) => typeof arg === "string" ? arg : arg instanceof Error ? arg.message : JSON.stringify(arg)).join(" ");
21
+ console.log = (...args) => {
22
+ logged.push(text(args));
23
+ };
24
+ console.info = console.log;
25
+ console.warn = (...args) => {
26
+ warnings.push(text(args));
27
+ };
28
+ console.error = (...args) => {
29
+ errors.push(text(args));
30
+ };
31
+ process.stdout.write = ((chunk) => {
32
+ logged.push(String(chunk));
33
+ return true;
34
+ });
35
+ process.exit = ((code) => {
36
+ throw new ExitRequested(typeof code === "number" ? code : 0);
37
+ });
38
+ let result;
39
+ let failure = null;
40
+ try {
41
+ result = await command();
42
+ if (process.exitCode && process.exitCode !== 0) failure = errors.join("\n") || `exited with code ${process.exitCode}`;
43
+ } catch (error) {
44
+ if (error instanceof ExitRequested) {
45
+ if (error.code !== 0) failure = errors.join("\n") || `exited with code ${error.code}`;
46
+ } else {
47
+ failure = error instanceof Error ? error.message : String(error);
48
+ }
49
+ } finally {
50
+ console.log = original.log;
51
+ console.info = original.info;
52
+ console.warn = original.warn;
53
+ console.error = original.error;
54
+ process.stdout.write = original.write;
55
+ process.exit = original.exit;
56
+ }
57
+ const allWarnings = [...warnings, ...failure ? [] : errors].flatMap(splitLines);
58
+ if (failure) return { ok: false, error: { message: failure.replace(/^Error: /, "") }, warnings: allWarnings };
59
+ return { ok: true, data: result !== void 0 ? result : dataFromOutput(logged), warnings: allWarnings };
60
+ }
61
+ async function runWithJsonEnvelope(command) {
62
+ const envelope = await captureEnvelope(command);
63
+ process.stdout.write(`${JSON.stringify(envelope)}
64
+ `);
65
+ process.exitCode = envelope.ok ? 0 : 1;
66
+ }
67
+ function dataFromOutput(chunks) {
68
+ const output = chunks.join("\n").trim();
69
+ if (!output) return null;
70
+ try {
71
+ return JSON.parse(output);
72
+ } catch {
73
+ const lines = output.split("\n").filter(Boolean);
74
+ try {
75
+ return JSON.parse(lines[lines.length - 1]);
76
+ } catch {
77
+ return { output: lines };
78
+ }
79
+ }
80
+ }
81
+ function splitLines(text) {
82
+ return text.split("\n").map((line) => line.trimEnd()).filter((line) => line.trim());
83
+ }
84
+
85
+ export {
86
+ captureEnvelope,
87
+ runWithJsonEnvelope
88
+ };
89
+ //# sourceMappingURL=chunk-CKS2FQWR.js.map