Skip to content

About

Repository with tools and technology that supports the Software Engineering Done Right API-Only paradigm

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Arc-E-Tect API-Only

Vulnerability Scan npm Gradle Plugin Portal MIT

Note
Where the links in this document go

Every link ends with a marker saying where it leads, so you can tell before you follow it:

↗

An external site, outside this repository.

→

Another file in this repository.

§

A section of this same file.

Tooling for treating an API description as a published artifact rather than a file someone copies around.

Component What it is

API-Only Publisher →

A Node.js command-line tool. Builds one self-contained, linted document per contract from a library of reusable fragments, stamps it with that contract’s own version, and publishes it: to a directory, a Maven repository, npm, or a GitHub release. Published as @arc-e-tect/api-only-publisher ↗, and as a sealed, signed Docker image for vetted CI pipelines, arcetect/api-only-publisher ↗, also at ghcr.io/arc-e-tect/api-only-publisher: The Docker image →.

API-Only Subscriber →

A Gradle plugin. Fetches a published contract into a build at the version the build declares, verifies it against a committed lockfile, and fails when the contract has drifted. Published to the Gradle Plugin Portal ↗.

API-Only TranscriberJ →

A Gradle plugin, building on the Subscriber. Generates a Java class tree from a subscribed contract: bodies, field descriptions and constants for contract tests and stubs, with room for dialects as separate emitter libraries: Spring REST Docs descriptors, and Microcks conformance tests for a contract’s events. Published to the Gradle Plugin Portal ↗.

Documentation

Start at the documentation →. It helps you pick the use case that fits your situation, and gives each an overview, a step-by-step tutorial, and prompts that have Claude Code or Codex build it in your project, test first.

The contract comes from Your project implements it Your project calls it

Your own project

Provider use case 1 →

Client use case 1 →

A specification library in the same repository

Provider use case 2 →

Client use case 2 →

A specification library in another repository

Provider use case 3 →

Client use case 3 →

A published release, and nothing more

Provider use case 4 →

Client use case 4 →

A project that implements one contract and calls others combines both columns, as implementing one contract and calling others → describes.

How the Publisher and the Subscriber work together → explains how the two tools correspond, and where each part lives in the code. The run-book → says how to operate them, and what to do when something fails.

Examples

Runnable projects that use the tools as any other project would: each is a standalone Gradle build, against the released versions, with a README that says what to look at and what to try.

Example What it shows

examples/transcriberj/generated-contract-tests/ →

A small Spring Boot service, and everything the API-Only TranscriberJ and its REST Docs and WireMock emitters generate for it: schema classes in every test suite, generated contract tests run against the real service and against a WireMock double, the fixtures that make them pass, the stubs as an archive and a Docker image, and published documentation from a documentation test suite.

An example is versioned independently of every component, and is no part of any component’s build, coverage or release: it has a build workflow of its own, whose path filters cover the example alone.

Why they share a repository

They are one design with two halves, and the seam between them is a single artifact: an archive containing the built documents and a manifest.json describing them. Neither knows anything else about the other.

That seam is the whole point, and it is also exactly the kind of thing that rots when its two sides live apart. A field added to the manifest by a producer that no consumer reads, or a consumer that starts requiring a field no producer writes, is a bug that only exists between two repositories and gets noticed by neither. Keeping them together means the contract between them is changed in one commit, reviewed as one change, and cannot drift silently.

They are nevertheless versioned independently. A Gradle consumer and an npm consumer are different people with different upgrade cadences, and a change to one half is no reason to make the other half’s users take a new version. The manifest carries a schemaVersion so that a producer and a consumer of different vintages can still tell whether they understand each other.

Development

# Install the repository's Git hooks first: they check for committed secrets and
# require actionlint when a workflow changes.
./scripts/setup-hooks.sh

# Publisher
npm ci  --prefix api-only-publisher
npm test --prefix api-only-publisher

# Subscriber
cd api-only-subscriber && ./gradlew build

# TranscriberJ
cd api-only-transcriberj && ./gradlew check

# The reference API every tool is tested against: rebuild it after changing a fragment
cd reference-api && npm run apionly -- build

# An example, against the released versions of the tools
cd examples/transcriberj/generated-contract-tests && ./gradlew check

reference-api/ → is the specification library the tools are tested against: two services, system-admin and user-account, built with the Publisher in this repository. It is not released; its built contracts are committed, and its CI fails when they are not what the fragments build.

To change either tool, read Where a change belongs → first.

Dependency updates

Every project’s dependencies are updated by a dependency updater, which commits them as maintenance and releases a patch, except one: the Publisher’s toolchain, which is part of the Publisher and changes in a release of its own.

Project Dependencies Changed by

api-only-publisher/ →

yaml, and semantic-release and its plugins

A dependency updater

