Skip to content

Commit ede3d67

Browse files
committed
Polish testing setup
1 parent 6d1eb32 commit ede3d67

35 files changed

+983
-598
lines changed

.eslintrc.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ module.exports = {
4646
"object-shorthand": "error", // don’t use foo["bar"]
4747
"no-console": "error",
4848
"no-global-assign": "error",
49+
"no-undef": "off", // handled by TS
4950
"no-unused-vars": "off",
5051
},
5152
overrides: [

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
"devDependencies": {
2525
"@changesets/changelog-github": "^0.4.8",
2626
"@changesets/cli": "^2.26.2",
27-
"@typescript-eslint/eslint-plugin": "^6.7.3",
28-
"@typescript-eslint/parser": "^6.7.3",
27+
"@typescript-eslint/eslint-plugin": "^6.7.4",
28+
"@typescript-eslint/parser": "^6.7.4",
2929
"del-cli": "^5.1.0",
3030
"eslint": "^8.50.0",
3131
"eslint-config-prettier": "^9.0.0",

packages/openapi-fetch/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,14 @@
6363
"version": "pnpm run prepare && pnpm run build"
6464
},
6565
"dependencies": {
66-
"openapi-typescript-helpers": "^0.0.3"
66+
"openapi-typescript-helpers": "workspace:^"
6767
},
6868
"devDependencies": {
6969
"axios": "^1.5.1",
7070
"del-cli": "^5.1.0",
7171
"esbuild": "^0.19.4",
7272
"nanostores": "^0.9.3",
73-
"openapi-typescript": "workspace:",
73+
"openapi-typescript": "workspace:^",
7474
"openapi-typescript-codegen": "^0.25.0",
7575
"openapi-typescript-fetch": "^1.1.3",
7676
"superagent": "^8.1.2",

packages/openapi-typescript/bin/cli.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { loadConfig } from "@redocly/openapi-core";
44
import glob from "fast-glob";
55
import fs from "node:fs";
66
import path from "node:path";
7-
import { URL } from "node:url";
87
import parser from "yargs-parser";
98
import openapiTS, { astToString, COMMENT_HEADER } from "../dist/index.js";
109
import { c, error } from "../dist/utils.js";

packages/openapi-typescript/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
"devDependencies": {
7070
"@types/degit": "^2.8.4",
7171
"@types/js-yaml": "^4.0.6",
72-
"@types/node": "^20.7.2",
72+
"@types/node": "^20.8.0",
7373
"degit": "^2.8.4",
7474
"del-cli": "^5.1.0",
7575
"esbuild": "^0.19.4",

packages/openapi-typescript/scripts/download-schemas.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1+
import degit from "degit";
12
import fs from "node:fs";
23
import path from "node:path";
34
import { fileURLToPath } from "node:url";
4-
import degit from "degit";
55
import { error } from "../src/lib/utils.js";
66
import { multiFile, singleFile } from "./schemas.js";
77

88
const ONE_DAY = 1000 * 60 * 60 * 24;
99
const EXAMPLES_DIR = new URL("../examples/", import.meta.url);
1010

11+
/* eslint-disable no-console */
12+
1113
export async function download() {
1214
const allSchemas = Object.keys({ ...singleFile, ...multiFile });
1315
let done = 0;
@@ -19,7 +21,9 @@ export async function download() {
1921
const dest = new URL(`${k}${ext}`, EXAMPLES_DIR);
2022
if (fs.existsSync(dest)) {
2123
const { mtime } = fs.statSync(dest);
22-
if (Date.now() - mtime.getTime() < ONE_DAY) return; // only update every 24 hours at most
24+
if (Date.now() - mtime.getTime() < ONE_DAY) {
25+
return; // only update every 24 hours at most
26+
}
2327
}
2428
const result = await fetch(url);
2529
if (!result.ok) {
@@ -40,7 +44,9 @@ export async function download() {
4044
const dest = new URL(k, EXAMPLES_DIR);
4145
if (fs.existsSync(dest)) {
4246
const { mtime } = fs.statSync(dest);
43-
if (Date.now() - mtime.getTime() < ONE_DAY) return; // only update every 24 hours at most
47+
if (Date.now() - mtime.getTime() < ONE_DAY) {
48+
return; // only update every 24 hours at most
49+
}
4450
}
4551
const emitter = degit(meta.repo, {
4652
force: true,

packages/openapi-typescript/scripts/update-examples.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { execa } from "execa";
22
import path from "node:path";
3-
import { URL } from "node:url";
43
import { multiFile, singleFile } from "./schemas.js";
54

5+
/* eslint-disable no-console */
6+
67
async function generateSchemas() {
78
const cwd = new URL("../", import.meta.url);
89
const allSchemas = Object.keys({ ...singleFile, ...multiFile });
@@ -13,15 +14,35 @@ async function generateSchemas() {
1314
...Object.keys(singleFile).map(async (name) => {
1415
const start = performance.now();
1516
const ext = path.extname(singleFile[name as keyof typeof singleFile]);
16-
await execa("./bin/cli.js", [`./examples/${name}${ext}`, "-o", `./examples/${name}.ts`], { cwd });
17+
await execa(
18+
"./bin/cli.js",
19+
[`./examples/${name}${ext}`, "-o", `./examples/${name}.ts`],
20+
{ cwd },
21+
);
1722
done++;
18-
console.log(`✔︎ [${done}/${allSchemas.length}] Updated ${name} (${Math.round(performance.now() - start)}ms)`); // eslint-disable-line no-console
23+
console.log(
24+
`✔︎ [${done}/${allSchemas.length}] Updated ${name} (${Math.round(
25+
performance.now() - start,
26+
)}ms)`,
27+
); // eslint-disable-line no-console
1928
}),
2029
...Object.entries(multiFile).map(async ([name, meta]) => {
2130
const start = performance.now();
22-
await execa("./bin/cli.js", [`./examples/${name}${meta.entry.substring(1)}`, "-o", `./examples/${name}.ts`], { cwd });
31+
await execa(
32+
"./bin/cli.js",
33+
[
34+
`./examples/${name}${meta.entry.substring(1)}`,
35+
"-o",
36+
`./examples/${name}.ts`,
37+
],
38+
{ cwd },
39+
);
2340
done++;
24-
console.log(`✔︎ [${done}/${allSchemas.length}] Updated ${name} (${Math.round(performance.now() - start)}ms)`); // eslint-disable-line no-console
41+
console.log(
42+
`✔︎ [${done}/${allSchemas.length}] Updated ${name} (${Math.round(
43+
performance.now() - start,
44+
)}ms)`,
45+
); // eslint-disable-line no-console
2546
}),
2647
]);
2748
console.log("Updating examples done."); // eslint-disable-line no-console

packages/openapi-typescript/src/index.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import type { Readable } from "node:stream";
2-
import { URL } from "node:url";
32
import ts from "typescript";
43
import { validateAndBundle } from "./lib/redoc.js";
5-
import { resolveRef, scanDiscriminators } from "./lib/utils.js";
4+
import { debug, resolveRef, scanDiscriminators } from "./lib/utils.js";
65
import transformSchema from "./transform/index.js";
76
import type { GlobalContext, OpenAPI3, OpenAPITSOptions } from "./types.js";
87

@@ -64,5 +63,13 @@ export default async function openapiTS(
6463
},
6564
};
6665

67-
return transformSchema(schema, ctx);
66+
const transformT = performance.now();
67+
const result = transformSchema(schema, ctx);
68+
debug(
69+
"Completed AST transformation for entire document",
70+
"ts",
71+
performance.now() - transformT,
72+
);
73+
74+
return result;
6875
}

packages/openapi-typescript/src/lib/redoc.ts

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
import { Readable } from "node:stream";
1212
import { fileURLToPath } from "node:url";
1313
import { OpenAPI3 } from "../types.js";
14-
import { error } from "./utils.js";
14+
import { debug, error } from "./utils.js";
1515

1616
export interface ValidateAndBundleOptions {
1717
redocly?: RedoclyConfig;
@@ -57,15 +57,45 @@ export async function validateAndBundle(
5757
source: string | URL | OpenAPI3 | Readable,
5858
options?: ValidateAndBundleOptions,
5959
) {
60+
const redocConfigT = performance.now();
6061
const redocConfig = await createConfig(options?.redocly ?? {});
62+
debug("Loaded Redoc config", "redoc", performance.now() - redocConfigT);
63+
const redocParseT = performance.now();
6164
const resolver = new BaseResolver(redocConfig.resolve);
6265
const document = await parseSchema(
6366
source,
6467
options?.cwd ?? process.cwd(),
6568
resolver,
6669
);
70+
debug("Parsed schema", "redoc", performance.now() - redocParseT);
6771

68-
// 1. lint
72+
// 1. check for OpenAPI 3 or greater
73+
const openapiVersion = parseFloat(document.parsed.openapi);
74+
75+
if (
76+
document.parsed.swagger ||
77+
!document.parsed.openapi ||
78+
Number.isNaN(openapiVersion) ||
79+
openapiVersion < 3 ||
80+
openapiVersion >= 4
81+
) {
82+
if (document.parsed.swagger) {
83+
error("Unsupported Swagger version: 2.x. Use OpenAPI 3.x instead.");
84+
} else if (
85+
document.parsed.openapi ||
86+
openapiVersion < 3 ||
87+
openapiVersion >= 4
88+
) {
89+
error(`Unsupported OpenAPI version: ${document.parsed.openapi}`);
90+
} else {
91+
error("Unsupported schema format, expected `openapi: 3.x`");
92+
}
93+
process.exit(1);
94+
return; // hack for tests/mocking
95+
}
96+
97+
// 2. lint
98+
const redocLintT = performance.now();
6999
const problems = await lintDocument({
70100
document,
71101
config: redocConfig.styleguide,
@@ -81,10 +111,13 @@ export async function validateAndBundle(
81111
}
82112
if (hasError) {
83113
process.exit(1);
114+
return;
84115
}
85116
}
117+
debug("Linted schema", "lint", performance.now() - redocLintT);
86118

87-
// 2. bundle
119+
// 3. bundle
120+
const redocBundleT = performance.now();
88121
const bundled = await bundle({
89122
config: redocConfig,
90123
dereference: true,
@@ -93,15 +126,17 @@ export async function validateAndBundle(
93126
if (bundled.problems.length) {
94127
let hasError = false;
95128
for (const problem of bundled.problems) {
129+
error(problem.message);
96130
if (problem.severity === "error") {
97-
error(problem.message);
98131
hasError = true;
99132
}
100133
}
101134
if (hasError) {
102135
process.exit(1);
136+
return;
103137
}
104138
}
139+
debug("Bundled schema", "bundle", performance.now() - redocBundleT);
105140

106141
return bundled.bundle.parsed;
107142
}

packages/openapi-typescript/src/lib/utils.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ if (!supportsColor.stdout || supportsColor.stdout.hasBasic === false) {
1212
c.enabled = false;
1313
}
1414

15+
const DEBUG_GROUPS: Record<string, c.StyleFunction | undefined> = {
16+
lint: c.yellowBright,
17+
redoc: c.cyanBright,
18+
bundle: c.magentaBright,
19+
ts: c.blueBright,
20+
};
21+
1522
export { c };
1623

1724
/** Given a discriminator object, get the property name */
@@ -62,6 +69,32 @@ export function createRef(parts: (number | string)[]): string {
6269
return pointer;
6370
}
6471

72+
/** Print debug message */
73+
export function debug(msg: string, group?: string, time?: number) {
74+
if (
75+
process.env.DEBUG &&
76+
(!group ||
77+
process.env.DEBUG === "*" ||
78+
process.env.DEBUG.toLocaleLowerCase() === group.toLocaleLowerCase())
79+
) {
80+
const groupColor = (group && DEBUG_GROUPS[group]) || c.whiteBright;
81+
const groupName = groupColor(`oapts:${group ?? "info"}`);
82+
let timeFormatted = "";
83+
if (typeof time === "number") {
84+
if (time < 1000) {
85+
timeFormatted = `${Math.round(100 * time) / 100}ms`;
86+
} else if (time < 60000) {
87+
timeFormatted = `${Math.round(time / 100) / 10}s`;
88+
} else {
89+
timeFormatted = `${Math.round(time / 6000) / 10}m`;
90+
}
91+
timeFormatted = c.green(` ${timeFormatted} `);
92+
}
93+
// eslint-disable-next-line no-console
94+
console.debug(` ${c.bold(groupName)}${timeFormatted}${msg}`);
95+
}
96+
}
97+
6598
/** Print error message */
6699
export function error(msg: string) {
67100
console.error(c.red(` ✘ ${msg}`)); // eslint-disable-line no-console

packages/openapi-typescript/src/transform/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import ts, { TypeAliasDeclaration, TypeLiteralNode } from "typescript";
22
import { NEVER, STRING, tsModifiers, tsRecord } from "../lib/ts.js";
3-
import { createRef } from "../lib/utils.js";
3+
import { createRef, debug } from "../lib/utils.js";
44
import { GlobalContext, OpenAPI3 } from "../types.js";
55
import transformComponentsObject from "./components-object.js";
66
import transformPathsObject from "./paths-object.js";
@@ -28,6 +28,7 @@ export default function transformSchema(schema: OpenAPI3, ctx: GlobalContext) {
2828

2929
for (const root of Object.keys(transformers) as SchemaTransforms[]) {
3030
if (schema[root]) {
31+
const rootT = performance.now();
3132
const subType = transformers[root](schema[root], ctx);
3233
type.push(
3334
ctx.exportType
@@ -51,6 +52,7 @@ export default function transformSchema(schema: OpenAPI3, ctx: GlobalContext) {
5152
/* members */ (subType as TypeLiteralNode).members,
5253
),
5354
);
55+
debug(`Transformed ${root} object`, "ts", performance.now() - rootT);
5456
} else {
5557
type.push(
5658
ts.factory.createTypeAliasDeclaration(
@@ -63,6 +65,7 @@ export default function transformSchema(schema: OpenAPI3, ctx: GlobalContext) {
6365
/* type */ tsRecord(STRING, NEVER),
6466
),
6567
);
68+
debug(`Skipped: ${root} object`, "ts", 0);
6669
}
6770
}
6871

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
/* eslint-disable @typescript-eslint/no-var-requires */
22

33
// important: MUST use require()!
4-
const fs = require("node:fs");
5-
const { fileURLToPath, URL } = require("node:url");
4+
const { fileURLToPath } = require("node:url");
65
const openapiTS = require("../dist/index.cjs");
7-
const yaml = require("js-yaml");
86

97
describe("CJS bundle", () => {
108
it("basic", async () => {
11-
const input = yaml.load(fs.readFileSync(new URL("../examples/stripe-api.yaml", import.meta.url), "utf8"));
12-
const output = await openapiTS(input);
13-
expect(output).toMatchFileSnapshot(fileURLToPath(new URL("../examples/stripe-api.ts", import.meta.url)));
9+
const output = await openapiTS(
10+
new URL("../examples/stripe-api.yaml", import.meta.url),
11+
);
12+
expect(output).toMatchFileSnapshot(
13+
fileURLToPath(new URL("../examples/stripe-api.ts", import.meta.url)),
14+
);
1415
});
1516
});

0 commit comments

Comments
 (0)