fix: adress pr reviews

This commit is contained in:
Kacper
2025-12-23 21:07:36 +01:00
parent 4958ee1dda
commit e0c5f55fe7
14 changed files with 325 additions and 345 deletions

View File

@@ -4,6 +4,7 @@
import { Router } from 'express';
import { validatePathParams } from '../../middleware/validate-paths.js';
import { requireValidWorktree, requireValidProject, requireGitRepoOnly } from './middleware.js';
import { createInfoHandler } from './routes/info.js';
import { createStatusHandler } from './routes/status.js';
import { createListHandler } from './routes/list.js';
@@ -38,17 +39,42 @@ export function createWorktreeRoutes(): Router {
router.post('/list', createListHandler());
router.post('/diffs', validatePathParams('projectPath'), createDiffsHandler());
router.post('/file-diff', validatePathParams('projectPath', 'filePath'), createFileDiffHandler());
router.post('/merge', validatePathParams('projectPath'), createMergeHandler());
router.post(
'/merge',
validatePathParams('projectPath'),
requireValidProject,
createMergeHandler()
);
router.post('/create', validatePathParams('projectPath'), createCreateHandler());
router.post('/delete', validatePathParams('projectPath', 'worktreePath'), createDeleteHandler());
router.post('/create-pr', createCreatePRHandler());
router.post('/pr-info', createPRInfoHandler());
router.post('/commit', validatePathParams('worktreePath'), createCommitHandler());
router.post('/push', validatePathParams('worktreePath'), createPushHandler());
router.post('/pull', validatePathParams('worktreePath'), createPullHandler());
router.post('/checkout-branch', createCheckoutBranchHandler());
router.post('/list-branches', validatePathParams('worktreePath'), createListBranchesHandler());
router.post('/switch-branch', createSwitchBranchHandler());
router.post(
'/commit',
validatePathParams('worktreePath'),
requireGitRepoOnly,
createCommitHandler()
);
router.post(
'/push',
validatePathParams('worktreePath'),
requireValidWorktree,
createPushHandler()
);
router.post(
'/pull',
validatePathParams('worktreePath'),
requireValidWorktree,
createPullHandler()
);
router.post('/checkout-branch', requireValidWorktree, createCheckoutBranchHandler());
router.post(
'/list-branches',
validatePathParams('worktreePath'),
requireValidWorktree,
createListBranchesHandler()
);
router.post('/switch-branch', requireValidWorktree, createSwitchBranchHandler());
router.post('/open-in-editor', validatePathParams('worktreePath'), createOpenInEditorHandler());
router.get('/default-editor', createGetDefaultEditorHandler());
router.post('/init-git', validatePathParams('projectPath'), createInitGitHandler());

View File

@@ -0,0 +1,74 @@
/**
* Middleware for worktree route validation
*/
import type { Request, Response, NextFunction } from 'express';
import { isGitRepo, hasCommits } from './common.js';
interface ValidationOptions {
/** Check if the path is a git repository (default: true) */
requireGitRepo?: boolean;
/** Check if the repository has at least one commit (default: true) */
requireCommits?: boolean;
/** The name of the request body field containing the path (default: 'worktreePath') */
pathField?: 'worktreePath' | 'projectPath';
}
/**
* Middleware factory to validate that a path is a valid git repository with commits.
* This reduces code duplication across route handlers.
*
* @param options - Validation options
* @returns Express middleware function
*/
export function requireValidGitRepo(options: ValidationOptions = {}) {
const { requireGitRepo = true, requireCommits = true, pathField = 'worktreePath' } = options;
return async (req: Request, res: Response, next: NextFunction): Promise<void> => {
const repoPath = req.body[pathField] as string | undefined;
if (!repoPath) {
// Let the route handler deal with missing path validation
next();
return;
}
if (requireGitRepo && !(await isGitRepo(repoPath))) {
res.status(400).json({
success: false,
error: 'Not a git repository',
code: 'NOT_GIT_REPO',
});
return;
}
if (requireCommits && !(await hasCommits(repoPath))) {
res.status(400).json({
success: false,
error: 'Repository has no commits yet',
code: 'NO_COMMITS',
});
return;
}
next();
};
}
/**
* Middleware to validate git repo for worktreePath field
*/
export const requireValidWorktree = requireValidGitRepo({ pathField: 'worktreePath' });
/**
* Middleware to validate git repo for projectPath field
*/
export const requireValidProject = requireValidGitRepo({ pathField: 'projectPath' });
/**
* Middleware to validate git repo without requiring commits (for commit route)
*/
export const requireGitRepoOnly = requireValidGitRepo({
pathField: 'worktreePath',
requireCommits: false,
});

View File

@@ -1,11 +1,14 @@
/**
* POST /checkout-branch endpoint - Create and checkout a new branch
*
* Note: Git repository validation (isGitRepo, hasCommits) is handled by
* the requireValidWorktree middleware in index.ts
*/
import type { Request, Response } from 'express';
import { exec } from 'child_process';
import { promisify } from 'util';
import { getErrorMessage, logError, isGitRepo, hasCommits } from '../common.js';
import { getErrorMessage, logError } from '../common.js';
const execAsync = promisify(exec);
@@ -43,26 +46,6 @@ export function createCheckoutBranchHandler() {
return;
}
// Check if path is a git repository
if (!(await isGitRepo(worktreePath))) {
res.status(400).json({
success: false,
error: 'Not a git repository',
code: 'NOT_GIT_REPO',
});
return;
}
// Check if repository has at least one commit
if (!(await hasCommits(worktreePath))) {
res.status(400).json({
success: false,
error: 'Repository has no commits yet',
code: 'NO_COMMITS',
});
return;
}
// Get current branch for reference
const { stdout: currentBranchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', {
cwd: worktreePath,

View File

@@ -1,11 +1,14 @@
/**
* POST /commit endpoint - Commit changes in a worktree
*
* Note: Git repository validation (isGitRepo) is handled by
* the requireGitRepoOnly middleware in index.ts
*/
import type { Request, Response } from 'express';
import { exec } from 'child_process';
import { promisify } from 'util';
import { getErrorMessage, logError, isGitRepo } from '../common.js';
import { getErrorMessage, logError } from '../common.js';
const execAsync = promisify(exec);
@@ -25,16 +28,6 @@ export function createCommitHandler() {
return;
}
// Check if path is a git repository
if (!(await isGitRepo(worktreePath))) {
res.status(400).json({
success: false,
error: 'Not a git repository',
code: 'NOT_GIT_REPO',
});
return;
}
// Check for uncommitted changes
const { stdout: status } = await execAsync('git status --porcelain', {
cwd: worktreePath,

View File

@@ -1,11 +1,14 @@
/**
* POST /list-branches endpoint - List all local branches
*
* Note: Git repository validation (isGitRepo, hasCommits) is handled by
* the requireValidWorktree middleware in index.ts
*/
import type { Request, Response } from 'express';
import { exec } from 'child_process';
import { promisify } from 'util';
import { getErrorMessage, logWorktreeError, isGitRepo, hasCommits } from '../common.js';
import { getErrorMessage, logWorktreeError } from '../common.js';
const execAsync = promisify(exec);
@@ -30,26 +33,6 @@ export function createListBranchesHandler() {
return;
}
// Check if the path is a git repository before running git commands
if (!(await isGitRepo(worktreePath))) {
res.status(400).json({
success: false,
error: 'Not a git repository',
code: 'NOT_GIT_REPO',
});
return;
}
// Check if the repository has any commits (freshly init'd repos have no HEAD)
if (!(await hasCommits(worktreePath))) {
res.status(400).json({
success: false,
error: 'Repository has no commits yet',
code: 'NO_COMMITS',
});
return;
}
// Get current branch
const { stdout: currentBranchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', {
cwd: worktreePath,

View File

@@ -1,12 +1,15 @@
/**
* POST /merge endpoint - Merge feature (merge worktree branch into main)
*
* Note: Git repository validation (isGitRepo, hasCommits) is handled by
* the requireValidProject middleware in index.ts
*/
import type { Request, Response } from 'express';
import { exec } from 'child_process';
import { promisify } from 'util';
import path from 'path';
import { getErrorMessage, logError, isGitRepo, hasCommits } from '../common.js';
import { getErrorMessage, logError } from '../common.js';
const execAsync = promisify(exec);
@@ -27,26 +30,6 @@ export function createMergeHandler() {
return;
}
// Check if path is a git repository
if (!(await isGitRepo(projectPath))) {
res.status(400).json({
success: false,
error: 'Not a git repository',
code: 'NOT_GIT_REPO',
});
return;
}
// Check if repository has at least one commit
if (!(await hasCommits(projectPath))) {
res.status(400).json({
success: false,
error: 'Repository has no commits yet',
code: 'NO_COMMITS',
});
return;
}
const branchName = `feature/${featureId}`;
// Git worktrees are stored in project directory
const worktreePath = path.join(projectPath, '.worktrees', featureId);

View File

@@ -1,11 +1,14 @@
/**
* POST /pull endpoint - Pull latest changes for a worktree/branch
*
* Note: Git repository validation (isGitRepo, hasCommits) is handled by
* the requireValidWorktree middleware in index.ts
*/
import type { Request, Response } from 'express';
import { exec } from 'child_process';
import { promisify } from 'util';
import { getErrorMessage, logError, isGitRepo, hasCommits } from '../common.js';
import { getErrorMessage, logError } from '../common.js';
const execAsync = promisify(exec);
@@ -24,26 +27,6 @@ export function createPullHandler() {
return;
}
// Check if path is a git repository
if (!(await isGitRepo(worktreePath))) {
res.status(400).json({
success: false,
error: 'Not a git repository',
code: 'NOT_GIT_REPO',
});
return;
}
// Check if repository has at least one commit
if (!(await hasCommits(worktreePath))) {
res.status(400).json({
success: false,
error: 'Repository has no commits yet',
code: 'NO_COMMITS',
});
return;
}
// Get current branch name
const { stdout: branchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', {
cwd: worktreePath,

View File

@@ -1,11 +1,14 @@
/**
* POST /push endpoint - Push a worktree branch to remote
*
* Note: Git repository validation (isGitRepo, hasCommits) is handled by
* the requireValidWorktree middleware in index.ts
*/
import type { Request, Response } from 'express';
import { exec } from 'child_process';
import { promisify } from 'util';
import { getErrorMessage, logError, isGitRepo, hasCommits } from '../common.js';
import { getErrorMessage, logError } from '../common.js';
const execAsync = promisify(exec);
@@ -25,26 +28,6 @@ export function createPushHandler() {
return;
}
// Check if path is a git repository
if (!(await isGitRepo(worktreePath))) {
res.status(400).json({
success: false,
error: 'Not a git repository',
code: 'NOT_GIT_REPO',
});
return;
}
// Check if repository has at least one commit
if (!(await hasCommits(worktreePath))) {
res.status(400).json({
success: false,
error: 'Repository has no commits yet',
code: 'NO_COMMITS',
});
return;
}
// Get branch name
const { stdout: branchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', {
cwd: worktreePath,

View File

@@ -4,12 +4,15 @@
* Simple branch switching.
* If there are uncommitted changes, the switch will fail and
* the user should commit first.
*
* Note: Git repository validation (isGitRepo, hasCommits) is handled by
* the requireValidWorktree middleware in index.ts
*/
import type { Request, Response } from 'express';
import { exec } from 'child_process';
import { promisify } from 'util';
import { getErrorMessage, logError, isGitRepo, hasCommits } from '../common.js';
import { getErrorMessage, logError } from '../common.js';
const execAsync = promisify(exec);
@@ -83,26 +86,6 @@ export function createSwitchBranchHandler() {
return;
}
// Check if path is a git repository
if (!(await isGitRepo(worktreePath))) {
res.status(400).json({
success: false,
error: 'Not a git repository',
code: 'NOT_GIT_REPO',
});
return;
}
// Check if repository has at least one commit
if (!(await hasCommits(worktreePath))) {
res.status(400).json({
success: false,
error: 'Repository has no commits yet',
code: 'NO_COMMITS',
});
return;
}
// Get current branch
const { stdout: currentBranchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', {
cwd: worktreePath,