@bakers-build/addon-forge-github 0.9.1-next.4 → 0.9.1-next.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/index.js +63 -6
- package/options.schema.json +5 -18
- package/package.json +4 -4
package/dist/src/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// shortly before it expires; or, with `auth: 'gh'`, the conductor's own `gh`
|
|
4
4
|
// login. The repository comes from config, never from a clone's `origin`.
|
|
5
5
|
import { createSign } from 'node:crypto';
|
|
6
|
-
import { cliToken, requestJson, } from '@bakers-build/addon-forge/backend';
|
|
6
|
+
import { cliToken, requestJson, requestText, tail, } from '@bakers-build/addon-forge/backend';
|
|
7
7
|
const API = 'https://api.github.com';
|
|
8
8
|
const REFRESH_MS = 60_000;
|
|
9
9
|
function settingsOf(raw) {
|
|
@@ -64,6 +64,7 @@ function stateStatus(state) {
|
|
|
64
64
|
return 'success';
|
|
65
65
|
return state === 'pending' ? 'pending' : 'failure';
|
|
66
66
|
}
|
|
67
|
+
const NO_RUN = { run_id: null, job_id: null, run_started_at: null };
|
|
67
68
|
const basic = (token) => Buffer.from(`x-access-token:${token}`).toString('base64');
|
|
68
69
|
const factory = async (raw, context) => {
|
|
69
70
|
const s = settingsOf(raw);
|
|
@@ -101,7 +102,7 @@ const factory = async (raw, context) => {
|
|
|
101
102
|
};
|
|
102
103
|
return out.token;
|
|
103
104
|
};
|
|
104
|
-
const api = async (path, init = {}) => requestJson(doFetch, `${repo}${path}`, {
|
|
105
|
+
const api = async (path, init = {}) => (init.text ? requestText : requestJson)(doFetch, `${repo}${path}`, {
|
|
105
106
|
method: init.method ?? 'GET',
|
|
106
107
|
headers: {
|
|
107
108
|
authorization: `Bearer ${await accessToken()}`,
|
|
@@ -135,11 +136,11 @@ const factory = async (raw, context) => {
|
|
|
135
136
|
openPrsWithBase: async (base) => (await api(`/pulls?state=open&base=${encodeURIComponent(base)}&per_page=100`))
|
|
136
137
|
.filter((p) => p.base.ref === base)
|
|
137
138
|
.map(prOf),
|
|
138
|
-
checks: async (ref, base) => {
|
|
139
|
+
checks: async (ref, base, opts = {}) => {
|
|
139
140
|
const at = encodeURIComponent(ref);
|
|
140
141
|
const b = encodeURIComponent(base);
|
|
141
142
|
const [runs, status, rules, branch] = await Promise.all([
|
|
142
|
-
api(`/commits/${at}/check-runs?per_page=100`),
|
|
143
|
+
api(`/commits/${at}/check-runs?filter=all&per_page=100`),
|
|
143
144
|
api(`/commits/${at}/status`),
|
|
144
145
|
api(`/rules/branches/${b}`, { allow404: true }),
|
|
145
146
|
api(`/branches/${b}`, { allow404: true }),
|
|
@@ -151,21 +152,67 @@ const factory = async (raw, context) => {
|
|
|
151
152
|
.map((c) => c.context),
|
|
152
153
|
...(branch?.protection?.required_status_checks?.contexts ?? []),
|
|
153
154
|
]);
|
|
155
|
+
// shortcut: one page only; a partial list could hide a newer attempt, so refuse it; paginate when this trips.
|
|
156
|
+
if ((runs.total_count ?? 0) > runs.check_runs.length)
|
|
157
|
+
throw new Error(`${ref} has ${runs.total_count} check runs, more than the ${runs.check_runs.length} one page holds; the result would be partial`);
|
|
158
|
+
// `ref` may be a branch: the runs lookup needs the commit the check runs report.
|
|
159
|
+
const sha = runs.check_runs[0]?.head_sha;
|
|
160
|
+
const workflowRuns = sha
|
|
161
|
+
? (await api(`/actions/runs?head_sha=${encodeURIComponent(sha)}&per_page=100`)).workflow_runs
|
|
162
|
+
: [];
|
|
163
|
+
const bySuite = new Map(workflowRuns.map((w) => [w.check_suite_id, w]));
|
|
164
|
+
const newest = new Map();
|
|
165
|
+
for (const w of workflowRuns) {
|
|
166
|
+
const kept = newest.get(w.workflow_id);
|
|
167
|
+
if (!kept ||
|
|
168
|
+
w.run_started_at > kept.run_started_at ||
|
|
169
|
+
(w.run_started_at === kept.run_started_at && w.id > kept.id))
|
|
170
|
+
newest.set(w.workflow_id, w);
|
|
171
|
+
}
|
|
172
|
+
let jobs = runs.check_runs.map((r) => ({
|
|
173
|
+
r,
|
|
174
|
+
w: r.check_suite ? bySuite.get(r.check_suite.id) : undefined,
|
|
175
|
+
}));
|
|
176
|
+
if (!opts.allRuns) {
|
|
177
|
+
// An Actions check run without a listed run belongs to an earlier attempt.
|
|
178
|
+
jobs = jobs.filter(({ r, w }) => w
|
|
179
|
+
? newest.get(w.workflow_id) === w
|
|
180
|
+
: r.app?.slug !== 'github-actions');
|
|
181
|
+
// A re-run attempt is a newer check run of the same name in the same run.
|
|
182
|
+
const latest = new Map();
|
|
183
|
+
const key = (j) => `${j.w?.id ?? ''}\0${j.r.name}`;
|
|
184
|
+
for (const j of jobs)
|
|
185
|
+
latest.set(key(j), Math.max(latest.get(key(j)) ?? 0, j.r.id));
|
|
186
|
+
jobs = jobs.filter((j) => latest.get(key(j)) === j.r.id);
|
|
187
|
+
}
|
|
154
188
|
const rows = [
|
|
155
|
-
...
|
|
189
|
+
...jobs.map(({ r, w }) => ({
|
|
156
190
|
name: r.name,
|
|
157
191
|
status: runStatus(r),
|
|
158
192
|
required: required.has(r.name),
|
|
193
|
+
conclusion: r.conclusion,
|
|
194
|
+
run_id: w?.id ?? null,
|
|
195
|
+
// An Actions job's id is its check run's id.
|
|
196
|
+
job_id: w ? r.id : null,
|
|
197
|
+
run_started_at: w?.run_started_at ?? null,
|
|
159
198
|
})),
|
|
160
199
|
...status.statuses.map((st) => ({
|
|
161
200
|
name: st.context,
|
|
162
201
|
status: stateStatus(st.state),
|
|
163
202
|
required: required.has(st.context),
|
|
203
|
+
conclusion: st.state,
|
|
204
|
+
...NO_RUN,
|
|
164
205
|
})),
|
|
165
206
|
];
|
|
166
207
|
for (const name of required)
|
|
167
208
|
if (!rows.some((r) => r.name === name))
|
|
168
|
-
rows.push({
|
|
209
|
+
rows.push({
|
|
210
|
+
name,
|
|
211
|
+
status: 'missing',
|
|
212
|
+
required: true,
|
|
213
|
+
conclusion: null,
|
|
214
|
+
...NO_RUN,
|
|
215
|
+
});
|
|
169
216
|
return rows;
|
|
170
217
|
},
|
|
171
218
|
mergeBlocker: async (pr) => {
|
|
@@ -190,6 +237,16 @@ const factory = async (raw, context) => {
|
|
|
190
237
|
});
|
|
191
238
|
},
|
|
192
239
|
isMerged: async (pr) => (await pull(pr.number)).merged === true,
|
|
240
|
+
// The API redirects to a signed download URL; fetch follows it.
|
|
241
|
+
jobLog: async (jobId, maxBytes) => {
|
|
242
|
+
const log = (await api(`/actions/jobs/${jobId}/logs`, {
|
|
243
|
+
allow404: true,
|
|
244
|
+
text: true,
|
|
245
|
+
}));
|
|
246
|
+
if (log === undefined)
|
|
247
|
+
throw new Error(`job ${jobId} is not a job of ${s.repository}`);
|
|
248
|
+
return tail(log, maxBytes);
|
|
249
|
+
},
|
|
193
250
|
};
|
|
194
251
|
return backend;
|
|
195
252
|
};
|
package/options.schema.json
CHANGED
|
@@ -5,9 +5,7 @@
|
|
|
5
5
|
"description": "The forge entry's with.backend.with: a GitHub App, or the conductor's gh login with auth 'gh'.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": false,
|
|
8
|
-
"required": [
|
|
9
|
-
"repository"
|
|
10
|
-
],
|
|
8
|
+
"required": ["repository"],
|
|
11
9
|
"properties": {
|
|
12
10
|
"repository": {
|
|
13
11
|
"type": "string",
|
|
@@ -19,18 +17,12 @@
|
|
|
19
17
|
"description": "Use the conductor's own gh login instead of a GitHub App."
|
|
20
18
|
},
|
|
21
19
|
"app_id": {
|
|
22
|
-
"type": [
|
|
23
|
-
"integer",
|
|
24
|
-
"string"
|
|
25
|
-
],
|
|
20
|
+
"type": ["integer", "string"],
|
|
26
21
|
"minLength": 1,
|
|
27
22
|
"description": "The GitHub App's ID; required unless `auth` is set."
|
|
28
23
|
},
|
|
29
24
|
"installation_id": {
|
|
30
|
-
"type": [
|
|
31
|
-
"integer",
|
|
32
|
-
"string"
|
|
33
|
-
],
|
|
25
|
+
"type": ["integer", "string"],
|
|
34
26
|
"minLength": 1,
|
|
35
27
|
"description": "The App's installation on the repository; required unless `auth` is set."
|
|
36
28
|
},
|
|
@@ -46,14 +38,9 @@
|
|
|
46
38
|
}
|
|
47
39
|
},
|
|
48
40
|
"if": {
|
|
49
|
-
"required": [
|
|
50
|
-
"auth"
|
|
51
|
-
]
|
|
41
|
+
"required": ["auth"]
|
|
52
42
|
},
|
|
53
43
|
"else": {
|
|
54
|
-
"required": [
|
|
55
|
-
"app_id",
|
|
56
|
-
"installation_id"
|
|
57
|
-
]
|
|
44
|
+
"required": ["app_id", "installation_id"]
|
|
58
45
|
}
|
|
59
46
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bakers-build/addon-forge-github",
|
|
3
|
-
"version": "0.9.1-next.
|
|
3
|
+
"version": "0.9.1-next.6",
|
|
4
4
|
"description": "bakers.build forge backend for GitHub: a GitHub App installation token scoped to one repository. Implements @bakers-build/addon-forge's ForgeBackend contract.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
"directory": "bakers-cli/addons/forge-github"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
|
-
"@bakers-build/addon-forge": "^0.9.1-next.
|
|
25
|
-
"@bakers-build/core": "^0.9.1-next.
|
|
24
|
+
"@bakers-build/addon-forge": "^0.9.1-next.6",
|
|
25
|
+
"@bakers-build/core": "^0.9.1-next.6"
|
|
26
26
|
},
|
|
27
27
|
"bakersBuild": {
|
|
28
|
-
"commit": "
|
|
28
|
+
"commit": "e7d9921d51d5c045f29ca7e35d1e6a348e6ddf08",
|
|
29
29
|
"channel": "next"
|
|
30
30
|
}
|
|
31
31
|
}
|