Files
paperclip/server/src/__tests__/dev-runner-worktree.test.ts
T
Dotta ece8a51e22 [codex] Bundle local branch fixes from PAP-10032 (#6604)
## Thinking Path

> - Paperclip orchestrates AI agents for zero-human companies.
> - This branch accumulated multiple already-tested control-plane,
adapter runtime, invite, workspace, plugin, and UI quality fixes on the
primary Paperclip checkout.
> - `origin/master` advanced while those commits were still local, so
the branch needed to be preserved and reconciled before review.
> - Splitting the branch commit-by-commit against the new base produced
overlapping conflicts with recently merged upstream PRs.
> - This pull request keeps the remaining branch as one standalone PR
because the final diff is 38 files after removing screenshot artifacts,
under Greptile's 100-file cap, and can be merged independently after
review.
> - The benefit is that none of the local work is lost, the branch is
now based on current `origin/master`, and reviewers can evaluate the
reconciled changes in one place.

## What Changed

- Merged the local accumulated branch with current `origin/master` and
resolved the invite-flow overlaps from the newer upstream companies
query helper.
- Preserved the local fixes for invite existing-member behavior, invite
link copy fallback, reusable workspace selection, worktree auth, static
SPA fallback, markdown wrapping, plugin slot registration, cloud
upstream UX/server polish, project sorting, and related tests.
- Removed screenshot artifacts from the PR per review request.
- Kept the PR under the requested file limit: 38 files changed, with no
`pnpm-lock.yaml` or `.github/workflows/*` changes.

## Verification

- `NODE_ENV=test pnpm exec vitest run
ui/src/pages/CompanyInvites.test.tsx ui/src/pages/InviteLanding.test.tsx
ui/src/pages/Projects.test.tsx ui/src/plugins/slots.test.ts
ui/src/components/MarkdownBody.test.tsx
server/src/__tests__/invite-accept-existing-member.test.ts
server/src/__tests__/static-index-html.test.ts
server/src/__tests__/execution-workspaces-service.test.ts
server/src/__tests__/better-auth.test.ts
server/src/__tests__/worktree-config.test.ts`
- `NODE_ENV=test pnpm --filter @paperclipai/ui typecheck`
- `NODE_ENV=test pnpm --filter @paperclipai/server typecheck`
- Confirmed `git diff --name-only origin/master...HEAD | wc -l` is `38`.
- Confirmed no PR diff entries match `pnpm-lock.yaml`,
`.github/workflows/*`, or `screenshots/*`.

## Risks

- Medium review risk because this is a bundled rescue PR rather than
several narrow feature PRs.
- Invite flow and company cache behavior overlapped with newer upstream
changes; the merge resolution intentionally keeps the shared
`companiesListQueryOptions` helper while preserving local
existing-member invite behavior.
- Visual review evidence is no longer attached in-repo because
screenshots were removed from this PR per review request.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- OpenAI Codex, GPT-5-based coding agent, with repository tool access,
terminal execution, and git/GitHub CLI operations.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] UI screenshots were intentionally removed from this PR per review
request
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: CodexCoder <codexcoder@paperclip.local>
2026-05-25 07:25:26 -05:00

112 lines
4.0 KiB
TypeScript

