import { pathToFileURL } from "node:url"; import type * as github from "@actions/github"; import { getReleasePlan } from "@changesets/get-release-plan"; import { exec } from "tinyexec"; import { describe, expect, it } from "vitest"; import { gitdir, shallowClone, testdir } from "../test-utils.ts"; import { getPullRequestWorktree } from "./worktree.ts"; type PullRequestContext = NonNullable< typeof github.context.payload.pull_request >; async function git(cwd: string, args: string[]) { const output = await exec("git", args, { nodeOptions: { cwd }, throwOnError: true, }); return output.stdout.trim(); } describe("getPullRequestWorktree", () => { it("fetches a PR branch into a detached worktree and keeps the main checkout untouched", async () => { // Local source repo await using sourceRepoFixture = await gitdir({ ".changeset/config.json": JSON.stringify({}), "package.json": JSON.stringify({ name: "repo", private: true, workspaces: ["packages/*"], }), "package-lock.json": "", "packages/pkg-a/package.json": JSON.stringify({ name: "pkg-a", version: "1.0.0", }), }); const sourceRepo = sourceRepoFixture.path; // Simulate remote bare git server await using originBareFixture = await testdir(); const originBare = originBareFixture.path; await git(originBare, [ "clone", "--bare", pathToFileURL(sourceRepo).toString(), ".", ]); // Simulate checkout PR in github action await using checkoutRepoFixture = await shallowClone(originBare); const checkoutRepo = checkoutRepoFixture.path; // Simulate remote fork bare git server await using forkBareFixture = await testdir(); const forkBare = forkBareFixture.path; await git(forkBare, [ "clone", "--bare", pathToFileURL(originBare).toString(), ".", ]); await using forkRepoFixture = await shallowClone(forkBare); const forkRepo = forkRepoFixture.path; await git(forkRepo, ["config", "user.name", "Test User"]); await git(forkRepo, ["config", "user.email", "test@example.com"]); await git(forkRepo, ["checkout", "-b", "feature"]); await forkRepoFixture.mkdir("packages/pkg-a/src"); await forkRepoFixture.writeFile( "packages/pkg-a/src/index.ts", "export const value = 1;\n", ); await forkRepoFixture.writeFile( ".changeset/add-pkg-a.md", `\ --- "pkg-a": patch --- Add pkg-a `, ); await git(forkRepo, ["add", "."]); await git(forkRepo, ["commit", "-m", "feature"]); await git(forkRepo, ["push", "origin", "feature"]); // Run tests const originalHead = await git(checkoutRepo, ["rev-parse", "HEAD"]); const context = { number: 123, base: { ref: "main", repo: { clone_url: pathToFileURL(originBare).toString(), }, }, head: { ref: "feature", repo: { clone_url: pathToFileURL(forkBare).toString(), }, }, }; await using worktree = await getPullRequestWorktree( context satisfies PullRequestContext as PullRequestContext, checkoutRepo, ); const releasePlan = await getReleasePlan(worktree.cwd, worktree.baseRef); const currentHead = await git(worktree.cwd, ["rev-parse", "HEAD"]); expect(currentHead).not.toBe(originalHead); const currentBranch = await git(worktree.cwd, ["branch", "--show-current"]); expect(currentBranch).toBe(""); const releases = releasePlan.releases.map((release) => ({ name: release.name, type: release.type, newVersion: release.newVersion, })); expect(releases).toEqual([ { name: "pkg-a", type: "patch", newVersion: "1.0.1", }, ]); expect(await git(checkoutRepo, ["rev-parse", "HEAD"])).toBe(originalHead); expect(await git(checkoutRepo, ["branch", "--show-current"])).toBe("main"); }); });