release: produce a versioned per-browser artifact and pin the Chrome extension id (closes #310)
manifest/chrome.json now carries a fixed public key, so the extension id and the chrome.storage.local partition holding the wallet stay stable across checkout moves and re-clones instead of being derived from the absolute path. A release entrypoint produces a self-contained versioned artifact per browser, including the files that sit at dist/ root outside both browser directories. One version source of truth, enforced: the build fails naming the culprit when the two manifests and package.json disagree, and BUILD_COMMIT now marks a dirty tree as dirty. Firefox ships an unsigned XPI; the README states that release Firefox and ESR refuse it, that Developer Edition or Unbranded is required, and that Remove is irreversible except from the recovery phrase, which is asserted by test.
This commit was merged in pull request #347.
This commit is contained in:
97
tests/version.test.js
Normal file
97
tests/version.test.js
Normal file
@@ -0,0 +1,97 @@
|
||||
// One version, three files, and the rule that they agree.
|
||||
//
|
||||
// package.json, manifest/chrome.json and manifest/firefox.json each declare a
|
||||
// version and none is derived from another. Before this, nothing compared
|
||||
// them: the manifests were hardcoded at 0.1.0 and copied to dist/ verbatim
|
||||
// while package.json fed the About screen separately, so the number the
|
||||
// browser reported and the number the extension displayed could drift apart
|
||||
// with no check anywhere failing.
|
||||
//
|
||||
// The build enforces the agreement (build.js calls resolveVersion before it
|
||||
// emits anything) and script/lib/package.js names the artifacts from it. This
|
||||
// asserts both halves: that the tree as committed agrees, and that a tree that
|
||||
// does not is refused rather than resolved to one of the answers.
|
||||
|
||||
const fs = require("fs");
|
||||
const os = require("os");
|
||||
const path = require("path");
|
||||
|
||||
const {
|
||||
VERSION_SOURCES,
|
||||
declaredVersions,
|
||||
resolveVersion,
|
||||
} = require("../script/lib/version");
|
||||
|
||||
const ROOT = path.join(__dirname, "..");
|
||||
|
||||
// A tree containing only the three version files, with the given versions.
|
||||
function fixture(versions) {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "autistmask-version-"));
|
||||
fs.mkdirSync(path.join(dir, "manifest"));
|
||||
VERSION_SOURCES.forEach((source, i) => {
|
||||
fs.writeFileSync(
|
||||
path.join(dir, source),
|
||||
JSON.stringify({ version: versions[i] }),
|
||||
);
|
||||
});
|
||||
return dir;
|
||||
}
|
||||
|
||||
describe("the declared version", () => {
|
||||
test("all three sources agree in this tree", () => {
|
||||
const declared = declaredVersions(ROOT);
|
||||
expect(declared.map((d) => d.source)).toEqual(VERSION_SOURCES);
|
||||
expect([...new Set(declared.map((d) => d.version))]).toHaveLength(1);
|
||||
expect(resolveVersion(ROOT)).toBe(declared[0].version);
|
||||
});
|
||||
|
||||
// Semver-shaped, because it names every release artifact and is what the
|
||||
// browser compares when deciding whether an install is an upgrade.
|
||||
test("is semver-shaped", () => {
|
||||
expect(resolveVersion(ROOT)).toMatch(/^\d+\.\d+\.\d+$/);
|
||||
});
|
||||
|
||||
// The load-bearing case: each of the three, disagreeing on its own, has to
|
||||
// fail. A check that only looked at two of them would pass one of these.
|
||||
test.each([
|
||||
["package.json", ["9.9.9", "0.1.0", "0.1.0"]],
|
||||
["manifest/chrome.json", ["0.1.0", "9.9.9", "0.1.0"]],
|
||||
["manifest/firefox.json", ["0.1.0", "0.1.0", "9.9.9"]],
|
||||
])("a disagreeing %s fails rather than resolving", (source, versions) => {
|
||||
const dir = fixture(versions);
|
||||
try {
|
||||
expect(() => resolveVersion(dir)).toThrow(
|
||||
/the declared versions disagree/,
|
||||
);
|
||||
expect(() => resolveVersion(dir)).toThrow(new RegExp(source));
|
||||
} finally {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("agreeing sources resolve", () => {
|
||||
const dir = fixture(["1.2.3", "1.2.3", "1.2.3"]);
|
||||
try {
|
||||
expect(resolveVersion(dir)).toBe("1.2.3");
|
||||
} finally {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
// A missing or empty version is not agreement. Left unchecked, three files
|
||||
// that all declared nothing would "agree" on undefined and the artifacts
|
||||
// would be named after it.
|
||||
test.each([[undefined], [""], [" "], [3]])(
|
||||
"a version of %p is refused",
|
||||
(bad) => {
|
||||
const dir = fixture([bad, bad, bad]);
|
||||
try {
|
||||
expect(() => resolveVersion(dir)).toThrow(
|
||||
/declares no usable "version"/,
|
||||
);
|
||||
} finally {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user