Skip to content

feat!: modernize the toolchain and drop EOL Node versions - #331

Merged
oliverlaz merged 17 commits into
mainfrom
chore/modernize-toolchain
Aug 18, 2026
Merged

feat!: modernize the toolchain and drop EOL Node versions#331
oliverlaz merged 17 commits into
mainfrom
chore/modernize-toolchain

Conversation

@oliverlaz

@oliverlaz oliverlaz commented Aug 17, 2026

Copy link
Copy Markdown
Member

Overview

Modernizes the build and test toolchain, and fixes CI, which has been red on main since June.

Yarn 1 → 4, Rollup → Vite 8 library mode, Vitest 3 → 4, ESLint 8 → 10 with typescript-eslint 8, TS target ES2020 → ES2022, and the GitHub Actions bumped to their latest majors. Node matrix is 22/24/26 now and engines.node is >=22.12.0, since 18 and 20 are both EOL.

Rollup drops out of the tree entirely — Vite 8 bundles with rolldown. eslint-config-standard-with-typescript had to go too, since it's deprecated and pinned to ESLint 8; the config uses typescript-eslint's recommendedTypeChecked instead, which also made eslint-plugin-import, -n and -promise unnecessary.

I diffed the built output against the old Rollup build to be sure nothing moved: same filenames, byte-identical .d.ts, same externals and exports, PKG_VERSION still inlined. Declarations come from tsc --emitDeclarationOnly, which also keeps type errors failing the build.

BREAKING CHANGE: minimum supported Node.js is now 22.12.0. Node 18 went EOL in April 2025, Node 20 in April 2026.

BREAKING CHANGE: @types/node moved to devDependencies, so it's no longer installed transitively. The public API exposes Buffer (e.g. verifyWebhook(requestBody: string | Buffer)), so consumers type-checking against those signatures now need @types/node themselves.

Migrate from Yarn 1.22.22 to Yarn 4.18.0 using the node-modules linker.

- .yarnrc.yml: node-modules linker, global cache with hardlinks,
  enableScripts: false, 2-day minimum age gate for npm packages
- CI: yarn install --frozen-lockfile -> --immutable (Yarn 1 only flag)
- prepare: husky install -> postinstall: husky, as Yarn 4 does not run
  the root prepare script on install (hooks silently stopped installing)
