@field/cascade 0.11.0 → 0.13.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 (220) hide show
  1. package/AGENTS.md +8 -5
  2. package/ARCHITECTURE.md +5 -4
  3. package/CHANGELOG.md +245 -0
  4. package/README.md +1 -1
  5. package/dist/assets/AgentPanel-1JugFjE4.css +1 -0
  6. package/dist/assets/AgentPanel-BDL5MddA.js +5 -0
  7. package/dist/assets/{CodeEditor-4O7FQ__R.js → CodeEditor-C-xYG-Wn.js} +120 -120
  8. package/dist/assets/CodePanel-CtXd50m6.js +2 -0
  9. package/dist/assets/CookInfoPanel-B3NMhKJF.css +1 -0
  10. package/dist/assets/CookInfoPanel-DG4_TRsX.js +2 -0
  11. package/dist/assets/CustomNodeDialog-CHfZUs-r.js +1 -0
  12. package/dist/assets/DefinitionNode-CaRTXQ_b.js +1 -0
  13. package/dist/assets/DefinitionPanel-C3CxZY9r.js +1 -0
  14. package/dist/assets/DefinitionPanel-VNLnAiQD.css +1 -0
  15. package/dist/assets/OpenGraphDialog-BLdr6k_q.js +1 -0
  16. package/dist/assets/ProjectSettings-ZnvmIAir.js +2 -0
  17. package/dist/assets/{SettingsDialog-Bz8JxISH.js → SettingsDialog-CBX3Wa87.js} +1 -1
  18. package/dist/assets/TimelinePanel-CDeR0arY.css +1 -0
  19. package/dist/assets/TimelinePanel-CWgnFH1e.js +1 -0
  20. package/dist/assets/{VersionHistoryDialog-D5Z0nJ5-.js → VersionHistoryDialog-BOSoW1R-.js} +1 -1
  21. package/dist/assets/assert-BO2_erPD.js +1 -0
  22. package/dist/assets/assert-hvykGCnP.js +1 -0
  23. package/dist/assets/builder-BmBKcDix.js +1 -0
  24. package/dist/assets/camera-DFF7YA3F.js +1 -0
  25. package/dist/assets/checks-DAvyO9Gf.js +1 -0
  26. package/dist/assets/contactSheet-N1NcaWxw.js +2 -0
  27. package/dist/assets/{core-Yqi0Ni61.js → core-DB-MkGjj.js} +3 -4
  28. package/dist/assets/coreTypes-DtnDATZ7.js +1 -0
  29. package/dist/assets/{cssMode-CxUzc4vp.js → cssMode-DPVckSOc.js} +1 -1
  30. package/dist/assets/definitions-B227e7f_.js +1 -0
  31. package/dist/assets/definitions-D0WOUJd7.js +1 -0
  32. package/dist/assets/{editor.api-BNFb25CX.js → editor.api-Dkutg_Z1.js} +1 -1
  33. package/dist/assets/expressions-X-Z-GrgW.js +5 -0
  34. package/dist/assets/file-DAL6IfjV.js +1 -0
  35. package/dist/assets/filters-D3k1LRMa.js +1 -0
  36. package/dist/assets/{freemarker2-0ZDnWxeM.js → freemarker2-CLiLKk3B.js} +1 -1
  37. package/dist/assets/generators-BhDxRAFC.js +1 -0
  38. package/dist/assets/geo-BsMImJIJ.js +1 -0
  39. package/dist/assets/geo-C4YngDob.js +2 -0
  40. package/dist/assets/{handlebars-Cy18MoaD.js → handlebars-CcD3aMeL.js} +1 -1
  41. package/dist/assets/{html-B-2Dx7nq.js → html-DvZWGfqb.js} +1 -1
  42. package/dist/assets/{htmlMode-DUAc9B_d.js → htmlMode-DUD3VaoN.js} +1 -1
  43. package/dist/assets/image-0SfH-snh.js +1 -0
  44. package/dist/assets/image-Ck-RdGU4.js +2 -0
  45. package/dist/assets/index-BWnrmIB9.js +137 -0
  46. package/dist/assets/index-DIaxEONI.css +1 -0
  47. package/dist/assets/io-BGmt2g0D.js +1 -0
  48. package/dist/assets/{javascript-DeQsDqP5.js → javascript-Cap2ahgh.js} +1 -1
  49. package/dist/assets/{jsonMode-BiFwJVSk.js → jsonMode-B0uLjp9C.js} +1 -1
  50. package/dist/assets/{liquid-BLoHH68a.js → liquid-CnxwhfLn.js} +1 -1
  51. package/dist/assets/{lspLanguageFeatures-CNFgm1Kv.js → lspLanguageFeatures-Dn9hlKX6.js} +1 -1
  52. package/dist/assets/math-GeULS-sk.js +1 -0
  53. package/dist/assets/{mdx-CFXQVNfs.js → mdx-BV3jbrVa.js} +1 -1
  54. package/dist/assets/pop-KMF_qloi.js +1 -0
  55. package/dist/assets/preload-helper-Czpn1I53.js +1 -0
  56. package/dist/assets/{python-Cf7S7p2E.js → python-CGyFk4XK.js} +1 -1
  57. package/dist/assets/{razor-CMHBcG-g.js → razor-CQfzX3-N.js} +1 -1
  58. package/dist/assets/remap-5SjXQU-q.js +1 -0
  59. package/dist/assets/renderMarkdown-ork0Oir7.js +67 -0
  60. package/dist/assets/studioImages-CAyH-0Pl.js +1 -0
  61. package/dist/assets/text-ZpX7qNkx.js +2 -0
  62. package/dist/assets/time-P_OiBhcf.js +1 -0
  63. package/dist/assets/{tsMode-BEih0GDj.js → tsMode-BOeXzO9H.js} +1 -1
  64. package/dist/assets/{typescript-COEKYD_a.js → typescript-J1SnE_Tu.js} +1 -1
  65. package/dist/assets/{workers-BNhAhMNn.js → workers-CYwwTiVF.js} +1 -1
  66. package/dist/assets/{xml-CPiMpnPl.js → xml-DnPRNzK9.js} +1 -1
  67. package/dist/assets/{yaml-Bgp1ITLR.js → yaml-BmmYBNa9.js} +1 -1
  68. package/dist/cli/DefinitionNode-P7AA4TAU.js +15 -0
  69. package/dist/cli/{Graph-ZJJTRQTM.js → Graph-OB6EWSGL.js} +12 -12
  70. package/dist/cli/assert-GROGQNCQ.js +25 -0
  71. package/dist/cli/checks-3WB44XBL.js +189 -0
  72. package/dist/cli/{chunk-3JTJHF7V.js → chunk-2L34ERS2.js} +4 -4
  73. package/dist/cli/chunk-53LGFPWX.js +138 -0
  74. package/dist/cli/chunk-5ORUQ6ES.js +267 -0
  75. package/dist/cli/{chunk-YAT5Y25U.js → chunk-7QE2ZNSN.js} +3 -3
  76. package/dist/cli/{chunk-SGJAET2L.js → chunk-BPODRH52.js} +8 -4
  77. package/dist/cli/{chunk-XWDSKG7N.js → chunk-BZ63XQ3I.js} +2 -2
  78. package/dist/cli/{chunk-FCDXJ6EK.js → chunk-CTPLRSWF.js} +2 -13
  79. package/dist/cli/{chunk-WIH4TEIS.js → chunk-EZRWJ4G2.js} +1 -1
  80. package/dist/cli/{chunk-BIJO5OFS.js → chunk-FWMSGKEH.js} +1 -1
  81. package/dist/cli/{chunk-POMMNKZZ.js → chunk-G23A2BZZ.js} +38 -5
  82. package/dist/cli/{chunk-35D343DC.js → chunk-GRIN2DDP.js} +19 -1
  83. package/dist/cli/{chunk-WKURDVYP.js → chunk-JEW5MPGI.js} +28 -331
  84. package/dist/cli/chunk-JVOCK5W4.js +83 -0
  85. package/dist/cli/chunk-KBGUO27Q.js +15 -0
  86. package/dist/cli/{chunk-GFS6W3C5.js → chunk-L6B6FUQP.js} +54 -32
  87. package/dist/cli/chunk-LTXWBXOM.js +438 -0
  88. package/dist/cli/chunk-MI5JLQYA.js +220 -0
  89. package/dist/cli/{chunk-A4OAMOZH.js → chunk-NJFMDXHR.js} +13 -3
  90. package/dist/cli/{chunk-DUIUFVU2.js → chunk-OBZAP3GG.js} +49 -23
  91. package/dist/cli/chunk-QAGATJGE.js +336 -0
  92. package/dist/cli/{chunk-EUIAC3U3.js → chunk-QKL7CCRS.js} +966 -132
  93. package/dist/cli/{chunk-RLCAJBVR.js → chunk-TJW67QSI.js} +8 -7
  94. package/dist/cli/{chunk-RQZ4SX5R.js → chunk-W5PJ5RMP.js} +156 -84
  95. package/dist/cli/chunk-W7OUAT7J.js +122 -0
  96. package/dist/cli/chunk-WGKPJZDY.js +364 -0
  97. package/dist/cli/{chunk-D6BUOHUG.js → chunk-X4WKAOQP.js} +4 -4
  98. package/dist/cli/{chunk-VVGSLVSF.js → chunk-X566FWBV.js} +6 -1
  99. package/dist/cli/chunk-YH637PDL.js +317 -0
  100. package/dist/cli/chunk-ZSCUZE6I.js +112 -0
  101. package/dist/cli/{cliCommands-UQSFGEHF.js → cliCommands-XVFRD7CF.js} +23 -17
  102. package/dist/cli/{commands-Q23M26QM.js → commands-M2XLTPTZ.js} +129 -47
  103. package/dist/cli/contactSheet-2PQVRUFW.js +11 -0
  104. package/dist/cli/{definitions-HF2FTHYU.js → definitions-BPOPW52L.js} +7 -7
  105. package/dist/cli/{doctorCommand-B7BPQMN6.js → doctorCommand-RAEI7GVY.js} +36 -6
  106. package/dist/cli/{edit-JHCPTQFT.js → edit-S5KEQ5RA.js} +3 -3
  107. package/dist/cli/editorCommands-5BBG4S6V.js +41 -0
  108. package/dist/cli/{encode-HAUZTYPT.js → encode-ICPEPMH5.js} +2 -2
  109. package/dist/cli/file-3HTQHPYQ.js +41 -0
  110. package/dist/cli/filters-6ODFWYK4.js +379 -0
  111. package/dist/cli/generators-YU7GB4MD.js +210 -0
  112. package/dist/cli/{geo-4RNK3QVN.js → geo-6ZPGKZRU.js} +7 -7
  113. package/dist/cli/{graphCommands-6BMAKGQE.js → graphCommands-MULQ7VD2.js} +4 -4
  114. package/dist/cli/{graphFile-VPSQ6OF7.js → graphFile-CITF36ZM.js} +25 -19
  115. package/dist/cli/{headlessCanvas-5XLDZT7M.js → headlessCanvas-ENRHC7GS.js} +3 -2
  116. package/dist/cli/{helpCommand-3VRE3KVO.js → helpCommand-X2QDANM3.js} +9 -7
  117. package/dist/cli/image-TXELZZK5.js +55 -0
  118. package/dist/cli/index.js +14 -14
  119. package/dist/cli/{math-ZRMLKU2D.js → math-RSYRQ3C6.js} +5 -5
  120. package/dist/cli/{mcp-ZLEEMYGF.js → mcp-YRIGRLYQ.js} +4 -4
  121. package/dist/cli/{parity-KINARCP2.js → parity-3N3HTKWX.js} +42 -36
  122. package/dist/cli/{pop-USDFXB7H.js → pop-B7SIIYKN.js} +5 -5
  123. package/dist/cli/project-SJF5ZF3F.js +12 -0
  124. package/dist/cli/{projectDefinition-DMTZN2NE.js → projectDefinition-F3D7UIHJ.js} +8 -8
  125. package/dist/cli/{projectRuntime-Z5PJOH47.js → projectRuntime-NY4XMDFH.js} +19 -12
  126. package/dist/cli/registerStandardNodes-BXJCB6E4.js +744 -0
  127. package/dist/cli/{render-YURAPPUI.js → render-Y23W6MY6.js} +29 -22
  128. package/dist/cli/{runCommand-PI764LEG.js → runCommand-XOG7AVC5.js} +3 -3
  129. package/dist/cli/runner-DNJO5WO2.js +43 -0
  130. package/dist/cli/runtime/image.ts +27 -2
  131. package/dist/cli/runtime/io.ts +78 -0
  132. package/dist/cli/share-B7HTCOJX.js +166 -0
  133. package/dist/cli/stageCache-TY2BFZJR.js +15 -0
  134. package/dist/cli/{stageRunner-KUNIMQKJ.js → stageRunner-KY2EJP7X.js} +2 -2
  135. package/dist/cli/text-MB3EKR4H.js +126 -0
  136. package/dist/cli/{verbs-RCE3K4VQ.js → verbs-PI2JH5FI.js} +2 -2
  137. package/dist/cli/webBuild-2K4COCGY.js +44 -0
  138. package/dist/contracts/capabilities.d.ts +39 -1
  139. package/dist/contracts/color.d.ts +8 -0
  140. package/dist/contracts/color.js +23 -0
  141. package/dist/contracts/definition.d.ts +10 -0
  142. package/dist/contracts/schema.d.ts +1 -1
  143. package/dist/contracts/schema.js +1 -0
  144. package/dist/contracts/validate.js +11 -1
  145. package/dist/contracts/values.d.ts +24 -0
  146. package/dist/image.d.ts +15 -0
  147. package/dist/image.js +36 -2
  148. package/dist/index.html +8 -2
  149. package/dist/io.d.ts +29 -0
  150. package/dist/io.js +40 -0
  151. package/dist/player/app.js +11 -7
  152. package/dist/player/editor.js +3 -2
  153. package/dist/player/editorEngine.js +3 -2
  154. package/dist/player/embed.js +2 -0
  155. package/dist/player/types.d.ts +6 -0
  156. package/dist/runtime/builtins/assert/checks.d.ts +18 -0
  157. package/dist/runtime/builtins/assert/checks.js +207 -0
  158. package/dist/runtime/builtins/assert/definitions.d.ts +130 -0
  159. package/dist/runtime/builtins/assert/definitions.js +54 -0
  160. package/dist/runtime/builtins/assert/index.d.ts +152 -0
  161. package/dist/runtime/builtins/assert/index.js +36 -0
  162. package/dist/runtime/builtins/image/contactSheet.d.ts +3 -165
  163. package/dist/runtime/builtins/image/contactSheet.js +0 -49
  164. package/dist/runtime/builtins/image/definitions.d.ts +950 -0
  165. package/dist/runtime/builtins/image/definitions.js +343 -0
  166. package/dist/runtime/builtins/image/file.d.ts +8 -0
  167. package/dist/runtime/builtins/image/file.js +37 -0
  168. package/dist/runtime/builtins/image/filters.d.ts +8 -0
  169. package/dist/runtime/builtins/image/filters.js +394 -0
  170. package/dist/runtime/builtins/image/generators.d.ts +7 -0
  171. package/dist/runtime/builtins/image/generators.js +144 -0
  172. package/dist/runtime/builtins/image/index.d.ts +947 -4
  173. package/dist/runtime/builtins/image/index.js +41 -6
  174. package/dist/runtime/builtins/image/io.d.ts +100 -0
  175. package/dist/runtime/builtins/image/io.js +142 -0
  176. package/dist/runtime/builtins/image/planar.d.ts +55 -0
  177. package/dist/runtime/builtins/image/planar.js +274 -0
  178. package/dist/runtime/builtins/image/simplex.d.ts +22 -0
  179. package/dist/runtime/builtins/image/simplex.js +92 -0
  180. package/dist/runtime/builtins/image/text.d.ts +4 -0
  181. package/dist/runtime/builtins/image/text.js +141 -0
  182. package/dist/runtime/builtins/index.d.ts +3 -0
  183. package/dist/runtime/builtins/index.js +7 -0
  184. package/dist/runtime/definition/architecture.js +89 -0
  185. package/dist/runtime/gpu.d.ts +25 -0
  186. package/dist/runtime/gpu.js +40 -0
  187. package/dist/runtime/runtime.d.ts +2 -0
  188. package/dist/runtime/runtime.js +268 -44
  189. package/dist/runtime/types.d.ts +30 -0
  190. package/doc/CLI_REFERENCE.md +21 -3
  191. package/doc/EDITOR_EXPORT.md +26 -0
  192. package/doc/MCP.md +2 -1
  193. package/doc/NODE_REFERENCE.md +286 -14
  194. package/doc/PROJECT_AUTHORING.md +158 -4
  195. package/doc/WEB_PLAYER.md +9 -1
  196. package/package.json +4 -1
  197. package/dist/assets/CodePanel-BU-autmq.js +0 -2
  198. package/dist/assets/CustomNodeDialog-BRpDfOV5.js +0 -1
  199. package/dist/assets/OpenGraphDialog-BwbRlC0X.js +0 -1
  200. package/dist/assets/ProjectSettings-DLOXLxcA.js +0 -2
  201. package/dist/assets/definitions-GreEMXLV.js +0 -1
  202. package/dist/assets/geo-u0n_jOsD.js +0 -1
  203. package/dist/assets/image-cAa-Nu9O.js +0 -2062
  204. package/dist/assets/index-5ZtDAuy2.js +0 -220
  205. package/dist/assets/index-7vae6YFC.css +0 -1
  206. package/dist/assets/math-DioIQOPJ.js +0 -1
  207. package/dist/assets/pop-DC_TQG9t.js +0 -1
  208. package/dist/cli/chunk-36P5K3SD.js +0 -631
  209. package/dist/cli/chunk-4UDFYF5G.js +0 -132
  210. package/dist/cli/chunk-53NMBJAO.js +0 -35
  211. package/dist/cli/chunk-MIIEFUGG.js +0 -623
  212. package/dist/cli/chunk-QBBKRNGY.js +0 -90
  213. package/dist/cli/chunk-WPUDGEFY.js +0 -20
  214. package/dist/cli/editorCommands-UIF5YLUZ.js +0 -33
  215. package/dist/cli/project-K3QIPGBB.js +0 -12
  216. package/dist/cli/registerStandardNodes-S2VLMF6J.js +0 -3081
  217. package/dist/cli/runner-RA6UKEPD.js +0 -37
  218. package/dist/cli/webBuild-GT4PZT5D.js +0 -37
  219. package/dist/runtime/builtins/headless.d.ts +0 -19
  220. package/dist/runtime/builtins/headless.js +0 -25
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-J7VK7MNF.js";
4
4
  import {
5
5
  CORE_TYPES
6
- } from "./chunk-BIJO5OFS.js";
6
+ } from "./chunk-FWMSGKEH.js";
7
7
 
