Skip to content

Commit 6f078c1

Browse files
authored
mirror directory structure if output is a directory (#1345)
* mirror directory structure if output is a directory * add a changeset * move mkdir to appropriate place, create directory structure if we are dealing with multiple files or output directory and file paths match (which indicates it's a directory) * remove leftover * fix weird output paths * add glob tests * fix remaining test case * ignore test output directory * run prettier * make conditions more readable
1 parent 33b87df commit 6f078c1

File tree

9 files changed

+122
-10
lines changed

9 files changed

+122
-10
lines changed

.changeset/nice-sheep-juggle.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"openapi-typescript": patch
3+
---
4+
5+
Mirror directory structure of input files if output is a directory to prevent overwriting the same file again and again.

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@
22
.astro
33
dist
44
node_modules
5+
6+
packages/openapi-typescript/test/fixtures/cli-outputs/out

packages/openapi-typescript/bin/cli.js

+11-10
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,13 @@ async function generateSchema(pathToSpec) {
114114
let outputFilePath = new URL(flags.output, CWD); // note: may be directory
115115
const isDir = fs.existsSync(outputFilePath) && fs.lstatSync(outputFilePath).isDirectory();
116116
if (isDir) {
117-
if (typeof flags.output === 'string' && !flags.output.endsWith('/')) {
118-
outputFilePath = new URL(`${flags.output}/`, CWD)
117+
if (typeof flags.output === "string" && !flags.output.endsWith("/")) {
118+
outputFilePath = new URL(`${flags.output}/`, CWD);
119119
}
120-
const filename = path.basename(pathToSpec).replace(EXT_RE, ".ts");
120+
const filename = pathToSpec.replace(EXT_RE, ".ts");
121121
const originalOutputFilePath = outputFilePath;
122122
outputFilePath = new URL(filename, originalOutputFilePath);
123-
if (outputFilePath.protocol !== 'file:') {
123+
if (outputFilePath.protocol !== "file:") {
124124
outputFilePath = new URL(outputFilePath.host.replace(EXT_RE, ".ts"), originalOutputFilePath);
125125
}
126126
}
@@ -174,6 +174,8 @@ async function main() {
174174
// handle local schema(s)
175175
const inputSpecPaths = await glob(pathToSpec);
176176
const isGlob = inputSpecPaths.length > 1;
177+
const isDirUrl = outputDir.pathname === outputFile.pathname;
178+
const isFile = fs.existsSync(outputDir) && fs.lstatSync(outputDir).isFile();
177179

178180
// error: no matches for glob
179181
if (inputSpecPaths.length === 0) {
@@ -182,7 +184,7 @@ async function main() {
182184
}
183185

184186
// error: tried to glob output to single file
185-
if (isGlob && output === OUTPUT_FILE && fs.existsSync(outputDir) && fs.lstatSync(outputDir).isFile()) {
187+
if (isGlob && output === OUTPUT_FILE && (isFile || !isDirUrl)) {
186188
error(`Expected directory for --output if using glob patterns. Received "${flags.output}".`);
187189
process.exit(1);
188190
}
@@ -191,15 +193,14 @@ async function main() {
191193
await Promise.all(
192194
inputSpecPaths.map(async (specPath) => {
193195
if (flags.output !== "." && output === OUTPUT_FILE) {
194-
if (isGlob) {
195-
fs.mkdirSync(outputFile, { recursive: true }); // recursively make parent dirs
196-
}
197-
else {
196+
if (isGlob || isDirUrl) {
197+
fs.mkdirSync(new URL(path.dirname(specPath), outputDir), { recursive: true }); // recursively make parent dirs
198+
} else {
198199
fs.mkdirSync(outputDir, { recursive: true }); // recursively make parent dirs
199200
}
200201
}
201202
await generateSchema(specPath);
202-
})
203+
}),
203204
);
204205
}
205206

packages/openapi-typescript/test/cli.test.ts

+64
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
import { execa } from "execa";
2+
import glob from "fast-glob";
23
import fs from "node:fs";
4+
import path from "node:path/posix"; // prevent issues with `\` on windows
35
import { URL, fileURLToPath } from "node:url";
46
import os from "node:os";
57

68
const root = new URL("../", import.meta.url);
79
const cwd = os.platform() === "win32" ? fileURLToPath(root) : root; // execa bug: fileURLToPath required on Windows
810
const cmd = "./bin/cli.js";
11+
const inputDir = "test/fixtures/cli-outputs/";
12+
const outputDir = path.join(inputDir, "out/");
913
const TIMEOUT = 90000;
1014

15+
// fast-glob does not sort results
16+
async function getOutputFiles() {
17+
return (await glob("**", { cwd: outputDir })).sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
18+
}
19+
1120
describe("CLI", () => {
1221
// note: the snapshots in index.test.ts test the Node API; these test the CLI
1322
describe("snapshots", () => {
@@ -76,4 +85,59 @@ describe("CLI", () => {
7685
expect(stdout).toEqual(expect.stringMatching(/^v[\d.]+(-.*)?$/));
7786
});
7887
});
88+
89+
describe("outputs", () => {
90+
beforeEach(() => {
91+
fs.rmSync(new URL(outputDir, root), { recursive: true, force: true });
92+
});
93+
94+
test("single file to file", async () => {
95+
const inputFile = path.join(inputDir, "file-a.yaml");
96+
const outputFile = path.join(outputDir, "file-a.ts");
97+
await execa(cmd, [inputFile, "--output", outputFile], { cwd });
98+
const result = await getOutputFiles();
99+
expect(result).toEqual(["file-a.ts"]);
100+
});
101+
102+
test("single file to directory", async () => {
103+
const inputFile = path.join(inputDir, "file-a.yaml");
104+
await execa(cmd, [inputFile, "--output", outputDir], { cwd });
105+
const result = await getOutputFiles();
106+
expect(result).toEqual(["test/fixtures/cli-outputs/file-a.ts"]);
107+
});
108+
109+
test("single file (glob) to file", async () => {
110+
const inputFile = path.join(inputDir, "*-a.yaml");
111+
const outputFile = path.join(outputDir, "file-a.ts");
112+
await execa(cmd, [inputFile, "--output", outputFile], { cwd });
113+
const result = await getOutputFiles();
114+
expect(result).toEqual(["file-a.ts"]);
115+
});
116+
117+
test("multiple files to file", async () => {
118+
const inputFile = path.join(inputDir, "*.yaml");
119+
const outputFile = path.join(outputDir, "file-a.ts");
120+
await expect(execa(cmd, [inputFile, "--output", outputFile], { cwd })).rejects.toThrow();
121+
});
122+
123+
test("multiple files to directory", async () => {
124+
const inputFile = path.join(inputDir, "*.yaml");
125+
await execa(cmd, [inputFile, "--output", outputDir], { cwd });
126+
const result = await getOutputFiles();
127+
expect(result).toEqual(["test/fixtures/cli-outputs/file-a.ts", "test/fixtures/cli-outputs/file-b.ts"]);
128+
});
129+
130+
test("multiple nested files to directory", async () => {
131+
const inputFile = path.join(inputDir, "**/*.yaml");
132+
await execa(cmd, [inputFile, "--output", outputDir], { cwd });
133+
const result = await getOutputFiles();
134+
expect(result).toEqual([
135+
"test/fixtures/cli-outputs/file-a.ts",
136+
"test/fixtures/cli-outputs/file-b.ts",
137+
"test/fixtures/cli-outputs/nested/deep/file-e.ts",
138+
"test/fixtures/cli-outputs/nested/file-c.ts",
139+
"test/fixtures/cli-outputs/nested/file-d.ts",
140+
]);
141+
});
142+
});
79143
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
openapi: "3.0"
2+
info:
3+
title: test file a
4+
version: "1.0"
5+
paths:
6+
/endpoint:
7+
get:
8+
description: OK
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
openapi: "3.0"
2+
info:
3+
title: test file b
4+
version: "1.0"
5+
paths:
6+
/endpoint:
7+
get:
8+
description: OK
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
openapi: "3.0"
2+
info:
3+
title: test file e
4+
version: "1.0"
5+
paths:
6+
/endpoint:
7+
get:
8+
description: OK
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
openapi: "3.0"
2+
info:
3+
title: test file c
4+
version: "1.0"
5+
paths:
6+
/endpoint:
7+
get:
8+
description: OK
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
openapi: "3.0"
2+
info:
3+
title: test file d
4+
version: "1.0"
5+
paths:
6+
/endpoint:
7+
get:
8+
description: OK

0 commit comments

Comments
 (0)