mage-remote-run 1.7.0 → 1.8.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.
@@ -15,6 +15,7 @@ program
15
15
  .name('mage-remote-run')
16
16
  .description('The remote swiss army knife for Magento Open Source, Mage-OS, Adobe Commerce')
17
17
  .version(pkg.version)
18
+ .option('--ignore-plugins', 'Skip loading configured plugins')
18
19
  .configureHelp({
19
20
  visibleCommands: (cmd) => {
20
21
  const commands = cmd.commands.filter(c => !c._hidden);
@@ -49,6 +50,7 @@ import { eventBus, events } from '../lib/events.js';
49
50
  import { createClient } from '../lib/api/factory.js';
50
51
  import * as utils from '../lib/utils.js';
51
52
  import * as commandHelper from '../lib/command-helper.js';
53
+ import { hasIgnorePluginsFlag } from '../lib/cli-options.js';
52
54
 
53
55
  // Connection commands are registered dynamically via registerCommands
54
56
  // But we need them registered early if we want them to show up in help even if config fails?
@@ -69,10 +71,14 @@ program.command('mcp [args...]')
69
71
  if (args && args.length > 0) {
70
72
  // console.error(chalk.yellow(`[mage-remote-run] Warning: Received extra arguments for mcp command: ${args.join(' ')}`));
71
73
  }
72
- await startMcpServer(options);
74
+ await startMcpServer({
75
+ ...options,
76
+ ignorePlugins: program.opts().ignorePlugins,
77
+ });
73
78
  });
74
79
 
75
80
  const profile = await getActiveProfile();
81
+ const ignorePlugins = hasIgnorePluginsFlag(process.argv.slice(2));
76
82
 
77
83
  // Load Plugins
78
84
  // We construct an initial context.
@@ -94,8 +100,10 @@ const appContext = {
94
100
  },
95
101
  };
96
102
 
97
- const pluginLoader = new PluginLoader(appContext);
98
- await pluginLoader.loadPlugins();
103
+ if (!ignorePlugins) {
104
+ const pluginLoader = new PluginLoader(appContext);
105
+ await pluginLoader.loadPlugins();
106
+ }
99
107
 
100
108
  eventBus.emit(events.INIT, appContext);
101
109
  import { registerVirtualCommands } from '../lib/commands/virtual.js';
@@ -0,0 +1,3 @@
1
+ export function hasIgnorePluginsFlag(argv = []) {
2
+ return argv.includes('--ignore-plugins');
3
+ }
@@ -3,6 +3,18 @@ import { loadConfig, saveConfig } from '../config.js';
3
3
  import path from 'node:path';
4
4
  import { realpath } from 'node:fs/promises';
5
5
 
