Skip to content

feat(schematics): warn during ng add when firebase is installed at more than one version - #3760

Open
armando-navarro wants to merge 3 commits into
angular:mainfrom
armando-navarro:a38-duplicate-package-detector
Open

feat(schematics): warn during ng add when firebase is installed at more than one version#3760
armando-navarro wants to merge 3 commits into
angular:mainfrom
armando-navarro:a38-duplicate-package-detector

Conversation

@armando-navarro

Copy link
Copy Markdown
Collaborator

Fixes #3754
Refs #3684
Refs #3681
Refs #3682

What this does

ng add @angular/fire now asks the project's own package manager which versions of firebase are installed, and says one of three things: more than one version was found, the check could not be completed, or nothing at all.

It runs after the install task and before the feature prompt, so node_modules is on disk to be asked about and the answer appears whatever the user then selects.

Why

Two copies of the Firebase SDK produce two separate module instances, and objects made by one are rejected by the other. The symptoms name the user's own code, so the cause is not guessable from the error:

  • Expected type '_Query', but it was: a custom _Collection object
  • Type does not match the expected instance

#3684, #3681 and #3682 are all this, and all open. In each case the diagnosis came down to one command a maintainer had to tell the reporter to run, npm ls firebase. This runs it for them.

What a user sees

Real ng add output, from a fresh Angular 21 app with firebase@12.18.0 at the root and firebase@11.10.0 under a local dependency:

✔ Packages installed successfully.
    ⚠️ Your workspace has more than one version of firebase installed.

    firebase: 2 distinct versions, reached by 4 dependent packages (according to npm)
      12.18.0 via @angular/fire
      12.18.0 via @angular/fire > rxfire
      12.18.0 via the workspace root
      11.10.0 via vendor-lib

    Two copies of the firebase SDK loaded by one app become two separate module instances, and they reject each other's objects at runtime with errors that name your own code rather than the duplication. Run npm ls firebase --all for the full tree.
? What features would you like to setup?

The via lines are the part worth having: they name the dependency that pulled the second copy in, which is the fact a user needs in order to act.

On the same app without the duplicate, ng add prints nothing between the install and the prompt.

The rule about silence

Printing nothing is itself a message: a user who reads nothing concludes the install was inspected and is fine. So every path that cannot reach an answer says so instead of staying quiet. A missing package manager, a lockfile written by a newer version, a manager that exits without printing, a project declaring bun, output in an unrecognized shape: each produces a second kind of warning that names what stopped the check.

How it works

  • Detect the manager from what the project declares (packageManager in package.json, cli.packageManager in angular.json), then from its lockfile. Both yarns share yarn.lock, so the lockfile's own first lines decide which (# yarn lockfile v1 for yarn 1.x, a __metadata: block for yarn 2+), with a yarn --version probe as the fallback when no lockfile is readable.
  • Run that manager's own query: npm ls, pnpm -r ls, yarn why, yarn list.
  • Read the four output formats into one shape, one file per manager.
  • Hand the result to setup/duplicateWarning.ts, which is the only place that decides what to say.

Commands run through cross-spawn with an argument array and no shell: true.

Known limit

In a monorepo the question is answered for the whole workspace while ng add was pointed at one project inside it. A project that resolves exactly one version of firebase can therefore be warned about a sibling's version that it does not depend on and cannot load.

This is not fixed here. Scoping the answer is per-manager work: npm scopes if the command runs in the project's own directory, pnpm scopes if -r is dropped as well, and neither yarn scopes by directory at all. The message is worded so it is not false in that case (it explains what two copies loaded by one app do, rather than asserting a conflict has happened) and the via lines name which project each version came through.

Tests

  • 314 unit specs. Parsing is tested against output captured verbatim from real npm, pnpm, yarn 2+ and yarn 1.x installs, because invented fixtures would test a shape I imagined rather than the one each tool emits.
  • One spec launches npm for real, since starting a package manager is the part captured output cannot cover and the part that fails first on Windows.
  • Separately, and not part of CI: an acceptance list run against 16 real installed projects covering all four managers, workspaces, overrides and resolutions, locally linked packages, yarn 2+ virtual locators, and a yarn 1.x project with yarn 2+ on the PATH.

