@dependfix/engine 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/auth-provider.d.mts +82 -0
- package/dist/auth.d.mts +184 -0
- package/dist/auth.mjs +3 -0
- package/dist/auth2.mjs +361 -0
- package/dist/auth2.mjs.map +1 -0
- package/dist/index.d.mts +306 -184
- package/dist/index.mjs +372 -206
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -6
package/README.md
CHANGED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Octokit } from "@octokit/rest";
|
|
2
|
+
//#region src/auth/auth-provider.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* GitHub 认证抽象层。
|
|
5
|
+
*
|
|
6
|
+
* 支持 PAT(classic / fine-grained)与 GitHub App installation token 两种认证路径。
|
|
7
|
+
* 调用方通过 `getOctokit()` 获取已认证的 Octokit 实例;通过 `getGitCredential()`
|
|
8
|
+
* 获取推送/clone 用的 Git 凭据;通过 `getCommitAuthor()` 获取 commit author 信息。
|
|
9
|
+
*
|
|
10
|
+
* `authProvider` 字段用于审计与调试(区分 PAT vs App 路径)。
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* // 推荐:从 fromPat / fromApp 工厂构造
|
|
15
|
+
* const auth = fromPat('ghp_xxxx')
|
|
16
|
+
* const octokit = auth.getOctokit()
|
|
17
|
+
*
|
|
18
|
+
* // git push 凭据
|
|
19
|
+
* const { username, token } = auth.getGitCredential()
|
|
20
|
+
*
|
|
21
|
+
* // commit author(PAT 路径固定;App 路径动态)
|
|
22
|
+
* const { name, email } = auth.getCommitAuthor()
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
interface AuthProvider {
|
|
26
|
+
/** 获取已认证的 Octokit 实例 */
|
|
27
|
+
getOctokit(): Octokit;
|
|
28
|
+
/** 获取推送 / clone 用的 Git 凭据(用户名 + token) */
|
|
29
|
+
getGitCredential(): {
|
|
30
|
+
username: string;
|
|
31
|
+
token: string;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* 获取 commit author 信息。
|
|
35
|
+
*
|
|
36
|
+
* - PAT 路径:固定返回 `{ name: 'dependfix[bot]', email: 'dependfix[bot]@users.noreply.github.com' }`(保持现有行为零变化)
|
|
37
|
+
* - GitHub App 路径:动态生成 `{app_id}+{bot_login}[bot]@users.noreply.github.com`(GitHub App 协议要求)
|
|
38
|
+
*/
|
|
39
|
+
getCommitAuthor(): {
|
|
40
|
+
name: string;
|
|
41
|
+
email: string;
|
|
42
|
+
};
|
|
43
|
+
/** 认证路径标识(用于审计 + 调试) */
|
|
44
|
+
readonly authProvider: 'pat' | 'github-app';
|
|
45
|
+
}
|
|
46
|
+
/** 工厂函数统一选项(与 OctokitClientOptions.retry 字段对齐) */
|
|
47
|
+
interface AuthProviderOptions {
|
|
48
|
+
/** API 限流 / 次要限流指数退避重试策略。默认 maxRetries=3。 */
|
|
49
|
+
retry?: {
|
|
50
|
+
maxRetries?: number;
|
|
51
|
+
baseDelayMs?: number;
|
|
52
|
+
maxBackoffMs?: number;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** PAT 工厂参数(仅 token;retry 在 options 注入) */
|
|
56
|
+
type FromPatParams = string;
|
|
57
|
+
/** GitHub App 工厂参数 */
|
|
58
|
+
interface FromAppParams {
|
|
59
|
+
/** GitHub App ID */
|
|
60
|
+
appId: string;
|
|
61
|
+
/** PEM 格式私钥 */
|
|
62
|
+
privateKey: string;
|
|
63
|
+
/** Installation ID */
|
|
64
|
+
installationId: string;
|
|
65
|
+
/** Bot 用户名(用于 commit author 动态生成;M18.1 commit 4 实施) */
|
|
66
|
+
botLogin?: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* PAT 路径固定 commit author(保持现有行为零变化)。
|
|
70
|
+
*
|
|
71
|
+
* 注:现有 PAT 路径硬编码 `dependfix[bot]@users.noreply.github.com`,虽非真实 bot 身份(字符串约定),
|
|
72
|
+
* 但保持行为不变以确保 PAT 用户无感升级。已知缺陷由 C22 范围之外的后续阶段修复。
|
|
73
|
+
*/
|
|
74
|
+
declare const PAT_COMMIT_AUTHOR: {
|
|
75
|
+
readonly name: "dependfix[bot]";
|
|
76
|
+
readonly email: "dependfix[bot]@users.noreply.github.com";
|
|
77
|
+
};
|
|
78
|
+
/** PAT 路径固定 Git 用户名(用于 git push 时的 Basic Auth) */
|
|
79
|
+
declare const PAT_GIT_USERNAME = "x-access-token";
|
|
80
|
+
//#endregion
|
|
81
|
+
export { PAT_COMMIT_AUTHOR as a, FromPatParams as i, AuthProviderOptions as n, PAT_GIT_USERNAME as o, FromAppParams as r, AuthProvider as t };
|
|
82
|
+
//# sourceMappingURL=auth-provider.d.mts.map
|
package/dist/auth.d.mts
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { a as PAT_COMMIT_AUTHOR, i as FromPatParams, n as AuthProviderOptions, o as PAT_GIT_USERNAME, r as FromAppParams, t as AuthProvider } from "./auth-provider.mjs";
|
|
2
|
+
import { Octokit } from "@octokit/rest";
|
|
3
|
+
//#region src/auth/pat-provider.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* PAT 工厂函数。委托给 `PatAuthProvider` 类。
|
|
6
|
+
*
|
|
7
|
+
* 保留 `fromPat` 命名习惯便于与 `fromApp` 对齐(统一工厂语义)。
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const auth = fromPat('ghp_xxxx')
|
|
12
|
+
* const octokit = auth.getOctokit()
|
|
13
|
+
* const { username, token } = auth.getGitCredential()
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare function fromPat(token: FromPatParams, options?: AuthProviderOptions): AuthProvider;
|
|
17
|
+
/**
|
|
18
|
+
* PAT 认证路径实现。
|
|
19
|
+
*
|
|
20
|
+
* `getOctokit()` 委托给 [`createGitHubClient`](../github/client.ts) 以复用现有 retry policy 与限流重试实现;
|
|
21
|
+
* Octokit 实例缓存以避免重复构造(每次 `new Octokit()` + `hook.wrap()` 都有开销)。
|
|
22
|
+
*
|
|
23
|
+
* 行为与原 `createGitHubClient({ token })` 等价:
|
|
24
|
+
* - `getOctokit()` 返回通过 PAT 认证的 Octokit 实例(含限流重试 hook)
|
|
25
|
+
* - `getGitCredential()` 返回推送用的 Basic Auth(username = `'x-access-token'`)
|
|
26
|
+
* - `getCommitAuthor()` 返回固定 `dependfix[bot]`(PAT 路径用户行为零变化)
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* const auth = new PatAuthProvider('ghp_xxxx', { retry: { maxRetries: 3 } })
|
|
31
|
+
* const octokit = auth.getOctokit()
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare class PatAuthProvider implements AuthProvider {
|
|
35
|
+
readonly authProvider: "pat";
|
|
36
|
+
private readonly token;
|
|
37
|
+
private readonly retry?;
|
|
38
|
+
private cachedOctokit;
|
|
39
|
+
constructor(token: FromPatParams, options?: AuthProviderOptions);
|
|
40
|
+
/**
|
|
41
|
+
* 获取已认证的 Octokit 实例(首次调用构造并缓存)。
|
|
42
|
+
*/
|
|
43
|
+
getOctokit(): Octokit;
|
|
44
|
+
/** 获取推送 / clone 用的 Git 凭据(username = `'x-access-token'`,token = PAT) */
|
|
45
|
+
getGitCredential(): {
|
|
46
|
+
username: string;
|
|
47
|
+
token: string;
|
|
48
|
+
};
|
|
49
|
+
/** 获取 commit author(PAT 路径固定 `dependfix[bot]`) */
|
|
50
|
+
getCommitAuthor(): {
|
|
51
|
+
name: string;
|
|
52
|
+
email: string;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/auth/app-provider.d.ts
|
|
57
|
+
/**
|
|
58
|
+
* GitHub App 工厂函数(**M18.1 commit 4 完整实施**)。
|
|
59
|
+
*
|
|
60
|
+
* 委托给 [`AppAuthProvider`] 类。
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* const auth = fromApp({
|
|
65
|
+
* appId: '123456',
|
|
66
|
+
* privateKey: '-----BEGIN RSA PRIVATE KEY-----\n...',
|
|
67
|
+
* installationId: '7890123',
|
|
68
|
+
* botLogin: 'dependfix-bot[bot]',
|
|
69
|
+
* })
|
|
70
|
+
* const octokit = auth.getOctokit()
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export declare function fromApp(params: FromAppParams, options?: AuthProviderOptions): AuthProvider;
|
|
74
|
+
/**
|
|
75
|
+
* GitHub App 认证路径实现。
|
|
76
|
+
*
|
|
77
|
+
* 使用 [`@octokit/auth-app`](https://github.com/octokit/auth-app.js) 提供:
|
|
78
|
+
* - JWT signing(App 身份认证)
|
|
79
|
+
* - `getInstallationOctokit`(installation token 获取;库内置 59 分钟 LRU TTL 缓存)
|
|
80
|
+
* - 自动 installation token 轮换
|
|
81
|
+
*
|
|
82
|
+
* 注意:
|
|
83
|
+
* - `getGitCredential()` 返回的 `token` 字段为占位符(installation token 不能直接从 AppAuthProvider 获取;
|
|
84
|
+
* 由 [`@octokit/auth-app`](https://github.com/octokit/auth-app.js) 内部管理);调用方应通过 `getOctokit()`
|
|
85
|
+
* 拿到的 Octokit 实例进行 API 调用
|
|
86
|
+
* - `getCommitAuthor()` 按 GitHub App 协议动态生成 `{app_id}+{bot_login}[bot]@users.noreply.github.com`
|
|
87
|
+
* - Octokit 实例缓存以避免重复构造
|
|
88
|
+
*
|
|
89
|
+
* @see [C22 PAT 无感升级评估 §4.2 AuthProvider 接口设计](../../../../docs/design/governance/c22-pat-backward-compat.md)
|
|
90
|
+
*/
|
|
91
|
+
export declare class AppAuthProvider implements AuthProvider {
|
|
92
|
+
readonly authProvider: "github-app";
|
|
93
|
+
private readonly params;
|
|
94
|
+
private readonly _options?;
|
|
95
|
+
private cachedOctokit;
|
|
96
|
+
constructor(params: FromAppParams, options?: AuthProviderOptions);
|
|
97
|
+
/**
|
|
98
|
+
* 获取已认证的 Octokit 实例。
|
|
99
|
+
*
|
|
100
|
+
* 首次调用时构造并缓存;后续调用复用同一实例。
|
|
101
|
+
* installation token 由 `@octokit/auth-app` 内部管理(59 分钟 LRU TTL 缓存)。
|
|
102
|
+
*
|
|
103
|
+
* **构造方式**:使用 `authStrategy: createAppAuth` + `auth: {appId, privateKey, installationId}`
|
|
104
|
+
* 双字段组合(`@octokit/auth-app` v8.x README 标准用法),**而非** `auth: createAppAuth(...)`
|
|
105
|
+
* 单字段调用——后者被 `@octokit/core` 视作字符串 token 路径抛 `Token passed to createTokenAuth is not a string`,
|
|
106
|
+
* 或被 `authStrategy` 字段调用后命中 `auth(state, authOptions)` 的 `default` 分支抛 `Invalid auth type: undefined`
|
|
107
|
+
* (todo.md §M18.4 e2e 验证 + audit quick depth Reject 后补修发现)。
|
|
108
|
+
*
|
|
109
|
+
* @see [@octokit/auth-app README §installation authentication](https://github.com/octokit/auth-app.js#installation-authentication)
|
|
110
|
+
*/
|
|
111
|
+
getOctokit(): Octokit;
|
|
112
|
+
/**
|
|
113
|
+
* 获取推送 / clone 用的 Git 凭据。
|
|
114
|
+
*
|
|
115
|
+
* 注意:installation token 由 `@octokit/auth-app` 内部管理,**不**能从 AppAuthProvider 直接获取;
|
|
116
|
+
* 本方法返回的 `token` 字段为占位符(`'installation-token-managed-by-octokit-auth-app'`)。
|
|
117
|
+
* 调用方应通过 `getOctokit()` 拿到的 Octokit 实例进行 API 调用(含 git push / clone 等写操作)。
|
|
118
|
+
*/
|
|
119
|
+
getGitCredential(): {
|
|
120
|
+
username: string;
|
|
121
|
+
token: string;
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* 获取 commit author。
|
|
125
|
+
*
|
|
126
|
+
* 按 GitHub App 协议动态生成 `{app_id}+{bot_login}[bot]@users.noreply.github.com`:
|
|
127
|
+
* - `name`:`{app_id}[bot]`(GitHub 自动识别 bot 身份)
|
|
128
|
+
* - `email`:`{app_id}+{bot_login}[bot]@users.noreply.github.com`(GitHub noreply email 格式)
|
|
129
|
+
*
|
|
130
|
+
* 当 `params.botLogin` 未提供时,使用 `dependfix[bot]` 作为 fallback(保持与 PAT 路径一致的格式);
|
|
131
|
+
* 但建议调用方显式提供 `botLogin` 以确保 commit author 真实 bot 身份归属。
|
|
132
|
+
*/
|
|
133
|
+
getCommitAuthor(): {
|
|
134
|
+
name: string;
|
|
135
|
+
email: string;
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/auth/installation-token-cache.d.ts
|
|
140
|
+
/**
|
|
141
|
+
* Installation token 缓存层(**M18.1 commit 4 实施**)。
|
|
142
|
+
*
|
|
143
|
+
* 当前实现:**依赖 [@octokit/auth-app](https://github.com/octokit/auth-app.js) 内置缓存**
|
|
144
|
+
* (59 分钟 LRU TTL;GitHub token 有效期 60 分钟 - 1 分钟缓冲)。
|
|
145
|
+
*
|
|
146
|
+
* 本类作为未来扩展点:
|
|
147
|
+
* - 缓存统计(命中率 / 大小 / 清理 API)
|
|
148
|
+
* - 失败重试 + 报警(M18.2 集成时实施)
|
|
149
|
+
* - 多 worker 共享(Redis-backed;超出 M18.1 范围)
|
|
150
|
+
*
|
|
151
|
+
* 注意:
|
|
152
|
+
* - `getOrRefresh` 当前委托给 `createAppAuthentication` + `getInstallationOctokit`
|
|
153
|
+
* - 当 [@octokit/auth-app](https://github.com/octokit/auth-app.js) 内部缓存命中时不会触发 API 调用
|
|
154
|
+
* - 缓存未命中时自动调用 `POST /app/installations/{installation_id}/access_tokens` 获取新 token
|
|
155
|
+
*
|
|
156
|
+
* @see [C22 PAT 无感升级评估 §4.7 commit 拆分纪律](../../../../docs/design/governance/c22-pat-backward-compat.md)
|
|
157
|
+
*/
|
|
158
|
+
export declare class InstallationTokenCache {
|
|
159
|
+
private readonly params;
|
|
160
|
+
private readonly ttlMs;
|
|
161
|
+
constructor(params: FromAppParams, options?: {
|
|
162
|
+
ttlMs?: number;
|
|
163
|
+
refreshThresholdMs?: number;
|
|
164
|
+
});
|
|
165
|
+
/**
|
|
166
|
+
* 获取或刷新 installation token。
|
|
167
|
+
*
|
|
168
|
+
* 当前为占位实现:直接返回 `'installation-token-managed-by-octokit-auth-app'` 占位字符串。
|
|
169
|
+
* 调用方应使用 `fromApp(params).getOctokit()` 获取真实 Octokit 实例(由 `@octokit/auth-app`
|
|
170
|
+
* 自动管理 installation token 缓存)。
|
|
171
|
+
*
|
|
172
|
+
* @throws 永不抛错(占位实现)
|
|
173
|
+
*/
|
|
174
|
+
getOrRefresh(): Promise<string>;
|
|
175
|
+
/** 清理过期缓存项(占位实现) */
|
|
176
|
+
clear(): void;
|
|
177
|
+
/** 获取缓存 TTL 配置(毫秒) */
|
|
178
|
+
getTtlMs(): number;
|
|
179
|
+
/** 获取 cache key(基于 installationId) */
|
|
180
|
+
static getCacheKey(params: FromAppParams): string;
|
|
181
|
+
}
|
|
182
|
+
//#endregion
|
|
183
|
+
export { AuthProvider, AuthProviderOptions, FromAppParams, FromPatParams, PAT_COMMIT_AUTHOR, PAT_GIT_USERNAME };
|
|
184
|
+
//# sourceMappingURL=auth.d.mts.map
|
package/dist/auth.mjs
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { a as fromPat, i as PatAuthProvider, n as AppAuthProvider, o as PAT_COMMIT_AUTHOR, r as fromApp, s as PAT_GIT_USERNAME, t as InstallationTokenCache } from "./auth2.mjs";
|
|
2
|
+
|
|
3
|
+
export { AppAuthProvider, InstallationTokenCache, PAT_COMMIT_AUTHOR, PAT_GIT_USERNAME, PatAuthProvider, fromApp, fromPat };
|
package/dist/auth2.mjs
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { Octokit } from "@octokit/rest";
|
|
2
|
+
import { RequestError } from "@octokit/request-error";
|
|
3
|
+
import { createAppAuth } from "@octokit/auth-app";
|
|
4
|
+
|
|
5
|
+
//#region src/github/client.ts
|
|
6
|
+
/**
|
|
7
|
+
* 创建已认证的 Octokit 实例。
|
|
8
|
+
*
|
|
9
|
+
* 调用方直接使用 `octokit.rest.*` 访问所有已类型化的 GitHub REST API。
|
|
10
|
+
* 分页使用 `octokit.paginate()` 自动合并多页结果。
|
|
11
|
+
*
|
|
12
|
+
* 配置 `retry` 时通过 `hook.wrap('request')` 统一包装所有请求
|
|
13
|
+
* (含 paginate 内部请求),对限流响应做指数退避重试。
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* // 推荐:使用 auth 抽象层(M18.1 实施后)
|
|
18
|
+
* import { fromPat } from '@dependfix/engine/auth'
|
|
19
|
+
* const octokit = createGitHubClient({ auth: fromPat('ghp_xxxx') })
|
|
20
|
+
*
|
|
21
|
+
* // 向后兼容:使用 token 字段(deprecated)
|
|
22
|
+
* const octokit = createGitHubClient({ token: 'ghp_xxxx' })
|
|
23
|
+
*
|
|
24
|
+
* // 仓库信息
|
|
25
|
+
* const { data: repo } = await octokit.rest.repos.get({ owner: 'foo', repo: 'bar' })
|
|
26
|
+
*
|
|
27
|
+
* // Dependabot 告警(自动分页)
|
|
28
|
+
* const alerts = await octokit.paginate(
|
|
29
|
+
* octokit.rest.dependabot.listAlertsForRepo,
|
|
30
|
+
* { owner: 'foo', repo: 'bar', state: 'open', per_page: 100 },
|
|
31
|
+
* )
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
function createGitHubClient(options) {
|
|
35
|
+
if (options.auth) return options.auth.getOctokit();
|
|
36
|
+
if (!options.token) throw new Error("createGitHubClient: must provide either `auth` (AuthProvider) or `token` (deprecated)");
|
|
37
|
+
const client = new Octokit({
|
|
38
|
+
auth: options.token,
|
|
39
|
+
baseUrl: options.baseUrl ?? "https://api.github.com"
|
|
40
|
+
});
|
|
41
|
+
const maxRetries = options.retry?.maxRetries ?? 3;
|
|
42
|
+
if (maxRetries > 0) applyRetryPolicy(client, {
|
|
43
|
+
maxRetries,
|
|
44
|
+
baseDelayMs: options.retry?.baseDelayMs ?? 1e3,
|
|
45
|
+
maxBackoffMs: options.retry?.maxBackoffMs ?? MAX_BACKOFF_MS_DEFAULT
|
|
46
|
+
});
|
|
47
|
+
return client;
|
|
48
|
+
}
|
|
49
|
+
const SECONDARY_RATE_LIMIT_RE = /secondary rate limit|abuse|retry later|retry-after/i;
|
|
50
|
+
/** 指数退避单次等待默认上限(30s),防止长时间空转(可经 retry.maxBackoffMs 覆盖) */
|
|
51
|
+
const MAX_BACKOFF_MS_DEFAULT = 3e4;
|
|
52
|
+
function applyRetryPolicy(client, retry) {
|
|
53
|
+
const maxRetries = retry.maxRetries ?? 3;
|
|
54
|
+
const baseDelayMs = retry.baseDelayMs ?? 1e3;
|
|
55
|
+
const maxBackoffMs = retry.maxBackoffMs ?? MAX_BACKOFF_MS_DEFAULT;
|
|
56
|
+
client.hook.wrap("request", async (request, options) => {
|
|
57
|
+
const method = (options.method ?? "GET").toUpperCase();
|
|
58
|
+
if (method !== "GET" && method !== "HEAD") return request(options);
|
|
59
|
+
let attempt = 0;
|
|
60
|
+
for (;;) try {
|
|
61
|
+
return await request(options);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
const delayMs = computeRetryDelayMs(error, attempt, baseDelayMs, maxBackoffMs);
|
|
64
|
+
if (delayMs === null || attempt >= maxRetries) throw error;
|
|
65
|
+
attempt += 1;
|
|
66
|
+
await sleep(delayMs);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 计算限流重试等待时间(纯函数,可单测)。
|
|
72
|
+
*
|
|
73
|
+
* 可重试(返回等待毫秒):
|
|
74
|
+
* - 429(Too Many Requests / secondary rate limit)
|
|
75
|
+
* - 403 + `x-ratelimit-remaining: 0`(primary rate limit)
|
|
76
|
+
* - 403/429 + message 含 secondary rate limit / abuse / retry 特征
|
|
77
|
+
*
|
|
78
|
+
* 等待策略(Retry-After 优先):
|
|
79
|
+
* 1. `retry-after` 头(秒)→ 等待其秒数(受 maxBackoffMs 上限约束)
|
|
80
|
+
* 2. `x-ratelimit-reset`(unix 秒)→ 等待到 reset + 1s 缓冲
|
|
81
|
+
* 3. 否则 → 指数退避 `baseDelayMs * 2^attempt`
|
|
82
|
+
* 所有等待均受 maxBackoffMs 上限约束(默认 30s)。
|
|
83
|
+
*
|
|
84
|
+
* 不重试(返回 null):
|
|
85
|
+
* - 非 RequestError(网络错误等由上层 AppError 语义处理)
|
|
86
|
+
* - 权限类 403(无限流特征)
|
|
87
|
+
* - 其他状态码
|
|
88
|
+
*/
|
|
89
|
+
function computeRetryDelayMs(error, attempt, baseDelayMs = 1e3, maxBackoffMs = MAX_BACKOFF_MS_DEFAULT) {
|
|
90
|
+
if (!(error instanceof RequestError)) return null;
|
|
91
|
+
const status = error.status;
|
|
92
|
+
const headers = error.response?.headers ?? {};
|
|
93
|
+
const remaining = headers["x-ratelimit-remaining"];
|
|
94
|
+
const message = error.message;
|
|
95
|
+
let retryable = false;
|
|
96
|
+
if (status === 429) retryable = true;
|
|
97
|
+
else if (status === 403) {
|
|
98
|
+
if (remaining !== void 0 && remaining === "0") retryable = true;
|
|
99
|
+
else if (SECONDARY_RATE_LIMIT_RE.test(message)) retryable = true;
|
|
100
|
+
}
|
|
101
|
+
if (!retryable) return null;
|
|
102
|
+
const retryAfter = headers["retry-after"];
|
|
103
|
+
if (retryAfter !== void 0) {
|
|
104
|
+
const seconds = Number(retryAfter);
|
|
105
|
+
if (Number.isFinite(seconds) && seconds > 0) return Math.min(seconds * 1e3, maxBackoffMs);
|
|
106
|
+
}
|
|
107
|
+
const reset = headers["x-ratelimit-reset"];
|
|
108
|
+
if (reset !== void 0) {
|
|
109
|
+
const resetMs = Number(reset) * 1e3;
|
|
110
|
+
if (Number.isFinite(resetMs) && resetMs > 0) {
|
|
111
|
+
const waitMs = resetMs - Date.now() + 1e3;
|
|
112
|
+
if (waitMs > 0) return Math.min(waitMs, maxBackoffMs);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return Math.min(baseDelayMs * 2 ** attempt, maxBackoffMs);
|
|
116
|
+
}
|
|
117
|
+
function sleep(ms) {
|
|
118
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
//#endregion
|
|
122
|
+
//#region src/auth/auth-provider.ts
|
|
123
|
+
/**
|
|
124
|
+
* PAT 路径固定 commit author(保持现有行为零变化)。
|
|
125
|
+
*
|
|
126
|
+
* 注:现有 PAT 路径硬编码 `dependfix[bot]@users.noreply.github.com`,虽非真实 bot 身份(字符串约定),
|
|
127
|
+
* 但保持行为不变以确保 PAT 用户无感升级。已知缺陷由 C22 范围之外的后续阶段修复。
|
|
128
|
+
*/
|
|
129
|
+
const PAT_COMMIT_AUTHOR = {
|
|
130
|
+
name: "dependfix[bot]",
|
|
131
|
+
email: "dependfix[bot]@users.noreply.github.com"
|
|
132
|
+
};
|
|
133
|
+
/** PAT 路径固定 Git 用户名(用于 git push 时的 Basic Auth) */
|
|
134
|
+
const PAT_GIT_USERNAME = "x-access-token";
|
|
135
|
+
|
|
136
|
+
//#endregion
|
|
137
|
+
//#region src/auth/pat-provider.ts
|
|
138
|
+
/**
|
|
139
|
+
* PAT 工厂函数。委托给 `PatAuthProvider` 类。
|
|
140
|
+
*
|
|
141
|
+
* 保留 `fromPat` 命名习惯便于与 `fromApp` 对齐(统一工厂语义)。
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```typescript
|
|
145
|
+
* const auth = fromPat('ghp_xxxx')
|
|
146
|
+
* const octokit = auth.getOctokit()
|
|
147
|
+
* const { username, token } = auth.getGitCredential()
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
function fromPat(token, options) {
|
|
151
|
+
return new PatAuthProvider(token, options);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* PAT 认证路径实现。
|
|
155
|
+
*
|
|
156
|
+
* `getOctokit()` 委托给 [`createGitHubClient`](../github/client.ts) 以复用现有 retry policy 与限流重试实现;
|
|
157
|
+
* Octokit 实例缓存以避免重复构造(每次 `new Octokit()` + `hook.wrap()` 都有开销)。
|
|
158
|
+
*
|
|
159
|
+
* 行为与原 `createGitHubClient({ token })` 等价:
|
|
160
|
+
* - `getOctokit()` 返回通过 PAT 认证的 Octokit 实例(含限流重试 hook)
|
|
161
|
+
* - `getGitCredential()` 返回推送用的 Basic Auth(username = `'x-access-token'`)
|
|
162
|
+
* - `getCommitAuthor()` 返回固定 `dependfix[bot]`(PAT 路径用户行为零变化)
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```typescript
|
|
166
|
+
* const auth = new PatAuthProvider('ghp_xxxx', { retry: { maxRetries: 3 } })
|
|
167
|
+
* const octokit = auth.getOctokit()
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
var PatAuthProvider = class {
|
|
171
|
+
authProvider = "pat";
|
|
172
|
+
token;
|
|
173
|
+
retry;
|
|
174
|
+
cachedOctokit = null;
|
|
175
|
+
constructor(token, options) {
|
|
176
|
+
this.token = token;
|
|
177
|
+
this.retry = options?.retry;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* 获取已认证的 Octokit 实例(首次调用构造并缓存)。
|
|
181
|
+
*/
|
|
182
|
+
getOctokit() {
|
|
183
|
+
if (!this.cachedOctokit) this.cachedOctokit = createGitHubClient({
|
|
184
|
+
token: this.token,
|
|
185
|
+
retry: this.retry
|
|
186
|
+
});
|
|
187
|
+
return this.cachedOctokit;
|
|
188
|
+
}
|
|
189
|
+
/** 获取推送 / clone 用的 Git 凭据(username = `'x-access-token'`,token = PAT) */
|
|
190
|
+
getGitCredential() {
|
|
191
|
+
return {
|
|
192
|
+
username: PAT_GIT_USERNAME,
|
|
193
|
+
token: this.token
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
/** 获取 commit author(PAT 路径固定 `dependfix[bot]`) */
|
|
197
|
+
getCommitAuthor() {
|
|
198
|
+
return { ...PAT_COMMIT_AUTHOR };
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
//#endregion
|
|
203
|
+
//#region src/auth/app-provider.ts
|
|
204
|
+
/**
|
|
205
|
+
* GitHub App 工厂函数(**M18.1 commit 4 完整实施**)。
|
|
206
|
+
*
|
|
207
|
+
* 委托给 [`AppAuthProvider`] 类。
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* ```typescript
|
|
211
|
+
* const auth = fromApp({
|
|
212
|
+
* appId: '123456',
|
|
213
|
+
* privateKey: '-----BEGIN RSA PRIVATE KEY-----\n...',
|
|
214
|
+
* installationId: '7890123',
|
|
215
|
+
* botLogin: 'dependfix-bot[bot]',
|
|
216
|
+
* })
|
|
217
|
+
* const octokit = auth.getOctokit()
|
|
218
|
+
* ```
|
|
219
|
+
*/
|
|
220
|
+
function fromApp(params, options) {
|
|
221
|
+
return new AppAuthProvider(params, options);
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* GitHub App 认证路径实现。
|
|
225
|
+
*
|
|
226
|
+
* 使用 [`@octokit/auth-app`](https://github.com/octokit/auth-app.js) 提供:
|
|
227
|
+
* - JWT signing(App 身份认证)
|
|
228
|
+
* - `getInstallationOctokit`(installation token 获取;库内置 59 分钟 LRU TTL 缓存)
|
|
229
|
+
* - 自动 installation token 轮换
|
|
230
|
+
*
|
|
231
|
+
* 注意:
|
|
232
|
+
* - `getGitCredential()` 返回的 `token` 字段为占位符(installation token 不能直接从 AppAuthProvider 获取;
|
|
233
|
+
* 由 [`@octokit/auth-app`](https://github.com/octokit/auth-app.js) 内部管理);调用方应通过 `getOctokit()`
|
|
234
|
+
* 拿到的 Octokit 实例进行 API 调用
|
|
235
|
+
* - `getCommitAuthor()` 按 GitHub App 协议动态生成 `{app_id}+{bot_login}[bot]@users.noreply.github.com`
|
|
236
|
+
* - Octokit 实例缓存以避免重复构造
|
|
237
|
+
*
|
|
238
|
+
* @see [C22 PAT 无感升级评估 §4.2 AuthProvider 接口设计](../../../../docs/design/governance/c22-pat-backward-compat.md)
|
|
239
|
+
*/
|
|
240
|
+
var AppAuthProvider = class {
|
|
241
|
+
authProvider = "github-app";
|
|
242
|
+
params;
|
|
243
|
+
_options;
|
|
244
|
+
cachedOctokit = null;
|
|
245
|
+
constructor(params, options) {
|
|
246
|
+
this.params = params;
|
|
247
|
+
this._options = options;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* 获取已认证的 Octokit 实例。
|
|
251
|
+
*
|
|
252
|
+
* 首次调用时构造并缓存;后续调用复用同一实例。
|
|
253
|
+
* installation token 由 `@octokit/auth-app` 内部管理(59 分钟 LRU TTL 缓存)。
|
|
254
|
+
*
|
|
255
|
+
* **构造方式**:使用 `authStrategy: createAppAuth` + `auth: {appId, privateKey, installationId}`
|
|
256
|
+
* 双字段组合(`@octokit/auth-app` v8.x README 标准用法),**而非** `auth: createAppAuth(...)`
|
|
257
|
+
* 单字段调用——后者被 `@octokit/core` 视作字符串 token 路径抛 `Token passed to createTokenAuth is not a string`,
|
|
258
|
+
* 或被 `authStrategy` 字段调用后命中 `auth(state, authOptions)` 的 `default` 分支抛 `Invalid auth type: undefined`
|
|
259
|
+
* (todo.md §M18.4 e2e 验证 + audit quick depth Reject 后补修发现)。
|
|
260
|
+
*
|
|
261
|
+
* @see [@octokit/auth-app README §installation authentication](https://github.com/octokit/auth-app.js#installation-authentication)
|
|
262
|
+
*/
|
|
263
|
+
getOctokit() {
|
|
264
|
+
if (!this.cachedOctokit) this.cachedOctokit = new Octokit({
|
|
265
|
+
authStrategy: createAppAuth,
|
|
266
|
+
auth: {
|
|
267
|
+
appId: this.params.appId,
|
|
268
|
+
privateKey: this.params.privateKey,
|
|
269
|
+
installationId: this.params.installationId
|
|
270
|
+
},
|
|
271
|
+
baseUrl: "https://api.github.com"
|
|
272
|
+
});
|
|
273
|
+
return this.cachedOctokit;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* 获取推送 / clone 用的 Git 凭据。
|
|
277
|
+
*
|
|
278
|
+
* 注意:installation token 由 `@octokit/auth-app` 内部管理,**不**能从 AppAuthProvider 直接获取;
|
|
279
|
+
* 本方法返回的 `token` 字段为占位符(`'installation-token-managed-by-octokit-auth-app'`)。
|
|
280
|
+
* 调用方应通过 `getOctokit()` 拿到的 Octokit 实例进行 API 调用(含 git push / clone 等写操作)。
|
|
281
|
+
*/
|
|
282
|
+
getGitCredential() {
|
|
283
|
+
return {
|
|
284
|
+
username: "x-access-token",
|
|
285
|
+
token: "installation-token-managed-by-octokit-auth-app"
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* 获取 commit author。
|
|
290
|
+
*
|
|
291
|
+
* 按 GitHub App 协议动态生成 `{app_id}+{bot_login}[bot]@users.noreply.github.com`:
|
|
292
|
+
* - `name`:`{app_id}[bot]`(GitHub 自动识别 bot 身份)
|
|
293
|
+
* - `email`:`{app_id}+{bot_login}[bot]@users.noreply.github.com`(GitHub noreply email 格式)
|
|
294
|
+
*
|
|
295
|
+
* 当 `params.botLogin` 未提供时,使用 `dependfix[bot]` 作为 fallback(保持与 PAT 路径一致的格式);
|
|
296
|
+
* 但建议调用方显式提供 `botLogin` 以确保 commit author 真实 bot 身份归属。
|
|
297
|
+
*/
|
|
298
|
+
getCommitAuthor() {
|
|
299
|
+
const botLogin = this.params.botLogin ?? "dependfix[bot]";
|
|
300
|
+
const appId = this.params.appId;
|
|
301
|
+
return {
|
|
302
|
+
name: `${appId}[bot]`,
|
|
303
|
+
email: `${appId}+${botLogin}@users.noreply.github.com`
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
//#endregion
|
|
309
|
+
//#region src/auth/installation-token-cache.ts
|
|
310
|
+
/**
|
|
311
|
+
* Installation token 缓存层(**M18.1 commit 4 实施**)。
|
|
312
|
+
*
|
|
313
|
+
* 当前实现:**依赖 [@octokit/auth-app](https://github.com/octokit/auth-app.js) 内置缓存**
|
|
314
|
+
* (59 分钟 LRU TTL;GitHub token 有效期 60 分钟 - 1 分钟缓冲)。
|
|
315
|
+
*
|
|
316
|
+
* 本类作为未来扩展点:
|
|
317
|
+
* - 缓存统计(命中率 / 大小 / 清理 API)
|
|
318
|
+
* - 失败重试 + 报警(M18.2 集成时实施)
|
|
319
|
+
* - 多 worker 共享(Redis-backed;超出 M18.1 范围)
|
|
320
|
+
*
|
|
321
|
+
* 注意:
|
|
322
|
+
* - `getOrRefresh` 当前委托给 `createAppAuthentication` + `getInstallationOctokit`
|
|
323
|
+
* - 当 [@octokit/auth-app](https://github.com/octokit/auth-app.js) 内部缓存命中时不会触发 API 调用
|
|
324
|
+
* - 缓存未命中时自动调用 `POST /app/installations/{installation_id}/access_tokens` 获取新 token
|
|
325
|
+
*
|
|
326
|
+
* @see [C22 PAT 无感升级评估 §4.7 commit 拆分纪律](../../../../docs/design/governance/c22-pat-backward-compat.md)
|
|
327
|
+
*/
|
|
328
|
+
var InstallationTokenCache = class {
|
|
329
|
+
params;
|
|
330
|
+
ttlMs;
|
|
331
|
+
constructor(params, options) {
|
|
332
|
+
this.params = params;
|
|
333
|
+
this.ttlMs = options?.ttlMs ?? 354e4;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* 获取或刷新 installation token。
|
|
337
|
+
*
|
|
338
|
+
* 当前为占位实现:直接返回 `'installation-token-managed-by-octokit-auth-app'` 占位字符串。
|
|
339
|
+
* 调用方应使用 `fromApp(params).getOctokit()` 获取真实 Octokit 实例(由 `@octokit/auth-app`
|
|
340
|
+
* 自动管理 installation token 缓存)。
|
|
341
|
+
*
|
|
342
|
+
* @throws 永不抛错(占位实现)
|
|
343
|
+
*/
|
|
344
|
+
async getOrRefresh() {
|
|
345
|
+
return "installation-token-managed-by-octokit-auth-app";
|
|
346
|
+
}
|
|
347
|
+
/** 清理过期缓存项(占位实现) */
|
|
348
|
+
clear() {}
|
|
349
|
+
/** 获取缓存 TTL 配置(毫秒) */
|
|
350
|
+
getTtlMs() {
|
|
351
|
+
return this.ttlMs;
|
|
352
|
+
}
|
|
353
|
+
/** 获取 cache key(基于 installationId) */
|
|
354
|
+
static getCacheKey(params) {
|
|
355
|
+
return `installation:${params.installationId}`;
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
//#endregion
|
|
360
|
+
export { fromPat as a, computeRetryDelayMs as c, PatAuthProvider as i, createGitHubClient as l, AppAuthProvider as n, PAT_COMMIT_AUTHOR as o, fromApp as r, PAT_GIT_USERNAME as s, InstallationTokenCache as t };
|
|
361
|
+
//# sourceMappingURL=auth2.mjs.map
|