Skip to content

Commit acbfbf9

Browse files
authored
Merge branch 'development' into chore/version-bump-v2
2 parents e1df89e + fb79f28 commit acbfbf9

1 file changed

Lines changed: 71 additions & 1 deletion

File tree

  • packages/contentstack-import/src/import/modules

‎packages/contentstack-import/src/import/modules/entries.ts‎

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)