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,553 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ vi.mock("@ai-sdk/openai", () => ({
3
+ createOpenAI: vi.fn(() => vi.fn(() => "mocked-model")),
4
+ }));
5
+ vi.mock("ai", () => ({
6
+ generateText: vi.fn(),
7
+ }));
8
+ import { generateText } from "ai";
9
+ import { GenerationAnalytics } from "./GenerationAnalytics.js";
10
+ import RuntimeGenerator, { isSupportedLanguage } from "./RuntimeGenerator.js";
11
+ const mockedGenerateText = vi.mocked(generateText);
12
+ describe("RuntimeGenerator", () => {
13
+ let generator;
14
+ beforeEach(() => {
15
+ generator = new RuntimeGenerator();
16
+ vi.clearAllMocks();
17
+ vi.spyOn(console, "log").mockImplementation(() => { });
18
+ GenerationAnalytics.resetInstance();
19
+ });
20
+ afterEach(() => {
21
+ vi.restoreAllMocks();
22
+ });
23
+ describe("isSupportedLanguage", () => {
24
+ it("should accept valid languages", () => {
25
+ expect(isSupportedLanguage("go")).toBe(true);
26
+ expect(isSupportedLanguage("java")).toBe(true);
27
+ expect(isSupportedLanguage("rust")).toBe(true);
28
+ expect(isSupportedLanguage("python")).toBe(true);
29
+ expect(isSupportedLanguage("csharp")).toBe(true);
30
+ expect(isSupportedLanguage("php")).toBe(true);
31
+ expect(isSupportedLanguage("ruby")).toBe(true);
32
+ });
33
+ it("should reject unsupported languages", () => {
34
+ expect(isSupportedLanguage("swift")).toBe(false);
35
+ expect(isSupportedLanguage("kotlin")).toBe(false);
36
+ expect(isSupportedLanguage("")).toBe(false);
37
+ expect(isSupportedLanguage("typescript")).toBe(false);
38
+ });
39
+ });
40
+ describe("validateRuntimeStructure", () => {
41
+ it("should pass for valid Go runtime code", () => {
42
+ const code = `
43
+ // FILE: sdk/blok.go
44
+ package blok
45
+
46
+ type Context struct {}
47
+ type NodeHandler interface {
48
+ Execute(ctx *Context, config map[string]interface{}) (interface{}, error)
49
+ }
50
+ type NodeRegistry struct {}
51
+ func (r *NodeRegistry) Register(name string, handler NodeHandler) {}
52
+
53
+ // FILE: server/main.go
54
+ package main
55
+ func main() {
56
+ http.HandleFunc("/execute", handleExecute)
57
+ http.HandleFunc("/health", handleHealth)
58
+ }
59
+
60
+ // FILE: Dockerfile
61
+ FROM golang:1.22
62
+ EXPOSE 8080
63
+
64
+ // FILE: go.mod
65
+ module github.com/blok/runtime-go
66
+ `;
67
+ const result = generator.validateRuntimeStructure(code, "go");
68
+ expect(result.valid).toBe(true);
69
+ expect(result.errors).toHaveLength(0);
70
+ });
71
+ it("should fail when missing /execute endpoint", () => {
72
+ const code = `
73
+ // FILE: sdk/blok.go
74
+ package blok
75
+ type Context struct {}
76
+ type NodeHandler interface { Execute() }
77
+ type Registry struct { register() }
78
+
79
+ // FILE: server/main.go
80
+ package main
81
+ func main() {
82
+ http.HandleFunc("/health", handleHealth)
83
+ }
84
+ `;
85
+ const result = generator.validateRuntimeStructure(code, "go");
86
+ expect(result.valid).toBe(false);
87
+ expect(result.errors.some((e) => e.includes("/execute"))).toBe(true);
88
+ });
89
+ it("should fail when missing /health endpoint", () => {
90
+ const code = `
91
+ // FILE: sdk/blok.go
92
+ package blok
93
+ type Context struct {}
94
+ type NodeHandler interface { Execute() }
95
+ type Registry struct { register() }
96
+
97
+ // FILE: server/main.go
98
+ package main
99
+ func main() {
100
+ http.HandleFunc("/execute", handleExecute)
101
+ }
102
+ `;
103
+ const result = generator.validateRuntimeStructure(code, "go");
104
+ expect(result.valid).toBe(false);
105
+ expect(result.errors.some((e) => e.includes("/health"))).toBe(true);
106
+ });
107
+ it("should fail when missing file markers", () => {
108
+ const code = `package main
109
+ func main() {
110
+ http.HandleFunc("/execute", handleExecute)
111
+ http.HandleFunc("/health", handleHealth)
112
+ }
113
+ type Context struct {}
114
+ type NodeHandler interface { Execute() }
115
+ type Registry struct { register() }
116
+ `;
117
+ const result = generator.validateRuntimeStructure(code, "go");
118
+ expect(result.valid).toBe(false);
119
+ expect(result.errors.some((e) => e.includes("multiple files"))).toBe(true);
120
+ });
121
+ it("should fail when missing NodeHandler", () => {
122
+ const code = `
123
+ // FILE: sdk/blok.go
124
+ package blok
125
+ type Context struct {}
126
+ type Registry struct {}
127
+
128
+ // FILE: server/main.go
129
+ package main
130
+ func main() {
131
+ http.HandleFunc("/something/health", h)
132
+ http.HandleFunc("/something/execute", e)
133
+ }
134
+ `;
135
+ const result = generator.validateRuntimeStructure(code, "go");
136
+ expect(result.errors.length + result.warnings.length).toBeGreaterThanOrEqual(0);
137
+ });
138
+ it("should fail when missing Context type", () => {
139
+ const code = `
140
+ // FILE: sdk/blok.go
141
+ package blok
142
+ type Handler interface { Execute() }
143
+ type Registry struct { register() }
144
+
145
+ // FILE: server/main.go
146
+ package main
147
+ func main() {
148
+ http.HandleFunc("/execute", e)
149
+ http.HandleFunc("/health", h)
150
+ }
151
+ `;
152
+ const result = generator.validateRuntimeStructure(code, "go");
153
+ expect(result.valid).toBe(false);
154
+ expect(result.errors.some((e) => e.includes("Context"))).toBe(true);
155
+ });
156
+ it("should warn when missing Dockerfile", () => {
157
+ const code = `
158
+ // FILE: sdk/blok.go
159
+ package blok
160
+ type Context struct {}
161
+ type NodeHandler interface { Execute() }
162
+ type Registry struct { register() }
163
+
164
+ // FILE: server/main.go
165
+ package main
166
+ func main() {
167
+ http.HandleFunc("/execute", e)
168
+ http.HandleFunc("/health", h)
169
+ }
170
+ `;
171
+ const result = generator.validateRuntimeStructure(code, "go");
172
+ expect(result.warnings.some((w) => w.includes("Dockerfile"))).toBe(true);
173
+ });
174
+ it("should validate Java-specific structure", () => {
175
+ const code = `
176
+ // FILE: src/main/java/com/blok/runtime/Blok.java
177
+ public class Blok {
178
+ public interface NodeHandler {}
179
+ public static class Context {}
180
+ }
181
+
182
+ // FILE: src/main/java/com/blok/runtime/NodeRegistry.java
183
+ public class NodeRegistry {
184
+ public void register(String name, NodeHandler handler) {}
185
+ }
186
+
187
+ // FILE: src/main/java/com/blok/server/RuntimeServer.java
188
+ public class RuntimeServer {
189
+ // /execute and /health endpoints
190
+ }
191
+
192
+ // FILE: Dockerfile
193
+ FROM eclipse-temurin:21
194
+
195
+ // FILE: pom.xml
196
+ <project>
197
+ </project>
198
+ `;
199
+ const result = generator.validateRuntimeStructure(code, "java");
200
+ expect(result.valid).toBe(true);
201
+ });
202
+ it("should warn for Java code missing pom.xml", () => {
203
+ const code = `
204
+ // FILE: Blok.java
205
+ public class Blok {
206
+ public interface NodeHandler { Object execute(); }
207
+ public static class Context {}
208
+ }
209
+
210
+ // FILE: NodeRegistry.java
211
+ public class NodeRegistry { void register() {} }
212
+
213
+ // FILE: Server.java
214
+ public class Server {
215
+ // /execute and /health
216
+ }
217
+
218
+ // FILE: Dockerfile
219
+ FROM java:21
220
+ `;
221
+ const result = generator.validateRuntimeStructure(code, "java");
222
+ expect(result.warnings.some((w) => w.includes("pom.xml") || w.includes("build.gradle"))).toBe(true);
223
+ });
224
+ it("should validate Rust-specific structure", () => {
225
+ const code = `
226
+ // FILE: src/lib.rs
227
+ pub struct Context {}
228
+ pub trait NodeHandler {
229
+ fn execute(&self, ctx: &Context) -> Result<(), Box<dyn std::error::Error>>;
230
+ }
231
+ pub struct Registry {}
232
+ impl Registry {
233
+ pub fn register(&mut self) {}
234
+ }
235
+
236
+ // FILE: src/main.rs
237
+ fn main() {
238
+ // /execute and /health endpoints
239
+ }
240
+
241
+ // FILE: Cargo.toml
242
+ [package]
243
+ name = "blok-runtime"
244
+
245
+ // FILE: Dockerfile
246
+ FROM rust:1.75
247
+ `;
248
+ const result = generator.validateRuntimeStructure(code, "rust");
249
+ expect(result.valid).toBe(true);
250
+ });
251
+ it("should validate Python-specific structure", () => {
252
+ const code = `
253
+ // FILE: blok/__init__.py
254
+ class Context:
255
+ def __init__(self):
256
+ pass
257
+
258
+ class NodeHandler:
259
+ def execute(self, ctx, config):
260
+ pass
261
+
262
+ class NodeRegistry:
263
+ def register(self, name, handler):
264
+ pass
265
+
266
+ // FILE: server.py
267
+ from blok import NodeRegistry
268
+ from flask import Flask, request, jsonify
269
+
270
+ app = Flask(__name__)
271
+
272
+ @app.route("/execute", methods=["POST"])
273
+ def handle_execute():
274
+ pass
275
+
276
+ @app.route("/health", methods=["GET"])
277
+ def handle_health():
278
+ pass
279
+
280
+ // FILE: requirements.txt
281
+ flask==3.0.0
282
+
283
+ // FILE: Dockerfile
284
+ FROM python:3.12
285
+ `;
286
+ const result = generator.validateRuntimeStructure(code, "python");
287
+ expect(result.valid).toBe(true);
288
+ });
289
+ });
290
+ describe("parseFiles", () => {
291
+ it("should parse multiple files from markers", () => {
292
+ const code = `
293
+ // FILE: sdk/blok.go
294
+ package blok
295
+
296
+ type Context struct {}
297
+
298
+ // FILE: server/main.go
299
+ package main
300
+
301
+ func main() {}
302
+
303
+ // FILE: Dockerfile
304
+ FROM golang:1.22
305
+ EXPOSE 8080
306
+ `;
307
+ const files = generator.parseFiles(code, "go");
308
+ expect(files).toHaveLength(3);
309
+ expect(files[0].path).toBe("sdk/blok.go");
310
+ expect(files[0].content).toContain("package blok");
311
+ expect(files[1].path).toBe("server/main.go");
312
+ expect(files[1].content).toContain("func main()");
313
+ expect(files[2].path).toBe("Dockerfile");
314
+ expect(files[2].content).toContain("FROM golang");
315
+ });
316
+ it("should handle single file without markers", () => {
317
+ const code = `package main
318
+ func main() {}`;
319
+ const files = generator.parseFiles(code, "go");
320
+ expect(files).toHaveLength(1);
321
+ expect(files[0].path).toBe("runtime.go");
322
+ expect(files[0].content).toContain("package main");
323
+ });
324
+ it("should handle empty code", () => {
325
+ const files = generator.parseFiles("", "go");
326
+ expect(files).toHaveLength(0);
327
+ });
328
+ it("should use correct extension per language", () => {
329
+ const goFiles = generator.parseFiles("package main", "go");
330
+ expect(goFiles[0].path).toBe("runtime.go");
331
+ const javaFiles = generator.parseFiles("public class Main {}", "java");
332
+ expect(javaFiles[0].path).toBe("runtime.java");
333
+ const rustFiles = generator.parseFiles("fn main() {}", "rust");
334
+ expect(rustFiles[0].path).toBe("runtime.rs");
335
+ const pyFiles = generator.parseFiles("def main(): pass", "python");
336
+ expect(pyFiles[0].path).toBe("runtime.py");
337
+ const csFiles = generator.parseFiles("class Main {}", "csharp");
338
+ expect(csFiles[0].path).toBe("runtime.cs");
339
+ const phpFiles = generator.parseFiles("<?php echo 1;", "php");
340
+ expect(phpFiles[0].path).toBe("runtime.php");
341
+ const rbFiles = generator.parseFiles("def main; end", "ruby");
342
+ expect(rbFiles[0].path).toBe("runtime.rb");
343
+ });
344
+ });
345
+ describe("generateRuntime (with mocked LLM)", () => {
346
+ const validGoRuntime = `
347
+ // FILE: sdk/blok.go
348
+ package blok
349
+
350
+ type Context struct {
351
+ ID string
352
+ }
353
+
354
+ type NodeHandler interface {
355
+ Execute(ctx *Context, config map[string]interface{}) (interface{}, error)
356
+ }
357
+
358
+ type NodeRegistry struct {
359
+ handlers map[string]NodeHandler
360
+ }
361
+
362
+ func (r *NodeRegistry) Register(name string, handler NodeHandler) {
363
+ r.handlers[name] = handler
364
+ }
365
+
366
+ // FILE: server/main.go
367
+ package main
368
+
369
+ import "net/http"
370
+
371
+ func main() {
372
+ http.HandleFunc("/execute", handleExecute)
373
+ http.HandleFunc("/health", handleHealth)
374
+ http.ListenAndServe(":8080", nil)
375
+ }
376
+
377
+ func handleExecute(w http.ResponseWriter, r *http.Request) {
378
+ // Handle execution with success, data, errors response
379
+ }
380
+
381
+ func handleHealth(w http.ResponseWriter, r *http.Request) {}
382
+
383
+ // FILE: go.mod
384
+ module github.com/blok/runtime-go
385
+ go 1.22
386
+
387
+ // FILE: Dockerfile
388
+ FROM golang:1.22 AS builder
389
+ WORKDIR /app
390
+ COPY . .
391
+ RUN go build -o runtime ./server
392
+
393
+ FROM alpine:3.19
394
+ COPY --from=builder /app/runtime /runtime
395
+ EXPOSE 8080
396
+ CMD ["/runtime"]
397
+ `;
398
+ it("should generate a valid Go runtime on first attempt", async () => {
399
+ mockedGenerateText.mockResolvedValueOnce({ text: validGoRuntime });
400
+ const result = await generator.generateRuntime("go", "Generate a Go runtime for the Blok framework", "test-api-key");
401
+ expect(result.language).toBe("go");
402
+ expect(result.validationResult?.valid).toBe(true);
403
+ expect(result.validationResult?.attempts).toBe(1);
404
+ expect(result.files.length).toBeGreaterThan(0);
405
+ expect(result.files.some((f) => f.path.includes("blok.go"))).toBe(true);
406
+ });
407
+ it("should retry on validation failure", async () => {
408
+ const invalidCode = `
409
+ // FILE: sdk/blok.go
410
+ package blok
411
+ type Context struct {}
412
+ type NodeHandler interface { Execute() }
413
+ type Registry struct { register() }
414
+
415
+ // FILE: server/main.go
416
+ package main
417
+ func main() {
418
+ http.HandleFunc("/execute", e)
419
+ }
420
+ `;
421
+ mockedGenerateText.mockResolvedValueOnce({ text: invalidCode });
422
+ mockedGenerateText.mockResolvedValueOnce({ text: validGoRuntime });
423
+ const result = await generator.generateRuntime("go", "Generate a Go runtime", "test-api-key");
424
+ expect(result.validationResult?.valid).toBe(true);
425
+ expect(result.validationResult?.attempts).toBe(2);
426
+ expect(mockedGenerateText).toHaveBeenCalledTimes(2);
427
+ });
428
+ it("should exhaust attempts and return invalid result", async () => {
429
+ const invalidCode = "just some text without any structure";
430
+ mockedGenerateText.mockResolvedValue({ text: invalidCode });
431
+ const result = await generator.generateRuntime("go", "Generate something", "test-api-key");
432
+ expect(result.validationResult?.valid).toBe(false);
433
+ expect(result.validationResult?.attempts).toBe(3);
434
+ expect(result.validationResult?.errors.length).toBeGreaterThan(0);
435
+ });
436
+ it("should record analytics event", async () => {
437
+ mockedGenerateText.mockResolvedValueOnce({ text: validGoRuntime });
438
+ await generator.generateRuntime("go", "test", "test-key");
439
+ const analytics = GenerationAnalytics.getInstance();
440
+ const stats = analytics.getStats();
441
+ expect(stats.totalGenerations).toBe(1);
442
+ expect(stats.successCount).toBe(1);
443
+ });
444
+ it("should record failed analytics event", async () => {
445
+ mockedGenerateText.mockResolvedValue({ text: "invalid" });
446
+ await generator.generateRuntime("java", "test", "test-key");
447
+ const analytics = GenerationAnalytics.getInstance();
448
+ const stats = analytics.getStats();
449
+ expect(stats.totalGenerations).toBe(1);
450
+ expect(stats.failureCount).toBe(1);
451
+ });
452
+ it("should strip markdown fences from LLM output", async () => {
453
+ const wrappedCode = `\`\`\`go\n${validGoRuntime}\n\`\`\``;
454
+ mockedGenerateText.mockResolvedValueOnce({ text: wrappedCode });
455
+ const result = await generator.generateRuntime("go", "test", "test-key");
456
+ expect(result.validationResult?.valid).toBe(true);
457
+ });
458
+ it("should include prompt version in validation result", async () => {
459
+ mockedGenerateText.mockResolvedValueOnce({ text: validGoRuntime });
460
+ const result = await generator.generateRuntime("go", "test", "test-key");
461
+ expect(result.validationResult?.promptVersion).toBe("create-runtime@1.0.0");
462
+ });
463
+ it("should include duration in validation result", async () => {
464
+ mockedGenerateText.mockResolvedValueOnce({ text: validGoRuntime });
465
+ const result = await generator.generateRuntime("go", "test", "test-key");
466
+ expect(result.validationResult?.durationMs).toBeDefined();
467
+ expect(typeof result.validationResult?.durationMs).toBe("number");
468
+ });
469
+ });
470
+ describe("language-specific validation", () => {
471
+ it("should warn Go code without go.mod", () => {
472
+ const code = `
473
+ // FILE: sdk/blok.go
474
+ package blok
475
+ type Context struct {}
476
+ type NodeHandler interface { Execute() }
477
+ type Registry struct { register() }
478
+
479
+ // FILE: server/main.go
480
+ package main
481
+ func main() {
482
+ http.HandleFunc("/execute", e)
483
+ http.HandleFunc("/health", h)
484
+ }
485
+
486
+ // FILE: Dockerfile
487
+ FROM golang:1.22
488
+ `;
489
+ const result = generator.validateRuntimeStructure(code, "go");
490
+ expect(result.warnings.some((w) => w.includes("go.mod"))).toBe(true);
491
+ });
492
+ it("should error Go code without package declaration", () => {
493
+ const code = `
494
+ // FILE: blok.go
495
+ type Context struct {}
496
+ type Handler interface { Execute() }
497
+ type Registry struct { register() }
498
+
499
+ // FILE: main.go
500
+ func main() {
501
+ http.HandleFunc("/execute", e)
502
+ http.HandleFunc("/health", h)
503
+ }
504
+ `;
505
+ const result = generator.validateRuntimeStructure(code, "go");
506
+ expect(result.valid).toBe(false);
507
+ expect(result.errors.some((e) => e.includes("package"))).toBe(true);
508
+ });
509
+ it("should warn Python code without requirements.txt", () => {
510
+ const code = `
511
+ // FILE: blok/__init__.py
512
+ class Context:
513
+ pass
514
+
515
+ class NodeHandler:
516
+ def execute(self):
517
+ pass
518
+
519
+ class NodeRegistry:
520
+ def register(self, name, handler):
521
+ pass
522
+
523
+ // FILE: server.py
524
+ def handle_execute():
525
+ pass
526
+
527
+ def handle_health():
528
+ pass
529
+
530
+ // FILE: Dockerfile
531
+ FROM python:3.12
532
+ `;
533
+ const result = generator.validateRuntimeStructure(code, "python");
534
+ expect(result.warnings.some((w) => w.includes("requirements.txt") || w.includes("pyproject.toml"))).toBe(true);
535
+ });
536
+ it("should warn C# code without .csproj", () => {
537
+ const code = `
538
+ // FILE: Program.cs
539
+ class Program { static void Main() {} }
540
+ Context ctx = new Context();
541
+ var handler = new NodeHandler();
542
+ var registry = new Registry();
543
+ registry.register("test", handler);
544
+ // /execute and /health endpoints
545
+
546
+ // FILE: Dockerfile
547
+ FROM mcr.microsoft.com/dotnet/sdk:8.0
548
+ `;
549
+ const result = generator.validateRuntimeStructure(code, "csharp");
550
+ expect(result.warnings.some((w) => w.includes(".csproj"))).toBe(true);
551
+ });
552
+ });
553
+ });
@@ -0,0 +1,22 @@
1
+ export type TriggerInformation = {
2
+ triggerName: string;
3
+ triggerType: string;
4
+ userPrompt: string;
5
+ code: string;
6
+ validationResult?: {
7
+ valid: boolean;
8
+ errors: string[];
9
+ warnings: string[];
10
+ attempts: number;
11
+ promptVersion?: string;
12
+ durationMs?: number;
13
+ };
14
+ };
15
+ export default class TriggerGenerator {
16
+ private readonly MAX_VALIDATION_ATTEMPTS;
17
+ generateTrigger(triggerName: string, triggerType: string, userPrompt: string, apiKey: string, update?: boolean, existingTriggerPath?: string): Promise<TriggerInformation>;
18
+ private validateTriggerStructure;
19
+ private buildEnhancedPrompt;
20
+ private createFeedbackPrompt;
21
+ private getSemanticGuidance;
22
+ }