willfire 0.1.2 → 0.1.3
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 +11 -1
- package/dist/predict.d.ts +45 -3
- package/dist/predict.js +9 -3
- package/dist/verify.js +2 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,9 +21,19 @@ import { predict } from "willfire";
|
|
|
21
21
|
import { getOctokit } from "@actions/github"; // or new Octokit({ auth: token })
|
|
22
22
|
|
|
23
23
|
const { entries, skip } = await predict(getOctokit(token), "owner/repo", 123);
|
|
24
|
-
// entries: [{ workflow, job, status: "run" | "skipped" | "unknown" | "no-dispatch", reason }]
|
|
25
24
|
```
|
|
26
25
|
|
|
26
|
+
`entries` is a union of two variants, both carrying `workflow` and `reason`:
|
|
27
|
+
|
|
28
|
+
| variant | `job` | `status` |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| `WorkflowEntry` | `"*"` | `"run" \| "skipped" \| "no-dispatch"` |
|
|
31
|
+
| `JobEntry` | the job's display name | `"run" \| "skipped" \| "unknown" \| "no-dispatch"` |
|
|
32
|
+
|
|
33
|
+
`"unknown"` is job-level only: every workflow-level verdict is decidable, so a
|
|
34
|
+
`WorkflowEntry` cannot express one. Narrow with the exported `isWorkflowEntry`
|
|
35
|
+
and `isJobEntry` guards rather than testing the `"*"` sentinel yourself.
|
|
36
|
+
|
|
27
37
|
Auth is any token with `contents: read`, `actions: read`, and
|
|
28
38
|
`pull-requests: read` — inside an action, the workflow's `GITHUB_TOKEN`.
|
|
29
39
|
|
package/dist/predict.d.ts
CHANGED
|
@@ -1,11 +1,53 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Octokit } from "@octokit/rest";
|
|
3
|
-
|
|
3
|
+
interface EntryBase {
|
|
4
4
|
workflow: string;
|
|
5
|
-
job: string;
|
|
6
|
-
status: "run" | "skipped" | "unknown" | "no-dispatch";
|
|
7
5
|
reason: string;
|
|
8
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* A job's display name, e.g. `test (18)`.
|
|
9
|
+
*
|
|
10
|
+
* Nominally distinct from `string` for one reason: `Entry` is a closed union
|
|
11
|
+
* and `"*"` is the workflow-level sentinel, so a plain `string` here would
|
|
12
|
+
* structurally admit `{ job: "*", status: "unknown" }` as a `JobEntry` — the
|
|
13
|
+
* exact shape this split exists to forbid. TypeScript cannot spell "string
|
|
14
|
+
* but not `"*"`", so the job side is branded instead. Build one with
|
|
15
|
+
* {@link jobName}; reading one is just a string.
|
|
16
|
+
*/
|
|
17
|
+
export type JobName = string & {
|
|
18
|
+
readonly __jobName: true;
|
|
19
|
+
};
|
|
20
|
+
/** Tag a job display name. Rejects the workflow-level sentinel. */
|
|
21
|
+
export declare const jobName: <S extends string>(name: S extends "*" ? never : S) => JobName;
|
|
22
|
+
/**
|
|
23
|
+
* A verdict about the workflow as a whole: it produces no run at all, or it
|
|
24
|
+
* produces a run that expands into no job entries.
|
|
25
|
+
*
|
|
26
|
+
* There is no `"unknown"` here, and that is the point. Every workflow-level
|
|
27
|
+
* verdict is decidable, and this type is what enforces it — the previous
|
|
28
|
+
* single-interface shape let `{ job: "*", status: "unknown" }` typecheck, which
|
|
29
|
+
* is what shipped and what pr-monitor#17 had to grow a `tolerated` bucket for.
|
|
30
|
+
*/
|
|
31
|
+
export interface WorkflowEntry extends EntryBase {
|
|
32
|
+
job: "*";
|
|
33
|
+
status: "run" | "skipped" | "no-dispatch";
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A verdict about one job entry inside a workflow that does dispatch.
|
|
37
|
+
*
|
|
38
|
+
* These can be genuinely undecidable statically — dynamic matrix, non-local
|
|
39
|
+
* reusable workflow, unresolvable `if`, or `needs` on any of those — so
|
|
40
|
+
* `"unknown"` lives here and only here.
|
|
41
|
+
*/
|
|
42
|
+
export interface JobEntry extends EntryBase {
|
|
43
|
+
job: JobName;
|
|
44
|
+
status: "run" | "skipped" | "unknown" | "no-dispatch";
|
|
45
|
+
}
|
|
46
|
+
export type Entry = WorkflowEntry | JobEntry;
|
|
47
|
+
/** Narrow to the workflow-level variant without inspecting the sentinel. */
|
|
48
|
+
export declare const isWorkflowEntry: (e: Entry) => e is WorkflowEntry;
|
|
49
|
+
/** Narrow to the job-level variant without inspecting the sentinel. */
|
|
50
|
+
export declare const isJobEntry: (e: Entry) => e is JobEntry;
|
|
9
51
|
export interface Prediction {
|
|
10
52
|
entries: Entry[];
|
|
11
53
|
skip: string | null;
|
package/dist/predict.js
CHANGED
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
// live dispatches on thekevinbot/willrun-probe (PRs 1-7).
|
|
10
10
|
import { Octokit } from "@octokit/rest";
|
|
11
11
|
import { parse as parseYaml } from "yaml";
|
|
12
|
+
/** Tag a job display name. Rejects the workflow-level sentinel. */
|
|
13
|
+
export const jobName = (name) => name;
|
|
14
|
+
/** Narrow to the workflow-level variant without inspecting the sentinel. */
|
|
15
|
+
export const isWorkflowEntry = (e) => e.job === "*";
|
|
16
|
+
/** Narrow to the job-level variant without inspecting the sentinel. */
|
|
17
|
+
export const isJobEntry = (e) => e.job !== "*";
|
|
12
18
|
// ------------------------------------------------- GitHub filter pattern glob
|
|
13
19
|
// Grammar per docs: * (any chars except /), ** (any chars), ? (zero or one of
|
|
14
20
|
// preceding char), + (one or more of preceding char), [ranges], leading ! negates.
|
|
@@ -347,8 +353,8 @@ export async function predict(octokit, repo, prNumber) {
|
|
|
347
353
|
entries.push({ workflow: path, job: "*", status: "no-dispatch", reason });
|
|
348
354
|
continue;
|
|
349
355
|
}
|
|
350
|
-
for (const [
|
|
351
|
-
entries.push({ workflow: path, job: jobName, status, reason: jreason || reason });
|
|
356
|
+
for (const [name, status, jreason] of await expandJobs(wf, ctx, fetchFile)) {
|
|
357
|
+
entries.push({ workflow: path, job: jobName(name), status, reason: jreason || reason });
|
|
352
358
|
}
|
|
353
359
|
}
|
|
354
360
|
return { entries, skip: null };
|
|
@@ -379,7 +385,7 @@ if (isMain) {
|
|
|
379
385
|
}
|
|
380
386
|
else {
|
|
381
387
|
for (const e of entries) {
|
|
382
|
-
if (e
|
|
388
|
+
if (isWorkflowEntry(e))
|
|
383
389
|
console.log(`# ${e.workflow} :: ${e.status} (${e.reason})`);
|
|
384
390
|
else
|
|
385
391
|
console.log(`${e.workflow} :: ${e.job} :: ${e.status}`);
|
package/dist/verify.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
//
|
|
5
5
|
// Ground truth: workflow runs for the PR head SHA with a pull_request event,
|
|
6
6
|
// and the job entries inside each run (skipped jobs included).
|
|
7
|
-
import { makeOctokit, predict } from "./predict.js";
|
|
7
|
+
import { isJobEntry, makeOctokit, predict } from "./predict.js";
|
|
8
8
|
async function actualEntries(octokit, repo, prNumber) {
|
|
9
9
|
const [owner, name] = repo.split("/");
|
|
10
10
|
const base = { owner, repo: name };
|
|
@@ -44,9 +44,7 @@ if (!repo || !prArg) {
|
|
|
44
44
|
const pr = Number(prArg);
|
|
45
45
|
const octokit = makeOctokit();
|
|
46
46
|
const { entries: predictedRaw } = await predict(octokit, repo, pr);
|
|
47
|
-
const predicted = new Map(predictedRaw
|
|
48
|
-
.filter((r) => r.job !== "*")
|
|
49
|
-
.map((r) => [`${r.workflow} :: ${r.job}`, r.status]));
|
|
47
|
+
const predicted = new Map(predictedRaw.filter(isJobEntry).map((r) => [`${r.workflow} :: ${r.job}`, r.status]));
|
|
50
48
|
const unknownWfs = new Set(predictedRaw.filter((r) => r.status === "unknown").map((r) => r.workflow));
|
|
51
49
|
const { entries: actual, incomplete } = await actualEntries(octokit, repo, pr);
|
|
52
50
|
if (incomplete.length > 0) {
|
|
@@ -82,10 +80,5 @@ for (const key of keys) {
|
|
|
82
80
|
console.log(`DIFF ${key} :: predicted ${p}, actual ${a}`);
|
|
83
81
|
}
|
|
84
82
|
}
|
|
85
|
-
for (const r of predictedRaw) {
|
|
86
|
-
if (r.job === "*" && r.status === "unknown") {
|
|
87
|
-
console.log(` ? ${r.workflow} :: workflow-level unknown: ${r.reason}`);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
83
|
console.log(ok ? "PASS" : "FAIL");
|
|
91
84
|
process.exit(ok ? 0 : 1);
|
package/package.json
CHANGED