"use strict"; const fs = require(`fs-extra`); const { EnumTypeComposer, InputTypeComposer, InterfaceTypeComposer, ObjectTypeComposer, ScalarTypeComposer, UnionTypeComposer } = require(`graphql-compose`); const report = require(`gatsby-cli/lib/reporter`); const { internalExtensionNames } = require(`./extensions`); const printTypeDefinitions = ({ config, schemaComposer }) => { if (!config) return Promise.resolve(); const { path, include = {}, exclude = {}, withFieldTypes } = config || {}; if (!path) { report.error(`Printing type definitions aborted. Please provide a file path.`); return Promise.resolve(); } if (fs.existsSync(path)) { report.error(`Printing type definitions aborted. The file \`${path}\` already exists.`); return Promise.resolve(); } const internalTypes = [`Boolean`, `Buffer`, `Date`, `Float`, `ID`, `Int`, `Internal`, `InternalInput`, `JSON`, `Json`, `Node`, `NodeInput`, `Query`, `String`]; const internalPlugins = [`internal-data-bridge`]; const typesToExclude = exclude.types || []; const pluginsToExclude = exclude.plugins || []; const getName = tc => tc.name || tc.getTypeName(); const isInternalType = tc => { const typeName = getName(tc); if (internalTypes.includes(typeName)) { return true; } const plugin = tc.getExtension(`plugin`); if (internalPlugins.includes(plugin)) { return true; } return false; }; const shouldIncludeType = tc => { const typeName = getName(tc); if (typesToExclude.includes(typeName)) { return false; } if (include.types && !include.types.includes(typeName)) { return false; } const plugin = tc.getExtension(`plugin`); if (pluginsToExclude.includes(plugin)) { return false; } if (include.plugins && !include.plugins.includes(plugin)) { return false; } return true; }; // Save processed type names, not references to the type composers, // because of how graphql-compose, at least in v6, processes // inline types const processedTypes = new Set(); const typeDefs = new Set(); const addType = tc => { const typeName = getName(tc); if (!processedTypes.has(typeName) && !isInternalType(tc)) { processedTypes.add(typeName); return typeDefs.add(tc); } processedTypes.add(typeName); return null; }; const addWithFieldTypes = tc => { if (addType(tc) && (tc instanceof ObjectTypeComposer || tc instanceof InterfaceTypeComposer || tc instanceof InputTypeComposer)) { if (tc instanceof ObjectTypeComposer) { const interfaces = tc.getInterfaces(); interfaces.forEach(iface => { const ifaceName = getName(iface); if (ifaceName !== `Node`) { addWithFieldTypes(schemaComposer.getAnyTC(ifaceName)); } }); } tc.getFieldNames().forEach(fieldName => { const fieldType = tc.getFieldTC(fieldName); addWithFieldTypes(fieldType); if (!(tc instanceof InputTypeComposer)) { const fieldArgs = tc.getFieldArgs(fieldName); Object.keys(fieldArgs).forEach(argName => { addWithFieldTypes(tc.getFieldArgTC(fieldName, argName)); }); } }); } }; schemaComposer.forEach(tc => { if (!isInternalType(tc) && shouldIncludeType(tc)) { if (withFieldTypes) { addWithFieldTypes(tc); } else { addType(tc); } } }); const printedTypeDefs = [`### Type definitions saved at ${new Date().toISOString()} ###`]; try { typeDefs.forEach(tc => printedTypeDefs.push(printType(tc))); report.info(`Writing GraphQL type definitions to ${path}`); return fs.writeFile(path, printedTypeDefs.join(`\n\n`)); } catch (error) { report.error(`Failed writing type definitions to \`${path}\`.`, error); return Promise.resolve(); } }; const printType = (tc, typeName) => { if (tc instanceof ObjectTypeComposer) { return printObjectType(tc); } else if (tc instanceof InterfaceTypeComposer) { return printInterfaceType(tc); } else if (tc instanceof UnionTypeComposer) { return printUnionType(tc); } else if (tc instanceof EnumTypeComposer) { return printEnumType(tc); } else if (tc instanceof ScalarTypeComposer) { return printScalarType(tc); } else if (tc instanceof InputTypeComposer) { return printInputObjectType(tc); } else { throw new Error(`Did not recognize type of ${typeName}.`); } }; // ------------------------- graphql-js schemaPrinter ------------------------- const { astFromValue, print, GraphQLString, DEFAULT_DEPRECATION_REASON } = require(`graphql`); const { printBlockString } = require(`graphql/language/blockString`); const { flatMap } = require(`graphql/polyfills/flatMap`); const printScalarType = tc => { const type = tc.getType(); return printDescription(type) + `scalar ${type.name}`; }; const printObjectType = tc => { const type = tc.getType(); const interfaces = type.getInterfaces(); const implementedInterfaces = interfaces.length ? ` implements ` + interfaces.map(i => i.name).join(` & `) : ``; const extensions = tc.getExtensions(); if (tc.hasInterface(`Node`)) { extensions.dontInfer = null; } const directives = tc.schemaComposer.getDirectives(); const printedDirectives = printDirectives(extensions, directives); const fields = tc.hasInterface(`Node`) ? Object.values(type.getFields()).filter(field => ![`id`, `parent`, `children`, `internal`].includes(field.name)) : Object.values(type.getFields()); return printDescription(type) + `type ${type.name}${implementedInterfaces}${printedDirectives}` + printFields(fields, directives); }; const printInterfaceType = tc => { const type = tc.getType(); const extensions = tc.getExtensions(); const directives = tc.schemaComposer.getDirectives(); const printedDirectives = printDirectives(extensions, directives); return printDescription(type) + `interface ${type.name}${printedDirectives}` + printFields(Object.values(type.getFields()), directives); }; const printUnionType = tc => { const type = tc.getType(); const types = type.getTypes(); const possibleTypes = types.length ? ` = ` + types.join(` | `) : ``; return printDescription(type) + `union ` + type.name + possibleTypes; }; const printEnumType = tc => { const type = tc.getType(); const values = type.getValues().map((value, i) => printDescription(value, ` `, !i) + ` ` + value.name + printDeprecated(value)); return printDescription(type) + `enum ${type.name}` + printBlock(values); }; const printInputObjectType = tc => { const type = tc.getType(); const fields = Object.values(type.getFields()).map((f, i) => printDescription(f, ` `, !i) + ` ` + printInputValue(f)); return printDescription(type) + `input ${type.name}` + printBlock(fields); }; const printFields = (fields, directives) => { const printedFields = fields.map((f, i) => printDescription(f, ` `, !i) + ` ` + f.name + printArgs(f.args, ` `) + `: ` + String(f.type) + printDirectives(f.extensions || {}, directives) + printDeprecated(f)); return printBlock(printedFields); }; const printBlock = items => items.length !== 0 ? ` {\n` + items.join(`\n`) + `\n}` : ``; const printArgs = (args, indentation = ``) => { if (args.length === 0) { return ``; } // If all args have no description, print them on one line if (args.every(arg => !arg.description)) { return `(` + args.map(printInputValue).join(`, `) + `)`; } return `(\n` + args.map((arg, i) => printDescription(arg, ` ` + indentation, !i) + ` ` + indentation + printInputValue(arg)).join(`\n`) + `\n` + indentation + `)`; }; const printInputValue = arg => { const defaultAST = astFromValue(arg.defaultValue, arg.type); let argDecl = arg.name + `: ` + String(arg.type); if (defaultAST) { argDecl += ` = ${print(defaultAST)}`; } return argDecl; }; const printDirectives = (extensions, directives) => Object.entries(extensions).map(([name, args]) => { if ([...internalExtensionNames, `deprecated`].includes(name)) return ``; return ` @${name}` + printDirectiveArgs(args, directives.find(directive => directive.name === name)); }).join(``); const printDirectiveArgs = (args, directive) => { if (!args || !directive) { return ``; } const directiveArgs = Object.entries(args); if (directiveArgs.length === 0) { return ``; } return `(` + directiveArgs.map(([name, value]) => { const arg = directive.args && directive.args.find(arg => arg.name === name); return arg && `${name}: ${print(astFromValue(value, arg.type))}`; }).join(`, `) + `)`; }; const printDeprecated = fieldOrEnumVal => { if (!fieldOrEnumVal.isDeprecated) { return ``; } const reason = fieldOrEnumVal.deprecationReason; const reasonAST = astFromValue(reason, GraphQLString); if (reasonAST && reason !== `` && reason !== DEFAULT_DEPRECATION_REASON) { return ` @deprecated(reason: ` + print(reasonAST) + `)`; } return ` @deprecated`; }; const printDescription = (def, indentation = ``, firstInBlock = true) => { if (!def.description) { return ``; } const lines = descriptionLines(def.description, 120 - indentation.length); const text = lines.join(`\n`); const preferMultipleLines = text.length > 70; const blockString = printBlockString(text, ``, preferMultipleLines); const prefix = indentation && !firstInBlock ? `\n` + indentation : indentation; return prefix + blockString.replace(/\n/g, `\n` + indentation) + `\n`; }; const descriptionLines = (description, maxLen) => { const rawLines = description.split(`\n`); return flatMap(rawLines, line => { if (line.length < maxLen + 5) { return line; } // For > 120 character long lines, cut at space boundaries into sublines // of ~80 chars. return breakLine(line, maxLen); }); }; const breakLine = (line, maxLen) => { const parts = line.split(new RegExp(`((?: |^).{15,${maxLen - 40}}(?= |$))`)); if (parts.length < 4) { return [line]; } const sublines = [parts[0] + parts[1] + parts[2]]; for (let i = 3; i < parts.length; i += 2) { sublines.push(parts[i].slice(1) + parts[i + 1]); } return sublines; }; module.exports = { printTypeDefinitions }; //# sourceMappingURL=print.js.map