ci: run every workflow step under Nushell #491

Merged
nrupard merged 2 commits from chore/BUNYIP-489-nushell-workflow-shell into main 2026-08-07 18:42:21 +02:00
Member

.forgejo/workflows/ was half converted: three workflows set shell: nu {0} on most steps while 26 run: steps still fell through to the runner's default shell. Mixed is the worst state to be in - a reader cannot tell which shell a step runs under without checking the preceding line, and any step added later silently inherits Bash.

The shell is now declared once per job as defaults.run.shell: nu {0} in all six workflows, and the redundant per-step shell: nu {0} lines are gone from build-api.yml, build-web.yml and create-release.yml. e2e.yml and e2e-pr.yml keep their working-directory: e2e default and gain the shell alongside it.

Two steps carried Bash-only syntax and were rewritten rather than relabelled. check.yml "Cap build parallelism" drops POSIX arithmetic expansion and >> for $"CARGO_BUILD_JOBS=((sys cpu | length) / 2 | into int)\n" | save --append $env.GITHUB_ENV, the same nproc/2 idiom the build workflows already use. check.yml "Docker builder stage" replaces 2>/dev/null || true with ^docker image rm ... | complete | ignore, which captures the exit code and stderr instead of raising, so the first run (tag absent) stays green; the build moves onto one line because Nushell has no backslash continuation. --provenance=false and the BUNYIP-231 / BUNYIP-241 comments are unchanged. e2e.yml's skip notice uses print instead of echo.

scripts/check-workflow-shell.nu is the gate: it parses every file under .forgejo/workflows/, and exits non-zero if a job is missing the Nushell shell default, if a step declares a non-nu shell, or if a file parses to no jobs at all (which would otherwise pass vacuously). Its --self-test mode builds three fixtures - a job with no shell default, a step declaring shell: bash, and a converted job - and fails unless the gate rejects the first two and accepts the third, so a gate that stopped detecting anything fails the build. check.yml runs --self-test then the real check, and just check-workflow-shell does the same locally as part of just check.

BUNYIP-490 merged to main while this PR was open and rewrote the scripts/check-*.sh guards in Nushell, so main is now merged in and the three conflicts it caused are resolved: just check keeps both new prerequisites (check-workflow-shell here, check-key-env + check-no-bash from main), CLAUDE.md keeps both convention bullets, and the getting-started prerequisite names all three Nushell consumers (just recipes, scripts/, workflow run: steps). The "the .sh guards keep their own shebang, rewrite tracked in BUNYIP-490" clause is dropped from CLAUDE.md and from the scripts/check-workflow-shell.nu header, since that rewrite has shipped. main's two new check.yml steps invoke .nu guards as external programs and carry no Bash syntax, so the gate passes over the merged workflows unchanged.

#BUNYIP-489

`.forgejo/workflows/` was half converted: three workflows set `shell: nu {0}` on most steps while 26 `run:` steps still fell through to the runner's default shell. Mixed is the worst state to be in - a reader cannot tell which shell a step runs under without checking the preceding line, and any step added later silently inherits Bash. The shell is now declared once per job as `defaults.run.shell: nu {0}` in all six workflows, and the redundant per-step `shell: nu {0}` lines are gone from build-api.yml, build-web.yml and create-release.yml. e2e.yml and e2e-pr.yml keep their `working-directory: e2e` default and gain the shell alongside it. Two steps carried Bash-only syntax and were rewritten rather than relabelled. check.yml "Cap build parallelism" drops POSIX arithmetic expansion and `>>` for `$"CARGO_BUILD_JOBS=((sys cpu | length) / 2 | into int)\n" | save --append $env.GITHUB_ENV`, the same nproc/2 idiom the build workflows already use. check.yml "Docker builder stage" replaces `2>/dev/null || true` with `^docker image rm ... | complete | ignore`, which captures the exit code and stderr instead of raising, so the first run (tag absent) stays green; the build moves onto one line because Nushell has no backslash continuation. `--provenance=false` and the BUNYIP-231 / BUNYIP-241 comments are unchanged. e2e.yml's skip notice uses `print` instead of `echo`. `scripts/check-workflow-shell.nu` is the gate: it parses every file under `.forgejo/workflows/`, and exits non-zero if a job is missing the Nushell shell default, if a step declares a non-`nu` shell, or if a file parses to no jobs at all (which would otherwise pass vacuously). Its `--self-test` mode builds three fixtures - a job with no shell default, a step declaring `shell: bash`, and a converted job - and fails unless the gate rejects the first two and accepts the third, so a gate that stopped detecting anything fails the build. check.yml runs `--self-test` then the real check, and `just check-workflow-shell` does the same locally as part of `just check`. BUNYIP-490 merged to main while this PR was open and rewrote the `scripts/check-*.sh` guards in Nushell, so main is now merged in and the three conflicts it caused are resolved: `just check` keeps both new prerequisites (`check-workflow-shell` here, `check-key-env` + `check-no-bash` from main), CLAUDE.md keeps both convention bullets, and the getting-started prerequisite names all three Nushell consumers (just recipes, `scripts/`, workflow `run:` steps). The "the .sh guards keep their own shebang, rewrite tracked in BUNYIP-490" clause is dropped from CLAUDE.md and from the `scripts/check-workflow-shell.nu` header, since that rewrite has shipped. main's two new check.yml steps invoke `.nu` guards as external programs and carry no Bash syntax, so the gate passes over the merged workflows unchanged. #BUNYIP-489
ci: run every workflow step under Nushell
All checks were successful
E2E PR gate / E2E / PR gate (pull_request) Successful in 35s
Check / fmt + clippy + build + tests (pull_request) Successful in 29m7s
46f5d6dcf9
`.forgejo/workflows/` was half converted: three workflows set `shell: nu {0}` on most steps while 26 `run:` steps still fell through to the runner's default shell. Mixed is the worst state to be in - a reader cannot tell which shell a step runs under without checking the preceding line, and any step added later silently inherits Bash.

