blokctl 0.2.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 (169) hide show
  1. package/dist/commands/build/index.d.ts +2 -0
  2. package/dist/commands/build/index.js +210 -0
  3. package/dist/commands/config/index.d.ts +1 -0
  4. package/dist/commands/config/index.js +46 -0
  5. package/dist/commands/cost/index.d.ts +1 -0
  6. package/dist/commands/cost/index.js +74 -0
  7. package/dist/commands/create/node.d.ts +2 -0
  8. package/dist/commands/create/node.js +541 -0
  9. package/dist/commands/create/project.d.ts +2 -0
  10. package/dist/commands/create/project.js +941 -0
  11. package/dist/commands/create/utils/Examples.d.ts +39 -0
  12. package/dist/commands/create/utils/Examples.js +983 -0
  13. package/dist/commands/create/workflow.d.ts +2 -0
  14. package/dist/commands/create/workflow.js +109 -0
  15. package/dist/commands/deploy/index.d.ts +2 -0
  16. package/dist/commands/deploy/index.js +176 -0
  17. package/dist/commands/dev/index.d.ts +2 -0
  18. package/dist/commands/dev/index.js +190 -0
  19. package/dist/commands/generate/GenerationAnalytics.d.ts +61 -0
  20. package/dist/commands/generate/GenerationAnalytics.js +162 -0
  21. package/dist/commands/generate/GenerationAnalytics.test.d.ts +1 -0
  22. package/dist/commands/generate/GenerationAnalytics.test.js +407 -0
  23. package/dist/commands/generate/NodeFileWriter.d.ts +5 -0
  24. package/dist/commands/generate/NodeFileWriter.js +240 -0
  25. package/dist/commands/generate/NodeGenerator.d.ts +20 -0
  26. package/dist/commands/generate/NodeGenerator.js +181 -0
  27. package/dist/commands/generate/NodeGenerator.test.d.ts +1 -0
  28. package/dist/commands/generate/NodeGenerator.test.js +101 -0
  29. package/dist/commands/generate/PromptVersioning.d.ts +25 -0
  30. package/dist/commands/generate/PromptVersioning.js +71 -0
  31. package/dist/commands/generate/PromptVersioning.test.d.ts +1 -0
  32. package/dist/commands/generate/PromptVersioning.test.js +120 -0
  33. package/dist/commands/generate/RegisterNode.d.ts +3 -0
  34. package/dist/commands/generate/RegisterNode.js +37 -0
  35. package/dist/commands/generate/RuntimeGenerator.d.ts +40 -0
  36. package/dist/commands/generate/RuntimeGenerator.js +369 -0
  37. package/dist/commands/generate/RuntimeGenerator.test.d.ts +1 -0
  38. package/dist/commands/generate/RuntimeGenerator.test.js +553 -0
  39. package/dist/commands/generate/TriggerGenerator.d.ts +22 -0
  40. package/dist/commands/generate/TriggerGenerator.js +220 -0
  41. package/dist/commands/generate/TriggerGenerator.test.d.ts +1 -0
  42. package/dist/commands/generate/TriggerGenerator.test.js +209 -0
  43. package/dist/commands/generate/WorkflowGenerator.d.ts +20 -0
  44. package/dist/commands/generate/WorkflowGenerator.js +131 -0
  45. package/dist/commands/generate/WorkflowGenerator.test.d.ts +1 -0
  46. package/dist/commands/generate/WorkflowGenerator.test.js +77 -0
  47. package/dist/commands/generate/e2e/NodeGenerator.e2e.test.d.ts +1 -0
  48. package/dist/commands/generate/e2e/NodeGenerator.e2e.test.js +216 -0
  49. package/dist/commands/generate/e2e/RuntimeGenerator.e2e.test.d.ts +1 -0
  50. package/dist/commands/generate/e2e/RuntimeGenerator.e2e.test.js +759 -0
  51. package/dist/commands/generate/e2e/TriggerGenerator.e2e.test.d.ts +1 -0
  52. package/dist/commands/generate/e2e/TriggerGenerator.e2e.test.js +295 -0
  53. package/dist/commands/generate/e2e/WorkflowGenerator.e2e.test.d.ts +1 -0
  54. package/dist/commands/generate/e2e/WorkflowGenerator.e2e.test.js +353 -0
  55. package/dist/commands/generate/index.d.ts +1 -0
  56. package/dist/commands/generate/index.js +418 -0
  57. package/dist/commands/generate/prompts/create-fn-node.system.d.ts +5 -0
  58. package/dist/commands/generate/prompts/create-fn-node.system.js +256 -0
  59. package/dist/commands/generate/prompts/create-node-manifest.system.d.ts +4 -0
  60. package/dist/commands/generate/prompts/create-node-manifest.system.js +41 -0
  61. package/dist/commands/generate/prompts/create-node.system.d.ts +5 -0
  62. package/dist/commands/generate/prompts/create-node.system.js +114 -0
  63. package/dist/commands/generate/prompts/create-readme.system.d.ts +4 -0
  64. package/dist/commands/generate/prompts/create-readme.system.js +83 -0
  65. package/dist/commands/generate/prompts/create-runtime.system.d.ts +5 -0
  66. package/dist/commands/generate/prompts/create-runtime.system.js +284 -0
  67. package/dist/commands/generate/prompts/create-trigger.system.d.ts +5 -0
  68. package/dist/commands/generate/prompts/create-trigger.system.js +293 -0
  69. package/dist/commands/generate/prompts/create-workflow.system.d.ts +5 -0
  70. package/dist/commands/generate/prompts/create-workflow.system.js +476 -0
  71. package/dist/commands/generate/prompts/register-node.system.d.ts +4 -0
  72. package/dist/commands/generate/prompts/register-node.system.js +26 -0
  73. package/dist/commands/generate/validators/CompilationValidator.d.ts +9 -0
  74. package/dist/commands/generate/validators/CompilationValidator.js +86 -0
  75. package/dist/commands/generate/validators/CompilationValidator.test.d.ts +1 -0
  76. package/dist/commands/generate/validators/CompilationValidator.test.js +161 -0
  77. package/dist/commands/generate/validators/NodeValidator.d.ts +18 -0
  78. package/dist/commands/generate/validators/NodeValidator.js +217 -0
  79. package/dist/commands/generate/validators/NodeValidator.test.d.ts +1 -0
  80. package/dist/commands/generate/validators/NodeValidator.test.js +281 -0
  81. package/dist/commands/generate/validators/WorkflowValidator.d.ts +6 -0
  82. package/dist/commands/generate/validators/WorkflowValidator.js +301 -0
  83. package/dist/commands/generate/validators/WorkflowValidator.test.d.ts +1 -0
  84. package/dist/commands/generate/validators/WorkflowValidator.test.js +647 -0
  85. package/dist/commands/generate/validators/index.d.ts +4 -0
  86. package/dist/commands/generate/validators/index.js +2 -0
  87. package/dist/commands/graph/index.d.ts +1 -0
  88. package/dist/commands/graph/index.js +69 -0
  89. package/dist/commands/install/index.d.ts +1 -0
  90. package/dist/commands/install/index.js +4 -0
  91. package/dist/commands/install/node.d.ts +4 -0
  92. package/dist/commands/install/node.js +136 -0
  93. package/dist/commands/install/workflow.d.ts +4 -0
  94. package/dist/commands/install/workflow.js +62 -0
  95. package/dist/commands/login/index.d.ts +2 -0
  96. package/dist/commands/login/index.js +77 -0
  97. package/dist/commands/logout/index.d.ts +2 -0
  98. package/dist/commands/logout/index.js +20 -0
  99. package/dist/commands/marketplace/runtime.d.ts +54 -0
  100. package/dist/commands/marketplace/runtime.js +350 -0
  101. package/dist/commands/migrate/index.d.ts +1 -0
  102. package/dist/commands/migrate/index.js +14 -0
  103. package/dist/commands/migrate/node.d.ts +2 -0
  104. package/dist/commands/migrate/node.js +110 -0
  105. package/dist/commands/monitor/index.d.ts +1 -0
  106. package/dist/commands/monitor/index.js +28 -0
  107. package/dist/commands/monitor/monitor-component.d.ts +1 -0
  108. package/dist/commands/monitor/monitor-component.js +271 -0
  109. package/dist/commands/monitor/static/index.html +2124 -0
  110. package/dist/commands/monitor/static-web-server.d.ts +1 -0
  111. package/dist/commands/monitor/static-web-server.js +89 -0
  112. package/dist/commands/profile/index.d.ts +1 -0
  113. package/dist/commands/profile/index.js +112 -0
  114. package/dist/commands/publish/index.d.ts +1 -0
  115. package/dist/commands/publish/index.js +4 -0
  116. package/dist/commands/publish/node.d.ts +4 -0
  117. package/dist/commands/publish/node.js +231 -0
  118. package/dist/commands/publish/workflow.d.ts +4 -0
  119. package/dist/commands/publish/workflow.js +165 -0
  120. package/dist/commands/search/docs.d.ts +17 -0
  121. package/dist/commands/search/docs.js +179 -0
  122. package/dist/commands/search/index.d.ts +1 -0
  123. package/dist/commands/search/index.js +5 -0
  124. package/dist/commands/search/indexer.d.ts +10 -0
  125. package/dist/commands/search/indexer.js +265 -0
  126. package/dist/commands/search/nodes.d.ts +4 -0
  127. package/dist/commands/search/nodes.js +101 -0
  128. package/dist/commands/search/workflow.d.ts +4 -0
  129. package/dist/commands/search/workflow.js +100 -0
  130. package/dist/commands/trace/index.d.ts +1 -0
  131. package/dist/commands/trace/index.js +26 -0
  132. package/dist/commands/trace/startStudio.d.ts +8 -0
  133. package/dist/commands/trace/startStudio.js +116 -0
  134. package/dist/index.d.ts +17 -0
  135. package/dist/index.js +186 -0
  136. package/dist/services/commander.d.ts +9 -0
  137. package/dist/services/commander.js +20 -0
  138. package/dist/services/constants.d.ts +1 -0
  139. package/dist/services/constants.js +3 -0
  140. package/dist/services/local-token-manager.d.ts +14 -0
  141. package/dist/services/local-token-manager.js +99 -0
  142. package/dist/services/non-interactive.d.ts +5 -0
  143. package/dist/services/non-interactive.js +30 -0
  144. package/dist/services/package-manager.d.ts +35 -0
  145. package/dist/services/package-manager.js +111 -0
  146. package/dist/services/posthog.d.ts +31 -0
  147. package/dist/services/posthog.js +159 -0
  148. package/dist/services/registry-manager.d.ts +9 -0
  149. package/dist/services/registry-manager.js +26 -0
  150. package/dist/services/runtime-detector.d.ts +23 -0
  151. package/dist/services/runtime-detector.js +181 -0
  152. package/dist/services/runtime-setup.d.ts +36 -0
  153. package/dist/services/runtime-setup.js +250 -0
  154. package/dist/services/utils.d.ts +2 -0
  155. package/dist/services/utils.js +29 -0
  156. package/dist/services/workflow-loader.d.ts +30 -0
  157. package/dist/services/workflow-loader.js +46 -0
  158. package/dist/studio-dist/assets/charts-Dso0hPUR.js +68 -0
  159. package/dist/studio-dist/assets/graph-CsV2nWGn.js +23 -0
  160. package/dist/studio-dist/assets/icons-zP8LLgPh.js +311 -0
  161. package/dist/studio-dist/assets/index-CLyEkXMx.css +1 -0
  162. package/dist/studio-dist/assets/index-CNXFX_ar.js +27 -0
  163. package/dist/studio-dist/assets/react-vendor--Eh9ivFN.js +17 -0
  164. package/dist/studio-dist/assets/tanstack-query-CiM1U6F5.js +1 -0
  165. package/dist/studio-dist/assets/tanstack-router-Btjy0MKq.js +25 -0
  166. package/dist/studio-dist/assets/tanstack-table-DhwRvuH2.js +22 -0
  167. package/dist/studio-dist/favicon.svg +5 -0
  168. package/dist/studio-dist/index.html +21 -0
  169. package/package.json +75 -0
