From 32b6ab5cead6d9b9c225302eb24466b71f7586ca Mon Sep 17 00:00:00 2001 From: Aaron Date: Fri, 20 Mar 2026 15:22:56 -0400 Subject: [PATCH 1/3] fix: use correct AssetMapping properties for URL rewriting (PROD-915) The mapAssetUrl method was accessing originUrl/url/edgeUrl on an AssetMapping object, but those properties only exist on mgmtApi.Media. The `as any` cast masked the type error, causing every lookup to fall through to returning the original source URL unchanged. Now correctly returns assetMapping.targetUrl when a mapping is found. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/lib/content/content-field-mapper.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/content/content-field-mapper.ts b/src/lib/content/content-field-mapper.ts index 6131429..a8aefae 100644 --- a/src/lib/content/content-field-mapper.ts +++ b/src/lib/content/content-field-mapper.ts @@ -248,11 +248,9 @@ export class ContentFieldMapper { // Try to find the asset by URL in the asset mapper const assetMapping = context.assetMapper.getAssetMappingByMediaUrl(sourceUrl, "source"); if (assetMapping) { - const asset = assetMapping as any; - return asset.originUrl || asset.url || asset.edgeUrl || sourceUrl; + return assetMapping.targetUrl || sourceUrl; } - // Return original URL if no mapping found return sourceUrl; } From ea84893dccdfc395069b0cfe161815142f8fe670 Mon Sep 17 00:00:00 2001 From: Aaron Date: Tue, 24 Mar 2026 13:22:59 -0400 Subject: [PATCH 2/3] feat: add container-aware URL remapping for asset references (PROD-915) Asset mappings now store container edge/origin URLs and use them as a fallback when exact URL matching fails. This handles content items that reference assets via subfolder paths (e.g. /mobile/feature-carousel/) which differ from the root edgeUrl stored in the mapping. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/lib/content/content-field-mapper.ts | 11 ++++- src/lib/mappers/asset-mapper.ts | 56 +++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/src/lib/content/content-field-mapper.ts b/src/lib/content/content-field-mapper.ts index a8aefae..454811e 100644 --- a/src/lib/content/content-field-mapper.ts +++ b/src/lib/content/content-field-mapper.ts @@ -245,9 +245,18 @@ export class ContentFieldMapper { private mapAssetUrl(sourceUrl: string, context?: ContentFieldMappingContext): string { - // Try to find the asset by URL in the asset mapper + // Try exact URL match first const assetMapping = context.assetMapper.getAssetMappingByMediaUrl(sourceUrl, "source"); if (assetMapping) { + // If exact match, use the target URL directly + if (assetMapping.sourceUrl === sourceUrl) { + return assetMapping.targetUrl || sourceUrl; + } + + // Container prefix match — swap source container for target, preserving subfolder path + const remapped = context.assetMapper.remapUrlByContainer(sourceUrl, "source"); + if (remapped) return remapped; + return assetMapping.targetUrl || sourceUrl; } diff --git a/src/lib/mappers/asset-mapper.ts b/src/lib/mappers/asset-mapper.ts index a80cbf6..f8db4de 100644 --- a/src/lib/mappers/asset-mapper.ts +++ b/src/lib/mappers/asset-mapper.ts @@ -10,6 +10,10 @@ interface AssetMapping { targetMediaID: number; sourceUrl?: string; targetUrl?: string; + sourceContainerEdgeUrl?: string; + targetContainerEdgeUrl?: string; + sourceContainerOriginUrl?: string; + targetContainerOriginUrl?: string; } @@ -44,9 +48,48 @@ export class AssetMapper { } getAssetMappingByMediaUrl(url: string, type: 'source' | 'target'): AssetMapping | null { - const mapping = this.mappings.find((m: AssetMapping) => type === 'source' ? m.sourceUrl === url : m.targetUrl === url); + // Try exact match first + const exact = this.mappings.find((m: AssetMapping) => type === 'source' ? m.sourceUrl === url : m.targetUrl === url); + if (exact) return exact; + + // Fallback: match by container prefix (handles subfolder paths like /mobile/feature-carousel/) + return this.findMappingByContainerPrefix(url, type); + } + + /** + * Remap a URL from source container to target container, preserving any subfolder path. + * e.g. "cdn-usa2.aglty.io/brightstar-tns-cat/mobile/feature-carousel/file.png" + * → "cdn-usa2.aglty.io/2151a7f2/mobile/feature-carousel/file.png" + * + * Returns null if the URL doesn't match any mapping's container prefix. + */ + remapUrlByContainer(url: string, type: 'source' | 'target'): string | null { + const mapping = this.findMappingByContainerPrefix(url, type); if (!mapping) return null; - return mapping; + + // Determine which container URLs to use based on whether this is an edge or origin URL + const sourceEdge = type === 'source' ? mapping.sourceContainerEdgeUrl : mapping.targetContainerEdgeUrl; + const targetEdge = type === 'source' ? mapping.targetContainerEdgeUrl : mapping.sourceContainerEdgeUrl; + const sourceOrigin = type === 'source' ? mapping.sourceContainerOriginUrl : mapping.targetContainerOriginUrl; + const targetOrigin = type === 'source' ? mapping.targetContainerOriginUrl : mapping.sourceContainerOriginUrl; + + // Try edge URL swap first, then origin URL swap + if (sourceEdge && targetEdge && url.startsWith(sourceEdge)) { + return url.replace(sourceEdge, targetEdge); + } + if (sourceOrigin && targetOrigin && url.startsWith(sourceOrigin)) { + return url.replace(sourceOrigin, targetOrigin); + } + + return null; + } + + private findMappingByContainerPrefix(url: string, type: 'source' | 'target'): AssetMapping | null { + return this.mappings.find((m: AssetMapping) => { + const edgeUrl = type === 'source' ? m.sourceContainerEdgeUrl : m.targetContainerEdgeUrl; + const originUrl = type === 'source' ? m.sourceContainerOriginUrl : m.targetContainerOriginUrl; + return (edgeUrl && url.startsWith(edgeUrl + '/')) || (originUrl && url.startsWith(originUrl + '/')); + }) || null; } getMappedEntity(mapping: AssetMapping, type: 'source' | 'target'): mgmtApi.Media | null { @@ -75,7 +118,10 @@ export class AssetMapper { targetMediaID: targetAsset.mediaID, sourceUrl: sourceAsset.edgeUrl, targetUrl: targetAsset.edgeUrl, - + sourceContainerEdgeUrl: sourceAsset.containerEdgeUrl, + targetContainerEdgeUrl: targetAsset.containerEdgeUrl, + sourceContainerOriginUrl: sourceAsset.containerOriginUrl, + targetContainerOriginUrl: targetAsset.containerOriginUrl, } this.mappings.push(newMapping); @@ -95,6 +141,10 @@ export class AssetMapper { mapping.targetMediaID = targetAsset.mediaID; mapping.sourceUrl = sourceAsset.edgeUrl; mapping.targetUrl = targetAsset.edgeUrl; + mapping.sourceContainerEdgeUrl = sourceAsset.containerEdgeUrl; + mapping.targetContainerEdgeUrl = targetAsset.containerEdgeUrl; + mapping.sourceContainerOriginUrl = sourceAsset.containerOriginUrl; + mapping.targetContainerOriginUrl = targetAsset.containerOriginUrl; } this.saveMapping(); } From eb4d6469ebcd27e31fa2b67b09306b2d68aaaccf Mon Sep 17 00:00:00 2001 From: Aaron Date: Tue, 31 Mar 2026 11:28:09 -0400 Subject: [PATCH 3/3] chore: bump version to 1.0.0-beta.13.5 Co-Authored-By: Claude Opus 4.6 (1M context) --- package-lock.json | 13 ++----------- package.json | 2 +- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index e4e8f37..d85a04d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@agility/cli", - "version": "1.0.0-beta.13", + "version": "1.0.0-beta.13.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@agility/cli", - "version": "1.0.0-beta.13", + "version": "1.0.0-beta.13.4", "license": "ISC", "dependencies": { "@agility/content-fetch": "^2.0.10", @@ -83,7 +83,6 @@ "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", "license": "MIT", - "peer": true, "dependencies": { "follow-redirects": "^1.14.0" } @@ -155,7 +154,6 @@ "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -1464,7 +1462,6 @@ "integrity": "sha512-hcxGs9TfQGghOM8atpRT+bBMUX7V8WosdYt98bQ59wUToJck55eCOlemJ+0FpOZOQw5ff7LSi9+IO56KvYEFyQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~5.26.4" } @@ -1953,7 +1950,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001726", "electron-to-chromium": "^1.5.173", @@ -3398,7 +3394,6 @@ "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", "license": "MIT", - "peer": true, "dependencies": { "ansi-escapes": "^4.2.1", "chalk": "^4.1.1", @@ -4466,7 +4461,6 @@ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -6104,7 +6098,6 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", "license": "MIT", - "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -7597,7 +7590,6 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -7705,7 +7697,6 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index a963a33..ebafd85 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@agility/cli", - "version": "1.0.0-beta.13.4", + "version": "1.0.0-beta.13.5", "description": "Agility CLI for working with your content. (Public Beta)", "repository": { "type": "git",