ts-runtime-validation 1.0.3 → 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 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 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
- -h, --help display help for command
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
@@ -0,0 +1,241 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
+ return new (P || (P = Promise))(function (resolve, reject) {
28
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
32
+ });
33
+ };
34
+ var __importDefault = (this && this.__importDefault) || function (mod) {
35
+ return (mod && mod.__esModule) ? mod : { "default": mod };
36
+ };
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.SchemaGenerator = void 0;
39
+ const fdir_1 = require("fdir");
40
+ const fs_1 = __importDefault(require("fs"));
41
+ const picomatch_1 = __importDefault(require("picomatch"));
42
+ const path_1 = __importDefault(require("path"));
43
+ const ts_morph_1 = require("ts-morph");
44
+ const tsj = __importStar(require("ts-json-schema-generator"));
45
+ const defaultTsMorphProjectSettings = {
46
+ manipulationSettings: {
47
+ indentationText: ts_morph_1.IndentationText.FourSpaces,
48
+ newLineKind: ts_morph_1.NewLineKind.LineFeed,
49
+ quoteKind: ts_morph_1.QuoteKind.Double,
50
+ usePrefixAndSuffixTextForRename: false,
51
+ useTrailingCommas: true,
52
+ },
53
+ };
54
+ const defaultCreateFileOptions = {
55
+ overwrite: true,
56
+ };
57
+ const validationSchemaFileName = "validation.schema.json";
58
+ const schemaDefinitionFileName = "SchemaDefinition.ts";
59
+ class SchemaGenerator {
60
+ constructor(options) {
61
+ this.options = options;
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");
66
+ this.Generate = () => __awaiter(this, void 0, void 0, function* () {
67
+ const { helpers, glob } = this.options;
68
+ const fileList = yield this.getMatchingFiles();
69
+ console.log(`Found ${fileList.length} schema file(s)`);
70
+ if (fileList.length === 0) {
71
+ console.log(`Aborting - no files found with glob: ${glob}`);
72
+ return;
73
+ }
74
+ const fileSchemas = yield this.getJsonSchemaMap(fileList);
75
+ console.log(`Generating ${fileSchemas.size} validation schema(s)`);
76
+ if (fileSchemas.size === 0) {
77
+ console.log(`Aborting - no interfaces found: ${glob}`);
78
+ return;
79
+ }
80
+ this.writeSchemaMapToValidationSchema(fileSchemas);
81
+ if (helpers === false) {
82
+ console.log("Skipping helper file generation");
83
+ return;
84
+ }
85
+ yield this.writeSchemaMapToValidationTypes(fileSchemas);
86
+ this.writeValidatorFunction();
87
+ });
88
+ this.getMatchingFiles = () => __awaiter(this, void 0, void 0, function* () {
89
+ const { glob, rootPath } = this.options;
90
+ const api = new fdir_1.fdir().crawlWithOptions(rootPath, {
91
+ includeBasePath: true,
92
+ includeDirs: false,
93
+ filters: [
94
+ (path) => {
95
+ return picomatch_1.default.isMatch(path, glob, { contains: true });
96
+ },
97
+ ],
98
+ });
99
+ return api.withPromise();
100
+ });
101
+ this.getJsonSchemaMap = (filesList) => __awaiter(this, void 0, void 0, function* () {
102
+ const { additionalProperties } = this.options;
103
+ const schemaMap = new Map();
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);
113
+ });
114
+ return schemaMap;
115
+ });
116
+ this.getSchemaVersion = (schemaMap) => {
117
+ var _a;
118
+ const firstEntry = schemaMap.values().next().value;
119
+ return (_a = firstEntry["$schema"]) !== null && _a !== void 0 ? _a : "";
120
+ };
121
+ this.ensureOutputPathExists = () => {
122
+ if (!fs_1.default.existsSync(this.outputPath)) {
123
+ fs_1.default.mkdirSync(this.outputPath, { recursive: true });
124
+ }
125
+ };
126
+ this.writeSchemaMapToValidationSchema = (schemaMap) => {
127
+ const definitions = {};
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
+ });
138
+ });
139
+ const outputBuffer = {
140
+ $schema: this.getSchemaVersion(schemaMap),
141
+ definitions,
142
+ };
143
+ this.ensureOutputPathExists();
144
+ fs_1.default.writeFileSync(this.jsonSchemaOutputFile, JSON.stringify(outputBuffer, null, 4));
145
+ };
146
+ this.writeSchemaMapToValidationTypes = (schemaMap) => __awaiter(this, void 0, void 0, function* () {
147
+ const project = new ts_morph_1.Project(defaultTsMorphProjectSettings);
148
+ const symbols = [];
149
+ const importMap = new Map();
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);
155
+ const importPath = `${relativeFilePath}/${fileWithoutExtension}`;
156
+ const defs = (_a = schema.definitions) !== null && _a !== void 0 ? _a : {};
157
+ Object.keys(defs).forEach((symbol) => {
158
+ var _a;
159
+ const namedImports = (_a = importMap.get(importPath)) !== null && _a !== void 0 ? _a : [];
160
+ namedImports.push(symbol);
161
+ importMap.set(importPath, namedImports);
162
+ symbols.push(symbol);
163
+ });
164
+ });
165
+ const sourceFile = project.createSourceFile(this.tsSchemaDefinitionOutputFile, {}, defaultCreateFileOptions);
166
+ importMap.forEach((namedImports, importPath) => {
167
+ sourceFile.addImportDeclaration({ namedImports, moduleSpecifier: importPath });
168
+ });
169
+ sourceFile.addVariableStatement({
170
+ declarationKind: ts_morph_1.VariableDeclarationKind.Const,
171
+ declarations: [
172
+ {
173
+ name: "schemas",
174
+ type: "Record<keyof ISchema, string>",
175
+ initializer: (writer) => {
176
+ writer.writeLine(`{`);
177
+ symbols.forEach((symbol) => {
178
+ writer.writeLine(`["#/definitions/${symbol}"] : "${symbol}",`);
179
+ }),
180
+ writer.writeLine(`}`);
181
+ },
182
+ },
183
+ ],
184
+ });
185
+ sourceFile.addInterface({
186
+ kind: ts_morph_1.StructureKind.Interface,
187
+ name: "ISchema",
188
+ isExported: false,
189
+ properties: symbols.map((symbol) => {
190
+ return { name: `readonly ["#/definitions/${symbol}"]`, type: symbol };
191
+ }),
192
+ });
193
+ sourceFile.addExportDeclaration({
194
+ namedExports: ["schemas", "ISchema"],
195
+ });
196
+ yield project.save();
197
+ });
198
+ this.writeValidatorFunction = () => __awaiter(this, void 0, void 0, function* () {
199
+ const project = new ts_morph_1.Project(defaultTsMorphProjectSettings);
200
+ const sourceFile = project.createSourceFile(this.isValidSchemaOutputFile, {}, defaultCreateFileOptions);
201
+ sourceFile.addImportDeclaration({ namespaceImport: "schema", moduleSpecifier: `./${validationSchemaFileName}` });
202
+ sourceFile.addImportDeclaration({ defaultImport: "Ajv", moduleSpecifier: "ajv" });
203
+ sourceFile.addImportDeclaration({
204
+ namedImports: ["ISchema", "schemas"],
205
+ moduleSpecifier: `./${path_1.default.parse(schemaDefinitionFileName).name}`,
206
+ });
207
+ sourceFile.addVariableStatement({
208
+ declarationKind: ts_morph_1.VariableDeclarationKind.Const,
209
+ declarations: [
210
+ {
211
+ name: "validator",
212
+ initializer: (writer) => {
213
+ writer.writeLine(`new Ajv({ allErrors: true });`);
214
+ writer.writeLine(`validator.compile(schema)`);
215
+ },
216
+ },
217
+ ],
218
+ });
219
+ sourceFile.addVariableStatement({
220
+ declarationKind: ts_morph_1.VariableDeclarationKind.Const,
221
+ declarations: [
222
+ {
223
+ name: "isValidSchema",
224
+ initializer: (writer) => {
225
+ writer.writeLine(`<T extends keyof typeof schemas>(data: unknown, schemaKeyRef: T): data is ISchema[T] => {`);
226
+ writer.writeLine(`validator.validate(schemaKeyRef as string, data);`);
227
+ writer.writeLine(`return Boolean(validator.errors) === false;`);
228
+ writer.writeLine(`}`);
229
+ },
230
+ },
231
+ ],
232
+ });
233
+ sourceFile.addExportDeclaration({
234
+ namedExports: ["validator", "isValidSchema"],
235
+ });
236
+ yield project.save();
237
+ });
238
+ }
239
+ }
240
+ exports.SchemaGenerator = SchemaGenerator;
241
+ //# sourceMappingURL=SchemaGenerator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SchemaGenerator.js","sourceRoot":"","sources":["../src/SchemaGenerator.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const SchemaGenerator_1 = require("./SchemaGenerator");
5
+ const commander_1 = require("commander");
6
+ const defaultGlobPattern = "*.jsonschema.{ts,tsx}";
7
+ const defaultRootPath = "./src";
8
+ const defaultOutputFolder = "./.ts-runtime-validation";
9
+ commander_1.program.option("--glob", `Glob file path of typescript files to generate ts-interface -> json-schema validations - default: ${defaultGlobPattern}`, defaultGlobPattern);
10
+ commander_1.program.option("--rootPath", `RootPath of source - default: ${defaultRootPath}`, defaultRootPath);
11
+ commander_1.program.option("--output", `Validation schema + typescript interface output directory (relative to root path) - default: ${defaultOutputFolder}`, defaultOutputFolder);
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);
14
+ commander_1.program.parse();
15
+ const options = commander_1.program.opts();
16
+ const generator = new SchemaGenerator_1.SchemaGenerator(options);
17
+ generator.Generate();
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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;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",
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
- "typescript-json-schema": "^0.53.0"
12
+ "ts-runtime-validation": "../ts-runtime-validation"
12
13
  },
