All checks were successful
check / check (push) Successful in 32s
- script/test runs the suite quietly, then reruns it with --verbose on failure and always exits 1 (REPO_POLICIES.md conditional verbose rerun pattern). New package.json script test:verbose is the -v form of the existing jest --forceExit invocation. - build.js calls node_modules/.bin/tailwindcss instead of npx, which would fetch from the registry unpinned if the binary were absent. - make install uses --frozen-lockfile, so a stale yarn.lock fails the target instead of being silently rewritten. - README Getting Started uses make setup (which also installs the pre-commit hook); the Makefile-only targets (install, hooks, build, build-debug, clean, dev) are now documented in Entrypoints. - .dockerignore records why .git is deliberately not excluded.
215 lines
7.5 KiB
JavaScript
215 lines
7.5 KiB
JavaScript
const fs = require("fs");
|
|
const path = require("path");
|
|
const { execSync } = require("child_process");
|
|
const esbuild = require("esbuild");
|
|
|
|
const DIST = path.join(__dirname, "dist");
|
|
const DIST_CHROME = path.join(DIST, "chrome");
|
|
const DIST_FIREFOX = path.join(DIST, "firefox");
|
|
const SRC = path.join(__dirname, "src");
|
|
|
|
// The module whose compiled DEBUG state script/verify-build asserts, and the
|
|
// manifest naming every emitted bundle that ends up containing it. The
|
|
// manifest is derived from esbuild's own dependency graph rather than from a
|
|
// hardcoded list, so it tracks the bundle layout instead of rotting with it.
|
|
const AUDITED_MODULE = "src/shared/constants.js";
|
|
const BUNDLE_MANIFEST = path.join(DIST, "constants-bundles.txt");
|
|
|
|
function ensureDir(dir) {
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
}
|
|
|
|
// Repo-relative, forward-slashed, so the manifest reads the same on every
|
|
// platform and can be consumed by a POSIX shell script without further work.
|
|
function repoRelative(p) {
|
|
return path.relative(__dirname, p).split(path.sep).join("/");
|
|
}
|
|
|
|
// Collect the outputs of one esbuild run that bundle AUDITED_MODULE. esbuild
|
|
// reports every input that contributed to an output in the metafile, which is
|
|
// the authoritative answer to "is constants.js in this bundle" — unlike
|
|
// searching the minified text, it does not depend on what survived minification.
|
|
function outputsContainingAuditedModule(metafile) {
|
|
return Object.entries(metafile.outputs)
|
|
.filter(([outFile, info]) => {
|
|
if (!outFile.endsWith(".js")) return false;
|
|
return Object.keys(info.inputs).some(
|
|
(input) => repoRelative(input) === AUDITED_MODULE,
|
|
);
|
|
})
|
|
.map(([outFile]) => repoRelative(outFile));
|
|
}
|
|
|
|
// DEBUG is a build-time flag, off unless explicitly requested. It is the only
|
|
// thing that makes the hardcoded test mnemonic reachable, so the opt-in must be
|
|
// exact: anything other than the literal "1" (unset, empty, "true", a typo)
|
|
// produces a release build. Failing towards the safe mode is deliberate.
|
|
function isDebugBuild() {
|
|
return process.env.AUTISTMASK_DEBUG === "1";
|
|
}
|
|
|
|
function getBuildInfo() {
|
|
const pkg = JSON.parse(
|
|
fs.readFileSync(path.join(__dirname, "package.json"), "utf8"),
|
|
);
|
|
let commitHash = "unknown";
|
|
try {
|
|
commitHash = execSync("git rev-parse --short HEAD", {
|
|
encoding: "utf8",
|
|
}).trim();
|
|
} catch (_) {
|
|
// not a git repo or git not available
|
|
}
|
|
let commitHashFull = "unknown";
|
|
try {
|
|
commitHashFull = execSync("git rev-parse HEAD", {
|
|
encoding: "utf8",
|
|
}).trim();
|
|
} catch (_) {
|
|
// not a git repo or git not available
|
|
}
|
|
return {
|
|
version: pkg.version,
|
|
license: pkg.license,
|
|
author: pkg.author,
|
|
commitHash,
|
|
commitHashFull,
|
|
buildDate: new Date().toISOString().slice(0, 10),
|
|
};
|
|
}
|
|
|
|
async function build() {
|
|
console.log("Building AutistMask extension...");
|
|
|
|
const buildInfo = getBuildInfo();
|
|
console.log("Build info:", buildInfo);
|
|
|
|
const debugBuild = isDebugBuild();
|
|
console.log(
|
|
debugBuild
|
|
? "Build mode: DEBUG (INSECURE - hardcoded test mnemonic, do not ship)"
|
|
: "Build mode: release (DEBUG off)",
|
|
);
|
|
|
|
const define = {
|
|
__BUILD_DEBUG__: JSON.stringify(debugBuild),
|
|
__BUILD_VERSION__: JSON.stringify(buildInfo.version),
|
|
__BUILD_LICENSE__: JSON.stringify(buildInfo.license),
|
|
__BUILD_AUTHOR__: JSON.stringify(buildInfo.author),
|
|
__BUILD_COMMIT__: JSON.stringify(buildInfo.commitHash),
|
|
__BUILD_COMMIT_FULL__: JSON.stringify(buildInfo.commitHashFull),
|
|
__BUILD_DATE__: JSON.stringify(buildInfo.buildDate),
|
|
};
|
|
|
|
// Emitted bundles that contain constants.js, accumulated across every
|
|
// esbuild run below and written out for script/verify-build.
|
|
const auditedBundles = [];
|
|
|
|
// compile tailwind CSS
|
|
console.log("Compiling Tailwind CSS...");
|
|
const tailwindInput = path.join(SRC, "popup", "styles", "main.css");
|
|
const tailwindOutput = path.join(DIST, "styles.css");
|
|
ensureDir(DIST);
|
|
|
|
// Drop any manifest from a previous build before emitting anything, so a
|
|
// build that never gets around to writing one cannot be verified against
|
|
// a stale list.
|
|
fs.rmSync(BUNDLE_MANIFEST, { force: true });
|
|
// The locally installed binary, not `npx` — npx silently fetches from the
|
|
// registry when the binary is absent, which is an unpinned network fetch
|
|
// in the middle of a build.
|
|
const tailwindBin = path.join(
|
|
__dirname,
|
|
"node_modules",
|
|
".bin",
|
|
"tailwindcss",
|
|
);
|
|
execSync(
|
|
`"${tailwindBin}" -i "${tailwindInput}" -o "${tailwindOutput}" --minify`,
|
|
{ stdio: "inherit" },
|
|
);
|
|
|
|
// Every bundle goes through here, so metafile collection cannot be
|
|
// forgotten when a new entry point is added.
|
|
async function bundle(entryPoint, outfile) {
|
|
const result = await esbuild.build({
|
|
entryPoints: [entryPoint],
|
|
bundle: true,
|
|
format: "iife",
|
|
outfile,
|
|
platform: "browser",
|
|
target: ["chrome110", "firefox110"],
|
|
minify: true,
|
|
metafile: true,
|
|
define,
|
|
});
|
|
auditedBundles.push(...outputsContainingAuditedModule(result.metafile));
|
|
}
|
|
|
|
for (const distDir of [DIST_CHROME, DIST_FIREFOX]) {
|
|
ensureDir(path.join(distDir, "src", "popup"));
|
|
ensureDir(path.join(distDir, "src", "background"));
|
|
ensureDir(path.join(distDir, "src", "content"));
|
|
|
|
// bundle popup JS with esbuild (inlines ethers, libsodium, etc.)
|
|
await bundle(
|
|
path.join(SRC, "popup", "index.js"),
|
|
path.join(distDir, "src", "popup", "index.js"),
|
|
);
|
|
|
|
// bundle background script
|
|
await bundle(
|
|
path.join(SRC, "background", "index.js"),
|
|
path.join(distDir, "src", "background", "index.js"),
|
|
);
|
|
|
|
// bundle content script
|
|
await bundle(
|
|
path.join(SRC, "content", "index.js"),
|
|
path.join(distDir, "src", "content", "index.js"),
|
|
);
|
|
|
|
// bundle inpage script (injected into page context, separate file)
|
|
await bundle(
|
|
path.join(SRC, "content", "inpage.js"),
|
|
path.join(distDir, "src", "content", "inpage.js"),
|
|
);
|
|
|
|
// copy popup HTML
|
|
fs.copyFileSync(
|
|
path.join(SRC, "popup", "index.html"),
|
|
path.join(distDir, "src", "popup", "index.html"),
|
|
);
|
|
|
|
// place compiled CSS next to popup HTML
|
|
fs.copyFileSync(
|
|
tailwindOutput,
|
|
path.join(distDir, "src", "popup", "styles.css"),
|
|
);
|
|
}
|
|
|
|
// copy manifests
|
|
fs.copyFileSync(
|
|
path.join(__dirname, "manifest", "chrome.json"),
|
|
path.join(DIST_CHROME, "manifest.json"),
|
|
);
|
|
fs.copyFileSync(
|
|
path.join(__dirname, "manifest", "firefox.json"),
|
|
path.join(DIST_FIREFOX, "manifest.json"),
|
|
);
|
|
|
|
// Written last so a build that died partway through leaves no manifest
|
|
// at all, which script/verify-build treats as a hard failure rather than
|
|
// as "nothing to check".
|
|
const manifest = [...new Set(auditedBundles)].sort();
|
|
fs.writeFileSync(BUNDLE_MANIFEST, manifest.map((p) => `${p}\n`).join(""));
|
|
console.log(
|
|
`Bundles containing ${AUDITED_MODULE}: ${manifest.length} ` +
|
|
`(listed in ${repoRelative(BUNDLE_MANIFEST)})`,
|
|
);
|
|
|
|
console.log("Build complete: dist/chrome/ and dist/firefox/");
|
|
}
|
|
|
|
build();
|