Skip to content

mirror directory structure if output is a directory #1345

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/nice-sheep-juggle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"openapi-typescript": patch
---

Mirror directory structure of input files if output is a directory to prevent overwriting the same file again and again.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
.astro
dist
node_modules

packages/openapi-typescript/test/fixtures/cli-outputs/out
21 changes: 11 additions & 10 deletions packages/openapi-typescript/bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,13 @@ async function generateSchema(pathToSpec) {
let outputFilePath = new URL(flags.output, CWD); // note: may be directory
const isDir = fs.existsSync(outputFilePath) && fs.lstatSync(outputFilePath).isDirectory();
if (isDir) {
if (typeof flags.output === 'string' && !flags.output.endsWith('/')) {
outputFilePath = new URL(`${flags.output}/`, CWD)
if (typeof flags.output === "string" && !flags.output.endsWith("/")) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like we’re accidentally skipping this from linting/Prettier. Thanks for fixing this file.

outputFilePath = new URL(`${flags.output}/`, CWD);
}
const filename = path.basename(pathToSpec).replace(EXT_RE, ".ts");
const filename = pathToSpec.replace(EXT_RE, ".ts");
const originalOutputFilePath = outputFilePath;
outputFilePath = new URL(filename, originalOutputFilePath);
if (outputFilePath.protocol !== 'file:') {
if (outputFilePath.protocol !== "file:") {
outputFilePath = new URL(outputFilePath.host.replace(EXT_RE, ".ts"), originalOutputFilePath);
}
}
Expand Down Expand Up @@ -174,6 +174,8 @@ async function main() {
// handle local schema(s)
const inputSpecPaths = await glob(pathToSpec);
const isGlob = inputSpecPaths.length > 1;
const isDirUrl = outputDir.pathname === outputFile.pathname;
const isFile = fs.existsSync(outputDir) && fs.lstatSync(outputDir).isFile();

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

// error: tried to glob output to single file
if (isGlob && output === OUTPUT_FILE && fs.existsSync(outputDir) && fs.lstatSync(outputDir).isFile()) {
if (isGlob && output === OUTPUT_FILE && (isFile || !isDirUrl)) {
error(`Expected directory for --output if using glob patterns. Received "${flags.output}".`);
process.exit(1);
}
Expand All @@ -191,15 +193,14 @@ async function main() {
await Promise.all(
inputSpecPaths.map(async (specPath) => {
if (flags.output !== "." && output === OUTPUT_FILE) {
if (isGlob) {
fs.mkdirSync(outputFile, { recursive: true }); // recursively make parent dirs
}
else {
if (isGlob || isDirUrl) {
fs.mkdirSync(new URL(path.dirname(specPath), outputDir), { recursive: true }); // recursively make parent dirs
} else {
fs.mkdirSync(outputDir, { recursive: true }); // recursively make parent dirs
}
}
await generateSchema(specPath);
})
}),
);
}

Expand Down
64 changes: 64 additions & 0 deletions packages/openapi-typescript/test/cli.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import { execa } from "execa";
import glob from "fast-glob";
import fs from "node:fs";
import path from "node:path/posix"; // prevent issues with `\` on windows
import { URL, fileURLToPath } from "node:url";
import os from "node:os";

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

// fast-glob does not sort results
async function getOutputFiles() {
return (await glob("**", { cwd: outputDir })).sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
}

describe("CLI", () => {
// note: the snapshots in index.test.ts test the Node API; these test the CLI
describe("snapshots", () => {
Expand Down Expand Up @@ -76,4 +85,59 @@ describe("CLI", () => {
expect(stdout).toEqual(expect.stringMatching(/^v[\d.]+(-.*)?$/));
});
});

describe("outputs", () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

beforeEach(() => {
fs.rmSync(new URL(outputDir, root), { recursive: true, force: true });
});

test("single file to file", async () => {
const inputFile = path.join(inputDir, "file-a.yaml");
const outputFile = path.join(outputDir, "file-a.ts");
await execa(cmd, [inputFile, "--output", outputFile], { cwd });
const result = await getOutputFiles();
expect(result).toEqual(["file-a.ts"]);
});

test("single file to directory", async () => {
const inputFile = path.join(inputDir, "file-a.yaml");
await execa(cmd, [inputFile, "--output", outputDir], { cwd });
const result = await getOutputFiles();
expect(result).toEqual(["test/fixtures/cli-outputs/file-a.ts"]);
});

test("single file (glob) to file", async () => {
const inputFile = path.join(inputDir, "*-a.yaml");
const outputFile = path.join(outputDir, "file-a.ts");
await execa(cmd, [inputFile, "--output", outputFile], { cwd });
const result = await getOutputFiles();
expect(result).toEqual(["file-a.ts"]);
});

test("multiple files to file", async () => {
const inputFile = path.join(inputDir, "*.yaml");
const outputFile = path.join(outputDir, "file-a.ts");
await expect(execa(cmd, [inputFile, "--output", outputFile], { cwd })).rejects.toThrow();
});

test("multiple files to directory", async () => {
const inputFile = path.join(inputDir, "*.yaml");
await execa(cmd, [inputFile, "--output", outputDir], { cwd });
const result = await getOutputFiles();
expect(result).toEqual(["test/fixtures/cli-outputs/file-a.ts", "test/fixtures/cli-outputs/file-b.ts"]);
});

test("multiple nested files to directory", async () => {
const inputFile = path.join(inputDir, "**/*.yaml");
await execa(cmd, [inputFile, "--output", outputDir], { cwd });
const result = await getOutputFiles();
expect(result).toEqual([
"test/fixtures/cli-outputs/file-a.ts",
"test/fixtures/cli-outputs/file-b.ts",
"test/fixtures/cli-outputs/nested/deep/file-e.ts",
"test/fixtures/cli-outputs/nested/file-c.ts",
"test/fixtures/cli-outputs/nested/file-d.ts",
]);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
openapi: "3.0"
info:
title: test file a
version: "1.0"
paths:
/endpoint:
get:
description: OK
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
openapi: "3.0"
info:
title: test file b
version: "1.0"
paths:
/endpoint:
get:
description: OK
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
openapi: "3.0"
info:
title: test file e
version: "1.0"
paths:
/endpoint:
get:
description: OK
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
openapi: "3.0"
info:
title: test file c
version: "1.0"
paths:
/endpoint:
get:
description: OK
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
openapi: "3.0"
info:
title: test file d
version: "1.0"
paths:
/endpoint:
get:
description: OK