Unverified 提交 9108f67c authored 作者: Will Chen's avatar Will Chen 提交者: GitHub

Fix repo name mismatch when GitHub repo name contains spaces (#2377)

## Summary - Adds `normalizeGitHubRepoName` function that converts spaces to hyphens to match GitHub's automatic normalization behavior - Applies normalization when creating repos, checking availability, and storing repo names in the database - Fixes issue where Dyad stored "my app" but GitHub created "my-app", causing a mismatch Fixes #1336 ## Test plan - Unit tests added for `normalizeGitHubRepoName` function covering edge cases - E2E test added to verify repo names with spaces are properly normalized when creating and syncing 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/dyad-sh/dyad/pull/2377"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Normalize GitHub repo names by converting all whitespace to hyphens so our stored name matches GitHub’s actual repo name. Fixes #1336. - **Bug Fixes** - Add normalizeGitHubRepoName to convert whitespace to hyphens. - Apply normalization for availability checks, repo creation, remote URLs, and DB storage. - Add unit tests for edge cases and an E2E test for create/sync with spaced names. <sup>Written for commit 0735ce38a11fced50e670821e58289dcc53fe86f. Summary will update on new commits.</sup> <!-- End of auto-generated description by cubic. --> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches the GitHub create/availability flow and the repo name stored in the app DB, which could break GitHub linking if normalization is incorrect. Logic is small and well-covered by new unit and E2E tests, reducing risk. > > **Overview** > Fixes repo-name mismatches when users enter names containing whitespace by introducing `normalizeGitHubRepoName` (trim + collapse whitespace to `-`). > > Applies normalization to repo availability checks, repo creation payloads, generated remote URLs, and `updateAppGithubRepo` persistence so the app consistently uses GitHub’s normalized repo name. > > Adds Vitest unit coverage for normalization edge cases and a Playwright E2E test that creates/syncs a repo named with spaces and asserts the hyphenated name is used end-to-end. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 0735ce38a11fced50e670821e58289dcc53fe86f. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: 's avatarClaude Opus 4.5 <noreply@anthropic.com>
上级 bb0f30b0
...@@ -84,6 +84,40 @@ test("create and sync to new repo - custom branch", async ({ po }) => { ...@@ -84,6 +84,40 @@ test("create and sync to new repo - custom branch", async ({ po }) => {
}); });
}); });
test("create repo with spaces in name - should normalize to hyphens", async ({
po,
}) => {
await po.setUp();
await po.sendPrompt("tc=basic");
await po.getTitleBarAppNameButton().click();
await po.githubConnector.connect();
// Enter a repo name with spaces - GitHub normalizes these to hyphens
await po.githubConnector.fillCreateRepoName("my new repo");
// Wait for availability check
await po.page.waitForSelector("text=Repository name is available!", {
timeout: 5000,
});
// Click create repo button
await po.githubConnector.clickCreateRepoButton();
// Verify the connected repo shows the normalized name (with hyphens, not spaces)
await expect(po.page.locator("text=testuser/my-new-repo")).toBeVisible();
// Sync to GitHub
await po.githubConnector.clickSyncToGithubButton();
// Verify the push was received with the normalized repo name
await po.githubConnector.verifyPushEvent({
repo: "my-new-repo",
branch: "main",
operation: "create",
});
});
test("disconnect from repo", async ({ po }) => { test("disconnect from repo", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.sendPrompt("tc=basic"); await po.sendPrompt("tc=basic");
......
import { normalizeGitHubRepoName } from "@/ipc/handlers/github_handlers";
import { describe, it, expect } from "vitest";
describe("normalizeGitHubRepoName", () => {
it("should replace single space with hyphen", () => {
expect(normalizeGitHubRepoName("my app")).toBe("my-app");
});
it("should replace multiple spaces with hyphens", () => {
expect(normalizeGitHubRepoName("my cool app")).toBe("my-cool-app");
});
it("should replace consecutive spaces with a single hyphen", () => {
expect(normalizeGitHubRepoName("my app")).toBe("my-app");
});
it("should not modify names without spaces", () => {
expect(normalizeGitHubRepoName("my-app")).toBe("my-app");
});
it("should handle empty string", () => {
expect(normalizeGitHubRepoName("")).toBe("");
});
it("should handle leading and trailing spaces", () => {
expect(normalizeGitHubRepoName(" my app ")).toBe("my-app");
});
it("should handle tabs as whitespace", () => {
expect(normalizeGitHubRepoName("my\tapp")).toBe("my-app");
});
});
...@@ -41,6 +41,16 @@ import type { CloneRepoParams, CloneRepoResult } from "../types/github"; ...@@ -41,6 +41,16 @@ import type { CloneRepoParams, CloneRepoResult } from "../types/github";
const logger = log.scope("github_handlers"); const logger = log.scope("github_handlers");
/**
* Normalizes a GitHub repository name to match GitHub's automatic normalization rules.
* GitHub converts spaces to hyphens when creating repositories.
* @param repoName - The original repository name
* @returns The normalized repository name with spaces replaced by hyphens
*/
export function normalizeGitHubRepoName(repoName: string): string {
return repoName.trim().replace(/\s+/g, "-");
}
// --- 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
const GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID || "Ov23liWV2HdC0RBLecWx"; const GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID || "Ov23liWV2HdC0RBLecWx";
...@@ -609,6 +619,9 @@ async function handleIsRepoAvailable( ...@@ -609,6 +619,9 @@ async function handleIsRepoAvailable(
event: IpcMainInvokeEvent, event: IpcMainInvokeEvent,
{ org, repo }: { org: string; repo: string }, { org, repo }: { org: string; repo: string },
): Promise<{ available: boolean; error?: string }> { ): Promise<{ available: boolean; error?: string }> {
// Normalize the repo name to match GitHub's automatic normalization
const normalizedRepo = normalizeGitHubRepoName(repo);
try { try {
// Get access token from settings // Get access token from settings
const settings = readSettings(); const settings = readSettings();
...@@ -624,8 +637,8 @@ async function handleIsRepoAvailable( ...@@ -624,8 +637,8 @@ async function handleIsRepoAvailable(
}) })
.then((r) => r.json()) .then((r) => r.json())
.then((u) => u.login)); .then((u) => u.login));
// Check if repo exists // Check if repo exists (using normalized name)
const url = `${GITHUB_API_BASE}/repos/${owner}/${repo}`; const url = `${GITHUB_API_BASE}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(normalizedRepo)}`;
const res = await fetch(url, { const res = await fetch(url, {
headers: { Authorization: `Bearer ${accessToken}` }, headers: { Authorization: `Bearer ${accessToken}` },
}); });
...@@ -652,6 +665,10 @@ async function handleCreateRepo( ...@@ -652,6 +665,10 @@ async function handleCreateRepo(
branch, branch,
}: { org: string; repo: string; appId: number; branch?: string }, }: { org: string; repo: string; appId: number; branch?: string },
): Promise<void> { ): Promise<void> {
// Normalize the repo name to match GitHub's automatic normalization
// GitHub converts spaces to hyphens when creating repositories
const normalizedRepo = normalizeGitHubRepoName(repo);
// Get access token from settings // Get access token from settings
const settings = readSettings(); const settings = readSettings();
const accessToken = settings.githubAccessToken?.value; const accessToken = settings.githubAccessToken?.value;
...@@ -679,7 +696,7 @@ async function handleCreateRepo( ...@@ -679,7 +696,7 @@ async function handleCreateRepo(
Accept: "application/vnd.github+json", Accept: "application/vnd.github+json",
}, },
body: JSON.stringify({ body: JSON.stringify({
name: repo, name: normalizedRepo,
private: true, private: true,
}), }),
}); });
...@@ -726,8 +743,8 @@ async function handleCreateRepo( ...@@ -726,8 +743,8 @@ async function handleCreateRepo(
// Set up remote URL before preparing branch // Set up remote URL before preparing branch
const remoteUrl = IS_TEST_BUILD const remoteUrl = IS_TEST_BUILD
? `${GITHUB_GIT_BASE}/${owner}/${repo}.git` ? `${GITHUB_GIT_BASE}/${owner}/${normalizedRepo}.git`
: `https://${accessToken}:x-oauth-basic@github.com/${owner}/${repo}.git`; : `https://${accessToken}:x-oauth-basic@github.com/${owner}/${normalizedRepo}.git`;
// Prepare local branch with remote URL set up // Prepare local branch with remote URL set up
await prepareLocalBranch({ await prepareLocalBranch({
...@@ -737,8 +754,13 @@ async function handleCreateRepo( ...@@ -737,8 +754,13 @@ async function handleCreateRepo(
accessToken, accessToken,
}); });
// Store org, repo, and branch in the app's DB row (apps table) // Store org, repo (normalized), and branch in the app's DB row (apps table)
await updateAppGithubRepo({ appId, org: owner, repo, branch }); await updateAppGithubRepo({
appId,
org: owner,
repo: normalizedRepo,
branch,
});
} }
// --- GitHub Connect to Existing Repo Handler --- // --- GitHub Connect to Existing Repo Handler ---
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论