ghagga 3.0.0 → 3.1.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 (95) hide show
  1. package/dist/commands/login.test.js +0 -2
  2. package/dist/commands/login.test.js.map +1 -1
  3. package/dist/commands/review-output.test.js +28 -0
  4. package/dist/commands/review-output.test.js.map +1 -1
  5. package/dist/commands/review.d.ts +12 -0
  6. package/dist/commands/review.d.ts.map +1 -1
  7. package/dist/commands/review.js +225 -3
  8. package/dist/commands/review.js.map +1 -1
  9. package/dist/commands/review.mr-exit.test.d.ts +13 -0
  10. package/dist/commands/review.mr-exit.test.d.ts.map +1 -0
  11. package/dist/commands/review.mr-exit.test.js +190 -0
  12. package/dist/commands/review.mr-exit.test.js.map +1 -0
  13. package/dist/commands/review.postback-disclosure.test.d.ts +22 -0
  14. package/dist/commands/review.postback-disclosure.test.d.ts.map +1 -0
  15. package/dist/commands/review.postback-disclosure.test.js +192 -0
  16. package/dist/commands/review.postback-disclosure.test.js.map +1 -0
  17. package/dist/commands/review.pr-exit.test.d.ts +13 -0
  18. package/dist/commands/review.pr-exit.test.d.ts.map +1 -0
  19. package/dist/commands/review.pr-exit.test.js +162 -0
  20. package/dist/commands/review.pr-exit.test.js.map +1 -0
  21. package/dist/commands/review.pr-jenkins.test.d.ts +16 -0
  22. package/dist/commands/review.pr-jenkins.test.d.ts.map +1 -0
  23. package/dist/commands/review.pr-jenkins.test.js +106 -0
  24. package/dist/commands/review.pr-jenkins.test.js.map +1 -0
  25. package/dist/commands/review.pr-nativeid-fallback.test.d.ts +15 -0
  26. package/dist/commands/review.pr-nativeid-fallback.test.d.ts.map +1 -0
  27. package/dist/commands/review.pr-nativeid-fallback.test.js +151 -0
  28. package/dist/commands/review.pr-nativeid-fallback.test.js.map +1 -0
  29. package/dist/commands/review.test.js +4 -0
  30. package/dist/commands/review.test.js.map +1 -1
  31. package/dist/index.js +39 -2
  32. package/dist/index.js.map +1 -1
  33. package/dist/lib/cli-github-client-port.d.ts +52 -0
  34. package/dist/lib/cli-github-client-port.d.ts.map +1 -0
  35. package/dist/lib/cli-github-client-port.js +199 -0
  36. package/dist/lib/cli-github-client-port.js.map +1 -0
  37. package/dist/lib/cli-github-client-port.test.d.ts +10 -0
  38. package/dist/lib/cli-github-client-port.test.d.ts.map +1 -0
  39. package/dist/lib/cli-github-client-port.test.js +47 -0
  40. package/dist/lib/cli-github-client-port.test.js.map +1 -0
  41. package/dist/lib/cli-gitlab-client-port.d.ts +62 -0
  42. package/dist/lib/cli-gitlab-client-port.d.ts.map +1 -0
  43. package/dist/lib/cli-gitlab-client-port.js +209 -0
  44. package/dist/lib/cli-gitlab-client-port.js.map +1 -0
  45. package/dist/lib/cli-gitlab-client-port.test.d.ts +9 -0
  46. package/dist/lib/cli-gitlab-client-port.test.d.ts.map +1 -0
  47. package/dist/lib/cli-gitlab-client-port.test.js +278 -0
  48. package/dist/lib/cli-gitlab-client-port.test.js.map +1 -0
  49. package/dist/lib/config.d.ts +12 -0
  50. package/dist/lib/config.d.ts.map +1 -1
  51. package/dist/lib/config.js +9 -0
  52. package/dist/lib/config.js.map +1 -1
  53. package/dist/lib/forge-postback.d.ts +72 -0
  54. package/dist/lib/forge-postback.d.ts.map +1 -0
  55. package/dist/lib/forge-postback.js +39 -0
  56. package/dist/lib/forge-postback.js.map +1 -0
  57. package/dist/lib/gitlab-api.d.ts +50 -0
  58. package/dist/lib/gitlab-api.d.ts.map +1 -0
  59. package/dist/lib/gitlab-api.js +75 -0
  60. package/dist/lib/gitlab-api.js.map +1 -0
  61. package/dist/lib/gitlab-api.test.d.ts +2 -0
  62. package/dist/lib/gitlab-api.test.d.ts.map +1 -0
  63. package/dist/lib/gitlab-api.test.js +79 -0
  64. package/dist/lib/gitlab-api.test.js.map +1 -0
  65. package/dist/lib/gitlab-e2e.manual.test.d.ts +24 -0
  66. package/dist/lib/gitlab-e2e.manual.test.d.ts.map +1 -0
  67. package/dist/lib/gitlab-e2e.manual.test.js +57 -0
  68. package/dist/lib/gitlab-e2e.manual.test.js.map +1 -0
  69. package/dist/lib/gitlab-token.d.ts +17 -0
  70. package/dist/lib/gitlab-token.d.ts.map +1 -0
  71. package/dist/lib/gitlab-token.js +24 -0
  72. package/dist/lib/gitlab-token.js.map +1 -0
  73. package/dist/lib/gitlab-token.test.d.ts +2 -0
  74. package/dist/lib/gitlab-token.test.d.ts.map +1 -0
  75. package/dist/lib/gitlab-token.test.js +38 -0
  76. package/dist/lib/gitlab-token.test.js.map +1 -0
  77. package/dist/lib/oauth.test.js +0 -1
  78. package/dist/lib/oauth.test.js.map +1 -1
  79. package/dist/lib/pr-postback.d.ts +42 -0
  80. package/dist/lib/pr-postback.d.ts.map +1 -0
  81. package/dist/lib/pr-postback.js +44 -0
  82. package/dist/lib/pr-postback.js.map +1 -0
  83. package/dist/lib/pr-postback.test.d.ts +9 -0
  84. package/dist/lib/pr-postback.test.d.ts.map +1 -0
  85. package/dist/lib/pr-postback.test.js +196 -0
  86. package/dist/lib/pr-postback.test.js.map +1 -0
  87. package/dist/lib/pr-token.d.ts +15 -0
  88. package/dist/lib/pr-token.d.ts.map +1 -0
  89. package/dist/lib/pr-token.js +22 -0
  90. package/dist/lib/pr-token.js.map +1 -0
  91. package/dist/lib/pr-token.test.d.ts +2 -0
  92. package/dist/lib/pr-token.test.d.ts.map +1 -0
  93. package/dist/lib/pr-token.test.js +25 -0
  94. package/dist/lib/pr-token.test.js.map +1 -0
  95. package/package.json +8 -7
