Skip to content

Commit 43f7d5f

Browse files
committed
feat(cli): add ability to read/write stdin/stdout
1 parent 83ee6c2 commit 43f7d5f

File tree

4 files changed

+81
-16
lines changed

4 files changed

+81
-16
lines changed

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,31 @@ operations:
309309
404: { $ref: '#/components/schemas/NotFound' }
310310
```
311311

312+
## Generating the resulting OpenAPI specification
313+
314+
The CLI includes a `yarpc-cli` that can be used to generate the resulting OpenAPI 3.1 specification. This works by transforming your queries and mutations and then merging them with any paths that may have been specified in the input YARPC specification.
315+
316+
```sh
317+
npx @freakyfelt/yarpc-cli --input yarpc.json --output openapi.json
318+
```
319+
320+
### Emitting YAML
321+
322+
By default the CLI will emit JSON, but `--format yaml` can be used to emit YAML instead
323+
324+
```
325+
npx @freakyfelt/yarpc-cli --input yarpc.json --output openapi.yaml --format yaml
326+
```
327+
328+
### Using stdin and stdout
329+
330+
The CLI can also read from `stdin` and write to `stdout` by using `-` instead of a file name
331+
332+
```sh
333+
# read the YARPC spec from stdin and write the OpenAPI spec to stdout
334+
cat yarpc.json | npx @freakyfelt/yarpc-cli --input - --output - | ./build-tool.sh
335+
```
336+
312337
## Customizations
313338

314339
The goal of the builder is to be a lightweight abstraction on top of OpenAPI, injecting in defaults where it doesn't really matter for services vending an SDK. That being said, the builder does allow you to provide any OpenAPI 3.1 operation definitions you want.

packages/cli/examples/widgets.oas.json

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,10 @@
125125
"$ref": "#/components/schemas/WidgetStatus"
126126
}
127127
},
128-
"required": ["name", "status"]
128+
"required": [
129+
"name",
130+
"status"
131+
]
129132
}
130133
}
131134
}
@@ -168,7 +171,9 @@
168171
"$ref": "#/components/schemas/WidgetID"
169172
}
170173
},
171-
"required": ["id"]
174+
"required": [
175+
"id"
176+
]
172177
}
173178
}
174179
}
@@ -274,7 +279,10 @@
274279
"type": "object"
275280
}
276281
},
277-
"required": ["code", "message"]
282+
"required": [
283+
"code",
284+
"message"
285+
]
278286
},
279287
"WidgetID": {
280288
"type": "string",
@@ -283,7 +291,10 @@
283291
"WidgetStatus": {
284292
"description": "The status of a widget",
285293
"type": "string",
286-
"enum": ["active", "inactive"]
294+
"enum": [
295+
"active",
296+
"inactive"
297+
]
287298
},
288299
"Widget": {
289300
"type": "object",
@@ -307,7 +318,13 @@
307318
"description": "The date and time the widget was last updated"
308319
}
309320
},
310-
"required": ["id", "name", "status", "createdAt", "updatedAt"]
321+
"required": [
322+
"id",
323+
"name",
324+
"status",
325+
"createdAt",
326+
"updatedAt"
327+
]
311328
},
312329
"GetWidgetInput": {
313330
"type": "object",
@@ -316,7 +333,9 @@
316333
"$ref": "#/components/schemas/WidgetID"
317334
}
318335
},
319-
"required": ["id"]
336+
"required": [
337+
"id"
338+
]
320339
},
321340
"ListWidgetsInput": {
322341
"type": "object",
@@ -355,7 +374,9 @@
355374
}
356375
}
357376
},
358-
"required": ["items"]
377+
"required": [
378+
"items"
379+
]
359380
},
360381
"CreateWidgetInput": {
361382
"type": "object",
@@ -368,7 +389,10 @@
368389
"$ref": "#/components/schemas/WidgetStatus"
369390
}
370391
},
371-
"required": ["name", "status"]
392+
"required": [
393+
"name",
394+
"status"
395+
]
372396
},
373397
"DeactivateWidgetInput": {
374398
"type": "object",
@@ -377,7 +401,9 @@
377401
"$ref": "#/components/schemas/WidgetID"
378402
}
379403
},
380-
"required": ["id"]
404+
"required": [
405+
"id"
406+
]
381407
}
382408
}
383409
}

packages/cli/examples/widgets.oas.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,3 +263,4 @@ components:
263263
$ref: "#/components/schemas/WidgetID"
264264
required:
265265
- id
266+

packages/cli/src/cli.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { readFile, writeFile } from "node:fs/promises";
1+
import { readFileSync } from "node:fs";
2+
import { writeFile } from "node:fs/promises";
23
import { parseArgs } from "node:util";
34
import pino from "pino";
45
import YAML from "yaml";
@@ -14,8 +15,8 @@ function showHelp() {
1415
Usage: yarpc-cli [options]
1516
1617
Options:
17-
-i, --input [string] Input RPC file path
18-
-o, --output [string] Output OpenAPI file path (default: openapi.json)
18+
-i, --input [string] Input RPC file path or "-" for stdin
19+
-o, --output [string] Output OpenAPI file path or "-" for stdout (default: openapi.json)
1920
-f, --format [yaml|json] Output format (default: json)
2021
2122
-h, --help Show help and exit
@@ -34,20 +35,29 @@ type EmitOpenAPIParams = {
3435
async function emitOpenAPI(params: EmitOpenAPIParams) {
3536
const { input, output, format, logger } = params;
3637

37-
const source = YAML.parse(await readFile(input, "utf8")) as RPCDocument;
38+
const text =
39+
input === "-"
40+
? readFileSync(process.stdin.fd, "utf8")
41+
: readFileSync(input, "utf8");
42+
const source = YAML.parse(text.toString()) as RPCDocument;
3843

3944
const transformed = await DocumentTransformer.transform(source, { logger });
4045
let contents;
4146

4247
if (format === "yaml") {
43-
contents = YAML.stringify(transformed, { aliasDuplicateObjects: false });
48+
contents =
49+
YAML.stringify(transformed, { aliasDuplicateObjects: false }) + "\n";
4450
} else if (format === "json") {
45-
contents = JSON.stringify(transformed, null, 2);
51+
contents = JSON.stringify(transformed, null, 2) + "\n";
4652
} else {
4753
throw new Error(`Unsupported format: ${String(format)}`);
4854
}
4955

50-
await writeFile(output, contents, "utf8");
56+
if (output === "-") {
57+
process.stdout.write(contents);
58+
} else {
59+
await writeFile(output, contents, "utf8");
60+
}
5161
}
5262

5363
export async function main() {
@@ -66,6 +76,9 @@ export async function main() {
6676
level: args.values.verbose ? "debug" : "info",
6777
transport: {
6878
target: "pino-pretty",
79+
options: {
80+
destination: process.stderr.fd,
81+
},
6982
},
7083
});
7184

0 commit comments

Comments
 (0)