Dev Workspace Quickstart — mj dev workspace
One sentence: mj dev workspace turns a plain folder of sibling repo clones (MJ + any Open App repos) into one pnpm workspace, so an edit in any repo is live in every other repo in about a second — with nothing committed to any repo.
This is the practical setup guide. The normative spec (what the generator MUST do and why) is
OPEN_APP_WORKSPACE_LINKING_SPEC.md.
Availability
Section titled “Availability”The command lives in @memberjunction/cli on next (merged 2026-08-14). Until the next edge
release ships it, run it from an MJ source checkout — that works today and is the flow below.
Once the edge release is out, a global npm i -g @memberjunction/cli@edge gives you the same
commands as plain mj dev workspace ....
Prerequisites
Section titled “Prerequisites”- Node 20+ with corepack enabled (
corepack enable) — pnpm versions are pinned per repo and the generated workspace pins its own; corepack fetches them automatically. gh auth login(the app repos are private).
Setup (one time, ~10 minutes)
Section titled “Setup (one time, ~10 minutes)”-
Make a plain parent folder. Not a git repo — the generator refuses a git-repo-root parent on purpose. Sibling clones go inside it:
Terminal window mkdir ~/dev/mj-workspace && cd ~/dev/mj-workspacegit clone -b next https://github.com/MemberJunction/MJ.gitgh repo clone MemberJunction/bizapps-common # default branch is main — that's correctgh repo clone MemberJunction/bizapps-tasks -- -b next# ...any other member repos you want linked -
Bootstrap-build MJ once (you need the CLI before the workspace exists):
Terminal window cd MJ && pnpm install && pnpm run build && cd .. -
Generate the workspace. From the parent folder:
Terminal window node MJ/packages/MJCLI/bin/run.js dev workspaceIt detects members (anything with
mj-app.json,@mj-biz-apps/*packages, or the MJ monorepo itself), writes four files at the parent (pnpm-workspace.yaml,package.json,.npmrc,turbo.json) plus a.mj-dev-workspace.jsonsentinel, then runs the install.When it offers to remove members’ own standalone installs, say yes — the MJ clone’s
node_modulesfrom step 2 must be replaced by the workspace install, or MJ’s code resolves against a second package store (split singletons, baffling type errors). This is the most common first-run mistake; the prompt (and--clean-members) exists because of it. -
Build everything from the parent and check health:
Terminal window pnpm run build # turbo across all membersnode MJ/packages/MJCLI/bin/run.js dev workspace status
Daily loop
Section titled “Daily loop”- Edit code in any member repo. Build just that package from the parent:
pnpm --filter <package-name> run build. Consumers see it immediately via workspace links. ... dev workspace statusany time you’re unsure of workspace health — it names problems and fixes.- Set
MJ_DEV_WORKSPACE_DIR=/path/to/parentin your shell profile to run the commands from anywhere without--dir.
What the generator handles for you (v2)
Section titled “What the generator handles for you (v2)”- Member globs come from each repo’s own
pnpm-workspace.yaml— nested package layouts work. - MJ’s
overridesandpatchedDependenciesare hoisted to the parent (patch paths re-rooted), so pnpm doesn’t silently re-resolve hundreds of packages away from MJ’s committed lockfile. - Versions are pinned from each member’s committed lockfile (exact, per-major) — the workspace reproduces what each repo’s own CI installs instead of re-floating to latest.
- Genuine version conflicts between members are reported, not hidden (highest committed version wins; the report names every declaring package).
Open App client packages
Section titled “Open App client packages”Every entry in a member’s mj-app.json packages.client[] and packages.shared[] is
registered in the generated parent package.json as dependencies at workspace:*, at every
role. That set is not a choice — it mirrors the host exactly: GetClientPackagesFromManifest
builds dynamicPackages.client as [...client, ...shared] with no role filter, and
mj codegen manifest --open-app-client-bootstrap turns every entry there into an import in the
shell’s generated class-registrations manifest. Anything narrower leaves a package the shell
imports and nothing declares, so pnpm never links it. (packages.server[] is excluded: it goes to
dynamicPackages.server, a Node process that resolves importer-relative, not from the vite root.)
It is also deliberate over-linking: a package is linked whether or not a host has registered it,
because linking and registration are independent
(OPEN_APP_WORKSPACE_LINKING_SPEC.md §17) — an unregistered
package resolves but does not load.
If one of those packages declares a peer your app shell does not, the generator warns and names
it with the version the parent already pins; it does not add it for you. Declare it in the
shell’s own package.json, the way MJExplorer declares @angular/service-worker and
@angular/elements. An Angular dev server externalizes @angular/* and resolves it from the
vite root (the shell), so a copy in the library’s own node_modules is never consulted — which
is why this fails at page load with a completely green build.
mj dev workspace doctor fails when a member’s declared client package is not linked at the
parent. A package declared by a repo that is not a workspace member is ignored (it can never be
linked — that is what excluding it means), and the check skips entirely until pnpm install has
run at the parent, since linkage is the install’s output rather than the generator’s.
Recovery
Section titled “Recovery”Someone (or an IDE) ran npm install / pnpm install inside a member repo? status will flag
it loudly (STANDALONE INSTALL: <member> ...). Fix:
node MJ/packages/MJCLI/bin/run.js dev workspace --force --clean-membersTeardown
Section titled “Teardown”node MJ/packages/MJCLI/bin/run.js dev workspace cleanRemoves only the generated parent files (sentinel-verified — it refuses to delete a workspace it
didn’t generate unless you --force). Member repos are untouched and stay git-clean throughout;
the workspace never commits anything to any repo.
Gotchas
Section titled “Gotchas”| Symptom | Cause / fix |
|---|---|
| Generator refuses to run | The parent is a git repo root. Use a plain folder of sibling clones. |
| A member won’t build from its own directory | The repo is still npm-pinned (packageManager: npm@...) — corepack refuses pnpm there. Build it from the parent with pnpm --filter <pkg> run build. The pnpm migration (in progress across the app repos) removes this. |
Split singletons / two copies of @angular/core / weird type mismatches | A member has its own standalone install — see Recovery above. |
| Peer-dependency warnings at install | Expected while app repos’ ^5.x MJ peers meet a 6.x source checkout; era-based comparison is the planned fix. Warnings, not errors. |
| Existing parent files in the way | The generator never overwrites without --force, and --force writes .bak copies first. |