Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions .github/release-notes/0.1.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# datex4j 0.1.0

The first datex4j release provides a modular Java 21 SDK for reading, writing, validating, and
converting DATEX II publications.

## Highlights

- Generated DATEX II models for versions 2.0–2.3 and 3.0–3.7.
- Version-neutral XML, conformant JSON, and XSD-validation facades.
- Builders and helpers for traffic, SRTI, parking, EV charging, UVAR, and locations.
- Generated OCPI 2.3.0 models with bidirectional charging-infrastructure mapping.
- Per-version model artifacts plus a BOM and an optional all-model aggregate.

## Install

Import `dev.juherr.datex4j:datex4j-bom:0.1.0`, then depend on a facade and the model versions your
application uses:

```xml
<dependencyManagement>
<dependencies>
<dependency>
<groupId>dev.juherr.datex4j</groupId>
<artifactId>datex4j-bom</artifactId>
<version>0.1.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>dev.juherr.datex4j</groupId>
<artifactId>datex4j-xml</artifactId>
</dependency>
<dependency>
<groupId>dev.juherr.datex4j</groupId>
<artifactId>datex4j-model-v3_7</artifactId>
</dependency>
</dependencies>
```

## Known limitations

- Domain builders currently target DATEX II 3.7.
- Conformant JSON fixtures currently cover DATEX II 3.6 and 3.7.
- OCPI mapping intentionally covers charging-infrastructure data rather than every OCPI endpoint.
- As a pre-1.0 release, future minor versions may require migrations documented in the changelog.

