@majkapp/plugin-kit 1.0.17 → 1.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/dist/generator/cli.d.ts +3 -0
- package/dist/generator/cli.d.ts.map +1 -0
- package/dist/generator/cli.js +161 -0
- package/dist/generator/generator.d.ts +6 -0
- package/dist/generator/generator.d.ts.map +1 -0
- package/dist/generator/generator.js +389 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/plugin-kit.d.ts +28 -2
- package/dist/plugin-kit.d.ts.map +1 -1
- package/dist/plugin-kit.js +622 -19
- package/dist/registry.d.ts +38 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +143 -0
- package/dist/transports.d.ts +59 -0
- package/dist/transports.d.ts.map +1 -0
- package/dist/transports.js +171 -0
- package/dist/types.d.ts +85 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +10 -5
package/dist/plugin-kit.js
CHANGED
|
@@ -7,6 +7,16 @@ exports.definePlugin = definePlugin;
|
|
|
7
7
|
const http_1 = __importDefault(require("http"));
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const registry_1 = require("./registry");
|
|
11
|
+
const transports_1 = require("./transports");
|
|
12
|
+
/**
|
|
13
|
+
* Conditional logging - suppressed in extract mode
|
|
14
|
+
*/
|
|
15
|
+
const log = (message) => {
|
|
16
|
+
if (process.env.PLUGIN_KIT_MODE !== 'extract') {
|
|
17
|
+
console.log(message);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
10
20
|
/**
|
|
11
21
|
* Build-time errors with clear, actionable messages
|
|
12
22
|
*/
|
|
@@ -143,25 +153,132 @@ function validateEntity(entityType, entity) {
|
|
|
143
153
|
}
|
|
144
154
|
return errors;
|
|
145
155
|
}
|
|
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
|
+
}
|
|
146
263
|
/**
|
|
147
264
|
* Path parameter regex builder
|
|
148
265
|
*/
|
|
149
266
|
function pathToRegex(p) {
|
|
150
267
|
const keys = [];
|
|
151
|
-
|
|
268
|
+
log(`[pathToRegex] Input path: ${p}`);
|
|
152
269
|
// First, escape special regex characters EXCEPT the colon (for path params)
|
|
153
270
|
const escaped = p.replace(/([.+*?=^!${}()[\]|\\])/g, '\\$1');
|
|
154
|
-
|
|
271
|
+
log(`[pathToRegex] After escaping: ${escaped}`);
|
|
155
272
|
// Then, replace path parameters with capturing groups
|
|
156
273
|
const pattern = escaped.replace(/\/:([A-Za-z0-9_]+)/g, (_m, k) => {
|
|
157
274
|
keys.push(k);
|
|
158
|
-
|
|
275
|
+
log(`[pathToRegex] Found param: ${k}`);
|
|
159
276
|
return '/([^/]+)';
|
|
160
277
|
});
|
|
161
|
-
|
|
162
|
-
|
|
278
|
+
log(`[pathToRegex] Final pattern: ${pattern}`);
|
|
279
|
+
log(`[pathToRegex] Keys: [${keys.join(', ')}]`);
|
|
163
280
|
const regex = new RegExp(`^${pattern}$`);
|
|
164
|
-
|
|
281
|
+
log(`[pathToRegex] Final regex: ${regex}`);
|
|
165
282
|
return { regex, keys };
|
|
166
283
|
}
|
|
167
284
|
/**
|
|
@@ -290,7 +407,11 @@ function serveSpa(ui, req, res, ctx) {
|
|
|
290
407
|
* Main plugin class built by the fluent API
|
|
291
408
|
*/
|
|
292
409
|
class BuiltPlugin {
|
|
293
|
-
constructor(id, name, version, capabilities, tools, apiRoutes, uiConfig, reactScreens, htmlScreens, wizard, onReadyFn, healthCheckFn, userProvidedPluginRoot
|
|
410
|
+
constructor(id, name, version, capabilities, tools, apiRoutes, uiConfig, reactScreens, htmlScreens, wizard, settings, onReadyFn, healthCheckFn, userProvidedPluginRoot,
|
|
411
|
+
// New parameters for function-first architecture
|
|
412
|
+
functionRegistry, transports,
|
|
413
|
+
// New parameter for configurable entities
|
|
414
|
+
configurableEntities) {
|
|
294
415
|
this.capabilities = capabilities;
|
|
295
416
|
this.tools = tools;
|
|
296
417
|
this.apiRoutes = apiRoutes;
|
|
@@ -298,19 +419,74 @@ class BuiltPlugin {
|
|
|
298
419
|
this.reactScreens = reactScreens;
|
|
299
420
|
this.htmlScreens = htmlScreens;
|
|
300
421
|
this.wizard = wizard;
|
|
422
|
+
this.settings = settings;
|
|
301
423
|
this.onReadyFn = onReadyFn;
|
|
302
424
|
this.healthCheckFn = healthCheckFn;
|
|
303
425
|
this.userProvidedPluginRoot = userProvidedPluginRoot;
|
|
426
|
+
this.configurableEntities = configurableEntities;
|
|
304
427
|
this.cleanups = [];
|
|
305
428
|
this.router = [];
|
|
429
|
+
this.transports = [];
|
|
306
430
|
this.id = id;
|
|
307
431
|
this.name = name;
|
|
308
432
|
this.version = version;
|
|
433
|
+
this.functionRegistry = functionRegistry;
|
|
434
|
+
this.transports = transports || [];
|
|
435
|
+
this.configurableEntities = configurableEntities || [];
|
|
309
436
|
}
|
|
310
|
-
getCapabilities() {
|
|
437
|
+
async getCapabilities() {
|
|
438
|
+
// Start with static capabilities
|
|
439
|
+
const dynamicCapabilities = [...this.capabilities];
|
|
440
|
+
// Only process configurable entities if context is available (after onLoad has been called)
|
|
441
|
+
if (!this.context) {
|
|
442
|
+
// Context not set yet - return only static capabilities
|
|
443
|
+
// This happens when getCapabilities() is called before onLoad()
|
|
444
|
+
return {
|
|
445
|
+
apiVersion: 'majk.capabilities/v1',
|
|
446
|
+
capabilities: dynamicCapabilities
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
// If wizard has schema, check for config and add configurable entities
|
|
450
|
+
if (this.wizard?.schema && this.configurableEntities && this.configurableEntities.length > 0) {
|
|
451
|
+
const storageKey = this.wizard.storageKey || '_plugin_config';
|
|
452
|
+
this.context.logger.info(`[ConfigWizard] getCapabilities: checking for config at key: ${storageKey}`);
|
|
453
|
+
try {
|
|
454
|
+
// Load config from storage
|
|
455
|
+
const config = await this.context.storage.get(storageKey);
|
|
456
|
+
if (config) {
|
|
457
|
+
this.context.logger.info(`[ConfigWizard] ✅ Config found - generating configurable entities`);
|
|
458
|
+
this.context.logger.info(`[ConfigWizard] Config:`, JSON.stringify(config, null, 2));
|
|
459
|
+
// Call each configurable entity factory with the config
|
|
460
|
+
for (const ce of this.configurableEntities) {
|
|
461
|
+
this.context.logger.info(`[ConfigWizard] Calling factory for ${ce.entityType}...`);
|
|
462
|
+
try {
|
|
463
|
+
const entities = ce.factory(config);
|
|
464
|
+
this.context.logger.info(`[ConfigWizard] Factory returned ${entities.length} ${ce.entityType} entities`);
|
|
465
|
+
if (entities.length > 0) {
|
|
466
|
+
dynamicCapabilities.push({
|
|
467
|
+
type: 'entity',
|
|
468
|
+
entityType: ce.entityType,
|
|
469
|
+
entities: entities
|
|
470
|
+
});
|
|
471
|
+
this.context.logger.info(`[ConfigWizard] ✅ Added ${entities.length} ${ce.entityType} entities to capabilities`);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
catch (factoryError) {
|
|
475
|
+
this.context.logger.error(`[ConfigWizard] ❌ Factory for ${ce.entityType} failed:`, factoryError.message);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
this.context.logger.info(`[ConfigWizard] ⚠️ No config found - configurable entities will not be registered`);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
catch (error) {
|
|
484
|
+
this.context.logger.error(`[ConfigWizard] ❌ Failed to load config:`, error.message);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
311
487
|
return {
|
|
312
488
|
apiVersion: 'majk.capabilities/v1',
|
|
313
|
-
capabilities:
|
|
489
|
+
capabilities: dynamicCapabilities
|
|
314
490
|
};
|
|
315
491
|
}
|
|
316
492
|
/**
|
|
@@ -349,6 +525,7 @@ class BuiltPlugin {
|
|
|
349
525
|
};
|
|
350
526
|
context.logger.info('═══════════════════════════════════════════════════════');
|
|
351
527
|
context.logger.info(`🚀 Loading Plugin: ${this.name} (${this.id}) v${this.version}`);
|
|
528
|
+
context.logger.info(`🔧 Plugin Kit Version: @majkapp/plugin-kit@1.0.19`);
|
|
352
529
|
context.logger.info(`📍 Plugin Root: ${effectivePluginRoot}`);
|
|
353
530
|
if (this.userProvidedPluginRoot) {
|
|
354
531
|
context.logger.info(` (user-provided via .pluginRoot(__dirname))`);
|
|
@@ -379,7 +556,35 @@ class BuiltPlugin {
|
|
|
379
556
|
}
|
|
380
557
|
try {
|
|
381
558
|
await this.startServer();
|
|
382
|
-
//
|
|
559
|
+
// Initialize transports for function-first architecture
|
|
560
|
+
if (this.functionRegistry) {
|
|
561
|
+
context.logger.info('');
|
|
562
|
+
context.logger.info('🔌 Initializing Transports...');
|
|
563
|
+
for (const transport of this.transports) {
|
|
564
|
+
await transport.initialize(this.functionRegistry, this.context);
|
|
565
|
+
await transport.start();
|
|
566
|
+
const metadata = transport.getMetadata();
|
|
567
|
+
context.logger.info(` ✅ ${transport.name} transport initialized`);
|
|
568
|
+
context.logger.info(` • Base Path: ${metadata.endpoint}`);
|
|
569
|
+
context.logger.info(` • Discovery: ${metadata.discovery}`);
|
|
570
|
+
}
|
|
571
|
+
// Log registered functions
|
|
572
|
+
const functionNames = this.functionRegistry.getFunctionNames();
|
|
573
|
+
if (functionNames.length > 0) {
|
|
574
|
+
context.logger.info('');
|
|
575
|
+
context.logger.info(`📦 Registered ${functionNames.length} Functions:`);
|
|
576
|
+
for (const fname of functionNames) {
|
|
577
|
+
const func = this.functionRegistry.functions.get(fname);
|
|
578
|
+
if (func) {
|
|
579
|
+
context.logger.info(` • ${fname}: ${func.description}`);
|
|
580
|
+
if (func.deprecated) {
|
|
581
|
+
context.logger.info(` ⚠️ DEPRECATED`);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
// Register API routes (legacy)
|
|
383
588
|
for (const route of this.apiRoutes) {
|
|
384
589
|
const { regex, keys } = pathToRegex(route.path);
|
|
385
590
|
this.router.push({
|
|
@@ -388,17 +593,127 @@ class BuiltPlugin {
|
|
|
388
593
|
keys,
|
|
389
594
|
name: route.name,
|
|
390
595
|
description: route.description,
|
|
391
|
-
handler: route.handler
|
|
596
|
+
handler: route.handler,
|
|
597
|
+
requestSchema: route.requestSchema,
|
|
598
|
+
responseSchema: route.responseSchema,
|
|
599
|
+
tags: route.tags,
|
|
600
|
+
deprecated: route.deprecated
|
|
392
601
|
});
|
|
393
602
|
context.logger.info(`📝 Registered route: ${route.method} ${route.path} - ${route.name}`);
|
|
394
603
|
}
|
|
604
|
+
// Register the discovery endpoint at /majk/plugin/api
|
|
605
|
+
const discoveryPath = '/majk/plugin/api';
|
|
606
|
+
const { regex: discoveryRegex, keys: discoveryKeys } = pathToRegex(discoveryPath);
|
|
607
|
+
this.router.push({
|
|
608
|
+
method: 'GET',
|
|
609
|
+
regex: discoveryRegex,
|
|
610
|
+
keys: discoveryKeys,
|
|
611
|
+
name: 'API Discovery',
|
|
612
|
+
description: 'Get OpenAPI specification for all plugin API routes',
|
|
613
|
+
handler: async (_req, _res, _ctx) => {
|
|
614
|
+
// If using function-first architecture, generate spec from function registry
|
|
615
|
+
if (this.functionRegistry && this.functionRegistry.getFunctionNames().length > 0) {
|
|
616
|
+
return this.functionRegistry.toOpenAPISpec();
|
|
617
|
+
}
|
|
618
|
+
// Otherwise fall back to legacy API routes
|
|
619
|
+
const openApiSpec = generateOpenApiSpec(this.id, this.name, this.version, this.apiRoutes, this.context.http.baseUrl);
|
|
620
|
+
return openApiSpec;
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
context.logger.info(`📝 Registered discovery route: GET ${discoveryPath}`);
|
|
624
|
+
// Also register a simpler JSON format at /majk/plugin/routes for quick access
|
|
625
|
+
const routesPath = '/majk/plugin/routes';
|
|
626
|
+
const { regex: routesRegex, keys: routesKeys } = pathToRegex(routesPath);
|
|
627
|
+
this.router.push({
|
|
628
|
+
method: 'GET',
|
|
629
|
+
regex: routesRegex,
|
|
630
|
+
keys: routesKeys,
|
|
631
|
+
name: 'Routes List',
|
|
632
|
+
description: 'Get a simple JSON list of all available API routes',
|
|
633
|
+
handler: async (_req, _res, _ctx) => {
|
|
634
|
+
return {
|
|
635
|
+
plugin: {
|
|
636
|
+
id: this.id,
|
|
637
|
+
name: this.name,
|
|
638
|
+
version: this.version
|
|
639
|
+
},
|
|
640
|
+
routes: this.apiRoutes.map(route => ({
|
|
641
|
+
method: route.method,
|
|
642
|
+
path: route.path,
|
|
643
|
+
name: route.name,
|
|
644
|
+
description: route.description,
|
|
645
|
+
deprecated: route.deprecated,
|
|
646
|
+
tags: route.tags,
|
|
647
|
+
requestSchema: route.requestSchema,
|
|
648
|
+
responseSchema: route.responseSchema
|
|
649
|
+
})),
|
|
650
|
+
discoveryUrl: `${this.context.http.baseUrl}/majk/plugin/api`
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
context.logger.info(`📝 Registered routes list: GET ${routesPath}`);
|
|
395
655
|
// Call onReady hook
|
|
396
656
|
if (this.onReadyFn) {
|
|
397
657
|
context.logger.info('⚙️ Calling onReady hook...');
|
|
398
658
|
await this.onReadyFn(this.context, (fn) => this.cleanups.push(fn));
|
|
399
659
|
context.logger.info('✅ onReady hook completed');
|
|
400
660
|
}
|
|
401
|
-
|
|
661
|
+
// Print comprehensive summary
|
|
662
|
+
context.logger.info('');
|
|
663
|
+
context.logger.info('╔══════════════════════════════════════════════════════════╗');
|
|
664
|
+
context.logger.info('║ 🎉 PLUGIN LOADED SUCCESSFULLY ║');
|
|
665
|
+
context.logger.info('╟──────────────────────────────────────────────────────────╢');
|
|
666
|
+
context.logger.info('║ 🌐 API ENDPOINTS ║');
|
|
667
|
+
context.logger.info('╟──────────────────────────────────────────────────────────╢');
|
|
668
|
+
// API Discovery endpoints
|
|
669
|
+
context.logger.info(`║ 🔍 Discovery: ║`);
|
|
670
|
+
context.logger.info(`║ ${context.http.baseUrl}/majk/plugin/api`);
|
|
671
|
+
context.logger.info(`║ ${context.http.baseUrl}/majk/plugin/routes`);
|
|
672
|
+
// Function endpoints
|
|
673
|
+
if (this.functionRegistry && this.functionRegistry.getFunctionNames().length > 0) {
|
|
674
|
+
context.logger.info(`║ ║`);
|
|
675
|
+
context.logger.info(`║ 🚀 Function Endpoints: ║`);
|
|
676
|
+
for (const fname of this.functionRegistry.getFunctionNames()) {
|
|
677
|
+
context.logger.info(`║ POST ${context.http.baseUrl}/api/fn/${fname}`);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
// Legacy API routes
|
|
681
|
+
if (this.apiRoutes.length > 0) {
|
|
682
|
+
context.logger.info(`║ ║`);
|
|
683
|
+
context.logger.info(`║ 📋 Legacy API Routes: ║`);
|
|
684
|
+
for (const route of this.apiRoutes) {
|
|
685
|
+
context.logger.info(`║ ${route.method} ${context.http.baseUrl}${route.path}`);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
// UI Screens
|
|
689
|
+
const allScreens = [...this.reactScreens, ...this.htmlScreens];
|
|
690
|
+
if (allScreens.length > 0 || this.uiConfig) {
|
|
691
|
+
context.logger.info('╟──────────────────────────────────────────────────────────╢');
|
|
692
|
+
context.logger.info('║ 🕹 UI SCREENS ║');
|
|
693
|
+
context.logger.info('╟──────────────────────────────────────────────────────────╢');
|
|
694
|
+
for (const screen of allScreens) {
|
|
695
|
+
context.logger.info(`║ • ${screen.name || screen.id}`);
|
|
696
|
+
context.logger.info(`║ ${context.http.baseUrl}${screen.route}`);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
// Config Wizard
|
|
700
|
+
if (this.wizard) {
|
|
701
|
+
context.logger.info('╟──────────────────────────────────────────────────────────╢');
|
|
702
|
+
context.logger.info('║ 🧙 CONFIG WIZARD ║');
|
|
703
|
+
context.logger.info('╟──────────────────────────────────────────────────────────╢');
|
|
704
|
+
context.logger.info(`║ ${this.wizard.title}`);
|
|
705
|
+
context.logger.info(`║ ${context.http.baseUrl}${this.wizard.path}`);
|
|
706
|
+
}
|
|
707
|
+
// Settings
|
|
708
|
+
if (this.settings) {
|
|
709
|
+
context.logger.info('╟──────────────────────────────────────────────────────────╢');
|
|
710
|
+
context.logger.info('║ ⚙️ SETTINGS ║');
|
|
711
|
+
context.logger.info('╟──────────────────────────────────────────────────────────╢');
|
|
712
|
+
context.logger.info(`║ ${this.settings.title}`);
|
|
713
|
+
context.logger.info(`║ ${context.http.baseUrl}${this.settings.path}`);
|
|
714
|
+
}
|
|
715
|
+
context.logger.info('╚══════════════════════════════════════════════════════════╝');
|
|
716
|
+
context.logger.info('');
|
|
402
717
|
}
|
|
403
718
|
catch (error) {
|
|
404
719
|
context.logger.error(`❌ Failed to load plugin "${this.name}": ${error.message}`);
|
|
@@ -526,8 +841,52 @@ class BuiltPlugin {
|
|
|
526
841
|
this.context.logger.debug(`← ${method} ${url} ${res.statusCode || 200} (${duration}ms)`);
|
|
527
842
|
return;
|
|
528
843
|
}
|
|
529
|
-
// API
|
|
530
|
-
if (req.url?.startsWith('/api/')) {
|
|
844
|
+
// Function calls (new function-first API)
|
|
845
|
+
if (req.method === 'POST' && req.url?.startsWith('/api/fn/')) {
|
|
846
|
+
const functionName = req.url.substring('/api/fn/'.length).split('?')[0];
|
|
847
|
+
if (this.functionRegistry && this.transports.length > 0) {
|
|
848
|
+
// Use the first HTTP transport to handle the function call
|
|
849
|
+
const httpTransport = this.transports.find(t => t.name === 'http');
|
|
850
|
+
if (httpTransport) {
|
|
851
|
+
const body = await readBody(req);
|
|
852
|
+
const request = {
|
|
853
|
+
method: 'POST',
|
|
854
|
+
url: req.url,
|
|
855
|
+
pathname: req.url.split('?')[0],
|
|
856
|
+
query: new URL(req.url, `http://localhost:${port}`).searchParams,
|
|
857
|
+
params: {},
|
|
858
|
+
body,
|
|
859
|
+
headers: req.headers
|
|
860
|
+
};
|
|
861
|
+
const response = makeResponse(res);
|
|
862
|
+
await httpTransport.handleFunctionCall(functionName, request, response, {
|
|
863
|
+
majk: this.context.majk,
|
|
864
|
+
storage: this.context.storage,
|
|
865
|
+
logger: this.context.logger,
|
|
866
|
+
http: this.context.http
|
|
867
|
+
});
|
|
868
|
+
const duration = Date.now() - startTime;
|
|
869
|
+
this.context.logger.debug(`← POST /api/fn/${functionName} ${res.statusCode || 200} (${duration}ms)`);
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
// Function discovery endpoint
|
|
875
|
+
if (req.method === 'GET' && req.url === '/api/fn/discovery') {
|
|
876
|
+
if (this.functionRegistry && this.transports.length > 0) {
|
|
877
|
+
const httpTransport = this.transports.find(t => t.name === 'http');
|
|
878
|
+
if (httpTransport) {
|
|
879
|
+
const discovery = httpTransport.getDiscovery();
|
|
880
|
+
res.writeHead(200, corsHeaders({ 'Content-Type': 'application/json' }));
|
|
881
|
+
res.end(JSON.stringify(discovery));
|
|
882
|
+
const duration = Date.now() - startTime;
|
|
883
|
+
this.context.logger.debug(`← GET /api/fn/discovery 200 (${duration}ms)`);
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
// API routes (including discovery endpoints) - legacy
|
|
889
|
+
if (req.url?.startsWith('/api/') || req.url?.startsWith('/majk/plugin/')) {
|
|
531
890
|
const url = new URL(req.url, `http://localhost:${port}`);
|
|
532
891
|
const pathname = url.pathname;
|
|
533
892
|
const method = req.method;
|
|
@@ -635,6 +994,9 @@ function groupByScope(tools) {
|
|
|
635
994
|
* Create a plugin with fluent builder API
|
|
636
995
|
*/
|
|
637
996
|
function definePlugin(id, name, version) {
|
|
997
|
+
log(`🔨 Building plugin: ${name} (${id}) v${version}`);
|
|
998
|
+
log(`📦 Plugin Kit: @majkapp/plugin-kit@1.0.19`);
|
|
999
|
+
log(``);
|
|
638
1000
|
const _capabilities = [];
|
|
639
1001
|
const _tools = new Map();
|
|
640
1002
|
const _toolSpecs = [];
|
|
@@ -643,6 +1005,11 @@ function definePlugin(id, name, version) {
|
|
|
643
1005
|
const _htmlScreens = [];
|
|
644
1006
|
const _topbars = [];
|
|
645
1007
|
const _entities = [];
|
|
1008
|
+
const _configurableEntities = [];
|
|
1009
|
+
// Function-first architecture state
|
|
1010
|
+
const _functionRegistry = new registry_1.FunctionRegistryImpl(id, name, version);
|
|
1011
|
+
const _transports = [];
|
|
1012
|
+
let _clientConfig = null;
|
|
646
1013
|
let _ui = { appDir: 'ui/dist', base: '/', history: 'browser' };
|
|
647
1014
|
let _uiConfigured = false;
|
|
648
1015
|
let _wizard = null;
|
|
@@ -696,12 +1063,17 @@ function definePlugin(id, name, version) {
|
|
|
696
1063
|
}
|
|
697
1064
|
const iframeUrl = `http://localhost:{port}${_ui.base}${screen.reactPath}`;
|
|
698
1065
|
_reactScreens.push(screen);
|
|
699
|
-
|
|
1066
|
+
log(` 🕹 Registered React screen: ${screen.name} at ${screen.route}`);
|
|
1067
|
+
const capability = {
|
|
700
1068
|
type: 'screen',
|
|
701
1069
|
id: screen.id,
|
|
702
1070
|
path: screen.route,
|
|
703
1071
|
iframeUrl
|
|
704
|
-
}
|
|
1072
|
+
};
|
|
1073
|
+
if (screen.hash) {
|
|
1074
|
+
capability.hash = screen.hash;
|
|
1075
|
+
}
|
|
1076
|
+
_capabilities.push(capability);
|
|
705
1077
|
return this;
|
|
706
1078
|
},
|
|
707
1079
|
screenHtml(screen) {
|
|
@@ -711,6 +1083,7 @@ function definePlugin(id, name, version) {
|
|
|
711
1083
|
}
|
|
712
1084
|
const iframeUrl = `http://localhost:{port}/__html/${encodeURIComponent(screen.id)}`;
|
|
713
1085
|
_htmlScreens.push(screen);
|
|
1086
|
+
log(` 📋 Registered HTML screen: ${screen.name} at ${screen.route}`);
|
|
714
1087
|
_capabilities.push({
|
|
715
1088
|
type: 'screen',
|
|
716
1089
|
id: screen.id,
|
|
@@ -724,9 +1097,70 @@ function definePlugin(id, name, version) {
|
|
|
724
1097
|
if (!route.path.startsWith('/api/')) {
|
|
725
1098
|
throw new PluginBuildError(`API route path must start with "/api/"`, `Change path from "${route.path}" to "/api/your-endpoint"`, { route: route.name });
|
|
726
1099
|
}
|
|
1100
|
+
// Runtime validation for request schema (TypeScript already enforces at compile-time)
|
|
1101
|
+
// This catches any runtime bypasses or JavaScript usage
|
|
1102
|
+
if (!route.requestSchema) {
|
|
1103
|
+
throw new PluginBuildError(`API route "${route.name}" must provide a requestSchema`, `Add requestSchema to your route definition. For routes with no input, use an empty object: requestSchema: {}`, { route: route.name, path: route.path, method: route.method });
|
|
1104
|
+
}
|
|
1105
|
+
// Validate request schema structure
|
|
1106
|
+
if (route.requestSchema) {
|
|
1107
|
+
const { query, params, body } = route.requestSchema;
|
|
1108
|
+
if (query && query.type !== 'object') {
|
|
1109
|
+
throw new PluginBuildError(`Query schema for route "${route.name}" must be of type "object"`, `Change query.type to "object" in your schema definition`, { route: route.name });
|
|
1110
|
+
}
|
|
1111
|
+
if (params && params.type !== 'object') {
|
|
1112
|
+
throw new PluginBuildError(`Params schema for route "${route.name}" must be of type "object"`, `Change params.type to "object" in your schema definition`, { route: route.name });
|
|
1113
|
+
}
|
|
1114
|
+
// Check for path parameters in the route
|
|
1115
|
+
const pathParamMatches = route.path.match(/\/:([A-Za-z0-9_]+)/g);
|
|
1116
|
+
const pathParams = pathParamMatches ? pathParamMatches.map(p => p.substring(2)) : [];
|
|
1117
|
+
if (pathParams.length > 0 && !params) {
|
|
1118
|
+
throw new PluginBuildError(`Route "${route.name}" has path parameters but no params schema`, `Add params schema defining: ${pathParams.join(', ')}`, { route: route.name, pathParams });
|
|
1119
|
+
}
|
|
1120
|
+
if (params && pathParams.length > 0) {
|
|
1121
|
+
const schemaProps = params.properties || {};
|
|
1122
|
+
const missingParams = pathParams.filter(p => !(p in schemaProps));
|
|
1123
|
+
if (missingParams.length > 0) {
|
|
1124
|
+
throw new PluginBuildError(`Params schema missing definitions for path parameters: ${missingParams.join(', ')}`, `Add properties for: ${missingParams.join(', ')} to params.properties`, { route: route.name, missingParams });
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
727
1128
|
_apiRoutes.push(route);
|
|
728
1129
|
return this;
|
|
729
1130
|
},
|
|
1131
|
+
function(name, config) {
|
|
1132
|
+
validateDescription(config.description, `Function "${name}"`);
|
|
1133
|
+
try {
|
|
1134
|
+
_functionRegistry.registerFunction(name, config.description, config.input, config.output, config.handler, {
|
|
1135
|
+
tags: config.tags,
|
|
1136
|
+
deprecated: config.deprecated
|
|
1137
|
+
});
|
|
1138
|
+
// Log function registration
|
|
1139
|
+
log(` 📦 Registered function: ${name} - ${config.description}`);
|
|
1140
|
+
}
|
|
1141
|
+
catch (error) {
|
|
1142
|
+
throw new PluginBuildError(`Failed to register function "${name}"`, error.message, { function: name });
|
|
1143
|
+
}
|
|
1144
|
+
return this;
|
|
1145
|
+
},
|
|
1146
|
+
subscription(name, config) {
|
|
1147
|
+
validateDescription(config.description, `Subscription "${name}"`);
|
|
1148
|
+
try {
|
|
1149
|
+
_functionRegistry.registerSubscription(name, config.description, config.input, config.output, config.handler);
|
|
1150
|
+
}
|
|
1151
|
+
catch (error) {
|
|
1152
|
+
throw new PluginBuildError(`Failed to register subscription "${name}"`, error.message, { subscription: name });
|
|
1153
|
+
}
|
|
1154
|
+
return this;
|
|
1155
|
+
},
|
|
1156
|
+
transport(transport) {
|
|
1157
|
+
_transports.push(transport);
|
|
1158
|
+
return this;
|
|
1159
|
+
},
|
|
1160
|
+
generateClient(config) {
|
|
1161
|
+
_clientConfig = config;
|
|
1162
|
+
return this;
|
|
1163
|
+
},
|
|
730
1164
|
tool(scope, spec, handler) {
|
|
731
1165
|
validateDescription(spec.description, `Tool "${spec.name}"`);
|
|
732
1166
|
if (_tools.has(spec.name)) {
|
|
@@ -762,12 +1196,97 @@ function definePlugin(id, name, version) {
|
|
|
762
1196
|
// Type-safe wrapper for team member entities
|
|
763
1197
|
return this.entity('teamMember', members);
|
|
764
1198
|
},
|
|
1199
|
+
configurableEntity(entityType, factory) {
|
|
1200
|
+
_configurableEntities.push({ entityType, factory });
|
|
1201
|
+
log(` 🔧 Registered configurable entity: ${entityType} (requires config)`);
|
|
1202
|
+
return this;
|
|
1203
|
+
},
|
|
1204
|
+
configurableMcp(factory) {
|
|
1205
|
+
_configurableEntities.push({ entityType: 'mcpServer', factory });
|
|
1206
|
+
log(` 🔧 Registered configurable MCP server (requires config)`);
|
|
1207
|
+
return this;
|
|
1208
|
+
},
|
|
1209
|
+
configurableTeamMember(factory) {
|
|
1210
|
+
_configurableEntities.push({ entityType: 'teamMember', factory });
|
|
1211
|
+
log(` 🔧 Registered configurable team member (requires config)`);
|
|
1212
|
+
return this;
|
|
1213
|
+
},
|
|
765
1214
|
configWizard(def) {
|
|
766
1215
|
if (_wizard) {
|
|
767
1216
|
throw new PluginBuildError('Config wizard already defined', 'You can only have one config wizard per plugin');
|
|
768
1217
|
}
|
|
769
1218
|
validateDescription(def.description, 'Config wizard');
|
|
770
1219
|
_wizard = def;
|
|
1220
|
+
// Auto-generate config management functions if schema is provided
|
|
1221
|
+
if (def.schema) {
|
|
1222
|
+
const storageKey = def.storageKey || '_plugin_config';
|
|
1223
|
+
log(` 🔧 Schema provided - auto-generating config functions`);
|
|
1224
|
+
log(` Storage key: ${storageKey}`);
|
|
1225
|
+
// Auto-generate updateConfig function
|
|
1226
|
+
_functionRegistry.registerFunction('updateConfig', 'Update plugin configuration (auto-generated)', def.schema, {
|
|
1227
|
+
type: 'object',
|
|
1228
|
+
properties: {
|
|
1229
|
+
success: { type: 'boolean' },
|
|
1230
|
+
message: { type: 'string' },
|
|
1231
|
+
data: def.schema,
|
|
1232
|
+
error: { type: 'string' }
|
|
1233
|
+
},
|
|
1234
|
+
required: ['success']
|
|
1235
|
+
}, async (config, ctx) => {
|
|
1236
|
+
ctx.logger.info(`[ConfigWizard] updateConfig called`);
|
|
1237
|
+
ctx.logger.info(`[ConfigWizard] Config to save:`, JSON.stringify(config, null, 2));
|
|
1238
|
+
try {
|
|
1239
|
+
// Save to storage
|
|
1240
|
+
await ctx.storage.set(storageKey, config);
|
|
1241
|
+
ctx.logger.info(`[ConfigWizard] ✅ Config saved to storage key: ${storageKey}`);
|
|
1242
|
+
return {
|
|
1243
|
+
success: true,
|
|
1244
|
+
message: 'Configuration saved successfully',
|
|
1245
|
+
data: config
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
catch (error) {
|
|
1249
|
+
ctx.logger.error(`[ConfigWizard] ❌ Failed to save config:`, error.message);
|
|
1250
|
+
return {
|
|
1251
|
+
success: false,
|
|
1252
|
+
error: error.message
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
}, { tags: ['config'], deprecated: false });
|
|
1256
|
+
// Auto-generate getConfig function
|
|
1257
|
+
_functionRegistry.registerFunction('getConfig', 'Get current plugin configuration (auto-generated)', {
|
|
1258
|
+
type: 'object',
|
|
1259
|
+
properties: {},
|
|
1260
|
+
additionalProperties: false
|
|
1261
|
+
}, {
|
|
1262
|
+
type: 'object',
|
|
1263
|
+
properties: {
|
|
1264
|
+
success: { type: 'boolean' },
|
|
1265
|
+
data: def.schema,
|
|
1266
|
+
error: { type: 'string' }
|
|
1267
|
+
},
|
|
1268
|
+
required: ['success']
|
|
1269
|
+
}, async (_input, ctx) => {
|
|
1270
|
+
ctx.logger.info(`[ConfigWizard] getConfig called`);
|
|
1271
|
+
try {
|
|
1272
|
+
const config = await ctx.storage.get(storageKey);
|
|
1273
|
+
if (config) {
|
|
1274
|
+
ctx.logger.info(`[ConfigWizard] ✅ Config loaded from storage`);
|
|
1275
|
+
return { success: true, data: config };
|
|
1276
|
+
}
|
|
1277
|
+
else {
|
|
1278
|
+
ctx.logger.info(`[ConfigWizard] ⚠️ No config found in storage`);
|
|
1279
|
+
return { success: false, error: 'No configuration found' };
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
catch (error) {
|
|
1283
|
+
ctx.logger.error(`[ConfigWizard] ❌ Failed to load config:`, error.message);
|
|
1284
|
+
return { success: false, error: error.message };
|
|
1285
|
+
}
|
|
1286
|
+
}, { tags: ['config'], deprecated: false });
|
|
1287
|
+
log(` ✅ Auto-generated: updateConfig()`);
|
|
1288
|
+
log(` ✅ Auto-generated: getConfig()`);
|
|
1289
|
+
}
|
|
771
1290
|
return this;
|
|
772
1291
|
},
|
|
773
1292
|
settings(def) {
|
|
@@ -780,6 +1299,7 @@ function definePlugin(id, name, version) {
|
|
|
780
1299
|
},
|
|
781
1300
|
onReady(fn) {
|
|
782
1301
|
_onReady = fn;
|
|
1302
|
+
log(` 🏁 Registered onReady lifecycle hook`);
|
|
783
1303
|
return this;
|
|
784
1304
|
},
|
|
785
1305
|
health(fn) {
|
|
@@ -795,6 +1315,27 @@ function definePlugin(id, name, version) {
|
|
|
795
1315
|
},
|
|
796
1316
|
build() {
|
|
797
1317
|
// ========== Build-Time Validation ==========
|
|
1318
|
+
// Validate configurable entities require configWizard with schema
|
|
1319
|
+
if (_configurableEntities.length > 0) {
|
|
1320
|
+
if (!_wizard) {
|
|
1321
|
+
throw new PluginBuildError('Configurable entities require configWizard', 'You used .configurableTeamMember(), .configurableMcp(), or .configurableEntity() but did not call .configWizard()', {
|
|
1322
|
+
configurableEntities: _configurableEntities.map(ce => ce.entityType),
|
|
1323
|
+
hint: 'Add .configWizard({ schema: YourSchema, ... }) to your plugin'
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
if (!_wizard.schema) {
|
|
1327
|
+
throw new PluginBuildError('Configurable entities require configWizard with schema', 'You used configurable entities but .configWizard() does not have a schema property', {
|
|
1328
|
+
configurableEntities: _configurableEntities.map(ce => ce.entityType),
|
|
1329
|
+
hint: 'Add schema property to .configWizard({ schema: YourSchema, ... })'
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
log(` ✅ Validated: ${_configurableEntities.length} configurable entities with config schema`);
|
|
1333
|
+
}
|
|
1334
|
+
// Warn if config wizard has schema but no configurable entities
|
|
1335
|
+
if (_wizard?.schema && _configurableEntities.length === 0) {
|
|
1336
|
+
log(` ⚠️ Warning: configWizard has schema but no configurable entities defined`);
|
|
1337
|
+
log(` Consider using .configurableTeamMember() or .configurableMcp() to take advantage of the config`);
|
|
1338
|
+
}
|
|
798
1339
|
// Validate React UI setup
|
|
799
1340
|
if (_reactScreens.length > 0 && !_uiConfigured) {
|
|
800
1341
|
throw new PluginBuildError('UI not configured but React screens were added', 'Call .ui() before adding React screens', { reactScreens: _reactScreens.map(s => s.id) });
|
|
@@ -889,6 +1430,10 @@ function definePlugin(id, name, version) {
|
|
|
889
1430
|
},
|
|
890
1431
|
required: true
|
|
891
1432
|
};
|
|
1433
|
+
// Add hash if provided
|
|
1434
|
+
if (_wizard.hash) {
|
|
1435
|
+
wizardCap.screen.hash = _wizard.hash;
|
|
1436
|
+
}
|
|
892
1437
|
// Add shouldShow as method name reference if provided
|
|
893
1438
|
if (_wizard.shouldShow) {
|
|
894
1439
|
wizardCap.shouldShow = 'shouldShowConfigWizard';
|
|
@@ -897,7 +1442,7 @@ function definePlugin(id, name, version) {
|
|
|
897
1442
|
}
|
|
898
1443
|
// Add settings
|
|
899
1444
|
if (_settings) {
|
|
900
|
-
|
|
1445
|
+
const settingsCap = {
|
|
901
1446
|
type: 'settings-screen',
|
|
902
1447
|
path: `/plugins/${id}/settings`,
|
|
903
1448
|
name: _settings.title,
|
|
@@ -905,14 +1450,72 @@ function definePlugin(id, name, version) {
|
|
|
905
1450
|
path: _settings.path,
|
|
906
1451
|
title: _settings.title
|
|
907
1452
|
}
|
|
908
|
-
}
|
|
1453
|
+
};
|
|
1454
|
+
// Add hash if provided
|
|
1455
|
+
if (_settings.hash) {
|
|
1456
|
+
settingsCap.screen.hash = _settings.hash;
|
|
1457
|
+
}
|
|
1458
|
+
_capabilities.push(settingsCap);
|
|
909
1459
|
}
|
|
910
1460
|
// ========== Build Plugin Class ==========
|
|
911
1461
|
// MAJK expects a class/constructor, not an instance
|
|
912
1462
|
// Return a class that instantiates BuiltPlugin
|
|
1463
|
+
// If no transports specified but functions were defined, add default HTTP transport
|
|
1464
|
+
if (_functionRegistry.functions.size > 0 && _transports.length === 0) {
|
|
1465
|
+
_transports.push(new transports_1.HttpTransport());
|
|
1466
|
+
}
|
|
1467
|
+
// TODO: Implement client generation if configured
|
|
1468
|
+
if (_clientConfig) {
|
|
1469
|
+
// This would generate the client files based on the function registry
|
|
1470
|
+
// For now, just log that it was requested
|
|
1471
|
+
log('Client generation requested: ' + JSON.stringify(_clientConfig));
|
|
1472
|
+
}
|
|
1473
|
+
// Check for extract mode - output metadata and exit
|
|
1474
|
+
if (process.env.PLUGIN_KIT_MODE === 'extract') {
|
|
1475
|
+
const metadata = {
|
|
1476
|
+
id,
|
|
1477
|
+
name,
|
|
1478
|
+
version,
|
|
1479
|
+
functions: Array.from(_functionRegistry.functions.entries()).map(([fname, func]) => ({
|
|
1480
|
+
name: fname,
|
|
1481
|
+
description: func.description,
|
|
1482
|
+
input: func.input,
|
|
1483
|
+
output: func.output,
|
|
1484
|
+
tags: func.tags,
|
|
1485
|
+
deprecated: func.deprecated
|
|
1486
|
+
})),
|
|
1487
|
+
screens: [..._reactScreens, ..._htmlScreens].map(s => ({
|
|
1488
|
+
id: s.id,
|
|
1489
|
+
name: s.name,
|
|
1490
|
+
route: s.route,
|
|
1491
|
+
type: 'reactPath' in s ? 'react' : 'html'
|
|
1492
|
+
})),
|
|
1493
|
+
tools: _toolSpecs.map(t => ({
|
|
1494
|
+
name: t.spec.name,
|
|
1495
|
+
description: t.spec.description,
|
|
1496
|
+
scope: t.scope
|
|
1497
|
+
})),
|
|
1498
|
+
openapi: _functionRegistry.toOpenAPISpec()
|
|
1499
|
+
};
|
|
1500
|
+
// Write to file specified by env variable
|
|
1501
|
+
const outputFile = process.env.PLUGIN_KIT_OUTPUT;
|
|
1502
|
+
if (outputFile) {
|
|
1503
|
+
fs_1.default.writeFileSync(outputFile, JSON.stringify(metadata, null, 2), 'utf-8');
|
|
1504
|
+
console.log(`Metadata written to ${outputFile}`);
|
|
1505
|
+
}
|
|
1506
|
+
else {
|
|
1507
|
+
// Fallback to stdout if no file specified
|
|
1508
|
+
console.log(JSON.stringify(metadata, null, 2));
|
|
1509
|
+
}
|
|
1510
|
+
process.exit(0);
|
|
1511
|
+
}
|
|
913
1512
|
return class extends BuiltPlugin {
|
|
914
1513
|
constructor() {
|
|
915
|
-
super(id, name, version, _capabilities, _tools, _apiRoutes, _uiConfigured ? _ui : null, _reactScreens, _htmlScreens, _wizard, _onReady, _healthCheck, _pluginRoot
|
|
1514
|
+
super(id, name, version, _capabilities, _tools, _apiRoutes, _uiConfigured ? _ui : null, _reactScreens, _htmlScreens, _wizard, _settings, _onReady, _healthCheck, _pluginRoot,
|
|
1515
|
+
// Pass function registry and transports if functions were defined
|
|
1516
|
+
_functionRegistry.functions.size > 0 ? _functionRegistry : undefined, _transports.length > 0 ? _transports : undefined,
|
|
1517
|
+
// Pass configurable entities
|
|
1518
|
+
_configurableEntities.length > 0 ? _configurableEntities : undefined);
|
|
916
1519
|
}
|
|
917
1520
|
};
|
|
918
1521
|
}
|