Skip to content

deps: bump @types/node 20 → 24 (Node 24 LTS) #19

@theagenticguy

Description

@theagenticguy

Context

Node 24 is the current active LTS (Node 22 moved to maintenance on
2026-04-22, Node 20 has entered end-of-life). Node 24 is the version
GitHub Actions runners default to for new Node.js actions.

This was attempted in #18 (bumping 20 → 22) and deliberately deferred
because bumping the types produced 17 hard type errors in
@opencodehub/sarif. The 24.x strictness is a superset, so the same
fixes apply and we should jump straight to 24 rather than land 22 and
re-bump again.

Runtime-support policy

We support Node 22 and Node 24. 22 is maintenance-line LTS with
bugfixes through 2027-04; 24 is the current active LTS. Node 20 is
end-of-life and no longer tested.

  • engines.node>=22.0.0
  • CI test matrix runs on both 22 and 24 across all three OSes

Why it breaks (TS4111)

@types/node@22+ ships stricter index-signature inference (carried
forward into 24.x). Properties accessed via . on a record whose type
is Record<string, T> or { [k: string]: T } must now use bracket
notation (record["key"]). This is the TS4111 diagnostic.

Concrete failures to fix

All are in packages/sarif/ (surfaced when the bump was attempted in
#18):

  • src/fingerprint.test.ts:145:49.primaryLocationLineHash
  • src/schema-validation.test.ts:53:32.primaryLocationLineHash
  • src/suppressions.ts:113:26.rules
  • src/suppressions.ts:113:42.suppressions
  • src/suppressions.ts:124:25.ruleId
  • src/suppressions.ts:125:34.filePathPattern
  • src/suppressions.ts:126:25.reason
  • src/suppressions.ts:127:28.expiresAt
  • src/suppressions.test.ts — 8 more .suppressions accesses
    (lines 45, 88, 119, 126, 138, 150, 159, 175, 216)

Total: 17 call sites across 4 files. Re-run pnpm -r exec tsc --noEmit
after the bump to catch anything new that surfaces under Node 24 types
specifically (fetch / Response / Headers / File lib-dom overlap
behavior tightened in 24.x).

Node 24-specific shifts to audit

  • fetch, Request, Response, Headers are built-in globals;
    @types/node@24 no longer redeclares them. If any workspace depended
    on the redeclaration (e.g. importing from undici purely for types),
    those imports become redundant and should be removed.
  • File and Blob are globals.
  • require(esm) is now stable — we don't use it, but worth noting if
    any loader shim was relying on require being CommonJS-only.

Two ways to fix the TS4111 errors

Option A (mechanical): Change x.foox["foo"] at each call
site. Minimal diff, preserves runtime behavior. Ugly but fast.

Option B (structural): Replace the passthrough Record<string, unknown>
shapes in packages/sarif/src/schemas.ts (SARIF properties bag) and
the suppressions schema with concrete Zod object schemas that declare
the fields explicitly. Those fields stop being index-signature
properties and the errors disappear without bracket-access ugliness.

Recommendation: Option B for suppressions.ts (the fields are
load-bearing and deserve concrete typing), Option A for the SARIF
passthrough bag
(it's intentionally open-ended to preserve foreign
namespace properties — see the comment in schemas.ts).

CI test matrix — add Node 22 + 24

Current ci.yml test job only varies os, so mise-action resolves
whichever Node version mise.toml pins — one Node version across three
OSes. To actually test both supported Node versions:

test:
  strategy:
    fail-fast: false
    matrix:
      os: [ubuntu-latest, macos-latest, windows-latest]
      node: [22, 24]
  runs-on: ${{ matrix.os }}
  steps:
    - uses: actions/checkout@v6
    - uses: jdx/mise-action@v4
      with:
        install_args: node@${{ matrix.node }}
    - name: Ensure node-gyp is available for native tree-sitter build
      run: npm i -g node-gyp
    - run: pnpm install --frozen-lockfile
    - run: pnpm -r test

That's 6 test jobs instead of 3. Also update the branch-protection
required-checks list to include the new job names
(test (ubuntu-latest, 22), test (ubuntu-latest, 24), etc.) via
gh api -X PUT /repos/theagenticguy/opencodehub/branches/main/protection.

Config files to update

  • mise.tomlnode = "22"node = "24" (local dev default)
  • .nvmrc / .node-version2224
  • Root package.json engines.node">=22.0.0"
  • .github/workflows/ci.yml test matrix — add node: [22, 24] axis
  • Branch-protection required-checks — add the matrix rows via gh api

Acceptance

  • @types/node at 24.12.x (latest 24 LTS point-release) in every
    workspace package.json
  • mise.toml + .nvmrc + .node-version pin Node 24 for local dev
  • engines.node at ">=22.0.0" in root package.json
  • CI test matrix runs on node: [22, 24] × all three OSes (6 jobs)
  • Branch protection updated to require all 6 test matrix rows
  • pnpm -r build clean
  • pnpm -r exec tsc --noEmit clean (expect ~17 TS4111 fixes)
  • pnpm -r test green on all 6 test matrix jobs
  • osv-scanner still clean on refreshed lockfile
  • Regenerate SBOM.cdx.json + THIRD_PARTY_LICENSES.md

Metadata

Metadata

Assignees

No one assigned

    Labels

    dependenciesPull requests that update a dependency file

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions