willfire 0.1.0 → 0.1.2

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.
Files changed (2) hide show
  1. package/dist/predict.js +28 -15
  2. package/package.json +2 -2
package/dist/predict.js CHANGED
@@ -77,33 +77,40 @@ function getPrTrigger(wf) {
77
77
  }
78
78
  return MISSING;
79
79
  }
80
+ // A predicate: does this workflow produce a run for the PR? Every workflow-level
81
+ // verdict is decidable, so there is no third answer to express. Only job
82
+ // expansion can be genuinely undecidable (dynamic matrix, non-local reusable
83
+ // workflow, unresolvable `if`), and that is a per-entry status.
80
84
  function workflowDispatches(wf, ctx) {
81
85
  const trig = getPrTrigger(wf);
82
86
  if (trig === MISSING)
83
- return ["no-dispatch", "no pull_request trigger"];
87
+ return [false, "no pull_request trigger"];
84
88
  const types = trig["types"] ?? DEFAULT_TYPES;
85
89
  if (!types.includes(ctx.action)) {
86
- return ["no-dispatch", `action '${ctx.action}' not in types [${types}]`];
90
+ return [false, `action '${ctx.action}' not in types [${types}]`];
87
91
  }
92
+ // Setting a filter and its -ignore twin on one trigger is invalid config.
93
+ // GitHub does not fall back to "no filter" or skip the workflow: it creates
94
+ // the run and concludes `startup_failure`. The run exists, so it dispatches.
88
95
  if ("branches" in trig && "branches-ignore" in trig) {
89
- return ["unknown", "both branches and branches-ignore set"];
96
+ return [true, "both branches and branches-ignore set: startup failure"];
90
97
  }
91
98
  if ("branches" in trig && !matchFilters(ctx.baseRef, trig["branches"])) {
92
- return ["no-dispatch", `base branch '${ctx.baseRef}' not in branches`];
99
+ return [false, `base branch '${ctx.baseRef}' not in branches`];
93
100
  }
94
101
  if ("branches-ignore" in trig && matchFilters(ctx.baseRef, trig["branches-ignore"])) {
95
- return ["no-dispatch", "base branch in branches-ignore"];
102
+ return [false, "base branch in branches-ignore"];
96
103
  }
97
104
  if ("paths" in trig && "paths-ignore" in trig) {
98
- return ["unknown", "both paths and paths-ignore set"];
105
+ return [true, "both paths and paths-ignore set: startup failure"];
99
106
  }
100
107
  if ("paths" in trig && !ctx.files.some((f) => matchFilters(f, trig["paths"]))) {
101
- return ["no-dispatch", "no changed file matches paths"];
108
+ return [false, "no changed file matches paths"];
102
109
  }
103
110
  if ("paths-ignore" in trig && ctx.files.every((f) => matchFilters(f, trig["paths-ignore"]))) {
104
- return ["no-dispatch", "all changed files match paths-ignore"];
111
+ return [false, "all changed files match paths-ignore"];
105
112
  }
106
- return ["dispatch", "trigger matched"];
113
+ return [true, "trigger matched"];
107
114
  }
108
115
  /** Return list of matrix combination dicts, or null if dynamic. */
109
116
  export function expandMatrix(strategy) {
@@ -308,11 +315,14 @@ export async function predict(octokit, repo, prNumber) {
308
315
  }
309
316
  const content = await fetchFile(path);
310
317
  if (content == null) {
318
+ // The Actions API keeps listing a workflow as `active` after its file is
319
+ // deleted. There is no file at head, so there is nothing to dispatch —
320
+ // the same verdict as the disabled case above, reached a different way.
311
321
  entries.push({
312
322
  workflow: path,
313
323
  job: "*",
314
- status: "unknown",
315
- reason: "cannot fetch workflow file at head",
324
+ status: "no-dispatch",
325
+ reason: "no workflow file at head",
316
326
  });
317
327
  continue;
318
328
  }
@@ -321,17 +331,20 @@ export async function predict(octokit, repo, prNumber) {
321
331
  wf = parseYaml(content);
322
332
  }
323
333
  catch (e) {
334
+ // GitHub creates a run for an unparseable workflow file and concludes it
335
+ // `startup_failure`. The run exists but has no jobs, so this is a
336
+ // workflow-level "it dispatches" with nothing to expand.
324
337
  entries.push({
325
338
  workflow: path,
326
339
  job: "*",
327
- status: "unknown",
340
+ status: "run",
328
341
  reason: `YAML parse error: ${e}`,
329
342
  });
330
343
  continue;
331
344
  }
332
- const [verdict, reason] = workflowDispatches(wf, ctx);
333
- if (verdict !== "dispatch") {
334
- entries.push({ workflow: path, job: "*", status: verdict, reason });
345
+ const [dispatches, reason] = workflowDispatches(wf, ctx);
346
+ if (!dispatches) {
347
+ entries.push({ workflow: path, job: "*", status: "no-dispatch", reason });
335
348
  continue;
336
349
  }
337
350
  for (const [jobName, status, jreason] of await expandJobs(wf, ctx, fetchFile)) {
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "willfire",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Predict the set of CI check entries GitHub Actions will create for a pull request",
5
5
  "license": "MIT",
6
6
  "author": "Kevin Scott <me@thekevinscott.com>",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "git+https://github.com/thekevinscott/willrun.git"
9
+ "url": "git+https://github.com/thekevinscott/willfire.git"
10
10
  },
11
11
  "keywords": [
12
12
  "github-actions",