13
14
  "bin": {
14
15
  "ts-runtime-validation": "dist/index.js"
@@ -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 defaultProjectSettings: ProjectOptions = {
21
+ const defaultTsMorphProjectSettings: ProjectOptions = {
21
22
  manipulationSettings: {
22
23
  indentationText: IndentationText.FourSpaces,
23
24
  newLineKind: NewLineKind.LineFeed,
@@ -43,17 +44,26 @@ export class SchemaGenerator {
43
44
  public constructor(private options: ICommandOptions) {}
44
45
 
45
46
  public Generate = async () => {
46
- const { helpers } = this.options;
47
+ const { helpers, glob } = this.options;
47
48
  const fileList = await this.getMatchingFiles();
49
+
48
50
  console.log(`Found ${fileList.length} schema file(s)`);
49
- const map = await this.getJsonSchemaMap(fileList);
50
- console.log(`Generating ${map.size} validation schema(s)`);
51
- this.writeSchemaMapToValidationSchema(map);
51
+ if (fileList.length === 0) {
52
+ console.log(`Aborting - no files found with glob: ${glob}`);
53
+ return;
54
+ }
55
+ const fileSchemas = await this.getJsonSchemaMap(fileList);
56
+ console.log(`Generating ${fileSchemas.size} validation schema(s)`);
57
+ if (fileSchemas.size === 0) {
58
+ console.log(`Aborting - no interfaces found: ${glob}`);
59
+ return;
60
+ }
61
+ this.writeSchemaMapToValidationSchema(fileSchemas);
52
62
  if (helpers === false) {
53
63
  console.log("Skipping helper file generation");
54
64
  return;
55
65
  }
56
- await this.writeSchemaMapToValidationTypes(map, fileList);
66
+ await this.writeSchemaMapToValidationTypes(fileSchemas);
57
67
  this.writeValidatorFunction();
58
68
  };
59
69
 
@@ -72,41 +82,23 @@ export class SchemaGenerator {
72
82
  };
73
83
 
74
84
  private getJsonSchemaMap = async (filesList: Array<string>) => {
75
- const schemaMap = new Map<string, TJS.Definition>();
76
- const files = filesList.map((fileName) => {
77
- return resolve(fileName);
78
- });
79
- const settings: TJS.PartialArgs = {
80
- required: true,
81
- titles: true,
82
- aliasRef: true,
83
- ref: true,
84
- noExtraProps: true,
85
- propOrder: true,
86
- };
87
-
88
- const compilerOptions: TJS.CompilerOptions = {
89
- strictNullChecks: true,
90
- };
91
-
92
- const program = TJS.getProgramFromFiles(files, compilerOptions);
93
-
94
- const generator = TJS.buildGenerator(program, settings);
95
- const userDefinedSymbols = generator?.getMainFileSymbols(program) ?? [];
96
- userDefinedSymbols.forEach((symbol) => {
97
- if (schemaMap.has(symbol)) {
98
- throw new Error(`Duplicate symbol "${symbol}" found.`);
99
- }
100
- const schema = generator?.getSchemaForSymbol(symbol);
101
- if (schema) {
102
- schemaMap.set(symbol, schema);
103
- }
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);
104
97
  });
105
-
106
98
  return schemaMap;
107
99
  };
108
100
 
109
- private getSchemaVersion = (schemaMap: Map<string, TJS.Definition>) => {
101
+ private getSchemaVersion = (schemaMap: Map<string, Schema>) => {
110
102
  const firstEntry = schemaMap.values().next().value;
111
103
  return firstEntry["$schema"] ?? "";
112
104
  };
@@ -117,12 +109,20 @@ export class SchemaGenerator {
117
109
  }
118
110
  };
119
111
 
120
- private writeSchemaMapToValidationSchema = (schemaMap: Map<string, TJS.Definition>) => {
121
- const definitions: { [id: string]: TJS.Definition } = {};
122
- schemaMap.forEach((schema, key) => {
123
- definitions[key] = schema;
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
+ });
124
124
  });
125
- const outputBuffer: TJS.Definition = {
125
+ const outputBuffer: Schema = {
126
126
  $schema: this.getSchemaVersion(schemaMap),
127
127
  definitions,
128
128
  };
@@ -131,32 +131,29 @@ export class SchemaGenerator {
131
131
  fs.writeFileSync(this.jsonSchemaOutputFile, JSON.stringify(outputBuffer, null, 4));
132
132
  };
133
133
 
134
- private writeSchemaMapToValidationTypes = async (schemaMap: Map<string, TJS.Definition>, fileList: Array<string>) => {
135
- const project = new Project(defaultProjectSettings);
136
-
137
- const symbols = Array.from(schemaMap.keys()).filter((symbol) => {
138
- return symbol !== "ISchema" && symbol !== "Schemas";
139
- });
134
+ private writeSchemaMapToValidationTypes = async (schemaMap: Map<string, Schema>) => {
135
+ const project = new Project(defaultTsMorphProjectSettings);
140
136
 
141
- const readerProject = new Project();
142
- readerProject.addSourceFilesAtPaths(fileList);
137
+ const symbols: Array<string> = [];
143
138
 
144
139
  const importMap = new Map<string, Array<string>>();
145
- fileList.forEach((file) => {
146
- const dir = path.dirname(file);
147
- const fileWithoutExtension = path.parse(file).name;
140
+ schemaMap.forEach((schema, filePath) => {
141
+ const dir = path.dirname(filePath);
142
+ const fileWithoutExtension = path.parse(filePath).name;
148
143
  const relativeFilePath = path.relative(this.outputPath, dir);
149
144
  const importPath = `${relativeFilePath}/${fileWithoutExtension}`;
150
- const source = readerProject.getSourceFile(file);
151
- source?.getInterfaces().forEach((interfaceDeclaration) => {
152
- const structure = interfaceDeclaration.getStructure();
145
+ const defs = schema.definitions ?? {};
146
+
147
+ Object.keys(defs).forEach((symbol) => {
153
148
  const namedImports = importMap.get(importPath) ?? [];
154
- namedImports.push(structure.name);
149
+ namedImports.push(symbol);
155
150
  importMap.set(importPath, namedImports);
151
+ symbols.push(symbol);
156
152
  });
157
153
  });
158
154
 
159
155
  const sourceFile = project.createSourceFile(this.tsSchemaDefinitionOutputFile, {}, defaultCreateFileOptions);
156
+
160
157
  importMap.forEach((namedImports, importPath) => {
161
158
  sourceFile.addImportDeclaration({ namedImports, moduleSpecifier: importPath });
162
159
  });
@@ -190,12 +187,11 @@ export class SchemaGenerator {
190
187
  sourceFile.addExportDeclaration({
191
188
  namedExports: ["schemas", "ISchema"],
192
189
  });
193
-
194
190
  await project.save();
195
191
  };
196
192
 
197
193
  private writeValidatorFunction = async () => {
198
- const project = new Project(defaultProjectSettings);
194
+ const project = new Project(defaultTsMorphProjectSettings);
199
195
  const sourceFile = project.createSourceFile(this.isValidSchemaOutputFile, {}, defaultCreateFileOptions);
200
196
  sourceFile.addImportDeclaration({ namespaceImport: "schema", moduleSpecifier: `./${validationSchemaFileName}` });
201
197
  sourceFile.addImportDeclaration({ defaultImport: "Ajv", moduleSpecifier: "ajv" });
@@ -203,7 +199,6 @@ export class SchemaGenerator {
203
199
  namedImports: ["ISchema", "schemas"],
204
200
  moduleSpecifier: `./${path.parse(schemaDefinitionFileName).name}`,
205
201
  });
206
-
207
202
  sourceFile.addVariableStatement({
208
203
  declarationKind: VariableDeclarationKind.Const,
209
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