diff --git a/packages/server/src/utils/worktree.test.ts b/packages/server/src/utils/worktree.test.ts index ac0146c5e..a8264b362 100644 --- a/packages/server/src/utils/worktree.test.ts +++ b/packages/server/src/utils/worktree.test.ts @@ -779,6 +779,66 @@ describe.skipIf(process.platform === "win32")("createWorktree", () => { ]), ); }); + + it("seeds an uncommitted paseo.json from the main repo into a new worktree", async () => { + writeFileSync( + join(repoDir, "paseo.json"), + JSON.stringify({ scripts: { dev: { command: "echo hi" } } }), + ); + + const result = await createLegacyWorktreeForTest({ + cwd: repoDir, + worktreeSlug: "seed-uncommitted", + source: { kind: "branch-off", baseBranch: "main", newBranchName: "feature/seed" }, + runSetup: false, + paseoHome, + }); + + const worktreeConfigPath = join(result.worktreePath, "paseo.json"); + expect(existsSync(worktreeConfigPath)).toBe(true); + expect(JSON.parse(readFileSync(worktreeConfigPath, "utf8"))).toEqual({ + scripts: { dev: { command: "echo hi" } }, + }); + }); + + it("does not overwrite a committed paseo.json with uncommitted edits in the main repo", async () => { + writeFileSync( + join(repoDir, "paseo.json"), + JSON.stringify({ scripts: { dev: { command: "committed" } } }), + ); + execSync("git add paseo.json", { cwd: repoDir }); + execSync('git -c commit.gpgsign=false commit -m "add paseo.json"', { cwd: repoDir }); + + writeFileSync( + join(repoDir, "paseo.json"), + JSON.stringify({ scripts: { dev: { command: "uncommitted" } } }), + ); + + const result = await createLegacyWorktreeForTest({ + cwd: repoDir, + worktreeSlug: "preserve-committed", + source: { kind: "branch-off", baseBranch: "main", newBranchName: "feature/preserve" }, + runSetup: false, + paseoHome, + }); + + const worktreeConfigPath = join(result.worktreePath, "paseo.json"); + expect(JSON.parse(readFileSync(worktreeConfigPath, "utf8"))).toEqual({ + scripts: { dev: { command: "committed" } }, + }); + }); + + it("creates a worktree without error when no paseo.json exists in the main repo", async () => { + const result = await createLegacyWorktreeForTest({ + cwd: repoDir, + worktreeSlug: "no-config", + source: { kind: "branch-off", baseBranch: "main", newBranchName: "feature/no-config" }, + runSetup: false, + paseoHome, + }); + + expect(existsSync(join(result.worktreePath, "paseo.json"))).toBe(false); + }); }); describe("paseo worktree manager", () => { diff --git a/packages/server/src/utils/worktree.ts b/packages/server/src/utils/worktree.ts index 767f51be4..ea9ab8b02 100644 --- a/packages/server/src/utils/worktree.ts +++ b/packages/server/src/utils/worktree.ts @@ -1,7 +1,7 @@ import { execFile } from "child_process"; import { promisify } from "util"; import { existsSync, mkdirSync, realpathSync, rmSync, statSync } from "fs"; -import { rm, stat } from "fs/promises"; +import { copyFile, rm, stat } from "fs/promises"; import { join, basename, dirname, resolve, sep } from "path"; import net from "node:net"; import { createHash } from "node:crypto"; @@ -1226,6 +1226,19 @@ export const createWorktree = async ({ writePaseoWorktreeMetadata(worktreePath, { baseRefName: sourcePlan.metadataBaseRefName }); + // If paseo.json exists in the main repo but wasn't checked into the worktree + // (e.g. uncommitted on first-time setup), seed the worktree with it so setup + // commands and scripts pick up the user's intended config. + const mainConfigPath = join(cwd, "paseo.json"); + const worktreeConfigPath = join(worktreePath, "paseo.json"); + try { + await stat(worktreeConfigPath); + } catch { + await copyFile(mainConfigPath, worktreeConfigPath).catch((err) => { + if ((err as NodeJS.ErrnoException).code !== "ENOENT") throw err; + }); + } + if (runSetup) { await runWorktreeSetupCommands({ worktreePath,