feat: pnpm dev --isolated runs against its own database and project
Working on Fusion from inside a machine that already runs one, a plain
`pnpm dev` shares that instance's LIVE database. Everything durable hangs off
$HOME/.fusion — settings, credentials, central DB, the embedded Postgres data
dir — and a process pointed at a data dir whose postmaster is already running
attaches to it instead of starting its own.
--isolated spawns the dev child with HOME pointed at a sandbox, so it gets its
own settings, credentials and Postgres cluster on its own port. It also moves
the child's cwd, which is the half that is easy to miss: `fn dashboard`
derives its project from the working directory and has no project flag, so
isolating HOME alone leaves both instances on `<repo>/.fusion` — including
`.fusion/tasks/<id>/`, which the orphaned-task-dir sweep re-imports, so a
fresh dev database would adopt the real instance's tasks.
The sandbox defaults to ~/.fusion-dev/<checkout-name>/{home,project}: outside
the work tree so it neither shows up in git status nor dies on a clean
checkout, and keyed by checkout so two clones cannot collide. The project dir
is git init-ed on first use because Fusion projects are git work trees.
Changing cwd is safe because PRELOAD/LOADER/ENTRY are already absolute.
Verified in a container beside a running Fusion: the isolated instance
reported zero projects while the real one reported two, on separate Postgres
clusters (ports 42617 and 38311).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
readDevServerListening,
|
||||
readDevServerListeningPort,
|
||||
resolveDevTunnelPort,
|
||||
resolveIsolatedDevPaths,
|
||||
resolvePrebuildMode,
|
||||
} from "../../../../scripts/dev-with-memory-lib.mjs";
|
||||
import {
|
||||
@@ -65,6 +66,8 @@ describe("dev-with-memory prebuild options", () => {
|
||||
watchSourceFromFlag: false,
|
||||
tunnel: false,
|
||||
tunnelPort: undefined,
|
||||
isolated: false,
|
||||
isolatedDir: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -77,6 +80,8 @@ describe("dev-with-memory prebuild options", () => {
|
||||
watchSourceFromFlag: true,
|
||||
tunnel: false,
|
||||
tunnelPort: undefined,
|
||||
isolated: false,
|
||||
isolatedDir: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -373,6 +378,47 @@ describe("development source restart watcher", () => {
|
||||
expect(resolveDevTunnelPort(5173, { PORT: "8080" })).toBe(5173);
|
||||
});
|
||||
|
||||
/*
|
||||
FNXC:DevIsolation 2026-08-20-04:10:
|
||||
A plain `pnpm dev` inside a machine already running Fusion SHARES its database: everything
|
||||
durable hangs off $HOME/.fusion, and a process pointed at a data dir whose postmaster is already
|
||||
up attaches to it rather than starting its own. Isolating HOME alone still leaves both instances
|
||||
on one project directory, where the orphaned-task-dir sweep would have the dev instance adopt
|
||||
the real one's tasks — so the flag moves the working directory too.
|
||||
*/
|
||||
it("parses --isolated with and without an explicit directory", () => {
|
||||
expect(parseDevWrapperArgs(["--isolated"], {})).toMatchObject({ isolated: true, isolatedDir: undefined });
|
||||
expect(parseDevWrapperArgs(["--isolated=/tmp/sandbox"], {})).toMatchObject({ isolated: true, isolatedDir: "/tmp/sandbox" });
|
||||
expect(parseDevWrapperArgs(["dashboard"], {})).toMatchObject({ isolated: false });
|
||||
expect(parseDevWrapperArgs(["dashboard"], { FUSION_DEV_ISOLATED: "1" })).toMatchObject({ isolated: true });
|
||||
expect(() => parseDevWrapperArgs(["--isolated="], {})).toThrow(/Missing directory/);
|
||||
});
|
||||
|
||||
it("does not swallow a following argument as the isolation directory", () => {
|
||||
// `--isolated dashboard` means "isolate, and run the dashboard".
|
||||
expect(parseDevWrapperArgs(["--isolated", "dashboard"], {})).toMatchObject({ isolated: true, isolatedDir: undefined, args: ["dashboard"] });
|
||||
});
|
||||
|
||||
it("keeps the sandbox out of the repo and separates database from project", () => {
|
||||
const paths = resolveIsolatedDevPaths({ repoRoot: "/Users/dev/Projects/kb", home: "/Users/dev" });
|
||||
// Outside the work tree: a project dir inside it shows up in git status and dies on a clean checkout.
|
||||
expect(paths.base).toBe("/Users/dev/.fusion-dev/kb");
|
||||
expect(paths.home).toBe("/Users/dev/.fusion-dev/kb/home");
|
||||
expect(paths.project).toBe("/Users/dev/.fusion-dev/kb/project");
|
||||
expect(paths.project.startsWith("/Users/dev/Projects/kb")).toBe(false);
|
||||
});
|
||||
|
||||
it("keys the sandbox by checkout so two clones do not share one database", () => {
|
||||
const a = resolveIsolatedDevPaths({ repoRoot: "/w/kb", home: "/h" });
|
||||
const b = resolveIsolatedDevPaths({ repoRoot: "/w/kb-feature", home: "/h" });
|
||||
expect(a.home).not.toBe(b.home);
|
||||
});
|
||||
|
||||
it("honours an explicit sandbox directory", () => {
|
||||
expect(resolveIsolatedDevPaths({ repoRoot: "/w/kb", explicitDir: "/tmp/sandbox" }))
|
||||
.toEqual({ base: "/tmp/sandbox", home: "/tmp/sandbox/home", project: "/tmp/sandbox/project" });
|
||||
});
|
||||
|
||||
it("recognises the cloudflare quick-tunnel hostname in agent output", () => {
|
||||
expect(extractQuickTunnelUrl("INF | https://neat-fox-tree.trycloudflare.com |")).toBe("https://neat-fox-tree.trycloudflare.com");
|
||||
expect(extractQuickTunnelUrl("INF Registered tunnel connection")).toBeNull();
|
||||
|
||||
Reference in New Issue
Block a user