Files
multica/apps/desktop/scripts/dev.mjs
Jiayuan Zhang 6dcf82a58a feat(desktop): isolate pnpm dev:desktop per worktree (MUL-3724) (#4598)
* feat(desktop): isolate pnpm dev:desktop per worktree (MUL-3724)

Two worktrees could not run pnpm dev:desktop at once: both grabbed the
renderer port 5173 and the single-instance lock keyed by the app name
"Multica Canary". The env hooks to override each already existed
(DESKTOP_RENDERER_PORT in electron.vite.config.ts, DESKTOP_APP_SUFFIX in
src/main/index.ts) but nothing derived per-worktree values.

A new dev launcher (scripts/dev.mjs) derives both from the worktree path
for linked worktrees only — reusing the same cksum%1000 offset as
scripts/init-worktree-env.sh, so renderer port is 5173+offset and the app
becomes "Multica Canary <folder>" with its own userData/lock. The primary
checkout is untouched; explicit env vars still win. Backend targeting is
unchanged (apps/desktop/.env*). Also: brand-dev-electron honors the suffix,
turbo globalEnv passes it through, and CONTRIBUTING documents the flow.

Co-authored-by: multica-agent <github@multica.ai>

* fix(desktop): make worktree dev port/suffix collision-safe (MUL-3724)

Addresses code review on #4598:

- Renderer port base 5173 → 5174 so a worktree whose offset is 0 (e.g.
  cksum("/tmp/multica-3494") % 1000 === 0) no longer collides with the
  primary checkout's default 5173.
- DESKTOP_APP_SUFFIX is now "<folder>-<offset>" instead of just the folder
  name, so worktrees that share a basename at different paths (or names that
  slug to the same fallback) get distinct single-instance locks. Without it
  the second Electron was still blocked by the shared lock.
- Tests: offset-0 port guard, and same-basename-different-path disambiguation.

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: Lambda <agent@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-06-26 15:11:28 +08:00

54 lines
1.6 KiB
JavaScript

#!/usr/bin/env node
// Dev launcher for `pnpm dev:desktop`.
//
// Derives per-worktree isolation env (renderer port + app name) so multiple
// worktrees can run `pnpm dev:desktop` side-by-side, then runs the same chain
// as before — bundle the CLI, brand the dev Electron, start electron-vite —
// inheriting the augmented env. A plain `&&` chain in package.json can't do
// this: each `&&` step is its own process, so an env tweak in step 1 wouldn't
// reach electron-vite in step 3. Args (e.g. `--mode staging`) pass through to
// electron-vite.
import { spawnSync } from "node:child_process";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import {
applyWorktreeDevEnv,
repoRootFromScriptDir,
} from "./worktree-dev-env.mjs";
const here = dirname(fileURLToPath(import.meta.url));
applyWorktreeDevEnv(process.env, {
root: repoRootFromScriptDir(here),
log: true,
});
function run(command, args, { shell = false } = {}) {
const result = spawnSync(command, args, {
stdio: "inherit",
env: process.env,
shell,
});
if (result.error) {
console.error(`[dev:desktop] failed to run ${command}: ${result.error.message}`);
process.exit(1);
}
if (result.status !== 0) process.exit(result.status ?? 1);
}
const node = process.execPath;
run(node, [join(here, "bundle-cli.mjs")]);
run(node, [join(here, "brand-dev-electron.mjs")]);
const isWin = process.platform === "win32";
const electronVite = join(
here,
"..",
"node_modules",
".bin",
isWin ? "electron-vite.cmd" : "electron-vite",
);
run(electronVite, ["dev", ...process.argv.slice(2)], { shell: isWin });