diff --git a/src/cli.ts b/src/cli.ts index cb9da040..217de215 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -5,9 +5,9 @@ import {readFile, writeFile, existsSync, lstatSync, readdirSync} from 'mz/fs' import * as mkdirp from 'mkdirp' import {glob} from 'glob' import isGlob from 'is-glob' -import {join, resolve, dirname, basename} from 'path' +import {join, resolve, dirname} from 'path' import {compile, DEFAULT_OPTIONS, Options} from './index' -import {pathTransform, error} from './utils' +import {pathTransform, error, parseFileAsJSONSchema, justName} from './utils' main( minimist(process.argv.slice(2), { @@ -88,7 +88,7 @@ async function processGlob(argIn: string, argOut: string | undefined, argv: Part // careful to do this serially results.forEach(([file, result]) => { - const outputPath = argOut && `${argOut}/${basename(file, '.json')}.d.ts` + const outputPath = argOut && `${argOut}/${justName(file)}.d.ts` outputResult(result, outputPath) }) } @@ -110,7 +110,7 @@ async function processDir(argIn: string, argOut: string | undefined, argv: Parti // careful to do this serially results.forEach(([file, result, outputPath]) => - outputResult(result, outputPath ? `${outputPath}/${basename(file, '.json')}.d.ts` : undefined), + outputResult(result, outputPath ? `${outputPath}/${justName(file)}.d.ts` : undefined), ) } @@ -126,7 +126,8 @@ async function outputResult(result: string, outputPath: string | undefined): Pro } async function processFile(argIn: string, argv: Partial): Promise { - const schema = JSON.parse(await readInput(argIn)) + const {filename, contents} = await readInput(argIn) + const schema = parseFileAsJSONSchema(filename, contents) return compile(schema, argIn, argv) } @@ -140,11 +141,17 @@ function getPaths(path: string, paths: string[] = []) { return paths } -async function readInput(argIn?: string): Promise { +async function readInput(argIn?: string): Promise<{filename: string | null; contents: string}> { if (!argIn) { - return readStream(process.stdin) + return { + filename: null, + contents: await readStream(process.stdin), + } + } + return { + filename: argIn, + contents: await readFile(resolve(process.cwd(), argIn), 'utf-8'), } - return readFile(resolve(process.cwd(), argIn), 'utf-8') } async function readStream(stream: NodeJS.ReadStream): Promise { diff --git a/src/index.ts b/src/index.ts index 48035e84..de22beaf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,13 +10,12 @@ import {normalize} from './normalizer' import {optimize} from './optimizer' import {parse} from './parser' import {dereference} from './resolver' -import {error, stripExtension, Try, log} from './utils' +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' -import yaml from 'js-yaml' export {EnumJSONSchema, JSONSchema, NamedEnumJSONSchema, CustomTypeJSONSchema} from './types/JSONSchema' @@ -125,26 +124,7 @@ function parseAsJSONSchema(filename: string): JSONSchema4 { throw new ReferenceError(`Unable to read file "${filename}"`) }, ) - - if (isYaml(filename)) { - return Try( - () => yaml.load(contents.toString()) as JSONSchema4, - () => { - throw new TypeError(`Error parsing YML in file "${filename}"`) - }, - ) - } - - return Try( - () => JSON.parse(contents.toString()), - () => { - throw new TypeError(`Error parsing JSON in file "${filename}"`) - }, - ) -} - -function isYaml(filename: string) { - return filename.endsWith('.yaml') || filename.endsWith('.yml') + return parseFileAsJSONSchema(filename, contents.toString()) } export async function compile(schema: JSONSchema4, name: string, options: Partial = {}): Promise { diff --git a/src/utils.ts b/src/utils.ts index c256dbec..c7f1f215 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,8 @@ import {deburr, isPlainObject, trim, upperFirst} from 'lodash' import {basename, dirname, extname, normalize, sep, posix} from 'path' import {JSONSchema, LinkedJSONSchema, Parent} from './types/JSONSchema' +import {JSONSchema4} from 'json-schema' +import yaml from 'js-yaml' // TODO: pull out into a separate package export function Try(fn: () => T, err: (e: Error) => any): T { @@ -384,3 +386,25 @@ export function isSchemaLike(schema: LinkedJSONSchema) { return true } + +export function parseFileAsJSONSchema(filename: string | null, contents: string): JSONSchema4 { + if (filename != null && isYaml(filename)) { + return Try( + () => yaml.load(contents.toString()) as JSONSchema4, + () => { + throw new TypeError(`Error parsing YML in file "${filename}"`) + }, + ) + } + + return Try( + () => JSON.parse(contents.toString()), + () => { + throw new TypeError(`Error parsing JSON in file "${filename}"`) + }, + ) +} + +function isYaml(filename: string) { + return filename.endsWith('.yaml') || filename.endsWith('.yml') +} diff --git a/test/__snapshots__/test/test.ts.md b/test/__snapshots__/test/test.ts.md index dabcecdc..e1c6e25d 100644 --- a/test/__snapshots__/test/test.ts.md +++ b/test/__snapshots__/test/test.ts.md @@ -449284,6 +449284,31 @@ Generated by [AVA](https://avajs.dev). ## file in (-i), pipe out (absolute path) +> Snapshot 1 + + `/* 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.␊ + */␊ + ␊ + export interface ExampleSchema {␊ + firstName: string;␊ + lastName: string;␊ + /**␊ + * Age in years␊ + */␊ + age?: number;␊ + height?: number;␊ + favoriteFoods?: unknown[];␊ + likesDogs?: boolean;␊ + [k: string]: unknown;␊ + }␊ + ` + +## file in (yaml), pipe out + > Snapshot 1 `/* eslint-disable */␊ @@ -449522,6 +449547,26 @@ Generated by [AVA](https://avajs.dev). }␊ ` +> Snapshot 5 + + './test/resources/MultiSchema/out/b.yaml.d.ts' + +> Snapshot 6 + + `/* 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.␊ + */␊ + ␊ + export interface BSchema {␊ + x?: string;␊ + y: number;␊ + [k: string]: unknown;␊ + }␊ + ` + ## files in (-i), pipe out > Snapshot 1 diff --git a/test/__snapshots__/test/test.ts.snap b/test/__snapshots__/test/test.ts.snap index 7f856f17..6640ff0a 100644 Binary files a/test/__snapshots__/test/test.ts.snap and b/test/__snapshots__/test/test.ts.snap differ diff --git a/test/resources/MultiSchema/b.json b/test/resources/MultiSchema/b.json deleted file mode 100644 index 05a65bf3..00000000 --- a/test/resources/MultiSchema/b.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "title": "B schema", - "type": "object", - "properties": { - "x": {"type": "string"}, - "y": {"type": "integer"} - }, - "additionalProperties": true, - "required": ["y"] -} \ No newline at end of file diff --git a/test/resources/MultiSchema/b.yaml b/test/resources/MultiSchema/b.yaml new file mode 100644 index 00000000..b950e5bb --- /dev/null +++ b/test/resources/MultiSchema/b.yaml @@ -0,0 +1,10 @@ +title: B schema +type: object +properties: + x: + type: string + "y": + type: integer +additionalProperties: true +required: +- "y" diff --git a/test/resources/Schema.yaml b/test/resources/Schema.yaml new file mode 100644 index 00000000..7713d126 --- /dev/null +++ b/test/resources/Schema.yaml @@ -0,0 +1,21 @@ +id: http://dummy.com/api/example-schema +title: Example Schema +type: object +properties: + firstName: + type: string + lastName: + type: string + age: + description: Age in years + type: integer + minimum: 0 + height: + type: number + favoriteFoods: + type: array + likesDogs: + type: boolean +required: +- firstName +- lastName diff --git a/test/testCLI.ts b/test/testCLI.ts index 3e2264f5..327c1004 100644 --- a/test/testCLI.ts +++ b/test/testCLI.ts @@ -47,6 +47,10 @@ export function run() { t.snapshot(execSync(`node dist/src/cli.js -i ${__dirname}/../../test/resources/ReferencedType.json`).toString()) }) + test('file in (yaml), pipe out', t => { + t.snapshot(execSync('node dist/src/cli.js ./test/resources/Schema.yaml').toString()) + }) + test('pipe in, file out (--output)', t => { execSync('shx cat ./test/resources/ReferencedType.json | node dist/src/cli.js --output ./ReferencedType.d.ts') t.snapshot(readFileSync('./ReferencedType.d.ts', 'utf-8')) @@ -92,7 +96,9 @@ export function run() { }) test('files in (-i), files out (-o)', t => { - execSync(`node dist/src/cli.js -i "./test/resources/MultiSchema/**/*.json" -o ./test/resources/MultiSchema/out`) + execSync( + `node dist/src/cli.js -i "./test/resources/MultiSchema/**/*.{json,yaml,yml}" -o ./test/resources/MultiSchema/out`, + ) readdirSync('./test/resources/MultiSchema/out').forEach(f => { const path = `./test/resources/MultiSchema/out/${f}` @@ -104,12 +110,12 @@ export function run() { }) test('files in (-i), pipe out', t => { - t.snapshot(execSync(`node dist/src/cli.js -i "./test/resources/MultiSchema/**/*.json"`).toString()) + t.snapshot(execSync(`node dist/src/cli.js -i "./test/resources/MultiSchema/**/*.{json,yaml,yml}"`).toString()) }) test('files in (-i), files out (-o) nested dir does not exist', t => { execSync( - `node dist/src/cli.js -i "./test/resources/MultiSchema/**/*.json" -o ./test/resources/MultiSchema/foo/bar/out`, + `node dist/src/cli.js -i "./test/resources/MultiSchema/**/*.{json,yaml,yml}" -o ./test/resources/MultiSchema/foo/bar/out`, ) readdirSync('./test/resources/MultiSchema/foo/bar/out').forEach(f => { const path = `./test/resources/MultiSchema/foo/bar/out/${f}`