@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,262 +1,262 @@
1
- import { EXPORT_ROOT, TYPE_DIR_MAP } from "../../vars/global.js";
2
- import fs from 'fs';
3
- import path from "path";
4
- import { compareLocalAndRemote } from "../compare.js";
5
- import { mergeFiles } from "../merge.js";
6
- import Config from "../config.js";
7
- import chalk from "chalk";
8
- import { diffLines } from 'diff';
9
- import readlineSync from "readline-sync";
10
- import { updateBase } from "../updateFileBase.js";
11
- import { sha256 } from "../hash.js";
12
- import { openDiffInVSCode } from "../diff.js";
13
- import { tmpdir } from "os";
14
- import { v4 as uuidv4 } from 'uuid';
15
- import { findFileByTag, getFileTag, setFileTag } from "../filetag.js";
16
- import { decompressString } from "../compress.js";
17
-
18
- const config = new Config();
19
-
20
- /**
21
- * Maps an ActivePage record to its local file representation.
22
- * @param {Object} record - The ActivePage record from the API.
23
- * @returns {Object} The enriched record with 'relativePath' property.
24
- */
25
- export const mapRecordToFile = (record) => {
26
- const { Type, Name, Id } = record;
27
- const mapping = TYPE_DIR_MAP[Type];
28
-
29
- if (!mapping) {
30
- const errMsg = `Unrecognized type "${Type}" for record "${Name || 'UNKNOWN'}". This can be resolved in the Magentrix IDE and is likely due to a misconfiguration.`;
31
- return { ...record, error: errMsg };
32
- }
33
-
34
- const safeName = Name.replace(/[<>:"/\\|?*]+/g, "_");
35
- const filename = `${safeName}.${mapping.extension}`;
36
-
37
- const foundPathByRecordId = findFileByTag(Id) || "";
38
- const relativeFoundPath = foundPathByRecordId
39
- ? path.join(mapping.directory, foundPathByRecordId.split(mapping.directory)[1] || "")
40
- : "";
41
-
42
- // Fix for Bug 1: If the file exists but the name doesn't match the record name,
43
- // we should treat it as a rename and use the new name.
44
- let useExistingPath = false;
45
- if (foundPathByRecordId) {
46
- const existingName = path.basename(foundPathByRecordId);
47
- // Check if existing name matches the expected filename (ignoring extension case if needed, but strict for now)
48
- if (existingName === filename) {
49
- useExistingPath = true;
50
- }
51
- }
52
-
53
- const filePath = useExistingPath ? relativeFoundPath : path.join(mapping.directory, filename);
54
-
55
- return {
56
- ...record,
57
- relativePath: filePath
58
- };
59
- };
60
-
61
- /**
62
- * Writes records to disk, handling conflicts per the chosen resolution method.
63
- * @param {Array} records - List of entity records (ActiveClass or ActivePage)
64
- * @param {string} resolutionMethod - One of: "overwrite", "merge", "diff", "skip"
65
- * @param {Object} progress - Optional progress tracker
66
- * @param {Object} logger - Optional logger for warnings/errors
67
- */
68
- export const writeRecords = async (records, resolutionMethod, progress = null, logger = null) => {
69
- for (let i = 0; i < records.length; i++) {
70
- const record = records[i];
71
- const mapping = TYPE_DIR_MAP[record.Type];
72
- if (!mapping) {
73
- const msg = `Skipping unknown type: ${record.Type} (record: ${record.Name})`;
74
- if (logger) {
75
- logger.warning(msg);
76
- } else {
77
- console.warn(chalk.gray(`⚠️ ${msg}`));
78
- }
79
- continue;
80
- }
81
-
82
- // Build output path and filename
83
- const outputDir = path.join(EXPORT_ROOT, mapping.directory);
84
- fs.mkdirSync(outputDir, { recursive: true }); // Ensure dir exists
85
-
86
- const safeName = record.Name.replace(/[<>:"/\\|?*]+/g, "_");
87
- const filename = `${safeName}.${mapping.extension}`;
88
- const filePath = path.join(outputDir, filename);
89
-
90
- const content = record.Content;
91
- if (!content) {
92
- const msg = `No content found for ${record.Name} (${record.Type}), skipping file.`;
93
- if (logger) {
94
- logger.warning(msg);
95
- } else {
96
- console.warn(`⚠️ ${msg}`);
97
- }
98
- continue;
99
- }
100
-
101
- // Compare local and remote to determine sync status/conflict
102
- const comparison = compareLocalAndRemote(filePath, { content, ...record });
103
-
104
- // --- Conflict Handling ---
105
- let finalContent = null; // Will hold the string to write if we should write the file
106
-
107
- if (['missing', 'in_sync'].includes(comparison.status)) {
108
- // No conflict, just write server version (create if missing)
109
- finalContent = content;
110
- } else {
111
- // Attempt to read local file (may not exist)
112
- let local = "";
113
-
114
- const base = config.read(record.Id, { global: false, filename: 'base.json' });
115
- const baseContent = decompressString(base?.compressedContent) || '';
116
-
117
- // const base = config.read(filePath);
118
- try { local = fs.readFileSync(filePath, "utf8"); } catch { }
119
-
120
- if (resolutionMethod === 'skip') {
121
- // Do nothing, never create or update file
122
- console.log(chalk.yellow(`⏭️ Skipped ${record.Name} (${record.Type}) due to conflict or mismatch.`));
123
- continue;
124
- }
125
- else if (resolutionMethod === 'overwrite') {
126
- // Always accept server version, creating file if missing
127
- console.log(chalk.redBright(`⚠️ Overwriting local file: ${filePath} with server version.`));
128
- finalContent = content;
129
- }
130
- else if (resolutionMethod === 'diff') {
131
- // Use mergeFiles, show diff, ask user
132
- let merged;
133
- try {
134
- merged = mergeFiles(baseContent, local, content);
135
- } catch (e) {
136
- console.log(chalk.red(`❌ Error merging: ${e.message}`));
137
- continue;
138
- }
139
-
140
- // Attempt to open the diff in vs code
141
- const randomStr = (length) => Math.random().toString(36).slice(2, 2 + length);
142
- const tempFileServer = path.join(tmpdir(), `${randomStr(4)}.Server${filename}`);
143
- const tempFileLocal = path.join(tmpdir(), `${randomStr(4)}.Local${filename}`);
144
- fs.writeFileSync(tempFileServer, content);
145
- fs.writeFileSync(tempFileLocal, local);
146
-
147
- const vsCodeDiffDisplayed = openDiffInVSCode(tempFileServer, tempFileLocal);
148
-
149
- // If the VS Code diff failed, show in terminal
150
- if (!vsCodeDiffDisplayed) {
151
- // Show diff (local vs merged)
152
- const diff = diffLines(local, merged);
153
- console.log(chalk.magenta.bold(`\n======= REVIEW MERGED for ${filename} =======`));
154
- diff.forEach(part => {
155
- const color = part.added ? 'green' :
156
- part.removed ? 'red' : 'gray';
157
- process.stdout.write(chalk[color](part.value));
158
- });
159
- console.log(chalk.magenta.bold("\n======= END REVIEW ======="));
160
-
161
- // Warn if there are still unresolved conflict markers
162
- if (merged.includes('<<<<<<<')) {
163
- console.log(chalk.red.bold("\n⚠️ Merge conflict markers present. Please resolve manually after accepting."));
164
- }
165
- }
166
-
167
- // Prompt user to accept or skip writing merged content
168
- const choice = readlineSync.question(
169
- chalk.yellow("\nAccept merged changes and write to file? (y/n): ")
170
- );
171
-
172
- // Delete the temp files
173
- fs.unlinkSync(tempFileLocal);
174
- fs.unlinkSync(tempFileServer);
175
-
176
- if (choice.trim().toLowerCase() === 'y') {
177
- finalContent = merged;
178
- } else {
179
- console.log(chalk.yellow("⏭️ Skipped due to user choice."));
180
- continue;
181
- }
182
- }
183
- else if (resolutionMethod === 'merge') {
184
- // No prompt: always write merged result (may contain conflict markers)
185
- let merged;
186
-
187
- try {
188
- merged = mergeFiles(baseContent, local, content);
189
- } catch (e) {
190
- console.log(chalk.red(`❌ Error merging: ${e.message}`));
191
- continue;
192
- }
193
- if (merged.includes('<<<<<<<')) {
194
- console.log(chalk.red.bold(`⚠️ Merge conflict markers written to ${filename}. Manual review required.`));
195
- } else {
196
- console.log(chalk.green(`✔️ Merged ${filename} automatically.`));
197
- }
198
- finalContent = merged;
199
- }
200
- else {
201
- // Unknown or unsupported mode (should not occur)
202
- console.log(chalk.red(`❌ Unknown resolution method: ${resolutionMethod}`));
203
- continue;
204
- }
205
- }
206
-
207
- // --- Actually Write File (if required by resolution) ---
208
- if (finalContent !== null) {
209
- try {
210
- // Lookup a matching file dir in our cache, in case the file was renamed
211
- const cachedFilePath = findFileByTag(record.Id) || path.resolve(filePath);
212
-
213
- // Ensure the directory exists (paranoia for deeply nested files)
214
- fs.mkdirSync(path.dirname(cachedFilePath), { recursive: true });
215
- fs.writeFileSync(cachedFilePath, finalContent, "utf8");
216
-
217
- // Add a file tag so we can keep track of file changes
218
- await setFileTag(cachedFilePath, record.Id);
219
-
220
- // Set mtime/atime for reproducible syncs (optional, best effort)
221
- let mtimeMs = null;
222
- if (record.ModifiedOn) {
223
- mtimeMs = new Date(record.ModifiedOn).getTime();
224
- } else if (record.CreatedOn) {
225
- mtimeMs = new Date(record.CreatedOn).getTime();
226
- }
227
- if (mtimeMs) {
228
- const mtimeSeconds = mtimeMs / 1000;
229
- try {
230
- fs.utimesSync(cachedFilePath, mtimeSeconds, mtimeSeconds);
231
- } catch (e) {
232
- const msg = `Could not set times for ${cachedFilePath}: ${e.message}`;
233
- if (logger) {
234
- logger.warning(msg);
235
- } else {
236
- console.warn(`⚠️ ${msg}`);
237
- }
238
- }
239
- }
240
-
241
-
242
- // If the server records content and the local files content match then we can update the base
243
- if (sha256(finalContent) === sha256(record.Content)) {
244
- // We still need to use the expected filePath as the rename might be local only
245
- updateBase(filePath, record, cachedFilePath);
246
- }
247
- } catch (err) {
248
- const msg = `Failed to write file ${filePath}: ${err.message}`;
249
- if (logger) {
250
- logger.error(msg, err);
251
- } else {
252
- console.error(chalk.red(`❌ ${msg}`));
253
- }
254
- }
255
- }
256
-
257
- // Update progress (outside try-catch so it always updates)
258
- if (progress && (i % 10 === 0 || i === records.length - 1)) {
259
- progress.updateProgress('write', i + 1, records.length, `Writing files...`);
260
- }
261
- }
262
- };
1
+ import { EXPORT_ROOT, TYPE_DIR_MAP } from "../../vars/global.js";
2
+ import fs from 'fs';
3
+ import path from "path";
4
+ import { compareLocalAndRemote } from "../compare.js";
5
+ import { mergeFiles } from "../merge.js";
6
+ import Config from "../config.js";
7
+ import chalk from "chalk";
8
+ import { diffLines } from 'diff';
9
+ import readlineSync from "readline-sync";
10
+ import { updateBase } from "../updateFileBase.js";
11
+ import { sha256 } from "../hash.js";
12
+ import { openDiffInVSCode } from "../diff.js";
13
+ import { tmpdir } from "os";
14
+ import { v4 as uuidv4 } from 'uuid';
15
+ import { findFileByTag, getFileTag, setFileTag } from "../filetag.js";
16
+ import { decompressString } from "../compress.js";
17
+
18
+ const config = new Config();
19
+
20
+ /**
21
+ * Maps an ActivePage record to its local file representation.
22
+ * @param {Object} record - The ActivePage record from the API.
23
+ * @returns {Object} The enriched record with 'relativePath' property.
24
+ */
25
+ export const mapRecordToFile = (record) => {
26
+ const { Type, Name, Id } = record;
27
+ const mapping = TYPE_DIR_MAP[Type];
28
+
29
+ if (!mapping) {
30
+ const errMsg = `Unrecognized type "${Type}" for record "${Name || 'UNKNOWN'}". This can be resolved in the Magentrix IDE and is likely due to a misconfiguration.`;
31
+ return { ...record, error: errMsg };
32
+ }
33
+
34
+ const safeName = Name.replace(/[<>:"/\\|?*]+/g, "_");
35
+ const filename = `${safeName}.${mapping.extension}`;
36
+
37
+ const foundPathByRecordId = findFileByTag(Id) || "";
38
+ const relativeFoundPath = foundPathByRecordId
39
+ ? path.join(mapping.directory, foundPathByRecordId.split(mapping.directory)[1] || "")
40
+ : "";
41
+
42
+ // Fix for Bug 1: If the file exists but the name doesn't match the record name,
43
+ // we should treat it as a rename and use the new name.
44
+ let useExistingPath = false;
45
+ if (foundPathByRecordId) {
46
+ const existingName = path.basename(foundPathByRecordId);
47
+ // Check if existing name matches the expected filename (ignoring extension case if needed, but strict for now)
48
+ if (existingName === filename) {
49
+ useExistingPath = true;
50
+ }
51
+ }
52
+
53
+ const filePath = useExistingPath ? relativeFoundPath : path.join(mapping.directory, filename);
54
+
55
+ return {
56
+ ...record,
57
+ relativePath: filePath
58
+ };
59
+ };
60
+
61
+ /**
62
+ * Writes records to disk, handling conflicts per the chosen resolution method.
63
+ * @param {Array} records - List of entity records (ActiveClass or ActivePage)
64
+ * @param {string} resolutionMethod - One of: "overwrite", "merge", "diff", "skip"
65
+ * @param {Object} progress - Optional progress tracker
66
+ * @param {Object} logger - Optional logger for warnings/errors
67
+ */
68
+ export const writeRecords = async (records, resolutionMethod, progress = null, logger = null) => {
69
+ for (let i = 0; i < records.length; i++) {
70
+ const record = records[i];
71
+ const mapping = TYPE_DIR_MAP[record.Type];
72
+ if (!mapping) {
73
+ const msg = `Skipping unknown type: ${record.Type} (record: ${record.Name})`;
74
+ if (logger) {
75
+ logger.warning(msg);
76
+ } else {
77
+ console.warn(chalk.gray(`⚠️ ${msg}`));
78
+ }
79
+ continue;
80
+ }
81
+
82
+ // Build output path and filename
83
+ const outputDir = path.join(EXPORT_ROOT, mapping.directory);
84
+ fs.mkdirSync(outputDir, { recursive: true }); // Ensure dir exists
85
+
86
+ const safeName = record.Name.replace(/[<>:"/\\|?*]+/g, "_");
87
+ const filename = `${safeName}.${mapping.extension}`;
88
+ const filePath = path.join(outputDir, filename);
89
+
90
+ const content = record.Content;
91
+ if (!content) {
92
+ const msg = `No content found for ${record.Name} (${record.Type}), skipping file.`;
93
+ if (logger) {
94
+ logger.warning(msg);
95
+ } else {
96
+ console.warn(`⚠️ ${msg}`);
97
+ }
98
+ continue;
99
+ }
100
+
101
+ // Compare local and remote to determine sync status/conflict
102
+ const comparison = compareLocalAndRemote(filePath, { content, ...record });
103
+
104
+ // --- Conflict Handling ---
105
+ let finalContent = null; // Will hold the string to write if we should write the file
106
+
107
+ if (['missing', 'in_sync'].includes(comparison.status)) {
108
+ // No conflict, just write server version (create if missing)
109
+ finalContent = content;
110
+ } else {
111
+ // Attempt to read local file (may not exist)
112
+ let local = "";
113
+
114
+ const base = config.read(record.Id, { global: false, filename: 'base.json' });
115
+ const baseContent = decompressString(base?.compressedContent) || '';
116
+
117
+ // const base = config.read(filePath);
118
+ try { local = fs.readFileSync(filePath, "utf8"); } catch { }
119
+
120
+ if (resolutionMethod === 'skip') {
121
+ // Do nothing, never create or update file
122
+ console.log(chalk.yellow(`⏭️ Skipped ${record.Name} (${record.Type}) due to conflict or mismatch.`));
123
+ continue;
124
+ }
125
+ else if (resolutionMethod === 'overwrite') {
126
+ // Always accept server version, creating file if missing
127
+ console.log(chalk.redBright(`⚠️ Overwriting local file: ${filePath} with server version.`));
128
+ finalContent = content;
129
+ }
130
+ else if (resolutionMethod === 'diff') {
131
+ // Use mergeFiles, show diff, ask user
132
+ let merged;
133
+ try {
134
+ merged = mergeFiles(baseContent, local, content);
135
+ } catch (e) {
136
+ console.log(chalk.red(`❌ Error merging: ${e.message}`));
137
+ continue;
138
+ }
139
+
140
+ // Attempt to open the diff in vs code
141
+ const randomStr = (length) => Math.random().toString(36).slice(2, 2 + length);
142
+ const tempFileServer = path.join(tmpdir(), `${randomStr(4)}.Server${filename}`);
143
+ const tempFileLocal = path.join(tmpdir(), `${randomStr(4)}.Local${filename}`);
144
+ fs.writeFileSync(tempFileServer, content);
145
+ fs.writeFileSync(tempFileLocal, local);
146
+
147
+ const vsCodeDiffDisplayed = openDiffInVSCode(tempFileServer, tempFileLocal);
148
+
149
+ // If the VS Code diff failed, show in terminal
150
+ if (!vsCodeDiffDisplayed) {
151
+ // Show diff (local vs merged)
152
+ const diff = diffLines(local, merged);
153
+ console.log(chalk.magenta.bold(`\n======= REVIEW MERGED for ${filename} =======`));
154
+ diff.forEach(part => {
155
+ const color = part.added ? 'green' :
156
+ part.removed ? 'red' : 'gray';
157
+ process.stdout.write(chalk[color](part.value));
158
+ });
159
+ console.log(chalk.magenta.bold("\n======= END REVIEW ======="));
160
+
161
+ // Warn if there are still unresolved conflict markers
162
+ if (merged.includes('<<<<<<<')) {
163
+ console.log(chalk.red.bold("\n⚠️ Merge conflict markers present. Please resolve manually after accepting."));
164
+ }
165
+ }
166
+
167
+ // Prompt user to accept or skip writing merged content
168
+ const choice = readlineSync.question(
169
+ chalk.yellow("\nAccept merged changes and write to file? (y/n): ")
170
+ );
171
+
172
+ // Delete the temp files
173
+ fs.unlinkSync(tempFileLocal);
174
+ fs.unlinkSync(tempFileServer);
175
+
176
+ if (choice.trim().toLowerCase() === 'y') {
177
+ finalContent = merged;
178
+ } else {
179
+ console.log(chalk.yellow("⏭️ Skipped due to user choice."));
180
+ continue;
181
+ }
182
+ }
183
+ else if (resolutionMethod === 'merge') {
184
+ // No prompt: always write merged result (may contain conflict markers)
185
+ let merged;
186
+
187
+ try {
188
+ merged = mergeFiles(baseContent, local, content);
189
+ } catch (e) {
190
+ console.log(chalk.red(`❌ Error merging: ${e.message}`));
191
+ continue;
192
+ }
193
+ if (merged.includes('<<<<<<<')) {
194
+ console.log(chalk.red.bold(`⚠️ Merge conflict markers written to ${filename}. Manual review required.`));
195
+ } else {
196
+ console.log(chalk.green(`✔️ Merged ${filename} automatically.`));
197
+ }
198
+ finalContent = merged;
199
+ }
200
+ else {
201
+ // Unknown or unsupported mode (should not occur)
202
+ console.log(chalk.red(`❌ Unknown resolution method: ${resolutionMethod}`));
203
+ continue;
204
+ }
205
+ }
206
+
207
+ // --- Actually Write File (if required by resolution) ---
208
+ if (finalContent !== null) {
209
+ try {
210
+ // Lookup a matching file dir in our cache, in case the file was renamed
211
+ const cachedFilePath = findFileByTag(record.Id) || path.resolve(filePath);
212
+
213
+ // Ensure the directory exists (paranoia for deeply nested files)
214
+ fs.mkdirSync(path.dirname(cachedFilePath), { recursive: true });
215
+ fs.writeFileSync(cachedFilePath, finalContent, "utf8");
216
+
217
+ // Add a file tag so we can keep track of file changes
218
+ await setFileTag(cachedFilePath, record.Id);
219
+
220
+ // Set mtime/atime for reproducible syncs (optional, best effort)
221
+ let mtimeMs = null;
222
+ if (record.ModifiedOn) {
223
+ mtimeMs = new Date(record.ModifiedOn).getTime();
224
+ } else if (record.CreatedOn) {
225
+ mtimeMs = new Date(record.CreatedOn).getTime();
226
+ }
227
+ if (mtimeMs) {
228
+ const mtimeSeconds = mtimeMs / 1000;
229
+ try {
230
+ fs.utimesSync(cachedFilePath, mtimeSeconds, mtimeSeconds);
231
+ } catch (e) {
232
+ const msg = `Could not set times for ${cachedFilePath}: ${e.message}`;
233
+ if (logger) {
234
+ logger.warning(msg);
235
+ } else {
236
+ console.warn(`⚠️ ${msg}`);
237
+ }
238
+ }
239
+ }
240
+
241
+
242
+ // If the server records content and the local files content match then we can update the base
243
+ if (sha256(finalContent) === sha256(record.Content)) {
244
+ // We still need to use the expected filePath as the rename might be local only
245
+ updateBase(filePath, record, cachedFilePath);
246
+ }
247
+ } catch (err) {
248
+ const msg = `Failed to write file ${filePath}: ${err.message}`;
249
+ if (logger) {
250
+ logger.error(msg, err);
251
+ } else {
252
+ console.error(chalk.red(`❌ ${msg}`));
253
+ }
254
+ }
255
+ }
256
+
257
+ // Update progress (outside try-catch so it always updates)
258
+ if (progress && (i % 10 === 0 || i === records.length - 1)) {
259
+ progress.updateProgress('write', i + 1, records.length, `Writing files...`);
260
+ }
261
+ }
262
+ };