Skip to content

Commit 56d4ddc

Browse files
authored
Add docs for glob feature to README (#632)
* Fix directory creating issue with glob; reorder error cases a bit better * Add glob feature to README * Improve folder gen
1 parent 5ba7056 commit 56d4ddc

File tree

6 files changed

+119
-129
lines changed

6 files changed

+119
-129
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
.cache
22
.DS_Store
3-
**/generated/*.ts
3+
**/generated/
44
coverage
55
dist
66
node_modules

README.md

Lines changed: 17 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,34 @@ View examples:
2828
```bash
2929
npx openapi-typescript schema.yaml --output schema.ts
3030

31-
# 🤞 Loading spec from tests/v2/specs/stripe.yaml…
31+
# 🔭 Loading spec from schema.yaml…
3232
# 🚀 schema.yaml -> schema.ts [250ms]
33+
34+
npx openapi-typescript "specs/**/*.yaml" --output schemas/
35+
# 🔭 Loading spec from specs/one.yaml…
36+
# 🔭 Loading spec from specs/two.yaml…
37+
# 🔭 Loading spec from specs/three.yaml…
38+
# 🚀 specs/one.yaml -> schemas/specs/one.ts [250ms]
39+
# 🚀 specs/two.yaml -> schemas/specs/two.ts [250ms]
40+
# 🚀 specs/three.yaml -> schemas/specs/three.ts [250ms]
3341
```
3442

43+
_Note: if generating a single schema, `--output` must be a file (preferably `*.ts`). If using globs, `--output` must be a directory._
44+
45+
_Thanks to [@sharmarajdaksh](https://github.com/sharmarajdaksh) for the glob feature!_
46+
3547
#### ☁️ Reading specs from remote resource
3648

3749
```bash
3850
npx openapi-typescript https://petstore.swagger.io/v2/swagger.json --output petstore.ts
3951

40-
# 🤞 Loading spec from https://petstore.swagger.io/v2/swagger.json…
52+
# 🔭 Loading spec from https://petstore.swagger.io/v2/swagger.json…
4153
# 🚀 https://petstore.swagger.io/v2/swagger.json -> petstore.ts [650ms]
4254
```
4355

44-
_Thanks to @psmyrdek for the remote spec feature!_
56+
_Note: for obvious reasons, globbing doesn’t work for remote schemas_
57+
58+
_Thanks to [@psmyrdek](https://github.com/psmyrdek) for the remote spec feature!_
4559

4660
#### Using in TypeScript
4761

@@ -73,37 +87,6 @@ _Thanks to @gr2m for the operations feature!_
7387
npx openapi-typescript schema.yaml
7488
```
7589

76-
#### Generating multiple schemas
77-
78-
In your `package.json`, for each schema you’d like to transform add one `generate:specs:[name]` npm-script. Then combine
79-
them all into one `generate:specs` script, like so:
80-
81-
```json
82-
"scripts": {
83-
"generate:specs": "npm run generate:specs:one && npm run generate:specs:two && npm run generate:specs:three",
84-
"generate:specs:one": "npx openapi-typescript one.yaml -o one.ts",
85-
"generate:specs:two": "npx openapi-typescript two.yaml -o two.ts",
86-
"generate:specs:three": "npx openapi-typescript three.yaml -o three.ts"
87-
}
88-
```
89-
90-
If you use [npm-run-all][npm-run-all], you can shorten this:
91-
92-
```json
93-
"scripts": {
94-
"generate:specs": "run-p generate:specs:*",
95-
```
96-
97-
You can even specify unique options per-spec, if needed. To generate them all together, run:
98-
99-
```bash
100-
npm run generate:specs
101-
```
102-
103-
Rinse and repeat for more specs.
104-
105-
For anything more complicated, or for generating specs dynamically, you can also use the [Node API](#node).
106-
10790
#### CLI Options
10891

10992
| Option | Alias | Default | Description |

bin/cli.js

Lines changed: 32 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -94,15 +94,14 @@ async function generateSchema(pathToSpec) {
9494

9595
// output
9696
if (output === OUTPUT_FILE) {
97-
let outputFile = path.resolve(process.cwd(), cli.flags.output);
98-
99-
// decide filename if outputFile is a directory
100-
if (fs.existsSync(outputFile) && fs.lstatSync(outputFile).isDirectory()) {
101-
const basename = path.basename(pathToSpec).split(".").slice(0, -1).join(".") + ".ts";
102-
outputFile = path.resolve(outputFile, basename);
97+
let outputFile = path.resolve(process.cwd(), cli.flags.output); // note: may be directory
98+
const isDir = fs.existsSync(outputFile) && fs.lstatSync(outputFile).isDirectory();
99+
if (isDir) {
100+
const filename = pathToSpec.replace(new RegExp(`${path.extname(pathToSpec)}$`), ".ts");
101+
outputFile = path.join(outputFile, filename);
103102
}
104103

105-
fs.writeFileSync(outputFile, result, "utf8");
104+
await fs.promises.writeFile(outputFile, result, "utf8");
106105

107106
const timeEnd = process.hrtime(timeStart);
108107
const time = timeEnd[0] + Math.round(timeEnd[1] / 1e6);
@@ -117,53 +116,49 @@ async function generateSchema(pathToSpec) {
117116
async function main() {
118117
const output = cli.flags.output ? OUTPUT_FILE : OUTPUT_STDOUT; // FILE or STDOUT
119118
const pathToSpec = cli.input[0];
120-
const inputSpecPaths = await glob(pathToSpec, { filesOnly: true });
121119

122120
if (output === OUTPUT_FILE) {
123121
console.info(bold(`✨ openapi-typescript ${require("../package.json").version}`)); // only log if we’re NOT writing to stdout
124122
}
125123

124+
// error: --raw-schema
126125
if (cli.flags.rawSchema && !cli.flags.version) {
127126
throw new Error(`--raw-schema requires --version flag`);
128127
}
129128

129+
// handle remote schema, exit
130130
if (/^https?:\/\//.test(pathToSpec)) {
131-
// handle remote resource input and exit
132-
return await generateSchema(pathToSpec);
131+
if (output !== "." && output === OUTPUT_FILE)
132+
await fs.promises.mkdir(path.dirname(cli.flags.output), { recursive: true });
133+
await generateSchema(pathToSpec);
134+
return;
133135
}
134136

135-
// no matches for glob
136-
if (inputSpecPaths.length === 0) {
137-
errorAndExit(
138-
`❌ Could not find any spec files matching the provided input path glob. Please check that the path is correct.`
139-
);
140-
}
141-
142-
if (output === OUTPUT_FILE) {
143-
// recursively create parent directories if they don’t exist
144-
const parentDirs = cli.flags.output.split(path.sep);
145-
for (var i = 1; i < parentDirs.length; i++) {
146-
const dir = path.resolve(process.cwd(), ...parentDirs.slice(0, i));
147-
if (!fs.existsSync(dir)) {
148-
fs.mkdirSync(dir);
149-
}
150-
}
151-
}
137+
// handle local schema(s)
138+
const inputSpecPaths = await glob(pathToSpec, { filesOnly: true });
139+
const isGlob = inputSpecPaths.length > 1;
152140

153-
// if there are multiple specs, ensure that output is a directory
154-
if (inputSpecPaths.length > 1 && output === OUTPUT_FILE && !fs.lstatSync(cli.flags.output).isDirectory()) {
155-
errorAndExit(
156-
`❌ When specifying a glob matching multiple input specs, you must specify a directory for generated type definitions.`
157-
);
141+
// error: no matches for glob
142+
if (inputSpecPaths.length === 0) {
143+
errorAndExit(`❌ Could not find any specs matching "${pathToSpec}". Please check that the path is correct.`);
158144
}
159145

160-
let result = "";
161-
for (const specPath of inputSpecPaths) {
162-
// append result returned for each spec
163-
result += await generateSchema(specPath);
146+
// error: tried to glob output to single file
147+
if (isGlob && output === OUTPUT_FILE && fs.existsSync(cli.flags.output) && fs.lstatSync(cli.flags.output).isFile()) {
148+
errorAndExit(`❌ Expected directory for --output if using glob patterns. Received "${cli.flags.output}".`);
164149
}
165150

166-
return result;
151+
// generate schema(s)
152+
await Promise.all(
153+
inputSpecPaths.map(async (specPath) => {
154+
if (cli.flags.output !== "." && output === OUTPUT_FILE) {
155+
let outputDir = path.join(process.cwd(), cli.flags.output);
156+
if (!isGlob) outputDir = path.dirname(outputDir); // use output dir for glob; use parent dir for single files
157+
await fs.promises.mkdir(path.join(outputDir, path.dirname(specPath)), { recursive: true }); // recursively make parent dirs
158+
}
159+
await generateSchema(specPath);
160+
})
161+
);
167162
}
168163

169164
main();

tests/bin/cli.test.ts

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,39 +6,45 @@ import { execSync } from "child_process";
66
// v3/index.test.ts. So this file is mainly for testing other flags.
77

88
describe("cli", () => {
9-
it("--prettier-config (JSON)", () => {
10-
const expected = fs.readFileSync(path.join(__dirname, "expected", "prettier-json.ts"), "utf8");
9+
it("--prettier-config (JSON)", async () => {
1110
execSync(
1211
`../../bin/cli.js specs/petstore.yaml -o generated/prettier-json.ts --prettier-config fixtures/.prettierrc`,
1312
{ cwd: __dirname }
1413
);
15-
const output = fs.readFileSync(path.join(__dirname, "generated", "prettier-json.ts"), "utf8");
16-
expect(output).toBe(expected);
14+
const [generated, expected] = await Promise.all([
15+
fs.promises.readFile(path.join(__dirname, "generated", "prettier-json.ts"), "utf8"),
16+
fs.promises.readFile(path.join(__dirname, "expected", "prettier-json.ts"), "utf8"),
17+
]);
18+
expect(generated).toBe(expected);
1719
});
1820

19-
it("--prettier-config (.js)", () => {
20-
const expected = fs.readFileSync(path.join(__dirname, "expected", "prettier-js.ts"), "utf8");
21+
it("--prettier-config (.js)", async () => {
2122
execSync(
2223
`../../bin/cli.js specs/petstore.yaml -o generated/prettier-js.ts --prettier-config fixtures/prettier.config.js`,
2324
{ cwd: __dirname }
2425
);
25-
const output = fs.readFileSync(path.join(__dirname, "generated", "prettier-js.ts"), "utf8");
26-
expect(output).toBe(expected);
26+
const [generated, expected] = await Promise.all([
27+
fs.promises.readFile(path.join(__dirname, "generated", "prettier-js.ts"), "utf8"),
28+
fs.promises.readFile(path.join(__dirname, "expected", "prettier-js.ts"), "utf8"),
29+
]);
30+
expect(generated).toBe(expected);
2731
});
2832

29-
it("stdout", () => {
33+
it("stdout", async () => {
3034
const expected = fs.readFileSync(path.join(__dirname, "expected", "stdout.ts"), "utf8");
31-
const result = execSync(`../../bin/cli.js specs/petstore.yaml`, { cwd: __dirname });
32-
expect(result.toString("utf8")).toBe(expected);
35+
const generated = execSync(`../../bin/cli.js specs/petstore.yaml`, { cwd: __dirname });
36+
expect(generated.toString("utf8")).toBe(expected);
3337
});
3438

35-
it("supports glob paths", () => {
36-
const expectedPetstore = fs.readFileSync(path.join(__dirname, "expected", "petstore.ts"), "utf8");
37-
const expectedManifold = fs.readFileSync(path.join(__dirname, "expected", "manifold.ts"), "utf8");
39+
it("supports glob paths", async () => {
3840
execSync(`../../bin/cli.js \"specs/*.yaml\" -o generated/`, { cwd: __dirname }); // Quotes are necessary because shells like zsh treats glob weirdly
39-
const outputPetstore = fs.readFileSync(path.join(__dirname, "generated", "petstore.ts"), "utf8");
40-
const outputManifold = fs.readFileSync(path.join(__dirname, "generated", "manifold.ts"), "utf8");
41-
expect(outputPetstore).toBe(expectedPetstore);
42-
expect(outputManifold).toBe(expectedManifold);
41+
const [generatedPetstore, expectedPetstore, generatedManifold, expectedManifold] = await Promise.all([
42+
fs.promises.readFile(path.join(__dirname, "generated", "specs", "petstore.ts"), "utf8"),
43+
fs.promises.readFile(path.join(__dirname, "expected", "petstore.ts"), "utf8"),
44+
fs.promises.readFile(path.join(__dirname, "generated", "specs", "manifold.ts"), "utf8"),
45+
fs.promises.readFile(path.join(__dirname, "expected", "manifold.ts"), "utf8"),
46+
]);
47+
expect(generatedPetstore).toBe(expectedPetstore);
48+
expect(generatedManifold).toBe(expectedManifold);
4349
});
4450
});

