@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.
- package/LICENSE +25 -25
- package/README.md +1166 -1166
- package/actions/autopublish.old.js +293 -293
- package/actions/config.js +182 -182
- package/actions/create.js +466 -466
- package/actions/help.js +164 -164
- package/actions/iris/buildStage.js +874 -874
- package/actions/iris/delete.js +256 -256
- package/actions/iris/dev.js +391 -391
- package/actions/iris/index.js +6 -6
- package/actions/iris/link.js +375 -375
- package/actions/iris/recover.js +268 -268
- package/actions/main.js +80 -80
- package/actions/publish.js +1420 -1420
- package/actions/pull.js +684 -684
- package/actions/setup.js +148 -148
- package/actions/status.js +17 -17
- package/actions/update.js +248 -248
- package/bin/magentrix.js +393 -393
- package/package.json +55 -55
- package/utils/assetPaths.js +158 -158
- package/utils/autopublishLock.js +77 -77
- package/utils/cacher.js +206 -206
- package/utils/cli/checkInstanceUrl.js +76 -74
- package/utils/cli/helpers/compare.js +282 -282
- package/utils/cli/helpers/ensureApiKey.js +63 -63
- package/utils/cli/helpers/ensureCredentials.js +68 -68
- package/utils/cli/helpers/ensureInstanceUrl.js +75 -75
- package/utils/cli/writeRecords.js +262 -262
- package/utils/compare.js +135 -135
- package/utils/compress.js +17 -17
- package/utils/config.js +527 -527
- package/utils/debug.js +144 -144
- package/utils/diagnostics/testPublishLogic.js +96 -96
- package/utils/diff.js +49 -49
- package/utils/downloadAssets.js +291 -291
- package/utils/filetag.js +115 -115
- package/utils/hash.js +14 -14
- package/utils/iris/backup.js +411 -411
- package/utils/iris/builder.js +541 -541
- package/utils/iris/config-reader.js +664 -664
- package/utils/iris/deleteHelper.js +150 -150
- package/utils/iris/errors.js +537 -537
- package/utils/iris/linker.js +601 -601
- package/utils/iris/lock.js +360 -360
- package/utils/iris/validation.js +360 -360
- package/utils/iris/validator.js +281 -281
- package/utils/iris/zipper.js +248 -248
- package/utils/logger.js +291 -291
- package/utils/magentrix/api/assets.js +220 -220
- package/utils/magentrix/api/auth.js +107 -107
- package/utils/magentrix/api/createEntity.js +61 -61
- package/utils/magentrix/api/deleteEntity.js +55 -55
- package/utils/magentrix/api/iris.js +251 -251
- package/utils/magentrix/api/meqlQuery.js +36 -36
- package/utils/magentrix/api/retrieveEntity.js +86 -86
- package/utils/magentrix/api/updateEntity.js +66 -66
- package/utils/magentrix/fetch.js +168 -168
- package/utils/merge.js +22 -22
- package/utils/permissionError.js +70 -70
- package/utils/preferences.js +40 -40
- package/utils/progress.js +469 -469
- package/utils/spinner.js +43 -43
- package/utils/template.js +52 -52
- package/utils/updateFileBase.js +121 -121
- package/utils/workspaces.js +108 -108
- package/vars/config.js +11 -11
- 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
|
+
|