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.
- package/README.md +90 -19
- package/dist/commands/hooks/index.d.ts +9 -0
- package/dist/commands/hooks/index.d.ts.map +1 -0
- package/dist/commands/hooks/index.js +16 -0
- package/dist/commands/hooks/index.js.map +1 -0
- package/dist/commands/hooks/install.d.ts +13 -0
- package/dist/commands/hooks/install.d.ts.map +1 -0
- package/dist/commands/hooks/install.js +64 -0
- package/dist/commands/hooks/install.js.map +1 -0
- package/dist/commands/hooks/install.test.d.ts +11 -0
- package/dist/commands/hooks/install.test.d.ts.map +1 -0
- package/dist/commands/hooks/install.test.js +157 -0
- package/dist/commands/hooks/install.test.js.map +1 -0
- package/dist/commands/hooks/status.d.ts +12 -0
- package/dist/commands/hooks/status.d.ts.map +1 -0
- package/dist/commands/hooks/status.js +38 -0
- package/dist/commands/hooks/status.js.map +1 -0
- package/dist/commands/hooks/status.test.d.ts +11 -0
- package/dist/commands/hooks/status.test.d.ts.map +1 -0
- package/dist/commands/hooks/status.test.js +123 -0
- package/dist/commands/hooks/status.test.js.map +1 -0
- package/dist/commands/hooks/uninstall.d.ts +12 -0
- package/dist/commands/hooks/uninstall.d.ts.map +1 -0
- package/dist/commands/hooks/uninstall.js +44 -0
- package/dist/commands/hooks/uninstall.js.map +1 -0
- package/dist/commands/hooks/uninstall.test.d.ts +10 -0
- package/dist/commands/hooks/uninstall.test.d.ts.map +1 -0
- package/dist/commands/hooks/uninstall.test.js +120 -0
- package/dist/commands/hooks/uninstall.test.js.map +1 -0
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +17 -14
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/login.test.d.ts +9 -0
- package/dist/commands/login.test.d.ts.map +1 -0
- package/dist/commands/login.test.js +138 -0
- package/dist/commands/login.test.js.map +1 -0
- package/dist/commands/logout.d.ts.map +1 -1
- package/dist/commands/logout.js +4 -3
- package/dist/commands/logout.js.map +1 -1
- package/dist/commands/logout.test.d.ts +8 -0
- package/dist/commands/logout.test.d.ts.map +1 -0
- package/dist/commands/logout.test.js +61 -0
- package/dist/commands/logout.test.js.map +1 -0
- package/dist/commands/memory/clear.d.ts +11 -0
- package/dist/commands/memory/clear.d.ts.map +1 -0
- package/dist/commands/memory/clear.js +45 -0
- package/dist/commands/memory/clear.js.map +1 -0
- package/dist/commands/memory/clear.test.d.ts +11 -0
- package/dist/commands/memory/clear.test.d.ts.map +1 -0
- package/dist/commands/memory/clear.test.js +178 -0
- package/dist/commands/memory/clear.test.js.map +1 -0
- package/dist/commands/memory/delete.d.ts +11 -0
- package/dist/commands/memory/delete.d.ts.map +1 -0
- package/dist/commands/memory/delete.js +38 -0
- package/dist/commands/memory/delete.js.map +1 -0
- package/dist/commands/memory/delete.test.d.ts +11 -0
- package/dist/commands/memory/delete.test.d.ts.map +1 -0
- package/dist/commands/memory/delete.test.js +176 -0
- package/dist/commands/memory/delete.test.js.map +1 -0
- package/dist/commands/memory/index.d.ts +10 -0
- package/dist/commands/memory/index.d.ts.map +1 -0
- package/dist/commands/memory/index.js +23 -0
- package/dist/commands/memory/index.js.map +1 -0
- package/dist/commands/memory/list.d.ts +11 -0
- package/dist/commands/memory/list.d.ts.map +1 -0
- package/dist/commands/memory/list.js +47 -0
- package/dist/commands/memory/list.js.map +1 -0
- package/dist/commands/memory/list.test.d.ts +10 -0
- package/dist/commands/memory/list.test.d.ts.map +1 -0
- package/dist/commands/memory/list.test.js +135 -0
- package/dist/commands/memory/list.test.js.map +1 -0
- package/dist/commands/memory/search.d.ts +12 -0
- package/dist/commands/memory/search.d.ts.map +1 -0
- package/dist/commands/memory/search.js +44 -0
- package/dist/commands/memory/search.js.map +1 -0
- package/dist/commands/memory/search.test.d.ts +11 -0
- package/dist/commands/memory/search.test.d.ts.map +1 -0
- package/dist/commands/memory/search.test.js +122 -0
- package/dist/commands/memory/search.test.js.map +1 -0
- package/dist/commands/memory/show.d.ts +10 -0
- package/dist/commands/memory/show.d.ts.map +1 -0
- package/dist/commands/memory/show.js +51 -0
- package/dist/commands/memory/show.js.map +1 -0
- package/dist/commands/memory/show.test.d.ts +11 -0
- package/dist/commands/memory/show.test.d.ts.map +1 -0
- package/dist/commands/memory/show.test.js +156 -0
- package/dist/commands/memory/show.test.js.map +1 -0
- package/dist/commands/memory/stats.d.ts +11 -0
- package/dist/commands/memory/stats.d.ts.map +1 -0
- package/dist/commands/memory/stats.js +63 -0
- package/dist/commands/memory/stats.js.map +1 -0
- package/dist/commands/memory/stats.test.d.ts +10 -0
- package/dist/commands/memory/stats.test.d.ts.map +1 -0
- package/dist/commands/memory/stats.test.js +122 -0
- package/dist/commands/memory/stats.test.js.map +1 -0
- package/dist/commands/memory/utils.d.ts +44 -0
- package/dist/commands/memory/utils.d.ts.map +1 -0
- package/dist/commands/memory/utils.js +90 -0
- package/dist/commands/memory/utils.js.map +1 -0
- package/dist/commands/memory/utils.test.d.ts +10 -0
- package/dist/commands/memory/utils.test.d.ts.map +1 -0
- package/dist/commands/memory/utils.test.js +93 -0
- package/dist/commands/memory/utils.test.js.map +1 -0
- package/dist/commands/review-commit-msg.d.ts +28 -0
- package/dist/commands/review-commit-msg.d.ts.map +1 -0
- package/dist/commands/review-commit-msg.js +126 -0
- package/dist/commands/review-commit-msg.js.map +1 -0
- package/dist/commands/review-commit-msg.test.d.ts +11 -0
- package/dist/commands/review-commit-msg.test.d.ts.map +1 -0
- package/dist/commands/review-commit-msg.test.js +126 -0
- package/dist/commands/review-commit-msg.test.js.map +1 -0
- package/dist/commands/review.d.ts +7 -0
- package/dist/commands/review.d.ts.map +1 -1
- package/dist/commands/review.js +138 -103
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/review.test.js +267 -1
- package/dist/commands/review.test.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +12 -11
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/status.test.d.ts +8 -0
- package/dist/commands/status.test.d.ts.map +1 -0
- package/dist/commands/status.test.js +106 -0
- package/dist/commands/status.test.js.map +1 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +48 -16
- package/dist/index.js.map +1 -1
- package/dist/lib/config.d.ts +1 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +1 -1
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/config.test.d.ts +9 -0
- package/dist/lib/config.test.d.ts.map +1 -0
- package/dist/lib/config.test.js +181 -0
- package/dist/lib/config.test.js.map +1 -0
- package/dist/lib/git-hooks.d.ts +19 -0
- package/dist/lib/git-hooks.d.ts.map +1 -0
- package/dist/lib/git-hooks.js +129 -0
- package/dist/lib/git-hooks.js.map +1 -0
- package/dist/lib/git-hooks.test.d.ts +11 -0
- package/dist/lib/git-hooks.test.d.ts.map +1 -0
- package/dist/lib/git-hooks.test.js +178 -0
- package/dist/lib/git-hooks.test.js.map +1 -0
- package/dist/lib/git.d.ts +33 -0
- package/dist/lib/git.d.ts.map +1 -0
- package/dist/lib/git.js +85 -0
- package/dist/lib/git.js.map +1 -0
- package/dist/lib/git.test.d.ts +2 -0
- package/dist/lib/git.test.d.ts.map +1 -0
- package/dist/lib/git.test.js +65 -0
- package/dist/lib/git.test.js.map +1 -0
- package/dist/lib/hook-templates.d.ts +12 -0
- package/dist/lib/hook-templates.d.ts.map +1 -0
- package/dist/lib/hook-templates.js +52 -0
- package/dist/lib/hook-templates.js.map +1 -0
- package/dist/lib/hook-templates.test.d.ts +11 -0
- package/dist/lib/hook-templates.test.d.ts.map +1 -0
- package/dist/lib/hook-templates.test.js +76 -0
- package/dist/lib/hook-templates.test.js.map +1 -0
- package/dist/lib/hooks-types.d.ts +30 -0
- package/dist/lib/hooks-types.d.ts.map +1 -0
- package/dist/lib/hooks-types.js +9 -0
- package/dist/lib/hooks-types.js.map +1 -0
- package/dist/lib/oauth.test.d.ts +8 -0
- package/dist/lib/oauth.test.d.ts.map +1 -0
- package/dist/lib/oauth.test.js +212 -0
- package/dist/lib/oauth.test.js.map +1 -0
- package/dist/ui/__tests__/format.test.d.ts +7 -0
- package/dist/ui/__tests__/format.test.d.ts.map +1 -0
- package/dist/ui/__tests__/format.test.js +220 -0
- package/dist/ui/__tests__/format.test.js.map +1 -0
- package/dist/ui/__tests__/theme.test.d.ts +7 -0
- package/dist/ui/__tests__/theme.test.d.ts.map +1 -0
- package/dist/ui/__tests__/theme.test.js +79 -0
- package/dist/ui/__tests__/theme.test.js.map +1 -0
- package/dist/ui/__tests__/tui.test.d.ts +9 -0
- package/dist/ui/__tests__/tui.test.d.ts.map +1 -0
- package/dist/ui/__tests__/tui.test.js +222 -0
- package/dist/ui/__tests__/tui.test.js.map +1 -0
- package/dist/ui/format.d.ts +38 -0
- package/dist/ui/format.d.ts.map +1 -0
- package/dist/ui/format.js +136 -0
- package/dist/ui/format.js.map +1 -0
- package/dist/ui/theme.d.ts +26 -0
- package/dist/ui/theme.d.ts.map +1 -0
- package/dist/ui/theme.js +63 -0
- package/dist/ui/theme.js.map +1 -0
- package/dist/ui/tui.d.ts +44 -0
- package/dist/ui/tui.d.ts.map +1 -0
- package/dist/ui/tui.js +121 -0
- package/dist/ui/tui.js.map +1 -0
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -12,12 +12,16 @@
|
|
|
12
12
|
* You can override with --provider, --model, --api-key for other providers.
|
|
13
13
|
*
|
|
14
14
|
* Environment variables (override stored config):
|
|
15
|
-
* GHAGGA_API_KEY
|
|
16
|
-
* GHAGGA_PROVIDER
|
|
17
|
-
* GHAGGA_MODEL
|
|
18
|
-
* GITHUB_TOKEN
|
|
15
|
+
* GHAGGA_API_KEY API key for the LLM provider
|
|
16
|
+
* GHAGGA_PROVIDER LLM provider: anthropic, openai, google, github, ollama, qwen
|
|
17
|
+
* GHAGGA_MODEL Model identifier
|
|
18
|
+
* GITHUB_TOKEN GitHub token (fallback for github provider)
|
|
19
|
+
* GHAGGA_MEMORY_BACKEND Memory backend: sqlite (default) or engram
|
|
19
20
|
*/
|
|
20
21
|
import 'dotenv/config';
|
|
22
|
+
import { readFileSync } from 'node:fs';
|
|
23
|
+
import { fileURLToPath } from 'node:url';
|
|
24
|
+
import { dirname, resolve } from 'node:path';
|
|
21
25
|
import { Command } from 'commander';
|
|
22
26
|
import { DEFAULT_MODELS } from 'ghagga-core';
|
|
23
27
|
import { loadConfig, getStoredToken } from './lib/config.js';
|
|
@@ -25,11 +29,23 @@ import { reviewCommand } from './commands/review.js';
|
|
|
25
29
|
import { loginCommand } from './commands/login.js';
|
|
26
30
|
import { logoutCommand } from './commands/logout.js';
|
|
27
31
|
import { statusCommand } from './commands/status.js';
|
|
32
|
+
import { memoryCommand } from './commands/memory/index.js';
|
|
33
|
+
import { hooksCommand } from './commands/hooks/index.js';
|
|
34
|
+
import * as tui from './ui/tui.js';
|
|
35
|
+
// Read version from package.json at runtime (no hardcoded strings)
|
|
36
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
37
|
+
const pkg = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf-8'));
|
|
28
38
|
const program = new Command();
|
|
29
39
|
program
|
|
30
40
|
.name('ghagga')
|
|
31
41
|
.description('AI-powered code review CLI')
|
|
32
|
-
.version(
|
|
42
|
+
.version(pkg.version)
|
|
43
|
+
.option('--plain', 'Disable styled terminal output');
|
|
44
|
+
// Initialize TUI mode before any command runs (Design AD3)
|
|
45
|
+
program.hook('preAction', (thisCommand) => {
|
|
46
|
+
const opts = thisCommand.optsWithGlobals();
|
|
47
|
+
tui.init({ plain: opts.plain });
|
|
48
|
+
});
|
|
33
49
|
// ─── Login ──────────────────────────────────────────────────────
|
|
34
50
|
program
|
|
35
51
|
.command('login')
|
|
@@ -64,8 +80,14 @@ program
|
|
|
64
80
|
.option('--no-semgrep', 'Disable Semgrep static analysis')
|
|
65
81
|
.option('--no-trivy', 'Disable Trivy vulnerability scanning')
|
|
66
82
|
.option('--no-cpd', 'Disable CPD duplicate detection')
|
|
83
|
+
.option('--no-memory', 'Disable review memory')
|
|
67
84
|
.option('-c, --config <path>', 'Path to .ghagga.json config file')
|
|
68
85
|
.option('-v, --verbose', 'Show detailed progress during review')
|
|
86
|
+
.option('--staged', 'Review only staged changes (for pre-commit hooks)')
|
|
87
|
+
.option('--commit-msg <file>', 'Validate commit message from file path')
|
|
88
|
+
.option('--exit-on-issues', 'Exit with code 1 if critical/high issues found')
|
|
89
|
+
.option('--quick', 'Static analysis only — skip LLM review')
|
|
90
|
+
.option('--memory-backend <type>', 'Memory backend: sqlite (default) or engram (env: GHAGGA_MEMORY_BACKEND)', 'sqlite')
|
|
69
91
|
.action(async (path, options) => {
|
|
70
92
|
// ── Auto-resolve auth from stored config ──────────────────
|
|
71
93
|
const config = loadConfig();
|
|
@@ -86,27 +108,27 @@ program
|
|
|
86
108
|
// ── Validate mode ─────────────────────────────────────────
|
|
87
109
|
const validModes = ['simple', 'workflow', 'consensus'];
|
|
88
110
|
if (!validModes.includes(options.mode)) {
|
|
89
|
-
|
|
111
|
+
tui.log.error(`❌ Invalid mode "${options.mode}". Choose from: ${validModes.join(', ')}`);
|
|
90
112
|
process.exit(1);
|
|
91
113
|
}
|
|
92
114
|
// ── Validate provider ─────────────────────────────────────
|
|
93
|
-
const validProviders = ['anthropic', 'openai', 'google', 'github', 'ollama'];
|
|
115
|
+
const validProviders = ['anthropic', 'openai', 'google', 'github', 'ollama', 'qwen'];
|
|
94
116
|
if (!validProviders.includes(options.provider)) {
|
|
95
|
-
|
|
117
|
+
tui.log.error(`❌ Invalid provider "${options.provider}". Choose from: ${validProviders.join(', ')}`);
|
|
96
118
|
process.exit(1);
|
|
97
119
|
}
|
|
98
120
|
// ── Validate format ───────────────────────────────────────
|
|
99
121
|
const validFormats = ['markdown', 'json'];
|
|
100
122
|
if (!validFormats.includes(options.format)) {
|
|
101
|
-
|
|
123
|
+
tui.log.error(`❌ Invalid format "${options.format}". Choose from: ${validFormats.join(', ')}`);
|
|
102
124
|
process.exit(1);
|
|
103
125
|
}
|
|
104
|
-
// ── Validate API key (not required for ollama)
|
|
105
|
-
if (!options.apiKey && options.provider !== 'ollama') {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
126
|
+
// ── Validate API key (not required for ollama or --quick mode) ──
|
|
127
|
+
if (!options.apiKey && options.provider !== 'ollama' && !options.quick) {
|
|
128
|
+
tui.log.error('❌ No API key available.\n');
|
|
129
|
+
tui.log.error(' Quick fix: run "ghagga login" to authenticate with GitHub (free!)');
|
|
130
|
+
tui.log.error(' Or pass --api-key <key> or set GHAGGA_API_KEY.');
|
|
131
|
+
tui.log.error(' Or use --provider ollama for local models (no key needed).\n');
|
|
110
132
|
process.exit(1);
|
|
111
133
|
}
|
|
112
134
|
// Ollama doesn't need an API key — use a placeholder
|
|
@@ -120,14 +142,24 @@ program
|
|
|
120
142
|
mode: options.mode,
|
|
121
143
|
provider,
|
|
122
144
|
model,
|
|
123
|
-
apiKey: options.apiKey,
|
|
145
|
+
apiKey: options.apiKey ?? '',
|
|
124
146
|
format: options.format,
|
|
125
147
|
semgrep: options.semgrep,
|
|
126
148
|
trivy: options.trivy,
|
|
127
149
|
cpd: options.cpd,
|
|
150
|
+
memory: options.memory,
|
|
151
|
+
memoryBackend: options.memoryBackend,
|
|
128
152
|
config: options.config,
|
|
129
153
|
verbose: options.verbose ?? false,
|
|
154
|
+
staged: options.staged ?? false,
|
|
155
|
+
commitMsg: options.commitMsg,
|
|
156
|
+
exitOnIssues: options.exitOnIssues ?? false,
|
|
157
|
+
quick: options.quick ?? false,
|
|
130
158
|
});
|
|
131
159
|
});
|
|
160
|
+
// ─── Memory ─────────────────────────────────────────────────────
|
|
161
|
+
program.addCommand(memoryCommand);
|
|
162
|
+
// ─── Hooks ──────────────────────────────────────────────────────
|
|
163
|
+
program.addCommand(hooksCommand);
|
|
132
164
|
program.parse();
|
|
133
165
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/config.d.ts
CHANGED
package/dist/lib/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH,MAAM,WAAW,eAAe;IAC9B,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,2BAA2B;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,qCAAqC;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH,MAAM,WAAW,eAAe;IAC9B,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,2BAA2B;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,qCAAqC;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAID,wBAAgB,YAAY,IAAI,MAAM,CAIrC;AAQD;;GAEG;AACH,wBAAgB,UAAU,IAAI,eAAe,CAa5C;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CASxD;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,IAAI,CAElC;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,OAAO,CAGpC;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,GAAG,IAAI,CAG9C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C"}
|
package/dist/lib/config.js
CHANGED
|
@@ -8,7 +8,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
8
8
|
import { join } from 'node:path';
|
|
9
9
|
import { homedir } from 'node:os';
|
|
10
10
|
// ─── Paths ──────────────────────────────────────────────────────
|
|
11
|
-
function getConfigDir() {
|
|
11
|
+
export function getConfigDir() {
|
|
12
12
|
const xdgConfig = process.env['XDG_CONFIG_HOME'];
|
|
13
13
|
const base = xdgConfig || join(homedir(), '.config');
|
|
14
14
|
return join(base, 'ghagga');
|
package/dist/lib/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAkBlC,mEAAmE;AAEnE,
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAkBlC,mEAAmE;AAEnE,MAAM,UAAU,YAAY;IAC1B,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACjD,MAAM,IAAI,GAAG,SAAS,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;IACrD,OAAO,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC9B,CAAC;AAED,SAAS,aAAa;IACpB,OAAO,IAAI,CAAC,YAAY,EAAE,EAAE,aAAa,CAAC,CAAC;AAC7C,CAAC;AAED,mEAAmE;AAEnE;;GAEG;AACH,MAAM,UAAU,UAAU;IACxB,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;IAEnC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAC9C,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAoB,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,MAAuB;IAChD,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;IACjC,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;IAEnC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3B,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;AAC7E,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW;IACzB,UAAU,CAAC,EAAE,CAAC,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU;IACxB,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,OAAO,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC;AAC9B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,OAAO,MAAM,CAAC,WAAW,IAAI,IAAI,CAAC;AACpC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,aAAa,EAAE,CAAC;AACzB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for CLI configuration management.
|
|
3
|
+
*
|
|
4
|
+
* Mocks node:fs and node:os to isolate file system operations.
|
|
5
|
+
* Validates loadConfig, saveConfig, clearConfig, isLoggedIn,
|
|
6
|
+
* getStoredToken, and getConfigFilePath.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=config.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.test.d.ts","sourceRoot":"","sources":["../../src/lib/config.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for CLI configuration management.
|
|
3
|
+
*
|
|
4
|
+
* Mocks node:fs and node:os to isolate file system operations.
|
|
5
|
+
* Validates loadConfig, saveConfig, clearConfig, isLoggedIn,
|
|
6
|
+
* getStoredToken, and getConfigFilePath.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
9
|
+
// ─── Mocks ─────────────────────────────────────────────────────
|
|
10
|
+
vi.mock('node:fs', () => ({
|
|
11
|
+
existsSync: vi.fn(),
|
|
12
|
+
mkdirSync: vi.fn(),
|
|
13
|
+
readFileSync: vi.fn(),
|
|
14
|
+
writeFileSync: vi.fn(),
|
|
15
|
+
}));
|
|
16
|
+
vi.mock('node:os', () => ({
|
|
17
|
+
homedir: vi.fn().mockReturnValue('/mock-home'),
|
|
18
|
+
}));
|
|
19
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
20
|
+
import { homedir } from 'node:os';
|
|
21
|
+
import { loadConfig, saveConfig, clearConfig, isLoggedIn, getStoredToken, getConfigFilePath, } from './config.js';
|
|
22
|
+
const mockExistsSync = vi.mocked(existsSync);
|
|
23
|
+
const mockMkdirSync = vi.mocked(mkdirSync);
|
|
24
|
+
const mockReadFileSync = vi.mocked(readFileSync);
|
|
25
|
+
const mockWriteFileSync = vi.mocked(writeFileSync);
|
|
26
|
+
const mockHomedir = vi.mocked(homedir);
|
|
27
|
+
// ─── Setup ─────────────────────────────────────────────────────
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
vi.clearAllMocks();
|
|
30
|
+
mockHomedir.mockReturnValue('/mock-home');
|
|
31
|
+
// Clear XDG override so getConfigDir uses homedir()
|
|
32
|
+
delete process.env['XDG_CONFIG_HOME'];
|
|
33
|
+
});
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
delete process.env['XDG_CONFIG_HOME'];
|
|
36
|
+
});
|
|
37
|
+
// ─── loadConfig ────────────────────────────────────────────────
|
|
38
|
+
describe('loadConfig', () => {
|
|
39
|
+
it('should return empty object when config file does not exist', () => {
|
|
40
|
+
mockExistsSync.mockReturnValue(false);
|
|
41
|
+
const config = loadConfig();
|
|
42
|
+
expect(config).toEqual({});
|
|
43
|
+
expect(mockExistsSync).toHaveBeenCalledWith(expect.stringContaining('config.json'));
|
|
44
|
+
});
|
|
45
|
+
it('should parse and return valid JSON config', () => {
|
|
46
|
+
const stored = { githubToken: 'gho_abc123', githubLogin: 'testuser' };
|
|
47
|
+
mockExistsSync.mockReturnValue(true);
|
|
48
|
+
mockReadFileSync.mockReturnValue(JSON.stringify(stored));
|
|
49
|
+
const config = loadConfig();
|
|
50
|
+
expect(config).toEqual(stored);
|
|
51
|
+
expect(mockReadFileSync).toHaveBeenCalledWith(expect.stringContaining('config.json'), 'utf-8');
|
|
52
|
+
});
|
|
53
|
+
it('should return empty object when file contains invalid JSON', () => {
|
|
54
|
+
mockExistsSync.mockReturnValue(true);
|
|
55
|
+
mockReadFileSync.mockReturnValue('{ broken json!!!');
|
|
56
|
+
const config = loadConfig();
|
|
57
|
+
expect(config).toEqual({});
|
|
58
|
+
});
|
|
59
|
+
it('should return empty object when readFileSync throws', () => {
|
|
60
|
+
mockExistsSync.mockReturnValue(true);
|
|
61
|
+
mockReadFileSync.mockImplementation(() => {
|
|
62
|
+
throw new Error('EACCES: permission denied');
|
|
63
|
+
});
|
|
64
|
+
const config = loadConfig();
|
|
65
|
+
expect(config).toEqual({});
|
|
66
|
+
});
|
|
67
|
+
it('should use XDG_CONFIG_HOME when set', () => {
|
|
68
|
+
process.env['XDG_CONFIG_HOME'] = '/custom/config';
|
|
69
|
+
mockExistsSync.mockReturnValue(false);
|
|
70
|
+
loadConfig();
|
|
71
|
+
expect(mockExistsSync).toHaveBeenCalledWith(expect.stringContaining('/custom/config/ghagga/config.json'));
|
|
72
|
+
});
|
|
73
|
+
it('should use homedir/.config when XDG_CONFIG_HOME is not set', () => {
|
|
74
|
+
mockExistsSync.mockReturnValue(false);
|
|
75
|
+
loadConfig();
|
|
76
|
+
expect(mockExistsSync).toHaveBeenCalledWith(expect.stringContaining('/mock-home/.config/ghagga/config.json'));
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
// ─── saveConfig ────────────────────────────────────────────────
|
|
80
|
+
describe('saveConfig', () => {
|
|
81
|
+
it('should create config directory if it does not exist', () => {
|
|
82
|
+
mockExistsSync.mockReturnValue(false);
|
|
83
|
+
saveConfig({ githubToken: 'gho_token' });
|
|
84
|
+
expect(mockMkdirSync).toHaveBeenCalledWith(expect.stringContaining('ghagga'), { recursive: true });
|
|
85
|
+
});
|
|
86
|
+
it('should not create directory if it already exists', () => {
|
|
87
|
+
mockExistsSync.mockReturnValue(true);
|
|
88
|
+
saveConfig({ githubToken: 'gho_token' });
|
|
89
|
+
expect(mockMkdirSync).not.toHaveBeenCalled();
|
|
90
|
+
});
|
|
91
|
+
it('should write JSON with 2-space indent and trailing newline', () => {
|
|
92
|
+
mockExistsSync.mockReturnValue(true);
|
|
93
|
+
const config = { githubToken: 'gho_abc', githubLogin: 'user' };
|
|
94
|
+
saveConfig(config);
|
|
95
|
+
expect(mockWriteFileSync).toHaveBeenCalledWith(expect.stringContaining('config.json'), JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
96
|
+
});
|
|
97
|
+
it('should save empty config object', () => {
|
|
98
|
+
mockExistsSync.mockReturnValue(true);
|
|
99
|
+
saveConfig({});
|
|
100
|
+
expect(mockWriteFileSync).toHaveBeenCalledWith(expect.stringContaining('config.json'), '{}\n', 'utf-8');
|
|
101
|
+
});
|
|
102
|
+
it('should save all optional fields when provided', () => {
|
|
103
|
+
mockExistsSync.mockReturnValue(true);
|
|
104
|
+
const config = {
|
|
105
|
+
githubToken: 'gho_tok',
|
|
106
|
+
githubLogin: 'user',
|
|
107
|
+
defaultProvider: 'anthropic',
|
|
108
|
+
defaultModel: 'claude-sonnet-4-20250514',
|
|
109
|
+
};
|
|
110
|
+
saveConfig(config);
|
|
111
|
+
const writtenJson = mockWriteFileSync.mock.calls[0][1];
|
|
112
|
+
const parsed = JSON.parse(writtenJson);
|
|
113
|
+
expect(parsed).toEqual(config);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
// ─── clearConfig ───────────────────────────────────────────────
|
|
117
|
+
describe('clearConfig', () => {
|
|
118
|
+
it('should save an empty object', () => {
|
|
119
|
+
// existsSync is called twice: once for dir check, once for config path
|
|
120
|
+
// In saveConfig, existsSync is called for the dir
|
|
121
|
+
mockExistsSync.mockReturnValue(true);
|
|
122
|
+
clearConfig();
|
|
123
|
+
expect(mockWriteFileSync).toHaveBeenCalledWith(expect.stringContaining('config.json'), '{}\n', 'utf-8');
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
// ─── isLoggedIn ────────────────────────────────────────────────
|
|
127
|
+
describe('isLoggedIn', () => {
|
|
128
|
+
it('should return true when githubToken exists', () => {
|
|
129
|
+
mockExistsSync.mockReturnValue(true);
|
|
130
|
+
mockReadFileSync.mockReturnValue(JSON.stringify({ githubToken: 'gho_valid' }));
|
|
131
|
+
expect(isLoggedIn()).toBe(true);
|
|
132
|
+
});
|
|
133
|
+
it('should return false when githubToken is missing', () => {
|
|
134
|
+
mockExistsSync.mockReturnValue(true);
|
|
135
|
+
mockReadFileSync.mockReturnValue(JSON.stringify({ githubLogin: 'user' }));
|
|
136
|
+
expect(isLoggedIn()).toBe(false);
|
|
137
|
+
});
|
|
138
|
+
it('should return false when config file does not exist', () => {
|
|
139
|
+
mockExistsSync.mockReturnValue(false);
|
|
140
|
+
expect(isLoggedIn()).toBe(false);
|
|
141
|
+
});
|
|
142
|
+
it('should return false when githubToken is empty string', () => {
|
|
143
|
+
mockExistsSync.mockReturnValue(true);
|
|
144
|
+
mockReadFileSync.mockReturnValue(JSON.stringify({ githubToken: '' }));
|
|
145
|
+
expect(isLoggedIn()).toBe(false);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
// ─── getStoredToken ────────────────────────────────────────────
|
|
149
|
+
describe('getStoredToken', () => {
|
|
150
|
+
it('should return the token when present', () => {
|
|
151
|
+
mockExistsSync.mockReturnValue(true);
|
|
152
|
+
mockReadFileSync.mockReturnValue(JSON.stringify({ githubToken: 'gho_secret123' }));
|
|
153
|
+
expect(getStoredToken()).toBe('gho_secret123');
|
|
154
|
+
});
|
|
155
|
+
it('should return null when token is missing', () => {
|
|
156
|
+
mockExistsSync.mockReturnValue(true);
|
|
157
|
+
mockReadFileSync.mockReturnValue(JSON.stringify({}));
|
|
158
|
+
expect(getStoredToken()).toBeNull();
|
|
159
|
+
});
|
|
160
|
+
it('should return null when config file does not exist', () => {
|
|
161
|
+
mockExistsSync.mockReturnValue(false);
|
|
162
|
+
expect(getStoredToken()).toBeNull();
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
// ─── getConfigFilePath ─────────────────────────────────────────
|
|
166
|
+
describe('getConfigFilePath', () => {
|
|
167
|
+
it('should return path ending with ghagga/config.json', () => {
|
|
168
|
+
const path = getConfigFilePath();
|
|
169
|
+
expect(path).toMatch(/ghagga[/\\]config\.json$/);
|
|
170
|
+
});
|
|
171
|
+
it('should use homedir base path', () => {
|
|
172
|
+
const path = getConfigFilePath();
|
|
173
|
+
expect(path).toContain('/mock-home/.config/ghagga');
|
|
174
|
+
});
|
|
175
|
+
it('should respect XDG_CONFIG_HOME', () => {
|
|
176
|
+
process.env['XDG_CONFIG_HOME'] = '/xdg/path';
|
|
177
|
+
const path = getConfigFilePath();
|
|
178
|
+
expect(path).toContain('/xdg/path/ghagga');
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
//# sourceMappingURL=config.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.test.js","sourceRoot":"","sources":["../../src/lib/config.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git hook utilities for install, uninstall, and status operations.
|
|
3
|
+
*
|
|
4
|
+
* Handles detection of the hooks directory (respects core.hooksPath),
|
|
5
|
+
* GHAGGA marker detection, hook installation with backup, and
|
|
6
|
+
* clean uninstallation with backup restoration.
|
|
7
|
+
*/
|
|
8
|
+
import { type HookType, type HookStatus, type HookOperationResult } from './hooks-types.js';
|
|
9
|
+
/** Get the git hooks directory (respects core.hooksPath config) */
|
|
10
|
+
export declare function getHooksDir(): string;
|
|
11
|
+
/** Check if we're in a git repository */
|
|
12
|
+
export declare function isGitRepo(): boolean;
|
|
13
|
+
/** Get the status of a specific hook */
|
|
14
|
+
export declare function getHookStatus(hooksDir: string, hookType: HookType): HookStatus;
|
|
15
|
+
/** Install a hook script */
|
|
16
|
+
export declare function installHook(hooksDir: string, hookType: HookType, content: string, force: boolean): HookOperationResult;
|
|
17
|
+
/** Uninstall a hook if it's managed by GHAGGA */
|
|
18
|
+
export declare function uninstallHook(hooksDir: string, hookType: HookType): HookOperationResult;
|
|
19
|
+
//# sourceMappingURL=git-hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-hooks.d.ts","sourceRoot":"","sources":["../../src/lib/git-hooks.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,EAAe,KAAK,QAAQ,EAAE,KAAK,UAAU,EAAE,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAEzG,mEAAmE;AACnE,wBAAgB,WAAW,IAAI,MAAM,CAYpC;AAED,yCAAyC;AACzC,wBAAgB,SAAS,IAAI,OAAO,CAOnC;AAED,wCAAwC;AACxC,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,UAAU,CAoB9E;AAED,4BAA4B;AAC5B,wBAAgB,WAAW,CACzB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,OAAO,GACb,mBAAmB,CAmCrB;AAED,iDAAiD;AACjD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,mBAAmB,CAsCvF"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git hook utilities for install, uninstall, and status operations.
|
|
3
|
+
*
|
|
4
|
+
* Handles detection of the hooks directory (respects core.hooksPath),
|
|
5
|
+
* GHAGGA marker detection, hook installation with backup, and
|
|
6
|
+
* clean uninstallation with backup restoration.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, readFileSync, writeFileSync, unlinkSync, renameSync, chmodSync } from 'node:fs';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { execSync } from 'node:child_process';
|
|
11
|
+
import { HOOK_MARKER } from './hooks-types.js';
|
|
12
|
+
/** Get the git hooks directory (respects core.hooksPath config) */
|
|
13
|
+
export function getHooksDir() {
|
|
14
|
+
// Try core.hooksPath first
|
|
15
|
+
try {
|
|
16
|
+
const hooksPath = execSync('git config core.hooksPath', { encoding: 'utf-8' }).trim();
|
|
17
|
+
if (hooksPath)
|
|
18
|
+
return hooksPath;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Not set, use default
|
|
22
|
+
}
|
|
23
|
+
// Default: .git/hooks
|
|
24
|
+
const gitDir = execSync('git rev-parse --git-dir', { encoding: 'utf-8' }).trim();
|
|
25
|
+
return join(gitDir, 'hooks');
|
|
26
|
+
}
|
|
27
|
+
/** Check if we're in a git repository */
|
|
28
|
+
export function isGitRepo() {
|
|
29
|
+
try {
|
|
30
|
+
execSync('git rev-parse --git-dir', { stdio: 'pipe' });
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Get the status of a specific hook */
|
|
38
|
+
export function getHookStatus(hooksDir, hookType) {
|
|
39
|
+
const hookPath = join(hooksDir, hookType);
|
|
40
|
+
const exists = existsSync(hookPath);
|
|
41
|
+
let managedByGhagga = false;
|
|
42
|
+
if (exists) {
|
|
43
|
+
try {
|
|
44
|
+
const content = readFileSync(hookPath, 'utf-8');
|
|
45
|
+
managedByGhagga = content.includes(HOOK_MARKER);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Can't read, not managed
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
type: hookType,
|
|
53
|
+
installed: exists,
|
|
54
|
+
managedByGhagga,
|
|
55
|
+
path: hookPath,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** Install a hook script */
|
|
59
|
+
export function installHook(hooksDir, hookType, content, force) {
|
|
60
|
+
const hookPath = join(hooksDir, hookType);
|
|
61
|
+
const exists = existsSync(hookPath);
|
|
62
|
+
let backedUp = false;
|
|
63
|
+
if (exists) {
|
|
64
|
+
const existing = readFileSync(hookPath, 'utf-8');
|
|
65
|
+
if (existing.includes(HOOK_MARKER)) {
|
|
66
|
+
// Already GHAGGA-managed, overwrite
|
|
67
|
+
}
|
|
68
|
+
else if (!force) {
|
|
69
|
+
return {
|
|
70
|
+
type: hookType,
|
|
71
|
+
success: false,
|
|
72
|
+
message: `Hook ${hookType} already exists (not managed by GHAGGA). Use --force to overwrite.`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
// Backup existing hook
|
|
77
|
+
const backupPath = `${hookPath}.ghagga-backup`;
|
|
78
|
+
renameSync(hookPath, backupPath);
|
|
79
|
+
backedUp = true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
writeFileSync(hookPath, content, { mode: 0o755 });
|
|
83
|
+
// Also chmod explicitly for systems where writeFileSync mode doesn't work
|
|
84
|
+
chmodSync(hookPath, 0o755);
|
|
85
|
+
return {
|
|
86
|
+
type: hookType,
|
|
87
|
+
success: true,
|
|
88
|
+
message: backedUp
|
|
89
|
+
? `Installed ${hookType} hook (existing hook backed up to ${hookType}.ghagga-backup)`
|
|
90
|
+
: `Installed ${hookType} hook`,
|
|
91
|
+
backedUp,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/** Uninstall a hook if it's managed by GHAGGA */
|
|
95
|
+
export function uninstallHook(hooksDir, hookType) {
|
|
96
|
+
const hookPath = join(hooksDir, hookType);
|
|
97
|
+
if (!existsSync(hookPath)) {
|
|
98
|
+
return {
|
|
99
|
+
type: hookType,
|
|
100
|
+
success: true,
|
|
101
|
+
message: `Hook ${hookType} not installed, nothing to remove`,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const content = readFileSync(hookPath, 'utf-8');
|
|
105
|
+
if (!content.includes(HOOK_MARKER)) {
|
|
106
|
+
return {
|
|
107
|
+
type: hookType,
|
|
108
|
+
success: false,
|
|
109
|
+
message: `Hook ${hookType} exists but is not managed by GHAGGA. Skipping.`,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
unlinkSync(hookPath);
|
|
113
|
+
// Restore backup if exists
|
|
114
|
+
const backupPath = `${hookPath}.ghagga-backup`;
|
|
115
|
+
if (existsSync(backupPath)) {
|
|
116
|
+
renameSync(backupPath, hookPath);
|
|
117
|
+
return {
|
|
118
|
+
type: hookType,
|
|
119
|
+
success: true,
|
|
120
|
+
message: `Removed ${hookType} hook (restored previous hook from backup)`,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
type: hookType,
|
|
125
|
+
success: true,
|
|
126
|
+
message: `Removed ${hookType} hook`,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=git-hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-hooks.js","sourceRoot":"","sources":["../../src/lib/git-hooks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for git hook utilities (install, uninstall, status).
|
|
3
|
+
*
|
|
4
|
+
* Mocks node:fs, node:child_process, and node:path to test
|
|
5
|
+
* isGitRepo, getHooksDir, getHookStatus, installHook, and uninstallHook
|
|
6
|
+
* without touching the real filesystem or git.
|
|
7
|
+
*
|
|
8
|
+
* @see Phase 4, Test 2
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=git-hooks.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-hooks.test.d.ts","sourceRoot":"","sources":["../../src/lib/git-hooks.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
|