Skip to content

Setup CI/CD pipeline: PHPCS, PHPUnit, and Dependabot - #1

Closed
raphaelsuzuki wants to merge 2 commits into
mainfrom
feature/setup-ci-cd
Closed

raphaelsuzuki wants to merge 2 commits into
mainfrom
feature/setup-ci-cd

Conversation

@raphaelsuzuki

@raphaelsuzuki raphaelsuzuki commented Feb 18, 2026 •

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • Chores

    • Established automated testing and code quality verification framework
    • Enabled automated dependency updates through Dependabot
    • Configured development infrastructure and test environment
  • Documentation

    • Updated README with CI/build status badges
    • Added detailed contribution guidelines for project developers

@coderabbitai

coderabbitai Bot commented Feb 18, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR establishes development and testing infrastructure for a WordPress plugin by adding Dependabot configuration for automated dependency management, GitHub Actions workflows for code quality checks and testing, Composer configuration with development dependencies, testing framework setup with PHPUnit configuration, and comprehensive documentation for contributors.

Changes

Cohort / File(s) Summary
GitHub Actions Workflows
.github/workflows/ci.yml, .github/workflows/phpcs.yml, .github/workflows/phpunit.yml
Introduces CI/CD workflows for PHPCS checks and PHPUnit testing across PHP versions (7.4, 8.0, 8.1) and WordPress versions (latest, trunk). Includes Composer dependency caching, MySQL 5.7 service with health checks, and WordPress test environment setup.
Project Configuration
composer.json, phpcs.xml.dist, phpunit.xml.dist, .github/dependabot.yml
Adds Composer manifest with development dependencies and script aliases (phpcs, phpcbf, test, test:coverage), PHPCS ruleset enforcing WordPress-Extra standards with PHP 7.4+ compatibility, PHPUnit configuration with test suite definitions and coverage rules, and Dependabot setup for weekly automated dependency updates across production and development groups.
Testing Infrastructure
tests/bootstrap.php, tests/test-sample.php
Establishes PHPUnit test framework with WordPress test environment bootstrap loader and sample test class validating plugin constant definition and availability.
Documentation & Project Files
CONTRIBUTING.md, README.md, .gitignore
Documents contribution guidelines covering local development, PHPCS usage, testing setup and CI/CD pipeline overview; updates README with CI badges and expanded Features section; configures Git ignore patterns for vendor dependencies, test artifacts, and configuration files.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main objective of the changeset: setting up CI/CD infrastructure with PHPCS, PHPUnit, and Dependabot automation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/setup-ci-cd

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (4)
composer.json (1)

24-24: Consider cross-platform compatibility for the coverage script.

The inline XDEBUG_MODE=coverage syntax works on Unix-like systems but may fail on Windows. If Windows development is expected, consider using a cross-platform solution.

💡 Alternative using cross-env (if needed)

If Windows support is required, you could use the cross-env npm package or document this limitation. For most CI environments (Linux-based), the current approach works fine.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composer.json` at line 24, The "test:coverage" npm script sets
XDEBUG_MODE=coverage using Unix shell syntax which breaks on Windows; update the
script named "test:coverage" in composer.json to be cross-platform by either
using cross-env (add cross-env as a dev dependency and change the script to
prepend cross-env to the existing command) or document the platform limitation
in README if Windows support isn't required—target the "test:coverage" script
entry when making the change.
tests/test-sample.php (1)

16-18: Consider replacing the placeholder test with meaningful assertions.

The test_sample method is a placeholder that always passes. While acceptable for initial CI setup, consider replacing it with actual tests for plugin functionality as the test suite matures.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test-sample.php` around lines 16 - 18, The test_sample placeholder
(function test_sample) always passes and should be replaced with meaningful
assertions that verify real plugin behavior; update the test to call the plugin
entrypoints or classes (e.g., plugin activation, public methods, shortcodes,
hooks or expected output), assert expected return values or side-effects rather
than assertTrue(true), and rename the test to reflect the scenario being
validated (e.g., test_plugin_activation, test_shortcode_output) so CI verifies
actual functionality.
.github/workflows/phpcs.yml (1)

17-22: Pin the action to a full commit SHA for improved supply chain security.

Using @v2 is convenient, but GitHub's official security recommendations advise pinning to a full 40-character commit SHA, which makes the dependency immutable and prevents risks from moved or retagged versions. Include the version as a comment for maintainability:

uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # v2
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/phpcs.yml around lines 17 - 22, Replace the loose tag on
the GitHub Action for setting up PHP by pinning the shivammathur/setup-php
action to the full commit SHA instead of `@v2`; update the uses entry that
currently reads uses: shivammathur/setup-php@v2 to use the 40-character commit
SHA and add a trailing inline comment with the original tag (e.g., "# v2") for
clarity and maintainability so the workflow becomes immutable while retaining
the human-readable version.
bin/install-wp-tests.sh (1)

20-29: Prefer command -v over which in download().

which output is not reliable for presence checks; using command -v avoids false positives and silences SC2046.

♻️ Suggested refactor
 download() {
-    if [ `which curl` ]; then
-        curl -s "$1" > "$2";
-    elif [ `which wget` ]; then
-        wget -nv -O "$2" "$1"
+    if command -v curl > /dev/null 2>&1; then
+        curl -s "$1" > "$2"
+    elif command -v wget > /dev/null 2>&1; then
+        wget -nv -O "$2" "$1"
     else
         echo "Error: Neither curl nor wget is installed."
         exit 1
     fi
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/install-wp-tests.sh` around lines 20 - 29, The download() function uses
`which` to check for curl/wget; replace those checks with POSIX-safe `command
-v` calls (e.g., `command -v curl >/dev/null 2>&1`) to reliably detect
executables and avoid SC2046; update both the curl and wget branches in
download() to use `command -v` with stderr redirected to /dev/null and preserve
the existing behavior (curl -> download, wget -> download, else -> error + exit
1).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 24-27: In the "Get Composer Cache Directory" step (id:
composer-cache) and the other step that writes to GITHUB_OUTPUT, the redirect
uses an unquoted $GITHUB_OUTPUT which can break on paths with spaces; update the
echo redirect expressions to quote the variable (use "$GITHUB_OUTPUT" instead of
$GITHUB_OUTPUT) so the shell does not perform word splitting when writing
outputs.
- Around line 14-18: Update all `uses:` entries to pin to full commit SHAs
rather than tag refs: replace `actions/checkout@v4`,
`shivammathur/setup-php@v2`, and `actions/cache@v4` occurrences with the
corresponding repository@<commit-sha> values (both instances in each job), e.g.,
fetch the latest v4/v2 commit SHAs using `gh api
repos/<owner>/<repo>/commits/<tag> --jq '.sha'` (e.g., actions/checkout,
shivammathur/setup-php, actions/cache) and substitute those SHAs into the
`uses:` lines so each `uses:` is pinned to repo@<full-sha>.
- Around line 87-90: The CI step named "Set up WordPress test environment"
invokes bin/install-wp-tests.sh which fails when SVN is missing; before running
that script, add a step to install Subversion (SVN) on the runner (e.g., run
apt-get update && apt-get install -y subversion or the runner's package manager
equivalent) so that bin/install-wp-tests.sh can fetch trunk resources without
exiting with "Error: svn is not installed."; place this installation command
immediately before the run that calls bin/install-wp-tests.sh to ensure SVN is
available for WordPress trunk tests.

In @.github/workflows/phpcs.yml:
- Line 26: The run line uses unquoted command substitution which can split paths
with spaces; change the command to quote the substitution and the assigned
variable (use "$(composer config cache-files-dir)") when appending to
GITHUB_OUTPUT so the dir value is written as a single token to $GITHUB_OUTPUT
(i.e., update the run that writes dir=$(composer config cache-files-dir) to use
quoted command substitution and proper quoting around the value and
GITHUB_OUTPUT reference).
- Line 32: The cache key references hashFiles('**/composer.lock') but
composer.lock is missing so the key will be constant; update the cache
configuration in .github/workflows/phpcs.yml by either adding a real
composer.lock to the repo or changing the key expression used by the 'key' entry
to a file that exists (for example hashFiles('**/composer.json')) or remove the
composer cache block entirely if PHP dependency caching is not needed, ensuring
you update the 'key' value and any corresponding 'restore-keys' to match.

In @.github/workflows/phpunit.yml:
- Around line 42-44: The run step in the "Get Composer Cache Directory" job (id:
composer-cache) is redirecting output into $GITHUB_OUTPUT without quotes; change
the redirect to use "$GITHUB_OUTPUT" (i.e., echo "dir=$(composer config
cache-files-dir)" >> "$GITHUB_OUTPUT") to prevent word-splitting and satisfy
ShellCheck/best practices.
- Around line 56-58: The CI step "Set up WordPress test environment" currently
runs bin/install-wp-tests.sh but fails when matrix value 'trunk' is used because
SVN is not installed; modify the workflow so the job installs SVN (e.g., apt-get
install subversion or the distro's package) before executing
bin/install-wp-tests.sh, ensuring the installation runs in the same job and
platform as the "Set up WordPress test environment" step and occurs prior to
invoking the script that checks for svn.

In `@bin/install-wp-tests.sh`:
- Around line 128-137: The guard currently checks for the existence of
wp-tests-config.php in the current working directory (if [ ! -f
wp-tests-config.php ]) which causes re-download/overwrite each run; change the
existence check to target the tests directory file
("$WP_TESTS_DIR/wp-tests-config.php") so the block that downloads and writes
wp-tests-config.php only runs when "$WP_TESTS_DIR/wp-tests-config.php" is
missing; update the if condition accordingly and keep the rest of the logic
(download, WP_CORE_DIR trimming, sed substitutions) unchanged so they operate on
the correct "$WP_TESTS_DIR/wp-tests-config.php" file.

In `@README.md`:
- Line 14: The README's schedule line "Schedule: Weekly (Monday mornings)" is
inconsistent with the Dependabot config which only sets interval: "weekly";
either update the README to "Weekly" to match the config, or add an explicit
day: "monday" to the dependabot.yml under the appropriate updates entry; locate
the README string "Schedule: Weekly (Monday mornings)" and the dependabot.yml
keys interval and updates (add day: "monday" beneath interval) to make the
documentation and config consistent.

---

Nitpick comments:
In @.github/workflows/phpcs.yml:
- Around line 17-22: Replace the loose tag on the GitHub Action for setting up
PHP by pinning the shivammathur/setup-php action to the full commit SHA instead
of `@v2`; update the uses entry that currently reads uses:
shivammathur/setup-php@v2 to use the 40-character commit SHA and add a trailing
inline comment with the original tag (e.g., "# v2") for clarity and
maintainability so the workflow becomes immutable while retaining the
human-readable version.

In `@bin/install-wp-tests.sh`:
- Around line 20-29: The download() function uses `which` to check for
curl/wget; replace those checks with POSIX-safe `command -v` calls (e.g.,
`command -v curl >/dev/null 2>&1`) to reliably detect executables and avoid
SC2046; update both the curl and wget branches in download() to use `command -v`
with stderr redirected to /dev/null and preserve the existing behavior (curl ->
download, wget -> download, else -> error + exit 1).

In `@composer.json`:
- Line 24: The "test:coverage" npm script sets XDEBUG_MODE=coverage using Unix
shell syntax which breaks on Windows; update the script named "test:coverage" in
composer.json to be cross-platform by either using cross-env (add cross-env as a
dev dependency and change the script to prepend cross-env to the existing
command) or document the platform limitation in README if Windows support isn't
required—target the "test:coverage" script entry when making the change.

In `@tests/test-sample.php`:
- Around line 16-18: The test_sample placeholder (function test_sample) always
passes and should be replaced with meaningful assertions that verify real plugin
behavior; update the test to call the plugin entrypoints or classes (e.g.,
plugin activation, public methods, shortcodes, hooks or expected output), assert
expected return values or side-effects rather than assertTrue(true), and rename
the test to reflect the scenario being validated (e.g., test_plugin_activation,
test_shortcode_output) so CI verifies actual functionality.

Comment thread .github/workflows/ci.yml
Comment on lines +14 to +18
- name: Checkout code
uses: actions/checkout@v4

- name: Set up PHP
uses: shivammathur/setup-php@v2

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/ci.yml

Repository: raphaelsuzuki/pmpro-level-explorer

Length of output: 3342


Pin GitHub Actions to full commit SHAs.

Tag refs are mutable; pinning improves supply-chain security and reproducibility. Currently, all 6 uses: entries in the workflow use semantic versions (v2, v4) instead of commit SHAs. Update all instances across both jobs:

  • Line 15: actions/checkout@v4
  • Line 18: shivammathur/setup-php@v2
  • Line 29: actions/cache@v4
  • Line 63: actions/checkout@v4
  • Line 66: shivammathur/setup-php@v2
  • Line 78: actions/cache@v4
🔒 Proposed pinning pattern
-        uses: actions/checkout@v4
+        uses: actions/checkout@<full_commit_sha> # v4

-        uses: shivammathur/setup-php@v2
+        uses: shivammathur/setup-php@<full_commit_sha> # v2

-        uses: actions/cache@v4
+        uses: actions/cache@<full_commit_sha> # v4

Retrieve commit SHAs from:

gh api repos/actions/checkout/commits/v4 --jq '.sha'
gh api repos/shivammathur/setup-php/commits/v2 --jq '.sha'
gh api repos/actions/cache/commits/v4 --jq '.sha'
🧰 Tools
🪛 GitHub Check: Codacy Static Code Analysis

[warning] 18-18: .github/workflows/ci.yml#L18
An action sourced from a third-party repository on GitHub is not pinned to a full length commit SHA. Pinning an action to a full length commit SHA is currently the only way to use an action as an immutable release.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 14 - 18, Update all `uses:` entries to
pin to full commit SHAs rather than tag refs: replace `actions/checkout@v4`,
`shivammathur/setup-php@v2`, and `actions/cache@v4` occurrences with the
corresponding repository@<commit-sha> values (both instances in each job), e.g.,
fetch the latest v4/v2 commit SHAs using `gh api
repos/<owner>/<repo>/commits/<tag> --jq '.sha'` (e.g., actions/checkout,
shivammathur/setup-php, actions/cache) and substitute those SHAs into the
`uses:` lines so each `uses:` is pinned to repo@<full-sha>.

Comment thread .github/workflows/ci.yml
Comment on lines +24 to +27
- name: Get Composer Cache Directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/ci.yml | head -80

Repository: raphaelsuzuki/pmpro-level-explorer

Length of output: 2805


Quote $GITHUB_OUTPUT to avoid word splitting in shell redirects.

Shellcheck flags unquoted variables used in redirects. If the path contains spaces, the redirect will fail without quoting. This applies to lines 26 and 75.

✅ Suggested fix
-        run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
+        run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Get Composer Cache Directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Get Composer Cache Directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"
🧰 Tools
🪛 actionlint (1.7.11)

[error] 26-26: shellcheck reported issue in this script: SC2086:info:1:50: Double quote to prevent globbing and word splitting

(shellcheck)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 24 - 27, In the "Get Composer Cache
Directory" step (id: composer-cache) and the other step that writes to
GITHUB_OUTPUT, the redirect uses an unquoted $GITHUB_OUTPUT which can break on
paths with spaces; update the echo redirect expressions to quote the variable
(use "$GITHUB_OUTPUT" instead of $GITHUB_OUTPUT) so the shell does not perform
word splitting when writing outputs.

Comment thread .github/workflows/ci.yml Outdated
Comment on lines +87 to +90
- name: Set up WordPress test environment
run: |
bash bin/install-wp-tests.sh wordpress_tests root '' localhost ${{ matrix.wordpress-versions }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find and read the workflow file
find . -name "ci.yml" -o -name "ci.yaml" | head -20

Repository: raphaelsuzuki/pmpro-level-explorer

Length of output: 104


🏁 Script executed:

# Read the workflow file to examine the context around lines 87-90
cat -n .github/workflows/ci.yml

Repository: raphaelsuzuki/pmpro-level-explorer

Length of output: 3342


🏁 Script executed:

# Check if the install-wp-tests.sh script exists and examine it for SVN requirements
find . -name "install-wp-tests.sh" -type f

Repository: raphaelsuzuki/pmpro-level-explorer

Length of output: 103


🏁 Script executed:

# Read the install-wp-tests.sh script to check for SVN dependencies
cat -n ./bin/install-wp-tests.sh

Repository: raphaelsuzuki/pmpro-level-explorer

Length of output: 7785


Add SVN installation before running WordPress test setup to fix CI failure.

The install-wp-tests.sh script requires SVN to be installed, particularly when testing WordPress trunk versions. Without it, the script exits with "Error: svn is not installed." Add the installation step to the workflow:

Suggested fix
      - name: Install dependencies
        run: composer install --prefer-dist --no-progress

+      - name: Install SVN
+        run: sudo apt-get update && sudo apt-get install -y subversion
+
       - name: Set up WordPress test environment
         run: |
           bash bin/install-wp-tests.sh wordpress_tests root '' localhost ${{ matrix.wordpress-versions }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 87 - 90, The CI step named "Set up
WordPress test environment" invokes bin/install-wp-tests.sh which fails when SVN
is missing; before running that script, add a step to install Subversion (SVN)
on the runner (e.g., run apt-get update && apt-get install -y subversion or the
runner's package manager equivalent) so that bin/install-wp-tests.sh can fetch
trunk resources without exiting with "Error: svn is not installed."; place this
installation command immediately before the run that calls
bin/install-wp-tests.sh to ensure SVN is available for WordPress trunk tests.


- name: Get Composer Cache Directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Quote the command substitution to prevent word splitting.

The shell variable should be quoted to handle paths with spaces correctly.

🔧 Proposed fix
-        run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
+        run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"
🧰 Tools
🪛 actionlint (1.7.11)

[error] 26-26: shellcheck reported issue in this script: SC2086:info:1:50: Double quote to prevent globbing and word splitting

(shellcheck)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/phpcs.yml at line 26, The run line uses unquoted command
substitution which can split paths with spaces; change the command to quote the
substitution and the assigned variable (use "$(composer config
cache-files-dir)") when appending to GITHUB_OUTPUT so the dir value is written
as a single token to $GITHUB_OUTPUT (i.e., update the run that writes
dir=$(composer config cache-files-dir) to use quoted command substitution and
proper quoting around the value and GITHUB_OUTPUT reference).

uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if composer.lock exists and is committed to git
fd -t f "composer.lock" .
echo "---"
# Check git status/tracking of composer.lock if found
if [ -f "composer.lock" ]; then
  git ls-files | grep composer.lock
  echo "---"
  echo "composer.lock found and checking git status..."
  git log --oneline -1 -- composer.lock 2>/dev/null && echo "Tracked in git" || echo "Not tracked in git"
fi

Repository: raphaelsuzuki/pmpro-level-explorer

Length of output: 81


composer.lock does not exist in the repository; the cache key will not work as intended.

The cache key uses hashFiles('**/composer.lock'), but since this file doesn't exist in the repository, GitHub Actions will generate a constant hash (the pattern string itself) instead of hashing actual file content. This means the cache will not invalidate when dependencies change. Either add composer.lock to the repository (for reproducible builds) or remove this cache configuration if PHP dependency management is not in use.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/phpcs.yml at line 32, The cache key references
hashFiles('**/composer.lock') but composer.lock is missing so the key will be
constant; update the cache configuration in .github/workflows/phpcs.yml by
either adding a real composer.lock to the repo or changing the key expression
used by the 'key' entry to a file that exists (for example
hashFiles('**/composer.json')) or remove the composer cache block entirely if
PHP dependency caching is not needed, ensuring you update the 'key' value and
any corresponding 'restore-keys' to match.

Comment on lines +42 to +44
- name: Get Composer Cache Directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's check if the file exists and read the relevant section
if [ -f ".github/workflows/phpunit.yml" ]; then
  echo "=== File content around lines 42-44 ==="
  sed -n '40,46p' ".github/workflows/phpunit.yml" | cat -n
else
  echo "File not found"
fi

Repository: raphaelsuzuki/pmpro-level-explorer

Length of output: 394


🏁 Script executed:

# Let's also check the full content of the step to understand context better
if [ -f ".github/workflows/phpunit.yml" ]; then
  echo "=== Full file ==="
  cat -n ".github/workflows/phpunit.yml"
fi

Repository: raphaelsuzuki/pmpro-level-explorer

Length of output: 2284


Quote $GITHUB_OUTPUT to avoid word splitting in the redirect.

While $GITHUB_OUTPUT is typically set to a path without spaces, quoting it follows shell best practices and makes the redirect robust against edge cases. Shellcheck flags this pattern.

✅ Suggested fix
-        run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
+        run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Get Composer Cache Directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Get Composer Cache Directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"
🧰 Tools
🪛 actionlint (1.7.11)

[error] 44-44: shellcheck reported issue in this script: SC2086:info:1:50: Double quote to prevent globbing and word splitting

(shellcheck)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/phpunit.yml around lines 42 - 44, The run step in the "Get
Composer Cache Directory" job (id: composer-cache) is redirecting output into
$GITHUB_OUTPUT without quotes; change the redirect to use "$GITHUB_OUTPUT"
(i.e., echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT") to
prevent word-splitting and satisfy ShellCheck/best practices.

Comment thread .github/workflows/phpunit.yml Outdated
Comment thread bin/install-wp-tests.sh Outdated
Comment on lines +128 to +137
if [ ! -f wp-tests-config.php ]; then
download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php
# remove all forward slashes in the end
WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::")
sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s:__DIR__ . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Check wp-tests-config.php inside $WP_TESTS_DIR.

The current guard checks the CWD, so it will re-download/overwrite the config every run.

✅ Suggested fix
-    if [ ! -f wp-tests-config.php ]; then
+    if [ ! -f "$WP_TESTS_DIR/wp-tests-config.php" ]; then
         download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if [ ! -f wp-tests-config.php ]; then
download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php
# remove all forward slashes in the end
WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::")
sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s:__DIR__ . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php
if [ ! -f "$WP_TESTS_DIR/wp-tests-config.php" ]; then
download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php
# remove all forward slashes in the end
WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::")
sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s:__DIR__ . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php
🧰 Tools
🪛 GitHub Check: Codacy Static Code Analysis

[warning] 131-131: bin/install-wp-tests.sh#L131
See if you can use ${variable//search/replace} instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/install-wp-tests.sh` around lines 128 - 137, The guard currently checks
for the existence of wp-tests-config.php in the current working directory (if [
! -f wp-tests-config.php ]) which causes re-download/overwrite each run; change
the existence check to target the tests directory file
("$WP_TESTS_DIR/wp-tests-config.php") so the block that downloads and writes
wp-tests-config.php only runs when "$WP_TESTS_DIR/wp-tests-config.php" is
missing; update the if condition accordingly and keep the rest of the logic
(download, WP_CORE_DIR trimming, sed substitutions) unchanged so they operate on
the correct "$WP_TESTS_DIR/wp-tests-config.php" file.

Comment thread README.md
Dependabot is enabled for this repository to automatically manage dependency updates.

- **What it does:** Automatically checks for outdated or vulnerable Composer dependencies and opens pull requests.
- **Schedule:** Weekly (Monday mornings).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Minor: Dependabot schedule description may be inaccurate.

The documentation states "Weekly (Monday mornings)" but the dependabot.yml only specifies interval: "weekly" without a specific day. Dependabot's default day for weekly updates may vary. Consider either updating the documentation to say "Weekly" or adding day: "monday" to the Dependabot config.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 14, The README's schedule line "Schedule: Weekly (Monday
mornings)" is inconsistent with the Dependabot config which only sets interval:
"weekly"; either update the README to "Weekly" to match the config, or add an
explicit day: "monday" to the dependabot.yml under the appropriate updates
entry; locate the README string "Schedule: Weekly (Monday mornings)" and the
dependabot.yml keys interval and updates (add day: "monday" beneath interval) to
make the documentation and config consistent.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/phpunit.yml:
- Around line 31-48: The workflow currently references mutable tag refs
(actions/checkout@v4, shivammathur/setup-php@v2, actions/cache@v4); replace
these with their corresponding full commit SHAs to harden supply-chain security
and ensure reproducible runs. Locate the steps using "uses:
actions/checkout@v4", "uses: shivammathur/setup-php@v2", and "uses:
actions/cache@v4" and update each to use the full commit SHA (obtained via the
gh api commands suggested in the comment) while leaving all step inputs
unchanged; commit the updated SHAs in the workflow file.

