1
+ import * as os from 'os' ;
2
+ import { promises as fs } from 'fs'
3
+ import { promisify } from 'util' ;
4
+ import { exec } from 'child_process' ;
5
+ const execAsync = promisify ( exec ) ;
6
+
7
+ import { logError } from './errors' ;
8
+
9
+ export async function getDeviceDetails ( ) {
10
+ const [
11
+ realArch ,
12
+ osDetails
13
+ ] = await Promise . all ( [
14
+ getRealArch ( ) ,
15
+ getOsDetails ( )
16
+ ] ) ;
17
+
18
+ return {
19
+ ...osDetails ,
20
+ runtimeArch : os . arch ( ) ,
21
+ realArch : realArch
22
+ }
23
+ }
24
+
25
+ const majorMinorOnly = < I extends string | undefined > ( input : I ) : I =>
26
+ input ?. split ( '.' ) . slice ( 0 , 2 ) . join ( '.' ) as I ;
27
+
28
+ async function getOsDetails ( ) {
29
+ const rawPlatform = os . platform ( ) ;
30
+ if ( rawPlatform == 'linux' ) {
31
+ return await getLinuxOsDetails ( ) ;
32
+ } else if ( rawPlatform === 'win32' ) {
33
+ // For Windows, the version numbers are oddly precise and weird. We simplify:
34
+ return {
35
+ platform : rawPlatform ,
36
+ version : getWindowsVersion ( )
37
+ } ;
38
+ } else {
39
+ return {
40
+ platform : rawPlatform ,
41
+ release : majorMinorOnly ( os . release ( ) )
42
+ }
43
+ }
44
+ }
45
+
46
+ function getWindowsVersion ( ) {
47
+ const rawVersion = os . release ( ) ;
48
+ try {
49
+ if ( rawVersion . startsWith ( '10.0.' ) ) {
50
+ // Windows 10.0.x < 22000 is Windows 10, >= 22000 is Windows 11
51
+ const buildNumber = parseInt ( rawVersion . slice ( '10.0.' . length ) , 10 ) ;
52
+ if ( isNaN ( buildNumber ) ) return 'Unknown' ;
53
+ else if ( buildNumber >= 22000 ) return '11' ;
54
+ else return '10'
55
+ } else {
56
+ // Other versions - e.g. 6.3 is Windows 8.1
57
+ return majorMinorOnly ( rawVersion ) ;
58
+ }
59
+ } catch ( e ) {
60
+ logError ( `Failed to detect windows version: ${ e . message || e } ` ) ;
61
+ return 'Unknown' ;
62
+ }
63
+ }
64
+
65
+ async function getLinuxOsDetails ( ) {
66
+ // For Linux, there's a relatively small number of users with a lot of variety.
67
+ // We do a bit more digging, to try to get meaningful data (e.g. distro) and
68
+ // drop unnecessary fingerprinting factors (kernel patch version & variants etc). End
69
+ // result is e.g. "ubuntu + 20.04" (just major+minor, for big distros supporting
70
+ // /etc/os-release) or "linux + 6.5" (just kernel major+minor).
71
+ try {
72
+ const osReleaseDetails = await fs . readFile ( '/etc/os-release' , 'utf8' )
73
+ . catch ( ( ) => '' ) ;
74
+ const osRelease = osReleaseDetails . split ( '\n' ) . reduce ( ( acc , line ) => {
75
+ const [ key , value ] = line . split ( '=' ) ;
76
+ if ( key && value ) {
77
+ acc [ key ] = value . replace ( / ( ^ " ) | ( " $ ) / g, '' ) ;
78
+ }
79
+ return acc ;
80
+ } , { } as { [ key : string ] : string | undefined } ) ;
81
+
82
+ return {
83
+ platform : osRelease [ 'ID' ] || osRelease [ 'NAME' ] || 'linux' ,
84
+ version : majorMinorOnly ( osRelease [ 'VERSION_ID' ] || os . release ( ) )
85
+ } ;
86
+ } catch ( e ) {
87
+ logError ( `Failed to detect Linux version: ${ e . message } ` ) ;
88
+ return {
89
+ platform : 'linux' ,
90
+ version : majorMinorOnly ( os . release ( ) )
91
+ } ;
92
+ }
93
+ }
94
+
95
+ // Detect the 'real' architecture of the system. We're concerned here with detecting the real arch
96
+ // despite emulation here, to help with launch subprocs. Not too worried about x86 vs x64.
97
+ async function getRealArch ( ) {
98
+ try {
99
+ switch ( process . platform ) {
100
+ case 'darwin' :
101
+ const { stdout : armCheck } = await execAsync ( 'sysctl -n hw.optional.arm64' )
102
+ . catch ( ( e : any ) => {
103
+ const output = e . message + e . stdout + e . stderr ;
104
+ // This id may not be available:
105
+ if ( output ?. includes ?.( "unknown oid" ) ) return { stdout : "0" } ;
106
+ else throw e ;
107
+ } ) ;
108
+ if ( armCheck . trim ( ) === '1' ) {
109
+ return 'arm64' ;
110
+ }
111
+
112
+ case 'linux' :
113
+ const { stdout : cpuInfo } = await execAsync ( 'cat /proc/cpuinfo' ) ;
114
+ const lcCpuInfo = cpuInfo . toLowerCase ( ) ;
115
+ if ( lcCpuInfo . includes ( 'aarch64' ) || lcCpuInfo . includes ( 'arm64' ) ) {
116
+ return 'arm64' ;
117
+ }
118
+
119
+ case 'win32' :
120
+ const arch = process . env . PROCESSOR_ARCHITEW6432 || process . env . PROCESSOR_ARCHITECTURE ;
121
+ if ( arch ?. toLowerCase ( ) === 'arm64' ) {
122
+ return 'arm64' ;
123
+ }
124
+ }
125
+ } catch ( e ) {
126
+ console . warn ( `Error querying system arch: ${ e . message } ` ) ;
127
+ logError ( e ) ;
128
+ }
129
+
130
+ return os . arch ( ) ;
131
+ }
0 commit comments