gatsby-source-wordpress
Version:
Source data from WordPress in an efficient and scalable way.
333 lines (320 loc) • 13.5 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
exports.__esModule = true;
exports.buildTypeName = exports.buildInterfacesListForType = void 0;
exports.diffBuiltTypeDefs = diffBuiltTypeDefs;
exports.typeIsASupportedScalar = exports.typeIsABuiltInScalar = exports.introspectionFieldTypeToSDL = exports.getTypesThatImplementInterfaceType = exports.getTypeSettingsByType = exports.findTypeKind = exports.findNamedTypeName = exports.findNamedType = exports.filterTypeDefinition = exports.fieldOfTypeWasFetched = void 0;
var _merge2 = _interopRequireDefault(require("lodash/merge"));
var _cloneDeep2 = _interopRequireDefault(require("lodash/cloneDeep"));
var _store = require("../../store");
var _typeFilters = require("./type-filters");
var _getGatsbyApi = require("../../utils/get-gatsby-api");
var _jsonDiff = require("json-diff");
var _formatLogMessage = require("../../utils/format-log-message");
var _report = require("../../utils/report");
const buildInterfacesListForType = type => {
let shouldAddNodeType = false;
const list = ((type === null || type === void 0 ? void 0 : type.interfaces) || []).filter(interfaceType => {
const interfaceTypeSettings = getTypeSettingsByType(interfaceType);
return !interfaceTypeSettings.exclude && fieldOfTypeWasFetched(interfaceType);
}).map(({
name
}) => {
if (name === `Node`) {
shouldAddNodeType = true;
}
return buildTypeName(name);
});
if (shouldAddNodeType) {
list.push(`Node`);
}
return list;
};
exports.buildInterfacesListForType = buildInterfacesListForType;
let ceIntCache = null;
const isWpgqlOneThirteenZeroPlus = () => {
if (ceIntCache !== null) {
return ceIntCache;
}
const {
typeMap
} = (0, _store.getStore)().getState().remoteSchema;
const connectionInterface = !!typeMap.get(`Connection`);
const edgeInterface = !!typeMap.get(`Edge`);
const isWpgqlOneThirteenZeroPlus = connectionInterface || edgeInterface;
ceIntCache = isWpgqlOneThirteenZeroPlus;
return isWpgqlOneThirteenZeroPlus;
};
/**
* This function namespaces typenames with a prefix
*/
const buildTypeName = (name, prefix) => {
if (!name || typeof name !== `string`) {
return null;
}
if (!prefix) {
prefix = (0, _getGatsbyApi.getPluginOptions)().schema.typePrefix;
}
// this is for our namespace type on the root { wp { ...fields } }
if (name === prefix) {
return name;
}
// Gatsby makes the same type, so we need to rename it to prevent conflicts
if (name === `Filter`) {
name = `FilterType`;
}
if (
// Starting in WPGraphQL 1.13.0, Gatsby and WPGraphQL both generate types ending in these strings for every node type in the schema, so we need to rename types to prevent conflicts.
// for users on older versions of WPGraphQL we should try to keep the schema how it was before
isWpgqlOneThirteenZeroPlus() && (name.endsWith(`Connection`) || name.endsWith(`Edge`) || name.endsWith(`PageInfo`))) {
name += `Type`;
}
if (name.startsWith(prefix)) {
return name;
}
return prefix + name;
};
/**
* Find the first type kind of a Type definition pulled via introspection
* @param {object} type
*/
exports.buildTypeName = buildTypeName;
const findTypeKind = type => {
if (type !== null && type !== void 0 && type.kind) {
return type.kind;
}
if (type !== null && type !== void 0 && type.ofType) {
return findTypeKind(type.ofType);
}
return null;
};
exports.findTypeKind = findTypeKind;
const findNamedType = type => {
if (!type) {
return null;
}
if (type.ofType) {
return findNamedType(type.ofType);
}
return type;
};
exports.findNamedType = findNamedType;
const findNamedTypeName = type => {
const namedType = findNamedType(type);
return namedType === null || namedType === void 0 ? void 0 : namedType.name;
};
exports.findNamedTypeName = findNamedTypeName;
const fieldOfTypeWasFetched = type => {
const {
fetchedTypes
} = (0, _store.getStore)().getState().remoteSchema;
const typeName = findNamedTypeName(type);
const typeWasFetched = !!fetchedTypes.get(typeName);
return typeWasFetched;
};
exports.fieldOfTypeWasFetched = fieldOfTypeWasFetched;
const implementingTypeCache = new Map();
const getTypesThatImplementInterfaceType = type => {
if (implementingTypeCache.has(type.name)) {
return implementingTypeCache.get(type.name);
}
const state = (0, _store.getStore)().getState();
const {
typeMap
} = state.remoteSchema;
const allTypes = typeMap.values();
const implementingTypes = Array.from(allTypes).filter(({
interfaces
}) => interfaces &&
// find types that implement this interface type
interfaces.find(singleInterface => singleInterface.name === type.name)).map(type => typeMap.get(type.name)).filter(type => type.kind !== `UNION` ||
// if this is a union type, make sure the union type has one or more member types, otherwise schema customization will throw an error
!!type.possibleTypes && !!type.possibleTypes.length);
implementingTypeCache.set(type.name, implementingTypes);
return implementingTypes;
};
exports.getTypesThatImplementInterfaceType = getTypesThatImplementInterfaceType;
const supportedScalars = [`Int`, `Float`, `String`, `Boolean`, `ID`, `Date`, `JSON`];
const typeIsABuiltInScalar = type =>
// @todo the next function and this one are redundant.
// see the next todo on how to fix the issue. If that todo is resolved, these functions will be identical. :(
supportedScalars.includes(findNamedTypeName(type));
exports.typeIsABuiltInScalar = typeIsABuiltInScalar;
const typeIsASupportedScalar = type => {
if (findTypeKind(type) !== `SCALAR`) {
// @todo returning true here seems wrong since a type that is not a scalar can't be a supported scalar... so there is some other logic elsewhere that is wrong
// making this return false causes errors in the schema
return true;
}
return supportedScalars.includes(findNamedTypeName(type));
};
exports.typeIsASupportedScalar = typeIsASupportedScalar;
const typeSettingCache = new Map();
// retrieves plugin settings for the provided type
const getTypeSettingsByType = type => {
if (!type) {
return {};
}
const typeName = findNamedTypeName(type);
if (!typeName) {
return {};
}
const cachedTypeSettings = typeSettingCache.get(typeName);
if (cachedTypeSettings) {
return cachedTypeSettings;
}
// the plugin options object containing every type setting
const allTypeSettings = (0, _store.getStore)().getState().gatsbyApi.pluginOptions.type;
const typeSettings = (0, _cloneDeep2.default)(allTypeSettings[typeName] || {});
// the type.__all plugin option which is applied to every type setting
const __allTypeSetting = (0, _cloneDeep2.default)(allTypeSettings.__all || {});
if (typeName === `MediaItem`) {
delete __allTypeSetting.limit;
delete typeSettings.limit;
}
if (typeSettings) {
const mergedSettings = (0, _merge2.default)(__allTypeSetting, typeSettings);
typeSettingCache.set(typeName, mergedSettings);
return mergedSettings;
}
typeSettingCache.set(typeName, __allTypeSetting);
return __allTypeSetting;
};
/**
* This is used to filter the automatically generated type definitions before they're added to the schema customization api.
*/
exports.getTypeSettingsByType = getTypeSettingsByType;
const filterTypeDefinition = (typeDefinition, typeBuilderApi, typeKind) => {
const filters = _typeFilters.typeDefinitionFilters.filter(filter => [typeBuilderApi.type.name, `__all`].includes(filter.typeName));
if (filters !== null && filters !== void 0 && filters.length) {
filters.forEach(filter => {
if (filter && typeof filter.typeDef === `function`) {
typeDefinition = filter.typeDef(typeDefinition, typeBuilderApi, typeKind);
}
});
}
return typeDefinition;
};
// we should be using graphql-js for this kind of thing, but unfortunately this project didn't use it from the beginning so it would be a huge lift to refactor to use it now. In the future we will be rewriting this plugin using a new Gatsby source plugin toolkit, and at that time we'll use graphql-js.
// from introspection field types this will return a value like:
// `String` or `[String]` or `[String!]!` or `[String]!` or `[[String]]` or `[[String]!]!` or `[[String]!]`, etc
exports.filterTypeDefinition = filterTypeDefinition;
const introspectionFieldTypeToSDL = fieldType => {
const openingTagsList = [];
const closingTagsList = [];
let reference = fieldType;
while (reference) {
switch (reference.kind) {
case `SCALAR`:
{
const normalizedTypeName = supportedScalars.includes(reference.name) ? reference.name : `JSON`;
openingTagsList.push(normalizedTypeName);
break;
}
case `OBJECT`:
case `INTERFACE`:
case `UNION`:
openingTagsList.push(buildTypeName(reference.name));
break;
case `NON_NULL`:
closingTagsList.push(`!`);
break;
case `LIST`:
openingTagsList.push(`[`);
closingTagsList.push(`]`);
break;
default:
break;
}
reference = reference.ofType;
}
return openingTagsList.join(``) + closingTagsList.reverse().join(``);
};
/**
* This is an expensive fn but it doesn't matter because it's only to show a debugging warning message when something is wrong.
*/
exports.introspectionFieldTypeToSDL = introspectionFieldTypeToSDL;
function mergeDuplicateTypesAndReturnDedupedList(typeDefs) {
const clonedDefs = (0, _cloneDeep2.default)(typeDefs);
const newList = [];
for (const def of clonedDefs) {
if (!def) {
continue;
}
const duplicateDefs = clonedDefs.filter(d => d.config.name === def.config.name);
const newDef = {};
for (const dDef of duplicateDefs) {
(0, _merge2.default)(newDef, dDef);
}
newList.push(newDef);
}
return newList;
}
/**
* Diffs the built types between this build and the last one with the same remote schema hash.
* This is to catch and add helpful error messages for when an inconsistent schema between builds is inadvertently created due to some bug
*/
async function diffBuiltTypeDefs(typeDefs) {
// only diff the schema if the user has opted in
if (process.env.WP_DIFF_SCHEMA_CUSTOMIZATION !== `true`) {
return;
}
const state = (0, _store.getStore)().getState();
const {
gatsbyApi: {
helpers: {
cache,
reporter
}
},
remoteSchema
} = state;
const previousTypeDefsKey = (0, _store.withPluginKey)(`previousTypeDefinitions`);
const previousTypeDefinitions = await cache.get(previousTypeDefsKey);
const typeDefString = JSON.stringify(typeDefs);
const typeNames = typeDefs.map(typeDef => typeDef.config.name);
const remoteSchemaChanged = !previousTypeDefinitions || (previousTypeDefinitions === null || previousTypeDefinitions === void 0 ? void 0 : previousTypeDefinitions.schemaHash) !== remoteSchema.schemaHash;
if (remoteSchemaChanged) {
await cache.set(previousTypeDefsKey, {
schemaHash: remoteSchema.schemaHash,
typeDefString,
typeNames
});
return;
}
// type defs are the same as last time, so don't check for missing/inconsistent types
if ((previousTypeDefinitions === null || previousTypeDefinitions === void 0 ? void 0 : previousTypeDefinitions.typeDefString) === typeDefString) {
return;
}
const missingTypeNames = previousTypeDefinitions.typeNames.filter(name => !typeNames.includes(name));
const previousTypeDefJson = mergeDuplicateTypesAndReturnDedupedList(JSON.parse(previousTypeDefinitions.typeDefString));
const newParsedTypeDefs = mergeDuplicateTypesAndReturnDedupedList(JSON.parse(typeDefString));
const changedTypeDefs = newParsedTypeDefs.map(typeDef => {
const previousTypeDef = previousTypeDefJson.find(previousTypeDef => previousTypeDef.config.name === typeDef.config.name);
const isDifferent = (0, _jsonDiff.diffString)(previousTypeDef, typeDef);
if (isDifferent) {
return `Typename ${typeDef.config.name} diff:\n${(0, _jsonDiff.diffString)(previousTypeDef, typeDef, {
// diff again to also show unchanged lines
full: true
})}`;
}
return null;
}).filter(Boolean);
let errorMessage = (0, _formatLogMessage.formatLogMessage)(`The remote WPGraphQL schema hasn't changed but local generated type definitions have. This is a bug, please open an issue on Github${missingTypeNames.length || changedTypeDefs.length ? ` and include the following text.` : ``}.${missingTypeNames.length ? `\n\nMissing type names: ${missingTypeNames.join(`\n`)}\n` : ``}${changedTypeDefs.length ? `\n\nChanged type defs:\n\n${changedTypeDefs.join(`\n`)}` : ``}`);
const maxErrorLength = 5000;
if (errorMessage.length > maxErrorLength) {
errorMessage = errorMessage.substring(0, maxErrorLength) + `\n\n...\n[Diff exceeded ${maxErrorLength} characters and was truncated]`;
}
if (process.env.WP_INCONSISTENT_SCHEMA_WARN !== `true`) {
reporter.info((0, _formatLogMessage.formatLogMessage)(`Panicking due to inconsistent schema customization. Turn this into a warning by setting process.env.WP_INCONSISTENT_SCHEMA_WARN to a string of "true"`));
reporter.panic({
id: _report.CODES.InconsistentSchemaCustomization,
context: {
sourceMessage: errorMessage
}
});
} else {
reporter.warn(errorMessage);
}
}
//# sourceMappingURL=helpers.js.map