@@ -47,6 +47,10 @@ export default class EntriesImport extends BaseClass {
4747 private cTs : Record < string , any > [ ] ;
4848 private modifiedCTs : Record < string , any > [ ] ;
4949 private refCTs : string [ ] ;
50+ // NOTE Number of entries written into the reference-update mapper dir, keyed by `${cTUid}:${locale}`.
51+ // Incremented by both the create and the replace path, then compared against what the
52+ // reference-update step actually reads back, so an unflushed mapper chunk cannot be skipped silently.
53+ private refUpdateExpected : Record < string , number > ;
5054 private jsonRteCTs : Record < string , any > ;
5155 private jsonRteCTsWithRef : Record < string , any > ;
5256 private jsonRteEntries : Record < string , any > ;
@@ -142,6 +146,7 @@ export default class EntriesImport extends BaseClass {
142146 this . entriesUidMapper = { } ;
143147 this . modifiedCTs = [ ] ;
144148 this . refCTs = [ ] ;
149+ this . refUpdateExpected = { } ;
145150 this . jsonRteCTs = [ ] ;
146151 this . jsonRteCTsWithRef = [ ] ;
147152 this . envs = { } ;
@@ -347,6 +352,17 @@ export default class EntriesImport extends BaseClass {
347352 log . debug ( `Initialized progress tracking for ${ contentTypesCount } content types` , this . importConfig . context ) ;
348353 }
349354
355+ /**
356+ * @method trackRefUpdateExpected
357+ * @description Records that one more entry has been written into the reference-update mapper dir
358+ * for this content type / locale. Used by `updateEntriesWithReferences` to verify that every
359+ * entry it was handed actually gets a reference update.
360+ */
361+ private trackRefUpdateExpected ( cTUid : string , locale : string ) : void {
362+ const key = `${ cTUid } :${ locale } ` ;
363+ this . refUpdateExpected [ key ] = ( this . refUpdateExpected [ key ] || 0 ) + 1 ;
364+ }
365+
350366 private async processEntryCreation ( ) : Promise < void > {
351367 log . info ( 'Starting entry creation process' , this . importConfig . context ) ;
352368 const entryRequestOptions = this . populateEntryCreatePayload ( ) ;
@@ -381,6 +397,12 @@ export default class EntriesImport extends BaseClass {
381397 ) ;
382398 } ) ;
383399 }
400+
401+ // NOTE `completeFile` writes the index synchronously but ends the chunk write stream
402+ // asynchronously, so chunk bytes can still be in flight. The reference-update step reads those
403+ // chunks next, so give the streams a moment to flush. Carried over from v1; the proper fix is to
404+ // make FsUtility.completeFile await the stream 'finish' event in @contentstack/cli-utilities.
405+ await this . delay ( 200 ) ;
384406 }
385407
386408 private async processEntryReferenceUpdates ( ) : Promise < void > {
@@ -637,6 +659,7 @@ export default class EntriesImport extends BaseClass {
637659 entry . entryOldUid = oldUid ;
638660 entry . sourceEntryFilePath = path . join ( sanitizePath ( basePath ) , sanitizePath ( additionalInfo . entryFileName ) ) ;
639661 entriesCreateFileHelper . writeIntoFile ( { [ oldUid ] : entry } as any , { mapKeyVal : true } ) ;
662+ this . trackRefUpdateExpected ( cTUid , locale ) ;
640663 } else {
641664 log . info (
642665 `Created entry: '${ entry . title } ' of content type ${ cTUid } in locale ${ locale } ` ,
@@ -656,6 +679,7 @@ export default class EntriesImport extends BaseClass {
656679 entry . sourceEntryFilePath = path . join ( sanitizePath ( basePath ) , sanitizePath ( additionalInfo . entryFileName ) ) ;
657680 entry . entryOldUid = entry . uid ;
658681 entriesCreateFileHelper . writeIntoFile ( { [ entry . uid ] : entry } as any , { mapKeyVal : true } ) ;
682+ this . trackRefUpdateExpected ( cTUid , locale ) ;
659683 }
660684 } ;
661685
@@ -838,6 +862,7 @@ export default class EntriesImport extends BaseClass {
838862 log . debug ( `Replaced entry UID mapping: ${ entry . uid } → ${ response . uid } ` , this . importConfig . context ) ;
839863 this . entriesUidMapper [ entry . uid ] = response . uid ;
840864 entriesReplaceFileHelper . writeIntoFile ( { [ entry . uid ] : entry } as any , { mapKeyVal : true } ) ;
865+ this . trackRefUpdateExpected ( cTUid , locale ) ;
841866 } ;
842867 const onReject = ( { error, apiData : { uid, title } } : any ) => {
843868 // NOTE Remove from list if any entry import failed
@@ -891,6 +916,26 @@ export default class EntriesImport extends BaseClass {
891916 ) ;
892917 }
893918 }
919+
920+ // NOTE Must flush before `updateEntriesWithReferences` runs. `completeFile` is the only path to
921+ // `closeFile`, which is the only place the mapper `index.json` is written. Without this the
922+ // replaced entries stay absent from the index, the reference-update step never sees them, and
923+ // their cross-entry references are silently never written. Also note that a chunk-size rollover
924+ // does NOT save us: `writeIntoExistingFile` calls `closeFile(options?.closeFile === true)`, and
925+ // we pass only `{ mapKeyVal: true }`, so the index is never written on rollover either.
926+ // A failure here IS the reference-loss condition, so report it loudly rather than throwing:
927+ // throwing would surface as a generic "Error while replacing existing entries" in the caller
928+ // and hide the fact that references are about to go missing.
929+ try {
930+ entriesReplaceFileHelper ?. completeFile ( true ) ;
931+ log . success ( `Replaced entries for content type ${ cTUid } in locale ${ locale } ` , this . importConfig . context ) ;
932+ } catch ( error ) {
933+ handleAndLogError (
934+ error ,
935+ { ...this . importConfig . context , cTUid, locale } ,
936+ `Failed to flush the reference-update mapper for ${ cTUid } in locale ${ locale } . Cross-entry references for its replaced entries will be missing.` ,
937+ ) ;
938+ }
894939 }
895940
896941 async replaceEntriesHandler ( {
@@ -969,8 +1014,18 @@ export default class EntriesImport extends BaseClass {
9691014 const fs = new FsUtility ( { basePath, indexFileName } ) ;
9701015 const indexer = fs . indexFileContent ;
9711016 const indexerCount = values ( indexer ) . length ;
1017+ const expectedCount = this . refUpdateExpected [ `${ cTUid } :${ locale } ` ] || 0 ;
9721018 if ( indexerCount === 0 ) {
973- log . debug ( `No entries found for reference updates in ${ cTUid } - ${ locale } ` , this . importConfig . context ) ;
1019+ // NOTE `expectedCount > 0` here means entries WERE written into this mapper dir but the index
1020+ // is empty, i.e. a chunk was never flushed. That is silent reference loss, not an empty set.
1021+ if ( expectedCount > 0 ) {
1022+ log . warn (
1023+ `Skipping reference updates for ${ cTUid } in locale ${ locale } : ${ expectedCount } entries were written to the mapper but its index is empty. Their cross-entry references will be missing.` ,
1024+ { ...this . importConfig . context , cTUid, locale } ,
1025+ ) ;
1026+ } else {
1027+ log . debug ( `No entries found for reference updates in ${ cTUid } - ${ locale } ` , this . importConfig . context ) ;
1028+ }
9741029 return Promise . resolve ( ) ;
9751030 }
9761031 log . debug (
@@ -1008,6 +1063,8 @@ export default class EntriesImport extends BaseClass {
10081063 ) ;
10091064 } ;
10101065
1066+ let processedCount = 0 ;
1067+
10111068 for ( const index in indexer ) {
10121069 log . debug (
10131070 `Processing reference update chunk ${ index } of ${ indexerCount } for ${ cTUid } in ${ locale } ` ,
@@ -1024,6 +1081,7 @@ export default class EntriesImport extends BaseClass {
10241081
10251082 if ( chunk ) {
10261083 let apiContent = values ( chunk as Record < string , any > [ ] ) ;
1084+ processedCount += apiContent . length ;
10271085 log . debug (
10281086 `Processing ${ apiContent . length } entries for reference updates in chunk ${ index } ` ,
10291087 this . importConfig . context ,
@@ -1046,6 +1104,18 @@ export default class EntriesImport extends BaseClass {
10461104 } ) ;
10471105 }
10481106 }
1107+
1108+ // NOTE Catches the mixed case that the indexerCount === 0 guard above cannot: when the create
1109+ // path flushed its chunk but the replace path did not, the index is non-empty so we do not bail
1110+ // early, we just silently process fewer entries than were written. Compare the two.
1111+ if ( processedCount < expectedCount ) {
1112+ log . warn (
1113+ `Reference updates for ${ cTUid } in locale ${ locale } covered only ${ processedCount } of ${ expectedCount } entries written to the mapper. Cross-entry references for the remaining ${
1114+ expectedCount - processedCount
1115+ } entries will be missing.`,
1116+ { ...this . importConfig . context , cTUid, locale } ,
1117+ ) ;
1118+ }
10491119 }
10501120
10511121 /**
0 commit comments