style: fix formatting with Prettier

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
SuperComboGamer
2025-12-21 20:31:57 -05:00
parent 584f5a3426
commit 8d578558ff
295 changed files with 9088 additions and 10546 deletions

View File

@@ -2,10 +2,10 @@
* POST /checkout-branch endpoint - Create and checkout a new branch
*/
import type { Request, Response } from "express";
import { exec } from "child_process";
import { promisify } from "util";
import { getErrorMessage, logError } from "../common.js";
import type { Request, Response } from 'express';
import { exec } from 'child_process';
import { promisify } from 'util';
import { getErrorMessage, logError } from '../common.js';
const execAsync = promisify(exec);
@@ -20,7 +20,7 @@ export function createCheckoutBranchHandler() {
if (!worktreePath) {
res.status(400).json({
success: false,
error: "worktreePath required",
error: 'worktreePath required',
});
return;
}
@@ -28,7 +28,7 @@ export function createCheckoutBranchHandler() {
if (!branchName) {
res.status(400).json({
success: false,
error: "branchName required",
error: 'branchName required',
});
return;
}
@@ -38,16 +38,15 @@ export function createCheckoutBranchHandler() {
if (invalidChars.test(branchName)) {
res.status(400).json({
success: false,
error: "Branch name contains invalid characters",
error: 'Branch name contains invalid characters',
});
return;
}
// Get current branch for reference
const { stdout: currentBranchOutput } = await execAsync(
"git rev-parse --abbrev-ref HEAD",
{ cwd: worktreePath }
);
const { stdout: currentBranchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', {
cwd: worktreePath,
});
const currentBranch = currentBranchOutput.trim();
// Check if branch already exists
@@ -79,7 +78,7 @@ export function createCheckoutBranchHandler() {
},
});
} catch (error) {
logError(error, "Checkout branch failed");
logError(error, 'Checkout branch failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};

View File

@@ -2,10 +2,10 @@
* POST /commit endpoint - Commit changes in a worktree
*/
import type { Request, Response } from "express";
import { exec } from "child_process";
import { promisify } from "util";
import { getErrorMessage, logError } from "../common.js";
import type { Request, Response } from 'express';
import { exec } from 'child_process';
import { promisify } from 'util';
import { getErrorMessage, logError } from '../common.js';
const execAsync = promisify(exec);
@@ -20,13 +20,13 @@ export function createCommitHandler() {
if (!worktreePath || !message) {
res.status(400).json({
success: false,
error: "worktreePath and message required",
error: 'worktreePath and message required',
});
return;
}
// Check for uncommitted changes
const { stdout: status } = await execAsync("git status --porcelain", {
const { stdout: status } = await execAsync('git status --porcelain', {
cwd: worktreePath,
});
@@ -35,14 +35,14 @@ export function createCommitHandler() {
success: true,
result: {
committed: false,
message: "No changes to commit",
message: 'No changes to commit',
},
});
return;
}
// Stage all changes
await execAsync("git add -A", { cwd: worktreePath });
await execAsync('git add -A', { cwd: worktreePath });
// Create commit
await execAsync(`git commit -m "${message.replace(/"/g, '\\"')}"`, {
@@ -50,16 +50,15 @@ export function createCommitHandler() {
});
// Get commit hash
const { stdout: hashOutput } = await execAsync("git rev-parse HEAD", {
const { stdout: hashOutput } = await execAsync('git rev-parse HEAD', {
cwd: worktreePath,
});
const commitHash = hashOutput.trim().substring(0, 8);
// Get branch name
const { stdout: branchOutput } = await execAsync(
"git rev-parse --abbrev-ref HEAD",
{ cwd: worktreePath }
);
const { stdout: branchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', {
cwd: worktreePath,
});
const branchName = branchOutput.trim();
res.json({
@@ -72,7 +71,7 @@ export function createCommitHandler() {
},
});
} catch (error) {
logError(error, "Commit worktree failed");
logError(error, 'Commit worktree failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};

View File

@@ -2,7 +2,7 @@
* POST /create-pr endpoint - Commit changes and create a pull request from a worktree
*/
import type { Request, Response } from "express";
import type { Request, Response } from 'express';
import {
getErrorMessage,
logError,
@@ -10,26 +10,27 @@ import {
execEnv,
isValidBranchName,
isGhCliAvailable,
} from "../common.js";
import { updateWorktreePRInfo } from "../../../lib/worktree-metadata.js";
} from '../common.js';
import { updateWorktreePRInfo } from '../../../lib/worktree-metadata.js';
export function createCreatePRHandler() {
return async (req: Request, res: Response): Promise<void> => {
try {
const { worktreePath, projectPath, commitMessage, prTitle, prBody, baseBranch, draft } = req.body as {
worktreePath: string;
projectPath?: string;
commitMessage?: string;
prTitle?: string;
prBody?: string;
baseBranch?: string;
draft?: boolean;
};
const { worktreePath, projectPath, commitMessage, prTitle, prBody, baseBranch, draft } =
req.body as {
worktreePath: string;
projectPath?: string;
commitMessage?: string;
prTitle?: string;
prBody?: string;
baseBranch?: string;
draft?: boolean;
};
if (!worktreePath) {
res.status(400).json({
success: false,
error: "worktreePath required",
error: 'worktreePath required',
});
return;
}
@@ -39,23 +40,23 @@ export function createCreatePRHandler() {
const effectiveProjectPath = projectPath || worktreePath;
// Get current branch name
const { stdout: branchOutput } = await execAsync(
"git rev-parse --abbrev-ref HEAD",
{ cwd: worktreePath, env: execEnv }
);
const { stdout: branchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', {
cwd: worktreePath,
env: execEnv,
});
const branchName = branchOutput.trim();
// Validate branch name for security
if (!isValidBranchName(branchName)) {
res.status(400).json({
success: false,
error: "Invalid branch name contains unsafe characters",
error: 'Invalid branch name contains unsafe characters',
});
return;
}
// Check for uncommitted changes
const { stdout: status } = await execAsync("git status --porcelain", {
const { stdout: status } = await execAsync('git status --porcelain', {
cwd: worktreePath,
env: execEnv,
});
@@ -67,7 +68,7 @@ export function createCreatePRHandler() {
const message = commitMessage || `Changes from ${branchName}`;
// Stage all changes
await execAsync("git add -A", { cwd: worktreePath, env: execEnv });
await execAsync('git add -A', { cwd: worktreePath, env: execEnv });
// Create commit
await execAsync(`git commit -m "${message.replace(/"/g, '\\"')}"`, {
@@ -76,7 +77,7 @@ export function createCreatePRHandler() {
});
// Get commit hash
const { stdout: hashOutput } = await execAsync("git rev-parse HEAD", {
const { stdout: hashOutput } = await execAsync('git rev-parse HEAD', {
cwd: worktreePath,
env: execEnv,
});
@@ -100,8 +101,8 @@ export function createCreatePRHandler() {
} catch (error2: unknown) {
// Capture push error for reporting
const err = error2 as { stderr?: string; message?: string };
pushError = err.stderr || err.message || "Push failed";
console.error("[CreatePR] Push failed:", pushError);
pushError = err.stderr || err.message || 'Push failed';
console.error('[CreatePR] Push failed:', pushError);
}
}
@@ -115,10 +116,10 @@ export function createCreatePRHandler() {
}
// Create PR using gh CLI or provide browser fallback
const base = baseBranch || "main";
const base = baseBranch || 'main';
const title = prTitle || branchName;
const body = prBody || `Changes from branch ${branchName}`;
const draftFlag = draft ? "--draft" : "";
const draftFlag = draft ? '--draft' : '';
let prUrl: string | null = null;
let prError: string | null = null;
@@ -131,7 +132,7 @@ export function createCreatePRHandler() {
let upstreamRepo: string | null = null;
let originOwner: string | null = null;
try {
const { stdout: remotes } = await execAsync("git remote -v", {
const { stdout: remotes } = await execAsync('git remote -v', {
cwd: worktreePath,
env: execEnv,
});
@@ -150,15 +151,17 @@ export function createCreatePRHandler() {
}
if (!match) {
// Try HTTPS format: https://github.com/owner/repo.git
match = line.match(/^(\w+)\s+https?:\/\/[^/]+\/([^/]+)\/([^\s]+?)(?:\.git)?\s+\(fetch\)/);
match = line.match(
/^(\w+)\s+https?:\/\/[^/]+\/([^/]+)\/([^\s]+?)(?:\.git)?\s+\(fetch\)/
);
}
if (match) {
const [, remoteName, owner, repo] = match;
if (remoteName === "upstream") {
if (remoteName === 'upstream') {
upstreamRepo = `${owner}/${repo}`;
repoUrl = `https://github.com/${owner}/${repo}`;
} else if (remoteName === "origin") {
} else if (remoteName === 'origin') {
originOwner = owner;
if (!repoUrl) {
repoUrl = `https://github.com/${owner}/${repo}`;
@@ -173,7 +176,7 @@ export function createCreatePRHandler() {
// Fallback: Try to get repo URL from git config if remote parsing failed
if (!repoUrl) {
try {
const { stdout: originUrl } = await execAsync("git config --get remote.origin.url", {
const { stdout: originUrl } = await execAsync('git config --get remote.origin.url', {
cwd: worktreePath,
env: execEnv,
});
@@ -217,9 +220,11 @@ export function createCreatePRHandler() {
// This is more reliable than gh pr view as it explicitly searches by branch name
// For forks, we need to use owner:branch format for the head parameter
const headRef = upstreamRepo && originOwner ? `${originOwner}:${branchName}` : branchName;
const repoArg = upstreamRepo ? ` --repo "${upstreamRepo}"` : "";
const repoArg = upstreamRepo ? ` --repo "${upstreamRepo}"` : '';
console.log(`[CreatePR] Checking for existing PR for branch: ${branchName} (headRef: ${headRef})`);
console.log(
`[CreatePR] Checking for existing PR for branch: ${branchName} (headRef: ${headRef})`
);
try {
const listCmd = `gh pr list${repoArg} --head "${headRef}" --json number,title,url,state --limit 1`;
console.log(`[CreatePR] Running: ${listCmd}`);
@@ -234,7 +239,9 @@ export function createCreatePRHandler() {
if (Array.isArray(existingPrs) && existingPrs.length > 0) {
const existingPr = existingPrs[0];
// PR already exists - use it and store metadata
console.log(`[CreatePR] PR already exists for branch ${branchName}: PR #${existingPr.number}`);
console.log(
`[CreatePR] PR already exists for branch ${branchName}: PR #${existingPr.number}`
);
prUrl = existingPr.url;
prNumber = existingPr.number;
prAlreadyExisted = true;
@@ -244,10 +251,12 @@ export function createCreatePRHandler() {
number: existingPr.number,
url: existingPr.url,
title: existingPr.title || title,
state: existingPr.state || "open",
state: existingPr.state || 'open',
createdAt: new Date().toISOString(),
});
console.log(`[CreatePR] Stored existing PR info for branch ${branchName}: PR #${existingPr.number}`);
console.log(
`[CreatePR] Stored existing PR info for branch ${branchName}: PR #${existingPr.number}`
);
} else {
console.log(`[CreatePR] No existing PR found for branch ${branchName}`);
}
@@ -293,23 +302,25 @@ export function createCreatePRHandler() {
number: prNumber,
url: prUrl,
title,
state: draft ? "draft" : "open",
state: draft ? 'draft' : 'open',
createdAt: new Date().toISOString(),
});
console.log(`[CreatePR] Stored PR info for branch ${branchName}: PR #${prNumber}`);
console.log(
`[CreatePR] Stored PR info for branch ${branchName}: PR #${prNumber}`
);
} catch (metadataError) {
console.error("[CreatePR] Failed to store PR metadata:", metadataError);
console.error('[CreatePR] Failed to store PR metadata:', metadataError);
}
}
}
} catch (ghError: unknown) {
// gh CLI failed - check if it's "already exists" error and try to fetch the PR
const err = ghError as { stderr?: string; message?: string };
const errorMessage = err.stderr || err.message || "PR creation failed";
const errorMessage = err.stderr || err.message || 'PR creation failed';
console.log(`[CreatePR] gh pr create failed: ${errorMessage}`);
// If error indicates PR already exists, try to fetch it
if (errorMessage.toLowerCase().includes("already exists")) {
if (errorMessage.toLowerCase().includes('already exists')) {
console.log(`[CreatePR] PR already exists error - trying to fetch existing PR`);
try {
const { stdout: viewOutput } = await execAsync(
@@ -326,13 +337,13 @@ export function createCreatePRHandler() {
number: existingPr.number,
url: existingPr.url,
title: existingPr.title || title,
state: existingPr.state || "open",
state: existingPr.state || 'open',
createdAt: new Date().toISOString(),
});
console.log(`[CreatePR] Fetched and stored existing PR: #${existingPr.number}`);
}
} catch (viewError) {
console.error("[CreatePR] Failed to fetch existing PR:", viewError);
console.error('[CreatePR] Failed to fetch existing PR:', viewError);
prError = errorMessage;
}
} else {
@@ -341,7 +352,7 @@ export function createCreatePRHandler() {
}
}
} else {
prError = "gh_cli_not_available";
prError = 'gh_cli_not_available';
}
// Return result with browser fallback URL
@@ -362,7 +373,7 @@ export function createCreatePRHandler() {
},
});
} catch (error) {
logError(error, "Create PR failed");
logError(error, 'Create PR failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};

View File

@@ -2,10 +2,10 @@
* POST /list-branches endpoint - List all local branches
*/
import type { Request, Response } from "express";
import { exec } from "child_process";
import { promisify } from "util";
import { getErrorMessage, logWorktreeError } from "../common.js";
import type { Request, Response } from 'express';
import { exec } from 'child_process';
import { promisify } from 'util';
import { getErrorMessage, logWorktreeError } from '../common.js';
const execAsync = promisify(exec);
@@ -25,33 +25,31 @@ export function createListBranchesHandler() {
if (!worktreePath) {
res.status(400).json({
success: false,
error: "worktreePath required",
error: 'worktreePath required',
});
return;
}
// Get current branch
const { stdout: currentBranchOutput } = await execAsync(
"git rev-parse --abbrev-ref HEAD",
{ cwd: worktreePath }
);
const { stdout: currentBranchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', {
cwd: worktreePath,
});
const currentBranch = currentBranchOutput.trim();
// List all local branches
// Use double quotes around the format string for cross-platform compatibility
// Single quotes are preserved literally on Windows; double quotes work on both
const { stdout: branchesOutput } = await execAsync(
'git branch --format="%(refname:short)"',
{ cwd: worktreePath }
);
const { stdout: branchesOutput } = await execAsync('git branch --format="%(refname:short)"', {
cwd: worktreePath,
});
const branches: BranchInfo[] = branchesOutput
.trim()
.split("\n")
.split('\n')
.filter((b) => b.trim())
.map((name) => {
// Remove any surrounding quotes (Windows git may preserve them)
const cleanName = name.trim().replace(/^['"]|['"]$/g, "");
const cleanName = name.trim().replace(/^['"]|['"]$/g, '');
return {
name: cleanName,
isCurrent: cleanName === currentBranch,
@@ -93,7 +91,7 @@ export function createListBranchesHandler() {
});
} catch (error) {
const worktreePath = req.body?.worktreePath;
logWorktreeError(error, "List branches failed", worktreePath);
logWorktreeError(error, 'List branches failed', worktreePath);
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};

View File

@@ -5,9 +5,9 @@
* including their ports and URLs.
*/
import type { Request, Response } from "express";
import { getDevServerService } from "../../../services/dev-server-service.js";
import { getErrorMessage, logError } from "../common.js";
import type { Request, Response } from 'express';
import { getDevServerService } from '../../../services/dev-server-service.js';
import { getErrorMessage, logError } from '../common.js';
export function createListDevServersHandler() {
return async (_req: Request, res: Response): Promise<void> => {
@@ -22,7 +22,7 @@ export function createListDevServersHandler() {
},
});
} catch (error) {
logError(error, "List dev servers failed");
logError(error, 'List dev servers failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};

View File

@@ -2,11 +2,11 @@
* POST /merge endpoint - Merge feature (merge worktree branch into main)
*/
import type { Request, Response } from "express";
import { exec } from "child_process";
import { promisify } from "util";
import path from "path";
import { getErrorMessage, logError } from "../common.js";
import type { Request, Response } from 'express';
import { exec } from 'child_process';
import { promisify } from 'util';
import path from 'path';
import { getErrorMessage, logError } from '../common.js';
const execAsync = promisify(exec);
@@ -20,42 +20,34 @@ export function createMergeHandler() {
};
if (!projectPath || !featureId) {
res
.status(400)
.json({
success: false,
error: "projectPath and featureId required",
});
res.status(400).json({
success: false,
error: 'projectPath and featureId required',
});
return;
}
const branchName = `feature/${featureId}`;
// Git worktrees are stored in project directory
const worktreePath = path.join(projectPath, ".worktrees", featureId);
const worktreePath = path.join(projectPath, '.worktrees', featureId);
// Get current branch
const { stdout: currentBranch } = await execAsync(
"git rev-parse --abbrev-ref HEAD",
{ cwd: projectPath }
);
const { stdout: currentBranch } = await execAsync('git rev-parse --abbrev-ref HEAD', {
cwd: projectPath,
});
// Merge the feature branch
const mergeCmd = options?.squash
? `git merge --squash ${branchName}`
: `git merge ${branchName} -m "${
options?.message || `Merge ${branchName}`
}"`;
: `git merge ${branchName} -m "${options?.message || `Merge ${branchName}`}"`;
await execAsync(mergeCmd, { cwd: projectPath });
// If squash merge, need to commit
if (options?.squash) {
await execAsync(
`git commit -m "${
options?.message || `Merge ${branchName} (squash)`
}"`,
{ cwd: projectPath }
);
await execAsync(`git commit -m "${options?.message || `Merge ${branchName} (squash)`}"`, {
cwd: projectPath,
});
}
// Clean up worktree and branch
@@ -70,7 +62,7 @@ export function createMergeHandler() {
res.json({ success: true, mergedBranch: branchName });
} catch (error) {
logError(error, "Merge worktree failed");
logError(error, 'Merge worktree failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};

View File

@@ -3,10 +3,10 @@
* GET /default-editor endpoint - Get the name of the default code editor
*/
import type { Request, Response } from "express";
import { exec } from "child_process";
import { promisify } from "util";
import { getErrorMessage, logError } from "../common.js";
import type { Request, Response } from 'express';
import { exec } from 'child_process';
import { promisify } from 'util';
import { getErrorMessage, logError } from '../common.js';
const execAsync = promisify(exec);
@@ -29,8 +29,8 @@ async function detectDefaultEditor(): Promise<EditorInfo> {
// Try Cursor first (if user has Cursor, they probably prefer it)
try {
await execAsync("which cursor || where cursor");
cachedEditor = { name: "Cursor", command: "cursor" };
await execAsync('which cursor || where cursor');
cachedEditor = { name: 'Cursor', command: 'cursor' };
return cachedEditor;
} catch {
// Cursor not found
@@ -38,8 +38,8 @@ async function detectDefaultEditor(): Promise<EditorInfo> {
// Try VS Code
try {
await execAsync("which code || where code");
cachedEditor = { name: "VS Code", command: "code" };
await execAsync('which code || where code');
cachedEditor = { name: 'VS Code', command: 'code' };
return cachedEditor;
} catch {
// VS Code not found
@@ -47,8 +47,8 @@ async function detectDefaultEditor(): Promise<EditorInfo> {
// Try Zed
try {
await execAsync("which zed || where zed");
cachedEditor = { name: "Zed", command: "zed" };
await execAsync('which zed || where zed');
cachedEditor = { name: 'Zed', command: 'zed' };
return cachedEditor;
} catch {
// Zed not found
@@ -56,8 +56,8 @@ async function detectDefaultEditor(): Promise<EditorInfo> {
// Try Sublime Text
try {
await execAsync("which subl || where subl");
cachedEditor = { name: "Sublime Text", command: "subl" };
await execAsync('which subl || where subl');
cachedEditor = { name: 'Sublime Text', command: 'subl' };
return cachedEditor;
} catch {
// Sublime not found
@@ -65,12 +65,12 @@ async function detectDefaultEditor(): Promise<EditorInfo> {
// Fallback to file manager
const platform = process.platform;
if (platform === "darwin") {
cachedEditor = { name: "Finder", command: "open" };
} else if (platform === "win32") {
cachedEditor = { name: "Explorer", command: "explorer" };
if (platform === 'darwin') {
cachedEditor = { name: 'Finder', command: 'open' };
} else if (platform === 'win32') {
cachedEditor = { name: 'Explorer', command: 'explorer' };
} else {
cachedEditor = { name: "File Manager", command: "xdg-open" };
cachedEditor = { name: 'File Manager', command: 'xdg-open' };
}
return cachedEditor;
}
@@ -87,7 +87,7 @@ export function createGetDefaultEditorHandler() {
},
});
} catch (error) {
logError(error, "Get default editor failed");
logError(error, 'Get default editor failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};
@@ -103,7 +103,7 @@ export function createOpenInEditorHandler() {
if (!worktreePath) {
res.status(400).json({
success: false,
error: "worktreePath required",
error: 'worktreePath required',
});
return;
}
@@ -125,15 +125,15 @@ export function createOpenInEditorHandler() {
let openCommand: string;
let fallbackName: string;
if (platform === "darwin") {
if (platform === 'darwin') {
openCommand = `open "${worktreePath}"`;
fallbackName = "Finder";
} else if (platform === "win32") {
fallbackName = 'Finder';
} else if (platform === 'win32') {
openCommand = `explorer "${worktreePath}"`;
fallbackName = "Explorer";
fallbackName = 'Explorer';
} else {
openCommand = `xdg-open "${worktreePath}"`;
fallbackName = "File Manager";
fallbackName = 'File Manager';
}
await execAsync(openCommand);
@@ -146,7 +146,7 @@ export function createOpenInEditorHandler() {
});
}
} catch (error) {
logError(error, "Open in editor failed");
logError(error, 'Open in editor failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};

View File

@@ -2,7 +2,7 @@
* POST /pr-info endpoint - Get PR info and comments for a branch
*/
import type { Request, Response } from "express";
import type { Request, Response } from 'express';
import {
getErrorMessage,
logError,
@@ -10,7 +10,7 @@ import {
execEnv,
isValidBranchName,
isGhCliAvailable,
} from "../common.js";
} from '../common.js';
export interface PRComment {
id: number;
@@ -44,7 +44,7 @@ export function createPRInfoHandler() {
if (!worktreePath || !branchName) {
res.status(400).json({
success: false,
error: "worktreePath and branchName required",
error: 'worktreePath and branchName required',
});
return;
}
@@ -53,7 +53,7 @@ export function createPRInfoHandler() {
if (!isValidBranchName(branchName)) {
res.status(400).json({
success: false,
error: "Invalid branch name contains unsafe characters",
error: 'Invalid branch name contains unsafe characters',
});
return;
}
@@ -67,7 +67,7 @@ export function createPRInfoHandler() {
result: {
hasPR: false,
ghCliAvailable: false,
error: "gh CLI not available",
error: 'gh CLI not available',
},
});
return;
@@ -79,7 +79,7 @@ export function createPRInfoHandler() {
let originRepo: string | null = null;
try {
const { stdout: remotes } = await execAsync("git remote -v", {
const { stdout: remotes } = await execAsync('git remote -v', {
cwd: worktreePath,
env: execEnv,
});
@@ -87,21 +87,15 @@ export function createPRInfoHandler() {
const lines = remotes.split(/\r?\n/);
for (const line of lines) {
let match =
line.match(
/^(\w+)\s+.*[:/]([^/]+)\/([^/\s]+?)(?:\.git)?\s+\(fetch\)/
) ||
line.match(
/^(\w+)\s+git@[^:]+:([^/]+)\/([^\s]+?)(?:\.git)?\s+\(fetch\)/
) ||
line.match(
/^(\w+)\s+https?:\/\/[^/]+\/([^/]+)\/([^\s]+?)(?:\.git)?\s+\(fetch\)/
);
line.match(/^(\w+)\s+.*[:/]([^/]+)\/([^/\s]+?)(?:\.git)?\s+\(fetch\)/) ||
line.match(/^(\w+)\s+git@[^:]+:([^/]+)\/([^\s]+?)(?:\.git)?\s+\(fetch\)/) ||
line.match(/^(\w+)\s+https?:\/\/[^/]+\/([^/]+)\/([^\s]+?)(?:\.git)?\s+\(fetch\)/);
if (match) {
const [, remoteName, owner, repo] = match;
if (remoteName === "upstream") {
if (remoteName === 'upstream') {
upstreamRepo = `${owner}/${repo}`;
} else if (remoteName === "origin") {
} else if (remoteName === 'origin') {
originOwner = owner;
originRepo = repo;
}
@@ -113,16 +107,11 @@ export function createPRInfoHandler() {
if (!originOwner || !originRepo) {
try {
const { stdout: originUrl } = await execAsync(
"git config --get remote.origin.url",
{
cwd: worktreePath,
env: execEnv,
}
);
const match = originUrl
.trim()
.match(/[:/]([^/]+)\/([^/\s]+?)(?:\.git)?$/);
const { stdout: originUrl } = await execAsync('git config --get remote.origin.url', {
cwd: worktreePath,
env: execEnv,
});
const match = originUrl.trim().match(/[:/]([^/]+)\/([^/\s]+?)(?:\.git)?$/);
if (match) {
if (!originOwner) {
originOwner = match[1];
@@ -137,21 +126,18 @@ export function createPRInfoHandler() {
}
const targetRepo =
upstreamRepo || (originOwner && originRepo
? `${originOwner}/${originRepo}`
: null);
const repoFlag = targetRepo ? ` --repo "${targetRepo}"` : "";
const headRef =
upstreamRepo && originOwner ? `${originOwner}:${branchName}` : branchName;
upstreamRepo || (originOwner && originRepo ? `${originOwner}/${originRepo}` : null);
const repoFlag = targetRepo ? ` --repo "${targetRepo}"` : '';
const headRef = upstreamRepo && originOwner ? `${originOwner}:${branchName}` : branchName;
// Get PR info for the branch using gh CLI
try {
// First, find the PR associated with this branch
const listCmd = `gh pr list${repoFlag} --head "${headRef}" --json number,title,url,state,author,body --limit 1`;
const { stdout: prListOutput } = await execAsync(
listCmd,
{ cwd: worktreePath, env: execEnv }
);
const { stdout: prListOutput } = await execAsync(listCmd, {
cwd: worktreePath,
env: execEnv,
});
const prList = JSON.parse(prListOutput);
@@ -173,25 +159,22 @@ export function createPRInfoHandler() {
let comments: PRComment[] = [];
try {
const viewCmd = `gh pr view ${prNumber}${repoFlag} --json comments`;
const { stdout: commentsOutput } = await execAsync(
viewCmd,
{ cwd: worktreePath, env: execEnv }
);
const { stdout: commentsOutput } = await execAsync(viewCmd, {
cwd: worktreePath,
env: execEnv,
});
const commentsData = JSON.parse(commentsOutput);
comments = (commentsData.comments || []).map((c: {
id: number;
author: { login: string };
body: string;
createdAt: string;
}) => ({
id: c.id,
author: c.author?.login || "unknown",
body: c.body,
createdAt: c.createdAt,
isReviewComment: false,
}));
comments = (commentsData.comments || []).map(
(c: { id: number; author: { login: string }; body: string; createdAt: string }) => ({
id: c.id,
author: c.author?.login || 'unknown',
body: c.body,
createdAt: c.createdAt,
isReviewComment: false,
})
);
} catch (error) {
console.warn("[PRInfo] Failed to fetch PR comments:", error);
console.warn('[PRInfo] Failed to fetch PR comments:', error);
}
// Get review comments (inline code comments)
@@ -201,33 +184,35 @@ export function createPRInfoHandler() {
try {
const reviewsEndpoint = `repos/${targetRepo}/pulls/${prNumber}/comments`;
const reviewsCmd = `gh api ${reviewsEndpoint}`;
const { stdout: reviewsOutput } = await execAsync(
reviewsCmd,
{ cwd: worktreePath, env: execEnv }
);
const { stdout: reviewsOutput } = await execAsync(reviewsCmd, {
cwd: worktreePath,
env: execEnv,
});
const reviewsData = JSON.parse(reviewsOutput);
reviewComments = reviewsData.map((c: {
id: number;
user: { login: string };
body: string;
path: string;
line?: number;
original_line?: number;
created_at: string;
}) => ({
id: c.id,
author: c.user?.login || "unknown",
body: c.body,
path: c.path,
line: c.line || c.original_line,
createdAt: c.created_at,
isReviewComment: true,
}));
reviewComments = reviewsData.map(
(c: {
id: number;
user: { login: string };
body: string;
path: string;
line?: number;
original_line?: number;
created_at: string;
}) => ({
id: c.id,
author: c.user?.login || 'unknown',
body: c.body,
path: c.path,
line: c.line || c.original_line,
createdAt: c.created_at,
isReviewComment: true,
})
);
} catch (error) {
console.warn("[PRInfo] Failed to fetch review comments:", error);
console.warn('[PRInfo] Failed to fetch review comments:', error);
}
} else {
console.warn("[PRInfo] Cannot fetch review comments: repository info not available");
console.warn('[PRInfo] Cannot fetch review comments: repository info not available');
}
const prInfo: PRInfo = {
@@ -235,8 +220,8 @@ export function createPRInfoHandler() {
title: pr.title,
url: pr.url,
state: pr.state,
author: pr.author?.login || "unknown",
body: pr.body || "",
author: pr.author?.login || 'unknown',
body: pr.body || '',
comments,
reviewComments,
};
@@ -251,7 +236,7 @@ export function createPRInfoHandler() {
});
} catch (error) {
// gh CLI failed - might not be authenticated or no remote
logError(error, "Failed to get PR info");
logError(error, 'Failed to get PR info');
res.json({
success: true,
result: {
@@ -262,7 +247,7 @@ export function createPRInfoHandler() {
});
}
} catch (error) {
logError(error, "PR info handler failed");
logError(error, 'PR info handler failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};

View File

@@ -2,10 +2,10 @@
* POST /pull endpoint - Pull latest changes for a worktree/branch
*/
import type { Request, Response } from "express";
import { exec } from "child_process";
import { promisify } from "util";
import { getErrorMessage, logError } from "../common.js";
import type { Request, Response } from 'express';
import { exec } from 'child_process';
import { promisify } from 'util';
import { getErrorMessage, logError } from '../common.js';
const execAsync = promisify(exec);
@@ -19,23 +19,22 @@ export function createPullHandler() {
if (!worktreePath) {
res.status(400).json({
success: false,
error: "worktreePath required",
error: 'worktreePath required',
});
return;
}
// Get current branch name
const { stdout: branchOutput } = await execAsync(
"git rev-parse --abbrev-ref HEAD",
{ cwd: worktreePath }
);
const { stdout: branchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', {
cwd: worktreePath,
});
const branchName = branchOutput.trim();
// Fetch latest from remote
await execAsync("git fetch origin", { cwd: worktreePath });
await execAsync('git fetch origin', { cwd: worktreePath });
// Check if there are local changes that would be overwritten
const { stdout: status } = await execAsync("git status --porcelain", {
const { stdout: status } = await execAsync('git status --porcelain', {
cwd: worktreePath,
});
const hasLocalChanges = status.trim().length > 0;
@@ -43,35 +42,34 @@ export function createPullHandler() {
if (hasLocalChanges) {
res.status(400).json({
success: false,
error: "You have local changes. Please commit them before pulling.",
error: 'You have local changes. Please commit them before pulling.',
});
return;
}
// Pull latest changes
try {
const { stdout: pullOutput } = await execAsync(
`git pull origin ${branchName}`,
{ cwd: worktreePath }
);
const { stdout: pullOutput } = await execAsync(`git pull origin ${branchName}`, {
cwd: worktreePath,
});
// Check if we pulled any changes
const alreadyUpToDate = pullOutput.includes("Already up to date");
const alreadyUpToDate = pullOutput.includes('Already up to date');
res.json({
success: true,
result: {
branch: branchName,
pulled: !alreadyUpToDate,
message: alreadyUpToDate ? "Already up to date" : "Pulled latest changes",
message: alreadyUpToDate ? 'Already up to date' : 'Pulled latest changes',
},
});
} catch (pullError: unknown) {
const err = pullError as { stderr?: string; message?: string };
const errorMsg = err.stderr || err.message || "Pull failed";
const errorMsg = err.stderr || err.message || 'Pull failed';
// Check for common errors
if (errorMsg.includes("no tracking information")) {
if (errorMsg.includes('no tracking information')) {
res.status(400).json({
success: false,
error: `Branch '${branchName}' has no upstream branch. Push it first or set upstream with: git branch --set-upstream-to=origin/${branchName}`,
@@ -85,7 +83,7 @@ export function createPullHandler() {
});
}
} catch (error) {
logError(error, "Pull failed");
logError(error, 'Pull failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};

View File

@@ -2,10 +2,10 @@
* POST /push endpoint - Push a worktree branch to remote
*/
import type { Request, Response } from "express";
import { exec } from "child_process";
import { promisify } from "util";
import { getErrorMessage, logError } from "../common.js";
import type { Request, Response } from 'express';
import { exec } from 'child_process';
import { promisify } from 'util';
import { getErrorMessage, logError } from '../common.js';
const execAsync = promisify(exec);
@@ -20,20 +20,19 @@ export function createPushHandler() {
if (!worktreePath) {
res.status(400).json({
success: false,
error: "worktreePath required",
error: 'worktreePath required',
});
return;
}
// Get branch name
const { stdout: branchOutput } = await execAsync(
"git rev-parse --abbrev-ref HEAD",
{ cwd: worktreePath }
);
const { stdout: branchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', {
cwd: worktreePath,
});
const branchName = branchOutput.trim();
// Push the branch
const forceFlag = force ? "--force" : "";
const forceFlag = force ? '--force' : '';
try {
await execAsync(`git push -u origin ${branchName} ${forceFlag}`, {
cwd: worktreePath,
@@ -54,7 +53,7 @@ export function createPushHandler() {
},
});
} catch (error) {
logError(error, "Push worktree failed");
logError(error, 'Push worktree failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};

View File

@@ -6,9 +6,9 @@
* affecting the main dev server.
*/
import type { Request, Response } from "express";
import { getDevServerService } from "../../../services/dev-server-service.js";
import { getErrorMessage, logError } from "../common.js";
import type { Request, Response } from 'express';
import { getDevServerService } from '../../../services/dev-server-service.js';
import { getErrorMessage, logError } from '../common.js';
export function createStartDevHandler() {
return async (req: Request, res: Response): Promise<void> => {
@@ -21,7 +21,7 @@ export function createStartDevHandler() {
if (!projectPath) {
res.status(400).json({
success: false,
error: "projectPath is required",
error: 'projectPath is required',
});
return;
}
@@ -29,7 +29,7 @@ export function createStartDevHandler() {
if (!worktreePath) {
res.status(400).json({
success: false,
error: "worktreePath is required",
error: 'worktreePath is required',
});
return;
}
@@ -50,11 +50,11 @@ export function createStartDevHandler() {
} else {
res.status(400).json({
success: false,
error: result.error || "Failed to start dev server",
error: result.error || 'Failed to start dev server',
});
}
} catch (error) {
logError(error, "Start dev server failed");
logError(error, 'Start dev server failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};

View File

@@ -5,9 +5,9 @@
* freeing up the ports for reuse.
*/
import type { Request, Response } from "express";
import { getDevServerService } from "../../../services/dev-server-service.js";
import { getErrorMessage, logError } from "../common.js";
import type { Request, Response } from 'express';
import { getDevServerService } from '../../../services/dev-server-service.js';
import { getErrorMessage, logError } from '../common.js';
export function createStopDevHandler() {
return async (req: Request, res: Response): Promise<void> => {
@@ -19,7 +19,7 @@ export function createStopDevHandler() {
if (!worktreePath) {
res.status(400).json({
success: false,
error: "worktreePath is required",
error: 'worktreePath is required',
});
return;
}
@@ -38,11 +38,11 @@ export function createStopDevHandler() {
} else {
res.status(400).json({
success: false,
error: result.error || "Failed to stop dev server",
error: result.error || 'Failed to stop dev server',
});
}
} catch (error) {
logError(error, "Stop dev server failed");
logError(error, 'Stop dev server failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};

View File

@@ -6,10 +6,10 @@
* the user should commit first.
*/
import type { Request, Response } from "express";
import { exec } from "child_process";
import { promisify } from "util";
import { getErrorMessage, logError } from "../common.js";
import type { Request, Response } from 'express';
import { exec } from 'child_process';
import { promisify } from 'util';
import { getErrorMessage, logError } from '../common.js';
const execAsync = promisify(exec);
@@ -19,13 +19,16 @@ const execAsync = promisify(exec);
*/
async function hasUncommittedChanges(cwd: string): Promise<boolean> {
try {
const { stdout } = await execAsync("git status --porcelain", { cwd });
const lines = stdout.trim().split("\n").filter((line) => {
if (!line.trim()) return false;
// Exclude .worktrees/ directory (created by automaker)
if (line.includes(".worktrees/") || line.endsWith(".worktrees")) return false;
return true;
});
const { stdout } = await execAsync('git status --porcelain', { cwd });
const lines = stdout
.trim()
.split('\n')
.filter((line) => {
if (!line.trim()) return false;
// Exclude .worktrees/ directory (created by automaker)
if (line.includes('.worktrees/') || line.endsWith('.worktrees')) return false;
return true;
});
return lines.length > 0;
} catch {
return false;
@@ -38,18 +41,21 @@ async function hasUncommittedChanges(cwd: string): Promise<boolean> {
*/
async function getChangesSummary(cwd: string): Promise<string> {
try {
const { stdout } = await execAsync("git status --short", { cwd });
const lines = stdout.trim().split("\n").filter((line) => {
if (!line.trim()) return false;
// Exclude .worktrees/ directory
if (line.includes(".worktrees/") || line.endsWith(".worktrees")) return false;
return true;
});
if (lines.length === 0) return "";
if (lines.length <= 5) return lines.join(", ");
return `${lines.slice(0, 5).join(", ")} and ${lines.length - 5} more files`;
const { stdout } = await execAsync('git status --short', { cwd });
const lines = stdout
.trim()
.split('\n')
.filter((line) => {
if (!line.trim()) return false;
// Exclude .worktrees/ directory
if (line.includes('.worktrees/') || line.endsWith('.worktrees')) return false;
return true;
});
if (lines.length === 0) return '';
if (lines.length <= 5) return lines.join(', ');
return `${lines.slice(0, 5).join(', ')} and ${lines.length - 5} more files`;
} catch {
return "unknown changes";
return 'unknown changes';
}
}
@@ -64,7 +70,7 @@ export function createSwitchBranchHandler() {
if (!worktreePath) {
res.status(400).json({
success: false,
error: "worktreePath required",
error: 'worktreePath required',
});
return;
}
@@ -72,16 +78,15 @@ export function createSwitchBranchHandler() {
if (!branchName) {
res.status(400).json({
success: false,
error: "branchName required",
error: 'branchName required',
});
return;
}
// Get current branch
const { stdout: currentBranchOutput } = await execAsync(
"git rev-parse --abbrev-ref HEAD",
{ cwd: worktreePath }
);
const { stdout: currentBranchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', {
cwd: worktreePath,
});
const previousBranch = currentBranchOutput.trim();
if (previousBranch === branchName) {
@@ -115,7 +120,7 @@ export function createSwitchBranchHandler() {
res.status(400).json({
success: false,
error: `Cannot switch branches: you have uncommitted changes (${summary}). Please commit your changes first.`,
code: "UNCOMMITTED_CHANGES",
code: 'UNCOMMITTED_CHANGES',
});
return;
}
@@ -132,7 +137,7 @@ export function createSwitchBranchHandler() {
},
});
} catch (error) {
logError(error, "Switch branch failed");
logError(error, 'Switch branch failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};