@magentrix-corp/magentrix-cli 1.3.16 → 1.3.17

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.
Files changed (68) hide show
  1. package/LICENSE +25 -25
  2. package/README.md +1166 -1166
  3. package/actions/autopublish.old.js +293 -293
  4. package/actions/config.js +182 -182
  5. package/actions/create.js +466 -466
  6. package/actions/help.js +164 -164
  7. package/actions/iris/buildStage.js +874 -874
  8. package/actions/iris/delete.js +256 -256
  9. package/actions/iris/dev.js +391 -391
  10. package/actions/iris/index.js +6 -6
  11. package/actions/iris/link.js +375 -375
  12. package/actions/iris/recover.js +268 -268
  13. package/actions/main.js +80 -80
  14. package/actions/publish.js +1420 -1420
  15. package/actions/pull.js +684 -684
  16. package/actions/setup.js +148 -148
  17. package/actions/status.js +17 -17
  18. package/actions/update.js +248 -248
  19. package/bin/magentrix.js +393 -393
  20. package/package.json +55 -55
  21. package/utils/assetPaths.js +158 -158
  22. package/utils/autopublishLock.js +77 -77
  23. package/utils/cacher.js +206 -206
  24. package/utils/cli/checkInstanceUrl.js +76 -74
  25. package/utils/cli/helpers/compare.js +282 -282
  26. package/utils/cli/helpers/ensureApiKey.js +63 -63
  27. package/utils/cli/helpers/ensureCredentials.js +68 -68
  28. package/utils/cli/helpers/ensureInstanceUrl.js +75 -75
  29. package/utils/cli/writeRecords.js +262 -262
  30. package/utils/compare.js +135 -135
  31. package/utils/compress.js +17 -17
  32. package/utils/config.js +527 -527
  33. package/utils/debug.js +144 -144
  34. package/utils/diagnostics/testPublishLogic.js +96 -96
  35. package/utils/diff.js +49 -49
  36. package/utils/downloadAssets.js +291 -291
  37. package/utils/filetag.js +115 -115
  38. package/utils/hash.js +14 -14
  39. package/utils/iris/backup.js +411 -411
  40. package/utils/iris/builder.js +541 -541
  41. package/utils/iris/config-reader.js +664 -664
  42. package/utils/iris/deleteHelper.js +150 -150
  43. package/utils/iris/errors.js +537 -537
  44. package/utils/iris/linker.js +601 -601
  45. package/utils/iris/lock.js +360 -360
  46. package/utils/iris/validation.js +360 -360
  47. package/utils/iris/validator.js +281 -281
  48. package/utils/iris/zipper.js +248 -248
  49. package/utils/logger.js +291 -291
  50. package/utils/magentrix/api/assets.js +220 -220
  51. package/utils/magentrix/api/auth.js +107 -107
  52. package/utils/magentrix/api/createEntity.js +61 -61
  53. package/utils/magentrix/api/deleteEntity.js +55 -55
  54. package/utils/magentrix/api/iris.js +251 -251
  55. package/utils/magentrix/api/meqlQuery.js +36 -36
  56. package/utils/magentrix/api/retrieveEntity.js +86 -86
  57. package/utils/magentrix/api/updateEntity.js +66 -66
  58. package/utils/magentrix/fetch.js +168 -168
  59. package/utils/merge.js +22 -22
  60. package/utils/permissionError.js +70 -70
  61. package/utils/preferences.js +40 -40
  62. package/utils/progress.js +469 -469
  63. package/utils/spinner.js +43 -43
  64. package/utils/template.js +52 -52
  65. package/utils/updateFileBase.js +121 -121
  66. package/utils/workspaces.js +108 -108
  67. package/vars/config.js +11 -11
  68. package/vars/global.js +50 -50
