mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 08:53:36 +00:00
feat: add GitHub setup step and enhance setup flow
- Introduced a new GitHubSetupStep component for GitHub CLI configuration during the setup process. - Updated SetupView to include the GitHub step in the setup flow, allowing users to skip or proceed based on their GitHub CLI status. - Enhanced state management to track GitHub CLI installation and authentication status. - Added logging for transitions between setup steps to improve user feedback. - Updated related files to ensure cross-platform path normalization and compatibility.
This commit is contained in:
@@ -34,7 +34,7 @@ import {
|
||||
} from "lucide-react";
|
||||
import { useAppStore } from "@/store/app-store";
|
||||
import { getElectronAPI } from "@/lib/electron";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cn, pathsEqual, normalizePath } from "@/lib/utils";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface WorktreeInfo {
|
||||
@@ -225,10 +225,10 @@ export function WorktreeSelector({
|
||||
const result = await api.worktree.startDevServer(projectPath, targetPath);
|
||||
|
||||
if (result.success && result.result) {
|
||||
// Update running servers map
|
||||
// Update running servers map (normalize path for cross-platform compatibility)
|
||||
setRunningDevServers((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(targetPath, {
|
||||
next.set(normalizePath(targetPath), {
|
||||
worktreePath: result.result!.worktreePath,
|
||||
port: result.result!.port,
|
||||
url: result.result!.url,
|
||||
@@ -260,10 +260,10 @@ export function WorktreeSelector({
|
||||
const result = await api.worktree.stopDevServer(targetPath);
|
||||
|
||||
if (result.success) {
|
||||
// Update running servers map
|
||||
// Update running servers map (normalize path for cross-platform compatibility)
|
||||
setRunningDevServers((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.delete(targetPath);
|
||||
next.delete(normalizePath(targetPath));
|
||||
return next;
|
||||
});
|
||||
toast.success(result.result?.message || "Dev server stopped");
|
||||
@@ -285,8 +285,10 @@ export function WorktreeSelector({
|
||||
};
|
||||
|
||||
// Helper to get the path key for a worktree (for looking up in runningDevServers)
|
||||
// Normalizes path for cross-platform compatibility
|
||||
const getWorktreeKey = (worktree: WorktreeInfo) => {
|
||||
return worktree.isMain ? projectPath : worktree.path;
|
||||
const path = worktree.isMain ? projectPath : worktree.path;
|
||||
return path ? normalizePath(path) : path;
|
||||
};
|
||||
|
||||
// Helper to check if a worktree has running features
|
||||
@@ -301,12 +303,13 @@ export function WorktreeSelector({
|
||||
if (!feature) return false;
|
||||
|
||||
// For main worktree, check features with no worktreePath or matching projectPath
|
||||
// Use pathsEqual for cross-platform compatibility (Windows uses backslashes)
|
||||
if (worktree.isMain) {
|
||||
return !feature.worktreePath || feature.worktreePath === projectPath;
|
||||
return !feature.worktreePath || pathsEqual(feature.worktreePath, projectPath);
|
||||
}
|
||||
|
||||
// For other worktrees, check if worktreePath matches
|
||||
return feature.worktreePath === worktreeKey;
|
||||
return pathsEqual(feature.worktreePath, worktreeKey);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -459,7 +462,7 @@ export function WorktreeSelector({
|
||||
// currentWorktree.path is null for main, or the worktree path for others
|
||||
const currentWorktreePath = currentWorktree?.path ?? null;
|
||||
const selectedWorktree = currentWorktreePath
|
||||
? worktrees.find((w) => w.path === currentWorktreePath)
|
||||
? worktrees.find((w) => pathsEqual(w.path, currentWorktreePath))
|
||||
: worktrees.find((w) => w.isMain);
|
||||
|
||||
|
||||
@@ -469,7 +472,7 @@ export function WorktreeSelector({
|
||||
// Default to main selected if currentWorktree is null/undefined or path is null
|
||||
const isSelected = worktree.isMain
|
||||
? currentWorktree === null || currentWorktree === undefined || currentWorktree.path === null
|
||||
: worktree.path === currentWorktreePath;
|
||||
: pathsEqual(worktree.path, currentWorktreePath);
|
||||
|
||||
const isRunning = hasRunningFeatures(worktree);
|
||||
|
||||
|
||||
@@ -47,6 +47,8 @@ export function CreatePRDialog({
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [prUrl, setPrUrl] = useState<string | null>(null);
|
||||
const [browserUrl, setBrowserUrl] = useState<string | null>(null);
|
||||
const [showBrowserFallback, setShowBrowserFallback] = useState(false);
|
||||
|
||||
// Reset state when dialog opens or worktree changes
|
||||
useEffect(() => {
|
||||
@@ -58,6 +60,8 @@ export function CreatePRDialog({
|
||||
setIsDraft(false);
|
||||
setError(null);
|
||||
setPrUrl(null);
|
||||
setBrowserUrl(null);
|
||||
setShowBrowserFallback(false);
|
||||
}
|
||||
}, [open, worktree?.path]);
|
||||
|
||||
@@ -93,14 +97,26 @@ export function CreatePRDialog({
|
||||
});
|
||||
onCreated();
|
||||
} else {
|
||||
// Branch was pushed successfully
|
||||
toast.success("Branch pushed", {
|
||||
description: result.result.committed
|
||||
? `Commit ${result.result.commitHash} pushed to ${result.result.branch}`
|
||||
: `Branch ${result.result.branch} pushed`,
|
||||
});
|
||||
if (!result.result.prCreated) {
|
||||
// Show the specific error if available
|
||||
|
||||
// Check if we should show browser fallback
|
||||
if (!result.result.prCreated && result.result.browserUrl) {
|
||||
const prError = result.result.prError;
|
||||
|
||||
// If gh CLI is not available, show browser fallback UI
|
||||
if (prError === "gh_cli_not_available" || !result.result.ghCliAvailable) {
|
||||
setBrowserUrl(result.result.browserUrl);
|
||||
setShowBrowserFallback(true);
|
||||
onCreated();
|
||||
return; // Don't close dialog, show browser fallback UI
|
||||
}
|
||||
|
||||
// gh CLI is available but failed - show error with browser option
|
||||
if (prError) {
|
||||
// Parse common gh CLI errors for better messages
|
||||
let errorMessage = prError;
|
||||
@@ -111,16 +127,25 @@ export function CreatePRDialog({
|
||||
} else if (prError.includes("not logged in") || prError.includes("auth")) {
|
||||
errorMessage = "GitHub CLI not authenticated. Run 'gh auth login' in terminal.";
|
||||
}
|
||||
|
||||
// Show error but also provide browser option
|
||||
setBrowserUrl(result.result.browserUrl);
|
||||
setShowBrowserFallback(true);
|
||||
toast.error("PR creation failed", {
|
||||
description: errorMessage,
|
||||
duration: 8000,
|
||||
});
|
||||
} else {
|
||||
toast.info("PR not created", {
|
||||
description: "GitHub CLI (gh) may not be installed or authenticated",
|
||||
});
|
||||
onCreated();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// No browser URL available, just close
|
||||
if (!result.result.prCreated) {
|
||||
toast.info("PR not created", {
|
||||
description: "GitHub CLI (gh) may not be installed or authenticated",
|
||||
});
|
||||
}
|
||||
onCreated();
|
||||
onOpenChange(false);
|
||||
}
|
||||
@@ -145,6 +170,8 @@ export function CreatePRDialog({
|
||||
setIsDraft(false);
|
||||
setError(null);
|
||||
setPrUrl(null);
|
||||
setBrowserUrl(null);
|
||||
setShowBrowserFallback(false);
|
||||
}, 200);
|
||||
};
|
||||
|
||||
@@ -185,6 +212,32 @@ export function CreatePRDialog({
|
||||
View Pull Request
|
||||
</Button>
|
||||
</div>
|
||||
) : showBrowserFallback && browserUrl ? (
|
||||
<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">
|
||||
<GitPullRequest className="w-8 h-8 text-blue-500" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">Branch Pushed!</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Your changes have been pushed to GitHub.
|
||||
<br />
|
||||
Click below to create a pull request in your browser.
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Button
|
||||
onClick={() => window.open(browserUrl, "_blank")}
|
||||
className="gap-2 w-full"
|
||||
>
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
Create PR in Browser
|
||||
</Button>
|
||||
<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
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="grid gap-4 py-4">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useMemo, useCallback } from "react";
|
||||
import { Feature } from "@/store/app-store";
|
||||
import { pathsEqual } from "@/lib/utils";
|
||||
|
||||
type ColumnId = Feature["status"];
|
||||
|
||||
@@ -65,8 +66,8 @@ export function useBoardColumnFeatures({
|
||||
// No worktree or branch assigned - show only on main
|
||||
matchesWorktree = !currentWorktreePath;
|
||||
} else if (f.worktreePath) {
|
||||
// Has worktreePath - match by path
|
||||
matchesWorktree = f.worktreePath === effectiveWorktreePath;
|
||||
// Has worktreePath - match by path (use pathsEqual for cross-platform compatibility)
|
||||
matchesWorktree = pathsEqual(f.worktreePath, effectiveWorktreePath);
|
||||
} else if (effectiveBranch === null) {
|
||||
// We're selecting a non-main worktree but can't determine its branch yet
|
||||
// (worktrees haven't loaded). Don't show branch-only features until we know.
|
||||
|
||||
Reference in New Issue
Block a user