@@ -0,0 +1,301 @@
1
+ const VALID_TRIGGER_TYPES = [
2
+ "http",
3
+ "grpc",
4
+ "manual",
5
+ "cron",
6
+ "queue",
7
+ "pubsub",
8
+ "worker",
9
+ "webhook",
10
+ "sse",
11
+ "websocket",
12
+ ];
13
+ const VALID_STEP_TYPES = ["module", "local", "runtime.python3", "runtime.go", "runtime.java"];
14
+ const VALID_HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH", "ANY", "*"];
15
+ const VALID_QUEUE_PROVIDERS = ["kafka", "rabbitmq", "sqs", "redis", "beanstalk"];
16
+ const VALID_PUBSUB_PROVIDERS = ["gcp", "aws", "azure"];
17
+ const VALID_WEBHOOK_SOURCES = ["github", "stripe", "shopify", "custom"];
18
+ export function validateWorkflow(jsonString) {
19
+ const errors = [];
20
+ const warnings = [];
21
+ let workflow;
22
+ try {
23
+ workflow = JSON.parse(jsonString);
24
+ }
25
+ catch (e) {
26
+ return {
27
+ valid: false,
28
+ errors: [`Invalid JSON: ${e.message}`],
29
+ warnings: [],
30
+ };
31
+ }
32
+ validateTopLevel(workflow, errors, warnings);
33
+ if (workflow.trigger && typeof workflow.trigger === "object") {
34
+ validateTrigger(workflow.trigger, errors, warnings);
35
+ }
36
+ const stepNames = new Set();
37
+ if (Array.isArray(workflow.steps)) {
38
+ validateSteps(workflow.steps, stepNames, errors, warnings);
39
+ }
40
+ if (workflow.nodes && typeof workflow.nodes === "object") {
41
+ validateNodes(workflow.nodes, stepNames, errors, warnings);
42
+ }
43
+ return {
44
+ valid: errors.length === 0,
45
+ errors,
46
+ warnings,
47
+ };
48
+ }
49
+ function validateTopLevel(workflow, errors, warnings) {
50
+ if (!workflow.name || typeof workflow.name !== "string") {
51
+ errors.push('Missing or invalid "name" field (must be a non-empty string)');
52
+ }
53
+ if (workflow.description === undefined) {
54
+ warnings.push('Missing "description" field - recommended for documentation');
55
+ }
56
+ else if (typeof workflow.description !== "string") {
57
+ errors.push('"description" must be a string');
58
+ }
59
+ if (!workflow.version || typeof workflow.version !== "string") {
60
+ errors.push('Missing or invalid "version" field (must be a string like "1.0.0")');
61
+ }
62
+ else if (!/^\d+\.\d+\.\d+/.test(workflow.version)) {
63
+ warnings.push('"version" should follow semver format (e.g., "1.0.0")');
64
+ }
65
+ if (!workflow.trigger || typeof workflow.trigger !== "object") {
66
+ errors.push('Missing or invalid "trigger" field (must be an object)');
67
+ }
68
+ if (!workflow.steps || !Array.isArray(workflow.steps)) {
69
+ errors.push('Missing or invalid "steps" field (must be an array)');
70
+ }
71
+ else if (workflow.steps.length === 0) {
72
+ errors.push('"steps" array must not be empty');
73
+ }
74
+ if (!workflow.nodes || typeof workflow.nodes !== "object") {
75
+ errors.push('Missing or invalid "nodes" field (must be an object)');
76
+ }
77
+ }
78
+ function validateTrigger(trigger, errors, warnings) {
79
+ const triggerKeys = Object.keys(trigger);
80
+ if (triggerKeys.length === 0) {
81
+ errors.push("Trigger must have exactly one trigger type");
82
+ return;
83
+ }
84
+ if (triggerKeys.length > 1) {
85
+ errors.push(`Trigger must have exactly one trigger type, found ${triggerKeys.length}: ${triggerKeys.join(", ")}`);
86
+ return;
87
+ }
88
+ const triggerType = triggerKeys[0];
89
+ if (!VALID_TRIGGER_TYPES.includes(triggerType)) {
90
+ errors.push(`Invalid trigger type "${triggerType}". Valid types: ${VALID_TRIGGER_TYPES.join(", ")}`);
91
+ return;
92
+ }
93
+ const config = trigger[triggerType];
94
+ if (!config || typeof config !== "object") {
95
+ errors.push(`Trigger "${triggerType}" must have a configuration object`);
96
+ return;
97
+ }
98
+ switch (triggerType) {
99
+ case "http":
100
+ validateHttpTrigger(config, errors, warnings);
101
+ break;
102
+ case "queue":
103
+ validateQueueTrigger(config, errors, warnings);
104
+ break;
105
+ case "pubsub":
106
+ validatePubSubTrigger(config, errors, warnings);
107
+ break;
108
+ case "cron":
109
+ validateCronTrigger(config, errors, warnings);
110
+ break;
111
+ case "webhook":
112
+ validateWebhookTrigger(config, errors, warnings);
113
+ break;
114
+ }
115
+ }
116
+ function validateHttpTrigger(config, errors, warnings) {
117
+ if (config.method && typeof config.method === "string") {
118
+ if (!VALID_HTTP_METHODS.includes(config.method.toUpperCase())) {
119
+ errors.push(`Invalid HTTP method "${config.method}". Valid methods: ${VALID_HTTP_METHODS.join(", ")}`);
120
+ }
121
+ }
122
+ if (config.path !== undefined && typeof config.path !== "string") {
123
+ errors.push('HTTP trigger "path" must be a string');
124
+ }
125
+ if (config.accept !== undefined && typeof config.accept !== "string") {
126
+ warnings.push('HTTP trigger "accept" should be a string');
127
+ }
128
+ }
129
+ function validateQueueTrigger(config, errors, warnings) {
130
+ if (!config.provider || typeof config.provider !== "string") {
131
+ errors.push('Queue trigger requires "provider" field');
132
+ }
133
+ else if (!VALID_QUEUE_PROVIDERS.includes(config.provider)) {
134
+ errors.push(`Invalid queue provider "${config.provider}". Valid providers: ${VALID_QUEUE_PROVIDERS.join(", ")}`);
135
+ }
136
+ if (!config.topic || typeof config.topic !== "string") {
137
+ errors.push('Queue trigger requires "topic" field');
138
+ }
139
+ }
140
+ function validatePubSubTrigger(config, errors, warnings) {
141
+ if (!config.provider || typeof config.provider !== "string") {
142
+ errors.push('Pub/Sub trigger requires "provider" field');
143
+ }
144
+ else if (!VALID_PUBSUB_PROVIDERS.includes(config.provider)) {
145
+ errors.push(`Invalid pub/sub provider "${config.provider}". Valid providers: ${VALID_PUBSUB_PROVIDERS.join(", ")}`);
146
+ }
147
+ if (!config.topic || typeof config.topic !== "string") {
148
+ errors.push('Pub/Sub trigger requires "topic" field');
149
+ }
150
+ if (!config.subscription || typeof config.subscription !== "string") {
151
+ errors.push('Pub/Sub trigger requires "subscription" field');
152
+ }
153
+ }
154
+ function validateCronTrigger(config, errors, warnings) {
155
+ if (!config.schedule || typeof config.schedule !== "string") {
156
+ errors.push('Cron trigger requires "schedule" field (cron expression)');
157
+ }
158
+ else {
159
+ const parts = config.schedule.trim().split(/\s+/);
160
+ if (parts.length < 5 || parts.length > 6) {
161
+ errors.push(`Invalid cron expression "${config.schedule}". Must have 5 or 6 space-separated fields`);
162
+ }
163
+ }
164
+ if (config.timezone !== undefined && typeof config.timezone !== "string") {
165
+ errors.push('Cron trigger "timezone" must be a string');
166
+ }
167
+ }
168
+ function validateWebhookTrigger(config, errors, warnings) {
169
+ if (!config.source || typeof config.source !== "string") {
170
+ errors.push('Webhook trigger requires "source" field');
171
+ }
172
+ else if (!VALID_WEBHOOK_SOURCES.includes(config.source)) {
173
+ warnings.push(`Unknown webhook source "${config.source}". Known sources: ${VALID_WEBHOOK_SOURCES.join(", ")}`);
174
+ }
175
+ if (!config.events || !Array.isArray(config.events)) {
176
+ errors.push('Webhook trigger requires "events" array');
177
+ }
178
+ else if (config.events.length === 0) {
179
+ errors.push('Webhook trigger "events" array must not be empty');
180
+ }
181
+ }
182
+ function validateSteps(steps, stepNames, errors, warnings) {
183
+ for (let i = 0; i < steps.length; i++) {
184
+ const step = steps[i];
185
+ if (!step || typeof step !== "object") {
186
+ errors.push(`Step at index ${i} must be an object`);
187
+ continue;
188
+ }
189
+ if (!step.name || typeof step.name !== "string") {
190
+ errors.push(`Step at index ${i} is missing required "name" field`);
191
+ continue;
192
+ }
193
+ if (!step.node || typeof step.node !== "string") {
194
+ errors.push(`Step "${step.name}" is missing required "node" field`);
195
+ }
196
+ if (!step.type || typeof step.type !== "string") {
197
+ errors.push(`Step "${step.name}" is missing required "type" field`);
198
+ }
199
+ else if (!VALID_STEP_TYPES.includes(step.type)) {
200
+ warnings.push(`Step "${step.name}" has unusual type "${step.type}". Common types: ${VALID_STEP_TYPES.join(", ")}`);
201
+ }
202
+ if (stepNames.has(step.name)) {
203
+ errors.push(`Duplicate step name "${step.name}"`);
204
+ }
205
+ stepNames.add(step.name);
206
+ }
207
+ }
208
+ function validateNodes(nodes, stepNames, errors, warnings) {
209
+ const allReferencedStepNames = new Set(stepNames);
210
+ for (const [nodeName, nodeConfig] of Object.entries(nodes)) {
211
+ if (!nodeConfig || typeof nodeConfig !== "object") {
212
+ errors.push(`Node "${nodeName}" must be an object`);
213
+ continue;
214
+ }
215
+ const config = nodeConfig;
216
+ if (config.conditions && Array.isArray(config.conditions)) {
217
+ collectConditionalStepNames(config.conditions, allReferencedStepNames);
218
+ }
219
+ }
220
+ for (const stepName of allReferencedStepNames) {
221
+ if (!nodes[stepName]) {
222
+ errors.push(`Step "${stepName}" is referenced but has no matching entry in "nodes"`);
223
+ }
224
+ }
225
+ for (const [nodeName, nodeConfig] of Object.entries(nodes)) {
226
+ if (!nodeConfig || typeof nodeConfig !== "object")
227
+ continue;
228
+ const config = nodeConfig;
229
+ if (config.conditions && Array.isArray(config.conditions)) {
230
+ validateConditions(nodeName, config.conditions, errors, warnings);
231
+ }
232
+ else if (config.inputs === undefined) {
233
+ warnings.push(`Node "${nodeName}" has neither "inputs" nor "conditions" - may need configuration`);
234
+ }
235
+ }
236
+ }
237
+ function collectConditionalStepNames(conditions, stepNames) {
238
+ for (const condition of conditions) {
239
+ if (!condition || typeof condition !== "object")
240
+ continue;
241
+ const cond = condition;
242
+ if (cond.steps && Array.isArray(cond.steps)) {
243
+ for (const step of cond.steps) {
244
+ if (step.name && typeof step.name === "string") {
245
+ stepNames.add(step.name);
246
+ }
247
+ }
248
+ }
249
+ }
250
+ }
251
+ function validateConditions(nodeName, conditions, errors, warnings) {
252
+ let hasElse = false;
253
+ const conditionStepNames = new Set();
254
+ for (let i = 0; i < conditions.length; i++) {
255
+ const cond = conditions[i];
256
+ if (!cond || typeof cond !== "object") {
257
+ errors.push(`Condition ${i} in node "${nodeName}" must be an object`);
258
+ continue;
259
+ }
260
+ if (!cond.type || !["if", "else"].includes(cond.type)) {
261
+ errors.push(`Condition ${i} in node "${nodeName}" must have type "if" or "else"`);
262
+ continue;
263
+ }
264
+ if (cond.type === "if") {
265
+ if (!cond.condition || typeof cond.condition !== "string") {
266
+ errors.push(`Condition ${i} in node "${nodeName}" (type "if") must have a "condition" string`);
267
+ }
268
+ else {
269
+ const condStr = cond.condition;
270
+ if (!condStr.includes("ctx.")) {
271
+ warnings.push(`Condition ${i} in node "${nodeName}" doesn't reference ctx.* - may be invalid`);
272
+ }
273
+ }
274
+ }
275
+ if (cond.type === "else") {
276
+ hasElse = true;
277
+ if (i !== conditions.length - 1) {
278
+ errors.push(`"else" condition in node "${nodeName}" must be the last condition`);
279
+ }
280
+ }
281
+ if (!cond.steps || !Array.isArray(cond.steps) || cond.steps.length === 0) {
282
+ errors.push(`Condition ${i} in node "${nodeName}" must have at least one step`);
283
+ }
284
+ else {
285
+ for (const step of cond.steps) {
286
+ if (step.name && typeof step.name === "string") {
287
+ if (conditionStepNames.has(step.name)) {
288
+ warnings.push(`Duplicate step name "${step.name}" in conditional branches of node "${nodeName}"`);
289
+ }
290
+ conditionStepNames.add(step.name);
291
+ }
292
+ if (!step.name || !step.node || !step.type) {
293
+ errors.push(`Step in condition ${i} of node "${nodeName}" must have name, node, and type fields`);
294
+ }
295
+ }
296
+ }
297
+ }
298
+ if (!hasElse) {
299
+ warnings.push(`Conditional node "${nodeName}" has no "else" branch - consider adding a fallback`);
300
+ }
301
+ }