@stacksjs/github 0.70.86 → 0.70.88
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/package.json +1 -1
- package/dist/bots.d.ts +0 -7
- package/dist/bots.js +0 -21
- package/dist/client.d.ts +0 -15
- package/dist/client.js +0 -43
- package/dist/dashboard.d.ts +0 -17
- package/dist/dashboard.js +0 -110
- package/dist/failure-detector.d.ts +0 -59
- package/dist/failure-detector.js +0 -67
- package/dist/index.d.ts +0 -35
- package/dist/index.js +0 -9
- package/dist/repos.d.ts +0 -7
- package/dist/repos.js +0 -26
- package/dist/run-history.d.ts +0 -66
- package/dist/run-history.js +0 -63
- package/dist/runner-pressure-detector.d.ts +0 -74
- package/dist/runner-pressure-detector.js +0 -27
- package/dist/runners.d.ts +0 -6
- package/dist/runners.js +0 -26
- package/dist/runs.d.ts +0 -3
- package/dist/runs.js +0 -84
- package/dist/types.d.ts +0 -59
- package/dist/types.js +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacksjs/github",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.70.
|
|
4
|
+
"version": "0.70.88",
|
|
5
5
|
"description": "GitHub API client used by Stacks framework features (dashboard CI surface, notifications, runner alerts).",
|
|
6
6
|
"author": "Chris Breuer",
|
|
7
7
|
"contributors": [
|
package/dist/bots.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Count open PRs authored by a GitHub App across every repo in an org.
|
|
3
|
-
* Returns a map keyed by `owner/repo` so callers can attribute counts back
|
|
4
|
-
* to the right repo card. Used to surface Renovate / GitHub Actions bot
|
|
5
|
-
* traffic in the dashboard.
|
|
6
|
-
*/
|
|
7
|
-
export declare function fetchBotPRCounts(org: string, authorSlug: string): Promise<Map<string, number>>;
|
package/dist/bots.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { ghFetch, GITHUB_API } from "./client";
|
|
2
|
-
export async function fetchBotPRCounts(org, authorSlug) {
|
|
3
|
-
const counts = new Map;
|
|
4
|
-
let page = 1;
|
|
5
|
-
while (!0) {
|
|
6
|
-
const q = `is:pr is:open org:${org} author:app/${authorSlug}`, res = await ghFetch(`${GITHUB_API}/search/issues?q=${encodeURIComponent(q)}&per_page=100&page=${page}`);
|
|
7
|
-
if (!res.ok)
|
|
8
|
-
break;
|
|
9
|
-
const data = await res.json();
|
|
10
|
-
if (!data.items || data.items.length === 0)
|
|
11
|
-
break;
|
|
12
|
-
for (const item of data.items) {
|
|
13
|
-
const fullName = item.repository_url.replace(`${GITHUB_API}/repos/`, "");
|
|
14
|
-
counts.set(fullName, (counts.get(fullName) ?? 0) + 1);
|
|
15
|
-
}
|
|
16
|
-
if (data.items.length < 100)
|
|
17
|
-
break;
|
|
18
|
-
page++;
|
|
19
|
-
}
|
|
20
|
-
return counts;
|
|
21
|
-
}
|
package/dist/client.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export declare function ghHeaders(): Record<string, string>;
|
|
2
|
-
/**
|
|
3
|
-
* `fetch` against the GitHub API that retries on secondary rate limits.
|
|
4
|
-
*
|
|
5
|
-
* GitHub signals back-off via either a `Retry-After` header (preferred) or
|
|
6
|
-
* an `x-ratelimit-reset` epoch second. The retry budget is bounded so a
|
|
7
|
-
* permanently rate-limited token doesn't hang callers indefinitely.
|
|
8
|
-
*/
|
|
9
|
-
export declare function ghFetch(url: string, attempt?: number): Promise<Response>;
|
|
10
|
-
/**
|
|
11
|
-
* Run `fn` over `items` with at most `limit` concurrent invocations.
|
|
12
|
-
* Result indices match input indices.
|
|
13
|
-
*/
|
|
14
|
-
export declare function mapWithConcurrency<T, R>(items: T[], limit: number, fn: (item: T) => Promise<R>): Promise<R[]>;
|
|
15
|
-
export declare const GITHUB_API: 'https://api.github.com';
|
package/dist/client.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
export const GITHUB_API = "https://api.github.com";
|
|
2
|
-
function getToken() {
|
|
3
|
-
const token = process.env.GITHUB_TOKEN;
|
|
4
|
-
if (!token)
|
|
5
|
-
throw Error("GITHUB_TOKEN environment variable is required");
|
|
6
|
-
return token;
|
|
7
|
-
}
|
|
8
|
-
export function ghHeaders() {
|
|
9
|
-
return {
|
|
10
|
-
Authorization: `Bearer ${getToken()}`,
|
|
11
|
-
Accept: "application/vnd.github+json",
|
|
12
|
-
"X-GitHub-Api-Version": "2022-11-28"
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
export async function ghFetch(url, attempt = 0) {
|
|
16
|
-
const res = await fetch(url, { headers: ghHeaders() });
|
|
17
|
-
if (res.ok || attempt >= 3)
|
|
18
|
-
return res;
|
|
19
|
-
if (!(res.status === 429 || res.status === 403 && (res.headers.get("x-ratelimit-remaining") === "0" || res.headers.get("retry-after"))))
|
|
20
|
-
return res;
|
|
21
|
-
const retryAfterHeader = res.headers.get("retry-after"), resetHeader = res.headers.get("x-ratelimit-reset");
|
|
22
|
-
let waitMs = 1000 * 2 ** attempt;
|
|
23
|
-
if (retryAfterHeader)
|
|
24
|
-
waitMs = Number(retryAfterHeader) * 1000;
|
|
25
|
-
else if (resetHeader)
|
|
26
|
-
waitMs = Math.max(0, Number(resetHeader) * 1000 - Date.now()) + 500;
|
|
27
|
-
await new Promise((r) => setTimeout(r, Math.min(waitMs, 30000)));
|
|
28
|
-
return ghFetch(url, attempt + 1);
|
|
29
|
-
}
|
|
30
|
-
export async function mapWithConcurrency(items, limit, fn) {
|
|
31
|
-
const results = Array.from({ length: items.length });
|
|
32
|
-
let next = 0;
|
|
33
|
-
async function worker() {
|
|
34
|
-
while (!0) {
|
|
35
|
-
const i = next++;
|
|
36
|
-
if (i >= items.length)
|
|
37
|
-
return;
|
|
38
|
-
results[i] = await fn(items[i]);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => worker()));
|
|
42
|
-
return results;
|
|
43
|
-
}
|
package/dist/dashboard.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { DashboardData, DashboardOptions } from './types';
|
|
2
|
-
/**
|
|
3
|
-
* Aggregated CI/runner snapshot across the configured orgs.
|
|
4
|
-
*
|
|
5
|
-
* Stale-while-revalidate: if a cached snapshot exists, it is returned
|
|
6
|
-
* immediately and a background refresh kicks off once the TTL has elapsed.
|
|
7
|
-
* First-ever call (no in-memory and no on-disk cache) waits for the build
|
|
8
|
-
* so the dashboard renders against real data instead of `null`.
|
|
9
|
-
*/
|
|
10
|
-
export declare function getDashboardData(opts: DashboardOptions): Promise<DashboardData>;
|
|
11
|
-
/**
|
|
12
|
-
* Drop the in-memory cache for a given cache path. Disk cache is left
|
|
13
|
-
* alone — callers that want a true reset should `Bun.write` an empty file
|
|
14
|
-
* or delete it before re-fetching. Used by tests and by an eventual
|
|
15
|
-
* "Refresh now" UI action.
|
|
16
|
-
*/
|
|
17
|
-
export declare function clearDashboardCache(cachePath?: unknown): void;
|
package/dist/dashboard.js
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { fetchBotPRCounts } from "./bots";
|
|
2
|
-
import { mapWithConcurrency } from "./client";
|
|
3
|
-
import { fetchAllRepos } from "./repos";
|
|
4
|
-
import { fetchRepoActiveRuns } from "./runners";
|
|
5
|
-
import { fetchRepoStatus } from "./runs";
|
|
6
|
-
const DEFAULT_TTL_MS = 30000, DEFAULT_CACHE_PATH = ".cache/dashboard.json", DEFAULT_RUNNER_CAP = 20, caches = new Map;
|
|
7
|
-
function entryFor(path) {
|
|
8
|
-
let e = caches.get(path);
|
|
9
|
-
if (!e) {
|
|
10
|
-
e = { data: null, savedAt: 0, inflight: null, diskLoaded: !1 };
|
|
11
|
-
caches.set(path, e);
|
|
12
|
-
}
|
|
13
|
-
return e;
|
|
14
|
-
}
|
|
15
|
-
async function loadCacheFromDisk(entry, path) {
|
|
16
|
-
if (entry.diskLoaded)
|
|
17
|
-
return;
|
|
18
|
-
entry.diskLoaded = !0;
|
|
19
|
-
try {
|
|
20
|
-
const file = Bun.file(path);
|
|
21
|
-
if (!await file.exists())
|
|
22
|
-
return;
|
|
23
|
-
const stored = await file.json();
|
|
24
|
-
entry.data = stored.data;
|
|
25
|
-
entry.savedAt = stored.savedAt;
|
|
26
|
-
} catch {}
|
|
27
|
-
}
|
|
28
|
-
async function saveCacheToDisk(path, data, savedAt) {
|
|
29
|
-
try {
|
|
30
|
-
await Bun.write(path, JSON.stringify({ data, savedAt }));
|
|
31
|
-
} catch (err) {
|
|
32
|
-
console.warn("[github/dashboard] cache write failed:", err);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
async function buildDashboardData(opts) {
|
|
36
|
-
const orgs = opts.orgs, runnerCaps = opts.runnerCaps ?? {}, defaultRunnerCap = opts.defaultRunnerCap ?? DEFAULT_RUNNER_CAP, repos = await fetchAllRepos(orgs, opts.ignoreRepos), statuses = await mapWithConcurrency(repos, 8, (r) => fetchRepoStatus(r.owner, r.name, r.default_branch)), prCountMaps = await Promise.all(orgs.flatMap((org) => [
|
|
37
|
-
fetchBotPRCounts(org, "renovate").then((m) => ({ type: "renovate", map: m })),
|
|
38
|
-
fetchBotPRCounts(org, "github-actions").then((m) => ({ type: "actions", map: m }))
|
|
39
|
-
])), renovateCounts = new Map, actionsCounts = new Map;
|
|
40
|
-
for (const { type, map } of prCountMaps) {
|
|
41
|
-
const target = type === "renovate" ? renovateCounts : actionsCounts;
|
|
42
|
-
for (const [k, v] of map)
|
|
43
|
-
target.set(k, (target.get(k) ?? 0) + v);
|
|
44
|
-
}
|
|
45
|
-
for (const s of statuses) {
|
|
46
|
-
const rCount = renovateCounts.get(s.fullName) ?? 0, aCount = actionsCounts.get(s.fullName) ?? 0;
|
|
47
|
-
s.renovatePRs = rCount;
|
|
48
|
-
s.actionsPRs = aCount;
|
|
49
|
-
if (rCount > 0)
|
|
50
|
-
s.renovatePRsUrl = `https://github.com/${s.fullName}/pulls?q=${encodeURIComponent("is:pr is:open author:app/renovate")}`;
|
|
51
|
-
if (aCount > 0)
|
|
52
|
-
s.actionsPRsUrl = `https://github.com/${s.fullName}/pulls?q=${encodeURIComponent("is:pr is:open author:app/github-actions")}`;
|
|
53
|
-
}
|
|
54
|
-
const runnerCounts = await mapWithConcurrency(repos, 8, async (r) => ({
|
|
55
|
-
owner: r.owner,
|
|
56
|
-
...await fetchRepoActiveRuns(r.owner, r.name)
|
|
57
|
-
})), runners = {};
|
|
58
|
-
for (const org of orgs)
|
|
59
|
-
runners[org] = { running: 0, queued: 0, cap: runnerCaps[org] ?? defaultRunnerCap };
|
|
60
|
-
for (const c of runnerCounts) {
|
|
61
|
-
if (!runners[c.owner])
|
|
62
|
-
runners[c.owner] = { running: 0, queued: 0, cap: runnerCaps[c.owner] ?? defaultRunnerCap };
|
|
63
|
-
runners[c.owner].running += c.running;
|
|
64
|
-
runners[c.owner].queued += c.queued;
|
|
65
|
-
}
|
|
66
|
-
const order = { failure: 0, error: 1, pending: 2, success: 3, no_runs: 4 };
|
|
67
|
-
statuses.sort((a, b) => (order[a.status] ?? 5) - (order[b.status] ?? 5));
|
|
68
|
-
return {
|
|
69
|
-
repos: statuses,
|
|
70
|
-
fetchedAt: new Date().toISOString(),
|
|
71
|
-
total: statuses.length,
|
|
72
|
-
passing: statuses.filter((r) => r.status === "success").length,
|
|
73
|
-
failing: statuses.filter((r) => r.status === "failure" || r.status === "error").length,
|
|
74
|
-
pending: statuses.filter((r) => r.status === "pending").length,
|
|
75
|
-
noRuns: statuses.filter((r) => r.status === "no_runs").length,
|
|
76
|
-
runners
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
export async function getDashboardData(opts) {
|
|
80
|
-
const ttl = opts.cacheTtlMs ?? DEFAULT_TTL_MS, path = opts.cachePath ?? DEFAULT_CACHE_PATH, entry = entryFor(path);
|
|
81
|
-
await loadCacheFromDisk(entry, path);
|
|
82
|
-
const now = Date.now();
|
|
83
|
-
if (entry.data) {
|
|
84
|
-
if (now - entry.savedAt >= ttl && !entry.inflight) {
|
|
85
|
-
entry.inflight = buildDashboardData(opts).then(async (data) => {
|
|
86
|
-
entry.data = data;
|
|
87
|
-
entry.savedAt = Date.now();
|
|
88
|
-
await saveCacheToDisk(path, data, entry.savedAt);
|
|
89
|
-
return data;
|
|
90
|
-
}).finally(() => {
|
|
91
|
-
entry.inflight = null;
|
|
92
|
-
});
|
|
93
|
-
entry.inflight.catch((err) => console.warn("[github/dashboard] refresh failed:", err));
|
|
94
|
-
}
|
|
95
|
-
return entry.data;
|
|
96
|
-
}
|
|
97
|
-
if (!entry.inflight)
|
|
98
|
-
entry.inflight = buildDashboardData(opts).then(async (data) => {
|
|
99
|
-
entry.data = data;
|
|
100
|
-
entry.savedAt = Date.now();
|
|
101
|
-
await saveCacheToDisk(path, data, entry.savedAt);
|
|
102
|
-
return data;
|
|
103
|
-
}).finally(() => {
|
|
104
|
-
entry.inflight = null;
|
|
105
|
-
});
|
|
106
|
-
return entry.inflight;
|
|
107
|
-
}
|
|
108
|
-
export function clearDashboardCache(cachePath = DEFAULT_CACHE_PATH) {
|
|
109
|
-
caches.delete(cachePath);
|
|
110
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import type { RepoStatus } from './types';
|
|
2
|
-
/**
|
|
3
|
-
* Compute the failed-transition list.
|
|
4
|
-
*
|
|
5
|
-
* The "transition" is gated on:
|
|
6
|
-
*
|
|
7
|
-
* 1. The repo is currently in a failed state.
|
|
8
|
-
* 2. The repo is NOT in flight (pending). Pending → failure happens
|
|
9
|
-
* mid-run; firing on it would just produce noise once the run
|
|
10
|
-
* finishes and the same failure resurfaces with a final
|
|
11
|
-
* conclusion.
|
|
12
|
-
* 3. The previous state was *not* failed. (Sticky-red repos don't
|
|
13
|
-
* keep firing.)
|
|
14
|
-
* 4. The run id changed — if the same failed run id is still
|
|
15
|
-
* surfacing, this is a duplicate poll, not a new failure.
|
|
16
|
-
* 5. Cooldown: `lastNotifiedAt` is either null or older than
|
|
17
|
-
* `cooldownMs` ago.
|
|
18
|
-
*/
|
|
19
|
-
export declare function detectNewlyFailedRuns(snapshot: { repos: RepoStatus[] }, previousStates: Map<string, PreviousRunState>, options?: DetectOptions): FailedTransition[];
|
|
20
|
-
/**
|
|
21
|
-
* Pure failure-transition detector (stacksjs/stacks#1849).
|
|
22
|
-
*
|
|
23
|
-
* Given a fresh CI snapshot and the previous per-repo states the
|
|
24
|
-
* dashboard knows about, return the list of repos that *just*
|
|
25
|
-
* transitioned to a failed conclusion — the moment worth firing a
|
|
26
|
-
* notification on. Anything still failing, or newly passing, or
|
|
27
|
-
* still in flight, is silenced.
|
|
28
|
-
*
|
|
29
|
-
* Pure for a reason: easy to unit-test the transition matrix without
|
|
30
|
-
* a DB, a notify implementation, or the network. The persistence
|
|
31
|
-
* layer + notification fan-out live in defaults so framework
|
|
32
|
-
* packages stay infrastructure-free.
|
|
33
|
-
*
|
|
34
|
-
* @see PreviousRunState what the persistence layer hands in
|
|
35
|
-
* @see FailedTransition what we return — caller decides how to notify
|
|
36
|
-
*/
|
|
37
|
-
/** What a caller stored about a repo on its last snapshot pass. */
|
|
38
|
-
export declare interface PreviousRunState {
|
|
39
|
-
repoFullName: string
|
|
40
|
-
lastConclusion: string | null
|
|
41
|
-
lastRunId: number | null
|
|
42
|
-
lastNotifiedAt: string | null
|
|
43
|
-
}
|
|
44
|
-
/** Per-transition payload the detector hands back to the caller. */
|
|
45
|
-
export declare interface FailedTransition {
|
|
46
|
-
repoFullName: string
|
|
47
|
-
conclusion: string
|
|
48
|
-
runId: number | null
|
|
49
|
-
workflowName: string | null
|
|
50
|
-
commitSha: string | null
|
|
51
|
-
commitMessage: string | null
|
|
52
|
-
commitAuthor: string | null
|
|
53
|
-
runUrl: string | null
|
|
54
|
-
previousConclusion: string | null
|
|
55
|
-
}
|
|
56
|
-
export declare interface DetectOptions {
|
|
57
|
-
cooldownMs?: number
|
|
58
|
-
now?: number
|
|
59
|
-
}
|
package/dist/failure-detector.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
const DEFAULT_COOLDOWN_MS = 300000, FAILED_CONCLUSIONS = new Set([
|
|
2
|
-
"failure",
|
|
3
|
-
"error",
|
|
4
|
-
"timed_out",
|
|
5
|
-
"startup_failure"
|
|
6
|
-
]);
|
|
7
|
-
function isFailed(repo) {
|
|
8
|
-
if (repo.status === "failure" || repo.status === "error")
|
|
9
|
-
return !0;
|
|
10
|
-
if (repo.conclusion && FAILED_CONCLUSIONS.has(repo.conclusion))
|
|
11
|
-
return !0;
|
|
12
|
-
return !1;
|
|
13
|
-
}
|
|
14
|
-
function isInFlight(repo) {
|
|
15
|
-
return repo.status === "pending";
|
|
16
|
-
}
|
|
17
|
-
export function detectNewlyFailedRuns(snapshot, previousStates, options = {}) {
|
|
18
|
-
const cooldownMs = options.cooldownMs ?? DEFAULT_COOLDOWN_MS, now = options.now ?? Date.now(), transitions = [];
|
|
19
|
-
for (const repo of snapshot.repos) {
|
|
20
|
-
if (!isFailed(repo) || isInFlight(repo))
|
|
21
|
-
continue;
|
|
22
|
-
const prev = previousStates.get(repo.fullName);
|
|
23
|
-
if (prev?.lastConclusion ? FAILED_CONCLUSIONS.has(prev.lastConclusion) || prev.lastConclusion === "failure" || prev.lastConclusion === "error" : !1) {
|
|
24
|
-
const currentRunId = parseRunIdFromUrl(repo.runUrl);
|
|
25
|
-
if (currentRunId !== null && prev?.lastRunId !== null && currentRunId === prev?.lastRunId)
|
|
26
|
-
continue;
|
|
27
|
-
if (prev?.lastNotifiedAt && isWithinCooldown(prev.lastNotifiedAt, cooldownMs, now))
|
|
28
|
-
continue;
|
|
29
|
-
transitions.push(toTransition(repo, prev?.lastConclusion ?? null));
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
if (prev?.lastNotifiedAt && isWithinCooldown(prev.lastNotifiedAt, cooldownMs, now))
|
|
33
|
-
continue;
|
|
34
|
-
transitions.push(toTransition(repo, prev?.lastConclusion ?? null));
|
|
35
|
-
}
|
|
36
|
-
return transitions;
|
|
37
|
-
}
|
|
38
|
-
function isWithinCooldown(lastNotifiedAt, cooldownMs, now) {
|
|
39
|
-
if (cooldownMs === 0)
|
|
40
|
-
return !1;
|
|
41
|
-
const ts = Date.parse(lastNotifiedAt);
|
|
42
|
-
if (Number.isNaN(ts))
|
|
43
|
-
return !1;
|
|
44
|
-
return now - ts < cooldownMs;
|
|
45
|
-
}
|
|
46
|
-
function parseRunIdFromUrl(runUrl) {
|
|
47
|
-
if (!runUrl)
|
|
48
|
-
return null;
|
|
49
|
-
const match = runUrl.match(/\/actions\/runs\/(\d+)/);
|
|
50
|
-
if (!match)
|
|
51
|
-
return null;
|
|
52
|
-
const id = Number(match[1]);
|
|
53
|
-
return Number.isFinite(id) ? id : null;
|
|
54
|
-
}
|
|
55
|
-
function toTransition(repo, previousConclusion) {
|
|
56
|
-
return {
|
|
57
|
-
repoFullName: repo.fullName,
|
|
58
|
-
conclusion: repo.conclusion ?? repo.status,
|
|
59
|
-
runId: parseRunIdFromUrl(repo.runUrl),
|
|
60
|
-
workflowName: repo.workflowName,
|
|
61
|
-
commitSha: repo.commitSha,
|
|
62
|
-
commitMessage: repo.commitMessage,
|
|
63
|
-
commitAuthor: repo.commitAuthor,
|
|
64
|
-
runUrl: repo.runUrl,
|
|
65
|
-
previousConclusion
|
|
66
|
-
};
|
|
67
|
-
}
|
package/dist/index.d.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
export type { DetectOptions, FailedTransition, PreviousRunState } from './failure-detector';
|
|
2
|
-
export type { WorkflowJob, WorkflowRun } from './run-history';
|
|
3
|
-
export type {
|
|
4
|
-
DetectOptions as PressureDetectOptions,
|
|
5
|
-
PressureAction,
|
|
6
|
-
RunnerAlertState,
|
|
7
|
-
RunnerSample,
|
|
8
|
-
} from './runner-pressure-detector';
|
|
9
|
-
export type {
|
|
10
|
-
DashboardData,
|
|
11
|
-
DashboardOptions,
|
|
12
|
-
FailedJob,
|
|
13
|
-
OrgRunnerUsage,
|
|
14
|
-
Repo,
|
|
15
|
-
RepoStatus,
|
|
16
|
-
RepoStatusKind,
|
|
17
|
-
} from './types';
|
|
18
|
-
/**
|
|
19
|
-
* @stacksjs/github — thin GitHub API client used by Stacks framework
|
|
20
|
-
* features (dashboard CI surface, future runner alerts, kanban→PR links).
|
|
21
|
-
*
|
|
22
|
-
* Top-level entry: {@link getDashboardData} returns the aggregated snapshot
|
|
23
|
-
* the dashboard renders. Lower-level helpers (`fetchAllRepos`,
|
|
24
|
-
* `fetchRepoStatus`, …) are also exported so other surfaces can pull a
|
|
25
|
-
* single dimension without pulling the whole snapshot through.
|
|
26
|
-
*/
|
|
27
|
-
export { fetchBotPRCounts } from './bots';
|
|
28
|
-
export { ghFetch, ghHeaders, GITHUB_API, mapWithConcurrency } from './client';
|
|
29
|
-
export { clearDashboardCache, getDashboardData } from './dashboard';
|
|
30
|
-
export { detectNewlyFailedRuns } from './failure-detector';
|
|
31
|
-
export { fetchAllRepos } from './repos';
|
|
32
|
-
export { fetchRepoActiveRuns } from './runners';
|
|
33
|
-
export { fetchRunJobs, fetchWorkflowRuns } from './run-history';
|
|
34
|
-
export { detectRunnerPressure } from './runner-pressure-detector';
|
|
35
|
-
export { fetchFailedJobs, fetchRepoStatus } from './runs';
|
package/dist/index.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export { fetchBotPRCounts } from "./bots";
|
|
2
|
-
export { ghFetch, ghHeaders, GITHUB_API, mapWithConcurrency } from "./client";
|
|
3
|
-
export { clearDashboardCache, getDashboardData } from "./dashboard";
|
|
4
|
-
export { detectNewlyFailedRuns } from "./failure-detector";
|
|
5
|
-
export { fetchAllRepos } from "./repos";
|
|
6
|
-
export { fetchRepoActiveRuns } from "./runners";
|
|
7
|
-
export { fetchRunJobs, fetchWorkflowRuns } from "./run-history";
|
|
8
|
-
export { detectRunnerPressure } from "./runner-pressure-detector";
|
|
9
|
-
export { fetchFailedJobs, fetchRepoStatus } from "./runs";
|
package/dist/repos.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { Repo } from './types';
|
|
2
|
-
/**
|
|
3
|
-
* Fetch all non-archived public repos across `orgs`, filtering out any names
|
|
4
|
-
* in `ignore` (defaults to `.github` which exists in every org and never has
|
|
5
|
-
* project CI on it).
|
|
6
|
-
*/
|
|
7
|
-
export declare function fetchAllRepos(orgs: string[], ignore?: string[]): Promise<Repo[]>;
|
package/dist/repos.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { ghFetch, GITHUB_API } from "./client";
|
|
2
|
-
export async function fetchAllRepos(orgs, ignore = [".github"]) {
|
|
3
|
-
const ignored = new Set(ignore), all = [];
|
|
4
|
-
for (const org of orgs) {
|
|
5
|
-
let page = 1;
|
|
6
|
-
while (!0) {
|
|
7
|
-
const res = await ghFetch(`${GITHUB_API}/orgs/${org}/repos?per_page=100&page=${page}&type=public`);
|
|
8
|
-
if (!res.ok)
|
|
9
|
-
break;
|
|
10
|
-
const repos = await res.json();
|
|
11
|
-
if (repos.length === 0)
|
|
12
|
-
break;
|
|
13
|
-
for (const repo of repos)
|
|
14
|
-
all.push({
|
|
15
|
-
name: repo.name,
|
|
16
|
-
owner: repo.owner.login,
|
|
17
|
-
full_name: repo.full_name,
|
|
18
|
-
html_url: repo.html_url,
|
|
19
|
-
default_branch: repo.default_branch,
|
|
20
|
-
archived: repo.archived
|
|
21
|
-
});
|
|
22
|
-
page++;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return all.filter((r) => !r.archived && !ignored.has(r.name));
|
|
26
|
-
}
|
package/dist/run-history.d.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The N most recent workflow runs for a repo. Each entry is a
|
|
3
|
-
* lightweight summary — failed-job detail is fetched on-demand via
|
|
4
|
-
* {@link fetchRunJobs} so the drawer only pays the round-trip cost
|
|
5
|
-
* when the user actually expands a row.
|
|
6
|
-
*/
|
|
7
|
-
export declare function fetchWorkflowRuns(owner: string, name: string, options?: FetchRunsOptions): Promise<WorkflowRun[]>;
|
|
8
|
-
/**
|
|
9
|
-
* Per-job detail for a single run. Used by the drilldown drawer when
|
|
10
|
-
* the user expands a failing run to see *which* step broke. Different
|
|
11
|
-
* shape from `fetchFailedJobs` in `runs.ts` (which is at-a-glance
|
|
12
|
-
* "failed job names" for the card) — here we want everything,
|
|
13
|
-
* including step-level breakdown + timing.
|
|
14
|
-
*
|
|
15
|
-
* Returns an empty array on error so the drawer can render an empty
|
|
16
|
-
* state rather than a 500.
|
|
17
|
-
*/
|
|
18
|
-
export declare function fetchRunJobs(owner: string, name: string, runId: number): Promise<WorkflowJob[]>;
|
|
19
|
-
/**
|
|
20
|
-
* Recent-workflow-runs + per-run job detail for the dashboard CI
|
|
21
|
-
* drilldown (stacksjs/stacks#1848).
|
|
22
|
-
*
|
|
23
|
-
* Distinct from `runs.ts` which is the *aggregator* — it folds the
|
|
24
|
-
* latest run per repo into a `RepoStatus` for the at-a-glance card.
|
|
25
|
-
* Here we want history: the last N runs for a single repo, plus the
|
|
26
|
-
* per-job detail for a single run. Different shapes, different
|
|
27
|
-
* callers, easier to keep them in separate files than to overload
|
|
28
|
-
* one module.
|
|
29
|
-
*/
|
|
30
|
-
export declare interface WorkflowRun {
|
|
31
|
-
id: number
|
|
32
|
-
status: 'queued' | 'in_progress' | 'completed' | string
|
|
33
|
-
conclusion: 'success' | 'failure' | 'cancelled' | 'skipped' | 'timed_out' | 'action_required' | 'neutral' | 'startup_failure' | null
|
|
34
|
-
name: string
|
|
35
|
-
headBranch: string | null
|
|
36
|
-
headSha: string
|
|
37
|
-
headShaShort: string
|
|
38
|
-
commitMessage: string | null
|
|
39
|
-
commitAuthor: string | null
|
|
40
|
-
event: string
|
|
41
|
-
url: string
|
|
42
|
-
startedAt: string | null
|
|
43
|
-
updatedAt: string
|
|
44
|
-
durationMs: number | null
|
|
45
|
-
}
|
|
46
|
-
export declare interface WorkflowJob {
|
|
47
|
-
id: number
|
|
48
|
-
name: string
|
|
49
|
-
status: 'queued' | 'in_progress' | 'completed' | string
|
|
50
|
-
conclusion: WorkflowRun['conclusion']
|
|
51
|
-
startedAt: string | null
|
|
52
|
-
completedAt: string | null
|
|
53
|
-
durationMs: number | null
|
|
54
|
-
url: string
|
|
55
|
-
steps: Array<{
|
|
56
|
-
name: string
|
|
57
|
-
status: WorkflowJob['status']
|
|
58
|
-
conclusion: WorkflowRun['conclusion']
|
|
59
|
-
number: number
|
|
60
|
-
}>
|
|
61
|
-
}
|
|
62
|
-
declare interface FetchRunsOptions {
|
|
63
|
-
limit?: number
|
|
64
|
-
branch?: string
|
|
65
|
-
event?: string
|
|
66
|
-
}
|
package/dist/run-history.js
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { ghFetch, GITHUB_API } from "./client";
|
|
2
|
-
function shortSha(sha) {
|
|
3
|
-
return sha.length > 7 ? sha.slice(0, 7) : sha;
|
|
4
|
-
}
|
|
5
|
-
function diffMs(start, end) {
|
|
6
|
-
if (!start || !end)
|
|
7
|
-
return null;
|
|
8
|
-
const s = Date.parse(start), e = Date.parse(end);
|
|
9
|
-
if (Number.isNaN(s) || Number.isNaN(e))
|
|
10
|
-
return null;
|
|
11
|
-
if (e < s)
|
|
12
|
-
return null;
|
|
13
|
-
return e - s;
|
|
14
|
-
}
|
|
15
|
-
export async function fetchWorkflowRuns(owner, name, options = {}) {
|
|
16
|
-
const limit = Math.max(1, Math.min(options.limit ?? 20, 100)), params = new URLSearchParams;
|
|
17
|
-
params.set("per_page", String(limit));
|
|
18
|
-
if (options.branch)
|
|
19
|
-
params.set("branch", options.branch);
|
|
20
|
-
if (options.event)
|
|
21
|
-
params.set("event", options.event);
|
|
22
|
-
const res = await ghFetch(`${GITHUB_API}/repos/${owner}/${name}/actions/runs?${params.toString()}`);
|
|
23
|
-
if (!res.ok)
|
|
24
|
-
return [];
|
|
25
|
-
return ((await res.json()).workflow_runs ?? []).map((r) => ({
|
|
26
|
-
id: r.id,
|
|
27
|
-
status: r.status,
|
|
28
|
-
conclusion: r.conclusion,
|
|
29
|
-
name: r.name,
|
|
30
|
-
headBranch: r.head_branch,
|
|
31
|
-
headSha: r.head_sha,
|
|
32
|
-
headShaShort: shortSha(r.head_sha),
|
|
33
|
-
commitMessage: r.head_commit?.message?.split(`
|
|
34
|
-
`)[0] ?? null,
|
|
35
|
-
commitAuthor: r.head_commit?.author?.name ?? r.actor?.login ?? null,
|
|
36
|
-
event: r.event,
|
|
37
|
-
url: r.html_url,
|
|
38
|
-
startedAt: r.run_started_at ?? r.created_at ?? null,
|
|
39
|
-
updatedAt: r.updated_at,
|
|
40
|
-
durationMs: diffMs(r.run_started_at ?? r.created_at, r.updated_at)
|
|
41
|
-
}));
|
|
42
|
-
}
|
|
43
|
-
export async function fetchRunJobs(owner, name, runId) {
|
|
44
|
-
const res = await ghFetch(`${GITHUB_API}/repos/${owner}/${name}/actions/runs/${runId}/jobs?per_page=100`);
|
|
45
|
-
if (!res.ok)
|
|
46
|
-
return [];
|
|
47
|
-
return ((await res.json()).jobs ?? []).map((j) => ({
|
|
48
|
-
id: j.id,
|
|
49
|
-
name: j.name,
|
|
50
|
-
status: j.status,
|
|
51
|
-
conclusion: j.conclusion,
|
|
52
|
-
startedAt: j.started_at,
|
|
53
|
-
completedAt: j.completed_at,
|
|
54
|
-
durationMs: diffMs(j.started_at, j.completed_at),
|
|
55
|
-
url: j.html_url,
|
|
56
|
-
steps: (j.steps ?? []).map((s) => ({
|
|
57
|
-
name: s.name,
|
|
58
|
-
status: s.status,
|
|
59
|
-
conclusion: s.conclusion,
|
|
60
|
-
number: s.number
|
|
61
|
-
}))
|
|
62
|
-
}));
|
|
63
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Compute the per-org actions.
|
|
3
|
-
*
|
|
4
|
-
* Algorithm:
|
|
5
|
-
*
|
|
6
|
-
* 1. Bucket samples by org.
|
|
7
|
-
* 2. For each org, slice the samples within the last
|
|
8
|
-
* `windowMinutes` of `now` (called `window`).
|
|
9
|
-
* 3. Skip orgs with `window.length === 0` (no samples ever) or
|
|
10
|
-
* where the window is shorter than `windowMinutes`
|
|
11
|
-
* (insufficient data — sustained-pressure can't be proven yet).
|
|
12
|
-
* 4. If the org is currently `alerting`:
|
|
13
|
-
* - If every sample in the window is < threshold → emit
|
|
14
|
-
* `clear` action.
|
|
15
|
-
* 5. If the org is NOT alerting:
|
|
16
|
-
* - If every sample in the window is >= threshold → emit
|
|
17
|
-
* `fire` action.
|
|
18
|
-
*
|
|
19
|
-
* Conditions that DON'T trigger anything:
|
|
20
|
-
*
|
|
21
|
-
* - Mixed window (some above, some below threshold) → still
|
|
22
|
-
* converging; wait for the next refresh.
|
|
23
|
-
* - Sticky alerting with sustained pressure → already alerting,
|
|
24
|
-
* no new action needed.
|
|
25
|
-
* - Sticky clear with sustained calm → no action needed.
|
|
26
|
-
*/
|
|
27
|
-
export declare function detectRunnerPressure(samples: RunnerSample[], alertStates: Map<string, RunnerAlertState>, options: DetectOptions): PressureAction[];
|
|
28
|
-
/**
|
|
29
|
-
* Pure runner-pressure detector (stacksjs/stacks#1850).
|
|
30
|
-
*
|
|
31
|
-
* Given a time-series of per-org runner samples + the dashboard's
|
|
32
|
-
* memory of which orgs are currently "alerting", return the
|
|
33
|
-
* actions to take: fire a fresh alert, clear an existing one, or do
|
|
34
|
-
* nothing.
|
|
35
|
-
*
|
|
36
|
-
* **Hysteresis** matters here. Once an org has been alerted on
|
|
37
|
-
* (queue > threshold sustained for one window), we DON'T re-fire
|
|
38
|
-
* just because the queue spikes a second time within the same hour.
|
|
39
|
-
* The org has to first *clear* — drop below threshold for a full
|
|
40
|
-
* window — before another fire becomes possible. This is the
|
|
41
|
-
* difference between "useful CI pressure paging" and "every dashboard
|
|
42
|
-
* load spams Slack".
|
|
43
|
-
*
|
|
44
|
-
* Pure for the same reason as `failure-detector.ts`: easy to unit-
|
|
45
|
-
* test the hysteresis transitions, the persistence + fan-out live
|
|
46
|
-
* in defaults.
|
|
47
|
-
*/
|
|
48
|
-
/** One time-series row per org per snapshot refresh. */
|
|
49
|
-
export declare interface RunnerSample {
|
|
50
|
-
org: string
|
|
51
|
-
running: number
|
|
52
|
-
queued: number
|
|
53
|
-
cap: number
|
|
54
|
-
sampledAt: string
|
|
55
|
-
}
|
|
56
|
-
/** Dashboard's last-known per-org alert state. */
|
|
57
|
-
export declare interface RunnerAlertState {
|
|
58
|
-
org: string
|
|
59
|
-
alerting: boolean
|
|
60
|
-
lastAlertedAt: string | null
|
|
61
|
-
lastClearedAt: string | null
|
|
62
|
-
}
|
|
63
|
-
/** Action the caller should take for one org. */
|
|
64
|
-
export declare interface PressureAction {
|
|
65
|
-
org: string
|
|
66
|
-
action: 'fire' | 'clear'
|
|
67
|
-
current: RunnerSample
|
|
68
|
-
sustainedMs: number
|
|
69
|
-
}
|
|
70
|
-
export declare interface DetectOptions {
|
|
71
|
-
queuedThreshold: number
|
|
72
|
-
windowMinutes: number
|
|
73
|
-
now?: number
|
|
74
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
export function detectRunnerPressure(samples, alertStates, options) {
|
|
2
|
-
const now = options.now ?? Date.now(), windowMs = options.windowMinutes * 60000, cutoffMs = now - windowMs, byOrg = new Map;
|
|
3
|
-
for (const s of samples) {
|
|
4
|
-
const t = Date.parse(s.sampledAt);
|
|
5
|
-
if (Number.isNaN(t) || t < cutoffMs)
|
|
6
|
-
continue;
|
|
7
|
-
const list = byOrg.get(s.org) ?? [];
|
|
8
|
-
list.push(s);
|
|
9
|
-
byOrg.set(s.org, list);
|
|
10
|
-
}
|
|
11
|
-
const actions = [];
|
|
12
|
-
for (const [org, orgSamples] of byOrg.entries()) {
|
|
13
|
-
if (orgSamples.length === 0)
|
|
14
|
-
continue;
|
|
15
|
-
orgSamples.sort((a, b) => Date.parse(a.sampledAt) - Date.parse(b.sampledAt));
|
|
16
|
-
const oldestMs = Date.parse(orgSamples[0].sampledAt), sustainedMs = Date.parse(orgSamples[orgSamples.length - 1].sampledAt) - oldestMs;
|
|
17
|
-
if (sustainedMs < windowMs - 1000)
|
|
18
|
-
continue;
|
|
19
|
-
const allAboveOrEqual = orgSamples.every((s) => s.queued >= options.queuedThreshold), allBelow = orgSamples.every((s) => s.queued < options.queuedThreshold), isAlerting = alertStates.get(org)?.alerting ?? !1, current = orgSamples[orgSamples.length - 1];
|
|
20
|
-
if (isAlerting) {
|
|
21
|
-
if (allBelow)
|
|
22
|
-
actions.push({ org, action: "clear", current, sustainedMs });
|
|
23
|
-
} else if (allAboveOrEqual)
|
|
24
|
-
actions.push({ org, action: "fire", current, sustainedMs });
|
|
25
|
-
}
|
|
26
|
-
return actions;
|
|
27
|
-
}
|
package/dist/runners.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Count jobs (not runs) currently using runners. A workflow run can be
|
|
3
|
-
* "in_progress" while some of its matrix jobs are still queued — only the
|
|
4
|
-
* actually-running jobs occupy runners, so we sum at the job level.
|
|
5
|
-
*/
|
|
6
|
-
export declare function fetchRepoActiveRuns(owner: string, name: string): Promise<{ running: number, queued: number }>;
|
package/dist/runners.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { ghFetch, GITHUB_API } from "./client";
|
|
2
|
-
export async function fetchRepoActiveRuns(owner, name) {
|
|
3
|
-
try {
|
|
4
|
-
const runRes = await ghFetch(`${GITHUB_API}/repos/${owner}/${name}/actions/runs?status=in_progress&per_page=100`);
|
|
5
|
-
if (!runRes.ok)
|
|
6
|
-
return { running: 0, queued: 0 };
|
|
7
|
-
const runs = (await runRes.json()).workflow_runs ?? [];
|
|
8
|
-
if (runs.length === 0)
|
|
9
|
-
return { running: 0, queued: 0 };
|
|
10
|
-
return (await Promise.all(runs.map(async (r) => {
|
|
11
|
-
const jobsRes = await ghFetch(`${GITHUB_API}/repos/${owner}/${name}/actions/runs/${r.id}/jobs`);
|
|
12
|
-
if (!jobsRes.ok)
|
|
13
|
-
return { running: 0, queued: 0 };
|
|
14
|
-
const jobsData = await jobsRes.json();
|
|
15
|
-
let running = 0, queued = 0;
|
|
16
|
-
for (const j of jobsData.jobs ?? [])
|
|
17
|
-
if (j.status === "in_progress")
|
|
18
|
-
running++;
|
|
19
|
-
else if (j.status === "queued")
|
|
20
|
-
queued++;
|
|
21
|
-
return { running, queued };
|
|
22
|
-
}))).reduce((a, b) => ({ running: a.running + b.running, queued: a.queued + b.queued }), { running: 0, queued: 0 });
|
|
23
|
-
} catch {
|
|
24
|
-
return { running: 0, queued: 0 };
|
|
25
|
-
}
|
|
26
|
-
}
|
package/dist/runs.d.ts
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import type { FailedJob, RepoStatus } from './types';
|
|
2
|
-
export declare function fetchFailedJobs(owner: string, name: string, runId: number): Promise<FailedJob[]>;
|
|
3
|
-
export declare function fetchRepoStatus(owner: string, name: string, defaultBranch: string): Promise<RepoStatus>;
|
package/dist/runs.js
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { ghFetch, GITHUB_API } from "./client";
|
|
2
|
-
async function fillLatestCommit(base, owner, name, branch) {
|
|
3
|
-
try {
|
|
4
|
-
const res = await ghFetch(`${GITHUB_API}/repos/${owner}/${name}/commits?sha=${branch}&per_page=1`);
|
|
5
|
-
if (!res.ok)
|
|
6
|
-
return;
|
|
7
|
-
const commits = await res.json(), [c] = commits;
|
|
8
|
-
if (!c)
|
|
9
|
-
return;
|
|
10
|
-
base.commitSha = c.sha.slice(0, 7);
|
|
11
|
-
base.commitMessage = c.commit.message.split(`
|
|
12
|
-
`)[0] ?? null;
|
|
13
|
-
base.commitUrl = `https://github.com/${owner}/${name}/commit/${c.sha}`;
|
|
14
|
-
base.commitAuthor = c.commit.author?.name ?? c.author?.login ?? null;
|
|
15
|
-
base.updatedAt = c.commit.author?.date ?? null;
|
|
16
|
-
} catch {}
|
|
17
|
-
}
|
|
18
|
-
export async function fetchFailedJobs(owner, name, runId) {
|
|
19
|
-
try {
|
|
20
|
-
const res = await ghFetch(`${GITHUB_API}/repos/${owner}/${name}/actions/runs/${runId}/jobs?filter=latest`);
|
|
21
|
-
if (!res.ok)
|
|
22
|
-
return [];
|
|
23
|
-
return (await res.json()).jobs.filter((j) => j.conclusion && j.conclusion !== "success" && j.conclusion !== "skipped").map((j) => ({ name: j.name, conclusion: j.conclusion, url: j.html_url }));
|
|
24
|
-
} catch {
|
|
25
|
-
return [];
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
export async function fetchRepoStatus(owner, name, defaultBranch) {
|
|
29
|
-
const base = {
|
|
30
|
-
name,
|
|
31
|
-
owner,
|
|
32
|
-
fullName: `${owner}/${name}`,
|
|
33
|
-
url: `https://github.com/${owner}/${name}`,
|
|
34
|
-
defaultBranch,
|
|
35
|
-
status: "no_runs",
|
|
36
|
-
conclusion: null,
|
|
37
|
-
workflowName: null,
|
|
38
|
-
commitSha: null,
|
|
39
|
-
commitMessage: null,
|
|
40
|
-
commitUrl: null,
|
|
41
|
-
commitAuthor: null,
|
|
42
|
-
commitCount: null,
|
|
43
|
-
updatedAt: null,
|
|
44
|
-
runUrl: null,
|
|
45
|
-
failedJobs: [],
|
|
46
|
-
renovatePRs: 0,
|
|
47
|
-
renovatePRsUrl: null,
|
|
48
|
-
actionsPRs: 0,
|
|
49
|
-
actionsPRsUrl: null
|
|
50
|
-
};
|
|
51
|
-
try {
|
|
52
|
-
const res = await ghFetch(`${GITHUB_API}/repos/${owner}/${name}/actions/runs?branch=${defaultBranch}&event=push&per_page=1`);
|
|
53
|
-
if (!res.ok) {
|
|
54
|
-
base.status = "error";
|
|
55
|
-
await fillLatestCommit(base, owner, name, defaultBranch);
|
|
56
|
-
return base;
|
|
57
|
-
}
|
|
58
|
-
const data = await res.json(), [run] = data.workflow_runs ?? [];
|
|
59
|
-
if (!run) {
|
|
60
|
-
await fillLatestCommit(base, owner, name, defaultBranch);
|
|
61
|
-
return base;
|
|
62
|
-
}
|
|
63
|
-
base.workflowName = run.name;
|
|
64
|
-
base.commitSha = run.head_sha.slice(0, 7);
|
|
65
|
-
base.commitMessage = run.head_commit?.message.split(`
|
|
66
|
-
`)[0] ?? null;
|
|
67
|
-
base.commitUrl = `https://github.com/${owner}/${name}/commit/${run.head_sha}`;
|
|
68
|
-
base.commitAuthor = run.head_commit?.author?.name ?? run.actor?.login ?? null;
|
|
69
|
-
base.updatedAt = run.updated_at;
|
|
70
|
-
base.runUrl = run.html_url;
|
|
71
|
-
if (run.status === "completed") {
|
|
72
|
-
base.status = run.conclusion === "success" ? "success" : "failure";
|
|
73
|
-
base.conclusion = run.conclusion;
|
|
74
|
-
if (base.status === "failure")
|
|
75
|
-
base.failedJobs = await fetchFailedJobs(owner, name, run.id);
|
|
76
|
-
} else {
|
|
77
|
-
base.status = "pending";
|
|
78
|
-
base.conclusion = run.status;
|
|
79
|
-
}
|
|
80
|
-
} catch {
|
|
81
|
-
base.status = "error";
|
|
82
|
-
}
|
|
83
|
-
return base;
|
|
84
|
-
}
|
package/dist/types.d.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
export declare interface FailedJob {
|
|
2
|
-
name: string
|
|
3
|
-
conclusion: string
|
|
4
|
-
url: string
|
|
5
|
-
}
|
|
6
|
-
export declare interface RepoStatus {
|
|
7
|
-
name: string
|
|
8
|
-
owner: string
|
|
9
|
-
fullName: string
|
|
10
|
-
url: string
|
|
11
|
-
defaultBranch: string
|
|
12
|
-
status: RepoStatusKind
|
|
13
|
-
conclusion: string | null
|
|
14
|
-
workflowName: string | null
|
|
15
|
-
commitSha: string | null
|
|
16
|
-
commitMessage: string | null
|
|
17
|
-
commitUrl: string | null
|
|
18
|
-
commitAuthor: string | null
|
|
19
|
-
commitCount: number | null
|
|
20
|
-
updatedAt: string | null
|
|
21
|
-
runUrl: string | null
|
|
22
|
-
failedJobs: FailedJob[]
|
|
23
|
-
renovatePRs: number
|
|
24
|
-
renovatePRsUrl: string | null
|
|
25
|
-
actionsPRs: number
|
|
26
|
-
actionsPRsUrl: string | null
|
|
27
|
-
}
|
|
28
|
-
export declare interface OrgRunnerUsage {
|
|
29
|
-
running: number
|
|
30
|
-
queued: number
|
|
31
|
-
cap: number
|
|
32
|
-
}
|
|
33
|
-
export declare interface DashboardData {
|
|
34
|
-
repos: RepoStatus[]
|
|
35
|
-
fetchedAt: string
|
|
36
|
-
total: number
|
|
37
|
-
passing: number
|
|
38
|
-
failing: number
|
|
39
|
-
pending: number
|
|
40
|
-
noRuns: number
|
|
41
|
-
runners: Record<string, OrgRunnerUsage>
|
|
42
|
-
}
|
|
43
|
-
export declare interface DashboardOptions {
|
|
44
|
-
orgs: string[]
|
|
45
|
-
runnerCaps?: Record<string, number>
|
|
46
|
-
defaultRunnerCap?: number
|
|
47
|
-
ignoreRepos?: string[]
|
|
48
|
-
cacheTtlMs?: number
|
|
49
|
-
cachePath?: string
|
|
50
|
-
}
|
|
51
|
-
export declare interface Repo {
|
|
52
|
-
name: string
|
|
53
|
-
owner: string
|
|
54
|
-
full_name: string
|
|
55
|
-
html_url: string
|
|
56
|
-
default_branch: string
|
|
57
|
-
archived: boolean
|
|
58
|
-
}
|
|
59
|
-
export type RepoStatusKind = 'success' | 'failure' | 'pending' | 'no_runs' | 'error';
|
package/dist/types.js
DELETED
|
File without changes
|