import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import {
bootstrapDevRunnerWorktreeEnv,
isLinkedGitWorktreeCheckout,
resolveWorktreeEnvFilePath,
} from "../dev-runner-worktree.ts";
const tempRoots = new Set<string>();
afterEach(() => {
for (const root of tempRoots) {
fs.rmSync(root, { recursive: true, force: true });
}
tempRoots.clear();
});
function createTempRoot(prefix: string): string {
const root = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
tempRoots.add(root);
return root;
}
describe("dev-runner worktree env bootstrap", () => {
it("detects linked git worktrees from .git files", () => {
const root = createTempRoot("paperclip-dev-runner-worktree-");
fs.writeFileSync(path.join(root, ".git"), "gitdir: /tmp/paperclip/.git/worktrees/feature\n", "utf8");
expect(isLinkedGitWorktreeCheckout(root)).toBe(true);
});
it("loads repo-local Paperclip env for initialized worktrees without overriding explicit env", () => {
const root = createTempRoot("paperclip-dev-runner-worktree-env-");
fs.mkdirSync(path.join(root, ".paperclip"), { recursive: true });
fs.writeFileSync(path.join(root, ".git"), "gitdir: /tmp/paperclip/.git/worktrees/feature\n", "utf8");
fs.writeFileSync(
resolveWorktreeEnvFilePath(root),
[
"PAPERCLIP_HOME=/tmp/paperclip-worktrees",
"PAPERCLIP_INSTANCE_ID=feature-worktree",
"PAPERCLIP_IN_WORKTREE=true",
"PAPERCLIP_WORKTREE_NAME=feature-worktree",
"PAPERCLIP_OPTIONAL= # comment-only value",
"",
].join("\n"),
"utf8",
);
const env: NodeJS.ProcessEnv = {
PAPERCLIP_INSTANCE_ID: "already-set",
};
const result = bootstrapDevRunnerWorktreeEnv(root, env);
expect(result).toEqual({
envPath: resolveWorktreeEnvFilePath(root),
missingEnv: false,
});
expect(env.PAPERCLIP_HOME).toBe("/tmp/paperclip-worktrees");
expect(env.PAPERCLIP_INSTANCE_ID).toBe("already-set");
expect(env.PAPERCLIP_IN_WORKTREE).toBe("true");
expect(env.PAPERCLIP_OPTIONAL).toBe("");
});
it("repairs stale migrated config paths before loading worktree env", () => {
const root = createTempRoot("paperclip-dev-runner-worktree-migrated-env-");
const localConfigPath = path.join(root, ".paperclip", "config.json");
const worktreesDir = path.join(root, ".paperclip-worktrees");
fs.mkdirSync(path.dirname(localConfigPath), { recursive: true });
fs.writeFileSync(path.join(root, ".git"), "gitdir: /tmp/paperclip/.git/worktrees/feature\n", "utf8");
fs.writeFileSync(localConfigPath, "{}\n", "utf8");
fs.writeFileSync(
resolveWorktreeEnvFilePath(root),
[
"PAPERCLIP_HOME=/old/home/.paperclip-worktrees",
"PAPERCLIP_INSTANCE_ID=feature-worktree",
"PAPERCLIP_CONFIG=/old/home/paperclip/.paperclip/worktrees/feature/.paperclip/config.json",
"PAPERCLIP_CONTEXT=/old/home/.paperclip-worktrees/context.json",
"PAPERCLIP_IN_WORKTREE=true",
"PAPERCLIP_WORKTREE_NAME=feature-worktree",
"",
].join("\n"),
"utf8",
);
const env: NodeJS.ProcessEnv = {
PAPERCLIP_WORKTREES_DIR: worktreesDir,
};
const result = bootstrapDevRunnerWorktreeEnv(root, env);
expect(result).toEqual({
envPath: resolveWorktreeEnvFilePath(root),
missingEnv: false,
});
expect(env.PAPERCLIP_HOME).toBe(worktreesDir);
expect(env.PAPERCLIP_CONFIG).toBe(localConfigPath);
expect(env.PAPERCLIP_CONTEXT).toBe(path.join(worktreesDir, "context.json"));
expect(env.PAPERCLIP_INSTANCE_ID).toBe("feature-worktree");
});
it("reports uninitialized linked worktrees so dev runner can fail fast", () => {
const root = createTempRoot("paperclip-dev-runner-worktree-missing-");
fs.writeFileSync(path.join(root, ".git"), "gitdir: /tmp/paperclip/.git/worktrees/feature\n", "utf8");
expect(bootstrapDevRunnerWorktreeEnv(root, {})).toEqual({
envPath: resolveWorktreeEnvFilePath(root),
missingEnv: true,
});
});
});