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.
- package/LICENSE +21 -0
- package/README.md +333 -0
- package/dist/cli/executor.d.ts +32 -0
- package/dist/cli/executor.js +517 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +411 -0
- package/dist/cli/reporters.d.ts +62 -0
- package/dist/cli/reporters.js +451 -0
- package/dist/client/assets/index-4hbFPUhP.js +2087 -0
- package/dist/client/assets/index-4hbFPUhP.js.map +1 -0
- package/dist/client/assets/index-Dnk1ti7l.css +1 -0
- package/dist/client/index.html +25 -0
- package/dist/core/blocks/api.d.ts +2 -0
- package/dist/core/blocks/api.js +610 -0
- package/dist/core/blocks/data-driven.d.ts +2 -0
- package/dist/core/blocks/data-driven.js +245 -0
- package/dist/core/blocks/index.d.ts +15 -0
- package/dist/core/blocks/index.js +71 -0
- package/dist/core/blocks/lifecycle.d.ts +2 -0
- package/dist/core/blocks/lifecycle.js +199 -0
- package/dist/core/blocks/logic.d.ts +2 -0
- package/dist/core/blocks/logic.js +357 -0
- package/dist/core/blocks/playwright.d.ts +2 -0
- package/dist/core/blocks/playwright.js +764 -0
- package/dist/core/blocks/procedures.d.ts +5 -0
- package/dist/core/blocks/procedures.js +321 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +44 -0
- package/dist/core/plugins.d.ts +66 -0
- package/dist/core/plugins.js +118 -0
- package/dist/core/types.d.ts +153 -0
- package/dist/core/types.js +2 -0
- package/dist/server/codegenManager.d.ts +54 -0
- package/dist/server/codegenManager.js +259 -0
- package/dist/server/codegenParser.d.ts +17 -0
- package/dist/server/codegenParser.js +598 -0
- package/dist/server/executor.d.ts +37 -0
- package/dist/server/executor.js +672 -0
- package/dist/server/globals.d.ts +85 -0
- package/dist/server/globals.js +273 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +361 -0
- package/dist/server/plugins.d.ts +55 -0
- package/dist/server/plugins.js +206 -0
- package/package.json +103 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.TestExecutor = void 0;
|
|
7
|
+
const express_1 = __importDefault(require("express"));
|
|
8
|
+
const cors_1 = __importDefault(require("cors"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const executor_1 = require("./executor");
|
|
11
|
+
Object.defineProperty(exports, "TestExecutor", { enumerable: true, get: function () { return executor_1.TestExecutor; } });
|
|
12
|
+
const reporters_1 = require("../cli/reporters");
|
|
13
|
+
const plugins_1 = require("./plugins");
|
|
14
|
+
const globals_1 = require("./globals");
|
|
15
|
+
const codegenManager_1 = require("./codegenManager");
|
|
16
|
+
// Set plugins directory (default to examples/plugins or can be overridden via env)
|
|
17
|
+
const pluginsDir = process.env.PLUGINS_DIR || path_1.default.join(process.cwd(), 'examples', 'plugins');
|
|
18
|
+
(0, plugins_1.setPluginsDirectory)(pluginsDir);
|
|
19
|
+
// Set globals directory (default to examples or can be overridden via env)
|
|
20
|
+
const globalsDir = process.env.GLOBALS_DIR || path_1.default.join(process.cwd(), 'examples');
|
|
21
|
+
(0, globals_1.setGlobalsDirectory)(globalsDir);
|
|
22
|
+
// Load all discovered plugins on startup
|
|
23
|
+
(0, plugins_1.loadAllPlugins)().then(() => {
|
|
24
|
+
(0, plugins_1.initializeServerPlugins)();
|
|
25
|
+
// Load globals and snippets after plugins
|
|
26
|
+
(0, globals_1.initializeGlobalsAndSnippets)();
|
|
27
|
+
}).catch(err => {
|
|
28
|
+
console.error('Failed to load plugins:', err);
|
|
29
|
+
});
|
|
30
|
+
const app = (0, express_1.default)();
|
|
31
|
+
const PORT = process.env.PORT || 3001;
|
|
32
|
+
app.use((0, cors_1.default)());
|
|
33
|
+
app.use(express_1.default.json({ limit: '10mb' }));
|
|
34
|
+
// Health check
|
|
35
|
+
app.get('/api/health', (req, res) => {
|
|
36
|
+
res.json({ status: 'ok', version: '1.0.0' });
|
|
37
|
+
});
|
|
38
|
+
// List available plugins
|
|
39
|
+
app.get('/api/plugins', (req, res) => {
|
|
40
|
+
const available = (0, plugins_1.discoverPlugins)();
|
|
41
|
+
const loaded = (0, plugins_1.getServerPlugins)().map(p => ({
|
|
42
|
+
name: p.name,
|
|
43
|
+
version: p.version,
|
|
44
|
+
description: p.description,
|
|
45
|
+
blockCount: p.blocks.length,
|
|
46
|
+
}));
|
|
47
|
+
res.json({
|
|
48
|
+
directory: (0, plugins_1.getPluginsDirectory)(),
|
|
49
|
+
available,
|
|
50
|
+
loaded,
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
// Load specific plugins
|
|
54
|
+
app.post('/api/plugins/load', async (req, res) => {
|
|
55
|
+
try {
|
|
56
|
+
const { plugins } = req.body;
|
|
57
|
+
await (0, plugins_1.loadTestFilePlugins)(plugins);
|
|
58
|
+
res.json({ loaded: plugins });
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
res.status(500).json({ error: error.message });
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
// Get globals and snippets
|
|
65
|
+
app.get('/api/globals', (req, res) => {
|
|
66
|
+
const globals = (0, globals_1.getGlobals)();
|
|
67
|
+
const snippets = (0, globals_1.getAllSnippets)().map(s => ({
|
|
68
|
+
name: s.name,
|
|
69
|
+
description: s.description,
|
|
70
|
+
category: s.category,
|
|
71
|
+
params: s.params,
|
|
72
|
+
stepCount: s.steps.length,
|
|
73
|
+
}));
|
|
74
|
+
res.json({
|
|
75
|
+
directory: (0, globals_1.getGlobalsDirectory)(),
|
|
76
|
+
globals,
|
|
77
|
+
snippets,
|
|
78
|
+
testIdAttribute: (0, globals_1.getTestIdAttribute)(),
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
// Update test ID attribute
|
|
82
|
+
app.put('/api/globals/test-id-attribute', (req, res) => {
|
|
83
|
+
const { testIdAttribute } = req.body;
|
|
84
|
+
if (!testIdAttribute || typeof testIdAttribute !== 'string') {
|
|
85
|
+
return res.status(400).json({ error: 'testIdAttribute is required and must be a string' });
|
|
86
|
+
}
|
|
87
|
+
(0, globals_1.setTestIdAttribute)(testIdAttribute);
|
|
88
|
+
res.json({ testIdAttribute: (0, globals_1.getTestIdAttribute)() });
|
|
89
|
+
});
|
|
90
|
+
// Run tests
|
|
91
|
+
app.post('/api/run', async (req, res) => {
|
|
92
|
+
try {
|
|
93
|
+
const testFile = req.body;
|
|
94
|
+
if (!testFile || !testFile.tests) {
|
|
95
|
+
return res.status(400).json({ error: 'Invalid test file format' });
|
|
96
|
+
}
|
|
97
|
+
console.log(`Running ${testFile.tests.length} tests from "${testFile.name}"...`);
|
|
98
|
+
// Merge global variables with test file variables
|
|
99
|
+
const globalVars = (0, globals_1.getGlobalVariables)();
|
|
100
|
+
const testIdAttr = (0, globals_1.getTestIdAttribute)();
|
|
101
|
+
const executor = new executor_1.TestExecutor({
|
|
102
|
+
headless: req.query.headless !== 'false',
|
|
103
|
+
timeout: Number(req.query.timeout) || 30000,
|
|
104
|
+
variables: globalVars, // Pass global variables
|
|
105
|
+
testIdAttribute: testIdAttr, // Pass test ID attribute
|
|
106
|
+
baseDir: globalsDir, // Base directory for resolving relative file paths
|
|
107
|
+
});
|
|
108
|
+
const results = await executor.runTestFile(testFile);
|
|
109
|
+
const passed = results.filter(r => r.status === 'passed').length;
|
|
110
|
+
const failed = results.filter(r => r.status === 'failed').length;
|
|
111
|
+
console.log(`Results: ${passed} passed, ${failed} failed`);
|
|
112
|
+
res.json(results);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
const err = error;
|
|
116
|
+
console.error('Test execution failed:', err.message);
|
|
117
|
+
console.error('Stack:', err.stack);
|
|
118
|
+
res.status(500).json({
|
|
119
|
+
error: 'Test execution failed',
|
|
120
|
+
message: err.message,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
// Run a single test
|
|
125
|
+
app.post('/api/run/:testId', async (req, res) => {
|
|
126
|
+
try {
|
|
127
|
+
const testFile = req.body;
|
|
128
|
+
const { testId } = req.params;
|
|
129
|
+
const test = testFile.tests.find(t => t.id === testId);
|
|
130
|
+
if (!test) {
|
|
131
|
+
return res.status(404).json({ error: `Test not found: ${testId}` });
|
|
132
|
+
}
|
|
133
|
+
// Merge global variables
|
|
134
|
+
const globalVars = (0, globals_1.getGlobalVariables)();
|
|
135
|
+
const testIdAttr = (0, globals_1.getTestIdAttribute)();
|
|
136
|
+
const executor = new executor_1.TestExecutor({
|
|
137
|
+
headless: req.query.headless !== 'false',
|
|
138
|
+
timeout: Number(req.query.timeout) || 30000,
|
|
139
|
+
variables: globalVars,
|
|
140
|
+
testIdAttribute: testIdAttr,
|
|
141
|
+
baseDir: globalsDir, // Base directory for resolving relative file paths
|
|
142
|
+
});
|
|
143
|
+
// Register custom blocks from procedures before running the test
|
|
144
|
+
if (testFile.procedures) {
|
|
145
|
+
executor.registerProcedures(testFile.procedures);
|
|
146
|
+
}
|
|
147
|
+
await executor.initialize();
|
|
148
|
+
const result = await executor.runTest(test, testFile.variables);
|
|
149
|
+
await executor.cleanup();
|
|
150
|
+
res.json(result);
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
console.error('Test execution failed:', error);
|
|
154
|
+
res.status(500).json({
|
|
155
|
+
error: 'Test execution failed',
|
|
156
|
+
message: error.message,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
// Validate test file
|
|
161
|
+
app.post('/api/validate', (req, res) => {
|
|
162
|
+
try {
|
|
163
|
+
const testFile = req.body;
|
|
164
|
+
const errors = [];
|
|
165
|
+
if (!testFile.version) {
|
|
166
|
+
errors.push('Missing version field');
|
|
167
|
+
}
|
|
168
|
+
if (!testFile.name) {
|
|
169
|
+
errors.push('Missing name field');
|
|
170
|
+
}
|
|
171
|
+
if (!testFile.tests || !Array.isArray(testFile.tests)) {
|
|
172
|
+
errors.push('Missing or invalid tests array');
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
testFile.tests.forEach((test, index) => {
|
|
176
|
+
if (!test.id) {
|
|
177
|
+
errors.push(`Test at index ${index} is missing an id`);
|
|
178
|
+
}
|
|
179
|
+
if (!test.name) {
|
|
180
|
+
errors.push(`Test at index ${index} is missing a name`);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
if (errors.length > 0) {
|
|
185
|
+
res.status(400).json({ valid: false, errors });
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
res.json({ valid: true });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
res.status(400).json({
|
|
193
|
+
valid: false,
|
|
194
|
+
errors: ['Invalid JSON: ' + error.message],
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
// ===== Recording Endpoints =====
|
|
199
|
+
// Start a recording session
|
|
200
|
+
app.post('/api/record/start', async (req, res) => {
|
|
201
|
+
try {
|
|
202
|
+
const { url, testIdAttribute } = req.body;
|
|
203
|
+
if (!url) {
|
|
204
|
+
return res.status(400).json({ error: 'URL is required' });
|
|
205
|
+
}
|
|
206
|
+
console.log(`Starting recording session for URL: ${url}`);
|
|
207
|
+
const sessionId = await codegenManager_1.codegenManager.startRecording(url, {
|
|
208
|
+
testIdAttribute: testIdAttribute || undefined,
|
|
209
|
+
});
|
|
210
|
+
res.json({
|
|
211
|
+
sessionId,
|
|
212
|
+
status: 'running',
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
console.error('Failed to start recording:', error);
|
|
217
|
+
res.status(500).json({
|
|
218
|
+
error: 'Failed to start recording',
|
|
219
|
+
message: error.message,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
// Stop a recording session and get the recorded steps
|
|
224
|
+
app.post('/api/record/stop', async (req, res) => {
|
|
225
|
+
try {
|
|
226
|
+
const { sessionId } = req.body;
|
|
227
|
+
if (!sessionId) {
|
|
228
|
+
return res.status(400).json({ error: 'Session ID is required' });
|
|
229
|
+
}
|
|
230
|
+
console.log(`Stopping recording session: ${sessionId}`);
|
|
231
|
+
const steps = await codegenManager_1.codegenManager.stopRecording(sessionId);
|
|
232
|
+
res.json({
|
|
233
|
+
status: 'completed',
|
|
234
|
+
steps,
|
|
235
|
+
});
|
|
236
|
+
// Clean up the session after returning the steps
|
|
237
|
+
setTimeout(() => {
|
|
238
|
+
codegenManager_1.codegenManager.cleanup(sessionId);
|
|
239
|
+
}, 5000);
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
console.error('Failed to stop recording:', error);
|
|
243
|
+
res.status(500).json({
|
|
244
|
+
error: 'Failed to stop recording',
|
|
245
|
+
message: error.message,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
// Get the status of a recording session
|
|
250
|
+
app.get('/api/record/status/:sessionId', (req, res) => {
|
|
251
|
+
const { sessionId } = req.params;
|
|
252
|
+
const session = codegenManager_1.codegenManager.getStatus(sessionId);
|
|
253
|
+
if (!session) {
|
|
254
|
+
return res.status(404).json({ error: 'Session not found' });
|
|
255
|
+
}
|
|
256
|
+
res.json({
|
|
257
|
+
sessionId: session.id,
|
|
258
|
+
status: session.status,
|
|
259
|
+
url: session.url,
|
|
260
|
+
error: session.error,
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
// ===== Report Generation Endpoints =====
|
|
264
|
+
// Generate HTML report from test results
|
|
265
|
+
app.post('/api/reports/html', (req, res) => {
|
|
266
|
+
try {
|
|
267
|
+
const { testFile, results } = req.body;
|
|
268
|
+
if (!testFile || !results) {
|
|
269
|
+
return res.status(400).json({ error: 'testFile and results are required' });
|
|
270
|
+
}
|
|
271
|
+
const timestamp = new Date().toISOString();
|
|
272
|
+
const reportData = {
|
|
273
|
+
timestamp,
|
|
274
|
+
summary: {
|
|
275
|
+
totalTests: results.length,
|
|
276
|
+
passed: results.filter(r => r.status === 'passed').length,
|
|
277
|
+
failed: results.filter(r => r.status !== 'passed').length,
|
|
278
|
+
duration: results.reduce((sum, r) => sum + r.duration, 0),
|
|
279
|
+
},
|
|
280
|
+
testFiles: [{
|
|
281
|
+
file: testFile.name || 'TestBlocks Test',
|
|
282
|
+
testFile,
|
|
283
|
+
results,
|
|
284
|
+
}],
|
|
285
|
+
};
|
|
286
|
+
const html = (0, reporters_1.generateHTMLReport)(reportData);
|
|
287
|
+
const filename = `report-${(0, reporters_1.getTimestamp)()}.html`;
|
|
288
|
+
res.setHeader('Content-Type', 'text/html');
|
|
289
|
+
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
|
290
|
+
res.send(html);
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
console.error('Failed to generate HTML report:', error);
|
|
294
|
+
res.status(500).json({
|
|
295
|
+
error: 'Failed to generate report',
|
|
296
|
+
message: error.message,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
// Generate JUnit XML report from test results
|
|
301
|
+
app.post('/api/reports/junit', (req, res) => {
|
|
302
|
+
try {
|
|
303
|
+
const { testFile, results } = req.body;
|
|
304
|
+
if (!testFile || !results) {
|
|
305
|
+
return res.status(400).json({ error: 'testFile and results are required' });
|
|
306
|
+
}
|
|
307
|
+
const timestamp = new Date().toISOString();
|
|
308
|
+
const reportData = {
|
|
309
|
+
timestamp,
|
|
310
|
+
summary: {
|
|
311
|
+
totalTests: results.length,
|
|
312
|
+
passed: results.filter(r => r.status === 'passed').length,
|
|
313
|
+
failed: results.filter(r => r.status !== 'passed').length,
|
|
314
|
+
duration: results.reduce((sum, r) => sum + r.duration, 0),
|
|
315
|
+
},
|
|
316
|
+
testFiles: [{
|
|
317
|
+
file: testFile.name || 'TestBlocks Test',
|
|
318
|
+
testFile,
|
|
319
|
+
results,
|
|
320
|
+
}],
|
|
321
|
+
};
|
|
322
|
+
const xml = (0, reporters_1.generateJUnitXML)(reportData);
|
|
323
|
+
const filename = `junit-${(0, reporters_1.getTimestamp)()}.xml`;
|
|
324
|
+
res.setHeader('Content-Type', 'application/xml');
|
|
325
|
+
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
|
326
|
+
res.send(xml);
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
console.error('Failed to generate JUnit report:', error);
|
|
330
|
+
res.status(500).json({
|
|
331
|
+
error: 'Failed to generate report',
|
|
332
|
+
message: error.message,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
// Clean up sessions on server shutdown
|
|
337
|
+
process.on('SIGTERM', () => {
|
|
338
|
+
console.log('Received SIGTERM, cleaning up...');
|
|
339
|
+
codegenManager_1.codegenManager.cleanupAll();
|
|
340
|
+
process.exit(0);
|
|
341
|
+
});
|
|
342
|
+
process.on('SIGINT', () => {
|
|
343
|
+
console.log('Received SIGINT, cleaning up...');
|
|
344
|
+
codegenManager_1.codegenManager.cleanupAll();
|
|
345
|
+
process.exit(0);
|
|
346
|
+
});
|
|
347
|
+
app.listen(PORT, () => {
|
|
348
|
+
console.log(`TestBlocks server running on http://localhost:${PORT}`);
|
|
349
|
+
console.log('API endpoints:');
|
|
350
|
+
console.log(' GET /api/health - Health check');
|
|
351
|
+
console.log(' GET /api/plugins - List plugins');
|
|
352
|
+
console.log(' GET /api/globals - Get globals and snippets');
|
|
353
|
+
console.log(' POST /api/run - Run all tests');
|
|
354
|
+
console.log(' POST /api/run/:id - Run single test');
|
|
355
|
+
console.log(' POST /api/validate - Validate test file');
|
|
356
|
+
console.log(' POST /api/record/start - Start recording session');
|
|
357
|
+
console.log(' POST /api/record/stop - Stop recording and get steps');
|
|
358
|
+
console.log(' GET /api/record/status - Get recording session status');
|
|
359
|
+
console.log(' POST /api/reports/html - Generate HTML report');
|
|
360
|
+
console.log(' POST /api/reports/junit - Generate JUnit XML report');
|
|
361
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side Plugin Initialization
|
|
3
|
+
*
|
|
4
|
+
* This module provides utilities for loading and registering plugins
|
|
5
|
+
* for the test executor. Plugins can be:
|
|
6
|
+
* 1. Registered manually via registerServerPlugin()
|
|
7
|
+
* 2. Discovered automatically from a plugins folder via discoverPlugins()
|
|
8
|
+
*/
|
|
9
|
+
import { Plugin } from '../core';
|
|
10
|
+
/**
|
|
11
|
+
* Set the plugins directory path
|
|
12
|
+
*/
|
|
13
|
+
export declare function setPluginsDirectory(dir: string): void;
|
|
14
|
+
/**
|
|
15
|
+
* Get the current plugins directory
|
|
16
|
+
*/
|
|
17
|
+
export declare function getPluginsDirectory(): string;
|
|
18
|
+
/**
|
|
19
|
+
* Register a plugin for server-side use
|
|
20
|
+
* This registers the plugin's blocks with the core block registry
|
|
21
|
+
*/
|
|
22
|
+
export declare function registerServerPlugin(plugin: Plugin): void;
|
|
23
|
+
/**
|
|
24
|
+
* Register multiple plugins at once
|
|
25
|
+
*/
|
|
26
|
+
export declare function registerServerPlugins(plugins: Plugin[]): void;
|
|
27
|
+
/**
|
|
28
|
+
* Discover and list available plugins from the plugins directory
|
|
29
|
+
* Returns plugin file names (without extension)
|
|
30
|
+
*/
|
|
31
|
+
export declare function discoverPlugins(): string[];
|
|
32
|
+
/**
|
|
33
|
+
* Load a specific plugin by name from the plugins directory
|
|
34
|
+
*/
|
|
35
|
+
export declare function loadPlugin(pluginName: string): Promise<Plugin | null>;
|
|
36
|
+
/**
|
|
37
|
+
* Load plugins specified in a test file
|
|
38
|
+
*/
|
|
39
|
+
export declare function loadTestFilePlugins(pluginNames: string[]): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Load all discovered plugins from the plugins directory
|
|
42
|
+
*/
|
|
43
|
+
export declare function loadAllPlugins(): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Initialize server plugins (call after all plugins are registered)
|
|
46
|
+
*/
|
|
47
|
+
export declare function initializeServerPlugins(): void;
|
|
48
|
+
/**
|
|
49
|
+
* Get all registered server plugins
|
|
50
|
+
*/
|
|
51
|
+
export declare function getServerPlugins(): Plugin[];
|
|
52
|
+
/**
|
|
53
|
+
* Check if a plugin is registered
|
|
54
|
+
*/
|
|
55
|
+
export declare function isPluginLoaded(pluginName: string): boolean;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Server-side Plugin Initialization
|
|
4
|
+
*
|
|
5
|
+
* This module provides utilities for loading and registering plugins
|
|
6
|
+
* for the test executor. Plugins can be:
|
|
7
|
+
* 1. Registered manually via registerServerPlugin()
|
|
8
|
+
* 2. Discovered automatically from a plugins folder via discoverPlugins()
|
|
9
|
+
*/
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
+
}) : function(o, v) {
|
|
24
|
+
o["default"] = v;
|
|
25
|
+
});
|
|
26
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
+
var ownKeys = function(o) {
|
|
28
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
+
var ar = [];
|
|
30
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
+
return ar;
|
|
32
|
+
};
|
|
33
|
+
return ownKeys(o);
|
|
34
|
+
};
|
|
35
|
+
return function (mod) {
|
|
36
|
+
if (mod && mod.__esModule) return mod;
|
|
37
|
+
var result = {};
|
|
38
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
+
__setModuleDefault(result, mod);
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.setPluginsDirectory = setPluginsDirectory;
|
|
45
|
+
exports.getPluginsDirectory = getPluginsDirectory;
|
|
46
|
+
exports.registerServerPlugin = registerServerPlugin;
|
|
47
|
+
exports.registerServerPlugins = registerServerPlugins;
|
|
48
|
+
exports.discoverPlugins = discoverPlugins;
|
|
49
|
+
exports.loadPlugin = loadPlugin;
|
|
50
|
+
exports.loadTestFilePlugins = loadTestFilePlugins;
|
|
51
|
+
exports.loadAllPlugins = loadAllPlugins;
|
|
52
|
+
exports.initializeServerPlugins = initializeServerPlugins;
|
|
53
|
+
exports.getServerPlugins = getServerPlugins;
|
|
54
|
+
exports.isPluginLoaded = isPluginLoaded;
|
|
55
|
+
const fs = __importStar(require("fs"));
|
|
56
|
+
const path = __importStar(require("path"));
|
|
57
|
+
const core_1 = require("../core");
|
|
58
|
+
// Track initialization
|
|
59
|
+
let pluginsInitialized = false;
|
|
60
|
+
// List of registered plugins
|
|
61
|
+
const registeredPlugins = new Map();
|
|
62
|
+
// Plugins directory (can be overridden)
|
|
63
|
+
let pluginsDirectory = path.join(process.cwd(), 'plugins');
|
|
64
|
+
/**
|
|
65
|
+
* Set the plugins directory path
|
|
66
|
+
*/
|
|
67
|
+
function setPluginsDirectory(dir) {
|
|
68
|
+
pluginsDirectory = path.isAbsolute(dir) ? dir : path.join(process.cwd(), dir);
|
|
69
|
+
console.log(`Plugins directory set to: ${pluginsDirectory}`);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get the current plugins directory
|
|
73
|
+
*/
|
|
74
|
+
function getPluginsDirectory() {
|
|
75
|
+
return pluginsDirectory;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Register a plugin for server-side use
|
|
79
|
+
* This registers the plugin's blocks with the core block registry
|
|
80
|
+
*/
|
|
81
|
+
function registerServerPlugin(plugin) {
|
|
82
|
+
if (registeredPlugins.has(plugin.name)) {
|
|
83
|
+
console.log(`Plugin already registered: ${plugin.name}`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
(0, core_1.registerPlugin)(plugin);
|
|
88
|
+
registeredPlugins.set(plugin.name, plugin);
|
|
89
|
+
console.log(`Registered plugin: ${plugin.name}`);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
console.error(`Failed to register plugin "${plugin.name}":`, error);
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Register multiple plugins at once
|
|
98
|
+
*/
|
|
99
|
+
function registerServerPlugins(plugins) {
|
|
100
|
+
for (const plugin of plugins) {
|
|
101
|
+
registerServerPlugin(plugin);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Discover and list available plugins from the plugins directory
|
|
106
|
+
* Returns plugin file names (without extension)
|
|
107
|
+
*/
|
|
108
|
+
function discoverPlugins() {
|
|
109
|
+
if (!fs.existsSync(pluginsDirectory)) {
|
|
110
|
+
console.log(`Plugins directory not found: ${pluginsDirectory}`);
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
const files = fs.readdirSync(pluginsDirectory);
|
|
114
|
+
const plugins = [];
|
|
115
|
+
for (const file of files) {
|
|
116
|
+
// Look for .js or compiled .js files (not .d.ts)
|
|
117
|
+
if ((file.endsWith('.js') || file.endsWith('.ts')) && !file.endsWith('.d.ts')) {
|
|
118
|
+
const pluginName = file.replace(/\.(js|ts)$/, '');
|
|
119
|
+
if (pluginName !== 'index') {
|
|
120
|
+
plugins.push(pluginName);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
console.log(`Discovered ${plugins.length} plugin(s): ${plugins.join(', ')}`);
|
|
125
|
+
return plugins;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Load a specific plugin by name from the plugins directory
|
|
129
|
+
*/
|
|
130
|
+
async function loadPlugin(pluginName) {
|
|
131
|
+
// Check if already loaded
|
|
132
|
+
if (registeredPlugins.has(pluginName)) {
|
|
133
|
+
return registeredPlugins.get(pluginName);
|
|
134
|
+
}
|
|
135
|
+
const jsPath = path.join(pluginsDirectory, `${pluginName}.js`);
|
|
136
|
+
const tsPath = path.join(pluginsDirectory, `${pluginName}.ts`);
|
|
137
|
+
let pluginPath = null;
|
|
138
|
+
if (fs.existsSync(jsPath)) {
|
|
139
|
+
pluginPath = jsPath;
|
|
140
|
+
}
|
|
141
|
+
else if (fs.existsSync(tsPath)) {
|
|
142
|
+
// For TypeScript files, require ts-node or esbuild-register
|
|
143
|
+
pluginPath = tsPath;
|
|
144
|
+
}
|
|
145
|
+
if (!pluginPath) {
|
|
146
|
+
console.error(`Plugin not found: ${pluginName} (looked in ${pluginsDirectory})`);
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
console.log(`Loading plugin: ${pluginName} from ${pluginPath}`);
|
|
151
|
+
// Dynamic import
|
|
152
|
+
const module = await Promise.resolve(`${pluginPath}`).then(s => __importStar(require(s)));
|
|
153
|
+
// Look for default export or named export matching plugin name
|
|
154
|
+
const plugin = module.default ||
|
|
155
|
+
module[pluginName.replace(/-./g, x => x[1].toUpperCase()) + 'Plugin'] ||
|
|
156
|
+
module[pluginName.replace(/-/g, '') + 'Plugin'] ||
|
|
157
|
+
Object.values(module).find((exp) => typeof exp === 'object' && exp !== null && 'name' in exp && 'blocks' in exp);
|
|
158
|
+
if (!plugin) {
|
|
159
|
+
console.error(`No valid plugin export found in ${pluginPath}`);
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
registerServerPlugin(plugin);
|
|
163
|
+
return plugin;
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
console.error(`Failed to load plugin ${pluginName}:`, error);
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Load plugins specified in a test file
|
|
172
|
+
*/
|
|
173
|
+
async function loadTestFilePlugins(pluginNames) {
|
|
174
|
+
for (const name of pluginNames) {
|
|
175
|
+
await loadPlugin(name);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Load all discovered plugins from the plugins directory
|
|
180
|
+
*/
|
|
181
|
+
async function loadAllPlugins() {
|
|
182
|
+
const pluginNames = discoverPlugins();
|
|
183
|
+
await loadTestFilePlugins(pluginNames);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Initialize server plugins (call after all plugins are registered)
|
|
187
|
+
*/
|
|
188
|
+
function initializeServerPlugins() {
|
|
189
|
+
if (pluginsInitialized) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
pluginsInitialized = true;
|
|
193
|
+
console.log(`Server plugins initialized: ${registeredPlugins.size} plugin(s) loaded`);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Get all registered server plugins
|
|
197
|
+
*/
|
|
198
|
+
function getServerPlugins() {
|
|
199
|
+
return Array.from(registeredPlugins.values());
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Check if a plugin is registered
|
|
203
|
+
*/
|
|
204
|
+
function isPluginLoaded(pluginName) {
|
|
205
|
+
return registeredPlugins.has(pluginName);
|
|
206
|
+
}
|