@workos/oagen 0.19.6 → 0.21.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.
@@ -1,6 +1,6 @@
1
1
  import { m as CommandError } from "../tree-sitter-DcUvm0KA.mjs";
2
- import { a as MANIFEST_FILENAME, i as generate, n as getEmitter, t as diffSpecs } from "../diff-C8mjApjv.mjs";
3
- import { A as getDefaultPolicy, T as apiSurfaceToSnapshot, c as filterSurface, f as specDerivedMethodPaths, i as getExtractor, j as mergePolicy, l as specDerivedEnumValues, n as isPatchableChange, o as diffSnapshots, p as specDerivedNames, r as patchOverlay, t as buildOverlayLookup, u as specDerivedFieldPaths } from "../overlay-Dk70ilXl.mjs";
2
+ import { a as MANIFEST_FILENAME, i as generate, n as getEmitter, t as diffSpecs } from "../diff-CBFCoTCw.mjs";
3
+ import { A as getDefaultPolicy, T as apiSurfaceToSnapshot, c as filterSurface, f as specDerivedMethodPaths, i as getExtractor, j as mergePolicy, l as specDerivedEnumValues, n as isPatchableChange, o as diffSnapshots, p as specDerivedNames, r as patchOverlay, t as buildOverlayLookup, u as specDerivedFieldPaths } from "../overlay-cb32jDBS.mjs";
4
4
  import * as path$1 from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { execFileSync } from "node:child_process";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workos/oagen",
3
- "version": "0.19.6",
3
+ "version": "0.21.0",
4
4
  "description": "Framework for building OpenAPI SDK emitters",
5
5
  "author": "WorkOS",
6
6
  "license": "MIT",
@@ -11,7 +11,7 @@ Create a self-contained smoke test script for a new SDK language that captures w
11
11
 
12
12
  Each language's smoke test is a single file: `smoke/sdk-{lang}.ts` **in the emitter project**. It uses the target language's native HTTP interception to capture what the SDK actually sends over the wire, then outputs `SmokeResults` JSON. The diff tool compares this against a baseline and reports mismatches by severity.
13
13
 
14
- The script is self-contained — no proxy, no subprocess protocol, no separate driver. It imports shared infrastructure from `@workos/oagen/smoke` and implements language-specific parts inline.
14
+ It imports shared infrastructure from `@workos/oagen/smoke` and implements language-specific parts inline.
15
15
 
16
16
  ## Resolve Paths
17
17
 
@@ -50,15 +50,15 @@ Store it as `spec`.
50
50
 
51
51
  ## Step 1: Determine HTTP Interception Strategy
52
52
 
53
- The interception must capture the raw request (method, path, query params, body) and raw response (status, body). Choose based on the target language's SDK:
53
+ Choose the interception mechanism for the target language. It must capture the raw request (method, path, query, body) and response (status, body), storing both in a `currentCapture` variable (~20-30 lines):
54
54
 
55
- - **Node:** Patch `globalThis.fetch`
56
- - **Ruby:** WebMock `stub_request` or monkey-patch `Net::HTTP`
57
- - **Python:** `responses`, `respx` (for httpx), or `unittest.mock.patch`
58
- - **Go:** Custom `http.RoundTripper`
59
- - **Java/Kotlin:** OkHttp `Interceptor`
60
-
61
- The interception code is typically ~20-30 lines. It must capture the request as-sent, let the real HTTP call proceed, capture the response, and store both in a `currentCapture` variable.
55
+ | Language | Mechanism |
56
+ | ----------- | ------------------------------------------------------ |
57
+ | Node | Patch `globalThis.fetch` |
58
+ | Ruby | WebMock `stub_request` or monkey-patch `Net::HTTP` |
59
+ | Python | `responses`, `respx` (httpx), or `unittest.mock.patch` |
60
+ | Go | Custom `http.RoundTripper` |
61
+ | Java/Kotlin | OkHttp `Interceptor` |
62
62
 
63
63
  ## Step 2: Build the SERVICE_MAP
