340 lines
11 KiB
JavaScript
340 lines
11 KiB
JavaScript
|
require('source-map-support').install();
|
||
|
|
||
|
const fs = require('fs');
|
||
|
const path = require('path');
|
||
|
const gulp = require('gulp');
|
||
|
const _ = require('lodash');
|
||
|
const gulpif = require('gulp-if');
|
||
|
const rev = require('gulp-rev');
|
||
|
const sass = require('gulp-sass');
|
||
|
const shell = require('gulp-shell');
|
||
|
const cssnano = require('gulp-cssnano');
|
||
|
const imagemin = require('gulp-imagemin');
|
||
|
const autoprefixer = require('gulp-autoprefixer');
|
||
|
const liveServer = require('gulp-live-server');
|
||
|
const sizereport = require('gulp-sizereport');
|
||
|
const markdownTree = require('markdown-tree');
|
||
|
const del = require('del');
|
||
|
const mocha = require('gulp-spawn-mocha');
|
||
|
const remapIstanbul = require('remap-istanbul/lib/gulpRemapIstanbul');
|
||
|
const spawn = require('child_process').spawn;
|
||
|
const argv = require('yargs').argv;
|
||
|
const config = require('./config.json');
|
||
|
const stamp = Math.floor(Math.random() * 0xffffffff);
|
||
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-';
|
||
|
const HASH = _.range(0, 10).map(() => chars[Math.floor(Math.random() * chars.length)]).join('');
|
||
|
|
||
|
let development = true;
|
||
|
|
||
|
function swallowError(e) {
|
||
|
console.log(e.message);
|
||
|
this.emit('end');
|
||
|
}
|
||
|
|
||
|
const runAsync = (command, args) => new Promise((resolve, reject) => {
|
||
|
const proc = spawn(command, args)
|
||
|
.on('error', reject)
|
||
|
.on('exit', resolve);
|
||
|
|
||
|
proc.stdout.pipe(process.stdout);
|
||
|
proc.stderr.pipe(process.stderr);
|
||
|
});
|
||
|
|
||
|
const readFile = src => fs.readFileSync(src, { encoding: 'utf-8' });
|
||
|
|
||
|
const lintCode = code => (code.trim() + '\n')
|
||
|
.replace(/\r\n/g, '\n')
|
||
|
.replace(/\n/g, '\r\n')
|
||
|
.replace(/ /g, '\t')
|
||
|
.replace(/"/g, "'");
|
||
|
|
||
|
const npmScript = (name, args = []) => {
|
||
|
const func = () => runAsync(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['run', name, ...args]);
|
||
|
func.displayName = `npm run ${name}`;
|
||
|
return func;
|
||
|
};
|
||
|
|
||
|
const clean = () => del([
|
||
|
'build/*',
|
||
|
'temp/*',
|
||
|
'!temp/.gitignore',
|
||
|
]);
|
||
|
|
||
|
const clearnAdmin = () => del([
|
||
|
'build/assets-admin',
|
||
|
]);
|
||
|
|
||
|
const manifest = cb => {
|
||
|
const json = {
|
||
|
name: config.title,
|
||
|
short_name: config.title,
|
||
|
start_url: '/',
|
||
|
scope: '/',
|
||
|
theme_color: '#333333',
|
||
|
background_color: '#333333',
|
||
|
display: 'standalone',
|
||
|
icons: [
|
||
|
{
|
||
|
src: '/android-chrome-192x192.png',
|
||
|
sizes: '192x192',
|
||
|
type: 'image/png'
|
||
|
},
|
||
|
{
|
||
|
src: '/android-chrome-512x512.png',
|
||
|
sizes: '512x512',
|
||
|
type: 'image/png'
|
||
|
}
|
||
|
]
|
||
|
};
|
||
|
|
||
|
fs.writeFile('src/ts/generated/changelog.ts', JSON.stringify(json, null, 2), 'utf8', cb);
|
||
|
};
|
||
|
|
||
|
const sprites = () => Promise.resolve() // del(['tools/output/images/*'])
|
||
|
.then(() => runAsync('node', ['src/scripts/tools/create-sprites.js']))
|
||
|
.then(() => gulp.src('tools/output/images/*')
|
||
|
.pipe(gulpif(!argv.fast, imagemin()))
|
||
|
.pipe(gulp.dest('assets/images')));
|
||
|
|
||
|
const changelog = cb => {
|
||
|
const changelog = readFile('CHANGELOG.md');
|
||
|
const tree = markdownTree(changelog);
|
||
|
const object = config.nochangelog ? [] : tree.children.map(c => ({
|
||
|
version: c.text,
|
||
|
changes: c.tokens
|
||
|
.map(t => t.text)
|
||
|
.filter(x => x)
|
||
|
.map(x => x.replace(/^\[test\]/, '<span class="badge badge-secondary">test</span>')),
|
||
|
}));
|
||
|
const type = `{ version: string; changes: string[]; }[]`;
|
||
|
const code = `/* tslint:disable */\n\nexport const CHANGELOG: ${type} = ${JSON.stringify(object, null, 2)};\n`;
|
||
|
fs.writeFile('src/ts/generated/changelog.ts', code, 'utf8', cb);
|
||
|
};
|
||
|
|
||
|
const icons = cb => {
|
||
|
const root1 = path.join('node_modules', '@fortawesome', 'free-solid-svg-icons');
|
||
|
const root2 = path.join('node_modules', '@fortawesome', 'free-brands-svg-icons');
|
||
|
|
||
|
const getIconCode = src => JSON.stringify(require(`./${src}`).definition);
|
||
|
const iconsTs = readFile('src/ts/client/icons.ts');
|
||
|
const matched = _.uniq(iconsTs.match(/\bfa[A-Z]\S*\b/g));
|
||
|
const icons = matched.map(m => ({
|
||
|
name: m,
|
||
|
code: fs.existsSync(path.join(root1, `${m}.js`)) ? getIconCode(path.join(root1, `${m}.js`)) : getIconCode(path.join(root2, `${m}.js`)),
|
||
|
})).sort((a, b) => a.name.localeCompare(b.name));
|
||
|
const code = `/* tslint:disable */\n\n${icons.map(({ name, code }) => `export const ${name} = ${code};`).join('\n')}`;
|
||
|
fs.writeFile('src/ts/generated/fa-icons.ts', lintCode(code), 'utf8', cb);
|
||
|
};
|
||
|
|
||
|
const shaders = cb => {
|
||
|
function getShaderCode(filePath) {
|
||
|
return fs.readFileSync(filePath, 'utf8')
|
||
|
.replace(/^\s*\n/gm, '').trim();
|
||
|
}
|
||
|
|
||
|
const dir = path.join('src', 'ts', 'graphics', 'shaders');
|
||
|
const code = '/* tslint:disable */\n\n' + fs.readdirSync(dir)
|
||
|
.map(file => [_.camelCase(file.replace(/\.glsl$/, '')), path.join(dir, file)])
|
||
|
.map(([name, filePath]) => `export const ${name}Shader = \`${getShaderCode(filePath)}\`;`)
|
||
|
.join('\n\n');
|
||
|
fs.writeFile('src/ts/generated/shaders.ts', lintCode(code), 'utf8', cb);
|
||
|
};
|
||
|
|
||
|
const hash = cb => {
|
||
|
const code = `export const HASH = '${HASH}';\nexport const STAMP = ${stamp};`;
|
||
|
fs.writeFileSync('src/ts/generated/hash.ts', lintCode(code), 'utf8');
|
||
|
fs.writeFile('src/ts/generated/hash.json', JSON.stringify({ hash: HASH, stamp }), 'utf8', cb);
|
||
|
};
|
||
|
|
||
|
const rollbar = cb => {
|
||
|
const { environment, clientToken } = config.rollbar || {};
|
||
|
const code = `export const ROLLBAR_ENV = '${environment}';\nexport const ROLLBAR_TOKEN = '${clientToken}';`;
|
||
|
fs.writeFile('src/ts/generated/rollbarConfig.ts', lintCode(code), 'utf8', cb);
|
||
|
};
|
||
|
|
||
|
const assetsRev = cb => {
|
||
|
const json = fs.readFileSync('build/rev-manifest.json', 'utf8');
|
||
|
const data = _.mapValues(JSON.parse(json), value => value.replace(/^\S+-([a-f0-9]{10})\.\S+$/, '$1'));
|
||
|
const code = `export const REV: { [key: string]: string; } = ${JSON.stringify(data, null, 4)};`;
|
||
|
fs.writeFile('src/ts/generated/rev.ts', lintCode(code), 'utf8', cb);
|
||
|
};
|
||
|
|
||
|
const assetsCopy = () => gulp.src('assets/**/*')
|
||
|
.pipe(gulpif(!argv.fast, imagemin()))
|
||
|
.pipe(rev())
|
||
|
.pipe(gulp.dest('build/assets'))
|
||
|
.pipe(rev.manifest())
|
||
|
.pipe(gulp.dest('build'));
|
||
|
|
||
|
function buildSass(name, src, dest) {
|
||
|
const result = () => gulp.src([src], { base: 'src' })
|
||
|
.pipe(sass({
|
||
|
includePaths: ['src/styles/'],
|
||
|
}).on('error', sass.logError))
|
||
|
.pipe(autoprefixer('last 2 versions'))
|
||
|
.pipe(gulpif(!development, cssnano({ discardComments: { removeAll: true } })))
|
||
|
.pipe(gulpif(!development, rev()))
|
||
|
.pipe(gulp.dest(dest));
|
||
|
result.displayName = `sass (${name})`;
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
const sassMain = buildSass('main', 'src/styles/style.scss', 'build/assets');
|
||
|
const sassInline = buildSass('inline', 'src/styles/style-inline.scss', 'build/assets');
|
||
|
const sassTools = buildSass('tools', 'src/styles/style-tools.scss', 'build/assets');
|
||
|
const sassAdmin = buildSass('admin', 'src/styles/style-admin.scss', 'build/assets-admin');
|
||
|
const sassTasks = gulp.series(sassMain, sassInline, sassTools, sassAdmin);
|
||
|
|
||
|
const testScripts = ['src/scripts/tests/**/*.js'];
|
||
|
const ts = npmScript('ts');
|
||
|
|
||
|
const tests = () => gulp.src(testScripts, { read: false })
|
||
|
.pipe(mocha({
|
||
|
exit: true,
|
||
|
reporter: 'progress',
|
||
|
timeout: 10000,
|
||
|
}))
|
||
|
.on('error', swallowError);
|
||
|
|
||
|
const coverage = () => gulp.src(testScripts, { read: false })
|
||
|
.pipe(mocha({
|
||
|
exit: true,
|
||
|
reporter: 'progress',
|
||
|
timeout: 10000,
|
||
|
istanbul: {
|
||
|
print: 'none',
|
||
|
},
|
||
|
}));
|
||
|
|
||
|
const remap = () => gulp.src('coverage/coverage.json')
|
||
|
.pipe(remapIstanbul({ reports: { html: 'coverage-remapped' } }));
|
||
|
|
||
|
const size = () => gulp.src([
|
||
|
'build/assets/*.js',
|
||
|
'build/assets/scripts/*.js',
|
||
|
'build/assets-admin/scripts/*.js',
|
||
|
'build/assets/styles/*.css',
|
||
|
'build/assets-admin/styles/*.css',
|
||
|
]).pipe(sizereport({ gzip: true, total: true }));
|
||
|
|
||
|
const music = () => gulp.src(path.join(config.assetsPath, 'assets/music/*.wav'), { read: false })
|
||
|
.pipe(shell([
|
||
|
'ffmpeg -y -i "<%= file.path %>" -acodec libmp3lame "<%= out(file.path, ".mp3") %>"',
|
||
|
'ffmpeg -y -i "<%= file.path %>" -acodec libvorbis "<%= out(file.path, ".webm") %>"',
|
||
|
], {
|
||
|
templateData: {
|
||
|
out: (file, ext) => path.join('assets', 'music', path.basename(file, '.wav') + ext),
|
||
|
}
|
||
|
}));
|
||
|
|
||
|
const serverDev = cb => {
|
||
|
if (!argv.noserver) {
|
||
|
const serverPath = path.join('src', 'scripts', 'server', 'server.js');
|
||
|
const options = { env: { NODE_OPTIONS: '--inspect' } };
|
||
|
const commonArgs = [serverPath, '--inspect', '--color', '--beta', '--admin'];
|
||
|
const server = argv.adm ?
|
||
|
liveServer([...commonArgs, '--adm'], options) :
|
||
|
liveServer([...commonArgs, '--login', '--game', '--tools'], options);
|
||
|
server.start();
|
||
|
|
||
|
const restart = cb => {
|
||
|
server.start();
|
||
|
cb();
|
||
|
};
|
||
|
|
||
|
gulp.watch(['build/**/*.css']).on('change', path => server.notify({ path }));
|
||
|
gulp.watch(['build/**/*.js']).on('change', path => server.notify({ path }));
|
||
|
gulp.watch([
|
||
|
'src/scripts/common/**/*.js',
|
||
|
'src/scripts/generated/**/*.js',
|
||
|
'src/scripts/graphics/**/*.js',
|
||
|
'src/scripts/server/**/*.js',
|
||
|
'views/index.pug',
|
||
|
], { debounceDelay: 1000 }, restart);
|
||
|
}
|
||
|
|
||
|
cb();
|
||
|
};
|
||
|
|
||
|
let webpackScript = 'webpack-prod';
|
||
|
const webpackArgs = ['--'];
|
||
|
|
||
|
if (argv.parallel) {
|
||
|
webpackScript = 'webpack-prod-parallel';
|
||
|
}
|
||
|
|
||
|
if (argv.debug) {
|
||
|
webpackScript = 'webpack-debug';
|
||
|
}
|
||
|
|
||
|
if (argv.main) {
|
||
|
webpackScript = 'webpack-main';
|
||
|
}
|
||
|
|
||
|
if (argv.beta) {
|
||
|
webpackArgs.push('--env.beta');
|
||
|
}
|
||
|
|
||
|
if (argv.timing) {
|
||
|
webpackArgs.push('--env.timing');
|
||
|
}
|
||
|
|
||
|
const webpackProd = npmScript(webpackScript, webpackArgs);
|
||
|
const webpackAdmin = npmScript('webpack-admin');
|
||
|
const sw = npmScript('sw');
|
||
|
|
||
|
const assets = gulp.series(assetsCopy, assetsRev);
|
||
|
const common = gulp.series(manifest, hash, rollbar, changelog, icons, shaders, assets, sassTasks);
|
||
|
const covRemap = gulp.series(coverage, remap);
|
||
|
|
||
|
const watch = cb => {
|
||
|
gulp.watch(['CHANGELOG.md'], changelog);
|
||
|
gulp.watch(['src/ts/client/icons.ts'], icons);
|
||
|
gulp.watch(['src/styles/**/*.scss'], sassTasks);
|
||
|
gulp.watch(['src/ts/graphics/shaders/*.glsl'], shaders);
|
||
|
|
||
|
if (argv.coverage || argv.tests) {
|
||
|
gulp.watch(['src/scripts/**/*.js'], { debounceDelay: 1000 }, argv.coverage ? covRemap : tests);
|
||
|
}
|
||
|
|
||
|
cb();
|
||
|
};
|
||
|
|
||
|
const watchTools = cb => {
|
||
|
if (argv.sprites) {
|
||
|
// gulp.watch(['src/scripts/tools/**/*.js'], { debounceDelay: 1000 }, sprites);
|
||
|
gulp.watch(['src/ts/tools/trigger.txt'], sprites);
|
||
|
gulp.watch(['assets/**/*'], { debounceDelay: 1000, readDelay: 1000 }, assets);
|
||
|
// gulp.watch([path.join(config.assetsPath, 'assets/**/*')], { debounceDelay: 1000, readDelay: 1000 }, sprites);
|
||
|
}
|
||
|
|
||
|
cb();
|
||
|
};
|
||
|
|
||
|
const watchTests = cb => {
|
||
|
const task = argv.coverage ? covRemap : tests;
|
||
|
gulp.watch(['src/scripts/**/*.js', 'src/tests/**/*.txt', 'src/tests/**/*.png'], { debounceDelay: 1000 }, task);
|
||
|
cb();
|
||
|
};
|
||
|
|
||
|
const setProd = cb => {
|
||
|
development = false;
|
||
|
cb();
|
||
|
};
|
||
|
|
||
|
const empty = cb => cb();
|
||
|
const spritesTask = argv.sprites ? sprites : empty;
|
||
|
|
||
|
const build = gulp.series(clean, setProd, common, ts, webpackProd, sw, size);
|
||
|
const admin = gulp.series(clearnAdmin, setProd, sassAdmin, ts, webpackAdmin);
|
||
|
const dev = gulp.series(clean, spritesTask, common, gulp.parallel(serverDev, watch, watchTools));
|
||
|
|
||
|
module.exports = {
|
||
|
music,
|
||
|
admin,
|
||
|
build,
|
||
|
dev,
|
||
|
default: dev,
|
||
|
test: watchTests,
|
||
|
};
|