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.
- package/dist/commands/build/index.d.ts +2 -0
- package/dist/commands/build/index.js +210 -0
- package/dist/commands/config/index.d.ts +1 -0
- package/dist/commands/config/index.js +46 -0
- package/dist/commands/cost/index.d.ts +1 -0
- package/dist/commands/cost/index.js +74 -0
- package/dist/commands/create/node.d.ts +2 -0
- package/dist/commands/create/node.js +541 -0
- package/dist/commands/create/project.d.ts +2 -0
- package/dist/commands/create/project.js +941 -0
- package/dist/commands/create/utils/Examples.d.ts +39 -0
- package/dist/commands/create/utils/Examples.js +983 -0
- package/dist/commands/create/workflow.d.ts +2 -0
- package/dist/commands/create/workflow.js +109 -0
- package/dist/commands/deploy/index.d.ts +2 -0
- package/dist/commands/deploy/index.js +176 -0
- package/dist/commands/dev/index.d.ts +2 -0
- package/dist/commands/dev/index.js +190 -0
- package/dist/commands/generate/GenerationAnalytics.d.ts +61 -0
- package/dist/commands/generate/GenerationAnalytics.js +162 -0
- package/dist/commands/generate/GenerationAnalytics.test.d.ts +1 -0
- package/dist/commands/generate/GenerationAnalytics.test.js +407 -0
- package/dist/commands/generate/NodeFileWriter.d.ts +5 -0
- package/dist/commands/generate/NodeFileWriter.js +240 -0
- package/dist/commands/generate/NodeGenerator.d.ts +20 -0
- package/dist/commands/generate/NodeGenerator.js +181 -0
- package/dist/commands/generate/NodeGenerator.test.d.ts +1 -0
- package/dist/commands/generate/NodeGenerator.test.js +101 -0
- package/dist/commands/generate/PromptVersioning.d.ts +25 -0
- package/dist/commands/generate/PromptVersioning.js +71 -0
- package/dist/commands/generate/PromptVersioning.test.d.ts +1 -0
- package/dist/commands/generate/PromptVersioning.test.js +120 -0
- package/dist/commands/generate/RegisterNode.d.ts +3 -0
- package/dist/commands/generate/RegisterNode.js +37 -0
- package/dist/commands/generate/RuntimeGenerator.d.ts +40 -0
- package/dist/commands/generate/RuntimeGenerator.js +369 -0
- package/dist/commands/generate/RuntimeGenerator.test.d.ts +1 -0
- package/dist/commands/generate/RuntimeGenerator.test.js +553 -0
- package/dist/commands/generate/TriggerGenerator.d.ts +22 -0
- package/dist/commands/generate/TriggerGenerator.js +220 -0
- package/dist/commands/generate/TriggerGenerator.test.d.ts +1 -0
- package/dist/commands/generate/TriggerGenerator.test.js +209 -0
- package/dist/commands/generate/WorkflowGenerator.d.ts +20 -0
- package/dist/commands/generate/WorkflowGenerator.js +131 -0
- package/dist/commands/generate/WorkflowGenerator.test.d.ts +1 -0
- package/dist/commands/generate/WorkflowGenerator.test.js +77 -0
- package/dist/commands/generate/e2e/NodeGenerator.e2e.test.d.ts +1 -0
- package/dist/commands/generate/e2e/NodeGenerator.e2e.test.js +216 -0
- package/dist/commands/generate/e2e/RuntimeGenerator.e2e.test.d.ts +1 -0
- package/dist/commands/generate/e2e/RuntimeGenerator.e2e.test.js +759 -0
- package/dist/commands/generate/e2e/TriggerGenerator.e2e.test.d.ts +1 -0
- package/dist/commands/generate/e2e/TriggerGenerator.e2e.test.js +295 -0
- package/dist/commands/generate/e2e/WorkflowGenerator.e2e.test.d.ts +1 -0
- package/dist/commands/generate/e2e/WorkflowGenerator.e2e.test.js +353 -0
- package/dist/commands/generate/index.d.ts +1 -0
- package/dist/commands/generate/index.js +418 -0
- package/dist/commands/generate/prompts/create-fn-node.system.d.ts +5 -0
- package/dist/commands/generate/prompts/create-fn-node.system.js +256 -0
- package/dist/commands/generate/prompts/create-node-manifest.system.d.ts +4 -0
- package/dist/commands/generate/prompts/create-node-manifest.system.js +41 -0
- package/dist/commands/generate/prompts/create-node.system.d.ts +5 -0
- package/dist/commands/generate/prompts/create-node.system.js +114 -0
- package/dist/commands/generate/prompts/create-readme.system.d.ts +4 -0
- package/dist/commands/generate/prompts/create-readme.system.js +83 -0
- package/dist/commands/generate/prompts/create-runtime.system.d.ts +5 -0
- package/dist/commands/generate/prompts/create-runtime.system.js +284 -0
- package/dist/commands/generate/prompts/create-trigger.system.d.ts +5 -0
- package/dist/commands/generate/prompts/create-trigger.system.js +293 -0
- package/dist/commands/generate/prompts/create-workflow.system.d.ts +5 -0
- package/dist/commands/generate/prompts/create-workflow.system.js +476 -0
- package/dist/commands/generate/prompts/register-node.system.d.ts +4 -0
- package/dist/commands/generate/prompts/register-node.system.js +26 -0
- package/dist/commands/generate/validators/CompilationValidator.d.ts +9 -0
- package/dist/commands/generate/validators/CompilationValidator.js +86 -0
- package/dist/commands/generate/validators/CompilationValidator.test.d.ts +1 -0
- package/dist/commands/generate/validators/CompilationValidator.test.js +161 -0
- package/dist/commands/generate/validators/NodeValidator.d.ts +18 -0
- package/dist/commands/generate/validators/NodeValidator.js +217 -0
- package/dist/commands/generate/validators/NodeValidator.test.d.ts +1 -0
- package/dist/commands/generate/validators/NodeValidator.test.js +281 -0
- package/dist/commands/generate/validators/WorkflowValidator.d.ts +6 -0
- package/dist/commands/generate/validators/WorkflowValidator.js +301 -0
- package/dist/commands/generate/validators/WorkflowValidator.test.d.ts +1 -0
- package/dist/commands/generate/validators/WorkflowValidator.test.js +647 -0
- package/dist/commands/generate/validators/index.d.ts +4 -0
- package/dist/commands/generate/validators/index.js +2 -0
- package/dist/commands/graph/index.d.ts +1 -0
- package/dist/commands/graph/index.js +69 -0
- package/dist/commands/install/index.d.ts +1 -0
- package/dist/commands/install/index.js +4 -0
- package/dist/commands/install/node.d.ts +4 -0
- package/dist/commands/install/node.js +136 -0
- package/dist/commands/install/workflow.d.ts +4 -0
- package/dist/commands/install/workflow.js +62 -0
- package/dist/commands/login/index.d.ts +2 -0
- package/dist/commands/login/index.js +77 -0
- package/dist/commands/logout/index.d.ts +2 -0
- package/dist/commands/logout/index.js +20 -0
- package/dist/commands/marketplace/runtime.d.ts +54 -0
- package/dist/commands/marketplace/runtime.js +350 -0
- package/dist/commands/migrate/index.d.ts +1 -0
- package/dist/commands/migrate/index.js +14 -0
- package/dist/commands/migrate/node.d.ts +2 -0
- package/dist/commands/migrate/node.js +110 -0
- package/dist/commands/monitor/index.d.ts +1 -0
- package/dist/commands/monitor/index.js +28 -0
- package/dist/commands/monitor/monitor-component.d.ts +1 -0
- package/dist/commands/monitor/monitor-component.js +271 -0
- package/dist/commands/monitor/static/index.html +2124 -0
- package/dist/commands/monitor/static-web-server.d.ts +1 -0
- package/dist/commands/monitor/static-web-server.js +89 -0
- package/dist/commands/profile/index.d.ts +1 -0
- package/dist/commands/profile/index.js +112 -0
- package/dist/commands/publish/index.d.ts +1 -0
- package/dist/commands/publish/index.js +4 -0
- package/dist/commands/publish/node.d.ts +4 -0
- package/dist/commands/publish/node.js +231 -0
- package/dist/commands/publish/workflow.d.ts +4 -0
- package/dist/commands/publish/workflow.js +165 -0
- package/dist/commands/search/docs.d.ts +17 -0
- package/dist/commands/search/docs.js +179 -0
- package/dist/commands/search/index.d.ts +1 -0
- package/dist/commands/search/index.js +5 -0
- package/dist/commands/search/indexer.d.ts +10 -0
- package/dist/commands/search/indexer.js +265 -0
- package/dist/commands/search/nodes.d.ts +4 -0
- package/dist/commands/search/nodes.js +101 -0
- package/dist/commands/search/workflow.d.ts +4 -0
- package/dist/commands/search/workflow.js +100 -0
- package/dist/commands/trace/index.d.ts +1 -0
- package/dist/commands/trace/index.js +26 -0
- package/dist/commands/trace/startStudio.d.ts +8 -0
- package/dist/commands/trace/startStudio.js +116 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +186 -0
- package/dist/services/commander.d.ts +9 -0
- package/dist/services/commander.js +20 -0
- package/dist/services/constants.d.ts +1 -0
- package/dist/services/constants.js +3 -0
- package/dist/services/local-token-manager.d.ts +14 -0
- package/dist/services/local-token-manager.js +99 -0
- package/dist/services/non-interactive.d.ts +5 -0
- package/dist/services/non-interactive.js +30 -0
- package/dist/services/package-manager.d.ts +35 -0
- package/dist/services/package-manager.js +111 -0
- package/dist/services/posthog.d.ts +31 -0
- package/dist/services/posthog.js +159 -0
- package/dist/services/registry-manager.d.ts +9 -0
- package/dist/services/registry-manager.js +26 -0
- package/dist/services/runtime-detector.d.ts +23 -0
- package/dist/services/runtime-detector.js +181 -0
- package/dist/services/runtime-setup.d.ts +36 -0
- package/dist/services/runtime-setup.js +250 -0
- package/dist/services/utils.d.ts +2 -0
- package/dist/services/utils.js +29 -0
- package/dist/services/workflow-loader.d.ts +30 -0
- package/dist/services/workflow-loader.js +46 -0
- package/dist/studio-dist/assets/charts-Dso0hPUR.js +68 -0
- package/dist/studio-dist/assets/graph-CsV2nWGn.js +23 -0
- package/dist/studio-dist/assets/icons-zP8LLgPh.js +311 -0
- package/dist/studio-dist/assets/index-CLyEkXMx.css +1 -0
- package/dist/studio-dist/assets/index-CNXFX_ar.js +27 -0
- package/dist/studio-dist/assets/react-vendor--Eh9ivFN.js +17 -0
- package/dist/studio-dist/assets/tanstack-query-CiM1U6F5.js +1 -0
- package/dist/studio-dist/assets/tanstack-router-Btjy0MKq.js +25 -0
- package/dist/studio-dist/assets/tanstack-table-DhwRvuH2.js +22 -0
- package/dist/studio-dist/favicon.svg +5 -0
- package/dist/studio-dist/index.html +21 -0
- 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
|
+
}
|