gatsby-source-contentful
Version:
Gatsby source plugin for building websites using the Contentful CMS as a data source
762 lines (735 loc) • 30.4 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
exports.__esModule = true;
exports.makeTypeName = exports.makeId = exports.getLocalizedField = exports.createNodesForContentType = exports.createAssetNodes = exports.buildResolvableSet = exports.buildForeignReferenceMap = exports.buildFallbackChain = exports.buildEntryList = void 0;
var _isArray2 = _interopRequireDefault(require("lodash/isArray"));
var _mapValues2 = _interopRequireDefault(require("lodash/mapValues"));
var _isPlainObject2 = _interopRequireDefault(require("lodash/isPlainObject"));
var _isString2 = _interopRequireDefault(require("lodash/isString"));
var _each2 = _interopRequireDefault(require("lodash/each"));
var _isUndefined2 = _interopRequireDefault(require("lodash/isUndefined"));
var _camelCase2 = _interopRequireDefault(require("lodash/camelCase"));
var _upperFirst2 = _interopRequireDefault(require("lodash/upperFirst"));
var _jsonStringifySafe = _interopRequireDefault(require("json-stringify-safe"));
var _gatsbyCoreUtils = require("gatsby-core-utils");
var _semver = require("semver");
var _fastq = _interopRequireDefault(require("fastq"));
// @ts-check
/**
* @param {string} type
* @param {string} typePrefix
*/
const makeTypeName = (type, typePrefix) => (0, _upperFirst2.default)((0, _camelCase2.default)(`${typePrefix} ${type}`));
exports.makeTypeName = makeTypeName;
const GATSBY_VERSION_MANIFEST_V2 = `4.3.0`;
const gatsbyVersion = typeof _gatsbyCoreUtils.getGatsbyVersion === `function` && (0, _gatsbyCoreUtils.getGatsbyVersion)() || `0.0.0`;
const gatsbyVersionIsPrerelease = (0, _semver.prerelease)(gatsbyVersion);
const shouldUpgradeGatsbyVersion = (0, _semver.lt)(gatsbyVersion, GATSBY_VERSION_MANIFEST_V2) && !gatsbyVersionIsPrerelease;
const getLocalizedField = ({
field,
locale,
localesFallback
}) => {
if (!field) {
return null;
}
if (!(0, _isUndefined2.default)(field[locale.code])) {
return field[locale.code];
} else if (!(0, _isUndefined2.default)(locale.code) && !(0, _isUndefined2.default)(localesFallback[locale.code])) {
return getLocalizedField({
field,
locale: {
code: localesFallback[locale.code]
},
localesFallback
});
} else {
return null;
}
};
exports.getLocalizedField = getLocalizedField;
const buildFallbackChain = locales => {
const localesFallback = {};
(0, _each2.default)(locales, locale => localesFallback[locale.code] = locale.fallbackCode);
return localesFallback;
};
exports.buildFallbackChain = buildFallbackChain;
const makeGetLocalizedField = ({
locale,
localesFallback
}) => field => getLocalizedField({
field,
locale,
localesFallback
});
const makeId = ({
spaceId,
id,
currentLocale,
defaultLocale,
type
}) => {
const normalizedType = type.startsWith(`Deleted`) ? type.substring(`Deleted`.length) : type;
return currentLocale === defaultLocale ? `${spaceId}___${id}___${normalizedType}` : `${spaceId}___${id}___${normalizedType}___${currentLocale}`;
};
exports.makeId = makeId;
const makeMakeId = ({
currentLocale,
defaultLocale,
createNodeId
}) => (spaceId, id, type) => createNodeId(makeId({
spaceId,
id,
currentLocale,
defaultLocale,
type
}));
const buildEntryList = ({
contentTypeItems,
currentSyncData
}) => {
// Create buckets for each type sys.id that we care about (we will always want an array for each, even if its empty)
const map = new Map(contentTypeItems.map(contentType => [contentType.sys.id, []]));
// Now fill the buckets. Ignore entries for which there exists no bucket. (This happens when filterContentType is used)
currentSyncData.entries.map(entry => {
const arr = map.get(entry.sys.contentType.sys.id);
if (arr) {
arr.push(entry);
}
});
// Order is relevant, must map 1:1 to contentTypeItems array
return contentTypeItems.map(contentType => map.get(contentType.sys.id));
};
exports.buildEntryList = buildEntryList;
const buildResolvableSet = ({
entryList,
existingNodes = new Map(),
assets = []
}) => {
const resolvable = new Set();
existingNodes.forEach(node => {
// We need to add only root level resolvable (assets and entries)
// Derived nodes (markdown or JSON) will be recreated if needed.
resolvable.add(`${node.contentful_id}___${node.sys.type}`);
});
entryList.forEach(entries => {
entries.forEach(entry => resolvable.add(`${entry.sys.id}___${entry.sys.type}`));
});
assets.forEach(assetItem => resolvable.add(`${assetItem.sys.id}___${assetItem.sys.type}`));
return resolvable;
};
exports.buildResolvableSet = buildResolvableSet;
function cleanupReferencesFromEntry(foreignReferenceMapState, entry) {
const {
links,
backLinks
} = foreignReferenceMapState;
const entryId = entry.sys.id;
const entryLinks = links[entryId];
if (entryLinks) {
entryLinks.forEach(link => {
const backLinksForLink = backLinks[link];
if (backLinksForLink) {
const newBackLinks = backLinksForLink.filter(({
id
}) => id !== entryId);
if (newBackLinks.lenth > 0) {
backLinks[link] = newBackLinks;
} else {
delete backLinks[link];
}
}
});
}
delete links[entryId];
}
const buildForeignReferenceMap = ({
contentTypeItems,
entryList,
resolvable,
defaultLocale,
space,
useNameForId,
previousForeignReferenceMapState,
deletedEntries
}) => {
const foreignReferenceMapState = previousForeignReferenceMapState || {
links: {},
backLinks: {}
};
const {
links,
backLinks
} = foreignReferenceMapState;
for (const deletedEntry of deletedEntries) {
// remove stored entries from entry that is being deleted
cleanupReferencesFromEntry(foreignReferenceMapState, deletedEntry);
}
contentTypeItems.forEach((contentTypeItem, i) => {
// Establish identifier for content type
// Use `name` if specified, otherwise, use internal id (usually a natural-language constant,
// but sometimes a base62 uuid generated by Contentful, hence the option)
let contentTypeItemId;
if (useNameForId) {
contentTypeItemId = contentTypeItem.name.toLowerCase();
} else {
contentTypeItemId = contentTypeItem.sys.id.toLowerCase();
}
entryList[i].forEach(entryItem => {
// clear links added in previous runs for given entry, as we will recreate them anyway
cleanupReferencesFromEntry(foreignReferenceMapState, entryItem);
const entryItemFields = entryItem.fields;
Object.keys(entryItemFields).forEach(entryItemFieldKey => {
if (entryItemFields[entryItemFieldKey]) {
var _entryItemFieldValue$;
const entryItemFieldValue = entryItemFields[entryItemFieldKey][defaultLocale];
// If this is an array of single reference object
// add to the reference map, otherwise ignore.
if (Array.isArray(entryItemFieldValue)) {
if (entryItemFieldValue[0] && entryItemFieldValue[0].sys && entryItemFieldValue[0].sys.type && entryItemFieldValue[0].sys.id) {
entryItemFieldValue.forEach(v => {
const key = `${v.sys.id}___${v.sys.linkType || v.sys.type}`;
// Don't create link to an unresolvable field.
if (!resolvable.has(key)) {
return;
}
if (!backLinks[key]) {
backLinks[key] = [];
}
backLinks[key].push({
name: `${contentTypeItemId}___NODE`,
id: entryItem.sys.id,
spaceId: space.sys.id,
type: entryItem.sys.type
});
if (!links[entryItem.sys.id]) {
links[entryItem.sys.id] = [];
}
links[entryItem.sys.id].push(key);
});
}
} else if (entryItemFieldValue !== null && entryItemFieldValue !== void 0 && (_entryItemFieldValue$ = entryItemFieldValue.sys) !== null && _entryItemFieldValue$ !== void 0 && _entryItemFieldValue$.type && entryItemFieldValue.sys.id) {
const key = `${entryItemFieldValue.sys.id}___${entryItemFieldValue.sys.linkType || entryItemFieldValue.sys.type}`;
// Don't create link to an unresolvable field.
if (!resolvable.has(key)) {
return;
}
if (!backLinks[key]) {
backLinks[key] = [];
}
backLinks[key].push({
name: `${contentTypeItemId}___NODE`,
id: entryItem.sys.id,
spaceId: space.sys.id,
type: entryItem.sys.type
});
if (!links[entryItem.sys.id]) {
links[entryItem.sys.id] = [];
}
links[entryItem.sys.id].push(key);
}
}
});
});
});
return foreignReferenceMapState;
};
exports.buildForeignReferenceMap = buildForeignReferenceMap;
function prepareTextNode(id, node, key, text) {
const str = (0, _isString2.default)(text) ? text : ``;
const textNode = {
id,
parent: node.id,
children: [],
[key]: str,
internal: {
type: (0, _camelCase2.default)(`${node.internal.type} ${key} TextNode`),
mediaType: `text/markdown`,
content: str,
// entryItem.sys.updatedAt is source of truth from contentful
contentDigest: node.updatedAt
},
sys: {
type: `TextNode`
}
};
node.children = node.children.concat([id]);
return textNode;
}
function prepareJSONNode(id, node, key, content) {
const str = JSON.stringify(content);
const JSONNode = {
...((0, _isPlainObject2.default)(content) ? {
...content
} : {
content: content
}),
id,
parent: node.id,
children: [],
internal: {
type: (0, _camelCase2.default)(`${node.internal.type} ${key} JSONNode`),
mediaType: `application/json`,
content: str,
// entryItem.sys.updatedAt is source of truth from contentful
contentDigest: node.updatedAt
},
sys: {
type: `JsonNode`
}
};
node.children = node.children.concat([id]);
return JSONNode;
}
let numberOfContentSyncDebugLogs = 0;
const maxContentSyncDebugLogTimes = 50;
let warnOnceForNoSupport = false;
let warnOnceToUpgradeGatsby = false;
/**
* This fn creates node manifests which are used for Gatsby Cloud Previews via the Content Sync API/feature.
* Content Sync routes a user from Contentful to a page created from the entry data they're interested in previewing.
*/
function contentfulCreateNodeManifest({
pluginConfig,
entryItem,
entryNode,
space,
unstable_createNodeManifest
}) {
const isPreview = pluginConfig.get(`host`) === `preview.contentful.com`;
const createNodeManifestIsSupported = typeof unstable_createNodeManifest === `function`;
const shouldCreateNodeManifest = isPreview && createNodeManifestIsSupported;
const updatedAt = entryItem.sys.updatedAt;
const manifestId = `${space.sys.id}-${entryItem.sys.id}-${updatedAt}`;
if (process.env.CONTENTFUL_DEBUG_NODE_MANIFEST === `true` && numberOfContentSyncDebugLogs <= maxContentSyncDebugLogTimes) {
numberOfContentSyncDebugLogs++;
console.info(JSON.stringify({
isPreview,
createNodeManifestIsSupported,
shouldCreateNodeManifest,
manifestId,
entryItemSysUpdatedAt: updatedAt
}));
}
if (shouldCreateNodeManifest) {
if (shouldUpgradeGatsbyVersion && !warnOnceToUpgradeGatsby) {
console.warn(`Your site is doing more work than it needs to for Preview, upgrade to Gatsby ^${GATSBY_VERSION_MANIFEST_V2} for better performance`);
warnOnceToUpgradeGatsby = true;
}
unstable_createNodeManifest({
manifestId,
node: entryNode,
updatedAtUTC: updatedAt
});
} else if (isPreview && !createNodeManifestIsSupported && !warnOnceForNoSupport) {
console.warn(`Contentful: Your version of Gatsby core doesn't support Content Sync (via the unstable_createNodeManifest action). Please upgrade to the latest version to use Content Sync in your site.`);
warnOnceForNoSupport = true;
}
}
function makeQueuedCreateNode({
nodeCount,
createNode
}) {
if (nodeCount > 5000) {
let createdNodeCount = 0;
const createNodesQueue = (0, _fastq.default)((node, cb) => {
function runCreateNode() {
const maybeNodePromise = createNode(node);
// checking for `.then` is vastly more performant than using `instanceof Promise`
if (`then` in maybeNodePromise) {
maybeNodePromise.then(() => {
cb(null);
});
} else {
cb(null);
}
}
if (++createdNodeCount % 100 === 0) {
setImmediate(() => {
runCreateNode();
});
} else {
runCreateNode();
}
}, 10);
const queueFinished = new Promise(resolve => {
createNodesQueue.drain = () => {
resolve(null);
};
});
return {
create: (node, callback) => createNodesQueue.push(node, callback),
createNodesPromise: queueFinished
};
} else {
const nodePromises = [];
const queueFinished = () => Promise.all(nodePromises);
return {
create: node => nodePromises.push(createNode(node)),
createNodesPromise: queueFinished()
};
}
}
const createNodesForContentType = async ({
contentTypeItem,
restrictedNodeFields,
conflictFieldPrefix,
entries,
unstable_createNodeManifest,
createNode,
createNodeId,
getNode,
resolvable,
foreignReferenceMap,
defaultLocale,
locales,
space,
useNameForId,
pluginConfig
}) => {
const {
create,
createNodesPromise
} = makeQueuedCreateNode({
nodeCount: entries.length,
createNode
});
const typePrefix = pluginConfig.get(`typePrefix`);
// Establish identifier for content type
// Use `name` if specified, otherwise, use internal id (usually a natural-language constant,
// but sometimes a base62 uuid generated by Contentful, hence the option)
let contentTypeItemId;
if (useNameForId) {
contentTypeItemId = contentTypeItem.name;
} else {
contentTypeItemId = contentTypeItem.sys.id;
}
// Create a node for the content type
const contentTypeNode = {
id: createNodeId(contentTypeItemId),
parent: null,
children: [],
name: contentTypeItem.name,
displayField: contentTypeItem.displayField,
description: contentTypeItem.description,
internal: {
type: `${makeTypeName(`ContentType`, typePrefix)}`,
contentDigest: contentTypeItem.sys.updatedAt
},
sys: {
type: contentTypeItem.sys.type
}
};
create(contentTypeNode);
locales.forEach(locale => {
const localesFallback = buildFallbackChain(locales);
const mId = makeMakeId({
currentLocale: locale.code,
defaultLocale,
createNodeId
});
const getField = makeGetLocalizedField({
locale,
localesFallback
});
// Warn about any field conflicts
const conflictFields = [];
contentTypeItem.fields.forEach(contentTypeItemField => {
const fieldName = contentTypeItemField.id;
if (restrictedNodeFields.includes(fieldName)) {
console.log(`Restricted field found for ContentType ${contentTypeItemId} and field ${fieldName}. Prefixing with ${conflictFieldPrefix}.`);
conflictFields.push(fieldName);
}
});
const childrenNodes = [];
// First create nodes for each of the entries of that content type
const entryNodes = entries.map(entryItem => {
const entryNodeId = mId(space.sys.id, entryItem.sys.id, entryItem.sys.type);
const existingNode = getNode(entryNodeId);
if ((existingNode === null || existingNode === void 0 ? void 0 : existingNode.updatedAt) === entryItem.sys.updatedAt) {
// The Contentful model has `.sys.updatedAt` leading for an entry. If the updatedAt value
// of an entry did not change, then we can trust that none of its children were changed either.
return null;
}
// Get localized fields.
const entryItemFields = (0, _mapValues2.default)(entryItem.fields, (v, k) => {
const fieldProps = contentTypeItem.fields.find(field => field.id === k);
const localizedField = fieldProps.localized ? getField(v) : v[defaultLocale];
return localizedField;
});
// Prefix any conflicting fields
// https://github.com/gatsbyjs/gatsby/pull/1084#pullrequestreview-41662888
conflictFields.forEach(conflictField => {
entryItemFields[`${conflictFieldPrefix}${conflictField}`] = entryItemFields[conflictField];
delete entryItemFields[conflictField];
});
// Add linkages to other nodes based on foreign references
Object.keys(entryItemFields).forEach(entryItemFieldKey => {
if (entryItemFields[entryItemFieldKey]) {
var _entryItemFieldValue$4;
const entryItemFieldValue = entryItemFields[entryItemFieldKey];
if (Array.isArray(entryItemFieldValue)) {
var _entryItemFieldValue$2, _entryItemFieldValue$3;
if (((_entryItemFieldValue$2 = entryItemFieldValue[0]) === null || _entryItemFieldValue$2 === void 0 ? void 0 : (_entryItemFieldValue$3 = _entryItemFieldValue$2.sys) === null || _entryItemFieldValue$3 === void 0 ? void 0 : _entryItemFieldValue$3.type) === `Link`) {
// Check if there are any values in entryItemFieldValue to prevent
// creating an empty node field in case when original key field value
// is empty due to links to missing entities
const resolvableEntryItemFieldValue = entryItemFieldValue.filter(function (v) {
return resolvable.has(`${v.sys.id}___${v.sys.linkType || v.sys.type}`);
}).map(function (v) {
return mId(space.sys.id, v.sys.id, v.sys.linkType || v.sys.type);
});
if (resolvableEntryItemFieldValue.length !== 0) {
entryItemFields[`${entryItemFieldKey}___NODE`] = resolvableEntryItemFieldValue;
}
delete entryItemFields[entryItemFieldKey];
}
} else if ((entryItemFieldValue === null || entryItemFieldValue === void 0 ? void 0 : (_entryItemFieldValue$4 = entryItemFieldValue.sys) === null || _entryItemFieldValue$4 === void 0 ? void 0 : _entryItemFieldValue$4.type) === `Link`) {
if (resolvable.has(`${entryItemFieldValue.sys.id}___${entryItemFieldValue.sys.linkType || entryItemFieldValue.sys.type}`)) {
entryItemFields[`${entryItemFieldKey}___NODE`] = mId(space.sys.id, entryItemFieldValue.sys.id, entryItemFieldValue.sys.linkType || entryItemFieldValue.sys.type);
}
delete entryItemFields[entryItemFieldKey];
}
}
});
// Add reverse linkages if there are any for this node
const foreignReferences = foreignReferenceMap[`${entryItem.sys.id}___${entryItem.sys.type}`];
if (foreignReferences) {
foreignReferences.forEach(foreignReference => {
const existingReference = entryItemFields[foreignReference.name];
if (existingReference) {
// If the existing reference is a string, we're dealing with a
// many-to-one reference which has already been recorded, so we can
// skip it. However, if it is an array, add it:
if (Array.isArray(existingReference)) {
entryItemFields[foreignReference.name].push(mId(foreignReference.spaceId, foreignReference.id, foreignReference.type));
}
} else {
// If there is one foreign reference, there can be many.
// Best to be safe and put it in an array to start with.
entryItemFields[foreignReference.name] = [mId(foreignReference.spaceId, foreignReference.id, foreignReference.type)];
}
});
}
let entryNode = {
id: entryNodeId,
spaceId: space.sys.id,
contentful_id: entryItem.sys.id,
createdAt: entryItem.sys.createdAt,
updatedAt: entryItem.sys.updatedAt,
parent: null,
children: [],
internal: {
type: `${makeTypeName(contentTypeItemId, typePrefix)}`
},
sys: {
type: entryItem.sys.type
}
};
contentfulCreateNodeManifest({
pluginConfig,
entryItem,
entryNode,
space,
unstable_createNodeManifest
});
// Revision applies to entries, assets, and content types
if (entryItem.sys.revision) {
entryNode.sys.revision = entryItem.sys.revision;
}
// Content type applies to entries only
if (entryItem.sys.contentType) {
entryNode.sys.contentType = entryItem.sys.contentType;
}
// Replace text fields with text nodes so we can process their markdown
// into HTML.
Object.keys(entryItemFields).forEach(entryItemFieldKey => {
// Ignore fields with "___node" as they're already handled
// and won't be a text field.
if (entryItemFieldKey.includes(`___`)) {
return;
}
const fieldType = contentTypeItem.fields.find(f => (restrictedNodeFields.includes(f.id) ? `${conflictFieldPrefix}${f.id}` : f.id) === entryItemFieldKey).type;
if (fieldType === `Text`) {
const textNodeId = createNodeId(`${entryNodeId}${entryItemFieldKey}TextNode`);
// The Contentful model has `.sys.updatedAt` leading for an entry. If the updatedAt value
// of an entry did not change, then we can trust that none of its children were changed either.
// (That's why child nodes use the updatedAt of the parent node as their digest, too)
const existingNode = getNode(textNodeId);
if ((existingNode === null || existingNode === void 0 ? void 0 : existingNode.updatedAt) !== entryItem.sys.updatedAt) {
const textNode = prepareTextNode(textNodeId, entryNode, entryItemFieldKey, entryItemFields[entryItemFieldKey]);
childrenNodes.push(textNode);
}
entryItemFields[`${entryItemFieldKey}___NODE`] = textNodeId;
delete entryItemFields[entryItemFieldKey];
} else if (fieldType === `RichText` && (0, _isPlainObject2.default)(entryItemFields[entryItemFieldKey])) {
const fieldValue = entryItemFields[entryItemFieldKey];
const rawReferences = [];
// Locate all Contentful Links within the rich text data
const traverse = obj => {
// eslint-disable-next-line guard-for-in
for (const k in obj) {
const v = obj[k];
if (v && v.sys && v.sys.type === `Link`) {
rawReferences.push(v);
} else if (v && typeof v === `object`) {
traverse(v);
}
}
};
traverse(fieldValue);
// Build up resolvable reference list
const resolvableReferenceIds = new Set();
rawReferences.filter(function (v) {
return resolvable.has(`${v.sys.id}___${v.sys.linkType || v.sys.type}`);
}).forEach(function (v) {
resolvableReferenceIds.add(mId(space.sys.id, v.sys.id, v.sys.linkType || v.sys.type));
});
entryItemFields[entryItemFieldKey] = {
raw: (0, _jsonStringifySafe.default)(fieldValue),
references___NODE: [...resolvableReferenceIds]
};
} else if (fieldType === `Object` && (0, _isPlainObject2.default)(entryItemFields[entryItemFieldKey])) {
const jsonNodeId = createNodeId(`${entryNodeId}${entryItemFieldKey}JSONNode`);
// The Contentful model has `.sys.updatedAt` leading for an entry. If the updatedAt value
// of an entry did not change, then we can trust that none of its children were changed either.
// (That's why child nodes use the updatedAt of the parent node as their digest, too)
const existingNode = getNode(jsonNodeId);
if ((existingNode === null || existingNode === void 0 ? void 0 : existingNode.updatedAt) !== entryItem.sys.updatedAt) {
const jsonNode = prepareJSONNode(jsonNodeId, entryNode, entryItemFieldKey, entryItemFields[entryItemFieldKey]);
childrenNodes.push(jsonNode);
}
entryItemFields[`${entryItemFieldKey}___NODE`] = jsonNodeId;
delete entryItemFields[entryItemFieldKey];
} else if (fieldType === `Object` && (0, _isArray2.default)(entryItemFields[entryItemFieldKey])) {
entryItemFields[`${entryItemFieldKey}___NODE`] = [];
entryItemFields[entryItemFieldKey].forEach((obj, i) => {
const jsonNodeId = createNodeId(`${entryNodeId}${entryItemFieldKey}${i}JSONNode`);
// The Contentful model has `.sys.updatedAt` leading for an entry. If the updatedAt value
// of an entry did not change, then we can trust that none of its children were changed either.
// (That's why child nodes use the updatedAt of the parent node as their digest, too)
const existingNode = getNode(jsonNodeId);
if ((existingNode === null || existingNode === void 0 ? void 0 : existingNode.updatedAt) !== entryItem.sys.updatedAt) {
const jsonNode = prepareJSONNode(jsonNodeId, entryNode, entryItemFieldKey, obj);
childrenNodes.push(jsonNode);
}
entryItemFields[`${entryItemFieldKey}___NODE`].push(jsonNodeId);
});
delete entryItemFields[entryItemFieldKey];
}
});
entryNode = {
...entryItemFields,
...entryNode,
node_locale: locale.code
};
// The content of an entry is guaranteed to be updated if and only if the .sys.updatedAt field changed
entryNode.internal.contentDigest = entryItem.sys.updatedAt;
// Link tags
if (pluginConfig.get(`enableTags`)) {
entryNode.metadata = {
tags___NODE: entryItem.metadata.tags.map(tag => createNodeId(`ContentfulTag__${space.sys.id}__${tag.sys.id}`))
};
}
return entryNode;
});
entryNodes.forEach((entryNode, index) => {
// entry nodes may be undefined here if the node was previously already created
if (entryNode) {
create(entryNode, () => {
entryNodes[index] = undefined;
});
}
});
childrenNodes.forEach((entryNode, index) => {
// entry nodes may be undefined here if the node was previously already created
if (entryNode) {
create(entryNode, () => {
childrenNodes[index] = undefined;
});
}
});
});
return createNodesPromise;
};
exports.createNodesForContentType = createNodesForContentType;
const createAssetNodes = async ({
assetItem,
createNode,
createNodeId,
defaultLocale,
locales,
space,
pluginConfig
}) => {
const {
create,
createNodesPromise
} = makeQueuedCreateNode({
createNode,
nodeCount: locales.length
});
const assetNodes = [];
locales.forEach(locale => {
var _getField, _assetItem$fields, _file$details$image$w, _file$details, _file$details$image, _file$details$image$h, _file$details2, _file$details2$image, _file$details$size, _file$details3;
const localesFallback = buildFallbackChain(locales);
const mId = makeMakeId({
currentLocale: locale.code,
defaultLocale,
createNodeId
});
const getField = makeGetLocalizedField({
locale,
localesFallback
});
const file = (_getField = getField((_assetItem$fields = assetItem.fields) === null || _assetItem$fields === void 0 ? void 0 : _assetItem$fields.file)) !== null && _getField !== void 0 ? _getField : null;
// Skip empty and unprocessed assets in Preview API
if (!file || !file.url || !file.contentType || !file.fileName) {
return;
}
const assetNode = {
contentful_id: assetItem.sys.id,
spaceId: space.sys.id,
id: mId(space.sys.id, assetItem.sys.id, assetItem.sys.type),
createdAt: assetItem.sys.createdAt,
updatedAt: assetItem.sys.updatedAt,
parent: null,
children: [],
file,
title: assetItem.fields.title ? getField(assetItem.fields.title) : ``,
description: assetItem.fields.description ? getField(assetItem.fields.description) : ``,
node_locale: locale.code,
internal: {
type: makeTypeName(`Asset`, pluginConfig.get(`typePrefix`))
},
sys: {
type: assetItem.sys.type
},
url: `https:${file.url}`,
placeholderUrl: `https:${file.url}?w=%width%&h=%height%`,
// These fields are optional for edge cases in the Preview API and Contentfuls asset processing
mimeType: file.contentType,
filename: file.fileName,
width: (_file$details$image$w = (_file$details = file.details) === null || _file$details === void 0 ? void 0 : (_file$details$image = _file$details.image) === null || _file$details$image === void 0 ? void 0 : _file$details$image.width) !== null && _file$details$image$w !== void 0 ? _file$details$image$w : null,
height: (_file$details$image$h = (_file$details2 = file.details) === null || _file$details2 === void 0 ? void 0 : (_file$details2$image = _file$details2.image) === null || _file$details2$image === void 0 ? void 0 : _file$details2$image.height) !== null && _file$details$image$h !== void 0 ? _file$details$image$h : null,
size: (_file$details$size = (_file$details3 = file.details) === null || _file$details3 === void 0 ? void 0 : _file$details3.size) !== null && _file$details$size !== void 0 ? _file$details$size : null
};
// Link tags
if (pluginConfig.get(`enableTags`)) {
assetNode.metadata = {
tags___NODE: assetItem.metadata.tags.map(tag => createNodeId(`ContentfulTag__${space.sys.id}__${tag.sys.id}`))
};
}
// Revision applies to entries, assets, and content types
if (assetItem.sys.revision) {
assetNode.sys.revision = assetItem.sys.revision;
}
// The content of an entry is guaranteed to be updated if and only if the .sys.updatedAt field changed
assetNode.internal.contentDigest = assetItem.sys.updatedAt;
assetNodes.push(assetNode);
create(assetNode);
});
await createNodesPromise;
return assetNodes;
};
exports.createAssetNodes = createAssetNodes;