Skip to content

Commit 73b2103

Browse files
authored
feat(get-os-info): derive Darwin / MacOS specific OS info COMPASS-9079 (#516)
* Refactor linux implementation and tests * Add darwin implementation
1 parent 00c2093 commit 73b2103

File tree

2 files changed

+147
-35
lines changed

2 files changed

+147
-35
lines changed

packages/get-os-info/src/get-os-info.spec.ts

Lines changed: 80 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,11 @@ import { expect } from 'chai';
22
import os from 'os';
33
import { promises as fs } from 'fs';
44

5-
import { getOsInfo } from './get-os-info';
5+
import { getOsInfo, parseDarwinInfo, parseLinuxInfo } from './get-os-info';
66

77
describe('get-os-info', function () {
8-
let osInfo;
9-
beforeEach(async function () {
10-
osInfo = await getOsInfo();
11-
});
12-
13-
it('returns info from "os" module', function () {
14-
const { os_arch, os_type, os_version, os_release } = osInfo;
8+
it('returns info from "os" module', async function () {
9+
const { os_arch, os_type, os_version, os_release } = await getOsInfo();
1510
expect({ os_arch, os_type, os_version, os_release }).to.deep.equal({
1611
os_arch: os.arch(),
1712
os_type: os.type(),
@@ -21,13 +16,31 @@ describe('get-os-info', function () {
2116
});
2217

2318
describe('on linux', function () {
24-
beforeEach(function () {
19+
it('parses os-release file', function () {
20+
// Copied from https://manpages.ubuntu.com/manpages/focal/man5/os-release.5.html#example
21+
const fixture = `
22+
NAME=Fedora
23+
VERSION="17 (Beefy Miracle)"
24+
ID=fedora
25+
VERSION_ID=17
26+
PRETTY_NAME="Fedora 17 (Beefy Miracle)"
27+
ANSI_COLOR="0;34"
28+
CPE_NAME="cpe:/o:fedoraproject:fedora:17"
29+
HOME_URL="https://fedoraproject.org/"
30+
BUG_REPORT_URL="https://bugzilla.redhat.com/"
31+
`;
32+
33+
expect(parseLinuxInfo(fixture)).to.deep.equal({
34+
os_linux_dist: 'fedora',
35+
os_linux_release: '17',
36+
});
37+
});
38+
39+
it('returns info from /etc/releases', async function () {
2540
if (process.platform !== 'linux') {
2641
this.skip();
2742
}
28-
});
2943

30-
it('returns info from /etc/releases', async function () {
3144
const etcRelease = await fs.readFile('/etc/os-release', 'utf-8');
3245

3346
const releaseKv = etcRelease
@@ -47,11 +60,66 @@ describe('get-os-info', function () {
4760
expect(distroId).to.match(/^(rhel|ubuntu|debian)$/);
4861
expect(distroVer).to.match(/^\d+/);
4962

50-
const { os_linux_dist, os_linux_release } = osInfo;
63+
const { os_linux_dist, os_linux_release } = await getOsInfo();
5164
expect({ os_linux_dist, os_linux_release }).to.deep.equal({
5265
os_linux_dist: distroId,
5366
os_linux_release: distroVer,
5467
});
5568
});
5669
});
70+
71+
describe('on darwin', function () {
72+
it('parses the SystemVersion.plist file', function () {
73+
const fixture = `
74+
<?xml version="1.0" encoding="UTF-8"?>
75+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
76+
<plist version="1.0">
77+
<dict>
78+
<key>BuildID</key>
79+
<string>2B3829A8-E319-11EF-8892-025514DE0AB1</string>
80+
<key>ProductBuildVersion</key>
81+
<string>24D70</string>
82+
<key>ProductCopyright</key>
83+
<string>1983-2025 Apple Inc.</string>
84+
<key>ProductName</key>
85+
<string>macOS</string>
86+
<key>ProductUserVisibleVersion</key>
87+
<string>15.3.1</string>
88+
<key>ProductVersion</key>
89+
<string>15.3.1</string>
90+
<key>iOSSupportVersion</key>
91+
<string>18.3</string>
92+
</dict>
93+
</plist>
94+
`;
95+
96+
expect(parseDarwinInfo(fixture)).to.deep.equal({
97+
os_darwin_product_name: 'macOS',
98+
os_darwin_product_version: '15.3.1',
99+
os_darwin_product_build_version: '24D70',
100+
});
101+
});
102+
103+
it('returns info from /System/Library/CoreServices/SystemVersion.plist', async function () {
104+
if (process.platform !== 'darwin') {
105+
this.skip();
106+
}
107+
108+
const systemVersionPlist = await fs.readFile(
109+
'/System/Library/CoreServices/SystemVersion.plist',
110+
'utf-8'
111+
);
112+
113+
const {
114+
os_darwin_product_name,
115+
os_darwin_product_version,
116+
os_darwin_product_build_version,
117+
} = await getOsInfo();
118+
119+
// Instead of reimplementing the parser, we simply check that the values are present in the original file
120+
expect(systemVersionPlist).contains(os_darwin_product_name);
121+
expect(systemVersionPlist).contains(os_darwin_product_version);
122+
expect(systemVersionPlist).contains(os_darwin_product_build_version);
123+
});
124+
});
57125
});
Lines changed: 67 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,89 @@
11
import os from 'os';
22
import { promises as fs } from 'fs';
33

4+
type LinuxInfo = {
5+
os_linux_dist: string;
6+
os_linux_release: string;
7+
};
8+
9+
type DarwinInfo = {
10+
os_darwin_product_name: string;
11+
os_darwin_product_version: string;
12+
os_darwin_product_build_version: string;
13+
};
14+
415
type OsInfo = {
516
os_type: string;
617
os_version: string;
718
os_arch: string;
819
os_release: string;
9-
os_linux_dist?: string;
10-
os_linux_release?: string;
11-
};
20+
} & Partial<LinuxInfo> &
21+
Partial<DarwinInfo>;
1222

13-
async function getLinuxInfo(): Promise<{
14-
os_linux_dist: string;
15-
os_linux_release: string;
16-
}> {
23+
async function getLinuxInfo(): Promise<LinuxInfo> {
1724
try {
1825
const releaseFile = '/etc/os-release';
1926
const etcRelease = await fs.readFile(releaseFile, 'utf-8');
27+
return parseLinuxInfo(etcRelease);
28+
} catch (e) {
29+
return {
30+
os_linux_dist: 'unknown',
31+
os_linux_release: 'unknown',
32+
};
33+
}
34+
}
35+
36+
export function parseLinuxInfo(etcRelease: string): LinuxInfo {
37+
const osReleaseEntries = etcRelease
38+
.split('\n')
39+
.map((l) => l.trim())
40+
.filter(Boolean)
41+
.map((l) => l.split('='))
42+
.map(([k, v]) => [k, (v || '').replace(/^["']/, '').replace(/["']$/, '')]);
2043

21-
const osReleaseEntries = etcRelease
22-
.split('\n')
23-
.map((l) => l.trim())
24-
.filter(Boolean)
25-
.map((l) => l.split('='))
26-
.map(([k, v]) => [
27-
k,
28-
(v || '').replace(/^["']/, '').replace(/["']$/, ''),
29-
]);
44+
const osReleaseKv = Object.fromEntries(osReleaseEntries);
3045

31-
const osReleaseKv = Object.fromEntries(osReleaseEntries);
46+
return {
47+
os_linux_dist: osReleaseKv.ID || 'unknown',
48+
os_linux_release: osReleaseKv.VERSION_ID || 'unknown',
49+
};
50+
}
3251

52+
async function getDarwinInfo(): Promise<DarwinInfo> {
53+
try {
54+
const systemVersionPlistPath =
55+
'/System/Library/CoreServices/SystemVersion.plist';
56+
const systemVersionPlist = await fs.readFile(
57+
systemVersionPlistPath,
58+
'utf-8'
59+
);
60+
return parseDarwinInfo(systemVersionPlist);
61+
} catch (e) {
3362
return {
34-
os_linux_dist: osReleaseKv.ID || 'unknown',
35-
os_linux_release: osReleaseKv.VERSION_ID || 'unknown',
63+
os_darwin_product_name: 'unknown',
64+
os_darwin_product_version: 'unknown',
65+
os_darwin_product_build_version: 'unknown',
3666
};
37-
} catch (e) {
38-
// couldn't read /etc/os-release
3967
}
68+
}
69+
70+
export function parseDarwinInfo(systemVersionPlist: string): DarwinInfo {
71+
const match = systemVersionPlist.matchAll(
72+
/<key>(?<key>[^<]+)<\/key>\s*<string>(?<value>[^<]+)<\/string>/gm
73+
);
74+
75+
const {
76+
ProductName: os_darwin_product_name = 'unknown',
77+
ProductVersion: os_darwin_product_version = 'unknown',
78+
ProductBuildVersion: os_darwin_product_build_version = 'unknown',
79+
} = Object.fromEntries(
80+
Array.from(match).map((m) => [m.groups?.key, m.groups?.value])
81+
);
4082

4183
return {
42-
os_linux_dist: 'unknown',
43-
os_linux_release: 'unknown',
84+
os_darwin_product_name,
85+
os_darwin_product_version,
86+
os_darwin_product_build_version,
4487
};
4588
}
4689

@@ -51,5 +94,6 @@ export async function getOsInfo(): Promise<OsInfo> {
5194
os_arch: os.arch(),
5295
os_release: os.release(),
5396
...(process.platform === 'linux' ? await getLinuxInfo() : {}),
97+
...(process.platform === 'darwin' ? await getDarwinInfo() : {}),
5498
};
5599
}

0 commit comments

Comments
 (0)