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,240 @@
1
+ import * as fs from "node:fs";
2
+ import os from "node:os";
3
+ import * as path from "node:path";
4
+ import { createOpenAI } from "@ai-sdk/openai";
5
+ import { generateText } from "ai";
6
+ import fsExtra from "fs-extra";
7
+ import color from "picocolors";
8
+ import generateNodeManifestSystemPrompt from "./prompts/create-node-manifest.system.js";
9
+ import generateReadmeFromBlokService from "./prompts/create-readme.system.js";
10
+ export default class NodeFileWriter {
11
+ nodeDependencies = [
12
+ "assert",
13
+ "async_hooks",
14
+ "buffer",
15
+ "child_process",
16
+ "cluster",
17
+ "console",
18
+ "constants",
19
+ "crypto",
20
+ "dgram",
21
+ "diagnostics_channel",
22
+ "dns",
23
+ "domain",
24
+ "events",
25
+ "fs",
26
+ "fs/promises",
27
+ "http",
28
+ "http2",
29
+ "https",
30
+ "inspector",
31
+ "module",
32
+ "net",
33
+ "os",
34
+ "path",
35
+ "perf_hooks",
36
+ "process",
37
+ "punycode",
38
+ "querystring",
39
+ "readline",
40
+ "repl",
41
+ "stream",
42
+ "stream/consumers",
43
+ "stream/promises",
44
+ "stream/web",
45
+ "string_decoder",
46
+ "sys",
47
+ "timers",
48
+ "timers/promises",
49
+ "tls",
50
+ "trace_events",
51
+ "tty",
52
+ "url",
53
+ "util",
54
+ "v8",
55
+ "vm",
56
+ "wasi",
57
+ "worker_threads",
58
+ "zlib",
59
+ ];
60
+ typesDependencies = [
61
+ "express",
62
+ "lodash",
63
+ "jest",
64
+ "mocha",
65
+ "chai",
66
+ "sinon",
67
+ "debug",
68
+ "cors",
69
+ "body-parser",
70
+ "cookie-parser",
71
+ "jsonwebtoken",
72
+ "multer",
73
+ "morgan",
74
+ "passport",
75
+ "uuid",
76
+ "pg",
77
+ "mysql",
78
+ "mysql2",
79
+ "mongodb",
80
+ "node-fetch",
81
+ "ws",
82
+ "socket.io",
83
+ "redis",
84
+ "react",
85
+ "react-dom",
86
+ "jquery",
87
+ "next",
88
+ "yargs",
89
+ "commander",
90
+ "dotenv",
91
+ "formidable",
92
+ "glob",
93
+ "jsonwebtoken",
94
+ "validator",
95
+ "connect",
96
+ "request",
97
+ "supertest",
98
+ "node-schedule",
99
+ ];
100
+ async generateFile(nodeName, nodeType, fileContent, apiKey, nodeStyle = "class") {
101
+ try {
102
+ const dirName = nodeName.toLowerCase().replace(/\s+/g, "-");
103
+ const dirPath = process.cwd();
104
+ const nodeDir = `${dirPath}/src/nodes`;
105
+ const HOME_DIR = `${os.homedir()}/.blok`;
106
+ const GITHUB_REPO_LOCAL = `${HOME_DIR}/blok`;
107
+ if (!fs.existsSync(nodeDir)) {
108
+ throw new Error("The nodes directory does not exist. Please ensure you are in the correct project directory.");
109
+ }
110
+ const currentDir = `${nodeDir}/${dirName}`;
111
+ if (!fs.existsSync(currentDir)) {
112
+ fs.mkdirSync(currentDir, { recursive: true });
113
+ }
114
+ const filePath = path.join(currentDir, "index.ts");
115
+ if (nodeType === "module") {
116
+ console.log("\n\nCreating required files for a module-type node.");
117
+ console.log("- index.ts");
118
+ console.log("- config.json");
119
+ console.log("- README.md");
120
+ console.log("- package.json\n");
121
+ if (nodeStyle === "function") {
122
+ fsExtra.copySync(`${GITHUB_REPO_LOCAL}/templates/node-function`, currentDir);
123
+ console.log(color.cyan("✨ Using function-first template (defineNode API)\n"));
124
+ }
125
+ else {
126
+ fsExtra.copySync(`${GITHUB_REPO_LOCAL}/templates/node`, currentDir);
127
+ }
128
+ fs.writeFileSync(filePath, fileContent, "utf8");
129
+ const configFileContent = fs.readFileSync(`${currentDir}/config.json`, "utf8");
130
+ const openai = createOpenAI({
131
+ compatibility: "strict",
132
+ apiKey: apiKey,
133
+ });
134
+ const config = await generateText({
135
+ model: openai("gpt-4o"),
136
+ system: `${generateNodeManifestSystemPrompt.prompt} \n${configFileContent}`,
137
+ prompt: `Node information:
138
+
139
+ Name: ${nodeName} (This is the key in the nodes object)
140
+ Source Code:
141
+ ${fileContent}
142
+
143
+ Take the class name from the source code and use it to register the node in Nodes.ts.`,
144
+ temperature: 0.2,
145
+ });
146
+ let cleaned = config.text.replace(/^```json\s*([\s\S]*?)\s*```$/gm, "$1");
147
+ fs.writeFileSync(`${currentDir}/config.json`, cleaned, "utf8");
148
+ const readme = await generateText({
149
+ model: openai("gpt-4o"),
150
+ system: `${generateReadmeFromBlokService.prompt} \n${configFileContent}`,
151
+ prompt: `Node information:
152
+
153
+ Name: ${nodeName} (This is the key in the nodes object)
154
+ Source Code:
155
+ ${fileContent}
156
+
157
+ Take the class name from the source code and use it to register the node in Nodes.ts.`,
158
+ temperature: 0.2,
159
+ });
160
+ cleaned = readme.text.replace(/^```markdown\s*([\s\S]*?)\s*```$/gm, "$1");
161
+ fs.writeFileSync(`${currentDir}/README.md`, cleaned, "utf8");
162
+ const packageJsonPath = `${currentDir}/package.json`;
163
+ const packageJsonContent = fs.readFileSync(packageJsonPath, "utf8");
164
+ const packageJson = JSON.parse(packageJsonContent);
165
+ packageJson.name = nodeName;
166
+ packageJson.description = `A BlokService node for ${nodeName}`;
167
+ packageJson.version = "1.0.0";
168
+ let hasDependencies = false;
169
+ const dependencies = fileContent.match(/import\s+.*?\s+from\s+['"](.*?)['"]/g);
170
+ const installedDependencies = [];
171
+ if (nodeStyle === "function") {
172
+ packageJson.dependencies = packageJson.dependencies || {};
173
+ if (!packageJson.dependencies.zod) {
174
+ packageJson.dependencies.zod = "^3.24.1";
175
+ installedDependencies.push({
176
+ name: "zod",
177
+ version: "^3.24.1",
178
+ });
179
+ }
180
+ }
181
+ if (dependencies) {
182
+ const depList = dependencies
183
+ .map((dep) => {
184
+ const match = dep.match(/['"](.*?)['"]/);
185
+ return match ? match[1] : null;
186
+ })
187
+ .filter((dep) => dep !== null);
188
+ packageJson.dependencies = packageJson.dependencies || {};
189
+ for (const dep of depList) {
190
+ if (!packageJson.dependencies[dep] && !this.nodeDependencies.includes(dep)) {
191
+ packageJson.dependencies[dep] = "latest";
192
+ installedDependencies.push({
193
+ name: dep,
194
+ version: "latest",
195
+ });
196
+ }
197
+ if (this.typesDependencies.includes(dep) &&
198
+ (!packageJson.devDependencies || !packageJson.devDependencies[`@types/${dep}`])) {
199
+ packageJson.devDependencies = packageJson.devDependencies || {};
200
+ packageJson.devDependencies[`@types/${dep}`] = "latest";
201
+ }
202
+ }
203
+ }
204
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2), "utf8");
205
+ if (installedDependencies.length > 0) {
206
+ const packageJsonDirPath = `${dirPath}/package.json`;
207
+ const packageJsonDirContent = fs.readFileSync(packageJsonDirPath, "utf8");
208
+ const packageJsonDir = JSON.parse(packageJsonDirContent);
209
+ console.log("\n");
210
+ for (const dep of installedDependencies) {
211
+ if (!packageJsonDir.dependencies[dep.name] && !this.nodeDependencies.includes(dep.name)) {
212
+ packageJsonDir.dependencies[dep.name] = dep.version;
213
+ console.log(color.blue(`Added dependency "${dep.name}": "${dep.version}".`));
214
+ hasDependencies = true;
215
+ }
216
+ if (this.typesDependencies.includes(dep.name) &&
217
+ (!packageJsonDir.devDependencies || !packageJsonDir.devDependencies[`@types/${dep.name}`])) {
218
+ packageJsonDir.devDependencies = packageJsonDir.devDependencies || {};
219
+ packageJsonDir.devDependencies[`@types/${dep.name}`] = "latest";
220
+ console.log(color.cyan(`Added dev dependency "@types/${dep.name}": "latest".`));
221
+ hasDependencies = true;
222
+ }
223
+ }
224
+ fs.writeFileSync(packageJsonDirPath, JSON.stringify(packageJsonDir, null, 2), "utf8");
225
+ if (hasDependencies) {
226
+ console.log(color.blue("Run `npm install` to install the new dependencies.\n"));
227
+ }
228
+ }
229
+ }
230
+ else {
231
+ fs.writeFileSync(filePath, fileContent, "utf8");
232
+ }
233
+ return filePath;
234
+ }
235
+ catch (error) {
236
+ console.error(`Error generating file: ${error.message}`);
237
+ throw error;
238
+ }
239
+ }
240
+ }
@@ -0,0 +1,20 @@
1
+ type NodeInformation = {
2
+ nodeName: string;
3
+ userPrompt: string;
4
+ code: string;
5
+ validationResult?: {
6
+ valid: boolean;
7
+ errors: string[];
8
+ warnings: string[];
9
+ attempts: number;
10
+ promptVersion?: string;
11
+ durationMs?: number;
12
+ };
13
+ };
14
+ export type { NodeInformation };
15
+ export default class NodeGenerator {
16
+ private readonly MAX_VALIDATION_ATTEMPTS;
17
+ generateNode(nodeName: string, userPrompt: string, apiKey: string, update?: boolean, nodeStyle?: string): Promise<NodeInformation>;
18
+ private createFeedbackPrompt;
19
+ private getSemanticGuidance;
20
+ }
@@ -0,0 +1,181 @@
1
+ import * as fs from "node:fs";
2
+ import { createOpenAI } from "@ai-sdk/openai";
3
+ import { generateText } from "ai";
4
+ import { GenerationAnalytics } from "./GenerationAnalytics.js";
5
+ import { getVersionStamp, registerPromptContent } from "./PromptVersioning.js";
6
+ import createFnNodeSystemPrompt from "./prompts/create-fn-node.system.js";
7
+ import createNodeSystemPrompt from "./prompts/create-node.system.js";
8
+ import * as CompilationValidator from "./validators/CompilationValidator.js";
9
+ import * as NodeValidator from "./validators/NodeValidator.js";
10
+ export default class NodeGenerator {
11
+ MAX_VALIDATION_ATTEMPTS = 3;
12
+ async generateNode(nodeName, userPrompt, apiKey, update = false, nodeStyle = "function") {
13
+ const analytics = GenerationAnalytics.getInstance();
14
+ const getElapsed = analytics.startTimer();
15
+ const promptId = nodeStyle === "function" ? "create-fn-node" : "create-node";
16
+ const promptVersion = getVersionStamp(promptId);
17
+ const openai = createOpenAI({
18
+ compatibility: "strict",
19
+ apiKey: apiKey,
20
+ });
21
+ const promptTemplate = nodeStyle === "function" ? createFnNodeSystemPrompt : createNodeSystemPrompt;
22
+ let prompt = promptTemplate.prompt;
23
+ let existingCode = null;
24
+ registerPromptContent(promptId, prompt);
25
+ if (update) {
26
+ const dirName = nodeName.toLowerCase().replace(/\s+/g, "-");
27
+ const dirPath = process.cwd();
28
+ const nodeDir = `${dirPath}/src/nodes`;
29
+ const currentDir = `${nodeDir}/${dirName}`;
30
+ const filePath = `${currentDir}/index.ts`;
31
+ existingCode = fs.readFileSync(filePath, "utf8");
32
+ prompt = `${promptTemplate.updatePrompt} \n\n ${existingCode}`;
33
+ }
34
+ let attempts = 0;
35
+ let generatedCode = "";
36
+ let validationErrors = [];
37
+ let validationWarnings = [];
38
+ let isValid = false;
39
+ const allErrors = [];
40
+ while (attempts < this.MAX_VALIDATION_ATTEMPTS && !isValid) {
41
+ attempts++;
42
+ let finalPrompt = userPrompt;
43
+ if (attempts > 1 && validationErrors.length > 0) {
44
+ finalPrompt = this.createFeedbackPrompt(userPrompt, generatedCode, validationErrors);
45
+ }
46
+ const { text } = await generateText({
47
+ model: openai("gpt-4o"),
48
+ system: prompt,
49
+ prompt: finalPrompt,
50
+ temperature: 0.2,
51
+ });
52
+ generatedCode = text;
53
+ const compilationResult = CompilationValidator.validateCode(generatedCode, `${nodeName}.ts`);
54
+ validationErrors = compilationResult.errors;
55
+ validationWarnings = compilationResult.warnings;
56
+ isValid = compilationResult.success;
57
+ if (isValid && nodeStyle === "function") {
58
+ const context = {
59
+ filePath: `${nodeName}.ts`,
60
+ nodeStyle: "function",
61
+ content: generatedCode,
62
+ };
63
+ const structureResult = NodeValidator.validateFunctionFirstStructure(context);
64
+ if (!structureResult.valid) {
65
+ isValid = false;
66
+ validationErrors.push(...structureResult.errors);
67
+ validationWarnings.push(...structureResult.warnings);
68
+ }
69
+ }
70
+ allErrors.push(...validationErrors);
71
+ if (!isValid && attempts < this.MAX_VALIDATION_ATTEMPTS) {
72
+ console.log(`⚠️ Validation failed (attempt ${attempts}/${this.MAX_VALIDATION_ATTEMPTS}). Retrying with feedback...`);
73
+ }
74
+ }
75
+ const durationMs = getElapsed();
76
+ analytics.recordEvent({
77
+ type: "node",
78
+ subtype: nodeStyle,
79
+ name: nodeName,
80
+ success: isValid,
81
+ attempts,
82
+ durationMs,
83
+ errors: allErrors,
84
+ promptVersion,
85
+ });
86
+ return {
87
+ nodeName,
88
+ userPrompt,
89
+ code: generatedCode,
90
+ validationResult: {
91
+ valid: isValid,
92
+ errors: validationErrors,
93
+ warnings: validationWarnings,
94
+ attempts,
95
+ promptVersion,
96
+ durationMs,
97
+ },
98
+ };
99
+ }
100
+ createFeedbackPrompt(originalPrompt, previousCode, errors) {
101
+ const analyzedErrors = errors.map((err, i) => {
102
+ const guidance = this.getSemanticGuidance(err);
103
+ return `${i + 1}. ${err}${guidance ? `\n 💡 Fix: ${guidance}` : ""}`;
104
+ });
105
+ const feedback = [
106
+ originalPrompt,
107
+ "",
108
+ "❌ The previous generation had validation errors. Here's what went wrong and how to fix it:",
109
+ "",
110
+ ...analyzedErrors,
111
+ "",
112
+ "Previous code:",
113
+ "```typescript",
114
+ previousCode,
115
+ "```",
116
+ "",
117
+ "Please fix ALL the errors listed above and regenerate the complete code.",
118
+ "Make sure to:",
119
+ "- Import defineNode from '@blok/runner'",
120
+ "- Import z from 'zod'",
121
+ "- Import Context type from '@blok/shared'",
122
+ "- Use z.object({...}) for input and output schemas",
123
+ "- Make the execute function async",
124
+ "- Export as default: export default defineNode({...})",
125
+ "- Return a plain object matching the output schema (no BlokResponse)",
126
+ ].join("\n");
127
+ return feedback;
128
+ }
129
+ getSemanticGuidance(error) {
130
+ const errorLower = error.toLowerCase();
131
+ if (errorLower.includes("missing") && errorLower.includes("definenode")) {
132
+ return "Add: import { defineNode } from '@blok/runner';";
133
+ }
134
+ if (errorLower.includes("missing") && errorLower.includes("zod")) {
135
+ return "Add: import { z } from 'zod';";
136
+ }
137
+ if (errorLower.includes("cannot find") && errorLower.includes("definenode")) {
138
+ return "Ensure defineNode is imported from '@blok/runner'";
139
+ }
140
+ if (errorLower.includes("z.object") || errorLower.includes("zod schema")) {
141
+ return "Use z.object({...}) for both input and output schemas with proper Zod types";
142
+ }
143
+ if (errorLower.includes("execute") && errorLower.includes("async")) {
144
+ return "The execute function must be async: async execute(ctx, input) { ... }";
145
+ }
146
+ if (errorLower.includes("execute") && errorLower.includes("missing")) {
147
+ return "Add execute property: async execute(ctx, input) { return { ... }; }";
148
+ }
149
+ if (errorLower.includes("export") && errorLower.includes("default")) {
150
+ return "Use: export default defineNode({...}) at the end of the file";
151
+ }
152
+ if (errorLower.includes("type") && errorLower.includes("not assignable")) {
153
+ return "Check that the return type of execute() matches the output Zod schema exactly";
154
+ }
155
+ if (errorLower.includes("blokresponse") || errorLower.includes("setsuccess")) {
156
+ return "Do NOT use BlokResponse in function-first nodes. Just return a plain object.";
157
+ }
158
+ if (errorLower.includes("ctx.request") || errorLower.includes("context")) {
159
+ return "Use ctx.request.body, ctx.request.query, ctx.request.params for HTTP data; ctx.vars for cross-node data";
160
+ }
161
+ if (errorLower.includes("cannot find module") || errorLower.includes("module not found")) {
162
+ return "Check import paths. Use '@blok/runner' for defineNode and '@blok/shared' for Context/GlobalError";
163
+ }
164
+ if (errorLower.includes("property") && errorLower.includes("does not exist")) {
165
+ return "Verify property names match your Zod schemas. Use z.infer<typeof schema> for type inference.";
166
+ }
167
+ if (errorLower.includes("name") && errorLower.includes("missing")) {
168
+ return "Add a 'name' property with a kebab-case identifier, e.g., name: 'my-node'";
169
+ }
170
+ if (errorLower.includes("description") && errorLower.includes("missing")) {
171
+ return "Add a 'description' property describing what the node does";
172
+ }
173
+ if (errorLower.includes("duplicate identifier") || errorLower.includes("already declared")) {
174
+ return "Remove duplicate variable/function declarations. Each identifier must be unique in its scope.";
175
+ }
176
+ if (errorLower.includes("implicit") && errorLower.includes("any")) {
177
+ return "Add explicit type annotations. Use z.infer<typeof inputSchema> for input types.";
178
+ }
179
+ return null;
180
+ }
181
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,101 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import NodeGenerator from "./NodeGenerator.js";
3
+ describe("NodeGenerator", () => {
4
+ describe("getSemanticGuidance (via reflection)", () => {
5
+ const generator = new NodeGenerator();
6
+ const getGuidance = (error) => generator.getSemanticGuidance(error);
7
+ it("should provide guidance for missing defineNode import", () => {
8
+ const guidance = getGuidance("Missing defineNode import");
9
+ expect(guidance).not.toBeNull();
10
+ expect(guidance).toContain("defineNode");
11
+ expect(guidance).toContain("@blok/runner");
12
+ });
13
+ it("should provide guidance for missing Zod import", () => {
14
+ const guidance = getGuidance("Missing zod import");
15
+ expect(guidance).not.toBeNull();
16
+ expect(guidance).toContain("zod");
17
+ });
18
+ it("should provide guidance for cannot find defineNode", () => {
19
+ const guidance = getGuidance("Cannot find name 'defineNode'");
20
+ expect(guidance).not.toBeNull();
21
+ expect(guidance).toContain("defineNode");
22
+ });
23
+ it("should provide guidance for Zod schema issues", () => {
24
+ const guidance = getGuidance("Missing z.object() for input schema");
25
+ expect(guidance).not.toBeNull();
26
+ expect(guidance).toContain("z.object");
27
+ });
28
+ it("should provide guidance for missing async execute", () => {
29
+ const guidance = getGuidance("Execute function must be async");
30
+ expect(guidance).not.toBeNull();
31
+ expect(guidance).toContain("async");
32
+ });
33
+ it("should provide guidance for missing execute function", () => {
34
+ const guidance = getGuidance("Missing execute property");
35
+ expect(guidance).not.toBeNull();
36
+ expect(guidance).toContain("execute");
37
+ });
38
+ it("should provide guidance for missing default export", () => {
39
+ const guidance = getGuidance("Missing export default");
40
+ expect(guidance).not.toBeNull();
41
+ expect(guidance).toContain("export default");
42
+ });
43
+ it("should provide guidance for type not assignable errors", () => {
44
+ const guidance = getGuidance("Type 'string' is not assignable to type 'number'");
45
+ expect(guidance).not.toBeNull();
46
+ expect(guidance).toContain("output Zod schema");
47
+ });
48
+ it("should provide guidance for BlokResponse misuse", () => {
49
+ const guidance = getGuidance("Do not use BlokResponse in function-first nodes");
50
+ expect(guidance).not.toBeNull();
51
+ expect(guidance).toContain("plain object");
52
+ });
53
+ it("should provide guidance for setSuccess misuse", () => {
54
+ const guidance = getGuidance("response.setSuccess is not needed");
55
+ expect(guidance).not.toBeNull();
56
+ expect(guidance).toContain("plain object");
57
+ });
58
+ it("should provide guidance for module not found", () => {
59
+ const guidance = getGuidance("Cannot find module '@blok/core'");
60
+ expect(guidance).not.toBeNull();
61
+ expect(guidance).toContain("import paths");
62
+ });
63
+ it("should provide guidance for context access patterns", () => {
64
+ const guidance = getGuidance("Property 'request' does not exist on type 'Context'");
65
+ expect(guidance).not.toBeNull();
66
+ expect(guidance).toContain("ctx.request");
67
+ });
68
+ it("should return null for unknown error patterns", () => {
69
+ const guidance = getGuidance("Some random error that doesn't match any pattern");
70
+ expect(guidance).toBeNull();
71
+ });
72
+ });
73
+ describe("createFeedbackPrompt (via reflection)", () => {
74
+ const generator = new NodeGenerator();
75
+ const createFeedback = (originalPrompt, previousCode, errors) => generator.createFeedbackPrompt(originalPrompt, previousCode, errors);
76
+ it("should include original prompt", () => {
77
+ const result = createFeedback("Create a user fetcher node", "code", ["error"]);
78
+ expect(result).toContain("Create a user fetcher node");
79
+ });
80
+ it("should include all errors with semantic guidance", () => {
81
+ const errors = ["Missing defineNode import", "Missing zod import"];
82
+ const result = createFeedback("test", "code", errors);
83
+ expect(result).toContain("Missing defineNode import");
84
+ expect(result).toContain("Missing zod import");
85
+ expect(result).toContain("Fix:");
86
+ });
87
+ it("should include previous code for context", () => {
88
+ const code = "const x = 42;";
89
+ const result = createFeedback("test", code, ["error"]);
90
+ expect(result).toContain(code);
91
+ });
92
+ it("should include checklist of common requirements", () => {
93
+ const result = createFeedback("test", "code", ["error"]);
94
+ expect(result).toContain("defineNode");
95
+ expect(result).toContain("zod");
96
+ expect(result).toContain("export default");
97
+ expect(result).toContain("z.object");
98
+ expect(result).toContain("async");
99
+ });
100
+ });
101
+ });
@@ -0,0 +1,25 @@
1
+ export interface PromptVersion {
2
+ id: string;
3
+ version: string;
4
+ changelog: string;
5
+ createdAt: string;
6
+ contentHash: string;
7
+ }
8
+ export interface PromptRegistry {
9
+ [promptId: string]: PromptVersion;
10
+ }
11
+ export declare const PROMPT_VERSIONS: PromptRegistry;
12
+ export declare function computeContentHash(content: string): string;
13
+ export declare function getPromptVersion(promptId: string): PromptVersion | undefined;
14
+ export declare function getAllPromptVersions(): PromptVersion[];
15
+ export declare function getVersionStamp(promptId: string): string;
16
+ export declare function registerPromptContent(promptId: string, content: string): void;
17
+ declare const _default: {
18
+ PROMPT_VERSIONS: PromptRegistry;
19
+ getPromptVersion: typeof getPromptVersion;
20
+ getAllPromptVersions: typeof getAllPromptVersions;
21
+ getVersionStamp: typeof getVersionStamp;
22
+ computeContentHash: typeof computeContentHash;
23
+ registerPromptContent: typeof registerPromptContent;
24
+ };
25
+ export default _default;
@@ -0,0 +1,71 @@
1
+ export const PROMPT_VERSIONS = {
2
+ "create-fn-node": {
3
+ id: "create-fn-node",
4
+ version: "2.0.0",
5
+ changelog: "Function-first defineNode pattern with Zod schemas, 2 real-world examples",
6
+ createdAt: "2026-01-28T00:00:00Z",
7
+ contentHash: "",
8
+ },
9
+ "create-node": {
10
+ id: "create-node",
11
+ version: "1.0.0",
12
+ changelog: "Class-based BlokService pattern with GlobalError handling",
13
+ createdAt: "2026-01-27T00:00:00Z",
14
+ contentHash: "",
15
+ },
16
+ "create-workflow": {
17
+ id: "create-workflow",
18
+ version: "2.0.0",
19
+ changelog: "7 trigger types (HTTP, Queue, Pub/Sub, Cron, Webhook, WebSocket, SSE), 4 examples",
20
+ createdAt: "2026-01-28T00:00:00Z",
21
+ contentHash: "",
22
+ },
23
+ "create-trigger": {
24
+ id: "create-trigger",
25
+ version: "2.0.0",
26
+ changelog: "TriggerBase architecture, 6 trigger type examples, context creation pattern",
27
+ createdAt: "2026-01-28T00:00:00Z",
28
+ contentHash: "",
29
+ },
30
+ "create-runtime": {
31
+ id: "create-runtime",
32
+ version: "1.0.0",
33
+ changelog: "Runtime SDK generation for Go, Java, Rust, Python, C#, PHP, Ruby with HTTP protocol",
34
+ createdAt: "2026-01-28T00:00:00Z",
35
+ contentHash: "",
36
+ },
37
+ };
38
+ export function computeContentHash(content) {
39
+ let hash = 0;
40
+ for (let i = 0; i < content.length; i++) {
41
+ const char = content.charCodeAt(i);
42
+ hash = ((hash << 5) - hash + char) | 0;
43
+ }
44
+ return Math.abs(hash).toString(36);
45
+ }
46
+ export function getPromptVersion(promptId) {
47
+ return PROMPT_VERSIONS[promptId];
48
+ }
49
+ export function getAllPromptVersions() {
50
+ return Object.values(PROMPT_VERSIONS);
51
+ }
52
+ export function getVersionStamp(promptId) {
53
+ const version = PROMPT_VERSIONS[promptId];
54
+ if (!version)
55
+ return `${promptId}@unknown`;
56
+ return `${version.id}@${version.version}`;
57
+ }
58
+ export function registerPromptContent(promptId, content) {
59
+ const version = PROMPT_VERSIONS[promptId];
60
+ if (version) {
61
+ version.contentHash = computeContentHash(content);
62
+ }
63
+ }
64
+ export default {
65
+ PROMPT_VERSIONS,
66
+ getPromptVersion,
67
+ getAllPromptVersions,
68
+ getVersionStamp,
69
+ computeContentHash,
70
+ registerPromptContent,
71
+ };
@@ -0,0 +1 @@
1
+ export {};