ghagga 3.0.0 → 3.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/dist/commands/login.test.js +0 -2
- package/dist/commands/login.test.js.map +1 -1
- package/dist/commands/memory/backfill.d.ts +13 -0
- package/dist/commands/memory/backfill.d.ts.map +1 -0
- package/dist/commands/memory/backfill.js +47 -0
- package/dist/commands/memory/backfill.js.map +1 -0
- package/dist/commands/memory/backfill.test.d.ts +11 -0
- package/dist/commands/memory/backfill.test.d.ts.map +1 -0
- package/dist/commands/memory/backfill.test.js +127 -0
- package/dist/commands/memory/backfill.test.js.map +1 -0
- package/dist/commands/memory/clear.test.js +9 -0
- package/dist/commands/memory/clear.test.js.map +1 -1
- package/dist/commands/memory/delete.test.js +9 -0
- package/dist/commands/memory/delete.test.js.map +1 -1
- package/dist/commands/memory/index.d.ts +3 -3
- package/dist/commands/memory/index.d.ts.map +1 -1
- package/dist/commands/memory/index.js +5 -3
- package/dist/commands/memory/index.js.map +1 -1
- package/dist/commands/memory/list.test.js +9 -0
- package/dist/commands/memory/list.test.js.map +1 -1
- package/dist/commands/memory/search.test.js +9 -0
- package/dist/commands/memory/search.test.js.map +1 -1
- package/dist/commands/memory/show.test.js +9 -0
- package/dist/commands/memory/show.test.js.map +1 -1
- package/dist/commands/memory/stats.test.js +9 -0
- package/dist/commands/memory/stats.test.js.map +1 -1
- package/dist/commands/memory/utils.d.ts.map +1 -1
- package/dist/commands/memory/utils.js +14 -1
- package/dist/commands/memory/utils.js.map +1 -1
- package/dist/commands/review-output.test.js +37 -0
- package/dist/commands/review-output.test.js.map +1 -1
- package/dist/commands/review.d.ts +12 -0
- package/dist/commands/review.d.ts.map +1 -1
- package/dist/commands/review.js +239 -5
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/review.mr-exit.test.d.ts +13 -0
- package/dist/commands/review.mr-exit.test.d.ts.map +1 -0
- package/dist/commands/review.mr-exit.test.js +190 -0
- package/dist/commands/review.mr-exit.test.js.map +1 -0
- package/dist/commands/review.postback-disclosure.test.d.ts +22 -0
- package/dist/commands/review.postback-disclosure.test.d.ts.map +1 -0
- package/dist/commands/review.postback-disclosure.test.js +192 -0
- package/dist/commands/review.postback-disclosure.test.js.map +1 -0
- package/dist/commands/review.pr-exit.test.d.ts +13 -0
- package/dist/commands/review.pr-exit.test.d.ts.map +1 -0
- package/dist/commands/review.pr-exit.test.js +162 -0
- package/dist/commands/review.pr-exit.test.js.map +1 -0
- package/dist/commands/review.pr-jenkins.test.d.ts +16 -0
- package/dist/commands/review.pr-jenkins.test.d.ts.map +1 -0
- package/dist/commands/review.pr-jenkins.test.js +106 -0
- package/dist/commands/review.pr-jenkins.test.js.map +1 -0
- package/dist/commands/review.pr-nativeid-fallback.test.d.ts +15 -0
- package/dist/commands/review.pr-nativeid-fallback.test.d.ts.map +1 -0
- package/dist/commands/review.pr-nativeid-fallback.test.js +151 -0
- package/dist/commands/review.pr-nativeid-fallback.test.js.map +1 -0
- package/dist/commands/review.test.js +13 -0
- package/dist/commands/review.test.js.map +1 -1
- package/dist/index.js +39 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/cli-github-client-port.d.ts +52 -0
- package/dist/lib/cli-github-client-port.d.ts.map +1 -0
- package/dist/lib/cli-github-client-port.js +199 -0
- package/dist/lib/cli-github-client-port.js.map +1 -0
- package/dist/lib/cli-github-client-port.test.d.ts +10 -0
- package/dist/lib/cli-github-client-port.test.d.ts.map +1 -0
- package/dist/lib/cli-github-client-port.test.js +47 -0
- package/dist/lib/cli-github-client-port.test.js.map +1 -0
- package/dist/lib/cli-gitlab-client-port.d.ts +62 -0
- package/dist/lib/cli-gitlab-client-port.d.ts.map +1 -0
- package/dist/lib/cli-gitlab-client-port.js +209 -0
- package/dist/lib/cli-gitlab-client-port.js.map +1 -0
- package/dist/lib/cli-gitlab-client-port.test.d.ts +9 -0
- package/dist/lib/cli-gitlab-client-port.test.d.ts.map +1 -0
- package/dist/lib/cli-gitlab-client-port.test.js +292 -0
- package/dist/lib/cli-gitlab-client-port.test.js.map +1 -0
- package/dist/lib/config.d.ts +23 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +9 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/embedding.d.ts +23 -0
- package/dist/lib/embedding.d.ts.map +1 -0
- package/dist/lib/embedding.js +39 -0
- package/dist/lib/embedding.js.map +1 -0
- package/dist/lib/embedding.test.d.ts +8 -0
- package/dist/lib/embedding.test.d.ts.map +1 -0
- package/dist/lib/embedding.test.js +101 -0
- package/dist/lib/embedding.test.js.map +1 -0
- package/dist/lib/forge-postback.d.ts +72 -0
- package/dist/lib/forge-postback.d.ts.map +1 -0
- package/dist/lib/forge-postback.js +39 -0
- package/dist/lib/forge-postback.js.map +1 -0
- package/dist/lib/gitlab-api.d.ts +50 -0
- package/dist/lib/gitlab-api.d.ts.map +1 -0
- package/dist/lib/gitlab-api.js +75 -0
- package/dist/lib/gitlab-api.js.map +1 -0
- package/dist/lib/gitlab-api.test.d.ts +2 -0
- package/dist/lib/gitlab-api.test.d.ts.map +1 -0
- package/dist/lib/gitlab-api.test.js +79 -0
- package/dist/lib/gitlab-api.test.js.map +1 -0
- package/dist/lib/gitlab-e2e.manual.test.d.ts +57 -0
- package/dist/lib/gitlab-e2e.manual.test.d.ts.map +1 -0
- package/dist/lib/gitlab-e2e.manual.test.js +135 -0
- package/dist/lib/gitlab-e2e.manual.test.js.map +1 -0
- package/dist/lib/gitlab-token.d.ts +17 -0
- package/dist/lib/gitlab-token.d.ts.map +1 -0
- package/dist/lib/gitlab-token.js +24 -0
- package/dist/lib/gitlab-token.js.map +1 -0
- package/dist/lib/gitlab-token.test.d.ts +2 -0
- package/dist/lib/gitlab-token.test.d.ts.map +1 -0
- package/dist/lib/gitlab-token.test.js +38 -0
- package/dist/lib/gitlab-token.test.js.map +1 -0
- package/dist/lib/oauth.test.js +0 -1
- package/dist/lib/oauth.test.js.map +1 -1
- package/dist/lib/pr-postback.d.ts +42 -0
- package/dist/lib/pr-postback.d.ts.map +1 -0
- package/dist/lib/pr-postback.js +44 -0
- package/dist/lib/pr-postback.js.map +1 -0
- package/dist/lib/pr-postback.test.d.ts +9 -0
- package/dist/lib/pr-postback.test.d.ts.map +1 -0
- package/dist/lib/pr-postback.test.js +212 -0
- package/dist/lib/pr-postback.test.js.map +1 -0
- package/dist/lib/pr-token.d.ts +15 -0
- package/dist/lib/pr-token.d.ts.map +1 -0
- package/dist/lib/pr-token.js +22 -0
- package/dist/lib/pr-token.js.map +1 -0
- package/dist/lib/pr-token.test.d.ts +2 -0
- package/dist/lib/pr-token.test.d.ts.map +1 -0
- package/dist/lib/pr-token.test.js +25 -0
- package/dist/lib/pr-token.test.js.map +1 -0
- package/package.json +9 -8
|
@@ -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 @@
|
|
|
1
|
+
{"version":3,"file":"gitlab-api.test.d.ts","sourceRoot":"","sources":["../../src/lib/gitlab-api.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { GitLabApiError, parseGitLabRemote } from './gitlab-api.js';
|
|
3
|
+
describe('parseGitLabRemote (host + projectPath, any GitLab host)', () => {
|
|
4
|
+
it('parses an HTTPS gitlab.com remote', () => {
|
|
5
|
+
expect(parseGitLabRemote('https://gitlab.com/acme/widgets.git')).toEqual({
|
|
6
|
+
host: 'gitlab.com',
|
|
7
|
+
projectPath: 'acme/widgets',
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
it('parses an HTTPS remote without .git suffix', () => {
|
|
11
|
+
expect(parseGitLabRemote('https://gitlab.com/acme/widgets')).toEqual({
|
|
12
|
+
host: 'gitlab.com',
|
|
13
|
+
projectPath: 'acme/widgets',
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
it('parses an SSH (git@) remote', () => {
|
|
17
|
+
expect(parseGitLabRemote('git@gitlab.com:acme/widgets.git')).toEqual({
|
|
18
|
+
host: 'gitlab.com',
|
|
19
|
+
projectPath: 'acme/widgets',
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
it('parses an ssh:// protocol remote', () => {
|
|
23
|
+
expect(parseGitLabRemote('ssh://git@gitlab.com/acme/widgets.git')).toEqual({
|
|
24
|
+
host: 'gitlab.com',
|
|
25
|
+
projectPath: 'acme/widgets',
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
it('supports NESTED groups (group/subgroup/project)', () => {
|
|
29
|
+
expect(parseGitLabRemote('https://gitlab.com/group/subgroup/project.git')).toEqual({
|
|
30
|
+
host: 'gitlab.com',
|
|
31
|
+
projectPath: 'group/subgroup/project',
|
|
32
|
+
});
|
|
33
|
+
expect(parseGitLabRemote('git@gitlab.com:group/subgroup/project.git')).toEqual({
|
|
34
|
+
host: 'gitlab.com',
|
|
35
|
+
projectPath: 'group/subgroup/project',
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
// ── Self-hosted GitLab (FIX A) ──────────────────────────────────
|
|
39
|
+
it('parses a self-hosted HTTPS remote and returns its host', () => {
|
|
40
|
+
expect(parseGitLabRemote('https://gitlab.example.com/team/repo.git')).toEqual({
|
|
41
|
+
host: 'gitlab.example.com',
|
|
42
|
+
projectPath: 'team/repo',
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
it('parses a self-hosted SSH (git@) remote', () => {
|
|
46
|
+
expect(parseGitLabRemote('git@gitlab.example.com:team/repo.git')).toEqual({
|
|
47
|
+
host: 'gitlab.example.com',
|
|
48
|
+
projectPath: 'team/repo',
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
it('strips a port from an ssh:// self-hosted remote (API rides HTTPS)', () => {
|
|
52
|
+
expect(parseGitLabRemote('ssh://git@gitlab.example.com:2222/team/repo.git')).toEqual({
|
|
53
|
+
host: 'gitlab.example.com',
|
|
54
|
+
projectPath: 'team/repo',
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
it('strips userinfo from an HTTPS remote', () => {
|
|
58
|
+
expect(parseGitLabRemote('https://user:tok@gitlab.example.com/team/repo.git')).toEqual({
|
|
59
|
+
host: 'gitlab.example.com',
|
|
60
|
+
projectPath: 'team/repo',
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
it('throws on a github.com remote (obvious --mr misuse)', () => {
|
|
64
|
+
expect(() => parseGitLabRemote('https://github.com/acme/widgets.git')).toThrow(/Not a GitLab remote URL/);
|
|
65
|
+
expect(() => parseGitLabRemote('git@github.com:acme/widgets.git')).toThrow(/Not a GitLab remote URL/);
|
|
66
|
+
});
|
|
67
|
+
it('throws on an unrecognizable remote', () => {
|
|
68
|
+
expect(() => parseGitLabRemote('not-a-url')).toThrow(/Not a GitLab remote URL/);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
describe('GitLabApiError', () => {
|
|
72
|
+
it('carries status + body', () => {
|
|
73
|
+
const err = new GitLabApiError('boom', 404, 'not found');
|
|
74
|
+
expect(err.status).toBe(404);
|
|
75
|
+
expect(err.body).toBe('not found');
|
|
76
|
+
expect(err.name).toBe('GitLabApiError');
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
//# sourceMappingURL=gitlab-api.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitlab-api.test.js","sourceRoot":"","sources":["../../src/lib/gitlab-api.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* P4 task 4.3 — MANUAL / E2E acceptance gate for the GitLab MR summary post-back
|
|
3
|
+
* (R-GITLAB). This is the ONLY test in the suite that talks to a LIVE GitLab
|
|
4
|
+
* instance. It is `skipIf(!process.env.GITLAB_PAT)` so CI ALWAYS skips it — CI
|
|
5
|
+
* coverage of the GitLab REST surface lives in `cli-gitlab-client-port.test.ts`
|
|
6
|
+
* (fetch-mocked). This file is the real-instance sign-off the spec mandates.
|
|
7
|
+
*
|
|
8
|
+
* ENVIRONMENT VARIABLES
|
|
9
|
+
* ---------------------
|
|
10
|
+
* REQUIRED:
|
|
11
|
+
* GITLAB_PAT a Personal Access Token with the `api` scope.
|
|
12
|
+
* GITLAB_E2E_PROJECT the `group/project` path (nested groups OK,
|
|
13
|
+
* e.g. `group/subgroup/project`).
|
|
14
|
+
* GITLAB_E2E_MR the iid of an OPEN, throwaway MR in that project. The
|
|
15
|
+
* test posts and sweeps notes on it, so it MUST be a
|
|
16
|
+
* disposable MR you control — never a real review.
|
|
17
|
+
* OPTIONAL (self-hosted GitLab — gitlab.com is the default):
|
|
18
|
+
* GITLAB_HOST a host-only override (e.g. `gitlab.example.com`) when the
|
|
19
|
+
* API host differs from gitlab.com.
|
|
20
|
+
* GITLAB_API_BASE a FULL `.../api/v4` base override, required for a
|
|
21
|
+
* self-managed GitLab served under a subpath
|
|
22
|
+
* (e.g. `https://example.com/gitlab/api/v4`). Wins over
|
|
23
|
+
* GITLAB_HOST. Both are resolved through the SAME
|
|
24
|
+
* production helper (`resolveGitLabApiBase`) the `--mr`
|
|
25
|
+
* command path uses, so this test exercises the real
|
|
26
|
+
* self-hosted resolution — not a test-only shortcut.
|
|
27
|
+
*
|
|
28
|
+
* HOW TO RUN IT MANUALLY
|
|
29
|
+
* ----------------------
|
|
30
|
+
* Against a throwaway GitLab project + OPEN, disposable MR you control:
|
|
31
|
+
*
|
|
32
|
+
* export GITLAB_PAT=glpat-xxxxxxxxxxxxxxxxxxxx # PAT with `api` scope
|
|
33
|
+
* export GITLAB_E2E_PROJECT=group/your-project # group/project path
|
|
34
|
+
* export GITLAB_E2E_MR=1 # an OPEN, throwaway MR iid
|
|
35
|
+
* # optional self-hosted: export GITLAB_HOST=gitlab.example.com
|
|
36
|
+
* pnpm --filter ghagga exec vitest run src/lib/gitlab-e2e.manual.test.ts
|
|
37
|
+
*
|
|
38
|
+
* It will: resolve the numeric project id from the path, post (idempotently
|
|
39
|
+
* upsert) a marker-tagged summary note to the MR, assert the returned id is a
|
|
40
|
+
* positive number AND that the SPECIFIC note production just created
|
|
41
|
+
* (first.createdNativeId) is live on the MR carrying MARKER.html, then run a
|
|
42
|
+
* SECOND upsert and assert (a) the first note's id is in deletedNativeIds and
|
|
43
|
+
* (b) against the real MR state exactly ONE marker note remains and it is the
|
|
44
|
+
* second note — proving the find→delete→repost idempotency FOLD against the
|
|
45
|
+
* live API. An `afterAll` ATTEMPTS to leave zero leftover notes (best-effort; a
|
|
46
|
+
* transient GitLab failure may leave one, which the next run's sweep removes).
|
|
47
|
+
*
|
|
48
|
+
* MARKER OWNERSHIP NOTE: the marker is OWNED BY THE CALLER, not the adapter —
|
|
49
|
+
* the test renders it into `body` (see where `body` is built below). Production
|
|
50
|
+
* never injects it; `upsertSummaryComment` only USES `marker.html` to FILTER
|
|
51
|
+
* which notes to sweep (gitlab-forge-adapter.ts:182). So the anchored assertion
|
|
52
|
+
* below simply verifies that the note production created persists with the
|
|
53
|
+
* marker on live GitLab; the "exactly one marker note after the repost" check
|
|
54
|
+
* is what actually proves the idempotency fold against real MR state.
|
|
55
|
+
*/
|
|
56
|
+
export {};
|
|
57
|
+
//# sourceMappingURL=gitlab-e2e.manual.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitlab-e2e.manual.test.d.ts","sourceRoot":"","sources":["../../src/lib/gitlab-e2e.manual.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* P4 task 4.3 — MANUAL / E2E acceptance gate for the GitLab MR summary post-back
|
|
3
|
+
* (R-GITLAB). This is the ONLY test in the suite that talks to a LIVE GitLab
|
|
4
|
+
* instance. It is `skipIf(!process.env.GITLAB_PAT)` so CI ALWAYS skips it — CI
|
|
5
|
+
* coverage of the GitLab REST surface lives in `cli-gitlab-client-port.test.ts`
|
|
6
|
+
* (fetch-mocked). This file is the real-instance sign-off the spec mandates.
|
|
7
|
+
*
|
|
8
|
+
* ENVIRONMENT VARIABLES
|
|
9
|
+
* ---------------------
|
|
10
|
+
* REQUIRED:
|
|
11
|
+
* GITLAB_PAT a Personal Access Token with the `api` scope.
|
|
12
|
+
* GITLAB_E2E_PROJECT the `group/project` path (nested groups OK,
|
|
13
|
+
* e.g. `group/subgroup/project`).
|
|
14
|
+
* GITLAB_E2E_MR the iid of an OPEN, throwaway MR in that project. The
|
|
15
|
+
* test posts and sweeps notes on it, so it MUST be a
|
|
16
|
+
* disposable MR you control — never a real review.
|
|
17
|
+
* OPTIONAL (self-hosted GitLab — gitlab.com is the default):
|
|
18
|
+
* GITLAB_HOST a host-only override (e.g. `gitlab.example.com`) when the
|
|
19
|
+
* API host differs from gitlab.com.
|
|
20
|
+
* GITLAB_API_BASE a FULL `.../api/v4` base override, required for a
|
|
21
|
+
* self-managed GitLab served under a subpath
|
|
22
|
+
* (e.g. `https://example.com/gitlab/api/v4`). Wins over
|
|
23
|
+
* GITLAB_HOST. Both are resolved through the SAME
|
|
24
|
+
* production helper (`resolveGitLabApiBase`) the `--mr`
|
|
25
|
+
* command path uses, so this test exercises the real
|
|
26
|
+
* self-hosted resolution — not a test-only shortcut.
|
|
27
|
+
*
|
|
28
|
+
* HOW TO RUN IT MANUALLY
|
|
29
|
+
* ----------------------
|
|
30
|
+
* Against a throwaway GitLab project + OPEN, disposable MR you control:
|
|
31
|
+
*
|
|
32
|
+
* export GITLAB_PAT=glpat-xxxxxxxxxxxxxxxxxxxx # PAT with `api` scope
|
|
33
|
+
* export GITLAB_E2E_PROJECT=group/your-project # group/project path
|
|
34
|
+
* export GITLAB_E2E_MR=1 # an OPEN, throwaway MR iid
|
|
35
|
+
* # optional self-hosted: export GITLAB_HOST=gitlab.example.com
|
|
36
|
+
* pnpm --filter ghagga exec vitest run src/lib/gitlab-e2e.manual.test.ts
|
|
37
|
+
*
|
|
38
|
+
* It will: resolve the numeric project id from the path, post (idempotently
|
|
39
|
+
* upsert) a marker-tagged summary note to the MR, assert the returned id is a
|
|
40
|
+
* positive number AND that the SPECIFIC note production just created
|
|
41
|
+
* (first.createdNativeId) is live on the MR carrying MARKER.html, then run a
|
|
42
|
+
* SECOND upsert and assert (a) the first note's id is in deletedNativeIds and
|
|
43
|
+
* (b) against the real MR state exactly ONE marker note remains and it is the
|
|
44
|
+
* second note — proving the find→delete→repost idempotency FOLD against the
|
|
45
|
+
* live API. An `afterAll` ATTEMPTS to leave zero leftover notes (best-effort; a
|
|
46
|
+
* transient GitLab failure may leave one, which the next run's sweep removes).
|
|
47
|
+
*
|
|
48
|
+
* MARKER OWNERSHIP NOTE: the marker is OWNED BY THE CALLER, not the adapter —
|
|
49
|
+
* the test renders it into `body` (see where `body` is built below). Production
|
|
50
|
+
* never injects it; `upsertSummaryComment` only USES `marker.html` to FILTER
|
|
51
|
+
* which notes to sweep (gitlab-forge-adapter.ts:182). So the anchored assertion
|
|
52
|
+
* below simply verifies that the note production created persists with the
|
|
53
|
+
* marker on live GitLab; the "exactly one marker note after the repost" check
|
|
54
|
+
* is what actually proves the idempotency fold against real MR state.
|
|
55
|
+
*/
|
|
56
|
+
import { GitLabForgeAdapter } from 'ghagga-forge';
|
|
57
|
+
import { afterAll, describe, expect, it } from 'vitest';
|
|
58
|
+
import { createCliGitLabClientPort, resolveGitLabApiBase, resolveGitLabProjectId, } from './cli-gitlab-client-port.js';
|
|
59
|
+
import { postSummaryComment } from './pr-postback.js';
|
|
60
|
+
const PAT = process.env.GITLAB_PAT;
|
|
61
|
+
const PROJECT_PATH = process.env.GITLAB_E2E_PROJECT ?? '';
|
|
62
|
+
const MR_IID = Number.parseInt(process.env.GITLAB_E2E_MR ?? '', 10);
|
|
63
|
+
const MARKER = { html: '<!-- ghagga-review -->' };
|
|
64
|
+
/**
|
|
65
|
+
* Resolve the API base the SAME way the production `--mr` command path does:
|
|
66
|
+
* feed gitlab.com as the remote-derived host and let GITLAB_HOST / GITLAB_API_BASE
|
|
67
|
+
* override it (resolveGitLabApiBase precedence). With no override this is exactly
|
|
68
|
+
* the gitlab.com default the back-compat callers use.
|
|
69
|
+
*/
|
|
70
|
+
const API_BASE = resolveGitLabApiBase('gitlab.com');
|
|
71
|
+
describe('GitLab MR summary post-back — LIVE E2E (manual gate, R-GITLAB)', () => {
|
|
72
|
+
// Note ids created during the test, swept best-effort in afterAll so the
|
|
73
|
+
// throwaway MR is left clean. Only populated when the test actually runs
|
|
74
|
+
// (it is empty when skipped without a PAT, so afterAll is a no-op).
|
|
75
|
+
const createdNoteIds = [];
|
|
76
|
+
let client;
|
|
77
|
+
let projectId;
|
|
78
|
+
const token = PAT;
|
|
79
|
+
afterAll(async () => {
|
|
80
|
+
// Best-effort cleanup: tolerate a per-note failure (404 / already gone /
|
|
81
|
+
// transient) so a delete error never fails the suite. Skipped entirely when
|
|
82
|
+
// the test did not run (no client / projectId resolved → nothing created).
|
|
83
|
+
if (!PAT || client == null || projectId == null)
|
|
84
|
+
return;
|
|
85
|
+
for (const noteId of createdNoteIds) {
|
|
86
|
+
try {
|
|
87
|
+
await client.deleteMrNote(projectId, MR_IID, noteId, token);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// Tolerate: leaving a stray note is better than failing the suite on
|
|
91
|
+
// cleanup. The next manual run's sweep removes it anyway.
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
it.skipIf(!PAT)('resolves numeric project id + posts a marker-tagged note + sweeps the stale one on re-run', async () => {
|
|
96
|
+
expect(PROJECT_PATH, 'set GITLAB_E2E_PROJECT=group/project').not.toBe('');
|
|
97
|
+
expect(Number.isInteger(MR_IID) && MR_IID > 0, 'set GITLAB_E2E_MR=<open MR iid>').toBe(true);
|
|
98
|
+
projectId = await resolveGitLabProjectId(PROJECT_PATH, token, API_BASE);
|
|
99
|
+
expect(projectId).toMatch(/^\d+$/); // numeric project id (R-GITLAB nativeId)
|
|
100
|
+
client = createCliGitLabClientPort(API_BASE);
|
|
101
|
+
const adapter = new GitLabForgeAdapter({ client, token, projectId });
|
|
102
|
+
const ref = {
|
|
103
|
+
repo: { kind: 'gitlab', nativeId: projectId, path: PROJECT_PATH },
|
|
104
|
+
iid: MR_IID,
|
|
105
|
+
};
|
|
106
|
+
const body = `${MARKER.html}\n## GHAGGA E2E\nposted at ${new Date().toISOString()}`;
|
|
107
|
+
// First upsert: posts a fresh note.
|
|
108
|
+
const first = await postSummaryComment(adapter, ref, body, MARKER);
|
|
109
|
+
expect(first.createdNativeId).toBeGreaterThan(0);
|
|
110
|
+
createdNoteIds.push(first.createdNativeId);
|
|
111
|
+
// Marker persistence: anchor the assertion to the note PRODUCTION created
|
|
112
|
+
// (first.createdNativeId === GitLabNote.id), not to "any note that happens
|
|
113
|
+
// to carry the marker". This proves the specific note the upsert posted is
|
|
114
|
+
// live on the MR AND still carries the marker the adapter's sweep filters
|
|
115
|
+
// on (gitlab-forge-adapter.ts:182).
|
|
116
|
+
const afterFirst = await client.listMrNotes(projectId, MR_IID, token);
|
|
117
|
+
const createdNote = afterFirst.find((n) => n.id === first.createdNativeId);
|
|
118
|
+
expect(createdNote, 'the note created by upsert must appear in the live listing').toBeDefined();
|
|
119
|
+
expect(createdNote?.body.includes(MARKER.html), 'the created note must carry the marker the sweep filters on').toBe(true);
|
|
120
|
+
// Second upsert: must SWEEP the first (idempotency) and repost.
|
|
121
|
+
const second = await postSummaryComment(adapter, ref, body, MARKER);
|
|
122
|
+
expect(second.createdNativeId).toBeGreaterThan(0);
|
|
123
|
+
createdNoteIds.push(second.createdNativeId);
|
|
124
|
+
expect(second.deletedNativeIds).toContain(first.createdNativeId);
|
|
125
|
+
// Idempotency FOLD against the REAL MR state (not just the return value):
|
|
126
|
+
// after the repost exactly ONE marker note must remain, and it must be the
|
|
127
|
+
// note the SECOND upsert created. This is the invariant the sweep exists to
|
|
128
|
+
// hold — complements the `deletedNativeIds toContain first` return-value check.
|
|
129
|
+
const afterSecond = await client.listMrNotes(projectId, MR_IID, token);
|
|
130
|
+
const markerNotes = afterSecond.filter((n) => n.body.includes(MARKER.html));
|
|
131
|
+
expect(markerNotes.length, 'the sweep must leave exactly one note carrying the marker').toBe(1);
|
|
132
|
+
expect(markerNotes[0]?.id).toBe(second.createdNativeId);
|
|
133
|
+
}, 30_000);
|
|
134
|
+
});
|
|
135
|
+
//# sourceMappingURL=gitlab-e2e.manual.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitlab-e2e.manual.test.js","sourceRoot":"","sources":["../../src/lib/gitlab-e2e.manual.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token resolution for the CLI GitLab MR post-back (`ghagga review --mr N`).
|
|
3
|
+
*
|
|
4
|
+
* Resolution order (CI-first, interactive fallback):
|
|
5
|
+
* 1. env GITLAB_TOKEN — the GitLab CI / standard convention.
|
|
6
|
+
* 2. env GL_TOKEN — the `glab` CLI / alternate convention.
|
|
7
|
+
* 3. a GitLab-SPECIFIC stored token (config `gitlabToken`), if one exists.
|
|
8
|
+
*
|
|
9
|
+
* CRITICAL: this does NOT fall back to the GitHub stored login token
|
|
10
|
+
* ({@link getStoredToken}). A GitHub token against the GitLab API yields a 401 /
|
|
11
|
+
* nonsense — a GitHub credential is NOT a GitLab credential. When no GitLab token
|
|
12
|
+
* resolves, return `null`; the caller turns that into an actionable error ONLY
|
|
13
|
+
* when `--mr` was actually requested (non-zero exit, P3/P4 exit semantics).
|
|
14
|
+
*/
|
|
15
|
+
/** Resolve a GitLab token: env GITLAB_TOKEN > GL_TOKEN > GitLab-specific stored. */
|
|
16
|
+
export declare function resolveMrToken(env?: NodeJS.ProcessEnv, storedTokenFn?: () => string | null): string | null;
|
|
17
|
+
//# sourceMappingURL=gitlab-token.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitlab-token.d.ts","sourceRoot":"","sources":["../../src/lib/gitlab-token.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,oFAAoF;AACpF,wBAAgB,cAAc,CAC5B,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,aAAa,GAAE,MAAM,MAAM,GAAG,IAA2B,GACxD,MAAM,GAAG,IAAI,CAKf"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token resolution for the CLI GitLab MR post-back (`ghagga review --mr N`).
|
|
3
|
+
*
|
|
4
|
+
* Resolution order (CI-first, interactive fallback):
|
|
5
|
+
* 1. env GITLAB_TOKEN — the GitLab CI / standard convention.
|
|
6
|
+
* 2. env GL_TOKEN — the `glab` CLI / alternate convention.
|
|
7
|
+
* 3. a GitLab-SPECIFIC stored token (config `gitlabToken`), if one exists.
|
|
8
|
+
*
|
|
9
|
+
* CRITICAL: this does NOT fall back to the GitHub stored login token
|
|
10
|
+
* ({@link getStoredToken}). A GitHub token against the GitLab API yields a 401 /
|
|
11
|
+
* nonsense — a GitHub credential is NOT a GitLab credential. When no GitLab token
|
|
12
|
+
* resolves, return `null`; the caller turns that into an actionable error ONLY
|
|
13
|
+
* when `--mr` was actually requested (non-zero exit, P3/P4 exit semantics).
|
|
14
|
+
*/
|
|
15
|
+
import { getStoredGitLabToken } from './config.js';
|
|
16
|
+
/** Resolve a GitLab token: env GITLAB_TOKEN > GL_TOKEN > GitLab-specific stored. */
|
|
17
|
+
export function resolveMrToken(env = process.env, storedTokenFn = getStoredGitLabToken) {
|
|
18
|
+
const fromEnv = env.GITLAB_TOKEN?.trim() || env.GL_TOKEN?.trim();
|
|
19
|
+
if (fromEnv)
|
|
20
|
+
return fromEnv;
|
|
21
|
+
const stored = storedTokenFn();
|
|
22
|
+
return stored?.trim() ? stored : null;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=gitlab-token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitlab-token.js","sourceRoot":"","sources":["../../src/lib/gitlab-token.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnD,oFAAoF;AACpF,MAAM,UAAU,cAAc,CAC5B,MAAyB,OAAO,CAAC,GAAG,EACpC,gBAAqC,oBAAoB;IAEzD,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,EAAE,IAAI,EAAE,IAAI,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC;IACjE,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC;IAC5B,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;IAC/B,OAAO,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AACxC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitlab-token.test.d.ts","sourceRoot":"","sources":["../../src/lib/gitlab-token.test.ts"],"names":[],"mappings":""}
|