@link-assistant/hive-mind 1.46.2 → 1.46.3
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/CHANGELOG.md +9 -0
- package/package.json +1 -1
- package/src/version-info.lib.mjs +287 -59
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.46.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- c425744: Standardize /version output — strip OS/arch, normalize dates, enhance platform detection (Issue #1524)
|
|
8
|
+
- Strip OS/architecture info (e.g. x86_64-unknown-linux-gnu, linux/amd64) from version strings for cleaner output
|
|
9
|
+
- Normalize date formats to ISO (YYYY-MM-DD) across all version components
|
|
10
|
+
- Enhance platform detection for consistent environment reporting
|
|
11
|
+
|
|
3
12
|
## 1.46.2
|
|
4
13
|
|
|
5
14
|
### Patch Changes
|
package/package.json
CHANGED
package/src/version-info.lib.mjs
CHANGED
|
@@ -13,6 +13,89 @@ import { promisify } from 'util';
|
|
|
13
13
|
|
|
14
14
|
const execAsync = promisify(exec);
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Month name/abbreviation to zero-padded number mapping
|
|
18
|
+
*/
|
|
19
|
+
const MONTH_MAP = {
|
|
20
|
+
jan: '01',
|
|
21
|
+
january: '01',
|
|
22
|
+
feb: '02',
|
|
23
|
+
february: '02',
|
|
24
|
+
mar: '03',
|
|
25
|
+
march: '03',
|
|
26
|
+
apr: '04',
|
|
27
|
+
april: '04',
|
|
28
|
+
may: '05',
|
|
29
|
+
jun: '06',
|
|
30
|
+
june: '06',
|
|
31
|
+
jul: '07',
|
|
32
|
+
july: '07',
|
|
33
|
+
aug: '08',
|
|
34
|
+
august: '08',
|
|
35
|
+
sep: '09',
|
|
36
|
+
september: '09',
|
|
37
|
+
oct: '10',
|
|
38
|
+
october: '10',
|
|
39
|
+
nov: '11',
|
|
40
|
+
november: '11',
|
|
41
|
+
dec: '12',
|
|
42
|
+
december: '12',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Normalize a date string to ISO format (YYYY-MM-DD).
|
|
47
|
+
* Handles formats like:
|
|
48
|
+
* "20-Aug-23" → "2023-08-20"
|
|
49
|
+
* "20 April 2009" → "2009-04-20"
|
|
50
|
+
* "July 5th 2008" → "2008-07-05"
|
|
51
|
+
* "Jan 13 2026" → "2026-01-13"
|
|
52
|
+
* "2024-02-29" → "2024-02-29" (passthrough)
|
|
53
|
+
* Returns the original string if parsing fails.
|
|
54
|
+
* @param {string} dateStr - Date string to normalize
|
|
55
|
+
* @returns {string} ISO date string or original
|
|
56
|
+
*/
|
|
57
|
+
export function normalizeDate(dateStr) {
|
|
58
|
+
if (!dateStr) return dateStr;
|
|
59
|
+
const s = dateStr.trim();
|
|
60
|
+
|
|
61
|
+
// Already ISO: YYYY-MM-DD
|
|
62
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(s)) return s;
|
|
63
|
+
|
|
64
|
+
// "DD-Mon-YY" (e.g. "20-Aug-23")
|
|
65
|
+
const dmy = s.match(/^(\d{1,2})-([A-Za-z]{3})-(\d{2})$/);
|
|
66
|
+
if (dmy) {
|
|
67
|
+
const month = MONTH_MAP[dmy[2].toLowerCase()];
|
|
68
|
+
if (month) {
|
|
69
|
+
const year = parseInt(dmy[3], 10);
|
|
70
|
+
const fullYear = year >= 70 ? `19${dmy[3]}` : `20${dmy[3]}`;
|
|
71
|
+
return `${fullYear}-${month}-${dmy[1].padStart(2, '0')}`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// "DD Month YYYY" (e.g. "20 April 2009")
|
|
76
|
+
const dmY = s.match(/^(\d{1,2})\s+([A-Za-z]+)\s+(\d{4})$/);
|
|
77
|
+
if (dmY) {
|
|
78
|
+
const month = MONTH_MAP[dmY[2].toLowerCase()];
|
|
79
|
+
if (month) return `${dmY[3]}-${month}-${dmY[1].padStart(2, '0')}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// "Month DDth YYYY" or "Month DD YYYY" (e.g. "July 5th 2008", "Jan 13 2026")
|
|
83
|
+
const mdY = s.match(/^([A-Za-z]+)\s+(\d{1,2})(?:st|nd|rd|th)?\s+(\d{4})$/);
|
|
84
|
+
if (mdY) {
|
|
85
|
+
const month = MONTH_MAP[mdY[1].toLowerCase()];
|
|
86
|
+
if (month) return `${mdY[3]}-${month}-${mdY[2].padStart(2, '0')}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// "Month DD YYYY HH:MM:SS" (e.g. "Jan 13 2026 22:36:55")
|
|
90
|
+
const mdYt = s.match(/^([A-Za-z]+)\s+(\d{1,2})\s+(\d{4})\s+(\d{2}:\d{2}:\d{2})$/);
|
|
91
|
+
if (mdYt) {
|
|
92
|
+
const month = MONTH_MAP[mdYt[1].toLowerCase()];
|
|
93
|
+
if (month) return `${mdYt[3]}-${month}-${mdYt[2].padStart(2, '0')} ${mdYt[4]}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return s;
|
|
97
|
+
}
|
|
98
|
+
|
|
16
99
|
/**
|
|
17
100
|
* Execute a command asynchronously and return its output, or null if it fails
|
|
18
101
|
* @param {string} command - Command to execute
|
|
@@ -58,19 +141,30 @@ const VERSION_PARSERS = {
|
|
|
58
141
|
const extra = m[2] ? m[2].trim().split(/\s+/) : [];
|
|
59
142
|
return { version: m[1], extra };
|
|
60
143
|
},
|
|
61
|
-
// go version go1.26.1 linux/amd64
|
|
144
|
+
// go version go1.26.1 linux/amd64 → strip platform/arch
|
|
62
145
|
go: raw => {
|
|
63
|
-
const m = raw.match(/go([\d.]+(?:\S*)?)
|
|
146
|
+
const m = raw.match(/go([\d.]+(?:\S*)?)/);
|
|
64
147
|
if (!m) return null;
|
|
65
|
-
return { version: m[1], extra: [
|
|
148
|
+
return { version: m[1], extra: [] };
|
|
66
149
|
},
|
|
67
|
-
// PHP 8.3.30 (cli) (built: Jan 13 2026 22:36:55) (NTS)
|
|
150
|
+
// PHP 8.3.30 (cli) (built: Jan 13 2026 22:36:55) (NTS) → strip cli, normalize date
|
|
68
151
|
php: raw => {
|
|
69
152
|
const m = raw.match(/^PHP\s+([\d.]+(?:-\S+)?)\s*(.*)/);
|
|
70
153
|
if (!m) return null;
|
|
71
154
|
const tags = [];
|
|
72
155
|
const parts = m[2].matchAll(/\(([^)]+)\)/g);
|
|
73
|
-
for (const p of parts)
|
|
156
|
+
for (const p of parts) {
|
|
157
|
+
const tag = p[1].trim();
|
|
158
|
+
// Skip "cli" — not meaningful for version display
|
|
159
|
+
if (tag === 'cli') continue;
|
|
160
|
+
// Normalize "built: Jan 13 2026 22:36:55" → "2026-01-13 22:36:55"
|
|
161
|
+
const built = tag.match(/^built:\s+(.+)$/);
|
|
162
|
+
if (built) {
|
|
163
|
+
tags.push(normalizeDate(built[1]));
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
tags.push(tag);
|
|
167
|
+
}
|
|
74
168
|
return { version: m[1], extra: tags };
|
|
75
169
|
},
|
|
76
170
|
// openjdk version "21" 2023-09-19 LTS
|
|
@@ -79,27 +173,32 @@ const VERSION_PARSERS = {
|
|
|
79
173
|
if (!m) return null;
|
|
80
174
|
return { version: m[1], extra: m[2] ? [m[2].trim()] : [] };
|
|
81
175
|
},
|
|
82
|
-
// gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0
|
|
176
|
+
// gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0 → use base version only
|
|
83
177
|
gcc: raw => {
|
|
84
|
-
const m = raw.match(/^gcc\s+(?:\(
|
|
178
|
+
const m = raw.match(/^gcc\s+(?:\([^)]*\)\s+)?([\d.]+)/);
|
|
85
179
|
if (!m) return null;
|
|
86
|
-
|
|
87
|
-
if (m[1] && m[2]) return { version: m[2], extra: [] };
|
|
88
|
-
return { version: m[3], extra: [] };
|
|
180
|
+
return { version: m[1], extra: [] };
|
|
89
181
|
},
|
|
90
|
-
// g++ (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0
|
|
182
|
+
// g++ (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0 → use base version only
|
|
91
183
|
gpp: raw => {
|
|
92
|
-
const m = raw.match(/^g\+\+\s+(?:\(
|
|
184
|
+
const m = raw.match(/^g\+\+\s+(?:\([^)]*\)\s+)?([\d.]+)/);
|
|
93
185
|
if (!m) return null;
|
|
94
|
-
|
|
95
|
-
if (m[1] && m[2]) return { version: m[2], extra: [] };
|
|
96
|
-
return { version: m[3], extra: [] };
|
|
186
|
+
return { version: m[1], extra: [] };
|
|
97
187
|
},
|
|
98
|
-
// clang version 17.0.0 (https://github.com/... commit
|
|
188
|
+
// clang version 17.0.0 (https://github.com/... 2e6139970eda) → strip URL, keep commit
|
|
99
189
|
clang: raw => {
|
|
100
190
|
const m = raw.match(/^clang\s+version\s+([\d.]+(?:-\S+)?)\s*(?:\(([^)]+)\))?/);
|
|
101
191
|
if (!m) return null;
|
|
102
|
-
|
|
192
|
+
if (m[2]) {
|
|
193
|
+
// Remove URLs, keep only hex commit hashes
|
|
194
|
+
const parts = m[2]
|
|
195
|
+
.trim()
|
|
196
|
+
.split(/\s+/)
|
|
197
|
+
.filter(p => !p.includes('://') && !p.includes('.git'));
|
|
198
|
+
const commitParts = parts.filter(p => /^[0-9a-f]{7,}$/i.test(p));
|
|
199
|
+
return { version: m[1], extra: commitParts };
|
|
200
|
+
}
|
|
201
|
+
return { version: m[1], extra: [] };
|
|
103
202
|
},
|
|
104
203
|
// LLD 17.0.0 (compatible with GNU linkers) — only version number matters
|
|
105
204
|
lld: raw => {
|
|
@@ -113,34 +212,47 @@ const VERSION_PARSERS = {
|
|
|
113
212
|
if (!m) return null;
|
|
114
213
|
return { version: m[1], extra: [] };
|
|
115
214
|
},
|
|
116
|
-
// ruby 3.4.9 (2026-03-11 revision 76cca827ab) +PRISM [x86_64-linux]
|
|
215
|
+
// ruby 3.4.9 (2026-03-11 revision 76cca827ab) +PRISM [x86_64-linux] → strip arch, reformat
|
|
117
216
|
ruby: raw => {
|
|
118
217
|
const m = raw.match(/^ruby\s+([\d.]+(?:p\d+)?)\s*(?:\(([^)]+)\))?\s*(.*)/);
|
|
119
218
|
if (!m) return null;
|
|
120
219
|
const extra = [];
|
|
121
|
-
if (m[2])
|
|
220
|
+
if (m[2]) {
|
|
221
|
+
// Parse "2026-03-11 revision 76cca827ab" → commit, date
|
|
222
|
+
const revMatch = m[2].match(/^(\d{4}-\d{2}-\d{2})\s+revision\s+(\w+)$/);
|
|
223
|
+
if (revMatch) {
|
|
224
|
+
extra.push(revMatch[2]); // commit first
|
|
225
|
+
extra.push(revMatch[1]); // then date
|
|
226
|
+
} else {
|
|
227
|
+
extra.push(m[2].trim());
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// Capture +PRISM or similar flags, but strip [arch] info
|
|
122
231
|
const tail = m[3] ? m[3].trim() : '';
|
|
123
|
-
if (tail)
|
|
232
|
+
if (tail) {
|
|
233
|
+
const cleaned = tail.replace(/\[[\w-]+\]/g, '').trim();
|
|
234
|
+
if (cleaned) extra.push(cleaned);
|
|
235
|
+
}
|
|
124
236
|
return { version: m[1], extra };
|
|
125
237
|
},
|
|
126
|
-
// Kotlin version 2.3.20-release-208 (JRE 21+35-LTS)
|
|
238
|
+
// Kotlin version 2.3.20-release-208 (JRE 21+35-LTS) → strip -release-NNN suffix
|
|
127
239
|
kotlin: raw => {
|
|
128
|
-
const m = raw.match(/^Kotlin\s+version\s+([\d
|
|
240
|
+
const m = raw.match(/^Kotlin\s+version\s+([\d.]+)(?:-release-\d+)?\s*(?:\(([^)]+)\))?/);
|
|
129
241
|
if (!m) return null;
|
|
130
242
|
return { version: m[1], extra: m[2] ? [m[2].trim()] : [] };
|
|
131
243
|
},
|
|
132
|
-
// Swift version 6.0.3 (swift-6.0.3-RELEASE)
|
|
244
|
+
// Swift version 6.0.3 (swift-6.0.3-RELEASE) → strip redundant release tag
|
|
133
245
|
swift: raw => {
|
|
134
|
-
const m = raw.match(/^Swift\s+version\s+([\d.]+(?:\.\d+)?)
|
|
246
|
+
const m = raw.match(/^Swift\s+version\s+([\d.]+(?:\.\d+)?)/);
|
|
135
247
|
if (!m) return null;
|
|
136
|
-
return { version: m[1], extra:
|
|
248
|
+
return { version: m[1], extra: [] };
|
|
137
249
|
},
|
|
138
|
-
// R version 4.3.3 (2024-02-29) -- "Angel Food Cake"
|
|
250
|
+
// R version 4.3.3 (2024-02-29) -- "Angel Food Cake" → normalize date
|
|
139
251
|
r: raw => {
|
|
140
252
|
const m = raw.match(/^R\s+version\s+([\d.]+)\s*(?:\(([^)]+)\))?(?:\s+--\s+"([^"]+)")?/);
|
|
141
253
|
if (!m) return null;
|
|
142
254
|
const extra = [];
|
|
143
|
-
if (m[2]) extra.push(m[2]);
|
|
255
|
+
if (m[2]) extra.push(normalizeDate(m[2]));
|
|
144
256
|
if (m[3]) extra.push(m[3]);
|
|
145
257
|
return { version: m[1], extra };
|
|
146
258
|
},
|
|
@@ -162,11 +274,11 @@ const VERSION_PARSERS = {
|
|
|
162
274
|
if (!m) return null;
|
|
163
275
|
return { version: m[1], extra: [] };
|
|
164
276
|
},
|
|
165
|
-
// curl 8.19.0 (x86_64-pc-linux-gnu) libcurl/8.19.0 ...
|
|
277
|
+
// curl 8.19.0 (x86_64-pc-linux-gnu) libcurl/8.19.0 ... → strip arch info
|
|
166
278
|
curl: raw => {
|
|
167
|
-
const m = raw.match(/^curl\s+([\d.]+)
|
|
279
|
+
const m = raw.match(/^curl\s+([\d.]+)/);
|
|
168
280
|
if (!m) return null;
|
|
169
|
-
return { version: m[1], extra:
|
|
281
|
+
return { version: m[1], extra: [] };
|
|
170
282
|
},
|
|
171
283
|
// GNU Wget 1.21.4 built on linux-gnu.
|
|
172
284
|
wget: raw => {
|
|
@@ -198,13 +310,13 @@ const VERSION_PARSERS = {
|
|
|
198
310
|
if (!m) return null;
|
|
199
311
|
return { version: m[1], extra: [] };
|
|
200
312
|
},
|
|
201
|
-
// Screen version 4.09.01 (GNU) 20-Aug-23
|
|
313
|
+
// Screen version 4.09.01 (GNU) 20-Aug-23 → normalize date, strip GNU
|
|
202
314
|
screen: raw => {
|
|
203
|
-
const m = raw.match(/^Screen\s+version\s+([\d.]+)\s*(?:\(
|
|
315
|
+
const m = raw.match(/^Screen\s+version\s+([\d.]+)\s*(?:\([^)]*\))?\s*(.*)/);
|
|
204
316
|
if (!m) return null;
|
|
205
317
|
const extra = [];
|
|
206
|
-
|
|
207
|
-
if (
|
|
318
|
+
const dateStr = m[2] ? m[2].trim() : '';
|
|
319
|
+
if (dateStr) extra.push(normalizeDate(dateStr));
|
|
208
320
|
return { version: m[1], extra };
|
|
209
321
|
},
|
|
210
322
|
// expect version 5.45.4
|
|
@@ -232,7 +344,7 @@ const VERSION_PARSERS = {
|
|
|
232
344
|
const extra = m[2] ? m[2].trim().split(/\s+/) : [];
|
|
233
345
|
return { version: m[1], extra };
|
|
234
346
|
},
|
|
235
|
-
// Lean (version 4.29.0, x86_64-unknown-linux-gnu, commit abc123, Release)
|
|
347
|
+
// Lean (version 4.29.0, x86_64-unknown-linux-gnu, commit abc123, Release) → strip arch/Release
|
|
236
348
|
lean: raw => {
|
|
237
349
|
const m = raw.match(/version\s+([\d.]+)(?:,\s*(.+?))\)?$/);
|
|
238
350
|
if (!m) return null;
|
|
@@ -240,7 +352,13 @@ const VERSION_PARSERS = {
|
|
|
240
352
|
? m[2]
|
|
241
353
|
.split(',')
|
|
242
354
|
.map(s => s.trim().replace(/\)$/, ''))
|
|
243
|
-
.filter(
|
|
355
|
+
.filter(s => {
|
|
356
|
+
if (!s) return false;
|
|
357
|
+
// Strip arch patterns and "Release"
|
|
358
|
+
if (/^\w+[-_]\w+[-_]\w+[-_]\w+$/.test(s)) return false;
|
|
359
|
+
if (s === 'Release') return false;
|
|
360
|
+
return true;
|
|
361
|
+
})
|
|
244
362
|
: [];
|
|
245
363
|
return { version: m[1], extra };
|
|
246
364
|
},
|
|
@@ -268,7 +386,7 @@ const VERSION_PARSERS = {
|
|
|
268
386
|
if (!m) return null;
|
|
269
387
|
return { version: m[1], extra: [] };
|
|
270
388
|
},
|
|
271
|
-
// deno 2.7.9 (stable, release, x86_64-unknown-linux-gnu)
|
|
389
|
+
// deno 2.7.9 (stable, release, x86_64-unknown-linux-gnu) → keep only channel (stable)
|
|
272
390
|
deno: raw => {
|
|
273
391
|
const m = raw.match(/^deno\s+([\d.]+)\s*(?:\(([^)]+)\))?/);
|
|
274
392
|
if (!m) return null;
|
|
@@ -276,7 +394,7 @@ const VERSION_PARSERS = {
|
|
|
276
394
|
? m[2]
|
|
277
395
|
.split(',')
|
|
278
396
|
.map(s => s.trim())
|
|
279
|
-
.filter(
|
|
397
|
+
.filter(s => s && s !== 'release' && !s.includes('-') && !s.includes('/'))
|
|
280
398
|
: [];
|
|
281
399
|
return { version: m[1], extra };
|
|
282
400
|
},
|
|
@@ -304,11 +422,11 @@ const VERSION_PARSERS = {
|
|
|
304
422
|
if (!m) return null;
|
|
305
423
|
return { version: m[1], extra: [] };
|
|
306
424
|
},
|
|
307
|
-
// 2.1.87 (Claude Code)
|
|
425
|
+
// 2.1.87 (Claude Code) → strip redundant product name
|
|
308
426
|
claudeCode: raw => {
|
|
309
|
-
const m = raw.match(/([\d.]+)
|
|
427
|
+
const m = raw.match(/([\d.]+)/);
|
|
310
428
|
if (!m) return null;
|
|
311
|
-
return { version: m[1], extra:
|
|
429
|
+
return { version: m[1], extra: [] };
|
|
312
430
|
},
|
|
313
431
|
// GitHub Copilot CLI 1.0.14.\nRun 'copilot update'...
|
|
314
432
|
copilot: raw => {
|
|
@@ -342,25 +460,26 @@ const VERSION_PARSERS = {
|
|
|
342
460
|
if (!m) return null;
|
|
343
461
|
return { version: m[1], extra: [] };
|
|
344
462
|
},
|
|
345
|
-
// This is Zip 3.0 (July 5th 2008), by Info-ZIP.
|
|
463
|
+
// This is Zip 3.0 (July 5th 2008), by Info-ZIP. → normalize date
|
|
346
464
|
zip: raw => {
|
|
347
465
|
const m = raw.match(/Zip\s+([\d.]+)\s*(?:\(([^)]+)\))?/);
|
|
348
466
|
if (!m) return null;
|
|
349
|
-
return { version: m[1], extra: m[2] ? [m[2]] : [] };
|
|
467
|
+
return { version: m[1], extra: m[2] ? [normalizeDate(m[2])] : [] };
|
|
350
468
|
},
|
|
351
|
-
// UnZip 6.00 of 20 April 2009, by Debian.
|
|
469
|
+
// UnZip 6.00 of 20 April 2009, by Debian. → normalize date
|
|
352
470
|
unzip: raw => {
|
|
353
471
|
const m = raw.match(/UnZip\s+([\d.]+)\s*(?:of\s+([^,]+))?/);
|
|
354
472
|
if (!m) return null;
|
|
355
|
-
return { version: m[1], extra: m[2] ? [m[2].trim()] : [] };
|
|
473
|
+
return { version: m[1], extra: m[2] ? [normalizeDate(m[2].trim())] : [] };
|
|
356
474
|
},
|
|
357
|
-
// ii xvfb 2:21.1.12-1ubuntu1.5 amd64 Virtual Framebuffer...
|
|
475
|
+
// ii xvfb 2:21.1.12-1ubuntu1.5 amd64 Virtual Framebuffer... → base version only
|
|
358
476
|
xvfb: raw => {
|
|
359
477
|
// dpkg output format
|
|
360
478
|
const dpkg = raw.match(/^ii\s+xvfb\s+(\S+)/);
|
|
361
479
|
if (dpkg) {
|
|
362
480
|
// Strip epoch (e.g. "2:21.1.12-1ubuntu1.5" -> "21.1.12-1ubuntu1.5")
|
|
363
|
-
|
|
481
|
+
// Then strip distro suffix (e.g. "21.1.12-1ubuntu1.5" -> "21.1.12")
|
|
482
|
+
const ver = dpkg[1].replace(/^\d+:/, '').replace(/-.*$/, '');
|
|
364
483
|
return { version: ver, extra: [] };
|
|
365
484
|
}
|
|
366
485
|
// X.Org X Server version output (if it ever works)
|
|
@@ -559,6 +678,95 @@ async function executeVersionCommand(cmdDef, verbose) {
|
|
|
559
678
|
return { key: cmdDef.key, value: result };
|
|
560
679
|
}
|
|
561
680
|
|
|
681
|
+
/**
|
|
682
|
+
* Map of process.arch values to human-friendly architecture names
|
|
683
|
+
*/
|
|
684
|
+
const ARCH_NAMES = {
|
|
685
|
+
x64: 'AMD64 (x86-64)',
|
|
686
|
+
arm64: 'ARM64 (aarch64)',
|
|
687
|
+
arm: 'ARM32',
|
|
688
|
+
ia32: 'x86 (IA-32)',
|
|
689
|
+
mips: 'MIPS',
|
|
690
|
+
mipsel: 'MIPS (LE)',
|
|
691
|
+
ppc64: 'PowerPC 64',
|
|
692
|
+
s390x: 's390x',
|
|
693
|
+
riscv64: 'RISC-V 64',
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Detect detailed platform information: environment type, OS, architecture, kernel.
|
|
698
|
+
* @param {boolean} verbose - Enable verbose logging
|
|
699
|
+
* @returns {Promise<{environment: string, arch: string, os: string, kernel: string}>}
|
|
700
|
+
*/
|
|
701
|
+
async function detectPlatformInfo(verbose) {
|
|
702
|
+
const info = { environment: '', arch: '', os: '', kernel: '' };
|
|
703
|
+
|
|
704
|
+
// Architecture
|
|
705
|
+
info.arch = ARCH_NAMES[process.arch] || process.arch;
|
|
706
|
+
|
|
707
|
+
// Kernel
|
|
708
|
+
const uname = await execCommandAsync('uname -r 2>/dev/null');
|
|
709
|
+
if (uname) {
|
|
710
|
+
info.kernel = `Linux ${uname}`;
|
|
711
|
+
} else if (process.platform === 'darwin') {
|
|
712
|
+
const darwinVer = await execCommandAsync('uname -r 2>/dev/null');
|
|
713
|
+
info.kernel = darwinVer ? `Darwin ${darwinVer}` : 'Darwin';
|
|
714
|
+
} else if (process.platform === 'win32') {
|
|
715
|
+
info.kernel = 'Windows NT';
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// OS detection
|
|
719
|
+
if (process.platform === 'linux') {
|
|
720
|
+
// Try /etc/os-release for distro info
|
|
721
|
+
const osRelease = await execCommandAsync('cat /etc/os-release 2>/dev/null');
|
|
722
|
+
if (osRelease) {
|
|
723
|
+
const nameMatch = osRelease.match(/^PRETTY_NAME="?([^"\n]+)"?/m);
|
|
724
|
+
if (nameMatch) {
|
|
725
|
+
info.os = nameMatch[1];
|
|
726
|
+
} else {
|
|
727
|
+
const idMatch = osRelease.match(/^ID="?([^"\n]+)"?/m);
|
|
728
|
+
const versionMatch = osRelease.match(/^VERSION_ID="?([^"\n]+)"?/m);
|
|
729
|
+
if (idMatch) {
|
|
730
|
+
info.os = versionMatch ? `${idMatch[1]} ${versionMatch[1]}` : idMatch[1];
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
if (!info.os) info.os = 'Linux';
|
|
735
|
+
} else if (process.platform === 'darwin') {
|
|
736
|
+
const swVers = await execCommandAsync('sw_vers -productVersion 2>/dev/null');
|
|
737
|
+
info.os = swVers ? `macOS ${swVers}` : 'macOS';
|
|
738
|
+
} else if (process.platform === 'win32') {
|
|
739
|
+
info.os = 'Windows';
|
|
740
|
+
} else {
|
|
741
|
+
info.os = process.platform;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// Environment detection: docker container, VM, or host
|
|
745
|
+
const isDocker = await execCommandAsync('cat /proc/1/cgroup 2>/dev/null | grep -qi docker && echo docker || test -f /.dockerenv && echo docker || echo no');
|
|
746
|
+
if (isDocker && isDocker.trim() === 'docker') {
|
|
747
|
+
info.environment = 'docker container';
|
|
748
|
+
} else {
|
|
749
|
+
// Check for VM/hypervisor
|
|
750
|
+
const systemdDetect = await execCommandAsync('systemd-detect-virt 2>/dev/null');
|
|
751
|
+
if (systemdDetect && systemdDetect !== 'none') {
|
|
752
|
+
info.environment = `virtual machine (${systemdDetect})`;
|
|
753
|
+
} else {
|
|
754
|
+
const dmi = await execCommandAsync('cat /sys/class/dmi/id/product_name 2>/dev/null');
|
|
755
|
+
if (dmi && /virtual|vmware|kvm|qemu|hyper-v|xen|bochs/i.test(dmi)) {
|
|
756
|
+
info.environment = `virtual machine`;
|
|
757
|
+
} else {
|
|
758
|
+
info.environment = 'host machine';
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
if (verbose) {
|
|
764
|
+
console.log(`[VERBOSE] Platform detection: ${JSON.stringify(info)}`);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
return info;
|
|
768
|
+
}
|
|
769
|
+
|
|
562
770
|
/**
|
|
563
771
|
* Get comprehensive version information for all components
|
|
564
772
|
* Uses Promise.all for parallel execution (issue #1320)
|
|
@@ -595,12 +803,16 @@ export async function getVersionInfo(verbose = false, processVersion = null) {
|
|
|
595
803
|
console.log(`[VERBOSE] Node.js version: ${versions.node}`);
|
|
596
804
|
}
|
|
597
805
|
|
|
598
|
-
// Platform information
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
versions.
|
|
806
|
+
// Platform information — detailed detection
|
|
807
|
+
const platformInfo = await detectPlatformInfo(verbose);
|
|
808
|
+
versions.platformEnvironment = platformInfo.environment;
|
|
809
|
+
versions.platformArch = platformInfo.arch;
|
|
810
|
+
versions.platformOs = platformInfo.os;
|
|
811
|
+
versions.platformKernel = platformInfo.kernel;
|
|
812
|
+
// Keep legacy field for backward compat
|
|
813
|
+
versions.platform = platformInfo.os;
|
|
602
814
|
if (verbose) {
|
|
603
|
-
console.log(`[VERBOSE] Platform:
|
|
815
|
+
console.log(`[VERBOSE] Platform: env=${platformInfo.environment}, arch=${platformInfo.arch}, os=${platformInfo.os}, kernel=${platformInfo.kernel}`);
|
|
604
816
|
}
|
|
605
817
|
|
|
606
818
|
// Check if process version differs from installed version (restart warning)
|
|
@@ -717,8 +929,12 @@ export async function getVersionInfo(verbose = false, processVersion = null) {
|
|
|
717
929
|
screen: versions.screen,
|
|
718
930
|
xvfb: versions.xvfb,
|
|
719
931
|
|
|
720
|
-
// Platform
|
|
932
|
+
// Platform (detailed)
|
|
721
933
|
platform: versions.platform,
|
|
934
|
+
platformEnvironment: versions.platformEnvironment,
|
|
935
|
+
platformArch: versions.platformArch,
|
|
936
|
+
platformOs: versions.platformOs,
|
|
937
|
+
platformKernel: versions.platformKernel,
|
|
722
938
|
},
|
|
723
939
|
// Performance metrics
|
|
724
940
|
gatherTimeMs: Date.now() - startTime,
|
|
@@ -941,7 +1157,7 @@ export function formatVersionMessage(versions) {
|
|
|
941
1157
|
|
|
942
1158
|
if (cppLines.length > 0) {
|
|
943
1159
|
lines.push('');
|
|
944
|
-
lines.push('*🔨 C
|
|
1160
|
+
lines.push('*🔨 C, C++, Assembly*');
|
|
945
1161
|
lines.push(...cppLines);
|
|
946
1162
|
}
|
|
947
1163
|
|
|
@@ -997,11 +1213,22 @@ export function formatVersionMessage(versions) {
|
|
|
997
1213
|
lines.push(...toolLines);
|
|
998
1214
|
}
|
|
999
1215
|
|
|
1000
|
-
// === Platform ===
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1216
|
+
// === Platform (detailed) ===
|
|
1217
|
+
{
|
|
1218
|
+
const platformLines = [];
|
|
1219
|
+
if (versions.platformEnvironment) platformLines.push(`• Environment: \`${versions.platformEnvironment}\``);
|
|
1220
|
+
if (versions.platformArch) platformLines.push(`• Architecture: \`${versions.platformArch}\``);
|
|
1221
|
+
if (versions.platformOs) platformLines.push(`• OS: \`${versions.platformOs}\``);
|
|
1222
|
+
if (versions.platformKernel) platformLines.push(`• Kernel: \`${versions.platformKernel}\``);
|
|
1223
|
+
// Fallback to legacy single-line format
|
|
1224
|
+
if (platformLines.length === 0 && versions.platform) {
|
|
1225
|
+
platformLines.push(`• System: \`${versions.platform}\``);
|
|
1226
|
+
}
|
|
1227
|
+
if (platformLines.length > 0) {
|
|
1228
|
+
lines.push('');
|
|
1229
|
+
lines.push('*💻 Platform*');
|
|
1230
|
+
lines.push(...platformLines);
|
|
1231
|
+
}
|
|
1005
1232
|
}
|
|
1006
1233
|
|
|
1007
1234
|
return lines.join('\n');
|
|
@@ -1011,4 +1238,5 @@ export default {
|
|
|
1011
1238
|
getVersionInfo,
|
|
1012
1239
|
formatVersionMessage,
|
|
1013
1240
|
parseVersion,
|
|
1241
|
+
normalizeDate,
|
|
1014
1242
|
};
|