Skip to content

T5-labs/Project-Mandrake

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

96 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Project-Mandrake

A containerized MSSQL database host and full dev-stack runner for CMMS development. Brings up a SQL Server 2022 container with a production-like baseline so you can run EF migrations, test schema changes, and snapshot/restore database state — all without touching your host SQL Server — and (via mandrake up / autopilot / the VS Code extension) wires the cmms-api clone to it and launches the frontend + backend dev servers.

Two ways to drive it: the mandrake CLI and a VS Code extension panel (Play/Stop, toggles, port fields, instance badge). Both read and write the same shared config at ~/.mandrake/config.json, so they stay in sync.

You can run multiple CMMS clones concurrently — each as its own isolated Mandrake instance (own container, ports, config, and snapshot store). And the whole thing has an autopilot "just works" mode that forces the right toggles and auto-allocates ports.

🚀 Setting up a fresh machine?

Follow the complete, beginner-proof Installation Guide → docs/INSTALL.md. It takes you from nothing — WSL, Docker, the Windows SQL tools, the mandrake command, and the VS Code extension path — step by step, with the path-sensitive bits auto-filled so there's nothing to hand-edit, plus a troubleshooting section keyed to every common failure. The sections below are the quick reference for people who've already done this.

Install the VS Code Extension

Requires Docker Desktop with WSL 2 integration enabled — see Prerequisites before installing. Without Docker, the harness cannot run.

Clone the repo, then run the one-command installer. The script installs deps, builds, packages, sanity-checks the bundle, and force-installs — works on a fresh clone or as an update after a pull:

git clone https://github.com/T5-labs/Project-Mandrake.git ~/Project-Mandrake
cd ~/Project-Mandrake
./vscode-extension/scripts/reinstall.sh

Run it from anywhere — it self-locates. Re-run it the same way whenever you pull new changes.

On a corporate or proxied network, watch the npm install output for esbuild / native-binary download failures: a partial install leaves the build with no compiled output, so vsce package produces a .vsix that has no entry point — it installs fine but the extension never activates ("stuck on loading"). The script's built-in unzip -l check catches this before installing.

Then reload VS Code (Ctrl+Shift+P -> "Developer: Reload Window"). So the extension works from any open repository, add this to your VS Code User settings.json:

"mandrake.binPath": "/home/<your-username>/Project-Mandrake/src/bin/mandrake"

This must be a full absolute path — ~ is not expanded in this setting, so a value like ~/Project-Mandrake/src/bin/mandrake will silently fail (the binary shows as unresolved and no commands run). Use the real /home/<your-username>/... path instead.

Open any repo, run Mandrake: Open Panel from the command palette, and you're live.

Updating the extension

From the panel (recommended): click the up-arrow Update Extension button in the top directory row of the Mandrake panel (tooltip "Update Extension"), or run Mandrake: Update Extension from the command palette. The extension runs reinstall.sh for you, streams output to a "Mandrake Update" output channel, and on success prompts Reload Window / Later — the whole update + reload is one click from inside VS Code.

Prerequisite: mandrake.binPath must be set (see above) so the extension can locate your checkout to run the script. If it is unset, the button surfaces the error "Set mandrake.binPath to your Project-Mandrake checkout before updating." with an Open Settings action.

From the terminal: re-run the same command from the repo root:

./vscode-extension/scripts/reinstall.sh

The script checks the remote for a newer version by version number: it fetches the upstream branch, reads the extension version from the remote's package.json, and compares it against the installed extension version (via code --list-extensions). If the installed version already matches the remote it reports "already up to date" and exits without building or installing anything — even when your local checkout happens to be current, a stale installed build gets caught and rebuilt. If the installed version differs, it rebuilds and force-installs; it only pulls remote code first when your local checkout is actually behind the remote version (avoiding a needless pull that would fail on a dirty tree). After building, it cleans old .vsix artifacts, installs any new deps, builds, packages, and force-installs the extension.

