From b6543bec03de4957aaf1d766700b43af1d9d419c Mon Sep 17 00:00:00 2001 From: Josef Schabasser Date: Thu, 14 Sep 2023 09:26:19 +0200 Subject: [PATCH 01/10] mirror directory structure if output is a directory --- packages/openapi-typescript/bin/cli.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/openapi-typescript/bin/cli.js b/packages/openapi-typescript/bin/cli.js index c73b445c3..fa6104bab 100755 --- a/packages/openapi-typescript/bin/cli.js +++ b/packages/openapi-typescript/bin/cli.js @@ -2,7 +2,7 @@ import fs from "node:fs"; import path from "node:path"; -import { URL } from "node:url"; +import { fileURLToPath, URL } from "node:url"; import glob from "fast-glob"; import parser from "yargs-parser"; import openapiTS from "../dist/index.js"; @@ -117,12 +117,13 @@ async function generateSchema(pathToSpec) { 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:') { outputFilePath = new URL(outputFilePath.host.replace(EXT_RE, ".ts"), originalOutputFilePath); } + fs.mkdirSync(path.dirname(fileURLToPath(outputFilePath)), { recursive: true }); // recursively make parent dirs } fs.writeFileSync(outputFilePath, result, "utf8"); From 5e82c8755fa6b8045dddef91d73f54c7caaed0de Mon Sep 17 00:00:00 2001 From: Josef Schabasser Date: Thu, 14 Sep 2023 09:34:47 +0200 Subject: [PATCH 02/10] add a changeset --- .changeset/nice-sheep-juggle.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/nice-sheep-juggle.md diff --git a/.changeset/nice-sheep-juggle.md b/.changeset/nice-sheep-juggle.md new file mode 100644 index 000000000..262615098 --- /dev/null +++ b/.changeset/nice-sheep-juggle.md @@ -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. From 7d4ac352b081e6ae9318c960b2d5be99d71493f4 Mon Sep 17 00:00:00 2001 From: Josef Schabasser Date: Thu, 14 Sep 2023 10:35:29 +0200 Subject: [PATCH 03/10] 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) --- packages/openapi-typescript/bin/cli.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/openapi-typescript/bin/cli.js b/packages/openapi-typescript/bin/cli.js index fa6104bab..1b89e99ec 100755 --- a/packages/openapi-typescript/bin/cli.js +++ b/packages/openapi-typescript/bin/cli.js @@ -123,7 +123,6 @@ async function generateSchema(pathToSpec) { if (outputFilePath.protocol !== 'file:') { outputFilePath = new URL(outputFilePath.host.replace(EXT_RE, ".ts"), originalOutputFilePath); } - fs.mkdirSync(path.dirname(fileURLToPath(outputFilePath)), { recursive: true }); // recursively make parent dirs } fs.writeFileSync(outputFilePath, result, "utf8"); @@ -192,8 +191,8 @@ 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 + if (isGlob || outputDir.pathname === outputFile.pathname) { + fs.mkdirSync(new URL(path.dirname(specPath), outputDir).pathname, { recursive: true }); // recursively make parent dirs } else { fs.mkdirSync(outputDir, { recursive: true }); // recursively make parent dirs From e856c248e6eb90ad163a3577567a3b66d8eeba2b Mon Sep 17 00:00:00 2001 From: Josef Schabasser Date: Thu, 14 Sep 2023 10:36:27 +0200 Subject: [PATCH 04/10] remove leftover --- packages/openapi-typescript/bin/cli.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/openapi-typescript/bin/cli.js b/packages/openapi-typescript/bin/cli.js index 1b89e99ec..f6a2879a8 100755 --- a/packages/openapi-typescript/bin/cli.js +++ b/packages/openapi-typescript/bin/cli.js @@ -2,7 +2,7 @@ import fs from "node:fs"; import path from "node:path"; -import { fileURLToPath, URL } from "node:url"; +import { URL } from "node:url"; import glob from "fast-glob"; import parser from "yargs-parser"; import openapiTS from "../dist/index.js"; From 8a1ae00a80a07266ba2c3d6213f322f8f362d973 Mon Sep 17 00:00:00 2001 From: Josef Schabasser Date: Mon, 18 Sep 2023 11:04:57 +0200 Subject: [PATCH 05/10] fix weird output paths --- packages/openapi-typescript/bin/cli.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/openapi-typescript/bin/cli.js b/packages/openapi-typescript/bin/cli.js index f6a2879a8..8669f75e5 100755 --- a/packages/openapi-typescript/bin/cli.js +++ b/packages/openapi-typescript/bin/cli.js @@ -192,7 +192,7 @@ async function main() { inputSpecPaths.map(async (specPath) => { if (flags.output !== "." && output === OUTPUT_FILE) { if (isGlob || outputDir.pathname === outputFile.pathname) { - fs.mkdirSync(new URL(path.dirname(specPath), outputDir).pathname, { recursive: true }); // recursively make parent dirs + fs.mkdirSync(new URL(path.dirname(specPath), outputDir), { recursive: true }); // recursively make parent dirs } else { fs.mkdirSync(outputDir, { recursive: true }); // recursively make parent dirs From 520897f8a54b5e196887d6a818ec65428011b4f5 Mon Sep 17 00:00:00 2001 From: Josef Schabasser Date: Mon, 18 Sep 2023 13:03:42 +0200 Subject: [PATCH 06/10] add glob tests --- packages/openapi-typescript/test/cli.test.ts | 67 +++++++++++++++++++ .../test/fixtures/cli-outputs/file-a.yaml | 8 +++ .../test/fixtures/cli-outputs/file-b.yaml | 8 +++ .../cli-outputs/nested/deep/file-e.yaml | 8 +++ .../fixtures/cli-outputs/nested/file-c.yaml | 8 +++ .../fixtures/cli-outputs/nested/file-d.yaml | 8 +++ 6 files changed, 107 insertions(+) create mode 100644 packages/openapi-typescript/test/fixtures/cli-outputs/file-a.yaml create mode 100644 packages/openapi-typescript/test/fixtures/cli-outputs/file-b.yaml create mode 100644 packages/openapi-typescript/test/fixtures/cli-outputs/nested/deep/file-e.yaml create mode 100644 packages/openapi-typescript/test/fixtures/cli-outputs/nested/file-c.yaml create mode 100644 packages/openapi-typescript/test/fixtures/cli-outputs/nested/file-d.yaml diff --git a/packages/openapi-typescript/test/cli.test.ts b/packages/openapi-typescript/test/cli.test.ts index 8acf3537c..c99cc714f 100644 --- a/packages/openapi-typescript/test/cli.test.ts +++ b/packages/openapi-typescript/test/cli.test.ts @@ -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", () => { @@ -76,4 +85,62 @@ 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.todo("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", + ]); + }) + }); }); diff --git a/packages/openapi-typescript/test/fixtures/cli-outputs/file-a.yaml b/packages/openapi-typescript/test/fixtures/cli-outputs/file-a.yaml new file mode 100644 index 000000000..7f4eaac81 --- /dev/null +++ b/packages/openapi-typescript/test/fixtures/cli-outputs/file-a.yaml @@ -0,0 +1,8 @@ +openapi: "3.0" +info: + title: test file a + version: "1.0" +paths: + /endpoint: + get: + description: OK diff --git a/packages/openapi-typescript/test/fixtures/cli-outputs/file-b.yaml b/packages/openapi-typescript/test/fixtures/cli-outputs/file-b.yaml new file mode 100644 index 000000000..e41219cf3 --- /dev/null +++ b/packages/openapi-typescript/test/fixtures/cli-outputs/file-b.yaml @@ -0,0 +1,8 @@ +openapi: "3.0" +info: + title: test file b + version: "1.0" +paths: + /endpoint: + get: + description: OK diff --git a/packages/openapi-typescript/test/fixtures/cli-outputs/nested/deep/file-e.yaml b/packages/openapi-typescript/test/fixtures/cli-outputs/nested/deep/file-e.yaml new file mode 100644 index 000000000..fc56849d8 --- /dev/null +++ b/packages/openapi-typescript/test/fixtures/cli-outputs/nested/deep/file-e.yaml @@ -0,0 +1,8 @@ +openapi: "3.0" +info: + title: test file e + version: "1.0" +paths: + /endpoint: + get: + description: OK diff --git a/packages/openapi-typescript/test/fixtures/cli-outputs/nested/file-c.yaml b/packages/openapi-typescript/test/fixtures/cli-outputs/nested/file-c.yaml new file mode 100644 index 000000000..ccf725a95 --- /dev/null +++ b/packages/openapi-typescript/test/fixtures/cli-outputs/nested/file-c.yaml @@ -0,0 +1,8 @@ +openapi: "3.0" +info: + title: test file c + version: "1.0" +paths: + /endpoint: + get: + description: OK diff --git a/packages/openapi-typescript/test/fixtures/cli-outputs/nested/file-d.yaml b/packages/openapi-typescript/test/fixtures/cli-outputs/nested/file-d.yaml new file mode 100644 index 000000000..27079dd0c --- /dev/null +++ b/packages/openapi-typescript/test/fixtures/cli-outputs/nested/file-d.yaml @@ -0,0 +1,8 @@ +openapi: "3.0" +info: + title: test file d + version: "1.0" +paths: + /endpoint: + get: + description: OK From 81aa451341b90227068e929b22b00b1e752666a7 Mon Sep 17 00:00:00 2001 From: Josef Schabasser Date: Mon, 18 Sep 2023 13:10:00 +0200 Subject: [PATCH 07/10] fix remaining test case --- packages/openapi-typescript/bin/cli.js | 5 ++--- packages/openapi-typescript/test/cli.test.ts | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/openapi-typescript/bin/cli.js b/packages/openapi-typescript/bin/cli.js index 8669f75e5..0912b362f 100755 --- a/packages/openapi-typescript/bin/cli.js +++ b/packages/openapi-typescript/bin/cli.js @@ -182,7 +182,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 && ((fs.existsSync(outputDir) && fs.lstatSync(outputDir).isFile()) || outputDir.pathname !== outputFile.pathname)) { error(`Expected directory for --output if using glob patterns. Received "${flags.output}".`); process.exit(1); } @@ -193,8 +193,7 @@ async function main() { if (flags.output !== "." && output === OUTPUT_FILE) { if (isGlob || outputDir.pathname === outputFile.pathname) { fs.mkdirSync(new URL(path.dirname(specPath), outputDir), { recursive: true }); // recursively make parent dirs - } - else { + } else { fs.mkdirSync(outputDir, { recursive: true }); // recursively make parent dirs } } diff --git a/packages/openapi-typescript/test/cli.test.ts b/packages/openapi-typescript/test/cli.test.ts index c99cc714f..f2f9d2b21 100644 --- a/packages/openapi-typescript/test/cli.test.ts +++ b/packages/openapi-typescript/test/cli.test.ts @@ -114,7 +114,7 @@ describe("CLI", () => { expect(result).toEqual([ "file-a.ts" ]); }) - test.todo("multiple files to file", async ()=>{ + 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(); From ec136a8b49f3b626691eab2979cb3c2f461cd0ef Mon Sep 17 00:00:00 2001 From: Josef Schabasser Date: Mon, 18 Sep 2023 13:11:49 +0200 Subject: [PATCH 08/10] ignore test output directory --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 28dd70cdf..c81238d0d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ .astro dist node_modules + +packages/openapi-typescript/test/fixtures/cli-outputs/out From 2fce0fe694f0ac5d24181981edfe2ae78dde187a Mon Sep 17 00:00:00 2001 From: Josef Schabasser Date: Mon, 18 Sep 2023 13:16:48 +0200 Subject: [PATCH 09/10] run prettier --- packages/openapi-typescript/bin/cli.js | 8 ++--- packages/openapi-typescript/test/cli.test.ts | 35 +++++++++----------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/packages/openapi-typescript/bin/cli.js b/packages/openapi-typescript/bin/cli.js index 0912b362f..bb75f0c23 100755 --- a/packages/openapi-typescript/bin/cli.js +++ b/packages/openapi-typescript/bin/cli.js @@ -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 = 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); } } @@ -198,7 +198,7 @@ async function main() { } } await generateSchema(specPath); - }) + }), ); } diff --git a/packages/openapi-typescript/test/cli.test.ts b/packages/openapi-typescript/test/cli.test.ts index f2f9d2b21..88ae34d3d 100644 --- a/packages/openapi-typescript/test/cli.test.ts +++ b/packages/openapi-typescript/test/cli.test.ts @@ -86,51 +86,48 @@ describe("CLI", () => { }); }); - describe("outputs", ()=>{ - beforeEach(()=>{ + describe("outputs", () => { + beforeEach(() => { fs.rmSync(new URL(outputDir, root), { recursive: true, force: true }); }); - test("single file to file", async ()=>{ + 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" ]); + expect(result).toEqual(["file-a.ts"]); }); - test("single file to directory", async ()=>{ + 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" ]); + expect(result).toEqual(["test/fixtures/cli-outputs/file-a.ts"]); }); - test("single file (glob) to file", async ()=>{ + 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" ]); - }) + expect(result).toEqual(["file-a.ts"]); + }); - test("multiple files to file", async ()=>{ + 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 ()=>{ + 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" - ]); - }) + expect(result).toEqual(["test/fixtures/cli-outputs/file-a.ts", "test/fixtures/cli-outputs/file-b.ts"]); + }); - test("multiple nested files to directory", async ()=>{ + test("multiple nested files to directory", async () => { const inputFile = path.join(inputDir, "**/*.yaml"); await execa(cmd, [inputFile, "--output", outputDir], { cwd }); const result = await getOutputFiles(); @@ -141,6 +138,6 @@ describe("CLI", () => { "test/fixtures/cli-outputs/nested/file-c.ts", "test/fixtures/cli-outputs/nested/file-d.ts", ]); - }) + }); }); }); From ff003493f12fe4b556d448171e4c5a870e1d880e Mon Sep 17 00:00:00 2001 From: Josef Schabasser Date: Tue, 19 Sep 2023 08:40:01 +0200 Subject: [PATCH 10/10] make conditions more readable --- packages/openapi-typescript/bin/cli.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/openapi-typescript/bin/cli.js b/packages/openapi-typescript/bin/cli.js index bb75f0c23..0e568e46e 100755 --- a/packages/openapi-typescript/bin/cli.js +++ b/packages/openapi-typescript/bin/cli.js @@ -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) { @@ -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()) || outputDir.pathname !== outputFile.pathname)) { + if (isGlob && output === OUTPUT_FILE && (isFile || !isDirUrl)) { error(`Expected directory for --output if using glob patterns. Received "${flags.output}".`); process.exit(1); } @@ -191,7 +193,7 @@ async function main() { await Promise.all( inputSpecPaths.map(async (specPath) => { if (flags.output !== "." && output === OUTPUT_FILE) { - if (isGlob || outputDir.pathname === outputFile.pathname) { + 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