UNPKG

10.3 kBJavaScriptView Raw
1import { debugLog } from 'graphql-codegen-core';
2import Listr from 'listr';
3import { normalizeOutputParam, normalizeInstanceOrArray, normalizeConfig } from './helpers';
4import { prettify } from './utils/prettier';
5import { Renderer } from './utils/listr-renderer';
6import { DetailedError } from './errors';
7import { loadSchema, loadDocuments } from './load';
8import { mergeSchemas } from './merge-schemas';
9import { GraphQLError, visit } from 'graphql';
10import { executePlugin, getPluginByName } from './execute-plugin';
11export async function executeCodegen(config) {
12 function wrapTask(task, source) {
13 return async () => {
14 try {
15 await task();
16 }
17 catch (error) {
18 if (source && !(error instanceof GraphQLError)) {
19 error.source = source;
20 }
21 throw error;
22 }
23 };
24 }
25 const result = [];
26 const commonListrOptions = {
27 exitOnError: true
28 };
29 let listr;
30 if (process.env.VERBOSE) {
31 listr = new Listr(Object.assign({}, commonListrOptions, { renderer: 'verbose', nonTTYRenderer: 'verbose' }));
32 }
33 else if (process.env.NODE_ENV === 'test') {
34 listr = new Listr(Object.assign({}, commonListrOptions, { renderer: 'silent', nonTTYRenderer: 'silent' }));
35 }
36 else {
37 listr = new Listr(Object.assign({}, commonListrOptions, { renderer: config.silent ? 'silent' : Renderer, nonTTYRenderer: config.silent ? 'silent' : 'default', collapse: true, clearOutput: false }));
38 }
39 let rootConfig = {};
40 let rootSchemas;
41 let rootDocuments;
42 let generates = {};
43 function normalize() {
44 /* Load Require extensions */
45 const requireExtensions = normalizeInstanceOrArray(config.require);
46 requireExtensions.forEach(mod => require(mod));
47 /* Root templates-config */
48 rootConfig = config.config || {};
49 /* Normalize root "schema" field */
50 rootSchemas = normalizeInstanceOrArray(config.schema);
51 /* Normalize root "documents" field */
52 rootDocuments = normalizeInstanceOrArray(config.documents);
53 /* Normalize "generators" field */
54 const generateKeys = Object.keys(config.generates);
55 if (generateKeys.length === 0) {
56 throw new DetailedError('Invalid Codegen Configuration!', `
57 Please make sure that your codegen config file contains the "generates" field, with a specification for the plugins you need.
58
59 It should looks like that:
60
61 schema:
62 - my-schema.graphql
63 generates:
64 my-file.ts:
65 - plugin1
66 - plugin2
67 - plugin3
68 `);
69 }
70 for (const filename of generateKeys) {
71 generates[filename] = normalizeOutputParam(config.generates[filename]);
72 if (generates[filename].plugins.length === 0) {
73 throw new DetailedError('Invalid Codegen Configuration!', `
74 Please make sure that your codegen config file has defined plugins list for output "${filename}".
75
76 It should looks like that:
77
78 schema:
79 - my-schema.graphql
80 generates:
81 my-file.ts:
82 - plugin1
83 - plugin2
84 - plugin3
85 `);
86 }
87 }
88 if (rootSchemas.length === 0 && Object.keys(generates).some(filename => generates[filename].schema.length === 0)) {
89 throw new DetailedError('Invalid Codegen Configuration!', `
90 Please make sure that your codegen config file contains either the "schema" field
91 or every generated file has its own "schema" field.
92
93 It should looks like that:
94 schema:
95 - my-schema.graphql
96
97 or:
98 generates:
99 path/to/output:
100 schema: my-schema.graphql
101 `);
102 }
103 }
104 listr.add({
105 title: 'Parse configuration',
106 task: () => normalize()
107 });
108 listr.add({
109 title: 'Generate outputs',
110 task: () => {
111 return new Listr(Object.keys(generates).map((filename, i) => ({
112 title: `Generate ${filename}`,
113 task: () => {
114 const outputConfig = generates[filename];
115 const outputFileTemplateConfig = outputConfig.config || {};
116 const outputDocuments = [];
117 let outputSchema;
118 const outputSpecificSchemas = normalizeInstanceOrArray(outputConfig.schema);
119 const outputSpecificDocuments = normalizeInstanceOrArray(outputConfig.documents);
120 return new Listr([
121 {
122 title: 'Load GraphQL schemas',
123 task: wrapTask(async () => {
124 debugLog(`[CLI] Loading Schemas`);
125 const allSchemas = [
126 ...rootSchemas.map(pointToScehma => loadSchema(pointToScehma, config)),
127 ...outputSpecificSchemas.map(pointToScehma => loadSchema(pointToScehma, config))
128 ];
129 if (allSchemas.length > 0) {
130 outputSchema = await mergeSchemas(await Promise.all(allSchemas));
131 }
132 }, filename)
133 },
134 {
135 title: 'Load GraphQL documents',
136 task: wrapTask(async () => {
137 debugLog(`[CLI] Loading Documents`);
138 const allDocuments = [...rootDocuments, ...outputSpecificDocuments];
139 for (const docDef of allDocuments) {
140 const documents = await loadDocuments(docDef, config);
141 if (documents.length > 0) {
142 outputDocuments.push(...documents);
143 }
144 }
145 }, filename)
146 },
147 {
148 title: 'Generate',
149 task: wrapTask(async () => {
150 debugLog(`[CLI] Generating output`);
151 const normalizedPluginsArray = normalizeConfig(outputConfig.plugins);
152 const output = await generateOutput({
153 filename,
154 plugins: normalizedPluginsArray,
155 schema: outputSchema,
156 documents: outputDocuments,
157 inheritedConfig: Object.assign({}, rootConfig, outputFileTemplateConfig),
158 pluginLoader: config.pluginLoader || require
159 });
160 result.push(output);
161 }, filename)
162 }
163 ], {
164 // it stops when one of tasks failed
165 exitOnError: true
166 });
167 }
168 })), {
169 // it doesn't stop when one of tasks failed, to finish at least some of outputs
170 exitOnError: false,
171 // run 4 at once
172 concurrent: 4
173 });
174 }
175 });
176 await listr.run();
177 return result;
178}
179function validateDocuments(schema, files) {
180 // duplicated names
181 const operationMap = {};
182 files.forEach(file => {
183 visit(file.content, {
184 OperationDefinition(node) {
185 if (typeof node.name !== 'undefined') {
186 if (!operationMap[node.name.value]) {
187 operationMap[node.name.value] = [];
188 }
189 operationMap[node.name.value].push(file.filePath);
190 }
191 }
192 });
193 });
194 const names = Object.keys(operationMap);
195 if (names.length) {
196 const duplicated = names.filter(name => operationMap[name].length > 1);
197 if (!duplicated.length) {
198 return;
199 }
200 const list = duplicated
201 .map(name => `
202 * ${name} found in:
203 ${operationMap[name]
204 .map(filepath => {
205 return `
206 - ${filepath}
207 `.trimRight();
208 })
209 .join('')}
210 `.trimRight())
211 .join('');
212 throw new DetailedError(`Not all operations have an unique name: ${duplicated.join(', ')}`, `
213 Not all operations have an unique name
214
215 ${list}
216 `);
217 }
218}
219export async function generateOutput(options) {
220 let output = '';
221 validateDocuments(options.schema, options.documents);
222 const pluginsPackages = await Promise.all(options.plugins.map(plugin => getPluginByName(Object.keys(plugin)[0], options.pluginLoader)));
223 // merged schema with parts added by plugins
224 const schema = pluginsPackages.reduce((schema, plugin) => {
225 return !plugin.addToSchema ? schema : mergeSchemas([schema, plugin.addToSchema]);
226 }, options.schema);
227 for (let i = 0; i < options.plugins.length; i++) {
228 const plugin = options.plugins[i];
229 const pluginPackage = pluginsPackages[i];
230 const name = Object.keys(plugin)[0];
231 const pluginConfig = plugin[name];
232 debugLog(`[CLI] Running plugin: ${name}`);
233 const result = await executePlugin({
234 name,
235 config: typeof pluginConfig !== 'object'
236 ? pluginConfig
237 : Object.assign({}, options.inheritedConfig, pluginConfig),
238 schema,
239 documents: options.documents,
240 outputFilename: options.filename,
241 allPlugins: options.plugins
242 }, pluginPackage);
243 debugLog(`[CLI] Completed executing plugin: ${name}`);
244 output += result;
245 }
246 return { filename: options.filename, content: await prettify(options.filename, output) };
247}
248//# sourceMappingURL=codegen.js.map
\No newline at end of file