Skip to content

Commit 7ed5503

Browse files
committed
refactor
1 parent 44573e7 commit 7ed5503

File tree

16 files changed

+726
-1578
lines changed

16 files changed

+726
-1578
lines changed

packages/cli/src/commands/submit.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
green,
77
printInstallInstructions,
88
} from "../utils/output";
9-
import { createJJ, unwrap } from "../utils/run";
9+
import { createStacks, unwrap } from "../utils/run";
1010

1111
export async function submit(
1212
flags: Record<string, string | boolean>,
@@ -29,7 +29,7 @@ export async function submit(
2929
console.log();
3030

3131
const result = unwrap(
32-
await createJJ().submitStack({ draft: Boolean(flags.draft) }),
32+
await createStacks().submitStack({ draft: Boolean(flags.draft) }),
3333
);
3434

3535
for (const pr of result.prs) {

packages/cli/src/commands/sync.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { createStackManager } from "@array/core";
12
import { dim, formatSuccess, yellow } from "../utils/output";
23
import { createJJ, unwrap } from "../utils/run";
34

@@ -27,7 +28,8 @@ export async function sync(): Promise<void> {
2728
console.log(dim(`Removed ${empty.length} empty change(s)`));
2829
}
2930

30-
const updateResult = await jj.updateStackComments();
31+
const stacks = createStackManager(jj);
32+
const updateResult = await stacks.updateStackComments();
3133
if (updateResult.ok && updateResult.value.updated > 0) {
3234
console.log(formatSuccess("Updated stack comments"));
3335
}

packages/cli/src/utils/run.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
1-
import { type Changeset, changeLabel, JJ, type Result } from "@array/core";
1+
import {
2+
type Changeset,
3+
changeLabel,
4+
createStackManager,
5+
JJ,
6+
type Result,
7+
type StackManager,
8+
} from "@array/core";
29
import { cyan, dim, formatError } from "./output";
310

411
export function createJJ(): JJ {
512
return new JJ({ cwd: process.cwd() });
613
}
714

15+
export function createStacks(): StackManager {
16+
return createStackManager(createJJ());
17+
}
18+
819
export function unwrap<T>(result: Result<T>): T {
920
if (!result.ok) {
1021
console.error(formatError(result.error.message));

packages/core/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121
"build:types": "tsc --emitDeclarationOnly --declaration --outDir dist",
2222
"dev": "bun build ./src/index.ts --outdir ./dist --target bun --watch",
2323
"typecheck": "tsc --noEmit",
24-
"test": "bun test --concurrent",
24+
"test": "bun test --concurrent --timeout 15000",
2525
"test:unit": "bun test --concurrent tests/unit",
26-
"test:integration": "bun test --concurrent tests/integration",
26+
"test:integration": "bun test --concurrent --timeout 15000 tests/integration",
2727
"test:fixtures": "bun run tests/fixtures/generate.ts"
2828
},
2929
"devDependencies": {

packages/core/src/github.ts

Lines changed: 112 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export interface PRStatus {
1717
reviewDecision: "approved" | "changes_requested" | "review_required" | null;
1818
title: string;
1919
baseRefName: string;
20+
url: string;
2021
}
2122

2223
export interface RepoInfo {
@@ -241,7 +242,7 @@ export class GitHub {
241242
"view",
242243
String(prNumber),
243244
"--json",
244-
"number,state,reviewDecision,title,baseRefName",
245+
"number,state,reviewDecision,title,baseRefName,url",
245246
],
246247
{ cwd: this.cwd },
247248
);
@@ -262,6 +263,7 @@ export class GitHub {
262263
reviewDecision: data.reviewDecision?.toLowerCase() ?? null,
263264
title: data.title,
264265
baseRefName: data.baseRefName,
266+
url: data.url,
265267
});
266268
} catch (e) {
267269
return err(
@@ -325,74 +327,151 @@ export class GitHub {
325327
}
326328
}
327329

330+
async updatePR(
331+
prNumber: number,
332+
options: { title?: string; body?: string; base?: string },
333+
): Promise<Result<void>> {
334+
const args = ["pr", "edit", String(prNumber)];
335+
if (options.title) {
336+
args.push("--title", options.title);
337+
}
338+
if (options.body) {
339+
args.push("--body", options.body);
340+
}
341+
if (options.base) {
342+
args.push("--base", options.base);
343+
}
344+
345+
try {
346+
const result = await this.executor.execute("gh", args, { cwd: this.cwd });
347+
if (result.exitCode !== 0) {
348+
return err(
349+
createError("COMMAND_FAILED", `gh pr edit failed: ${result.stderr}`),
350+
);
351+
}
352+
return ok(undefined);
353+
} catch (e) {
354+
return err(createError("COMMAND_FAILED", `gh pr edit failed: ${e}`));
355+
}
356+
}
357+
328358
async updatePRBase(prNumber: number, newBase: string): Promise<Result<void>> {
359+
return this.updatePR(prNumber, { base: newBase });
360+
}
361+
362+
async createPR(options: {
363+
head: string;
364+
title?: string;
365+
body?: string;
366+
base?: string;
367+
draft?: boolean;
368+
}): Promise<Result<{ url: string; number: number }>> {
369+
const args = ["pr", "create", "--head", options.head];
370+
if (options.title) {
371+
args.push("--title", options.title);
372+
}
373+
if (options.body) {
374+
args.push("--body", options.body);
375+
}
376+
if (options.base) {
377+
args.push("--base", options.base);
378+
}
379+
if (options.draft) {
380+
args.push("--draft");
381+
}
382+
329383
try {
330-
const result = await this.executor.execute(
331-
"gh",
332-
["pr", "edit", String(prNumber), "--base", newBase],
333-
{ cwd: this.cwd },
334-
);
384+
const result = await this.executor.execute("gh", args, {
385+
cwd: this.cwd,
386+
});
335387

336388
if (result.exitCode !== 0) {
337389
return err(
338390
createError(
339391
"COMMAND_FAILED",
340-
`Failed to update PR base: ${result.stderr}`,
392+
`gh pr create failed: ${result.stderr}`,
341393
),
342394
);
343395
}
344396

345-
return ok(undefined);
346-
} catch (e) {
347-
return err(
348-
createError("COMMAND_FAILED", `Failed to update PR base: ${e}`),
397+
const urlMatch = result.stdout.match(
398+
/https:\/\/github\.com\/[^\s]+\/pull\/(\d+)/,
349399
);
400+
const url = urlMatch ? urlMatch[0] : result.stdout.trim();
401+
const number = urlMatch ? Number.parseInt(urlMatch[1], 10) : 0;
402+
403+
return ok({ url, number });
404+
} catch (e) {
405+
return err(createError("COMMAND_FAILED", `gh pr create failed: ${e}`));
350406
}
351407
}
352408

353409
async getPRForBranch(branchName: string): Promise<Result<PRStatus | null>> {
410+
const result = await this.batchGetPRsForBranches([branchName]);
411+
if (!result.ok) return result;
412+
return ok(result.value.get(branchName) ?? null);
413+
}
414+
415+
async batchGetPRsForBranches(
416+
branchNames: string[],
417+
): Promise<Result<Map<string, PRStatus>>> {
418+
if (branchNames.length === 0) {
419+
return ok(new Map());
420+
}
421+
354422
try {
355423
const result = await this.executor.execute(
356424
"gh",
357425
[
358426
"pr",
359427
"list",
360-
"--head",
361-
branchName,
428+
"--state",
429+
"all",
362430
"--json",
363-
"number,state,reviewDecision,title,baseRefName",
431+
"number,state,reviewDecision,title,baseRefName,headRefName,url",
364432
"--limit",
365-
"1",
433+
"100",
366434
],
367435
{ cwd: this.cwd },
368436
);
369437

370438
if (result.exitCode !== 0) {
371439
return err(
372-
createError(
373-
"COMMAND_FAILED",
374-
`Failed to find PR for branch: ${result.stderr}`,
375-
),
440+
createError("COMMAND_FAILED", `Failed to list PRs: ${result.stderr}`),
376441
);
377442
}
378443

379-
const prs = JSON.parse(result.stdout);
380-
if (prs.length === 0) {
381-
return ok(null);
444+
const prs = JSON.parse(result.stdout) as Array<{
445+
number: number;
446+
state: string;
447+
reviewDecision: string | null;
448+
title: string;
449+
baseRefName: string;
450+
headRefName: string;
451+
url: string;
452+
}>;
453+
454+
const branchSet = new Set(branchNames);
455+
const prMap = new Map<string, PRStatus>();
456+
457+
for (const pr of prs) {
458+
if (branchSet.has(pr.headRefName)) {
459+
prMap.set(pr.headRefName, {
460+
number: pr.number,
461+
state: pr.state.toLowerCase() as "open" | "closed" | "merged",
462+
reviewDecision:
463+
(pr.reviewDecision?.toLowerCase() as PRStatus["reviewDecision"]) ??
464+
null,
465+
title: pr.title,
466+
baseRefName: pr.baseRefName,
467+
url: pr.url,
468+
});
469+
}
382470
}
383471

384-
const data = prs[0];
385-
return ok({
386-
number: data.number,
387-
state: data.state.toLowerCase() as "open" | "closed" | "merged",
388-
reviewDecision: data.reviewDecision?.toLowerCase() ?? null,
389-
title: data.title,
390-
baseRefName: data.baseRefName,
391-
});
472+
return ok(prMap);
392473
} catch (e) {
393-
return err(
394-
createError("COMMAND_FAILED", `Failed to find PR for branch: ${e}`),
395-
);
474+
return err(createError("COMMAND_FAILED", `Failed to list PRs: ${e}`));
396475
}
397476
}
398477
}

packages/core/src/index.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -93,15 +93,16 @@ export {
9393
type LogResult,
9494
} from "./log";
9595
export {
96+
type Changeset,
9697
detectError,
9798
filterRootChanges,
9899
isRootChange,
99100
parseChangesets,
100101
parseConflicts,
101102
parseFileChanges,
102-
parseStatus,
103103
ROOT_CHANGE_ID,
104104
} from "./parser";
105+
export { generatePRBody } from "./pr-body";
105106
export {
106107
createError,
107108
err,
@@ -126,16 +127,12 @@ export {
126127
type StackEntry,
127128
type StackEntryStatus,
128129
} from "./stack-comment";
129-
export {
130-
BOOKMARK_TEMPLATE,
131-
CHANGESET_TEMPLATE,
132-
STATUS_TEMPLATE,
133-
} from "./templates";
130+
export { createStackManager, StackManager } from "./stacks";
131+
export { CHANGESET_JSON_TEMPLATE } from "./templates";
134132
export type {
135133
Author,
136134
Bookmark,
137135
BookmarkOptions,
138-
Changeset,
139136
ChangesetStatus,
140137
ConflictInfo,
141138
CreateOptions,

0 commit comments

Comments
 (0)