@akiojin/gwt 2.10.0 → 2.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/ui/components/App.d.ts.map +1 -1
- package/dist/cli/ui/components/App.js +5 -47
- package/dist/cli/ui/components/App.js.map +1 -1
- package/dist/cli/ui/components/common/Input.d.ts +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.d.ts +1 -2
- package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.js +19 -14
- package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/ModelSelectorScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/ModelSelectorScreen.js +6 -6
- package/dist/cli/ui/components/screens/ModelSelectorScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/PRCleanupScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/PRCleanupScreen.js +0 -3
- package/dist/cli/ui/components/screens/PRCleanupScreen.js.map +1 -1
- package/dist/cli/ui/hooks/useGitData.d.ts.map +1 -1
- package/dist/cli/ui/hooks/useGitData.js +43 -2
- package/dist/cli/ui/hooks/useGitData.js.map +1 -1
- package/dist/cli/ui/types.d.ts +16 -4
- package/dist/cli/ui/types.d.ts.map +1 -1
- package/dist/cli/ui/utils/branchFormatter.d.ts.map +1 -1
- package/dist/cli/ui/utils/branchFormatter.js +124 -28
- package/dist/cli/ui/utils/branchFormatter.js.map +1 -1
- package/dist/cli/ui/utils/modelOptions.d.ts.map +1 -1
- package/dist/cli/ui/utils/modelOptions.js.map +1 -1
- package/dist/client/assets/{index-CNWntAlF.js → index-Dl798X5w.js} +1 -1
- package/dist/client/index.html +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js.map +1 -1
- package/dist/git.d.ts +6 -0
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +40 -5
- package/dist/git.js.map +1 -1
- package/dist/web/client/src/components/BranchGraph.js +1 -1
- package/dist/web/client/src/components/BranchGraph.js.map +1 -1
- package/dist/web/client/src/pages/BranchDetailPage.d.ts.map +1 -1
- package/dist/web/client/src/pages/BranchDetailPage.js +8 -3
- package/dist/web/client/src/pages/BranchDetailPage.js.map +1 -1
- package/dist/web/server/routes/sessions.d.ts.map +1 -1
- package/dist/web/server/routes/sessions.js +4 -2
- package/dist/web/server/routes/sessions.js.map +1 -1
- package/dist/worktree.d.ts.map +1 -1
- package/dist/worktree.js +31 -44
- package/dist/worktree.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/ui/__tests__/acceptance/navigation.acceptance.test.tsx +9 -17
- package/src/cli/ui/__tests__/components/App.protected-branch.test.tsx +14 -20
- package/src/cli/ui/__tests__/components/App.shortcuts.test.tsx +14 -44
- package/src/cli/ui/__tests__/components/App.test.tsx +8 -15
- package/src/cli/ui/__tests__/components/ModelSelectorScreen.initial.test.tsx +12 -5
- package/src/cli/ui/__tests__/components/common/Confirm.test.tsx +3 -2
- package/src/cli/ui/__tests__/components/common/ErrorBoundary.test.tsx +3 -2
- package/src/cli/ui/__tests__/components/common/Input.test.tsx +3 -2
- package/src/cli/ui/__tests__/components/common/LoadingIndicator.test.tsx +15 -14
- package/src/cli/ui/__tests__/components/common/Select.memo.test.tsx +3 -3
- package/src/cli/ui/__tests__/components/common/Select.test.tsx +1 -4
- package/src/cli/ui/__tests__/components/parts/Footer.test.tsx +3 -2
- package/src/cli/ui/__tests__/components/parts/Header.test.tsx +3 -2
- package/src/cli/ui/__tests__/components/parts/ScrollableList.test.tsx +3 -2
- package/src/cli/ui/__tests__/components/parts/Stats.test.tsx +3 -2
- package/src/cli/ui/__tests__/components/screens/AIToolSelectorScreen.test.tsx +3 -2
- package/src/cli/ui/__tests__/components/screens/BranchCreatorScreen.test.tsx +3 -2
- package/src/cli/ui/__tests__/components/screens/BranchListScreen.test.tsx +33 -43
- package/src/cli/ui/__tests__/components/screens/ExecutionModeSelectorScreen.test.tsx +3 -2
- package/src/cli/ui/__tests__/components/screens/PRCleanupScreen.test.tsx +3 -2
- package/src/cli/ui/__tests__/components/screens/SessionSelectorScreen.test.tsx +3 -2
- package/src/cli/ui/__tests__/hooks/useScreenState.test.ts +18 -17
- package/src/cli/ui/__tests__/hooks/useTerminalSize.test.ts +3 -2
- package/src/cli/ui/__tests__/integration/edgeCases.test.tsx +5 -12
- package/src/cli/ui/__tests__/integration/navigation.test.tsx +15 -10
- package/src/cli/ui/__tests__/integration/realtimeUpdate.test.tsx +3 -2
- package/src/cli/ui/__tests__/performance/branchList.performance.test.tsx +0 -4
- package/src/cli/ui/__tests__/performance/useMemoOptimization.test.tsx +3 -5
- package/src/cli/ui/__tests__/utils/branchFormatter.test.ts +24 -23
- package/src/cli/ui/components/App.tsx +3 -74
- package/src/cli/ui/components/common/Input.tsx +1 -1
- package/src/cli/ui/components/screens/BranchListScreen.tsx +32 -22
- package/src/cli/ui/components/screens/ModelSelectorScreen.tsx +46 -49
- package/src/cli/ui/components/screens/PRCleanupScreen.tsx +0 -3
- package/src/cli/ui/hooks/useGitData.ts +59 -1
- package/src/cli/ui/screens/__tests__/BranchActionSelectorScreen.test.tsx +3 -2
- package/src/cli/ui/types.ts +24 -5
- package/src/cli/ui/utils/branchFormatter.ts +123 -28
- package/src/cli/ui/utils/modelOptions.test.ts +4 -6
- package/src/cli/ui/utils/modelOptions.ts +1 -2
- package/src/config/index.ts +2 -1
- package/src/git.ts +56 -16
- package/src/web/client/src/components/BranchGraph.tsx +1 -1
- package/src/web/client/src/pages/BranchDetailPage.tsx +8 -3
- package/src/web/server/routes/sessions.ts +12 -5
- package/src/worktree.ts +31 -59
- package/dist/cli/ui/components/screens/WorktreeManagerScreen.d.ts +0 -20
- package/dist/cli/ui/components/screens/WorktreeManagerScreen.d.ts.map +0 -1
- package/dist/cli/ui/components/screens/WorktreeManagerScreen.js +0 -65
- package/dist/cli/ui/components/screens/WorktreeManagerScreen.js.map +0 -1
- package/src/cli/ui/__tests__/components/screens/WorktreeManagerScreen.test.tsx +0 -151
- package/src/cli/ui/components/screens/WorktreeManagerScreen.tsx +0 -117
package/src/worktree.ts
CHANGED
|
@@ -6,10 +6,9 @@ import {
|
|
|
6
6
|
WorktreeConfig,
|
|
7
7
|
WorktreeWithPR,
|
|
8
8
|
CleanupTarget,
|
|
9
|
-
MergedPullRequest,
|
|
10
9
|
CleanupReason,
|
|
11
10
|
} from "./cli/ui/types.js";
|
|
12
|
-
import { getPullRequestByBranch
|
|
11
|
+
import { getPullRequestByBranch } from "./github.js";
|
|
13
12
|
import {
|
|
14
13
|
hasUncommittedChanges,
|
|
15
14
|
hasUnpushedCommits,
|
|
@@ -27,6 +26,23 @@ import { getConfig } from "./config/index.js";
|
|
|
27
26
|
import { GIT_CONFIG } from "./config/constants.js";
|
|
28
27
|
import { startSpinner } from "./utils/spinner.js";
|
|
29
28
|
|
|
29
|
+
async function getUpstreamBranch(branch: string): Promise<string | null> {
|
|
30
|
+
try {
|
|
31
|
+
const result = await execa("git", [
|
|
32
|
+
"rev-parse",
|
|
33
|
+
"--abbrev-ref",
|
|
34
|
+
`${branch}@{upstream}`,
|
|
35
|
+
]);
|
|
36
|
+
const stdout =
|
|
37
|
+
typeof (result as { stdout?: unknown })?.stdout === "string"
|
|
38
|
+
? (result as { stdout: string }).stdout.trim()
|
|
39
|
+
: "";
|
|
40
|
+
return stdout.length ? stdout : null;
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
30
46
|
// Re-export WorktreeConfig for external use
|
|
31
47
|
export type { WorktreeConfig };
|
|
32
48
|
|
|
@@ -414,15 +430,13 @@ async function getWorktreesWithPRStatus(): Promise<WorktreeWithPR[]> {
|
|
|
414
430
|
}
|
|
415
431
|
|
|
416
432
|
/**
|
|
417
|
-
* worktree
|
|
433
|
+
* worktreeに存在しないローカルブランチのクリーンアップ候補を取得
|
|
418
434
|
* @returns {Promise<CleanupTarget[]>} クリーンアップ候補の配列
|
|
419
435
|
*/
|
|
420
436
|
async function getOrphanedLocalBranches({
|
|
421
|
-
mergedPRs,
|
|
422
437
|
baseBranch,
|
|
423
438
|
repoRoot,
|
|
424
439
|
}: {
|
|
425
|
-
mergedPRs: MergedPullRequest[];
|
|
426
440
|
baseBranch: string;
|
|
427
441
|
repoRoot: string;
|
|
428
442
|
}): Promise<CleanupTarget[]> {
|
|
@@ -467,7 +481,6 @@ async function getOrphanedLocalBranches({
|
|
|
467
481
|
|
|
468
482
|
// worktreeに存在しないローカルブランチのみ対象
|
|
469
483
|
if (!worktreeBranches.has(localBranch.name)) {
|
|
470
|
-
const mergedPR = findMatchingPR(localBranch.name, mergedPRs);
|
|
471
484
|
let hasUnpushed = false;
|
|
472
485
|
try {
|
|
473
486
|
hasUnpushed = await hasUnpushedCommitsInRepo(
|
|
@@ -480,13 +493,12 @@ async function getOrphanedLocalBranches({
|
|
|
480
493
|
|
|
481
494
|
const reasons: CleanupReason[] = [];
|
|
482
495
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
}
|
|
496
|
+
const upstreamBranch = await getUpstreamBranch(localBranch.name);
|
|
497
|
+
const comparisonBase = upstreamBranch ?? baseBranch;
|
|
486
498
|
|
|
487
499
|
const hasUniqueCommits = await branchHasUniqueCommitsComparedToBase(
|
|
488
500
|
localBranch.name,
|
|
489
|
-
|
|
501
|
+
comparisonBase,
|
|
490
502
|
repoRoot,
|
|
491
503
|
);
|
|
492
504
|
|
|
@@ -497,7 +509,7 @@ async function getOrphanedLocalBranches({
|
|
|
497
509
|
if (process.env.DEBUG_CLEANUP) {
|
|
498
510
|
console.log(
|
|
499
511
|
chalk.gray(
|
|
500
|
-
`Debug: Checking orphaned branch ${localBranch.name} ->
|
|
512
|
+
`Debug: Checking orphaned branch ${localBranch.name} -> reasons: ${reasons.join(", ")}`,
|
|
501
513
|
),
|
|
502
514
|
);
|
|
503
515
|
}
|
|
@@ -517,7 +529,7 @@ async function getOrphanedLocalBranches({
|
|
|
517
529
|
cleanupTargets.push({
|
|
518
530
|
worktreePath: null, // worktreeは存在しない
|
|
519
531
|
branch: localBranch.name,
|
|
520
|
-
pullRequest:
|
|
532
|
+
pullRequest: null,
|
|
521
533
|
hasUncommittedChanges: false, // worktreeが存在しないため常にfalse
|
|
522
534
|
hasUnpushedCommits: hasUnpushed,
|
|
523
535
|
cleanupType: "branch-only",
|
|
@@ -546,49 +558,19 @@ async function getOrphanedLocalBranches({
|
|
|
546
558
|
}
|
|
547
559
|
}
|
|
548
560
|
|
|
549
|
-
function normalizeBranchName(branchName: string): string {
|
|
550
|
-
return branchName
|
|
551
|
-
.replace(/^origin\//, "")
|
|
552
|
-
.replace(/^refs\/heads\//, "")
|
|
553
|
-
.replace(/^refs\/remotes\/origin\//, "")
|
|
554
|
-
.trim();
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
function findMatchingPR(
|
|
558
|
-
worktreeBranch: string,
|
|
559
|
-
mergedPRs: MergedPullRequest[],
|
|
560
|
-
): MergedPullRequest | null {
|
|
561
|
-
const normalizedWorktreeBranch = normalizeBranchName(worktreeBranch);
|
|
562
|
-
|
|
563
|
-
for (const pr of mergedPRs) {
|
|
564
|
-
const normalizedPRBranch = normalizeBranchName(pr.branch);
|
|
565
|
-
|
|
566
|
-
if (normalizedWorktreeBranch === normalizedPRBranch) {
|
|
567
|
-
return pr;
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
return null;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
561
|
/**
|
|
575
562
|
* マージ済みPRに関連するworktreeおよびローカルブランチのクリーンアップ候補を取得
|
|
576
563
|
* @returns {Promise<CleanupTarget[]>} クリーンアップ候補の配列
|
|
577
564
|
*/
|
|
578
565
|
export async function getMergedPRWorktrees(): Promise<CleanupTarget[]> {
|
|
579
|
-
const [config, repoRoot] = await Promise.all([
|
|
566
|
+
const [config, repoRoot, worktreesWithPR] = await Promise.all([
|
|
580
567
|
getConfig(),
|
|
581
568
|
getRepositoryRoot(),
|
|
569
|
+
getWorktreesWithPRStatus(),
|
|
582
570
|
]);
|
|
583
571
|
const baseBranch = config.defaultBaseBranch || GIT_CONFIG.DEFAULT_BASE_BRANCH;
|
|
584
572
|
|
|
585
|
-
// 並列実行で高速化 - worktreeとマージ済みPRの両方を取得
|
|
586
|
-
const [mergedPRs, worktreesWithPR] = await Promise.all([
|
|
587
|
-
getMergedPullRequests(),
|
|
588
|
-
getWorktreesWithPRStatus(),
|
|
589
|
-
]);
|
|
590
573
|
const orphanedBranches = await getOrphanedLocalBranches({
|
|
591
|
-
mergedPRs,
|
|
592
574
|
baseBranch,
|
|
593
575
|
repoRoot,
|
|
594
576
|
});
|
|
@@ -599,8 +581,6 @@ export async function getMergedPRWorktrees(): Promise<CleanupTarget[]> {
|
|
|
599
581
|
worktreesWithPR.forEach((w) =>
|
|
600
582
|
console.log(` ${w.branch} -> ${w.worktreePath}`),
|
|
601
583
|
);
|
|
602
|
-
console.log(chalk.cyan("Debug: Merged PRs:"));
|
|
603
|
-
mergedPRs.forEach((pr) => console.log(` ${pr.branch} (PR #${pr.number})`));
|
|
604
584
|
}
|
|
605
585
|
|
|
606
586
|
for (const worktree of worktreesWithPR) {
|
|
@@ -614,15 +594,8 @@ export async function getMergedPRWorktrees(): Promise<CleanupTarget[]> {
|
|
|
614
594
|
continue;
|
|
615
595
|
}
|
|
616
596
|
|
|
617
|
-
const mergedPR = findMatchingPR(worktree.branch, mergedPRs);
|
|
618
|
-
|
|
619
597
|
if (process.env.DEBUG_CLEANUP) {
|
|
620
|
-
|
|
621
|
-
console.log(
|
|
622
|
-
chalk.gray(
|
|
623
|
-
`Debug: Checking worktree ${worktree.branch} (normalized: ${normalizedWorktree}) -> ${mergedPR ? "MATCH" : "NO MATCH"}`,
|
|
624
|
-
),
|
|
625
|
-
);
|
|
598
|
+
console.log(chalk.gray(`Debug: Checking worktree ${worktree.branch}`));
|
|
626
599
|
}
|
|
627
600
|
|
|
628
601
|
const cleanupReasons: CleanupReason[] = [];
|
|
@@ -670,13 +643,12 @@ export async function getMergedPRWorktrees(): Promise<CleanupTarget[]> {
|
|
|
670
643
|
hasRemoteBranch = false;
|
|
671
644
|
}
|
|
672
645
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
}
|
|
646
|
+
const upstreamBranch = await getUpstreamBranch(worktree.branch);
|
|
647
|
+
const comparisonBase = upstreamBranch ?? baseBranch;
|
|
676
648
|
|
|
677
649
|
const hasUniqueCommits = await branchHasUniqueCommitsComparedToBase(
|
|
678
650
|
worktree.branch,
|
|
679
|
-
|
|
651
|
+
comparisonBase,
|
|
680
652
|
repoRoot,
|
|
681
653
|
);
|
|
682
654
|
|
|
@@ -703,7 +675,7 @@ export async function getMergedPRWorktrees(): Promise<CleanupTarget[]> {
|
|
|
703
675
|
const target: CleanupTarget = {
|
|
704
676
|
worktreePath: worktree.worktreePath,
|
|
705
677
|
branch: worktree.branch,
|
|
706
|
-
pullRequest:
|
|
678
|
+
pullRequest: null,
|
|
707
679
|
hasUncommittedChanges: hasUncommitted,
|
|
708
680
|
hasUnpushedCommits: hasUnpushed,
|
|
709
681
|
cleanupType: "worktree-and-branch",
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
export interface WorktreeItem {
|
|
3
|
-
branch: string;
|
|
4
|
-
path: string;
|
|
5
|
-
isAccessible: boolean;
|
|
6
|
-
label?: string;
|
|
7
|
-
value?: string;
|
|
8
|
-
}
|
|
9
|
-
export interface WorktreeManagerScreenProps {
|
|
10
|
-
worktrees: WorktreeItem[];
|
|
11
|
-
onBack: () => void;
|
|
12
|
-
onSelect: (worktree: WorktreeItem) => void;
|
|
13
|
-
version?: string | null;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* WorktreeManagerScreen - Screen for managing worktrees
|
|
17
|
-
* Layout: Header + Stats + Worktree List + Footer
|
|
18
|
-
*/
|
|
19
|
-
export declare function WorktreeManagerScreen({ worktrees, onBack, onSelect, version, }: WorktreeManagerScreenProps): React.JSX.Element;
|
|
20
|
-
//# sourceMappingURL=WorktreeManagerScreen.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"WorktreeManagerScreen.d.ts","sourceRoot":"","sources":["../../../../../src/cli/ui/components/screens/WorktreeManagerScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAO1B,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,0BAA0B;IACzC,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,QAAQ,EAAE,CAAC,QAAQ,EAAE,YAAY,KAAK,IAAI,CAAC;IAC3C,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,SAAS,EACT,MAAM,EACN,QAAQ,EACR,OAAO,GACR,EAAE,0BAA0B,qBAqF5B"}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Box, Text, useInput } from "ink";
|
|
3
|
-
import { Header } from "../parts/Header.js";
|
|
4
|
-
import { Footer } from "../parts/Footer.js";
|
|
5
|
-
import { Select } from "../common/Select.js";
|
|
6
|
-
import { useTerminalSize } from "../../hooks/useTerminalSize.js";
|
|
7
|
-
/**
|
|
8
|
-
* WorktreeManagerScreen - Screen for managing worktrees
|
|
9
|
-
* Layout: Header + Stats + Worktree List + Footer
|
|
10
|
-
*/
|
|
11
|
-
export function WorktreeManagerScreen({ worktrees, onBack, onSelect, version, }) {
|
|
12
|
-
const { rows } = useTerminalSize();
|
|
13
|
-
// Handle keyboard input
|
|
14
|
-
// Note: Select component handles Enter and arrow keys
|
|
15
|
-
useInput((input, key) => {
|
|
16
|
-
if (key.escape) {
|
|
17
|
-
onBack();
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
// Calculate accessible and inaccessible counts
|
|
21
|
-
const accessibleCount = worktrees.filter((w) => w.isAccessible).length;
|
|
22
|
-
const inaccessibleCount = worktrees.filter((w) => !w.isAccessible).length;
|
|
23
|
-
// Format worktrees for Select component
|
|
24
|
-
const worktreeItems = worktrees.map((wt) => ({
|
|
25
|
-
...wt,
|
|
26
|
-
label: wt.isAccessible
|
|
27
|
-
? `${wt.branch} (${wt.path})`
|
|
28
|
-
: `${wt.branch} (${wt.path}) [Inaccessible]`,
|
|
29
|
-
value: wt.branch,
|
|
30
|
-
}));
|
|
31
|
-
// Calculate available space for worktree list
|
|
32
|
-
const headerLines = 2;
|
|
33
|
-
const statsLines = 1;
|
|
34
|
-
const emptyLine = 1;
|
|
35
|
-
const footerLines = 1;
|
|
36
|
-
const fixedLines = headerLines + statsLines + emptyLine + footerLines;
|
|
37
|
-
const contentHeight = rows - fixedLines;
|
|
38
|
-
const limit = Math.max(5, contentHeight);
|
|
39
|
-
// Footer actions
|
|
40
|
-
const footerActions = [
|
|
41
|
-
{ key: "enter", description: "Select" },
|
|
42
|
-
{ key: "esc", description: "Back" },
|
|
43
|
-
];
|
|
44
|
-
return (React.createElement(Box, { flexDirection: "column", height: rows },
|
|
45
|
-
React.createElement(Header, { title: "Worktree Manager", titleColor: "magenta", version: version }),
|
|
46
|
-
React.createElement(Box, { marginTop: 1 },
|
|
47
|
-
React.createElement(Box, { flexDirection: "row" },
|
|
48
|
-
React.createElement(Box, { marginRight: 2 },
|
|
49
|
-
React.createElement(Text, null,
|
|
50
|
-
"Total: ",
|
|
51
|
-
React.createElement(Text, { bold: true }, worktrees.length))),
|
|
52
|
-
React.createElement(Box, { marginRight: 2 },
|
|
53
|
-
React.createElement(Text, { color: "green" },
|
|
54
|
-
"Accessible: ",
|
|
55
|
-
React.createElement(Text, { bold: true }, accessibleCount))),
|
|
56
|
-
inaccessibleCount > 0 && (React.createElement(Box, null,
|
|
57
|
-
React.createElement(Text, { color: "red" },
|
|
58
|
-
"Inaccessible: ",
|
|
59
|
-
React.createElement(Text, { bold: true }, inaccessibleCount)))))),
|
|
60
|
-
React.createElement(Box, { height: 1 }),
|
|
61
|
-
React.createElement(Box, { flexDirection: "column", flexGrow: 1 }, worktrees.length === 0 ? (React.createElement(Box, null,
|
|
62
|
-
React.createElement(Text, { dimColor: true }, "No worktrees found"))) : (React.createElement(Select, { items: worktreeItems, onSelect: onSelect, limit: limit }))),
|
|
63
|
-
React.createElement(Footer, { actions: footerActions })));
|
|
64
|
-
}
|
|
65
|
-
//# sourceMappingURL=WorktreeManagerScreen.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"WorktreeManagerScreen.js","sourceRoot":"","sources":["../../../../../src/cli/ui/components/screens/WorktreeManagerScreen.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @vitest-environment happy-dom
|
|
3
|
-
*/
|
|
4
|
-
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
5
|
-
import { render } from "@testing-library/react";
|
|
6
|
-
import React from "react";
|
|
7
|
-
import { WorktreeManagerScreen } from "../../../components/screens/WorktreeManagerScreen.js";
|
|
8
|
-
import { Window } from "happy-dom";
|
|
9
|
-
|
|
10
|
-
describe("WorktreeManagerScreen", () => {
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
// Setup happy-dom
|
|
13
|
-
const window = new Window();
|
|
14
|
-
globalThis.window = window as any;
|
|
15
|
-
globalThis.document = window.document as any;
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
const mockWorktrees = [
|
|
19
|
-
{
|
|
20
|
-
branch: "feature/test-1",
|
|
21
|
-
path: "/path/to/worktree-1",
|
|
22
|
-
isAccessible: true,
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
branch: "feature/test-2",
|
|
26
|
-
path: "/path/to/worktree-2",
|
|
27
|
-
isAccessible: true,
|
|
28
|
-
},
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
it("should render header with title", () => {
|
|
32
|
-
const onBack = vi.fn();
|
|
33
|
-
const onSelect = vi.fn();
|
|
34
|
-
const { getByText } = render(
|
|
35
|
-
<WorktreeManagerScreen
|
|
36
|
-
worktrees={mockWorktrees}
|
|
37
|
-
onBack={onBack}
|
|
38
|
-
onSelect={onSelect}
|
|
39
|
-
/>,
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
expect(getByText(/Worktree Manager/i)).toBeDefined();
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it("should render worktree list", () => {
|
|
46
|
-
const onBack = vi.fn();
|
|
47
|
-
const onSelect = vi.fn();
|
|
48
|
-
const { getByText } = render(
|
|
49
|
-
<WorktreeManagerScreen
|
|
50
|
-
worktrees={mockWorktrees}
|
|
51
|
-
onBack={onBack}
|
|
52
|
-
onSelect={onSelect}
|
|
53
|
-
/>,
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
expect(getByText(/feature\/test-1/)).toBeDefined();
|
|
57
|
-
expect(getByText(/feature\/test-2/)).toBeDefined();
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it("should render footer with actions", () => {
|
|
61
|
-
const onBack = vi.fn();
|
|
62
|
-
const onSelect = vi.fn();
|
|
63
|
-
const { getAllByText } = render(
|
|
64
|
-
<WorktreeManagerScreen
|
|
65
|
-
worktrees={mockWorktrees}
|
|
66
|
-
onBack={onBack}
|
|
67
|
-
onSelect={onSelect}
|
|
68
|
-
/>,
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
expect(getAllByText(/enter/i).length).toBeGreaterThan(0);
|
|
72
|
-
expect(getAllByText(/esc/i).length).toBeGreaterThan(0);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("should handle empty worktree list", () => {
|
|
76
|
-
const onBack = vi.fn();
|
|
77
|
-
const onSelect = vi.fn();
|
|
78
|
-
const { getByText } = render(
|
|
79
|
-
<WorktreeManagerScreen
|
|
80
|
-
worktrees={[]}
|
|
81
|
-
onBack={onBack}
|
|
82
|
-
onSelect={onSelect}
|
|
83
|
-
/>,
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
expect(getByText(/No worktrees found/i)).toBeDefined();
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("should display inaccessible worktrees differently", () => {
|
|
90
|
-
const worktreesWithInaccessible = [
|
|
91
|
-
{
|
|
92
|
-
branch: "feature/accessible",
|
|
93
|
-
path: "/path/accessible",
|
|
94
|
-
isAccessible: true,
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
branch: "feature/inaccessible",
|
|
98
|
-
path: "/path/inaccessible",
|
|
99
|
-
isAccessible: false,
|
|
100
|
-
},
|
|
101
|
-
];
|
|
102
|
-
|
|
103
|
-
const onBack = vi.fn();
|
|
104
|
-
const onSelect = vi.fn();
|
|
105
|
-
const { getByText } = render(
|
|
106
|
-
<WorktreeManagerScreen
|
|
107
|
-
worktrees={worktreesWithInaccessible}
|
|
108
|
-
onBack={onBack}
|
|
109
|
-
onSelect={onSelect}
|
|
110
|
-
/>,
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
expect(getByText(/feature\/accessible/)).toBeDefined();
|
|
114
|
-
expect(getByText(/feature\/inaccessible/)).toBeDefined();
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it("should use terminal height for layout calculation", () => {
|
|
118
|
-
const originalRows = process.stdout.rows;
|
|
119
|
-
process.stdout.rows = 30;
|
|
120
|
-
|
|
121
|
-
const onBack = vi.fn();
|
|
122
|
-
const onSelect = vi.fn();
|
|
123
|
-
const { container } = render(
|
|
124
|
-
<WorktreeManagerScreen
|
|
125
|
-
worktrees={mockWorktrees}
|
|
126
|
-
onBack={onBack}
|
|
127
|
-
onSelect={onSelect}
|
|
128
|
-
/>,
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
expect(container).toBeDefined();
|
|
132
|
-
|
|
133
|
-
process.stdout.rows = originalRows;
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it("should display worktree count in stats", () => {
|
|
137
|
-
const onBack = vi.fn();
|
|
138
|
-
const onSelect = vi.fn();
|
|
139
|
-
const { getByText, getAllByText } = render(
|
|
140
|
-
<WorktreeManagerScreen
|
|
141
|
-
worktrees={mockWorktrees}
|
|
142
|
-
onBack={onBack}
|
|
143
|
-
onSelect={onSelect}
|
|
144
|
-
/>,
|
|
145
|
-
);
|
|
146
|
-
|
|
147
|
-
// Check for worktree count
|
|
148
|
-
expect(getByText(/Total:/i)).toBeDefined();
|
|
149
|
-
expect(getAllByText(/2/).length).toBeGreaterThan(0);
|
|
150
|
-
});
|
|
151
|
-
});
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Box, Text, useInput } from "ink";
|
|
3
|
-
import { Header } from "../parts/Header.js";
|
|
4
|
-
import { Footer } from "../parts/Footer.js";
|
|
5
|
-
import { Select } from "../common/Select.js";
|
|
6
|
-
import { useTerminalSize } from "../../hooks/useTerminalSize.js";
|
|
7
|
-
|
|
8
|
-
export interface WorktreeItem {
|
|
9
|
-
branch: string;
|
|
10
|
-
path: string;
|
|
11
|
-
isAccessible: boolean;
|
|
12
|
-
label?: string;
|
|
13
|
-
value?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface WorktreeManagerScreenProps {
|
|
17
|
-
worktrees: WorktreeItem[];
|
|
18
|
-
onBack: () => void;
|
|
19
|
-
onSelect: (worktree: WorktreeItem) => void;
|
|
20
|
-
version?: string | null;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* WorktreeManagerScreen - Screen for managing worktrees
|
|
25
|
-
* Layout: Header + Stats + Worktree List + Footer
|
|
26
|
-
*/
|
|
27
|
-
export function WorktreeManagerScreen({
|
|
28
|
-
worktrees,
|
|
29
|
-
onBack,
|
|
30
|
-
onSelect,
|
|
31
|
-
version,
|
|
32
|
-
}: WorktreeManagerScreenProps) {
|
|
33
|
-
const { rows } = useTerminalSize();
|
|
34
|
-
|
|
35
|
-
// Handle keyboard input
|
|
36
|
-
// Note: Select component handles Enter and arrow keys
|
|
37
|
-
useInput((input, key) => {
|
|
38
|
-
if (key.escape) {
|
|
39
|
-
onBack();
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
// Calculate accessible and inaccessible counts
|
|
44
|
-
const accessibleCount = worktrees.filter((w) => w.isAccessible).length;
|
|
45
|
-
const inaccessibleCount = worktrees.filter((w) => !w.isAccessible).length;
|
|
46
|
-
|
|
47
|
-
// Format worktrees for Select component
|
|
48
|
-
const worktreeItems = worktrees.map((wt) => ({
|
|
49
|
-
...wt,
|
|
50
|
-
label: wt.isAccessible
|
|
51
|
-
? `${wt.branch} (${wt.path})`
|
|
52
|
-
: `${wt.branch} (${wt.path}) [Inaccessible]`,
|
|
53
|
-
value: wt.branch,
|
|
54
|
-
}));
|
|
55
|
-
|
|
56
|
-
// Calculate available space for worktree list
|
|
57
|
-
const headerLines = 2;
|
|
58
|
-
const statsLines = 1;
|
|
59
|
-
const emptyLine = 1;
|
|
60
|
-
const footerLines = 1;
|
|
61
|
-
const fixedLines = headerLines + statsLines + emptyLine + footerLines;
|
|
62
|
-
const contentHeight = rows - fixedLines;
|
|
63
|
-
const limit = Math.max(5, contentHeight);
|
|
64
|
-
|
|
65
|
-
// Footer actions
|
|
66
|
-
const footerActions = [
|
|
67
|
-
{ key: "enter", description: "Select" },
|
|
68
|
-
{ key: "esc", description: "Back" },
|
|
69
|
-
];
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
<Box flexDirection="column" height={rows}>
|
|
73
|
-
{/* Header */}
|
|
74
|
-
<Header title="Worktree Manager" titleColor="magenta" version={version} />
|
|
75
|
-
|
|
76
|
-
{/* Stats */}
|
|
77
|
-
<Box marginTop={1}>
|
|
78
|
-
<Box flexDirection="row">
|
|
79
|
-
<Box marginRight={2}>
|
|
80
|
-
<Text>
|
|
81
|
-
Total: <Text bold>{worktrees.length}</Text>
|
|
82
|
-
</Text>
|
|
83
|
-
</Box>
|
|
84
|
-
<Box marginRight={2}>
|
|
85
|
-
<Text color="green">
|
|
86
|
-
Accessible: <Text bold>{accessibleCount}</Text>
|
|
87
|
-
</Text>
|
|
88
|
-
</Box>
|
|
89
|
-
{inaccessibleCount > 0 && (
|
|
90
|
-
<Box>
|
|
91
|
-
<Text color="red">
|
|
92
|
-
Inaccessible: <Text bold>{inaccessibleCount}</Text>
|
|
93
|
-
</Text>
|
|
94
|
-
</Box>
|
|
95
|
-
)}
|
|
96
|
-
</Box>
|
|
97
|
-
</Box>
|
|
98
|
-
|
|
99
|
-
{/* Empty line */}
|
|
100
|
-
<Box height={1} />
|
|
101
|
-
|
|
102
|
-
{/* Content */}
|
|
103
|
-
<Box flexDirection="column" flexGrow={1}>
|
|
104
|
-
{worktrees.length === 0 ? (
|
|
105
|
-
<Box>
|
|
106
|
-
<Text dimColor>No worktrees found</Text>
|
|
107
|
-
</Box>
|
|
108
|
-
) : (
|
|
109
|
-
<Select items={worktreeItems} onSelect={onSelect} limit={limit} />
|
|
110
|
-
)}
|
|
111
|
-
</Box>
|
|
112
|
-
|
|
113
|
-
{/* Footer */}
|
|
114
|
-
<Footer actions={footerActions} />
|
|
115
|
-
</Box>
|
|
116
|
-
);
|
|
117
|
-
}
|