mage-remote-run 0.1.0 → 0.2.5

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.
@@ -35,11 +35,63 @@ registerEavCommands(program);
35
35
  registerProductsCommands(program);
36
36
  registerTaxCommands(program);
37
37
 
38
+ function resolveCommandMatch(parent, token) {
39
+ const tokenLower = token.toLowerCase();
40
+ const matches = parent.commands.filter((cmd) => {
41
+ const name = cmd.name().toLowerCase();
42
+ if (name.startsWith(tokenLower)) return true;
43
+ const aliases = cmd.aliases ? cmd.aliases() : [];
44
+ return aliases.some((alias) => alias.toLowerCase().startsWith(tokenLower));
45
+ });
46
+
47
+ return {
48
+ match: matches.length === 1 ? matches[0] : null,
49
+ matches
50
+ };
51
+ }
52
+
53
+ function expandCommandAbbreviations(rootCommand, argv) {
54
+ const expanded = [];
55
+ let current = rootCommand;
56
+ const path = [];
57
+
58
+ for (let i = 0; i < argv.length; i += 1) {
59
+ const token = argv[i];
60
+ if (token.startsWith('-')) {
61
+ expanded.push(token);
62
+ continue;
63
+ }
64
+
65
+ if (!current.commands || current.commands.length === 0) {
66
+ expanded.push(...argv.slice(i));
67
+ break;
68
+ }
69
+
70
+ const { match, matches } = resolveCommandMatch(current, token);
71
+ if (!match) {
72
+ if (matches.length > 1) {
73
+ const parentName = path.length > 0 ? path.join(' ') : current.name();
74
+ const options = matches.map((cmd) => cmd.name()).join(', ');
75
+ console.error(`Ambiguous command "${token}" under "${parentName}". Options: ${options}.`);
76
+ process.exit(1);
77
+ }
78
+ expanded.push(token);
79
+ continue;
80
+ }
81
+
82
+ expanded.push(match.name());
83
+ current = match;
84
+ path.push(match.name());
85
+ }
86
+
87
+ return expanded;
88
+ }
89
+
38
90
  // Check for first run (no profiles configured and no arguments or just help)
39
91
  // We need to check args length.
40
92
  // node script.js -> length 2.
41
93
  // node script.js command -> length 3.
42
- const args = process.argv.slice(2);
94
+ let args = process.argv.slice(2);
43
95
  const config = await loadConfig();
44
96
  const hasProfiles = Object.keys(config.profiles || {}).length > 0;
45
97
 
@@ -55,7 +107,10 @@ if (!hasProfiles && args.length === 0) {
55
107
  // Easiest is to manually invoke program.parse with ['node', 'script', 'connection', 'add']
56
108
  // BUT program.parse executes asynchronously usually? commander is synchronous by default but actions are async.
57
109
  // Let's modify process.argv before parsing.
58
- process.argv = [...process.argv.slice(0, 2), 'connection', 'add'];
110
+ args = ['connection', 'add'];
59
111
  }
60
112
 
113
+ args = expandCommandAbbreviations(program, args);
114
+ process.argv = [...process.argv.slice(0, 2), ...args];
115
+
61
116
  program.parse(process.argv);
