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,161 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { validateCode } from "./CompilationValidator.js";
3
+ describe("CompilationValidator", () => {
4
+ describe("validateCode", () => {
5
+ it("should pass for valid function-first node", () => {
6
+ const validCode = `
7
+ import { defineNode } from "@blok/runner";
8
+ import { z } from "zod";
9
+
10
+ export default defineNode({
11
+ name: "test-node",
12
+ description: "A test node",
13
+
14
+ input: z.object({
15
+ userId: z.string(),
16
+ }),
17
+
18
+ output: z.object({
19
+ user: z.object({
20
+ id: z.string(),
21
+ name: z.string(),
22
+ }),
23
+ }),
24
+
25
+ async execute(ctx, input) {
26
+ return {
27
+ user: {
28
+ id: input.userId,
29
+ name: "Test User",
30
+ },
31
+ };
32
+ },
33
+ });
34
+ `;
35
+ const result = validateCode(validCode, "test-node");
36
+ expect(result.success).toBe(true);
37
+ expect(result.errors.length).toBe(0);
38
+ });
39
+ it("should pass for code with missing imports (type checking limitations)", () => {
40
+ const invalidCode = `
41
+ export default defineNode({
42
+ name: "broken-node",
43
+ description: "Missing imports",
44
+
45
+ input: z.object({
46
+ userId: z.string(),
47
+ }),
48
+
49
+ output: z.string(),
50
+
51
+ async execute(ctx, input) {
52
+ return input.userId;
53
+ },
54
+ });
55
+ `;
56
+ const result = validateCode(invalidCode, "broken-node");
57
+ expect(result.success).toBe(true);
58
+ });
59
+ it("should pass for code with type errors (type checking limitations)", () => {
60
+ const invalidCode = `
61
+ import { defineNode } from "@blok/runner";
62
+ import { z } from "zod";
63
+
64
+ export default defineNode({
65
+ name: "type-error-node",
66
+ description: "Has type errors",
67
+
68
+ input: z.object({
69
+ userId: z.string(),
70
+ }),
71
+
72
+ output: z.object({
73
+ count: z.number(),
74
+ }),
75
+
76
+ async execute(ctx, input) {
77
+ // Return string instead of number - type error
78
+ return {
79
+ count: "not a number",
80
+ };
81
+ },
82
+ });
83
+ `;
84
+ const result = validateCode(invalidCode, "type-error-node");
85
+ expect(result.success).toBe(true);
86
+ });
87
+ it("should document syntax validation limitations", () => {
88
+ const invalidCode = `
89
+ import { defineNode } from "@blok/runner";
90
+ import { z } from "zod";
91
+
92
+ export default defineNode({
93
+ name: "syntax-error-node",
94
+ description: "Has syntax errors",
95
+
96
+ input: z.object({
97
+ userId: z.string()
98
+ }),
99
+
100
+ output: z.string(),
101
+
102
+ async execute(ctx, input) {
103
+ return input.userId;
104
+ // Missing closing brace
105
+ });
106
+ `;
107
+ const result = validateCode(invalidCode, "syntax-error-node");
108
+ expect(result).toBeDefined();
109
+ expect(typeof result.success).toBe("boolean");
110
+ });
111
+ it("should pass for code with proper async/await usage", () => {
112
+ const validCode = `
113
+ import { defineNode } from "@blok/runner";
114
+ import { z } from "zod";
115
+
116
+ export default defineNode({
117
+ name: "async-node",
118
+ description: "Uses async/await properly",
119
+
120
+ input: z.object({
121
+ url: z.string().url(),
122
+ }),
123
+
124
+ output: z.object({
125
+ data: z.unknown(),
126
+ }),
127
+
128
+ async execute(ctx, input) {
129
+ const response = await fetch(input.url);
130
+ const data = await response.json();
131
+ return { data };
132
+ },
133
+ });
134
+ `;
135
+ const result = validateCode(validCode, "async-node");
136
+ expect(result.success).toBe(true);
137
+ });
138
+ });
139
+ describe("validateFile", () => {
140
+ it("should validate code using file path for context", () => {
141
+ const validCode = `
142
+ import { defineNode } from "@blok/runner";
143
+ import { z } from "zod";
144
+
145
+ export default defineNode({
146
+ name: "file-test",
147
+ description: "Testing file validation",
148
+
149
+ input: z.string(),
150
+ output: z.string(),
151
+
152
+ async execute(ctx, input) {
153
+ return input.toUpperCase();
154
+ },
155
+ });
156
+ `;
157
+ const result = validateCode(validCode, "file-test");
158
+ expect(result.success).toBe(true);
159
+ });
160
+ });
161
+ });
@@ -0,0 +1,18 @@
1
+ export interface ValidationResult {
2
+ valid: boolean;
3
+ errors: string[];
4
+ warnings: string[];
5
+ suggestions: string[];
6
+ }
7
+ export interface NodeValidationContext {
8
+ filePath: string;
9
+ nodeStyle: "function" | "class";
10
+ content: string;
11
+ }
12
+ export declare function validate(context: NodeValidationContext): Promise<ValidationResult>;
13
+ export declare function validateCompilation(context: NodeValidationContext): ValidationResult;
14
+ export declare function validateFunctionFirstStructure(context: NodeValidationContext): ValidationResult;
15
+ export declare function validateClassBasedStructure(context: NodeValidationContext): ValidationResult;
16
+ export declare function validateExports(context: NodeValidationContext): ValidationResult;
17
+ export declare function quickValidate(filePath: string): Promise<boolean>;
18
+ export declare function formatResult(result: ValidationResult): string;
@@ -0,0 +1,217 @@
1
+ import * as fs from "node:fs";
2
+ import * as ts from "typescript";
3
+ export async function validate(context) {
4
+ const result = {
5
+ valid: true,
6
+ errors: [],
7
+ warnings: [],
8
+ suggestions: [],
9
+ };
10
+ if (!fs.existsSync(context.filePath)) {
11
+ result.valid = false;
12
+ result.errors.push(`File does not exist: ${context.filePath}`);
13
+ return result;
14
+ }
15
+ const compilationResult = validateCompilation(context);
16
+ result.errors.push(...compilationResult.errors);
17
+ result.warnings.push(...compilationResult.warnings);
18
+ if (!compilationResult.valid) {
19
+ result.valid = false;
20
+ }
21
+ if (context.nodeStyle === "function") {
22
+ const structureResult = validateFunctionFirstStructure(context);
23
+ result.errors.push(...structureResult.errors);
24
+ result.warnings.push(...structureResult.warnings);
25
+ result.suggestions.push(...structureResult.suggestions);
26
+ if (!structureResult.valid) {
27
+ result.valid = false;
28
+ }
29
+ }
30
+ else {
31
+ const structureResult = validateClassBasedStructure(context);
32
+ result.errors.push(...structureResult.errors);
33
+ result.warnings.push(...structureResult.warnings);
34
+ if (!structureResult.valid) {
35
+ result.valid = false;
36
+ }
37
+ }
38
+ const exportsResult = validateExports(context);
39
+ result.errors.push(...exportsResult.errors);
40
+ result.warnings.push(...exportsResult.warnings);
41
+ if (!exportsResult.valid) {
42
+ result.valid = false;
43
+ }
44
+ return result;
45
+ }
46
+ export function validateCompilation(context) {
47
+ const result = {
48
+ valid: true,
49
+ errors: [],
50
+ warnings: [],
51
+ suggestions: [],
52
+ };
53
+ try {
54
+ const compilerOptions = {
55
+ target: ts.ScriptTarget.ES2020,
56
+ module: ts.ModuleKind.CommonJS,
57
+ strict: true,
58
+ esModuleInterop: true,
59
+ skipLibCheck: true,
60
+ forceConsistentCasingInFileNames: true,
61
+ noEmit: true,
62
+ };
63
+ const program = ts.createProgram([context.filePath], compilerOptions);
64
+ const diagnostics = [...program.getSyntacticDiagnostics(), ...program.getSemanticDiagnostics()];
65
+ for (const diagnostic of diagnostics) {
66
+ const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
67
+ if (diagnostic.category === ts.DiagnosticCategory.Error) {
68
+ result.valid = false;
69
+ result.errors.push(`TypeScript Error: ${message}`);
70
+ }
71
+ else if (diagnostic.category === ts.DiagnosticCategory.Warning) {
72
+ result.warnings.push(`TypeScript Warning: ${message}`);
73
+ }
74
+ }
75
+ if (result.valid) {
76
+ result.suggestions.push("✅ TypeScript compilation successful");
77
+ }
78
+ }
79
+ catch (error) {
80
+ result.valid = false;
81
+ result.errors.push(`Compilation check failed: ${error instanceof Error ? error.message : String(error)}`);
82
+ }
83
+ return result;
84
+ }
85
+ export function validateFunctionFirstStructure(context) {
86
+ const result = {
87
+ valid: true,
88
+ errors: [],
89
+ warnings: [],
90
+ suggestions: [],
91
+ };
92
+ const content = context.content;
93
+ if (!content.includes("import") || !content.includes("defineNode")) {
94
+ result.valid = false;
95
+ result.errors.push("Missing 'defineNode' import from '@blok/runner'");
96
+ }
97
+ if (!content.includes("import") || !content.includes("zod")) {
98
+ result.valid = false;
99
+ result.errors.push("Missing 'zod' import - required for schema validation");
100
+ }
101
+ if (!content.includes("defineNode({")) {
102
+ result.valid = false;
103
+ result.errors.push("Missing 'defineNode()' call");
104
+ }
105
+ const requiredProps = ["name:", "description:", "input:", "output:", "execute"];
106
+ for (const prop of requiredProps) {
107
+ if (!content.includes(prop)) {
108
+ result.valid = false;
109
+ result.errors.push(`Missing required property: ${prop.replace(":", "")}`);
110
+ }
111
+ }
112
+ if (!content.includes("z.object(")) {
113
+ result.warnings.push("No Zod schema detected - ensure input/output use z.object()");
114
+ }
115
+ if (!content.includes("async execute") && !content.includes("async (ctx")) {
116
+ result.warnings.push("Execute function should be async for consistent error handling");
117
+ }
118
+ if (!content.includes("Context") && !content.includes("ctx")) {
119
+ result.warnings.push("Context parameter (ctx) not found in execute function");
120
+ }
121
+ if (result.valid) {
122
+ result.suggestions.push("✅ Function-first structure looks good");
123
+ }
124
+ return result;
125
+ }
126
+ export function validateClassBasedStructure(context) {
127
+ const result = {
128
+ valid: true,
129
+ errors: [],
130
+ warnings: [],
131
+ suggestions: [],
132
+ };
133
+ const content = context.content;
134
+ if (!content.includes("BlokService")) {
135
+ result.valid = false;
136
+ result.errors.push("Missing 'BlokService' import from '@blok/runner'");
137
+ }
138
+ if (!content.includes("class ") || !content.includes("extends BlokService")) {
139
+ result.valid = false;
140
+ result.errors.push("Missing class declaration extending BlokService");
141
+ }
142
+ if (!content.includes("async handle(")) {
143
+ result.valid = false;
144
+ result.errors.push("Missing 'async handle()' method");
145
+ }
146
+ if (!content.includes("inputSchema") || !content.includes("outputSchema")) {
147
+ result.warnings.push("Missing inputSchema or outputSchema in constructor");
148
+ }
149
+ if (result.valid) {
150
+ result.suggestions.push("✅ Class-based structure looks good");
151
+ result.suggestions.push("💡 Consider migrating to function-first pattern for better DX");
152
+ }
153
+ return result;
154
+ }
155
+ export function validateExports(context) {
156
+ const result = {
157
+ valid: true,
158
+ errors: [],
159
+ warnings: [],
160
+ suggestions: [],
161
+ };
162
+ const content = context.content;
163
+ if (!content.includes("export default")) {
164
+ result.valid = false;
165
+ result.errors.push("Missing default export");
166
+ }
167
+ if (result.valid) {
168
+ result.suggestions.push("✅ Export structure is correct");
169
+ }
170
+ return result;
171
+ }
172
+ export async function quickValidate(filePath) {
173
+ try {
174
+ const content = fs.readFileSync(filePath, "utf-8");
175
+ const context = {
176
+ filePath,
177
+ nodeStyle: content.includes("defineNode") ? "function" : "class",
178
+ content,
179
+ };
180
+ const result = await validate(context);
181
+ return result.valid;
182
+ }
183
+ catch {
184
+ return false;
185
+ }
186
+ }
187
+ export function formatResult(result) {
188
+ const lines = [];
189
+ if (result.errors.length > 0) {
190
+ lines.push("❌ Errors:");
191
+ for (const err of result.errors) {
192
+ lines.push(` - ${err}`);
193
+ }
194
+ lines.push("");
195
+ }
196
+ if (result.warnings.length > 0) {
197
+ lines.push("⚠️ Warnings:");
198
+ for (const warn of result.warnings) {
199
+ lines.push(` - ${warn}`);
200
+ }
201
+ lines.push("");
202
+ }
203
+ if (result.suggestions.length > 0) {
204
+ lines.push("💡 Suggestions:");
205
+ for (const sug of result.suggestions) {
206
+ lines.push(` - ${sug}`);
207
+ }
208
+ lines.push("");
209
+ }
210
+ if (result.valid) {
211
+ lines.push("✅ Validation passed!");
212
+ }
213
+ else {
214
+ lines.push("❌ Validation failed. Please fix the errors above.");
215
+ }
216
+ return lines.join("\n");
217
+ }
@@ -0,0 +1,281 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { validateExports, validateFunctionFirstStructure } from "./NodeValidator.js";
3
+ describe("NodeValidator", () => {
4
+ describe("validateFunctionFirstStructure", () => {
5
+ it("should pass for valid function-first node", () => {
6
+ const validCode = `
7
+ import { defineNode } from "@blok/runner";
8
+ import { z } from "zod";
9
+
10
+ export default defineNode({
11
+ name: "test-node",
12
+ description: "A test node",
13
+
14
+ input: z.object({
15
+ userId: z.string(),
16
+ }),
17
+
18
+ output: z.string(),
19
+
20
+ async execute(ctx, input) {
21
+ return input.userId;
22
+ },
23
+ });
24
+ `;
25
+ const context = {
26
+ filePath: "/tmp/test-node.ts",
27
+ nodeStyle: "function",
28
+ content: validCode,
29
+ };
30
+ const result = validateFunctionFirstStructure(context);
31
+ expect(result.valid).toBe(true);
32
+ expect(result.errors.length).toBe(0);
33
+ });
34
+ it("should fail for class-based node (old pattern)", () => {
35
+ const classBasedCode = `
36
+ import { BlokService } from "@blok/runner";
37
+
38
+ export default class TestNode extends BlokService {
39
+ async handle(ctx: any, input: any) {
40
+ return { success: true };
41
+ }
42
+ }
43
+ `;
44
+ const context = {
45
+ filePath: "/tmp/class-node.ts",
46
+ nodeStyle: "function",
47
+ content: classBasedCode,
48
+ };
49
+ const result = validateFunctionFirstStructure(context);
50
+ expect(result.valid).toBe(false);
51
+ expect(result.errors).toBeDefined();
52
+ expect(result.errors.some((err) => err.includes("defineNode"))).toBe(true);
53
+ });
54
+ it("should fail for node missing execute function", () => {
55
+ const incompleteCode = `
56
+ import { defineNode } from "@blok/runner";
57
+ import { z } from "zod";
58
+
59
+ export default defineNode({
60
+ name: "incomplete-node",
61
+ description: "A node without implementation",
62
+
63
+ input: z.string(),
64
+ output: z.string(),
65
+ });
66
+ `;
67
+ const context = {
68
+ filePath: "/tmp/incomplete-node.ts",
69
+ nodeStyle: "function",
70
+ content: incompleteCode,
71
+ };
72
+ const result = validateFunctionFirstStructure(context);
73
+ expect(result.valid).toBe(false);
74
+ expect(result.errors).toBeDefined();
75
+ expect(result.errors.some((err) => err.includes("execute"))).toBe(true);
76
+ });
77
+ it("should fail for node missing name property", () => {
78
+ const invalidCode = `
79
+ import { defineNode } from "@blok/runner";
80
+ import { z } from "zod";
81
+
82
+ export default defineNode({
83
+ description: "Missing name property",
84
+
85
+ input: z.string(),
86
+ output: z.string(),
87
+
88
+ async execute(ctx, input) {
89
+ return input;
90
+ },
91
+ });
92
+ `;
93
+ const context = {
94
+ filePath: "/tmp/no-name-node.ts",
95
+ nodeStyle: "function",
96
+ content: invalidCode,
97
+ };
98
+ const result = validateFunctionFirstStructure(context);
99
+ expect(result.valid).toBe(false);
100
+ expect(result.errors).toBeDefined();
101
+ expect(result.errors.some((err) => err.includes("name"))).toBe(true);
102
+ });
103
+ it("should fail for node missing description property", () => {
104
+ const invalidCode = `
105
+ import { defineNode } from "@blok/runner";
106
+ import { z } from "zod";
107
+
108
+ export default defineNode({
109
+ name: "no-description",
110
+
111
+ input: z.string(),
112
+ output: z.string(),
113
+
114
+ async execute(ctx, input) {
115
+ return input;
116
+ },
117
+ });
118
+ `;
119
+ const context = {
120
+ filePath: "/tmp/no-desc-node.ts",
121
+ nodeStyle: "function",
122
+ content: invalidCode,
123
+ };
124
+ const result = validateFunctionFirstStructure(context);
125
+ expect(result.valid).toBe(false);
126
+ expect(result.errors).toBeDefined();
127
+ expect(result.errors.some((err) => err.includes("description"))).toBe(true);
128
+ });
129
+ it("should fail for node missing input schema", () => {
130
+ const invalidCode = `
131
+ import { defineNode } from "@blok/runner";
132
+ import { z } from "zod";
133
+
134
+ export default defineNode({
135
+ name: "no-input",
136
+ description: "Missing input schema",
137
+
138
+ output: z.string(),
139
+
140
+ async execute(ctx, input) {
141
+ return "test";
142
+ },
143
+ });
144
+ `;
145
+ const context = {
146
+ filePath: "/tmp/no-input-node.ts",
147
+ nodeStyle: "function",
148
+ content: invalidCode,
149
+ };
150
+ const result = validateFunctionFirstStructure(context);
151
+ expect(result.valid).toBe(false);
152
+ expect(result.errors).toBeDefined();
153
+ expect(result.errors.some((err) => err.includes("input"))).toBe(true);
154
+ });
155
+ it("should fail for node missing output schema", () => {
156
+ const invalidCode = `
157
+ import { defineNode } from "@blok/runner";
158
+ import { z } from "zod";
159
+
160
+ export default defineNode({
161
+ name: "no-output",
162
+ description: "Missing output schema",
163
+
164
+ input: z.string(),
165
+
166
+ async execute(ctx, input) {
167
+ return input;
168
+ },
169
+ });
170
+ `;
171
+ const context = {
172
+ filePath: "/tmp/no-output-node.ts",
173
+ nodeStyle: "function",
174
+ content: invalidCode,
175
+ };
176
+ const result = validateFunctionFirstStructure(context);
177
+ expect(result.valid).toBe(false);
178
+ expect(result.errors).toBeDefined();
179
+ expect(result.errors.some((err) => err.includes("output"))).toBe(true);
180
+ });
181
+ it("should pass for node with complex input/output schemas", () => {
182
+ const validCode = `
183
+ import { defineNode } from "@blok/runner";
184
+ import { z } from "zod";
185
+
186
+ export default defineNode({
187
+ name: "complex-node",
188
+ description: "Node with complex schemas",
189
+
190
+ input: z.object({
191
+ user: z.object({
192
+ id: z.string().uuid(),
193
+ email: z.string().email(),
194
+ age: z.number().int().positive(),
195
+ }),
196
+ filters: z.array(z.string()).optional(),
197
+ metadata: z.record(z.unknown()),
198
+ }),
199
+
200
+ output: z.union([
201
+ z.object({
202
+ success: z.literal(true),
203
+ data: z.unknown(),
204
+ }),
205
+ z.object({
206
+ success: z.literal(false),
207
+ error: z.string(),
208
+ }),
209
+ ]),
210
+
211
+ async execute(ctx, input) {
212
+ return {
213
+ success: true,
214
+ data: input.user,
215
+ };
216
+ },
217
+ });
218
+ `;
219
+ const context = {
220
+ filePath: "/tmp/complex-node.ts",
221
+ nodeStyle: "function",
222
+ content: validCode,
223
+ };
224
+ const result = validateFunctionFirstStructure(context);
225
+ expect(result.valid).toBe(true);
226
+ });
227
+ });
228
+ describe("validateExports", () => {
229
+ it("should pass for valid default export", () => {
230
+ const validCode = `
231
+ import { defineNode } from "@blok/runner";
232
+ import { z } from "zod";
233
+
234
+ export default defineNode({
235
+ name: "test",
236
+ description: "Test",
237
+ input: z.string(),
238
+ output: z.string(),
239
+ async execute(ctx, input) {
240
+ return input;
241
+ },
242
+ });
243
+ `;
244
+ const context = {
245
+ filePath: "/tmp/export-test.ts",
246
+ nodeStyle: "function",
247
+ content: validCode,
248
+ };
249
+ const result = validateExports(context);
250
+ expect(result.valid).toBe(true);
251
+ });
252
+ it("should fail for missing default export", () => {
253
+ const invalidCode = `
254
+ import { defineNode } from "@blok/runner";
255
+ import { z } from "zod";
256
+
257
+ const myNode = defineNode({
258
+ name: "test",
259
+ description: "Test",
260
+ input: z.string(),
261
+ output: z.string(),
262
+ async execute(ctx, input) {
263
+ return input;
264
+ },
265
+ });
266
+
267
+ // No default export
268
+ export { myNode };
269
+ `;
270
+ const context = {
271
+ filePath: "/tmp/no-export-node.ts",
272
+ nodeStyle: "function",
273
+ content: invalidCode,
274
+ };
275
+ const result = validateExports(context);
276
+ expect(result.valid).toBe(false);
277
+ expect(result.errors).toBeDefined();
278
+ expect(result.errors.some((err) => err.includes("default export"))).toBe(true);
279
+ });
280
+ });
281
+ });
@@ -0,0 +1,6 @@
1
+ export interface WorkflowValidationResult {
2
+ valid: boolean;
3
+ errors: string[];
4
+ warnings: string[];
5
+ }
6
+ export declare function validateWorkflow(jsonString: string): WorkflowValidationResult;