@powerduck/openapi-codegen 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +583 -0
- package/dist/index.js +6711 -0
- package/package.json +65 -0
package/README.md
ADDED
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
# @powerduck/openapi-codegen
|
|
2
|
+
|
|
3
|
+
Generate runnable HTTP request examples from parsed OpenAPI documents for **21 language targets** and **41 language/client combinations**.
|
|
4
|
+
|
|
5
|
+
`@powerduck/openapi-codegen` is an ESM-first TypeScript library for API documentation systems, developer portals, API explorers, command-line tools, and build-time code generation.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Supports parsed OpenAPI documents, including OpenAPI 3.2
|
|
10
|
+
- Generates readable HTTP request examples across 41 generators
|
|
11
|
+
- Resolves path, query, header, and cookie parameters
|
|
12
|
+
- Supports API key, HTTP bearer, and other security schemes
|
|
13
|
+
- Supports JSON, plain-text, URL-encoded form, and multipart bodies
|
|
14
|
+
- Generates multipart file-upload examples with placeholder paths
|
|
15
|
+
- Preserves request bodies that have already been serialized
|
|
16
|
+
- Adds timeouts, HTTP status checks, and resource cleanup where supported
|
|
17
|
+
- Exposes generator discovery through `list()`
|
|
18
|
+
- Includes TypeScript declarations
|
|
19
|
+
- Ships as an ECMAScript module
|
|
20
|
+
|
|
21
|
+
## Supported Languages and Clients
|
|
22
|
+
|
|
23
|
+
Use the exact `language` and `client` identifiers shown below when calling `generate()`. Identifiers are case-sensitive.
|
|
24
|
+
|
|
25
|
+
| Language | `language` | Supported `client` values |
|
|
26
|
+
| ----------------- | ------------ | ----------------------------------------------------------------- |
|
|
27
|
+
| C | `c` | `libcurl` |
|
|
28
|
+
| C# | `csharp` | `httpclient`, `restsharp` |
|
|
29
|
+
| Clojure | `clojure` | `clj-http` |
|
|
30
|
+
| Dart | `dart` | `http` |
|
|
31
|
+
| F# | `fsharp` | `httpclient` |
|
|
32
|
+
| Go | `go` | `new-request` |
|
|
33
|
+
| HTTP request file | `http` | `http1` |
|
|
34
|
+
| Java | `java` | `asynchttp`, `java-net-http`, `okhttp`, `unirest` |
|
|
35
|
+
| JavaScript | `javascript` | `fetch`, `axios`, `ofetch`, `jquery`, `xhr` |
|
|
36
|
+
| Kotlin | `kotlin` | `okhttp` |
|
|
37
|
+
| Node.js | `node` | `fetch`, `axios`, `ofetch`, `undici` |
|
|
38
|
+
| Objective-C | `objc` | `nsurlsession` |
|
|
39
|
+
| OCaml | `ocaml` | `cohttp` |
|
|
40
|
+
| PHP | `php` | `curl`, `guzzle`, `laravel-http` |
|
|
41
|
+
| PowerShell | `powershell` | `invoke-webrequest`, `invoke-restmethod` |
|
|
42
|
+
| Python | `python` | `http-client`, `requests`, `aiohttp`, `httpx-sync`, `httpx-async` |
|
|
43
|
+
| R | `r` | `httr2` |
|
|
44
|
+
| Ruby | `ruby` | `net-http` |
|
|
45
|
+
| Rust | `rust` | `reqwest` |
|
|
46
|
+
| Shell | `shell` | `curl`, `wget`, `httpie` |
|
|
47
|
+
| Swift | `swift` | `nsurlsession` |
|
|
48
|
+
|
|
49
|
+
> Generator availability does not imply that every client can represent every OpenAPI operation. For example, GNU Wget cannot safely construct arbitrary `multipart/form-data` requests. Use `shell/curl` or `shell/httpie` for multipart uploads.
|
|
50
|
+
|
|
51
|
+
## Requirements
|
|
52
|
+
|
|
53
|
+
- Node.js 20 or later
|
|
54
|
+
- A parsed OpenAPI document
|
|
55
|
+
- The runtime and dependencies required by the selected generated client
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
Using npm:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npm install @powerduck/openapi-codegen
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Using pnpm:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pnpm add @powerduck/openapi-codegen
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Using Yarn:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
yarn add @powerduck/openapi-codegen
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Quick Start
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { generate } from "@powerduck/openapi-codegen";
|
|
81
|
+
import document from "./openapi.json" with { type: "json" };
|
|
82
|
+
|
|
83
|
+
const code = generate({
|
|
84
|
+
document,
|
|
85
|
+
path: "/users/{id}",
|
|
86
|
+
method: "get",
|
|
87
|
+
language: "javascript",
|
|
88
|
+
client: "fetch",
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
console.log(code);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`generate()` returns the generated source code as a string.
|
|
95
|
+
|
|
96
|
+
## Authentication
|
|
97
|
+
|
|
98
|
+
Provide credentials through `securityValues`:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import { generate } from "@powerduck/openapi-codegen";
|
|
102
|
+
import document from "./openapi.json" with { type: "json" };
|
|
103
|
+
|
|
104
|
+
const code = generate({
|
|
105
|
+
document,
|
|
106
|
+
path: "/users/{id}",
|
|
107
|
+
method: "get",
|
|
108
|
+
language: "javascript",
|
|
109
|
+
client: "fetch",
|
|
110
|
+
securityValues: {
|
|
111
|
+
bearer: "YOUR_ACCESS_TOKEN",
|
|
112
|
+
apiKey: "YOUR_API_KEY",
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
console.log(code);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The keys in `securityValues` must match the security scheme names defined in the OpenAPI document.
|
|
120
|
+
|
|
121
|
+
Do not commit real credentials to source control.
|
|
122
|
+
|
|
123
|
+
## Multipart Uploads
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
import { generate } from "@powerduck/openapi-codegen";
|
|
127
|
+
import document from "./openapi.json" with { type: "json" };
|
|
128
|
+
|
|
129
|
+
const code = generate({
|
|
130
|
+
document,
|
|
131
|
+
path: "/upload/{id}",
|
|
132
|
+
method: "post",
|
|
133
|
+
language: "shell",
|
|
134
|
+
client: "curl",
|
|
135
|
+
securityValues: {
|
|
136
|
+
bearer: "YOUR_ACCESS_TOKEN",
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
console.log(code);
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Generated multipart examples may include placeholder file paths:
|
|
144
|
+
|
|
145
|
+
```text
|
|
146
|
+
/tmp/file.bin
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Replace all placeholder paths before running the generated code.
|
|
150
|
+
|
|
151
|
+
Some clients cannot safely represent every OpenAPI request. Generation may throw a descriptive error when the selected client is incompatible with an operation.
|
|
152
|
+
|
|
153
|
+
For example, `shell/wget` does not support arbitrary multipart generation. Use `shell/curl` or `shell/httpie` instead.
|
|
154
|
+
|
|
155
|
+
## Discover Available Generators
|
|
156
|
+
|
|
157
|
+
Use `list()` to inspect every installed language/client combination:
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
import { list } from "@powerduck/openapi-codegen";
|
|
161
|
+
|
|
162
|
+
for (const { language, client } of list()) {
|
|
163
|
+
console.log(`${language}/${client}`);
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
You can also select a generator programmatically:
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
import { generate, list } from "@powerduck/openapi-codegen";
|
|
171
|
+
import document from "./openapi.json" with { type: "json" };
|
|
172
|
+
|
|
173
|
+
const generator = list().find(
|
|
174
|
+
({ language, client }) => language === "javascript" && client === "fetch",
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
if (!generator) {
|
|
178
|
+
throw new Error("The requested generator is not available");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const code = generate({
|
|
182
|
+
document,
|
|
183
|
+
path: "/users",
|
|
184
|
+
method: "get",
|
|
185
|
+
language: generator.language,
|
|
186
|
+
client: generator.client,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
console.log(code);
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## API Reference
|
|
193
|
+
|
|
194
|
+
### `generate(options)`
|
|
195
|
+
|
|
196
|
+
Generates an HTTP request example for an OpenAPI operation.
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
const code = generate({
|
|
200
|
+
document,
|
|
201
|
+
path,
|
|
202
|
+
method,
|
|
203
|
+
language,
|
|
204
|
+
client,
|
|
205
|
+
securityValues,
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
#### Options
|
|
210
|
+
|
|
211
|
+
| Option | Type | Required | Description |
|
|
212
|
+
| ---------------- | ------------------------ | -------: | -------------------------------------- |
|
|
213
|
+
| `document` | `unknown` | Yes | Parsed OpenAPI document |
|
|
214
|
+
| `path` | `string` | Yes | Exact OpenAPI path template |
|
|
215
|
+
| `method` | `string` | Yes | HTTP operation method |
|
|
216
|
+
| `language` | `string` | Yes | Target language identifier |
|
|
217
|
+
| `client` | `string` | Yes | Target HTTP client identifier |
|
|
218
|
+
| `securityValues` | `Record<string, string>` | No | Credentials for named security schemes |
|
|
219
|
+
|
|
220
|
+
#### Return Value
|
|
221
|
+
|
|
222
|
+
Returns the generated source code as a `string`.
|
|
223
|
+
|
|
224
|
+
#### Errors
|
|
225
|
+
|
|
226
|
+
Generation can throw when:
|
|
227
|
+
|
|
228
|
+
- The requested path or operation does not exist
|
|
229
|
+
- The language/client combination is unknown
|
|
230
|
+
- The OpenAPI document is invalid or unsupported
|
|
231
|
+
- Required generation data cannot be resolved
|
|
232
|
+
- The selected client cannot safely represent the request
|
|
233
|
+
|
|
234
|
+
Handle errors when processing untrusted documents or user-selected generators:
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
try {
|
|
238
|
+
const code = generate({
|
|
239
|
+
document,
|
|
240
|
+
path: "/users/{id}",
|
|
241
|
+
method: "get",
|
|
242
|
+
language: "javascript",
|
|
243
|
+
client: "fetch",
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
console.log(code);
|
|
247
|
+
} catch (error) {
|
|
248
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
249
|
+
|
|
250
|
+
console.error(`Generation failed: ${message}`);
|
|
251
|
+
process.exitCode = 1;
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### `list()`
|
|
256
|
+
|
|
257
|
+
Returns the available generator descriptors:
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
const generators = list();
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Each descriptor contains the identifiers required by `generate()`:
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
for (const { language, client } of generators) {
|
|
267
|
+
console.log(language, client);
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## OpenAPI Document Input
|
|
272
|
+
|
|
273
|
+
Pass a parsed JavaScript object rather than a JSON or YAML source string.
|
|
274
|
+
|
|
275
|
+
### JSON
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
import { readFile } from "node:fs/promises";
|
|
279
|
+
import { generate } from "@powerduck/openapi-codegen";
|
|
280
|
+
|
|
281
|
+
const source = await readFile("./openapi.json", "utf8");
|
|
282
|
+
const document = JSON.parse(source);
|
|
283
|
+
|
|
284
|
+
const code = generate({
|
|
285
|
+
document,
|
|
286
|
+
path: "/pets/{petId}",
|
|
287
|
+
method: "get",
|
|
288
|
+
language: "javascript",
|
|
289
|
+
client: "fetch",
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
console.log(code);
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### YAML
|
|
296
|
+
|
|
297
|
+
Install a YAML parser separately:
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
npm install yaml
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Parse the document before passing it to `generate()`:
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
import { readFile } from "node:fs/promises";
|
|
307
|
+
import YAML from "yaml";
|
|
308
|
+
import { generate } from "@powerduck/openapi-codegen";
|
|
309
|
+
|
|
310
|
+
const source = await readFile("./openapi.yaml", "utf8");
|
|
311
|
+
const document = YAML.parse(source);
|
|
312
|
+
|
|
313
|
+
const code = generate({
|
|
314
|
+
document,
|
|
315
|
+
path: "/pets/{petId}",
|
|
316
|
+
method: "get",
|
|
317
|
+
language: "javascript",
|
|
318
|
+
client: "fetch",
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
console.log(code);
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Generated Code
|
|
325
|
+
|
|
326
|
+
Generated examples are intended to be readable, runnable starting points. Depending on the selected language and client, they may include:
|
|
327
|
+
|
|
328
|
+
- A 30-second connection or request timeout
|
|
329
|
+
- HTTP status validation
|
|
330
|
+
- Response body output
|
|
331
|
+
- Resource cleanup
|
|
332
|
+
- Runtime and dependency notes
|
|
333
|
+
- Placeholder multipart file paths
|
|
334
|
+
- Environment-specific proxy or TLS behavior
|
|
335
|
+
|
|
336
|
+
Review generated code before using it in production. You may need to:
|
|
337
|
+
|
|
338
|
+
- Install dependencies for the selected client
|
|
339
|
+
- Replace placeholder parameter values
|
|
340
|
+
- Replace multipart file paths
|
|
341
|
+
- Supply authentication credentials securely
|
|
342
|
+
- Configure certificate trust
|
|
343
|
+
- Configure proxies
|
|
344
|
+
- Adjust timeout and redirect policies
|
|
345
|
+
- Add application-specific response parsing
|
|
346
|
+
|
|
347
|
+
## Development
|
|
348
|
+
|
|
349
|
+
Clone the repository and install dependencies:
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
git clone https://github.com/PowerDuckie/-powerduck-openapi-codegen.git
|
|
353
|
+
cd ./-powerduck-openapi-codegen
|
|
354
|
+
npm install
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
Run the test suite:
|
|
358
|
+
|
|
359
|
+
```bash
|
|
360
|
+
npm test
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Run property-based fuzz tests:
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
npm run fuzz
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Build the package:
|
|
370
|
+
|
|
371
|
+
```bash
|
|
372
|
+
npm run build
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Run all release checks:
|
|
376
|
+
|
|
377
|
+
```bash
|
|
378
|
+
npm run check
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Run the demo:
|
|
382
|
+
|
|
383
|
+
```bash
|
|
384
|
+
npm run demo
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## Testing
|
|
388
|
+
|
|
389
|
+
The project uses:
|
|
390
|
+
|
|
391
|
+
- [Vitest](https://vitest.dev/) for unit and snapshot tests
|
|
392
|
+
- [fast-check](https://fast-check.dev/) for property-based fuzz testing
|
|
393
|
+
|
|
394
|
+
Run all tests:
|
|
395
|
+
|
|
396
|
+
```bash
|
|
397
|
+
npm test
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
Update snapshots after intentionally changing generated output:
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
npx vitest run -u
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
Run only the fuzz test suite:
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
npm run fuzz
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Before committing snapshot changes, inspect the diff:
|
|
413
|
+
|
|
414
|
+
```bash
|
|
415
|
+
git diff
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
## Releasing
|
|
419
|
+
|
|
420
|
+
Before releasing, verify that:
|
|
421
|
+
|
|
422
|
+
- The working tree is clean
|
|
423
|
+
- You are on the intended branch
|
|
424
|
+
- The full validation suite passes
|
|
425
|
+
- You are authenticated with npm
|
|
426
|
+
- The package is owned by your npm account or organization
|
|
427
|
+
- The Git remote points to the intended repository
|
|
428
|
+
|
|
429
|
+
Check the repository state:
|
|
430
|
+
|
|
431
|
+
```bash
|
|
432
|
+
git status
|
|
433
|
+
git remote -v
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
Sign in to npm:
|
|
437
|
+
|
|
438
|
+
```bash
|
|
439
|
+
npm login
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
Inspect the package contents without publishing:
|
|
443
|
+
|
|
444
|
+
```bash
|
|
445
|
+
npm pack --dry-run
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### Patch Release
|
|
449
|
+
|
|
450
|
+
Use a patch release for backward-compatible bug fixes:
|
|
451
|
+
|
|
452
|
+
```bash
|
|
453
|
+
npm run release:patch
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
Example:
|
|
457
|
+
|
|
458
|
+
```text
|
|
459
|
+
0.4.0 → 0.4.1
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### Minor Release
|
|
463
|
+
|
|
464
|
+
Use a minor release for backward-compatible features:
|
|
465
|
+
|
|
466
|
+
```bash
|
|
467
|
+
npm run release:minor
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
Example:
|
|
471
|
+
|
|
472
|
+
```text
|
|
473
|
+
0.4.0 → 0.5.0
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### Major Release
|
|
477
|
+
|
|
478
|
+
Use a major release for breaking changes:
|
|
479
|
+
|
|
480
|
+
```bash
|
|
481
|
+
npm run release:major
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
Example:
|
|
485
|
+
|
|
486
|
+
```text
|
|
487
|
+
0.4.0 → 1.0.0
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
The release scripts run the configured checks, update the package version, create a Git commit and tag, and push the commit and tags.
|
|
491
|
+
|
|
492
|
+
Publish the public scoped package:
|
|
493
|
+
|
|
494
|
+
```bash
|
|
495
|
+
npm publish --access public
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
## Initial GitHub Setup
|
|
499
|
+
|
|
500
|
+
Initialize the repository and push it to GitHub:
|
|
501
|
+
|
|
502
|
+
```bash
|
|
503
|
+
git init
|
|
504
|
+
git branch -M main
|
|
505
|
+
git remote add origin https://github.com/PowerDuckie/-powerduck-openapi-codegen.git
|
|
506
|
+
git add .
|
|
507
|
+
git commit -m "feat: initialize OpenAPI code generator"
|
|
508
|
+
git push -u origin main
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
If `origin` already exists, update it:
|
|
512
|
+
|
|
513
|
+
```bash
|
|
514
|
+
git remote set-url origin https://github.com/PowerDuckie/-powerduck-openapi-codegen.git
|
|
515
|
+
git push -u origin main
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
## Project Structure
|
|
519
|
+
|
|
520
|
+
A typical project layout is:
|
|
521
|
+
|
|
522
|
+
```text
|
|
523
|
+
.
|
|
524
|
+
├── demo/
|
|
525
|
+
├── src/
|
|
526
|
+
│ ├── core/
|
|
527
|
+
│ ├── emitters/
|
|
528
|
+
│ ├── generators/
|
|
529
|
+
│ ├── common.ts
|
|
530
|
+
│ ├── index.ts
|
|
531
|
+
│ └── types.ts
|
|
532
|
+
├── tests/
|
|
533
|
+
│ ├── fixtures/
|
|
534
|
+
│ ├── fuzz.test.ts
|
|
535
|
+
│ └── snapshot.test.ts
|
|
536
|
+
├── package.json
|
|
537
|
+
├── README.md
|
|
538
|
+
└── tsconfig.json
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
## Security
|
|
542
|
+
|
|
543
|
+
Generated source code can contain request URLs, headers, parameter values, and credentials supplied to the generator.
|
|
544
|
+
|
|
545
|
+
Avoid:
|
|
546
|
+
|
|
547
|
+
- Committing generated examples that contain real credentials
|
|
548
|
+
- Logging secrets in CI output
|
|
549
|
+
- Running unreviewed generated commands in production
|
|
550
|
+
- Passing untrusted file paths directly to generated upload code
|
|
551
|
+
|
|
552
|
+
Report security issues privately to the repository owner instead of opening a public issue.
|
|
553
|
+
|
|
554
|
+
## Contributing
|
|
555
|
+
|
|
556
|
+
Contributions are welcome.
|
|
557
|
+
|
|
558
|
+
1. Fork the repository.
|
|
559
|
+
2. Create a feature branch.
|
|
560
|
+
3. Add or update tests.
|
|
561
|
+
4. Run the full validation suite.
|
|
562
|
+
5. Commit the changes.
|
|
563
|
+
6. Open a pull request.
|
|
564
|
+
|
|
565
|
+
```bash
|
|
566
|
+
git checkout -b feature/my-change
|
|
567
|
+
npm install
|
|
568
|
+
npm run check
|
|
569
|
+
git add .
|
|
570
|
+
git commit -m "feat: describe the change"
|
|
571
|
+
git push -u origin feature/my-change
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
When changing generated output, update and review the affected snapshots:
|
|
575
|
+
|
|
576
|
+
```bash
|
|
577
|
+
npx vitest run -u
|
|
578
|
+
git diff
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
## License
|
|
582
|
+
|
|
583
|
+
[MIT](LICENSE)
|