@shipworthy/openapi-cli 0.0.2 → 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 +165 -103
- 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 +1 -1
- 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,7 +10,7 @@
|
|
|
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
16
|
bun add -d @shipworthy/openapi-cli @shipworthy/openapi-typescript
|
|
@@ -18,23 +18,23 @@ bun add -d @shipworthy/openapi-cli @shipworthy/openapi-typescript
|
|
|
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
|
|
@@ -44,9 +44,11 @@ Run the `openapi` commands below in a package script, or use `bunx --bun @shipwo
|
|
|
44
44
|
```sh
|
|
45
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.
|
|
@@ -198,7 +200,7 @@ writing output. See the overlay package's operation filtering example below.
|
|
|
198
200
|
An existing output file is overwritten only after bundling, overlay application, and optional pruning succeed.
|
|
199
201
|
Loading, invalid overlay, incompatible merge, JSONPath, pruning, and write errors exit with code `1`.
|
|
200
202
|
Action errors include the one-based action number and target. See the
|
|
201
|
-
[`@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.
|
|
202
204
|
|
|
203
205
|
### `openapi lint [--strict] <input>`
|
|
204
206
|
|
|
@@ -226,22 +228,48 @@ ERROR
|
|
|
226
228
|
Every operation must have unique "operationId". at #/paths//b/get/operationId
|
|
227
229
|
```
|
|
228
230
|
|
|
229
|
-
### `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.
|
|
230
234
|
|
|
231
|
-
|
|
235
|
+
```text
|
|
236
|
+
openapi generate <plugins...> [-i <input>] [-o <dir>] [--name <name>] [--camel-case] [--wire-types] [--check] [type flags]
|
|
237
|
+
```
|
|
232
238
|
|
|
233
239
|
```sh
|
|
234
|
-
openapi generate
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
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
|
|
238
247
|
```
|
|
239
248
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
|
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. |
|
|
245
273
|
|
|
246
274
|
Type-generation flags. All are boolean and default to `false` except `--array-length`, which
|
|
247
275
|
defaults to `true`:
|
|
@@ -254,39 +282,74 @@ defaults to `true`:
|
|
|
254
282
|
| `--dedupe-enums` | Dedupe enum types when `--enum` is set. |
|
|
255
283
|
| `--export-type`, `-t` | Export top-level type aliases instead of interfaces. |
|
|
256
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`. |
|
|
257
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. |
|
|
258
287
|
| `--read-write-markers` | Generate `$Read`/`$Write` markers for `readOnly`/`writeOnly` properties. |
|
|
259
288
|
|
|
260
|
-
|
|
261
|
-
OpenAPI templates, such as `/pets/{id}`.
|
|
289
|
+
#### Which files are written
|
|
262
290
|
|
|
263
|
-
|
|
291
|
+
A run writes:
|
|
264
292
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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.
|
|
268
296
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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>]
|
|
272
333
|
```
|
|
273
334
|
|
|
274
|
-
|
|
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`)
|
|
275
338
|
|
|
276
|
-
|
|
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).
|
|
277
344
|
|
|
278
345
|
```sh
|
|
279
|
-
openapi
|
|
280
|
-
|
|
346
|
+
openapi overlay openapi.yaml --overlay types.overlay.yaml -o types.openapi.json
|
|
347
|
+
openapi generate typescript -i types.openapi.json -o src/generated
|
|
281
348
|
```
|
|
282
349
|
|
|
283
|
-
|
|
284
|
-
| ---------------- | ----------- | ---------- | --------------------------------------------------------- |
|
|
285
|
-
| `<input>` | path or URL | (required) | OpenAPI document. |
|
|
286
|
-
| `--output`, `-o` | file path | (required) | Output file. `types.ts` is written to the same directory. |
|
|
287
|
-
| `--force` | boolean | `false` | Regenerate `types.ts` even when its stamp matches. |
|
|
350
|
+
#### `zod` output (`zod-schema.ts`)
|
|
288
351
|
|
|
289
|
-
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`.
|
|
290
353
|
|
|
291
354
|
```ts
|
|
292
355
|
import { Pet } from "./generated/pets/zod-schema";
|
|
@@ -294,29 +357,17 @@ import { Pet } from "./generated/pets/zod-schema";
|
|
|
294
357
|
const pet = Pet.parse(await response.json()); // typed as components["schemas"]["Pet"]
|
|
295
358
|
```
|
|
296
359
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
Generates a typed API client and TanStack Query helpers. Requires `@shipworthy/openapi-tanstack-query` and `@shipworthy/openapi-typescript`.
|
|
300
|
-
|
|
301
|
-
```sh
|
|
302
|
-
openapi generate tanstack-query ./openapi.yaml -o ./src/generated --name hotels
|
|
303
|
-
# writes src/generated/hotels/client.ts and src/generated/hotels/types.ts
|
|
304
|
-
```
|
|
360
|
+
With `--camel-case`, the validators check camelCase objects. Convert wire payloads to camelCase before you parse them.
|
|
305
361
|
|
|
306
|
-
|
|
307
|
-
| ---------------- | ----------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
308
|
-
| `<input>` | path or URL | (required) | OpenAPI document. |
|
|
309
|
-
| `--output`, `-o` | directory | (required) | Directory that will contain `<name>/client.ts` and `<name>/types.ts`. |
|
|
310
|
-
| `--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]+)*$`). |
|
|
311
|
-
| `--force` | boolean | `false` | Regenerate `types.ts` even when its stamp matches. |
|
|
362
|
+
#### `tanstack-query` output (`client.ts`)
|
|
312
363
|
|
|
313
|
-
For
|
|
364
|
+
Generates a typed API client and TanStack Query helpers. For `-o src/generated/hotels`, `client.ts` exports:
|
|
314
365
|
|
|
315
366
|
- `createHotelsApiClient(options)` and the `HotelsApiClient` type.
|
|
316
367
|
- `createHotelsApiQueryUtils(client, options?)`, which returns per-operation helpers (`queryOptions`, `mutationOptions`, `infiniteOptions`) keyed by `operationId`.
|
|
317
368
|
- Type re-exports `HotelsApiPaths`, `HotelsApiComponents`, and `HotelsApiOperations`.
|
|
318
369
|
|
|
319
|
-
|
|
370
|
+
Operations need unique `operationId`s.
|
|
320
371
|
|
|
321
372
|
```ts
|
|
322
373
|
import { useQuery } from "@tanstack/react-query";
|
|
@@ -333,24 +384,32 @@ const hotels = useQuery(
|
|
|
333
384
|
);
|
|
334
385
|
```
|
|
335
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
|
+
|
|
336
390
|
See the `@shipworthy/openapi-tanstack-query` README for the full runtime API.
|
|
337
391
|
|
|
338
|
-
|
|
392
|
+
#### `standard-schema` output (`standard-schema.ts`)
|
|
339
393
|
|
|
340
|
-
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`.
|
|
341
395
|
|
|
342
|
-
|
|
343
|
-
openapi generate standard-schema ./openapi.yaml -o ./src/generated/schemas.ts
|
|
344
|
-
```
|
|
396
|
+
#### Migrating from the per-plugin subcommands
|
|
345
397
|
|
|
346
|
-
|
|
347
|
-
| ---------------- | ----------- | ---------- | ------------------------------------------- |
|
|
348
|
-
| `<input>` | path or URL | (required) | OpenAPI document. |
|
|
349
|
-
| `--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.
|
|
350
399
|
|
|
351
|
-
|
|
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. |
|
|
352
409
|
|
|
353
|
-
|
|
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
|
|
354
413
|
|
|
355
414
|
Every generated file starts with a stamped header:
|
|
356
415
|
|
|
@@ -359,18 +418,13 @@ Every generated file starts with a stamped header:
|
|
|
359
418
|
* This file was auto-generated by @shipworthy/openapi-typescript.
|
|
360
419
|
* Do not make direct changes to the file.
|
|
361
420
|
*
|
|
362
|
-
* @shipworthy/openapi-typescript@0.0.1:<sha256
|
|
421
|
+
* @shipworthy/openapi-typescript@0.0.1:<sha256>
|
|
363
422
|
*/
|
|
364
423
|
```
|
|
365
424
|
|
|
366
|
-
The stamp is `<generator package>@<generator version>:<sha256>`. The hash covers the document text
|
|
367
|
-
|
|
368
|
-
`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.
|
|
369
426
|
|
|
370
|
-
|
|
371
|
-
- `--force` always regenerates it.
|
|
372
|
-
- These commands always write `types.ts` with the default type flags. To use custom flags (`--enum`, `--root-types`, …), run `openapi generate typescript` yourself.
|
|
373
|
-
- 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.
|
|
374
428
|
|
|
375
429
|
## Programmatic use
|
|
376
430
|
|
|
@@ -397,29 +451,38 @@ openapi.pipe(
|
|
|
397
451
|
);
|
|
398
452
|
```
|
|
399
453
|
|
|
400
|
-
This uses `effect`
|
|
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`.
|
|
401
455
|
|
|
402
456
|
## Errors and exit codes
|
|
403
457
|
|
|
404
458
|
Each failure prints an `ERROR` block to stderr and exits with `1`. Common messages:
|
|
405
459
|
|
|
406
|
-
| Message
|
|
407
|
-
|
|
|
408
|
-
| `Globbing is unsupported.`
|
|
409
|
-
| `Missing required
|
|
410
|
-
| `
|
|
411
|
-
|
|
|
412
|
-
|
|
|
413
|
-
|
|
|
414
|
-
|
|
|
415
|
-
|
|
|
416
|
-
| `
|
|
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.
|
|
417
480
|
|
|
418
481
|
## Limitations
|
|
419
482
|
|
|
420
483
|
- One input per run. Globs are not supported.
|
|
421
|
-
- Only `generate
|
|
422
|
-
-
|
|
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.
|
|
423
486
|
- `lint` does not resolve external `$ref`s. It lints the document text exactly as given.
|
|
424
487
|
- Lint rules are fixed (Spectral `oas` with the adjustments listed above). Custom rulesets are not supported.
|
|
425
488
|
|
|
@@ -429,12 +492,11 @@ Each failure prints an `ERROR` block to stderr and exits with `1`. Common messag
|
|
|
429
492
|
- Commands:
|
|
430
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.
|
|
431
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.
|
|
432
|
-
- `openapi generate
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
- Each `generate`
|
|
437
|
-
- `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`.
|
|
438
500
|
- Do not edit generated files by hand. Re-run the command instead.
|
|
439
501
|
- The only programmatic export is `openapi`, an `effect/cli` `Command`.
|
|
440
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
|
+
]));
|