diff --git a/knip.json b/knip.json index 70e847bce..6789a3bf0 100644 --- a/knip.json +++ b/knip.json @@ -5,7 +5,8 @@ "src/api/**", "src/components/ui/**", "src/config/announcements.ts", - "src/components/shared/BetaFeatureWrapper/BetaFeatureWrapper.tsx" + "src/components/shared/BetaFeatureWrapper/BetaFeatureWrapper.tsx", + "src/utils/publicAsset.ts" ], "ignoreDependencies": [ "@tanstack/react-query-devtools", diff --git a/src/utils/publicAsset.test.ts b/src/utils/publicAsset.test.ts new file mode 100644 index 000000000..d02aa7cd3 --- /dev/null +++ b/src/utils/publicAsset.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, it } from "vitest"; + +import { computeAssetsBase } from "./publicAsset"; + +describe("computeAssetsBase", () => { + it("returns the dev server root in dev mode", () => { + const base = computeAssetsBase( + "http://localhost:5173/src/utils/publicAsset.ts", + true, + ); + expect(base).toBe("http://localhost:5173/"); + expect(new URL("example-pipelines/foo.yaml", base).href).toBe( + "http://localhost:5173/example-pipelines/foo.yaml", + ); + }); + + it("returns the bundle base in built mode when assets live under a nested path", () => { + const base = computeAssetsBase( + "https://cdn.example.com/app/build-123/abc1234/assets/index-hash.js", + false, + ); + expect(base).toBe("https://cdn.example.com/app/build-123/abc1234/"); + expect(new URL("example-pipelines/foo.yaml", base).href).toBe( + "https://cdn.example.com/app/build-123/abc1234/example-pipelines/foo.yaml", + ); + }); + + it("returns the same-origin base in built mode with a same-origin import.meta.url", () => { + const base = computeAssetsBase( + "https://app.example.com/assets/index-hash.js", + false, + ); + expect(base).toBe("https://app.example.com/"); + expect(new URL("example-pipelines/foo.yaml", base).href).toBe( + "https://app.example.com/example-pipelines/foo.yaml", + ); + }); + + it("preserves filenames with spaces when resolved against the computed base", () => { + const base = computeAssetsBase( + "https://cdn.example.com/app/b/abc/assets/index.js", + false, + ); + expect(new URL("example-pipelines/Intro-Hello World.yaml", base).href).toBe( + "https://cdn.example.com/app/b/abc/example-pipelines/Intro-Hello%20World.yaml", + ); + }); +}); diff --git a/src/utils/publicAsset.ts b/src/utils/publicAsset.ts new file mode 100644 index 000000000..2dfafa3e9 --- /dev/null +++ b/src/utils/publicAsset.ts @@ -0,0 +1,13 @@ +export function computeAssetsBase(metaUrl: string, isDev: boolean): string { + const here = new URL(metaUrl); + return isDev ? new URL("/", here).href : new URL("..", here).href; +} + +export const ASSETS_BASE = computeAssetsBase( + import.meta.url, + Boolean(import.meta.env.DEV), +); + +export function publicAsset(path: string): string { + return new URL(path, ASSETS_BASE).href; +}