Commit with GitHub user email (needed for vercel deployment)

This commit is contained in:
Will Chen
2025-04-14 22:39:42 -07:00
parent d55d6781a6
commit 1c325eccf4
7 changed files with 145 additions and 100 deletions

View File

@@ -171,126 +171,7 @@ export function GitHubConnector({ appId, folderName }: GitHubConnectorProps) {
}
};
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 (
<div className="mt-4 w-full border border-gray-200 rounded-md p-4">
<p>Connected to GitHub Repo:</p>
<a
onClick={(e) => {
e.preventDefault();
IpcClient.getInstance().openExternalUrl(
`https://github.com/${app.githubOrg}/${app.githubRepo}`
);
}}
className="cursor-pointer text-blue-600 hover:underline dark:text-blue-400"
target="_blank"
rel="noopener noreferrer"
>
{app.githubOrg}/{app.githubRepo}
</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>
);
}
if (settings?.githubSettings.secrets) {
return (
<div className="mt-4 w-full border border-gray-200 rounded-md p-4">
<p>Set up your GitHub repo</p>
<form className="mt-4 space-y-2" onSubmit={handleCreateRepo}>
<label className="block text-sm font-medium">Repository Name</label>
<input
className="w-full border rounded px-2 py-1"
value={repoName}
onChange={(e) => {
setRepoName(e.target.value);
setRepoAvailable(null);
setRepoCheckError(null);
}}
onBlur={handleRepoNameBlur}
disabled={isCreatingRepo}
/>
{isCheckingRepo && (
<p className="text-xs text-gray-500">Checking availability...</p>
)}
{repoAvailable === true && (
<p className="text-xs text-green-600">
Repository name is available!
</p>
)}
{repoAvailable === false && (
<p className="text-xs text-red-600">{repoCheckError}</p>
)}
<Button
type="submit"
disabled={isCreatingRepo || repoAvailable === false || !repoName}
>
{isCreatingRepo ? "Creating..." : "Create Repo"}
</Button>
</form>
{createRepoError && (
<p className="text-red-600 mt-2">{createRepoError}</p>
)}
{createRepoSuccess && (
<p className="text-green-600 mt-2">Repository created and linked!</p>
)}
</div>
);
}
if (!settings?.githubSettings.secrets?.accessToken) {
return (
<div className="mt-4 w-full">
{" "}
@@ -372,3 +253,122 @@ export function GitHubConnector({ appId, folderName }: GitHubConnectorProps) {
</div>
);
}
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 (
<div className="mt-4 w-full border border-gray-200 rounded-md p-4">
<p>Connected to GitHub Repo:</p>
<a
onClick={(e) => {
e.preventDefault();
IpcClient.getInstance().openExternalUrl(
`https://github.com/${app.githubOrg}/${app.githubRepo}`
);
}}
className="cursor-pointer text-blue-600 hover:underline dark:text-blue-400"
target="_blank"
rel="noopener noreferrer"
>
{app.githubOrg}/{app.githubRepo}
</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>
);
} else {
return (
<div className="mt-4 w-full border border-gray-200 rounded-md p-4">
<p>Set up your GitHub repo</p>
<form className="mt-4 space-y-2" onSubmit={handleCreateRepo}>
<label className="block text-sm font-medium">Repository Name</label>
<input
className="w-full border rounded px-2 py-1"
value={repoName}
onChange={(e) => {
setRepoName(e.target.value);
setRepoAvailable(null);
setRepoCheckError(null);
}}
onBlur={handleRepoNameBlur}
disabled={isCreatingRepo}
/>
{isCheckingRepo && (
<p className="text-xs text-gray-500">Checking availability...</p>
)}
{repoAvailable === true && (
<p className="text-xs text-green-600">
Repository name is available!
</p>
)}
{repoAvailable === false && (
<p className="text-xs text-red-600">{repoCheckError}</p>
)}
<Button
type="submit"
disabled={isCreatingRepo || repoAvailable === false || !repoName}
>
{isCreatingRepo ? "Creating..." : "Create Repo"}
</Button>
</form>
{createRepoError && (
<p className="text-red-600 mt-2">{createRepoError}</p>
)}
{createRepoSuccess && (
<p className="text-green-600 mt-2">Repository created and linked!</p>
)}
</div>
);
}
}

View File