Not included

  • Scoping the answer to one project in a monorepo, described above.
  • Any check outside ng add. This does not run at build time or as a lint rule.

…rejected dependency

workspace.ts holds what more than one schematic needs to know about a user's
workspace on disk: the lockfile-to-manager table, tolerant JSON readers, the
upward walk that finds the directory owning an install, and
assertSafeDependencyName, moved from deploy/actions.ts unchanged.

Files are parsed with jsonc-parser, as utils.ts already does: Angular
tolerates comments in angular.json, and strict parsing silently dropped a
commented file's cli.packageManager declaration. The upward walk also stops
at bun.lock, bun.lockb and deno.lock, which mark the directory owning an
install even though nothing here can query those managers.

assertSafeDependencyName gains a required source parameter naming where the
value came from. A rejected name is useless to a user who is not told which
file to go and edit, and the deploy spec pins that its error still points at
angular.json.
…its own package manager

A package installed at two versions is two module instances, and they reject
each other's objects at runtime with errors naming the caller's code. The
reports that reach this repo are diagnosed by telling the reporter to run
npm ls firebase. This module runs that question itself.

One file per manager: npm, pnpm, yarn 2+ and yarn 1.x each get their command
and their reader, since the four output formats share nothing. index.ts
identifies the workspace's manager from its own declarations before its
lockfiles, and tells the two yarns apart by the lockfile's own header
('# yarn lockfile v1' against an __metadata: block), probing yarn --version
only when no lockfile is readable: the binary on PATH and the project
disagree in corepack's default state. The query runs through one spawn
wrapper (cross-spawn, argument array, no shell) and reports entries,
distinct versions and problems without rendering any verdict.

Finding nothing is ambiguous, so every path that cannot reach an answer
records a problem: silence downstream has to mean checked and fine.

Parsing specs run against output captured verbatim from real installs of all
four managers. One spec launches npm for real, which is the part captured
output cannot cover and the part that fails first on Windows.

Known limit, deliberate: in a monorepo the question is answered for the whole
workspace while the caller was pointed at one project inside it, so a project
resolving one version can be warned about a sibling's.
…re than one version

Fixes angular#3754. ng add runs the check after the install task and before the
feature prompt, so node_modules is on disk to be asked about and the warning
appears whatever the user selects.

duplicateWarning.ts owns the verdict the reporter refuses to render. It says
one of three things: more than one version found, with each version's
dependency chain named so the user can see what pulled the second copy in; the
check could not be completed, with the reasons; or nothing, which a reader may
take as checked and fine. The whole body is inside one try, because this is a
courtesy and an exception here must not abort ng add.

Verified end to end against the built tarball: a fresh Angular 21 app with a
planted second firebase warns between the install and the prompt, and the same
app without the duplicate prints nothing.
@armando-navarro armando-navarro added bump: minor comp: schematics ng add / deploy schematics (src/schematics). type: feature New capability or enhancement. labels Sep 1, 2026

@tyler-reitz tyler-reitz left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. I checked the parts only running can settle.

The real-spawn spec really does spawn: pointing executableFor at a binary that doesn't exist turns exactly that spec red, so it isn't quietly short-circuiting into the captured-output path. 314 specs, 0 failures here.

The no-silent-failure rule holds across all four managers. Feeding each parser nonsense gives npm and pnpm a throw, which findInstalledCopies converts into a problem, and both yarns a 2 lines of the output could not be read problem. None of them returns an empty answer that would read as clean.

assertSafeDependencyName moving into workspace.ts is behavior-preserving, which felt worth checking properly since it's the guard from the injection fixes: same function object re-exported, 17 hostile names still rejected, 4 legitimate ones still accepted, and the message at the deploy call site is byte-identical.

cross-spawn isn't in src/package.json, which looked like #3747 again until I checked the output. esbuild inlines it, no require("cross-spawn") in the built bundle, so it's fine as is.

One note, not blocking: defaultTimeoutMs is 30 seconds and nothing is printed while the query runs, so a wedged package manager buys the user half a minute of silence between the install and the prompt.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bump: minor comp: schematics ng add / deploy schematics (src/schematics). type: feature New capability or enhancement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ng add should warn when the workspace has more than one version of firebase installed

2 participants