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,647 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { validateWorkflow } from "./WorkflowValidator.js";
3
+ describe("WorkflowValidator", () => {
4
+ describe("validateWorkflow - JSON parsing", () => {
5
+ it("should fail for invalid JSON", () => {
6
+ const result = validateWorkflow("not valid json {{{");
7
+ expect(result.valid).toBe(false);
8
+ expect(result.errors.length).toBeGreaterThan(0);
9
+ expect(result.errors[0]).toContain("Invalid JSON");
10
+ });
11
+ it("should fail for empty string", () => {
12
+ const result = validateWorkflow("");
13
+ expect(result.valid).toBe(false);
14
+ expect(result.errors[0]).toContain("Invalid JSON");
15
+ });
16
+ });
17
+ describe("validateWorkflow - top-level structure", () => {
18
+ it("should pass for valid minimal workflow", () => {
19
+ const workflow = JSON.stringify({
20
+ name: "test-workflow",
21
+ description: "A test workflow",
22
+ version: "1.0.0",
23
+ trigger: { http: { method: "GET", path: "/" } },
24
+ steps: [{ name: "step1", node: "some-node", type: "module" }],
25
+ nodes: { step1: { inputs: {} } },
26
+ });
27
+ const result = validateWorkflow(workflow);
28
+ expect(result.valid).toBe(true);
29
+ expect(result.errors.length).toBe(0);
30
+ });
31
+ it("should fail for missing name", () => {
32
+ const workflow = JSON.stringify({
33
+ description: "test",
34
+ version: "1.0.0",
35
+ trigger: { http: {} },
36
+ steps: [{ name: "s", node: "n", type: "module" }],
37
+ nodes: { s: { inputs: {} } },
38
+ });
39
+ const result = validateWorkflow(workflow);
40
+ expect(result.valid).toBe(false);
41
+ expect(result.errors.some((e) => e.includes("name"))).toBe(true);
42
+ });
43
+ it("should fail for missing version", () => {
44
+ const workflow = JSON.stringify({
45
+ name: "test",
46
+ description: "test",
47
+ trigger: { http: {} },
48
+ steps: [{ name: "s", node: "n", type: "module" }],
49
+ nodes: { s: { inputs: {} } },
50
+ });
51
+ const result = validateWorkflow(workflow);
52
+ expect(result.valid).toBe(false);
53
+ expect(result.errors.some((e) => e.includes("version"))).toBe(true);
54
+ });
55
+ it("should warn for non-semver version", () => {
56
+ const workflow = JSON.stringify({
57
+ name: "test",
58
+ description: "test",
59
+ version: "latest",
60
+ trigger: { http: { method: "GET" } },
61
+ steps: [{ name: "s", node: "n", type: "module" }],
62
+ nodes: { s: { inputs: {} } },
63
+ });
64
+ const result = validateWorkflow(workflow);
65
+ expect(result.warnings.some((w) => w.includes("semver"))).toBe(true);
66
+ });
67
+ it("should fail for missing trigger", () => {
68
+ const workflow = JSON.stringify({
69
+ name: "test",
70
+ description: "test",
71
+ version: "1.0.0",
72
+ steps: [{ name: "s", node: "n", type: "module" }],
73
+ nodes: { s: { inputs: {} } },
74
+ });
75
+ const result = validateWorkflow(workflow);
76
+ expect(result.valid).toBe(false);
77
+ expect(result.errors.some((e) => e.includes("trigger"))).toBe(true);
78
+ });
79
+ it("should fail for missing steps", () => {
80
+ const workflow = JSON.stringify({
81
+ name: "test",
82
+ description: "test",
83
+ version: "1.0.0",
84
+ trigger: { http: {} },
85
+ nodes: {},
86
+ });
87
+ const result = validateWorkflow(workflow);
88
+ expect(result.valid).toBe(false);
89
+ expect(result.errors.some((e) => e.includes("steps"))).toBe(true);
90
+ });
91
+ it("should fail for empty steps array", () => {
92
+ const workflow = JSON.stringify({
93
+ name: "test",
94
+ description: "test",
95
+ version: "1.0.0",
96
+ trigger: { http: {} },
97
+ steps: [],
98
+ nodes: {},
99
+ });
100
+ const result = validateWorkflow(workflow);
101
+ expect(result.valid).toBe(false);
102
+ expect(result.errors.some((e) => e.includes("steps"))).toBe(true);
103
+ });
104
+ it("should fail for missing nodes", () => {
105
+ const workflow = JSON.stringify({
106
+ name: "test",
107
+ description: "test",
108
+ version: "1.0.0",
109
+ trigger: { http: {} },
110
+ steps: [{ name: "s", node: "n", type: "module" }],
111
+ });
112
+ const result = validateWorkflow(workflow);
113
+ expect(result.valid).toBe(false);
114
+ expect(result.errors.some((e) => e.includes("nodes"))).toBe(true);
115
+ });
116
+ });
117
+ describe("validateWorkflow - trigger validation", () => {
118
+ it("should fail for multiple trigger types", () => {
119
+ const workflow = JSON.stringify({
120
+ name: "test",
121
+ description: "test",
122
+ version: "1.0.0",
123
+ trigger: { http: {}, cron: {} },
124
+ steps: [{ name: "s", node: "n", type: "module" }],
125
+ nodes: { s: { inputs: {} } },
126
+ });
127
+ const result = validateWorkflow(workflow);
128
+ expect(result.valid).toBe(false);
129
+ expect(result.errors.some((e) => e.includes("exactly one"))).toBe(true);
130
+ });
131
+ it("should fail for invalid trigger type", () => {
132
+ const workflow = JSON.stringify({
133
+ name: "test",
134
+ description: "test",
135
+ version: "1.0.0",
136
+ trigger: { ftp: {} },
137
+ steps: [{ name: "s", node: "n", type: "module" }],
138
+ nodes: { s: { inputs: {} } },
139
+ });
140
+ const result = validateWorkflow(workflow);
141
+ expect(result.valid).toBe(false);
142
+ expect(result.errors.some((e) => e.includes("Invalid trigger type"))).toBe(true);
143
+ });
144
+ it("should pass for valid http trigger", () => {
145
+ const workflow = JSON.stringify({
146
+ name: "test",
147
+ description: "test",
148
+ version: "1.0.0",
149
+ trigger: { http: { method: "GET", path: "/api/test", accept: "application/json" } },
150
+ steps: [{ name: "s", node: "n", type: "module" }],
151
+ nodes: { s: { inputs: {} } },
152
+ });
153
+ const result = validateWorkflow(workflow);
154
+ expect(result.valid).toBe(true);
155
+ });
156
+ it("should fail for invalid HTTP method", () => {
157
+ const workflow = JSON.stringify({
158
+ name: "test",
159
+ description: "test",
160
+ version: "1.0.0",
161
+ trigger: { http: { method: "FETCH" } },
162
+ steps: [{ name: "s", node: "n", type: "module" }],
163
+ nodes: { s: { inputs: {} } },
164
+ });
165
+ const result = validateWorkflow(workflow);
166
+ expect(result.valid).toBe(false);
167
+ expect(result.errors.some((e) => e.includes("Invalid HTTP method"))).toBe(true);
168
+ });
169
+ it("should pass for valid queue trigger", () => {
170
+ const workflow = JSON.stringify({
171
+ name: "test",
172
+ description: "test",
173
+ version: "1.0.0",
174
+ trigger: { queue: { provider: "kafka", topic: "events" } },
175
+ steps: [{ name: "s", node: "n", type: "module" }],
176
+ nodes: { s: { inputs: {} } },
177
+ });
178
+ const result = validateWorkflow(workflow);
179
+ expect(result.valid).toBe(true);
180
+ });
181
+ it("should fail for queue trigger missing provider", () => {
182
+ const workflow = JSON.stringify({
183
+ name: "test",
184
+ description: "test",
185
+ version: "1.0.0",
186
+ trigger: { queue: { topic: "events" } },
187
+ steps: [{ name: "s", node: "n", type: "module" }],
188
+ nodes: { s: { inputs: {} } },
189
+ });
190
+ const result = validateWorkflow(workflow);
191
+ expect(result.valid).toBe(false);
192
+ expect(result.errors.some((e) => e.includes("provider"))).toBe(true);
193
+ });
194
+ it("should fail for queue trigger with invalid provider", () => {
195
+ const workflow = JSON.stringify({
196
+ name: "test",
197
+ description: "test",
198
+ version: "1.0.0",
199
+ trigger: { queue: { provider: "oracle", topic: "events" } },
200
+ steps: [{ name: "s", node: "n", type: "module" }],
201
+ nodes: { s: { inputs: {} } },
202
+ });
203
+ const result = validateWorkflow(workflow);
204
+ expect(result.valid).toBe(false);
205
+ expect(result.errors.some((e) => e.includes("Invalid queue provider"))).toBe(true);
206
+ });
207
+ it("should pass for valid cron trigger", () => {
208
+ const workflow = JSON.stringify({
209
+ name: "test",
210
+ description: "test",
211
+ version: "1.0.0",
212
+ trigger: { cron: { schedule: "0 * * * *", timezone: "UTC" } },
213
+ steps: [{ name: "s", node: "n", type: "module" }],
214
+ nodes: { s: { inputs: {} } },
215
+ });
216
+ const result = validateWorkflow(workflow);
217
+ expect(result.valid).toBe(true);
218
+ });
219
+ it("should fail for invalid cron expression", () => {
220
+ const workflow = JSON.stringify({
221
+ name: "test",
222
+ description: "test",
223
+ version: "1.0.0",
224
+ trigger: { cron: { schedule: "invalid cron" } },
225
+ steps: [{ name: "s", node: "n", type: "module" }],
226
+ nodes: { s: { inputs: {} } },
227
+ });
228
+ const result = validateWorkflow(workflow);
229
+ expect(result.valid).toBe(false);
230
+ expect(result.errors.some((e) => e.includes("Invalid cron expression"))).toBe(true);
231
+ });
232
+ it("should pass for valid pubsub trigger", () => {
233
+ const workflow = JSON.stringify({
234
+ name: "test",
235
+ description: "test",
236
+ version: "1.0.0",
237
+ trigger: { pubsub: { provider: "gcp", topic: "events", subscription: "sub1" } },
238
+ steps: [{ name: "s", node: "n", type: "module" }],
239
+ nodes: { s: { inputs: {} } },
240
+ });
241
+ const result = validateWorkflow(workflow);
242
+ expect(result.valid).toBe(true);
243
+ });
244
+ it("should fail for pubsub missing subscription", () => {
245
+ const workflow = JSON.stringify({
246
+ name: "test",
247
+ description: "test",
248
+ version: "1.0.0",
249
+ trigger: { pubsub: { provider: "gcp", topic: "events" } },
250
+ steps: [{ name: "s", node: "n", type: "module" }],
251
+ nodes: { s: { inputs: {} } },
252
+ });
253
+ const result = validateWorkflow(workflow);
254
+ expect(result.valid).toBe(false);
255
+ expect(result.errors.some((e) => e.includes("subscription"))).toBe(true);
256
+ });
257
+ it("should pass for valid webhook trigger", () => {
258
+ const workflow = JSON.stringify({
259
+ name: "test",
260
+ description: "test",
261
+ version: "1.0.0",
262
+ trigger: { webhook: { source: "github", events: ["push"] } },
263
+ steps: [{ name: "s", node: "n", type: "module" }],
264
+ nodes: { s: { inputs: {} } },
265
+ });
266
+ const result = validateWorkflow(workflow);
267
+ expect(result.valid).toBe(true);
268
+ });
269
+ it("should fail for webhook trigger with empty events", () => {
270
+ const workflow = JSON.stringify({
271
+ name: "test",
272
+ description: "test",
273
+ version: "1.0.0",
274
+ trigger: { webhook: { source: "github", events: [] } },
275
+ steps: [{ name: "s", node: "n", type: "module" }],
276
+ nodes: { s: { inputs: {} } },
277
+ });
278
+ const result = validateWorkflow(workflow);
279
+ expect(result.valid).toBe(false);
280
+ expect(result.errors.some((e) => e.includes("events"))).toBe(true);
281
+ });
282
+ it("should pass for valid websocket trigger", () => {
283
+ const workflow = JSON.stringify({
284
+ name: "test",
285
+ description: "test",
286
+ version: "1.0.0",
287
+ trigger: { websocket: { events: ["message"], path: "/ws" } },
288
+ steps: [{ name: "s", node: "n", type: "module" }],
289
+ nodes: { s: { inputs: {} } },
290
+ });
291
+ const result = validateWorkflow(workflow);
292
+ expect(result.valid).toBe(true);
293
+ });
294
+ it("should pass for valid sse trigger", () => {
295
+ const workflow = JSON.stringify({
296
+ name: "test",
297
+ description: "test",
298
+ version: "1.0.0",
299
+ trigger: { sse: { events: ["update"], channels: ["feed"] } },
300
+ steps: [{ name: "s", node: "n", type: "module" }],
301
+ nodes: { s: { inputs: {} } },
302
+ });
303
+ const result = validateWorkflow(workflow);
304
+ expect(result.valid).toBe(true);
305
+ });
306
+ });
307
+ describe("validateWorkflow - steps validation", () => {
308
+ it("should fail for step missing name", () => {
309
+ const workflow = JSON.stringify({
310
+ name: "test",
311
+ description: "test",
312
+ version: "1.0.0",
313
+ trigger: { http: { method: "GET" } },
314
+ steps: [{ node: "n", type: "module" }],
315
+ nodes: {},
316
+ });
317
+ const result = validateWorkflow(workflow);
318
+ expect(result.valid).toBe(false);
319
+ expect(result.errors.some((e) => e.includes("name"))).toBe(true);
320
+ });
321
+ it("should fail for step missing node", () => {
322
+ const workflow = JSON.stringify({
323
+ name: "test",
324
+ description: "test",
325
+ version: "1.0.0",
326
+ trigger: { http: { method: "GET" } },
327
+ steps: [{ name: "s", type: "module" }],
328
+ nodes: { s: { inputs: {} } },
329
+ });
330
+ const result = validateWorkflow(workflow);
331
+ expect(result.valid).toBe(false);
332
+ expect(result.errors.some((e) => e.includes("node"))).toBe(true);
333
+ });
334
+ it("should fail for duplicate step names", () => {
335
+ const workflow = JSON.stringify({
336
+ name: "test",
337
+ description: "test",
338
+ version: "1.0.0",
339
+ trigger: { http: { method: "GET" } },
340
+ steps: [
341
+ { name: "s1", node: "n1", type: "module" },
342
+ { name: "s1", node: "n2", type: "module" },
343
+ ],
344
+ nodes: { s1: { inputs: {} } },
345
+ });
346
+ const result = validateWorkflow(workflow);
347
+ expect(result.valid).toBe(false);
348
+ expect(result.errors.some((e) => e.includes("Duplicate"))).toBe(true);
349
+ });
350
+ });
351
+ describe("validateWorkflow - nodes cross-reference", () => {
352
+ it("should fail when step has no matching node", () => {
353
+ const workflow = JSON.stringify({
354
+ name: "test",
355
+ description: "test",
356
+ version: "1.0.0",
357
+ trigger: { http: { method: "GET" } },
358
+ steps: [{ name: "missing-node", node: "n", type: "module" }],
359
+ nodes: { "other-node": { inputs: {} } },
360
+ });
361
+ const result = validateWorkflow(workflow);
362
+ expect(result.valid).toBe(false);
363
+ expect(result.errors.some((e) => e.includes("missing-node") && e.includes("no matching"))).toBe(true);
364
+ });
365
+ it("should pass when all steps have matching nodes", () => {
366
+ const workflow = JSON.stringify({
367
+ name: "test",
368
+ description: "test",
369
+ version: "1.0.0",
370
+ trigger: { http: { method: "GET" } },
371
+ steps: [
372
+ { name: "s1", node: "n1", type: "module" },
373
+ { name: "s2", node: "n2", type: "module" },
374
+ ],
375
+ nodes: {
376
+ s1: { inputs: {} },
377
+ s2: { inputs: { data: "test" } },
378
+ },
379
+ });
380
+ const result = validateWorkflow(workflow);
381
+ expect(result.valid).toBe(true);
382
+ });
383
+ });
384
+ describe("validateWorkflow - conditional nodes", () => {
385
+ it("should pass for valid conditional routing", () => {
386
+ const workflow = JSON.stringify({
387
+ name: "test",
388
+ description: "test",
389
+ version: "1.0.0",
390
+ trigger: { http: { method: "*", path: "/:action?" } },
391
+ steps: [{ name: "router", node: "@blok/if-else", type: "module" }],
392
+ nodes: {
393
+ router: {
394
+ conditions: [
395
+ {
396
+ type: "if",
397
+ condition: 'ctx.request.method.toLowerCase() === "get"',
398
+ steps: [{ name: "get-data", node: "fetch", type: "module" }],
399
+ },
400
+ {
401
+ type: "else",
402
+ steps: [{ name: "error", node: "error", type: "module" }],
403
+ },
404
+ ],
405
+ },
406
+ "get-data": { inputs: {} },
407
+ error: { inputs: { message: "Not allowed" } },
408
+ },
409
+ });
410
+ const result = validateWorkflow(workflow);
411
+ expect(result.valid).toBe(true);
412
+ });
413
+ it("should fail for condition missing condition string", () => {
414
+ const workflow = JSON.stringify({
415
+ name: "test",
416
+ description: "test",
417
+ version: "1.0.0",
418
+ trigger: { http: { method: "GET" } },
419
+ steps: [{ name: "router", node: "if-else", type: "module" }],
420
+ nodes: {
421
+ router: {
422
+ conditions: [
423
+ {
424
+ type: "if",
425
+ steps: [{ name: "s", node: "n", type: "module" }],
426
+ },
427
+ ],
428
+ },
429
+ s: { inputs: {} },
430
+ },
431
+ });
432
+ const result = validateWorkflow(workflow);
433
+ expect(result.valid).toBe(false);
434
+ expect(result.errors.some((e) => e.includes("condition"))).toBe(true);
435
+ });
436
+ it("should fail when conditional step has no matching node entry", () => {
437
+ const workflow = JSON.stringify({
438
+ name: "test",
439
+ description: "test",
440
+ version: "1.0.0",
441
+ trigger: { http: { method: "GET" } },
442
+ steps: [{ name: "router", node: "if-else", type: "module" }],
443
+ nodes: {
444
+ router: {
445
+ conditions: [
446
+ {
447
+ type: "if",
448
+ condition: "ctx.request.method === 'GET'",
449
+ steps: [{ name: "missing-step", node: "n", type: "module" }],
450
+ },
451
+ {
452
+ type: "else",
453
+ steps: [{ name: "fallback", node: "error", type: "module" }],
454
+ },
455
+ ],
456
+ },
457
+ },
458
+ });
459
+ const result = validateWorkflow(workflow);
460
+ expect(result.valid).toBe(false);
461
+ expect(result.errors.some((e) => e.includes("missing-step"))).toBe(true);
462
+ });
463
+ it("should fail when else is not last condition", () => {
464
+ const workflow = JSON.stringify({
465
+ name: "test",
466
+ description: "test",
467
+ version: "1.0.0",
468
+ trigger: { http: { method: "GET" } },
469
+ steps: [{ name: "router", node: "if-else", type: "module" }],
470
+ nodes: {
471
+ router: {
472
+ conditions: [
473
+ {
474
+ type: "else",
475
+ steps: [{ name: "s1", node: "n", type: "module" }],
476
+ },
477
+ {
478
+ type: "if",
479
+ condition: "ctx.request.method === 'GET'",
480
+ steps: [{ name: "s2", node: "n", type: "module" }],
481
+ },
482
+ ],
483
+ },
484
+ s1: { inputs: {} },
485
+ s2: { inputs: {} },
486
+ },
487
+ });
488
+ const result = validateWorkflow(workflow);
489
+ expect(result.valid).toBe(false);
490
+ expect(result.errors.some((e) => e.includes("else") && e.includes("last"))).toBe(true);
491
+ });
492
+ it("should warn when no else branch exists", () => {
493
+ const workflow = JSON.stringify({
494
+ name: "test",
495
+ description: "test",
496
+ version: "1.0.0",
497
+ trigger: { http: { method: "GET" } },
498
+ steps: [{ name: "router", node: "if-else", type: "module" }],
499
+ nodes: {
500
+ router: {
501
+ conditions: [
502
+ {
503
+ type: "if",
504
+ condition: "ctx.request.method === 'GET'",
505
+ steps: [{ name: "s1", node: "n", type: "module" }],
506
+ },
507
+ ],
508
+ },
509
+ s1: { inputs: {} },
510
+ },
511
+ });
512
+ const result = validateWorkflow(workflow);
513
+ expect(result.warnings.some((w) => w.includes("else"))).toBe(true);
514
+ });
515
+ it("should fail for condition with empty steps array", () => {
516
+ const workflow = JSON.stringify({
517
+ name: "test",
518
+ description: "test",
519
+ version: "1.0.0",
520
+ trigger: { http: { method: "GET" } },
521
+ steps: [{ name: "router", node: "if-else", type: "module" }],
522
+ nodes: {
523
+ router: {
524
+ conditions: [
525
+ {
526
+ type: "if",
527
+ condition: "ctx.request.method === 'GET'",
528
+ steps: [],
529
+ },
530
+ ],
531
+ },
532
+ },
533
+ });
534
+ const result = validateWorkflow(workflow);
535
+ expect(result.valid).toBe(false);
536
+ expect(result.errors.some((e) => e.includes("at least one step"))).toBe(true);
537
+ });
538
+ });
539
+ describe("validateWorkflow - real-world examples", () => {
540
+ it("should pass for countries API proxy workflow", () => {
541
+ const workflow = JSON.stringify({
542
+ name: "World Countries",
543
+ description: "Workflow description",
544
+ version: "1.0.0",
545
+ trigger: {
546
+ http: { method: "GET", path: "/", accept: "application/json" },
547
+ },
548
+ steps: [{ name: "get-countries-api", node: "@blok/api-call", type: "module" }],
549
+ nodes: {
550
+ "get-countries-api": {
551
+ inputs: {
552
+ url: "https://countriesnow.space/api/v0.1/countries/capital",
553
+ method: "GET",
554
+ headers: { "Content-Type": "application/json" },
555
+ responseType: "application/json",
556
+ },
557
+ },
558
+ },
559
+ });
560
+ const result = validateWorkflow(workflow);
561
+ expect(result.valid).toBe(true);
562
+ expect(result.errors.length).toBe(0);
563
+ });
564
+ it("should pass for CRUD workflow with conditional routing", () => {
565
+ const workflow = JSON.stringify({
566
+ name: "feedback",
567
+ description: "",
568
+ version: "1.0.0",
569
+ trigger: {
570
+ http: { method: "*", path: "/:function?/:id?", accept: "application/json" },
571
+ },
572
+ steps: [{ name: "filter-request", node: "@blok/if-else", type: "module" }],
573
+ nodes: {
574
+ "filter-request": {
575
+ conditions: [
576
+ {
577
+ type: "if",
578
+ steps: [{ name: "feedback-ui", node: "feedback-ui", type: "module" }],
579
+ condition: "ctx.request.method.toLowerCase() === \"get\" && ctx.request.params.function === ''",
580
+ },
581
+ {
582
+ type: "if",
583
+ steps: [
584
+ { name: "generate-sentiment", node: "generate-sentiment", type: "runtime.python3" },
585
+ { name: "save-feedback", node: "memory-storage", type: "module" },
586
+ ],
587
+ condition: 'ctx.request.method.toLowerCase() === "post" && ctx.request.params.function === "create"',
588
+ },
589
+ {
590
+ type: "else",
591
+ steps: [{ name: "method-not-allowed", node: "error", type: "module" }],
592
+ },
593
+ ],
594
+ },
595
+ "feedback-ui": { inputs: {} },
596
+ "generate-sentiment": { inputs: { comment: "${ctx.request.body.comment}" } },
597
+ "save-feedback": { inputs: { action: "set", key: "${ctx.request.body.id}" } },
598
+ "method-not-allowed": { inputs: { message: "Method not allowed" } },
599
+ },
600
+ });
601
+ const result = validateWorkflow(workflow);
602
+ expect(result.valid).toBe(true);
603
+ expect(result.errors.length).toBe(0);
604
+ });
605
+ it("should pass for queue-triggered workflow", () => {
606
+ const workflow = JSON.stringify({
607
+ name: "Event Processor",
608
+ description: "Processes events from Kafka",
609
+ version: "1.0.0",
610
+ trigger: {
611
+ queue: {
612
+ provider: "kafka",
613
+ topic: "user-events",
614
+ consumerGroup: "event-processor",
615
+ ack: true,
616
+ },
617
+ },
618
+ steps: [{ name: "process-event", node: "event-handler", type: "module" }],
619
+ nodes: {
620
+ "process-event": {
621
+ inputs: { eventType: "${ctx.request.body.type}" },
622
+ },
623
+ },
624
+ });
625
+ const result = validateWorkflow(workflow);
626
+ expect(result.valid).toBe(true);
627
+ });
628
+ it("should pass for cron-triggered workflow", () => {
629
+ const workflow = JSON.stringify({
630
+ name: "Daily Report",
631
+ description: "Generates daily reports",
632
+ version: "1.0.0",
633
+ trigger: {
634
+ cron: { schedule: "0 8 * * *", timezone: "America/New_York", overlap: false },
635
+ },
636
+ steps: [{ name: "fetch-metrics", node: "@blok/api-call", type: "module" }],
637
+ nodes: {
638
+ "fetch-metrics": {
639
+ inputs: { url: "${ctx.env.METRICS_API_URL}", method: "GET" },
640
+ },
641
+ },
642
+ });
643
+ const result = validateWorkflow(workflow);
644
+ expect(result.valid).toBe(true);
645
+ });
646
+ });
647
+ });
@@ -0,0 +1,4 @@
1
+ export * as NodeValidator from "./NodeValidator.js";
2
+ export * as CompilationValidator from "./CompilationValidator.js";
3
+ export type { ValidationResult, NodeValidationContext } from "./NodeValidator.js";
4
+ export type { CompilationResult } from "./CompilationValidator.js";
@@ -0,0 +1,2 @@
1
+ export * as NodeValidator from "./NodeValidator.js";
2
+ export * as CompilationValidator from "./CompilationValidator.js";
@@ -0,0 +1 @@
1
+ export {};