mage-remote-run 0.25.0 → 0.26.1

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/lib/api/paas.js CHANGED
@@ -40,15 +40,21 @@ export class PaasClient extends ApiClient {
40
40
  const baseURL = config.baseURL || '';
41
41
  const url = baseURL.replace(/\/$/, '') + '/' + config.url.replace(/^\//, ''); // rudimentary join
42
42
 
43
+ // Determine signature method (default to HMAC-SHA256)
44
+ const signatureMethod = (this.config.auth.signatureMethod || 'hmac-sha256').toLowerCase();
45
+ const isSha1 = signatureMethod === 'hmac-sha1' || signatureMethod === 'sha1';
46
+ const methodStr = isSha1 ? 'HMAC-SHA1' : 'HMAC-SHA256';
47
+ const hashAlgo = isSha1 ? 'sha1' : 'sha256';
48
+
43
49
  const oauth = OAuth({
44
50
  consumer: {
45
51
  key: this.config.auth.consumerKey,
46
52
  secret: this.config.auth.consumerSecret
47
53
  },
48
- signature_method: 'HMAC-SHA256',
54
+ signature_method: methodStr,
49
55
  hash_function(base_string, key) {
50
56
  return crypto
51
- .createHmac('sha256', key)
57
+ .createHmac(hashAlgo, key)
52
58
  .update(base_string)
53
59
  .digest('base64');
54
60
  }
package/lib/api/saas.js CHANGED
@@ -38,6 +38,11 @@ export class SaasClient extends ApiClient {
38
38
  }
39
39
 
40
40
  async getToken() {
41
+ // Support static token from config
42
+ if (this.config.auth.token) {
43
+ return this.config.auth.token;
44
+ }
45
+
41
46
  if (this.token && Date.now() < this.tokenExpiresAt) {
42
47
  return this.token;
43
48
  }
@@ -7,6 +7,22 @@ import inquirer from 'inquirer';
7
7
  import chalk from 'chalk';
8
8
  import { getMissingB2BModules } from '../b2b.js';
9
9
 
10
+ // Helper to test connection (non-interactive or one-shot)
11
+ async function testConnection(name, settings) {
12
+ console.log(chalk.blue(`\nTesting connection "${name}"...`));
13
+ try {
14
+ const client = await createClient(settings);
15
+ const start = Date.now();
16
+ await client.get('V1/store/storeViews');
17
+ const duration = Date.now() - start;
18
+ console.log(chalk.green(`✔ Connection successful! (${duration}ms)`));
19
+ return { success: true };
20
+ } catch (e) {
21
+ console.error(chalk.red(`✖ Connection failed: ${e.message}`));
22
+ return { success: false, error: e };
23
+ }
24
+ }
25
+
10
26
  // Helper to handle interactive connection configuration and testing
11
27
  async function configureAndTestConnection(name, initialSettings = {}) {
12
28
  let settings = await askForProfileSettings(initialSettings);
@@ -19,18 +35,13 @@ async function configureAndTestConnection(name, initialSettings = {}) {
19
35
  });
20
36
 
21
37
  if (shouldTest) {
22
- console.log(chalk.blue(`\nTesting connection "${name}"...`));
23
- try {
24
- const client = await createClient(settings);
25
- const start = Date.now();
26
- await client.get('V1/store/storeViews');
27
- const duration = Date.now() - start;
28
- console.log(chalk.green(`✔ Connection successful! (${duration}ms)`));
38
+ const result = await testConnection(name, settings);
39
+
40
+ if (result.success) {
29
41
  lastTestError = null;
30
42
  break; // Test passed, proceed to save
31
- } catch (e) {
32
- console.error(chalk.red(`✖ Connection failed: ${e.message}`));
33
- lastTestError = e;
43
+ } else {
44
+ lastTestError = result.error;
34
45
  const shouldEdit = await confirm({
35
46
  message: 'Connection failed. Do you want to change settings?',
36
47
  default: true
@@ -345,41 +356,167 @@ export function registerConnectionCommands(program) {
345
356
  //-------------------------------------------------------
346
357
  connections.command('add')
347
358
  .description('Configure a new connection profile')
359
+ .option('--name <name>', 'Profile Name')
360
+ .option('--type <type>', 'System Type (magento-os, mage-os, ac-on-prem, ac-cloud-paas, ac-saas)')
361
+ .option('--url <url>', 'Instance URL')
362
+ .option('--client-id <id>', 'Client ID (SaaS)')
363
+ .option('--client-secret <secret>', 'Client Secret (SaaS)')
364
+ .option('--auth-method <method>', 'Auth Method (bearer, oauth1)')
365
+ .option('--token <token>', 'Bearer Token')
366
+ .option('--consumer-key <key>', 'Consumer Key (OAuth1)')
367
+ .option('--consumer-secret <secret>', 'Consumer Secret (OAuth1)')
368
+ .option('--access-token <token>', 'Access Token (OAuth1)')
369
+ .option('--token-secret <secret>', 'Token Secret (OAuth1)')
370
+ .option('--signature-method <method>', 'Signature Method (hmac-sha256, hmac-sha1)', 'hmac-sha256')
371
+ .option('--active', 'Set as active profile')
372
+ .option('--no-test', 'Skip connection test')
348
373
  .addHelpText('after', `
349
374
  Examples:
375
+ Interactive Mode:
350
376
  $ mage-remote-run connection add
377
+
378
+ SaaS (Non-Interactive):
379
+ $ mage-remote-run connection add --name "MySaaS" --type ac-saas --url "https://example.com" --client-id "id" --client-secret "secret" --active
380
+
381
+ SaaS (Pre-generated Token):
382
+ $ mage-remote-run connection add --name "MySaaS" --type ac-saas --url "https://example.com" --token "access_token_here"
383
+
384
+ PaaS (Integration Token):
385
+ $ mage-remote-run connection add --name "MyPaaS" --type ac-cloud-paas --url "https://paas.example.com" --token "integration_token"
386
+
387
+ OAuth 1.0a (Non-Interactive):
388
+ $ mage-remote-run connection add --name "MyOAuth" --type ac-on-prem --url "https://example.com" --consumer-key "ck" --consumer-secret "cs" --access-token "at" --token-secret "ts"
389
+
390
+ Bearer Token (Non-Interactive):
391
+ $ mage-remote-run connection add --name "MyStore" --type magento-os --url "https://magento.example.com" --token "tkn"
351
392
  `)
352
- .action(async () => {
393
+ .action(async (options) => {
353
394
  console.log(chalk.blue('Configure a new connection Profile'));
354
395
  try {
355
- const name = await input({
356
- message: 'Profile Name:',
357
- validate: value => value ? true : 'Name is required'
358
- });
396
+ let name = options.name;
397
+ let settings = null;
359
398
 
360
- const settings = await configureAndTestConnection(name);
361
- if (!settings) {
362
- console.log(chalk.yellow('\nConfiguration cancelled.'));
363
- return;
399
+ // Non-Interactive Mode
400
+ if (options.type) {
401
+ if (!name) {
402
+ throw new Error('Option --name is required when using --type');
403
+ }
404
+ if (!options.url) {
405
+ throw new Error('Option --url is required when using --type');
406
+ }
407
+
408
+ settings = {
409
+ type: options.type,
410
+ url: options.url,
411
+ auth: {}
412
+ };
413
+
414
+ if (options.type === 'ac-saas') {
415
+ if (options.token) {
416
+ settings.auth = { token: options.token };
417
+ // Optional: still save client ID/secret if provided, but token takes precedence
418
+ if (options.clientId) settings.auth.clientId = options.clientId;
419
+ if (options.clientSecret) settings.auth.clientSecret = options.clientSecret;
420
+ } else if (!options.clientId || !options.clientSecret) {
421
+ throw new Error('SaaS authentication requires --client-id and --client-secret (or --token)');
422
+ } else {
423
+ settings.auth = {
424
+ clientId: options.clientId,
425
+ clientSecret: options.clientSecret
426
+ };
427
+ }
428
+ } else {
429
+ // Infer auth method if not provided
430
+ let method = options.authMethod;
431
+ if (!method) {
432
+ if (options.token) {
433
+ method = 'bearer';
434
+ } else if (options.consumerKey && options.consumerSecret) {
435
+ method = 'oauth1';
436
+ }
437
+ }
438
+
439
+ if (method === 'bearer') {
440
+ if (!options.token) {
441
+ throw new Error('Bearer authentication requires --token');
442
+ }
443
+ settings.auth = {
444
+ method: 'bearer',
445
+ token: options.token
446
+ };
447
+ } else if (method === 'oauth1') {
448
+ if (!options.consumerKey || !options.consumerSecret || !options.accessToken || !options.tokenSecret) {
449
+ throw new Error('OAuth1 authentication requires --consumer-key, --consumer-secret, --access-token, and --token-secret');
450
+ }
451
+ settings.auth = {
452
+ method: 'oauth1',
453
+ consumerKey: options.consumerKey,
454
+ consumerSecret: options.consumerSecret,
455
+ accessToken: options.accessToken,
456
+ tokenSecret: options.tokenSecret,
457
+ signatureMethod: options.signatureMethod
458
+ };
459
+ } else {
460
+ throw new Error('Invalid or missing authentication options. Use --token for Bearer or provide OAuth keys.');
461
+ }
462
+ }
463
+
464
+ // Test connection if not skipped
465
+ let lastTestError = null;
466
+ if (options.test !== false) { // --no-test sets options.test to false
467
+ const result = await testConnection(name, settings);
468
+ if (!result.success) {
469
+ throw new Error(`Connection test failed: ${result.error.message}`);
470
+ }
471
+ }
472
+
473
+ settings = await updateProfileCapabilities(settings, lastTestError);
474
+
475
+ } else {
476
+ // Interactive Mode
477
+ name = await input({
478
+ message: 'Profile Name:',
479
+ validate: value => value ? true : 'Name is required'
480
+ });
481
+
482
+ settings = await configureAndTestConnection(name);
483
+ if (!settings) {
484
+ console.log(chalk.yellow('\nConfiguration cancelled.'));
485
+ return;
486
+ }
364
487
  }
365
488
 
366
489
  await addProfile(name, settings);
367
490
  console.log(chalk.green(`\nProfile "${name}" saved successfully!`));
368
491
 
369
- // Ask to set as active if multiple exist
370
492
  const config = await loadConfig();
371
- if (Object.keys(config.profiles).length > 1) {
372
- const setActive = await confirm({
373
- message: 'Set this as the active profile?',
374
- default: true
375
- });
376
-
377
- if (setActive) {
378
- config.activeProfile = name;
379
- await saveConfig(config);
380
- console.log(chalk.green(`Profile "${name}" set as active.`));
493
+
494
+ let setActive = false;
495
+ if (options.type) {
496
+ // Non-interactive: only set active if requested or if it's the only one (handled by addProfile implicitly for first one, but we check logic here)
497
+ if (options.active) {
498
+ setActive = true;
381
499
  }
500
+ } else {
501
+ // Interactive
502
+ if (Object.keys(config.profiles).length > 1) {
503
+ setActive = await confirm({
504
+ message: 'Set this as the active profile?',
505
+ default: true
506
+ });
507
+ }
508
+ }
509
+
510
+ // addProfile already sets active if it's the first profile.
511
+ // We only need to force it if requested or confirmed and it wasn't already set (e.g. multiple profiles)
512
+ // Actually addProfile sets it if !activeProfile.
513
+
514
+ if (setActive && config.activeProfile !== name) {
515
+ config.activeProfile = name;
516
+ await saveConfig(config);
517
+ console.log(chalk.green(`Profile "${name}" set as active.`));
382
518
  }
519
+
383
520
  } catch (e) {
384
521
  if (e.name === 'ExitPromptError') {
385
522
  console.log(chalk.yellow('\nConfiguration cancelled.'));
package/lib/prompts.js CHANGED
@@ -81,12 +81,22 @@ export async function askForProfileSettings(defaults = {}) {
81
81
  validate: value => (value || defaults.auth?.tokenSecret) ? true : 'Token Secret is required'
82
82
  });
83
83
 
84
+ const signatureMethod = await select({
85
+ message: 'Signature Method:',
86
+ default: defaults.auth?.signatureMethod || 'hmac-sha256',
87
+ choices: [
88
+ { name: 'HMAC-SHA256 (Default, Newer Versions)', value: 'hmac-sha256' },
89
+ { name: 'HMAC-SHA1 (Older Versions)', value: 'hmac-sha1' }
90
+ ]
91
+ });
92
+
84
93
  authConfig = {
85
94
  method: 'oauth1',
86
95
  consumerKey,
87
96
  consumerSecret: consumerSecret || defaults.auth?.consumerSecret,
88
97
  accessToken: accessToken || defaults.auth?.accessToken,
89
- tokenSecret: tokenSecret || defaults.auth?.tokenSecret
98
+ tokenSecret: tokenSecret || defaults.auth?.tokenSecret,
99
+ signatureMethod
90
100
  };
91
101
  }
92
102
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mage-remote-run",
3
- "version": "0.25.0",
3
+ "version": "0.26.1",
4
4
  "description": "The remote swiss army knife for Magento Open Source, Mage-OS, Adobe Commerce",
5
5
  "main": "index.js",
6
6
  "scripts": {