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
3 changes: 1 addition & 2 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,7 @@
{
"devDependencies": true,
"optionalDependencies": false,
"peerDependencies": false,
"packageDir": "./"
"peerDependencies": false
}
],
"@typescript-eslint/consistent-type-imports": [
Expand Down
23 changes: 5 additions & 18 deletions .github/workflows/e2e-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,8 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Run Verdaccio Docker
run: |
docker run -d --name verdaccio \
-p 4873:4873 \
-v ${{ github.workspace }}/e2e/config.yaml:/verdaccio/conf/config.yaml \
verdaccio/verdaccio
- name: Start Verdaccio
run: docker compose -f local-e2e.docker-compose.yaml up -d verdaccio

- name: Wait for Verdaccio
run: |
Expand All @@ -80,24 +76,13 @@ jobs:
always-auth: false

- name: Install jq
run: sudo apt-get install jq

- name: Remove provenance from package.json files
run: |
find packages -name 'package.json' | while read filename; do
jq 'del(.publishConfig.provenance)' "$filename" > temp.json && mv temp.json "$filename"
done
run: sudo apt-get install -y jq

- name: Config Git
run: |
git config --global user.email "e2e@contractual.dev"
git config --global user.name "Contractual e2e"

- name: Commit Provenance Change
run: |
git add .
git commit -am "remove provenance"

- name: Version Packages
run: |
BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)
Expand Down Expand Up @@ -135,6 +120,8 @@ jobs:
--no-git-reset \
--exact \
--dist-tag e2e
env:
NPM_CONFIG_PROVENANCE: false

- name: Clean Source (simulate fresh install)
run: |
Expand Down
154 changes: 78 additions & 76 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,107 +1,109 @@
# Contractual
<p align="center">
<img width="100" src="logo.png" alt="Contractual" />
</p>

The `contractual` CLI and GitHub Action manage schema contract lifecycle for OpenAPI, JSON Schema, and AsyncAPI.
<h1 align="center">Contractual</h1>

It provides:
- Linting of specs
- Structural breaking change detection against snapshots
- Changeset generation and versioning
- Changelog generation
- GitHub Action integration for PR checks and release automation
<p align="center">
Schema contract lifecycle for OpenAPI, JSON Schema, and AsyncAPI
<br />
Linting • Breaking change detection • Versioning • Release automation
</p>

## Installation
<div align="center">
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="license" /></a>
<a href="https://github.com/contractual-dev/contractual/blob/main/CONTRIBUTING.md"><img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg" alt="PRs welcome" /></a>
<a href="https://npmjs.org/package/@contractual/cli"><img src="https://img.shields.io/npm/dm/@contractual/cli.svg?label=%40contractual%2Fcli" alt="npm downloads" /></a>
</div>

### npm
<h3 align="center">
<a href="https://contractual.dev">Docs</a>
<span>&nbsp;&nbsp;•&nbsp;&nbsp;</span>
<a href="https://contractual.dev/getting-started/quickstart">Quickstart</a>
<span>&nbsp;&nbsp;•&nbsp;&nbsp;</span>
<a href="https://contractual.dev/breaking/overview">Breaking Detection</a>
<span>&nbsp;&nbsp;•&nbsp;&nbsp;</span>
<a href="https://contractual.dev/github-action/setup">GitHub Action</a>
</h3>

```sh
npm install -g contractual
```
<p align="center">
<strong>Supported Formats:</strong> <a href="https://www.openapis.org/">OpenAPI</a>, <a href="https://json-schema.org/">JSON Schema</a>, <a href="https://www.asyncapi.com/">AsyncAPI</a>
</p>

### Other package managers
## Features

```sh
pnpm add -g contractual
yarn global add contractual
bun add -g contractual
```
- **Structural Breaking Change Detection** - Compares specs against versioned snapshots using structural diffing, not string comparison. Catches removed fields, type changes, and endpoint deletions.

## Usage
- **Automated Versioning** - Changesets declare bump levels (major/minor/patch). `contractual version` consumes them, bumps versions, updates snapshots, and generates changelogs.

The CLI provides command summaries and flags:
- **CI Integration** - GitHub Action posts diff tables on PRs, auto-generates changesets, and opens Version PRs for release automation.

```sh
contractual --help
```
- **Format Agnostic** - Works with OpenAPI, JSON Schema, and AsyncAPI. Custom linters and differs can be configured per contract.

For usage details, see the documentation, especially:
## Quick Example

- [`contractual breaking`][breaking-docs]
- [`contractual lint`][lint-docs]
- [`contractual changeset`][changeset-docs]
- [`contractual version`][version-docs]
- [GitHub Action setup][action-docs]
### Detect changes

## CLI breaking change policy
```bash
$ contractual diff

orders-api: 3 changes (2 breaking, 1 non-breaking) — suggested bump: major

BREAKING Removed endpoint GET /orders/{id}/details
BREAKING Changed type of field 'amount': string → number
non-breaking Added optional field 'tracking_url'
```

