From df51d523beb4d6e2fc5b17aa0be2607a1207500b Mon Sep 17 00:00:00 2001 From: Satya Patel Date: Fri, 28 Nov 2025 14:15:42 -0800 Subject: [PATCH 1/2] WIP --- .../lib/trpc/routers/workspaces/utils/git.ts | 34 ++- .../lib/trpc/routers/workspaces/workspaces.ts | 70 +++++- apps/desktop/src/main/lib/db/schemas.ts | 20 ++ apps/desktop/src/shared/ipc-channels/index.ts | 6 +- .../src/shared/ipc-channels/workspace.ts | 224 ------------------ .../src/shared/ipc-channels/worktree.ts | 183 -------------- 6 files changed, 122 insertions(+), 415 deletions(-) delete mode 100644 apps/desktop/src/shared/ipc-channels/workspace.ts delete mode 100644 apps/desktop/src/shared/ipc-channels/worktree.ts diff --git a/apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts b/apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts index fe5fe09e3f0..9f73c42ec97 100644 --- a/apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts +++ b/apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts @@ -50,15 +50,18 @@ export async function createWorktree( mainRepoPath: string, branch: string, worktreePath: string, + startPoint = "origin/main", ): Promise { try { const parentDir = join(worktreePath, ".."); await mkdir(parentDir, { recursive: true }); const git = simpleGit(mainRepoPath); - await git.raw(["worktree", "add", worktreePath, "-b", branch]); + await git.raw(["worktree", "add", worktreePath, "-b", branch, startPoint]); - console.log(`Created worktree at ${worktreePath} with branch ${branch}`); + console.log( + `Created worktree at ${worktreePath} with branch ${branch} from ${startPoint}`, + ); } catch (error) { console.error(`Failed to create worktree: ${error}`); throw new Error(`Failed to create worktree: ${error}`); @@ -114,3 +117,30 @@ export async function worktreeExists( throw error; } } + +/** + * Fetches origin/main and returns the latest commit SHA + * @param mainRepoPath - Path to the main repository + * @returns The commit SHA of origin/main after fetch + */ +export async function fetchOriginMain(mainRepoPath: string): Promise { + const git = simpleGit(mainRepoPath); + await git.fetch("origin", "main"); + const commit = await git.revparse("origin/main"); + return commit.trim(); +} + +/** + * Checks if a worktree's branch is behind origin/main + * @param worktreePath - Path to the worktree + * @returns true if the branch has commits on origin/main that it doesn't have + */ +export async function checkNeedsRebase(worktreePath: string): Promise { + const git = simpleGit(worktreePath); + const behindCount = await git.raw([ + "rev-list", + "--count", + "HEAD..origin/main", + ]); + return Number.parseInt(behindCount.trim(), 10) > 0; +} diff --git a/apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts b/apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts index f911a7b1799..bb3e13e2ba9 100644 --- a/apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts +++ b/apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts @@ -6,7 +6,9 @@ import { SUPERSET_DIR_NAME, WORKTREES_DIR_NAME } from "shared/constants"; import { z } from "zod"; import { publicProcedure, router } from "../.."; import { + checkNeedsRebase, createWorktree, + fetchOriginMain, generateBranchName, removeWorktree, worktreeExists, @@ -39,7 +41,19 @@ export const createWorkspacesRouter = () => { branch, ); - await createWorktree(project.mainRepoPath, branch, worktreePath); + // Fetch origin/main to ensure we're branching from latest (best-effort) + try { + await fetchOriginMain(project.mainRepoPath); + } catch { + // Silently continue - origin/main still exists locally, just might be stale + } + + await createWorktree( + project.mainRepoPath, + branch, + worktreePath, + "origin/main", + ); const worktree = { id: nanoid(), @@ -47,6 +61,11 @@ export const createWorkspacesRouter = () => { path: worktreePath, branch, createdAt: Date.now(), + gitStatus: { + branch, + needsRebase: false, // Fresh off main, doesn't need rebase + lastRefreshed: Date.now(), + }, }; const projectWorkspaces = db.data.workspaces.filter( @@ -426,6 +445,55 @@ export const createWorkspacesRouter = () => { return { success: true }; }), + + refreshGitStatus: publicProcedure + .input(z.object({ workspaceId: z.string() })) + .mutation(async ({ input }) => { + const workspace = db.data.workspaces.find( + (w) => w.id === input.workspaceId, + ); + if (!workspace) { + throw new Error(`Workspace ${input.workspaceId} not found`); + } + + const worktree = db.data.worktrees.find( + (wt) => wt.id === workspace.worktreeId, + ); + if (!worktree) { + throw new Error( + `Worktree for workspace ${input.workspaceId} not found`, + ); + } + + const project = db.data.projects.find( + (p) => p.id === workspace.projectId, + ); + if (!project) { + throw new Error(`Project ${workspace.projectId} not found`); + } + + // Fetch origin/main to get latest + await fetchOriginMain(project.mainRepoPath); + + // Check if worktree branch is behind origin/main + const needsRebase = await checkNeedsRebase(worktree.path); + + const gitStatus = { + branch: worktree.branch, + needsRebase, + lastRefreshed: Date.now(), + }; + + // Update worktree in db + await db.update((data) => { + const wt = data.worktrees.find((w) => w.id === worktree.id); + if (wt) { + wt.gitStatus = gitStatus; + } + }); + + return { gitStatus }; + }), }); }; diff --git a/apps/desktop/src/main/lib/db/schemas.ts b/apps/desktop/src/main/lib/db/schemas.ts index 9b8c69f8d1a..00abfd7f852 100644 --- a/apps/desktop/src/main/lib/db/schemas.ts +++ b/apps/desktop/src/main/lib/db/schemas.ts @@ -8,12 +8,32 @@ export interface Project { createdAt: number; } +export interface GitStatus { + branch: string; + needsRebase: boolean; + lastRefreshed: number; +} + +export interface GitHubStatus { + pr: { + number: number; + title: string; + url: string; + state: "open" | "draft" | "merged" | "closed"; + mergedAt?: number; + } | null; + repoUrl: string; + lastRefreshed: number; +} + export interface Worktree { id: string; projectId: string; path: string; branch: string; createdAt: number; + gitStatus?: GitStatus; + githubStatus?: GitHubStatus; } export interface Workspace { diff --git a/apps/desktop/src/shared/ipc-channels/index.ts b/apps/desktop/src/shared/ipc-channels/index.ts index e10ff37fcff..b0f209d8d61 100644 --- a/apps/desktop/src/shared/ipc-channels/index.ts +++ b/apps/desktop/src/shared/ipc-channels/index.ts @@ -13,8 +13,6 @@ import type { TabChannels } from "./tab"; import type { TerminalChannels } from "./terminal"; import type { UiChannels } from "./ui"; import type { WindowChannels } from "./window"; -import type { WorkspaceChannels } from "./workspace"; -import type { WorktreeChannels } from "./worktree"; // Re-export shared types export type { @@ -28,9 +26,7 @@ export type { * Combine all channel definitions into a single interface */ export interface IpcChannels - extends WorkspaceChannels, - WorktreeChannels, - TabChannels, + extends TabChannels, TerminalChannels, ProxyChannels, ExternalChannels, diff --git a/apps/desktop/src/shared/ipc-channels/workspace.ts b/apps/desktop/src/shared/ipc-channels/workspace.ts deleted file mode 100644 index 8bfdcb9cc69..00000000000 --- a/apps/desktop/src/shared/ipc-channels/workspace.ts +++ /dev/null @@ -1,224 +0,0 @@ -/** - * Workspace-related IPC channels - */ - -import type { - CreateWorkspaceInput, - UpdateWorkspaceInput, - Workspace, -} from "../types"; -import type { IpcResponse, NoRequest } from "./types"; - -export interface WorkspaceChannels { - "workspace-list": { - request: NoRequest; - response: Workspace[]; - }; - - "workspace-get": { - request: string; - response: Workspace | null; - }; - - "workspace-create": { - request: CreateWorkspaceInput; - response: IpcResponse; - }; - - "workspace-update": { - request: UpdateWorkspaceInput; - response: IpcResponse; - }; - - "workspace-delete": { - request: { id: string; removeWorktree?: boolean }; - response: IpcResponse; - }; - - "workspace-get-last-opened": { - request: NoRequest; - response: Workspace | null; - }; - - "workspace-scan-worktrees": { - request: string; - response: { success: boolean; imported?: number; error?: string }; - }; - - "workspace-list-branches": { - request: string; - response: { branches: string[]; currentBranch: string | null }; - }; - - "workspace-set-ports": { - request: { - workspaceId: string; - ports: Array; - }; - response: IpcResponse; - }; - - "workspace-get-detected-ports": { - request: { worktreeId: string }; - response: Record; - }; - - "workspace-update-terminal-cwd": { - request: { - workspaceId: string; - worktreeId: string; - tabId: string; - cwd: string; - }; - response: boolean; - }; - - // Workspace Selection & State - "workspace-get-active-selection": { - request: string; - response: { - worktreeId: string | null; - tabId: string | null; - } | null; - }; - - "workspace-set-active-selection": { - request: { - workspaceId: string; - worktreeId: string | null; - tabId: string | null; - }; - response: boolean; - }; - - "workspace-get-active-workspace-id": { - request: NoRequest; - response: string | null; - }; - - "workspace-set-active-workspace-id": { - request: string; - response: boolean; - }; - - "workspace-get-window-workspace-id": { - request: NoRequest; - response: string | null; - }; - - "workspace-set-window-workspace-id": { - request: string | null; - response: boolean; - }; - - // New architecture: Workspace activation and composition - "workspace-activate": { - request: { workspaceId: string }; - response: IpcResponse<{ - workspace: { - id: string; - type: "local"; - environmentId: string; - path: string; - }; - worktrees: Array<{ - path: string; - branch: string; - currentBranch: string; - bare: boolean; - merged?: boolean; - ui: { - path: string; - branch: string; - description?: string; - prUrl?: string; - merged?: boolean; - tabs: Array<{ - id: string; - name: string; - type: string; - cwd?: string; - url?: string; - command?: string | null; - tabs?: unknown[]; - mosaicTree?: unknown; - createdAt: string; - }>; - mosaicTree?: unknown; - activeTabId: string | null; - updatedAt: string; - }; - }>; - ui: { - activeWorktreePath: string | null; - activeTabId: string | null; - }; - }>; - }; - - "workspace-rescan": { - request: { workspaceId: string }; - response: IpcResponse<{ - added: Array<{ - path: string; - branch: string; - bare: boolean; - currentBranch: string; - merged?: boolean; - }>; - removed: Array<{ - path: string; - branch: string; - bare: boolean; - currentBranch: string; - merged?: boolean; - }>; - changed: Array<{ - old: { - path: string; - branch: string; - bare: boolean; - currentBranch: string; - merged?: boolean; - }; - new: { - path: string; - branch: string; - bare: boolean; - currentBranch: string; - merged?: boolean; - }; - }>; - state: { - workspace: { - id: string; - type: "local"; - environmentId: string; - path: string; - }; - worktrees: Array<{ - path: string; - branch: string; - currentBranch: string; - bare: boolean; - merged?: boolean; - ui: { - path: string; - branch: string; - description?: string; - prUrl?: string; - merged?: boolean; - tabs: unknown[]; - mosaicTree?: unknown; - activeTabId: string | null; - updatedAt: string; - }; - }>; - ui: { - activeWorktreePath: string | null; - activeTabId: string | null; - }; - }; - }>; - }; -} diff --git a/apps/desktop/src/shared/ipc-channels/worktree.ts b/apps/desktop/src/shared/ipc-channels/worktree.ts deleted file mode 100644 index 26ad42a9bea..00000000000 --- a/apps/desktop/src/shared/ipc-channels/worktree.ts +++ /dev/null @@ -1,183 +0,0 @@ -/** - * Worktree-related IPC channels - */ - -import type { CreateWorktreeInput, Worktree } from "../types"; -import type { IpcResponse, SuccessResponse } from "./types"; - -export interface WorktreeChannels { - "worktree-create": { - request: CreateWorktreeInput; - response: { - success: boolean; - worktree?: Worktree; - error?: string; - }; - }; - - "worktree-remove": { - request: { workspaceId: string; worktreeId: string }; - response: IpcResponse; - }; - - "worktree-get-path": { - request: { workspaceId: string; worktreeId: string }; - response: string | null; - }; - - "worktree-update-description": { - request: { - workspaceId: string; - worktreeId: string; - description: string; - }; - response: IpcResponse; - }; - - // Worktree Merge Operations - "worktree-can-merge": { - request: { - workspaceId: string; - worktreeId: string; - targetWorktreeId?: string; - }; - response: { - canMerge: boolean; - reason?: string; - isActiveWorktree?: boolean; - hasUncommittedChanges?: boolean; - targetHasUncommittedChanges?: boolean; - sourceHasUncommittedChanges?: boolean; - }; - }; - - "worktree-merge": { - request: { - workspaceId: string; - worktreeId: string; - targetWorktreeId?: string; - }; - response: IpcResponse; - }; - - "worktree-can-remove": { - request: { workspaceId: string; worktreeId: string }; - response: { - success: boolean; - canRemove?: boolean; - hasUncommittedChanges?: boolean; - error?: string; - }; - }; - - // Worktree Settings - "worktree-check-settings": { - request: { workspaceId: string; worktreeId: string }; - response: { success: boolean; exists?: boolean; error?: string }; - }; - - "worktree-open-settings": { - request: { - workspaceId: string; - worktreeId: string; - createIfMissing?: boolean; - }; - response: { success: boolean; created?: boolean; error?: string }; - }; - - // Worktree Git Operations - "worktree-get-git-status": { - request: { workspaceId: string; worktreeId: string }; - response: { - success: boolean; - status?: { - branch: string; - ahead: number; - behind: number; - files: { - staged: Array<{ path: string; status: string }>; - unstaged: Array<{ path: string; status: string }>; - untracked: Array<{ path: string }>; - }; - diffAgainstMain: string; - isMerging: boolean; - isRebasing: boolean; - conflictFiles: string[]; - }; - error?: string; - }; - }; - - "worktree-get-git-diff": { - request: { workspaceId: string; worktreeId: string }; - response: { - success: boolean; - diff?: { - files: Array<{ - id: string; - fileName: string; - filePath: string; - status: "added" | "deleted" | "modified" | "renamed"; - oldPath?: string; - additions: number; - deletions: number; - changes: Array<{ - type: "added" | "removed" | "modified" | "unchanged"; - oldLineNumber: number | null; - newLineNumber: number | null; - content: string; - }>; - }>; - }; - error?: string; - }; - }; - - "worktree-get-git-diff-file-list": { - request: { workspaceId: string; worktreeId: string }; - response: { - success: boolean; - files?: Array<{ - id: string; - fileName: string; - filePath: string; - status: "added" | "deleted" | "modified" | "renamed"; - oldPath?: string; - additions: number; - deletions: number; - }>; - error?: string; - }; - }; - - "worktree-get-git-diff-file": { - request: { - workspaceId: string; - worktreeId: string; - filePath: string; - oldPath?: string; - status: "added" | "deleted" | "modified" | "renamed"; - }; - response: { - success: boolean; - changes?: Array<{ - type: "added" | "removed" | "modified" | "unchanged"; - oldLineNumber: number | null; - newLineNumber: number | null; - content: string; - }>; - error?: string; - }; - }; - - // Worktree PR Operations - "worktree-create-pr": { - request: { workspaceId: string; worktreeId: string }; - response: { success: boolean; prUrl?: string; error?: string }; - }; - - "worktree-merge-pr": { - request: { workspaceId: string; worktreeId: string }; - response: SuccessResponse; - }; -} From 3b7525d6d77f9e193e0fedd6f17b5df3f620f6e7 Mon Sep 17 00:00:00 2001 From: Satya Patel Date: Fri, 28 Nov 2025 20:51:13 -0800 Subject: [PATCH 2/2] WIP --- superset-teardown.sh | 47 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/superset-teardown.sh b/superset-teardown.sh index 063fd74aab6..58441d6554a 100755 --- a/superset-teardown.sh +++ b/superset-teardown.sh @@ -8,19 +8,48 @@ NC='\033[0m' error() { echo -e "${RED}โœ—${NC} $1"; exit 1; } success() { echo -e "${GREEN}โœ“${NC} $1"; } -echo "๐Ÿงน Tearing down Superset workspace..." +echo "๐Ÿš€ Setting up Superset workspace..." # Check dependencies +command -v bun &> /dev/null || error "Bun not installed. Install from https://bun.sh" command -v neonctl &> /dev/null || error "Neon CLI not installed. Run: npm install -g neonctl" +command -v jq &> /dev/null || error "jq not installed. Run: brew install jq" -# Delete Neon branch for this workspace -WORKSPACE_NAME="${SUPERSET_WORKSPACE_NAME:-$(basename "$PWD")}" +# Install dependencies +echo "๐Ÿ“ฅ Installing dependencies..." +bun install +success "Dependencies installed" -echo "๐Ÿ—„๏ธ Deleting Neon branch: $WORKSPACE_NAME" -if neonctl branches delete "$WORKSPACE_NAME" --project-id tiny-cherry-82420694 --force 2>/dev/null; then - success "Neon branch deleted: $WORKSPACE_NAME" -else - echo "โš ๏ธ Neon branch '$WORKSPACE_NAME' not found or already deleted" +# Link direnv config from root repo if it exists +if [ -n "$SUPERSET_ROOT_PATH" ] && [ -f "$SUPERSET_ROOT_PATH/.envrc" ]; then + echo "๐Ÿ”ง Linking .envrc..." + ln -sf "$SUPERSET_ROOT_PATH/.envrc" .envrc + if command -v direnv &> /dev/null; then + direnv allow + fi + success "direnv configured" fi -echo "โœจ Teardown complete!" +# Create Neon branch for this workspace +echo "๐Ÿ—„๏ธ Creating Neon branch..." +WORKSPACE_NAME="${SUPERSET_WORKSPACE_NAME:-$(basename "$PWD")}" +NEON_OUTPUT=$(neonctl branches create \ + --project-id tiny-cherry-82420694 \ + --name "$WORKSPACE_NAME" \ + --output json 2>&1 | grep -v "^WARNING:") + +# Parse connection strings from create output +DATABASE_URL=$(echo "$NEON_OUTPUT" | jq -r '.connection_uris[0].connection_uri') +POOLER_HOST=$(echo "$NEON_OUTPUT" | jq -r '.connection_uris[0].connection_parameters.pooler_host') +PASSWORD=$(echo "$NEON_OUTPUT" | jq -r '.connection_uris[0].connection_parameters.password') +ROLE=$(echo "$NEON_OUTPUT" | jq -r '.connection_uris[0].connection_parameters.role') +DATABASE=$(echo "$NEON_OUTPUT" | jq -r '.connection_uris[0].connection_parameters.database') +DATABASE_POOLED_URL="postgresql://${ROLE}:${PASSWORD}@${POOLER_HOST}/${DATABASE}?sslmode=require" + +cat > .env << EOF +DATABASE_URL=$DATABASE_URL +DATABASE_POOLED_URL=$DATABASE_POOLED_URL +EOF + +success "Neon branch created: $WORKSPACE_NAME" +echo "โœจ Done!"