@majkapp/plugin-kit 1.0.7 → 1.0.9
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/plugin-kit.d.ts +12 -0
- package/dist/plugin-kit.d.ts.map +1 -1
- package/dist/plugin-kit.js +57 -11
- package/package.json +1 -1
package/dist/plugin-kit.d.ts
CHANGED
|
@@ -33,6 +33,18 @@ export interface FluentBuilder<Id extends string> {
|
|
|
33
33
|
onReady(fn: (ctx: PluginContext, cleanup: (fn: CleanupFn) => void) => void | Promise<void>): this;
|
|
34
34
|
/** Custom health check */
|
|
35
35
|
health(fn: HealthCheckFn): this;
|
|
36
|
+
/**
|
|
37
|
+
* Set the plugin root directory explicitly (use __dirname from your plugin entry file).
|
|
38
|
+
* This ensures plugin-kit can find your files in both local development and npm installed scenarios.
|
|
39
|
+
* If not provided, falls back to the pluginRoot from MAJK's context.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* const plugin = definePlugin('my-plugin', 'My Plugin', '1.0.0')
|
|
43
|
+
* .pluginRoot(__dirname) // Use Node's __dirname
|
|
44
|
+
* .ui({ appDir: 'dist' }) // Now resolves relative to __dirname
|
|
45
|
+
* .build();
|
|
46
|
+
*/
|
|
47
|
+
pluginRoot(dir: string): this;
|
|
36
48
|
/** Build the plugin */
|
|
37
49
|
build(): InProcessPlugin;
|
|
38
50
|
}
|
package/dist/plugin-kit.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-kit.d.ts","sourceRoot":"","sources":["../src/plugin-kit.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,aAAa,EAGb,eAAe,EACf,QAAQ,EACR,WAAW,EACX,WAAW,EACX,QAAQ,EACR,WAAW,EACX,UAAU,EACV,eAAe,EACf,WAAW,EACX,KAAK,EACL,UAAU,EACV,SAAS,EACT,aAAa,EAIb,eAAe,EACf,gBAAgB,EACjB,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"plugin-kit.d.ts","sourceRoot":"","sources":["../src/plugin-kit.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,aAAa,EAGb,eAAe,EACf,QAAQ,EACR,WAAW,EACX,WAAW,EACX,QAAQ,EACR,WAAW,EACX,UAAU,EACV,eAAe,EACf,WAAW,EACX,KAAK,EACL,UAAU,EACV,SAAS,EACT,aAAa,EAIb,eAAe,EACf,gBAAgB,EACjB,MAAM,SAAS,CAAC;AAwsBjB;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,EAAE,SAAS,MAAM;IAC9C,mDAAmD;IACnD,MAAM,CAAC,KAAK,EAAE,mBAAmB,EAAE,IAAI,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAE7G,0CAA0C;IAC1C,EAAE,CAAC,MAAM,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;IAE5B,yBAAyB;IACzB,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC;IAE3C,yBAAyB;IACzB,UAAU,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC;IAEzC,uBAAuB;IACvB,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;IAEnC,iBAAiB;IACjB,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IAE/D,oEAAoE;IACpE,MAAM,CAAC,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAEtD,8DAA8D;IAC9D,SAAS,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,IAAI,CAAC;IAE5C,+DAA+D;IAC/D,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,IAAI,CAAC;IAE9C,wBAAwB;IACxB,YAAY,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI,CAAC;IAEzC,0BAA0B;IAC1B,QAAQ,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,CAAC;IAEjC,sCAAsC;IACtC,OAAO,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,aAAa,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAElG,0BAA0B;IAC1B,MAAM,CAAC,EAAE,EAAE,aAAa,GAAG,IAAI,CAAC;IAEhC;;;;;;;;;;OAUG;IACH,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAE9B,uBAAuB;IACvB,KAAK,IAAI,eAAe,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAClD,EAAE,EAAE,EAAE,EACN,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,aAAa,CAAC,EAAE,CAAC,CAkVnB"}
|
package/dist/plugin-kit.js
CHANGED
|
@@ -286,7 +286,7 @@ function serveSpa(ui, req, res, ctx) {
|
|
|
286
286
|
* Main plugin class built by the fluent API
|
|
287
287
|
*/
|
|
288
288
|
class BuiltPlugin {
|
|
289
|
-
constructor(id, name, version, capabilities, tools, apiRoutes, uiConfig, reactScreens, htmlScreens, wizard, onReadyFn, healthCheckFn) {
|
|
289
|
+
constructor(id, name, version, capabilities, tools, apiRoutes, uiConfig, reactScreens, htmlScreens, wizard, onReadyFn, healthCheckFn, userProvidedPluginRoot) {
|
|
290
290
|
this.capabilities = capabilities;
|
|
291
291
|
this.tools = tools;
|
|
292
292
|
this.apiRoutes = apiRoutes;
|
|
@@ -296,6 +296,7 @@ class BuiltPlugin {
|
|
|
296
296
|
this.wizard = wizard;
|
|
297
297
|
this.onReadyFn = onReadyFn;
|
|
298
298
|
this.healthCheckFn = healthCheckFn;
|
|
299
|
+
this.userProvidedPluginRoot = userProvidedPluginRoot;
|
|
299
300
|
this.cleanups = [];
|
|
300
301
|
this.router = [];
|
|
301
302
|
this.id = id;
|
|
@@ -334,27 +335,40 @@ class BuiltPlugin {
|
|
|
334
335
|
};
|
|
335
336
|
}
|
|
336
337
|
async onLoad(context) {
|
|
337
|
-
|
|
338
|
+
// Use user-provided pluginRoot (from __dirname) if available, otherwise fall back to context.pluginRoot
|
|
339
|
+
// The user-provided path comes from Node's __dirname which automatically resolves to the real directory
|
|
340
|
+
// (following symlinks), making it work correctly in both local development and npm installed scenarios
|
|
341
|
+
const effectivePluginRoot = this.userProvidedPluginRoot || context.pluginRoot;
|
|
342
|
+
this.context = {
|
|
343
|
+
...context,
|
|
344
|
+
pluginRoot: effectivePluginRoot
|
|
345
|
+
};
|
|
338
346
|
context.logger.info('═══════════════════════════════════════════════════════');
|
|
339
347
|
context.logger.info(`🚀 Loading Plugin: ${this.name} (${this.id}) v${this.version}`);
|
|
340
|
-
context.logger.info(`📍 Plugin Root: ${
|
|
348
|
+
context.logger.info(`📍 Plugin Root: ${effectivePluginRoot}`);
|
|
349
|
+
if (this.userProvidedPluginRoot) {
|
|
350
|
+
context.logger.info(` (user-provided via .pluginRoot(__dirname))`);
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
context.logger.info(` (from MAJK context - consider using .pluginRoot(__dirname) for better reliability)`);
|
|
354
|
+
}
|
|
341
355
|
context.logger.info(`🌐 HTTP Port: ${context.http.port}`);
|
|
342
356
|
context.logger.info(`🔗 Base URL: ${context.http.baseUrl}`);
|
|
343
357
|
context.logger.info('═══════════════════════════════════════════════════════');
|
|
344
358
|
// Validate file paths now that we have pluginRoot context
|
|
345
359
|
if (this.reactScreens.length > 0 && this.uiConfig) {
|
|
346
|
-
const indexPath = path_1.default.join(context.pluginRoot, this.uiConfig.appDir || '', 'index.html');
|
|
360
|
+
const indexPath = path_1.default.join(this.context.pluginRoot, this.uiConfig.appDir || '', 'index.html');
|
|
347
361
|
if (!fs_1.default.existsSync(indexPath)) {
|
|
348
|
-
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: context.pluginRoot });
|
|
362
|
+
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 });
|
|
349
363
|
}
|
|
350
364
|
context.logger.info(`✅ React UI found at: ${indexPath}`);
|
|
351
365
|
}
|
|
352
366
|
// Validate HTML screen files
|
|
353
367
|
for (const screen of this.htmlScreens) {
|
|
354
368
|
if ('htmlFile' in screen) {
|
|
355
|
-
const filePath = path_1.default.join(context.pluginRoot, screen.htmlFile);
|
|
369
|
+
const filePath = path_1.default.join(this.context.pluginRoot, screen.htmlFile);
|
|
356
370
|
if (!fs_1.default.existsSync(filePath)) {
|
|
357
|
-
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: context.pluginRoot });
|
|
371
|
+
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 });
|
|
358
372
|
}
|
|
359
373
|
context.logger.info(`✅ HTML screen file found: ${screen.htmlFile}`);
|
|
360
374
|
}
|
|
@@ -434,11 +448,18 @@ class BuiltPlugin {
|
|
|
434
448
|
async startServer() {
|
|
435
449
|
const { port } = this.context.http;
|
|
436
450
|
this.server = http_1.default.createServer(async (req, res) => {
|
|
451
|
+
const startTime = Date.now();
|
|
452
|
+
const method = req.method || 'UNKNOWN';
|
|
453
|
+
const url = req.url || '/';
|
|
437
454
|
try {
|
|
455
|
+
// Log incoming request
|
|
456
|
+
this.context.logger.debug(`→ ${method} ${url}`);
|
|
438
457
|
// CORS preflight
|
|
439
458
|
if (req.method === 'OPTIONS') {
|
|
440
459
|
res.writeHead(204, corsHeaders());
|
|
441
460
|
res.end();
|
|
461
|
+
const duration = Date.now() - startTime;
|
|
462
|
+
this.context.logger.debug(`← ${method} ${url} 204 (${duration}ms)`);
|
|
442
463
|
return;
|
|
443
464
|
}
|
|
444
465
|
// Health check
|
|
@@ -446,6 +467,8 @@ class BuiltPlugin {
|
|
|
446
467
|
const health = await this.isHealthy();
|
|
447
468
|
res.writeHead(200, corsHeaders({ 'Content-Type': 'application/json' }));
|
|
448
469
|
res.end(JSON.stringify({ ...health, plugin: this.name, version: this.version }));
|
|
470
|
+
const duration = Date.now() - startTime;
|
|
471
|
+
this.context.logger.debug(`← ${method} ${url} 200 (${duration}ms)`);
|
|
449
472
|
return;
|
|
450
473
|
}
|
|
451
474
|
// HTML screens virtual route
|
|
@@ -455,6 +478,8 @@ class BuiltPlugin {
|
|
|
455
478
|
if (!screen) {
|
|
456
479
|
res.writeHead(404, corsHeaders({ 'Content-Type': 'text/plain' }));
|
|
457
480
|
res.end('HTML screen not found');
|
|
481
|
+
const duration = Date.now() - startTime;
|
|
482
|
+
this.context.logger.debug(`← ${method} ${url} 404 (${duration}ms)`);
|
|
458
483
|
return;
|
|
459
484
|
}
|
|
460
485
|
res.writeHead(200, corsHeaders({ 'Content-Type': 'text/html; charset=utf-8' }));
|
|
@@ -470,6 +495,8 @@ class BuiltPlugin {
|
|
|
470
495
|
res.writeHead(403, corsHeaders({ 'Content-Type': 'text/plain' }));
|
|
471
496
|
res.end('Forbidden');
|
|
472
497
|
this.context.logger.warn(`Path traversal attempt: ${screen.htmlFile}`);
|
|
498
|
+
const duration = Date.now() - startTime;
|
|
499
|
+
this.context.logger.debug(`← ${method} ${url} 403 (${duration}ms)`);
|
|
473
500
|
return;
|
|
474
501
|
}
|
|
475
502
|
try {
|
|
@@ -479,13 +506,20 @@ class BuiltPlugin {
|
|
|
479
506
|
this.context.logger.error(`Failed to read HTML file: ${error.message}`);
|
|
480
507
|
res.writeHead(500, corsHeaders({ 'Content-Type': 'text/html' }));
|
|
481
508
|
res.end('<!doctype html><html><body><h1>Error loading screen</h1></body></html>');
|
|
509
|
+
const duration = Date.now() - startTime;
|
|
510
|
+
this.context.logger.debug(`← ${method} ${url} 500 (${duration}ms)`);
|
|
511
|
+
return;
|
|
482
512
|
}
|
|
483
513
|
}
|
|
514
|
+
const duration = Date.now() - startTime;
|
|
515
|
+
this.context.logger.debug(`← ${method} ${url} 200 (${duration}ms)`);
|
|
484
516
|
return;
|
|
485
517
|
}
|
|
486
518
|
// React SPA
|
|
487
519
|
if (req.method === 'GET' && this.uiConfig && req.url?.startsWith(this.uiConfig.base)) {
|
|
488
520
|
serveSpa(this.uiConfig, req, res, this.context);
|
|
521
|
+
const duration = Date.now() - startTime;
|
|
522
|
+
this.context.logger.debug(`← ${method} ${url} ${res.statusCode || 200} (${duration}ms)`);
|
|
489
523
|
return;
|
|
490
524
|
}
|
|
491
525
|
// API routes
|
|
@@ -502,6 +536,8 @@ class BuiltPlugin {
|
|
|
502
536
|
method,
|
|
503
537
|
hint: `Available routes: ${this.router.map(r => `${r.method} ${r.name}`).join(', ')}`
|
|
504
538
|
}));
|
|
539
|
+
const duration = Date.now() - startTime;
|
|
540
|
+
this.context.logger.debug(`← ${method} ${pathname} 404 (${duration}ms)`);
|
|
505
541
|
return;
|
|
506
542
|
}
|
|
507
543
|
const body = await readBody(req);
|
|
@@ -527,16 +563,16 @@ class BuiltPlugin {
|
|
|
527
563
|
http: this.context.http
|
|
528
564
|
};
|
|
529
565
|
try {
|
|
530
|
-
this.context.logger.debug(`📥 ${method} ${pathname}`, { params, body });
|
|
531
566
|
const result = await route.handler(request, response, routeContext);
|
|
532
567
|
if (!res.headersSent) {
|
|
533
568
|
res.writeHead(200, corsHeaders({ 'Content-Type': 'application/json' }));
|
|
534
569
|
res.end(JSON.stringify(result ?? { success: true }));
|
|
535
570
|
}
|
|
536
|
-
|
|
571
|
+
const duration = Date.now() - startTime;
|
|
572
|
+
this.context.logger.debug(`← ${method} ${pathname} ${res.statusCode || 200} (${duration}ms)`);
|
|
537
573
|
}
|
|
538
574
|
catch (error) {
|
|
539
|
-
this.context.logger.error(
|
|
575
|
+
this.context.logger.error(`API route error: ${method} ${pathname} - ${error.message}`);
|
|
540
576
|
if (error.stack) {
|
|
541
577
|
this.context.logger.error(error.stack);
|
|
542
578
|
}
|
|
@@ -548,6 +584,8 @@ class BuiltPlugin {
|
|
|
548
584
|
path: pathname
|
|
549
585
|
}));
|
|
550
586
|
}
|
|
587
|
+
const duration = Date.now() - startTime;
|
|
588
|
+
this.context.logger.debug(`← ${method} ${pathname} 500 (${duration}ms)`);
|
|
551
589
|
}
|
|
552
590
|
return;
|
|
553
591
|
}
|
|
@@ -601,6 +639,7 @@ function definePlugin(id, name, version) {
|
|
|
601
639
|
let _settings = null;
|
|
602
640
|
let _onReady = null;
|
|
603
641
|
let _healthCheck = null;
|
|
642
|
+
let _pluginRoot = undefined;
|
|
604
643
|
const builder = {
|
|
605
644
|
topbar(route, opts) {
|
|
606
645
|
_topbars.push({
|
|
@@ -718,6 +757,13 @@ function definePlugin(id, name, version) {
|
|
|
718
757
|
_healthCheck = fn;
|
|
719
758
|
return this;
|
|
720
759
|
},
|
|
760
|
+
pluginRoot(dir) {
|
|
761
|
+
if (!dir || typeof dir !== 'string') {
|
|
762
|
+
throw new PluginBuildError('pluginRoot must be a non-empty string', 'Pass __dirname from your plugin entry file: .pluginRoot(__dirname)', { provided: dir });
|
|
763
|
+
}
|
|
764
|
+
_pluginRoot = dir;
|
|
765
|
+
return this;
|
|
766
|
+
},
|
|
721
767
|
build() {
|
|
722
768
|
// ========== Build-Time Validation ==========
|
|
723
769
|
// Validate React UI setup
|
|
@@ -811,7 +857,7 @@ function definePlugin(id, name, version) {
|
|
|
811
857
|
// Return a class that instantiates BuiltPlugin
|
|
812
858
|
return class extends BuiltPlugin {
|
|
813
859
|
constructor() {
|
|
814
|
-
super(id, name, version, _capabilities, _tools, _apiRoutes, _uiConfigured ? _ui : null, _reactScreens, _htmlScreens, _wizard, _onReady, _healthCheck);
|
|
860
|
+
super(id, name, version, _capabilities, _tools, _apiRoutes, _uiConfigured ? _ui : null, _reactScreens, _htmlScreens, _wizard, _onReady, _healthCheck, _pluginRoot);
|
|
815
861
|
}
|
|
816
862
|
};
|
|
817
863
|
}
|