@field/cascade 0.11.0 → 0.12.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 (216) hide show
  1. package/AGENTS.md +7 -4
  2. package/ARCHITECTURE.md +5 -4
  3. package/CHANGELOG.md +195 -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-RQZ4SX5R.js → chunk-2JEU3MGB.js} +2 -2
  73. package/dist/cli/{chunk-WKURDVYP.js → chunk-3RXL5GHB.js} +28 -331
  74. package/dist/cli/chunk-53LGFPWX.js +138 -0
  75. package/dist/cli/chunk-5ORUQ6ES.js +267 -0
  76. package/dist/cli/chunk-73S4JZXK.js +336 -0
  77. package/dist/cli/{chunk-YAT5Y25U.js → chunk-7QE2ZNSN.js} +3 -3
  78. package/dist/cli/{chunk-SGJAET2L.js → chunk-BPODRH52.js} +8 -4
  79. package/dist/cli/{chunk-XWDSKG7N.js → chunk-BZ63XQ3I.js} +2 -2
  80. package/dist/cli/{chunk-FCDXJ6EK.js → chunk-CTPLRSWF.js} +2 -13
  81. package/dist/cli/chunk-EU4ZFZ7Y.js +122 -0
  82. package/dist/cli/{chunk-WIH4TEIS.js → chunk-EZRWJ4G2.js} +1 -1
  83. package/dist/cli/{chunk-BIJO5OFS.js → chunk-FWMSGKEH.js} +1 -1
  84. package/dist/cli/{chunk-POMMNKZZ.js → chunk-G23A2BZZ.js} +38 -5
  85. package/dist/cli/{chunk-35D343DC.js → chunk-GRIN2DDP.js} +19 -1
  86. package/dist/cli/chunk-JVOCK5W4.js +83 -0
  87. package/dist/cli/chunk-KBGUO27Q.js +15 -0
  88. package/dist/cli/{chunk-GFS6W3C5.js → chunk-L6B6FUQP.js} +54 -32
  89. package/dist/cli/chunk-LTXWBXOM.js +438 -0
  90. package/dist/cli/chunk-MI5JLQYA.js +220 -0
  91. package/dist/cli/{chunk-EUIAC3U3.js → chunk-MP653A5O.js} +884 -124
  92. package/dist/cli/{chunk-A4OAMOZH.js → chunk-NJFMDXHR.js} +13 -3
  93. package/dist/cli/{chunk-3JTJHF7V.js → chunk-OM6BEME6.js} +4 -4
  94. package/dist/cli/{chunk-DUIUFVU2.js → chunk-ST6LROUG.js} +49 -23
  95. package/dist/cli/{chunk-RLCAJBVR.js → chunk-TJW67QSI.js} +8 -7
  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-JJN5Z3ZC.js} +23 -17
  102. package/dist/cli/{commands-Q23M26QM.js → commands-PHYFYA3F.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-ORWQRNYP.js} +36 -6
  106. package/dist/cli/{edit-JHCPTQFT.js → edit-S5KEQ5RA.js} +3 -3
  107. package/dist/cli/editorCommands-NFR32JOY.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-EOUOBOIJ.js} +4 -4
  114. package/dist/cli/{graphFile-VPSQ6OF7.js → graphFile-YTGJB5F5.js} +25 -19
  115. package/dist/cli/{headlessCanvas-5XLDZT7M.js → headlessCanvas-ENRHC7GS.js} +3 -2
  116. package/dist/cli/{helpCommand-3VRE3KVO.js → helpCommand-PULH6ZYB.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-JVZFQOEX.js} +4 -4
  121. package/dist/cli/{parity-KINARCP2.js → parity-X6DEH2DV.js} +42 -36
  122. package/dist/cli/{pop-USDFXB7H.js → pop-B7SIIYKN.js} +5 -5
  123. package/dist/cli/project-VYGIA7JX.js +12 -0
  124. package/dist/cli/{projectDefinition-DMTZN2NE.js → projectDefinition-F3D7UIHJ.js} +8 -8
  125. package/dist/cli/{projectRuntime-Z5PJOH47.js → projectRuntime-2Q4JKEH3.js} +19 -12
  126. package/dist/cli/registerStandardNodes-BXJCB6E4.js +744 -0
  127. package/dist/cli/{render-YURAPPUI.js → render-PT4MWI6U.js} +24 -22
  128. package/dist/cli/{runCommand-PI764LEG.js → runCommand-HNPQVZMS.js} +3 -3
  129. package/dist/cli/runner-5XPXVW7J.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-MHF6TNRK.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-WPPMGGW7.js +44 -0
  138. package/dist/contracts/capabilities.d.ts +18 -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 +3 -2
  152. package/dist/player/editor.js +3 -2
  153. package/dist/player/editorEngine.js +3 -2
  154. package/dist/runtime/builtins/assert/checks.d.ts +18 -0
  155. package/dist/runtime/builtins/assert/checks.js +207 -0
  156. package/dist/runtime/builtins/assert/definitions.d.ts +130 -0
  157. package/dist/runtime/builtins/assert/definitions.js +54 -0
  158. package/dist/runtime/builtins/assert/index.d.ts +152 -0
  159. package/dist/runtime/builtins/assert/index.js +36 -0
  160. package/dist/runtime/builtins/image/contactSheet.d.ts +3 -165
  161. package/dist/runtime/builtins/image/contactSheet.js +0 -49
  162. package/dist/runtime/builtins/image/definitions.d.ts +950 -0
  163. package/dist/runtime/builtins/image/definitions.js +343 -0
  164. package/dist/runtime/builtins/image/file.d.ts +8 -0
  165. package/dist/runtime/builtins/image/file.js +37 -0
  166. package/dist/runtime/builtins/image/filters.d.ts +8 -0
  167. package/dist/runtime/builtins/image/filters.js +394 -0
  168. package/dist/runtime/builtins/image/generators.d.ts +7 -0
  169. package/dist/runtime/builtins/image/generators.js +144 -0
  170. package/dist/runtime/builtins/image/index.d.ts +947 -4
  171. package/dist/runtime/builtins/image/index.js +41 -6
  172. package/dist/runtime/builtins/image/io.d.ts +100 -0
  173. package/dist/runtime/builtins/image/io.js +142 -0
  174. package/dist/runtime/builtins/image/planar.d.ts +55 -0
  175. package/dist/runtime/builtins/image/planar.js +274 -0
  176. package/dist/runtime/builtins/image/simplex.d.ts +22 -0
  177. package/dist/runtime/builtins/image/simplex.js +92 -0
  178. package/dist/runtime/builtins/image/text.d.ts +4 -0
  179. package/dist/runtime/builtins/image/text.js +141 -0
  180. package/dist/runtime/builtins/index.d.ts +3 -0
  181. package/dist/runtime/builtins/index.js +7 -0
  182. package/dist/runtime/gpu.d.ts +25 -0
  183. package/dist/runtime/gpu.js +40 -0
  184. package/dist/runtime/runtime.d.ts +2 -0
  185. package/dist/runtime/runtime.js +268 -44
  186. package/dist/runtime/types.d.ts +30 -0
  187. package/doc/CLI_REFERENCE.md +21 -3
  188. package/doc/EDITOR_EXPORT.md +26 -0
  189. package/doc/MCP.md +2 -1
  190. package/doc/NODE_REFERENCE.md +286 -14
  191. package/doc/PROJECT_AUTHORING.md +98 -3
  192. package/package.json +4 -1
  193. package/dist/assets/CodePanel-BU-autmq.js +0 -2
  194. package/dist/assets/CustomNodeDialog-BRpDfOV5.js +0 -1
  195. package/dist/assets/OpenGraphDialog-BwbRlC0X.js +0 -1
  196. package/dist/assets/ProjectSettings-DLOXLxcA.js +0 -2
  197. package/dist/assets/definitions-GreEMXLV.js +0 -1
  198. package/dist/assets/geo-u0n_jOsD.js +0 -1
  199. package/dist/assets/image-cAa-Nu9O.js +0 -2062
  200. package/dist/assets/index-5ZtDAuy2.js +0 -220
  201. package/dist/assets/index-7vae6YFC.css +0 -1
  202. package/dist/assets/math-DioIQOPJ.js +0 -1
  203. package/dist/assets/pop-DC_TQG9t.js +0 -1
  204. package/dist/cli/chunk-36P5K3SD.js +0 -631
  205. package/dist/cli/chunk-4UDFYF5G.js +0 -132
  206. package/dist/cli/chunk-53NMBJAO.js +0 -35
  207. package/dist/cli/chunk-MIIEFUGG.js +0 -623
  208. package/dist/cli/chunk-QBBKRNGY.js +0 -90
  209. package/dist/cli/chunk-WPUDGEFY.js +0 -20
  210. package/dist/cli/editorCommands-UIF5YLUZ.js +0 -33
  211. package/dist/cli/project-K3QIPGBB.js +0 -12
  212. package/dist/cli/registerStandardNodes-S2VLMF6J.js +0 -3081
  213. package/dist/cli/runner-RA6UKEPD.js +0 -37
  214. package/dist/cli/webBuild-GT4PZT5D.js +0 -37
  215. package/dist/runtime/builtins/headless.d.ts +0 -19
  216. package/dist/runtime/builtins/headless.js +0 -25