6
+ function getHomeDirectory() {
7
+ return process.env.HOME || process.env.USERPROFILE || '';
8
+ }
9
+
10
+ function expandHomeDirectory(pluginRef) {
11
+ if (pluginRef.startsWith('~/') || pluginRef.startsWith('~\\')) {
12
+ return path.join(getHomeDirectory(), pluginRef.slice(2));
13
+ }
14
+
15
+ return pluginRef;
16
+ }
17
+
6
18
  function isFilesystemPath(pluginRef) {
7
19
  const isScopedPackageName = /^@[^/\\]+\/[^/\\]+$/.test(pluginRef);
8
20
  const hasPathSeparator = pluginRef.includes('/') || pluginRef.includes('\\');
@@ -23,24 +35,47 @@ function isFilesystemPath(pluginRef) {
23
35
  async function resolvePluginReference(pluginRef) {
24
36
  if (!isFilesystemPath(pluginRef)) return pluginRef;
25
37
 
38
+ if (pluginRef.startsWith('file:')) {
39
+ return realpath(new URL(pluginRef));
40
+ }
41
+
42
+ return realpath(expandHomeDirectory(pluginRef));
43
+ }
44
+
45
+ async function normalizePluginReferenceForStorage(pluginRef) {
46
+ if (!isFilesystemPath(pluginRef) || pluginRef.startsWith('file:')) {
47
+ return resolvePluginReference(pluginRef);
48
+ }
49
+
50
+ const resolvedPluginRef = await resolvePluginReference(pluginRef);
51
+
26
52
  if (pluginRef.startsWith('~/') || pluginRef.startsWith('~\\')) {
27
- return realpath(path.join(process.env.HOME || process.env.USERPROFILE || '', pluginRef.slice(2)));
53
+ return pluginRef;
28
54
  }
29
55
 
30
- if (pluginRef.startsWith('file:')) {
31
- return realpath(new URL(pluginRef));
56
+ return resolvedPluginRef;
57
+ }
58
+
59
+ async function findRegisteredPluginIndex(plugins, pluginRef) {
60
+ const resolvedPluginRef = await resolvePluginReference(pluginRef);
61
+
62
+ for (const [index, registeredPlugin] of plugins.entries()) {
63
+ const resolvedRegisteredPlugin = await resolvePluginReference(registeredPlugin);
64
+ if (resolvedRegisteredPlugin === resolvedPluginRef) {
65
+ return index;
66
+ }
32
67
  }
33
68
 
34
- return realpath(pluginRef);
69
+ return -1;
35
70
  }
36
71
 
37
72
  export async function registerPluginAction(packageName) {
38
73
  try {
39
- const pluginRef = await resolvePluginReference(packageName);
74
+ const pluginRef = await normalizePluginReferenceForStorage(packageName);
40
75
  const config = await loadConfig();
41
76
  if (!config.plugins) config.plugins = [];
42
77
 
43
- if (config.plugins.includes(pluginRef)) {
78
+ if ((await findRegisteredPluginIndex(config.plugins, pluginRef)) !== -1) {
44
79
  console.log(chalk.yellow(`Plugin "${pluginRef}" is already registered.`));
45
80
  return;
46
81
  }
@@ -56,14 +91,15 @@ export async function registerPluginAction(packageName) {
56
91
 
57
92
  export async function unregisterPluginAction(packageName) {
58
93
  try {
59
- const pluginRef = await resolvePluginReference(packageName);
94
+ const pluginRef = await normalizePluginReferenceForStorage(packageName);
60
95
  const config = await loadConfig();
61
- if (!config.plugins || !config.plugins.includes(pluginRef)) {
96
+ const pluginIndex = config.plugins ? await findRegisteredPluginIndex(config.plugins, pluginRef) : -1;
97
+ if (pluginIndex === -1) {
62
98
  console.log(chalk.yellow(`Plugin "${pluginRef}" is not registered.`));
63
99
  return;
64
100
  }
65
101
 
66
- config.plugins = config.plugins.filter(p => p !== pluginRef);
102
+ config.plugins = config.plugins.filter((_, index) => index !== pluginIndex);
67
103
  await saveConfig(config);
68
104
  console.log(chalk.green(`Plugin "${pluginRef}" successfully unregistered.`));
69
105
  } catch (error) {
package/lib/mcp.js CHANGED
@@ -139,7 +139,7 @@ export async function startMcpServer(options) {
139
139
  );
140
140
 
141
141
  // 1. Setup a dynamic program to discovery commands
142
- const program = await setupProgramAsync();
142
+ const program = await setupProgramAsync(options);
143
143
 
144
144
  const server = new McpServer({
145
145
  name: "mage-remote-run",
@@ -362,7 +362,7 @@ function registerTools(server, program, options = {}) {
362
362
  description,
363
363
  zodShape,
364
364
  async (args) => {
365
- return await executeCommand(cmd, args, segments);
365
+ return await executeCommand(cmd, args, segments, options);
366
366
  }
367
367
  );
368
368
  count++;
@@ -373,7 +373,7 @@ function registerTools(server, program, options = {}) {
373
373
 
374
374
  // Re-register all commands on a fresh program instance
375
375
  // We export this logic so we can reuse it
376
- async function setupProgramAsync() {
376
+ async function setupProgramAsync(options = {}) {
377
377
  const program = new Command();
378
378
 
379
379
  // Silence output for the main program instance to avoid double printing during parsing
@@ -394,8 +394,10 @@ async function setupProgramAsync() {
394
394
  events
395
395
  };
396
396
 
397
- const pluginLoader = new PluginLoader(appContext);
398
- await pluginLoader.loadPlugins();
397
+ if (!options.ignorePlugins) {
398
+ const pluginLoader = new PluginLoader(appContext);
399
+ await pluginLoader.loadPlugins();
400
+ }
399
401
 
400
402
  localEventBus.emit(events.INIT, appContext);
401
403
 
@@ -412,7 +414,7 @@ async function setupProgramAsync() {
412
414
  return program;
413
415
  }
414
416
 
415
- async function executeCommand(cmdDefinition, args, commandSegments) {
417
+ async function executeCommand(cmdDefinition, args, commandSegments, options = {}) {
416
418
  // Intercept Console
417
419
  let output = '';
418
420
  const originalLog = console.log;
@@ -428,7 +430,7 @@ async function executeCommand(cmdDefinition, args, commandSegments) {
428
430
  console.error = logInterceptor;
429
431
 
430
432
  try {
431
- const program = await setupProgramAsync();
433
+ const program = await setupProgramAsync(options);
432
434
 
433
435
  // Construct argv
434
436
  // We need to build [node, script, command, subcommand, ..., args, options]
@@ -8,6 +8,14 @@ import { loadConfig } from './config.js';
8
8
 
9
9
  const require = createRequire(import.meta.url);
10
10
 
11
+ function expandHomeDirectory(pluginRef) {
12
+ if (pluginRef.startsWith('~/') || pluginRef.startsWith('~\\')) {
13
+ return path.join(process.env.HOME || process.env.USERPROFILE || '', pluginRef.slice(2));
14
+ }
15
+
16
+ return pluginRef;
17
+ }
18
+
11
19
  export class PluginLoader {
12
20
  constructor(appContext) {
13
21
  this.appContext = appContext;
@@ -32,15 +40,16 @@ export class PluginLoader {
32
40
  }
33
41
 
34
42
  async loadPlugin(pluginName) {
43
+ const resolvedPluginName = expandHomeDirectory(pluginName);
35
44
  let pluginPath;
36
45
 
37
46
  // 1. Try local node_modules
38
47
  try {
39
- pluginPath = require.resolve(pluginName);
48
+ pluginPath = require.resolve(resolvedPluginName);
40
49
  } catch (e) {
41
50
  // 2. Try global node_modules (npm)
42
51
  try {
43
- const globalNpmPath = path.join(globalDirs.npm.packages, pluginName);
52
+ const globalNpmPath = path.join(globalDirs.npm.packages, resolvedPluginName);
44
53
  const npmExists = await fs.promises.access(globalNpmPath).then(() => true).catch(() => false);
45
54
  if (npmExists) {
46
55
  const pkgJsonPath = path.join(globalNpmPath, 'package.json');
@@ -55,7 +64,7 @@ export class PluginLoader {
55
64
  }
56
65
  } else {
57
66
  // 3. Try global node_modules (yarn)
58
- const globalYarnPath = path.join(globalDirs.yarn.packages, pluginName);
67
+ const globalYarnPath = path.join(globalDirs.yarn.packages, resolvedPluginName);
59
68
  const yarnExists = await fs.promises.access(globalYarnPath).then(() => true).catch(() => false);
60
69
  if (yarnExists) {
61
70
  const pkgJsonPath = path.join(globalYarnPath, 'package.json');
@@ -77,7 +86,7 @@ export class PluginLoader {
77
86
 
78
87
  if (!pluginPath) {
79
88
  throw new Error(`Could not resolve plugin '${pluginName}' locally or globally.`);
80
- }
89
+ }
81
90
 
82
91
  // Import using file URL for absolute paths in ESM
83
92
  // Windows paths need to be converted to file URLs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mage-remote-run",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "The remote swiss army knife for Magento Open Source, Mage-OS, Adobe Commerce",
5
5
  "main": "index.js",
6
6
  "scripts": {