---

Duplicate comments:
In @.github/workflows/ci.yml:
- Around line 14-18: Replace the loose version tags for GitHub Action usages
(e.g., "uses: actions/checkout@v4" and "uses: shivammathur/setup-php@v2") with
immutable full commit SHAs to harden the supply chain; locate every "uses:"
entry in the workflow (including the other occurrences referenced) and update
each tag to the corresponding commit SHA for that action repository (e.g.,
actions/checkout@<full-sha>), ensuring you verify the SHA on the action's
official repo and update the workflow YAML accordingly.
- Around line 24-26: The shell redirect should quote the $GITHUB_OUTPUT variable
to prevent word-splitting/globbing: update the "Get Composer Cache Directory"
step (id: composer-cache) to use ">> \"$GITHUB_OUTPUT\"" instead of >>
$GITHUB_OUTPUT, and make the same change for the other identical redirect
occurrence later in the workflow (the second composer-cache-like step).

In @.github/workflows/phpunit.yml:
- Around line 42-44: The workflow step "Get Composer Cache Directory" (id:
composer-cache) is appending to $GITHUB_OUTPUT without quotes; update the run
command so the redirection uses a quoted variable to handle paths with spaces,
i.e. change the line echo "dir=$(composer config cache-files-dir)" >>
$GITHUB_OUTPUT to use >> "$GITHUB_OUTPUT" instead.

