@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
@@ -1,623 +0,0 @@
1
- import {
2
- writeFileAtomicSync
3
- } from "./chunk-WPUDGEFY.js";
4
- import {
5
- readProjectManifest
6
- } from "./chunk-RQZ4SX5R.js";
7
-
8
- // server/src/compile.ts
9
- import * as esbuild from "esbuild";
10
- import fs from "fs";
11
- import path from "path";
12
- import { fileURLToPath } from "url";
13
- var __dirname_compile = path.dirname(fileURLToPath(import.meta.url));
14
- function cascadeRuntimePlugin(project) {
15
- return {
16
- name: "cascade-runtime",
17
- setup(build2) {
18
- build2.onResolve({ filter: /^cascade\/(?:io|image|shell|net|config|stage)$/ }, (args) => ({
19
- path: args.path,
20
- namespace: "cascade-runtime"
21
- }));
22
- build2.onResolve({ filter: /^(?:@cascade|cascade)\/contracts$/ }, () => {
23
- const candidates = [
24
- path.join(__dirname_compile, "..", "contracts", "index.js"),
25
- path.join(__dirname_compile, "..", "..", "packages", "contracts", "dist", "index.js")
26
- ];
27
- const found = candidates.find((c) => fs.existsSync(c));
28
- if (!found) {
29
- return { errors: [{ text: `@cascade/contracts not found (looked in ${candidates.join(", ")})` }] };
30
- }
31
- return { path: found };
32
- });
33
- build2.onLoad({ filter: /.*/, namespace: "cascade-runtime" }, (args) => {
34
- if (args.path === "cascade/config") {
35
- return { contents: configRuntimeSource(readProjectManifest(project.root).settings), loader: "ts" };
36
- }
37
- const candidates = [
38
- path.join(__dirname_compile, "runtime", `${args.path.slice("cascade/".length)}.ts`),
39
- path.join(__dirname_compile, "..", "src", "runtime", `${args.path.slice("cascade/".length)}.ts`)
40
- ];
41
- const found = candidates.find((c) => fs.existsSync(c));
42
- if (!found) {
43
- return { errors: [{ text: `${args.path} runtime not found (looked in ${candidates.join(", ")})` }] };
44
- }
45
- return { contents: fs.readFileSync(found, "utf-8"), loader: "ts", resolveDir: path.dirname(found) };
46
- });
47
- }
48
- };
49
- }
50
- function commonOptions(project) {
51
- return {
52
- bundle: true,
53
- format: "esm",
54
- platform: "browser",
55
- target: "es2022",
56
- write: false,
57
- logLevel: "silent",
58
- plugins: [cascadeRuntimePlugin(project)]
59
- };
60
- }
61
- async function compileProjectModule(project, moduleName) {
62
- try {
63
- await project.moduleRunsOn(moduleName);
64
- return await compileEntry(project, project.resolve(`nodes/${moduleName}/index.ts`));
65
- } catch (err) {
66
- return { ok: false, code: "", errors: [errorMessage(err)] };
67
- }
68
- }
69
- async function compileProjectPanel(project, panelName) {
70
- try {
71
- return await compileEntry(project, await project.resolvePanelEntry(panelName));
72
- } catch (err) {
73
- return { ok: false, code: "", errors: [errorMessage(err)] };
74
- }
75
- }
76
- async function compileEmbedded(project, code) {
77
- try {
78
- const result = await esbuild.build({
79
- ...commonOptions(project),
80
- stdin: {
81
- contents: code,
82
- loader: "ts",
83
- resolveDir: project.root,
84
- sourcefile: "embedded.ts"
85
- }
86
- });
87
- return toResult(result);
88
- } catch (err) {
89
- return { ok: false, code: "", errors: [errorMessage(err)] };
90
- }
91
- }
92
- function toResult(result) {
93
- const errors = result.errors.map((e) => e.text);
94
- const code = result.outputFiles?.[0]?.text ?? "";
95
- return { ok: errors.length === 0, code, errors };
96
- }
97
- async function compileEntry(project, entryPath2) {
98
- return toResult(await esbuild.build({ ...commonOptions(project), entryPoints: [entryPath2] }));
99
- }
100
- function configRuntimeSource(settings) {
101
- return `function deepFreeze(value) { if (value && typeof value === 'object') { for (const item of Object.values(value)) deepFreeze(item); Object.freeze(value); } return value; }
102
- const values = deepFreeze(${JSON.stringify(settings)});
103
- function value(name) { return Object.prototype.hasOwnProperty.call(values, name) ? values[name] : undefined; }
104
- export const config = Object.freeze({
105
- get(name, fallback) { return value(name) ?? fallback; },
106
- string(name, fallback = '') { const item = value(name); return typeof item === 'string' ? item : fallback; },
107
- number(name, fallback = 0) { const item = value(name); return typeof item === 'number' && Number.isFinite(item) ? item : fallback; },
108
- boolean(name, fallback = false) { const item = value(name); return typeof item === 'boolean' ? item : fallback; },
109
- has(name) { return value(name) !== undefined; }
110
- });`;
111
- }
112
- function errorMessage(err) {
113
- if (err && typeof err === "object" && "errors" in err) {
114
- const errors = err.errors;
115
- if (errors?.length) return errors.map((e) => e.text).join("\n");
116
- }
117
- return err instanceof Error ? err.message : String(err);
118
- }
119
-
120
- // server/src/pythonWorker.ts
121
- import { spawn } from "child_process";
122
- import os from "node:os";
123
- import path2 from "node:path";
124
- import readline from "readline";
125
- var REQUEST_TIMEOUT_MS = 12e4;
126
- var CANCEL_GRACE_MS = 3e3;
127
- var StageCancelledError = class extends Error {
128
- constructor(stage) {
129
- super(`stage "${stage}" was cancelled`);
130
- this.cancelled = true;
131
- this.name = "StageCancelledError";
132
- }
133
- };
134
- var liveWorkers = /* @__PURE__ */ new Set();
135
- function pythonWorkerStatuses() {
136
- return [...liveWorkers].map((worker) => worker.status);
137
- }
138
- function shutdownAllPythonWorkers() {
139
- for (const worker of [...liveWorkers]) worker.shutdown();
140
- }
141
- var PythonWorker = class {
142
- constructor(project, entrypoint, python = "python3", env = process.env, options = {}) {
143
- this.proc = null;
144
- this.nextId = 1;
145
- this.queue = [];
146
- /** The request the worker is running, or one it was told to abandon and has
147
- * not yet answered. Nothing else is written until it answers or exits. */
148
- this.inFlight = null;
149
- /** Set once the in-flight request has been cancelled: its answer is
150
- * discarded whatever it says. */
151
- this.inFlightCancelled = false;
152
- this.killTimer = null;
153
- /** The code hash the running process started with. */
154
- this.spawnedCodeHash = null;
155
- /** Set by a restart, cleared by the answer that follows it. */
156
- this.restartPending = false;
157
- /** How many times the worker restarted because its code changed. */
158
- this.restarts = 0;
159
- this.project = project;
160
- this.entrypoint = entrypoint;
161
- this.python = python;
162
- this.env = env;
163
- this.cancelGraceMs = options.cancelGraceMs ?? CANCEL_GRACE_MS;
164
- this.requestTimeoutMs = options.requestTimeoutMs ?? REQUEST_TIMEOUT_MS;
165
- this.sources = options.sources ?? null;
166
- this.onStderr = options.onStderr ?? ((text) => console.error("[python-worker]", text));
167
- liveWorkers.add(this);
168
- }
169
- get status() {
170
- const pid = this.pid;
171
- return {
172
- entrypoint: this.entrypoint,
173
- pid,
174
- state: pid === null ? "stopped" : this.inFlight ? "busy" : "idle",
175
- queued: this.queue.length,
176
- codeHash: this.spawnedCodeHash,
177
- restarts: this.restarts
178
- };
179
- }
180
- /** The code hash the running worker started with; null before the first
181
- * request or when sources are not watched. */
182
- get codeHash() {
183
- return this.spawnedCodeHash;
184
- }
185
- /** The worker's process id, or null when none is running. For tests and
186
- * diagnostics: a cancelled stage must not leave an orphan behind. */
187
- get pid() {
188
- return this.proc?.pid ?? null;
189
- }
190
- ensureStarted() {
191
- if (this.proc && this.proc.exitCode === null && this.proc.signalCode === null) return this.proc;
192
- this.spawnedCodeHash = this.sources?.codeHash() ?? null;
193
- const scriptPath = this.project.resolve(this.entrypoint);
194
- const proc = spawn(this.python, [scriptPath], { cwd: this.project.root, env: this.spawnEnv() });
195
- readline.createInterface({ input: proc.stdout }).on("line", (line) => this.handleLine(proc, line));
196
- proc.stderr.on("data", (chunk) => this.onStderr(chunk.toString().trimEnd()));
197
- proc.stdin.on("error", () => {
198
- });
199
- proc.on("exit", (code, signal) => {
200
- if (this.proc !== proc) return;
201
- this.proc = null;
202
- this.clearKillTimer();
203
- const current = this.inFlight;
204
- const cancelled = this.inFlightCancelled;
205
- this.inFlight = null;
206
- this.inFlightCancelled = false;
207
- if (current && !cancelled) {
208
- console.warn(`[python-worker] exited (${signal ?? `code ${code}`}) \u2014 will respawn on next call`);
209
- this.settle(current, new Error(`python worker exited (${signal ?? `code ${code}`}) before responding`));
210
- }
211
- this.pump();
212
- });
213
- this.proc = proc;
214
- return proc;
215
- }
216
- /**
217
- * The environment for a new process. With sources watched, Python's bytecode
218
- * cache is moved to a folder named by the code hash.
219
- *
220
- * Without that a restart is not enough: Python validates a `.pyc` by the
221
- * source's size and its mtime in whole seconds, so an edit that keeps the
222
- * length (`VALUE = 1` to `VALUE = 2`) within the same second is served from
223
- * the old bytecode by the fresh process. Found by the respawn test.
224
- */
225
- spawnEnv() {
226
- if (!this.spawnedCodeHash) return this.env;
227
- const prefix = path2.join(os.tmpdir(), "cascade-pycache", this.spawnedCodeHash);
228
- return { ...this.env, PYTHONPYCACHEPREFIX: prefix };
229
- }
230
- handleLine(proc, line) {
231
- if (this.proc !== proc) return;
232
- let msg;
233
- try {
234
- msg = JSON.parse(line);
235
- } catch {
236
- console.error("[python-worker] non-JSON line from worker:", line);
237
- return;
238
- }
239
- const current = this.inFlight;
240
- if (!current || msg.id !== current.id) return;
241
- const cancelled = this.inFlightCancelled;
242
- this.inFlight = null;
243
- this.inFlightCancelled = false;
244
- this.clearKillTimer();
245
- if (!cancelled) {
246
- const restarted = this.restartPending;
247
- this.restartPending = false;
248
- if (msg.ok) {
249
- this.settle(current, null, {
250
- result: msg.result,
251
- codeHash: this.spawnedCodeHash,
252
- restarted,
253
- ...typeof msg.cache === "string" ? { projectCache: msg.cache } : {}
254
- });
255
- } else this.settle(current, new Error(msg.error || "python worker reported an error"));
256
- }
257
- this.pump();
258
- }
259
- settle(request, error, answer) {
260
- if (request.timer) clearTimeout(request.timer);
261
- request.timer = null;
262
- if (error) request.reject(error);
263
- else request.resolve(answer);
264
- }
265
- /**
266
- * Stop a running worker whose code changed since it started. Only ever
267
- * called with nothing in flight, so no request is lost: the exit handler
268
- * finds no current request, and the next `ensureStarted` spawns afresh.
269
- */
270
- restartIfCodeChanged() {
271
- if (!this.sources || !this.proc) return;
272
- const now = this.sources.codeHash();
273
- if (now === this.spawnedCodeHash) return;
274
- console.warn(`[python-worker] stage code changed (${this.spawnedCodeHash} -> ${now}), restarting`);
275
- const stale = this.proc;
276
- this.proc = null;
277
- stale.kill();
278
- this.restarts += 1;
279
- this.restartPending = true;
280
- }
281
- clearKillTimer() {
282
- if (this.killTimer) clearTimeout(this.killTimer);
283
- this.killTimer = null;
284
- }
285
- /** Send the next queued request if the worker is free. */
286
- pump() {
287
- if (this.inFlight) return;
288
- const next = this.queue.shift();
289
- if (!next) return;
290
- this.restartIfCodeChanged();
291
- const proc = this.ensureStarted();
292
- this.inFlight = next;
293
- this.inFlightCancelled = false;
294
- proc.stdin.write(next.line);
295
- }
296
- /** Run a stage and return its result. */
297
- async call(stage, args, options = {}) {
298
- return (await this.callWithMeta(stage, args, options)).result;
299
- }
300
- /** Run a stage and return its result with the code hash that produced it. */
301
- callWithMeta(stage, args, options = {}) {
302
- const { signal, cascade } = options;
303
- if (signal?.aborted) return Promise.reject(new StageCancelledError(stage));
304
- const id = this.nextId++;
305
- return new Promise((resolve, reject) => {
306
- const onAbort = () => this.cancel(id);
307
- const request = {
308
- id,
309
- stage,
310
- line: JSON.stringify(cascade ? { id, stage, args, cascade } : { id, stage, args }) + "\n",
311
- resolve: (r) => {
312
- signal?.removeEventListener("abort", onAbort);
313
- resolve(r);
314
- },
315
- reject: (e) => {
316
- signal?.removeEventListener("abort", onAbort);
317
- reject(e);
318
- },
319
- timer: null
320
- };
321
- request.timer = setTimeout(() => {
322
- this.cancel(id, new Error(`python worker timed out after ${this.requestTimeoutMs}ms on stage "${stage}"`));
323
- }, this.requestTimeoutMs);
324
- signal?.addEventListener("abort", onAbort, { once: true });
325
- this.queue.push(request);
326
- this.pump();
327
- });
328
- }
329
- /**
330
- * Withdraw a request. A queued one is dropped without ever reaching Python;
331
- * the one in flight is interrupted, and its answer — whenever it comes — is
332
- * discarded, so a superseded result is never delivered.
333
- */
334
- cancel(id, reason) {
335
- const queued = this.queue.findIndex((request) => request.id === id);
336
- if (queued >= 0) {
337
- const [request] = this.queue.splice(queued, 1);
338
- this.settle(request, reason ?? new StageCancelledError(request.stage));
339
- return;
340
- }
341
- const current = this.inFlight;
342
- if (!current || current.id !== id || this.inFlightCancelled) return;
343
- this.inFlightCancelled = true;
344
- this.settle(current, reason ?? new StageCancelledError(current.stage));
345
- const proc = this.proc;
346
- if (!proc) return;
347
- proc.kill("SIGINT");
348
- this.clearKillTimer();
349
- this.killTimer = setTimeout(() => {
350
- this.killTimer = null;
351
- if (this.proc === proc && this.inFlight === current) proc.kill("SIGKILL");
352
- }, this.cancelGraceMs);
353
- }
354
- shutdown() {
355
- liveWorkers.delete(this);
356
- this.clearKillTimer();
357
- for (const request of this.queue.splice(0)) this.settle(request, new Error("python worker shut down"));
358
- const current = this.inFlight;
359
- this.inFlight = null;
360
- this.inFlightCancelled = false;
361
- if (current) this.settle(current, new Error("python worker shut down"));
362
- const proc = this.proc;
363
- this.proc = null;
364
- proc?.kill();
365
- }
366
- };
367
-
368
- // server/src/stageCache.ts
369
- import { createHash as createHash2 } from "node:crypto";
370
- import fs3 from "node:fs";
371
- import path4 from "node:path";
372
- import { fileURLToPath as fileURLToPath2 } from "node:url";
373
-
374
- // server/src/stageSources.ts
375
- import { createHash } from "node:crypto";
376
- import fs2 from "node:fs";
377
- import path3 from "node:path";
378
- var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set(["__pycache__", "node_modules", "venv", ".venv", ".git", ".cascade", ".cascade-cache"]);
379
- var digests = /* @__PURE__ */ new Map();
380
- function contentDigest(fullPath) {
381
- let stat;
382
- try {
383
- stat = fs2.statSync(fullPath, { bigint: true });
384
- } catch {
385
- return null;
386
- }
387
- if (!stat.isFile()) return null;
388
- const stamp = `${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
389
- const known = digests.get(fullPath);
390
- if (known?.stamp === stamp) return known.digest;
391
- const hash = createHash("sha256");
392
- const handle = fs2.openSync(fullPath, "r");
393
- try {
394
- const buffer = Buffer.allocUnsafe(1 << 20);
395
- let read;
396
- while ((read = fs2.readSync(handle, buffer, 0, buffer.length, null)) > 0) hash.update(buffer.subarray(0, read));
397
- } finally {
398
- fs2.closeSync(handle);
399
- }
400
- const digest = hash.digest("hex");
401
- digests.set(fullPath, { stamp, digest });
402
- return digest;
403
- }
404
- var StageSources = class {
405
- constructor(project, roots) {
406
- this.project = project;
407
- this.roots = [...new Set(roots)].sort();
408
- }
409
- /** The default roots for a stage configuration without `sources`. */
410
- static defaultRoots(project, entrypoint, worker) {
411
- const roots = [path3.posix.dirname(entrypoint)];
412
- if (worker) roots.push(path3.posix.dirname(worker));
413
- if (fs2.existsSync(project.resolve("src"))) roots.push("src");
414
- return roots.map((root) => root === "" ? "." : root);
415
- }
416
- /** Every source file, project-relative and sorted. */
417
- files() {
418
- const found = /* @__PURE__ */ new Set();
419
- for (const root of this.roots) {
420
- let full;
421
- try {
422
- full = this.project.resolveMedia(root);
423
- } catch {
424
- continue;
425
- }
426
- let stat;
427
- try {
428
- stat = fs2.statSync(full);
429
- } catch {
430
- continue;
431
- }
432
- if (stat.isFile()) found.add(root);
433
- else if (stat.isDirectory()) this.walk(full, root, found);
434
- }
435
- return [...found].sort();
436
- }
437
- walk(directory, relative, found) {
438
- let entries;
439
- try {
440
- entries = fs2.readdirSync(directory, { withFileTypes: true });
441
- } catch {
442
- return;
443
- }
444
- for (const entry of entries) {
445
- const child = relative === "." ? entry.name : path3.posix.join(relative, entry.name);
446
- if (entry.isDirectory()) {
447
- if (SKIPPED_DIRECTORIES.has(entry.name) || entry.name.startsWith(".")) continue;
448
- this.walk(path3.join(directory, entry.name), child, found);
449
- } else if (entry.isFile() && entry.name.endsWith(".py")) {
450
- found.add(child);
451
- }
452
- }
453
- }
454
- digest(relative) {
455
- return contentDigest(this.project.resolveMedia(relative)) ?? "missing";
456
- }
457
- /**
458
- * The code hash, 16 hex characters. A file added, removed or edited changes
459
- * it. Cheap to call per request: one directory walk and one stat per file,
460
- * with a file read only when its stat changed.
461
- */
462
- codeHash() {
463
- const hash = createHash("sha256");
464
- for (const file of this.files()) hash.update(`${file}\0${this.digest(file)}
465
- `);
466
- return hash.digest("hex").slice(0, 16);
467
- }
468
- };
469
-
470
- // server/src/stageCache.ts
471
- var CACHE_FORMAT = 1;
472
- var CACHE_DIRECTORY = ".cascade/cache/stages";
473
- var version;
474
- function cascadeVersion() {
475
- if (version) return version;
476
- let directory = path4.dirname(fileURLToPath2(import.meta.url));
477
- for (let depth = 0; depth < 6; depth++) {
478
- try {
479
- const manifest = JSON.parse(fs3.readFileSync(path4.join(directory, "package.json"), "utf8"));
480
- if (manifest.name === "@field/cascade" && manifest.version) return version = manifest.version;
481
- } catch {
482
- }
483
- directory = path4.dirname(directory);
484
- }
485
- return version = "unknown";
486
- }
487
- function projectFile(project, value) {
488
- if (!value || value.length > 1024 || value.includes("\0") || /^[a-z][a-z0-9+.-]*:/i.test(value)) return null;
489
- if (path4.isAbsolute(value) || !/[./]/.test(value)) return null;
490
- try {
491
- return project.resolveMedia(value.replace(/^\.\//, ""));
492
- } catch {
493
- return null;
494
- }
495
- }
496
- function replaceFiles(project, value, at, skip, onFile) {
497
- if (typeof value === "string") {
498
- const full = projectFile(project, value);
499
- const digest = full ? contentDigest(full) : null;
500
- if (!digest) return value;
501
- onFile(at, value, digest);
502
- return `file:${digest}`;
503
- }
504
- if (Array.isArray(value)) return value.map((item, index) => replaceFiles(project, item, `${at}[${index}]`, skip, onFile));
505
- if (value && typeof value === "object") {
506
- const out = {};
507
- for (const name of Object.keys(value).sort()) {
508
- const child = value[name];
509
- const where = at ? `${at}.${name}` : name;
510
- out[name] = !at && skip.has(name) ? child : replaceFiles(project, child, where, skip, onFile);
511
- }
512
- return out;
513
- }
514
- return value;
515
- }
516
- function stageCacheKey(project, stage, args, code, outputs = []) {
517
- const inputs = {};
518
- const params = replaceFiles(project, args, "", new Set(outputs), (where, _relative, digest) => {
519
- inputs[where] = digest.slice(0, 16);
520
- });
521
- const cascade = cascadeVersion();
522
- const blob = JSON.stringify({ format: CACHE_FORMAT, stage, code, cascade, params });
523
- return { key: createHash2("sha256").update(blob).digest("hex").slice(0, 32), code, cascade, inputs };
524
- }
525
- function entryPath(project, stage, key) {
526
- return project.resolve(path4.posix.join(CACHE_DIRECTORY, stage, `${key}.json`));
527
- }
528
- function resultFiles(project, result) {
529
- const files = {};
530
- replaceFiles(project, result, "", /* @__PURE__ */ new Set(), (_where, relative, digest) => {
531
- files[relative] = digest;
532
- });
533
- return files;
534
- }
535
- function readEntry(project, stage, key) {
536
- let entry;
537
- try {
538
- entry = JSON.parse(fs3.readFileSync(entryPath(project, stage, key), "utf8"));
539
- } catch {
540
- return { why: "no entry" };
541
- }
542
- if (entry.format !== CACHE_FORMAT || entry.key !== key) return { why: "no entry" };
543
- for (const [relative, digest] of Object.entries(entry.outputs ?? {})) {
544
- const full = projectFile(project, relative);
545
- if (!full || contentDigest(full) !== digest) return { why: `output changed: ${relative}` };
546
- }
547
- return { result: entry.result };
548
- }
549
- function writeEntry(project, stage, key, result) {
550
- const file = entryPath(project, stage, key);
551
- const entry = { format: CACHE_FORMAT, stage, key, result, outputs: resultFiles(project, result) };
552
- fs3.mkdirSync(path4.dirname(file), { recursive: true });
553
- writeFileAtomicSync(file, JSON.stringify(entry));
554
- }
555
- function cacheMode(policy, stage, caller, env) {
556
- if (env.CASCADE_STAGE_CACHE === "off") return { use: false, bypass: true, why: "CASCADE_STAGE_CACHE=off" };
557
- if (!policy.enabled) return { use: false, bypass: false, why: "not enabled" };
558
- if (policy.exclude.includes(stage)) return { use: false, bypass: false, why: "excluded" };
559
- if (policy.scope === "cook" && caller !== "cook") return { use: false, bypass: false, why: "not a cook call" };
560
- return { use: true, bypass: false };
561
- }
562
- function stageSources(project, config) {
563
- return new StageSources(project, config.sources ?? StageSources.defaultRoots(project, config.entrypoint, config.worker));
564
- }
565
- async function callStage(project, config, stage, args, invoke, options = {}) {
566
- const started = performance.now();
567
- const code = stageSources(project, config).codeHash();
568
- const identity = stageCacheKey(project, stage, args, code, config.cache.outputs);
569
- const mode = cacheMode(config.cache, stage, options.caller ?? "cook", options.env ?? process.env);
570
- const finish = (result, trace) => {
571
- const full = {
572
- stage,
573
- key: identity.key,
574
- ...trace,
575
- code: identity.code,
576
- cascade: identity.cascade,
577
- inputs: identity.inputs,
578
- ms: Math.round(performance.now() - started)
579
- };
580
- options.onTrace?.(full);
581
- return { result, trace: full };
582
- };
583
- let why = mode.why;
584
- if (mode.use) {
585
- const stored = readEntry(project, stage, identity.key);
586
- if ("result" in stored) return finish(stored.result, { cache: "hit" });
587
- why = stored.why;
588
- }
589
- const answer = await invoke({ key: identity.key, code, cache: mode.bypass ? "off" : "use" });
590
- if (mode.use) {
591
- try {
592
- writeEntry(project, stage, identity.key, answer.result);
593
- } catch {
594
- }
595
- }
596
- return finish(answer.result, {
597
- cache: mode.use ? "miss" : "off",
598
- ...why ? { why } : {},
599
- ...answer.projectCache ? { project: answer.projectCache } : {},
600
- ...answer.restarted ? { restarted: true } : {}
601
- });
602
- }
603
- function formatStageTrace(trace) {
604
- const reason = trace.why ? ` (${trace.why})` : "";
605
- const project = trace.project ? ` project:${trace.project}` : "";
606
- const restarted = trace.restarted ? " worker restarted for new code" : "";
607
- return `${trace.stage}: ${trace.cache}${reason}${project} key ${trace.key.slice(0, 12)} code ${trace.code} ${trace.ms} ms${restarted}`;
608
- }
609
-
610
- export {
611
- compileProjectModule,
612
- compileProjectPanel,
613
- compileEmbedded,
614
- StageCancelledError,
615
- pythonWorkerStatuses,
616
- shutdownAllPythonWorkers,
617
- PythonWorker,
618
- cascadeVersion,
619
- stageSources,
620
- callStage,
621
- formatStageTrace
622
- };
623
- //# sourceMappingURL=chunk-MIIEFUGG.js.map