@@ -5,30 +5,21 @@ import {
5
5
  inside,
6
6
  playerFile,
7
7
  staticRuntimePlugin
8
- } from "./chunk-3JTJHF7V.js";
8
+ } from "./chunk-OM6BEME6.js";
9
9
  import {
10
10
  PREFLIGHT_WARNING_CODES,
11
- cookProjectGraph,
12
11
  createRuntime,
13
- installProjectIo,
14
12
  moduleId,
15
13
  projectModuleFile,
16
14
  validateNodeModuleArchitecture
17
- } from "./chunk-EUIAC3U3.js";
15
+ } from "./chunk-MP653A5O.js";
18
16
  import {
19
17
  builtinNodeRegistration
20
- } from "./chunk-36P5K3SD.js";
18
+ } from "./chunk-MI5JLQYA.js";
21
19
  import {
22
20
  ProjectRoot,
23
21
  extractNodeDefinition
24
- } from "./chunk-RQZ4SX5R.js";
25
- import {
26
- installHeadlessCanvas
27
- } from "./chunk-WIH4TEIS.js";
28
-
29
- // src/cli/editorCommands.ts
30
- import fs2 from "node:fs/promises";
31
- import path2 from "node:path";
22
+ } from "./chunk-2JEU3MGB.js";
32
23
 
