Skip to content

chore(shellcheck): add yarn shellcheck script and CI workflow#401

Draft
kriscendobot wants to merge 5 commits into
master-4a04d07from
chore/shellcheck-ci
Draft

chore(shellcheck): add yarn shellcheck script and CI workflow#401
kriscendobot wants to merge 5 commits into
master-4a04d07from
chore/shellcheck-ci

Conversation

@kriscendobot

Copy link
Copy Markdown
Collaborator

Summary

Adds yarn shellcheck plus a CI workflow running shellcheck against every .sh file tracked in the repo. A PR that touches no .sh files does not trigger the workflow at all (the workflow's paths: filter on **/*.sh provides the skip-when-no-.sh-touched behavior).

What lands

  • Root package.json: "shellcheck": "scripts/shellcheck.sh" script.
  • scripts/shellcheck.sh: wrapper that enumerates git ls-files '*.sh', exits 0 when empty, otherwise runs shellcheck -S warning over the list (forwarding any extra args to the underlying checker).
  • .github/workflows/shellcheck.yml: workflow with paths: ['**/*.sh', ...] on pull_request and an unfiltered run on push to master.
  • Mechanical shellcheck cleanups across the tree to make the gate green:
    • packages/compartment-mapper/test/neutralize.sh
    • packages/nat/scripts/npm-audit-fix.sh
    • scripts/check-packages.sh
    • scripts/maintenance/check-unused-deps.sh
    • scripts/npm-audit-fix.sh
    • scripts/posttypedoc.sh
    • scripts/set-versions.sh

Pre-existing shellcheck findings and how they were handled

Running shellcheck -S warning over the tree before this PR produced sixteen findings across seven files, in six distinct codes:

Code Count What it flags How handled
SC2148 4 missing #!/... shebang added #!/bin/sh or #!/bin/bash per the file's style
SC2164 5 cd without || exit appended || exit
SC2044 1 for ... in $(find ...) converted to while IFS= read -r ... ; do ... done < <(find ...)
SC2038 1 find ... | xargs converted to find ... -print0 | xargs -0
SC1007 3 CDPATH= ambiguous rewrote as CDPATH=''
SC2034 2 unused DIR=$(...) dropped the assignment (genuinely unused), and tightened the related read to read -r

All fixes are mechanical. None alter runtime behavior. The two stale npm-audit-fix.sh files (root scripts/ and packages/nat/scripts/) date back to 2019 and reference an AgoricBot-era flow with literal ??? placeholders; they remain in the tree because they are tracked, and the shebangs added here at least make them shellcheck-clean.

Skip behavior

A PR that touches no .sh files does not trigger the workflow at all (the workflow's paths: filter excludes everything else). Pushes to master run unconditionally so the gate is authoritative for the branch.

Local invocation

yarn shellcheck                     # default severity warning
yarn shellcheck --severity=error    # tighter gate
yarn shellcheck -- -e SC2086        # exclude a code

Regression evidence

The gate was sanity-tested by removing the shebang from packages/compartment-mapper/test/neutralize.sh and re-running scripts/shellcheck.sh; it failed with SC2148 on that file. The sabotage was reverted before commit.

Notes for reviewers

  • shellcheck is preinstalled on ubuntu-latest runners; no apt install or dev-dep is needed.
  • Action pin (actions/checkout@de0fac2e...) matches the project's existing zizmor-pedantic posture; workflow scope grants only contents: read; checkout runs with persist-credentials: false.
  • The workflow uses the project's standard concurrency block.

Comment thread scripts/shellcheck.sh Outdated
# can branch on emptiness. xargs is not used directly because BSD xargs
# (macOS) lacks `-r` and runs the command on an empty list, which would
# invoke `shellcheck` with no positional arguments and read from stdin.
files=$(git ls-files -z '*.sh' | tr '\0' '\n')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an oversize variable for bash. Consider using HASH=$(git hash-object -w --stdin) and git cat-file blob $HASH to ensure we do not run through the maximum command line argument length.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 46ba165 (fix(scripts): pass shellcheck targets via git blob to avoid argv limit). The file list now streams through git hash-object -w --stdin then git cat-file blob $HASH per your suggestion; the intermediate $files variable is gone. Local ./scripts/shellcheck.sh runs clean.

Comment thread .github/workflows/shellcheck.yml Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please verify that this is not duplicative with shellcheck work in the lint job. If so, please consolidate. This appears to be more effective than whatever’s latent.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified before pushing: grep -rni shellcheck .github/ package.json shows the new files added by this PR are the only shellcheck references in the repo. Root package.json defines "lint": "yarn lint:prettier && yarn lint:eslint" and the lint job in .github/workflows/ci.yml runs yarn lint, so neither invokes shellcheck. The new workflow is the only shellcheck gate in CI; no consolidation needed.

kriscendobot pushed a commit that referenced this pull request Jun 2, 2026
Per kriskowal review on PR #401, stream the
.sh file list through a git blob (`git hash-object -w --stdin` then
`git cat-file blob $HASH`) instead of stuffing the list into a shell
variable. This keeps us clear of ARG_MAX limits on hosts with many
tracked .sh files.
@kriscendobot

Copy link
Copy Markdown
Collaborator Author

Thanks for the review @kriskowal. Addressed both items:

  • scripts/shellcheck.sh:21 (#discussion_r3344750572): switched to git hash-object -w --stdin + git cat-file blob $HASH per your suggestion in 46ba165.
  • .github/workflows/shellcheck.yml:1 (#discussion_r3344754582): verified non-duplicative; yarn lint runs prettier + eslint only, no shellcheck step in ci.yml. The new workflow is the only shellcheck gate in CI.

Local ./scripts/shellcheck.sh runs clean. PR is still draft; not re-requesting review until the gauntlet completes.

@kriskowal

Copy link
Copy Markdown
Member

Please rebase on actual master and shepherd through CI.

@kriskowal

kriskowal commented Jun 8, 2026

Copy link
Copy Markdown
Member

Mirror of endojs/endo#3300 (head 32d76f46b).

Comment thread packages/nat/scripts/npm-audit-fix.sh Outdated
#!/bin/bash
git clone https://github.com/AgoricBot/nat.git
cd nat
cd nat || exit

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Early termination should exit non-zero and emit an error message to stderr, as with the die pattern from Perl. Please research the appropriate idiom and apply throughout.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in a7b9b9a ("chore(shellcheck): adopt die idiom for early termination in touched .sh files").

Adopted the canonical POSIX shell die shape, defined per-script near the top:

die() { printf '%s\n' "$*" >&2; exit 1; }

(printf rather than echo -e to keep behavior portable across /bin/sh and /bin/bash.) I checked the tree for an existing project helper (die/fail/error_exit); none exists, so each touched script defines its own.

Applied throughout the .sh files this PR touched at every early-termination site:

  • packages/nat/scripts/npm-audit-fix.sh:5cd nat || die "cd: nat failed"
  • packages/nat/scripts/npm-audit-fix.sh:30cd integration-test || die "cd: integration-test failed"
  • scripts/npm-audit-fix.sh:5cd ??? || die "cd: ??? failed"
  • scripts/posttypedoc.sh:5cd docs || die "cd: docs failed"
  • scripts/posttypedoc.sh:9cd api-docs || die "cd: api-docs failed"

scripts/check-packages.sh's terminal exit "$EXIT" and scripts/shellcheck.sh's exit 0 skip-when-empty are orderly success/aggregated-status exits rather than the silent-non-zero early termination the comment objects to, so they stay as-is. Let me know if you want either upgraded too.

yarn shellcheck still passes on the new tip.

kriscendobot pushed a commit that referenced this pull request Jun 9, 2026
…sh files

Address kriskowal's CHANGES_REQUESTED review on #401: replace bare
`|| exit` after `cd` with `|| die "<context>"` so early-termination
sites emit a context line to stderr and exit non-zero, as with Perl's
`die`. Each touched script defines its own helper near the top:

    die() { printf '%s\n' "$*" >&2; exit 1; }

`printf` (not `echo -e`) avoids portability traps under /bin/sh.

Sites upgraded across the .sh files this PR touched:
- packages/nat/scripts/npm-audit-fix.sh: `cd nat`, `cd integration-test`
- scripts/npm-audit-fix.sh:              `cd ???`
- scripts/posttypedoc.sh:                `cd docs`, `cd api-docs`

scripts/check-packages.sh's terminal `exit "$EXIT"` and scripts/
shellcheck.sh's `exit 0` skip-when-empty are intentional orderly
exits, not the early-termination pattern the review objects to.
Left as-is.

The "throughout" scope is the .sh files this PR touched (the seven
plus the new scripts/shellcheck.sh); broader cross-tree adoption is
left to a separate dispatch if the maintainer wants it.
@kriscendobot

Copy link
Copy Markdown
Collaborator Author

@kriskowal addressed in a7b9b9a ("chore(shellcheck): adopt die idiom for early termination in touched .sh files").

Adopted the canonical POSIX shell die shape, defined per-script near the top of each file:

die() { printf '%s\n' "$*" >&2; exit 1; }

printf (not echo -e) keeps behavior portable across /bin/sh and /bin/bash. No existing project helper (die/fail/error_exit) was present in the tree, so each touched script defines its own.

Replaced bare || exit with || die "<context>" at every early-termination site in the .sh files this PR touched:

File Sites
packages/nat/scripts/npm-audit-fix.sh cd nat, cd integration-test
scripts/npm-audit-fix.sh cd ???
scripts/posttypedoc.sh cd docs, cd api-docs

scripts/check-packages.sh's terminal exit "$EXIT" (propagating assert-loop status) and scripts/shellcheck.sh's exit 0 skip-when-empty (after announcing the skip on stdout) are orderly exits, not the silent-non-zero early termination the review objects to. Both left as-is; happy to upgrade either if you'd prefer.

yarn shellcheck still passes on the new tip locally.

endolinbot added 4 commits June 9, 2026 04:31
Address every finding shellcheck emits at -S warning over the
repository's tracked shell scripts. The changes are mechanical and
do not alter runtime behavior:

- Add missing `#!/bin/sh` / `#!/bin/bash` shebangs (SC2148).
- Append `|| exit` to bare `cd` invocations whose failure should
  abort (SC2164).
- Convert `for X in $(find ...)` to `while read; do ...; done < <(find ...)`
  so filenames with whitespace survive (SC2044).
- Convert `find ... | xargs` to `find ... -print0 | xargs -0` for
  the same reason (SC2038).
- Replace `CDPATH=` with `CDPATH=''` so the assignment is
  unambiguous (SC1007).
- Drop an unused `DIR=$(dirname ...)` assignment (SC2034).
- Add `-r` to `read` calls so backslashes round-trip through the
  pipe.

These prepare the tree for the `yarn shellcheck` gate that lands in
the same PR.
Adds a deterministic lint gate over every tracked .sh file in the
repository.

- `scripts/shellcheck.sh` enumerates `git ls-files '*.sh'` and runs
  `shellcheck -S warning` over the list, forwarding any extra args
  to the underlying checker. Exits 0 when the list is empty so
  forks or branches that have not yet adopted a shell script stay
  green.
- `yarn shellcheck` wires the wrapper into the root `package.json`
  scripts table.
- `.github/workflows/shellcheck.yml` runs the gate on push to
  master and on any pull_request that touches a `.sh` file (the
  workflow's `paths:` filter). A PR that touches no shell scripts
  does not trigger the workflow at all, matching the maintainer's
  "skip when no .sh in the diff from the base" requirement.

shellcheck ships preinstalled on `ubuntu-latest` runners so no
additional dependency or apt install is needed. Action pins follow
the project's existing zizmor-pedantic posture
(`actions/checkout@de0fac2e...`); the workflow scope grants
`contents: read` and the job runs with `persist-credentials: false`.
Per kriskowal review on PR #401, stream the
.sh file list through a git blob (`git hash-object -w --stdin` then
`git cat-file blob $HASH`) instead of stuffing the list into a shell
variable. This keeps us clear of ARG_MAX limits on hosts with many
tracked .sh files.
…sh files

Address kriskowal's CHANGES_REQUESTED review on #401: replace bare
`|| exit` after `cd` with `|| die "<context>"` so early-termination
sites emit a context line to stderr and exit non-zero, as with Perl's
`die`. Each touched script defines its own helper near the top:

    die() { printf '%s\n' "$*" >&2; exit 1; }

`printf` (not `echo -e`) avoids portability traps under /bin/sh.

Sites upgraded across the .sh files this PR touched:
- packages/nat/scripts/npm-audit-fix.sh: `cd nat`, `cd integration-test`
- scripts/npm-audit-fix.sh:              `cd ???`
- scripts/posttypedoc.sh:                `cd docs`, `cd api-docs`

scripts/check-packages.sh's terminal `exit "$EXIT"` and scripts/
shellcheck.sh's `exit 0` skip-when-empty are intentional orderly
exits, not the early-termination pattern the review objects to.
Left as-is.

The "throughout" scope is the .sh files this PR touched (the seven
plus the new scripts/shellcheck.sh); broader cross-tree adoption is
left to a separate dispatch if the maintainer wants it.
@kriscendobot kriscendobot force-pushed the chore/shellcheck-ci branch from 8fcb241 to 85ebc88 Compare June 9, 2026 04:32
@kriscendobot kriscendobot changed the base branch from master-814dfa1 to master-4a04d07 June 9, 2026 04:32
@kriscendobot

Copy link
Copy Markdown
Collaborator Author

Rebased onto fresh frozen base master-4a04d07 (was master-814dfa1) per shepherd 344723's escalation: the prior base predated upstream ba26f4cdb (PR #3294, fix(benchmark): install xs/v8 via direct download instead of esvu), which was the test-xs root cause. No conflicts; 4 commits replayed (the release.yml changesets/action version-comment fix from 8fcb24157 dropped as already-applied upstream via PR #75 — same one-character fix). Head: 85ebc883a (was 8fcb24157). Awaiting CI.

@kriskowal

Copy link
Copy Markdown
Member

@kriscendobot Please integrate the shellcheck step into the lint job instead of a fresh job.

Per kriskowal's directive on PR #401 (issuecomment-4676193609),
integrate the shellcheck step into the existing CI lint job instead
of running it from a separate workflow.

The standalone `Shell script lint` workflow used a `paths:` filter on
`**/*.sh` so PRs touching no shell scripts skipped it entirely. The
lint job in `ci.yml` has no such filter, so shellcheck now runs on
every PR. The wrapper `scripts/shellcheck.sh` already short-circuits
to exit 0 when no `.sh` files are tracked, so the cost on a `.sh`-free
diff is a fast no-op enumeration. Gate semantics are preserved: every
.sh-touching diff still hits shellcheck, and master pushes still hit
it unconditionally via the lint job's own master trigger.

- `.github/workflows/ci.yml`: add `Show shellcheck version` and
  `Run yarn shellcheck` steps to the lint job, between `yarn lint`
  and `yarn build:types:check`.
- `.github/workflows/shellcheck.yml`: deleted.
@kriscendobot

Copy link
Copy Markdown
Collaborator Author

Addressed in 2ee513b. Folded the shellcheck step into the lint job in ci.yml (between yarn lint and yarn build:types:check) and deleted .github/workflows/shellcheck.yml. Single commit.

Gate-semantics note: the standalone workflow had a paths: filter on **/*.sh that skipped PRs touching no shell scripts. The lint job has no such filter, so shellcheck now runs on every PR. The wrapper short-circuits to exit 0 when no .sh files are tracked, so the cost on a .sh-free diff is a fast no-op enumeration; master pushes still gate unconditionally.

@kriscendobot kriscendobot requested a review from kriskowal June 11, 2026 01:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants