mirror of
https://github.com/Rushilwiz/spaceout.git
synced 2025-04-21 20:09:50 -04:00
281 lines
10 KiB
JavaScript
281 lines
10 KiB
JavaScript
const { packageExists } = require('./utils/package-exists');
|
|
const webpack = packageExists('webpack') ? require('webpack') : undefined;
|
|
const logger = require('./utils/logger');
|
|
const webpackMerge = require('webpack-merge');
|
|
const { core, coreFlagMap } = require('./utils/cli-flags');
|
|
const argParser = require('./utils/arg-parser');
|
|
const { outputStrategy } = require('./utils/merge-strategies');
|
|
const assignFlagDefaults = require('./utils/flag-defaults');
|
|
const { writeFileSync } = require('fs');
|
|
const { options: coloretteOptions } = require('colorette');
|
|
const WebpackCLIPlugin = require('./plugins/WebpackCLIPlugin');
|
|
|
|
// CLI arg resolvers
|
|
const handleConfigResolution = require('./groups/resolveConfig');
|
|
const resolveMode = require('./groups/resolveMode');
|
|
const resolveStats = require('./groups/resolveStats');
|
|
const resolveOutput = require('./groups/resolveOutput');
|
|
const basicResolver = require('./groups/basicResolver');
|
|
const resolveAdvanced = require('./groups/resolveAdvanced');
|
|
const { toKebabCase } = require('./utils/helpers');
|
|
|
|
class WebpackCLI {
|
|
constructor() {
|
|
this.compilerConfiguration = {};
|
|
this.outputConfiguration = {};
|
|
}
|
|
|
|
/**
|
|
* Responsible for handling flags coming from webpack/webpack
|
|
* @private\
|
|
* @returns {void}
|
|
*/
|
|
_handleCoreFlags(parsedArgs) {
|
|
const coreCliHelper = require('webpack').cli;
|
|
if (!coreCliHelper) return;
|
|
const coreConfig = Object.keys(parsedArgs)
|
|
.filter((arg) => {
|
|
return coreFlagMap.has(toKebabCase(arg));
|
|
})
|
|
.reduce((acc, cur) => {
|
|
acc[toKebabCase(cur)] = parsedArgs[cur];
|
|
return acc;
|
|
}, {});
|
|
const coreCliArgs = coreCliHelper.getArguments();
|
|
// Merge the core flag config with the compilerConfiguration
|
|
coreCliHelper.processArguments(coreCliArgs, this.compilerConfiguration, coreConfig);
|
|
// Assign some defaults to core flags
|
|
const configWithDefaults = assignFlagDefaults(this.compilerConfiguration, parsedArgs, this.outputConfiguration);
|
|
this._mergeOptionsToConfiguration(configWithDefaults);
|
|
}
|
|
|
|
async _baseResolver(cb, parsedArgs, strategy) {
|
|
const resolvedConfig = await cb(parsedArgs, this.compilerConfiguration);
|
|
this._mergeOptionsToConfiguration(resolvedConfig.options, strategy);
|
|
this._mergeOptionsToOutputConfiguration(resolvedConfig.outputOptions);
|
|
}
|
|
|
|
/**
|
|
* Expose commander argParser
|
|
* @param {...any} args args for argParser
|
|
*/
|
|
argParser(...args) {
|
|
return argParser(...args);
|
|
}
|
|
|
|
getCoreFlags() {
|
|
return core;
|
|
}
|
|
|
|
/**
|
|
* Responsible to override webpack options.
|
|
* @param {Object} options The options returned by a group helper
|
|
* @param {Object} strategy The strategy to pass to webpack-merge. The strategy
|
|
* is implemented inside the group helper
|
|
* @private
|
|
* @returns {void}
|
|
*/
|
|
_mergeOptionsToConfiguration(options, strategy) {
|
|
/**
|
|
* options where they differ per config use this method to apply relevant option to relevant config
|
|
* eg mode flag applies per config
|
|
*/
|
|
if (Array.isArray(options) && Array.isArray(this.compilerConfiguration)) {
|
|
this.compilerConfiguration = options.map((option, index) => {
|
|
const compilerConfig = this.compilerConfiguration[index];
|
|
if (strategy) {
|
|
return webpackMerge.strategy(strategy)(compilerConfig, option);
|
|
}
|
|
return webpackMerge(compilerConfig, option);
|
|
});
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* options is an array (multiple configuration) so we create a new
|
|
* configuration where each element is individually merged
|
|
*/
|
|
if (Array.isArray(options)) {
|
|
this.compilerConfiguration = options.map((configuration) => {
|
|
if (strategy) {
|
|
return webpackMerge.strategy(strategy)(this.compilerConfiguration, configuration);
|
|
}
|
|
return webpackMerge(this.compilerConfiguration, configuration);
|
|
});
|
|
} else {
|
|
/**
|
|
* The compiler configuration is already an array, so for each element
|
|
* we merge the options
|
|
*/
|
|
if (Array.isArray(this.compilerConfiguration)) {
|
|
this.compilerConfiguration = this.compilerConfiguration.map((thisConfiguration) => {
|
|
if (strategy) {
|
|
return webpackMerge.strategy(strategy)(thisConfiguration, options);
|
|
}
|
|
return webpackMerge(thisConfiguration, options);
|
|
});
|
|
} else {
|
|
if (strategy) {
|
|
this.compilerConfiguration = webpackMerge.strategy(strategy)(this.compilerConfiguration, options);
|
|
} else {
|
|
this.compilerConfiguration = webpackMerge(this.compilerConfiguration, options);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Responsible for creating and updating the new output configuration
|
|
*
|
|
* @param {Object} options Output options emitted by the group helper
|
|
* @private
|
|
* @returns {void}
|
|
*/
|
|
_mergeOptionsToOutputConfiguration(options) {
|
|
if (options) {
|
|
this.outputConfiguration = Object.assign(this.outputConfiguration, options);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* It runs in a fancy order all the expected groups.
|
|
* Zero config and configuration goes first.
|
|
*
|
|
* The next groups will override existing parameters
|
|
* @returns {Promise<void>} A Promise
|
|
*/
|
|
async runOptionGroups(parsedArgs) {
|
|
await Promise.resolve()
|
|
.then(() => this._baseResolver(handleConfigResolution, parsedArgs))
|
|
.then(() => this._baseResolver(resolveMode, parsedArgs))
|
|
.then(() => this._baseResolver(resolveOutput, parsedArgs, outputStrategy))
|
|
.then(() => this._handleCoreFlags(parsedArgs))
|
|
.then(() => this._baseResolver(basicResolver, parsedArgs))
|
|
.then(() => this._baseResolver(resolveAdvanced, parsedArgs))
|
|
.then(() => this._baseResolver(resolveStats, parsedArgs));
|
|
}
|
|
|
|
handleError(error) {
|
|
// https://github.com/webpack/webpack/blob/master/lib/index.js#L267
|
|
// https://github.com/webpack/webpack/blob/v4.44.2/lib/webpack.js#L90
|
|
const ValidationError = webpack.ValidationError || webpack.WebpackOptionsValidationError;
|
|
|
|
// In case of schema errors print and exit process
|
|
// For webpack@4 and webpack@5
|
|
if (error instanceof ValidationError) {
|
|
logger.error(error.message);
|
|
} else {
|
|
logger.error(error);
|
|
}
|
|
}
|
|
|
|
createCompiler(options, callback) {
|
|
let compiler;
|
|
|
|
try {
|
|
compiler = webpack(options, callback);
|
|
} catch (error) {
|
|
this.handleError(error);
|
|
process.exit(2);
|
|
}
|
|
|
|
return compiler;
|
|
}
|
|
|
|
async getCompiler(args) {
|
|
await this.runOptionGroups(args);
|
|
return this.createCompiler(this.compilerConfiguration);
|
|
}
|
|
|
|
async run(args) {
|
|
await this.runOptionGroups(args);
|
|
|
|
let compiler;
|
|
|
|
let options = this.compilerConfiguration;
|
|
let outputOptions = this.outputConfiguration;
|
|
|
|
const isRawOutput = typeof outputOptions.json === 'undefined';
|
|
|
|
if (isRawOutput) {
|
|
const webpackCLIPlugin = new WebpackCLIPlugin({
|
|
progress: outputOptions.progress,
|
|
});
|
|
|
|
const addPlugin = (options) => {
|
|
if (!options.plugins) {
|
|
options.plugins = [];
|
|
}
|
|
options.plugins.unshift(webpackCLIPlugin);
|
|
};
|
|
if (Array.isArray(options)) {
|
|
options.forEach(addPlugin);
|
|
} else {
|
|
addPlugin(options);
|
|
}
|
|
}
|
|
|
|
const callback = (error, stats) => {
|
|
if (error) {
|
|
this.handleError(error);
|
|
process.exit(2);
|
|
}
|
|
|
|
if (stats.hasErrors()) {
|
|
process.exitCode = 1;
|
|
}
|
|
|
|
const getStatsOptions = (stats) => {
|
|
// TODO remove after drop webpack@4
|
|
if (webpack.Stats && webpack.Stats.presetToOptions) {
|
|
if (!stats) {
|
|
stats = {};
|
|
} else if (typeof stats === 'boolean' || typeof stats === 'string') {
|
|
stats = webpack.Stats.presetToOptions(stats);
|
|
}
|
|
}
|
|
|
|
stats.colors = typeof stats.colors !== 'undefined' ? stats.colors : coloretteOptions.enabled;
|
|
|
|
return stats;
|
|
};
|
|
|
|
const getStatsOptionsFromCompiler = (compiler) => getStatsOptions(compiler.options ? compiler.options.stats : undefined);
|
|
|
|
const foundStats = compiler.compilers
|
|
? { children: compiler.compilers.map(getStatsOptionsFromCompiler) }
|
|
: getStatsOptionsFromCompiler(compiler);
|
|
|
|
if (outputOptions.json === true) {
|
|
process.stdout.write(JSON.stringify(stats.toJson(foundStats), null, 2) + '\n');
|
|
} else if (typeof outputOptions.json === 'string') {
|
|
const JSONStats = JSON.stringify(stats.toJson(foundStats), null, 2);
|
|
|
|
try {
|
|
writeFileSync(outputOptions.json, JSONStats);
|
|
|
|
logger.success(`stats are successfully stored as json to ${outputOptions.json}`);
|
|
} catch (error) {
|
|
logger.error(error);
|
|
|
|
process.exit(2);
|
|
}
|
|
} else {
|
|
logger.raw(`${stats.toString(foundStats)}`);
|
|
}
|
|
};
|
|
|
|
compiler = this.createCompiler(options, callback);
|
|
|
|
if (compiler && outputOptions.interactive) {
|
|
const interactive = require('./utils/interactive');
|
|
|
|
interactive(compiler, options, outputOptions);
|
|
}
|
|
|
|
return Promise.resolve();
|
|
}
|
|
}
|
|
|
|
module.exports = WebpackCLI;
|