package/lib/api/paas.js CHANGED
@@ -19,7 +19,7 @@ export class PaasClient extends ApiClient {
19
19
  this.openApi = new OpenAPIClientAxios({
20
20
  definition,
21
21
  axiosConfigDefaults: {
22
- baseURL: `${this.baseUrl}/rest/V1`
22
+ baseURL: `${this.baseUrl}/rest`
23
23
  }
24
24
  });
25
25
 
package/lib/api/saas.js CHANGED
@@ -2,6 +2,7 @@ import axios from 'axios';
2
2
  import { ApiClient } from './client.js';
3
3
  import { OpenAPIClientAxios } from 'openapi-client-axios';
4
4
  import { loadSpec } from './spec-loader.js';
5
+ import { loadTokenCache, saveTokenCache } from '../config.js';
5
6
 
6
7
  export class SaasClient extends ApiClient {
7
8
  constructor(config) {
@@ -16,10 +17,11 @@ export class SaasClient extends ApiClient {
16
17
  if (this.openApi) return;
17
18
 
18
19
  const definition = loadSpec(this.config.type);
20
+ const basePath = this.baseUrl;
19
21
  this.openApi = new OpenAPIClientAxios({
20
22
  definition,
21
23
  axiosConfigDefaults: {
22
- baseURL: `${this.baseUrl}/rest/V1`, // Base for generated client
24
+ baseURL: basePath, // Base for generated client
23
25
  // Note: The definition might have servers block, but we override functionality.
24
26
  }
25
27
  });
@@ -40,19 +42,49 @@ export class SaasClient extends ApiClient {
40
42
  return this.token;
41
43
  }
42
44
 
43
- const tokenUrl = `${this.baseUrl}/oauth/token`;
45
+ const tokenUrl = (this.config.type === 'ac-saas' || this.config.type === 'saas')
46
+ ? 'https://ims-na1.adobelogin.com/ims/token/v3'
47
+ : `${this.baseUrl}/oauth/token`;
44
48
 
45
49
  try {
50
+ const cacheKey = `${this.config.type}|${this.config.url}|${this.config.auth.clientId}`;
51
+ const cache = await loadTokenCache();
52
+ const cachedEntry = cache[cacheKey];
53
+ if (cachedEntry?.token && cachedEntry?.expiresAt && Date.now() < cachedEntry.expiresAt) {
54
+ this.token = cachedEntry.token;
55
+ this.tokenExpiresAt = cachedEntry.expiresAt;
56
+ return this.token;
57
+ }
58
+
46
59
  // Use raw axios for token fetch to avoid circular dependency or interceptor issues
47
- const response = await axios.post(tokenUrl, {
48
- grant_type: 'client_credentials',
49
- client_id: this.config.auth.clientId,
50
- client_secret: this.config.auth.clientSecret
51
- });
60
+ let response;
61
+ if (this.config.type === 'ac-saas' || this.config.type === 'saas') {
62
+ const payload = new URLSearchParams({
63
+ grant_type: 'client_credentials',
64
+ client_id: this.config.auth.clientId,
65
+ client_secret: this.config.auth.clientSecret,
66
+ scope: 'openid,AdobeID,email,profile,additional_info.roles,additional_info.projectedProductContext,commerce.accs'
67
+ });
68
+ response = await axios.post(tokenUrl, payload.toString(), {
69
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
70
+ });
71
+ } else {
72
+ const payload = {
73
+ grant_type: 'client_credentials',
74
+ client_id: this.config.auth.clientId,
75
+ client_secret: this.config.auth.clientSecret
76
+ };
77
+ response = await axios.post(tokenUrl, payload);
78
+ }
52
79
 
53
80
  this.token = response.data.access_token;
54
81
  const expiresIn = response.data.expires_in || 3600;
55
82
  this.tokenExpiresAt = Date.now() + (expiresIn * 1000) - 60000;
83
+ cache[cacheKey] = {
84
+ token: this.token,
85
+ expiresAt: this.tokenExpiresAt
86
+ };
87
+ await saveTokenCache(cache);
56
88
 
57
89
  return this.token;
58
90
  } catch (error) {
@@ -66,7 +98,7 @@ export class SaasClient extends ApiClient {
66
98
  // Use the configured axios instance for generic requests
67
99
  // Note: endpoint passed here is like 'store/websites'
68
100
  // openapi-client-axios client base is usually set to server root or we adjusted it.
69
- // If we set baseURL to /rest/V1, we just need the relative path.
101
+ // baseURL is the instance root; include API version in the endpoint.
70
102
 
71
103
  // Ensure endpoint does not start with / if base has it, or handle cleanly
72
104
  const cleanEndpoint = endpoint.replace(/^\//, '');
@@ -1,4 +1,4 @@
1
- import { loadConfig, saveConfig, addProfile } from '../config.js';
1
+ import { loadConfig, saveConfig, addProfile, clearTokenCache } from '../config.js';
2
2
  import { printTable, handleError } from '../utils.js';
3
3
  import { askForProfileSettings } from '../prompts.js';
4
4
  import { createClient } from '../api/factory.js';
@@ -134,7 +134,7 @@ export function registerConnectionCommands(program) {
134
134
  const client = await createClient(profileConfig);
135
135
 
136
136
  const start = Date.now();
137
- await client.get('store/storeViews');
137
+ await client.get('V1/store/storeViews');
138
138
  const duration = Date.now() - start;
139
139
 
140
140
  results.push([name, chalk.green('SUCCESS'), `${duration}ms`]);
@@ -155,7 +155,7 @@ export function registerConnectionCommands(program) {
155
155
  try {
156
156
  const client = await createClient(); // Uses active profile
157
157
  const start = Date.now();
158
- await client.get('store/storeViews');
158
+ await client.get('V1/store/storeViews');
159
159
  const duration = Date.now() - start;
160
160
 
161
161
  console.log(chalk.green(`\n✔ Connection successful! (${duration}ms)`));
@@ -211,4 +211,13 @@ export function registerConnectionCommands(program) {
211
211
  console.log(chalk.green(`Active profile set to "${selected}".`));
212
212
  } catch (e) { handleError(e); }
213
213
  });
214
+
215
+ connections.command('clear-token-cache')
216
+ .description('Clear cached access tokens')
217
+ .action(async () => {
218
+ try {
219
+ await clearTokenCache();
220
+ console.log(chalk.green('Token cache cleared.'));
221
+ } catch (e) { handleError(e); }
222
+ });
214
223
  }
@@ -16,7 +16,7 @@ export function registerCustomersCommands(program) {
16
16
  'searchCriteria[currentPage]': options.page,
17
17
  'searchCriteria[pageSize]': options.size
18
18
  };
19
- const data = await client.get('customers/search', params);
19
+ const data = await client.get('V1/customers/search', params);
20
20
  const rows = (data.items || []).map(c => [c.id, c.email, c.firstname, c.lastname, c.group_id]);
21
21
  console.log(chalk.bold(`Total: ${data.total_count}, Page: ${options.page}, Size: ${options.size}`));
22
22
  printTable(['ID', 'Email', 'First Name', 'Last Name', 'Group'], rows);
@@ -33,7 +33,7 @@ export function registerCustomersCommands(program) {
33
33
  'searchCriteria[filter_groups][0][filters][0][value]': `%${query}%`,
34
34
  'searchCriteria[filter_groups][0][filters][0][condition_type]': 'like'
35
35
  };
36
- const data = await client.get('customers/search', params);
36
+ const data = await client.get('V1/customers/search', params);
37
37
  const rows = (data.items || []).map(c => [c.id, c.email, c.firstname, c.lastname]);
38
38
  printTable(['ID', 'Email', 'Name', 'Lastname'], rows);
39
39
  } catch (e) { handleError(e); }
@@ -43,7 +43,7 @@ export function registerCustomersCommands(program) {
43
43
  .action(async (id) => {
44
44
  try {
45
45
  const client = await createClient();
46
- const data = await client.get(`customers/${id}`);
46
+ const data = await client.get(`V1/customers/${id}`);
47
47
 
48
48
  const answers = await inquirer.prompt([
49
49
  { name: 'firstname', message: 'First Name', default: data.firstname },
@@ -60,7 +60,7 @@ export function registerCustomersCommands(program) {
60
60
  }
61
61
  };
62
62
 
63
- await client.put(`customers/${id}`, payload);
63
+ await client.put(`V1/customers/${id}`, payload);
64
64
  console.log(chalk.green(`Customer ${id} updated.`));
65
65
  } catch (e) { handleError(e); }
66
66
  });
@@ -75,7 +75,7 @@ export function registerCustomersCommands(program) {
75
75
  if (options.format === 'json') headers['Accept'] = 'application/json';
76
76
  else if (options.format === 'xml') headers['Accept'] = 'application/xml';
77
77
 
78
- const data = await client.get(`customers/${customerId}`, {}, { headers });
78
+ const data = await client.get(`V1/customers/${customerId}`, {}, { headers });
79
79
 
80
80
  if (options.format === 'json') {
81
81
  console.log(JSON.stringify(data, null, 2));
@@ -139,7 +139,7 @@ export function registerCustomersCommands(program) {
139
139
  }
140
140
 
141
141
  const client = await createClient();
142
- await client.delete(`customers/${customerId}`);
142
+ await client.delete(`V1/customers/${customerId}`);
143
143
  console.log(chalk.green(`✅ Customer ${customerId} deleted.`));
144
144
  } catch (e) { handleError(e); }
145
145
  });
@@ -154,7 +154,7 @@ export function registerCustomersCommands(program) {
154
154
 
155
155
  if (customerId) {
156
156
  try {
157
- const customer = await client.get(`customers/${customerId}`);
157
+ const customer = await client.get(`V1/customers/${customerId}`);
158
158
  email = customer.email;
159
159
  websiteId = customer.website_id;
160
160
  console.log(chalk.gray(`Fetched customer ${customerId}: ${email} (Website: ${websiteId})`));
@@ -183,7 +183,7 @@ export function registerCustomersCommands(program) {
183
183
  redirectUrl: options.redirectUrl || undefined
184
184
  };
185
185
 
186
- await client.post('customers/confirm', payload);
186
+ await client.post('V1/customers/confirm', payload);
187
187
  console.log(chalk.green(`✅ Confirmation email sent to ${email}`));
188
188
  } catch (e) {
189
189
  if (e.response && e.response.status === 400 && e.response.data && e.response.data.message === "Confirmation isn't needed.") {
@@ -19,7 +19,7 @@ export function registerEavCommands(program) {
19
19
  'searchCriteria[currentPage]': options.page,
20
20
  'searchCriteria[pageSize]': options.size
21
21
  };
22
- const data = await client.get('eav/attribute-sets/list', params);
22
+ const data = await client.get('V1/eav/attribute-sets/list', params);
23
23
  const rows = (data.items || []).map(set => [
24
24
  set.attribute_set_id,
25
25
  set.attribute_set_name,
@@ -36,7 +36,7 @@ export function registerEavCommands(program) {
36
36
  .action(async (id) => {
37
37
  try {
38
38
  const client = await createClient();
39
- const data = await client.get(`eav/attribute-sets/${id}`);
39
+ const data = await client.get(`V1/eav/attribute-sets/${id}`);
40
40
 
41
41
  console.log(chalk.bold.blue('\nAttribute Set Details:'));
42
42
  console.log(`${chalk.bold('ID:')} ${data.attribute_set_id}`);
@@ -16,7 +16,7 @@ export function registerOrdersCommands(program) {
16
16
  'searchCriteria[currentPage]': options.page,
17
17
  'searchCriteria[pageSize]': options.size
18
18
  };
19
- const data = await client.get('orders', params);
19
+ const data = await client.get('V1/orders', params);
20
20
  const rows = (data.items || []).map(o => [o.entity_id, o.increment_id, o.status, o.grand_total, o.customer_email]);
21
21
  console.log(chalk.bold(`Total: ${data.total_count}, Page: ${options.page}, Size: ${options.size}`));
22
22
  printTable(['ID', 'Increment ID', 'Status', 'Total', 'Email'], rows);
@@ -33,7 +33,7 @@ export function registerOrdersCommands(program) {
33
33
  'searchCriteria[filter_groups][0][filters][0][value]': `%${query}%`,
34
34
  'searchCriteria[filter_groups][0][filters][0][condition_type]': 'like'
35
35
  };
36
- const data = await client.get('orders', params);
36
+ const data = await client.get('V1/orders', params);
37
37
  const rows = (data.items || []).map(o => [o.entity_id, o.increment_id, o.status, o.grand_total]);
38
38
  printTable(['ID', 'Increment ID', 'Status', 'Total'], rows);
39
39
  } catch (e) { handleError(e); }
@@ -52,7 +52,7 @@ export function registerOrdersCommands(program) {
52
52
  ]);
53
53
 
54
54
  // POST /V1/orders/:id/comments
55
- await client.post(`orders/${id}/comments`, {
55
+ await client.post(`V1/orders/${id}/comments`, {
56
56
  statusHistory: {
57
57
  comment: answers.comment,
58
58
  status: answers.status,
@@ -86,7 +86,7 @@ export function registerOrdersCommands(program) {
86
86
  'searchCriteria[sortOrders][0][field]': 'created_at',
87
87
  'searchCriteria[sortOrders][0][direction]': 'DESC'
88
88
  };
89
- const data = await client.get('orders', params);
89
+ const data = await client.get('V1/orders', params);
90
90
  const items = data.items || [];
91
91
 
92
92
  if (items.length === 0) {
@@ -141,7 +141,7 @@ async function showOrder(identifier, format = 'text') {
141
141
  }
142
142
 
143
143
  try {
144
- order = await client.get(`orders/${identifier}`, {}, { headers });
144
+ order = await client.get(`V1/orders/${identifier}`, {}, { headers });
145
145
  } catch (e) {
146
146
  // If 404, maybe it was an increment ID
147
147
  // Or if user provided something that is definitely an increment ID
@@ -150,14 +150,14 @@ async function showOrder(identifier, format = 'text') {
150
150
  'searchCriteria[filter_groups][0][filters][0][value]': identifier,
151
151
  'searchCriteria[filter_groups][0][filters][0][condition_type]': 'eq'
152
152
  };
153
- const searchData = await client.get('orders', params);
153
+ const searchData = await client.get('V1/orders', params);
154
154
  if (searchData.items && searchData.items.length > 0) {
155
155
  order = searchData.items[0];
156
156
  // If format is specific, we might want to re-fetch by ID with headers if search didn't return format?
157
157
  // Search API usually returns JSON. If user wants XML, search return helps find ID, then we fetch XML.
158
158
  if (format !== 'text') {
159
159
  try {
160
- order = await client.get(`orders/${order.entity_id}`, {}, { headers });
160
+ order = await client.get(`V1/orders/${order.entity_id}`, {}, { headers });
161
161
  } catch (subError) {
162
162
  // ignore or log? If we fail to get formatted, fallback or throw?
163
163
  // If we found it via search, 'order' is the object.
@@ -10,7 +10,7 @@ export function registerProductsCommands(program) {
10
10
  .action(async () => {
11
11
  try {
12
12
  const client = await createClient();
13
- const data = await client.get('products/types');
13
+ const data = await client.get('V1/products/types');
14
14
  const rows = (data || []).map(t => [t.name, t.label]);
15
15
  printTable(['Name (ID)', 'Label'], rows);
16
16
  } catch (e) { handleError(e); }
@@ -31,7 +31,7 @@ export function registerProductsCommands(program) {
31
31
  'searchCriteria[sortOrders][0][field]': 'attribute_code',
32
32
  'searchCriteria[sortOrders][0][direction]': 'ASC'
33
33
  };
34
- const data = await client.get('products/attributes', params);
34
+ const data = await client.get('V1/products/attributes', params);
35
35
  const rows = (data.items || []).map(a => [
36
36
  a.attribute_id,
37
37
  a.attribute_code,
@@ -56,7 +56,7 @@ export function registerProductsCommands(program) {
56
56
 
57
57
  let data;
58
58
  try {
59
- data = await client.get(`products/attributes/${attributeCode}`, {}, { headers });
59
+ data = await client.get(`V1/products/attributes/${attributeCode}`, {}, { headers });
60
60
  } catch (e) {
61
61
  throw new Error(`Attribute '${attributeCode}' not found.`);
62
62
  }
@@ -125,7 +125,7 @@ export function registerProductsCommands(program) {
125
125
  .action(async () => {
126
126
  try {
127
127
  const client = await createClient();
128
- const data = await client.get('products/attributes/types');
128
+ const data = await client.get('V1/products/attributes/types');
129
129
  const rows = (data || []).map(t => [t.value, t.label]);
130
130
  printTable(['Value', 'Label'], rows);
131
131
  } catch (e) { handleError(e); }
@@ -10,7 +10,7 @@ export function registerStoresCommands(program) {
10
10
  .action(async () => {
11
11
  try {
12
12
  const client = await createClient();
13
- const data = await client.get('store/storeGroups');
13
+ const data = await client.get('V1/store/storeGroups');
14
14
  const rows = data.map(s => [s.id, s.code, s.name, s.website_id, s.root_category_id]);
15
15
  printTable(['ID', 'Code', 'Name', 'Web ID', 'Root Cat'], rows);
16
16
  } catch (e) { handleError(e); }
@@ -20,7 +20,7 @@ export function registerStoresCommands(program) {
20
20
  .action(async (query) => {
21
21
  try {
22
22
  const client = await createClient();
23
- const data = await client.get('store/storeGroups');
23
+ const data = await client.get('V1/store/storeGroups');
24
24
  const filtered = data.filter(s => s.name.includes(query) || s.code.includes(query));
25
25
  const rows = filtered.map(s => [s.id, s.code, s.name]);
26
26
  printTable(['ID', 'Code', 'Name'], rows);
@@ -33,7 +33,7 @@ export function registerStoresCommands(program) {
33
33
  const client = await createClient();
34
34
  const { confirm } = await inquirer.prompt([{ type: 'confirm', name: 'confirm', message: `Delete Store Group ${id}?` }]);
35
35
  if (!confirm) return;
36
- await client.delete(`store/storeGroups/${id}`);
36
+ await client.delete(`V1/store/storeGroups/${id}`);
37
37
  console.log(chalk.green(`Store Group ${id} deleted.`));
38
38
  } catch (e) { handleError(e); }
39
39
  });
@@ -42,7 +42,7 @@ export function registerStoresCommands(program) {
42
42
  .action(async (id) => {
43
43
  try {
44
44
  const client = await createClient();
45
- const all = await client.get('store/storeGroups');
45
+ const all = await client.get('V1/store/storeGroups');
46
46
  const current = all.find(s => s.id == id);
47
47
  if (!current) throw new Error(`Store ${id} not found`);
48
48
 
@@ -52,7 +52,7 @@ export function registerStoresCommands(program) {
52
52
  { name: 'website_id', message: 'Website ID', default: current.website_id }
53
53
  ]);
54
54
 
55
- await client.put(`store/storeGroups/${id}`, { group: { id: parseInt(id), ...answers } });
55
+ await client.put(`V1/store/storeGroups/${id}`, { group: { id: parseInt(id), ...answers } });
56
56
  console.log(chalk.green(`Store ${id} updated.`));
57
57
  } catch (e) { handleError(e); }
58
58
  });
@@ -64,7 +64,7 @@ export function registerStoresCommands(program) {
64
64
  .action(async () => {
65
65
  try {
66
66
  const client = await createClient();
67
- const data = await client.get('store/storeViews');
67
+ const data = await client.get('V1/store/storeViews');
68
68
  const rows = data.map(v => [v.id, v.code, v.name, v.store_group_id, v.is_active]);
69
69
  printTable(['ID', 'Code', 'Name', 'Group ID', 'Active'], rows);
70
70
  } catch (e) { handleError(e); }
@@ -74,7 +74,7 @@ export function registerStoresCommands(program) {
74
74
  .action(async (query) => {
75
75
  try {
76
76
  const client = await createClient();
77
- const data = await client.get('store/storeViews');
77
+ const data = await client.get('V1/store/storeViews');
78
78
  const filtered = data.filter(v => v.name.includes(query) || v.code.includes(query));
79
79
  const rows = filtered.map(v => [v.id, v.code, v.name]);
80
80
  printTable(['ID', 'Code', 'Name'], rows);
@@ -87,7 +87,7 @@ export function registerStoresCommands(program) {
87
87
  const client = await createClient();
88
88
  const { confirm } = await inquirer.prompt([{ type: 'confirm', name: 'confirm', message: `Delete Store View ${id}?` }]);
89
89
  if (!confirm) return;
90
- await client.delete(`store/storeViews/${id}`);
90
+ await client.delete(`V1/store/storeViews/${id}`);
91
91
  console.log(chalk.green(`Store View ${id} deleted.`));
92
92
  } catch (e) { handleError(e); }
93
93
  });
@@ -96,7 +96,7 @@ export function registerStoresCommands(program) {
96
96
  .action(async (id) => {
97
97
  try {
98
98
  const client = await createClient();
99
- const all = await client.get('store/storeViews');
99
+ const all = await client.get('V1/store/storeViews');
100
100
  const current = all.find(v => v.id == id);
101
101
  if (!current) throw new Error(`View ${id} not found`);
102
102
 
@@ -106,7 +106,7 @@ export function registerStoresCommands(program) {
106
106
  { name: 'is_active', message: 'Is Active (0/1)', default: current.is_active }
107
107
  ]);
108
108
 
109
- await client.put(`store/storeViews/${id}`, { store: { id: parseInt(id), ...answers } });
109
+ await client.put(`V1/store/storeViews/${id}`, { store: { id: parseInt(id), ...answers } });
110
110
  console.log(chalk.green(`Store View ${id} updated.`));
111
111
  } catch (e) { handleError(e); }
112
112
  });
@@ -18,7 +18,7 @@ export function registerTaxCommands(program) {
18
18
  'searchCriteria[currentPage]': options.page,
19
19
  'searchCriteria[pageSize]': options.size
20
20
  };
21
- const data = await client.get('taxClasses/search', params);
21
+ const data = await client.get('V1/taxClasses/search', params);
22
22
  const rows = (data.items || []).map(tc => [
23
23
  tc.class_id,
24
24
  tc.class_name,
@@ -34,7 +34,7 @@ export function registerTaxCommands(program) {
34
34
  .action(async (id) => {
35
35
  try {
36
36
  const client = await createClient();
37
- const data = await client.get(`taxClasses/${id}`);
37
+ const data = await client.get(`V1/taxClasses/${id}`);
38
38
 
39
39
  console.log(chalk.bold.blue('\nTax Class Details:'));
40
40
  console.log(`${chalk.bold('ID:')} ${data.class_id}`);
@@ -11,7 +11,7 @@ export function registerWebsitesCommands(program) {
11
11
  .action(async () => {
12
12
  try {
13
13
  const client = await createClient();
14
- const data = await client.get('store/websites');
14
+ const data = await client.get('V1/store/websites');
15
15
  const rows = data.map(w => [w.id, w.code, w.name, w.default_group_id]);
16
16
  printTable(['ID', 'Code', 'Name', 'Def Group ID'], rows);
17
17
  } catch (e) { handleError(e); }
@@ -22,7 +22,7 @@ export function registerWebsitesCommands(program) {
22
22
  .action(async (query) => {
23
23
  try {
24
24
  const client = await createClient();
25
- const data = await client.get('store/websites');
25
+ const data = await client.get('V1/store/websites');
26
26
  const filtered = data.filter(w =>
27
27
  w.code.includes(query) || w.name.includes(query)
28
28
  );
@@ -44,7 +44,7 @@ export function registerWebsitesCommands(program) {
44
44
  }]);
45
45
  if (!confirm) return;
46
46
 
47
- await client.delete(`store/websites/${id}`);
47
+ await client.delete(`V1/store/websites/${id}`);
48
48
  console.log(chalk.green(`Website ${id} deleted.`));
49
49
  } catch (e) { handleError(e); }
50
50
  });
@@ -57,7 +57,7 @@ export function registerWebsitesCommands(program) {
57
57
  // Fetch current
58
58
  // Actually Magento doesn't have a single GET /store/websites/:id easily publicly documented distinct from the list?
59
59
  // But let's assume we fetch list and find it.
60
- const all = await client.get('store/websites');
60
+ const all = await client.get('V1/store/websites');
61
61
  const current = all.find(w => w.id == id);
62
62
  if (!current) throw new Error(`Website ${id} not found`);
63
63
 
@@ -69,7 +69,7 @@ export function registerWebsitesCommands(program) {
69
69
  ]);
70
70
 
71
71
  // PUT /V1/store/websites/:id
72
- await client.put(`store/websites/${id}`, { website: { id: parseInt(id), ...answers } });
72
+ await client.put(`V1/store/websites/${id}`, { website: { id: parseInt(id), ...answers } });
73
73
  console.log(chalk.green(`Website ${id} updated.`));
74
74
  } catch (e) { handleError(e); }
75
75
  });
package/lib/config.js CHANGED
@@ -6,6 +6,7 @@ import { mkdirp } from 'mkdirp';
6
6
  const paths = envPaths('mage-remote-run', { suffix: '' });
7
7
  const CONFIG_DIR = paths.config;
8
8
  const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
9
+ const TOKEN_CACHE_FILE = path.join(CONFIG_DIR, 'token-cache.json');
9
10
 
10
11
  export async function loadConfig() {
11
12
  try {
@@ -52,3 +53,37 @@ export async function getActiveProfile() {
52
53
  export function getConfigPath() {
53
54
  return CONFIG_FILE;
54
55
  }
56
+
57
+ export async function loadTokenCache() {
58
+ try {
59
+ if (!fs.existsSync(TOKEN_CACHE_FILE)) {
60
+ return {};
61
+ }
62
+ const data = fs.readFileSync(TOKEN_CACHE_FILE, 'utf-8');
63
+ return JSON.parse(data);
64
+ } catch (e) {
65
+ console.error("Error loading token cache:", e.message);
66
+ return {};
67
+ }
68
+ }
69
+
70
+ export async function saveTokenCache(cache) {
71
+ try {
72
+ await mkdirp(CONFIG_DIR);
73
+ fs.writeFileSync(TOKEN_CACHE_FILE, JSON.stringify(cache, null, 2));
74
+ } catch (e) {
75
+ console.error("Error saving token cache:", e.message);
76
+ throw e;
77
+ }
78
+ }
79
+
80
+ export async function clearTokenCache() {
81
+ try {
82
+ if (fs.existsSync(TOKEN_CACHE_FILE)) {
83
+ fs.unlinkSync(TOKEN_CACHE_FILE);
84
+ }
85
+ } catch (e) {
86
+ console.error("Error clearing token cache:", e.message);
87
+ throw e;
88
+ }
89
+ }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "mage-remote-run",
3
- "version": "0.1.0",
3
+ "version": "0.2.5",
4
4
  "description": "The remote swiss army knife for Magento Open Source, Mage-OS, Adobe Commerce",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
- "test": "NODE_OPTIONS=--experimental-vm-modules jest",
7
+ "test": "NODE_OPTIONS=\"--experimental-vm-modules --localstorage-file=./.localstorage\" jest",
8
8
  "start": "node bin/mage-remote-run.js",
9
9
  "release": "release-it"
10
10
  },
@@ -16,6 +16,10 @@
16
16
  },
17
17
  "license": "MIT",
18
18
  "type": "module",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/muench-dev/mage-remote-run"
22
+ },
19
23
  "bin": {
20
24
  "mage-remote-run": "./bin/mage-remote-run.js"
21
25
  },