tests/v2/index.test.ts

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
import { execSync } from "child_process";
2-
import { readdirSync, readFileSync } from "fs";
3-
import { join } from "path";
2+
import fs from "fs";
3+
import path from "path";
44

5-
const schemas = readdirSync(join(__dirname, "specs"));
5+
const schemas = fs.readdirSync(path.join(__dirname, "specs"));
66

77
// simple snapshot tests with valid schemas to make sure it can generally parse & generate output
88
describe("cli", () => {
99
schemas.forEach((schema) => {
1010
const output = schema.replace(".yaml", ".ts");
1111

12-
it(`reads ${schema} spec (v2) from file`, () => {
12+
it(`reads ${schema} spec (v2) from file`, async () => {
1313
execSync(`../../bin/cli.js specs/${schema} -o generated/${output} --prettier-config .prettierrc`, {
1414
cwd: __dirname,
1515
});
16-
const expected = readFileSync(join(__dirname, "expected", output), "utf8");
17-
const generated = readFileSync(join(__dirname, "generated", output), "utf8");
16+
const [generated, expected] = await Promise.all([
17+
fs.promises.readFile(path.join(__dirname, "generated", output), "utf8"),
18+
fs.promises.readFile(path.join(__dirname, "expected", output), "utf8"),
19+
]);
1820
expect(generated).toBe(expected);
1921
});
2022

21-
it(`reads ${schema} spec (v2) from file (immutable types)`, () => {
23+
it(`reads ${schema} spec (v2) from file (immutable types)`, async () => {
2224
const output = schema.replace(".yaml", ".immutable.ts");
2325

2426
execSync(
@@ -28,21 +30,23 @@ describe("cli", () => {
2830
}
2931
);
3032

31-
const expected = readFileSync(join(__dirname, "expected", output), "utf8");
32-
const generated = readFileSync(join(__dirname, "generated", output), "utf8");
33+
const [generated, expected] = await Promise.all([
34+
fs.promises.readFile(path.join(__dirname, "generated", output), "utf8"),
35+
fs.promises.readFile(path.join(__dirname, "expected", output), "utf8"),
36+
]);
3337
expect(generated).toBe(expected);
3438
});
3539
});
3640

37-
it("reads spec (v2) from remote resource", () => {
41+
it("reads spec (v2) from remote resource", async () => {
3842
execSync(
3943
"../../bin/cli.js https://raw.githubusercontent.com/drwpow/openapi-typescript/main/tests/v2/specs/manifold.yaml -o generated/http.ts",
40-
{
41-
cwd: __dirname,
42-
}
44+
{ cwd: __dirname }
4345
);
44-
const expected = readFileSync(join(__dirname, "expected", "http.ts"), "utf8");
45-
const generated = readFileSync(join(__dirname, "generated", "http.ts"), "utf8");
46+
const [generated, expected] = await Promise.all([
47+
fs.promises.readFile(path.join(__dirname, "generated", "http.ts"), "utf8"),
48+
fs.promises.readFile(path.join(__dirname, "expected", "http.ts"), "utf8"),
49+
]);
4650
expect(generated).toBe(expected);
4751
});
4852
});

0 commit comments

Comments
 (0)