From 698d08d2e92349ad9b81a044634a324b720dfe86 Mon Sep 17 00:00:00 2001 From: "pulse-triage[bot]" <249995291+pulse-triage[bot]@users.noreply.github.com> Date: Sat, 19 Sep 2026 21:49:17 +0100 Subject: [PATCH] build(frontend): align @types/node 26 with the ES2022 lib declaration Dependabot #2099 bumps @types/node 20.19.43 to 26.5.1 in frontend-modern, but the PR is blocked twice: Frontend tsc fails with TS2550 on .at() (26 removed the compatibility polyfill), and Canonical Governance blocks the package.json/lock change for a missing deployment-installability contract update and frontend dependency security proof. Carry the bump with its required governance artifacts in one commit. tsconfig already declares the ES2022 lib (95db35b6c3), which the source's .at() usage needs; this adds the manifest and lock bump (matching tests/integration, which already pins @types/node ^26.5.1), a Completion Obligations entry, and a dependencySecurity proof that keeps the manifest range, the locked @types/node/undici-types versions and the declared lib in step. Verified offline: the guard passes and the proof test passes. Change-source: pulse-maintainer --- .../subsystems/deployment-installability.md | 11 +++++++ frontend-modern/package-lock.json | 16 +++++----- frontend-modern/package.json | 2 +- .../__tests__/dependencySecurity.test.ts | 32 +++++++++++++++++++ 4 files changed, 52 insertions(+), 9 deletions(-) diff --git a/docs/release-control/v6/internal/subsystems/deployment-installability.md b/docs/release-control/v6/internal/subsystems/deployment-installability.md index 3b3b28bed..26f8a9443 100644 --- a/docs/release-control/v6/internal/subsystems/deployment-installability.md +++ b/docs/release-control/v6/internal/subsystems/deployment-installability.md @@ -2366,6 +2366,17 @@ artifact-selection behaviour. `scripts/trigger-release.sh` and `scripts/trigger-stable-patch.sh` must send the exact remote candidate SHA they already verified; branch ancestry or a later branch tip is not equivalent release admission. +18. Keep the frontend type-surface dependency and its compiler lib aligned. + `frontend-modern/package.json` and `frontend-modern/package-lock.json` must + pin a single `@types/node` line at or above the reviewed floor with its + `undici-types` companion resolved consistently, and + `frontend-modern/tsconfig.json` must declare the `ES2022` lib whenever the + source uses `Array.prototype.at()`/`String.prototype.at()`. `@types/node` + before 26 shipped an `.at()` compatibility polyfill that masked the missing + lib; a bump that removes it must keep + `frontend-modern/src/security/__tests__/dependencySecurity.test.ts` proving + the manifest range, the locked `@types/node`/`undici-types` versions and the + `ES2022` lib declaration stay in step. ## Current State diff --git a/frontend-modern/package-lock.json b/frontend-modern/package-lock.json index 1cd175933..7bc5c96d0 100644 --- a/frontend-modern/package-lock.json +++ b/frontend-modern/package-lock.json @@ -22,7 +22,7 @@ "@solidjs/testing-library": "^0.8.5", "@tailwindcss/typography": "^0.5.19", "@testing-library/jest-dom": "^6.5.0", - "@types/node": "^20.10.0", + "@types/node": "^26.5.1", "@types/qrcode": "^1.5.6", "@typescript-eslint/eslint-plugin": "^8.24.0", "@typescript-eslint/parser": "^8.24.0", @@ -1969,13 +1969,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.19.43", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", - "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "version": "26.5.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.5.1.tgz", + "integrity": "sha512-CzNm2FezW4VR/LjG6yUdiEgLE/rAQ9Slj5gCu/C2VrdcW7I0ahNZ8DRbHT7zOZ6r3ONgd/bsQIeSaoDGrd1C6g==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~8.9.0" } }, "node_modules/@types/qrcode": { @@ -6473,9 +6473,9 @@ } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", "dev": true, "license": "MIT" }, diff --git a/frontend-modern/package.json b/frontend-modern/package.json index 6289dc6c4..b52ba77fd 100644 --- a/frontend-modern/package.json +++ b/frontend-modern/package.json @@ -73,7 +73,7 @@ "@solidjs/testing-library": "^0.8.5", "@tailwindcss/typography": "^0.5.19", "@testing-library/jest-dom": "^6.5.0", - "@types/node": "^20.10.0", + "@types/node": "^26.5.1", "@types/qrcode": "^1.5.6", "@typescript-eslint/eslint-plugin": "^8.24.0", "@typescript-eslint/parser": "^8.24.0", diff --git a/frontend-modern/src/security/__tests__/dependencySecurity.test.ts b/frontend-modern/src/security/__tests__/dependencySecurity.test.ts index 774241c3b..aafd006d8 100644 --- a/frontend-modern/src/security/__tests__/dependencySecurity.test.ts +++ b/frontend-modern/src/security/__tests__/dependencySecurity.test.ts @@ -20,6 +20,13 @@ const lock = JSON.parse( readFileSync(new URL('../../../package-lock.json', import.meta.url), 'utf8'), ) as PackageLock; +const tsconfig = readFileSync(new URL('../../../tsconfig.json', import.meta.url), 'utf8'); +const libMatch = /"lib"\s*:\s*\[([^\]]*)\]/.exec(tsconfig); +const declaredLib = (libMatch?.[1] ?? '') + .split(',') + .map((entry) => entry.trim().replace(/^"|"$/g, '')) + .filter(Boolean); + const parseVersion = (version: string): [number, number, number] => { const [major = 0, minor = 0, patch = 0] = version .split('-', 1)[0] @@ -139,4 +146,29 @@ describe('frontend dependency security floors', () => { expect(atLeast(version, [4, 28, 7]), `browserslist ${version} is vulnerable`).toBe(true); } }); + + it('keeps @types/node and its undici-types companion aligned with the declared ES2022 lib', () => { + // @types/node 20 shipped an .at() compatibility polyfill that masked the + // missing ES2022 lib; 26 drops it, so tsconfig must declare ES2022 itself + // (Dependabot #2099). Keep the manifest range, the lock and the lib in step. + const range = manifest.devDependencies['@types/node']; + expect(/^\^26\.\d+\.\d+$/.test(range), `unexpected @types/node range ${range}`).toBe(true); + expect(atLeast(range.slice(1), [26, 5, 1])).toBe(true); + const nodeTypes = lockedVersions('@types/node'); + expect(nodeTypes).toHaveLength(1); + expect(nodeTypes[0]).not.toContain('-'); + expect( + atLeast(nodeTypes[0], [26, 5, 1]), + `@types/node ${nodeTypes[0]} is below the reviewed floor`, + ).toBe(true); + const undici = lockedVersions('undici-types'); + expect(undici).not.toHaveLength(0); + for (const version of undici) { + expect( + atLeast(version, [8, 9, 0]), + `undici-types ${version} must satisfy @types/node 26`, + ).toBe(true); + } + expect(declaredLib).toContain('ES2022'); + }); });