diff --git a/.github/workflows/pr_commands_comment.yml b/.github/workflows/pr_commands_comment.yml new file mode 100644 index 000000000000..e9bed3fd65ee --- /dev/null +++ b/.github/workflows/pr_commands_comment.yml @@ -0,0 +1,73 @@ +#/ +# @license Apache-2.0 +# +# Copyright (c) 2025 The Stdlib Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#/ + +# Workflow name: +name: pr_commands_comment + +# Workflow triggers: +on: + # Allow the workflow to be triggered by other workflows + workflow_call: + # Define the input parameters for the workflow: + inputs: + pull_request_number: + description: 'Pull request number' + required: true + type: number + + # Define the secrets accessible by the workflow: + secrets: + STDLIB_BOT_GITHUB_TOKEN: + description: 'stdlib-bot GitHub token to create pull request comments' + required: true + + # Allow the workflow to be manually triggered: + workflow_dispatch: + inputs: + pull_request_number: + description: 'Pull request number' + required: true + type: number + +# Global permissions: +permissions: + # Allow read-only access to the repository contents: + contents: read + +# Workflow jobs: +jobs: + + # Define a job for leaving a comment on a PR with package make command instructions: + pr_commands_comment: + + # Define a display name: + name: 'Leave comment with package make command instructions' + + # Define the type of virtual host machine: + runs-on: ubuntu-latest + + # Define the sequence of job steps... + steps: + + # Leave comment with package make command instructions: + - name: 'Leave comment with package make command instructions' + env: + PR_NUMBER: ${{ inputs.pull_request_number }} + run: | + . "$GITHUB_WORKSPACE/.github/workflows/scripts/package_commands_comment" "$PR_NUMBER" + timeout-minutes: 30 diff --git a/.github/workflows/scripts/check_contributing_guidelines_acceptance b/.github/workflows/scripts/check_contributing_guidelines_acceptance index ae55821f9c1d..de39a9a2e97d 100755 --- a/.github/workflows/scripts/check_contributing_guidelines_acceptance +++ b/.github/workflows/scripts/check_contributing_guidelines_acceptance @@ -72,7 +72,7 @@ main() { fi # Check for the contributing guidelines checkbox: - if echo "${pr_body}" | grep -qE '^\s*-\s*\[x\]\s*Read,\s*understood,\s*and\s*followed\s*the\s*\[contributing\s*guidelines\]'; then + if echo "${pr_body}" | grep -qE '^\s*-\s*\[[xX]\]\s*Read,\s*understood,\s*and\s*followed\s*the\s*\[contributing\s*guidelines\]'; then echo "Contributing guidelines acknowledged." print_success exit 0 diff --git a/.github/workflows/scripts/package_commands_comment b/.github/workflows/scripts/package_commands_comment new file mode 100755 index 000000000000..115b14ec1bc2 --- /dev/null +++ b/.github/workflows/scripts/package_commands_comment @@ -0,0 +1,253 @@ +#!/usr/bin/env bash +# +# @license Apache-2.0 +# +# Copyright (c) 2025 The Stdlib Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Script to post a comment with relevant `make` commands tailored to packages changed in a PR. +# +# Usage: package_commands_comment +# +# Arguments: +# +# pr_number Pull request number. +# +# Environment variables: +# +# GITHUB_TOKEN GitHub token for authentication. + +# Ensure that the exit status of pipelines is non-zero in the event that at least one of the commands in a pipeline fails: +set -o pipefail + + +# VARIABLES # + +# Resolve the pull request number: +pr_number="$1" + +# GitHub API base URL: +github_api_url="https://api.github.com" + +# Repository owner and name: +repo_owner="stdlib-js" +repo_name="stdlib" + + +# FUNCTIONS # + +# Error handler. +# +# $1 - error status +on_error() { + echo 'ERROR: An error was encountered during execution.' >&2 + exit "$1" +} + +# Prints a success message. +print_success() { + echo 'Success!' >&2 +} + +# Performs a GitHub API request. +# +# $1 - HTTP method (GET or POST) +# $2 - API endpoint +# $3 - data for POST requests +github_api() { + local method="$1" + local endpoint="$2" + local data="$3" + + # Initialize an array to hold curl headers: + local headers=() + + # If GITHUB_TOKEN is set, add the Authorization header: + if [ -n "${GITHUB_TOKEN}" ]; then + headers+=("-H" "Authorization: token ${GITHUB_TOKEN}") + fi + + # Determine the HTTP method and construct the curl command accordingly... + case "${method}" in + GET) + curl -s "${headers[@]}" "${github_api_url}${endpoint}" + ;; + POST) + # For POST requests, always set the Content-Type header: + headers+=("-H" "Content-Type: application/json") + + # If data is provided, include it in the request: + if [ -n "${data}" ]; then + curl -s -X POST "${headers[@]}" -d "${data}" "${github_api_url}${endpoint}" + else + # Handle cases where POST data is required but not provided: + echo "ERROR: POST request requires data." + on_error 1 + fi + ;; + *) + echo "ERROR: Invalid HTTP method: ${method}." + on_error 1 + ;; + esac +} + +# Main execution sequence. +main() { + local directories + local packages + local response + local c_files + local comment + local files + + if [ -z "${pr_number}" ]; then + echo "ERROR: Pull request number is required." >&2 + on_error 1 + fi + + # Fetch changed files in pull request: + response=$(github_api "GET" "/repos/${repo_owner}/${repo_name}/pulls/${pr_number}/files?per_page=100") + files=$(echo "${response}" | jq -r '.[] | .filename') + + # Extract files associated with native add-ons: + c_files=$(echo "${files}" | grep -e '/benchmark/c' -e '/examples/c' -e '/binding.gyp' -e '/include.gypi' -e '/src/') + + # Find unique package directories: + directories=$(echo "${files}" | tr ' ' '\n' | \ + xargs dirname | \ + grep '^lib/node_modules/@stdlib' | \ + sed -E 's/\/(benchmark|bin|data|docs|etc|examples|include|lib|scripts|src|test)(\/.*)?$//' | \ + sort -u) + + # Extract package names from changed package directories (e.g., @stdlib/math/base/special/sin) by removing the leading 'lib/node_modules/': + packages=$(echo "${directories}" | sed -E 's/^lib\/node_modules\///') + + # Documentation links: + docs_links=" +## Documentation Links + +- [make rules for running examples][make-docs-examples] +- [make rules for running unit tests][make-docs-test] +- [make rules for running benchmarks][make-docs-benchmark] + +[make-docs-examples]: https://github.com/stdlib-js/stdlib/blob/develop/tools/make/lib/examples/README.md +[make-docs-test]: https://github.com/stdlib-js/stdlib/blob/develop/tools/make/lib/test/README.md +[make-docs-benchmark]: https://github.com/stdlib-js/stdlib/blob/develop/tools/make/lib/benchmark/README.md" + + # Count the number of packages: + package_count=$(echo "${packages}" | wc -l) + + if [[ $package_count -gt 1 ]]; then + # Multiple packages case: + comment="Hello! 👋 + +Pro-tip: This PR changes multiple packages. You can use various \`make\` rules with \`*_FILTER\` environment variables to run tests, benchmarks, and examples for specific packages. + +For each of the commands, please run them from the root stdlib repository directory (not the package folder!). + +You can use pattern matching to target specific packages as follows: + +\`\`\`bash +# Run tests for all packages in the math namespace: +make test TESTS_FILTER=\".*/@stdlib/math/.*\" + +# Run benchmarks for specific packages (using OR pattern): +make benchmark BENCHMARKS_FILTER=\".*/@stdlib/math/base/special/(sin|cos)/.*\" +\`\`\` + +## Changed Packages + +$(echo "${packages}" | sed 's/^/- `/' | sed 's/$/`/') +${docs_links}" + + else + # Single package case: + if [[ "${#c_files[@]}" -eq 0 ]]; then + comment="Hello! 👋 + +Pro-tip: Use the \`make\` commands below during local development to ensure that all tests, examples, and benchmark files in your PR run successfully. + +For each of the commands, please run them from the root stdlib repository directory (not the package folder!). + +To run unit tests, + +\`\`\`bash +make test TESTS_FILTER=\".*/${packages}/.*\" +\`\`\` + +To run benchmarks, + +\`\`\`bash +make benchmark BENCHMARKS_FILTER=\".*/${packages}/.*\" +\`\`\` + +To run examples, + +\`\`\`bash +make examples EXAMPLES_FILTER=\".*/${packages}/.*\" +\`\`\` +${docs_links}" + else + comment="Hello! 👋 + +Pro-tip: Use the \`make\` below commands during local development to ensure that all tests, examples, and benchmark files in your PR run successfully. + +For each of the commands, please run them from the root stdlib repository directory (not the package folder!). + +To build a native add-on, + +\`\`\`bash +NODE_ADDONS_PATTERN=\"${packages}\" make install-node-addons +\`\`\` + +To run unit tests, + +\`\`\`bash +make test TESTS_FILTER=\".*/${packages}/.*\" +\`\`\` + +To run benchmarks, + +\`\`\`bash +make benchmark BENCHMARKS_FILTER=\".*/${packages}/.*\" +\`\`\` + +\`\`\`bash +make benchmark-c BENCHMARKS_FILTER=\".*/${packages}/.*\" +\`\`\` + +To run examples, + +\`\`\`bash +make examples EXAMPLES_FILTER=\".*/${packages}/.*\" +\`\`\` + +\`\`\`bash +make examples-c EXAMPLES_FILTER=\".*/${packages}/.*\" +\`\`\` +${docs_links}" + fi + fi + + if ! github_api "POST" "/repos/${repo_owner}/${repo_name}/issues/${pr_number}/comments" "{\"body\":$(echo "${comment}" | jq -R -s -c .)}"; then + echo "Failed to post comment on PR." + on_error 1 + fi + + print_success + exit 0 +} + +main diff --git a/.github/workflows/slash_commands.yml b/.github/workflows/slash_commands.yml index 7fbac945941c..8cc39611060f 100644 --- a/.github/workflows/slash_commands.yml +++ b/.github/workflows/slash_commands.yml @@ -64,7 +64,7 @@ jobs: github-token: ${{ secrets.STDLIB_BOT_PAT_REPO_WRITE }} script: | const commentBody = context.payload.comment.body.trim(); - const RE_COMMANDS = /^\/stdlib\s+(help|check-files|update-copyright-years|lint-autofix|merge|rebase)$/i; + const RE_COMMANDS = /^\/stdlib\s+(help|check-files|update-copyright-years|lint-autofix|merge|rebase|make-commands)$/i; const isRecognizedCommand = RE_COMMANDS.test( commentBody ); if ( isRecognizedCommand ) { @@ -107,6 +107,24 @@ jobs: secrets: STDLIB_BOT_GITHUB_TOKEN: ${{ secrets.STDLIB_BOT_PAT_REPO_WRITE }} + # Define a command for leaving a comment with make command instructions: + make-commands: + # Define a display name: + name: 'Post a comment with make command instructions' + + # Ensure initial reaction job has completed before running this job: + needs: [ add_initial_reaction ] + + # Define the conditions under which the job should run: + if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/stdlib make-commands') + + # Run reusable workflow: + uses: ./.github/workflows/pr_commands_comment.yml + with: + pull_request_number: ${{ github.event.issue.number }} + secrets: + STDLIB_BOT_GITHUB_TOKEN: ${{ secrets.STDLIB_BOT_PAT_REPO_WRITE }} + # Define a job for updating copyright header years: update_copyright_years: @@ -218,6 +236,7 @@ jobs: @${{ github.event.comment.user.login }}, available slash commands include: - `/stdlib check-files` - Check for required files. + - `/stdlib make-commands` - Print `make` commands for package changed in PR. - `/stdlib update-copyright-years` - Update copyright header years. - `/stdlib lint-autofix` - Auto-fix lint errors. - `/stdlib merge` - Merge changes from develop branch into this PR. @@ -236,7 +255,7 @@ jobs: runs-on: ubuntu-latest # Ensure all previous jobs have completed before running this job: - needs: [ add_initial_reaction, check_files, update_copyright_years, fix_lint_errors, merge_develop, rebase_develop, help ] + needs: [ add_initial_reaction, check_files, make-commands, update_copyright_years, fix_lint_errors, merge_develop, rebase_develop, help ] # Define the conditions under which the job should run: if: |