support github push
This commit is contained in:
@@ -117,7 +117,10 @@ export function GitHubConnector({ appId, folderName }: GitHubConnectorProps) {
|
|||||||
const [isCreatingRepo, setIsCreatingRepo] = useState(false);
|
const [isCreatingRepo, setIsCreatingRepo] = useState(false);
|
||||||
const [createRepoError, setCreateRepoError] = useState<string | null>(null);
|
const [createRepoError, setCreateRepoError] = useState<string | null>(null);
|
||||||
const [createRepoSuccess, setCreateRepoSuccess] = useState<boolean>(false);
|
const [createRepoSuccess, setCreateRepoSuccess] = useState<boolean>(false);
|
||||||
|
// --- Sync to GitHub State ---
|
||||||
|
const [isSyncing, setIsSyncing] = useState(false);
|
||||||
|
const [syncError, setSyncError] = useState<string | null>(null);
|
||||||
|
const [syncSuccess, setSyncSuccess] = useState<boolean>(false);
|
||||||
// Assume org is the authenticated user for now (could add org input later)
|
// Assume org is the authenticated user for now (could add org input later)
|
||||||
// TODO: After device flow, fetch and store the GitHub username/org in settings for use here
|
// TODO: After device flow, fetch and store the GitHub username/org in settings for use here
|
||||||
const githubOrg = ""; // Use empty string for now (GitHub API will default to the authenticated user)
|
const githubOrg = ""; // Use empty string for now (GitHub API will default to the authenticated user)
|
||||||
@@ -169,6 +172,24 @@ export function GitHubConnector({ appId, folderName }: GitHubConnectorProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (app?.githubOrg && app?.githubRepo) {
|
if (app?.githubOrg && app?.githubRepo) {
|
||||||
|
const handleSyncToGithub = async () => {
|
||||||
|
setIsSyncing(true);
|
||||||
|
setSyncError(null);
|
||||||
|
setSyncSuccess(false);
|
||||||
|
try {
|
||||||
|
const result = await IpcClient.getInstance().syncGithubRepo(appId!);
|
||||||
|
if (result.success) {
|
||||||
|
setSyncSuccess(true);
|
||||||
|
} else {
|
||||||
|
setSyncError(result.error || "Failed to sync to GitHub.");
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
setSyncError(err.message || "Failed to sync to GitHub.");
|
||||||
|
} finally {
|
||||||
|
setIsSyncing(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-4 w-full border border-gray-200 rounded-md p-4">
|
<div className="mt-4 w-full border border-gray-200 rounded-md p-4">
|
||||||
<p>Connected to GitHub Repo:</p>
|
<p>Connected to GitHub Repo:</p>
|
||||||
@@ -185,6 +206,42 @@ export function GitHubConnector({ appId, folderName }: GitHubConnectorProps) {
|
|||||||
>
|
>
|
||||||
{app.githubOrg}/{app.githubRepo}
|
{app.githubOrg}/{app.githubRepo}
|
||||||
</a>
|
</a>
|
||||||
|
<div className="mt-2">
|
||||||
|
<Button onClick={handleSyncToGithub} disabled={isSyncing}>
|
||||||
|
{isSyncing ? (
|
||||||
|
<>
|
||||||
|
<svg
|
||||||
|
className="animate-spin h-5 w-5 mr-2 inline"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
style={{ display: "inline" }}
|
||||||
|
>
|
||||||
|
<circle
|
||||||
|
className="opacity-25"
|
||||||
|
cx="12"
|
||||||
|
cy="12"
|
||||||
|
r="10"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="4"
|
||||||
|
></circle>
|
||||||
|
<path
|
||||||
|
className="opacity-75"
|
||||||
|
fill="currentColor"
|
||||||
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
Syncing...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
"Sync to GitHub"
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{syncError && <p className="text-red-600 mt-2">{syncError}</p>}
|
||||||
|
{syncSuccess && (
|
||||||
|
<p className="text-green-600 mt-2">Successfully pushed to GitHub!</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,14 @@ import {
|
|||||||
import fetch from "node-fetch"; // Use node-fetch for making HTTP requests in main process
|
import fetch from "node-fetch"; // Use node-fetch for making HTTP requests in main process
|
||||||
import { writeSettings, readSettings } from "../../main/settings";
|
import { writeSettings, readSettings } from "../../main/settings";
|
||||||
import { updateAppGithubRepo } from "../../db/index";
|
import { updateAppGithubRepo } from "../../db/index";
|
||||||
|
import git from "isomorphic-git";
|
||||||
|
import http from "isomorphic-git/http/node";
|
||||||
|
import path from "node:path";
|
||||||
|
import fs from "node:fs";
|
||||||
|
import { getDyadAppPath } from "../../paths/paths";
|
||||||
|
import { db } from "../../db";
|
||||||
|
import { apps } from "../../db/schema";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
|
||||||
// --- GitHub Device Flow Constants ---
|
// --- GitHub Device Flow Constants ---
|
||||||
// TODO: Fetch this securely, e.g., from environment variables or a config file
|
// TODO: Fetch this securely, e.g., from environment variables or a config file
|
||||||
@@ -363,10 +371,57 @@ async function handleCreateRepo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- GitHub Push Handler ---
|
||||||
|
async function handlePushToGithub(
|
||||||
|
event: IpcMainInvokeEvent,
|
||||||
|
{ appId }: { appId: number }
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
// Get access token from settings
|
||||||
|
const settings = readSettings();
|
||||||
|
const accessToken = settings.githubSettings?.secrets?.accessToken;
|
||||||
|
if (!accessToken) {
|
||||||
|
return { success: false, error: "Not authenticated with GitHub." };
|
||||||
|
}
|
||||||
|
// Get app info from DB
|
||||||
|
const app = await db.query.apps.findFirst({ where: eq(apps.id, appId) });
|
||||||
|
if (!app || !app.githubOrg || !app.githubRepo) {
|
||||||
|
return { success: false, error: "App is not linked to a GitHub repo." };
|
||||||
|
}
|
||||||
|
const appPath = getDyadAppPath(app.path);
|
||||||
|
// Set up remote URL with token
|
||||||
|
const remoteUrl = `https://${accessToken}:x-oauth-basic@github.com/${app.githubOrg}/${app.githubRepo}.git`;
|
||||||
|
// Set or update remote URL using git config
|
||||||
|
await git.setConfig({
|
||||||
|
fs,
|
||||||
|
dir: appPath,
|
||||||
|
path: "remote.origin.url",
|
||||||
|
value: remoteUrl,
|
||||||
|
});
|
||||||
|
// Push to GitHub
|
||||||
|
await git.push({
|
||||||
|
fs,
|
||||||
|
http,
|
||||||
|
dir: appPath,
|
||||||
|
remote: "origin",
|
||||||
|
ref: "main",
|
||||||
|
onAuth: () => ({ username: accessToken, password: "x-oauth-basic" }),
|
||||||
|
force: false,
|
||||||
|
});
|
||||||
|
return { success: true };
|
||||||
|
} catch (err: any) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: err.message || "Failed to push to GitHub.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- Registration ---
|
// --- Registration ---
|
||||||
export function registerGithubHandlers() {
|
export function registerGithubHandlers() {
|
||||||
ipcMain.handle("github:start-flow", handleStartGithubFlow);
|
ipcMain.handle("github:start-flow", handleStartGithubFlow);
|
||||||
// ipcMain.on('github:cancel-flow', handleCancelGithubFlow); // Uncomment if you add cancellation
|
// ipcMain.on('github:cancel-flow', handleCancelGithubFlow); // Uncomment if you add cancellation
|
||||||
ipcMain.handle("github:is-repo-available", handleIsRepoAvailable);
|
ipcMain.handle("github:is-repo-available", handleIsRepoAvailable);
|
||||||
ipcMain.handle("github:create-repo", handleCreateRepo);
|
ipcMain.handle("github:create-repo", handleCreateRepo);
|
||||||
|
ipcMain.handle("github:push", handlePushToGithub);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -603,6 +603,18 @@ export class IpcClient {
|
|||||||
return { success: false, error: error.message || "Unknown error" };
|
return { success: false, error: error.message || "Unknown error" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sync (push) local repo to GitHub
|
||||||
|
public async syncGithubRepo(
|
||||||
|
appId: number
|
||||||
|
): Promise<{ success: boolean; error?: string }> {
|
||||||
|
try {
|
||||||
|
const result = await this.ipcRenderer.invoke("github:push", { appId });
|
||||||
|
return result;
|
||||||
|
} catch (error: any) {
|
||||||
|
return { success: false, error: error.message || "Unknown error" };
|
||||||
|
}
|
||||||
|
}
|
||||||
// --- End GitHub Repo Management ---
|
// --- End GitHub Repo Management ---
|
||||||
|
|
||||||
// Example methods for listening to events (if needed)
|
// Example methods for listening to events (if needed)
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ const validInvokeChannels = [
|
|||||||
"github:start-flow",
|
"github:start-flow",
|
||||||
"github:is-repo-available",
|
"github:is-repo-available",
|
||||||
"github:create-repo",
|
"github:create-repo",
|
||||||
|
"github:push",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
// Add valid receive channels
|
// Add valid receive channels
|
||||||
|
|||||||
Reference in New Issue
Block a user