8
8
  // packages/contracts/src/validate.ts
9
9
  var IDENTIFIER = /^[A-Za-z][A-Za-z0-9_]*$/;
@@ -19,7 +19,7 @@ var CAPABILITIES_BY_ENVIRONMENT = {
19
19
  var ALLOWED = CAPABILITIES_BY_ENVIRONMENT;
20
20
  var fields = (names) => new Set(names.split(" "));
21
21
  var TOP_FIELDS = fields(
22
- "apiVersion runsOn container capabilities source label description icon inputs outputs props"
22
+ "apiVersion runsOn container capabilities gpuLimits source label description icon inputs outputs props"
23
23
  );
24
24
  var DATA_FIELDS = fields(
25
25
  "kind type default description variadic min max step accept control options schema fields"
@@ -111,6 +111,16 @@ function validateNodeDefinition(value) {
111
111
  add("duplicate-capability", `${String(capability)} is duplicated`, path);
112
112
  seen.add(capability);
113
113
  }
114
+ if (value.gpuLimits !== void 0) {
115
+ if (!seen.has("gpu"))
116
+ add("gpu-limits-without-gpu", "gpuLimits needs the gpu capability", "gpuLimits");
117
+ if (!isRecord(value.gpuLimits))
118
+ add("invalid-gpu-limits", "gpuLimits must be an object of WebGPU limit names to numbers", "gpuLimits");
119
+ else
120
+ for (const [name, limit] of Object.entries(value.gpuLimits))
121
+ if (typeof limit !== "number" || !Number.isFinite(limit) || limit < 0)
122
+ add("invalid-gpu-limits", `gpuLimits.${name} must be a non-negative number`, `gpuLimits.${name}`);
123
+ }
114
124
  const inward = /* @__PURE__ */ new Map();
115
125
  const outward = /* @__PURE__ */ new Map();
116
126
  for (const section of ["inputs", "outputs", "props"]) {
@@ -307,4 +317,4 @@ export {
307
317
  CAPABILITIES_BY_ENVIRONMENT,
308
318
  validateNodeDefinition
309
319
  };
310
- //# sourceMappingURL=chunk-A4OAMOZH.js.map
320
+ //# sourceMappingURL=chunk-NJFMDXHR.js.map
@@ -1,53 +1,60 @@
1
- import {
2
- RunTrace,
3
- captureNodeConsole
4
- } from "./chunk-4UDFYF5G.js";
5
- import {
6
- installProjectStages
7
- } from "./chunk-QBBKRNGY.js";
8
1
  import {
9
2
  setEmbeddedCompiler,
10
3
  setProjectModuleCompiler
11
4
  } from "./chunk-2KXB47FP.js";
5
+ import {
6
+ installHeadlessCanvas
7
+ } from "./chunk-EZRWJ4G2.js";
12
8
  import {
13
9
  parseFrameSpec
14
10
  } from "./chunk-BP7UKF36.js";
15
11
  import {
16
12
  encodeMp4,
17
13
  timelineFrameSpec
18
- } from "./chunk-YAT5Y25U.js";
14
+ } from "./chunk-7QE2ZNSN.js";
19
15
  import {
20
16
  applyEnv
21
17
  } from "./chunk-ZL4JTG5Y.js";
22
18
  import {
23
- cascadeVersion,
24
- compileEmbedded,
25
- compileProjectModule,
26
- formatStageTrace
27
- } from "./chunk-MIIEFUGG.js";
28
- import {
19
+ RunTrace,
20
+ captureNodeConsole,
29
21
  checkProjectGraph,
22
+ cookingNode,
30
23
  inspectProjectGraph,
31
24
  installProjectIo,
25
+ installProjectStages,
32
26
  renderDeterministicProjectFrames,
33
27
  runDeterministicProjectGraph,
34
28
  saveStillImages,
35
29
  validateProjectGraph
36
- } from "./chunk-EUIAC3U3.js";
30
+ } from "./chunk-QKL7CCRS.js";
37
31
  import {
38
32
  graphSeed
39
- } from "./chunk-GFS6W3C5.js";
33
+ } from "./chunk-L6B6FUQP.js";
40
34
  import {
41
- ProjectRoot
42
- } from "./chunk-RQZ4SX5R.js";
35
+ frameRange
36
+ } from "./chunk-CO22XHNG.js";
43
37
  import {
44
- installHeadlessCanvas
45
- } from "./chunk-WIH4TEIS.js";
38
+ compileEmbedded,
39
+ compileProjectModule
40
+ } from "./chunk-W7OUAT7J.js";
41
+ import {
42
+ cascadeVersion,
43
+ formatStageTrace
44
+ } from "./chunk-5ORUQ6ES.js";
45
+ import {
46
+ ProjectRoot
47
+ } from "./chunk-W5PJ5RMP.js";
46
48
 
