From 73aabdc17354777cb8a3536daa7d17e7425816aa Mon Sep 17 00:00:00 2001 From: Jacob Ransom Date: Tue, 4 Aug 2026 17:17:17 +1200 Subject: [PATCH 1/4] Add reusable license check workflow for plugin repos --- .github/workflows/plugin-license-check.yml | 46 +++++++ README.md | 20 +++ scripts/bash/license_check.sh | 144 +++++++++++++++++++++ 3 files changed, 210 insertions(+) create mode 100644 .github/workflows/plugin-license-check.yml create mode 100755 scripts/bash/license_check.sh diff --git a/.github/workflows/plugin-license-check.yml b/.github/workflows/plugin-license-check.yml new file mode 100644 index 0000000..0a0e07e --- /dev/null +++ b/.github/workflows/plugin-license-check.yml @@ -0,0 +1,46 @@ +name: License check + +on: + workflow_call: + inputs: + fail-on-missing-header: + description: "Treat source files without any recognized license header as errors instead of warnings" + required: false + type: boolean + default: false + script-ref: + description: "Ref of matomo-org/github-action-tests to take the check script from" + required: false + type: string + default: 'main' + +# The job only reads the repository; everything else stays at none so any caller +# with default workflow permissions can use this without granting scopes. +permissions: + contents: read + +jobs: + license-check: + name: License check + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + with: + lfs: false + persist-credentials: false + - name: Check out the license check script + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + with: + repository: matomo-org/github-action-tests + ref: ${{ inputs.script-ref }} + path: .github-action-tests + persist-credentials: false + - name: Run license check + env: + FAIL_ON_MISSING_HEADER: ${{ inputs.fail-on-missing-header }} + run: | + # Move the script out of the workspace so its own checkout + # directory is not scanned as part of the plugin. + cp .github-action-tests/scripts/bash/license_check.sh "$RUNNER_TEMP/" + rm -rf .github-action-tests + bash "$RUNNER_TEMP/license_check.sh" . diff --git a/README.md b/README.md index c4c668b..df38e18 100644 --- a/README.md +++ b/README.md @@ -154,3 +154,23 @@ This action is able to run certain test suites for Matomo or any Matomo plugin. dependent-plugins: 'slug/plugin-AdditionalPlugin' github-token: ${{ secrets.TESTS_ACCESS_TOKEN || secrets.GITHUB_TOKEN }} ``` + +### License check (`.github/workflows/plugin-license-check.yml`) + +Checks that the repository ships a `LICENSE` file matching the license declared in +`plugin.json`, and that source files (`*.php`, `*.js`, `*.ts`, `*.vue`) carry the matching +license header: the GPL header for OSS plugins, the InnoCraft EULA header for premium ones. +A file carrying the opposite header fails the check; files with no recognized header are +reported as warnings. Inputs: `fail-on-missing-header` (optional, default `false`) turns +those warnings into errors; `script-ref` (optional, default `main`) selects the ref of this +repository to take the check script from. Glob patterns in a `.license-check-ignore` file +at the repository root are skipped, e.g. for bundled third-party files under their own +license. + +```yaml +name: License check +on: pull_request +jobs: + license-check: + uses: matomo-org/github-action-tests/.github/workflows/plugin-license-check.yml@main +``` diff --git a/scripts/bash/license_check.sh b/scripts/bash/license_check.sh new file mode 100755 index 0000000..605bb42 --- /dev/null +++ b/scripts/bash/license_check.sh @@ -0,0 +1,144 @@ +#!/bin/bash +# Checks that a plugin repository ships a license file and that its source +# files carry the license header matching the repository's license: +# the GPL header for OSS plugins, the InnoCraft EULA header for premium ones. +# +# Usage: license_check.sh [repo-root] +# +# Files carrying the opposite header are errors; files with no recognized +# header are warnings, or errors when FAIL_ON_MISSING_HEADER is 1 or true. +# Glob patterns in a .license-check-ignore file at the repository root are +# skipped, e.g. for bundled third-party files under their own license. +set -euo pipefail + +REPO_ROOT="${1:-.}" +FAIL_ON_MISSING_HEADER="${FAIL_ON_MISSING_HEADER:-0}" + +PREMIUM_MARKER='Copyright (C) InnoCraft Ltd' +# Matches both the http and https URL variants in use across plugins. +OSS_MARKER='gnu\.org/licenses/gpl-3\.0' +# Only the start of a file counts as its header; a byte window rather than a +# line count so minified single-line bundles are still covered. +HEADER_WINDOW_BYTES=2048 + +errors=0 +warnings=0 + +report_error() { + echo "::error file=$1::$2" + errors=$((errors + 1)) +} + +report_missing_header() { + case "$FAIL_ON_MISSING_HEADER" in + 1|true) + report_error "$1" "$2" + ;; + *) + echo "::warning file=$1::$2" + warnings=$((warnings + 1)) + ;; + esac +} + +cd "$REPO_ROOT" + +if [ ! -f plugin.json ]; then + echo "::error::No plugin.json found; cannot determine whether this repository is OSS or premium" + exit 1 +fi + +license=$(python3 -c "import json; print(json.load(open('plugin.json')).get('license') or '')") +case "$license" in + 'InnoCraft EULA') + repo_type=premium + expected_header='the InnoCraft EULA header' + ;; + GPL*) + repo_type=oss + expected_header='the GPL header' + ;; + *) + echo "::error file=plugin.json::Unrecognized license \"$license\" in plugin.json; expected \"InnoCraft EULA\" or a GPL license" + exit 1 + ;; +esac +echo "Repository type: $repo_type (plugin.json license: \"$license\")" + +license_file='' +for candidate in LICENSE LICENSE.md; do + if [ -f "$candidate" ]; then + license_file="$candidate" + break + fi +done + +if [ -z "$license_file" ]; then + report_error LICENSE 'No LICENSE or LICENSE.md file found at the repository root' +elif [ "$repo_type" = premium ] && ! grep -qi 'InnoCraft' "$license_file"; then + report_error "$license_file" 'plugin.json declares the InnoCraft EULA but the license file does not mention InnoCraft' +elif [ "$repo_type" = oss ] && ! grep -qi 'GNU GENERAL PUBLIC LICENSE' "$license_file"; then + report_error "$license_file" 'plugin.json declares a GPL license but the license file is not the GPL' +fi + +ignore_patterns=() +if [ -f .license-check-ignore ]; then + while IFS= read -r pattern; do + case "$pattern" in ''|'#'*) continue ;; esac + ignore_patterns+=("$pattern") + done < .license-check-ignore +fi + +is_ignored() { + local path="$1" pattern + if [ "${#ignore_patterns[@]}" -eq 0 ]; then + return 1 + fi + for pattern in "${ignore_patterns[@]}"; do + # shellcheck disable=SC2254 -- the patterns are globs by design + case "$path" in + $pattern) return 0 ;; + esac + done + return 1 +} + +scanned=0 +while IFS= read -r -d '' file; do + rel="${file#./}" + if is_ignored "$rel"; then + continue + fi + scanned=$((scanned + 1)) + + header=$(head -c "$HEADER_WINDOW_BYTES" -- "$file") + has_premium=0 + has_oss=0 + if grep -qF "$PREMIUM_MARKER" <<< "$header"; then has_premium=1; fi + if grep -qE "$OSS_MARKER" <<< "$header"; then has_oss=1; fi + + if [ "$repo_type" = premium ]; then + has_expected=$has_premium + has_wrong=$has_oss + wrong_header='a GPL header' + repo_desc='a premium repository' + else + has_expected=$has_oss + has_wrong=$has_premium + wrong_header='an InnoCraft EULA header' + repo_desc='an OSS repository' + fi + + if [ "$has_wrong" = 1 ]; then + report_error "$rel" "File carries $wrong_header but this is $repo_desc" + elif [ "$has_expected" = 0 ]; then + report_missing_header "$rel" "File has no recognized license header; expected $expected_header" + fi +done < <(find . \ + \( -name .git -o -name vendor -o -name node_modules -o -name libs -o -path ./tests/resources \) -prune \ + -o -type f \( -name '*.php' -o -name '*.js' -o -name '*.ts' -o -name '*.vue' \) -print0) + +echo "Checked $scanned files: $errors error(s), $warnings warning(s)" +if [ "$errors" -gt 0 ]; then + exit 1 +fi From c7c15b13f39dc40342df9c8e774062d31b6a7f8b Mon Sep 17 00:00:00 2001 From: Jacob Ransom Date: Wed, 5 Aug 2026 10:51:20 +1200 Subject: [PATCH 2/4] Anchor header markers to comment lines, add tests, avoid checkout collisions --- .github/workflows/plugin-license-check.yml | 8 +- .github/workflows/tests.yml | 17 +++ scripts/bash/license_check.sh | 9 +- tests/license_check_test.sh | 131 +++++++++++++++++++++ 4 files changed, 159 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/tests.yml create mode 100755 tests/license_check_test.sh diff --git a/.github/workflows/plugin-license-check.yml b/.github/workflows/plugin-license-check.yml index 0a0e07e..cc9ccd2 100644 --- a/.github/workflows/plugin-license-check.yml +++ b/.github/workflows/plugin-license-check.yml @@ -33,14 +33,16 @@ jobs: with: repository: matomo-org/github-action-tests ref: ${{ inputs.script-ref }} - path: .github-action-tests + # Unique name so it cannot collide with a directory the caller tracks + path: .github-action-tests-${{ github.run_id }}-${{ github.run_attempt }} persist-credentials: false - name: Run license check env: FAIL_ON_MISSING_HEADER: ${{ inputs.fail-on-missing-header }} + SCRIPT_CHECKOUT: .github-action-tests-${{ github.run_id }}-${{ github.run_attempt }} run: | # Move the script out of the workspace so its own checkout # directory is not scanned as part of the plugin. - cp .github-action-tests/scripts/bash/license_check.sh "$RUNNER_TEMP/" - rm -rf .github-action-tests + cp "$SCRIPT_CHECKOUT/scripts/bash/license_check.sh" "$RUNNER_TEMP/" + rm -rf "$SCRIPT_CHECKOUT" bash "$RUNNER_TEMP/license_check.sh" . diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..f8e8bb5 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,17 @@ +name: Tests + +on: pull_request + +permissions: + contents: read + +jobs: + license-check-script: + name: License check script tests + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + with: + persist-credentials: false + - name: Run tests + run: bash tests/license_check_test.sh diff --git a/scripts/bash/license_check.sh b/scripts/bash/license_check.sh index 605bb42..1bd9957 100755 --- a/scripts/bash/license_check.sh +++ b/scripts/bash/license_check.sh @@ -14,9 +14,12 @@ set -euo pipefail REPO_ROOT="${1:-.}" FAIL_ON_MISSING_HEADER="${FAIL_ON_MISSING_HEADER:-0}" -PREMIUM_MARKER='Copyright (C) InnoCraft Ltd' +# Anchored to a comment line — bare or "*"-prefixed, covering both block and +# HTML comments — so marker text inside code or string literals does not count +# as a header. +PREMIUM_MARKER='^[[:space:]]*(\*[[:space:]]*)?Copyright \(C\) InnoCraft Ltd - All rights reserved\.' # Matches both the http and https URL variants in use across plugins. -OSS_MARKER='gnu\.org/licenses/gpl-3\.0' +OSS_MARKER='^[[:space:]]*(\*[[:space:]]*)?@license[[:space:]]+https?://(www\.)?gnu\.org/licenses/gpl-3\.0' # Only the start of a file counts as its header; a byte window rather than a # line count so minified single-line bundles are still covered. HEADER_WINDOW_BYTES=2048 @@ -114,7 +117,7 @@ while IFS= read -r -d '' file; do header=$(head -c "$HEADER_WINDOW_BYTES" -- "$file") has_premium=0 has_oss=0 - if grep -qF "$PREMIUM_MARKER" <<< "$header"; then has_premium=1; fi + if grep -qE "$PREMIUM_MARKER" <<< "$header"; then has_premium=1; fi if grep -qE "$OSS_MARKER" <<< "$header"; then has_oss=1; fi if [ "$repo_type" = premium ]; then diff --git a/tests/license_check_test.sh b/tests/license_check_test.sh new file mode 100755 index 0000000..64c6f9e --- /dev/null +++ b/tests/license_check_test.sh @@ -0,0 +1,131 @@ +#!/bin/bash +# Regression tests for scripts/bash/license_check.sh, run against throwaway +# fixture repositories. Usage: bash tests/license_check_test.sh +set -u + +SCRIPT="$(cd "$(dirname "$0")/.." && pwd)/scripts/bash/license_check.sh" +WORK=$(mktemp -d) +trap 'rm -rf "$WORK"' EXIT + +tests=0 +failures=0 + +OSS_HEADER=' "$dir/plugin.json" + echo 'Copyright InnoCraft Ltd license text' > "$dir/LICENSE" + printf '%s' "$PREMIUM_HEADER" > "$dir/Good.php" + echo "$dir" +} + +new_oss_repo() { + local dir="$WORK/$1" + mkdir -p "$dir" + echo '{"name":"Test","license":"GPL v3+"}' > "$dir/plugin.json" + echo 'GNU GENERAL PUBLIC LICENSE' > "$dir/LICENSE" + printf '%s' "$OSS_HEADER" > "$dir/Good.php" + echo "$dir" +} + +# check [ENV=value...] +check() { + local desc="$1" expected_exit="$2" expected_output="$3" dir="$4" + shift 4 + tests=$((tests + 1)) + local output actual + output=$(cd "$dir" && env "$@" bash "$SCRIPT" . 2>&1) + actual=$? + local ok=1 + [ "$actual" -eq "$expected_exit" ] || ok=0 + if [ -n "$expected_output" ] && ! grep -qF "$expected_output" <<< "$output"; then + ok=0 + fi + if [ "$ok" -eq 1 ]; then + echo "ok - $desc" + else + failures=$((failures + 1)) + echo "FAIL - $desc (exit $actual, expected $expected_exit)" + sed 's/^/ /' <<< "$output" + fi +} + +dir=$(new_premium_repo clean-premium) +check 'clean premium repository passes' 0 '0 error(s), 0 warning(s)' "$dir" + +dir=$(new_oss_repo clean-oss) +check 'clean OSS repository passes' 0 '0 error(s), 0 warning(s)' "$dir" + +dir=$(new_premium_repo premium-with-gpl) +printf '%s' "$OSS_HEADER" > "$dir/Wrong.php" +check 'GPL header in premium repository fails' 1 'carries a GPL header' "$dir" + +dir=$(new_oss_repo oss-with-eula) +printf '%s' "$PREMIUM_HEADER" > "$dir/Wrong.php" +check 'InnoCraft header in OSS repository fails' 1 'carries an InnoCraft EULA header' "$dir" + +dir=$(new_oss_repo marker-in-code) +{ + printf '%s' "$OSS_HEADER" + printf "\$notice = 'Copyright (C) InnoCraft Ltd - All rights reserved.';\n" +} > "$dir/Strings.php" +check 'marker text in a string literal is not a header' 0 '0 error(s)' "$dir" + +dir=$(new_premium_repo vue-html-comment) +printf '\n\n' > "$dir/Comp.vue" +check 'HTML-comment header in a .vue file is recognized' 0 '0 error(s), 0 warning(s)' "$dir" + +dir=$(new_premium_repo missing-header) +printf ' "$dir/NoHeader.php" +check 'missing header warns but passes by default' 0 '::warning file=NoHeader.php' "$dir" +check 'missing header fails with FAIL_ON_MISSING_HEADER=1' 1 '::error file=NoHeader.php' "$dir" FAIL_ON_MISSING_HEADER=1 +check 'missing header fails with FAIL_ON_MISSING_HEADER=true' 1 '::error file=NoHeader.php' "$dir" FAIL_ON_MISSING_HEADER=true + +dir=$(new_premium_repo ignored-file) +printf '%s' "$OSS_HEADER" > "$dir/ThirdParty.php" +echo 'ThirdParty.php' > "$dir/.license-check-ignore" +check 'ignore glob skips a listed file' 0 '0 error(s)' "$dir" + +dir=$(new_premium_repo pruned-vendor) +mkdir -p "$dir/vendor" "$dir/node_modules" +printf '%s' "$OSS_HEADER" > "$dir/vendor/dep.php" +printf '%s' "$OSS_HEADER" > "$dir/node_modules/dep.js" +check 'vendor and node_modules are not scanned' 0 'Checked 1 files' "$dir" + +dir=$(new_premium_repo no-license-file) +rm "$dir/LICENSE" +check 'missing LICENSE file fails' 1 'No LICENSE or LICENSE.md file found' "$dir" + +dir=$(new_premium_repo license-mismatch) +echo 'GNU GENERAL PUBLIC LICENSE' > "$dir/LICENSE" +check 'LICENSE contradicting plugin.json fails' 1 'does not mention InnoCraft' "$dir" + +dir="$WORK/unknown-license" +mkdir -p "$dir" +echo '{"name":"Test","license":"MIT"}' > "$dir/plugin.json" +check 'unrecognized license value fails' 1 'Unrecognized license "MIT"' "$dir" + +dir="$WORK/no-plugin-json" +mkdir -p "$dir" +check 'repository without plugin.json fails' 1 'No plugin.json found' "$dir" + +echo +echo "$tests tests, $failures failure(s)" +exit "$((failures > 0))" From 02257112120738b38aa89a9735598084b3774c61 Mon Sep 17 00:00:00 2001 From: Jacob Ransom Date: Wed, 5 Aug 2026 11:01:22 +1200 Subject: [PATCH 3/4] Match headers only in the leading comment block, catch contradictory LICENSE files --- scripts/bash/license_check.sh | 45 +++++++++++++++++++++++++++-------- tests/license_check_test.sh | 33 +++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 10 deletions(-) diff --git a/scripts/bash/license_check.sh b/scripts/bash/license_check.sh index 1bd9957..e68274f 100755 --- a/scripts/bash/license_check.sh +++ b/scripts/bash/license_check.sh @@ -14,12 +14,11 @@ set -euo pipefail REPO_ROOT="${1:-.}" FAIL_ON_MISSING_HEADER="${FAIL_ON_MISSING_HEADER:-0}" -# Anchored to a comment line — bare or "*"-prefixed, covering both block and -# HTML comments — so marker text inside code or string literals does not count -# as a header. -PREMIUM_MARKER='^[[:space:]]*(\*[[:space:]]*)?Copyright \(C\) InnoCraft Ltd - All rights reserved\.' +# Matched only against the leading comment block, so no line anchoring is +# needed; code and string literals never enter the searched region. +PREMIUM_MARKER='Copyright \(C\) InnoCraft Ltd - All rights reserved\.' # Matches both the http and https URL variants in use across plugins. -OSS_MARKER='^[[:space:]]*(\*[[:space:]]*)?@license[[:space:]]+https?://(www\.)?gnu\.org/licenses/gpl-3\.0' +OSS_MARKER='@license[[:space:]]+https?://(www\.)?gnu\.org/licenses/gpl-3\.0' # Only the start of a file counts as its header; a byte window rather than a # line count so minified single-line bundles are still covered. HEADER_WINDOW_BYTES=2048 @@ -44,6 +43,22 @@ report_missing_header() { esac } +# A header is the contiguous comment run at the top of the file — after an +# optional form. +# Anything from the first code line on is not a header. +extract_header_comments() { + sed '1s/^\xEF\xBB\xBF//' | awk ' + in_block == 1 { print; if (index($0, "*/")) in_block = 0; next } + in_block == 2 { print; if (index($0, "-->")) in_block = 0; next } + /^[[:space:]]*$/ { next } + /^<\?php[[:space:]]*$/ { next } + /^[[:space:]]*\/\// { print; next } + /^[[:space:]]*\/\*/ { print; if (!index(substr($0, index($0, "/*") + 2), "*/")) in_block = 1; next } + /^[[:space:]]*")) in_block = 2; next } + { exit } + ' +} + cd "$REPO_ROOT" if [ ! -f plugin.json ]; then @@ -78,10 +93,20 @@ done if [ -z "$license_file" ]; then report_error LICENSE 'No LICENSE or LICENSE.md file found at the repository root' -elif [ "$repo_type" = premium ] && ! grep -qi 'InnoCraft' "$license_file"; then - report_error "$license_file" 'plugin.json declares the InnoCraft EULA but the license file does not mention InnoCraft' -elif [ "$repo_type" = oss ] && ! grep -qi 'GNU GENERAL PUBLIC LICENSE' "$license_file"; then - report_error "$license_file" 'plugin.json declares a GPL license but the license file is not the GPL' +elif [ "$repo_type" = premium ]; then + if ! grep -qi 'InnoCraft' "$license_file"; then + report_error "$license_file" 'plugin.json declares the InnoCraft EULA but the license file does not mention InnoCraft' + elif grep -qi 'GNU GENERAL PUBLIC LICENSE' "$license_file"; then + report_error "$license_file" 'plugin.json declares the InnoCraft EULA but the license file contains the GPL' + fi +else + if ! grep -qi 'GNU GENERAL PUBLIC LICENSE' "$license_file"; then + report_error "$license_file" 'plugin.json declares a GPL license but the license file is not the GPL' + # "InnoCraft EULA" rather than "InnoCraft": a GPL license file may carry a + # legitimate InnoCraft copyright line. + elif grep -qi 'InnoCraft EULA' "$license_file"; then + report_error "$license_file" 'plugin.json declares a GPL license but the license file contains the InnoCraft EULA' + fi fi ignore_patterns=() @@ -114,7 +139,7 @@ while IFS= read -r -d '' file; do fi scanned=$((scanned + 1)) - header=$(head -c "$HEADER_WINDOW_BYTES" -- "$file") + header=$(head -c "$HEADER_WINDOW_BYTES" -- "$file" | extract_header_comments) has_premium=0 has_oss=0 if grep -qE "$PREMIUM_MARKER" <<< "$header"; then has_premium=1; fi diff --git a/tests/license_check_test.sh b/tests/license_check_test.sh index 64c6f9e..3d0383a 100755 --- a/tests/license_check_test.sh +++ b/tests/license_check_test.sh @@ -92,6 +92,27 @@ dir=$(new_premium_repo vue-html-comment) printf '\n\n' > "$dir/Comp.vue" check 'HTML-comment header in a .vue file is recognized' 0 '0 error(s), 0 warning(s)' "$dir" +dir=$(new_premium_repo single-line-comments) +printf '\n\n' > "$dir/Comp.vue" +printf '/* Copyright (C) InnoCraft Ltd - All rights reserved. */\nconsole.log(1);\n' > "$dir/one.js" +printf '// Copyright (C) InnoCraft Ltd - All rights reserved.\nconsole.log(1);\n' > "$dir/two.js" +check 'single-line comment headers are recognized' 0 '0 error(s), 0 warning(s)' "$dir" + +dir=$(new_premium_repo late-comment) +{ + printf ' "$dir/Late.php" +check 'marker in a comment after code is not a header' 0 '::warning file=Late.php' "$dir" + +dir=$(new_oss_repo late-wrong-comment) +{ + printf '%s' "$OSS_HEADER" + printf 'class Code {}\n' + printf '%s' "$PREMIUM_HEADER" | tail -n +2 +} > "$dir/LateWrong.php" +check 'opposite marker in a comment after code is not an error' 0 '0 error(s)' "$dir" + dir=$(new_premium_repo missing-header) printf ' "$dir/NoHeader.php" check 'missing header warns but passes by default' 0 '::warning file=NoHeader.php' "$dir" @@ -117,6 +138,18 @@ dir=$(new_premium_repo license-mismatch) echo 'GNU GENERAL PUBLIC LICENSE' > "$dir/LICENSE" check 'LICENSE contradicting plugin.json fails' 1 'does not mention InnoCraft' "$dir" +dir=$(new_premium_repo license-contradiction) +printf 'InnoCraft something\nGNU GENERAL PUBLIC LICENSE\n' > "$dir/LICENSE" +check 'premium LICENSE containing GPL text fails' 1 'license file contains the GPL' "$dir" + +dir=$(new_oss_repo oss-license-contradiction) +printf 'GNU GENERAL PUBLIC LICENSE\nInnoCraft EULA terms\n' > "$dir/LICENSE" +check 'OSS LICENSE containing the InnoCraft EULA fails' 1 'license file contains the InnoCraft EULA' "$dir" + +dir=$(new_oss_repo oss-license-copyright-line) +printf 'GNU GENERAL PUBLIC LICENSE\nCopyright (C) InnoCraft Ltd\n' > "$dir/LICENSE" +check 'InnoCraft copyright line in a GPL LICENSE is fine' 0 '0 error(s)' "$dir" + dir="$WORK/unknown-license" mkdir -p "$dir" echo '{"name":"Test","license":"MIT"}' > "$dir/plugin.json" From 5155e118c2ebdbae767e200bc1e11b66e5e9a42f Mon Sep 17 00:00:00 2001 From: Jacob Ransom Date: Thu, 6 Aug 2026 08:57:47 +1200 Subject: [PATCH 4/4] Document that script-ref pinning is the caller's job --- .github/workflows/plugin-license-check.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/plugin-license-check.yml b/.github/workflows/plugin-license-check.yml index cc9ccd2..7309497 100644 --- a/.github/workflows/plugin-license-check.yml +++ b/.github/workflows/plugin-license-check.yml @@ -9,7 +9,7 @@ on: type: boolean default: false script-ref: - description: "Ref of matomo-org/github-action-tests to take the check script from" + description: "Ref of matomo-org/github-action-tests to take the check script from. When pinning the workflow to a SHA, pass the same SHA here." required: false type: string default: 'main' @@ -28,6 +28,9 @@ jobs: with: lfs: false persist-credentials: false + # No GitHub context identifies the called workflow's own commit (verified + # empirically: github.job_workflow_sha evaluates to empty), so pinning the + # script is the caller's job via script-ref. - name: Check out the license check script uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 with: