Files
wehub-resource-sync b3a7f98e5a
CI / E2E Cloudflare (4/8) (push) Failing after 0s
CI / E2E Cloudflare (8/8) (push) Failing after 1s
CI / Lint (push) Failing after 1s
Auto Extract / Extract (push) Failing after 4s
CI / Version Check (push) Failing after 9s
CI / Integration Tests (push) Failing after 1s
CI / E2E tests (1/8) (push) Failing after 1s
CI / E2E tests (2/8) (push) Failing after 2s
CI / E2E tests (3/8) (push) Failing after 2s
CI / Browser Tests (push) Failing after 1s
CI / E2E tests (5/8) (push) Failing after 1s
CI / E2E Cloudflare (2/8) (push) Failing after 2s
CI / Typecheck (push) Failing after 1s
CI / Changeset Validation (push) Failing after 2s
CI / E2E Cloudflare (5/8) (push) Failing after 1s
CI / E2E Cloudflare (6/8) (push) Failing after 1s
CI / E2E Cloudflare (7/8) (push) Failing after 1s
CodeQL / Analyze (javascript-typescript) (push) Failing after 1s
Format / Format (push) Failing after 0s
CodeQL / Analyze (actions) (push) Failing after 4s
CI / E2E tests (4/8) (push) Failing after 1s
CI / E2E tests (6/8) (push) Failing after 1s
CI / E2E tests (7/8) (push) Failing after 2s
CI / E2E tests (8/8) (push) Failing after 1s
CI / E2E Cloudflare (1/8) (push) Failing after 1s
CI / E2E Cloudflare (3/8) (push) Failing after 2s
Preview Releases / Publish Preview (push) Failing after 0s
zizmor / Run zizmor (push) Failing after 1s
Release / Release (push) Failing after 2s
CI / Smoke Tests (push) Failing after 5m36s
CI / Tests (push) Failing after 6m36s
Release / Sync Templates (push) Has been skipped
CI / E2E Tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:23:53 +08:00

225 lines
7.0 KiB
TypeScript

