Skip to content

Commit 6359be9

Browse files
authored
Types: Update Typescript typings (#197)
* feat: Update typescript types * test: Added filtering tests * test: Added command tests * test: Added Overlay tests * chore: Format code * test: Added sorting tests * test: Added parse tpl tests * test: Added logging tests * test: Added core tests
1 parent 2a54712 commit 6359be9

17 files changed

Lines changed: 764 additions & 274 deletions

.github/workflows/npm-test.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,9 @@ jobs:
2727

2828
- name: Run Unit Tests
2929
run: npm run test
30+
31+
- name: Check Type Export Parity
32+
run: npm run check:exports
33+
34+
- name: Check TypeScript Consumer
35+
run: npm run check:types

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
## unreleased
22

3+
## [1.29.5] - 2026-02-28
4+
5+
- Types: Update Typescript typings (#194)
6+
37
## [1.29.4] - 2026-02-23
48

59
- Overlay: refactor: Switch to jsonpathly library for RFC 9535 compliance (#190)

package-lock.json

Lines changed: 36 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
"scripts": {
3333
"test": "jest --colors --verbose --reporters=default --collectCoverage --no-cache --maxWorkers=2",
3434
"lint": "npx prettier --write '**/*.js'",
35+
"check:exports": "node scripts/audit-runtime-exports.js && node scripts/audit-types-exports.js",
36+
"check:types": "tsc -p tsconfig.types.json",
3537
"release": "npx np --branch main"
3638
},
3739
"dependencies": {
@@ -43,7 +45,8 @@
4345
},
4446
"devDependencies": {
4547
"jest": "^30.2.0",
46-
"openapi-types": "^12.1.3"
48+
"openapi-types": "^12.1.3",
49+
"typescript": "^5.9.3"
4750
},
4851
"engines": {
4952
"node": ">=18"

scripts/audit-runtime-exports.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env node
2+
'use strict';
3+
4+
const runtimeExports = Object.keys(require('../openapi-format')).sort();
5+
console.log(runtimeExports.join('\n'));

scripts/audit-types-exports.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/usr/bin/env node
2+
'use strict';
3+
4+
const fs = require('fs');
5+
const path = require('path');
6+
7+
const runtimeExports = Object.keys(require('../openapi-format')).sort();
8+
const dtsPath = path.join(__dirname, '..', 'types', 'openapi-format.d.ts');
9+
const dts = fs.readFileSync(dtsPath, 'utf8');
10+
11+
const typeExportNames = new Set();
12+
for (const match of dts.matchAll(/export\s+function\s+([A-Za-z0-9_]+)/g)) {
13+
typeExportNames.add(match[1]);
14+
}
15+
16+
const typeExports = [...typeExportNames].sort();
17+
const missingInTypes = runtimeExports.filter(name => !typeExportNames.has(name));
18+
const extrasInTypes = typeExports.filter(name => !runtimeExports.includes(name));
19+
20+
if (missingInTypes.length || extrasInTypes.length) {
21+
console.error('Type export parity check failed.');
22+
if (missingInTypes.length) {
23+
console.error(`Missing in types: ${missingInTypes.join(', ')}`);
24+
}
25+
if (extrasInTypes.length) {
26+
console.error(`Missing at runtime: ${extrasInTypes.join(', ')}`);
27+
}
28+
process.exit(1);
29+
}
30+
31+
console.log('Type export parity check passed.');

test/_types/consumer.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import * as openapiFormat from 'openapi-format';
2+
3+
const document = {
4+
openapi: '3.0.3',
5+
info: {title: 'API', version: '1.0.0'},
6+
paths: {}
7+
};
8+
9+
async function verifyTypes() {
10+
await openapiFormat.openapiSort(document, {});
11+
await openapiFormat.openapiFilter(document, {});
12+
await openapiFormat.openapiGenerate(document, {});
13+
await openapiFormat.openapiChangeCase(document, {});
14+
await openapiFormat.openapiOverlay(document, {overlaySet: {actions: []}});
15+
await openapiFormat.openapiSplit(document, {output: 'test/_split/snap.yaml'});
16+
await openapiFormat.openapiConvertVersion(document, {convertTo: '3.1'});
17+
await openapiFormat.openapiRename(document, {rename: 'renamed'});
18+
19+
await openapiFormat.readFile('readme.md', {});
20+
await openapiFormat.parseFile('test/yaml-default/input.yaml', {});
21+
await openapiFormat.parseString('openapi: 3.0.3', {});
22+
await openapiFormat.stringify(document, {});
23+
await openapiFormat.writeFile('test/_split/_types-output.yaml', document, {});
24+
25+
await openapiFormat.detectFormat('openapi: 3.0.3');
26+
openapiFormat.analyzeOpenApi(document);
27+
28+
openapiFormat.changeCase('hello_world', 'camelCase');
29+
openapiFormat.resolveJsonPath(document, '$.paths');
30+
openapiFormat.resolveJsonPathValue(document, '$.paths');
31+
}
32+
33+
void verifyTypes();

test/_types/tsconfig.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"extends": "../../tsconfig.types.json",
3+
"include": [
4+
"./**/*.ts"
5+
]
6+
}

test/command.test.js

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
'use strict';
2+
3+
const {describe, it, expect, beforeEach, afterEach} = require('@jest/globals');
4+
5+
function createCommander() {
6+
jest.resetModules();
7+
const commander = require('../utils/command');
8+
const exits = [];
9+
commander.exitOverride(err => {
10+
exits.push(err);
11+
});
12+
return {commander, exits};
13+
}
14+
15+
describe('MiniCommander', () => {
16+
let stdoutSpy;
17+
let consoleErrorSpy;
18+
19+
beforeEach(() => {
20+
stdoutSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
21+
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
22+
});
23+
24+
afterEach(() => {
25+
stdoutSpy.mockRestore();
26+
consoleErrorSpy.mockRestore();
27+
});
28+
29+
it('displays help and version', () => {
30+
const {commander, exits} = createCommander();
31+
commander.usage('[file]');
32+
commander.description('test description');
33+
commander.version('1.2.3');
34+
35+
commander.parse(['node', '/tmp/cli.js', '--help']);
36+
expect(exits[0].code).toBe('commander.helpDisplayed');
37+
expect(exits[0].exitCode).toBe(0);
38+
expect(stdoutSpy).toHaveBeenCalled();
39+
40+
commander.parse(['node', '/tmp/cli.js', '--version']);
41+
expect(exits[1].code).toBe('commander.version');
42+
expect(exits[1].exitCode).toBe(0);
43+
});
44+
45+
it('handles unknown and missing arguments', () => {
46+
const {commander, exits} = createCommander();
47+
commander.option('-f, --file <path>', 'file path');
48+
49+
commander.parse(['node', '/tmp/cli.js', '--unknown']);
50+
expect(exits[0].code).toBe('commander.unknownOption');
51+
expect(exits[0].message).toContain('--unknown');
52+
53+
commander.parse(['node', '/tmp/cli.js', '--file']);
54+
expect(exits[1].code).toBe('commander.missingArgument');
55+
expect(exits[1].message).toContain('--file');
56+
});
57+
58+
it('parses long options, no-* options and positional args', () => {
59+
const {commander} = createCommander();
60+
let capturedArg;
61+
let capturedOptions;
62+
63+
commander
64+
.option('--name <value>', 'name')
65+
.option('--tag [value]', 'tag')
66+
.option('--no-sort', 'disable sort')
67+
.action((arg, options) => {
68+
capturedArg = arg;
69+
capturedOptions = options;
70+
});
71+
72+
commander.parse(['node', '/tmp/cli.js', 'input.yaml', '--name=alice', '--tag', 'beta', '--no-sort']);
73+
74+
expect(capturedArg).toBe('input.yaml');
75+
expect(capturedOptions).toMatchObject({
76+
name: 'alice',
77+
tag: 'beta',
78+
sort: false
79+
});
80+
});
81+
82+
it('parses short options, including grouped flags and attached values', () => {
83+
const {commander} = createCommander();
84+
let capturedOptions;
85+
86+
commander
87+
.option('-v, --verbose', 'verbose')
88+
.option('-n, --number <num>', 'number', 7)
89+
.action((arg, options) => {
90+
capturedOptions = options;
91+
});
92+
93+
commander.parse(['node', '/tmp/cli.js', 'input.yaml', '-vn5']);
94+
expect(capturedOptions).toMatchObject({verbose: true, number: 5});
95+
96+
commander.parse(['node', '/tmp/cli.js', 'input.yaml', '-n', 'not-a-number']);
97+
expect(capturedOptions.number).toBe(7);
98+
});
99+
100+
it('uses parser functions with previous values and keeps default ordering behavior', () => {
101+
const {commander} = createCommander();
102+
let capturedOptions;
103+
104+
commander
105+
.option(
106+
'--include [value]',
107+
'include values',
108+
(value, previous) => {
109+
const list = Array.isArray(previous) ? previous : [];
110+
return value === undefined ? list : list.concat(value);
111+
},
112+
[]
113+
)
114+
.action((arg, options) => {
115+
capturedOptions = options;
116+
});
117+
118+
commander.parse(['node', '/tmp/cli.js', 'input.yaml', '--include', 'a', '--include', 'b']);
119+
120+
expect(capturedOptions.include).toStrictEqual(['a', 'b']);
121+
expect(Object.keys(capturedOptions)).toContain('include');
122+
});
123+
124+
it('handles async action rejection by exiting with error code', async () => {
125+
const {commander, exits} = createCommander();
126+
127+
commander.action(async () => {
128+
throw new Error('boom');
129+
});
130+
131+
commander.parse(['node', '/tmp/cli.js', 'input.yaml']);
132+
133+
await new Promise(resolve => setImmediate(resolve));
134+
expect(exits[0].code).toBe('commander.asyncActionRejected');
135+
expect(exits[0].exitCode).toBe(1);
136+
});
137+
138+
it('handles unknown short options', () => {
139+
const {commander, exits} = createCommander();
140+
commander.parse(['node', '/tmp/cli.js', '-z']);
141+
expect(exits[0].code).toBe('commander.unknownOption');
142+
expect(exits[0].message).toContain("'-z'");
143+
});
144+
});

test/filtering.test.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
const testUtils = require('./__utils__/test-utils');
4+
const {isUsedComp} = require('../utils/filtering');
45
const {describe, it, expect} = require('@jest/globals');
56

67
describe('openapi-format CLI filtering tests', () => {
@@ -308,4 +309,23 @@ describe('openapi-format CLI filtering tests', () => {
308309
expect(outputAfter).toStrictEqual(outputBefore);
309310
});
310311
});
312+
313+
describe('isUsedComp', () => {
314+
it('returns false for non-object input', () => {
315+
expect(isUsedComp(null, 'schemas')).toBe(false);
316+
});
317+
318+
it('returns false for non-string prop', () => {
319+
expect(isUsedComp({schemas: {used: true}}, 1)).toBe(false);
320+
});
321+
322+
it('returns false when used flag is missing or false', () => {
323+
expect(isUsedComp({schemas: {}}, 'schemas')).toBe(false);
324+
expect(isUsedComp({schemas: {used: false}}, 'schemas')).toBe(false);
325+
});
326+
327+
it('returns true when used flag is true', () => {
328+
expect(isUsedComp({schemas: {used: true}}, 'schemas')).toBe(true);
329+
});
330+
});
311331
});

0 commit comments

Comments
 (0)