@amplience/dc-cli
Version:
Dynamic Content CLI Tool
261 lines (260 loc) • 10.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ContentDependancyTree = exports.referenceTypes = void 0;
exports.referenceTypes = [
'http://bigcontent.io/cms/schema/v1/core#/definitions/content-link',
'http://bigcontent.io/cms/schema/v1/core#/definitions/content-reference'
];
var CircularDependencyStage;
(function (CircularDependencyStage) {
CircularDependencyStage[CircularDependencyStage["Standalone"] = 0] = "Standalone";
CircularDependencyStage[CircularDependencyStage["Intertwined"] = 1] = "Intertwined";
CircularDependencyStage[CircularDependencyStage["Parent"] = 2] = "Parent";
})(CircularDependencyStage || (CircularDependencyStage = {}));
class ContentDependancyTree {
constructor(items, mapping) {
let info = this.identifyContentDependancies(items);
const allInfo = info;
this.resolveContentDependancies(info);
const requiredSchema = new Set();
info.forEach(item => {
requiredSchema.add(item.owner.content.body._meta.schema);
});
const resolved = new Set();
mapping.contentItems.forEach((to, from) => {
resolved.add(from);
});
let unresolvedCount = info.length;
const stages = [];
while (unresolvedCount > 0) {
const stage = [];
const lastUnresolvedCount = unresolvedCount;
info = info.filter(item => {
const unresolvedDependancies = item.dependancies.filter(dep => !resolved.has(dep.dependancy.id));
if (unresolvedDependancies.length === 0) {
stage.push(item);
return false;
}
return true;
});
stage.forEach(item => {
resolved.add(item.owner.content.id);
});
unresolvedCount = info.length;
if (unresolvedCount === lastUnresolvedCount) {
break;
}
stages.push({ items: stage });
}
const circularStages = [];
while (unresolvedCount > 0) {
const stage = [];
const lastUnresolvedCount = unresolvedCount;
const circularLevels = info.map(item => this.topLevelCircular(item, info));
const chosenLevel = Math.min(...circularLevels);
for (let i = 0; i < info.length; i++) {
const item = info[i];
if (circularLevels[i] === chosenLevel) {
stage.push(item);
circularLevels.splice(i, 1);
info.splice(i--, 1);
}
}
unresolvedCount = info.length;
if (unresolvedCount === lastUnresolvedCount) {
break;
}
circularStages.push(stage);
}
this.levels = stages;
this.circularLinks = [];
circularStages.forEach(stage => this.circularLinks.push(...stage));
this.all = allInfo;
this.byId = new Map(allInfo.map(info => [info.owner.content.id, info]));
this.requiredSchema = Array.from(requiredSchema);
}
searchObjectForContentDependancies(item, body, result, parent, index) {
if (Array.isArray(body)) {
body.forEach((contained, index) => {
this.searchObjectForContentDependancies(item, contained, result, body, index);
});
}
else if (body != null) {
const allPropertyNames = Object.getOwnPropertyNames(body);
if (body._meta &&
exports.referenceTypes.indexOf(body._meta.schema) !== -1 &&
typeof body.contentType === 'string' &&
typeof body.id === 'string') {
result.push({ dependancy: body, owner: item, parent, index });
}
allPropertyNames.forEach(propName => {
const prop = body[propName];
if (typeof prop === 'object') {
this.searchObjectForContentDependancies(item, prop, result, body, propName);
}
});
}
}
removeContentDependanciesFromBody(body, remove) {
if (Array.isArray(body)) {
for (let i = 0; i < body.length; i++) {
if (remove.indexOf(body[i]) !== -1) {
body.splice(i--, 1);
}
else {
this.removeContentDependanciesFromBody(body[i], remove);
}
}
}
else {
const allPropertyNames = Object.getOwnPropertyNames(body);
allPropertyNames.forEach(propName => {
const prop = body[propName];
if (remove.indexOf(prop) !== -1) {
delete body[propName];
}
else if (typeof prop === 'object') {
this.removeContentDependanciesFromBody(prop, remove);
}
});
}
}
topLevelCircular(top, unresolved) {
let selfLoop = false;
let intertwinedLoop = false;
let isParent = false;
const seenBefore = new Set();
const traverse = (top, item, depth, unresolved, seenBefore, intertwined) => {
let hasCircular = false;
if (item == null) {
return false;
}
else if (top === item && depth > 0) {
selfLoop = true;
return false;
}
else if (top !== item && unresolved.indexOf(item) !== -1) {
if (!intertwined) {
const storedSelfLoop = selfLoop;
const childIntertwined = traverse(item, item, 0, [top], new Set(), true);
selfLoop = storedSelfLoop;
if (childIntertwined) {
intertwinedLoop = true;
}
else {
isParent = true;
}
}
hasCircular = true;
}
if (seenBefore.has(item)) {
return false;
}
seenBefore.add(item);
item.dependancies.forEach(dep => {
hasCircular = traverse(top, dep.resolved, depth + 1, unresolved, seenBefore, intertwined) || hasCircular;
});
return hasCircular;
};
const hasCircular = traverse(top, top, 0, unresolved, seenBefore, false);
if (hasCircular) {
if (intertwinedLoop) {
if (selfLoop && !isParent) {
return CircularDependencyStage.Intertwined;
}
else {
return CircularDependencyStage.Parent;
}
}
else {
return CircularDependencyStage.Parent;
}
}
else {
return CircularDependencyStage.Standalone;
}
}
identifyContentDependancies(items) {
return items.map(item => {
const result = [];
this.searchObjectForContentDependancies(item, item.content.body, result, undefined, 0);
if (item.content.body._meta.hierarchy && item.content.body._meta.hierarchy.parentId) {
result.push({
dependancy: {
_meta: {
schema: '_hierarchy',
name: '_hierarchy'
},
id: item.content.body._meta.hierarchy.parentId,
contentType: ''
},
owner: item,
parent: undefined,
index: 0
});
}
return { owner: item, dependancies: result, dependants: [] };
});
}
resolveContentDependancies(items) {
const idMap = new Map(items.map(item => [item.owner.content.id, item]));
const visited = new Set();
const resolve = (item) => {
if (visited.has(item))
return;
visited.add(item);
item.dependancies.forEach(dep => {
const target = idMap.get(dep.dependancy.id);
dep.resolved = target;
if (target) {
target.dependants.push({
owner: target.owner,
resolved: item,
dependancy: dep.dependancy,
parent: dep.parent,
index: dep.index
});
resolve(target);
}
});
};
items.forEach(item => resolve(item));
}
traverseDependants(item, action, onlyLinks = false, traversed) {
const traversedSet = traversed || new Set();
traversedSet.add(item);
action(item);
item.dependants.forEach(dependant => {
if (onlyLinks &&
dependant.dependancy._meta.schema !== 'http://bigcontent.io/cms/schema/v1/core#/definitions/content-link') {
return;
}
const resolved = dependant.resolved;
if (!traversedSet.has(resolved)) {
this.traverseDependants(resolved, action, onlyLinks, traversedSet);
}
});
}
filterAny(action) {
return this.all.filter(item => {
let match = false;
this.traverseDependants(item, item => {
if (action(item)) {
match = true;
}
});
return match;
});
}
removeContent(items) {
this.levels.forEach(level => {
level.items = level.items.filter(item => items.indexOf(item) === -1);
});
this.all = this.all.filter(item => items.indexOf(item) === -1);
this.circularLinks = this.circularLinks.filter(item => items.indexOf(item) === -1);
items.forEach(item => {
this.byId.delete(item.owner.content.id);
});
}
}
exports.ContentDependancyTree = ContentDependancyTree;