From a969b71cc3fc0478d77192aa08bb8f2837b70253 Mon Sep 17 00:00:00 2001 From: Snider Date: Wed, 29 Jul 2026 04:13:33 +0100 Subject: [PATCH] fix(wails3): forward `task`, and report inputs no caller can reach MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The v3 stack runs the project's own Taskfile target rather than composing a command, so which target runs is the entire configuration. The root action never declared `task` and the orchestrator never forwarded it, so every v3 build ran :build no matter what the caller asked for — and a caller that asked got no error, because Actions ignores a `with:` key the action does not declare. lthn/desktop asked for darwin:package, got darwin:build, and its next step failed on a bundle that was never created. macOS and Windows both went red; the release published nothing. The asymmetry report could not have caught it. It compares a callee's callers against each other, so an input *no* caller wires reads as unanimous agreement rather than as a dead option — and that is the worse case, because the option is documented on the callee and unreachable through the caller. Those are now their own section: 79 of them across the repo, most deliberate (a wrapper choosing not to expose a knob), and the counter is in the default output so the number moving is visible. Co-Authored-By: Virgil --- action.yml | 7 +++++++ actions/action.yml | 6 ++++++ tests/action_contracts.py | 34 +++++++++++++++++++++++++--------- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/action.yml b/action.yml index 1afa76e..3d80f6e 100644 --- a/action.yml +++ b/action.yml @@ -63,6 +63,13 @@ inputs: description: "Base name for release assets, before the -- suffix" required: false default: "" + task: + description: > + Wails v3 stack: the Taskfile target to run. Empty runs :build, which + produces a bare binary — a project wanting the bundle or an installer + names its package target here. + required: false + default: "" entry: description: > Deno stack: entry module to compile. Empty probes for run.ts, main.ts, diff --git a/actions/action.yml b/actions/action.yml index 70f84c4..2b7f3a1 100644 --- a/actions/action.yml +++ b/actions/action.yml @@ -172,6 +172,12 @@ runs: build-name: ${{ inputs.build-name }} app-working-directory: ${{ inputs.app-working-directory }} release-asset-prefix: ${{ inputs.release-asset-prefix }} + # The v3 stack runs the project's own Taskfile target, so which target + # is the whole configuration. Unforwarded it defaulted to :build on + # every caller — a caller asking for a package target got a bare binary + # and no error, because Actions ignores a `with:` key the action does + # not declare. + task: ${{ inputs.task }} - name: Call Go wrapper id: go diff --git a/tests/action_contracts.py b/tests/action_contracts.py index 38f80f2..4ffa68e 100755 --- a/tests/action_contracts.py +++ b/tests/action_contracts.py @@ -150,15 +150,28 @@ def main() -> int: # the output. Reach for `--asymmetries` when wiring a new stack or hunting # an option that appears to do nothing. asymmetries: list[str] = [] + # Inputs no caller wires at all. The asymmetry report cannot show these — + # it compares callers against each other, and unanimous silence looks like + # agreement — yet they are the worse case: the option exists on the callee, + # is documented there, and cannot be reached through the caller at all. A + # workflow that sets one gets no error, because Actions ignores an unknown + # `with:` key. That is how `task` reached the wails3 stack and never left + # the root action, pinning every v3 build to :build. + unreachable: list[str] = [] for callee, inputs in sorted(wiring.items()): for name, callers in sorted(inputs.items()): missing = sorted(c for c, ok in callers.items() if not ok) - if missing and len(missing) != len(callers): - has = sorted(c for c, ok in callers.items() if ok) - asymmetries.append( - f" {callee} :: {name}\n" - f" wired by {', '.join(has)}\n" - f" not by {', '.join(missing)}") + if not missing: + continue + if len(missing) == len(callers): + unreachable.append(f" {callee} :: {name}\n" + f" reached by no caller ({', '.join(missing)})") + continue + has = sorted(c for c, ok in callers.items() if ok) + asymmetries.append( + f" {callee} :: {name}\n" + f" wired by {', '.join(has)}\n" + f" not by {', '.join(missing)}") if problems: print("\n".join(f" {p}" for p in problems)) @@ -170,9 +183,12 @@ def main() -> int: print(f"\n{len(asymmetries)} input(s) wired by some callers of a callee " f"and not others:\n") print("\n".join(asymmetries)) - elif asymmetries: - print(f"({len(asymmetries)} caller asymmetries — " - f"run with --asymmetries to list them)") + print(f"\n{len(unreachable)} input(s) no caller wires — settable on the " + f"callee, unreachable through it:\n") + print("\n".join(unreachable)) + elif asymmetries or unreachable: + print(f"({len(asymmetries)} caller asymmetries, {len(unreachable)} " + f"unreachable inputs — run with --asymmetries to list them)") return 0