@shipworthy/openapi-cli 0.0.1 → 1.0.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/README.md +170 -106
- package/dist/bin.js +2 -1
- package/dist/cli.d.ts +1 -1
- package/dist/commands/generate/command.d.ts +19 -2
- package/dist/commands/generate/command.js +74 -6
- package/dist/commands/generate/{plugins/typescript/flags.d.ts → flags.d.ts} +5 -4
- package/dist/commands/generate/{plugins/typescript/flags.js → flags.js} +5 -12
- package/dist/commands/generate/output.d.ts +15 -0
- package/dist/commands/generate/output.js +35 -0
- package/dist/commands/generate/plan.d.ts +53 -0
- package/dist/commands/generate/plan.js +93 -0
- package/dist/commands/generate/render.d.ts +25 -0
- package/dist/commands/generate/render.js +63 -0
- package/dist/commands/generate/source.d.ts +5 -0
- package/dist/commands/generate/source.js +18 -0
- package/dist/commands/generate/stamp.d.ts +14 -10
- package/dist/commands/generate/stamp.js +16 -28
- package/dist/commands/input.js +2 -2
- package/dist/commands/overlay/command.js +4 -3
- package/package.json +18 -17
- package/dist/commands/generate/ensure.d.ts +0 -9
- package/dist/commands/generate/ensure.js +0 -31
- package/dist/commands/generate/files.d.ts +0 -4
- package/dist/commands/generate/files.js +0 -16
- package/dist/commands/generate/force.d.ts +0 -2
- package/dist/commands/generate/force.js +0 -2
- package/dist/commands/generate/plugins/standard-schema/plugin.d.ts +0 -7
- package/dist/commands/generate/plugins/standard-schema/plugin.js +0 -44
- package/dist/commands/generate/plugins/tanstack-query/plugin.d.ts +0 -9
- package/dist/commands/generate/plugins/tanstack-query/plugin.js +0 -42
- package/dist/commands/generate/plugins/typescript/plugin.d.ts +0 -32
- package/dist/commands/generate/plugins/typescript/plugin.js +0 -84
- package/dist/commands/generate/plugins/zod/plugin.d.ts +0 -8
- package/dist/commands/generate/plugins/zod/plugin.js +0 -48
- package/dist/commands/generate/publish.d.ts +0 -11
- package/dist/commands/generate/publish.js +0 -23
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @shipworthy/openapi-cli
|
|
2
2
|
|
|
3
|
-
`@shipworthy/openapi-cli` installs the `openapi` command. It bundles, overlays, and lints OpenAPI 3.x documents and generates code from them: TypeScript types, Zod validators, Standard Schema validators, and typed TanStack Query clients. Use it in a build step or `generate` script that keeps generated API code in sync with a local or remote OpenAPI document. Each generator is a separate optional package (`@shipworthy/openapi-typescript`, `@shipworthy/openapi-zod`, `@shipworthy/openapi-standard-schema`, `@shipworthy/openapi-tanstack-query`). The CLI loads a generator only when
|
|
3
|
+
`@shipworthy/openapi-cli` installs the `openapi` command. It bundles, overlays, and lints OpenAPI 3.x documents and generates code from them: TypeScript types, Zod validators, Standard Schema validators, and typed TanStack Query clients. Use it in a build step or `generate` script that keeps generated API code in sync with a local or remote OpenAPI document. Each generator is a separate optional package (`@shipworthy/openapi-typescript`, `@shipworthy/openapi-zod`, `@shipworthy/openapi-standard-schema`, `@shipworthy/openapi-tanstack-query`). One `openapi generate` run can select several generator plugins and writes one file per plugin into an output directory. The CLI loads a generator only when a run selects its plugin, so install just the ones you use.
|
|
4
4
|
|
|
5
5
|
## Requirements
|
|
6
6
|
|
|
@@ -10,31 +10,31 @@
|
|
|
10
10
|
|
|
11
11
|
## Installation
|
|
12
12
|
|
|
13
|
-
Install the CLI and the generator packages you need as dev dependencies. The CLI brings its own dependencies (`effect
|
|
13
|
+
Install the CLI and the generator packages you need as dev dependencies. The CLI brings its own dependencies (`effect`, `@effect/platform-node`, and `@effect/platform-node-shared` pinned to `4.1.4`, plus `@shipworthy/openapi-generator`, `@shipworthy/openapi-parser`, `@shipworthy/openapi-overlay`, and `@shipworthy/openapi-validator`):
|
|
14
14
|
|
|
15
15
|
```sh
|
|
16
|
-
bun add -d @shipworthy/openapi-cli @shipworthy/openapi-typescript
|
|
16
|
+
bun add -d @shipworthy/openapi-cli @shipworthy/openapi-typescript
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
### Optional peer dependencies (generators)
|
|
20
20
|
|
|
21
|
-
Install the generator package for each `generate`
|
|
21
|
+
Install the generator package for each `generate` plugin you use, at the same version as the CLI:
|
|
22
22
|
|
|
23
|
-
|
|
|
24
|
-
|
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
29
|
-
| `openapi lint`
|
|
23
|
+
| Plugin / command | Requires | Notes |
|
|
24
|
+
| ----------------- | ------------------------------------------------------------------------- | ----------------------------------------------------------- |
|
|
25
|
+
| `typescript` | `@shipworthy/openapi-typescript` | Writes `types.ts`. |
|
|
26
|
+
| `zod` | `@shipworthy/openapi-zod` and `@shipworthy/openapi-typescript` | Writes `zod-schema.ts`; also needs `types.ts`. |
|
|
27
|
+
| `tanstack-query` | `@shipworthy/openapi-tanstack-query` and `@shipworthy/openapi-typescript` | Writes `client.ts`; also needs `types.ts`. |
|
|
28
|
+
| `standard-schema` | `@shipworthy/openapi-standard-schema` | Writes `standard-schema.ts`. |
|
|
29
|
+
| `openapi lint` | nothing extra | Bundled through `@shipworthy/openapi-validator` (Spectral). |
|
|
30
30
|
|
|
31
31
|
The generated files import packages at runtime, so your application needs those packages too:
|
|
32
32
|
|
|
33
33
|
| Generated file | Imports |
|
|
34
34
|
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
35
35
|
| `types.ts` (TypeScript) | nothing (types only) |
|
|
36
|
-
| Zod
|
|
37
|
-
|
|
|
36
|
+
| `zod-schema.ts` (Zod) | `zod` (v4; the generator is built against `zod@4.6.7`) and `./types` |
|
|
37
|
+
| `standard-schema.ts` | `effect` (`4.1.4`) |
|
|
38
38
|
| `client.ts` (TanStack Query) | `@shipworthy/openapi-tanstack-query` (a runtime dependency), which peers on `@tanstack/react-query@^5.104.1` and `react@^18.0.0 \|\| ^19.0.0` |
|
|
39
39
|
|
|
40
40
|
## Quick start
|
|
@@ -42,11 +42,13 @@ The generated files import packages at runtime, so your application needs those
|
|
|
42
42
|
Run the `openapi` commands below in a package script, or use `bunx --bun @shipworthy/openapi-cli` in place of `openapi`.
|
|
43
43
|
|
|
44
44
|
```sh
|
|
45
|
-
bun add -d @shipworthy/openapi-cli @shipworthy/openapi-typescript
|
|
45
|
+
bun add -d @shipworthy/openapi-cli @shipworthy/openapi-typescript
|
|
46
46
|
bunx --bun @shipworthy/openapi-cli lint ./openapi.yaml
|
|
47
|
-
bunx --bun @shipworthy/openapi-cli generate typescript ./openapi.yaml -o ./src/generated
|
|
47
|
+
bunx --bun @shipworthy/openapi-cli generate typescript -i ./openapi.yaml -o ./src/generated
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
The `generate` command writes `src/generated/types.ts`.
|
|
51
|
+
|
|
50
52
|
Given this `openapi.yaml`:
|
|
51
53
|
|
|
52
54
|
```yaml
|
|
@@ -106,12 +108,12 @@ A typical `package.json` script that generates a full SDK (from the repository's
|
|
|
106
108
|
```json
|
|
107
109
|
{
|
|
108
110
|
"scripts": {
|
|
109
|
-
"generate": "openapi generate tanstack-query
|
|
111
|
+
"generate": "openapi generate tanstack-query zod standard-schema -i openapi.json -o src/generated/hotels"
|
|
110
112
|
}
|
|
111
113
|
}
|
|
112
114
|
```
|
|
113
115
|
|
|
114
|
-
That script produces `src/generated/hotels/{types.ts,client.ts,zod-schema.ts,standard-schema.ts}`.
|
|
116
|
+
That script produces `src/generated/hotels/{types.ts,client.ts,zod-schema.ts,standard-schema.ts}`. The client name `hotels` comes from the output directory.
|
|
115
117
|
|
|
116
118
|
## Commands
|
|
117
119
|
|
|
@@ -121,13 +123,13 @@ openapi <subcommand> [flags]
|
|
|
121
123
|
SUBCOMMANDS
|
|
122
124
|
bundle Bundle an OpenAPI document
|
|
123
125
|
overlay Apply an OpenAPI Overlay
|
|
124
|
-
generate
|
|
126
|
+
generate Generate types, validators, and clients
|
|
125
127
|
lint Lint an OpenAPI schema
|
|
126
128
|
```
|
|
127
129
|
|
|
128
130
|
### Input argument (all commands)
|
|
129
131
|
|
|
130
|
-
|
|
132
|
+
`bundle`, `overlay`, and `lint` take a positional `<input>`. `generate` takes the input with `-i`/`--input` and reads the document from stdin when the flag is omitted. In both forms the input is:
|
|
131
133
|
|
|
132
134
|
- A local path, relative to the current directory, e.g. `./openapi.yaml`.
|
|
133
135
|
- An `http:`, `https:`, or `file:` URL. Remote documents are fetched over HTTP, and the response must have a 2xx status.
|
|
@@ -181,7 +183,9 @@ openapi overlay openapi.yaml --overlay selected.overlay.yaml --remove-unused-com
|
|
|
181
183
|
|
|
182
184
|
Actions run sequentially against the bundled document. Supports RFC 9535 JSONPath, recursive
|
|
183
185
|
object updates, array concatenation/appending, primitive replacement, copying from a single
|
|
184
|
-
source node, and removal.
|
|
186
|
+
source node, and removal. Invalid JSONPath target expressions and targets matching no nodes
|
|
187
|
+
are skipped; the CLI logs each as a warning to stderr with its action number and target, then
|
|
188
|
+
writes the result of the remaining actions. `extends` is metadata;
|
|
185
189
|
`<input>` determines the document. The overlaid result is written directly without another
|
|
186
190
|
bundling or validation pass.
|
|
187
191
|
|
|
@@ -196,7 +200,7 @@ writing output. See the overlay package's operation filtering example below.
|
|
|
196
200
|
An existing output file is overwritten only after bundling, overlay application, and optional pruning succeed.
|
|
197
201
|
Loading, invalid overlay, incompatible merge, JSONPath, pruning, and write errors exit with code `1`.
|
|
198
202
|
Action errors include the one-based action number and target. See the
|
|
199
|
-
[`@shipworthy/overlay` documentation](../openapi-overlay/README.md) for merge rules and errors.
|
|
203
|
+
[`@shipworthy/openapi-overlay` documentation](../openapi-overlay/README.md) for merge rules and errors.
|
|
200
204
|
|
|
201
205
|
### `openapi lint [--strict] <input>`
|
|
202
206
|
|
|
@@ -224,22 +228,48 @@ ERROR
|
|
|
224
228
|
Every operation must have unique "operationId". at #/paths//b/get/operationId
|
|
225
229
|
```
|
|
226
230
|
|
|
227
|
-
### `openapi generate
|
|
231
|
+
### `openapi generate <plugins...>`
|
|
232
|
+
|
|
233
|
+
Runs one or more generator plugins against one OpenAPI document and writes one file per plugin into an output directory.
|
|
228
234
|
|
|
229
|
-
|
|
235
|
+
```text
|
|
236
|
+
openapi generate <plugins...> [-i <input>] [-o <dir>] [--name <name>] [--camel-case] [--wire-types] [--check] [type flags]
|
|
237
|
+
```
|
|
230
238
|
|
|
231
239
|
```sh
|
|
232
|
-
openapi generate
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
openapi generate typescript ./openapi.yaml -o ./
|
|
240
|
+
openapi generate zod tanstack-query -i ./openapi.json -o src/generated/reservations --camel-case --wire-types
|
|
241
|
+
# writes src/generated/reservations/{client.ts,types.ts,wire-types.ts,zod-schema.ts}
|
|
242
|
+
|
|
243
|
+
openapi generate typescript -i ./openapi.yaml -o ./src/generated # writes src/generated/types.ts
|
|
244
|
+
openapi generate typescript -i https://example.com/openapi.json > schema.ts # one file, printed to stdout
|
|
245
|
+
cat openapi.yaml | openapi generate standard-schema > schemas.ts # reads stdin
|
|
246
|
+
openapi generate typescript zod -i ./openapi.yaml -o ./src/generated --check # CI drift check
|
|
236
247
|
```
|
|
237
248
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
|
249
|
+
#### Plugins
|
|
250
|
+
|
|
251
|
+
Pass at least one plugin. Each plugin writes one file with a fixed name:
|
|
252
|
+
|
|
253
|
+
| Plugin | Package (optional peer) | File | Also needs `types.ts` |
|
|
254
|
+
| ----------------- | ------------------------------------- | -------------------- | --------------------- |
|
|
255
|
+
| `typescript` | `@shipworthy/openapi-typescript` | `types.ts` | (it is `types.ts`) |
|
|
256
|
+
| `zod` | `@shipworthy/openapi-zod` | `zod-schema.ts` | yes |
|
|
257
|
+
| `tanstack-query` | `@shipworthy/openapi-tanstack-query` | `client.ts` | yes |
|
|
258
|
+
| `standard-schema` | `@shipworthy/openapi-standard-schema` | `standard-schema.ts` | no |
|
|
259
|
+
|
|
260
|
+
`zod` and `tanstack-query` import their types from `./types`, so they also require `@shipworthy/openapi-typescript` to be installed. The order of plugins does not matter, and a plugin listed twice runs once. Any order produces identical files.
|
|
261
|
+
|
|
262
|
+
#### Options
|
|
263
|
+
|
|
264
|
+
| Flag | Type | Default | Description |
|
|
265
|
+
| ---------------- | ------------------------------------ | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
266
|
+
| `--input`, `-i` | path or `http:`/`https:`/`file:` URL | stdin | OpenAPI document (JSON or YAML). Reads the document from stdin when omitted. An input containing `*` fails with `Globbing is unsupported.` |
|
|
267
|
+
| `--output`, `-o` | directory | stdout | Directory that receives the files. It is created recursively. When omitted, the run must produce exactly one file, which is written to stdout. |
|
|
268
|
+
| `--name` | string | basename of the output directory | Used only by `tanstack-query`: the PascalCase prefix of generated exports and the query-key prefix. Must match `^[a-zA-Z][a-zA-Z0-9]*(?:[-_][a-zA-Z0-9]+)*$`. Ignored when `tanstack-query` is not selected. See [Client name](#client-name). |
|
|
269
|
+
| `--camel-case` | boolean | `false` | Spell snake_case field and parameter names in camelCase in every generated file. See [camelCase and wire types](#camelcase-and-wire-types). |
|
|
270
|
+
| `--wire-types` | boolean | `false` | Also write `wire-types.ts`: the same types as `types.ts` with the API's field names. Requires `--camel-case` and a plugin that writes `types.ts`. |
|
|
271
|
+
| `--check` | boolean | `false` | Writes nothing. Compares every generated file with the existing file in `--output` and exits `1` when any differs or is missing. Requires `--output`. |
|
|
272
|
+
| type flags | boolean | `false` (`--array-length`: `true`) | See the next table. They apply to `types.ts` and `wire-types.ts` and are ignored when neither is generated. |
|
|
243
273
|
|
|
244
274
|
Type-generation flags. All are boolean and default to `false` except `--array-length`, which
|
|
245
275
|
defaults to `true`:
|
|
@@ -252,39 +282,74 @@ defaults to `true`:
|
|
|
252
282
|
| `--dedupe-enums` | Dedupe enum types when `--enum` is set. |
|
|
253
283
|
| `--export-type`, `-t` | Export top-level type aliases instead of interfaces. |
|
|
254
284
|
| `--array-length` | Generate tuples using array `minItems` / `maxItems`. Enabled by default; disable with `--array-length=false` or `--no-array-length`. |
|
|
285
|
+
| `--camel-case` | Spell snake_case schema fields and parameter names in camelCase. Applies to every generated file, not only `types.ts`. |
|
|
255
286
|
| `--root-types` | Export component and operation aliases at the root level. Schema aliases have no prefix; operation aliases uppercase the first character of the operation ID. |
|
|
256
287
|
| `--read-write-markers` | Generate `$Read`/`$Write` markers for `readOnly`/`writeOnly` properties. |
|
|
257
288
|
|
|
258
|
-
|
|
259
|
-
OpenAPI templates, such as `/pets/{id}`.
|
|
289
|
+
#### Which files are written
|
|
260
290
|
|
|
261
|
-
|
|
291
|
+
A run writes:
|
|
262
292
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
293
|
+
- `types.ts` when `typescript`, `zod`, or `tanstack-query` is selected. It is generated once and shared by `zod-schema.ts` and `client.ts`.
|
|
294
|
+
- `zod-schema.ts`, `client.ts`, and `standard-schema.ts` for each selected plugin.
|
|
295
|
+
- `wire-types.ts` when `--wire-types` is set.
|
|
266
296
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
297
|
+
File names are fixed and `-o` is always a directory. An `--output` that ends in `.ts`, `.mts`, `.cts`, `.js`, `.mjs`, `.cjs`, or `.json` is rejected. Every run regenerates and overwrites every file it writes.
|
|
298
|
+
|
|
299
|
+
#### Client name
|
|
300
|
+
|
|
301
|
+
`tanstack-query` needs a client name. Without `--name`, it is the basename of the output directory: `-o src/generated/hotels` gives `hotels`. The name in PascalCase is the prefix of the generated exports, and a `-` or `_` marks a word boundary: `-o src/generated/my-pets` gives `createMyPetsApiClient`. The raw name is the default query-key prefix.
|
|
302
|
+
|
|
303
|
+
When the basename does not match the name pattern, the run fails with `Cannot derive a client name from the output directory "<basename>". Pass --name.`
|
|
304
|
+
|
|
305
|
+
#### camelCase and wire types
|
|
306
|
+
|
|
307
|
+
`--camel-case` applies to every generated file:
|
|
308
|
+
|
|
309
|
+
- `types.ts` spells snake_case schema fields and parameter names in camelCase.
|
|
310
|
+
- `client.ts` converts declared parameter and JSON body fields between camelCase and the API's names at runtime.
|
|
311
|
+
- `zod-schema.ts` validators accept and return the camelCase shapes declared in `types.ts`.
|
|
312
|
+
- `standard-schema.ts` exports camelCase types and validators.
|
|
313
|
+
|
|
314
|
+
Enum and const values keep the API's spelling. When two property names at one object level become the same camelCase name, generation fails with a message such as `#/components/schemas/User: user_id and userId both become userId`, and nothing is written.
|
|
315
|
+
|
|
316
|
+
`--wire-types` also writes `wire-types.ts`: the same types as `types.ts`, with the API's field names. Import its `operations` or `components` types when a custom `fetch` or mock `Response` handles the API's snake_case payload before client conversion. It requires `--camel-case` and at least one of `typescript`, `zod`, or `tanstack-query`.
|
|
317
|
+
|
|
318
|
+
#### stdin and stdout
|
|
319
|
+
|
|
320
|
+
- Without `-i`, the document is read from stdin. Invalid invocations fail before stdin is read.
|
|
321
|
+
- Without `-o`, the run must produce exactly one file. Only that file is written to stdout, with no log output, and the `typescript` plugin's warnings are suppressed. A run that would produce more than one file (for example `zod`, which also needs `types.ts`) fails with `--output is required when generating more than one file.`
|
|
322
|
+
|
|
323
|
+
#### CI drift check (`--check`)
|
|
324
|
+
|
|
325
|
+
`--check` writes nothing. It generates every file the run would write and compares each one with the existing file in `--output`. When any file differs or is missing, it exits `1` with `Generated files are not up-to-date: <file names>`, listing the file names sorted and separated by `, `. A file that cannot be read counts as out of date. When every file matches, `--check` prints nothing and exits `0`. `--check` requires `--output`.
|
|
326
|
+
|
|
327
|
+
#### How a run executes
|
|
328
|
+
|
|
329
|
+
The document is read and parsed once, however many plugins run. All files are then rendered concurrently from that document. Nothing is written unless every file was generated successfully; if any generator fails, the output directory is left untouched. Invalid invocations fail before the input is read. With `-o`, after all files are written, the command logs one line per file, sorted by file name:
|
|
330
|
+
|
|
331
|
+
```text
|
|
332
|
+
<input URL or stdin> → <output>/<file> [<elapsed>]
|
|
270
333
|
```
|
|
271
334
|
|
|
272
|
-
|
|
335
|
+
The input is shown as a URL (`file:///…` for local paths) or as `stdin`, and the output directory as you typed it, without trailing slashes. Every line of one run shows the same elapsed time.
|
|
336
|
+
|
|
337
|
+
#### `typescript` output (`types.ts`)
|
|
273
338
|
|
|
274
|
-
|
|
339
|
+
Converts the OpenAPI document to TypeScript types (`paths`, `webhooks`, `components`, `operations`, …) and formats the result. The output always includes an `ApiPaths` enum with route values. Paths retain their literal OpenAPI templates, such as `/pets/{id}`.
|
|
340
|
+
|
|
341
|
+
Use an overlay before generation to require properties, allow additional keys, remove
|
|
342
|
+
deprecated members, or declare path parameters. See the
|
|
343
|
+
[overlay examples](../openapi-overlay/README.md#preparing-a-document-for-generation).
|
|
275
344
|
|
|
276
345
|
```sh
|
|
277
|
-
openapi
|
|
278
|
-
|
|
346
|
+
openapi overlay openapi.yaml --overlay types.overlay.yaml -o types.openapi.json
|
|
347
|
+
openapi generate typescript -i types.openapi.json -o src/generated
|
|
279
348
|
```
|
|
280
349
|
|
|
281
|
-
|
|
282
|
-
| ---------------- | ----------- | ---------- | --------------------------------------------------------- |
|
|
283
|
-
| `<input>` | path or URL | (required) | OpenAPI document. |
|
|
284
|
-
| `--output`, `-o` | file path | (required) | Output file. `types.ts` is written to the same directory. |
|
|
285
|
-
| `--force` | boolean | `false` | Regenerate `types.ts` even when its stamp matches. |
|
|
350
|
+
#### `zod` output (`zod-schema.ts`)
|
|
286
351
|
|
|
287
|
-
The
|
|
352
|
+
The file imports `zod` and `type { components, operations } from "./types"`. It exports one validator per component schema, typed as `z.ZodType<components["schemas"][Name]>`, plus `<operationId>RequestBody` validators for request bodies. During generation, Zod is resolved from the output directory and must be `zod@4.6.7`.
|
|
288
353
|
|
|
289
354
|
```ts
|
|
290
355
|
import { Pet } from "./generated/pets/zod-schema";
|
|
@@ -292,29 +357,17 @@ import { Pet } from "./generated/pets/zod-schema";
|
|
|
292
357
|
const pet = Pet.parse(await response.json()); // typed as components["schemas"]["Pet"]
|
|
293
358
|
```
|
|
294
359
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
Generates a typed API client and TanStack Query helpers. Requires `@shipworthy/openapi-tanstack-query` and `@shipworthy/openapi-typescript`.
|
|
298
|
-
|
|
299
|
-
```sh
|
|
300
|
-
openapi generate tanstack-query ./openapi.yaml -o ./src/generated --name hotels
|
|
301
|
-
# writes src/generated/hotels/client.ts and src/generated/hotels/types.ts
|
|
302
|
-
```
|
|
360
|
+
With `--camel-case`, the validators check camelCase objects. Convert wire payloads to camelCase before you parse them.
|
|
303
361
|
|
|
304
|
-
|
|
305
|
-
| ---------------- | ----------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
306
|
-
| `<input>` | path or URL | (required) | OpenAPI document. |
|
|
307
|
-
| `--output`, `-o` | directory | (required) | Directory that will contain `<name>/client.ts` and `<name>/types.ts`. |
|
|
308
|
-
| `--name` | string | (required) | Client name. Used as the output subdirectory and, in PascalCase, as the prefix of generated exports. It must start with a letter and contain only letters, numbers, and single `-` or `_` separators (`^[a-zA-Z][a-zA-Z0-9]*(?:[-_][a-zA-Z0-9]+)*$`). |
|
|
309
|
-
| `--force` | boolean | `false` | Regenerate `types.ts` even when its stamp matches. |
|
|
362
|
+
#### `tanstack-query` output (`client.ts`)
|
|
310
363
|
|
|
311
|
-
For
|
|
364
|
+
Generates a typed API client and TanStack Query helpers. For `-o src/generated/hotels`, `client.ts` exports:
|
|
312
365
|
|
|
313
366
|
- `createHotelsApiClient(options)` and the `HotelsApiClient` type.
|
|
314
367
|
- `createHotelsApiQueryUtils(client, options?)`, which returns per-operation helpers (`queryOptions`, `mutationOptions`, `infiniteOptions`) keyed by `operationId`.
|
|
315
368
|
- Type re-exports `HotelsApiPaths`, `HotelsApiComponents`, and `HotelsApiOperations`.
|
|
316
369
|
|
|
317
|
-
|
|
370
|
+
Operations need unique `operationId`s.
|
|
318
371
|
|
|
319
372
|
```ts
|
|
320
373
|
import { useQuery } from "@tanstack/react-query";
|
|
@@ -331,24 +384,32 @@ const hotels = useQuery(
|
|
|
331
384
|
);
|
|
332
385
|
```
|
|
333
386
|
|
|
387
|
+
Add `--camel-case` to use `minStars` in app code while sending `min_stars` to the API. The
|
|
388
|
+
generated client also converts JSON request, success, and error bodies according to the schema.
|
|
389
|
+
|
|
334
390
|
See the `@shipworthy/openapi-tanstack-query` README for the full runtime API.
|
|
335
391
|
|
|
336
|
-
|
|
392
|
+
#### `standard-schema` output (`standard-schema.ts`)
|
|
337
393
|
|
|
338
|
-
Generates [Standard Schema](https://standardschema.dev) validators backed by Effect `Schema`.
|
|
394
|
+
Generates [Standard Schema](https://standardschema.dev) validators backed by Effect `Schema`. For each component schema, the output exports a type and a value with the same name, e.g. `export type Pet = …` and `export const Pet = Schema.toStandardSchemaV1(…)`. This plugin does not need `types.ts`.
|
|
339
395
|
|
|
340
|
-
|
|
341
|
-
openapi generate standard-schema ./openapi.yaml -o ./src/generated/schemas.ts
|
|
342
|
-
```
|
|
396
|
+
#### Migrating from the per-plugin subcommands
|
|
343
397
|
|
|
344
|
-
|
|
345
|
-
| ---------------- | ----------- | ---------- | ------------------------------------------- |
|
|
346
|
-
| `<input>` | path or URL | (required) | OpenAPI document. |
|
|
347
|
-
| `--output`, `-o` | file path | stdout | Output file. Writes to stdout when omitted. |
|
|
398
|
+
Earlier versions had one subcommand per generator, a positional `<input>`, an output file path for some generators, a `--force` flag, and a `types.ts` cache that skipped regeneration when its stamp matched. These are removed: the input moved to `-i`, `-o` is always a directory, file names are fixed, and every run regenerates all its files. `--check` covers every generated file.
|
|
348
399
|
|
|
349
|
-
|
|
400
|
+
| Before | Now |
|
|
401
|
+
| -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
|
|
402
|
+
| `openapi generate typescript spec.yaml -o ./schema.ts` | `openapi generate typescript -i spec.yaml -o ./dir` (writes `./dir/types.ts`) |
|
|
403
|
+
| `openapi generate typescript spec.yaml > schema.ts` | `openapi generate typescript -i spec.yaml > schema.ts` |
|
|
404
|
+
| `openapi generate zod spec.yaml -o src/generated/hotels/zod-schema.ts` | `openapi generate zod -i spec.yaml -o src/generated/hotels` |
|
|
405
|
+
| `openapi generate tanstack-query spec.yaml -o src/generated --name hotels` | `openapi generate tanstack-query -i spec.yaml -o src/generated/hotels` |
|
|
406
|
+
| `openapi generate standard-schema spec.yaml -o src/generated/schemas.ts` | `openapi generate standard-schema -i spec.yaml -o src/generated` (writes `standard-schema.ts`) |
|
|
407
|
+
| Several `generate` commands for one SDK | One command: `openapi generate tanstack-query zod standard-schema -i spec.yaml -o src/generated/hotels` |
|
|
408
|
+
| `--force` | Removed. Every run regenerates every file. |
|
|
350
409
|
|
|
351
|
-
|
|
410
|
+
`--camel-case` now also applies to `zod-schema.ts` and `standard-schema.ts`, so Zod no longer needs a separate directory with API-cased types. `--wire-types` is a general flag rather than a `tanstack-query` option.
|
|
411
|
+
|
|
412
|
+
## Generated file headers
|
|
352
413
|
|
|
353
414
|
Every generated file starts with a stamped header:
|
|
354
415
|
|
|
@@ -357,18 +418,13 @@ Every generated file starts with a stamped header:
|
|
|
357
418
|
* This file was auto-generated by @shipworthy/openapi-typescript.
|
|
358
419
|
* Do not make direct changes to the file.
|
|
359
420
|
*
|
|
360
|
-
* @shipworthy/openapi-typescript@0.0.1:<sha256
|
|
421
|
+
* @shipworthy/openapi-typescript@0.0.1:<sha256>
|
|
361
422
|
*/
|
|
362
423
|
```
|
|
363
424
|
|
|
364
|
-
The stamp is `<generator package>@<generator version>:<sha256>`. The hash covers the document text
|
|
365
|
-
|
|
366
|
-
`generate zod` and `generate tanstack-query` both need `types.ts`:
|
|
425
|
+
The stamp is `<generator package>@<generator version>:<sha256>`. The hash covers the document text. It does not depend on the document's location, so reading the same document from stdin, another path, or a URL gives the same stamp.
|
|
367
426
|
|
|
368
|
-
|
|
369
|
-
- `--force` always regenerates it.
|
|
370
|
-
- These commands always write `types.ts` with the default type flags. To use custom flags (`--enum`, `--root-types`, …), run `openapi generate typescript` yourself.
|
|
371
|
-
- The main output (`client.ts`, the Zod file) is always overwritten.
|
|
427
|
+
Files generated with `--camel-case` add a `Field casing: camelCase` line after the stamp, and their hash covers the document text plus the casing setting, so toggling the flag changes the stamp. `wire-types.ts` never has the casing line and always uses the plain hash.
|
|
372
428
|
|
|
373
429
|
## Programmatic use
|
|
374
430
|
|
|
@@ -395,29 +451,38 @@ openapi.pipe(
|
|
|
395
451
|
);
|
|
396
452
|
```
|
|
397
453
|
|
|
398
|
-
This
|
|
454
|
+
This uses `effect` and `@effect/platform-node` `4.1.4`. The CLI depends on both, but add them to your own `package.json` when your code imports them, using versions compatible with the CLI so they resolve to one copy. No other functions or types are exported. For direct access to the generation pipeline, use `@shipworthy/openapi-generator`, `@shipworthy/openapi-parser`, and `@shipworthy/openapi-transformer`.
|
|
399
455
|
|
|
400
456
|
## Errors and exit codes
|
|
401
457
|
|
|
402
458
|
Each failure prints an `ERROR` block to stderr and exits with `1`. Common messages:
|
|
403
459
|
|
|
404
|
-
| Message
|
|
405
|
-
|
|
|
406
|
-
| `Globbing is unsupported.`
|
|
407
|
-
| `Missing required
|
|
408
|
-
| `
|
|
409
|
-
|
|
|
410
|
-
|
|
|
411
|
-
|
|
|
412
|
-
|
|
|
413
|
-
|
|
|
414
|
-
| `
|
|
460
|
+
| Message | Cause |
|
|
461
|
+
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
|
|
462
|
+
| `Globbing is unsupported.` | The input contains `*`. |
|
|
463
|
+
| `Missing required argument: plugins` | `generate` was run without a plugin. |
|
|
464
|
+
| `Unknown plugin: <value>. Available plugins: standard-schema, tanstack-query, typescript, zod.` | A `generate` positional is not a known plugin. |
|
|
465
|
+
| `--wire-types requires --camel-case.` | `--wire-types` was passed without `--camel-case`. |
|
|
466
|
+
| `--wire-types requires typescript, zod, or tanstack-query.` | No selected plugin writes `types.ts`. |
|
|
467
|
+
| `--check requires --output.` | `--check` was passed without `-o`. |
|
|
468
|
+
| `--output is required when generating more than one file.` | `-o` was omitted and the run would write more than one file. |
|
|
469
|
+
| `--output must be a directory. Each plugin writes its own file inside it.` | `-o` ends in `.ts`, `.mts`, `.cts`, `.js`, `.mjs`, `.cjs`, or `.json`. |
|
|
470
|
+
| `Invalid value for flag --name: "<value>". Expected: Schema validation failed: --name must start with a letter and contain only letters, numbers, and single hyphen or underscore separators.` | `--name` does not match the allowed pattern. |
|
|
471
|
+
| `Cannot derive a client name from the output directory "<basename>". Pass --name.` | `tanstack-query` without `--name`, and the output directory's basename is not a valid name. |
|
|
472
|
+
| `Generated files are not up-to-date: <file names>` | `generate --check` found generated files that differ from, or are missing in, the output directory. |
|
|
473
|
+
| `Install <package> to load <export>.` (or a module-resolution error) | The generator package for a selected plugin is not installed. |
|
|
474
|
+
| `Unsupported Swagger version: 2.x. Use OpenAPI 3.x instead.` | The input is a Swagger 2.0 document. |
|
|
475
|
+
| `Unsupported OpenAPI version: <v>` | The `openapi` field is not a 3.x version. |
|
|
476
|
+
| `NotFound: FileSystem.readFile (<path>)` | The input file does not exist. |
|
|
477
|
+
| `Expected an OpenAPI 3.x document` | `lint` was given something other than an OpenAPI 3.x document. |
|
|
478
|
+
|
|
479
|
+
When `generate` fails validation or any file fails to generate, nothing is written and the output directory is unchanged.
|
|
415
480
|
|
|
416
481
|
## Limitations
|
|
417
482
|
|
|
418
483
|
- One input per run. Globs are not supported.
|
|
419
|
-
- Only `generate
|
|
420
|
-
-
|
|
484
|
+
- Only `generate` reads from stdin. It writes to stdout only when the run produces exactly one file.
|
|
485
|
+
- `generate` file names are fixed; `-o` names a directory, not a file.
|
|
421
486
|
- `lint` does not resolve external `$ref`s. It lints the document text exactly as given.
|
|
422
487
|
- Lint rules are fixed (Spectral `oas` with the adjustments listed above). Custom rulesets are not supported.
|
|
423
488
|
|
|
@@ -427,12 +492,11 @@ Each failure prints an `ERROR` block to stderr and exits with `1`. Common messag
|
|
|
427
492
|
- Commands:
|
|
428
493
|
- `openapi overlay <input> --overlay <overlay> -o <output> [--remove-unused-components]`: bundles and upgrades first, then applies actions using the declared Overlay 1.0 or 1.1 version and writes JSON. The optional flag prunes paths without operations and unreachable components after overlaying.
|
|
429
494
|
- `openapi lint [--strict] <input>`: exits 1 only on errors such as duplicate `operationId`. `--strict` also fails on every `error`-severity `oas` rule and on syntax errors.
|
|
430
|
-
- `openapi generate
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
- Each `generate`
|
|
435
|
-
- `types.ts` is regenerated only when its header stamp (`pkg@version:sha256(schema)`) changes. Pass `--force` to override.
|
|
495
|
+
- `openapi generate <plugins...> [-i <input>] [-o <dir>] [--name <name>] [--camel-case] [--wire-types] [--check] [type flags]`: one command runs any number of plugins against one document. Plugin order does not matter. Nothing is written if any file fails to generate.
|
|
496
|
+
- `generate` plugin → file in the `-o` directory: `typescript` → `types.ts`, `zod` → `zod-schema.ts`, `tanstack-query` → `client.ts`, `standard-schema` → `standard-schema.ts`. `types.ts` is also written (once) when `zod` or `tanstack-query` is selected. `tanstack-query` names its exports after `--name`, which defaults to the output directory's basename.
|
|
497
|
+
- `--camel-case` applies to every generated file. `--wire-types` (requires `--camel-case` and a plugin that writes `types.ts`) also writes `wire-types.ts` with the API's field names.
|
|
498
|
+
- `generate` reads stdin when `-i` is omitted and writes the single generated file to stdout when `-o` is omitted. `--check` (requires `-o`) writes nothing and exits 1 when any generated file differs or is missing. Every run regenerates every file; there is no cache and no `--force`.
|
|
499
|
+
- Each `generate` plugin needs its optional peer package installed: `@shipworthy/openapi-typescript` (also required by `zod` and `tanstack-query`), `@shipworthy/openapi-zod`, `@shipworthy/openapi-tanstack-query`, and `@shipworthy/openapi-standard-schema`.
|
|
436
500
|
- Do not edit generated files by hand. Re-run the command instead.
|
|
437
501
|
- The only programmatic export is `openapi`, an `effect/cli` `Command`.
|
|
438
502
|
|
package/dist/bin.js
CHANGED
|
@@ -13,7 +13,8 @@ import * as NodeServices from "@effect/platform-node/NodeServices";
|
|
|
13
13
|
import { ModuleLoader } from "@shipworthy/openapi-generator";
|
|
14
14
|
import { Effect, Layer } from "effect";
|
|
15
15
|
import { Command } from "effect/cli";
|
|
16
|
+
import { Digest } from "effect/crypto";
|
|
16
17
|
import { FetchHttpClient } from "effect/http";
|
|
17
18
|
import { openapi } from "./cli.js";
|
|
18
19
|
import { version } from "./version.js";
|
|
19
|
-
version.pipe(Effect.flatMap((version) => openapi.pipe(Command.run({ version }))), Effect.provideService(ModuleLoader, { load: (specifier) => import(__rewriteRelativeImportExtension(specifier, true)) }), Effect.provide(Layer.mergeAll(NodeServices.layer, FetchHttpClient.layer)), NodeRuntime.runMain);
|
|
20
|
+
version.pipe(Effect.flatMap((version) => openapi.pipe(Command.run({ version }))), Effect.provideService(ModuleLoader, { load: (specifier) => import(__rewriteRelativeImportExtension(specifier, true)) }), Effect.provide(Layer.mergeAll(NodeServices.layer, FetchHttpClient.layer, Digest.layerWebCrypto)), NodeRuntime.runMain);
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { Command } from "effect/cli";
|
|
2
|
-
export declare const openapi: Command.Command<"openapi", {}, {}, import("effect/cli/CliError").UserError, import("effect/
|
|
2
|
+
export declare const openapi: Command.Command<"openapi", {}, {}, import("effect/cli/CliError").UserError, import("effect/crypto/Digest").Digest | import("effect/FileSystem").FileSystem | import("effect/http/HttpClient").HttpClient | import("effect/Path").Path | import("effect/Stdio").Stdio>;
|
|
@@ -1,2 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { Option } from "effect";
|
|
2
|
+
import { CliError, Command } from "effect/cli";
|
|
3
|
+
export declare const generate: Command.Command<"generate", {
|
|
4
|
+
readonly camelCase: boolean;
|
|
5
|
+
readonly enum: boolean;
|
|
6
|
+
readonly enumValues: boolean;
|
|
7
|
+
readonly conditionalEnums: boolean;
|
|
8
|
+
readonly dedupeEnums: boolean;
|
|
9
|
+
readonly exportType: boolean;
|
|
10
|
+
readonly arrayLength: boolean;
|
|
11
|
+
readonly rootTypes: boolean;
|
|
12
|
+
readonly readWriteMarkers: boolean;
|
|
13
|
+
readonly plugins: readonly string[];
|
|
14
|
+
readonly input: Option.Option<URL>;
|
|
15
|
+
readonly output: Option.Option<string>;
|
|
16
|
+
readonly name: Option.Option<string>;
|
|
17
|
+
readonly wireTypes: boolean;
|
|
18
|
+
readonly check: boolean;
|
|
19
|
+
}, {}, CliError.UserError, import("effect/crypto/Digest").Digest | import("effect/Stdio").Stdio | import("@shipworthy/openapi-generator").GenerateServices>;
|
|
@@ -1,6 +1,74 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
1
|
+
import { loadService } from "@shipworthy/openapi-generator";
|
|
2
|
+
import { Clock, Effect, Option, Path, Result } from "effect";
|
|
3
|
+
import { Argument, CliError, Command, Flag } from "effect/cli";
|
|
4
|
+
import { asCliError, schemaSource } from "../input.js";
|
|
5
|
+
import { typeFlags } from "./flags.js";
|
|
6
|
+
import { checkFiles, writeFiles, writeStdout } from "./output.js";
|
|
7
|
+
import { ClientName, planFiles } from "./plan.js";
|
|
8
|
+
import { renderFile } from "./render.js";
|
|
9
|
+
import { readSource, readStdin } from "./source.js";
|
|
10
|
+
const run = Effect.fn("generate")(function* (flags) {
|
|
11
|
+
const path = yield* Path.Path;
|
|
12
|
+
const files = planFiles({
|
|
13
|
+
plugins: flags.plugins,
|
|
14
|
+
output: flags.output,
|
|
15
|
+
outputName: Option.map(flags.output, (output) => path.basename(path.resolve(output))),
|
|
16
|
+
name: flags.name,
|
|
17
|
+
camelCase: flags.camelCase,
|
|
18
|
+
wireTypes: flags.wireTypes,
|
|
19
|
+
check: flags.check,
|
|
20
|
+
});
|
|
21
|
+
if (Result.isFailure(files)) {
|
|
22
|
+
return yield* new CliError.UserError({ cause: files.failure });
|
|
23
|
+
}
|
|
24
|
+
const started = yield* Clock.currentTimeMillis;
|
|
25
|
+
const source = yield* Option.match(flags.input, {
|
|
26
|
+
onNone: readStdin,
|
|
27
|
+
onSome: (input) => Effect.succeed(input),
|
|
28
|
+
});
|
|
29
|
+
const [text, model] = yield* Effect.all([typeof source === "string" ? Effect.succeed(source) : readSource(source), loadService(source)], { concurrency: "unbounded" });
|
|
30
|
+
const directory = path.resolve(Option.getOrElse(flags.output, () => "."));
|
|
31
|
+
const rendered = yield* Effect.forEach(files.success, (file) => renderFile(file, {
|
|
32
|
+
model,
|
|
33
|
+
text,
|
|
34
|
+
directory,
|
|
35
|
+
typeFlags: flags,
|
|
36
|
+
silent: Option.isNone(flags.output),
|
|
37
|
+
}), { concurrency: "unbounded" });
|
|
38
|
+
yield* Option.match(flags.output, {
|
|
39
|
+
onNone: () => Effect.forEach(rendered, (file) => writeStdout(file.contents), { discard: true }),
|
|
40
|
+
onSome: (output) => flags.check
|
|
41
|
+
? checkFiles(rendered, directory)
|
|
42
|
+
: writeFiles(rendered, {
|
|
43
|
+
directory,
|
|
44
|
+
label: Option.match(flags.input, { onNone: () => "stdin", onSome: (url) => url.href }),
|
|
45
|
+
outputLabel: output,
|
|
46
|
+
started,
|
|
47
|
+
}),
|
|
48
|
+
});
|
|
49
|
+
}, Effect.mapError(asCliError));
|
|
50
|
+
export const generate = Command.make("generate", {
|
|
51
|
+
plugins: Argument.String("plugins").pipe(Argument.withDescription("Generator plugins to run, in any order: typescript, zod, tanstack-query, standard-schema."), Argument.variadic({ min: 1 })),
|
|
52
|
+
input: Flag.String("input").pipe(Flag.withAlias("i"), Flag.withDescription("OpenAPI schema file or URL. Reads stdin when omitted."), Flag.optional, Flag.mapEffect((input) => Option.match(input, {
|
|
53
|
+
onNone: () => Effect.succeed(Option.none()),
|
|
54
|
+
onSome: (value) => Effect.asSome(schemaSource(value)),
|
|
55
|
+
}))),
|
|
56
|
+
output: Flag.String("output").pipe(Flag.withAlias("o"), Flag.withDescription("Directory that receives the generated files. Writes the only file to stdout when omitted."), Flag.optional),
|
|
57
|
+
name: Flag.String("name").pipe(Flag.withDescription("tanstack-query client name: the prefix of generated exports. Defaults to the output directory name."), Flag.withSchema(ClientName), Flag.optional),
|
|
58
|
+
wireTypes: Flag.Boolean("wire-types").pipe(Flag.withDescription("Also write wire-types.ts with API field names (requires --camel-case)."), Flag.withDefault(false)),
|
|
59
|
+
check: Flag.Boolean("check").pipe(Flag.withDescription("Write nothing; fail when a generated file in --output is missing or out of date."), Flag.withDefault(false)),
|
|
60
|
+
...typeFlags,
|
|
61
|
+
}, run).pipe(Command.withDescription("Generate code from an OpenAPI schema with one or more plugins."), Command.withShortDescription("Generate types, validators, and clients"), Command.withExamples([
|
|
62
|
+
{
|
|
63
|
+
command: "openapi generate typescript -i ./openapi.yaml -o ./src/generated/api",
|
|
64
|
+
description: "Write src/generated/api/types.ts",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
command: "openapi generate zod tanstack-query -i ./openapi.json -o ./src/generated/reservations --camel-case --wire-types",
|
|
68
|
+
description: "Write client.ts, types.ts, wire-types.ts, and zod-schema.ts in src/generated/reservations",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
command: "openapi generate typescript < ./openapi.json > ./types.ts",
|
|
72
|
+
description: "Read stdin and write types.ts to stdout",
|
|
73
|
+
},
|
|
74
|
+
]));
|