Skip to content

Commit

Permalink
mirror directory structure if output is a directory (#1345)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
SchabaJo committed Sep 22, 2023
1 parent 33b87df commit 6f078c1
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 10 deletions.
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("/")) {
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", () => {
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

0 comments on commit 6f078c1

Please sign in to comment.