gulpfile.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. const fs = require('fs').promises;
  2. const gulp = require('gulp');
  3. const del = require('del');
  4. const log = require('fancy-log');
  5. const gulpFilter = require('gulp-filter');
  6. const uglify = require('gulp-uglify');
  7. const plumber = require('gulp-plumber');
  8. const Sharp = require('sharp');
  9. const { isProd } = require('@gera2ld/plaid/util');
  10. const spawn = require('cross-spawn');
  11. const i18n = require('./scripts/i18n');
  12. const { getVersion, isBeta } = require('./scripts/version-helper');
  13. const { buildManifest } = require('./scripts/manifest-helper');
  14. const pkg = require('./package.json');
  15. const DIST = 'dist';
  16. const paths = {
  17. manifest: 'src/manifest.yml',
  18. copy: [
  19. 'src/public/lib/**',
  20. ],
  21. locales: [
  22. 'src/_locales/**',
  23. ],
  24. templates: [
  25. 'src/**/*.@(js|html|json|yml|vue)',
  26. ],
  27. };
  28. function clean() {
  29. return del(DIST);
  30. }
  31. function watch() {
  32. gulp.watch(paths.manifest, manifest);
  33. gulp.watch(paths.copy, copyFiles);
  34. gulp.watch(paths.locales.concat(paths.templates), copyI18n);
  35. }
  36. async function jsDev() {
  37. require('@gera2ld/plaid-webpack/bin/develop')();
  38. }
  39. async function jsProd() {
  40. return require('@gera2ld/plaid-webpack/bin/build')({
  41. api: true,
  42. keep: true,
  43. });
  44. }
  45. /**
  46. * Versioning
  47. *
  48. * The version of extension is composed of `version` and `beta` fields in `package.json`.
  49. *
  50. * Note: prerelease is ignored and not recommended since both Chrome and Firefox do not support semver
  51. *
  52. */
  53. async function manifest() {
  54. const data = await buildManifest();
  55. await fs.writeFile(`${DIST}/manifest.json`, JSON.stringify(data), 'utf8');
  56. }
  57. async function createIcons() {
  58. const ALPHA = .5;
  59. const dist = `${DIST}/public/images`;
  60. await fs.mkdir(dist, { recursive: true });
  61. const icon = Sharp(`src/resources/icon${isBeta() ? '-beta' : ''}.png`);
  62. const gray = icon.clone().grayscale();
  63. const transparent = icon.clone().composite([{
  64. input: Buffer.from([255, 255, 255, 256 * ALPHA]),
  65. raw: { width: 1, height: 1, channels: 4 },
  66. tile: true,
  67. blend: 'dest-in',
  68. }]);
  69. const types = [
  70. ['', icon],
  71. ['b', gray],
  72. ['w', transparent],
  73. ];
  74. const handle = (size, type = '', image = icon) => {
  75. let res = image.clone().resize({ width: size });
  76. if (size < 48) res = res.sharpen(size < 32 ? .5 : .25);
  77. return res.toFile(`${dist}/icon${size}${type}.png`);
  78. };
  79. const darkenOuterEdge = async img => img.composite([{
  80. input: await img.toBuffer(),
  81. blend: 'over',
  82. }]);
  83. const handle16 = async ([type, image]) => {
  84. const res = image.clone()
  85. .resize({ width: 18 })
  86. .sharpen(.5, 0)
  87. .extract({ left: 1, top: 2, width: 16, height: 16 });
  88. return (type === 'w' ? res : await darkenOuterEdge(res))
  89. .toFile(`${dist}/icon16${type}.png`);
  90. };
  91. return Promise.all([
  92. handle(48),
  93. handle(128),
  94. ...types.map(handle16),
  95. ...[19, 32, 38].flatMap(size => types.map(t => handle(size, ...t))),
  96. ]);
  97. }
  98. /**
  99. * Bump `beta` in `package.json` to release a new beta version.
  100. */
  101. async function bump() {
  102. if (process.argv.includes('--reset')) {
  103. delete pkg.beta;
  104. } else {
  105. pkg.beta = (+pkg.beta || 0) + 1;
  106. }
  107. await fs.writeFile('package.json', JSON.stringify(pkg, null, 2), 'utf8');
  108. if (process.argv.includes('--commit')) {
  109. const version = `v${getVersion()}`;
  110. spawn.sync('git', ['commit', '-am', version]);
  111. spawn.sync('git', ['tag', '-m', version, version]);
  112. }
  113. }
  114. function copyFiles() {
  115. const jsFilter = gulpFilter(['**/*.js'], { restore: true });
  116. let stream = gulp.src(paths.copy, { base: 'src' });
  117. if (isProd) stream = stream
  118. .pipe(jsFilter)
  119. .pipe(uglify())
  120. .pipe(jsFilter.restore);
  121. return stream
  122. .pipe(gulp.dest(DIST));
  123. }
  124. function checkI18n() {
  125. return i18n.read({
  126. base: 'src/_locales',
  127. extension: '.json',
  128. });
  129. }
  130. function copyI18n() {
  131. return i18n.read({
  132. base: 'src/_locales',
  133. touchedOnly: true,
  134. useDefaultLang: true,
  135. markUntouched: false,
  136. extension: '.json',
  137. })
  138. .pipe(gulp.dest(`${DIST}/_locales`));
  139. }
  140. /**
  141. * Load locale files (src/_locales/<lang>/message.[json|yml]), and
  142. * update them with keys in template files, then store in `message.yml`.
  143. */
  144. function updateI18n() {
  145. return gulp.src(paths.templates)
  146. .pipe(plumber(logError))
  147. .pipe(i18n.extract({
  148. base: 'src/_locales',
  149. touchedOnly: false,
  150. useDefaultLang: false,
  151. markUntouched: true,
  152. extension: '.yml',
  153. }))
  154. .pipe(gulp.dest('src/_locales'));
  155. }
  156. function logError(err) {
  157. log(err.toString());
  158. return this.emit('end');
  159. }
  160. const pack = gulp.parallel(manifest, createIcons, copyFiles, copyI18n);
  161. exports.clean = clean;
  162. exports.manifest = manifest;
  163. exports.dev = gulp.series(gulp.parallel(pack, jsDev), watch);
  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;