Skip to content

Commit 3544cdb

Browse files
authored
feat: windows test bug, add support for derefencing definitions (#30)
* Clean up type imports, fix release scripts * removed dependency, only test on greater than node 14, added lint checks * test on windows * test on windows * test on windows * test on windows * support definitions conversions * fix typescript complaints in tests
1 parent 18393ac commit 3544cdb

14 files changed

+273
-1526
lines changed

Diff for: .eslintrc.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ module.exports = {
2727
SwitchCase: 1,
2828
},
2929
],
30-
'linebreak-style': ['error', 'unix'],
30+
'linebreak-style': [
31+
'error',
32+
process.platform === 'win32' ? 'windows' : 'unix',
33+
],
3134
quotes: ['error', 'single'],
3235
semi: ['error', 'always'],
3336
'@typescript-eslint/ban-ts-comment': 'off',
@@ -38,5 +41,7 @@ module.exports = {
3841
prefer: 'type-imports',
3942
},
4043
],
44+
'@typescript-eslint/no-explicit-any': 'off',
4145
},
46+
ignorePatterns: ['dist/**', 'bin/**'],
4247
};

Diff for: .github/workflows/release.yml

+6-6
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ jobs:
1010
name: Release
1111
runs-on: ubuntu-latest
1212
steps:
13-
- uses: actions/checkout@v2
14-
- uses: actions/setup-node@v2
13+
- uses: actions/checkout@v3
14+
- uses: actions/setup-node@v3
1515
with:
16-
node-version: 'lts/*'
17-
- run: npm ci
18-
- run: npm run build --if-present
19-
- run: npm test
16+
node-version: 18
17+
- run: yarn install --frozen-lockfile
18+
- run: yarn build
19+
- run: yarn test
2020
- run: npx semantic-release --branches main
2121
env:
2222
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Diff for: .github/workflows/test.yml

+15-7
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,23 @@ on: push
55
jobs:
66
test:
77
runs-on: ubuntu-latest
8+
strategy:
9+
matrix:
10+
node-version:
11+
- 14
12+
- 16
13+
- 18
814
steps:
9-
- uses: actions/checkout@v2
10-
- uses: actions/setup-node@v2
15+
- uses: actions/checkout@v3
16+
- name: Use Node.js ${{ matrix.node-version }}
17+
uses: actions/setup-node@v3
1118
with:
12-
node-version: 'lts/*'
13-
- name: npm install, build, and test
19+
node-version: ${{ matrix.node-version }}
20+
- name: yarn install, build, and test
1421
run: |
15-
npm ci
16-
npm run build --if-present
17-
npm test
22+
yarn --frozen-lockfile
23+
yarn build --if-present
24+
yarn lint
25+
yarn test
1826
env:
1927
CI: true

Diff for: bin/json-schema-to-openapi-schema.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
'use strict';
33