64
64
 
@@ -91,48 +91,27 @@ Each language's SDK will have different accessor names — discover them by read
91
91
 
92
92
  ## Step 3: Implement SDK Method Resolution
93
93
 
94
- Adapt the 4-tier resolution to the target language's naming conventions:
94
+ Adapt the 4-tier resolution to the target language's naming conventions (Ruby/Python: `snake_case`, Go: `PascalCase`, Node: `camelCase`):
95
95
 
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.
96
+ 0. **Manifest match** — Primary path. Uses the operations map loaded in Step 2. Fall through if unavailable.
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
100
100
 
101
- Key convention differences: Ruby/Python use `snake_case`, Go uses `PascalCase`, Node uses `camelCase`.
102
-
103
101
  Each resolution records provenance metadata (`ExchangeProvenance`) so findings can be traced back to the resolution path.
104
102
 
105
103
  ## Step 4: Implement Argument Construction
106
104
 
107
- Build SDK call arguments from IR operations (reference `buildArgs()` in existing smoke scripts):
108
-
109
- - No path params + has body → `method(payload)`
110
- - No path params + has query params → `method(queryOpts)`
111
- - Single path param, no body/query → `method(id)` (positional)
112
- - Complex (path params + body/query) → `method(mergedOptions)`
113
- - Idempotent POST → append empty options object for idempotency key
114
-
115
- Choose the right payload convention: Node uses `generateCamelPayload()`, Ruby/Python may use `generatePayload()` directly (snake_case).
105
+ Build SDK call arguments from IR operations. Reference `buildArgs()` in existing smoke scripts and adapt to the target language's calling convention. See [references/implementation-patterns.md](references/implementation-patterns.md) for the concrete branching template covering all argument patterns (positional, payload-only, query-only, complex, idempotent POST).
116
106
 
117
107
  ## Step 5: Write `smoke/sdk-{lang}.ts`
118
108
 
119
- Create the script **in the emitter project**:
120
-
121
- 1. Imports from `@workos/oagen/smoke`
122
- 2. HTTP interception setup
123
- 3. `main()` function:
124
- - Parse CLI args, validate API key
125
- - Parse spec via `parseSpec()`
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
- - Load and configure the SDK
128
- - Iterate `planOperations()` groups
129
- - For each operation: resolve SDK method, resolve path params, build args, call SDK, capture exchange
130
- - Extract IDs via `ids.extractAndStore()`
131
- - Track POST creates for cleanup
132
- - Cleanup created entities in reverse
133
- - Restore original HTTP behavior
134
- - Write `smoke-results-sdk-{lang}.json`
135
- 4. Summary output (successes, errors, skipped, unexpected statuses)
109
+ Create the script **in the emitter project**. See [references/implementation-patterns.md](references/implementation-patterns.md) for the full structural template. The script follows this flow:
110
+
111
+ 1. Import shared infrastructure from `@workos/oagen/smoke`
112
+ 2. Set up HTTP interception (Step 1)
113
+ 3. `main()`: parse spec → load operations map → init SDK → iterate `planOperations()` groups → resolve method (Step 3) → build args (Step 4) → call SDK → capture exchange → extract IDs via `ids.extractAndStore()` → track POST creates for cleanup
114
+ 4. Cleanup created entities in reverse order, restore HTTP, write `smoke-results-sdk-{lang}.json`
136
115
 
137
116
  ## Step 6: Register the Smoke Runner
138
117
 
@@ -167,11 +146,10 @@ During initial setup, run `oagen generate` then the smoke test until skips are m
167
146
 
168
147
  ```bash
169
148
  oagen generate --lang {lang} --output {sdk-path} --spec {spec} --namespace {ns}
170
- # The emitter writes the operations map into .oagen-manifest.json — the smoke test loads it automatically.
171
149
  oagen verify --lang {lang} --output {sdk-path} --spec {spec}
172
150
  ```
173
151
 
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.
152
+ If many operations are skipped with "No matching SDK method", verify the operations map is present (see Step 2).
175
153
 
