diff --git a/.superset/setup.json b/.superset/setup.json index 901d9f3dd52..936f2ddf3b6 100644 --- a/.superset/setup.json +++ b/.superset/setup.json @@ -1,8 +1,5 @@ { - "copy": [ - "**/.env*" - ], "commands": [ - "bun i" + "./superset-setup.sh" ] } \ No newline at end of file diff --git a/apps/desktop/src/lib/trpc/routers/terminal/terminal.ts b/apps/desktop/src/lib/trpc/routers/terminal/terminal.ts index 2a41ebc6471..0c8dc740b1b 100644 --- a/apps/desktop/src/lib/trpc/routers/terminal/terminal.ts +++ b/apps/desktop/src/lib/trpc/routers/terminal/terminal.ts @@ -31,6 +31,7 @@ export const createTerminalRouter = () => { cols: z.number().optional(), rows: z.number().optional(), cwd: z.string().optional(), + initialCommands: z.array(z.string()).optional(), }), ) .mutation(async ({ input }) => { @@ -41,6 +42,7 @@ export const createTerminalRouter = () => { cols, rows, cwd: cwdOverride, + initialCommands, } = input; // Get workspace to determine cwd and workspace name @@ -50,16 +52,29 @@ export const createTerminalRouter = () => { cwdOverride || (workspace ? getWorktreePath(workspace.worktreeId) : undefined); + // Get project to get root path for setup scripts + const project = workspace + ? db.data.projects.find((p) => p.id === workspace.projectId) + : undefined; + const rootPath = project?.mainRepoPath; + const result = await terminalManager.createOrAttach({ tabId, workspaceId, tabTitle, workspaceName, + rootPath, cwd, cols, rows, }); + // Run initial commands on new terminals + if (result.isNew && initialCommands && initialCommands.length > 0) { + const commandString = `${initialCommands.join(" && ")}\n`; + terminalManager.write({ tabId, data: commandString }); + } + return { tabId, isNew: result.isNew, diff --git a/apps/desktop/src/lib/trpc/routers/workspaces/utils/setup.test.ts b/apps/desktop/src/lib/trpc/routers/workspaces/utils/setup.test.ts index a74dd9c53db..9e572242d61 100644 --- a/apps/desktop/src/lib/trpc/routers/workspaces/utils/setup.test.ts +++ b/apps/desktop/src/lib/trpc/routers/workspaces/utils/setup.test.ts @@ -1,11 +1,10 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test"; import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; import { join } from "node:path"; -import { copySetupFiles, loadSetupConfig } from "./setup"; +import { loadSetupConfig } from "./setup"; const TEST_DIR = join(__dirname, ".test-tmp"); const MAIN_REPO = join(TEST_DIR, "main-repo"); -const WORKTREE = join(TEST_DIR, "worktree"); describe("loadSetupConfig", () => { beforeEach(() => { @@ -27,7 +26,6 @@ describe("loadSetupConfig", () => { test("loads valid setup config", () => { const setupConfig = { - copy: ["*.env", "package.json"], commands: ["npm install", "npm run build"], }; @@ -47,79 +45,13 @@ describe("loadSetupConfig", () => { expect(config).toBeNull(); }); - test("validates copy field must be an array", () => { + test("validates commands field must be an array", () => { writeFileSync( join(MAIN_REPO, ".superset", "setup.json"), - JSON.stringify({ copy: "not-an-array" }), + JSON.stringify({ commands: "not-an-array" }), ); const config = loadSetupConfig(MAIN_REPO); expect(config).toBeNull(); }); }); - -describe("copySetupFiles", () => { - beforeEach(() => { - // Create test directories - mkdirSync(MAIN_REPO, { recursive: true }); - mkdirSync(WORKTREE, { recursive: true }); - }); - - afterEach(() => { - // Clean up - if (existsSync(TEST_DIR)) { - rmSync(TEST_DIR, { recursive: true, force: true }); - } - }); - - test("returns empty result for empty patterns", async () => { - const result = await copySetupFiles(MAIN_REPO, WORKTREE, []); - expect(result.copied).toEqual([]); - expect(result.errors).toEqual([]); - }); - - test("copies matching files", async () => { - // Create test files - writeFileSync(join(MAIN_REPO, "test.txt"), "test content"); - writeFileSync(join(MAIN_REPO, "README.md"), "readme"); - - const result = await copySetupFiles(MAIN_REPO, WORKTREE, ["*.txt"]); - - expect(result.copied).toContain("test.txt"); - expect(result.errors).toEqual([]); - expect(existsSync(join(WORKTREE, "test.txt"))).toBe(true); - }); - - test("creates nested directories", async () => { - // Create nested file - mkdirSync(join(MAIN_REPO, "src"), { recursive: true }); - writeFileSync(join(MAIN_REPO, "src", "index.ts"), "export {}"); - - const result = await copySetupFiles(MAIN_REPO, WORKTREE, ["src/**/*.ts"]); - - expect(result.copied).toContain("src/index.ts"); - expect(existsSync(join(WORKTREE, "src", "index.ts"))).toBe(true); - }); - - test("reports errors for files that don't match", async () => { - const result = await copySetupFiles(MAIN_REPO, WORKTREE, [ - "nonexistent.txt", - ]); - - expect(result.copied).toEqual([]); - expect(result.errors.length).toBeGreaterThan(0); - }); - - test("copies multiple files matching glob pattern", async () => { - writeFileSync(join(MAIN_REPO, "file1.txt"), "content1"); - writeFileSync(join(MAIN_REPO, "file2.txt"), "content2"); - writeFileSync(join(MAIN_REPO, "file.md"), "markdown"); - - const result = await copySetupFiles(MAIN_REPO, WORKTREE, ["*.txt"]); - - expect(result.copied).toContain("file1.txt"); - expect(result.copied).toContain("file2.txt"); - expect(result.copied).not.toContain("file.md"); - expect(result.errors).toEqual([]); - }); -}); diff --git a/apps/desktop/src/lib/trpc/routers/workspaces/utils/setup.ts b/apps/desktop/src/lib/trpc/routers/workspaces/utils/setup.ts index 76b51eed2ef..ebbcac90aec 100644 --- a/apps/desktop/src/lib/trpc/routers/workspaces/utils/setup.ts +++ b/apps/desktop/src/lib/trpc/routers/workspaces/utils/setup.ts @@ -1,7 +1,5 @@ import { existsSync, readFileSync } from "node:fs"; -import { copyFile, mkdir } from "node:fs/promises"; -import { dirname, join } from "node:path"; -import fg from "fast-glob"; +import { join } from "node:path"; import type { SetupConfig } from "shared/types"; export function loadSetupConfig(mainRepoPath: string): SetupConfig | null { @@ -15,10 +13,6 @@ export function loadSetupConfig(mainRepoPath: string): SetupConfig | null { const content = readFileSync(configPath, "utf-8"); const parsed = JSON.parse(content) as SetupConfig; - if (parsed.copy && !Array.isArray(parsed.copy)) { - throw new Error("'copy' field must be an array of strings"); - } - if (parsed.commands && !Array.isArray(parsed.commands)) { throw new Error("'commands' field must be an array of strings"); } @@ -31,50 +25,3 @@ export function loadSetupConfig(mainRepoPath: string): SetupConfig | null { return null; } } - -export async function copySetupFiles( - mainRepoPath: string, - worktreePath: string, - patterns: string[], -): Promise<{ copied: string[]; errors: string[] }> { - const copied: string[] = []; - const errors: string[] = []; - - for (const pattern of patterns) { - try { - const matches = await fg(pattern, { - cwd: mainRepoPath, - dot: true, - followSymbolicLinks: false, - onlyFiles: true, - ignore: [".superset/**"], - }); - - if (matches.length === 0) { - errors.push(`No files matched pattern: ${pattern}`); - continue; - } - - for (const relativePath of matches) { - const sourcePath = join(mainRepoPath, relativePath); - const destinationPath = join(worktreePath, relativePath); - - try { - await mkdir(dirname(destinationPath), { recursive: true }); - await copyFile(sourcePath, destinationPath); - copied.push(relativePath); - } catch (copyError) { - errors.push( - `Failed to copy ${relativePath}: ${copyError instanceof Error ? copyError.message : String(copyError)}`, - ); - } - } - } catch (globError) { - errors.push( - `Failed to process pattern '${pattern}': ${globError instanceof Error ? globError.message : String(globError)}`, - ); - } - } - - return { copied, errors }; -} diff --git a/apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts b/apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts index 8980c8f93eb..c3a4bed44a7 100644 --- a/apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts +++ b/apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts @@ -11,7 +11,7 @@ import { removeWorktree, worktreeExists, } from "./utils/git"; -import { copySetupFiles, loadSetupConfig } from "./utils/setup"; +import { loadSetupConfig } from "./utils/setup"; import { getWorktreePath } from "./utils/worktree"; export const createWorkspacesRouter = () => { @@ -92,33 +92,10 @@ export const createWorkspacesRouter = () => { // Load setup configuration const setupConfig = loadSetupConfig(project.mainRepoPath); - let setupCopyResults: { copied: string[]; errors: string[] } | null = - null; - - // Copy setup files if config exists and has copy patterns - if (setupConfig?.copy && setupConfig.copy.length > 0) { - try { - setupCopyResults = await copySetupFiles( - project.mainRepoPath, - worktreePath, - setupConfig.copy, - ); - } catch (error) { - console.error("Failed to copy setup files:", error); - // Non-fatal: return error info but continue - setupCopyResults = { - copied: [], - errors: [ - `Setup file copy failed: ${error instanceof Error ? error.message : String(error)}`, - ], - }; - } - } return { workspace, - setupConfig: setupConfig?.commands || null, - setupCopyResults, + initialCommands: setupConfig?.commands || null, worktreePath, }; }), diff --git a/apps/desktop/src/main/lib/terminal-manager.ts b/apps/desktop/src/main/lib/terminal-manager.ts index 8bcbaf50917..893f7b65756 100644 --- a/apps/desktop/src/main/lib/terminal-manager.ts +++ b/apps/desktop/src/main/lib/terminal-manager.ts @@ -45,6 +45,7 @@ export class TerminalManager extends EventEmitter { workspaceId: string; tabTitle: string; workspaceName: string; + rootPath?: string; cwd?: string; cols?: number; rows?: number; @@ -53,8 +54,16 @@ export class TerminalManager extends EventEmitter { scrollback: string; wasRecovered: boolean; }> { - const { tabId, workspaceId, tabTitle, workspaceName, cwd, cols, rows } = - params; + const { + tabId, + workspaceId, + tabTitle, + workspaceName, + rootPath, + cwd, + cols, + rows, + } = params; const existing = this.sessions.get(tabId); if (existing?.isAlive) { @@ -85,6 +94,8 @@ export class TerminalManager extends EventEmitter { SUPERSET_TAB_TITLE: tabTitle, SUPERSET_WORKSPACE_NAME: workspaceName, SUPERSET_WORKSPACE_ID: workspaceId, + SUPERSET_WORKSPACE_PATH: workingDir, + SUPERSET_ROOT_PATH: rootPath || "", SUPERSET_PORT: String(PORTS.NOTIFICATIONS), }; diff --git a/apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts b/apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts index bb09d6abad4..5db3af70a30 100644 --- a/apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts +++ b/apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts @@ -4,13 +4,14 @@ import { useTabsStore } from "renderer/stores/tabs/store"; /** * Mutation hook for creating a new workspace * Automatically invalidates all workspace queries on success - * Creates a setup tab if setup commands are present + * Creates a terminal tab with setup commands if present */ export function useCreateWorkspace( options?: Parameters[0], ) { const utils = trpc.useUtils(); - const addSetupTab = useTabsStore((state) => state.addSetupTab); + const addTab = useTabsStore((state) => state.addTab); + const createOrAttach = trpc.terminal.createOrAttach.useMutation(); return trpc.workspaces.create.useMutation({ ...options, @@ -18,14 +19,20 @@ export function useCreateWorkspace( // Auto-invalidate all workspace queries await utils.workspaces.invalidate(); - // Create setup tab if setup commands are present and is an array - if (Array.isArray(data.setupConfig) && data.setupConfig.length > 0) { - addSetupTab( - data.workspace.id, - data.setupConfig, - data.worktreePath, - data.setupCopyResults ?? undefined, - ); + // Create terminal tab with setup commands if present + if ( + Array.isArray(data.initialCommands) && + data.initialCommands.length > 0 + ) { + const tabId = addTab(data.workspace.id); + // Pre-create terminal session with initial commands + // Terminal component will attach to this session when it mounts + createOrAttach.mutate({ + tabId, + workspaceId: data.workspace.id, + tabTitle: "Terminal", + initialCommands: data.initialCommands, + }); } // Call user's onSuccess if provided diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SetupTabView.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SetupTabView.tsx deleted file mode 100644 index 014a12eb026..00000000000 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SetupTabView.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import type { SetupTab } from "renderer/stores"; -import { SetupTerminal } from "./SetupTerminal"; - -interface SetupTabViewProps { - tab: SetupTab; -} - -export function SetupTabView({ tab }: SetupTabViewProps) { - return ( -
- -
- ); -} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SetupTerminal/SetupTerminal.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SetupTerminal/SetupTerminal.tsx deleted file mode 100644 index d6c04a37c1c..00000000000 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SetupTerminal/SetupTerminal.tsx +++ /dev/null @@ -1,237 +0,0 @@ -import "@xterm/xterm/css/xterm.css"; -import type { FitAddon } from "@xterm/addon-fit"; -import type { Terminal as XTerm } from "@xterm/xterm"; -import { useEffect, useRef, useState } from "react"; -import { trpc } from "renderer/lib/trpc"; -import { useTabsStore, useTerminalTheme } from "renderer/stores"; -import { - createTerminalInstance, - getDefaultTerminalBg, - setupResizeHandlers, -} from "../Terminal/helpers"; -import type { TerminalStreamEvent } from "../Terminal/types"; - -const AUTO_CLOSE_DELAY = 1500; -const SETUP_TAB_TITLE = "Setup Worktree"; - -const MESSAGES = { - SETUP_HEADER: "\x1b[1m\x1b[34mSetting up worktree...\x1b[0m\r\n", - COMMANDS_HEADER: "\x1b[1mRunning setup commands:\x1b[0m", - SUCCESS: "\r\n\x1b[32m✓ Setup completed successfully!\x1b[0m", - FAILURE: "\r\n\x1b[31m✗ Setup failed\x1b[0m", - FAILURE_HINT: "Please check the errors above.", - CLOSING: "Closing tab...", - FILES_COPIED: (count: number) => `\x1b[32m✓ Copied ${count} file(s):\x1b[0m`, - COPY_WARNINGS: "\r\n\x1b[33m⚠ Copy warnings:\x1b[0m", - TERMINAL_ERROR: "\r\n\x1b[31m✗ Failed to create terminal session\x1b[0m", - ERROR_DETAILS: (message: string) => `\x1b[31mError: ${message}\x1b[0m`, -} as const; - -interface SetupTerminalProps { - tabId: string; - workspaceId: string; - setupCommands: string[]; - setupCwd: string; - setupCopyResults?: { copied: string[]; errors: string[] }; -} - -export const SetupTerminal = ({ - tabId, - workspaceId, - setupCommands, - setupCwd, - setupCopyResults, -}: SetupTerminalProps) => { - const terminalRef = useRef(null); - const xtermRef = useRef(null); - const fitAddonRef = useRef(null); - const setupExecutedRef = useRef(false); - const [subscriptionEnabled, setSubscriptionEnabled] = useState(false); - - const removeTab = useTabsStore((state) => state.removeTab); - const terminalTheme = useTerminalTheme(); - const { data: workspaceCwd } = - trpc.terminal.getWorkspaceCwd.useQuery(workspaceId); - - const createOrAttachMutation = trpc.terminal.createOrAttach.useMutation(); - const writeMutation = trpc.terminal.write.useMutation(); - const resizeMutation = trpc.terminal.resize.useMutation(); - const detachMutation = trpc.terminal.detach.useMutation(); - - // Store mutation functions in refs to avoid infinite loops - const createOrAttachRef = useRef(createOrAttachMutation.mutate); - const writeRef = useRef(writeMutation.mutate); - const resizeRef = useRef(resizeMutation.mutate); - const detachRef = useRef(detachMutation.mutate); - - // Update refs on every render to capture latest mutation functions - createOrAttachRef.current = createOrAttachMutation.mutate; - writeRef.current = writeMutation.mutate; - resizeRef.current = resizeMutation.mutate; - detachRef.current = detachMutation.mutate; - - const handleStreamData = (event: TerminalStreamEvent) => { - const xterm = xtermRef.current; - if (!xterm || !subscriptionEnabled) return; - - if (event.type === "data") { - xterm.write(event.data); - return; - } - - if (event.type === "exit") { - xterm.writeln(`\r\n\r\n[Process exited with code ${event.exitCode}]`); - - if (event.exitCode === 0) { - xterm.writeln(MESSAGES.SUCCESS); - xterm.writeln(MESSAGES.CLOSING); - setTimeout(() => removeTab(tabId), AUTO_CLOSE_DELAY); - } else { - xterm.writeln(MESSAGES.FAILURE); - xterm.writeln(MESSAGES.FAILURE_HINT); - } - - setSubscriptionEnabled(false); - } - }; - - trpc.terminal.stream.useSubscription(tabId, { - onData: handleStreamData, - enabled: true, - }); - - useEffect(() => { - const container = terminalRef.current; - if (!container) return; - - const { xterm, fitAddon } = createTerminalInstance( - container, - workspaceCwd, - terminalTheme, - ); - xtermRef.current = xterm; - fitAddonRef.current = fitAddon; - - // Display setup information - xterm.writeln(MESSAGES.SETUP_HEADER); - - if (setupCopyResults) { - const { copied, errors } = setupCopyResults; - - if (copied.length > 0) { - xterm.writeln(MESSAGES.FILES_COPIED(copied.length)); - for (const file of copied) { - xterm.writeln(` - ${file}`); - } - } - - if (errors.length > 0) { - xterm.writeln(MESSAGES.COPY_WARNINGS); - for (const error of errors) { - xterm.writeln(` ${error}`); - } - } - - xterm.writeln("\r"); - } - - xterm.writeln(MESSAGES.COMMANDS_HEADER); - for (const cmd of setupCommands) { - xterm.writeln(` $ ${cmd}`); - } - xterm.writeln("\r"); - - // Create terminal session and execute setup commands - createOrAttachRef.current( - { - tabId, - workspaceId, - tabTitle: SETUP_TAB_TITLE, - cols: xterm.cols, - rows: xterm.rows, - cwd: setupCwd, - }, - { - onSuccess: () => { - setSubscriptionEnabled(true); - - if (!setupExecutedRef.current) { - setupExecutedRef.current = true; - const commands = `${setupCommands.join("\n")}\nexit\n`; - writeRef.current({ tabId, data: commands }); - } - }, - onError: (error) => { - setSubscriptionEnabled(true); - - // Display error message in terminal - xterm.writeln(MESSAGES.TERMINAL_ERROR); - - // Include error details if available - const errorMessage = - error instanceof Error - ? error.message - : typeof error === "string" - ? error - : "Unknown error occurred"; - - xterm.writeln(MESSAGES.ERROR_DETAILS(errorMessage)); - xterm.writeln( - "\r\n\x1b[33mPlease check your workspace configuration and try again.\x1b[0m", - ); - }, - }, - ); - - // Disable user input (read-only display) - const inputDisposable = xterm.onData(() => {}); - - const cleanupResize = setupResizeHandlers( - container, - xterm, - fitAddon, - (cols, rows) => { - resizeRef.current({ tabId, cols, rows }); - }, - ); - - return () => { - inputDisposable.dispose(); - cleanupResize(); - detachRef.current({ tabId }); - setSubscriptionEnabled(false); - xterm.dispose(); - xtermRef.current = null; - }; - }, [ - tabId, - workspaceId, - setupCommands, - setupCwd, - setupCopyResults, - workspaceCwd, - terminalTheme, - ]); - - // Update terminal theme when it changes - useEffect(() => { - const xterm = xtermRef.current; - if (!xterm || !terminalTheme) return; - - // Set theme via property setter - preserves all other options - // xterm.js v5 uses setters that trigger internal repaint - xterm.options.theme = terminalTheme; - }, [terminalTheme]); - - // Get terminal background color from theme, with theme-aware default - const terminalBg = terminalTheme?.background ?? getDefaultTerminalBg(); - - return ( -
-
-
- ); -}; diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SetupTerminal/index.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SetupTerminal/index.ts deleted file mode 100644 index 77667c93ec5..00000000000 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SetupTerminal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { SetupTerminal } from "./SetupTerminal"; diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx index 1e53b53e68d..d25fc6b90dc 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx @@ -1,11 +1,9 @@ import { useMemo } from "react"; import { trpc } from "renderer/lib/trpc"; -import type { SetupTab } from "renderer/stores"; import { TabType, useActiveTabIds, useTabs } from "renderer/stores"; import { DropOverlay } from "./DropOverlay"; import { EmptyTabView } from "./EmptyTabView"; import { GroupTabView } from "./GroupTabView"; -import { SetupTabView } from "./SetupTabView"; import { SingleTabView } from "./SingleTabView"; import { useTabContentDrop } from "./useTabContentDrop"; @@ -31,42 +29,18 @@ export function TabsContent() { return activeTab; }, [activeWorkspaceId, activeTabIds, allTabs]); - // Get all setup tabs to keep them mounted (so they can complete and auto-close) - const setupTabs = useMemo(() => { - if (!activeWorkspaceId) return []; - return allTabs.filter( - (tab): tab is SetupTab => - tab.type === TabType.Setup && tab.workspaceId === activeWorkspaceId, - ); - }, [allTabs, activeWorkspaceId]); - const { isDropZone, attachDrop } = useTabContentDrop(tabToRender); if (!tabToRender) { return (
- {/* Keep setup tabs mounted so they can complete and auto-close */} - {setupTabs.map((tab) => ( -
- -
- ))}
); } return (
- {/* Keep all setup tabs mounted (hidden when not active) so they can complete and auto-close */} - {setupTabs.map((tab) => ( -
- -
- ))} {tabToRender.type === TabType.Single && ( )} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx index 26f4acc63f0..b2a9eabcac1 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx @@ -1,6 +1,5 @@ import { Button } from "@superset/ui/button"; import { useState } from "react"; -import { CgSpinner } from "react-icons/cg"; import { HiChevronRight, HiMiniXMark } from "react-icons/hi2"; import { trpc } from "renderer/lib/trpc"; import { @@ -83,7 +82,6 @@ export function TabItem({ tab, childTabs = [] }: TabItemProps) { }; const isGroupTab = tab.type === TabType.Group; - const isSetupTab = tab.type === TabType.Setup; const hasChildren = childTabs.length > 0; return ( @@ -133,9 +131,6 @@ export function TabItem({ tab, childTabs = [] }: TabItemProps) { /> )} - {isSetupTab && ( - - )} {rename.isRenaming ? ( => { +): { newState: Partial; tabId: string } => { const newTab = createNewTab(workspaceId, type, state.tabs); const currentActiveId = state.activeTabIds[workspaceId]; const historyStack = state.tabHistoryStacks[workspaceId] || []; @@ -16,15 +16,18 @@ export const handleAddTab = ( : historyStack; return { - tabs: [newTab, ...state.tabs], - activeTabIds: { - ...state.activeTabIds, - [workspaceId]: newTab.id, - }, - tabHistoryStacks: { - ...state.tabHistoryStacks, - [workspaceId]: newHistoryStack, + newState: { + tabs: [newTab, ...state.tabs], + activeTabIds: { + ...state.activeTabIds, + [workspaceId]: newTab.id, + }, + tabHistoryStacks: { + ...state.tabHistoryStacks, + [workspaceId]: newHistoryStack, + }, }, + tabId: newTab.id, }; }; @@ -94,39 +97,3 @@ export const handleMarkTabAsUsed = ( ), }; }; - -export const handleAddSetupTab = ( - state: TabsState, - workspaceId: string, - setupCommands: string[], - setupCwd: string, - setupCopyResults?: { copied: string[]; errors: string[] }, -): Partial => { - const baseTab = createNewTab(workspaceId, TabType.Setup, state.tabs); - const setupTab = { - ...baseTab, - type: TabType.Setup as const, - title: "Setup Worktree", - setupCommands, - setupCwd, - setupCopyResults, - }; - - const currentActiveId = state.activeTabIds[workspaceId]; - const historyStack = state.tabHistoryStacks[workspaceId] || []; - const newHistoryStack = currentActiveId - ? [currentActiveId, ...historyStack.filter((id) => id !== currentActiveId)] - : historyStack; - - return { - tabs: [setupTab, ...state.tabs], - activeTabIds: { - ...state.activeTabIds, - [workspaceId]: setupTab.id, - }, - tabHistoryStacks: { - ...state.tabHistoryStacks, - [workspaceId]: newHistoryStack, - }, - }; -}; diff --git a/apps/desktop/src/renderer/stores/tabs/store.ts b/apps/desktop/src/renderer/stores/tabs/store.ts index 2b839aa3396..fc8dd848b9c 100644 --- a/apps/desktop/src/renderer/stores/tabs/store.ts +++ b/apps/desktop/src/renderer/stores/tabs/store.ts @@ -20,7 +20,6 @@ import { handleSplitTabVertical, } from "./helpers/split-operations"; import { - handleAddSetupTab, handleAddTab, handleMarkTabAsUsed, handleRemoveTab, @@ -42,24 +41,13 @@ export const useTabsStore = create()( tabHistoryStacks: {}, addTab: (workspaceId, type = TabType.Single) => { - set((state) => handleAddTab(state, workspaceId, type)); - }, - - addSetupTab: ( - workspaceId, - setupCommands, - setupCwd, - setupCopyResults, - ) => { - set((state) => - handleAddSetupTab( - state, - workspaceId, - setupCommands, - setupCwd, - setupCopyResults, - ), - ); + let tabId = ""; + set((state) => { + const result = handleAddTab(state, workspaceId, type); + tabId = result.tabId; + return result.newState; + }); + return tabId; }, removeTab: (id) => { diff --git a/apps/desktop/src/renderer/stores/tabs/types.ts b/apps/desktop/src/renderer/stores/tabs/types.ts index 12e40d2bacc..aceeda08336 100644 --- a/apps/desktop/src/renderer/stores/tabs/types.ts +++ b/apps/desktop/src/renderer/stores/tabs/types.ts @@ -3,7 +3,6 @@ import type { MosaicBranch, MosaicNode } from "react-mosaic-component"; export enum TabType { Single = "single", Group = "group", - Setup = "setup", } interface BaseTab { @@ -24,14 +23,7 @@ export interface TabGroup extends BaseTab { layout: MosaicNode | null; } -export interface SetupTab extends BaseTab { - type: TabType.Setup; - setupCommands: string[]; - setupCwd: string; - setupCopyResults?: { copied: string[]; errors: string[] }; -} - -export type Tab = SingleTab | TabGroup | SetupTab; +export type Tab = SingleTab | TabGroup; export interface TabsState { tabs: Tab[]; @@ -40,13 +32,7 @@ export interface TabsState { } export interface TabsStore extends TabsState { - addTab: (workspaceId: string, type?: TabType) => void; - addSetupTab: ( - workspaceId: string, - setupCommands: string[], - setupCwd: string, - setupCopyResults?: { copied: string[]; errors: string[] }, - ) => void; + addTab: (workspaceId: string, type?: TabType) => string; removeTab: (id: string) => void; renameTab: (id: string, newTitle: string) => void; setActiveTab: (workspaceId: string, tabId: string) => void; diff --git a/apps/desktop/src/shared/ipc-channels/worktree.ts b/apps/desktop/src/shared/ipc-channels/worktree.ts index bf4f1a991bd..26ad42a9bea 100644 --- a/apps/desktop/src/shared/ipc-channels/worktree.ts +++ b/apps/desktop/src/shared/ipc-channels/worktree.ts @@ -11,7 +11,6 @@ export interface WorktreeChannels { response: { success: boolean; worktree?: Worktree; - setupResult?: import("../types").SetupResult; error?: string; }; }; diff --git a/apps/desktop/src/shared/types.ts b/apps/desktop/src/shared/types.ts index 4a4a06e557a..efd54242694 100644 --- a/apps/desktop/src/shared/types.ts +++ b/apps/desktop/src/shared/types.ts @@ -128,16 +128,9 @@ export interface UpdateWorkspaceInput { // Setup script configuration export interface SetupConfig { - copy?: string[]; // File patterns to copy from main repo (supports globs) commands?: string[]; // Shell commands to run in worktree directory } -export interface SetupResult { - success: boolean; - output: string; // Combined stdout/stderr - error?: string; // Error message if failed -} - // Port detection types export interface DetectedPort { port: number; diff --git a/conductor.json b/conductor.json deleted file mode 100644 index 518fc6e5e78..00000000000 --- a/conductor.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "scripts": { - "setup": "./conductor-setup.sh" - } -} diff --git a/conductor-setup.sh b/superset-setup.sh similarity index 79% rename from conductor-setup.sh rename to superset-setup.sh index 9a0a58136ff..889f7a37aa4 100755 --- a/conductor-setup.sh +++ b/superset-setup.sh @@ -20,14 +20,16 @@ echo "📥 Installing dependencies..." bun install success "Dependencies installed" -# Link direnv config -echo "🔧 Linking .envrc..." -ln -sf "$CONDUCTOR_ROOT_PATH/.envrc" .envrc -success "direnv configured" +# 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 + success "direnv configured" +fi # Create Neon branch for this workspace echo "🗄️ Creating Neon branch..." -WORKSPACE_NAME=$(basename "${CONDUCTOR_WORKSPACE_PATH:-$PWD}") +WORKSPACE_NAME="${SUPERSET_WORKSPACE_NAME:-$(basename "$PWD")}" NEON_OUTPUT=$(neonctl branches create \ --project-id tiny-cherry-82420694 \ --name "$WORKSPACE_NAME" \