Skip to content

Commit 0597b62

Browse files
committed
Get CLI running
1 parent ede3d67 commit 0597b62

File tree

14 files changed

+1179
-139
lines changed

14 files changed

+1179
-139
lines changed

docs/src/content/docs/advanced.md

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,9 @@ const BASE_URL = "https://myapi.com/v1";
8888
// End Settings
8989

9090
// type helpers — ignore these; these just make TS lookups better
91-
type FilterKeys<Obj, Matchers> = { [K in keyof Obj]: K extends Matchers ? Obj[K] : never }[keyof Obj];
91+
type FilterKeys<Obj, Matchers> = {
92+
[K in keyof Obj]: K extends Matchers ? Obj[K] : never;
93+
}[keyof Obj];
9294
type PathResponses<T> = T extends { responses: any } ? T["responses"] : unknown;
9395
type OperationContent<T> = T extends { content: any } ? T["content"] : unknown;
9496
type MediaType = `${string}/${string}`;
@@ -107,26 +109,39 @@ type MockedResponse<T, Status extends keyof T = keyof T> = FilterKeys<
107109
*/
108110
export function mockResponses(responses: {
109111
[Path in keyof Partial<paths>]: {
110-
[Method in keyof Partial<paths[Path]>]: MockedResponse<PathResponses<paths[Path][Method]>>;
112+
[Method in keyof Partial<paths[Path]>]: MockedResponse<
113+
PathResponses<paths[Path][Method]>
114+
>;
111115
};
112116
}) {
113117
fetchMock.mockResponse((req) => {
114-
const mockedPath = findPath(req.url.replace(BASE_URL, ""), Object.keys(responses))!;
118+
const mockedPath = findPath(
119+
req.url.replace(BASE_URL, ""),
120+
Object.keys(responses),
121+
)!;
115122
// note: we get lazy with the types here, because the inference is bad anyway and this has a `void` return signature. The important bit is the parameter signature.
116-
if (!mockedPath || (!responses as any)[mockedPath]) throw new Error(`No mocked response for ${req.url}`); // throw error if response not mocked (remove or modify if you’d like different behavior)
123+
if (!mockedPath || (!responses as any)[mockedPath])
124+
throw new Error(`No mocked response for ${req.url}`); // throw error if response not mocked (remove or modify if you’d like different behavior)
117125
const method = req.method.toLowerCase();
118-
if (!(responses as any)[mockedPath][method]) throw new Error(`${req.method} called but not mocked on ${mockedPath}`); // likewise throw error if other parts of response aren’t mocked
126+
if (!(responses as any)[mockedPath][method])
127+
throw new Error(`${req.method} called but not mocked on ${mockedPath}`); // likewise throw error if other parts of response aren’t mocked
119128
if (!(responses as any)[mockedPath][method]) {
120129
throw new Error(`${req.method} called but not mocked on ${mockedPath}`);
121130
}
122131
const { status, body } = (responses as any)[mockedPath][method];
123132
return { status, body: JSON.stringify(body) };
124-
})
133+
});
125134
}
126135