176
154
  | Exit | Meaning | Output | Action |
177
155
  | ---- | ------------- | --------------------------- | --------------------------------------- |
@@ -0,0 +1,87 @@
1
+ # Smoke Test Implementation Patterns
2
+
3
+ Concrete templates for the language-specific parts of a smoke test script. Adapt naming conventions and HTTP interception to the target language.
4
+
5
+ ## Argument Construction (`buildArgs`)
6
+
7
+ Build SDK call arguments from IR operations. Choose payload generator based on target language: Node uses `generateCamelPayload()`, Ruby/Python use `generatePayload()` (snake_case).
8
+
9
+ ```typescript
10
+ function buildArgs(op: OperationPlan, spec: ApiSpec): unknown[] {
11
+ const pathParams = op.parameters.filter((p) => p.in === "path");
12
+ const hasBody = !!op.requestBody;
13
+ const hasQuery = op.parameters.some((p) => p.in === "query");
14
+
15
+ if (pathParams.length === 0 && hasBody) return [generatePayload(op, spec)];
16
+ if (pathParams.length === 0 && hasQuery) return [generateQueryParams(op)];
17
+ if (pathParams.length === 1 && !hasBody && !hasQuery)
18
+ return [ids.get(pathParams[0].schema) ?? "test_id"];
19
+ // Complex: merge path params + body/query into options object
20
+ const opts = { ...generatePayload(op, spec), ...resolvePathParams(op) };
21
+ if (hasQuery) Object.assign(opts, generateQueryParams(op));
22
+ // Idempotent POST: append empty options for idempotency key
23
+ if (
24
+ op.method === "post" &&
25
+ op.parameters.some((p) => p.name === "idempotency_key")
26
+ )
27
+ return [opts, {}];
28
+ return [opts];
29
+ }
30
+ ```
31
+
32
+ ## Smoke Script Structural Template
33
+
34
+ The complete structure for `smoke/sdk-{lang}.ts`:
35
+
36
+ ```typescript
37
+ import {
38
+ parseSpec,
39
+ planOperations,
40
+ generatePayload,
41
+ generateQueryParams,
42
+ IdRegistry,
43
+ isUnexpectedStatus,
44
+ resolvePath,
45
+ type CapturedExchange,
46
+ type SmokeResults,
47
+ } from "@workos/oagen/smoke";
48
+
49
+ let currentCapture: CapturedExchange | null = null;
50
+ const ids = new IdRegistry();
51
+
52
+ // Interception setup — see language table in SKILL.md Step 1
53
+
54
+ async function main() {
55
+ const spec = await parseSpec(specPath);
56
+ const opsMap = loadManifestOperations(sdkPath); // from Step 2
57
+ const client = initSdk(apiKey);
58
+ const results: SmokeResults = { exchanges: [], errors: [], skipped: [] };
59
+ const cleanups: Array<() => Promise<void>> = [];
60
+
61
+ for (const group of planOperations(spec)) {
62
+ for (const op of group.operations) {
63
+ const resolved = resolveMethod(op, opsMap, client); // Step 3
64
+ if (!resolved) {
65
+ results.skipped.push(op);
66
+ continue;
67
+ }
68
+ const args = buildArgs(op, spec); // Step 4
69
+ try {
70
+ currentCapture = null;
71
+ await resolved.fn(...args);
72
+ if (currentCapture) {
73
+ ids.extractAndStore(currentCapture.response);
74
+ results.exchanges.push(currentCapture);
75
+ if (op.method === "post")
76
+ cleanups.push(() => deleteEntity(client, op));
77
+ }
78
+ } catch (e) {
79
+ results.errors.push({ op, error: e });
80
+ }
81
+ }
82
+ }
83
+ for (const cleanup of cleanups.reverse()) await cleanup();
84
+ writeFileSync(`smoke-results-sdk-{lang}.json`, JSON.stringify(results));
85
+ printSummary(results); // successes, errors, skipped, unexpected statuses
86
+ }
87
+ ```