fix(ci): make release zips reproducible and bump version to 0.2.1

adm-zip stamps entries with file mtimes, i.e. the git checkout time on
CI, so two builds of the same commit differed byte-wise and tripped the
immutable no-overwrite guard on workflow re-runs. Pin every entry to the
commit date (SOURCE_DATE_EPOCH convention) instead.

0.2.0 was never published (no manifest entry, no GitHub release), but a
stale chrome-store zip from the first failed run occupies its immutable
slot; move the release to 0.2.1 rather than deleting the object.
This commit is contained in:
go0p
2026-08-18 14:52:48 +08:00
parent c1476a3337
commit 4507c2689d
2 changed files with 12 additions and 1 deletions
+11
View File
@@ -67,12 +67,21 @@ const pkg = JSON.parse(await readFile(resolve(root, 'package.json'), 'utf8'));
const { version } = pkg;
let commit = null;
let commitTime = null;
try {
const { stdout } = await execFileAsync('git', ['rev-parse', 'HEAD'], { cwd: root });
commit = stdout.trim();
const { stdout: iso } = await execFileAsync('git', ['show', '-s', '--format=%cI', 'HEAD'], { cwd: root });
commitTime = new Date(iso.trim());
} catch {
// Not fatal: local runs outside a git worktree still package fine.
}
// adm-zip stamps every entry with the file mtime, which is the checkout time
// on CI — two builds of the same commit would differ byte-wise and trip the
// immutable no-overwrite guard on re-runs. Pin all entries to the commit
// date (SOURCE_DATE_EPOCH convention) so artifacts are reproducible.
const sourceDateEpoch = Number(process.env.SOURCE_DATE_EPOCH)
|| (commitTime && !Number.isNaN(commitTime.getTime()) ? commitTime.getTime() : 0);
const versionDir = resolve(distDir, version);
await mkdir(versionDir, { recursive: true });
@@ -94,6 +103,8 @@ for (const target of VARIANTS) {
// zip root, which is what browsers expect from a sideloaded extension.
const zip = new AdmZip();
zip.addLocalFolder(outputDir);
const pinned = new Date(Math.floor(sourceDateEpoch / 2000) * 2000); // DOS time has 2s granularity
for (const entry of zip.getEntries()) entry.header.time = pinned;
await zip.writeZipPromise(zipPath);
const sha256 = await sha256File(zipPath);
const size = (await stat(zipPath)).size;