The shell is now declared once per job as `defaults.run.shell: nu {0}` in all six workflows, and the redundant per-step `shell: nu {0}` lines are gone from build-api.yml, build-web.yml and create-release.yml. e2e.yml and e2e-pr.yml keep their `working-directory: e2e` default and gain the shell alongside it.

Two steps carried Bash-only syntax and were rewritten rather than relabelled. check.yml "Cap build parallelism" drops POSIX arithmetic expansion and `>>` for `$"CARGO_BUILD_JOBS=((sys cpu | length) / 2 | into int)\n" | save --append $env.GITHUB_ENV`, the same nproc/2 idiom the build workflows already use. check.yml "Docker builder stage" replaces `2>/dev/null || true` with `^docker image rm ... | complete | ignore`, which captures the exit code and stderr instead of raising, so the first run (tag absent) stays green; the build moves onto one line because Nushell has no backslash continuation. `--provenance=false` and the BUNYIP-231 / BUNYIP-241 comments are unchanged. e2e.yml's skip notice uses `print` instead of `echo`.

`scripts/check-workflow-shell.nu` is the gate: it parses every file under `.forgejo/workflows/`, and exits non-zero if a job is missing the Nushell shell default, if a step declares a non-`nu` shell, or if a file parses to no jobs at all (which would otherwise pass vacuously). Its `--self-test` mode builds three fixtures - a job with no shell default, a step declaring `shell: bash`, and a converted job - and fails unless the gate rejects the first two and accepts the third, so a gate that stopped detecting anything fails the build. check.yml runs `--self-test` then the real check, and `just check-workflow-shell` does the same locally as part of `just check`.

The six `./scripts/check-*.sh` guards keep their own shebang, so the calling shell does not change their behaviour; rewriting them in Nushell is tracked in BUNYIP-490 and stays out of this PR.

#BUNYIP-489
Merge branch 'main' into chore/BUNYIP-489-nushell-workflow-shell
All checks were successful
E2E / PR gate (pull_request) Successful in 32s
Check / fmt + clippy + build + tests (pull_request) Successful in 27m32s
Create release / Create release from merged PR (pull_request) Has been skipped
d645134949
BUNYIP-490 landed on main first and rewrote the `scripts/check-*.sh` guards in Nushell, so the three conflicts are all places this branch described those guards as still being Bash. `just check` keeps both new prerequisites (`check-workflow-shell` from this branch, `check-key-env` + `check-no-bash` from main), CLAUDE.md keeps both convention bullets with the "the .sh guards keep their own shebang, rewrite tracked in BUNYIP-490" clause dropped as spent, and the getting-started prerequisite names all three Nushell consumers (just recipes, `scripts/`, workflow `run:` steps). The same spent clause is dropped from the header comment of `scripts/check-workflow-shell.nu`.

The gate still passes over the merged workflows: main's new check.yml steps ("Check the at-rest key stays single", "Check scripts/ stays Nushell") invoke `.nu` guards as external programs and carry no Bash syntax, and all six jobs keep their `defaults.run.shell: nu {0}`.

#BUNYIP-489
nrupard deleted branch chore/BUNYIP-489-nushell-workflow-shell 2026-08-07 18:42:21 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/bunyip!491
No description provided.