@magentrix-corp/magentrix-cli 1.3.13 → 1.3.15
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/actions/pull.js +10 -0
- package/actions/setup.js +9 -0
- package/bin/magentrix.js +18 -4
- package/package.json +1 -1
- package/utils/cli/checkInstanceUrl.js +17 -1
- package/utils/cli/helpers/ensureCredentials.js +8 -0
- package/utils/cli/helpers/ensureInstanceUrl.js +4 -0
- package/utils/debug.js +144 -0
- package/utils/magentrix/api/auth.js +12 -0
- package/utils/magentrix/api/meqlQuery.js +5 -0
- package/utils/magentrix/fetch.js +15 -1
package/actions/pull.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ensureValidCredentials } from "../utils/cli/helpers/ensureCredentials.js";
|
|
2
|
+
import debug from '../utils/debug.js';
|
|
2
3
|
import Config from "../utils/config.js";
|
|
3
4
|
import { meqlQuery } from "../utils/magentrix/api/meqlQuery.js";
|
|
4
5
|
import fs from "fs";
|
|
@@ -63,7 +64,9 @@ export const pull = async () => {
|
|
|
63
64
|
try {
|
|
64
65
|
// Step 1: Authenticate and retrieve instance URL and token
|
|
65
66
|
progress.startStep('auth');
|
|
67
|
+
debug.log('PULL', 'Step 1: Authenticating...');
|
|
66
68
|
const { instanceUrl, token } = await ensureValidCredentials();
|
|
69
|
+
debug.log('PULL', `Authenticated with instance: ${instanceUrl}`);
|
|
67
70
|
progress.completeStep('auth', '✓ Authenticated');
|
|
68
71
|
|
|
69
72
|
// Step 2: Check if instance URL has changed (credential switch detected)
|
|
@@ -138,6 +141,8 @@ export const pull = async () => {
|
|
|
138
141
|
|
|
139
142
|
// Step 4a: Download code entities (ActiveClass and ActivePage)
|
|
140
143
|
progress.startStep('download-code');
|
|
144
|
+
debug.log('PULL', 'Step 4a: Downloading code entities...');
|
|
145
|
+
debug.log('PULL', `Queries: ${queries.map(q => q.query).join(' | ')}`);
|
|
141
146
|
|
|
142
147
|
let activeClassResult, activePageResult;
|
|
143
148
|
const codeDownloadErrors = [];
|
|
@@ -188,6 +193,7 @@ export const pull = async () => {
|
|
|
188
193
|
let assets;
|
|
189
194
|
const assetDownloadErrors = [];
|
|
190
195
|
|
|
196
|
+
debug.log('PULL', 'Step 4b: Downloading static assets...');
|
|
191
197
|
try {
|
|
192
198
|
logger.info('Starting static asset downloads');
|
|
193
199
|
assets = await downloadAssets(instanceUrl, token.value, null, (current, total, message) => {
|
|
@@ -642,6 +648,8 @@ export const pull = async () => {
|
|
|
642
648
|
// Step 7: Finish progress tracker
|
|
643
649
|
logger.info('Pull completed successfully');
|
|
644
650
|
logger.close();
|
|
651
|
+
debug.log('PULL', 'Pull completed successfully');
|
|
652
|
+
debug.close();
|
|
645
653
|
progress.finish('Pull completed successfully!');
|
|
646
654
|
|
|
647
655
|
// Clear any incomplete-pull marker (set by vue-run-build when a pull is
|
|
@@ -663,6 +671,8 @@ export const pull = async () => {
|
|
|
663
671
|
} catch (error) {
|
|
664
672
|
logger.error('Pull operation failed', error);
|
|
665
673
|
logger.close();
|
|
674
|
+
debug.log('PULL', `Pull failed: ${error.message}`, error.stack);
|
|
675
|
+
debug.close();
|
|
666
676
|
progress.abort(error.message);
|
|
667
677
|
|
|
668
678
|
// Display log summary even on failure
|
package/actions/setup.js
CHANGED
|
@@ -6,6 +6,7 @@ import { ensureVSCodeFileAssociation } from "../utils/preferences.js";
|
|
|
6
6
|
import { CWD, EXPORT_ROOT, HASHED_CWD } from "../vars/global.js";
|
|
7
7
|
import { select } from "@inquirer/prompts";
|
|
8
8
|
import { registerWorkspace } from "../utils/workspaces.js";
|
|
9
|
+
import debug from '../utils/debug.js';
|
|
9
10
|
|
|
10
11
|
const config = new Config();
|
|
11
12
|
|
|
@@ -52,6 +53,9 @@ export const setup = async (cliOptions = {}) => {
|
|
|
52
53
|
let authSuccess = false;
|
|
53
54
|
let apiKey, instanceUrl, tokenData;
|
|
54
55
|
|
|
56
|
+
debug.log('SETUP', `Starting setup (pathHash: ${HASHED_CWD}, CWD: ${CWD})`);
|
|
57
|
+
debug.log('SETUP', `CLI options provided: apiKey=${cliOptions.apiKey ? 'yes' : 'no'}, instanceUrl=${cliOptions.instanceUrl || 'no'}`);
|
|
58
|
+
|
|
55
59
|
while (!authSuccess) {
|
|
56
60
|
// Get API key (from CLI or prompt)
|
|
57
61
|
apiKey = cliOptions.apiKey
|
|
@@ -63,11 +67,15 @@ export const setup = async (cliOptions = {}) => {
|
|
|
63
67
|
? cliOptions.instanceUrl.trim()
|
|
64
68
|
: await ensureInstanceUrl(true);
|
|
65
69
|
|
|
70
|
+
debug.log('SETUP', `Attempting authentication with instance: ${instanceUrl}`);
|
|
71
|
+
|
|
66
72
|
// Validate credentials by attempting to fetch an access token
|
|
67
73
|
try {
|
|
68
74
|
tokenData = await tryAuthenticate(apiKey, instanceUrl);
|
|
75
|
+
debug.log('SETUP', 'Authentication successful');
|
|
69
76
|
authSuccess = true;
|
|
70
77
|
} catch (error) {
|
|
78
|
+
debug.log('SETUP', `Authentication failed: ${error.message}`);
|
|
71
79
|
console.log(error.message);
|
|
72
80
|
console.log();
|
|
73
81
|
|
|
@@ -105,6 +113,7 @@ export const setup = async (cliOptions = {}) => {
|
|
|
105
113
|
console.log(); // Blank line for spacing
|
|
106
114
|
|
|
107
115
|
// Save values since authentication succeeded
|
|
116
|
+
debug.log('SETUP', `Saving credentials for pathHash: ${HASHED_CWD}`);
|
|
108
117
|
config.save('instanceUrl', instanceUrl, { global: true, pathHash: HASHED_CWD });
|
|
109
118
|
console.log('✅ Instance URL saved securely!');
|
|
110
119
|
|
package/bin/magentrix.js
CHANGED
|
@@ -21,9 +21,17 @@ import { irisLink, irisDev, irisDelete, irisRecover, vueBuildStage } from '../ac
|
|
|
21
21
|
import Config from '../utils/config.js';
|
|
22
22
|
import { registerWorkspace, getRegisteredWorkspaces } from '../utils/workspaces.js';
|
|
23
23
|
import { ensureVSCodeFileAssociation } from '../utils/preferences.js';
|
|
24
|
+
import debug from '../utils/debug.js';
|
|
24
25
|
|
|
25
26
|
const config = new Config();
|
|
26
27
|
|
|
28
|
+
// ── Debug Mode ───────────────────────────────
|
|
29
|
+
// Check early (before Commander parses) so debug logging covers the full lifecycle
|
|
30
|
+
if (process.argv.includes('--debug') || process.env.DEBUG === 'true') {
|
|
31
|
+
debug.enable();
|
|
32
|
+
debug.env();
|
|
33
|
+
}
|
|
34
|
+
|
|
27
35
|
// ── Vue Project Detection ────────────────────────────────
|
|
28
36
|
/**
|
|
29
37
|
* Check if current directory is a Vue project (has config.ts)
|
|
@@ -134,6 +142,7 @@ program
|
|
|
134
142
|
.name('magentrix')
|
|
135
143
|
.description('Manage Magentrix assets and automation')
|
|
136
144
|
.version(VERSION)
|
|
145
|
+
.option('--debug', 'Enable debug logging')
|
|
137
146
|
.configureHelp({
|
|
138
147
|
formatHelp: (_cmd, _helper) => {
|
|
139
148
|
const divider = chalk.gray('━'.repeat(60));
|
|
@@ -173,6 +182,7 @@ program
|
|
|
173
182
|
|
|
174
183
|
help += `\n${chalk.bold.yellow('OPTIONS')}\n`;
|
|
175
184
|
help += ` ${chalk.cyan('-V, --version')} ${chalk.dim('Output the version number')}\n`;
|
|
185
|
+
help += ` ${chalk.cyan('--debug')} ${chalk.dim('Enable debug logging to .magentrix/logs/')}\n`;
|
|
176
186
|
help += ` ${chalk.cyan('-h, --help')} ${chalk.dim('Display this help message')}\n`;
|
|
177
187
|
|
|
178
188
|
help += `\n${chalk.bold.yellow('EXAMPLES')}\n`;
|
|
@@ -365,11 +375,15 @@ function handleFatal(err) {
|
|
|
365
375
|
console.error(`\n${divider}\n${header}`);
|
|
366
376
|
console.error(`${chalk.redBright(err?.message || 'An unexpected error occurred.')}\n`);
|
|
367
377
|
|
|
368
|
-
if (
|
|
369
|
-
|
|
370
|
-
|
|
378
|
+
if (debug.enabled) {
|
|
379
|
+
debug.log('FATAL', err?.message, err?.stack);
|
|
380
|
+
debug.close();
|
|
381
|
+
if (err?.stack) {
|
|
382
|
+
console.error(chalk.dim(err.stack));
|
|
383
|
+
console.error();
|
|
384
|
+
}
|
|
371
385
|
} else {
|
|
372
|
-
console.log(`${chalk.yellow('💡 Run with')} ${chalk.cyan('
|
|
386
|
+
console.log(`${chalk.yellow('💡 Run with')} ${chalk.cyan('--debug')} ${chalk.yellow('for full details.')}`);
|
|
373
387
|
}
|
|
374
388
|
|
|
375
389
|
console.log(divider + '\n');
|
package/package.json
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import debug from '../debug.js';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Checks if the provided Magentrix instance URL is reachable by making a GET request.
|
|
3
5
|
* Throws an error if the response status is not 200 (OK), or if there is a network error.
|
|
@@ -8,6 +10,7 @@
|
|
|
8
10
|
* @returns {Promise<void>} Resolves if the instance is reachable (status 200); otherwise throws.
|
|
9
11
|
*/
|
|
10
12
|
export const checkInstanceUrl = async (instanceUrl) => {
|
|
13
|
+
debug.log('URL-CHECK', `Checking reachability: GET ${instanceUrl}`);
|
|
11
14
|
try {
|
|
12
15
|
// Native fetch is available in Node 18+; for older versions, use node-fetch.
|
|
13
16
|
const response = await fetch(instanceUrl, {
|
|
@@ -15,15 +18,28 @@ export const checkInstanceUrl = async (instanceUrl) => {
|
|
|
15
18
|
// You can add a small timeout here using AbortController if needed.
|
|
16
19
|
});
|
|
17
20
|
|
|
21
|
+
debug.log('URL-CHECK', `Response: ${response.status} ${response.statusText}`);
|
|
22
|
+
|
|
18
23
|
if (response.status !== 200) {
|
|
19
24
|
throw new Error(
|
|
20
25
|
`Instance URL responded with status ${response.status} (${response.statusText}). Expected 200 OK.`
|
|
21
26
|
);
|
|
22
27
|
}
|
|
23
28
|
} catch (err) {
|
|
29
|
+
// Node's fetch wraps the real error in err.cause (e.g. ENOTFOUND, ECONNREFUSED, ETIMEDOUT)
|
|
30
|
+
const cause = err.cause || {};
|
|
31
|
+
const rootCode = cause.code || '';
|
|
32
|
+
const rootMsg = cause.message || '';
|
|
33
|
+
const detail = rootCode ? `${rootCode}: ${rootMsg}` : (err.message || String(err));
|
|
34
|
+
|
|
35
|
+
debug.log('URL-CHECK', `Failed: ${detail}`);
|
|
36
|
+
if (cause.code) debug.log('URL-CHECK', `Error code: ${cause.code}`);
|
|
37
|
+
if (cause.hostname) debug.log('URL-CHECK', `Hostname: ${cause.hostname}`);
|
|
38
|
+
if (err.stack) debug.log('URL-CHECK', `Stack: ${err.stack}`);
|
|
39
|
+
|
|
24
40
|
// Wrap and re-throw to provide a clear error message.
|
|
25
41
|
throw new Error(
|
|
26
|
-
`Failed to reach instance URL "${instanceUrl}": ${
|
|
42
|
+
`Failed to reach instance URL "${instanceUrl}": ${detail}`
|
|
27
43
|
);
|
|
28
44
|
}
|
|
29
45
|
};
|
|
@@ -2,6 +2,7 @@ import Config from "../../config.js";
|
|
|
2
2
|
import { setup } from "../../../actions/setup.js";
|
|
3
3
|
import { HASHED_CWD } from "../../../vars/global.js";
|
|
4
4
|
import { tryAuthenticate } from "../../magentrix/api/auth.js";
|
|
5
|
+
import debug from '../../debug.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Returns true if the token is present and not expired (60 seconds buffer).
|
|
@@ -31,16 +32,22 @@ export async function ensureValidCredentials() {
|
|
|
31
32
|
const instanceUrl = config.read('instanceUrl', { global: true, pathHash: HASHED_CWD });
|
|
32
33
|
const token = config.read('token', { global: true, pathHash: HASHED_CWD });
|
|
33
34
|
|
|
35
|
+
debug.auth(`Credential check: apiKey=${apiKey ? 'present' : 'missing'}, instanceUrl=${instanceUrl || '(missing)'}`);
|
|
36
|
+
|
|
34
37
|
// If missing API key or URL, prompt/setup immediately
|
|
35
38
|
if (!apiKey || !instanceUrl) {
|
|
39
|
+
debug.auth(`Missing credentials for this directory (pathHash: ${HASHED_CWD}). No API key or instance URL found in global config for this workspace. This usually means 'magentrix setup' was never run from this directory, or was run from a different path. Falling back to setup wizard.`);
|
|
36
40
|
return setup();
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
// If token is present and valid, return immediately
|
|
40
44
|
if (isTokenValid(token)) {
|
|
45
|
+
debug.auth(`Existing token is valid (expires: ${token.validUntil})`);
|
|
41
46
|
return { apiKey, instanceUrl, token };
|
|
42
47
|
}
|
|
43
48
|
|
|
49
|
+
debug.auth(`Token ${token ? `expired (was valid until: ${token.validUntil})` : 'missing'}, refreshing...`);
|
|
50
|
+
|
|
44
51
|
// If we have API key & URL but no valid token, try to refresh
|
|
45
52
|
try {
|
|
46
53
|
const result = await tryAuthenticate(apiKey, instanceUrl);
|
|
@@ -54,6 +61,7 @@ export async function ensureValidCredentials() {
|
|
|
54
61
|
config.save('token', newToken, { global: true, pathHash: HASHED_CWD });
|
|
55
62
|
return { apiKey, instanceUrl, token: newToken };
|
|
56
63
|
} catch (err) {
|
|
64
|
+
debug.auth(`Token refresh failed: ${err.message}, falling back to setup wizard`);
|
|
57
65
|
// Failed to refresh, fall back to prompting the user
|
|
58
66
|
return setup();
|
|
59
67
|
}
|
|
@@ -3,6 +3,7 @@ import Config from "../../config.js";
|
|
|
3
3
|
import { checkInstanceUrl } from "../checkInstanceUrl.js";
|
|
4
4
|
import { withSpinner } from "../../spinner.js";
|
|
5
5
|
import { HASHED_CWD } from "../../../vars/global.js";
|
|
6
|
+
import debug from '../../debug.js';
|
|
6
7
|
|
|
7
8
|
const config = new Config();
|
|
8
9
|
const urlRegex = /^https:\/\/[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
|
@@ -45,13 +46,16 @@ export const ensureInstanceUrl = async (forcePrompt = false) => {
|
|
|
45
46
|
const trimmed = inputUrl.trim().replace(/\/+$/, '');
|
|
46
47
|
|
|
47
48
|
// Now check reachability WITH a spinner
|
|
49
|
+
debug.log('SETUP', `User entered instance URL: ${trimmed}`);
|
|
48
50
|
try {
|
|
49
51
|
await withSpinner('Checking instance URL...', () =>
|
|
50
52
|
checkInstanceUrl(trimmed)
|
|
51
53
|
);
|
|
52
54
|
|
|
55
|
+
debug.log('SETUP', `Instance URL reachable: ${trimmed}`);
|
|
53
56
|
return trimmed;
|
|
54
57
|
} catch (error) {
|
|
58
|
+
debug.log('SETUP', `Instance URL not reachable: ${trimmed} — ${error.message}`);
|
|
55
59
|
// Print error AFTER spinner, then re-prompt
|
|
56
60
|
console.error('❌ Instance URL not reachable. Try again.');
|
|
57
61
|
}
|
package/utils/debug.js
ADDED
|
@@ -0,0 +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,5 +1,6 @@
|
|
|
1
1
|
import { fetchMagentrix } from "../fetch.js";
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
+
import debug from '../../debug.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Authenticates with Magentrix and retrieves an access token using the API key as a refresh token.
|
|
@@ -11,6 +12,7 @@ import chalk from "chalk";
|
|
|
11
12
|
* @returns {Promise<string>} Resolves to the Magentrix access token string.
|
|
12
13
|
*/
|
|
13
14
|
export const getAccessToken = async (apiKey, instanceUrl) => {
|
|
15
|
+
debug.auth(`Requesting access token from ${instanceUrl}/api/3.0/token`);
|
|
14
16
|
try {
|
|
15
17
|
const data = await fetchMagentrix({
|
|
16
18
|
instanceUrl,
|
|
@@ -22,12 +24,14 @@ export const getAccessToken = async (apiKey, instanceUrl) => {
|
|
|
22
24
|
method: "POST"
|
|
23
25
|
})
|
|
24
26
|
|
|
27
|
+
debug.auth('Token received successfully', { validUntil: data.validUntil });
|
|
25
28
|
// Success
|
|
26
29
|
return {
|
|
27
30
|
token: data.token,
|
|
28
31
|
validUntil: data.validUntil
|
|
29
32
|
};
|
|
30
33
|
} catch (error) {
|
|
34
|
+
debug.auth(`Token request failed: ${error.message}`);
|
|
31
35
|
throw new Error(`Error retrieving Magentrix access token: ${error.message}`);
|
|
32
36
|
}
|
|
33
37
|
};
|
|
@@ -52,35 +56,43 @@ export const tryAuthenticate = async (apiKey, instanceUrl) => {
|
|
|
52
56
|
let formattedMessage = '\n' + chalk.red.bold('✖ Authentication Failed') + '\n';
|
|
53
57
|
formattedMessage += chalk.dim('─'.repeat(50)) + '\n\n';
|
|
54
58
|
|
|
59
|
+
debug.auth(`Authentication failed, categorizing error: ${errorMessage.substring(0, 100)}`);
|
|
60
|
+
|
|
55
61
|
if (errorMessage.includes('Network error')) {
|
|
62
|
+
debug.auth('Error category: Network error (unable to reach instance)');
|
|
56
63
|
formattedMessage += chalk.cyan.bold('🌐 Unable to reach the Magentrix instance') + '\n\n';
|
|
57
64
|
formattedMessage += chalk.yellow(' Possible causes:') + '\n';
|
|
58
65
|
formattedMessage += chalk.gray(' • Check your internet connection') + '\n';
|
|
59
66
|
formattedMessage += chalk.gray(' • Verify the instance URL is correct') + '\n';
|
|
60
67
|
formattedMessage += chalk.gray(' • Ensure the server is online and accessible') + '\n';
|
|
61
68
|
} else if (errorMessage.includes('HTTP 401') || errorMessage.includes('HTTP 403') || errorMessage.includes('Unauthorized')) {
|
|
69
|
+
debug.auth('Error category: Invalid API key (401/403)');
|
|
62
70
|
formattedMessage += chalk.cyan.bold('🔑 Invalid API Key') + '\n\n';
|
|
63
71
|
formattedMessage += chalk.yellow(' What to do:') + '\n';
|
|
64
72
|
formattedMessage += chalk.gray(' • The API key you entered is incorrect') + '\n';
|
|
65
73
|
formattedMessage += chalk.gray(' • Verify your API key from the Magentrix admin panel') + '\n';
|
|
66
74
|
} else if (errorMessage.includes('HTTP 404')) {
|
|
75
|
+
debug.auth('Error category: Invalid instance URL (404)');
|
|
67
76
|
formattedMessage += chalk.cyan.bold('🔍 Invalid Magentrix Instance URL') + '\n\n';
|
|
68
77
|
formattedMessage += chalk.yellow(' What to do:') + '\n';
|
|
69
78
|
formattedMessage += chalk.gray(' • The URL does not appear to be a valid Magentrix server') + '\n';
|
|
70
79
|
formattedMessage += chalk.gray(' • Verify the URL matches your Magentrix instance') + '\n';
|
|
71
80
|
} else if (errorMessage.includes('HTTP 5')) {
|
|
81
|
+
debug.auth('Error category: Server error (5xx)');
|
|
72
82
|
formattedMessage += chalk.cyan.bold('⚠️ Magentrix Server Error') + '\n\n';
|
|
73
83
|
formattedMessage += chalk.yellow(' What to do:') + '\n';
|
|
74
84
|
formattedMessage += chalk.gray(' • The server is experiencing issues') + '\n';
|
|
75
85
|
formattedMessage += chalk.gray(' • Please try again in a few moments') + '\n';
|
|
76
86
|
formattedMessage += chalk.gray(' • Contact support if the issue persists') + '\n';
|
|
77
87
|
} else if (errorMessage.includes('timeout') || errorMessage.includes('ETIMEDOUT')) {
|
|
88
|
+
debug.auth('Error category: Connection timeout');
|
|
78
89
|
formattedMessage += chalk.cyan.bold('⏱️ Connection Timeout') + '\n\n';
|
|
79
90
|
formattedMessage += chalk.yellow(' What to do:') + '\n';
|
|
80
91
|
formattedMessage += chalk.gray(' • The server took too long to respond') + '\n';
|
|
81
92
|
formattedMessage += chalk.gray(' • Check your internet connection') + '\n';
|
|
82
93
|
formattedMessage += chalk.gray(' • Try again in a moment') + '\n';
|
|
83
94
|
} else {
|
|
95
|
+
debug.auth('Error category: Unknown error');
|
|
84
96
|
formattedMessage += chalk.cyan.bold('❓ Unable to Authenticate') + '\n\n';
|
|
85
97
|
formattedMessage += chalk.yellow(' What to do:') + '\n';
|
|
86
98
|
formattedMessage += chalk.gray(' • Verify both your API key and instance URL are correct') + '\n';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { fetchMagentrix } from "../fetch.js";
|
|
2
|
+
import debug from '../../debug.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Executes a Magentrix Entity Query Language (MEQL) query via the REST API v3.
|
|
@@ -18,6 +19,8 @@ export const meqlQuery = async (instanceUrl, token, query = '') => {
|
|
|
18
19
|
throw new Error('MEQL query string is required');
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
debug.log('MEQL', `Query: ${query}`);
|
|
23
|
+
|
|
21
24
|
const data = await fetchMagentrix({
|
|
22
25
|
instanceUrl,
|
|
23
26
|
token,
|
|
@@ -26,6 +29,8 @@ export const meqlQuery = async (instanceUrl, token, query = '') => {
|
|
|
26
29
|
body: query
|
|
27
30
|
})
|
|
28
31
|
|
|
32
|
+
debug.log('MEQL', `Result: ${data?.Records?.length ?? 0} records returned`);
|
|
33
|
+
|
|
29
34
|
// --- Success ---
|
|
30
35
|
return data;
|
|
31
36
|
};
|
package/utils/magentrix/fetch.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import debug from '../debug.js';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Checks if a request body should be JSON-stringified.
|
|
3
5
|
* Excludes FormData, Blob, ArrayBuffer, URLSearchParams, and typed arrays.
|
|
@@ -71,14 +73,22 @@ export const fetchMagentrix = async ({
|
|
|
71
73
|
}
|
|
72
74
|
if (!finalHeaders['Content-Type'] && !ignoreContentType) finalHeaders['Content-Type'] = 'application/json';
|
|
73
75
|
|
|
76
|
+
const fullUrl = `${instanceUrl.replace(/\/$/, '')}${path}`;
|
|
77
|
+
debug.request(method, fullUrl, finalHeaders, body);
|
|
78
|
+
|
|
74
79
|
let response, responseData;
|
|
75
80
|
try {
|
|
76
|
-
response = await fetch(
|
|
81
|
+
response = await fetch(fullUrl, {
|
|
77
82
|
method,
|
|
78
83
|
headers: finalHeaders,
|
|
79
84
|
body: requestBody
|
|
80
85
|
});
|
|
81
86
|
} catch (err) {
|
|
87
|
+
const cause = err.cause || {};
|
|
88
|
+
const rootDetail = cause.code ? `${cause.code}: ${cause.message || ''}` : '';
|
|
89
|
+
debug.log('HTTP-ERR', `Network error: ${err.message}${rootDetail ? ` (${rootDetail})` : ''}`);
|
|
90
|
+
if (cause.code) debug.log('HTTP-ERR', `Error code: ${cause.code}, hostname: ${cause.hostname || 'N/A'}`);
|
|
91
|
+
if (err.stack) debug.log('HTTP-ERR', `Stack: ${err.stack}`);
|
|
82
92
|
const errorObj = {
|
|
83
93
|
type: 'network',
|
|
84
94
|
message: `Network error contacting Magentrix API: ${err.message}`,
|
|
@@ -94,6 +104,8 @@ export const fetchMagentrix = async ({
|
|
|
94
104
|
responseData = null;
|
|
95
105
|
}
|
|
96
106
|
|
|
107
|
+
debug.response(response.status, response.statusText, response.headers, responseData);
|
|
108
|
+
|
|
97
109
|
if (!response.ok) {
|
|
98
110
|
const errorObj = {
|
|
99
111
|
type: 'http',
|
|
@@ -117,6 +129,7 @@ export const fetchMagentrix = async ({
|
|
|
117
129
|
}
|
|
118
130
|
if (errorConfig?.includeURL) msg += `\nURL: ${response.url}`;
|
|
119
131
|
errorObj.message = msg;
|
|
132
|
+
debug.log('HTTP-ERR', `HTTP ${response.status}: ${msg}`);
|
|
120
133
|
if (returnErrorObject) throw errorObj;
|
|
121
134
|
throw new Error(msg);
|
|
122
135
|
}
|
|
@@ -146,6 +159,7 @@ export const fetchMagentrix = async ({
|
|
|
146
159
|
details = String(responseData);
|
|
147
160
|
}
|
|
148
161
|
errorObj.message = `Magentrix API error:\n${details}`;
|
|
162
|
+
debug.log('API-ERR', errorObj.message);
|
|
149
163
|
if (returnErrorObject) throw errorObj;
|
|
150
164
|
throw new Error(errorObj.message);
|
|
151
165
|
}
|