Comment on lines +31 to +48
- name: Checkout code
uses: actions/checkout@v4

- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
extensions: mysqli, zip, gd
coverage: xdebug
tools: composer:v2, wp-cli

- name: Get Composer Cache Directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

- name: Cache Composer dependencies
uses: actions/cache@v4
with:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the phpunit.yml file and check lines 31-48
head -50 .github/workflows/phpunit.yml | tail -25

Repository: raphaelsuzuki/pmpro-level-explorer

Length of output: 899


🏁 Script executed:

# Get the entire phpunit.yml file to understand the structure
cat -n .github/workflows/phpunit.yml

Repository: raphaelsuzuki/pmpro-level-explorer

Length of output: 2663


🌐 Web query:

GitHub Actions security best practices pinning commit SHA vs tags 2024 2025

💡 Result:

  • Most secure default (GitHub’s recommendation): pin uses: to a full-length commit SHA. This is the only universally immutable way to reference an action, and it reduces the risk of a compromised/moved tag being picked up automatically. (docs.github.com)

    • Example: uses: actions/checkout@<40-char-sha> # v4.1.7 (comment the release tag for human readability and easier audits). (wellarchitected.github.com)
  • Tags/branches are convenient but mutable unless protected by GitHub features.

    • A normal tag (e.g., @v4 / @v4.1.7) can be moved or deleted if the repo is compromised, so GitHub advises using tags only when you trust the publisher (e.g., “Verified creator”). (docs.github.com)
    • Immutable Releases (public preview Aug 2025; later broadly available) lock the release’s tag to a specific commit and prevent modifying/deleting release assets, adding supply-chain protections (and attestations). If you rely on tags, prefer tags that are part of an immutable release. (docs.github.com)
  • 2024–2025 governance improvements you can use/enforce

    • GitHub’s allowed-actions policy now supports enforcing SHA pinning (and also blocking specific actions/versions), causing workflows to fail if an action isn’t SHA-pinned. (github.blog)
  • Operational best practice: combine SHA pinning with automated updates

    • Use Dependabot version updates for github-actions so you still get regular upgrades while keeping workflows immutable between upgrades. (docs.github.com)

