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