navidrome/.github/workflows/detect-changes.sh
Deluan dd6d9b7891 ci: treat the plugin manifest schema as a Go change
plugins/manifest.go:37 declares manifest-schema.json as the input to a real
go:generate directive producing manifest_gen.go, so a schema-only edit skipped
the very check that catches a forgotten regeneration. Verified by adding a
property to the schema and re-running go generate, which changed the output.

All nine commits that ever touched the schema were already caught, but that is
the check working rather than evidence it is unnecessary: an author who forgets
to regenerate cannot reach merged history while the check runs.

This is the only gap of its kind. The repo has two go:generate directives, and
wire's inputs are all Go source.
2026-09-05 14:40:35 -04:00

68 lines
2.8 KiB
Bash
Executable file

#!/usr/bin/env bash
#
# Emits per-area change flags to $GITHUB_OUTPUT so the pipeline can skip work a
# pull request cannot affect:
#
# go - Go sources, module files, linter config, embedded resources and
# the go:generate inputs whose generated output CI verifies
# js - anything under ui/
# i18n - translation files and their validation script
# build - anything that ends up in a binary, image or package (i.e. every
# change except the doc-only paths in $DOC_ONLY_RE)
#
# pipeline.yml and this script are inputs to every suite they gate: a wrong
# gating expression skips a suite green, in every run, with no other signal.
#
# Only pull requests are narrowed. Master pushes, tags and manual runs always get
# every flag, so a release can never be built from a partially validated tree.
#
# Flags gate STEPS, not jobs: a job-level skip propagates through the needs
# chain (actions/runner#491) and would take the release jobs down with it.
#
# Compares HEAD against $BASE_REF (default master). Requires full history
# (fetch-depth: 0 in CI).
set -uo pipefail
export LC_ALL=C
GO_RE='(\.go$|(^|/)go\.(mod|sum)$|^Makefile$|^\.golangci\.yml$|^resources/|^db/migrations/|^tests/|^plugins/manifest-schema\.json$|^\.github/workflows/(pipeline\.yml|detect-changes\.sh)$)'
JS_RE='(^ui/|^\.github/workflows/(pipeline\.yml|detect-changes\.sh)$)'
I18N_RE='(^resources/i18n/|^ui/src/i18n/en\.json$|^\.github/workflows/validate-translations\.sh$|^\.github/workflows/(pipeline\.yml|detect-changes\.sh)$)'
DOC_ONLY_RE='(\.md$|^LICENSE$|^\.git-blame-ignore-revs$|^\.gitignore$|^\.devcontainer/)'
emit() { printf '%s=%s\n' "$1" "$2" | tee -a "${GITHUB_OUTPUT:-/dev/null}"; }
if [ "${GITHUB_EVENT_NAME:-}" != "pull_request" ]; then
echo "Not a pull request — running everything."
for area in go js i18n build; do emit "$area" true; done
exit 0
fi
BASE_REF="${BASE_REF:-master}"
git fetch --no-tags --quiet origin "+refs/heads/${BASE_REF}:refs/remotes/origin/${BASE_REF}" || true
# Guard the diff, not the fetch: checkout already created the ref, so a failed
# refresh is harmless, but an unresolvable ref would emit every flag as false.
if ! git rev-parse --verify --quiet "origin/${BASE_REF}" >/dev/null; then
printf '::error::Cannot resolve origin/%s. In CI, check out with fetch-depth: 0.\n' "$BASE_REF" >&2
exit 1
fi
# --no-renames: rename detection reports only the destination, so moving a file
# out of a gated area would drop the source path from every filter.
files="$(git diff --no-renames --name-only "origin/${BASE_REF}...HEAD")"
echo "Changed files:"
printf '%s\n' "$files" | sed 's/^/ /'
echo
flag() { # $1=name $2=regex
if grep -qE "$2" <<< "$files"; then emit "$1" true; else emit "$1" false; fi
}
flag go "$GO_RE"
flag js "$JS_RE"
flag i18n "$I18N_RE"
if [ -n "$(grep -vE "$DOC_ONLY_RE" <<< "$files")" ]; then
emit build true
else
emit build false
fi