-
Notifications
You must be signed in to change notification settings - Fork 489
pull-request #173
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
pull-request #173
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
b8afb6c
Changes from pull-request
webdevcody 6a8f5c6
feat: enhance Kanban card functionality with Verify button
webdevcody 9bfcb91
Merge branch 'main' into pull-request
webdevcody d4365de
feat: enhance PR handling and UI integration for worktrees
webdevcody 6c25680
Changes from pull-request
webdevcody ec7c289
fix: address PR #173 security and code quality feedback
webdevcody bb5f68c
refactor: improve PR display and interaction in worktree components
webdevcody 3842eb1
cleaning up code
webdevcody ff6a5a5
test: enhance visibility checks in worktree integration tests
webdevcody 19fd23c
test: enhance error handling in fs-utils tests
webdevcody File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,183 @@ | ||
| /** | ||
| * Worktree metadata storage utilities | ||
| * Stores worktree-specific data in .automaker/worktrees/:branch/worktree.json | ||
| */ | ||
|
|
||
| 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; | ||
| title: string; | ||
| state: string; | ||
| createdAt: string; | ||
| } | ||
|
|
||
| export interface WorktreeMetadata { | ||
| branch: string; | ||
| createdAt: string; | ||
| pr?: WorktreePRInfo; | ||
| } | ||
|
|
||
| /** | ||
| * Sanitize branch name for cross-platform filesystem safety | ||
| */ | ||
| function sanitizeBranchName(branch: string): string { | ||
| // Replace characters that are invalid or problematic on various filesystems: | ||
| // - Forward and backslashes (path separators) | ||
| // - Windows invalid chars: : * ? " < > | | ||
| // - Other potentially problematic chars | ||
| let safeBranch = branch | ||
| .replace(/[/\\:*?"<>|]/g, "-") // Replace invalid chars with dash | ||
| .replace(/\s+/g, "_") // Replace spaces with underscores | ||
| .replace(/\.+$/g, "") // Remove trailing dots (Windows issue) | ||
| .replace(/-+/g, "-") // Collapse multiple dashes | ||
| .replace(/^-|-$/g, ""); // Remove leading/trailing dashes | ||
|
|
||
| // Truncate to safe length (leave room for path components) | ||
| 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; | ||
| if (windowsReserved.test(safeBranch) || safeBranch.length === 0) { | ||
| safeBranch = `_${safeBranch || "branch"}`; | ||
| } | ||
|
|
||
| return safeBranch; | ||
| } | ||
|
|
||
| /** | ||
| * Get the path to the worktree metadata directory | ||
| */ | ||
| function getWorktreeMetadataDir(projectPath: string, branch: string): string { | ||
| const safeBranch = sanitizeBranchName(branch); | ||
| return path.join(projectPath, ".automaker", "worktrees", safeBranch); | ||
| } | ||
|
|
||
| /** | ||
| * Get the path to the worktree metadata file | ||
| */ | ||
| function getWorktreeMetadataPath(projectPath: string, branch: string): string { | ||
| return path.join(getWorktreeMetadataDir(projectPath, branch), "worktree.json"); | ||
| } | ||
|
|
||
| /** | ||
| * Read worktree metadata for a branch | ||
| */ | ||
| export async function readWorktreeMetadata( | ||
| projectPath: string, | ||
| branch: string | ||
| ): Promise<WorktreeMetadata | null> { | ||
| try { | ||
| const metadataPath = getWorktreeMetadataPath(projectPath, branch); | ||
| const content = await fs.readFile(metadataPath, "utf-8"); | ||
| return JSON.parse(content) as WorktreeMetadata; | ||
| } catch (error) { | ||
| // File doesn't exist or can't be read | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Write worktree metadata for a branch | ||
| */ | ||
| export async function writeWorktreeMetadata( | ||
| projectPath: string, | ||
| branch: string, | ||
| metadata: WorktreeMetadata | ||
| ): Promise<void> { | ||
| const metadataDir = getWorktreeMetadataDir(projectPath, branch); | ||
| const metadataPath = getWorktreeMetadataPath(projectPath, branch); | ||
|
|
||
| // Ensure directory exists | ||
| await fs.mkdir(metadataDir, { recursive: true }); | ||
|
|
||
| // Write metadata | ||
| await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2), "utf-8"); | ||
| } | ||
|
|
||
| /** | ||
| * Update PR info in worktree metadata | ||
| */ | ||
| export async function updateWorktreePRInfo( | ||
| projectPath: string, | ||
| branch: string, | ||
| prInfo: WorktreePRInfo | ||
| ): Promise<void> { | ||
| // Read existing metadata or create new | ||
| let metadata = await readWorktreeMetadata(projectPath, branch); | ||
|
|
||
| if (!metadata) { | ||
| metadata = { | ||
| branch, | ||
| createdAt: new Date().toISOString(), | ||
| }; | ||
| } | ||
|
|
||
| // Update PR info | ||
| metadata.pr = prInfo; | ||
|
|
||
| // Write back | ||
| await writeWorktreeMetadata(projectPath, branch, metadata); | ||
| } | ||
|
|
||
| /** | ||
| * Get PR info for a branch from metadata | ||
| */ | ||
| export async function getWorktreePRInfo( | ||
| projectPath: string, | ||
| branch: string | ||
| ): Promise<WorktreePRInfo | null> { | ||
| const metadata = await readWorktreeMetadata(projectPath, branch); | ||
| return metadata?.pr || null; | ||
| } | ||
|
|
||
| /** | ||
| * Read all worktree metadata for a project | ||
| */ | ||
| export async function readAllWorktreeMetadata( | ||
| projectPath: string | ||
| ): Promise<Map<string, WorktreeMetadata>> { | ||
| const result = new Map<string, WorktreeMetadata>(); | ||
| const worktreesDir = path.join(projectPath, ".automaker", "worktrees"); | ||
|
|
||
| try { | ||
| const dirs = await fs.readdir(worktreesDir, { withFileTypes: true }); | ||
|
|
||
| for (const dir of dirs) { | ||
| if (dir.isDirectory()) { | ||
| const metadataPath = path.join(worktreesDir, dir.name, "worktree.json"); | ||
| try { | ||
| const content = await fs.readFile(metadataPath, "utf-8"); | ||
| const metadata = JSON.parse(content) as WorktreeMetadata; | ||
| result.set(metadata.branch, metadata); | ||
| } catch { | ||
| // Skip if file doesn't exist or can't be read | ||
| } | ||
| } | ||
| } | ||
| } catch { | ||
| // Directory doesn't exist | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| /** | ||
| * Delete worktree metadata for a branch | ||
| */ | ||
| export async function deleteWorktreeMetadata( | ||
| projectPath: string, | ||
| branch: string | ||
| ): Promise<void> { | ||
| const metadataDir = getWorktreeMetadataDir(projectPath, branch); | ||
| try { | ||
| await fs.rm(metadataDir, { recursive: true, force: true }); | ||
| } catch { | ||
| // Ignore errors if directory doesn't exist | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard against undefined
process.env.HOME.Line 51 uses
${process.env.HOME}/.local/binin a template literal. Ifprocess.env.HOMEis undefined (which can happen in some environments), this produces the string"undefined/.local/bin"which passes the.filter(Boolean)check but is an invalid path.🔎 Proposed fix
} 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 ); + if (process.env.HOME) { + additionalPaths.push(`${process.env.HOME}/.local/bin`); + } }📝 Committable suggestion
🤖 Prompt for AI Agents