ghagga 2.0.1 → 2.2.0

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 (193) hide show
  1. package/README.md +90 -19
  2. package/dist/commands/hooks/index.d.ts +9 -0
  3. package/dist/commands/hooks/index.d.ts.map +1 -0
  4. package/dist/commands/hooks/index.js +16 -0
  5. package/dist/commands/hooks/index.js.map +1 -0
  6. package/dist/commands/hooks/install.d.ts +13 -0
  7. package/dist/commands/hooks/install.d.ts.map +1 -0
  8. package/dist/commands/hooks/install.js +64 -0
  9. package/dist/commands/hooks/install.js.map +1 -0
  10. package/dist/commands/hooks/install.test.d.ts +11 -0
  11. package/dist/commands/hooks/install.test.d.ts.map +1 -0
  12. package/dist/commands/hooks/install.test.js +157 -0
  13. package/dist/commands/hooks/install.test.js.map +1 -0
  14. package/dist/commands/hooks/status.d.ts +12 -0
  15. package/dist/commands/hooks/status.d.ts.map +1 -0
  16. package/dist/commands/hooks/status.js +38 -0
  17. package/dist/commands/hooks/status.js.map +1 -0
  18. package/dist/commands/hooks/status.test.d.ts +11 -0
  19. package/dist/commands/hooks/status.test.d.ts.map +1 -0
  20. package/dist/commands/hooks/status.test.js +123 -0
  21. package/dist/commands/hooks/status.test.js.map +1 -0
  22. package/dist/commands/hooks/uninstall.d.ts +12 -0
  23. package/dist/commands/hooks/uninstall.d.ts.map +1 -0
  24. package/dist/commands/hooks/uninstall.js +44 -0
  25. package/dist/commands/hooks/uninstall.js.map +1 -0
  26. package/dist/commands/hooks/uninstall.test.d.ts +10 -0
  27. package/dist/commands/hooks/uninstall.test.d.ts.map +1 -0
  28. package/dist/commands/hooks/uninstall.test.js +120 -0
  29. package/dist/commands/hooks/uninstall.test.js.map +1 -0
  30. package/dist/commands/login.d.ts.map +1 -1
  31. package/dist/commands/login.js +17 -14
  32. package/dist/commands/login.js.map +1 -1
  33. package/dist/commands/login.test.d.ts +9 -0
  34. package/dist/commands/login.test.d.ts.map +1 -0
  35. package/dist/commands/login.test.js +138 -0
  36. package/dist/commands/login.test.js.map +1 -0
  37. package/dist/commands/logout.d.ts.map +1 -1
  38. package/dist/commands/logout.js +4 -3
  39. package/dist/commands/logout.js.map +1 -1
  40. package/dist/commands/logout.test.d.ts +8 -0
  41. package/dist/commands/logout.test.d.ts.map +1 -0
  42. package/dist/commands/logout.test.js +61 -0
  43. package/dist/commands/logout.test.js.map +1 -0
  44. package/dist/commands/memory/clear.d.ts +11 -0
  45. package/dist/commands/memory/clear.d.ts.map +1 -0
  46. package/dist/commands/memory/clear.js +45 -0
  47. package/dist/commands/memory/clear.js.map +1 -0
  48. package/dist/commands/memory/clear.test.d.ts +11 -0
  49. package/dist/commands/memory/clear.test.d.ts.map +1 -0
  50. package/dist/commands/memory/clear.test.js +178 -0
  51. package/dist/commands/memory/clear.test.js.map +1 -0
  52. package/dist/commands/memory/delete.d.ts +11 -0
  53. package/dist/commands/memory/delete.d.ts.map +1 -0
  54. package/dist/commands/memory/delete.js +38 -0
  55. package/dist/commands/memory/delete.js.map +1 -0
  56. package/dist/commands/memory/delete.test.d.ts +11 -0
  57. package/dist/commands/memory/delete.test.d.ts.map +1 -0
  58. package/dist/commands/memory/delete.test.js +176 -0
  59. package/dist/commands/memory/delete.test.js.map +1 -0
  60. package/dist/commands/memory/index.d.ts +10 -0
  61. package/dist/commands/memory/index.d.ts.map +1 -0
  62. package/dist/commands/memory/index.js +23 -0
  63. package/dist/commands/memory/index.js.map +1 -0
  64. package/dist/commands/memory/list.d.ts +11 -0
  65. package/dist/commands/memory/list.d.ts.map +1 -0
  66. package/dist/commands/memory/list.js +47 -0
  67. package/dist/commands/memory/list.js.map +1 -0
  68. package/dist/commands/memory/list.test.d.ts +10 -0
  69. package/dist/commands/memory/list.test.d.ts.map +1 -0
  70. package/dist/commands/memory/list.test.js +135 -0
  71. package/dist/commands/memory/list.test.js.map +1 -0
  72. package/dist/commands/memory/search.d.ts +12 -0
  73. package/dist/commands/memory/search.d.ts.map +1 -0
  74. package/dist/commands/memory/search.js +44 -0
  75. package/dist/commands/memory/search.js.map +1 -0
  76. package/dist/commands/memory/search.test.d.ts +11 -0
  77. package/dist/commands/memory/search.test.d.ts.map +1 -0
  78. package/dist/commands/memory/search.test.js +122 -0
  79. package/dist/commands/memory/search.test.js.map +1 -0
  80. package/dist/commands/memory/show.d.ts +10 -0
  81. package/dist/commands/memory/show.d.ts.map +1 -0
  82. package/dist/commands/memory/show.js +51 -0
  83. package/dist/commands/memory/show.js.map +1 -0
  84. package/dist/commands/memory/show.test.d.ts +11 -0
  85. package/dist/commands/memory/show.test.d.ts.map +1 -0
  86. package/dist/commands/memory/show.test.js +156 -0
  87. package/dist/commands/memory/show.test.js.map +1 -0
  88. package/dist/commands/memory/stats.d.ts +11 -0
  89. package/dist/commands/memory/stats.d.ts.map +1 -0
  90. package/dist/commands/memory/stats.js +63 -0
  91. package/dist/commands/memory/stats.js.map +1 -0
  92. package/dist/commands/memory/stats.test.d.ts +10 -0
  93. package/dist/commands/memory/stats.test.d.ts.map +1 -0
  94. package/dist/commands/memory/stats.test.js +122 -0
  95. package/dist/commands/memory/stats.test.js.map +1 -0
  96. package/dist/commands/memory/utils.d.ts +44 -0
  97. package/dist/commands/memory/utils.d.ts.map +1 -0
  98. package/dist/commands/memory/utils.js +90 -0
  99. package/dist/commands/memory/utils.js.map +1 -0
  100. package/dist/commands/memory/utils.test.d.ts +10 -0
  101. package/dist/commands/memory/utils.test.d.ts.map +1 -0
  102. package/dist/commands/memory/utils.test.js +93 -0
  103. package/dist/commands/memory/utils.test.js.map +1 -0
  104. package/dist/commands/review-commit-msg.d.ts +28 -0
  105. package/dist/commands/review-commit-msg.d.ts.map +1 -0
  106. package/dist/commands/review-commit-msg.js +126 -0
  107. package/dist/commands/review-commit-msg.js.map +1 -0
  108. package/dist/commands/review-commit-msg.test.d.ts +11 -0
  109. package/dist/commands/review-commit-msg.test.d.ts.map +1 -0
  110. package/dist/commands/review-commit-msg.test.js +126 -0
  111. package/dist/commands/review-commit-msg.test.js.map +1 -0
  112. package/dist/commands/review.d.ts +7 -0
  113. package/dist/commands/review.d.ts.map +1 -1
  114. package/dist/commands/review.js +138 -103
  115. package/dist/commands/review.js.map +1 -1
  116. package/dist/commands/review.test.js +267 -1
  117. package/dist/commands/review.test.js.map +1 -1
  118. package/dist/commands/status.d.ts.map +1 -1
  119. package/dist/commands/status.js +12 -11
  120. package/dist/commands/status.js.map +1 -1
  121. package/dist/commands/status.test.d.ts +8 -0
  122. package/dist/commands/status.test.d.ts.map +1 -0
  123. package/dist/commands/status.test.js +106 -0
  124. package/dist/commands/status.test.js.map +1 -0
  125. package/dist/index.d.ts +5 -4
  126. package/dist/index.d.ts.map +1 -1
  127. package/dist/index.js +48 -16
  128. package/dist/index.js.map +1 -1
  129. package/dist/lib/config.d.ts +1 -0
  130. package/dist/lib/config.d.ts.map +1 -1
  131. package/dist/lib/config.js +1 -1
  132. package/dist/lib/config.js.map +1 -1
  133. package/dist/lib/config.test.d.ts +9 -0
  134. package/dist/lib/config.test.d.ts.map +1 -0
  135. package/dist/lib/config.test.js +181 -0
  136. package/dist/lib/config.test.js.map +1 -0
  137. package/dist/lib/git-hooks.d.ts +19 -0
  138. package/dist/lib/git-hooks.d.ts.map +1 -0
  139. package/dist/lib/git-hooks.js +129 -0
  140. package/dist/lib/git-hooks.js.map +1 -0
  141. package/dist/lib/git-hooks.test.d.ts +11 -0
  142. package/dist/lib/git-hooks.test.d.ts.map +1 -0
  143. package/dist/lib/git-hooks.test.js +178 -0
  144. package/dist/lib/git-hooks.test.js.map +1 -0
  145. package/dist/lib/git.d.ts +33 -0
  146. package/dist/lib/git.d.ts.map +1 -0
  147. package/dist/lib/git.js +85 -0
  148. package/dist/lib/git.js.map +1 -0
  149. package/dist/lib/git.test.d.ts +2 -0
  150. package/dist/lib/git.test.d.ts.map +1 -0
  151. package/dist/lib/git.test.js +65 -0
  152. package/dist/lib/git.test.js.map +1 -0
  153. package/dist/lib/hook-templates.d.ts +12 -0
  154. package/dist/lib/hook-templates.d.ts.map +1 -0
  155. package/dist/lib/hook-templates.js +52 -0
  156. package/dist/lib/hook-templates.js.map +1 -0
  157. package/dist/lib/hook-templates.test.d.ts +11 -0
  158. package/dist/lib/hook-templates.test.d.ts.map +1 -0
  159. package/dist/lib/hook-templates.test.js +76 -0
  160. package/dist/lib/hook-templates.test.js.map +1 -0
  161. package/dist/lib/hooks-types.d.ts +30 -0
  162. package/dist/lib/hooks-types.d.ts.map +1 -0
  163. package/dist/lib/hooks-types.js +9 -0
  164. package/dist/lib/hooks-types.js.map +1 -0
  165. package/dist/lib/oauth.test.d.ts +8 -0
  166. package/dist/lib/oauth.test.d.ts.map +1 -0
  167. package/dist/lib/oauth.test.js +212 -0
  168. package/dist/lib/oauth.test.js.map +1 -0
  169. package/dist/ui/__tests__/format.test.d.ts +7 -0
  170. package/dist/ui/__tests__/format.test.d.ts.map +1 -0
  171. package/dist/ui/__tests__/format.test.js +220 -0
  172. package/dist/ui/__tests__/format.test.js.map +1 -0
  173. package/dist/ui/__tests__/theme.test.d.ts +7 -0
  174. package/dist/ui/__tests__/theme.test.d.ts.map +1 -0
  175. package/dist/ui/__tests__/theme.test.js +79 -0
  176. package/dist/ui/__tests__/theme.test.js.map +1 -0
  177. package/dist/ui/__tests__/tui.test.d.ts +9 -0
  178. package/dist/ui/__tests__/tui.test.d.ts.map +1 -0
  179. package/dist/ui/__tests__/tui.test.js +222 -0
  180. package/dist/ui/__tests__/tui.test.js.map +1 -0
  181. package/dist/ui/format.d.ts +38 -0
  182. package/dist/ui/format.d.ts.map +1 -0
  183. package/dist/ui/format.js +136 -0
  184. package/dist/ui/format.js.map +1 -0
  185. package/dist/ui/theme.d.ts +26 -0
  186. package/dist/ui/theme.d.ts.map +1 -0
  187. package/dist/ui/theme.js +63 -0
  188. package/dist/ui/theme.js.map +1 -0
  189. package/dist/ui/tui.d.ts +44 -0
  190. package/dist/ui/tui.d.ts.map +1 -0
  191. package/dist/ui/tui.js +121 -0
  192. package/dist/ui/tui.js.map +1 -0
  193. package/package.json +4 -2
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Tests for `ghagga hooks status` subcommand.
3
+ *
4
+ * Mocks git-hooks utilities and the TUI layer.
5
+ * Tests display of hook status for both hooks, non-git-repo error,
6
+ * and correct labels for not-installed / GHAGGA / external hooks.
7
+ *
8
+ * @see Phase 4, Test 6
9
+ */
10
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
11
+ import { Command } from 'commander';
12
+ // ─── Mocks ──────────────────────────────────────────────────────
13
+ const { mockIsGitRepo, mockGetHooksDir, mockGetHookStatus } = vi.hoisted(() => ({
14
+ mockIsGitRepo: vi.fn(),
15
+ mockGetHooksDir: vi.fn(),
16
+ mockGetHookStatus: vi.fn(),
17
+ }));
18
+ vi.mock('../../lib/git-hooks.js', () => ({
19
+ isGitRepo: (...args) => mockIsGitRepo(...args),
20
+ getHooksDir: (...args) => mockGetHooksDir(...args),
21
+ getHookStatus: (...args) => mockGetHookStatus(...args),
22
+ }));
23
+ vi.mock('../../ui/tui.js', () => ({
24
+ log: {
25
+ success: vi.fn(),
26
+ error: vi.fn(),
27
+ info: vi.fn(),
28
+ warn: vi.fn(),
29
+ },
30
+ }));
31
+ import { registerStatusCommand } from './status.js';
32
+ import * as tui from '../../ui/tui.js';
33
+ // ─── Helpers ────────────────────────────────────────────────────
34
+ class ProcessExitError extends Error {
35
+ code;
36
+ constructor(code) {
37
+ super(`process.exit(${code})`);
38
+ this.code = code;
39
+ }
40
+ }
41
+ async function runStatusCommand(args = []) {
42
+ const parent = new Command('hooks');
43
+ registerStatusCommand(parent);
44
+ try {
45
+ await parent.parseAsync(['status', ...args], { from: 'user' });
46
+ }
47
+ catch (err) {
48
+ if (!(err instanceof ProcessExitError))
49
+ throw err;
50
+ }
51
+ }
52
+ // ─── Setup ──────────────────────────────────────────────────────
53
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
54
+ let exitSpy;
55
+ beforeEach(() => {
56
+ vi.clearAllMocks();
57
+ exitSpy = vi
58
+ .spyOn(process, 'exit')
59
+ .mockImplementation(((code) => {
60
+ throw new ProcessExitError(code);
61
+ }));
62
+ mockIsGitRepo.mockReturnValue(true);
63
+ mockGetHooksDir.mockReturnValue('/repo/.git/hooks');
64
+ });
65
+ afterEach(() => {
66
+ exitSpy.mockRestore();
67
+ });
68
+ // ─── Tests ──────────────────────────────────────────────────────
69
+ describe('ghagga hooks status', () => {
70
+ it('shows status for both pre-commit and commit-msg hooks', async () => {
71
+ mockGetHookStatus
72
+ .mockReturnValueOnce({ type: 'pre-commit', installed: true, managedByGhagga: true, path: '/repo/.git/hooks/pre-commit' })
73
+ .mockReturnValueOnce({ type: 'commit-msg', installed: false, managedByGhagga: false, path: '/repo/.git/hooks/commit-msg' });
74
+ await runStatusCommand();
75
+ expect(mockGetHookStatus).toHaveBeenCalledTimes(2);
76
+ expect(mockGetHookStatus).toHaveBeenCalledWith('/repo/.git/hooks', 'pre-commit');
77
+ expect(mockGetHookStatus).toHaveBeenCalledWith('/repo/.git/hooks', 'commit-msg');
78
+ });
79
+ it('exits with error when not in a git repo', async () => {
80
+ mockIsGitRepo.mockReturnValue(false);
81
+ await runStatusCommand();
82
+ expect(exitSpy).toHaveBeenCalledWith(1);
83
+ expect(tui.log.error).toHaveBeenCalledWith(expect.stringContaining('Not a git repository'));
84
+ expect(mockGetHookStatus).not.toHaveBeenCalled();
85
+ });
86
+ it('shows "not installed" for hooks that do not exist', async () => {
87
+ mockGetHookStatus.mockReturnValue({
88
+ type: 'pre-commit', installed: false, managedByGhagga: false, path: '/repo/.git/hooks/pre-commit',
89
+ });
90
+ await runStatusCommand();
91
+ expect(tui.log.info).toHaveBeenCalledWith(expect.stringContaining('not installed'));
92
+ });
93
+ it('shows "GHAGGA-managed" for installed GHAGGA hooks', async () => {
94
+ mockGetHookStatus.mockReturnValue({
95
+ type: 'pre-commit', installed: true, managedByGhagga: true, path: '/repo/.git/hooks/pre-commit',
96
+ });
97
+ await runStatusCommand();
98
+ expect(tui.log.success).toHaveBeenCalledWith(expect.stringContaining('GHAGGA-managed'));
99
+ });
100
+ it('shows "external" for installed non-GHAGGA hooks', async () => {
101
+ mockGetHookStatus.mockReturnValue({
102
+ type: 'pre-commit', installed: true, managedByGhagga: false, path: '/repo/.git/hooks/pre-commit',
103
+ });
104
+ await runStatusCommand();
105
+ expect(tui.log.warn).toHaveBeenCalledWith(expect.stringContaining('external'));
106
+ });
107
+ it('shows hooks directory path', async () => {
108
+ mockGetHookStatus.mockReturnValue({
109
+ type: 'pre-commit', installed: false, managedByGhagga: false, path: '/repo/.git/hooks/pre-commit',
110
+ });
111
+ await runStatusCommand();
112
+ expect(tui.log.info).toHaveBeenCalledWith(expect.stringContaining('/repo/.git/hooks'));
113
+ });
114
+ it('shows mixed status for different hooks', async () => {
115
+ mockGetHookStatus
116
+ .mockReturnValueOnce({ type: 'pre-commit', installed: true, managedByGhagga: true, path: '/repo/.git/hooks/pre-commit' })
117
+ .mockReturnValueOnce({ type: 'commit-msg', installed: true, managedByGhagga: false, path: '/repo/.git/hooks/commit-msg' });
118
+ await runStatusCommand();
119
+ expect(tui.log.success).toHaveBeenCalledWith(expect.stringContaining('pre-commit'));
120
+ expect(tui.log.warn).toHaveBeenCalledWith(expect.stringContaining('commit-msg'));
121
+ });
122
+ });
123
+ //# sourceMappingURL=status.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.test.js","sourceRoot":"","sources":["../../../src/commands/hooks/status.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `ghagga hooks uninstall` subcommand.
3
+ *
4
+ * Removes GHAGGA-managed hooks from the current git repository.
5
+ * Only removes hooks that contain the GHAGGA marker comment.
6
+ * Restores backed-up hooks if they exist.
7
+ *
8
+ * @see Phase 3, Task 3.3
9
+ */
10
+ import { Command } from 'commander';
11
+ export declare function registerUninstallCommand(parent: Command): void;
12
+ //# sourceMappingURL=uninstall.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uninstall.d.ts","sourceRoot":"","sources":["../../../src/commands/hooks/uninstall.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAgC9D"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * `ghagga hooks uninstall` subcommand.
3
+ *
4
+ * Removes GHAGGA-managed hooks from the current git repository.
5
+ * Only removes hooks that contain the GHAGGA marker comment.
6
+ * Restores backed-up hooks if they exist.
7
+ *
8
+ * @see Phase 3, Task 3.3
9
+ */
10
+ import { isGitRepo, getHooksDir, uninstallHook } from '../../lib/git-hooks.js';
11
+ import * as tui from '../../ui/tui.js';
12
+ const HOOK_TYPES = ['pre-commit', 'commit-msg'];
13
+ export function registerUninstallCommand(parent) {
14
+ parent
15
+ .command('uninstall')
16
+ .description('Remove GHAGGA-managed git hooks')
17
+ .action(() => {
18
+ if (!isGitRepo()) {
19
+ tui.log.error('Not a git repository. Run this command from inside a git repo.');
20
+ process.exit(1);
21
+ }
22
+ const hooksDir = getHooksDir();
23
+ let removed = 0;
24
+ for (const hookType of HOOK_TYPES) {
25
+ const result = uninstallHook(hooksDir, hookType);
26
+ if (result.success) {
27
+ tui.log.success(result.message);
28
+ if (result.message.includes('Removed')) {
29
+ removed++;
30
+ }
31
+ }
32
+ else {
33
+ tui.log.warn(result.message);
34
+ }
35
+ }
36
+ if (removed > 0) {
37
+ tui.log.info(`\nRemoved ${removed} GHAGGA hook(s) from ${hooksDir}`);
38
+ }
39
+ else {
40
+ tui.log.info('\nNo GHAGGA hooks were found to remove.');
41
+ }
42
+ });
43
+ }
44
+ //# sourceMappingURL=uninstall.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uninstall.js","sourceRoot":"","sources":["../../../src/commands/hooks/uninstall.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAE/E,OAAO,KAAK,GAAG,MAAM,iBAAiB,CAAC;AAEvC,MAAM,UAAU,GAAe,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;AAE5D,MAAM,UAAU,wBAAwB,CAAC,MAAe;IACtD,MAAM;SACH,OAAO,CAAC,WAAW,CAAC;SACpB,WAAW,CAAC,iCAAiC,CAAC;SAC9C,MAAM,CAAC,GAAG,EAAE;QACX,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;YACjB,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,gEAAgE,CAAC,CAAC;YAChF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;QAC/B,IAAI,OAAO,GAAG,CAAC,CAAC;QAEhB,KAAK,MAAM,QAAQ,IAAI,UAAU,EAAE,CAAC;YAClC,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YAEjD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACnB,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;gBAChC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;oBACvC,OAAO,EAAE,CAAC;gBACZ,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC/B,CAAC;QACH,CAAC;QAED,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;YAChB,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,OAAO,wBAAwB,QAAQ,EAAE,CAAC,CAAC;QACvE,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,yCAAyC,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Tests for `ghagga hooks uninstall` subcommand.
3
+ *
4
+ * Mocks git-hooks utilities and the TUI layer.
5
+ * Tests uninstall of both hooks, non-git-repo error, and result messages.
6
+ *
7
+ * @see Phase 4, Test 5
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=uninstall.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uninstall.test.d.ts","sourceRoot":"","sources":["../../../src/commands/hooks/uninstall.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Tests for `ghagga hooks uninstall` subcommand.
3
+ *
4
+ * Mocks git-hooks utilities and the TUI layer.
5
+ * Tests uninstall of both hooks, non-git-repo error, and result messages.
6
+ *
7
+ * @see Phase 4, Test 5
8
+ */
9
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
10
+ import { Command } from 'commander';
11
+ // ─── Mocks ──────────────────────────────────────────────────────
12
+ const { mockIsGitRepo, mockGetHooksDir, mockUninstallHook } = vi.hoisted(() => ({
13
+ mockIsGitRepo: vi.fn(),
14
+ mockGetHooksDir: vi.fn(),
15
+ mockUninstallHook: vi.fn(),
16
+ }));
17
+ vi.mock('../../lib/git-hooks.js', () => ({
18
+ isGitRepo: (...args) => mockIsGitRepo(...args),
19
+ getHooksDir: (...args) => mockGetHooksDir(...args),
20
+ uninstallHook: (...args) => mockUninstallHook(...args),
21
+ }));
22
+ vi.mock('../../ui/tui.js', () => ({
23
+ log: {
24
+ success: vi.fn(),
25
+ error: vi.fn(),
26
+ info: vi.fn(),
27
+ warn: vi.fn(),
28
+ },
29
+ }));
30
+ import { registerUninstallCommand } from './uninstall.js';
31
+ import * as tui from '../../ui/tui.js';
32
+ // ─── Helpers ────────────────────────────────────────────────────
33
+ class ProcessExitError extends Error {
34
+ code;
35
+ constructor(code) {
36
+ super(`process.exit(${code})`);
37
+ this.code = code;
38
+ }
39
+ }
40
+ async function runUninstallCommand(args = []) {
41
+ const parent = new Command('hooks');
42
+ registerUninstallCommand(parent);
43
+ try {
44
+ await parent.parseAsync(['uninstall', ...args], { from: 'user' });
45
+ }
46
+ catch (err) {
47
+ if (!(err instanceof ProcessExitError))
48
+ throw err;
49
+ }
50
+ }
51
+ // ─── Setup ──────────────────────────────────────────────────────
52
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
53
+ let exitSpy;
54
+ beforeEach(() => {
55
+ vi.clearAllMocks();
56
+ exitSpy = vi
57
+ .spyOn(process, 'exit')
58
+ .mockImplementation(((code) => {
59
+ throw new ProcessExitError(code);
60
+ }));
61
+ mockIsGitRepo.mockReturnValue(true);
62
+ mockGetHooksDir.mockReturnValue('/repo/.git/hooks');
63
+ });
64
+ afterEach(() => {
65
+ exitSpy.mockRestore();
66
+ });
67
+ // ─── Tests ──────────────────────────────────────────────────────
68
+ describe('ghagga hooks uninstall', () => {
69
+ it('calls uninstallHook for both pre-commit and commit-msg', async () => {
70
+ mockUninstallHook
71
+ .mockReturnValueOnce({ type: 'pre-commit', success: true, message: 'Removed pre-commit hook' })
72
+ .mockReturnValueOnce({ type: 'commit-msg', success: true, message: 'Removed commit-msg hook' });
73
+ await runUninstallCommand();
74
+ expect(mockUninstallHook).toHaveBeenCalledTimes(2);
75
+ expect(mockUninstallHook).toHaveBeenCalledWith('/repo/.git/hooks', 'pre-commit');
76
+ expect(mockUninstallHook).toHaveBeenCalledWith('/repo/.git/hooks', 'commit-msg');
77
+ });
78
+ it('exits with error when not in a git repo', async () => {
79
+ mockIsGitRepo.mockReturnValue(false);
80
+ await runUninstallCommand();
81
+ expect(exitSpy).toHaveBeenCalledWith(1);
82
+ expect(tui.log.error).toHaveBeenCalledWith(expect.stringContaining('Not a git repository'));
83
+ expect(mockUninstallHook).not.toHaveBeenCalled();
84
+ });
85
+ it('shows success messages for removed hooks', async () => {
86
+ mockUninstallHook
87
+ .mockReturnValueOnce({ type: 'pre-commit', success: true, message: 'Removed pre-commit hook' })
88
+ .mockReturnValueOnce({ type: 'commit-msg', success: true, message: 'Removed commit-msg hook' });
89
+ await runUninstallCommand();
90
+ expect(tui.log.success).toHaveBeenCalledWith('Removed pre-commit hook');
91
+ expect(tui.log.success).toHaveBeenCalledWith('Removed commit-msg hook');
92
+ expect(tui.log.info).toHaveBeenCalledWith(expect.stringContaining('Removed 2 GHAGGA hook(s)'));
93
+ });
94
+ it('shows warn for skipped external hooks', async () => {
95
+ mockUninstallHook.mockReturnValue({
96
+ type: 'pre-commit',
97
+ success: false,
98
+ message: 'Hook pre-commit exists but is not managed by GHAGGA. Skipping.',
99
+ });
100
+ await runUninstallCommand();
101
+ expect(tui.log.warn).toHaveBeenCalledWith(expect.stringContaining('not managed by GHAGGA'));
102
+ });
103
+ it('shows info when no GHAGGA hooks were found', async () => {
104
+ mockUninstallHook.mockReturnValue({
105
+ type: 'pre-commit',
106
+ success: true,
107
+ message: 'Hook pre-commit not installed, nothing to remove',
108
+ });
109
+ await runUninstallCommand();
110
+ expect(tui.log.info).toHaveBeenCalledWith(expect.stringContaining('No GHAGGA hooks were found to remove'));
111
+ });
112
+ it('shows correct count when only one hook is actually removed', async () => {
113
+ mockUninstallHook
114
+ .mockReturnValueOnce({ type: 'pre-commit', success: true, message: 'Removed pre-commit hook' })
115
+ .mockReturnValueOnce({ type: 'commit-msg', success: true, message: 'Hook commit-msg not installed, nothing to remove' });
116
+ await runUninstallCommand();
117
+ expect(tui.log.info).toHaveBeenCalledWith(expect.stringContaining('Removed 1 GHAGGA hook(s)'));
118
+ });
119
+ });
120
+ //# sourceMappingURL=uninstall.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uninstall.test.js","sourceRoot":"","sources":["../../../src/commands/hooks/uninstall.test.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAgCH,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAwDlD"}
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAiCH,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CA2DlD"}
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import { loadConfig, saveConfig } from '../lib/config.js';
8
8
  import { requestDeviceCode, pollForAccessToken, fetchGitHubUser, } from '../lib/oauth.js';
