willfire 0.1.29 → 0.1.31
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/jobs/absentInputs.d.ts +8 -0
- package/dist/jobs/absentInputs.js +18 -0
- package/dist/jobs/calleeInputs.d.ts +12 -0
- package/dist/jobs/calleeInputs.js +28 -0
- package/dist/jobs/expandJobs.d.ts +2 -2
- package/dist/jobs/expandJobs.js +7 -83
- package/dist/jobs/inputValue.d.ts +7 -0
- package/dist/jobs/inputValue.js +30 -0
- package/dist/jobs/neededJobIds.d.ts +7 -0
- package/dist/jobs/neededJobIds.js +15 -0
- package/dist/jobs/workflowCallInputs.d.ts +3 -0
- package/dist/jobs/workflowCallInputs.js +6 -0
- package/package.json +1 -1
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Val } from "../expr/val.js";
|
|
2
|
+
import type { Workflow } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Every declared input bound to the empty string, which is what an absent one
|
|
5
|
+
* reads as. `default:` is deliberately ignored: a default is applied by the
|
|
6
|
+
* dispatch or the call, and on a `pull_request` neither happened.
|
|
7
|
+
*/
|
|
8
|
+
export declare function absentInputs(wf: Workflow): Record<string, Val>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** The two triggers that carry an `inputs` context. A `pull_request` is neither. */
|
|
2
|
+
const INPUT_TRIGGERS = ["workflow_dispatch", "workflow_call"];
|
|
3
|
+
/**
|
|
4
|
+
* Every declared input bound to the empty string, which is what an absent one
|
|
5
|
+
* reads as. `default:` is deliberately ignored: a default is applied by the
|
|
6
|
+
* dispatch or the call, and on a `pull_request` neither happened.
|
|
7
|
+
*/
|
|
8
|
+
export function absentInputs(wf) {
|
|
9
|
+
// Tolerates the YAML 1.1 `on` -> true key, as the callee's own lookup does.
|
|
10
|
+
const on = wf["on"] ?? wf["true"];
|
|
11
|
+
const out = {};
|
|
12
|
+
for (const trigger of INPUT_TRIGGERS) {
|
|
13
|
+
for (const name of Object.keys(on?.[trigger]?.["inputs"] ?? {})) {
|
|
14
|
+
out[name] = { kind: "value", v: "" };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type Scope, type Val } from "../expr/val.js";
|
|
2
|
+
import type { Workflow } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* What `inputs.*` resolves to inside a called workflow: what the caller passed,
|
|
5
|
+
* over the defaults the callee declares.
|
|
6
|
+
*
|
|
7
|
+
* A declared input the caller omits falls back to its `default`. A declared
|
|
8
|
+
* input with no default and no caller value is unknown rather than empty — the
|
|
9
|
+
* workflow would be invalid if it were required, and guessing empty would
|
|
10
|
+
* silently decide guards that are not decided.
|
|
11
|
+
*/
|
|
12
|
+
export declare function calleeInputs(withBlock: unknown, subWf: Workflow, scope: Scope): Record<string, Val>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { UNKNOWN } from "../expr/val.js";
|
|
2
|
+
import { inputValue } from "./inputValue.js";
|
|
3
|
+
import { workflowCallInputs } from "./workflowCallInputs.js";
|
|
4
|
+
/**
|
|
5
|
+
* What `inputs.*` resolves to inside a called workflow: what the caller passed,
|
|
6
|
+
* over the defaults the callee declares.
|
|
7
|
+
*
|
|
8
|
+
* A declared input the caller omits falls back to its `default`. A declared
|
|
9
|
+
* input with no default and no caller value is unknown rather than empty — the
|
|
10
|
+
* workflow would be invalid if it were required, and guessing empty would
|
|
11
|
+
* silently decide guards that are not decided.
|
|
12
|
+
*/
|
|
13
|
+
export function calleeInputs(withBlock, subWf, scope) {
|
|
14
|
+
const out = {};
|
|
15
|
+
for (const [name, decl] of Object.entries(workflowCallInputs(subWf))) {
|
|
16
|
+
out[name] =
|
|
17
|
+
decl !== null && typeof decl === "object" && "default" in decl
|
|
18
|
+
? // Defaults live in the callee, out of the caller's context's reach.
|
|
19
|
+
inputValue(decl["default"], {})
|
|
20
|
+
: UNKNOWN;
|
|
21
|
+
}
|
|
22
|
+
if (withBlock !== null && typeof withBlock === "object") {
|
|
23
|
+
for (const [name, raw] of Object.entries(withBlock)) {
|
|
24
|
+
out[name] = inputValue(raw, scope);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import type { Scope } from "../expr/val.js";
|
|
2
|
+
import type { JobExecutor } from "../execute.js";
|
|
3
3
|
import type { Ctx, ExpandedJob, Workflow, WorkflowReader, WorkflowSource } from "../types.js";
|
|
4
4
|
export declare function expandJobs(wf: Workflow, ctx: Ctx, reader: WorkflowReader, source: WorkflowSource, depth?: number, prefix?: string, prefixResolved?: boolean, scope?: Scope, executor?: JobExecutor): Promise<ExpandedJob[]>;
|
package/dist/jobs/expandJobs.js
CHANGED
|
@@ -1,77 +1,13 @@
|
|
|
1
1
|
import { parse as parseYaml } from "yaml";
|
|
2
|
-
import { evaluateValue } from "../expr/evaluateValue.js";
|
|
3
|
-
import { UNKNOWN } from "../expr/val.js";
|
|
4
|
-
import { renderTemplate } from "../execute.js";
|
|
5
2
|
import { expandMatrixDetailed } from "../matrix/expandMatrixDetailed.js";
|
|
6
3
|
import { jobDisplayName } from "../names/jobDisplayName.js";
|
|
7
4
|
import { skippedDisplayName } from "../names/skippedDisplayName.js";
|
|
8
5
|
import { parseUses } from "../uses/parseUses.js";
|
|
6
|
+
import { absentInputs } from "./absentInputs.js";
|
|
7
|
+
import { calleeInputs } from "./calleeInputs.js";
|
|
9
8
|
import { evalIf } from "./evalIf.js";
|
|
9
|
+
import { neededJobIds } from "./neededJobIds.js";
|
|
10
10
|
import { prScope } from "./prScope.js";
|
|
11
|
-
/**
|
|
12
|
-
* A `with:` value as the callee will see it, evaluated in the caller's scope.
|
|
13
|
-
* A whole-expression value keeps its evaluated type; mixed text renders to a
|
|
14
|
-
* string, all or nothing; anything unresolvable stays unknown.
|
|
15
|
-
*/
|
|
16
|
-
function inputValue(raw, scope) {
|
|
17
|
-
if (raw === null || raw === undefined) {
|
|
18
|
-
return { kind: "value", v: "" };
|
|
19
|
-
}
|
|
20
|
-
if (typeof raw === "boolean" || typeof raw === "number") {
|
|
21
|
-
return { kind: "value", v: raw };
|
|
22
|
-
}
|
|
23
|
-
if (typeof raw !== "string") {
|
|
24
|
-
return UNKNOWN;
|
|
25
|
-
}
|
|
26
|
-
if (!raw.includes("${{")) {
|
|
27
|
-
return { kind: "value", v: raw };
|
|
28
|
-
}
|
|
29
|
-
const t = raw.trim();
|
|
30
|
-
// `${{a}} x ${{b}}` fails this test and takes the render path instead.
|
|
31
|
-
if (t.startsWith("${{") && t.indexOf("}}") === t.length - 2) {
|
|
32
|
-
return evaluateValue(t.slice(3, -2), prScope(scope));
|
|
33
|
-
}
|
|
34
|
-
const rendered = renderTemplate(raw, prScope(scope));
|
|
35
|
-
return rendered === null ? UNKNOWN : { kind: "value", v: rendered };
|
|
36
|
-
}
|
|
37
|
-
/** The `on.workflow_call.inputs` block, tolerating the YAML 1.1 `on` -> true key. */
|
|
38
|
-
function workflowCallInputs(wf) {
|
|
39
|
-
const on = wf?.["on"] ?? wf?.["true"];
|
|
40
|
-
if (on === null || typeof on !== "object") {
|
|
41
|
-
return {};
|
|
42
|
-
}
|
|
43
|
-
const call = on["workflow_call"];
|
|
44
|
-
if (call === null || call === undefined) {
|
|
45
|
-
return {};
|
|
46
|
-
}
|
|
47
|
-
const inputs = call["inputs"];
|
|
48
|
-
return inputs !== null && typeof inputs === "object" ? inputs : {};
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* What `inputs.*` resolves to inside a called workflow: what the caller passed,
|
|
52
|
-
* over the defaults the callee declares.
|
|
53
|
-
*
|
|
54
|
-
* A declared input the caller omits falls back to its `default`. A declared
|
|
55
|
-
* input with no default and no caller value is unknown rather than empty — the
|
|
56
|
-
* workflow would be invalid if it were required, and guessing empty would
|
|
57
|
-
* silently decide guards that are not decided.
|
|
58
|
-
*/
|
|
59
|
-
function calleeInputs(withBlock, subWf, scope) {
|
|
60
|
-
const out = {};
|
|
61
|
-
for (const [name, decl] of Object.entries(workflowCallInputs(subWf))) {
|
|
62
|
-
out[name] =
|
|
63
|
-
decl !== null && typeof decl === "object" && "default" in decl
|
|
64
|
-
? // Defaults live in the callee, out of the caller's context's reach.
|
|
65
|
-
inputValue(decl["default"], {})
|
|
66
|
-
: UNKNOWN;
|
|
67
|
-
}
|
|
68
|
-
if (withBlock !== null && typeof withBlock === "object") {
|
|
69
|
-
for (const [name, raw] of Object.entries(withBlock)) {
|
|
70
|
-
out[name] = inputValue(raw, scope);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return out;
|
|
74
|
-
}
|
|
75
11
|
/**
|
|
76
12
|
* GitHub allows a reusable-workflow call chain four levels deep. Past that the
|
|
77
13
|
* run itself fails, so anything deeper is not a name we could predict anyway.
|
|
@@ -84,29 +20,17 @@ const MAX_REUSABLE_DEPTH = 4;
|
|
|
84
20
|
/** `ref` is already a commit id, so resolving it is a no-op. */
|
|
85
21
|
const SHA_RE = /^[0-9a-f]{40}$/i;
|
|
86
22
|
const isSha = (ref) => SHA_RE.test(ref);
|
|
87
|
-
const NEEDS_OUTPUTS_RE = /needs\s*\.\s*([A-Za-z_][A-Za-z0-9_-]*)\s*\.\s*outputs\b/g;
|
|
88
|
-
/**
|
|
89
|
-
* The jobs some sibling reads outputs from — the only jobs worth executing.
|
|
90
|
-
* Matching over the serialized job catches every read site without modelling
|
|
91
|
-
* any; a false positive costs one wasted run, never a verdict.
|
|
92
|
-
*/
|
|
93
|
-
function neededJobIds(jobs) {
|
|
94
|
-
const needed = new Set();
|
|
95
|
-
for (const job of Object.values(jobs)) {
|
|
96
|
-
for (const m of JSON.stringify(job ?? {}).matchAll(NEEDS_OUTPUTS_RE)) {
|
|
97
|
-
needed.add(m[1]);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return needed;
|
|
101
|
-
}
|
|
102
23
|
export async function expandJobs(wf, ctx, reader, source, depth = 0, prefix = "", prefixResolved = true, scope = {}, executor) {
|
|
103
24
|
const entries = [];
|
|
104
25
|
const jobs = wf.jobs ?? {};
|
|
105
26
|
const statuses = {};
|
|
27
|
+
// Nothing dispatched or called this run, so an input this workflow declares
|
|
28
|
+
// and nothing supplied reads as the empty string (#125) — laid under, never
|
|
29
|
+
// over, whatever the caller bound.
|
|
30
|
+
let scoped = { ...scope, inputs: { ...absentInputs(wf), ...scope.inputs } };
|
|
106
31
|
// Selection is derived, never configured: execute exactly the jobs some
|
|
107
32
|
// sibling's `needs.*.outputs` read depends on, under the same `evalIf`
|
|
108
33
|
// verdict the main loop applies.
|
|
109
|
-
let scoped = scope;
|
|
110
34
|
const execFailures = {};
|
|
111
35
|
if (executor !== undefined) {
|
|
112
36
|
const needed = neededJobIds(jobs);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type Scope, type Val } from "../expr/val.js";
|
|
2
|
+
/**
|
|
3
|
+
* A `with:` value as the callee will see it, evaluated in the caller's scope.
|
|
4
|
+
* A whole-expression value keeps its evaluated type; mixed text renders to a
|
|
5
|
+
* string, all or nothing; anything unresolvable stays unknown.
|
|
6
|
+
*/
|
|
7
|
+
export declare function inputValue(raw: unknown, scope: Scope): Val;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { evaluateValue } from "../expr/evaluateValue.js";
|
|
2
|
+
import { UNKNOWN } from "../expr/val.js";
|
|
3
|
+
import { renderTemplate } from "../execute.js";
|
|
4
|
+
import { prScope } from "./prScope.js";
|
|
5
|
+
/**
|
|
6
|
+
* A `with:` value as the callee will see it, evaluated in the caller's scope.
|
|
7
|
+
* A whole-expression value keeps its evaluated type; mixed text renders to a
|
|
8
|
+
* string, all or nothing; anything unresolvable stays unknown.
|
|
9
|
+
*/
|
|
10
|
+
export function inputValue(raw, scope) {
|
|
11
|
+
if (raw === null || raw === undefined) {
|
|
12
|
+
return { kind: "value", v: "" };
|
|
13
|
+
}
|
|
14
|
+
if (typeof raw === "boolean" || typeof raw === "number") {
|
|
15
|
+
return { kind: "value", v: raw };
|
|
16
|
+
}
|
|
17
|
+
if (typeof raw !== "string") {
|
|
18
|
+
return UNKNOWN;
|
|
19
|
+
}
|
|
20
|
+
if (!raw.includes("${{")) {
|
|
21
|
+
return { kind: "value", v: raw };
|
|
22
|
+
}
|
|
23
|
+
const t = raw.trim();
|
|
24
|
+
// `${{a}} x ${{b}}` fails this test and takes the render path instead.
|
|
25
|
+
if (t.startsWith("${{") && t.indexOf("}}") === t.length - 2) {
|
|
26
|
+
return evaluateValue(t.slice(3, -2), prScope(scope));
|
|
27
|
+
}
|
|
28
|
+
const rendered = renderTemplate(raw, prScope(scope));
|
|
29
|
+
return rendered === null ? UNKNOWN : { kind: "value", v: rendered };
|
|
30
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Workflow } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The jobs some sibling reads outputs from — the only jobs worth executing.
|
|
4
|
+
* Matching over the serialized job catches every read site without modelling
|
|
5
|
+
* any; a false positive costs one wasted run, never a verdict.
|
|
6
|
+
*/
|
|
7
|
+
export declare function neededJobIds(jobs: Record<string, Workflow>): Set<string>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const NEEDS_OUTPUTS_RE = /needs\s*\.\s*([A-Za-z_][A-Za-z0-9_-]*)\s*\.\s*outputs\b/g;
|
|
2
|
+
/**
|
|
3
|
+
* The jobs some sibling reads outputs from — the only jobs worth executing.
|
|
4
|
+
* Matching over the serialized job catches every read site without modelling
|
|
5
|
+
* any; a false positive costs one wasted run, never a verdict.
|
|
6
|
+
*/
|
|
7
|
+
export function neededJobIds(jobs) {
|
|
8
|
+
const needed = new Set();
|
|
9
|
+
for (const job of Object.values(jobs)) {
|
|
10
|
+
for (const m of JSON.stringify(job ?? {}).matchAll(NEEDS_OUTPUTS_RE)) {
|
|
11
|
+
needed.add(m[1]);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return needed;
|
|
15
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** The `on.workflow_call.inputs` block, tolerating the YAML 1.1 `on` -> true key. */
|
|
2
|
+
export function workflowCallInputs(wf) {
|
|
3
|
+
const on = wf?.["on"] ?? wf?.["true"];
|
|
4
|
+
const inputs = on?.["workflow_call"]?.["inputs"];
|
|
5
|
+
return inputs !== null && typeof inputs === "object" ? inputs : {};
|
|
6
|
+
}
|