@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.
@@ -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
  }
@@ -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;AA+pBjB;;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,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,CAoUnB"}
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"}
@@ -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(process.cwd(), ui.appDir);
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
- this.context = context;
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: ${context.pluginRoot}`);
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(process.cwd(), screen.htmlFile);
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
- if (!normalized.startsWith(process.cwd())) {
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@majkapp/plugin-kit",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Fluent builder framework for creating robust MAJK plugins",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",