|
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:
|
This run-book says how to invoke both tools in normal operation, once a setup from one of the use cases exists, and what to do when something fails. It describes the Publisher and the Subscriber as they are on this repository’s main branch; the badges at the top of the Publisher’s README → and the Subscriber’s README → show each one’s latest release. How the Publisher and the Subscriber work together → explains the mechanism behind every step.
| Role | Does |
|---|---|
Contract author |
Changes fragments, raises the contract’s version, and runs the Publisher’s |
Implementer or consumer |
Declares the contract version a project builds against, runs |
The build |
In provider and client use cases 1 and 2, and the combined use case, runs the Publisher and the Subscriber in one Gradle build. |
The release job |
In provider use case 3, publishes every contract whose version is new, and tags it. |
Run in the directory that holds apionly.yaml and the Publisher’s package.json, always through the npm script.
| Command | What it does | When |
|---|---|---|
|
Installs the Publisher at the version |
After cloning, and whenever |
|
Builds, stamps and lints every contract, or one, and logs each operation that has no example (a warning unless |
After changing fragments or a version file |
|
Lints every built contract, and fails on unreferenced fragments. |
After |
|
Lists the contracts a change reaches. |
Before raising versions; in release checks |
|
Packs and ships built contracts. |
From the build (provider and client use cases 1 and 2) or the release job (provider use case 3) |
|
Builds and publishes a pre-release of the version in the contract’s version file. |
When a consumer needs an unfinished contract |
|
Lists the contracts and what each builds. |
When in doubt |
|
Shows each contract’s files and hash. |
When investigating what a fragment affects |
Never run npx api-only-publisher: that unscoped name is not this package.
| Task | What it does |
|---|---|
|
In provider and client use cases 1 and 2, and the combined use case: builds, publishes and lints the contracts, fetches and verifies them, and runs the tests. In provider use case 4, and client use cases 3 and 4: fetches, verifies and tests. |
|
Fetches every subscribed contract, and updates |
|
Checks the fetched documents against |
|
After upgrading the Subscriber: adds new defaulted properties to the |
-
Edit the fragments.
-
Raise
versionin the contract’s<target>.bundle.properties: major for a breaking change, minor for an additive one, patch for anything else. -
In provider use cases 1 and 2, raise
apiContractVersionin the implementing project’sgradle.propertiesto the same version, and run./gradlew check; it printsUpdated <target> from <old> to <new>. -
Commit the fragments, the version changes and the updated
apionly.locktogether.
In provider use case 3, the pull request changes the fragments and the version file; consumers adopt the release afterwards, as in Taking a new version §.
A shared fragment changes every contract that reaches it.
-
Edit the fragment.
-
List the contracts it reaches:
npm run apionly — changed --since HEAD --quiet. -
Raise the version of each of those contracts, and, in provider use cases 1 and 2, the
apiContractVersionof each implementing project. -
Run
./gradlew check, or the release check in provider use case 3, and commit everything together.
-
Add a bundle root and its
<target>.bundle.propertiesto the library. -
Add the target to
targets:inapionly.yaml. -
In provider use case 2, add a project named after the target, with
apiContractVersion; in provider use cases 3 and 4, and client use cases 3 and 4, subscribe from the projects that implement or call it after the first release. -
Run
npm run apionly — buildandnpm run apionly — lint.
Remove every $ref to it, then delete the file.
A fragment that stays behind unreferenced fails lint.
-
Change
apiContractVersionin the project’sgradle.properties, or on the subscription; for an API the project calls, theapiContractVersionof itssubscribeAsClientblock. To build against another version once, without changing anything, pass-PapiContractVersion=<version>; it overrides every other setting, but not a called API’s. -
Run
./gradlew check; it printsUpdated <target> from <old> to <new>. -
Commit the version change and
apionly.locktogether.
-
Producer:
buildandpublishwith--pre-release rc.1, which publishes<version>-rc.1for the version in the contract’s version file. -
Consumer: subscribe to that version, with
allowPrerelease = trueon the subscription. -
At the release: the consumer takes the released version and removes
allowPrerelease.
On the main branch, the release job runs npm ci, build, lint, and a script that publishes every contract whose version has no <target>-v<version> tag, then tags it and pushes the tags.
A pull request runs a script that fails when a changed contract’s version is already tagged.
Both scripts are in provider use case 3’s tutorial →.
Never delete or move a release tag, and never republish a released version.
| Tool | Where it is pinned | Upgrade |
|---|---|---|
API-Only Publisher |
|
|
Redocly CLI, AsyncAPI CLI |
|
Change the pinned version |
Node.js |
Your CI configuration |
Change the version |
API-Only Subscriber |
The |
Change the version, then run |
A Publisher, bundler or Node.js upgrade can change the published documents without any fragment changing. Find out before committing the upgrade:
-
Build with the versions in force now, and set
build/dist/(or yourbuild.dist) aside. -
Upgrade, build again, and
diff -rthe two. -
No difference: commit the upgrade.
-
A difference in formatting or ordering: raise the patch version of each contract that differs, adopt them, and commit everything together.
-
A difference in what a contract says: treat it as a contract change, as in Changing a contract §.
From Publisher 0.3.0 or earlier, every OpenAPI document with components built from fragments gains an x-fragment-path on each such component.
That is an addition to the contract: raise the minor version of each of those contracts, or set defaults.openapi.fragmentPaths: false in apionly.yaml until you do.
From Publisher 0.4.x or earlier, every AsyncAPI document gains an x-fragment-path on each fragment the bundler inlined, likewise: raise the minor version of each contract with an AsyncAPI document, or set defaults.asyncapi.fragmentPaths: false until you do.
What a change to x-fragment-path means for a contract’s version → explains why.
A Subscriber upgrade does not change the contracts, so apionly.lock should not change; if it does, read the Subscriber’s release notes before committing it.
| Setup | CI |
|---|---|
Provider and client use cases 1 and 2, and the combined use case |
Node.js and Java; |
Provider use case 3, contracts repository |
Pull requests: |
Provider use cases 3 and 4, and client use cases 3 and 4 |
Java; |
A project implements at most one contract, at the version its apiContractVersion declares.
Two implemented contracts in one project would share one version, one classpath root and one release, so neither could change without the other.
Put each in a project of its own, in a multi-project build: each project applies the Subscriber, subscribes to its contract, and declares apiContractVersion in its own gradle.properties.
The Subscriber’s README → shows the setup.
A second subscribe to a different contract fails the build, and the message suggests subscribeAsClient for a contract the project calls instead.
A project may call any number of APIs, next to the contract it implements.
It declares each with subscribeAsClient('<target>') { apiContractVersion = '<version>' }.
Each gets every check the implemented contract gets: the manifest check, its own entry in apionly.lock, the refusal of changed bytes under a locked version, and verifyApiSpec as part of check.
Its documents are on the classpath under contracts/<target>/, and it sets its own version, because apiContractVersion is the implemented contract’s.
When its provider publishes somewhere else than the project’s other contracts, it sets a channel { } of its own; every setting it leaves out comes from the project’s channel.
The Subscriber’s README → has the details.
In a build that fetches before verifying, such as ./gradlew check, the fetch restores the published document and the edit disappears.
./gradlew verifyApiSpec on its own reports the edit.
Change the contract in the library, never the fetched document.
| Message | Meaning | What to do |
|---|---|---|
|
The Publisher is not installed in this directory. |
|
|
|
Run it as |
The captured output starts with |
npm printed the script’s banner, and something is parsing the output. |
Run it as |
|
The command ran outside the specification library. |
Run it from the directory that holds |
|
The configuration is newer than the Publisher. |
Upgrade the Publisher, as Upgrading the tools § describes. |
|
Git noise: the Publisher asks git for provenance, and the directory is not a repository, or has no commit yet. |
Nothing. The document is built and published either way; commit the repository and it stops. |
|
The change is outside every target’s closure, or it is not committed yet. |
Check with |
|
A published contract has no version file. |
Create |
|
The version file holds a pre-release. |
Put the release version in the file, and use |
|
An old invocation. |
Remove |
|
A placeholder has no |
Add the file, or fix the token. |
|
The bundle path is wrong; it is relative to |
Fix |
|
YAML files under the source root that no contract references. |
Reference each from a contract, or delete it. |
|
Lint errors, listed above that line. |
Fix the fragments. |
|
The library set |
Add the example to the fragment, or relax the setting (Give every operation an example →). |
|
The document was built for another version than the one being packed. |
Run |
|
A remote Maven upload without a token. |
Set the variable in the release job’s environment. |
|
The token was rejected. |
Check the token, and that the repository accepts a bearer token. |
|
A contract changed, but its version is already released. |
Raise the contract’s version in the pull request. |
| Message | Meaning | What to do |
|---|---|---|
|
No |
Set |
|
A pre-release, without asking for one. |
Take a released version, or set |
|
Nothing is published at that version in the |
Make the contract’s version and the project’s version agree. |
|
Gradle could not resolve the archive from the Maven repository. |
Check the repository entry, the credentials, the coordinates, and that the version was released. |
|
A contract changed under a version that was already locked. |
Raise the contract’s version and adopt it; or undo the change. |
|
A released version in a registry came back with different documents. |
Ask the producer, and take a new version when they release one; do not delete the lock entry. |
|
The archive is not the version it was published as. |
Report it to the producer. |
|
The archive is corrupt or was tampered with. |
Fetch again; if it persists, report it to the producer. |
|
A fetched document was edited. |
|
|
|
|
|
A subscription that was never fetched and committed. |
|
|
A second implemented contract in one project. |
If the project calls |
|
The same contract declared as implemented and as called. |
Keep the one that says what the project does with it. |
|
An API the project calls has no version of its own. |
Set |