From e9011a3e7283062ddeb5570138c1edfa253e0629 Mon Sep 17 00:00:00 2001 From: Pritesh Jain Date: Sun, 1 Nov 2026 21:53:02 +0530 Subject: [PATCH 1/2] fix(sap-smartedit): set ContentPage url from label and homepage flag Every migrated SAP ContentPage had an empty url: each content type gets a mandatory url field, but ContentPage carries its route in label and the homepage flag. Storefronts that look pages up by url found none. Co-Authored-By: Claude Opus 5.5 --- api/src/services/sap-smartedit.service.ts | 15 ++++++ .../sap-smartedit/contentpage-url.impex | 8 +++ .../services/sap-smartedit.service.test.ts | 51 +++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 api/tests/fixtures/sap-smartedit/contentpage-url.impex diff --git a/api/src/services/sap-smartedit.service.ts b/api/src/services/sap-smartedit.service.ts index ea64f8df..2f071b2c 100644 --- a/api/src/services/sap-smartedit.service.ts +++ b/api/src/services/sap-smartedit.service.ts @@ -606,6 +606,13 @@ function rowSourceId(row: Record, block: ImpexBlock): string | n .filter((v) => v !== undefined && v !== ''); return parts.length ? parts.join('-') : null; } + +/** A SAP ContentPage's route: `/` for the homepage, else its label with one leading slash. */ +function contentPageUrl(row: Record): string | null { + if (row.homepage === 'true') return '/'; + const label = (row.label ?? '').trim(); + return label ? `/${label.replace(/^\/+/, '')}` : null; +} interface Counters { assetsSkipped: number; refsSkipped: number } /** Transform one ImpEx cell value to its Contentstack field value, by target type. */ @@ -942,6 +949,14 @@ async function createEntry( if (defaultLabel || !existingEntry) { if (!entry.title || !unambiguousDefault) entry.title = id; } + // Every CT is a page with a mandatory `url` (ensureMandatoryFields), but SAP + // ContentPage has no url column: its route is the `label` (/faq), and the + // homepage flag means `/`. Without this every migrated page had an empty url, + // so a storefront querying pages by url found none. + if (srcType === 'ContentPage' && !entry.url) { + const pageUrl = contentPageUrl(row); + if (pageUrl) entry.url = pageUrl; + } entryData[uid] = entry; } diff --git a/api/tests/fixtures/sap-smartedit/contentpage-url.impex b/api/tests/fixtures/sap-smartedit/contentpage-url.impex new file mode 100644 index 00000000..72a91984 --- /dev/null +++ b/api/tests/fixtures/sap-smartedit/contentpage-url.impex @@ -0,0 +1,8 @@ +INSERT_UPDATE ContentPage; uid[unique=true]; name; label; homepage + ; homepage; "Homepage"; homepage; true + ; faq; "FAQ"; /faq; false + ; shipping; "Shipping"; shipping; false + ; nolabel; "No Label"; ; false + +UPDATE ContentPage; uid[unique=true]; name + ; faq; "FAQ" diff --git a/api/tests/unit/services/sap-smartedit.service.test.ts b/api/tests/unit/services/sap-smartedit.service.test.ts index 1101fea5..e79a8be2 100644 --- a/api/tests/unit/services/sap-smartedit.service.test.ts +++ b/api/tests/unit/services/sap-smartedit.service.test.ts @@ -1343,3 +1343,54 @@ describe('sap-smartedit getAllAssets/createEntry — a genuine directory as the expect(fs.readFileSync(onDisk).equals(fs.readFileSync(src))).toBe(true); }); }); + +/** + * Every content type is created as a page with a mandatory `url`, but SAP + * ContentPage has no url column — its route lives in `label`, and the homepage + * is flagged `homepage=true`. Every migrated page used to get an empty url, so + * a storefront looking pages up by url (Spartacus) found none of them. + */ +describe('sap-smartedit createEntry — ContentPage url from label/homepage', () => { + const STACK_URL = 'test-stack-sap-smartedit-contentpage-url'; + const OUT_URL = path.join(process.cwd(), './cmsMigrationData', STACK_URL); + const URL_FIXTURE = path.join(__dirname, '../../fixtures/sap-smartedit/contentpage-url.impex'); + const CT = { + otherCmsTitle: 'ContentPage', contentstackUid: 'cs_contentpage', + fieldMapping: [ + { otherCmsField: 'name', contentstackFieldUid: 'title', contentstackFieldType: 'single_line_text' }, + { otherCmsField: 'label', contentstackFieldUid: 'label', contentstackFieldType: 'single_line_text' }, + ], + }; + let byTitle: Record; + + beforeAll(async () => { + fs.rmSync(OUT_URL, { recursive: true, force: true }); + await sapSmarteditService.createEntry(URL_FIXTURE, '', STACK_URL, 'test-project', [CT], {}, 'en-us', {}); + const entries = JSON.parse(fs.readFileSync(path.join(OUT_URL, 'entries', 'cs_contentpage', 'en-us', 'en-us.json'), 'utf8')); + byTitle = Object.fromEntries(Object.values(entries).map((e: any) => [e.title, e])); + }); + + afterAll(() => { + fs.rmSync(OUT_URL, { recursive: true, force: true }); + }); + + it('gives the homepage the root url', () => { + expect(byTitle.Homepage.url).toBe('/'); + }); + + it('uses a label that is already a path as-is', () => { + expect(byTitle.FAQ.url).toBe('/faq'); + }); + + it('adds the leading slash to a bare label', () => { + expect(byTitle.Shipping.url).toBe('/shipping'); + }); + + it('writes no url when the page has neither label nor homepage flag', () => { + expect('url' in byTitle['No Label']).toBe(false); + }); + + it('keeps the url when a later partial UPDATE row carries no label', () => { + expect(byTitle.FAQ.url).toBe('/faq'); + }); +}); From 2d5dac3892fefe626cb86d6d500d3dd7c85229d9 Mon Sep 17 00:00:00 2001 From: Pritesh Jain Date: Mon, 2 Nov 2026 18:44:48 +0530 Subject: [PATCH 2/2] fix(sap-smartedit): homepage url without a homepage column, flag in any case Exports without a homepage column (HAC exports often omit it) now give the page labelled homepage the root url, SAP's default homepage label. The homepage flag is read in any case, like the rest of the connector's booleans. Co-Authored-By: Claude Opus 5.5 --- api/src/services/sap-smartedit.service.ts | 11 ++++- .../contentpage-url-no-flag.impex | 3 ++ .../sap-smartedit/contentpage-url.impex | 2 + .../services/sap-smartedit.service.test.ts | 42 +++++++++++++++++++ 4 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 api/tests/fixtures/sap-smartedit/contentpage-url-no-flag.impex diff --git a/api/src/services/sap-smartedit.service.ts b/api/src/services/sap-smartedit.service.ts index 2f071b2c..43304006 100644 --- a/api/src/services/sap-smartedit.service.ts +++ b/api/src/services/sap-smartedit.service.ts @@ -607,10 +607,17 @@ function rowSourceId(row: Record, block: ImpexBlock): string | n return parts.length ? parts.join('-') : null; } -/** A SAP ContentPage's route: `/` for the homepage, else its label with one leading slash. */ +/** + * A SAP ContentPage's route: `/` for the homepage, else its label with one leading slash. + * Exports without a `homepage` column (HAC exports often omit it) fall back to SAP's + * default homepage label, `homepage`. + */ function contentPageUrl(row: Record): string | null { - if (row.homepage === 'true') return '/'; const label = (row.label ?? '').trim(); + const isHome = row.homepage === undefined + ? /^\/?homepage$/i.test(label) + : String(row.homepage).trim().toLowerCase() === 'true'; + if (isHome) return '/'; return label ? `/${label.replace(/^\/+/, '')}` : null; } interface Counters { assetsSkipped: number; refsSkipped: number } diff --git a/api/tests/fixtures/sap-smartedit/contentpage-url-no-flag.impex b/api/tests/fixtures/sap-smartedit/contentpage-url-no-flag.impex new file mode 100644 index 00000000..dca9b0f4 --- /dev/null +++ b/api/tests/fixtures/sap-smartedit/contentpage-url-no-flag.impex @@ -0,0 +1,3 @@ +INSERT_UPDATE ContentPage; uid[unique=true]; name; label + ; home; "Home"; homepage + ; about; "About"; about diff --git a/api/tests/fixtures/sap-smartedit/contentpage-url.impex b/api/tests/fixtures/sap-smartedit/contentpage-url.impex index 72a91984..7777bb4e 100644 --- a/api/tests/fixtures/sap-smartedit/contentpage-url.impex +++ b/api/tests/fixtures/sap-smartedit/contentpage-url.impex @@ -3,6 +3,8 @@ INSERT_UPDATE ContentPage; uid[unique=true]; name; label; homepage ; faq; "FAQ"; /faq; false ; shipping; "Shipping"; shipping; false ; nolabel; "No Label"; ; false + ; campaign; "Campaign"; campaign; TRUE + ; legacyhome; "Legacy Home"; homepage; false UPDATE ContentPage; uid[unique=true]; name ; faq; "FAQ" diff --git a/api/tests/unit/services/sap-smartedit.service.test.ts b/api/tests/unit/services/sap-smartedit.service.test.ts index e79a8be2..d565e2aa 100644 --- a/api/tests/unit/services/sap-smartedit.service.test.ts +++ b/api/tests/unit/services/sap-smartedit.service.test.ts @@ -1393,4 +1393,46 @@ describe('sap-smartedit createEntry — ContentPage url from label/homepage', () it('keeps the url when a later partial UPDATE row carries no label', () => { expect(byTitle.FAQ.url).toBe('/faq'); }); + + it('reads the homepage flag in any case (TRUE)', () => { + expect(byTitle.Campaign.url).toBe('/'); + }); + + it('keeps /homepage for a page labelled homepage whose flag is false', () => { + expect(byTitle['Legacy Home'].url).toBe('/homepage'); + }); +}); + +/** HAC exports often have no `homepage` column; SAP's default homepage label is `homepage`. */ +describe('sap-smartedit createEntry — ContentPage url without a homepage column', () => { + const STACK_NOFLAG = 'test-stack-sap-smartedit-contentpage-url-no-flag'; + const OUT_NOFLAG = path.join(process.cwd(), './cmsMigrationData', STACK_NOFLAG); + const NOFLAG_FIXTURE = path.join(__dirname, '../../fixtures/sap-smartedit/contentpage-url-no-flag.impex'); + const CT = { + otherCmsTitle: 'ContentPage', contentstackUid: 'cs_contentpage', + fieldMapping: [ + { otherCmsField: 'name', contentstackFieldUid: 'title', contentstackFieldType: 'single_line_text' }, + { otherCmsField: 'label', contentstackFieldUid: 'label', contentstackFieldType: 'single_line_text' }, + ], + }; + let byTitle: Record; + + beforeAll(async () => { + fs.rmSync(OUT_NOFLAG, { recursive: true, force: true }); + await sapSmarteditService.createEntry(NOFLAG_FIXTURE, '', STACK_NOFLAG, 'test-project', [CT], {}, 'en-us', {}); + const entries = JSON.parse(fs.readFileSync(path.join(OUT_NOFLAG, 'entries', 'cs_contentpage', 'en-us', 'en-us.json'), 'utf8')); + byTitle = Object.fromEntries(Object.values(entries).map((e: any) => [e.title, e])); + }); + + afterAll(() => { + fs.rmSync(OUT_NOFLAG, { recursive: true, force: true }); + }); + + it('gives the page labelled homepage the root url', () => { + expect(byTitle.Home.url).toBe('/'); + }); + + it('still routes other pages by label', () => { + expect(byTitle.About.url).toBe('/about'); + }); });