From f34772040fbdf749c9c83e6860634aacbe65f886 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Fri, 12 Dec 2025 11:41:41 +0100 Subject: [PATCH 01/12] fix: handle architecture-specific license differences The licenses script now: - Generates separate license reports per GOOS/GOARCH combination - Groups identical reports together (comma-separated arch names) - Adds a Table of Contents at the top of each platform file - Handles cases where different architectures have different dependencies (e.g., x/sys/unix vs x/sys/windows, mousetrap on Windows only) This addresses the issue discovered in cli/cli where some deps changed which changed the mod graph for different GOARCH and affected the exported licenses because go-licenses tries to find common ancestors. --- script/licenses | 124 +++++++++++++++++++++++++++++--- third-party-licenses.darwin.md | 17 +++-- third-party-licenses.linux.md | 17 +++-- third-party-licenses.windows.md | 17 +++-- 4 files changed, 148 insertions(+), 27 deletions(-) diff --git a/script/licenses b/script/licenses index 4200316b9..f85499d38 100755 --- a/script/licenses +++ b/script/licenses @@ -1,4 +1,19 @@ #!/bin/bash +# +# Generate license files for all platform/arch combinations. +# This script handles architecture-specific dependency differences by: +# 1. Generating separate license reports per GOOS/GOARCH combination +# 2. Grouping identical reports together (comma-separated arch names) +# 3. Creating an index at the top of each platform file +# 4. Copying all license files to third-party/ +# +# Note: we ignore warnings because we want the command to succeed, however the output should be checked +# for any new warnings, and potentially we may need to add license information. +# +# Normally these warnings are packages containing non go code, which may or may not require explicit attribution, +# depending on the license. + +set -e go install github.com/google/go-licenses@latest @@ -8,14 +23,105 @@ export TEMPDIR="$(mktemp -d)" trap "rm -fr ${TEMPDIR}" EXIT -for goos in linux darwin windows ; do - # Note: we ignore warnings because we want the command to succeed, however the output should be checked - # for any new warnings, and potentially we may need to add license information. - # - # Normally these warnings are packages containing non go code, which may or may not require explicit attribution, - # depending on the license. - GOOS="${goos}" GOFLAGS=-mod=mod go-licenses save ./... --save_path="${TEMPDIR}/${goos}" --force || echo "Ignore warnings" - GOOS="${goos}" GOFLAGS=-mod=mod go-licenses report ./... --template .github/licenses.tmpl > third-party-licenses.${goos}.md || echo "Ignore warnings" - cp -fR "${TEMPDIR}/${goos}"/* third-party/ +# Define platforms and their architectures +declare -A PLATFORM_ARCHS +PLATFORM_ARCHS["linux"]="amd64 arm64 386" +PLATFORM_ARCHS["darwin"]="amd64 arm64" +PLATFORM_ARCHS["windows"]="amd64 arm64 386" + +# Generate reports for each platform/arch combination +for goos in linux darwin windows; do + echo "Processing ${goos}..." + + # Store reports per arch for this platform + declare -A ARCH_REPORTS + declare -A ARCH_HASHES + + for goarch in ${PLATFORM_ARCHS[$goos]}; do + echo " Generating for ${goos}/${goarch}..." + + # Generate the license report for this arch + report_file="${TEMPDIR}/${goos}_${goarch}_report.md" + GOOS="${goos}" GOARCH="${goarch}" GOFLAGS=-mod=mod go-licenses report ./... --template .github/licenses.tmpl > "${report_file}" 2>/dev/null || echo " (warnings ignored for ${goos}/${goarch})" + + # Save licenses to temp directory + GOOS="${goos}" GOARCH="${goarch}" GOFLAGS=-mod=mod go-licenses save ./... --save_path="${TEMPDIR}/${goos}_${goarch}" --force 2>/dev/null || echo " (warnings ignored for ${goos}/${goarch})" + + # Copy to third-party (accumulate all) + if [ -d "${TEMPDIR}/${goos}_${goarch}" ]; then + cp -fR "${TEMPDIR}/${goos}_${goarch}"/* third-party/ 2>/dev/null || true + fi + + # Extract just the package list (skip header) and hash it + packages=$(grep -E '^ - \[' "${report_file}" 2>/dev/null | sort || echo "") + hash=$(echo "${packages}" | md5sum | cut -d' ' -f1) + + ARCH_REPORTS["${goarch}"]="${packages}" + ARCH_HASHES["${goarch}"]="${hash}" + done + + # Group architectures with identical reports + declare -A HASH_TO_ARCHS + for goarch in ${PLATFORM_ARCHS[$goos]}; do + hash="${ARCH_HASHES[$goarch]}" + if [ -n "${HASH_TO_ARCHS[$hash]}" ]; then + HASH_TO_ARCHS["${hash}"]="${HASH_TO_ARCHS[$hash]}, ${goarch}" + else + HASH_TO_ARCHS["${hash}"]="${goarch}" + fi + done + + # Generate the combined report for this platform + output_file="third-party-licenses.${goos}.md" + + cat > "${output_file}" << 'EOF' +# GitHub MCP Server dependencies + +The following open source dependencies are used to build the [github/github-mcp-server][] GitHub Model Context Protocol Server. + +## Table of Contents + +EOF + + # Build table of contents + for hash in "${!HASH_TO_ARCHS[@]}"; do + archs="${HASH_TO_ARCHS[$hash]}" + # Create anchor-friendly name + anchor=$(echo "${archs}" | tr ', ' '-' | tr -s '-') + echo "- [${archs}](#${anchor})" >> "${output_file}" + done + + echo "" >> "${output_file}" + echo "---" >> "${output_file}" + echo "" >> "${output_file}" + + # Add each unique report section + for hash in "${!HASH_TO_ARCHS[@]}"; do + archs="${HASH_TO_ARCHS[$hash]}" + # Get the packages from the first arch in this group + first_arch=$(echo "${archs}" | cut -d',' -f1 | tr -d ' ') + packages="${ARCH_REPORTS[$first_arch]}" + + cat >> "${output_file}" << EOF +## ${archs} + +The following packages are included for the ${archs} architecture(s). + +${packages} + +EOF + done + + # Add footer + echo "[github/github-mcp-server]: https://github.com/github/github-mcp-server" >> "${output_file}" + + echo "Generated ${output_file}" + + # Clean up associative arrays for next platform + unset ARCH_REPORTS + unset ARCH_HASHES + unset HASH_TO_ARCHS done +echo "Done! License files generated." + diff --git a/third-party-licenses.darwin.md b/third-party-licenses.darwin.md index 32cdb5b6d..63484cfe2 100644 --- a/third-party-licenses.darwin.md +++ b/third-party-licenses.darwin.md @@ -2,23 +2,28 @@ The following open source dependencies are used to build the [github/github-mcp-server][] GitHub Model Context Protocol Server. -## Go Packages +## Table of Contents -Some packages may only be included on certain architectures or operating systems. +- [amd64, arm64](#amd64-arm64) +--- + +## amd64, arm64 + +The following packages are included for the amd64, arm64 architecture(s). - [github.com/aymerick/douceur](https://pkg.go.dev/github.com/aymerick/douceur) ([MIT](https://github.com/aymerick/douceur/blob/v0.2.0/LICENSE)) - [github.com/fsnotify/fsnotify](https://pkg.go.dev/github.com/fsnotify/fsnotify) ([BSD-3-Clause](https://github.com/fsnotify/fsnotify/blob/v1.9.0/LICENSE)) - [github.com/github/github-mcp-server](https://pkg.go.dev/github.com/github/github-mcp-server) ([MIT](https://github.com/github/github-mcp-server/blob/HEAD/LICENSE)) - - [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.19.5/LICENSE)) - - [github.com/go-openapi/swag](https://pkg.go.dev/github.com/go-openapi/swag) ([Apache-2.0](https://github.com/go-openapi/swag/blob/v0.21.1/LICENSE)) - - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.4.0/LICENSE)) - [github.com/google/go-github/v71/github](https://pkg.go.dev/github.com/google/go-github/v71/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v71.0.0/LICENSE)) - [github.com/google/go-github/v79/github](https://pkg.go.dev/github.com/google/go-github/v79/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v79.0.0/LICENSE)) - [github.com/google/go-querystring/query](https://pkg.go.dev/github.com/google/go-querystring/query) ([BSD-3-Clause](https://github.com/google/go-querystring/blob/v1.1.0/LICENSE)) - [github.com/google/jsonschema-go/jsonschema](https://pkg.go.dev/github.com/google/jsonschema-go/jsonschema) ([MIT](https://github.com/google/jsonschema-go/blob/v0.3.0/LICENSE)) + - [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.19.5/LICENSE)) + - [github.com/go-openapi/swag](https://pkg.go.dev/github.com/go-openapi/swag) ([Apache-2.0](https://github.com/go-openapi/swag/blob/v0.21.1/LICENSE)) - [github.com/gorilla/css/scanner](https://pkg.go.dev/github.com/gorilla/css/scanner) ([BSD-3-Clause](https://github.com/gorilla/css/blob/v1.0.1/LICENSE)) - [github.com/gorilla/mux](https://pkg.go.dev/github.com/gorilla/mux) ([BSD-3-Clause](https://github.com/gorilla/mux/blob/v1.8.0/LICENSE)) + - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.4.0/LICENSE)) - [github.com/josephburnett/jd/v2](https://pkg.go.dev/github.com/josephburnett/jd/v2) ([MIT](https://github.com/josephburnett/jd/blob/v1.9.2/LICENSE)) - [github.com/josharian/intern](https://pkg.go.dev/github.com/josharian/intern) ([MIT](https://github.com/josharian/intern/blob/v1.0.0/license.md)) - [github.com/mailru/easyjson](https://pkg.go.dev/github.com/mailru/easyjson) ([MIT](https://github.com/mailru/easyjson/blob/v0.7.7/LICENSE)) @@ -39,12 +44,12 @@ Some packages may only be included on certain architectures or operating systems - [github.com/subosito/gotenv](https://pkg.go.dev/github.com/subosito/gotenv) ([MIT](https://github.com/subosito/gotenv/blob/v1.6.0/LICENSE)) - [github.com/yosida95/uritemplate/v3](https://pkg.go.dev/github.com/yosida95/uritemplate/v3) ([BSD-3-Clause](https://github.com/yosida95/uritemplate/blob/v3.0.2/LICENSE)) - [github.com/yudai/golcs](https://pkg.go.dev/github.com/yudai/golcs) ([MIT](https://github.com/yudai/golcs/blob/ecda9a501e82/LICENSE)) - - [go.yaml.in/yaml/v3](https://pkg.go.dev/go.yaml.in/yaml/v3) ([MIT](https://github.com/yaml/go-yaml/blob/v3.0.4/LICENSE)) - [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/8a7402ab:LICENSE)) - [golang.org/x/net/html](https://pkg.go.dev/golang.org/x/net/html) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.38.0:LICENSE)) - [golang.org/x/sys/unix](https://pkg.go.dev/golang.org/x/sys/unix) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.31.0:LICENSE)) - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.28.0:LICENSE)) - [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.5.0:LICENSE)) - [gopkg.in/yaml.v2](https://pkg.go.dev/gopkg.in/yaml.v2) ([Apache-2.0](https://github.com/go-yaml/yaml/blob/v2.4.0/LICENSE)) + - [go.yaml.in/yaml/v3](https://pkg.go.dev/go.yaml.in/yaml/v3) ([MIT](https://github.com/yaml/go-yaml/blob/v3.0.4/LICENSE)) [github/github-mcp-server]: https://github.com/github/github-mcp-server diff --git a/third-party-licenses.linux.md b/third-party-licenses.linux.md index 32cdb5b6d..f018e35f4 100644 --- a/third-party-licenses.linux.md +++ b/third-party-licenses.linux.md @@ -2,23 +2,28 @@ The following open source dependencies are used to build the [github/github-mcp-server][] GitHub Model Context Protocol Server. -## Go Packages +## Table of Contents -Some packages may only be included on certain architectures or operating systems. +- [amd64, arm64, 386](#amd64-arm64-386) +--- + +## amd64, arm64, 386 + +The following packages are included for the amd64, arm64, 386 architecture(s). - [github.com/aymerick/douceur](https://pkg.go.dev/github.com/aymerick/douceur) ([MIT](https://github.com/aymerick/douceur/blob/v0.2.0/LICENSE)) - [github.com/fsnotify/fsnotify](https://pkg.go.dev/github.com/fsnotify/fsnotify) ([BSD-3-Clause](https://github.com/fsnotify/fsnotify/blob/v1.9.0/LICENSE)) - [github.com/github/github-mcp-server](https://pkg.go.dev/github.com/github/github-mcp-server) ([MIT](https://github.com/github/github-mcp-server/blob/HEAD/LICENSE)) - - [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.19.5/LICENSE)) - - [github.com/go-openapi/swag](https://pkg.go.dev/github.com/go-openapi/swag) ([Apache-2.0](https://github.com/go-openapi/swag/blob/v0.21.1/LICENSE)) - - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.4.0/LICENSE)) - [github.com/google/go-github/v71/github](https://pkg.go.dev/github.com/google/go-github/v71/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v71.0.0/LICENSE)) - [github.com/google/go-github/v79/github](https://pkg.go.dev/github.com/google/go-github/v79/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v79.0.0/LICENSE)) - [github.com/google/go-querystring/query](https://pkg.go.dev/github.com/google/go-querystring/query) ([BSD-3-Clause](https://github.com/google/go-querystring/blob/v1.1.0/LICENSE)) - [github.com/google/jsonschema-go/jsonschema](https://pkg.go.dev/github.com/google/jsonschema-go/jsonschema) ([MIT](https://github.com/google/jsonschema-go/blob/v0.3.0/LICENSE)) + - [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.19.5/LICENSE)) + - [github.com/go-openapi/swag](https://pkg.go.dev/github.com/go-openapi/swag) ([Apache-2.0](https://github.com/go-openapi/swag/blob/v0.21.1/LICENSE)) - [github.com/gorilla/css/scanner](https://pkg.go.dev/github.com/gorilla/css/scanner) ([BSD-3-Clause](https://github.com/gorilla/css/blob/v1.0.1/LICENSE)) - [github.com/gorilla/mux](https://pkg.go.dev/github.com/gorilla/mux) ([BSD-3-Clause](https://github.com/gorilla/mux/blob/v1.8.0/LICENSE)) + - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.4.0/LICENSE)) - [github.com/josephburnett/jd/v2](https://pkg.go.dev/github.com/josephburnett/jd/v2) ([MIT](https://github.com/josephburnett/jd/blob/v1.9.2/LICENSE)) - [github.com/josharian/intern](https://pkg.go.dev/github.com/josharian/intern) ([MIT](https://github.com/josharian/intern/blob/v1.0.0/license.md)) - [github.com/mailru/easyjson](https://pkg.go.dev/github.com/mailru/easyjson) ([MIT](https://github.com/mailru/easyjson/blob/v0.7.7/LICENSE)) @@ -39,12 +44,12 @@ Some packages may only be included on certain architectures or operating systems - [github.com/subosito/gotenv](https://pkg.go.dev/github.com/subosito/gotenv) ([MIT](https://github.com/subosito/gotenv/blob/v1.6.0/LICENSE)) - [github.com/yosida95/uritemplate/v3](https://pkg.go.dev/github.com/yosida95/uritemplate/v3) ([BSD-3-Clause](https://github.com/yosida95/uritemplate/blob/v3.0.2/LICENSE)) - [github.com/yudai/golcs](https://pkg.go.dev/github.com/yudai/golcs) ([MIT](https://github.com/yudai/golcs/blob/ecda9a501e82/LICENSE)) - - [go.yaml.in/yaml/v3](https://pkg.go.dev/go.yaml.in/yaml/v3) ([MIT](https://github.com/yaml/go-yaml/blob/v3.0.4/LICENSE)) - [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/8a7402ab:LICENSE)) - [golang.org/x/net/html](https://pkg.go.dev/golang.org/x/net/html) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.38.0:LICENSE)) - [golang.org/x/sys/unix](https://pkg.go.dev/golang.org/x/sys/unix) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.31.0:LICENSE)) - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.28.0:LICENSE)) - [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.5.0:LICENSE)) - [gopkg.in/yaml.v2](https://pkg.go.dev/gopkg.in/yaml.v2) ([Apache-2.0](https://github.com/go-yaml/yaml/blob/v2.4.0/LICENSE)) + - [go.yaml.in/yaml/v3](https://pkg.go.dev/go.yaml.in/yaml/v3) ([MIT](https://github.com/yaml/go-yaml/blob/v3.0.4/LICENSE)) [github/github-mcp-server]: https://github.com/github/github-mcp-server diff --git a/third-party-licenses.windows.md b/third-party-licenses.windows.md index c7e00fb13..ba3252d1b 100644 --- a/third-party-licenses.windows.md +++ b/third-party-licenses.windows.md @@ -2,23 +2,28 @@ The following open source dependencies are used to build the [github/github-mcp-server][] GitHub Model Context Protocol Server. -## Go Packages +## Table of Contents -Some packages may only be included on certain architectures or operating systems. +- [amd64, arm64, 386](#amd64-arm64-386) +--- + +## amd64, arm64, 386 + +The following packages are included for the amd64, arm64, 386 architecture(s). - [github.com/aymerick/douceur](https://pkg.go.dev/github.com/aymerick/douceur) ([MIT](https://github.com/aymerick/douceur/blob/v0.2.0/LICENSE)) - [github.com/fsnotify/fsnotify](https://pkg.go.dev/github.com/fsnotify/fsnotify) ([BSD-3-Clause](https://github.com/fsnotify/fsnotify/blob/v1.9.0/LICENSE)) - [github.com/github/github-mcp-server](https://pkg.go.dev/github.com/github/github-mcp-server) ([MIT](https://github.com/github/github-mcp-server/blob/HEAD/LICENSE)) - - [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.19.5/LICENSE)) - - [github.com/go-openapi/swag](https://pkg.go.dev/github.com/go-openapi/swag) ([Apache-2.0](https://github.com/go-openapi/swag/blob/v0.21.1/LICENSE)) - - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.4.0/LICENSE)) - [github.com/google/go-github/v71/github](https://pkg.go.dev/github.com/google/go-github/v71/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v71.0.0/LICENSE)) - [github.com/google/go-github/v79/github](https://pkg.go.dev/github.com/google/go-github/v79/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v79.0.0/LICENSE)) - [github.com/google/go-querystring/query](https://pkg.go.dev/github.com/google/go-querystring/query) ([BSD-3-Clause](https://github.com/google/go-querystring/blob/v1.1.0/LICENSE)) - [github.com/google/jsonschema-go/jsonschema](https://pkg.go.dev/github.com/google/jsonschema-go/jsonschema) ([MIT](https://github.com/google/jsonschema-go/blob/v0.3.0/LICENSE)) + - [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.19.5/LICENSE)) + - [github.com/go-openapi/swag](https://pkg.go.dev/github.com/go-openapi/swag) ([Apache-2.0](https://github.com/go-openapi/swag/blob/v0.21.1/LICENSE)) - [github.com/gorilla/css/scanner](https://pkg.go.dev/github.com/gorilla/css/scanner) ([BSD-3-Clause](https://github.com/gorilla/css/blob/v1.0.1/LICENSE)) - [github.com/gorilla/mux](https://pkg.go.dev/github.com/gorilla/mux) ([BSD-3-Clause](https://github.com/gorilla/mux/blob/v1.8.0/LICENSE)) + - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.4.0/LICENSE)) - [github.com/inconshreveable/mousetrap](https://pkg.go.dev/github.com/inconshreveable/mousetrap) ([Apache-2.0](https://github.com/inconshreveable/mousetrap/blob/v1.1.0/LICENSE)) - [github.com/josephburnett/jd/v2](https://pkg.go.dev/github.com/josephburnett/jd/v2) ([MIT](https://github.com/josephburnett/jd/blob/v1.9.2/LICENSE)) - [github.com/josharian/intern](https://pkg.go.dev/github.com/josharian/intern) ([MIT](https://github.com/josharian/intern/blob/v1.0.0/license.md)) @@ -40,12 +45,12 @@ Some packages may only be included on certain architectures or operating systems - [github.com/subosito/gotenv](https://pkg.go.dev/github.com/subosito/gotenv) ([MIT](https://github.com/subosito/gotenv/blob/v1.6.0/LICENSE)) - [github.com/yosida95/uritemplate/v3](https://pkg.go.dev/github.com/yosida95/uritemplate/v3) ([BSD-3-Clause](https://github.com/yosida95/uritemplate/blob/v3.0.2/LICENSE)) - [github.com/yudai/golcs](https://pkg.go.dev/github.com/yudai/golcs) ([MIT](https://github.com/yudai/golcs/blob/ecda9a501e82/LICENSE)) - - [go.yaml.in/yaml/v3](https://pkg.go.dev/go.yaml.in/yaml/v3) ([MIT](https://github.com/yaml/go-yaml/blob/v3.0.4/LICENSE)) - [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/8a7402ab:LICENSE)) - [golang.org/x/net/html](https://pkg.go.dev/golang.org/x/net/html) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.38.0:LICENSE)) - [golang.org/x/sys/windows](https://pkg.go.dev/golang.org/x/sys/windows) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.31.0:LICENSE)) - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.28.0:LICENSE)) - [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.5.0:LICENSE)) - [gopkg.in/yaml.v2](https://pkg.go.dev/gopkg.in/yaml.v2) ([Apache-2.0](https://github.com/go-yaml/yaml/blob/v2.4.0/LICENSE)) + - [go.yaml.in/yaml/v3](https://pkg.go.dev/go.yaml.in/yaml/v3) ([MIT](https://github.com/yaml/go-yaml/blob/v3.0.4/LICENSE)) [github/github-mcp-server]: https://github.com/github/github-mcp-server From ea9a04daa1aeadf0791232e4972dc20308302a13 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Fri, 12 Dec 2025 12:09:09 +0100 Subject: [PATCH 02/12] fix: make license script portable and deterministic Address review feedback: - Remove bash 4.0+ associative array requirement for macOS compatibility - Add cross-platform hash function (md5sum on Linux, md5 on macOS) - Ensure deterministic iteration order using sorted groups file - Add better error handling for failed go-licenses commands - Fix grammar: 'architecture(s)' -> 'architectures' - Add documentation for third-party/ being a union of all architectures - Use file-based state instead of associative arrays for portability --- script/licenses | 105 ++++++++++++++++++++------------ third-party-licenses.darwin.md | 2 +- third-party-licenses.linux.md | 6 +- third-party-licenses.windows.md | 6 +- 4 files changed, 72 insertions(+), 47 deletions(-) diff --git a/script/licenses b/script/licenses index f85499d38..ebc97c86d 100755 --- a/script/licenses +++ b/script/licenses @@ -7,6 +7,10 @@ # 3. Creating an index at the top of each platform file # 4. Copying all license files to third-party/ # +# Note: third-party/ is a union of all license files across all architectures. +# This means that license files for dependencies present in only some architectures +# may still appear in third-party/. This is intentional and ensures compliance. +# # Note: we ignore warnings because we want the command to succeed, however the output should be checked # for any new warnings, and potentially we may need to add license information. # @@ -23,21 +27,34 @@ export TEMPDIR="$(mktemp -d)" trap "rm -fr ${TEMPDIR}" EXIT -# Define platforms and their architectures -declare -A PLATFORM_ARCHS -PLATFORM_ARCHS["linux"]="amd64 arm64 386" -PLATFORM_ARCHS["darwin"]="amd64 arm64" -PLATFORM_ARCHS["windows"]="amd64 arm64 386" +# Cross-platform hash function (works on both Linux and macOS) +compute_hash() { + if command -v md5sum >/dev/null 2>&1; then + md5sum | cut -d' ' -f1 + elif command -v md5 >/dev/null 2>&1; then + md5 -q + else + # Fallback to cksum if neither is available + cksum | cut -d' ' -f1 + fi +} + +# Function to get architectures for a given OS +get_archs() { + case "$1" in + linux) echo "386 amd64 arm64" ;; + darwin) echo "amd64 arm64" ;; + windows) echo "386 amd64 arm64" ;; + esac +} # Generate reports for each platform/arch combination -for goos in linux darwin windows; do +for goos in darwin linux windows; do echo "Processing ${goos}..." - # Store reports per arch for this platform - declare -A ARCH_REPORTS - declare -A ARCH_HASHES + archs=$(get_archs "$goos") - for goarch in ${PLATFORM_ARCHS[$goos]}; do + for goarch in $archs; do echo " Generating for ${goos}/${goarch}..." # Generate the license report for this arch @@ -47,27 +64,42 @@ for goos in linux darwin windows; do # Save licenses to temp directory GOOS="${goos}" GOARCH="${goarch}" GOFLAGS=-mod=mod go-licenses save ./... --save_path="${TEMPDIR}/${goos}_${goarch}" --force 2>/dev/null || echo " (warnings ignored for ${goos}/${goarch})" - # Copy to third-party (accumulate all) + # Copy to third-party (accumulate all - union of all architectures for compliance) if [ -d "${TEMPDIR}/${goos}_${goarch}" ]; then cp -fR "${TEMPDIR}/${goos}_${goarch}"/* third-party/ 2>/dev/null || true fi - # Extract just the package list (skip header) and hash it - packages=$(grep -E '^ - \[' "${report_file}" 2>/dev/null | sort || echo "") - hash=$(echo "${packages}" | md5sum | cut -d' ' -f1) + # Extract just the package list (skip header), sort it, and hash it + packages_file="${TEMPDIR}/${goos}_${goarch}_packages.txt" + if [ -s "${report_file}" ] && grep -qE '^ - \[' "${report_file}" 2>/dev/null; then + grep -E '^ - \[' "${report_file}" | sort > "${packages_file}" + hash=$(cat "${packages_file}" | compute_hash) + else + echo "(FAILED TO GENERATE LICENSE REPORT FOR ${goos}/${goarch})" > "${packages_file}" + hash="FAILED_${goos}_${goarch}" + fi - ARCH_REPORTS["${goarch}"]="${packages}" - ARCH_HASHES["${goarch}"]="${hash}" + # Store hash for grouping + echo "${hash}" > "${TEMPDIR}/${goos}_${goarch}_hash.txt" done - # Group architectures with identical reports - declare -A HASH_TO_ARCHS - for goarch in ${PLATFORM_ARCHS[$goos]}; do - hash="${ARCH_HASHES[$goarch]}" - if [ -n "${HASH_TO_ARCHS[$hash]}" ]; then - HASH_TO_ARCHS["${hash}"]="${HASH_TO_ARCHS[$hash]}, ${goarch}" + # Group architectures with identical reports (deterministic order) + # Create groups file: hash -> comma-separated archs + groups_file="${TEMPDIR}/${goos}_groups.txt" + rm -f "${groups_file}" + + # Process architectures in order to build groups + for goarch in $archs; do + hash=$(cat "${TEMPDIR}/${goos}_${goarch}_hash.txt") + # Check if we've seen this hash before + if grep -q "^${hash}:" "${groups_file}" 2>/dev/null; then + # Append to existing group + existing=$(grep "^${hash}:" "${groups_file}" | cut -d: -f2) + sed -i.bak "s/^${hash}:.*/${hash}:${existing}, ${goarch}/" "${groups_file}" + rm -f "${groups_file}.bak" else - HASH_TO_ARCHS["${hash}"]="${goarch}" + # New group + echo "${hash}:${goarch}" >> "${groups_file}" fi done @@ -83,29 +115,27 @@ The following open source dependencies are used to build the [github/github-mcp- EOF - # Build table of contents - for hash in "${!HASH_TO_ARCHS[@]}"; do - archs="${HASH_TO_ARCHS[$hash]}" + # Build table of contents (sorted for determinism) + sort "${groups_file}" | while IFS=: read -r hash group_archs; do # Create anchor-friendly name - anchor=$(echo "${archs}" | tr ', ' '-' | tr -s '-') - echo "- [${archs}](#${anchor})" >> "${output_file}" + anchor=$(echo "${group_archs}" | tr ', ' '-' | tr -s '-') + echo "- [${group_archs}](#${anchor})" >> "${output_file}" done echo "" >> "${output_file}" echo "---" >> "${output_file}" echo "" >> "${output_file}" - # Add each unique report section - for hash in "${!HASH_TO_ARCHS[@]}"; do - archs="${HASH_TO_ARCHS[$hash]}" + # Add each unique report section (sorted for determinism) + sort "${groups_file}" | while IFS=: read -r hash group_archs; do # Get the packages from the first arch in this group - first_arch=$(echo "${archs}" | cut -d',' -f1 | tr -d ' ') - packages="${ARCH_REPORTS[$first_arch]}" + first_arch=$(echo "${group_archs}" | cut -d',' -f1 | tr -d ' ') + packages=$(cat "${TEMPDIR}/${goos}_${first_arch}_packages.txt") cat >> "${output_file}" << EOF -## ${archs} +## ${group_archs} -The following packages are included for the ${archs} architecture(s). +The following packages are included for the ${group_archs} architectures. ${packages} @@ -116,11 +146,6 @@ EOF echo "[github/github-mcp-server]: https://github.com/github/github-mcp-server" >> "${output_file}" echo "Generated ${output_file}" - - # Clean up associative arrays for next platform - unset ARCH_REPORTS - unset ARCH_HASHES - unset HASH_TO_ARCHS done echo "Done! License files generated." diff --git a/third-party-licenses.darwin.md b/third-party-licenses.darwin.md index 63484cfe2..dfb590f25 100644 --- a/third-party-licenses.darwin.md +++ b/third-party-licenses.darwin.md @@ -10,7 +10,7 @@ The following open source dependencies are used to build the [github/github-mcp- ## amd64, arm64 -The following packages are included for the amd64, arm64 architecture(s). +The following packages are included for the amd64, arm64 architectures. - [github.com/aymerick/douceur](https://pkg.go.dev/github.com/aymerick/douceur) ([MIT](https://github.com/aymerick/douceur/blob/v0.2.0/LICENSE)) - [github.com/fsnotify/fsnotify](https://pkg.go.dev/github.com/fsnotify/fsnotify) ([BSD-3-Clause](https://github.com/fsnotify/fsnotify/blob/v1.9.0/LICENSE)) diff --git a/third-party-licenses.linux.md b/third-party-licenses.linux.md index f018e35f4..c947e158c 100644 --- a/third-party-licenses.linux.md +++ b/third-party-licenses.linux.md @@ -4,13 +4,13 @@ The following open source dependencies are used to build the [github/github-mcp- ## Table of Contents -- [amd64, arm64, 386](#amd64-arm64-386) +- [386, amd64, arm64](#386-amd64-arm64) --- -## amd64, arm64, 386 +## 386, amd64, arm64 -The following packages are included for the amd64, arm64, 386 architecture(s). +The following packages are included for the 386, amd64, arm64 architectures. - [github.com/aymerick/douceur](https://pkg.go.dev/github.com/aymerick/douceur) ([MIT](https://github.com/aymerick/douceur/blob/v0.2.0/LICENSE)) - [github.com/fsnotify/fsnotify](https://pkg.go.dev/github.com/fsnotify/fsnotify) ([BSD-3-Clause](https://github.com/fsnotify/fsnotify/blob/v1.9.0/LICENSE)) diff --git a/third-party-licenses.windows.md b/third-party-licenses.windows.md index ba3252d1b..29e107a5b 100644 --- a/third-party-licenses.windows.md +++ b/third-party-licenses.windows.md @@ -4,13 +4,13 @@ The following open source dependencies are used to build the [github/github-mcp- ## Table of Contents -- [amd64, arm64, 386](#amd64-arm64-386) +- [386, amd64, arm64](#386-amd64-arm64) --- -## amd64, arm64, 386 +## 386, amd64, arm64 -The following packages are included for the amd64, arm64, 386 architecture(s). +The following packages are included for the 386, amd64, arm64 architectures. - [github.com/aymerick/douceur](https://pkg.go.dev/github.com/aymerick/douceur) ([MIT](https://github.com/aymerick/douceur/blob/v0.2.0/LICENSE)) - [github.com/fsnotify/fsnotify](https://pkg.go.dev/github.com/fsnotify/fsnotify) ([BSD-3-Clause](https://github.com/fsnotify/fsnotify/blob/v1.9.0/LICENSE)) From eb7d73c50562a7be630250c1eb1ef8053dfa0610 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Fri, 12 Dec 2025 12:31:24 +0100 Subject: [PATCH 03/12] fix: update licenses-check to use new architecture-aware format - Check now regenerates using ./script/licenses and compares - Add GOROOT/PATH setup in CI to fix go-licenses module info errors - Check both license files AND third-party directory for changes - See: https://github.com/google/go-licenses/issues/244 --- script/licenses | 8 ++++++++ script/licenses-check | 43 ++++++++++++++++++++++++++++--------------- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/script/licenses b/script/licenses index ebc97c86d..062e8e283 100755 --- a/script/licenses +++ b/script/licenses @@ -21,6 +21,14 @@ set -e go install github.com/google/go-licenses@latest +# actions/setup-go does not setup the installed toolchain to be preferred over the system install, +# which causes go-licenses to raise "Package ... does not have module info" errors in CI. +# For more information, https://github.com/google/go-licenses/issues/244#issuecomment-1885098633 +if [ "$CI" = "true" ]; then + export GOROOT=$(go env GOROOT) + export PATH=${GOROOT}/bin:$PATH +fi + rm -rf third-party mkdir -p third-party export TEMPDIR="$(mktemp -d)" diff --git a/script/licenses-check b/script/licenses-check index 67b567d02..430c8170b 100755 --- a/script/licenses-check +++ b/script/licenses-check @@ -1,21 +1,34 @@ #!/bin/bash +# +# Check that license files are up to date. +# This script regenerates the license files and compares them with the committed versions. +# If there are differences, it exits with an error. -go install github.com/google/go-licenses@latest - -for goos in linux darwin windows ; do - # Note: we ignore warnings because we want the command to succeed, however the output should be checked - # for any new warnings, and potentially we may need to add license information. - # - # Normally these warnings are packages containing non go code, which may or may not require explicit attribution, - # depending on the license. - GOOS="${goos}" GOFLAGS=-mod=mod go-licenses report ./... --template .github/licenses.tmpl > third-party-licenses.${goos}.copy.md || echo "Ignore warnings" - if ! diff -s third-party-licenses.${goos}.copy.md third-party-licenses.${goos}.md; then - printf "License check failed.\n\nPlease update the license file by running \`.script/licenses\` and committing the output." - rm -f third-party-licenses.${goos}.copy.md - exit 1 - fi - rm -f third-party-licenses.${goos}.copy.md +set -e + +# Store original files for comparison +TEMPDIR="$(mktemp -d)" +trap "rm -fr ${TEMPDIR}" EXIT + +# Save original license markdown files +for goos in darwin linux windows; do + cp "third-party-licenses.${goos}.md" "${TEMPDIR}/" done +# Save the state of third-party directory +cp -r third-party "${TEMPDIR}/third-party.orig" + +# Regenerate using the same script +./script/licenses + +# Check for any differences in workspace +if ! git diff --exit-code --quiet third-party-licenses.*.md third-party/; then + echo "License files are out of date:" + git diff third-party-licenses.*.md third-party/ + echo "" + printf "\nLicense check failed.\n\nPlease update the license files by running \`./script/licenses\` and committing the output.\n" + exit 1 +fi +echo "License check passed for all platforms." From ea7324feb4ae3f03f1a55ed97368b1aacd174252 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Fri, 12 Dec 2025 14:47:54 +0100 Subject: [PATCH 04/12] fix: use LC_ALL=C for consistent sorting across systems The sort command uses locale-specific ordering which can differ between systems. Use LC_ALL=C to ensure consistent ordering in CI and locally. --- script/licenses | 9 ++++++--- third-party-licenses.darwin.md | 8 ++++---- third-party-licenses.linux.md | 8 ++++---- third-party-licenses.windows.md | 8 ++++---- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/script/licenses b/script/licenses index 062e8e283..214efa435 100755 --- a/script/licenses +++ b/script/licenses @@ -78,9 +78,10 @@ for goos in darwin linux windows; do fi # Extract just the package list (skip header), sort it, and hash it + # Use LC_ALL=C for consistent sorting across different systems packages_file="${TEMPDIR}/${goos}_${goarch}_packages.txt" if [ -s "${report_file}" ] && grep -qE '^ - \[' "${report_file}" 2>/dev/null; then - grep -E '^ - \[' "${report_file}" | sort > "${packages_file}" + grep -E '^ - \[' "${report_file}" | LC_ALL=C sort > "${packages_file}" hash=$(cat "${packages_file}" | compute_hash) else echo "(FAILED TO GENERATE LICENSE REPORT FOR ${goos}/${goarch})" > "${packages_file}" @@ -124,7 +125,8 @@ The following open source dependencies are used to build the [github/github-mcp- EOF # Build table of contents (sorted for determinism) - sort "${groups_file}" | while IFS=: read -r hash group_archs; do + # Use LC_ALL=C for consistent sorting across different systems + LC_ALL=C sort "${groups_file}" | while IFS=: read -r hash group_archs; do # Create anchor-friendly name anchor=$(echo "${group_archs}" | tr ', ' '-' | tr -s '-') echo "- [${group_archs}](#${anchor})" >> "${output_file}" @@ -135,7 +137,8 @@ EOF echo "" >> "${output_file}" # Add each unique report section (sorted for determinism) - sort "${groups_file}" | while IFS=: read -r hash group_archs; do + # Use LC_ALL=C for consistent sorting across different systems + LC_ALL=C sort "${groups_file}" | while IFS=: read -r hash group_archs; do # Get the packages from the first arch in this group first_arch=$(echo "${group_archs}" | cut -d',' -f1 | tr -d ' ') packages=$(cat "${TEMPDIR}/${goos}_${first_arch}_packages.txt") diff --git a/third-party-licenses.darwin.md b/third-party-licenses.darwin.md index dfb590f25..ef8816689 100644 --- a/third-party-licenses.darwin.md +++ b/third-party-licenses.darwin.md @@ -15,15 +15,15 @@ The following packages are included for the amd64, arm64 architectures. - [github.com/aymerick/douceur](https://pkg.go.dev/github.com/aymerick/douceur) ([MIT](https://github.com/aymerick/douceur/blob/v0.2.0/LICENSE)) - [github.com/fsnotify/fsnotify](https://pkg.go.dev/github.com/fsnotify/fsnotify) ([BSD-3-Clause](https://github.com/fsnotify/fsnotify/blob/v1.9.0/LICENSE)) - [github.com/github/github-mcp-server](https://pkg.go.dev/github.com/github/github-mcp-server) ([MIT](https://github.com/github/github-mcp-server/blob/HEAD/LICENSE)) + - [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.19.5/LICENSE)) + - [github.com/go-openapi/swag](https://pkg.go.dev/github.com/go-openapi/swag) ([Apache-2.0](https://github.com/go-openapi/swag/blob/v0.21.1/LICENSE)) + - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.4.0/LICENSE)) - [github.com/google/go-github/v71/github](https://pkg.go.dev/github.com/google/go-github/v71/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v71.0.0/LICENSE)) - [github.com/google/go-github/v79/github](https://pkg.go.dev/github.com/google/go-github/v79/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v79.0.0/LICENSE)) - [github.com/google/go-querystring/query](https://pkg.go.dev/github.com/google/go-querystring/query) ([BSD-3-Clause](https://github.com/google/go-querystring/blob/v1.1.0/LICENSE)) - [github.com/google/jsonschema-go/jsonschema](https://pkg.go.dev/github.com/google/jsonschema-go/jsonschema) ([MIT](https://github.com/google/jsonschema-go/blob/v0.3.0/LICENSE)) - - [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.19.5/LICENSE)) - - [github.com/go-openapi/swag](https://pkg.go.dev/github.com/go-openapi/swag) ([Apache-2.0](https://github.com/go-openapi/swag/blob/v0.21.1/LICENSE)) - [github.com/gorilla/css/scanner](https://pkg.go.dev/github.com/gorilla/css/scanner) ([BSD-3-Clause](https://github.com/gorilla/css/blob/v1.0.1/LICENSE)) - [github.com/gorilla/mux](https://pkg.go.dev/github.com/gorilla/mux) ([BSD-3-Clause](https://github.com/gorilla/mux/blob/v1.8.0/LICENSE)) - - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.4.0/LICENSE)) - [github.com/josephburnett/jd/v2](https://pkg.go.dev/github.com/josephburnett/jd/v2) ([MIT](https://github.com/josephburnett/jd/blob/v1.9.2/LICENSE)) - [github.com/josharian/intern](https://pkg.go.dev/github.com/josharian/intern) ([MIT](https://github.com/josharian/intern/blob/v1.0.0/license.md)) - [github.com/mailru/easyjson](https://pkg.go.dev/github.com/mailru/easyjson) ([MIT](https://github.com/mailru/easyjson/blob/v0.7.7/LICENSE)) @@ -44,12 +44,12 @@ The following packages are included for the amd64, arm64 architectures. - [github.com/subosito/gotenv](https://pkg.go.dev/github.com/subosito/gotenv) ([MIT](https://github.com/subosito/gotenv/blob/v1.6.0/LICENSE)) - [github.com/yosida95/uritemplate/v3](https://pkg.go.dev/github.com/yosida95/uritemplate/v3) ([BSD-3-Clause](https://github.com/yosida95/uritemplate/blob/v3.0.2/LICENSE)) - [github.com/yudai/golcs](https://pkg.go.dev/github.com/yudai/golcs) ([MIT](https://github.com/yudai/golcs/blob/ecda9a501e82/LICENSE)) + - [go.yaml.in/yaml/v3](https://pkg.go.dev/go.yaml.in/yaml/v3) ([MIT](https://github.com/yaml/go-yaml/blob/v3.0.4/LICENSE)) - [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/8a7402ab:LICENSE)) - [golang.org/x/net/html](https://pkg.go.dev/golang.org/x/net/html) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.38.0:LICENSE)) - [golang.org/x/sys/unix](https://pkg.go.dev/golang.org/x/sys/unix) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.31.0:LICENSE)) - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.28.0:LICENSE)) - [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.5.0:LICENSE)) - [gopkg.in/yaml.v2](https://pkg.go.dev/gopkg.in/yaml.v2) ([Apache-2.0](https://github.com/go-yaml/yaml/blob/v2.4.0/LICENSE)) - - [go.yaml.in/yaml/v3](https://pkg.go.dev/go.yaml.in/yaml/v3) ([MIT](https://github.com/yaml/go-yaml/blob/v3.0.4/LICENSE)) [github/github-mcp-server]: https://github.com/github/github-mcp-server diff --git a/third-party-licenses.linux.md b/third-party-licenses.linux.md index c947e158c..851a70594 100644 --- a/third-party-licenses.linux.md +++ b/third-party-licenses.linux.md @@ -15,15 +15,15 @@ The following packages are included for the 386, amd64, arm64 architectures. - [github.com/aymerick/douceur](https://pkg.go.dev/github.com/aymerick/douceur) ([MIT](https://github.com/aymerick/douceur/blob/v0.2.0/LICENSE)) - [github.com/fsnotify/fsnotify](https://pkg.go.dev/github.com/fsnotify/fsnotify) ([BSD-3-Clause](https://github.com/fsnotify/fsnotify/blob/v1.9.0/LICENSE)) - [github.com/github/github-mcp-server](https://pkg.go.dev/github.com/github/github-mcp-server) ([MIT](https://github.com/github/github-mcp-server/blob/HEAD/LICENSE)) + - [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.19.5/LICENSE)) + - [github.com/go-openapi/swag](https://pkg.go.dev/github.com/go-openapi/swag) ([Apache-2.0](https://github.com/go-openapi/swag/blob/v0.21.1/LICENSE)) + - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.4.0/LICENSE)) - [github.com/google/go-github/v71/github](https://pkg.go.dev/github.com/google/go-github/v71/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v71.0.0/LICENSE)) - [github.com/google/go-github/v79/github](https://pkg.go.dev/github.com/google/go-github/v79/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v79.0.0/LICENSE)) - [github.com/google/go-querystring/query](https://pkg.go.dev/github.com/google/go-querystring/query) ([BSD-3-Clause](https://github.com/google/go-querystring/blob/v1.1.0/LICENSE)) - [github.com/google/jsonschema-go/jsonschema](https://pkg.go.dev/github.com/google/jsonschema-go/jsonschema) ([MIT](https://github.com/google/jsonschema-go/blob/v0.3.0/LICENSE)) - - [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.19.5/LICENSE)) - - [github.com/go-openapi/swag](https://pkg.go.dev/github.com/go-openapi/swag) ([Apache-2.0](https://github.com/go-openapi/swag/blob/v0.21.1/LICENSE)) - [github.com/gorilla/css/scanner](https://pkg.go.dev/github.com/gorilla/css/scanner) ([BSD-3-Clause](https://github.com/gorilla/css/blob/v1.0.1/LICENSE)) - [github.com/gorilla/mux](https://pkg.go.dev/github.com/gorilla/mux) ([BSD-3-Clause](https://github.com/gorilla/mux/blob/v1.8.0/LICENSE)) - - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.4.0/LICENSE)) - [github.com/josephburnett/jd/v2](https://pkg.go.dev/github.com/josephburnett/jd/v2) ([MIT](https://github.com/josephburnett/jd/blob/v1.9.2/LICENSE)) - [github.com/josharian/intern](https://pkg.go.dev/github.com/josharian/intern) ([MIT](https://github.com/josharian/intern/blob/v1.0.0/license.md)) - [github.com/mailru/easyjson](https://pkg.go.dev/github.com/mailru/easyjson) ([MIT](https://github.com/mailru/easyjson/blob/v0.7.7/LICENSE)) @@ -44,12 +44,12 @@ The following packages are included for the 386, amd64, arm64 architectures. - [github.com/subosito/gotenv](https://pkg.go.dev/github.com/subosito/gotenv) ([MIT](https://github.com/subosito/gotenv/blob/v1.6.0/LICENSE)) - [github.com/yosida95/uritemplate/v3](https://pkg.go.dev/github.com/yosida95/uritemplate/v3) ([BSD-3-Clause](https://github.com/yosida95/uritemplate/blob/v3.0.2/LICENSE)) - [github.com/yudai/golcs](https://pkg.go.dev/github.com/yudai/golcs) ([MIT](https://github.com/yudai/golcs/blob/ecda9a501e82/LICENSE)) + - [go.yaml.in/yaml/v3](https://pkg.go.dev/go.yaml.in/yaml/v3) ([MIT](https://github.com/yaml/go-yaml/blob/v3.0.4/LICENSE)) - [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/8a7402ab:LICENSE)) - [golang.org/x/net/html](https://pkg.go.dev/golang.org/x/net/html) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.38.0:LICENSE)) - [golang.org/x/sys/unix](https://pkg.go.dev/golang.org/x/sys/unix) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.31.0:LICENSE)) - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.28.0:LICENSE)) - [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.5.0:LICENSE)) - [gopkg.in/yaml.v2](https://pkg.go.dev/gopkg.in/yaml.v2) ([Apache-2.0](https://github.com/go-yaml/yaml/blob/v2.4.0/LICENSE)) - - [go.yaml.in/yaml/v3](https://pkg.go.dev/go.yaml.in/yaml/v3) ([MIT](https://github.com/yaml/go-yaml/blob/v3.0.4/LICENSE)) [github/github-mcp-server]: https://github.com/github/github-mcp-server diff --git a/third-party-licenses.windows.md b/third-party-licenses.windows.md index 29e107a5b..f4f8ee42c 100644 --- a/third-party-licenses.windows.md +++ b/third-party-licenses.windows.md @@ -15,15 +15,15 @@ The following packages are included for the 386, amd64, arm64 architectures. - [github.com/aymerick/douceur](https://pkg.go.dev/github.com/aymerick/douceur) ([MIT](https://github.com/aymerick/douceur/blob/v0.2.0/LICENSE)) - [github.com/fsnotify/fsnotify](https://pkg.go.dev/github.com/fsnotify/fsnotify) ([BSD-3-Clause](https://github.com/fsnotify/fsnotify/blob/v1.9.0/LICENSE)) - [github.com/github/github-mcp-server](https://pkg.go.dev/github.com/github/github-mcp-server) ([MIT](https://github.com/github/github-mcp-server/blob/HEAD/LICENSE)) + - [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.19.5/LICENSE)) + - [github.com/go-openapi/swag](https://pkg.go.dev/github.com/go-openapi/swag) ([Apache-2.0](https://github.com/go-openapi/swag/blob/v0.21.1/LICENSE)) + - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.4.0/LICENSE)) - [github.com/google/go-github/v71/github](https://pkg.go.dev/github.com/google/go-github/v71/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v71.0.0/LICENSE)) - [github.com/google/go-github/v79/github](https://pkg.go.dev/github.com/google/go-github/v79/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v79.0.0/LICENSE)) - [github.com/google/go-querystring/query](https://pkg.go.dev/github.com/google/go-querystring/query) ([BSD-3-Clause](https://github.com/google/go-querystring/blob/v1.1.0/LICENSE)) - [github.com/google/jsonschema-go/jsonschema](https://pkg.go.dev/github.com/google/jsonschema-go/jsonschema) ([MIT](https://github.com/google/jsonschema-go/blob/v0.3.0/LICENSE)) - - [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.19.5/LICENSE)) - - [github.com/go-openapi/swag](https://pkg.go.dev/github.com/go-openapi/swag) ([Apache-2.0](https://github.com/go-openapi/swag/blob/v0.21.1/LICENSE)) - [github.com/gorilla/css/scanner](https://pkg.go.dev/github.com/gorilla/css/scanner) ([BSD-3-Clause](https://github.com/gorilla/css/blob/v1.0.1/LICENSE)) - [github.com/gorilla/mux](https://pkg.go.dev/github.com/gorilla/mux) ([BSD-3-Clause](https://github.com/gorilla/mux/blob/v1.8.0/LICENSE)) - - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.4.0/LICENSE)) - [github.com/inconshreveable/mousetrap](https://pkg.go.dev/github.com/inconshreveable/mousetrap) ([Apache-2.0](https://github.com/inconshreveable/mousetrap/blob/v1.1.0/LICENSE)) - [github.com/josephburnett/jd/v2](https://pkg.go.dev/github.com/josephburnett/jd/v2) ([MIT](https://github.com/josephburnett/jd/blob/v1.9.2/LICENSE)) - [github.com/josharian/intern](https://pkg.go.dev/github.com/josharian/intern) ([MIT](https://github.com/josharian/intern/blob/v1.0.0/license.md)) @@ -45,12 +45,12 @@ The following packages are included for the 386, amd64, arm64 architectures. - [github.com/subosito/gotenv](https://pkg.go.dev/github.com/subosito/gotenv) ([MIT](https://github.com/subosito/gotenv/blob/v1.6.0/LICENSE)) - [github.com/yosida95/uritemplate/v3](https://pkg.go.dev/github.com/yosida95/uritemplate/v3) ([BSD-3-Clause](https://github.com/yosida95/uritemplate/blob/v3.0.2/LICENSE)) - [github.com/yudai/golcs](https://pkg.go.dev/github.com/yudai/golcs) ([MIT](https://github.com/yudai/golcs/blob/ecda9a501e82/LICENSE)) + - [go.yaml.in/yaml/v3](https://pkg.go.dev/go.yaml.in/yaml/v3) ([MIT](https://github.com/yaml/go-yaml/blob/v3.0.4/LICENSE)) - [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/8a7402ab:LICENSE)) - [golang.org/x/net/html](https://pkg.go.dev/golang.org/x/net/html) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.38.0:LICENSE)) - [golang.org/x/sys/windows](https://pkg.go.dev/golang.org/x/sys/windows) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.31.0:LICENSE)) - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.28.0:LICENSE)) - [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.5.0:LICENSE)) - [gopkg.in/yaml.v2](https://pkg.go.dev/gopkg.in/yaml.v2) ([Apache-2.0](https://github.com/go-yaml/yaml/blob/v2.4.0/LICENSE)) - - [go.yaml.in/yaml/v3](https://pkg.go.dev/go.yaml.in/yaml/v3) ([MIT](https://github.com/yaml/go-yaml/blob/v3.0.4/LICENSE)) [github/github-mcp-server]: https://github.com/github/github-mcp-server From 58f49f5099ecdfcb74d2b626937390ab254b38ff Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Fri, 12 Dec 2025 12:32:56 +0100 Subject: [PATCH 05/12] feat: auto-fix license files on PRs and improve CI reliability Changes: - Pin go-licenses version in CI for reproducibility (commit 5348b744) - Add GOROOT/PATH setup for 'Package does not have module info' fix - Update license-check.yml to auto-fix and push to PR branches - Add CI=true env var to use pinned go-licenses version - Add dependabot exclusion from auto-fix workflow - Add code-scanning exclusion for third-party files --- .github/workflows/code-scanning.yml | 3 ++ .github/workflows/license-check.yml | 76 +++++++++++++++++++++++++++-- script/licenses | 8 ++- 3 files changed, 81 insertions(+), 6 deletions(-) diff --git a/.github/workflows/code-scanning.yml b/.github/workflows/code-scanning.yml index 7dda8c9bd..468f425ae 100644 --- a/.github/workflows/code-scanning.yml +++ b/.github/workflows/code-scanning.yml @@ -46,6 +46,9 @@ jobs: queries: "" # Default query suite packs: github/ccr-${{ matrix.language }}-queries config: | + paths-ignore: + - third-party + - third-party-licenses.*.md default-setup: org: model-packs: [ ${{ github.event.inputs.code_scanning_codeql_packs }} ] diff --git a/.github/workflows/license-check.yml b/.github/workflows/license-check.yml index d9cb59fb7..175a7483f 100644 --- a/.github/workflows/license-check.yml +++ b/.github/workflows/license-check.yml @@ -1,21 +1,87 @@ -# Create a github action that runs the license check script and fails if it exits with a non-zero status +# Automatically fix license files on PRs that need updates +# Instead of just failing, this workflow pushes the fix and comments on the PR name: License Check -on: [push, pull_request] +on: + pull_request: + paths: + - "**.go" + - go.mod + - go.sum + - ".github/licenses.tmpl" + - "script/licenses*" + - "third-party-licenses.*.md" + - "third-party/**" permissions: - contents: read + contents: write + pull-requests: write jobs: license-check: runs-on: ubuntu-latest + # Don't run on forks (they can't push back) or dependabot + if: github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]' steps: - name: Check out code uses: actions/checkout@v6 + with: + ref: ${{ github.head_ref }} - name: Set up Go uses: actions/setup-go@v6 with: go-version-file: "go.mod" - - name: check licenses - run: ./script/licenses-check + + # actions/setup-go does not setup the installed toolchain to be preferred over the system install, + # which causes go-licenses to raise "Package ... does not have module info" errors. + # For more information, https://github.com/google/go-licenses/issues/244#issuecomment-1885098633 + - name: Regenerate licenses + env: + CI: "true" + run: | + export GOROOT=$(go env GOROOT) + export PATH=${GOROOT}/bin:$PATH + ./script/licenses + + - name: Check for changes + id: changes + run: | + if git diff --exit-code; then + echo "changed=false" >> $GITHUB_OUTPUT + echo "✅ License files are up to date" + else + echo "changed=true" >> $GITHUB_OUTPUT + echo "📝 License files need updating" + git diff --stat + fi + + - name: Commit and push fixes + if: steps.changes.outputs.changed == 'true' + run: | + git config --local user.name "github-actions[bot]" + git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add third-party third-party-licenses.*.md + git commit -m "chore: regenerate third-party licenses" + git push + + - name: Comment on PR + if: steps.changes.outputs.changed == 'true' + uses: actions/github-script@v7 + with: + script: | + github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: `## 📜 License files updated + +I noticed the third-party license files were out of date and pushed a fix to this PR. + +**What changed:** Dependencies were added, removed, or updated, which requires regenerating the license documentation. + +**What I did:** Ran \`./script/licenses\` and committed the result. + +Please pull the latest changes before pushing again.` + }) + diff --git a/script/licenses b/script/licenses index 214efa435..026af7f98 100755 --- a/script/licenses +++ b/script/licenses @@ -19,7 +19,13 @@ set -e -go install github.com/google/go-licenses@latest +# Pinned version for CI reproducibility, latest for local development +# See: https://github.com/cli/cli/pull/11161 +if [ "$CI" = "true" ]; then + go install github.com/google/go-licenses@5348b744d0983d85713295ea08a20cca1654a45e # v2.0.1 +else + go install github.com/google/go-licenses@latest +fi # actions/setup-go does not setup the installed toolchain to be preferred over the system install, # which causes go-licenses to raise "Package ... does not have module info" errors in CI. From 935f7d4c9a050da040b2301765c5cef4dc0b47e7 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Fri, 12 Dec 2025 14:52:50 +0100 Subject: [PATCH 06/12] feat: auto-close PRs that only needed license updates After the bot pushes license fixes, check if the PR now only contains license file changes. If so, close it automatically with a comment explaining that the license updates are complete. This prevents stale PRs from accumulating when someone creates a PR just to fix licenses, or when all other changes were already merged to the base branch. --- .github/workflows/license-check.yml | 58 +++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/.github/workflows/license-check.yml b/.github/workflows/license-check.yml index 175a7483f..fe701a57f 100644 --- a/.github/workflows/license-check.yml +++ b/.github/workflows/license-check.yml @@ -85,3 +85,61 @@ I noticed the third-party license files were out of date and pushed a fix to thi Please pull the latest changes before pushing again.` }) + # After pushing the fix, check if PR now has no functional changes (just license updates) + # This handles the case where the PR only needed license updates and nothing else + - name: Check if PR is now empty + if: steps.changes.outputs.changed == 'true' + id: empty_check + uses: actions/github-script@v7 + with: + script: | + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number + }); + + const { data: files } = await github.rest.pulls.listFiles({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number + }); + + // Check if ALL changes are just license files + const nonLicenseFiles = files.filter(f => + !f.filename.startsWith('third-party-licenses.') && + !f.filename.startsWith('third-party/') + ); + + const isEmpty = nonLicenseFiles.length === 0; + core.setOutput('is_empty', isEmpty); + + if (isEmpty) { + core.info('PR only contains license file changes - will close as stale'); + } else { + core.info(`PR has ${nonLicenseFiles.length} non-license file changes - keeping open`); + } + + - name: Close stale license-only PR + if: steps.changes.outputs.changed == 'true' && steps.empty_check.outputs.is_empty == 'true' + uses: actions/github-script@v7 + with: + script: | + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: `## 🤖 Auto-closing stale PR + +This PR now only contains license file updates with no other functional changes. The license updates have been applied, so closing this PR as complete. + +If this PR should have had other changes, please reopen it and add the intended changes.` + }); + + await github.rest.pulls.update({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number, + state: 'closed' + }); + From d191f50c0a8498e8569d3ef796c53f168c8c3e36 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Fri, 12 Dec 2025 15:05:17 +0100 Subject: [PATCH 07/12] feat: auto-create/manage license fix PRs for failing PRs Creates stacked PRs to fix license issues: - Detects when a PR needs license updates - Creates child PR: main <- PR:feature <- PR:license-fix - Tracks PRs with metadata and hash of license changes - Auto-closes if user fixes licenses manually - Auto-closes and recreates if dependencies change - Prevents multiple fix PRs for same base PR Rules: - Only targets PRs against main (not stacked PRs) - Only runs on ready-for-review PRs (not drafts) - Skips bots and forks - Hash-based detection avoids unnecessary work --- .github/workflows/auto-fix-licenses.yml | 241 ++++++++++++++++++++++++ .github/workflows/license-check.yml | 22 ++- 2 files changed, 259 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/auto-fix-licenses.yml diff --git a/.github/workflows/auto-fix-licenses.yml b/.github/workflows/auto-fix-licenses.yml new file mode 100644 index 000000000..1a6b82ac9 --- /dev/null +++ b/.github/workflows/auto-fix-licenses.yml @@ -0,0 +1,241 @@ +name: Auto-fix License Files + +# Automatically create/update/close PRs to fix license files on failing PRs +# Creates: main <- PR:feature <- PR:license-fix +# Only targets PRs against main, not stacked PRs + +on: + pull_request: + types: [synchronize, opened, reopened, ready_for_review] + paths: + - "**.go" + - go.mod + - go.sum + +permissions: + contents: write + pull-requests: write + +jobs: + auto-fix-licenses: + runs-on: ubuntu-latest + # Only run on PRs targeting main, from same repo, not drafts, not bots + if: | + github.event.pull_request.base.ref == 'main' && + github.event.pull_request.head.repo.full_name == github.repository && + github.event.pull_request.draft == false && + github.actor != 'dependabot[bot]' + + steps: + - name: Check out base PR branch + uses: actions/checkout@v6 + with: + ref: ${{ github.event.pull_request.head.ref }} + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: "go.mod" + + - name: Regenerate licenses + id: regen + env: + CI: "true" + run: | + export GOROOT=$(go env GOROOT) + export PATH=${GOROOT}/bin:$PATH + ./script/licenses + + # Check if licenses changed + if git diff --exit-code --quiet third-party-licenses.*.md third-party/; then + echo "needs_fix=false" >> $GITHUB_OUTPUT + echo "✅ License files are up to date" + else + echo "needs_fix=true" >> $GITHUB_OUTPUT + echo "📝 License files need updating" + + # Compute hash of license changes only + LICENSE_HASH=$(git diff third-party-licenses.*.md third-party/ | sha256sum | cut -c1-8) + echo "license_hash=${LICENSE_HASH}" >> $GITHUB_OUTPUT + echo "License changes hash: ${LICENSE_HASH}" + fi + + - name: Find existing license fix PR + id: find_pr + if: steps.regen.outputs.needs_fix == 'true' + uses: actions/github-script@v7 + with: + script: | + const basePR = context.payload.pull_request.number; + const baseBranch = context.payload.pull_request.head.ref; + + // Search for existing auto-fix PR + const { data: prs } = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + base: baseBranch, + sort: 'created', + direction: 'desc' + }); + + // Find PR with our marker in the title or body + const existingPR = prs.find(pr => + pr.title.includes('🤖 Auto-fix licenses') && + pr.body?.includes(``) + ); + + if (existingPR) { + core.setOutput('exists', 'true'); + core.setOutput('pr_number', existingPR.number); + core.setOutput('pr_branch', existingPR.head.ref); + + // Extract hash from PR body + const hashMatch = existingPR.body?.match(//); + const oldHash = hashMatch ? hashMatch[1] : ''; + core.setOutput('old_hash', oldHash); + + core.info(`Found existing PR #${existingPR.number} with hash ${oldHash}`); + } else { + core.setOutput('exists', 'false'); + core.info('No existing auto-fix PR found'); + } + + - name: Check if hash matches (user already fixed it) + id: check_fixed + if: steps.regen.outputs.needs_fix == 'false' && steps.find_pr.outputs.exists == 'true' + uses: actions/github-script@v7 + with: + script: | + // User fixed licenses themselves, close our auto-fix PR + const prNumber = ${{ steps.find_pr.outputs.pr_number }}; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: `## ✅ Closing - licenses already fixed\n\nThe base PR #${context.payload.pull_request.number} now has up-to-date license files. This auto-fix PR is no longer needed.` + }); + + await github.rest.pulls.update({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber, + state: 'closed' + }); + + core.info(`Closed PR #${prNumber} as licenses are now fixed`); + + - name: Close PR if hash changed (dependencies changed) + id: check_hash_changed + if: | + steps.regen.outputs.needs_fix == 'true' && + steps.find_pr.outputs.exists == 'true' && + steps.find_pr.outputs.old_hash != '' && + steps.find_pr.outputs.old_hash != steps.regen.outputs.license_hash + uses: actions/github-script@v7 + with: + script: | + const prNumber = ${{ steps.find_pr.outputs.pr_number }}; + const oldHash = '${{ steps.find_pr.outputs.old_hash }}'; + const newHash = '${{ steps.regen.outputs.license_hash }}'; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: `## 🔄 Closing - dependencies changed\n\nThe base PR #${context.payload.pull_request.number} has different license requirements now.\n\n- Old hash: \`${oldHash}\`\n- New hash: \`${newHash}\`\n\nA new auto-fix PR will be created.` + }); + + await github.rest.pulls.update({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber, + state: 'closed' + }); + + core.setOutput('create_new', 'true'); + core.info(`Closed PR #${prNumber} due to hash change`); + + - name: Create or update license fix PR + if: | + steps.regen.outputs.needs_fix == 'true' && + (steps.find_pr.outputs.exists == 'false' || steps.check_hash_changed.outputs.create_new == 'true') + uses: actions/github-script@v7 + with: + script: | + const basePR = context.payload.pull_request.number; + const baseBranch = context.payload.pull_request.head.ref; + const licenseHash = '${{ steps.regen.outputs.license_hash }}'; + const branchName = `auto-fix/licenses-for-pr-${basePR}`; + + // Create new branch from base PR + const { data: baseRef } = await github.rest.git.getRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `heads/${baseBranch}` + }); + + try { + await github.rest.git.createRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `refs/heads/${branchName}`, + sha: baseRef.object.sha + }); + } catch (error) { + // Branch might exist, update it + await github.rest.git.updateRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `heads/${branchName}`, + sha: baseRef.object.sha, + force: true + }); + } + + // Checkout the new branch and commit license changes + await exec.exec('git', ['fetch', 'origin', branchName]); + await exec.exec('git', ['checkout', '-B', branchName, `origin/${branchName}`]); + await exec.exec('git', ['config', 'user.name', 'github-actions[bot]']); + await exec.exec('git', ['config', 'user.email', '41898282+github-actions[bot]@users.noreply.github.com']); + + // Regenerate licenses on this branch + process.env.CI = 'true'; + const goRoot = (await exec.getExecOutput('go', ['env', 'GOROOT'])).stdout.trim(); + process.env.GOROOT = goRoot; + process.env.PATH = `${goRoot}/bin:${process.env.PATH}`; + await exec.exec('./script/licenses'); + + await exec.exec('git', ['add', 'third-party', 'third-party-licenses.*.md']); + await exec.exec('git', ['commit', '-m', `chore: auto-fix license files for PR #${basePR}`]); + await exec.exec('git', ['push', 'origin', branchName, '--force']); + + // Create PR + const { data: newPR } = await github.rest.pulls.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: `🤖 Auto-fix licenses for PR #${basePR}`, + head: branchName, + base: baseBranch, + body: `## Automated License Update + +This PR automatically updates license files for PR #${basePR}. + +### What happened +Dependencies were added/updated in #${basePR}, which requires regenerating license documentation. + +### What to do +- **Option 1:** Merge this PR to add the license updates to #${basePR} +- **Option 2:** Manually run \`./script/licenses\` in #${basePR} and push (this PR will auto-close) + +This PR will automatically close if: +- License files in #${basePR} are updated manually +- Dependencies in #${basePR} change (a new PR will be created) + + +` + }); + + core.info(`Created auto-fix PR #${newPR.number}`); diff --git a/.github/workflows/license-check.yml b/.github/workflows/license-check.yml index fe701a57f..d06cac9fc 100644 --- a/.github/workflows/license-check.yml +++ b/.github/workflows/license-check.yml @@ -112,16 +112,30 @@ Please pull the latest changes before pushing again.` ); const isEmpty = nonLicenseFiles.length === 0; - core.setOutput('is_empty', isEmpty); - if (isEmpty) { - core.info('PR only contains license file changes - will close as stale'); + // Only close if the PR title/body suggests it wasn't meant to be about licenses + // or if it was created by a bot (which might auto-update dependencies) + const isLicenseFocused = + pr.title.toLowerCase().includes('licen') || + pr.title.toLowerCase().includes('third-party') || + pr.body?.toLowerCase().includes('update.*licen'); + + const shouldClose = isEmpty && !isLicenseFocused && pr.user.type !== 'Bot'; + + core.setOutput('should_close', shouldClose); + + if (isEmpty && isLicenseFocused) { + core.info('PR only has license files but appears to be intentionally about licenses - keeping open'); + } else if (isEmpty && pr.user.type === 'Bot') { + core.info('PR is from a bot and only has license files - keeping open (might be dependabot)'); + } else if (shouldClose) { + core.info('PR only contains license file changes and appears stale - will close'); } else { core.info(`PR has ${nonLicenseFiles.length} non-license file changes - keeping open`); } - name: Close stale license-only PR - if: steps.changes.outputs.changed == 'true' && steps.empty_check.outputs.is_empty == 'true' + if: steps.changes.outputs.changed == 'true' && steps.empty_check.outputs.should_close == 'true' uses: actions/github-script@v7 with: script: | From 4a1e257571c059dd6383fb188ece26c443d6a751 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Fri, 12 Dec 2025 15:07:03 +0100 Subject: [PATCH 08/12] fix: allow auto-fix workflow to run on dependabot PRs Dependabot PRs frequently need license updates and can't be merged until fixed. The auto-fix workflow helps by creating a child PR with the license changes, making it easy to merge both together. --- .github/workflows/auto-fix-licenses.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/auto-fix-licenses.yml b/.github/workflows/auto-fix-licenses.yml index 1a6b82ac9..0de3a4473 100644 --- a/.github/workflows/auto-fix-licenses.yml +++ b/.github/workflows/auto-fix-licenses.yml @@ -19,12 +19,11 @@ permissions: jobs: auto-fix-licenses: runs-on: ubuntu-latest - # Only run on PRs targeting main, from same repo, not drafts, not bots + # Only run on PRs targeting main, from same repo, not drafts if: | github.event.pull_request.base.ref == 'main' && github.event.pull_request.head.repo.full_name == github.repository && - github.event.pull_request.draft == false && - github.actor != 'dependabot[bot]' + github.event.pull_request.draft == false steps: - name: Check out base PR branch From 34b6b266139f57fe7602ffc76c8e7dbc13e8b90c Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Fri, 12 Dec 2025 15:12:33 +0100 Subject: [PATCH 09/12] fix: address Copilot review comments - Remove dependabot exclusion (we want to support dependabot PRs) - Comment indentation already fixed - CI env var already set for reproducibility --- .github/workflows/license-check.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/license-check.yml b/.github/workflows/license-check.yml index d06cac9fc..0e71a4dbb 100644 --- a/.github/workflows/license-check.yml +++ b/.github/workflows/license-check.yml @@ -19,8 +19,8 @@ permissions: jobs: license-check: runs-on: ubuntu-latest - # Don't run on forks (they can't push back) or dependabot - if: github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]' + # Don't run on forks (they can't push back) + if: github.event.pull_request.head.repo.full_name == github.repository steps: - name: Check out code From 79a471a9d0d8d4448c59d48623b32fd84b56fcb3 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Fri, 12 Dec 2025 15:16:37 +0100 Subject: [PATCH 10/12] refactor: move base branch filter to on: block Moved the 'targets main' check from job if: to workflow on.pull_request.branches. This prevents the workflow from even triggering for PRs targeting other branches, saving CI resources. Draft check is implicit in the types list (opened + ready_for_review). Fork check must stay in if: condition (can't be filtered in on: block). --- .github/workflows/auto-fix-licenses.yml | 9 ++++----- .github/workflows/license-check.yml | 2 ++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/auto-fix-licenses.yml b/.github/workflows/auto-fix-licenses.yml index 0de3a4473..9656d6f9d 100644 --- a/.github/workflows/auto-fix-licenses.yml +++ b/.github/workflows/auto-fix-licenses.yml @@ -7,6 +7,8 @@ name: Auto-fix License Files on: pull_request: types: [synchronize, opened, reopened, ready_for_review] + branches: + - main # Only run when PR targets main paths: - "**.go" - go.mod @@ -19,11 +21,8 @@ permissions: jobs: auto-fix-licenses: runs-on: ubuntu-latest - # Only run on PRs targeting main, from same repo, not drafts - if: | - github.event.pull_request.base.ref == 'main' && - github.event.pull_request.head.repo.full_name == github.repository && - github.event.pull_request.draft == false + # Only run on PRs from same repo (not forks) + if: github.event.pull_request.head.repo.full_name == github.repository steps: - name: Check out base PR branch diff --git a/.github/workflows/license-check.yml b/.github/workflows/license-check.yml index 0e71a4dbb..8a4c8351b 100644 --- a/.github/workflows/license-check.yml +++ b/.github/workflows/license-check.yml @@ -4,6 +4,8 @@ name: License Check on: pull_request: + branches: + - main # Only run when PR targets main paths: - "**.go" - go.mod From 878140934fc3b897967312c9f7d4d19dff01b5b8 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Fri, 12 Dec 2025 15:24:24 +0100 Subject: [PATCH 11/12] refactor: merge auto-fix into license-check workflow Combines both workflows into one with two jobs: 1. license-check: Checks licenses, fails if needed, sets outputs 2. auto-create-fix-pr: Creates child PR if needed (only for non-forks) Benefits: - Single workflow file, easier to maintain - Check fails (blocks merge) while still creating helpful fix PR - Fork detection in first job, second job skips for forks - Hash-based tracking prevents duplicate PRs --- .github/workflows/auto-fix-licenses.yml | 239 ------------------------ .github/workflows/license-check.yml | 234 ++++++++++++++++++++++- 2 files changed, 226 insertions(+), 247 deletions(-) delete mode 100644 .github/workflows/auto-fix-licenses.yml diff --git a/.github/workflows/auto-fix-licenses.yml b/.github/workflows/auto-fix-licenses.yml deleted file mode 100644 index 9656d6f9d..000000000 --- a/.github/workflows/auto-fix-licenses.yml +++ /dev/null @@ -1,239 +0,0 @@ -name: Auto-fix License Files - -# Automatically create/update/close PRs to fix license files on failing PRs -# Creates: main <- PR:feature <- PR:license-fix -# Only targets PRs against main, not stacked PRs - -on: - pull_request: - types: [synchronize, opened, reopened, ready_for_review] - branches: - - main # Only run when PR targets main - paths: - - "**.go" - - go.mod - - go.sum - -permissions: - contents: write - pull-requests: write - -jobs: - auto-fix-licenses: - runs-on: ubuntu-latest - # Only run on PRs from same repo (not forks) - if: github.event.pull_request.head.repo.full_name == github.repository - - steps: - - name: Check out base PR branch - uses: actions/checkout@v6 - with: - ref: ${{ github.event.pull_request.head.ref }} - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v6 - with: - go-version-file: "go.mod" - - - name: Regenerate licenses - id: regen - env: - CI: "true" - run: | - export GOROOT=$(go env GOROOT) - export PATH=${GOROOT}/bin:$PATH - ./script/licenses - - # Check if licenses changed - if git diff --exit-code --quiet third-party-licenses.*.md third-party/; then - echo "needs_fix=false" >> $GITHUB_OUTPUT - echo "✅ License files are up to date" - else - echo "needs_fix=true" >> $GITHUB_OUTPUT - echo "📝 License files need updating" - - # Compute hash of license changes only - LICENSE_HASH=$(git diff third-party-licenses.*.md third-party/ | sha256sum | cut -c1-8) - echo "license_hash=${LICENSE_HASH}" >> $GITHUB_OUTPUT - echo "License changes hash: ${LICENSE_HASH}" - fi - - - name: Find existing license fix PR - id: find_pr - if: steps.regen.outputs.needs_fix == 'true' - uses: actions/github-script@v7 - with: - script: | - const basePR = context.payload.pull_request.number; - const baseBranch = context.payload.pull_request.head.ref; - - // Search for existing auto-fix PR - const { data: prs } = await github.rest.pulls.list({ - owner: context.repo.owner, - repo: context.repo.repo, - state: 'open', - base: baseBranch, - sort: 'created', - direction: 'desc' - }); - - // Find PR with our marker in the title or body - const existingPR = prs.find(pr => - pr.title.includes('🤖 Auto-fix licenses') && - pr.body?.includes(``) - ); - - if (existingPR) { - core.setOutput('exists', 'true'); - core.setOutput('pr_number', existingPR.number); - core.setOutput('pr_branch', existingPR.head.ref); - - // Extract hash from PR body - const hashMatch = existingPR.body?.match(//); - const oldHash = hashMatch ? hashMatch[1] : ''; - core.setOutput('old_hash', oldHash); - - core.info(`Found existing PR #${existingPR.number} with hash ${oldHash}`); - } else { - core.setOutput('exists', 'false'); - core.info('No existing auto-fix PR found'); - } - - - name: Check if hash matches (user already fixed it) - id: check_fixed - if: steps.regen.outputs.needs_fix == 'false' && steps.find_pr.outputs.exists == 'true' - uses: actions/github-script@v7 - with: - script: | - // User fixed licenses themselves, close our auto-fix PR - const prNumber = ${{ steps.find_pr.outputs.pr_number }}; - - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - body: `## ✅ Closing - licenses already fixed\n\nThe base PR #${context.payload.pull_request.number} now has up-to-date license files. This auto-fix PR is no longer needed.` - }); - - await github.rest.pulls.update({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: prNumber, - state: 'closed' - }); - - core.info(`Closed PR #${prNumber} as licenses are now fixed`); - - - name: Close PR if hash changed (dependencies changed) - id: check_hash_changed - if: | - steps.regen.outputs.needs_fix == 'true' && - steps.find_pr.outputs.exists == 'true' && - steps.find_pr.outputs.old_hash != '' && - steps.find_pr.outputs.old_hash != steps.regen.outputs.license_hash - uses: actions/github-script@v7 - with: - script: | - const prNumber = ${{ steps.find_pr.outputs.pr_number }}; - const oldHash = '${{ steps.find_pr.outputs.old_hash }}'; - const newHash = '${{ steps.regen.outputs.license_hash }}'; - - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - body: `## 🔄 Closing - dependencies changed\n\nThe base PR #${context.payload.pull_request.number} has different license requirements now.\n\n- Old hash: \`${oldHash}\`\n- New hash: \`${newHash}\`\n\nA new auto-fix PR will be created.` - }); - - await github.rest.pulls.update({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: prNumber, - state: 'closed' - }); - - core.setOutput('create_new', 'true'); - core.info(`Closed PR #${prNumber} due to hash change`); - - - name: Create or update license fix PR - if: | - steps.regen.outputs.needs_fix == 'true' && - (steps.find_pr.outputs.exists == 'false' || steps.check_hash_changed.outputs.create_new == 'true') - uses: actions/github-script@v7 - with: - script: | - const basePR = context.payload.pull_request.number; - const baseBranch = context.payload.pull_request.head.ref; - const licenseHash = '${{ steps.regen.outputs.license_hash }}'; - const branchName = `auto-fix/licenses-for-pr-${basePR}`; - - // Create new branch from base PR - const { data: baseRef } = await github.rest.git.getRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: `heads/${baseBranch}` - }); - - try { - await github.rest.git.createRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: `refs/heads/${branchName}`, - sha: baseRef.object.sha - }); - } catch (error) { - // Branch might exist, update it - await github.rest.git.updateRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: `heads/${branchName}`, - sha: baseRef.object.sha, - force: true - }); - } - - // Checkout the new branch and commit license changes - await exec.exec('git', ['fetch', 'origin', branchName]); - await exec.exec('git', ['checkout', '-B', branchName, `origin/${branchName}`]); - await exec.exec('git', ['config', 'user.name', 'github-actions[bot]']); - await exec.exec('git', ['config', 'user.email', '41898282+github-actions[bot]@users.noreply.github.com']); - - // Regenerate licenses on this branch - process.env.CI = 'true'; - const goRoot = (await exec.getExecOutput('go', ['env', 'GOROOT'])).stdout.trim(); - process.env.GOROOT = goRoot; - process.env.PATH = `${goRoot}/bin:${process.env.PATH}`; - await exec.exec('./script/licenses'); - - await exec.exec('git', ['add', 'third-party', 'third-party-licenses.*.md']); - await exec.exec('git', ['commit', '-m', `chore: auto-fix license files for PR #${basePR}`]); - await exec.exec('git', ['push', 'origin', branchName, '--force']); - - // Create PR - const { data: newPR } = await github.rest.pulls.create({ - owner: context.repo.owner, - repo: context.repo.repo, - title: `🤖 Auto-fix licenses for PR #${basePR}`, - head: branchName, - base: baseBranch, - body: `## Automated License Update - -This PR automatically updates license files for PR #${basePR}. - -### What happened -Dependencies were added/updated in #${basePR}, which requires regenerating license documentation. - -### What to do -- **Option 1:** Merge this PR to add the license updates to #${basePR} -- **Option 2:** Manually run \`./script/licenses\` in #${basePR} and push (this PR will auto-close) - -This PR will automatically close if: -- License files in #${basePR} are updated manually -- Dependencies in #${basePR} change (a new PR will be created) - - -` - }); - - core.info(`Created auto-fix PR #${newPR.number}`); diff --git a/.github/workflows/license-check.yml b/.github/workflows/license-check.yml index 8a4c8351b..211bc516f 100644 --- a/.github/workflows/license-check.yml +++ b/.github/workflows/license-check.yml @@ -21,10 +21,17 @@ permissions: jobs: license-check: runs-on: ubuntu-latest - # Don't run on forks (they can't push back) - if: github.event.pull_request.head.repo.full_name == github.repository + outputs: + needs_fix: ${{ steps.changes.outputs.changed }} + is_fork: ${{ steps.fork_check.outputs.is_fork }} steps: + - name: Check if fork PR + id: fork_check + run: | + IS_FORK=${{ github.event.pull_request.head.repo.full_name != github.repository }} + echo "is_fork=${IS_FORK}" >> $GITHUB_OUTPUT + - name: Check out code uses: actions/checkout@v6 with: @@ -60,6 +67,8 @@ jobs: - name: Commit and push fixes if: steps.changes.outputs.changed == 'true' + continue-on-error: true # Don't fail if push fails (e.g., on forks) + id: push run: | git config --local user.name "github-actions[bot]" git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" @@ -72,11 +81,26 @@ jobs: uses: actions/github-script@v7 with: script: | - github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body: `## 📜 License files updated + const isFork = context.payload.pull_request.head.repo.full_name !== context.repo.owner + '/' + context.repo.repo; + const pushFailed = '${{ steps.push.outcome }}' === 'failure'; + + let body; + if (isFork || pushFailed) { + body = `## 📜 License files need updating + +License files are out of date. Since this is from a fork, I can't push the fix directly. + +**Please run locally:** +\`\`\`bash +./script/licenses +git add third-party third-party-licenses.*.md third-party/ +git commit -m "chore: update license files" +git push +\`\`\` + +Or a maintainer can push to your branch after approving the workflow.`; + } else { + body = `## 📜 License files updated I noticed the third-party license files were out of date and pushed a fix to this PR. @@ -84,9 +108,203 @@ I noticed the third-party license files were out of date and pushed a fix to thi **What I did:** Ran \`./script/licenses\` and committed the result. -Please pull the latest changes before pushing again.` +Please pull the latest changes before pushing again.`; + } + + github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body }) + - name: Fail check if licenses not fixed + if: steps.changes.outputs.changed == 'true' && steps.push.outcome == 'failure' + run: | + echo "::error::License files are out of date. Please run ./script/licenses locally." + exit 1 + + auto-create-fix-pr: + runs-on: ubuntu-latest + needs: license-check + if: needs.license-check.outputs.needs_fix == 'true' && needs.license-check.outputs.is_fork == 'false' + + steps: + - name: Check out base PR branch + uses: actions/checkout@v6 + with: + ref: ${{ github.event.pull_request.head.ref }} + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: "go.mod" + + - name: Regenerate licenses + id: regen + env: + CI: "true" + run: | + export GOROOT=$(go env GOROOT) + export PATH=${GOROOT}/bin:$PATH + ./script/licenses + + # Compute hash of license changes only + LICENSE_HASH=$(git diff third-party-licenses.*.md third-party/ | sha256sum | cut -c1-8) + echo "license_hash=${LICENSE_HASH}" >> $GITHUB_OUTPUT + echo "License changes hash: ${LICENSE_HASH}" + + - name: Find existing license fix PR + id: find_pr + uses: actions/github-script@v7 + with: + script: | + const basePR = context.payload.pull_request.number; + const baseBranch = context.payload.pull_request.head.ref; + + // Search for existing auto-fix PR + const { data: prs } = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + base: baseBranch, + sort: 'created', + direction: 'desc' + }); + + // Find PR with our marker in the title or body + const existingPR = prs.find(pr => + pr.title.includes('🤖 Auto-fix licenses') && + pr.body?.includes(``) + ); + + if (existingPR) { + core.setOutput('exists', 'true'); + core.setOutput('pr_number', existingPR.number); + core.setOutput('pr_branch', existingPR.head.ref); + + // Extract hash from PR body + const hashMatch = existingPR.body?.match(//); + const oldHash = hashMatch ? hashMatch[1] : ''; + core.setOutput('old_hash', oldHash); + + core.info(`Found existing PR #${existingPR.number} with hash ${oldHash}`); + } else { + core.setOutput('exists', 'false'); + core.info('No existing auto-fix PR found'); + } + + - name: Close PR if hash changed (dependencies changed) + id: check_hash_changed + if: | + steps.find_pr.outputs.exists == 'true' && + steps.find_pr.outputs.old_hash != '' && + steps.find_pr.outputs.old_hash != steps.regen.outputs.license_hash + uses: actions/github-script@v7 + with: + script: | + const prNumber = ${{ steps.find_pr.outputs.pr_number }}; + const oldHash = '${{ steps.find_pr.outputs.old_hash }}'; + const newHash = '${{ steps.regen.outputs.license_hash }}'; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: `## 🔄 Closing - dependencies changed\n\nThe base PR #${context.payload.pull_request.number} has different license requirements now.\n\n- Old hash: \`${oldHash}\`\n- New hash: \`${newHash}\`\n\nA new auto-fix PR will be created.` + }); + + await github.rest.pulls.update({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber, + state: 'closed' + }); + + core.setOutput('create_new', 'true'); + core.info(`Closed PR #${prNumber} due to hash change`); + + - name: Create or update license fix PR + if: steps.find_pr.outputs.exists == 'false' || steps.check_hash_changed.outputs.create_new == 'true' + uses: actions/github-script@v7 + with: + script: | + const basePR = context.payload.pull_request.number; + const baseBranch = context.payload.pull_request.head.ref; + const licenseHash = '${{ steps.regen.outputs.license_hash }}'; + const branchName = `auto-fix/licenses-for-pr-${basePR}`; + + // Create new branch from base PR + const { data: baseRef } = await github.rest.git.getRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `heads/${baseBranch}` + }); + + try { + await github.rest.git.createRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `refs/heads/${branchName}`, + sha: baseRef.object.sha + }); + } catch (error) { + // Branch might exist, update it + await github.rest.git.updateRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `heads/${branchName}`, + sha: baseRef.object.sha, + force: true + }); + } + + // Checkout the new branch and commit license changes + await exec.exec('git', ['fetch', 'origin', branchName]); + await exec.exec('git', ['checkout', '-B', branchName, `origin/${branchName}`]); + await exec.exec('git', ['config', 'user.name', 'github-actions[bot]']); + await exec.exec('git', ['config', 'user.email', '41898282+github-actions[bot]@users.noreply.github.com']); + + // Regenerate licenses on this branch + process.env.CI = 'true'; + const goRoot = (await exec.getExecOutput('go', ['env', 'GOROOT'])).stdout.trim(); + process.env.GOROOT = goRoot; + process.env.PATH = `${goRoot}/bin:${process.env.PATH}`; + await exec.exec('./script/licenses'); + + await exec.exec('git', ['add', 'third-party', 'third-party-licenses.*.md']); + await exec.exec('git', ['commit', '-m', `chore: auto-fix license files for PR #${basePR}`]); + await exec.exec('git', ['push', 'origin', branchName, '--force']); + + // Create PR + const { data: newPR } = await github.rest.pulls.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: `🤖 Auto-fix licenses for PR #${basePR}`, + head: branchName, + base: baseBranch, + body: `## Automated License Update + +This PR automatically updates license files for PR #${basePR}. + +### What happened +Dependencies were added/updated in #${basePR}, which requires regenerating license documentation. + +### What to do +- **Option 1:** Merge this PR to add the license updates to #${basePR} +- **Option 2:** Manually run \`./script/licenses\` in #${basePR} and push (this PR will auto-close) + +This PR will automatically close if: +- License files in #${basePR} are updated manually +- Dependencies in #${basePR} change (a new PR will be created) + + +` + }); + + core.info(`Created auto-fix PR #${newPR.number}`); + # After pushing the fix, check if PR now has no functional changes (just license updates) # This handles the case where the PR only needed license updates and nothing else - name: Check if PR is now empty From 5654f2862ebb822682b8cf93ae1ac398ad01e1f8 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Fri, 12 Dec 2025 15:32:41 +0100 Subject: [PATCH 12/12] refactor: simplify license-check to auto-commit approach Much simpler workflow: 1. Always try to auto-commit fix directly to PR branch 2. If push fails (fork without permissions), comment once with instructions 3. Don't create child PRs - just fix in place or give instructions 4. Only comment if not already commented (prevent spam) 5. Always fail check if licenses need updating Benefits: - Much simpler - single job - No child PR management complexity - Clear UX: either fixed or instructed - Works for all PRs (internal/fork/dependabot) --- .github/workflows/license-check.yml | 340 +++------------------------- 1 file changed, 36 insertions(+), 304 deletions(-) diff --git a/.github/workflows/license-check.yml b/.github/workflows/license-check.yml index 211bc516f..a2638eb1f 100644 --- a/.github/workflows/license-check.yml +++ b/.github/workflows/license-check.yml @@ -1,5 +1,5 @@ # Automatically fix license files on PRs that need updates -# Instead of just failing, this workflow pushes the fix and comments on the PR +# Tries to auto-commit the fix, or comments with instructions if push fails name: License Check on: @@ -21,17 +21,8 @@ permissions: jobs: license-check: runs-on: ubuntu-latest - outputs: - needs_fix: ${{ steps.changes.outputs.changed }} - is_fork: ${{ steps.fork_check.outputs.is_fork }} steps: - - name: Check if fork PR - id: fork_check - run: | - IS_FORK=${{ github.event.pull_request.head.repo.full_name != github.repository }} - echo "is_fork=${IS_FORK}" >> $GITHUB_OUTPUT - - name: Check out code uses: actions/checkout@v6 with: @@ -55,325 +46,66 @@ jobs: - name: Check for changes id: changes - run: | - if git diff --exit-code; then - echo "changed=false" >> $GITHUB_OUTPUT - echo "✅ License files are up to date" - else - echo "changed=true" >> $GITHUB_OUTPUT - echo "📝 License files need updating" - git diff --stat - fi + continue-on-error: true + run: script/licenses-check - name: Commit and push fixes - if: steps.changes.outputs.changed == 'true' - continue-on-error: true # Don't fail if push fails (e.g., on forks) + if: steps.changes.outcome == 'failure' + continue-on-error: true id: push run: | - git config --local user.name "github-actions[bot]" - git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add third-party third-party-licenses.*.md - git commit -m "chore: regenerate third-party licenses" - git push - - - name: Comment on PR - if: steps.changes.outputs.changed == 'true' - uses: actions/github-script@v7 - with: - script: | - const isFork = context.payload.pull_request.head.repo.full_name !== context.repo.owner + '/' + context.repo.repo; - const pushFailed = '${{ steps.push.outcome }}' === 'failure'; - - let body; - if (isFork || pushFailed) { - body = `## 📜 License files need updating - -License files are out of date. Since this is from a fork, I can't push the fix directly. - -**Please run locally:** -\`\`\`bash -./script/licenses -git add third-party third-party-licenses.*.md third-party/ -git commit -m "chore: update license files" -git push -\`\`\` + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add third-party-licenses.*.md third-party/ + git commit -m "chore: regenerate license files -Or a maintainer can push to your branch after approving the workflow.`; - } else { - body = `## 📜 License files updated - -I noticed the third-party license files were out of date and pushed a fix to this PR. - -**What changed:** Dependencies were added, removed, or updated, which requires regenerating the license documentation. - -**What I did:** Ran \`./script/licenses\` and committed the result. - -Please pull the latest changes before pushing again.`; - } - - github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body - }) - - - name: Fail check if licenses not fixed - if: steps.changes.outputs.changed == 'true' && steps.push.outcome == 'failure' - run: | - echo "::error::License files are out of date. Please run ./script/licenses locally." - exit 1 - - auto-create-fix-pr: - runs-on: ubuntu-latest - needs: license-check - if: needs.license-check.outputs.needs_fix == 'true' && needs.license-check.outputs.is_fork == 'false' - - steps: - - name: Check out base PR branch - uses: actions/checkout@v6 - with: - ref: ${{ github.event.pull_request.head.ref }} - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v6 - with: - go-version-file: "go.mod" - - - name: Regenerate licenses - id: regen - env: - CI: "true" - run: | - export GOROOT=$(go env GOROOT) - export PATH=${GOROOT}/bin:$PATH - ./script/licenses - - # Compute hash of license changes only - LICENSE_HASH=$(git diff third-party-licenses.*.md third-party/ | sha256sum | cut -c1-8) - echo "license_hash=${LICENSE_HASH}" >> $GITHUB_OUTPUT - echo "License changes hash: ${LICENSE_HASH}" +Auto-generated by license-check workflow" + git push - - name: Find existing license fix PR - id: find_pr + - name: Check if already commented + if: steps.changes.outcome == 'failure' && steps.push.outcome == 'failure' + id: check_comment uses: actions/github-script@v7 with: script: | - const basePR = context.payload.pull_request.number; - const baseBranch = context.payload.pull_request.head.ref; - - // Search for existing auto-fix PR - const { data: prs } = await github.rest.pulls.list({ + const { data: comments } = await github.rest.issues.listComments({ owner: context.repo.owner, repo: context.repo.repo, - state: 'open', - base: baseBranch, - sort: 'created', - direction: 'desc' + issue_number: context.issue.number }); - // Find PR with our marker in the title or body - const existingPR = prs.find(pr => - pr.title.includes('🤖 Auto-fix licenses') && - pr.body?.includes(``) + const alreadyCommented = comments.some(comment => + comment.user.login === 'github-actions[bot]' && + comment.body.includes('## ⚠️ License files need updating') ); - if (existingPR) { - core.setOutput('exists', 'true'); - core.setOutput('pr_number', existingPR.number); - core.setOutput('pr_branch', existingPR.head.ref); - - // Extract hash from PR body - const hashMatch = existingPR.body?.match(//); - const oldHash = hashMatch ? hashMatch[1] : ''; - core.setOutput('old_hash', oldHash); - - core.info(`Found existing PR #${existingPR.number} with hash ${oldHash}`); - } else { - core.setOutput('exists', 'false'); - core.info('No existing auto-fix PR found'); - } + core.setOutput('already_commented', alreadyCommented ? 'true' : 'false'); - - name: Close PR if hash changed (dependencies changed) - id: check_hash_changed - if: | - steps.find_pr.outputs.exists == 'true' && - steps.find_pr.outputs.old_hash != '' && - steps.find_pr.outputs.old_hash != steps.regen.outputs.license_hash + - name: Comment with instructions if cannot push + if: steps.changes.outcome == 'failure' && steps.push.outcome == 'failure' && steps.check_comment.outputs.already_commented == 'false' uses: actions/github-script@v7 with: script: | - const prNumber = ${{ steps.find_pr.outputs.pr_number }}; - const oldHash = '${{ steps.find_pr.outputs.old_hash }}'; - const newHash = '${{ steps.regen.outputs.license_hash }}'; - await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, - issue_number: prNumber, - body: `## 🔄 Closing - dependencies changed\n\nThe base PR #${context.payload.pull_request.number} has different license requirements now.\n\n- Old hash: \`${oldHash}\`\n- New hash: \`${newHash}\`\n\nA new auto-fix PR will be created.` - }); - - await github.rest.pulls.update({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: prNumber, - state: 'closed' - }); - - core.setOutput('create_new', 'true'); - core.info(`Closed PR #${prNumber} due to hash change`); - - - name: Create or update license fix PR - if: steps.find_pr.outputs.exists == 'false' || steps.check_hash_changed.outputs.create_new == 'true' - uses: actions/github-script@v7 - with: - script: | - const basePR = context.payload.pull_request.number; - const baseBranch = context.payload.pull_request.head.ref; - const licenseHash = '${{ steps.regen.outputs.license_hash }}'; - const branchName = `auto-fix/licenses-for-pr-${basePR}`; - - // Create new branch from base PR - const { data: baseRef } = await github.rest.git.getRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: `heads/${baseBranch}` - }); - - try { - await github.rest.git.createRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: `refs/heads/${branchName}`, - sha: baseRef.object.sha - }); - } catch (error) { - // Branch might exist, update it - await github.rest.git.updateRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: `heads/${branchName}`, - sha: baseRef.object.sha, - force: true - }); - } - - // Checkout the new branch and commit license changes - await exec.exec('git', ['fetch', 'origin', branchName]); - await exec.exec('git', ['checkout', '-B', branchName, `origin/${branchName}`]); - await exec.exec('git', ['config', 'user.name', 'github-actions[bot]']); - await exec.exec('git', ['config', 'user.email', '41898282+github-actions[bot]@users.noreply.github.com']); - - // Regenerate licenses on this branch - process.env.CI = 'true'; - const goRoot = (await exec.getExecOutput('go', ['env', 'GOROOT'])).stdout.trim(); - process.env.GOROOT = goRoot; - process.env.PATH = `${goRoot}/bin:${process.env.PATH}`; - await exec.exec('./script/licenses'); - - await exec.exec('git', ['add', 'third-party', 'third-party-licenses.*.md']); - await exec.exec('git', ['commit', '-m', `chore: auto-fix license files for PR #${basePR}`]); - await exec.exec('git', ['push', 'origin', branchName, '--force']); - - // Create PR - const { data: newPR } = await github.rest.pulls.create({ - owner: context.repo.owner, - repo: context.repo.repo, - title: `🤖 Auto-fix licenses for PR #${basePR}`, - head: branchName, - base: baseBranch, - body: `## Automated License Update - -This PR automatically updates license files for PR #${basePR}. - -### What happened -Dependencies were added/updated in #${basePR}, which requires regenerating license documentation. - -### What to do -- **Option 1:** Merge this PR to add the license updates to #${basePR} -- **Option 2:** Manually run \`./script/licenses\` in #${basePR} and push (this PR will auto-close) + issue_number: context.issue.number, + body: `## ⚠️ License files need updating -This PR will automatically close if: -- License files in #${basePR} are updated manually -- Dependencies in #${basePR} change (a new PR will be created) +The license files are out of date. I tried to fix them automatically but don't have permission to push to this branch. - -` - }); - - core.info(`Created auto-fix PR #${newPR.number}`); +**Please run:** +\`\`\`bash +script/licenses +git add third-party-licenses.*.md third-party/ +git commit -m "chore: regenerate license files" +git push +\`\`\` - # After pushing the fix, check if PR now has no functional changes (just license updates) - # This handles the case where the PR only needed license updates and nothing else - - name: Check if PR is now empty - if: steps.changes.outputs.changed == 'true' - id: empty_check - uses: actions/github-script@v7 - with: - script: | - const { data: pr } = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.issue.number +Alternatively, enable "Allow edits by maintainers" in the PR settings so I can fix it automatically.` }); - - const { data: files } = await github.rest.pulls.listFiles({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.issue.number - }); - - // Check if ALL changes are just license files - const nonLicenseFiles = files.filter(f => - !f.filename.startsWith('third-party-licenses.') && - !f.filename.startsWith('third-party/') - ); - - const isEmpty = nonLicenseFiles.length === 0; - - // Only close if the PR title/body suggests it wasn't meant to be about licenses - // or if it was created by a bot (which might auto-update dependencies) - const isLicenseFocused = - pr.title.toLowerCase().includes('licen') || - pr.title.toLowerCase().includes('third-party') || - pr.body?.toLowerCase().includes('update.*licen'); - - const shouldClose = isEmpty && !isLicenseFocused && pr.user.type !== 'Bot'; - - core.setOutput('should_close', shouldClose); - - if (isEmpty && isLicenseFocused) { - core.info('PR only has license files but appears to be intentionally about licenses - keeping open'); - } else if (isEmpty && pr.user.type === 'Bot') { - core.info('PR is from a bot and only has license files - keeping open (might be dependabot)'); - } else if (shouldClose) { - core.info('PR only contains license file changes and appears stale - will close'); - } else { - core.info(`PR has ${nonLicenseFiles.length} non-license file changes - keeping open`); - } - - - name: Close stale license-only PR - if: steps.changes.outputs.changed == 'true' && steps.empty_check.outputs.should_close == 'true' - uses: actions/github-script@v7 - with: - script: | - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body: `## 🤖 Auto-closing stale PR -This PR now only contains license file updates with no other functional changes. The license updates have been applied, so closing this PR as complete. - -If this PR should have had other changes, please reopen it and add the intended changes.` - }); - - await github.rest.pulls.update({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.issue.number, - state: 'closed' - }); + - name: Fail check if changes needed + if: steps.changes.outcome == 'failure' + run: exit 1