mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-13 22:06:14 +01:00
227 lines
6.8 KiB
JavaScript
227 lines
6.8 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Git post-checkout hook for auto-configuring worktree environments.
|
|
* This runs after 'git checkout' or 'git worktree add'.
|
|
*
|
|
* Args from git:
|
|
* process.argv[2] - previous HEAD ref
|
|
* process.argv[3] - new HEAD ref
|
|
* process.argv[4] - flag (1 = branch checkout, 0 = file checkout)
|
|
*/
|
|
|
|
import fs from "fs";
|
|
import path from "path";
|
|
import { execSync, execFileSync } from "child_process";
|
|
import { fileURLToPath } from "url";
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
|
const isBranchCheckout = process.argv[4] === "1";
|
|
|
|
if (!isBranchCheckout) {
|
|
process.exit(0);
|
|
}
|
|
|
|
// Check if we're in a worktree by looking for .git file (not directory)
|
|
const gitPath = path.join(process.cwd(), ".git");
|
|
const isWorktree = fs.existsSync(gitPath) && fs.statSync(gitPath).isFile();
|
|
|
|
if (!isWorktree) {
|
|
process.exit(0);
|
|
}
|
|
|
|
const envLocalPath = path.join(process.cwd(), ".env.local");
|
|
|
|
// Don't overwrite existing .env.local
|
|
if (fs.existsSync(envLocalPath)) {
|
|
process.exit(0);
|
|
}
|
|
|
|
console.log("Detected new worktree - configuring ports in .env.local");
|
|
|
|
const CLIENT_PORT_BASE = 1420;
|
|
const PROXY_PORT_BASE = 2420; // Keep proxy +1000 from client to avoid cross-app conflicts
|
|
|
|
// Find the highest worktree index and MCP port in use across all worktrees.
|
|
// Main worktree (first in list) is assumed to use index 0:
|
|
// client=1420, proxy=2420, mcp=64343.
|
|
let maxMcpPort = 64343;
|
|
let maxWorktreeIndex = 0;
|
|
|
|
try {
|
|
const worktreeList = execSync("git worktree list --porcelain", {
|
|
encoding: "utf8",
|
|
});
|
|
const worktreePaths = worktreeList
|
|
.split("\n")
|
|
.filter((line) => line.startsWith("worktree "))
|
|
.map((line) => line.replace("worktree ", "").trim());
|
|
|
|
// Skip the first worktree (main) since it uses default ports
|
|
for (let i = 1; i < worktreePaths.length; i++) {
|
|
const worktreePath = worktreePaths[i];
|
|
const envPath = path.join(worktreePath, ".env.local");
|
|
|
|
if (fs.existsSync(envPath)) {
|
|
const content = fs.readFileSync(envPath, "utf8");
|
|
|
|
const mcpMatch = content.match(/^YAAK_PLUGIN_MCP_SERVER_PORT=(\d+)/m);
|
|
if (mcpMatch) {
|
|
const port = parseInt(mcpMatch[1], 10);
|
|
if (port > maxMcpPort) {
|
|
maxMcpPort = port;
|
|
}
|
|
}
|
|
|
|
const clientDevMatch = content.match(
|
|
/^(?:YAAK_CLIENT_DEV_PORT|YAAK_DEV_PORT)=(\d+)/m,
|
|
);
|
|
if (clientDevMatch) {
|
|
const port = parseInt(clientDevMatch[1], 10);
|
|
const index = port - CLIENT_PORT_BASE;
|
|
if (index > maxWorktreeIndex) {
|
|
maxWorktreeIndex = index;
|
|
}
|
|
}
|
|
|
|
const proxyDevMatch = content.match(/^YAAK_PROXY_DEV_PORT=(\d+)/m);
|
|
if (proxyDevMatch) {
|
|
const port = parseInt(proxyDevMatch[1], 10);
|
|
const index = port - PROXY_PORT_BASE;
|
|
if (index > maxWorktreeIndex) {
|
|
maxWorktreeIndex = index;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Increment MCP to get the next available port
|
|
maxMcpPort++;
|
|
} catch (err) {
|
|
console.error(
|
|
"Warning: Could not check other worktrees for port conflicts:",
|
|
err.message,
|
|
);
|
|
// Continue with default ports
|
|
}
|
|
|
|
const nextWorktreeIndex = maxWorktreeIndex + 1;
|
|
const nextClientDevPort = CLIENT_PORT_BASE + nextWorktreeIndex;
|
|
const nextProxyDevPort = PROXY_PORT_BASE + nextWorktreeIndex;
|
|
|
|
// Get worktree name from current directory
|
|
const worktreeName = path.basename(process.cwd());
|
|
|
|
// Create .env.local with unique ports
|
|
const envContent = `# Auto-generated by git post-checkout hook
|
|
# This file configures ports for this worktree to avoid conflicts
|
|
|
|
# Vite dev server port for the client app (main worktree uses ${CLIENT_PORT_BASE})
|
|
YAAK_CLIENT_DEV_PORT=${nextClientDevPort}
|
|
|
|
# Vite dev server port for the proxy app (main worktree uses ${PROXY_PORT_BASE})
|
|
YAAK_PROXY_DEV_PORT=${nextProxyDevPort}
|
|
|
|
# MCP Server port (main worktree uses 64343)
|
|
YAAK_PLUGIN_MCP_SERVER_PORT=${maxMcpPort}
|
|
`;
|
|
|
|
fs.writeFileSync(envLocalPath, envContent, "utf8");
|
|
console.log(
|
|
`Created .env.local with YAAK_CLIENT_DEV_PORT=${nextClientDevPort}, YAAK_PROXY_DEV_PORT=${nextProxyDevPort}, and YAAK_PLUGIN_MCP_SERVER_PORT=${maxMcpPort}`,
|
|
);
|
|
|
|
// Create tauri.worktree.conf.json with unique app identifier for complete isolation
|
|
// This gives each worktree its own app data directory, avoiding the need for DB path prefixes
|
|
const clientTauriWorktreeConfig = {
|
|
identifier: `app.yaak.desktop.dev.${worktreeName}`,
|
|
productName: `Daak (${worktreeName})`,
|
|
};
|
|
|
|
const clientTauriConfigPath = path.join(
|
|
process.cwd(),
|
|
"crates-tauri",
|
|
"yaak-app-client",
|
|
"tauri.worktree.conf.json",
|
|
);
|
|
fs.writeFileSync(
|
|
clientTauriConfigPath,
|
|
JSON.stringify(clientTauriWorktreeConfig, null, 2) + "\n",
|
|
"utf8",
|
|
);
|
|
console.log(
|
|
`Created client tauri.worktree.conf.json with identifier: ${clientTauriWorktreeConfig.identifier}`,
|
|
);
|
|
|
|
const proxyTauriWorktreeConfig = {
|
|
identifier: `app.yaak.proxy.dev.${worktreeName}`,
|
|
productName: `Yaak Proxy (${worktreeName})`,
|
|
};
|
|
|
|
const proxyTauriConfigPath = path.join(
|
|
process.cwd(),
|
|
"crates-tauri",
|
|
"yaak-app-proxy",
|
|
"tauri.worktree.conf.json",
|
|
);
|
|
fs.writeFileSync(
|
|
proxyTauriConfigPath,
|
|
JSON.stringify(proxyTauriWorktreeConfig, null, 2) + "\n",
|
|
"utf8",
|
|
);
|
|
console.log(
|
|
`Created proxy tauri.worktree.conf.json with identifier: ${proxyTauriWorktreeConfig.identifier}`,
|
|
);
|
|
|
|
// Copy gitignored editor config folders from main worktree (.zed, .vscode, .claude, etc.)
|
|
// This ensures your editor settings, tasks, and configurations are available in the new worktree
|
|
// without needing to manually copy them or commit them to git.
|
|
try {
|
|
const worktreeList = execSync("git worktree list --porcelain", {
|
|
encoding: "utf8",
|
|
});
|
|
const mainWorktreePath = worktreeList
|
|
.split("\n")
|
|
.find((line) => line.startsWith("worktree "))
|
|
?.replace("worktree ", "")
|
|
.trim();
|
|
|
|
if (mainWorktreePath) {
|
|
// Find all .* folders in main worktree root that are gitignored
|
|
const entries = fs.readdirSync(mainWorktreePath, { withFileTypes: true });
|
|
const dotFolders = entries
|
|
.filter((entry) => entry.isDirectory() && entry.name.startsWith("."))
|
|
.map((entry) => entry.name);
|
|
|
|
for (const folder of dotFolders) {
|
|
const sourcePath = path.join(mainWorktreePath, folder);
|
|
const destPath = path.join(process.cwd(), folder);
|
|
|
|
try {
|
|
// Check if it's gitignored - run from main worktree directory
|
|
execFileSync("git", ["check-ignore", "-q", folder], {
|
|
stdio: "pipe",
|
|
cwd: mainWorktreePath,
|
|
});
|
|
|
|
// It's gitignored, copy it
|
|
fs.cpSync(sourcePath, destPath, { recursive: true });
|
|
console.log(`Copied ${folder} from main worktree`);
|
|
} catch {
|
|
// Not gitignored or doesn't exist, skip
|
|
}
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.warn(
|
|
"Warning: Could not copy files from main worktree:",
|
|
err.message,
|
|
);
|
|
// Continue anyway
|
|
}
|
|
|
|
console.log(
|
|
"\n✓ Worktree setup complete! Run `npm run init` to install dependencies.",
|
|
);
|