willfire 0.1.8 → 0.1.9

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 CHANGED
@@ -20,10 +20,19 @@ pnpm add willfire
20
20
  import { predict } from "willfire";
21
21
  import { getOctokit } from "@actions/github"; // or new Octokit({ auth: token })
22
22
 
23
- const { entries, checkNames, skip } = await predict(getOctokit(token), "owner/repo", 123);
23
+ const { entries, checkNames, skip } = await predict(getOctokit(token), "owner/repo", 123, {
24
+ action: context.payload.action, // "opened" | "synchronize" | "reopened"
25
+ });
24
26
  // checkNames: sorted, deduped checkName of every entry with status "run"
25
27
  ```
26
28
 
29
+ `action` is optional but worth passing. Omitted, the event action is inferred
30
+ from the PR's commit count, which is wrong in both directions — a PR opened
31
+ from a branch with several commits looks like `synchronize`, a force-push down
32
+ to one commit looks like `opened` — and can never produce `reopened`. That only
33
+ matters to a workflow narrowing `types:`, where it decides whether the workflow
34
+ dispatches at all.
35
+
27
36
  `entries` is a union of two variants, both carrying `workflow` and `reason`:
28
37
 
29
38
  | variant | `job` | `checkName` | `status` |
@@ -58,7 +67,8 @@ Auth is any token with `contents: read`, `actions: read`, and
58
67
  ## CLI
59
68
 
60
69
  ```sh
