ts-runtime-validation 1.0.5 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -5
- package/dist/SchemaGenerator.js +48 -58
- package/dist/SchemaGenerator.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/SchemaGenerator.ts +49 -62
- package/src/index.ts +2 -0
- package/tsconfig.json +2 -1
package/README.md
CHANGED
|
@@ -24,11 +24,12 @@ Ensure your project files containing the schemas you want to validate end with t
|
|
|
24
24
|
Usage: ts-runtime-validation [options]
|
|
25
25
|
|
|
26
26
|
Options:
|
|
27
|
-
--glob
|
|
28
|
-
--rootPath
|
|
29
|
-
--output
|
|
30
|
-
--no-helpers
|
|
31
|
-
|
|
27
|
+
--glob Glob file path of typescript files to generate ts-interface -> json-schema validations - default: *.jsonschema.{ts,tsx}
|
|
28
|
+
--rootPath RootPath of source - default: ./src
|
|
29
|
+
--output Validation schema + typescript interface output directory (relative to root path) - default: ./.ts-runtime-validation
|
|
30
|
+
--no-helpers Only generate JSON schema without typescript helper files
|
|
31
|
+
--additionalProperties Allow additional properties to pass validation (default: false)
|
|
32
|
+
-h, --help display help for command
|
|
32
33
|
```
|
|
33
34
|
|
|
34
35
|
## Example usage of generated ts type validation
|
package/dist/SchemaGenerator.js
CHANGED
|
@@ -37,13 +37,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
38
|
exports.SchemaGenerator = void 0;
|
|
39
39
|
const fdir_1 = require("fdir");
|
|
40
|
-
const path_1 = require("path");
|
|
41
|
-
const TJS = __importStar(require("typescript-json-schema"));
|
|
42
40
|
const fs_1 = __importDefault(require("fs"));
|
|
43
41
|
const picomatch_1 = __importDefault(require("picomatch"));
|
|
44
|
-
const
|
|
42
|
+
const path_1 = __importDefault(require("path"));
|
|
45
43
|
const ts_morph_1 = require("ts-morph");
|
|
46
|
-
const
|
|
44
|
+
const tsj = __importStar(require("ts-json-schema-generator"));
|
|
45
|
+
const defaultTsMorphProjectSettings = {
|
|
47
46
|
manipulationSettings: {
|
|
48
47
|
indentationText: ts_morph_1.IndentationText.FourSpaces,
|
|
49
48
|
newLineKind: ts_morph_1.NewLineKind.LineFeed,
|
|
@@ -60,10 +59,10 @@ const schemaDefinitionFileName = "SchemaDefinition.ts";
|
|
|
60
59
|
class SchemaGenerator {
|
|
61
60
|
constructor(options) {
|
|
62
61
|
this.options = options;
|
|
63
|
-
this.outputPath =
|
|
64
|
-
this.jsonSchemaOutputFile =
|
|
65
|
-
this.tsSchemaDefinitionOutputFile =
|
|
66
|
-
this.isValidSchemaOutputFile =
|
|
62
|
+
this.outputPath = path_1.default.join(this.options.rootPath, this.options.output);
|
|
63
|
+
this.jsonSchemaOutputFile = path_1.default.join(this.options.rootPath, this.options.output, validationSchemaFileName);
|
|
64
|
+
this.tsSchemaDefinitionOutputFile = path_1.default.join(this.options.rootPath, this.options.output, schemaDefinitionFileName);
|
|
65
|
+
this.isValidSchemaOutputFile = path_1.default.join(this.options.rootPath, this.options.output, "isSchemaValid.ts");
|
|
67
66
|
this.Generate = () => __awaiter(this, void 0, void 0, function* () {
|
|
68
67
|
const { helpers, glob } = this.options;
|
|
69
68
|
const fileList = yield this.getMatchingFiles();
|
|
@@ -72,18 +71,18 @@ class SchemaGenerator {
|
|
|
72
71
|
console.log(`Aborting - no files found with glob: ${glob}`);
|
|
73
72
|
return;
|
|
74
73
|
}
|
|
75
|
-
const
|
|
76
|
-
console.log(`Generating ${
|
|
77
|
-
if (
|
|
74
|
+
const fileSchemas = yield this.getJsonSchemaMap(fileList);
|
|
75
|
+
console.log(`Generating ${fileSchemas.size} validation schema(s)`);
|
|
76
|
+
if (fileSchemas.size === 0) {
|
|
78
77
|
console.log(`Aborting - no interfaces found: ${glob}`);
|
|
79
78
|
return;
|
|
80
79
|
}
|
|
81
|
-
this.writeSchemaMapToValidationSchema(
|
|
80
|
+
this.writeSchemaMapToValidationSchema(fileSchemas);
|
|
82
81
|
if (helpers === false) {
|
|
83
82
|
console.log("Skipping helper file generation");
|
|
84
83
|
return;
|
|
85
84
|
}
|
|
86
|
-
yield this.writeSchemaMapToValidationTypes(
|
|
85
|
+
yield this.writeSchemaMapToValidationTypes(fileSchemas);
|
|
87
86
|
this.writeValidatorFunction();
|
|
88
87
|
});
|
|
89
88
|
this.getMatchingFiles = () => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -100,33 +99,19 @@ class SchemaGenerator {
|
|
|
100
99
|
return api.withPromise();
|
|
101
100
|
});
|
|
102
101
|
this.getJsonSchemaMap = (filesList) => __awaiter(this, void 0, void 0, function* () {
|
|
103
|
-
|
|
102
|
+
const { additionalProperties } = this.options;
|
|
104
103
|
const schemaMap = new Map();
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const compilerOptions = {
|
|
117
|
-
strictNullChecks: true,
|
|
118
|
-
};
|
|
119
|
-
const program = TJS.getProgramFromFiles(files, compilerOptions);
|
|
120
|
-
const generator = TJS.buildGenerator(program, settings);
|
|
121
|
-
const userDefinedSymbols = (_a = generator === null || generator === void 0 ? void 0 : generator.getMainFileSymbols(program)) !== null && _a !== void 0 ? _a : [];
|
|
122
|
-
userDefinedSymbols.forEach((symbol) => {
|
|
123
|
-
if (schemaMap.has(symbol)) {
|
|
124
|
-
throw new Error(`Duplicate symbol "${symbol}" found.`);
|
|
125
|
-
}
|
|
126
|
-
const schema = generator === null || generator === void 0 ? void 0 : generator.getSchemaForSymbol(symbol);
|
|
127
|
-
if (schema) {
|
|
128
|
-
schemaMap.set(symbol, schema);
|
|
129
|
-
}
|
|
104
|
+
filesList.forEach((file) => {
|
|
105
|
+
const config = {
|
|
106
|
+
path: file,
|
|
107
|
+
type: "*",
|
|
108
|
+
additionalProperties,
|
|
109
|
+
encodeRefs: false,
|
|
110
|
+
topRef: true,
|
|
111
|
+
};
|
|
112
|
+
const schemaGenerator = tsj.createGenerator(config);
|
|
113
|
+
const fileSchemas = schemaGenerator.createSchema(config.type);
|
|
114
|
+
schemaMap.set(file, fileSchemas);
|
|
130
115
|
});
|
|
131
116
|
return schemaMap;
|
|
132
117
|
});
|
|
@@ -142,8 +127,16 @@ class SchemaGenerator {
|
|
|
142
127
|
};
|
|
143
128
|
this.writeSchemaMapToValidationSchema = (schemaMap) => {
|
|
144
129
|
const definitions = {};
|
|
145
|
-
schemaMap.forEach((
|
|
146
|
-
|
|
130
|
+
schemaMap.forEach((fileSchema) => {
|
|
131
|
+
var _a;
|
|
132
|
+
const defs = (_a = fileSchema.definitions) !== null && _a !== void 0 ? _a : {};
|
|
133
|
+
Object.keys(defs).forEach((key) => {
|
|
134
|
+
if (definitions[key] !== undefined) {
|
|
135
|
+
throw new Error(`Duplicate symbol: ${key} found`);
|
|
136
|
+
}
|
|
137
|
+
const schema = defs[key];
|
|
138
|
+
definitions[key] = schema;
|
|
139
|
+
});
|
|
147
140
|
});
|
|
148
141
|
const outputBuffer = {
|
|
149
142
|
$schema: this.getSchemaVersion(schemaMap),
|
|
@@ -152,26 +145,23 @@ class SchemaGenerator {
|
|
|
152
145
|
this.ensureOutputPathExists();
|
|
153
146
|
fs_1.default.writeFileSync(this.jsonSchemaOutputFile, JSON.stringify(outputBuffer, null, 4));
|
|
154
147
|
};
|
|
155
|
-
this.writeSchemaMapToValidationTypes = (schemaMap
|
|
156
|
-
const project = new ts_morph_1.Project(
|
|
157
|
-
const symbols =
|
|
158
|
-
return symbol !== "ISchema" && symbol !== "Schemas";
|
|
159
|
-
});
|
|
160
|
-
const readerProject = new ts_morph_1.Project();
|
|
161
|
-
readerProject.addSourceFilesAtPaths(fileList);
|
|
148
|
+
this.writeSchemaMapToValidationTypes = (schemaMap) => __awaiter(this, void 0, void 0, function* () {
|
|
149
|
+
const project = new ts_morph_1.Project(defaultTsMorphProjectSettings);
|
|
150
|
+
const symbols = [];
|
|
162
151
|
const importMap = new Map();
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
const
|
|
152
|
+
schemaMap.forEach((schema, filePath) => {
|
|
153
|
+
var _a;
|
|
154
|
+
const dir = path_1.default.dirname(filePath);
|
|
155
|
+
const fileWithoutExtension = path_1.default.parse(filePath).name;
|
|
156
|
+
const relativeFilePath = path_1.default.relative(this.outputPath, dir);
|
|
167
157
|
const importPath = `${relativeFilePath}/${fileWithoutExtension}`;
|
|
168
|
-
const
|
|
169
|
-
|
|
158
|
+
const defs = (_a = schema.definitions) !== null && _a !== void 0 ? _a : {};
|
|
159
|
+
Object.keys(defs).forEach((symbol) => {
|
|
170
160
|
var _a;
|
|
171
|
-
const structure = interfaceDeclaration.getStructure();
|
|
172
161
|
const namedImports = (_a = importMap.get(importPath)) !== null && _a !== void 0 ? _a : [];
|
|
173
|
-
namedImports.push(
|
|
162
|
+
namedImports.push(symbol);
|
|
174
163
|
importMap.set(importPath, namedImports);
|
|
164
|
+
symbols.push(symbol);
|
|
175
165
|
});
|
|
176
166
|
});
|
|
177
167
|
const sourceFile = project.createSourceFile(this.tsSchemaDefinitionOutputFile, {}, defaultCreateFileOptions);
|
|
@@ -208,13 +198,13 @@ class SchemaGenerator {
|
|
|
208
198
|
yield project.save();
|
|
209
199
|
});
|
|
210
200
|
this.writeValidatorFunction = () => __awaiter(this, void 0, void 0, function* () {
|
|
211
|
-
const project = new ts_morph_1.Project(
|
|
201
|
+
const project = new ts_morph_1.Project(defaultTsMorphProjectSettings);
|
|
212
202
|
const sourceFile = project.createSourceFile(this.isValidSchemaOutputFile, {}, defaultCreateFileOptions);
|
|
213
203
|
sourceFile.addImportDeclaration({ namespaceImport: "schema", moduleSpecifier: `./${validationSchemaFileName}` });
|
|
214
204
|
sourceFile.addImportDeclaration({ defaultImport: "Ajv", moduleSpecifier: "ajv" });
|
|
215
205
|
sourceFile.addImportDeclaration({
|
|
216
206
|
namedImports: ["ISchema", "schemas"],
|
|
217
|
-
moduleSpecifier: `./${
|
|
207
|
+
moduleSpecifier: `./${path_1.default.parse(schemaDefinitionFileName).name}`,
|
|
218
208
|
});
|
|
219
209
|
sourceFile.addVariableStatement({
|
|
220
210
|
declarationKind: ts_morph_1.VariableDeclarationKind.Const,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SchemaGenerator.js","sourceRoot":"","sources":["../src/SchemaGenerator.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+BAA4B;AAE5B
|
|
1
|
+
{"version":3,"file":"SchemaGenerator.js","sourceRoot":"","sources":["../src/SchemaGenerator.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ commander_1.program.option("--glob", `Glob file path of typescript files to gene
|
|
|
10
10
|
commander_1.program.option("--rootPath", `RootPath of source - default: ${defaultRootPath}`, defaultRootPath);
|
|
11
11
|
commander_1.program.option("--output", `Validation schema + typescript interface output directory (relative to root path) - default: ${defaultOutputFolder}`, defaultOutputFolder);
|
|
12
12
|
commander_1.program.option("--no-helpers", "Only generate JSON schema without typescript helper files", true);
|
|
13
|
+
commander_1.program.option("--additionalProperties", "Allow additional properties to pass validation", false);
|
|
13
14
|
commander_1.program.parse();
|
|
14
15
|
const options = commander_1.program.opts();
|
|
15
16
|
const generator = new SchemaGenerator_1.SchemaGenerator(options);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAEA,uDAAoD;AACpD,yCAAoC;AAEpC,MAAM,kBAAkB,GAAG,uBAAuB,CAAC;AAEnD,MAAM,eAAe,GAAG,OAAO,CAAC;AAChC,MAAM,mBAAmB,GAAG,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAEA,uDAAoD;AACpD,yCAAoC;AAEpC,MAAM,kBAAkB,GAAG,uBAAuB,CAAC;AAEnD,MAAM,eAAe,GAAG,OAAO,CAAC;AAChC,MAAM,mBAAmB,GAAG,0BAA0B,CAAC;AAUvD,mBAAO,CAAC,MAAM,CACV,QAAQ,EACR,qGAAqG,kBAAkB,EAAE,EACzH,kBAAkB,CACrB,CAAC;AACF,mBAAO,CAAC,MAAM,CAAC,YAAY,EAAE,iCAAiC,eAAe,EAAE,EAAE,eAAe,CAAC,CAAC;AAClG,mBAAO,CAAC,MAAM,CACV,UAAU,EACV,gGAAgG,mBAAmB,EAAE,EACrH,mBAAmB,CACtB,CAAC;AACF,mBAAO,CAAC,MAAM,CAAC,cAAc,EAAE,2DAA2D,EAAE,IAAI,CAAC,CAAC;AAClG,mBAAO,CAAC,MAAM,CAAC,wBAAwB,EAAE,gDAAgD,EAAE,KAAK,CAAC,CAAC;AAElG,mBAAO,CAAC,KAAK,EAAE,CAAC;AAEhB,MAAM,OAAO,GAAG,mBAAO,CAAC,IAAI,EAAmB,CAAC;AAEhD,MAAM,SAAS,GAAG,IAAI,iCAAe,CAAC,OAAO,CAAC,CAAC;AAC/C,SAAS,CAAC,QAAQ,EAAE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-runtime-validation",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"author": "Matthew Duong <thegalah@gmail.com>",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"commander": "^9.1.0",
|
|
8
8
|
"fdir": "^5.2.0",
|
|
9
9
|
"picomatch": "^2.3.1",
|
|
10
|
-
"ts-
|
|
11
|
-
"
|
|
10
|
+
"ts-json-schema-generator": "^1.0.0",
|
|
11
|
+
"ts-morph": "^14.0.0"
|
|
12
12
|
},
|
|
13
13
|
"bin": {
|
|
14
14
|
"ts-runtime-validation": "dist/index.js"
|
package/src/SchemaGenerator.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { fdir } from "fdir";
|
|
2
2
|
import { ICommandOptions } from "./index";
|
|
3
|
-
import { resolve } from "path";
|
|
4
|
-
import * as TJS from "typescript-json-schema";
|
|
5
3
|
import fs from "fs";
|
|
6
4
|
import picomatch from "picomatch";
|
|
7
5
|
import path from "path";
|
|
@@ -16,8 +14,10 @@ import {
|
|
|
16
14
|
ProjectOptions,
|
|
17
15
|
SourceFileCreateOptions,
|
|
18
16
|
} from "ts-morph";
|
|
17
|
+
import * as tsj from "ts-json-schema-generator";
|
|
18
|
+
import { Config, Schema } from "ts-json-schema-generator";
|
|
19
19
|
|
|
20
|
-
const
|
|
20
|
+
const defaultTsMorphProjectSettings: ProjectOptions = {
|
|
21
21
|
manipulationSettings: {
|
|
22
22
|
indentationText: IndentationText.FourSpaces,
|
|
23
23
|
newLineKind: NewLineKind.LineFeed,
|
|
@@ -51,18 +51,18 @@ export class SchemaGenerator {
|
|
|
51
51
|
console.log(`Aborting - no files found with glob: ${glob}`);
|
|
52
52
|
return;
|
|
53
53
|
}
|
|
54
|
-
const
|
|
55
|
-
console.log(`Generating ${
|
|
56
|
-
if (
|
|
54
|
+
const fileSchemas = await this.getJsonSchemaMap(fileList);
|
|
55
|
+
console.log(`Generating ${fileSchemas.size} validation schema(s)`);
|
|
56
|
+
if (fileSchemas.size === 0) {
|
|
57
57
|
console.log(`Aborting - no interfaces found: ${glob}`);
|
|
58
58
|
return;
|
|
59
59
|
}
|
|
60
|
-
this.writeSchemaMapToValidationSchema(
|
|
60
|
+
this.writeSchemaMapToValidationSchema(fileSchemas);
|
|
61
61
|
if (helpers === false) {
|
|
62
62
|
console.log("Skipping helper file generation");
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
65
|
-
await this.writeSchemaMapToValidationTypes(
|
|
65
|
+
await this.writeSchemaMapToValidationTypes(fileSchemas);
|
|
66
66
|
this.writeValidatorFunction();
|
|
67
67
|
};
|
|
68
68
|
|
|
@@ -81,41 +81,25 @@ export class SchemaGenerator {
|
|
|
81
81
|
};
|
|
82
82
|
|
|
83
83
|
private getJsonSchemaMap = async (filesList: Array<string>) => {
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
|
|
84
|
+
const { additionalProperties } = this.options;
|
|
85
|
+
const schemaMap = new Map<string, Schema>();
|
|
86
|
+
filesList.forEach((file) => {
|
|
87
|
+
const config: Config = {
|
|
88
|
+
path: file,
|
|
89
|
+
type: "*",
|
|
90
|
+
additionalProperties,
|
|
91
|
+
encodeRefs: false,
|
|
92
|
+
topRef: true,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const schemaGenerator = tsj.createGenerator(config);
|
|
96
|
+
const fileSchemas = schemaGenerator.createSchema(config.type);
|
|
97
|
+
schemaMap.set(file, fileSchemas);
|
|
87
98
|
});
|
|
88
|
-
const settings: TJS.PartialArgs = {
|
|
89
|
-
required: true,
|
|
90
|
-
titles: true,
|
|
91
|
-
aliasRef: true,
|
|
92
|
-
ref: true,
|
|
93
|
-
noExtraProps: true,
|
|
94
|
-
propOrder: true,
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const compilerOptions: TJS.CompilerOptions = {
|
|
98
|
-
strictNullChecks: true,
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
const program = TJS.getProgramFromFiles(files, compilerOptions);
|
|
102
|
-
|
|
103
|
-
const generator = TJS.buildGenerator(program, settings);
|
|
104
|
-
const userDefinedSymbols = generator?.getMainFileSymbols(program) ?? [];
|
|
105
|
-
userDefinedSymbols.forEach((symbol) => {
|
|
106
|
-
if (schemaMap.has(symbol)) {
|
|
107
|
-
throw new Error(`Duplicate symbol "${symbol}" found.`);
|
|
108
|
-
}
|
|
109
|
-
const schema = generator?.getSchemaForSymbol(symbol);
|
|
110
|
-
if (schema) {
|
|
111
|
-
schemaMap.set(symbol, schema);
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
|
|
115
99
|
return schemaMap;
|
|
116
100
|
};
|
|
117
101
|
|
|
118
|
-
private getSchemaVersion = (schemaMap: Map<string,
|
|
102
|
+
private getSchemaVersion = (schemaMap: Map<string, Schema>) => {
|
|
119
103
|
const firstEntry = schemaMap.values().next().value;
|
|
120
104
|
return firstEntry["$schema"] ?? "";
|
|
121
105
|
};
|
|
@@ -126,12 +110,20 @@ export class SchemaGenerator {
|
|
|
126
110
|
}
|
|
127
111
|
};
|
|
128
112
|
|
|
129
|
-
private writeSchemaMapToValidationSchema = (schemaMap: Map<string,
|
|
130
|
-
const definitions: { [id: string]:
|
|
131
|
-
schemaMap.forEach((
|
|
132
|
-
|
|
113
|
+
private writeSchemaMapToValidationSchema = (schemaMap: Map<string, Schema>) => {
|
|
114
|
+
const definitions: { [id: string]: Schema } = {};
|
|
115
|
+
schemaMap.forEach((fileSchema) => {
|
|
116
|
+
const defs = fileSchema.definitions ?? {};
|
|
117
|
+
|
|
118
|
+
Object.keys(defs).forEach((key) => {
|
|
119
|
+
if (definitions[key] !== undefined) {
|
|
120
|
+
throw new Error(`Duplicate symbol: ${key} found`);
|
|
121
|
+
}
|
|
122
|
+
const schema = defs[key] as Schema;
|
|
123
|
+
definitions[key] = schema;
|
|
124
|
+
});
|
|
133
125
|
});
|
|
134
|
-
const outputBuffer:
|
|
126
|
+
const outputBuffer: Schema = {
|
|
135
127
|
$schema: this.getSchemaVersion(schemaMap),
|
|
136
128
|
definitions,
|
|
137
129
|
};
|
|
@@ -140,32 +132,29 @@ export class SchemaGenerator {
|
|
|
140
132
|
fs.writeFileSync(this.jsonSchemaOutputFile, JSON.stringify(outputBuffer, null, 4));
|
|
141
133
|
};
|
|
142
134
|
|
|
143
|
-
private writeSchemaMapToValidationTypes = async (schemaMap: Map<string,
|
|
144
|
-
const project = new Project(
|
|
135
|
+
private writeSchemaMapToValidationTypes = async (schemaMap: Map<string, Schema>) => {
|
|
136
|
+
const project = new Project(defaultTsMorphProjectSettings);
|
|
145
137
|
|
|
146
|
-
const symbols
|
|
147
|
-
return symbol !== "ISchema" && symbol !== "Schemas";
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
const readerProject = new Project();
|
|
151
|
-
readerProject.addSourceFilesAtPaths(fileList);
|
|
138
|
+
const symbols: Array<string> = [];
|
|
152
139
|
|
|
153
140
|
const importMap = new Map<string, Array<string>>();
|
|
154
|
-
|
|
155
|
-
const dir = path.dirname(
|
|
156
|
-
const fileWithoutExtension = path.parse(
|
|
141
|
+
schemaMap.forEach((schema, filePath) => {
|
|
142
|
+
const dir = path.dirname(filePath);
|
|
143
|
+
const fileWithoutExtension = path.parse(filePath).name;
|
|
157
144
|
const relativeFilePath = path.relative(this.outputPath, dir);
|
|
158
145
|
const importPath = `${relativeFilePath}/${fileWithoutExtension}`;
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
146
|
+
const defs = schema.definitions ?? {};
|
|
147
|
+
|
|
148
|
+
Object.keys(defs).forEach((symbol) => {
|
|
162
149
|
const namedImports = importMap.get(importPath) ?? [];
|
|
163
|
-
namedImports.push(
|
|
150
|
+
namedImports.push(symbol);
|
|
164
151
|
importMap.set(importPath, namedImports);
|
|
152
|
+
symbols.push(symbol);
|
|
165
153
|
});
|
|
166
154
|
});
|
|
167
155
|
|
|
168
156
|
const sourceFile = project.createSourceFile(this.tsSchemaDefinitionOutputFile, {}, defaultCreateFileOptions);
|
|
157
|
+
|
|
169
158
|
importMap.forEach((namedImports, importPath) => {
|
|
170
159
|
sourceFile.addImportDeclaration({ namedImports, moduleSpecifier: importPath });
|
|
171
160
|
});
|
|
@@ -199,12 +188,11 @@ export class SchemaGenerator {
|
|
|
199
188
|
sourceFile.addExportDeclaration({
|
|
200
189
|
namedExports: ["schemas", "ISchema"],
|
|
201
190
|
});
|
|
202
|
-
|
|
203
191
|
await project.save();
|
|
204
192
|
};
|
|
205
193
|
|
|
206
194
|
private writeValidatorFunction = async () => {
|
|
207
|
-
const project = new Project(
|
|
195
|
+
const project = new Project(defaultTsMorphProjectSettings);
|
|
208
196
|
const sourceFile = project.createSourceFile(this.isValidSchemaOutputFile, {}, defaultCreateFileOptions);
|
|
209
197
|
sourceFile.addImportDeclaration({ namespaceImport: "schema", moduleSpecifier: `./${validationSchemaFileName}` });
|
|
210
198
|
sourceFile.addImportDeclaration({ defaultImport: "Ajv", moduleSpecifier: "ajv" });
|
|
@@ -212,7 +200,6 @@ export class SchemaGenerator {
|
|
|
212
200
|
namedImports: ["ISchema", "schemas"],
|
|
213
201
|
moduleSpecifier: `./${path.parse(schemaDefinitionFileName).name}`,
|
|
214
202
|
});
|
|
215
|
-
|
|
216
203
|
sourceFile.addVariableStatement({
|
|
217
204
|
declarationKind: VariableDeclarationKind.Const,
|
|
218
205
|
declarations: [
|
package/src/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ export interface ICommandOptions {
|
|
|
13
13
|
readonly rootPath: string;
|
|
14
14
|
readonly output: string;
|
|
15
15
|
readonly helpers: boolean;
|
|
16
|
+
readonly additionalProperties: boolean;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
program.option(
|
|
@@ -27,6 +28,7 @@ program.option(
|
|
|
27
28
|
defaultOutputFolder
|
|
28
29
|
);
|
|
29
30
|
program.option("--no-helpers", "Only generate JSON schema without typescript helper files", true);
|
|
31
|
+
program.option("--additionalProperties", "Allow additional properties to pass validation", false);
|
|
30
32
|
|
|
31
33
|
program.parse();
|
|
32
34
|
|