forked from farhoodlabs/paperclip
Provision local node_modules in issue worktrees
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -335,6 +335,44 @@ disable_seeded_routines() {
|
||||
|
||||
disable_seeded_routines
|
||||
|
||||
if [[ -f "$worktree_cwd/package.json" && -f "$worktree_cwd/pnpm-lock.yaml" ]]; then
|
||||
needs_install=0
|
||||
|
||||
while IFS= read -r relative_path; do
|
||||
[[ -n "$relative_path" ]] || continue
|
||||
target_path="$worktree_cwd/$relative_path"
|
||||
|
||||
if [[ -L "$target_path" ]]; then
|
||||
rm "$target_path"
|
||||
needs_install=1
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ ! -e "$target_path" ]]; then
|
||||
needs_install=1
|
||||
fi
|
||||
done < <(
|
||||
cd "$base_cwd" &&
|
||||
find . \
|
||||
-mindepth 1 \
|
||||
-maxdepth 3 \
|
||||
-type d \
|
||||
-name node_modules \
|
||||
! -path './.git/*' \
|
||||
! -path './.paperclip/*' \
|
||||
| sed 's#^\./##'
|
||||
)
|
||||
|
||||
if [[ "$needs_install" -eq 1 ]]; then
|
||||
(
|
||||
cd "$worktree_cwd"
|
||||
pnpm install --frozen-lockfile
|
||||
)
|
||||
fi
|
||||
|
||||
exit 0
|
||||
fi
|
||||
|
||||
while IFS= read -r relative_path; do
|
||||
[[ -n "$relative_path" ]] || continue
|
||||
source_path="$base_cwd/$relative_path"
|
||||
|
||||
@@ -50,11 +50,16 @@ if (!embeddedPostgresSupport.supported) {
|
||||
`Skipping embedded Postgres workspace-runtime tests on this host: ${embeddedPostgresSupport.reason ?? "unsupported environment"}`,
|
||||
);
|
||||
}
|
||||
const provisionWorktreeScriptPath = new URL("../../../scripts/provision-worktree.sh", import.meta.url);
|
||||
|
||||
async function runGit(cwd: string, args: string[]) {
|
||||
await execFileAsync("git", args, { cwd });
|
||||
}
|
||||
|
||||
async function runPnpm(cwd: string, args: string[]) {
|
||||
await execFileAsync("pnpm", args, { cwd });
|
||||
}
|
||||
|
||||
async function createTempRepo(defaultBranch = "main") {
|
||||
const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-worktree-repo-"));
|
||||
await runGit(repoRoot, ["init"]);
|
||||
@@ -557,6 +562,106 @@ describe("realizeExecutionWorkspace", () => {
|
||||
}
|
||||
}, 15_000);
|
||||
|
||||
it("provisions worktree-local pnpm node_modules instead of reusing base-repo links", async () => {
|
||||
const repoRoot = await createTempRepo();
|
||||
await fs.mkdir(path.join(repoRoot, "scripts"), { recursive: true });
|
||||
await fs.mkdir(path.join(repoRoot, "packages", "shared"), { recursive: true });
|
||||
await fs.mkdir(path.join(repoRoot, "server"), { recursive: true });
|
||||
await fs.writeFile(
|
||||
path.join(repoRoot, "package.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
name: "workspace-root",
|
||||
private: true,
|
||||
packageManager: "pnpm@9.15.4",
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(
|
||||
path.join(repoRoot, "pnpm-workspace.yaml"),
|
||||
["packages:", " - packages/*", " - server", ""].join("\n"),
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(
|
||||
path.join(repoRoot, "packages", "shared", "package.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
name: "@repo/shared",
|
||||
version: "1.0.0",
|
||||
private: true,
|
||||
type: "module",
|
||||
exports: "./index.js",
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(path.join(repoRoot, "packages", "shared", "index.js"), "export const value = 'shared';\n", "utf8");
|
||||
await fs.writeFile(
|
||||
path.join(repoRoot, "server", "package.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
name: "server",
|
||||
private: true,
|
||||
type: "module",
|
||||
dependencies: {
|
||||
"@repo/shared": "workspace:*",
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(path.join(repoRoot, "server", "index.js"), "export {};\n", "utf8");
|
||||
await fs.copyFile(provisionWorktreeScriptPath, path.join(repoRoot, "scripts", "provision-worktree.sh"));
|
||||
await fs.chmod(path.join(repoRoot, "scripts", "provision-worktree.sh"), 0o755);
|
||||
await runPnpm(repoRoot, ["install"]);
|
||||
await runGit(repoRoot, ["add", "."]);
|
||||
await runGit(repoRoot, ["commit", "-m", "Add pnpm workspace fixture"]);
|
||||
|
||||
const workspace = await realizeExecutionWorkspace({
|
||||
base: {
|
||||
baseCwd: repoRoot,
|
||||
source: "project_primary",
|
||||
projectId: "project-1",
|
||||
workspaceId: "workspace-1",
|
||||
repoUrl: null,
|
||||
repoRef: "HEAD",
|
||||
},
|
||||
config: {
|
||||
workspaceStrategy: {
|
||||
type: "git_worktree",
|
||||
branchTemplate: "{{issue.identifier}}-{{slug}}",
|
||||
provisionCommand: "bash ./scripts/provision-worktree.sh",
|
||||
},
|
||||
},
|
||||
issue: {
|
||||
id: "issue-1",
|
||||
identifier: "PAP-551",
|
||||
title: "Provision local workspace dependencies",
|
||||
},
|
||||
agent: {
|
||||
id: "agent-1",
|
||||
name: "Codex Coder",
|
||||
companyId: "company-1",
|
||||
},
|
||||
});
|
||||
|
||||
expect((await fs.lstat(path.join(workspace.cwd, "node_modules"))).isSymbolicLink()).toBe(false);
|
||||
expect((await fs.lstat(path.join(workspace.cwd, "server", "node_modules"))).isSymbolicLink()).toBe(false);
|
||||
await expect(fs.realpath(path.join(workspace.cwd, "server", "node_modules", "@repo", "shared"))).resolves.toBe(
|
||||
await fs.realpath(path.join(workspace.cwd, "packages", "shared")),
|
||||
);
|
||||
await expect(fs.realpath(path.join(repoRoot, "server", "node_modules", "@repo", "shared"))).resolves.toBe(
|
||||
await fs.realpath(path.join(repoRoot, "packages", "shared")),
|
||||
);
|
||||
});
|
||||
|
||||
it("records worktree setup and provision operations when a recorder is provided", async () => {
|
||||
const repoRoot = await createTempRepo();
|
||||
const { recorder, operations } = createWorkspaceOperationRecorderDouble();
|
||||
|
||||
Reference in New Issue
Block a user