Skip to content

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.

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 ....

  • 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).
  1. 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-workspace
    git clone -b next https://github.com/MemberJunction/MJ.git
    gh repo clone MemberJunction/bizapps-common # default branch is main — that's correct
    gh repo clone MemberJunction/bizapps-tasks -- -b next
    # ...any other member repos you want linked
  2. Bootstrap-build MJ once (you need the CLI before the workspace exists):

    Terminal window
    cd MJ && pnpm install && pnpm run build && cd ..
  3. Generate the workspace. From the parent folder:

    Terminal window
    node MJ/packages/MJCLI/bin/run.js dev workspace

    It 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.json sentinel, then runs the install.

    When it offers to remove members’ own standalone installs, say yes — the MJ clone’s node_modules from 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.

  4. Build everything from the parent and check health:

    Terminal window
    pnpm run build # turbo across all members
    node MJ/packages/MJCLI/bin/run.js dev workspace status
  • 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 status any time you’re unsure of workspace health — it names problems and fixes.
  • Set MJ_DEV_WORKSPACE_DIR=/path/to/parent in your shell profile to run the commands from anywhere without --dir.
  • Member globs come from each repo’s own pnpm-workspace.yaml — nested package layouts work.
  • MJ’s overrides and patchedDependencies are 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).

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.

Someone (or an IDE) ran npm install / pnpm install inside a member repo? status will flag it loudly (STANDALONE INSTALL: <member> ...). Fix:

Terminal window
node MJ/packages/MJCLI/bin/run.js dev workspace --force --clean-members
Terminal window
node MJ/packages/MJCLI/bin/run.js dev workspace clean

Removes 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.

SymptomCause / fix
Generator refuses to runThe parent is a git repo root. Use a plain folder of sibling clones.
A member won’t build from its own directoryThe 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 mismatchesA member has its own standalone install — see Recovery above.
Peer-dependency warnings at installExpected 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 wayThe generator never overwrites without --force, and --force writes .bak copies first.