-
Notifications
You must be signed in to change notification settings - Fork 350
/
Copy pathgenerateTypes.ts
139 lines (115 loc) · 5.1 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
137
138
139
/* 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 FA 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 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)
}
}
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')
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 FA.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.0.0'
if (lang === 'typescript') {
code = prettier.format(code, { parser: 'typescript' })
// insert schemaVer constant
code = `export const schemaVer = '${schemaVer}'\n\n${code}`
// make schemaVer to be of an exact string type (for example '2.0.0', instead of 'string')
code = code.replace(`schemaVer: string`, `schemaVer: '${schemaVer}'`)
} 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)
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)
const validateFunction = ajv.compile(schema)
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @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 FA.concurrent.forEach(ajvAddSources(schemasRoot, ajv), schemaFilenames)
return FA.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 FA.concurrent.forEach(async (d) => rimraf(`${d}/**`), [tsOutputDir, pyOutputDir])
await FA.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)
})