Files
halo-dark-mode-plugin/third-party/darkreader/tasks/cli.js
T

209 lines
7.3 KiB
JavaScript

/**
* This file executes build.js in a child process, this is needed for two things:
* 1. Enable interrupts like Ctrl+C for regular builds
* 2. Support building older versions of Dark Reader and then inserting signatures into archives
*/
// @ts-check
import assert from 'node:assert/strict';
import {fork} from 'node:child_process';
import {rm, stat} from 'node:fs/promises';
import {join} from 'node:path';
import process from 'node:process';
import {fileURLToPath} from 'node:url';
import signature from './bundle-signature.js';
import {PLATFORM} from './platform.js';
import {runTasks} from './task.js';
import {execute, log} from './utils.js';
import zip from './zip.js';
const __filename = join(fileURLToPath(import.meta.url), '../build.js');
function getSignatureDir(version) {
return join(fileURLToPath(import.meta.url), `../../integrity/firefox/`, version);
}
async function executeChildProcess(args) {
const child = fork(__filename, args);
// Send SIGINTs as SIGKILLs, which are not ignored
process.on('SIGINT', () => {
child.kill('SIGKILL');
process.exit(130);
});
return new Promise((resolve, reject) => child.on('error', reject).on('close', resolve));
}
function printHelp() {
console.log([
'Dark Reader build utility',
'',
'Usage: build [build parameters]',
'',
'To narrow down the list of build targets (for efficiency):',
' --api Library build (published to NPM)',
' --chrome MV2 for Chromium-based browsers (published to Chrome Web Store)',
' --chrome-mv3 MV3 for Chromium-based browsers (will replace MV2 version eventually)',
' --firefox MV2 for Firefox (published to Mozilla Add-on store)',
' --thunderbird Thunderbird',
'',
'To specify type of build:',
' --release Release bundle for signing prior to publication',
' --version=* Released bundle complete with digital signature (Firefox only)',
' --debug Build for development',
' --watch Incremental build for development',
'',
'To log errors to disk (for debugging and bug reports):',
' --log-info Log lots of data',
' --log-warn Log only warnings',
'',
'Build for testing (not to be used by humans):',
' --test',
].join('\n'));
}
function getVersion(args) {
const prefix = '--version=';
const arg = args.find((arg) => arg.startsWith(prefix));
if (!arg) {
return null;
}
const version = arg.substring(prefix.length);
if (/^\d+(.\d+){0,3}$/.test(version)) {
return version;
}
throw new Error(`Invalid version argument ${version}`);
}
async function ensureGitClean() {
const diff = await execute('git diff');
if (diff) {
throw new Error('git source tree is not clean. Pease commit your work and try again');
}
}
/**
* Checks out a particular revision of source code and dependencies,
* audits dependencies and applies fixes to vulnerabilities.
* Fixes for vulnerabilities should not affect build output since most
* vulnerabilities reside in code which never gets reached during build.
* However, fixing the vulnerabilities and obtaining a build with all "clean"
* dependencies which is identical to already published version serves as a proof
* that the published version was always free of (now known) vulnerabilities.
*
* @param {string} version The desired git version, e.g., 'v4.9.63' or 'v4.9.37.1'
* @param {boolean} fixVulnerabilities Whether of not to attempt to fix known vulnerabilities
*/
async function checkoutVersion(version, fixVulnerabilities) {
log.ok(`Checking out version ${version}`);
// Use -- to disambiguate the tag (release version) and file paths
await rm('src', {force: true, recursive: true});
await execute(`git restore --source v${version} -- package.json package-lock.json src/ tasks/`);
log.ok(`Installing dependencies`);
await execute('npm install --ignore-scripts');
if (!fixVulnerabilities) {
log.ok(`Skipping dependency audit`);
return;
}
log.ok(`Dependency audit`);
const deps = JSON.parse(await execute('npm audit fix --force --ignore-scripts --json'));
if (deps.audit.auditReportVersion !== 2) {
throw new Error('Could not audit dependencies');
}
if (deps.audit.metadata.vulnerabilities.total !== 0) {
throw new Error('Dependency vulnerability without a fix found, please audit manually');
}
}
async function checkoutHead() {
// Restore current files
await execute('git restore --source HEAD -- package.json package-lock.json src/ tasks/');
// Clean up files which existed earlier but were deleted
await execute('git clean -f -- package.json package-lock.json src/ tasks/');
await execute('npm install --ignore-scripts');
}
function validateArguments(args) {
const validationErrors = [];
const validFlags = ['--api', '--chrome', '--chrome-mv2', '--chrome-mv3', '--firefox', '--firefox-mv2', '--thunderbird', '--release', '--debug', '--watch', '--plus', '--log-info', '--log-warn', '--test'];
const invalidFlags = args.filter((flag) => !validFlags.includes(flag) && !flag.startsWith('--version='));
invalidFlags.forEach((flag) => validationErrors.push(`Invalid flag ${flag}`));
if (args.some((arg) => arg.startsWith('--version='))) {
if (!args.includes('--firefox') || !args.includes('--release') || args.length !== 3) {
validationErrors.push('Only Firefox build currently supports signed builds');
}
}
return validationErrors;
}
function parseArguments(args) {
return args.filter((arg) => !arg.startsWith('--version='));
}
async function run() {
const args = process.argv.slice(3);
const shouldPrintHelp = args.length === 0 || process.argv[2] !== 'build' || args.includes('-h') || args.includes('--help');
if (shouldPrintHelp) {
printHelp();
process.exit(0);
}
const validationErrors = validateArguments(args);
if (validationErrors.length > 0) {
validationErrors.forEach(log.error);
printHelp();
process.exit(130);
}
const version = getVersion(args);
// If building signed build, check that required signature files exist
if (version) {
try {
const signatureDir = getSignatureDir(version);
const stats = await stat(signatureDir);
assert(stats.isDirectory());
} catch (e) {
console.log(`Could not find signature files for version ${version}`);
return;
}
}
// We need to install new deps prior to forking for them to be loaded properly
if (version) {
try {
await ensureGitClean();
await checkoutVersion(version, args.includes('--fix-deps'));
} catch (e) {
log.error(`Could not check out tag ${version}. ${e}`);
return;
}
}
const childArgs = parseArguments(args);
await executeChildProcess(childArgs);
if (version) {
log.ok('PACKING SIGNATURES');
await checkoutHead();
await runTasks([signature, zip], {
version,
platforms: {
[PLATFORM.FIREFOX_MV2]: true,
},
debug: false,
watch: false,
log: false,
test: false,
});
}
}
run();