Files
member-console/scripts/spdx-headers.sh
T
cgalo5758 f340def385 Take spdx-headers.sh's root from its own location
spdx-headers.sh changed into `git rev-parse --show-toplevel` before
stamping. Run from a copy of the repository that has no .git of its
own, such as a git archive export under the working tree, git names
the enclosing checkout, so the copy stamped the checkout's tracked
files instead of its own. The script now takes as its root the
directory above its own scripts/ directory. When git does not name
that directory as the top of a checkout, it prints a refusal on stderr
and exits 2 before it reads any file. Run from its own checkout it
behaves as before, which is how `make spdx-headers` and
`make sqlc-generate` call it.

TestSPDXHeadersStampsOnlyItsOwnCheckout builds a scratch git checkout
with one tracked Go file and, under it, an export holding the same
files without a .git. Run from the export, the script must exit 2 and
leave both Go files unstamped; run from the checkout's own copy, it
must stamp the checkout's file and not the export's. At the parent the
export case fails: the script exits 0 and stamps the checkout's a.go.
The checkout case passes on both sides.

Run in the working tree, the new script stamped 0 of 934 eligible
files and changed no path. go vet, gofmt, bash -n and make lint pass.
2026-10-15 20:34:33 -05:00

122 lines
4.5 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Stamp SPDX license headers on hand-written source files.
#
# member-console is dual-licensed: AGPL-3.0-only for everyone, or a separate
# commercial license from the copyright holder (see COMMERCIAL.md). Every
# hand-written source file carries the dual identifier so the offer is legible
# from the file itself, not only from the repository root.
#
# Covered: Go, SQL migrations and seeds, the Go HTML templates under
# internal/, and the first-party JavaScript and CSS under internal/. Not
# covered: sqlc query files (sqlc copies every comment preceding the first
# "-- name:" line into the generated Go as that query's doc comment, so a
# header there pollutes the generated code; the generated .sql.go beside each
# query file carries the header instead), the vendored browser assets listed
# below (their own licenses, see NOTICE), files under a testdata directory
# (tests read them byte for byte), configuration, shell, documentation,
# images. Repository-wide coverage of those, and a check that fails on a bare
# new file, is what REUSE tooling provides; adopting it is deferred until CI
# exists (forge issue #111).
#
# sqlc rewrites its output on every run and has no option for emitting a
# header, so `make sqlc-generate` invokes this script afterwards to restore the
# headers it drops. Run `make spdx-headers` to stamp the whole tree.
#
# Usage:
# scripts/spdx-headers.sh # every tracked eligible file
# scripts/spdx-headers.sh DIR [DIR..] # every eligible file on disk under
# # DIR, including files not yet tracked
#
# Idempotent: a file whose first lines already carry an SPDX-License-Identifier
# is left untouched, so a changed identifier must be rewritten deliberately.
#
# Templates get the {{- /* */ -}} form: an HTML comment would be sent to every
# browser, and the trim markers keep the rendered output byte-identical.
set -euo pipefail
IDENTIFIER="AGPL-3.0-only OR LicenseRef-Commercial"
COPYRIGHT="2025-2026 Christian Galo"
# Third-party assets redistributed under their own licenses; see NOTICE.
VENDORED=(
internal/embeds/static/bootstrap.bundle.js
internal/embeds/static/bootstrap.css
internal/embeds/static/htmx.min.js
internal/embeds/static/sortable.min.js
)
# The tree to stamp is the checkout this script belongs to. Inside a copy of
# the repository that has no .git of its own, such as an export under the
# working tree, git would name the enclosing checkout instead, so the script
# refuses rather than stamp files the caller did not point it at.
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
top="$(git -C "$root" rev-parse --show-toplevel 2>/dev/null || true)"
if [ "$top" != "$root" ]; then
echo "spdx-headers: $root is not the top of a git checkout; run the copy of this script inside the checkout to stamp" >&2
exit 2
fi
cd "$root"
# Go and SQL are eligible anywhere; templates and assets only under internal/,
# which keeps archived review pages and exploration dossiers out.
eligible() {
local f="$1"
for v in "${VENDORED[@]}"; do
[ "$f" = "$v" ] && return 1
done
case "$f" in
*/testdata/*) return 1 ;;
*/queries/*.sql) return 1 ;;
*.go | *.sql) return 0 ;;
internal/*.html | internal/*.js | internal/*.css) return 0 ;;
*) return 1 ;;
esac
}
header() {
case "$1" in
*.go | *.js)
printf '// SPDX-License-Identifier: %s\n// SPDX-FileCopyrightText: %s\n\n' "$IDENTIFIER" "$COPYRIGHT"
;;
*.sql)
printf -- '-- SPDX-License-Identifier: %s\n-- SPDX-FileCopyrightText: %s\n\n' "$IDENTIFIER" "$COPYRIGHT"
;;
*.css)
printf '/* SPDX-License-Identifier: %s */\n/* SPDX-FileCopyrightText: %s */\n\n' "$IDENTIFIER" "$COPYRIGHT"
;;
*.html)
printf '{{- /* SPDX-License-Identifier: %s */ -}}\n{{- /* SPDX-FileCopyrightText: %s */ -}}\n\n' "$IDENTIFIER" "$COPYRIGHT"
;;
esac
}
if [ "$#" -gt 0 ]; then
mapfile -t candidates < <(find "$@" -type f \( -name '*.go' -o -name '*.sql' -o -name '*.html' -o -name '*.js' -o -name '*.css' \) | sed 's#^\./##' | sort)
else
mapfile -t candidates < <(git ls-files -- '*.go' '*.sql' 'internal/*.html' 'internal/*.js' 'internal/*.css')
fi
stamped=0
eligible_count=0
for f in "${candidates[@]}"; do
[ -n "$f" ] || continue
eligible "$f" || continue
eligible_count=$((eligible_count + 1))
if head -n 3 "$f" | grep -q 'SPDX-License-Identifier:'; then
continue
fi
tmp="$(mktemp)"
{
header "$f"
cat "$f"
} >"$tmp"
# Copy through rather than mv so the file keeps its original mode.
cat "$tmp" >"$f"
rm -f "$tmp"
stamped=$((stamped + 1))
done
printf 'spdx-headers: stamped %d of %d eligible files\n' "$stamped" "$eligible_count"