|
|
|
const path = require('node:path');
|
|
|
|
const {exec} = require('node:child_process');
|
|
|
|
const {
|
|
|
|
lstatSync,
|
|
|
|
readdirSync,
|
|
|
|
readFileSync,
|
|
|
|
writeFileSync,
|
|
|
|
rmSync
|
|
|
|
} = require('node:fs');
|
|
|
|
|
|
|
|
const {series, parallel, src, dest} = require('gulp');
|
|
|
|
const postcss = require('gulp-postcss');
|
|
|
|
const gulpif = require('gulp-if');
|
|
|
|
const jsonMerge = require('gulp-merge-json');
|
|
|
|
const jsonmin = require('gulp-jsonmin');
|
|
|
|
const htmlmin = require('gulp-htmlmin');
|
|
|
|
const imagemin = require('gulp-imagemin');
|
|
|
|
const {ensureDirSync, readJsonSync} = require('fs-extra');
|
|
|
|
const sharp = require('sharp');
|
|
|
|
const CryptoJS = require('crypto-js');
|
|
|
|
|
|
|
|
const targetEnv = process.env.TARGET_ENV || 'chrome';
|
|
|
|
const isProduction = process.env.NODE_ENV === 'production';
|
|
|
|
const enableContributions =
|
|
|
|
(process.env.ENABLE_CONTRIBUTIONS || 'true') === 'true';
|
|
|
|
const distDir = path.join(__dirname, 'dist', targetEnv);
|
|
|
|
|
|
|
|
function initEnv() {
|
|
|
|
process.env.BROWSERSLIST_ENV = targetEnv;
|
|
|
|
}
|
|
|
|
|
|
|
|
function init(done) {
|
|
|
|
initEnv();
|
|
|
|
|
|
|
|
rmSync(distDir, {recursive: true, force: true});
|
|
|
|
ensureDirSync(distDir);
|
|
|
|
done();
|
|
|
|
}
|
|
|
|
|
|
|
|
function js(done) {
|
|
|
|
exec('webpack-cli build --color', function (err, stdout, stderr) {
|
|
|
|
console.log(stdout);
|
|
|
|
console.log(stderr);
|
|
|
|
done(err);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function html() {
|
|
|
|
return src(
|
|
|
|
enableContributions
|
|
|
|
? 'src/**/*.html'
|
|
|
|
: ['src/**/*.html', '!src/contribute/*.html'],
|
|
|
|
{base: '.'}
|
|
|
|
)
|
|
|
|
.pipe(gulpif(isProduction, htmlmin({collapseWhitespace: true})))
|
|
|
|
.pipe(dest(distDir));
|
|
|
|
}
|
|
|
|
|
|
|
|
function css() {
|
|
|
|
return src('src/solve/*.css', {base: '.'})
|
|
|
|
.pipe(postcss())
|
|
|
|
.pipe(dest(distDir));
|
|
|
|
}
|
|
|
|
|
|
|
|
async function images(done) {
|
|
|
|
ensureDirSync(path.join(distDir, 'src/assets/icons/app'));
|
|
|
|
const appIconSvg = readFileSync('src/assets/icons/app/icon.svg');
|
|
|
|
const appIconSizes = [16, 19, 24, 32, 38, 48, 64, 96, 128];
|
|
|
|
for (const size of appIconSizes) {
|
|
|
|
await sharp(appIconSvg, {density: (72 * size) / 24})
|
|
|
|
.resize(size)
|
|
|
|
.toFile(path.join(distDir, `src/assets/icons/app/icon-${size}.png`));
|
|
|
|
}
|
|
|
|
// Chrome Web Store does not correctly display optimized icons
|
|
|
|
if (isProduction && targetEnv !== 'chrome') {
|
|
|
|
await new Promise(resolve => {
|
|
|
|
src(path.join(distDir, 'src/assets/icons/app/*.png'), {base: '.'})
|
|
|
|
.pipe(imagemin())
|
|
|
|
.pipe(dest('.'))
|
|
|
|
.on('error', done)
|
|
|
|
.on('finish', resolve);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (enableContributions) {
|
|
|
|
await new Promise(resolve => {
|
|
|
|
src('node_modules/vueton/components/contribute/assets/*.@(png|svg)')
|
|
|
|
.pipe(gulpif(isProduction, imagemin()))
|
|
|
|
.pipe(dest(path.join(distDir, 'src/contribute/assets')))
|
|
|
|
.on('error', done)
|
|
|
|
.on('finish', resolve);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function fonts(done) {
|
|
|
|
await new Promise(resolve => {
|
|
|
|
src('src/assets/fonts/roboto.css', {base: '.'})
|
|
|
|
.pipe(postcss())
|
|
|
|
.pipe(dest(distDir))
|
|
|
|
.on('error', done)
|
|
|
|
.on('finish', resolve);
|
|
|
|
});
|
|
|
|
|
|
|
|
await new Promise(resolve => {
|
|
|
|
src(
|
|
|
|
'node_modules/@fontsource/roboto/files/roboto-latin-@(400|500|700)-normal.woff2'
|
|
|
|
)
|
|
|
|
.pipe(dest(path.join(distDir, 'src/assets/fonts/files')))
|
|
|
|
.on('error', done)
|
|
|
|
.on('finish', resolve);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async function locale(done) {
|
|
|
|
const localesRootDir = path.join(__dirname, 'src/assets/locales');
|
|
|
|
const localeDirs = readdirSync(localesRootDir).filter(function (file) {
|
|
|
|
return lstatSync(path.join(localesRootDir, file)).isDirectory();
|
|
|
|
});
|
|
|
|
for (const localeDir of localeDirs) {
|
|
|
|
const localePath = path.join(localesRootDir, localeDir);
|
|
|
|
await new Promise(resolve => {
|
|
|
|
src(
|
|
|
|
[
|
|
|
|
path.join(localePath, 'messages.json'),
|
|
|
|
path.join(localePath, `messages-${targetEnv}.json`)
|
|
|
|
],
|
|
|
|
{allowEmpty: true}
|
|
|
|
)
|
|
|
|
.pipe(
|
|
|
|
jsonMerge({
|
|
|
|
fileName: 'messages.json',
|
|
|
|
edit: (parsedJson, file) => {
|
|
|
|
if (isProduction) {
|
|
|
|
for (let [key, value] of Object.entries(parsedJson)) {
|
|
|
|
if (value.hasOwnProperty('description')) {
|
|
|
|
delete parsedJson[key].description;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return parsedJson;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
)
|
|
|
|
.pipe(gulpif(isProduction, jsonmin()))
|
|
|
|
.pipe(dest(path.join(distDir, '_locales', localeDir)))
|
|
|
|
.on('error', done)
|
|
|
|
.on('finish', resolve);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function manifest() {
|
|
|
|
return src(`src/assets/manifest/${targetEnv}.json`)
|
|
|
|
.pipe(
|
|
|
|
jsonMerge({
|
|
|
|
fileName: 'manifest.json',
|
|
|
|
edit: (parsedJson, file) => {
|
|
|
|
parsedJson.version = require('./package.json').version;
|
|
|
|
return parsedJson;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
)
|
|
|
|
.pipe(gulpif(isProduction, jsonmin()))
|
|
|
|
.pipe(dest(distDir));
|
|
|
|
}
|
|
|
|
|
|
|
|
function license() {
|
|
|
|
let year = '2018';
|
|
|
|
const currentYear = new Date().getFullYear().toString();
|
|
|
|
if (year !== currentYear) {
|
|
|
|
year = `${year}-${currentYear}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
const notice = `Buster: Captcha Solver for Humans
|
|
|
|
Copyright (c) ${year} Armin Sebastian
|
|
|
|
|
|
|
|
This software is released under the terms of the GNU General Public License v3.0.
|
|
|
|
See the LICENSE file for further information.
|
|
|
|
`;
|
|
|
|
|
|
|
|
writeFileSync(path.join(distDir, 'NOTICE'), notice);
|
|
|
|
return src('LICENSE').pipe(dest(distDir));
|
|
|
|
}
|
|
|
|
|
|
|
|
function secrets(done) {
|
|
|
|
try {
|
|
|
|
let data = process.env.BUSTER_SECRETS;
|
|
|
|
if (data) {
|
|
|
|
data = JSON.parse(data);
|
|
|
|
} else {
|
|
|
|
data = readJsonSync('secrets.json');
|
|
|
|
}
|
|
|
|
data = JSON.stringify(data);
|
|
|
|
|
|
|
|
const key = CryptoJS.SHA256(
|
|
|
|
readFileSync(path.join(distDir, 'src/background/script.js')).toString() +
|
|
|
|
readFileSync(path.join(distDir, 'src/solve/script.js')).toString()
|
|
|
|
).toString();
|
|
|
|
|
|
|
|
const ciphertext = CryptoJS.AES.encrypt(data, key).toString();
|
|
|
|
|
|
|
|
writeFileSync(path.join(distDir, 'secrets.txt'), ciphertext);
|
|
|
|
} catch (err) {
|
|
|
|
console.log(
|
|
|
|
'Secrets are missing, secrets.txt will not be included in the extension package.'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
done();
|
|
|
|
}
|
|
|
|
|
|
|
|
function zip(done) {
|
|
|
|
exec(
|
|
|
|
`web-ext build -s dist/${targetEnv} -a artifacts/${targetEnv} -n "{name}-{version}-${targetEnv}.zip" --overwrite-dest`,
|
|
|
|
function (err, stdout, stderr) {
|
|
|
|
console.log(stdout);
|
|
|
|
console.log(stderr);
|
|
|
|
done(err);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function inspect(done) {
|
|
|
|
initEnv();
|
|
|
|
|
|
|
|
exec(
|
|
|
|
`npm run build:prod:chrome && \
|
|
|
|
webpack --profile --json > report.json && \
|
|
|
|
webpack-bundle-analyzer --mode static report.json dist/chrome/src && \
|
|
|
|
sleep 3 && rm report.{json,html}`,
|
|
|
|
function (err, stdout, stderr) {
|
|
|
|
console.log(stdout);
|
|
|
|
console.log(stderr);
|
|
|
|
done(err);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.build = series(
|
|
|
|
init,
|
|
|
|
parallel(js, html, css, images, fonts, locale, manifest, license),
|
|
|
|
secrets
|
|
|
|
);
|
|
|
|
exports.zip = zip;
|
|
|
|
exports.inspect = inspect;
|
|
|
|
exports.secrets = secrets;
|