import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import {
ArtifactUploadError,
resolveReleaseArtifacts,
type ArtifactUploader,
} from "../src/publish/upload-artifacts.js";
const PNG_1x1 = Uint8Array.from(
Buffer.from(
"89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c4890000000a49444154789c6360000002000100ffff03000006000557bf0a8a0000000049454e44ae426082",
"hex",
),
);
interface Uploaded {
url: string;
contentType: string;
bytes: number;
}
function recordingUploader(): { uploader: ArtifactUploader; uploads: Uploaded[] } {
const uploads: Uploaded[] = [];
const uploader: ArtifactUploader = async ({ url, contentType, bytes }) => {
uploads.push({ url, contentType, bytes: bytes.length });
};
return { uploader, uploads };
}
describe("resolveReleaseArtifacts", () => {
let dir: string;
beforeEach(async () => {
dir = await mkdtemp(join(tmpdir(), "emdash-artifacts-"));
await writeFile(join(dir, "icon.png"), PNG_1x1);
await writeFile(join(dir, "banner.png"), PNG_1x1);
await writeFile(join(dir, "s1.png"), PNG_1x1);
await writeFile(join(dir, "s2.png"), PNG_1x1);
});
afterEach(async () => {
await rm(dir, { recursive: true, force: true });
});
it("returns undefined when no artifacts are declared", async () => {
const result = await resolveReleaseArtifacts({
artifacts: undefined,
manifestDir: dir,
baseUrl: "https://cdn.example.com",
slug: "gallery",
version: "1.0.0",
upload: recordingUploader().uploader,
});
expect(result).toBeUndefined();
});
it("uploads and records icon, banner, and a screenshot gallery", async () => {
const { uploader, uploads } = recordingUploader();
const result = await resolveReleaseArtifacts({
artifacts: {
icon: { file: "./icon.png" },
banner: { file: "./banner.png" },
screenshots: [{ file: "./s1.png" }, { file: "./s2.png", lang: "de" }],
},
manifestDir: dir,
baseUrl: "https://cdn.example.com/",
slug: "gallery",
version: "1.0.0",
upload: uploader,
});
expect(result?.icon).toMatchObject({
url: "https://cdn.example.com/gallery/1.0.0/icon-icon.png",
contentType: "image/png",
width: 1,
height: 1,
});
expect(result?.banner?.url).toBe("https://cdn.example.com/gallery/1.0.0/banner-banner.png");
expect(result?.screenshots).toHaveLength(2);
expect(result?.screenshots?.[0]?.url).toBe(
"https://cdn.example.com/gallery/1.0.0/screenshot-1-s1.png",
);
expect(result?.screenshots?.[1]?.lang).toBe("de");
// One PUT per artifact, with the measured content type.
expect(uploads).toHaveLength(4);
expect(uploads.every((u) => u.contentType === "image/png")).toBe(true);
});
it("preserves screenshot order", async () => {
const { uploader } = recordingUploader();
const result = await resolveReleaseArtifacts({
artifacts: { screenshots: [{ file: "./s2.png" }, { file: "./s1.png" }] },
manifestDir: dir,
baseUrl: "https://cdn.example.com",
slug: "gallery",
version: "1.0.0",
upload: uploader,
});
expect(result?.screenshots?.map((s) => s.url)).toEqual([
"https://cdn.example.com/gallery/1.0.0/screenshot-1-s2.png",
"https://cdn.example.com/gallery/1.0.0/screenshot-2-s1.png",
]);
});
it("gives same-basename screenshots in different dirs distinct upload URLs", async () => {
await mkdir(join(dir, "light"));
await mkdir(join(dir, "dark"));
await writeFile(join(dir, "light", "shot.png"), PNG_1x1);
await writeFile(join(dir, "dark", "shot.png"), PNG_1x1);
const { uploader, uploads } = recordingUploader();
const result = await resolveReleaseArtifacts({
artifacts: { screenshots: [{ file: "./light/shot.png" }, { file: "./dark/shot.png" }] },
manifestDir: dir,
baseUrl: "https://cdn.example.com",
slug: "gallery",
version: "1.0.0",
upload: uploader,
});
const urls = result?.screenshots?.map((s) => s.url) ?? [];
expect(urls).toEqual([
"https://cdn.example.com/gallery/1.0.0/screenshot-1-shot.png",
"https://cdn.example.com/gallery/1.0.0/screenshot-2-shot.png",
]);
expect(new Set(urls).size).toBe(2);
expect(new Set(uploads.map((u) => u.url)).size).toBe(2);
});
it("gives an icon and a same-basename screenshot distinct upload URLs", async () => {
await writeFile(join(dir, "image.png"), PNG_1x1);
const result = await resolveReleaseArtifacts({
artifacts: { icon: { file: "./image.png" }, screenshots: [{ file: "./image.png" }] },
manifestDir: dir,
baseUrl: "https://cdn.example.com",
slug: "gallery",
version: "1.0.0",
upload: recordingUploader().uploader,
});
expect(result?.icon?.url).toBe("https://cdn.example.com/gallery/1.0.0/icon-image.png");
expect(result?.screenshots?.[0]?.url).toBe(
"https://cdn.example.com/gallery/1.0.0/screenshot-1-image.png",
);
expect(result?.icon?.url).not.toBe(result?.screenshots?.[0]?.url);
});
it("accepts a filename that begins with two dots", async () => {
await writeFile(join(dir, "..config.png"), PNG_1x1);
const result = await resolveReleaseArtifacts({
artifacts: { icon: { file: "./..config.png" } },
manifestDir: dir,
baseUrl: "https://cdn.example.com",
slug: "gallery",
version: "1.0.0",
upload: recordingUploader().uploader,
});
expect(result?.icon?.url).toBe("https://cdn.example.com/gallery/1.0.0/icon-..config.png");
});
it("rejects a file path that escapes the manifest directory", async () => {
await expect(
resolveReleaseArtifacts({
artifacts: { icon: { file: "../secret.png" } },
manifestDir: dir,
baseUrl: "https://cdn.example.com",
slug: "gallery",
version: "1.0.0",
upload: recordingUploader().uploader,
}),
).rejects.toMatchObject({ name: "ArtifactUploadError", code: "ARTIFACT_PATH_ESCAPE" });
});
it("surfaces an unreadable file as a typed error", async () => {
await expect(
resolveReleaseArtifacts({
artifacts: { icon: { file: "./missing.png" } },
manifestDir: dir,
baseUrl: "https://cdn.example.com",
slug: "gallery",
version: "1.0.0",
upload: recordingUploader().uploader,
}),
).rejects.toMatchObject({ name: "ArtifactUploadError", code: "ARTIFACT_FILE_UNREADABLE" });
});
it("surfaces an upload failure as a typed error", async () => {
const failing: ArtifactUploader = async () => {
throw new Error("503 from CDN");
};
await expect(
resolveReleaseArtifacts({
artifacts: { icon: { file: "./icon.png" } },
manifestDir: dir,
baseUrl: "https://cdn.example.com",
slug: "gallery",
version: "1.0.0",
upload: failing,
}),
).rejects.toBeInstanceOf(ArtifactUploadError);
});
it("rejects a non-image file", async () => {
await writeFile(join(dir, "notimage.png"), new TextEncoder().encode("nope"));
await expect(
resolveReleaseArtifacts({
artifacts: { icon: { file: "./notimage.png" } },
manifestDir: dir,
baseUrl: "https://cdn.example.com",
slug: "gallery",
version: "1.0.0",
upload: recordingUploader().uploader,
}),
).rejects.toBeInstanceOf(ArtifactUploadError);
});
});