Skip to content

Commit 4ea0c81

Browse files
authored
feat: drop support for node 18 (#1007)
* feat: drop support for node 18 * fix: prettier issue
1 parent b23f301 commit 4ea0c81

File tree

12 files changed

+153
-199
lines changed

12 files changed

+153
-199
lines changed

package-lock.json

+87-130
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"url": "https://github.com/readmeio/api.git"
2626
},
2727
"engines": {
28-
"node": ">=16"
28+
"node": ">=20.10.0"
2929
},
3030
"workspaces": [
3131
"./packages/*"

packages/api/package.json

+4-5
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"author": "Jon Ursenbach <jon@readme.io>",
3434
"license": "MIT",
3535
"engines": {
36-
"node": "^18.20.0 || >=20.10.0"
36+
"node": ">=20.10.0"
3737
},
3838
"files": [
3939
"dist",
@@ -48,7 +48,6 @@
4848
],
4949
"dependencies": {
5050
"@readme/api-core": "file:../core",
51-
"@readme/openapi-parser": "^2.4.0",
5251
"chalk": "^5.3.0",
5352
"ci-info": "^4.0.0",
5453
"commander": "^13.0.0",
@@ -59,7 +58,8 @@
5958
"js-yaml": "^4.1.0",
6059
"license": "^1.0.3",
6160
"lodash-es": "^4.17.21",
62-
"oas": "^25.0.1",
61+
"oas": "^26.0.1",
62+
"oas-normalize": "^13.1.0",
6363
"ora": "^8.0.1",
6464
"preferred-pm": "^4.0.0",
6565
"prompts": "^2.4.2",
@@ -72,7 +72,7 @@
7272
},
7373
"devDependencies": {
7474
"@api/test-utils": "file:../test-utils",
75-
"@readme/oas-examples": "^5.12.1",
75+
"@readme/oas-examples": "^5.19.1",
7676
"@types/js-yaml": "^4.0.9",
7777
"@types/lodash-es": "^4.17.12",
7878
"@types/prompts": "^2.4.9",
@@ -85,7 +85,6 @@
8585
"ajv": "^8.12.0",
8686
"ajv-formats": "^3.0.1",
8787
"nock": "^14.0.1",
88-
"oas-normalize": "^12.0.0",
8988
"openapi-types": "^12.1.3",
9089
"tsup": "^8.4.0",
9190
"tsx": "^4.19.1",

packages/api/src/codegen/languages/typescript/index.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1151,7 +1151,10 @@ Generated at ${createdAt}
11511151

11521152
return s;
11531153
},
1154-
});
1154+
/**
1155+
* @todo can remove this casting after https://github.com/readmeio/oas/pull/956 is published
1156+
*/
1157+
}) as SchemaObject[];
11551158

11561159
if (!schema) {
11571160
return false;

packages/api/src/fetcher.ts

+19-13
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import type { OASDocument } from 'oas/types';
33
import fs from 'node:fs';
44
import path from 'node:path';
55

6-
import OpenAPIParser from '@readme/openapi-parser';
76
import yaml from 'js-yaml';
7+
import OASNormalize from 'oas-normalize';
88

99
export default class Fetcher {
1010
uri: OASDocument | string;
@@ -124,27 +124,33 @@ export default class Fetcher {
124124
});
125125
}
126126

