Skip to content

Commit 7925f3f

Browse files
authored
Fix Redocly config load error (openapi-ts#1645)
* Fix Redocly config load error * Update CHANGELOG * Actually nevermind JSON5 isn’t a normal thing in OpenAPI * Ensure lint error test fails if it didn’t lint * Curse you, Windows GitHub Actions runner * Aggregate output * Update GH action syntax * Fix Windows?
1 parent 877d319 commit 7925f3f

File tree

9 files changed

+91
-42
lines changed

9 files changed

+91
-42
lines changed

bin/cli.js

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const HELP = `Usage
1212
Options
1313
--help Display this
1414
--version Display the version
15-
--redoc [path], -c Specify path to Redocly config (default: redocly.yaml)
15+
--redocly [path], -c Specify path to Redocly config (default: redocly.yaml)
1616
--output, -o Specify output file (if not specified in redocly.yaml)
1717
--enum Export true TS enums instead of unions
1818
--export-type, -t Export top-level \`type\` instead of \`interface\`
@@ -48,6 +48,9 @@ if (args.includes("--support-array-length")) {
4848
if (args.includes("-it")) {
4949
errorAndExit(`The -it alias has been deprecated. Use "--immutable-types" instead.`);
5050
}
51+
if (args.includes("--redoc")) {
52+
errorAndExit(`The --redoc config flag has been renamed to "--redocly" (or -c as shorthand).`);
53+
}
5154

5255
const flags = parser(args, {
5356
boolean: [
@@ -65,19 +68,19 @@ const flags = parser(args, {
6568
"immutable",
6669
"pathParamsAsTypes",
6770
],
68-
string: ["output", "redoc"],
71+
string: ["output", "redocly"],
6972
alias: {
70-
redoc: ["c"],
73+
redocly: ["c"],
7174
exportType: ["t"],
7275
output: ["o"],
7376
},
7477
});
7578

7679
/**
7780
* @param {string | URL} schema
78-
* @param {@type import('@redocly/openapi-core').Config} redoc
81+
* @param {@type import('@redocly/openapi-core').Config} redocly
7982
*/
80-
async function generateSchema(schema, { redoc, silent = false }) {
83+
async function generateSchema(schema, { redocly, silent = false }) {
8184
return `${COMMENT_HEADER}${astToString(
8285
await openapiTS(schema, {
8386
additionalProperties: flags.additionalProperties,
@@ -92,7 +95,7 @@ async function generateSchema(schema, { redoc, silent = false }) {
9295
exportType: flags.exportType,
9396
immutable: flags.immutable,
9497
pathParamsAsTypes: flags.pathParamsAsTypes,
95-
redoc,
98+
redocly,
9699
silent,
97100
}),
98101
)}`;
@@ -129,39 +132,32 @@ async function main() {
129132
const input = flags._[0];
130133

131134
// load Redocly config
132-
const maybeRedoc = findConfig(flags.redoc ? path.dirname(flags.redoc) : undefined);
133-
const redoc = maybeRedoc
135+
const maybeRedoc = findConfig(flags.redocly ? path.dirname(flags.redocly) : undefined);
136+
const redocly = maybeRedoc
134137
? await loadConfig({ configPath: maybeRedoc })
135138
: await createConfig({}, { extends: ["minimal"] });
136139

137140
// handle Redoc APIs
138-
const hasRedoclyApis = Object.keys(redoc?.apis ?? {}).length > 0;
141+
const hasRedoclyApis = Object.keys(redocly?.apis ?? {}).length > 0;
139142
if (hasRedoclyApis) {
140143
if (input) {
141144
warn("APIs are specified both in Redocly Config and CLI argument. Only using Redocly config.");
142145
}
143146
await Promise.all(
144-
Object.entries(redoc.apis).map(async ([name, api]) => {
147+
Object.entries(redocly.apis).map(async ([name, api]) => {
145148
let configRoot = CWD;
146-
if (redoc.configFile) {
149+
if (redocly.configFile) {
147150
// note: this will be absolute if --redoc is passed; otherwise, relative
148-
configRoot = path.isAbsolute(redoc.configFile)
149-
? new URL(`file://${redoc.configFile}`)
150-
: new URL(redoc.configFile, `file://${process.cwd()}/`);
151+
configRoot = path.isAbsolute(redocly.configFile)
152+
? new URL(`file://${redocly.configFile}`)
153+
: new URL(redocly.configFile, `file://${process.cwd()}/`);
151154
}
152155
if (!api[REDOC_CONFIG_KEY]?.output) {
153-
// TODO: remove in stable v7
154-
if (api["openapi-ts"]) {
155-
errorAndExit(`Please rename "openapi-ts" to "x-openapi-ts" in your Redoc config.`);
156-
}
157-
158156
errorAndExit(
159157
`API ${name} is missing an \`${REDOC_CONFIG_KEY}.output\` key. See https://openapi-ts.pages.dev/cli/#multiple-schemas.`,
160158
);
161159
}
162-
const result = await generateSchema(new URL(api.root, configRoot), {
163-
redoc, // TODO: merge API overrides better?
164-
});
160+
const result = await generateSchema(new URL(api.root, configRoot), { redocly });
165161
const outFile = new URL(api[REDOC_CONFIG_KEY].output, configRoot);
166162
fs.mkdirSync(new URL(".", outFile), { recursive: true });
167163
fs.writeFileSync(outFile, result, "utf8");
@@ -173,7 +169,7 @@ async function main() {
173169
// handle stdin
174170
else if (!input) {
175171
const result = await generateSchema(process.stdin, {
176-
redoc,
172+
redocly,
177173
silent: outputType === OUTPUT_STDOUT,
178174
});
179175
if (outputType === OUTPUT_STDOUT) {
@@ -196,7 +192,7 @@ async function main() {
196192
);
197193
}
198194
const result = await generateSchema(new URL(input, CWD), {
199-
redoc,
195+
redocly,
200196
silent: outputType === OUTPUT_STDOUT,
201197
});
202198
if (outputType === OUTPUT_STDOUT) {

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
},
4242
"scripts": {
4343
"build": "pnpm run build:clean && pnpm run build:esm && pnpm run build:cjs",
44-
"build:clean": "del dist",
44+
"build:clean": "del-cli dist",
4545
"build:esm": "tsc -p tsconfig.build.json",
4646
"build:cjs": "esbuild --bundle --platform=node --target=es2019 --outfile=dist/index.cjs --external:@redocly/ajv --external:@redocly/openapi-core --external:typescript src/index.ts",
4747
"dev": "tsc -p tsconfig.build.json --watch",
@@ -62,6 +62,7 @@
6262
"dependencies": {
6363
"@redocly/openapi-core": "^1.12.0",
6464
"ansi-colors": "^4.1.3",
65+
"parse-json": "^8.1.0",
6566
"supports-color": "^9.4.0",
6667
"yargs-parser": "^21.1.1"
6768
},
@@ -72,7 +73,8 @@
7273
"degit": "^2.8.4",
7374
"del-cli": "^5.1.0",
7475
"esbuild": "^0.20.2",
75-
"execa": "^7.2.0",
76+
"execa": "^8.0.1",
77+
"strip-ansi": "^7.1.0",
7678
"typescript": "^5.4.5",
7779
"vite-node": "^1.5.2",
7880
"vitest": "^1.5.2"

src/lib/redoc.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
} from "@redocly/openapi-core";
1010
import { Readable } from "node:stream";
1111
import { fileURLToPath } from "node:url";
12+
import parseJson from "parse-json";
1213
import type { OpenAPI3 } from "../types.js";
1314
import { debug, error, warn } from "./utils.js";
1415

@@ -64,7 +65,7 @@ export async function parseSchema(schema: unknown, { absoluteRef, resolver }: Pa
6465
if (schema[0] === "{") {
6566
return {
6667
source: new Source(absoluteRef, schema, "application/json"),
67-
parsed: JSON.parse(schema),
68+
parsed: parseJson(schema),
6869
};
6970
}
7071
// YAML

test/cli.test.ts

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import { execa } from "execa";
22
import fs from "node:fs";
33
import os from "node:os";
44
import { fileURLToPath } from "node:url";
5+
import stripAnsi from "strip-ansi";
56
import type { TestCase } from "./test-helpers.js";
67

78
const root = new URL("../", import.meta.url);
89
const cwd = os.platform() === "win32" ? fileURLToPath(root) : root; // execa bug: fileURLToPath required on Windows
910
const cmd = "./bin/cli.js";
10-
const TIMEOUT = 90000;
11+
const TIMEOUT = 90_000;
1112

1213
describe("CLI", () => {
1314
const tests: TestCase<any, any>[] = [
@@ -107,22 +108,21 @@ describe("CLI", () => {
107108
});
108109

109110
describe("Redocly config", () => {
110-
// TODO: why won’t this run on Windows?
111111
test.skipIf(os.platform() === "win32")("automatic config", async () => {
112-
// note: we have to change the cwd here for the automatic config to pick up properly
113-
const root = new URL("./fixtures/redocly/", import.meta.url);
114-
const cwd = os.platform() === "win32" ? fileURLToPath(root) : root;
112+
const cwd = new URL("./fixtures/redocly/", import.meta.url);
115113

116-
await execa("../../../bin/cli.js", { cwd });
114+
await execa("../../../bin/cli.js", {
115+
cwd: fileURLToPath(cwd),
116+
});
117117
for (const schema of ["a", "b", "c"]) {
118-
expect(fs.readFileSync(new URL(`./output/${schema}.ts`, root), "utf8")).toMatchFileSnapshot(
119-
fileURLToPath(new URL("../../../examples/simple-example.ts", root)),
118+
expect(fs.readFileSync(new URL(`./output/${schema}.ts`, cwd), "utf8")).toMatchFileSnapshot(
119+
fileURLToPath(new URL("../../../examples/simple-example.ts", cwd)),
120120
);
121121
}
122122
});
123123

124-
test("--redoc config", async () => {
125-
await execa(cmd, ["--redoc", "test/fixtures/redocly-flag/redocly.yaml"], {
124+
test("--redocly config", async () => {
125+
await execa(cmd, ["--redocly", "test/fixtures/redocly-flag/redocly.yaml"], {
126126
cwd,
127127
});
128128
for (const schema of ["a", "b", "c"]) {
@@ -131,5 +131,18 @@ describe("CLI", () => {
131131
).toMatchFileSnapshot(fileURLToPath(new URL("./examples/simple-example.ts", root)));
132132
}
133133
});
134+
135+
test.skipIf(os.platform() === "win32")("lint error", async () => {
136+
const cwd = new URL("./fixtures/redocly-lint-error", import.meta.url);
137+
138+
try {
139+
await execa("../../../bin/cli.js", {
140+
cwd: fileURLToPath(cwd),
141+
});
142+
throw new Error("Linting should have thrown an error");
143+
} catch (err) {
144+
expect(stripAnsi(String(err))).toMatch(/ {2}Servers must be present/);
145+
}
146+
});
134147
});
135148
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
output
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
openapi: 3.1.0
2+
paths:
3+
/:
4+
get:
5+
responses:
6+
200:
7+
content:
8+
"application/json":
9+
required: true
10+
schema:
11+
type: object
12+
required:
13+
- success
14+
properties:
15+
success:
16+
type: boolean
17+
default:
18+
content:
19+
"application/json":
20+
required: true
21+
schema:
22+
type: object
23+
required:
24+
- status
25+
properties:
26+
status:
27+
type: number
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
extends:
2+
- recommended
3+
4+
apis:
5+
a@v1:
6+
root: ./openapi/openapi.yaml
7+
x-openapi-ts:
8+
output: ./output/openapi.ts

test/node-api.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -710,7 +710,7 @@ export type operations = Record<string, never>;`,
710710
given: new URL("./github-api.yaml", EXAMPLES_DIR),
711711
want: new URL("./github-api.ts", EXAMPLES_DIR),
712712
// options: DEFAULT_OPTIONS,
713-
ci: { timeout: 3000 },
713+
ci: { timeout: 3_000 },
714714
},
715715
],
716716
[
@@ -719,7 +719,7 @@ export type operations = Record<string, never>;`,
719719
given: new URL("./github-api-next.yaml", EXAMPLES_DIR),
720720
want: new URL("./github-api-next.ts", EXAMPLES_DIR),
721721
// options: DEFAULT_OPTIONS,
722-
ci: { timeout: 30000 },
722+
ci: { timeout: 30_000 },
723723
},
724724
],
725725
[
@@ -728,7 +728,7 @@ export type operations = Record<string, never>;`,
728728
given: new URL("./octokit-ghes-3.6-diff-to-api.json", EXAMPLES_DIR),
729729
want: new URL("./octokit-ghes-3.6-diff-to-api.ts", EXAMPLES_DIR),
730730
// options: DEFAULT_OPTIONS,
731-
ci: { timeout: 30000 },
731+
ci: { timeout: 30_000 },
732732
},
733733
],
734734
[
@@ -737,7 +737,7 @@ export type operations = Record<string, never>;`,
737737
given: new URL("./stripe-api.yaml", EXAMPLES_DIR),
738738
want: new URL("./stripe-api.ts", EXAMPLES_DIR),
739739
// options: DEFAULT_OPTIONS,
740-
ci: { timeout: 30000 },
740+
ci: { timeout: 30_000 },
741741
},
742742
],
743743
[
@@ -746,7 +746,7 @@ export type operations = Record<string, never>;`,
746746
given: new URL("./digital-ocean-api/DigitalOcean-public.v2.yaml", EXAMPLES_DIR),
747747
want: new URL("./digital-ocean-api.ts", EXAMPLES_DIR),
748748
// options: DEFAULT_OPTIONS,
749-
ci: { timeout: 30000 },
749+
ci: { timeout: 30_000 },
750750
},
751751
],
752752
];

vitest.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { defineConfig } from "vitest/config";
2+
import os from "node:os";
23

34
export default defineConfig({
45
test: {

0 commit comments

Comments
 (0)