GitHub Import Feature: Import repositories/projects from GitHub (#1424) (#1454)

## Summary
Adds the ability to import GitHub repositories directly into Dyad from
the home screen, complementing the existing local folder import feature.
- GitHub Import Modal: New modal accessible from home screen via "Import
from Github" button with two Import methods
- Select project from GitHub repositories list
- Clone from any GitHub URL
- Advanced Options: Optional custom install/start commands (defaults to
project's package.json scripts)
- Auto AI_RULES Generation: Automatically generates AI_RULES.md if not
present in imported repo

closes #1424
    
<!-- This is an auto-generated description by cubic. -->
---

## Summary by cubic
Adds a GitHub import flow from the home screen so users can clone repos
via their list or any URL, with optional install/start commands and
automatic AI_RULES.md generation. Addresses Linear #1424 by enabling
seamless project setup from GitHub.

- **New Features**
  - Import modal with two tabs: Your Repositories and From URL.
- Advanced options for install/start commands with validation; defaults
used when both are empty.
- After cloning, navigate to chat and auto-generate AI_RULES.md if
missing.
- New IPC handler github:clone-repo-from-url with token auth support,
plus IpcClient method and preload channel.
- E2E tests cover modal open, auth, import via URL/repo list, and
advanced options.

- **Dependencies**
  - Added @radix-ui/react-tabs for the modal tab UI.

<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Adeniji Adekunle James
2025-10-14 03:10:04 +01:00
committed by GitHub
parent 7acbe73c73
commit 348521ce82
12 changed files with 934 additions and 157 deletions

View File

@@ -0,0 +1,173 @@
import { expect } from "@playwright/test";
import { test } from "./helpers/test_helper";
test("should open GitHub import modal from home", async ({ po }) => {
await po.setUp();
// Click the "Import from Github" button
await po.page.getByRole("button", { name: "Import App" }).click();
// Verify modal opened with import UI (showing all tabs even when not authenticated)
await expect(
po.page.getByRole("heading", { name: "Import App" }),
).toBeVisible();
await expect(
po.page.getByText(
"Import existing app from local folder or clone from Github",
),
).toBeVisible();
// All tabs should be visible
await expect(
po.page.getByRole("tab", { name: "Local Folder" }),
).toBeVisible();
await expect(
po.page.getByRole("tab", { name: "Your GitHub Repos" }),
).toBeVisible();
await expect(po.page.getByRole("tab", { name: "GitHub URL" })).toBeVisible();
// Local Folder tab should be active by default
await expect(
po.page.getByRole("button", { name: "Select Folder" }),
).toBeVisible();
// Switch to Your GitHub Repos tab - should show GitHub connector
await po.page.getByRole("tab", { name: "Your GitHub Repos" }).click();
await expect(
po.page.getByRole("button", { name: "Connect to GitHub" }),
).toBeVisible();
});
test("should connect to GitHub and show import UI", async ({ po }) => {
await po.setUp();
// Open modal
await po.page.getByRole("button", { name: "Import App" }).click();
// Switch to Your GitHub Repos tab - should show GitHub connector when not authenticated
await po.page.getByRole("tab", { name: "Your GitHub Repos" }).click();
// Connect to GitHub (reuse existing connector)
await po.page.getByRole("button", { name: "Connect to GitHub" }).click();
// Wait for device flow code
await expect(po.page.locator("text=FAKE-CODE")).toBeVisible();
// After connection, should show repositories list instead of connector
await expect(po.page.getByText("testuser/existing-app")).toBeVisible();
// Should be able to see all tabs
await expect(
po.page.getByRole("tab", { name: "Your GitHub Repos" }),
).toBeVisible();
await expect(po.page.getByRole("tab", { name: "GitHub URL" })).toBeVisible();
await expect(
po.page.getByRole("tab", { name: "Local Folder" }),
).toBeVisible();
});
test("should import GitHub URL", async ({ po }) => {
await po.setUp();
// Open modal and connect
await po.page.getByRole("button", { name: "Import App" }).click();
await po.page.getByRole("tab", { name: "Your GitHub Repos" }).click();
await po.page.getByRole("button", { name: "Connect to GitHub" }).click();
await expect(po.page.locator("text=FAKE-CODE")).toBeVisible();
// Switch to "GitHub URL" tab
await po.page.getByRole("tab", { name: "GitHub URL" }).click();
// Enter URL
await po.page
.getByPlaceholder("https://github.com/user/repo.git")
.fill("https://github.com/dyad-sh/nextjs-template.git");
// Click import
await po.page.getByRole("button", { name: "Import", exact: true }).click();
// Should close modal and navigate to chat
await expect(
po.page.getByRole("heading", { name: "Import App" }),
).not.toBeVisible();
// Verify AI_RULES generation prompt was sent
});
test("should import from repository list", async ({ po }) => {
await po.setUp();
// Open modal and connect
await po.page.getByRole("button", { name: "Import App" }).click();
// Switch to Your GitHub Repos tab - should show GitHub connector when not authenticated
await po.page.getByRole("tab", { name: "Your GitHub Repos" }).click();
await po.page.getByRole("button", { name: "Connect to GitHub" }).click();
await expect(po.page.locator("text=FAKE-CODE")).toBeVisible();
// Switch to Your GitHub Repos tab
await po.page.getByRole("tab", { name: "Your GitHub Repos" }).click();
// Should show repositories list
await expect(po.page.getByText("testuser/existing-app")).toBeVisible();
// Click the first Import button in the repo list
await po.page.getByRole("button", { name: "Import" }).first().click();
// Should close modal and navigate to chat
await expect(
po.page.getByRole("heading", { name: "Import App" }),
).not.toBeVisible();
// Verify AI_RULES generation prompt
await po.snapshotMessages();
});
test("should support advanced options with custom commands", async ({ po }) => {
await po.setUp();
// Open modal and connect
await po.page.getByRole("button", { name: "Import App" }).click();
// Go to GitHub URL tab
await po.page.getByRole("tab", { name: "GitHub URL" }).click();
await po.page
.getByPlaceholder("https://github.com/user/repo.git")
.fill("https://github.com/dyad-sh/nextjs-template.git");
// Open advanced options
await po.page.getByRole("button", { name: "Advanced options" }).click();
// Fill one command - should show error
await po.page.getByPlaceholder("pnpm install").fill("npm install");
await expect(
po.page.getByText("Both commands are required when customizing"),
).toBeVisible();
await expect(
po.page.getByRole("button", { name: "Import", exact: true }),
).toBeDisabled();
// Fill both commands
await po.page.getByPlaceholder("pnpm dev").fill("npm start");
await expect(
po.page.getByRole("button", { name: "Import", exact: true }),
).toBeEnabled();
await expect(
po.page.getByText("Both commands are required when customizing"),
).not.toBeVisible();
// Import with custom commands
await po.page.getByRole("button", { name: "Import", exact: true }).click();
await expect(
po.page.getByRole("heading", { name: "Import App" }),
).not.toBeVisible();
});
test("should allow empty commands to use defaults", async ({ po }) => {
await po.setUp();
// Open modal and connect
await po.page.getByRole("button", { name: "Import App" }).click();
// Go to GitHub URL tab
await po.page.getByRole("tab", { name: "GitHub URL" }).click();
await po.page
.getByPlaceholder("https://github.com/user/repo.git")
.fill("https://github.com/dyad-sh/nextjs-template.git");
// Commands are empty by default, so import should be enabled
await expect(
po.page.getByRole("button", { name: "Import", exact: true }),
).toBeEnabled();
await po.page.getByRole("button", { name: "Import", exact: true }).click();
await expect(
po.page.getByRole("heading", { name: "Import App" }),
).not.toBeVisible();
});

View File

@@ -0,0 +1,14 @@
- paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./
- img
- text: file1.txt
- button "Edit":
- img
- img
- text: file1.txt
- paragraph: More EOM
- button:
- img
- img
- text: less than a minute ago
- button "Retry":
- img

View File

@@ -0,0 +1,14 @@
- paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./
- img
- text: file1.txt
- button "Edit":
- img
- img
- text: file1.txt
- paragraph: More EOM
- button:
- img
- img
- text: less than a minute ago
- button "Retry":
- img