Maintainers testing uncommitted local changes can bypass the remote check entirely with --local:

./vscode-extension/scripts/reinstall.sh --local

This builds whatever is currently on disk (your working tree, local branches, etc.) without touching the remote.

After it finishes, reload VS Code (Ctrl+Shift+P -> "Developer: Reload Window").

Highlights

  • Containerized SQL Server: A production-like SQL Server 2022 baseline runs in a Docker container on localhost:14331, so you never have to touch or risk your host SQL Server.
  • Full dev-stack runner: Mandrake doesn't just host the DB, it wires the cmms-api clone to the container and launches both frontend and backend dev servers in one move.
  • Dual control surfaces: Drive the entire harness from either a polished mandrake CLI or a VS Code extension panel, and they never drift because both read and write the same ~/.mandrake/config.json.
  • Single source of truth config: One shared config file (clone dir, ports, lifecycle toggles, run commands) keeps the CLI and the extension in perfect lockstep, with live container state reflected in the panel.
  • Multi-instance isolation: Run as many CMMS clones concurrently as you want, each with its own container, volume, Compose project, ports, config subtree, and snapshot store, colliding with nothing.
  • Autopilot "just works" mode: Flip one master switch and Mandrake forces the right toggles and auto-allocates collision-free ports as a non-destructive overlay, then restores exactly what you had when you turn it off.
  • One-command stack lifecycle: mandrake up brings the whole world online (container, auth bypass, and detached frontend/backend dev servers) and mandrake down tears it all back down cleanly.
  • LAN sharing and mobile access: Flip on mandrake up --lan (or the extension's Share on Network toggle) and your running instance is reachable across the network, so teammates can open it from their own machines and you can trial the app on a phone or tablet over corporate Wi-Fi.
  • Instant snapshots: Capture full database state to a .bak in roughly five seconds, switch between named baselines on a whim, and share them with teammates, surviving Docker restarts.
  • Four-tier safety net: From a 5-second snapshot reset to a full nuclear rebuild, every "oops" has a graded, documented recovery path.
  • First-class EF migration tooling: A complete mandrake migrations suite (update, add, applied, merge, test, parity, preview) wires every dotnet ef call directly to the container connection string, never reading from .env or app config.
  • Branch-aware migration merge: mandrake migrations merge orchestrates the entire rebase-your-migration-onto-a-remote-branch dance (rollback, remove, merge, re-scaffold, and test) and halts cleanly on conflict.
  • Auto branch-sync watcher: mandrake watch polls your git branch and re-syncs the container DB on every switch, optionally redeploying the whole app, fully hands-free.
  • FILESTREAM tables made restorable: We strip the unrestorable FILESTREAM filegroup, recreate the three tables as VARBINARY(MAX) stubs, and backfill their real data from the host via bcp, so migrations and file metadata just work on a Linux container.
  • Per-developer baseline generation: mandrake create builds the baseline from your own host CMMS DB in 2-3 minutes, with an offline --from-bak path for machines with no host SQL Server at all.
  • Pipeline provisioning for CI: A non-interactive provision entrypoint spins up a full, health-gated CMMS instance for any git branch and prints one machine-parseable URL line, auto-destroying itself on failure so nothing orphans.
  • On-demand review deploys: provision review autonomously picks a free lane, deploys a branch on isolated ports, health-gates it, and hands back a single shareable LAN URL for teammates to test.
  • Global activity logging: Every command invocation is logged to one global, instance-tagged ~/.mandrake/mandrake.log with start/outcome lines and 7-day retention, filterable by instance.

Prerequisites

  • Docker Desktop installed with WSL 2 integration enabled. Install from https://www.docker.com/products/docker-desktop/. Enable WSL 2 integration in Settings > Resources > WSL Integration. Verify with docker version from WSL.
  • Host SQL Server reachable at localhost,1433 with a cmms database. mandrake create generates the container baseline from your own host CMMS DB (it is no longer shipped). Your host SQL Server must be running and reachable via trusted (Windows) auth, and the Microsoft SQL command-line tools (sqlcmd + bcp) must be installed — these ship with SSMS / the SQL Client SDK.
  • dotnet-ef global tool:
    dotnet tool install --global dotnet-ef
    echo 'export PATH="$PATH:$HOME/.dotnet/tools"' >> ~/.bashrc
    source ~/.bashrc
    dotnet ef --version   # verify

Install

# Add mandrake to your PATH (one-time shell config)
# Replace <path-to-clone> with wherever you cloned this repo
echo 'export PATH="$PATH:<path-to-clone>/src/bin"' >> ~/.bashrc
source ~/.bashrc

# First-time setup: generate baseline from your host cmms DB, start container,
# restore it, create cmms login, and backfill file-attachment data
mandrake create            # (~2-3 min, one-time; host SQL Server must be reachable)

# Verify
mandrake status
# Expected:
# [mandrake] Container: Up X minutes
# [mandrake] Database: localhost:14331
# [mandrake] Login: cmms / cmms
# [mandrake] Snapshot: main

mandrake doctor            # read-only health check (Docker, toolchain, host SQL, ports, seed state)

That's it — containerized CMMS is live on localhost:14331. SA password is Mandrake!DevPass1. A cmms SQL login (password: cmms) is also created for CMMS application compatibility.

Offline bootstrap (no host SQL Server)

On a machine with no host MSSQL to generate the baseline from, drop a pre-supplied src/db-backups/main.bak in place and run:

mandrake create --from-bak     # restore the supplied main.bak; no host SQL Server needed

This skips the host strip + file-attachment backfill; everything else (restore, cmms login, FILESTREAM stubs, manifest pointer) still runs. Requires db-backups/main.bak to be present and non-empty.

Daily use

Just the DB (migration work)

cd ~/cmms/cmms-api             # any cmms-api clone

mandrake start                 # starts container
mandrake migrations update     # apply any pending migrations from the clone

# If the migration blows something up:
mandrake snapshots reset       # ~5s rollback to current.txt's snapshot (main by default)

# End of session:
mandrake stop                  # stop container; volume preserved, DB intact

The whole dev stack (up / down)

mandrake up is the CLI equivalent of the extension's Play button: it starts the container, applies auth bypass (or points the dev proxy when bypass is off), and launches the frontend + backend dev servers as detached background process groups (no terminals), logging to ~/.mandrake/run/<frontend|backend>.log. Both tiers always run (there is no per-tier opt-out). It honors the shared config (authBypassDesired/ports/run-commands/cloneDir).

mandrake up                    # bring the full stack up (container + bypass + dev servers)
mandrake up --lan              # use the LAN run command + URL
mandrake up --no-browser       # don't open the browser
mandrake up --attach           # tail the logs; Ctrl+C tears the stack down
mandrake up --autopilot        # one-shot autopilot (forces toggles + auto-ports for this run)

mandrake down                  # kill the dev servers, restore proxy/bypass, stop the container

Autopilot — "just works" mode

mandrake autopilot on flips a per-instance master switch. While on, Mandrake forces sandboxMode and authBypassDesired ON and auto-allocates ports (forbidding the CMMS defaults 1433/7266/4200) as a non-destructive overlay — your stored toggles aren't overwritten, so autopilot off restores exactly what you had. (Running both the frontend and backend is now inherent on every up, so it's no longer a toggle autopilot needs to force.) Toggle it from the CLI, with mandrake up --autopilot for a one-shot, or the Autopilot switch at the top of the extension panel. In the extension, Autopilot is blocked while Bypass Auth has drifted (the amorphic bypass patches can't place cleanly on the clone — i.e. an auth file changed at an injection point) and is force-disabled if it was on — Autopilot would otherwise auto-reshuffle ports over a broken bypass; regenerate the bypass baselines/templates for the drifted file first.

mandrake autopilot on          # forces toggles + auto-ports as an overlay
mandrake autopilot status      # show current mode
mandrake autopilot off         # restore your stored toggles

Auto branch-sync (sync / watch)

mandrake sync <branch>         # one-shot: sync the container DB to <branch>'s migrations
mandrake watch start           # detached poller: re-syncs on a branch change (5s poll)
mandrake watch status          # show the watched branch
mandrake watch stop            # stop the poller

sync main/master restores the baseline; a feature branch restores/builds its per-branch snapshot. watch honors autoBranchSync + sandboxMode (re-read each tick) and logs to ~/.mandrake/run/watch.log. Set redeployOnSwitch (default false) to have watch REDEPLOY the whole app (db → backend → frontend) on a branch change instead of only syncing the DB — stop the app before switching for a clean fresh deploy.

Running several clones at once (multi-instance)

Each Mandrake instance is fully isolated: its own container cmms-db-<id>, volume, Compose project, ports, config subtree, and snapshot store (~/.mandrake/instances/<id>/db-backups). Select an instance per shell with the MANDRAKE_INSTANCE env var, or per VS Code window with the mandrake.instanceId setting ("" = the legacy default instance, "auto" = derive a unique id from the clone path, or a custom name to share a named instance across windows).

MANDRAKE_INSTANCE=ticket-2608 mandrake up    # this clone's own container + ports

With no MANDRAKE_INSTANCE set, behavior is byte-identical to the single-instance setup (container cmms-db, store under $MANDRAKE_HOME/db-backups).

Running two or more repos simultaneously? If both VS Code windows are left at the default (mandrake.instanceId = ""), they both resolve to default — the same container, ports, and snapshot store. Set each window's mandrake.instanceId to "auto" (recommended) or a distinct literal so every repo gets its own isolated instance. Activity logs are global (~/.mandrake/mandrake.log) but every line is tagged with the instance id; filter with mandrake logs --instance <id> or mandrake logs --all.

Snapshots

Snapshots are the core safety mechanism. Each snapshot is a .bak file in src/db-backups/ that captures the full database state at a point in time. They persist on disk, survive Docker restarts, and can be shared with teammates.

Command What it does
mandrake snapshots add NAME Save current DB state as a new snapshot (main is protected)
mandrake snapshots switch NAME Restore a different snapshot (~5s)
mandrake snapshots reset Re-restore the current snapshot
mandrake snapshots remove NAME Delete a snapshot (can't remove main)
mandrake snapshots List all saved snapshots
mandrake snapshots compatible Compare snapshots against clone migrations (GREEN/YELLOW/RED)
mandrake snapshots import <dir> Seed this instance's snapshot store from an existing db-backups dir (clean swap; confirms, --yes to skip)
mandrake snapshots export <dir> Back up this instance's snapshot store to a folder (non-destructive; creates a timestamped subdir; re-import it with import)

Bank a ticket-specific baseline after setting up test data, then switch back and forth between baselines instantly:

mandrake snapshots add ticket-2608-baseline
# ... later ...
mandrake snapshots switch main     # back to clean state

Migrations

All migration commands live under mandrake migrations .... Run from a cmms-api clone root. All dotnet ef calls use --connection with the container connection string directly — they never read from .env or app config.

  • mandrake migrations — List migrations in the active snapshot (reads from the snapshot manifest, not the live DB).
  • mandrake migrations NAME — Show migrations captured when snapshot NAME was saved.
  • mandrake migrations update — Apply all pending migrations from the current clone and update the snapshot manifest. Blocked while on the main snapshot — switch to a branch snapshot first.
  • mandrake migrations update NAME — Move the DB to a specific migration (forward or backward). Same main snapshot protection applies.
  • mandrake migrations applied — List the migration ids actually applied to the ACTIVE database (the container in Sandbox Mode, or the real appsettings DB when Sandbox is OFF), read from __EFMigrationsHistory. Used by the extension's parity view.
  • mandrake migrations add NAME — Scaffold a new EF migration against the container DB. Equivalent to dotnet ef migrations add NAME but wired to the container automatically.
  • mandrake migrations remove — Remove the most recent migration: revert the container DB to the prior migration, then dotnet ef migrations remove. Refuses to remove the only/base migration. Leaves the change uncommitted in your working tree. (Sandbox-only.)
  • mandrake migrations squash — Collapse every migration above the last stable release (cmms-release-*) into one new migration named <release>-squash. Auto-detects the release floor (no selection needed), backs up the squashed files, reverts the DB to the release, removes them, and re-scaffolds one migration. Detect-and-choose on custom SQL: if none of the squashed migrations had migrationBuilder.Sql(...) it runs migrations test automatically; if any did, it halts and hands you the backups to reassemble the SQL first. (Sandbox-only.)
  • mandrake migrations merge [BRANCH] — Sync your migration with a remote branch (default: dev). Must be on a feature branch, not the target branch. Working tree must be clean. The flow: roll DB back to the stable release, remove the latest migration, commit the removal, fetch + merge origin/BRANCH, re-scaffold the migration with its original name, then run mandrake migrations test. Halts on merge conflict, build failure, or custom SQL — fix and re-run.
  • mandrake migrations test — Standalone up/down/up test cycle against the container DB. Returns pass/fail with error output on failure.
  • mandrake migrations parity — Compare the active snapshot's migration manifest against migration files in the cmms-api clone. Reports in-sync, pending, and orphaned counts. No container needed — reads from .migrations.txt files offline.
cd ~/cmms/cmms-api
mandrake migrations                     # list migrations in the active snapshot
mandrake migrations update              # apply pending migrations from the clone
mandrake migrations update SomeMigration  # move DB to a specific migration
mandrake migrations add MyNewMigration  # scaffold a new migration against the container
mandrake migrations remove              # remove the latest migration (revert DB to prior)
mandrake migrations squash              # collapse everything above the last release into one
mandrake migrations merge               # sync with dev (default)
mandrake migrations merge feature/other  # sync with a specific branch
mandrake migrations test                # standalone migration test
mandrake migrations parity              # check migration alignment (offline, no container needed)

Safety net

Level Command Time What it does
1 mandrake snapshots reset ~5s Restore DB from last saved snapshot (current.txt)
2 mandrake snapshots switch <name> ~5s Restore from a different named snapshot
3 mandrake destroy && mandrake create && mandrake snapshots switch <name> ~2-3 min Full rebuild of the baseline from your host cmms DB, then restore a specific snapshot
4 mandrake destroy && mandrake create ~2-3 min Nuclear: full teardown and rebuild of the baseline from your host cmms DB (use when the volume itself is corrupted; host SQL Server must be reachable)

Architecture

Containerized SQL Server 2022 Developer edition listens on container-internal port 1433, mapped to host port 14331 so it can coexist with the host's Windows SQL Server (which keeps port 1433). The baseline is generated per-developer at mandrake create time from your own host cmms DB — it is not shipped. The flow: strip-filestream.sql runs on the host (driven by sqlcmd -v STRIP_DIR=...), making a COPY_ONLY backup of cmms, restoring it as a throwaway cmms_strip, dropping the three FILESTREAM tables and the FILESTREAM filegroup, and backing up the clean result; mandrake copies that into src/db-backups/main.bak and restores it into the container (a Linux container cannot restore a backup containing a FILESTREAM filegroup, which is why the strip is required).

The three FILESTREAM tables (Storage.Files, Reporting.File, Reporting.Image) are then recreated as VARBINARY(MAX) stubs (via src/scripts/recreate-filestream-tables.sql) and their actual data is backfilled from the host via bcp (default behavior — no flag). So these tables are PRESENT with real rows: EF migrations and read/write of file metadata work against the container. Runtime FILESTREAM streaming semantics (the on-disk filegroup) are still not real — the bytes live in a plain VARBINARY(MAX) column instead.

Shared config

~/.mandrake/config.json (v2, with per-instance subtrees) is the single source of truth for both the CLI and the VS Code extension — clone dir, ports (dbPort 14331 / backendPort 7266 / frontendPort 4200), the lifecycle toggles (sandboxMode/authBypassDesired/openFrontendInBrowser/autoBranchSync/autopilot/staticWebsite/ticketIsPort/httpsEnabled), browser, and the frontend/backend run commands (both tiers always launch on up). Edit it with mandrake config <get|set|list|save|reset> or via the extension; the two stay in sync (the extension also reflects live container + config state). dbPort 1433 is reserved for the host SQL Server and is rejected/coerced.

Multi-instance

Per-instance globals (container name, volume, Compose project, ports, config subtree, snapshot store) are derived from MANDRAKE_INSTANCE / mandrake.instanceId. The default instance maps to the original single-instance names (cmms-db, compose_cmms-db-data, $MANDRAKE_HOME/db-backups); a named instance gets cmms-db-<id>, cmms-db-data-<id>, and ~/.mandrake/instances/<id>/db-backups. This lets several CMMS clones run side-by-side without colliding.

Collision warning: two repos both left at the default ("") share the same default instance — same container, ports, and snapshot store. For multiple simultaneous repos, set each to "auto" or a distinct literal. The activity log is always a single global file (~/.mandrake/mandrake.log); each line is tagged [timestamp] [instance] [caller], so per-instance filtering works cleanly once ids are distinct.

Run stack & VS Code extension

mandrake up/down and the extension's Play/Stop drive the same flow: container start, auth-bypass apply (or dev-proxy retarget when bypass is off), and detached frontend/backend dev servers. The extension panel surfaces this as Play/Stop, Create/Destroy, port fields, the toggle stack (Autopilot at the top, then Bypass Auth, Static Website, HTTPS, Sandbox, Auto Branch Sync, Ticket-is-Port), split dev terminals, a "Bootstrap from .bak" action, an instance badge, and a left-side status-bar badge showing which cmms repo the window is on (cmms0/cmms/cmms2). HTTPS is a per-instance lifecycle toggle, default on (see below).

HTTPS

The frontend dev server terminates TLS by default (config httpsEnabled, default true, with a per-instance HTTPS toggle in the extension panel): mandrake up generates a self-signed cert at ~/.mandrake/certs (SAN covers localhost and, when sharing, the LAN IP) and launches ng serve --ssl (or serve-static over HTTPS). TLS terminates at the frontend only — the /Controllers → backend proxy hop stays plain HTTP, and the backend's launchSettings/proxy.conf.json are bound to http://localhost:<port> (the HTTPS→HTTP redirect would otherwise break the bypass). Because the cert is self-signed, browsers show a one-time "not secure" warning to click past — this is expected, works over the LAN, and gives a genuine secure context (required by CMMS features like the camera). Turning the panel toggle OFF serves the frontend over plain http and drops the secure context — camera/mic/geolocation will not work; the change applies on the next Start. (provision review instances remain https-forced regardless.) Internal helper: mandrake https-cert [--lan] (re)generates the cert and prints its paths.

Pipeline Provisioning (provision)

provision (at src/bin/provision) is a host-side, non-interactive entrypoint for CI/automation that spins up a full CMMS instance for a given git branch, health-gates it, and prints one machine-parseable line on success:

200: https://<lan-ip>:<port> id=<instance-id>

It composes the mandrake CLI. Each run creates a fresh, isolated, throwaway Mandrake instance (own container, volume, and ports) and is torn down with provision destroy. On failure, the in-progress instance is auto-destroyed so nothing orphans in CI.

Pipeline server setup (one-time)

Standing up a server to run provision requires: the harness cloned and on PATH; host tooling (Docker, .NET 8 SDK + dotnet-ef, Node + @angular/cli) — no host SQL Server needed; a CMMS clone (staged manually, origin = Bitbucket), usable as a lane pool; a golden main.bak seed produced from a known-good instance; and BYPASS_USER set to the GivenName of a Super User in the seed DB (critical — mismatch causes silent auth failure).

Full step-by-step: docs/provision-setup.md.

Usage

provision start --branch=<git-branch> --seed-dir <golden-dir> --clone-dir <cmms-clone> [--instance <id>] [--timeout <sec>]
provision destroy --instance <id>
provision stop    --instance <id>   (non-destructive: preserves volume/store)
Flag / env var Required Notes
--branch Yes Git branch to check out, migrate, and deploy
--seed-dir Yes (or MANDRAKE_SEED_DIR env var) Path to the golden seed dir containing main.bak
--clone-dir No Selects the CMMS clone; precedence: --clone-dir > MANDRAKE_CLONE_DIR env > configured cloneDir
--instance No Custom instance id; auto-generated as <branch>-<epoch> if omitted
--timeout No Health-gate wait limit in seconds (default 300)

On success: exit 0 and the single stdout line 200: https://<lan-ip>:<port> id=<id> (all other output goes to stderr; the frontend serves over HTTPS by default — see HTTPS below). On failure: non-zero exit, reason on stderr, no 200: line, and the throwaway instance is auto-destroyed.

Per-perspective share links (provision review --perspectives)

provision review accepts an optional --perspectives <csv> (alias --as <csv>) that, after the unchanged success line, prints one labeled share link per perspective. Each link pins the auth-bypass identity for whoever opens it (one URL per role/user, parallel-safe across browsers — the ?as=<perspective> query sets a per-browser cookie):

200: https://<lan-ip>:<port> id=<id>
perspectives:
  super      -> https://<lan-ip>:<port>/?as=super
  admin      -> https://<lan-ip>:<port>/?as=admin
  5 (HTI Test 02) -> https://<lan-ip>:<port>/?as=5
  • <csv> is a comma list of role names (super/approver/restricted), roster UserIds, and/or admin — each printed verbatim as its own label.
  • The special value all enumerates the three roles + admin + every roster UserId (each user link labelled with its name from mandrake authbypass list-users). If the roster can't be enumerated (DB unreachable), the role + admin links still print and a note goes to stderr.
  • Each token is sanitized to ^[A-Za-z0-9._-]+$; anything else is skipped with a stderr warning. Link-printing is a convenience appended after the instance is already up — it never fails the run.
  • Without the flag, output is byte-identical to before (just the success line), so the CI contract is unchanged.

Example: provision review --ticket CMMS-1234 --perspectives all or provision review --branch feature/CMMS-1234-foo --as super,approver,5.

What a run does (per branch)

Fetch + force-checkout origin/<branch> → prepare clone (seed env.js from bypass template if missing, dotnet restore, npm ci if node_modules is absent) → autopilot on (forces sandbox + auto-allocates collision-free ports; the CMMS defaults 1433/7266/4200 are forbidden, so instances land on 14331+/7267+/4201+) → import golden seed (snapshots import) → create --from-bak (restore baseline into a fresh container) → start (bring the container up) → sync <branch> (apply the branch's EF migrations on top of the baseline) → up --autopilot --lan (launch backend + frontend bound to the LAN) → health-gate (frontend HTTP 200 AND backend port open) → print the success line.

Gotchas (learned the hard way)

  • Clone path is the cmms-api solution dir, not the repo root. --clone-dir / MANDRAKE_CLONE_DIR must point at the cmms-api subdir inside the clone — e.g. …/cmms1/cmms-api, not …/cmms1. (provision review is the exception — it takes the lane root and locates cmms-api/ itself.)
  • Linux case-sensitivity. The CMMS frontend must compile on a case-sensitive filesystem. A component templateUrl whose casing differs from the actual .html filename works on Windows but fails the Angular build on a Linux pipeline host.
  • EF migrations are not auto-applied on startup. The CMMS API does not run dotnet ef database update at startup; the explicit sync step in the provision sequence is required.
  • Concurrency. One clone per concurrent lane; each lane gets its own auto-allocated db/backend/frontend ports, so multiple instances run side by side without collision.

Future

  • The north-star one-shot: mandrake up --branch=… --remote — point at any branch (local or remote) and have the whole stack come up against it in one command.
  • Container entrypoint auto-restore on fresh volume (so mandrake start after mandrake destroy + mandrake create auto-restores from current.txt)
  • Tier-based seed SQL scripts (layered baselines for different test scenarios)
  • cmd_migrations_merge error output polish
  • Agent-safe migration runner (auto-snapshot + auto-restore)

Layout

Project-Mandrake/
├── README.md               <- this file (setup, usage, architecture)
├── CLAUDE.md               <- project-specific reflexes for Claude/TPM sessions
├── vscode-extension/       <- VS Code panel that fronts the harness (shares ~/.mandrake/config.json)
└── src/
    ├── README.md           <- full mandrake command reference + state cheat-sheet
    ├── VERSION             <- mandrake CLI version
    ├── bin/mandrake        <- CLI dispatcher
    ├── bin/provision       <- host-side CI entrypoint (composes mandrake; see Pipeline Provisioning above)
    ├── commands.sh         <- all cmd_* functions (sourced by dispatcher)
    ├── compose/db.yml      <- Docker Compose for SQL Server container
    ├── db-backups/         <- locally generated baseline (main.bak) + snapshot .bak files (all git-ignored)
    │                          (for the default instance; named instances live under ~/.mandrake/instances/<id>/db-backups)
    └── scripts/
        ├── strip-filestream.sql         <- produces a FILESTREAM-free main.bak from host SQL
        └── recreate-filestream-tables.sql  <- recreates FILESTREAM tables as VARBINARY stubs

Runtime state (not in the repo) lives under ~/.mandrake/: config.json (shared config), run/*.log (detached dev-server / watch logs), mandrake.log (the global activity log — see below), and instances/<id>/db-backups (per-instance snapshot stores).

Every command invocation is logged to one global ~/.mandrake/mandrake.log (a START line plus an END (exit <code>) outcome line, 7-day retention). Lines are tagged with the instance id and a caller (cli, or extension when the VS Code extension sets MANDRAKE_CALLER). View it with mandrake logs [--all | --instance <id>] [--tail N] [--path] — default shows just the current instance's lines; --all shows every instance; --instance <id> a specific one.

For the full command reference, state cheat-sheet, and troubleshooting guide, see src/README.md.

Current state

  • DB container + baseline generated from host cmms (strip-filestream.sql auto-driven) — done
  • Offline bootstrap (mandrake create --from-bak, no host SQL Server) — done
  • FILESTREAM table data backfilled from host into VARBINARY stubs on mandrake create — done
  • Snapshots (add / switch / reset / remove / compatible) — done, verified
  • Migrations (list / update / applied / add / merge / test / parity / preview / has-custom-sql) — done, verified
  • Full CLI parity with the extension: run stack (up/down), auto branch-sync (sync/watch), open, authbypass, devproxy, config — done
  • Autopilot master mode (mandrake autopilot on|off|status, up --autopilot) — done
  • Multi-instance (MANDRAKE_INSTANCE / mandrake.instanceId — isolated container/ports/config/snapshots) — done
  • Shared config (~/.mandrake/config.json) as the single source of truth for CLI + extension — done
  • Activity logging (one global ~/.mandrake/mandrake.log, 7-day retention, every invocation + outcome; mandrake logs to view) — done
  • VS Code extension panel (Play/Stop, toggles, ports, instance badge, bootstrap-from-bak) — done
  • FILESTREAM strip + recreate scripts (src/scripts/) — done
  • Container entrypoint auto-restore on fresh volume — TODO

About

Spin up a production-like CMMS database and dev stack in a container.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages