Implement branch selection and worktree management features

- Added a new BranchAutocomplete component for selecting branches in feature dialogs.
- Enhanced BoardView to fetch and display branch suggestions.
- Updated CreateWorktreeDialog and EditFeatureDialog to include branch selection.
- Modified worktree management to ensure proper handling of branch-specific worktrees.
- Refactored related components and hooks to support the new branch management functionality.
- Removed unused revert and merge handlers from Kanban components for cleaner code.
This commit is contained in:
Cody Seibert
2025-12-16 12:12:10 -05:00
parent 54a102f029
commit a3c9c9cee5
52 changed files with 2969 additions and 588 deletions

View File

@@ -6,6 +6,7 @@ import type { Request, Response } from "express";
import fs from "fs/promises";
import path from "path";
import { getErrorMessage, logError } from "../common.js";
import { getBoardDir } from "../../../lib/automaker-paths.js";
export function createDeleteBoardBackgroundHandler() {
return async (req: Request, res: Response): Promise<void> => {
@@ -20,10 +21,11 @@ export function createDeleteBoardBackgroundHandler() {
return;
}
const boardDir = path.join(projectPath, ".automaker", "board");
// Get external board directory
const boardDir = await getBoardDir(projectPath);
try {
// Try to remove all files in the board directory
// Try to remove all background files in the board directory
const files = await fs.readdir(boardDir);
for (const file of files) {
if (file.startsWith("background")) {

View File

@@ -1,5 +1,6 @@
/**
* POST /mkdir endpoint - Create directory
* Handles symlinks safely to avoid ELOOP errors
*/
import type { Request, Response } from "express";
@@ -20,13 +21,46 @@ export function createMkdirHandler() {
const resolvedPath = path.resolve(dirPath);
// Check if path already exists using lstat (doesn't follow symlinks)
try {
const stats = await fs.lstat(resolvedPath);
// Path exists - if it's a directory or symlink, consider it success
if (stats.isDirectory() || stats.isSymbolicLink()) {
addAllowedPath(resolvedPath);
res.json({ success: true });
return;
}
// It's a file - can't create directory
res.status(400).json({
success: false,
error: "Path exists and is not a directory",
});
return;
} catch (statError: any) {
// ENOENT means path doesn't exist - we should create it
if (statError.code !== "ENOENT") {
// Some other error (could be ELOOP in parent path)
throw statError;
}
}
// Path doesn't exist, create it
await fs.mkdir(resolvedPath, { recursive: true });
// Add the new directory to allowed paths for tracking
addAllowedPath(resolvedPath);
res.json({ success: true });
} catch (error) {
} catch (error: any) {
// Handle ELOOP specifically
if (error.code === "ELOOP") {
logError(error, "Create directory failed - symlink loop detected");
res.status(400).json({
success: false,
error: "Cannot create directory: symlink loop detected in path",
});
return;
}
logError(error, "Create directory failed");
res.status(500).json({ success: false, error: getErrorMessage(error) });
}

View File

@@ -7,6 +7,7 @@ import fs from "fs/promises";
import path from "path";
import { addAllowedPath } from "../../../lib/security.js";
import { getErrorMessage, logError } from "../common.js";
import { getBoardDir } from "../../../lib/automaker-paths.js";
export function createSaveBoardBackgroundHandler() {
return async (req: Request, res: Response): Promise<void> => {
@@ -26,8 +27,8 @@ export function createSaveBoardBackgroundHandler() {
return;
}
// Create .automaker/board directory if it doesn't exist
const boardDir = path.join(projectPath, ".automaker", "board");
// Get external board directory
const boardDir = await getBoardDir(projectPath);
await fs.mkdir(boardDir, { recursive: true });
// Decode base64 data (remove data URL prefix if present)
@@ -42,12 +43,11 @@ export function createSaveBoardBackgroundHandler() {
// Write file
await fs.writeFile(filePath, buffer);
// Add project path to allowed paths if not already
addAllowedPath(projectPath);
// Add board directory to allowed paths
addAllowedPath(boardDir);
// Return the relative path for storage
const relativePath = `.automaker/board/${uniqueFilename}`;
res.json({ success: true, path: relativePath });
// Return the absolute path
res.json({ success: true, path: filePath });
} catch (error) {
logError(error, "Save board background failed");
res.status(500).json({ success: false, error: getErrorMessage(error) });

View File

@@ -1,5 +1,5 @@
/**
* POST /save-image endpoint - Save image to .automaker/images directory
* POST /save-image endpoint - Save image to external automaker images directory
*/
import type { Request, Response } from "express";
@@ -7,6 +7,7 @@ import fs from "fs/promises";
import path from "path";
import { addAllowedPath } from "../../../lib/security.js";
import { getErrorMessage, logError } from "../common.js";
import { getImagesDir } from "../../../lib/automaker-paths.js";
export function createSaveImageHandler() {
return async (req: Request, res: Response): Promise<void> => {
@@ -26,8 +27,8 @@ export function createSaveImageHandler() {
return;
}
// Create .automaker/images directory if it doesn't exist
const imagesDir = path.join(projectPath, ".automaker", "images");
// Get external images directory
const imagesDir = await getImagesDir(projectPath);
await fs.mkdir(imagesDir, { recursive: true });
// Decode base64 data (remove data URL prefix if present)
@@ -44,9 +45,10 @@ export function createSaveImageHandler() {
// Write file
await fs.writeFile(filePath, buffer);
// Add project path to allowed paths if not already
addAllowedPath(projectPath);
// Add automaker directory to allowed paths
addAllowedPath(imagesDir);
// Return the absolute path
res.json({ success: true, path: filePath });
} catch (error) {
logError(error, "Save image failed");

View File

@@ -7,6 +7,7 @@ import fs from "fs/promises";
import path from "path";
import { validatePath } from "../../../lib/security.js";
import { getErrorMessage, logError } from "../common.js";
import { mkdirSafe } from "../../../lib/fs-utils.js";
export function createWriteHandler() {
return async (req: Request, res: Response): Promise<void> => {
@@ -23,8 +24,8 @@ export function createWriteHandler() {
const resolvedPath = validatePath(filePath);
// Ensure parent directory exists
await fs.mkdir(path.dirname(resolvedPath), { recursive: true });
// Ensure parent directory exists (symlink-safe)
await mkdirSafe(path.dirname(resolvedPath));
await fs.writeFile(resolvedPath, content, "utf-8");
res.json({ success: true });