dsh-ci-diet 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +43 -7
- package/README.md +45 -8
- package/dist/adapters/github/errors.d.ts +3 -1
- package/dist/adapters/github/errors.js +9 -2
- package/dist/adapters/github/errors.js.map +1 -1
- package/dist/adapters/github/transport.d.ts +4 -1
- package/dist/adapters/github/transport.js +49 -17
- package/dist/adapters/github/transport.js.map +1 -1
- package/dist/cli/index.js +236 -26
- package/dist/cli/index.js.map +1 -1
- package/dist/core/analysis/coverage.d.ts +2 -1
- package/dist/core/analysis/coverage.js +10 -6
- package/dist/core/analysis/coverage.js.map +1 -1
- package/dist/core/analysis/diagnostics.d.ts +57 -0
- package/dist/core/analysis/diagnostics.js +166 -0
- package/dist/core/analysis/diagnostics.js.map +1 -0
- package/dist/core/analysis/report.js +1 -1
- package/dist/core/analysis/report.js.map +1 -1
- package/dist/core/contracts/schema-json.generated.d.ts +1 -1
- package/dist/core/contracts/schema-json.generated.js +1 -1
- package/dist/core/contracts/schema-json.generated.js.map +1 -1
- package/dist/core/contracts/schema.d.ts +12 -1
- package/dist/core/data/index.d.ts +7 -0
- package/dist/core/data/index.js +28 -0
- package/dist/core/data/index.js.map +1 -1
- package/dist/core/history/sync.js +40 -7
- package/dist/core/history/sync.js.map +1 -1
- package/dist/core/patch/apply.d.ts +13 -1
- package/dist/core/patch/apply.js +322 -52
- package/dist/core/patch/apply.js.map +1 -1
- package/dist/core/patch/approval.d.ts +1 -0
- package/dist/core/patch/approval.js +4 -3
- package/dist/core/patch/approval.js.map +1 -1
- package/dist/core/patch/edits.js +20 -1
- package/dist/core/patch/edits.js.map +1 -1
- package/dist/core/patch/plan.js +2 -1
- package/dist/core/patch/plan.js.map +1 -1
- package/dist/core/patch/store.d.ts +62 -3
- package/dist/core/patch/store.js +222 -31
- package/dist/core/patch/store.js.map +1 -1
- package/dist/core/report/html.d.ts +2 -0
- package/dist/core/report/html.js +51 -32
- package/dist/core/report/html.js.map +1 -1
- package/dist/core/report/localize.d.ts +13 -0
- package/dist/core/report/localize.js +19 -0
- package/dist/core/report/localize.js.map +1 -0
- package/dist/core/rules/cd001.js +11 -0
- package/dist/core/rules/cd001.js.map +1 -1
- package/dist/core/rules/cd002.js +5 -0
- package/dist/core/rules/cd002.js.map +1 -1
- package/dist/core/rules/cd003.js +11 -0
- package/dist/core/rules/cd003.js.map +1 -1
- package/dist/core/rules/cd004.js +13 -0
- package/dist/core/rules/cd004.js.map +1 -1
- package/dist/core/rules/cd005.js +45 -11
- package/dist/core/rules/cd005.js.map +1 -1
- package/dist/core/rules/cd008.js +20 -2
- package/dist/core/rules/cd008.js.map +1 -1
- package/dist/core/rules/types.d.ts +6 -0
- package/dist/core/rules/types.js +9 -0
- package/dist/core/rules/types.js.map +1 -1
- package/dist/core/savings/report-metrics.d.ts +8 -0
- package/dist/core/savings/report-metrics.js +20 -1
- package/dist/core/savings/report-metrics.js.map +1 -1
- package/dist/core/scan/discover.js +6 -2
- package/dist/core/scan/discover.js.map +1 -1
- package/dist/core/scan/scan.js +2 -2
- package/dist/core/scan/scan.js.map +1 -1
- package/dist/core/version.d.ts +5 -2
- package/dist/core/version.js +5 -2
- package/dist/core/version.js.map +1 -1
- package/dist/core/workflow/jobkey.js +27 -6
- package/dist/core/workflow/jobkey.js.map +1 -1
- package/dist/dsh/host/plugin.d.ts +3 -0
- package/dist/dsh/host/plugin.js +45 -33
- package/dist/dsh/host/plugin.js.map +1 -1
- package/dist/shared/i18n/en.d.ts +100 -2
- package/dist/shared/i18n/en.js +100 -2
- package/dist/shared/i18n/en.js.map +1 -1
- package/dist/shared/i18n/es.d.ts +2 -0
- package/dist/shared/i18n/es.js +215 -0
- package/dist/shared/i18n/es.js.map +1 -0
- package/dist/shared/i18n/fr.d.ts +2 -0
- package/dist/shared/i18n/fr.js +215 -0
- package/dist/shared/i18n/fr.js.map +1 -0
- package/dist/shared/i18n/hi.d.ts +2 -0
- package/dist/shared/i18n/hi.js +215 -0
- package/dist/shared/i18n/hi.js.map +1 -0
- package/dist/shared/i18n/host.d.ts +14 -0
- package/dist/shared/i18n/host.js +118 -0
- package/dist/shared/i18n/host.js.map +1 -0
- package/dist/shared/i18n/index.d.ts +3 -1
- package/dist/shared/i18n/index.js +37 -14
- package/dist/shared/i18n/index.js.map +1 -1
- package/dist/shared/i18n/ko.js +100 -2
- package/dist/shared/i18n/ko.js.map +1 -1
- package/dist/shared/i18n/zh-Hans.d.ts +1 -1
- package/dist/shared/i18n/zh-Hans.js +214 -3
- package/dist/shared/i18n/zh-Hans.js.map +1 -1
- package/package.json +5 -4
package/README.ko.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
낭비된 GitHub Actions 러너 시간을 찾고, 작고 검토 가능한 패치를 제안합니다.
|
|
4
4
|
|
|
5
|
-
상태: **알파, npm에 `dsh-ci-diet@0.
|
|
5
|
+
상태: **알파, npm에 `dsh-ci-diet@0.2.0`로 배포됨**. 계약, 명세, 분석 엔진,
|
|
6
6
|
패치 엔진, CLI, DSH 플러그인 도구, 자체 완결형 HTML 보고서는 구현과 테스트를
|
|
7
7
|
마쳤습니다. DSH Web 클라이언트 화면은 아직 남아 있습니다.
|
|
8
8
|
|
|
@@ -66,7 +66,7 @@ dsh plugin --profile web add dsh-ci-diet
|
|
|
66
66
|
플러그인은 기본으로 꺼진 로더 항목 하나를 추가합니다. 이력 동기화를 쓸 때만
|
|
67
67
|
`networkEnabled`를 켜십시오. 전역 설치는 선택 사항입니다. `npx dsh-ci-diet`로
|
|
68
68
|
같은 CLI를 실행할 수 있고, DSH 플러그인은 프로필에 패키지가 있으면 됩니다.
|
|
69
|
-
릴리스 태그는 GitHub에, 같은 버전은 npm `0.
|
|
69
|
+
릴리스 태그는 GitHub에, 같은 버전은 npm `0.2.0`에 있습니다.
|
|
70
70
|
|
|
71
71
|
## 실행
|
|
72
72
|
|
|
@@ -91,23 +91,51 @@ ci-diet revert --change <changeId> # 백업한 바이트를 복원
|
|
|
91
91
|
```
|
|
92
92
|
ci-diet report # 저장된 최신 보고서의 텍스트 요약
|
|
93
93
|
ci-diet report --format html --out report.html
|
|
94
|
+
ci-diet diagnose # Job 이름 매칭과 수집 커버리지 설명
|
|
94
95
|
ci-diet prune # 만료된 보고서, 계획, 캐시 정리
|
|
95
96
|
```
|
|
96
97
|
|
|
97
98
|
HTML 파일은 자체 완결형입니다. 인라인 CSS만 쓰고 스크립트와 외부 요청이 없습니다.
|
|
98
99
|
읽기 전용이므로 승인은 digest와 nonce 검사가 있는 CLI에 남습니다.
|
|
99
100
|
|
|
101
|
+
`diagnose`는 저장된 최신 이력을 읽어 GitHub Job 이름이 워크플로 키로 얼마나
|
|
102
|
+
해석됐는지, 어떤 matrix Job이 몇 개 레그로 확장됐는지, 어느 수집 상한 때문에
|
|
103
|
+
커버리지 축이 불완전한지 보여줍니다. 오프라인에서도 동작하며 `--require-complete`를
|
|
104
|
+
쓰면 축이 불완전할 때 종료 코드 `3`으로 끝납니다.
|
|
105
|
+
|
|
106
|
+
점검용 캐시 힌트도 함께 보여줍니다. 캐시 없이 의존성을 설치하는 Job이나
|
|
107
|
+
lockfile 변경을 따라가지 못하는 리터럴 캐시 키가 대상입니다. 동결된 계약에는
|
|
108
|
+
캐시 규칙 ID가 없으므로 힌트는 발견 항목이나 패치로 이어지지 않습니다.
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
## 릴리스
|
|
112
|
+
|
|
113
|
+
`package.json`의 버전과 일치하는 태그를 푸시하면
|
|
114
|
+
`.github/workflows/publish.yml`이 실행됩니다.
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
git tag v0.2.0
|
|
118
|
+
git push origin v0.2.0
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
워크플로는 태그와 `package.json` 버전을 대조하고(`node
|
|
122
|
+
scripts/check-release-tag.mjs`), CI와 같은 검사를 실행한 뒤, 이미 배포된
|
|
123
|
+
버전이면 게시를 거부합니다. 통과하면 npm trusted publishing으로 게시합니다.
|
|
124
|
+
GitHub OIDC(`id-token: write`)를 쓰고 npm 토큰은 쓰지 않으므로, 패키지의
|
|
125
|
+
trusted publisher에 이 저장소의 `publish.yml`이 등록되어 있어야 합니다.
|
|
126
|
+
|
|
100
127
|
## 요구 사항
|
|
101
128
|
|
|
102
129
|
- Node `^22.19.0 || >=24.0.0`
|
|
103
130
|
- `gh` CLI(선택): 실행 이력 조회에만 사용
|
|
104
|
-
- DSH `>=0.2.1-rc.2 <0.3.0`(선택): 플러그인 도구에만
|
|
131
|
+
- DSH `>=0.2.1-rc.2 <0.3.0-0 || >=0.2.2-rc.1 <0.3.0-0`(선택): 플러그인 도구에만
|
|
132
|
+
사용
|
|
105
133
|
|
|
106
134
|
## 개발
|
|
107
135
|
|
|
108
136
|
```
|
|
109
137
|
bun install
|
|
110
|
-
node
|
|
138
|
+
node scripts/test-core.mjs
|
|
111
139
|
node scripts/gen-types.mjs --check
|
|
112
140
|
node scripts/sync-contracts.mjs --check
|
|
113
141
|
bun run typecheck
|
|
@@ -135,9 +163,17 @@ tests/unit 네트워크가 필요 없는 빠른 검사
|
|
|
135
163
|
|
|
136
164
|
## 언어
|
|
137
165
|
|
|
138
|
-
영어가 기본이며 메시지 키의 단일 소스입니다.
|
|
139
|
-
|
|
140
|
-
|
|
166
|
+
영어가 기본이며 메시지 키의 단일 소스입니다. CLI, HTML 보고서, DSH 도구 문구는
|
|
167
|
+
영어·한국어·중국어 간체·힌디어·스페인어·프랑스어로 제공하며, 모든 사전이 완역이라
|
|
168
|
+
키가 빠지면 타입 오류가 납니다. 지역 태그는 언어로 해석하고(`hi-IN`, `es-MX`,
|
|
169
|
+
`fr-CA`), 간체가 아닌 중국어 변형은 영어로 대체합니다.
|
|
170
|
+
|
|
171
|
+
언어 선택 순서는 `--lang`, DSH 언어 설정, `LC_ALL`/`LC_MESSAGES`/`LANG`,
|
|
172
|
+
영어입니다. 보고서는 영어로 저장하고 표시할 때만 번역합니다. 규칙 ID, 오류 코드,
|
|
173
|
+
열거값, 경로, 다이제스트, 수치는 번역하지 않으며 HTML 보고서의 `lang` 속성은
|
|
174
|
+
선택한 언어를 따릅니다. DSH 도구 설명은 플러그인 로드 시점에 고정되므로 DSH
|
|
175
|
+
언어 설정을 바꾸면 다시 로드해야 합니다. 공개 문서는 영어이고 README만 한국어로
|
|
176
|
+
미러링합니다.
|
|
141
177
|
|
|
142
178
|
## 문서
|
|
143
179
|
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Find wasted GitHub Actions runner time and propose small, reviewable patches.
|
|
4
4
|
|
|
5
|
-
Status: **alpha, published on npm** as `dsh-ci-diet@0.
|
|
5
|
+
Status: **alpha, published on npm** as `dsh-ci-diet@0.2.0`. The contracts,
|
|
6
6
|
specification, analysis engine, patch engine, CLI, DSH plugin tools, and a
|
|
7
7
|
self-contained HTML report are implemented and tested. The DSH Web client
|
|
8
8
|
screens are still open.
|
|
@@ -68,7 +68,7 @@ dsh plugin --profile web add dsh-ci-diet
|
|
|
68
68
|
The plugin adds one disabled loader entry; enable `networkEnabled` only if you
|
|
69
69
|
want history sync. Installing the package globally is optional: `npx dsh-ci-diet`
|
|
70
70
|
runs the same CLI, and the DSH plugin only needs the package in the profile.
|
|
71
|
-
Release tags are on GitHub; the matching npm version is `0.
|
|
71
|
+
Release tags are on GitHub; the matching npm version is `0.2.0`.
|
|
72
72
|
|
|
73
73
|
## Run it
|
|
74
74
|
|
|
@@ -93,6 +93,7 @@ pushed, or applied without the explicit `approve` step.
|
|
|
93
93
|
```
|
|
94
94
|
ci-diet report # text summary of the latest stored report
|
|
95
95
|
ci-diet report --format html --out report.html
|
|
96
|
+
ci-diet diagnose # explain job-name attribution and collection coverage
|
|
96
97
|
ci-diet prune # drop expired reports, plans, and cache entries
|
|
97
98
|
```
|
|
98
99
|
|
|
@@ -100,17 +101,44 @@ The HTML file is self-contained: inline CSS, no scripts, no external requests.
|
|
|
100
101
|
It is read-only, so approvals stay in the CLI where the digest and nonce checks
|
|
101
102
|
live.
|
|
102
103
|
|
|
104
|
+
`diagnose` reads the latest stored history and reports how many GitHub job
|
|
105
|
+
names resolved to workflow keys, which matrix jobs expanded, and which
|
|
106
|
+
collection caps left a coverage axis incomplete. It is read-only and works
|
|
107
|
+
offline; `--require-complete` exits `3` when any axis is incomplete.
|
|
108
|
+
|
|
109
|
+
It also lists advisory cache hygiene hints: a dependency install with no cache,
|
|
110
|
+
and a literal cache key that cannot follow the lockfile. The frozen contract has
|
|
111
|
+
no cache rule id, so hints never become findings or patches.
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
## Releasing
|
|
115
|
+
|
|
116
|
+
Pushing a tag that matches the version in `package.json` triggers
|
|
117
|
+
`.github/workflows/publish.yml`:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
git tag v0.2.0
|
|
121
|
+
git push origin v0.2.0
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The workflow compares the tag with `package.json` (`node
|
|
125
|
+
scripts/check-release-tag.mjs`), runs the same checks as CI, refuses to
|
|
126
|
+
overwrite an already published version, and then publishes with npm trusted
|
|
127
|
+
publishing. It uses GitHub OIDC (`id-token: write`) and no npm token, so the
|
|
128
|
+
package's trusted publisher must name `publish.yml` for this repository.
|
|
129
|
+
|
|
103
130
|
## Requirements
|
|
104
131
|
|
|
105
132
|
- Node `^22.19.0 || >=24.0.0`
|
|
106
133
|
- `gh` CLI, optional, only for execution history
|
|
107
|
-
- DSH `>=0.2.1-rc.2 <0.3.0`, optional, only for
|
|
134
|
+
- DSH `>=0.2.1-rc.2 <0.3.0-0 || >=0.2.2-rc.1 <0.3.0-0`, optional, only for
|
|
135
|
+
the plugin tools
|
|
108
136
|
|
|
109
137
|
## Development
|
|
110
138
|
|
|
111
139
|
```
|
|
112
140
|
bun install
|
|
113
|
-
node
|
|
141
|
+
node scripts/test-core.mjs
|
|
114
142
|
node scripts/gen-types.mjs --check
|
|
115
143
|
node scripts/sync-contracts.mjs --check
|
|
116
144
|
bun run typecheck
|
|
@@ -138,10 +166,19 @@ tests/unit fast checks that need no network
|
|
|
138
166
|
|
|
139
167
|
## Languages
|
|
140
168
|
|
|
141
|
-
English is the default and the source of truth for message keys.
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
169
|
+
English is the default and the source of truth for message keys. The CLI, the
|
|
170
|
+
HTML report, and DSH tool text ship in English, Korean, Simplified Chinese,
|
|
171
|
+
Hindi, Spanish, and French; every dictionary is complete, so a missing key is a
|
|
172
|
+
type error. Regional tags resolve by language (`hi-IN`, `es-MX`, `fr-CA`), and
|
|
173
|
+
Chinese variants that are not Simplified fall back to English.
|
|
174
|
+
|
|
175
|
+
Language selection is an explicit `--lang`, then the DSH locale preference,
|
|
176
|
+
then `LC_ALL`/`LC_MESSAGES`/`LANG`, then English. Reports are stored in
|
|
177
|
+
English and translated only when displayed: rule ids, error codes, enum values,
|
|
178
|
+
paths, digests, and numbers stay untranslated, and the HTML report sets its
|
|
179
|
+
`lang` attribute to the selected locale. DSH tool descriptions are captured at
|
|
180
|
+
plugin load, so changing the DSH language preference needs a reload. Public
|
|
181
|
+
documentation is English, with a Korean README mirror.
|
|
145
182
|
|
|
146
183
|
## Documentation
|
|
147
184
|
|
|
@@ -4,7 +4,9 @@ export declare class GitHubError extends Error {
|
|
|
4
4
|
readonly code: GitHubErrorCode;
|
|
5
5
|
readonly status: number | null;
|
|
6
6
|
readonly detail: string | null;
|
|
7
|
-
|
|
7
|
+
/** Server-requested pause before a retry, when GitHub supplied one. */
|
|
8
|
+
readonly retryAfterMs: number | null;
|
|
9
|
+
constructor(code: GitHubErrorCode, message: string, status?: number | null, detail?: string | null, retryAfterMs?: number | null);
|
|
8
10
|
}
|
|
9
11
|
/** True when a retry could plausibly succeed without new credentials. */
|
|
10
12
|
export declare function isTransient(error: unknown): boolean;
|
|
@@ -2,16 +2,23 @@ export class GitHubError extends Error {
|
|
|
2
2
|
code;
|
|
3
3
|
status;
|
|
4
4
|
detail;
|
|
5
|
-
|
|
5
|
+
/** Server-requested pause before a retry, when GitHub supplied one. */
|
|
6
|
+
retryAfterMs;
|
|
7
|
+
constructor(code, message, status = null, detail = null, retryAfterMs = null) {
|
|
6
8
|
super(message);
|
|
7
9
|
this.name = "GitHubError";
|
|
8
10
|
this.code = code;
|
|
9
11
|
this.status = status;
|
|
10
12
|
this.detail = detail;
|
|
13
|
+
this.retryAfterMs = retryAfterMs;
|
|
11
14
|
}
|
|
12
15
|
}
|
|
13
16
|
/** True when a retry could plausibly succeed without new credentials. */
|
|
14
17
|
export function isTransient(error) {
|
|
15
|
-
|
|
18
|
+
if (!(error instanceof GitHubError))
|
|
19
|
+
return false;
|
|
20
|
+
if (error.code === "NETWORK_ERROR")
|
|
21
|
+
return true;
|
|
22
|
+
return error.status === 429 || error.status === 502 || error.status === 503 || error.status === 504;
|
|
16
23
|
}
|
|
17
24
|
//# sourceMappingURL=errors.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/adapters/github/errors.ts"],"names":[],"mappings":"AAWA,MAAM,OAAO,WAAY,SAAQ,KAAK;IAC3B,IAAI,CAAkB;IACtB,MAAM,CAAgB;IACtB,MAAM,CAAgB;
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/adapters/github/errors.ts"],"names":[],"mappings":"AAWA,MAAM,OAAO,WAAY,SAAQ,KAAK;IAC3B,IAAI,CAAkB;IACtB,MAAM,CAAgB;IACtB,MAAM,CAAgB;IAC/B,uEAAuE;IAC9D,YAAY,CAAgB;IAErC,YAAY,IAAqB,EAAE,OAAe,EAAE,SAAwB,IAAI,EAAE,SAAwB,IAAI,EAAE,eAA8B,IAAI;QAChJ,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;QAC1B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;IACnC,CAAC;CACF;AAED,yEAAyE;AACzE,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,IAAI,CAAC,CAAC,KAAK,YAAY,WAAW,CAAC;QAAE,OAAO,KAAK,CAAC;IAClD,IAAI,KAAK,CAAC,IAAI,KAAK,eAAe;QAAE,OAAO,IAAI,CAAC;IAChD,OAAO,KAAK,CAAC,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,CAAC;AACtG,CAAC"}
|
|
@@ -7,8 +7,10 @@ export interface GhResult {
|
|
|
7
7
|
readonly exitCode: number;
|
|
8
8
|
readonly stdout: string;
|
|
9
9
|
readonly stderr: string;
|
|
10
|
+
/** True when gh was killed before it could answer, e.g. by the execFile timeout. */
|
|
11
|
+
readonly timedOut?: boolean;
|
|
10
12
|
}
|
|
11
|
-
export type GhRunner = (args: readonly string[]) => Promise<GhResult>;
|
|
13
|
+
export type GhRunner = (args: readonly string[], timeoutMs?: number) => Promise<GhResult>;
|
|
12
14
|
export interface GhResponse {
|
|
13
15
|
readonly status: number;
|
|
14
16
|
readonly headers: Readonly<Record<string, string>>;
|
|
@@ -32,6 +34,7 @@ export declare function readGhVersion(runner?: GhRunner): Promise<string | null>
|
|
|
32
34
|
export declare class GhTransport {
|
|
33
35
|
private readonly budget;
|
|
34
36
|
private readonly runner;
|
|
37
|
+
private readonly timeoutMs;
|
|
35
38
|
private readonly retries;
|
|
36
39
|
private readonly sleep;
|
|
37
40
|
private readonly cache;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
|
-
import { GitHubError } from "./errors.js";
|
|
2
|
+
import { GitHubError, isTransient } from "./errors.js";
|
|
3
3
|
import { renderPath } from "./endpoints.js";
|
|
4
4
|
export const API_VERSION = "2026-03-10";
|
|
5
5
|
export const DEFAULT_TIMEOUT_MS = 20_000;
|
|
@@ -12,17 +12,20 @@ function delay(milliseconds) {
|
|
|
12
12
|
});
|
|
13
13
|
}
|
|
14
14
|
/** Real runner: resolves the gh binary from PATH and never uses a shell. */
|
|
15
|
-
export const ghCliRunner = (args) => new Promise((resolveRun) => {
|
|
16
|
-
execFile("gh", [...args], { timeout:
|
|
15
|
+
export const ghCliRunner = (args, timeoutMs = DEFAULT_TIMEOUT_MS) => new Promise((resolveRun) => {
|
|
16
|
+
execFile("gh", [...args], { timeout: timeoutMs, maxBuffer: 32 * 1024 * 1024, windowsHide: true }, (error, stdout, stderr) => {
|
|
17
17
|
if (error === null) {
|
|
18
18
|
resolveRun({ exitCode: 0, stdout: String(stdout), stderr: String(stderr) });
|
|
19
19
|
return;
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
if (code === "ENOENT") {
|
|
21
|
+
if (error.code === "ENOENT") {
|
|
23
22
|
resolveRun({ exitCode: 127, stdout: String(stdout), stderr: "gh was not found on PATH" });
|
|
24
23
|
return;
|
|
25
24
|
}
|
|
25
|
+
if (error.killed === true) {
|
|
26
|
+
resolveRun({ exitCode: 1, stdout: String(stdout), stderr: String(stderr), timedOut: true });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
26
29
|
const status = typeof error.code === "number" ? Number(error.code) : 1;
|
|
27
30
|
resolveRun({ exitCode: Number.isInteger(status) ? status : 1, stdout: String(stdout), stderr: String(stderr) });
|
|
28
31
|
});
|
|
@@ -59,6 +62,34 @@ function parseResponse(stdout) {
|
|
|
59
62
|
}
|
|
60
63
|
return { status: match === null ? 0 : Number(match[1]), headers, text };
|
|
61
64
|
}
|
|
65
|
+
const MAX_RETRY_DELAY_MS = 30_000;
|
|
66
|
+
/** Parse Retry-After: whole seconds or an HTTP-date; anything else is ignored. */
|
|
67
|
+
function parseRetryAfter(value) {
|
|
68
|
+
if (value === undefined)
|
|
69
|
+
return null;
|
|
70
|
+
const trimmed = value.trim();
|
|
71
|
+
if (trimmed === "")
|
|
72
|
+
return null;
|
|
73
|
+
if (/^[0-9]+$/.test(trimmed)) {
|
|
74
|
+
return Math.min(Number(trimmed) * 1000, MAX_RETRY_DELAY_MS);
|
|
75
|
+
}
|
|
76
|
+
const at = Date.parse(trimmed);
|
|
77
|
+
if (Number.isNaN(at))
|
|
78
|
+
return null;
|
|
79
|
+
return Math.min(Math.max(0, at - Date.now()), MAX_RETRY_DELAY_MS);
|
|
80
|
+
}
|
|
81
|
+
function retryAfterFromHeaders(headers) {
|
|
82
|
+
const direct = parseRetryAfter(headers["retry-after"]);
|
|
83
|
+
if (direct !== null)
|
|
84
|
+
return direct;
|
|
85
|
+
const reset = headers["x-ratelimit-reset"];
|
|
86
|
+
if (reset === undefined)
|
|
87
|
+
return null;
|
|
88
|
+
const seconds = Number(reset.trim());
|
|
89
|
+
if (!Number.isFinite(seconds))
|
|
90
|
+
return null;
|
|
91
|
+
return Math.min(Math.max(0, seconds * 1000 - Date.now()), MAX_RETRY_DELAY_MS);
|
|
92
|
+
}
|
|
62
93
|
function classify(status, headers, text) {
|
|
63
94
|
const remaining = headers["x-ratelimit-remaining"];
|
|
64
95
|
const reset = headers["x-ratelimit-reset"];
|
|
@@ -67,27 +98,26 @@ function classify(status, headers, text) {
|
|
|
67
98
|
return new GitHubError("AUTH_REQUIRED", "GitHub rejected the stored credentials; run gh auth login", 401, detail);
|
|
68
99
|
if (status === 403 && remaining === "0") {
|
|
69
100
|
const suffix = reset === undefined ? "" : " (resets at " + reset + ")";
|
|
70
|
-
return new GitHubError("RATE_LIMITED", "GitHub primary rate limit exhausted" + suffix, 403, detail);
|
|
101
|
+
return new GitHubError("RATE_LIMITED", "GitHub primary rate limit exhausted" + suffix, 403, detail, retryAfterFromHeaders(headers));
|
|
71
102
|
}
|
|
72
103
|
if (status === 403)
|
|
73
104
|
return new GitHubError("PERMISSION_DENIED", "the token cannot read this resource", 403, detail);
|
|
74
105
|
if (status === 404)
|
|
75
106
|
return new GitHubError("NOT_FOUND", "resource not found or not accessible", 404, detail);
|
|
76
107
|
if (status === 429)
|
|
77
|
-
return new GitHubError("RATE_LIMITED", "GitHub secondary rate limit hit", 429, detail);
|
|
108
|
+
return new GitHubError("RATE_LIMITED", "GitHub secondary rate limit hit", 429, detail, retryAfterFromHeaders(headers));
|
|
78
109
|
return new GitHubError("NETWORK_ERROR", "unexpected GitHub response: HTTP " + String(status), status, detail);
|
|
79
110
|
}
|
|
80
|
-
function retryDelay(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return Math.min(parsed * 1000, 30_000);
|
|
85
|
-
return Math.min(500 * 2 ** attempt, 8_000);
|
|
111
|
+
function retryDelay(failure, attempt) {
|
|
112
|
+
if (failure.retryAfterMs !== null)
|
|
113
|
+
return failure.retryAfterMs;
|
|
114
|
+
return Math.min(500 * 2 ** attempt, MAX_RETRY_DELAY_MS);
|
|
86
115
|
}
|
|
87
116
|
/** Read-only GitHub.com transport: allowlisted GETs through the user`s gh credentials. */
|
|
88
117
|
export class GhTransport {
|
|
89
118
|
budget;
|
|
90
119
|
runner;
|
|
120
|
+
timeoutMs;
|
|
91
121
|
retries;
|
|
92
122
|
sleep;
|
|
93
123
|
cache;
|
|
@@ -95,6 +125,7 @@ export class GhTransport {
|
|
|
95
125
|
constructor(options) {
|
|
96
126
|
this.budget = options.budget;
|
|
97
127
|
this.runner = options.runner ?? ghCliRunner;
|
|
128
|
+
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
98
129
|
this.retries = options.retries ?? DEFAULT_RETRIES;
|
|
99
130
|
this.sleep = options.sleep ?? delay;
|
|
100
131
|
this.cache = options.cache ?? true;
|
|
@@ -116,10 +147,9 @@ export class GhTransport {
|
|
|
116
147
|
catch (error) {
|
|
117
148
|
const failure = error instanceof GitHubError ? error : new GitHubError("NETWORK_ERROR", String(error));
|
|
118
149
|
lastError = failure;
|
|
119
|
-
|
|
120
|
-
if (!transient || attempt === this.retries)
|
|
150
|
+
if (!isTransient(failure) || attempt === this.retries)
|
|
121
151
|
break;
|
|
122
|
-
const wait = retryDelay(failure
|
|
152
|
+
const wait = retryDelay(failure, attempt);
|
|
123
153
|
await this.sleep(wait);
|
|
124
154
|
}
|
|
125
155
|
finally {
|
|
@@ -135,13 +165,15 @@ export class GhTransport {
|
|
|
135
165
|
args.push(path);
|
|
136
166
|
let result;
|
|
137
167
|
try {
|
|
138
|
-
result = await this.runner(args);
|
|
168
|
+
result = await this.runner(args, this.timeoutMs);
|
|
139
169
|
}
|
|
140
170
|
catch (error) {
|
|
141
171
|
throw new GitHubError("NETWORK_ERROR", "gh invocation failed: " + String(error));
|
|
142
172
|
}
|
|
143
173
|
if (result.exitCode === 127)
|
|
144
174
|
throw new GitHubError("GH_NOT_INSTALLED", "gh CLI is not installed or not on PATH", null, result.stderr.trim());
|
|
175
|
+
if (result.timedOut === true)
|
|
176
|
+
throw new GitHubError("NETWORK_ERROR", "gh timed out after " + String(this.timeoutMs) + "ms", 0);
|
|
145
177
|
const parsed = parseResponse(result.stdout);
|
|
146
178
|
if (parsed.status === 304) {
|
|
147
179
|
return { status: 304, headers: parsed.headers, body: null, etag, notified304: true };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../../../src/adapters/github/transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../../../src/adapters/github/transport.ts"],"names":[],"mappings":"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"}
|