mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-06 22:09:44 +02:00
* 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>
80 lines
2.6 KiB
JavaScript
80 lines
2.6 KiB
JavaScript
#!/usr/bin/env node
|
|
// Rebrand the bundled Electron.app's Info.plist so `pnpm dev:desktop`
|
|
// shows "Multica Canary" in the menu bar, Cmd+Tab switcher, and
|
|
// Activity Monitor. On macOS these titles come from CFBundleName at
|
|
// launch time — `app.setName()` cannot override them at runtime, so
|
|
// patching the plist in node_modules is the only working fix.
|
|
//
|
|
// Idempotent: runs on every dev launch and no-ops once the plist already
|
|
// matches. The patch is isolated to this worktree's node_modules — we
|
|
// unlink the file before rewriting so we never mutate a pnpm-store inode
|
|
// shared with another project.
|
|
//
|
|
// In a worktree, scripts/dev.mjs sets DESKTOP_APP_SUFFIX so the name becomes
|
|
// "Multica Canary <suffix>" — distinguishable in Cmd+Tab and matching the app
|
|
// name src/main/index.ts derives from the same env var.
|
|
|
|
import { createRequire } from "node:module";
|
|
import { execFileSync } from "node:child_process";
|
|
import { readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
import { resolve } from "node:path";
|
|
|
|
if (process.platform !== "darwin") process.exit(0);
|
|
|
|
const DESIRED_NAME = process.env.DESKTOP_APP_SUFFIX
|
|
? `Multica Canary ${process.env.DESKTOP_APP_SUFFIX}`
|
|
: "Multica Canary";
|
|
|
|
const require = createRequire(import.meta.url);
|
|
// `require('electron')` returns the path to the executable
|
|
// (.../Electron.app/Contents/MacOS/Electron). Walk up to Contents/Info.plist.
|
|
const electronBin = require("electron");
|
|
const plistPath = resolve(electronBin, "../../Info.plist");
|
|
|
|
function plistGet(key) {
|
|
try {
|
|
return execFileSync(
|
|
"/usr/libexec/PlistBuddy",
|
|
["-c", `Print :${key}`, plistPath],
|
|
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] },
|
|
).trim();
|
|
} catch {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
function plistSet(key, value) {
|
|
try {
|
|
execFileSync("/usr/libexec/PlistBuddy", [
|
|
"-c",
|
|
`Set :${key} ${value}`,
|
|
plistPath,
|
|
]);
|
|
} catch {
|
|
execFileSync("/usr/libexec/PlistBuddy", [
|
|
"-c",
|
|
`Add :${key} string ${value}`,
|
|
plistPath,
|
|
]);
|
|
}
|
|
}
|
|
|
|
if (
|
|
plistGet("CFBundleName") === DESIRED_NAME &&
|
|
plistGet("CFBundleDisplayName") === DESIRED_NAME
|
|
) {
|
|
process.exit(0);
|
|
}
|
|
|
|
// Break any pnpm hardlink to the global store: read, unlink, rewrite.
|
|
// PlistBuddy would otherwise write through the hardlink and mutate the
|
|
// shared store file (and every other project's Electron.app with it).
|
|
const original = readFileSync(plistPath);
|
|
unlinkSync(plistPath);
|
|
writeFileSync(plistPath, original);
|
|
|
|
plistSet("CFBundleName", DESIRED_NAME);
|
|
plistSet("CFBundleDisplayName", DESIRED_NAME);
|
|
|
|
console.log(`[brand-dev-electron] ${plistPath} → CFBundleName="${DESIRED_NAME}"`);
|