- lint scripts: pass explicit paths instead of **/*.ts, which Yarn 4's
  portable shell expands recursively into node_modules

The lint change surfaced two violations in src/utils/create-token.ts
that the previous shallow glob never checked; fixed via eslint --fix.
Upgrade vite 6.4.1 -> 8.2.1 and vitest 3.1.4 -> 4.1.10. No changes to
vite.config.ts were required; vitest 4 swaps its internal transform
from esbuild to rolldown.

Both vite 8 and vitest 4 declare engines that exclude Node 18, and
Node 20 reached end-of-life in April 2026, so the test matrix becomes
[22, 24, 26] and @types/node moves to ^22.20.1 to match the new floor.

Also drop vitest-mock-extended, which was never imported anywhere.

BREAKING CHANGE: the minimum supported Node.js version is now 22.12.0.
Node 18 (EOL April 2025) and Node 20 (EOL April 2026) are no longer
supported.
The lint job set node-version to ${{ matrix.version }}, but that job has
no strategy.matrix, so it resolved to empty and lint ran on whatever Node
the runner shipped. Point it at .nvmrc (v24) and bump setup-node v3 -> v6
to match every other job.
It is only needed to build and type-check this package, not at runtime.

BREAKING CHANGE: @types/node is no longer a runtime dependency, so it is
no longer installed transitively. The public API exposes Buffer (e.g.
verifyWebhook(requestBody: string | Buffer)), so consumers type-checking
against those signatures now need @types/node in their own devDependencies.
Replace the deprecated eslint-config-standard-with-typescript, which is
pinned to eslint ^8 and @typescript-eslint ^6, with typescript-eslint's own
recommendedTypeChecked config. Its official successor eslint-config-love
peers eslint ^9.35.0, so it could not reach latest either.

- eslint 8.57.1 -> 10.8.1, @typescript-eslint 6.21.0 -> typescript-eslint
  8.67.0, eslint-config-prettier 9.1.0 -> 10.1.8
- .eslintrc.js and .eslintignore -> eslint.config.mjs, as eslint 10 removed
  eslintrc support and the --ext flag
- drop eslint-plugin-import, -n and -promise, only needed as peers of the
  standard config

no-explicit-any is off: the generated OpenAPI models, JWT payload types and
JSON transport are untyped by design. The no-unsafe-* rules are scoped off
for src/gen, src/ApiClient.ts and __tests__, where every occurrence lived,
so they stay enforced in hand-written source.

The newly enabled rules surfaced 20 real issues, all fixed rather than
suppressed: unused catch bindings (typescript-eslint 8 changed the
no-unused-vars caughtErrors default from 'none' to 'all'), empty catch
blocks, async functions with no await, a dead assignment, a single-argument
Object.assign, and 4 eslint-disable directives for rules deleted in
typescript-eslint 8.
Vite 8 bundles with rolldown, so this drops rollup from the tree entirely.

- vite.config.ts -> vite.config.mts, adding build.lib alongside the existing
  vitest config; .mts avoids the ESM-in-CJS config loader warning
- declarations now come from tsc --emitDeclarationOnly, which keeps type
  errors failing the build since rolldown only strips types
- drop rollup, @rollup/plugin-replace, @rollup/plugin-typescript and tslib,
  the last only required as a peer of the typescript plugin
- raise target to es2022, fully supported by the Node 22.12 floor

Output is equivalent to the previous build: identical file names, identical
declarations, the same four external imports, the same 12 exports, require()
rather than import() in the cjs bundle, and PKG_VERSION still inlined.
Note that npmPublishProvenance only applies to `yarn npm publish`. The
release workflow currently runs `npm publish`, which needs npm's own
--provenance flag to emit an attestation.
- actions/checkout v6 -> v7
- actions/setup-node v6 -> v7
- googleapis/release-please-action v4 -> v5
- oven-sh/setup-bun v1 -> v2

aslafy-z/conventional-pr-title-action stays on v3, which is a branch already
tracking the newest v3 release. The release-please major only changes its
runtime to node24; no inputs or config changed.
Node.js floor is now 22.12, the runtime targets are 22, 24 and 26, and the
package manager is Yarn 4. Also describe the Vite library-mode build and the
flat ESLint config.

Unrelated fixes while here: the consumer example pinned a long-stale 0.6.5,
and the __tests__ folder name was being rendered as bold "tests", so the
project layout is now a code block.
prepublish was the only thing building dist in CI, because Yarn 1 ran it on
install. Yarn 4 never runs prepublish, and npm has not run it on publish
since npm 5, so after the Yarn 4 upgrade the release job installed and
packed without ever building. prepack runs before packing under both npm
and Yarn, so the build no longer depends on an install-time side effect.
Yarn 4 supports npm trusted publishing, exchanging the GitHub Actions OIDC
token for a registry token, so publishing stays tokenless. This also makes
the existing npmPublishProvenance setting effective, as it never applied to
`npm publish`.

Yarn defaults scoped packages to restricted access, so set
publishConfig.access to public explicitly; npm honours the same field.
The unfiltered queryChannels assertion required more than one channel to
already exist, but the suite only keeps one alive by that point, so it
passed only in apps with leftovers from earlier runs and failed in a clean
app. Create the second channel in the test and delete it in a finally
block.
The role is created and deleted by separate tests, so any failure between
the two leaks it. Apps allow only 25 custom roles, so leaked roles would
eventually fail every later run. Clean up in afterAll, tolerating a role
that was never created or already removed.
- dotenv 17.3.1 -> 17.4.2
- prettier 3.8.1 -> 3.9.6
- lint-staged 16.3.3 -> 17.3.0

Prettier 3.9 wraps union types differently, which reformats the generated
models; the change is cosmetic and is included here so the committed
formatting matches the committed prettier version.

lint-staged 17 requires Node 22.22.1, above the 22.12.0 engines floor, but
it only runs in the pre-commit hook and .nvmrc already pins v24. Its other
breaking changes do not apply: git is well past 2.32, and the lint-staged
config lives in package.json rather than a YAML file.
@oliverlaz
oliverlaz requested a review from szuperaz as a code owner August 17, 2026 11:37
oliverlaz and others added 3 commits August 17, 2026 14:52
postinstall runs when the package is installed as a dependency, and husky
is a devDependency consumers do not get, so `npm install` failed with
"husky: command not found". Guard on husky being resolvable so the script
is a silent no-op outside this repo.

prepare was safe here because npm never runs it for registry-installed
dependencies, but Yarn 4 does not run prepare on install at all, so
postinstall has to stay.
TypeScript 6 rejects moduleResolution=node10 (TS5107, removed in 7.0), so
the two changes go together. Pinned with ~ because typescript-eslint peers
typescript <6.1.0; TypeScript 7 is not usable until that ceiling lifts.

Declaration emit now references the package root as import("..") rather
than import("./gen/models"). Verified against the packed tarball from a
consumer project: type-checks under bundler, node16 and nodenext, and both
bundles are byte-identical.
@oliverlaz
oliverlaz merged commit dbabec0 into main Aug 18, 2026
10 checks passed
@oliverlaz
oliverlaz deleted the chore/modernize-toolchain branch August 18, 2026 07:46
oliverlaz pushed a commit that referenced this pull request Aug 18, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.8.0](v0.7.64...v0.8.0)
(2026-08-18)


### ⚠ BREAKING CHANGES

* minimum supported Node.js is now 22.12.0. Node 18 went EOL in April
2025, Node 20 in April 2026.

### Features

* modernize the toolchain and drop EOL Node versions
([#331](#331))
([dbabec0](dbabec0))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants