From a533dd96072bb685105c96329ded9628b3469a23 Mon Sep 17 00:00:00 2001 From: Jack Lavigne Date: Wed, 30 Sep 2026 20:10:58 +0200 Subject: [PATCH] chore: replace regedit with handrolled utility --- package-lock.json | 140 +++-------------------------------------- package.json | 4 +- src/utils/registry.ts | 53 +++++++++++++--- tests/registry.test.ts | 50 +++++++++++++++ 4 files changed, 102 insertions(+), 145 deletions(-) create mode 100644 tests/registry.test.ts diff --git a/package-lock.json b/package-lock.json index a5e46dc..c44313d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,7 @@ "license": "LGPL-3.0-or-later", "dependencies": { "debug": "^4.3.4", - "ini": "^2.0.0", - "regedit": "^5.1.1" + "ini": "^2.0.0" }, "devDependencies": { "@eslint/js": "^10.0.1", @@ -20,6 +19,7 @@ "@types/jest": "^29.2.4", "@types/node": "^25.6.2", "eslint": "^10.3.0", + "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.5", "globals": "^17.6.0", "husky": "^9.1.7", @@ -2193,11 +2193,6 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2412,8 +2407,7 @@ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, - "optional": true, - "peer": true, + "license": "MIT", "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -3031,11 +3025,6 @@ "url": "https://github.com/sponsors/typicode" } }, - "node_modules/if-async": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/if-async/-/if-async-3.7.4.tgz", - "integrity": "sha1-VYaN6wCT08Z79xZudFNT+5vLIaI=" - }, "node_modules/ignore": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", @@ -3086,7 +3075,8 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "node_modules/ini": { "version": "2.0.0", @@ -3177,11 +3167,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -4814,28 +4799,6 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, - "node_modules/readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "node_modules/regedit": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/regedit/-/regedit-5.1.1.tgz", - "integrity": "sha512-NJ2OZ14czryt3RiQyMM/nptaiu0bF1CT+YVw0PBziMrLJyq8pdmIAvupi9E+NPWJLDJtyHDoKs5g5wcbP8ANIg==", - "dependencies": { - "debug": "^4.1.0", - "if-async": "^3.7.4", - "stream-slicer": "0.0.6", - "through2": "^0.6.3" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -5083,16 +5046,6 @@ "node": ">=10" } }, - "node_modules/stream-slicer": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/stream-slicer/-/stream-slicer-0.0.6.tgz", - "integrity": "sha1-+GsqxcJEC3oKh7cfM2ZcB4gEYTg=" - }, - "node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - }, "node_modules/string-argv": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", @@ -5225,15 +5178,6 @@ "node": ">=8" } }, - "node_modules/through2": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", - "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", - "dependencies": { - "readable-stream": ">=1.0.33-1 <1.1.0-0", - "xtend": ">=4.0.0 <4.1.0-0" - } - }, "node_modules/tinyexec": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", @@ -5734,14 +5678,6 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "engines": { - "node": ">=0.4" - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -7407,11 +7343,6 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, "cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -7633,8 +7564,6 @@ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, - "optional": true, - "peer": true, "requires": {} }, "eslint-plugin-prettier": { @@ -7977,11 +7906,6 @@ "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", "dev": true }, - "if-async": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/if-async/-/if-async-3.7.4.tgz", - "integrity": "sha1-VYaN6wCT08Z79xZudFNT+5vLIaI=" - }, "ignore": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", @@ -8017,7 +7941,8 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "ini": { "version": "2.0.0", @@ -8078,11 +8003,6 @@ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -9304,28 +9224,6 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "regedit": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/regedit/-/regedit-5.1.1.tgz", - "integrity": "sha512-NJ2OZ14czryt3RiQyMM/nptaiu0bF1CT+YVw0PBziMrLJyq8pdmIAvupi9E+NPWJLDJtyHDoKs5g5wcbP8ANIg==", - "requires": { - "debug": "^4.1.0", - "if-async": "^3.7.4", - "stream-slicer": "0.0.6", - "through2": "^0.6.3" - } - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -9495,16 +9393,6 @@ "escape-string-regexp": "^2.0.0" } }, - "stream-slicer": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/stream-slicer/-/stream-slicer-0.0.6.tgz", - "integrity": "sha1-+GsqxcJEC3oKh7cfM2ZcB4gEYTg=" - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - }, "string-argv": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", @@ -9594,15 +9482,6 @@ "minimatch": "^3.0.4" } }, - "through2": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", - "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", - "requires": { - "readable-stream": ">=1.0.33-1 <1.1.0-0", - "xtend": ">=4.0.0 <4.1.0-0" - } - }, "tinyexec": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", @@ -9913,11 +9792,6 @@ "signal-exit": "^3.0.7" } }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" - }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index ab1e6bb..c73a807 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "@types/jest": "^29.2.4", "@types/node": "^25.6.2", "eslint": "^10.3.0", + "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.5", "jiti": "latest", "globals": "^17.6.0", @@ -71,8 +72,7 @@ }, "dependencies": { "debug": "^4.3.4", - "ini": "^2.0.0", - "regedit": "^5.1.1" + "ini": "^2.0.0" }, "engines": { "node": ">=20.17.0", diff --git a/src/utils/registry.ts b/src/utils/registry.ts index 445c3b5..12e55e7 100644 --- a/src/utils/registry.ts +++ b/src/utils/registry.ts @@ -1,21 +1,54 @@ -import regedit from 'regedit'; +import { execFile } from 'child_process'; +import * as Path from 'path'; + +// Matches a value line from `reg query`, e.g. " Name REG_SZ data". +const VALUE_LINE = /^ {4}(.*?) {4}(REG_[A-Z_]+)(?: {4}(.*))?$/; /** * Returns `undefined` if the key does not exist. */ export function readRegistryValue(key: string, subKey: string): Promise { + if (process.platform !== 'win32') return Promise.resolve(undefined); + + const regExe = Path.join(process.env.SystemRoot ?? 'C:\\Windows', 'System32', 'reg.exe'); + return new Promise((resolve, reject) => { - regedit.list([key], (err, result) => { - if (err) { - reject(new Error(`Failed to read registry value ${key} (${err})`)); - } else { - const values = result[key]?.values ?? {}; - if (subKey in values) { - resolve(values[subKey]!.value as string); + execFile( + regExe, + ['query', key, '/v', subKey], + { windowsHide: true }, + (err, stdout, stderr) => { + if (err) { + // reg exits with 1 when the key or value is missing + if (err.code === 1) { + resolve(undefined); + } else { + reject( + new Error( + `Failed to read registry value ${key} (${stderr.trim() || err.message})` + ) + ); + } } else { - resolve(undefined); + resolve(parseRegQueryOutput(stdout, subKey)); } } - }); + ); }); } + +export function parseRegQueryOutput(stdout: string, valueName: string): string | undefined { + for (const line of stdout.split(/\r?\n/)) { + const match = VALUE_LINE.exec(line); + if (match && match[1]!.toLowerCase() === valueName.toLowerCase()) { + const type = match[2]!; + const data = match[3] ?? ''; + // reg prints DWORD/QWORD as hex, callers expect decimal + if (type === 'REG_DWORD' || type === 'REG_QWORD') { + return BigInt(data).toString(); + } + return data; + } + } + return undefined; +} diff --git a/tests/registry.test.ts b/tests/registry.test.ts new file mode 100644 index 0000000..f7440dc --- /dev/null +++ b/tests/registry.test.ts @@ -0,0 +1,50 @@ +import { parseRegQueryOutput, readRegistryValue } from '../src/utils/registry'; + +const KEY = 'HKEY_CURRENT_USER\\Software\\Microsoft\\Microsoft Games\\Flight Simulator'; + +describe('parseRegQueryOutput', () => { + test('reads a REG_SZ value', () => { + const stdout = `\r\n${KEY}\r\n SimConnect_Port_IPv4 REG_SZ 500\r\n\r\n`; + expect(parseRegQueryOutput(stdout, 'SimConnect_Port_IPv4')).toBe('500'); + }); + + test('converts DWORD hex to decimal', () => { + const stdout = `\r\n${KEY}\r\n SimConnect_Port_IPv4 REG_DWORD 0x800\r\n\r\n`; + expect(parseRegQueryOutput(stdout, 'SimConnect_Port_IPv4')).toBe('2048'); + }); + + test('picks the requested value among several', () => { + const stdout = [ + '', + KEY, + ' SimConnect_Port_IPv6 REG_SZ 501', + ' SimConnect_Port_IPv4 REG_SZ 500', + '', + ].join('\r\n'); + expect(parseRegQueryOutput(stdout, 'SimConnect_Port_IPv4')).toBe('500'); + }); + + test('handles value names with spaces and empty data', () => { + const stdout = `\r\n${KEY}\r\n Some Value REG_SZ \r\n`; + expect(parseRegQueryOutput(stdout, 'Some Value')).toBe(''); + }); + + test('returns undefined when the value is absent', () => { + expect(parseRegQueryOutput(`\r\n${KEY}\r\n`, 'SimConnect_Port_IPv4')).toBeUndefined(); + }); +}); + +describe('readRegistryValue', () => { + const onWindows = process.platform === 'win32' ? test : test.skip; + + onWindows('returns undefined for a missing key', async () => { + await expect( + readRegistryValue('HKCU\\Software\\node-simconnect-does-not-exist', 'X') + ).resolves.toBeUndefined(); + }); + + onWindows('reads an existing value', async () => { + const value = await readRegistryValue('HKCU\\Environment', 'TEMP'); + expect(typeof value).toBe('string'); + }); +});