diff --git a/tests/e2e/release/public-htaccess.local-prod.spec.ts b/tests/e2e/release/public-htaccess.local-prod.spec.ts index 24b324f4..ce992b49 100644 --- a/tests/e2e/release/public-htaccess.local-prod.spec.ts +++ b/tests/e2e/release/public-htaccess.local-prod.spec.ts @@ -231,9 +231,9 @@ async function distStaticPaths() { workboxFile ? `/${workboxFile}` : "", releaseEntry.entry ? `/${releaseEntry.entry}` : "", ...(releaseEntry.css || []).map((fileName: string) => `/${fileName}`), - ...(releaseManifest.index_asset_urls || []), - ...(releaseManifest.pwa_asset_urls || []), - ...(releaseManifest.asset_urls || []), + ...(releaseManifest.index_asset_urls || []).map(releaseRootStaticPath), + ...(releaseManifest.pwa_asset_urls || []).map(releaseRootStaticPath), + ...(releaseManifest.asset_urls || []).map(releaseRootStaticPath), cssFile || "", ].filter(Boolean) ) @@ -304,6 +304,7 @@ test.describe("public .htaccess static fallback", () => { test("emits favicon and manifest links from generated assets", async () => { const indexHtml = await fs.readFile(path.join(DIST_DIR, "index.html"), "utf8"); + const releaseManifest = JSON.parse(await fs.readFile(path.join(DIST_DIR, "release-manifest.json"), "utf8")); const legacyManifest = JSON.parse(await fs.readFile(path.join(DIST_DIR, "manifest.json"), "utf8")); const rootWebManifest = JSON.parse(await fs.readFile(path.join(DIST_DIR, "manifest.webmanifest"), "utf8")); @@ -327,6 +328,16 @@ test.describe("public .htaccess static fallback", () => { for (const icon of [...(legacyManifest.icons || []), ...(rootWebManifest.icons || [])]) { expect(icon.src).toMatch(/^(?:assets|\/master\/frontend\/assets)\/favicons\//); } + + for (const assetPath of [ + releaseManifest.entry, + ...(releaseManifest.css || []), + ...(releaseManifest.index_asset_urls || []), + ...(releaseManifest.pwa_asset_urls || []), + ...(releaseManifest.asset_urls || []), + ].filter(Boolean)) { + expect(assetPath, "release manifest asset paths must be base-relative").not.toMatch(/^\/(?!master\/frontend\/)/); + } }); test("serves the app shell from root, deep links, and release-prefixed paths", async ({ request }) => { diff --git a/tests/e2e/release/release.live-smoke.spec.ts b/tests/e2e/release/release.live-smoke.spec.ts index 7dd4c7b6..ffe63213 100644 --- a/tests/e2e/release/release.live-smoke.spec.ts +++ b/tests/e2e/release/release.live-smoke.spec.ts @@ -128,7 +128,7 @@ test("@public-live release manifest, shell, and static assets are available", as for (const assetPath of assetPaths) { const hashKey = assetPath.startsWith("/") ? assetPath : `/${assetPath}`; - await expectStaticAsset(request, assetPath, manifest.asset_hashes?.[hashKey]); + await expectStaticAsset(request, assetPath, manifest.asset_hashes?.[assetPath] || manifest.asset_hashes?.[hashKey]); } }); diff --git a/vite.config.js b/vite.config.js index ac4367d2..8a16e7aa 100644 --- a/vite.config.js +++ b/vite.config.js @@ -86,6 +86,10 @@ function releaseManifestPath(pathname) { return normalizedPathname } +function releaseManifestAssetPath(value) { + return String(value || '').replace(/^\/+/, '') +} + function getGitCommit() { const envCommit = firstReleaseCommitEnv() if (envCommit) { @@ -200,7 +204,7 @@ function releaseDistAssetUrls(outputDirectory) { continue } - assetUrls.push(`/${relativePath}`) + assetUrls.push(relativePath) } } @@ -228,7 +232,7 @@ function patchBuefyCssMediaQuery() { nextCode = nextCode.replaceAll(invalidSnippet, fixedSnippet) didPatch = true } - + if (!didPatch) { return null } @@ -356,10 +360,11 @@ function releaseMetadataManifest() { const emittedAssetUrls = releaseDistAssetUrls(outputDirectory) const indexAssetUrls = Array.from(indexHtml.matchAll(/\b(?:href|src)=["']([^"']+)["']/g)) .map((match) => releaseUrlPath(match[1], '/index.html')) + .map(releaseManifestAssetPath) .filter(Boolean) const releaseEntryAssetUrls = [ - releaseEntry.entry ? `/${releaseEntry.entry}` : '', - ...(Array.isArray(releaseEntry.css) ? releaseEntry.css.map((fileName) => `/${fileName}`) : []) + releaseEntry.entry ? releaseManifestAssetPath(releaseEntry.entry) : '', + ...(Array.isArray(releaseEntry.css) ? releaseEntry.css.map(releaseManifestAssetPath) : []) ].filter(Boolean) const pwaAssetUrls = [ 'manifest.json', @@ -373,7 +378,7 @@ function releaseMetadataManifest() { ...rootFiles.filter((fileName) => /^workbox-[^/]+\.js$/.test(fileName)) ] .filter((fileName) => fs.existsSync(path.join(outputDirectory, fileName))) - .map((fileName) => `/${fileName.replace(/\\/g, '/')}`) + .map((fileName) => releaseManifestAssetPath(fileName.replace(/\\/g, '/'))) for (const manifestPath of ['manifest.webmanifest', 'assets/manifest.webmanifest']) { const absoluteManifestPath = path.join(outputDirectory, manifestPath) @@ -386,7 +391,7 @@ function releaseMetadataManifest() { for (const icon of pwaManifest.icons || []) { const iconPath = releaseUrlPath(icon.src || '', `/${manifestPath}`) if (iconPath) { - pwaAssetUrls.push(iconPath) + pwaAssetUrls.push(releaseManifestAssetPath(iconPath)) } } } catch { @@ -395,8 +400,8 @@ function releaseMetadataManifest() { } const criticalAssetUrls = Array.from(new Set([ - '/index.html', - '/release-entry.json', + 'index.html', + 'release-entry.json', ...emittedAssetUrls, ...indexAssetUrls, ...releaseEntryAssetUrls,