@@ -1,220 +1,220 @@
1
- import { fetchMagentrix } from "../fetch.js"
2
- import { createWriteStream, readFileSync } from "node:fs";
3
- import { pipeline } from "node:stream/promises";
4
- import { Readable } from "node:stream";
5
- import { Blob, File } from "node:buffer";
6
- import fsPath from 'path';
7
-
8
- async function fileToFileObj(fp, type = "application/octet-stream") {
9
- let buf = readFileSync(fp);
10
-
11
- // If file is empty, inject a single space (or any character)
12
- if (!buf || buf.length === 0) {
13
- buf = Buffer.from(" "); // could also use Buffer.from("x") or Buffer.alloc(1)
14
- }
15
-
16
- return new File([buf], fsPath.basename(fp), { type });
17
- }
18
-
19
- export const listAssets = async (instanceUrl, token, path) => {
20
- if (!instanceUrl || !token) {
21
- throw new Error('Missing required Magentrix instanceUrl or token');
22
- }
23
-
24
- let reqPath = `/api/3.0/staticassets`;
25
-
26
- if (path) {
27
- reqPath += `?path=${encodeURIComponent(path)}`
28
- }
29
-
30
- const response = await fetchMagentrix({
31
- instanceUrl,
32
- token,
33
- path: reqPath,
34
- method: "GET",
35
- returnErrorObject: true
36
- });
37
-
38
- return response;
39
- }
40
-
41
- export const uploadAsset = async (instanceUrl, token, targetPath, uploadPaths = []) => {
42
- if (!instanceUrl || !token) {
43
- throw new Error('Missing required Magentrix instanceUrl or token');
44
- }
45
-
46
- let reqPath = `/api/3.0/staticassets`;
47
-
48
- if (targetPath) {
49
- reqPath += `?path=${targetPath}`
50
- }
51
-
52
- const formData = new FormData();
53
- for (const fp of uploadPaths) {
54
- formData.append("files", await fileToFileObj(fp));
55
- }
56
-
57
- const response = await fetchMagentrix({
58
- instanceUrl,
59
- token,
60
- path: reqPath,
61
- ignoreContentType: true,
62
- // headers: {
63
- // 'Content-Type': "multipart/form-data"
64
- // },
65
- body: formData,
66
- method: "POST",
67
- returnErrorObject: true
68
- });
69
-
70
- return response;
71
- }
72
-
73
- // Name = asset names (including folders)
74
- // Path = path to assets
75
- export const deleteAsset = async (instanceUrl, token, path = '/contents/assets', names = []) => {
76
- if (!instanceUrl || !token) {
77
- throw new Error('Missing required Magentrix instanceUrl or token');
78
- }
79
-
80
- if (!path) throw new Error("Path is required when deleting assets.");
81
- if (!Array.isArray(names) || names?.length === 0) throw new Error("At least one file name is required when deleting static assets.");
82
-
83
- // Batch deletes dynamically to avoid URL length limits
84
- // Most servers/proxies have a 2048-8192 character limit for URLs
85
- // We use 2000 as a safe limit to account for other URL components
86
- const MAX_URL_LENGTH = 2000;
87
- const results = [];
88
-
89
- // Calculate base URL length (without file names)
90
- const basePath = `/api/3.0/staticassets?path=${encodeURIComponent(path)}&names=`;
91
- const baseLength = instanceUrl.length + basePath.length;
92
-
93
- let currentBatch = [];
94
- let currentUrlLength = baseLength;
95
-
96
- for (const name of names) {
97
- // Calculate the additional length this file would add
98
- const encodedName = encodeURIComponent(name);
99
- const additionalLength = encodedName.length + (currentBatch.length > 0 ? 1 : 0); // +1 for comma
100
-
101
- // Check if adding this file would exceed the URL limit
102
- if (currentUrlLength + additionalLength > MAX_URL_LENGTH && currentBatch.length > 0) {
103
- // Execute the current batch
104
- const reqPath = `/api/3.0/staticassets?path=${encodeURIComponent(path)}&names=${currentBatch.map(n => encodeURIComponent(n)).join(",")}`;
105
- const response = await fetchMagentrix({
106
- instanceUrl,
107
- token,
108
- path: reqPath,
109
- method: "DELETE",
110
- returnErrorObject: true
111
- });
112
- results.push(response);
113
-
114
- // Start new batch
115
- currentBatch = [name];
116
- currentUrlLength = baseLength + encodedName.length;
117
- } else {
118
- // Add to current batch
119
- currentBatch.push(name);
120
- currentUrlLength += additionalLength;
121
- }
122
- }
123
-
124
- // Execute the last batch if it has files
125
- if (currentBatch.length > 0) {
126
- const reqPath = `/api/3.0/staticassets?path=${encodeURIComponent(path)}&names=${currentBatch.map(n => encodeURIComponent(n)).join(",")}`;
127
- const response = await fetchMagentrix({
128
- instanceUrl,
129
- token,
130
- path: reqPath,
131
- method: "DELETE",
132
- returnErrorObject: true
133
- });
134
- results.push(response);
135
- }
136
-
137
- // Return the last response, or combine all responses if needed
138
- // For now, return the last one since deleteAsset is typically called with single files
139
- return results[results.length - 1];
140
- }
141
-
142
- export const createFolder = async (instanceUrl, token, path = '/contents/assets', name) => {
143
- if (!instanceUrl || !token) {
144
- throw new Error('Missing required Magentrix instanceUrl or token');
145
- }
146
-
147
- if (!name) throw new Error("Folder name is required when creating a folder.");
148
-
149
- let reqPath = `/api/3.0/staticassets/folder?path=${encodeURIComponent(path)}&name=${encodeURIComponent(name)}`;
150
-
151
- const response = await fetchMagentrix({
152
- instanceUrl,
153
- token,
154
- path: reqPath,
155
- method: "POST",
156
- returnErrorObject: true
157
- });
158
-
159
- return response;
160
- }
161
-
162
- /**
163
- * Download multiple static assets as a ZIP.
164
- * @param {object} opts
165
- * @param {string} opts.baseUrl - e.g. "https://your-app.com"
166
- * @param {string} opts.token - Bearer token
167
- * @param {string} opts.path - e.g. "/contents/assets/images"
168
- * @param {string[]} opts.names - e.g. ["logo.png","banner.jpg"]
169
- * @param {string} [opts.outFile] - Optional explicit output filename
170
- */
171
- export async function downloadAssetsZip({ baseUrl, token, path, names, outFile }) {
172
- if (!Array.isArray(names) || names.length === 0) {
173
- throw new Error("names must be a non-empty string[]");
174
- }
175
-
176
- const url = new URL("/api/3.0/staticassets/download", baseUrl);
177
- if (path) url.searchParams.set("path", path);
178
- url.searchParams.set('download-format', 'zip'); // Ensure even one file gets downloaded as a zip
179
- url.searchParams.set("names", names.join(",")); // API expects comma-separated names
180
-
181
- const res = await fetch(url, {
182
- method: "GET",
183
- headers: { Authorization: `Bearer ${token}` },
184
- });
185
-
186
- if (!res.ok) {
187
- // Try to surface server error body if JSON
188
- let detail = "";
189
- try {
190
- const data = await res.json();
191
- detail = data?.message || JSON.stringify(data);
192
- } catch {
193
- /* ignore */
194
- }
195
-
196
- // Create a more informative error message
197
- const errorInfo = [
198
- `Download failed (${res.status}): ${detail || res.statusText}`,
199
- `Path: ${path || '(root)'}`,
200
- `Files requested: ${names.join(', ')}`,
201
- `URL: ${url.toString()}`
202
- ].join('\n');
203
-
204
- throw new Error(errorInfo);
205
- }
206
-
207
- // Determine filename: use header if present, else fallback
208
- const cd = res.headers.get("content-disposition") || "";
209
- const match = /filename\*?=(?:UTF-8'')?["']?([^"';]+)["']?/i.exec(cd);
210
- const filename = outFile || (match ? decodeURIComponent(match[1]) : "assets.zip");
211
-
212
- // Stream to disk (no buffering whole file)
213
- const fileStream = createWriteStream(filename);
214
- const body = res.body; // ReadableStream (web)
215
- // Convert web stream -> node stream for pipeline
216
- const nodeReadable = Readable.fromWeb ? Readable.fromWeb(body) : body;
217
-
218
- await pipeline(nodeReadable, fileStream);
219
- return filename;
220
- }
1
+ import { fetchMagentrix } from "../fetch.js"
2
+ import { createWriteStream, readFileSync } from "node:fs";
3
+ import { pipeline } from "node:stream/promises";
4
+ import { Readable } from "node:stream";
5
+ import { Blob, File } from "node:buffer";
6
+ import fsPath from 'path';
7
+
8
+ async function fileToFileObj(fp, type = "application/octet-stream") {
9
+ let buf = readFileSync(fp);
10
+
11
+ // If file is empty, inject a single space (or any character)
12
+ if (!buf || buf.length === 0) {
13
+ buf = Buffer.from(" "); // could also use Buffer.from("x") or Buffer.alloc(1)
14
+ }
15
+
16
+ return new File([buf], fsPath.basename(fp), { type });
17
+ }
18
+
19
+ export const listAssets = async (instanceUrl, token, path) => {
20
+ if (!instanceUrl || !token) {
21
+ throw new Error('Missing required Magentrix instanceUrl or token');
22
+ }
23
+
24
+ let reqPath = `/api/3.0/staticassets`;
25
+
26
+ if (path) {
27
+ reqPath += `?path=${encodeURIComponent(path)}`
28
+ }
29
+
30
+ const response = await fetchMagentrix({
31
+ instanceUrl,
32
+ token,
33
+ path: reqPath,
34
+ method: "GET",
35
+ returnErrorObject: true
36
+ });
37
+
38
+ return response;
39
+ }
40
+
41
+ export const uploadAsset = async (instanceUrl, token, targetPath, uploadPaths = []) => {
42
+ if (!instanceUrl || !token) {
43
+ throw new Error('Missing required Magentrix instanceUrl or token');
44
+ }
45
+
46
+ let reqPath = `/api/3.0/staticassets`;
47
+
48
+ if (targetPath) {
49
+ reqPath += `?path=${targetPath}`
50
+ }
51
+
52
+ const formData = new FormData();
53
+ for (const fp of uploadPaths) {
54
+ formData.append("files", await fileToFileObj(fp));
55
+ }
56
+
57
+ const response = await fetchMagentrix({
58
+ instanceUrl,
59
+ token,
60
+ path: reqPath,
61
+ ignoreContentType: true,
62
+ // headers: {
63
+ // 'Content-Type': "multipart/form-data"
64
+ // },
65
+ body: formData,
66
+ method: "POST",
67
+ returnErrorObject: true
68
+ });
69
+
70
+ return response;
71
+ }
72
+
73
+ // Name = asset names (including folders)
74
+ // Path = path to assets
75
+ export const deleteAsset = async (instanceUrl, token, path = '/contents/assets', names = []) => {
76
+ if (!instanceUrl || !token) {
77
+ throw new Error('Missing required Magentrix instanceUrl or token');
78
+ }
79
+
80
+ if (!path) throw new Error("Path is required when deleting assets.");
81
+ if (!Array.isArray(names) || names?.length === 0) throw new Error("At least one file name is required when deleting static assets.");
82
+
83
+ // Batch deletes dynamically to avoid URL length limits
84
+ // Most servers/proxies have a 2048-8192 character limit for URLs
85
+ // We use 2000 as a safe limit to account for other URL components
86
+ const MAX_URL_LENGTH = 2000;
87
+ const results = [];
88
+
89
+ // Calculate base URL length (without file names)
90
+ const basePath = `/api/3.0/staticassets?path=${encodeURIComponent(path)}&names=`;
91
+ const baseLength = instanceUrl.length + basePath.length;
92
+
93
+ let currentBatch = [];
94
+ let currentUrlLength = baseLength;
95
+
96
+ for (const name of names) {
97
+ // Calculate the additional length this file would add
98
+ const encodedName = encodeURIComponent(name);
99
+ const additionalLength = encodedName.length + (currentBatch.length > 0 ? 1 : 0); // +1 for comma
100
+
101
+ // Check if adding this file would exceed the URL limit
102
+ if (currentUrlLength + additionalLength > MAX_URL_LENGTH && currentBatch.length > 0) {
103
+ // Execute the current batch
104
+ const reqPath = `/api/3.0/staticassets?path=${encodeURIComponent(path)}&names=${currentBatch.map(n => encodeURIComponent(n)).join(",")}`;
105
+ const response = await fetchMagentrix({
106
+ instanceUrl,
107
+ token,
108
+ path: reqPath,
109
+ method: "DELETE",
110
+ returnErrorObject: true
111
+ });
112
+ results.push(response);
113
+
114
+ // Start new batch
115
+ currentBatch = [name];
116
+ currentUrlLength = baseLength + encodedName.length;
117
+ } else {
118
+ // Add to current batch
119
+ currentBatch.push(name);
120
+ currentUrlLength += additionalLength;
121
+ }
122
+ }
123
+
124
+ // Execute the last batch if it has files
125
+ if (currentBatch.length > 0) {
126
+ const reqPath = `/api/3.0/staticassets?path=${encodeURIComponent(path)}&names=${currentBatch.map(n => encodeURIComponent(n)).join(",")}`;
127
+ const response = await fetchMagentrix({
128
+ instanceUrl,
129
+ token,
130
+ path: reqPath,
131
+ method: "DELETE",
132
+ returnErrorObject: true
133
+ });
134
+ results.push(response);
135
+ }
136
+
137
+ // Return the last response, or combine all responses if needed
138
+ // For now, return the last one since deleteAsset is typically called with single files
139
+ return results[results.length - 1];
140
+ }
141
+
142
+ export const createFolder = async (instanceUrl, token, path = '/contents/assets', name) => {
143
+ if (!instanceUrl || !token) {
144
+ throw new Error('Missing required Magentrix instanceUrl or token');
145
+ }
146
+
147
+ if (!name) throw new Error("Folder name is required when creating a folder.");
148
+
149
+ let reqPath = `/api/3.0/staticassets/folder?path=${encodeURIComponent(path)}&name=${encodeURIComponent(name)}`;
150
+
151
+ const response = await fetchMagentrix({
152
+ instanceUrl,
153
+ token,
154
+ path: reqPath,
155
+ method: "POST",
156
+ returnErrorObject: true
157
+ });
158
+
159
+ return response;
160
+ }
161
+
162
+ /**
163
+ * Download multiple static assets as a ZIP.
164
+ * @param {object} opts
165
+ * @param {string} opts.baseUrl - e.g. "https://your-app.com"
166
+ * @param {string} opts.token - Bearer token
167
+ * @param {string} opts.path - e.g. "/contents/assets/images"
168
+ * @param {string[]} opts.names - e.g. ["logo.png","banner.jpg"]
169
+ * @param {string} [opts.outFile] - Optional explicit output filename
170
+ */
171
+ export async function downloadAssetsZip({ baseUrl, token, path, names, outFile }) {
172
+ if (!Array.isArray(names) || names.length === 0) {
173
+ throw new Error("names must be a non-empty string[]");
174
+ }
175
+
176
+ const url = new URL("/api/3.0/staticassets/download", baseUrl);
177
+ if (path) url.searchParams.set("path", path);
178
+ url.searchParams.set('download-format', 'zip'); // Ensure even one file gets downloaded as a zip
179
+ url.searchParams.set("names", names.join(",")); // API expects comma-separated names
180
+
181
+ const res = await fetch(url, {
182
+ method: "GET",
183
+ headers: { Authorization: `Bearer ${token}` },
184
+ });
185
+
186
+ if (!res.ok) {
187
+ // Try to surface server error body if JSON
188
+ let detail = "";
189
+ try {
190
+ const data = await res.json();
191
+ detail = data?.message || JSON.stringify(data);
192
+ } catch {
193
+ /* ignore */
194
+ }
195
+
196
+ // Create a more informative error message
197
+ const errorInfo = [
198
+ `Download failed (${res.status}): ${detail || res.statusText}`,
199
+ `Path: ${path || '(root)'}`,
200
+ `Files requested: ${names.join(', ')}`,
201
+ `URL: ${url.toString()}`
202
+ ].join('\n');
203
+
204
+ throw new Error(errorInfo);
205
+ }
206
+
207
+ // Determine filename: use header if present, else fallback
208
+ const cd = res.headers.get("content-disposition") || "";
209
+ const match = /filename\*?=(?:UTF-8'')?["']?([^"';]+)["']?/i.exec(cd);
210
+ const filename = outFile || (match ? decodeURIComponent(match[1]) : "assets.zip");
211
+
212
+ // Stream to disk (no buffering whole file)
213
+ const fileStream = createWriteStream(filename);
214
+ const body = res.body; // ReadableStream (web)
215
+ // Convert web stream -> node stream for pipeline
216
+ const nodeReadable = Readable.fromWeb ? Readable.fromWeb(body) : body;
217
+
218
+ await pipeline(nodeReadable, fileStream);
219
+ return filename;
220
+ }
@@ -1,107 +1,107 @@
1
- import { fetchMagentrix } from "../fetch.js";
2
- import chalk from "chalk";
3
- import debug from '../../debug.js';
4
-
5
- /**
6
- * Authenticates with Magentrix and retrieves an access token using the API key as a refresh token.
7
- *
8
- * @async
9
- * @param {string} apiKey - The Magentrix API key (used as refresh_token).
10
- * @param {string} instanceUrl - The full Magentrix instance URL (e.g., https://yourorg.magentrixcloud.com).
11
- * @throws {Error} If the request fails, the server returns an error, or the response is invalid.
12
- * @returns {Promise<string>} Resolves to the Magentrix access token string.
13
- */
14
- export const getAccessToken = async (apiKey, instanceUrl) => {
15
- debug.auth(`Requesting access token from ${instanceUrl}/api/3.0/token`);
16
- try {
17
- const data = await fetchMagentrix({
18
- instanceUrl,
19
- path: '/api/3.0/token',
20
- body: {
21
- grant_type: "refresh_token",
22
- refresh_token: apiKey
23
- },
24
- method: "POST"
25
- })
26
-
27
- debug.auth('Token received successfully', { validUntil: data.validUntil });
28
- // Success
29
- return {
30
- token: data.token,
31
- validUntil: data.validUntil
32
- };
33
- } catch (error) {
34
- debug.auth(`Token request failed: ${error.message}`);
35
- throw new Error(`Error retrieving Magentrix access token: ${error.message}`);
36
- }
37
- };
38
-
39
- /**
40
- * Attempts to authenticate with the given API key and instance URL.
41
- * If authentication fails, throws an error with a friendly message and error details.
42
- *
43
- * @async
44
- * @param {string} apiKey - The Magentrix API key to authenticate with.
45
- * @param {string} instanceUrl - The Magentrix instance URL to authenticate against.
46
- * @returns {Promise<Object>} The token data returned by getAccessToken if successful.
47
- * @throws {Error} If authentication fails, with a message explaining the failure.
48
- */
49
- export const tryAuthenticate = async (apiKey, instanceUrl) => {
50
- try {
51
- return await getAccessToken(apiKey, instanceUrl);
52
- } catch (error) {
53
- const errorMessage = error.message || String(error);
54
-
55
- // Build formatted error message with colors and spacing
56
- let formattedMessage = '\n' + chalk.red.bold('✖ Authentication Failed') + '\n';
57
- formattedMessage += chalk.dim('─'.repeat(50)) + '\n\n';
58
-
59
- debug.auth(`Authentication failed, categorizing error: ${errorMessage.substring(0, 100)}`);
60
-
61
- if (errorMessage.includes('Network error')) {
62
- debug.auth('Error category: Network error (unable to reach instance)');
63
- formattedMessage += chalk.cyan.bold('🌐 Unable to reach the Magentrix instance') + '\n\n';
64
- formattedMessage += chalk.yellow(' Possible causes:') + '\n';
65
- formattedMessage += chalk.gray(' • Check your internet connection') + '\n';
66
- formattedMessage += chalk.gray(' • Verify the instance URL is correct') + '\n';
67
- formattedMessage += chalk.gray(' • Ensure the server is online and accessible') + '\n';
68
- } else if (errorMessage.includes('HTTP 401') || errorMessage.includes('HTTP 403') || errorMessage.includes('Unauthorized')) {
69
- debug.auth('Error category: Invalid API key (401/403)');
70
- formattedMessage += chalk.cyan.bold('🔑 Invalid API Key') + '\n\n';
71
- formattedMessage += chalk.yellow(' What to do:') + '\n';
72
- formattedMessage += chalk.gray(' • The API key you entered is incorrect') + '\n';
73
- formattedMessage += chalk.gray(' • Verify your API key from the Magentrix admin panel') + '\n';
74
- } else if (errorMessage.includes('HTTP 404')) {
75
- debug.auth('Error category: Invalid instance URL (404)');
76
- formattedMessage += chalk.cyan.bold('🔍 Invalid Magentrix Instance URL') + '\n\n';
77
- formattedMessage += chalk.yellow(' What to do:') + '\n';
78
- formattedMessage += chalk.gray(' • The URL does not appear to be a valid Magentrix server') + '\n';
79
- formattedMessage += chalk.gray(' • Verify the URL matches your Magentrix instance') + '\n';
80
- } else if (errorMessage.includes('HTTP 5')) {
81
- debug.auth('Error category: Server error (5xx)');
82
- formattedMessage += chalk.cyan.bold('⚠️ Magentrix Server Error') + '\n\n';
83
- formattedMessage += chalk.yellow(' What to do:') + '\n';
84
- formattedMessage += chalk.gray(' • The server is experiencing issues') + '\n';
85
- formattedMessage += chalk.gray(' • Please try again in a few moments') + '\n';
86
- formattedMessage += chalk.gray(' • Contact support if the issue persists') + '\n';
87
- } else if (errorMessage.includes('timeout') || errorMessage.includes('ETIMEDOUT')) {
88
- debug.auth('Error category: Connection timeout');
89
- formattedMessage += chalk.cyan.bold('⏱️ Connection Timeout') + '\n\n';
90
- formattedMessage += chalk.yellow(' What to do:') + '\n';
91
- formattedMessage += chalk.gray(' • The server took too long to respond') + '\n';
92
- formattedMessage += chalk.gray(' • Check your internet connection') + '\n';
93
- formattedMessage += chalk.gray(' • Try again in a moment') + '\n';
94
- } else {
95
- debug.auth('Error category: Unknown error');
96
- formattedMessage += chalk.cyan.bold('❓ Unable to Authenticate') + '\n\n';
97
- formattedMessage += chalk.yellow(' What to do:') + '\n';
98
- formattedMessage += chalk.gray(' • Verify both your API key and instance URL are correct') + '\n';
99
- formattedMessage += chalk.gray(' • Ensure the API key matches the instance URL') + '\n';
100
- }
101
-
102
- formattedMessage += '\n' + chalk.dim('─'.repeat(50)) + '\n';
103
-
104
- throw new Error(formattedMessage);
105
- }
106
- };
107
-
1
+ import { fetchMagentrix } from "../fetch.js";
2
+ import chalk from "chalk";
3
+ import debug from '../../debug.js';
4
+
5
+ /**
6
+ * Authenticates with Magentrix and retrieves an access token using the API key as a refresh token.
7
+ *
8
+ * @async
9
+ * @param {string} apiKey - The Magentrix API key (used as refresh_token).
10
+ * @param {string} instanceUrl - The full Magentrix instance URL (e.g., https://yourorg.magentrixcloud.com).
11
+ * @throws {Error} If the request fails, the server returns an error, or the response is invalid.
12
+ * @returns {Promise<string>} Resolves to the Magentrix access token string.
13
+ */
14
+ export const getAccessToken = async (apiKey, instanceUrl) => {
15
+ debug.auth(`Requesting access token from ${instanceUrl}/api/3.0/token`);
16
+ try {
17
+ const data = await fetchMagentrix({
18
+ instanceUrl,
19
+ path: '/api/3.0/token',
20
+ body: {
21
+ grant_type: "refresh_token",
22
+ refresh_token: apiKey
23
+ },
24
+ method: "POST"
25
+ })
26
+
27
+ debug.auth('Token received successfully', { validUntil: data.validUntil });
28
+ // Success
29
+ return {
30
+ token: data.token,
31
+ validUntil: data.validUntil
32
+ };
33
+ } catch (error) {
34
+ debug.auth(`Token request failed: ${error.message}`);
35
+ throw new Error(`Error retrieving Magentrix access token: ${error.message}`);
36
+ }
37
+ };
38
+
39
+ /**
40
+ * Attempts to authenticate with the given API key and instance URL.
41
+ * If authentication fails, throws an error with a friendly message and error details.
42
+ *
43
+ * @async
44
+ * @param {string} apiKey - The Magentrix API key to authenticate with.
45
+ * @param {string} instanceUrl - The Magentrix instance URL to authenticate against.
46
+ * @returns {Promise<Object>} The token data returned by getAccessToken if successful.
47
+ * @throws {Error} If authentication fails, with a message explaining the failure.
48
+ */
49
+ export const tryAuthenticate = async (apiKey, instanceUrl) => {
50
+ try {
51
+ return await getAccessToken(apiKey, instanceUrl);
52
+ } catch (error) {
53
+ const errorMessage = error.message || String(error);
54
+
55
+ // Build formatted error message with colors and spacing
56
+ let formattedMessage = '\n' + chalk.red.bold('✖ Authentication Failed') + '\n';
57
+ formattedMessage += chalk.dim('─'.repeat(50)) + '\n\n';
58
+
59
+ debug.auth(`Authentication failed, categorizing error: ${errorMessage.substring(0, 100)}`);
60
+
61
+ if (errorMessage.includes('Network error')) {
62
+ debug.auth('Error category: Network error (unable to reach instance)');
63
+ formattedMessage += chalk.cyan.bold('🌐 Unable to reach the Magentrix instance') + '\n\n';
64
+ formattedMessage += chalk.yellow(' Possible causes:') + '\n';
65
+ formattedMessage += chalk.gray(' • Check your internet connection') + '\n';
66
+ formattedMessage += chalk.gray(' • Verify the instance URL is correct') + '\n';
67
+ formattedMessage += chalk.gray(' • Ensure the server is online and accessible') + '\n';
68
+ } else if (errorMessage.includes('HTTP 401') || errorMessage.includes('HTTP 403') || errorMessage.includes('Unauthorized')) {
69
+ debug.auth('Error category: Invalid API key (401/403)');
70
+ formattedMessage += chalk.cyan.bold('🔑 Invalid API Key') + '\n\n';
71
+ formattedMessage += chalk.yellow(' What to do:') + '\n';
72
+ formattedMessage += chalk.gray(' • The API key you entered is incorrect') + '\n';
73
+ formattedMessage += chalk.gray(' • Verify your API key from the Magentrix admin panel') + '\n';
74
+ } else if (errorMessage.includes('HTTP 404')) {
75
+ debug.auth('Error category: Invalid instance URL (404)');
76
+ formattedMessage += chalk.cyan.bold('🔍 Invalid Magentrix Instance URL') + '\n\n';
77
+ formattedMessage += chalk.yellow(' What to do:') + '\n';
78
+ formattedMessage += chalk.gray(' • The URL does not appear to be a valid Magentrix server') + '\n';
79
+ formattedMessage += chalk.gray(' • Verify the URL matches your Magentrix instance') + '\n';
80
+ } else if (errorMessage.includes('HTTP 5')) {
81
+ debug.auth('Error category: Server error (5xx)');
82
+ formattedMessage += chalk.cyan.bold('⚠️ Magentrix Server Error') + '\n\n';
83
+ formattedMessage += chalk.yellow(' What to do:') + '\n';
84
+ formattedMessage += chalk.gray(' • The server is experiencing issues') + '\n';
85
+ formattedMessage += chalk.gray(' • Please try again in a few moments') + '\n';
86
+ formattedMessage += chalk.gray(' • Contact support if the issue persists') + '\n';
87
+ } else if (errorMessage.includes('timeout') || errorMessage.includes('ETIMEDOUT')) {
88
+ debug.auth('Error category: Connection timeout');
89
+ formattedMessage += chalk.cyan.bold('⏱️ Connection Timeout') + '\n\n';
90
+ formattedMessage += chalk.yellow(' What to do:') + '\n';
91
+ formattedMessage += chalk.gray(' • The server took too long to respond') + '\n';
92
+ formattedMessage += chalk.gray(' • Check your internet connection') + '\n';
93
+ formattedMessage += chalk.gray(' • Try again in a moment') + '\n';
94
+ } else {
95
+ debug.auth('Error category: Unknown error');
96
+ formattedMessage += chalk.cyan.bold('❓ Unable to Authenticate') + '\n\n';
97
+ formattedMessage += chalk.yellow(' What to do:') + '\n';
98
+ formattedMessage += chalk.gray(' • Verify both your API key and instance URL are correct') + '\n';
99
+ formattedMessage += chalk.gray(' • Ensure the API key matches the instance URL') + '\n';
100
+ }
101
+
102
+ formattedMessage += '\n' + chalk.dim('─'.repeat(50)) + '\n';
103
+
104
+ throw new Error(formattedMessage);
105
+ }
106
+ };
107
+