Upgrade ng-packagr, vitest, and eslint where each was actually causing a problem - #34
Merged
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
Both violated, which is why installing in
packages/angularneeded--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, plainnpm installsucceeds, 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.tsandpublic-api.d.tsare byte-identical — but ng-packagr stopped emitting the per-fileesm2022/output in 18, along with itsesm2022/esmexport conditions. The.export keepstypesanddefault, which is what Angular 19+ libraries ship, anddefaultpoints at the same file those conditions already resolved to.In-range updates
npm updateonly — 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.
rimraf3.0.2→6.1.3 at the hoist root is core and angular's already-declared^6.1.3finally resolving there; the 3.0.2 was a transitive from flat-cache, which keeps its own nested copy.vite6.4.3 at the root is a transitive of the newer @analogjs — the vitepackages/reactdeclares 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
--typecheckflag, sotest:typesneeded the 0.34-onlyvitest typecheck --runsubcommand; and the hoisted@vitest/coverage-v8@1.6.1is 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:types→vitest --run --typecheck.only, same scope as before. Appendingconst broken: number = 'nope'totypes.test-d.tsexits 1 withTypeCheckError.thresholdswrapper. 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 withERROR: 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 ciprinted "This version is no longer supported" on every install. eslint 9 reads neither.eslintrc.cjsnor.eslintignore, so both becomeeslint.config.mjs, translated rule for rule with.eslintignore's reasoning carried into theignorescomment.Three dependency decisions worth flagging:
format-checkis already its own CI gate and lint-staged runsprettier --writebefore eslint, so linting formatting was a slower second copy of an existing check.eslint-config-prettierstays to keep turning off rules that fight the formatter.^9. This branch opens by removing a peer violation; adding one at the end would be incoherent.extendsorplugins. 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 ano-empty-interfacedisable 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
filesglob produces, so the rules were checked against probe files rather than trusted. On a.tsfile: 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.tsremain ignored.Release scope
Only
@dynamic-field-kit/angulargets a changeset. core, react and vue changed only tooling that never ships — devDependencies,vitest.config.js, and two stale eslint-disable comments — and their builtdistis 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.