mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-05 09:33:07 +00:00
cleaning up code
This commit is contained in:
@@ -6,6 +6,9 @@
|
|||||||
import * as fs from "fs/promises";
|
import * as fs from "fs/promises";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
|
|
||||||
|
/** Maximum length for sanitized branch names in filesystem paths */
|
||||||
|
const MAX_SANITIZED_BRANCH_PATH_LENGTH = 200;
|
||||||
|
|
||||||
export interface WorktreePRInfo {
|
export interface WorktreePRInfo {
|
||||||
number: number;
|
number: number;
|
||||||
url: string;
|
url: string;
|
||||||
@@ -36,7 +39,7 @@ function sanitizeBranchName(branch: string): string {
|
|||||||
.replace(/^-|-$/g, ""); // Remove leading/trailing dashes
|
.replace(/^-|-$/g, ""); // Remove leading/trailing dashes
|
||||||
|
|
||||||
// Truncate to safe length (leave room for path components)
|
// 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)
|
// 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;
|
const windowsReserved = /^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i;
|
||||||
|
|||||||
@@ -14,9 +14,87 @@ import {
|
|||||||
import { FeatureLoader } from "../../services/feature-loader.js";
|
import { FeatureLoader } from "../../services/feature-loader.js";
|
||||||
|
|
||||||
const logger = createLogger("Worktree");
|
const logger = createLogger("Worktree");
|
||||||
const execAsync = promisify(exec);
|
export const execAsync = promisify(exec);
|
||||||
const featureLoader = new FeatureLoader();
|
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 =
|
export const AUTOMAKER_INITIAL_COMMIT_MESSAGE =
|
||||||
"chore: automaker initial commit";
|
"chore: automaker initial commit";
|
||||||
|
|
||||||
|
|||||||
@@ -3,67 +3,16 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from "express";
|
||||||
import { exec } from "child_process";
|
import {
|
||||||
import { promisify } from "util";
|
getErrorMessage,
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
logError,
|
||||||
|
execAsync,
|
||||||
|
execEnv,
|
||||||
|
isValidBranchName,
|
||||||
|
isGhCliAvailable,
|
||||||
|
} from "../common.js";
|
||||||
import { updateWorktreePRInfo } from "../../../lib/worktree-metadata.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() {
|
export function createCreatePRHandler() {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
@@ -244,15 +193,7 @@ export function createCreatePRHandler() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if gh CLI is available (cross-platform)
|
// Check if gh CLI is available (cross-platform)
|
||||||
try {
|
ghCliAvailable = await isGhCliAvailable();
|
||||||
const checkCommand = process.platform === "win32"
|
|
||||||
? "where gh"
|
|
||||||
: "command -v gh";
|
|
||||||
await execAsync(checkCommand, { env: execEnv });
|
|
||||||
ghCliAvailable = true;
|
|
||||||
} catch {
|
|
||||||
ghCliAvailable = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct browser URL for PR creation
|
// Construct browser URL for PR creation
|
||||||
if (repoUrl) {
|
if (repoUrl) {
|
||||||
|
|||||||
@@ -3,53 +3,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from "express";
|
||||||
import { exec } from "child_process";
|
import {
|
||||||
import { promisify } from "util";
|
getErrorMessage,
|
||||||
import { getErrorMessage, logError } from "../common.js";
|
logError,
|
||||||
|
execAsync,
|
||||||
const execAsync = promisify(exec);
|
execEnv,
|
||||||
|
isValidBranchName,
|
||||||
// Extended PATH to include common tool installation locations
|
isGhCliAvailable,
|
||||||
const pathSeparator = process.platform === "win32" ? ";" : ":";
|
} from "../common.js";
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PRComment {
|
export interface PRComment {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -98,16 +59,7 @@ export function createPRInfoHandler() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if gh CLI is available
|
// Check if gh CLI is available
|
||||||
let ghCliAvailable = false;
|
const ghCliAvailable = await isGhCliAvailable();
|
||||||
try {
|
|
||||||
const checkCommand = process.platform === "win32"
|
|
||||||
? "where gh"
|
|
||||||
: "command -v gh";
|
|
||||||
await execAsync(checkCommand, { env: execEnv });
|
|
||||||
ghCliAvailable = true;
|
|
||||||
} catch {
|
|
||||||
ghCliAvailable = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ghCliAvailable) {
|
if (!ghCliAvailable) {
|
||||||
res.json({
|
res.json({
|
||||||
|
|||||||
@@ -58,6 +58,9 @@ const EMPTY_WORKTREES: ReturnType<
|
|||||||
ReturnType<typeof useAppStore.getState>["getWorktrees"]
|
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() {
|
export function BoardView() {
|
||||||
const {
|
const {
|
||||||
currentProject,
|
currentProject,
|
||||||
@@ -458,7 +461,7 @@ export function BoardView() {
|
|||||||
if (newFeature) {
|
if (newFeature) {
|
||||||
await handleStartImplementation(newFeature);
|
await handleStartImplementation(newFeature);
|
||||||
}
|
}
|
||||||
}, 500);
|
}, FEATURE_CREATION_SETTLE_DELAY_MS);
|
||||||
},
|
},
|
||||||
[handleAddFeature, handleStartImplementation, defaultSkipTests]
|
[handleAddFeature, handleStartImplementation, defaultSkipTests]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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}` : ""}`;
|
const prLabel = `Pull Request #${worktree.pr.number}, ${prState}${worktree.pr.title ? `: ${worktree.pr.title}` : ""}`;
|
||||||
|
|
||||||
// Helper to get status icon color for the selected state
|
// Helper to get status icon color for the selected state
|
||||||
|
|||||||
Reference in New Issue
Block a user