127136
// helper function that matches a realistic URL (/users/123) to an OpenAPI path (/users/{user_id}
128-
export function findPath(actual: string, testPaths: string[]): string | undefined {
129-
const url = new URL(actual, actual.startsWith("http") ? undefined : "http://testapi.com");
137+
export function findPath(
138+
actual: string,
139+
testPaths: string[],
140+
): string | undefined {
141+
const url = new URL(
142+
actual,
143+
actual.startsWith("http") ? undefined : "http://testapi.com",
144+
);
130145
const actualParts = url.pathname.split("/");
131146
for (const p of testPaths) {
132147
let matched = true;
@@ -149,7 +164,9 @@ export function findPath(actual: string, testPaths: string[]): string | undefine
149164
```ts
150165
export function mockResponses(responses: {
151166
[Path in keyof Partial<paths>]: {
152-
[Method in keyof Partial<paths[Path]>]: MockedResponse<PathResponses<paths[Path][Method]>>;
167+
[Method in keyof Partial<paths[Path]>]: MockedResponse<
168+
PathResponses<paths[Path][Method]>
169+
>;
153170
};
154171
});
155172
```
@@ -158,6 +175,18 @@ export function mockResponses(responses: {
158175

159176
Now, whenever your schema updates, **all your mock data will be typechecked correctly** 🎉. This is a huge step in ensuring resilient, accurate tests.
160177

178+
## Debugging
179+
180+
To enable debugging, set `DEBUG=openapi-ts:*` as an env var like so:
181+
182+
```sh
183+
$ DEBUG=openapi-ts:* npx openapi-typescript schema.yaml -o my-types.ts
184+
```
185+
186+
To only see certain types of debug messages, you can set `DEBUG=openapi-ts:[scope]` instead. Valid scopes are `redoc`, `lint`, `bundle`, and `ts`.
187+
188+
Note that debug messages will be suppressed if using the CLI and outputting via `stdout`.
189+
161190
## Tips
162191

163192
In no particular order, here are a few best practices to make life easier when working with OpenAPI-derived types.

packages/openapi-typescript/bin/cli.js

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,17 @@
33
import { loadConfig } from "@redocly/openapi-core";
44
import glob from "fast-glob";
55
import fs from "node:fs";
6-
import path from "node:path";
6+
import { fileURLToPath } from "node:url";
77
import parser from "yargs-parser";
8-
import openapiTS, { astToString, COMMENT_HEADER } from "../dist/index.js";
9-
import { c, error } from "../dist/utils.js";
8+
import openapiTS, {
9+
astToString,
10+
c,
11+
COMMENT_HEADER,
12+
error,
13+
formatTime,
14+
} from "../dist/index.js";
15+
16+
/* eslint-disable no-console */
1017

1118
const HELP = `Usage
1219
$ openapi-typescript [input] [options]
@@ -34,7 +41,7 @@ const CWD = new URL(`file://${process.cwd()}/`);
3441
const EXT_RE = /\.[^.]+$/i;
3542
const HTTP_RE = /^https?:\/\//;
3643

37-
const timeStart = process.hrtime();
44+
const timeStart = performance.now();
3845

3946
const [, , ...args] = process.argv;
4047
if (args.includes("-ap")) {
@@ -76,14 +83,14 @@ const flags = parser(args, {
7683
},
7784
});
7885

79-
async function generateSchema(pathToSpec) {
86+
async function generateSchema(url) {
8087
const output = flags.output ? OUTPUT_FILE : OUTPUT_STDOUT; // FILE or STDOUT
8188

82-
const redoclyConfig = await loadConfig(flags.redoc);
89+
const redoclyConfig = flags.redoc ? await loadConfig(flags.redoc) : undefined;
8390

8491
// generate schema
8592
const result = `${COMMENT_HEADER}${astToString(
86-
await openapiTS(pathToSpec, {
93+
await openapiTS(url, {
8794
additionalProperties: flags.additionalProperties,
8895
alphabetize: flags.alphabetize,
8996
contentNever: flags.contentNever,
@@ -110,7 +117,7 @@ async function generateSchema(pathToSpec) {
110117
if (typeof flags.output === "string" && !flags.output.endsWith("/")) {
111118
outputFilePath = new URL(`${flags.output}/`, CWD);
112119
}
113-
const filename = pathToSpec.replace(EXT_RE, ".ts");
120+
const filename = fileURLToPath(url).replace(EXT_RE, ".ts");
114121
const originalOutputFilePath = outputFilePath;
115122
outputFilePath = new URL(filename, originalOutputFilePath);
116123
if (outputFilePath.protocol !== "file:") {
@@ -123,12 +130,10 @@ async function generateSchema(pathToSpec) {
123130

124131
fs.writeFileSync(outputFilePath, result, "utf8");
125132

126-
const timeEnd = process.hrtime(timeStart);
127-
const time = timeEnd[0] + Math.round(timeEnd[1] / 1e6);
128133
console.log(
129-
`🚀 ${c.green(`${pathToSpec}${c.bold(outputFilePath)}`)} ${c.dim(
130-
`[${time}ms]`,
131-
)}`,
134+
`🚀 ${c.green(
135+
`${fileURLToPath(url)}${c.bold(fileURLToPath(outputFilePath))}`,
136+
)} ${c.dim(`[${formatTime(performance.now() - timeStart)}]`)}`,
132137
);
133138
} else {
134139
process.stdout.write(result);
@@ -175,7 +180,7 @@ async function main() {
175180
if (output !== "." && output === OUTPUT_FILE) {
176181
fs.mkdirSync(outputDir, { recursive: true });
177182
}
178-
await generateSchema(pathToSpec);
183+
await generateSchema(new URL(pathToSpec));
179184
return;
180185
}
181186

@@ -204,16 +209,15 @@ async function main() {
204209
// generate schema(s) in parallel
205210
await Promise.all(
206211
inputSpecPaths.map(async (specPath) => {
212+
const globInputFile = new URL(specPath, CWD);
207213
if (flags.output !== "." && output === OUTPUT_FILE) {
208214
if (isGlob || isDirUrl) {
209-
fs.mkdirSync(new URL(path.dirname(specPath), outputDir), {
210-
recursive: true,
211-
}); // recursively make parent dirs
215+
fs.mkdirSync(outputDir, { recursive: true }); // recursively make parent dirs
212216
} else {
213217
fs.mkdirSync(outputDir, { recursive: true }); // recursively make parent dirs
214218
}
215219
}
216-
await generateSchema(specPath);
220+
await generateSchema(globInputFile);
217221
}),
218222
);
219223
}

0 commit comments

Comments
 (0)