From 7827adeaf086840cc9fcc708d1944e721572241b Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Mon, 27 Apr 2026 14:06:23 +0700 Subject: [PATCH] Seed paseo.json into new worktrees when missing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lets first-time users edit project settings via the UI and have those scripts apply to their first worktree without first having to commit paseo.json. Committed configs are unaffected — git worktree add already checks them out, so the seed is a no-op there. --- packages/server/src/utils/worktree.test.ts | 60 ++++++++++++++++++++++ packages/server/src/utils/worktree.ts | 15 +++++- 2 files changed, 74 insertions(+), 1 deletion(-) 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,