Commit with GitHub user email (needed for vercel deployment)
This commit is contained in:
@@ -171,126 +171,7 @@ export function GitHubConnector({ appId, folderName }: GitHubConnectorProps) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (app?.githubOrg && app?.githubRepo) {
|
if (!settings?.githubSettings.secrets?.accessToken) {
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-4 w-full">
|
<div className="mt-4 w-full">
|
||||||
{" "}
|
{" "}
|
||||||
@@ -372,3 +253,122 @@ export function GitHubConnector({ appId, folderName }: GitHubConnectorProps) {
|
|||||||
</div>
|
</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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import { getEnvVar } from "../utils/read_env";
|
|||||||
import { readSettings } from "../../main/settings";
|
import { readSettings } from "../../main/settings";
|
||||||
import { Worker } from "worker_threads";
|
import { Worker } from "worker_threads";
|
||||||
import fixPath from "fix-path";
|
import fixPath from "fix-path";
|
||||||
|
import { getGitAuthor } from "../utils/git_author";
|
||||||
|
|
||||||
// Needed, otherwise electron in MacOS/Linux will not be able
|
// Needed, otherwise electron in MacOS/Linux will not be able
|
||||||
// to find "npm".
|
// to find "npm".
|
||||||
@@ -346,10 +347,7 @@ export function registerAppHandlers() {
|
|||||||
fs: fs,
|
fs: fs,
|
||||||
dir: fullAppPath,
|
dir: fullAppPath,
|
||||||
message: "Init from react vite template",
|
message: "Init from react vite template",
|
||||||
author: {
|
author: await getGitAuthor(),
|
||||||
name: "Dyad",
|
|
||||||
email: "dyad@example.com",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error in background app initialization:", error);
|
console.error("Error in background app initialization:", error);
|
||||||
@@ -708,10 +706,7 @@ export function registerAppHandlers() {
|
|||||||
fs,
|
fs,
|
||||||
dir: appPath,
|
dir: appPath,
|
||||||
message: `Reverted all changes back to version ${previousVersionId}`,
|
message: `Reverted all changes back to version ${previousVersionId}`,
|
||||||
author: {
|
author: await getGitAuthor(),
|
||||||
name: "Dyad",
|
|
||||||
email: "hi@dyad.sh",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
@@ -845,10 +840,7 @@ export function registerAppHandlers() {
|
|||||||
fs,
|
fs,
|
||||||
dir: appPath,
|
dir: appPath,
|
||||||
message: `Updated ${filePath}`,
|
message: `Updated ${filePath}`,
|
||||||
author: {
|
author: await getGitAuthor(),
|
||||||
name: "Dyad",
|
|
||||||
email: "hi@dyad.sh",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { getDyadAppPath } from "../../paths/paths";
|
|||||||
import { db } from "../../db";
|
import { db } from "../../db";
|
||||||
import { apps } from "../../db/schema";
|
import { apps } from "../../db/schema";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
|
import { GithubUser } from "../../lib/schemas";
|
||||||
|
|
||||||
// --- 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
|
||||||
@@ -38,6 +39,37 @@ let currentFlowState: DeviceFlowState | null = null;
|
|||||||
|
|
||||||
// --- Helper Functions ---
|
// --- 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) {
|
// function event.sender.send(channel: string, data: any) {
|
||||||
// if (currentFlowState?.window && !currentFlowState.window.isDestroyed()) {
|
// if (currentFlowState?.window && !currentFlowState.window.isDestroyed()) {
|
||||||
// currentFlowState.window.webContents.send(channel, data);
|
// currentFlowState.window.webContents.send(channel, data);
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import fs from "node:fs";
|
|||||||
import { getDyadAppPath } from "../../paths/paths";
|
import { getDyadAppPath } from "../../paths/paths";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import git from "isomorphic-git";
|
import git from "isomorphic-git";
|
||||||
|
import { getGithubUser } from "../handlers/github_handlers";
|
||||||
|
import { getGitAuthor } from "../utils/git_author";
|
||||||
|
|
||||||
export function getDyadWriteTags(fullResponse: string): {
|
export function getDyadWriteTags(fullResponse: string): {
|
||||||
path: string;
|
path: string;
|
||||||
@@ -210,10 +212,7 @@ export async function processFullResponseActions(
|
|||||||
message: chatSummary
|
message: chatSummary
|
||||||
? `[dyad] ${chatSummary} - ${changes.join(", ")}`
|
? `[dyad] ${chatSummary} - ${changes.join(", ")}`
|
||||||
: `[dyad] ${changes.join(", ")}`,
|
: `[dyad] ${changes.join(", ")}`,
|
||||||
author: {
|
author: await getGitAuthor(),
|
||||||
name: "Dyad AI",
|
|
||||||
email: "dyad-ai@example.com",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
console.log(`Successfully committed changes: ${changes.join(", ")}`);
|
console.log(`Successfully committed changes: ${changes.join(", ")}`);
|
||||||
return { updatedFiles: true };
|
return { updatedFiles: true };
|
||||||
|
|||||||
15
src/ipc/utils/git_author.ts
Normal file
15
src/ipc/utils/git_author.ts
Normal 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;
|
||||||
|
}
|
||||||
@@ -74,6 +74,11 @@ export const GitHubSettingsSchema = z.object({
|
|||||||
});
|
});
|
||||||
export type GitHubSettings = z.infer<typeof GitHubSettingsSchema>;
|
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
|
* Zod schema for user settings
|
||||||
*/
|
*/
|
||||||
@@ -82,6 +87,7 @@ export const UserSettingsSchema = z.object({
|
|||||||
providerSettings: z.record(z.string(), ProviderSettingSchema),
|
providerSettings: z.record(z.string(), ProviderSettingSchema),
|
||||||
runtimeMode: RuntimeModeSchema,
|
runtimeMode: RuntimeModeSchema,
|
||||||
githubSettings: GitHubSettingsSchema,
|
githubSettings: GitHubSettingsSchema,
|
||||||
|
githubUser: GithubUserSchema.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import path from "node:path";
|
|||||||
import { getUserDataPath } from "../paths/paths";
|
import { getUserDataPath } from "../paths/paths";
|
||||||
import { UserSettingsSchema, type UserSettings } from "../lib/schemas";
|
import { UserSettingsSchema, type UserSettings } from "../lib/schemas";
|
||||||
import { safeStorage } from "electron";
|
import { safeStorage } from "electron";
|
||||||
|
|
||||||
const DEFAULT_SETTINGS: UserSettings = {
|
const DEFAULT_SETTINGS: UserSettings = {
|
||||||
selectedModel: {
|
selectedModel: {
|
||||||
name: "auto",
|
name: "auto",
|
||||||
|
|||||||
Reference in New Issue
Block a user