api-only-publisher/docker/toolchain/ →

Redocly CLI and AsyncAPI CLI, the bundlers the Publisher pins and its Docker image installs

api-only-publisher/scripts/toolchain-update.sh →, Moving the Publisher’s own pins →. Its manifest says "x-updater": "report-only": a dependency updater only reports a newer version and what npm audit finds.

api-only-subscriber/ →

Gradle wrapper and version catalog; npm: semantic-release and its plugins

A dependency updater

api-only-transcriberj/ →

Gradle wrapper and version catalog; npm: semantic-release and its plugins

A dependency updater

examples/transcriberj/generated-contract-tests/ →

Gradle wrapper and version catalog; npm: the released Publisher

A dependency updater

sedr_utils/nvd-cache-primer/ →

Gradle wrapper and version catalog

A dependency updater

reference-api/ →

None: it runs the Publisher in this repository

Nothing to update

.github/workflows/ →

GitHub Actions, pinned by commit SHA

A dependency updater

Whatever updates the dependencies has to run each npm project’s npm test once all of them are updated, and push nothing when one fails: a project’s tests may read another project’s files, as the Publisher’s read its toolchain’s manifest.

Quality gates

Every component gates its own build on coverage, and every gate was checked by making it fail before trusting it to pass.

Publisher Subscriber TranscriberJ

Coverage

npm test enforces 90% of lines and functions, 80% of branches

JaCoCo, 0.9 minimum, wired into check

JaCoCo, 0.9 minimum, wired into check

Measured

94.8% lines, 95.8% functions, 88.9% branches

97.1% instructions, 90.8% branches

97.2% instructions, 89.9% branches

Exclusions

none

none

none

Dependencies

npm audit of the runtime dependencies, failing at moderate and above; the release toolchain’s findings are reported, not enforced

OWASP dependency-check, as the Gradle plugins run it

OWASP dependency-check, as the Gradle plugins run it

Coverage gates a release; dependency scanning does not. A vulnerability is discovered by someone else, at a time unrelated to anything this repository does, so gating a release on it means an upstream CVE blocks an unrelated release while a quiet week leaves a known problem unreported. Both scans run weekly instead, in nvd-cache-refresh.yml, which first refreshes the shared NVD database that dependency-check needs and that would otherwise be downloaded per release.

Neither coverage rule excludes anything. The usual reason to exclude a plugin, its extension and its tasks is that TestKit runs them in another process where the coverage agent cannot see them, so they report zero however well they are tested, and the rule is made to look away from precisely the code worth measuring. The answer taken here is to test those classes in-process as well, so the number means what it says.

Releasing

Every component releases through semantic-release, driven by Conventional Commits, each with its own tag namespace (api-only-publisher-v*, api-only-subscriber-v*, api-only-transcriberj-v*) so their version histories are genuinely independent.

The TranscriberJ’s emitter interface is part of the plugin, so one release publishes both.

Each is seeded with a -v0.0.0 tag, and release/first-release-patch-analyzer.js forces the first real release to be a patch, so a new component starts at 0.0.1 rather than semantic-release’s default 1.0.0.

Before 1.0.0, the same analyzer releases a breaking change as a minor version, not a major one: a 0.x component is still in development, and the READMEs of the Publisher →, the Subscriber → and the TranscriberJ → each say any release may break compatibility. From 1.0.0 on, a breaking change releases a major version. No commit takes a component to 1.0.0: reaching it is a decision.

A commit that changes only a component’s README.adoc releases nothing: it documents the component without changing what is published, and the next release carries it. Run that component’s release workflow manually when a README has to reach the registry sooner.

Required repository secrets:

Secret Used for

SEMANTIC_RELEASE_TOKEN

Tagging, the release commit, and GitHub releases. Needs a role that can bypass the commit-push-main ruleset, since a release pushes to a protected branch.

GRADLE_PUBLISH_KEY / GRADLE_PUBLISH_SECRET

Publishing the Subscriber and the TranscriberJ to the Gradle Plugin Portal.

DOCKERHUB_TOKEN / DOCKERHUB_DESCRIPTION_TOKEN

Pushing the Publisher’s Docker image to Docker Hub, and updating its description there; with the variables DOCKERHUB_NAMESPACE and DOCKERHUB_USERNAME. Each token’s scope is in the Publisher’s maintainer section →.

The Publisher needs no npm credential: it publishes through npm trusted publishing ↗, exchanging a short-lived OIDC token for publish rights, and attaches a provenance attestation tying each tarball to the commit and workflow run that produced it. Its Docker image is pushed to GHCR with the job’s GITHUB_TOKEN, and signed keyless with the job’s OIDC token. Nothing long-lived is stored.

About

Repository with tools and technology that supports the Software Engineering Done Right API-Only paradigm

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages