forked from darkreader/darkreader
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcli.js
More file actions
209 lines (182 loc) · 7.33 KB
/
Copy pathcli.js
File metadata and controls
209 lines (182 loc) · 7.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
/**
* 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 {execute, log} from './utils.js';
import {fork} from 'node:child_process';
import process from 'node:process';
import {fileURLToPath} from 'node:url';
import {join} from 'node:path';
import {rm, stat} from 'node:fs/promises';
import assert from 'node:assert/strict';
import {runTasks} from './task.js';
import zip from './zip.js';
import signature from './bundle-signature.js';
import {PLATFORM} from './platform.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();