9
+ import * as tui from '../ui/tui.js';
9
10
  /**
10
11
  * Try to open a URL in the default browser.
11
12
  * Fails silently if no browser is available (e.g., headless server).
@@ -30,26 +31,28 @@ export async function loginCommand() {
30
31
  const config = loadConfig();
31
32
  // Check if already logged in
32
33
  if (config.githubToken && config.githubLogin) {
33
- console.log(`\u2139\ufe0f Already logged in as ${config.githubLogin}.`);
34
- console.log(' Run "ghagga logout" first to switch accounts.\n');
34
+ tui.log.info(`ℹ️ Already logged in as ${config.githubLogin}.`);
35
+ tui.log.info(' Run "ghagga logout" first to switch accounts.\n');
35
36
  return;
36
37
  }
37
- console.log('\ud83d\udd10 Authenticating with GitHub...\n');
38
+ tui.intro('🔐 Authenticating with GitHub');
38
39
  try {
39
40
  // Step 1: Request device code
40
41
  const deviceCode = await requestDeviceCode();
41
42
  // Step 2: Show user code and open browser
42
- console.log(' \u2794 Open this URL in your browser:\n');
43
- console.log(` \x1b[1m\x1b[36mhttps://github.com/login/device\x1b[0m\n`);
44
- console.log(` \u2794 Enter this code:\n`);
45
- console.log(` \x1b[1m\x1b[33m${deviceCode.user_code}\x1b[0m\n`);
43
+ tui.log.step(' Open this URL in your browser:\n');
44
+ tui.log.message(` https://github.com/login/device\n`);
45
+ tui.log.step(` Enter this code:\n`);
46
+ tui.log.message(` ${deviceCode.user_code}\n`);
46
47
  const opened = await tryOpenBrowser(deviceCode.verification_uri);
47
48
  if (opened) {
48
- console.log(' (Browser opened automatically)\n');
49
+ tui.log.info(' (Browser opened automatically)\n');
49
50
  }
50
- console.log(' Waiting for authorization...');
51
- // Step 3: Poll for access token
51
+ // Step 3: Poll for access token (with spinner)
52
+ const s = tui.spinner();
53
+ s.start('Waiting for authorization...');
52
54
  const tokenResponse = await pollForAccessToken(deviceCode.device_code, deviceCode.interval, deviceCode.expires_in);
55
+ s.stop('Authorization received');
53
56
  // Step 4: Fetch user profile
54
57
  const user = await fetchGitHubUser(tokenResponse.access_token);
55
58
  // Step 5: Save to config
@@ -60,13 +63,13 @@ export async function loginCommand() {
60
63
  defaultProvider: 'github',
61
64
  defaultModel: 'gpt-4o-mini',
62
65
  });
63
- console.log(`\n\u2705 Logged in as \x1b[1m${user.login}\x1b[0m`);
64
- console.log(' Provider: github (gpt-4o-mini) — free tier');
65
- console.log('\n Run "ghagga review ." to review your code!\n');
66
+ tui.log.success(`\n Logged in as ${user.login}`);
67
+ tui.log.info(' Provider: github (gpt-4o-mini) — free tier');
68
+ tui.outro('Run "ghagga review ." to review your code!');
66
69
  }
67
70
  catch (error) {
68
71
  const message = error instanceof Error ? error.message : String(error);
69
- console.error(`\n\u274c Login failed: ${message}\n`);
72
+ tui.log.error(`\n Login failed: ${message}\n`);
70
73
  process.exit(1);
71
74
  }
72
75
  }
@@ -1 +1 @@
1
- {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Login command tests.
3
+ *
4
+ * Tests the GitHub Device Flow login orchestration:
5
+ * already-logged-in shortcut, happy path, spinner lifecycle,
6
+ * browser auto-open, and error handling.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=login.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.test.d.ts","sourceRoot":"","sources":["../../src/commands/login.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Login command tests.
3
+ *
4
+ * Tests the GitHub Device Flow login orchestration:
5
+ * already-logged-in shortcut, happy path, spinner lifecycle,
6
+ * browser auto-open, and error handling.
7
+ */
8
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
9
+ // ─── Mocks ─────────────────────────────────────────────────────
10
+ vi.mock('../lib/config.js', () => ({
11
+ loadConfig: vi.fn(),
12
+ saveConfig: vi.fn(),
13
+ }));
14
+ vi.mock('../lib/oauth.js', () => ({
15
+ requestDeviceCode: vi.fn(),
16
+ pollForAccessToken: vi.fn(),
17
+ fetchGitHubUser: vi.fn(),
18
+ }));
19
+ const mockSpinner = { start: vi.fn(), stop: vi.fn(), message: vi.fn() };
20
+ vi.mock('../ui/tui.js', () => ({
21
+ intro: vi.fn(),
22
+ outro: vi.fn(),
23
+ log: {
24
+ info: vi.fn(),
25
+ step: vi.fn(),
26
+ message: vi.fn(),
27
+ success: vi.fn(),
28
+ error: vi.fn(),
29
+ warn: vi.fn(),
30
+ },
31
+ spinner: vi.fn(() => mockSpinner),
32
+ }));
33
+ vi.mock('node:child_process', () => ({
34
+ exec: vi.fn(),
35
+ }));
36
+ vi.mock('node:os', () => ({
37
+ platform: vi.fn().mockReturnValue('linux'),
38
+ }));
39
+ import { loadConfig, saveConfig } from '../lib/config.js';
40
+ import { requestDeviceCode, pollForAccessToken, fetchGitHubUser, } from '../lib/oauth.js';
41
+ import * as tui from '../ui/tui.js';
42
+ import { loginCommand } from './login.js';
43
+ const mockLoadConfig = vi.mocked(loadConfig);
44
+ const mockSaveConfig = vi.mocked(saveConfig);
45
+ const mockRequestDeviceCode = vi.mocked(requestDeviceCode);
46
+ const mockPollForAccessToken = vi.mocked(pollForAccessToken);
47
+ const mockFetchGitHubUser = vi.mocked(fetchGitHubUser);
48
+ // ─── Setup ─────────────────────────────────────────────────────
49
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
50
+ let mockExit;
51
+ beforeEach(() => {
52
+ vi.clearAllMocks();
53
+ mockExit = vi.spyOn(process, 'exit').mockImplementation((() => { }));
54
+ });
55
+ afterEach(() => {
56
+ mockExit.mockRestore();
57
+ });
58
+ // ─── Helpers ───────────────────────────────────────────────────
59
+ function setupHappyPath() {
60
+ mockLoadConfig.mockReturnValue({});
61
+ mockRequestDeviceCode.mockResolvedValue({
62
+ device_code: 'dc_123',
63
+ user_code: 'ABCD-1234',
64
+ verification_uri: 'https://github.com/login/device',
65
+ expires_in: 900,
66
+ interval: 5,
67
+ });
68
+ mockPollForAccessToken.mockResolvedValue({
69
+ access_token: 'gho_newtoken',
70
+ token_type: 'bearer',
71
+ scope: '',
72
+ });
73
+ mockFetchGitHubUser.mockResolvedValue({
74
+ login: 'newuser',
75
+ id: 42,
76
+ avatar_url: 'https://github.com/newuser.png',
77
+ });
78
+ }
79
+ // ─── Tests ─────────────────────────────────────────────────────
80
+ describe('loginCommand', () => {
81
+ it('shows "already logged in" when token and login exist', async () => {
82
+ mockLoadConfig.mockReturnValue({
83
+ githubToken: 'gho_existing',
84
+ githubLogin: 'existinguser',
85
+ });
86
+ await loginCommand();
87
+ expect(tui.log.info).toHaveBeenCalledWith(expect.stringContaining('Already logged in as existinguser'));
88
+ expect(mockRequestDeviceCode).not.toHaveBeenCalled();
89
+ expect(mockSaveConfig).not.toHaveBeenCalled();
90
+ });
91
+ it('completes full device flow and saves config on success', async () => {
92
+ setupHappyPath();
93
+ await loginCommand();
94
+ // Verify device code was requested
95
+ expect(mockRequestDeviceCode).toHaveBeenCalledOnce();
96
+ // Verify user code was displayed
97
+ expect(tui.log.message).toHaveBeenCalledWith(expect.stringContaining('ABCD-1234'));
98
+ // Verify poll was called with device code params
99
+ expect(mockPollForAccessToken).toHaveBeenCalledWith('dc_123', 5, 900);
100
+ // Verify user was fetched with the new token
101
+ expect(mockFetchGitHubUser).toHaveBeenCalledWith('gho_newtoken');
102
+ // Verify config was saved with correct data
103
+ expect(mockSaveConfig).toHaveBeenCalledWith({
104
+ githubToken: 'gho_newtoken',
105
+ githubLogin: 'newuser',
106
+ defaultProvider: 'github',
107
+ defaultModel: 'gpt-4o-mini',
108
+ });
109
+ // Verify success message
110
+ expect(tui.log.success).toHaveBeenCalledWith(expect.stringContaining('newuser'));
111
+ });
112
+ it('manages spinner lifecycle: start → stop', async () => {
113
+ setupHappyPath();
114
+ await loginCommand();
115
+ expect(tui.spinner).toHaveBeenCalledOnce();
116
+ expect(mockSpinner.start).toHaveBeenCalledWith('Waiting for authorization...');
117
+ expect(mockSpinner.stop).toHaveBeenCalledWith('Authorization received');
118
+ // start must be called before stop
119
+ const startOrder = mockSpinner.start.mock.invocationCallOrder[0];
120
+ const stopOrder = mockSpinner.stop.mock.invocationCallOrder[0];
121
+ expect(startOrder).toBeLessThan(stopOrder);
122
+ });
123
+ it('shows browser-opened message when tryOpenBrowser succeeds', async () => {
124
+ setupHappyPath();
125
+ await loginCommand();
126
+ // tryOpenBrowser uses dynamic import of node:child_process
127
+ // When exec doesn't throw, the browser is considered "opened"
128
+ expect(tui.log.info).toHaveBeenCalledWith(expect.stringContaining('Browser opened automatically'));
129
+ });
130
+ it('calls tui.log.error and process.exit(1) on failure', async () => {
131
+ mockLoadConfig.mockReturnValue({});
132
+ mockRequestDeviceCode.mockRejectedValue(new Error('Network error'));
133
+ await loginCommand();
134
+ expect(tui.log.error).toHaveBeenCalledWith(expect.stringContaining('Login failed: Network error'));
135
+ expect(mockExit).toHaveBeenCalledWith(1);
136
+ });
137
+ });
138
+ //# sourceMappingURL=login.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.test.js","sourceRoot":"","sources":["../../src/commands/login.test.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"logout.d.ts","sourceRoot":"","sources":["../../src/commands/logout.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,wBAAgB,aAAa,IAAI,IAAI,CAapC"}
1
+ {"version":3,"file":"logout.d.ts","sourceRoot":"","sources":["../../src/commands/logout.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,wBAAgB,aAAa,IAAI,IAAI,CAapC"}
@@ -2,15 +2,16 @@
2
2
  * Logout command — clears stored GitHub credentials.
3
3
  */
4
4
  import { clearConfig, isLoggedIn, loadConfig } from '../lib/config.js';
5
+ import * as tui from '../ui/tui.js';
5
6
  export function logoutCommand() {
6
7
  if (!isLoggedIn()) {
7
- console.log('\u2139\ufe0f Not currently logged in.\n');
8
+ tui.log.info('ℹ️ Not currently logged in.\n');
8
9
  return;
9
10
  }
10
11
  const config = loadConfig();
11
12
  const login = config.githubLogin ?? 'unknown';
12
13
  clearConfig();
13
- console.log(`\u2705 Logged out from ${login}.`);
14
- console.log(' Stored credentials have been removed.\n');
14
+ tui.log.success(`✅ Logged out from ${login}.`);
15
+ tui.log.info(' Stored credentials have been removed.\n');
15
16
  }
16
17
  //# sourceMappingURL=logout.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"logout.js","sourceRoot":"","sources":["../../src/commands/logout.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEvE,MAAM,UAAU,aAAa;IAC3B,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;QAClB,OAAO,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC;QACxD,OAAO;IACT,CAAC;IAED,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,IAAI,SAAS,CAAC;IAE9C,WAAW,EAAE,CAAC;IAEd,OAAO,CAAC,GAAG,CAAC,0BAA0B,KAAK,GAAG,CAAC,CAAC;IAChD,OAAO,CAAC,GAAG,CAAC,4CAA4C,CAAC,CAAC;AAC5D,CAAC"}
1
+ {"version":3,"file":"logout.js","sourceRoot":"","sources":["../../src/commands/logout.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AAEpC,MAAM,UAAU,aAAa;IAC3B,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;QAClB,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;QAC/C,OAAO;IACT,CAAC;IAED,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,IAAI,SAAS,CAAC;IAE9C,WAAW,EAAE,CAAC;IAEd,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,qBAAqB,KAAK,GAAG,CAAC,CAAC;IAC/C,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;AAC7D,CAAC"}