MemberJunction / MemberJunction/MJ
mj dev workspace: lockfile pin derivation decides each package independently, pinning auth wrapper libs and their inner SDKs from different members — generated workspace fails the Explorer build
- Dominant language
- TSQL
- Stars
- 29
- Forks
- 6
- Avg merge
- 2d 1h
- Merged PRs (30d)
- 323
Description
## Summary
`mj dev workspace` derives the parent `pnpm.overrides` pins one package name at a time. Each name's pin is decided from whichever members happen to declare that name *directly*, and the highest exact resolution among them wins. Nothing checks that the resulting set of pins is co-installable. For the auth SDK family that is enough to produce a set no member's lockfile ever resolved, and the generated workspace fails the MJExplorer build on its first run.
## Environment
- MJ `next`, CLI `@memberjunction/cli/6.1.0-edge.4`, Node 24.14.1, pnpm 10.33.0, macOS arm64.
- 17 member repos: `MJ` plus the BizApps repos. 15 of them commit a lockfile (`MJ` and the pnpm-based BizApps repos commit `pnpm-lock.yaml`; `bizapps-sonar`, `bizapps-secure-messaging` and one internal repo commit `package-lock.json`).
## What happened
Generated `pnpm.overrides` (the auth entries only):
```
"@auth0/auth0-angular": "2.10.0"
"@auth0/auth0-spa-js": "2.14.0"
"@azure/msal-angular": "5.3.1"
"@azure/msal-browser@^5": "5.20.0"
"@azure/msal-common@^16": "16.11.1"
```
`pnpm install` succeeds. `ng build` for MJExplorer then fails with esbuild `No matching export` errors: the `Passkey*Error` classes `@auth0/auth0-angular` imports are not exported by `@auth0/auth0-spa-js` 2.14.0, and `JsonWebTokenAlgorithms` / `DEFAULT_TOKEN_BINDING_KEY_MANAGER`, which `@azure/msal-browser` 5.20.0 imports, are not exported by `@azure/msal-common` 16.11.1.
Neither pairing exists in any member's lockfile. MJ's own lockfile is internally consistent: auth0-angular 2.6.0 with spa-js 2.14.0, msal-browser 5.17.0 with msal-common 16.11.1, msal-angular 5.0.3.
## Root cause
`DeriveLockfilePins` in `packages/MJCLI/src/lib/dev-workspace/lockfile.ts` (the derivation at :437-467, its authority rule documented at :416-436, the winner picked by `decideMajorPin` at :403-414) groups candidate resolutions by `(name, major)` and takes the highest exact version. The authority rule admits a version only where some member declares that name *directly* in one of its importers; at-depth resolutions do not vote.
Applied per name, that gives each name a different voting population, and siblings in one SDK family end up decided by different members:
| Override | Voting members (direct declarations) | Pin |
|---|---|---|
| `@auth0/auth0-angular` | MJ 2.6.0, `bizapps-sonar` 2.10.0 | 2.10.0 |
| `@auth0/auth0-spa-js` | MJ 2.14.0 only — sonar's 2.23.0 and secure-messaging's 2.24.1 are transitive | 2.14.0 |
| `@azure/msal-angular` | MJ 5.0.3, sonar 5.3.1, secure-messaging 5.3.1 | 5.3.1 |
| `@azure/msal-browser` | MJ 5.17.0, sonar 5.17.0, secure-messaging 5.20.0 | 5.20.0 |
| `@azure/msal-common` | MJ 16.11.1, sonar 16.11.1 — secure-messaging's 16.14.0 is transitive | 16.11.1 |
The wrapper libraries (`auth0-angular`, `msal-angular`, `msal-browser`) are declared by the app shells of several members, so they get the highest resolution across all of them. Their inner SDKs (`auth0-spa-js`, `msal-common`) are declared directly only where MJ declares them, so they keep MJ's older resolution. The generator then writes both, and the pair does not build.
Two things make this reachable rather than theoretical:
1. **Nothing validates the assembled set.** `AssembleParentOverrides` (`build.ts:509-530`) layers lockfile pins under member overrides under family `workspace:*` and reports displacements, but there is no check that a wrapper's pin and its dependency's pin come from the same committed graph.
2. **Importers the generator deliberately excludes from the workspace still vote.** The winning `msal-browser` and `auth0-angular` versions come from `apps/MJExplorer` importer entries in `bizapps-sonar` and `bizapps-secure-messaging`. `SelectPackagesGlobs` (`detect.ts:263`, doc at :250-262) drops `apps/*` globs on purpose so those shells never become workspace members, and `memberGlobLines` (`build.ts:166-180`) throws on any positive non-`packages/` glob that reaches it. But `ParseNpmLockfile` / `collectNpmImporterDeps` (`lockfile.ts:298-326` and :282-295) treat every non-`node_modules` key as an importer, and `parsePnpmImporters` (:136-159) does the same for pnpm lockfiles. So a package that is excluded from the generated workspace by design still decides that workspace's pins.
The auth family is the visible case because MJ declares those packages as peers that the shell must supply (`SHELL_PROVIDED_PEERS`, `build.ts:89-106`), so every Open App repo carries its own copy in its own shell. The same shape applies to any wrapper/inner pair where the wrapper is more widely declared than its dependency.
## Workaround
Edit the generated `package.json` and realign the wrapper pins to MJ's lockfile: `@auth0/auth0-angular` 2.10.0 to 2.6.0, `@azure/msal-browser@^5` 5.20.0 to 5.17.0, `@azure/msal-angular` 5.3.1 to 5.0.3, then `pnpm install`. MJExplorer builds clean after that. The edit is lost on any `mj dev workspace` regeneration.
## Notes
A fix could take several shapes — deriving one member's whole graph as the authority rather than a per-name maximum, restricting the vote to importers the workspace actually includes, or validating co-installability before writing. Happy to send a PR once you say which direction you prefer.
Contributor guide
Research direction
Start with DeriveLockfilePins in packages/MJCLI/src/lib/dev-workspace/lockfile.ts, then trace AssembleParentOverrides in build.ts and importer filtering in detect.ts. Compare the available authority, workspace-scope, and co-installability approaches with the maintainer; done means regeneration produces compatible dependency pins and MJExplorer builds without manual package.json edits.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- angular, javascript, node.js, typescript
- Domain
- build-system, cli, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100