See the [API compatibility policy](https://github.com/juherr/datex4j/blob/0.1.0/docs/api-compatibility.md)
and the [changelog](https://github.com/juherr/datex4j/blob/0.1.0/CHANGELOG.md) for details.
261 changes: 261 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
name: Release to Maven Central

on:
workflow_dispatch:
inputs:
version:
description: Release version
required: true
default: 0.1.0
type: string

permissions:
contents: read

concurrency:
group: maven-central-release
cancel-in-progress: false

jobs:
publish:
name: Verify and publish
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: maven-central
env:
CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }}
CENTRAL_TOKEN: ${{ secrets.CENTRAL_TOKEN }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
RELEASE_VERSION: ${{ inputs.version }}
steps:
- name: Checkout main
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false

- name: Set up Java and signing
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5
with:
distribution: temurin
java-version: '21'
cache: maven
server-id: central
server-username: CENTRAL_USERNAME
server-password: CENTRAL_TOKEN
gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
gpg-passphrase: GPG_PASSPHRASE

- name: Validate release inputs
shell: bash
run: |
set -euo pipefail
[[ "${GITHUB_REF}" == "refs/heads/main" ]]
[[ "${RELEASE_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]
actual_version=$(./mvnw --quiet help:evaluate -Dexpression=project.version -DforceStdout)
[[ "${actual_version}" == "${RELEASE_VERSION}" ]]
grep -Fq "## [${RELEASE_VERSION}] -" CHANGELOG.md
test -s ".github/release-notes/${RELEASE_VERSION}.md"
scripts/verify-revapi-allowlist.sh
test -n "${CENTRAL_USERNAME}"
test -n "${CENTRAL_TOKEN}"
test -n "${GPG_PASSPHRASE}"

- name: Verify signing capability
shell: bash
run: |
set -euo pipefail
signing_key=$(gpg --batch --with-colons --list-secret-keys | awk -F: '$1 == "sec" { print $5; exit }')
test -n "${signing_key}"
umask 077
GPG_PASSPHRASE_FILE="${RUNNER_TEMP}/datex4j-gpg-passphrase"
printf '%s' "${GPG_PASSPHRASE}" > "${GPG_PASSPHRASE_FILE}"
export GPG_PASSPHRASE_FILE
git config user.name "datex4j release workflow"
git config user.email "actions@users.noreply.github.com"
git config user.signingkey "${signing_key}"
git config gpg.program "$(pwd)/scripts/git-gpg-wrapper.sh"
git tag --sign --message "Signing check" "__signing-check-${GITHUB_RUN_ID}"
git tag --verify "__signing-check-${GITHUB_RUN_ID}"
git tag --delete "__signing-check-${GITHUB_RUN_ID}"

- name: Check existing tag
shell: bash
run: |
set -euo pipefail
remote_tag=$(git ls-remote --tags origin "refs/tags/${RELEASE_VERSION}^{}" | awk '{print $1}')
if [[ -z "${remote_tag}" ]]; then
remote_tag=$(git ls-remote --tags origin "refs/tags/${RELEASE_VERSION}" | awk '{print $1}')
fi
if [[ -n "${remote_tag}" && "${remote_tag}" != "${GITHUB_SHA}" ]]; then
echo "Tag ${RELEASE_VERSION} already points to ${remote_tag}, not ${GITHUB_SHA}." >&2
exit 1
fi

- name: Verify project
run: ./mvnw --batch-mode --no-transfer-progress verify

- name: Build local Central bundle
shell: bash
run: |
set -euo pipefail
./mvnw --batch-mode --no-transfer-progress \
-Prelease -DskipTests -DskipPublishing=true deploy
scripts/build-central-bundle.sh target/central-bundle.zip "${RELEASE_VERSION}"

- name: Validate Central bundle
id: bundle
shell: bash
run: |
set -euo pipefail
bundle=$(find . -type f -name central-bundle.zip -print -quit)
test -n "${bundle}"
scripts/verify-central-bundle.sh "${bundle}" "${RELEASE_VERSION}"
echo "path=${bundle}" >> "${GITHUB_OUTPUT}"

- name: Test isolated Maven consumer
shell: bash
run: |
set -euo pipefail
bundle_repository=$(mktemp -d)
consumer_repository=$(mktemp -d)
unzip -q "${{ steps.bundle.outputs.path }}" -d "${bundle_repository}"
./mvnw --batch-mode --no-transfer-progress \
-f config/release-smoke/pom.xml \
-Dmaven.repo.local="${consumer_repository}" \
-Ddatex4j.version="${RELEASE_VERSION}" \
-Ddatex4j.repository="file://${bundle_repository}" \
verify

- name: Publish to Maven Central
shell: bash
run: |
set -euo pipefail
base_url="https://repo1.maven.org/maven2/dev/juherr/datex4j"
missing=0
while read -r artifact_id _packaging; do
[[ -z "${artifact_id}" || "${artifact_id}" == \#* ]] && continue
pom_url="${base_url}/${artifact_id}/${RELEASE_VERSION}/${artifact_id}-${RELEASE_VERSION}.pom"
if ! curl \
--connect-timeout 5 \
--max-time 10 \
--fail \
--silent \
--show-error \
--head \
"${pom_url}" >/dev/null; then
missing=1
break
fi
done < config/release/public-artifacts.txt
if [[ "${missing}" -eq 0 ]]; then
echo "Release ${RELEASE_VERSION} is already available from Maven Central; skipping deploy."
exit 0
fi
./mvnw --batch-mode --no-transfer-progress \
-Prelease -DskipTests -DautoPublish=true -DwaitUntil=published deploy

Comment thread
coderabbitai[bot] marked this conversation as resolved.
- name: Wait for Maven Central resolution
shell: bash
run: |
set -euo pipefail
base_url="https://repo1.maven.org/maven2/dev/juherr/datex4j"
for attempt in $(seq 1 30); do
missing=0
while read -r artifact_id _packaging; do
[[ -z "${artifact_id}" || "${artifact_id}" == \#* ]] && continue
pom_url="${base_url}/${artifact_id}/${RELEASE_VERSION}/${artifact_id}-${RELEASE_VERSION}.pom"
if ! curl \
--connect-timeout 5 \
--max-time 10 \
--fail \
--silent \
--show-error \
--head \
"${pom_url}" >/dev/null; then
missing=1
break
fi
done < config/release/public-artifacts.txt
[[ "${missing}" -eq 0 ]] && break
if [[ "${attempt}" -eq 30 ]]; then
echo "Release did not become resolvable from Maven Central in time." >&2
exit 1
fi
sleep 20
done
consumer_repository=$(mktemp -d)
./mvnw --batch-mode --no-transfer-progress \
-f config/release-smoke/pom.xml \
-Dmaven.repo.local="${consumer_repository}" \
-Ddatex4j.version="${RELEASE_VERSION}" \
-Ddatex4j.repository="${base_url%/dev/juherr/datex4j}" \
verify

finalize:
name: Tag and create GitHub Release
needs: publish
runs-on: ubuntu-latest
environment: maven-central
permissions:
contents: write
env:
GH_TOKEN: ${{ github.token }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
RELEASE_VERSION: ${{ inputs.version }}
steps:
- name: Checkout published commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false

- name: Set up Java and signing
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5
with:
distribution: temurin
java-version: '21'
gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
gpg-passphrase: GPG_PASSPHRASE

- name: Create or verify signed tag
shell: bash
run: |
set -euo pipefail
signing_key=$(gpg --batch --with-colons --list-secret-keys | awk -F: '$1 == "sec" { print $5; exit }')
test -n "${signing_key}"
umask 077
GPG_PASSPHRASE_FILE="${RUNNER_TEMP}/datex4j-gpg-passphrase"
printf '%s' "${GPG_PASSPHRASE}" > "${GPG_PASSPHRASE_FILE}"
export GPG_PASSPHRASE_FILE
git config user.name "datex4j release workflow"
git config user.email "actions@users.noreply.github.com"
git config user.signingkey "${signing_key}"
git config gpg.program "$(pwd)/scripts/git-gpg-wrapper.sh"

if git ls-remote --exit-code --tags origin "refs/tags/${RELEASE_VERSION}" >/dev/null 2>&1; then
git fetch --force origin "refs/tags/${RELEASE_VERSION}:refs/tags/${RELEASE_VERSION}"
tagged_commit=$(git rev-list -n 1 "${RELEASE_VERSION}")
[[ "${tagged_commit}" == "${GITHUB_SHA}" ]]
git tag --verify "${RELEASE_VERSION}"
else
git tag --sign --message "datex4j ${RELEASE_VERSION}" "${RELEASE_VERSION}" "${GITHUB_SHA}"
git tag --verify "${RELEASE_VERSION}"
git push \
"https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" \
"refs/tags/${RELEASE_VERSION}"
fi

- name: Create or verify GitHub Release
shell: bash
run: |
set -euo pipefail
if gh release view "${RELEASE_VERSION}" >/dev/null 2>&1; then
existing_tag=$(gh release view "${RELEASE_VERSION}" --json tagName --jq .tagName)
[[ "${existing_tag}" == "${RELEASE_VERSION}" ]]
else
gh release create "${RELEASE_VERSION}" \
--title "datex4j ${RELEASE_VERSION}" \
--notes-file ".github/release-notes/${RELEASE_VERSION}.md" \
--verify-tag
fi
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic

## [Unreleased]

## [0.1.0] - 2026-07-25

### Added

- Generated DATEX II model artifacts for versions 2.0–2.3 and 3.0–3.7, discovered through a
Expand All @@ -21,6 +23,8 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic
- 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.
- A documented supported API boundary and automated Revapi compatibility checks.
- A resumable, manually dispatched Maven Central publication workflow.

### Changed

Expand All @@ -38,6 +42,7 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic
- Hardened XML reading and validation so documents containing DTDs or entity declarations are
rejected before JAXB binding or schema validation.

[Unreleased]: https://github.com/juherr/datex4j/commits/main
[Unreleased]: https://github.com/juherr/datex4j/compare/0.1.0...HEAD
[0.1.0]: https://github.com/juherr/datex4j/releases/tag/0.1.0
[Keep a Changelog]: https://keepachangelog.com/en/1.1.0/
[Semantic Versioning]: https://semver.org/spec/v2.0.0.html
45 changes: 27 additions & 18 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,28 +135,37 @@ version runbook, BOM, examples, and relevant data documentation in the same chan

## Public API stability

datex4j follows Semantic Versioning and treats public types and methods as compatibility
commitments. Document additive changes in [CHANGELOG.md](CHANGELOG.md). Highlight breaking changes
before release and include migration guidance.
datex4j follows the [API compatibility policy](docs/api-compatibility.md). Revapi checks every
module against the latest final release during `verify`; implementation packages listed in the
policy are excluded. Validate configuration changes explicitly:

## Release workflow
```bash
./mvnw revapi:validate-configuration
```

Artifacts publish to Maven Central through the Sonatype Central Portal with the opt-in `release`
profile. The profile adds source, Javadoc, signing, and central-publishing plugins. `examples` and
`datex4j-integration-tests` are not published.
The allowlist in `config/revapi/accepted-differences.json` is intentionally empty, and the
`revapi.differences` transform is disabled while it remains empty. Every exception must enable the
transform, identify the exact difference, use a justification beginning with `Migration:`, and
repeat that migration in `CHANGELOG.md`. Run `scripts/verify-revapi-allowlist.sh` after any change
to the allowlist.

Release prerequisites remain outside the repository:
## Release workflow

Artifacts publish to Maven Central through the manually dispatched `Release to Maven Central`
workflow. It only accepts `main`, uses the protected `maven-central` environment, validates a local
Central bundle and an isolated Maven consumer, then publishes automatically. A separate job creates
the signed tag and GitHub Release only after Central reports `published`.

- a published GPG signing key;
- Central Portal credentials in `~/.m2/settings.xml` under server id `central`.
The environment must provide `CENTRAL_USERNAME`, `CENTRAL_TOKEN`, `GPG_PRIVATE_KEY`, and
`GPG_PASSPHRASE`. The corresponding public key must be available from a public keyserver.

Release steps:

1. Run `./mvnw verify` and confirm the working tree is clean.
2. Update `CHANGELOG.md`, moving relevant `Unreleased` entries into
`## [X.Y.Z] - YYYY-MM-DD`. Keep an empty `Unreleased` section and add comparison links for the
release and the next development cycle.
3. Remove `-SNAPSHOT`, set the release version, and create the release tag.
4. Run `./mvnw -Prelease deploy`.
5. Review and release the deployment in the Central Portal because `autoPublish=false`.
6. Open the next `-SNAPSHOT` version and restore an `Unreleased` changelog section.
1. Open and merge a release PR containing the final version, dated changelog section, and versioned
release notes.
2. Dispatch `Release to Maven Central` from `main` with that version.
3. Verify Central resolution, the signed tag, and the GitHub Release.
4. Open a separate PR for the next `X.Y.Z-SNAPSHOT` version and an empty `Unreleased` section.

The workflow safely resumes when its signed tag or GitHub Release already exists at the same
commit. It refuses conflicting tags or releases.
Loading
Loading