| author | |
| committer | |
| log | caab2fdbac041ac2e851ad4fa3ac4a40a1d4a8f6 |
| tree | 021bd438f2ef51ef5a54ffea4fd0632acb74f3a5 |
| parent | 5cc369b7eb882dbea8d5ad21ba688c73a0058748 |
| signature | Signed by PGP key B5690EEEBB952194 |
30 files changed, 152 insertions(+), 36 deletions(-)
apps/mobile/src/features/threads/thread-list-items.tsx+10-6| ... | ... | @@ -23,7 +23,7 @@ import { relativeTime } from "../../lib/time"; |
| 23 | 23 | import { themeColorWithAlpha } from "../../lib/mobileTheme"; |
| 24 | 24 | import { useUniwindTheme } from "../../lib/useUniwindTheme"; |
| 25 | 25 | import type { PendingNewTask } from "../../state/use-pending-new-tasks"; |
| 26 | import { useThreadPr, type ThreadPr } from "../../state/use-thread-pr"; | |
| 26 | import { useThreadPr, type ThreadPrPresentation } from "../../state/use-thread-pr"; | |
| 27 | 27 | import type { HomeGroupDisplayAction } from "../home/homeListItems"; |
| 28 | 28 | import { ThreadSwipeable } from "../home/thread-swipe-actions"; |
| 29 | 29 | import { buildThreadTitleRegenerationMenuItems } from "./thread-title-regeneration-menu"; |
| ... | ... | @@ -42,9 +42,15 @@ export type ThreadListVariant = "compact" | "sidebar"; |
| 42 | 42 | export const THREAD_LIST_COMPACT_INSET = HOME_HORIZONTAL_INSET; |
| 43 | 43 | const SIDEBAR_ROW_RADIUS = 12; |
| 44 | 44 | |
| 45 | function pullRequestTintColor(state: ThreadPr["state"], colorScheme: "light" | "dark") { | |
| 45 | function pullRequestTintColor( | |
| 46 | pr: Pick<ThreadPrPresentation, "state" | "isDraft">, | |
| 47 | colorScheme: "light" | "dark", | |
| 48 | ) { | |
| 46 | 49 | const dark = colorScheme === "dark"; |
| 47 | switch (state) { | |
| 50 | if (pr.state === "open" && pr.isDraft === true) { | |
| 51 | return dark ? "#a1a1aa" : "#71717a"; | |
| 52 | } | |
| 53 | switch (pr.state) { | |
| 48 | 54 | case "open": |
| 49 | 55 | return dark ? "#34d399" : "#059669"; |
| 50 | 56 | case "merged": |
| ... | ... | @@ -548,9 +554,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: { |
| 548 | 554 | <PullRequestIcon |
| 549 | 555 | size={compact ? 13 : 11} |
| 550 | 556 | color={ |
| 551 | selected | |
| 552 | ? String(selectedForegroundColor) | |
| 553 | : pullRequestTintColor(pr.state, colorScheme) | |
| 557 | selected ? String(selectedForegroundColor) : pullRequestTintColor(pr, colorScheme) | |
| 554 | 558 | } |
| 555 | 559 | /> |
| 556 | 560 | <Text |
apps/mobile/src/state/thread-pr-presentation.ts+5-2| ... | ... | @@ -6,6 +6,7 @@ export type ThreadPr = NonNullable<VcsStatusResult["pr"]>; |
| 6 | 6 | export interface ThreadPrPresentation { |
| 7 | 7 | readonly number: number; |
| 8 | 8 | readonly state: ThreadPr["state"]; |
| 9 | readonly isDraft: boolean; | |
| 9 | 10 | /** Provider-side last activity, bounding when a terminal state landed. */ |
| 10 | 11 | readonly updatedAt: string | null; |
| 11 | 12 | readonly url: string; |
| ... | ... | @@ -27,13 +28,15 @@ export function presentThreadPr( |
| 27 | 28 | provider: VcsStatusResult["sourceControlProvider"] | null | undefined, |
| 28 | 29 | ): ThreadPrPresentation { |
| 29 | 30 | const presentation = resolveChangeRequestPresentation(provider); |
| 31 | const isDraft = pr.state === "open" && pr.isDraft === true; | |
| 30 | 32 | return { |
| 31 | 33 | number: pr.number, |
| 32 | 34 | state: pr.state, |
| 35 | isDraft, | |
| 33 | 36 | updatedAt: pr.updatedAt ?? null, |
| 34 | 37 | url: pr.url, |
| 35 | 38 | label: String(pr.number), |
| 36 | accessibilityLabel: `#${pr.number} ${presentation.longName} ${pr.state}`, | |
| 37 | textClassName: PR_STATE_TEXT_CLASS[pr.state], | |
| 39 | accessibilityLabel: `#${pr.number} ${presentation.longName} ${isDraft ? "draft" : pr.state}`, | |
| 40 | textClassName: isDraft ? "text-adaptive-zinc-500-400" : PR_STATE_TEXT_CLASS[pr.state], | |
| 38 | 41 | }; |
| 39 | 42 | } |
apps/mobile/src/state/use-thread-pr.test.ts+9| ... | ... | @@ -33,4 +33,13 @@ describe("presentThreadPr", () => { |
| 33 | 33 | accessibilityLabel: "#3774 merge request merged", |
| 34 | 34 | }); |
| 35 | 35 | }); |
| 36 | ||
| 37 | it("uses gray for draft pull requests", () => { | |
| 38 | expect( | |
| 39 | presentThreadPr({ ...pullRequest, state: "open", isDraft: true }, undefined), | |
| 40 | ).toMatchObject({ | |
| 41 | accessibilityLabel: "#3774 pull request draft", | |
| 42 | textClassName: "text-adaptive-zinc-500-400", | |
| 43 | }); | |
| 44 | }); | |
| 36 | 45 | }); |
apps/server/src/git/GitManager.test.ts+10-5| ... | ... | @@ -54,6 +54,7 @@ interface FakeGhScenario { |
| 54 | 54 | baseRefName: string; |
| 55 | 55 | headRefName: string; |
| 56 | 56 | state?: "open" | "closed" | "merged"; |
| 57 | isDraft?: boolean; | |
| 57 | 58 | isCrossRepository?: boolean; |
| 58 | 59 | headRepositoryNameWithOwner?: string | null; |
| 59 | 60 | headRepositoryOwnerLogin?: string | null; |
| ... | ... | @@ -116,6 +117,7 @@ function normalizeFakePullRequestSummary(raw: unknown): GitHubCli.GitHubPullRequ |
| 116 | 117 | ? "closed" |
| 117 | 118 | : "merged" |
| 118 | 119 | : undefined; |
| 120 | const isDraft = typeof record.isDraft === "boolean" ? record.isDraft : undefined; | |
| 119 | 121 | const isCrossRepository = |
| 120 | 122 | typeof record.isCrossRepository === "boolean" ? record.isCrossRepository : undefined; |
| 121 | 123 | const headRepositoryNameWithOwner = |
| ... | ... | @@ -138,6 +140,7 @@ function normalizeFakePullRequestSummary(raw: unknown): GitHubCli.GitHubPullRequ |
| 138 | 140 | baseRefName, |
| 139 | 141 | headRefName, |
| 140 | 142 | ...(state ? { state } : {}), |
| 143 | ...(isDraft === true ? { isDraft: true } : {}), | |
| 141 | 144 | ...(isCrossRepository !== undefined ? { isCrossRepository } : {}), |
| 142 | 145 | ...(headRepositoryNameWithOwner ? { headRepositoryNameWithOwner } : {}), |
| 143 | 146 | ...(headRepositoryOwnerLogin ? { headRepositoryOwnerLogin } : {}), |
| ... | ... | @@ -508,7 +511,7 @@ function createGitHubCliWithFakeGh(scenario: FakeGhScenario = {}): { |
| 508 | 511 | "--limit", |
| 509 | 512 | String(input.limit ?? 1), |
| 510 | 513 | "--json", |
| 511 | "number,title,url,baseRefName,headRefName,state,mergedAt,isCrossRepository,headRepository,headRepositoryOwner", | |
| 514 | "number,title,url,baseRefName,headRefName,state,isDraft,mergedAt,isCrossRepository,headRepository,headRepositoryOwner", | |
| 512 | 515 | ], |
| 513 | 516 | }).pipe( |
| 514 | 517 | Effect.map((result) => JSON.parse(result.stdout) as unknown[]), |
| ... | ... | @@ -552,7 +555,7 @@ function createGitHubCliWithFakeGh(scenario: FakeGhScenario = {}): { |
| 552 | 555 | "view", |
| 553 | 556 | input.reference, |
| 554 | 557 | "--json", |
| 555 | "number,title,url,baseRefName,headRefName,state,mergedAt,isCrossRepository,headRepository,headRepositoryOwner", | |
| 558 | "number,title,url,baseRefName,headRefName,state,isDraft,mergedAt,isCrossRepository,headRepository,headRepositoryOwner", | |
| 556 | 559 | ], |
| 557 | 560 | }).pipe( |
| 558 | 561 | Effect.map((result) => JSON.parse(result.stdout) as GitHubCli.GitHubPullRequestSummary), |
| ... | ... | @@ -699,7 +702,7 @@ const GitManagerTestLayer = GitVcsDriver.layer.pipe( |
| 699 | 702 | ); |
| 700 | 703 | |
| 701 | 704 | it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 702 | it.effect("status includes PR metadata when branch already has an open PR", () => | |
| 705 | it.effect("status includes draft PR metadata when branch already has a draft PR", () => | |
| 703 | 706 | Effect.gen(function* () { |
| 704 | 707 | const repoDir = yield* makeTempDir("t3code-git-manager-"); |
| 705 | 708 | yield* initRepo(repoDir); |
| ... | ... | @@ -719,6 +722,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 719 | 722 | url: "https://github.com/pingdotgg/codething-mvp/pull/13", |
| 720 | 723 | baseRefName: "main", |
| 721 | 724 | headRefName: "feature/status-open-pr", |
| 725 | isDraft: true, | |
| 722 | 726 | }, |
| 723 | 727 | ]), |
| 724 | 728 | ], |
| ... | ... | @@ -737,6 +741,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 737 | 741 | baseRef: "main", |
| 738 | 742 | headRef: "feature/status-open-pr", |
| 739 | 743 | state: "open", |
| 744 | isDraft: true, | |
| 740 | 745 | updatedAt: null, |
| 741 | 746 | }); |
| 742 | 747 | }), |
| ... | ... | @@ -1686,7 +1691,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 1686 | 1691 | updatedAt: "2026-03-10T07:00:00.000Z", |
| 1687 | 1692 | }); |
| 1688 | 1693 | expect(ghCalls).toContain( |
| 1689 | "pr list --head jasonLaster:statemachine --state all --limit 20 --json number,title,url,baseRefName,headRefName,state,mergedAt,updatedAt,isCrossRepository,headRepository,headRepositoryOwner", | |
| 1694 | "pr list --head jasonLaster:statemachine --state all --limit 20 --json number,title,url,baseRefName,headRefName,state,isDraft,mergedAt,updatedAt,isCrossRepository,headRepository,headRepositoryOwner", | |
| 1690 | 1695 | ); |
| 1691 | 1696 | }), |
| 1692 | 1697 | 20_000, |
| ... | ... | @@ -1752,7 +1757,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 1752 | 1757 | updatedAt: "2026-03-10T07:00:00.000Z", |
| 1753 | 1758 | }); |
| 1754 | 1759 | expect(ghCalls).toContain( |
| 1755 | "pr list --head contributor:main --state all --limit 20 --json number,title,url,baseRefName,headRefName,state,mergedAt,updatedAt,isCrossRepository,headRepository,headRepositoryOwner", | |
| 1760 | "pr list --head contributor:main --state all --limit 20 --json number,title,url,baseRefName,headRefName,state,isDraft,mergedAt,updatedAt,isCrossRepository,headRepository,headRepositoryOwner", | |
| 1756 | 1761 | ); |
| 1757 | 1762 | }), |
| 1758 | 1763 | 20_000, |
apps/server/src/git/GitManager.ts+5-5| ... | ... | @@ -170,6 +170,7 @@ interface OpenPrInfo { |
| 170 | 170 | |
| 171 | 171 | interface PullRequestInfo extends OpenPrInfo, PullRequestHeadRemoteInfo { |
| 172 | 172 | state: "open" | "closed" | "merged"; |
| 173 | isDraft?: boolean; | |
| 173 | 174 | updatedAt: Option.Option<DateTime.Utc>; |
| 174 | 175 | } |
| 175 | 176 | |
| ... | ... | @@ -404,6 +405,7 @@ function toPullRequestInfo(summary: ChangeRequest): PullRequestInfo { |
| 404 | 405 | baseRefName: summary.baseRefName, |
| 405 | 406 | headRefName: summary.headRefName, |
| 406 | 407 | state: summary.state ?? "open", |
| 408 | ...(summary.isDraft === true ? { isDraft: true } : {}), | |
| 407 | 409 | updatedAt: summary.updatedAt, |
| 408 | 410 | ...(summary.isCrossRepository !== undefined |
| 409 | 411 | ? { isCrossRepository: summary.isCrossRepository } |
| ... | ... | @@ -558,6 +560,7 @@ function toStatusPr(pr: PullRequestInfo): { |
| 558 | 560 | baseRef: string; |
| 559 | 561 | headRef: string; |
| 560 | 562 | state: "open" | "closed" | "merged"; |
| 563 | isDraft?: boolean; | |
| 561 | 564 | updatedAt: string | null; |
| 562 | 565 | } { |
| 563 | 566 | return { |
| ... | ... | @@ -567,6 +570,7 @@ function toStatusPr(pr: PullRequestInfo): { |
| 567 | 570 | baseRef: pr.baseRefName, |
| 568 | 571 | headRef: pr.headRefName, |
| 569 | 572 | state: pr.state, |
| 573 | ...(pr.isDraft === true ? { isDraft: true } : {}), | |
| 570 | 574 | updatedAt: Option.match(pr.updatedAt, { |
| 571 | 575 | onNone: () => null, |
| 572 | 576 | onSome: (updatedAt) => DateTime.formatIso(updatedAt), |
| ... | ... | @@ -1518,11 +1522,7 @@ export const make = Effect.gen(function* () { |
| 1518 | 1522 | ); |
| 1519 | 1523 | if (firstPullRequest) { |
| 1520 | 1524 | return { |
| 1521 | number: firstPullRequest.number, | |
| 1522 | title: firstPullRequest.title, | |
| 1523 | url: firstPullRequest.url, | |
| 1524 | baseRefName: firstPullRequest.baseRefName, | |
| 1525 | headRefName: firstPullRequest.headRefName, | |
| 1525 | ...firstPullRequest, | |
| 1526 | 1526 | state: "open", |
| 1527 | 1527 | updatedAt: Option.none(), |
| 1528 | 1528 | } satisfies PullRequestInfo; |
apps/server/src/pullRequest/GitHubPullRequestCli.ts+2| ... | ... | @@ -468,6 +468,7 @@ export class GitHubPullRequestCli extends Context.Service< |
| 468 | 468 | readonly headBranch: string; |
| 469 | 469 | readonly baseBranch: string; |
| 470 | 470 | readonly state: "open" | "closed" | "merged"; |
| 471 | readonly isDraft?: boolean; | |
| 471 | 472 | readonly updatedAt: string; |
| 472 | 473 | }, |
| 473 | 474 | GitHubPullRequestCliError |
| ... | ... | @@ -1650,6 +1651,7 @@ export const make = Effect.gen(function* () { |
| 1650 | 1651 | headBranch: summary.headRefName, |
| 1651 | 1652 | baseBranch: summary.baseRefName, |
| 1652 | 1653 | state: summary.state ?? "open", |
| 1654 | ...(summary.isDraft === true ? { isDraft: true } : {}), | |
| 1653 | 1655 | updatedAt: summary.updatedAt, |
| 1654 | 1656 | }), |
| 1655 | 1657 | ), |
apps/server/src/pullRequest/PullRequestProvider.ts+2| ... | ... | @@ -96,6 +96,8 @@ export interface ProviderChangeRequestSummary { |
| 96 | 96 | readonly headBranch: string; |
| 97 | 97 | readonly baseBranch: string; |
| 98 | 98 | readonly state: PullRequestState; |
| 99 | /** Present when the host says an open pull request is still a draft. */ | |
| 100 | readonly isDraft?: boolean; | |
| 99 | 101 | readonly updatedAt: string; |
| 100 | 102 | } |
| 101 | 103 |
apps/server/src/pullRequest/PullRequestService.ts+2| ... | ... | @@ -1254,6 +1254,7 @@ export const make = Effect.gen(function* () { |
| 1254 | 1254 | title: changeRequest.title, |
| 1255 | 1255 | url: changeRequest.url, |
| 1256 | 1256 | state: changeRequest.state, |
| 1257 | ...(changeRequest.isDraft === true ? { isDraft: true } : {}), | |
| 1257 | 1258 | headBranch: changeRequest.headBranch, |
| 1258 | 1259 | baseBranch: changeRequest.baseBranch, |
| 1259 | 1260 | updatedAt: changeRequest.updatedAt, |
| ... | ... | @@ -2278,6 +2279,7 @@ export const make = Effect.gen(function* () { |
| 2278 | 2279 | title: detail.title, |
| 2279 | 2280 | url: detail.url, |
| 2280 | 2281 | state: detail.state, |
| 2282 | ...(detail.isDraft === true ? { isDraft: true } : {}), | |
| 2281 | 2283 | headBranch: detail.headBranch, |
| 2282 | 2284 | baseBranch: detail.baseBranch, |
| 2283 | 2285 | updatedAt: detail.updatedAt, |
apps/server/src/sourceControl/AzureDevOpsSourceControlProvider.ts+2| ... | ... | @@ -61,6 +61,7 @@ function toChangeRequest(summary: { |
| 61 | 61 | readonly baseRefName: string; |
| 62 | 62 | readonly headRefName: string; |
| 63 | 63 | readonly state: "open" | "closed" | "merged"; |
| 64 | readonly isDraft?: boolean; | |
| 64 | 65 | readonly updatedAt: ChangeRequest["updatedAt"]; |
| 65 | 66 | }): ChangeRequest { |
| 66 | 67 | return { |
| ... | ... | @@ -71,6 +72,7 @@ function toChangeRequest(summary: { |
| 71 | 72 | baseRefName: summary.baseRefName, |
| 72 | 73 | headRefName: summary.headRefName, |
| 73 | 74 | state: summary.state, |
| 75 | ...(summary.isDraft === true ? { isDraft: true } : {}), | |
| 74 | 76 | updatedAt: summary.updatedAt, |
| 75 | 77 | isCrossRepository: false, |
| 76 | 78 | }; |
apps/server/src/sourceControl/BitbucketSourceControlProvider.ts+1| ... | ... | @@ -17,6 +17,7 @@ function toChangeRequest(summary: NormalizedBitbucketPullRequestRecord): ChangeR |
| 17 | 17 | baseRefName: summary.baseRefName, |
| 18 | 18 | headRefName: summary.headRefName, |
| 19 | 19 | state: summary.state, |
| 20 | ...(summary.isDraft === true ? { isDraft: true } : {}), | |
| 20 | 21 | updatedAt: summary.updatedAt ?? Option.none(), |
| 21 | 22 | ...(summary.isCrossRepository !== undefined |
| 22 | 23 | ? { isCrossRepository: summary.isCrossRepository } |
apps/server/src/sourceControl/GitHubCli.test.ts+3-1| ... | ... | @@ -65,6 +65,7 @@ describe("GitHubCli.layer", () => { |
| 65 | 65 | baseRefName: "main", |
| 66 | 66 | headRefName: "feature/pr-threads", |
| 67 | 67 | state: "OPEN", |
| 68 | isDraft: true, | |
| 68 | 69 | mergedAt: null, |
| 69 | 70 | updatedAt: "2026-08-24T12:34:56Z", |
| 70 | 71 | isCrossRepository: true, |
| ... | ... | @@ -92,6 +93,7 @@ describe("GitHubCli.layer", () => { |
| 92 | 93 | baseRefName: "main", |
| 93 | 94 | headRefName: "feature/pr-threads", |
| 94 | 95 | state: "open", |
| 96 | isDraft: true, | |
| 95 | 97 | updatedAt: "2026-08-24T12:34:56.000Z", |
| 96 | 98 | isCrossRepository: true, |
| 97 | 99 | headRepositoryNameWithOwner: "octocat/codething-mvp", |
| ... | ... | @@ -105,7 +107,7 @@ describe("GitHubCli.layer", () => { |
| 105 | 107 | "view", |
| 106 | 108 | "#42", |
| 107 | 109 | "--json", |
| 108 | "number,title,url,baseRefName,headRefName,state,mergedAt,updatedAt,isCrossRepository,headRepository,headRepositoryOwner", | |
| 110 | "number,title,url,baseRefName,headRefName,state,isDraft,mergedAt,updatedAt,isCrossRepository,headRepository,headRepositoryOwner", | |
| 109 | 111 | ], |
| 110 | 112 | cwd: "/repo", |
| 111 | 113 | timeoutMs: 30_000, |
apps/server/src/sourceControl/GitHubCli.ts+3-2| ... | ... | @@ -205,6 +205,7 @@ export interface GitHubPullRequestSummary { |
| 205 | 205 | readonly baseRefName: string; |
| 206 | 206 | readonly headRefName: string; |
| 207 | 207 | readonly state?: "open" | "closed" | "merged"; |
| 208 | readonly isDraft?: boolean; | |
| 208 | 209 | readonly updatedAt?: string; |
| 209 | 210 | readonly isCrossRepository?: boolean; |
| 210 | 211 | readonly headRepositoryNameWithOwner?: string | null; |
| ... | ... | @@ -366,7 +367,7 @@ export const make = Effect.gen(function* () { |
| 366 | 367 | "--limit", |
| 367 | 368 | String(input.limit ?? 1), |
| 368 | 369 | "--json", |
| 369 | "number,title,url,baseRefName,headRefName,state,mergedAt,isCrossRepository,headRepository,headRepositoryOwner", | |
| 370 | "number,title,url,baseRefName,headRefName,state,isDraft,mergedAt,isCrossRepository,headRepository,headRepositoryOwner", | |
| 370 | 371 | ], |
| 371 | 372 | }).pipe( |
| 372 | 373 | Effect.map((result) => result.stdout.trim()), |
| ... | ... | @@ -398,7 +399,7 @@ export const make = Effect.gen(function* () { |
| 398 | 399 | "view", |
| 399 | 400 | input.reference, |
| 400 | 401 | "--json", |
| 401 | "number,title,url,baseRefName,headRefName,state,mergedAt,updatedAt,isCrossRepository,headRepository,headRepositoryOwner", | |
| 402 | "number,title,url,baseRefName,headRefName,state,isDraft,mergedAt,updatedAt,isCrossRepository,headRepository,headRepositoryOwner", | |
| 402 | 403 | ], |
| 403 | 404 | }).pipe( |
| 404 | 405 | Effect.map((result) => result.stdout.trim()), |
apps/server/src/sourceControl/GitHubSourceControlProvider.test.ts+1-1| ... | ... | @@ -150,7 +150,7 @@ it.effect("uses gh json listing for non-open change request state queries", () = |
| 150 | 150 | "--limit", |
| 151 | 151 | "10", |
| 152 | 152 | "--json", |
| 153 | "number,title,url,baseRefName,headRefName,state,mergedAt,updatedAt,isCrossRepository,headRepository,headRepositoryOwner", | |
| 153 | "number,title,url,baseRefName,headRefName,state,isDraft,mergedAt,updatedAt,isCrossRepository,headRepository,headRepositoryOwner", | |
| 154 | 154 | ]); |
| 155 | 155 | assert.strictEqual(changeRequests[0]?.provider, "github"); |
| 156 | 156 | assert.strictEqual(changeRequests[0]?.state, "merged"); |
apps/server/src/sourceControl/GitHubSourceControlProvider.ts+2-1| ... | ... | @@ -30,6 +30,7 @@ function toChangeRequest(summary: GitHubCli.GitHubPullRequestSummary): ChangeReq |
| 30 | 30 | baseRefName: summary.baseRefName, |
| 31 | 31 | headRefName: summary.headRefName, |
| 32 | 32 | state: summary.state ?? "open", |
| 33 | ...(summary.isDraft === true ? { isDraft: true } : {}), | |
| 33 | 34 | updatedAt: |
| 34 | 35 | summary.updatedAt === undefined |
| 35 | 36 | ? Option.none() |
| ... | ... | @@ -153,7 +154,7 @@ export const make = Effect.gen(function* () { |
| 153 | 154 | "--limit", |
| 154 | 155 | String(input.limit ?? 20), |
| 155 | 156 | "--json", |
| 156 | "number,title,url,baseRefName,headRefName,state,mergedAt,updatedAt,isCrossRepository,headRepository,headRepositoryOwner", | |
| 157 | "number,title,url,baseRefName,headRefName,state,isDraft,mergedAt,updatedAt,isCrossRepository,headRepository,headRepositoryOwner", | |
| 157 | 158 | ], |
| 158 | 159 | }) |
| 159 | 160 | .pipe( |
apps/server/src/sourceControl/GitLabCli.ts+1| ... | ... | @@ -246,6 +246,7 @@ export interface GitLabMergeRequestSummary { |
| 246 | 246 | readonly baseRefName: string; |
| 247 | 247 | readonly headRefName: string; |
| 248 | 248 | readonly state?: "open" | "closed" | "merged"; |
| 249 | readonly isDraft?: boolean; | |
| 249 | 250 | readonly updatedAt?: Option.Option<DateTime.Utc>; |
| 250 | 251 | readonly isCrossRepository?: boolean; |
| 251 | 252 | readonly headRepositoryNameWithOwner?: string | null; |
apps/server/src/sourceControl/GitLabSourceControlProvider.ts+1| ... | ... | @@ -26,6 +26,7 @@ function toChangeRequest(summary: GitLabCli.GitLabMergeRequestSummary): ChangeRe |
| 26 | 26 | baseRefName: summary.baseRefName, |
| 27 | 27 | headRefName: summary.headRefName, |
| 28 | 28 | state: summary.state ?? "open", |
| 29 | ...(summary.isDraft === true ? { isDraft: true } : {}), | |
| 29 | 30 | updatedAt: summary.updatedAt ?? Option.none(), |
| 30 | 31 | ...(summary.isCrossRepository !== undefined |
| 31 | 32 | ? { isCrossRepository: summary.isCrossRepository } |
apps/server/src/sourceControl/azureDevOpsPullRequests.ts+3| ... | ... | @@ -14,6 +14,7 @@ export interface NormalizedAzureDevOpsPullRequestRecord { |
| 14 | 14 | readonly baseRefName: string; |
| 15 | 15 | readonly headRefName: string; |
| 16 | 16 | readonly state: "open" | "closed" | "merged"; |
| 17 | readonly isDraft?: boolean; | |
| 17 | 18 | readonly updatedAt: Option.Option<DateTime.Utc>; |
| 18 | 19 | } |
| 19 | 20 | |
| ... | ... | @@ -35,6 +36,7 @@ const AzureDevOpsPullRequestSchema = Schema.Struct({ |
| 35 | 36 | sourceRefName: TrimmedNonEmptyString, |
| 36 | 37 | targetRefName: TrimmedNonEmptyString, |
| 37 | 38 | status: Schema.String, |
| 39 | isDraft: Schema.optional(Schema.Boolean), | |
| 38 | 40 | creationDate: Schema.optional(Schema.OptionFromNullOr(Schema.DateTimeUtcFromString)), |
| 39 | 41 | closedDate: Schema.optional(Schema.OptionFromNullOr(Schema.DateTimeUtcFromString)), |
| 40 | 42 | _links: Schema.optional( |
| ... | ... | @@ -168,6 +170,7 @@ function normalizeAzureDevOpsPullRequestRecord( |
| 168 | 170 | baseRefName: normalizeRefName(raw.targetRefName), |
| 169 | 171 | headRefName: normalizeRefName(raw.sourceRefName), |
| 170 | 172 | state: normalizeAzureDevOpsPullRequestState(raw.status), |
| 173 | ...(raw.isDraft === true ? { isDraft: true } : {}), | |
| 171 | 174 | updatedAt: (raw.closedDate ?? Option.none()).pipe( |
| 172 | 175 | Option.orElse(() => raw.creationDate ?? Option.none()), |
| 173 | 176 | ), |
apps/server/src/sourceControl/bitbucketPullRequests.ts+3| ... | ... | @@ -10,6 +10,7 @@ export interface NormalizedBitbucketPullRequestRecord { |
| 10 | 10 | readonly baseRefName: string; |
| 11 | 11 | readonly headRefName: string; |
| 12 | 12 | readonly state: "open" | "closed" | "merged"; |
| 13 | readonly isDraft?: boolean; | |
| 13 | 14 | readonly updatedAt: Option.Option<DateTime.Utc>; |
| 14 | 15 | readonly isCrossRepository?: boolean; |
| 15 | 16 | readonly headRepositoryNameWithOwner?: string | null; |
| ... | ... | @@ -38,6 +39,7 @@ export const BitbucketPullRequestSchema = Schema.Struct({ |
| 38 | 39 | id: PositiveInt, |
| 39 | 40 | title: TrimmedNonEmptyString, |
| 40 | 41 | state: Schema.optional(Schema.NullOr(Schema.String)), |
| 42 | draft: Schema.optional(Schema.Boolean), | |
| 41 | 43 | updated_on: Schema.optional(Schema.OptionFromNullOr(Schema.DateTimeUtcFromString)), |
| 42 | 44 | links: Schema.Struct({ |
| 43 | 45 | html: Schema.Struct({ |
| ... | ... | @@ -98,6 +100,7 @@ export function normalizeBitbucketPullRequestRecord( |
| 98 | 100 | baseRefName: raw.destination.branch.name, |
| 99 | 101 | headRefName: raw.source.branch.name, |
| 100 | 102 | state: normalizeBitbucketPullRequestState(raw.state), |
| 103 | ...(raw.draft === true ? { isDraft: true } : {}), | |
| 101 | 104 | updatedAt: raw.updated_on ?? Option.none(), |
| 102 | 105 | ...(isCrossRepository ? { isCrossRepository: true } : {}), |
| 103 | 106 | ...(headRepositoryNameWithOwner ? { headRepositoryNameWithOwner } : {}), |
apps/server/src/sourceControl/gitHubPullRequests.ts+3| ... | ... | @@ -14,6 +14,7 @@ export interface NormalizedGitHubPullRequestRecord { |
| 14 | 14 | readonly baseRefName: string; |
| 15 | 15 | readonly headRefName: string; |
| 16 | 16 | readonly state: "open" | "closed" | "merged"; |
| 17 | readonly isDraft?: boolean; | |
| 17 | 18 | readonly updatedAt: Option.Option<DateTime.Utc>; |
| 18 | 19 | readonly isCrossRepository?: boolean; |
| 19 | 20 | readonly headRepositoryNameWithOwner?: string | null; |
| ... | ... | @@ -27,6 +28,7 @@ const GitHubPullRequestSchema = Schema.Struct({ |
| 27 | 28 | baseRefName: TrimmedNonEmptyString, |
| 28 | 29 | headRefName: TrimmedNonEmptyString, |
| 29 | 30 | state: Schema.optional(Schema.NullOr(Schema.String)), |
| 31 | isDraft: Schema.optional(Schema.Boolean), | |
| 30 | 32 | mergedAt: Schema.optional(Schema.NullOr(Schema.String)), |
| 31 | 33 | updatedAt: Schema.optional(Schema.OptionFromNullOr(Schema.DateTimeUtcFromString)), |
| 32 | 34 | isCrossRepository: Schema.optional(Schema.Boolean), |
| ... | ... | @@ -93,6 +95,7 @@ function normalizeGitHubPullRequestRecord( |
| 93 | 95 | baseRefName: raw.baseRefName, |
| 94 | 96 | headRefName: raw.headRefName, |
| 95 | 97 | state: normalizeGitHubPullRequestState(raw), |
| 98 | ...(raw.isDraft === true ? { isDraft: true } : {}), | |
| 96 | 99 | updatedAt: raw.updatedAt ?? Option.none(), |
| 97 | 100 | ...(typeof raw.isCrossRepository === "boolean" |
| 98 | 101 | ? { isCrossRepository: raw.isCrossRepository } |
apps/server/src/sourceControl/gitLabMergeRequests.ts+4| ... | ... | @@ -14,6 +14,7 @@ export interface NormalizedGitLabMergeRequestRecord { |
| 14 | 14 | readonly baseRefName: string; |
| 15 | 15 | readonly headRefName: string; |
| 16 | 16 | readonly state: "open" | "closed" | "merged"; |
| 17 | readonly isDraft?: boolean; | |
| 17 | 18 | readonly updatedAt: Option.Option<DateTime.Utc>; |
| 18 | 19 | readonly isCrossRepository?: boolean; |
| 19 | 20 | readonly headRepositoryNameWithOwner?: string | null; |
| ... | ... | @@ -41,6 +42,8 @@ const GitLabMergeRequestSchema = Schema.Struct({ |
| 41 | 42 | source_branch: TrimmedNonEmptyString, |
| 42 | 43 | target_branch: TrimmedNonEmptyString, |
| 43 | 44 | state: Schema.optional(Schema.NullOr(Schema.String)), |
| 45 | draft: Schema.optional(Schema.Boolean), | |
| 46 | work_in_progress: Schema.optional(Schema.Boolean), | |
| 44 | 47 | updated_at: Schema.optional(Schema.OptionFromNullOr(Schema.DateTimeUtcFromString)), |
| 45 | 48 | source_project_id: Schema.optional(Schema.NullOr(Schema.Number)), |
| 46 | 49 | target_project_id: Schema.optional(Schema.NullOr(Schema.Number)), |
| ... | ... | @@ -108,6 +111,7 @@ function normalizeGitLabMergeRequestRecord( |
| 108 | 111 | baseRefName: raw.target_branch, |
| 109 | 112 | headRefName: raw.source_branch, |
| 110 | 113 | state: normalizeGitLabMergeRequestState(raw.state), |
| 114 | ...(raw.draft === true || raw.work_in_progress === true ? { isDraft: true } : {}), | |
| 111 | 115 | updatedAt: raw.updated_at ?? Option.none(), |
| 112 | 116 | ...(typeof isCrossRepository === "boolean" ? { isCrossRepository } : {}), |
| 113 | 117 | ...(sourceProjectPath ? { headRepositoryNameWithOwner: sourceProjectPath } : {}), |
apps/web/src/components/Sidebar.tsx+1-1| ... | ... | @@ -954,7 +954,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { |
| 954 | 954 | linkedPullRequestStatus, |
| 955 | 955 | }); |
| 956 | 956 | const prStatus = prStatusIndicator(pr, prProvider); |
| 957 | const settledPrHoverClass = pr ? settledPrHoverColorClass(pr.state) : undefined; | |
| 957 | const settledPrHoverClass = pr ? settledPrHoverColorClass(pr.state, pr.isDraft) : undefined; | |
| 958 | 958 | useEffect(() => { |
| 959 | 959 | const nextSnapshot = nextThreadChangeRequestSnapshot({ |
| 960 | 960 | threadBranch: thread.branch, |
apps/web/src/components/ThreadStatusIndicators.test.ts+28| ... | ... | @@ -10,6 +10,7 @@ import { |
| 10 | 10 | resolveDisplayedThreadPrProvider, |
| 11 | 11 | resolveThreadPr, |
| 12 | 12 | settledPrHoverColorClass, |
| 13 | threadChangeRequestSnapshotsEqual, | |
| 13 | 14 | threadChangeRequestSnapshotsAtom, |
| 14 | 15 | type ThreadChangeRequestSnapshot, |
| 15 | 16 | } from "./ThreadStatusIndicators"; |
| ... | ... | @@ -559,6 +560,18 @@ describe("resolveDisplayedThreadPr + nextThreadChangeRequestSnapshot", () => { |
| 559 | 560 | }); |
| 560 | 561 | expect(displayed?.state).toBe("merged"); |
| 561 | 562 | }); |
| 563 | ||
| 564 | it("refreshes a cached snapshot when a pull request becomes ready", () => { | |
| 565 | const readyPr = { ...mergedPr, state: "open" as const }; | |
| 566 | const draftPr = { ...readyPr, isDraft: true }; | |
| 567 | ||
| 568 | expect( | |
| 569 | threadChangeRequestSnapshotsEqual( | |
| 570 | snapshotFor(featureBranch, draftPr), | |
| 571 | snapshotFor(featureBranch, readyPr), | |
| 572 | ), | |
| 573 | ).toBe(false); | |
| 574 | }); | |
| 562 | 575 | }); |
| 563 | 576 | |
| 564 | 577 | describe("threadChangeRequestSnapshotsAtom", () => { |
| ... | ... | @@ -600,6 +613,17 @@ describe("prStatusIndicator", () => { |
| 600 | 613 | "text-red-600", |
| 601 | 614 | ); |
| 602 | 615 | }); |
| 616 | ||
| 617 | it("uses gray and draft wording for draft pull requests", () => { | |
| 618 | const draftPr = status().pr; | |
| 619 | if (!draftPr) throw new Error("Expected pull request fixture"); | |
| 620 | ||
| 621 | expect(prStatusIndicator({ ...draftPr, isDraft: true }, undefined)).toMatchObject({ | |
| 622 | label: "PR draft", | |
| 623 | colorClass: "text-zinc-500 dark:text-zinc-400/80", | |
| 624 | tooltipLead: "PR #42 - Draft", | |
| 625 | }); | |
| 626 | }); | |
| 603 | 627 | }); |
| 604 | 628 | |
| 605 | 629 | describe("settledPrHoverColorClass", () => { |
| ... | ... | @@ -610,4 +634,8 @@ describe("settledPrHoverColorClass", () => { |
| 610 | 634 | ] as const)("restores the %s pull request color on row hover", (state, colorClass) => { |
| 611 | 635 | expect(settledPrHoverColorClass(state)).toContain(`group-hover/v2-row:${colorClass}`); |
| 612 | 636 | }); |
| 637 | ||
| 638 | it("keeps draft pull requests gray on row hover", () => { | |
| 639 | expect(settledPrHoverColorClass("open", true)).toContain("group-hover/v2-row:text-zinc-500"); | |
| 640 | }); | |
| 613 | 641 | }); |
apps/web/src/components/ThreadStatusIndicators.tsx+17-6| ... | ... | @@ -84,9 +84,15 @@ export function useLinkedThreadPullRequest( |
| 84 | 84 | ); |
| 85 | 85 | } |
| 86 | 86 | |
| 87 | export function settledPrHoverColorClass(state: NonNullable<ThreadPr>["state"]): string { | |
| 87 | export function settledPrHoverColorClass( | |
| 88 | state: NonNullable<ThreadPr>["state"], | |
| 89 | isDraft = false, | |
| 90 | ): string { | |
| 88 | 91 | switch (state) { |
| 89 | 92 | case "open": |
| 93 | if (isDraft) { | |
| 94 | return "group-hover/v2-row:text-zinc-500 dark:group-hover/v2-row:text-zinc-400/80"; | |
| 95 | } | |
| 90 | 96 | return "group-hover/v2-row:text-emerald-600 dark:group-hover/v2-row:text-emerald-300/90"; |
| 91 | 97 | case "merged": |
| 92 | 98 | return "group-hover/v2-row:text-violet-600 dark:group-hover/v2-row:text-violet-300/90"; |
| ... | ... | @@ -99,12 +105,13 @@ export function prStatusIndicator( |
| 99 | 105 | pr: ThreadPr, |
| 100 | 106 | provider: VcsStatusResult["sourceControlProvider"] | null | undefined, |
| 101 | 107 | ): PrStatusIndicator | null { |
| 102 | function formatPrState(state: NonNullable<ThreadPr>["state"]): string { | |
| 103 | return state.charAt(0).toUpperCase() + state.slice(1); | |
| 108 | function formatPrState(pr: NonNullable<ThreadPr>): string { | |
| 109 | if (pr.state === "open" && pr.isDraft === true) return "Draft"; | |
| 110 | return pr.state.charAt(0).toUpperCase() + pr.state.slice(1); | |
| 104 | 111 | } |
| 105 | 112 | |
| 106 | 113 | function formatPrStatusLead(pr: NonNullable<ThreadPr>, changeRequestShortName: string): string { |
| 107 | return `${changeRequestShortName} #${pr.number} - ${formatPrState(pr.state)}`; | |
| 114 | return `${changeRequestShortName} #${pr.number} - ${formatPrState(pr)}`; | |
| 108 | 115 | } |
| 109 | 116 | if (!pr) return null; |
| 110 | 117 | const presentation = resolveChangeRequestPresentation(provider); |
| ... | ... | @@ -113,9 +120,12 @@ export function prStatusIndicator( |
| 113 | 120 | const tooltip = `${tooltipLead}: ${pr.title}`; |
| 114 | 121 | |
| 115 | 122 | if (pr.state === "open") { |
| 123 | const isDraft = pr.isDraft === true; | |
| 116 | 124 | return { |
| 117 | label: `${presentation.shortName} open`, | |
| 118 | colorClass: "text-emerald-600 dark:text-emerald-300/90", | |
| 125 | label: `${presentation.shortName} ${isDraft ? "draft" : "open"}`, | |
| 126 | colorClass: isDraft | |
| 127 | ? "text-zinc-500 dark:text-zinc-400/80" | |
| 128 | : "text-emerald-600 dark:text-emerald-300/90", | |
| 119 | 129 | tooltip, |
| 120 | 130 | tooltipLead, |
| 121 | 131 | tooltipTitle: pr.title, |
| ... | ... | @@ -230,6 +240,7 @@ export function threadChangeRequestSnapshotsEqual( |
| 230 | 240 | left.pr.baseRef === right.pr.baseRef && |
| 231 | 241 | left.pr.headRef === right.pr.headRef && |
| 232 | 242 | left.pr.state === right.pr.state && |
| 243 | left.pr.isDraft === right.pr.isDraft && | |
| 233 | 244 | (left.pr.updatedAt ?? null) === (right.pr.updatedAt ?? null) && |
| 234 | 245 | sourceControlProvidersEqual(left.sourceControlProvider, right.sourceControlProvider) && |
| 235 | 246 | linkedPullRequestsEqual(left.linkedPullRequest, right.linkedPullRequest) |
apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx+8-2| ... | ... | @@ -625,7 +625,13 @@ export function PullRequestDetailPanel({ |
| 625 | 625 | () => |
| 626 | 626 | resolvedCoreDetail === null || sharedSummary === null || sharedSummary === resolvedCoreDetail |
| 627 | 627 | ? resolvedCoreDetail |
| 628 | : { ...resolvedCoreDetail, ...sharedSummary }, | |
| 628 | : { | |
| 629 | ...resolvedCoreDetail, | |
| 630 | ...sharedSummary, | |
| 631 | // A summary may come from an older server that does not report draft state. Keep the | |
| 632 | // detail's required value instead of making the complete detail shape partial. | |
| 633 | isDraft: sharedSummary.isDraft ?? resolvedCoreDetail.isDraft, | |
| 634 | }, | |
| 629 | 635 | [resolvedCoreDetail, sharedSummary], |
| 630 | 636 | ); |
| 631 | 637 | const activity = activityQuery.data; |
| ... | ... | @@ -1297,7 +1303,7 @@ export function PullRequestDetailPanel({ |
| 1297 | 1303 | !conflicting && |
| 1298 | 1304 | allowedMergeMethods.length > 1; |
| 1299 | 1305 | // The pull request number carries this state in the overview and the right-panel tab mirrors |
| 1300 | // it. The conflict action is separate from this state: an open pull request remains green. | |
| 1306 | // it. Conflicts take the action slot while they need a person, but do not change the PR state. | |
| 1301 | 1307 | const statePresentation = detail |
| 1302 | 1308 | ? resolvePullRequestState({ state: detail.state, isDraft: detail.isDraft }) |
| 1303 | 1309 | : null; |
apps/web/src/components/pullRequest/pullRequestPresentation.tsx+3-3| ... | ... | @@ -33,9 +33,9 @@ interface StatePresentation { |
| 33 | 33 | } |
| 34 | 34 | |
| 35 | 35 | /** |
| 36 | * How a pull request's state reads on this page. Open, closed and merged use the same ink as | |
| 37 | * the thread badge in `ThreadStatusIndicators`, so one pull request cannot look like two | |
| 38 | * different things in two places; draft and conflicts are states that badge never shows. | |
| 36 | * How a pull request's state reads on this page. Open, closed, merged, and draft use the same | |
| 37 | * ink as the thread badge in `ThreadStatusIndicators`, so one pull request cannot look like two | |
| 38 | * different things in two places. | |
| 39 | 39 | * |
| 40 | 40 | * Draft outranks conflicts: a draft is not heading for a merge yet, so conflicts only surface |
| 41 | 41 | * once it is real work. |
packages/client-runtime/src/state/pullRequests.ts+1| ... | ... | @@ -69,6 +69,7 @@ export function pullRequestDetailToVcsStatus( |
| 69 | 69 | baseRef: detail.baseBranch, |
| 70 | 70 | headRef: detail.headBranch, |
| 71 | 71 | state: detail.state, |
| 72 | ...(detail.isDraft === true ? { isDraft: true } : {}), | |
| 72 | 73 | updatedAt: detail.updatedAt, |
| 73 | 74 | }; |
| 74 | 75 | } |
packages/contracts/src/git.ts+2| ... | ... | @@ -198,6 +198,8 @@ const VcsStatusChangeRequest = Schema.Struct({ |
| 198 | 198 | baseRef: TrimmedNonEmptyStringSchema, |
| 199 | 199 | headRef: TrimmedNonEmptyStringSchema, |
| 200 | 200 | state: VcsStatusChangeRequestState, |
| 201 | /** Optional for compatibility with older servers and providers. */ | |
| 202 | isDraft: Schema.optional(Schema.Boolean), | |
| 201 | 203 | /** |
| 202 | 204 | * Last provider-side activity (ISO). For a merged/closed change request |
| 203 | 205 | * this bounds when it reached that state, so clients can tell a PR that |
packages/contracts/src/pullRequest.ts+2| ... | ... | @@ -634,6 +634,8 @@ export const PullRequestSummary = Schema.Struct({ |
| 634 | 634 | title: TrimmedNonEmptyString, |
| 635 | 635 | url: TrimmedNonEmptyString, |
| 636 | 636 | state: PullRequestState, |
| 637 | /** Present when the host says the open pull request is still a draft. */ | |
| 638 | isDraft: Schema.optional(Schema.Boolean), | |
| 637 | 639 | headBranch: TrimmedNonEmptyString, |
| 638 | 640 | baseBranch: TrimmedNonEmptyString, |
| 639 | 641 | updatedAt: IsoDateTime, |
packages/contracts/src/sourceControl.ts+2| ... | ... | @@ -29,6 +29,8 @@ export const ChangeRequest = Schema.Struct({ |
| 29 | 29 | baseRefName: TrimmedNonEmptyString, |
| 30 | 30 | headRefName: TrimmedNonEmptyString, |
| 31 | 31 | state: ChangeRequestState, |
| 32 | /** Present when the provider can tell that an open change request is still a draft. */ | |
| 33 | isDraft: Schema.optional(Schema.Boolean), | |
| 32 | 34 | updatedAt: Schema.Option(Schema.DateTimeUtc), |
| 33 | 35 | isCrossRepository: Schema.optional(Schema.Boolean), |
| 34 | 36 | headRepositoryNameWithOwner: Schema.optional(Schema.NullOr(TrimmedNonEmptyString)), |
scripts/build-desktop-artifact.ts+16-1| ... | ... | @@ -2863,7 +2863,7 @@ export const packWindowsServerAsar = Effect.fn("packWindowsServerAsar")(function |
| 2863 | 2863 | readonly arch: typeof BuildArch.Type; |
| 2864 | 2864 | }) { |
| 2865 | 2865 | const fs = yield* FileSystem.FileSystem; |
| 2866 | yield* Effect.tryPromise({ | |
| 2866 | const archiveStream = yield* Effect.tryPromise({ | |
| 2867 | 2867 | try: () => |
| 2868 | 2868 | createPackageWithOptions(input.sourceDir, input.asarPath, { |
| 2869 | 2869 | dot: true, |
| ... | ... | @@ -2872,6 +2872,21 @@ export const packWindowsServerAsar = Effect.fn("packWindowsServerAsar")(function |
| 2872 | 2872 | }), |
| 2873 | 2873 | catch: (cause) => new WindowsServerSidecarPackError({ asarPath: input.asarPath, cause }), |
| 2874 | 2874 | }); |
| 2875 | yield* Effect.tryPromise({ | |
| 2876 | try: () => | |
| 2877 | new Promise<void>((resolve, reject) => { | |
| 2878 | const stream = archiveStream as NodeJS.WritableStream & { | |
| 2879 | readonly writableFinished?: boolean; | |
| 2880 | }; | |
| 2881 | if (stream.writableFinished === true) { | |
| 2882 | resolve(); | |
| 2883 | return; | |
| 2884 | } | |
| 2885 | stream.once("finish", resolve); | |
| 2886 | stream.once("error", reject); | |
| 2887 | }), | |
| 2888 | catch: (cause) => new WindowsServerSidecarPackError({ asarPath: input.asarPath, cause }), | |
| 2889 | }); | |
| 2875 | 2890 | const unpackedDirPath = `${input.asarPath}.unpacked`; |
| 2876 | 2891 | if (!(yield* fs.exists(unpackedDirPath))) { |
| 2877 | 2892 | return yield* new WindowsServerSidecarPackError({ |