@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,282 +1,282 @@
1
- import path from 'path';
2
- import chalk from 'chalk'; // For pretty colors, install with `npm install chalk`
3
- import { compareLocalAndRemote } from '../../compare.js';
4
- import inquirer from 'inquirer';
5
- import { mapRecordToFile } from '../writeRecords.js';
6
- import { withSpinner } from '../../spinner.js';
7
- import { meqlQuery } from '../../magentrix/api/meqlQuery.js';
8
- import readline from 'readline';
9
-
10
- /**
11
- * Logs a formatted, user-friendly status message for a single file, given its sync comparison result.
12
- *
13
- * @param {string} relativePath - File path relative to root directory.
14
- * @param {Object} statusResult - Result object from compareLocalAndRemote.
15
- * @returns {boolean} True if an issue was found (not 'in_sync' or 'missing'), otherwise false.
16
- */
17
- export function logFileStatus(relativePath, statusResult) {
18
- const fileDisplay = chalk.bold.blue(relativePath);
19
-
20
- switch (statusResult.status) {
21
- case 'in_sync':
22
- // Uncomment to show successes:
23
- // console.log(chalk.green(`āœ“ ${fileDisplay} is up to date.`));
24
- return false;
25
- case 'behind':
26
- console.log(
27
- chalk.yellow(`āš ļø ${fileDisplay} is OUTDATED:`) +
28
- `\n Local version is older than the server version, but contents are identical.\n` +
29
- ` Consider pulling the latest metadata from the server.`
30
- );
31
- return true;
32
- case 'conflict':
33
- console.log(
34
- chalk.red(`šŸ›‘ ${fileDisplay} has a CONFLICT:`) +
35
- `\n Local file is behind the remote, AND both were edited differently.\n` +
36
- ` Resolve this conflict before pushing or pulling.`
37
- );
38
- return true;
39
- case 'ahead':
40
- console.log(
41
- chalk.yellow(`āš ļø ${fileDisplay} is AHEAD:`) +
42
- `\n Local file was modified after the remote version, and contents differ.\n` +
43
- ` You may want to push your changes to the server.`
44
- );
45
- return true;
46
- case 'ahead_identical':
47
- console.log(
48
- chalk.yellow(`āš ļø ${fileDisplay} is AHEAD (identical):`) +
49
- `\n Local file has a newer timestamp, but contents match remote.\n` +
50
- ` This could be a system clock drift.`
51
- );
52
- return true;
53
- case 'content_differs':
54
- console.log(
55
- chalk.red(`šŸ›‘ ${fileDisplay} CONTENT MISMATCH:`) +
56
- `\n File timestamps match, but contents differ!\n` +
57
- ` This is unusual—please investigate.`
58
- );
59
- return true;
60
- case 'missing':
61
- // Not an error, file will be downloaded.
62
- // Uncomment if you want to notify about downloads:
63
- console.log(
64
- chalk.yellow(`āš ļø ${fileDisplay} is MISSING:`) +
65
- `\n Local file has been removed.`
66
- );
67
-
68
- // console.log(chalk.cyan(`ā¬‡ļø ${fileDisplay} will be downloaded from the server.`));
69
- return true;
70
- default:
71
- console.log(
72
- chalk.magenta(`ā“ ${fileDisplay} has an unknown status: ${statusResult.status}`)
73
- );
74
- return true;
75
- }
76
- }
77
-
78
- /**
79
- * Compares all local files in a specified root directory against their remote versions,
80
- * then logs user-friendly, formatted status messages for any files that are not in sync.
81
- *
82
- * - Uses logFileStatus to handle message formatting for each file.
83
- * - Only logs files that require attention; in-sync files are silent by default.
84
- * - If no issues are found, logs a celebratory success message.
85
- * - Intended for CLI status or sync-check commands.
86
- *
87
- * @param {string} rootDir
88
- * The local root directory where your files are stored (e.g., './export').
89
- * @param {Array<Object>} fileRecords
90
- * Array of remote file descriptors to compare. Each object should include:
91
- * - {string} relativePath: File path relative to rootDir (e.g. 'Controllers/Foo.ctrl')
92
- * - {string} content: Latest file content from the server
93
- * - {string} ModifiedOn: Last modified date/time on server (ISO string)
94
- *
95
- * @returns {void}
96
- *
97
- * @example
98
- * const filesFromServer = [
99
- * {
100
- * relativePath: "Controllers/AccountController.ctrl",
101
- * content: "...",
102
- * ModifiedOn: "2025-07-10T18:00:00.000Z"
103
- * },
104
- * // ...
105
- * ];
106
- * compareAllFilesAndLogStatus('./export', filesFromServer);
107
- */
108
- export function compareAllFilesAndLogStatus(rootDir, fileRecords) {
109
- let numIssues = 0;
110
-
111
- console.log();
112
-
113
- for (const record of fileRecords) {
114
- const localFilePath = path.join(rootDir, record.relativePath);
115
-
116
- const result = compareLocalAndRemote(localFilePath, {
117
- content: record.Content,
118
- ...record
119
- });
120
-
121
- // Use the single-file logger and aggregate if any issues found
122
- const hadIssue = logFileStatus(record.relativePath, result);
123
- if (hadIssue) numIssues++;
124
- }
125
-
126
- if (numIssues === 0) {
127
- console.log(chalk.green.bold('\nšŸŽ‰ All files are up to date and in sync!\n'));
128
- }
129
- }
130
-
131
- /**
132
- * Checks local vs remote Magentrix file status and warns on conflicts/out-of-sync.
133
- * If issues are found, blocks execution unless `forceContinue` is true.
134
- * If not forced, user can press any key to continue, or ESC to abort.
135
- *
136
- * @param {string} rootDir
137
- * @param {string} instanceUrl
138
- * @param {string} token
139
- * @param {boolean} [forceContinue=false]
140
- */
141
- export async function showCurrentConflicts(rootDir, instanceUrl, token, forceContinue = false) {
142
- const queries = [
143
- {
144
- name: "ActiveClass",
145
- query: "SELECT Id,Body,Name,CreatedOn,Description,ModifiedOn,Type FROM ActiveClass",
146
- contentField: "Body",
147
- },
148
- {
149
- name: "ActivePage",
150
- query: "SELECT Id,Content,Name,CreatedOn,Description,ModifiedOn,Type FROM ActivePage",
151
- contentField: "Content",
152
- },
153
- ];
154
-
155
- console.log(chalk.cyan.bold('šŸ” Checking local files vs remote Magentrix...'));
156
- console.log(chalk.gray('------------------------------------------------'));
157
-
158
- const [activeClassResult, activePageResult] = await withSpinner(
159
- chalk.gray('Retrieving files from server...'),
160
- async () => Promise.all(
161
- queries.map(q => meqlQuery(instanceUrl, token, q.query))
162
- )
163
- );
164
-
165
- const activeClassRecords = (activeClassResult.Records || []).map(record => {
166
- record.Content = record.Body;
167
- delete record.Body;
168
- return record;
169
- });
170
- const activePageRecords = (activePageResult.Records || []);
171
- const allRecords = [...activeClassRecords, ...activePageRecords].map(mapRecordToFile);
172
-
173
- let warningCount = 0;
174
- console.log();
175
-
176
- for (const record of allRecords) {
177
- if (record?.error) {
178
- continue;
179
- }
180
-
181
- const status = compareLocalAndRemote(
182
- path.join(rootDir, record.relativePath),
183
- { ...record, content: record.Content }
184
- );
185
-
186
- const hasIssue = logFileStatus(record.relativePath, status);
187
- if (hasIssue) warningCount++;
188
- }
189
-
190
- if (warningCount > 0) {
191
- console.log();
192
- console.log(
193
- chalk.yellow.bold(`āš ļø Conflict${warningCount > 1 ? 's' : ''} detected!`) +
194
- chalk.yellow('\n These are just warnings, but you should resolve them before continuing.')
195
- );
196
- console.log(
197
- chalk.yellow('\nšŸ‘‰ To sync and resolve conflicts, run: ') +
198
- chalk.cyan.bold('magentrix pull')
199
- );
200
- console.log(chalk.gray('------------------------------------------------'));
201
-
202
- if (!forceContinue) {
203
- await new Promise((resolve) => {
204
- process.stdout.write(
205
- chalk.yellow(
206
- '\nPress any key to continue, or press ESC to abort... '
207
- )
208
- );
209
-
210
- // Set raw mode so we capture a single key, including ESC
211
- process.stdin.setRawMode(true);
212
- process.stdin.resume();
213
- process.stdin.once('data', (data) => {
214
- process.stdin.setRawMode(false);
215
- process.stdin.pause();
216
- // ESC key is 27 in Buffer
217
- if (data.length === 1 && data[0] === 27) {
218
- console.log(chalk.red.bold('\nAborted due to unresolved conflicts.\n'));
219
- process.exit(1);
220
- } else {
221
- // Continue
222
- process.stdout.write('\n');
223
- resolve();
224
- }
225
- });
226
- });
227
- }
228
- } else {
229
- console.log();
230
- console.log(chalk.green.bold('šŸŽ‰ All local files are in sync with the server!'));
231
- console.log(chalk.gray('------------------------------------------------'));
232
- }
233
- }
234
-
235
-
236
-
237
- /**
238
- * Presents all files with conflicts and prompts the user for a global resolution action.
239
- *
240
- * @param {Array<{relativePath: string, status: string}>} fileIssues
241
- * @returns {Promise<'skip'|'overwrite'|'diff'|'accept_ours'|'accept_theirs'|'manual'>}
242
- */
243
- export async function promptConflictResolution(fileIssues) {
244
- if (!fileIssues.length) return 'skip';
245
-
246
- // Add spacing instead of clearing (clearing causes flickering with progress tracker)
247
- console.log('\n');
248
- console.log(chalk.gray('─'.repeat(48)));
249
- console.log(
250
- chalk.bold.yellow(
251
- `${fileIssues.length} file${fileIssues.length > 1 ? 's' : ''} require conflict resolution:\n`
252
- )
253
- );
254
-
255
- fileIssues.forEach((file, i) => {
256
- console.log(
257
- chalk.cyan(`${i + 1}.`) +
258
- ' ' +
259
- chalk.bold(file.relativePath) +
260
- chalk.gray(` [${file.status}]`)
261
- );
262
- });
263
-
264
- console.log();
265
-
266
- // Present actions menu
267
- const { action } = await inquirer.prompt([
268
- {
269
- type: 'list',
270
- name: 'action',
271
- message: chalk.green('Choose how to resolve these conflicts:'),
272
- choices: [
273
- { name: 'Replace local files with server versions (discard my changes)', value: 'overwrite' },
274
- { name: 'Automatically merge changes (may require manual edits if conflicts remain)', value: 'merge' },
275
- { name: 'Review each conflict and choose (see diffs)', value: 'diff' },
276
- { name: 'Do nothing for now (skip conflicted files)', value: 'skip' }
277
- ]
278
- },
279
- ]);
280
-
281
- return action;
282
- }
1
+ import path from 'path';
2
+ import chalk from 'chalk'; // For pretty colors, install with `npm install chalk`
3
+ import { compareLocalAndRemote } from '../../compare.js';
4
+ import inquirer from 'inquirer';
5
+ import { mapRecordToFile } from '../writeRecords.js';
6
+ import { withSpinner } from '../../spinner.js';
7
+ import { meqlQuery } from '../../magentrix/api/meqlQuery.js';
8
+ import readline from 'readline';
9
+
10
+ /**
11
+ * Logs a formatted, user-friendly status message for a single file, given its sync comparison result.
12
+ *
13
+ * @param {string} relativePath - File path relative to root directory.
14
+ * @param {Object} statusResult - Result object from compareLocalAndRemote.
15
+ * @returns {boolean} True if an issue was found (not 'in_sync' or 'missing'), otherwise false.
16
+ */
17
+ export function logFileStatus(relativePath, statusResult) {
18
+ const fileDisplay = chalk.bold.blue(relativePath);
19
+
20
+ switch (statusResult.status) {
21
+ case 'in_sync':
22
+ // Uncomment to show successes:
23
+ // console.log(chalk.green(`āœ“ ${fileDisplay} is up to date.`));
24
+ return false;
25
+ case 'behind':
26
+ console.log(
27
+ chalk.yellow(`āš ļø ${fileDisplay} is OUTDATED:`) +
28
+ `\n Local version is older than the server version, but contents are identical.\n` +
29
+ ` Consider pulling the latest metadata from the server.`
30
+ );
31
+ return true;
32
+ case 'conflict':
33
+ console.log(
34
+ chalk.red(`šŸ›‘ ${fileDisplay} has a CONFLICT:`) +
35
+ `\n Local file is behind the remote, AND both were edited differently.\n` +
36
+ ` Resolve this conflict before pushing or pulling.`
37
+ );
38
+ return true;
39
+ case 'ahead':
40
+ console.log(
41
+ chalk.yellow(`āš ļø ${fileDisplay} is AHEAD:`) +
42
+ `\n Local file was modified after the remote version, and contents differ.\n` +
43
+ ` You may want to push your changes to the server.`
44
+ );
45
+ return true;
46
+ case 'ahead_identical':
47
+ console.log(
48
+ chalk.yellow(`āš ļø ${fileDisplay} is AHEAD (identical):`) +
49
+ `\n Local file has a newer timestamp, but contents match remote.\n` +
50
+ ` This could be a system clock drift.`
51
+ );
52
+ return true;
53
+ case 'content_differs':
54
+ console.log(
55
+ chalk.red(`šŸ›‘ ${fileDisplay} CONTENT MISMATCH:`) +
56
+ `\n File timestamps match, but contents differ!\n` +
57
+ ` This is unusual—please investigate.`
58
+ );
59
+ return true;
60
+ case 'missing':
61
+ // Not an error, file will be downloaded.
62
+ // Uncomment if you want to notify about downloads:
63
+ console.log(
64
+ chalk.yellow(`āš ļø ${fileDisplay} is MISSING:`) +
65
+ `\n Local file has been removed.`
66
+ );
67
+
68
+ // console.log(chalk.cyan(`ā¬‡ļø ${fileDisplay} will be downloaded from the server.`));
69
+ return true;
70
+ default:
71
+ console.log(
72
+ chalk.magenta(`ā“ ${fileDisplay} has an unknown status: ${statusResult.status}`)
73
+ );
74
+ return true;
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Compares all local files in a specified root directory against their remote versions,
80
+ * then logs user-friendly, formatted status messages for any files that are not in sync.
81
+ *
82
+ * - Uses logFileStatus to handle message formatting for each file.
83
+ * - Only logs files that require attention; in-sync files are silent by default.
84
+ * - If no issues are found, logs a celebratory success message.
85
+ * - Intended for CLI status or sync-check commands.
86
+ *
87
+ * @param {string} rootDir
88
+ * The local root directory where your files are stored (e.g., './export').
89
+ * @param {Array<Object>} fileRecords
90
+ * Array of remote file descriptors to compare. Each object should include:
91
+ * - {string} relativePath: File path relative to rootDir (e.g. 'Controllers/Foo.ctrl')
92
+ * - {string} content: Latest file content from the server
93
+ * - {string} ModifiedOn: Last modified date/time on server (ISO string)
94
+ *
95
+ * @returns {void}
96
+ *
97
+ * @example
98
+ * const filesFromServer = [
99
+ * {
100
+ * relativePath: "Controllers/AccountController.ctrl",
101
+ * content: "...",
102
+ * ModifiedOn: "2025-07-10T18:00:00.000Z"
103
+ * },
104
+ * // ...
105
+ * ];
106
+ * compareAllFilesAndLogStatus('./export', filesFromServer);
107
+ */
108
+ export function compareAllFilesAndLogStatus(rootDir, fileRecords) {
109
+ let numIssues = 0;
110
+
111
+ console.log();
112
+
113
+ for (const record of fileRecords) {
114
+ const localFilePath = path.join(rootDir, record.relativePath);
115
+
116
+ const result = compareLocalAndRemote(localFilePath, {
117
+ content: record.Content,
118
+ ...record
119
+ });
120
+
121
+ // Use the single-file logger and aggregate if any issues found
122
+ const hadIssue = logFileStatus(record.relativePath, result);
123
+ if (hadIssue) numIssues++;
124
+ }
125
+
126
+ if (numIssues === 0) {
127
+ console.log(chalk.green.bold('\nšŸŽ‰ All files are up to date and in sync!\n'));
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Checks local vs remote Magentrix file status and warns on conflicts/out-of-sync.
133
+ * If issues are found, blocks execution unless `forceContinue` is true.
134
+ * If not forced, user can press any key to continue, or ESC to abort.
135
+ *
136
+ * @param {string} rootDir
137
+ * @param {string} instanceUrl
138
+ * @param {string} token
139
+ * @param {boolean} [forceContinue=false]
140
+ */
141
+ export async function showCurrentConflicts(rootDir, instanceUrl, token, forceContinue = false) {
142
+ const queries = [
143
+ {
144
+ name: "ActiveClass",
145
+ query: "SELECT Id,Body,Name,CreatedOn,Description,ModifiedOn,Type FROM ActiveClass",
146
+ contentField: "Body",
147
+ },
148
+ {
149
+ name: "ActivePage",
150
+ query: "SELECT Id,Content,Name,CreatedOn,Description,ModifiedOn,Type FROM ActivePage",
151
+ contentField: "Content",
152
+ },
153
+ ];
154
+
155
+ console.log(chalk.cyan.bold('šŸ” Checking local files vs remote Magentrix...'));
156
+ console.log(chalk.gray('------------------------------------------------'));
157
+
158
+ const [activeClassResult, activePageResult] = await withSpinner(
159
+ chalk.gray('Retrieving files from server...'),
160
+ async () => Promise.all(
161
+ queries.map(q => meqlQuery(instanceUrl, token, q.query))
162
+ )
163
+ );
164
+
165
+ const activeClassRecords = (activeClassResult.Records || []).map(record => {
166
+ record.Content = record.Body;
167
+ delete record.Body;
168
+ return record;
169
+ });
170
+ const activePageRecords = (activePageResult.Records || []);
171
+ const allRecords = [...activeClassRecords, ...activePageRecords].map(mapRecordToFile);
172
+
173
+ let warningCount = 0;
174
+ console.log();
175
+
176
+ for (const record of allRecords) {
177
+ if (record?.error) {
178
+ continue;
179
+ }
180
+
181
+ const status = compareLocalAndRemote(
182
+ path.join(rootDir, record.relativePath),
183
+ { ...record, content: record.Content }
184
+ );
185
+
186
+ const hasIssue = logFileStatus(record.relativePath, status);
187
+ if (hasIssue) warningCount++;
188
+ }
189
+
190
+ if (warningCount > 0) {
191
+ console.log();
192
+ console.log(
193
+ chalk.yellow.bold(`āš ļø Conflict${warningCount > 1 ? 's' : ''} detected!`) +
194
+ chalk.yellow('\n These are just warnings, but you should resolve them before continuing.')
195
+ );
196
+ console.log(
197
+ chalk.yellow('\nšŸ‘‰ To sync and resolve conflicts, run: ') +
198
+ chalk.cyan.bold('magentrix pull')
199
+ );
200
+ console.log(chalk.gray('------------------------------------------------'));
201
+
202
+ if (!forceContinue) {
203
+ await new Promise((resolve) => {
204
+ process.stdout.write(
205
+ chalk.yellow(
206
+ '\nPress any key to continue, or press ESC to abort... '
207
+ )
208
+ );
209
+
210
+ // Set raw mode so we capture a single key, including ESC
211
+ process.stdin.setRawMode(true);
212
+ process.stdin.resume();
213
+ process.stdin.once('data', (data) => {
214
+ process.stdin.setRawMode(false);
215
+ process.stdin.pause();
216
+ // ESC key is 27 in Buffer
217
+ if (data.length === 1 && data[0] === 27) {
218
+ console.log(chalk.red.bold('\nAborted due to unresolved conflicts.\n'));
219
+ process.exit(1);
220
+ } else {
221
+ // Continue
222
+ process.stdout.write('\n');
223
+ resolve();
224
+ }
225
+ });
226
+ });
227
+ }
228
+ } else {
229
+ console.log();
230
+ console.log(chalk.green.bold('šŸŽ‰ All local files are in sync with the server!'));
231
+ console.log(chalk.gray('------------------------------------------------'));
232
+ }
233
+ }
234
+
235
+
236
+
237
+ /**
238
+ * Presents all files with conflicts and prompts the user for a global resolution action.
239
+ *
240
+ * @param {Array<{relativePath: string, status: string}>} fileIssues
241
+ * @returns {Promise<'skip'|'overwrite'|'diff'|'accept_ours'|'accept_theirs'|'manual'>}
242
+ */
243
+ export async function promptConflictResolution(fileIssues) {
244
+ if (!fileIssues.length) return 'skip';
245
+
246
+ // Add spacing instead of clearing (clearing causes flickering with progress tracker)
247
+ console.log('\n');
248
+ console.log(chalk.gray('─'.repeat(48)));
249
+ console.log(
250
+ chalk.bold.yellow(
251
+ `${fileIssues.length} file${fileIssues.length > 1 ? 's' : ''} require conflict resolution:\n`
252
+ )
253
+ );
254
+
255
+ fileIssues.forEach((file, i) => {
256
+ console.log(
257
+ chalk.cyan(`${i + 1}.`) +
258
+ ' ' +
259
+ chalk.bold(file.relativePath) +
260
+ chalk.gray(` [${file.status}]`)
261
+ );
262
+ });
263
+
264
+ console.log();
265
+
266
+ // Present actions menu
267
+ const { action } = await inquirer.prompt([
268
+ {
269
+ type: 'list',
270
+ name: 'action',
271
+ message: chalk.green('Choose how to resolve these conflicts:'),
272
+ choices: [
273
+ { name: 'Replace local files with server versions (discard my changes)', value: 'overwrite' },
274
+ { name: 'Automatically merge changes (may require manual edits if conflicts remain)', value: 'merge' },
275
+ { name: 'Review each conflict and choose (see diffs)', value: 'diff' },
276
+ { name: 'Do nothing for now (skip conflicted files)', value: 'skip' }
277
+ ]
278
+ },
279
+ ]);
280
+
281
+ return action;
282
+ }