@workos/oagen 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.mjs +1123 -436
- package/dist/cli/index.mjs.map +1 -1
- package/dist/compat/index.d.mts +144 -57
- package/dist/compat/index.d.mts.map +1 -1
- package/dist/compat/index.mjs +362 -41
- package/dist/compat/index.mjs.map +1 -1
- package/dist/{diff-BYadQQV7.mjs → diff-BaH9qa8k.mjs} +11 -2
- package/dist/{diff-BYadQQV7.mjs.map → diff-BaH9qa8k.mjs.map} +1 -1
- package/dist/differ-CTl98yoS.d.mts +116 -0
- package/dist/differ-CTl98yoS.d.mts.map +1 -0
- package/dist/index.d.mts +3 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +50 -10
- package/dist/index.mjs.map +1 -1
- package/dist/{differ-CG5ZErKl.mjs → overlay-TYxHOCow.mjs} +1112 -553
- package/dist/overlay-TYxHOCow.mjs.map +1 -0
- package/dist/scripts/smoke/baseline.mjs +49 -9
- package/dist/scripts/smoke/baseline.mjs.map +1 -1
- package/dist/smoke.d.mts +10 -0
- package/dist/smoke.d.mts.map +1 -1
- package/dist/smoke.mjs +49 -9
- package/dist/smoke.mjs.map +1 -1
- package/dist/tree-sitter-C53w_0WE.mjs.map +1 -1
- package/dist/{types-DJMbCtIO.d.mts → types-mTtkgy_h.d.mts} +180 -2
- package/dist/types-mTtkgy_h.d.mts.map +1 -0
- package/dist/verify/index.d.mts +4 -3
- package/dist/verify/index.d.mts.map +1 -1
- package/dist/verify/index.mjs +12 -10
- package/dist/verify/index.mjs.map +1 -1
- package/docs/architecture/emitter-contract.md +2 -2
- package/docs/architecture/extractor-contract.md +6 -5
- package/docs/architecture/pipeline.md +4 -3
- package/docs/architecture/workflows.md +5 -3
- package/docs/cli.md +148 -6
- package/docs/contributor/public-api.md +1 -1
- package/docs/core/compatibility-ir.md +200 -0
- package/docs/core/compatibility-policy.md +170 -0
- package/docs/core/compatibility.md +83 -0
- package/docs/core/index.md +5 -0
- package/docs/core/manifest-schema.md +76 -0
- package/docs/core/quickstart.md +9 -2
- package/docs/core/verify-compat.md +235 -0
- package/package.json +1 -1
- package/skills/generate-emitter/SKILL.md +12 -7
- package/skills/generate-emitter/references/project-scaffold.md +27 -6
- package/skills/generate-extractor/SKILL.md +14 -8
- package/skills/generate-sdk/SKILL.md +2 -2
- package/skills/generate-smoke-test/SKILL.md +9 -5
- package/skills/review-operations/SKILL.md +2 -2
- package/skills/verify-compat/SKILL.md +1 -1
- package/dist/differ-CG5ZErKl.mjs.map +0 -1
- package/dist/types-DJMbCtIO.d.mts.map +0 -1
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Compatibility Verification
|
|
2
|
+
|
|
3
|
+
oagen includes a cross-language compatibility engine that detects breaking changes, classifies their severity per-language, and enforces policy before generated SDKs ship.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The compatibility system answers: **"will this regeneration break existing callers?"**
|
|
8
|
+
|
|
9
|
+
It works by comparing two snapshots of an SDK's public API surface — a baseline (the live SDK) and a candidate (the newly generated output) — and classifying every difference.
|
|
10
|
+
|
|
11
|
+
## Lifecycle
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
compat-extract → .oagen-compat-snapshot.json → compat-diff → report → compat-summary
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
1. **Extract** — `oagen compat-extract` runs an extractor against the live SDK and writes a `CompatSnapshot` JSON file to disk (`.oagen-compat-snapshot.json`)
|
|
18
|
+
2. **Diff** — `oagen compat-diff` compares a baseline snapshot (committed) against a candidate snapshot (freshly extracted from generated output), classifying each difference
|
|
19
|
+
3. **Enforce** — `compat-diff` exits non-zero when unapproved changes meet or exceed the fail threshold (`--fail-on`)
|
|
20
|
+
4. **Summary** — `oagen compat-summary` formats the JSON report as a markdown PR comment
|
|
21
|
+
|
|
22
|
+
The baseline snapshot is committed to the repository and updated on each release. The candidate snapshot is extracted in CI from the freshly generated SDK.
|
|
23
|
+
|
|
24
|
+
### CLI Commands
|
|
25
|
+
|
|
26
|
+
| Command | Purpose |
|
|
27
|
+
| ---------------------- | ---------------------------------------------- |
|
|
28
|
+
| `oagen compat-extract` | Extract a snapshot from a live SDK → JSON file |
|
|
29
|
+
| `oagen compat-diff` | Diff two snapshot files → classified report |
|
|
30
|
+
| `oagen compat-summary` | Format a report → markdown PR comment |
|
|
31
|
+
|
|
32
|
+
See [CLI Reference](../cli.md) for full argument tables.
|
|
33
|
+
|
|
34
|
+
## Relation to Smoke Tests
|
|
35
|
+
|
|
36
|
+
Smoke tests verify **behavioral** correctness: does the generated SDK call the right endpoints with the right parameters? Compatibility verification checks **structural** correctness: does the generated SDK preserve the public API shape that callers depend on?
|
|
37
|
+
|
|
38
|
+
Both run during `oagen verify`. Smoke tests catch bugs. Compat checks catch breaking changes.
|
|
39
|
+
|
|
40
|
+
## Relation to Spec Diffing
|
|
41
|
+
|
|
42
|
+
`oagen diff` compares two OpenAPI specs to show what changed at the API level. Compat verification compares two SDK surfaces to show what changed at the SDK level. A spec change may or may not produce a breaking SDK change, depending on the language and how the emitter handles it.
|
|
43
|
+
|
|
44
|
+
## Key Concepts
|
|
45
|
+
|
|
46
|
+
### Classification
|
|
47
|
+
|
|
48
|
+
Every detected change gets a category and severity:
|
|
49
|
+
|
|
50
|
+
- **Breaking** — Will break existing callers (e.g., `parameter_removed`, `symbol_renamed`)
|
|
51
|
+
- **Soft-risk** — May affect callers depending on usage (e.g., `constructor_reordered_named_friendly`)
|
|
52
|
+
- **Additive** — Safe to ship (e.g., `symbol_added`, `parameter_added_optional_terminal`)
|
|
53
|
+
|
|
54
|
+
See [Compatibility IR](compatibility-ir.md) for the full category list.
|
|
55
|
+
|
|
56
|
+
### Language Policy
|
|
57
|
+
|
|
58
|
+
The same conceptual change has different severity in different languages. A parameter rename is breaking in PHP (named arguments) but soft-risk in Go (positional only). Language policy is defined by `CompatPolicyHints` with built-in defaults for all 9 supported languages.
|
|
59
|
+
|
|
60
|
+
See [Compatibility Policy](compatibility-policy.md) for details.
|
|
61
|
+
|
|
62
|
+
### Concept-First Approvals
|
|
63
|
+
|
|
64
|
+
When a breaking change is intentional, it can be approved in `oagen.config.ts`. Approvals are concept-first: one approval targets one symbol and one change category, optionally scoped to specific languages.
|
|
65
|
+
|
|
66
|
+
See [Compatibility Policy](compatibility-policy.md) for the approval schema.
|
|
67
|
+
|
|
68
|
+
### Provenance
|
|
69
|
+
|
|
70
|
+
Each classified change includes a provenance bucket explaining where the drift came from:
|
|
71
|
+
|
|
72
|
+
- `spec_shape_change` — The spec changed
|
|
73
|
+
- `emitter_template_change` — The emitter template changed
|
|
74
|
+
- `normalization_change` — Naming normalization produced different output
|
|
75
|
+
- `unknown` — Source of drift not determined
|
|
76
|
+
|
|
77
|
+
## Further Reading
|
|
78
|
+
|
|
79
|
+
- [CLI Reference](../cli.md) — `compat-extract`, `compat-diff`, `compat-summary` argument tables
|
|
80
|
+
- [Compatibility IR](compatibility-ir.md) — Types, categories, parameters, `extractSnapshot()`
|
|
81
|
+
- [Compatibility Policy](compatibility-policy.md) — Config, approvals, language defaults
|
|
82
|
+
- [Manifest Schema](manifest-schema.md) — `.oagen-manifest.json` format
|
|
83
|
+
- [Verify Compat](verify-compat.md) — CI workflows, reports, GitHub Actions example
|
package/docs/core/index.md
CHANGED
|
@@ -8,5 +8,10 @@ These documents define the stable center of oagen as an OSS framework.
|
|
|
8
8
|
- [Pipeline Architecture](../architecture/pipeline.md)
|
|
9
9
|
- [IR Type System Reference](../architecture/ir-types.md)
|
|
10
10
|
- [Emitter Contract](../architecture/emitter-contract.md)
|
|
11
|
+
- [Compatibility Verification](compatibility.md)
|
|
12
|
+
- [Compatibility IR](compatibility-ir.md)
|
|
13
|
+
- [Compatibility Policy](compatibility-policy.md)
|
|
14
|
+
- [Manifest Schema](manifest-schema.md)
|
|
15
|
+
- [Verify Compat](verify-compat.md)
|
|
11
16
|
|
|
12
17
|
Read the quickstart first. The goal is to get from a spec to generated files without learning compatibility overlays, extractors, or smoke workflows.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Manifest Schema
|
|
2
|
+
|
|
3
|
+
`.oagen-manifest.json` is a generated provenance artifact. It records what was generated, from which inputs, and when. It is machine-owned — humans should not edit it.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
1. **Pruning** — On regeneration, oagen deletes files from the previous manifest that are no longer emitted, preventing stale-file accumulation
|
|
8
|
+
2. **Provenance** — Records the spec, emitter, and config used for each generation run
|
|
9
|
+
3. **Compat versioning** — Records the compat schema version for snapshot compatibility
|
|
10
|
+
|
|
11
|
+
## Schema (v2)
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"version": 2,
|
|
16
|
+
"language": "php",
|
|
17
|
+
"sdkName": "workos-php",
|
|
18
|
+
"generatedAt": "2026-04-18T22:02:14.077Z",
|
|
19
|
+
"specSha": "sha256:abc123...",
|
|
20
|
+
"specPath": "../openapi-spec/spec/open-api-spec.yaml",
|
|
21
|
+
"emitterSha": "def456",
|
|
22
|
+
"emitterVersion": "0.7.0",
|
|
23
|
+
"configSha": "sha256:789abc...",
|
|
24
|
+
"compatSchemaVersion": "1",
|
|
25
|
+
"files": ["lib/Resource/CreateUser.php", "lib/Service/UserManagement.php"]
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Field Semantics
|
|
30
|
+
|
|
31
|
+
| Field | Type | Required | Description |
|
|
32
|
+
| --------------------- | ---------- | -------- | -------------------------------------------------------------------- |
|
|
33
|
+
| `version` | `number` | yes | Manifest schema version (currently `2`) |
|
|
34
|
+
| `language` | `string` | yes | Emitter target language (e.g., `"php"`) |
|
|
35
|
+
| `sdkName` | `string` | no | Human-readable SDK identity (e.g., `"workos-php"`) |
|
|
36
|
+
| `generatedAt` | `string` | yes | ISO-8601 timestamp of the generation run |
|
|
37
|
+
| `specSha` | `string` | no | SHA-256 hash of the source OpenAPI spec |
|
|
38
|
+
| `specPath` | `string` | no | Path or reference to the source spec |
|
|
39
|
+
| `emitterSha` | `string` | no | Git SHA of the emitter code used |
|
|
40
|
+
| `emitterVersion` | `string` | no | Emitter version string |
|
|
41
|
+
| `configSha` | `string` | no | SHA-256 hash of the effective `oagen.config.ts` |
|
|
42
|
+
| `compatSchemaVersion` | `string` | no | Version of the compat snapshot schema |
|
|
43
|
+
| `files` | `string[]` | yes | Sorted list of generated file paths (relative to manifest directory) |
|
|
44
|
+
|
|
45
|
+
## Generated Ownership
|
|
46
|
+
|
|
47
|
+
The manifest is generated by `oagen generate` and written to the output directory. It should be committed to the SDK repository for pruning to work across regenerations.
|
|
48
|
+
|
|
49
|
+
**Never put human-authored content in the manifest.** Approvals, allowlists, and policy belong in `oagen.config.ts`. The manifest is regenerated on every run — any manual edits will be overwritten.
|
|
50
|
+
|
|
51
|
+
## Pruning Semantics
|
|
52
|
+
|
|
53
|
+
When `oagen generate` runs:
|
|
54
|
+
|
|
55
|
+
1. Read the existing `.oagen-manifest.json` (if present)
|
|
56
|
+
2. Compare the previous `files` list against the current emission set
|
|
57
|
+
3. Delete files that were previously generated but are no longer emitted
|
|
58
|
+
4. Only delete files whose contents start with the oagen generation header (when a header guard is available)
|
|
59
|
+
5. Write a new manifest with the current file list
|
|
60
|
+
|
|
61
|
+
On first adoption (no previous manifest), pruning is skipped.
|
|
62
|
+
|
|
63
|
+
## Backward Compatibility
|
|
64
|
+
|
|
65
|
+
The manifest reader accepts both v1 and v2 manifests. v1 manifests (which lack `sdkName`, `specSha`, etc.) are read successfully — the new fields are simply absent. A v1 manifest written by an older oagen version will work with the current pruning system.
|
|
66
|
+
|
|
67
|
+
If the manifest version is higher than supported, it is ignored with a warning.
|
|
68
|
+
|
|
69
|
+
## What Must Not Go in the Manifest
|
|
70
|
+
|
|
71
|
+
- Approvals or allowlists (human-authored → `oagen.config.ts`)
|
|
72
|
+
- Release thresholds (human-authored → `oagen.config.ts`)
|
|
73
|
+
- Manual exception records
|
|
74
|
+
- Policy overrides
|
|
75
|
+
|
|
76
|
+
The rule: **never make the generator rewrite a file that humans edit for policy.**
|
package/docs/core/quickstart.md
CHANGED
|
@@ -35,11 +35,18 @@ npm install
|
|
|
35
35
|
|
|
36
36
|
The scaffold gives you:
|
|
37
37
|
|
|
38
|
-
- `oagen.config.ts`
|
|
39
|
-
- `src/
|
|
38
|
+
- `oagen.config.ts` -- a minimal config for local development that imports the plugin bundle
|
|
39
|
+
- `src/plugin.ts` -- plugin bundle export registering the stub emitter
|
|
40
|
+
- `src/demo/index.ts` -- stub emitter implementing the `Emitter` interface
|
|
40
41
|
- build/test tooling
|
|
41
42
|
- `sdk:generate` and related package scripts
|
|
42
43
|
|
|
44
|
+
> **Note:** The scaffolded project uses the plugin model. The consumer project
|
|
45
|
+
> (e.g. a spec repo) imports the plugin bundle from `src/plugin.ts` and layers
|
|
46
|
+
> its own spec interpretation policy on top. See the
|
|
47
|
+
> [CLI Reference](../cli.md#configuration-oagenconfigts) for plugin
|
|
48
|
+
> composition examples.
|
|
49
|
+
|
|
43
50
|
## 4. Replace the Stub Emitter
|
|
44
51
|
|
|
45
52
|
Edit `src/demo/index.ts` to return a few simple files:
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# Verify Compat
|
|
2
|
+
|
|
3
|
+
`oagen verify` runs compatibility checks as part of its verification pipeline. This document covers the compat-specific CLI flags, report outputs, and CI integration.
|
|
4
|
+
|
|
5
|
+
## CLI Flags
|
|
6
|
+
|
|
7
|
+
### Existing Flags (Compat-Related)
|
|
8
|
+
|
|
9
|
+
| Flag | Description |
|
|
10
|
+
| ---------------------- | ------------------------------------------------------------------- |
|
|
11
|
+
| `--api-surface <path>` | Baseline API surface JSON — enables compat verification |
|
|
12
|
+
| `--spec <path>` | OpenAPI spec for spec-scoped comparison |
|
|
13
|
+
| `--scope <mode>` | `full` or `spec-only` (default: `spec-only` when `--spec` provided) |
|
|
14
|
+
| `--max-retries <n>` | Max overlay patch iterations (default: 3) |
|
|
15
|
+
| `--diagnostics` | Write `verify-diagnostics.json` |
|
|
16
|
+
|
|
17
|
+
### New Flags (Classified Compat)
|
|
18
|
+
|
|
19
|
+
| Flag | Description |
|
|
20
|
+
| -------------------------- | ------------------------------------------------------------ |
|
|
21
|
+
| `--compat-report <path>` | Write machine-readable classified compat report to this path |
|
|
22
|
+
| `--compat-fail-on <level>` | Override fail threshold: `none`, `breaking`, or `soft-risk` |
|
|
23
|
+
| `--compat-baseline <path>` | Path to baseline compatibility snapshot |
|
|
24
|
+
| `--compat-explain` | Include provenance explanations in terminal output |
|
|
25
|
+
|
|
26
|
+
CLI flags take precedence over `oagen.config.ts` settings.
|
|
27
|
+
|
|
28
|
+
## Report Outputs
|
|
29
|
+
|
|
30
|
+
### Machine-Readable Report (`--compat-report`)
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"schemaVersion": "1",
|
|
35
|
+
"language": "php",
|
|
36
|
+
"summary": {
|
|
37
|
+
"breaking": 3,
|
|
38
|
+
"softRisk": 2,
|
|
39
|
+
"additive": 11
|
|
40
|
+
},
|
|
41
|
+
"changes": [
|
|
42
|
+
{
|
|
43
|
+
"severity": "breaking",
|
|
44
|
+
"category": "parameter_renamed",
|
|
45
|
+
"symbol": "Authorization.check",
|
|
46
|
+
"conceptualChangeId": "chg_parameter_renamed_authorization.check_resourceid",
|
|
47
|
+
"provenance": "emitter_template_change",
|
|
48
|
+
"old": { "parameter": "resourceId" },
|
|
49
|
+
"new": { "parameter": "resourceTarget" }
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Human Summary (`--compat-explain`)
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
Compat report for php:
|
|
59
|
+
3 breaking, 2 soft-risk, 11 additive
|
|
60
|
+
|
|
61
|
+
Breaking:
|
|
62
|
+
[parameter_renamed] Authorization.check — Parameter "resourceId" renamed to "resourceTarget"
|
|
63
|
+
provenance: emitter_template_change
|
|
64
|
+
[parameter_removed] UserManagement.createUser — Parameter "passwordHashType" removed
|
|
65
|
+
provenance: spec_shape_change
|
|
66
|
+
|
|
67
|
+
Soft-risk:
|
|
68
|
+
[constructor_reordered_named_friendly] CreateUser.constructor — reordered (named-friendly)
|
|
69
|
+
provenance: normalization_change
|
|
70
|
+
|
|
71
|
+
Additive: 11 new symbol(s)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Diagnostics (`--diagnostics`)
|
|
75
|
+
|
|
76
|
+
The existing `verify-diagnostics.json` continues to include legacy compat data (preservation score, violations by category/severity). The new classified report is written separately via `--compat-report`.
|
|
77
|
+
|
|
78
|
+
## Failure Thresholds
|
|
79
|
+
|
|
80
|
+
The verify command fails when unapproved changes meet or exceed the fail threshold:
|
|
81
|
+
|
|
82
|
+
| `--compat-fail-on` | Fails on |
|
|
83
|
+
| ------------------ | ---------------------------------------- |
|
|
84
|
+
| `none` | Never fails for compat changes |
|
|
85
|
+
| `breaking` | Unapproved breaking changes (default) |
|
|
86
|
+
| `soft-risk` | Unapproved breaking OR soft-risk changes |
|
|
87
|
+
|
|
88
|
+
Approved changes (matched by `compat.allow` in config) are excluded from the failure threshold.
|
|
89
|
+
|
|
90
|
+
## CI Integration
|
|
91
|
+
|
|
92
|
+
There are two CI models: **integrated** (via `oagen verify`) and **file-based** (via standalone `compat-extract` / `compat-diff` / `compat-summary` commands). The file-based model is recommended for new setups.
|
|
93
|
+
|
|
94
|
+
### File-Based CI (Recommended)
|
|
95
|
+
|
|
96
|
+
The file-based model uses `.oagen-compat-snapshot.json` as a committed baseline artifact. On each PR, the candidate SDK is extracted, diffed against the baseline, and a summary is posted as a PR comment.
|
|
97
|
+
|
|
98
|
+
```yaml
|
|
99
|
+
name: Compat Check
|
|
100
|
+
on: [pull_request]
|
|
101
|
+
|
|
102
|
+
jobs:
|
|
103
|
+
compat:
|
|
104
|
+
runs-on: ubuntu-latest
|
|
105
|
+
strategy:
|
|
106
|
+
matrix:
|
|
107
|
+
lang: [node, php, python, ruby, go, kotlin, dotnet, elixir, rust]
|
|
108
|
+
steps:
|
|
109
|
+
- uses: actions/checkout@v4
|
|
110
|
+
|
|
111
|
+
- name: Generate candidate SDK
|
|
112
|
+
run: oagen generate --spec openapi.yml --lang ${{ matrix.lang }} --output ./generated
|
|
113
|
+
|
|
114
|
+
- name: Extract candidate snapshot
|
|
115
|
+
run: |
|
|
116
|
+
oagen compat-extract \
|
|
117
|
+
--sdk-path ./generated \
|
|
118
|
+
--lang ${{ matrix.lang }} \
|
|
119
|
+
--output /tmp \
|
|
120
|
+
--spec openapi.yml
|
|
121
|
+
|
|
122
|
+
- name: Diff against baseline
|
|
123
|
+
run: |
|
|
124
|
+
oagen compat-diff \
|
|
125
|
+
--baseline .oagen-compat-snapshot.json \
|
|
126
|
+
--candidate /tmp/.oagen-compat-snapshot.json \
|
|
127
|
+
--output compat-report-${{ matrix.lang }}.json \
|
|
128
|
+
--fail-on breaking
|
|
129
|
+
|
|
130
|
+
- uses: actions/upload-artifact@v4
|
|
131
|
+
if: always()
|
|
132
|
+
with:
|
|
133
|
+
name: compat-report-${{ matrix.lang }}
|
|
134
|
+
path: compat-report-${{ matrix.lang }}.json
|
|
135
|
+
|
|
136
|
+
summary:
|
|
137
|
+
needs: compat
|
|
138
|
+
if: always()
|
|
139
|
+
runs-on: ubuntu-latest
|
|
140
|
+
steps:
|
|
141
|
+
- uses: actions/checkout@v4
|
|
142
|
+
- uses: actions/download-artifact@v4
|
|
143
|
+
with:
|
|
144
|
+
pattern: compat-report-*
|
|
145
|
+
merge-multiple: true
|
|
146
|
+
|
|
147
|
+
- name: Post cross-language PR summary
|
|
148
|
+
run: |
|
|
149
|
+
oagen compat-summary \
|
|
150
|
+
--report compat-report-*.json \
|
|
151
|
+
| gh pr comment ${{ github.event.pull_request.number }} --body-file -
|
|
152
|
+
env:
|
|
153
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
For a single-language project, simplify to one job without the matrix:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
oagen compat-extract --sdk-path ./generated --lang node --output /tmp --spec openapi.yml
|
|
160
|
+
oagen compat-diff --baseline .oagen-compat-snapshot.json --candidate /tmp/.oagen-compat-snapshot.json --output report.json --fail-on breaking
|
|
161
|
+
oagen compat-summary --report report.json | gh pr comment --body-file -
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
#### Baseline Management
|
|
165
|
+
|
|
166
|
+
The baseline snapshot is committed to the repository and updated as part of the release process:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
# After releasing a new SDK version, update the baseline
|
|
170
|
+
oagen compat-extract --sdk-path . --lang node --output .
|
|
171
|
+
git add .oagen-compat-snapshot.json
|
|
172
|
+
git commit -m "chore: update compat baseline for v2.1.0"
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
#### Report-Only Mode (Phase 4)
|
|
176
|
+
|
|
177
|
+
To run compat checks without blocking PRs, set `--fail-on none`:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
oagen compat-diff \
|
|
181
|
+
--baseline .oagen-compat-snapshot.json \
|
|
182
|
+
--candidate /tmp/candidate-snapshot.json \
|
|
183
|
+
--output compat-report.json \
|
|
184
|
+
--fail-on none
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
The PR summary will still be posted, but the job will not fail. Switch to `--fail-on breaking` when ready for hard enforcement.
|
|
188
|
+
|
|
189
|
+
### Integrated CI (via `oagen verify`)
|
|
190
|
+
|
|
191
|
+
The `oagen verify` command runs compat checks as part of its verification pipeline. This is suitable when you want smoke tests and compat checks in a single step.
|
|
192
|
+
|
|
193
|
+
```yaml
|
|
194
|
+
- name: Verify SDK
|
|
195
|
+
run: |
|
|
196
|
+
oagen verify \
|
|
197
|
+
--lang php \
|
|
198
|
+
--output ./generated \
|
|
199
|
+
--api-surface sdk-php-surface.json \
|
|
200
|
+
--spec openapi.yaml \
|
|
201
|
+
--compat-report compat-report.json \
|
|
202
|
+
--compat-fail-on breaking \
|
|
203
|
+
--diagnostics
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Exit Codes
|
|
207
|
+
|
|
208
|
+
| Code | Meaning |
|
|
209
|
+
| ---- | ---------------------------------------- |
|
|
210
|
+
| 0 | All checks passed |
|
|
211
|
+
| 1 | Compat violations or smoke test findings |
|
|
212
|
+
| 2 | SDK compile errors |
|
|
213
|
+
|
|
214
|
+
## How to Read and Act on Findings
|
|
215
|
+
|
|
216
|
+
### Breaking Changes
|
|
217
|
+
|
|
218
|
+
1. Check the `provenance` field — is this from a spec change or an emitter change?
|
|
219
|
+
2. If spec-driven: this may need a major version release
|
|
220
|
+
3. If emitter-driven: fix the emitter to preserve backward compatibility
|
|
221
|
+
4. If intentional: add an approval to `oagen.config.ts`
|
|
222
|
+
|
|
223
|
+
### Soft-Risk Changes
|
|
224
|
+
|
|
225
|
+
1. Review whether callers are likely affected
|
|
226
|
+
2. If safe: no action needed (or add approval for documentation)
|
|
227
|
+
3. If risky: fix the emitter or bump the release level
|
|
228
|
+
|
|
229
|
+
### Additive Changes
|
|
230
|
+
|
|
231
|
+
Safe to ship. No action needed.
|
|
232
|
+
|
|
233
|
+
## Overlay Retry Loop
|
|
234
|
+
|
|
235
|
+
The existing overlay retry loop (`--max-retries`) continues to work with the legacy compat system. It patches `public-api` and `export-structure` violations by correcting overlay mappings and regenerating. The classified compat report runs after the overlay loop has converged.
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@ Scaffold a complete language emitter for oagen that translates the intermediate
|
|
|
11
11
|
|
|
12
12
|
oagen has a plugin architecture for code generation. Each target language is an **emitter** — a TypeScript module that implements the `Emitter` interface. An emitter receives parsed IR nodes (models, enums, services, etc.) and returns `GeneratedFile[]` — arrays of `{ path, content }` pairs. The engine orchestrator calls each emitter method, prepends a file header, and writes the results to disk.
|
|
13
13
|
|
|
14
|
-
Emitters live in **external projects** (not inside the oagen core repo). They import all oagen types from `@workos/oagen` and
|
|
14
|
+
Emitters live in **external projects** (not inside the oagen core repo). They import all oagen types from `@workos/oagen` and are exported through the emitter project's plugin bundle (e.g., `workosEmittersPlugin`), which the consumer project's `oagen.config.ts` imports.
|
|
15
15
|
|
|
16
16
|
## Reference Docs
|
|
17
17
|
|
|
@@ -41,7 +41,7 @@ Before starting, read and understand these oagen core types (all imported from `
|
|
|
41
41
|
- `planOperation`, `OperationPlan` — operation analysis helpers
|
|
42
42
|
- `toPascalCase`, `toSnakeCase`, `toCamelCase`, `toKebabCase`, `toUpperSnakeCase` — naming utilities
|
|
43
43
|
|
|
44
|
-
Also read the project's `
|
|
44
|
+
Also read the project's plugin bundle export (e.g., `src/plugin.ts`) to understand how emitters are registered.
|
|
45
45
|
|
|
46
46
|
If an `sdk_path` argument is provided, you MUST thoroughly study that SDK before proceeding to Step 1. The existing SDK's actual code — not generic conventions — drives every design decision.
|
|
47
47
|
|
|
@@ -227,17 +227,22 @@ export const {language}Emitter: Emitter = {
|
|
|
227
227
|
|
|
228
228
|
## Step 7: Register Emitter
|
|
229
229
|
|
|
230
|
-
Add the emitter to `
|
|
230
|
+
Add the emitter to the plugin bundle export (e.g., `src/plugin.ts`) and re-export from `src/index.ts`:
|
|
231
231
|
|
|
232
232
|
```typescript
|
|
233
|
-
//
|
|
234
|
-
import { {language}Emitter } from './
|
|
235
|
-
const
|
|
233
|
+
// src/plugin.ts
|
|
234
|
+
import { {language}Emitter } from './{language}/index.js';
|
|
235
|
+
export const workosEmittersPlugin = {
|
|
236
|
+
emitters: [/* existing, */ {language}Emitter],
|
|
237
|
+
// ...
|
|
238
|
+
};
|
|
236
239
|
|
|
237
240
|
// src/index.ts
|
|
238
241
|
export { {language}Emitter } from './{language}/index.js';
|
|
239
242
|
```
|
|
240
243
|
|
|
244
|
+
The consumer project's `oagen.config.ts` imports the plugin bundle, so the new emitter is automatically available.
|
|
245
|
+
|
|
241
246
|
## Step 8: Create Tests
|
|
242
247
|
|
|
243
248
|
Create test files under `test/{language}/` **in the emitter project**:
|
|
@@ -386,7 +391,7 @@ This skill produces, in the emitter project:
|
|
|
386
391
|
- `src/{language}/*.ts` — emitter generator files implementing the `Emitter` interface
|
|
387
392
|
- `test/{language}/*.ts` — unit tests for each generator
|
|
388
393
|
- `docs/sdk-architecture/{language}.md` — SDK design document
|
|
389
|
-
- Updated `
|
|
394
|
+
- Updated plugin bundle (`src/plugin.ts`) and `src/index.ts` with the new emitter registered
|
|
390
395
|
|
|
391
396
|
## Backwards Compatibility
|
|
392
397
|
|
|
@@ -12,7 +12,8 @@ When `{project}/package.json` does **NOT** exist, create the following boilerpla
|
|
|
12
12
|
"main": "dist/index.js",
|
|
13
13
|
"types": "dist/index.d.ts",
|
|
14
14
|
"exports": {
|
|
15
|
-
".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" }
|
|
15
|
+
".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" },
|
|
16
|
+
"./plugin": { "types": "./dist/plugin.d.ts", "import": "./dist/plugin.js" }
|
|
16
17
|
},
|
|
17
18
|
"scripts": {
|
|
18
19
|
"build": "tsup",
|
|
@@ -76,7 +77,7 @@ export default defineConfig({
|
|
|
76
77
|
```ts
|
|
77
78
|
import { defineConfig } from "tsup";
|
|
78
79
|
export default defineConfig({
|
|
79
|
-
entry: { index: "src/index.ts" },
|
|
80
|
+
entry: { index: "src/index.ts", plugin: "src/plugin.ts" },
|
|
80
81
|
format: ["esm"],
|
|
81
82
|
dts: true,
|
|
82
83
|
clean: true,
|
|
@@ -86,21 +87,41 @@ export default defineConfig({
|
|
|
86
87
|
|
|
87
88
|
## `oagen.config.ts`
|
|
88
89
|
|
|
90
|
+
Minimal config for local emitter development. The canonical consumer config lives in the consumer project.
|
|
91
|
+
|
|
89
92
|
```ts
|
|
90
93
|
import type { OagenConfig } from "@workos/oagen";
|
|
91
|
-
|
|
94
|
+
import { plugin } from "./src/plugin.js";
|
|
95
|
+
const config: OagenConfig = { ...plugin };
|
|
92
96
|
export default config;
|
|
93
97
|
```
|
|
94
98
|
|
|
95
|
-
|
|
99
|
+
## `src/plugin.ts`
|
|
100
|
+
|
|
101
|
+
Plugin bundle export. The consumer project imports this to get all emitters, extractors, and smoke runners.
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import type { OagenConfig } from "@workos/oagen";
|
|
105
|
+
export const plugin: Pick<
|
|
106
|
+
OagenConfig,
|
|
107
|
+
"emitters" | "extractors" | "smokeRunners"
|
|
108
|
+
> = {
|
|
109
|
+
emitters: [],
|
|
110
|
+
extractors: [],
|
|
111
|
+
smokeRunners: {},
|
|
112
|
+
};
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
(Step 7 of the main skill adds the emitter to this plugin bundle.)
|
|
96
116
|
|
|
97
117
|
## `src/index.ts`
|
|
98
118
|
|
|
99
119
|
```ts
|
|
100
|
-
// Barrel export — re-exports all emitters
|
|
120
|
+
// Barrel export — re-exports all emitters and the plugin bundle
|
|
121
|
+
export { plugin } from "./plugin.js";
|
|
101
122
|
```
|
|
102
123
|
|
|
103
|
-
(Step
|
|
124
|
+
(Step 7 adds the emitter re-export.)
|
|
104
125
|
|
|
105
126
|
## `.gitignore`
|
|
106
127
|
|
|
@@ -13,7 +13,7 @@ Scaffold a language extractor for oagen's compat verification system. An extract
|
|
|
13
13
|
Live SDK → Extractor → ApiSurface JSON → Differ ← Generated SDK → Violations
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
Each language needs its own extractor because public surface detection is language-specific (e.g., TypeScript exports vs. Ruby public methods vs. Python `__all__` vs. Go capitalized identifiers). An extractor implements the `Extractor` interface and is
|
|
16
|
+
Each language needs its own extractor because public surface detection is language-specific (e.g., TypeScript exports vs. Ruby public methods vs. Python `__all__` vs. Go capitalized identifiers). An extractor implements the `Extractor` interface and is exported through the emitter project's plugin bundle (e.g., `workosEmittersPlugin`), which the consumer project's `oagen.config.ts` imports.
|
|
17
17
|
|
|
18
18
|
## Reference Docs
|
|
19
19
|
|
|
@@ -115,16 +115,22 @@ export const {language}Extractor: Extractor = {
|
|
|
115
115
|
|
|
116
116
|
## Step 3: Register the Extractor
|
|
117
117
|
|
|
118
|
-
Add to `
|
|
118
|
+
Add the extractor to the plugin bundle export (e.g., `src/plugin.ts`) and re-export from `src/index.ts`:
|
|
119
119
|
|
|
120
120
|
```typescript
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
extractors: [{language}Extractor],
|
|
121
|
+
// src/plugin.ts
|
|
122
|
+
import { {language}Extractor } from './compat/extractors/{language}.js';
|
|
123
|
+
export const workosEmittersPlugin = {
|
|
124
|
+
extractors: [/* existing, */ {language}Extractor],
|
|
125
|
+
// ...
|
|
125
126
|
};
|
|
127
|
+
|
|
128
|
+
// src/index.ts
|
|
129
|
+
export { {language}Extractor } from './compat/extractors/{language}.js';
|
|
126
130
|
```
|
|
127
131
|
|
|
132
|
+
The consumer project's `oagen.config.ts` imports the plugin bundle, so the new extractor is automatically available.
|
|
133
|
+
|
|
128
134
|
## Step 4: Create a Fixture SDK
|
|
129
135
|
|
|
130
136
|
Create a minimal but representative fixture at `test/fixtures/sample-sdk-{language}/` **in the emitter project**. It must include:
|
|
@@ -175,7 +181,7 @@ If `sdk_path` was provided, also test against the real SDK and verify all public
|
|
|
175
181
|
```
|
|
176
182
|
=== Extractor: {language} ===
|
|
177
183
|
Files created: src/compat/extractors/{language}.ts, test suite, fixture SDK
|
|
178
|
-
Modified:
|
|
184
|
+
Modified: src/plugin.ts, src/index.ts
|
|
179
185
|
Validation: Tests / Fixture extract / Determinism / Real SDK extract
|
|
180
186
|
ApiSurface: {N} classes, {N} interfaces, {N} type aliases, {N} enums, {N} exports
|
|
181
187
|
```
|
|
@@ -187,7 +193,7 @@ This skill produces, in the emitter project:
|
|
|
187
193
|
- `src/compat/extractors/{language}.ts` — extractor implementing the `Extractor` interface with `LanguageHints`
|
|
188
194
|
- `test/compat/extractors/{language}.test.ts` — extraction tests against fixture SDK
|
|
189
195
|
- `test/fixtures/sample-sdk-{language}/` — minimal fixture SDK for testing
|
|
190
|
-
- Updated `
|
|
196
|
+
- Updated plugin bundle (`src/plugin.ts`) and `src/index.ts` with the new extractor registered
|
|
191
197
|
|
|
192
198
|
## Common Pitfalls
|
|
193
199
|
|
|
@@ -91,7 +91,7 @@ ls {project}/src/{language}/index.ts
|
|
|
91
91
|
cd {project} && npx vitest run test/{language}/ 2>&1 | tail -5
|
|
92
92
|
|
|
93
93
|
# After /generate-extractor — extractor registered with hints
|
|
94
|
-
grep -l "{language}Extractor\|{language}_extractor" {project}/
|
|
94
|
+
grep -l "{language}Extractor\|{language}_extractor" {project}/src/plugin.ts {project}/src/index.ts
|
|
95
95
|
grep -l "hints" {project}/src/compat/extractors/{language}.ts
|
|
96
96
|
|
|
97
97
|
# After /verify-compat — handled by the skill itself
|
|
@@ -148,7 +148,7 @@ Only include this next section in the output if this is Scenario A:
|
|
|
148
148
|
|
|
149
149
|
This skill produces, via its sub-skills:
|
|
150
150
|
|
|
151
|
-
- A complete language emitter registered in `
|
|
151
|
+
- A complete language emitter registered in the plugin bundle (`src/plugin.ts`)
|
|
152
152
|
- An API surface extractor (Scenario A only)
|
|
153
153
|
- A smoke test script at `smoke/sdk-{language}.ts`
|
|
154
154
|
- Generated SDK code at `{project}/sdk/`
|
|
@@ -136,17 +136,21 @@ Create the script **in the emitter project**:
|
|
|
136
136
|
|
|
137
137
|
## Step 6: Register the Smoke Runner
|
|
138
138
|
|
|
139
|
-
Add to `
|
|
139
|
+
Add the smoke runner path to the plugin bundle export (e.g., `src/plugin.ts`):
|
|
140
140
|
|
|
141
141
|
```typescript
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
// src/plugin.ts
|
|
143
|
+
export const workosEmittersPlugin = {
|
|
144
144
|
smokeRunners: {
|
|
145
|
-
|
|
145
|
+
// existing runners...
|
|
146
|
+
{language}: path.join(smokeDir, 'sdk-{language}.ts'),
|
|
146
147
|
},
|
|
148
|
+
// ...
|
|
147
149
|
};
|
|
148
150
|
```
|
|
149
151
|
|
|
152
|
+
The consumer project's `oagen.config.ts` imports the plugin bundle, so the new smoke runner is automatically available.
|
|
153
|
+
|
|
150
154
|
## Step 7: Validate
|
|
151
155
|
|
|
152
156
|
```bash
|
|
@@ -182,4 +186,4 @@ See `scripts/smoke/README.md` (in oagen core) for the full remediation guide. Se
|
|
|
182
186
|
This skill produces, in the emitter project:
|
|
183
187
|
|
|
184
188
|
- `smoke/sdk-{language}.ts` — self-contained smoke test script
|
|
185
|
-
- Updated `
|
|
189
|
+
- Updated plugin bundle (`src/plugin.ts`) with the smoke runner path registered
|