mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 08:13:37 +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:
@@ -11,6 +11,7 @@ import { createDeleteApiKeyHandler } from "./routes/delete-api-key.js";
|
||||
import { createApiKeysHandler } from "./routes/api-keys.js";
|
||||
import { createPlatformHandler } from "./routes/platform.js";
|
||||
import { createVerifyClaudeAuthHandler } from "./routes/verify-claude-auth.js";
|
||||
import { createGhStatusHandler } from "./routes/gh-status.js";
|
||||
|
||||
export function createSetupRoutes(): Router {
|
||||
const router = Router();
|
||||
@@ -23,6 +24,7 @@ export function createSetupRoutes(): Router {
|
||||
router.get("/api-keys", createApiKeysHandler());
|
||||
router.get("/platform", createPlatformHandler());
|
||||
router.post("/verify-claude-auth", createVerifyClaudeAuthHandler());
|
||||
router.get("/gh-status", createGhStatusHandler());
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
131
apps/server/src/routes/setup/routes/gh-status.ts
Normal file
131
apps/server/src/routes/setup/routes/gh-status.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* GET /gh-status endpoint - Get GitHub CLI status
|
||||
*/
|
||||
|
||||
import type { Request, Response } from "express";
|
||||
import { exec } from "child_process";
|
||||
import { promisify } from "util";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
import fs from "fs/promises";
|
||||
import { getErrorMessage, logError } from "../common.js";
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
// Extended PATH to include common tool installation locations
|
||||
const extendedPath = [
|
||||
process.env.PATH,
|
||||
"/opt/homebrew/bin",
|
||||
"/usr/local/bin",
|
||||
"/home/linuxbrew/.linuxbrew/bin",
|
||||
`${process.env.HOME}/.local/bin`,
|
||||
].filter(Boolean).join(":");
|
||||
|
||||
const execEnv = {
|
||||
...process.env,
|
||||
PATH: extendedPath,
|
||||
};
|
||||
|
||||
export interface GhStatus {
|
||||
installed: boolean;
|
||||
authenticated: boolean;
|
||||
version: string | null;
|
||||
path: string | null;
|
||||
user: string | null;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
async function getGhStatus(): Promise<GhStatus> {
|
||||
const status: GhStatus = {
|
||||
installed: false,
|
||||
authenticated: false,
|
||||
version: null,
|
||||
path: null,
|
||||
user: null,
|
||||
};
|
||||
|
||||
const isWindows = process.platform === "win32";
|
||||
|
||||
// Check if gh CLI is installed
|
||||
try {
|
||||
const findCommand = isWindows ? "where gh" : "command -v gh";
|
||||
const { stdout } = await execAsync(findCommand, { env: execEnv });
|
||||
status.path = stdout.trim().split(/\r?\n/)[0];
|
||||
status.installed = true;
|
||||
} catch {
|
||||
// gh not in PATH, try common locations
|
||||
const commonPaths = isWindows
|
||||
? [
|
||||
path.join(process.env.LOCALAPPDATA || "", "Programs", "gh", "bin", "gh.exe"),
|
||||
path.join(process.env.ProgramFiles || "", "GitHub CLI", "gh.exe"),
|
||||
]
|
||||
: [
|
||||
"/opt/homebrew/bin/gh",
|
||||
"/usr/local/bin/gh",
|
||||
path.join(os.homedir(), ".local", "bin", "gh"),
|
||||
"/home/linuxbrew/.linuxbrew/bin/gh",
|
||||
];
|
||||
|
||||
for (const p of commonPaths) {
|
||||
try {
|
||||
await fs.access(p);
|
||||
status.path = p;
|
||||
status.installed = true;
|
||||
break;
|
||||
} catch {
|
||||
// Not found at this path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!status.installed) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Get version
|
||||
try {
|
||||
const { stdout } = await execAsync("gh --version", { env: execEnv });
|
||||
// Extract version from output like "gh version 2.40.1 (2024-01-09)"
|
||||
const versionMatch = stdout.match(/gh version ([\d.]+)/);
|
||||
status.version = versionMatch ? versionMatch[1] : stdout.trim().split("\n")[0];
|
||||
} catch {
|
||||
// Version command failed
|
||||
}
|
||||
|
||||
// Check authentication status
|
||||
try {
|
||||
const { stdout } = await execAsync("gh auth status", { env: execEnv });
|
||||
// If this succeeds without error, we're authenticated
|
||||
status.authenticated = true;
|
||||
|
||||
// Try to extract username from output
|
||||
const userMatch = stdout.match(/Logged in to [^\s]+ account ([^\s]+)/i) ||
|
||||
stdout.match(/Logged in to [^\s]+ as ([^\s]+)/i);
|
||||
if (userMatch) {
|
||||
status.user = userMatch[1];
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
// Auth status returns non-zero if not authenticated
|
||||
const err = error as { stderr?: string };
|
||||
if (err.stderr?.includes("not logged in")) {
|
||||
status.authenticated = false;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
export function createGhStatusHandler() {
|
||||
return async (_req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const status = await getGhStatus();
|
||||
res.json({
|
||||
success: true,
|
||||
...status,
|
||||
});
|
||||
} catch (error) {
|
||||
logError(error, "Get GitHub CLI status failed");
|
||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user