[
  {
    "path": ".github/workflows/ci.yml",
    "content": "---\nname: Build\n\non:\n  push:\n    branches:\n      - master\n\n  pull_request:\n    branches:\n      - master\n\n  workflow_dispatch:\n\njobs:\n  lint:\n    name: lint\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: sh-checker\n        uses: ./. # Use the local action, at the current version\n        with:\n          sh_checker_comment: true\n          sh_checker_only_diff: true\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM alpine:3.18.4\nLABEL \"name\"=\"sh-checker\"\nLABEL \"maintainer\"=\"Luiz Muller <contact@luizm.dev>\"\n\nARG shfmt_version=3.12.0\nARG shellcheck_version=0.11.0\nARG gh_version=2.37.0\n\nRUN apk add --no-cache bash git jq curl checkbashisms xz \\\n    && apk add --no-cache --virtual .build-deps tar \\\n    && wget \"https://github.com/mvdan/sh/releases/download/v${shfmt_version}/shfmt_v${shfmt_version}_linux_amd64\" -O /usr/local/bin/shfmt \\\n    && chmod +x /usr/local/bin/shfmt \\\n    && wget \"https://github.com/koalaman/shellcheck/releases/download/v${shellcheck_version}/shellcheck-v${shellcheck_version}.linux.x86_64.tar.xz\"  -O- | tar xJ -C /usr/local/bin/ --strip-components=1 --wildcards '*/shellcheck' \\\n    && chmod +x /usr/local/bin/shellcheck \\\n    && curl -L https://github.com/cli/cli/releases/download/v${gh_version}/gh_${gh_version}_linux_amd64.tar.gz | tar xz -C /usr/local/ --strip-components=1 \\\n    && apk del --no-cache .build-deps \\\n    && rm -rf /tmp/*\n\n# https://github.com/actions/runner-images/issues/6775#issuecomment-1410270956\nRUN git config --system --add safe.directory /github/workspace\n\nCOPY entrypoint.sh /entrypoint.sh\nRUN chmod +x /entrypoint.sh\n\nENTRYPOINT [\"/entrypoint.sh\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Luiz Muller\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# sh-checker\n\n[![Build](https://github.com/luizm/action-sh-checker/actions/workflows/ci.yml/badge.svg)](https://github.com/luizm/action-sh-checker/actions/workflows/ci.yml)\n\nA [GitHub action](https://docs.github.com/en/free-pro-team@latest/actions) that performs static analysis of shell scripts using [shellcheck](https://github.com/koalaman/shellcheck), [shfmt](https://github.com/mvdan/sh) and [checkbashisms](https://linux.die.net/man/1/checkbashisms)\n\n![Screen Shot 2020-04-01 at 12 18 59](https://user-images.githubusercontent.com/6004689/78155536-f9a8a080-7413-11ea-8b5c-2c96484feb61.png)\n\n## Usage\n\nJob example to check all sh files but ignore the directory `.terraform` and file `dir/example.sh`\n\n```yaml\nname: example\non:\n  - pull_request\njobs:\n  sh-checker:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - name: Run the sh-checker\n        uses: luizm/action-sh-checker@master\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          SHELLCHECK_OPTS: -e SC1004 # exclude some shellcheck warnings.\n          SHFMT_OPTS: -s # arguments to shfmt.\n        with:\n          sh_checker_comment: true\n          sh_checker_exclude: \".terraform ^dir/example.sh\"\n```\n\n### Environment Variables\n\n`SHELLCHECK_OPTS`: Used to specify shellcheck arguments.\n\n`SHFMT_OPTS`: Used to specify shfmt arguments.\n\n### Inputs\n\n`sh_checker_only_diff`: (optional) Only check the files that were changed in the pull request. Default is to check all files in the repo.\n\n`sh_checker_exclude`: (optional) Directory or file name that doesn't need to be checked.\n\n`sh_checker_comment`: (optional) If true, it will show the errors as commentaries in the pull requests. Default is false.\n\n`sh_checker_shfmt_disable`: (optional) If true, it will skip shfmt. Default is false.\n\n`sh_checker_shellcheck_disable`: (optional) If true, it will skip shellcheck. Default is false.\n\n`sh_checker_checkbashisms_enable`: (optional) If true, run checkbashisms tool against scripts. Default is false.\n\n### Secrets\n\n`GITHUB_TOKEN`: The GitHub API token used to post comments to pull requests. Required only if `sh_checker_only_diff` or `sh_checker_comment` is set to true.\n"
  },
  {
    "path": "action.yml",
    "content": "name: \"sh-checker\"\ndescription: \"Run shellcheck and/or shfmt on all shell script files and include commentaries, there is a exclude parameter\"\nauthor: \"luizm\"\nbranding:\n  icon: \"terminal\"\n  color: \"black\"\ninputs:\n  sh_checker_exclude:\n    description: \"Directory or file name that doesn't need to be checked\"\n    required: false\n  sh_checker_shfmt_disable:\n    description: \"If true, it will skip the shfmt. Default is false\"\n    required: false\n  sh_checker_shellcheck_disable:\n    description: \"If true, it will skip the shellcheck. Default is false\"\n    required: false\n  sh_checker_checkbashisms_enable:\n    description: \"If true, run checkbashisms tool against scripts. Default is false\"\n    required: false\n    default: \"false\"\n  sh_checker_comment:\n    description: \"If true, it will show the errors as commentaries in the pull requests. Default is false\"\n    required: false\n  sh_checker_only_diff:\n    description: \"If true, run only on files changed in the PR branch. Default is false\"\n    required: false\nruns:\n  using: \"docker\"\n  image: \"Dockerfile\"\n"
  },
  {
    "path": "entrypoint.sh",
    "content": "#!/usr/bin/env bash\n\ncd \"$GITHUB_WORKSPACE\" || {\n\tprintf 'Directory not found: \"%s\"\\n' \"$GITHUB_WORKSPACE\"\n\texit 1\n}\n\nSHELLCHECK_DISABLE=0\nSHFMT_DISABLE=0\nSH_CHECKER_COMMENT=0\nCHECKBASHISMS_ENABLE=0\nSH_CHECKER_ONLY_DIFF=0\n\nshopt -s nocasematch\n\nif [[ \"${INPUT_SH_CHECKER_SHELLCHECK_DISABLE}\" =~ ^(1|true|on|yes)$ ]]; then\n\tSHELLCHECK_DISABLE=1\nfi\n\nif [[ \"${INPUT_SH_CHECKER_SHFMT_DISABLE}\" =~ ^(1|true|on|yes)$ ]]; then\n\tSHFMT_DISABLE=1\nfi\n\nif [[ \"${INPUT_SH_CHECKER_COMMENT}\" =~ ^(1|true|on|yes)$ ]]; then\n\tSH_CHECKER_COMMENT=1\nfi\n\nif [[ \"${INPUT_SH_CHECKER_CHECKBASHISMS_ENABLE}\" =~ ^(1|true|on|yes)$ ]]; then\n\tCHECKBASHISMS_ENABLE=1\nfi\n\nif [[ \"${INPUT_SH_CHECKER_ONLY_DIFF}\" =~ ^(1|true|on|yes)$ ]]; then\n\tSH_CHECKER_ONLY_DIFF=1\nfi\n\nif ((SHELLCHECK_DISABLE == 1 && SHFMT_DISABLE == 1 && CHECKBASHISMS_ENABLE != 1)); then\n\techo \"All checks are disabled: \\`sh_checker_shellcheck_disable\\` and \\`sh_checker_shfmt_disable\\` are both set to 1/true.\"\nfi\n\n# Internal functions\n_show_sh_files() {\n\t# Store the array of files to check in sh_files\n\t# using a global, as returning arrays in bash is ugly\n\t# setting IFS to \\n allows for spaces in file names:\n\tif ((SH_CHECKER_ONLY_DIFF == 1)); then\n\t\t# Compute the intersection of all shell scripts in the repo and files changes on the PR branch\n\t\tif [[ \"$GITHUB_REF\" =~ ^refs/pull/ ]]; then\n\t\t\t# The `on: pull_request` trigger does not give branch information, so we need to supply the PR number to gh\n\t\t\t# See https://frontside.com/blog/2020-05-26-github-actions-pull_request/\n\t\t\t# and https://docs.github.com/en/actions/learn-github-actions/variables\n\t\t\tpr_number=\"$(echo \"$GITHUB_REF\" | cut -d/ -f3)\"\n\t\telse\n\t\t\tpr_number=\"\" # have gh figure out the PR number from the branch name\n\t\tfi\n\t\tIFS=$'\\n' mapfile -t sh_files < <(sort <(shfmt -f .) <(gh pr diff \"$pr_number\" --name-only) | uniq -d)\n\t\techo \"Checking only the shell script(s) changed in the PR branch:\"\n\t\tprintf '\"%s\"\\n' \"${sh_files[@]}\"\n\telse\n\t\tIFS=$'\\n' mapfile -t sh_files < <(shfmt -f .)\n\tfi\n\n\tif [ -z \"$INPUT_SH_CHECKER_EXCLUDE\" ]; then\n\t\treturn 0\n\tfi\n\n\tOLDIFS=\"$IFS\"\n\tIFS=$' \\t\\n' read -d '' -ra excludes <<<\"$INPUT_SH_CHECKER_EXCLUDE\"\n\tIFS=$'\\n'\n\tsh_all=(\"${sh_files[@]}\")\n\tsh_files=()\n\texcluded=()\n\tlocal sh exclude\n\tfor sh in \"${sh_all[@]}\"; do\n\t\tfor exclude in \"${excludes[@]}\"; do\n\t\t\tgrep -q -E \"$exclude\" <<<\"$sh\" || continue\n\t\t\texcluded+=(\"$sh\")\n\t\t\tcontinue 2\n\t\tdone\n\t\tsh_files+=(\"$sh\")\n\tdone\n\tif ((\"${#excluded[@]}\" != 0)); then\n\t\tprintf 'The following %d shell script(s) will not be checked:\\n' \"${#excluded[@]}\"\n\t\tprintf '\"%s\"\\n' \"${excluded[@]}\"\n\tfi\n\tIFS=\"$OLDIFS\"\n}\n\n_comment_on_github() {\n\tlocal content\n\tIFS= read -r -d '' content <<EOF\n#### \\`sh-checker report\\`\n\nTo get the full details, please check in the [job](\"https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID\") output.\n\n<details>\n<summary>shellcheck errors</summary>\n\n\\`\\`\\`\n$1\n\\`\\`\\`\n</details>\n\n<details>\n<summary>shfmt errors</summary>\n\n\\`\\`\\`\n$2\n\\`\\`\\`\n</details>\n\nEOF\n\tlocal -r payload=$(jq -R --slurp '{body: .}' <<<\"$content\")\n\tlocal -r comment_url=$(jq -r .pull_request.comments_url <\"$GITHUB_EVENT_PATH\")\n\n\techo \"Commenting on the pull request\"\n\tcurl -s -S -H \"Authorization: token $GITHUB_TOKEN\" --header \"Content-Type: application/json\" --data @- \"$comment_url\" <<<\"$payload\"\n}\n\n_show_sh_files\n\n((${#sh_files[@]} == 0)) && {\n\tif ((SH_CHECKER_ONLY_DIFF == 1)); then\n\t\techo \"No shell scripts were changed.\"\n\t\texit 0\n\tfi\n\tif [ -n \"$INPUT_SH_CHECKER_EXCLUDE\" ]; then\n\t\tif ((${#excluded[@]} == ${#sh_all[@]})); then\n\t\t\tprintf 'All %d shell script(s) have been excluded per your sh_checker_exclude setting:\\n' \"${#sh_all[@]}\"\n\t\t\tIFS=$' \\t\\n' printf '\"%s\"\\n' \"${excludes[@]}\"\n\t\t\texit 0\n\t\tfi\n\tfi\n\techo \"No shell scripts were found in this repository. Please check your settings.\"\n\texit 0\n}\n\n# Validate sh files\nshellcheck_code=0\ncheckbashisms_code=0\nshfmt_code=0\nexit_code=0\nshellcheck_error='shellcheck checking is disabled.'\nshfmt_error='shfmt checking is disabled.'\n\nif ((SHELLCHECK_DISABLE != 1)); then\n\tprintf \"Validating %d shell script(s) using 'shellcheck %s':\\\\n\" \"${#sh_files[@]}\" \"$SHELLCHECK_OPTS\"\n\tIFS=$' \\t\\n' read -d '' -ra args <<<\"$SHELLCHECK_OPTS\"\n\tshellcheck_output=\"$(shellcheck \"${args[@]}\" \"${sh_files[@]}\" 2>&1)\"\n\tshellcheck_code=$?\n\tif ((shellcheck_code == 0)); then\n\t\tprintf -v shellcheck_error \"'shellcheck %s' found no issues.\\\\n\" \"$SHELLCHECK_OPTS\"\n\telse\n\t\t# .shellcheck returns 0-4: https://github.com/koalaman/shellcheck/blob/dff8f9492a153b4ad8ac7d085136ce532e8ea081/shellcheck.hs#L191\n\t\texit_code=$shellcheck_code\n\t\tIFS= read -r -d '' shellcheck_error <<EOF\n\n'shellcheck $SHELLCHECK_OPTS' returned error $shellcheck_code finding the following syntactical issues:\n\n----------\n$shellcheck_output\n----------\n\nYou can address the above issues in one of three ways:\n1. Manually correct the issue in the offending shell script;\n2. Disable specific issues by adding the comment:\n  # shellcheck disable=NNNN\nabove the line that contains the issue, where NNNN is the error code;\n3. Add '-e NNNN' to the SHELLCHECK_OPTS setting in your .yml action file.\n\n\nEOF\n\tfi\n\tprintf '%s' \"$shellcheck_error\"\nfi\n\nif ((SHFMT_DISABLE != 1)); then\n\tprintf \"Validating %d shell script(s) using 'shfmt %s':\\\\n\" \"${#sh_files[@]}\" \"$SHFMT_OPTS\"\n\tIFS=$' \\t\\n' read -d '' -ra args <<<\"$SHFMT_OPTS\"\n\t# Error with a diff when the formatting differs\n\targs+=('-d')\n\t# Disable colorization of diff output\n\texport NO_COLOR=1\n\tshfmt_output=\"$(shfmt \"${args[@]}\" \"${sh_files[@]}\" 2>&1)\"\n\tshfmt_code=$?\n\tif ((shfmt_code == 0)); then\n\t\tprintf -v shfmt_error \"'shfmt %s' found no issues.\\\\n\" \"$SHFMT_OPTS\"\n\telse\n\t\t# shfmt returns 0 or 1: https://github.com/mvdan/sh/blob/dbbad59b44d586c0f3d044a3820c18c41b495e2a/cmd/shfmt/main.go#L72\n\t\t((exit_code |= 8))\n\t\tIFS= read -r -d '' shfmt_error <<EOF\n\n'shfmt $SHFMT_OPTS' returned error $shfmt_code finding the following formatting issues:\n\n----------\n$shfmt_output\n----------\n\nYou can reformat the above files to meet shfmt's requirements by typing:\n\n  shfmt $SHFMT_OPTS -w filename\n\nEOF\n\tfi\n\tprintf '%s' \"$shfmt_error\"\nfi\n\nif ((CHECKBASHISMS_ENABLE == 1)); then\n\tprintf '\\n\\nValidating %d shell script(s) files using checkbashisms:\\n' \"${#sh_files[@]}\"\n\tcheckbashisms \"${sh_files[@]}\"\n\tcheckbashisms_code=$?\n\tif ((checkbashisms_code == 0)); then\n\t\tprintf '\\ncheckbashisms found no issues.\\n'\n\telse\n\t\tprintf '\\ncheckbashisms returned error %d finding the bashisms listed above.\\n' \"$checkbashisms_code\"\n\t\tif ((checkbashisms_code == 4)); then\n\t\t\t# see https://github.com/duggan/shlint/blob/0fcd979319e3f37c2cd53ccea0b51e16fda710a1/lib/checkbashisms#L489\n\t\t\tprintf \"\\\\nIgnoring the spurious non-issue titled 'could not find any possible bashisms in bash script'\\\\n\"\n\t\telse\n\t\t\t# checkbashisms returns 0-3: https://linux.die.net/man/1/checkbashisms\n\t\t\t((exit_code |= (checkbashisms_code << 4)))\n\t\tfi\n\tfi\nfi\n\nif ((shellcheck_code != 0 || shfmt_code != 0)); then\n\tif [[ \"$GITHUB_EVENT_NAME\" == \"pull_request\" || \"$GITHUB_EVENT_NAME\" == \"pull_request_target\" ]] && ((SH_CHECKER_COMMENT == 1)); then\n\t\t_comment_on_github \"$shellcheck_error\" \"$shfmt_error\"\n\tfi\nfi\n\nif ((exit_code == 0)); then\n\tprintf '\\nNo issues found in the %d shell script(s) scanned :)\\n' \"${#sh_files[@]}\"\nfi\n\n# shellcheck disable=SC2086\nexit $exit_code\n"
  },
  {
    "path": "upgrade.sh",
    "content": "#!/usr/bin/env bash\n# shellcheck disable=SC2312 # (info): Consider invoking this command separately to avoid masking its return value (or use '|| true' to ignore).\n\nset -e\n\nif command -v curl >/dev/null; then\n  # -sq == --silent --disable\n  dl=\"curl -sq\"\nelse\n  # -q0 == --quiet --output-document\n  dl='wget --no-config -qO -'\nfi\n\nALPINE_VER=$(${dl} 'https://registry.hub.docker.com/v2/repositories/library/alpine/tags/' | jq -r '.results[1].name')\nGH_VER=$(${dl} https://api.github.com/repos/cli/cli/tags | jq -r '.[0].name')\nSHELLCHECK_VER=$(${dl} https://api.github.com/repos/koalaman/shellcheck/tags | jq -r '.[0].name')\nSHFMT_VER=$(${dl} https://api.github.com/repos/mvdan/sh/tags | jq -r '.[0].name')\n\nGH_VER=\"${GH_VER/v/}\"\nSHELLCHECK_VER=\"${SHELLCHECK_VER/v/}\"\nSHFMT_VER=\"${SHFMT_VER/v/}\"\n\nsed -Ei \"\ns/^(FROM\\s+alpine:).*/\\1${ALPINE_VER}/;\ns/^(ARG\\s+gh_version=).*/\\1${GH_VER}/;\ns/^(ARG\\s+shellcheck_version=).*/\\1${SHELLCHECK_VER}/;\ns/^(ARG\\s+shfmt_version=).*/\\1${SHFMT_VER}/;\n\" Dockerfile\n\ngit diff\n"
  }
]