Skip to content

Commit 6a32a28

Browse files
authored
Fix Redocly config load error (#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 e7b7f6f commit 6a32a28

File tree

14 files changed

+539
-84
lines changed

14 files changed

+539
-84
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
runs-on: ubuntu-latest
2828
strategy:
2929
matrix:
30-
node-version: [18.x, 20.x, 21.x]
30+
node-version: [18, 20, 21]
3131
steps:
3232
- uses: actions/checkout@v4
3333
- uses: actions/setup-node@v4
@@ -52,8 +52,6 @@ jobs:
5252
- run: pnpm i
5353
- run: pnpm run build
5454
- run: pnpm test
55-
env:
56-
CI_ENV: macos
5755
test-windows:
5856
runs-on: windows-latest
5957
steps:
@@ -67,5 +65,3 @@ jobs:
6765
- run: pnpm i
6866
- run: pnpm run build
6967
- run: pnpm test
70-
env:
71-
CI_ENV: windows

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
"email": "[email protected]"
99
},
1010
"scripts": {
11-
"build": "pnpm run -r --parallel --filter \"!*docs\" build",
12-
"lint": "pnpm run -r --parallel lint",
13-
"format": "pnpm run -r --parallel format",
14-
"test": "pnpm run -r --parallel test",
11+
"build": "pnpm run -r --parallel --filter \"!*docs\" --aggregate-output build",
12+
"lint": "pnpm run -r --parallel --aggregate-output lint",
13+
"format": "pnpm run -r --parallel --aggregate-output format",
14+
"test": "pnpm run -r --parallel --aggregate-output test",
1515
"version": "pnpm run build && changeset version && pnpm i"
1616
},
1717
"devDependencies": {

packages/openapi-fetch/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,13 @@
4848
],
4949
"scripts": {
5050
"build": "pnpm run build:clean && pnpm run build:js && pnpm run build:js-min && pnpm run build:cjs",
51-
"build:clean": "del dist",
51+
"build:clean": "del-cli dist",
5252
"build:js": "mkdir -p dist && cp src/* dist",
5353
"build:js-min": "esbuild --bundle src/index.js --format=esm --minify --outfile=dist/index.min.js && cp dist/index.d.ts dist/index.min.d.ts",
5454
"build:cjs": "esbuild --bundle src/index.js --format=cjs --outfile=dist/cjs/index.cjs && cp dist/index.d.ts dist/cjs/index.d.cts",
5555
"format": "biome format . --write",
5656
"lint": "biome check .",
57-
"generate-types": "cd ../openapi-typescript && pnpm run build && cd ../openapi-fetch && ../openapi-typescript/bin/cli.js ./test/fixtures/api.yaml -o ./test/fixtures/v7-beta.d.ts && npx openapi-typescript ./test/fixtures/api.yaml -o ./test/fixtures/api.d.ts",
57+
"generate-types": "node ./scripts/generate-types.js",
5858
"pretest": "pnpm run generate-types",
5959
"test": "pnpm run \"/^test:/\"",
6060
"test:js": "vitest run",
@@ -68,6 +68,7 @@
6868
"axios": "^1.6.8",
6969
"del-cli": "^5.1.0",
7070
"esbuild": "^0.20.2",
71+
"execa": "^8.0.1",
7172
"msw": "^2.2.14",
7273
"openapi-typescript": "^6.7.5",
7374
"openapi-typescript-codegen": "^0.25.0",
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { execa } from "execa";
2+
import { fileURLToPath } from "node:url";
3+
4+
const root = new URL("../", import.meta.url);
5+
6+
const SCHEMA = "./test/fixtures/api.yaml";
7+
const V6_OUTPUT = "./test/fixtures/api.d.ts";
8+
const V7_OUTPUT = "./test/fixtures/v7-beta.d.ts";
9+
10+
async function generate() {
11+
const cwd = fileURLToPath(root);
12+
13+
await Promise.all([
14+
// note: the version inside node_modules is 6.x
15+
execa("pnpm", ["exec", "openapi-typescript", SCHEMA, "-o", V6_OUTPUT], { cwd }),
16+
// note: the version locally is 7.x
17+
execa("../openapi-typescript/bin/cli.js", [SCHEMA, "-o", V7_OUTPUT], { cwd }),
18+
]);
19+
}
20+
21+
generate();

packages/openapi-typescript/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) {

packages/openapi-typescript/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"

packages/openapi-typescript/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

packages/openapi-typescript/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

0 commit comments

Comments
 (0)