diff --git a/apps/server/src/routes/worktree/routes/init-git.ts b/apps/server/src/routes/worktree/routes/init-git.ts index 0a5c1a0b..656a8472 100644 --- a/apps/server/src/routes/worktree/routes/init-git.ts +++ b/apps/server/src/routes/worktree/routes/init-git.ts @@ -43,10 +43,14 @@ export function createInitGitHandler() { // .git doesn't exist, continue with initialization } - // Initialize git and create an initial empty commit - await execAsync(`git init && git commit --allow-empty -m "Initial commit"`, { - cwd: projectPath, - }); + // Initialize git with 'main' as the default branch (matching GitHub's standard since 2020) + // and create an initial empty commit + await execAsync( + `git init --initial-branch=main && git commit --allow-empty -m "Initial commit"`, + { + cwd: projectPath, + } + ); res.json({ success: true, diff --git a/apps/server/tests/integration/helpers/git-test-repo.ts b/apps/server/tests/integration/helpers/git-test-repo.ts index 7871e8e8..de5e4215 100644 --- a/apps/server/tests/integration/helpers/git-test-repo.ts +++ b/apps/server/tests/integration/helpers/git-test-repo.ts @@ -20,8 +20,8 @@ export interface TestRepo { export async function createTestGitRepo(): Promise { const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'automaker-test-')); - // Initialize git repo - await execAsync('git init', { cwd: tmpDir }); + // Initialize git repo with 'main' as the default branch (matching GitHub's standard) + await execAsync('git init --initial-branch=main', { cwd: tmpDir }); // Use environment variables instead of git config to avoid affecting user's git config // These env vars override git config without modifying it @@ -38,9 +38,6 @@ export async function createTestGitRepo(): Promise { await execAsync('git add .', { cwd: tmpDir, env: gitEnv }); await execAsync('git commit -m "Initial commit"', { cwd: tmpDir, env: gitEnv }); - // Create main branch explicitly - await execAsync('git branch -M main', { cwd: tmpDir }); - return { path: tmpDir, cleanup: async () => { diff --git a/apps/server/tests/integration/routes/worktree/create.integration.test.ts b/apps/server/tests/integration/routes/worktree/create.integration.test.ts index 6d274a0d..a751706b 100644 --- a/apps/server/tests/integration/routes/worktree/create.integration.test.ts +++ b/apps/server/tests/integration/routes/worktree/create.integration.test.ts @@ -14,7 +14,8 @@ describe('worktree create route - repositories without commits', () => { async function initRepoWithoutCommit() { repoPath = await fs.mkdtemp(path.join(os.tmpdir(), 'automaker-no-commit-')); - await execAsync('git init', { cwd: repoPath }); + // Initialize with 'main' as the default branch (matching GitHub's standard) + await execAsync('git init --initial-branch=main', { cwd: repoPath }); // Don't set git config - use environment variables in commit operations instead // to avoid affecting user's git config // Intentionally skip creating an initial commit diff --git a/apps/server/tests/unit/services/dev-server-service.test.ts b/apps/server/tests/unit/services/dev-server-service.test.ts index 03d3d01c..d390926a 100644 --- a/apps/server/tests/unit/services/dev-server-service.test.ts +++ b/apps/server/tests/unit/services/dev-server-service.test.ts @@ -30,11 +30,16 @@ import net from 'net'; describe('dev-server-service.ts', () => { let testDir: string; + let originalHostname: string | undefined; beforeEach(async () => { vi.clearAllMocks(); vi.resetModules(); + // Store and set HOSTNAME for consistent test behavior + originalHostname = process.env.HOSTNAME; + process.env.HOSTNAME = 'localhost'; + testDir = path.join(os.tmpdir(), `dev-server-test-${Date.now()}`); await fs.mkdir(testDir, { recursive: true }); @@ -56,6 +61,13 @@ describe('dev-server-service.ts', () => { }); afterEach(async () => { + // Restore original HOSTNAME + if (originalHostname === undefined) { + delete process.env.HOSTNAME; + } else { + process.env.HOSTNAME = originalHostname; + } + try { await fs.rm(testDir, { recursive: true, force: true }); } catch { diff --git a/apps/ui/scripts/prepare-server.mjs b/apps/ui/scripts/prepare-server.mjs index 82309574..db92b385 100644 --- a/apps/ui/scripts/prepare-server.mjs +++ b/apps/ui/scripts/prepare-server.mjs @@ -7,8 +7,8 @@ */ import { execSync } from 'child_process'; -import { cpSync, existsSync, mkdirSync, rmSync, writeFileSync, readFileSync } from 'fs'; -import { join, dirname } from 'path'; +import { cpSync, existsSync, mkdirSync, rmSync, writeFileSync, readFileSync, lstatSync } from 'fs'; +import { join, dirname, resolve } from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); @@ -112,6 +112,29 @@ execSync('npm install --omit=dev', { }, }); +// Step 6b: Replace symlinks for local packages with real copies +// npm install creates symlinks for file: references, but these break when packaged by electron-builder +console.log('🔗 Replacing symlinks with real directory copies...'); +const nodeModulesAutomaker = join(BUNDLE_DIR, 'node_modules', '@automaker'); +for (const pkgName of LOCAL_PACKAGES) { + const pkgDir = pkgName.replace('@automaker/', ''); + const nmPkgPath = join(nodeModulesAutomaker, pkgDir); + try { + // lstatSync does not follow symlinks, allowing us to check for broken ones + if (lstatSync(nmPkgPath).isSymbolicLink()) { + const realPath = resolve(BUNDLE_DIR, 'libs', pkgDir); + rmSync(nmPkgPath); + cpSync(realPath, nmPkgPath, { recursive: true }); + console.log(` ✓ Replaced symlink: ${pkgName}`); + } + } catch (error) { + // If the path doesn't exist, lstatSync throws ENOENT. We can safely ignore this. + if (error.code !== 'ENOENT') { + throw error; + } + } +} + // Step 7: Rebuild native modules for current architecture // This is critical for modules like node-pty that have native bindings console.log('🔨 Rebuilding native modules for current architecture...'); diff --git a/apps/ui/src/components/layout/project-switcher/project-switcher.tsx b/apps/ui/src/components/layout/project-switcher/project-switcher.tsx index 541fa83c..a8aa521f 100644 --- a/apps/ui/src/components/layout/project-switcher/project-switcher.tsx +++ b/apps/ui/src/components/layout/project-switcher/project-switcher.tsx @@ -1,7 +1,7 @@ import { useState, useCallback, useEffect } from 'react'; import { Plus, Bug, FolderOpen, BookOpen } from 'lucide-react'; import { useNavigate, useLocation } from '@tanstack/react-router'; -import { cn } from '@/lib/utils'; +import { cn, isMac } from '@/lib/utils'; import { useAppStore } from '@/store/app-store'; import { useOSDetection } from '@/hooks/use-os-detection'; import { ProjectSwitcherItem } from './components/project-switcher-item'; @@ -11,9 +11,12 @@ import { NotificationBell } from './components/notification-bell'; import { NewProjectModal } from '@/components/dialogs/new-project-modal'; import { OnboardingDialog } from '@/components/layout/sidebar/dialogs'; import { useProjectCreation } from '@/components/layout/sidebar/hooks'; -import { SIDEBAR_FEATURE_FLAGS } from '@/components/layout/sidebar/constants'; +import { + MACOS_ELECTRON_TOP_PADDING_CLASS, + SIDEBAR_FEATURE_FLAGS, +} from '@/components/layout/sidebar/constants'; import type { Project } from '@/lib/electron'; -import { getElectronAPI } from '@/lib/electron'; +import { getElectronAPI, isElectron } from '@/lib/electron'; import { initializeProject, hasAppSpec, hasAutomakerDir } from '@/lib/project-init'; import { toast } from 'sonner'; import { CreateSpecDialog } from '@/components/views/spec-view/dialogs'; @@ -279,7 +282,12 @@ export function ProjectSwitcher() { data-testid="project-switcher" > {/* Automaker Logo and Version */} -
+