@workos/oagen 0.0.1 → 0.1.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/docs/cli.md CHANGED
@@ -19,27 +19,23 @@ oagen generate --spec openapi.yml --lang node --output ./sdk --namespace WorkOS
19
19
  | `--dry-run` | No | `false` | Print the list of file paths that would be generated, without writing anything to disk |
20
20
  | `--api-surface <path>` | No | | Path to baseline API surface JSON for compat overlay |
21
21
  | `--manifest <path>` | No | `<output>/smoke-manifest.json` | Path to smoke-manifest.json for method overlay (auto-discovered in output dir if present) |
22
+ | `--target <dir>` | No | | Target directory for live SDK integration — generated files are merged into this directory instead of `--output` |
22
23
  | `--no-compat-check` | No | `false` | Skip compat overlay even if `--api-surface` is provided |
23
24
 
24
25
  ## `oagen diff`
25
26
 
26
- Review or apply spec changes. Use `--report` to see what changed, or pass `--lang` and `--output` to incrementally regenerate affected files.
27
+ Compare two OpenAPI specs and output a diff report as JSON. Use this to review what changed before regenerating.
27
28
 
28
29
  ```bash
29
- oagen diff --old v1.yml --new v2.yml --report # review changes
30
- oagen diff --old v1.yml --new v2.yml --lang node --output ./sdk # regenerate
30
+ oagen diff --old v1.yml --new v2.yml
31
31
  ```
32
32
 
33
- | Argument | Required | Description |
34
- | ---------------------- | -------- | ------------------------------------------------------- |
35
- | `--old <path>` | Yes | Path to the old/previous spec |
36
- | `--new <path>` | Yes | Path to the new/current spec |
37
- | `--lang <language>` | No | Target language (required unless `--report`) |
38
- | `--output <dir>` | No | Output directory for regenerated files |
39
- | `--report` | No | Output a diff report as JSON instead of generating code |
40
- | `--force` | No | Allow file deletions without confirmation |
41
- | `--api-surface <path>` | No | Path to baseline API surface JSON for compat overlay |
42
- | `--manifest <path>` | No | Path to smoke-manifest.json for method overlay |
33
+ | Argument | Required | Description |
34
+ | -------------- | -------- | ----------------------------- |
35
+ | `--old <path>` | Yes | Path to the old/previous spec |
36
+ | `--new <path>` | Yes | Path to the new/current spec |
37
+
38
+ **Exit codes:** 0 = no changes, 1 = additive or modified changes, 2 = breaking changes.
43
39
 
44
40
  ## `oagen extract`
45
41
 
@@ -58,7 +54,7 @@ oagen extract --sdk-path ./existing-sdk --lang ruby --output my-surface.json
58
54
 
59
55
  ## `oagen verify`
60
56
 
61
- Verify an already-generated SDK — run smoke tests and optional compat checks. Use after `oagen generate` or `oagen diff`.
57
+ Verify an already-generated SDK — run smoke tests and optional compat checks. Use after `oagen generate`.
62
58
 
