@workos/oagen 0.9.0 → 0.9.1
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 +346 -265
- package/dist/cli/index.mjs.map +1 -1
- package/docs/agents/architecture.md +1 -1
- package/docs/architecture/emitter-contract.md +3 -3
- package/docs/architecture/pipeline.md +1 -1
- package/docs/cli.md +10 -11
- package/package.json +1 -1
- package/skills/generate-emitter/SKILL.md +8 -11
- package/skills/generate-smoke-test/SKILL.md +6 -6
- package/skills/integrate/SKILL.md +1 -1
|
@@ -10,7 +10,7 @@ Three stages transform an OpenAPI spec into SDK files:
|
|
|
10
10
|
|
|
11
11
|
1. **Parse** (`src/parser/parse.ts`) — Load and bundle the spec via `@redocly/openapi-core`, extract `Model[]`, `Enum[]`, and `Service[]` into the IR (`ApiSpec`). Sub-modules handle ref resolution (`refs.ts`), schema walking (`schemas.ts`), operation grouping (`operations.ts`), pagination detection (`pagination.ts`), and inline model extraction (`inline-models.ts`).
|
|
12
12
|
|
|
13
|
-
2. **Emit** (`src/engine/orchestrator.ts`) — Call each emitter method in order (`generateModels` → `generateEnums` → `generateResources` → `generateClient` → `generateErrors` → `generateTypeSignatures` → `generateTests`
|
|
13
|
+
2. **Emit** (`src/engine/orchestrator.ts`) — Call each emitter method in order (`generateModels` → `generateEnums` → `generateResources` → `generateClient` → `generateErrors` → `generateTypeSignatures` → `generateTests`), then call `buildOperationsMap` to produce the operations map (stored in `.oagen-manifest.json`), collect `GeneratedFile[]`, prepend file headers.
|
|
14
14
|
|
|
15
15
|
3. **Write** (`src/engine/writer.ts`) — Write files to disk. New files are created in full. Existing files are merged at the AST level via `merger.ts` (additive-only — new symbols appended, existing symbols untouched). Files marked `skipIfExists` are never overwritten.
|
|
16
16
|
|
|
@@ -19,7 +19,7 @@ interface Emitter {
|
|
|
19
19
|
generateErrors(ctx: EmitterContext): GeneratedFile[];
|
|
20
20
|
generateTypeSignatures?(spec: ApiSpec, ctx: EmitterContext): GeneratedFile[];
|
|
21
21
|
generateTests(spec: ApiSpec, ctx: EmitterContext): GeneratedFile[];
|
|
22
|
-
|
|
22
|
+
buildOperationsMap?(spec: ApiSpec, ctx: EmitterContext): OperationsMap;
|
|
23
23
|
fileHeader(): string;
|
|
24
24
|
}
|
|
25
25
|
```
|
|
@@ -85,7 +85,7 @@ When a user passes `--api-surface` to `oagen generate`, the engine builds an `Ov
|
|
|
85
85
|
| `modelNameByIR` | `Map<string, string>` | IR model name → SDK interface name (auto-inferred from field structure) |
|
|
86
86
|
| `fileBySymbol` | `Map<string, string>` | IR symbol name → relative file path in the live SDK |
|
|
87
87
|
|
|
88
|
-
The `httpKeyByMethod` reverse map is only populated when
|
|
88
|
+
The `httpKeyByMethod` reverse map is only populated when an `operations` map is present in `.oagen-manifest.json`. Without it, method-level violations cannot be auto-patched in the self-correcting loop. Emitters that support compat verification should implement `buildOperationsMap`.
|
|
89
89
|
|
|
90
90
|
## Rules
|
|
91
91
|
|
|
@@ -97,7 +97,7 @@ The `httpKeyByMethod` reverse map is only populated when a manifest (`smoke-mani
|
|
|
97
97
|
|
|
98
98
|
4. **Tests include fixtures** — `generateTests` should internally call fixture generation and return both test files and fixture files in the combined `GeneratedFile[]`.
|
|
99
99
|
|
|
100
|
-
5. **Inapplicable methods return `[]`** — If a language doesn't need type signature files, `generateTypeSignatures` returns `[]`. Optional methods like `
|
|
100
|
+
5. **Inapplicable methods return `[]`** — If a language doesn't need type signature files, `generateTypeSignatures` returns `[]`. Optional methods like `buildOperationsMap` can simply be omitted.
|
|
101
101
|
|
|
102
102
|
6. **Composable generators** — Interface methods can compose multiple internal generators when a language needs them.
|
|
103
103
|
|
|
@@ -63,7 +63,7 @@ generateClient(spec, ctx); // HTTP client
|
|
|
63
63
|
generateErrors(ctx); // Error hierarchy
|
|
64
64
|
generateTypeSignatures(spec, ctx); // Type annotations (optional)
|
|
65
65
|
generateTests(spec, ctx); // Tests + fixtures
|
|
66
|
-
|
|
66
|
+
buildOperationsMap?.(spec, ctx); // Operations map for .oagen-manifest.json (optional)
|
|
67
67
|
```
|
|
68
68
|
|
|
69
69
|
### OperationPlan
|
package/docs/cli.md
CHANGED
|
@@ -10,17 +10,16 @@ Generate SDK code from an OpenAPI spec.
|
|
|
10
10
|
oagen generate --spec openapi.yml --lang node --output ./sdk --namespace WorkOS
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
| Argument | Required | Default
|
|
14
|
-
| ---------------------- | -------- |
|
|
15
|
-
| `--spec <path>` | No | `OPENAPI_SPEC_PATH` env
|
|
16
|
-
| `--lang <language>` | Yes |
|
|
17
|
-
| `--output <dir>` | Yes |
|
|
18
|
-
| `--namespace <name>` | No | Spec's `info.title`
|
|
19
|
-
| `--dry-run` | No | `false`
|
|
20
|
-
| `--api-surface <path>` | No |
|
|
21
|
-
| `--
|
|
22
|
-
| `--
|
|
23
|
-
| `--no-compat-check` | No | `false` | Skip compat overlay even if `--api-surface` is provided |
|
|
13
|
+
| Argument | Required | Default | Description |
|
|
14
|
+
| ---------------------- | -------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
15
|
+
| `--spec <path>` | No | `OPENAPI_SPEC_PATH` env | Path to an OpenAPI 3.x spec file. |
|
|
16
|
+
| `--lang <language>` | Yes | | Target language — must have a registered emitter (via `oagen.config.ts`) |
|
|
17
|
+
| `--output <dir>` | Yes | | Directory to write generated files into |
|
|
18
|
+
| `--namespace <name>` | No | Spec's `info.title` | SDK namespace in PascalCase — used directly for code identifiers (e.g., `WorkOS::Client`) and converted to `snake_case` for file paths (e.g., `lib/work_os/`). Use PascalCase with the exact casing you want (e.g., `WorkOS` not `work_os`). |
|
|
19
|
+
| `--dry-run` | No | `false` | Print the list of file paths that would be generated, without writing anything to disk |
|
|
20
|
+
| `--api-surface <path>` | No | | Path to baseline API surface JSON for compat overlay |
|
|
21
|
+
| `--target <dir>` | No | | Target directory for live SDK integration — generated files are merged into this directory instead of `--output` |
|
|
22
|
+
| `--no-compat-check` | No | `false` | Skip compat overlay even if `--api-surface` is provided |
|
|
24
23
|
|
|
25
24
|
## `oagen diff`
|
|
26
25
|
|
package/package.json
CHANGED
|
@@ -220,7 +220,7 @@ export const {language}Emitter: Emitter = {
|
|
|
220
220
|
generateConfig(ctx) { return generateConfig(ctx); },
|
|
221
221
|
generateTypeSignatures(spec, ctx) { return generateTypeSignatures(spec, ctx); },
|
|
222
222
|
generateTests(spec, ctx) { return generateTests(spec, ctx); },
|
|
223
|
-
|
|
223
|
+
buildOperationsMap(spec, ctx) { return buildOperationsMap(spec, ctx); },
|
|
224
224
|
fileHeader() { return "{language-appropriate auto-generated file header}"; },
|
|
225
225
|
};
|
|
226
226
|
```
|
|
@@ -323,9 +323,9 @@ Patterns replicated: (Scenario A only)
|
|
|
323
323
|
Model / Enum / Resource / Client / Error / Serialization / Pagination / Testing / Barrel exports
|
|
324
324
|
```
|
|
325
325
|
|
|
326
|
-
##
|
|
326
|
+
## Operations Map (required)
|
|
327
327
|
|
|
328
|
-
Every emitter **must** implement `
|
|
328
|
+
Every emitter **must** implement `buildOperationsMap`. The smoke test runner uses the operations map (stored in `.oagen-manifest.json`) to resolve SDK methods from HTTP operations — without it, the smoke test cannot find methods and most operations will be skipped.
|
|
329
329
|
|
|
330
330
|
The manifest maps every `HTTP_METHOD /path` to `{ sdkMethod, service }`:
|
|
331
331
|
|
|
@@ -342,25 +342,22 @@ The manifest maps every `HTTP_METHOD /path` to `{ sdkMethod, service }`:
|
|
|
342
342
|
}
|
|
343
343
|
```
|
|
344
344
|
|
|
345
|
-
The emitter already knows every operation→method mapping (from `resolveMethodName` or the equivalent). `
|
|
345
|
+
The emitter already knows every operation→method mapping (from `resolveMethodName` or the equivalent). `buildOperationsMap` returns this mapping as an `OperationsMap` object. The orchestrator calls `emitter.buildOperationsMap?.(spec, ctx)` and writes the result into the `operations` field of `.oagen-manifest.json`.
|
|
346
346
|
|
|
347
347
|
**Implementation pattern** (`manifest.ts`):
|
|
348
348
|
|
|
349
349
|
```typescript
|
|
350
|
-
export function
|
|
351
|
-
const
|
|
350
|
+
export function buildOperationsMap(spec: ApiSpec, ctx: EmitterContext): OperationsMap {
|
|
351
|
+
const operations: OperationsMap = {};
|
|
352
352
|
for (const service of spec.services) {
|
|
353
353
|
const propName = /* service property name on the client */;
|
|
354
354
|
for (const op of service.operations) {
|
|
355
355
|
const httpKey = `${op.httpMethod.toUpperCase()} ${op.path}`;
|
|
356
356
|
const methodName = /* resolved method name for this operation */;
|
|
357
|
-
|
|
357
|
+
operations[httpKey] = { sdkMethod: methodName, service: propName };
|
|
358
358
|
}
|
|
359
359
|
}
|
|
360
|
-
return
|
|
361
|
-
path: "smoke-manifest.json",
|
|
362
|
-
content: JSON.stringify(manifest, null, 2),
|
|
363
|
-
}];
|
|
360
|
+
return operations;
|
|
364
361
|
}
|
|
365
362
|
```
|
|
366
363
|
|
|
@@ -66,9 +66,9 @@ Map IR service names to SDK resource accessors. There are two sources of truth d
|
|
|
66
66
|
|
|
67
67
|
### Generated SDK (preferred)
|
|
68
68
|
|
|
69
|
-
For emitter-generated SDKs, the `
|
|
69
|
+
For emitter-generated SDKs, the `operations` field in `.oagen-manifest.json` (produced by the emitter's `buildOperationsMap` hook) contains the authoritative `service` field for every operation. The smoke test should load this manifest and use its service mappings. The `SERVICE_MAP` only needs fallback entries for services not covered by the manifest.
|
|
70
70
|
|
|
71
|
-
If no
|
|
71
|
+
If no operations map exists, warn the user: the emitter should implement `buildOperationsMap`. Without it, most operations will be skipped because heuristic method resolution fails on disambiguated names.
|
|
72
72
|
|
|
73
73
|
### Live/hand-written SDK
|
|
74
74
|
|
|
@@ -93,7 +93,7 @@ Each language's SDK will have different accessor names — discover them by read
|
|
|
93
93
|
|
|
94
94
|
Adapt the 4-tier resolution to the target language's naming conventions:
|
|
95
95
|
|
|
96
|
-
0. **Manifest match** — Load `
|
|
96
|
+
0. **Manifest match** — Load the `operations` map from `.oagen-manifest.json` in the SDK output directory (emitter-generated, not hand-maintained). This is the **primary** resolution path for generated SDKs. The manifest maps every `HTTP_METHOD /path` to `{ sdkMethod, service }` and is produced by the emitter's `buildOperationsMap` hook. If the operations map is missing, warn and fall through to heuristic tiers.
|
|
97
97
|
1. **Exact match** — IR operation name converted to target convention
|
|
98
98
|
2. **CRUD prefix match** — standard verbs (create, list, retrieve/get, update, delete) with service name tiebreaker
|
|
99
99
|
3. **Keyword fuzzy match** — stem words and score overlap
|
|
@@ -123,7 +123,7 @@ Create the script **in the emitter project**:
|
|
|
123
123
|
3. `main()` function:
|
|
124
124
|
- Parse CLI args, validate API key
|
|
125
125
|
- Parse spec via `parseSpec()`
|
|
126
|
-
- Load
|
|
126
|
+
- Load operations map from `{sdk-path}/.oagen-manifest.json` (emitter-generated). If missing, log a warning — method resolution will rely on heuristic tiers and most operations will likely be skipped.
|
|
127
127
|
- Load and configure the SDK
|
|
128
128
|
- Iterate `planOperations()` groups
|
|
129
129
|
- For each operation: resolve SDK method, resolve path params, build args, call SDK, capture exchange
|
|
@@ -167,11 +167,11 @@ During initial setup, run `oagen generate` then the smoke test until skips are m
|
|
|
167
167
|
|
|
168
168
|
```bash
|
|
169
169
|
oagen generate --lang {lang} --output {sdk-path} --spec {spec} --namespace {ns}
|
|
170
|
-
# The emitter
|
|
170
|
+
# The emitter writes the operations map into .oagen-manifest.json — the smoke test loads it automatically.
|
|
171
171
|
oagen verify --lang {lang} --output {sdk-path} --spec {spec}
|
|
172
172
|
```
|
|
173
173
|
|
|
174
|
-
If many operations are skipped with "No matching SDK method", check that the emitter's `
|
|
174
|
+
If many operations are skipped with "No matching SDK method", check that the emitter's `buildOperationsMap` is implemented and that `.oagen-manifest.json` contains an `operations` field. The operations map is the primary mechanism the smoke test uses to find SDK methods.
|
|
175
175
|
|
|
176
176
|
| Exit | Meaning | Output | Action |
|
|
177
177
|
| ---- | ------------- | --------------------------- | --------------------------------------- |
|
|
@@ -32,7 +32,7 @@ Determine required paths:
|
|
|
32
32
|
- **Emitter project** (`project`): from argument, or detect from current directory
|
|
33
33
|
- **Output directory** (`output`): use a temp directory or the emitter project's `sdk/` directory
|
|
34
34
|
- **API surface** (`api_surface`): `sdk-{language}-surface.json` in the emitter project
|
|
35
|
-
- **Manifest** (`manifest`):
|
|
35
|
+
- **Manifest** (`manifest`): `.oagen-manifest.json` in the output directory (contains file list and operations map)
|
|
36
36
|
|
|
37
37
|
## Step 2: Dry-Run Preview
|
|
38
38
|
|