UNPKG

10.3 kBJavaScriptView Raw
1"use strict";
2
3const fs = require(`fs-extra`);
4
5const {
6 EnumTypeComposer,
7 InputTypeComposer,
8 InterfaceTypeComposer,
9 ObjectTypeComposer,
10 ScalarTypeComposer,
11 UnionTypeComposer
12} = require(`graphql-compose`);
13
14const report = require(`gatsby-cli/lib/reporter`);
15
16const {
17 internalExtensionNames
18} = require(`./extensions`);
19
20const printTypeDefinitions = ({
21 config,
22 schemaComposer
23}) => {
24 if (!config) return Promise.resolve();
25 const {
26 path,
27 include = {},
28 exclude = {},
29 withFieldTypes
30 } = config || {};
31
32 if (!path) {
33 report.error(`Printing type definitions aborted. Please provide a file path.`);
34 return Promise.resolve();
35 }
36
37 if (fs.existsSync(path)) {
38 report.error(`Printing type definitions aborted. The file \`${path}\` already exists.`);
39 return Promise.resolve();
40 }
41
42 const internalTypes = [`Boolean`, `Buffer`, `Date`, `Float`, `ID`, `Int`, `Internal`, `InternalInput`, `JSON`, `Json`, `Node`, `NodeInput`, `Query`, `String`];
43 const internalPlugins = [`internal-data-bridge`];
44 const typesToExclude = exclude.types || [];
45 const pluginsToExclude = exclude.plugins || [];
46
47 const getName = tc => tc.name || tc.getTypeName();
48
49 const isInternalType = tc => {
50 const typeName = getName(tc);
51
52 if (internalTypes.includes(typeName)) {
53 return true;
54 }
55
56 const plugin = tc.getExtension(`plugin`);
57
58 if (internalPlugins.includes(plugin)) {
59 return true;
60 }
61
62 return false;
63 };
64
65 const shouldIncludeType = tc => {
66 const typeName = getName(tc);
67
68 if (typesToExclude.includes(typeName)) {
69 return false;
70 }
71
72 if (include.types && !include.types.includes(typeName)) {
73 return false;
74 }
75
76 const plugin = tc.getExtension(`plugin`);
77
78 if (pluginsToExclude.includes(plugin)) {
79 return false;
80 }
81
82 if (include.plugins && !include.plugins.includes(plugin)) {
83 return false;
84 }
85
86 return true;
87 }; // Save processed type names, not references to the type composers,
88 // because of how graphql-compose, at least in v6, processes
89 // inline types
90
91
92 const processedTypes = new Set();
93 const typeDefs = new Set();
94
95 const addType = tc => {
96 const typeName = getName(tc);
97
98 if (!processedTypes.has(typeName) && !isInternalType(tc)) {
99 processedTypes.add(typeName);
100 return typeDefs.add(tc);
101 }
102
103 processedTypes.add(typeName);
104 return null;
105 };
106
107 const addWithFieldTypes = tc => {
108 if (addType(tc) && (tc instanceof ObjectTypeComposer || tc instanceof InterfaceTypeComposer || tc instanceof InputTypeComposer)) {
109 if (tc instanceof ObjectTypeComposer) {
110 const interfaces = tc.getInterfaces();
111 interfaces.forEach(iface => {
112 const ifaceName = getName(iface);
113
114 if (ifaceName !== `Node`) {
115 addWithFieldTypes(schemaComposer.getAnyTC(ifaceName));
116 }
117 });
118 }
119
120 tc.getFieldNames().forEach(fieldName => {
121 const fieldType = tc.getFieldTC(fieldName);
122 addWithFieldTypes(fieldType);
123
124 if (!(tc instanceof InputTypeComposer)) {
125 const fieldArgs = tc.getFieldArgs(fieldName);
126 Object.keys(fieldArgs).forEach(argName => {
127 addWithFieldTypes(tc.getFieldArgTC(fieldName, argName));
128 });
129 }
130 });
131 }
132 };
133
134 schemaComposer.forEach(tc => {
135 if (!isInternalType(tc) && shouldIncludeType(tc)) {
136 if (withFieldTypes) {
137 addWithFieldTypes(tc);
138 } else {
139 addType(tc);
140 }
141 }
142 });
143 const printedTypeDefs = [`### Type definitions saved at ${new Date().toISOString()} ###`];
144
145 try {
146 typeDefs.forEach(tc => printedTypeDefs.push(printType(tc)));
147 report.info(`Writing GraphQL type definitions to ${path}`);
148 return fs.writeFile(path, printedTypeDefs.join(`\n\n`));
149 } catch (error) {
150 report.error(`Failed writing type definitions to \`${path}\`.`, error);
151 return Promise.resolve();
152 }
153};
154
155const printType = (tc, typeName) => {
156 if (tc instanceof ObjectTypeComposer) {
157 return printObjectType(tc);
158 } else if (tc instanceof InterfaceTypeComposer) {
159 return printInterfaceType(tc);
160 } else if (tc instanceof UnionTypeComposer) {
161 return printUnionType(tc);
162 } else if (tc instanceof EnumTypeComposer) {
163 return printEnumType(tc);
164 } else if (tc instanceof ScalarTypeComposer) {
165 return printScalarType(tc);
166 } else if (tc instanceof InputTypeComposer) {
167 return printInputObjectType(tc);
168 } else {
169 throw new Error(`Did not recognize type of ${typeName}.`);
170 }
171}; // ------------------------- graphql-js schemaPrinter -------------------------
172
173
174const {
175 astFromValue,
176 print,
177 GraphQLString,
178 DEFAULT_DEPRECATION_REASON
179} = require(`graphql`);
180
181const {
182 printBlockString
183} = require(`graphql/language/blockString`);
184
185const _ = require(`lodash`);
186
187const printScalarType = tc => {
188 const type = tc.getType();
189 return printDescription(type) + `scalar ${type.name}`;
190};
191
192const printObjectType = tc => {
193 const type = tc.getType();
194 const interfaces = type.getInterfaces();
195 const implementedInterfaces = interfaces.length ? ` implements ` + interfaces.map(i => i.name).join(` & `) : ``;
196 const extensions = tc.getExtensions();
197
198 if (tc.hasInterface(`Node`)) {
199 extensions.dontInfer = null;
200 }
201
202 const directives = tc.schemaComposer.getDirectives();
203 const printedDirectives = printDirectives(extensions, directives);
204 const fields = tc.hasInterface(`Node`) ? Object.values(type.getFields()).filter(field => ![`id`, `parent`, `children`, `internal`].includes(field.name)) : Object.values(type.getFields());
205 return printDescription(type) + `type ${type.name}${implementedInterfaces}${printedDirectives}` + printFields(fields, directives);
206};
207
208const printInterfaceType = tc => {
209 const type = tc.getType();
210 const extensions = tc.getExtensions();
211 const directives = tc.schemaComposer.getDirectives();
212 const printedDirectives = printDirectives(extensions, directives);
213 return printDescription(type) + `interface ${type.name}${printedDirectives}` + printFields(Object.values(type.getFields()), directives);
214};
215
216const printUnionType = tc => {
217 const type = tc.getType();
218 const types = type.getTypes();
219 const possibleTypes = types.length ? ` = ` + types.join(` | `) : ``;
220 return printDescription(type) + `union ` + type.name + possibleTypes;
221};
222
223const printEnumType = tc => {
224 const type = tc.getType();
225 const values = type.getValues().map((value, i) => printDescription(value, ` `, !i) + ` ` + value.name + printDeprecated(value));
226 return printDescription(type) + `enum ${type.name}` + printBlock(values);
227};
228
229const printInputObjectType = tc => {
230 const type = tc.getType();
231 const fields = Object.values(type.getFields()).map((f, i) => printDescription(f, ` `, !i) + ` ` + printInputValue(f));
232 return printDescription(type) + `input ${type.name}` + printBlock(fields);
233};
234
235const printFields = (fields, directives) => {
236 const printedFields = fields.map((f, i) => printDescription(f, ` `, !i) + ` ` + f.name + printArgs(f.args, ` `) + `: ` + String(f.type) + printDirectives(f.extensions || {}, directives) + printDeprecated(f));
237 return printBlock(printedFields);
238};
239
240const printBlock = items => items.length !== 0 ? ` {\n` + items.join(`\n`) + `\n}` : ``;
241
242const printArgs = (args, indentation = ``) => {
243 if (args.length === 0) {
244 return ``;
245 } // If all args have no description, print them on one line
246
247
248 if (args.every(arg => !arg.description)) {
249 return `(` + args.map(printInputValue).join(`, `) + `)`;
250 }
251
252 return `(\n` + args.map((arg, i) => printDescription(arg, ` ` + indentation, !i) + ` ` + indentation + printInputValue(arg)).join(`\n`) + `\n` + indentation + `)`;
253};
254
255const printInputValue = arg => {
256 const defaultAST = astFromValue(arg.defaultValue, arg.type);
257 let argDecl = arg.name + `: ` + String(arg.type);
258
259 if (defaultAST) {
260 argDecl += ` = ${print(defaultAST)}`;
261 }
262
263 return argDecl;
264};
265
266const printDirectives = (extensions, directives) => Object.entries(extensions).map(([name, args]) => {
267 if ([...internalExtensionNames, `deprecated`].includes(name)) return ``;
268 return ` @${name}` + printDirectiveArgs(args, directives.find(directive => directive.name === name));
269}).join(``);
270
271const printDirectiveArgs = (args, directive) => {
272 if (!args || !directive) {
273 return ``;
274 }
275
276 const directiveArgs = Object.entries(args);
277
278 if (directiveArgs.length === 0) {
279 return ``;
280 }
281
282 return `(` + directiveArgs.map(([name, value]) => {
283 const arg = directive.args && directive.args.find(arg => arg.name === name);
284 return arg && `${name}: ${print(astFromValue(value, arg.type))}`;
285 }).join(`, `) + `)`;
286};
287
288const printDeprecated = fieldOrEnumVal => {
289 if (!fieldOrEnumVal.isDeprecated) {
290 return ``;
291 }
292
293 const reason = fieldOrEnumVal.deprecationReason;
294 const reasonAST = astFromValue(reason, GraphQLString);
295
296 if (reasonAST && reason !== `` && reason !== DEFAULT_DEPRECATION_REASON) {
297 return ` @deprecated(reason: ` + print(reasonAST) + `)`;
298 }
299
300 return ` @deprecated`;
301};
302
303const printDescription = (def, indentation = ``, firstInBlock = true) => {
304 if (!def.description) {
305 return ``;
306 }
307
308 const lines = descriptionLines(def.description, 120 - indentation.length);
309 const text = lines.join(`\n`);
310 const preferMultipleLines = text.length > 70;
311 const blockString = printBlockString(text, ``, preferMultipleLines);
312 const prefix = indentation && !firstInBlock ? `\n` + indentation : indentation;
313 return prefix + blockString.replace(/\n/g, `\n` + indentation) + `\n`;
314};
315
316const descriptionLines = (description, maxLen) => {
317 const rawLines = description.split(`\n`);
318 return _.flatMap(rawLines, line => {
319 if (line.length < maxLen + 5) {
320 return line;
321 } // For > 120 character long lines, cut at space boundaries into sublines
322 // of ~80 chars.
323
324
325 return breakLine(line, maxLen);
326 });
327};
328
329const breakLine = (line, maxLen) => {
330 const parts = line.split(new RegExp(`((?: |^).{15,${maxLen - 40}}(?= |$))`));
331
332 if (parts.length < 4) {
333 return [line];
334 }
335
336 const sublines = [parts[0] + parts[1] + parts[2]];
337
338 for (let i = 3; i < parts.length; i += 2) {
339 sublines.push(parts[i].slice(1) + parts[i + 1]);
340 }
341
342 return sublines;
343};
344
345module.exports = {
346 printTypeDefinitions
347};
348//# sourceMappingURL=print.js.map
\No newline at end of file