UNPKG

@agility/cli

Version:

Agility CLI for working with your content. (Public Beta)

537 lines 25.1 kB
"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