07cd947496bb
consolidate source archive behind one packager with crypto-core, SOURCE-MANIFEST, and reproducibility (PB-26-212, v5.0.68)
- time
- 2026-09-03 19:03 UTC · 1 month ago
- machine
- koh2
- agent
- muse-spark-1.3-contributor-free
- session
- PB-26-212 source package consolidation
- parent
- 700030ce6645
↓ peach-browser-07cd9474.tar.gzatlas at this save
files changed11 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+2 −2scripts/package-source.test.ts+253 −0scripts/package-source.ts+291 −0scripts/release-firefox.ts+2 −2scripts/release-gate.test.ts+1 −1scripts/release-lib.test.ts+1 −3scripts/release-lib.ts+1 −42scripts/release.test.ts+10 −44scripts/release.ts+2 −2manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach Password Manager",4- "version": "5.0.67",4+ "version": "5.0.68",55 "description": "Zero-knowledge password manager",66 "browser_specific_settings": {77 "gecko": {manifest.json+1 −1
@@ -2,7 +2,7 @@22 "manifest_version": 3,33 "minimum_chrome_version": "116",44 "name": "Peach Password Manager",5- "version": "5.0.67",5+ "version": "5.0.68",66 "description": "Zero-knowledge password manager",77 "action": {88 "default_popup": "src/popup/index.html",package.json+2 −2
@@ -1,6 +1,6 @@11 {22 "name": "peach",3- "version": "5.0.67",3+ "version": "5.0.68",44 "description": "Zero-knowledge password manager",55 "license": "GPL-3.0-only",66 "type": "module",@@ -22,7 +22,7 @@2222 "test:e2e": "npm run build:chrome && playwright test tests/vault-lifecycle.spec.ts tests/entry-crud.spec.ts tests/search.spec.ts tests/settings.spec.ts tests/lock-screen.spec.ts tests/vault-security.spec.ts tests/sync-alerts.spec.ts tests/pb26016-sender-context.spec.ts",2323 "test:passkeys": "npm run build:e2e && playwright test tests/passkeys.spec.ts",2424 "ci": "bun run scripts/ci.ts",25- "package:source": "zip -r peach-source.zip src scripts public manifest.json manifest.firefox.json package.json bun.lock tsconfig.json vite.config.ts --exclude 'node_modules/*' --exclude 'dist-*/*' --exclude '*.zip'",25+ "package:source": "bun run scripts/package-source.ts",2626 "release": "bun run scripts/release.ts",2727 "release:chrome": "bun run scripts/release.ts --chrome-only",2828 "release:firefox": "bun run scripts/release-firefox.ts",scripts/package-source.test.ts+253 −0
@@ -0,0 +1,253 @@1+import { execFileSync } from "node:child_process";2+import { createHash } from "node:crypto";3+import { existsSync, readFileSync } from "node:fs";4+import { fileURLToPath } from "node:url";5+import { dirname, join, resolve } from "node:path";6+import { describe, expect, it } from "vitest";7+8+import {9+ buildSourceManifest,10+ collectSourceFiles,11+ packageSourceArchive,12+ type SourceManifest,13+} from "./package-source";14+15+const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");16+17+const REQUIRED_FILES = [18+ "src/shared/noise-core-wasm/peach_noise_core_bg.wasm",19+ "crypto-core/Cargo.toml",20+ "crypto-core/Cargo.lock",21+ "crypto-core/src/lib.rs",22+ "crypto-core/src/jni_bridge.rs",23+ "crypto-core/.cargo/config.toml",24+ "scripts/build-noise-core.mjs",25+ "scripts/check-noise-core.mjs",26+ "manifest.json",27+ "manifest.firefox.json",28+ "package.json",29+ "bun.lock",30+ "tsconfig.json",31+ "vite.config.ts",32+];33+34+const EXCLUDE_PATTERNS = [35+ /node_modules\//,36+ /dist-[^/]+\//,37+ /\.zip$/,38+ /crypto-core\/target\//,39+ /crypto-core\/generated\//,40+ /\.DS_Store$/,41+ /__MACOSX\//,42+];43+44+function listArchive(archivePath: string): string[] {45+ return execFileSync("unzip", ["-Z1", archivePath], { encoding: "utf8" })46+ .split("\n")47+ .filter(Boolean);48+}49+50+describe("PB-26-212: source package — required files", () => {51+ it("collects all security-critical files including crypto-core", () => {52+ const files = collectSourceFiles();53+ for (const required of REQUIRED_FILES) {54+ expect(files).toContain(required);55+ }56+ });57+58+ it("includes crypto-core/Cargo.toml, Cargo.lock, and src/lib.rs (the core regression)", () => {59+ const files = collectSourceFiles();60+ expect(files).toContain("crypto-core/Cargo.toml");61+ expect(files).toContain("crypto-core/Cargo.lock");62+ expect(files).toContain("crypto-core/src/lib.rs");63+ });64+65+ it("excludes forbidden directories and files", () => {66+ const files = collectSourceFiles();67+ for (const file of files) {68+ for (const pattern of EXCLUDE_PATTERNS) {69+ expect(pattern.test(file)).toBe(false);70+ }71+ }72+ });73+74+ it("collects files sorted and without duplicates", () => {75+ const files = collectSourceFiles();76+ const sorted = [...files].sort();77+ expect(files).toEqual(sorted);78+ expect(new Set(files).size).toBe(files.length);79+ });80+});81+82+describe("PB-26-212: SOURCE-MANIFEST.json", () => {83+ it("has correct structure with version, noise WASM SHA-256, toolchain, and file hashes", () => {84+ const files = collectSourceFiles();85+ const manifest = buildSourceManifest(files, "0.0.0-test");86+87+ expect(manifest.peachVersion).toBe("0.0.0-test");88+ expect(manifest.noiseWasmSha256).toMatch(/^[0-9a-f]{64}$/);89+ expect(manifest.toolchain).toBeDefined();90+ expect(manifest.toolchain.bun).toBeDefined();91+ expect(manifest.toolchain.node).toBeDefined();92+ expect(manifest.toolchain.rust).toBeDefined();93+94+ expect(manifest.files).toHaveLength(files.length);95+ for (const entry of manifest.files) {96+ expect(entry.path).toBeDefined();97+ expect(entry.sha256).toMatch(/^[0-9a-f]{64}$/);98+ }99+ });100+101+ it("noiseWasmSha256 matches the actual shipped WASM", () => {102+ const wasmPath = join(103+ ROOT,104+ "src/shared/noise-core-wasm/peach_noise_core_bg.wasm",105+ );106+ const expected = createHash("sha256")107+ .update(readFileSync(wasmPath))108+ .digest("hex");109+ const files = collectSourceFiles();110+ const manifest = buildSourceManifest(files, "0.0.0-test");111+ expect(manifest.noiseWasmSha256).toBe(expected);112+ });113+114+ it("file hashes match actual file contents", () => {115+ const files = collectSourceFiles();116+ const manifest = buildSourceManifest(files, "0.0.0-test");117+ for (const entry of manifest.files) {118+ const actual = createHash("sha256")119+ .update(readFileSync(join(ROOT, entry.path)))120+ .digest("hex");121+ expect(entry.sha256).toBe(actual);122+ }123+ });124+});125+126+describe("PB-26-212: source archive contents", () => {127+ it("creates an archive containing every required file exactly once", () => {128+ const archivePath = packageSourceArchive("0.0.0-test");129+ expect(existsSync(archivePath)).toBe(true);130+131+ const contents = listArchive(archivePath);132+ for (const required of REQUIRED_FILES) {133+ const matches = contents.filter((c) => c === required);134+ expect(matches).toHaveLength(1);135+ }136+137+ expect(contents).toContain("SOURCE-MANIFEST.json");138+ });139+140+ it("excludes forbidden paths from the archive", () => {141+ const archivePath = packageSourceArchive("0.0.0-test");142+ const contents = listArchive(archivePath);143+ for (const file of contents) {144+ for (const pattern of EXCLUDE_PATTERNS) {145+ expect(pattern.test(file)).toBe(false);146+ }147+ }148+ });149+150+ it("archive SOURCE-MANIFEST.json contains the noise WASM SHA-256 and version", () => {151+ const archivePath = packageSourceArchive("0.0.0-test");152+ const manifestJson = execFileSync(153+ "unzip",154+ ["-p", archivePath, "SOURCE-MANIFEST.json"],155+ { encoding: "utf8" },156+ );157+ const manifest: SourceManifest = JSON.parse(manifestJson);158+159+ expect(manifest.peachVersion).toBe("0.0.0-test");160+ expect(manifest.noiseWasmSha256).toMatch(/^[0-9a-f]{64}$/);161+ expect(Array.isArray(manifest.files)).toBe(true);162+ expect(manifest.files.length).toBeGreaterThan(0);163+ });164+165+ it("archive contains the same crypto-core source as the repo (rebuild provenance)", () => {166+ const archivePath = packageSourceArchive("0.0.0-test");167+168+ const archiveLibRs = execFileSync(169+ "unzip",170+ ["-p", archivePath, "crypto-core/src/lib.rs"],171+ { encoding: "utf8" },172+ );173+ const repoLibRs = readFileSync(174+ join(ROOT, "crypto-core/src/lib.rs"),175+ "utf8",176+ );177+ expect(archiveLibRs).toBe(repoLibRs);178+179+ const archiveBuildScript = execFileSync(180+ "unzip",181+ ["-p", archivePath, "scripts/build-noise-core.mjs"],182+ { encoding: "utf8" },183+ );184+ const repoBuildScript = readFileSync(185+ join(ROOT, "scripts/build-noise-core.mjs"),186+ "utf8",187+ );188+ expect(archiveBuildScript).toBe(repoBuildScript);189+190+ const manifestJson = execFileSync(191+ "unzip",192+ ["-p", archivePath, "SOURCE-MANIFEST.json"],193+ { encoding: "utf8" },194+ );195+ const manifest: SourceManifest = JSON.parse(manifestJson);196+ const wasmPath = join(197+ ROOT,198+ "src/shared/noise-core-wasm/peach_noise_core_bg.wasm",199+ );200+ const expectedWasmHash = createHash("sha256")201+ .update(readFileSync(wasmPath))202+ .digest("hex");203+ expect(manifest.noiseWasmSha256).toBe(expectedWasmHash);204+ });205+});206+207+describe("PB-26-212: reproducibility", () => {208+ it("produces byte-identical archives from the same baseline", () => {209+ const archive1 = packageSourceArchive("0.0.0-repro");210+ const hash1 = createHash("sha256")211+ .update(readFileSync(archive1))212+ .digest("hex");213+214+ const archive2 = packageSourceArchive("0.0.0-repro");215+ const hash2 = createHash("sha256")216+ .update(readFileSync(archive2))217+ .digest("hex");218+219+ expect(hash1).toBe(hash2);220+ });221+});222+223+describe("PB-26-212: all entry points use the shared packager", () => {224+ it("package.json package:source calls scripts/package-source.ts", () => {225+ const pkg = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8"));226+ expect(pkg.scripts["package:source"]).toContain("package-source.ts");227+ });228+229+ it("package.json package:source does not contain a duplicate file list", () => {230+ const pkg = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8"));231+ const script = pkg.scripts["package:source"];232+ expect(script).not.toContain("manifest.json manifest.firefox.json");233+ expect(script).not.toMatch(/^zip\s/);234+ });235+236+ it("release.ts imports and calls packageSourceArchive", () => {237+ const source = readFileSync(join(ROOT, "scripts/release.ts"), "utf8");238+ expect(source).toContain("packageSourceArchive");239+ });240+241+ it("release-firefox.ts imports and calls packageSourceArchive", () => {242+ const source = readFileSync(243+ join(ROOT, "scripts/release-firefox.ts"),244+ "utf8",245+ );246+ expect(source).toContain("packageSourceArchive");247+ });248+249+ it("release-lib.ts does not contain a duplicate zip file list", () => {250+ const source = readFileSync(join(ROOT, "scripts/release-lib.ts"), "utf8");251+ expect(source).not.toContain("packageFirefoxSourceZip");252+ });253+});scripts/package-source.ts+291 −0
@@ -0,0 +1,291 @@1+#!/usr/bin/env bun2+// scripts/package-source.ts3+// PB-26-212: ONE source-archive definition for standalone, Chrome+Firefox,4+// and Firefox-only release paths. Includes crypto-core, generates5+// SOURCE-MANIFEST.json, and produces deterministic (reproducible) ZIPs.6+7+import { createHash } from "node:crypto";8+import { crc32, deflateRawSync } from "node:zlib";9+import {10+ existsSync,11+ mkdirSync,12+ readFileSync,13+ readdirSync,14+ writeFileSync,15+} from "node:fs";16+import { basename, join, relative, resolve } from "node:path";17+import { fileURLToPath } from "node:url";18+19+const SCRIPT_DIR = resolve(dirname_safe());20+const REPO_ROOT = resolve(SCRIPT_DIR, "..");21+22+function dirname_safe(): string {23+ try {24+ return fileURLToPath(new URL(".", import.meta.url));25+ } catch {26+ return __dirname;27+ }28+}29+30+// ─── Exclusions ─────────────────────────────────────────────────────────────31+32+const EXCLUDE_PATTERNS = [33+ /node_modules\//,34+ /dist-[^/]+\//,35+ /\.zip$/,36+ /crypto-core\/target\//,37+ /crypto-core\/generated\//,38+ /\.DS_Store$/,39+ /__MACOSX\//,40+];41+42+function shouldExclude(relPath: string): boolean {43+ return EXCLUDE_PATTERNS.some((p) => p.test(relPath));44+}45+46+// ─── File collection ────────────────────────────────────────────────────────47+48+const INCLUDE_DIRS = ["src", "scripts", "public", "crypto-core"];49+const INCLUDE_FILES = [50+ "manifest.json",51+ "manifest.firefox.json",52+ "package.json",53+ "bun.lock",54+ "tsconfig.json",55+ "vite.config.ts",56+];57+58+function walkDir(dir: string, files: string[]): void {59+ let entries: ReturnType<typeof readdirSync>;60+ try {61+ entries = readdirSync(dir, { withFileTypes: true });62+ } catch {63+ return;64+ }65+ for (const entry of entries) {66+ const fullPath = join(dir, entry.name);67+ const relPath = relative(REPO_ROOT, fullPath);68+ if (entry.isDirectory()) {69+ if (shouldExclude(`${relPath}/`)) continue;70+ walkDir(fullPath, files);71+ } else if (entry.isFile()) {72+ if (shouldExclude(relPath)) continue;73+ files.push(relPath);74+ }75+ }76+}77+78+export function collectSourceFiles(): string[] {79+ const files: string[] = [];80+ for (const dir of INCLUDE_DIRS) {81+ const absDir = join(REPO_ROOT, dir);82+ if (existsSync(absDir)) {83+ walkDir(absDir, files);84+ }85+ }86+ for (const file of INCLUDE_FILES) {87+ if (existsSync(join(REPO_ROOT, file))) {88+ files.push(file);89+ }90+ }91+ return [...new Set(files)].sort();92+}93+94+// ─── SOURCE-MANIFEST.json ───────────────────────────────────────────────────95+96+export interface SourceManifest {97+ peachVersion: string;98+ noiseWasmSha256: string;99+ toolchain: {100+ bun: string;101+ node: string;102+ rust: string;103+ };104+ files: Array<{ path: string; sha256: string }>;105+}106+107+function sha256(data: Buffer | string): string {108+ return createHash("sha256").update(data).digest("hex");109+}110+111+function getNoiseWasmSha256(): string {112+ const wasmPath = join(113+ REPO_ROOT,114+ "src/shared/noise-core-wasm/peach_noise_core_bg.wasm",115+ );116+ return sha256(readFileSync(wasmPath));117+}118+119+function readToolchain(): { bun: string; node: string; rust: string } {120+ const pkg = JSON.parse(121+ readFileSync(join(REPO_ROOT, "package.json"), "utf8"),122+ );123+ const bunVersion = (pkg.packageManager ?? "").replace("bun@", "");124+125+ const cargoToml = readFileSync(126+ join(REPO_ROOT, "crypto-core/Cargo.toml"),127+ "utf8",128+ );129+ const rustMatch = cargoToml.match(/rust-version\s*=\s*"([^"]+)"/);130+ const rustVersion = rustMatch ? rustMatch[1] : "unknown";131+132+ return {133+ bun: bunVersion,134+ node: pkg.engines?.node ?? "unknown",135+ rust: rustVersion,136+ };137+}138+139+export function buildSourceManifest(140+ files: string[],141+ version: string,142+): SourceManifest {143+ const entries = files.map((path) => ({144+ path,145+ sha256: sha256(readFileSync(join(REPO_ROOT, path))),146+ }));147+ return {148+ peachVersion: version,149+ noiseWasmSha256: getNoiseWasmSha256(),150+ toolchain: readToolchain(),151+ files: entries,152+ };153+}154+155+// ─── Deterministic ZIP writer ───────────────────────────────────────────────156+// Fixed DOS timestamp (1980-01-01 00:00:00) for reproducibility.157+158+const FIXED_DOS_TIME = 0x0000;159+const FIXED_DOS_DATE = 0x0021;160+161+interface ZipEntry {162+ path: string;163+ data: Buffer;164+}165+166+export function createDeterministicZip(entries: ZipEntry[]): Buffer {167+ const sorted = [...entries].sort((a, b) =>168+ a.path.localeCompare(b.path),169+ );170+ const localParts: Buffer[] = [];171+ const centralParts: Buffer[] = [];172+ let offset = 0;173+174+ for (const entry of sorted) {175+ const filename = Buffer.from(entry.path, "utf8");176+ const compressed = deflateRawSync(entry.data, { level: 9 });177+ const crc = crc32(entry.data) >>> 0;178+ const compSize = compressed.length;179+ const uncompSize = entry.data.length;180+181+ // Local file header182+ const lh = Buffer.alloc(30 + filename.length);183+ lh.writeUInt32LE(0x04034b50, 0);184+ lh.writeUInt16LE(20, 4);185+ lh.writeUInt16LE(0, 6);186+ lh.writeUInt16LE(8, 8);187+ lh.writeUInt16LE(FIXED_DOS_TIME, 10);188+ lh.writeUInt16LE(FIXED_DOS_DATE, 12);189+ lh.writeUInt32LE(crc, 14);190+ lh.writeUInt32LE(compSize, 18);191+ lh.writeUInt32LE(uncompSize, 22);192+ lh.writeUInt16LE(filename.length, 26);193+ lh.writeUInt16LE(0, 28);194+ filename.copy(lh, 30);195+196+ localParts.push(lh, compressed);197+198+ // Central directory record199+ const cd = Buffer.alloc(46 + filename.length);200+ cd.writeUInt32LE(0x02014b50, 0);201+ cd.writeUInt16LE(20, 4);202+ cd.writeUInt16LE(20, 6);203+ cd.writeUInt16LE(0, 8);204+ cd.writeUInt16LE(8, 10);205+ cd.writeUInt16LE(FIXED_DOS_TIME, 12);206+ cd.writeUInt16LE(FIXED_DOS_DATE, 14);207+ cd.writeUInt32LE(crc, 16);208+ cd.writeUInt32LE(compSize, 20);209+ cd.writeUInt32LE(uncompSize, 24);210+ cd.writeUInt16LE(filename.length, 28);211+ cd.writeUInt16LE(0, 30);212+ cd.writeUInt16LE(0, 32);213+ cd.writeUInt16LE(0, 34);214+ cd.writeUInt16LE(0, 36);215+ cd.writeUInt32LE(0, 38);216+ cd.writeUInt32LE(offset, 42);217+ filename.copy(cd, 46);218+219+ centralParts.push(cd);220+ offset += lh.length + compressed.length;221+ }222+223+ const centralDir = Buffer.concat(centralParts);224+ const centralOffset = offset;225+ const centralSize = centralDir.length;226+227+ // End of central directory record228+ const eocd = Buffer.alloc(22);229+ eocd.writeUInt32LE(0x06054b50, 0);230+ eocd.writeUInt16LE(0, 4);231+ eocd.writeUInt16LE(0, 6);232+ eocd.writeUInt16LE(sorted.length, 8);233+ eocd.writeUInt16LE(sorted.length, 10);234+ eocd.writeUInt32LE(centralSize, 12);235+ eocd.writeUInt32LE(centralOffset, 16);236+ eocd.writeUInt16LE(0, 20);237+238+ return Buffer.concat([...localParts, centralDir, eocd]);239+}240+241+// ─── Package source archive ─────────────────────────────────────────────────242+243+function readVersion(manifestPath: string): string {244+ const manifest = JSON.parse(245+ readFileSync(join(REPO_ROOT, manifestPath), "utf8"),246+ );247+ if (!manifest.version) {248+ throw new Error(`Could not read version from ${manifestPath}`);249+ }250+ return manifest.version;251+}252+253+function ensureArtifactsDir(): string {254+ const dir = join(REPO_ROOT, "artifacts");255+ if (!existsSync(dir)) mkdirSync(dir);256+ return dir;257+}258+259+export function packageSourceArchive(version: string): string {260+ const artifactsDir = ensureArtifactsDir();261+ const archivePath = join(artifactsDir, `peach-source-${version}.zip`);262+263+ const files = collectSourceFiles();264+ const manifest = buildSourceManifest(files, version);265+ const manifestJson = `${JSON.stringify(manifest, null, 2)}\n`;266+267+ const entries: ZipEntry[] = files.map((path) => ({268+ path,269+ data: readFileSync(join(REPO_ROOT, path)),270+ }));271+ entries.push({272+ path: "SOURCE-MANIFEST.json",273+ data: Buffer.from(manifestJson, "utf8"),274+ });275+276+ const zipBuffer = createDeterministicZip(entries);277+ writeFileSync(archivePath, zipBuffer);278+279+ console.log(280+ `Packaged source archive → ${basename(archivePath)} (${files.length} files + SOURCE-MANIFEST.json)`,281+ );282+ return archivePath;283+}284+285+// ─── Standalone execution ───────────────────────────────────────────────────286+287+if (import.meta.main) {288+ const version = readVersion("manifest.json");289+ const archivePath = packageSourceArchive(version);290+ console.log(`\nSource archive: ${archivePath}`);291+}scripts/release-firefox.ts+2 −2
@@ -14,7 +14,7 @@1414 readVersion,1515 runCommand,1616 safeEnv,17- packageFirefoxSourceZip,17+ packageSourceArchive,1818 signFirefox,1919 } from "./release-lib";2020 @@ -43,7 +43,7 @@4343 // AMO requires source for any extension with minified/transpiled code.4444 // This zips everything a reviewer needs to build Peach from source.4545 46-const sourceZip = packageFirefoxSourceZip(version);46+const sourceZip = packageSourceArchive(version);4747 4848 // ─── Sign and submit ───────────────────────────────────────────────────────────4949 // PB-26-211: AMO credentials are read and validated here, immediately beforescripts/release-gate.test.ts+1 −1
@@ -97,7 +97,7 @@9797 // Packaging/upload calls appear after CI. PB-26-211: these are now9898 // function calls to uploadChrome / signFirefox / packageFirefoxSourceZip.9999 // Match calls (with paren), not import names.100- const packageIndex = source.search(/uploadChrome\(|signFirefox\(|packageFirefoxSourceZip\(/);100+ const packageIndex = source.search(/uploadChrome\(|signFirefox\(|packageSourceArchive\(/);101101 if (packageIndex > -1) {102102 expect(ciIndex).toBeLessThan(packageIndex);103103 }scripts/release-lib.test.ts+1 −3
@@ -13,7 +13,6 @@1313 resolveBin,1414 preflight,1515 packageChromeZip,16- packageFirefoxSourceZip,1716 uploadChrome,1817 signFirefox,1918 CWS_SECRET_NAMES,@@ -87,11 +86,10 @@8786 for (const name of ALL_SECRET_NAMES) setEnv(name, "secret-value");8887 8988 packageChromeZip("9.9.9", fake);90- packageFirefoxSourceZip("9.9.9", fake);9189 uploadChrome("9.9.9", fake);9290 signFirefox("/tmp/fake-source.zip", fake);9391 94- expect(captured.length).toBeGreaterThanOrEqual(4);92+ expect(captured.length).toBeGreaterThanOrEqual(3);9593 for (const spec of captured) {9694 expect(spec.executable).not.toBe("npx");9795 expect(spec.args).not.toContain("npx");scripts/release-lib.ts+1 −42
@@ -247,48 +247,7 @@247247 );248248 }249249 250-export function packageFirefoxSourceZip(251- version: string,252- runner: Runner = runCommand,253-): string {254- const artifactsDir = ensureArtifactsDir();255- const sourceZip = join(artifactsDir, `peach-source-${version}.zip`);256- runner(257- "zip",258- [259- "-r",260- sourceZip,261- "src",262- "scripts",263- "public",264- "crypto-core",265- "manifest.json",266- "manifest.firefox.json",267- "package.json",268- "bun.lock",269- "tsconfig.json",270- "vite.config.ts",271- "--exclude",272- "node_modules/*",273- "--exclude",274- "dist-*/*",275- "--exclude",276- "crypto-core/target/*",277- "--exclude",278- "crypto-core/generated/*",279- "--exclude",280- "*.zip",281- "--exclude",282- ".DS_Store",283- "--exclude",284- "__MACOSX/*",285- ],286- {287- label: "Packaging source archive for AMO",288- },289- );290- return sourceZip;291-}250+export { packageSourceArchive } from "./package-source";292251 293252 export function signFirefox(294253 sourceZip: string,scripts/release.test.ts+10 −44
@@ -1,13 +1,12 @@11 import { execFileSync } from "node:child_process";2-import { existsSync, mkdtempSync, readFileSync } from "node:fs";2+import { existsSync, readFileSync } from "node:fs";33 import { fileURLToPath } from "node:url";44 import { dirname, join, resolve } from "node:path";5-import { tmpdir } from "node:os";65 import { describe, expect, it } from "vitest";76 7+import { packageSourceArchive } from "./package-source";8+89 const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");9-// PB-26-211: packaging/signing logic moved to release-lib.ts. Both release10-// entry points import from it, so verifying release-lib.ts covers both.1110 const RELEASE_LIB = "scripts/release-lib.ts";1211 1312 function readReleaseLib(): string {@@ -17,49 +16,19 @@1716 describe("Firefox release source submission", () => {1817 it("passes the generated source archive to web-ext sign with --upload-source-code", () => {1918 const source = readReleaseLib();20- // web-ext sign is called with --upload-source-code and the source zip path.2119 expect(source).toContain('"--upload-source-code"');2220 expect(source).toContain("sourceZip");2321 expect(source.match(/--upload-source-code/g)).toHaveLength(1);2422 });2523 26- it("builds an archive containing the reviewed crypto source and Noise build script", () => {27- const archive = join(mkdtempSync(join(tmpdir(), "peach-source-test-")), "source.zip");28- execFileSync(29- "zip",30- [31- "-q",32- "-r",33- archive,34- "src",35- "scripts",36- "public",37- "crypto-core",38- "manifest.json",39- "manifest.firefox.json",40- "package.json",41- "bun.lock",42- "tsconfig.json",43- "vite.config.ts",44- "--exclude",45- "node_modules/*",46- "--exclude",47- "dist-*/*",48- "--exclude",49- "crypto-core/target/*",50- "--exclude",51- "crypto-core/generated/*",52- "--exclude",53- "*.zip",54- "--exclude",55- ".DS_Store",56- "--exclude",57- "__MACOSX/*",58- ],59- { cwd: ROOT },60- );24+ it("shared packager builds an archive containing the reviewed crypto source and Noise build script", () => {25+ const archivePath = packageSourceArchive("0.0.0-release-test");26+ expect(existsSync(archivePath)).toBe(true);6127 62- const contents = execFileSync("unzip", ["-Z1", archive], { encoding: "utf8" }).split("\n");28+ const contents = execFileSync("unzip", ["-Z1", archivePath], {29+ encoding: "utf8",30+ }).split("\n");31+6332 expect(contents).toEqual(6433 expect.arrayContaining([6534 "crypto-core/Cargo.toml",@@ -69,9 +38,6 @@6938 ]),7039 );7140 72- // PB-26-211: verify release-lib.ts includes crypto-core in the source archive.73- const source = readReleaseLib();74- expect(source).toContain('"crypto-core"');7541 expect(existsSync(join(ROOT, "crypto-core/Cargo.toml"))).toBe(true);7642 expect(existsSync(join(ROOT, "crypto-core/Cargo.lock"))).toBe(true);7743 expect(existsSync(join(ROOT, "crypto-core/src/lib.rs"))).toBe(true);scripts/release.ts+2 −2
@@ -19,7 +19,7 @@1919 runCommand,2020 safeEnv,2121 uploadChrome,22- packageFirefoxSourceZip,22+ packageSourceArchive,2323 signFirefox,2424 } from "./release-lib";2525 @@ -91,7 +91,7 @@9191 9292 if (!CHROME_ONLY) {9393 console.log(`\n${ACCENT}${BOLD}Firefox Add-ons (AMO)${RESET}`);94- const sourceZip = packageFirefoxSourceZip(version);94+ const sourceZip = packageSourceArchive(version);9595 signFirefox(sourceZip);9696 success(`Firefox v${version} submitted with source archive retained in artifacts/`);9797 }