All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
--ignoredoublestar (**) glob patterns — patterns like**/generated/**,docs/**/*.md, and**/node_modulesnow resolve correctly at any directory depth. The scanner previously usedfilepath.Match, which silently ignored**patterns. It now usesgithub.com/bmatcuk/doublestar/v4for both relative-path and basename matching. Simple glob patterns (*.pb.go) and substring patterns (generated) continue to work unchanged.
- Ecosystem documentation — README now includes an Ecosystem section linking
addlicense-action,addlicense-npm,addlicense-winget, and the Homebrew tap. The GitHub Action example was corrected (GregoireF/addlicense-action@v1, not the core repo).
--sbom <file>flag — generates a minimal SPDX 2.3 tag-value document from the existing licence headers in the scanned files. No files are modified. Output path accepts-for stdout. For each file:LicenseInfoInFilecomes from theSPDX-License-Identifier:header in the file;LicenseConcludedfalls back to--licensewhen the file has no header;FileCopyrightTextcomes from the copyright line. Designed for EU Cyber Resilience Act compliance (ENISA CRA guidance, SPDX 2.3 file-element section).internal/sbompackage — pure-Go SPDX 2.3 tag-value document builder. No external SPDX library dependency — keeps the binary lean and the build reproducible.Build(Document) stringgenerates valid SPDX 2.3 with document header, package section, file elements, andCONTAINSrelationships. Deterministic output (walk order fromfilepath.WalkDir).injector.ExtractLicenseInfo(path)— reads the first 20 lines and returns theSPDX-License-Identifiervalue and the first copyright line. Used by the SBOM mode to audit existing file state without modifying files.Sbom stringinpkg/addlicense.Options— SBOM mode available from the public Go library API.
validateOptsrefactored intovalidateOutputOpts+validateModeMutualExclusionto keep cyclomatic complexity under the gocyclo-15 lint threshold after adding the new--sbomexclusions.pkg/addlicense.DefaultIgnoreis now an independent copy of the internal default list (not an alias). Appending to it is safe across callers without risk of mutating the shared source.
- PATH conflict documented —
go installoverwrites google/addlicense when both are installed. README now warns and recommends binary download or Docker to avoid the collision.
The pkg/addlicense package is now under semantic versioning freeze. Any rename, removal, or signature change to an exported symbol requires a v2.0.0 major version bump. internal/ packages remain free to evolve.
Extended test suite:
internal/sbom/sbom_test.go: 8 tests — header section, file with/without header, fallback licence, multiple files, namespace, defaults, path normalisation.internal/injector: 5 newTestExtractLicenseInfotests — both fields present, REUSE style, no header, file not found, beyond scan window.tests/integration: 4 new SBOM tests — writes file, unlicensed fallback, mutual exclusion, stdout.tests/cli: 3 new SBOM tests — writes file, stdout, mutual exclusion.pkg/addlicense: 1 new SBOM test.
-
Extended language support — 8 new file types recognised out of the box:
Extension(s) Comment style Language .lua-- lineLua .nix# lineNix .zig// lineZig .dockerfile# lineDockerfile (named variants, e.g. app.dockerfile).mk# lineMakefile fragments .md.mdx<!-- block -->Markdown / MDX (HTML comments are invisible in rendered output) -
--author-fileflag — reads copyright holders from a text file, one per line. Lines starting with#and blank lines are ignored. Mutually exclusive with--author. Useful for organisations that maintain anAUTHORSfile or CODEOWNERS list.# AUTHORS Alice Dupont Bob Martinaddlicense --license MIT --author-file AUTHORS . -
Extended test suite:
internal/header: 7 newTestLangForcases for new extensions.tests/integration: 5 new tests —TestRun_NewLang_Lua,_Nix,_Zig,_Dockerfile,_Markdown.tests/cli: 4 new tests —TestCLI_AuthorFile_InjectsAllAuthors,_MutualExclusionWithAuthor,_NotFound,_Empty_Error.
- Public Go API (
pkg/addlicense) —addlicenseis now importable as a Go library without invoking a subprocess. Import path:github.com/GregoireF/addlicense/pkg/addlicense. The public surface is a distinctOptionsstruct and aRun(Options) errorfunction, backed by the matureinternal/implementation. This allows IDE plugins, CI runners, and code generators to embed addlicense natively.Optionsmirrors all CLI flags:License,Author,Template,Ignore,Year,Paths,CheckOnly,Reuse,Remove,Update,DryRun,Diff,YearRange,Dep5,Verbose,Quiet,Format,Workers.DefaultIgnorere-exported frominternal/configso callers can extend without modifying the original slice.FormatTextandFormatJSONconstants for theFormatfield.- A thin
toInternalconversion layer mapspkg.Options → config.Options;internal/packages can refactor freely without breaking the public API.
pkg/addlicense/addlicense_test.go— 12 tests covering: licensed/unlicensed check mode, header injection, idempotence, diff mode (no write), dry run (no write), remove, REUSE mode, multi-author, and mutual-exclusion validation.
- File permission preservation —
injector.Injectandinjector.Removenow preserve the original file's permission bits (info.Mode().Perm()) instead of always writing0o644. Previously, executable scripts (chmod +x deploy.sh) would silently lose their execute bit after anaddlicenserun. The fix is a no-op on Windows where the OS does not enforce Unix permission bits.
- Coverage: 89.1% → 90.4% — 10 new integration tests covering
diffFileerror paths, remove-with-header, remove-without-header, update-no-header, and malformed-template error propagation. Dead code removed fromemit(text-mode diff branch was unreachable sincehandleResultsforces JSON when--diffis set).emitfunction is now 100% covered. - Documentation —
--ignorematching rules documented (glob + substring semantics). Multi-author +--updatereplace semantics documented in README.
TestInject_PreservesPermissionsandTestRemove_PreservesPermissions(skipped on Windows).
- Multi-author support —
--authornow accepts a comma-separated list of copyright holders (e.g.--author "Alice, Bob"). Each author gets its own copyright line in the rendered header; single-author behaviour is unchanged. Works with--reuse(emitsSPDX-FileCopyrightText:per author) and--year-range. --diffflag — emits JSON Lines ({"file":"…","status":"diff-add|diff-update|diff-remove","header":"…"}) for every file that would be modified, without writing to disk. Exit 1 if any file would change, exit 0 if the tree is already up to date. Designed for PR validation pipelines: "does this PR introduce files without license headers?" Mutually exclusive with--check.- Error double-print fix —
SilenceErrors: trueadded to the Cobra root command; errors are now printed exactly once (bymain) instead of twice (Cobra + main). - Extended test suite:
tests/integration/: 9 new tests — multi-author (two authors, REUSE mode, single-author unchanged);--diff(no-write exit 1, already-licensed exit 0, JSON with header field,--updateemits diff-update,--checkconflict).tests/cli/: 4 new tests —TestCLI_MultiAuthor,TestCLI_Diff_NoWrite_ExitsOne,TestCLI_Diff_AlreadyLicensed_ExitsZero,TestCLI_Diff_CheckConflict.internal/injector/bench_test.go— 7 micro-benchmarks (HasHeader, Inject, Remove, ExtractYear, large-file variants).tests/bench/pipeline_bench_test.go— 12 pipeline benchmarks (Add/Check/Idempotent at scale, Workers 1/4/NumCPU).
--year-rangeflag — when combined with--update, reads the original copyright year from the existing header and emitsYYYY-YYYY(e.g.Copyright 2023-2026 Author) instead of overwriting with the current year alone. Falls back silently to a single year when the original year cannot be extracted or equals the current year. Works with--reuse(emitsSPDX-FileCopyrightText: 2023-2026 Author).--dep5flag — generates a REUSE-compliant.reuse/dep5bulk-licence declaration for files that cannot carry inline SPDX headers (images, fonts, binaries, and any other extension not in the language map). The dep5 root defaults to the first path argument. Supports--dry-run(prints[dry-run] would-write: .reuse/dep5).- Native GitHub Action (
action.yml) — composite action usingrunner.osandrunner.archdetection to download the correct binary from the GitHub Release tag, add it to$GITHUB_PATH, and run addlicense. Supportsubuntu-*,macos-*, andwindows-*runners on amd64 + arm64. Usage:uses: GregoireF/addlicense@v0.5.0with optionalargsinput (default:--check .). internal/dep5package — pure Go dep5 document generator;Build(paths, year, author, license)returns the formatted dep5 content.- Extended test suite:
internal/injector: 5 new unit tests forExtractYear— copyright, SPDX-FileCopyrightText, no year, file not found, beyond scan window.internal/dep5: 5 unit tests — with paths, empty paths (header-only), no author, multiple paths (continuation lines), forward-slash path normalisation.tests/integration/: 7 new tests —--year-range(original preserved, same year, no header, with REUSE);--dep5(creates file, no unhandled files, dry-run).
--remove/-Rflag — strips existing license headers from files. Detection uses the same heuristic as the idempotence check (top-20-line scan forspdx-license-identifierorcopyright); only comment blocks that contain a license marker are removed — non-license comment blocks (package docs, etc.) are left untouched. Handles both line-comment (//,#,--) and block-comment (<!-- -->,/* */) styles.--update/-uflag — replaces the existing header with a new one in a single pass (--remove+ inject). The canonical migration workflow:addlicense --update --license EUPL-1.2 .--dry-run/-nflag — previews changes without writing to disk. Emits[dry-run] would-add: <path>,[dry-run] would-remove: <path>,[dry-run] would-update: <path>for every file that would be affected.--verbose/-vflag — prints every processed file, including already-licensed ones (skipped/ok), in addition to the default modified-file output.--quiet/-qflag — suppresses all stdout; errors still go to stderr. Designed for pure CI pipelines where only the exit code matters.--format/-fflag — selects output format:text(default, human-readable) orjson(JSON Lines:{"file":"…","status":"…","error":"…"}). The string flag is extensible to future formats without breaking the CLI contract.--workersflag — controls the number of parallel goroutines (default:runtime.NumCPU()). Files are processed concurrently via a buffered jobs channel and async.WaitGroup-managed worker pool, bounded tomin(workers, len(files)).reuse:field in.addlicenserc.yaml—reuse: truein the config file now activates REUSE/FSFE mode without requiring the--reuseflag on every invocation. CLI flag takes precedence per the usual merge order..pre-commit-hooks.yaml— official pre-commit hook definitions shipped in the repository root. Two hooks:addlicense-check(exit 1 on missing headers, read-only) andaddlicense-add(inject missing headers). Language isgolang; pre-commit installs the binary automatically viago install. Both hooks setpass_filenames: falseandalways_run: true.- Extended test suite:
internal/injector: 8 new unit tests coveringRemove— line-comment removal, shebang preservation, idempotence, non-license-comment safety, block-comment HTML/CSS styles, nonexistent-file error.internal/config: 3 new unit tests coveringreuse:merge — from config file, CLI precedence, default false.tests/integration/: 20 new integration tests covering--remove,--update,--dry-run,--quiet,--verbose,--format json,--workers,--format xml(invalid), all mutual-exclusion flag combinations,reuse:from config file, and parallel processing across 20 files.
- Mutual exclusion validation (
validateOpts):--verbose⊕--quiet,--check⊕ (--remove∨--update),--remove⊕--update— all return descriptive errors before any file I/O. - Runner refactored into
runParallel+processFile+ per-mode functions (checkFile,addFile,removeFile,updateFile) to keep cyclomatic complexity under the gocyclo-15 limit.
--reuse/-rflag — emitsSPDX-FileCopyrightText: <year> <author>instead ofCopyright <year> <author>, complying with the REUSE/FSFE specification. Idempotence works transparently (FileCopyrightTextcontainscopyrightas a substring). Opt-in flag rather than default to preservegrep -r "Copyright"workflows; making it the default would require a major version bump.header.Data.CopyrightLine— pre-formatted string computed once byrunner.buildCopyrightLine(year, author, reuse)and consumed by all built-in templates via{{.CopyrightLine}}; removes per-template{{if .Author}}conditionals and centralises REUSE/non-REUSE logic in one place.- Built-in SPDX templates: EUPL-1.2, AGPL-3.0-only, LGPL-2.1-only, LGPL-3.0-only — EU public sector and strong-copyleft licences (see ROADMAP.md for compliance context).
- golangci-lint v2 configuration (
.golangci.yml): 15 linters beyond the default —bodyclose,exhaustive,goconst,gocritic,gocyclo,godot,misspell,nestif,nilerr,prealloc,revive,unconvert,unparam;goimportsinformatterssection (v2 separates formatters from linters). - Codecov (
.codecov.yml): 90 % coverage threshold enforced on both project and patch. - Extended test suite — coverage ≥ 90 %:
internal/scanner: 4 unit tests — glob ignore patterns, invalid root, no-extension files, markdown/JSON collectioninternal/injector: 5 unit tests — file-not-found, empty file, scan-window boundary, case-insensitive detectioninternal/config: 11 unit tests —Loadpriority rules, YAML/YML/JSON auto-detection, CLI flag override, invalid YAML errortests/integration/: 3 REUSE tests + 8 additional scenarios (block/HTML/SQL comment styles, EU licences, multiple roots, custom templates)
CONTRIBUTING.md— development setup, test commands, lint config decisions, commit/PR conventions, adding-a-language and adding-a-template guides, release process, design principles.CHANGELOG.mdandROADMAP.md— version planning and EU/French compliance context.- GitHub issue templates (
.github/ISSUE_TEMPLATE/): bug report and feature request with structured fields. - GitHub PR template (
.github/PULL_REQUEST_TEMPLATE.md): checklist covering tests, CHANGELOG, lint, coverage. CODEOWNERS— GregoireF as default reviewer on all PRs.
- GoReleaser v2:
brews:key replaced byhomebrew_casks:(removed in v2.10) — Homebrew tap formula now lives atCasks/addlicense.rb; oldFormula/addlicense.rbremoved from tap. - CI coverage:
-coverpkg=./...flag ensures integration tests intests/integration/correctly attribute coverage tointernal/packages. - Test organisation: integration tests promoted from
internal/runner/to dedicatedtests/integration/package (package integration_test), following Go conventions.
internal/header/header.go:Langstruct andlangsmap brought into strict gofmt compliance — gofmt aligns map-literal values per comment-separated group (comments reset the tabwriter), not globally.
- Extended language support:
.html,.vue,.svelte(<!-- -->),.css,.scss(/* */),.proto(//),.sql(--) - Docker image published to GHCR (
ghcr.io/gregoiref/addlicense) on every release tag - Codecov integration — coverage badge in README
.addlicenserc.yamldogfooding — repo now uses its own config file; CI no longer hardcodes--licenseand--authorflags- Dependabot for Go modules and GitHub Actions (weekly, auto-merge on passing checks)
- Auto-merge workflow for Dependabot PRs
- GitHub Actions bumped to Node.js 24 compatible versions:
actions/checkoutv6,actions/setup-gov6,goreleaser/goreleaser-actionv7,golangci-lint-actionv9 - CI caching re-enabled after
go.sumcommitted goreleaser-actionversion locked to~> v2(waslatest)
errchecklint violations:HasHeaderrefactored frombufio.Scanner+defer f.Close()toos.ReadFile— no unchecked close errorf.Close()in injector test helper now checked and fatal on error
- Initial release
- Unified CLI — single command, no subcommands:
addlicense [flags] [path...] - Flags:
--license/-l,--author/-a,--year/-y(defaults to current year),--template/-t,--ignore/-i,--check/-c,--version - Built-in SPDX templates: MIT, Apache-2.0, GPL-3.0-only, MPL-2.0, BSD-2-Clause, BSD-3-Clause
- Generic fallback template for any other SPDX identifier
- Custom header template support via
--template ./header.txt - Config file auto-detection in priority order:
.addlicenserc.yaml,.addlicenserc.yml,.addlicenserc.json,addlicense.json - Idempotent header detection — scans first 20 lines for
SPDX-License-Identifier:orcopyright; already-licensed files are skipped - Shebang line preservation (
#!/usr/bin/env bashstays on line 1) - Supported languages: Go, TypeScript/TSX, JavaScript/JSX, Java, C/C++/H, Rust, Python, Shell/Bash, YAML, Terraform, TOML, Ruby, Swift, Kotlin, Scala, PHP, C#
- Check mode: exit 0 if all files are licensed, exit 1 with list of missing files
- Multi-platform binaries: Linux, macOS, Windows × amd64, arm64 — via GoReleaser
- Homebrew tap:
brew install GregoireF/tap/addlicense - Docker image (
FROM scratch, ~3 MB):ghcr.io/gregoiref/addlicense - Dogfooding: the
license-checkCI workflow runs addlicense on its own source on every push