263 lines
9.6 KiB
JavaScript
263 lines
9.6 KiB
JavaScript
'use strict';
|
|
|
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
|
const pluginHelpers = require('@graphql-codegen/plugin-helpers');
|
|
const graphql = require('graphql');
|
|
const utils = require('@graphql-tools/utils');
|
|
const merge = require('@graphql-tools/merge');
|
|
|
|
async function executePlugin(options, plugin) {
|
|
if (!plugin || !plugin.plugin || typeof plugin.plugin !== 'function') {
|
|
throw new pluginHelpers.DetailedError(`Invalid Custom Plugin "${options.name}"`, `
|
|
Plugin ${options.name} does not export a valid JS object with "plugin" function.
|
|
|
|
Make sure your custom plugin is written in the following form:
|
|
|
|
module.exports = {
|
|
plugin: (schema, documents, config) => {
|
|
return 'my-custom-plugin-content';
|
|
},
|
|
};
|
|
`);
|
|
}
|
|
const outputSchema = options.schemaAst || graphql.buildASTSchema(options.schema, options.config);
|
|
const documents = options.documents || [];
|
|
const pluginContext = options.pluginContext || {};
|
|
if (plugin.validate && typeof plugin.validate === 'function') {
|
|
try {
|
|
// FIXME: Sync validate signature with plugin signature
|
|
await plugin.validate(outputSchema, documents, options.config, options.outputFilename, options.allPlugins, pluginContext);
|
|
}
|
|
catch (e) {
|
|
throw new pluginHelpers.DetailedError(`Plugin "${options.name}" validation failed:`, `
|
|
${e.message}
|
|
`);
|
|
}
|
|
}
|
|
return Promise.resolve(plugin.plugin(outputSchema, documents, typeof options.config === 'object' ? { ...options.config } : options.config, {
|
|
outputFile: options.outputFilename,
|
|
allPlugins: options.allPlugins,
|
|
pluginContext,
|
|
}));
|
|
}
|
|
|
|
async function codegen(options) {
|
|
const documents = options.documents || [];
|
|
if (documents.length > 0 && !options.skipDocumentsValidation) {
|
|
validateDuplicateDocuments(documents);
|
|
}
|
|
const pluginPackages = Object.keys(options.pluginMap).map(key => options.pluginMap[key]);
|
|
if (!options.schemaAst) {
|
|
options.schemaAst = merge.mergeSchemas({
|
|
schemas: [],
|
|
typeDefs: [options.schema],
|
|
convertExtensions: true,
|
|
assumeValid: true,
|
|
assumeValidSDL: true,
|
|
...options.config,
|
|
});
|
|
}
|
|
// merged schema with parts added by plugins
|
|
let schemaChanged = false;
|
|
let schemaAst = pluginPackages.reduce((schemaAst, plugin) => {
|
|
const addToSchema = typeof plugin.addToSchema === 'function' ? plugin.addToSchema(options.config) : plugin.addToSchema;
|
|
if (!addToSchema) {
|
|
return schemaAst;
|
|
}
|
|
return merge.mergeSchemas({
|
|
schemas: [schemaAst],
|
|
typeDefs: [addToSchema],
|
|
});
|
|
}, options.schemaAst);
|
|
const federationInConfig = pickFlag('federation', options.config);
|
|
const isFederation = prioritize(federationInConfig, false);
|
|
if (isFederation &&
|
|
!schemaAst.getDirective('external') &&
|
|
!schemaAst.getDirective('requires') &&
|
|
!schemaAst.getDirective('provides') &&
|
|
!schemaAst.getDirective('key')) {
|
|
schemaChanged = true;
|
|
schemaAst = merge.mergeSchemas({
|
|
schemas: [schemaAst],
|
|
typeDefs: [pluginHelpers.federationSpec],
|
|
convertExtensions: true,
|
|
assumeValid: true,
|
|
assumeValidSDL: true,
|
|
});
|
|
}
|
|
if (schemaChanged) {
|
|
options.schema = graphql.parse(utils.printSchemaWithDirectives(schemaAst));
|
|
}
|
|
const skipDocumentValidation = typeof options.config === 'object' && !Array.isArray(options.config) && options.config.skipDocumentsValidation;
|
|
if (options.schemaAst && documents.length > 0 && !skipDocumentValidation) {
|
|
const extraFragments = options.config && options.config.externalFragments ? options.config.externalFragments : [];
|
|
const errors = await utils.validateGraphQlDocuments(options.schemaAst, [
|
|
...documents,
|
|
...extraFragments.map(f => ({
|
|
location: f.importFrom,
|
|
document: { kind: graphql.Kind.DOCUMENT, definitions: [f.node] },
|
|
})),
|
|
]);
|
|
utils.checkValidationErrors(errors);
|
|
}
|
|
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'
|
|
? pluginConfig
|
|
: {
|
|
...options.config,
|
|
...pluginConfig,
|
|
};
|
|
const result = await executePlugin({
|
|
name,
|
|
config: execConfig,
|
|
parentConfig: options.config,
|
|
schema: options.schema,
|
|
schemaAst,
|
|
documents: options.documents,
|
|
outputFilename: options.filename,
|
|
allPlugins: options.plugins,
|
|
skipDocumentsValidation: options.skipDocumentsValidation,
|
|
pluginContext: options.pluginContext,
|
|
}, pluginPackage);
|
|
if (typeof result === 'string') {
|
|
return result || '';
|
|
}
|
|
else if (pluginHelpers.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;
|
|
}
|
|
else if (a.startsWith('package')) {
|
|
return 1;
|
|
}
|
|
else if (a.startsWith('import')) {
|
|
return 2;
|
|
}
|
|
else {
|
|
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') {
|
|
if (!definitionMap[node.kind]) {
|
|
definitionMap[node.kind] = {};
|
|
}
|
|
if (!definitionMap[node.kind][node.name.value]) {
|
|
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(graphql.print(node));
|
|
if (length === definitionKindMap[node.name.value].contents.size) {
|
|
return null;
|
|
}
|
|
}
|
|
return deduplicatedDefinitions.add(node);
|
|
}
|
|
files.forEach(file => {
|
|
const deduplicatedDefinitions = new Set();
|
|
graphql.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);
|
|
kinds.forEach(kind => {
|
|
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) {
|
|
return;
|
|
}
|
|
const list = duplicated
|
|
.map(name => `
|
|
* ${name} found in:
|
|
${[...definitionKindMap[name].paths]
|
|
.map(filepath => {
|
|
return `
|
|
- ${filepath}
|
|
`.trimRight();
|
|
})
|
|
.join('')}
|
|
`.trimRight())
|
|
.join('');
|
|
const definitionKindName = kind.replace('Definition', '').toLowerCase();
|
|
throw new pluginHelpers.DetailedError(`Not all ${definitionKindName}s have an unique name: ${duplicated.join(', ')}`, `
|
|
Not all ${definitionKindName}s have an unique name
|
|
${list}
|
|
`);
|
|
}
|
|
});
|
|
}
|
|
function isObjectMap(obj) {
|
|
return obj && typeof obj === 'object' && !Array.isArray(obj);
|
|
}
|
|
function prioritize(...values) {
|
|
const picked = values.find(val => typeof val === 'boolean');
|
|
if (typeof picked !== 'boolean') {
|
|
return values[values.length - 1];
|
|
}
|
|
return picked;
|
|
}
|
|
function pickFlag(flag, config) {
|
|
return isObjectMap(config) ? config[flag] : undefined;
|
|
}
|
|
|
|
exports.codegen = codegen;
|
|
exports.executePlugin = executePlugin;
|
|
//# sourceMappingURL=index.cjs.js.map
|