From 4f7aeda20b9c4bd195b13b8dc710d9ecd59fcc3b Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Tue, 5 May 2026 15:09:17 -0500 Subject: [PATCH 1/3] build: remove package-json dependency --- package.json | 1 - src/main/electron-types.ts | 21 +++++-- yarn.lock | 121 +------------------------------------ 3 files changed, 17 insertions(+), 126 deletions(-) diff --git a/package.json b/package.json index e8d7fdd853..1214ccd372 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "namor": "^2.0.2", "node-watch": "^0.7.3", "p-debounce": "^2.0.0", - "package-json": "^10.0.1", "parse-env-string": "^1.0.1", "prettier": "^3.6.2", "react": "^16.14.0", diff --git a/src/main/electron-types.ts b/src/main/electron-types.ts index 72df8658dd..0887ab1ab9 100644 --- a/src/main/electron-types.ts +++ b/src/main/electron-types.ts @@ -5,7 +5,6 @@ import { ElectronVersions } from '@electron/fiddle-core'; import { BrowserWindow, IpcMainInvokeEvent, app } from 'electron'; import fs from 'fs-extra'; import watch from 'node-watch'; -import packageJson from 'package-json'; import semver from 'semver'; import { ipcMainManager } from './ipc'; @@ -197,12 +196,22 @@ export class ElectronTypes { ); if (response.status === 404) { - const types = await packageJson('@types/node', { - version: semver.major(version).toString(), - fullMetadata: false, - }); + const url = 'https://registry.npmjs.org/@types%2Fnode'; + const headers: HeadersInit = { + Accept: 'application/vnd.npm.install-v1+json', + }; + const res = await fetch(url, { headers }); + const data = (await res.json()) as { versions: Record }; + + const major = semver.major(version); + const matched = semver.maxSatisfying( + Object.keys(data.versions), + `${major}`, + ); + if (!matched) + throw new Error(`No @types/node version found for ${major}`); - downloadVersion = types.version as string; + downloadVersion = matched; console.log( `falling back to the latest applicable Node.js version type: ${downloadVersion}`, ); diff --git a/yarn.lock b/yarn.lock index 26d9663236..d57db185d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2166,33 +2166,6 @@ __metadata: languageName: node linkType: hard -"@pnpm/config.env-replace@npm:^1.1.0": - version: 1.1.0 - resolution: "@pnpm/config.env-replace@npm:1.1.0" - checksum: 10c0/4cfc4a5c49ab3d0c6a1f196cfd4146374768b0243d441c7de8fa7bd28eaab6290f514b98490472cc65dbd080d34369447b3e9302585e1d5c099befd7c8b5e55f - languageName: node - linkType: hard - -"@pnpm/network.ca-file@npm:^1.0.1": - version: 1.0.2 - resolution: "@pnpm/network.ca-file@npm:1.0.2" - dependencies: - graceful-fs: "npm:4.2.10" - checksum: 10c0/95f6e0e38d047aca3283550719155ce7304ac00d98911e4ab026daedaf640a63bd83e3d13e17c623fa41ac72f3801382ba21260bcce431c14fbbc06430ecb776 - languageName: node - linkType: hard - -"@pnpm/npm-conf@npm:^3.0.2": - version: 3.0.2 - resolution: "@pnpm/npm-conf@npm:3.0.2" - dependencies: - "@pnpm/config.env-replace": "npm:^1.1.0" - "@pnpm/network.ca-file": "npm:^1.0.1" - config-chain: "npm:^1.1.11" - checksum: 10c0/50026ae4cac7d5d055d4dd4b2886fbc41964db6179406cf2decf625e7a280fbfffd47380df584c085464deba060101169caca5f79e6a062b6c25b527bf60cb67 - languageName: node - linkType: hard - "@popperjs/core@npm:^2.5.4": version: 2.10.1 resolution: "@popperjs/core@npm:2.10.1" @@ -4835,16 +4808,6 @@ __metadata: languageName: node linkType: hard -"config-chain@npm:^1.1.11": - version: 1.1.13 - resolution: "config-chain@npm:1.1.13" - dependencies: - ini: "npm:^1.3.4" - proto-list: "npm:~1.2.1" - checksum: 10c0/39d1df18739d7088736cc75695e98d7087aea43646351b028dfabd5508d79cf6ef4c5bcd90471f52cd87ae470d1c5490c0a8c1a292fbe6ee9ff688061ea0963e - languageName: node - linkType: hard - "connect-history-api-fallback@npm:^2.0.0": version: 2.0.0 resolution: "connect-history-api-fallback@npm:2.0.0" @@ -5178,13 +5141,6 @@ __metadata: languageName: node linkType: hard -"deep-extend@npm:^0.6.0": - version: 0.6.0 - resolution: "deep-extend@npm:0.6.0" - checksum: 10c0/1c6b0abcdb901e13a44c7d699116d3d4279fdb261983122a3783e7273844d5f2537dc2e1c454a23fcf645917f93fbf8d07101c1d03c015a87faa662755212566 - languageName: node - linkType: hard - "deep-is@npm:^0.1.3": version: 0.1.3 resolution: "deep-is@npm:0.1.3" @@ -5608,7 +5564,6 @@ __metadata: node-watch: "npm:^0.7.3" npm-run-all2: "npm:^7.0.1" p-debounce: "npm:^2.0.0" - package-json: "npm:^10.0.1" parse-env-string: "npm:^1.0.1" postcss: "npm:^8.5.10" postcss-less: "npm:^6.0.0" @@ -7378,13 +7333,6 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:4.2.10": - version: 4.2.10 - resolution: "graceful-fs@npm:4.2.10" - checksum: 10c0/4223a833e38e1d0d2aea630c2433cfb94ddc07dfc11d511dbd6be1d16688c5be848acc31f9a5d0d0ddbfb56d2ee5a6ae0278aceeb0ca6a13f27e06b9956fb952 - languageName: node - linkType: hard - "grapheme-splitter@npm:^1.0.4": version: 1.0.4 resolution: "grapheme-splitter@npm:1.0.4" @@ -7951,7 +7899,7 @@ __metadata: languageName: node linkType: hard -"ini@npm:^1.3.4, ini@npm:^1.3.5, ini@npm:~1.3.0": +"ini@npm:^1.3.5": version: 1.3.8 resolution: "ini@npm:1.3.8" checksum: 10c0/ec93838d2328b619532e4f1ff05df7909760b6f66d9c9e2ded11e5c1897d6f2f9980c54dd638f88654b00919ce31e827040631eab0a3969e4d1abefa0719516a @@ -8720,13 +8668,6 @@ __metadata: languageName: node linkType: hard -"ky@npm:^1.2.0": - version: 1.14.3 - resolution: "ky@npm:1.14.3" - checksum: 10c0/8e91c9512c8f1501201108ad58ed437eaf3f5b0a0da842bd846d785932426e84a31cf51d0fffce1921d4e70e26465a9b2b89ed2477822975568258a1fa68a740 - languageName: node - linkType: hard - "less-loader@npm:^11.0.0": version: 11.0.0 resolution: "less-loader@npm:11.0.0" @@ -10710,18 +10651,6 @@ __metadata: languageName: node linkType: hard -"package-json@npm:^10.0.1": - version: 10.0.1 - resolution: "package-json@npm:10.0.1" - dependencies: - ky: "npm:^1.2.0" - registry-auth-token: "npm:^5.0.2" - registry-url: "npm:^6.0.1" - semver: "npm:^7.6.0" - checksum: 10c0/4a55648d820496326730a7b149fd3fd8382e96f3d6def5ec687f46b75063894acf06b21f79832b40bb094c821d97f532cb0f009f85c4102d0084b488d4f492d3 - languageName: node - linkType: hard - "pako@npm:~1.0.2": version: 1.0.11 resolution: "pako@npm:1.0.11" @@ -11290,13 +11219,6 @@ __metadata: languageName: node linkType: hard -"proto-list@npm:~1.2.1": - version: 1.2.4 - resolution: "proto-list@npm:1.2.4" - checksum: 10c0/b9179f99394ec8a68b8afc817690185f3b03933f7b46ce2e22c1930dc84b60d09f5ad222beab4e59e58c6c039c7f7fcf620397235ef441a356f31f9744010e12 - languageName: node - linkType: hard - "proxy-addr@npm:~2.0.7": version: 2.0.7 resolution: "proxy-addr@npm:2.0.7" @@ -11389,20 +11311,6 @@ __metadata: languageName: node linkType: hard -"rc@npm:1.2.8": - version: 1.2.8 - resolution: "rc@npm:1.2.8" - dependencies: - deep-extend: "npm:^0.6.0" - ini: "npm:~1.3.0" - minimist: "npm:^1.2.0" - strip-json-comments: "npm:~2.0.1" - bin: - rc: ./cli.js - checksum: 10c0/24a07653150f0d9ac7168e52943cc3cb4b7a22c0e43c7dff3219977c2fdca5a2760a304a029c20811a0e79d351f57d46c9bde216193a0f73978496afc2b85b15 - languageName: node - linkType: hard - "react-base16-styling@npm:~0.9.0": version: 0.9.1 resolution: "react-base16-styling@npm:0.9.1" @@ -11795,24 +11703,6 @@ __metadata: languageName: node linkType: hard -"registry-auth-token@npm:^5.0.2": - version: 5.1.1 - resolution: "registry-auth-token@npm:5.1.1" - dependencies: - "@pnpm/npm-conf": "npm:^3.0.2" - checksum: 10c0/86b0f7fd87d327cb4177fee69bcf96563147ea72e206bc9c7a6a50a51c785a31b83a6c45956a489ed292d23b908b2755a075d0b2f7fec1ba91b1fb800b24cee3 - languageName: node - linkType: hard - -"registry-url@npm:^6.0.1": - version: 6.0.1 - resolution: "registry-url@npm:6.0.1" - dependencies: - rc: "npm:1.2.8" - checksum: 10c0/66e2221c8113fc35ee9d23fe58cb516fc8d556a189fb8d6f1011a02efccc846c4c9b5075b4027b99a5d5c9ad1345ac37f297bea3c0ca30d607ec8084bf561b90 - languageName: node - linkType: hard - "relateurl@npm:^0.2.7": version: 0.2.7 resolution: "relateurl@npm:0.2.7" @@ -12287,7 +12177,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.2.1, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.8, semver@npm:^7.5.0, semver@npm:^7.5.3, semver@npm:^7.6.0, semver@npm:^7.6.3, semver@npm:^7.7.1, semver@npm:^7.7.2": +"semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.2.1, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.8, semver@npm:^7.5.0, semver@npm:^7.5.3, semver@npm:^7.6.3, semver@npm:^7.7.1, semver@npm:^7.7.2": version: 7.7.4 resolution: "semver@npm:7.7.4" bin: @@ -13010,13 +12900,6 @@ __metadata: languageName: node linkType: hard -"strip-json-comments@npm:~2.0.1": - version: 2.0.1 - resolution: "strip-json-comments@npm:2.0.1" - checksum: 10c0/b509231cbdee45064ff4f9fd73609e2bcc4e84a4d508e9dd0f31f70356473fde18abfb5838c17d56fb236f5a06b102ef115438de0600b749e818a35fbbc48c43 - languageName: node - linkType: hard - "strip-outer@npm:^1.0.1": version: 1.0.1 resolution: "strip-outer@npm:1.0.1" From c062733433fcef290d93a32bcfe19a91642b93c4 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 6 May 2026 07:18:09 -0500 Subject: [PATCH 2/3] fixup! build: remove package-json dependency check res.ok before parsing json --- src/main/electron-types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/electron-types.ts b/src/main/electron-types.ts index 0887ab1ab9..96ae82716d 100644 --- a/src/main/electron-types.ts +++ b/src/main/electron-types.ts @@ -201,6 +201,7 @@ export class ElectronTypes { Accept: 'application/vnd.npm.install-v1+json', }; const res = await fetch(url, { headers }); + if (!res.ok) throw new Error(`npm registry returned ${res.status}`); const data = (await res.json()) as { versions: Record }; const major = semver.major(version); From 3b20bfbca5ffd32ffca0bd2386ffab987cfa6142 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 6 May 2026 09:05:09 -0500 Subject: [PATCH 3/3] refactor: extract-method getLatestMajorVersion() into a standalone helper test: add tests for getLatestMajorVersion() --- src/main/electron-types.ts | 18 +------- src/main/utils/npm-version.ts | 29 +++++++++++++ tests/main/utils/npm-version.spec.ts | 65 ++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 16 deletions(-) create mode 100644 src/main/utils/npm-version.ts create mode 100644 tests/main/utils/npm-version.spec.ts diff --git a/src/main/electron-types.ts b/src/main/electron-types.ts index 96ae82716d..e37fc5a929 100644 --- a/src/main/electron-types.ts +++ b/src/main/electron-types.ts @@ -8,6 +8,7 @@ import watch from 'node-watch'; import semver from 'semver'; import { ipcMainManager } from './ipc'; +import { getLatestMajorVersion } from './utils/npm-version'; import { ELECTRON_DTS } from '../constants'; import { NodeTypes, RunnableVersion, VersionSource } from '../interfaces'; import { IpcEvents } from '../ipc-events'; @@ -196,23 +197,8 @@ export class ElectronTypes { ); if (response.status === 404) { - const url = 'https://registry.npmjs.org/@types%2Fnode'; - const headers: HeadersInit = { - Accept: 'application/vnd.npm.install-v1+json', - }; - const res = await fetch(url, { headers }); - if (!res.ok) throw new Error(`npm registry returned ${res.status}`); - const data = (await res.json()) as { versions: Record }; - const major = semver.major(version); - const matched = semver.maxSatisfying( - Object.keys(data.versions), - `${major}`, - ); - if (!matched) - throw new Error(`No @types/node version found for ${major}`); - - downloadVersion = matched; + downloadVersion = await getLatestMajorVersion('@types/node', major); console.log( `falling back to the latest applicable Node.js version type: ${downloadVersion}`, ); diff --git a/src/main/utils/npm-version.ts b/src/main/utils/npm-version.ts new file mode 100644 index 0000000000..a0ef1f4096 --- /dev/null +++ b/src/main/utils/npm-version.ts @@ -0,0 +1,29 @@ +import semver from 'semver'; + +/** + * Fetches the latest version of an npm package matching a given major version. + * + * @param packageName - the npm package name (e.g. '\@types/node') + * @param major - the major version to match against + * @returns the latest version string matching the major version + */ +export async function getLatestMajorVersion( + packageName: string, + major: number, +): Promise { + const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}`; + const headers: HeadersInit = { + Accept: 'application/vnd.npm.install-v1+json', + }; + const res = await fetch(url, { headers }); + if (!res.ok) throw new Error(`npm registry returned ${res.status}`); + + const data = (await res.json()) as { versions: Record }; + const matched = semver.maxSatisfying(Object.keys(data.versions), `${major}`); + if (!matched) + throw new Error( + `No ${packageName} version found for major version ${major}`, + ); + + return matched; +} diff --git a/tests/main/utils/npm-version.spec.ts b/tests/main/utils/npm-version.spec.ts new file mode 100644 index 0000000000..5745f09fde --- /dev/null +++ b/tests/main/utils/npm-version.spec.ts @@ -0,0 +1,65 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import { getLatestMajorVersion } from '../../../src/main/utils/npm-version'; + +describe('getLatestMajorVersion', () => { + afterEach(() => { + vi.mocked(fetch).mockReset(); + }); + + it('returns the latest version matching the major', async () => { + vi.mocked(fetch).mockResolvedValue({ + ok: true, + json: vi.fn().mockResolvedValue({ + versions: { + '20.0.0': {}, + '20.5.0': {}, + '20.17.12': {}, + '22.0.0': {}, + }, + }), + } as unknown as Response); + + const result = await getLatestMajorVersion('@types/node', 20); + expect(result).toBe('20.17.12'); + expect(fetch).toHaveBeenCalledWith( + 'https://registry.npmjs.org/%40types%2Fnode', + { headers: { Accept: 'application/vnd.npm.install-v1+json' } }, + ); + }); + + it('throws if the registry returns a non-ok response', async () => { + vi.mocked(fetch).mockResolvedValue({ + ok: false, + status: 503, + } as unknown as Response); + + await expect(getLatestMajorVersion('@types/node', 20)).rejects.toThrow( + 'npm registry returned 503', + ); + }); + + it('throws if no version matches the major', async () => { + vi.mocked(fetch).mockResolvedValue({ + ok: true, + json: vi.fn().mockResolvedValue({ + versions: { + '18.0.0': {}, + '22.0.0': {}, + }, + }), + } as unknown as Response); + + await expect(getLatestMajorVersion('@types/node', 20)).rejects.toThrow( + 'No @types/node version found for major version 20', + ); + }); + + it('throws if fetch rejects', async () => { + vi.mocked(fetch).mockRejectedValue(new Error('network error')); + + await expect(getLatestMajorVersion('@types/node', 20)).rejects.toThrow( + 'network error', + ); + }); +});