From 4848819666d6af41f74393046f3b48ce8c9871f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Wed, 5 Mar 2025 14:12:17 +0100 Subject: [PATCH 1/2] Refactor linux implementation and tests --- packages/get-os-info/src/get-os-info.spec.ts | 37 ++++++++++----- packages/get-os-info/src/get-os-info.ts | 50 ++++++++++---------- 2 files changed, 49 insertions(+), 38 deletions(-) diff --git a/packages/get-os-info/src/get-os-info.spec.ts b/packages/get-os-info/src/get-os-info.spec.ts index f0b6ad6e..1ece9ac1 100644 --- a/packages/get-os-info/src/get-os-info.spec.ts +++ b/packages/get-os-info/src/get-os-info.spec.ts @@ -2,16 +2,11 @@ import { expect } from 'chai'; import os from 'os'; import { promises as fs } from 'fs'; -import { getOsInfo } from './get-os-info'; +import { getOsInfo, parseDarwinInfo, parseLinuxInfo } from './get-os-info'; describe('get-os-info', function () { - let osInfo; - beforeEach(async function () { - osInfo = await getOsInfo(); - }); - - it('returns info from "os" module', function () { - const { os_arch, os_type, os_version, os_release } = osInfo; + it('returns info from "os" module', async function () { + const { os_arch, os_type, os_version, os_release } = await getOsInfo(); expect({ os_arch, os_type, os_version, os_release }).to.deep.equal({ os_arch: os.arch(), os_type: os.type(), @@ -21,13 +16,31 @@ describe('get-os-info', function () { }); describe('on linux', function () { - beforeEach(function () { + it('parses os-release file', function () { + // Copied from https://manpages.ubuntu.com/manpages/focal/man5/os-release.5.html#example + const fixture = ` + NAME=Fedora + VERSION="17 (Beefy Miracle)" + ID=fedora + VERSION_ID=17 + PRETTY_NAME="Fedora 17 (Beefy Miracle)" + ANSI_COLOR="0;34" + CPE_NAME="cpe:/o:fedoraproject:fedora:17" + HOME_URL="https://fedoraproject.org/" + BUG_REPORT_URL="https://bugzilla.redhat.com/" + `; + + expect(parseLinuxInfo(fixture)).to.deep.equal({ + os_linux_dist: 'fedora', + os_linux_release: '17', + }); + }); + + it('returns info from /etc/releases', async function () { if (process.platform !== 'linux') { this.skip(); } - }); - it('returns info from /etc/releases', async function () { const etcRelease = await fs.readFile('/etc/os-release', 'utf-8'); const releaseKv = etcRelease @@ -47,7 +60,7 @@ describe('get-os-info', function () { expect(distroId).to.match(/^(rhel|ubuntu|debian)$/); expect(distroVer).to.match(/^\d+/); - const { os_linux_dist, os_linux_release } = osInfo; + const { os_linux_dist, os_linux_release } = await getOsInfo(); expect({ os_linux_dist, os_linux_release }).to.deep.equal({ os_linux_dist: distroId, os_linux_release: distroVer, diff --git a/packages/get-os-info/src/get-os-info.ts b/packages/get-os-info/src/get-os-info.ts index 67585f50..7a5147a7 100644 --- a/packages/get-os-info/src/get-os-info.ts +++ b/packages/get-os-info/src/get-os-info.ts @@ -1,46 +1,44 @@ import os from 'os'; import { promises as fs } from 'fs'; +type LinuxInfo = { + os_linux_dist: string; + os_linux_release: string; +}; + type OsInfo = { os_type: string; os_version: string; os_arch: string; os_release: string; - os_linux_dist?: string; - os_linux_release?: string; -}; +} & Partial; -async function getLinuxInfo(): Promise<{ - os_linux_dist: string; - os_linux_release: string; -}> { +async function getLinuxInfo(): Promise { try { const releaseFile = '/etc/os-release'; const etcRelease = await fs.readFile(releaseFile, 'utf-8'); - - const osReleaseEntries = etcRelease - .split('\n') - .map((l) => l.trim()) - .filter(Boolean) - .map((l) => l.split('=')) - .map(([k, v]) => [ - k, - (v || '').replace(/^["']/, '').replace(/["']$/, ''), - ]); - - const osReleaseKv = Object.fromEntries(osReleaseEntries); - + return parseLinuxInfo(etcRelease); + } catch (e) { return { - os_linux_dist: osReleaseKv.ID || 'unknown', - os_linux_release: osReleaseKv.VERSION_ID || 'unknown', + os_linux_dist: 'unknown', + os_linux_release: 'unknown', }; - } catch (e) { - // couldn't read /etc/os-release } +} + +export function parseLinuxInfo(etcRelease: string): LinuxInfo { + const osReleaseEntries = etcRelease + .split('\n') + .map((l) => l.trim()) + .filter(Boolean) + .map((l) => l.split('=')) + .map(([k, v]) => [k, (v || '').replace(/^["']/, '').replace(/["']$/, '')]); + + const osReleaseKv = Object.fromEntries(osReleaseEntries); return { - os_linux_dist: 'unknown', - os_linux_release: 'unknown', + os_linux_dist: osReleaseKv.ID || 'unknown', + os_linux_release: osReleaseKv.VERSION_ID || 'unknown', }; } From b471349d1ecffe98dfa912a0e8382560e0d657b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Wed, 5 Mar 2025 14:12:32 +0100 Subject: [PATCH 2/2] Add darwin implementation --- packages/get-os-info/src/get-os-info.spec.ts | 55 ++++++++++++++++++++ packages/get-os-info/src/get-os-info.ts | 48 ++++++++++++++++- 2 files changed, 102 insertions(+), 1 deletion(-) diff --git a/packages/get-os-info/src/get-os-info.spec.ts b/packages/get-os-info/src/get-os-info.spec.ts index 1ece9ac1..00beb2dc 100644 --- a/packages/get-os-info/src/get-os-info.spec.ts +++ b/packages/get-os-info/src/get-os-info.spec.ts @@ -67,4 +67,59 @@ describe('get-os-info', function () { }); }); }); + + describe('on darwin', function () { + it('parses the SystemVersion.plist file', function () { + const fixture = ` + + + + + BuildID + 2B3829A8-E319-11EF-8892-025514DE0AB1 + ProductBuildVersion + 24D70 + ProductCopyright + 1983-2025 Apple Inc. + ProductName + macOS + ProductUserVisibleVersion + 15.3.1 + ProductVersion + 15.3.1 + iOSSupportVersion + 18.3 + + + `; + + expect(parseDarwinInfo(fixture)).to.deep.equal({ + os_darwin_product_name: 'macOS', + os_darwin_product_version: '15.3.1', + os_darwin_product_build_version: '24D70', + }); + }); + + it('returns info from /System/Library/CoreServices/SystemVersion.plist', async function () { + if (process.platform !== 'darwin') { + this.skip(); + } + + const systemVersionPlist = await fs.readFile( + '/System/Library/CoreServices/SystemVersion.plist', + 'utf-8' + ); + + const { + os_darwin_product_name, + os_darwin_product_version, + os_darwin_product_build_version, + } = await getOsInfo(); + + // Instead of reimplementing the parser, we simply check that the values are present in the original file + expect(systemVersionPlist).contains(os_darwin_product_name); + expect(systemVersionPlist).contains(os_darwin_product_version); + expect(systemVersionPlist).contains(os_darwin_product_build_version); + }); + }); }); diff --git a/packages/get-os-info/src/get-os-info.ts b/packages/get-os-info/src/get-os-info.ts index 7a5147a7..818f05b9 100644 --- a/packages/get-os-info/src/get-os-info.ts +++ b/packages/get-os-info/src/get-os-info.ts @@ -6,12 +6,19 @@ type LinuxInfo = { os_linux_release: string; }; +type DarwinInfo = { + os_darwin_product_name: string; + os_darwin_product_version: string; + os_darwin_product_build_version: string; +}; + type OsInfo = { os_type: string; os_version: string; os_arch: string; os_release: string; -} & Partial; +} & Partial & + Partial; async function getLinuxInfo(): Promise { try { @@ -42,6 +49,44 @@ export function parseLinuxInfo(etcRelease: string): LinuxInfo { }; } +async function getDarwinInfo(): Promise { + try { + const systemVersionPlistPath = + '/System/Library/CoreServices/SystemVersion.plist'; + const systemVersionPlist = await fs.readFile( + systemVersionPlistPath, + 'utf-8' + ); + return parseDarwinInfo(systemVersionPlist); + } catch (e) { + return { + os_darwin_product_name: 'unknown', + os_darwin_product_version: 'unknown', + os_darwin_product_build_version: 'unknown', + }; + } +} + +export function parseDarwinInfo(systemVersionPlist: string): DarwinInfo { + const match = systemVersionPlist.matchAll( + /(?[^<]+)<\/key>\s*(?[^<]+)<\/string>/gm + ); + + const { + ProductName: os_darwin_product_name = 'unknown', + ProductVersion: os_darwin_product_version = 'unknown', + ProductBuildVersion: os_darwin_product_build_version = 'unknown', + } = Object.fromEntries( + Array.from(match).map((m) => [m.groups?.key, m.groups?.value]) + ); + + return { + os_darwin_product_name, + os_darwin_product_version, + os_darwin_product_build_version, + }; +} + export async function getOsInfo(): Promise { return { os_type: os.type(), @@ -49,5 +94,6 @@ export async function getOsInfo(): Promise { os_arch: os.arch(), os_release: os.release(), ...(process.platform === 'linux' ? await getLinuxInfo() : {}), + ...(process.platform === 'darwin' ? await getDarwinInfo() : {}), }; }