@field/cascade 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +20 -3
- package/CHANGELOG.md +187 -0
- package/README.md +1 -2
- package/dist/assets/{CodeEditor-DSZBpzdy.js → CodeEditor-4O7FQ__R.js} +4 -4
- package/dist/assets/{CodePanel-sY6sZa7B.js → CodePanel-BU-autmq.js} +2 -2
- package/dist/assets/{CustomNodeDialog-BRKnqZhp.js → CustomNodeDialog-BRpDfOV5.js} +1 -1
- package/dist/assets/{OpenGraphDialog-ZsgL-iwr.js → OpenGraphDialog-BwbRlC0X.js} +1 -1
- package/dist/assets/{ProjectSettings-Cw17nNe3.js → ProjectSettings-DLOXLxcA.js} +1 -1
- package/dist/assets/{SettingsDialog-p25h3tj0.js → SettingsDialog-Bz8JxISH.js} +1 -1
- package/dist/assets/{VersionHistoryDialog-DzFLUotB.js → VersionHistoryDialog-D5Z0nJ5-.js} +1 -1
- package/dist/assets/{core-BICrEt0y.js → core-Yqi0Ni61.js} +1 -1
- package/dist/assets/{cssMode-D7TXw57I.js → cssMode-CxUzc4vp.js} +1 -1
- package/dist/assets/{definitions-DUww2Rt-.js → definitions-GreEMXLV.js} +1 -1
- package/dist/assets/{editor.api-DvkCO3ko.js → editor.api-BNFb25CX.js} +1 -1
- package/dist/assets/{freemarker2-BA2lxsF1.js → freemarker2-0ZDnWxeM.js} +1 -1
- package/dist/assets/geo-u0n_jOsD.js +1 -0
- package/dist/assets/{handlebars-BcDiyPd8.js → handlebars-Cy18MoaD.js} +1 -1
- package/dist/assets/{html-C7O4TyhX.js → html-B-2Dx7nq.js} +1 -1
- package/dist/assets/{htmlMode-WaDGF3Mj.js → htmlMode-DUAc9B_d.js} +1 -1
- package/dist/assets/{image-DR4oeyJB.js → image-cAa-Nu9O.js} +1 -1
- package/dist/assets/{index-DyvDC6pS.js → index-5ZtDAuy2.js} +42 -41
- package/dist/assets/{javascript-CpIEK7tz.js → javascript-DeQsDqP5.js} +1 -1
- package/dist/assets/{jsonMode-B2FltW0f.js → jsonMode-BiFwJVSk.js} +1 -1
- package/dist/assets/{liquid-CN6FFHZE.js → liquid-BLoHH68a.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-QhV9XtLp.js → lspLanguageFeatures-CNFgm1Kv.js} +1 -1
- package/dist/assets/math-DioIQOPJ.js +1 -0
- package/dist/assets/{mdx-DE89pafV.js → mdx-CFXQVNfs.js} +1 -1
- package/dist/assets/pop-DC_TQG9t.js +1 -0
- package/dist/assets/{python-ByTllTB9.js → python-Cf7S7p2E.js} +1 -1
- package/dist/assets/{razor-USYlRRCE.js → razor-CMHBcG-g.js} +1 -1
- package/dist/assets/{tsMode-BXpN8iUz.js → tsMode-BEih0GDj.js} +1 -1
- package/dist/assets/{typescript-DInBjGQQ.js → typescript-COEKYD_a.js} +1 -1
- package/dist/assets/{workers-C7Ye5B7D.js → workers-BNhAhMNn.js} +1 -1
- package/dist/assets/{xml-U0Uocqyo.js → xml-CPiMpnPl.js} +1 -1
- package/dist/assets/{yaml-CBw_PVYf.js → yaml-Bgp1ITLR.js} +1 -1
- package/dist/cli/Graph-ZJJTRQTM.js +2975 -0
- package/dist/cli/chunk-2KXB47FP.js +115 -0
- package/dist/cli/chunk-35D343DC.js +1447 -0
- package/dist/cli/chunk-36P5K3SD.js +631 -0
- package/dist/cli/chunk-3JTJHF7V.js +267 -0
- package/dist/cli/chunk-4UDFYF5G.js +132 -0
- package/dist/cli/chunk-53NMBJAO.js +35 -0
- package/dist/cli/chunk-A4OAMOZH.js +310 -0
- package/dist/cli/chunk-BIJO5OFS.js +71 -0
- package/dist/cli/chunk-BP7UKF36.js +376 -0
- package/dist/cli/chunk-CKS2FQWR.js +89 -0
- package/dist/cli/chunk-CO22XHNG.js +593 -0
- package/dist/cli/chunk-D6BUOHUG.js +634 -0
- package/dist/cli/chunk-DEQSEFFH.js +153 -0
- package/dist/cli/chunk-DUIUFVU2.js +241 -0
- package/dist/cli/chunk-EUIAC3U3.js +3006 -0
- package/dist/cli/chunk-FCDXJ6EK.js +2208 -0
- package/dist/cli/chunk-GFS6W3C5.js +308 -0
- package/dist/cli/chunk-HFHWYL55.js +139 -0
- package/dist/cli/chunk-HSNOBIVT.js +263 -0
- package/dist/cli/chunk-ICH7N5PN.js +26 -0
- package/dist/cli/chunk-J7VK7MNF.js +418 -0
- package/dist/cli/chunk-KH4MUBKF.js +1551 -0
- package/dist/cli/chunk-MIIEFUGG.js +623 -0
- package/dist/cli/chunk-OWWBYLHC.js +136 -0
- package/dist/cli/chunk-POMMNKZZ.js +672 -0
- package/dist/cli/chunk-QBBKRNGY.js +90 -0
- package/dist/cli/chunk-QQPA2ZHH.js +273 -0
- package/dist/cli/chunk-RLCAJBVR.js +224 -0
- package/dist/cli/chunk-RQZ4SX5R.js +1125 -0
- package/dist/cli/chunk-RUU3YWXU.js +99 -0
- package/dist/cli/chunk-RXDIMXN5.js +98 -0
- package/dist/cli/chunk-SGJAET2L.js +197 -0
- package/dist/cli/chunk-VVGSLVSF.js +151 -0
- package/dist/cli/chunk-WD62JIP6.js +118 -0
- package/dist/cli/chunk-WIH4TEIS.js +104 -0
- package/dist/cli/chunk-WKURDVYP.js +1119 -0
- package/dist/cli/chunk-WPUDGEFY.js +20 -0
- package/dist/cli/chunk-XWDSKG7N.js +1301 -0
- package/dist/cli/chunk-YAT5Y25U.js +121 -0
- package/dist/cli/chunk-ZL4JTG5Y.js +37 -0
- package/dist/cli/cliCommands-UQSFGEHF.js +3464 -0
- package/dist/cli/commands-Q23M26QM.js +1292 -0
- package/dist/cli/definitions-HF2FTHYU.js +32 -0
- package/dist/cli/doctorCommand-B7BPQMN6.js +76 -0
- package/dist/cli/edit-JHCPTQFT.js +28 -0
- package/dist/cli/editorCommands-UIF5YLUZ.js +33 -0
- package/dist/cli/encode-HAUZTYPT.js +17 -0
- package/dist/cli/frames-LSAUBZ7P.js +16 -0
- package/dist/cli/geo-4RNK3QVN.js +43 -0
- package/dist/cli/graphCommands-6BMAKGQE.js +184 -0
- package/dist/cli/graphFile-VPSQ6OF7.js +286 -0
- package/dist/cli/headlessCanvas-5XLDZT7M.js +15 -0
- package/dist/cli/helpCommand-3VRE3KVO.js +155 -0
- package/dist/cli/index.js +100 -30756
- package/dist/cli/math-ZRMLKU2D.js +16 -0
- package/dist/cli/mcp-ZLEEMYGF.js +164 -0
- package/dist/cli/parity-KINARCP2.js +328 -0
- package/dist/cli/pop-USDFXB7H.js +39 -0
- package/dist/cli/project-K3QIPGBB.js +12 -0
- package/dist/cli/projectDefinition-DMTZN2NE.js +27 -0
- package/dist/cli/projectRuntime-Z5PJOH47.js +48 -0
- package/dist/cli/registerStandardNodes-S2VLMF6J.js +3081 -0
- package/dist/cli/render-YURAPPUI.js +436 -0
- package/dist/cli/runCommand-PI764LEG.js +198 -0
- package/dist/cli/runner-RA6UKEPD.js +37 -0
- package/dist/cli/runtime/image.ts +277 -0
- package/dist/cli/runtime/stage.ts +4 -1
- package/dist/cli/stageRunner-KUNIMQKJ.js +10 -0
- package/dist/cli/verbs-RCE3K4VQ.js +19 -0
- package/dist/cli/webBuild-GT4PZT5D.js +37 -0
- package/dist/contracts/documentFormat.d.ts +18 -0
- package/dist/contracts/documentFormat.js +195 -0
- package/dist/contracts/editor.d.ts +17 -0
- package/dist/contracts/index.d.ts +1 -0
- package/dist/contracts/index.js +1 -0
- package/dist/image.d.ts +141 -0
- package/dist/image.js +127 -0
- package/dist/index.html +1 -1
- package/dist/player/editor.js +32 -7
- package/dist/runtime/builtins/headless.d.ts +19 -0
- package/dist/runtime/builtins/headless.js +25 -0
- package/dist/runtime/builtins/image/contactSheet.d.ts +226 -0
- package/dist/runtime/builtins/image/contactSheet.js +199 -0
- package/dist/runtime/builtins/image/index.d.ts +9 -0
- package/dist/runtime/builtins/image/index.js +9 -0
- package/dist/stage.js +4 -1
- package/doc/CLI_REFERENCE.md +73 -2
- package/doc/EDITOR_EXPORT.md +11 -2
- package/doc/MCP.md +17 -1
- package/doc/NODE_REFERENCE.md +142 -0
- package/doc/PROJECT_AUTHORING.md +127 -7
- package/package.json +16 -4
- package/dist/assets/ExportDialog-C7dVGua4.js +0 -358
- package/dist/assets/ExportDialog-DOq0flMV.css +0 -1
- package/dist/assets/geo-C2RHOVHC.js +0 -1
- package/dist/assets/math-DaDEgWKt.js +0 -1
- package/dist/assets/pop-pLH7L0PW.js +0 -1
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ExpressionEvaluator,
|
|
3
|
+
parseBundleFieldPath
|
|
4
|
+
} from "./chunk-CO22XHNG.js";
|
|
5
|
+
|
|
6
|
+
// src/engine/expressions/ExpressionEngine.ts
|
|
7
|
+
var ExpressionEngine = class extends ExpressionEvaluator {
|
|
8
|
+
constructor() {
|
|
9
|
+
super();
|
|
10
|
+
this.graph = null;
|
|
11
|
+
const scope = {
|
|
12
|
+
resolveParam: (from, path) => this.resolveNodePath(from, path),
|
|
13
|
+
nodeExists: (from, path) => from.node(path) !== null,
|
|
14
|
+
inputNode: (from, index) => this.resolveInput(from, index)
|
|
15
|
+
};
|
|
16
|
+
this.setScope(scope);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Set the graph reference for node lookups
|
|
20
|
+
*/
|
|
21
|
+
setGraph(graph) {
|
|
22
|
+
this.graph = graph;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Resolve a path to get the parameter value
|
|
26
|
+
*/
|
|
27
|
+
resolveNodePath(fromNode, path) {
|
|
28
|
+
const lastSlash = path.lastIndexOf("/");
|
|
29
|
+
if (lastSlash === -1 || path.startsWith("./")) {
|
|
30
|
+
const paramName2 = path.replace("./", "");
|
|
31
|
+
const prop2 = fromNode.props[paramName2];
|
|
32
|
+
if (prop2) return { value: prop2.value };
|
|
33
|
+
return this.resolveBundleField(fromNode, paramName2);
|
|
34
|
+
}
|
|
35
|
+
const nodePath = path.substring(0, lastSlash) || ".";
|
|
36
|
+
const paramName = path.substring(lastSlash + 1);
|
|
37
|
+
const targetNode = fromNode.node(nodePath);
|
|
38
|
+
if (!targetNode) return null;
|
|
39
|
+
const prop = targetNode.props[paramName];
|
|
40
|
+
return prop ? { value: prop.value } : null;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* `format.dpi`: field `dpi` of the bundle wired into this node's `format`
|
|
44
|
+
* input. Read off the input port, so it is whatever the producer committed
|
|
45
|
+
* on its last cook, which the wire guarantees came first.
|
|
46
|
+
*/
|
|
47
|
+
resolveBundleField(node, name) {
|
|
48
|
+
const reference = parseBundleFieldPath(name);
|
|
49
|
+
if (!reference) return null;
|
|
50
|
+
const port = node.inputs.find((input) => input.name === reference.port);
|
|
51
|
+
if (!port || port.dataType !== "bundle") return null;
|
|
52
|
+
const bundle = port.value;
|
|
53
|
+
if (bundle === null || typeof bundle !== "object") return null;
|
|
54
|
+
if (!Object.prototype.hasOwnProperty.call(bundle, reference.field)) return null;
|
|
55
|
+
return { value: bundle[reference.field] };
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get input node by index
|
|
59
|
+
*/
|
|
60
|
+
resolveInput(node, index) {
|
|
61
|
+
if (!this.graph) return null;
|
|
62
|
+
const inputPort = node.inputs[index];
|
|
63
|
+
if (!inputPort || inputPort.connections.length === 0) return null;
|
|
64
|
+
const conn = inputPort.connections[0];
|
|
65
|
+
return this.graph.getNode(conn.from.nodeId);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Validate all expression paths in the graph
|
|
69
|
+
* Returns list of broken paths with their locations
|
|
70
|
+
*/
|
|
71
|
+
validateAllPaths() {
|
|
72
|
+
if (!this.graph) return [];
|
|
73
|
+
const warnings = [];
|
|
74
|
+
for (const node of this.graph.nodes) {
|
|
75
|
+
for (const [propKey, prop] of Object.entries(node.props)) {
|
|
76
|
+
if (!prop.expression) continue;
|
|
77
|
+
const paths = this.extractDependencies(prop.expression);
|
|
78
|
+
for (const path of paths) {
|
|
79
|
+
if (!this.validatePath(node, path)) {
|
|
80
|
+
warnings.push({
|
|
81
|
+
nodeId: node.id,
|
|
82
|
+
nodePath: node.path(),
|
|
83
|
+
propName: propKey,
|
|
84
|
+
expression: prop.expression,
|
|
85
|
+
brokenPath: path
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return warnings;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
var expressionEngine = new ExpressionEngine();
|
|
95
|
+
|
|
96
|
+
export {
|
|
97
|
+
expressionEngine
|
|
98
|
+
};
|
|
99
|
+
//# sourceMappingURL=chunk-RUU3YWXU.js.map
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// packages/runtime/src/builtins/core/random.ts
|
|
2
|
+
var randomDefinition = {
|
|
3
|
+
apiVersion: 1,
|
|
4
|
+
label: "Random",
|
|
5
|
+
description: "Generate a deterministic unit value from an explicit seed.",
|
|
6
|
+
icon: "Dice5",
|
|
7
|
+
runsOn: "portable",
|
|
8
|
+
inputs: {
|
|
9
|
+
seed: {
|
|
10
|
+
kind: "data",
|
|
11
|
+
type: "int",
|
|
12
|
+
default: 0,
|
|
13
|
+
description: "Integer seed. Equal seeds always produce equal values."
|
|
14
|
+
},
|
|
15
|
+
sample: {
|
|
16
|
+
kind: "data",
|
|
17
|
+
type: "int",
|
|
18
|
+
default: 0,
|
|
19
|
+
description: "Stable sample index within the seeded sequence."
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
outputs: {
|
|
23
|
+
value: {
|
|
24
|
+
kind: "data",
|
|
25
|
+
type: "float",
|
|
26
|
+
description: "Deterministic value in the half-open range [0, 1)."
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
function randomFromSeed(seed, sample = 0) {
|
|
31
|
+
const streamSeed = (Math.trunc(seed) ^ Math.imul(Math.trunc(sample), 2654435761)) >>> 0;
|
|
32
|
+
let value = streamSeed + 1831565813 >>> 0;
|
|
33
|
+
value = Math.imul(value ^ value >>> 15, value | 1);
|
|
34
|
+
value ^= value + Math.imul(value ^ value >>> 7, value | 61);
|
|
35
|
+
return ((value ^ value >>> 14) >>> 0) / 4294967296;
|
|
36
|
+
}
|
|
37
|
+
function executeRandom(context) {
|
|
38
|
+
context.outputs.value.set(randomFromSeed(context.inputs.seed, context.inputs.sample));
|
|
39
|
+
}
|
|
40
|
+
var randomRegistration = {
|
|
41
|
+
kind: "definition-v1",
|
|
42
|
+
moduleId: "cascade.core.Random",
|
|
43
|
+
definition: randomDefinition,
|
|
44
|
+
loadExecute: async () => executeRandom
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// packages/runtime/src/builtins/core/remap.ts
|
|
48
|
+
var remapDefinition = {
|
|
49
|
+
apiVersion: 1,
|
|
50
|
+
label: "Remap",
|
|
51
|
+
description: "Map a number from one range into another.",
|
|
52
|
+
icon: "ArrowRightLeft",
|
|
53
|
+
runsOn: "portable",
|
|
54
|
+
inputs: {
|
|
55
|
+
value: { kind: "data", type: "float", default: 0 },
|
|
56
|
+
inMin: { kind: "data", type: "float", default: 0 },
|
|
57
|
+
inMax: { kind: "data", type: "float", default: 1 },
|
|
58
|
+
outMin: { kind: "data", type: "float", default: 0 },
|
|
59
|
+
outMax: { kind: "data", type: "float", default: 1 }
|
|
60
|
+
},
|
|
61
|
+
outputs: {
|
|
62
|
+
result: { kind: "data", type: "float" }
|
|
63
|
+
},
|
|
64
|
+
props: {
|
|
65
|
+
clamp: {
|
|
66
|
+
type: "bool",
|
|
67
|
+
default: false,
|
|
68
|
+
label: "Clamp",
|
|
69
|
+
description: "Keep the normalized input between zero and one."
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
function remap(value, inMin, inMax, outMin, outMax, clamp) {
|
|
74
|
+
if (inMin === inMax) return outMin;
|
|
75
|
+
const normalized = (value - inMin) / (inMax - inMin);
|
|
76
|
+
const amount = clamp ? Math.min(1, Math.max(0, normalized)) : normalized;
|
|
77
|
+
return outMin + amount * (outMax - outMin);
|
|
78
|
+
}
|
|
79
|
+
function executeRemap(context) {
|
|
80
|
+
const { value, inMin, inMax, outMin, outMax } = context.inputs;
|
|
81
|
+
context.outputs.result.set(
|
|
82
|
+
remap(value, inMin, inMax, outMin, outMax, context.props.clamp)
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
var remapRegistration = {
|
|
86
|
+
kind: "definition-v1",
|
|
87
|
+
moduleId: "cascade.core.Remap",
|
|
88
|
+
definition: remapDefinition,
|
|
89
|
+
loadExecute: async () => executeRemap
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export {
|
|
93
|
+
randomFromSeed,
|
|
94
|
+
randomRegistration,
|
|
95
|
+
remap,
|
|
96
|
+
remapRegistration
|
|
97
|
+
};
|
|
98
|
+
//# sourceMappingURL=chunk-RXDIMXN5.js.map
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Node,
|
|
3
|
+
packagePathToType,
|
|
4
|
+
registerNodeClasses,
|
|
5
|
+
registerNodeMetadata
|
|
6
|
+
} from "./chunk-FCDXJ6EK.js";
|
|
7
|
+
import {
|
|
8
|
+
bundleValueProblem,
|
|
9
|
+
graphSeed,
|
|
10
|
+
isGpuHost,
|
|
11
|
+
nodeSeed,
|
|
12
|
+
seededRandom,
|
|
13
|
+
selectBundleFields
|
|
14
|
+
} from "./chunk-GFS6W3C5.js";
|
|
15
|
+
|
|
16
|
+
// src/nodes/definition/DefinitionNode.ts
|
|
17
|
+
var silentProgress = { report: () => {
|
|
18
|
+
} };
|
|
19
|
+
function parameterOptions(definition) {
|
|
20
|
+
return {
|
|
21
|
+
...definition.label ? { label: definition.label } : {},
|
|
22
|
+
...definition.expression ? { defaultExpression: definition.expression } : {},
|
|
23
|
+
...definition.action ? { action: definition.action } : {},
|
|
24
|
+
...definition.min === void 0 ? {} : { min: definition.min },
|
|
25
|
+
...definition.max === void 0 ? {} : { max: definition.max },
|
|
26
|
+
...definition.step === void 0 ? {} : { step: definition.step },
|
|
27
|
+
...definition.options ? {
|
|
28
|
+
// A declared label wins; a bare value is its own label, which is what
|
|
29
|
+
// every definition written before the labelled form meant. Stringifying
|
|
30
|
+
// unconditionally is what turned "Magnific — costs money" into
|
|
31
|
+
// "magnific" and threw away the only notice that it bills per call.
|
|
32
|
+
choices: definition.options.map((option) => option !== null && typeof option === "object" && "value" in option ? {
|
|
33
|
+
value: option.value,
|
|
34
|
+
label: String(option.label ?? option.value),
|
|
35
|
+
...option.disabled ? { disabled: true } : {}
|
|
36
|
+
} : { value: option, label: String(option) })
|
|
37
|
+
} : {},
|
|
38
|
+
promotable: false
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function bundlePortOptions(port) {
|
|
42
|
+
if (port.type !== "bundle") return {};
|
|
43
|
+
return {
|
|
44
|
+
...port.schema ? { schema: port.schema } : {},
|
|
45
|
+
...Array.isArray(port.fields) ? { fieldSelection: port.fields } : port.fields ? { bundleFields: port.fields } : {}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function attachDefinition(node, registration, capabilities) {
|
|
49
|
+
const definition = registration.definition;
|
|
50
|
+
const pendingTriggers = /* @__PURE__ */ new Map();
|
|
51
|
+
let triggerSequence = 0;
|
|
52
|
+
const container = definition.container;
|
|
53
|
+
if (container === "subnet" || container === "array") {
|
|
54
|
+
node.declaredContainer = container;
|
|
55
|
+
}
|
|
56
|
+
for (const [name, input] of Object.entries(definition.inputs ?? {})) {
|
|
57
|
+
if (input.kind === "trigger") {
|
|
58
|
+
const port = node.in(name, void 0, {
|
|
59
|
+
type: "any",
|
|
60
|
+
...input.description ? { description: input.description } : {}
|
|
61
|
+
});
|
|
62
|
+
port.portType = "trigger";
|
|
63
|
+
port.onTrigger = async (payload) => {
|
|
64
|
+
const sequence = triggerSequence++;
|
|
65
|
+
pendingTriggers.set(name, {
|
|
66
|
+
id: `${node.id}:${sequence}`,
|
|
67
|
+
runId: `studio:${node.id}:${sequence}`,
|
|
68
|
+
sequence,
|
|
69
|
+
...payload === void 0 ? {} : { payload },
|
|
70
|
+
source: { kind: "external" }
|
|
71
|
+
});
|
|
72
|
+
node.markDirty();
|
|
73
|
+
await node.execute();
|
|
74
|
+
};
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (input.variadic) {
|
|
78
|
+
node.setVariadic(void 0, { type: input.type });
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
node.in(name, input.default, {
|
|
82
|
+
type: input.type,
|
|
83
|
+
...input.min === void 0 ? {} : { min: input.min },
|
|
84
|
+
...input.max === void 0 ? {} : { max: input.max },
|
|
85
|
+
...input.step === void 0 ? {} : { step: input.step },
|
|
86
|
+
...input.options ? {
|
|
87
|
+
choices: input.options.map((value) => ({ value, label: String(value) }))
|
|
88
|
+
} : {},
|
|
89
|
+
...input.description ? { description: input.description } : {},
|
|
90
|
+
...bundlePortOptions(input)
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
for (const [name, output] of Object.entries(definition.outputs ?? {})) {
|
|
94
|
+
node.out(
|
|
95
|
+
name,
|
|
96
|
+
output.kind === "trigger" ? "trigger" : "param",
|
|
97
|
+
output.kind === "data" ? { type: output.type, description: output.description, ...bundlePortOptions(output) } : { description: output.description }
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
for (const [name, prop] of Object.entries(definition.props ?? {})) {
|
|
101
|
+
const parameter = node.param(name, prop.default, {
|
|
102
|
+
type: prop.type,
|
|
103
|
+
...parameterOptions(prop)
|
|
104
|
+
});
|
|
105
|
+
parameter.documentField = "props";
|
|
106
|
+
}
|
|
107
|
+
const hasTriggerInputs = Object.values(definition.inputs ?? {}).some((input) => input.kind === "trigger");
|
|
108
|
+
let executePromise;
|
|
109
|
+
return async () => {
|
|
110
|
+
if (hasTriggerInputs && pendingTriggers.size === 0) return;
|
|
111
|
+
for (const name of definition.capabilities ?? []) {
|
|
112
|
+
if (!capabilities[name]) {
|
|
113
|
+
throw new Error(`${registration.moduleId} requires the Studio ${name} capability`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const execute = await (executePromise ??= registration.loadExecute("browser"));
|
|
117
|
+
const inputValues = {};
|
|
118
|
+
for (const [name, input] of Object.entries(definition.inputs ?? {})) {
|
|
119
|
+
inputValues[name] = input.kind === "trigger" ? pendingTriggers.get(name) : input.variadic ? node.getVariadicInputs().map((port) => port.value ?? void 0) : input.type === "bundle" && input.fields ? selectBundleFields(node.inputs.find((port) => port.name === name)?.value, input.fields) : node.inputs.find((port) => port.name === name)?.value;
|
|
120
|
+
}
|
|
121
|
+
pendingTriggers.clear();
|
|
122
|
+
const outputs = Object.fromEntries(Object.entries(definition.outputs ?? {}).map(([name, output]) => {
|
|
123
|
+
const port = node.outputs.find((candidate) => candidate.name === name);
|
|
124
|
+
if (!port) throw new Error(`Missing output ${registration.moduleId}.${name}`);
|
|
125
|
+
return [name, output.kind === "trigger" ? { trigger: (payload) => port.trigger(payload) } : {
|
|
126
|
+
set: (value) => {
|
|
127
|
+
if (output.type === "bundle") {
|
|
128
|
+
const problem = bundleValueProblem(value, output.fields);
|
|
129
|
+
if (problem) throw new Error(`${node.id}.${name} ${problem}`);
|
|
130
|
+
}
|
|
131
|
+
port.setValue(value);
|
|
132
|
+
}
|
|
133
|
+
}];
|
|
134
|
+
}));
|
|
135
|
+
let nodeCapabilities = capabilities;
|
|
136
|
+
const gpuHost = (definition.capabilities ?? []).includes("gpu") && isGpuHost(capabilities.gpu) ? capabilities.gpu : null;
|
|
137
|
+
if (gpuHost) {
|
|
138
|
+
await gpuHost.ensure();
|
|
139
|
+
nodeCapabilities = { ...capabilities, gpu: gpuHost.forNode(node.id) };
|
|
140
|
+
}
|
|
141
|
+
const seed = nodeSeed(graphSeed({ seed: node.documentSeed }), node.id);
|
|
142
|
+
const context = {
|
|
143
|
+
nodeId: node.id,
|
|
144
|
+
inputs: inputValues,
|
|
145
|
+
outputs,
|
|
146
|
+
props: Object.fromEntries(node.parameters.map((parameter) => [parameter.name, parameter.value])),
|
|
147
|
+
capabilities: nodeCapabilities,
|
|
148
|
+
// The node's own cook signal, not a fresh one that nobody aborts. See
|
|
149
|
+
// `Node.cookSignal` for what that cost.
|
|
150
|
+
signal: node.cookSignal ?? new AbortController().signal,
|
|
151
|
+
progress: silentProgress,
|
|
152
|
+
// The same derivation as the headless runtime, so a seeded node draws
|
|
153
|
+
// the same thing in both hosts.
|
|
154
|
+
seed,
|
|
155
|
+
random: seededRandom(seed)
|
|
156
|
+
};
|
|
157
|
+
await execute(context);
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function configureNode(node, registration, capabilities) {
|
|
161
|
+
node.setFunction(attachDefinition(node, registration, capabilities));
|
|
162
|
+
}
|
|
163
|
+
function registerDefinitionNodes(registrations, capabilities = {}) {
|
|
164
|
+
const libraries = /* @__PURE__ */ new Map();
|
|
165
|
+
for (const registration of registrations) {
|
|
166
|
+
const parts = registration.moduleId.split(".");
|
|
167
|
+
if (parts.length !== 3 || parts[0] !== "cascade") {
|
|
168
|
+
throw new Error(`Studio built-ins require cascade.<library>.<node>: ${registration.moduleId}`);
|
|
169
|
+
}
|
|
170
|
+
const libraryId = parts[1];
|
|
171
|
+
const nodeType = packagePathToType(registration.moduleId);
|
|
172
|
+
const classes = libraries.get(libraryId) ?? {};
|
|
173
|
+
classes[nodeType] = class extends Node {
|
|
174
|
+
constructor(id, graph) {
|
|
175
|
+
super(id, nodeType, graph);
|
|
176
|
+
}
|
|
177
|
+
setup() {
|
|
178
|
+
configureNode(this, registration, capabilities);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
libraries.set(libraryId, classes);
|
|
182
|
+
registerNodeMetadata({
|
|
183
|
+
type: registration.moduleId,
|
|
184
|
+
name: registration.definition.label ?? nodeType,
|
|
185
|
+
icon: registration.definition.icon,
|
|
186
|
+
description: registration.definition.description,
|
|
187
|
+
category: libraryId
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
for (const [libraryId, classes] of libraries) registerNodeClasses(libraryId, classes);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export {
|
|
194
|
+
attachDefinition,
|
|
195
|
+
registerDefinitionNodes
|
|
196
|
+
};
|
|
197
|
+
//# sourceMappingURL=chunk-SGJAET2L.js.map
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import {
|
|
2
|
+
declaredStagePython
|
|
3
|
+
} from "./chunk-WD62JIP6.js";
|
|
4
|
+
|
|
5
|
+
// server/src/stageRunner.ts
|
|
6
|
+
import { execFile } from "node:child_process";
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import os from "node:os";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
var TIMEOUT_MS = 10 * 60 * 1e3;
|
|
11
|
+
var MAX_BUFFER = 64 * 1024 * 1024;
|
|
12
|
+
function resolveProjectExecutable(project, configuredPath) {
|
|
13
|
+
if (path.isAbsolute(configuredPath)) {
|
|
14
|
+
throw new Error("cascade.json exec.stages.python must be python, python3, or a project-relative executable");
|
|
15
|
+
}
|
|
16
|
+
const executable = path.resolve(project.root, configuredPath);
|
|
17
|
+
const relative = path.relative(project.root, executable);
|
|
18
|
+
if (!relative || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
19
|
+
throw new Error("cascade.json exec.stages.python must be python, python3, or a project-relative executable");
|
|
20
|
+
}
|
|
21
|
+
return executable;
|
|
22
|
+
}
|
|
23
|
+
function readStageConfig(project) {
|
|
24
|
+
let raw;
|
|
25
|
+
try {
|
|
26
|
+
raw = JSON.parse(fs.readFileSync(project.resolve("cascade.json"), "utf8"));
|
|
27
|
+
} catch (error) {
|
|
28
|
+
if (error.code === "ENOENT") {
|
|
29
|
+
throw new Error("Project stages are not configured; add exec.stages to cascade.json");
|
|
30
|
+
}
|
|
31
|
+
throw new Error(`Cannot read cascade.json: ${error instanceof Error ? error.message : String(error)}`);
|
|
32
|
+
}
|
|
33
|
+
const stages = raw?.exec?.stages;
|
|
34
|
+
const value = stages && typeof stages === "object" && !Array.isArray(stages) ? stages : null;
|
|
35
|
+
const entrypoint = value && typeof value.entrypoint === "string" ? value.entrypoint : "";
|
|
36
|
+
if (!entrypoint) {
|
|
37
|
+
throw new Error("Project stages are not configured; add exec.stages.entrypoint to cascade.json");
|
|
38
|
+
}
|
|
39
|
+
project.resolve(entrypoint);
|
|
40
|
+
const worker = typeof value?.worker === "string" && value.worker ? value.worker : void 0;
|
|
41
|
+
if (value?.worker !== void 0 && !worker) {
|
|
42
|
+
throw new Error("cascade.json exec.stages.worker must be a project-relative path");
|
|
43
|
+
}
|
|
44
|
+
if (worker) project.resolve(worker);
|
|
45
|
+
const configuredPython = value?.python;
|
|
46
|
+
let python = "python3";
|
|
47
|
+
let explicit = false;
|
|
48
|
+
if (configuredPython !== void 0) {
|
|
49
|
+
if (typeof configuredPython !== "string" || !configuredPython.trim()) {
|
|
50
|
+
throw new Error("cascade.json exec.stages.python must be python, python3, or a project-relative executable");
|
|
51
|
+
}
|
|
52
|
+
const executable = configuredPython.trim();
|
|
53
|
+
explicit = executable !== "python" && executable !== "python3";
|
|
54
|
+
python = explicit ? resolveProjectExecutable(project, executable) : executable;
|
|
55
|
+
}
|
|
56
|
+
if (!explicit) python = declaredStagePython(project.root) ?? python;
|
|
57
|
+
const sources = value?.sources;
|
|
58
|
+
if (sources !== void 0 && (!Array.isArray(sources) || sources.some((item) => typeof item !== "string" || !item))) {
|
|
59
|
+
throw new Error("cascade.json exec.stages.sources must be a list of project-relative paths");
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
python,
|
|
63
|
+
entrypoint,
|
|
64
|
+
...worker ? { worker } : {},
|
|
65
|
+
...sources ? { sources } : {},
|
|
66
|
+
cache: readCachePolicy(value?.cache)
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function readCachePolicy(value) {
|
|
70
|
+
const strings = (list, name) => {
|
|
71
|
+
if (list === void 0) return [];
|
|
72
|
+
if (!Array.isArray(list) || list.some((item) => typeof item !== "string")) {
|
|
73
|
+
throw new Error(`cascade.json exec.stages.cache.${name} must be a list of names`);
|
|
74
|
+
}
|
|
75
|
+
return list;
|
|
76
|
+
};
|
|
77
|
+
if (value === void 0) return { enabled: true, scope: "cook", exclude: [], outputs: [] };
|
|
78
|
+
if (value === false) return { enabled: false, scope: "cook", exclude: [], outputs: [] };
|
|
79
|
+
if (value === true) return { enabled: true, scope: "all", exclude: [], outputs: [] };
|
|
80
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
81
|
+
const record = value;
|
|
82
|
+
if (record.scope !== void 0 && record.scope !== "cook" && record.scope !== "all") {
|
|
83
|
+
throw new Error('cascade.json exec.stages.cache.scope must be "cook" or "all"');
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
enabled: record.enabled !== false,
|
|
87
|
+
scope: record.scope === "all" ? "all" : "cook",
|
|
88
|
+
exclude: strings(record.exclude, "exclude"),
|
|
89
|
+
outputs: strings(record.outputs, "outputs")
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
throw new Error("cascade.json exec.stages.cache must be true, false or { exclude, outputs, scope }");
|
|
93
|
+
}
|
|
94
|
+
async function runProjectStage(project, stage, args, options = {}) {
|
|
95
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(stage)) {
|
|
96
|
+
throw new Error("stage contains unsupported characters");
|
|
97
|
+
}
|
|
98
|
+
const { python, entrypoint } = readStageConfig(project);
|
|
99
|
+
const script = project.resolve(entrypoint);
|
|
100
|
+
const payload = JSON.stringify(args);
|
|
101
|
+
const stdout = await new Promise((resolve, reject) => {
|
|
102
|
+
const child = execFile(
|
|
103
|
+
python,
|
|
104
|
+
[script, "--stage", stage, "--args", "-"],
|
|
105
|
+
{
|
|
106
|
+
cwd: project.root,
|
|
107
|
+
timeout: options.timeout ?? TIMEOUT_MS,
|
|
108
|
+
maxBuffer: MAX_BUFFER,
|
|
109
|
+
env: {
|
|
110
|
+
...options.env ?? process.env,
|
|
111
|
+
...options.cascade ? { CASCADE_STAGE_ENVELOPE: JSON.stringify(options.cascade) } : {},
|
|
112
|
+
// Python checks a .pyc against the source's size and whole-second
|
|
113
|
+
// mtime, so a quick same-length edit would run the old bytecode.
|
|
114
|
+
...options.codeHash ? { PYTHONPYCACHEPREFIX: path.join(os.tmpdir(), "cascade-pycache", options.codeHash) } : {}
|
|
115
|
+
},
|
|
116
|
+
signal: options.signal
|
|
117
|
+
},
|
|
118
|
+
(error, out, err) => {
|
|
119
|
+
if (error) {
|
|
120
|
+
const output = err?.trim() || out.trim().split("\n").pop() || error.message;
|
|
121
|
+
return reject(new Error(`stage "${stage}" failed: ${output}`));
|
|
122
|
+
}
|
|
123
|
+
if (err?.trim()) options.onOutput?.("stderr", err.trim());
|
|
124
|
+
resolve(out);
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
child.stdin?.end(payload);
|
|
128
|
+
});
|
|
129
|
+
if (options.onOutput) {
|
|
130
|
+
const lines = stdout.trim().split("\n");
|
|
131
|
+
const chatter = lines.slice(0, -1).join("\n").trim();
|
|
132
|
+
if (chatter) options.onOutput("stdout", chatter);
|
|
133
|
+
}
|
|
134
|
+
const lastLine = stdout.trim().split("\n").pop() || "{}";
|
|
135
|
+
let parsed;
|
|
136
|
+
try {
|
|
137
|
+
parsed = JSON.parse(lastLine);
|
|
138
|
+
} catch {
|
|
139
|
+
throw new Error(`stage "${stage}" returned no JSON: ${lastLine.slice(0, 200)}`);
|
|
140
|
+
}
|
|
141
|
+
if (parsed && typeof parsed === "object" && "error" in parsed) {
|
|
142
|
+
throw new Error(`stage "${stage}" failed: ${String(parsed.error)}`);
|
|
143
|
+
}
|
|
144
|
+
return parsed;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export {
|
|
148
|
+
readStageConfig,
|
|
149
|
+
runProjectStage
|
|
150
|
+
};
|
|
151
|
+
//# sourceMappingURL=chunk-VVGSLVSF.js.map
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// server/src/pythonEnv.ts
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
var RELATIVE_DIRECTORY = path.join(".cascade-cache", "python", "venv");
|
|
7
|
+
var MARKER = "cascade-env.json";
|
|
8
|
+
function pythonEnvDirectory(root) {
|
|
9
|
+
return path.join(root, RELATIVE_DIRECTORY);
|
|
10
|
+
}
|
|
11
|
+
function pythonEnvInterpreter(root) {
|
|
12
|
+
const directory = pythonEnvDirectory(root);
|
|
13
|
+
return process.platform === "win32" ? path.join(directory, "Scripts", "python.exe") : path.join(directory, "bin", "python");
|
|
14
|
+
}
|
|
15
|
+
function readPythonEnv(root) {
|
|
16
|
+
let raw;
|
|
17
|
+
try {
|
|
18
|
+
raw = JSON.parse(fs.readFileSync(path.join(root, "cascade.json"), "utf8"));
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const value = raw?.python;
|
|
23
|
+
if (value === void 0) return null;
|
|
24
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
25
|
+
throw new Error('cascade.json python must be an object: { "requirements": ["numpy", ...] | "requirements.txt", "interpreter"?: "python3" }');
|
|
26
|
+
}
|
|
27
|
+
const { requirements, interpreter } = value;
|
|
28
|
+
const list = Array.isArray(requirements) && requirements.every((item) => typeof item === "string" && item.trim());
|
|
29
|
+
if (!list && !(typeof requirements === "string" && requirements.trim())) {
|
|
30
|
+
throw new Error("cascade.json python.requirements must be a list of pip requirements or a project-relative requirements file");
|
|
31
|
+
}
|
|
32
|
+
if (typeof requirements === "string") containedPath(root, requirements, "python.requirements");
|
|
33
|
+
if (interpreter !== void 0 && (typeof interpreter !== "string" || !/^[A-Za-z0-9._/-]+$/.test(interpreter))) {
|
|
34
|
+
throw new Error("cascade.json python.interpreter must be an executable name such as python3.12");
|
|
35
|
+
}
|
|
36
|
+
return { requirements, interpreter: interpreter ?? "python3" };
|
|
37
|
+
}
|
|
38
|
+
function pythonEnvKey(root, spec) {
|
|
39
|
+
const requirements = typeof spec.requirements === "string" ? `file ${spec.requirements}
|
|
40
|
+
${fs.readFileSync(containedPath(root, spec.requirements, "python.requirements"), "utf8")}` : `list
|
|
41
|
+
${spec.requirements.join("\n")}`;
|
|
42
|
+
return crypto.createHash("sha256").update(`${spec.interpreter}\0${requirements}`).digest("hex");
|
|
43
|
+
}
|
|
44
|
+
function pythonEnvStatus(root) {
|
|
45
|
+
const directory = pythonEnvDirectory(root);
|
|
46
|
+
const python = pythonEnvInterpreter(root);
|
|
47
|
+
const spec = readPythonEnv(root);
|
|
48
|
+
if (!spec) return { declared: false, directory, python, state: "none" };
|
|
49
|
+
if (!fs.existsSync(python)) return { declared: true, directory, python, state: "missing", reason: `no environment at ${path.relative(root, directory)}` };
|
|
50
|
+
let marker = {};
|
|
51
|
+
try {
|
|
52
|
+
marker = JSON.parse(fs.readFileSync(path.join(directory, MARKER), "utf8"));
|
|
53
|
+
} catch {
|
|
54
|
+
}
|
|
55
|
+
let key;
|
|
56
|
+
try {
|
|
57
|
+
key = pythonEnvKey(root, spec);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
return { declared: true, directory, python, state: "stale", reason: error instanceof Error ? error.message : String(error) };
|
|
60
|
+
}
|
|
61
|
+
if (marker.key !== key) return { declared: true, directory, python, state: "stale", reason: "cascade.json python changed since the environment was built" };
|
|
62
|
+
return { declared: true, directory, python, state: "current" };
|
|
63
|
+
}
|
|
64
|
+
async function provisionPythonEnv(root, log = () => {
|
|
65
|
+
}) {
|
|
66
|
+
const spec = readPythonEnv(root);
|
|
67
|
+
if (!spec) throw new Error('cascade.json declares no python environment ("python": { "requirements": [...] })');
|
|
68
|
+
const before = pythonEnvStatus(root);
|
|
69
|
+
if (before.state === "current") return before;
|
|
70
|
+
const directory = pythonEnvDirectory(root);
|
|
71
|
+
if (fs.existsSync(directory)) {
|
|
72
|
+
const archive = path.join(path.dirname(directory), "_archive");
|
|
73
|
+
fs.mkdirSync(archive, { recursive: true });
|
|
74
|
+
fs.renameSync(directory, path.join(archive, `venv-${Date.now()}`));
|
|
75
|
+
}
|
|
76
|
+
fs.mkdirSync(path.dirname(directory), { recursive: true });
|
|
77
|
+
const file = typeof spec.requirements === "string" ? containedPath(root, spec.requirements, "python.requirements") : void 0;
|
|
78
|
+
const fileHasRequirements = file !== void 0 && fs.readFileSync(file, "utf8").split("\n").some((line) => line.trim() && !line.trim().startsWith("#"));
|
|
79
|
+
const requirements = file !== void 0 ? fileHasRequirements ? ["-r", file] : [] : [...spec.requirements];
|
|
80
|
+
log(`Creating ${path.relative(root, directory)} with ${spec.interpreter}`);
|
|
81
|
+
await run(spec.interpreter, ["-m", "venv", ...requirements.length ? [] : ["--without-pip"], directory], root);
|
|
82
|
+
if (requirements.length) {
|
|
83
|
+
log(`Installing ${typeof spec.requirements === "string" ? spec.requirements : spec.requirements.join(", ")}`);
|
|
84
|
+
await run(pythonEnvInterpreter(root), ["-m", "pip", "install", "--disable-pip-version-check", "--quiet", ...requirements], root);
|
|
85
|
+
}
|
|
86
|
+
fs.writeFileSync(path.join(directory, MARKER), `${JSON.stringify({ key: pythonEnvKey(root, spec), requirements: spec.requirements, interpreter: spec.interpreter, builtAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
|
|
87
|
+
`);
|
|
88
|
+
return pythonEnvStatus(root);
|
|
89
|
+
}
|
|
90
|
+
function declaredStagePython(root) {
|
|
91
|
+
const status = pythonEnvStatus(root);
|
|
92
|
+
if (status.state === "none") return void 0;
|
|
93
|
+
if (status.state !== "current") {
|
|
94
|
+
throw new Error(`This project declares a Python environment (cascade.json "python") that is ${status.state}: ${status.reason}. Run \`cascade doctor --fix\` in ${root} to build it.`);
|
|
95
|
+
}
|
|
96
|
+
return status.python;
|
|
97
|
+
}
|
|
98
|
+
function containedPath(root, file, name) {
|
|
99
|
+
const resolved = path.resolve(root, file);
|
|
100
|
+
const relative = path.relative(root, resolved);
|
|
101
|
+
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) throw new Error(`cascade.json ${name} must be a project-relative path`);
|
|
102
|
+
return resolved;
|
|
103
|
+
}
|
|
104
|
+
function run(command, args, cwd) {
|
|
105
|
+
return new Promise((resolve, reject) => {
|
|
106
|
+
execFile(command, [...args], { cwd, maxBuffer: 16 * 1024 * 1024, timeout: 20 * 6e4 }, (error, _stdout, stderr) => {
|
|
107
|
+
if (error) reject(new Error(`${path.basename(command)} ${args.slice(0, 3).join(" ")} failed: ${(stderr || error.message).trim().slice(-2e3)}`));
|
|
108
|
+
else resolve();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export {
|
|
114
|
+
pythonEnvStatus,
|
|
115
|
+
provisionPythonEnv,
|
|
116
|
+
declaredStagePython
|
|
117
|
+
};
|
|
118
|
+
//# sourceMappingURL=chunk-WD62JIP6.js.map
|