47
49
  // src/cli/runner.ts
48
50
  import * as fs from "fs/promises";
49
51
  import * as path from "path";
50
52
  import { existsSync } from "node:fs";
53
+ var RENDER_SUGGESTION_FRAMES = 30;
54
+ function renderSuggestion(file, frames, count) {
55
+ if (count < RENDER_SUGGESTION_FRAMES) return null;
56
+ return `${count} frames in one process. cascade render ${file} --frames ${frames} renders them across worker processes (and encodes a preview mp4; --no-video for frames only).`;
57
+ }
51
58
  async function runGraph(options) {
52
59
  const { file, entryNode, nodes, validateOnly, checkOnly, inspectOnly, strict, verbose, frames, fps, out } = options;
53
60
  if (verbose) {
@@ -98,6 +105,10 @@ async function runGraph(options) {
98
105
  setEmbeddedCompiler(async (code) => (await compileEmbedded(project, code)).code);
99
106
  disposeStageBridge = installProjectStages(project, {
100
107
  verbose,
108
+ // A render worker starts Python while the probe frames run.
109
+ prewarm: Boolean(options.worker),
110
+ // A render worker is already one of several processes: one Python each.
111
+ ...options.worker ? { poolSize: 1 } : {},
101
112
  onTrace: (stage) => {
102
113
  trace.recordStage(stage);
103
114
  if (verbose) console.log(`[stage] ${formatStageTrace(stage)}`);
@@ -108,10 +119,11 @@ async function runGraph(options) {
108
119
  onOutput: (stage, stream, line) => {
109
120
  const text = `[${stage ?? "stage-worker"}] ${line}`;
110
121
  if (verbose) (stream === "stderr" ? console.warn : console.log)(text);
122
+ else if (stage === null) cookingNode.exit(() => trace.recordLog(stream, text));
111
123
  else trace.recordLog(stream, text);
112
124
  }
113
125
  });
114
- disposeIoBridge = installProjectIo(project);
126
+ disposeIoBridge = installProjectIo(project, { resident: true });
115
127
  } catch (error) {
116
128
  if (verbose) console.warn(`No project context for ${file}: ${error instanceof Error ? error.message : error}`);
117
129
  }
@@ -123,6 +135,10 @@ async function runGraph(options) {
123
135
  if (verbose) console.warn(canvasError.message);
124
136
  }
125
137
  const frameSpec = frames ? frames === "timeline" ? timelineFrameSpec(graphData) : parseFrameSpec(frames) : null;
138
+ if (frameSpec && frames && !options.worker) {
139
+ const hint = renderSuggestion(file, frames, frameRange(frameSpec.start, frameSpec.end, frameSpec.step ?? 1).length);
140
+ if (hint) (options.json ? console.error : console.log)(hint);
141
+ }
126
142
  const restoreConsole = captureNodeConsole(trace);
127
143
  const restoreEnv = applyEnv(options.env);
128
144
  if (options.env) trace.setEnvNames(Object.keys(options.env));
@@ -159,7 +175,11 @@ async function runGraph(options) {
159
175
  ...options.outNodes?.length ? { outNodes: options.outNodes } : {},
160
176
  ...verbose ? { verbose } : {},
161
177
  ...options.fresh ? { fresh: true } : {},
162
- ...options.worker ? { frameSource: options.worker.frameSource, onFrame: options.worker.onFrame } : {},
178
+ ...options.worker ? { frameSource: options.worker.frameSource } : {},
179
+ onFrame: async (frame) => {
180
+ trace.recordSaved(frame.durationMs);
181
+ await options.worker?.onFrame(frame);
182
+ },
163
183
  observe
164
184
  });
165
185
  if (rendered && options.worker) {
@@ -194,11 +214,15 @@ async function runGraph(options) {
194
214
  const target = options.outNodes?.length || (nodes?.length ?? 0) > 1 ? void 0 : nodes?.[0] ?? entryNode;
195
215
  const saveImages = async (graph) => {
196
216
  if (!project) return;
217
+ const saving = performance.now();
197
218
  images = await saveStillImages(graph, project, {
198
219
  ...out ? { out } : {},
199
220
  ...nodes?.length ? { nodes } : entryNode ? { nodes: [entryNode] } : {},
200
221
  ...options.outNodes?.length ? { extra: options.outNodes } : {}
201
222
  });
223
+ const runs = trace.toJSON().runs;
224
+ const cook = runs[runs.length - 1]?.ms;
225
+ if (cook !== void 0) trace.recordSaved(cook + performance.now() - saving);
202
226
  };
203
227
  if (await runDeterministicProjectGraph(file, graphData, target, observe, saveImages)) {
204
228
  if (verbose) console.log("Graph execution completed");
@@ -236,6 +260,8 @@ Run trace: ${tracePath}`;
236
260
  }
237
261
 
238
262
  export {
263
+ RENDER_SUGGESTION_FRAMES,
264
+ renderSuggestion,
239
265
  runGraph
240
266
  };
241
- //# sourceMappingURL=chunk-DUIUFVU2.js.map
267
+ //# sourceMappingURL=chunk-OBZAP3GG.js.map
@@ -0,0 +1,336 @@
1
+ import {
2
+ installHeadlessCanvas
3
+ } from "./chunk-EZRWJ4G2.js";
4
+ import {
5
+ applyScene,
6
+ exportEditor,
7
+ formatPlan,
8
+ fromBase64,
9
+ parseScene
10
+ } from "./chunk-JEW5MPGI.js";
11
+ import {
12
+ cookProjectGraph,
13
+ installProjectIo
14
+ } from "./chunk-QKL7CCRS.js";
15
+ import {
16
+ ProjectRoot
17
+ } from "./chunk-W5PJ5RMP.js";
18
+
19
+ // src/cli/editorCommands.ts
20
+ import fs from "node:fs/promises";
21
+ import path from "node:path";
22
+ var EDITOR_COMMANDS = ["export", "share", "freeze", "publish", "scene"];
23
+ var EDITOR_HELP = `
24
+ Shareable editors (see doc/EDITOR_EXPORT.md):
25
+ cascade export <graph> --plan Print what would ship, build nothing
26
+ cascade export <graph> --out <dir> [--deploy vercel [--project <name>] [--scope <team>] [--public]]
27
+ cascade share <graph | site-dir> [--name <slug>] [--scope <team>] [--protected] Deploy as cascade-<slug>, print the link
28
+ cascade publish <graph> <node>.<prop> [--id <id>] [--label <text>] [--group <name>]
29
+ [--control <kind>] [--min n] [--max n] [--step n] [--help <text>] [--remove]
30
+ cascade freeze <graph> <node> --fields <a,b.c> [--output <port>] [--personal <a,..>]
31
+ cascade scene <graph> <scene.json> --out <new-graph.cascade>
32
+ cascade run <graph> --scene <scene.json> [--write <node>.<port>=<file>]...
33
+ `;
34
+ async function runEditorCommand(args) {
35
+ const [command, graph] = args;
36
+ if (command === "share") {
37
+ const { runShareCommand } = await import("./share-B7HTCOJX.js");
38
+ return runShareCommand(args);
39
+ }
40
+ if (!graph || graph.startsWith("-")) throw new Error(`cascade ${command} requires a graph file`);
41
+ const flags = parseFlags(args.slice(2));
42
+ switch (command) {
43
+ case "export":
44
+ return exportCommand(graph, flags);
45
+ case "publish":
46
+ return publishCommand(graph, flags);
47
+ case "freeze":
48
+ return freezeCommand(graph, flags);
49
+ case "scene":
50
+ return sceneCommand(graph, flags);
51
+ case "run":
52
+ return runWithScene(graph, flags);
53
+ default:
54
+ throw new Error(`Unknown command ${command}`);
55
+ }
56
+ }
57
+ var SWITCHES = /* @__PURE__ */ new Set(["--plan", "--remove", "--json", "--public"]);
58
+ function parseFlags(args) {
59
+ const positional = [];
60
+ const values = /* @__PURE__ */ new Map();
61
+ const switches = /* @__PURE__ */ new Set();
62
+ for (let index = 0; index < args.length; index++) {
63
+ const arg = args[index];
64
+ if (!arg.startsWith("--")) {
65
+ positional.push(arg);
66
+ continue;
67
+ }
68
+ if (SWITCHES.has(arg)) {
69
+ switches.add(arg);
70
+ continue;
71
+ }
72
+ const value = args[index + 1];
73
+ if (value === void 0 || value.startsWith("--") && !/^--?\d/.test(value)) throw new Error(`${arg} needs a value`);
74
+ values.set(arg, [...values.get(arg) ?? [], value]);
75
+ index++;
76
+ }
77
+ return { positional, values, switches };
78
+ }
79
+ var one = (flags, name) => {
80
+ const list = flags.values.get(name);
81
+ return list?.[list.length - 1];
82
+ };
83
+ async function exportCommand(graph, flags) {
84
+ const deploy = one(flags, "--deploy");
85
+ if (deploy && deploy !== "vercel") throw new Error("--deploy supports vercel only");
86
+ const result = await exportEditor(graph, {
87
+ plan: flags.switches.has("--plan"),
88
+ out: one(flags, "--out"),
89
+ ...deploy ? { deploy: "vercel" } : {},
90
+ project: one(flags, "--project"),
91
+ scope: one(flags, "--scope"),
92
+ ...flags.switches.has("--public") ? { public: true } : {}
93
+ });
94
+ if (flags.switches.has("--json")) console.log(JSON.stringify(result.plan, null, 2));
95
+ else console.log(formatPlan(result.plan));
96
+ if (result.directory) console.log(`
97
+ Editor exported: ${result.directory}
98
+ Manifest: ${path.join(result.directory, "export-manifest.json")}`);
99
+ if (result.url) console.log(`Deployed: ${result.url}`);
100
+ }
101
+ async function readGraph(file) {
102
+ return JSON.parse(await fs.readFile(file, "utf8"));
103
+ }
104
+ async function writeEditor(file, document, editor) {
105
+ const next = { ...document, editor };
106
+ await fs.writeFile(file, `${JSON.stringify(next, null, 2)}
107
+ `);
108
+ }
109
+ async function publishCommand(graph, flags) {
110
+ const target = flags.positional[0];
111
+ const match = target ? /^([^.]+)\.(.+)$/.exec(target) : null;
112
+ if (!match) throw new Error("cascade publish needs <node>.<prop>");
113
+ const [, node, prop] = match;
114
+ const document = await readGraph(graph);
115
+ if (!document.nodes.some((item) => item.id === node)) throw new Error(`No node ${node} in ${graph}`);
116
+ const editor = document.editor ?? { id: path.basename(graph).replace(/\.cascade$/, "") };
117
+ const parameters = [...editor.parameters ?? []];
118
+ const id = one(flags, "--id") ?? prop;
119
+ const existing = parameters.findIndex((item) => item.node === node && item.prop === prop);
120
+ if (flags.switches.has("--remove")) {
121
+ if (existing < 0) throw new Error(`${node}.${prop} is not published`);
122
+ parameters.splice(existing, 1);
123
+ await writeEditor(graph, document, { ...editor, parameters });
124
+ console.log(`Unpublished ${node}.${prop}`);
125
+ return;
126
+ }
127
+ const number = (name) => {
128
+ const value = one(flags, name);
129
+ if (value === void 0) return void 0;
130
+ const parsed = Number(value);
131
+ if (!Number.isFinite(parsed)) throw new Error(`${name} must be a number`);
132
+ return parsed;
133
+ };
134
+ const previous = existing >= 0 ? parameters[existing] : void 0;
135
+ const parameter = {
136
+ ...previous,
137
+ id: previous?.id ?? id,
138
+ node,
139
+ prop,
140
+ ...one(flags, "--label") ? { label: one(flags, "--label") } : {},
141
+ ...one(flags, "--group") ? { group: one(flags, "--group") } : {},
142
+ ...one(flags, "--control") ? { control: one(flags, "--control") } : {},
143
+ ...number("--min") !== void 0 ? { min: number("--min") } : {},
144
+ ...number("--max") !== void 0 ? { max: number("--max") } : {},
145
+ ...number("--step") !== void 0 ? { step: number("--step") } : {},
146
+ ...number("--order") !== void 0 ? { order: number("--order") } : {},
147
+ ...one(flags, "--help") ? { help: one(flags, "--help") } : {},
148
+ ...one(flags, "--unit") ? { unit: one(flags, "--unit") } : {}
149
+ };
150
+ if (existing >= 0) parameters[existing] = parameter;
151
+ else {
152
+ if (parameters.some((item) => item.id === parameter.id)) throw new Error(`Parameter id ${parameter.id} is taken; pass --id`);
153
+ parameters.push(parameter);
154
+ }
155
+ await writeEditor(graph, document, { ...editor, parameters });
156
+ console.log(`${existing >= 0 ? "Updated" : "Published"} ${node}.${prop} as "${parameter.id}"`);
157
+ }
158
+ async function freezeCommand(graph, flags) {
159
+ const node = flags.positional[0];
160
+ if (!node) throw new Error("cascade freeze needs a node id");
161
+ const fields = (one(flags, "--fields") ?? "").split(",").map((item) => item.trim()).filter(Boolean);
162
+ if (!fields.length) throw new Error("cascade freeze needs --fields: name exactly the values to keep");
163
+ const personal = (one(flags, "--personal") ?? "").split(",").map((item) => item.trim()).filter(Boolean);
164
+ const document = await readGraph(graph);
165
+ if (!document.nodes.some((item) => item.id === node)) throw new Error(`No node ${node} in ${graph}`);
166
+ const graphFile = path.resolve(graph);
167
+ const project = new ProjectRoot(path.dirname(graphFile));
168
+ await installHeadlessCanvas().catch(() => void 0);
169
+ const removeIo = installProjectIo(project);
170
+ const { outputs } = await cookProjectGraph(graphFile, document, node).finally(removeIo);
171
+ const produced = outputs.get(node);
172
+ if (!produced) throw new Error(`${node} produced nothing`);
173
+ const output = one(flags, "--output") ?? [...produced.keys()][0];
174
+ if (!produced.has(output)) throw new Error(`${node} has no output ${output}; it has ${[...produced.keys()].join(", ")}`);
175
+ const full = toJson(produced.get(output));
176
+ const directory = path.join("frozen", node);
177
+ await fs.mkdir(project.resolve(directory), { recursive: true });
178
+ const files = [];
179
+ const pick = async (value) => {
180
+ if (value && typeof value === "object" && !Array.isArray(value) && typeof value.path === "string") {
181
+ const from = value.path;
182
+ const source = project.resolve(from.replace(/^\.\//, ""));
183
+ const name = `${directory}/${path.basename(from)}`;
184
+ await fs.copyFile(source, project.resolve(name));
185
+ files.push(name);
186
+ return { ...value, path: name };
187
+ }
188
+ if (Array.isArray(value)) return Promise.all(value.map(pick));
189
+ if (value && typeof value === "object")
190
+ return Object.fromEntries(await Promise.all(Object.entries(value).map(async ([key, item]) => [key, await pick(item)])));
191
+ return value;
192
+ };
193
+ const chosen = {};
194
+ for (const field of fields) {
195
+ const value = getPath(full, field);
196
+ if (value === void 0) throw new Error(`${node}.${output} has no field ${field}`);
197
+ setPath(chosen, field, await pick(value));
198
+ }
199
+ if (fields.includes("path") && typeof chosen.path === "string" && full && typeof full === "object" && !Array.isArray(full)) {
200
+ const { path: copied } = await pick({ path: chosen.path });
201
+ chosen.path = copied;
202
+ }
203
+ const snapshot = {
204
+ format: "cascade.frozen",
205
+ version: 1,
206
+ node,
207
+ output,
208
+ fields,
209
+ ...personal.length ? { personal } : {},
210
+ value: chosen,
211
+ ...files.length ? { files } : {},
212
+ frozenAt: (/* @__PURE__ */ new Date()).toISOString()
213
+ };
214
+ const snapshotFile = `${directory}.json`;
215
+ await fs.writeFile(project.resolve(snapshotFile), `${JSON.stringify(snapshot, null, 2)}
216
+ `);
217
+ const editor = document.editor ?? { id: path.basename(graph).replace(/\.cascade$/, "") };
218
+ const freeze = (editor.freeze ?? []).filter((item) => item.node !== node);
219
+ freeze.push({ node, output, snapshot: snapshotFile });
220
+ await writeEditor(graph, document, { ...editor, freeze });
221
+ console.log(`Froze ${node}.${output}: ${fields.join(", ")} \u2192 ${snapshotFile}${files.length ? ` (+${files.length} file${files.length > 1 ? "s" : ""})` : ""}`);
222
+ }
223
+ async function sceneCommand(graph, flags) {
224
+ const sceneFile = flags.positional[0];
225
+ const out = one(flags, "--out");
226
+ if (!sceneFile || !out) throw new Error("cascade scene needs <scene.json> --out <new-graph.cascade>");
227
+ if (path.resolve(out) === path.resolve(graph)) throw new Error("--out must be a new file; the source graph is never overwritten");
228
+ const { document, warnings } = await sceneDocument(graph, sceneFile);
229
+ await fs.writeFile(out, `${JSON.stringify(document, null, 2)}
230
+ `);
231
+ for (const warning of warnings) console.warn(`Warning: ${warning}`);
232
+ console.log(`Scene applied: ${out}`);
233
+ }
234
+ async function sceneDocument(graph, sceneFile) {
235
+ const document = await readGraph(graph);
236
+ const scene = parseScene(await fs.readFile(sceneFile, "utf8"));
237
+ const project = new ProjectRoot(path.dirname(path.resolve(graph)));
238
+ const located = /* @__PURE__ */ new Map();
239
+ for (const [id, file] of Object.entries(scene.files ?? {})) {
240
+ const found = await locateSceneFile(project, file);
241
+ if (found) located.set(id, { path: found, ...file.mediaType ? { mediaType: file.mediaType } : {} });
242
+ }
243
+ return applyScene(document, scene, { resolveFile: (parameter) => located.get(parameter.id) });
244
+ }
245
+ async function locateSceneFile(project, file) {
246
+ const { createHash } = await import("node:crypto");
247
+ const safe = path.basename(file.name).replace(/[^A-Za-z0-9._-]/g, "_");
248
+ if (file.data) {
249
+ const bytes = fromBase64(file.data);
250
+ const digest = createHash("sha256").update(bytes).digest("hex");
251
+ if (digest !== file.sha256) throw new Error(`Scene file ${file.name} does not match its hash`);
252
+ const name = `assets/scenes/${digest.slice(0, 12)}-${safe}`;
253
+ await fs.mkdir(path.dirname(project.resolve(name)), { recursive: true });
254
+ await fs.writeFile(project.resolve(name), bytes);
255
+ return name;
256
+ }
257
+ for (const candidate of await filesNamed(project.root, path.basename(file.name))) {
258
+ const digest = createHash("sha256").update(await fs.readFile(candidate)).digest("hex");
259
+ if (digest === file.sha256) return path.relative(project.root, candidate).split(path.sep).join("/");
260
+ }
261
+ return void 0;
262
+ }
263
+ async function filesNamed(root, name) {
264
+ const found = [];
265
+ const walk = async (directory, depth) => {
266
+ if (depth > 6) return;
267
+ for (const entry of await fs.readdir(directory, { withFileTypes: true }).catch(() => [])) {
268
+ if (entry.name === "node_modules" || entry.name === ".git" || entry.name === ".cascade-cache") continue;
269
+ const file = path.join(directory, entry.name);
270
+ if (entry.isDirectory()) await walk(file, depth + 1);
271
+ else if (entry.name === name) found.push(file);
272
+ }
273
+ };
274
+ await walk(path.join(root, "assets"), 0);
275
+ await walk(path.join(root, "licensed"), 0);
276
+ return found;
277
+ }
278
+ async function runWithScene(graph, flags) {
279
+ const graphFile = path.resolve(graph);
280
+ let document = await readGraph(graph);
281
+ const scene = one(flags, "--scene");
282
+ if (scene) {
283
+ const applied = await sceneDocument(graph, scene);
284
+ document = applied.document;
285
+ for (const warning of applied.warnings) console.warn(`Warning: ${warning}`);
286
+ }
287
+ await installHeadlessCanvas().catch(() => void 0);
288
+ const project = new ProjectRoot(path.dirname(graphFile));
289
+ const removeIo = installProjectIo(project);
290
+ const { outputs } = await cookProjectGraph(graphFile, document).finally(removeIo);
291
+ for (const spec of flags.values.get("--write") ?? []) {
292
+ const match = /^([^.=]+)\.([^=]+)=(.+)$/.exec(spec);
293
+ if (!match) throw new Error(`--write expects <node>.<port>=<file>, got ${spec}`);
294
+ const [, node, port, file] = match;
295
+ const value = outputs.get(node)?.get(port);
296
+ if (value === void 0) throw new Error(`${node}.${port} produced nothing`);
297
+ if (typeof value === "string") await fs.writeFile(file, value);
298
+ else if (value && typeof value === "object" && typeof value.path === "string")
299
+ await fs.copyFile(project.resolve(value.path.replace(/^\.\//, "")), file);
300
+ else await fs.writeFile(file, JSON.stringify(toJson(value)));
301
+ console.log(`Wrote ${node}.${port} \u2192 ${file}`);
302
+ }
303
+ }
304
+ function toJson(value) {
305
+ return JSON.parse(JSON.stringify(value, (_key, item) => ArrayBuffer.isView(item) ? Array.from(item) : item) ?? "null");
306
+ }
307
+ function getPath(value, field) {
308
+ let current = value;
309
+ for (const part of field.split(".").flatMap((item) => item.split(/\[(\d+)\]/).filter(Boolean))) {
310
+ if (current === null || typeof current !== "object") return void 0;
311
+ current = current[part];
312
+ }
313
+ return current;
314
+ }
315
+ function setPath(target, field, value) {
316
+ const parts = field.split(".").flatMap((item) => item.split(/\[(\d+)\]/).filter(Boolean));
317
+ let current = target;
318
+ parts.forEach((part, index) => {
319
+ const last = index === parts.length - 1;
320
+ const container = current;
321
+ if (last) {
322
+ container[part] = value;
323
+ return;
324
+ }
325
+ if (container[part] === void 0) container[part] = /^\d+$/.test(parts[index + 1]) ? [] : {};
326
+ current = container[part];
327
+ });
328
+ }
329
+
330
+ export {
331
+ EDITOR_COMMANDS,
332
+ EDITOR_HELP,
333
+ runEditorCommand,
334
+ sceneDocument
335
+ };
336
+ //# sourceMappingURL=chunk-QAGATJGE.js.map