Skip to content

CMG-1101: fix delta reference resolution and locale-migrated tracking - #1128

Open
chetan-contentstack wants to merge 5 commits into
devfrom
bugfix/cmg-1101-ref-locale
Open

CMG-1101: fix delta reference resolution and locale-migrated tracking#1128
chetan-contentstack wants to merge 5 commits into
devfrom
bugfix/cmg-1101-ref-locale

Conversation

@chetan-contentstack

@chetan-contentstack chetan-contentstack commented Jul 31, 2026

Copy link
Copy Markdown

🔗 Jira Tickets

Parent story: CMG-789 — Delta Migration | v1.0.0 | Update Feature Implementation


📋 PR Type

  • ✨ Feature
  • 🐛 Bug Fix
  • 🔥 Hotfix
  • ♻️ Refactor
  • 🧹 Chore / Dependency Update
  • 📝 Documentation

📝 Description

What changed?

  • CMG-1101 — References resolve on the delta/update path. The master-locale bulk import resolves references via the CLI's uid-mapper; the update path skipped it and wrote source-cms ids straight to the destination. Threads the flat + per-locale entry uid-mapper into the update config as __entryMapping__ and remaps Link(Entry) uids in entry-update-script.cjs.
  • CMG-1101 — Only actually-processed locales get marked "migrated". Was recording all configured locales after every run, so a newly-added locale got flagged done before it ran and was skipped on later restarts. Iteration 1 keeps the old behaviour (single bulk); iteration 2+ records master + locales present in updated-entries.json.
  • Typo in contentful.service.ts:439: Array?.isArray(entryId?.id)Array.isArray(entryId?.[id]).
  • Unit coverage added for the new helpers.

Why?

Reproduced with the bundled contentful-export.json / -v2.json / -v3.json: on iteration 2 the destination reference field went empty and the newly-added locale had zero entries.


🧩 Affected Areas

  • api — Node.js backend
  • ui
  • upload-api
  • docker / docker-compose
  • CI / GitHub Actions workflows
  • Environment variables / config

🧪 How to Test