Citations:


Pin GitHub Actions to full commit SHAs for supply-chain safety.

Tag refs are mutable; using full commit SHAs makes runs reproducible and more secure against tag tampering. GitHub's official security guidance recommends this pattern:

-      - name: Checkout code
-        uses: actions/checkout@v4
+      - name: Checkout code
+        uses: actions/checkout@<full_commit_sha> # v4

-      - name: Set up PHP
-        uses: shivammathur/setup-php@v2
+      - name: Set up PHP
+        uses: shivammathur/setup-php@<full_commit_sha> # v2

-      - name: Cache Composer dependencies
-        uses: actions/cache@v4
+      - name: Cache Composer dependencies
+        uses: actions/cache@<full_commit_sha> # v4

Fetch SHAs with:

gh api repos/actions/checkout/commits/v4 --jq '.sha'
gh api repos/shivammathur/setup-php/commits/v2 --jq '.sha'
gh api repos/actions/cache/commits/v4 --jq '.sha'
🧰 Tools
🪛 GitHub Check: Codacy Static Code Analysis

[warning] 35-35: .github/workflows/phpunit.yml#L35
An action sourced from a third-party repository on GitHub is not pinned to a full length commit SHA. Pinning an action to a full length commit SHA is currently the only way to use an action as an immutable release.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/phpunit.yml around lines 31 - 48, The workflow currently
references mutable tag refs (actions/checkout@v4, shivammathur/setup-php@v2,
actions/cache@v4); replace these with their corresponding full commit SHAs to
harden supply-chain security and ensure reproducible runs. Locate the steps
using "uses: actions/checkout@v4", "uses: shivammathur/setup-php@v2", and "uses:
actions/cache@v4" and update each to use the full commit SHA (obtained via the
gh api commands suggested in the comment) while leaving all step inputs
unchanged; commit the updated SHAs in the workflow file.

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.

1 participant