33
24
  // packages/runtime/src/editor/index.ts
34
25
  var SCENE_FORMAT = "cascade.scene";
@@ -335,7 +326,8 @@ async function exportEditor(file, options = {}) {
335
326
  const directory = await buildSite(prepared, options.out);
336
327
  const plan = JSON.parse(await fs.readFile(path.join(directory, "export-manifest.json"), "utf8"));
337
328
  let url;
338
- if (options.deploy === "vercel") url = await deployVercel(directory, options.project ?? prepared.plan.editor.id, options.scope, log, options.public === true);
329
+ if (options.deploy === "vercel")
330
+ url = (await deployVercel(directory, { project: options.project ?? prepared.plan.editor.id, scope: options.scope, public: options.public === true, log })).deployment;
339
331
  return { plan, directory, ...url ? { url } : {} };
340
332
  }
341
333
  async function prepareExport(file) {
@@ -659,7 +651,7 @@ async function secretValues(projectRoot) {
659
651
  values.set("your home directory", os.homedir());
660
652
  return [...values];
661
653
  }
662
- async function deployVercel(directory, projectName, scope, log, intendedPublic) {
654
+ var deployVercel = async (directory, { project: projectName, scope, public: intendedPublic, log }) => {
663
655
  const team = scope ?? "field-io";
664
656
  const scratch = await fs.mkdtemp(path.join(os.tmpdir(), "cascade-vercel-"));
665
657
  try {
@@ -671,16 +663,21 @@ async function deployVercel(directory, projectName, scope, log, intendedPublic)
671
663
  await fs.rm(scratch, { recursive: true, force: true });
672
664
  }
673
665
  log(`Deploying ${directory} to Vercel project ${team}/${projectName}\u2026`);
674
- const { stdout } = await run("vercel", ["deploy", "--prod", "--yes", "--scope", team], { cwd: directory, maxBuffer: 16 * 1024 * 1024 });
675
- const url = stdout.trim().split("\n").reverse().find((line) => /^https:\/\//.test(line.trim()))?.trim();
676
- if (!url) throw new Error(`Vercel did not report a deployment URL:
666
+ const { stdout, stderr } = await run("vercel", ["deploy", "--prod", "--yes", "--scope", team], { cwd: directory, maxBuffer: 16 * 1024 * 1024 });
667
+ const deployment = stdout.trim().split("\n").reverse().find((line) => /^https:\/\//.test(line.trim()))?.trim();
668
+ if (!deployment) throw new Error(`Vercel did not report a deployment URL:
677
669
  ${stdout}`);
678
- const response = await fetch(`https://${projectName}.vercel.app/`, { redirect: "manual" });
670
+ const url = productionAlias(`${stdout}
671
+ ${stderr}`) ?? `https://${projectName}.vercel.app`;
672
+ const response = await fetch(`${url}/`, { redirect: "manual" });
679
673
  const verdict = publicVerdict(response.status, intendedPublic);
680
- if (verdict === "public-as-intended") log(`Public, as intended (--public): https://${projectName}.vercel.app`);
674
+ if (verdict === "public-as-intended") log(`Public, as intended: ${url}`);
681
675
  else if (verdict === "public-by-accident")
682
- throw new Error(`Deployed, but https://${projectName}.vercel.app is publicly readable. Turn on Vercel Authentication (Settings \u2192 Deployment Protection) for ${team}/${projectName}.`);
683
- return url;
676
+ throw new Error(`Deployed, but ${url} is publicly readable. Turn on Vercel Authentication (Settings \u2192 Deployment Protection) for ${team}/${projectName}.`);
677
+ return { deployment, url };
678
+ };
679
+ function productionAlias(output) {
680
+ return /Aliased:?\s+(https:\/\/[^\s[\]]+)/.exec(output)?.[1]?.replace(/\/$/, "");
684
681
  }
685
682
  function publicVerdict(status, intendedPublic) {
686
683
  if (status !== 200) return "protected";
@@ -806,314 +803,14 @@ function formatSize(bytes) {
806
803
  return bytes >= 1048576 ? `${(bytes / 1048576).toFixed(1)} MB` : bytes >= 1024 ? `${Math.round(bytes / 1024)} KB` : `${bytes} B`;
807
804
  }
808
805
 
809
- // src/cli/editorCommands.ts
810
- var EDITOR_COMMANDS = ["export", "freeze", "publish", "scene"];
811
- var EDITOR_HELP = `
812
- Shareable editors (see doc/EDITOR_EXPORT.md):
813
- cascade export <graph> --plan Print what would ship, build nothing
814
- cascade export <graph> --out <dir> [--deploy vercel [--project <name>] [--scope <team>] [--public]]
815
- cascade publish <graph> <node>.<prop> [--id <id>] [--label <text>] [--group <name>]
816
- [--control <kind>] [--min n] [--max n] [--step n] [--help <text>] [--remove]
817
- cascade freeze <graph> <node> --fields <a,b.c> [--output <port>] [--personal <a,..>]
818
- cascade scene <graph> <scene.json> --out <new-graph.cascade>
819
- cascade run <graph> --scene <scene.json> [--write <node>.<port>=<file>]...
820
- `;
821
- async function runEditorCommand(args) {
822
- const [command, graph] = args;
823
- if (!graph || graph.startsWith("-")) throw new Error(`cascade ${command} requires a graph file`);
824
- const flags = parseFlags(args.slice(2));
825
- switch (command) {
826
- case "export":
827
- return exportCommand(graph, flags);
828
- case "publish":
829
- return publishCommand(graph, flags);
830
- case "freeze":
831
- return freezeCommand(graph, flags);
832
- case "scene":
833
- return sceneCommand(graph, flags);
834
- case "run":
835
- return runWithScene(graph, flags);
836
- default:
837
- throw new Error(`Unknown command ${command}`);
838
- }
839
- }
840
- var SWITCHES = /* @__PURE__ */ new Set(["--plan", "--remove", "--json", "--public"]);
841
- function parseFlags(args) {
842
- const positional = [];
843
- const values = /* @__PURE__ */ new Map();
844
- const switches = /* @__PURE__ */ new Set();
845
- for (let index = 0; index < args.length; index++) {
846
- const arg = args[index];
847
- if (!arg.startsWith("--")) {
848
- positional.push(arg);
849
- continue;
850
- }
851
- if (SWITCHES.has(arg)) {
852
- switches.add(arg);
853
- continue;
854
- }
855
- const value = args[index + 1];
856
- if (value === void 0 || value.startsWith("--") && !/^--?\d/.test(value)) throw new Error(`${arg} needs a value`);
857
- values.set(arg, [...values.get(arg) ?? [], value]);
858
- index++;
859
- }
860
- return { positional, values, switches };
861
- }
862
- var one = (flags, name) => {
863
- const list = flags.values.get(name);
864
- return list?.[list.length - 1];
865
- };
866
- async function exportCommand(graph, flags) {
867
- const deploy = one(flags, "--deploy");
868
- if (deploy && deploy !== "vercel") throw new Error("--deploy supports vercel only");
869
- const result = await exportEditor(graph, {
870
- plan: flags.switches.has("--plan"),
871
- out: one(flags, "--out"),
872
- ...deploy ? { deploy: "vercel" } : {},
873
- project: one(flags, "--project"),
874
- scope: one(flags, "--scope"),
875
- ...flags.switches.has("--public") ? { public: true } : {}
876
- });
877
- if (flags.switches.has("--json")) console.log(JSON.stringify(result.plan, null, 2));
878
- else console.log(formatPlan(result.plan));
879
- if (result.directory) console.log(`
880
- Editor exported: ${result.directory}
881
- Manifest: ${path2.join(result.directory, "export-manifest.json")}`);
882
- if (result.url) console.log(`Deployed: ${result.url}`);
883
- }
884
- async function readGraph(file) {
885
- return JSON.parse(await fs2.readFile(file, "utf8"));
886
- }
887
- async function writeEditor(file, document, editor) {
888
- const next = { ...document, editor };
889
- await fs2.writeFile(file, `${JSON.stringify(next, null, 2)}
890
- `);
891
- }
892
- async function publishCommand(graph, flags) {
893
- const target = flags.positional[0];
894
- const match = target ? /^([^.]+)\.(.+)$/.exec(target) : null;
895
- if (!match) throw new Error("cascade publish needs <node>.<prop>");
896
- const [, node, prop] = match;
897
- const document = await readGraph(graph);
898
- if (!document.nodes.some((item) => item.id === node)) throw new Error(`No node ${node} in ${graph}`);
899
- const editor = document.editor ?? { id: path2.basename(graph).replace(/\.cascade$/, "") };
900
- const parameters = [...editor.parameters ?? []];
901
- const id = one(flags, "--id") ?? prop;
902
- const existing = parameters.findIndex((item) => item.node === node && item.prop === prop);
903
- if (flags.switches.has("--remove")) {
904
- if (existing < 0) throw new Error(`${node}.${prop} is not published`);
905
- parameters.splice(existing, 1);
906
- await writeEditor(graph, document, { ...editor, parameters });
907
- console.log(`Unpublished ${node}.${prop}`);
908
- return;
909
- }
910
- const number = (name) => {
911
- const value = one(flags, name);
912
- if (value === void 0) return void 0;
913
- const parsed = Number(value);
914
- if (!Number.isFinite(parsed)) throw new Error(`${name} must be a number`);
915
- return parsed;
916
- };
917
- const previous = existing >= 0 ? parameters[existing] : void 0;
918
- const parameter = {
919
- ...previous,
920
- id: previous?.id ?? id,
921
- node,
922
- prop,
923
- ...one(flags, "--label") ? { label: one(flags, "--label") } : {},
924
- ...one(flags, "--group") ? { group: one(flags, "--group") } : {},
925
- ...one(flags, "--control") ? { control: one(flags, "--control") } : {},
926
- ...number("--min") !== void 0 ? { min: number("--min") } : {},
927
- ...number("--max") !== void 0 ? { max: number("--max") } : {},
928
- ...number("--step") !== void 0 ? { step: number("--step") } : {},
929
- ...number("--order") !== void 0 ? { order: number("--order") } : {},
930
- ...one(flags, "--help") ? { help: one(flags, "--help") } : {},
931
- ...one(flags, "--unit") ? { unit: one(flags, "--unit") } : {}
932
- };
933
- if (existing >= 0) parameters[existing] = parameter;
934
- else {
935
- if (parameters.some((item) => item.id === parameter.id)) throw new Error(`Parameter id ${parameter.id} is taken; pass --id`);
936
- parameters.push(parameter);
937
- }
938
- await writeEditor(graph, document, { ...editor, parameters });
939
- console.log(`${existing >= 0 ? "Updated" : "Published"} ${node}.${prop} as "${parameter.id}"`);
940
- }
941
- async function freezeCommand(graph, flags) {
942
- const node = flags.positional[0];
943
- if (!node) throw new Error("cascade freeze needs a node id");
944
- const fields = (one(flags, "--fields") ?? "").split(",").map((item) => item.trim()).filter(Boolean);
945
- if (!fields.length) throw new Error("cascade freeze needs --fields: name exactly the values to keep");
946
- const personal = (one(flags, "--personal") ?? "").split(",").map((item) => item.trim()).filter(Boolean);
947
- const document = await readGraph(graph);
948
- if (!document.nodes.some((item) => item.id === node)) throw new Error(`No node ${node} in ${graph}`);
949
- const graphFile = path2.resolve(graph);
950
- const project = new ProjectRoot(path2.dirname(graphFile));
951
- await installHeadlessCanvas().catch(() => void 0);
952
- const removeIo = installProjectIo(project);
953
- const { outputs } = await cookProjectGraph(graphFile, document, node).finally(removeIo);
954
- const produced = outputs.get(node);
955
- if (!produced) throw new Error(`${node} produced nothing`);
956
- const output = one(flags, "--output") ?? [...produced.keys()][0];
957
- if (!produced.has(output)) throw new Error(`${node} has no output ${output}; it has ${[...produced.keys()].join(", ")}`);
958
- const full = toJson(produced.get(output));
959
- const directory = path2.join("frozen", node);
960
- await fs2.mkdir(project.resolve(directory), { recursive: true });
961
- const files = [];
962
- const pick = async (value) => {
963
- if (value && typeof value === "object" && !Array.isArray(value) && typeof value.path === "string") {
964
- const from = value.path;
965
- const source = project.resolve(from.replace(/^\.\//, ""));
966
- const name = `${directory}/${path2.basename(from)}`;
967
- await fs2.copyFile(source, project.resolve(name));
968
- files.push(name);
969
- return { ...value, path: name };
970
- }
971
- if (Array.isArray(value)) return Promise.all(value.map(pick));
972
- if (value && typeof value === "object")
973
- return Object.fromEntries(await Promise.all(Object.entries(value).map(async ([key, item]) => [key, await pick(item)])));
974
- return value;
975
- };
976
- const chosen = {};
977
- for (const field of fields) {
978
- const value = getPath(full, field);
979
- if (value === void 0) throw new Error(`${node}.${output} has no field ${field}`);
980
- setPath(chosen, field, await pick(value));
981
- }
982
- if (fields.includes("path") && typeof chosen.path === "string" && full && typeof full === "object" && !Array.isArray(full)) {
983
- const { path: copied } = await pick({ path: chosen.path });
984
- chosen.path = copied;
985
- }
986
- const snapshot = {
987
- format: "cascade.frozen",
988
- version: 1,
989
- node,
990
- output,
991
- fields,
992
- ...personal.length ? { personal } : {},
993
- value: chosen,
994
- ...files.length ? { files } : {},
995
- frozenAt: (/* @__PURE__ */ new Date()).toISOString()
996
- };
997
- const snapshotFile = `${directory}.json`;
998
- await fs2.writeFile(project.resolve(snapshotFile), `${JSON.stringify(snapshot, null, 2)}
999
- `);
1000
- const editor = document.editor ?? { id: path2.basename(graph).replace(/\.cascade$/, "") };
1001
- const freeze = (editor.freeze ?? []).filter((item) => item.node !== node);
1002
- freeze.push({ node, output, snapshot: snapshotFile });
1003
- await writeEditor(graph, document, { ...editor, freeze });
1004
- console.log(`Froze ${node}.${output}: ${fields.join(", ")} \u2192 ${snapshotFile}${files.length ? ` (+${files.length} file${files.length > 1 ? "s" : ""})` : ""}`);
1005
- }
1006
- async function sceneCommand(graph, flags) {
1007
- const sceneFile = flags.positional[0];
1008
- const out = one(flags, "--out");
1009
- if (!sceneFile || !out) throw new Error("cascade scene needs <scene.json> --out <new-graph.cascade>");
1010
- if (path2.resolve(out) === path2.resolve(graph)) throw new Error("--out must be a new file; the source graph is never overwritten");
1011
- const { document, warnings } = await sceneDocument(graph, sceneFile);
1012
- await fs2.writeFile(out, `${JSON.stringify(document, null, 2)}
1013
- `);
1014
- for (const warning of warnings) console.warn(`Warning: ${warning}`);
1015
- console.log(`Scene applied: ${out}`);
1016
- }
1017
- async function sceneDocument(graph, sceneFile) {
1018
- const document = await readGraph(graph);
1019
- const scene = parseScene(await fs2.readFile(sceneFile, "utf8"));
1020
- const project = new ProjectRoot(path2.dirname(path2.resolve(graph)));
1021
- const located = /* @__PURE__ */ new Map();
1022
- for (const [id, file] of Object.entries(scene.files ?? {})) {
1023
- const found = await locateSceneFile(project, file);
1024
- if (found) located.set(id, { path: found, ...file.mediaType ? { mediaType: file.mediaType } : {} });
1025
- }
1026
- return applyScene(document, scene, { resolveFile: (parameter) => located.get(parameter.id) });
1027
- }
1028
- async function locateSceneFile(project, file) {
1029
- const { createHash } = await import("node:crypto");
1030
- const safe = path2.basename(file.name).replace(/[^A-Za-z0-9._-]/g, "_");
1031
- if (file.data) {
1032
- const bytes = fromBase64(file.data);
1033
- const digest = createHash("sha256").update(bytes).digest("hex");
1034
- if (digest !== file.sha256) throw new Error(`Scene file ${file.name} does not match its hash`);
1035
- const name = `assets/scenes/${digest.slice(0, 12)}-${safe}`;
1036
- await fs2.mkdir(path2.dirname(project.resolve(name)), { recursive: true });
1037
- await fs2.writeFile(project.resolve(name), bytes);
1038
- return name;
1039
- }
1040
- for (const candidate of await filesNamed(project.root, path2.basename(file.name))) {
1041
- const digest = createHash("sha256").update(await fs2.readFile(candidate)).digest("hex");
1042
- if (digest === file.sha256) return path2.relative(project.root, candidate).split(path2.sep).join("/");
1043
- }
1044
- return void 0;
1045
- }
1046
- async function filesNamed(root, name) {
1047
- const found = [];
1048
- const walk = async (directory, depth) => {
1049
- if (depth > 6) return;
1050
- for (const entry of await fs2.readdir(directory, { withFileTypes: true }).catch(() => [])) {
1051
- if (entry.name === "node_modules" || entry.name === ".git" || entry.name === ".cascade-cache") continue;
1052
- const file = path2.join(directory, entry.name);
1053
- if (entry.isDirectory()) await walk(file, depth + 1);
1054
- else if (entry.name === name) found.push(file);
1055
- }
1056
- };
1057
- await walk(path2.join(root, "assets"), 0);
1058
- await walk(path2.join(root, "licensed"), 0);
1059
- return found;
1060
- }
1061
- async function runWithScene(graph, flags) {
1062
- const graphFile = path2.resolve(graph);
1063
- let document = await readGraph(graph);
1064
- const scene = one(flags, "--scene");
1065
- if (scene) {
1066
- const applied = await sceneDocument(graph, scene);
1067
- document = applied.document;
1068
- for (const warning of applied.warnings) console.warn(`Warning: ${warning}`);
1069
- }
1070
- await installHeadlessCanvas().catch(() => void 0);
1071
- const project = new ProjectRoot(path2.dirname(graphFile));
1072
- const removeIo = installProjectIo(project);
1073
- const { outputs } = await cookProjectGraph(graphFile, document).finally(removeIo);
1074
- for (const spec of flags.values.get("--write") ?? []) {
1075
- const match = /^([^.=]+)\.([^=]+)=(.+)$/.exec(spec);
1076
- if (!match) throw new Error(`--write expects <node>.<port>=<file>, got ${spec}`);
1077
- const [, node, port, file] = match;
1078
- const value = outputs.get(node)?.get(port);
1079
- if (value === void 0) throw new Error(`${node}.${port} produced nothing`);
1080
- if (typeof value === "string") await fs2.writeFile(file, value);
1081
- else if (value && typeof value === "object" && typeof value.path === "string")
1082
- await fs2.copyFile(project.resolve(value.path.replace(/^\.\//, "")), file);
1083
- else await fs2.writeFile(file, JSON.stringify(toJson(value)));
1084
- console.log(`Wrote ${node}.${port} \u2192 ${file}`);
1085
- }
1086
- }
1087
- function toJson(value) {
1088
- return JSON.parse(JSON.stringify(value, (_key, item) => ArrayBuffer.isView(item) ? Array.from(item) : item) ?? "null");
1089
- }
1090
- function getPath(value, field) {
1091
- let current = value;
1092
- for (const part of field.split(".").flatMap((item) => item.split(/\[(\d+)\]/).filter(Boolean))) {
1093
- if (current === null || typeof current !== "object") return void 0;
1094
- current = current[part];
1095
- }
1096
- return current;
1097
- }
1098
- function setPath(target, field, value) {
1099
- const parts = field.split(".").flatMap((item) => item.split(/\[(\d+)\]/).filter(Boolean));
1100
- let current = target;
1101
- parts.forEach((part, index) => {
1102
- const last = index === parts.length - 1;
1103
- const container = current;
1104
- if (last) {
1105
- container[part] = value;
1106
- return;
1107
- }
1108
- if (container[part] === void 0) container[part] = /^\d+$/.test(parts[index + 1]) ? [] : {};
1109
- current = container[part];
1110
- });
1111
- }
1112
-
1113
806
  export {
1114
- EDITOR_COMMANDS,
1115
- EDITOR_HELP,
1116
- runEditorCommand,
1117
- sceneDocument
807
+ parseScene,
808
+ applyScene,
809
+ fromBase64,
810
+ exportEditor,
811
+ formatPlan,
812
+ vercelConfig,
813
+ scanForLeaks,
814
+ deployVercel
1118
815
  };
1119
- //# sourceMappingURL=chunk-WKURDVYP.js.map
816
+ //# sourceMappingURL=chunk-3RXL5GHB.js.map
@@ -0,0 +1,138 @@
1
+ // packages/runtime/src/builtins/image/contactSheet.ts
2
+ function contactSheetLayout(sizes, options = {}) {
3
+ const count = sizes.length;
4
+ if (!count) throw new Error("cascade.image.ContactSheet needs at least one image");
5
+ for (const [width, height] of sizes) {
6
+ if (!(width > 0 && height > 0)) throw new Error(`cascade.image.ContactSheet: an image is ${width}x${height}`);
7
+ }
8
+ const gap = Math.max(0, Math.round(options.gap ?? 16));
9
+ const columns = Math.min(count, options.columns && options.columns > 0 ? Math.round(options.columns) : Math.ceil(Math.sqrt(count)));
10
+ const rows = Math.ceil(count / columns);
11
+ const scaled = Boolean(options.cellWidth && options.cellWidth > 0);
12
+ const cellWidth = scaled ? Math.round(options.cellWidth) : Math.max(...sizes.map(([width]) => width));
13
+ const cellHeight = scaled ? Math.max(...sizes.map(([width, height]) => Math.round(height * cellWidth / width))) : Math.max(...sizes.map(([, height]) => height));
14
+ const labelHeight = Math.max(0, Math.round(options.labelHeight ?? 0));
15
+ const cells = sizes.map(([width, height], index) => {
16
+ const column = index % columns;
17
+ const row = Math.floor(index / columns);
18
+ const fit = Math.min(cellWidth / width, cellHeight / height);
19
+ const scale = scaled ? fit : Math.min(1, fit);
20
+ const drawnWidth = Math.max(1, Math.round(width * scale));
21
+ const drawnHeight = Math.max(1, Math.round(height * scale));
22
+ const left = gap + column * (cellWidth + gap);
23
+ const top = gap + row * (cellHeight + labelHeight + gap);
24
+ return {
25
+ x: left + Math.floor((cellWidth - drawnWidth) / 2),
26
+ y: top + Math.floor((cellHeight - drawnHeight) / 2),
27
+ width: drawnWidth,
28
+ height: drawnHeight,
29
+ labelX: left,
30
+ labelY: top + cellHeight
31
+ };
32
+ });
33
+ return {
34
+ width: gap + columns * (cellWidth + gap),
35
+ height: gap + rows * (cellHeight + labelHeight + gap),
36
+ columns,
37
+ rows,
38
+ cell: [cellWidth, cellHeight],
39
+ labelHeight,
40
+ cells
41
+ };
42
+ }
43
+ function host() {
44
+ const globals = globalThis;
45
+ const Canvas = globals.OffscreenCanvas;
46
+ const createBitmap = globals.createImageBitmap;
47
+ if (!Canvas || !createBitmap || !globals.Blob) {
48
+ throw new Error("cascade.image.ContactSheet needs OffscreenCanvas and createImageBitmap, which this host does not provide");
49
+ }
50
+ const BlobType = globals.Blob;
51
+ return {
52
+ surface: (width, height) => new Canvas(width, height),
53
+ decode: (bytes) => createBitmap(new BlobType([bytes]))
54
+ };
55
+ }
56
+ function css(colour, fallback) {
57
+ const [r = 0, g = 0, b = 0, a = 1] = colour ?? fallback;
58
+ return `rgba(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(b * 255)}, ${a})`;
59
+ }
60
+ async function composeContactSheet(images, options = {}) {
61
+ const canvas = host();
62
+ const bitmaps = [];
63
+ try {
64
+ for (const bytes of images) bitmaps.push(await canvas.decode(bytes));
65
+ const labels = options.labels ?? [];
66
+ const labelSize = Math.round(options.labelSize ?? 14);
67
+ const labelled = labels.some((label) => label.trim().length > 0);
68
+ const layout = contactSheetLayout(bitmaps.map((bitmap) => [bitmap.width, bitmap.height]), {
69
+ columns: options.columns,
70
+ gap: options.gap,
71
+ cellWidth: options.cellWidth,
72
+ labelHeight: labelled ? Math.round(labelSize * 1.8) : 0
73
+ });
74
+ const surface = canvas.surface(layout.width, layout.height);
75
+ const context = surface.getContext("2d");
76
+ if (!context) throw new Error("cascade.image.ContactSheet: no 2D context");
77
+ context.fillStyle = css(options.background, [1, 1, 1, 1]);
78
+ context.fillRect(0, 0, layout.width, layout.height);
79
+ context.imageSmoothingEnabled = true;
80
+ context.imageSmoothingQuality = "high";
81
+ layout.cells.forEach((cell, index) => context.drawImage(bitmaps[index], cell.x, cell.y, cell.width, cell.height));
82
+ if (labelled) {
83
+ context.fillStyle = css(options.labelColor, [0.1, 0.1, 0.1, 1]);
84
+ context.font = `${labelSize}px sans-serif`;
85
+ context.textBaseline = "top";
86
+ layout.cells.forEach((cell, index) => {
87
+ const label = labels[index];
88
+ if (label) context.fillText(label, cell.labelX, cell.labelY + Math.round((layout.labelHeight - labelSize) / 2), layout.cell[0]);
89
+ });
90
+ }
91
+ const blob = await surface.convertToBlob({ type: "image/png" });
92
+ return { bytes: new Uint8Array(await blob.arrayBuffer()), layout };
93
+ } finally {
94
+ for (const bitmap of bitmaps) bitmap.close?.();
95
+ }
96
+ }
97
+ function imageReference(value) {
98
+ if (typeof value === "string" && value) return { path: value };
99
+ const candidate = value?.path;
100
+ return typeof candidate === "string" && candidate ? { path: candidate } : void 0;
101
+ }
102
+ async function executeContactSheet(context) {
103
+ const { props } = context;
104
+ const references = [...context.inputs.images, ...context.inputs.list ?? []].map(imageReference).filter((reference) => reference !== void 0);
105
+ const bytes = [];
106
+ for (const reference of references) {
107
+ bytes.push(await context.capabilities.assets.read(reference, { signal: context.signal }));
108
+ }
109
+ const { bytes: sheet, layout } = await composeContactSheet(bytes, {
110
+ columns: props.columns,
111
+ gap: props.gap,
112
+ cellWidth: props.cellWidth,
113
+ labels: props.labels ? props.labels.split("\n") : [],
114
+ labelSize: props.labelSize,
115
+ background: props.background,
116
+ labelColor: props.labelColor
117
+ });
118
+ const asset = await context.capabilities.assets.write(
119
+ sheet,
120
+ { mediaType: "image/png", suggestedName: props.filename },
121
+ { signal: context.signal }
122
+ );
123
+ context.outputs.asset.set(asset);
124
+ context.outputs.image.set({
125
+ path: asset.path ?? props.filename,
126
+ size: [layout.width, layout.height],
127
+ channels: "rgba",
128
+ depth: "u8",
129
+ space: "srgb"
130
+ });
131
+ }
132
+
133
+ export {
134
+ contactSheetLayout,
135
+ composeContactSheet,
136
+ executeContactSheet
137
+ };
138
+ //# sourceMappingURL=chunk-53LGFPWX.js.map