-
Notifications
You must be signed in to change notification settings - Fork 222
/
Copy pathtype-generator.js
236 lines (205 loc) · 7.78 KB
/
type-generator.js
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
const fs = require('fs-extra')
const immutable = require('immutable')
const path = require('path')
const prettier = require('prettier')
const graphql = require('graphql/language')
const chalk = require('chalk')
const toolbox = require('gluegun/toolbox')
const Schema = require('./schema')
const Subgraph = require('./subgraph')
const DataSourceTemplateCodeGenerator = require('./codegen/template')
const Watcher = require('./watcher')
const { step, withSpinner } = require('./command-helpers/spinner')
const { applyMigrations } = require('./migrations')
const { GENERATED_FILE_NOTE } = require('./codegen/typescript')
const { displayPath } = require('./command-helpers/fs')
module.exports = class TypeGenerator {
constructor(options) {
this.options = options || {}
this.sourceDir =
this.options.sourceDir ||
(this.options.subgraphManifest && path.dirname(this.options.subgraphManifest))
this.protocol = this.options.protocol
this.protocolTypeGenerator = this.protocol.getTypeGenerator({
sourceDir: this.sourceDir,
outputDir: this.options.outputDir,
})
process.on('uncaughtException', function(e) {
toolbox.print.error(`UNCAUGHT EXCEPTION: ${e}`)
})
}
async generateTypes() {
try {
if (!this.options.skipMigrations && this.options.subgraphManifest) {
await applyMigrations({
sourceDir: this.sourceDir,
manifestFile: this.options.subgraphManifest,
})
}
let subgraph = await this.loadSubgraph()
// Not all protocols support/have ABIs.
if (this.protocol.hasABIs()) {
const abis = await this.protocolTypeGenerator.loadABIs(subgraph)
await this.protocolTypeGenerator.generateTypesForABIs(abis)
}
await this.generateTypesForDataSourceTemplates(subgraph)
// Not all protocols support/have ABIs.
if (this.protocol.hasABIs()) {
const templateAbis = await this.protocolTypeGenerator.loadDataSourceTemplateABIs(subgraph)
await this.protocolTypeGenerator.generateTypesForDataSourceTemplateABIs(templateAbis)
}
let schema = await this.loadSchema(subgraph)
await this.generateTypesForSchema(schema)
toolbox.print.success('\nTypes generated successfully\n')
return true
} catch (e) {
return false
}
}
async loadSubgraph({ quiet } = { quiet: false }) {
const subgraphLoadOptions = { protocol: this.protocol, skipValidation: false }
if (quiet) {
return this.options.subgraph
? this.options.subgraph
: Subgraph.load(this.options.subgraphManifest, subgraphLoadOptions).result
} else {
const manifestPath = displayPath(this.options.subgraphManifest)
return await withSpinner(
`Load subgraph from ${manifestPath}`,
`Failed to load subgraph from ${manifestPath}`,
`Warnings while loading subgraph from ${manifestPath}`,
async spinner => {
return this.options.subgraph
? this.options.subgraph
: Subgraph.load(this.options.subgraphManifest, subgraphLoadOptions)
},
)
}
}
async loadSchema(subgraph) {
let maybeRelativePath = subgraph.getIn(['schema', 'file'])
let absolutePath = path.resolve(this.sourceDir, maybeRelativePath)
return await withSpinner(
`Load GraphQL schema from ${displayPath(absolutePath)}`,
`Failed to load GraphQL schema from ${displayPath(absolutePath)}`,
`Warnings while loading GraphQL schema from ${displayPath(absolutePath)}`,
async spinner => {
let maybeRelativePath = subgraph.getIn(['schema', 'file'])
let absolutePath = path.resolve(this.sourceDir, maybeRelativePath)
return Schema.load(absolutePath)
},
)
}
async generateTypesForSchema(schema) {
return await withSpinner(
`Generate types for GraphQL schema`,
`Failed to generate types for GraphQL schema`,
`Warnings while generating types for GraphQL schema`,
async spinner => {
// Generate TypeScript module from schema
let codeGenerator = schema.codeGenerator()
let code = prettier.format(
[
GENERATED_FILE_NOTE,
...codeGenerator.generateModuleImports(),
...codeGenerator.generateDerivedLoader(),
...codeGenerator.generateTypes(),
].join('\n'),
{
parser: 'typescript',
},
)
let outputFile = path.join(this.options.outputDir, 'schema.ts')
step(spinner, 'Write types to', displayPath(outputFile))
await fs.mkdirs(path.dirname(outputFile))
await fs.writeFile(outputFile, code)
},
)
}
async generateTypesForDataSourceTemplates(subgraph) {
return await withSpinner(
`Generate types for data source templates`,
`Failed to generate types for data source templates`,
`Warnings while generating types for data source templates`,
async spinner => {
// Combine the generated code for all templates
let codeSegments = subgraph
.get('templates', immutable.List())
.reduce((codeSegments, template) => {
step(
spinner,
'Generate types for data source template',
`${template.get('name')}`,
)
let codeGenerator = new DataSourceTemplateCodeGenerator(template, this.protocol)
// Only generate module imports once, because they are identical for
// all types generated for data source templates.
if (codeSegments.isEmpty()) {
codeSegments = codeSegments.concat(codeGenerator.generateModuleImports())
}
return codeSegments.concat(codeGenerator.generateTypes())
}, immutable.List())
if (!codeSegments.isEmpty()) {
let code = prettier.format([GENERATED_FILE_NOTE, ...codeSegments].join('\n'), {
parser: 'typescript',
})
let outputFile = path.join(this.options.outputDir, 'templates.ts')
step(spinner, `Write types for templates to`, displayPath(outputFile))
await fs.mkdirs(path.dirname(outputFile))
await fs.writeFile(outputFile, code)
}
},
)
}
async getFilesToWatch() {
try {
let files = []
let subgraph = await this.loadSubgraph({ quiet: true })
// Add the subgraph manifest file
files.push(this.options.subgraphManifest)
// Add the GraphQL schema to the watched files
files.push(subgraph.getIn(['schema', 'file']))
// Add all file paths specified in manifest
subgraph.get('dataSources').map(dataSource => {
dataSource.getIn(['mapping', 'abis']).map(abi => {
files.push(abi.get('file'))
})
})
// Make paths absolute
return files.map(file => path.resolve(file))
} catch (e) {
throw Error(`Failed to load subgraph: ${e.message}`)
}
}
async watchAndGenerateTypes() {
let generator = this
let spinner
// Create watcher and generate types once and then on every change to a watched file
let watcher = new Watcher({
onReady: () => (spinner = toolbox.print.spin('Watching subgraph files')),
onTrigger: async changedFile => {
if (changedFile !== undefined) {
spinner.info(`File change detected: ${displayPath(changedFile)}\n`)
}
await generator.generateTypes()
spinner.start()
},
onCollectFiles: async () => await generator.getFilesToWatch(),
onError: error => {
spinner.stop()
toolbox.print.error(`${error}\n`)
spinner.start()
},
})
// Catch keyboard interrupt: close watcher and exit process
process.on('SIGINT', () => {
watcher.close()
process.exit()
})
try {
await watcher.watch()
} catch (e) {
toolbox.print.error(`${e.message}`)
}
}
}