Skip to content

Commit 866409c

Browse files
adds support for glob-style input spec paths and directory output paths (#616)
* adds support for glob-style input spec paths and directory output paths * rebased and updated expected schemas with main to fix breaking tests
1 parent 4466097 commit 866409c

File tree

7 files changed

+3273
-31
lines changed

7 files changed

+3273
-31
lines changed

bin/cli.js

Lines changed: 72 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const fs = require("fs");
44
const { bold, green, red } = require("kleur");
55
const path = require("path");
66
const meow = require("meow");
7+
const glob = require("tiny-glob");
78
const { default: openapiTS } = require("../dist/cjs/index.js");
89
const { loadSpec } = require("./loaders");
910

@@ -52,36 +53,31 @@ Options
5253
}
5354
);
5455

56+
const OUTPUT_FILE = "FILE";
57+
const OUTPUT_STDOUT = "STDOUT";
58+
5559
const timeStart = process.hrtime();
5660

57-
async function main() {
58-
let output = "FILE"; // FILE or STDOUT
59-
const pathToSpec = cli.input[0];
61+
function errorAndExit(errorMessage) {
62+
process.exitCode = 1; // needed for async functions
63+
throw new Error(red(errorMessage));
64+
}
6065

61-
// 0. setup
62-
if (!cli.flags.output) {
63-
output = "STDOUT"; // if --output not specified, fall back to stdout
64-
}
65-
if (output === "FILE") {
66-
console.info(bold(`✨ openapi-typescript ${require("../package.json").version}`)); // only log if we’re NOT writing to stdout
67-
}
68-
if (cli.flags.rawSchema && !cli.flags.version) {
69-
throw new Error(`--raw-schema requires --version flag`);
70-
}
66+
async function generateSchema(pathToSpec) {
67+
const output = cli.flags.output ? OUTPUT_FILE : OUTPUT_STDOUT; // FILE or STDOUT
7168

72-
// 1. input
69+
// load spec
7370
let spec = undefined;
7471
try {
7572
spec = await loadSpec(pathToSpec, {
7673
auth: cli.flags.auth,
77-
log: output !== "STDOUT",
74+
log: output !== OUTPUT_STDOUT,
7875
});
7976
} catch (err) {
80-
process.exitCode = 1; // needed for async functions
81-
throw new Error(red(`❌ ${err}`));
77+
errorAndExit(`❌ ${err}`);
8278
}
8379

84-
// 2. generate schema (the main part!)
80+
// generate schema
8581
const result = openapiTS(spec, {
8682
auth: cli.flags.auth,
8783
additionalProperties: cli.flags.additionalProperties,
@@ -91,11 +87,54 @@ async function main() {
9187
version: cli.flags.version,
9288
});
9389

94-
// 3. output
95-
if (output === "FILE") {
96-
// output option 1: file
97-
const outputFile = path.resolve(process.cwd(), cli.flags.output);
90+
// output
91+
if (output === OUTPUT_FILE) {
92+
let outputFile = path.resolve(process.cwd(), cli.flags.output);
93+
94+
// decide filename if outputFile is a directory
95+
if (fs.existsSync(outputFile) && fs.lstatSync(outputFile).isDirectory()) {
96+
const basename = path.basename(pathToSpec).split(".").slice(0, -1).join(".") + ".ts";
97+
outputFile = path.resolve(outputFile, basename);
98+
}
99+
100+
fs.writeFileSync(outputFile, result, "utf8");
101+
102+
const timeEnd = process.hrtime(timeStart);
103+
const time = timeEnd[0] + Math.round(timeEnd[1] / 1e6);
104+
console.log(green(`🚀 ${pathToSpec} -> ${bold(outputFile)} [${time}ms]`));
105+
} else {
106+
process.stdout.write(result);
107+
}
108+
109+
return result;
110+
}
111+
112+
async function main() {
113+
const output = cli.flags.output ? OUTPUT_FILE : OUTPUT_STDOUT; // FILE or STDOUT
114+
const pathToSpec = cli.input[0];
115+
const inputSpecPaths = await glob(pathToSpec, { filesOnly: true });
116+
117+
if (output === OUTPUT_FILE) {
118+
console.info(bold(`✨ openapi-typescript ${require("../package.json").version}`)); // only log if we’re NOT writing to stdout
119+
}
120+
121+
if (cli.flags.rawSchema && !cli.flags.version) {
122+
throw new Error(`--raw-schema requires --version flag`);
123+
}
124+
125+
if (/^https?:\/\//.test(pathToSpec)) {
126+
// handle remote resource input and exit
127+
return await generateSchema(pathToSpec);
128+
}
129+
130+
// no matches for glob
131+
if (inputSpecPaths.length === 0) {
132+
errorAndExit(
133+
`❌ Could not find any spec files matching the provided input path glob. Please check that the path is correct.`
134+
);
135+
}
98136

137+
if (output === OUTPUT_FILE) {
99138
// recursively create parent directories if they don’t exist
100139
const parentDirs = cli.flags.output.split(path.sep);
101140
for (var i = 1; i < parentDirs.length; i++) {
@@ -104,15 +143,19 @@ async function main() {
104143
fs.mkdirSync(dir);
105144
}
106145
}
146+
}
107147

108-
fs.writeFileSync(outputFile, result, "utf8");
148+
// if there are multiple specs, ensure that output is a directory
149+
if (inputSpecPaths.length > 1 && output === OUTPUT_FILE && !fs.lstatSync(cli.flags.output).isDirectory()) {
150+
errorAndExit(
151+
`❌ When specifying a glob matching multiple input specs, you must specify a directory for generated type definitions.`
152+
);
153+
}
109154

110-
const timeEnd = process.hrtime(timeStart);
111-
const time = timeEnd[0] + Math.round(timeEnd[1] / 1e6);
112-
console.log(green(`🚀 ${pathToSpec} -> ${bold(cli.flags.output)} [${time}ms]`));
113-
} else {
114-
// output option 2: stdout
115-
process.stdout.write(result);
155+
let result = "";
156+
for (const specPath of inputSpecPaths) {
157+
// append result returned for each spec
158+
result += await generateSchema(specPath);
116159
}
117160

118161
return result;

package-lock.json

Lines changed: 40 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@
5959
"kleur": "^4.1.4",
6060
"meow": "^9.0.0",
6161
"mime": "^2.5.2",
62-
"prettier": "^2.3.0"
62+
"prettier": "^2.3.0",
63+
"tiny-glob": "^0.2.9"
6364
},
6465
"devDependencies": {
6566
"@types/jest": "^26.0.14",

tests/bin/cli.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,14 @@ describe("cli", () => {
3131
const result = execSync(`../../bin/cli.js specs/petstore.yaml`, { cwd: __dirname });
3232
expect(result.toString("utf8")).toBe(expected);
3333
});
34+
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");
38+
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);
43+
});
3444
});

0 commit comments

Comments
 (0)