Use the three bundled fixtures.

  1. Iter 1 — migrate contentful-export.json (1 locale, 2 refs). Confirm both refs resolve in destination.
  2. Iter 2 — restart with contentful-export-v2.json (adds en-IN, updates one entry's title). On Map Entry, select the changed entry. After migration, confirm refs still resolve in en-US and en-IN is populated.
  3. Iter 3 — restart with contentful-export-v3.json (adds en-GB, adds relatedArticles ref). Confirm both author and relatedArticles resolve in all three locales.
  4. cd api && npm test passes.

📸 Screenshots / Recordings

Before After
Iter 2: author ref empty in destination after re-mapping Ref resolves to destination uid
Iter 2: en-IN locale has 0 entries en-IN populated for every entry
Iter 3: relatedArticles empty across locales Both refs resolve in all 3 locales

🔗 Related PRs / Dependencies


✅ Author Checklist

  • Branch follows naming convention: bugfix/cmg-1101-delta-ref-locale
  • Jira ticket linked
  • Self-reviewed the diff
  • .env / example.env updated — N/A
  • No secrets committed
  • Existing tests pass (npm test in api/)
  • New tests written for the added helpers
  • README.md / docs updated — N/A
  • Talisman pre-push scan passes

👀 Reviewer Notes

  • Reference resolution precedence: per-locale new → per-locale old → flat new → flat old → identity. Identity fallback preserves current behaviour for non-Contentful sources.
  • Iteration-1 locale tracking is intentionally unchanged — that path is a single bulk import, so the full configured set is what actually ran.
  • No changes outside api/src/ and api/tests/.

chetan-contentstack added 2 commits July 31, 2026 12:17
…le-migrated tracking

Reference fields (Link/Array Entry) written during a locale-localize restart
kept the source CMS entry id instead of the real Contentstack uid, since only
the master-locale bulk import resolved references correctly. Threads entry
uid-mapper data into the update config so entry-update-script.cjs can resolve
them, mirroring the existing asset uid resolution.

Also fixes a bug where finishing any locale marked ALL configured locales as
migrated, causing not-yet-migrated locales to be silently skipped on later
delta restarts. Now only locales actually processed in that run are recorded.
Was reading a literal .id property on the entryId map instead of the
dynamic key, so the single-reference branch never took the array path
when the mapper legitimately held an array of destination uids.
…k SLA breach

SNYK-JS-FASTURI-17675102 (fast-uri) and SNYK-JS-BRACEEXPANSION-17706650
(brace-expansion) crossed the 30-day SLA and were blocking every PR
against dev. Bumping the root overrides to the latest patched versions.

@umesh-more-cstk umesh-more-cstk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review summary

Three independent changes here: (a) a delta-migration fix that threads entry uid-mapper data into the entry-update script so reference fields resolve to real Contentstack uids, (b) a change to how migrated_locales is recorded, and (c) a root-level dependency override bump. The delta reference-resolution work is sound and well tested — I traced the premise end-to-end and it holds: removeEntriesFromDatabase snapshots the on-disk transformed entries before the CLI import, and createRefrence (contentful.service.ts:1584) writes the source cms id into { uid, _content_type_uid }, so this update path really was writing source-side uids onto localized entries. Wiring order is also correct — writeUidMapping/writePerLocaleEntryUidMapping run inside runCli before enrichConfigWithEntryMapping is called at migration.service.ts:1265.

Blocking

  1. runCli.service.ts:373recordMigratedLocales still runs before the localize/update CLI that actually writes those locales, and that CLI swallows every failure. Same premature-marking class of bug this PR is fixing, just via a different trigger.
  2. package.json:49-50 — the Snyk bump only touches the root project; the three deployables still pin the vulnerable versions.

Non-blocking

  1. entry-update.utils.ts:328data?.entry is read as flat, but the sibling reader in contentMapper.service.ts explicitly handles a nested-per-content-type shape.
  2. runCli.service.ts:369 — a new locale whose entries are all newly created never lands in updated-entries.json, so it is never recorded as migrated.
  3. entry-update-script.cjs:13isReferenceArray is all-or-nothing; one unresolved element disables resolution for the whole field.
  4. entry-update-script.cjs:149 — resolution is top-level only (group/modular-block/JSON-RTE-embedded references still keep source uids).
  5. contentful.service.ts:439 — the typo fix is correct but the branch it fixes is unreachable, so net behaviour is unchanged.

Verified good

  • enrichConfigWithEntryMapping mirrors enrichConfigWithAssetMapping exactly (try/catch, empty-mapping default, non-throwing on a malformed config) and never blocks the main flow.
  • extractLocalesFromUpdateConfig's __-prefix skip correctly excludes __assetMapping__ / __entryMapping__ / __assetUpdates__, and it was verified that those keys are written after runCli reads the file — so the skip is belt-and-braces, not load-bearing.
  • Locale-code direction is consistent on both sides: updated-entries.json keys carry the destination code (removeEntriesFromDatabase uses the on-disk locale dir name), which is what migrated_locales / isFullMigrationForLocale compare against.
  • New unit tests are genuinely good — they cover the full fallback precedence chain, the back-compat identity path with no mapping supplied, and malformed/absent config files.

Out-of-diff notes (can't be anchored inline)

  • api/src/services/updateEntryCli.service.ts:240-249 — the catch only logs; a failed entry-update CLI run is indistinguishable from success to the caller. This is what makes finding #1 a silent data problem rather than a retryable one.
  • api/src/services/contentMapper.service.ts:2206-2233getEntryUidMap / flattenNestedUidMap, the existing precedent for reading uid-mapper.json's entry key (handles nested shape and an entryUid variant). Relevant to finding #3.
  • api/src/utils/entry-update.utils.ts:188-190if (!csEntryUid) continue; is why brand-new entries never reach entriesToUpdate. Relevant to finding #4.
  • Lockfile evidence for finding #2: api/package-lock.json fast-uri 3.1.2 / brace-expansion 5.0.6; ui/package-lock.json brace-expansion 5.0.6; upload-api/package-lock.json fast-uri 3.1.2 / brace-expansion 5.0.7. Root package.json declares no workspaces, and its only deps are husky + validate-branch-name.
  • Process note: I could not read the PR description / PR-type / Affected-Areas checkboxes (the GitHub API is SAML-SSO blocked for my token), so I could not check the diff against the stated scope. Flagging on content alone: the dependency bump commit (cb7c1ef1) is unrelated to the delta and Contentful fixes in the same branch and would be easier to review — and to revert — on its own.

Comment thread api/src/services/runCli.service.ts Outdated
Comment thread api/src/services/runCli.service.ts Outdated
Comment thread api/src/utils/entry-update.utils.ts Outdated
Comment thread api/src/utils/entry-update-script.cjs Outdated
Comment thread api/src/utils/entry-update-script.cjs
Comment thread api/src/services/contentful.service.ts
Comment thread package.json
@umesh-more-cstk

Copy link
Copy Markdown
Contributor

Follow-up to my review above — the SAML-SSO block on my token is resolved, so I could finally read the PR description and check the diff against the stated scope. Retracting the "could not check" caveat at the end of that review; here is the actual result.

The three functional bullets under What changed? match the diff accurately, and Affected Areas → api is right for them. The dependency bump is the part that isn't covered:

  • PR Type has only 🐛 Bug Fix ticked. Commit cb7c1ef1 bumps the fast-uri and brace-expansion overrides (package.json +2/-2, package-lock.json +7/-7), so 🧹 Chore / Dependency Update applies too.
  • What changed? does not mention the bump at all — a reader reviewing from the description alone would not know it is in here.
  • Affected Areas — the bump lives in the root package.json, which none of the listed areas covers. Worth noting that ui and upload-api being unticked is consistent with blocker Development #2: the override genuinely does not reach them, which is exactly the problem.
  • How to Test covers the three migration iterations and cd api && npm test, but has no step that verifies the vulnerable versions actually resolved away. Given blocker Development #2, a npm ls fast-uri brace-expansion in each of api, ui, upload-api would have caught it before review.

None of this changes the technical findings above — the two blockers and five non-blocking items stand as written.

chetan-contentstack added 2 commits July 31, 2026 15:46
Root package.json has no workspaces, so root overrides don't reach
api/ui/upload-api installs. Adding the same pins to each sub-project's
package.json and regenerating their lockfiles so Snyk stops flagging
the transitive vulnerabilities.
…le-migrated tracking

Address review comments on #1128:

- Move iteration 2+ recordMigratedLocales out of runCli into migration.service.ts,
  after utilsUpdateCli.updateEntryCli resolves. Recording pre-update meant a silent
  update-CLI failure would still flag the locales as migrated, permanently skipping
  them on later restarts — same class of bug this PR fixes.
- Union in Object.keys(uid-mapper.entryByLocale) so brand-new-entries-only locales
  (no rows in updated-entries.json because they had no prior csEntryUid) are also
  recorded as migrated.
- Extract flattenNestedUidMap into uid-mapper.utils.js and reuse it in both
  contentMapper.service and entry-update.utils. entry-update.utils.enrichConfigWithEntryMapping
  now handles the nested per-content-type shape (and the entryUid variant) instead
  of assuming a flat map.
- entry-update-script.isReferenceArray: switch from .every() to .some(), and pass
  non-reference items through in resolveReferenceField. Producers legitimately emit
  mixed arrays (raw Contentful Link objects for unresolved refs, [undefined] from
  the single-ref fallback) — one such item no longer disables resolution for the
  whole field.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants