cleaning up code

This commit is contained in:
Cody Seibert
2025-12-19 20:55:43 -05:00
parent bb5f68c2f0
commit 3842eb1328
6 changed files with 105 additions and 129 deletions

View File

@@ -6,6 +6,9 @@
import * as fs from "fs/promises";
import * as path from "path";
/** Maximum length for sanitized branch names in filesystem paths */
const MAX_SANITIZED_BRANCH_PATH_LENGTH = 200;
export interface WorktreePRInfo {
number: number;
url: string;
@@ -36,7 +39,7 @@ function sanitizeBranchName(branch: string): string {
.replace(/^-|-$/g, ""); // Remove leading/trailing dashes
// Truncate to safe length (leave room for path components)
safeBranch = safeBranch.substring(0, 200);
safeBranch = safeBranch.substring(0, MAX_SANITIZED_BRANCH_PATH_LENGTH);
// Handle Windows reserved names (CON, PRN, AUX, NUL, COM1-9, LPT1-9)
const windowsReserved = /^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i;

View File

@@ -14,9 +14,87 @@ import {
import { FeatureLoader } from "../../services/feature-loader.js";
const logger = createLogger("Worktree");
const execAsync = promisify(exec);
export const execAsync = promisify(exec);
const featureLoader = new FeatureLoader();
// ============================================================================
// Constants
// ============================================================================
/** Maximum allowed length for git branch names */
export const MAX_BRANCH_NAME_LENGTH = 250;
// ============================================================================
// Extended PATH configuration for Electron apps
// ============================================================================
const pathSeparator = process.platform === "win32" ? ";" : ":";
const additionalPaths: string[] = [];
if (process.platform === "win32") {
// Windows paths
if (process.env.LOCALAPPDATA) {
additionalPaths.push(`${process.env.LOCALAPPDATA}\\Programs\\Git\\cmd`);
}
if (process.env.PROGRAMFILES) {
additionalPaths.push(`${process.env.PROGRAMFILES}\\Git\\cmd`);
}
if (process.env["ProgramFiles(x86)"]) {
additionalPaths.push(`${process.env["ProgramFiles(x86)"]}\\Git\\cmd`);
}
} else {
// Unix/Mac paths
additionalPaths.push(
"/opt/homebrew/bin", // Homebrew on Apple Silicon
"/usr/local/bin", // Homebrew on Intel Mac, common Linux location
"/home/linuxbrew/.linuxbrew/bin", // Linuxbrew
`${process.env.HOME}/.local/bin`, // pipx, other user installs
);
}
const extendedPath = [
process.env.PATH,
...additionalPaths.filter(Boolean),
].filter(Boolean).join(pathSeparator);
/**
* Environment variables with extended PATH for executing shell commands.
* Electron apps don't inherit the user's shell PATH, so we need to add
* common tool installation locations.
*/
export const execEnv = {
...process.env,
PATH: extendedPath,
};
// ============================================================================
// Validation utilities
// ============================================================================
/**
* Validate branch name to prevent command injection.
* Git branch names cannot contain: space, ~, ^, :, ?, *, [, \, or control chars.
* We also reject shell metacharacters for safety.
*/
export function isValidBranchName(name: string): boolean {
return /^[a-zA-Z0-9._\-/]+$/.test(name) && name.length < MAX_BRANCH_NAME_LENGTH;
}
/**
* Check if gh CLI is available on the system
*/
export async function isGhCliAvailable(): Promise<boolean> {
try {
const checkCommand = process.platform === "win32"
? "where gh"
: "command -v gh";
await execAsync(checkCommand, { env: execEnv });
return true;
} catch {
return false;
}
}
export const AUTOMAKER_INITIAL_COMMIT_MESSAGE =
"chore: automaker initial commit";

View File

@@ -3,67 +3,16 @@
*/
import type { Request, Response } from "express";
import { exec } from "child_process";
import { promisify } from "util";
import { getErrorMessage, logError } from "../common.js";
import {
getErrorMessage,
logError,
execAsync,
execEnv,
isValidBranchName,
isGhCliAvailable,
} from "../common.js";
import { updateWorktreePRInfo } from "../../../lib/worktree-metadata.js";
// Shell escaping utility to prevent command injection
function shellEscape(arg: string): string {
if (process.platform === "win32") {
// Windows CMD shell escaping
return `"${arg.replace(/"/g, '""')}"`;
} else {
// Unix shell escaping
return `'${arg.replace(/'/g, "'\\''")}'`;
}
}
// Validate branch name to prevent command injection
function isValidBranchName(name: string): boolean {
// Git branch names cannot contain: space, ~, ^, :, ?, *, [, \, or control chars
// Also reject shell metacharacters for safety
return /^[a-zA-Z0-9._\-/]+$/.test(name) && name.length < 250;
}
const execAsync = promisify(exec);
// Extended PATH to include common tool installation locations
// This is needed because Electron apps don't inherit the user's shell PATH
const pathSeparator = process.platform === "win32" ? ";" : ":";
const additionalPaths: string[] = [];
if (process.platform === "win32") {
// Windows paths
if (process.env.LOCALAPPDATA) {
additionalPaths.push(`${process.env.LOCALAPPDATA}\\Programs\\Git\\cmd`);
}
if (process.env.PROGRAMFILES) {
additionalPaths.push(`${process.env.PROGRAMFILES}\\Git\\cmd`);
}
if (process.env["ProgramFiles(x86)"]) {
additionalPaths.push(`${process.env["ProgramFiles(x86)"]}\\Git\\cmd`);
}
} else {
// Unix/Mac paths
additionalPaths.push(
"/opt/homebrew/bin", // Homebrew on Apple Silicon
"/usr/local/bin", // Homebrew on Intel Mac, common Linux location
"/home/linuxbrew/.linuxbrew/bin", // Linuxbrew
`${process.env.HOME}/.local/bin`, // pipx, other user installs
);
}
const extendedPath = [
process.env.PATH,
...additionalPaths.filter(Boolean),
].filter(Boolean).join(pathSeparator);
const execEnv = {
...process.env,
PATH: extendedPath,
};
export function createCreatePRHandler() {
return async (req: Request, res: Response): Promise<void> => {
try {
@@ -244,15 +193,7 @@ export function createCreatePRHandler() {
}
// Check if gh CLI is available (cross-platform)
try {
const checkCommand = process.platform === "win32"
? "where gh"
: "command -v gh";
await execAsync(checkCommand, { env: execEnv });
ghCliAvailable = true;
} catch {
ghCliAvailable = false;
}
ghCliAvailable = await isGhCliAvailable();
// Construct browser URL for PR creation
if (repoUrl) {

View File

@@ -3,53 +3,14 @@
*/
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);
// Extended PATH to include common tool installation locations
const pathSeparator = process.platform === "win32" ? ";" : ":";
const additionalPaths: string[] = [];
if (process.platform === "win32") {
if (process.env.LOCALAPPDATA) {
additionalPaths.push(`${process.env.LOCALAPPDATA}\\Programs\\Git\\cmd`);
}
if (process.env.PROGRAMFILES) {
additionalPaths.push(`${process.env.PROGRAMFILES}\\Git\\cmd`);
}
if (process.env["ProgramFiles(x86)"]) {
additionalPaths.push(`${process.env["ProgramFiles(x86)"]}\\Git\\cmd`);
}
} else {
additionalPaths.push(
"/opt/homebrew/bin",
"/usr/local/bin",
"/home/linuxbrew/.linuxbrew/bin",
`${process.env.HOME}/.local/bin`,
);
}
const extendedPath = [
process.env.PATH,
...additionalPaths.filter(Boolean),
].filter(Boolean).join(pathSeparator);
const execEnv = {
...process.env,
PATH: extendedPath,
};
/**
* Validate branch name to prevent command injection.
* Git branch names cannot contain: space, ~, ^, :, ?, *, [, \, or control chars.
* We also reject shell metacharacters for safety.
*/
function isValidBranchName(name: string): boolean {
return /^[a-zA-Z0-9._\-/]+$/.test(name) && name.length < 250;
}
import {
getErrorMessage,
logError,
execAsync,
execEnv,
isValidBranchName,
isGhCliAvailable,
} from "../common.js";
export interface PRComment {
id: number;
@@ -98,16 +59,7 @@ export function createPRInfoHandler() {
}
// Check if gh CLI is available
let ghCliAvailable = false;
try {
const checkCommand = process.platform === "win32"
? "where gh"
: "command -v gh";
await execAsync(checkCommand, { env: execEnv });
ghCliAvailable = true;
} catch {
ghCliAvailable = false;
}
const ghCliAvailable = await isGhCliAvailable();
if (!ghCliAvailable) {
res.json({

View File

@@ -58,6 +58,9 @@ const EMPTY_WORKTREES: ReturnType<
ReturnType<typeof useAppStore.getState>["getWorktrees"]
> = [];
/** Delay before starting a newly created feature to allow state to settle */
const FEATURE_CREATION_SETTLE_DELAY_MS = 500;
export function BoardView() {
const {
currentProject,
@@ -458,7 +461,7 @@ export function BoardView() {
if (newFeature) {
await handleStartImplementation(newFeature);
}
}, 500);
}, FEATURE_CREATION_SETTLE_DELAY_MS);
},
[handleAddFeature, handleStartImplementation, defaultSkipTests]
);

View File

@@ -130,7 +130,6 @@ export function WorktreeTab({
}
})();
const prTitle = worktree.pr.title || `Pull Request #${worktree.pr.number}`;
const prLabel = `Pull Request #${worktree.pr.number}, ${prState}${worktree.pr.title ? `: ${worktree.pr.title}` : ""}`;
// Helper to get status icon color for the selected state