127-
static validate(json: OASDocument) {
127+
static async validate(json: OASDocument) {
128128
if (json.swagger) {
129129
throw new Error('Sorry, this module only supports OpenAPI definitions.');
130130
}
131131

132-
// The `validate` method handles dereferencing for us.
133-
return OpenAPIParser.validate(json, {
134-
dereference: {
135-
/**
136-
* If circular `$refs` are ignored they'll remain in the API definition as `$ref: String`.
137-
* This allows us to not only do easy circular reference detection but also stringify and
138-
* save dereferenced API definitions back into the cache directory.
139-
*/
140-
circular: 'ignore',
132+
const normalize = new OASNormalize(json, {
133+
parser: {
134+
dereference: {
135+
/**
136+
* If circular `$refs` are ignored they'll remain in the API definition as `$ref: String`.
137+
* This allows us to not only do easy circular reference detection but also stringify and
138+
* save dereferenced API definitions back into the cache directory.
139+
*/
140+
circular: 'ignore',
141+
},
141142
},
142-
}).catch(err => {
143-
if (/is not a valid openapi definition/i.test(err.message)) {
143+
});
144+
145+
await normalize.validate().catch(err => {
146+
// Zhuzh up this error message a bit so our errors here are consistenly prefixed with "Sorry".
147+
if (err.message === 'The supplied API definition is unsupported.') {
144148
throw new Error("Sorry, that doesn't look like a valid OpenAPI definition.");
145149
}
146150

147151
throw err;
148152
});
153+
154+
return normalize.dereference();
149155
}
150156
}

packages/core/package.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -46,22 +46,22 @@
4646
"author": "Jon Ursenbach <jon@readme.io>",
4747
"license": "MIT",
4848
"engines": {
49-
"node": ">=18"
49+
"node": ">=20.10.0"
5050
},
5151
"dependencies": {
52-
"@readme/oas-to-har": "^24.0.0",
52+
"@readme/oas-to-har": "^25.0.1",
5353
"caseless": "^0.12.0",
5454
"datauri": "^4.1.0",
5555
"fetch-har": "^11.0.1",
5656
"json-schema-to-ts": "^3.0.0",
5757
"json-schema-traverse": "^1.0.0",
5858
"lodash.merge": "^4.6.2",
59-
"oas": "^25.0.1",
59+
"oas": "^26.0.1",
6060
"remove-undefined-objects": "^6.0.0"
6161
},
6262
"devDependencies": {
6363
"@api/test-utils": "file:../test-utils",
64-
"@readme/oas-examples": "^5.12.0",
64+
"@readme/oas-examples": "^5.19.1",
6565
"@types/caseless": "^0.12.5",
6666
"@types/lodash.merge": "^4.6.9",
6767
"@vitest/coverage-v8": "^3.0.5",

packages/core/src/index.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,10 @@ export default class APICore {
104104
init.signal = controller.signal;
105105
}
106106

107-
return fetchHar(har as Har, {
108-
files: data.files || {},
109-
init,
110-
userAgent: this.userAgent,
111-
})
107+
// `getHarForRequest` returns a partial HAR object, by way of `@readme/oas-to-har` but
108+
// `fetch-har` is typed to expect the full thing. Though we're supplying an incomplete HAR
109+
// object, at least the spec, our partial is fine.
110+
return fetchHar(har as unknown as Har, { files: data.files || {}, init, userAgent: this.userAgent })
112111
.then(async (res: Response) => {
113112
const parsed = await parseResponse<HTTPStatus>(res);
114113

packages/core/src/lib/prepareParams.ts

+16-25
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { ReadStream } from 'node:fs';
22
import type { Operation } from 'oas/operation';
3-
import type { ParameterObject, SchemaObject } from 'oas/types';
3+
import type { DataForHAR, ParameterObject, SchemaObject } from 'oas/types';
44

55
import stream from 'node:stream';
66

@@ -13,6 +13,8 @@ import removeUndefinedObjects from 'remove-undefined-objects';
1313

1414
import getJSONSchemaDefaults from './getJSONSchemaDefaults.js';
1515

16+
type DataForHARWithFiles = DataForHAR & { files?: Record<string, Buffer> };
17+
1618
// These headers are normally only defined by the OpenAPI definition but we allow the user to
1719
// manually supply them in their `metadata` parameter if they wish.
1820
const specialHeaders = ['accept', 'authorization'];
@@ -56,15 +58,15 @@ function isPrimitive(obj: unknown) {
5658
return obj === null || typeof obj === 'number' || typeof obj === 'string';
5759
}
5860

59-
function merge(src: unknown, target: unknown) {
61+
function merge<R = unknown>(src: R, target: R): R {
6062
if (Array.isArray(target)) {
6163
// @todo we need to add support for merging array defaults with array body/metadata arguments
62-
return target;
64+
return target as R;
6365
} else if (!isObject(target)) {
64-
return target;
66+
return target as R;
6567
}
6668

67-
return lodashMerge(src, target);
69+
return lodashMerge<R, R>(src, target);
6870
}
6971

7072
/**
@@ -144,7 +146,11 @@ async function processFile(
144146
* with `@readme/oas-to-har`.
145147
*
146148
*/
147-
export default async function prepareParams(operation: Operation, body?: unknown, metadata?: Record<string, unknown>) {
149+
export default async function prepareParams(
150+
operation: Operation,
151+
body?: unknown,
152+
metadata?: Record<string, unknown>,
153+
): Promise<DataForHARWithFiles> {
148154
let metadataIntersected = false;
149155
const digestedParameters = digestParameters(operation.getParameters());
150156
const jsonSchema = operation.getParametersAsJSONSchema();
@@ -189,22 +195,7 @@ export default async function prepareParams(operation: Operation, body?: unknown
189195
}
190196

191197
const jsonSchemaDefaults = jsonSchema ? getJSONSchemaDefaults(jsonSchema) : {};
192-
193-
const params: {
194-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
195-
body?: any;
196-
cookie?: Record<string, boolean | number | string>;
197-
files?: Record<string, Buffer>;
198-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
199-
formData?: any;
200-
header?: Record<string, boolean | number | string>;
201-
path?: Record<string, boolean | number | string>;
202-
query?: Record<string, boolean | number | string>;
203-
server?: {
204-
selected: number;
205-
variables: Record<string, number | string>;
206-
};
207-
} = jsonSchemaDefaults;
198+
const params: DataForHARWithFiles = jsonSchemaDefaults;
208199

209200
// If a body argument was supplied we need to do a bit of work to see if it's actually a body
210201
// argument or metadata because the library lets you supply either a body, metadata, or body with
@@ -321,7 +312,7 @@ export default async function prepareParams(operation: Operation, body?: unknown
321312
// Form data should be placed within `formData` instead of `body` for it to properly get picked
322313
// up by `fetch-har`.
323314
if (operation.isFormUrlEncoded()) {
324-
params.formData = merge(params.formData, params.body);
315+
params.formData = merge<DataForHARWithFiles['formData']>(params.formData, params.body);
325316
delete params.body;
326317
}
327318

@@ -380,7 +371,7 @@ export default async function prepareParams(operation: Operation, body?: unknown
380371
// out anything that they sent that is a parameter from also being sent as part of a form
381372
// data payload for `x-www-form-urlencoded` requests.
382373
if (metadataIntersected && operation.isFormUrlEncoded()) {
383-
if (paramName in params.formData) {
374+
if (params.formData && paramName in params.formData) {
384375
delete params.formData[paramName];
385376
}
386377
}
@@ -412,7 +403,7 @@ export default async function prepareParams(operation: Operation, body?: unknown
412403
}
413404

414405
if (operation.isFormUrlEncoded()) {
415-
params.formData = merge(params.formData, metadata);
406+
params.formData = merge<DataForHARWithFiles['formData']>(params.formData, metadata);
416407
} else {
417408
// Any other remaining unused metadata will be unused because we don't know where to place
418409
// it in the request.

packages/httpsnippet-client-api/package.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,23 @@
3333
"author": "Jon Ursenbach <jon@readme.io>",
3434
"license": "MIT",
3535
"engines": {
36-
"node": ">=18"
36+
"node": ">=20.10.0"
3737
},
3838
"dependencies": {
3939
"content-type": "^1.0.5",
4040
"reserved2": "^0.1.5"
4141
},
4242
"peerDependencies": {
4343
"@readme/httpsnippet": "^11.0.0",
44-
"oas": "^25.0.1"
44+
"oas": "^26.0.1"
4545
},
4646
"devDependencies": {
47-
"@readme/oas-examples": "^5.12.0",
48-
"@readme/openapi-parser": "^2.5.0",
47+
"@readme/oas-examples": "^5.19.1",
4948
"@types/content-type": "^1.1.8",
5049
"@types/stringify-object": "^4.0.5",
5150
"@vitest/coverage-v8": "^3.0.5",
5251
"camelcase": "^8.0.0",
52+
"jest-expect-openapi": "^2.0.1",
5353
"stringify-object": "^5.0.0",
5454
"typescript": "^5.8.2",
5555
"vitest": "^3.0.4"

packages/httpsnippet-client-api/test/index.test.ts

+7-9
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ import fs from 'node:fs/promises';
77
import path from 'node:path';
88

99
import { HTTPSnippet, addClientPlugin } from '@readme/httpsnippet';
10-
import readme from '@readme/oas-examples/3.0/json/readme.json';
11-
import openapiParser from '@readme/openapi-parser';
10+
import readme from '@readme/oas-examples/3.0/json/readme.json' with { type: 'json' };
11+
import toBeAValidOpenAPIDefinition from 'jest-expect-openapi';
1212
import { describe, beforeEach, expect, it } from 'vitest';
1313

1414
import plugin from '../src/index.js';
1515

16+
expect.extend({ toBeAValidOpenAPIDefinition });
17+
1618
const DATASETS_DIR = path.join(__dirname, '__datasets__');
1719
const SNIPPETS = readdirSync(DATASETS_DIR);
1820

@@ -109,18 +111,14 @@ describe('httpsnippet-client-api', () => {
109111

110112
describe('snippets', () => {
111113
describe.each(SNIPPETS)('%s', snippet => {
112-
let mock: SnippetMock;
113-
114-
beforeEach(async () => {
115-
mock = await getSnippetDataset(snippet);
114+
it('should generate the expected snippet', async () => {
115+
const mock = await getSnippetDataset(snippet);
116116

117117
// `OpenAPIParser.validate()` updates the spec that's passed and we just want to validate
118118
// it here so we need to clone the object.
119119
const spec = JSON.parse(JSON.stringify(mock.definition));
120-
await openapiParser.validate(spec);
121-
});
120+
await expect(spec).toBeAValidOpenAPIDefinition();
122121

123-
it('should generate the expected snippet', async () => {
124122
const expected = await fs.readFile(path.join(DATASETS_DIR, snippet, 'output.js'), 'utf-8');
125123

126124
const code = new HTTPSnippet(mock.har).convert('node', 'api', {

packages/test-utils/load-spec.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
* Because parts of our OpenAPI dereferencer in `oas` can update variable references passed to it
33
* we may need to sometimes fully clone a spec to test something. This is just a small DIY wrapper
44
* for doing so.
5+
*
56
*/
6-
export function loadSpec(spec: string) {
7+
export async function loadSpec(spec: string) {
78
return import(spec).then(({ default: data }) => JSON.stringify(data)).then(JSON.parse);
89
}

packages/test-utils/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
},
1414
"devDependencies": {
1515
"@types/caseless": "^0.12.3",
16-
"oas": "^25.0.2",
16+
"oas": "^26.0.1",
1717
"typescript": "^5.8.2"
1818
},
1919
"prettier": "@readme/eslint-config/prettier"

0 commit comments

Comments
 (0)