fix: declare toolbar icons and ship real PNGs in both archives (closes #371)
Neither manifest declared an `icons` block, so Chrome and Firefox both drew the generic puzzle piece for this extension. That is the first thing the owner sees on every launch, and an unbranded placeholder is also how a user fails to tell a real extension from a look-alike. Both manifests now declare 16/32/48/128 as `icons/icon<size>.png`, and the four PNGs live at `icons/` in the tree. build.js copies them into each browser directory relative to it, so nothing points up and out the way `dist/styles.css` does, and the receipt records them like every other emitted file. The sizes copied come from the manifest that will ship next to them, not from a second list in build.js: a size a manifest declares and `icons/` does not hold fails the build with the path that is missing, rather than emitting a directory whose manifest references nothing. script/lib/package.js already resolved `.png` strings, so an icon that reached a manifest but not the archive fails self-containment; tests/packaging.test.js now pins that case, since it was covered only incidentally before. tests/manifest.test.js asserts the declaration in both manifests, that both declare the same set, and that each referenced file is a PNG whose IHDR states the size the entry claims — a declaration alone would still permit a reference to a file that is not there or is not an image. The artwork is original, drawn from geometry rather than traced or downloaded: a flat dark-navy rounded square (#101A2E) with a teal (#35E0C2) triangular "A" — one outer triangle minus a triangular counter — rasterised with 8x8 supersampling and encoded as RGBA PNG. One shape, two flat colours, which is what a 16px toolbar slot can carry. Verified: make check 56 suites / 1023 tests, make build and make package green, both archives unpacked and the four icons confirmed inside each with bytes identical to the tree, make test-e2e 55/55 and 5/5, make test-e2e-firefox 8/8 and 7/7 (the latter installs the packaged XPI). Removing icons/icon48.png fails make build; making build.js skip one copy fails make package with "the chrome archive would not be self-contained: it is told to load icons/icon48.png, which is not in it".
This commit is contained in:
49
build.js
49
build.js
@@ -51,6 +51,17 @@ const RECEIPT_ENV = "AUTISTMASK_BUILD_RECEIPT";
|
||||
// rather than writing a receipt that cannot be checked.
|
||||
const SAFE_EMITTED_PATH = /^dist\/[A-Za-z0-9._][A-Za-z0-9._/-]*$/;
|
||||
|
||||
// Where each browser directory's manifest comes from, and — through its
|
||||
// "icons" — which image files ship inside that directory.
|
||||
const MANIFEST_SOURCES = new Map([
|
||||
[DIST_CHROME, path.join(__dirname, "manifest", "chrome.json")],
|
||||
[DIST_FIREFOX, path.join(__dirname, "manifest", "firefox.json")],
|
||||
]);
|
||||
|
||||
// What an "icons" entry may name: a plain file under icons/, so a manifest
|
||||
// value is never joined into a path that leaves the repo.
|
||||
const ICON_REF_RE = /^icons\/[A-Za-z0-9._-]+\.png$/;
|
||||
|
||||
function ensureDir(dir) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
@@ -257,6 +268,42 @@ function copyEmitted(src, dest) {
|
||||
recordEmitted(dest);
|
||||
}
|
||||
|
||||
// Copy the icons one browser directory ships. The sizes come from the manifest
|
||||
// that will sit next to them, not from a second list here: a size the manifest
|
||||
// declares and icons/ does not hold fails the build, rather than shipping a
|
||||
// manifest whose reference resolves to nothing. Relative to the browser
|
||||
// directory, so nothing points up and out of it the way dist/styles.css does.
|
||||
function copyIcons(distDir) {
|
||||
const manifestPath = MANIFEST_SOURCES.get(distDir);
|
||||
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
||||
const refs = Object.values(manifest.icons || {});
|
||||
if (refs.length === 0) {
|
||||
throw new Error(
|
||||
`${repoRelative(manifestPath)} declares no icons, so the browser ` +
|
||||
`renders a generic placeholder for this extension`,
|
||||
);
|
||||
}
|
||||
for (const ref of refs) {
|
||||
if (!ICON_REF_RE.test(ref)) {
|
||||
throw new Error(
|
||||
`${repoRelative(manifestPath)} declares icon ` +
|
||||
`${JSON.stringify(ref)}, which is not a plain file under ` +
|
||||
`icons/`,
|
||||
);
|
||||
}
|
||||
const src = path.join(__dirname, ref);
|
||||
if (!fs.existsSync(src)) {
|
||||
throw new Error(
|
||||
`${repoRelative(manifestPath)} declares ${ref}, which is not ` +
|
||||
`in this tree`,
|
||||
);
|
||||
}
|
||||
const dest = path.join(distDir, ref);
|
||||
ensureDir(path.dirname(dest));
|
||||
copyEmitted(src, dest);
|
||||
}
|
||||
}
|
||||
|
||||
function sha256File(absPath) {
|
||||
return crypto
|
||||
.createHash("sha256")
|
||||
@@ -524,6 +571,8 @@ async function build() {
|
||||
tailwindOutput,
|
||||
path.join(distDir, "src", "popup", "styles.css"),
|
||||
);
|
||||
|
||||
copyIcons(distDir);
|
||||
}
|
||||
|
||||
// copy manifests
|
||||
|
||||
Reference in New Issue
Block a user