@akiojin/gwt 4.3.1 → 4.4.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.
Files changed (46) hide show
  1. package/dist/cli/ui/components/App.d.ts.map +1 -1
  2. package/dist/cli/ui/components/App.js +12 -61
  3. package/dist/cli/ui/components/App.js.map +1 -1
  4. package/dist/cli/ui/components/common/SpinnerIcon.d.ts +20 -0
  5. package/dist/cli/ui/components/common/SpinnerIcon.d.ts.map +1 -0
  6. package/dist/cli/ui/components/common/SpinnerIcon.js +61 -0
  7. package/dist/cli/ui/components/common/SpinnerIcon.js.map +1 -0
  8. package/dist/cli/ui/components/parts/Stats.d.ts +2 -5
  9. package/dist/cli/ui/components/parts/Stats.d.ts.map +1 -1
  10. package/dist/cli/ui/components/parts/Stats.js +16 -3
  11. package/dist/cli/ui/components/parts/Stats.js.map +1 -1
  12. package/dist/cli/ui/components/screens/BranchListScreen.d.ts +6 -2
  13. package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
  14. package/dist/cli/ui/components/screens/BranchListScreen.js +95 -42
  15. package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
  16. package/dist/cli/ui/hooks/useAppInput.d.ts +1 -0
  17. package/dist/cli/ui/hooks/useAppInput.d.ts.map +1 -1
  18. package/dist/cli/ui/hooks/useAppInput.js +2 -1
  19. package/dist/cli/ui/hooks/useAppInput.js.map +1 -1
  20. package/dist/cli/ui/hooks/useGitData.d.ts +1 -0
  21. package/dist/cli/ui/hooks/useGitData.d.ts.map +1 -1
  22. package/dist/cli/ui/hooks/useGitData.js +43 -15
  23. package/dist/cli/ui/hooks/useGitData.js.map +1 -1
  24. package/dist/cli/ui/types.d.ts +4 -0
  25. package/dist/cli/ui/types.d.ts.map +1 -1
  26. package/dist/git.d.ts +7 -4
  27. package/dist/git.d.ts.map +1 -1
  28. package/dist/git.js +54 -34
  29. package/dist/git.js.map +1 -1
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +14 -1
  33. package/dist/index.js.map +1 -1
  34. package/package.json +4 -4
  35. package/src/cli/ui/__tests__/components/screens/BranchListScreen.test.tsx +208 -0
  36. package/src/cli/ui/__tests__/hooks/useGitData.nonblocking.test.tsx +206 -0
  37. package/src/cli/ui/components/App.tsx +22 -77
  38. package/src/cli/ui/components/common/SpinnerIcon.tsx +86 -0
  39. package/src/cli/ui/components/parts/Stats.tsx +24 -3
  40. package/src/cli/ui/components/screens/BranchListScreen.tsx +117 -45
  41. package/src/cli/ui/hooks/useAppInput.ts +2 -1
  42. package/src/cli/ui/hooks/useGitData.ts +101 -18
  43. package/src/cli/ui/screens/__tests__/BranchActionSelectorScreen.test.tsx +46 -1
  44. package/src/cli/ui/types.ts +5 -0
  45. package/src/git.ts +72 -37
  46. package/src/index.ts +14 -1
package/src/git.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { execa } from "execa";
2
2
  import path from "node:path";
3
3
  import { BranchInfo } from "./cli/ui/types.js";
4
+ import { GIT_CONFIG } from "./config/constants.js";
4
5
 
