@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
package/utils/cacher.js
CHANGED
|
@@ -1,206 +1,206 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import Config from './config.js';
|
|
4
|
-
import { findFileByTag, getFileTag, isPathLinkedToTagByLastKnownPath, setFileTag } from './filetag.js';
|
|
5
|
-
import { compressString } from './compress.js';
|
|
6
|
-
import { sha256 } from './hash.js';
|
|
7
|
-
import { EXPORT_ROOT, ALLOWED_SRC_DIRS, IRIS_APPS_DIR } from '../vars/global.js';
|
|
8
|
-
|
|
9
|
-
const config = new Config();
|
|
10
|
-
|
|
11
|
-
// System/hidden files that should never be synced
|
|
12
|
-
const IGNORED_FILES = new Set(['.DS_Store', 'Thumbs.db', 'desktop.ini']);
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Recursively caches all files in a directory with tagging and content snapshotting
|
|
16
|
-
* OPTIMIZED: Parallel processing + mtime checks for maximum speed
|
|
17
|
-
* @param {string} dir - Directory to cache
|
|
18
|
-
* @returns {Promise<void>}
|
|
19
|
-
*/
|
|
20
|
-
export const cacheDir = async (dir) => {
|
|
21
|
-
if (!fs.existsSync(dir)) return;
|
|
22
|
-
|
|
23
|
-
const absDir = path.resolve(dir);
|
|
24
|
-
|
|
25
|
-
// Only walk whitelisted code entity directories (exclude Assets and iris-apps, handled separately)
|
|
26
|
-
const codeEntityDirs = ALLOWED_SRC_DIRS.filter(d => d !== 'Assets' && d !== IRIS_APPS_DIR);
|
|
27
|
-
const fileArrays = await Promise.all(
|
|
28
|
-
codeEntityDirs.map(d => {
|
|
29
|
-
const dirPath = path.join(absDir, d);
|
|
30
|
-
return fs.existsSync(dirPath) ? walkFiles(dirPath) : Promise.resolve([]);
|
|
31
|
-
})
|
|
32
|
-
);
|
|
33
|
-
const files = fileArrays.flat();
|
|
34
|
-
|
|
35
|
-
const cache = config.read('cachedFiles', { global: false, filename: 'fileCache.json' }) || {};
|
|
36
|
-
|
|
37
|
-
// Process files in parallel batches
|
|
38
|
-
const BATCH_SIZE = 50;
|
|
39
|
-
const updates = [];
|
|
40
|
-
|
|
41
|
-
for (let i = 0; i < files.length; i += BATCH_SIZE) {
|
|
42
|
-
const batch = files.slice(i, i + BATCH_SIZE);
|
|
43
|
-
|
|
44
|
-
// Process batch in parallel
|
|
45
|
-
const batchResults = await Promise.all(
|
|
46
|
-
batch.map(async (file) => {
|
|
47
|
-
try {
|
|
48
|
-
const stats = fs.statSync(file);
|
|
49
|
-
if (!stats.isFile()) return null;
|
|
50
|
-
|
|
51
|
-
const checkFileTag = async (retry = true) => {
|
|
52
|
-
const tag = await getFileTag(file);
|
|
53
|
-
if (!tag) {
|
|
54
|
-
const dirLinkedTag = isPathLinkedToTagByLastKnownPath(file);
|
|
55
|
-
if (dirLinkedTag && retry) {
|
|
56
|
-
await setFileTag(file, dirLinkedTag);
|
|
57
|
-
return await checkFileTag(false);
|
|
58
|
-
}
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
return tag;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const tag = await checkFileTag();
|
|
65
|
-
if (!tag) return null;
|
|
66
|
-
|
|
67
|
-
const objectId = tag;
|
|
68
|
-
|
|
69
|
-
// OPTIMIZATION: Only read/hash/compress if file changed (mtime check)
|
|
70
|
-
const existingCache = cache[objectId];
|
|
71
|
-
if (existingCache &&
|
|
72
|
-
existingCache.mtimeMs === stats.mtimeMs &&
|
|
73
|
-
existingCache.size === stats.size &&
|
|
74
|
-
existingCache.lastKnownPath === file) {
|
|
75
|
-
// File hasn't changed, skip expensive operations
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// File is new or changed - do the expensive operations
|
|
80
|
-
const content = fs.readFileSync(file, 'utf8');
|
|
81
|
-
|
|
82
|
-
return {
|
|
83
|
-
objectId,
|
|
84
|
-
data: {
|
|
85
|
-
tag,
|
|
86
|
-
lastKnownPath: file,
|
|
87
|
-
contentHash: sha256(content),
|
|
88
|
-
compressedContent: compressString(content),
|
|
89
|
-
size: stats.size,
|
|
90
|
-
mtimeMs: stats.mtimeMs,
|
|
91
|
-
dev: stats.dev,
|
|
92
|
-
ino: stats.ino
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
} catch (err) {
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
})
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
// Collect updates
|
|
102
|
-
updates.push(...batchResults.filter(r => r !== null));
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Apply all updates to cache
|
|
106
|
-
if (updates.length > 0) {
|
|
107
|
-
for (const { objectId, data } of updates) {
|
|
108
|
-
cache[objectId] = data;
|
|
109
|
-
}
|
|
110
|
-
config.save('cachedFiles', cache, { global: false, filename: 'fileCache.json' });
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Recaches file ID index by walking files and updating tags
|
|
116
|
-
* OPTIMIZED: Parallel processing with batching for speed
|
|
117
|
-
* @param {string} dir - Directory to recache
|
|
118
|
-
* @returns {Promise<void>}
|
|
119
|
-
*/
|
|
120
|
-
export const recacheFileIdIndex = async (dir) => {
|
|
121
|
-
const absDir = path.resolve(dir);
|
|
122
|
-
|
|
123
|
-
// Only walk whitelisted code entity directories (exclude Assets and iris-apps, handled separately)
|
|
124
|
-
const codeEntityDirs = ALLOWED_SRC_DIRS.filter(d => d !== 'Assets' && d !== IRIS_APPS_DIR);
|
|
125
|
-
const fileArrays = await Promise.all(
|
|
126
|
-
codeEntityDirs.map(d => {
|
|
127
|
-
const dirPath = path.join(absDir, d);
|
|
128
|
-
return fs.existsSync(dirPath) ? walkFiles(dirPath) : Promise.resolve([]);
|
|
129
|
-
})
|
|
130
|
-
);
|
|
131
|
-
const files = fileArrays.flat();
|
|
132
|
-
if (!files || files?.length < 1) return;
|
|
133
|
-
|
|
134
|
-
// Process files in parallel batches of 50 for speed
|
|
135
|
-
const BATCH_SIZE = 50;
|
|
136
|
-
const updates = [];
|
|
137
|
-
|
|
138
|
-
for (let i = 0; i < files.length; i += BATCH_SIZE) {
|
|
139
|
-
const batch = files.slice(i, i + BATCH_SIZE);
|
|
140
|
-
|
|
141
|
-
// Process batch in parallel
|
|
142
|
-
const batchResults = await Promise.all(
|
|
143
|
-
batch.map(async (file) => {
|
|
144
|
-
try {
|
|
145
|
-
const tag = await getFileTag(file);
|
|
146
|
-
if (!tag) return null;
|
|
147
|
-
|
|
148
|
-
// Check if path changed
|
|
149
|
-
const lastKnownPath = findFileByTag(tag);
|
|
150
|
-
if (lastKnownPath !== file) {
|
|
151
|
-
return { file, tag };
|
|
152
|
-
}
|
|
153
|
-
return null;
|
|
154
|
-
} catch (err) {
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
})
|
|
158
|
-
);
|
|
159
|
-
|
|
160
|
-
// Collect updates
|
|
161
|
-
updates.push(...batchResults.filter(r => r !== null));
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Apply all updates at once
|
|
165
|
-
for (const { file, tag } of updates) {
|
|
166
|
-
await setFileTag(file, tag);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Recursively walks a directory and returns all file paths
|
|
172
|
-
* @param {string} dir
|
|
173
|
-
* @returns {Promise<string[]>}
|
|
174
|
-
*/
|
|
175
|
-
export async function walkFiles(dir, settings) {
|
|
176
|
-
const ignore = settings?.ignore || [];
|
|
177
|
-
|
|
178
|
-
if (!fs.existsSync(dir)) return [];
|
|
179
|
-
let entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
180
|
-
let paths = [];
|
|
181
|
-
|
|
182
|
-
for (const entry of entries) {
|
|
183
|
-
const fullPath = path.join(dir, entry.name);
|
|
184
|
-
|
|
185
|
-
// Skip OS-generated system files (.DS_Store, Thumbs.db, etc.)
|
|
186
|
-
if (IGNORED_FILES.has(entry.name)) {
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Check if this path should be ignored
|
|
191
|
-
if (ignore.find(p => fullPath.startsWith(p) || fullPath === p)) {
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if (entry.isDirectory()) {
|
|
196
|
-
const subPaths = await walkFiles(fullPath, settings);
|
|
197
|
-
if (subPaths && subPaths.length > 0) {
|
|
198
|
-
paths.push(...subPaths);
|
|
199
|
-
}
|
|
200
|
-
} else if (entry.isFile()) {
|
|
201
|
-
paths.push(fullPath);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return paths;
|
|
206
|
-
}
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import Config from './config.js';
|
|
4
|
+
import { findFileByTag, getFileTag, isPathLinkedToTagByLastKnownPath, setFileTag } from './filetag.js';
|
|
5
|
+
import { compressString } from './compress.js';
|
|
6
|
+
import { sha256 } from './hash.js';
|
|
7
|
+
import { EXPORT_ROOT, ALLOWED_SRC_DIRS, IRIS_APPS_DIR } from '../vars/global.js';
|
|
8
|
+
|
|
9
|
+
const config = new Config();
|
|
10
|
+
|
|
11
|
+
// System/hidden files that should never be synced
|
|
12
|
+
const IGNORED_FILES = new Set(['.DS_Store', 'Thumbs.db', 'desktop.ini']);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Recursively caches all files in a directory with tagging and content snapshotting
|
|
16
|
+
* OPTIMIZED: Parallel processing + mtime checks for maximum speed
|
|
17
|
+
* @param {string} dir - Directory to cache
|
|
18
|
+
* @returns {Promise<void>}
|
|
19
|
+
*/
|
|
20
|
+
export const cacheDir = async (dir) => {
|
|
21
|
+
if (!fs.existsSync(dir)) return;
|
|
22
|
+
|
|
23
|
+
const absDir = path.resolve(dir);
|
|
24
|
+
|
|
25
|
+
// Only walk whitelisted code entity directories (exclude Assets and iris-apps, handled separately)
|
|
26
|
+
const codeEntityDirs = ALLOWED_SRC_DIRS.filter(d => d !== 'Assets' && d !== IRIS_APPS_DIR);
|
|
27
|
+
const fileArrays = await Promise.all(
|
|
28
|
+
codeEntityDirs.map(d => {
|
|
29
|
+
const dirPath = path.join(absDir, d);
|
|
30
|
+
return fs.existsSync(dirPath) ? walkFiles(dirPath) : Promise.resolve([]);
|
|
31
|
+
})
|
|
32
|
+
);
|
|
33
|
+
const files = fileArrays.flat();
|
|
34
|
+
|
|
35
|
+
const cache = config.read('cachedFiles', { global: false, filename: 'fileCache.json' }) || {};
|
|
36
|
+
|
|
37
|
+
// Process files in parallel batches
|
|
38
|
+
const BATCH_SIZE = 50;
|
|
39
|
+
const updates = [];
|
|
40
|
+
|
|
41
|
+
for (let i = 0; i < files.length; i += BATCH_SIZE) {
|
|
42
|
+
const batch = files.slice(i, i + BATCH_SIZE);
|
|
43
|
+
|
|
44
|
+
// Process batch in parallel
|
|
45
|
+
const batchResults = await Promise.all(
|
|
46
|
+
batch.map(async (file) => {
|
|
47
|
+
try {
|
|
48
|
+
const stats = fs.statSync(file);
|
|
49
|
+
if (!stats.isFile()) return null;
|
|
50
|
+
|
|
51
|
+
const checkFileTag = async (retry = true) => {
|
|
52
|
+
const tag = await getFileTag(file);
|
|
53
|
+
if (!tag) {
|
|
54
|
+
const dirLinkedTag = isPathLinkedToTagByLastKnownPath(file);
|
|
55
|
+
if (dirLinkedTag && retry) {
|
|
56
|
+
await setFileTag(file, dirLinkedTag);
|
|
57
|
+
return await checkFileTag(false);
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return tag;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const tag = await checkFileTag();
|
|
65
|
+
if (!tag) return null;
|
|
66
|
+
|
|
67
|
+
const objectId = tag;
|
|
68
|
+
|
|
69
|
+
// OPTIMIZATION: Only read/hash/compress if file changed (mtime check)
|
|
70
|
+
const existingCache = cache[objectId];
|
|
71
|
+
if (existingCache &&
|
|
72
|
+
existingCache.mtimeMs === stats.mtimeMs &&
|
|
73
|
+
existingCache.size === stats.size &&
|
|
74
|
+
existingCache.lastKnownPath === file) {
|
|
75
|
+
// File hasn't changed, skip expensive operations
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// File is new or changed - do the expensive operations
|
|
80
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
objectId,
|
|
84
|
+
data: {
|
|
85
|
+
tag,
|
|
86
|
+
lastKnownPath: file,
|
|
87
|
+
contentHash: sha256(content),
|
|
88
|
+
compressedContent: compressString(content),
|
|
89
|
+
size: stats.size,
|
|
90
|
+
mtimeMs: stats.mtimeMs,
|
|
91
|
+
dev: stats.dev,
|
|
92
|
+
ino: stats.ino
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
} catch (err) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// Collect updates
|
|
102
|
+
updates.push(...batchResults.filter(r => r !== null));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Apply all updates to cache
|
|
106
|
+
if (updates.length > 0) {
|
|
107
|
+
for (const { objectId, data } of updates) {
|
|
108
|
+
cache[objectId] = data;
|
|
109
|
+
}
|
|
110
|
+
config.save('cachedFiles', cache, { global: false, filename: 'fileCache.json' });
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Recaches file ID index by walking files and updating tags
|
|
116
|
+
* OPTIMIZED: Parallel processing with batching for speed
|
|
117
|
+
* @param {string} dir - Directory to recache
|
|
118
|
+
* @returns {Promise<void>}
|
|
119
|
+
*/
|
|
120
|
+
export const recacheFileIdIndex = async (dir) => {
|
|
121
|
+
const absDir = path.resolve(dir);
|
|
122
|
+
|
|
123
|
+
// Only walk whitelisted code entity directories (exclude Assets and iris-apps, handled separately)
|
|
124
|
+
const codeEntityDirs = ALLOWED_SRC_DIRS.filter(d => d !== 'Assets' && d !== IRIS_APPS_DIR);
|
|
125
|
+
const fileArrays = await Promise.all(
|
|
126
|
+
codeEntityDirs.map(d => {
|
|
127
|
+
const dirPath = path.join(absDir, d);
|
|
128
|
+
return fs.existsSync(dirPath) ? walkFiles(dirPath) : Promise.resolve([]);
|
|
129
|
+
})
|
|
130
|
+
);
|
|
131
|
+
const files = fileArrays.flat();
|
|
132
|
+
if (!files || files?.length < 1) return;
|
|
133
|
+
|
|
134
|
+
// Process files in parallel batches of 50 for speed
|
|
135
|
+
const BATCH_SIZE = 50;
|
|
136
|
+
const updates = [];
|
|
137
|
+
|
|
138
|
+
for (let i = 0; i < files.length; i += BATCH_SIZE) {
|
|
139
|
+
const batch = files.slice(i, i + BATCH_SIZE);
|
|
140
|
+
|
|
141
|
+
// Process batch in parallel
|
|
142
|
+
const batchResults = await Promise.all(
|
|
143
|
+
batch.map(async (file) => {
|
|
144
|
+
try {
|
|
145
|
+
const tag = await getFileTag(file);
|
|
146
|
+
if (!tag) return null;
|
|
147
|
+
|
|
148
|
+
// Check if path changed
|
|
149
|
+
const lastKnownPath = findFileByTag(tag);
|
|
150
|
+
if (lastKnownPath !== file) {
|
|
151
|
+
return { file, tag };
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
} catch (err) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
// Collect updates
|
|
161
|
+
updates.push(...batchResults.filter(r => r !== null));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Apply all updates at once
|
|
165
|
+
for (const { file, tag } of updates) {
|
|
166
|
+
await setFileTag(file, tag);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Recursively walks a directory and returns all file paths
|
|
172
|
+
* @param {string} dir
|
|
173
|
+
* @returns {Promise<string[]>}
|
|
174
|
+
*/
|
|
175
|
+
export async function walkFiles(dir, settings) {
|
|
176
|
+
const ignore = settings?.ignore || [];
|
|
177
|
+
|
|
178
|
+
if (!fs.existsSync(dir)) return [];
|
|
179
|
+
let entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
180
|
+
let paths = [];
|
|
181
|
+
|
|
182
|
+
for (const entry of entries) {
|
|
183
|
+
const fullPath = path.join(dir, entry.name);
|
|
184
|
+
|
|
185
|
+
// Skip OS-generated system files (.DS_Store, Thumbs.db, etc.)
|
|
186
|
+
if (IGNORED_FILES.has(entry.name)) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Check if this path should be ignored
|
|
191
|
+
if (ignore.find(p => fullPath.startsWith(p) || fullPath === p)) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (entry.isDirectory()) {
|
|
196
|
+
const subPaths = await walkFiles(fullPath, settings);
|
|
197
|
+
if (subPaths && subPaths.length > 0) {
|
|
198
|
+
paths.push(...subPaths);
|
|
199
|
+
}
|
|
200
|
+
} else if (entry.isFile()) {
|
|
201
|
+
paths.push(fullPath);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return paths;
|
|
206
|
+
}
|
|
@@ -1,74 +1,76 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* @
|
|
11
|
-
* @
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
debug.log('URL-CHECK', `DNS
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
};
|
|
1
|
+
import https from 'node:https';
|
|
2
|
+
import { lookup } from 'node:dns/promises';
|
|
3
|
+
import debug from '../debug.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Checks if the provided Magentrix instance URL is reachable by making a GET request.
|
|
7
|
+
* Uses Node's https module instead of fetch/undici, which hangs on loopback addresses.
|
|
8
|
+
* Accepts any 2xx or 3xx status as "reachable".
|
|
9
|
+
*
|
|
10
|
+
* @async
|
|
11
|
+
* @param {string} instanceUrl - The https://subdomain.magentrixcloud.com URL to check.
|
|
12
|
+
* @throws {Error} Throws if the instance is unreachable or returns a 4xx/5xx status.
|
|
13
|
+
* @returns {Promise<void>} Resolves if the instance is reachable; otherwise throws.
|
|
14
|
+
*/
|
|
15
|
+
export const checkInstanceUrl = async (instanceUrl) => {
|
|
16
|
+
debug.log('URL-CHECK', `Checking reachability: GET ${instanceUrl}`);
|
|
17
|
+
|
|
18
|
+
// Resolve DNS first to help diagnose hosts file / local server issues
|
|
19
|
+
const hostname = new URL(instanceUrl).hostname;
|
|
20
|
+
try {
|
|
21
|
+
const { address, family } = await lookup(hostname);
|
|
22
|
+
debug.log('URL-CHECK', `DNS resolved: ${hostname} -> ${address} (IPv${family})`);
|
|
23
|
+
} catch (dnsErr) {
|
|
24
|
+
debug.log('URL-CHECK', `DNS lookup failed for ${hostname}: ${dnsErr.code || dnsErr.message}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
await new Promise((resolve, reject) => {
|
|
28
|
+
const timer = setTimeout(() => {
|
|
29
|
+
req.destroy();
|
|
30
|
+
debug.log('URL-CHECK', `Timeout after 15s — aborting request to ${instanceUrl}`);
|
|
31
|
+
reject(new Error(
|
|
32
|
+
`Connection timed out after 15s. The server at "${hostname}" did not respond.`
|
|
33
|
+
));
|
|
34
|
+
}, 15000);
|
|
35
|
+
|
|
36
|
+
const req = https.get(instanceUrl, { rejectUnauthorized: process.env.NODE_TLS_REJECT_UNAUTHORIZED !== '0' }, (res) => {
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
// Consume body so socket is freed
|
|
39
|
+
res.resume();
|
|
40
|
+
|
|
41
|
+
debug.log('URL-CHECK', `Response: ${res.statusCode} ${res.statusMessage}`);
|
|
42
|
+
|
|
43
|
+
if (res.statusCode >= 400) {
|
|
44
|
+
reject(new Error(
|
|
45
|
+
`Instance URL responded with status ${res.statusCode} (${res.statusMessage}).`
|
|
46
|
+
));
|
|
47
|
+
} else {
|
|
48
|
+
resolve();
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
req.on('error', (err) => {
|
|
53
|
+
clearTimeout(timer);
|
|
54
|
+
const code = err.code || '';
|
|
55
|
+
const msg = err.message || String(err);
|
|
56
|
+
const isTLS = code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE' || code === 'CERT_HAS_EXPIRED'
|
|
57
|
+
|| code === 'DEPTH_ZERO_SELF_SIGNED_CERT' || code === 'ERR_TLS_CERT_ALTNAME_INVALID'
|
|
58
|
+
|| code === 'SELF_SIGNED_CERT_IN_CHAIN' || msg.includes('certificate');
|
|
59
|
+
|
|
60
|
+
let detail;
|
|
61
|
+
if (isTLS) {
|
|
62
|
+
detail = `SSL/TLS error (${code}). If using a local server with a self-signed certificate, set NODE_TLS_REJECT_UNAUTHORIZED=0 or use NODE_EXTRA_CA_CERTS. Detail: ${msg}`;
|
|
63
|
+
} else {
|
|
64
|
+
detail = code ? `${code}: ${msg}` : msg;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
debug.log('URL-CHECK', `Failed: ${detail}`);
|
|
68
|
+
if (err.code) debug.log('URL-CHECK', `Error code: ${err.code}`);
|
|
69
|
+
if (err.stack) debug.log('URL-CHECK', `Stack: ${err.stack}`);
|
|
70
|
+
|
|
71
|
+
reject(new Error(
|
|
72
|
+
`Failed to reach instance URL "${instanceUrl}": ${detail}`
|
|
73
|
+
));
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
};
|