ghagga-forge 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.
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/adapters/github/github-app-credential-provider.d.ts +102 -0
- package/dist/adapters/github/github-app-credential-provider.d.ts.map +1 -0
- package/dist/adapters/github/github-app-credential-provider.js +166 -0
- package/dist/adapters/github/github-app-credential-provider.js.map +1 -0
- package/dist/adapters/github/github-client-port.d.ts +92 -0
- package/dist/adapters/github/github-client-port.d.ts.map +1 -0
- package/dist/adapters/github/github-client-port.js +24 -0
- package/dist/adapters/github/github-client-port.js.map +1 -0
- package/dist/adapters/github/github-forge-adapter.d.ts +105 -0
- package/dist/adapters/github/github-forge-adapter.d.ts.map +1 -0
- package/dist/adapters/github/github-forge-adapter.js +225 -0
- package/dist/adapters/github/github-forge-adapter.js.map +1 -0
- package/dist/adapters/github/static-token-provider.d.ts +30 -0
- package/dist/adapters/github/static-token-provider.d.ts.map +1 -0
- package/dist/adapters/github/static-token-provider.js +35 -0
- package/dist/adapters/github/static-token-provider.js.map +1 -0
- package/dist/adapters/gitlab/gitlab-client-port.d.ts +82 -0
- package/dist/adapters/gitlab/gitlab-client-port.d.ts.map +1 -0
- package/dist/adapters/gitlab/gitlab-client-port.js +27 -0
- package/dist/adapters/gitlab/gitlab-client-port.js.map +1 -0
- package/dist/adapters/gitlab/gitlab-forge-adapter.d.ts +118 -0
- package/dist/adapters/gitlab/gitlab-forge-adapter.d.ts.map +1 -0
- package/dist/adapters/gitlab/gitlab-forge-adapter.js +238 -0
- package/dist/adapters/gitlab/gitlab-forge-adapter.js.map +1 -0
- package/dist/comment-id.d.ts +45 -0
- package/dist/comment-id.d.ts.map +1 -0
- package/dist/comment-id.js +48 -0
- package/dist/comment-id.js.map +1 -0
- package/dist/errors.d.ts +48 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +67 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/lint-boundary.d.ts +36 -0
- package/dist/lint-boundary.d.ts.map +1 -0
- package/dist/lint-boundary.impl.d.mts +41 -0
- package/dist/lint-boundary.impl.mjs +400 -0
- package/dist/lint-boundary.js +35 -0
- package/dist/lint-boundary.js.map +1 -0
- package/dist/ports/ci-runner.d.ts +48 -0
- package/dist/ports/ci-runner.d.ts.map +1 -0
- package/dist/ports/ci-runner.js +10 -0
- package/dist/ports/ci-runner.js.map +1 -0
- package/dist/ports/credential-provider.d.ts +32 -0
- package/dist/ports/credential-provider.d.ts.map +1 -0
- package/dist/ports/credential-provider.js +10 -0
- package/dist/ports/credential-provider.js.map +1 -0
- package/dist/ports/forge-adapter.d.ts +174 -0
- package/dist/ports/forge-adapter.d.ts.map +1 -0
- package/dist/ports/forge-adapter.js +34 -0
- package/dist/ports/forge-adapter.js.map +1 -0
- package/dist/ports/webhook-codec.d.ts +41 -0
- package/dist/ports/webhook-codec.d.ts.map +1 -0
- package/dist/ports/webhook-codec.js +18 -0
- package/dist/ports/webhook-codec.js.map +1 -0
- package/dist/project.d.ts +32 -0
- package/dist/project.d.ts.map +1 -0
- package/dist/project.js +41 -0
- package/dist/project.js.map +1 -0
- package/dist/ref.d.ts +20 -0
- package/dist/ref.d.ts.map +1 -0
- package/dist/ref.js +21 -0
- package/dist/ref.js.map +1 -0
- package/dist/registry.d.ts +69 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +68 -0
- package/dist/registry.js.map +1 -0
- package/dist/types.d.ts +310 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +50 -0
- package/dist/types.js.map +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitLabForgeAdapter — wraps a GitLab REST client behind the forge-agnostic
|
|
3
|
+
* {@link ForgeAdapterBase} + {@link InlineCapable} surfaces.
|
|
4
|
+
*
|
|
5
|
+
* DEPENDENCY INVERSION (boundary rule R-AGNOSTIC):
|
|
6
|
+
* `packages/forge` MUST NOT import a concrete HTTP client. This adapter depends
|
|
7
|
+
* on the injected {@link GitLabClientPort} (declared inside the forge package);
|
|
8
|
+
* the CLI (P4) constructs the adapter passing a native-fetch implementation:
|
|
9
|
+
*
|
|
10
|
+
* new GitLabForgeAdapter({ client, token, projectId })
|
|
11
|
+
*
|
|
12
|
+
* IDENTITY (R-GITLAB):
|
|
13
|
+
* - {@link RepoRef.nativeId} is the GitLab NUMERIC project id (the group/project
|
|
14
|
+
* `path` is mutable → the id is canonical). The adapter is constructed with the
|
|
15
|
+
* numeric `projectId` and routes EVERY REST call through it — never the path.
|
|
16
|
+
* - {@link ChangeRequestRef.iid} is the MR iid (project-scoped). The GitLab MR
|
|
17
|
+
* API path is `/projects/:id/merge_requests/:iid/...`.
|
|
18
|
+
*
|
|
19
|
+
* CAPABILITIES: reactions ❌ (GitLab reaction-award is not modelled — no
|
|
20
|
+
* `addReaction`), inlineComments ✅ (publishInline present), graphRead ❌ (no
|
|
21
|
+
* graph methods). The `capabilities` field is a HINT only (R-CAPABILITY): callers
|
|
22
|
+
* guard optional methods by method-presence, never by these flags.
|
|
23
|
+
*
|
|
24
|
+
* SCOPE — v1 deliverable is the SUMMARY COMMENT (upsertSummaryComment). The
|
|
25
|
+
* adapter also implements {@link InlineCapable.publishInline} to satisfy
|
|
26
|
+
* R-LEAK-PUBLISH: it posts N INDEPENDENT inline posts, returning a
|
|
27
|
+
* {@link PublishReport} where partial failure is FIRST-CLASS (never swallowed).
|
|
28
|
+
* INLINE POSITIONING: when an {@link InlineComment} carries `position` and the
|
|
29
|
+
* injected client supports `createMrDiscussion`, v1 posts a TRUE diff-anchored
|
|
30
|
+
* discussion (`position[position_type]=text` + the three SHAs + old/new line +
|
|
31
|
+
* old/new path). When `position` is absent (or the client lacks discussion
|
|
32
|
+
* support) it degrades to a plain MR note carrying the `path:line` prefix in the
|
|
33
|
+
* body. The public {@link InlineComment} type carries `position` + `oldPath`/
|
|
34
|
+
* `newPath` regardless, so broadening positioned coverage needs NO API change.
|
|
35
|
+
*
|
|
36
|
+
* COMMENT IDs: `upsertSummaryComment` returns PLAIN GitLab-native numeric ids
|
|
37
|
+
* (boxing happens caller-LOCAL via {@link gitlabCommentId}). `publishInline`
|
|
38
|
+
* boxes per the {@link PublishReport} contract (CommentId) since the report is
|
|
39
|
+
* the canonical cross-forge shape.
|
|
40
|
+
*/
|
|
41
|
+
import { gitlabCommentId } from '../../comment-id.js';
|
|
42
|
+
import { ForgeAuthError, getErrorStatus, isForgeAuthError } from '../../errors.js';
|
|
43
|
+
/**
|
|
44
|
+
* GitLab implementation of the forge adapter.
|
|
45
|
+
*
|
|
46
|
+
* Implements the mandatory base (summary-comment + the read methods, the latter
|
|
47
|
+
* unused by the CLI v1 path and intentionally absent from the client port) plus
|
|
48
|
+
* {@link InlineCapable}. Deliberately does NOT implement reactions or graph read.
|
|
49
|
+
*/
|
|
50
|
+
export class GitLabForgeAdapter {
|
|
51
|
+
capabilities = {
|
|
52
|
+
reactions: false,
|
|
53
|
+
inlineComments: true,
|
|
54
|
+
graphRead: false,
|
|
55
|
+
};
|
|
56
|
+
#client;
|
|
57
|
+
#token;
|
|
58
|
+
#projectId;
|
|
59
|
+
constructor(deps) {
|
|
60
|
+
this.#client = deps.client;
|
|
61
|
+
this.#token = deps.token;
|
|
62
|
+
this.#projectId = deps.projectId;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Run a client call, reclassifying a 401/403 failure as a {@link ForgeAuthError}
|
|
66
|
+
* (mirrors the GitHub adapter). With a static PAT a 401 is terminal — there is
|
|
67
|
+
* nothing to re-mint — but the typed signal keeps the CLI's error reporting
|
|
68
|
+
* consistent across forges. NON-auth failures rethrow UNCHANGED.
|
|
69
|
+
*/
|
|
70
|
+
async #mapAuth(call) {
|
|
71
|
+
try {
|
|
72
|
+
return await call();
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
const status = getErrorStatus(error);
|
|
76
|
+
if (status === 401 || status === 403) {
|
|
77
|
+
throw new ForgeAuthError(status, error instanceof Error ? error.message : `Forge auth failure (HTTP ${status})`, { cause: error });
|
|
78
|
+
}
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// ─── Base: reads ───────────────────────────────────────────────
|
|
83
|
+
//
|
|
84
|
+
// The GitLab CLI v1 deliverable is the summary comment; the diff/details/
|
|
85
|
+
// file-list/commits are sourced LOCALLY by the CLI (same posture as the GitHub
|
|
86
|
+
// CLI port). These methods are part of the mandatory base surface but are NOT
|
|
87
|
+
// exercised by the `--mr` flow, so they fail loudly rather than fabricate data.
|
|
88
|
+
fetchDiff(_ref) {
|
|
89
|
+
return Promise.reject(new Error('GitLabForgeAdapter.fetchDiff is not supported in the CLI summary-comment path — ' +
|
|
90
|
+
'the diff is sourced locally. (GitLab read methods are a v2 deliverable.)'));
|
|
91
|
+
}
|
|
92
|
+
fetchChangeRequest(_ref) {
|
|
93
|
+
return Promise.reject(new Error('GitLabForgeAdapter.fetchChangeRequest is not supported (v2 deliverable).'));
|
|
94
|
+
}
|
|
95
|
+
fetchFileList(_ref) {
|
|
96
|
+
return Promise.reject(new Error('GitLabForgeAdapter.fetchFileList is not supported (v2 deliverable).'));
|
|
97
|
+
}
|
|
98
|
+
fetchCommits(_ref) {
|
|
99
|
+
return Promise.reject(new Error('GitLabForgeAdapter.fetchCommits is not supported (v2 deliverable).'));
|
|
100
|
+
}
|
|
101
|
+
// ─── Base: upsert summary comment (fold list/delete/create → 1) ──
|
|
102
|
+
/**
|
|
103
|
+
* Idempotently upsert the single GHAGGA summary note on an MR.
|
|
104
|
+
*
|
|
105
|
+
* Behavior (mirrors the GitHub adapter's delete-all-stale + repost-at-bottom):
|
|
106
|
+
* 1. list MR notes; match the GHAGGA-owned ones by `marker.html` substring.
|
|
107
|
+
* 2. delete ALL matches in `[latest, ...stale]` order — BEST-EFFORT: each
|
|
108
|
+
* delete is per-note try/catch so a 404 (already gone) OR any other failure
|
|
109
|
+
* is tolerated and does NOT block the repost. Only ids that actually deleted
|
|
110
|
+
* (no throw) are reported in `deleted`.
|
|
111
|
+
* 3. create a FRESH note at the bottom — this is the ONLY failure that
|
|
112
|
+
* propagates (a failed create means no summary exists, which is fatal).
|
|
113
|
+
*
|
|
114
|
+
* "latest" = the LAST marker-matching note in chronological order (GitLab list
|
|
115
|
+
* returns notes oldest-first), matching the GitHub adapter's convention.
|
|
116
|
+
*
|
|
117
|
+
* Returns GitLab-native numeric ids (boxing happens caller-local via
|
|
118
|
+
* {@link gitlabCommentId}).
|
|
119
|
+
*/
|
|
120
|
+
async upsertSummaryComment(ref, body, marker) {
|
|
121
|
+
const notes = await this.#mapAuth(() => this.#client.listMrNotes(this.#projectId, ref.iid, this.#token));
|
|
122
|
+
const matchIds = notes.filter((n) => n.body.includes(marker.html)).map((n) => n.id);
|
|
123
|
+
const deleted = [];
|
|
124
|
+
if (matchIds.length > 0) {
|
|
125
|
+
// latest = last marker note; the rest are stale. Delete latest FIRST then
|
|
126
|
+
// stale, mirroring the GitHub adapter's baseline order. (Guaranteed
|
|
127
|
+
// non-empty inside this `matchIds.length > 0` branch.)
|
|
128
|
+
const latestId = matchIds[matchIds.length - 1];
|
|
129
|
+
const staleIds = matchIds.slice(0, -1);
|
|
130
|
+
const ordered = [latestId, ...staleIds];
|
|
131
|
+
for (const noteId of ordered) {
|
|
132
|
+
try {
|
|
133
|
+
await this.#client.deleteMrNote(this.#projectId, ref.iid, noteId, this.#token);
|
|
134
|
+
deleted.push(noteId);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// Best-effort: tolerate any delete failure (404 or non-404). A stale
|
|
138
|
+
// note that cannot be deleted must NOT block the fresh repost.
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Always create a fresh note at the bottom. This is the ONLY error that
|
|
143
|
+
// propagates. A 401/403 here is reclassified to ForgeAuthError.
|
|
144
|
+
const created = await this.#mapAuth(() => this.#client.createMrNote(this.#projectId, ref.iid, body, this.#token));
|
|
145
|
+
if (created?.id == null) {
|
|
146
|
+
throw new TypeError('GitLabForgeAdapter.upsertSummaryComment: createMrNote returned no id (expected { id: number })');
|
|
147
|
+
}
|
|
148
|
+
return { created: created.id, deleted };
|
|
149
|
+
}
|
|
150
|
+
// ─── InlineCapable ─────────────────────────────────────────────
|
|
151
|
+
/**
|
|
152
|
+
* Publish a batch of inline comments as N INDEPENDENT posts (R-LEAK-PUBLISH).
|
|
153
|
+
*
|
|
154
|
+
* PARTIAL FAILURE IS FIRST-CLASS: GitLab posts each comment independently, so
|
|
155
|
+
* each create is wrapped in its own try/catch. A failure records
|
|
156
|
+
* `{index, error, status?, authFailure?}` into `failed` and the loop CONTINUES
|
|
157
|
+
* — failures are NEVER swallowed and never abort the remaining posts. The
|
|
158
|
+
* `status`/`authFailure` tags let a caller tell an AUTH failure (401/403, a
|
|
159
|
+
* static PAT cannot recover) from a transient one without string-parsing the
|
|
160
|
+
* message. Successes are boxed into `CommentId` (kind:'gitlab') and collected
|
|
161
|
+
* into `posted`.
|
|
162
|
+
*
|
|
163
|
+
* POSITION HANDLING (v1):
|
|
164
|
+
* - When a comment carries `position` AND the injected client implements
|
|
165
|
+
* `createMrDiscussion`, the adapter posts a TRUE diff-anchored discussion
|
|
166
|
+
* (`position[position_type]=text` with the three SHAs + old/new line +
|
|
167
|
+
* old/new path — renames are honored via `oldPath`/`newPath`, defaulting to
|
|
168
|
+
* `path`). The boxed id is the discussion's first-note id.
|
|
169
|
+
* - Otherwise (no `position`, or a client without discussion support) it
|
|
170
|
+
* degrades to a plain MR note carrying a `path:line` body prefix so the
|
|
171
|
+
* anchor info is not lost. Either way the partial-failure REPORT SHAPE +
|
|
172
|
+
* independent-post semantics — the load-bearing R-LEAK-PUBLISH contract —
|
|
173
|
+
* hold. The public {@link InlineComment} type carries `position` regardless,
|
|
174
|
+
* so no future API change is needed to broaden positioned coverage.
|
|
175
|
+
*/
|
|
176
|
+
async publishInline(ref, comments) {
|
|
177
|
+
const posted = [];
|
|
178
|
+
const failed = [];
|
|
179
|
+
for (let index = 0; index < comments.length; index++) {
|
|
180
|
+
const comment = comments[index];
|
|
181
|
+
if (comment == null)
|
|
182
|
+
continue;
|
|
183
|
+
try {
|
|
184
|
+
const created = await this.#postInline(ref, comment);
|
|
185
|
+
if (created?.id == null) {
|
|
186
|
+
throw new TypeError('inline post returned no id (expected { id: number })');
|
|
187
|
+
}
|
|
188
|
+
posted.push(gitlabCommentId(created.id));
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
// First-class partial failure: record and CONTINUE (never swallow, never
|
|
192
|
+
// abort the rest). No #mapAuth reclassification here — a per-note failure
|
|
193
|
+
// is reported, not thrown, so the batch outcome is observable.
|
|
194
|
+
//
|
|
195
|
+
// TAG the failure so a caller can distinguish an AUTH failure (401/403 —
|
|
196
|
+
// a static PAT cannot recover, surface "fix your token") from a transient
|
|
197
|
+
// one WITHOUT string-parsing `error`. `#postInline` does not run through
|
|
198
|
+
// `#mapAuth`, so the raw client error still carries its numeric `status`;
|
|
199
|
+
// `isForgeAuthError` ALSO catches an already-reclassified ForgeAuthError
|
|
200
|
+
// for robustness.
|
|
201
|
+
const status = getErrorStatus(error);
|
|
202
|
+
const authFailure = isForgeAuthError(error) || status === 401 || status === 403;
|
|
203
|
+
failed.push({
|
|
204
|
+
index,
|
|
205
|
+
error: error instanceof Error ? error.message : String(error),
|
|
206
|
+
...(status != null ? { status } : {}),
|
|
207
|
+
...(authFailure ? { authFailure: true } : {}),
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return { posted, failed };
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Post a single inline comment: a TRUE diff-anchored discussion when the
|
|
215
|
+
* comment carries `position` AND the client supports it, else a degraded
|
|
216
|
+
* `path:line` plain note. Throws on a missing id (the caller records it as a
|
|
217
|
+
* per-comment failure).
|
|
218
|
+
*/
|
|
219
|
+
#postInline(ref, comment) {
|
|
220
|
+
const createDiscussion = this.#client.createMrDiscussion;
|
|
221
|
+
if (comment.position && typeof createDiscussion === 'function') {
|
|
222
|
+
return createDiscussion.call(this.#client, this.#projectId, ref.iid, comment.body, {
|
|
223
|
+
baseSha: comment.position.baseSha,
|
|
224
|
+
headSha: comment.position.headSha,
|
|
225
|
+
startSha: comment.position.startSha,
|
|
226
|
+
// GitLab requires BOTH paths for a text diff note; default to `path`.
|
|
227
|
+
oldPath: comment.oldPath ?? comment.path,
|
|
228
|
+
newPath: comment.newPath ?? comment.path,
|
|
229
|
+
...(comment.position.oldLine != null ? { oldLine: comment.position.oldLine } : {}),
|
|
230
|
+
...(comment.position.newLine != null ? { newLine: comment.position.newLine } : {}),
|
|
231
|
+
}, this.#token);
|
|
232
|
+
}
|
|
233
|
+
// Degrade: encode the anchor in the body as a plain note.
|
|
234
|
+
const noteBody = `\`${comment.path}:${comment.line}\`\n\n${comment.body}`;
|
|
235
|
+
return this.#client.createMrNote(this.#projectId, ref.iid, noteBody, this.#token);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=gitlab-forge-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitlab-forge-adapter.js","sourceRoot":"","sources":["../../../src/adapters/gitlab/gitlab-forge-adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanctioned comment-id boxing helpers (R-COMMENTID).
|
|
3
|
+
*
|
|
4
|
+
* The forge adapters RETURN forge-native primitives (e.g. GitHub numeric
|
|
5
|
+
* comment ids) — see {@link UpsertSummaryResult}. Boxing those primitives into
|
|
6
|
+
* the canonical {@link CommentId} ({kind, raw}) happens caller-LOCAL, AFTER the
|
|
7
|
+
* adapter returns. This module is the ONE blessed place that boxing lives, so
|
|
8
|
+
* both the server worker (apps/server review.ts) AND the future P3 CLI can reuse
|
|
9
|
+
* it WITHOUT importing side-effectful modules (review.ts constructs Redis/BullMQ
|
|
10
|
+
* queue state at import time — unusable from a CLI / from the helper's own test).
|
|
11
|
+
*
|
|
12
|
+
* This is a PURE helper using the forge {@link CommentId} type. It does NOT
|
|
13
|
+
* brand the adapter's return type (the adapter still returns numbers); it is the
|
|
14
|
+
* sanctioned boxing step the caller applies at the seam.
|
|
15
|
+
*
|
|
16
|
+
* Boundary note (R-AGNOSTIC): this file imports only the forge-local
|
|
17
|
+
* {@link CommentId} type — no `apps/server`, no `ghagga-core` value import — so
|
|
18
|
+
* it does not breach any package boundary.
|
|
19
|
+
*/
|
|
20
|
+
import type { CommentId } from './types.js';
|
|
21
|
+
/**
|
|
22
|
+
* BOX a GitHub-native numeric comment id into the canonical {@link CommentId}.
|
|
23
|
+
*
|
|
24
|
+
* The `kind: 'github'` tag prevents a GitHub comment id from being
|
|
25
|
+
* cross-assigned as a GitLab note id — the same numeric value across forges
|
|
26
|
+
* never collides because the discriminator differs.
|
|
27
|
+
*
|
|
28
|
+
* @param raw the GitHub-native numeric comment id.
|
|
29
|
+
* @returns the boxed `{ kind: 'github', raw: String(raw) }`.
|
|
30
|
+
*/
|
|
31
|
+
export declare function githubCommentId(raw: number): CommentId;
|
|
32
|
+
/**
|
|
33
|
+
* BOX a GitLab-native numeric note id into the canonical {@link CommentId}.
|
|
34
|
+
*
|
|
35
|
+
* GitLab note ids are NUMERIC but MR-scoped — the `kind: 'gitlab'` tag is what
|
|
36
|
+
* disambiguates them from a GitHub comment id (R-COMMENTID). The SAME numeric
|
|
37
|
+
* value boxed as `'github'` vs `'gitlab'` never collides because the
|
|
38
|
+
* discriminator differs, so a GitHub comment id can never be cross-assigned as a
|
|
39
|
+
* GitLab note id (and vice-versa).
|
|
40
|
+
*
|
|
41
|
+
* @param raw the GitLab-native numeric note id.
|
|
42
|
+
* @returns the boxed `{ kind: 'gitlab', raw: String(raw) }`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function gitlabCommentId(raw: number): CommentId;
|
|
45
|
+
//# sourceMappingURL=comment-id.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comment-id.d.ts","sourceRoot":"","sources":["../src/comment-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAEtD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAEtD"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanctioned comment-id boxing helpers (R-COMMENTID).
|
|
3
|
+
*
|
|
4
|
+
* The forge adapters RETURN forge-native primitives (e.g. GitHub numeric
|
|
5
|
+
* comment ids) — see {@link UpsertSummaryResult}. Boxing those primitives into
|
|
6
|
+
* the canonical {@link CommentId} ({kind, raw}) happens caller-LOCAL, AFTER the
|
|
7
|
+
* adapter returns. This module is the ONE blessed place that boxing lives, so
|
|
8
|
+
* both the server worker (apps/server review.ts) AND the future P3 CLI can reuse
|
|
9
|
+
* it WITHOUT importing side-effectful modules (review.ts constructs Redis/BullMQ
|
|
10
|
+
* queue state at import time — unusable from a CLI / from the helper's own test).
|
|
11
|
+
*
|
|
12
|
+
* This is a PURE helper using the forge {@link CommentId} type. It does NOT
|
|
13
|
+
* brand the adapter's return type (the adapter still returns numbers); it is the
|
|
14
|
+
* sanctioned boxing step the caller applies at the seam.
|
|
15
|
+
*
|
|
16
|
+
* Boundary note (R-AGNOSTIC): this file imports only the forge-local
|
|
17
|
+
* {@link CommentId} type — no `apps/server`, no `ghagga-core` value import — so
|
|
18
|
+
* it does not breach any package boundary.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* BOX a GitHub-native numeric comment id into the canonical {@link CommentId}.
|
|
22
|
+
*
|
|
23
|
+
* The `kind: 'github'` tag prevents a GitHub comment id from being
|
|
24
|
+
* cross-assigned as a GitLab note id — the same numeric value across forges
|
|
25
|
+
* never collides because the discriminator differs.
|
|
26
|
+
*
|
|
27
|
+
* @param raw the GitHub-native numeric comment id.
|
|
28
|
+
* @returns the boxed `{ kind: 'github', raw: String(raw) }`.
|
|
29
|
+
*/
|
|
30
|
+
export function githubCommentId(raw) {
|
|
31
|
+
return { kind: 'github', raw: String(raw) };
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* BOX a GitLab-native numeric note id into the canonical {@link CommentId}.
|
|
35
|
+
*
|
|
36
|
+
* GitLab note ids are NUMERIC but MR-scoped — the `kind: 'gitlab'` tag is what
|
|
37
|
+
* disambiguates them from a GitHub comment id (R-COMMENTID). The SAME numeric
|
|
38
|
+
* value boxed as `'github'` vs `'gitlab'` never collides because the
|
|
39
|
+
* discriminator differs, so a GitHub comment id can never be cross-assigned as a
|
|
40
|
+
* GitLab note id (and vice-versa).
|
|
41
|
+
*
|
|
42
|
+
* @param raw the GitLab-native numeric note id.
|
|
43
|
+
* @returns the boxed `{ kind: 'gitlab', raw: String(raw) }`.
|
|
44
|
+
*/
|
|
45
|
+
export function gitlabCommentId(raw) {
|
|
46
|
+
return { kind: 'gitlab', raw: String(raw) };
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=comment-id.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comment-id.js","sourceRoot":"","sources":["../src/comment-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;AAC9C,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;AAC9C,CAAC"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forge error taxonomy (SDD forge-agnostic — P2 401-recovery fix).
|
|
3
|
+
*
|
|
4
|
+
* The forge boundary throws plain `Error`s on most failures. But the in-job
|
|
5
|
+
* 401-recovery seam (review.ts postback) needs to distinguish an AUTH failure
|
|
6
|
+
* (HTTP 401/403 — the cached installation token was revoked/rotated server-side)
|
|
7
|
+
* from any other failure, GENERICALLY, without sniffing error message strings.
|
|
8
|
+
*
|
|
9
|
+
* {@link ForgeAuthError} is that typed signal. The adapter raises it when an
|
|
10
|
+
* underlying forge call fails with a 401/403 status; the worker catches it via
|
|
11
|
+
* {@link isForgeAuthError}, invalidates the credential provider's cache, re-mints,
|
|
12
|
+
* and retries the call ONCE. Non-auth failures stay plain `Error`s (NOT
|
|
13
|
+
* reclassified) so retry logic only fires for genuine auth failures.
|
|
14
|
+
*/
|
|
15
|
+
/** HTTP statuses that mean "the token is no longer accepted" → re-mint + retry. */
|
|
16
|
+
export declare const FORGE_AUTH_STATUSES: readonly [401, 403];
|
|
17
|
+
/**
|
|
18
|
+
* A forge call failed because the access token was rejected (HTTP 401/403).
|
|
19
|
+
*
|
|
20
|
+
* Carries the originating HTTP `status` and (optionally) the underlying `cause`
|
|
21
|
+
* so callers can log the real error while reacting to the auth class generically.
|
|
22
|
+
*/
|
|
23
|
+
export declare class ForgeAuthError extends Error {
|
|
24
|
+
/** The HTTP status that triggered this auth error (401 or 403). */
|
|
25
|
+
readonly status: number;
|
|
26
|
+
constructor(status: number, message?: string, options?: {
|
|
27
|
+
cause?: unknown;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Type guard: is `e` a {@link ForgeAuthError}?
|
|
32
|
+
*
|
|
33
|
+
* Robust to cross-realm / multiple-bundle-copy situations (where `instanceof`
|
|
34
|
+
* can fail) by ALSO accepting any error shaped like a ForgeAuthError (name +
|
|
35
|
+
* numeric auth status). Non-auth errors return `false` so they are never
|
|
36
|
+
* mistaken for a retry-able auth failure.
|
|
37
|
+
*/
|
|
38
|
+
export declare function isForgeAuthError(e: unknown): e is ForgeAuthError;
|
|
39
|
+
/**
|
|
40
|
+
* Extract an HTTP status from an arbitrary thrown value, if it carries one.
|
|
41
|
+
*
|
|
42
|
+
* The `apps/server` GitHub client tags its thrown errors with a numeric
|
|
43
|
+
* `status` field (see client.ts). The adapter uses this to decide whether a
|
|
44
|
+
* client failure is an auth failure (401/403) it should reclassify as a
|
|
45
|
+
* {@link ForgeAuthError}. Returns `undefined` when no usable status is present.
|
|
46
|
+
*/
|
|
47
|
+
export declare function getErrorStatus(e: unknown): number | undefined;
|
|
48
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,mFAAmF;AACnF,eAAO,MAAM,mBAAmB,qBAAsB,CAAC;AAEvD;;;;;GAKG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACvC,mEAAmE;IACnE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAQ5E;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,cAAc,CAShE;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAI7D"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forge error taxonomy (SDD forge-agnostic — P2 401-recovery fix).
|
|
3
|
+
*
|
|
4
|
+
* The forge boundary throws plain `Error`s on most failures. But the in-job
|
|
5
|
+
* 401-recovery seam (review.ts postback) needs to distinguish an AUTH failure
|
|
6
|
+
* (HTTP 401/403 — the cached installation token was revoked/rotated server-side)
|
|
7
|
+
* from any other failure, GENERICALLY, without sniffing error message strings.
|
|
8
|
+
*
|
|
9
|
+
* {@link ForgeAuthError} is that typed signal. The adapter raises it when an
|
|
10
|
+
* underlying forge call fails with a 401/403 status; the worker catches it via
|
|
11
|
+
* {@link isForgeAuthError}, invalidates the credential provider's cache, re-mints,
|
|
12
|
+
* and retries the call ONCE. Non-auth failures stay plain `Error`s (NOT
|
|
13
|
+
* reclassified) so retry logic only fires for genuine auth failures.
|
|
14
|
+
*/
|
|
15
|
+
/** HTTP statuses that mean "the token is no longer accepted" → re-mint + retry. */
|
|
16
|
+
export const FORGE_AUTH_STATUSES = [401, 403];
|
|
17
|
+
/**
|
|
18
|
+
* A forge call failed because the access token was rejected (HTTP 401/403).
|
|
19
|
+
*
|
|
20
|
+
* Carries the originating HTTP `status` and (optionally) the underlying `cause`
|
|
21
|
+
* so callers can log the real error while reacting to the auth class generically.
|
|
22
|
+
*/
|
|
23
|
+
export class ForgeAuthError extends Error {
|
|
24
|
+
/** The HTTP status that triggered this auth error (401 or 403). */
|
|
25
|
+
status;
|
|
26
|
+
constructor(status, message, options) {
|
|
27
|
+
super(message ?? `Forge auth failure (HTTP ${status})`, options);
|
|
28
|
+
this.name = 'ForgeAuthError';
|
|
29
|
+
this.status = status;
|
|
30
|
+
// Preserve prototype chain across the TS-to-ES5/ES2015 transpile boundary so
|
|
31
|
+
// `instanceof` keeps working even if a downstream target down-levels classes.
|
|
32
|
+
Object.setPrototypeOf(this, ForgeAuthError.prototype);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Type guard: is `e` a {@link ForgeAuthError}?
|
|
37
|
+
*
|
|
38
|
+
* Robust to cross-realm / multiple-bundle-copy situations (where `instanceof`
|
|
39
|
+
* can fail) by ALSO accepting any error shaped like a ForgeAuthError (name +
|
|
40
|
+
* numeric auth status). Non-auth errors return `false` so they are never
|
|
41
|
+
* mistaken for a retry-able auth failure.
|
|
42
|
+
*/
|
|
43
|
+
export function isForgeAuthError(e) {
|
|
44
|
+
if (e instanceof ForgeAuthError)
|
|
45
|
+
return true;
|
|
46
|
+
if (typeof e !== 'object' || e === null)
|
|
47
|
+
return false;
|
|
48
|
+
const candidate = e;
|
|
49
|
+
return (candidate.name === 'ForgeAuthError' &&
|
|
50
|
+
typeof candidate.status === 'number' &&
|
|
51
|
+
FORGE_AUTH_STATUSES.includes(candidate.status));
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Extract an HTTP status from an arbitrary thrown value, if it carries one.
|
|
55
|
+
*
|
|
56
|
+
* The `apps/server` GitHub client tags its thrown errors with a numeric
|
|
57
|
+
* `status` field (see client.ts). The adapter uses this to decide whether a
|
|
58
|
+
* client failure is an auth failure (401/403) it should reclassify as a
|
|
59
|
+
* {@link ForgeAuthError}. Returns `undefined` when no usable status is present.
|
|
60
|
+
*/
|
|
61
|
+
export function getErrorStatus(e) {
|
|
62
|
+
if (typeof e !== 'object' || e === null)
|
|
63
|
+
return undefined;
|
|
64
|
+
const status = e.status;
|
|
65
|
+
return typeof status === 'number' ? status : undefined;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,mFAAmF;AACnF,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,GAAG,EAAE,GAAG,CAAU,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,mEAAmE;IAC1D,MAAM,CAAS;IAExB,YAAY,MAAc,EAAE,OAAgB,EAAE,OAA6B;QACzE,KAAK,CAAC,OAAO,IAAI,4BAA4B,MAAM,GAAG,EAAE,OAAO,CAAC,CAAC;QACjE,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,6EAA6E;QAC7E,8EAA8E;QAC9E,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC;IACxD,CAAC;CACF;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,CAAU;IACzC,IAAI,CAAC,YAAY,cAAc;QAAE,OAAO,IAAI,CAAC;IAC7C,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IACtD,MAAM,SAAS,GAAG,CAAyC,CAAC;IAC5D,OAAO,CACL,SAAS,CAAC,IAAI,KAAK,gBAAgB;QACnC,OAAO,SAAS,CAAC,MAAM,KAAK,QAAQ;QACnC,mBAAyC,CAAC,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,CACtE,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,CAAU;IACvC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAC1D,MAAM,MAAM,GAAI,CAA0B,CAAC,MAAM,CAAC;IAClD,OAAO,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AACzD,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ghagga-forge — Public API
|
|
3
|
+
*
|
|
4
|
+
* Forge-agnostic ports and canonical domain types. The core review engine and
|
|
5
|
+
* the server talk to these abstractions instead of any concrete forge SDK.
|
|
6
|
+
*
|
|
7
|
+
* Architecture invariants (see individual files for detail):
|
|
8
|
+
* - R-AGNOSTIC: `ghagga-core` MUST NOT import this package (value OR type).
|
|
9
|
+
* This package MAY import core in TYPE position only.
|
|
10
|
+
* - R-PROJECTION / R-COMMENTID / R-CAPABILITY / R-RESOLVE / R-GRAPH are pinned
|
|
11
|
+
* by the types and the test suite.
|
|
12
|
+
*/
|
|
13
|
+
export type { Actor, ActorKind, AuthorAssociation, ChangedFile, ChangeKind, ChangeRequest, ChangeRequestRef, CommentId, CommentMarker, Commit, DiffRefs, ForgeCapabilities, ForgeEvent, ForgeKind, NormalizedComment, PublishFailure, PublishReport, RepoRef, TenantHint, UnifiedDiff, UpsertSummaryResult, } from './types.js';
|
|
14
|
+
export { ACTOR_KIND, AUTHOR_ASSOCIATION, CHANGE_KIND, FORGE_KIND, } from './types.js';
|
|
15
|
+
export type { ForgeAdapter, ForgeAdapterBase, GraphReadCapable, InlineCapable, InlineComment, MarkerExtractable, ReactionCapable, ReactionKind, } from './ports/forge-adapter.js';
|
|
16
|
+
export { REACTION_KIND } from './ports/forge-adapter.js';
|
|
17
|
+
export type { CiDispatchRequest, CiDispatchResult, CiRunner, EnsureWorkflowResult, } from './ports/ci-runner.js';
|
|
18
|
+
export { FORGE_AUTH_STATUSES, ForgeAuthError, getErrorStatus, isForgeAuthError } from './errors.js';
|
|
19
|
+
export type { ForgeCredentialProvider } from './ports/credential-provider.js';
|
|
20
|
+
export type { ForgeWebhookCodec } from './ports/webhook-codec.js';
|
|
21
|
+
export type { ForgeRegistry, UnknownForgeErrorContext } from './registry.js';
|
|
22
|
+
export { MapForgeRegistry, UnknownForgeError } from './registry.js';
|
|
23
|
+
export type { GitHubAppCredentialProviderDeps } from './adapters/github/github-app-credential-provider.js';
|
|
24
|
+
export { BUDGET_SECONDS, GitHubAppCredentialProvider, SKEW_SECONDS, } from './adapters/github/github-app-credential-provider.js';
|
|
25
|
+
export type { GitHubClientPort, GitHubInstallationTokenMintWithExpiry, GitHubReactionContent, MintedInstallationToken, } from './adapters/github/github-client-port.js';
|
|
26
|
+
export type { GitHubForgeAdapterDeps } from './adapters/github/github-forge-adapter.js';
|
|
27
|
+
export { GitHubForgeAdapter } from './adapters/github/github-forge-adapter.js';
|
|
28
|
+
export { StaticTokenProvider } from './adapters/github/static-token-provider.js';
|
|
29
|
+
export type { GitLabClientPort, GitLabDiffPosition, GitLabNote, } from './adapters/gitlab/gitlab-client-port.js';
|
|
30
|
+
export type { GitLabForgeAdapterDeps } from './adapters/gitlab/gitlab-forge-adapter.js';
|
|
31
|
+
export { GitLabForgeAdapter } from './adapters/gitlab/gitlab-forge-adapter.js';
|
|
32
|
+
export { githubCommentId, gitlabCommentId } from './comment-id.js';
|
|
33
|
+
export { repoRefEquals } from './ref.js';
|
|
34
|
+
export { toCommitMessages, toFileList, toReviewContext } from './project.js';
|
|
35
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,YAAY,EACV,KAAK,EACL,SAAS,EACT,iBAAiB,EACjB,WAAW,EACX,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,SAAS,EACT,aAAa,EACb,MAAM,EACN,QAAQ,EACR,iBAAiB,EACjB,UAAU,EACV,SAAS,EACT,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,OAAO,EACP,UAAU,EACV,WAAW,EACX,mBAAmB,GACpB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,WAAW,EACX,UAAU,GACX,MAAM,YAAY,CAAC;AAIpB,YAAY,EACV,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,YAAY,GACb,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAIzD,YAAY,EACV,iBAAiB,EACjB,gBAAgB,EAChB,QAAQ,EACR,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAO9B,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpG,YAAY,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAC9E,YAAY,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAIlE,YAAY,EAAE,aAAa,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AASpE,YAAY,EAAE,+BAA+B,EAAE,MAAM,qDAAqD,CAAC;AAC3G,OAAO,EACL,cAAc,EACd,2BAA2B,EAC3B,YAAY,GACb,MAAM,qDAAqD,CAAC;AAC7D,YAAY,EACV,gBAAgB,EAChB,qCAAqC,EACrC,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,yCAAyC,CAAC;AACjD,YAAY,EAAE,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AACxF,OAAO,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAC/E,OAAO,EAAE,mBAAmB,EAAE,MAAM,4CAA4C,CAAC;AAOjF,YAAY,EACV,gBAAgB,EAChB,kBAAkB,EAClB,UAAU,GACX,MAAM,yCAAyC,CAAC;AACjD,YAAY,EAAE,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AACxF,OAAO,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAQ/E,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAIzC,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ghagga-forge — Public API
|
|
3
|
+
*
|
|
4
|
+
* Forge-agnostic ports and canonical domain types. The core review engine and
|
|
5
|
+
* the server talk to these abstractions instead of any concrete forge SDK.
|
|
6
|
+
*
|
|
7
|
+
* Architecture invariants (see individual files for detail):
|
|
8
|
+
* - R-AGNOSTIC: `ghagga-core` MUST NOT import this package (value OR type).
|
|
9
|
+
* This package MAY import core in TYPE position only.
|
|
10
|
+
* - R-PROJECTION / R-COMMENTID / R-CAPABILITY / R-RESOLVE / R-GRAPH are pinned
|
|
11
|
+
* by the types and the test suite.
|
|
12
|
+
*/
|
|
13
|
+
export { ACTOR_KIND, AUTHOR_ASSOCIATION, CHANGE_KIND, FORGE_KIND, } from './types.js';
|
|
14
|
+
export { REACTION_KIND } from './ports/forge-adapter.js';
|
|
15
|
+
// ─── Ports: credentials & webhooks ──────────────────────────────
|
|
16
|
+
// ─── Errors ─────────────────────────────────────────────────────
|
|
17
|
+
// ForgeAuthError + isForgeAuthError: the typed 401/403 signal the worker catches
|
|
18
|
+
// to drive the in-job credential re-mint + retry (P2 401-recovery seam).
|
|
19
|
+
export { FORGE_AUTH_STATUSES, ForgeAuthError, getErrorStatus, isForgeAuthError } from './errors.js';
|
|
20
|
+
export { MapForgeRegistry, UnknownForgeError } from './registry.js';
|
|
21
|
+
export { BUDGET_SECONDS, GitHubAppCredentialProvider, SKEW_SECONDS, } from './adapters/github/github-app-credential-provider.js';
|
|
22
|
+
export { GitHubForgeAdapter } from './adapters/github/github-forge-adapter.js';
|
|
23
|
+
export { StaticTokenProvider } from './adapters/github/static-token-provider.js';
|
|
24
|
+
export { GitLabForgeAdapter } from './adapters/gitlab/gitlab-forge-adapter.js';
|
|
25
|
+
// ─── Identity helpers ───────────────────────────────────────────
|
|
26
|
+
// ─── Comment-id boxing (R-COMMENTID) ────────────────────────────
|
|
27
|
+
// Sanctioned, side-effect-free boxing helper reusable by both the server
|
|
28
|
+
// worker and the P3 CLI (the adapter still RETURNS native numbers; this is the
|
|
29
|
+
// caller-local boxing step, NOT a branding of the adapter return type).
|
|
30
|
+
export { githubCommentId, gitlabCommentId } from './comment-id.js';
|
|
31
|
+
export { repoRefEquals } from './ref.js';
|
|
32
|
+
// ─── Projection helpers ─────────────────────────────────────────
|
|
33
|
+
export { toCommitMessages, toFileList, toReviewContext } from './project.js';
|
|
34
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA2BH,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,WAAW,EACX,UAAU,GACX,MAAM,YAAY,CAAC;AAcpB,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAWzD,mEAAmE;AAEnE,mEAAmE;AACnE,iFAAiF;AACjF,yEAAyE;AACzE,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAOpG,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAUpE,OAAO,EACL,cAAc,EACd,2BAA2B,EAC3B,YAAY,GACb,MAAM,qDAAqD,CAAC;AAQ7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAC/E,OAAO,EAAE,mBAAmB,EAAE,MAAM,4CAA4C,CAAC;AAajF,OAAO,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAE/E,mEAAmE;AAEnE,mEAAmE;AACnE,yEAAyE;AACzE,+EAA+E;AAC/E,wEAAwE;AACxE,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,mEAAmE;AAEnE,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forge-internal boundary checker (task 0.8).
|
|
3
|
+
*
|
|
4
|
+
* Enforces the R-AGNOSTIC import rules that Biome 2.5 cannot express on its own,
|
|
5
|
+
* specifically the TYPE-vs-VALUE distinction for forge→core imports:
|
|
6
|
+
*
|
|
7
|
+
* - `import type { X } from 'ghagga-core'` ✅ allowed (type position)
|
|
8
|
+
* - `import { type X } from 'ghagga-core'` ✅ allowed (all specifiers inline-type)
|
|
9
|
+
* - `import { X } from 'ghagga-core'` ❌ forbidden (value position)
|
|
10
|
+
* - `export { X } from 'ghagga-core'` ❌ forbidden (re-export = value escape)
|
|
11
|
+
* - `import('ghagga-core')` / `require('ghagga-core')` ❌ forbidden (dynamic = value)
|
|
12
|
+
* - `ghagga-core/<subpath>` / `@ghagga/core` ❌ same rules (subpath & scoped alias)
|
|
13
|
+
* - any import of `apps/server` / `ghagga-server` ❌ forbidden outright
|
|
14
|
+
*
|
|
15
|
+
* Biome's `noRestrictedImports` is a blunt path ban — it would reject the LEGAL
|
|
16
|
+
* `import type` forge→core case (false positive). This checker closes that gap
|
|
17
|
+
* so the boundary test (`lint-boundary.test.ts`) can pin both directions.
|
|
18
|
+
*
|
|
19
|
+
* It is deliberately a small, dependency-free scanner over source text rather
|
|
20
|
+
* than a full AST pass: the boundary rules only care about import-statement
|
|
21
|
+
* forms, which are matched reliably with focused regexes. The Biome overrides in
|
|
22
|
+
* `biome.json` cover the forge↛server and core↛forge path bans; this module
|
|
23
|
+
* adds the type-position nuance for forge→core.
|
|
24
|
+
*
|
|
25
|
+
* IMPLEMENTATION LIVES IN A `.mjs` SIBLING (P0 fix F2 hardening):
|
|
26
|
+
* The actual scanner logic is in `lint-boundary.impl.mjs` (plain JS). The
|
|
27
|
+
* lint:boundary RUNNER (`scripts/lint-boundary.mjs`) imports that `.mjs`
|
|
28
|
+
* DIRECTLY, so the gate never imports a `.ts` file at runtime and therefore does
|
|
29
|
+
* NOT depend on Node's experimental TS type-stripping (Node >= 22.18 / unflagged)
|
|
30
|
+
* — eliminating the `ERR_UNKNOWN_FILE_EXTENSION` Node-version fragility. This
|
|
31
|
+
* module re-exports the function with a precise TS type and owns the
|
|
32
|
+
* {@link BoundaryViolation} interface so the tests keep a fully typed import.
|
|
33
|
+
*/
|
|
34
|
+
export type { BoundaryViolation } from './lint-boundary.impl.mjs';
|
|
35
|
+
export { BANNED_CLIENT_FORGE_FNS, checkForgeBoundary, checkServerForgeClientBoundary, } from './lint-boundary.impl.mjs';
|
|
36
|
+
//# sourceMappingURL=lint-boundary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lint-boundary.d.ts","sourceRoot":"","sources":["../src/lint-boundary.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAKH,YAAY,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EACL,uBAAuB,EACvB,kBAAkB,EAClB,8BAA8B,GAC/B,MAAM,0BAA0B,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type surface for the plain-JS boundary checker implementation
|
|
3
|
+
* ({@link lint-boundary.impl.mjs}). Lets `lint-boundary.ts` import the runtime
|
|
4
|
+
* function with a precise type WITHOUT enabling `allowJs` for the package.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** A single boundary violation. */
|
|
8
|
+
export interface BoundaryViolation {
|
|
9
|
+
/** The offending import source/module specifier. */
|
|
10
|
+
module: string;
|
|
11
|
+
/** Why it violates the boundary. */
|
|
12
|
+
reason: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Scan a single source file's text for forge-boundary violations.
|
|
17
|
+
*
|
|
18
|
+
* @param rawSource the file contents to scan.
|
|
19
|
+
* @returns the list of violations (empty when the file is clean).
|
|
20
|
+
*/
|
|
21
|
+
export function checkForgeBoundary(rawSource: string): BoundaryViolation[];
|
|
22
|
+
|
|
23
|
+
/** The 11 @internal client.ts forge-adapter fns banned outside the factory. */
|
|
24
|
+
export const BANNED_CLIENT_FORGE_FNS: readonly string[];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Scan an apps/server source file for direct use of the 11 @internal client.ts
|
|
28
|
+
* forge-adapter fns. Catches NAMED imports, the NAMESPACE-alias member-access
|
|
29
|
+
* bypass, dynamic `import()`/`require()` member access, re-export laundering
|
|
30
|
+
* (`export { ... } from` / `export * from`), and destructuring off a namespace
|
|
31
|
+
* or dynamic-import alias — all the forms Biome's `noRestrictedImports` cannot
|
|
32
|
+
* see.
|
|
33
|
+
*
|
|
34
|
+
* @param filePath the file path (used only in the violation message).
|
|
35
|
+
* @param rawSource the file contents to scan.
|
|
36
|
+
* @returns the list of violations (empty when the file is clean).
|
|
37
|
+
*/
|
|
38
|
+
export function checkServerForgeClientBoundary(
|
|
39
|
+
filePath: string,
|
|
40
|
+
rawSource: string,
|
|
41
|
+
): BoundaryViolation[];
|