UNPKG

json-schema-to-typescript

Version:
195 lines (174 loc) 6.23 kB
import {readFileSync} from 'fs' import {JSONSchema4} from 'json-schema' import {ParserOptions as $RefOptions} from '@apidevtools/json-schema-ref-parser' import {cloneDeep, endsWith, merge} from 'lodash' import {dirname} from 'path' import {Options as PrettierOptions} from 'prettier' import {format} from './formatter' import {generate} from './generator' import {normalize} from './normalizer' import {optimize} from './optimizer' import {parse} from './parser' import {dereference} from './resolver' import {error, stripExtension, Try, log, parseFileAsJSONSchema} from './utils' import {validate} from './validator' import {isDeepStrictEqual} from 'util' import {link} from './linker' import {validateOptions} from './optionValidator' import {JSONSchema as LinkedJSONSchema} from './types/JSONSchema' export {EnumJSONSchema, JSONSchema, NamedEnumJSONSchema, CustomTypeJSONSchema} from './types/JSONSchema' export interface Options { /** * [$RefParser](https://github.com/APIDevTools/json-schema-ref-parser) Options, used when resolving `$ref`s */ $refOptions: $RefOptions /** * Default value for additionalProperties, when it is not explicitly set. */ additionalProperties: boolean /** * Disclaimer comment prepended to the top of each generated file. */ bannerComment: string /** * Custom function to provide a type name for a given schema */ customName?: (schema: LinkedJSONSchema, keyNameFromDefinition: string | undefined) => string | undefined /** * Root directory for resolving [`$ref`](https://tools.ietf.org/id/draft-pbryan-zyp-json-ref-03.html)s. */ cwd: string /** * Declare external schemas referenced via `$ref`? */ declareExternallyReferenced: boolean /** * Prepend enums with [`const`](https://www.typescriptlang.org/docs/handbook/enums.html#computed-and-constant-members)? */ enableConstEnums: boolean /** * Create enums from JSON enums with eponymous keys */ inferStringEnumKeysFromValues: boolean /** * Format code? Set this to `false` to improve performance. */ format: boolean /** * Ignore maxItems and minItems for `array` types, preventing tuples being generated. */ ignoreMinAndMaxItems: boolean /** * Maximum number of unioned tuples to emit when representing bounded-size array types, * before falling back to emitting unbounded arrays. Increase this to improve precision * of emitted types, decrease it to improve performance, or set it to `-1` to ignore * `minItems` and `maxItems`. */ maxItems: number /** * Append all index signatures with `| undefined` so that they are strictly typed. * * This is required to be compatible with `strictNullChecks`. */ strictIndexSignatures: boolean /** * A [Prettier](https://prettier.io/docs/en/options.html) configuration. */ style: PrettierOptions /** * Generate code for `definitions` that aren't referenced by the schema? */ unreachableDefinitions: boolean /** * Generate unknown type instead of any */ unknownAny: boolean } export const DEFAULT_OPTIONS: Options = { $refOptions: {}, additionalProperties: true, // TODO: default to empty schema (as per spec) instead bannerComment: `/* eslint-disable */ /** * This file was automatically generated by json-schema-to-typescript. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run json-schema-to-typescript to regenerate this file. */`, cwd: process.cwd(), declareExternallyReferenced: true, enableConstEnums: true, inferStringEnumKeysFromValues: false, format: true, ignoreMinAndMaxItems: false, maxItems: 20, strictIndexSignatures: false, style: { bracketSpacing: false, printWidth: 120, semi: true, singleQuote: false, tabWidth: 2, trailingComma: 'none', useTabs: false, }, unreachableDefinitions: false, unknownAny: true, } export function compileFromFile(filename: string, options: Partial<Options> = DEFAULT_OPTIONS): Promise<string> { const schema = parseAsJSONSchema(filename) return compile(schema, stripExtension(filename), {cwd: dirname(filename), ...options}) } function parseAsJSONSchema(filename: string): JSONSchema4 { const contents = Try( () => readFileSync(filename), () => { throw new ReferenceError(`Unable to read file "${filename}"`) }, ) return parseFileAsJSONSchema(filename, contents.toString()) } export async function compile(schema: JSONSchema4, name: string, options: Partial<Options> = {}): Promise<string> { validateOptions(options) const _options = merge({}, DEFAULT_OPTIONS, options) const start = Date.now() function time() { return `(${Date.now() - start}ms)` } // normalize options if (!endsWith(_options.cwd, '/')) { _options.cwd += '/' } // Initial clone to avoid mutating the input const _schema = cloneDeep(schema) const {dereferencedPaths, dereferencedSchema} = await dereference(_schema, _options) if (process.env.VERBOSE) { if (isDeepStrictEqual(_schema, dereferencedSchema)) { log('green', 'dereferencer', time(), '✅ No change') } else { log('green', 'dereferencer', time(), '✅ Result:', dereferencedSchema) } } const linked = link(dereferencedSchema) if (process.env.VERBOSE) { log('green', 'linker', time(), '✅ No change') } const errors = validate(linked, name) if (errors.length) { errors.forEach(_ => error(_)) throw new ValidationError() } if (process.env.VERBOSE) { log('green', 'validator', time(), '✅ No change') } const normalized = normalize(linked, dereferencedPaths, name, _options) log('yellow', 'normalizer', time(), '✅ Result:', normalized) const parsed = parse(normalized, _options) log('blue', 'parser', time(), '✅ Result:', parsed) const optimized = optimize(parsed, _options) log('cyan', 'optimizer', time(), '✅ Result:', optimized) const generated = generate(optimized, _options) log('magenta', 'generator', time(), '✅ Result:', generated) const formatted = await format(generated, _options) log('white', 'formatter', time(), '✅ Result:', formatted) return formatted } export class ValidationError extends Error {}