willfire 0.1.11 → 0.1.13
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/cli/index.d.ts +1 -0
- package/dist/cli/index.js +1 -0
- package/dist/cli/parseArgs.d.ts +9 -0
- package/dist/cli/parseArgs.js +42 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +41 -0
- package/dist/entries/index.d.ts +3 -0
- package/dist/entries/index.js +3 -0
- package/dist/entries/isJobEntry.d.ts +3 -0
- package/dist/entries/isJobEntry.js +2 -0
- package/dist/entries/isWorkflowEntry.d.ts +3 -0
- package/dist/entries/isWorkflowEntry.js +2 -0
- package/dist/entries/jobName.d.ts +3 -0
- package/dist/entries/jobName.js +2 -0
- package/dist/execute.d.ts +2 -2
- package/dist/execute.js +3 -1
- package/dist/expr/applyFunction.d.ts +11 -0
- package/dist/expr/applyFunction.js +41 -0
- package/dist/expr/asBool.d.ts +10 -0
- package/dist/expr/asBool.js +12 -0
- package/dist/expr/coalesceAnd.d.ts +7 -0
- package/dist/expr/coalesceAnd.js +17 -0
- package/dist/expr/coalesceOr.d.ts +6 -0
- package/dist/expr/coalesceOr.js +16 -0
- package/dist/expr/compare.d.ts +12 -0
- package/dist/expr/compare.js +54 -0
- package/dist/expr/cursor.d.ts +11 -0
- package/dist/expr/cursor.js +25 -0
- package/dist/expr/evaluate.d.ts +3 -0
- package/dist/expr/evaluate.js +6 -0
- package/dist/expr/evaluateValue.d.ts +14 -0
- package/dist/expr/evaluateValue.js +37 -0
- package/dist/expr/fromJson.d.ts +11 -0
- package/dist/expr/fromJson.js +29 -0
- package/dist/expr/index.d.ts +35 -0
- package/dist/expr/index.js +34 -0
- package/dist/expr/indexVal.d.ts +7 -0
- package/dist/expr/indexVal.js +34 -0
- package/dist/expr/lookup.d.ts +2 -0
- package/dist/expr/lookup.js +51 -0
- package/dist/expr/negate.d.ts +1 -0
- package/dist/expr/negate.js +3 -0
- package/dist/expr/parseAnd.d.ts +3 -0
- package/dist/expr/parseAnd.js +10 -0
- package/dist/expr/parseAtom.d.ts +3 -0
- package/dist/expr/parseAtom.js +43 -0
- package/dist/expr/parseCall.d.ts +9 -0
- package/dist/expr/parseCall.js +25 -0
- package/dist/expr/parseCmp.d.ts +3 -0
- package/dist/expr/parseCmp.js +12 -0
- package/dist/expr/parseOr.d.ts +11 -0
- package/dist/expr/parseOr.js +18 -0
- package/dist/expr/parsePrimary.d.ts +4 -0
- package/dist/expr/parsePrimary.js +16 -0
- package/dist/expr/parseUnary.d.ts +3 -0
- package/dist/expr/parseUnary.js +11 -0
- package/dist/expr/tokenize.d.ts +24 -0
- package/dist/expr/tokenize.js +74 -0
- package/dist/expr/truthy.d.ts +7 -0
- package/dist/expr/truthy.js +29 -0
- package/dist/expr/val.d.ts +72 -0
- package/dist/expr/val.js +1 -0
- package/dist/filters/index.d.ts +2 -0
- package/dist/filters/index.js +2 -0
- package/dist/filters/matchFilters.d.ts +2 -0
- package/dist/filters/matchFilters.js +12 -0
- package/dist/filters/patternToRegex.d.ts +1 -0
- package/dist/filters/patternToRegex.js +35 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +6 -0
- package/dist/jobs/evalIf.d.ts +10 -0
- package/dist/jobs/evalIf.js +18 -0
- package/dist/jobs/expandJobs.d.ts +4 -0
- package/dist/jobs/expandJobs.js +247 -0
- package/dist/jobs/expandWorkflowJobs.d.ts +14 -0
- package/dist/jobs/expandWorkflowJobs.js +14 -0
- package/dist/jobs/index.d.ts +4 -0
- package/dist/jobs/index.js +4 -0
- package/dist/jobs/prScope.d.ts +10 -0
- package/dist/jobs/prScope.js +19 -0
- package/dist/matrix/expandMatrix.d.ts +4 -0
- package/dist/matrix/expandMatrix.js +6 -0
- package/dist/matrix/expandMatrixDetailed.d.ts +3 -0
- package/dist/matrix/expandMatrixDetailed.js +74 -0
- package/dist/matrix/formatMatrixValue.d.ts +8 -0
- package/dist/matrix/formatMatrixValue.js +16 -0
- package/dist/matrix/index.d.ts +4 -0
- package/dist/matrix/index.js +4 -0
- package/dist/matrix/matrixSuffix.d.ts +3 -0
- package/dist/matrix/matrixSuffix.js +8 -0
- package/dist/names/index.d.ts +4 -0
- package/dist/names/index.js +4 -0
- package/dist/names/jobDisplayName.d.ts +16 -0
- package/dist/names/jobDisplayName.js +25 -0
- package/dist/names/lookupPath.d.ts +1 -0
- package/dist/names/lookupPath.js +9 -0
- package/dist/names/renderName.d.ts +2 -0
- package/dist/names/renderName.js +26 -0
- package/dist/names/skippedDisplayName.d.ts +14 -0
- package/dist/names/skippedDisplayName.js +16 -0
- package/dist/predict/finalizePrediction.d.ts +2 -0
- package/dist/predict/finalizePrediction.js +19 -0
- package/dist/predict/index.d.ts +5 -0
- package/dist/predict/index.js +5 -0
- package/dist/predict/makeOctokit.d.ts +2 -0
- package/dist/predict/makeOctokit.js +7 -0
- package/dist/predict/predict.d.ts +3 -0
- package/dist/predict/predict.js +214 -0
- package/dist/predict/sourceKey.d.ts +3 -0
- package/dist/predict/sourceKey.js +2 -0
- package/dist/predict/stackTargetRef.d.ts +14 -0
- package/dist/predict/stackTargetRef.js +58 -0
- package/dist/triggers/getPrTrigger.d.ts +3 -0
- package/dist/triggers/getPrTrigger.js +18 -0
- package/dist/triggers/index.d.ts +2 -0
- package/dist/triggers/index.js +2 -0
- package/dist/triggers/workflowDispatches.d.ts +2 -0
- package/dist/triggers/workflowDispatches.js +45 -0
- package/dist/{predict.d.ts → types.d.ts} +64 -53
- package/dist/types.js +1 -0
- package/dist/uses/index.d.ts +1 -0
- package/dist/uses/index.js +1 -0
- package/dist/uses/parseUses.d.ts +15 -0
- package/dist/uses/parseUses.js +33 -0
- package/dist/verify.js +1 -1
- package/package.json +7 -7
- package/dist/expr.d.ts +0 -117
- package/dist/expr.js +0 -455
- package/dist/predict.js +0 -912
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { UNKNOWN } from "./val.js";
|
|
2
|
+
export function lookup(scope, path) {
|
|
3
|
+
const dot = path.indexOf(".");
|
|
4
|
+
if (dot < 0) {
|
|
5
|
+
return UNKNOWN;
|
|
6
|
+
}
|
|
7
|
+
const head = path.slice(0, dot);
|
|
8
|
+
const rest = path.slice(dot + 1);
|
|
9
|
+
if (head === "inputs") {
|
|
10
|
+
return scope.inputs?.[rest] ?? UNKNOWN;
|
|
11
|
+
}
|
|
12
|
+
if (head === "github") {
|
|
13
|
+
const v = scope.github?.[rest];
|
|
14
|
+
return v === undefined ? UNKNOWN : { kind: "value", v };
|
|
15
|
+
}
|
|
16
|
+
if (head === "needs") {
|
|
17
|
+
// Only `needs.<job>.outputs.<name>` is modelled. `needs.<job>.result`
|
|
18
|
+
// is a verdict on a run that has not happened; anything else is not a
|
|
19
|
+
// shape the context has.
|
|
20
|
+
const parts = rest.split(".");
|
|
21
|
+
if (parts.length !== 3 || parts[1] !== "outputs") {
|
|
22
|
+
return UNKNOWN;
|
|
23
|
+
}
|
|
24
|
+
const job = scope.needs?.[parts[0]];
|
|
25
|
+
if (job == null) {
|
|
26
|
+
return UNKNOWN;
|
|
27
|
+
}
|
|
28
|
+
// A known job's missing output is the empty string, not a hole: the
|
|
29
|
+
// caller promised the set is complete, and that is what the runner
|
|
30
|
+
// substitutes for an output no step wrote.
|
|
31
|
+
return { kind: "value", v: job.outputs[parts[2]] ?? "" };
|
|
32
|
+
}
|
|
33
|
+
if (head === "steps") {
|
|
34
|
+
// The same shape as `needs`, for the same reason: only
|
|
35
|
+
// `steps.<id>.outputs.<name>` is modelled. `steps.<id>.outcome` and
|
|
36
|
+
// `.conclusion` are verdicts on how a step ran, which the executor does
|
|
37
|
+
// not track — a failed step fails the whole execution instead.
|
|
38
|
+
const parts = rest.split(".");
|
|
39
|
+
if (parts.length !== 3 || parts[1] !== "outputs") {
|
|
40
|
+
return UNKNOWN;
|
|
41
|
+
}
|
|
42
|
+
const step = scope.steps?.[parts[0]];
|
|
43
|
+
if (step == null) {
|
|
44
|
+
return UNKNOWN;
|
|
45
|
+
}
|
|
46
|
+
return { kind: "value", v: step.outputs[parts[2]] ?? "" };
|
|
47
|
+
}
|
|
48
|
+
// `matrix.*`, `env.*`, `vars.*`, `secrets.*`: all require something that
|
|
49
|
+
// has not happened yet at prediction time.
|
|
50
|
+
return UNKNOWN;
|
|
51
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function negate(b: boolean | null): boolean | null;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { coalesceAnd } from "./coalesceAnd.js";
|
|
2
|
+
import { parseCmp } from "./parseCmp.js";
|
|
3
|
+
export function parseAnd(cur, scope) {
|
|
4
|
+
let left = parseCmp(cur, scope);
|
|
5
|
+
while (cur.eatOp("&&")) {
|
|
6
|
+
const right = parseCmp(cur, scope);
|
|
7
|
+
left = coalesceAnd(left, right);
|
|
8
|
+
}
|
|
9
|
+
return left;
|
|
10
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { lookup } from "./lookup.js";
|
|
2
|
+
import { parseCall } from "./parseCall.js";
|
|
3
|
+
import { parseOr } from "./parseOr.js";
|
|
4
|
+
import { UNKNOWN } from "./val.js";
|
|
5
|
+
export function parseAtom(cur, scope) {
|
|
6
|
+
const t = cur.peek();
|
|
7
|
+
if (t == null) {
|
|
8
|
+
return UNKNOWN;
|
|
9
|
+
}
|
|
10
|
+
if (t.t === "op" && t.v === "(") {
|
|
11
|
+
cur.advance();
|
|
12
|
+
const v = parseOr(cur, scope);
|
|
13
|
+
if (!cur.eatOp(")")) {
|
|
14
|
+
return UNKNOWN;
|
|
15
|
+
}
|
|
16
|
+
return v;
|
|
17
|
+
}
|
|
18
|
+
if (t.t === "str") {
|
|
19
|
+
cur.advance();
|
|
20
|
+
return { kind: "value", v: t.v };
|
|
21
|
+
}
|
|
22
|
+
if (t.t === "num") {
|
|
23
|
+
cur.advance();
|
|
24
|
+
return { kind: "value", v: t.v };
|
|
25
|
+
}
|
|
26
|
+
if (t.t === "bool") {
|
|
27
|
+
cur.advance();
|
|
28
|
+
return { kind: "value", v: t.v };
|
|
29
|
+
}
|
|
30
|
+
if (t.t === "null") {
|
|
31
|
+
cur.advance();
|
|
32
|
+
return { kind: "value", v: "" };
|
|
33
|
+
}
|
|
34
|
+
if (t.t === "path") {
|
|
35
|
+
cur.advance();
|
|
36
|
+
// A `(` right after a name makes it a call, not a path.
|
|
37
|
+
if (cur.eatOp("(")) {
|
|
38
|
+
return parseCall(cur, scope, t.v);
|
|
39
|
+
}
|
|
40
|
+
return lookup(scope, t.v);
|
|
41
|
+
}
|
|
42
|
+
return UNKNOWN;
|
|
43
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Cursor } from "./cursor.js";
|
|
2
|
+
import { type Scope, type Val } from "./val.js";
|
|
3
|
+
/**
|
|
4
|
+
* A function call, entered after its `(` was consumed. Arguments are always
|
|
5
|
+
* parsed, even for functions we cannot evaluate — the tokens have to be
|
|
6
|
+
* consumed either way or the rest of the expression parses against the wrong
|
|
7
|
+
* position.
|
|
8
|
+
*/
|
|
9
|
+
export declare function parseCall(cur: Cursor, scope: Scope, name: string): Val;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { applyFunction } from "./applyFunction.js";
|
|
2
|
+
import { parseOr } from "./parseOr.js";
|
|
3
|
+
import { UNKNOWN } from "./val.js";
|
|
4
|
+
/**
|
|
5
|
+
* A function call, entered after its `(` was consumed. Arguments are always
|
|
6
|
+
* parsed, even for functions we cannot evaluate — the tokens have to be
|
|
7
|
+
* consumed either way or the rest of the expression parses against the wrong
|
|
8
|
+
* position.
|
|
9
|
+
*/
|
|
10
|
+
export function parseCall(cur, scope, name) {
|
|
11
|
+
const args = [];
|
|
12
|
+
if (!cur.eatOp(")")) {
|
|
13
|
+
for (;;) {
|
|
14
|
+
args.push(parseOr(cur, scope));
|
|
15
|
+
if (cur.eatOp(",")) {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (cur.eatOp(")")) {
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
return UNKNOWN; // malformed argument list
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return applyFunction(name.toLowerCase(), args);
|
|
25
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { compare } from "./compare.js";
|
|
2
|
+
import { parseUnary } from "./parseUnary.js";
|
|
3
|
+
export function parseCmp(cur, scope) {
|
|
4
|
+
const left = parseUnary(cur, scope);
|
|
5
|
+
for (const op of ["==", "!=", "<=", ">=", "<", ">"]) {
|
|
6
|
+
if (cur.eatOp(op)) {
|
|
7
|
+
const right = parseUnary(cur, scope);
|
|
8
|
+
return compare(op, left, right);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return left;
|
|
12
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Cursor } from "./cursor.js";
|
|
2
|
+
import type { Scope, Val } from "./val.js";
|
|
3
|
+
/**
|
|
4
|
+
* Recursive descent over GitHub's precedence order, loosest first:
|
|
5
|
+
* `||`, then `&&`, then comparison, then `!`, then a primary.
|
|
6
|
+
*
|
|
7
|
+
* `&&` and `||` are value operators, not boolean ones — `a || b` yields the
|
|
8
|
+
* first truthy operand, which is why `(x || y) != '[]'` parses as a
|
|
9
|
+
* comparison against a coalesced value rather than a boolean.
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseOr(cur: Cursor, scope: Scope): Val;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { coalesceOr } from "./coalesceOr.js";
|
|
2
|
+
import { parseAnd } from "./parseAnd.js";
|
|
3
|
+
/**
|
|
4
|
+
* Recursive descent over GitHub's precedence order, loosest first:
|
|
5
|
+
* `||`, then `&&`, then comparison, then `!`, then a primary.
|
|
6
|
+
*
|
|
7
|
+
* `&&` and `||` are value operators, not boolean ones — `a || b` yields the
|
|
8
|
+
* first truthy operand, which is why `(x || y) != '[]'` parses as a
|
|
9
|
+
* comparison against a coalesced value rather than a boolean.
|
|
10
|
+
*/
|
|
11
|
+
export function parseOr(cur, scope) {
|
|
12
|
+
let left = parseAnd(cur, scope);
|
|
13
|
+
while (cur.eatOp("||")) {
|
|
14
|
+
const right = parseAnd(cur, scope);
|
|
15
|
+
left = coalesceOr(left, right);
|
|
16
|
+
}
|
|
17
|
+
return left;
|
|
18
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { indexVal } from "./indexVal.js";
|
|
2
|
+
import { parseAtom } from "./parseAtom.js";
|
|
3
|
+
import { parseOr } from "./parseOr.js";
|
|
4
|
+
import { UNKNOWN } from "./val.js";
|
|
5
|
+
/** An atom plus any `[...]` accesses hanging off it, tightest-binding. */
|
|
6
|
+
export function parsePrimary(cur, scope) {
|
|
7
|
+
let v = parseAtom(cur, scope);
|
|
8
|
+
while (cur.eatOp("[")) {
|
|
9
|
+
const idx = parseOr(cur, scope);
|
|
10
|
+
if (!cur.eatOp("]")) {
|
|
11
|
+
return UNKNOWN;
|
|
12
|
+
}
|
|
13
|
+
v = indexVal(v, idx);
|
|
14
|
+
}
|
|
15
|
+
return v;
|
|
16
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { asBool } from "./asBool.js";
|
|
2
|
+
import { negate } from "./negate.js";
|
|
3
|
+
import { parsePrimary } from "./parsePrimary.js";
|
|
4
|
+
import { truthy } from "./truthy.js";
|
|
5
|
+
export function parseUnary(cur, scope) {
|
|
6
|
+
if (cur.eatOp("!")) {
|
|
7
|
+
const v = parseUnary(cur, scope);
|
|
8
|
+
return asBool(negate(truthy(v)));
|
|
9
|
+
}
|
|
10
|
+
return parsePrimary(cur, scope);
|
|
11
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type Tok = {
|
|
2
|
+
t: "str";
|
|
3
|
+
v: string;
|
|
4
|
+
} | {
|
|
5
|
+
t: "num";
|
|
6
|
+
v: number;
|
|
7
|
+
} | {
|
|
8
|
+
t: "bool";
|
|
9
|
+
v: boolean;
|
|
10
|
+
} | {
|
|
11
|
+
t: "null";
|
|
12
|
+
} | {
|
|
13
|
+
t: "path";
|
|
14
|
+
v: string;
|
|
15
|
+
} | {
|
|
16
|
+
t: "op";
|
|
17
|
+
v: string;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Split a condition into tokens, or return null if it contains something this
|
|
21
|
+
* evaluator has no token for. Returning null rather than throwing keeps the
|
|
22
|
+
* "unrecognized is unknown" rule in one place at the top of `evaluate`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function tokenize(src: string): Tok[] | null;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const OPS = ["&&", "||", "==", "!=", "<=", ">=", "!", "<", ">", "(", ")", "[", "]", ","];
|
|
2
|
+
/**
|
|
3
|
+
* Split a condition into tokens, or return null if it contains something this
|
|
4
|
+
* evaluator has no token for. Returning null rather than throwing keeps the
|
|
5
|
+
* "unrecognized is unknown" rule in one place at the top of `evaluate`.
|
|
6
|
+
*/
|
|
7
|
+
export function tokenize(src) {
|
|
8
|
+
const out = [];
|
|
9
|
+
let i = 0;
|
|
10
|
+
while (i < src.length) {
|
|
11
|
+
const c = src[i];
|
|
12
|
+
if (c === " " || c === "\t" || c === "\n" || c === "\r") {
|
|
13
|
+
i++;
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
// Single-quoted string. GitHub escapes an inner quote by doubling it.
|
|
17
|
+
if (c === "'") {
|
|
18
|
+
let j = i + 1;
|
|
19
|
+
let s = "";
|
|
20
|
+
for (;;) {
|
|
21
|
+
if (j >= src.length) {
|
|
22
|
+
return null; // unterminated
|
|
23
|
+
}
|
|
24
|
+
if (src[j] === "'") {
|
|
25
|
+
if (src[j + 1] === "'") {
|
|
26
|
+
s += "'";
|
|
27
|
+
j += 2;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
j++;
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
s += src[j];
|
|
34
|
+
j++;
|
|
35
|
+
}
|
|
36
|
+
out.push({ t: "str", v: s });
|
|
37
|
+
i = j;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const op = OPS.find((o) => src.startsWith(o, i));
|
|
41
|
+
if (op != null) {
|
|
42
|
+
out.push({ t: "op", v: op });
|
|
43
|
+
i += op.length;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const word = /^[A-Za-z_][A-Za-z0-9_.\-]*/.exec(src.slice(i));
|
|
47
|
+
if (word != null) {
|
|
48
|
+
const w = word[0];
|
|
49
|
+
i += w.length;
|
|
50
|
+
const lower = w.toLowerCase();
|
|
51
|
+
if (lower === "true") {
|
|
52
|
+
out.push({ t: "bool", v: true });
|
|
53
|
+
}
|
|
54
|
+
else if (lower === "false") {
|
|
55
|
+
out.push({ t: "bool", v: false });
|
|
56
|
+
}
|
|
57
|
+
else if (lower === "null") {
|
|
58
|
+
out.push({ t: "null" });
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
out.push({ t: "path", v: w });
|
|
62
|
+
}
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const num = /^-?\d+(\.\d+)?/.exec(src.slice(i));
|
|
66
|
+
if (num != null) {
|
|
67
|
+
out.push({ t: "num", v: Number(num[0]) });
|
|
68
|
+
i += num[0].length;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
return null; // a character we have no token for
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Val } from "./val.js";
|
|
2
|
+
/**
|
|
3
|
+
* GitHub's truthiness: empty string, zero, `false` and null are false, and
|
|
4
|
+
* every other value is true. `'0'` and `'false'` are non-empty strings, so
|
|
5
|
+
* both are true — the same trap as JavaScript, kept deliberately identical.
|
|
6
|
+
*/
|
|
7
|
+
export declare function truthy(val: Val): boolean | null;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub's truthiness: empty string, zero, `false` and null are false, and
|
|
3
|
+
* every other value is true. `'0'` and `'false'` are non-empty strings, so
|
|
4
|
+
* both are true — the same trap as JavaScript, kept deliberately identical.
|
|
5
|
+
*/
|
|
6
|
+
export function truthy(val) {
|
|
7
|
+
switch (val.kind) {
|
|
8
|
+
case "truthy":
|
|
9
|
+
return true;
|
|
10
|
+
case "falsy":
|
|
11
|
+
return false;
|
|
12
|
+
case "unknown":
|
|
13
|
+
return null;
|
|
14
|
+
// GitHub does cast an array or an object to a boolean, but no workflow
|
|
15
|
+
// asks it to, and the answer is not worth guessing at to find out.
|
|
16
|
+
case "json":
|
|
17
|
+
return null;
|
|
18
|
+
case "value": {
|
|
19
|
+
const v = val.v;
|
|
20
|
+
if (typeof v === "boolean") {
|
|
21
|
+
return v;
|
|
22
|
+
}
|
|
23
|
+
if (typeof v === "number") {
|
|
24
|
+
return v !== 0;
|
|
25
|
+
}
|
|
26
|
+
return v !== "";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A partially-known value.
|
|
3
|
+
*
|
|
4
|
+
* `truthy` and `falsy` exist because short-circuiting yields truthiness
|
|
5
|
+
* without a value: `unknown && false` is falsy, but there is no string to
|
|
6
|
+
* hand back for it. Collapsing those into `unknown` would throw away the
|
|
7
|
+
* only fact worth having.
|
|
8
|
+
*/
|
|
9
|
+
export type Val = {
|
|
10
|
+
kind: "value";
|
|
11
|
+
v: string | number | boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* An array or an object, which only `fromJSON` produces. Kept apart from
|
|
15
|
+
* `value` because nothing else in the language accepts one: comparison
|
|
16
|
+
* refuses it, and its truthiness is not modelled. Matrix expansion is the
|
|
17
|
+
* one consumer, and it asks for the array directly.
|
|
18
|
+
*/
|
|
19
|
+
| {
|
|
20
|
+
kind: "json";
|
|
21
|
+
v: unknown[] | Record<string, unknown>;
|
|
22
|
+
} | {
|
|
23
|
+
kind: "truthy";
|
|
24
|
+
} | {
|
|
25
|
+
kind: "falsy";
|
|
26
|
+
} | {
|
|
27
|
+
kind: "unknown";
|
|
28
|
+
};
|
|
29
|
+
export declare const UNKNOWN: Val;
|
|
30
|
+
/** What a bare path resolves against. Anything absent is unknown, not empty. */
|
|
31
|
+
export interface Scope {
|
|
32
|
+
/**
|
|
33
|
+
* The inputs the caller passed, plus declared defaults for the ones it did
|
|
34
|
+
* not. A key mapped to `unknown` is deliberate: the caller supplied it, but
|
|
35
|
+
* as a `${{ }}` template we cannot evaluate. That is different from absent.
|
|
36
|
+
*/
|
|
37
|
+
inputs?: Record<string, Val>;
|
|
38
|
+
/** `github.*` values that are fixed for the run being predicted. */
|
|
39
|
+
github?: Record<string, string>;
|
|
40
|
+
/**
|
|
41
|
+
* Outputs of jobs this workflow's jobs `needs`, keyed by job id.
|
|
42
|
+
*
|
|
43
|
+
* `outputs` is the *complete* set for that job, which is what makes a key
|
|
44
|
+
* that is absent from it mean the empty string — the same answer the runner
|
|
45
|
+
* gives for an output no step wrote. Handing in a partial map is therefore a
|
|
46
|
+
* lie, not a shortcut: leave the job out entirely instead, and every lookup
|
|
47
|
+
* against it stays unknown.
|
|
48
|
+
*
|
|
49
|
+
* The values are raw strings, because that is what a step wrote to
|
|
50
|
+
* `$GITHUB_OUTPUT` and what the runner substitutes. Parsing eagerly would
|
|
51
|
+
* break the guards written against them — `!= '[]'` compares a string to a
|
|
52
|
+
* string, and an array on the left makes it unknown. `fromJSON` is the only
|
|
53
|
+
* thing that turns one into a structure, at the point the workflow asks.
|
|
54
|
+
*/
|
|
55
|
+
needs?: Record<string, {
|
|
56
|
+
outputs: Record<string, string>;
|
|
57
|
+
}>;
|
|
58
|
+
/**
|
|
59
|
+
* Outputs of steps that already ran, keyed by step id. Only one caller can
|
|
60
|
+
* fill this honestly: the executor's step walk (see ../execute/runSteps.ts),
|
|
61
|
+
* which is the single place a step has actually run by the time an
|
|
62
|
+
* expression reads it. The contract is the same as `needs`: a step named
|
|
63
|
+
* here carries its *complete* output set, so an absent key is the empty
|
|
64
|
+
* string the runner substitutes, while a step this map does not name stays
|
|
65
|
+
* unknown. A skipped step is present with no outputs at all — which is
|
|
66
|
+
* exactly what lets `steps.a.outputs.x || steps.b.outputs.x` coalesce past
|
|
67
|
+
* it.
|
|
68
|
+
*/
|
|
69
|
+
steps?: Record<string, {
|
|
70
|
+
outputs: Record<string, string>;
|
|
71
|
+
}>;
|
|
72
|
+
}
|
package/dist/expr/val.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const UNKNOWN = { kind: "unknown" };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { patternToRegex } from "./patternToRegex.js";
|
|
2
|
+
/** Order-sensitive match: last matching pattern wins; ! negates. */
|
|
3
|
+
export function matchFilters(value, patterns) {
|
|
4
|
+
let matched = false;
|
|
5
|
+
for (const pat of patterns) {
|
|
6
|
+
const neg = pat.startsWith("!");
|
|
7
|
+
const p = neg ? pat.slice(1) : pat;
|
|
8
|
+
if (patternToRegex(p).test(value))
|
|
9
|
+
matched = !neg;
|
|
10
|
+
}
|
|
11
|
+
return matched;
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function patternToRegex(pat: string): RegExp;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// GitHub filter pattern glob. Grammar per docs: * (any chars except /),
|
|
2
|
+
// ** (any chars), ? (zero or one of preceding char), + (one or more of
|
|
3
|
+
// preceding char), [ranges], leading ! negates.
|
|
4
|
+
const escapeRegex = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5
|
+
export function patternToRegex(pat) {
|
|
6
|
+
let out = "";
|
|
7
|
+
for (let i = 0; i < pat.length; i++) {
|
|
8
|
+
const c = pat[i];
|
|
9
|
+
if (c === "*") {
|
|
10
|
+
if (pat[i + 1] === "*") {
|
|
11
|
+
out += ".*";
|
|
12
|
+
i++;
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
out += "[^/]*";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
else if (c === "?" || c === "+") {
|
|
19
|
+
out += c;
|
|
20
|
+
}
|
|
21
|
+
else if (c === "[") {
|
|
22
|
+
const j = pat.indexOf("]", i + 1);
|
|
23
|
+
out += pat.slice(i, j + 1);
|
|
24
|
+
i = j;
|
|
25
|
+
}
|
|
26
|
+
else if (c === "\\") {
|
|
27
|
+
i++;
|
|
28
|
+
out += escapeRegex(pat[i]);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
out += escapeRegex(c);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return new RegExp(`^${out}$`);
|
|
35
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { jobName, isWorkflowEntry, isJobEntry } from "./entries/index.js";
|
|
2
|
+
export { patternToRegex, matchFilters } from "./filters/index.js";
|
|
3
|
+
export { expandMatrix } from "./matrix/index.js";
|
|
4
|
+
export { evalIf, expandWorkflowJobs } from "./jobs/index.js";
|
|
5
|
+
export { parseUses } from "./uses/index.js";
|
|
6
|
+
export { makeOctokit, predict } from "./predict/index.js";
|
|
7
|
+
export type { JobName, WorkflowEntry, JobEntry, Entry, Prediction, PrEventAction, PredictOptions, Ctx, ExpandedJob, SourceRef, WorkflowSource, FetchWorkflow, ResolveRef, WorkflowReader, UsesTarget, } from "./types.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { jobName, isWorkflowEntry, isJobEntry } from "./entries/index.js";
|
|
2
|
+
export { patternToRegex, matchFilters } from "./filters/index.js";
|
|
3
|
+
export { expandMatrix } from "./matrix/index.js";
|
|
4
|
+
export { evalIf, expandWorkflowJobs } from "./jobs/index.js";
|
|
5
|
+
export { parseUses } from "./uses/index.js";
|
|
6
|
+
export { makeOctokit, predict } from "./predict/index.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Scope } from "../expr/val.js";
|
|
2
|
+
/**
|
|
3
|
+
* Return run|skipped|unknown for a job-level `if:`.
|
|
4
|
+
*
|
|
5
|
+
* `scope` carries the inputs the calling workflow passed down, and any job
|
|
6
|
+
* outputs the caller knows. Without it a reusable workflow's guards are all
|
|
7
|
+
* unknown, because every one of them is written against `inputs.*` or
|
|
8
|
+
* `needs.*`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function evalIf(cond: any, scope?: Scope): "run" | "skipped" | "unknown";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { evaluate } from "../expr/evaluate.js";
|
|
2
|
+
import { prScope } from "./prScope.js";
|
|
3
|
+
/**
|
|
4
|
+
* Return run|skipped|unknown for a job-level `if:`.
|
|
5
|
+
*
|
|
6
|
+
* `scope` carries the inputs the calling workflow passed down, and any job
|
|
7
|
+
* outputs the caller knows. Without it a reusable workflow's guards are all
|
|
8
|
+
* unknown, because every one of them is written against `inputs.*` or
|
|
9
|
+
* `needs.*`.
|
|
10
|
+
*/
|
|
11
|
+
export function evalIf(cond, scope = {}) {
|
|
12
|
+
if (cond == null)
|
|
13
|
+
return "run";
|
|
14
|
+
const verdict = evaluate(String(cond), prScope(scope));
|
|
15
|
+
if (verdict === null)
|
|
16
|
+
return "unknown";
|
|
17
|
+
return verdict ? "run" : "skipped";
|
|
18
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type Scope } from "../expr/val.js";
|
|
2
|
+
import type { JobExecutor } from "../execute.js";
|
|
3
|
+
import type { Ctx, ExpandedJob, Workflow, WorkflowReader, WorkflowSource } from "../types.js";
|
|
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[]>;
|