@majkapp/plugin-kit 1.0.6 → 1.0.8
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 +34 -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;AA8qBjB;;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
|
@@ -247,7 +247,7 @@ function getContentType(filePath) {
|
|
|
247
247
|
function serveSpa(ui, req, res, ctx) {
|
|
248
248
|
const url = new URL(req.url, 'http://localhost');
|
|
249
249
|
const rel = url.pathname.substring(ui.base.length) || '/';
|
|
250
|
-
const distPath = path_1.default.join(
|
|
250
|
+
const distPath = path_1.default.join(ctx.pluginRoot, ui.appDir);
|
|
251
251
|
let filePath = path_1.default.join(distPath, rel);
|
|
252
252
|
if (filePath.endsWith('/')) {
|
|
253
253
|
filePath = path_1.default.join(filePath, 'index.html');
|
|
@@ -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
|
}
|
|
@@ -462,10 +476,11 @@ class BuiltPlugin {
|
|
|
462
476
|
res.end(screen.html);
|
|
463
477
|
}
|
|
464
478
|
else {
|
|
465
|
-
const filePath = path_1.default.join(
|
|
479
|
+
const filePath = path_1.default.join(this.context.pluginRoot, screen.htmlFile);
|
|
466
480
|
// Security check
|
|
467
481
|
const normalized = path_1.default.resolve(filePath);
|
|
468
|
-
|
|
482
|
+
const pluginRoot = path_1.default.resolve(this.context.pluginRoot);
|
|
483
|
+
if (!normalized.startsWith(pluginRoot)) {
|
|
469
484
|
res.writeHead(403, corsHeaders({ 'Content-Type': 'text/plain' }));
|
|
470
485
|
res.end('Forbidden');
|
|
471
486
|
this.context.logger.warn(`Path traversal attempt: ${screen.htmlFile}`);
|
|
@@ -600,6 +615,7 @@ function definePlugin(id, name, version) {
|
|
|
600
615
|
let _settings = null;
|
|
601
616
|
let _onReady = null;
|
|
602
617
|
let _healthCheck = null;
|
|
618
|
+
let _pluginRoot = undefined;
|
|
603
619
|
const builder = {
|
|
604
620
|
topbar(route, opts) {
|
|
605
621
|
_topbars.push({
|
|
@@ -717,6 +733,13 @@ function definePlugin(id, name, version) {
|
|
|
717
733
|
_healthCheck = fn;
|
|
718
734
|
return this;
|
|
719
735
|
},
|
|
736
|
+
pluginRoot(dir) {
|
|
737
|
+
if (!dir || typeof dir !== 'string') {
|
|
738
|
+
throw new PluginBuildError('pluginRoot must be a non-empty string', 'Pass __dirname from your plugin entry file: .pluginRoot(__dirname)', { provided: dir });
|
|
739
|
+
}
|
|
740
|
+
_pluginRoot = dir;
|
|
741
|
+
return this;
|
|
742
|
+
},
|
|
720
743
|
build() {
|
|
721
744
|
// ========== Build-Time Validation ==========
|
|
722
745
|
// Validate React UI setup
|
|
@@ -810,7 +833,7 @@ function definePlugin(id, name, version) {
|
|
|
810
833
|
// Return a class that instantiates BuiltPlugin
|
|
811
834
|
return class extends BuiltPlugin {
|
|
812
835
|
constructor() {
|
|
813
|
-
super(id, name, version, _capabilities, _tools, _apiRoutes, _uiConfigured ? _ui : null, _reactScreens, _htmlScreens, _wizard, _onReady, _healthCheck);
|
|
836
|
+
super(id, name, version, _capabilities, _tools, _apiRoutes, _uiConfigured ? _ui : null, _reactScreens, _htmlScreens, _wizard, _onReady, _healthCheck, _pluginRoot);
|
|
814
837
|
}
|
|
815
838
|
};
|
|
816
839
|
}
|