@@ -42,6 +42,7 @@ interface FieldMapping {
4242interface Project {
4343 master_locale ?: object ;
4444 locales ?: object ;
45+ // add other properties as needed
4546}
4647
4748interface ContentType {
@@ -91,42 +92,52 @@ interface AssetJSON {
9192async function isAssetJsonCreated ( assetJsonPath : string ) : Promise < boolean > {
9293 try {
9394 await fs . promises . access ( assetJsonPath , fs . constants . F_OK ) ;
94- return true ;
95+ return true ; // File exists
9596 } catch {
96- return false ;
97+ return false ; // File does not exist
9798 }
9899}
99100
101+ // Helper function to sanitize data and remove unwanted HTML tags and other chars
100102function stripHtmlTags ( html : string ) : string {
101103 if ( ! html || typeof html !== 'string' ) return '' ;
102104
105+ // Use JSDOM to parse and extract text content
103106 const dom = new JSDOM ( html ) ;
104107 const text = dom . window . document . body . textContent || '' ;
105108
109+ // Clean up extra whitespace and newlines
106110 return text . trim ( ) . replace ( / \s + / g, ' ' ) ;
107111}
108112
113+ // Helper Function to extract value from items object based on the fieldName
109114function getFieldValue ( items : any , fieldName : string ) : any {
110115 if ( ! items || ! fieldName ) return undefined ;
111116
117+ // Try exact match first
112118 if ( items [ fieldName ] !== undefined ) {
113119 return items [ fieldName ] ;
114120 }
115121
122+ // Try camelCase conversion (snake_case → camelCase)
123+ // Handle both single letter and multiple letter segments
116124 const camelCaseFieldName = fieldName ?. replace ( / _ ( [ a - z ] + ) / gi, ( _ , letters ) => {
125+ // Capitalize first letter, keep rest as-is for acronyms
117126 return letters ?. charAt ( 0 ) ?. toUpperCase ( ) + letters ?. slice ( 1 ) ;
118127 } ) ;
119128 if ( items [ camelCaseFieldName ] !== undefined ) {
120129 return items [ camelCaseFieldName ] ;
121130 }
122131
132+ // Try all uppercase version for acronyms
123133 const acronymVersion = fieldName ?. replace ( / _ ( [ a - z ] + ) / gi, ( _ , letters ) => {
124134 return letters ?. toUpperCase ( ) ;
125135 } ) ;
126136 if ( items [ acronymVersion ] !== undefined ) {
127137 return items [ acronymVersion ] ;
128138 }
129139
140+ // Try case-insensitive match as last resort
130141 const itemKeys = Object . keys ( items ) ;
131142 const matchedKey = itemKeys ?. find ( key => key . toLowerCase ( ) === fieldName ?. toLowerCase ( ) ) ;
132143 if ( matchedKey && items [ matchedKey ] !== undefined ) {
@@ -146,6 +157,12 @@ function getActualFieldUid(uid: string, fieldUid: string): string {
146157 return uid ;
147158}
148159
160+ /**
161+ * Finds and returns the asset object from assetJsonData where assetPath matches the given string.
162+ * @param assetJsonData - The asset JSON data object.
163+ * @param assetPathToFind - The asset path string to match.
164+ * @returns The matching AssetJSON object, or undefined if not found.
165+ */
149166function findAssetByPath (
150167 assetJsonData : Record < string , AssetJSON > ,
151168 assetPathToFind : string
@@ -204,11 +221,11 @@ function slugify(text: unknown): string {
204221 if ( typeof text !== 'string' ) return '' ;
205222 return text
206223 . toLowerCase ( )
207- . replace ( / \| / g, '' )
208- . replace ( / [ ^ \w \s - ] / g, '' )
209- . replace ( / \s + / g, '-' )
210- . replace ( / - + / g, '-' )
211- . replace ( / ^ - + | - + $ / g, '' ) ;
224+ . replace ( / \| / g, '' ) // Remove pipe characters
225+ . replace ( / [ ^ \w \s - ] / g, '' ) // Remove special characters
226+ . replace ( / \s + / g, '-' ) // Replace spaces with hyphens
227+ . replace ( / - + / g, '-' ) // Replace multiple hyphens with a single hyphen
228+ . replace ( / ^ - + | - + $ / g, '' ) ; // Remove leading and trailing hyphens
212229}
213230
214231function addEntryToEntriesData (
@@ -226,6 +243,11 @@ function addEntryToEntriesData(
226243 entriesData [ contentTypeUid ] [ mappedLocale ] . push ( entryObj ) ;
227244}
228245
246+ /**
247+ * Extracts the current locale from the given parseData object.
248+ * @param parseData The parsed data object from the JSON file.
249+ * @returns The locale string if found, otherwise undefined.
250+ */
229251function getCurrentLocale ( parseData : any ) : string | undefined {
230252 if ( parseData . language ) {
231253 return parseData . language ;
@@ -264,9 +286,12 @@ export function isImageType(path: string): boolean {
264286
265287export function isExperienceFragment ( data : any ) {
266288 if ( data ?. templateType && data [ ':type' ] ) {
289+ // Check templateType starts with 'xf-'
267290 const hasXfTemplate = data ?. templateType ?. startsWith ( 'xf-' ) ;
291+ // Check :type contains 'components/xfpage'
268292 const hasXfComponent = data [ ':type' ] ?. includes ( 'components/xfpage' ) ;
269293
294+ // Return analysis
270295 return {
271296 isXF : hasXfTemplate || hasXfComponent ,
272297 confidence : ( hasXfTemplate && hasXfComponent ) ? 'high'
@@ -280,17 +305,31 @@ export function isExperienceFragment(data: any) {
280305 return null ;
281306}
282307
308+ /**
309+ * Ensures the assets directory exists.
310+ * If it does not exist, creates it recursively.
311+ * @param assetsSave The path to the assets directory.
312+ */
283313async function ensureAssetsDirectory ( assetsSave : string ) : Promise < void > {
284314 const fullPath = path . join ( process . cwd ( ) , assetsSave ) ;
285315 try {
286316 await fs . promises . access ( fullPath ) ;
317+ // Directory exists, log it
287318 console . info ( `Directory exists: ${ fullPath } ` ) ;
288319 } catch ( err ) {
289320 await fs . promises . mkdir ( fullPath , { recursive : true } ) ;
321+ // Directory created, log it
290322 console . info ( `Directory created: ${ fullPath } ` ) ;
291323 }
292324}
293325
326+
327+ /**
328+ * Fetch files from a directory based on a query.
329+ * @param dirPath - The directory to search in.
330+ * @param query - The query string (filename, extension, or regex).
331+ * @returns Array of matching file paths.
332+ */
294333export async function fetchFilesByQuery (
295334 dirPath : string ,
296335 query : string | RegExp
@@ -339,11 +378,12 @@ const createAssets = async ({
339378 await ensureAssetsDirectory ( assetsSave ) ;
340379 const assetsDir = path . resolve ( packagePath ) ;
341380
342- const allAssetJSON : Record < string , AssetJSON > = { } ;
343- const pathToUidMap : Record < string , string > = { } ;
381+ const allAssetJSON : Record < string , AssetJSON > = { } ; // UID-based index.json
382+ const pathToUidMap : Record < string , string > = { } ; // Path to UID mapping
344383 const seenFilenames = new Map < string , { uid : string ; metadata : any ; blobPath : string } > ( ) ;
345384 const pathToFilenameMap = new Map < string , string > ( ) ;
346385
386+ // Discover assets and deduplicate by filename
347387 for await ( const fileName of read ( assetsDir ) ) {
348388 const filePath = path . join ( assetsDir , fileName ) ;
349389 if ( filePath ?. startsWith ?.( damPath ) ) {
@@ -367,9 +407,9 @@ const createAssets = async ({
367407 if ( typeof contentAst === 'string' ) {
368408 const parseData = JSON . parse ( contentAst ) ;
369409 const filename = parseData ?. asset ?. name ;
370-
410+ // Store mapping from this AEM path to filename
371411 pathToFilenameMap . set ( value , filename ) ;
372-
412+ // Only create asset ONCE per unique filename
373413 if ( ! seenFilenames ?. has ( filename ) ) {
374414 const uid = uuidv4 ?.( ) ?. replace ?.( / - / g, '' ) ;
375415 const blobPath = firstJson ?. replace ?.( '.metadata.json' , '' ) ;
@@ -391,6 +431,7 @@ const createAssets = async ({
391431 }
392432 }
393433
434+ // Create physical asset files (one per unique filename)
394435 for ( const [ filename , assetInfo ] of seenFilenames ?. entries ( ) ) {
395436 const { uid, metadata, blobPath } = assetInfo ;
396437 const nameWithoutExt = typeof filename === 'string'
@@ -426,19 +467,23 @@ const createAssets = async ({
426467 }
427468 }
428469
470+ // Track first path for each asset and build mappings
429471 const assetFirstPath = new Map < string , string > ( ) ;
430472
473+ // Build path-to-UID mapping (ALL paths map to the SAME deduplicated UID)
431474 for ( const [ aemPath , filename ] of pathToFilenameMap . entries ( ) ) {
432475 const assetInfo = seenFilenames ?. get ( filename ) ;
433476 if ( assetInfo ) {
434477 pathToUidMap [ aemPath ] = assetInfo . uid ;
435478
479+ // Track first path for index.json
436480 if ( ! assetFirstPath . has ( assetInfo . uid ) ) {
437481 assetFirstPath . set ( assetInfo . uid , aemPath ) ;
438482 }
439483 }
440484 }
441485
486+ // Create UID-based index.json
442487 for ( const [ filename , assetInfo ] of seenFilenames ?. entries ( ) ) {
443488 const { uid, metadata } = assetInfo ;
444489 const nameWithoutExt = typeof filename === 'string'
@@ -463,17 +508,20 @@ const createAssets = async ({
463508 } ;
464509 }
465510
511+ // Write files
466512 const fileMeta = { '1' : ASSETS_SCHEMA_FILE } ;
467513 await fs . promises . writeFile (
468514 path . join ( process . cwd ( ) , assetsSave , ASSETS_FILE_NAME ) ,
469515 JSON . stringify ( fileMeta )
470516 ) ;
471517
518+ // index.json - UID-based
472519 await fs . promises . writeFile (
473520 path . join ( process . cwd ( ) , assetsSave , ASSETS_SCHEMA_FILE ) ,
474521 JSON . stringify ( allAssetJSON , null , 2 )
475522 ) ;
476523
524+ // path-mapping.json - For entry transformation
477525 await fs . promises . writeFile (
478526 path . join ( process . cwd ( ) , assetsSave , 'path-mapping.json' ) ,
479527 JSON . stringify ( pathToUidMap , null , 2 )
@@ -1068,7 +1116,7 @@ function processFieldsRecursive(
10681116 break ;
10691117 }
10701118 default : {
1071- console . info ( "Unhandled field type: " , field ?. uid , field ?. contentstackFieldType ) ;
1119+ console . info ( "🚀 ~ processFieldsRecursive ~ childItems " , field ?. uid , field ?. contentstackFieldType ) ;
10721120 break ;
10731121 }
10741122 }
@@ -1108,13 +1156,15 @@ const createEntry = async ({
11081156 let pathToUidMap : Record < string , string > = { } ;
11091157 let assetDetailsMap : Record < string , AssetJSON > = { } ;
11101158
1159+ // Load path-to-UID mapping
11111160 try {
11121161 const mappingData = await fs . promises . readFile ( pathMappingFile , 'utf-8' ) ;
11131162 pathToUidMap = JSON . parse ( mappingData ) ;
11141163 } catch ( err ) {
11151164 console . warn ( 'path-mapping.json not found, assets will not be attached' ) ;
11161165 }
11171166
1167+ // Load full asset details from index.json
11181168 try {
11191169 const assetIndexData = await fs . promises . readFile ( assetIndexFile , 'utf-8' ) ;
11201170 assetDetailsMap = JSON . parse ( assetIndexData ) ;
@@ -1128,6 +1178,7 @@ const createEntry = async ({
11281178 const allLocales : object = { ...project ?. master_locale , ...project ?. locales } ;
11291179 const entryMapping : Record < string , string [ ] > = { } ;
11301180
1181+ // Process each entry file
11311182 for await ( const fileName of read ( entriesDir ) ) {
11321183 const filePath = path . join ( entriesDir , fileName ) ;
11331184 if ( filePath ?. startsWith ?.( damPath ) ) {
0 commit comments