@darkhorseprojects/circuitry 0.3.10 → 0.4.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/SPEC.md +96 -0
- package/dist/cli.js +132 -142
- package/dist/graph.d.ts +155 -156
- package/dist/index.js +144 -141
- package/dist/node.js +131 -145
- package/examples/agent.circuitry.yaml +24 -0
- package/examples/imports.circuitry.yaml +13 -0
- package/examples/minimal.circuitry.yaml +20 -0
- package/examples/run-resource.circuitry.yaml +24 -0
- package/package.json +4 -1
- package/schema.json +65 -0
package/SPEC.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Circuitry 0.4
|
|
2
|
+
|
|
3
|
+
Circuitry is a resource-native source format for executable graphs.
|
|
4
|
+
|
|
5
|
+
A Circuitry file describes named resources and the dependencies between them. Running a graph means resolving an entry resource. Runtimes decide how to execute supported resource types.
|
|
6
|
+
|
|
7
|
+
## Top-level shape
|
|
8
|
+
|
|
9
|
+
```yaml
|
|
10
|
+
circuitry: "0.4"
|
|
11
|
+
title: string
|
|
12
|
+
description: string
|
|
13
|
+
imports: []
|
|
14
|
+
inputs: {}
|
|
15
|
+
entry: resource_id
|
|
16
|
+
entries: {}
|
|
17
|
+
resources: {}
|
|
18
|
+
outputs: {}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Resources
|
|
22
|
+
|
|
23
|
+
Resources are the graph. A resource is identified by its key under `resources`.
|
|
24
|
+
|
|
25
|
+
```yaml
|
|
26
|
+
resources:
|
|
27
|
+
id:
|
|
28
|
+
type: string
|
|
29
|
+
label: string
|
|
30
|
+
description: string
|
|
31
|
+
inputs: []
|
|
32
|
+
from: string
|
|
33
|
+
value: any
|
|
34
|
+
path: string
|
|
35
|
+
uri: string
|
|
36
|
+
mime: string
|
|
37
|
+
identity: string
|
|
38
|
+
instructions: string
|
|
39
|
+
tools: []
|
|
40
|
+
graph: string
|
|
41
|
+
entry: string
|
|
42
|
+
expect: {}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`inputs` may be a list of resource ids or a map of local input names to resource ids.
|
|
46
|
+
|
|
47
|
+
## Inputs
|
|
48
|
+
|
|
49
|
+
Top-level `inputs` declare runtime inputs. Resolvers may inject matching `type: input` resources when absent.
|
|
50
|
+
|
|
51
|
+
```yaml
|
|
52
|
+
inputs:
|
|
53
|
+
user_turn:
|
|
54
|
+
type: text
|
|
55
|
+
required: true
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Entry points
|
|
59
|
+
|
|
60
|
+
`entry` is the default resource to resolve. `entries` optionally names multiple run surfaces.
|
|
61
|
+
|
|
62
|
+
```yaml
|
|
63
|
+
entry: assistant
|
|
64
|
+
entries:
|
|
65
|
+
recover: recovered_context
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Run resources
|
|
69
|
+
|
|
70
|
+
`type: run` represents declarative graph execution as a resource.
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
resources:
|
|
74
|
+
recovered_context:
|
|
75
|
+
type: run
|
|
76
|
+
graph: ./context.circuitry.yaml
|
|
77
|
+
entry: recovery
|
|
78
|
+
inputs:
|
|
79
|
+
user_turn: user_turn
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
A runtime tool such as `run_graph` is dynamic capability. A `type: run` resource is declarative topology.
|
|
83
|
+
|
|
84
|
+
## Outputs
|
|
85
|
+
|
|
86
|
+
```yaml
|
|
87
|
+
outputs:
|
|
88
|
+
response:
|
|
89
|
+
from: assistant.response
|
|
90
|
+
schema:
|
|
91
|
+
response: str
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Extensions
|
|
95
|
+
|
|
96
|
+
Fields beginning with `x-` are preserved for runtimes and ecosystems.
|
package/dist/cli.js
CHANGED
|
@@ -12,150 +12,128 @@ import YAML2 from "yaml";
|
|
|
12
12
|
|
|
13
13
|
// src/graph.ts
|
|
14
14
|
import YAML from "yaml";
|
|
15
|
-
var CIRCUITRY_SPEC_VERSION = "0.
|
|
16
|
-
var DEFAULT_CIRCUITRY_VALIDATION_RULES = [
|
|
17
|
-
"no-self-loops",
|
|
18
|
-
"no-unknown-edge-endpoints",
|
|
19
|
-
"require-executable-inputs",
|
|
20
|
-
"no-cycles"
|
|
21
|
-
];
|
|
15
|
+
var CIRCUITRY_SPEC_VERSION = "0.4";
|
|
22
16
|
var DEFAULT_CIRCUITRY_VALIDATION_STANDARD = {
|
|
23
17
|
version: CIRCUITRY_SPEC_VERSION,
|
|
24
|
-
requireSpecVersion: true
|
|
25
|
-
rules: DEFAULT_CIRCUITRY_VALIDATION_RULES,
|
|
26
|
-
executableKinds: ["agent", "tool", "output"]
|
|
18
|
+
requireSpecVersion: true
|
|
27
19
|
};
|
|
28
|
-
var
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
20
|
+
var createCircuitryValidationStandard = (standard = {}) => ({
|
|
21
|
+
version: standard.version || DEFAULT_CIRCUITRY_VALIDATION_STANDARD.version,
|
|
22
|
+
requireSpecVersion: standard.requireSpecVersion ?? DEFAULT_CIRCUITRY_VALIDATION_STANDARD.requireSpecVersion
|
|
23
|
+
});
|
|
24
|
+
var getResourceInputs = (resource) => {
|
|
25
|
+
const inputs = resource.inputs;
|
|
26
|
+
if (!inputs) return [];
|
|
27
|
+
if (Array.isArray(inputs)) return inputs.map((from) => ({ from, to: "" }));
|
|
28
|
+
return Object.entries(inputs).map(([name, from]) => ({ from, to: "", name }));
|
|
29
|
+
};
|
|
30
|
+
var resourceInputIds = (resource) => getResourceInputs(resource).map((dependency) => dependency.from);
|
|
31
|
+
var getCircuitryDependencies = (graph) => {
|
|
32
|
+
const dependencies = [];
|
|
33
|
+
for (const [to, resource] of Object.entries(graph.resources || {})) {
|
|
34
|
+
for (const dependency of getResourceInputs(resource)) dependencies.push({ ...dependency, to });
|
|
35
|
+
}
|
|
36
|
+
return dependencies;
|
|
37
|
+
};
|
|
38
|
+
var getCircuitryRunSet = (graph, entry = graph.entry) => {
|
|
39
|
+
if (!entry) return [];
|
|
40
|
+
const visited = /* @__PURE__ */ new Set();
|
|
41
|
+
const visit = (id) => {
|
|
42
|
+
if (visited.has(id)) return;
|
|
43
|
+
const resource = graph.resources?.[id];
|
|
44
|
+
if (!resource) return;
|
|
45
|
+
for (const input of resourceInputIds(resource)) visit(input);
|
|
46
|
+
visited.add(id);
|
|
37
47
|
};
|
|
48
|
+
visit(entry);
|
|
49
|
+
return [...visited];
|
|
38
50
|
};
|
|
39
|
-
var
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
nodes.push({ id, kind: "input", label: input.label || id, input: { type: input.type, value: input.value, uri: input.uri || input.path, mimeType: input.mimeType, data: input.data } });
|
|
51
|
+
var toposortCircuitryResources = (graph, entry) => {
|
|
52
|
+
const ids = entry ? new Set(getCircuitryRunSet(graph, entry)) : new Set(Object.keys(graph.resources || {}));
|
|
53
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
54
|
+
const visited = /* @__PURE__ */ new Set();
|
|
55
|
+
const ordered = [];
|
|
56
|
+
const visit = (id) => {
|
|
57
|
+
if (!ids.has(id) || visited.has(id)) return;
|
|
58
|
+
if (visiting.has(id)) throw new Error(`Circuitry resource dependency cycle at ${id}`);
|
|
59
|
+
visiting.add(id);
|
|
60
|
+
const resource = graph.resources?.[id];
|
|
61
|
+
if (resource) for (const input of resourceInputIds(resource)) visit(input);
|
|
62
|
+
visiting.delete(id);
|
|
63
|
+
visited.add(id);
|
|
64
|
+
ordered.push(id);
|
|
65
|
+
};
|
|
66
|
+
for (const id of ids) visit(id);
|
|
67
|
+
return ordered;
|
|
68
|
+
};
|
|
69
|
+
var resolveCircuitrySource = (graph) => {
|
|
70
|
+
const inputs = graph.inputs || graph.args || {};
|
|
71
|
+
const resources = { ...graph.resources || {} };
|
|
72
|
+
for (const [id, input] of Object.entries(inputs)) {
|
|
73
|
+
if (!resources[id]) {
|
|
74
|
+
resources[id] = {
|
|
75
|
+
type: "input",
|
|
76
|
+
from: id,
|
|
77
|
+
label: input.label,
|
|
78
|
+
description: input.description,
|
|
79
|
+
mime: input.mime || input.mimeType
|
|
80
|
+
};
|
|
70
81
|
}
|
|
71
82
|
}
|
|
72
|
-
return {
|
|
83
|
+
return { ...graph, circuitry: CIRCUITRY_SPEC_VERSION, imports: graph.imports || [], inputs, resources };
|
|
73
84
|
};
|
|
74
|
-
var
|
|
75
|
-
|
|
76
|
-
return { ...graph, ...expandResources(graph.resources) };
|
|
77
|
-
};
|
|
78
|
-
var validateCircuitryGraphInternal = (graph, standard = {}, source) => {
|
|
79
|
-
const resolved = createCircuitryValidationStandard(standard, graph && typeof graph === "object" && !Array.isArray(graph) ? graph : void 0);
|
|
85
|
+
var validateCircuitrySource = (graph, standard = {}) => {
|
|
86
|
+
const resolvedStandard = createCircuitryValidationStandard(standard);
|
|
80
87
|
const errors = [];
|
|
81
88
|
const issue = (code, message = code, path2) => errors.push({ code, message, ...path2 ? { path: path2 } : {} });
|
|
82
|
-
if (!graph || typeof graph !== "object" || Array.isArray(graph))
|
|
89
|
+
if (!graph || typeof graph !== "object" || Array.isArray(graph)) {
|
|
90
|
+
return { ok: false, errors: [{ code: "invalid_graph", message: "invalid graph" }], standard: resolvedStandard };
|
|
91
|
+
}
|
|
83
92
|
const g = graph;
|
|
84
|
-
if (
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
if (resolvedStandard.requireSpecVersion && g.circuitry !== resolvedStandard.version) issue("invalid_version", "invalid version", ["circuitry"]);
|
|
94
|
+
if (!g.resources || Object.keys(g.resources).length === 0) issue("missing_resources", "missing resources", ["resources"]);
|
|
95
|
+
if (g.nodes) issue("forbidden_nodes", "forbidden nodes", ["nodes"]);
|
|
96
|
+
if (g.edges) issue("forbidden_edges", "forbidden edges", ["edges"]);
|
|
97
|
+
if (g.agents) issue("forbidden_agents", "forbidden agents", ["agents"]);
|
|
98
|
+
const resolved = resolveCircuitrySource(g);
|
|
99
|
+
const ids = new Set(Object.keys(resolved.resources || {}));
|
|
100
|
+
if (resolved.entry && !ids.has(resolved.entry)) issue("unknown_entry", "unknown entry", ["entry"]);
|
|
101
|
+
for (const [name, target] of Object.entries(resolved.entries || {})) {
|
|
102
|
+
if (!ids.has(target)) issue("unknown_entry", `unknown entry: ${name}`, ["entries", name]);
|
|
93
103
|
}
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
issue("missing_node_id", "missing node id", ["nodes", index, "id"]);
|
|
100
|
-
continue;
|
|
104
|
+
for (const [id, resource] of Object.entries(resolved.resources || {})) {
|
|
105
|
+
if (!resource.type) issue("missing_resource_type", "missing resource type", ["resources", id, "type"]);
|
|
106
|
+
for (const input of resourceInputIds(resource)) {
|
|
107
|
+
if (!ids.has(input)) issue("unknown_resource_input", `unknown resource input: ${input}`, ["resources", id, "inputs"]);
|
|
108
|
+
if (input === id) issue("self_input", "self input", ["resources", id, "inputs"]);
|
|
101
109
|
}
|
|
102
|
-
if (ids.has(node.id)) issue("duplicate_node_id", "duplicate node id", ["nodes", index, "id"]);
|
|
103
|
-
ids.add(node.id);
|
|
104
|
-
if (!node.kind) issue("missing_node_kind", "missing node kind", ["nodes", index, "kind"]);
|
|
105
110
|
}
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
adjacency.set(id, []);
|
|
110
|
-
incoming.set(id, 0);
|
|
111
|
+
for (const [name, output] of Object.entries(resolved.outputs || {})) {
|
|
112
|
+
const root = output.from.split(".")[0];
|
|
113
|
+
if (!ids.has(root)) issue("unknown_output_source", `unknown output source: ${output.from}`, ["outputs", name, "from"]);
|
|
111
114
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
if (rules.has("no-self-loops") && edge.from === edge.to) {
|
|
118
|
-
issue("self_loop", "self loop", ["edges", index]);
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
const ok = ids.has(edge.from) && ids.has(edge.to);
|
|
122
|
-
if (rules.has("no-unknown-edge-endpoints")) {
|
|
123
|
-
if (!ids.has(edge.from)) issue("unknown_edge_source", "unknown edge source", ["edges", index, "from"]);
|
|
124
|
-
if (!ids.has(edge.to)) issue("unknown_edge_target", "unknown edge target", ["edges", index, "to"]);
|
|
125
|
-
}
|
|
126
|
-
if (ok) {
|
|
127
|
-
adjacency.get(edge.from).push(edge.to);
|
|
128
|
-
incoming.set(edge.to, (incoming.get(edge.to) || 0) + 1);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
if (rules.has("require-executable-inputs")) {
|
|
132
|
-
const executable = new Set(resolved.executableKinds);
|
|
133
|
-
for (const [index, node] of (normalized.nodes || []).entries()) {
|
|
134
|
-
if (executable.has(node.kind) && (incoming.get(node.id) || 0) === 0) issue("missing_executable_input", "missing executable input", ["nodes", index]);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
if (rules.has("no-cycles")) {
|
|
138
|
-
const visiting = /* @__PURE__ */ new Set();
|
|
139
|
-
const visited = /* @__PURE__ */ new Set();
|
|
140
|
-
const visit = (id) => {
|
|
141
|
-
if (visiting.has(id)) return true;
|
|
142
|
-
if (visited.has(id)) return false;
|
|
143
|
-
visiting.add(id);
|
|
144
|
-
for (const next of adjacency.get(id) || []) if (visit(next)) return true;
|
|
145
|
-
visiting.delete(id);
|
|
146
|
-
visited.add(id);
|
|
147
|
-
return false;
|
|
148
|
-
};
|
|
149
|
-
for (const id of ids) if (visit(id)) {
|
|
150
|
-
issue("cycle", "cycle");
|
|
151
|
-
break;
|
|
152
|
-
}
|
|
115
|
+
try {
|
|
116
|
+
toposortCircuitryResources(resolved);
|
|
117
|
+
} catch {
|
|
118
|
+
issue("cycle", "cycle");
|
|
153
119
|
}
|
|
154
|
-
return { ok: errors.length === 0, errors, standard:
|
|
120
|
+
return { ok: errors.length === 0, errors, standard: resolvedStandard };
|
|
121
|
+
};
|
|
122
|
+
var validateCircuitryExecutionGraphWithStandard = validateCircuitrySource;
|
|
123
|
+
var validateCircuitryGraph = (graph, standard = {}) => validateCircuitrySource(graph, standard).errors.map((error) => error.message);
|
|
124
|
+
var inspectCircuitrySource = (graph) => {
|
|
125
|
+
const resolved = resolveCircuitrySource(graph);
|
|
126
|
+
return {
|
|
127
|
+
version: String(resolved.circuitry),
|
|
128
|
+
title: resolved.title,
|
|
129
|
+
entry: resolved.entry,
|
|
130
|
+
entries: resolved.entries || {},
|
|
131
|
+
inputs: Object.keys(resolved.inputs || {}),
|
|
132
|
+
resources: Object.keys(resolved.resources || {}),
|
|
133
|
+
outputs: Object.keys(resolved.outputs || {}),
|
|
134
|
+
dependencies: getCircuitryDependencies(resolved)
|
|
135
|
+
};
|
|
155
136
|
};
|
|
156
|
-
var validateCircuitryGraphWithStandard = (graph, standard = {}) => validateCircuitryGraphInternal(graph, standard, true);
|
|
157
|
-
var validateCircuitryExecutionGraphWithStandard = (graph, standard = {}) => validateCircuitryGraphInternal(graph, standard, false);
|
|
158
|
-
var validateCircuitryGraph = (graph, standard = {}) => validateCircuitryGraphWithStandard(graph, standard).errors.map((error) => error.message);
|
|
159
137
|
|
|
160
138
|
// src/yaml.ts
|
|
161
139
|
var parseYamlData = (text) => {
|
|
@@ -186,10 +164,19 @@ import YAML3 from "yaml";
|
|
|
186
164
|
|
|
187
165
|
// src/node.ts
|
|
188
166
|
var defaultFilename = "graph.circuitry.yaml";
|
|
189
|
-
var resourceInputs = (resource) =>
|
|
190
|
-
|
|
167
|
+
var resourceInputs = (resource) => {
|
|
168
|
+
if (!("inputs" in resource) || !resource.inputs) return [];
|
|
169
|
+
return Array.isArray(resource.inputs) ? resource.inputs : Object.values(resource.inputs);
|
|
170
|
+
};
|
|
171
|
+
var withResourceInputs = (resource, inputs) => {
|
|
172
|
+
if (!("inputs" in resource) || !resource.inputs) return resource;
|
|
173
|
+
if (Array.isArray(resource.inputs)) return { ...resource, inputs };
|
|
174
|
+
const inputMap = resource.inputs;
|
|
175
|
+
const names = Object.keys(inputMap);
|
|
176
|
+
return { ...resource, inputs: Object.fromEntries(names.map((name, index) => [name, inputs[index] || inputMap[name]])) };
|
|
177
|
+
};
|
|
191
178
|
var selectedImportResources = (imp, resources) => {
|
|
192
|
-
if (imp.resources === "*") return Object.fromEntries(Object.keys(resources).map((id) => [id, id]));
|
|
179
|
+
if (!imp.resources || imp.resources === "*") return Object.fromEntries(Object.keys(resources).map((id) => [id, id]));
|
|
193
180
|
if (Array.isArray(imp.resources)) return Object.fromEntries(imp.resources.map((id) => [id, id]));
|
|
194
181
|
return imp.resources;
|
|
195
182
|
};
|
|
@@ -227,7 +214,7 @@ var resolveCircuitryGraph = async (parsed, graphFile, text, standard, stack = []
|
|
|
227
214
|
resources[id] = resource;
|
|
228
215
|
origins[id] = graphFile;
|
|
229
216
|
}
|
|
230
|
-
const graph =
|
|
217
|
+
const graph = { ...parsed, circuitry: "0.4", imports: [], inputs: parsed.inputs || parsed.args || {}, resources };
|
|
231
218
|
if (stack.length === 0) {
|
|
232
219
|
const validation = validateCircuitryExecutionGraphWithStandard(graph, standard);
|
|
233
220
|
if (validation.errors.length) {
|
|
@@ -247,27 +234,30 @@ var loadCircuitryGraphFile = async (filename2 = defaultFilename, standard, stack
|
|
|
247
234
|
// src/cli.ts
|
|
248
235
|
var [, , command, filename] = process.argv;
|
|
249
236
|
var usage = () => {
|
|
250
|
-
console.error("usage: circuitry <check|parse> <file.yaml>");
|
|
237
|
+
console.error("usage: circuitry <check|resolve|inspect|parse> <file.yaml|--stdin>");
|
|
251
238
|
process.exit(2);
|
|
252
239
|
};
|
|
240
|
+
var readInput = async (name) => name === "--stdin" ? await new Promise((resolve, reject) => {
|
|
241
|
+
let data = "";
|
|
242
|
+
process.stdin.on("data", (chunk) => data += chunk);
|
|
243
|
+
process.stdin.on("end", () => resolve(data));
|
|
244
|
+
process.stdin.on("error", reject);
|
|
245
|
+
}) : await readFile2(name, "utf8");
|
|
253
246
|
if (!command || !filename) usage();
|
|
254
247
|
try {
|
|
255
|
-
if (command === "parse") {
|
|
248
|
+
if (command === "resolve" || command === "parse") {
|
|
256
249
|
if (filename.endsWith(".circuitry.yaml") || filename.endsWith(".circuitry.yml")) {
|
|
257
250
|
const loaded = await loadCircuitryGraphFile(filename);
|
|
258
|
-
process.stdout.write(JSON.stringify(
|
|
251
|
+
process.stdout.write(JSON.stringify(loaded.graph, null, 2));
|
|
259
252
|
} else {
|
|
260
|
-
|
|
261
|
-
let data = "";
|
|
262
|
-
process.stdin.on("data", (chunk) => data += chunk);
|
|
263
|
-
process.stdin.on("end", () => resolve(data));
|
|
264
|
-
process.stdin.on("error", reject);
|
|
265
|
-
}) : await readFile2(filename, "utf8");
|
|
266
|
-
process.stdout.write(JSON.stringify(parseYamlData(text)));
|
|
253
|
+
process.stdout.write(JSON.stringify(parseYamlData(await readInput(filename)), null, 2));
|
|
267
254
|
}
|
|
255
|
+
} else if (command === "inspect") {
|
|
256
|
+
const loaded = await loadCircuitryGraphFile(filename);
|
|
257
|
+
process.stdout.write(JSON.stringify(inspectCircuitrySource(loaded.graph), null, 2));
|
|
268
258
|
} else if (command === "check") {
|
|
269
259
|
const loaded = await loadCircuitryGraphFile(filename);
|
|
270
|
-
const result =
|
|
260
|
+
const result = validateCircuitrySource(loaded.graph);
|
|
271
261
|
if (!result.ok) {
|
|
272
262
|
console.error(result.errors.map((error) => error.message).join("\n"));
|
|
273
263
|
process.exit(1);
|