UNPKG

vibetunnel

Version:

Terminal sharing server with web interface - supports macOS, Linux, and headless environments

633 lines 82 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.createWorktreeRoutes = createWorktreeRoutes; const express_1 = require("express"); const path = __importStar(require("path")); const util_1 = require("util"); const git_error_js_1 = require("../utils/git-error.js"); const git_hooks_js_1 = require("../utils/git-hooks.js"); const logger_js_1 = require("../utils/logger.js"); const control_protocol_js_1 = require("../websocket/control-protocol.js"); const control_unix_handler_js_1 = require("../websocket/control-unix-handler.js"); const logger = (0, logger_js_1.createLogger)('worktree-routes'); const execFile = (0, util_1.promisify)(require('child_process').execFile); /** * Execute a git command with proper error handling and security * @param args Git command arguments * @param options Execution options * @returns Command output */ async function execGit(args, options = {}) { try { const { stdout, stderr } = await execFile('git', args, { cwd: options.cwd || process.cwd(), timeout: options.timeout || 10000, // 10s for potentially slow operations maxBuffer: 10 * 1024 * 1024, // 10MB for large diffs env: { ...process.env, GIT_TERMINAL_PROMPT: '0' }, // Disable git prompts }); return { stdout: stdout.toString(), stderr: stderr.toString() }; } catch (error) { // Re-throw with more context throw (0, git_error_js_1.createGitError)(error, 'Git command failed'); } } /** * Detect the repository's default branch * @param repoPath Repository path * @returns Default branch name */ async function detectDefaultBranch(repoPath) { try { // Try to get the default branch from origin const { stdout } = await execGit(['symbolic-ref', 'refs/remotes/origin/HEAD'], { cwd: repoPath, }); // Output format: refs/remotes/origin/main const match = stdout.trim().match(/refs\/remotes\/origin\/(.+)$/); if (match) { return match[1]; } } catch (_error) { logger.debug('Could not detect default branch from origin'); } // Fallback: check if main exists try { await execGit(['rev-parse', '--verify', 'main'], { cwd: repoPath }); return 'main'; } catch { // Fallback to master return 'master'; } } /** * Parse git worktree list --porcelain output * @param output Git command output * @returns Parsed worktrees */ function parseWorktreePorcelain(output) { const worktrees = []; const lines = output.trim().split('\n'); let current = null; for (const line of lines) { if (line === '') { if (current?.path && current.HEAD) { worktrees.push({ path: current.path, branch: current.branch || 'HEAD', HEAD: current.HEAD, detached: current.detached || false, prunable: current.prunable, locked: current.locked, lockedReason: current.lockedReason, }); } current = null; continue; } const [key, ...valueParts] = line.split(' '); const value = valueParts.join(' '); if (key === 'worktree') { current = { path: value }; } else if (current) { switch (key) { case 'HEAD': current.HEAD = value; break; case 'branch': current.branch = value; break; case 'detached': current.detached = true; break; case 'prunable': current.prunable = true; break; case 'locked': current.locked = true; if (value) { current.lockedReason = value; } break; } } } // Handle last worktree if no trailing newline if (current?.path && current.HEAD) { worktrees.push({ path: current.path, branch: current.branch || 'HEAD', HEAD: current.HEAD, detached: current.detached || false, prunable: current.prunable, locked: current.locked, lockedReason: current.lockedReason, }); } return worktrees; } /** * Get commit and diff stats for a branch * @param repoPath Repository path * @param branch Branch name * @param baseBranch Base branch to compare against * @returns Stats */ async function getBranchStats(repoPath, branch, baseBranch) { const stats = { commitsAhead: 0, filesChanged: 0, insertions: 0, deletions: 0, }; try { // Get commit count const { stdout: commitCount } = await execGit(['rev-list', '--count', `${baseBranch}...${branch}`], { cwd: repoPath }); stats.commitsAhead = Number.parseInt(commitCount.trim()) || 0; } catch (error) { logger.debug(`Could not get commit count for ${branch}: ${error}`); } try { // Get diff stats const { stdout: diffStat } = await execGit(['diff', '--shortstat', `${baseBranch}...${branch}`], { cwd: repoPath }); // Parse output like: "3 files changed, 10 insertions(+), 5 deletions(-)" const match = diffStat.match(/(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?/); if (match) { stats.filesChanged = Number.parseInt(match[1]) || 0; stats.insertions = Number.parseInt(match[2]) || 0; stats.deletions = Number.parseInt(match[3]) || 0; } } catch (error) { logger.debug(`Could not get diff stats for ${branch}: ${error}`); } return stats; } /** * Check if a worktree has uncommitted changes * @param worktreePath Worktree path * @returns True if there are uncommitted changes */ async function hasUncommittedChanges(worktreePath) { try { const { stdout } = await execGit(['status', '--porcelain'], { cwd: worktreePath }); return stdout.trim().length > 0; } catch (error) { logger.debug(`Could not check uncommitted changes for ${worktreePath}: ${error}`); return false; } } /** * Slugify branch name for directory naming * @param branch Branch name * @returns Slugified name */ function _slugifyBranch(branch) { return branch .replace(/\//g, '-') .replace(/[^a-zA-Z0-9-_]/g, '_') .toLowerCase(); } /** * Create worktree management routes */ function createWorktreeRoutes() { const router = (0, express_1.Router)(); /** * GET /api/worktrees * List all worktrees with extended information */ router.get('/worktrees', async (req, res) => { try { const { repoPath } = req.query; if (!repoPath || typeof repoPath !== 'string') { return res.status(400).json({ error: 'Missing or invalid repoPath parameter', }); } const absoluteRepoPath = path.resolve(repoPath); logger.debug(`Listing worktrees for repo: ${absoluteRepoPath}`); // Detect default branch const baseBranch = await detectDefaultBranch(absoluteRepoPath); logger.debug(`Using base branch: ${baseBranch}`); // Get follow worktree if configured let followBranch; try { const { stdout } = await execGit(['config', 'vibetunnel.followWorktree'], { cwd: absoluteRepoPath, }); const followWorktreePath = stdout.trim(); if (followWorktreePath) { // Find the branch for this worktree path - we need to parse worktrees first // This is a bit of a circular dependency, so let's get minimal worktree info const { stdout: worktreeListOutput } = await execGit(['worktree', 'list', '--porcelain'], { cwd: absoluteRepoPath, }); const allWorktrees = parseWorktreePorcelain(worktreeListOutput); const followWorktree = allWorktrees.find((w) => w.path === followWorktreePath); if (followWorktree) { followBranch = followWorktree.branch.replace(/^refs\/heads\//, ''); } } } catch { // No follow worktree configured } // Get worktree list const { stdout } = await execGit(['worktree', 'list', '--porcelain'], { cwd: absoluteRepoPath, }); const allWorktrees = parseWorktreePorcelain(stdout); // Enrich all worktrees with additional stats (including main repository) const enrichedWorktrees = await Promise.all(allWorktrees.map(async (worktree) => { // Skip stats for detached HEAD if (worktree.detached || !worktree.branch) { return worktree; } // Get branch stats const stats = await getBranchStats(worktree.path, worktree.branch, baseBranch); // Check for uncommitted changes const hasChanges = await hasUncommittedChanges(worktree.path); return { ...worktree, ...stats, stats, // Also include stats as a nested object for compatibility hasUncommittedChanges: hasChanges, }; })); return res.json({ worktrees: enrichedWorktrees, baseBranch, followBranch, }); } catch (error) { logger.error('Error listing worktrees:', error); const gitError = error; // Check if it's a "not a git repository" error or git not found if (gitError.code === 'ENOENT' || gitError.stderr?.includes('not a git repository')) { // Return empty worktrees list for non-git directories or when git is not available return res.json({ worktrees: [], baseBranch: 'main', followBranch: undefined, }); } return res.status(500).json({ error: 'Failed to list worktrees', details: gitError.stderr || gitError.message, }); } }); /** * DELETE /api/worktrees/:branch * Remove a worktree */ router.delete('/worktrees/:branch', async (req, res) => { try { const { branch } = req.params; const { repoPath, force } = req.query; if (!repoPath || typeof repoPath !== 'string') { return res.status(400).json({ error: 'Missing or invalid repoPath parameter', }); } const absoluteRepoPath = path.resolve(repoPath); const forceDelete = force === 'true'; logger.debug(`Removing worktree for branch: ${branch}, force: ${forceDelete}`); // First, find the worktree path for this branch const { stdout: listOutput } = await execGit(['worktree', 'list', '--porcelain'], { cwd: absoluteRepoPath, }); const worktrees = parseWorktreePorcelain(listOutput); const worktree = worktrees.find((w) => { // Match against both the full ref path and the short branch name const shortBranch = w.branch?.replace(/^refs\/heads\//, ''); return w.branch === `refs/heads/${branch}` || shortBranch === branch || w.branch === branch; }); if (!worktree) { return res.status(404).json({ error: `Worktree for branch '${branch}' not found`, }); } // Check for uncommitted changes if not forcing if (!forceDelete) { const hasChanges = await hasUncommittedChanges(worktree.path); if (hasChanges) { return res.status(409).json({ error: 'Worktree has uncommitted changes', worktreePath: worktree.path, }); } } // Remove the worktree const removeArgs = ['worktree', 'remove']; if (forceDelete) { removeArgs.push('--force'); } removeArgs.push(worktree.path); await execGit(removeArgs, { cwd: absoluteRepoPath }); logger.info(`Successfully removed worktree: ${worktree.path}`); return res.json({ success: true, message: 'Worktree removed successfully', removedPath: worktree.path, }); } catch (error) { logger.error('Error removing worktree:', error); const gitError = error; return res.status(500).json({ error: 'Failed to remove worktree', details: gitError.stderr || gitError.message, }); } }); /** * POST /api/worktrees/prune * Prune worktree information */ router.post('/worktrees/prune', async (req, res) => { try { const { repoPath } = req.body; if (!repoPath || typeof repoPath !== 'string') { return res.status(400).json({ error: 'Missing or invalid repoPath in request body', }); } const absoluteRepoPath = path.resolve(repoPath); logger.debug(`Pruning worktrees for repo: ${absoluteRepoPath}`); const { stdout, stderr } = await execGit(['worktree', 'prune'], { cwd: absoluteRepoPath }); logger.info('Successfully pruned worktree information'); return res.json({ success: true, message: 'Worktree information pruned successfully', output: stdout || stderr || 'No output', pruned: stdout || stderr || '', }); } catch (error) { logger.error('Error pruning worktrees:', error); const gitError = error; return res.status(500).json({ error: 'Failed to prune worktrees', details: gitError.stderr || gitError.message, }); } }); /** * POST /api/worktrees * Create a new worktree */ router.post('/worktrees', async (req, res) => { try { const { repoPath, branch, path: worktreePath, baseBranch } = req.body; if (!repoPath || typeof repoPath !== 'string') { return res.status(400).json({ error: 'Missing or invalid repoPath in request body', }); } if (!branch || typeof branch !== 'string') { return res.status(400).json({ error: 'Missing or invalid branch in request body', }); } if (!worktreePath || typeof worktreePath !== 'string') { return res.status(400).json({ error: 'Missing or invalid path in request body', }); } const absoluteRepoPath = path.resolve(repoPath); const absoluteWorktreePath = path.resolve(worktreePath); logger.debug(`Creating worktree for branch: ${branch} at path: ${absoluteWorktreePath}`); // Create the worktree const createArgs = ['worktree', 'add']; // If baseBranch is provided, create new branch from it if (baseBranch) { createArgs.push('-b', branch, absoluteWorktreePath, baseBranch); } else { // Otherwise just checkout existing branch createArgs.push(absoluteWorktreePath, branch); } await execGit(createArgs, { cwd: absoluteRepoPath }); logger.info(`Successfully created worktree at: ${absoluteWorktreePath}`); return res.json({ message: 'Worktree created successfully', worktreePath: absoluteWorktreePath, branch, }); } catch (error) { logger.error('Error creating worktree:', error); const gitError = error; return res.status(500).json({ error: 'Failed to create worktree', details: gitError.stderr || gitError.message, }); } }); /** * POST /api/worktrees/follow * Enable or disable follow mode for a branch */ router.post('/worktrees/follow', async (req, res) => { try { const { repoPath, branch, enable } = req.body; if (!repoPath || typeof repoPath !== 'string') { return res.status(400).json({ error: 'Missing or invalid repoPath in request body', }); } if (typeof enable !== 'boolean') { return res.status(400).json({ error: 'Missing or invalid enable flag in request body', }); } // Branch is only required when enabling follow mode if (enable && (!branch || typeof branch !== 'string')) { return res.status(400).json({ error: 'Missing or invalid branch in request body', }); } const absoluteRepoPath = path.resolve(repoPath); logger.debug(`${enable ? 'Enabling' : 'Disabling'} follow mode${branch ? ` for branch: ${branch}` : ''}`); if (enable) { // Check if Git hooks are already installed const hooksAlreadyInstalled = await (0, git_hooks_js_1.areHooksInstalled)(absoluteRepoPath); logger.debug(`Git hooks installed: ${hooksAlreadyInstalled}`); let hooksInstallResult = null; if (!hooksAlreadyInstalled) { // Install Git hooks logger.info('Installing Git hooks for follow mode'); const installResult = await (0, git_hooks_js_1.installGitHooks)(absoluteRepoPath); hooksInstallResult = installResult; if (!installResult.success) { logger.error('Failed to install Git hooks:', installResult.errors); return res.status(500).json({ error: 'Failed to install Git hooks', details: installResult.errors, }); } logger.info('Git hooks installed successfully'); } // Get worktree information to find the path for this branch const { stdout: worktreeListOutput } = await execGit(['worktree', 'list', '--porcelain'], { cwd: absoluteRepoPath, }); const allWorktrees = parseWorktreePorcelain(worktreeListOutput); const worktree = allWorktrees.find((w) => w.branch === branch || w.branch === `refs/heads/${branch}` || w.branch.replace(/^refs\/heads\//, '') === branch); if (!worktree) { return res.status(400).json({ error: `No worktree found for branch: ${branch}`, }); } // Set the follow worktree path (not branch name) await execGit(['config', '--local', 'vibetunnel.followWorktree', worktree.path], { cwd: absoluteRepoPath, }); logger.info(`Follow mode enabled for branch: ${branch}`); // Immediately sync main repository to the followed branch try { // Strip refs/heads/ prefix if present const cleanBranch = branch.replace(/^refs\/heads\//, ''); // Check if the branch exists locally const { stdout: branchList } = await execGit(['branch', '--list', cleanBranch], { cwd: absoluteRepoPath, }); if (branchList.trim()) { // Branch exists locally, switch to it await execGit(['checkout', cleanBranch], { cwd: absoluteRepoPath }); logger.info(`Main repository switched to branch: ${cleanBranch}`); } else { // Branch doesn't exist locally, try to fetch and create it try { await execGit(['fetch', 'origin', `${cleanBranch}:${cleanBranch}`], { cwd: absoluteRepoPath, }); await execGit(['checkout', cleanBranch], { cwd: absoluteRepoPath }); logger.info(`Fetched and switched to branch: ${cleanBranch}`); } catch (error) { logger.warn(`Could not fetch/switch to branch ${cleanBranch}:`, error); // Don't fail follow mode enable if branch switch fails } } } catch (error) { logger.warn(`Could not immediately switch to branch ${branch}:`, error); // Don't fail follow mode enable if branch switch fails } // Send notification to Mac app if (control_unix_handler_js_1.controlUnixHandler.isMacAppConnected()) { const notification = (0, control_protocol_js_1.createControlEvent)('system', 'notification', { level: 'info', title: 'Follow Mode Enabled', message: `Now following branch '${branch}' in ${path.basename(absoluteRepoPath)}`, }); control_unix_handler_js_1.controlUnixHandler.sendToMac(notification); } return res.json({ success: true, enabled: true, message: 'Follow mode enabled', branch, hooksInstalled: true, hooksInstallResult: hooksInstallResult, }); } else { // Unset the follow worktree config await execGit(['config', '--local', '--unset', 'vibetunnel.followWorktree'], { cwd: absoluteRepoPath, }); // Uninstall Git hooks when disabling follow mode logger.info('Uninstalling Git hooks'); const uninstallResult = await (0, git_hooks_js_1.uninstallGitHooks)(absoluteRepoPath); if (!uninstallResult.success) { logger.warn('Failed to uninstall some Git hooks:', uninstallResult.errors); // Continue anyway - follow mode is still disabled } else { logger.info('Git hooks uninstalled successfully'); } logger.info('Follow mode disabled'); // Send notification to Mac app if (control_unix_handler_js_1.controlUnixHandler.isMacAppConnected()) { const notification = (0, control_protocol_js_1.createControlEvent)('system', 'notification', { level: 'info', title: 'Follow Mode Disabled', message: `Follow mode has been disabled for ${path.basename(absoluteRepoPath)}`, }); control_unix_handler_js_1.controlUnixHandler.sendToMac(notification); } return res.json({ success: true, enabled: false, message: 'Follow mode disabled', branch, }); } } catch (error) { // Ignore error if config key doesn't exist when unsetting if ((0, git_error_js_1.isGitConfigNotFoundError)(error) && !req.body.enable) { logger.debug('Follow mode was already disabled'); return res.json({ success: true, enabled: false, message: 'Follow mode disabled', }); } logger.error('Error managing follow mode:', error); const gitError = error; return res.status(500).json({ error: 'Failed to manage follow mode', details: gitError.stderr || gitError.message, }); } }); return router; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid29ya3RyZWVzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL3NlcnZlci9yb3V0ZXMvd29ya3RyZWVzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBcVBBLG9EQWdkQztBQXJzQkQscUNBQWlDO0FBQ2pDLDJDQUE2QjtBQUM3QiwrQkFBaUM7QUFDakMsd0RBQWdHO0FBQ2hHLHdEQUE4RjtBQUM5RixrREFBa0Q7QUFDbEQsMEVBQXNFO0FBQ3RFLGtGQUEwRTtBQUUxRSxNQUFNLE1BQU0sR0FBRyxJQUFBLHdCQUFZLEVBQUMsaUJBQWlCLENBQUMsQ0FBQztBQUMvQyxNQUFNLFFBQVEsR0FBRyxJQUFBLGdCQUFTLEVBQUMsT0FBTyxDQUFDLGVBQWUsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDO0FBeUI5RDs7Ozs7R0FLRztBQUNILEtBQUssVUFBVSxPQUFPLENBQ3BCLElBQWMsRUFDZCxVQUE4QyxFQUFFO0lBRWhELElBQUksQ0FBQztRQUNILE1BQU0sRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLEdBQUcsTUFBTSxRQUFRLENBQUMsS0FBSyxFQUFFLElBQUksRUFBRTtZQUNyRCxHQUFHLEVBQUUsT0FBTyxDQUFDLEdBQUcsSUFBSSxPQUFPLENBQUMsR0FBRyxFQUFFO1lBQ2pDLE9BQU8sRUFBRSxPQUFPLENBQUMsT0FBTyxJQUFJLEtBQUssRUFBRSxzQ0FBc0M7WUFDekUsU0FBUyxFQUFFLEVBQUUsR0FBRyxJQUFJLEdBQUcsSUFBSSxFQUFFLHVCQUF1QjtZQUNwRCxHQUFHLEVBQUUsRUFBRSxHQUFHLE9BQU8sQ0FBQyxHQUFHLEVBQUUsbUJBQW1CLEVBQUUsR0FBRyxFQUFFLEVBQUUsc0JBQXNCO1NBQzFFLENBQUMsQ0FBQztRQUNILE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxDQUFDLFFBQVEsRUFBRSxFQUFFLE1BQU0sRUFBRSxNQUFNLENBQUMsUUFBUSxF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