Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ The exact order and ownership of static, contract, artifact/runtime, browser, se

`index.html` and `sbom.spdx.json` are tracked, reproducible release evidence. Versioned `dist/` artifacts, `SHA256SUMS`, and the temporary Pages `_site/` tree are generated outputs and must not be committed as release history.

Generate and verify the portable checksum file with `npm run generate:release-checksums` and `npm run validate:release-assets`. After publication, verify provenance with `gh attestation verify dist/hSQLite-Editor-v<version>.html --repo <owner>/<repository>`. Verify the associated SPDX predicate by adding `--predicate-type https://spdx.dev/Document/v2.3`.
Generate and verify the portable checksum file with `npm run generate:release-checksums` and `npm run validate:release-assets`. Published checksum entries use release asset basenames, so users can download the HTML, SBOM, and `SHA256SUMS` into one directory and run `shasum -a 256 -c SHA256SUMS`. After publication, verify provenance with `gh attestation verify dist/hSQLite-Editor-v<version>.html --repo <owner>/<repository>`. Verify the associated SPDX predicate by adding `--predicate-type https://spdx.dev/Document/v2.3`.

Immutable release publication is atomic: create the exact tag and draft, build from that tag, validate, attest, upload the complete asset bundle, then publish. The workflow forbids `--clobber`. If upload is partial or an asset-name conflict occurs, delete and recreate the unpublished draft after diagnosing the cause; never repair a release by overwriting an asset.

Expand Down
18 changes: 18 additions & 0 deletions scripts/contract-tests/release-utils.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
escapeRegExp,
extractInlineScripts,
extractMarkupText,
formatReleaseChecksumLine,
getReleaseArtifactPath,
getReleaseTag
} from "../release-utils.mjs";
Expand Down Expand Up @@ -75,3 +76,20 @@ test("release tuple helpers reject unsafe package names and versions", () => {
assert.throws(() => assertReleasePackageName(packageName), /Invalid release package name/);
}
});

test("release checksum lines use flat public asset names", () => {
const digest = "a".repeat(64);

assert.equal(
formatReleaseChecksumLine("/tmp/release/dist/hSQLite-Editor-v0.6.1.html", digest),
`${digest} hSQLite-Editor-v0.6.1.html`
);
assert.equal(
formatReleaseChecksumLine("/tmp/release/sbom.spdx.json", digest),
`${digest} sbom.spdx.json`
);
assert.throws(
() => formatReleaseChecksumLine("/tmp/release/SHA256SUMS", "not-a-digest"),
/Invalid SHA-256 digest/
);
});
8 changes: 6 additions & 2 deletions scripts/release-assets.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { getReleaseArtifactPath, getReleaseTag } from "./release-utils.mjs";
import {
formatReleaseChecksumLine,
getReleaseArtifactPath,
getReleaseTag
} from "./release-utils.mjs";

const rootDir = path.resolve(new URL("..", import.meta.url).pathname);
const checkOnly = process.argv.includes("--check");
Expand Down Expand Up @@ -38,7 +42,7 @@ if (sbom.spdxVersion !== "SPDX-2.3" || sbom.documentNamespace !== expectedNamesp

const checksumText = subjects.map(subjectPath => {
const digest = crypto.createHash("sha256").update(fs.readFileSync(subjectPath)).digest("hex");
return `${digest} ${path.relative(rootDir, subjectPath)}`;
return formatReleaseChecksumLine(subjectPath, digest);
}).join("\n") + "\n";

if (checkOnly) {
Expand Down
13 changes: 13 additions & 0 deletions scripts/release-utils.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,19 @@ export function getReleaseArtifactPath(rootDir, version) {
return path.join(rootDir, "dist", `hSQLite-Editor-v${assertStableReleaseVersion(version)}.html`);
}

export function formatReleaseChecksumLine(subjectPath, digest) {
const normalizedDigest = String(digest ?? "");
if (!/^[a-f0-9]{64}$/.test(normalizedDigest)) {
throw new Error(`Invalid SHA-256 digest: ${JSON.stringify(normalizedDigest)}`);
}

const assetName = path.basename(String(subjectPath ?? ""));
if (!assetName || assetName === "." || assetName === "..") {
throw new Error(`Invalid release asset path: ${JSON.stringify(subjectPath)}`);
}
return `${normalizedDigest} ${assetName}`;
}

export function getReleaseTag(packageName, version) {
return `${assertReleasePackageName(packageName)}-v${assertStableReleaseVersion(version)}`;
}