gulpfile.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. const fs = require('fs').promises;
  2. const gulp = require('gulp');
  3. const del = require('del');
  4. const log = require('fancy-log');
  5. const plumber = require('gulp-plumber');
  6. const Sharp = require('sharp');
  7. const spawn = require('cross-spawn');
  8. const i18n = require('./scripts/i18n');
  9. const { getVersion, isBeta } = require('./scripts/version-helper');
  10. const { buildManifest } = require('./scripts/manifest-helper');
  11. const pkg = require('./package.json');
  12. const DIST = 'dist';
  13. const paths = {
  14. manifest: 'src/manifest.yml',
  15. locales: [
  16. 'src/_locales/**',
  17. ],
  18. templates: [
  19. 'src/**/*.@(js|html|json|yml|vue)',
  20. ],
  21. };
  22. function clean() {
  23. return del(DIST);
  24. }
  25. function watch() {
  26. gulp.watch(paths.manifest, manifest);
  27. gulp.watch(paths.locales.concat(paths.templates), copyI18n);
  28. }
  29. async function jsDev() {
  30. return runCommand('webpack-cli', ['-w', '--config', 'scripts/webpack.conf.js']);
  31. }
  32. async function jsProd() {
  33. return runCommand('webpack-cli', ['--config', 'scripts/webpack.conf.js']);
  34. }
  35. function runCommand(command, args) {
  36. return new Promise((resolve, reject) => {
  37. const child = spawn(command, args, {
  38. stdio: 'inherit',
  39. });
  40. child.on('close', (code, signal) => {
  41. (code ? reject : resolve)(signal);
  42. });
  43. });
  44. }
  45. /**
  46. * manifest is already handled in ListBackgroundScriptsPlugin
  47. *
  48. * This task is only used to tweak dist/manifest.json without rebuilding
  49. */
  50. async function manifest() {
  51. const base = JSON.parse(await fs.readFile(`${DIST}/manifest.json`, 'utf8'));
  52. const data = await buildManifest(base);
  53. await fs.mkdir(DIST).catch(() => {});
  54. await fs.writeFile(`${DIST}/manifest.json`, JSON.stringify(data), 'utf8');
  55. }
  56. async function createIcons() {
  57. const ALPHA = 0.5;
  58. const dist = `${DIST}/public/images`;
  59. await fs.mkdir(dist, { recursive: true });
  60. const icon = Sharp(`src/resources/icon${isBeta() ? '-beta' : ''}.png`);
  61. const gray = icon.clone().grayscale();
  62. const transparent = icon.clone().composite([{
  63. input: Buffer.from([255, 255, 255, 256 * ALPHA]),
  64. raw: { width: 1, height: 1, channels: 4 },
  65. tile: true,
  66. blend: 'dest-in',
  67. }]);
  68. const types = [
  69. ['', icon],
  70. ['b', gray],
  71. ['w', transparent],
  72. ];
  73. const handle = (size, type = '', image = icon) => {
  74. let res = image.clone().resize({ width: size });
  75. if (size < 48) res = res.sharpen(size < 32 ? 0.5 : 0.25);
  76. return res.toFile(`${dist}/icon${size}${type}.png`);
  77. };
  78. const darkenOuterEdge = async img => img.composite([{
  79. input: await img.toBuffer(),
  80. blend: 'over',
  81. }]);
  82. const handle16 = async ([type, image]) => {
  83. const res = image.clone()
  84. .resize({ width: 18 })
  85. .sharpen(0.5, 0)
  86. .extract({ left: 1, top: 2, width: 16, height: 16 });
  87. return (type === 'w' ? res : await darkenOuterEdge(res))
  88. .toFile(`${dist}/icon16${type}.png`);
  89. };
  90. return Promise.all([
  91. handle(128),
  92. ...types.map(handle16),
  93. // 32px dashboard icon (recycled) + 2xDPI browser_action desktop
  94. // 38px dashboard icon (normal) + 1.5xDPI browser_action Android
  95. // 48px 2xDPI browser_action Android
  96. ...[32, 38, 48, 64].flatMap(size => types.map(t => handle(size, ...t))),
  97. ]);
  98. }
  99. /**
  100. * Bump `beta` in `package.json` to release a new beta version.
  101. */
  102. async function bump() {
  103. if (process.argv.includes('--reset')) {
  104. delete pkg.beta;
  105. } else {
  106. pkg.beta = (+pkg.beta || 0) + 1;
  107. }
  108. await fs.writeFile('package.json', JSON.stringify(pkg, null, 2) + '\n', 'utf8');
  109. if (process.argv.includes('--commit')) {
  110. const version = `v${getVersion()}`;
  111. spawn.sync('git', ['commit', '-am', version]);
  112. spawn.sync('git', ['tag', '-m', version, version]);
  113. }
  114. }
  115. function checkI18n() {
  116. return i18n.read({
  117. base: 'src/_locales',
  118. extension: '.json',
  119. });
  120. }
  121. function copyI18n() {
  122. return i18n.read({
  123. base: 'src/_locales',
  124. touchedOnly: true,
  125. useDefaultLang: true,
  126. markUntouched: false,
  127. extension: '.json',
  128. stripDescriptions: true,
  129. })
  130. .pipe(gulp.dest(`${DIST}/_locales`));
  131. }
  132. /**
  133. * Load locale files (src/_locales/<lang>/message.[json|yml]), and
  134. * update them with keys in template files, then store in `message.yml`.
  135. */
  136. function updateI18n() {
  137. return gulp.src(paths.templates)
  138. .pipe(plumber(logError))
  139. .pipe(i18n.extract({
  140. base: 'src/_locales',
  141. manifest: 'src/manifest.yml',
  142. touchedOnly: false,
  143. useDefaultLang: false,
  144. markUntouched: true,
  145. extension: '.yml',
  146. }))
  147. .pipe(gulp.dest('src/_locales'));
  148. }
  149. function logError(err) {
  150. log(err.toString());
  151. return this.emit('end');
  152. }
  153. function copyZip() {
  154. return gulp.src([
  155. 'node_modules/@zip.js/zip.js/dist/zip-no-worker.min.js',
  156. 'node_modules/@zip.js/zip.js/dist/z-worker.js',
  157. ])
  158. .pipe(gulp.dest(`${DIST}/public/lib`));
  159. }
  160. const pack = gulp.parallel(createIcons, copyI18n, copyZip);
  161. exports.clean = clean;
  162. exports.manifest = manifest;
  163. exports.dev = gulp.parallel(gulp.series(pack, watch), jsDev);
  164. exports.build = gulp.series(clean, gulp.parallel(pack, jsProd));
  165. exports.i18n = updateI18n;
  166. exports.check = checkI18n;
  167. exports.copyI18n = copyI18n;
  168. exports.bump = bump;