ts2json-schema
Version:
Build JSON schemas for your typescript types matching a pattern.
268 lines • 10.8 kB
JavaScript
;
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