UNPKG

ts2json-schema

Version:

Build JSON schemas for your typescript types matching a pattern.

268 lines 10.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const path_1 = __importDefault(require("path")); const fs_1 = require("fs"); const TJS = __importStar(require("typescript-json-schema")); const VegaTSJ = __importStar(require("ts-json-schema-generator")); const commander_1 = require("commander"); const AppRootPath = __importStar(require("app-root-path")); const logger_1 = require("./logger"); const errors_1 = require("./errors"); // What is needed // Location of the files which need to be processed. // Output path where files should be placed. const commandManager = new commander_1.Command() .requiredOption('-p, --path <directory>', 'Source files') .requiredOption('-m, --match <regex pattern>', 'Build schema for types that match the pattern') .option('-o, --out <directory>', 'Set the output dir (default: <source path>/../schema)') .option('-D, --debug', 'Enable debug logging') .option('-v, --verbose', 'Enable verbose output') .option('-f, --filematch <regex pattern>', 'Use file names that match the pattern') .option('-A, --vega', 'Use vega/ts-json-schema-generator') .option('-t, --tsconfig <path>', 'Provide path to tsconfig including filename') .option('-R, --root <path>', 'Provide a root path to override the auto configuration') .option('-e, --exclude <pattern>', 'Exclude types that match the pattern'); let rootPath, inputPath, outputPath, tsPath; let logger; /** * Configure various settings based on supplied arguments. */ const configure = () => { commandManager.parse(process.argv); if (commandManager.opts().vega && !commandManager.opts().tsconfig) { process.stderr.write('Program requires location of tsconfig when using vega/ts-json-schema-generator\n'); process.exit(); } if (commandManager.opts().root) { rootPath = path_1.default.resolve(commandManager.opts().root); if (!fs_1.existsSync(rootPath)) { process.stderr.write(`Invalid root path at ${rootPath}\n`); process.exit(); } } else { rootPath = `${AppRootPath}`; } inputPath = path_1.default.resolve(rootPath, commandManager.opts().path); if (commandManager.opts().tsconfig) { tsPath = path_1.default.resolve(rootPath, commandManager.opts().tsconfig); if (!fs_1.existsSync(tsPath)) { process.stderr.write(`Could not find tsconfig at ${tsPath}\n`); process.exit(); } } if (commandManager.opts().out) { outputPath = path_1.default.resolve(rootPath, commandManager.opts().out); } else { outputPath = path_1.default.resolve(inputPath, '../schema'); } // check if inputPath is a valid directory else throw error if (!fs_1.existsSync(inputPath) || !fs_1.lstatSync(inputPath).isDirectory()) { const configError = new Error(`Invalid Path: '${inputPath}'. Enter a valid path to a directory.`); configError.name = 'ConfigError'; throw configError; } let level; const { debug, verbose } = commandManager.opts(); if (debug) { level = 4 /* DEBUG */; } else if (verbose) { level = 3 /* VERBOSE */; } else { level = 2 /* INFO */; } // eslint-disable-next-line no-console logger = new logger_1.Logger(level, console.log); }; /** * Retrieve typescript files from the source directory. * @returns {Array<string>} list of file paths to process */ const buildFileList = () => { let filematch = commandManager.opts().filematch; if (filematch) { logger.info(`Looking for files matching pattern '${filematch}'`); } else { filematch = '.*'; } // get list of files at inputPath const allFiles = fs_1.readdirSync(inputPath); // filter to typescript files const tsRegex = new RegExp('.+\\.ts$'); const fileMatchRegex = new RegExp(filematch); const files = allFiles.filter(file => tsRegex.test(file) && fileMatchRegex.test(file)); // convert typescript filename to absolute file path const filePaths = files.map(file => path_1.default.resolve(inputPath, file)); return filePaths.filter(filePath => !fs_1.lstatSync(filePath).isDirectory()); }; /** * Using the supplied generator, generate schema for the provided symbol (a.k.a type) * @param symbol type for which schema is to be generated * @param generator Generator to use for schema generation * @returns schema object */ // Type generated by TJS generator is any so we have to use that. // eslint-disable-next-line @typescript-eslint/no-explicit-any const generateSchemaFromGenerator = (symbol, generator) => { try { logger.verbose(`Generating schema for '${symbol}'`); if (commandManager.opts().vega) { return generator.createSchema(symbol); } else { return generator.getSchemaForSymbol(symbol); } } catch (error) { throw new errors_1.GeneratorError(error); } }; /** * Convert schema to writable string and write them to files. Function * throw errors when they are encountered so handling them will allow the caller * to gracefully perform multiple conversions. * @param symbol {string} name of the type. * @param generator {TJS.JsonSchemaGenerator} instance of configured schema generator. */ const saveSchemaForSymbol = (symbol, schema) => { const prefix = 'export default '; const filePath = path_1.default.join(outputPath, `${symbol}JSC.ts`); let fileContents; try { fileContents = `${prefix}${JSON.stringify(schema, null, 2)}`; } catch (error) { throw new errors_1.JSONBuilderError(error); } try { fs_1.writeFileSync(filePath, fileContents); } catch (error) { if (error) { throw new errors_1.FileWriteError(error); } } }; /** * Generate JSON schema from typescript files at the source directory and save * it at output directory. * Generated files have have the following pattern - <type name>JSC.ts * @returns {void} */ const generateSchemas = () => { configure(); logger.info(`Configured to process files from: '${inputPath}'` + ` and writing schemas to: '${outputPath}'`); const files = buildFileList(); if (!files || files.length < 1) { logger.info('Found no matching files to process.'); return; } logger.verbose('Processing files:\n', files); const settings = { required: true, }; const compilerOptions = { strictNullChecks: true, }; logger.debug('Configuring schema generator'); const program = TJS.getProgramFromFiles(files, compilerOptions); const generator = TJS.buildGenerator(program, settings); if (!generator) { logger.error('Failed to build a schema generator. Please report issue.'); return; } // get all symbols which meet regex const matchPattern = commandManager.opts().match; logger.debug(`Using '${matchPattern}' to filter types.`); logger.debug('Fetching user types from files.'); const symbols = generator.getUserSymbols(); const typeMatchPattern = new RegExp(matchPattern); let filtered = symbols.filter(symbol => typeMatchPattern.test(symbol)); if (commandManager.opts().exclude) { const excludePattern = new RegExp(commandManager.opts().exclude); filtered = filtered.filter(symbol => !excludePattern.test(symbol)); } logger.verbose(`Filtered ${symbols.length} symbols using '${matchPattern}' to obtain:\n`, filtered); // create directory if it doesn't exist if (!fs_1.existsSync(outputPath)) { fs_1.mkdirSync(outputPath); } // Generating using vega let vegaGenerator; let vegaConfig; const vegaSourcePath = inputPath.endsWith(path_1.default.sep) ? inputPath + '*.ts' : inputPath + path_1.default.sep + '*.ts'; if (commandManager.opts().vega) { vegaConfig = { path: vegaSourcePath, tsconfig: tsPath, type: '*', topRef: true, skipTypeCheck: true, additionalProperties: true, }; logger.verbose('Configured vega generator with following settings', vegaConfig); vegaGenerator = VegaTSJ.createGenerator(vegaConfig); } // store all schema files filtered.forEach(symbol => { try { // Type generated by TJS generator is any so we have to use that. // eslint-disable-next-line @typescript-eslint/no-explicit-any let schema; if (commandManager.opts().vega) { schema = generateSchemaFromGenerator(symbol, vegaGenerator); } else { schema = generateSchemaFromGenerator(symbol, generator); } saveSchemaForSymbol(symbol, schema); } catch (error) { if (error instanceof errors_1.GeneratorError) { logger.error(`Failed to generate schema for '${symbol}' with error:\n`, error); logger.info('Continue processing other types'); return; } if (error instanceof errors_1.FileWriteError) { logger.error(`Failed to write file for '${symbol}' with error:\n`, error); logger.info('Continue processing other types'); return; } if (error instanceof errors_1.JSONBuilderError) { logger.error(`Failed to build JSON from schema for '${symbol}' with error:\n`, error); logger.info('Continue processing other types'); return; } } }); }; // Entry point function generateSchemas(); //# sourceMappingURL=index.js.map