feat(checkout): merge PR action with real-GitHub e2e test (#814)
* feat(checkout): merge PR action with real-GitHub e2e test Adds a `checkoutPrMerge` server RPC backed by `gh pr merge` (squash, merge, or rebase) and surfaces three new actions in the git-actions menu — "Squash and merge", "Create a merge commit", "Rebase and merge" — with squash taking the primary slot when a PR is open and mergeable. Also renames the existing local merge action to "Merge locally" to disambiguate it from the new GitHub-side merge. Adds an end-to-end test that creates a real temporary GitHub repo, opens a PR via the daemon, polls until mergeable, calls `checkoutPrMerge`, and verifies the merge landed on `main` — no mocks. The test pre-flights the `delete_repo` scope so it skips cleanly on tokens that can't tear down, and fails loud if cleanup ever errors so we don't leak repos. PR status gains an optional `mergeable` field (`MERGEABLE | CONFLICTING | UNKNOWN`) populated from `gh pr view --json mergeable`. The schema is backward compatible — old payloads without the field still parse and default to `UNKNOWN` via `z.catch`. * Update PR status snapshot expectations * refactor: collect git/checkout/PR feature into packages/app/src/git/ Move 21 git/checkout/PR files out of components/, hooks/, stores/, screens/, utils/ flat-peer slop into a single packages/app/src/git/ module with shrunk filenames (git-diff-pane.tsx -> diff-pane.tsx, use-git-actions.ts -> use-actions.ts, etc). Dedupe: drop the duplicate useGitActionHandlers from diff-pane.tsx — the shared useGitActions hook is the single source. Drop the buildGitActionsForPane wrapper. Collapse the merge-PR triple fan-out (mergePrSquash/Merge/RebaseStatus) into one record keyed by CheckoutPrMergeMethod. Streamline server-side single-use helpers (dispatchStash/PullRequestMessage adapters, GitHubPullRequestMergeError wrapper, legacy worktree test helpers). Schema-additive only: messages.ts exports CheckoutPrMergeMethod and PullRequestMergeable types; daemon-client.ts uses the typed alias. Backward-compatible. Net delta: 43 files, +219/-1212. * fix(app): use GitHub icon for all merge-PR menu actions * test: namespace temp GitHub repos under `paseotmp-` and centralize naming Old prefixes (`paseo-checkout-ship-`, `paseo-e2e-`, `paseo-checkout-pr-merge-`) collided with the `paseo-*` namespace of real repos, making bulk cleanup unsafe. Switch every test that creates real GitHub repos to a single unmistakable `paseotmp-` prefix, owned by one shared helper per test boundary so no caller can name a repo outside the namespace. - New `packages/server/src/server/test-utils/temp-github-repo.ts` exports `TEMP_GITHUB_REPO_PREFIX` + `createTempGithubRepoName(category)`; both server e2e tests (checkout-ship, checkout-pr-merge) consume it. - `packages/app/e2e/helpers/github-fixtures.ts` locks the prefix internally and now requires callers to pass a `category` instead of an arbitrary `prefix`. Updated pr-pane and composer-attachments specs accordingly.
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import { CheckoutPrMergeRequestSchema, CheckoutPrStatusSchema } from "./messages.js";
|
||||
|
||||
describe("checkout PR schemas", () => {
|
||||
test("parses PR status payloads without mergeability", () => {
|
||||
expect(
|
||||
CheckoutPrStatusSchema.parse({
|
||||
number: 42,
|
||||
url: "https://github.com/getpaseo/paseo/pull/42",
|
||||
title: "Ship it",
|
||||
state: "open",
|
||||
baseRefName: "main",
|
||||
headRefName: "feature/ship-it",
|
||||
isMerged: false,
|
||||
}),
|
||||
).toMatchObject({
|
||||
number: 42,
|
||||
mergeable: "UNKNOWN",
|
||||
});
|
||||
});
|
||||
|
||||
test.each(["merge", "squash", "rebase"] as const)(
|
||||
"accepts %s as a PR merge method",
|
||||
(mergeMethod) => {
|
||||
expect(
|
||||
CheckoutPrMergeRequestSchema.parse({
|
||||
type: "checkout_pr_merge_request",
|
||||
cwd: "/tmp/repo",
|
||||
mergeMethod,
|
||||
requestId: "request-merge-pr",
|
||||
}),
|
||||
).toMatchObject({ mergeMethod });
|
||||
},
|
||||
);
|
||||
|
||||
test("rejects unknown PR merge methods", () => {
|
||||
expect(() =>
|
||||
CheckoutPrMergeRequestSchema.parse({
|
||||
type: "checkout_pr_merge_request",
|
||||
cwd: "/tmp/repo",
|
||||
mergeMethod: "auto",
|
||||
requestId: "request-merge-pr",
|
||||
}),
|
||||
).toThrow();
|
||||
});
|
||||
});
|
||||
@@ -1272,6 +1272,13 @@ export const CheckoutPrCreateRequestSchema = z.object({
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
export const CheckoutPrMergeRequestSchema = z.object({
|
||||
type: z.literal("checkout_pr_merge_request"),
|
||||
cwd: z.string(),
|
||||
mergeMethod: z.enum(["merge", "squash", "rebase"]),
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
export const CheckoutPrStatusRequestSchema = z.object({
|
||||
type: z.literal("checkout_pr_status_request"),
|
||||
cwd: z.string(),
|
||||
@@ -1719,6 +1726,7 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
|
||||
CheckoutPullRequestSchema,
|
||||
CheckoutPushRequestSchema,
|
||||
CheckoutPrCreateRequestSchema,
|
||||
CheckoutPrMergeRequestSchema,
|
||||
CheckoutPrStatusRequestSchema,
|
||||
PullRequestTimelineRequestSchema,
|
||||
CheckoutSwitchBranchRequestSchema,
|
||||
@@ -2661,6 +2669,11 @@ export const CheckoutPrStatusSchema = z.object({
|
||||
headRefName: z.string(),
|
||||
isMerged: z.boolean(),
|
||||
isDraft: z.boolean().optional().default(false),
|
||||
mergeable: z
|
||||
.enum(["MERGEABLE", "CONFLICTING", "UNKNOWN"])
|
||||
.catch("UNKNOWN")
|
||||
.optional()
|
||||
.default("UNKNOWN"),
|
||||
checks: z
|
||||
.array(
|
||||
z.object({
|
||||
@@ -2782,6 +2795,16 @@ export const CheckoutPrCreateResponseSchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
export const CheckoutPrMergeResponseSchema = z.object({
|
||||
type: z.literal("checkout_pr_merge_response"),
|
||||
payload: z.object({
|
||||
cwd: z.string(),
|
||||
success: z.boolean(),
|
||||
error: CheckoutErrorSchema.nullable(),
|
||||
requestId: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const CheckoutPrStatusResponseSchema = z.object({
|
||||
type: z.literal("checkout_pr_status_response"),
|
||||
payload: CheckoutPrStatusPayloadSchema,
|
||||
@@ -3338,6 +3361,7 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
|
||||
CheckoutPullResponseSchema,
|
||||
CheckoutPushResponseSchema,
|
||||
CheckoutPrCreateResponseSchema,
|
||||
CheckoutPrMergeResponseSchema,
|
||||
CheckoutPrStatusResponseSchema,
|
||||
PullRequestTimelineResponseSchema,
|
||||
CheckoutSwitchBranchResponseSchema,
|
||||
@@ -3585,6 +3609,10 @@ export type CheckoutPushRequest = z.infer<typeof CheckoutPushRequestSchema>;
|
||||
export type CheckoutPushResponse = z.infer<typeof CheckoutPushResponseSchema>;
|
||||
export type CheckoutPrCreateRequest = z.infer<typeof CheckoutPrCreateRequestSchema>;
|
||||
export type CheckoutPrCreateResponse = z.infer<typeof CheckoutPrCreateResponseSchema>;
|
||||
export type CheckoutPrMergeRequest = z.infer<typeof CheckoutPrMergeRequestSchema>;
|
||||
export type CheckoutPrMergeResponse = z.infer<typeof CheckoutPrMergeResponseSchema>;
|
||||
export type CheckoutPrMergeMethod = z.infer<typeof CheckoutPrMergeRequestSchema>["mergeMethod"];
|
||||
export type PullRequestMergeable = z.infer<typeof CheckoutPrStatusSchema>["mergeable"];
|
||||
export type CheckoutPrStatusRequest = z.infer<typeof CheckoutPrStatusRequestSchema>;
|
||||
export type CheckoutPrStatusResponse = z.infer<typeof CheckoutPrStatusResponseSchema>;
|
||||
export type PullRequestTimelineRequest = z.infer<typeof PullRequestTimelineRequestSchema>;
|
||||
|
||||
Reference in New Issue
Block a user