5
6
  export class GitError extends Error {
6
7
  constructor(
@@ -40,12 +41,17 @@ export async function isGitRepository(): Promise<boolean> {
40
41
  console.error(`[DEBUG] git rev-parse --git-dir: ${result.stdout}`);
41
42
  }
42
43
  return true;
43
- } catch (error: any) {
44
+ } catch (error: unknown) {
44
45
  // Debug: log the error for troubleshooting
45
46
  if (process.env.DEBUG) {
46
- console.error(`[DEBUG] git rev-parse --git-dir failed:`, error.message);
47
- if (error.stderr) {
48
- console.error(`[DEBUG] stderr:`, error.stderr);
47
+ const message = error instanceof Error ? error.message : String(error);
48
+ const stderr =
49
+ typeof error === "object" && error !== null && "stderr" in error
50
+ ? (error as { stderr?: string }).stderr
51
+ : undefined;
52
+ console.error(`[DEBUG] git rev-parse --git-dir failed:`, message);
53
+ if (stderr) {
54
+ console.error(`[DEBUG] stderr:`, stderr);
49
55
  }
50
56
  }
51
57
  return false;
@@ -171,11 +177,11 @@ export async function getWorktreeRoot(): Promise<string> {
171
177
  }
172
178
  }
173
179
 
174
- export async function getCurrentBranch(): Promise<string | null> {
180
+ export async function getCurrentBranch(cwd?: string): Promise<string | null> {
175
181
  try {
176
- const repoRoot = await getRepositoryRoot();
182
+ const workDir = cwd ?? (await getRepositoryRoot());
177
183
  const { stdout } = await execa("git", ["branch", "--show-current"], {
178
- cwd: repoRoot,
184
+ cwd: workDir,
179
185
  });
180
186
  return stdout.trim() || null;
181
187
  } catch {
@@ -221,13 +227,15 @@ async function getBranchCommitTimestamps(
221
227
  }
222
228
  }
223
229
 
224
- export async function getLocalBranches(): Promise<BranchInfo[]> {
230
+ export async function getLocalBranches(cwd?: string): Promise<BranchInfo[]> {
225
231
  try {
226
- const commitMap = await getBranchCommitTimestamps(["refs/heads"]);
227
- const { stdout = "" } = (await execa("git", [
228
- "branch",
229
- "--format=%(refname:short)",
230
- ])) ?? { stdout: "" };
232
+ const execOptions = cwd ? { cwd } : undefined;
233
+ const commitMap = await getBranchCommitTimestamps(["refs/heads"], cwd);
234
+ const { stdout = "" } = (await execa(
235
+ "git",
236
+ ["branch", "--format=%(refname:short)"],
237
+ execOptions,
238
+ )) ?? { stdout: "" };
231
239
  return stdout
232
240
  .split("\n")
233
241
  .filter((line) => line.trim())
@@ -250,14 +258,15 @@ export async function getLocalBranches(): Promise<BranchInfo[]> {
250
258
  }
251
259
  }
252
260
 
253
- export async function getRemoteBranches(): Promise<BranchInfo[]> {
261
+ export async function getRemoteBranches(cwd?: string): Promise<BranchInfo[]> {
254
262
  try {
255
- const commitMap = await getBranchCommitTimestamps(["refs/remotes"]);
256
- const { stdout = "" } = (await execa("git", [
257
- "branch",
258
- "-r",
259
- "--format=%(refname:short)",
260
- ])) ?? { stdout: "" };
263
+ const execOptions = cwd ? { cwd } : undefined;
264
+ const commitMap = await getBranchCommitTimestamps(["refs/remotes"], cwd);
265
+ const { stdout = "" } = (await execa(
266
+ "git",
267
+ ["branch", "-r", "--format=%(refname:short)"],
268
+ execOptions,
269
+ )) ?? { stdout: "" };
261
270
  return stdout
262
271
  .split("\n")
263
272
  .filter((line) => line.trim() && !line.includes("HEAD"))
@@ -283,13 +292,14 @@ export async function getRemoteBranches(): Promise<BranchInfo[]> {
283
292
 
284
293
  /**
285
294
  * ローカルとリモートのすべてのブランチ情報を取得
295
+ * @param cwd - 作業ディレクトリ(省略時はリポジトリルート)
286
296
  * @returns {Promise<BranchInfo[]>} ブランチ情報の配列
287
297
  */
288
- export async function getAllBranches(): Promise<BranchInfo[]> {
298
+ export async function getAllBranches(cwd?: string): Promise<BranchInfo[]> {
289
299
  const [localBranches, remoteBranches, currentBranch] = await Promise.all([
290
- getLocalBranches(),
291
- getRemoteBranches(),
292
- getCurrentBranch(),
300
+ getLocalBranches(cwd),
301
+ getRemoteBranches(cwd),
302
+ getCurrentBranch(cwd),
293
303
  ]);
294
304
 
295
305
  // 現在のブランチ情報を設定
@@ -752,15 +762,27 @@ export async function getEnhancedSessionInfo(
752
762
 
753
763
  export async function fetchAllRemotes(options?: {
754
764
  cwd?: string;
765
+ timeoutMs?: number;
766
+ allowPrompt?: boolean;
755
767
  }): Promise<void> {
756
768
  try {
757
- const execOptions = options?.cwd ? { cwd: options.cwd } : undefined;
769
+ const execOptions = options?.cwd ? { cwd: options.cwd } : {};
770
+ const timeoutMs = options?.timeoutMs ?? GIT_CONFIG.FETCH_TIMEOUT;
771
+ const allowPrompt = options?.allowPrompt === true;
772
+ const env = allowPrompt
773
+ ? process.env
774
+ : {
775
+ ...process.env,
776
+ GIT_TERMINAL_PROMPT: "0",
777
+ GCM_INTERACTIVE: "Never",
778
+ };
758
779
  const args = ["fetch", "--all", "--prune"];
759
- if (execOptions) {
760
- await execa("git", args, execOptions);
761
- } else {
762
- await execa("git", args);
763
- }
780
+ await execa("git", args, {
781
+ ...execOptions,
782
+ timeout: timeoutMs,
783
+ env,
784
+ stdin: allowPrompt ? "inherit" : "ignore",
785
+ });
764
786
  } catch (error) {
765
787
  throw new GitError("Failed to fetch remote branches", error);
766
788
  }
@@ -854,11 +876,19 @@ export async function executeNpmVersionInWorktree(
854
876
  { cwd: worktreePath },
855
877
  );
856
878
  }
857
- } catch (error: any) {
879
+ } catch (error: unknown) {
858
880
  // エラーの詳細情報を含める
859
881
  const errorMessage = error instanceof Error ? error.message : String(error);
860
- const errorDetails = error?.stderr ? ` (stderr: ${error.stderr})` : "";
861
- const errorStdout = error?.stdout ? ` (stdout: ${error.stdout})` : "";
882
+ const stderr =
883
+ typeof error === "object" && error !== null && "stderr" in error
884
+ ? (error as { stderr?: string }).stderr
885
+ : undefined;
886
+ const stdout =
887
+ typeof error === "object" && error !== null && "stdout" in error
888
+ ? (error as { stdout?: string }).stdout
889
+ : undefined;
890
+ const errorDetails = stderr ? ` (stderr: ${stderr})` : "";
891
+ const errorStdout = stdout ? ` (stdout: ${stdout})` : "";
862
892
  throw new GitError(
863
893
  `Failed to update version to ${newVersion} in worktree: ${errorMessage}${errorDetails}${errorStdout}`,
864
894
  error,
@@ -1201,9 +1231,13 @@ export async function ensureGitignoreEntry(
1201
1231
  if (content.includes("\r\n")) {
1202
1232
  eol = "\r\n";
1203
1233
  }
1204
- } catch (error: any) {
1234
+ } catch (error: unknown) {
1205
1235
  // ENOENTエラー(ファイルが存在しない)は無視
1206
- if (error.code !== "ENOENT") {
1236
+ const code =
1237
+ typeof error === "object" && error !== null && "code" in error
1238
+ ? (error as { code?: string }).code
1239
+ : undefined;
1240
+ if (code !== "ENOENT") {
1207
1241
  throw error;
1208
1242
  }
1209
1243
  }
@@ -1222,7 +1256,8 @@ export async function ensureGitignoreEntry(
1222
1256
 
1223
1257
  const newContent = `${content}${separator}${entry}${eol}`;
1224
1258
  await fs.writeFile(gitignorePath, newContent, "utf-8");
1225
- } catch (error: any) {
1226
- throw new GitError(`Failed to update .gitignore: ${error.message}`, error);
1259
+ } catch (error: unknown) {
1260
+ const message = error instanceof Error ? error.message : String(error);
1261
+ throw new GitError(`Failed to update .gitignore: ${message}`, error);
1227
1262
  }
1228
1263
  }
package/src/index.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
3
5
  import {
4
6
  isGitRepository,
5
7
  getRepositoryRoot,
@@ -914,8 +916,19 @@ export async function main(): Promise<void> {
914
916
  await runInteractiveLoop();
915
917
  }
916
918
 
919
+ export function isEntryPoint(metaUrl: string, argv1?: string): boolean {
920
+ if (!argv1) {
921
+ return false;
922
+ }
923
+ try {
924
+ return fileURLToPath(metaUrl) === path.resolve(argv1);
925
+ } catch {
926
+ return false;
927
+ }
928
+ }
929
+
917
930
  // Run the application if this module is executed directly
918
- if (import.meta.url === `file://${process.argv[1]}`) {
931
+ if (isEntryPoint(import.meta.url, process.argv[1])) {
919
932
  main().catch(async (error) => {
920
933
  console.error("Fatal error:", error);
921
934
  await waitForErrorAcknowledgement();