diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c34bd01..054aac8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,6 +13,43 @@ concurrency: cancel-in-progress: true jobs: + repository-policy: + name: Repository policy + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Set up repository linters + uses: jdx/mise-action@9e7f7633ff6f6d6048a9418a68d48f288f50eb14 # v4.2.3 + with: + install_args: actionlint zizmor + + - name: Lint GitHub Actions + run: | + actionlint -color + zizmor --min-severity medium .github/workflows + + consumer-isolation: + name: Consumer dependency isolation + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Set up Java + uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5 + with: + distribution: temurin + java-version: '21' + + - name: Verify isolated consumer + run: ./scripts/verify-consumer-isolation.sh + build: name: Build (Java ${{ matrix.java }}) runs-on: ${{ matrix.os }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index a4fb56d..f24993d 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -26,6 +26,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Install mdbook-lint shell: bash @@ -65,6 +67,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Check external links uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2.9.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f8dc30..6310bf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic - Documentation checks based on `mdbook-lint` and Lychee. - Consumer-classpath verification for applications that install a single DATEX II model. - Adversarial XML tests covering DTDs, external entities, and recursive entity expansion. +- A single contributor verification command with isolated Maven consumer checks, actionable + failure logs, coverage summaries, and GitHub Actions linting. ### Changed diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2de09f2..8f75cbe 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,9 +11,14 @@ Maven wrapper pins Maven. ```bash mise install -./mvnw verify +./scripts/verify.sh ``` +The verification script checks GitHub Actions syntax and security, verifies the critical XML and +validation modules, tests the consumer dependency graph with an isolated Maven repository, runs +the complete Maven reactor, and prints the final coverage ratios. Its full log is written to +`target/verification/verify.log`. + Read the [architecture guide](docs/architecture.md) before changing module boundaries or adding a dependency. diff --git a/mise.toml b/mise.toml index da805ab..686231b 100644 --- a/mise.toml +++ b/mise.toml @@ -1,5 +1,7 @@ # Developer toolchain pinned for reproducible local and CI builds. -# Maven is provided by the committed Maven Wrapper (./mvnw), so only Java is pinned here. -# Run `mise install` to provision Java, then `./mvnw verify`. +# Maven is provided by the committed Maven Wrapper (./mvnw). +# Run `mise install`, then `./scripts/verify.sh`. [tools] java = "temurin-21.0.11+10.0.LTS" +actionlint = "1.7.12" +zizmor = "1.28.0" diff --git a/scripts/verify-consumer-isolation.sh b/scripts/verify-consumer-isolation.sh new file mode 100755 index 0000000..8fd7372 --- /dev/null +++ b/scripts/verify-consumer-isolation.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +set -euo pipefail + +project_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +temporary_root="$(mktemp -d "${TMPDIR:-/tmp}/datex4j-consumer-isolation.XXXXXX")" +local_repository="${temporary_root}/repository" +dependency_tree="${temporary_root}/dependency-tree.txt" + +cleanup() { + rm -rf "${temporary_root}" +} +trap cleanup EXIT + +cd "${project_root}" + +./mvnw --batch-mode --no-transfer-progress \ + -Dmaven.repo.local="${local_repository}" \ + -DskipTests \ + -Djacoco.skip=true \ + -pl datex4j-xml,datex4j-json,datex4j-model-v3_7 \ + -am \ + install + +./mvnw --batch-mode --no-transfer-progress \ + -Dmaven.repo.local="${local_repository}" \ + -f datex4j-consumer-tests/pom.xml \ + -Dscope=test \ + -DoutputType=text \ + -DoutputFile="${dependency_tree}" \ + dependency:tree + +if ! grep -Eq 'dev\.juherr\.datex4j:datex4j-model-v3_7:' "${dependency_tree}"; then + echo "Expected datex4j-model-v3_7 is missing from the consumer dependency tree." >&2 + exit 1 +fi + +if grep -Eq \ + 'dev\.juherr\.datex4j:(datex4j-model:|datex4j-model-v(2_[0-3]|3_[0-6]):)' \ + "${dependency_tree}"; then + echo "Unexpected generated model dependency found:" >&2 + grep -E 'dev\.juherr\.datex4j:(datex4j-model:|datex4j-model-v[23]_)' \ + "${dependency_tree}" >&2 + exit 1 +fi + +echo "Consumer isolation verified: only datex4j-model-v3_7 is installed." diff --git a/scripts/verify.sh b/scripts/verify.sh new file mode 100755 index 0000000..616b266 --- /dev/null +++ b/scripts/verify.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +set -euo pipefail + +project_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +report_directory="${project_root}/target/verification" +log_file="${report_directory}/verify.log" + +mkdir -p "${report_directory}" +: >"${log_file}" + +run_logged() { + local description="$1" + shift + echo "==> ${description}" | tee -a "${log_file}" + if "$@" 2>&1 | tee -a "${log_file}"; then + return + fi + echo "Verification failed during: ${description}" >&2 + echo "Last 80 log lines:" >&2 + tail -n 80 "${log_file}" >&2 + exit 1 +} + +print_coverage() { + local module="$1" + local report="${project_root}/${module}/target/site/jacoco/jacoco.csv" + if [[ ! -f "${report}" ]]; then + echo "${module}: no JaCoCo report" + return + fi + awk -F, -v module="${module}" \ + 'NR > 1 { missed += $8; covered += $9 } + END { + total = missed + covered; + if (total == 0) { + printf "%s line coverage: 0/0 (0.0%%)\n", module; + exit; + } + printf "%s line coverage: %d/%d (%.1f%%)\n", module, covered, total, 100 * covered / total + }' \ + "${report}" +} + +cd "${project_root}" + +run_logged "GitHub Actions syntax" mise exec -- actionlint -color +run_logged \ + "GitHub Actions security" \ + mise exec -- zizmor --min-severity medium .github/workflows +run_logged \ + "critical XML and validation modules" \ + ./mvnw --batch-mode --no-transfer-progress \ + -pl datex4j-xml,datex4j-validation -am verify +run_logged "isolated consumer dependency graph" ./scripts/verify-consumer-isolation.sh +run_logged \ + "full Maven reactor" \ + ./mvnw --batch-mode --no-transfer-progress verify + +print_coverage datex4j-xml | tee -a "${log_file}" +print_coverage datex4j-validation | tee -a "${log_file}" +echo "Verification succeeded. Full log: ${log_file}"