-
Notifications
You must be signed in to change notification settings - Fork 10
Dev #1153
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dev #1153
Changes from all commits
e3d9df5
cc632b4
cf6f1c3
0d8faf4
e002672
9708bdb
014cfa9
c2d98f2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1363,8 +1363,11 @@ const createEntry = async ({ | |
| const entryMapping: Record<string, string[]> = {}; | ||
| const usedEntryUids = new Set<string>(); | ||
|
|
||
| // Process each entry file | ||
| for await (const fileName of read(entriesDir)) { | ||
| // Process each entry file. Sorted (not raw fs-readdir-recursive order) so that when two | ||
| // files legitimately collide on the same modelId+locale (see CMG-1112), which one "wins" | ||
| // and gets migrated is deterministic and reproducible across machines/runs, rather than | ||
| // depending on filesystem directory order. | ||
| for await (const fileName of [...read(entriesDir)].sort()) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: The sibling layer wasn't sorted with it. Failure scenario: for a Suggested fix: apply the same Unrelated to this PR, but noting it while we're here: Generated by Claude Code |
||
| const filePath = path.join(entriesDir, fileName); | ||
| if (filePath?.startsWith?.(damPath)) { | ||
| continue; | ||
|
|
@@ -1373,23 +1376,6 @@ const createEntry = async ({ | |
| if (typeof content === 'string') { | ||
| const parseData = JSON.parse(content); | ||
|
|
||
| // AEM can export a page TEMPLATE's own structure/schema definition (e.g. | ||
| // /conf/.../settings/wcm/templates/<template>/structure[.html]) as a separate file | ||
| // alongside real pages that use that template β it isn't content, just the | ||
| // template's own component-allow-list. Exclude it outright rather than letting it | ||
| // compete with a real page for the same derived id (see CMG-1112): this removes the | ||
| // ambiguity entirely instead of leaving the outcome dependent on directory-walk order. | ||
| const repoPath: string | undefined = parseData?.dataLayer?.[parseData?.id]?.['repo:path']; | ||
| if (repoPath && /\/settings\/wcm\/templates\/[^/]+\/structure(\.html)?$/.test(repoPath)) { | ||
| await customLogger( | ||
| projectId, | ||
| destinationStackId, | ||
| 'warn', | ||
| getLogMessage(srcFunc, `Skipped entry from "${fileName}": AEM template structure/schema definition, not real content (repo:path "${repoPath}").`, {}) | ||
| ); | ||
| continue; | ||
| } | ||
|
|
||
| // Use the page model's stable "id" as the entry uid so uid-mapper keys | ||
| // stay consistent across delta iterations; random uuid only as fallback. | ||
| let modelId = typeof parseData?.id === 'string' && parseData.id.trim() !== '' | ||
|
|
@@ -1430,9 +1416,6 @@ const createEntry = async ({ | |
| continue; | ||
| } | ||
| const uid = modelId || uuidv4?.()?.replace?.(/-/g, ''); | ||
| if (collisionKey) { | ||
| usedEntryUids.add(collisionKey); | ||
| } | ||
| const title = getTitle(parseData); | ||
| const isEFragment = isExperienceFragment(parseData); | ||
| const templateUid = isEFragment?.isXF ? parseData?.title : parseData?.templateName ?? parseData?.templateType; | ||
|
|
@@ -1446,6 +1429,14 @@ const createEntry = async ({ | |
| data.publish_details = []; | ||
|
|
||
| if (contentType?.contentstackUid && data && mappedLocale) { | ||
| // Reserve the collision key only now that an entry is actually being emitted β a | ||
| // file that reaches the "no content type matched" / "no mapped locale" branch below | ||
| // must NOT consume the key, or it would permanently block a sibling file (sharing | ||
| // the same modelId::locale) that could otherwise have produced the real entry, | ||
| // leaving zero entries instead of one. | ||
| if (collisionKey) { | ||
| usedEntryUids.add(collisionKey); | ||
| } | ||
| const mappedValue = (keyMapper as Record<string, string> | undefined)?.[contentType.contentstackUid]; | ||
| const resolvedCtUid: string = | ||
| mappedValue && mappedValue !== '' | ||
|
|
||
| Original file line number | Diff line number | Diff line change | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -29,7 +29,7 @@ | ||||||||||||||||
| "overrides": { | |||||||||||||||||
| "axios": ">=1.16.0", | |||||||||||||||||
| "nth-check": ">=2.0.1", | |||||||||||||||||
| "postcss": ">=8.5.10", | |||||||||||||||||
| "postcss": ">=8.5.23", | |||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. blocker: This override doesn't reach anything, and two of the three workspaces stay on the vulnerable postcss. Verified against the lockfiles at this head:
Root Failure scenario: the CVE this PR is titled for (e002672, "close SLA-breached CVEs") stays open in Suggested fix: add Generated by Claude Code |
|||||||||||||||||
| "serialize-javascript": ">=6.0.2", | |||||||||||||||||
| "@babel/runtime": ">=7.26.10", | |||||||||||||||||
| "lodash": "^4.18.1", | |||||||||||||||||
|
|
|||||||||||||||||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: Sorting makes the collision winner deterministic, but it doesn't make it correct β and the
at most one entry per modelId::localeinvariant is unchanged by this PR. So when the templatestructureexport and a real page derive the samemodelId(the exact CMG-1112 case the deleted block described), exactly one of them can still be migrated, and which one is now decided purely by lexicographic path order.Failure scenario, if the structure export does resolve a content type (i.e. it carries a
templateName/templateTypethat matches anotherCmsUid, or anisXFtitlematch):conf/...sorts beforecontent/...("conf"<"cont"), so the structure file is processed first, matches at line 1432, reservesmodelId::localeat 1437, and the real page then hits the duplicate check at ~1408 and is dropped with aSkipped duplicate entrywarning. That swaps the reported symptom (structure entry missing) for a worse one (the actual page missing), and it's silent apart from a warn log.Conversely, if the structure export never matches a content type β which is what 9708bdb's message assumes β then it can never emit an entry at all, and deleting the exclusion block is a no-op for output: the deferred reservation alone fixes the reopened bug. Either way the exclusion removal doesn't get the structure entry migrated.
Could you confirm which case the CMG-1112 export actually hits? If it's the first, the fix wants to be at the uid derivation rather than the walk order β fold the disambiguating path (
dataLayer[id]['repo:path']or:path) intomodelIdwhen two files shareparseData.id, so both can be emitted instead of one shadowing the other; or, if only one should ever win, encode that precedence explicitly (real content beats template structure) rather than leaving it tosort().Generated by Claude Code