testblocks 0.1.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 (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +333 -0
  3. package/dist/cli/executor.d.ts +32 -0
  4. package/dist/cli/executor.js +517 -0
  5. package/dist/cli/index.d.ts +2 -0
  6. package/dist/cli/index.js +411 -0
  7. package/dist/cli/reporters.d.ts +62 -0
  8. package/dist/cli/reporters.js +451 -0
  9. package/dist/client/assets/index-4hbFPUhP.js +2087 -0
  10. package/dist/client/assets/index-4hbFPUhP.js.map +1 -0
  11. package/dist/client/assets/index-Dnk1ti7l.css +1 -0
  12. package/dist/client/index.html +25 -0
  13. package/dist/core/blocks/api.d.ts +2 -0
  14. package/dist/core/blocks/api.js +610 -0
  15. package/dist/core/blocks/data-driven.d.ts +2 -0
  16. package/dist/core/blocks/data-driven.js +245 -0
  17. package/dist/core/blocks/index.d.ts +15 -0
  18. package/dist/core/blocks/index.js +71 -0
  19. package/dist/core/blocks/lifecycle.d.ts +2 -0
  20. package/dist/core/blocks/lifecycle.js +199 -0
  21. package/dist/core/blocks/logic.d.ts +2 -0
  22. package/dist/core/blocks/logic.js +357 -0
  23. package/dist/core/blocks/playwright.d.ts +2 -0
  24. package/dist/core/blocks/playwright.js +764 -0
  25. package/dist/core/blocks/procedures.d.ts +5 -0
  26. package/dist/core/blocks/procedures.js +321 -0
  27. package/dist/core/index.d.ts +5 -0
  28. package/dist/core/index.js +44 -0
  29. package/dist/core/plugins.d.ts +66 -0
  30. package/dist/core/plugins.js +118 -0
  31. package/dist/core/types.d.ts +153 -0
  32. package/dist/core/types.js +2 -0
  33. package/dist/server/codegenManager.d.ts +54 -0
  34. package/dist/server/codegenManager.js +259 -0
  35. package/dist/server/codegenParser.d.ts +17 -0
  36. package/dist/server/codegenParser.js +598 -0
  37. package/dist/server/executor.d.ts +37 -0
  38. package/dist/server/executor.js +672 -0
  39. package/dist/server/globals.d.ts +85 -0
  40. package/dist/server/globals.js +273 -0
  41. package/dist/server/index.d.ts +2 -0
  42. package/dist/server/index.js +361 -0
  43. package/dist/server/plugins.d.ts +55 -0
  44. package/dist/server/plugins.js +206 -0
  45. package/package.json +103 -0
@@ -0,0 +1,5 @@
1
+ import { BlockDefinition, ProcedureDefinition } from '../types';
2
+ export declare function registerProcedure(name: string, procedure: ProcedureDefinition): void;
3
+ export declare function getProcedure(name: string): ProcedureDefinition | undefined;
4
+ export declare function clearProcedures(): void;
5
+ export declare const procedureBlocks: BlockDefinition[];
@@ -0,0 +1,321 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.procedureBlocks = void 0;
4
+ exports.registerProcedure = registerProcedure;
5
+ exports.getProcedure = getProcedure;
6
+ exports.clearProcedures = clearProcedures;
7
+ // Procedure registry for runtime
8
+ const procedureRegistry = new Map();
9
+ function registerProcedure(name, procedure) {
10
+ procedureRegistry.set(name, procedure);
11
+ }
12
+ function getProcedure(name) {
13
+ return procedureRegistry.get(name);
14
+ }
15
+ function clearProcedures() {
16
+ procedureRegistry.clear();
17
+ }
18
+ // Procedure Blocks - Custom Reusable Actions
19
+ exports.procedureBlocks = [
20
+ // Define a procedure (function)
21
+ {
22
+ type: 'procedure_define',
23
+ category: 'Procedures',
24
+ color: '#9C27B0',
25
+ tooltip: 'Define a reusable procedure with parameters',
26
+ inputs: [
27
+ { name: 'NAME', type: 'field', fieldType: 'text', required: true },
28
+ { name: 'DESCRIPTION', type: 'field', fieldType: 'text', default: '' },
29
+ { name: 'PARAMS', type: 'field', fieldType: 'text', default: '' }, // comma-separated: "username, password, timeout"
30
+ { name: 'DO', type: 'statement' },
31
+ ],
32
+ execute: async (params, context) => {
33
+ const name = params.NAME;
34
+ const description = params.DESCRIPTION;
35
+ const paramsStr = params.PARAMS;
36
+ // Parse parameters
37
+ const procedureParams = paramsStr
38
+ .split(',')
39
+ .map(p => p.trim())
40
+ .filter(p => p)
41
+ .map(p => {
42
+ // Support type annotations: "username:string", "count:number"
43
+ const [paramName, paramType] = p.split(':').map(s => s.trim());
44
+ return {
45
+ name: paramName,
46
+ type: paramType || 'any',
47
+ };
48
+ });
49
+ // Register the procedure
50
+ const procedure = {
51
+ name,
52
+ description,
53
+ params: procedureParams,
54
+ steps: [], // Steps will be extracted by the executor
55
+ };
56
+ registerProcedure(name, procedure);
57
+ if (context.procedures) {
58
+ context.procedures.set(name, procedure);
59
+ }
60
+ context.logger.debug(`Defined procedure: ${name}(${procedureParams.map(p => p.name).join(', ')})`);
61
+ return { procedureDefine: true, name, procedure };
62
+ },
63
+ },
64
+ // Call a procedure
65
+ {
66
+ type: 'procedure_call',
67
+ category: 'Procedures',
68
+ color: '#9C27B0',
69
+ tooltip: 'Call a defined procedure',
70
+ inputs: [
71
+ { name: 'NAME', type: 'field', fieldType: 'text', required: true },
72
+ { name: 'ARGS', type: 'field', fieldType: 'text', default: '' }, // JSON object or comma-separated values
73
+ ],
74
+ previousStatement: true,
75
+ nextStatement: true,
76
+ execute: async (params, context) => {
77
+ const name = params.NAME;
78
+ const argsStr = resolveVariables(params.ARGS, context);
79
+ // Look up procedure
80
+ const procedure = context.procedures?.get(name) || getProcedure(name);
81
+ if (!procedure) {
82
+ throw new Error(`Procedure not found: ${name}`);
83
+ }
84
+ // Parse arguments
85
+ let args = {};
86
+ if (argsStr.trim()) {
87
+ try {
88
+ // Try JSON format first: {"username": "test", "password": "123"}
89
+ args = JSON.parse(argsStr);
90
+ }
91
+ catch {
92
+ // Fall back to comma-separated values matching parameter order
93
+ const values = argsStr.split(',').map(v => {
94
+ const trimmed = v.trim();
95
+ try {
96
+ return JSON.parse(trimmed);
97
+ }
98
+ catch {
99
+ return trimmed;
100
+ }
101
+ });
102
+ if (procedure.params) {
103
+ procedure.params.forEach((param, index) => {
104
+ if (index < values.length) {
105
+ args[param.name] = values[index];
106
+ }
107
+ else if (param.default !== undefined) {
108
+ args[param.name] = param.default;
109
+ }
110
+ });
111
+ }
112
+ }
113
+ }
114
+ context.logger.info(`Calling procedure: ${name}`);
115
+ return { procedureCall: true, name, args, procedure };
116
+ },
117
+ },
118
+ // Call procedure with return value
119
+ {
120
+ type: 'procedure_call_with_return',
121
+ category: 'Procedures',
122
+ color: '#9C27B0',
123
+ tooltip: 'Call a procedure and get return value',
124
+ inputs: [
125
+ { name: 'NAME', type: 'field', fieldType: 'text', required: true },
126
+ { name: 'ARGS', type: 'field', fieldType: 'text', default: '' },
127
+ ],
128
+ output: { type: ['String', 'Number', 'Boolean', 'Object', 'Array'] },
129
+ execute: async (params, context) => {
130
+ const name = params.NAME;
131
+ const argsStr = resolveVariables(params.ARGS, context);
132
+ const procedure = context.procedures?.get(name) || getProcedure(name);
133
+ if (!procedure) {
134
+ throw new Error(`Procedure not found: ${name}`);
135
+ }
136
+ let args = {};
137
+ if (argsStr.trim()) {
138
+ try {
139
+ args = JSON.parse(argsStr);
140
+ }
141
+ catch {
142
+ const values = argsStr.split(',').map(v => v.trim());
143
+ if (procedure.params) {
144
+ procedure.params.forEach((param, index) => {
145
+ if (index < values.length) {
146
+ args[param.name] = values[index];
147
+ }
148
+ });
149
+ }
150
+ }
151
+ }
152
+ return { procedureCall: true, name, args, procedure, expectReturn: true };
153
+ },
154
+ },
155
+ // Return from procedure
156
+ {
157
+ type: 'procedure_return',
158
+ category: 'Procedures',
159
+ color: '#9C27B0',
160
+ tooltip: 'Return a value from a procedure',
161
+ inputs: [
162
+ { name: 'VALUE', type: 'value' },
163
+ ],
164
+ previousStatement: true,
165
+ execute: async (params, _context) => {
166
+ const value = params.VALUE;
167
+ return { procedureReturn: true, value };
168
+ },
169
+ },
170
+ // Get procedure parameter
171
+ {
172
+ type: 'procedure_get_param',
173
+ category: 'Procedures',
174
+ color: '#9C27B0',
175
+ tooltip: 'Get a procedure parameter value',
176
+ inputs: [
177
+ { name: 'NAME', type: 'field', fieldType: 'text', required: true },
178
+ ],
179
+ output: { type: ['String', 'Number', 'Boolean', 'Object', 'Array'] },
180
+ execute: async (params, context) => {
181
+ const name = params.NAME;
182
+ // Parameters are stored in variables with a prefix
183
+ const value = context.variables.get(`__param_${name}`);
184
+ if (value === undefined) {
185
+ // Check regular variables as fallback
186
+ return context.variables.get(name);
187
+ }
188
+ return value;
189
+ },
190
+ },
191
+ // Define inline procedure (lambda-style)
192
+ {
193
+ type: 'procedure_inline',
194
+ category: 'Procedures',
195
+ color: '#9C27B0',
196
+ tooltip: 'Define and immediately use an inline procedure',
197
+ inputs: [
198
+ { name: 'PARAMS', type: 'field', fieldType: 'text', default: '' },
199
+ { name: 'DO', type: 'statement' },
200
+ ],
201
+ output: { type: 'Procedure' },
202
+ execute: async (params, _context) => {
203
+ return {
204
+ inlineProcedure: true,
205
+ params: params.PARAMS.split(',').map(p => p.trim()).filter(p => p),
206
+ statement: 'DO',
207
+ };
208
+ },
209
+ },
210
+ // Apply/Map procedure to array
211
+ {
212
+ type: 'procedure_map',
213
+ category: 'Procedures',
214
+ color: '#9C27B0',
215
+ tooltip: 'Apply a procedure to each item in an array',
216
+ inputs: [
217
+ { name: 'ARRAY', type: 'value', check: 'Array', required: true },
218
+ { name: 'PROCEDURE', type: 'field', fieldType: 'text', required: true },
219
+ { name: 'ITEM_PARAM', type: 'field', fieldType: 'text', default: 'item' },
220
+ ],
221
+ output: { type: 'Array' },
222
+ execute: async (params, _context) => {
223
+ const array = params.ARRAY;
224
+ const procedureName = params.PROCEDURE;
225
+ const itemParam = params.ITEM_PARAM;
226
+ return {
227
+ procedureMap: true,
228
+ array,
229
+ procedureName,
230
+ itemParam,
231
+ };
232
+ },
233
+ },
234
+ // Common action blocks - frequently used patterns
235
+ // Login procedure template
236
+ {
237
+ type: 'procedure_login',
238
+ category: 'Procedures',
239
+ color: '#AB47BC',
240
+ tooltip: 'Common login action with username and password',
241
+ inputs: [
242
+ { name: 'USERNAME_SELECTOR', type: 'field', fieldType: 'text', default: '#username' },
243
+ { name: 'PASSWORD_SELECTOR', type: 'field', fieldType: 'text', default: '#password' },
244
+ { name: 'SUBMIT_SELECTOR', type: 'field', fieldType: 'text', default: 'button[type="submit"]' },
245
+ { name: 'USERNAME', type: 'value', check: 'String', required: true },
246
+ { name: 'PASSWORD', type: 'value', check: 'String', required: true },
247
+ ],
248
+ previousStatement: true,
249
+ nextStatement: true,
250
+ execute: async (params, _context) => {
251
+ // This is a compound action that expands to multiple steps
252
+ return {
253
+ compoundAction: 'login',
254
+ steps: [
255
+ { type: 'web_fill', params: { SELECTOR: params.USERNAME_SELECTOR, VALUE: params.USERNAME } },
256
+ { type: 'web_fill', params: { SELECTOR: params.PASSWORD_SELECTOR, VALUE: params.PASSWORD } },
257
+ { type: 'web_click', params: { SELECTOR: params.SUBMIT_SELECTOR } },
258
+ ],
259
+ };
260
+ },
261
+ },
262
+ // Wait for element and click
263
+ {
264
+ type: 'procedure_wait_and_click',
265
+ category: 'Procedures',
266
+ color: '#AB47BC',
267
+ tooltip: 'Wait for element to be visible then click',
268
+ inputs: [
269
+ { name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
270
+ { name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
271
+ ],
272
+ previousStatement: true,
273
+ nextStatement: true,
274
+ execute: async (params, _context) => {
275
+ return {
276
+ compoundAction: 'waitAndClick',
277
+ steps: [
278
+ { type: 'web_wait_for_element', params: { SELECTOR: params.SELECTOR, STATE: 'visible', TIMEOUT: params.TIMEOUT } },
279
+ { type: 'web_click', params: { SELECTOR: params.SELECTOR } },
280
+ ],
281
+ };
282
+ },
283
+ },
284
+ // Fill form fields from object
285
+ {
286
+ type: 'procedure_fill_form',
287
+ category: 'Procedures',
288
+ color: '#AB47BC',
289
+ tooltip: 'Fill multiple form fields from a data object',
290
+ inputs: [
291
+ { name: 'FIELDS', type: 'value', check: 'Object', required: true },
292
+ ],
293
+ previousStatement: true,
294
+ nextStatement: true,
295
+ execute: async (params, _context) => {
296
+ const fields = params.FIELDS;
297
+ const steps = Object.entries(fields).map(([selector, value]) => ({
298
+ type: 'web_fill',
299
+ params: { SELECTOR: selector, VALUE: value },
300
+ }));
301
+ return { compoundAction: 'fillForm', steps };
302
+ },
303
+ },
304
+ ];
305
+ // Helper function
306
+ function resolveVariables(text, context) {
307
+ return text.replace(/\$\{(\w+)\}/g, (_, varName) => {
308
+ // Check param prefix first
309
+ const paramValue = context.variables.get(`__param_${varName}`);
310
+ if (paramValue !== undefined) {
311
+ return String(paramValue);
312
+ }
313
+ // Then check current data
314
+ if (context.currentData?.values[varName] !== undefined) {
315
+ return String(context.currentData.values[varName]);
316
+ }
317
+ // Then regular variables
318
+ const value = context.variables.get(varName);
319
+ return value !== undefined ? String(value) : '';
320
+ });
321
+ }
@@ -0,0 +1,5 @@
1
+ export * from './types';
2
+ export * from './blocks';
3
+ export * from './plugins';
4
+ export { builtInBlocks, blockRegistry, registerBlock, registerBlocks, getBlock, getAllBlocks, getBlocksByCategory, getCategories, } from './blocks';
5
+ export { registerPlugin, getPlugin, getAllPlugins, getAllPluginBlocks, unregisterPlugin, createPlugin, createBlock, createActionBlock, createValueBlock, createAssertionBlock, } from './plugins';
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.createAssertionBlock = exports.createValueBlock = exports.createActionBlock = exports.createBlock = exports.createPlugin = exports.unregisterPlugin = exports.getAllPluginBlocks = exports.getAllPlugins = exports.getPlugin = exports.registerPlugin = exports.getCategories = exports.getBlocksByCategory = exports.getAllBlocks = exports.getBlock = exports.registerBlocks = exports.registerBlock = exports.blockRegistry = exports.builtInBlocks = void 0;
18
+ // Types
19
+ __exportStar(require("./types"), exports);
20
+ // Blocks
21
+ __exportStar(require("./blocks"), exports);
22
+ // Plugins
23
+ __exportStar(require("./plugins"), exports);
24
+ // Re-export specific items for convenience
25
+ var blocks_1 = require("./blocks");
26
+ Object.defineProperty(exports, "builtInBlocks", { enumerable: true, get: function () { return blocks_1.builtInBlocks; } });
27
+ Object.defineProperty(exports, "blockRegistry", { enumerable: true, get: function () { return blocks_1.blockRegistry; } });
28
+ Object.defineProperty(exports, "registerBlock", { enumerable: true, get: function () { return blocks_1.registerBlock; } });
29
+ Object.defineProperty(exports, "registerBlocks", { enumerable: true, get: function () { return blocks_1.registerBlocks; } });
30
+ Object.defineProperty(exports, "getBlock", { enumerable: true, get: function () { return blocks_1.getBlock; } });
31
+ Object.defineProperty(exports, "getAllBlocks", { enumerable: true, get: function () { return blocks_1.getAllBlocks; } });
32
+ Object.defineProperty(exports, "getBlocksByCategory", { enumerable: true, get: function () { return blocks_1.getBlocksByCategory; } });
33
+ Object.defineProperty(exports, "getCategories", { enumerable: true, get: function () { return blocks_1.getCategories; } });
34
+ var plugins_1 = require("./plugins");
35
+ Object.defineProperty(exports, "registerPlugin", { enumerable: true, get: function () { return plugins_1.registerPlugin; } });
36
+ Object.defineProperty(exports, "getPlugin", { enumerable: true, get: function () { return plugins_1.getPlugin; } });
37
+ Object.defineProperty(exports, "getAllPlugins", { enumerable: true, get: function () { return plugins_1.getAllPlugins; } });
38
+ Object.defineProperty(exports, "getAllPluginBlocks", { enumerable: true, get: function () { return plugins_1.getAllPluginBlocks; } });
39
+ Object.defineProperty(exports, "unregisterPlugin", { enumerable: true, get: function () { return plugins_1.unregisterPlugin; } });
40
+ Object.defineProperty(exports, "createPlugin", { enumerable: true, get: function () { return plugins_1.createPlugin; } });
41
+ Object.defineProperty(exports, "createBlock", { enumerable: true, get: function () { return plugins_1.createBlock; } });
42
+ Object.defineProperty(exports, "createActionBlock", { enumerable: true, get: function () { return plugins_1.createActionBlock; } });
43
+ Object.defineProperty(exports, "createValueBlock", { enumerable: true, get: function () { return plugins_1.createValueBlock; } });
44
+ Object.defineProperty(exports, "createAssertionBlock", { enumerable: true, get: function () { return plugins_1.createAssertionBlock; } });
@@ -0,0 +1,66 @@
1
+ import { Plugin, BlockDefinition, ExecutionContext } from './types';
2
+ export declare function registerPlugin(plugin: Plugin): void;
3
+ export declare function getPlugin(name: string): Plugin | undefined;
4
+ export declare function getAllPlugins(): Plugin[];
5
+ export declare function getAllPluginBlocks(): BlockDefinition[];
6
+ export declare function unregisterPlugin(name: string): boolean;
7
+ export declare function createPlugin(config: {
8
+ name: string;
9
+ version: string;
10
+ description?: string;
11
+ blocks?: BlockDefinition[];
12
+ hooks?: Plugin['hooks'];
13
+ }): Plugin;
14
+ export declare function createBlock(config: {
15
+ type: string;
16
+ category: string;
17
+ color?: string;
18
+ tooltip?: string;
19
+ helpUrl?: string;
20
+ inputs: BlockDefinition['inputs'];
21
+ output?: BlockDefinition['output'];
22
+ previousStatement?: boolean;
23
+ nextStatement?: boolean;
24
+ execute: BlockDefinition['execute'];
25
+ }): BlockDefinition;
26
+ /**
27
+ * Create an action block - executes an action, can be chained with other blocks
28
+ * Has previous/next statements, no output value
29
+ */
30
+ export declare function createActionBlock(config: {
31
+ type: string;
32
+ category: string;
33
+ color?: string;
34
+ tooltip?: string;
35
+ inputs: BlockDefinition['inputs'];
36
+ execute: (params: Record<string, unknown>, context: ExecutionContext) => Promise<unknown>;
37
+ }): BlockDefinition;
38
+ /**
39
+ * Create a value block - returns a value that can be used as input to other blocks
40
+ * Has output, no previous/next statements
41
+ */
42
+ export declare function createValueBlock(config: {
43
+ type: string;
44
+ category: string;
45
+ color?: string;
46
+ tooltip?: string;
47
+ inputs: BlockDefinition['inputs'];
48
+ outputType: string | string[];
49
+ execute: (params: Record<string, unknown>, context: ExecutionContext) => Promise<unknown>;
50
+ }): BlockDefinition;
51
+ /**
52
+ * Create an assertion block - validates a condition
53
+ * Throws an error if assertion fails
54
+ */
55
+ export declare function createAssertionBlock(config: {
56
+ type: string;
57
+ category?: string;
58
+ color?: string;
59
+ tooltip?: string;
60
+ inputs: BlockDefinition['inputs'];
61
+ assert: (params: Record<string, unknown>, context: ExecutionContext) => Promise<{
62
+ pass: boolean;
63
+ message?: string;
64
+ }>;
65
+ }): BlockDefinition;
66
+ export type { Plugin, BlockDefinition, ExecutionContext };
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerPlugin = registerPlugin;
4
+ exports.getPlugin = getPlugin;
5
+ exports.getAllPlugins = getAllPlugins;
6
+ exports.getAllPluginBlocks = getAllPluginBlocks;
7
+ exports.unregisterPlugin = unregisterPlugin;
8
+ exports.createPlugin = createPlugin;
9
+ exports.createBlock = createBlock;
10
+ exports.createActionBlock = createActionBlock;
11
+ exports.createValueBlock = createValueBlock;
12
+ exports.createAssertionBlock = createAssertionBlock;
13
+ const blocks_1 = require("./blocks");
14
+ // Plugin registry
15
+ const pluginRegistry = new Map();
16
+ // Register a plugin
17
+ function registerPlugin(plugin) {
18
+ if (pluginRegistry.has(plugin.name)) {
19
+ console.warn(`Plugin "${plugin.name}" is already registered. Overwriting...`);
20
+ }
21
+ pluginRegistry.set(plugin.name, plugin);
22
+ // Register the plugin's blocks
23
+ if (plugin.blocks && plugin.blocks.length > 0) {
24
+ (0, blocks_1.registerBlocks)(plugin.blocks);
25
+ }
26
+ console.log(`Plugin registered: ${plugin.name} v${plugin.version}`);
27
+ }
28
+ // Get a registered plugin
29
+ function getPlugin(name) {
30
+ return pluginRegistry.get(name);
31
+ }
32
+ // Get all registered plugins
33
+ function getAllPlugins() {
34
+ return Array.from(pluginRegistry.values());
35
+ }
36
+ // Get all plugin blocks
37
+ function getAllPluginBlocks() {
38
+ const blocks = [];
39
+ for (const plugin of pluginRegistry.values()) {
40
+ blocks.push(...plugin.blocks);
41
+ }
42
+ return blocks;
43
+ }
44
+ // Unregister a plugin
45
+ function unregisterPlugin(name) {
46
+ return pluginRegistry.delete(name);
47
+ }
48
+ // Create a plugin helper
49
+ function createPlugin(config) {
50
+ return {
51
+ name: config.name,
52
+ version: config.version,
53
+ description: config.description,
54
+ blocks: config.blocks || [],
55
+ hooks: config.hooks,
56
+ };
57
+ }
58
+ // Helper to create a custom block easily
59
+ function createBlock(config) {
60
+ return {
61
+ type: config.type,
62
+ category: config.category,
63
+ color: config.color || '#607D8B',
64
+ tooltip: config.tooltip,
65
+ helpUrl: config.helpUrl,
66
+ inputs: config.inputs,
67
+ output: config.output,
68
+ previousStatement: config.previousStatement,
69
+ nextStatement: config.nextStatement,
70
+ execute: config.execute,
71
+ };
72
+ }
73
+ /**
74
+ * Create an action block - executes an action, can be chained with other blocks
75
+ * Has previous/next statements, no output value
76
+ */
77
+ function createActionBlock(config) {
78
+ return createBlock({
79
+ ...config,
80
+ previousStatement: true,
81
+ nextStatement: true,
82
+ });
83
+ }
84
+ /**
85
+ * Create a value block - returns a value that can be used as input to other blocks
86
+ * Has output, no previous/next statements
87
+ */
88
+ function createValueBlock(config) {
89
+ return createBlock({
90
+ ...config,
91
+ output: { type: config.outputType },
92
+ previousStatement: false,
93
+ nextStatement: false,
94
+ });
95
+ }
96
+ /**
97
+ * Create an assertion block - validates a condition
98
+ * Throws an error if assertion fails
99
+ */
100
+ function createAssertionBlock(config) {
101
+ return createBlock({
102
+ type: config.type,
103
+ category: config.category || 'Assertions',
104
+ color: config.color || '#E91E63',
105
+ tooltip: config.tooltip,
106
+ inputs: config.inputs,
107
+ previousStatement: true,
108
+ nextStatement: true,
109
+ execute: async (params, context) => {
110
+ const result = await config.assert(params, context);
111
+ if (!result.pass) {
112
+ throw new Error(result.message || `Assertion failed: ${config.type}`);
113
+ }
114
+ context.logger.info(`✓ ${config.tooltip || config.type}`);
115
+ return result;
116
+ },
117
+ });
118
+ }