63
59
  ```bash
64
60
  oagen verify --lang node --output ./sdk --spec openapi.yml
@@ -11,8 +11,8 @@ It includes:
11
11
  - IR types
12
12
  - parser entrypoints such as `parseSpec`
13
13
  - emitter-facing types such as `Emitter`, `EmitterContext`, and `GeneratedFile`
14
- - generation runtime entrypoints such as `generate`, `generateFiles`, and `generateIncremental`
15
- - spec diffing APIs such as `diffSpecs` and `mapChangesToFiles`
14
+ - generation runtime entrypoints such as `generate` and `generateFiles`
15
+ - spec diffing APIs such as `diffSpecs`
16
16
  - naming utilities used by emitters
17
17
  - `planOperation`
18
18
  - config typing needed by emitter projects
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workos/oagen",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "Framework for building OpenAPI SDK emitters",
5
5
  "author": "WorkOS",
6
6
  "license": "MIT",
@@ -0,0 +1,232 @@
1
+ ---
2
+ name: check-emitter-parity
3
+ description: >-
4
+ Audit an emitter's coverage of IR fields and produce a structured gap analysis.
5
+ Use when checking if an emitter handles all IR types, after updating oagen,
6
+ or verifying emitter completeness. Also triggers for "IR coverage",
7
+ "emitter audit", "field coverage", "parity check".
8
+ ---
9
+
10
+ # /check-emitter-parity
11
+
12
+ Audit an emitter's coverage of IR fields — both behavioral and documentation — and produce a structured, priority-tiered gap analysis.
13
+
14
+ ## Overview
15
+
16
+ The IR defines fields that fall into two categories:
17
+
18
+ 1. **Behavioral fields** affect generated code correctness: authentication, pagination, request body encoding, TypeRef variant handling, discriminated unions, idempotency keys. Missing these means the SDK won't work for certain API shapes.
19
+ 2. **Documentation fields** affect developer experience: descriptions, deprecation markers, defaults, examples, readOnly/writeOnly. Missing these means the SDK compiles but is harder to use.
20
+
21
+ Emitters should handle every field relevant to their target language. This skill automates the gap analysis by reading the IR types, scanning the emitter implementation, and reporting which fields are covered vs. ignored — grouped by impact priority.
22
+
23
+ This skill is **language-agnostic** — it works for any emitter (Node, Ruby, Kotlin, Rust, Python, Go, etc.) by examining the emitter source files rather than assuming any particular output format.
24
+
25
+ ## Accept Arguments
26
+
27
+ Accept `<language>` as an argument. If not provided, use `AskUserQuestion` to ask which language emitter to audit.
28
+
29
+ ## Step 1: Resolve Paths
30
+
31
+ Determine required paths:
32
+
33
+ 1. **Language**: from argument, or use `AskUserQuestion`: "Which language emitter do you want to audit?"
34
+ 2. **oagen core path**: Check for `src/ir/types.ts` in the current directory, or `node_modules/@workos/oagen/src/ir/types.ts`. If neither exists, use `AskUserQuestion`: "Where is the oagen core package? (absolute or relative path)"
35
+ 3. **Emitter project** (`project`): from argument, or use `AskUserQuestion`: "Where is your emitter project? (absolute or relative path, e.g. `../oagen-emitters`)"
36
+ 4. **Emitter source directory**: `{project}/src/{language}/`
37
+
38
+ Validate that both the IR types file and the emitter source directory exist before proceeding.
39
+
40
+ ## Step 2: Read IR Types
41
+
42
+ Read `src/ir/types.ts` from the oagen package. Extract fields dynamically from the interfaces — the list below is a reference guide for known fields, but always check the actual types file for any additions since this list was written.
43
+
44
+ ### Priority: HIGH (behavioral — affects code correctness)
45
+
46
+ #### TypeRef Variant Coverage
47
+
48
+ Every emitter must handle all `TypeRef.kind` variants in its type-mapping logic. Check that the emitter handles each of these:
49
+
50
+ - `primitive` — basic types (string, integer, number, boolean, unknown)
51
+ - `array` — list/array types
52
+ - `model` — references to model definitions
53
+ - `enum` — references to enum definitions
54
+ - `union` — union/variant types
55
+ - `nullable` — nullable wrappers
56
+ - `literal` — literal value types
57
+ - `map` — dictionary/map types
58
+
59
+ A missing kind means an entire category of types won't generate correctly.
60
+
61
+ #### ApiSpec
62
+
63
+ - `auth` — Authentication schemes (bearer, apiKey, oauth2). Determines how the client authenticates requests.
64
+
65
+ #### Operation
66
+
67
+ - `pagination` — Auto-paging iterator generation (cursor, offset, link-header strategies)
68
+ - `requestBodyEncoding` — Content-type handling: json, form-data, form-urlencoded, binary, text
69
+ - `injectIdempotencyKey` — Whether to inject an idempotency key header
70
+
71
+ #### UnionType
72
+
73
+ - `discriminator` — Discriminator property and mapping for tagged unions
74
+ - `compositionKind` — Whether the union came from allOf (inheritance), oneOf (exclusive), or anyOf (open). Emitters use this to decide serialization strategy.
75
+
76
+ #### PrimitiveType
77
+
78
+ - `format` — Format hints like `date-time`, `uuid`, `email`, `uri`, `int64`. Determines whether to use language-specific types (e.g., `DateTime` in Ruby, `OffsetDateTime` in Kotlin).
79
+
80
+ #### ErrorResponse
81
+
82
+ - `type` — Structured error type for typed exception/error classes
83
+
84
+ ### Priority: MEDIUM (documentation — affects developer experience)
85
+
86
+ #### ApiSpec
87
+
88
+ - `description` — API-level description for the root client class doc comment
89
+ - `servers` — Server entries (url, description)
90
+
91
+ #### Service
92
+
93
+ - `description` — Service-level description for resource class doc comment
94
+
95
+ #### Operation
96
+
97
+ - `description` — Method-level description
98
+ - `deprecated` — Deprecation marker on methods
99
+ - `async` — Async operation marker
100
+ - `successResponses` — Multiple 2xx response types
101
+ - `errors` — Error responses for exception/error documentation
102
+ - `cookieParams` — Cookie parameter handling
103
+
104
+ #### Parameter
105
+
106
+ - `description` — Parameter description content
107
+ - `deprecated` — Deprecated marker on params
108
+ - `default` — Default value annotation
109
+ - `example` — Example value annotation
110
+
111
+ #### Model
112
+
113
+ - `description` — Model-level doc comment
114
+ - `typeParams` — Generic type parameters
115
+
116
+ #### Field
117
+
118
+ - `description` — Field-level doc comment
119
+ - `readOnly` — Read-only annotation (language-appropriate: `readonly` in TS, `attr_reader` in Ruby, `val` in Kotlin, etc.)
120
+ - `writeOnly` — Write-only annotation
121
+ - `deprecated` — Deprecation marker on fields
122
+ - `default` — Default value annotation
123
+
124
+ #### EnumValue
125
+
126
+ - `description` — Enum value doc comment
127
+ - `deprecated` — Deprecation marker on enum values
128
+
129
+ ## Step 3: Read Emitter Generators
130
+
131
+ Read all source files in `src/{language}/`.
132
+
133
+ ### 3a: TypeRef Variant Coverage
134
+
135
+ Search for `switch` statements or conditional chains on `ref.kind`, `.kind`, or equivalent pattern-matching constructs. Verify that all 8 TypeRef kinds are handled: `primitive`, `array`, `model`, `enum`, `union`, `nullable`, `literal`, `map`. Also check for `assertNever` usage — emitters that use the exhaustive check helper are guaranteed to handle all variants at compile time.
136
+
137
+ ### 3b: Field Coverage
138
+
139
+ For each IR field from Step 2, search for references to the field name in the emitter source. Consider a field "covered" if:
140
+
141
+ - The field name appears as a property access (e.g., `op.description`, `field.readOnly`)
142
+ - The field is used in conditional logic or output generation
143
+ - The field is iterated over (e.g., `op.cookieParams`, `op.successResponses`)
144
+
145
+ Use `Grep` with patterns like `\.description`, `\.readOnly`, `\.writeOnly`, `\.deprecated`, `\.default`, `\.example`, `\.async`, `\.cookieParams`, `\.successResponses`, `\.servers`, `\.typeParams`, `\.pagination`, `\.auth`, `\.requestBodyEncoding`, `\.injectIdempotencyKey`, `\.discriminator`, `\.compositionKind`, `\.format` scoped to the emitter source directory.
146
+
147
+ ### 3c: Intentional Skips
148
+
149
+ If the emitter source contains comments like `// @oagen-ignore: <field>` or similar documented justifications for skipping a field, mark those fields as "intentionally skipped" rather than "missing." These are acceptable gaps — not every field applies to every language.
150
+
151
+ ## Step 4: Produce Gap Analysis
152
+
153
+ Output a structured report grouped by priority tier:
154
+
155
+ ```
156
+ === Emitter Parity Report: {language} ===
157
+
158
+ --- TypeRef Variant Coverage ---
159
+ ✓ primitive, array, model, enum, union, nullable, literal, map
160
+ (all 8 variants handled — uses assertNever exhaustive check)
161
+ OR
162
+ ✗ Missing variants: literal, map
163
+ (only 6/8 handled — these type categories will not generate)
164
+
165
+ --- HIGH PRIORITY (behavioral) ---
166
+ ✓ ApiSpec.auth → client.{ext}:23
167
+ ✓ Operation.pagination → resources.{ext}:200
168
+ ✗ Operation.requestBodyEncoding — not referenced
169
+ ✗ Operation.injectIdempotencyKey — not referenced
170
+ ✗ UnionType.discriminator — not referenced
171
+ ✗ UnionType.compositionKind — not referenced
172
+ ✗ PrimitiveType.format — not referenced
173
+ ✗ ErrorResponse.type — not referenced
174
+
175
+ --- MEDIUM PRIORITY (documentation) ---
176
+ ✓ ApiSpec.description → client.{ext}:47
177
+ ✓ Service.description → resources.{ext}:130
178
+ ✓ Operation.description → resources.{ext}:162
179
+ ✓ Operation.deprecated → resources.{ext}:168
180
+ ...
181
+ ✗ Field.readOnly — not referenced
182
+ ✗ Field.writeOnly — not referenced
183
+ ~ Parameter.example — intentionally skipped (// @oagen-ignore: no example support in target)
184
+
185
+ --- INTENTIONALLY SKIPPED ---
186
+ ~ Parameter.example — "no example support in target"
187
+
188
+ SCORE: 18/30 fields covered (60%)
189
+ HIGH: 3/8 (38%) ← focus here first
190
+ MEDIUM: 15/22 (68%)
191
+ ```
192
+
193
+ ## Step 5: Suggest Fixes
194
+
195
+ For each gap, provide a specific, actionable suggestion for what the emitter should generate **in its target language's idiomatic style**. Adapt the suggestion to the language's conventions. Group suggestions by priority — HIGH gaps first.
196
+
197
+ ### HIGH priority suggestions
198
+
199
+ | IR Field | What to Generate |
200
+ | -------------------------------- | ------------------------------------------------------------------------------------------------------- |
201
+ | Missing TypeRef kinds | Add cases to type-mapping switch; use `assertNever` for compile-time exhaustiveness |
202
+ | `ApiSpec.auth` | Client constructor auth configuration (API key header, bearer token, OAuth flow) |
203
+ | `Operation.pagination` | Auto-paging iterator/generator that handles cursor, offset, or link-header strategies |
204
+ | `Operation.requestBodyEncoding` | Set correct Content-Type header; use multipart handling for form-data, raw bytes for binary |
205
+ | `Operation.injectIdempotencyKey` | Generate and attach `Idempotency-Key` header on applicable requests |
206
+ | `UnionType.discriminator` | Tagged union deserialization using the discriminator property and mapping |
207
+ | `UnionType.compositionKind` | Distinguish allOf (merge/inherit) from oneOf (exclusive union) from anyOf (open union) in serialization |
208
+ | `PrimitiveType.format` | Map formats to language types (e.g., `date-time` → `DateTime`/`OffsetDateTime`/`Time`, `uuid` → `UUID`) |
209
+ | `ErrorResponse.type` | Typed exception/error classes per status code |
210
+
211
+ ### MEDIUM priority suggestions
212
+
213
+ | IR Field | What to Generate |
214
+ | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
215
+ | `ApiSpec.description` | Doc comment on the root client class (e.g., YARD `#` for Ruby, KDoc `/**` for Kotlin, `///` for Rust, docstring for Python) |
216
+ | `ApiSpec.servers` | Server URL constants or configuration |
217
+ | `Operation.async` | Mark method as async or add async behavior hints (e.g., `async` in TS/Rust, coroutine in Kotlin, `async def` in Python) |
218
+ | `Operation.successResponses` | Document additional return types for each 2xx response |
219
+ | `Operation.cookieParams` | Document cookie parameters |
220
+ | `Parameter.description` | Document query/header/cookie params in method doc comments |
221
+ | `Parameter.deprecated` | Mark parameter as deprecated in docs |
222
+ | `Parameter.default` | Document default value |
223
+ | `Parameter.example` | Document example value |
224
+ | `Field.readOnly` | Read-only annotation and language-appropriate modifier (`readonly` in TS, `attr_reader` in Ruby, `val` in Kotlin, `pub` without setter in Rust) |
225
+ | `Field.writeOnly` | Write-only annotation in doc comments |
226
+ | `Field.default` | Document default value |
227
+
228
+ Include the specific file and approximate location where each fix should be applied.
229
+
230
+ ## Output
231
+
232
+ The report is printed to stdout. No files are created or modified — this is a read-only audit.