ts-runtime-validation 1.0.5 → 1.1.0
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 +46 -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 -2
- package/src/SchemaGenerator.ts +47 -61
- package/src/index.ts +2 -0
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,17 @@ 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
|
-
propOrder: true,
|
|
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
|
+
};
|
|
110
|
+
const schemaGenerator = tsj.createGenerator(config);
|
|
111
|
+
const fileSchemas = schemaGenerator.createSchema(config.type);
|
|
112
|
+
schemaMap.set(file, fileSchemas);
|
|
130
113
|
});
|
|
131
114
|
return schemaMap;
|
|
132
115
|
});
|
|
@@ -142,8 +125,16 @@ class SchemaGenerator {
|
|
|
142
125
|
};
|
|
143
126
|
this.writeSchemaMapToValidationSchema = (schemaMap) => {
|
|
144
127
|
const definitions = {};
|
|
145
|
-
schemaMap.forEach((
|
|
146
|
-
|
|
128
|
+
schemaMap.forEach((fileSchema) => {
|
|
129
|
+
var _a;
|
|
130
|
+
const defs = (_a = fileSchema.definitions) !== null && _a !== void 0 ? _a : {};
|
|
131
|
+
Object.keys(defs).forEach((key) => {
|
|
132
|
+
if (definitions[key] !== undefined) {
|
|
133
|
+
throw new Error(`Duplicate symbol: ${key} found`);
|
|
134
|
+
}
|
|
135
|
+
const schema = defs[key];
|
|
136
|
+
definitions[key] = schema;
|
|
137
|
+
});
|
|
147
138
|
});
|
|
148
139
|
const outputBuffer = {
|
|
149
140
|
$schema: this.getSchemaVersion(schemaMap),
|
|
@@ -152,26 +143,23 @@ class SchemaGenerator {
|
|
|
152
143
|
this.ensureOutputPathExists();
|
|
153
144
|
fs_1.default.writeFileSync(this.jsonSchemaOutputFile, JSON.stringify(outputBuffer, null, 4));
|
|
154
145
|
};
|
|
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);
|
|
146
|
+
this.writeSchemaMapToValidationTypes = (schemaMap) => __awaiter(this, void 0, void 0, function* () {
|
|
147
|
+
const project = new ts_morph_1.Project(defaultTsMorphProjectSettings);
|
|
148
|
+
const symbols = [];
|
|
162
149
|
const importMap = new Map();
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
const
|
|
150
|
+
schemaMap.forEach((schema, filePath) => {
|
|
151
|
+
var _a;
|
|
152
|
+
const dir = path_1.default.dirname(filePath);
|
|
153
|
+
const fileWithoutExtension = path_1.default.parse(filePath).name;
|
|
154
|
+
const relativeFilePath = path_1.default.relative(this.outputPath, dir);
|
|
167
155
|
const importPath = `${relativeFilePath}/${fileWithoutExtension}`;
|
|
168
|
-
const
|
|
169
|
-
|
|
156
|
+
const defs = (_a = schema.definitions) !== null && _a !== void 0 ? _a : {};
|
|
157
|
+
Object.keys(defs).forEach((symbol) => {
|
|
170
158
|
var _a;
|
|
171
|
-
const structure = interfaceDeclaration.getStructure();
|
|
172
159
|
const namedImports = (_a = importMap.get(importPath)) !== null && _a !== void 0 ? _a : [];
|
|
173
|
-
namedImports.push(
|
|
160
|
+
namedImports.push(symbol);
|
|
174
161
|
importMap.set(importPath, namedImports);
|
|
162
|
+
symbols.push(symbol);
|
|
175
163
|
});
|
|
176
164
|
});
|
|
177
165
|
const sourceFile = project.createSourceFile(this.tsSchemaDefinitionOutputFile, {}, defaultCreateFileOptions);
|
|
@@ -208,13 +196,13 @@ class SchemaGenerator {
|
|
|
208
196
|
yield project.save();
|
|
209
197
|
});
|
|
210
198
|
this.writeValidatorFunction = () => __awaiter(this, void 0, void 0, function* () {
|
|
211
|
-
const project = new ts_morph_1.Project(
|
|
199
|
+
const project = new ts_morph_1.Project(defaultTsMorphProjectSettings);
|
|
212
200
|
const sourceFile = project.createSourceFile(this.isValidSchemaOutputFile, {}, defaultCreateFileOptions);
|
|
213
201
|
sourceFile.addImportDeclaration({ namespaceImport: "schema", moduleSpecifier: `./${validationSchemaFileName}` });
|
|
214
202
|
sourceFile.addImportDeclaration({ defaultImport: "Ajv", moduleSpecifier: "ajv" });
|
|
215
203
|
sourceFile.addImportDeclaration({
|
|
216
204
|
namedImports: ["ISchema", "schemas"],
|
|
217
|
-
moduleSpecifier: `./${
|
|
205
|
+
moduleSpecifier: `./${path_1.default.parse(schemaDefinitionFileName).name}`,
|
|
218
206
|
});
|
|
219
207
|
sourceFile.addVariableStatement({
|
|
220
208
|
declarationKind: ts_morph_1.VariableDeclarationKind.Const,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SchemaGenerator.js","sourceRoot":"","sources":["../src/SchemaGenerator.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+BAA4B;
|
|
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,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-runtime-validation",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
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-json-schema-generator": "^1.0.0",
|
|
10
11
|
"ts-morph": "^14.0.0",
|
|
11
|
-
"
|
|
12
|
+
"ts-runtime-validation": "../ts-runtime-validation"
|
|
12
13
|
},
|
|
13
14
|
"bin": {
|
|
14
15
|
"ts-runtime-validation": "dist/index.js"
|
package/src/SchemaGenerator.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { fdir } from "fdir";
|
|
2
2
|
import { ICommandOptions } from "./index";
|
|
3
3
|
import { resolve } from "path";
|
|
4
|
-
import * as TJS from "typescript-json-schema";
|
|
5
4
|
import fs from "fs";
|
|
6
5
|
import picomatch from "picomatch";
|
|
7
6
|
import path from "path";
|
|
@@ -16,8 +15,10 @@ import {
|
|
|
16
15
|
ProjectOptions,
|
|
17
16
|
SourceFileCreateOptions,
|
|
18
17
|
} from "ts-morph";
|
|
18
|
+
import * as tsj from "ts-json-schema-generator";
|
|
19
|
+
import { Config, Schema } from "ts-json-schema-generator";
|
|
19
20
|
|
|
20
|
-
const
|
|
21
|
+
const defaultTsMorphProjectSettings: ProjectOptions = {
|
|
21
22
|
manipulationSettings: {
|
|
22
23
|
indentationText: IndentationText.FourSpaces,
|
|
23
24
|
newLineKind: NewLineKind.LineFeed,
|
|
@@ -51,18 +52,18 @@ export class SchemaGenerator {
|
|
|
51
52
|
console.log(`Aborting - no files found with glob: ${glob}`);
|
|
52
53
|
return;
|
|
53
54
|
}
|
|
54
|
-
const
|
|
55
|
-
console.log(`Generating ${
|
|
56
|
-
if (
|
|
55
|
+
const fileSchemas = await this.getJsonSchemaMap(fileList);
|
|
56
|
+
console.log(`Generating ${fileSchemas.size} validation schema(s)`);
|
|
57
|
+
if (fileSchemas.size === 0) {
|
|
57
58
|
console.log(`Aborting - no interfaces found: ${glob}`);
|
|
58
59
|
return;
|
|
59
60
|
}
|
|
60
|
-
this.writeSchemaMapToValidationSchema(
|
|
61
|
+
this.writeSchemaMapToValidationSchema(fileSchemas);
|
|
61
62
|
if (helpers === false) {
|
|
62
63
|
console.log("Skipping helper file generation");
|
|
63
64
|
return;
|
|
64
65
|
}
|
|
65
|
-
await this.writeSchemaMapToValidationTypes(
|
|
66
|
+
await this.writeSchemaMapToValidationTypes(fileSchemas);
|
|
66
67
|
this.writeValidatorFunction();
|
|
67
68
|
};
|
|
68
69
|
|
|
@@ -81,41 +82,23 @@ export class SchemaGenerator {
|
|
|
81
82
|
};
|
|
82
83
|
|
|
83
84
|
private getJsonSchemaMap = async (filesList: Array<string>) => {
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
|
|
85
|
+
const { additionalProperties } = this.options;
|
|
86
|
+
const schemaMap = new Map<string, Schema>();
|
|
87
|
+
filesList.forEach((file) => {
|
|
88
|
+
const config: Config = {
|
|
89
|
+
path: file,
|
|
90
|
+
type: "*",
|
|
91
|
+
additionalProperties,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const schemaGenerator = tsj.createGenerator(config);
|
|
95
|
+
const fileSchemas = schemaGenerator.createSchema(config.type);
|
|
96
|
+
schemaMap.set(file, fileSchemas);
|
|
87
97
|
});
|
|
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
98
|
return schemaMap;
|
|
116
99
|
};
|
|
117
100
|
|
|
118
|
-
private getSchemaVersion = (schemaMap: Map<string,
|
|
101
|
+
private getSchemaVersion = (schemaMap: Map<string, Schema>) => {
|
|
119
102
|
const firstEntry = schemaMap.values().next().value;
|
|
120
103
|
return firstEntry["$schema"] ?? "";
|
|
121
104
|
};
|
|
@@ -126,12 +109,20 @@ export class SchemaGenerator {
|
|
|
126
109
|
}
|
|
127
110
|
};
|
|
128
111
|
|
|
129
|
-
private writeSchemaMapToValidationSchema = (schemaMap: Map<string,
|
|
130
|
-
const definitions: { [id: string]:
|
|
131
|
-
schemaMap.forEach((
|
|
132
|
-
|
|
112
|
+
private writeSchemaMapToValidationSchema = (schemaMap: Map<string, Schema>) => {
|
|
113
|
+
const definitions: { [id: string]: Schema } = {};
|
|
114
|
+
schemaMap.forEach((fileSchema) => {
|
|
115
|
+
const defs = fileSchema.definitions ?? {};
|
|
116
|
+
|
|
117
|
+
Object.keys(defs).forEach((key) => {
|
|
118
|
+
if (definitions[key] !== undefined) {
|
|
119
|
+
throw new Error(`Duplicate symbol: ${key} found`);
|
|
120
|
+
}
|
|
121
|
+
const schema = defs[key] as Schema;
|
|
122
|
+
definitions[key] = schema;
|
|
123
|
+
});
|
|
133
124
|
});
|
|
134
|
-
const outputBuffer:
|
|
125
|
+
const outputBuffer: Schema = {
|
|
135
126
|
$schema: this.getSchemaVersion(schemaMap),
|
|
136
127
|
definitions,
|
|
137
128
|
};
|
|
@@ -140,32 +131,29 @@ export class SchemaGenerator {
|
|
|
140
131
|
fs.writeFileSync(this.jsonSchemaOutputFile, JSON.stringify(outputBuffer, null, 4));
|
|
141
132
|
};
|
|
142
133
|
|
|
143
|
-
private writeSchemaMapToValidationTypes = async (schemaMap: Map<string,
|
|
144
|
-
const project = new Project(
|
|
134
|
+
private writeSchemaMapToValidationTypes = async (schemaMap: Map<string, Schema>) => {
|
|
135
|
+
const project = new Project(defaultTsMorphProjectSettings);
|
|
145
136
|
|
|
146
|
-
const symbols
|
|
147
|
-
return symbol !== "ISchema" && symbol !== "Schemas";
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
const readerProject = new Project();
|
|
151
|
-
readerProject.addSourceFilesAtPaths(fileList);
|
|
137
|
+
const symbols: Array<string> = [];
|
|
152
138
|
|
|
153
139
|
const importMap = new Map<string, Array<string>>();
|
|
154
|
-
|
|
155
|
-
const dir = path.dirname(
|
|
156
|
-
const fileWithoutExtension = path.parse(
|
|
140
|
+
schemaMap.forEach((schema, filePath) => {
|
|
141
|
+
const dir = path.dirname(filePath);
|
|
142
|
+
const fileWithoutExtension = path.parse(filePath).name;
|
|
157
143
|
const relativeFilePath = path.relative(this.outputPath, dir);
|
|
158
144
|
const importPath = `${relativeFilePath}/${fileWithoutExtension}`;
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
145
|
+
const defs = schema.definitions ?? {};
|
|
146
|
+
|
|
147
|
+
Object.keys(defs).forEach((symbol) => {
|
|
162
148
|
const namedImports = importMap.get(importPath) ?? [];
|
|
163
|
-
namedImports.push(
|
|
149
|
+
namedImports.push(symbol);
|
|
164
150
|
importMap.set(importPath, namedImports);
|
|
151
|
+
symbols.push(symbol);
|
|
165
152
|
});
|
|
166
153
|
});
|
|
167
154
|
|
|
168
155
|
const sourceFile = project.createSourceFile(this.tsSchemaDefinitionOutputFile, {}, defaultCreateFileOptions);
|
|
156
|
+
|
|
169
157
|
importMap.forEach((namedImports, importPath) => {
|
|
170
158
|
sourceFile.addImportDeclaration({ namedImports, moduleSpecifier: importPath });
|
|
171
159
|
});
|
|
@@ -199,12 +187,11 @@ export class SchemaGenerator {
|
|
|
199
187
|
sourceFile.addExportDeclaration({
|
|
200
188
|
namedExports: ["schemas", "ISchema"],
|
|
201
189
|
});
|
|
202
|
-
|
|
203
190
|
await project.save();
|
|
204
191
|
};
|
|
205
192
|
|
|
206
193
|
private writeValidatorFunction = async () => {
|
|
207
|
-
const project = new Project(
|
|
194
|
+
const project = new Project(defaultTsMorphProjectSettings);
|
|
208
195
|
const sourceFile = project.createSourceFile(this.isValidSchemaOutputFile, {}, defaultCreateFileOptions);
|
|
209
196
|
sourceFile.addImportDeclaration({ namespaceImport: "schema", moduleSpecifier: `./${validationSchemaFileName}` });
|
|
210
197
|
sourceFile.addImportDeclaration({ defaultImport: "Ajv", moduleSpecifier: "ajv" });
|
|
@@ -212,7 +199,6 @@ export class SchemaGenerator {
|
|
|
212
199
|
namedImports: ["ISchema", "schemas"],
|
|
213
200
|
moduleSpecifier: `./${path.parse(schemaDefinitionFileName).name}`,
|
|
214
201
|
});
|
|
215
|
-
|
|
216
202
|
sourceFile.addVariableStatement({
|
|
217
203
|
declarationKind: VariableDeclarationKind.Const,
|
|
218
204
|
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
|
|