@@ -0,0 +1,278 @@
1
+ /**
2
+ * R-GITLAB / R-LEAK-PUBLISH: the `--mr` post-back routes through the REAL
3
+ * GitLabForgeAdapter folding over the CLI's own GitLabClientPort. We mock ONLY
4
+ * `fetch` (the network seam), so this exercises the genuine adapter
5
+ * find→delete→repost idempotency AND the genuine CLI port REST calls — a
6
+ * faithful simulation against a mocked GitLab.
7
+ */
8
+ import { GitLabForgeAdapter } from 'ghagga-forge';
9
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
10
+ import { createCliGitLabClientPort, resolveGitLabApiBase, resolveGitLabProjectId, } from './cli-gitlab-client-port.js';
11
+ import { postSummaryComment } from './pr-postback.js';
12
+ const mockFetch = vi.fn();
13
+ vi.stubGlobal('fetch', mockFetch);
14
+ function jsonResponse(status, body) {
15
+ return {
16
+ ok: status >= 200 && status < 300,
17
+ status,
18
+ statusText: status === 200 ? 'OK' : status === 201 ? 'Created' : 'ERR',
19
+ text: () => Promise.resolve(JSON.stringify(body)),
20
+ json: () => Promise.resolve(body),
21
+ };
22
+ }
23
+ const MARKER = { html: '<!-- ghagga-review -->' };
24
+ const PROJECT_ID = '12345';
25
+ const REF = {
26
+ repo: { kind: 'gitlab', nativeId: PROJECT_ID, path: 'acme/widgets' },
27
+ iid: 7,
28
+ };
29
+ function buildAdapter(token = 'glpat') {
30
+ return new GitLabForgeAdapter({
31
+ client: createCliGitLabClientPort(),
32
+ token,
33
+ projectId: PROJECT_ID,
34
+ });
35
+ }
36
+ describe('resolveGitLabProjectId (numeric project id resolution, R-GITLAB)', () => {
37
+ beforeEach(() => mockFetch.mockReset());
38
+ it('GET /projects/:url-encoded-path → numeric id as string', async () => {
39
+ mockFetch.mockResolvedValueOnce(jsonResponse(200, { id: 12345, path_with_namespace: 'acme/widgets' }));
40
+ const id = await resolveGitLabProjectId('acme/widgets', 'glpat');
41
+ expect(id).toBe('12345');
42
+ const [url] = mockFetch.mock.calls[0];
43
+ // path is URL-encoded (nested-group safe).
44
+ expect(url).toBe('https://gitlab.com/api/v4/projects/acme%2Fwidgets');
45
+ });
46
+ it('URL-encodes nested-group paths', async () => {
47
+ mockFetch.mockResolvedValueOnce(jsonResponse(200, { id: 99 }));
48
+ await resolveGitLabProjectId('group/subgroup/project', 'glpat');
49
+ const [url] = mockFetch.mock.calls[0];
50
+ expect(url).toContain('projects/group%2Fsubgroup%2Fproject');
51
+ });
52
+ it('throws on a non-ok response', async () => {
53
+ mockFetch.mockResolvedValueOnce(jsonResponse(404, { message: '404 Project Not Found' }));
54
+ await expect(resolveGitLabProjectId('nope/nope', 'glpat')).rejects.toThrow(/resolving project id/);
55
+ });
56
+ });
57
+ describe('postSummaryComment via CLI GitLab port + GitLabForgeAdapter', () => {
58
+ beforeEach(() => mockFetch.mockReset());
59
+ it('creates a fresh note when none exists (no stale to delete)', async () => {
60
+ // listMrNotes: empty
61
+ mockFetch.mockResolvedValueOnce(jsonResponse(200, []));
62
+ // createMrNote → { id }
63
+ mockFetch.mockResolvedValueOnce(jsonResponse(201, { id: 9001 }));
64
+ const adapter = buildAdapter();
65
+ const body = `${MARKER.html}\n## body`;
66
+ const result = await postSummaryComment(adapter, REF, body, MARKER);
67
+ expect(result.createdNativeId).toBe(9001);
68
+ expect(result.deletedNativeIds).toEqual([]);
69
+ expect(mockFetch).toHaveBeenCalledTimes(2);
70
+ const [listUrl] = mockFetch.mock.calls[0];
71
+ expect(listUrl).toContain('/projects/12345/merge_requests/7/notes');
72
+ const [postUrl, postInit] = mockFetch.mock.calls[1];
73
+ expect(postUrl).toBe('https://gitlab.com/api/v4/projects/12345/merge_requests/7/notes');
74
+ expect(postInit.method).toBe('POST');
75
+ expect(JSON.parse(postInit.body)).toEqual({ body });
76
+ });
77
+ it('finds stale by marker → deletes ALL → reposts fresh (idempotent upsert)', async () => {
78
+ mockFetch.mockResolvedValueOnce(jsonResponse(200, [
79
+ { id: 100, body: `old ${MARKER.html}` },
80
+ { id: 200, body: 'someone else' },
81
+ { id: 300, body: `newer ${MARKER.html}` },
82
+ ]));
83
+ // delete latest=300, delete stale=100
84
+ mockFetch.mockResolvedValueOnce(jsonResponse(204, {}));
85
+ mockFetch.mockResolvedValueOnce(jsonResponse(204, {}));
86
+ // create fresh
87
+ mockFetch.mockResolvedValueOnce(jsonResponse(201, { id: 400 }));
88
+ const adapter = buildAdapter();
89
+ const result = await postSummaryComment(adapter, REF, 'body', MARKER);
90
+ expect(result.createdNativeId).toBe(400);
91
+ expect(result.deletedNativeIds).toEqual([300, 100]);
92
+ const del1 = mockFetch.mock.calls[1];
93
+ const del2 = mockFetch.mock.calls[2];
94
+ expect(del1[0]).toContain('/notes/300');
95
+ expect(del1[1].method).toBe('DELETE');
96
+ expect(del2[0]).toContain('/notes/100');
97
+ // foreign note 200 never deleted.
98
+ const allUrls = mockFetch.mock.calls.map((c) => c[0]).join('\n');
99
+ expect(allUrls).not.toContain('/notes/200');
100
+ });
101
+ it('sends ONLY PRIVATE-TOKEN (no Bearer) + an AbortSignal (FIX E)', async () => {
102
+ mockFetch.mockResolvedValueOnce(jsonResponse(200, []));
103
+ mockFetch.mockResolvedValueOnce(jsonResponse(201, { id: 1 }));
104
+ const adapter = buildAdapter('secret-pat');
105
+ await postSummaryComment(adapter, REF, 'body', MARKER);
106
+ const [, postInit] = mockFetch.mock.calls[1];
107
+ const headers = postInit.headers;
108
+ expect(headers['PRIVATE-TOKEN']).toBe('secret-pat');
109
+ expect(headers.Authorization).toBeUndefined();
110
+ for (const call of mockFetch.mock.calls) {
111
+ expect(call[1].signal).toBeInstanceOf(AbortSignal);
112
+ }
113
+ });
114
+ it('a 401 with the static PAT is fatal (no retry loop)', async () => {
115
+ mockFetch.mockResolvedValueOnce(jsonResponse(401, { message: '401 Unauthorized' }));
116
+ const adapter = buildAdapter();
117
+ await expect(postSummaryComment(adapter, REF, 'body', MARKER)).rejects.toThrow();
118
+ expect(mockFetch).toHaveBeenCalledTimes(1);
119
+ });
120
+ });
121
+ describe('CLI GitLab port — publishInline partial failure end-to-end (R-LEAK-PUBLISH)', () => {
122
+ beforeEach(() => mockFetch.mockReset());
123
+ it('5 inline notes, 3 fail (500) → posted 2, failed 3, never aborts', async () => {
124
+ // create calls in order: ok, 500, 500, ok, 500
125
+ mockFetch
126
+ .mockResolvedValueOnce(jsonResponse(201, { id: 11 }))
127
+ .mockResolvedValueOnce(jsonResponse(500, { message: 'boom' }))
128
+ .mockResolvedValueOnce(jsonResponse(500, { message: 'boom' }))
129
+ .mockResolvedValueOnce(jsonResponse(201, { id: 14 }))
130
+ .mockResolvedValueOnce(jsonResponse(500, { message: 'boom' }));
131
+ const adapter = buildAdapter();
132
+ const report = await adapter.publishInline(REF, [
133
+ { path: 'a.ts', line: 1, body: 'c0' },
134
+ { path: 'b.ts', line: 2, body: 'c1' },
135
+ { path: 'c.ts', line: 3, body: 'c2' },
136
+ { path: 'd.ts', line: 4, body: 'c3' },
137
+ { path: 'e.ts', line: 5, body: 'c4' },
138
+ ]);
139
+ expect(report.posted).toEqual([
140
+ { kind: 'gitlab', raw: '11' },
141
+ { kind: 'gitlab', raw: '14' },
142
+ ]);
143
+ expect(report.failed.map((f) => f.index)).toEqual([1, 2, 4]);
144
+ expect(mockFetch).toHaveBeenCalledTimes(5);
145
+ });
146
+ });
147
+ describe('resolveGitLabApiBase (FIX A — derive API base from host + env overrides)', () => {
148
+ it('defaults to https://<host>/api/v4 for the parsed remote host', () => {
149
+ expect(resolveGitLabApiBase('gitlab.com', {})).toBe('https://gitlab.com/api/v4');
150
+ expect(resolveGitLabApiBase('gitlab.example.com', {})).toBe('https://gitlab.example.com/api/v4');
151
+ });
152
+ it('GITLAB_HOST overrides the remote host', () => {
153
+ expect(resolveGitLabApiBase('gitlab.com', { GITLAB_HOST: 'api.internal' })).toBe('https://api.internal/api/v4');
154
+ });
155
+ it('GITLAB_API_BASE is a full override (subpath self-hosted)', () => {
156
+ expect(resolveGitLabApiBase('gitlab.com', {
157
+ GITLAB_API_BASE: 'https://example.com/gitlab/api/v4',
158
+ })).toBe('https://example.com/gitlab/api/v4');
159
+ });
160
+ it('GITLAB_API_BASE wins over GITLAB_HOST + trims trailing slash', () => {
161
+ expect(resolveGitLabApiBase('gitlab.com', {
162
+ GITLAB_HOST: 'ignored.example',
163
+ GITLAB_API_BASE: 'https://example.com/gitlab/api/v4/',
164
+ })).toBe('https://example.com/gitlab/api/v4');
165
+ });
166
+ });
167
+ describe('CLI GitLab port — self-hosted API base (FIX A)', () => {
168
+ beforeEach(() => mockFetch.mockReset());
169
+ function buildSelfHostedAdapter() {
170
+ return new GitLabForgeAdapter({
171
+ client: createCliGitLabClientPort('https://gitlab.example.com/api/v4'),
172
+ token: 'glpat',
173
+ projectId: PROJECT_ID,
174
+ });
175
+ }
176
+ it('routes note calls + project-id resolution against the self-hosted host', async () => {
177
+ // resolveGitLabProjectId against the self-hosted base.
178
+ mockFetch.mockResolvedValueOnce(jsonResponse(200, { id: 12345 }));
179
+ const id = await resolveGitLabProjectId('team/repo', 'glpat', 'https://gitlab.example.com/api/v4');
180
+ expect(id).toBe('12345');
181
+ const [idUrl] = mockFetch.mock.calls[0];
182
+ expect(idUrl).toBe('https://gitlab.example.com/api/v4/projects/team%2Frepo');
183
+ mockFetch.mockReset();
184
+ // upsert against the self-hosted base.
185
+ mockFetch.mockResolvedValueOnce(jsonResponse(200, []));
186
+ mockFetch.mockResolvedValueOnce(jsonResponse(201, { id: 9001 }));
187
+ const adapter = buildSelfHostedAdapter();
188
+ await postSummaryComment(adapter, REF, 'body', MARKER);
189
+ const [postUrl, postInit] = mockFetch.mock.calls[1];
190
+ expect(postUrl).toBe('https://gitlab.example.com/api/v4/projects/12345/merge_requests/7/notes');
191
+ // PRIVATE-TOKEN only.
192
+ const headers = postInit.headers;
193
+ expect(headers['PRIVATE-TOKEN']).toBe('glpat');
194
+ expect(headers.Authorization).toBeUndefined();
195
+ });
196
+ });
197
+ describe('CLI GitLab port — listMrNotes pagination (backlog #6)', () => {
198
+ beforeEach(() => mockFetch.mockReset());
199
+ function makeNotes(count, startId = 0, markerAt) {
200
+ return Array.from({ length: count }, (_, i) => ({
201
+ id: startId + i,
202
+ body: i === markerAt ? `summary ${startId + i} ${MARKER.html}` : `noise ${startId + i}`,
203
+ }));
204
+ }
205
+ it('single page (< 100 notes) → exactly one list call, no extra fetch', async () => {
206
+ mockFetch.mockResolvedValueOnce(jsonResponse(200, makeNotes(20, 0)));
207
+ const port = createCliGitLabClientPort();
208
+ const notes = await port.listMrNotes('12345', 7, 'glpat');
209
+ expect(notes).toHaveLength(20);
210
+ expect(mockFetch).toHaveBeenCalledTimes(1);
211
+ const [url] = mockFetch.mock.calls[0];
212
+ expect(url).toContain('page=1');
213
+ });
214
+ it('multi-page → full page 1 forces page 2 fetch; stale-on-page-2 found + deleted (no duplicate)', async () => {
215
+ // page 1: 100 notes, NO marker → must fetch page 2; page 2 carries the stale marker.
216
+ mockFetch.mockResolvedValueOnce(jsonResponse(200, makeNotes(100, 0)));
217
+ mockFetch.mockResolvedValueOnce(jsonResponse(200, makeNotes(15, 100, 5))); // marker id 105
218
+ // adapter then deletes the found stale (id 105) and reposts fresh.
219
+ mockFetch.mockResolvedValueOnce(jsonResponse(204, {})); // delete 105
220
+ mockFetch.mockResolvedValueOnce(jsonResponse(201, { id: 999 })); // repost
221
+ const adapter = buildAdapter();
222
+ const result = await postSummaryComment(adapter, REF, 'body', MARKER);
223
+ // Stale on page 2 WAS found and deleted — not duplicated.
224
+ expect(result.deletedNativeIds).toEqual([105]);
225
+ expect(result.createdNativeId).toBe(999);
226
+ // page 1 + page 2 list calls happened.
227
+ expect(mockFetch.mock.calls[0][0]).toContain('page=1');
228
+ expect(mockFetch.mock.calls[1][0]).toContain('page=2');
229
+ const delCall = mockFetch.mock.calls[2];
230
+ expect(delCall[0]).toContain('/notes/105');
231
+ expect(delCall[1].method).toBe('DELETE');
232
+ });
233
+ it('safety bound → stops at MAX_PAGES (50) and warns instead of looping forever', async () => {
234
+ for (let page = 0; page < 50; page++) {
235
+ mockFetch.mockResolvedValueOnce(jsonResponse(200, makeNotes(100, page * 100)));
236
+ }
237
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
238
+ const port = createCliGitLabClientPort();
239
+ const notes = await port.listMrNotes('12345', 7, 'glpat');
240
+ expect(notes).toHaveLength(5000);
241
+ expect(mockFetch).toHaveBeenCalledTimes(50);
242
+ expect(mockFetch.mock.calls[49][0]).toContain('page=50');
243
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('MAX_PAGES (50)'));
244
+ warnSpy.mockRestore();
245
+ });
246
+ });
247
+ describe('CLI GitLab port — createMrDiscussion (FIX C true positioning)', () => {
248
+ beforeEach(() => mockFetch.mockReset());
249
+ it('posts a positioned inline comment to /discussions and boxes notes[0].id', async () => {
250
+ mockFetch.mockResolvedValueOnce(jsonResponse(201, { id: 'disc-1', notes: [{ id: 555 }] }));
251
+ const adapter = buildAdapter();
252
+ const report = await adapter.publishInline(REF, [
253
+ {
254
+ path: 'src/x.ts',
255
+ line: 4,
256
+ side: 'new',
257
+ body: 'anchored',
258
+ position: { baseSha: 'B', headSha: 'H', startSha: 'S', newLine: 4 },
259
+ },
260
+ ]);
261
+ expect(report.posted).toEqual([{ kind: 'gitlab', raw: '555' }]);
262
+ const [discUrl, discInit] = mockFetch.mock.calls[0];
263
+ expect(discUrl).toBe('https://gitlab.com/api/v4/projects/12345/merge_requests/7/discussions');
264
+ expect(discInit.method).toBe('POST');
265
+ const payload = JSON.parse(discInit.body);
266
+ expect(payload.body).toBe('anchored');
267
+ expect(payload.position).toEqual({
268
+ position_type: 'text',
269
+ base_sha: 'B',
270
+ head_sha: 'H',
271
+ start_sha: 'S',
272
+ old_path: 'src/x.ts',
273
+ new_path: 'src/x.ts',
274
+ new_line: 4,
275
+ });
276
+ });
277
+ });
278
+ //# sourceMappingURL=cli-gitlab-client-port.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-gitlab-client-port.test.js","sourceRoot":"","sources":["../../src/lib/cli-gitlab-client-port.test.ts"],"names":[],"mappings":"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"}
@@ -9,6 +9,12 @@ export interface GhaggaCliConfig {
9
9
  githubToken?: string;
10
10
  /** GitHub username (fetched after login) */
11
11
  githubLogin?: string;
12
+ /**
13
+ * GitLab Personal/Project/Group Access Token (PAT). SEPARATE from the GitHub
14
+ * token: a GitHub token MUST NOT be used against GitLab. Resolved by the `--mr`
15
+ * post-back AFTER env GITLAB_TOKEN / GL_TOKEN.
16
+ */
17
+ gitlabToken?: string;
12
18
  /** Default LLM provider */
13
19
  defaultProvider?: string;
14
20
  /** Default model for the provider */
@@ -35,6 +41,12 @@ export declare function isLoggedIn(): boolean;
35
41
  * Get the stored GitHub token, or null if not logged in.
36
42
  */
37
43
  export declare function getStoredToken(): string | null;
44
+ /**
45
+ * Get the stored GitLab token, or null if none. SEPARATE from
46
+ * {@link getStoredToken} (GitHub): the `--mr` post-back must never authenticate
47
+ * to GitLab with a GitHub token.
48
+ */
49
+ export declare function getStoredGitLabToken(): string | null;
38
50
  /**
39
51
  * Get the config file path (for display in status/error messages).
40
52
  */
@@ -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;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,CAYxD;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"}
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;;;;OAIG;IACH,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,CAYxD;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;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,GAAG,IAAI,CAGpD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C"}
@@ -67,6 +67,15 @@ export function getStoredToken() {
67
67
  const config = loadConfig();
68
68
  return config.githubToken ?? null;
69
69
  }
70
+ /**
71
+ * Get the stored GitLab token, or null if none. SEPARATE from
72
+ * {@link getStoredToken} (GitHub): the `--mr` post-back must never authenticate
73
+ * to GitLab with a GitHub token.
74
+ */
75
+ export function getStoredGitLabToken() {
76
+ const config = loadConfig();
77
+ return config.gitlabToken ?? null;
78
+ }
70
79
  /**
71
80
  * Get the config file path (for display in status/error messages).
72
81
  */
@@ -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,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAkBjC,mEAAmE;AAEnE,MAAM,UAAU,YAAY;IAC1B,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;IAC9C,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,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACzD,CAAC;IAED,aAAa,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;QAChE,QAAQ,EAAE,OAAO;QACjB,IAAI,EAAE,KAAK;KACZ,CAAC,CAAC;AACL,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"}
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,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAyBjC,mEAAmE;AAEnE,MAAM,UAAU,YAAY;IAC1B,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;IAC9C,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,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACzD,CAAC;IAED,aAAa,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;QAChE,QAAQ,EAAE,OAAO;QACjB,IAAI,EAAE,KAAK;KACZ,CAAC,CAAC;AACL,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;;;;GAIG;AACH,MAAM,UAAU,oBAAoB;IAClC,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,72 @@
1
+ /**
2
+ * Shared forge-NEUTRAL composition helper for the CLI change-request post-back
3
+ * (`ghagga review --pr N` GitHub / `--mr N` GitLab) — resolves
4
+ * BL-CLI-FORGE-COMPOSITION.
5
+ *
6
+ * The P3 `--pr` path and the P4 `--mr` path are the SAME pipeline:
7
+ * resolve-token → parse-remote → build-adapter → make-ref → postSummaryComment
8
+ * The ONLY differences are forge-specific: which token env vars to read, how to
9
+ * parse the remote into a `RepoRef`, and which adapter to construct. This module
10
+ * captures the SHARED pipeline ONCE — the forge-specific steps are injected via a
11
+ * {@link ForgeCompositionBuilder} so BOTH commands route through one path instead
12
+ * of each hand-rolling the glue. Adding a third forge (Gitea) is a new builder,
13
+ * not a new branch.
14
+ *
15
+ * The actual idempotent upsert (find stale → delete → repost) lives inside the
16
+ * adapter + the forge-neutral {@link postSummaryComment}; this helper only
17
+ * orchestrates token-resolution → composition-build → post.
18
+ */
19
+ import type { ChangeRequestRef, CommentMarker, ForgeAdapterBase } from 'ghagga-forge';
20
+ import { type PostBackResult } from './pr-postback.js';
21
+ /** The forge-specific pieces a post-back composition resolves to. */
22
+ export interface ForgeComposition {
23
+ /** The forge adapter (built over its CLI client port + the resolved token). */
24
+ adapter: ForgeAdapterBase;
25
+ /** The canonical change-request ref (PR/MR) with forge-native identity. */
26
+ ref: ChangeRequestRef;
27
+ }
28
+ /**
29
+ * Builds the forge-specific composition (adapter + ref) from a resolved token +
30
+ * the change-request number. ASYNC because some forges need a pre-flight REST
31
+ * call (GitLab resolves the numeric project id from the remote path).
32
+ */
33
+ export type ForgeCompositionBuilder = (token: string, changeRequestNumber: number) => Promise<ForgeComposition>;
34
+ /**
35
+ * Why a forge post-back could not run, distinct from a thrown post error.
36
+ *
37
+ * `missing-token` is surfaced separately so the caller can print the
38
+ * forge-appropriate "set X / run ghagga login" guidance and apply the
39
+ * blocking/soft-fail exit policy uniformly.
40
+ */
41
+ export type ForgePostbackOutcome = {
42
+ kind: 'posted';
43
+ result: PostBackResult;
44
+ } | {
45
+ kind: 'missing-token';
46
+ };
47
+ /** Inputs for {@link composeForgePostback}. */
48
+ export interface ForgePostbackInput {
49
+ /** The change-request number (PR/MR). */
50
+ changeRequestNumber: number;
51
+ /** Resolve the forge token (env-first, stored fallback). null ⇒ missing. */
52
+ resolveToken: () => string | null;
53
+ /** Build the forge-specific adapter + ref from the resolved token. */
54
+ buildComposition: ForgeCompositionBuilder;
55
+ /** The pre-rendered comment body (parity with the server post-back). */
56
+ body: string;
57
+ /** The comment marker identifying GHAGGA-owned comments. */
58
+ marker: CommentMarker;
59
+ }
60
+ /**
61
+ * Run the SHARED post-back pipeline for one forge.
62
+ *
63
+ * 1. resolve the token (forge-specific resolver). Missing ⇒ `missing-token`.
64
+ * 2. build the forge composition (adapter + ref) — async (GitLab project-id).
65
+ * 3. upsert the summary comment via the forge-neutral {@link postSummaryComment}.
66
+ *
67
+ * Throws on a post/build failure (the caller owns the blocking vs soft-fail exit
68
+ * policy). A missing token is returned as data, NOT thrown, so the caller prints
69
+ * forge-appropriate guidance.
70
+ */
71
+ export declare function composeForgePostback(input: ForgePostbackInput): Promise<ForgePostbackOutcome>;
72
+ //# sourceMappingURL=forge-postback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forge-postback.d.ts","sourceRoot":"","sources":["../../src/lib/forge-postback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACtF,OAAO,EAAE,KAAK,cAAc,EAAsB,MAAM,kBAAkB,CAAC;AAE3E,qEAAqE;AACrE,MAAM,WAAW,gBAAgB;IAC/B,+EAA+E;IAC/E,OAAO,EAAE,gBAAgB,CAAC;IAC1B,2EAA2E;IAC3E,GAAG,EAAE,gBAAgB,CAAC;CACvB;AAED;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG,CACpC,KAAK,EAAE,MAAM,EACb,mBAAmB,EAAE,MAAM,KACxB,OAAO,CAAC,gBAAgB,CAAC,CAAC;AAE/B;;;;;;GAMG;AACH,MAAM,MAAM,oBAAoB,GAC5B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,cAAc,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE,CAAC;AAE9B,+CAA+C;AAC/C,MAAM,WAAW,kBAAkB;IACjC,yCAAyC;IACzC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,4EAA4E;IAC5E,YAAY,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IAClC,sEAAsE;IACtE,gBAAgB,EAAE,uBAAuB,CAAC;IAC1C,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,MAAM,EAAE,aAAa,CAAC;CACvB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,kBAAkB,GACxB,OAAO,CAAC,oBAAoB,CAAC,CAY/B"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Shared forge-NEUTRAL composition helper for the CLI change-request post-back
3
+ * (`ghagga review --pr N` GitHub / `--mr N` GitLab) — resolves
4
+ * BL-CLI-FORGE-COMPOSITION.
5
+ *
6
+ * The P3 `--pr` path and the P4 `--mr` path are the SAME pipeline:
7
+ * resolve-token → parse-remote → build-adapter → make-ref → postSummaryComment
8
+ * The ONLY differences are forge-specific: which token env vars to read, how to
9
+ * parse the remote into a `RepoRef`, and which adapter to construct. This module
10
+ * captures the SHARED pipeline ONCE — the forge-specific steps are injected via a
11
+ * {@link ForgeCompositionBuilder} so BOTH commands route through one path instead
12
+ * of each hand-rolling the glue. Adding a third forge (Gitea) is a new builder,
13
+ * not a new branch.
14
+ *
15
+ * The actual idempotent upsert (find stale → delete → repost) lives inside the
16
+ * adapter + the forge-neutral {@link postSummaryComment}; this helper only
17
+ * orchestrates token-resolution → composition-build → post.
18
+ */
19
+ import { postSummaryComment } from './pr-postback.js';
20
+ /**
21
+ * Run the SHARED post-back pipeline for one forge.
22
+ *
23
+ * 1. resolve the token (forge-specific resolver). Missing ⇒ `missing-token`.
24
+ * 2. build the forge composition (adapter + ref) — async (GitLab project-id).
25
+ * 3. upsert the summary comment via the forge-neutral {@link postSummaryComment}.
26
+ *
27
+ * Throws on a post/build failure (the caller owns the blocking vs soft-fail exit
28
+ * policy). A missing token is returned as data, NOT thrown, so the caller prints
29
+ * forge-appropriate guidance.
30
+ */
31
+ export async function composeForgePostback(input) {
32
+ const token = input.resolveToken();
33
+ if (!token)
34
+ return { kind: 'missing-token' };
35
+ const composition = await input.buildComposition(token, input.changeRequestNumber);
36
+ const result = await postSummaryComment(composition.adapter, composition.ref, input.body, input.marker);
37
+ return { kind: 'posted', result };
38
+ }
39
+ //# sourceMappingURL=forge-postback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forge-postback.js","sourceRoot":"","sources":["../../src/lib/forge-postback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,EAAuB,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AA6C3E;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,KAAyB;IAEzB,MAAM,KAAK,GAAG,KAAK,CAAC,YAAY,EAAE,CAAC;IACnC,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;IAE7C,MAAM,WAAW,GAAG,MAAM,KAAK,CAAC,gBAAgB,CAAC,KAAK,EAAE,KAAK,CAAC,mBAAmB,CAAC,CAAC;IACnF,MAAM,MAAM,GAAG,MAAM,kBAAkB,CACrC,WAAW,CAAC,OAAO,EACnB,WAAW,CAAC,GAAG,EACf,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,MAAM,CACb,CAAC;IACF,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;AACpC,CAAC"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Minimal GitLab REST helpers for the CLI MR summary post-back (`ghagga review
3
+ * --mr N`).
4
+ *
5
+ * Scope (P4): parse the GitLab remote → group/project path, and the GitLab API
6
+ * error type. The fetch-backed note client lives in `cli-gitlab-client-port.ts`;
7
+ * this module owns the remote-URL parsing + the shared error class, mirroring
8
+ * `github-api.ts`'s `parseGitHubRemote` / `GitHubApiError`.
9
+ */
10
+ /** Thrown by the CLI GitLab fetch calls; carries the HTTP status + raw body. */
11
+ export declare class GitLabApiError extends Error {
12
+ readonly status: number;
13
+ readonly body: string;
14
+ constructor(message: string, status: number, body: string);
15
+ }
16
+ /** A parsed GitLab remote: the host (for the API base) + the project path. */
17
+ export interface ParsedGitLabRemote {
18
+ /**
19
+ * The GitLab HOST (e.g. `gitlab.com` or `gitlab.example.com`). Drives the API
20
+ * base (`https://<host>/api/v4`). For SSH remotes that carry a non-default port
21
+ * (`ssh://git@host:2222/...`) the port is STRIPPED — the API is HTTPS on the
22
+ * web host, not the SSH port.
23
+ */
24
+ host: string;
25
+ /**
26
+ * The MUTABLE group/project path (e.g. `acme/widgets` or
27
+ * `group/subgroup/project`). Used to RESOLVE the canonical numeric project id
28
+ * (via `GET /projects/:url-encoded-path`), never as the identity key itself
29
+ * (R-GITLAB).
30
+ */
31
+ projectPath: string;
32
+ }
33
+ /**
34
+ * Parse the host + group/project path from a GitLab git remote URL.
35
+ *
36
+ * Supports HTTPS, SSH (git@), and ssh:// protocol formats against ANY GitLab
37
+ * host (gitlab.com OR a self-managed instance like `gitlab.example.com`). GitLab
38
+ * supports NESTED groups (e.g. `group/subgroup/project`), so the path segment is
39
+ * captured greedily (everything after the host, minus a trailing `.git`).
40
+ *
41
+ * DISAMBIGUATION: a generic git host cannot be told apart from GitHub by URL
42
+ * shape alone. The `--mr` flag is the EXPLICIT signal that the remote is GitLab;
43
+ * this parser is only reached on that path, so it accepts any host. It still
44
+ * rejects the well-known `github.com` host to catch an obvious `--mr` misuse on a
45
+ * GitHub remote early with a clear error.
46
+ *
47
+ * @throws if the remote is not a recognizable git remote URL, or is github.com.
48
+ */
49
+ export declare function parseGitLabRemote(remoteUrl: string): ParsedGitLabRemote;
50
+ //# sourceMappingURL=gitlab-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitlab-api.d.ts","sourceRoot":"","sources":["../../src/lib/gitlab-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,gFAAgF;AAChF,qBAAa,cAAe,SAAQ,KAAK;aAGrB,MAAM,EAAE,MAAM;aACd,IAAI,EAAE,MAAM;gBAF5B,OAAO,EAAE,MAAM,EACC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM;CAK/B;AAID,8EAA8E;AAC9E,MAAM,WAAW,kBAAkB;IACjC;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,kBAAkB,CAuBvE"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Minimal GitLab REST helpers for the CLI MR summary post-back (`ghagga review
3
+ * --mr N`).
4
+ *
5
+ * Scope (P4): parse the GitLab remote → group/project path, and the GitLab API
6
+ * error type. The fetch-backed note client lives in `cli-gitlab-client-port.ts`;
7
+ * this module owns the remote-URL parsing + the shared error class, mirroring
8
+ * `github-api.ts`'s `parseGitHubRemote` / `GitHubApiError`.
9
+ */
10
+ // ─── Error ──────────────────────────────────────────────────────
11
+ /** Thrown by the CLI GitLab fetch calls; carries the HTTP status + raw body. */
12
+ export class GitLabApiError extends Error {
13
+ status;
14
+ body;
15
+ constructor(message, status, body) {
16
+ super(message);
17
+ this.status = status;
18
+ this.body = body;
19
+ this.name = 'GitLabApiError';
20
+ }
21
+ }
22
+ /**
23
+ * Parse the host + group/project path from a GitLab git remote URL.
24
+ *
25
+ * Supports HTTPS, SSH (git@), and ssh:// protocol formats against ANY GitLab
26
+ * host (gitlab.com OR a self-managed instance like `gitlab.example.com`). GitLab
27
+ * supports NESTED groups (e.g. `group/subgroup/project`), so the path segment is
28
+ * captured greedily (everything after the host, minus a trailing `.git`).
29
+ *
30
+ * DISAMBIGUATION: a generic git host cannot be told apart from GitHub by URL
31
+ * shape alone. The `--mr` flag is the EXPLICIT signal that the remote is GitLab;
32
+ * this parser is only reached on that path, so it accepts any host. It still
33
+ * rejects the well-known `github.com` host to catch an obvious `--mr` misuse on a
34
+ * GitHub remote early with a clear error.
35
+ *
36
+ * @throws if the remote is not a recognizable git remote URL, or is github.com.
37
+ */
38
+ export function parseGitLabRemote(remoteUrl) {
39
+ const trimmed = remoteUrl.trim();
40
+ // HTTPS: https://<host>/group/subgroup/repo.git
41
+ const httpsMatch = trimmed.match(/^https?:\/\/([^/]+)\/(.+?)(?:\.git)?$/);
42
+ if (httpsMatch?.[1] && httpsMatch[2]) {
43
+ return finalize(stripUserInfo(httpsMatch[1]), httpsMatch[2], trimmed);
44
+ }
45
+ // SSH protocol: ssh://git@<host>[:port]/group/subgroup/repo.git
46
+ // (checked BEFORE the scp form so the `:port` is not mis-parsed as the path).
47
+ const sshProtoMatch = trimmed.match(/^ssh:\/\/[^@\s]+@([^/]+)\/(.+?)(?:\.git)?$/);
48
+ if (sshProtoMatch?.[1] && sshProtoMatch[2]) {
49
+ return finalize(stripPort(sshProtoMatch[1]), sshProtoMatch[2], trimmed);
50
+ }
51
+ // SSH (scp-style): git@<host>:group/subgroup/repo.git — no `://` scheme.
52
+ const sshMatch = trimmed.match(/^[^@\s:]+@([^:/]+):(.+?)(?:\.git)?$/);
53
+ if (sshMatch?.[1] && sshMatch[2]) {
54
+ return finalize(sshMatch[1], sshMatch[2], trimmed);
55
+ }
56
+ throw new Error(`Not a GitLab remote URL: "${trimmed}"`);
57
+ }
58
+ /** Drop a `user:pass@` / `user@` prefix that may ride on an HTTPS host. */
59
+ function stripUserInfo(host) {
60
+ const at = host.lastIndexOf('@');
61
+ return at === -1 ? host : host.slice(at + 1);
62
+ }
63
+ /** Drop a trailing `:port` from a host (the API rides on HTTPS, not the SSH port). */
64
+ function stripPort(host) {
65
+ return host.replace(/:\d+$/, '');
66
+ }
67
+ /** Reject github.com (obvious --mr misuse) and assemble the parsed remote. */
68
+ function finalize(host, projectPath, original) {
69
+ const cleanHost = stripPort(host);
70
+ if (cleanHost.toLowerCase() === 'github.com') {
71
+ throw new Error(`Not a GitLab remote URL (looks like GitHub): "${original}"`);
72
+ }
73
+ return { host: cleanHost, projectPath };
74
+ }
75
+ //# sourceMappingURL=gitlab-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitlab-api.js","sourceRoot":"","sources":["../../src/lib/gitlab-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,mEAAmE;AAEnE,gFAAgF;AAChF,MAAM,OAAO,cAAe,SAAQ,KAAK;IAGrB;IACA;IAHlB,YACE,OAAe,EACC,MAAc,EACd,IAAY;QAE5B,KAAK,CAAC,OAAO,CAAC,CAAC;QAHC,WAAM,GAAN,MAAM,CAAQ;QACd,SAAI,GAAJ,IAAI,CAAQ;QAG5B,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAsBD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAAiB;IACjD,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC;IAEjC,gDAAgD;IAChD,MAAM,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC1E,IAAI,UAAU,EAAE,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;QACrC,OAAO,QAAQ,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IACxE,CAAC;IAED,gEAAgE;IAChE,8EAA8E;IAC9E,MAAM,aAAa,GAAG,OAAO,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAClF,IAAI,aAAa,EAAE,CAAC,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3C,OAAO,QAAQ,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IAC1E,CAAC;IAED,yEAAyE;IACzE,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;IACtE,IAAI,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;QACjC,OAAO,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IACrD,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,6BAA6B,OAAO,GAAG,CAAC,CAAC;AAC3D,CAAC;AAED,2EAA2E;AAC3E,SAAS,aAAa,CAAC,IAAY;IACjC,MAAM,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACjC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED,sFAAsF;AACtF,SAAS,SAAS,CAAC,IAAY;IAC7B,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AACnC,CAAC;AAED,8EAA8E;AAC9E,SAAS,QAAQ,CAAC,IAAY,EAAE,WAAmB,EAAE,QAAgB;IACnE,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,SAAS,CAAC,WAAW,EAAE,KAAK,YAAY,EAAE,CAAC;QAC7C,MAAM,IAAI,KAAK,CAAC,iDAAiD,QAAQ,GAAG,CAAC,CAAC;IAChF,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=gitlab-api.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitlab-api.test.d.ts","sourceRoot":"","sources":["../../src/lib/gitlab-api.test.ts"],"names":[],"mappings":""}