Skip to content

Upgrade ng-packagr, vitest, and eslint where each was actually causing a problem - #34

Merged
vannt-dev merged 6 commits into
developfrom
chore/dependency-upgrades
Aug 14, 2026
Merged

Upgrade ng-packagr, vitest, and eslint where each was actually causing a problem#34
vannt-dev merged 6 commits into
developfrom
chore/dependency-upgrades

Conversation

@vannt-dev

Copy link
Copy Markdown
Owner

Four dependency upgrades, each picked because something concrete was wrong rather than because a number was behind. Only one changes what any package publishes.

ng-packagr 17 → 19

The angular package built against Angular 19 with a tool that declared it wanted Angular 17:

peer @angular/compiler-cli: ^17.0.0     installed: 19.2.25
peer typescript:            >=5.2 <5.5  installed: 5.6.3

Both violated, which is why installing in packages/angular needed --legacy-peer-deps. It worked — the bundle carries ɵɵngDeclareComponent ... version: "19.2.25" — on a combination ng-packagr never claimed to support. 19.2.2 matches exactly, plain npm install succeeds, and 36 transitive packages go away for 4.

This is the one commit that changes a published artifact. The part consumers load is untouched — fesm2022, index.d.ts and public-api.d.ts are byte-identical — but ng-packagr stopped emitting the per-file esm2022/ output in 18, along with its esm2022/esm export conditions. The . export keeps types and default, which is what Angular 19+ libraries ship, and default points at the same file those conditions already resolved to.

Before After
Unpacked 317.1 KB 165.9 KB (−48%)
Tarball 75.8 KB 33.7 KB (−56%)
Files 33 19

In-range updates

