|
1 |
| -#!/usr/bin/env node |
2 |
| - |
3 |
| -import minimist = require('minimist') |
4 |
| -import getStdin from 'get-stdin' |
5 |
| -import {readFile, writeFile, existsSync, lstatSync, readdirSync} from 'mz/fs' |
6 |
| -import * as mkdirp from 'mkdirp' |
7 |
| -import glob from 'glob-promise' |
8 |
| -import isGlob = require('is-glob') |
9 |
| -import {join, resolve, dirname, basename} from 'path' |
10 |
| -import {compile, DEFAULT_OPTIONS, Options} from './index' |
11 |
| -import {pathTransform, error} from './utils' |
12 |
| - |
13 |
| -main( |
14 |
| - minimist(process.argv.slice(2), { |
15 |
| - alias: { |
16 |
| - help: ['h'], |
17 |
| - input: ['i'], |
18 |
| - output: ['o'], |
19 |
| - }, |
20 |
| - boolean: [ |
21 |
| - 'additionalProperties', |
22 |
| - 'declareExternallyReferenced', |
23 |
| - 'enableConstEnums', |
24 |
| - 'format', |
25 |
| - 'ignoreMinAndMaxItems', |
26 |
| - 'strictIndexSignatures', |
27 |
| - 'unknownAny', |
28 |
| - 'unreachableDefinitions', |
29 |
| - ], |
30 |
| - default: DEFAULT_OPTIONS, |
31 |
| - string: ['bannerComment', 'cwd'], |
32 |
| - }), |
33 |
| -) |
34 |
| - |
35 |
| -async function main(argv: minimist.ParsedArgs) { |
36 |
| - if (argv.help) { |
37 |
| - printHelp() |
38 |
| - process.exit(0) |
39 |
| - } |
40 |
| - |
41 |
| - const argIn: string = argv._[0] || argv.input |
42 |
| - const argOut: string | undefined = argv._[1] || argv.output // the output can be omitted so this can be undefined |
43 |
| - |
44 |
| - const ISGLOB = isGlob(argIn) |
45 |
| - const ISDIR = isDir(argIn) |
46 |
| - |
47 |
| - if ((ISGLOB || ISDIR) && argOut && argOut.includes('.d.ts')) { |
48 |
| - throw new ReferenceError( |
49 |
| - `You have specified a single file ${argOut} output for a multi file input ${argIn}. This feature is not yet supported, refer to issue #272 (https://github.com/bcherny/json-schema-to-typescript/issues/272)`, |
50 |
| - ) |
51 |
| - } |
52 |
| - |
53 |
| - try { |
54 |
| - // Process input as either glob, directory, or single file |
55 |
| - if (ISGLOB) { |
56 |
| - await processGlob(argIn, argOut, argv as Partial<Options>) |
57 |
| - } else if (ISDIR) { |
58 |
| - await processDir(argIn, argOut, argv as Partial<Options>) |
59 |
| - } else { |
60 |
| - const result = await processFile(argIn, argv as Partial<Options>) |
61 |
| - outputResult(result, argOut) |
62 |
| - } |
63 |
| - } catch (e) { |
64 |
| - error(e) |
65 |
| - process.exit(1) |
66 |
| - } |
67 |
| -} |
68 |
| - |
69 |
| -// check if path is an existing directory |
70 |
| -function isDir(path: string): boolean { |
71 |
| - return existsSync(path) && lstatSync(path).isDirectory() |
72 |
| -} |
73 |
| - |
74 |
| -async function processGlob(argIn: string, argOut: string | undefined, argv: Partial<Options>) { |
75 |
| - const files = await glob(argIn) // execute glob pattern match |
76 |
| - |
77 |
| - if (files.length === 0) { |
78 |
| - throw ReferenceError( |
79 |
| - `You passed a glob pattern "${argIn}", but there are no files that match that pattern in ${process.cwd()}`, |
80 |
| - ) |
81 |
| - } |
82 |
| - |
83 |
| - // we can do this concurrently for perf |
84 |
| - const results = await Promise.all( |
85 |
| - files.map(async file => { |
86 |
| - return [file, await processFile(file, argv)] as const |
87 |
| - }), |
88 |
| - ) |
89 |
| - |
90 |
| - // careful to do this serially |
91 |
| - results.forEach(([file, result]) => { |
92 |
| - const outputPath = argOut && `${argOut}/${basename(file, '.json')}.d.ts` |
93 |
| - outputResult(result, outputPath) |
94 |
| - }) |
95 |
| -} |
96 |
| - |
97 |
| -async function processDir(argIn: string, argOut: string | undefined, argv: Partial<Options>) { |
98 |
| - const files = getPaths(argIn) |
99 |
| - |
100 |
| - // we can do this concurrently for perf |
101 |
| - const results = await Promise.all( |
102 |
| - files.map(async file => { |
103 |
| - if (!argOut) { |
104 |
| - return [file, await processFile(file, argv)] as const |
105 |
| - } else { |
106 |
| - const outputPath = pathTransform(argOut, argIn, file) |
107 |
| - return [file, await processFile(file, argv), outputPath] as const |
108 |
| - } |
109 |
| - }), |
110 |
| - ) |
111 |
| - |
112 |
| - // careful to do this serially |
113 |
| - results.forEach(([file, result, outputPath]) => |
114 |
| - outputResult(result, outputPath ? `${outputPath}/${basename(file, '.json')}.d.ts` : undefined), |
115 |
| - ) |
116 |
| -} |
117 |
| - |
118 |
| -async function outputResult(result: string, outputPath: string | undefined): Promise<void> { |
119 |
| - if (!outputPath) { |
120 |
| - process.stdout.write(result) |
121 |
| - } else { |
122 |
| - if (!isDir(dirname(outputPath))) { |
123 |
| - mkdirp.sync(dirname(outputPath)) |
124 |
| - } |
125 |
| - return await writeFile(outputPath, result) |
126 |
| - } |
127 |
| -} |
128 |
| - |
129 |
| -async function processFile(argIn: string, argv: Partial<Options>): Promise<string> { |
130 |
| - const schema = JSON.parse(await readInput(argIn)) |
131 |
| - return compile(schema, argIn, argv) |
132 |
| -} |
133 |
| - |
134 |
| -function getPaths(path: string, paths: string[] = []) { |
135 |
| - if (existsSync(path) && lstatSync(path).isDirectory()) { |
136 |
| - readdirSync(resolve(path)).forEach(item => getPaths(join(path, item), paths)) |
137 |
| - } else { |
138 |
| - paths.push(path) |
139 |
| - } |
140 |
| - |
141 |
| - return paths |
142 |
| -} |
143 |
| - |
144 |
| -function readInput(argIn?: string) { |
145 |
| - if (!argIn) { |
146 |
| - return getStdin() |
147 |
| - } |
148 |
| - return readFile(resolve(process.cwd(), argIn), 'utf-8') |
149 |
| -} |
150 |
| - |
151 |
| -function printHelp() { |
152 |
| - const pkg = require('../../package.json') |
153 |
| - |
154 |
| - process.stdout.write( |
155 |
| - ` |
156 |
| -${pkg.name} ${pkg.version} |
157 |
| -Usage: json2ts [--input, -i] [IN_FILE] [--output, -o] [OUT_FILE] [OPTIONS] |
158 |
| -
|
159 |
| -With no IN_FILE, or when IN_FILE is -, read standard input. |
160 |
| -With no OUT_FILE and when IN_FILE is specified, create .d.ts file in the same directory. |
161 |
| -With no OUT_FILE nor IN_FILE, write to standard output. |
162 |
| -
|
163 |
| -You can use any of the following options by adding them at the end. |
164 |
| -Boolean values can be set to false using the 'no-' prefix. |
165 |
| -
|
166 |
| - --additionalProperties |
167 |
| - Default value for additionalProperties, when it is not explicitly set |
168 |
| - --cwd=XXX |
169 |
| - Root directory for resolving $ref |
170 |
| - --declareExternallyReferenced |
171 |
| - Declare external schemas referenced via '$ref'? |
172 |
| - --enableConstEnums |
173 |
| - Prepend enums with 'const'? |
174 |
| - --format |
175 |
| - Format code? Set this to false to improve performance. |
176 |
| - --maxItems |
177 |
| - Maximum number of unioned tuples to emit when representing bounded-size |
178 |
| - array types, before falling back to emitting unbounded arrays. Increase |
179 |
| - this to improve precision of emitted types, decrease it to improve |
180 |
| - performance, or set it to -1 to ignore minItems and maxItems. |
181 |
| - --style.XXX=YYY |
182 |
| - Prettier configuration |
183 |
| - --unknownAny |
184 |
| - Output unknown type instead of any type |
185 |
| - --unreachableDefinitions |
186 |
| - Generates code for definitions that aren't referenced by the schema |
187 |
| -`, |
188 |
| - ) |
189 |
| -} |
| 1 | +#!/usr/bin/env node |
| 2 | +import minimist = require('minimist') |
| 3 | +import getStdin from 'get-stdin' |
| 4 | +import {readFile, writeFile, existsSync, lstatSync, readdirSync} from 'mz/fs' |
| 5 | +import * as mkdirp from 'mkdirp' |
| 6 | +import glob from 'glob-promise' |
| 7 | +import isGlob = require('is-glob') |
| 8 | +import {join, resolve, dirname, basename} from 'path' |
| 9 | +import {compile, DEFAULT_OPTIONS, Options} from './index' |
| 10 | +import {pathTransform, error} from './utils' |
| 11 | + |
| 12 | +main( |
| 13 | + minimist(process.argv.slice(2), { |
| 14 | + alias: { |
| 15 | + help: ['h'], |
| 16 | + input: ['i'], |
| 17 | + output: ['o'], |
| 18 | + }, |
| 19 | + boolean: [ |
| 20 | + 'additionalProperties', |
| 21 | + 'declareExternallyReferenced', |
| 22 | + 'enableConstEnums', |
| 23 | + 'format', |
| 24 | + 'ignoreMinAndMaxItems', |
| 25 | + 'strictIndexSignatures', |
| 26 | + 'unknownAny', |
| 27 | + 'unreachableDefinitions', |
| 28 | + 'usePrefixItems', |
| 29 | + ], |
| 30 | + default: DEFAULT_OPTIONS, |
| 31 | + string: ['bannerComment', 'cwd'], |
| 32 | + }), |
| 33 | +) |
| 34 | + |
| 35 | +async function main(argv: minimist.ParsedArgs) { |
| 36 | + if (argv.help) { |
| 37 | + printHelp() |
| 38 | + process.exit(0) |
| 39 | + } |
| 40 | + |
| 41 | + const argIn: string = argv._[0] || argv.input |
| 42 | + const argOut: string | undefined = argv._[1] || argv.output // the output can be omitted so this can be undefined |
| 43 | + |
| 44 | + const ISGLOB = isGlob(argIn) |
| 45 | + const ISDIR = isDir(argIn) |
| 46 | + |
| 47 | + if ((ISGLOB || ISDIR) && argOut && argOut.includes('.d.ts')) { |
| 48 | + throw new ReferenceError( |
| 49 | + `You have specified a single file ${argOut} output for a multi file input ${argIn}. This feature is not yet supported, refer to issue #272 (https://github.com/bcherny/json-schema-to-typescript/issues/272)`, |
| 50 | + ) |
| 51 | + } |
| 52 | + |
| 53 | + try { |
| 54 | + // Process input as either glob, directory, or single file |
| 55 | + if (ISGLOB) { |
| 56 | + await processGlob(argIn, argOut, argv as Partial<Options>) |
| 57 | + } else if (ISDIR) { |
| 58 | + await processDir(argIn, argOut, argv as Partial<Options>) |
| 59 | + } else { |
| 60 | + const result = await processFile(argIn, argv as Partial<Options>) |
| 61 | + outputResult(result, argOut) |
| 62 | + } |
| 63 | + } catch (e) { |
| 64 | + error(e) |
| 65 | + process.exit(1) |
| 66 | + } |
| 67 | +} |
| 68 | + |
| 69 | +// check if path is an existing directory |
| 70 | +function isDir(path: string): boolean { |
| 71 | + return existsSync(path) && lstatSync(path).isDirectory() |
| 72 | +} |
| 73 | + |
| 74 | +async function processGlob(argIn: string, argOut: string | undefined, argv: Partial<Options>) { |
| 75 | + const files = await glob(argIn) // execute glob pattern match |
| 76 | + |
| 77 | + if (files.length === 0) { |
| 78 | + throw ReferenceError( |
| 79 | + `You passed a glob pattern "${argIn}", but there are no files that match that pattern in ${process.cwd()}`, |
| 80 | + ) |
| 81 | + } |
| 82 | + |
| 83 | + // we can do this concurrently for perf |
| 84 | + const results = await Promise.all( |
| 85 | + files.map(async file => { |
| 86 | + return [file, await processFile(file, argv)] as const |
| 87 | + }), |
| 88 | + ) |
| 89 | + |
| 90 | + // careful to do this serially |
| 91 | + results.forEach(([file, result]) => { |
| 92 | + const outputPath = argOut && `${argOut}/${basename(file, '.json')}.d.ts` |
| 93 | + outputResult(result, outputPath) |
| 94 | + }) |
| 95 | +} |
| 96 | + |
| 97 | +async function processDir(argIn: string, argOut: string | undefined, argv: Partial<Options>) { |
| 98 | + const files = getPaths(argIn) |
| 99 | + |
| 100 | + // we can do this concurrently for perf |
| 101 | + const results = await Promise.all( |
| 102 | + files.map(async file => { |
| 103 | + if (!argOut) { |
| 104 | + return [file, await processFile(file, argv)] as const |
| 105 | + } else { |
| 106 | + const outputPath = pathTransform(argOut, argIn, file) |
| 107 | + return [file, await processFile(file, argv), outputPath] as const |
| 108 | + } |
| 109 | + }), |
| 110 | + ) |
| 111 | + |
| 112 | + // careful to do this serially |
| 113 | + results.forEach(([file, result, outputPath]) => |
| 114 | + outputResult(result, outputPath ? `${outputPath}/${basename(file, '.json')}.d.ts` : undefined), |
| 115 | + ) |
| 116 | +} |
| 117 | + |
| 118 | +async function outputResult(result: string, outputPath: string | undefined): Promise<void> { |
| 119 | + if (!outputPath) { |
| 120 | + process.stdout.write(result) |
| 121 | + } else { |
| 122 | + if (!isDir(dirname(outputPath))) { |
| 123 | + mkdirp.sync(dirname(outputPath)) |
| 124 | + } |
| 125 | + return await writeFile(outputPath, result) |
| 126 | + } |
| 127 | +} |
| 128 | + |
| 129 | +async function processFile(argIn: string, argv: Partial<Options>): Promise<string> { |
| 130 | + const schema = JSON.parse(await readInput(argIn)) |
| 131 | + return compile(schema, argIn, argv) |
| 132 | +} |
| 133 | + |
| 134 | +function getPaths(path: string, paths: string[] = []) { |
| 135 | + if (existsSync(path) && lstatSync(path).isDirectory()) { |
| 136 | + readdirSync(resolve(path)).forEach(item => getPaths(join(path, item), paths)) |
| 137 | + } else { |
| 138 | + paths.push(path) |
| 139 | + } |
| 140 | + |
| 141 | + return paths |
| 142 | +} |
| 143 | + |
| 144 | +function readInput(argIn?: string) { |
| 145 | + if (!argIn) { |
| 146 | + return getStdin() |
| 147 | + } |
| 148 | + return readFile(resolve(process.cwd(), argIn), 'utf-8') |
| 149 | +} |
| 150 | + |
| 151 | +function printHelp() { |
| 152 | + const pkg = require('../../package.json') |
| 153 | + |
| 154 | + process.stdout.write( |
| 155 | + ` |
| 156 | +${pkg.name} ${pkg.version} |
| 157 | +Usage: json2ts [--input, -i] [IN_FILE] [--output, -o] [OUT_FILE] [OPTIONS] |
| 158 | +
|
| 159 | +With no IN_FILE, or when IN_FILE is -, read standard input. |
| 160 | +With no OUT_FILE and when IN_FILE is specified, create .d.ts file in the same directory. |
| 161 | +With no OUT_FILE nor IN_FILE, write to standard output. |
| 162 | +
|
| 163 | +You can use any of the following options by adding them at the end. |
| 164 | +Boolean values can be set to false using the 'no-' prefix. |
| 165 | +
|
| 166 | + --additionalProperties |
| 167 | + Default value for additionalProperties, when it is not explicitly set |
| 168 | + --cwd=XXX |
| 169 | + Root directory for resolving $ref |
| 170 | + --declareExternallyReferenced |
| 171 | + Declare external schemas referenced via '$ref'? |
| 172 | + --enableConstEnums |
| 173 | + Prepend enums with 'const'? |
| 174 | + --format |
| 175 | + Format code? Set this to false to improve performance. |
| 176 | + --maxItems |
| 177 | + Maximum number of unioned tuples to emit when representing bounded-size |
| 178 | + array types, before falling back to emitting unbounded arrays. Increase |
| 179 | + this to improve precision of emitted types, decrease it to improve |
| 180 | + performance, or set it to -1 to ignore minItems and maxItems. |
| 181 | + --style.XXX=YYY |
| 182 | + Prettier configuration |
| 183 | + --unknownAny |
| 184 | + Output unknown type instead of any type |
| 185 | + --unreachableDefinitions |
| 186 | + Generates code for definitions that aren't referenced by the schema |
| 187 | +`, |
| 188 | + ) |
| 189 | +} |
0 commit comments