@graphql-codegen/core
Version: 
<p align="center"> <img src="https://github.com/dotansimha/graphql-code-generator/blob/master/logo.png?raw=true" /> </p>
218 lines (217 loc) • 10 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.codegen = codegen;
exports.sortPrependValues = sortPrependValues;
const plugin_helpers_1 = require("@graphql-codegen/plugin-helpers");
const schema_1 = require("@graphql-tools/schema");
const utils_1 = require("@graphql-tools/utils");
const graphql_1 = require("graphql");
const execute_plugin_js_1 = require("./execute-plugin.js");
const utils_js_1 = require("./utils.js");
const transform_document_js_1 = require("./transform-document.js");
async function codegen(options) {
    const documents = options.documents || [];
    const profiler = options.profiler ?? (0, plugin_helpers_1.createNoopProfiler)();
    const skipDocumentsValidation = (0, utils_js_1.getSkipDocumentsValidationOption)(options);
    if (documents.length > 0 && (0, utils_js_1.shouldValidateDuplicateDocuments)(skipDocumentsValidation)) {
        await profiler.run(async () => validateDuplicateDocuments(documents), 'validateDuplicateDocuments');
    }
    const pluginPackages = Object.keys(options.pluginMap).map(key => options.pluginMap[key]);
    // merged schema with parts added by plugins
    const additionalTypeDefs = [];
    for (const plugin of pluginPackages) {
        const addToSchema = typeof plugin.addToSchema === 'function' ? plugin.addToSchema(options.config) : plugin.addToSchema;
        if (addToSchema) {
            additionalTypeDefs.push(addToSchema);
        }
    }
    const federationInConfig = (0, utils_js_1.pickFlag)('federation', options.config);
    const isFederation = (0, utils_js_1.prioritize)(federationInConfig, false);
    if (isFederation && !(0, utils_js_1.hasFederationSpec)(options.schemaAst || options.schema)) {
        additionalTypeDefs.push(plugin_helpers_1.federationSpec);
    }
    // Use mergeSchemas, only if there is no GraphQLSchema provided or the schema should be extended
    const mergeNeeded = !options.schemaAst || additionalTypeDefs.length > 0;
    const schemaInstance = await profiler.run(async () => {
        return mergeNeeded
            ? (0, schema_1.mergeSchemas)({
                // If GraphQLSchema provided, use it
                schemas: options.schemaAst ? [options.schemaAst] : [],
                // If GraphQLSchema isn't provided but DocumentNode is, use it to get the final GraphQLSchema
                typeDefs: options.schemaAst ? additionalTypeDefs : [options.schema, ...additionalTypeDefs],
                convertExtensions: true,
                assumeValid: true,
                assumeValidSDL: true,
                ...options.config,
            })
            : options.schemaAst;
    }, 'Create schema instance');
    const schemaDocumentNode = mergeNeeded || !options.schema ? (0, plugin_helpers_1.getCachedDocumentNodeFromSchema)(schemaInstance) : options.schema;
    const documentTransforms = Array.isArray(options.documentTransforms) ? options.documentTransforms : [];
    const transformedDocuments = await (0, transform_document_js_1.transformDocuments)({
        ...options,
        documentTransforms,
        schema: schemaDocumentNode,
        schemaAst: schemaInstance,
        profiler,
    });
    if (schemaInstance &&
        transformedDocuments.length > 0 &&
        (0, utils_js_1.shouldValidateDocumentsAgainstSchema)(skipDocumentsValidation)) {
        const ignored = ['NoUnusedFragments', 'NoUnusedVariables', 'KnownDirectives'];
        if (typeof skipDocumentsValidation === 'object' && skipDocumentsValidation.ignoreRules) {
            ignored.push(...(0, utils_1.asArray)(skipDocumentsValidation.ignoreRules));
        }
        const extraFragments = (0, utils_js_1.pickFlag)('externalFragments', options.config) || [];
        const errors = await profiler.run(() => {
            const fragments = extraFragments.map(f => ({
                location: f.importFrom,
                document: { kind: graphql_1.Kind.DOCUMENT, definitions: [f.node] },
            }));
            const rules = graphql_1.specifiedRules.filter(rule => !ignored.some(ignoredRule => rule.name.startsWith(ignoredRule)));
            const schemaHash = (0, utils_js_1.extractHashFromSchema)(schemaInstance);
            if (!schemaHash || !options.cache || transformedDocuments.some(d => typeof d.hash !== 'string')) {
                return Promise.resolve((0, utils_1.validateGraphQlDocuments)(schemaInstance, [...transformedDocuments.flatMap(d => d.document), ...fragments.flatMap(f => f.document)], rules));
            }
            const cacheKey = [schemaHash]
                .concat(transformedDocuments.map(doc => doc.hash))
                .concat(JSON.stringify(fragments))
                .join(',');
            return options.cache('documents-validation', cacheKey, () => Promise.resolve((0, utils_1.validateGraphQlDocuments)(schemaInstance, [...transformedDocuments.flatMap(d => d.document), ...fragments.flatMap(f => f.document)], rules)));
        }, 'Validate documents against schema');
        if (errors.length > 0) {
            throw new Error(`GraphQL Document Validation failed with ${errors.length} errors;
  ${errors.map((error, index) => `Error ${index}: ${error.stack}`).join('\n\n')}`);
        }
    }
    const prepend = new Set();
    const append = new Set();
    const output = await Promise.all(options.plugins.map(async (plugin) => {
        const name = Object.keys(plugin)[0];
        const pluginPackage = options.pluginMap[name];
        const pluginConfig = plugin[name] || {};
        const execConfig = typeof pluginConfig === 'object' ? { ...options.config, ...pluginConfig } : pluginConfig;
        const result = await profiler.run(() => (0, execute_plugin_js_1.executePlugin)({
            name,
            config: execConfig,
            parentConfig: options.config,
            schema: schemaDocumentNode,
            schemaAst: schemaInstance,
            documents: transformedDocuments,
            outputFilename: options.filename,
            allPlugins: options.plugins,
            skipDocumentsValidation: options.skipDocumentsValidation,
            pluginContext: options.pluginContext,
            profiler,
        }, pluginPackage), `Plugin ${name}`);
        if (typeof result === 'string') {
            return result || '';
        }
        if ((0, plugin_helpers_1.isComplexPluginOutput)(result)) {
            if (result.append && result.append.length > 0) {
                for (const item of result.append) {
                    if (item) {
                        append.add(item);
                    }
                }
            }
            if (result.prepend && result.prepend.length > 0) {
                for (const item of result.prepend) {
                    if (item) {
                        prepend.add(item);
                    }
                }
            }
            return result.content || '';
        }
        return '';
    }));
    return [...sortPrependValues(Array.from(prepend.values())), ...output, ...Array.from(append.values())]
        .filter(Boolean)
        .join('\n');
}
function resolveCompareValue(a) {
    if (a.startsWith('/*') || a.startsWith('//') || a.startsWith(' *') || a.startsWith(' */') || a.startsWith('*/')) {
        return 0;
    }
    if (a.startsWith('package')) {
        return 1;
    }
    if (a.startsWith('import')) {
        return 2;
    }
    return 3;
}
function sortPrependValues(values) {
    return values.sort((a, b) => {
        const aV = resolveCompareValue(a);
        const bV = resolveCompareValue(b);
        if (aV < bV) {
            return -1;
        }
        if (aV > bV) {
            return 1;
        }
        return 0;
    });
}
function validateDuplicateDocuments(files) {
    // duplicated names
    const definitionMap = {};
    function addDefinition(file, node, deduplicatedDefinitions) {
        if (typeof node.name !== 'undefined') {
            definitionMap[node.kind] ||= {};
            definitionMap[node.kind][node.name.value] ||= {
                paths: new Set(),
                contents: new Set(),
            };
            const definitionKindMap = definitionMap[node.kind];
            const length = definitionKindMap[node.name.value].contents.size;
            definitionKindMap[node.name.value].paths.add(file.location);
            definitionKindMap[node.name.value].contents.add((0, graphql_1.print)(node));
            if (length === definitionKindMap[node.name.value].contents.size) {
                return null;
            }
        }
        return deduplicatedDefinitions.add(node);
    }
    for (const file of files) {
        const deduplicatedDefinitions = new Set();
        (0, graphql_1.visit)(file.document, {
            OperationDefinition(node) {
                addDefinition(file, node, deduplicatedDefinitions);
            },
            FragmentDefinition(node) {
                addDefinition(file, node, deduplicatedDefinitions);
            },
        });
        file.document.definitions = Array.from(deduplicatedDefinitions);
    }
    const kinds = Object.keys(definitionMap);
    for (const kind of kinds) {
        const definitionKindMap = definitionMap[kind];
        const names = Object.keys(definitionKindMap);
        if (names.length) {
            const duplicated = names.filter(name => definitionKindMap[name].contents.size > 1);
            if (!duplicated.length) {
                continue;
            }
            const list = duplicated
                .map(name => `
        * ${name} found in:
          ${[...definitionKindMap[name].paths]
                .map(filepath => {
                return `
              - ${filepath}
            `.trimEnd();
            })
                .join('')}
    `.trimEnd())
                .join('');
            const definitionKindName = kind.replace('Definition', '').toLowerCase();
            throw new Error(`Not all ${definitionKindName}s have an unique name: ${duplicated.join(', ')}: \n
          ${list}
        `);
        }
    }
}