npm update only — no package.json touched, nothing moved outside a range already declared. @angular/* 19.2.20→19.2.25, @analogjs/* 1.16→1.22, react/react-dom 19.2.4→19.2.8, vue 3.5.32→3.5.41, plus @vue/test-utils, lint-staged, @commitlint/, @types/.

Two rows in the lockfile diff look like majors and are not. rimraf 3.0.2→6.1.3 at the hoist root is core and angular's already-declared ^6.1.3 finally resolving there; the 3.0.2 was a transitive from flat-cache, which keeps its own nested copy. vite 6.4.3 at the root is a transitive of the newer @analogjs — the vite packages/react declares stays at 5.4.21 under its own node_modules, and vitest keeps its nested 5.4.21.

core onto the workspace's vitest

core was pinned to vitest 0.34 while everything else ran 1.6, and that single split caused both workarounds the repo carried: 0.34 has no --typecheck flag, so test:types needed the 0.34-only vitest typecheck --run subcommand; and the hoisted @vitest/coverage-v8@1.6.1 is incompatible with 0.34, so core alone used @vitest/coverage-istanbul. Both are gone — core now resolves the hoisted 1.6.1 with no nested copy.

Two config shapes changed with it, and both were proven live rather than assumed, because a silently-ignored gate is worse than no gate:

  • test:typesvitest --run --typecheck.only, same scope as before. Appending const broken: number = 'nope' to types.test-d.ts exits 1 with TypeCheckError.
  • The coverage floor moves from 0.34's flat keys to 1.x's thresholds wrapper. This is the dangerous half — 1.x silently ignores the flat keys, so the floor would have vanished without a word. Raising statements to 99 exits 1 with ERROR: Coverage for statements (95.12%) does not meet global threshold (99%).

v8 and istanbul count differently, so core's reported numbers move to 95.12 statements / 88.69 branches / 100 functions against the unchanged 85/75/85/85 floor.

eslint 8 → 9, flat config

eslint 8.57.1 is end of life; npm ci printed "This version is no longer supported" on every install. eslint 9 reads neither .eslintrc.cjs nor .eslintignore, so both become eslint.config.mjs, translated rule for rule with .eslintignore's reasoning carried into the ignores comment.

Three dependency decisions worth flagging:

  • eslint-plugin-prettier is removed, not upgraded. v5 requires prettier >=3, which would have pulled the whole prettier 2→3 reformat into this PR. It is redundant regardless: format-check is already its own CI gate and lint-staged runs prettier --write before eslint, so linting formatting was a slower second copy of an existing check. eslint-config-prettier stays to keep turning off rules that fight the formatter.
  • Stayed on eslint 9, not 10, because eslint-plugin-import 2.32 declares peers only up to ^9. This branch opens by removing a peer violation; adding one at the end would be incoherent.
  • eslint-config-airbnb-base and eslint-plugin-node are dropped — neither appeared in the old config's extends or plugins. Installed, never used.

The upgrade surfaced six real things: typescript-eslint 8 flags unused catch bindings (three in scripts/build-changed.js, now optional catch bindings), and eslint 9 reports unused disable directives (three stale ones, including a no-empty-interface disable for a rule v8 renamed, guarding an interface that is not empty).

A clean lint run is also what a flat config with a wrong files glob produces, so the rules were checked against probe files rather than trusted. On a .ts file: curly, eqeqeq, semi, max-len, prefer-const and no-unused-vars all fire. On a second probe: import/order and import/no-unresolved fire. example/, smoke/ and *.d.ts remain ignored.

Release scope

Only @dynamic-field-kit/angular gets a changeset. core, react and vue changed only tooling that never ships — devDependencies, vitest.config.js, and two stale eslint-disable comments — and their built dist is byte-identical to develop, verified by building both revisions and diffing. They get an empty changeset rather than version bumps that would publish identical tarballs.

Verification

Every commit was verified after a clean npx npm@10 ci: build, lint, format-check, typecheck, core type tests, all four package suites (plain and --coverage), the published-package smoke suite, test:scripts, npm audit --omit=dev, the three verify scripts, and all three example app builds.

The lockfile was regenerated with npm@10 throughout, never the locally installed npm 11 which prunes other platforms' optional binaries. Linux entries went 100 → 115 — an increase from new transitive platform packages, not the pruning that bug causes.

ng-packagr sat at 17.3.0 while the package builds against Angular 19,
and its peer ranges said so plainly:

  peer @angular/compiler-cli: ^17.0.0     installed: 19.2.20
  peer typescript:            >=5.2 <5.5  installed: 5.6.3

Both violated, which is why installing in packages/angular needed
--legacy-peer-deps. It worked -- the emitted bundle carries
`ɵɵngDeclareComponent ... version: "19.2.20"` -- but on a combination
ng-packagr never claimed to support.

19.2.2 matches exactly: compiler-cli ^19.x and typescript >=5.5 <5.9.
The install now completes with plain `npm install`, no
--legacy-peer-deps, and drops 36 transitive packages (ng-packagr 17's
cacache/tar/minipass/esbuild-wasm chain) for 4.

The published artifact is smaller, and the part consumers load is
unchanged:

  fesm2022 bundle   byte-identical
  index.d.ts        identical
  public-api.d.ts   identical
  tarball           317.1 -> 165.9 KB unpacked, 75.8 -> 33.7 KB packed
  files             33 -> 19

The difference is the per-file `esm2022/` output, which ng-packagr
stopped emitting in 18 because the fesm2022 bundle is what the Angular
linker consumes. Its `esm2022` and `esm` export conditions go with it;
`types` and `default` remain, which is what every Angular 19+ library
ships.

Angular suite passes unchanged at 91 tests, typecheck and the three
verify scripts pass, and the angular demo app still builds against the
package through its `file:` dependency.

Lockfile regenerated with npm@10: all 100 linux entries intact.
`npm update` only, so every package.json is untouched and nothing moved
outside a range this repo already declared. Direct dependencies that
advanced:

  @analogjs/vite-plugin-angular      1.16.1 -> 1.22.5
  @analogjs/vitest-angular           1.16.1 -> 1.22.5
  @angular/* (7 packages)           19.2.20 -> 19.2.25
  @commitlint/cli                     21.2.1 -> 21.2.2
  @commitlint/config-conventional     21.2.0 -> 21.2.2
  @testing-library/user-event         14.6.1 -> 14.6.4
  @types/node                       20.19.38 -> 20.19.43
  @types/react                       19.2.14 -> 19.2.18
  @vue/test-utils                      2.4.6 -> 2.4.11
  lint-staged                         17.2.0 -> 17.3.0
  react, react-dom                    19.2.4 -> 19.2.8
  vue                                 3.5.32 -> 3.5.41

Two entries in the lockfile diff look like major jumps and are not.
`rimraf` at the hoist root goes 3.0.2 -> 6.1.3, but core and angular
already declared ^6.1.3 -- the 3.0.2 that used to sit there was a
transitive from flat-cache, which now keeps its own nested copy. `vite`
appears at the root as 6.4.3, pulled in by the newer @analogjs; the vite
that packages/react declares stays at 5.4.21 under its own node_modules,
matching its ^5.4.0, and vitest keeps its nested 5.4.21 too.

Linux optional binaries went 100 -> 115 entries, an increase from the new
transitive platform packages -- not the pruning that npm 11 causes, which
is why this was regenerated with npm@10 as usual.

Verified after a clean `npm@10 ci`: build, typecheck, core type tests,
all four package suites, the published-package smoke suite, test:scripts,
lint, format-check, audit, the three verify scripts, and all three
example app builds. Bundle sizes are unchanged bar a few bytes from the
newer tsup/esbuild.
core was pinned to vitest 0.34 while react, vue and angular ran 1.6, and
that single split was the cause of two workarounds the repo had been
carrying:

- 0.34 has no `--typecheck` CLI flag, so `test:types` had to use the
  0.34-only `vitest typecheck --run` subcommand.
- The hoisted `@vitest/coverage-v8@1.6.1` is incompatible with 0.34
  (reports 0% or errors), so core alone used
  `@vitest/coverage-istanbul@0.34.6` as its own nested devDependency.

Moving core to ^1.6.0 with `@vitest/coverage-v8@^1.6.0` removes both.
core now resolves the hoisted vitest 1.6.1 with no nested copy at all,
and the istanbul provider is gone.

Two config shapes had to change with it, and both were verified live
rather than assumed -- a silently-ignored gate is worse than no gate:

- `test:types` becomes `vitest --run --typecheck.only`, which reproduces
  the old scope exactly (1 file, 9 tests, types.test-d.ts only).
  Appending `const broken: number = 'nope'` to types.test-d.ts makes it
  exit 1 with `TypeCheckError: Type 'string' is not assignable to type
  'number'`.
- The coverage floor moves from 0.34's flat `coverage.lines/statements/
  functions/branches` keys to 1.x's `coverage.thresholds` wrapper. This
  is the risky half: leaving the flat keys would have made vitest 1.x
  ignore them and the floor would have vanished without a word. Raising
  statements to 99 makes the run exit 1 with `ERROR: Coverage for
  statements (95.12%) does not meet global threshold (99%)`, so the new
  shape is being read.

Switching provider moves the reported numbers, since v8 and istanbul
count differently: core now reports 95.12 statements / 88.69 branches /
100 functions / 95.12 lines against the unchanged 85/75/85/85 floor,
where istanbul reported 91.1 lines / 85.9 branches.

Verified after a clean npm@10 ci: build, typecheck, type tests, all four
suites both plain and with --coverage, smoke, test:scripts, lint,
format-check and the three verify scripts.
…elease

ng-packagr 19 changes what the angular tarball contains, so that needs a
patch. core's move to vitest 1.6 touched only devDependencies and test
config, neither of which ships, so it gets an empty changeset rather than
a version bump that would publish an identical tarball.
eslint 8.57.1 is end of life -- `npm ci` printed "This version is no
longer supported" on every install. Moving to 9 means flat config, since
eslint 9 reads neither .eslintrc.cjs nor .eslintignore.

Both files are replaced by eslint.config.mjs, translated rule for rule.
.eslintignore's contents become the global `ignores`, comment and all:
the example apps and smoke workspace stay excluded because the lint
script never covered them, while the pre-commit hook matches *.js
anywhere and would otherwise lint them against a config written for
library source.

Dependency changes fall out of it:

- @typescript-eslint/{eslint-plugin,parser} 5 -> the `typescript-eslint`
  meta package at 8.67.0, which is what supports flat config. The parser
  is still declared explicitly: eslint-plugin-import resolves it by name
  through eslint-module-utils, and without the declaration 71 of the
  first run's 77 errors were that require failing.
- eslint-config-airbnb-base and eslint-plugin-node are removed. Neither
  appeared in the old config's `extends` or `plugins` -- they had been
  installed but unused.
- eslint-plugin-prettier is removed rather than upgraded. v5 requires
  prettier >=3, which would have dragged the prettier 2 -> 3 reformat
  into this change. It is redundant here anyway: `npm run format-check`
  is already its own CI gate and lint-staged runs `prettier --write`
  before eslint, so linting formatting was a slower second copy of a
  check that already exists. eslint-config-prettier stays, at 10.1.8, to
  keep turning off rules that fight the formatter.
- Stayed on eslint 9 rather than 10 deliberately: eslint-plugin-import
  2.32 declares peers only up to ^9, and this branch opens by removing a
  peer violation, not adding one.

eslint 9 and typescript-eslint 8 then surfaced six real things:

- typescript-eslint 8 flags unused catch bindings by default, which v5
  did not. scripts/build-changed.js had three; they become optional
  catch bindings.
- eslint 9 reports unused disable directives by default. Three were
  stale: a no-empty-interface disable in core/src/types.ts guarding an
  interface that is not empty and a rule v8 has since renamed; an
  import/no-unresolved disable in vue's DynamicInput (narrowed to the
  import/order it still needs); and a no-explicit-any disable in vue's
  defaultRenderers for a rule this config sets to off.

A clean lint run is also what a flat config with a wrong `files` glob
produces, so the rules were checked against a probe file rather than
assumed. On a .ts file, curly, eqeqeq, semi, max-len, prefer-const and
@typescript-eslint/no-unused-vars all fire; on a second probe,
import/order and import/no-unresolved fire. example/, smoke/ and *.d.ts
are still ignored.

Verified after a clean npm@10 ci: build, lint, format-check, typecheck,
core type tests, all four suites, smoke, test:scripts, audit, the three
verify scripts, and all three example app builds. The eslint@8
end-of-life warning is gone from npm ci.
The eslint 9 migration touched core and vue sources too, but only by
removing stale eslint-disable comments. Built dist for all three is
byte-identical to develop, checked by building both revisions and
diffing, so the empty changeset still describes the truth.
@vannt-dev
vannt-dev merged commit 5577177 into develop Aug 14, 2026
10 checks passed
@vannt-dev
vannt-dev deleted the chore/dependency-upgrades branch August 14, 2026 17:39
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.

1 participant