Breaking changes are documented in release notes for the npm package.
### Generate a changeset

## Goals for schema contracts
```bash
$ contractual changeset

Schema contracts are a compatibility boundary between producers and consumers. Contractual standardizes linting, breaking change detection, versioning, and changelog generation across OpenAPI, JSON Schema, and AsyncAPI.
? Bump type for orders-api: major
? Summary: Remove deprecated endpoint, change amount type

Contractual wraps existing tooling where possible and adds missing lifecycle steps, including built-in JSON Schema diffing where production-grade tooling is limited.
Wrote .contractual/changesets/fuzzy-lion-dances.md
```

## The Contractual workflow
### Bump versions

Contractual uses a repository state directory at `.contractual/` to store versions, snapshots, and pending changesets. The GitHub Action can post diff tables on pull requests and open a Version Contracts PR for release automation.
```bash
$ contractual version

The GitHub Action is optional. The CLI can run locally or in CI.
orders-api 1.4.2 → 2.0.0 (major)

## More advanced CLI features
Updated .contractual/versions.json
Updated CHANGELOG.md
```

- Custom linters and differs via `contractual.yaml`
- Custom outputs for code generation
- JSON output formats for CI systems
- Base snapshot selection with `--base`
- Monorepo support with multiple configs
- Optional AI explanations with `ANTHROPIC_API_KEY`
## Installation

## Next steps
```bash
npm install -g @contractual/cli
```

After installation, follow the CLI quickstart:
Or with other package managers:

- [Quickstart][quickstart-docs]
- [Configuration reference][config-docs]
- [Breaking change detection][breaking-overview]
```bash
pnpm add -g @contractual/cli
yarn global add @contractual/cli
```

## Builds
## Getting Started

The CLI is distributed via npm and requires Node.js 18 or later.
1. **Initialize** - `contractual init` scans for specs and creates `contractual.yaml`
2. **Lint** - `contractual lint` validates specs
3. **Detect changes** - `contractual diff` shows all changes classified
4. **CI gate** - `contractual breaking` fails if breaking changes exist
5. **Version** - `contractual changeset` + `contractual version` for releases

| Platform | Support |
|----------|---------|
| macOS | Node.js 18+ |
| Linux | Node.js 18+ |
| Windows | Node.js 18+ |
[→ Full Quickstart Guide](https://contractual.dev/getting-started/quickstart)

## Community

Issues and feature requests:
- [GitHub issues][issues]

Documentation:
- [contractual.dev][docs]

License:
- [MIT](LICENSE)

[docs]: https://contractual.dev
[issues]: https://github.com/contractual-dev/contractual/issues
[quickstart-docs]: https://contractual.dev/getting-started/quickstart
[config-docs]: https://contractual.dev/reference/configuration
[breaking-docs]: https://contractual.dev/breaking/usage
[breaking-overview]: https://contractual.dev/breaking/overview
[lint-docs]: https://contractual.dev/linting/usage
[changeset-docs]: https://contractual.dev/versioning/usage
[version-docs]: https://contractual.dev/versioning/usage
[action-docs]: https://contractual.dev/github-action/setup
- [Documentation](https://contractual.dev)
- [GitHub Issues](https://github.com/contractual-dev/contractual/issues)

## License

[MIT](LICENSE)
37 changes: 37 additions & 0 deletions e2e/cli-basic/cli-install.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@ describe('CLI Installation and Basic Commands', () => {
const result = run('npx contractual --help');
expect(result).toContain('init');
expect(result).toContain('lint');
expect(result).toContain('diff');
expect(result).toContain('breaking');
expect(result).toContain('changeset');
expect(result).toContain('version');
expect(result).toContain('status');
expect(result).toContain('contract');
expect(result).toContain('pre');
});

test('contractual init --help shows init options', () => {
Expand All @@ -39,4 +42,38 @@ describe('CLI Installation and Basic Commands', () => {
const result = run('npx contractual breaking --help');
expect(result).toContain('--format');
});

test('contractual diff --help shows diff options', () => {
const result = run('npx contractual diff --help');
expect(result).toContain('--format');
expect(result).toContain('--severity');
expect(result).toContain('--verbose');
});

test('contractual contract --help shows subcommands', () => {
const result = run('npx contractual contract --help');
expect(result).toContain('add');
expect(result).toContain('list');
});

test('contractual contract add --help shows add options', () => {
const result = run('npx contractual contract add --help');
expect(result).toContain('--name');
expect(result).toContain('--type');
expect(result).toContain('--path');
});

test('contractual pre --help shows subcommands', () => {
const result = run('npx contractual pre --help');
expect(result).toContain('enter');
expect(result).toContain('exit');
expect(result).toContain('status');
});

test('contractual version --help shows version options', () => {
const result = run('npx contractual version --help');
expect(result).toContain('--dry-run');
expect(result).toContain('--json');
expect(result).toContain('--yes');
});
});
Loading
Loading