mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-04 09:13:08 +00:00
Merge branch 'feature/worktrees' of github.com:AutoMaker-Org/automaker into feature/worktrees
This commit is contained in:
@@ -53,6 +53,16 @@ export function CreatePRDialog({
|
|||||||
// Reset state when dialog opens or worktree changes
|
// Reset state when dialog opens or worktree changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) {
|
if (open) {
|
||||||
|
// Only reset form fields, not the result states (prUrl, browserUrl, showBrowserFallback)
|
||||||
|
// These are set by the API response and should persist until dialog closes
|
||||||
|
setTitle("");
|
||||||
|
setBody("");
|
||||||
|
setCommitMessage("");
|
||||||
|
setBaseBranch("main");
|
||||||
|
setIsDraft(false);
|
||||||
|
setError(null);
|
||||||
|
} else {
|
||||||
|
// Reset everything when dialog closes
|
||||||
setTitle("");
|
setTitle("");
|
||||||
setBody("");
|
setBody("");
|
||||||
setCommitMessage("");
|
setCommitMessage("");
|
||||||
@@ -98,21 +108,22 @@ export function CreatePRDialog({
|
|||||||
onCreated();
|
onCreated();
|
||||||
} else {
|
} else {
|
||||||
// Branch was pushed successfully
|
// Branch was pushed successfully
|
||||||
toast.success("Branch pushed", {
|
const prError = result.result.prError;
|
||||||
description: result.result.committed
|
const hasBrowserUrl = !!result.result.browserUrl;
|
||||||
? `Commit ${result.result.commitHash} pushed to ${result.result.branch}`
|
|
||||||
: `Branch ${result.result.branch} pushed`,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check if we should show browser fallback
|
// Check if we should show browser fallback
|
||||||
if (!result.result.prCreated && result.result.browserUrl) {
|
if (!result.result.prCreated && hasBrowserUrl) {
|
||||||
const prError = result.result.prError;
|
|
||||||
|
|
||||||
// If gh CLI is not available, show browser fallback UI
|
// If gh CLI is not available, show browser fallback UI
|
||||||
if (prError === "gh_cli_not_available" || !result.result.ghCliAvailable) {
|
if (prError === "gh_cli_not_available" || !result.result.ghCliAvailable) {
|
||||||
setBrowserUrl(result.result.browserUrl);
|
setBrowserUrl(result.result.browserUrl);
|
||||||
setShowBrowserFallback(true);
|
setShowBrowserFallback(true);
|
||||||
onCreated();
|
toast.success("Branch pushed", {
|
||||||
|
description: result.result.committed
|
||||||
|
? `Commit ${result.result.commitHash} pushed to ${result.result.branch}`
|
||||||
|
: `Branch ${result.result.branch} pushed`,
|
||||||
|
});
|
||||||
|
// Don't call onCreated() here - we want to keep the dialog open to show the browser URL
|
||||||
|
setIsLoading(false);
|
||||||
return; // Don't close dialog, show browser fallback UI
|
return; // Don't close dialog, show browser fallback UI
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,16 +146,27 @@ export function CreatePRDialog({
|
|||||||
description: errorMessage,
|
description: errorMessage,
|
||||||
duration: 8000,
|
duration: 8000,
|
||||||
});
|
});
|
||||||
onCreated();
|
// Don't call onCreated() here - we want to keep the dialog open to show the browser URL
|
||||||
|
setIsLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show success toast for push
|
||||||
|
toast.success("Branch pushed", {
|
||||||
|
description: result.result.committed
|
||||||
|
? `Commit ${result.result.commitHash} pushed to ${result.result.branch}`
|
||||||
|
: `Branch ${result.result.branch} pushed`,
|
||||||
|
});
|
||||||
|
|
||||||
// No browser URL available, just close
|
// No browser URL available, just close
|
||||||
if (!result.result.prCreated) {
|
if (!result.result.prCreated) {
|
||||||
toast.info("PR not created", {
|
if (!hasBrowserUrl) {
|
||||||
description: "GitHub CLI (gh) may not be installed or authenticated",
|
toast.info("PR not created", {
|
||||||
});
|
description: "Could not determine repository URL. GitHub CLI (gh) may not be installed or authenticated.",
|
||||||
|
duration: 8000,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
onCreated();
|
onCreated();
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
@@ -177,6 +199,8 @@ export function CreatePRDialog({
|
|||||||
|
|
||||||
if (!worktree) return null;
|
if (!worktree) return null;
|
||||||
|
|
||||||
|
const shouldShowBrowserFallback = showBrowserFallback && browserUrl;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={handleClose}>
|
<Dialog open={open} onOpenChange={handleClose}>
|
||||||
<DialogContent className="sm:max-w-[550px]">
|
<DialogContent className="sm:max-w-[550px]">
|
||||||
@@ -212,7 +236,7 @@ export function CreatePRDialog({
|
|||||||
View Pull Request
|
View Pull Request
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
) : showBrowserFallback && browserUrl ? (
|
) : shouldShowBrowserFallback ? (
|
||||||
<div className="py-6 text-center space-y-4">
|
<div className="py-6 text-center space-y-4">
|
||||||
<div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-blue-500/10">
|
<div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-blue-500/10">
|
||||||
<GitPullRequest className="w-8 h-8 text-blue-500" />
|
<GitPullRequest className="w-8 h-8 text-blue-500" />
|
||||||
@@ -225,17 +249,30 @@ export function CreatePRDialog({
|
|||||||
Click below to create a pull request in your browser.
|
Click below to create a pull request in your browser.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-3">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => window.open(browserUrl, "_blank")}
|
onClick={() => {
|
||||||
|
if (browserUrl) {
|
||||||
|
window.open(browserUrl, "_blank");
|
||||||
|
}
|
||||||
|
}}
|
||||||
className="gap-2 w-full"
|
className="gap-2 w-full"
|
||||||
|
size="lg"
|
||||||
>
|
>
|
||||||
<ExternalLink className="w-4 h-4" />
|
<ExternalLink className="w-4 h-4" />
|
||||||
Create PR in Browser
|
Create PR in Browser
|
||||||
</Button>
|
</Button>
|
||||||
|
<div className="p-2 bg-muted rounded text-xs break-all font-mono">
|
||||||
|
{browserUrl}
|
||||||
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Tip: Install the GitHub CLI (<code className="bg-muted px-1 rounded">gh</code>) to create PRs directly from the app
|
Tip: Install the GitHub CLI (<code className="bg-muted px-1 rounded">gh</code>) to create PRs directly from the app
|
||||||
</p>
|
</p>
|
||||||
|
<DialogFooter className="mt-4">
|
||||||
|
<Button variant="outline" onClick={handleClose}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -11,13 +11,34 @@ const execAsync = promisify(exec);
|
|||||||
|
|
||||||
// Extended PATH to include common tool installation locations
|
// Extended PATH to include common tool installation locations
|
||||||
// This is needed because Electron apps don't inherit the user's shell PATH
|
// 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 = [
|
const extendedPath = [
|
||||||
process.env.PATH,
|
process.env.PATH,
|
||||||
"/opt/homebrew/bin", // Homebrew on Apple Silicon
|
...additionalPaths.filter(Boolean),
|
||||||
"/usr/local/bin", // Homebrew on Intel Mac, common Linux location
|
].filter(Boolean).join(pathSeparator);
|
||||||
"/home/linuxbrew/.linuxbrew/bin", // Linuxbrew
|
|
||||||
`${process.env.HOME}/.local/bin`, // pipx, other user installs
|
|
||||||
].filter(Boolean).join(":");
|
|
||||||
|
|
||||||
const execEnv = {
|
const execEnv = {
|
||||||
...process.env,
|
...process.env,
|
||||||
@@ -122,9 +143,12 @@ export function createCreatePRHandler() {
|
|||||||
let browserUrl: string | null = null;
|
let browserUrl: string | null = null;
|
||||||
let ghCliAvailable = false;
|
let ghCliAvailable = false;
|
||||||
|
|
||||||
// Check if gh CLI is available
|
// Check if gh CLI is available (cross-platform)
|
||||||
try {
|
try {
|
||||||
await execAsync("command -v gh", { env: execEnv });
|
const checkCommand = process.platform === "win32"
|
||||||
|
? "where gh"
|
||||||
|
: "command -v gh";
|
||||||
|
await execAsync(checkCommand, { env: execEnv });
|
||||||
ghCliAvailable = true;
|
ghCliAvailable = true;
|
||||||
} catch {
|
} catch {
|
||||||
ghCliAvailable = false;
|
ghCliAvailable = false;
|
||||||
@@ -141,9 +165,22 @@ export function createCreatePRHandler() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Parse remotes to detect fork workflow and get repo URL
|
// Parse remotes to detect fork workflow and get repo URL
|
||||||
const lines = remotes.split("\n");
|
const lines = remotes.split(/\r?\n/); // Handle both Unix and Windows line endings
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const match = line.match(/^(\w+)\s+.*[:/]([^/]+)\/([^/\s]+?)(?:\.git)?\s+\(fetch\)/);
|
// Try multiple patterns to match different remote URL formats
|
||||||
|
// Pattern 1: git@github.com:owner/repo.git (fetch)
|
||||||
|
// Pattern 2: https://github.com/owner/repo.git (fetch)
|
||||||
|
// Pattern 3: https://github.com/owner/repo (fetch)
|
||||||
|
let match = line.match(/^(\w+)\s+.*[:/]([^/]+)\/([^/\s]+?)(?:\.git)?\s+\(fetch\)/);
|
||||||
|
if (!match) {
|
||||||
|
// Try SSH format: git@github.com:owner/repo.git
|
||||||
|
match = line.match(/^(\w+)\s+git@[^:]+:([^/]+)\/([^\s]+?)(?:\.git)?\s+\(fetch\)/);
|
||||||
|
}
|
||||||
|
if (!match) {
|
||||||
|
// Try HTTPS format: https://github.com/owner/repo.git
|
||||||
|
match = line.match(/^(\w+)\s+https?:\/\/[^/]+\/([^/]+)\/([^\s]+?)(?:\.git)?\s+\(fetch\)/);
|
||||||
|
}
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
const [, remoteName, owner, repo] = match;
|
const [, remoteName, owner, repo] = match;
|
||||||
if (remoteName === "upstream") {
|
if (remoteName === "upstream") {
|
||||||
@@ -157,8 +194,30 @@ export function createCreatePRHandler() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (error) {
|
||||||
// Couldn't parse remotes
|
// Couldn't parse remotes - will try fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: Try to get repo URL from git config if remote parsing failed
|
||||||
|
if (!repoUrl) {
|
||||||
|
try {
|
||||||
|
const { stdout: originUrl } = await execAsync("git config --get remote.origin.url", {
|
||||||
|
cwd: worktreePath,
|
||||||
|
env: execEnv,
|
||||||
|
});
|
||||||
|
const url = originUrl.trim();
|
||||||
|
|
||||||
|
// Parse URL to extract owner/repo
|
||||||
|
// Handle both SSH (git@github.com:owner/repo.git) and HTTPS (https://github.com/owner/repo.git)
|
||||||
|
let match = url.match(/[:/]([^/]+)\/([^/\s]+?)(?:\.git)?$/);
|
||||||
|
if (match) {
|
||||||
|
const [, owner, repo] = match;
|
||||||
|
originOwner = owner;
|
||||||
|
repoUrl = `https://github.com/${owner}/${repo}`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Failed to get repo URL from config
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct browser URL for PR creation
|
// Construct browser URL for PR creation
|
||||||
@@ -192,7 +251,6 @@ export function createCreatePRHandler() {
|
|||||||
prCmd += ` --title "${title.replace(/"/g, '\\"')}" --body "${body.replace(/"/g, '\\"')}" ${draftFlag}`;
|
prCmd += ` --title "${title.replace(/"/g, '\\"')}" --body "${body.replace(/"/g, '\\"')}" ${draftFlag}`;
|
||||||
prCmd = prCmd.trim();
|
prCmd = prCmd.trim();
|
||||||
|
|
||||||
console.log("[CreatePR] Running:", prCmd);
|
|
||||||
const { stdout: prOutput } = await execAsync(prCmd, {
|
const { stdout: prOutput } = await execAsync(prCmd, {
|
||||||
cwd: worktreePath,
|
cwd: worktreePath,
|
||||||
env: execEnv,
|
env: execEnv,
|
||||||
@@ -202,11 +260,9 @@ export function createCreatePRHandler() {
|
|||||||
// gh CLI failed
|
// gh CLI failed
|
||||||
const err = ghError as { stderr?: string; message?: string };
|
const err = ghError as { stderr?: string; message?: string };
|
||||||
prError = err.stderr || err.message || "PR creation failed";
|
prError = err.stderr || err.message || "PR creation failed";
|
||||||
console.warn("[CreatePR] gh CLI error:", prError);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
prError = "gh_cli_not_available";
|
prError = "gh_cli_not_available";
|
||||||
console.log("[CreatePR] gh CLI not available, returning browser URL");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return result with browser fallback URL
|
// Return result with browser fallback URL
|
||||||
|
|||||||
Reference in New Issue
Block a user