From 7d0464a2ce7fe77fb133dc29ad6efe87162628f0 Mon Sep 17 00:00:00 2001 From: tommy230 Date: Tue, 18 Aug 2026 20:18:26 +0000 Subject: [PATCH] Resolve passthrough assets after exact misses Fall back to passthrough-prefixed FileMap entries when exact asset paths are absent. Cover passthrough resolution and preserve exact-key precedence. --- docs/features/site-import.md | 3 +- src/__tests__/siteImport/assetPlan.test.ts | 43 ++++++++++++++++++++++ src/core/siteImport/assetPlan.ts | 7 +++- 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/docs/features/site-import.md b/docs/features/site-import.md index f6f28c9d8..6270cc589 100644 --- a/docs/features/site-import.md +++ b/docs/features/site-import.md @@ -120,7 +120,8 @@ User drops files / folder / static .zip / CMS bundle .zip ▼ buildAssetPlan(pagePlans, cssFileResults, fileMap, rawStylesheetSources) │ normalizes url() in node props, HTML attributes, CSS values, raw @keyframes - │ CSS, and kept-stylesheet text to FileMap keys + │ CSS, and kept-stylesheet text to FileMap keys; a key that misses + │ exactly is retried under passthrough/ before being dropped │ resolves @font-face → ImportFontFamily[] │ flattens kept stylesheets (mode 'file') → ImportStylesheet[] │ collects deduplicated asset list diff --git a/src/__tests__/siteImport/assetPlan.test.ts b/src/__tests__/siteImport/assetPlan.test.ts index 1a42c6250..695b084f4 100644 --- a/src/__tests__/siteImport/assetPlan.test.ts +++ b/src/__tests__/siteImport/assetPlan.test.ts @@ -45,6 +45,49 @@ describe('buildAssetPlan — img src normalisation', () => { expect(assets.some((a) => a.sourcePath === 'images/hero.png')).toBe(true) }) + it('falls back to a passthrough FileMap key for a root-relative img src', () => { + const sourcePath = 'passthrough/wp-content/uploads/x.png' + const fileMap = makeFileMap({ + 'index.html': { bytes: txt('') }, + [sourcePath]: { bytes: MINIMAL_PNG, mimeType: 'image/png' }, + }) + const { pagePlan } = makeHtmlPagePlan( + 'index.html', + new TextDecoder().decode(fileMap.files['index.html']!.bytes), + fileMap, + ) + const { normalizedPagePlans, assets } = buildAssetPlan([pagePlan], [], fileMap) + + const imageNode = Object.values(normalizedPagePlans[0].nodeFragment.nodes).find( + (node) => node.moduleId === 'base.image', + ) + expect(imageNode?.props['src']).toBe(sourcePath) + expect(assets.some((asset) => asset.sourcePath === sourcePath)).toBe(true) + }) + + it('prefers an exact FileMap key over its passthrough counterpart', () => { + const exactPath = 'wp-content/uploads/x.png' + const passthroughPath = `passthrough/${exactPath}` + const exactBytes = txt('exact') + const fileMap = makeFileMap({ + 'index.html': { bytes: txt('') }, + [exactPath]: { bytes: exactBytes, mimeType: 'image/png' }, + [passthroughPath]: { bytes: txt('passthrough'), mimeType: 'image/png' }, + }) + const { pagePlan } = makeHtmlPagePlan( + 'index.html', + new TextDecoder().decode(fileMap.files['index.html']!.bytes), + fileMap, + ) + const { normalizedPagePlans, assets } = buildAssetPlan([pagePlan], [], fileMap) + + const imageNode = Object.values(normalizedPagePlans[0].nodeFragment.nodes).find( + (node) => node.moduleId === 'base.image', + ) + expect(imageNode?.props['src']).toBe(exactPath) + expect(assets.find((asset) => asset.sourcePath === exactPath)?.bytes).toBe(exactBytes) + }) + it('leaves external URLs unchanged', () => { const fileMap = makeFileMap({ 'index.html': { bytes: txt('') }, diff --git a/src/core/siteImport/assetPlan.ts b/src/core/siteImport/assetPlan.ts index d57e85213..05bcd94d2 100644 --- a/src/core/siteImport/assetPlan.ts +++ b/src/core/siteImport/assetPlan.ts @@ -545,9 +545,12 @@ function resolveAndRecord( ): string | null { if (!rawUrl || EXTERNAL_URL_RE.test(rawUrl)) return null - const fileMapKey = resolveRelativePath(rawUrl, basePath) - if (!fileMapKey) return null + const resolvedKey = resolveRelativePath(rawUrl, basePath) + if (!resolvedKey) return null + const fileMapKey = fileMap.files[resolvedKey] + ? resolvedKey + : `passthrough/${resolvedKey}` const entry = fileMap.files[fileMapKey] if (!entry) return null