61
- GH_TOKEN=... willfire --repo owner/repo --pr 123 [--json]
70
+ GH_TOKEN=... willfire --repo owner/repo --pr 123 \
71
+ [--action opened|synchronize|reopened] [--json]
62
72
  ```
63
73
 
64
74
  ## What it handles
@@ -124,7 +134,10 @@ the docs do not state:
124
134
 
125
135
  Scope notes: validated on `opened` pull_request events; `synchronize`/`labeled`
126
136
  live events, `branches-ignore`, and diffs far beyond 301 files are not yet
127
- probe-verified. Reusable-workflow name prefixing is probe-verified to three
137
+ probe-verified passing `action` explicitly is what makes probing the other
138
+ two possible. `action` accepts the three default `types:` only; `pull_request`
139
+ fires on around twenty actions, and a workflow narrowing to `ready_for_review`
140
+ or `edited` is out of scope either way. Reusable-workflow name prefixing is probe-verified to three
128
141
  levels; deeper nesting is inferred. The cross-repo probe calls back into the
129
142
  probe repo itself by full `owner/repo@ref` reference, so it pins ref
130
143
  resolution but not the owner/repo half of the address.
package/dist/predict.d.ts CHANGED
@@ -41,6 +41,11 @@ export interface WorkflowEntry extends EntryBase {
41
41
  * These can be genuinely undecidable statically — dynamic matrix, a reusable
42
42
  * workflow we cannot read, unresolvable `if`, or `needs` on any of those — so
43
43
  * `"unknown"` lives here and only here.
44
+ *
45
+ * `"no-dispatch"` is the mirror image, and lives on {@link WorkflowEntry} and
46
+ * only there. It is a verdict about whether the run happens at all, which is
47
+ * settled before any job is looked at — so by the time there is a job entry to
48
+ * label, the answer is already yes.
44
49
  */
45
50
  export interface JobEntry extends EntryBase {
46
51
  job: JobName;
@@ -54,7 +59,7 @@ export interface JobEntry extends EntryBase {
54
59
  * interpolates something we cannot evaluate ahead of the run.
55
60
  */
56
61
  checkName: string | null;
57
- status: "run" | "skipped" | "unknown" | "no-dispatch";
62
+ status: "run" | "skipped" | "unknown";
58
63
  }
59
64
  export type Entry = WorkflowEntry | JobEntry;
60
65
  /** Narrow to the workflow-level variant without inspecting the sentinel. */
@@ -74,6 +79,30 @@ export interface Prediction {
74
79
  export declare function patternToRegex(pat: string): RegExp;
75
80
  /** Order-sensitive match: last matching pattern wins; ! negates. */
76
81
  export declare function matchFilters(value: string, patterns: string[]): boolean;
82
+ /**
83
+ * The `pull_request` actions a caller can name.
84
+ *
85
+ * Deliberately the three default types and no more. `pull_request` fires on
86
+ * around twenty actions — `ready_for_review`, `edited`, `labeled` — and a
87
+ * workflow that narrows `types:` to one of those is not predictable today
88
+ * regardless of what is passed here. Widening this union later is a
89
+ * non-breaking change; narrowing it would not be, so it starts narrow.
90
+ */
91
+ export type PrEventAction = "opened" | "synchronize" | "reopened";
92
+ /** Options every caller may omit. */
93
+ export interface PredictOptions {
94
+ /**
95
+ * The literal event action the run was triggered by — `github.event.action`
96
+ * inside an Action, or `--action` on the CLI.
97
+ *
98
+ * Omitting it falls back to inferring from the commit count, which is a
99
+ * guess and is wrong in both directions: a PR opened from a branch with
100
+ * several commits looks like `synchronize`, a force-push down to one commit
101
+ * looks like `opened`, and `reopened` is never produced. That only matters
102
+ * to a workflow narrowing `types:`, where it matters completely.
103
+ */
104
+ action?: PrEventAction;
105
+ }
77
106
  export interface Ctx {
78
107
  action: string;
79
108
  baseRef: string;
@@ -148,5 +177,5 @@ export declare function parseUses(uses: string): UsesTarget | null;
148
177
  */
149
178
  export declare function expandWorkflowJobs(wf: Workflow, ctx: Ctx, fetchWorkflow: FetchWorkflow, source: WorkflowSource): Promise<ExpandedJob[]>;
150
179
  export declare function makeOctokit(): Octokit;
151
- export declare function predict(octokit: Octokit, repo: string, prNumber: number): Promise<Prediction>;
180
+ export declare function predict(octokit: Octokit, repo: string, prNumber: number, opts?: PredictOptions): Promise<Prediction>;
152
181
  export {};
package/dist/predict.js CHANGED
@@ -535,7 +535,7 @@ export function makeOctokit() {
535
535
  throw new Error("GH_TOKEN or GITHUB_TOKEN must be set");
536
536
  return new Octokit({ auth: token });
537
537
  }
538
- export async function predict(octokit, repo, prNumber) {
538
+ export async function predict(octokit, repo, prNumber, opts = {}) {
539
539
  const [owner, name] = repo.split("/");
540
540
  const base = { owner, repo: name };
541
541
  const { data: pr } = await octokit.rest.pulls.get({ ...base, pull_number: prNumber });
@@ -545,7 +545,9 @@ export async function predict(octokit, repo, prNumber) {
545
545
  per_page: 100,
546
546
  });
547
547
  const ctx = {
548
- action: pr.commits > 1 ? "synchronize" : "opened",
548
+ // The caller's answer wins whenever it has one. The commit-count fallback
549
+ // is a guess kept only so existing callers keep working.
550
+ action: opts.action ?? (pr.commits > 1 ? "synchronize" : "opened"),
549
551
  baseRef: pr.base.ref,
550
552
  files: files.map((f) => f.filename),
551
553
  };
@@ -662,6 +664,8 @@ function finalizePrediction(entries, skip) {
662
664
  return { entries: final, checkNames: [...names].sort(), skip };
663
665
  }
664
666
  // ------------------------------------------------------------------------ CLI
667
+ const USAGE = "usage: predict --repo owner/name --pr N [--action opened|synchronize|reopened] [--json]";
668
+ const isPrEventAction = (v) => v === "opened" || v === "synchronize" || v === "reopened";
665
669
  function parseArgs(argv) {
666
670
  const get = (flag) => {
667
671
  const i = argv.indexOf(flag);
@@ -670,15 +674,26 @@ function parseArgs(argv) {
670
674
  const repo = get("--repo");
671
675
  const pr = get("--pr");
672
676
  if (!repo || !pr) {
673
- console.error("usage: predict --repo owner/name --pr N [--json]");
677
+ console.error(USAGE);
674
678
  process.exit(2);
675
679
  }
676
- return { repo, pr: Number(pr), json: argv.includes("--json") };
680
+ // An unrecognised action is refused rather than ignored. Silently falling
681
+ // back to the guess would turn a typo into a wrong prediction, which is the
682
+ // failure this flag exists to remove.
683
+ const action = get("--action");
684
+ if (action !== undefined && !isPrEventAction(action)) {
685
+ console.error(`unknown --action: ${action}`);
686
+ console.error(USAGE);
687
+ process.exit(2);
688
+ }
689
+ return { repo, pr: Number(pr), json: argv.includes("--json"), action };
677
690
  }
678
691
  const isMain = /predict\.(ts|js)$|\/willfire$/.test(process.argv[1] ?? "");
679
692
  if (isMain) {
680
693
  const args = parseArgs(process.argv.slice(2));
681
- const { entries, checkNames, skip } = await predict(makeOctokit(), args.repo, args.pr);
694
+ const { entries, checkNames, skip } = await predict(makeOctokit(), args.repo, args.pr, {
695
+ action: args.action,
696
+ });
682
697
  if (args.json) {
683
698
  console.log(JSON.stringify({ entries, checkNames, skip }, null, 2));
684
699
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "willfire",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "Predict the set of CI check entries GitHub Actions will create for a pull request",
5
5
  "license": "MIT",
6
6
  "packageManager": "pnpm@10.33.0",