mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 08:13:37 +00:00
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:
@@ -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) });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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) });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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) });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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) });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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) });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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) });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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) });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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) });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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) });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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) });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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) });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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) });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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) });
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user