@magentrix-corp/magentrix-cli 1.3.15 → 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 -45
  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
package/utils/debug.js CHANGED
@@ -1,144 +1,144 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import os from 'node:os';
4
- import { VERSION } from '../vars/config.js';
5
- import { CWD, HASHED_CWD } from '../vars/global.js';
6
- import Config from './config.js';
7
-
8
- /**
9
- * Masks a sensitive string, showing only the first 4 and last 4 characters.
10
- * @param {string} value
11
- * @returns {string}
12
- */
13
- function mask(value) {
14
- if (!value || typeof value !== 'string') return '(empty)';
15
- if (value.length <= 12) return '****';
16
- return `${value.slice(0, 4)}...${value.slice(-4)}`;
17
- }
18
-
19
- /**
20
- * Centralized debug logger singleton.
21
- * When enabled, writes timestamped lines to .magentrix/logs/debug-<timestamp>.log.
22
- * All methods are no-ops when debug.enabled === false.
23
- */
24
- const debug = {
25
- enabled: false,
26
- _logFile: null,
27
- _startTime: null,
28
-
29
- /**
30
- * Enable debug mode and open the log file.
31
- */
32
- enable() {
33
- this.enabled = true;
34
- this._startTime = Date.now();
35
-
36
- const logsDir = path.join(CWD, '.magentrix', 'logs');
37
- fs.mkdirSync(logsDir, { recursive: true, mode: 0o700 });
38
-
39
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
40
- this._logFile = path.join(logsDir, `debug-${timestamp}.log`);
41
-
42
- // Write header
43
- fs.writeFileSync(this._logFile, `# MagentrixCLI Debug Log\n# Started: ${new Date().toISOString()}\n\n`, { mode: 0o600 });
44
- console.log(`Debug log: ${path.relative(CWD, this._logFile)}`);
45
- },
46
-
47
- /**
48
- * Write a timestamped line to the debug log.
49
- * @param {string} label
50
- * @param {...any} args
51
- */
52
- log(label, ...args) {
53
- if (!this.enabled || !this._logFile) return;
54
- const elapsed = Date.now() - this._startTime;
55
- const ts = `[+${String(elapsed).padStart(6)}ms]`;
56
- const detail = args.length
57
- ? ' ' + args.map(a => (typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a))).join(' ')
58
- : '';
59
- fs.appendFileSync(this._logFile, `${ts} [${label}]${detail}\n`);
60
- },
61
-
62
- /**
63
- * Log an outgoing HTTP request (sanitizes auth tokens/API keys).
64
- */
65
- request(method, url, headers, body) {
66
- if (!this.enabled) return;
67
- const safeHeaders = { ...headers };
68
- if (safeHeaders.Authorization) {
69
- safeHeaders.Authorization = `Bearer ${mask(safeHeaders.Authorization.replace('Bearer ', ''))}`;
70
- }
71
- this.log('HTTP-REQ', `${method} ${url}`);
72
- this.log('HTTP-REQ', 'Headers:', JSON.stringify(safeHeaders, null, 2));
73
- if (body !== undefined && body !== null) {
74
- let bodyStr = typeof body === 'string' ? body : JSON.stringify(body, null, 2);
75
- // Mask sensitive fields in body
76
- bodyStr = bodyStr.replace(/"refresh_token"\s*:\s*"([^"]+)"/g, (_, val) => `"refresh_token": "${mask(val)}"`);
77
- bodyStr = bodyStr.replace(/"apiKey"\s*:\s*"([^"]+)"/g, (_, val) => `"apiKey": "${mask(val)}"`);
78
- this.log('HTTP-REQ', 'Body:', bodyStr);
79
- }
80
- },
81
-
82
- /**
83
- * Log an HTTP response including status, headers, and body.
84
- */
85
- response(status, statusText, headers, body) {
86
- if (!this.enabled) return;
87
- this.log('HTTP-RES', `${status} ${statusText}`);
88
- if (headers) {
89
- const headerObj = {};
90
- if (typeof headers.forEach === 'function') {
91
- headers.forEach((value, key) => { headerObj[key] = value; });
92
- } else if (typeof headers === 'object') {
93
- Object.assign(headerObj, headers);
94
- }
95
- this.log('HTTP-RES', 'Headers:', JSON.stringify(headerObj, null, 2));
96
- }
97
- if (body !== undefined && body !== null) {
98
- let bodyStr = typeof body === 'string' ? body : JSON.stringify(body, null, 2);
99
- // Mask tokens in response
100
- bodyStr = bodyStr.replace(/"token"\s*:\s*"([^"]+)"/g, (_, val) => `"token": "${mask(val)}"`);
101
- this.log('HTTP-RES', 'Body:', bodyStr);
102
- }
103
- },
104
-
105
- /**
106
- * Log environment information.
107
- */
108
- env() {
109
- if (!this.enabled) return;
110
- const config = new Config();
111
- const instanceUrl = config.read('instanceUrl', { global: true, pathHash: HASHED_CWD }) || '(not set)';
112
-
113
- this.log('ENV', 'Node version:', process.version);
114
- this.log('ENV', 'OS:', `${os.type()} ${os.release()} (${os.arch()})`);
115
- this.log('ENV', 'CLI version:', VERSION);
116
- this.log('ENV', 'CWD:', CWD);
117
- this.log('ENV', 'Hashed CWD:', HASHED_CWD);
118
- this.log('ENV', 'Instance URL:', instanceUrl);
119
- },
120
-
121
- /**
122
- * Auth-specific debug logging.
123
- */
124
- auth(message, details) {
125
- if (!this.enabled) return;
126
- if (details) {
127
- this.log('AUTH', message, details);
128
- } else {
129
- this.log('AUTH', message);
130
- }
131
- },
132
-
133
- /**
134
- * Finalize the debug log and print its path.
135
- */
136
- close() {
137
- if (!this.enabled || !this._logFile) return;
138
- const elapsed = Date.now() - this._startTime;
139
- this.log('END', `Debug session ended. Total time: ${elapsed}ms`);
140
- console.log(`\nDebug log saved: ${path.relative(CWD, this._logFile)}`);
141
- }
142
- };
143
-
144
- export default debug;
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import { VERSION } from '../vars/config.js';
5
+ import { CWD, HASHED_CWD } from '../vars/global.js';
6
+ import Config from './config.js';
7
+
8
+ /**
9
+ * Masks a sensitive string, showing only the first 4 and last 4 characters.
10
+ * @param {string} value
11
+ * @returns {string}
12
+ */
13
+ function mask(value) {
14
+ if (!value || typeof value !== 'string') return '(empty)';
15
+ if (value.length <= 12) return '****';
16
+ return `${value.slice(0, 4)}...${value.slice(-4)}`;
17
+ }
18
+
19
+ /**
20
+ * Centralized debug logger singleton.
21
+ * When enabled, writes timestamped lines to .magentrix/logs/debug-<timestamp>.log.
22
+ * All methods are no-ops when debug.enabled === false.
23
+ */
24
+ const debug = {
25
+ enabled: false,
26
+ _logFile: null,
27
+ _startTime: null,
28
+
29
+ /**
30
+ * Enable debug mode and open the log file.
31
+ */
32
+ enable() {
33
+ this.enabled = true;
34
+ this._startTime = Date.now();
35
+
36
+ const logsDir = path.join(CWD, '.magentrix', 'logs');
37
+ fs.mkdirSync(logsDir, { recursive: true, mode: 0o700 });
38
+
39
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
40
+ this._logFile = path.join(logsDir, `debug-${timestamp}.log`);
41
+
42
+ // Write header
43
+ fs.writeFileSync(this._logFile, `# MagentrixCLI Debug Log\n# Started: ${new Date().toISOString()}\n\n`, { mode: 0o600 });
44
+ console.log(`Debug log: ${path.relative(CWD, this._logFile)}`);
45
+ },
46
+
47
+ /**
48
+ * Write a timestamped line to the debug log.
49
+ * @param {string} label
50
+ * @param {...any} args
51
+ */
52
+ log(label, ...args) {
53
+ if (!this.enabled || !this._logFile) return;
54
+ const elapsed = Date.now() - this._startTime;
55
+ const ts = `[+${String(elapsed).padStart(6)}ms]`;
56
+ const detail = args.length
57
+ ? ' ' + args.map(a => (typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a))).join(' ')
58
+ : '';
59
+ fs.appendFileSync(this._logFile, `${ts} [${label}]${detail}\n`);
60
+ },
61
+
62
+ /**
63
+ * Log an outgoing HTTP request (sanitizes auth tokens/API keys).
64
+ */
65
+ request(method, url, headers, body) {
66
+ if (!this.enabled) return;
67
+ const safeHeaders = { ...headers };
68
+ if (safeHeaders.Authorization) {
69
+ safeHeaders.Authorization = `Bearer ${mask(safeHeaders.Authorization.replace('Bearer ', ''))}`;
70
+ }
71
+ this.log('HTTP-REQ', `${method} ${url}`);
72
+ this.log('HTTP-REQ', 'Headers:', JSON.stringify(safeHeaders, null, 2));
73
+ if (body !== undefined && body !== null) {
74
+ let bodyStr = typeof body === 'string' ? body : JSON.stringify(body, null, 2);
75
+ // Mask sensitive fields in body
76
+ bodyStr = bodyStr.replace(/"refresh_token"\s*:\s*"([^"]+)"/g, (_, val) => `"refresh_token": "${mask(val)}"`);
77
+ bodyStr = bodyStr.replace(/"apiKey"\s*:\s*"([^"]+)"/g, (_, val) => `"apiKey": "${mask(val)}"`);
78
+ this.log('HTTP-REQ', 'Body:', bodyStr);
79
+ }
80
+ },
81
+
82
+ /**
83
+ * Log an HTTP response including status, headers, and body.
84
+ */
85
+ response(status, statusText, headers, body) {
86
+ if (!this.enabled) return;
87
+ this.log('HTTP-RES', `${status} ${statusText}`);
88
+ if (headers) {
89
+ const headerObj = {};
90
+ if (typeof headers.forEach === 'function') {
91
+ headers.forEach((value, key) => { headerObj[key] = value; });
92
+ } else if (typeof headers === 'object') {
93
+ Object.assign(headerObj, headers);
94
+ }
95
+ this.log('HTTP-RES', 'Headers:', JSON.stringify(headerObj, null, 2));
96
+ }
97
+ if (body !== undefined && body !== null) {
98
+ let bodyStr = typeof body === 'string' ? body : JSON.stringify(body, null, 2);
99
+ // Mask tokens in response
100
+ bodyStr = bodyStr.replace(/"token"\s*:\s*"([^"]+)"/g, (_, val) => `"token": "${mask(val)}"`);
101
+ this.log('HTTP-RES', 'Body:', bodyStr);
102
+ }
103
+ },
104
+
105
+ /**
106
+ * Log environment information.
107
+ */
108
+ env() {
109
+ if (!this.enabled) return;
110
+ const config = new Config();
111
+ const instanceUrl = config.read('instanceUrl', { global: true, pathHash: HASHED_CWD }) || '(not set)';
112
+
113
+ this.log('ENV', 'Node version:', process.version);
114
+ this.log('ENV', 'OS:', `${os.type()} ${os.release()} (${os.arch()})`);
115
+ this.log('ENV', 'CLI version:', VERSION);
116
+ this.log('ENV', 'CWD:', CWD);
117
+ this.log('ENV', 'Hashed CWD:', HASHED_CWD);
118
+ this.log('ENV', 'Instance URL:', instanceUrl);
119
+ },
120
+
121
+ /**
122
+ * Auth-specific debug logging.
123
+ */
124
+ auth(message, details) {
125
+ if (!this.enabled) return;
126
+ if (details) {
127
+ this.log('AUTH', message, details);
128
+ } else {
129
+ this.log('AUTH', message);
130
+ }
131
+ },
132
+
133
+ /**
134
+ * Finalize the debug log and print its path.
135
+ */
136
+ close() {
137
+ if (!this.enabled || !this._logFile) return;
138
+ const elapsed = Date.now() - this._startTime;
139
+ this.log('END', `Debug session ended. Total time: ${elapsed}ms`);
140
+ console.log(`\nDebug log saved: ${path.relative(CWD, this._logFile)}`);
141
+ }
142
+ };
143
+
144
+ export default debug;
@@ -1,96 +1,96 @@
1
- import { walkFiles } from '../cacher.js';
2
- import Config from '../config.js';
3
- import path from 'path';
4
-
5
- const config = new Config();
6
- const EXPORT_ROOT = 'src';
7
-
8
- // Load cache
9
- const hits = await config.searchObject({}, { filename: "base.json", global: false });
10
- const cachedResults = hits?.[0]?.value || {};
11
-
12
- const cachedFiles = Object.values(cachedResults).map((c) => ({
13
- ...c,
14
- tag: c.recordId,
15
- filePath: c.filePath || c.lastKnownPath,
16
- }));
17
-
18
- console.log('Total cached files:', cachedFiles.length);
19
- console.log('Cached assets (type=File):', cachedFiles.filter(cf => cf.type === 'File').length);
20
- console.log('Cached folders (type=Folder):', cachedFiles.filter(cf => cf.type === 'Folder').length);
21
- console.log('');
22
-
23
- // Build Set like in publish
24
- const cachedAssetPaths = new Set();
25
- cachedFiles
26
- .filter(cf => cf.type === 'File' || cf.type === 'Folder')
27
- .forEach(cf => {
28
- if (cf.lastKnownActualPath) {
29
- cachedAssetPaths.add(path.normalize(path.resolve(cf.lastKnownActualPath)).toLowerCase());
30
- }
31
- if (cf.filePath) {
32
- cachedAssetPaths.add(path.normalize(path.resolve(cf.filePath)).toLowerCase());
33
- }
34
- if (cf.lastKnownPath) {
35
- cachedAssetPaths.add(path.normalize(path.resolve(cf.lastKnownPath)).toLowerCase());
36
- }
37
- });
38
-
39
- console.log('Cached asset paths in Set:', cachedAssetPaths.size);
40
- console.log('');
41
-
42
- // Get local assets
43
- const assetPaths = await walkFiles(path.join(EXPORT_ROOT, 'Assets'));
44
- console.log('Local asset files found:', assetPaths.length);
45
- console.log('');
46
-
47
- // Check first few
48
- let matched = 0;
49
- let notMatched = 0;
50
-
51
- for (let i = 0; i < Math.min(10, assetPaths.length); i++) {
52
- const assetPath = assetPaths[i];
53
- const normalizedAssetPath = path.normalize(path.resolve(assetPath)).toLowerCase();
54
- const inCache = cachedAssetPaths.has(normalizedAssetPath);
55
-
56
- if (inCache) {
57
- matched++;
58
- } else {
59
- notMatched++;
60
- console.log('NOT IN CACHE:', assetPath);
61
- console.log(' Normalized:', normalizedAssetPath);
62
- console.log('');
63
- }
64
- }
65
-
66
- console.log(`First 10 files: ${matched} matched, ${notMatched} not matched`);
67
- console.log('');
68
-
69
- // Check if the Set actually contains the sample path
70
- const samplePath = assetPaths[0];
71
- const sampleNormalized = path.normalize(path.resolve(samplePath)).toLowerCase();
72
- console.log('Sample path:', samplePath);
73
- console.log('Normalized:', sampleNormalized);
74
- console.log('In Set:', cachedAssetPaths.has(sampleNormalized));
75
- console.log('');
76
-
77
- // Check what's actually in the Set for this file
78
- const sampleInBase = Object.values(cachedResults).find(b =>
79
- b.filePath === 'Assets/Acronis/Banners/1.png' ||
80
- b.lastKnownActualPath === 'src/Assets/Acronis/Banners/1.png'
81
- );
82
-
83
- if (sampleInBase) {
84
- console.log('Found in base.json:');
85
- console.log(' type:', sampleInBase.type);
86
- console.log(' filePath:', sampleInBase.filePath);
87
- console.log(' lastKnownActualPath:', sampleInBase.lastKnownActualPath);
88
- console.log(' lastKnownPath:', sampleInBase.lastKnownPath);
89
-
90
- // Check what gets added to the Set
91
- if (sampleInBase.lastKnownActualPath) {
92
- const normalized = path.normalize(path.resolve(sampleInBase.lastKnownActualPath)).toLowerCase();
93
- console.log(' Normalized lastKnownActualPath:', normalized);
94
- console.log(' Match:', normalized === sampleNormalized);
95
- }
96
- }
1
+ import { walkFiles } from '../cacher.js';
2
+ import Config from '../config.js';
3
+ import path from 'path';
4
+
5
+ const config = new Config();
6
+ const EXPORT_ROOT = 'src';
7
+
8
+ // Load cache
9
+ const hits = await config.searchObject({}, { filename: "base.json", global: false });
10
+ const cachedResults = hits?.[0]?.value || {};
11
+
12
+ const cachedFiles = Object.values(cachedResults).map((c) => ({
13
+ ...c,
14
+ tag: c.recordId,
15
+ filePath: c.filePath || c.lastKnownPath,
16
+ }));
17
+
18
+ console.log('Total cached files:', cachedFiles.length);
19
+ console.log('Cached assets (type=File):', cachedFiles.filter(cf => cf.type === 'File').length);
20
+ console.log('Cached folders (type=Folder):', cachedFiles.filter(cf => cf.type === 'Folder').length);
21
+ console.log('');
22
+
23
+ // Build Set like in publish
24
+ const cachedAssetPaths = new Set();
25
+ cachedFiles
26
+ .filter(cf => cf.type === 'File' || cf.type === 'Folder')
27
+ .forEach(cf => {
28
+ if (cf.lastKnownActualPath) {
29
+ cachedAssetPaths.add(path.normalize(path.resolve(cf.lastKnownActualPath)).toLowerCase());
30
+ }
31
+ if (cf.filePath) {
32
+ cachedAssetPaths.add(path.normalize(path.resolve(cf.filePath)).toLowerCase());
33
+ }
34
+ if (cf.lastKnownPath) {
35
+ cachedAssetPaths.add(path.normalize(path.resolve(cf.lastKnownPath)).toLowerCase());
36
+ }
37
+ });
38
+
39
+ console.log('Cached asset paths in Set:', cachedAssetPaths.size);
40
+ console.log('');
41
+
42
+ // Get local assets
43
+ const assetPaths = await walkFiles(path.join(EXPORT_ROOT, 'Assets'));
44
+ console.log('Local asset files found:', assetPaths.length);
45
+ console.log('');
46
+
47
+ // Check first few
48
+ let matched = 0;
49
+ let notMatched = 0;
50
+
51
+ for (let i = 0; i < Math.min(10, assetPaths.length); i++) {
52
+ const assetPath = assetPaths[i];
53
+ const normalizedAssetPath = path.normalize(path.resolve(assetPath)).toLowerCase();
54
+ const inCache = cachedAssetPaths.has(normalizedAssetPath);
55
+
56
+ if (inCache) {
57
+ matched++;
58
+ } else {
59
+ notMatched++;
60
+ console.log('NOT IN CACHE:', assetPath);
61
+ console.log(' Normalized:', normalizedAssetPath);
62
+ console.log('');
63
+ }
64
+ }
65
+
66
+ console.log(`First 10 files: ${matched} matched, ${notMatched} not matched`);
67
+ console.log('');
68
+
69
+ // Check if the Set actually contains the sample path
70
+ const samplePath = assetPaths[0];
71
+ const sampleNormalized = path.normalize(path.resolve(samplePath)).toLowerCase();
72
+ console.log('Sample path:', samplePath);
73
+ console.log('Normalized:', sampleNormalized);
74
+ console.log('In Set:', cachedAssetPaths.has(sampleNormalized));
75
+ console.log('');
76
+
77
+ // Check what's actually in the Set for this file
78
+ const sampleInBase = Object.values(cachedResults).find(b =>
79
+ b.filePath === 'Assets/Acronis/Banners/1.png' ||
80
+ b.lastKnownActualPath === 'src/Assets/Acronis/Banners/1.png'
81
+ );
82
+
83
+ if (sampleInBase) {
84
+ console.log('Found in base.json:');
85
+ console.log(' type:', sampleInBase.type);
86
+ console.log(' filePath:', sampleInBase.filePath);
87
+ console.log(' lastKnownActualPath:', sampleInBase.lastKnownActualPath);
88
+ console.log(' lastKnownPath:', sampleInBase.lastKnownPath);
89
+
90
+ // Check what gets added to the Set
91
+ if (sampleInBase.lastKnownActualPath) {
92
+ const normalized = path.normalize(path.resolve(sampleInBase.lastKnownActualPath)).toLowerCase();
93
+ console.log(' Normalized lastKnownActualPath:', normalized);
94
+ console.log(' Match:', normalized === sampleNormalized);
95
+ }
96
+ }
package/utils/diff.js CHANGED
@@ -1,49 +1,49 @@
1
- import { execSync, spawn } from 'child_process';
2
- import chalk from 'chalk';
3
-
4
- /**
5
- * Checks if VS Code is installed and accessible via the CLI (`code` command).
6
- * @returns {boolean} True if VS Code is available in PATH, false otherwise.
7
- */
8
- export const canOpenDiffInVSCode = () => {
9
- try {
10
- execSync('code --version', { stdio: 'ignore' });
11
- return true;
12
- } catch (err) {
13
- return false;
14
- }
15
- };
16
-
17
- /**
18
- * Opens a side-by-side diff of two files in VS Code, if available.
19
- * Falls back to terminal diff if VS Code is unavailable.
20
- * @param {string} file1 - Path to the first file.
21
- * @param {string} file2 - Path to the second file.
22
- * @returns {boolean} True if VS Code diff was opened, false if fallback used.
23
- */
24
- export const openDiffInVSCode = (file1, file2) => {
25
- if (!canOpenDiffInVSCode()) {
26
- console.log(
27
- chalk.yellow(
28
- 'Warning: VS Code is not installed or the `code` command is not in your PATH.\n' +
29
- 'Falling back to terminal diff.\nSee: https://code.visualstudio.com/docs/setup/mac#_launching-from-the-command-line'
30
- )
31
- );
32
- // Fallback: Use built-in diff (Unix) or fc (Windows)
33
- try {
34
- if (process.platform === 'win32') {
35
- execSync(`fc "${file1}" "${file2}"`, { stdio: 'inherit' });
36
- } else {
37
- execSync(`diff -u "${file1}" "${file2}"`, { stdio: 'inherit' });
38
- }
39
- } catch (e) {
40
- // Optionally handle diff exit code (e.g., files differ)
41
- }
42
- return false;
43
- }
44
-
45
- const child = spawn('code', ['--diff', file1, file2], { stdio: 'inherit', shell: true });
46
- console.log(chalk.green('Opening diff in VS Code...'));
47
-
48
- return true;
49
- };
1
+ import { execSync, spawn } from 'child_process';
2
+ import chalk from 'chalk';
3
+
4
+ /**
5
+ * Checks if VS Code is installed and accessible via the CLI (`code` command).
6
+ * @returns {boolean} True if VS Code is available in PATH, false otherwise.
7
+ */
8
+ export const canOpenDiffInVSCode = () => {
9
+ try {
10
+ execSync('code --version', { stdio: 'ignore' });
11
+ return true;
12
+ } catch (err) {
13
+ return false;
14
+ }
15
+ };
16
+
17
+ /**
18
+ * Opens a side-by-side diff of two files in VS Code, if available.
19
+ * Falls back to terminal diff if VS Code is unavailable.
20
+ * @param {string} file1 - Path to the first file.
21
+ * @param {string} file2 - Path to the second file.
22
+ * @returns {boolean} True if VS Code diff was opened, false if fallback used.
23
+ */
24
+ export const openDiffInVSCode = (file1, file2) => {
25
+ if (!canOpenDiffInVSCode()) {
26
+ console.log(
27
+ chalk.yellow(
28
+ 'Warning: VS Code is not installed or the `code` command is not in your PATH.\n' +
29
+ 'Falling back to terminal diff.\nSee: https://code.visualstudio.com/docs/setup/mac#_launching-from-the-command-line'
30
+ )
31
+ );
32
+ // Fallback: Use built-in diff (Unix) or fc (Windows)
33
+ try {
34
+ if (process.platform === 'win32') {
35
+ execSync(`fc "${file1}" "${file2}"`, { stdio: 'inherit' });
36
+ } else {
37
+ execSync(`diff -u "${file1}" "${file2}"`, { stdio: 'inherit' });
38
+ }
39
+ } catch (e) {
40
+ // Optionally handle diff exit code (e.g., files differ)
41
+ }
42
+ return false;
43
+ }
44
+
45
+ const child = spawn('code', ['--diff', file1, file2], { stdio: 'inherit', shell: true });
46
+ console.log(chalk.green('Opening diff in VS Code...'));
47
+
48
+ return true;
49
+ };