fix(schematics): pin prerelease installs to the exact version#3709
Conversation
ng add records the installed package with npm's default caret prefix. For a stable release that only drifts to reviewed patch releases, but a prerelease range like ^21.0.0-rc.0 also matches the canary build published for every merge to main — so a later fresh install could silently replace the version the user chose with an unreviewed snapshot. After install, if the workspace's @angular/fire entry is a caret or tilde range around a prerelease, rewrite it to the exact installed version (only when that version already satisfies the range). Stable ranges and hand-authored range expressions are left untouched.
| * directories up. Returns undefined when that layout doesn't hold (e.g. running from source), | ||
| * so callers skip pinning instead of failing the schematic. | ||
| */ | ||
| const readInstalledVersion = () => { |
There was a problem hiding this comment.
Optional/nit, design suggestion: tools/build.ts already stamps values into versions.json at build time, and add/index.ts already imports from it. Adding angularFireVersion there would let you replace the readFileSync/__dirname traversal with a plain JSON import, same pattern already in use and no filesystem-path assumptions.
There was a problem hiding this comment.
This one turned out more interesting than expected. Trying it revealed that versions.json values are actually dead at runtime: esbuild bundles the schematics and inlines the JSON import from the source stub at build time, so the post-build write to versions.json (the mechanism add/index.ts relies on) is never read by the shipped code. Extending it would have shipped a permanently disabled pin.
So I took your underlying idea — resolve the version at build time, drop the __dirname traversal — via a different mechanism: a placeholder string the build replaces with the real version in the compiled output, with the build failing hard if the placeholder isn't substituted (so a broken pin can't ship silently). Same outcome you were after, without depending on the broken versions.json path.
I've filed the versions.json-is-dead-at-runtime problem as #3715 for a proper fix — it also explains why addDependencies is currently a no-op.
- warn (instead of silently skipping) when the installed version can't be determined - pin the @angular/fire entry whether it sits in dependencies or devDependencies - resolve the installed version from a build-written placeholder rather than a runtime readFileSync/__dirname traversal; the build fails if the placeholder is not substituted, so a broken pin can't ship silently
ng add @angular/fire@nextrecords the install with npm's default caret (^21.0.0-rc.0). A prerelease range like that also matches the-canary.<sha>builds published for every merge to main — they sort higher than the RC — so any later fresh install silently replaces the version the user chose with an unreviewed snapshot.After install, the schematic now pins the workspace's
@angular/fireentry to the exact installed version when the recorded spec is a caret or tilde range around a prerelease (and only when the installed version already satisfies that range). Stable ranges, exact versions, and hand-authored range expressions are left untouched. Includes unit specs for the pinning and all leave-alone cases.Fixes #3708