@magentrix-corp/magentrix-cli 1.1.5 → 1.2.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/README.md +156 -8
- package/actions/config.js +182 -0
- package/actions/create.js +30 -9
- package/actions/publish.js +509 -82
- package/actions/pull.js +494 -199
- package/actions/setup.js +63 -16
- package/actions/update.js +248 -0
- package/bin/magentrix.js +13 -1
- package/package.json +1 -1
- package/utils/assetPaths.js +24 -4
- package/utils/cacher.js +122 -53
- package/utils/cli/helpers/ensureApiKey.js +28 -22
- package/utils/cli/helpers/ensureInstanceUrl.js +36 -28
- package/utils/cli/writeRecords.js +47 -8
- package/utils/config.js +76 -0
- package/utils/diagnostics/testPublishLogic.js +96 -0
- package/utils/downloadAssets.js +230 -19
- package/utils/logger.js +283 -0
- package/utils/magentrix/api/assets.js +65 -10
- package/utils/magentrix/api/auth.js +45 -6
- package/utils/progress.js +383 -0
- package/utils/updateFileBase.js +6 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { fetchMagentrix } from "../fetch.js";
|
|
2
|
+
import chalk from "chalk";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Authenticates with Magentrix and retrieves an access token using the API key as a refresh token.
|
|
@@ -45,12 +46,50 @@ export const tryAuthenticate = async (apiKey, instanceUrl) => {
|
|
|
45
46
|
try {
|
|
46
47
|
return await getAccessToken(apiKey, instanceUrl);
|
|
47
48
|
} catch (error) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
const errorMessage = error.message || String(error);
|
|
50
|
+
|
|
51
|
+
// Build formatted error message with colors and spacing
|
|
52
|
+
let formattedMessage = '\n' + chalk.red.bold('✖ Authentication Failed') + '\n';
|
|
53
|
+
formattedMessage += chalk.dim('─'.repeat(50)) + '\n\n';
|
|
54
|
+
|
|
55
|
+
if (errorMessage.includes('Network error')) {
|
|
56
|
+
formattedMessage += chalk.cyan.bold('🌐 Unable to reach the Magentrix instance') + '\n\n';
|
|
57
|
+
formattedMessage += chalk.yellow(' Possible causes:') + '\n';
|
|
58
|
+
formattedMessage += chalk.gray(' • Check your internet connection') + '\n';
|
|
59
|
+
formattedMessage += chalk.gray(' • Verify the instance URL is correct') + '\n';
|
|
60
|
+
formattedMessage += chalk.gray(' • Ensure the server is online and accessible') + '\n';
|
|
61
|
+
} else if (errorMessage.includes('HTTP 401') || errorMessage.includes('HTTP 403') || errorMessage.includes('Unauthorized')) {
|
|
62
|
+
formattedMessage += chalk.cyan.bold('🔑 Invalid API Key') + '\n\n';
|
|
63
|
+
formattedMessage += chalk.yellow(' What to do:') + '\n';
|
|
64
|
+
formattedMessage += chalk.gray(' • The API key you entered is incorrect') + '\n';
|
|
65
|
+
formattedMessage += chalk.gray(' • Verify your API key from the Magentrix admin panel') + '\n';
|
|
66
|
+
} else if (errorMessage.includes('HTTP 404')) {
|
|
67
|
+
formattedMessage += chalk.cyan.bold('🔍 Invalid Magentrix Instance URL') + '\n\n';
|
|
68
|
+
formattedMessage += chalk.yellow(' What to do:') + '\n';
|
|
69
|
+
formattedMessage += chalk.gray(' • The URL does not appear to be a valid Magentrix server') + '\n';
|
|
70
|
+
formattedMessage += chalk.gray(' • Verify the URL matches your Magentrix instance') + '\n';
|
|
71
|
+
} else if (errorMessage.includes('HTTP 5')) {
|
|
72
|
+
formattedMessage += chalk.cyan.bold('⚠️ Magentrix Server Error') + '\n\n';
|
|
73
|
+
formattedMessage += chalk.yellow(' What to do:') + '\n';
|
|
74
|
+
formattedMessage += chalk.gray(' • The server is experiencing issues') + '\n';
|
|
75
|
+
formattedMessage += chalk.gray(' • Please try again in a few moments') + '\n';
|
|
76
|
+
formattedMessage += chalk.gray(' • Contact support if the issue persists') + '\n';
|
|
77
|
+
} else if (errorMessage.includes('timeout') || errorMessage.includes('ETIMEDOUT')) {
|
|
78
|
+
formattedMessage += chalk.cyan.bold('⏱️ Connection Timeout') + '\n\n';
|
|
79
|
+
formattedMessage += chalk.yellow(' What to do:') + '\n';
|
|
80
|
+
formattedMessage += chalk.gray(' • The server took too long to respond') + '\n';
|
|
81
|
+
formattedMessage += chalk.gray(' • Check your internet connection') + '\n';
|
|
82
|
+
formattedMessage += chalk.gray(' • Try again in a moment') + '\n';
|
|
83
|
+
} else {
|
|
84
|
+
formattedMessage += chalk.cyan.bold('❓ Unable to Authenticate') + '\n\n';
|
|
85
|
+
formattedMessage += chalk.yellow(' What to do:') + '\n';
|
|
86
|
+
formattedMessage += chalk.gray(' • Verify both your API key and instance URL are correct') + '\n';
|
|
87
|
+
formattedMessage += chalk.gray(' • Ensure the API key matches the instance URL') + '\n';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
formattedMessage += '\n' + chalk.dim('─'.repeat(50)) + '\n';
|
|
91
|
+
|
|
92
|
+
throw new Error(formattedMessage);
|
|
54
93
|
}
|
|
55
94
|
};
|
|
56
95
|
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Comprehensive progress tracker for multi-step operations with sub-progress.
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Multiple step tracking
|
|
8
|
+
* - Progress bars for individual steps
|
|
9
|
+
* - Nested progress (e.g., batches within a step)
|
|
10
|
+
* - Time tracking
|
|
11
|
+
* - Dynamic message updates
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* const progress = new ProgressTracker('Pull Operation');
|
|
15
|
+
* progress.addStep('auth', 'Authenticating...');
|
|
16
|
+
* progress.addStep('download', 'Downloading files...');
|
|
17
|
+
* progress.addStep('process', 'Processing files...');
|
|
18
|
+
*
|
|
19
|
+
* progress.start();
|
|
20
|
+
* progress.startStep('auth');
|
|
21
|
+
* // ... do auth work
|
|
22
|
+
* progress.completeStep('auth', 'Authenticated');
|
|
23
|
+
*
|
|
24
|
+
* progress.startStep('download');
|
|
25
|
+
* progress.updateProgress('download', 0, 100);
|
|
26
|
+
* // ... download files
|
|
27
|
+
* progress.updateProgress('download', 50, 100);
|
|
28
|
+
* progress.completeStep('download', 'Downloaded 100 files');
|
|
29
|
+
*
|
|
30
|
+
* progress.finish();
|
|
31
|
+
*/
|
|
32
|
+
export class ProgressTracker {
|
|
33
|
+
constructor(title = 'Operation') {
|
|
34
|
+
this.title = title;
|
|
35
|
+
this.steps = new Map();
|
|
36
|
+
this.currentStep = null;
|
|
37
|
+
this.startTime = null;
|
|
38
|
+
this.spinnerChars = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
39
|
+
this.spinnerIndex = 0;
|
|
40
|
+
this.interval = null;
|
|
41
|
+
this.lastRenderLength = 0;
|
|
42
|
+
this.isFinished = false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Add a step to track
|
|
47
|
+
* @param {string} id - Unique identifier for the step
|
|
48
|
+
* @param {string} label - Display label for the step
|
|
49
|
+
* @param {object} options - Optional configuration
|
|
50
|
+
* @param {boolean} options.hasProgress - Whether this step has progress (0-100%)
|
|
51
|
+
*/
|
|
52
|
+
addStep(id, label, options = {}) {
|
|
53
|
+
this.steps.set(id, {
|
|
54
|
+
id,
|
|
55
|
+
label,
|
|
56
|
+
status: 'pending', // pending, active, completed, failed
|
|
57
|
+
hasProgress: options.hasProgress || false,
|
|
58
|
+
progress: { current: 0, total: 100 },
|
|
59
|
+
message: '',
|
|
60
|
+
startTime: null,
|
|
61
|
+
endTime: null,
|
|
62
|
+
error: null
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Start the progress tracker
|
|
68
|
+
*/
|
|
69
|
+
start() {
|
|
70
|
+
this.startTime = Date.now();
|
|
71
|
+
this.isFinished = false;
|
|
72
|
+
this.render();
|
|
73
|
+
this.startSpinner();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Start a specific step
|
|
78
|
+
* @param {string} id - Step identifier
|
|
79
|
+
* @param {string} message - Optional custom message
|
|
80
|
+
*/
|
|
81
|
+
startStep(id, message = '') {
|
|
82
|
+
const step = this.steps.get(id);
|
|
83
|
+
if (!step) {
|
|
84
|
+
throw new Error(`Step '${id}' not found`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Complete previous step if any
|
|
88
|
+
if (this.currentStep && this.steps.get(this.currentStep).status === 'active') {
|
|
89
|
+
this.completeStep(this.currentStep);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
step.status = 'active';
|
|
93
|
+
step.startTime = Date.now();
|
|
94
|
+
step.message = message || step.label;
|
|
95
|
+
this.currentStep = id;
|
|
96
|
+
this.render();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Update progress for a step
|
|
101
|
+
* @param {string} id - Step identifier
|
|
102
|
+
* @param {number} current - Current progress value
|
|
103
|
+
* @param {number} total - Total progress value
|
|
104
|
+
* @param {string} message - Optional message to display
|
|
105
|
+
*/
|
|
106
|
+
updateProgress(id, current, total, message = '') {
|
|
107
|
+
const step = this.steps.get(id);
|
|
108
|
+
if (!step) return;
|
|
109
|
+
|
|
110
|
+
step.progress.current = current;
|
|
111
|
+
step.progress.total = total;
|
|
112
|
+
if (message) {
|
|
113
|
+
step.message = message;
|
|
114
|
+
}
|
|
115
|
+
this.render();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Complete a step
|
|
120
|
+
* @param {string} id - Step identifier
|
|
121
|
+
* @param {string} message - Optional completion message
|
|
122
|
+
*/
|
|
123
|
+
completeStep(id, message = '') {
|
|
124
|
+
const step = this.steps.get(id);
|
|
125
|
+
if (!step) return;
|
|
126
|
+
|
|
127
|
+
step.status = 'completed';
|
|
128
|
+
step.endTime = Date.now();
|
|
129
|
+
if (message) {
|
|
130
|
+
step.message = message;
|
|
131
|
+
}
|
|
132
|
+
this.render();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Mark a step as failed
|
|
137
|
+
* @param {string} id - Step identifier
|
|
138
|
+
* @param {string} error - Error message
|
|
139
|
+
*/
|
|
140
|
+
failStep(id, error) {
|
|
141
|
+
const step = this.steps.get(id);
|
|
142
|
+
if (!step) return;
|
|
143
|
+
|
|
144
|
+
step.status = 'failed';
|
|
145
|
+
step.endTime = Date.now();
|
|
146
|
+
step.error = error;
|
|
147
|
+
this.render();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Update the message for the current step
|
|
152
|
+
* @param {string} message - New message
|
|
153
|
+
*/
|
|
154
|
+
updateMessage(message) {
|
|
155
|
+
if (!this.currentStep) return;
|
|
156
|
+
const step = this.steps.get(this.currentStep);
|
|
157
|
+
if (step) {
|
|
158
|
+
step.message = message;
|
|
159
|
+
this.render();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Render the progress display
|
|
165
|
+
*/
|
|
166
|
+
render() {
|
|
167
|
+
if (this.isFinished) return;
|
|
168
|
+
|
|
169
|
+
// Clear previous render
|
|
170
|
+
if (this.lastRenderLength > 0) {
|
|
171
|
+
for (let i = 0; i < this.lastRenderLength; i++) {
|
|
172
|
+
process.stdout.write('\x1B[1A\x1B[2K'); // Move up and clear line
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const lines = [];
|
|
177
|
+
|
|
178
|
+
// Title with elapsed time
|
|
179
|
+
const elapsed = this.formatDuration(Date.now() - this.startTime);
|
|
180
|
+
lines.push(chalk.bold.blue(`\n${this.title}`) + chalk.gray(` (${elapsed})`));
|
|
181
|
+
lines.push('');
|
|
182
|
+
|
|
183
|
+
// Render each step
|
|
184
|
+
for (const [id, step] of this.steps) {
|
|
185
|
+
const prefix = this.getStepPrefix(step);
|
|
186
|
+
const label = step.status === 'active'
|
|
187
|
+
? chalk.cyan(step.label)
|
|
188
|
+
: step.status === 'completed'
|
|
189
|
+
? chalk.gray(step.label)
|
|
190
|
+
: step.status === 'failed'
|
|
191
|
+
? chalk.red(step.label)
|
|
192
|
+
: chalk.gray(step.label);
|
|
193
|
+
|
|
194
|
+
let line = `${prefix} ${label}`;
|
|
195
|
+
|
|
196
|
+
// Add progress bar if applicable
|
|
197
|
+
if (step.hasProgress && step.status === 'active' && step.progress.total > 0) {
|
|
198
|
+
const percentage = Math.floor((step.progress.current / step.progress.total) * 100);
|
|
199
|
+
const progressBar = this.renderProgressBar(percentage);
|
|
200
|
+
line += ` ${progressBar} ${percentage}%`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Add message or duration
|
|
204
|
+
if (step.message && step.status === 'active') {
|
|
205
|
+
line += chalk.gray(` - ${step.message}`);
|
|
206
|
+
} else if (step.status === 'completed' && step.endTime && step.startTime) {
|
|
207
|
+
const duration = this.formatDuration(step.endTime - step.startTime);
|
|
208
|
+
line += chalk.gray(` (${duration})`);
|
|
209
|
+
if (step.message) {
|
|
210
|
+
line += chalk.gray(` - ${step.message}`);
|
|
211
|
+
}
|
|
212
|
+
} else if (step.status === 'failed' && step.error) {
|
|
213
|
+
line += chalk.red(` - ${step.error}`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
lines.push(line);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
lines.push(''); // Empty line at the end
|
|
220
|
+
|
|
221
|
+
this.lastRenderLength = lines.length;
|
|
222
|
+
process.stdout.write(lines.join('\n'));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Get the prefix icon for a step
|
|
227
|
+
*/
|
|
228
|
+
getStepPrefix(step) {
|
|
229
|
+
switch (step.status) {
|
|
230
|
+
case 'pending':
|
|
231
|
+
return chalk.gray('◯');
|
|
232
|
+
case 'active':
|
|
233
|
+
return chalk.cyan(this.spinnerChars[this.spinnerIndex]);
|
|
234
|
+
case 'completed':
|
|
235
|
+
return chalk.green('✓');
|
|
236
|
+
case 'failed':
|
|
237
|
+
return chalk.red('✗');
|
|
238
|
+
default:
|
|
239
|
+
return chalk.gray('◯');
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Render a progress bar
|
|
245
|
+
* @param {number} percentage - Progress percentage (0-100)
|
|
246
|
+
*/
|
|
247
|
+
renderProgressBar(percentage) {
|
|
248
|
+
const width = 20;
|
|
249
|
+
// Clamp percentage between 0 and 100 to prevent negative values
|
|
250
|
+
const clampedPercentage = Math.max(0, Math.min(100, percentage));
|
|
251
|
+
const filled = Math.floor((clampedPercentage / 100) * width);
|
|
252
|
+
const empty = width - filled;
|
|
253
|
+
|
|
254
|
+
const bar = chalk.cyan('█'.repeat(filled)) + chalk.gray('░'.repeat(empty));
|
|
255
|
+
return `[${bar}]`;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Format duration in ms to human readable format
|
|
260
|
+
*/
|
|
261
|
+
formatDuration(ms) {
|
|
262
|
+
if (ms < 1000) return `${ms}ms`;
|
|
263
|
+
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
264
|
+
const minutes = Math.floor(ms / 60000);
|
|
265
|
+
const seconds = Math.floor((ms % 60000) / 1000);
|
|
266
|
+
return `${minutes}m ${seconds}s`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Start the spinner animation
|
|
271
|
+
*/
|
|
272
|
+
startSpinner() {
|
|
273
|
+
this.interval = setInterval(() => {
|
|
274
|
+
this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerChars.length;
|
|
275
|
+
this.render();
|
|
276
|
+
}, 80);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Stop the spinner animation
|
|
281
|
+
*/
|
|
282
|
+
stopSpinner() {
|
|
283
|
+
if (this.interval) {
|
|
284
|
+
clearInterval(this.interval);
|
|
285
|
+
this.interval = null;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Finish the progress tracker
|
|
291
|
+
* @param {string} message - Optional final message
|
|
292
|
+
*/
|
|
293
|
+
finish(message = '') {
|
|
294
|
+
this.stopSpinner();
|
|
295
|
+
this.isFinished = true;
|
|
296
|
+
|
|
297
|
+
// Complete any active steps
|
|
298
|
+
if (this.currentStep && this.steps.get(this.currentStep).status === 'active') {
|
|
299
|
+
this.completeStep(this.currentStep);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
this.render();
|
|
303
|
+
|
|
304
|
+
// Print summary
|
|
305
|
+
const totalTime = this.formatDuration(Date.now() - this.startTime);
|
|
306
|
+
const completed = Array.from(this.steps.values()).filter(s => s.status === 'completed').length;
|
|
307
|
+
const failed = Array.from(this.steps.values()).filter(s => s.status === 'failed').length;
|
|
308
|
+
|
|
309
|
+
if (message) {
|
|
310
|
+
console.log(chalk.bold.green(`\n${message}`));
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
console.log(chalk.gray(`Total time: ${totalTime}`));
|
|
314
|
+
|
|
315
|
+
if (failed > 0) {
|
|
316
|
+
console.log(chalk.red(`✗ ${failed} step(s) failed`));
|
|
317
|
+
} else {
|
|
318
|
+
console.log(chalk.green(`✓ All ${completed} step(s) completed successfully`));
|
|
319
|
+
}
|
|
320
|
+
console.log('');
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Abort the progress tracker with an error
|
|
325
|
+
* @param {string} error - Error message
|
|
326
|
+
*/
|
|
327
|
+
abort(error) {
|
|
328
|
+
this.stopSpinner();
|
|
329
|
+
this.isFinished = true;
|
|
330
|
+
|
|
331
|
+
if (this.currentStep) {
|
|
332
|
+
this.failStep(this.currentStep, error);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
this.render();
|
|
336
|
+
console.log(chalk.bold.red(`\n✗ ${error}\n`));
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Helper function to create and use a simple progress tracker
|
|
342
|
+
* @param {string} title - Title of the operation
|
|
343
|
+
* @param {Array} steps - Array of step definitions
|
|
344
|
+
* @param {Function} fn - Async function that receives the progress tracker
|
|
345
|
+
* @returns {Promise} Result of the function
|
|
346
|
+
*
|
|
347
|
+
* @example
|
|
348
|
+
* await withProgress('Pull Operation', [
|
|
349
|
+
* { id: 'auth', label: 'Authenticating...' },
|
|
350
|
+
* { id: 'download', label: 'Downloading files...', hasProgress: true },
|
|
351
|
+
* { id: 'process', label: 'Processing files...' }
|
|
352
|
+
* ], async (progress) => {
|
|
353
|
+
* progress.startStep('auth');
|
|
354
|
+
* await authenticate();
|
|
355
|
+
* progress.completeStep('auth');
|
|
356
|
+
*
|
|
357
|
+
* progress.startStep('download');
|
|
358
|
+
* for (let i = 0; i <= 100; i += 10) {
|
|
359
|
+
* progress.updateProgress('download', i, 100);
|
|
360
|
+
* await downloadBatch(i);
|
|
361
|
+
* }
|
|
362
|
+
* progress.completeStep('download');
|
|
363
|
+
* });
|
|
364
|
+
*/
|
|
365
|
+
export async function withProgress(title, steps, fn) {
|
|
366
|
+
const progress = new ProgressTracker(title);
|
|
367
|
+
|
|
368
|
+
// Add all steps
|
|
369
|
+
for (const step of steps) {
|
|
370
|
+
progress.addStep(step.id, step.label, step);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
progress.start();
|
|
374
|
+
|
|
375
|
+
try {
|
|
376
|
+
const result = await fn(progress);
|
|
377
|
+
progress.finish();
|
|
378
|
+
return result;
|
|
379
|
+
} catch (error) {
|
|
380
|
+
progress.abort(error.message);
|
|
381
|
+
throw error;
|
|
382
|
+
}
|
|
383
|
+
}
|
package/utils/updateFileBase.js
CHANGED
|
@@ -53,7 +53,8 @@ export const updateBase = (filePath, record, actualPath = '', contentSnapshot =
|
|
|
53
53
|
const fileSystemLocation = actualPath || path.resolve(filePath);
|
|
54
54
|
|
|
55
55
|
if (!fs.existsSync(fileSystemLocation)) {
|
|
56
|
-
|
|
56
|
+
// Silently skip files that don't exist - they may have failed to download
|
|
57
|
+
// This is expected behavior for files that returned 404 during download
|
|
57
58
|
return;
|
|
58
59
|
}
|
|
59
60
|
|
|
@@ -106,3 +107,7 @@ export const updateBase = (filePath, record, actualPath = '', contentSnapshot =
|
|
|
106
107
|
export const removeFromBase = (recordId) => {
|
|
107
108
|
config.removeKey(recordId, { filename: "base.json" });
|
|
108
109
|
}
|
|
110
|
+
|
|
111
|
+
export const removeFromBaseBulk = (recordIds) => {
|
|
112
|
+
config.removeKeys(recordIds, { filename: "base.json" });
|
|
113
|
+
}
|