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:
@@ -16,8 +16,12 @@ import {
|
||||
parseDevWrapperArgs,
|
||||
readDevServerListening,
|
||||
resolveDevTunnelPort,
|
||||
resolveIsolatedDevPaths,
|
||||
resolvePrebuildMode,
|
||||
} from "./dev-with-memory-lib.mjs";
|
||||
import { existsSync as fsExistsSync, mkdirSync as fsMkdirSync } from "node:fs";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { join as pathJoin, resolve as pathResolve } from "node:path";
|
||||
import { createDevSourceWatcher } from "./lib/dev-source-watch.mjs";
|
||||
import { resolveDevTunnelAuth, startDevTunnel } from "./lib/dev-tunnel.mjs";
|
||||
|
||||
@@ -34,7 +38,7 @@ try {
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
}
|
||||
const { inspectFlags, args, requestedPrebuild, watchSourceFromFlag, tunnel, tunnelPort } = parsedArgs;
|
||||
const { inspectFlags, args, requestedPrebuild, watchSourceFromFlag, tunnel, tunnelPort, isolated, isolatedDir } = parsedArgs;
|
||||
let { watchSource } = parsedArgs;
|
||||
|
||||
// NODE_OPTIONS is shared with every spawned node process (build + run +
|
||||
@@ -47,6 +51,31 @@ process.env.NODE_OPTIONS = nodeOptions;
|
||||
// builds default to 127.0.0.1; this override only applies when starting
|
||||
// the dashboard via `pnpm dev dashboard` and only if no --host was passed.
|
||||
const forwardedArgs = buildForwardedDevArgs(args);
|
||||
|
||||
/*
|
||||
FNXC:DevIsolation 2026-08-20-04:10:
|
||||
Prepare the isolated sandbox up front so the child can be spawned straight into it. The project
|
||||
directory is `git init`-ed when empty because Fusion projects are git work trees — worktrees,
|
||||
branches and merges all assume one — and an isolated instance that cannot resolve a repository is
|
||||
not usable for the UI work this flag exists to support.
|
||||
*/
|
||||
let isolatedPaths;
|
||||
if (isolated) {
|
||||
const realHome = process.env.HOME || process.env.USERPROFILE;
|
||||
isolatedPaths = resolveIsolatedDevPaths({
|
||||
repoRoot: process.cwd(),
|
||||
home: realHome,
|
||||
explicitDir: isolatedDir ? pathResolve(isolatedDir) : undefined,
|
||||
});
|
||||
fsMkdirSync(isolatedPaths.home, { recursive: true });
|
||||
fsMkdirSync(isolatedPaths.project, { recursive: true });
|
||||
if (!fsExistsSync(pathJoin(isolatedPaths.project, ".git"))) {
|
||||
// Short, deterministic git plumbing — the engine-wide execSync ban targets user-configured
|
||||
// commands, not this.
|
||||
spawnSync("git", ["init", "-q"], { cwd: isolatedPaths.project, stdio: "ignore" });
|
||||
}
|
||||
console.log(`[fusion:dev] isolated instance — database ${isolatedPaths.home}/.fusion, project ${isolatedPaths.project}`);
|
||||
}
|
||||
if (watchSource && forwardedArgs[0] !== "dashboard") {
|
||||
if (watchSourceFromFlag) {
|
||||
console.error("[fusion:dev] --watch is supported for the dashboard engine process only");
|
||||
@@ -200,11 +229,16 @@ function runApp(extraArgs) {
|
||||
// FNXC:SystemPanel 2026-07-25-10:05: stamp the supervisor pid alongside the
|
||||
// flag so the child can tell a real supervising parent from an inherited
|
||||
// copy of the variable (see hasLiveSupervisingParent in commands/dashboard.ts).
|
||||
// FNXC:DevIsolation 2026-08-20-04:10: HOME moves the whole durable state (settings, credentials,
|
||||
// central DB, embedded Postgres cluster); cwd moves the project, so the two instances cannot
|
||||
// share `.fusion/tasks/`. Absolute PRELOAD/LOADER/ENTRY paths make the cwd change safe.
|
||||
...(isolatedPaths ? { cwd: isolatedPaths.project } : {}),
|
||||
env: {
|
||||
...process.env,
|
||||
FUSION_RESTART_SUPERVISED: "1",
|
||||
FUSION_SUPERVISOR_PID: String(process.pid),
|
||||
...(watchSource ? { FUSION_DEV_WATCH: "1" } : {}),
|
||||
...(isolatedPaths ? { HOME: isolatedPaths.home, USERPROFILE: isolatedPaths.home } : {}),
|
||||
},
|
||||
});
|
||||
appChild = tsx;
|
||||
|
||||
Reference in New Issue
Block a user