diff --git a/apps/web/src/myra-deploy.test.ts b/apps/web/src/myra-deploy.test.ts index 60e9f7096a..8a3aca6d4c 100644 --- a/apps/web/src/myra-deploy.test.ts +++ b/apps/web/src/myra-deploy.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; -import { buildMyraDeployInput, MyraDeployError } from "./myra-deploy"; +import { buildMyraDeployInput, MyraDeployError, packTarball } from "./myra-deploy"; import { MYRA_SOURCE_CONFIG } from "./myra-source"; describe("buildMyraDeployInput", () => { @@ -44,3 +44,20 @@ describe("buildMyraDeployInput", () => { ).toThrow(MyraDeployError); }); }); + +describe("packTarball", () => { + test("writes USTAR headers whose checksum verifies and whose typeflag survives", async () => { + const tarball = await packTarball({ "package.json": "{}" }); + const tar = new Uint8Array( + await new Response( + new Blob([tarball as Uint8Array]).stream().pipeThrough(new DecompressionStream("gzip")), + ).arrayBuffer(), + ); + const header = tar.slice(0, 512); + let expected = 0; + for (let i = 0; i < 512; i++) expected += i >= 148 && i < 156 ? 0x20 : header[i]!; + const stored = parseInt(new TextDecoder().decode(header.slice(148, 154)), 8); + expect(stored).toBe(expected); + expect(String.fromCharCode(header[156]!)).toBe("0"); + }); +}); diff --git a/apps/web/src/myra-deploy.ts b/apps/web/src/myra-deploy.ts index 144bb2e9e3..8c11d31dbb 100644 --- a/apps/web/src/myra-deploy.ts +++ b/apps/web/src/myra-deploy.ts @@ -125,7 +125,9 @@ function tarEntry(path: string, content: Uint8Array): Uint8Array { let checksum = 0; for (const byte of header) checksum += byte; - writeField(148, padOctal(checksum, 8).slice(0, 7) + "\0 "); + // The 8-byte checksum field is 6 octal digits, NUL, space; anything + // longer spills into the typeflag byte and fails every reader's check. + writeField(148, checksum.toString(8).padStart(6, "0") + "\0 "); const paddedLength = Math.ceil(content.length / TAR_BLOCK_SIZE) * TAR_BLOCK_SIZE; const body = new Uint8Array(paddedLength);