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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 JNZader & Gentleman Programming Community
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
# ghagga-forge
|
|
2
|
+
|
|
3
|
+
`ghagga-forge` is GHAGGA's forge-agnostic port layer: the canonical, provider-neutral domain types and abstract ports (forge adapter, CI runner, credential provider, webhook codec, adapter registry) that the core review engine and the server talk to instead of any concrete forge SDK. It keeps GHAGGA decoupled from GitHub, GitLab, or Gitea specifics so the same review pipeline can target any forge. The package depends on `ghagga-core` in TYPE position only, and `ghagga-core` must never depend on it (R-AGNOSTIC).
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHubAppCredentialProvider — the REAL P2 {@link ForgeCredentialProvider} for
|
|
3
|
+
* the GitHub App installation-token flow (SDD forge-agnostic task 2.1).
|
|
4
|
+
*
|
|
5
|
+
* Replaces P1's `TemporaryGitHubTokenSource` (which minted a fresh token on every
|
|
6
|
+
* `getToken()` call, no cache). This provider delivers the R-TOKEN contract:
|
|
7
|
+
*
|
|
8
|
+
* - TTL CACHE: a minted token is reused until `expiresAtMs - SKEW_SECONDS`.
|
|
9
|
+
* - BUDGET VALIDITY: `getToken()` returns a token valid for >= `BUDGET_SECONDS`
|
|
10
|
+
* of REMAINING life. If the cached token would expire within that budget, it
|
|
11
|
+
* is re-minted PROACTIVELY (never "wait for a 401"). This is what makes the
|
|
12
|
+
* ~11-min fetch+dispatch+poll phase reuse one token AND guarantees the
|
|
13
|
+
* postback token outlives the postback.
|
|
14
|
+
* - SINGLEFLIGHT: concurrent `getToken()` calls with no valid cached token
|
|
15
|
+
* share ONE in-flight mint promise (no duplicate concurrent mints).
|
|
16
|
+
* - 401/403 FORCE-REFRESH: {@link GitHubAppCredentialProvider.invalidate}
|
|
17
|
+
* drops the cache so the next `getToken()` re-mints. The caller signals an
|
|
18
|
+
* auth failure (401/403) by calling `invalidate()`.
|
|
19
|
+
* - HARD-FAIL: if the mint POST fails HARD (non-401, e.g. 500/network), the
|
|
20
|
+
* in-flight promise rejects and `getToken()` REJECTS — NO stale/empty token
|
|
21
|
+
* is returned, so the job fails fast.
|
|
22
|
+
*
|
|
23
|
+
* DEPENDENCY INVERSION (R-AGNOSTIC): like the adapter, this class does NOT import
|
|
24
|
+
* `apps/server`. The real expiry-carrying mint is injected as
|
|
25
|
+
* {@link GitHubInstallationTokenMintWithExpiry}; the composition root in
|
|
26
|
+
* apps/server adapts `client.getInstallationToken` to also surface `expires_at`.
|
|
27
|
+
*
|
|
28
|
+
* DETERMINISTIC CLOCK: time is read through an injected `now()` (defaults to
|
|
29
|
+
* {@link Date.now}). `Date.now` is not otherwise controllable in tests; injecting
|
|
30
|
+
* it lets the fake-clock tests (task 2.4) drive TTL/budget/expiry decisions
|
|
31
|
+
* deterministically.
|
|
32
|
+
*/
|
|
33
|
+
import type { ForgeCredentialProvider } from '../../ports/credential-provider.js';
|
|
34
|
+
import type { GitHubInstallationTokenMintWithExpiry } from './github-client-port.js';
|
|
35
|
+
/**
|
|
36
|
+
* Early-refresh margin (seconds). The cached token is treated as expired this
|
|
37
|
+
* many seconds BEFORE its real `expiresAtMs`, absorbing clock skew between this
|
|
38
|
+
* process and GitHub so a token never gets USED in the moments around its true
|
|
39
|
+
* expiry. (Design Open Question resolved: a fixed value is enough for P1–P4.)
|
|
40
|
+
*/
|
|
41
|
+
export declare const SKEW_SECONDS = 60;
|
|
42
|
+
/**
|
|
43
|
+
* Required remaining validity (seconds). `getToken()` guarantees the returned
|
|
44
|
+
* token has at least this much life left; otherwise it re-mints PROACTIVELY.
|
|
45
|
+
* This is the "token must OUTLIVE the postback" budget — sized so a token handed
|
|
46
|
+
* out just before the comment postback cannot expire mid-postback.
|
|
47
|
+
*/
|
|
48
|
+
export declare const BUDGET_SECONDS = 120;
|
|
49
|
+
/** Construction options for {@link GitHubAppCredentialProvider}. */
|
|
50
|
+
export interface GitHubAppCredentialProviderDeps {
|
|
51
|
+
/**
|
|
52
|
+
* Injected expiry-carrying installation-token mint. Real impl = the apps/server
|
|
53
|
+
* composition root adapting `client.getInstallationToken` to surface
|
|
54
|
+
* `expires_at`. MUST reject on a HARD failure (non-401) — the provider relies
|
|
55
|
+
* on that rejection to fail fast.
|
|
56
|
+
*/
|
|
57
|
+
mint: GitHubInstallationTokenMintWithExpiry;
|
|
58
|
+
/** GitHub App installation id. */
|
|
59
|
+
installationId: number;
|
|
60
|
+
/** GitHub App id. */
|
|
61
|
+
appId: string;
|
|
62
|
+
/** GitHub App private key (PEM). */
|
|
63
|
+
privateKey: string;
|
|
64
|
+
/** Optional repository-id scoping for the minted token. */
|
|
65
|
+
repositoryIds?: number[];
|
|
66
|
+
/**
|
|
67
|
+
* Injected clock — epoch millis. Defaults to {@link Date.now}. Override in
|
|
68
|
+
* tests to make TTL/budget/expiry decisions deterministic.
|
|
69
|
+
*/
|
|
70
|
+
now?: () => number;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* TTL-cached, single-flight, budget-valid GitHub installation-token provider.
|
|
74
|
+
*
|
|
75
|
+
* @see {@link ForgeCredentialProvider} — the port it implements.
|
|
76
|
+
*/
|
|
77
|
+
export declare class GitHubAppCredentialProvider implements ForgeCredentialProvider {
|
|
78
|
+
#private;
|
|
79
|
+
constructor(deps: GitHubAppCredentialProviderDeps);
|
|
80
|
+
/**
|
|
81
|
+
* Resolve a budget-valid installation token.
|
|
82
|
+
*
|
|
83
|
+
* Returns the cached token when it still has >= `BUDGET_SECONDS` of life
|
|
84
|
+
* (minus `SKEW_SECONDS` margin). Otherwise mints a fresh one — coalescing
|
|
85
|
+
* concurrent callers onto a single in-flight mint. Rejects (no stale token) if
|
|
86
|
+
* the mint fails hard.
|
|
87
|
+
*/
|
|
88
|
+
getToken(): Promise<string>;
|
|
89
|
+
/**
|
|
90
|
+
* Drop the cached token so the NEXT `getToken()` re-mints. The caller invokes
|
|
91
|
+
* this on a 401/403 auth failure (the token was revoked/rotated server-side
|
|
92
|
+
* before its advertised expiry).
|
|
93
|
+
*
|
|
94
|
+
* FIX 4 (in-flight fence): a mint that was ALREADY in flight when this is
|
|
95
|
+
* called will still RESOLVE (its awaiter gets that token), but it will NOT be
|
|
96
|
+
* re-cached — it belongs to a pre-invalidation generation and could be the very
|
|
97
|
+
* token that was just revoked. The next fresh `getToken()` therefore mints
|
|
98
|
+
* anew rather than reusing a possibly-revoked in-flight result.
|
|
99
|
+
*/
|
|
100
|
+
invalidate(): void;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=github-app-credential-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-app-credential-provider.d.ts","sourceRoot":"","sources":["../../../src/adapters/github/github-app-credential-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,oCAAoC,CAAC;AAClF,OAAO,KAAK,EACV,qCAAqC,EAEtC,MAAM,yBAAyB,CAAC;AAEjC;;;;;GAKG;AACH,eAAO,MAAM,YAAY,KAAK,CAAC;AAE/B;;;;;GAKG;AACH,eAAO,MAAM,cAAc,MAAM,CAAC;AAElC,oEAAoE;AACpE,MAAM,WAAW,+BAA+B;IAC9C;;;;;OAKG;IACH,IAAI,EAAE,qCAAqC,CAAC;IAC5C,kCAAkC;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,qBAAa,2BAA4B,YAAW,uBAAuB;;gBA4B7D,IAAI,EAAE,+BAA+B;IASjD;;;;;;;OAOG;IACG,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IAoDjC;;;;;;;;;;OAUG;IACH,UAAU,IAAI,IAAI;CAoBnB"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHubAppCredentialProvider — the REAL P2 {@link ForgeCredentialProvider} for
|
|
3
|
+
* the GitHub App installation-token flow (SDD forge-agnostic task 2.1).
|
|
4
|
+
*
|
|
5
|
+
* Replaces P1's `TemporaryGitHubTokenSource` (which minted a fresh token on every
|
|
6
|
+
* `getToken()` call, no cache). This provider delivers the R-TOKEN contract:
|
|
7
|
+
*
|
|
8
|
+
* - TTL CACHE: a minted token is reused until `expiresAtMs - SKEW_SECONDS`.
|
|
9
|
+
* - BUDGET VALIDITY: `getToken()` returns a token valid for >= `BUDGET_SECONDS`
|
|
10
|
+
* of REMAINING life. If the cached token would expire within that budget, it
|
|
11
|
+
* is re-minted PROACTIVELY (never "wait for a 401"). This is what makes the
|
|
12
|
+
* ~11-min fetch+dispatch+poll phase reuse one token AND guarantees the
|
|
13
|
+
* postback token outlives the postback.
|
|
14
|
+
* - SINGLEFLIGHT: concurrent `getToken()` calls with no valid cached token
|
|
15
|
+
* share ONE in-flight mint promise (no duplicate concurrent mints).
|
|
16
|
+
* - 401/403 FORCE-REFRESH: {@link GitHubAppCredentialProvider.invalidate}
|
|
17
|
+
* drops the cache so the next `getToken()` re-mints. The caller signals an
|
|
18
|
+
* auth failure (401/403) by calling `invalidate()`.
|
|
19
|
+
* - HARD-FAIL: if the mint POST fails HARD (non-401, e.g. 500/network), the
|
|
20
|
+
* in-flight promise rejects and `getToken()` REJECTS — NO stale/empty token
|
|
21
|
+
* is returned, so the job fails fast.
|
|
22
|
+
*
|
|
23
|
+
* DEPENDENCY INVERSION (R-AGNOSTIC): like the adapter, this class does NOT import
|
|
24
|
+
* `apps/server`. The real expiry-carrying mint is injected as
|
|
25
|
+
* {@link GitHubInstallationTokenMintWithExpiry}; the composition root in
|
|
26
|
+
* apps/server adapts `client.getInstallationToken` to also surface `expires_at`.
|
|
27
|
+
*
|
|
28
|
+
* DETERMINISTIC CLOCK: time is read through an injected `now()` (defaults to
|
|
29
|
+
* {@link Date.now}). `Date.now` is not otherwise controllable in tests; injecting
|
|
30
|
+
* it lets the fake-clock tests (task 2.4) drive TTL/budget/expiry decisions
|
|
31
|
+
* deterministically.
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* Early-refresh margin (seconds). The cached token is treated as expired this
|
|
35
|
+
* many seconds BEFORE its real `expiresAtMs`, absorbing clock skew between this
|
|
36
|
+
* process and GitHub so a token never gets USED in the moments around its true
|
|
37
|
+
* expiry. (Design Open Question resolved: a fixed value is enough for P1–P4.)
|
|
38
|
+
*/
|
|
39
|
+
export const SKEW_SECONDS = 60;
|
|
40
|
+
/**
|
|
41
|
+
* Required remaining validity (seconds). `getToken()` guarantees the returned
|
|
42
|
+
* token has at least this much life left; otherwise it re-mints PROACTIVELY.
|
|
43
|
+
* This is the "token must OUTLIVE the postback" budget — sized so a token handed
|
|
44
|
+
* out just before the comment postback cannot expire mid-postback.
|
|
45
|
+
*/
|
|
46
|
+
export const BUDGET_SECONDS = 120;
|
|
47
|
+
/**
|
|
48
|
+
* TTL-cached, single-flight, budget-valid GitHub installation-token provider.
|
|
49
|
+
*
|
|
50
|
+
* @see {@link ForgeCredentialProvider} — the port it implements.
|
|
51
|
+
*/
|
|
52
|
+
export class GitHubAppCredentialProvider {
|
|
53
|
+
#mint;
|
|
54
|
+
#installationId;
|
|
55
|
+
#appId;
|
|
56
|
+
#privateKey;
|
|
57
|
+
#repositoryIds;
|
|
58
|
+
#now;
|
|
59
|
+
/** The last successfully minted token + its expiry, or null when uncached. */
|
|
60
|
+
#cached = null;
|
|
61
|
+
/**
|
|
62
|
+
* The in-flight mint promise, or null when no mint is running. SINGLEFLIGHT:
|
|
63
|
+
* concurrent callers that find no valid cached token await THIS shared promise
|
|
64
|
+
* instead of each starting their own mint.
|
|
65
|
+
*/
|
|
66
|
+
#inFlight = null;
|
|
67
|
+
/**
|
|
68
|
+
* Monotonic generation counter, bumped on every {@link invalidate}. FIX 4
|
|
69
|
+
* (in-flight fence): a mint that STARTED before an `invalidate()` must NOT
|
|
70
|
+
* repopulate the cache when it resolves afterwards — its token belongs to a
|
|
71
|
+
* pre-invalidation generation (it may be the very token that just got revoked).
|
|
72
|
+
* Each `getToken` snapshots the generation at mint-start; on resolution it
|
|
73
|
+
* caches ONLY if the snapshot still matches the current generation.
|
|
74
|
+
*/
|
|
75
|
+
#generation = 0;
|
|
76
|
+
constructor(deps) {
|
|
77
|
+
this.#mint = deps.mint;
|
|
78
|
+
this.#installationId = deps.installationId;
|
|
79
|
+
this.#appId = deps.appId;
|
|
80
|
+
this.#privateKey = deps.privateKey;
|
|
81
|
+
this.#repositoryIds = deps.repositoryIds;
|
|
82
|
+
this.#now = deps.now ?? Date.now;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Resolve a budget-valid installation token.
|
|
86
|
+
*
|
|
87
|
+
* Returns the cached token when it still has >= `BUDGET_SECONDS` of life
|
|
88
|
+
* (minus `SKEW_SECONDS` margin). Otherwise mints a fresh one — coalescing
|
|
89
|
+
* concurrent callers onto a single in-flight mint. Rejects (no stale token) if
|
|
90
|
+
* the mint fails hard.
|
|
91
|
+
*/
|
|
92
|
+
async getToken() {
|
|
93
|
+
const cached = this.#cached;
|
|
94
|
+
if (cached !== null && this.#isUsable(cached)) {
|
|
95
|
+
return cached.token;
|
|
96
|
+
}
|
|
97
|
+
// SINGLEFLIGHT: if a mint is already running, await it instead of starting
|
|
98
|
+
// a second one. All concurrent callers share the same promise + result.
|
|
99
|
+
if (this.#inFlight !== null) {
|
|
100
|
+
const minted = await this.#inFlight;
|
|
101
|
+
return minted.token;
|
|
102
|
+
}
|
|
103
|
+
// FIX 4: snapshot the generation at mint-START. If invalidate() bumps the
|
|
104
|
+
// generation while this mint is in flight, the resolution below will see a
|
|
105
|
+
// mismatch and DISCARD the result (a token from a pre-invalidation generation
|
|
106
|
+
// must not be re-cached). The token is still RETURNED to this caller (it is a
|
|
107
|
+
// fresh acquisition for them); it just isn't promoted to the shared cache.
|
|
108
|
+
const startGeneration = this.#generation;
|
|
109
|
+
const inFlight = this.#mint(this.#installationId, this.#appId, this.#privateKey, this.#repositoryIds && this.#repositoryIds.length > 0
|
|
110
|
+
? { repositoryIds: this.#repositoryIds }
|
|
111
|
+
: undefined);
|
|
112
|
+
this.#inFlight = inFlight;
|
|
113
|
+
try {
|
|
114
|
+
const minted = await inFlight;
|
|
115
|
+
// HARD-FAIL safety: only cache on success. On rejection we fall through to
|
|
116
|
+
// the finally + the throw below — the cache is NEVER populated with a
|
|
117
|
+
// stale/empty token, so the job fails fast.
|
|
118
|
+
//
|
|
119
|
+
// FIX 4 fence: only cache if no invalidate() happened during this mint.
|
|
120
|
+
// Otherwise discard (do not re-cache) — but still return the token to the
|
|
121
|
+
// caller who awaited this specific mint.
|
|
122
|
+
if (this.#generation === startGeneration) {
|
|
123
|
+
this.#cached = minted;
|
|
124
|
+
}
|
|
125
|
+
return minted.token;
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
// Clear the in-flight slot whether the mint resolved or rejected, so a
|
|
129
|
+
// failed mint does not wedge every future caller onto a rejected promise.
|
|
130
|
+
if (this.#inFlight === inFlight) {
|
|
131
|
+
this.#inFlight = null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Drop the cached token so the NEXT `getToken()` re-mints. The caller invokes
|
|
137
|
+
* this on a 401/403 auth failure (the token was revoked/rotated server-side
|
|
138
|
+
* before its advertised expiry).
|
|
139
|
+
*
|
|
140
|
+
* FIX 4 (in-flight fence): a mint that was ALREADY in flight when this is
|
|
141
|
+
* called will still RESOLVE (its awaiter gets that token), but it will NOT be
|
|
142
|
+
* re-cached — it belongs to a pre-invalidation generation and could be the very
|
|
143
|
+
* token that was just revoked. The next fresh `getToken()` therefore mints
|
|
144
|
+
* anew rather than reusing a possibly-revoked in-flight result.
|
|
145
|
+
*/
|
|
146
|
+
invalidate() {
|
|
147
|
+
this.#cached = null;
|
|
148
|
+
// FIX 4: bump the generation so any mint already IN FLIGHT (started before
|
|
149
|
+
// this call) will NOT repopulate the cache when it resolves — its token
|
|
150
|
+
// belongs to a now-superseded generation (possibly the just-revoked token).
|
|
151
|
+
this.#generation += 1;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* A cached token is usable when its remaining life (with the skew margin
|
|
155
|
+
* applied) still covers the required budget. i.e.
|
|
156
|
+
* expiresAtMs - SKEW*1000 >= now + BUDGET*1000
|
|
157
|
+
* Equivalently: the skew-adjusted expiry is at least `BUDGET_SECONDS` in the
|
|
158
|
+
* future. Otherwise it must be re-minted PROACTIVELY (not via a 401).
|
|
159
|
+
*/
|
|
160
|
+
#isUsable(token) {
|
|
161
|
+
const skewAdjustedExpiry = token.expiresAtMs - SKEW_SECONDS * 1000;
|
|
162
|
+
const budgetDeadline = this.#now() + BUDGET_SECONDS * 1000;
|
|
163
|
+
return skewAdjustedExpiry >= budgetDeadline;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=github-app-credential-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-app-credential-provider.js","sourceRoot":"","sources":["../../../src/adapters/github/github-app-credential-provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub client PORT (dependency inversion seam for the GitHub adapter).
|
|
3
|
+
*
|
|
4
|
+
* R-AGNOSTIC / boundary rule: `packages/forge` MUST NOT import `apps/server`
|
|
5
|
+
* (the boundary lint forbids it). The concrete GitHub HTTP client lives in
|
|
6
|
+
* `apps/server/src/github/client.ts`. To wrap it WITHOUT importing it, the
|
|
7
|
+
* adapter declares — here — the exact function-set it depends on, and the caller
|
|
8
|
+
* (review.ts, task 1.4) injects the real `client.ts` functions at construction.
|
|
9
|
+
*
|
|
10
|
+
* This interface mirrors the SIGNATURES of the client.ts functions the adapter
|
|
11
|
+
* folds over (owner/repo/token positional style). It is intentionally
|
|
12
|
+
* GitHub-native (numeric ids, GitHub-shaped returns): the adapter is the layer
|
|
13
|
+
* that maps GitHub-native shapes ↔ canonical forge types. Comment ids cross THIS
|
|
14
|
+
* seam as PLAIN numbers (GitHub-native) — boxing into `CommentId` happens
|
|
15
|
+
* review.ts-LOCAL in 1.4, not here.
|
|
16
|
+
*
|
|
17
|
+
* Graph return types are kept structural (`unknown`-validated downstream is done
|
|
18
|
+
* inside client.ts itself) so this port does NOT need a value/type import of
|
|
19
|
+
* core; client.ts already returns the validated `DependencyGraph | null` /
|
|
20
|
+
* `GraphMetadata | null`. We re-state those as type-only imports from core,
|
|
21
|
+
* which the boundary lint explicitly permits (`import type` from core).
|
|
22
|
+
*/
|
|
23
|
+
import type { DependencyGraph, GraphMetadata } from 'ghagga-core';
|
|
24
|
+
/** Reaction emojis the underlying GitHub client accepts. */
|
|
25
|
+
export type GitHubReactionContent = '+1' | '-1' | 'laugh' | 'confused' | 'heart' | 'hooray' | 'rocket' | 'eyes';
|
|
26
|
+
/**
|
|
27
|
+
* The set of GitHub client functions the {@link GitHubForgeAdapter} depends on.
|
|
28
|
+
*
|
|
29
|
+
* Each member matches the corresponding `apps/server/src/github/client.ts`
|
|
30
|
+
* export by signature. The adapter receives an implementation of this port via
|
|
31
|
+
* its constructor (dependency inversion) so the forge package never imports the
|
|
32
|
+
* server.
|
|
33
|
+
*/
|
|
34
|
+
export interface GitHubClientPort {
|
|
35
|
+
/** Fetch the unified diff text for a PR. */
|
|
36
|
+
fetchPRDiff(owner: string, repo: string, prNumber: number, token: string): Promise<string>;
|
|
37
|
+
/** Fetch PR details (head SHA, base branch, author login). */
|
|
38
|
+
fetchPRDetails(owner: string, repo: string, prNumber: number, token: string): Promise<{
|
|
39
|
+
headSha: string;
|
|
40
|
+
baseBranch: string;
|
|
41
|
+
prAuthor: string;
|
|
42
|
+
}>;
|
|
43
|
+
/** Fetch the list of changed file paths for a PR. */
|
|
44
|
+
getPRFileList(owner: string, repo: string, prNumber: number, token: string): Promise<string[]>;
|
|
45
|
+
/** Fetch commit messages for a PR. */
|
|
46
|
+
getPRCommitMessages(owner: string, repo: string, prNumber: number, token: string): Promise<string[]>;
|
|
47
|
+
/** Post a fresh comment; returns the GitHub-native numeric id. */
|
|
48
|
+
postComment(owner: string, repo: string, prNumber: number, body: string, token: string): Promise<{
|
|
49
|
+
id: number;
|
|
50
|
+
}>;
|
|
51
|
+
/** Find existing GHAGGA comments; returns latest + stale numeric ids, or null. */
|
|
52
|
+
findExistingComment(owner: string, repo: string, prNumber: number, token: string): Promise<{
|
|
53
|
+
latestId: number;
|
|
54
|
+
staleIds: number[];
|
|
55
|
+
} | null>;
|
|
56
|
+
/** Delete a comment by its GitHub-native numeric id. */
|
|
57
|
+
deleteComment(owner: string, repo: string, commentId: number, token: string): Promise<void>;
|
|
58
|
+
/** Update a comment in place by its GitHub-native numeric id. */
|
|
59
|
+
updateComment(owner: string, repo: string, commentId: number, body: string, token: string): Promise<void>;
|
|
60
|
+
/** Add a reaction to an issue comment (best-effort in client.ts). */
|
|
61
|
+
addCommentReaction(owner: string, repo: string, commentId: number, reaction: GitHubReactionContent, token: string): Promise<void>;
|
|
62
|
+
/** Read the dependency graph from the `ghagga/graph` orphan branch, or null. */
|
|
63
|
+
fetchGraphFromBranch(owner: string, repo: string, token: string): Promise<DependencyGraph | null>;
|
|
64
|
+
/** Read graph metadata from the `ghagga/graph` orphan branch, or null. */
|
|
65
|
+
fetchGraphMetadata(owner: string, repo: string, token: string): Promise<GraphMetadata | null>;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* A minted installation token plus its absolute expiry.
|
|
69
|
+
*
|
|
70
|
+
* `expiresAtMs` is the token's expiry as an epoch-millis timestamp (comparable to
|
|
71
|
+
* `Date.now()`), derived by the composition root from the GitHub access-token
|
|
72
|
+
* response `expires_at` (an ISO-8601 string the REST API already returns).
|
|
73
|
+
*/
|
|
74
|
+
export interface MintedInstallationToken {
|
|
75
|
+
/** The installation access token string. */
|
|
76
|
+
token: string;
|
|
77
|
+
/** Absolute expiry, epoch millis (comparable to {@link Date.now}). */
|
|
78
|
+
expiresAtMs: number;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Mints a GitHub installation access token AND reports its expiry (P2).
|
|
82
|
+
*
|
|
83
|
+
* This is the expiry-carrying mint the {@link GitHubAppCredentialProvider} TTL
|
|
84
|
+
* cache requires. The underlying `client.getInstallationToken` returns ONLY the
|
|
85
|
+
* token string today (it discards the GitHub `expires_at`); the composition root
|
|
86
|
+
* (apps/server) adapts it to ALSO surface the expiry so the forge package can
|
|
87
|
+
* cache without importing apps/server (dependency inversion, R-AGNOSTIC).
|
|
88
|
+
*/
|
|
89
|
+
export type GitHubInstallationTokenMintWithExpiry = (installationId: number, appId: string, privateKey: string, options?: {
|
|
90
|
+
repositoryIds?: number[];
|
|
91
|
+
}) => Promise<MintedInstallationToken>;
|
|
92
|
+
//# sourceMappingURL=github-client-port.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-client-port.d.ts","sourceRoot":"","sources":["../../../src/adapters/github/github-client-port.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub client PORT (dependency inversion seam for the GitHub adapter).
|
|
3
|
+
*
|
|
4
|
+
* R-AGNOSTIC / boundary rule: `packages/forge` MUST NOT import `apps/server`
|
|
5
|
+
* (the boundary lint forbids it). The concrete GitHub HTTP client lives in
|
|
6
|
+
* `apps/server/src/github/client.ts`. To wrap it WITHOUT importing it, the
|
|
7
|
+
* adapter declares — here — the exact function-set it depends on, and the caller
|
|
8
|
+
* (review.ts, task 1.4) injects the real `client.ts` functions at construction.
|
|
9
|
+
*
|
|
10
|
+
* This interface mirrors the SIGNATURES of the client.ts functions the adapter
|
|
11
|
+
* folds over (owner/repo/token positional style). It is intentionally
|
|
12
|
+
* GitHub-native (numeric ids, GitHub-shaped returns): the adapter is the layer
|
|
13
|
+
* that maps GitHub-native shapes ↔ canonical forge types. Comment ids cross THIS
|
|
14
|
+
* seam as PLAIN numbers (GitHub-native) — boxing into `CommentId` happens
|
|
15
|
+
* review.ts-LOCAL in 1.4, not here.
|
|
16
|
+
*
|
|
17
|
+
* Graph return types are kept structural (`unknown`-validated downstream is done
|
|
18
|
+
* inside client.ts itself) so this port does NOT need a value/type import of
|
|
19
|
+
* core; client.ts already returns the validated `DependencyGraph | null` /
|
|
20
|
+
* `GraphMetadata | null`. We re-state those as type-only imports from core,
|
|
21
|
+
* which the boundary lint explicitly permits (`import type` from core).
|
|
22
|
+
*/
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=github-client-port.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-client-port.js","sourceRoot":"","sources":["../../../src/adapters/github/github-client-port.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHubForgeAdapter — wraps the GitHub HTTP client behind the forge-agnostic
|
|
3
|
+
* {@link ForgeAdapterBase} + {@link ReactionCapable} + {@link GraphReadCapable}
|
|
4
|
+
* surfaces.
|
|
5
|
+
*
|
|
6
|
+
* DEPENDENCY INVERSION (boundary rule R-AGNOSTIC):
|
|
7
|
+
* `packages/forge` MUST NOT import `apps/server`. The concrete GitHub client
|
|
8
|
+
* lives in `apps/server/src/github/client.ts`. So this adapter NEVER imports
|
|
9
|
+
* that client directly — it depends on the injected {@link GitHubClientPort}
|
|
10
|
+
* (declared inside the forge package). Task 1.4 (review.ts) constructs the
|
|
11
|
+
* adapter passing the real `client.ts` functions as the port implementation:
|
|
12
|
+
*
|
|
13
|
+
* new GitHubForgeAdapter({ client, token, owner, repo })
|
|
14
|
+
*
|
|
15
|
+
* CAPABILITIES: reactions ✅, graphRead ✅ (both methods co-present),
|
|
16
|
+
* inlineComments ❌ (GitHub inline review is deferred — no `publishInline`).
|
|
17
|
+
* The `capabilities` field is a HINT only (R-CAPABILITY): callers guard optional
|
|
18
|
+
* methods by method-presence, never by these flags.
|
|
19
|
+
*
|
|
20
|
+
* COMMENT IDs: this adapter returns PLAIN GitHub-native numeric ids (number /
|
|
21
|
+
* number[]). Boxing into {@link CommentId} happens caller-LOCAL in review.ts.
|
|
22
|
+
* The adapter accepts a boxed {@link CommentId} in `addReaction` (port contract)
|
|
23
|
+
* and unwraps `raw` to the GitHub-native number internally.
|
|
24
|
+
*/
|
|
25
|
+
import type { DependencyGraph, GraphMetadata } from 'ghagga-core';
|
|
26
|
+
import type { ForgeAdapterBase, GraphReadCapable, ReactionCapable, ReactionKind } from '../../ports/forge-adapter.js';
|
|
27
|
+
import { REACTION_KIND } from '../../ports/forge-adapter.js';
|
|
28
|
+
import type { ChangedFile, ChangeRequest, ChangeRequestRef, CommentId, CommentMarker, Commit, ForgeCapabilities, RepoRef, UnifiedDiff, UpsertSummaryResult } from '../../types.js';
|
|
29
|
+
import type { GitHubClientPort } from './github-client-port.js';
|
|
30
|
+
/** Construction options for {@link GitHubForgeAdapter}. */
|
|
31
|
+
export interface GitHubForgeAdapterDeps {
|
|
32
|
+
/** Injected GitHub client function-set (real impl = apps/server client.ts). */
|
|
33
|
+
client: GitHubClientPort;
|
|
34
|
+
/** Installation access token used for all calls. */
|
|
35
|
+
token: string;
|
|
36
|
+
/** Repository owner (login). */
|
|
37
|
+
owner: string;
|
|
38
|
+
/** Repository name. */
|
|
39
|
+
repo: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* GitHub implementation of the forge adapter.
|
|
43
|
+
*
|
|
44
|
+
* Implements the mandatory base plus the reaction and graph-read capabilities.
|
|
45
|
+
* Deliberately does NOT implement {@link InlineCapable} (GitHub inline review
|
|
46
|
+
* deferred), so `publishInline` is ABSENT — callers guarding by method-presence
|
|
47
|
+
* will skip inline publishing cleanly.
|
|
48
|
+
*/
|
|
49
|
+
export declare class GitHubForgeAdapter implements ForgeAdapterBase, ReactionCapable, GraphReadCapable {
|
|
50
|
+
#private;
|
|
51
|
+
readonly capabilities: ForgeCapabilities;
|
|
52
|
+
constructor(deps: GitHubForgeAdapterDeps);
|
|
53
|
+
fetchDiff(ref: ChangeRequestRef): Promise<UnifiedDiff>;
|
|
54
|
+
fetchChangeRequest(ref: ChangeRequestRef): Promise<ChangeRequest>;
|
|
55
|
+
fetchFileList(ref: ChangeRequestRef): Promise<ChangedFile[]>;
|
|
56
|
+
fetchCommits(ref: ChangeRequestRef): Promise<Commit[]>;
|
|
57
|
+
/**
|
|
58
|
+
* Idempotently upsert the single GHAGGA summary comment.
|
|
59
|
+
*
|
|
60
|
+
* Behavior (preserves the review.ts baseline exactly):
|
|
61
|
+
* 1. find existing GHAGGA comments (latest + stale).
|
|
62
|
+
* 2. delete ALL of them in the order `[latestId, ...staleIds]` — BEST-EFFORT:
|
|
63
|
+
* each delete is wrapped in try/catch so BOTH 404 (already gone) AND any
|
|
64
|
+
* non-404 failure are tolerated and do NOT block the repost. Only ids that
|
|
65
|
+
* actually deleted (no throw) are reported in `deleted`.
|
|
66
|
+
* 3. post a FRESH comment at the bottom — this is the ONLY failure that
|
|
67
|
+
* propagates (a failed post means no summary exists, which is fatal).
|
|
68
|
+
*
|
|
69
|
+
* Returns GitHub-native numeric ids (boxing happens caller-local).
|
|
70
|
+
*
|
|
71
|
+
* NOTE: `marker` is accepted for the port contract but is NOT threaded into
|
|
72
|
+
* the client. The GitHub adapter currently matches the FIXED
|
|
73
|
+
* `REVIEW_COMMENT_MARKER` (`<!-- ghagga-review -->`) hard-coded inside
|
|
74
|
+
* client.findExistingComment (a "stale ghagga comment" = any comment whose body
|
|
75
|
+
* contains that marker; author/bot status is NOT inspected). This is
|
|
76
|
+
* baseline-faithful: threading `marker` into
|
|
77
|
+
* GitHubClientPort.findExistingComment is DEFERRED (would change the port
|
|
78
|
+
* signature). Consequently, if review.ts (1.4) passes a `marker`, it MUST equal
|
|
79
|
+
* the existing `REVIEW_COMMENT_MARKER` — any other value is silently ignored.
|
|
80
|
+
*/
|
|
81
|
+
upsertSummaryComment(ref: ChangeRequestRef, body: string, _marker: CommentMarker): Promise<UpsertSummaryResult>;
|
|
82
|
+
/**
|
|
83
|
+
* Add a reaction to a comment (R-5).
|
|
84
|
+
*
|
|
85
|
+
* CRITICAL: the trigger-comment reaction MUST live in the adapter — it was
|
|
86
|
+
* accidentally dropped once before. It is preserved here, wrapping
|
|
87
|
+
* client.addCommentReaction (which is itself best-effort / non-throwing).
|
|
88
|
+
*/
|
|
89
|
+
addReaction(commentId: CommentId, reaction: ReactionKind): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Read the dependency graph from the `ghagga/graph` orphan branch.
|
|
92
|
+
*
|
|
93
|
+
* The orphan-branch ref (`?ref=ghagga/graph`), the 404→null behavior, and the
|
|
94
|
+
* typed JSON validation all live INSIDE client.fetchGraphFromBranch — this
|
|
95
|
+
* adapter preserves them by delegating, returning `null` for "no graph".
|
|
96
|
+
*/
|
|
97
|
+
fetchGraph(_repo: RepoRef): Promise<DependencyGraph | null>;
|
|
98
|
+
/**
|
|
99
|
+
* Read graph metadata from the `ghagga/graph` orphan branch (orphan-ref +
|
|
100
|
+
* 404→null + validation handled inside client.fetchGraphMetadata).
|
|
101
|
+
*/
|
|
102
|
+
fetchGraphMetadata(_repo: RepoRef): Promise<GraphMetadata | null>;
|
|
103
|
+
}
|
|
104
|
+
export { REACTION_KIND };
|
|
105
|
+
//# sourceMappingURL=github-forge-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-forge-adapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/github/github-forge-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAElE,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,YAAY,EACb,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,KAAK,EACV,WAAW,EACX,aAAa,EACb,gBAAgB,EAChB,SAAS,EACT,aAAa,EACb,MAAM,EACN,iBAAiB,EACjB,OAAO,EACP,WAAW,EACX,mBAAmB,EACpB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,gBAAgB,EAAyB,MAAM,yBAAyB,CAAC;AAEvF,2DAA2D;AAC3D,MAAM,WAAW,sBAAsB;IACrC,+EAA+E;IAC/E,MAAM,EAAE,gBAAgB,CAAC;IACzB,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAC;CACd;AA4BD;;;;;;;GAOG;AACH,qBAAa,kBAAmB,YAAW,gBAAgB,EAAE,eAAe,EAAE,gBAAgB;;IAC5F,QAAQ,CAAC,YAAY,EAAE,iBAAiB,CAItC;gBAOU,IAAI,EAAE,sBAAsB;IAqClC,SAAS,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,WAAW,CAAC;IAOtD,kBAAkB,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,aAAa,CAAC;IAcjE,aAAa,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAY5D,YAAY,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAa5D;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACG,oBAAoB,CACxB,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,mBAAmB,CAAC;IAiD/B;;;;;;OAMG;IACG,WAAW,CAAC,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAc9E;;;;;;OAMG;IACG,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAIjE;;;OAGG;IACG,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;CAGxE;AAGD,OAAO,EAAE,aAAa,EAAE,CAAC"}
|