@@ -33,6 +33,7 @@ import { getEnvVar } from "../utils/read_env";
import { readSettings } from "../../main/settings";
import { Worker } from "worker_threads";
import fixPath from "fix-path";
import { getGitAuthor } from "../utils/git_author";
// Needed, otherwise electron in MacOS/Linux will not be able
// to find "npm".
@@ -346,10 +347,7 @@ export function registerAppHandlers() {
fs: fs,
dir: fullAppPath,
message: "Init from react vite template",
author: {
name: "Dyad",
email: "dyad@example.com",
},
author: await getGitAuthor(),
});
} catch (error) {
console.error("Error in background app initialization:", error);
@@ -708,10 +706,7 @@ export function registerAppHandlers() {
fs,
dir: appPath,
message: `Reverted all changes back to version ${previousVersionId}`,
author: {
name: "Dyad",
email: "hi@dyad.sh",
},
author: await getGitAuthor(),
});
return { success: true };
@@ -845,10 +840,7 @@ export function registerAppHandlers() {
fs,
dir: appPath,
message: `Updated ${filePath}`,
author: {
name: "Dyad",
email: "hi@dyad.sh",
},
author: await getGitAuthor(),
});
}

View File

@@ -15,6 +15,7 @@ import { getDyadAppPath } from "../../paths/paths";
import { db } from "../../db";
import { apps } from "../../db/schema";
import { eq } from "drizzle-orm";
import { GithubUser } from "../../lib/schemas";
// --- GitHub Device Flow Constants ---
// TODO: Fetch this securely, e.g., from environment variables or a config file
@@ -38,6 +39,37 @@ let currentFlowState: DeviceFlowState | null = null;
// --- Helper Functions ---
/**
* Fetches the GitHub username of the currently authenticated user (using the stored access token).
* @returns {Promise<string|null>} The GitHub username, or null if not authenticated or on error.
*/
export async function getGithubUser(): Promise<GithubUser | null> {
const settings = readSettings();
const email = settings.githubUser?.email;
if (email) return { email };
try {
const accessToken = settings.githubSettings?.secrets?.accessToken;
if (!accessToken) return null;
const res = await fetch("https://api.github.com/user/emails", {
headers: { Authorization: `Bearer ${accessToken}` },
});
if (!res.ok) return null;
const emails = await res.json();
const email = emails.find((e: any) => e.primary)?.email;
if (!email) return null;
writeSettings({
githubUser: {
email,
},
});
return { email };
} catch (err) {
console.error("[GitHub Handler] Failed to get GitHub username:", err);
return null;
}
}
// function event.sender.send(channel: string, data: any) {
// if (currentFlowState?.window && !currentFlowState.window.isDestroyed()) {
// currentFlowState.window.webContents.send(channel, data);

View File

@@ -5,6 +5,8 @@ import fs from "node:fs";
import { getDyadAppPath } from "../../paths/paths";
import path from "node:path";
import git from "isomorphic-git";
import { getGithubUser } from "../handlers/github_handlers";
import { getGitAuthor } from "../utils/git_author";
export function getDyadWriteTags(fullResponse: string): {
path: string;
@@ -210,10 +212,7 @@ export async function processFullResponseActions(
message: chatSummary
? `[dyad] ${chatSummary} - ${changes.join(", ")}`
: `[dyad] ${changes.join(", ")}`,
author: {
name: "Dyad AI",
email: "dyad-ai@example.com",
},
author: await getGitAuthor(),
});
console.log(`Successfully committed changes: ${changes.join(", ")}`);
return { updatedFiles: true };

View File

@@ -0,0 +1,15 @@
import { getGithubUser } from "../handlers/github_handlers";
export async function getGitAuthor() {
const user = await getGithubUser();
const author = user
? {
name: `[dyad]`,
email: user.email,
}
: {
name: "[dyad]",
email: "git@dyad.sh",
};
return author;
}

View File

@@ -74,6 +74,11 @@ export const GitHubSettingsSchema = z.object({
});
export type GitHubSettings = z.infer<typeof GitHubSettingsSchema>;
export const GithubUserSchema = z.object({
email: z.string(),
});
export type GithubUser = z.infer<typeof GithubUserSchema>;
/**
* Zod schema for user settings
*/
@@ -82,6 +87,7 @@ export const UserSettingsSchema = z.object({
providerSettings: z.record(z.string(), ProviderSettingSchema),
runtimeMode: RuntimeModeSchema,
githubSettings: GitHubSettingsSchema,
githubUser: GithubUserSchema.optional(),
});
/**

View File

@@ -3,6 +3,7 @@ import path from "node:path";
import { getUserDataPath } from "../paths/paths";
import { UserSettingsSchema, type UserSettings } from "../lib/schemas";
import { safeStorage } from "electron";
const DEFAULT_SETTINGS: UserSettings = {
selectedModel: {
name: "auto",