'use strict'; const os = require('os'); const async = require('async'); const winston = require('winston'); const nconf = require('nconf'); const _ = require('lodash'); const cacheBuster = require('./cacheBuster'); let meta; function step(target, callback) { var startTime = Date.now(); winston.info('[build] ' + target + ' build started'); return function (err) { if (err) { winston.error('[build] ' + target + ' build failed'); return callback(err); } var time = (Date.now() - startTime) / 1000; winston.info('[build] ' + target + ' build completed in ' + time + 'sec'); callback(); }; } var targetHandlers = { 'plugin static dirs': function (parallel, callback) { meta.js.linkStatics(callback); }, 'requirejs modules': function (parallel, callback) { meta.js.buildModules(parallel, callback); }, 'client js bundle': function (parallel, callback) { meta.js.buildBundle('client', parallel, callback); }, 'admin js bundle': function (parallel, callback) { meta.js.buildBundle('admin', parallel, callback); }, javascript: [ 'plugin static dirs', 'requirejs modules', 'client js bundle', 'admin js bundle', ], 'client side styles': function (parallel, callback) { meta.css.buildBundle('client', parallel, callback); }, 'admin control panel styles': function (parallel, callback) { meta.css.buildBundle('admin', parallel, callback); }, styles: [ 'client side styles', 'admin control panel styles', ], templates: function (parallel, callback) { meta.templates.compile(callback); }, languages: function (parallel, callback) { meta.languages.build(callback); }, sounds: function (parallel, callback) { meta.sounds.build(callback); }, }; var aliases = { 'plugin static dirs': ['staticdirs'], 'requirejs modules': ['rjs', 'modules'], 'client js bundle': ['clientjs', 'clientscript', 'clientscripts'], 'admin js bundle': ['adminjs', 'adminscript', 'adminscripts'], javascript: ['js'], 'client side styles': [ 'clientcss', 'clientless', 'clientstyles', 'clientstyle', ], 'admin control panel styles': [ 'admincss', 'adminless', 'adminstyles', 'adminstyle', 'acpcss', 'acpless', 'acpstyles', 'acpstyle', ], styles: ['css', 'less', 'style'], templates: ['tpl'], languages: ['lang', 'i18n'], sounds: ['sound'], }; exports.aliases = aliases; aliases = Object.keys(aliases).reduce(function (prev, key) { var arr = aliases[key]; arr.forEach(function (alias) { prev[alias] = key; }); prev[key] = key; return prev; }, {}); function beforeBuild(targets, callback) { var db = require('../database'); require('colors'); process.stdout.write(' started'.green + '\n'.reset); async.series([ function (next) { db.init(next); }, function (next) { meta = require('../meta'); meta.themes.setupPaths(next); }, function (next) { var plugins = require('../plugins'); plugins.prepareForBuild(targets, next); }, ], function (err) { if (err) { winston.error('[build] Encountered error preparing for build', err); return callback(err); } callback(); }); } var allTargets = Object.keys(targetHandlers).filter(function (name) { return typeof targetHandlers[name] === 'function'; }); function buildTargets(targets, parallel, callback) { var all = parallel ? async.each : async.eachSeries; var length = Math.max.apply(Math, targets.map(function (name) { return name.length; })); all(targets, function (target, next) { targetHandlers[target](parallel, step(_.padStart(target, length) + ' ', next)); }, callback); } exports.build = function (targets, options, callback) { if (!callback && typeof options === 'function') { callback = options; options = {}; } else if (!options) { options = {}; } if (targets === true) { targets = allTargets; } else if (!Array.isArray(targets)) { targets = targets.split(','); } let series = nconf.get('series') || options.series; if (series === undefined) { // Detect # of CPUs and select strategy as appropriate winston.verbose('[build] Querying CPU core count for build strategy'); const cpus = os.cpus(); series = cpus.length < 4; winston.verbose('[build] System returned ' + cpus.length + ' cores, opting for ' + (series ? 'series' : 'parallel') + ' build strategy'); } targets = targets // get full target name .map(function (target) { target = target.toLowerCase().replace(/-/g, ''); if (!aliases[target]) { winston.warn('[build] Unknown target: ' + target); if (target.includes(',')) { winston.warn('[build] Are you specifying multiple targets? Separate them with spaces:'); winston.warn('[build] e.g. `./nodebb build adminjs tpl`'); } return false; } return aliases[target]; }) // filter nonexistent targets .filter(Boolean); // map multitargets to their sets targets = _.uniq(_.flatMap(targets, target => ( Array.isArray(targetHandlers[target]) ? targetHandlers[target] : target ))); winston.verbose('[build] building the following targets: ' + targets.join(', ')); if (!targets) { winston.info('[build] No valid targets supplied. Aborting.'); callback(); } var startTime; var totalTime; async.series([ async.apply(beforeBuild, targets), function (next) { var threads = parseInt(nconf.get('threads'), 10); if (threads) { require('./minifier').maxThreads = threads - 1; } if (!series) { winston.info('[build] Building in parallel mode'); } else { winston.info('[build] Building in series mode'); } startTime = Date.now(); buildTargets(targets, !series, next); }, function (next) { totalTime = (Date.now() - startTime) / 1000; cacheBuster.write(next); }, ], function (err) { if (err) { winston.error('[build] Encountered error during build step', err); return callback(err); } winston.info('[build] Asset compilation successful. Completed in ' + totalTime + 'sec.'); callback(); }); }; exports.buildAll = function (callback) { exports.build(allTargets, callback); }; require('../promisify')(exports);