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 +8 -2
- package/lib/api/saas.js +5 -0
- package/lib/commands/connections.js +167 -30
- package/lib/prompts.js +11 -1
- package/package.json +1 -1
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:
|
|
54
|
+
signature_method: methodStr,
|
|
49
55
|
hash_function(base_string, key) {
|
|
50
56
|
return crypto
|
|
51
|
-
.createHmac(
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
}
|
|
32
|
-
|
|
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
|
-
|
|
356
|
-
|
|
357
|
-
validate: value => value ? true : 'Name is required'
|
|
358
|
-
});
|
|
396
|
+
let name = options.name;
|
|
397
|
+
let settings = null;
|
|
359
398
|
|
|
360
|
-
|
|
361
|
-
if (
|
|
362
|
-
|
|
363
|
-
|
|
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
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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
|
}
|