ci(workflows): isolated manual workflow_dispatch runs in release concurrency groups
- Updated the CI workflow concurrency rules to treat `workflow_dispatch` like a release path, grouping those runs by SHA and disabling cancel-in-progress. - Extended the `GhaEval` expression evaluator in `scripts/ci-concurrency.test.ts` to support `==`/`!=` and align falsy checks. - Added a regression test covering tagged-main `workflow_dispatch` runs using the release-style concurrency behavior.
This commit is contained in:
@@ -26,8 +26,8 @@ interface GhaCtx {
|
||||
}
|
||||
|
||||
// Single-purpose, hand-rolled evaluator for the operators / functions the
|
||||
// workflow's `concurrency` block uses: `startsWith`, `format`, `!`, `&&`, `||`,
|
||||
// parens, single-quoted strings, dotted property access. Matches GitHub's
|
||||
// workflow's `concurrency` block uses: `startsWith`, `format`, `!`, `==`,
|
||||
// `&&`, `||`, parens, single-quoted strings, dotted property access. Matches
|
||||
// short-circuit semantics: `&&`/`||` return the underlying value (not a coerced
|
||||
// bool), missing identifiers resolve to `null`, and `startsWith(null, …)` is
|
||||
// false because the searchString coerces to `""`.
|
||||
@@ -73,30 +73,47 @@ class GhaEval {
|
||||
let left = this.#and();
|
||||
while (this.#consume("||")) {
|
||||
const right = this.#and();
|
||||
// Truthy left wins; only null/false/""/0 fall through.
|
||||
if (left !== null && left !== false && left !== "" && left !== 0) continue;
|
||||
// Truthy left wins; only null/false/"" fall through.
|
||||
if (left !== null && left !== false && left !== "") continue;
|
||||
left = right;
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
#and(): Value {
|
||||
let left = this.#unary();
|
||||
let left = this.#eq();
|
||||
while (this.#consume("&&")) {
|
||||
const right = this.#unary();
|
||||
const right = this.#eq();
|
||||
// Falsy left short-circuits and is returned verbatim.
|
||||
if (left === null || left === false || left === "" || left === 0) continue;
|
||||
if (left === null || left === false || left === "") continue;
|
||||
left = right;
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
#eq(): Value {
|
||||
let left = this.#unary();
|
||||
while (true) {
|
||||
if (this.#consume("==")) {
|
||||
const right = this.#unary();
|
||||
left = left === right;
|
||||
continue;
|
||||
}
|
||||
if (this.#consume("!=")) {
|
||||
const right = this.#unary();
|
||||
left = left !== right;
|
||||
continue;
|
||||
}
|
||||
return left;
|
||||
}
|
||||
}
|
||||
|
||||
#unary(): Value {
|
||||
this.#skipWs();
|
||||
if (this.src[this.#pos] === "!") {
|
||||
this.#pos++;
|
||||
const v = this.#unary();
|
||||
return v === null || v === false || v === "" || v === 0;
|
||||
return v === null || v === false || v === "";
|
||||
}
|
||||
return this.#primary();
|
||||
}
|
||||
@@ -270,6 +287,16 @@ describe("ci.yml concurrency", () => {
|
||||
expect(GhaEval.template(cancelTemplate, ctx)).toBe("false");
|
||||
});
|
||||
|
||||
it("workflow_dispatch from tagged main HEAD is isolated before release_metadata can inspect tags", () => {
|
||||
const ctx = baseCtx({
|
||||
event_name: "workflow_dispatch",
|
||||
sha: "taggedmain123",
|
||||
event: {},
|
||||
});
|
||||
expect(GhaEval.template(groupTemplate, ctx)).toBe("CI-release-taggedmain123");
|
||||
expect(GhaEval.template(cancelTemplate, ctx)).toBe("false");
|
||||
});
|
||||
|
||||
it("regular main push: branch-wide group, cancel-in-progress enabled", () => {
|
||||
const ctx = baseCtx({ event: { head_commit: { message: "fix(ux): theme tweak" } } });
|
||||
expect(GhaEval.template(groupTemplate, ctx)).toBe("CI-refs/heads/main");
|
||||
|
||||
Reference in New Issue
Block a user