@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,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
|
+
}
|