diff --git a/.github/release.yaml b/.github/release.yaml new file mode 100644 index 0000000..5f89818 --- /dev/null +++ b/.github/release.yaml @@ -0,0 +1,5 @@ +changelog: + exclude: + authors: + - dependabot[bot] + - pre-commit-ci[bot] diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 961d020..b952061 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,21 +1,8 @@ repos: - repo: https://github.com/codespell-project/codespell - rev: v2.4.0 + rev: v2.4.1 hooks: - id: codespell args: [--ignore-words-list, "abandonned,ackward,bu"] additional_dependencies: - tomli - - repo: https://github.com/gitleaks/gitleaks - rev: v8.16.3 - hooks: - - id: gitleaks - - repo: https://github.com/jumanjihouse/pre-commit-hooks - rev: 3.0.0 - hooks: - - id: shellcheck - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 - hooks: - - id: end-of-file-fixer - - id: trailing-whitespace diff --git a/Dockerfile b/Dockerfile index de2057c..823a198 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,4 @@ -FROM python:3.14-alpine3.23@sha256:faee120f7885a06fcc9677922331391fa690d911c020abb9e8025ff3d908e510 - -RUN apk add --no-cache curl jq && apk upgrade --no-cache zlib +FROM python:3.13-alpine COPY LICENSE \ README.md \ @@ -9,7 +7,7 @@ COPY LICENSE \ requirements.txt \ /code/ -RUN pip install -r /code/requirements.txt +RUN pip install --no-cache-dir -r /code/requirements.txt ENTRYPOINT ["/code/entrypoint.sh"] CMD [] diff --git a/README.md b/README.md index f72b4ff..7e64276 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -[![StepSecurity Maintained Action](https://raw.githubusercontent.com/step-security/maintained-actions-assets/main/assets/maintained-action-banner.png)](https://docs.stepsecurity.io/actions/stepsecurity-maintained-actions) - # Codespell with GitHub Actions -- including annotations for Pull Requests This GitHub Actions runs codespell over your code. @@ -8,7 +6,8 @@ Any warnings or errors will be annotated in the Pull Request. ## Usage ```yml -uses: step-security/actions-codespell@v2 +- uses: actions/checkout@v5 +- uses: codespell-project/actions-codespell@v2 ``` ### Parameter: check_filenames @@ -18,7 +17,7 @@ If set, check file names for spelling mistakes as well. This parameter is optional; by default `codespell` will only check the file contents. ```yml -uses: step-security/actions-codespell@v2 +uses: codespell-project/actions-codespell@v2 with: check_filenames: true ``` @@ -30,7 +29,7 @@ If set, check hidden files (those starting with ".") for spelling mistakes as we This parameter is optional; by default `codespell` will not check hidden files. ```yml -uses: step-security/actions-codespell@v2 +uses: codespell-project/actions-codespell@v2 with: check_hidden: true ``` @@ -42,7 +41,7 @@ File with lines that should not be checked for spelling mistakes. This parameter is optional; by default `codespell` will check all lines. ```yml -uses: step-security/actions-codespell@v2 +uses: codespell-project/actions-codespell@v2 with: exclude_file: src/foo ``` @@ -54,7 +53,7 @@ Comma-separated list of files to skip (it accepts globs as well). This parameter is optional; by default `codespell` won't skip any files. ```yml -uses: step-security/actions-codespell@v2 +uses: codespell-project/actions-codespell@v2 with: skip: foo,bar ``` @@ -66,7 +65,7 @@ Comma-separated list of builtin dictionaries to use. This parameter is optional; by default `codespell` will use its default selection of built in dictionaries. ```yml -uses: step-security/actions-codespell@v2 +uses: codespell-project/actions-codespell@v2 with: builtin: clear,rare ``` @@ -79,7 +78,7 @@ Words are case sensitive based on how they are written in the dictionary file. This parameter is optional; by default `codespell` will check all words for typos. ```yml -uses: step-security/actions-codespell@v2 +uses: codespell-project/actions-codespell@v2 with: ignore_words_file: .codespellignore ``` @@ -92,7 +91,7 @@ Words are case sensitive based on how they are written in the dictionary file. This parameter is optional; by default `codespell` will check all words for typos. ```yml -uses: step-security/actions-codespell@v2 +uses: codespell-project/actions-codespell@v2 with: ignore_words_list: abandonned,ackward ``` @@ -106,7 +105,7 @@ If set to "*", all misspelling in URIs and emails will be ignored. This parameter is optional; by default `codespell` will check all URIs and emails for typos. ```yml -uses: step-security/actions-codespell@v2 +uses: codespell-project/actions-codespell@v2 with: uri_ignore_words_list: abandonned ``` @@ -119,7 +118,7 @@ This can be useful if your project has code you don't want to spell check for so This parameter is optional; by default `codespell` will run on your whole repository. ```yml -uses: step-security/actions-codespell@v2 +uses: codespell-project/actions-codespell@v2 with: path: src ``` @@ -133,7 +132,7 @@ All errors and warnings are annotated in Pull Requests, but it will act like eve This parameter is optional; setting this to any value will enable it. ```yml -uses: step-security/actions-codespell@v2 +uses: codespell-project/actions-codespell@v2 with: only_warn: 1 ``` diff --git a/action.yml b/action.yml index 8ccafb0..fdd369e 100644 --- a/action.yml +++ b/action.yml @@ -1,7 +1,11 @@ name: 'Codespell with annotations' -author: 'step-security' +author: 'Peter Newman' description: 'Codespell with annotations for Pull Request' inputs: + builtin: + description: 'Comma-separated list of builtin dictionaries to include' + required: false + default: '' check_filenames: description: 'If set, check file names as well' required: false @@ -10,16 +14,12 @@ inputs: description: 'If set, check hidden files (those starting with ".") as well' required: false default: '' - exclude_file: - description: 'File with lines that should not be checked for spelling mistakes' + config: + description: 'Path to a codespell config file' required: false default: '' - skip: - description: 'Comma-separated list of files to skip (it accepts globs as well)' - required: false - default: './.git' - builtin: - description: 'Comma-separated list of builtin dictionaries to include' + exclude_file: + description: 'File with lines that should not be checked for spelling mistakes' required: false default: '' ignore_words_file: @@ -38,6 +38,10 @@ inputs: description: 'Path to run codespell in' required: false default: '' + skip: + description: 'Comma-separated list of files to skip (it accepts globs as well)' + required: false + default: './.git' only_warn: description: 'If set, only warn, never error' required: false diff --git a/entrypoint.sh b/entrypoint.sh index 598d671..10b5a43 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,49 +1,7 @@ #!/bin/sh -REPO_PRIVATE=$(jq -r '.repository.private | tostring' "$GITHUB_EVENT_PATH" 2>/dev/null || echo "") -UPSTREAM="codespell-project/actions-codespell" -ACTION_REPO="${GITHUB_ACTION_REPOSITORY:-}" -DOCS_URL="https://docs.stepsecurity.io/actions/stepsecurity-maintained-actions" - -echo "" -echo -e "\033[1;36mStepSecurity Maintained Action\033[0m" -echo "Secure drop-in replacement for $UPSTREAM" -if [ "$REPO_PRIVATE" = "false" ]; then - echo -e "\033[32m✓ Free for public repositories\033[0m" -fi -echo -e "\033[36mLearn more:\033[0m $DOCS_URL" -echo "" - -if [ "$REPO_PRIVATE" != "false" ]; then - SERVER_URL="${GITHUB_SERVER_URL:-https://github.com}" - - if [ "$SERVER_URL" != "https://github.com" ]; then - BODY=$(printf '{"action":"%s","ghes_server":"%s"}' "$ACTION_REPO" "$SERVER_URL") - else - BODY=$(printf '{"action":"%s"}' "$ACTION_REPO") - fi - - API_URL="https://agent.api.stepsecurity.io/v1/github/$GITHUB_REPOSITORY/actions/maintained-actions-subscription" - - RESPONSE=$(curl --max-time 3 -s -w "%{http_code}" \ - -X POST \ - -H "Content-Type: application/json" \ - -d "$BODY" \ - "$API_URL" -o /dev/null) && CURL_EXIT_CODE=0 || CURL_EXIT_CODE=$? - - if [ $CURL_EXIT_CODE -ne 0 ]; then - echo "Timeout or API not reachable. Continuing to next step." - elif [ "$RESPONSE" = "403" ]; then - echo -e "::error::\033[1;31mThis action requires a StepSecurity subscription for private repositories.\033[0m" - echo -e "::error::\033[31mLearn how to enable a subscription: $DOCS_URL\033[0m" - exit 1 - fi -fi - -# Copy the matcher directly to RUNNER_TEMP; the /github/workflow bind-mount is not reliable. -CODE_DIR="${CODE_DIR:-/code}" -mkdir -p "${RUNNER_TEMP}/_github_workflow" -cp "${CODE_DIR}/codespell-matcher.json" "${RUNNER_TEMP}/_github_workflow/codespell-matcher.json" +# Copy the matcher to the host system; otherwise "add-matcher" can't find it. +cp /code/codespell-matcher.json /github/workflow/codespell-matcher.json echo "::add-matcher::${RUNNER_TEMP}/_github_workflow/codespell-matcher.json" # Run codespell. @@ -52,6 +10,10 @@ echo "::add-matcher::${RUNNER_TEMP}/_github_workflow/codespell-matcher.json" # e.g. PIPESTATUS and pipestatus only work in bash/zsh respectively. echo "Running codespell on '${INPUT_PATH}' with the following options..." command_args="" +echo "Builtin dictionaries '${INPUT_BUILTIN}'" +if [ "x${INPUT_BUILTIN}" != "x" ]; then + command_args="${command_args} --builtin ${INPUT_BUILTIN}" +fi echo "Check filenames? '${INPUT_CHECK_FILENAMES}'" if [ -n "${INPUT_CHECK_FILENAMES}" ]; then echo "Checking filenames" @@ -62,18 +24,14 @@ if [ -n "${INPUT_CHECK_HIDDEN}" ]; then echo "Checking hidden" command_args="${command_args} --check-hidden" fi +echo "Config '${INPUT_CONFIG}'" +if [ "x${INPUT_CONFIG}" != "x" ]; then + command_args="${command_args} --config ${INPUT_CONFIG}" +fi echo "Exclude file '${INPUT_EXCLUDE_FILE}'" if [ "x${INPUT_EXCLUDE_FILE}" != "x" ]; then command_args="${command_args} --exclude-file ${INPUT_EXCLUDE_FILE}" fi -echo "Skipping '${INPUT_SKIP}'" -if [ "x${INPUT_SKIP}" != "x" ]; then - command_args="${command_args} --skip ${INPUT_SKIP}" -fi -echo "Builtin dictionaries '${INPUT_BUILTIN}'" -if [ "x${INPUT_BUILTIN}" != "x" ]; then - command_args="${command_args} --builtin ${INPUT_BUILTIN}" -fi echo "Ignore words file '${INPUT_IGNORE_WORDS_FILE}'" if [ "x${INPUT_IGNORE_WORDS_FILE}" != "x" ]; then command_args="${command_args} --ignore-words ${INPUT_IGNORE_WORDS_FILE}" @@ -86,6 +44,10 @@ echo "Ignore URI words list '${INPUT_URI_IGNORE_WORDS_LIST}'" if [ "x${INPUT_URI_IGNORE_WORDS_LIST}" != "x" ]; then command_args="${command_args} --uri-ignore-words-list ${INPUT_URI_IGNORE_WORDS_LIST}" fi +echo "Skipping '${INPUT_SKIP}'" +if [ "x${INPUT_SKIP}" != "x" ]; then + command_args="${command_args} --skip ${INPUT_SKIP}" +fi echo "Resulting CLI options ${command_args}" exec 5>&1 res=`{ { codespell --count ${command_args} ${INPUT_PATH}; echo $? 1>&4; } 1>&5; } 4>&1` diff --git a/test/test.bats b/test/test.bats index 560a756..80536a4 100644 --- a/test/test.bats +++ b/test/test.bats @@ -29,24 +29,17 @@ function setup() { [ -d "/code/" ] || sudo mkdir -p /code/ [ -f "/code/codespell-matcher.json" ] || sudo cp codespell-problem-matcher/codespell-matcher.json /code/ #ls -alR /code/ - # Create the _github_workflow dir that entrypoint.sh copies the matcher into - [ -d "${RUNNER_TEMP}/_github_workflow/" ] || sudo mkdir -p ${RUNNER_TEMP}/_github_workflow/ && sudo chmod 777 ${RUNNER_TEMP}/_github_workflow/ # Add a random place BATS tries to put it [ -d "/github/workflow/" ] || sudo mkdir -p /github/workflow/ && sudo chmod 777 /github/workflow/ #ls -alR /github/workflow/ - # Set GITHUB_EVENT_PATH to a fake public-repo event so REPO_PRIVATE=false, - # which skips the subscription check and keeps banner output deterministic. - local event_file="/tmp/test-event.json" - printf '{"repository":{"private":false}}' > "${event_file}" - export GITHUB_EVENT_PATH="${event_file}" - # Set default input values export INPUT_CHECK_FILENAMES="" export INPUT_CHECK_HIDDEN="" export INPUT_EXCLUDE_FILE="" export INPUT_SKIP="" export INPUT_BUILTIN="" + export INPUT_CONFIG="" export INPUT_IGNORE_WORDS_FILE="" export INPUT_IGNORE_WORDS_LIST="" export INPUT_URI_IGNORE_WORDS_LIST="" @@ -62,9 +55,9 @@ function setup() { [ $status -eq $expectedExitStatus ] # Check output - [[ "${output}" == *"::add-matcher::${RUNNER_TEMP}/_github_workflow/codespell-matcher.json"* ]] - outputRegex="Running codespell on '${INPUT_PATH}'" - [[ "${output}" =~ $outputRegex ]] + [ "${lines[0]}" == "::add-matcher::${RUNNER_TEMP}/_github_workflow/codespell-matcher.json" ] + outputRegex="^Running codespell on '${INPUT_PATH}'" + [[ "${lines[1]}" =~ $outputRegex ]] [ "${lines[-4 - $errorCount]}" == "$errorCount" ] [ "${lines[-3]}" == "Codespell found one or more problems" ] [ "${lines[-2]}" == "::remove-matcher owner=codespell-matcher-default::" ] @@ -102,6 +95,50 @@ function setup() { [ "${lines[-4 - $errorCount]}" == "$errorCount" ] } +@test "Pass an ill-formed file to INPUT_CONFIG" { + # codespell's exit status is 78 for a configparser.Error exception + expectedExitStatus=78 + INPUT_CONFIG="./test/testdata/.badcfg" + run "./entrypoint.sh" + [ $status -eq $expectedExitStatus ] +} + +@test "Pass a non-existing file to INPUT_CONFIG" { + errorCount=$((ROOT_MISSPELLING_COUNT + SUBFOLDER_MISSPELLING_COUNT)) + # codespell's exit status is 0, or 65 if there are errors found + if [ $errorCount -eq 0 ]; then expectedExitStatus=0; else expectedExitStatus=65; fi + INPUT_CONFIG="./foo" + run "./entrypoint.sh" + [ $status -eq $expectedExitStatus ] + + # Check output + [ "${lines[0]}" == "::add-matcher::${RUNNER_TEMP}/_github_workflow/codespell-matcher.json" ] + outputRegex="^Running codespell on '${INPUT_PATH}'" + [[ "${lines[1]}" =~ $outputRegex ]] + [ "${lines[-4 - $errorCount]}" == "$errorCount" ] + [ "${lines[-3]}" == "Codespell found one or more problems" ] + [ "${lines[-2]}" == "::remove-matcher owner=codespell-matcher-default::" ] + [ "${lines[-1]}" == "::remove-matcher owner=codespell-matcher-specified::" ] +} + +@test "Pass a valid file to INPUT_CONFIG" { + errorCount=$((ROOT_MISSPELLING_COUNT + SUBFOLDER_MISSPELLING_COUNT)) + # codespell's exit status is 0, or 65 if there are errors found + if [ $errorCount -eq 0 ]; then expectedExitStatus=0; else expectedExitStatus=65; fi + INPUT_CONFIG="./test/testdata/.goodcfg" + run "./entrypoint.sh" + [ $status -eq $expectedExitStatus ] + + # Check output + [ "${lines[0]}" == "::add-matcher::${RUNNER_TEMP}/_github_workflow/codespell-matcher.json" ] + outputRegex="^Running codespell on '${INPUT_PATH}'" + [[ "${lines[1]}" =~ $outputRegex ]] + [ "${lines[-4 - $errorCount]}" == "$errorCount" ] + [ "${lines[-3]}" == "Codespell found one or more problems" ] + [ "${lines[-2]}" == "::remove-matcher owner=codespell-matcher-default::" ] + [ "${lines[-1]}" == "::remove-matcher owner=codespell-matcher-specified::" ] +} + @test "Use an exclude file" { errorCount=$((ROOT_MISSPELLING_COUNT + SUBFOLDER_MISSPELLING_COUNT - EXCLUDED_MISSPELLING_COUNT)) # codespell's exit status is 0, or 65 if there are errors found diff --git a/test/testdata/.badcfg b/test/testdata/.badcfg new file mode 100644 index 0000000..e810594 --- /dev/null +++ b/test/testdata/.badcfg @@ -0,0 +1 @@ +foobar = diff --git a/test/testdata/.goodcfg b/test/testdata/.goodcfg new file mode 100644 index 0000000..60bbe06 --- /dev/null +++ b/test/testdata/.goodcfg @@ -0,0 +1 @@ +[codespell]