1 | import { debugLog } from 'graphql-codegen-core';
|
2 | import Listr from 'listr';
|
3 | import { normalizeOutputParam, normalizeInstanceOrArray, normalizeConfig } from './helpers';
|
4 | import { prettify } from './utils/prettier';
|
5 | import { Renderer } from './utils/listr-renderer';
|
6 | import { DetailedError } from './errors';
|
7 | import { loadSchema, loadDocuments } from './load';
|
8 | import { mergeSchemas } from './merge-schemas';
|
9 | import { GraphQLError, visit } from 'graphql';
|
10 | import { executePlugin, getPluginByName } from './execute-plugin';
|
11 | export 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 |
|
45 | const requireExtensions = normalizeInstanceOrArray(config.require);
|
46 | requireExtensions.forEach(mod => require(mod));
|
47 |
|
48 | rootConfig = config.config || {};
|
49 |
|
50 | rootSchemas = normalizeInstanceOrArray(config.schema);
|
51 |
|
52 | rootDocuments = normalizeInstanceOrArray(config.documents);
|
53 |
|
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 |
|
165 | exitOnError: true
|
166 | });
|
167 | }
|
168 | })), {
|
169 |
|
170 | exitOnError: false,
|
171 |
|
172 | concurrent: 4
|
173 | });
|
174 | }
|
175 | });
|
176 | await listr.run();
|
177 | return result;
|
178 | }
|
179 | function validateDocuments(schema, files) {
|
180 |
|
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 | }
|
219 | export 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 |
|
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 |
|
\ | No newline at end of file |