-
Notifications
You must be signed in to change notification settings - Fork 355
/
generateTypes.ts
136 lines (113 loc) · 5.03 KB
/
generateTypes.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/* eslint-disable array-func/no-unnecessary-this-arg,unicorn/no-process-exit */
import Ajv, { Ajv as AjvModule } from 'ajv'
import rimrafOriginal from 'rimraf'
import pack from 'ajv-pack'
import { concurrent } from 'fasy'
import fs from 'fs-extra'
import yaml from 'js-yaml'
import path from 'path'
import prettier from 'prettier'
import { quicktype, InputData, JSONSchema, JSONSchemaInput, JSONSchemaStore, parseJSON } from 'quicktype-core'
import util from 'util'
import { findModuleRoot } from '../lib/findModuleRoot'
const rimraf = util.promisify(rimrafOriginal)
const SCHEMA_EXTENSION = '.yml'
class Store extends JSONSchemaStore {
private readonly schemasRoot: string
constructor(schemasRoot: string) {
super()
this.schemasRoot = schemasRoot
}
async fetch(address: string): Promise<JSONSchema | undefined> {
const schemaFilepath = path.join(this.schemasRoot, address)
const jsonSchemaString = (await fs.readFile(schemaFilepath)).toString('utf-8')
return parseJSON(jsonSchemaString, 'JSON Schema', address) as Record<string, unknown>
}
}
function quicktypesAddSources(schemasRoot: string, schemaInput: JSONSchemaInput) {
return async (schemaFilename: string) => {
const schemaFilepath = path.join(schemasRoot, schemaFilename)
const typeName = schemaFilename.replace(SCHEMA_EXTENSION, '')
const jsonSchemaString = (await fs.readFile(schemaFilepath)).toString('utf-8')
await schemaInput.addSource({ name: typeName, schema: jsonSchemaString })
}
}
async function quicktypesGenerate(
lang: string,
schemasRoot: string,
schemaFilenames: string[],
outputPath: string,
rendererOptions?: { [name: string]: string },
) {
const schemaInput = new JSONSchemaInput(new Store(schemasRoot))
await concurrent.forEach(quicktypesAddSources(schemasRoot, schemaInput), schemaFilenames)
const inputData = new InputData()
inputData.addInput(schemaInput)
const { lines } = await quicktype({ inputData, lang, rendererOptions })
let code = lines.join('\n')
const schemaVer = '2.1.0'
if (lang === 'typescript') {
code = prettier.format(code, { parser: 'typescript' })
// insert schemaVer constant
code = `export const schemaVer = '${schemaVer}'\n\n${code}`
} else if (lang === 'python') {
// insert schema_ver variable
code = code.replace(`T = TypeVar("T")`, `schema_ver = '${schemaVer}'\n\nT = TypeVar("T")`)
}
return fs.writeFile(outputPath, code)
}
function ajvAddSources(schemasRoot: string, ajv: AjvModule) {
return async (schemaFilename: string) => {
const schemaFilepath = path.join(schemasRoot, schemaFilename)
const jsonSchemaString = fs.readFileSync(schemaFilepath).toString('utf-8')
const schema = yaml.safeLoad(jsonSchemaString) as Record<string, unknown>
ajv.addSchema(schema)
}
}
function ajvGenerateOne(schemasRoot: string, ajv: AjvModule, outputDir: string) {
return async (schemaFilename: string) => {
const schemaFilepath = path.join(schemasRoot, schemaFilename)
const typeName = schemaFilename.replace(SCHEMA_EXTENSION, '')
const jsonSchemaString = fs.readFileSync(schemaFilepath).toString('utf-8')
const schema = yaml.safeLoad(jsonSchemaString) as Record<string, unknown>
const validateFunction = ajv.compile(schema)
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
let code = pack(ajv, validateFunction)
code = prettier.format(code, { parser: 'babel' })
return fs.writeFile(path.join(outputDir, `validate${typeName}.js`), code)
}
}
async function ajvGenerate(schemasRoot: string, schemaFilenames: string[], outputDir: string) {
const ajv = new Ajv({ sourceCode: true, $data: true, jsonPointers: true, allErrors: true })
await concurrent.forEach(ajvAddSources(schemasRoot, ajv), schemaFilenames)
return concurrent.forEach(ajvGenerateOne(schemasRoot, ajv, outputDir), schemaFilenames)
}
export default async function generateTypes() {
const { moduleRoot } = findModuleRoot()
const schemasRoot = path.join(moduleRoot, 'schemas')
const tsOutputDir = path.join(moduleRoot, 'src', '.generated', 'latest')
const pyOutputDir = path.join(moduleRoot, 'data', 'generated')
const tsOutput = path.join(tsOutputDir, 'types.ts')
const pyOutput = path.join(pyOutputDir, 'types.py')
let schemaFilenames = await fs.readdir(schemasRoot)
schemaFilenames = schemaFilenames.filter((schemaFilename) => schemaFilename.endsWith(SCHEMA_EXTENSION))
await concurrent.forEach(async (d) => rimraf(`${d}/**`), [tsOutputDir, pyOutputDir])
await concurrent.forEach(async (d) => fs.mkdirp(d), [tsOutputDir, pyOutputDir])
return Promise.all([
quicktypesGenerate('typescript', schemasRoot, schemaFilenames, tsOutput, {
'converters': 'all-objects',
'nice-property-names': 'true',
'runtime-typecheck': 'true',
}),
quicktypesGenerate('python', schemasRoot, schemaFilenames, pyOutput, {
'python-version': '3.6',
'alphabetize-properties': 'false',
}),
ajvGenerate(schemasRoot, schemaFilenames, tsOutputDir),
])
}
generateTypes().catch((error) => {
console.error(error)
process.exit(1)
})