44
const yargs = require('yargs');
5-
const chalk = require('chalk');
65
const converter = require('../dist/cjs/index.js').default;
76
const helpText = require('./help-text.json');
87
const fs = require('fs');
@@ -103,6 +102,6 @@ function getHelpText(commandName) {
103102
*/
104103
function errorHandler(err) {
105104
let errorMessage = process.env.DEBUG ? err.stack : err.message;
106-
console.error(chalk.red(errorMessage));
105+
console.error(errorMessage);
107106
process.exit(1);
108107
}

Diff for: package.json

+11-9
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,23 @@
55
"bin": {
66
"json-schema-to-openapi-schema": "bin/json-schema-to-openapi-schema.js"
77
},
8-
"types": "dist/mjs/src/index.d.ts",
8+
"types": "dist/mjs/index.d.ts",
99
"files": [
1010
"/dist"
1111
],
12-
"main": "dist/cjs/src/index.js",
13-
"module": "dist/mjs/src/index.js",
12+
"main": "dist/cjs/index.js",
13+
"module": "dist/mjs/index.js",
1414
"exports": {
1515
".": {
16-
"import": "./dist/mjs/src/index.js",
17-
"require": "./dist/cjs/src/index.js"
16+
"import": "./dist/mjs/index.js",
17+
"require": "./dist/cjs/index.js"
1818
}
1919
},
2020
"scripts": {
2121
"prepublish": "yarn build",
2222
"build": "rm -fr dist/* && tsc -p tsconfig.json && tsc -p tsconfig-cjs.json && node scripts/fixup.cjs",
23+
"lint": "eslint . && prettier -c src",
24+
"lint:fix": "eslint . --fix && prettier -c src -w",
2325
"typecheck": "tsc --noEmit",
2426
"test": "vitest",
2527
"coverage": "vitest --coverage"
@@ -28,27 +30,27 @@
2830
"author": "OpenAPI Contrib",
2931
"license": "MIT",
3032
"engines": {
31-
"node": ">=10"
33+
"node": ">=14"
3234
},
3335
"dependencies": {
3436
"@apidevtools/json-schema-ref-parser": "^9.0.9",
35-
"chalk": "^5.0.1",
3637
"json-schema-walker": "^0.0.4",
3738
"yargs": "^17.5.1"
3839
},
3940
"devDependencies": {
41+
"@types/json-schema": "^7.0.11",
4042
"@typescript-eslint/eslint-plugin": "^5.32.0",
4143
"@typescript-eslint/parser": "^5.32.0",
4244
"c8": "^7.12.0",
4345
"eslint": "^8.21.0",
4446
"eslint-config-prettier": "^8.5.0",
4547
"eslint-plugin-prettier": "^4.2.1",
4648
"eslint-plugin-unused-imports": "^2.0.0",
47-
"mocha": "^10.0.0",
4849
"nock": "^13.2.9",
50+
"openapi-typescript": "^5.4.1",
4951
"prettier": "^2.7.1",
5052
"typescript": "^4.7.4",
51-
"vitest": "^0.20.2"
53+
"vitest": "^0.20.3"
5254
},
5355
"prettier": {
5456
"singleQuote": true,

Diff for: src/const.ts

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// TODO: having definitions inside an oas3 schema isn't exactly valid,
2+
// maybe it is an idea to extract and split them into multiple oas3 schemas and reference to them.
3+
// For now leaving as is.
4+
export const allowedKeywords = [
5+
'$ref',
6+
'definitions',
7+
// From Schema
8+
'title',
9+
'multipleOf',
10+
'maximum',
11+
'exclusiveMaximum',
12+
'minimum',
13+
'exclusiveMinimum',
14+
'maxLength',
15+
'minLength',
16+
'pattern',
17+
'maxItems',
18+
'minItems',
19+
'uniqueItems',
20+
'maxProperties',
21+
'minProperties',
22+
'required',
23+
'enum',
24+
'type',
25+
'not',
26+
'allOf',
27+
'oneOf',
28+
'anyOf',
29+
'items',
30+
'properties',
31+
'additionalProperties',
32+
'description',
33+
'format',
34+
'default',
35+
'nullable',
36+
'discriminator',
37+
'readOnly',
38+
'writeOnly',
39+
'example',
40+
'externalDocs',
41+
'deprecated',
42+
'xml',
43+
];

Diff for: src/index.ts

+52-11
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import type {
55
JSONSchema7Definition,
66
} from 'json-schema';
77
import type { Options, SchemaType, SchemaTypeKeys } from './types';
8-
import { oas3schema } from './lib/openApiSchema';
98
import { Walker } from 'json-schema-walker';
9+
import { allowedKeywords } from './const';
10+
import type { OpenAPI3 } from 'openapi-typescript';
1011

1112
class InvalidTypeError extends Error {
1213
constructor(message: string) {
@@ -18,23 +19,63 @@ class InvalidTypeError extends Error {
1819

1920
const oasExtensionPrefix = 'x-';
2021

21-
// TODO: having definitions inside an oas3 schema isn't exactly valid,
22-
// maybe it is an idea to extract and split them into multiple oas3 schemas and reference to them.
23-
// For now leaving as is.
24-
const allowedKeywords = [
25-
'$ref',
26-
'definitions',
27-
...Object.keys(oas3schema.definitions.Schema.properties),
28-
];
22+
const handleDefinition = async <T>(
23+
def: JSONSchema7Definition | JSONSchema6Definition | JSONSchema4,
24+
schema: T
25+
) => {
26+
if (typeof def !== 'object') {
27+
return def;
28+
}
29+
30+
const type = def.type;
31+
if (type) {
32+
// Walk just the definitions types
33+
const walker = new Walker<T>();
34+
await walker.loadSchema({ ...def, $schema: schema['$schema'] } as any, {
35+
dereference: true,
36+
cloneSchema: true,
37+
dereferenceOptions: {
38+
dereference: {
39+
circular: 'ignore',
40+
},
41+
},
42+
});
43+
await walker.walk(convertSchema, walker.vocabularies.DRAFT_07);
44+
return walker.rootSchema;
45+
} else if (Array.isArray(def)) {
46+
// if it's an array, we might want to reconstruct the type;
47+
const typeArr = def;
48+
const hasNull = typeArr.includes('null');
49+
if (hasNull) {
50+
const actualTypes = typeArr.filter((l) => l !== 'null');
51+
return {
52+
type: actualTypes.length === 1 ? actualTypes[0] : actualTypes,
53+
nullable: true,
54+
// this is incorrect but thats ok, we are in the inbetween phase here
55+
} as JSONSchema7Definition | JSONSchema6Definition | JSONSchema4;
56+
}
57+
}
58+
59+
return def;
60+
};
2961

3062
const convert = async <T = JSONSchema>(
3163
schema: T,
3264
options?: Options
33-
): Promise<SchemaType> => {
65+
): Promise<OpenAPI3> => {
3466
const walker = new Walker<T>();
67+
const convertDefs = options?.convertUnreferencedDefinitions ?? true;
3568
await walker.loadSchema(schema, options);
3669
await walker.walk(convertSchema, walker.vocabularies.DRAFT_07);
37-
return walker.rootSchema;
70+
// if we want to convert unreferenced definitions, we need to do it iteratively here
71+
const rootSchema = walker.rootSchema as unknown as JSONSchema;
72+
if (convertDefs && rootSchema?.definitions) {
73+
for (const defName in rootSchema.definitions) {
74+
const def = rootSchema.definitions[defName];
75+
rootSchema.definitions[defName] = await handleDefinition(def, schema);
76+
}
77+
}
78+
return rootSchema as OpenAPI3;
3879
};
3980

4081
function stripIllegalKeywords(schema: SchemaType) {

0 commit comments

Comments
 (0)