@agility/cli
Version:
Agility CLI for working with your content. (Public Beta)
537 lines • 25.1 kB
JavaScript
"use strict";
/**
* Model Dependency Tree Builder Service
*
* Builds comprehensive dependency trees for selective model-based sync operations.
* Analyzes all entity relationships to ensure complete synchronization of specified models.
*
* Task 104: Create Model Dependency Tree Builder
* Phase 21: Selective Model-Based Sync Implementation
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ModelDependencyTreeBuilder = void 0;
var ansi_colors_1 = __importDefault(require("ansi-colors"));
var sitemap_hierarchy_1 = require("../pushers/page-pusher/sitemap-hierarchy");
var ModelDependencyTreeBuilder = /** @class */ (function () {
function ModelDependencyTreeBuilder(sourceData) {
this.sourceData = sourceData;
}
/**
* Build comprehensive dependency tree from specified model names
*/
ModelDependencyTreeBuilder.prototype.buildDependencyTree = function (modelNames, channel) {
if (!modelNames || modelNames.length === 0) {
throw new Error('Model names are required for dependency tree building');
}
// console.log(ansiColors.cyan(`🌳 Building dependency tree for models: ${modelNames.join(', ')}`));
var tree = {
models: new Set(modelNames),
containers: new Set(),
lists: new Set(),
content: new Set(),
templates: new Set(),
pages: new Set(),
assets: new Set(),
galleries: new Set()
};
// Build dependency tree in CORRECTED logical order
this.findContainersForModels(modelNames, tree);
this.findContentForModels(modelNames, tree);
this.findTemplatesForContainers(tree);
this.findPagesForTemplatesAndContent(tree);
// 🎯 NEW: After finding pages, discover templates used by those pages
this.findTemplatesUsedByPages(tree);
// 🎯 NEW: Include ALL content referenced by discovered pages for complete renderability
this.findAllContentReferencedByPages(tree);
// 🎯 NEW: Include parent pages for proper hierarchy and security
this.findParentPagesForDiscoveredPages(tree, channel);
// 🎯 NEW: Second template discovery pass for parent pages
this.findTemplatesUsedByPages(tree);
// 🎯 NEW: Include models for the newly discovered content
this.findModelsForDiscoveredContent(tree);
// 🎯 NEW: Include containers for the newly discovered models
this.findContainersForDiscoveredModels(tree);
// 🎯 NEW: Include containers that contain discovered content items
this.findContainersForDiscoveredContent(tree);
this.findAssetsInContent(tree);
this.findGalleriesInContent(tree);
// console.log(ansiColors.green(`✅ Dependency tree built - ${this.getTreeSummary(tree)}`));
return tree;
};
/**
* Find containers that use the specified models
*/
ModelDependencyTreeBuilder.prototype.findContainersForModels = function (modelNames, tree) {
var _this = this;
if (!this.sourceData.containers || !this.sourceData.models)
return;
// Create model reference name to ID mapping
var modelMap = new Map();
this.sourceData.models.forEach(function (model) {
modelMap.set(model.referenceName, model.id);
});
// Find containers that use these models
modelNames.forEach(function (modelName) {
var modelId = modelMap.get(modelName);
if (modelId) {
var containers = _this.sourceData.containers.filter(function (c) {
return c.contentDefinitionID === modelId;
});
containers.forEach(function (container) {
tree.containers.add(container.contentViewID);
});
}
});
// console.log(ansiColors.gray(` 📦 Found ${tree.containers.size} containers using specified models`));
};
/**
* Find content items of the specified models
*/
ModelDependencyTreeBuilder.prototype.findContentForModels = function (modelNames, tree) {
var _this = this;
if (!this.sourceData.content)
return;
modelNames.forEach(function (modelName) {
var contentItems = _this.sourceData.content.filter(function (c) { var _a; return ((_a = c.properties) === null || _a === void 0 ? void 0 : _a.definitionName) === modelName; });
contentItems.forEach(function (content) {
tree.content.add(content.contentID);
});
});
// console.log(ansiColors.gray(` 📄 Found ${tree.content.size} content items of specified models`));
};
/**
* Find templates that use the discovered containers
*/
ModelDependencyTreeBuilder.prototype.findTemplatesForContainers = function (tree) {
if (!this.sourceData.templates)
return;
// Find templates that use discovered containers through contentSectionDefinitions
this.sourceData.templates.forEach(function (template) {
if (template.contentSectionDefinitions) {
template.contentSectionDefinitions.forEach(function (section) {
// Check if section references discovered containers
if (section.contentViewID && tree.containers.has(section.contentViewID)) {
tree.templates.add(template.pageTemplateID);
}
// Also check itemContainerID for container references
if (section.itemContainerID && tree.containers.has(section.itemContainerID)) {
tree.templates.add(template.pageTemplateID);
}
});
}
});
// console.log(ansiColors.gray(` 🎨 Found ${tree.templates.size} templates using discovered containers`));
};
/**
* Find pages that use the discovered templates or reference discovered content
*/
ModelDependencyTreeBuilder.prototype.findPagesForTemplatesAndContent = function (tree) {
if (!this.sourceData.pages)
return;
this.sourceData.pages.forEach(function (page) {
var shouldIncludePage = false;
var pageAny = page; // Use defensive typing for complex Agility CMS structures
// Check if page uses discovered templates (check multiple possible property names)
var templateId = pageAny.pageTemplateID || pageAny.templateID || pageAny.templateId;
if (templateId && tree.templates.has(templateId)) {
shouldIncludePage = true;
}
// Check if page content references discovered content (page zones/content areas)
if (pageAny.zones) {
var zones = pageAny.zones;
if (zones && typeof zones === 'object') {
// Zones is an object with zone names as keys
Object.values(zones).forEach(function (zoneModules) {
if (Array.isArray(zoneModules)) {
zoneModules.forEach(function (module) {
if (module.item && (module.item.contentid || module.item.contentID)) {
var contentId = module.item.contentid || module.item.contentID;
if (tree.content.has(contentId)) {
shouldIncludePage = true;
}
}
});
}
});
}
}
if (shouldIncludePage) {
tree.pages.add(page.pageID);
}
});
// console.log(ansiColors.gray(` 📑 Found ${tree.pages.size} pages using discovered templates/content`));
};
/**
* Find templates used by pages that reference discovered content
*/
ModelDependencyTreeBuilder.prototype.findTemplatesUsedByPages = function (tree) {
var _this = this;
if (!this.sourceData.pages)
return;
this.sourceData.pages.forEach(function (page) {
if (tree.pages.has(page.pageID)) {
var templateIds = _this.extractTemplateIdsFromPage(page);
templateIds.forEach(function (id) { return tree.templates.add(id); });
}
});
// console.log(ansiColors.gray(` 🎨 Found ${tree.templates.size} templates used by pages`));
};
/**
* Extract template IDs from a page
*/
ModelDependencyTreeBuilder.prototype.extractTemplateIdsFromPage = function (page) {
var templateIds = [];
// Check multiple possible property names for template reference
var templateId = page.pageTemplateID || page.templateID || page.templateId;
if (templateId && typeof templateId === 'number') {
templateIds.push(templateId);
}
// Also check if templateName exists and try to resolve to ID
if (page.templateName && this.sourceData.templates) {
var template = this.sourceData.templates.find(function (t) {
return t.pageTemplateName === page.templateName;
});
if (template && template.pageTemplateID) {
templateIds.push(template.pageTemplateID);
}
}
return templateIds;
};
/**
* Find ALL content referenced by discovered pages for complete renderability
* This ensures pages can render completely even if they reference content from other models
*/
ModelDependencyTreeBuilder.prototype.findAllContentReferencedByPages = function (tree) {
var _this = this;
if (!this.sourceData.pages)
return;
var initialContentSize = tree.content.size;
this.sourceData.pages.forEach(function (page) {
if (tree.pages.has(page.pageID)) {
// Extract all content IDs from page zones
var contentIds = _this.extractContentIdsFromPage(page);
contentIds.forEach(function (id) { return tree.content.add(id); });
}
});
var newContentCount = tree.content.size - initialContentSize;
// console.log(ansiColors.gray(` 📄 Added ${newContentCount} additional content items for page renderability`));
};
/**
* Find ALL ancestor pages for discovered pages to ensure proper hierarchy and security
* Recursively walks up the hierarchy to find all parents, grandparents, etc.
*/
ModelDependencyTreeBuilder.prototype.findParentPagesForDiscoveredPages = function (tree, channel) {
var _this = this;
if (!this.sourceData.pages)
return;
var initialPageCount = tree.pages.size;
// Keep track of pages we need to process for parent discovery
var pagesToProcess = new Set();
// Start with all currently discovered pages
tree.pages.forEach(function (pageId) { return pagesToProcess.add(pageId); });
// Process each page and find all its ancestors
pagesToProcess.forEach(function (pageId) {
var page = _this.sourceData.pages.find(function (p) { return p.pageID === pageId; });
if (page) {
_this.findAllAncestorPages(page, tree, channel);
}
});
var newPageCount = tree.pages.size - initialPageCount;
// console.log(ansiColors.gray(` 📑 Added ${newPageCount} ancestor pages for proper hierarchy`));
};
/**
* Recursively find all ancestor pages (parents, grandparents, etc.) for a given page
*/
ModelDependencyTreeBuilder.prototype.findAllAncestorPages = function (page, tree, channel) {
var parentPage = this.findParentPage(page, channel);
if (parentPage && !tree.pages.has(parentPage.pageID)) {
// Add this parent to the tree
tree.pages.add(parentPage.pageID);
console.log(ansi_colors_1.default.gray(" \uD83D\uDCD1 [ANCESTOR] Added parent page ".concat(parentPage.name, " (ID:").concat(parentPage.pageID, ") for child ").concat(page.name, " (ID:").concat(page.pageID, ")")));
// Recursively find this parent's ancestors
this.findAllAncestorPages(parentPage, tree, channel);
}
};
/**
* Find the direct parent page for a given page
* Returns null if no parent exists (root level page)
*/
ModelDependencyTreeBuilder.prototype.findParentPage = function (page, channel) {
if (!this.sourceData.pages)
return null;
// Use existing SitemapHierarchy utility to find parent
var sitemapHierarchy = new sitemap_hierarchy_1.SitemapHierarchy();
var parentResult = sitemapHierarchy.findPageParentInSourceSitemap(page.pageID, page.name, channel);
if (!parentResult.parentId)
return null;
// Find the actual page object by ID
var parentPage = this.sourceData.pages.find(function (p) { return p.pageID === parentResult.parentId; });
return parentPage || null;
};
/**
* Find models for all discovered content to ensure complete model coverage
*/
ModelDependencyTreeBuilder.prototype.findModelsForDiscoveredContent = function (tree) {
if (!this.sourceData.content || !this.sourceData.models)
return;
var initialModelSize = tree.models.size;
// Find models for all content in the tree
this.sourceData.content.forEach(function (contentItem) {
var _a;
if (tree.content.has(contentItem.contentID)) {
// Find the model for this content item
var modelName = (_a = contentItem.properties) === null || _a === void 0 ? void 0 : _a.definitionName;
if (modelName) {
tree.models.add(modelName);
}
}
});
var newModelCount = tree.models.size - initialModelSize;
// console.log(ansiColors.gray(` 📋 Added ${newModelCount} additional models for content dependencies`));
};
/**
* Find containers for newly discovered models
*/
ModelDependencyTreeBuilder.prototype.findContainersForDiscoveredModels = function (tree) {
var _this = this;
if (!this.sourceData.containers || !this.sourceData.models)
return;
var initialContainerSize = tree.containers.size;
// Create model reference name to ID mapping
var modelMap = new Map();
this.sourceData.models.forEach(function (model) {
modelMap.set(model.referenceName, model.id);
});
// Find containers for all models in the tree
tree.models.forEach(function (modelName) {
var modelId = modelMap.get(modelName);
if (modelId) {
var containers = _this.sourceData.containers.filter(function (c) {
return c.contentDefinitionID === modelId;
});
containers.forEach(function (container) {
tree.containers.add(container.contentViewID);
});
}
});
var newContainerCount = tree.containers.size - initialContainerSize;
// console.log(ansiColors.gray(` 📦 Added ${newContainerCount} additional containers for model dependencies`));
};
/**
* Find containers that contain discovered content items
* Uses case-insensitive matching to handle Agility CMS pattern:
* - Containers: "news1_RichTextArea" (PascalCase)
* - Content: "news1_richtextarea" (lowercase)
*/
ModelDependencyTreeBuilder.prototype.findContainersForDiscoveredContent = function (tree) {
if (!this.sourceData.containers || !this.sourceData.content)
return;
var initialContainerSize = tree.containers.size;
// Create a map of content reference names (lowercase) to content IDs
var contentReferenceMap = new Map();
this.sourceData.content.forEach(function (contentItem) {
var _a;
if (tree.content.has(contentItem.contentID)) {
var referenceName = (_a = contentItem.properties) === null || _a === void 0 ? void 0 : _a.referenceName;
if (referenceName) {
contentReferenceMap.set(referenceName.toLowerCase(), contentItem.contentID);
}
}
});
// Find containers with case-insensitive matching
this.sourceData.containers.forEach(function (container) {
var _a;
var containerRefLower = (_a = container.referenceName) === null || _a === void 0 ? void 0 : _a.toLowerCase();
if (containerRefLower && contentReferenceMap.has(containerRefLower)) {
tree.containers.add(container.contentViewID);
// console.log(ansiColors.gray(` 📦 [CASE MATCH] Found container ${container.referenceName} (ID:${container.contentViewID}) for content ${contentReferenceMap.get(containerRefLower)}`));
}
});
var newContainerCount = tree.containers.size - initialContainerSize;
// console.log(ansiColors.gray(` 📦 Added ${newContainerCount} additional containers for discovered content`));
};
/**
* Extract all content IDs referenced in a page's zones
*/
ModelDependencyTreeBuilder.prototype.extractContentIdsFromPage = function (page) {
var contentIds = [];
if (page.zones) {
var zones = page.zones;
if (zones && typeof zones === 'object') {
// Zones is an object with zone names as keys
Object.values(zones).forEach(function (zoneModules) {
if (Array.isArray(zoneModules)) {
zoneModules.forEach(function (module) {
if (module.item && (module.item.contentid || module.item.contentID)) {
var contentId = module.item.contentid || module.item.contentID;
if (typeof contentId === 'number') {
contentIds.push(contentId);
}
}
});
}
});
}
}
return contentIds;
};
/**
* Find assets referenced in discovered content and pages
*/
ModelDependencyTreeBuilder.prototype.findAssetsInContent = function (tree) {
var _this = this;
if (!this.sourceData.content || !this.sourceData.assets)
return;
// Extract asset URLs from content items in the tree
this.sourceData.content.forEach(function (contentItem) {
if (tree.content.has(contentItem.contentID)) {
var assetUrls = _this.extractAssetUrlsFromContent(contentItem);
assetUrls.forEach(function (url) { return tree.assets.add(url); });
}
});
// Also check pages for asset references
if (this.sourceData.pages) {
this.sourceData.pages.forEach(function (page) {
if (tree.pages.has(page.pageID)) {
var assetUrls = _this.extractAssetUrlsFromPage(page);
assetUrls.forEach(function (url) { return tree.assets.add(url); });
}
});
}
// console.log(ansiColors.gray(` 🖼️ Found ${tree.assets.size} assets referenced in content/pages`));
};
/**
* Find galleries referenced in discovered content
*/
ModelDependencyTreeBuilder.prototype.findGalleriesInContent = function (tree) {
var _this = this;
if (!this.sourceData.content || !this.sourceData.galleries)
return;
this.sourceData.content.forEach(function (contentItem) {
if (tree.content.has(contentItem.contentID)) {
var galleryIds = _this.extractGalleryIdsFromContent(contentItem);
galleryIds.forEach(function (id) { return tree.galleries.add(id); });
}
});
// console.log(ansiColors.gray(` 📸 Found ${tree.galleries.size} galleries referenced in content`));
};
/**
* Extract asset URLs from content item fields
*/
ModelDependencyTreeBuilder.prototype.extractAssetUrlsFromContent = function (contentItem) {
var urls = [];
if (contentItem.fields) {
this.scanObjectForAssetUrls(contentItem.fields, urls);
}
return urls;
};
/**
* Extract asset URLs from page content
*/
ModelDependencyTreeBuilder.prototype.extractAssetUrlsFromPage = function (page) {
var urls = [];
// Scan page zones for asset references
if (page.zones) {
this.scanObjectForAssetUrls(page.zones, urls);
}
// Scan page content if it exists
if (page.content) {
this.scanObjectForAssetUrls(page.content, urls);
}
return urls;
};
/**
* Extract gallery IDs from content item fields
*/
ModelDependencyTreeBuilder.prototype.extractGalleryIdsFromContent = function (contentItem) {
var galleryIds = [];
if (contentItem.fields) {
this.scanObjectForGalleryIds(contentItem.fields, galleryIds);
}
return galleryIds;
};
/**
* Recursively scan object for asset URLs (cdn.aglty.io references)
*/
ModelDependencyTreeBuilder.prototype.scanObjectForAssetUrls = function (obj, urls, path) {
var _this = this;
if (path === void 0) { path = ''; }
if (typeof obj === 'string' && obj.includes('cdn.aglty.io')) {
urls.push(obj);
}
else if (obj && typeof obj === 'object') {
if (Array.isArray(obj)) {
obj.forEach(function (item, index) {
_this.scanObjectForAssetUrls(item, urls, "".concat(path, "[").concat(index, "]"));
});
}
else {
Object.keys(obj).forEach(function (key) {
_this.scanObjectForAssetUrls(obj[key], urls, "".concat(path, ".").concat(key));
});
}
}
};
/**
* Recursively scan object for gallery ID references
*/
ModelDependencyTreeBuilder.prototype.scanObjectForGalleryIds = function (obj, galleryIds, path) {
var _this = this;
if (path === void 0) { path = ''; }
if (typeof obj === 'object' && obj !== null) {
// Look for gallery field patterns
if (obj.mediaGroupingID && typeof obj.mediaGroupingID === 'number') {
galleryIds.push(obj.mediaGroupingID);
}
// Look for gallery reference patterns in field values
if (obj.galleryID && typeof obj.galleryID === 'number') {
galleryIds.push(obj.galleryID);
}
// Recursively scan nested objects
if (Array.isArray(obj)) {
obj.forEach(function (item, index) {
_this.scanObjectForGalleryIds(item, galleryIds, "".concat(path, "[").concat(index, "]"));
});
}
else {
Object.keys(obj).forEach(function (key) {
_this.scanObjectForGalleryIds(obj[key], galleryIds, "".concat(path, ".").concat(key));
});
}
}
};
/**
* Get a summary string of the dependency tree
*/
ModelDependencyTreeBuilder.prototype.getTreeSummary = function (tree) {
var total = tree.models.size + tree.containers.size + tree.content.size +
tree.templates.size + tree.pages.size + tree.assets.size + tree.galleries.size;
return "".concat(total, " total entities across 7 types");
};
/**
* Validate that specified models exist in source data
*/
ModelDependencyTreeBuilder.prototype.validateModels = function (modelNames) {
var valid = [];
var invalid = [];
if (!this.sourceData.models || this.sourceData.models.length === 0) {
return { valid: [], invalid: modelNames };
}
var availableModels = new Set(this.sourceData.models.map(function (m) { return m.referenceName; }));
modelNames.forEach(function (modelName) {
if (availableModels.has(modelName)) {
valid.push(modelName);
}
else {
invalid.push(modelName);
}
});
return { valid: valid, invalid: invalid };
};
return ModelDependencyTreeBuilder;
}());
exports.ModelDependencyTreeBuilder = ModelDependencyTreeBuilder;
//# sourceMappingURL=model-dependency-tree-builder.js.map