@majkapp/plugin-kit 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generator/cli.js +0 -0
- package/dist/generator/generator.js +1 -3
- package/dist/index.d.ts +12 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -11
- package/dist/plugin-kit.d.ts +6 -3
- package/dist/plugin-kit.d.ts.map +1 -1
- package/dist/plugin-kit.js +214 -854
- package/dist/registry.d.ts +15 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +41 -1
- package/dist/resource-provider.d.ts +28 -0
- package/dist/resource-provider.d.ts.map +1 -0
- package/dist/resource-provider.js +86 -0
- package/dist/types.d.ts +157 -8
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/plugin-kit.js
CHANGED
|
@@ -4,11 +4,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.definePlugin = definePlugin;
|
|
7
|
-
const http_1 = __importDefault(require("http"));
|
|
8
7
|
const fs_1 = __importDefault(require("fs"));
|
|
9
8
|
const path_1 = __importDefault(require("path"));
|
|
10
9
|
const registry_1 = require("./registry");
|
|
11
|
-
const transports_1 = require("./transports");
|
|
12
10
|
/**
|
|
13
11
|
* Conditional logging - suppressed in extract mode
|
|
14
12
|
*/
|
|
@@ -153,333 +151,19 @@ function validateEntity(entityType, entity) {
|
|
|
153
151
|
}
|
|
154
152
|
return errors;
|
|
155
153
|
}
|
|
156
|
-
/**
|
|
157
|
-
* Convert JSON Schema to OpenAPI Schema
|
|
158
|
-
*/
|
|
159
|
-
function jsonSchemaToOpenApi(schema) {
|
|
160
|
-
// OpenAPI doesn't use the $schema property
|
|
161
|
-
const { ...openApiSchema } = schema;
|
|
162
|
-
return openApiSchema;
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Generate OpenAPI specification from routes
|
|
166
|
-
*/
|
|
167
|
-
function generateOpenApiSpec(pluginId, pluginName, pluginVersion, routes, baseUrl) {
|
|
168
|
-
const paths = {};
|
|
169
|
-
for (const route of routes) {
|
|
170
|
-
const openApiPath = route.path.replace(/:([A-Za-z0-9_]+)/g, '{$1}');
|
|
171
|
-
if (!paths[openApiPath]) {
|
|
172
|
-
paths[openApiPath] = {};
|
|
173
|
-
}
|
|
174
|
-
const operation = {
|
|
175
|
-
operationId: `${pluginId}_${route.name.replace(/\s+/g, '_')}`,
|
|
176
|
-
summary: route.name,
|
|
177
|
-
description: route.description,
|
|
178
|
-
tags: route.tags || [pluginId],
|
|
179
|
-
deprecated: route.deprecated
|
|
180
|
-
};
|
|
181
|
-
// Add parameters from request schema
|
|
182
|
-
if (route.requestSchema) {
|
|
183
|
-
const parameters = [];
|
|
184
|
-
// Path parameters
|
|
185
|
-
if (route.requestSchema.params) {
|
|
186
|
-
const paramProps = route.requestSchema.params.properties || {};
|
|
187
|
-
for (const [name, schema] of Object.entries(paramProps)) {
|
|
188
|
-
parameters.push({
|
|
189
|
-
name,
|
|
190
|
-
in: 'path',
|
|
191
|
-
required: route.requestSchema.params.required?.includes(name) ?? true,
|
|
192
|
-
schema: jsonSchemaToOpenApi(schema),
|
|
193
|
-
description: schema.description
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
// Query parameters
|
|
198
|
-
if (route.requestSchema.query) {
|
|
199
|
-
const queryProps = route.requestSchema.query.properties || {};
|
|
200
|
-
for (const [name, schema] of Object.entries(queryProps)) {
|
|
201
|
-
parameters.push({
|
|
202
|
-
name,
|
|
203
|
-
in: 'query',
|
|
204
|
-
required: route.requestSchema.query.required?.includes(name) ?? false,
|
|
205
|
-
schema: jsonSchemaToOpenApi(schema),
|
|
206
|
-
description: schema.description
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
if (parameters.length > 0) {
|
|
211
|
-
operation.parameters = parameters;
|
|
212
|
-
}
|
|
213
|
-
// Request body
|
|
214
|
-
if (route.requestSchema.body) {
|
|
215
|
-
operation.requestBody = {
|
|
216
|
-
required: true,
|
|
217
|
-
content: {
|
|
218
|
-
'application/json': {
|
|
219
|
-
schema: jsonSchemaToOpenApi(route.requestSchema.body)
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
// Response schema
|
|
226
|
-
operation.responses = {
|
|
227
|
-
'200': {
|
|
228
|
-
description: 'Successful response',
|
|
229
|
-
content: route.responseSchema ? {
|
|
230
|
-
'application/json': {
|
|
231
|
-
schema: jsonSchemaToOpenApi(route.responseSchema)
|
|
232
|
-
}
|
|
233
|
-
} : {}
|
|
234
|
-
},
|
|
235
|
-
'400': {
|
|
236
|
-
description: 'Bad request'
|
|
237
|
-
},
|
|
238
|
-
'500': {
|
|
239
|
-
description: 'Internal server error'
|
|
240
|
-
}
|
|
241
|
-
};
|
|
242
|
-
paths[openApiPath][route.method.toLowerCase()] = operation;
|
|
243
|
-
}
|
|
244
|
-
return {
|
|
245
|
-
openapi: '3.0.0',
|
|
246
|
-
info: {
|
|
247
|
-
title: `${pluginName} API`,
|
|
248
|
-
version: pluginVersion,
|
|
249
|
-
description: `API documentation for the ${pluginName} MAJK plugin`
|
|
250
|
-
},
|
|
251
|
-
servers: [
|
|
252
|
-
{
|
|
253
|
-
url: baseUrl,
|
|
254
|
-
description: 'Plugin API server'
|
|
255
|
-
}
|
|
256
|
-
],
|
|
257
|
-
paths,
|
|
258
|
-
components: {
|
|
259
|
-
schemas: {}
|
|
260
|
-
}
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
/**
|
|
264
|
-
* Path parameter regex builder
|
|
265
|
-
*/
|
|
266
|
-
function pathToRegex(p) {
|
|
267
|
-
const keys = [];
|
|
268
|
-
log(`[pathToRegex] Input path: ${p}`);
|
|
269
|
-
// First, escape special regex characters EXCEPT the colon (for path params)
|
|
270
|
-
const escaped = p.replace(/([.+*?=^!${}()[\]|\\])/g, '\\$1');
|
|
271
|
-
log(`[pathToRegex] After escaping: ${escaped}`);
|
|
272
|
-
// Then, replace path parameters with capturing groups
|
|
273
|
-
const pattern = escaped.replace(/\/:([A-Za-z0-9_]+)/g, (_m, k) => {
|
|
274
|
-
keys.push(k);
|
|
275
|
-
log(`[pathToRegex] Found param: ${k}`);
|
|
276
|
-
return '/([^/]+)';
|
|
277
|
-
});
|
|
278
|
-
log(`[pathToRegex] Final pattern: ${pattern}`);
|
|
279
|
-
log(`[pathToRegex] Keys: [${keys.join(', ')}]`);
|
|
280
|
-
const regex = new RegExp(`^${pattern}$`);
|
|
281
|
-
log(`[pathToRegex] Final regex: ${regex}`);
|
|
282
|
-
return { regex, keys };
|
|
283
|
-
}
|
|
284
|
-
/**
|
|
285
|
-
* CORS headers
|
|
286
|
-
*/
|
|
287
|
-
function corsHeaders(extra = {}) {
|
|
288
|
-
return {
|
|
289
|
-
'Access-Control-Allow-Origin': '*',
|
|
290
|
-
'Access-Control-Allow-Methods': 'GET, POST, PUT, PATCH, DELETE, OPTIONS',
|
|
291
|
-
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
|
292
|
-
...extra
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
/**
|
|
296
|
-
* Read request body
|
|
297
|
-
*/
|
|
298
|
-
async function readBody(req) {
|
|
299
|
-
if (req.method === 'GET' || req.method === 'HEAD' || req.method === 'OPTIONS') {
|
|
300
|
-
return undefined;
|
|
301
|
-
}
|
|
302
|
-
return new Promise((resolve) => {
|
|
303
|
-
let data = '';
|
|
304
|
-
req.on('data', (chunk) => (data += chunk.toString()));
|
|
305
|
-
req.on('end', () => {
|
|
306
|
-
if (!data) {
|
|
307
|
-
resolve(undefined);
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
try {
|
|
311
|
-
resolve(JSON.parse(data));
|
|
312
|
-
}
|
|
313
|
-
catch {
|
|
314
|
-
resolve(data);
|
|
315
|
-
}
|
|
316
|
-
});
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
/**
|
|
320
|
-
* Create response wrapper
|
|
321
|
-
*/
|
|
322
|
-
function makeResponse(res) {
|
|
323
|
-
return {
|
|
324
|
-
status(code) {
|
|
325
|
-
res.statusCode = code;
|
|
326
|
-
return this;
|
|
327
|
-
},
|
|
328
|
-
setHeader(key, value) {
|
|
329
|
-
res.setHeader(key, value);
|
|
330
|
-
},
|
|
331
|
-
json(data) {
|
|
332
|
-
if (!res.headersSent) {
|
|
333
|
-
res.writeHead(res.statusCode || 200, corsHeaders({ 'Content-Type': 'application/json' }));
|
|
334
|
-
}
|
|
335
|
-
res.end(JSON.stringify(data));
|
|
336
|
-
},
|
|
337
|
-
send(data) {
|
|
338
|
-
if (!res.headersSent) {
|
|
339
|
-
res.writeHead(res.statusCode || 200, corsHeaders());
|
|
340
|
-
}
|
|
341
|
-
res.end(data);
|
|
342
|
-
}
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
/**
|
|
346
|
-
* Content type helper
|
|
347
|
-
*/
|
|
348
|
-
function getContentType(filePath) {
|
|
349
|
-
const ext = path_1.default.extname(filePath).toLowerCase();
|
|
350
|
-
const types = {
|
|
351
|
-
'.html': 'text/html',
|
|
352
|
-
'.js': 'application/javascript',
|
|
353
|
-
'.css': 'text/css',
|
|
354
|
-
'.json': 'application/json',
|
|
355
|
-
'.png': 'image/png',
|
|
356
|
-
'.jpg': 'image/jpeg',
|
|
357
|
-
'.jpeg': 'image/jpeg',
|
|
358
|
-
'.svg': 'image/svg+xml',
|
|
359
|
-
'.woff': 'font/woff',
|
|
360
|
-
'.woff2': 'font/woff2',
|
|
361
|
-
'.ttf': 'font/ttf'
|
|
362
|
-
};
|
|
363
|
-
return types[ext] || 'application/octet-stream';
|
|
364
|
-
}
|
|
365
|
-
/**
|
|
366
|
-
* Serve React SPA
|
|
367
|
-
*/
|
|
368
|
-
function serveSpa(ui, req, res, ctx) {
|
|
369
|
-
const url = new URL(req.url, 'http://localhost');
|
|
370
|
-
const rel = url.pathname.substring(ui.base.length) || '/';
|
|
371
|
-
const distPath = path_1.default.join(ctx.pluginRoot, ui.appDir);
|
|
372
|
-
let filePath = path_1.default.join(distPath, rel);
|
|
373
|
-
if (filePath.endsWith('/')) {
|
|
374
|
-
filePath = path_1.default.join(filePath, 'index.html');
|
|
375
|
-
}
|
|
376
|
-
// Security: prevent path traversal
|
|
377
|
-
const normalizedPath = path_1.default.resolve(filePath);
|
|
378
|
-
if (!normalizedPath.startsWith(path_1.default.resolve(distPath))) {
|
|
379
|
-
res.writeHead(403, corsHeaders({ 'Content-Type': 'text/plain' }));
|
|
380
|
-
res.end('Forbidden: Path traversal detected');
|
|
381
|
-
ctx.logger.warn(`Path traversal attempt blocked: ${rel}`);
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
const exists = fs_1.default.existsSync(filePath) && fs_1.default.statSync(filePath).isFile();
|
|
385
|
-
const targetFile = exists ? filePath : path_1.default.join(distPath, 'index.html');
|
|
386
|
-
if (!fs_1.default.existsSync(targetFile)) {
|
|
387
|
-
res.writeHead(404, corsHeaders({ 'Content-Type': 'text/plain' }));
|
|
388
|
-
res.end('Not found');
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
let content = fs_1.default.readFileSync(targetFile);
|
|
392
|
-
// Inject base URL for React apps and MCP DOM Agent
|
|
393
|
-
if (path_1.default.basename(targetFile) === 'index.html') {
|
|
394
|
-
const html = content.toString('utf-8');
|
|
395
|
-
// Inject configuration
|
|
396
|
-
const configInject = `<script>` +
|
|
397
|
-
`window.__MAJK_BASE_URL__=${JSON.stringify(ctx.http.baseUrl)};` +
|
|
398
|
-
`window.__MAJK_IFRAME_BASE__=${JSON.stringify(ui.base)};` +
|
|
399
|
-
`window.__MAJK_PLUGIN_ID__=${JSON.stringify(ctx.pluginId)};` +
|
|
400
|
-
`</script>`;
|
|
401
|
-
// Inject html2canvas library (inline from node_modules)
|
|
402
|
-
const html2canvasScript = BuiltPlugin.html2canvasScript
|
|
403
|
-
? `<script>${BuiltPlugin.html2canvasScript}</script>`
|
|
404
|
-
: '';
|
|
405
|
-
// Inject MCP DOM Agent script
|
|
406
|
-
const mcpScript = BuiltPlugin.mcpDomAgentScript
|
|
407
|
-
? `<script>${BuiltPlugin.mcpDomAgentScript}</script>`
|
|
408
|
-
: '';
|
|
409
|
-
// Debug logging
|
|
410
|
-
if (html2canvasScript) {
|
|
411
|
-
ctx.logger.debug(`[React SPA] Injecting html2canvas (${(BuiltPlugin.html2canvasScript?.length || 0) / 1024} KB)`);
|
|
412
|
-
}
|
|
413
|
-
else {
|
|
414
|
-
ctx.logger.warn('[React SPA] html2canvas NOT injected - script not loaded');
|
|
415
|
-
}
|
|
416
|
-
if (mcpScript) {
|
|
417
|
-
ctx.logger.debug('[React SPA] Injecting MCP DOM Agent');
|
|
418
|
-
}
|
|
419
|
-
const allInjections = configInject + html2canvasScript + mcpScript;
|
|
420
|
-
const injected = html.replace('</head>', `${allInjections}</head>`);
|
|
421
|
-
content = Buffer.from(injected, 'utf-8');
|
|
422
|
-
}
|
|
423
|
-
res.writeHead(200, corsHeaders({ 'Content-Type': getContentType(targetFile) }));
|
|
424
|
-
res.end(content);
|
|
425
|
-
}
|
|
426
|
-
/**
|
|
427
|
-
* Load html2canvas library from node_modules
|
|
428
|
-
*/
|
|
429
|
-
function loadHtml2CanvasScript() {
|
|
430
|
-
try {
|
|
431
|
-
// Try multiple possible paths for html2canvas
|
|
432
|
-
const possiblePaths = [
|
|
433
|
-
// When running from dist/ (after build)
|
|
434
|
-
path_1.default.join(__dirname, '..', 'node_modules', 'html2canvas', 'dist', 'html2canvas.min.js'),
|
|
435
|
-
// When running from src/ (development)
|
|
436
|
-
path_1.default.join(__dirname, '..', '..', 'node_modules', 'html2canvas', 'dist', 'html2canvas.min.js'),
|
|
437
|
-
// When installed as a dependency in user's project
|
|
438
|
-
path_1.default.join(__dirname, '..', '..', '..', 'html2canvas', 'dist', 'html2canvas.min.js'),
|
|
439
|
-
];
|
|
440
|
-
for (const scriptPath of possiblePaths) {
|
|
441
|
-
if (fs_1.default.existsSync(scriptPath)) {
|
|
442
|
-
log(`[html2canvas] Loaded from: ${scriptPath}`);
|
|
443
|
-
return fs_1.default.readFileSync(scriptPath, 'utf-8');
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
log('[html2canvas] Script file not found in any expected location');
|
|
447
|
-
return '';
|
|
448
|
-
}
|
|
449
|
-
catch (error) {
|
|
450
|
-
log(`[html2canvas] Failed to load script: ${error.message}`);
|
|
451
|
-
return '';
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
/**
|
|
455
|
-
* Load MCP DOM Agent script
|
|
456
|
-
*/
|
|
457
|
-
function loadMcpDomAgentScript() {
|
|
458
|
-
try {
|
|
459
|
-
const scriptPath = path_1.default.join(__dirname, 'mcp-dom-agent.js');
|
|
460
|
-
if (fs_1.default.existsSync(scriptPath)) {
|
|
461
|
-
return fs_1.default.readFileSync(scriptPath, 'utf-8');
|
|
462
|
-
}
|
|
463
|
-
// Fallback: return minimal inline version if file not found
|
|
464
|
-
log('[MCP DOM Agent] Script file not found, using minimal fallback');
|
|
465
|
-
return `(function(){if(window.__mcpDomAgent)return;window.__mcpDomAgent=true;console.log('[MCP DOM Agent] Minimal fallback loaded');})();`;
|
|
466
|
-
}
|
|
467
|
-
catch (error) {
|
|
468
|
-
log(`[MCP DOM Agent] Failed to load script: ${error.message}`);
|
|
469
|
-
return '';
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
154
|
/**
|
|
473
155
|
* Main plugin class built by the fluent API
|
|
474
156
|
*/
|
|
475
157
|
class BuiltPlugin {
|
|
476
158
|
constructor(id, name, version, capabilities, tools, apiRoutes, uiConfig, reactScreens, htmlScreens, wizard, settings, onReadyFn, healthCheckFn, userProvidedPluginRoot,
|
|
477
|
-
//
|
|
478
|
-
functionRegistry,
|
|
479
|
-
//
|
|
159
|
+
// Function registry
|
|
160
|
+
functionRegistry,
|
|
161
|
+
// Configurable entities
|
|
480
162
|
configurableEntities,
|
|
481
|
-
//
|
|
482
|
-
secretProviders
|
|
163
|
+
// Secret providers
|
|
164
|
+
secretProviders,
|
|
165
|
+
// Package name for RPC service registration
|
|
166
|
+
packageName) {
|
|
483
167
|
this.capabilities = capabilities;
|
|
484
168
|
this.tools = tools;
|
|
485
169
|
this.apiRoutes = apiRoutes;
|
|
@@ -493,16 +177,15 @@ class BuiltPlugin {
|
|
|
493
177
|
this.userProvidedPluginRoot = userProvidedPluginRoot;
|
|
494
178
|
this.configurableEntities = configurableEntities;
|
|
495
179
|
this.secretProviders = secretProviders;
|
|
180
|
+
this.packageName = packageName;
|
|
496
181
|
this.cleanups = [];
|
|
497
|
-
this.router = [];
|
|
498
|
-
this.transports = [];
|
|
499
182
|
this.id = id;
|
|
500
183
|
this.name = name;
|
|
501
184
|
this.version = version;
|
|
502
185
|
this.functionRegistry = functionRegistry;
|
|
503
|
-
this.transports = transports || [];
|
|
504
186
|
this.configurableEntities = configurableEntities || [];
|
|
505
187
|
this.secretProviders = secretProviders || [];
|
|
188
|
+
this.packageName = packageName;
|
|
506
189
|
}
|
|
507
190
|
async getCapabilities() {
|
|
508
191
|
// Start with static capabilities
|
|
@@ -568,6 +251,19 @@ class BuiltPlugin {
|
|
|
568
251
|
}
|
|
569
252
|
return false;
|
|
570
253
|
}
|
|
254
|
+
/**
|
|
255
|
+
* Get function provider (new async Plugin interface)
|
|
256
|
+
* Returns null if plugin has no functions
|
|
257
|
+
*/
|
|
258
|
+
async getFunctions() {
|
|
259
|
+
if (!this.functionRegistry) {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
if (!this._functionProvider) {
|
|
263
|
+
this._functionProvider = new registry_1.FunctionProviderImpl(this.functionRegistry, this.context);
|
|
264
|
+
}
|
|
265
|
+
return this._functionProvider;
|
|
266
|
+
}
|
|
571
267
|
async createTool(toolName) {
|
|
572
268
|
const handler = this.tools.get(toolName);
|
|
573
269
|
if (!handler) {
|
|
@@ -605,77 +301,17 @@ class BuiltPlugin {
|
|
|
605
301
|
}
|
|
606
302
|
}
|
|
607
303
|
async onLoad(context) {
|
|
608
|
-
//
|
|
609
|
-
//
|
|
610
|
-
|
|
611
|
-
const effectivePluginRoot = this.userProvidedPluginRoot || context.pluginRoot;
|
|
612
|
-
this.context = {
|
|
613
|
-
...context,
|
|
614
|
-
pluginRoot: effectivePluginRoot
|
|
615
|
-
};
|
|
304
|
+
// NOTE: context.resources is now provided by MAJK (ResourceProvider abstraction)
|
|
305
|
+
// Plugin-kit no longer manages HTTP servers or file paths directly
|
|
306
|
+
this.context = context;
|
|
616
307
|
context.logger.info('═══════════════════════════════════════════════════════');
|
|
617
308
|
context.logger.info(`🚀 Loading Plugin: ${this.name} (${this.id}) v${this.version}`);
|
|
618
|
-
context.logger.info(`🔧 Plugin Kit Version: @majkapp/plugin-kit@
|
|
619
|
-
context.logger.info(`📍 Plugin Root: ${effectivePluginRoot}`);
|
|
620
|
-
if (this.userProvidedPluginRoot) {
|
|
621
|
-
context.logger.info(` (user-provided via .pluginRoot(__dirname))`);
|
|
622
|
-
}
|
|
623
|
-
else {
|
|
624
|
-
context.logger.info(` (from MAJK context - consider using .pluginRoot(__dirname) for better reliability)`);
|
|
625
|
-
}
|
|
626
|
-
context.logger.info(`🌐 HTTP Port: ${context.http.port}`);
|
|
309
|
+
context.logger.info(`🔧 Plugin Kit Version: @majkapp/plugin-kit@2.0.0`);
|
|
627
310
|
context.logger.info(`🔗 Base URL: ${context.http.baseUrl}`);
|
|
628
311
|
context.logger.info('═══════════════════════════════════════════════════════');
|
|
629
|
-
// Load html2canvas and MCP DOM Agent scripts once for all plugins
|
|
630
|
-
if (!BuiltPlugin.html2canvasScript) {
|
|
631
|
-
BuiltPlugin.html2canvasScript = loadHtml2CanvasScript();
|
|
632
|
-
if (BuiltPlugin.html2canvasScript) {
|
|
633
|
-
const sizeKB = (BuiltPlugin.html2canvasScript.length / 1024).toFixed(2);
|
|
634
|
-
context.logger.info(`✅ html2canvas library loaded (${sizeKB} KB) - screenshot support enabled`);
|
|
635
|
-
}
|
|
636
|
-
else {
|
|
637
|
-
context.logger.warn('⚠️ html2canvas not found - screenshots will use fallback mode');
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
if (!BuiltPlugin.mcpDomAgentScript) {
|
|
641
|
-
BuiltPlugin.mcpDomAgentScript = loadMcpDomAgentScript();
|
|
642
|
-
if (BuiltPlugin.mcpDomAgentScript) {
|
|
643
|
-
context.logger.info('✅ MCP DOM Agent script loaded - will be injected into all screens');
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
// Validate file paths now that we have pluginRoot context
|
|
647
|
-
if (this.reactScreens.length > 0 && this.uiConfig) {
|
|
648
|
-
const indexPath = path_1.default.join(this.context.pluginRoot, this.uiConfig.appDir || '', 'index.html');
|
|
649
|
-
if (!fs_1.default.existsSync(indexPath)) {
|
|
650
|
-
throw new PluginRuntimeError(`React app not built: ${indexPath} does not exist. Run "npm run build" in your UI directory to build the React app.`, 'onLoad', { appDir: this.uiConfig.appDir, indexPath, pluginRoot: this.context.pluginRoot });
|
|
651
|
-
}
|
|
652
|
-
context.logger.info(`✅ React UI found at: ${indexPath}`);
|
|
653
|
-
}
|
|
654
|
-
// Validate HTML screen files
|
|
655
|
-
for (const screen of this.htmlScreens) {
|
|
656
|
-
if ('htmlFile' in screen) {
|
|
657
|
-
const filePath = path_1.default.join(this.context.pluginRoot, screen.htmlFile);
|
|
658
|
-
if (!fs_1.default.existsSync(filePath)) {
|
|
659
|
-
throw new PluginRuntimeError(`HTML screen file not found: ${screen.htmlFile}. Create the HTML file at ${filePath} or fix the path.`, 'onLoad', { screen: screen.id, file: screen.htmlFile, resolvedPath: filePath, pluginRoot: this.context.pluginRoot });
|
|
660
|
-
}
|
|
661
|
-
context.logger.info(`✅ HTML screen file found: ${screen.htmlFile}`);
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
312
|
try {
|
|
665
|
-
|
|
666
|
-
// Initialize transports for function-first architecture
|
|
313
|
+
// Log registered functions
|
|
667
314
|
if (this.functionRegistry) {
|
|
668
|
-
context.logger.info('');
|
|
669
|
-
context.logger.info('🔌 Initializing Transports...');
|
|
670
|
-
for (const transport of this.transports) {
|
|
671
|
-
await transport.initialize(this.functionRegistry, this.context);
|
|
672
|
-
await transport.start();
|
|
673
|
-
const metadata = transport.getMetadata();
|
|
674
|
-
context.logger.info(` ✅ ${transport.name} transport initialized`);
|
|
675
|
-
context.logger.info(` • Base Path: ${metadata.endpoint}`);
|
|
676
|
-
context.logger.info(` • Discovery: ${metadata.discovery}`);
|
|
677
|
-
}
|
|
678
|
-
// Log registered functions
|
|
679
315
|
const functionNames = this.functionRegistry.getFunctionNames();
|
|
680
316
|
if (functionNames.length > 0) {
|
|
681
317
|
context.logger.info('');
|
|
@@ -691,136 +327,70 @@ class BuiltPlugin {
|
|
|
691
327
|
}
|
|
692
328
|
}
|
|
693
329
|
}
|
|
694
|
-
// Register API routes (legacy)
|
|
695
|
-
for (const route of this.apiRoutes) {
|
|
696
|
-
const { regex, keys } = pathToRegex(route.path);
|
|
697
|
-
this.router.push({
|
|
698
|
-
method: route.method,
|
|
699
|
-
regex,
|
|
700
|
-
keys,
|
|
701
|
-
name: route.name,
|
|
702
|
-
description: route.description,
|
|
703
|
-
handler: route.handler,
|
|
704
|
-
requestSchema: route.requestSchema,
|
|
705
|
-
responseSchema: route.responseSchema,
|
|
706
|
-
tags: route.tags,
|
|
707
|
-
deprecated: route.deprecated
|
|
708
|
-
});
|
|
709
|
-
context.logger.info(`📝 Registered route: ${route.method} ${route.path} - ${route.name}`);
|
|
710
|
-
}
|
|
711
|
-
// Register the discovery endpoint at /majk/plugin/api
|
|
712
|
-
const discoveryPath = '/majk/plugin/api';
|
|
713
|
-
const { regex: discoveryRegex, keys: discoveryKeys } = pathToRegex(discoveryPath);
|
|
714
|
-
this.router.push({
|
|
715
|
-
method: 'GET',
|
|
716
|
-
regex: discoveryRegex,
|
|
717
|
-
keys: discoveryKeys,
|
|
718
|
-
name: 'API Discovery',
|
|
719
|
-
description: 'Get OpenAPI specification for all plugin API routes',
|
|
720
|
-
handler: async (_req, _res, _ctx) => {
|
|
721
|
-
// If using function-first architecture, generate spec from function registry
|
|
722
|
-
if (this.functionRegistry && this.functionRegistry.getFunctionNames().length > 0) {
|
|
723
|
-
return this.functionRegistry.toOpenAPISpec();
|
|
724
|
-
}
|
|
725
|
-
// Otherwise fall back to legacy API routes
|
|
726
|
-
const openApiSpec = generateOpenApiSpec(this.id, this.name, this.version, this.apiRoutes, this.context.http.baseUrl);
|
|
727
|
-
return openApiSpec;
|
|
728
|
-
}
|
|
729
|
-
});
|
|
730
|
-
context.logger.info(`📝 Registered discovery route: GET ${discoveryPath}`);
|
|
731
|
-
// Also register a simpler JSON format at /majk/plugin/routes for quick access
|
|
732
|
-
const routesPath = '/majk/plugin/routes';
|
|
733
|
-
const { regex: routesRegex, keys: routesKeys } = pathToRegex(routesPath);
|
|
734
|
-
this.router.push({
|
|
735
|
-
method: 'GET',
|
|
736
|
-
regex: routesRegex,
|
|
737
|
-
keys: routesKeys,
|
|
738
|
-
name: 'Routes List',
|
|
739
|
-
description: 'Get a simple JSON list of all available API routes',
|
|
740
|
-
handler: async (_req, _res, _ctx) => {
|
|
741
|
-
return {
|
|
742
|
-
plugin: {
|
|
743
|
-
id: this.id,
|
|
744
|
-
name: this.name,
|
|
745
|
-
version: this.version
|
|
746
|
-
},
|
|
747
|
-
routes: this.apiRoutes.map(route => ({
|
|
748
|
-
method: route.method,
|
|
749
|
-
path: route.path,
|
|
750
|
-
name: route.name,
|
|
751
|
-
description: route.description,
|
|
752
|
-
deprecated: route.deprecated,
|
|
753
|
-
tags: route.tags,
|
|
754
|
-
requestSchema: route.requestSchema,
|
|
755
|
-
responseSchema: route.responseSchema
|
|
756
|
-
})),
|
|
757
|
-
discoveryUrl: `${this.context.http.baseUrl}/majk/plugin/api`
|
|
758
|
-
};
|
|
759
|
-
}
|
|
760
|
-
});
|
|
761
|
-
context.logger.info(`📝 Registered routes list: GET ${routesPath}`);
|
|
762
330
|
// Call onReady hook
|
|
763
331
|
if (this.onReadyFn) {
|
|
332
|
+
context.logger.info('');
|
|
764
333
|
context.logger.info('⚙️ Calling onReady hook...');
|
|
765
334
|
await this.onReadyFn(this.context, (fn) => this.cleanups.push(fn));
|
|
766
335
|
context.logger.info('✅ onReady hook completed');
|
|
767
336
|
}
|
|
768
|
-
//
|
|
769
|
-
context.
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
// Function endpoints
|
|
780
|
-
if (this.functionRegistry && this.functionRegistry.getFunctionNames().length > 0) {
|
|
781
|
-
context.logger.info(`║ ║`);
|
|
782
|
-
context.logger.info(`║ 🚀 Function Endpoints: ║`);
|
|
783
|
-
for (const fname of this.functionRegistry.getFunctionNames()) {
|
|
784
|
-
context.logger.info(`║ POST ${context.http.baseUrl}/api/fn/${fname}`);
|
|
337
|
+
// Register RPC service
|
|
338
|
+
if (this.packageName && this.functionRegistry && context.rpc) {
|
|
339
|
+
const functionNames = this.functionRegistry.getFunctionNames();
|
|
340
|
+
context.logger.info('');
|
|
341
|
+
context.logger.info('═══════════════════════════════════════════════════════');
|
|
342
|
+
context.logger.info('📡 Registering RPC Service');
|
|
343
|
+
context.logger.info(` Service Name: ${this.packageName}`);
|
|
344
|
+
context.logger.info(` Methods: ${functionNames.length}`);
|
|
345
|
+
for (const fname of functionNames) {
|
|
346
|
+
const func = this.functionRegistry.functions.get(fname);
|
|
347
|
+
context.logger.info(` • ${fname}${func?.deprecated ? ' (DEPRECATED)' : ''}`);
|
|
785
348
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
349
|
+
context.logger.info('═══════════════════════════════════════════════════════');
|
|
350
|
+
const serviceMethods = {};
|
|
351
|
+
for (const [fname, func] of this.functionRegistry.functions) {
|
|
352
|
+
serviceMethods[fname] = async (input) => {
|
|
353
|
+
const callId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
354
|
+
const startTime = Date.now();
|
|
355
|
+
context.logger.info(`📡 [RPC IN] ${this.packageName}.${fname} (call: ${callId})`);
|
|
356
|
+
context.logger.debug(` Input: ${JSON.stringify(input).substring(0, 200)}`);
|
|
357
|
+
try {
|
|
358
|
+
const result = await func.handler(input, context);
|
|
359
|
+
const duration = Date.now() - startTime;
|
|
360
|
+
context.logger.info(`📡 [RPC OUT] ${this.packageName}.${fname} (call: ${callId}, ${duration}ms) ✅`);
|
|
361
|
+
context.logger.debug(` Output: ${JSON.stringify(result).substring(0, 200)}`);
|
|
362
|
+
return result;
|
|
363
|
+
}
|
|
364
|
+
catch (error) {
|
|
365
|
+
const duration = Date.now() - startTime;
|
|
366
|
+
context.logger.error(`📡 [RPC ERR] ${this.packageName}.${fname} (call: ${callId}, ${duration}ms) ❌`);
|
|
367
|
+
context.logger.error(` Error: ${error.message}`);
|
|
368
|
+
if (error.stack) {
|
|
369
|
+
context.logger.debug(` Stack: ${error.stack}`);
|
|
370
|
+
}
|
|
371
|
+
throw error;
|
|
372
|
+
}
|
|
373
|
+
};
|
|
793
374
|
}
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
context.logger.info(
|
|
803
|
-
context.logger.info(`║ ${context.http.baseUrl}${screen.route}`);
|
|
375
|
+
try {
|
|
376
|
+
context.rpc.registerService(this.packageName, serviceMethods);
|
|
377
|
+
context.logger.info('✅ RPC service registered successfully');
|
|
378
|
+
context.logger.info('');
|
|
379
|
+
}
|
|
380
|
+
catch (error) {
|
|
381
|
+
context.logger.error(`❌ Failed to register RPC service: ${error.message}`);
|
|
382
|
+
context.logger.error(' Functions will still be available via direct invocation');
|
|
383
|
+
context.logger.info('');
|
|
804
384
|
}
|
|
805
385
|
}
|
|
806
|
-
//
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
context.logger.info('╟──────────────────────────────────────────────────────────╢');
|
|
811
|
-
context.logger.info(`║ ${this.wizard.title}`);
|
|
812
|
-
context.logger.info(`║ ${context.http.baseUrl}${this.wizard.path}`);
|
|
813
|
-
}
|
|
814
|
-
// Settings
|
|
815
|
-
if (this.settings) {
|
|
816
|
-
context.logger.info('╟──────────────────────────────────────────────────────────╢');
|
|
817
|
-
context.logger.info('║ ⚙️ SETTINGS ║');
|
|
818
|
-
context.logger.info('╟──────────────────────────────────────────────────────────╢');
|
|
819
|
-
context.logger.info(`║ ${this.settings.title}`);
|
|
820
|
-
context.logger.info(`║ ${context.http.baseUrl}${this.settings.path}`);
|
|
821
|
-
}
|
|
386
|
+
// Print summary
|
|
387
|
+
context.logger.info('');
|
|
388
|
+
context.logger.info('╔══════════════════════════════════════════════════════════╗');
|
|
389
|
+
context.logger.info('║ 🎉 PLUGIN LOADED SUCCESSFULLY ║');
|
|
822
390
|
context.logger.info('╚══════════════════════════════════════════════════════════╝');
|
|
823
391
|
context.logger.info('');
|
|
392
|
+
context.logger.info('ℹ️ Note: HTTP serving is managed by MAJK, not plugin-kit');
|
|
393
|
+
context.logger.info('');
|
|
824
394
|
}
|
|
825
395
|
catch (error) {
|
|
826
396
|
context.logger.error(`❌ Failed to load plugin "${this.name}": ${error.message}`);
|
|
@@ -832,6 +402,18 @@ class BuiltPlugin {
|
|
|
832
402
|
}
|
|
833
403
|
async onUnload() {
|
|
834
404
|
this.context.logger.info(`🛑 Unloading plugin: ${this.name}`);
|
|
405
|
+
// Unregister RPC service
|
|
406
|
+
if (this.packageName && this.context.rpc) {
|
|
407
|
+
try {
|
|
408
|
+
if (typeof this.context.rpc.unregisterService === 'function') {
|
|
409
|
+
this.context.rpc.unregisterService(this.packageName);
|
|
410
|
+
this.context.logger.info(`📡 RPC service unregistered: ${this.packageName}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
catch (error) {
|
|
414
|
+
this.context.logger.error(`Failed to unregister RPC service: ${error.message}`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
835
417
|
// Run cleanup functions
|
|
836
418
|
for (const cleanup of this.cleanups.splice(0)) {
|
|
837
419
|
try {
|
|
@@ -841,17 +423,10 @@ class BuiltPlugin {
|
|
|
841
423
|
this.context.logger.error(`Error during cleanup: ${error.message}`);
|
|
842
424
|
}
|
|
843
425
|
}
|
|
844
|
-
// Stop HTTP server
|
|
845
|
-
if (this.server) {
|
|
846
|
-
await new Promise((resolve) => {
|
|
847
|
-
this.server.close(() => {
|
|
848
|
-
this.context.logger.info('✅ HTTP server stopped');
|
|
849
|
-
resolve();
|
|
850
|
-
});
|
|
851
|
-
});
|
|
852
|
-
}
|
|
853
426
|
this.context.logger.info(`✅ Plugin "${this.name}" unloaded`);
|
|
854
427
|
}
|
|
428
|
+
// isHealthy() - implements both old and new interface
|
|
429
|
+
// Returns compatible format for both interfaces
|
|
855
430
|
async isHealthy() {
|
|
856
431
|
if (this.healthCheckFn) {
|
|
857
432
|
try {
|
|
@@ -860,325 +435,25 @@ class BuiltPlugin {
|
|
|
860
435
|
storage: this.context.storage,
|
|
861
436
|
logger: this.context.logger
|
|
862
437
|
});
|
|
863
|
-
return {
|
|
438
|
+
return {
|
|
439
|
+
healthy: result.healthy,
|
|
440
|
+
checks: result.details,
|
|
441
|
+
errors: [],
|
|
442
|
+
warnings: []
|
|
443
|
+
};
|
|
864
444
|
}
|
|
865
445
|
catch (error) {
|
|
866
446
|
return {
|
|
867
447
|
healthy: false,
|
|
868
|
-
|
|
448
|
+
error: `Health check failed: ${error.message}`,
|
|
449
|
+
errors: [`Health check failed: ${error.message}`],
|
|
450
|
+
warnings: []
|
|
869
451
|
};
|
|
870
452
|
}
|
|
871
453
|
}
|
|
872
|
-
return { healthy: true };
|
|
873
|
-
}
|
|
874
|
-
async startServer() {
|
|
875
|
-
const { port } = this.context.http;
|
|
876
|
-
this.server = http_1.default.createServer(async (req, res) => {
|
|
877
|
-
const startTime = Date.now();
|
|
878
|
-
const method = req.method || 'UNKNOWN';
|
|
879
|
-
const url = req.url || '/';
|
|
880
|
-
try {
|
|
881
|
-
// Log incoming request
|
|
882
|
-
this.context.logger.debug(`→ ${method} ${url}`);
|
|
883
|
-
// CORS preflight
|
|
884
|
-
if (req.method === 'OPTIONS') {
|
|
885
|
-
res.writeHead(204, corsHeaders());
|
|
886
|
-
res.end();
|
|
887
|
-
const duration = Date.now() - startTime;
|
|
888
|
-
this.context.logger.debug(`← ${method} ${url} 204 (${duration}ms)`);
|
|
889
|
-
return;
|
|
890
|
-
}
|
|
891
|
-
// Health check
|
|
892
|
-
if (req.method === 'GET' && req.url === '/health') {
|
|
893
|
-
const health = await this.isHealthy();
|
|
894
|
-
res.writeHead(200, corsHeaders({ 'Content-Type': 'application/json' }));
|
|
895
|
-
res.end(JSON.stringify({ ...health, plugin: this.name, version: this.version }));
|
|
896
|
-
const duration = Date.now() - startTime;
|
|
897
|
-
this.context.logger.debug(`← ${method} ${url} 200 (${duration}ms)`);
|
|
898
|
-
return;
|
|
899
|
-
}
|
|
900
|
-
// MCP Screenshot endpoint
|
|
901
|
-
if (req.method === 'POST' && req.url === '/api/mcp/screenshot') {
|
|
902
|
-
try {
|
|
903
|
-
const body = await readBody(req);
|
|
904
|
-
if (!body || !body.data) {
|
|
905
|
-
res.writeHead(400, corsHeaders({ 'Content-Type': 'application/json' }));
|
|
906
|
-
res.end(JSON.stringify({ error: 'Missing screenshot data' }));
|
|
907
|
-
return;
|
|
908
|
-
}
|
|
909
|
-
// Create screenshots directory if it doesn't exist
|
|
910
|
-
const screenshotsDir = path_1.default.join(this.context.pluginRoot, 'screenshots');
|
|
911
|
-
if (!fs_1.default.existsSync(screenshotsDir)) {
|
|
912
|
-
fs_1.default.mkdirSync(screenshotsDir, { recursive: true });
|
|
913
|
-
}
|
|
914
|
-
// Generate filename
|
|
915
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
916
|
-
const metadata = body.metadata || {};
|
|
917
|
-
const customName = metadata.filename || `screenshot-${timestamp}`;
|
|
918
|
-
const filename = `${customName}.png`;
|
|
919
|
-
const filepath = path_1.default.join(screenshotsDir, filename);
|
|
920
|
-
// Extract base64 data (remove data:image/png;base64, prefix if present)
|
|
921
|
-
const base64Data = body.data.replace(/^data:image\/\w+;base64,/, '');
|
|
922
|
-
const buffer = Buffer.from(base64Data, 'base64');
|
|
923
|
-
// Write file
|
|
924
|
-
fs_1.default.writeFileSync(filepath, buffer);
|
|
925
|
-
// Also save metadata
|
|
926
|
-
const metadataPath = path_1.default.join(screenshotsDir, `${customName}.json`);
|
|
927
|
-
fs_1.default.writeFileSync(metadataPath, JSON.stringify({
|
|
928
|
-
...metadata,
|
|
929
|
-
filename,
|
|
930
|
-
filepath,
|
|
931
|
-
timestamp: new Date().toISOString(),
|
|
932
|
-
size: buffer.length
|
|
933
|
-
}, null, 2));
|
|
934
|
-
// Log with full details
|
|
935
|
-
const absolutePath = path_1.default.resolve(filepath);
|
|
936
|
-
const absoluteMetadataPath = path_1.default.resolve(metadataPath);
|
|
937
|
-
this.context.logger.info(`📸 Screenshot saved successfully:`);
|
|
938
|
-
this.context.logger.info(` Image: ${absolutePath}`);
|
|
939
|
-
this.context.logger.info(` Metadata: ${absoluteMetadataPath}`);
|
|
940
|
-
this.context.logger.info(` Size: ${(buffer.length / 1024).toFixed(2)} KB`);
|
|
941
|
-
res.writeHead(200, corsHeaders({ 'Content-Type': 'application/json' }));
|
|
942
|
-
res.end(JSON.stringify({
|
|
943
|
-
success: true,
|
|
944
|
-
filename,
|
|
945
|
-
filepath,
|
|
946
|
-
size: buffer.length,
|
|
947
|
-
message: 'Screenshot saved successfully'
|
|
948
|
-
}));
|
|
949
|
-
const duration = Date.now() - startTime;
|
|
950
|
-
this.context.logger.debug(`← ${method} ${url} 200 (${duration}ms)`);
|
|
951
|
-
return;
|
|
952
|
-
}
|
|
953
|
-
catch (error) {
|
|
954
|
-
this.context.logger.error(`Failed to save screenshot: ${error.message}`);
|
|
955
|
-
res.writeHead(500, corsHeaders({ 'Content-Type': 'application/json' }));
|
|
956
|
-
res.end(JSON.stringify({
|
|
957
|
-
success: false,
|
|
958
|
-
error: error.message || 'Failed to save screenshot'
|
|
959
|
-
}));
|
|
960
|
-
return;
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
// HTML screens virtual route
|
|
964
|
-
if (req.method === 'GET' && req.url?.startsWith('/__html/')) {
|
|
965
|
-
const id = decodeURIComponent(req.url.substring('/__html/'.length)).split('?')[0];
|
|
966
|
-
const screen = this.htmlScreens.find(s => s.id === id);
|
|
967
|
-
if (!screen) {
|
|
968
|
-
res.writeHead(404, corsHeaders({ 'Content-Type': 'text/plain' }));
|
|
969
|
-
res.end('HTML screen not found');
|
|
970
|
-
const duration = Date.now() - startTime;
|
|
971
|
-
this.context.logger.debug(`← ${method} ${url} 404 (${duration}ms)`);
|
|
972
|
-
return;
|
|
973
|
-
}
|
|
974
|
-
res.writeHead(200, corsHeaders({ 'Content-Type': 'text/html; charset=utf-8' }));
|
|
975
|
-
let htmlContent;
|
|
976
|
-
if ('html' in screen) {
|
|
977
|
-
htmlContent = screen.html;
|
|
978
|
-
}
|
|
979
|
-
else {
|
|
980
|
-
const filePath = path_1.default.join(this.context.pluginRoot, screen.htmlFile);
|
|
981
|
-
// Security check
|
|
982
|
-
const normalized = path_1.default.resolve(filePath);
|
|
983
|
-
const pluginRoot = path_1.default.resolve(this.context.pluginRoot);
|
|
984
|
-
if (!normalized.startsWith(pluginRoot)) {
|
|
985
|
-
res.writeHead(403, corsHeaders({ 'Content-Type': 'text/plain' }));
|
|
986
|
-
res.end('Forbidden');
|
|
987
|
-
this.context.logger.warn(`Path traversal attempt: ${screen.htmlFile}`);
|
|
988
|
-
const duration = Date.now() - startTime;
|
|
989
|
-
this.context.logger.debug(`← ${method} ${url} 403 (${duration}ms)`);
|
|
990
|
-
return;
|
|
991
|
-
}
|
|
992
|
-
try {
|
|
993
|
-
htmlContent = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
994
|
-
}
|
|
995
|
-
catch (error) {
|
|
996
|
-
this.context.logger.error(`Failed to read HTML file: ${error.message}`);
|
|
997
|
-
res.writeHead(500, corsHeaders({ 'Content-Type': 'text/html' }));
|
|
998
|
-
res.end('<!doctype html><html><body><h1>Error loading screen</h1></body></html>');
|
|
999
|
-
const duration = Date.now() - startTime;
|
|
1000
|
-
this.context.logger.debug(`← ${method} ${url} 500 (${duration}ms)`);
|
|
1001
|
-
return;
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
// Inject html2canvas and MCP DOM Agent script into HTML screens
|
|
1005
|
-
if (BuiltPlugin.mcpDomAgentScript) {
|
|
1006
|
-
const html2canvasScript = BuiltPlugin.html2canvasScript
|
|
1007
|
-
? `<script>${BuiltPlugin.html2canvasScript}</script>`
|
|
1008
|
-
: '';
|
|
1009
|
-
const mcpScript = `<script>${BuiltPlugin.mcpDomAgentScript}</script>`;
|
|
1010
|
-
const allScripts = html2canvasScript + mcpScript;
|
|
1011
|
-
// Debug logging
|
|
1012
|
-
if (html2canvasScript) {
|
|
1013
|
-
this.context.logger.debug(`[HTML Screen] Injecting html2canvas (${(BuiltPlugin.html2canvasScript?.length || 0) / 1024} KB)`);
|
|
1014
|
-
}
|
|
1015
|
-
else {
|
|
1016
|
-
this.context.logger.warn('[HTML Screen] html2canvas NOT injected - script not loaded');
|
|
1017
|
-
}
|
|
1018
|
-
this.context.logger.debug('[HTML Screen] Injecting MCP DOM Agent');
|
|
1019
|
-
// Try to inject before </body>, fallback to </head>, fallback to end of HTML
|
|
1020
|
-
if (htmlContent.includes('</body>')) {
|
|
1021
|
-
htmlContent = htmlContent.replace('</body>', `${allScripts}</body>`);
|
|
1022
|
-
}
|
|
1023
|
-
else if (htmlContent.includes('</head>')) {
|
|
1024
|
-
htmlContent = htmlContent.replace('</head>', `${allScripts}</head>`);
|
|
1025
|
-
}
|
|
1026
|
-
else {
|
|
1027
|
-
htmlContent += allScripts;
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
res.end(htmlContent);
|
|
1031
|
-
const duration = Date.now() - startTime;
|
|
1032
|
-
this.context.logger.debug(`← ${method} ${url} 200 (${duration}ms)`);
|
|
1033
|
-
return;
|
|
1034
|
-
}
|
|
1035
|
-
// React SPA
|
|
1036
|
-
if (req.method === 'GET' && this.uiConfig && req.url?.startsWith(this.uiConfig.base)) {
|
|
1037
|
-
serveSpa(this.uiConfig, req, res, this.context);
|
|
1038
|
-
const duration = Date.now() - startTime;
|
|
1039
|
-
this.context.logger.debug(`← ${method} ${url} ${res.statusCode || 200} (${duration}ms)`);
|
|
1040
|
-
return;
|
|
1041
|
-
}
|
|
1042
|
-
// Function calls (new function-first API)
|
|
1043
|
-
if (req.method === 'POST' && req.url?.startsWith('/api/fn/')) {
|
|
1044
|
-
const functionName = req.url.substring('/api/fn/'.length).split('?')[0];
|
|
1045
|
-
if (this.functionRegistry && this.transports.length > 0) {
|
|
1046
|
-
// Use the first HTTP transport to handle the function call
|
|
1047
|
-
const httpTransport = this.transports.find(t => t.name === 'http');
|
|
1048
|
-
if (httpTransport) {
|
|
1049
|
-
const body = await readBody(req);
|
|
1050
|
-
const request = {
|
|
1051
|
-
method: 'POST',
|
|
1052
|
-
url: req.url,
|
|
1053
|
-
pathname: req.url.split('?')[0],
|
|
1054
|
-
query: new URL(req.url, `http://localhost:${port}`).searchParams,
|
|
1055
|
-
params: {},
|
|
1056
|
-
body,
|
|
1057
|
-
headers: req.headers
|
|
1058
|
-
};
|
|
1059
|
-
const response = makeResponse(res);
|
|
1060
|
-
await httpTransport.handleFunctionCall(functionName, request, response, {
|
|
1061
|
-
majk: this.context.majk,
|
|
1062
|
-
storage: this.context.storage,
|
|
1063
|
-
logger: this.context.logger,
|
|
1064
|
-
http: this.context.http
|
|
1065
|
-
});
|
|
1066
|
-
const duration = Date.now() - startTime;
|
|
1067
|
-
this.context.logger.debug(`← POST /api/fn/${functionName} ${res.statusCode || 200} (${duration}ms)`);
|
|
1068
|
-
return;
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
// Function discovery endpoint
|
|
1073
|
-
if (req.method === 'GET' && req.url === '/api/fn/discovery') {
|
|
1074
|
-
if (this.functionRegistry && this.transports.length > 0) {
|
|
1075
|
-
const httpTransport = this.transports.find(t => t.name === 'http');
|
|
1076
|
-
if (httpTransport) {
|
|
1077
|
-
const discovery = httpTransport.getDiscovery();
|
|
1078
|
-
res.writeHead(200, corsHeaders({ 'Content-Type': 'application/json' }));
|
|
1079
|
-
res.end(JSON.stringify(discovery));
|
|
1080
|
-
const duration = Date.now() - startTime;
|
|
1081
|
-
this.context.logger.debug(`← GET /api/fn/discovery 200 (${duration}ms)`);
|
|
1082
|
-
return;
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
// API routes (including discovery endpoints) - legacy
|
|
1087
|
-
if (req.url?.startsWith('/api/') || req.url?.startsWith('/majk/plugin/')) {
|
|
1088
|
-
const url = new URL(req.url, `http://localhost:${port}`);
|
|
1089
|
-
const pathname = url.pathname;
|
|
1090
|
-
const method = req.method;
|
|
1091
|
-
this.context.logger.info(`[ROUTE MATCHING] Incoming: ${method} ${pathname}`);
|
|
1092
|
-
this.context.logger.info(`[ROUTE MATCHING] Available routes: ${this.router.length}`);
|
|
1093
|
-
this.router.forEach((r, idx) => {
|
|
1094
|
-
const matches = r.regex.test(pathname);
|
|
1095
|
-
this.context.logger.info(`[ROUTE MATCHING] Route ${idx}: ${r.method} ${r.name} - regex: ${r.regex} - keys: [${r.keys.join(', ')}] - matches: ${matches}`);
|
|
1096
|
-
});
|
|
1097
|
-
const route = this.router.find(r => r.method === method && r.regex.test(pathname));
|
|
1098
|
-
if (!route) {
|
|
1099
|
-
res.writeHead(404, corsHeaders({ 'Content-Type': 'application/json' }));
|
|
1100
|
-
res.end(JSON.stringify({
|
|
1101
|
-
error: 'Route not found',
|
|
1102
|
-
path: pathname,
|
|
1103
|
-
method,
|
|
1104
|
-
hint: `Available routes: ${this.router.map(r => `${r.method} ${r.name}`).join(', ')}`
|
|
1105
|
-
}));
|
|
1106
|
-
const duration = Date.now() - startTime;
|
|
1107
|
-
this.context.logger.debug(`← ${method} ${pathname} 404 (${duration}ms)`);
|
|
1108
|
-
return;
|
|
1109
|
-
}
|
|
1110
|
-
const body = await readBody(req);
|
|
1111
|
-
const match = pathname.match(route.regex);
|
|
1112
|
-
const params = {};
|
|
1113
|
-
route.keys.forEach((key, i) => {
|
|
1114
|
-
params[key] = decodeURIComponent(match[i + 1]);
|
|
1115
|
-
});
|
|
1116
|
-
const request = {
|
|
1117
|
-
method,
|
|
1118
|
-
url: req.url,
|
|
1119
|
-
pathname,
|
|
1120
|
-
query: url.searchParams,
|
|
1121
|
-
params,
|
|
1122
|
-
body,
|
|
1123
|
-
headers: req.headers
|
|
1124
|
-
};
|
|
1125
|
-
const response = makeResponse(res);
|
|
1126
|
-
const routeContext = {
|
|
1127
|
-
majk: this.context.majk,
|
|
1128
|
-
storage: this.context.storage,
|
|
1129
|
-
logger: this.context.logger,
|
|
1130
|
-
http: this.context.http
|
|
1131
|
-
};
|
|
1132
|
-
try {
|
|
1133
|
-
const result = await route.handler(request, response, routeContext);
|
|
1134
|
-
if (!res.headersSent) {
|
|
1135
|
-
res.writeHead(200, corsHeaders({ 'Content-Type': 'application/json' }));
|
|
1136
|
-
res.end(JSON.stringify(result ?? { success: true }));
|
|
1137
|
-
}
|
|
1138
|
-
const duration = Date.now() - startTime;
|
|
1139
|
-
this.context.logger.debug(`← ${method} ${pathname} ${res.statusCode || 200} (${duration}ms)`);
|
|
1140
|
-
}
|
|
1141
|
-
catch (error) {
|
|
1142
|
-
this.context.logger.error(`API route error: ${method} ${pathname} - ${error.message}`);
|
|
1143
|
-
if (error.stack) {
|
|
1144
|
-
this.context.logger.error(error.stack);
|
|
1145
|
-
}
|
|
1146
|
-
if (!res.headersSent) {
|
|
1147
|
-
res.writeHead(500, corsHeaders({ 'Content-Type': 'application/json' }));
|
|
1148
|
-
res.end(JSON.stringify({
|
|
1149
|
-
error: error.message || 'Internal server error',
|
|
1150
|
-
route: route.name,
|
|
1151
|
-
path: pathname
|
|
1152
|
-
}));
|
|
1153
|
-
}
|
|
1154
|
-
const duration = Date.now() - startTime;
|
|
1155
|
-
this.context.logger.debug(`← ${method} ${pathname} 500 (${duration}ms)`);
|
|
1156
|
-
}
|
|
1157
|
-
return;
|
|
1158
|
-
}
|
|
1159
|
-
// 404
|
|
1160
|
-
res.writeHead(404, corsHeaders({ 'Content-Type': 'application/json' }));
|
|
1161
|
-
res.end(JSON.stringify({ error: 'Not found', path: req.url }));
|
|
1162
|
-
}
|
|
1163
|
-
catch (error) {
|
|
1164
|
-
this.context.logger.error(`Unhandled server error: ${error.message}`);
|
|
1165
|
-
if (!res.headersSent) {
|
|
1166
|
-
res.writeHead(500, corsHeaders({ 'Content-Type': 'text/plain' }));
|
|
1167
|
-
res.end('Internal server error');
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
});
|
|
1171
|
-
await new Promise((resolve) => {
|
|
1172
|
-
this.server.listen(port, () => {
|
|
1173
|
-
this.context.logger.info(`✅ HTTP server listening on port ${port}`);
|
|
1174
|
-
resolve();
|
|
1175
|
-
});
|
|
1176
|
-
});
|
|
454
|
+
return { healthy: true, errors: [], warnings: [] };
|
|
1177
455
|
}
|
|
1178
456
|
}
|
|
1179
|
-
// MCP DOM Agent script (loaded once at startup, accessible to serveSpa function)
|
|
1180
|
-
BuiltPlugin.mcpDomAgentScript = null;
|
|
1181
|
-
BuiltPlugin.html2canvasScript = null;
|
|
1182
457
|
/**
|
|
1183
458
|
* Group tools by scope
|
|
1184
459
|
*/
|
|
@@ -1211,7 +486,6 @@ function definePlugin(id, name, version) {
|
|
|
1211
486
|
const _secretProviders = [];
|
|
1212
487
|
// Function-first architecture state
|
|
1213
488
|
const _functionRegistry = new registry_1.FunctionRegistryImpl(id, name, version);
|
|
1214
|
-
const _transports = [];
|
|
1215
489
|
let _clientConfig = null;
|
|
1216
490
|
let _ui = { appDir: 'ui/dist', base: '/', history: 'browser' };
|
|
1217
491
|
let _uiConfigured = false;
|
|
@@ -1315,17 +589,32 @@ function definePlugin(id, name, version) {
|
|
|
1315
589
|
if (!screen.route.startsWith(`/plugin-screens/${id}/`)) {
|
|
1316
590
|
throw new PluginBuildError(`React screen route must start with "/plugin-screens/${id}/"`, `Change route from "${screen.route}" to "/plugin-screens/${id}/your-route"`, { screen: screen.id, route: screen.route });
|
|
1317
591
|
}
|
|
1318
|
-
|
|
592
|
+
// Validate that at least one of the path properties is provided
|
|
593
|
+
if (!screen.pluginPath && !screen.reactPath) {
|
|
594
|
+
throw new PluginBuildError(`React screen "${screen.name}" must provide either pluginPath or reactPath (deprecated)`, `Add pluginPath: '/ui/index.html' to your screen definition`, { screen: screen.id });
|
|
595
|
+
}
|
|
596
|
+
// Warn if using deprecated properties
|
|
597
|
+
if (screen.reactPath) {
|
|
598
|
+
log(` ⚠️ Warning: reactPath is deprecated for screen "${screen.name}". Use pluginPath instead.`);
|
|
599
|
+
}
|
|
600
|
+
if (screen.hash) {
|
|
601
|
+
log(` ⚠️ Warning: hash is deprecated for screen "${screen.name}". Use pluginPathHash instead.`);
|
|
602
|
+
}
|
|
1319
603
|
_reactScreens.push(screen);
|
|
1320
604
|
log(` 🕹 Registered React screen: ${screen.name} at ${screen.route}`);
|
|
1321
605
|
const capability = {
|
|
1322
606
|
type: 'screen',
|
|
1323
607
|
id: screen.id,
|
|
1324
|
-
path: screen.route
|
|
1325
|
-
iframeUrl
|
|
608
|
+
path: screen.route
|
|
1326
609
|
};
|
|
1327
|
-
|
|
1328
|
-
|
|
610
|
+
// Prefer new properties, fallback to deprecated ones
|
|
611
|
+
const effectivePluginPath = screen.pluginPath || screen.reactPath;
|
|
612
|
+
const effectivePluginPathHash = screen.pluginPathHash || screen.hash;
|
|
613
|
+
if (effectivePluginPath) {
|
|
614
|
+
capability.pluginPath = effectivePluginPath;
|
|
615
|
+
}
|
|
616
|
+
if (effectivePluginPathHash) {
|
|
617
|
+
capability.pluginPathHash = effectivePluginPathHash;
|
|
1329
618
|
}
|
|
1330
619
|
_capabilities.push(capability);
|
|
1331
620
|
return this;
|
|
@@ -1335,15 +624,22 @@ function definePlugin(id, name, version) {
|
|
|
1335
624
|
if (!screen.route.startsWith(`/plugin-screens/${id}/`)) {
|
|
1336
625
|
throw new PluginBuildError(`HTML screen route must start with "/plugin-screens/${id}/"`, `Change route from "${screen.route}" to "/plugin-screens/${id}/your-route"`, { screen: screen.id, route: screen.route });
|
|
1337
626
|
}
|
|
1338
|
-
const iframeUrl = `http://localhost:{port}/__html/${encodeURIComponent(screen.id)}`;
|
|
1339
627
|
_htmlScreens.push(screen);
|
|
1340
628
|
log(` 📋 Registered HTML screen: ${screen.name} at ${screen.route}`);
|
|
1341
|
-
|
|
629
|
+
const capability = {
|
|
1342
630
|
type: 'screen',
|
|
1343
631
|
id: screen.id,
|
|
1344
632
|
path: screen.route,
|
|
1345
|
-
|
|
1346
|
-
}
|
|
633
|
+
pluginRoute: `/__html/${encodeURIComponent(screen.id)}`
|
|
634
|
+
};
|
|
635
|
+
// Add pluginPath and pluginPathHash if present
|
|
636
|
+
if (screen.pluginPath) {
|
|
637
|
+
capability.pluginPath = screen.pluginPath;
|
|
638
|
+
}
|
|
639
|
+
if (screen.pluginPathHash) {
|
|
640
|
+
capability.pluginPathHash = screen.pluginPathHash;
|
|
641
|
+
}
|
|
642
|
+
_capabilities.push(capability);
|
|
1347
643
|
return this;
|
|
1348
644
|
},
|
|
1349
645
|
apiRoute(route) {
|
|
@@ -1408,7 +704,7 @@ function definePlugin(id, name, version) {
|
|
|
1408
704
|
return this;
|
|
1409
705
|
},
|
|
1410
706
|
transport(transport) {
|
|
1411
|
-
|
|
707
|
+
log('⚠️ WARNING: .transport() is deprecated in v2.0.0. HTTP serving is now managed by MAJK. This call does nothing.');
|
|
1412
708
|
return this;
|
|
1413
709
|
},
|
|
1414
710
|
generateClient(config) {
|
|
@@ -1579,6 +875,25 @@ function definePlugin(id, name, version) {
|
|
|
1579
875
|
return this;
|
|
1580
876
|
},
|
|
1581
877
|
build() {
|
|
878
|
+
// ========== Load Package Info ==========
|
|
879
|
+
let packageName;
|
|
880
|
+
let packageVersion;
|
|
881
|
+
if (_pluginRoot) {
|
|
882
|
+
try {
|
|
883
|
+
const packageJsonPath = path_1.default.resolve(_pluginRoot, 'package.json');
|
|
884
|
+
if (fs_1.default.existsSync(packageJsonPath)) {
|
|
885
|
+
const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'));
|
|
886
|
+
packageName = packageJson.name;
|
|
887
|
+
packageVersion = packageJson.version;
|
|
888
|
+
if (packageName) {
|
|
889
|
+
log(` 📦 Package: ${packageName}@${packageVersion}`);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
catch (error) {
|
|
894
|
+
log(` ⚠️ Could not read package.json: ${error.message}`);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
1582
897
|
// ========== Build-Time Validation ==========
|
|
1583
898
|
// Validate configurable entities require configWizard with schema
|
|
1584
899
|
if (_configurableEntities.length > 0) {
|
|
@@ -1723,19 +1038,34 @@ function definePlugin(id, name, version) {
|
|
|
1723
1038
|
}
|
|
1724
1039
|
// Add wizard
|
|
1725
1040
|
if (_wizard) {
|
|
1041
|
+
// Validate that at least one path property is provided
|
|
1042
|
+
if (!_wizard.pluginPath && !_wizard.path) {
|
|
1043
|
+
throw new PluginBuildError('Config wizard must provide either pluginPath or path (deprecated)', 'Add pluginPath: "/main" to your configWizard definition', { wizard: _wizard.title });
|
|
1044
|
+
}
|
|
1045
|
+
// Warn if using deprecated properties
|
|
1046
|
+
if (_wizard.path) {
|
|
1047
|
+
log(` ⚠️ Warning: path is deprecated in configWizard. Use pluginPath instead.`);
|
|
1048
|
+
}
|
|
1049
|
+
if (_wizard.hash) {
|
|
1050
|
+
log(` ⚠️ Warning: hash is deprecated in configWizard. Use pluginPathHash instead.`);
|
|
1051
|
+
}
|
|
1726
1052
|
const wizardCap = {
|
|
1727
1053
|
type: 'config-wizard',
|
|
1728
1054
|
screen: {
|
|
1729
|
-
path: _wizard.path,
|
|
1730
1055
|
title: _wizard.title,
|
|
1731
1056
|
width: _wizard.width,
|
|
1732
1057
|
height: _wizard.height
|
|
1733
1058
|
},
|
|
1734
1059
|
required: true
|
|
1735
1060
|
};
|
|
1736
|
-
//
|
|
1737
|
-
|
|
1738
|
-
|
|
1061
|
+
// Prefer new properties, fallback to deprecated ones
|
|
1062
|
+
const effectivePluginPath = _wizard.pluginPath || _wizard.path;
|
|
1063
|
+
const effectivePluginPathHash = _wizard.pluginPathHash || _wizard.hash;
|
|
1064
|
+
if (effectivePluginPath) {
|
|
1065
|
+
wizardCap.screen.pluginPath = effectivePluginPath;
|
|
1066
|
+
}
|
|
1067
|
+
if (effectivePluginPathHash) {
|
|
1068
|
+
wizardCap.screen.pluginPathHash = effectivePluginPathHash;
|
|
1739
1069
|
}
|
|
1740
1070
|
// Add shouldShow as method name reference if provided
|
|
1741
1071
|
if (_wizard.shouldShow) {
|
|
@@ -1745,28 +1075,56 @@ function definePlugin(id, name, version) {
|
|
|
1745
1075
|
}
|
|
1746
1076
|
// Add settings
|
|
1747
1077
|
if (_settings) {
|
|
1078
|
+
// Validate that at least one path property is provided
|
|
1079
|
+
if (!_settings.pluginPath && !_settings.path) {
|
|
1080
|
+
throw new PluginBuildError('Settings must provide either pluginPath or path (deprecated)', 'Add pluginPath: "/settings" to your settings definition', { settings: _settings.title });
|
|
1081
|
+
}
|
|
1082
|
+
// Warn if using deprecated properties
|
|
1083
|
+
if (_settings.path) {
|
|
1084
|
+
log(` ⚠️ Warning: path is deprecated in settings. Use pluginPath instead.`);
|
|
1085
|
+
}
|
|
1086
|
+
if (_settings.hash) {
|
|
1087
|
+
log(` ⚠️ Warning: hash is deprecated in settings. Use pluginPathHash instead.`);
|
|
1088
|
+
}
|
|
1748
1089
|
const settingsCap = {
|
|
1749
1090
|
type: 'settings-screen',
|
|
1750
1091
|
path: `/plugins/${id}/settings`,
|
|
1751
1092
|
name: _settings.title,
|
|
1752
1093
|
screen: {
|
|
1753
|
-
path: _settings.path,
|
|
1754
1094
|
title: _settings.title
|
|
1755
1095
|
}
|
|
1756
1096
|
};
|
|
1757
|
-
//
|
|
1758
|
-
|
|
1759
|
-
|
|
1097
|
+
// Prefer new properties, fallback to deprecated ones
|
|
1098
|
+
const effectivePluginPath = _settings.pluginPath || _settings.path;
|
|
1099
|
+
const effectivePluginPathHash = _settings.pluginPathHash || _settings.hash;
|
|
1100
|
+
if (effectivePluginPath) {
|
|
1101
|
+
settingsCap.screen.pluginPath = effectivePluginPath;
|
|
1102
|
+
}
|
|
1103
|
+
if (effectivePluginPathHash) {
|
|
1104
|
+
settingsCap.screen.pluginPathHash = effectivePluginPathHash;
|
|
1760
1105
|
}
|
|
1761
1106
|
_capabilities.push(settingsCap);
|
|
1762
1107
|
}
|
|
1108
|
+
// Add functions
|
|
1109
|
+
for (const [fname, func] of _functionRegistry.functions) {
|
|
1110
|
+
const functionCap = {
|
|
1111
|
+
type: 'function',
|
|
1112
|
+
name: fname,
|
|
1113
|
+
description: func.description,
|
|
1114
|
+
input: func.input,
|
|
1115
|
+
output: func.output
|
|
1116
|
+
};
|
|
1117
|
+
if (func.tags && func.tags.length > 0) {
|
|
1118
|
+
functionCap.tags = func.tags;
|
|
1119
|
+
}
|
|
1120
|
+
if (packageName) {
|
|
1121
|
+
functionCap.serviceName = packageName;
|
|
1122
|
+
}
|
|
1123
|
+
_capabilities.push(functionCap);
|
|
1124
|
+
}
|
|
1763
1125
|
// ========== Build Plugin Class ==========
|
|
1764
1126
|
// MAJK expects a class/constructor, not an instance
|
|
1765
1127
|
// Return a class that instantiates BuiltPlugin
|
|
1766
|
-
// If no transports specified but functions were defined, add default HTTP transport
|
|
1767
|
-
if (_functionRegistry.functions.size > 0 && _transports.length === 0) {
|
|
1768
|
-
_transports.push(new transports_1.HttpTransport());
|
|
1769
|
-
}
|
|
1770
1128
|
// TODO: Implement client generation if configured
|
|
1771
1129
|
if (_clientConfig) {
|
|
1772
1130
|
// This would generate the client files based on the function registry
|
|
@@ -1815,12 +1173,14 @@ function definePlugin(id, name, version) {
|
|
|
1815
1173
|
return class extends BuiltPlugin {
|
|
1816
1174
|
constructor() {
|
|
1817
1175
|
super(id, name, version, _capabilities, _tools, _apiRoutes, _uiConfigured ? _ui : null, _reactScreens, _htmlScreens, _wizard, _settings, _onReady, _healthCheck, _pluginRoot,
|
|
1818
|
-
// Pass function registry
|
|
1819
|
-
_functionRegistry.functions.size > 0 ? _functionRegistry : undefined,
|
|
1176
|
+
// Pass function registry if functions were defined
|
|
1177
|
+
_functionRegistry.functions.size > 0 ? _functionRegistry : undefined,
|
|
1820
1178
|
// Pass configurable entities
|
|
1821
1179
|
_configurableEntities.length > 0 ? _configurableEntities : undefined,
|
|
1822
1180
|
// Pass secret providers
|
|
1823
|
-
_secretProviders.length > 0 ? _secretProviders : undefined
|
|
1181
|
+
_secretProviders.length > 0 ? _secretProviders : undefined,
|
|
1182
|
+
// Pass package name for RPC service registration
|
|
1183
|
+
packageName);
|
|
1824
1184
|
}
|
|
1825
1185
|
};
|
|
1826
1186
|
}
|