Last commit july 5th

This commit is contained in:
2024-07-05 13:46:23 +02:00
parent dad0d86e8c
commit b0e4dfbb76
24982 changed files with 2621219 additions and 413 deletions

19
spa/node_modules/@symfony/webpack-encore/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2017-2021 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

27
spa/node_modules/@symfony/webpack-encore/README.md generated vendored Normal file
View File

@@ -0,0 +1,27 @@
# Webpack Encore: A Simple & Powerful Webpack API
[![Build Status](https://travis-ci.org/symfony/webpack-encore.svg?branch=main)](https://travis-ci.org/symfony/webpack-encore)
[![NPM Version](https://badge.fury.io/js/%40symfony%2Fwebpack-encore.svg)](https://badge.fury.io/js/%40symfony%2Fwebpack-encore)
![Node](https://img.shields.io/node/v/@symfony/webpack-encore.svg)
Webpack Encore is a simpler way to integrate [Webpack](https://webpack.js.org/) into your
application. It *wraps* Webpack, giving you a clean & powerful API
for bundling JavaScript modules, pre-processing CSS & JS and compiling
and minifying assets. Encore gives you a professional asset system
that's a *delight* to use.
> [!TIP]
> Symfony released an [AssetMapper](https://symfony.com/doc/current/frontend/asset_mapper.html) component, a production-ready simpler alternative to Webpack Encore
> that runs entirely in PHP.
Encore is inspired by [Webpacker](https://github.com/rails/webpacker) and
[Mix](https://laravel.com/docs/mix), but stays in the spirit of
Webpack: using its features, concepts and naming conventions for a familiar
feel. It aims to solve the most common Webpack use cases.
> Encore is made by Symfony and works *beautifully* in Symfony applications.
> But it can easily be used in any application... in any language!
## Documentation
[Read the Documentation on symfony.com](https://symfony.com/doc/current/frontend.html).

97
spa/node_modules/@symfony/webpack-encore/bin/encore.js generated vendored Executable file
View File

@@ -0,0 +1,97 @@
#!/usr/bin/env node
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const parseRuntime = require('../lib/config/parse-runtime');
const context = require('../lib/context');
const chalk = require('chalk');
const logger = require('../lib/logger');
const runtimeConfig = parseRuntime(
require('yargs-parser')(process.argv.slice(2)),
process.cwd()
);
context.runtimeConfig = runtimeConfig;
// prevent logs from being dumped
if (runtimeConfig.outputJson) {
logger.quiet();
}
// remove the command from the input
process.argv.splice(2, 1);
// remove arguments not supported by webpack/-dev-server
const encoreOnlyArguments = new Set(['--keep-public-path']);
process.argv = process.argv.filter(arg => !encoreOnlyArguments.has(arg));
// remove argument --public not supported by webpack/dev-server
const indexPublicArgument = process.argv.indexOf('--public');
if (indexPublicArgument !== -1) {
process.argv.splice(indexPublicArgument, 2);
}
if (!runtimeConfig.isValidCommand) {
if (runtimeConfig.command) {
console.log(chalk.bgRed.white(`Invalid command "${runtimeConfig.command}"`));
console.log();
}
showUsageInstructions();
process.exit(1); // eslint-disable-line no-process-exit
}
if (runtimeConfig.helpRequested) {
showUsageInstructions();
// allow it to continue to the help command of webpack
}
if (runtimeConfig.useDevServer) {
console.log('Running webpack-dev-server ...');
console.log();
return require('webpack-dev-server/bin/webpack-dev-server');
} else {
if (!runtimeConfig.outputJson) {
console.log('Running webpack ...');
console.log();
}
return require('webpack/bin/webpack');
}
function showUsageInstructions() {
const validCommands = ['dev', 'prod', 'production', 'dev-server'];
console.log(`usage ${chalk.green('encore')} [${ validCommands.map(command => chalk.green(command)).join('|') }]`);
console.log();
console.log('encore is a thin executable around the webpack or webpack-dev-server executables');
console.log();
console.log('Commands:');
console.log(` ${chalk.green('dev')} : runs webpack for development`);
console.log(' - Supports any webpack options (e.g. --watch)');
console.log();
console.log(` ${chalk.green('dev-server')} : runs webpack-dev-server`);
console.log(` - ${chalk.yellow('--host')} The hostname/ip address the webpack-dev-server will bind to`);
console.log(` - ${chalk.yellow('--port')} The port the webpack-dev-server will bind to`);
console.log(` - ${chalk.yellow('--keep-public-path')} Do not change the public path (it is usually prefixed by the dev server URL)`);
console.log(` - ${chalk.yellow('--public')} The public url for entry asset in entrypoints.json`);
console.log(' - Supports any webpack-dev-server options');
console.log();
console.log(` ${chalk.green('production')} : runs webpack for production`);
console.log(' - Supports any webpack options (e.g. --watch)');
console.log();
console.log(chalk.yellow(' encore dev --watch'));
console.log(chalk.yellow(' encore dev-server'));
console.log(chalk.yellow(' encore production'));
console.log();
}

1749
spa/node_modules/@symfony/webpack-encore/index.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,102 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const chalk = require('chalk');
const levenshtein = require('fast-levenshtein');
const prettyError = require('./utils/pretty-error');
module.exports = {
createProxy: (Encore) => {
const EncoreProxy = new Proxy(Encore, {
get: (target, prop) => {
if (typeof prop !== 'string') {
// Only care about strings there since prop
// could also be a number or a symbol
return target[prop];
}
if (prop === '__esModule') {
// When using Babel to preprocess a webpack.config.babel.js file
// (for instance if we want to use ES6 syntax) the __esModule
// property needs to be whitelisted to avoid an "Unknown property"
// error.
return target[prop];
}
if (typeof target[prop] === 'function') {
// These methods of the public API can be called even if the
// webpackConfig object hasn't been initialized yet.
const safeMethods = [
'configureRuntimeEnvironment',
'clearRuntimeEnvironment',
'isRuntimeEnvironmentConfigured',
];
if (!Encore.isRuntimeEnvironmentConfigured() && !safeMethods.includes(prop)) {
throw new Error(`Encore.${prop}() cannot be called yet because the runtime environment doesn't appear to be configured. Make sure you're using the encore executable or call Encore.configureRuntimeEnvironment() first if you're purposely not calling Encore directly.`);
}
// Either a safe method has been called or the webpackConfig
// object is already available. In this case act as a passthrough.
return (...parameters) => {
try {
const res = target[prop](...parameters);
return (res === target) ? EncoreProxy : res;
} catch (error) {
prettyError(error);
process.exit(1); // eslint-disable-line
}
};
}
if (typeof target[prop] === 'undefined') {
// Find the property with the closest Levenshtein distance
let similarProperty;
let minDistance = Number.MAX_VALUE;
const encorePrototype = Object.getPrototypeOf(Encore);
for (const apiProperty of Object.getOwnPropertyNames(encorePrototype)) {
// Ignore class constructor
if (apiProperty === 'constructor') {
continue;
}
const distance = levenshtein.get(apiProperty, prop);
if (distance <= minDistance) {
similarProperty = apiProperty;
minDistance = distance;
}
}
let errorMessage = `${chalk.red(`Encore.${prop}`)} is not a recognized property or method.`;
if (minDistance < (prop.length / 3)) {
errorMessage += ` Did you mean ${chalk.green(`Encore.${similarProperty}`)}?`;
}
// Prettify the error message.
// Only keep the 2nd line of the stack trace:
// - First line should be the index.js file
// - Second line should be the Webpack config file
prettyError(
new Error(errorMessage),
{ skipTrace: (traceLine, lineNumber) => lineNumber !== 1 }
);
process.exit(1); // eslint-disable-line
}
return target[prop];
}
});
return EncoreProxy;
}
};

View File

@@ -0,0 +1,975 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const path = require('path');
const fs = require('fs');
const crypto = require('crypto');
const RuntimeConfig = require('./config/RuntimeConfig'); //eslint-disable-line no-unused-vars
const logger = require('./logger');
const regexpEscaper = require('./utils/regexp-escaper');
const { calculateDevServerUrl } = require('./config/path-util');
/**
* @param {RuntimeConfig|null} runtimeConfig
* @return {void}
*/
function validateRuntimeConfig(runtimeConfig) {
// if you're using the encore executable, these things should never happen
if (null === runtimeConfig) {
throw new Error('RuntimeConfig must be initialized');
}
if (null === runtimeConfig.context) {
throw new Error('RuntimeConfig.context must be set.');
}
if (null === runtimeConfig.babelRcFileExists) {
throw new Error('RuntimeConfig.babelRcFileExists must be set.');
}
}
class WebpackConfig {
constructor(runtimeConfig) {
validateRuntimeConfig(runtimeConfig);
if (runtimeConfig.verbose) {
logger.verbose();
}
this.runtimeConfig = runtimeConfig;
this.entries = new Map();
this.styleEntries = new Map();
this.plugins = [];
this.loaders = [];
// Global settings
this.outputPath = null;
this.publicPath = null;
this.manifestKeyPrefix = null;
this.cacheGroups = {};
this.providedVariables = {};
this.configuredFilenames = {};
this.aliases = {};
this.externals = [];
this.integrityAlgorithms = [];
this.shouldUseSingleRuntimeChunk = null;
this.shouldSplitEntryChunks = false;
// Features/Loaders flags
this.useVersioning = false;
this.useSourceMaps = false;
this.cleanupOutput = false;
this.usePersistentCache = false;
this.extractCss = true;
this.imageRuleOptions = {
type: 'asset/resource',
maxSize: null,
filename: 'images/[name].[hash:8][ext]',
enabled: true,
};
this.fontRuleOptions = {
type: 'asset/resource',
maxSize: null,
filename: 'fonts/[name].[hash:8][ext]',
enabled: true,
};
this.usePostCssLoader = false;
this.useLessLoader = false;
this.useStylusLoader = false;
this.useSassLoader = false;
this.useStimulusBridge = false;
this.useReact = false;
this.usePreact = false;
this.useVueLoader = false;
this.useEslintPlugin = false;
this.useTypeScriptLoader = false;
this.useForkedTypeScriptTypeChecking = false;
this.useBabelTypeScriptPreset = false;
this.useWebpackNotifier = false;
this.useHandlebarsLoader = false;
this.useSvelte = false;
// Features/Loaders options
this.copyFilesConfigs = [];
this.sassOptions = {
resolveUrlLoader: true,
resolveUrlLoaderOptions: {}
};
this.preactOptions = {
preactCompat: false
};
this.babelOptions = {
exclude: /(node_modules|bower_components)/,
useBuiltIns: false,
corejs: null,
};
this.babelTypeScriptPresetOptions = {};
this.vueOptions = {
useJsx: false,
version: null,
runtimeCompilerBuild: null
};
this.persistentCacheBuildDependencies = {};
// Features/Loaders options callbacks
this.imageRuleCallback = () => {};
this.fontRuleCallback = () => {};
this.postCssLoaderOptionsCallback = () => {};
this.sassLoaderOptionsCallback = () => {};
this.lessLoaderOptionsCallback = () => {};
this.stylusLoaderOptionsCallback = () => {};
this.babelConfigurationCallback = () => {};
this.babelPresetEnvOptionsCallback = () => {};
this.cssLoaderConfigurationCallback = () => {};
this.styleLoaderConfigurationCallback = () => {};
this.splitChunksConfigurationCallback = () => {};
this.watchOptionsConfigurationCallback = () => {};
this.devServerOptionsConfigurationCallback = () => {};
this.vueLoaderOptionsCallback = () => {};
this.eslintPluginOptionsCallback = () => {};
this.tsConfigurationCallback = () => {};
this.handlebarsConfigurationCallback = () => {};
this.miniCssExtractLoaderConfigurationCallback = () => {};
this.miniCssExtractPluginConfigurationCallback = () => {};
this.loaderConfigurationCallbacks = {
javascript: () => {},
css: () => {},
images: () => {},
fonts: () => {},
sass: () => {},
less: () => {},
stylus: () => {},
vue: () => {},
eslint: () => {},
typescript: () => {},
handlebars: () => {},
svelte: () => {},
};
// Plugins options
this.cleanWebpackPluginPaths = ['**/*'];
// Plugins callbacks
this.cleanWebpackPluginOptionsCallback = () => {};
this.definePluginOptionsCallback = () => {};
this.forkedTypeScriptTypesCheckOptionsCallback = () => {};
this.friendlyErrorsPluginOptionsCallback = () => {};
this.manifestPluginOptionsCallback = () => {};
this.terserPluginOptionsCallback = () => {};
this.cssMinimizerPluginOptionsCallback = () => {};
this.notifierPluginOptionsCallback = () => {};
this.persistentCacheCallback = () => {};
}
getContext() {
return this.runtimeConfig.context;
}
doesBabelRcFileExist() {
return this.runtimeConfig.babelRcFileExists;
}
setOutputPath(outputPath) {
if (!path.isAbsolute(outputPath)) {
outputPath = path.resolve(this.getContext(), outputPath);
}
if (!fs.existsSync(outputPath)) {
// If the parent of the output directory does not exist either
// check if it is located under the context directory before
// creating it and its parent.
const parentPath = path.dirname(outputPath);
if (!fs.existsSync(parentPath)) {
const context = path.resolve(this.getContext());
if (outputPath.indexOf(context) !== 0) {
throw new Error(`outputPath directory "${outputPath}" does not exist and is not located under the context directory "${context}". Please check the path you're passing to setOutputPath() or create this directory.`);
}
parentPath.split(path.sep).reduce((previousPath, directory) => {
const newPath = path.resolve(previousPath, directory);
if (!fs.existsSync(newPath)) {
fs.mkdirSync(newPath);
}
return newPath;
}, path.sep);
}
fs.mkdirSync(outputPath);
}
this.outputPath = outputPath;
}
setPublicPath(publicPath) {
if (publicPath.includes('://') === false && publicPath.indexOf('/') !== 0) {
// technically, not starting with "/" is legal, but not
// what you want in most cases. Let's warn the user that
// they might be making a mistake.
logger.warning('The value passed to setPublicPath() should *usually* start with "/" or be a full URL (http://...). If you\'re not sure, then you should probably change your public path and make this message disappear.');
}
// guarantee a single trailing slash
publicPath = publicPath.replace(/\/$/,'');
publicPath = publicPath + '/';
this.publicPath = publicPath;
}
setManifestKeyPrefix(manifestKeyPrefix) {
/*
* Normally, we make sure that the manifest keys don't start
* with an opening "/" ever... for consistency. If you need
* to manually specify the manifest key (e.g. because you're
* publicPath is absolute), it's easy to accidentally add
* an opening slash (thereby changing your key prefix) without
* intending to. Hence, the warning.
*/
if (manifestKeyPrefix.indexOf('/') === 0) {
logger.warning(`The value passed to setManifestKeyPrefix "${manifestKeyPrefix}" starts with "/". This is allowed, but since the key prefix does not normally start with a "/", you may have just changed the prefix accidentally.`);
}
// guarantee a single trailing slash, except for blank strings
if (manifestKeyPrefix !== '') {
manifestKeyPrefix = manifestKeyPrefix.replace(/\/$/, '');
manifestKeyPrefix = manifestKeyPrefix + '/';
}
this.manifestKeyPrefix = manifestKeyPrefix;
}
configureDefinePlugin(definePluginOptionsCallback = () => {}) {
if (typeof definePluginOptionsCallback !== 'function') {
throw new Error('Argument 1 to configureDefinePlugin() must be a callback function');
}
this.definePluginOptionsCallback = definePluginOptionsCallback;
}
configureFriendlyErrorsPlugin(friendlyErrorsPluginOptionsCallback = () => {}) {
if (typeof friendlyErrorsPluginOptionsCallback !== 'function') {
throw new Error('Argument 1 to configureFriendlyErrorsPlugin() must be a callback function');
}
this.friendlyErrorsPluginOptionsCallback = friendlyErrorsPluginOptionsCallback;
}
configureManifestPlugin(manifestPluginOptionsCallback = () => {}) {
if (typeof manifestPluginOptionsCallback !== 'function') {
throw new Error('Argument 1 to configureManifestPlugin() must be a callback function');
}
this.manifestPluginOptionsCallback = manifestPluginOptionsCallback;
}
configureTerserPlugin(terserPluginOptionsCallback = () => {}) {
if (typeof terserPluginOptionsCallback !== 'function') {
throw new Error('Argument 1 to configureTerserPlugin() must be a callback function');
}
this.terserPluginOptionsCallback = terserPluginOptionsCallback;
}
configureCssMinimizerPlugin(cssMinimizerPluginOptionsCallback = () => {}) {
if (typeof cssMinimizerPluginOptionsCallback !== 'function') {
throw new Error('Argument 1 to configureCssMinimizerPlugin() must be a callback function');
}
this.cssMinimizerPluginOptionsCallback = cssMinimizerPluginOptionsCallback;
}
/**
* Returns the value that should be used as the publicPath,
* which can be overridden by enabling the webpackDevServer
*
* @returns {string}
*/
getRealPublicPath() {
if (!this.useDevServer()) {
return this.publicPath;
}
if (this.runtimeConfig.devServerKeepPublicPath) {
return this.publicPath;
}
if (this.publicPath.includes('://')) {
return this.publicPath;
}
const devServerUrl = calculateDevServerUrl(this.runtimeConfig);
// if using dev-server, prefix the publicPath with the dev server URL
return devServerUrl.replace(/\/$/,'') + this.publicPath;
}
addEntry(name, src) {
this.validateNameIsNewEntry(name);
this.entries.set(name, src);
}
/**
* Provide a has of entries at once, as an alternative to calling `addEntry` several times.
*
* @param {Object.<string, string|string[]>} entries
* @returns {Void}
*/
addEntries(entries = {}) {
if (typeof entries !== 'object') {
throw new Error('Argument 1 to addEntries() must be an object.');
}
Object.entries(entries).forEach((entry) => this.addEntry(entry[0], entry[1]));
}
addStyleEntry(name, src) {
this.validateNameIsNewEntry(name);
this.styleEntries.set(name, src);
}
addPlugin(plugin, priority = 0) {
if (typeof priority !== 'number') {
throw new Error('Argument 2 to addPlugin() must be a number.');
}
this.plugins.push({
plugin: plugin,
priority: priority
});
}
addLoader(loader) {
this.loaders.push(loader);
}
addAliases(aliases = {}) {
if (typeof aliases !== 'object') {
throw new Error('Argument 1 to addAliases() must be an object.');
}
Object.assign(this.aliases, aliases);
}
addExternals(externals = []) {
if (!Array.isArray(externals)) {
externals = [externals];
}
this.externals = this.externals.concat(externals);
}
enableVersioning(enabled = true) {
this.useVersioning = enabled;
}
enableSourceMaps(enabled = true) {
this.useSourceMaps = enabled;
}
configureBabel(callback, options = {}) {
if (callback) {
if (typeof callback !== 'function') {
throw new Error('Argument 1 to configureBabel() must be a callback function or null.');
}
if (this.doesBabelRcFileExist()) {
throw new Error('The "callback" argument of configureBabel() will not be used because your app already provides an external Babel configuration (e.g. a ".babelrc" or "babel.config.js" file or "babel" key in "package.json"). Use null as the first argument to remove this error.');
}
}
this.babelConfigurationCallback = callback || (() => {});
// Whitelist some options that can be used even if there
// is an external Babel config. The other ones won't be
// applied and a warning message will be displayed instead.
const allowedOptionsWithExternalConfig = ['includeNodeModules', 'exclude'];
for (const optionKey of Object.keys(options)) {
if (this.doesBabelRcFileExist() && !allowedOptionsWithExternalConfig.includes(optionKey)) {
logger.warning(`The "${optionKey}" option of configureBabel() will not be used because your app already provides an external Babel configuration (e.g. a ".babelrc" or "babelrc.config.js" file or "babel" key in "package.json").`);
continue;
}
if (optionKey === 'includeNodeModules') {
if (Object.keys(options).includes('exclude')) {
throw new Error('"includeNodeModules" and "exclude" options can\'t be used together when calling configureBabel().');
}
if (!Array.isArray(options[optionKey])) {
throw new Error('Option "includeNodeModules" passed to configureBabel() must be an Array.');
}
this.babelOptions['exclude'] = (filePath) => {
// Don't exclude modules outside of node_modules/bower_components
if (!/(node_modules|bower_components)/.test(filePath)) {
return false;
}
// Don't exclude whitelisted Node modules
const whitelistedModules = options[optionKey].map(
module => path.join('node_modules', module) + path.sep
);
for (const modulePath of whitelistedModules) {
if (filePath.includes(modulePath)) {
return false;
}
}
// Exclude other modules
return true;
};
} else if (!(optionKey in this.babelOptions)) {
throw new Error(`Invalid option "${optionKey}" passed to configureBabel(). Valid keys are ${[...Object.keys(this.babelOptions), 'includeNodeModules'].join(', ')}`);
} else {
this.babelOptions[optionKey] = options[optionKey];
}
}
}
configureBabelPresetEnv(callback) {
if (typeof callback !== 'function') {
throw new Error('Argument 1 to configureBabelPresetEnv() must be a callback function.');
}
if (this.doesBabelRcFileExist()) {
throw new Error('The "callback" argument of configureBabelPresetEnv() will not be used because your app already provides an external Babel configuration (e.g. a ".babelrc" or "babel.config.js" file or "babel" key in "package.json").');
}
this.babelPresetEnvOptionsCallback = callback;
}
configureCssLoader(callback) {
if (typeof callback !== 'function') {
throw new Error('Argument 1 to configureCssLoader() must be a callback function.');
}
this.cssLoaderConfigurationCallback = callback;
}
configureStyleLoader(callback) {
if (typeof callback !== 'function') {
throw new Error('Argument 1 to configureStyleLoader() must be a callback function.');
}
this.styleLoaderConfigurationCallback = callback;
}
configureMiniCssExtractPlugin(loaderOptionsCallback, pluginOptionsCallback = () => {}) {
if (typeof loaderOptionsCallback !== 'function') {
throw new Error('Argument 1 to configureMiniCssExtractPluginLoader() must be a callback function.');
}
if (typeof pluginOptionsCallback !== 'function') {
throw new Error('Argument 2 to configureMiniCssExtractPluginLoader() must be a callback function.');
}
this.miniCssExtractLoaderConfigurationCallback = loaderOptionsCallback;
this.miniCssExtractPluginConfigurationCallback = pluginOptionsCallback;
}
enableSingleRuntimeChunk() {
this.shouldUseSingleRuntimeChunk = true;
}
disableSingleRuntimeChunk() {
this.shouldUseSingleRuntimeChunk = false;
}
splitEntryChunks() {
this.shouldSplitEntryChunks = true;
}
configureSplitChunks(callback) {
if (typeof callback !== 'function') {
throw new Error('Argument 1 to configureSplitChunks() must be a callback function.');
}
this.splitChunksConfigurationCallback = callback;
}
configureWatchOptions(callback) {
if (typeof callback !== 'function') {
throw new Error('Argument 1 to configureWatchOptions() must be a callback function.');
}
this.watchOptionsConfigurationCallback = callback;
}
configureDevServerOptions(callback) {
if (typeof callback !== 'function') {
throw new Error('Argument 1 to configureDevServerOptions() must be a callback function.');
}
this.devServerOptionsConfigurationCallback = callback;
}
addCacheGroup(name, options) {
if (typeof name !== 'string') {
throw new Error('Argument 1 to addCacheGroup() must be a string.');
}
if (typeof options !== 'object') {
throw new Error('Argument 2 to addCacheGroup() must be an object.');
}
if (!options['test'] && !options['node_modules']) {
throw new Error('Either the "test" option or the "node_modules" option of addCacheGroup() must be set');
}
if (options['node_modules']) {
if (!Array.isArray(options['node_modules'])) {
throw new Error('The "node_modules" option of addCacheGroup() must be an array');
}
options.test = new RegExp(`[\\\\/]node_modules[\\\\/](${
options['node_modules']
.map(regexpEscaper)
.join('|')
})[\\\\/]`);
delete options['node_modules'];
}
this.cacheGroups[name] = options;
}
copyFiles(configs = []) {
if (!Array.isArray(configs)) {
configs = [configs];
}
if (configs.some(elt => typeof elt !== 'object')) {
throw new Error('copyFiles() must be called with either a config object or an array of config objects.');
}
const defaultConfig = {
from: null,
pattern: /.*/,
to: null,
includeSubdirectories: true,
context: null,
};
for (const config of configs) {
if (!config.from) {
throw new Error('Config objects passed to copyFiles() must have a "from" property.');
}
for (const configKey of Object.keys(config)) {
if (!(configKey in defaultConfig)) {
throw new Error(`Invalid config option "${configKey}" passed to copyFiles(). Valid keys are ${Object.keys(defaultConfig).join(', ')}`);
}
}
if (typeof config.pattern !== 'undefined' && !(config.pattern instanceof RegExp)) {
let validPattern = false;
if (typeof config.pattern === 'string') {
const regexPattern = /^\/(.*)\/([a-z]*)?$/;
if (regexPattern.test(config.pattern)) {
validPattern = true;
}
}
if (!validPattern) {
throw new Error(`Invalid pattern "${config.pattern}" passed to copyFiles(). Make sure it contains a valid regular expression.`);
}
}
this.copyFilesConfigs.push(
Object.assign({}, defaultConfig, config)
);
}
}
enablePostCssLoader(postCssLoaderOptionsCallback = () => {}) {
this.usePostCssLoader = true;
if (typeof postCssLoaderOptionsCallback !== 'function') {
throw new Error('Argument 1 to enablePostCssLoader() must be a callback function.');
}
this.postCssLoaderOptionsCallback = postCssLoaderOptionsCallback;
}
enableSassLoader(sassLoaderOptionsCallback = () => {}, options = {}) {
this.useSassLoader = true;
if (typeof sassLoaderOptionsCallback !== 'function') {
throw new Error('Argument 1 to enableSassLoader() must be a callback function.');
}
this.sassLoaderOptionsCallback = sassLoaderOptionsCallback;
for (const optionKey of Object.keys(options)) {
if (!(optionKey in this.sassOptions)) {
throw new Error(`Invalid option "${optionKey}" passed to enableSassLoader(). Valid keys are ${Object.keys(this.sassOptions).join(', ')}`);
}
this.sassOptions[optionKey] = options[optionKey];
}
}
enableLessLoader(lessLoaderOptionsCallback = () => {}) {
this.useLessLoader = true;
if (typeof lessLoaderOptionsCallback !== 'function') {
throw new Error('Argument 1 to enableLessLoader() must be a callback function.');
}
this.lessLoaderOptionsCallback = lessLoaderOptionsCallback;
}
enableStylusLoader(stylusLoaderOptionsCallback = () => {}) {
this.useStylusLoader = true;
if (typeof stylusLoaderOptionsCallback !== 'function') {
throw new Error('Argument 1 to enableStylusLoader() must be a callback function.');
}
this.stylusLoaderOptionsCallback = stylusLoaderOptionsCallback;
}
enableStimulusBridge(controllerJsonPath) {
this.useStimulusBridge = true;
if (!fs.existsSync(controllerJsonPath)) {
throw new Error(`File "${controllerJsonPath}" could not be found.`);
}
// Add configured entrypoints
const controllersData = JSON.parse(fs.readFileSync(controllerJsonPath));
const rootDir = path.dirname(path.resolve(controllerJsonPath));
for (let name in controllersData.entrypoints) {
this.addEntry(name, rootDir + '/' + controllersData.entrypoints[name]);
}
this.addAliases({
'@symfony/stimulus-bridge/controllers.json': path.resolve(controllerJsonPath),
});
}
enableBuildCache(buildDependencies, callback = (cache) => {}) {
if (typeof buildDependencies !== 'object') {
throw new Error('Argument 1 to enableBuildCache() must be an object.');
}
if (!buildDependencies.config) {
throw new Error('Argument 1 to enableBuildCache() should contain an object with at least a "config" key. See the documentation for this method.');
}
this.usePersistentCache = true;
this.persistentCacheBuildDependencies = buildDependencies;
if (typeof callback !== 'function') {
throw new Error('Argument 2 to enableBuildCache() must be a callback function.');
}
this.persistentCacheCallback = callback;
}
enableReactPreset() {
this.useReact = true;
}
enablePreactPreset(options = {}) {
this.usePreact = true;
for (const optionKey of Object.keys(options)) {
if (!(optionKey in this.preactOptions)) {
throw new Error(`Invalid option "${optionKey}" passed to enablePreactPreset(). Valid keys are ${Object.keys(this.preactOptions).join(', ')}`);
}
this.preactOptions[optionKey] = options[optionKey];
}
}
enableSvelte() {
this.useSvelte = true;
}
enableTypeScriptLoader(callback = () => {}) {
if (this.useBabelTypeScriptPreset) {
throw new Error('Encore.enableTypeScriptLoader() can not be called when Encore.enableBabelTypeScriptPreset() has been called.');
}
this.useTypeScriptLoader = true;
if (typeof callback !== 'function') {
throw new Error('Argument 1 to enableTypeScriptLoader() must be a callback function.');
}
this.tsConfigurationCallback = callback;
}
enableForkedTypeScriptTypesChecking(forkedTypeScriptTypesCheckOptionsCallback = () => {}) {
if (this.useBabelTypeScriptPreset) {
throw new Error('Encore.enableForkedTypeScriptTypesChecking() can not be called when Encore.enableBabelTypeScriptPreset() has been called.');
}
if (typeof forkedTypeScriptTypesCheckOptionsCallback !== 'function') {
throw new Error('Argument 1 to enableForkedTypeScriptTypesChecking() must be a callback function.');
}
this.useForkedTypeScriptTypeChecking = true;
this.forkedTypeScriptTypesCheckOptionsCallback =
forkedTypeScriptTypesCheckOptionsCallback;
}
enableBabelTypeScriptPreset(options = {}) {
if (this.useTypeScriptLoader) {
throw new Error('Encore.enableBabelTypeScriptPreset() can not be called when Encore.enableTypeScriptLoader() has been called.');
}
if (this.useForkedTypeScriptTypeChecking) {
throw new Error('Encore.enableBabelTypeScriptPreset() can not be called when Encore.enableForkedTypeScriptTypesChecking() has been called.');
}
this.useBabelTypeScriptPreset = true;
this.babelTypeScriptPresetOptions = options;
}
enableVueLoader(vueLoaderOptionsCallback = () => {}, vueOptions = {}) {
this.useVueLoader = true;
if (typeof vueLoaderOptionsCallback !== 'function') {
throw new Error('Argument 1 to enableVueLoader() must be a callback function.');
}
this.vueLoaderOptionsCallback = vueLoaderOptionsCallback;
// Check allowed keys
for (const key of Object.keys(vueOptions)) {
if (!(key in this.vueOptions)) {
throw new Error(`"${key}" is not a valid key for enableVueLoader(). Valid keys: ${Object.keys(this.vueOptions).join(', ')}.`);
}
if (key === 'version') {
const validVersions = [2, 3];
if (!validVersions.includes(vueOptions.version)) {
throw new Error(`"${vueOptions.version}" is not a valid value for the "version" option passed to enableVueLoader(). Valid versions are: ${validVersions.join(', ')}.`);
}
}
this.vueOptions[key] = vueOptions[key];
}
// useJsx and vue 3 are not currently supported by Encore
if (this.vueOptions.useJsx && this.vueOptions.version === 3) {
throw new Error('Setting both "useJsx: true" and "version: 3" for enableVueLoader() is not currently supported. Please use version: 2 or disable useJsx.');
}
}
enableEslintPlugin(eslintPluginOptionsOrCallback = () => {}) {
this.useEslintPlugin = true;
if (typeof eslintPluginOptionsOrCallback === 'function') {
this.eslintPluginOptionsCallback = eslintPluginOptionsOrCallback;
} else if (typeof eslintPluginOptionsOrCallback === 'object') {
this.eslintPluginOptionsCallback = (options) => {
Object.assign(options, eslintPluginOptionsOrCallback);
};
} else {
throw new Error('Argument 1 to enableEslintPlugin() must be either an object or callback function.');
}
}
enableBuildNotifications(enabled = true, notifierPluginOptionsCallback = () => {}) {
if (typeof notifierPluginOptionsCallback !== 'function') {
throw new Error('Argument 2 to enableBuildNotifications() must be a callback function.');
}
this.useWebpackNotifier = enabled;
this.notifierPluginOptionsCallback = notifierPluginOptionsCallback;
}
enableHandlebarsLoader(callback = () => {}) {
this.useHandlebarsLoader = true;
if (typeof callback !== 'function') {
throw new Error('Argument 1 to enableHandlebarsLoader() must be a callback function.');
}
this.handlebarsConfigurationCallback = callback;
}
disableCssExtraction(disabled = true) {
this.extractCss = !disabled;
}
configureFilenames(configuredFilenames = {}) {
if (typeof configuredFilenames !== 'object') {
throw new Error('Argument 1 to configureFilenames() must be an object.');
}
// Check allowed keys
const validKeys = ['js', 'css', 'assets'];
for (const key of Object.keys(configuredFilenames)) {
if (!validKeys.includes(key)) {
throw new Error(`"${key}" is not a valid key for configureFilenames(). Valid keys: ${validKeys.join(', ')}. Use configureImageRule() or configureFontRule() to control image or font filenames.`);
}
}
this.configuredFilenames = configuredFilenames;
}
configureImageRule(options = {}, ruleCallback = () => {}) {
for (const optionKey of Object.keys(options)) {
if (!(optionKey in this.imageRuleOptions)) {
throw new Error(`Invalid option "${optionKey}" passed to configureImageRule(). Valid keys are ${Object.keys(this.imageRuleOptions).join(', ')}`);
}
this.imageRuleOptions[optionKey] = options[optionKey];
}
if (this.imageRuleOptions.maxSize && this.imageRuleOptions.type !== 'asset') {
throw new Error('Invalid option "maxSize" passed to configureImageRule(): this option is only valid when "type" is set to "asset".');
}
if (typeof ruleCallback !== 'function') {
throw new Error('Argument 2 to configureImageRule() must be a callback function.');
}
this.imageRuleCallback = ruleCallback;
}
configureFontRule(options = {}, ruleCallback = () => {}) {
for (const optionKey of Object.keys(options)) {
if (!(optionKey in this.fontRuleOptions)) {
throw new Error(`Invalid option "${optionKey}" passed to configureFontRule(). Valid keys are ${Object.keys(this.fontRuleOptions).join(', ')}`);
}
this.fontRuleOptions[optionKey] = options[optionKey];
}
if (this.fontRuleOptions.maxSize && this.fontRuleOptions.type !== 'asset') {
throw new Error('Invalid option "maxSize" passed to configureFontRule(): this option is only valid when "type" is set to "asset".');
}
if (typeof ruleCallback !== 'function') {
throw new Error('Argument 2 to configureFontRule() must be a callback function.');
}
this.fontRuleCallback = ruleCallback;
}
cleanupOutputBeforeBuild(paths = ['**/*'], cleanWebpackPluginOptionsCallback = () => {}) {
if (!Array.isArray(paths)) {
throw new Error('Argument 1 to cleanupOutputBeforeBuild() must be an Array of paths - e.g. [\'**/*\']');
}
if (typeof cleanWebpackPluginOptionsCallback !== 'function') {
throw new Error('Argument 2 to cleanupOutputBeforeBuild() must be a callback function');
}
this.cleanupOutput = true;
this.cleanWebpackPluginPaths = paths;
this.cleanWebpackPluginOptionsCallback = cleanWebpackPluginOptionsCallback;
}
autoProvideVariables(variables) {
// do a few sanity checks, so we can give better user errors
if (typeof variables === 'string' || Array.isArray(variables)) {
throw new Error('Invalid argument passed to autoProvideVariables: you must pass an object map - e.g. { $: "jquery" }');
}
// merge new variables into the object
this.providedVariables = Object.assign(
{},
this.providedVariables,
variables
);
}
autoProvidejQuery() {
this.autoProvideVariables({
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery',
});
}
configureLoaderRule(name, callback) {
logger.warning('Be careful when using Encore.configureLoaderRule(), this is a low-level method that can potentially break Encore and Webpack when not used carefully.');
// Key: alias, Value: existing loader in `this.loaderConfigurationCallbacks`
const aliases = {
js: 'javascript',
ts: 'typescript',
scss: 'sass',
};
if (name in aliases) {
name = aliases[name];
}
if (!(name in this.loaderConfigurationCallbacks)) {
throw new Error(`Loader "${name}" is not configurable. Valid loaders are "${Object.keys(this.loaderConfigurationCallbacks).join('", "')}" and the aliases "${Object.keys(aliases).join('", "')}".`);
}
if (typeof callback !== 'function') {
throw new Error('Argument 2 to configureLoaderRule() must be a callback function.');
}
this.loaderConfigurationCallbacks[name] = callback;
}
enableIntegrityHashes(enabled = true, algorithms = ['sha384']) {
if (!Array.isArray(algorithms)) {
algorithms = [algorithms];
}
const availableHashes = crypto.getHashes();
for (const algorithm of algorithms) {
if (typeof algorithm !== 'string') {
throw new Error('Argument 2 to enableIntegrityHashes() must be a string or an array of strings.');
}
if (!availableHashes.includes(algorithm)) {
throw new Error(`Invalid hash algorithm "${algorithm}" passed to enableIntegrityHashes().`);
}
}
this.integrityAlgorithms = enabled ? algorithms : [];
}
useDevServer() {
return this.runtimeConfig.useDevServer;
}
isProduction() {
return this.runtimeConfig.environment === 'production';
}
isDev() {
return this.runtimeConfig.environment === 'dev';
}
isDevServer() {
return this.isDev() && this.runtimeConfig.useDevServer;
}
validateNameIsNewEntry(name) {
const entryNamesOverlapMsg = 'The entry names between addEntry(), addEntries(), and addStyleEntry() must be unique.';
if (this.entries.has(name)) {
throw new Error(`Duplicate name "${name}" already exists as an Entrypoint. ${entryNamesOverlapMsg}`);
}
if (this.styleEntries.has(name)) {
throw new Error(`The "${name}" already exists as a Style Entrypoint. ${entryNamesOverlapMsg}`);
}
}
}
module.exports = WebpackConfig;

View File

@@ -0,0 +1,641 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const WebpackConfig = require('./WebpackConfig'); //eslint-disable-line no-unused-vars
const cssExtractLoaderUtil = require('./loaders/css-extract');
const pathUtil = require('./config/path-util');
const featuresHelper = require('./features');
// loaders utils
const cssLoaderUtil = require('./loaders/css');
const sassLoaderUtil = require('./loaders/sass');
const lessLoaderUtil = require('./loaders/less');
const stylusLoaderUtil = require('./loaders/stylus');
const babelLoaderUtil = require('./loaders/babel');
const tsLoaderUtil = require('./loaders/typescript');
const vueLoaderUtil = require('./loaders/vue');
const handlebarsLoaderUtil = require('./loaders/handlebars');
// plugins utils
const miniCssExtractPluginUtil = require('./plugins/mini-css-extract');
const deleteUnusedEntriesPluginUtil = require('./plugins/delete-unused-entries');
const entryFilesManifestPlugin = require('./plugins/entry-files-manifest');
const manifestPluginUtil = require('./plugins/manifest');
const variableProviderPluginUtil = require('./plugins/variable-provider');
const cleanPluginUtil = require('./plugins/clean');
const definePluginUtil = require('./plugins/define');
const terserPluginUtil = require('./plugins/terser');
const optimizeCssAssetsUtil = require('./plugins/optimize-css-assets');
const vuePluginUtil = require('./plugins/vue');
const friendlyErrorPluginUtil = require('./plugins/friendly-errors');
const assetOutputDisplay = require('./plugins/asset-output-display');
const notifierPluginUtil = require('./plugins/notifier');
const eslintPluginUtil = require('./plugins/eslint');
const PluginPriorities = require('./plugins/plugin-priorities');
const applyOptionsCallback = require('./utils/apply-options-callback');
const copyEntryTmpName = require('./utils/copyEntryTmpName');
const getVueVersion = require('./utils/get-vue-version');
const tmp = require('tmp');
const fs = require('fs');
const path = require('path');
const stringEscaper = require('./utils/string-escaper');
const logger = require('./logger');
class ConfigGenerator {
/**
* @param {WebpackConfig} webpackConfig
*/
constructor(webpackConfig) {
this.webpackConfig = webpackConfig;
}
getWebpackConfig() {
const devServerConfig = this.webpackConfig.useDevServer() ? this.buildDevServerConfig() : null;
/*
* An unfortunate situation where we need to configure the final runtime
* config later in the process. The problem is that devServer https can
* be activated with either a --server-type=https flag or by setting the devServer.server.type='https'
* config to true. So, only at this moment can we determine
* if https has been activated by either method.
*/
if (this.webpackConfig.useDevServer() &&
(
devServerConfig.https
|| (devServerConfig.server && devServerConfig.server.type === 'https')
|| this.webpackConfig.runtimeConfig.devServerHttps
)) {
this.webpackConfig.runtimeConfig.devServerFinalIsHttps = true;
if (devServerConfig.https) {
logger.deprecation('The "https" option inside of configureDevServerOptions() is deprecated. Use "server = { type: \'https\' }" instead.');
}
} else {
this.webpackConfig.runtimeConfig.devServerFinalIsHttps = false;
}
const config = {
context: this.webpackConfig.getContext(),
entry: this.buildEntryConfig(),
mode: this.webpackConfig.isProduction() ? 'production' : 'development',
output: this.buildOutputConfig(),
module: {
rules: this.buildRulesConfig(),
},
plugins: this.buildPluginsConfig(),
optimization: this.buildOptimizationConfig(),
watchOptions: this.buildWatchOptionsConfig(),
devtool: false,
};
if (this.webpackConfig.usePersistentCache) {
config.cache = this.buildCacheConfig();
}
if (this.webpackConfig.useSourceMaps) {
if (this.webpackConfig.isProduction()) {
// https://webpack.js.org/configuration/devtool/#for-production
config.devtool = 'source-map';
} else {
// https://webpack.js.org/configuration/devtool/#for-development
config.devtool = 'inline-source-map';
}
}
if (null !== devServerConfig) {
config.devServer = devServerConfig;
}
config.performance = {
// silence performance hints
hints: false
};
config.stats = this.buildStatsConfig();
config.resolve = {
extensions: ['.wasm', '.mjs', '.js', '.json', '.jsx', '.vue', '.ts', '.tsx', '.svelte'],
alias: {}
};
if (this.webpackConfig.useVueLoader && (this.webpackConfig.vueOptions.runtimeCompilerBuild === true || this.webpackConfig.vueOptions.runtimeCompilerBuild === null)) {
if (this.webpackConfig.vueOptions.runtimeCompilerBuild === null) {
logger.recommendation('To create a smaller (and CSP-compliant) build, see https://symfony.com/doc/current/frontend/encore/vuejs.html#runtime-compiler-build');
}
const vueVersion = getVueVersion(this.webpackConfig);
switch (vueVersion) {
case 2:
case '2.7':
config.resolve.alias['vue$'] = 'vue/dist/vue.esm.js';
break;
case 3:
config.resolve.alias['vue$'] = 'vue/dist/vue.esm-bundler.js';
break;
default:
throw new Error(`Invalid vue version ${vueVersion}`);
}
}
if (this.webpackConfig.usePreact && this.webpackConfig.preactOptions.preactCompat) {
config.resolve.alias['react'] = 'preact/compat';
config.resolve.alias['react-dom'] = 'preact/compat';
}
Object.assign(config.resolve.alias, this.webpackConfig.aliases);
config.externals = [...this.webpackConfig.externals];
return config;
}
buildEntryConfig() {
const entry = {};
for (const [entryName, entryChunks] of this.webpackConfig.entries) {
// entryFile could be an array, we don't care
entry[entryName] = entryChunks;
}
for (const [entryName, entryChunks] of this.webpackConfig.styleEntries) {
// entryFile could be an array, we don't care
entry[entryName] = entryChunks;
}
if (this.webpackConfig.copyFilesConfigs.length > 0) {
featuresHelper.ensurePackagesExistAndAreCorrectVersion('copy_files');
}
const copyFilesConfigs = this.webpackConfig.copyFilesConfigs.filter(entry => {
const copyFrom = path.resolve(
this.webpackConfig.getContext(),
entry.from
);
if (!fs.existsSync(copyFrom)) {
logger.warning(`The "from" option of copyFiles() should be set to an existing directory but "${entry.from}" does not seem to exist. Nothing will be copied for this copyFiles() config object.`);
return false;
}
if (!fs.lstatSync(copyFrom).isDirectory()) {
logger.warning(`The "from" option of copyFiles() should be set to an existing directory but "${entry.from}" seems to be a file. Nothing will be copied for this copyFiles() config object.`);
return false;
}
return true;
});
if (copyFilesConfigs.length > 0) {
const tmpFileObject = tmp.fileSync();
fs.writeFileSync(
tmpFileObject.name,
copyFilesConfigs.reduce((buffer, entry, index) => {
const copyFrom = path.resolve(
this.webpackConfig.getContext(),
entry.from
);
let copyTo = entry.to;
if (copyTo === null) {
copyTo = this.webpackConfig.useVersioning ? '[path][name].[hash:8].[ext]' : '[path][name].[ext]';
}
const copyFilesLoaderPath = require.resolve('./webpack/copy-files-loader');
const copyFilesLoaderConfig = `${copyFilesLoaderPath}?${JSON.stringify({
// file-loader options
context: entry.context ? path.resolve(this.webpackConfig.getContext(), entry.context) : copyFrom,
name: copyTo,
// custom copy-files-loader options
// the patternSource is base64 encoded in case
// it contains characters that don't work with
// the "inline loader" syntax
patternSource: Buffer.from(entry.pattern.source).toString('base64'),
patternFlags: entry.pattern.flags,
})}`;
return buffer + `
const context_${index} = require.context(
'${stringEscaper(`!!${copyFilesLoaderConfig}!${copyFrom}?copy-files-loader`)}',
${!!entry.includeSubdirectories},
${entry.pattern}
);
context_${index}.keys().forEach(context_${index});
`;
}, '')
);
entry[copyEntryTmpName] = tmpFileObject.name;
}
return entry;
}
buildOutputConfig() {
// Default filename can be overridden using Encore.configureFilenames({ js: '...' })
let filename = this.webpackConfig.useVersioning ? '[name].[contenthash:8].js' : '[name].js';
if (this.webpackConfig.configuredFilenames.js) {
filename = this.webpackConfig.configuredFilenames.js;
}
return {
path: this.webpackConfig.outputPath,
filename: filename,
// default "asset module" filename
// this is overridden for the image & font rules
assetModuleFilename: this.webpackConfig.configuredFilenames.assets ? this.webpackConfig.configuredFilenames.assets : 'assets/[name].[hash:8][ext]',
// will use the CDN path (if one is available) so that split
// chunks load internally through the CDN.
publicPath: this.webpackConfig.getRealPublicPath(),
pathinfo: !this.webpackConfig.isProduction()
};
}
buildRulesConfig() {
const applyRuleConfigurationCallback = (name, defaultRules) => {
return applyOptionsCallback(this.webpackConfig.loaderConfigurationCallbacks[name], defaultRules);
};
const generateAssetRuleConfig = (testRegex, ruleOptions, ruleCallback, ruleName) => {
const generatorOptions = {};
if (ruleOptions.filename) {
generatorOptions.filename = ruleOptions.filename;
}
const parserOptions = {};
if (ruleOptions.maxSize) {
parserOptions.dataUrlCondition = {
maxSize: ruleOptions.maxSize,
};
}
// apply callback from, for example, configureImageRule()
const ruleConfig = applyOptionsCallback(
ruleCallback,
{
test: testRegex,
oneOf: [
{
resourceQuery: /copy-files-loader/,
type: 'javascript/auto',
},{
type: ruleOptions.type,
generator: generatorOptions,
parser: parserOptions
}
]
},
);
// apply callback from lower-level configureLoaderRule()
return applyRuleConfigurationCallback(ruleName, ruleConfig);
};
// When the PostCSS loader is enabled, allow to use
// files with the `.postcss` extension. It also
// makes it possible to use `lang="postcss"` in Vue
// files.
const cssExtensions = ['css'];
if (this.webpackConfig.usePostCssLoader) {
cssExtensions.push('pcss');
cssExtensions.push('postcss');
}
let rules = [
applyRuleConfigurationCallback('javascript', {
test: babelLoaderUtil.getTest(this.webpackConfig),
exclude: this.webpackConfig.babelOptions.exclude,
use: babelLoaderUtil.getLoaders(this.webpackConfig)
}),
applyRuleConfigurationCallback('css', {
resolve: {
mainFields: ['style', 'main'],
extensions: cssExtensions.map(ext => `.${ext}`),
},
test: new RegExp(`\\.(${cssExtensions.join('|')})$`),
oneOf: [
{
resourceQuery: /module/,
use: cssExtractLoaderUtil.prependLoaders(
this.webpackConfig,
cssLoaderUtil.getLoaders(this.webpackConfig, true)
)
},
{
use: cssExtractLoaderUtil.prependLoaders(
this.webpackConfig,
cssLoaderUtil.getLoaders(this.webpackConfig)
)
}
]
})
];
if (this.webpackConfig.imageRuleOptions.enabled) {
rules.push(generateAssetRuleConfig(
/\.(png|jpg|jpeg|gif|ico|svg|webp|avif)$/,
this.webpackConfig.imageRuleOptions,
this.webpackConfig.imageRuleCallback,
'images'
));
}
if (this.webpackConfig.fontRuleOptions.enabled) {
rules.push(generateAssetRuleConfig(
/\.(woff|woff2|ttf|eot|otf)$/,
this.webpackConfig.fontRuleOptions,
this.webpackConfig.fontRuleCallback,
'fonts'
));
}
if (this.webpackConfig.useSassLoader) {
rules.push(applyRuleConfigurationCallback('sass', {
resolve: {
mainFields: ['sass', 'style', 'main'],
extensions: ['.scss', '.sass', '.css']
},
test: /\.s[ac]ss$/,
oneOf: [
{
resourceQuery: /module/,
use: cssExtractLoaderUtil.prependLoaders(this.webpackConfig, sassLoaderUtil.getLoaders(this.webpackConfig, true))
},
{
use: cssExtractLoaderUtil.prependLoaders(this.webpackConfig, sassLoaderUtil.getLoaders(this.webpackConfig))
}
]
}));
}
if (this.webpackConfig.useLessLoader) {
rules.push(applyRuleConfigurationCallback('less', {
test: /\.less/,
oneOf: [
{
resourceQuery: /module/,
use: cssExtractLoaderUtil.prependLoaders(this.webpackConfig, lessLoaderUtil.getLoaders(this.webpackConfig, true))
},
{
use: cssExtractLoaderUtil.prependLoaders(this.webpackConfig, lessLoaderUtil.getLoaders(this.webpackConfig))
}
]
}));
}
if (this.webpackConfig.useStylusLoader) {
rules.push(applyRuleConfigurationCallback('stylus', {
test: /\.styl/,
oneOf: [
{
resourceQuery: /module/,
use: cssExtractLoaderUtil.prependLoaders(this.webpackConfig, stylusLoaderUtil.getLoaders(this.webpackConfig, true))
},
{
use: cssExtractLoaderUtil.prependLoaders(this.webpackConfig, stylusLoaderUtil.getLoaders(this.webpackConfig))
}
]
}));
}
if (this.webpackConfig.useSvelte) {
rules.push(applyRuleConfigurationCallback('svelte', {
resolve: {
mainFields: ['svelte', 'browser', 'module', 'main'],
extensions: ['.mjs', '.js', '.svelte'],
},
test: /\.svelte$/,
loader: 'svelte-loader',
}));
}
if (this.webpackConfig.useVueLoader) {
rules.push(applyRuleConfigurationCallback('vue', {
test: /\.vue$/,
use: vueLoaderUtil.getLoaders(this.webpackConfig)
}));
}
if (this.webpackConfig.useTypeScriptLoader) {
rules.push(applyRuleConfigurationCallback('typescript', {
test: /\.tsx?$/,
exclude: /node_modules/,
use: tsLoaderUtil.getLoaders(this.webpackConfig)
}));
}
if (this.webpackConfig.useHandlebarsLoader) {
rules.push(applyRuleConfigurationCallback('handlebars', {
test: /\.(handlebars|hbs)$/,
use: handlebarsLoaderUtil.getLoaders(this.webpackConfig)
}));
}
this.webpackConfig.loaders.forEach((loader) => {
rules.push(loader);
});
return rules;
}
buildPluginsConfig() {
const plugins = [];
miniCssExtractPluginUtil(plugins, this.webpackConfig);
// register the pure-style entries that should be deleted
deleteUnusedEntriesPluginUtil(plugins, this.webpackConfig);
entryFilesManifestPlugin(plugins, this.webpackConfig);
// Dump the manifest.json file
manifestPluginUtil(plugins, this.webpackConfig);
variableProviderPluginUtil(plugins, this.webpackConfig);
cleanPluginUtil(plugins, this.webpackConfig);
definePluginUtil(plugins, this.webpackConfig);
notifierPluginUtil(plugins, this.webpackConfig);
vuePluginUtil(plugins, this.webpackConfig);
eslintPluginUtil(plugins, this.webpackConfig);
if (!this.webpackConfig.runtimeConfig.outputJson) {
const friendlyErrorPlugin = friendlyErrorPluginUtil(this.webpackConfig);
plugins.push({
plugin: friendlyErrorPlugin,
priority: PluginPriorities.FriendlyErrorsWebpackPlugin
});
assetOutputDisplay(plugins, this.webpackConfig, friendlyErrorPlugin);
}
this.webpackConfig.plugins.forEach(function(plugin) {
plugins.push(plugin);
});
// Return sorted plugins
return plugins
.map((plugin, position) => Object.assign({}, plugin, { position: position }))
.sort((a, b) => {
// Keep the original order if two plugins have the same priority
if (a.priority === b.priority) {
return a.position - b.position;
}
// A plugin with a priority of -10 will be placed after one
// that has a priority of 0.
return b.priority - a.priority;
})
.map((plugin) => plugin.plugin);
}
buildOptimizationConfig() {
const optimization = {
};
if (this.webpackConfig.isProduction()) {
optimization.minimizer = [
terserPluginUtil(this.webpackConfig),
optimizeCssAssetsUtil(this.webpackConfig)
];
}
const splitChunks = {
chunks: this.webpackConfig.shouldSplitEntryChunks ? 'all' : 'async'
};
const cacheGroups = {};
for (const groupName in this.webpackConfig.cacheGroups) {
cacheGroups[groupName] = Object.assign(
{
name: groupName,
chunks: 'all',
enforce: true
},
this.webpackConfig.cacheGroups[groupName]
);
}
splitChunks.cacheGroups = cacheGroups;
if (this.webpackConfig.shouldUseSingleRuntimeChunk === null) {
throw new Error('Either the Encore.enableSingleRuntimeChunk() or Encore.disableSingleRuntimeChunk() method should be called. The recommended setting is Encore.enableSingleRuntimeChunk().');
}
if (this.webpackConfig.shouldUseSingleRuntimeChunk) {
optimization.runtimeChunk = 'single';
}
optimization.splitChunks = applyOptionsCallback(
this.webpackConfig.splitChunksConfigurationCallback,
splitChunks
);
return optimization;
}
buildCacheConfig() {
const cache = {};
cache.type = 'filesystem';
cache.buildDependencies = this.webpackConfig.persistentCacheBuildDependencies;
applyOptionsCallback(
this.webpackConfig.persistentCacheCallback,
cache
);
return cache;
}
buildStatsConfig() {
// try to silence as much as possible: the output is rarely helpful
// this still doesn't remove all output
let stats = {};
if (!this.webpackConfig.runtimeConfig.outputJson && !this.webpackConfig.runtimeConfig.profile) {
stats = {
hash: false,
version: false,
timings: false,
assets: false,
chunks: false,
modules: false,
reasons: false,
children: false,
source: false,
errors: false,
errorDetails: false,
warnings: false,
publicPath: false,
builtAt: false,
};
}
return stats;
}
buildWatchOptionsConfig() {
const watchOptions = {
ignored: /node_modules/
};
return applyOptionsCallback(
this.webpackConfig.watchOptionsConfigurationCallback,
watchOptions
);
}
buildDevServerConfig() {
const contentBase = pathUtil.getContentBase(this.webpackConfig);
const devServerOptions = {
static: {
directory: contentBase,
},
// avoid CORS concerns trying to load things like fonts from the dev server
headers: { 'Access-Control-Allow-Origin': '*' },
compress: true,
historyApiFallback: true,
// In webpack-dev-server v4 beta 0, liveReload always causes
// the page to refresh, not allowing HMR to update the page.
// This is somehow related to the "static" option, but it's
// unknown if there is a better option.
// See https://github.com/webpack/webpack-dev-server/issues/2893
liveReload: false,
// see https://github.com/symfony/webpack-encore/issues/931#issuecomment-784483725
host: this.webpackConfig.runtimeConfig.devServerHost,
// see https://github.com/symfony/webpack-encore/issues/941#issuecomment-787568811
// we cannot let webpack-dev-server find an open port, because we need
// to know the port for sure at Webpack config build time
port: this.webpackConfig.runtimeConfig.devServerPort,
};
return applyOptionsCallback(
this.webpackConfig.devServerOptionsConfigurationCallback,
devServerOptions
);
}
}
/**
* @param {WebpackConfig} webpackConfig A configured WebpackConfig object
*
* @return {*} The final webpack config object
*/
module.exports = function(webpackConfig) {
const generator = new ConfigGenerator(webpackConfig);
return generator.getWebpackConfig();
};

View File

@@ -0,0 +1,37 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
class RuntimeConfig {
constructor() {
this.command = null;
this.context = null;
this.isValidCommand = false;
this.environment = process.env.NODE_ENV ? process.env.NODE_ENV : 'dev';
this.useDevServer = false;
this.devServerHttps = null;
// see config-generator - getWebpackConfig()
this.devServerFinalIsHttps = null;
this.devServerHost = null;
this.devServerPort = null;
this.devServerPublic = null;
this.devServerKeepPublicPath = false;
this.outputJson = false;
this.profile = false;
this.babelRcFileExists = null;
this.helpRequested = false;
this.verbose = false;
}
}
module.exports = RuntimeConfig;

View File

@@ -0,0 +1,102 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const RuntimeConfig = require('./RuntimeConfig');
const pkgUp = require('pkg-up');
const path = require('path');
const babel = require('@babel/core');
/**
* @param {object} argv
* @param {String} cwd
* @returns {RuntimeConfig}
*/
module.exports = function(argv, cwd) {
const runtimeConfig = new RuntimeConfig();
runtimeConfig.command = argv._[0];
switch (runtimeConfig.command) {
case 'dev':
runtimeConfig.isValidCommand = true;
runtimeConfig.environment = 'dev';
runtimeConfig.verbose = true;
break;
case 'production':
case 'prod':
runtimeConfig.isValidCommand = true;
runtimeConfig.environment = 'production';
runtimeConfig.verbose = false;
break;
case 'dev-server':
runtimeConfig.isValidCommand = true;
runtimeConfig.environment = 'dev';
runtimeConfig.verbose = true;
runtimeConfig.useDevServer = true;
runtimeConfig.devServerKeepPublicPath = argv.keepPublicPath || false;
if (argv.https || argv.serverType === 'https') {
runtimeConfig.devServerHttps = true;
}
if (typeof argv.public === 'string') {
runtimeConfig.devServerPublic = argv.public;
}
runtimeConfig.devServerHost = argv.host ? argv.host : 'localhost';
runtimeConfig.devServerPort = argv.port ? argv.port : '8080';
break;
}
runtimeConfig.context = argv.context;
if (typeof runtimeConfig.context === 'undefined') {
const packagesPath = pkgUp.sync({ cwd });
if (null === packagesPath) {
throw new Error('Cannot determine webpack context. (Are you executing webpack from a directory outside of your project?). Try passing the --context option.');
}
runtimeConfig.context = path.dirname(packagesPath);
}
if (argv.h || argv.help) {
runtimeConfig.helpRequested = true;
}
if (argv.j || argv.json) {
runtimeConfig.outputJson = true;
}
if (argv.profile) {
runtimeConfig.profile = true;
}
const partialConfig = babel.loadPartialConfig({
/*
* There are two types of babel configuration:
* - project-wide configuration in babel.config.* files
* - file-relative configuration in .babelrc.* files
* or package.json files with a "babel" key
*
* To detect the file-relative configuration we need
* to set the following values. The filename is needed
* for Babel as an example so that it knows where it
* needs to search the relative config for.
*/
root: cwd,
cwd: cwd,
filename: path.join(cwd, 'webpack.config.js')
});
runtimeConfig.babelRcFileExists = partialConfig.hasFilesystemConfig();
return runtimeConfig;
};

View File

@@ -0,0 +1,144 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const path = require('path');
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
const RuntimeConfig = require('./RuntimeConfig'); //eslint-disable-line no-unused-vars
const logger = require('../logger');
module.exports = {
/**
* Determines the "contentBase" to use for the devServer.
*
* @param {WebpackConfig} webpackConfig
* @return {String}
*/
getContentBase(webpackConfig) {
// strip trailing slash (for Unix or Windows)
const outputPath = webpackConfig.outputPath.replace(/\/$/,'').replace(/\\$/, '');
// use the manifestKeyPrefix if available
const publicPath = webpackConfig.manifestKeyPrefix ? webpackConfig.manifestKeyPrefix.replace(/\/$/,'') : webpackConfig.publicPath.replace(/\/$/,'');
/*
* We use the intersection of the publicPath (or manifestKeyPrefix) and outputPath
* to determine the "document root" of the web server. For example:
* * outputPath = /var/www/public/build
* * publicPath = /build/
* => contentBase should be /var/www/public
*
* At this point, if the publicPath is non-standard (e.g. it contains
* a sub-directory or is absolute), then the user will already see
* an error that they must set the manifestKeyPrefix.
*/
// start with outputPath, then join publicPath with it, see if it equals outputPath
// in loop, do dirname on outputPath and repeat
// eventually, you (may) get to the right path
let contentBase = outputPath;
while (path.dirname(contentBase) !== contentBase) {
if (path.join(contentBase, publicPath) === outputPath) {
return contentBase;
}
// go up one directory
contentBase = path.dirname(contentBase);
}
throw new Error(`Unable to determine contentBase option for webpack's devServer configuration. The ${webpackConfig.manifestKeyPrefix ? 'manifestKeyPrefix' : 'publicPath'} (${webpackConfig.manifestKeyPrefix ? webpackConfig.manifestKeyPrefix : webpackConfig.publicPath}) string does not exist in the outputPath (${webpackConfig.outputPath}), and so the "document root" cannot be determined.`);
},
/**
* Returns the output path, but as a relative string (e.g. web/build)
*
* @param {WebpackConfig} webpackConfig
* @return {String}
*/
getRelativeOutputPath(webpackConfig) {
return webpackConfig.outputPath.replace(webpackConfig.getContext() + path.sep, '');
},
/**
* If the manifestKeyPrefix is not set, this uses the publicPath to generate it.
*
* Most importantly, this runs some sanity checks to make sure that it's
* ok to use the publicPath as the manifestKeyPrefix.
*
* @param {WebpackConfig} webpackConfig
* @return {void}
*/
validatePublicPathAndManifestKeyPrefix(webpackConfig) {
if (webpackConfig.manifestKeyPrefix !== null) {
// nothing to check - they have manually set the key prefix
return;
}
if (webpackConfig.publicPath.includes('://')) {
/*
* If publicPath is absolute, you probably don't want your manifests.json
* keys to be prefixed with the CDN URL. Instead, we force you to
* choose your manifestKeyPrefix.
*/
throw new Error('Cannot determine how to prefix the keys in manifest.json. Call Encore.setManifestKeyPrefix() to choose what path (e.g. build/) to use when building your manifest keys. This is happening because you passed an absolute URL to setPublicPath().');
}
let outputPath = webpackConfig.outputPath;
// for comparison purposes, change \ to / on Windows
outputPath = outputPath.replace(/\\/g, '/');
// remove trailing slash on each
outputPath = outputPath.replace(/\/$/, '');
const publicPath = webpackConfig.publicPath.replace(/\/$/, '');
/*
* This is a sanity check. If, for example, you are deploying
* to a subdirectory, then you might have something like this:
* outputPath = /var/www/public/build
* publicPath = /subdir/build/
*
* In that case, you probably don't want the keys in the manifest.json
* file to be prefixed with /subdir/build - it makes more sense
* to prefix them with /build, which is the true prefix relative
* to your application (the subdirectory is a deployment detail).
*
* For that reason, we force you to choose your manifestKeyPrefix().
*/
if (!outputPath.includes(publicPath)) {
const suggestion = publicPath.substr(publicPath.lastIndexOf('/') + 1) + '/';
throw new Error(`Cannot determine how to prefix the keys in manifest.json. Call Encore.setManifestKeyPrefix() to choose what path (e.g. ${suggestion}) to use when building your manifest keys. This is caused by setOutputPath() (${outputPath}) and setPublicPath() (${publicPath}) containing paths that don't seem compatible.`);
}
},
/**
* @param {RuntimeConfig} runtimeConfig
* @return {string|null|Object.public|*}
*/
calculateDevServerUrl(runtimeConfig) {
if (runtimeConfig.devServerFinalIsHttps === null) {
logger.warning('The final devServerFinalHttpsConfig was never calculated. This may cause some paths to incorrectly use or not use https and could be a bug.');
}
if (runtimeConfig.devServerPublic) {
if (runtimeConfig.devServerPublic.includes('://')) {
return runtimeConfig.devServerPublic;
}
if (runtimeConfig.devServerFinalIsHttps) {
return `https://${runtimeConfig.devServerPublic}`;
}
return `http://${runtimeConfig.devServerPublic}`;
}
return `http${runtimeConfig.devServerFinalIsHttps ? 's' : ''}://${runtimeConfig.devServerHost}:${runtimeConfig.devServerPort}`;
}
};

View File

@@ -0,0 +1,90 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const pathUtil = require('./path-util');
const logger = require('./../logger');
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
class Validator {
/**
* @param {WebpackConfig} webpackConfig
*/
constructor(webpackConfig) {
this.webpackConfig = webpackConfig;
}
validate() {
this._validateBasic();
this._validatePublicPathAndManifestKeyPrefix();
this._validateDevServer();
this._validateCacheGroupNames();
}
_validateBasic() {
if (this.webpackConfig.outputPath === null) {
throw new Error('Missing output path: Call setOutputPath() to control where the files will be written.');
}
if (this.webpackConfig.publicPath === null) {
throw new Error('Missing public path: Call setPublicPath() to control the public path relative to where the files are written (the output path).');
}
if (this.webpackConfig.entries.size === 0
&& this.webpackConfig.styleEntries.size === 0
&& this.webpackConfig.copyFilesConfigs.length === 0
&& this.webpackConfig.plugins.length === 0
) {
throw new Error('No entries found! You must call addEntry() or addEntries() or addStyleEntry() or copyFiles() or addPlugin() at least once - otherwise... there is nothing to webpack!');
}
}
_validatePublicPathAndManifestKeyPrefix() {
pathUtil.validatePublicPathAndManifestKeyPrefix(this.webpackConfig);
}
_validateDevServer() {
if (!this.webpackConfig.useDevServer()) {
return;
}
if (this.webpackConfig.useVersioning) {
throw new Error('Don\'t enable versioning with the dev-server. A good setting is Encore.enableVersioning(Encore.isProduction()).');
}
/*
* An absolute publicPath is incompatible with webpackDevServer.
* This is because we want to *change* the publicPath to point
* to the webpackDevServer URL (e.g. http://localhost:8080/).
* There are some valid use-cases for not wanting this behavior
* (see #59), but we want to warn the user.
*/
if (this.webpackConfig.publicPath.includes('://')) {
logger.warning(`Passing an absolute URL to setPublicPath() *and* using the dev-server can cause issues. Your assets will load from the publicPath (${this.webpackConfig.publicPath}) instead of from the dev server URL.`);
}
}
_validateCacheGroupNames() {
for (const groupName of Object.keys(this.webpackConfig.cacheGroups)) {
if (['defaultVendors', 'default'].includes(groupName)) {
logger.warning(`Passing "${groupName}" to addCacheGroup() is not recommended, as it will override the built-in cache group by this name.`);
}
}
}
}
module.exports = function(webpackConfig) {
const validator = new Validator(webpackConfig);
validator.validate();
};

View File

@@ -0,0 +1,18 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
/**
* Stores the current RuntimeConfig created by the encore executable.
*/
module.exports = {
runtimeConfig: null
};

View File

@@ -0,0 +1,209 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const packageHelper = require('./package-helper');
/**
* An object that holds internal configuration about different
* "loaders"/"plugins" that can be enabled/used.
*/
const features = {
sass: {
method: 'enableSassLoader()',
packages: [
{ name: 'sass-loader', enforce_version: true },
[{ name: 'sass' }, { name: 'sass-embedded' }, { name: 'node-sass' }]
],
description: 'load Sass files'
},
less: {
method: 'enableLessLoader()',
packages: [
{ name: 'less-loader', enforce_version: true },
],
description: 'load LESS files'
},
stylus: {
method: 'enableStylusLoader()',
packages: [
{ name: 'stylus-loader', enforce_version: true },
{ name: 'stylus' }
],
description: 'load Stylus files'
},
postcss: {
method: 'enablePostCssLoader()',
packages: [
{ name: 'postcss-loader', enforce_version: true }
],
description: 'process through PostCSS'
},
react: {
method: 'enableReactPreset()',
packages: [
{ name: '@babel/preset-react', enforce_version: true }
],
description: 'process React JS files'
},
preact: {
method: 'enablePreactPreset()',
packages: [
{ name: '@babel/plugin-transform-react-jsx', enforce_version: true }
],
description: 'process Preact JS files'
},
typescript: {
method: 'enableTypeScriptLoader()',
packages: [
{ name: 'typescript' },
{ name: 'ts-loader', enforce_version: true },
],
description: 'process TypeScript files'
},
forkedtypecheck: {
method: 'enableForkedTypeScriptTypesChecking()',
packages: [
{ name: 'typescript' },
{ name: 'ts-loader', enforce_version: true },
{ name: 'fork-ts-checker-webpack-plugin', enforce_version: true },
],
description: 'check TypeScript types in a separate process'
},
'typescript-babel': {
method: 'enableBabelTypeScriptPreset',
packages: [
{ name: 'typescript' },
{ name: '@babel/preset-typescript', enforce_version: true },
],
description: 'process TypeScript files with Babel'
},
vue2: {
method: 'enableVueLoader()',
// vue is needed so the end-user can do things
// vue-template-compiler is a peer dep of vue-loader
packages: [
{ name: 'vue', version: '^2.5' },
{ name: 'vue-loader', version: '^15.9.5' },
{ name: 'vue-template-compiler' }
],
description: 'load Vue files'
},
'vue2.7': {
method: 'enableVueLoader()',
// vue is needed so the end-user can do things
packages: [
{ name: 'vue', version: '^2.7' },
{ name: 'vue-loader', version: '^15.10.0' },
],
description: 'load Vue files'
},
vue3: {
method: 'enableVueLoader()',
// vue is needed so the end-user can do things
// @vue/compiler-sfc is an optional peer dep of vue-loader
packages: [
{ name: 'vue', enforce_version: true },
{ name: 'vue-loader', enforce_version: true },
{ name: '@vue/compiler-sfc' }
],
description: 'load Vue files'
},
'vue-jsx': {
method: 'enableVueLoader()',
packages: [
{ name: '@vue/babel-preset-jsx' },
{ name: '@vue/babel-helper-vue-jsx-merge-props' }
],
description: 'use Vue with JSX support'
},
eslint_plugin: {
method: 'enableEslintPlugin()',
// eslint is needed so the end-user can do things
packages: [
{ name: 'eslint' },
{ name: 'eslint-webpack-plugin', enforce_version: true },
],
description: 'Enable ESLint checks'
},
copy_files: {
method: 'copyFiles()',
packages: [
{ name: 'file-loader', enforce_version: true },
],
description: 'Copy files'
},
notifier: {
method: 'enableBuildNotifications()',
packages: [
{ name: 'webpack-notifier', enforce_version: true },
],
description: 'display build notifications'
},
handlebars: {
method: 'enableHandlebarsLoader()',
packages: [
{ name: 'handlebars' },
{ name: 'handlebars-loader', enforce_version: true }
],
description: 'load Handlebars files'
},
stimulus: {
method: 'enableStimulusBridge()',
packages: [
{ name: '@symfony/stimulus-bridge', enforce_version: true }
],
description: 'enable Stimulus bridge'
},
svelte: {
method: 'enableSvelte()',
packages: [
{ name: 'svelte', enforce_version: true },
{ name: 'svelte-loader', enforce_version: true }
],
description: 'process Svelte JS files'
}
};
function getFeatureConfig(featureName) {
if (!features[featureName]) {
throw new Error(`Unknown feature ${featureName}`);
}
return features[featureName];
}
module.exports = {
ensurePackagesExistAndAreCorrectVersion: function(featureName) {
const config = getFeatureConfig(featureName);
packageHelper.ensurePackagesExist(
packageHelper.addPackagesVersionConstraint(config.packages),
config.method
);
},
getMissingPackageRecommendations: function(featureName) {
const config = getFeatureConfig(featureName);
return packageHelper.getMissingPackageRecommendations(
packageHelper.addPackagesVersionConstraint(config.packages),
config.method
);
},
getFeatureMethod: function(featureName) {
return getFeatureConfig(featureName).method;
},
getFeatureDescription: function(featureName) {
return getFeatureConfig(featureName).description;
},
};

View File

@@ -0,0 +1,36 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const chalk = require('chalk');
function AssetOutputDisplayPlugin(outputPath, friendlyErrorsPlugin) {
this.outputPath = outputPath;
this.friendlyErrorsPlugin = friendlyErrorsPlugin;
}
AssetOutputDisplayPlugin.prototype.apply = function(compiler) {
const emit = (compilation, callback) => {
// completely reset messages key to avoid adding more and more messages
// when using watch
this.friendlyErrorsPlugin.compilationSuccessInfo.messages = [
`${chalk.yellow(Object.keys(compilation.assets).length)} files written to ${chalk.yellow(this.outputPath)}`
];
callback();
};
compiler.hooks.emit.tapAsync(
{ name: 'AssetOutputDisplayPlugin' },
emit
);
};
module.exports = AssetOutputDisplayPlugin;

View File

@@ -0,0 +1,39 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const chalk = require('chalk');
function formatErrors(errors) {
if (errors.length === 0) {
return [];
}
let messages = [];
messages.push(
chalk.red('Module build failed: Module not found:')
);
for (let error of errors) {
messages.push(`"${error.file}" contains a reference to the file "${error.ref}".`);
messages.push('This file can not be found, please check it for typos or update it if the file got moved.');
messages.push('');
}
return messages;
}
function format(errors) {
return formatErrors(errors.filter((e) => (
e.type === 'missing-css-file'
)));
}
module.exports = format;

View File

@@ -0,0 +1,73 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const chalk = require('chalk');
const loaderFeatures = require('../../features');
function formatErrors(errors) {
if (errors.length === 0) {
return [];
}
let messages = [];
for (let error of errors) {
const fixes = [];
if (error.loaderName) {
let neededCode = `Encore.${loaderFeatures.getFeatureMethod(error.loaderName)}`;
fixes.push(`Add ${chalk.green(neededCode)} to your webpack.config.js file.`);
const packageRecommendations = loaderFeatures.getMissingPackageRecommendations(error.loaderName);
if (packageRecommendations) {
fixes.push(`${packageRecommendations.message}\n ${packageRecommendations.installCommand}`);
}
} else {
fixes.push('You may need to install and configure a special loader for this file type.');
}
// vue hides their filenames (via a stacktrace) inside error.origin
if (error.isVueLoader) {
messages.push(error.message);
messages.push(error.origin);
messages.push('');
} else {
messages = messages.concat([
chalk.red(`Error loading ${chalk.yellow(error.file)}`),
''
]);
}
if (error.loaderName) {
messages.push(`${chalk.bgGreen.black('', 'FIX', '')} To ${loaderFeatures.getFeatureDescription(error.loaderName)}:`);
} else {
messages.push(`${chalk.bgGreen.black('', 'FIX', '')} To load "${error.file}":`);
}
let index = 0;
for (let fix of fixes) {
messages.push(` ${++index}. ${fix}`);
}
messages.push('');
}
return messages;
}
function format(errors) {
return formatErrors(errors.filter((e) => (
e.type === 'loader-not-enabled'
)));
}
module.exports = format;

View File

@@ -0,0 +1,51 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const chalk = require('chalk');
function formatErrors(errors) {
if (errors.length === 0) {
return [];
}
let messages = [];
// there will be an error for *every* file, but showing
// the error over and over again is not helpful
messages.push(
chalk.red('Module build failed: Error: No PostCSS Config found')
);
messages.push('');
messages.push(`${chalk.bgGreen.black('', 'FIX', '')} Create a ${chalk.yellow('postcss.config.js')} file at the root of your project.`);
messages.push('');
messages.push('Here is an example to get you started!');
messages.push(chalk.yellow(`
// postcss.config.js
module.exports = {
plugins: {
'autoprefixer': {},
}
}
`));
messages.push('');
messages.push('');
return messages;
}
function format(errors) {
return formatErrors(errors.filter((e) => (
e.type === 'missing-postcss-config'
)));
}
module.exports = format;

View File

@@ -0,0 +1,47 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const TYPE = 'missing-css-file';
function isMissingConfigError(e) {
if (e.name !== 'ModuleNotFoundError') {
return false;
}
if (!e.message.includes('Module not found: Error: Can\'t resolve')) {
return false;
}
return true;
}
function getReference(error) {
const index = error.message.indexOf('Can\'t resolve \'') + 15;
const endIndex = error.message.indexOf('\' in \'');
return error.message.substring(index, endIndex);
}
function transform(error) {
if (!isMissingConfigError(error)) {
return error;
}
error = Object.assign({}, error);
error.type = TYPE;
error.ref = getReference(error);
error.severity = 900;
return error;
}
module.exports = transform;

View File

@@ -0,0 +1,114 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const getVueVersion = require('../../utils/get-vue-version');
const TYPE = 'loader-not-enabled';
function isMissingLoaderError(e) {
if (e.name !== 'ModuleParseError') {
return false;
}
if (!/You may need an (appropriate|additional) loader/.test(e.message)) {
return false;
}
return true;
}
function isErrorFromVueLoader(filename) {
// vue2
if (filename.includes('??vue-loader-options')) {
return true;
}
// vue3
if (/vue-loader\/dist(\/index\.js)?\?\?/.test(filename)) {
return true;
}
// later vue3 variant
if (filename.includes('?vue') && filename.includes('lang=')) {
return true;
}
return false;
}
function getFileExtension(filename) {
// ??vue-loader-options
if (isErrorFromVueLoader(filename)) {
// vue is strange, the "filename" is reported as something like
// vue2: /path/to/project/node_modules/vue-loader/lib??vue-loader-options!./vuejs/App.vue?vue&type=style&index=1&lang=scss
// vue3: /path/to/project/node_modules/vue-loader/dist??ref--4-0!./vuejs/App.vue?vue&type=style&index=1&lang=scss
const langPos = filename.indexOf('lang=') + 5;
let endLangPos = filename.indexOf('&', langPos);
if (endLangPos === -1) {
endLangPos = filename.length;
}
return filename.substring(langPos, endLangPos);
}
const str = filename.replace(/\?.*/, '');
const split = str.split('.');
return split.pop();
}
function transform(error, webpackConfig) {
if (!isMissingLoaderError(error)) {
return error;
}
error = Object.assign({}, error);
error.isVueLoader = isErrorFromVueLoader(error.file);
const extension = getFileExtension(error.file);
switch (extension) {
case 'sass':
case 'scss':
error.loaderName = 'sass';
break;
case 'less':
error.loaderName = 'less';
break;
case 'jsx':
error.loaderName = 'react';
break;
case 'vue':
error.loaderName = 'vue' + getVueVersion(webpackConfig);
break;
case 'tsx':
case 'ts':
error.loaderName = 'typescript';
break;
// add more as needed
default:
return error;
}
error.type = TYPE;
error.severity = 900;
error.name = 'Loader not enabled';
return error;
}
/*
* Returns a factory to get the function.
*/
module.exports = function(webpackConfig) {
return function(error) {
return transform(error, webpackConfig);
};
};

View File

@@ -0,0 +1,35 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const TYPE = 'missing-postcss-config';
function isMissingConfigError(e) {
if (!e.message || !e.message.includes('No PostCSS Config found')) {
return false;
}
return true;
}
function transform(error) {
if (!isMissingConfigError(error)) {
return error;
}
error = Object.assign({}, error);
error.type = TYPE;
error.severity = 900;
return error;
}
module.exports = transform;

View File

@@ -0,0 +1,121 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
const loaderFeatures = require('../features');
const applyOptionsCallback = require('../utils/apply-options-callback');
module.exports = {
/**
* @param {WebpackConfig} webpackConfig
* @return {Array} of loaders to use for Babel
*/
getLoaders(webpackConfig) {
let babelConfig = {
// improves performance by caching babel compiles
// this option is always added but is set to FALSE in
// production to avoid cache invalidation issues caused
// by some Babel presets/plugins (for instance the ones
// that use browserslist)
// https://github.com/babel/babel-loader#options
cacheDirectory: !webpackConfig.isProduction(),
// let Babel guess which kind of import/export syntax
// it should use based on the content of files
sourceType: 'unambiguous',
};
// configure babel (unless the user is specifying .babelrc)
// todo - add a sanity check for their babelrc contents
if (!webpackConfig.doesBabelRcFileExist()) {
let presetEnvOptions = {
// modules don't need to be transformed - webpack will parse
// the modules for us. This is a performance improvement
// https://babeljs.io/docs/en/babel-preset-env#modules
modules: false,
targets: {},
useBuiltIns: webpackConfig.babelOptions.useBuiltIns,
corejs: webpackConfig.babelOptions.corejs,
};
presetEnvOptions = applyOptionsCallback(
webpackConfig.babelPresetEnvOptionsCallback,
presetEnvOptions
);
Object.assign(babelConfig, {
presets: [
[require.resolve('@babel/preset-env'), presetEnvOptions]
],
plugins: []
});
if (webpackConfig.useBabelTypeScriptPreset) {
loaderFeatures.ensurePackagesExistAndAreCorrectVersion('typescript-babel');
babelConfig.presets.push([require.resolve('@babel/preset-typescript'), webpackConfig.babelTypeScriptPresetOptions]);
}
if (webpackConfig.useReact) {
loaderFeatures.ensurePackagesExistAndAreCorrectVersion('react');
babelConfig.presets.push(require.resolve('@babel/preset-react'));
}
if (webpackConfig.usePreact) {
loaderFeatures.ensurePackagesExistAndAreCorrectVersion('preact');
if (webpackConfig.preactOptions.preactCompat) {
// If preact-compat is enabled tell babel to
// transform JSX into React.createElement calls.
babelConfig.plugins.push([require.resolve('@babel/plugin-transform-react-jsx')]);
} else {
// If preact-compat is disabled tell babel to
// transform JSX into Preact h() calls.
babelConfig.plugins.push([
require.resolve('@babel/plugin-transform-react-jsx'),
{ 'pragma': 'h' }
]);
}
}
if (webpackConfig.useVueLoader && webpackConfig.vueOptions.useJsx) {
loaderFeatures.ensurePackagesExistAndAreCorrectVersion('vue-jsx');
babelConfig.presets.push(require.resolve('@vue/babel-preset-jsx'));
}
babelConfig = applyOptionsCallback(webpackConfig.babelConfigurationCallback, babelConfig);
}
return [
{
loader: require.resolve('babel-loader'),
options: babelConfig
}
];
},
/**
* @param {WebpackConfig} webpackConfig
* @return {RegExp} to use for eslint-loader `test` rule
*/
getTest(webpackConfig) {
const extensions = [
'm?jsx?', // match .js and .jsx and .mjs
];
if (webpackConfig.useBabelTypeScriptPreset) {
extensions.push('tsx?'); // match .ts and .tsx
}
return new RegExp(`\\.(${extensions.join('|')})$`);
}
};

View File

@@ -0,0 +1,46 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const applyOptionsCallback = require('../utils/apply-options-callback');
module.exports = {
/**
* Prepends loaders with MiniCssExtractPlugin.loader
*
* @param {WebpackConfig} webpackConfig
* @param {Array} loaders An array of some style loaders
* @return {Array}
*/
prependLoaders(webpackConfig, loaders) {
if (!webpackConfig.extractCss) {
const options = {};
// If the CSS extraction is disabled, use the
// style-loader instead.
return [{
loader: require.resolve('style-loader'),
options: applyOptionsCallback(webpackConfig.styleLoaderConfigurationCallback, options)
}, ...loaders];
}
return [{
loader: MiniCssExtractPlugin.loader,
options: applyOptionsCallback(
webpackConfig.miniCssExtractLoaderConfigurationCallback,
{}
),
}, ...loaders];
}
};

View File

@@ -0,0 +1,64 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
const loaderFeatures = require('../features');
const applyOptionsCallback = require('../utils/apply-options-callback');
module.exports = {
/**
* @param {WebpackConfig} webpackConfig
* @param {boolean} useCssModules
* @return {Array} of loaders to use for CSS files
*/
getLoaders(webpackConfig, useCssModules = false) {
const usePostCssLoader = webpackConfig.usePostCssLoader;
let modulesConfig = false;
if (useCssModules) {
modulesConfig = {
localIdentName: '[local]_[hash:base64:5]',
};
}
const options = {
sourceMap: webpackConfig.useSourceMaps,
// when using @import, how many loaders *before* css-loader should
// be applied to those imports? This defaults to 0. When postcss-loader
// is used, we set it to 1, so that postcss-loader is applied
// to @import resources.
importLoaders: usePostCssLoader ? 1 : 0,
modules: modulesConfig
};
const cssLoaders = [
{
loader: require.resolve('css-loader'),
options: applyOptionsCallback(webpackConfig.cssLoaderConfigurationCallback, options)
},
];
if (usePostCssLoader) {
loaderFeatures.ensurePackagesExistAndAreCorrectVersion('postcss');
const postCssLoaderOptions = {
sourceMap: webpackConfig.useSourceMaps
};
cssLoaders.push({
loader: require.resolve('postcss-loader'), //eslint-disable-line node/no-unpublished-require
options: applyOptionsCallback(webpackConfig.postCssLoaderOptionsCallback, postCssLoaderOptions)
});
}
return cssLoaders;
}
};

View File

@@ -0,0 +1,33 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
const loaderFeatures = require('../features');
const applyOptionsCallback = require('../utils/apply-options-callback');
module.exports = {
/**
* @param {WebpackConfig} webpackConfig
* @return {Array} of loaders to use for Handlebars
*/
getLoaders(webpackConfig) {
loaderFeatures.ensurePackagesExistAndAreCorrectVersion('handlebars');
const options = {};
return [
{
loader: require.resolve('handlebars-loader'), //eslint-disable-line node/no-unpublished-require
options: applyOptionsCallback(webpackConfig.handlebarsConfigurationCallback, options)
}
];
}
};

View File

@@ -0,0 +1,38 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
const loaderFeatures = require('../features');
const cssLoader = require('./css');
const applyOptionsCallback = require('../utils/apply-options-callback');
module.exports = {
/**
* @param {WebpackConfig} webpackConfig
* @param {boolean} useCssModules
* @return {Array} of loaders to use for Less files
*/
getLoaders(webpackConfig, useCssModules = false) {
loaderFeatures.ensurePackagesExistAndAreCorrectVersion('less');
const config = {
sourceMap: webpackConfig.useSourceMaps
};
return [
...cssLoader.getLoaders(webpackConfig, useCssModules),
{
loader: require.resolve('less-loader'), //eslint-disable-line node/no-unpublished-require
options: applyOptionsCallback(webpackConfig.lessLoaderOptionsCallback, config)
},
];
}
};

View File

@@ -0,0 +1,58 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
const loaderFeatures = require('../features');
const cssLoader = require('./css');
const applyOptionsCallback = require('../utils/apply-options-callback');
module.exports = {
/**
* @param {WebpackConfig} webpackConfig
* @param {boolean} useCssModules
* @return {Array} of loaders to use for Sass files
*/
getLoaders(webpackConfig, useCssModules = false) {
loaderFeatures.ensurePackagesExistAndAreCorrectVersion('sass');
const sassLoaders = [...cssLoader.getLoaders(webpackConfig, useCssModules)];
if (true === webpackConfig.sassOptions.resolveUrlLoader) {
// responsible for resolving Sass url() paths
// without this, all url() paths must be relative to the
// entry file, not the file that contains the url()
sassLoaders.push({
loader: require.resolve('resolve-url-loader'),
options: Object.assign(
{
sourceMap: webpackConfig.useSourceMaps
},
webpackConfig.sassOptions.resolveUrlLoaderOptions
)
});
}
const config = Object.assign({}, {
// needed by the resolve-url-loader
sourceMap: (true === webpackConfig.sassOptions.resolveUrlLoader) || webpackConfig.useSourceMaps,
sassOptions: {
// CSS minification is handled with mini-css-extract-plugin
outputStyle: 'expanded'
}
});
sassLoaders.push({
loader: require.resolve('sass-loader'), //eslint-disable-line node/no-unpublished-require
options: applyOptionsCallback(webpackConfig.sassLoaderOptionsCallback, config)
});
return sassLoaders;
}
};

View File

@@ -0,0 +1,38 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
const loaderFeatures = require('../features');
const cssLoader = require('./css');
const applyOptionsCallback = require('../utils/apply-options-callback');
module.exports = {
/**
* @param {WebpackConfig} webpackConfig
* @param {boolean} useCssModules
* @return {Array} of loaders to use for Stylus files
*/
getLoaders(webpackConfig, useCssModules = false) {
loaderFeatures.ensurePackagesExistAndAreCorrectVersion('stylus');
const config = {
sourceMap: webpackConfig.useSourceMaps
};
return [
...cssLoader.getLoaders(webpackConfig, useCssModules),
{
loader: require.resolve('stylus-loader'), //eslint-disable-line node/no-unpublished-require
options: applyOptionsCallback(webpackConfig.stylusLoaderOptionsCallback, config)
},
];
}
};

View File

@@ -0,0 +1,60 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
const loaderFeatures = require('../features');
const babelLoader = require('./babel');
const applyOptionsCallback = require('../utils/apply-options-callback');
module.exports = {
/**
* @param {WebpackConfig} webpackConfig
* @return {Array} of loaders to use for TypeScript
*/
getLoaders(webpackConfig) {
loaderFeatures.ensurePackagesExistAndAreCorrectVersion('typescript');
// some defaults
let config = {
silent: true,
};
// allow for ts-loader config to be controlled
config = applyOptionsCallback(webpackConfig.tsConfigurationCallback, config);
// fork-ts-checker-webpack-plugin integration
if (webpackConfig.useForkedTypeScriptTypeChecking) {
loaderFeatures.ensurePackagesExistAndAreCorrectVersion('forkedtypecheck');
// force transpileOnly to speed up
config.transpileOnly = true;
// add forked ts types plugin to the stack
const forkedTypesPluginUtil = require('../plugins/forked-ts-types'); // eslint-disable-line
forkedTypesPluginUtil(webpackConfig);
}
// allow to import .vue files
if (webpackConfig.useVueLoader) {
config.appendTsSuffixTo = [/\.vue$/];
}
// use ts alongside with babel
// @see https://github.com/TypeStrong/ts-loader/blob/master/README.md#babel
let loaders = babelLoader.getLoaders(webpackConfig);
return loaders.concat([
{
loader: require.resolve('ts-loader'), //eslint-disable-line node/no-unpublished-require
// @see https://github.com/TypeStrong/ts-loader/blob/master/README.md#available-options
options: config
}
]);
}
};

View File

@@ -0,0 +1,35 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
const loaderFeatures = require('../features');
const applyOptionsCallback = require('../utils/apply-options-callback');
const getVueVersion = require('../utils/get-vue-version');
module.exports = {
/**
* @param {WebpackConfig} webpackConfig
* @return {Array} of loaders to use for Vue files
*/
getLoaders(webpackConfig) {
const vueVersion = getVueVersion(webpackConfig);
loaderFeatures.ensurePackagesExistAndAreCorrectVersion('vue' + vueVersion);
const options = {};
return [
{
loader: require.resolve('vue-loader'), //eslint-disable-line node/no-unpublished-require
options: applyOptionsCallback(webpackConfig.vueLoaderOptionsCallback, options)
}
];
}
};

87
spa/node_modules/@symfony/webpack-encore/lib/logger.js generated vendored Normal file
View File

@@ -0,0 +1,87 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const chalk = require('chalk');
const messagesKeys = [
'debug',
'recommendation',
'warning',
'deprecation',
];
const defaultConfig = {
isVerbose: false,
quiet: false
};
let messages = {};
let config = {};
const reset = function() {
messages = {};
for (let messageKey of messagesKeys) {
messages[messageKey] = [];
}
config = Object.assign({}, defaultConfig);
};
reset();
function log(message) {
if (config.quiet) {
return;
}
console.log(message);
}
module.exports = {
debug(message) {
messages.debug.push(message);
if (config.isVerbose) {
log(`${chalk.bgBlack.white(' DEBUG ')} ${message}`);
}
},
recommendation(message) {
messages.recommendation.push(message);
log(`${chalk.bgBlue.white(' RECOMMEND ')} ${message}`);
},
warning(message) {
messages.warning.push(message);
log(`${chalk.bgYellow.black(' WARNING ')} ${chalk.yellow(message)}`);
},
deprecation(message) {
messages.deprecation.push(message);
log(`${chalk.bgYellow.black(' DEPRECATION ')} ${chalk.yellow(message)}`);
},
getMessages() {
return messages;
},
quiet(setQuiet = true) {
config.quiet = setQuiet;
},
verbose(setVerbose = true) {
config.isVerbose = setVerbose;
},
reset() {
reset();
}
};

View File

@@ -0,0 +1,211 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const chalk = require('chalk');
const fs = require('fs');
const logger = require('./logger');
const semver = require('semver');
function ensurePackagesExist(packagesConfig, requestedFeature) {
const missingPackagesRecommendation = getMissingPackageRecommendations(packagesConfig, requestedFeature);
if (missingPackagesRecommendation) {
throw `
${missingPackagesRecommendation.message}
${missingPackagesRecommendation.installCommand}
`;
}
// check for invalid versions & warn
const invalidVersionRecommendations = getInvalidPackageVersionRecommendations(packagesConfig);
for (let message of invalidVersionRecommendations) {
logger.warning(message);
}
}
function getInstallCommand(packageConfigs) {
const hasYarnLockfile = fs.existsSync('yarn.lock');
const hasNpmLockfile = fs.existsSync('package-lock.json');
const packageInstallStrings = packageConfigs.map((packageConfig) => {
const firstPackage = packageConfig[0];
if (typeof firstPackage.version === 'undefined') {
return firstPackage.name;
}
// e.g. ^4.0||^5.0: use the latest version
let recommendedVersion = firstPackage.version;
if (recommendedVersion.includes('||')) {
recommendedVersion = recommendedVersion.split('|').pop().trim();
}
// recommend the version included in our package.json file
return `${firstPackage.name}@${recommendedVersion}`;
});
if (hasNpmLockfile && !hasYarnLockfile) {
return chalk.yellow(`npm install ${packageInstallStrings.join(' ')} --save-dev`);
}
return chalk.yellow(`yarn add ${packageInstallStrings.join(' ')} --dev`);
}
function isPackageInstalled(packageConfig) {
try {
require.resolve(packageConfig.name);
return true;
} catch (e) {
return false;
}
}
/**
*
* @param {string} packageName
* @returns {null|string}
*/
function getPackageVersion(packageName) {
try {
return require(`${packageName}/package.json`).version;
} catch (e) {
return null;
}
}
function getMissingPackageRecommendations(packagesConfig, requestedFeature = null) {
let missingPackageConfigs = [];
for (let packageConfig of packagesConfig) {
if (!Array.isArray(packageConfig)) {
packageConfig = [packageConfig];
}
if (!packageConfig.some(isPackageInstalled)) {
missingPackageConfigs.push(packageConfig);
}
}
if (missingPackageConfigs.length === 0) {
return;
}
const missingPackageNamesChalked = missingPackageConfigs.map(function(packageConfigs) {
const packageNames = packageConfigs.map(packageConfig => {
return chalk.green(packageConfig.name);
});
let missingPackages = packageNames[0];
if (packageNames.length > 1) {
const alternativePackages = packageNames.slice(1);
missingPackages = `${missingPackages} (or ${alternativePackages.join(' or ')})`;
}
return missingPackages;
});
let message = `Install ${missingPackageNamesChalked.join(' & ')}`;
if (requestedFeature) {
message += ` to use ${chalk.green(requestedFeature)}`;
}
const installCommand = getInstallCommand(missingPackageConfigs);
return {
message,
installCommand
};
}
function getInvalidPackageVersionRecommendations(packagesConfig) {
const processPackagesConfig = (packageConfig) => {
if (Array.isArray(packageConfig)) {
let messages = [];
for (const config of packageConfig) {
messages = messages.concat(processPackagesConfig(config));
}
return messages;
}
if (typeof packageConfig.version === 'undefined') {
return [];
}
const version = getPackageVersion(packageConfig.name);
// If version is null at this point it should be because
// of an optional dependency whose presence has already
// been checked before.
if (version === null) {
return [];
}
if (semver.satisfies(version, packageConfig.version)) {
return [];
}
if (semver.gtr(version, packageConfig.version)) {
return [
`Webpack Encore requires version ${chalk.green(packageConfig.version)} of ${chalk.green(packageConfig.name)}. Your version ${chalk.green(version)} is too new. The related feature *may* still work properly. If you have issues, try downgrading the library, or upgrading Encore.`
];
} else {
return [
`Webpack Encore requires version ${chalk.green(packageConfig.version)} of ${chalk.green(packageConfig.name)}, but your version (${chalk.green(version)}) is too old. The related feature will probably *not* work correctly.`
];
}
};
return processPackagesConfig(packagesConfig);
}
function addPackagesVersionConstraint(packages) {
const packageJsonData = require('../package.json');
const addConstraint = (packageData) => {
if (Array.isArray(packageData)) {
return packageData.map(addConstraint);
}
const newData = Object.assign({}, packageData);
if (packageData.enforce_version) {
if (!packageJsonData.devDependencies) {
logger.warning('Could not find devDependencies key on @symfony/webpack-encore package');
return newData;
}
// this method only supports devDependencies due to how it's used:
// it's mean to inform the user what deps they need to install
// for optional features
if (!packageJsonData.devDependencies[packageData.name]) {
throw new Error(`Could not find package ${packageData.name}`);
}
newData.version = packageJsonData.devDependencies[packageData.name];
delete newData['enforce_version'];
}
return newData;
};
return packages.map(addConstraint);
}
module.exports = {
ensurePackagesExist,
getMissingPackageRecommendations,
getInvalidPackageVersionRecommendations,
addPackagesVersionConstraint,
getInstallCommand,
getPackageVersion,
};

View File

@@ -0,0 +1,36 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
const FriendlyErrorsWebpackPlugin = require('@nuxt/friendly-errors-webpack-plugin'); //eslint-disable-line no-unused-vars
const pathUtil = require('../config/path-util');
const AssetOutputDisplayPlugin = require('../friendly-errors/asset-output-display-plugin');
const PluginPriorities = require('./plugin-priorities');
/**
* Updates plugins array passed adding AssetOutputDisplayPlugin instance
*
* @param {Array} plugins
* @param {WebpackConfig} webpackConfig
* @param {FriendlyErrorsWebpackPlugin} friendlyErrorsPlugin
* @return {void}
*/
module.exports = function(plugins, webpackConfig, friendlyErrorsPlugin) {
if (webpackConfig.useDevServer()) {
return;
}
const outputPath = pathUtil.getRelativeOutputPath(webpackConfig);
plugins.push({
plugin: new AssetOutputDisplayPlugin(outputPath, friendlyErrorsPlugin),
priority: PluginPriorities.AssetOutputDisplayPlugin
});
};

View File

@@ -0,0 +1,48 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const PluginPriorities = require('./plugin-priorities');
const applyOptionsCallback = require('../utils/apply-options-callback');
/**
* Updates plugins array passed adding CleanWebpackPlugin instance
*
* @param {Array} plugins to push to
* @param {WebpackConfig} webpackConfig read only variable
* @return {void}
*/
module.exports = function(plugins, webpackConfig) {
if (!webpackConfig.cleanupOutput) {
return;
}
const cleanOnceBeforeBuildPatterns = webpackConfig.cleanWebpackPluginPaths;
// works around a bug where manifest.json is emitted when
// using dev-server... but then CleanWebpackPlugin deletes it
cleanOnceBeforeBuildPatterns.push('!manifest.json');
const cleanWebpackPluginOptions = {
verbose: false,
cleanOnceBeforeBuildPatterns: webpackConfig.cleanWebpackPluginPaths,
// disabled to avoid a bug where some files were incorrectly deleted on watch rebuild
cleanStaleWebpackAssets: false
};
plugins.push({
plugin: new CleanWebpackPlugin(
applyOptionsCallback(webpackConfig.cleanWebpackPluginOptionsCallback, cleanWebpackPluginOptions)
),
priority: PluginPriorities.CleanWebpackPlugin
});
};

View File

@@ -0,0 +1,35 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const webpack = require('webpack');
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
const PluginPriorities = require('./plugin-priorities');
const applyOptionsCallback = require('../utils/apply-options-callback');
/**
* @param {Array} plugins
* @param {WebpackConfig} webpackConfig
* @return {void}
*/
module.exports = function(plugins, webpackConfig) {
const definePluginOptions = {
'process.env.NODE_ENV': webpackConfig.isProduction()
? '"production"'
: '"development"',
};
plugins.push({
plugin: new webpack.DefinePlugin(
applyOptionsCallback(webpackConfig.definePluginOptionsCallback, definePluginOptions)
),
priority: PluginPriorities.DefinePlugin
});
};

View File

@@ -0,0 +1,33 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
const DeleteUnusedEntriesJSPlugin = require('../webpack/delete-unused-entries-js-plugin');
const PluginPriorities = require('./plugin-priorities');
const copyEntryTmpName = require('../utils/copyEntryTmpName');
/**
* @param {Array} plugins
* @param {WebpackConfig} webpackConfig
* @return {void}
*/
module.exports = function(plugins, webpackConfig) {
const entries = [... webpackConfig.styleEntries.keys()];
if (webpackConfig.copyFilesConfigs.length > 0) {
entries.push(copyEntryTmpName);
}
plugins.push({
plugin: new DeleteUnusedEntriesJSPlugin(entries),
priority: PluginPriorities.DeleteUnusedEntriesJSPlugin
});
};

View File

@@ -0,0 +1,100 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
const PluginPriorities = require('./plugin-priorities');
const copyEntryTmpName = require('../utils/copyEntryTmpName');
const AssetsPlugin = require('assets-webpack-plugin');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
function processOutput(webpackConfig) {
return (assets) => {
// Remove temporary entry added by the copyFiles feature
delete assets[copyEntryTmpName];
// with --watch or dev-server, subsequent calls will include
// the original assets (so, assets.entrypoints) + the new
// assets (which will have their original structure). We
// delete the entrypoints key, and then process the new assets
// like normal below. The same reasoning applies to the
// integrity key.
delete assets.entrypoints;
delete assets.integrity;
// This will iterate over all the entry points and convert the
// one file entries into an array of one entry since that was how the entry point file was before this change.
const integrity = {};
const integrityAlgorithms = webpackConfig.integrityAlgorithms;
const publicPath = webpackConfig.getRealPublicPath();
for (const asset in assets) {
for (const fileType in assets[asset]) {
if (!Array.isArray(assets[asset][fileType])) {
assets[asset][fileType] = [assets[asset][fileType]];
}
if (integrityAlgorithms.length) {
for (const file of assets[asset][fileType]) {
if (file in integrity) {
continue;
}
const filePath = path.resolve(
webpackConfig.outputPath,
file.replace(publicPath, '')
);
if (fs.existsSync(filePath)) {
const fileHashes = [];
for (const algorithm of webpackConfig.integrityAlgorithms) {
const hash = crypto.createHash(algorithm);
const fileContent = fs.readFileSync(filePath, 'utf8');
hash.update(fileContent, 'utf8');
fileHashes.push(`${algorithm}-${hash.digest('base64')}`);
}
integrity[file] = fileHashes.join(' ');
}
}
}
}
}
const manifestContent = { entrypoints: assets };
if (integrityAlgorithms.length) {
manifestContent.integrity = integrity;
}
return JSON.stringify(manifestContent, null, 2);
};
}
/**
* @param {Array} plugins
* @param {WebpackConfig} webpackConfig
* @return {void}
*/
module.exports = function(plugins, webpackConfig) {
plugins.push({
plugin: new AssetsPlugin({
path: webpackConfig.outputPath,
filename: 'entrypoints.json',
includeAllFileTypes: true,
entrypoints: true,
processOutput: processOutput(webpackConfig)
}),
priority: PluginPriorities.AssetsPlugin
});
};

View File

@@ -0,0 +1,88 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const forceSync = require('sync-rpc');
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
const applyOptionsCallback = require('../utils/apply-options-callback');
const pluginFeatures = require('../features');
const babelLoaderUtil = require('../loaders/babel');
/**
* Support for ESLint.
*
* @param {Array} plugins
* @param {WebpackConfig} webpackConfig
* @return {void}
*/
module.exports = function(plugins, webpackConfig) {
if (webpackConfig.useEslintPlugin) {
const hasEslintConfiguration = forceSync(require.resolve('../utils/has-eslint-configuration'));
pluginFeatures.ensurePackagesExistAndAreCorrectVersion('eslint_plugin');
if (!hasEslintConfiguration(webpackConfig)) {
const chalk = require('chalk');
const packageHelper = require('../package-helper');
let message = `No ESLint configuration has been found.
${chalk.bgGreen.black('', 'FIX', '')} Run command ${chalk.yellow('./node_modules/.bin/eslint --init')} or manually create a ${chalk.yellow('.eslintrc.js')} file at the root of your project.
If you prefer to create a ${chalk.yellow('.eslintrc.js')} file by yourself, here is an example to get you started:
${chalk.yellow(`// .eslintrc.js
module.exports = {
parser: '@babel/eslint-parser',
extends: ['eslint:recommended'],
}
`)}
Install ${chalk.yellow('@babel/eslint-parser')} to prevent potential parsing issues: ${packageHelper.getInstallCommand([[{ name: '@babel/eslint-parser' }]])}
`;
if (!webpackConfig.doesBabelRcFileExist()) {
const babelConfig = babelLoaderUtil.getLoaders(webpackConfig)[0].options;
// cacheDirectory is a custom loader option, not a Babel option
delete babelConfig['cacheDirectory'];
message += `
You will also need to specify your Babel config in a separate file. The current
configuration Encore has been adding for your is:
${chalk.yellow(`// babel.config.js
module.exports = ${JSON.stringify(babelConfig, null, 4)}
`)}`;
if (webpackConfig.babelConfigurationCallback) {
message += `\nAdditionally, remove the ${chalk.yellow('.configureBabel()')} in webpack.config.js: this will no longer be used.`;
}
if (webpackConfig.babelPresetEnvOptionsCallback) {
message += `\nAnd remove the ${chalk.yellow('.configureBabelPresetEnv()')} in webpack.config.js: this will no longer be used.`;
}
}
throw new Error(message);
}
const eslintPluginOptions = {
emitWarning: true,
extensions: ['js', 'jsx'],
};
const EslintPlugin = require('eslint-webpack-plugin'); //eslint-disable-line node/no-unpublished-require
plugins.push({
plugin: new EslintPlugin(
applyOptionsCallback(webpackConfig.eslintPluginOptionsCallback, eslintPluginOptions)
),
});
}
};

View File

@@ -0,0 +1,30 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); // eslint-disable-line
const PluginPriorities = require('./plugin-priorities');
const applyOptionsCallback = require('../utils/apply-options-callback');
/**
* @param {WebpackConfig} webpackConfig
* @return {void}
*/
module.exports = function(webpackConfig) {
const config = {};
webpackConfig.addPlugin(
new ForkTsCheckerWebpackPlugin(
applyOptionsCallback(webpackConfig.forkedTypeScriptTypesCheckOptionsCallback, config)
),
PluginPriorities.ForkTsCheckerWebpackPlugin
);
};

View File

@@ -0,0 +1,47 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
const FriendlyErrorsWebpackPlugin = require('@nuxt/friendly-errors-webpack-plugin');
const missingCssFileTransformer = require('../friendly-errors/transformers/missing-css-file');
const missingCssFileFormatter = require('../friendly-errors/formatters/missing-css-file');
const missingLoaderTransformerFactory = require('../friendly-errors/transformers/missing-loader');
const missingLoaderFormatter = require('../friendly-errors/formatters/missing-loader');
const missingPostCssConfigTransformer = require('../friendly-errors/transformers/missing-postcss-config');
const missingPostCssConfigFormatter = require('../friendly-errors/formatters/missing-postcss-config');
const applyOptionsCallback = require('../utils/apply-options-callback');
/**
* @param {WebpackConfig} webpackConfig
* @return {FriendlyErrorsWebpackPlugin}
*/
module.exports = function(webpackConfig) {
const friendlyErrorsPluginOptions = {
clearConsole: false,
additionalTransformers: [
missingCssFileTransformer,
missingLoaderTransformerFactory(webpackConfig),
missingPostCssConfigTransformer
],
additionalFormatters: [
missingCssFileFormatter,
missingLoaderFormatter,
missingPostCssConfigFormatter
],
compilationSuccessInfo: {
messages: []
}
};
return new FriendlyErrorsWebpackPlugin(
applyOptionsCallback(webpackConfig.friendlyErrorsPluginOptionsCallback, friendlyErrorsPluginOptions)
);
};

View File

@@ -0,0 +1,61 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
const { WebpackManifestPlugin } = require('../webpack-manifest-plugin');
const PluginPriorities = require('./plugin-priorities');
const applyOptionsCallback = require('../utils/apply-options-callback');
const copyEntryTmpName = require('../utils/copyEntryTmpName');
const manifestKeyPrefixHelper = require('../utils/manifest-key-prefix-helper');
/**
* @param {Array} plugins
* @param {WebpackConfig} webpackConfig
* @return {void}
*/
module.exports = function(plugins, webpackConfig) {
let manifestPluginOptions = {
seed: {},
basePath: manifestKeyPrefixHelper(webpackConfig),
// always write a manifest.json file, even with webpack-dev-server
writeToFileEmit: true,
filter: (file) => {
const isCopyEntry = file.isChunk && copyEntryTmpName === file.chunk.id;
const isStyleEntry = file.isChunk && webpackConfig.styleEntries.has(file.chunk.name);
const isJsOrJsMapFile = /\.js(\.map)?$/.test(file.name);
return !isCopyEntry && !(isStyleEntry && isJsOrJsMapFile);
}
};
manifestPluginOptions = applyOptionsCallback(
webpackConfig.manifestPluginOptionsCallback,
manifestPluginOptions
);
const userMapOption = manifestPluginOptions.map;
manifestPluginOptions.map = (file) => {
const newFile = Object.assign({}, file, {
name: file.name.replace('?copy-files-loader', ''),
});
if (typeof userMapOption === 'function') {
return userMapOption(newFile);
}
return newFile;
};
plugins.push({
plugin: new WebpackManifestPlugin(manifestPluginOptions),
priority: PluginPriorities.WebpackManifestPlugin
});
};

View File

@@ -0,0 +1,62 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const PluginPriorities = require('./plugin-priorities');
const applyOptionsCallback = require('../utils/apply-options-callback');
/**
* @param {Array} plugins
* @param {WebpackConfig} webpackConfig
* @return {void}
*/
module.exports = function(plugins, webpackConfig) {
// Don't add the plugin if CSS extraction is disabled
if (!webpackConfig.extractCss) {
return;
}
// Default filename can be overridden using Encore.configureFilenames({ css: '...' })
let filename = webpackConfig.useVersioning ? '[name].[contenthash:8].css' : '[name].css';
// the chunk filename should use [id], not [name]. But, due
// to weird behavior (bug?) that's exposed in a functional test
// (in production mode, code is uglified), in some cases, an entry
// CSS file mysteriously becomes a chunk. In other words, it
// will have a filename like 1.css instead of entry_name.css
// This is related to setting optimization.runtimeChunk = 'single';
// See https://github.com/webpack/webpack/issues/6598
let chunkFilename = webpackConfig.useVersioning ? '[name].[contenthash:8].css' : '[name].css';
if (webpackConfig.configuredFilenames.css) {
filename = webpackConfig.configuredFilenames.css;
// see above: originally we did NOT set this, because this was
// only for split chunks. But now, sometimes the "entry" CSS chunk
// will use chunkFilename. So, we need to always respect the
// user's wishes
chunkFilename = webpackConfig.configuredFilenames.css;
}
const miniCssPluginOptions = {
filename: filename,
chunkFilename: chunkFilename
};
plugins.push({
plugin: new MiniCssExtractPlugin(
applyOptionsCallback(
webpackConfig.miniCssExtractPluginConfigurationCallback,
miniCssPluginOptions
)
),
priority: PluginPriorities.MiniCssExtractPlugin
});
};

View File

@@ -0,0 +1,40 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
const pluginFeatures = require('../features');
const PluginPriorities = require('./plugin-priorities');
const applyOptionsCallback = require('../utils/apply-options-callback');
/**
* @param {Array} plugins
* @param {WebpackConfig} webpackConfig
* @return {void}
*/
module.exports = function(plugins, webpackConfig) {
if (!webpackConfig.useWebpackNotifier) {
return;
}
pluginFeatures.ensurePackagesExistAndAreCorrectVersion('notifier');
const notifierPluginOptions = {
title: 'Webpack Encore'
};
const WebpackNotifier = require('webpack-notifier'); // eslint-disable-line
plugins.push({
plugin: new WebpackNotifier(
applyOptionsCallback(webpackConfig.notifierPluginOptionsCallback, notifierPluginOptions)
),
priority: PluginPriorities.WebpackNotifier
});
};

View File

@@ -0,0 +1,26 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const applyOptionsCallback = require('../utils/apply-options-callback');
/**
* @param {WebpackConfig} webpackConfig
* @return {object}
*/
module.exports = function(webpackConfig) {
const minimizerPluginOptions = {};
return new CssMinimizerPlugin(
applyOptionsCallback(webpackConfig.cssMinimizerPluginOptionsCallback, minimizerPluginOptions)
);
};

View File

@@ -0,0 +1,26 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
module.exports = {
MiniCssExtractPlugin: 140,
DeleteUnusedEntriesJSPlugin: 130,
WebpackManifestPlugin: 120,
LoaderOptionsPlugin: 110,
ProvidePlugin: 90,
CleanWebpackPlugin: 80,
DefinePlugin: 70,
WebpackNotifier: 60,
VueLoaderPlugin: 50,
FriendlyErrorsWebpackPlugin: 40,
AssetOutputDisplayPlugin: 30,
ForkTsCheckerWebpackPlugin: 10,
AssetsPlugin: -10,
};

View File

@@ -0,0 +1,28 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
const TerserPlugin = require('terser-webpack-plugin');
const applyOptionsCallback = require('../utils/apply-options-callback');
/**
* @param {WebpackConfig} webpackConfig
* @return {object}
*/
module.exports = function(webpackConfig) {
const terserPluginOptions = {
parallel: true,
};
return new TerserPlugin(
applyOptionsCallback(webpackConfig.terserPluginOptionsCallback, terserPluginOptions)
);
};

View File

@@ -0,0 +1,28 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
const webpack = require('webpack');
const PluginPriorities = require('./plugin-priorities');
/**
* @param {Array} plugins
* @param {WebpackConfig} webpackConfig
* @return {void}
*/
module.exports = function(plugins, webpackConfig) {
if (Object.keys(webpackConfig.providedVariables).length > 0) {
plugins.push({
plugin: new webpack.ProvidePlugin(webpackConfig.providedVariables),
priority: PluginPriorities.ProvidePlugin
});
}
};

View File

@@ -0,0 +1,31 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
const PluginPriorities = require('./plugin-priorities');
/**
* @param {Array} plugins
* @param {WebpackConfig} webpackConfig
* @return {void}
*/
module.exports = function(plugins, webpackConfig) {
if (!webpackConfig.useVueLoader) {
return;
}
const { VueLoaderPlugin } = require('vue-loader'); // eslint-disable-line node/no-unpublished-require
plugins.push({
plugin: new VueLoaderPlugin(),
priority: PluginPriorities.VueLoaderPlugin
});
};

View File

@@ -0,0 +1,20 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
module.exports = function(optionsCallback, options) {
const result = optionsCallback.call(options, options);
if (typeof result === 'object') {
return result;
}
return options;
};

View File

@@ -0,0 +1,12 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
module.exports = '_tmp_copy';

View File

@@ -0,0 +1,19 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const path = require('path');
const url = require('url');
module.exports = function(filename) {
const parsedFilename = new url.URL(filename, 'http://foo');
const extension = path.extname(parsedFilename.pathname);
return extension ? extension.slice(1) : '';
};

View File

@@ -0,0 +1,52 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
const packageHelper = require('../package-helper');
const semver = require('semver');
const logger = require('../logger');
/**
* @param {WebpackConfig} webpackConfig
* @return {int|string|null}
*/
module.exports = function(webpackConfig) {
if (webpackConfig.vueOptions.version !== null) {
return webpackConfig.vueOptions.version;
}
// detect installed version
const vueVersion = packageHelper.getPackageVersion('vue');
if (null === vueVersion) {
// 2 is the current default version to recommend
return 2;
}
if (semver.satisfies(vueVersion, '^2.7')) {
return '2.7';
}
if (semver.satisfies(vueVersion, '^2')) {
return 2;
}
if (semver.satisfies(vueVersion, '^3.0.0-beta.1')) {
return 3;
}
if (semver.satisfies(vueVersion, '^1')) {
throw new Error('vue version 1 is not supported.');
}
logger.warning(`Your version of vue "${vueVersion}" is newer than this version of Encore supports and may or may not function properly.`);
return 3;
};

View File

@@ -0,0 +1,43 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
function isMissingConfigError(e) {
if (!e.message || !e.message.includes('No ESLint configuration found')) {
return false;
}
return true;
}
/**
* @returns {Promise<boolean>}
*/
module.exports = async function() {
/**
* @param {WebpackConfig} webpackConfig
* @returns {Promise<boolean>}
*/
return async function(webpackConfig) {
const { ESLint } = require('eslint'); // eslint-disable-line node/no-unpublished-require
const eslint = new ESLint({
cwd: webpackConfig.runtimeConfig.context,
});
try {
await eslint.calculateConfigForFile('webpack.config.js');
} catch (e) {
return !isMissingConfigError(e);
}
return true;
};
};

View File

@@ -0,0 +1,32 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
/**
* Helper for determining the manifest.json key prefix.
*
* @param {WebpackConfig} webpackConfig
* @return {string}
*/
module.exports = function(webpackConfig) {
let manifestPrefix = webpackConfig.manifestKeyPrefix;
if (null === manifestPrefix) {
if (null === webpackConfig.publicPath) {
throw new Error('publicPath is not set on WebpackConfig');
}
// by convention, we remove the opening slash on the manifest keys
manifestPrefix = webpackConfig.publicPath.replace(/^\//, '');
}
return manifestPrefix;
};

View File

@@ -0,0 +1,45 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const PrettyError = require('pretty-error');
/**
* Render a pretty version of the given error.
*
* Supported options:
* * {function} skipTrace
* An optional callback that defines whether
* or not each line of the eventual stacktrace
* should be kept. First argument is the content
* of the line, second argument is the line number.
*
* @param {*} error
* @param {object} options
*
* @returns {void}
*/
module.exports = function(error, options = {}) {
const pe = new PrettyError();
// Use the default terminal's color
// for the error message.
pe.appendStyle({
'pretty-error > header > message': { color: 'none' }
});
// Allow to skip some parts of the
// stacktrace if there is one.
if (options.skipTrace) {
pe.skip(options.skipTrace);
}
console.log(pe.render(error));
};

View File

@@ -0,0 +1,22 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
/**
* Function that escapes a string so it can be used in a RegExp.
*
* See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping
*
* @param {string} str
* @return {string}
*/
module.exports = function regexpEscaper(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
};

View File

@@ -0,0 +1,24 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
/**
* Function that escapes a string so it can be written into a
* file surrounded by single quotes.
*
* This is imperfect - is used to escape a filename (so, mostly,
* it needs to escape the Window path slashes).
*
* @param {string} str
* @return {string}
*/
module.exports = function stringEscaper(str) {
return str.replace(/\\/g, '\\\\').replace(/\x27/g, '\\\x27');
};

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Dane Thurber <dane.thurber@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,6 @@
# webpack-manifest-plugin
This is a copy of https://github.com/shellscape/webpack-manifest-plugin
at sha: 9f408f609d9b1af255491036b6fc127777ee6f9a.
It has been modified to fix this bug: https://github.com/shellscape/webpack-manifest-plugin/pull/249

View File

@@ -0,0 +1,115 @@
const { dirname, join, basename } = require('path');
const generateManifest = (compilation, files, { generate, seed = {} }) => {
let result;
if (generate) {
const entrypointsArray = Array.from(compilation.entrypoints.entries());
const entrypoints = entrypointsArray.reduce(
(e, [name, entrypoint]) => Object.assign(e, { [name]: entrypoint.getFiles() }),
{}
);
result = generate(seed, files, entrypoints);
} else {
result = files.reduce(
(manifest, file) => Object.assign(manifest, { [file.name]: file.path }),
seed
);
}
return result;
};
const getFileType = (fileName, { transformExtensions }) => {
const replaced = fileName.replace(/\?.*/, '');
const split = replaced.split('.');
const extension = split.pop();
return transformExtensions.test(extension) ? `${split.pop()}.${extension}` : extension;
};
const reduceAssets = (files, asset, moduleAssets) => {
let name;
if (moduleAssets[asset.name]) {
name = moduleAssets[asset.name];
} else if (asset.info.sourceFilename) {
name = join(dirname(asset.name), basename(asset.info.sourceFilename));
}
if (name) {
return files.concat({
path: asset.name,
name,
isInitial: false,
isChunk: false,
isAsset: true,
isModuleAsset: true
});
}
const isEntryAsset = asset.chunks && asset.chunks.length > 0;
if (isEntryAsset) {
return files;
}
return files.concat({
path: asset.name,
name: asset.name,
isInitial: false,
isChunk: false,
isAsset: true,
isModuleAsset: false
});
};
const reduceChunk = (files, chunk, options, auxiliaryFiles) => {
// auxiliary files contain things like images, fonts AND, most
// importantly, other files like .map sourcemap files
// we modify the auxiliaryFiles so that we can add any of these
// to the manifest that was not added by another method
// (sourcemaps files are not added via any other method)
Array.from(chunk.auxiliaryFiles || []).forEach((auxiliaryFile) => {
auxiliaryFiles[auxiliaryFile] = {
path: auxiliaryFile,
name: basename(auxiliaryFile),
isInitial: false,
isChunk: false,
isAsset: true,
isModuleAsset: true
};
});
return Array.from(chunk.files).reduce((prev, path) => {
let name = chunk.name ? chunk.name : null;
// chunk name, or for nameless chunks, just map the files directly.
name = name
? options.useEntryKeys && !path.endsWith('.map')
? name
: `${name}.${getFileType(path, options)}`
: path;
return prev.concat({
path,
chunk,
name,
isInitial: chunk.isOnlyInitial(),
isChunk: true,
isAsset: false,
isModuleAsset: false
});
}, files);
};
const standardizeFilePaths = (file) => {
const result = Object.assign({}, file);
result.name = file.name.replace(/\\/g, '/');
result.path = file.path.replace(/\\/g, '/');
return result;
};
const transformFiles = (files, options) =>
['filter', 'map', 'sort']
.filter((fname) => !!options[fname])
// TODO: deprecate these
.reduce((prev, fname) => prev[fname](options[fname]), files)
.map(standardizeFilePaths);
module.exports = { generateManifest, reduceAssets, reduceChunk, transformFiles };

View File

@@ -0,0 +1,145 @@
const { mkdirSync, writeFileSync } = require('fs');
const { basename, dirname, join } = require('path');
const { SyncWaterfallHook } = require('tapable');
const webpack = require('webpack');
// eslint-disable-next-line global-require
const { RawSource } = webpack.sources || require('webpack-sources');
const { generateManifest, reduceAssets, reduceChunk, transformFiles } = require('./helpers');
const compilerHookMap = new WeakMap();
const getCompilerHooks = (compiler) => {
let hooks = compilerHookMap.get(compiler);
if (typeof hooks === 'undefined') {
hooks = {
afterEmit: new SyncWaterfallHook(['manifest']),
beforeEmit: new SyncWaterfallHook(['manifest'])
};
compilerHookMap.set(compiler, hooks);
}
return hooks;
};
const beforeRunHook = ({ emitCountMap, manifestFileName }, compiler, callback) => {
const emitCount = emitCountMap.get(manifestFileName) || 0;
emitCountMap.set(manifestFileName, emitCount + 1);
/* istanbul ignore next */
if (callback) {
callback();
}
};
const emitHook = function emit(
{ compiler, emitCountMap, manifestAssetId, manifestFileName, moduleAssets, options },
compilation
) {
const emitCount = emitCountMap.get(manifestFileName) - 1;
// Disable everything we don't use, add asset info, show cached assets
const stats = compilation.getStats().toJson({
all: false,
assets: true,
cachedAssets: true,
ids: true,
publicPath: true
});
const publicPath = options.publicPath !== null ? options.publicPath : stats.publicPath;
const { basePath, removeKeyHash } = options;
emitCountMap.set(manifestFileName, emitCount);
const auxiliaryFiles = {};
let files = Array.from(compilation.chunks).reduce(
(prev, chunk) => reduceChunk(prev, chunk, options, auxiliaryFiles),
[]
);
// module assets don't show up in assetsByChunkName, we're getting them this way
files = stats.assets.reduce((prev, asset) => reduceAssets(prev, asset, moduleAssets), files);
// don't add hot updates and don't add manifests from other instances
files = files.filter(
({ name, path }) =>
!path.includes('hot-update') &&
typeof emitCountMap.get(join(compiler.options.output.path, name)) === 'undefined'
);
// auxiliary files are "extra" files that are probably already included
// in other ways. Loop over files and remove any from auxiliaryFiles
files.forEach((file) => {
delete auxiliaryFiles[file.path];
});
// if there are any auxiliaryFiles left, add them to the files
// this handles, specifically, sourcemaps
Object.keys(auxiliaryFiles).forEach((auxiliaryFile) => {
files = files.concat(auxiliaryFiles[auxiliaryFile]);
});
files = files.map((file) => {
const changes = {
// Append optional basepath onto all references. This allows output path to be reflected in the manifest.
name: basePath ? basePath + file.name : file.name,
// Similar to basePath but only affects the value (e.g. how output.publicPath turns
// require('foo/bar') into '/public/foo/bar', see https://github.com/webpack/docs/wiki/configuration#outputpublicpath
path: publicPath ? publicPath + file.path : file.path
};
// Fixes #210
changes.name = removeKeyHash ? changes.name.replace(removeKeyHash, '') : changes.name;
return Object.assign(file, changes);
});
files = transformFiles(files, options);
let manifest = generateManifest(compilation, files, options);
const isLastEmit = emitCount === 0;
manifest = getCompilerHooks(compiler).beforeEmit.call(manifest);
if (isLastEmit) {
const output = options.serialize(manifest);
//
// Object.assign(compilation.assets, {
// [manifestAssetId]: {
// source() {
// return output;
// },
// size() {
// return output.length;
// }
// }
// });
//
compilation.emitAsset(manifestAssetId, new RawSource(output));
if (options.writeToFileEmit) {
mkdirSync(dirname(manifestFileName), { recursive: true });
writeFileSync(manifestFileName, output);
}
}
getCompilerHooks(compiler).afterEmit.call(manifest);
};
const normalModuleLoaderHook = ({ moduleAssets }, loaderContext, module) => {
const { emitFile } = loaderContext;
// eslint-disable-next-line no-param-reassign
loaderContext.emitFile = (file, content, sourceMap, assetInfo) => {
const info = Object.assign({}, assetInfo);
if (module.userRequest && !moduleAssets[file]) {
info.sourceFilename = join(dirname(file), basename(module.userRequest));
Object.assign(moduleAssets, { [file]: info.sourceFilename });
}
return emitFile.call(module, file, content, sourceMap, info);
};
};
module.exports = { beforeRunHook, emitHook, getCompilerHooks, normalModuleLoaderHook };

View File

@@ -0,0 +1,73 @@
const { relative, resolve } = require('path');
const webpack = require('webpack');
const NormalModule = require('webpack/lib/NormalModule');
const { beforeRunHook, emitHook, getCompilerHooks, normalModuleLoaderHook } = require('./hooks');
const emitCountMap = new Map();
const defaults = {
basePath: '',
fileName: 'manifest.json',
filter: null,
generate: void 0,
map: null,
publicPath: null,
removeKeyHash: /([a-f0-9]{32}\.?)/gi,
// seed must be reset for each compilation. let the code initialize it to {}
seed: void 0,
serialize(manifest) {
return JSON.stringify(manifest, null, 2);
},
sort: null,
transformExtensions: /^(gz|map)$/i,
useEntryKeys: false,
writeToFileEmit: false
};
class WebpackManifestPlugin {
constructor(opts) {
this.options = Object.assign({}, defaults, opts);
}
apply(compiler) {
const moduleAssets = {};
const manifestFileName = resolve(compiler.options.output.path, this.options.fileName);
const manifestAssetId = relative(compiler.options.output.path, manifestFileName);
const beforeRun = beforeRunHook.bind(this, { emitCountMap, manifestFileName });
const emit = emitHook.bind(this, {
compiler,
emitCountMap,
manifestAssetId,
manifestFileName,
moduleAssets,
options: this.options
});
const normalModuleLoader = normalModuleLoaderHook.bind(this, { moduleAssets });
const hookOptions = {
name: 'WebpackManifestPlugin',
stage: Infinity
};
compiler.hooks.compilation.tap(hookOptions, (compilation) => {
const hook = !NormalModule.getCompilationHooks
? compilation.hooks.normalModuleLoader
: NormalModule.getCompilationHooks(compilation).loader;
hook.tap(hookOptions, normalModuleLoader);
});
if (webpack.version.startsWith('4')) {
compiler.hooks.emit.tap(hookOptions, emit);
} else {
compiler.hooks.thisCompilation.tap(hookOptions, (compilation) => {
compilation.hooks.processAssets.tap(hookOptions, () => emit(compilation));
});
}
compiler.hooks.run.tap(hookOptions, beforeRun);
compiler.hooks.watchRun.tap(hookOptions, beforeRun);
}
}
module.exports = { getCompilerHooks, WebpackManifestPlugin };

View File

@@ -0,0 +1,91 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
const LoaderDependency = require('webpack/lib/dependencies/LoaderDependency');
const path = require('path');
module.exports.raw = true; // Needed to avoid corrupted binary files
module.exports.default = function loader(source) {
// This is a hack that allows `Encore.copyFiles()` to support
// JSON files using the file-loader (which is not something
// that is supported in Webpack 4, see https://github.com/symfony/webpack-encore/issues/535)
//
// Since there is no way to change the module's resource type from a loader
// without using private properties yet we have to use "this._module".
//
// By setting its type to 'javascript/auto' Webpack won't try parsing
// the result of the loader as a JSON object.
//
// For more information:
// https://github.com/webpack/webpack/issues/6572#issuecomment-368376326
// https://github.com/webpack/webpack/issues/7057
const requiredType = 'javascript/auto';
if (this._module.type !== requiredType) {
// Try to retrieve the factory used by the LoaderDependency type
// which should be the NormalModuleFactory.
const factory = this._compilation.dependencyFactories.get(LoaderDependency);
if (factory === undefined) {
throw new Error('Could not retrieve module factory for type LoaderDependency');
}
this._module.type = requiredType;
this._module.generator = factory.getGenerator(requiredType);
this._module.parser = factory.getParser(requiredType);
}
const options = this.getOptions();
// Retrieve the real path of the resource, relative
// to the context used by copyFiles(...)
const context = options.context;
const resourcePath = this.resourcePath;
const relativeResourcePath = path.relative(context, resourcePath);
// Retrieve the pattern used in copyFiles(...)
// The "source" part of the regexp is base64 encoded
// in case it contains characters that don't work with
// the "inline loader" syntax
const pattern = options.regExp || new RegExp(
Buffer.from(options.patternSource, 'base64').toString(),
options.patternFlags
);
// If the pattern does not match the ressource's path
// it probably means that the import was resolved using the
// "resolve.extensions" parameter of Webpack (for instance
// if "./test.js" was matched by "./test").
if (!pattern.test(relativeResourcePath)) {
return 'module.exports = "";';
}
// Add the "regExp" option (needed to use the [N] placeholder
// see: https://github.com/webpack-contrib/file-loader#n)
options.regExp = pattern;
// Remove copy-files-loader's custom options (in case the
// file-loader starts looking for thing it doesn't expect)
delete options.patternSource;
delete options.patternFlags;
// Update loader's options.
this.loaders.forEach(loader => {
if (__filename === loader.path) {
loader.options = options;
delete loader.query;
}
});
const fileLoader = require('file-loader'); // eslint-disable-line node/no-unpublished-require
// If everything is OK, let the file-loader do the copy
return fileLoader.bind(this)(source);
};

View File

@@ -0,0 +1,65 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict';
function DeleteUnusedEntriesJSPlugin(entriesToDelete = []) {
this.entriesToDelete = entriesToDelete;
}
DeleteUnusedEntriesJSPlugin.prototype.apply = function(compiler) {
const deleteEntries = (compilation) => {
// loop over output chunks
compilation.chunks.forEach((chunk) => {
// see of this chunk is one that needs its .js deleted
if (this.entriesToDelete.includes(chunk.name)) {
const removedFiles = [];
// look for main files to delete first
for (const filename of Array.from(chunk.files)) {
if (/\.js?(\?[^.]*)?$/.test(filename)) {
removedFiles.push(filename);
// remove the output file
compilation.deleteAsset(filename);
// remove the file, so that it does not dump in the manifest
chunk.files.delete(filename);
}
}
// then also look in auxiliary files for source maps
for (const filename of Array.from(chunk.auxiliaryFiles)) {
if (removedFiles.map(name => `${name}.map`).includes(`${filename}`)) {
removedFiles.push(filename);
// remove the output file
compilation.deleteAsset(filename);
// remove the file, so that it does not dump in the manifest
chunk.auxiliaryFiles.delete(filename);
}
}
// sanity check: make sure 1 or 2 files were deleted
// if there's some edge case where more .js files
// or 0 .js files might be deleted, I'd rather error
if (removedFiles.length === 0 || removedFiles.length > 2) {
throw new Error(`Problem deleting JS entry for ${chunk.name}: ${removedFiles.length} files were deleted (${removedFiles.join(', ')})`);
}
}
});
};
compiler.hooks.compilation.tap('DeleteUnusedEntriesJSPlugin', function(compilation) {
compilation.hooks.additionalAssets.tap(
'DeleteUnusedEntriesJsPlugin',
function() {
deleteEntries(compilation);
}
);
});
};
module.exports = DeleteUnusedEntriesJSPlugin;

View File

@@ -0,0 +1 @@
../semver/bin/semver.js

View File

@@ -0,0 +1,345 @@
declare type CSSColor =
| 'aliceblue'
| 'antiquewhite'
| 'aqua'
| 'aquamarine'
| 'azure'
| 'beige'
| 'bisque'
| 'black'
| 'blanchedalmond'
| 'blue'
| 'blueviolet'
| 'brown'
| 'burlywood'
| 'cadetblue'
| 'chartreuse'
| 'chocolate'
| 'coral'
| 'cornflowerblue'
| 'cornsilk'
| 'crimson'
| 'cyan'
| 'darkblue'
| 'darkcyan'
| 'darkgoldenrod'
| 'darkgray'
| 'darkgreen'
| 'darkgrey'
| 'darkkhaki'
| 'darkmagenta'
| 'darkolivegreen'
| 'darkorange'
| 'darkorchid'
| 'darkred'
| 'darksalmon'
| 'darkseagreen'
| 'darkslateblue'
| 'darkslategray'
| 'darkslategrey'
| 'darkturquoise'
| 'darkviolet'
| 'deeppink'
| 'deepskyblue'
| 'dimgray'
| 'dimgrey'
| 'dodgerblue'
| 'firebrick'
| 'floralwhite'
| 'forestgreen'
| 'fuchsia'
| 'gainsboro'
| 'ghostwhite'
| 'gold'
| 'goldenrod'
| 'gray'
| 'green'
| 'greenyellow'
| 'grey'
| 'honeydew'
| 'hotpink'
| 'indianred'
| 'indigo'
| 'ivory'
| 'khaki'
| 'lavender'
| 'lavenderblush'
| 'lawngreen'
| 'lemonchiffon'
| 'lightblue'
| 'lightcoral'
| 'lightcyan'
| 'lightgoldenrodyellow'
| 'lightgray'
| 'lightgreen'
| 'lightgrey'
| 'lightpink'
| 'lightsalmon'
| 'lightseagreen'
| 'lightskyblue'
| 'lightslategray'
| 'lightslategrey'
| 'lightsteelblue'
| 'lightyellow'
| 'lime'
| 'limegreen'
| 'linen'
| 'magenta'
| 'maroon'
| 'mediumaquamarine'
| 'mediumblue'
| 'mediumorchid'
| 'mediumpurple'
| 'mediumseagreen'
| 'mediumslateblue'
| 'mediumspringgreen'
| 'mediumturquoise'
| 'mediumvioletred'
| 'midnightblue'
| 'mintcream'
| 'mistyrose'
| 'moccasin'
| 'navajowhite'
| 'navy'
| 'oldlace'
| 'olive'
| 'olivedrab'
| 'orange'
| 'orangered'
| 'orchid'
| 'palegoldenrod'
| 'palegreen'
| 'paleturquoise'
| 'palevioletred'
| 'papayawhip'
| 'peachpuff'
| 'peru'
| 'pink'
| 'plum'
| 'powderblue'
| 'purple'
| 'rebeccapurple'
| 'red'
| 'rosybrown'
| 'royalblue'
| 'saddlebrown'
| 'salmon'
| 'sandybrown'
| 'seagreen'
| 'seashell'
| 'sienna'
| 'silver'
| 'skyblue'
| 'slateblue'
| 'slategray'
| 'slategrey'
| 'snow'
| 'springgreen'
| 'steelblue'
| 'tan'
| 'teal'
| 'thistle'
| 'tomato'
| 'turquoise'
| 'violet'
| 'wheat'
| 'white'
| 'whitesmoke'
| 'yellow'
| 'yellowgreen';
declare namespace ansiStyles {
interface ColorConvert {
/**
The RGB color space.
@param red - (`0`-`255`)
@param green - (`0`-`255`)
@param blue - (`0`-`255`)
*/
rgb(red: number, green: number, blue: number): string;
/**
The RGB HEX color space.
@param hex - A hexadecimal string containing RGB data.
*/
hex(hex: string): string;
/**
@param keyword - A CSS color name.
*/
keyword(keyword: CSSColor): string;
/**
The HSL color space.
@param hue - (`0`-`360`)
@param saturation - (`0`-`100`)
@param lightness - (`0`-`100`)
*/
hsl(hue: number, saturation: number, lightness: number): string;
/**
The HSV color space.
@param hue - (`0`-`360`)
@param saturation - (`0`-`100`)
@param value - (`0`-`100`)
*/
hsv(hue: number, saturation: number, value: number): string;
/**
The HSV color space.
@param hue - (`0`-`360`)
@param whiteness - (`0`-`100`)
@param blackness - (`0`-`100`)
*/
hwb(hue: number, whiteness: number, blackness: number): string;
/**
Use a [4-bit unsigned number](https://en.wikipedia.org/wiki/ANSI_escape_code#3/4-bit) to set text color.
*/
ansi(ansi: number): string;
/**
Use an [8-bit unsigned number](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit) to set text color.
*/
ansi256(ansi: number): string;
}
interface CSPair {
/**
The ANSI terminal control sequence for starting this style.
*/
readonly open: string;
/**
The ANSI terminal control sequence for ending this style.
*/
readonly close: string;
}
interface ColorBase {
readonly ansi: ColorConvert;
readonly ansi256: ColorConvert;
readonly ansi16m: ColorConvert;
/**
The ANSI terminal control sequence for ending this color.
*/
readonly close: string;
}
interface Modifier {
/**
Resets the current color chain.
*/
readonly reset: CSPair;
/**
Make text bold.
*/
readonly bold: CSPair;
/**
Emitting only a small amount of light.
*/
readonly dim: CSPair;
/**
Make text italic. (Not widely supported)
*/
readonly italic: CSPair;
/**
Make text underline. (Not widely supported)
*/
readonly underline: CSPair;
/**
Inverse background and foreground colors.
*/
readonly inverse: CSPair;
/**
Prints the text, but makes it invisible.
*/
readonly hidden: CSPair;
/**
Puts a horizontal line through the center of the text. (Not widely supported)
*/
readonly strikethrough: CSPair;
}
interface ForegroundColor {
readonly black: CSPair;
readonly red: CSPair;
readonly green: CSPair;
readonly yellow: CSPair;
readonly blue: CSPair;
readonly cyan: CSPair;
readonly magenta: CSPair;
readonly white: CSPair;
/**
Alias for `blackBright`.
*/
readonly gray: CSPair;
/**
Alias for `blackBright`.
*/
readonly grey: CSPair;
readonly blackBright: CSPair;
readonly redBright: CSPair;
readonly greenBright: CSPair;
readonly yellowBright: CSPair;
readonly blueBright: CSPair;
readonly cyanBright: CSPair;
readonly magentaBright: CSPair;
readonly whiteBright: CSPair;
}
interface BackgroundColor {
readonly bgBlack: CSPair;
readonly bgRed: CSPair;
readonly bgGreen: CSPair;
readonly bgYellow: CSPair;
readonly bgBlue: CSPair;
readonly bgCyan: CSPair;
readonly bgMagenta: CSPair;
readonly bgWhite: CSPair;
/**
Alias for `bgBlackBright`.
*/
readonly bgGray: CSPair;
/**
Alias for `bgBlackBright`.
*/
readonly bgGrey: CSPair;
readonly bgBlackBright: CSPair;
readonly bgRedBright: CSPair;
readonly bgGreenBright: CSPair;
readonly bgYellowBright: CSPair;
readonly bgBlueBright: CSPair;
readonly bgCyanBright: CSPair;
readonly bgMagentaBright: CSPair;
readonly bgWhiteBright: CSPair;
}
}
declare const ansiStyles: {
readonly modifier: ansiStyles.Modifier;
readonly color: ansiStyles.ForegroundColor & ansiStyles.ColorBase;
readonly bgColor: ansiStyles.BackgroundColor & ansiStyles.ColorBase;
readonly codes: ReadonlyMap<number, number>;
} & ansiStyles.BackgroundColor & ansiStyles.ForegroundColor & ansiStyles.Modifier;
export = ansiStyles;

View File

@@ -0,0 +1,163 @@
'use strict';
const wrapAnsi16 = (fn, offset) => (...args) => {
const code = fn(...args);
return `\u001B[${code + offset}m`;
};
const wrapAnsi256 = (fn, offset) => (...args) => {
const code = fn(...args);
return `\u001B[${38 + offset};5;${code}m`;
};
const wrapAnsi16m = (fn, offset) => (...args) => {
const rgb = fn(...args);
return `\u001B[${38 + offset};2;${rgb[0]};${rgb[1]};${rgb[2]}m`;
};
const ansi2ansi = n => n;
const rgb2rgb = (r, g, b) => [r, g, b];
const setLazyProperty = (object, property, get) => {
Object.defineProperty(object, property, {
get: () => {
const value = get();
Object.defineProperty(object, property, {
value,
enumerable: true,
configurable: true
});
return value;
},
enumerable: true,
configurable: true
});
};
/** @type {typeof import('color-convert')} */
let colorConvert;
const makeDynamicStyles = (wrap, targetSpace, identity, isBackground) => {
if (colorConvert === undefined) {
colorConvert = require('color-convert');
}
const offset = isBackground ? 10 : 0;
const styles = {};
for (const [sourceSpace, suite] of Object.entries(colorConvert)) {
const name = sourceSpace === 'ansi16' ? 'ansi' : sourceSpace;
if (sourceSpace === targetSpace) {
styles[name] = wrap(identity, offset);
} else if (typeof suite === 'object') {
styles[name] = wrap(suite[targetSpace], offset);
}
}
return styles;
};
function assembleStyles() {
const codes = new Map();
const styles = {
modifier: {
reset: [0, 0],
// 21 isn't widely supported and 22 does the same thing
bold: [1, 22],
dim: [2, 22],
italic: [3, 23],
underline: [4, 24],
inverse: [7, 27],
hidden: [8, 28],
strikethrough: [9, 29]
},
color: {
black: [30, 39],
red: [31, 39],
green: [32, 39],
yellow: [33, 39],
blue: [34, 39],
magenta: [35, 39],
cyan: [36, 39],
white: [37, 39],
// Bright color
blackBright: [90, 39],
redBright: [91, 39],
greenBright: [92, 39],
yellowBright: [93, 39],
blueBright: [94, 39],
magentaBright: [95, 39],
cyanBright: [96, 39],
whiteBright: [97, 39]
},
bgColor: {
bgBlack: [40, 49],
bgRed: [41, 49],
bgGreen: [42, 49],
bgYellow: [43, 49],
bgBlue: [44, 49],
bgMagenta: [45, 49],
bgCyan: [46, 49],
bgWhite: [47, 49],
// Bright color
bgBlackBright: [100, 49],
bgRedBright: [101, 49],
bgGreenBright: [102, 49],
bgYellowBright: [103, 49],
bgBlueBright: [104, 49],
bgMagentaBright: [105, 49],
bgCyanBright: [106, 49],
bgWhiteBright: [107, 49]
}
};
// Alias bright black as gray (and grey)
styles.color.gray = styles.color.blackBright;
styles.bgColor.bgGray = styles.bgColor.bgBlackBright;
styles.color.grey = styles.color.blackBright;
styles.bgColor.bgGrey = styles.bgColor.bgBlackBright;
for (const [groupName, group] of Object.entries(styles)) {
for (const [styleName, style] of Object.entries(group)) {
styles[styleName] = {
open: `\u001B[${style[0]}m`,
close: `\u001B[${style[1]}m`
};
group[styleName] = styles[styleName];
codes.set(style[0], style[1]);
}
Object.defineProperty(styles, groupName, {
value: group,
enumerable: false
});
}
Object.defineProperty(styles, 'codes', {
value: codes,
enumerable: false
});
styles.color.close = '\u001B[39m';
styles.bgColor.close = '\u001B[49m';
setLazyProperty(styles.color, 'ansi', () => makeDynamicStyles(wrapAnsi16, 'ansi16', ansi2ansi, false));
setLazyProperty(styles.color, 'ansi256', () => makeDynamicStyles(wrapAnsi256, 'ansi256', ansi2ansi, false));
setLazyProperty(styles.color, 'ansi16m', () => makeDynamicStyles(wrapAnsi16m, 'rgb', rgb2rgb, false));
setLazyProperty(styles.bgColor, 'ansi', () => makeDynamicStyles(wrapAnsi16, 'ansi16', ansi2ansi, true));
setLazyProperty(styles.bgColor, 'ansi256', () => makeDynamicStyles(wrapAnsi256, 'ansi256', ansi2ansi, true));
setLazyProperty(styles.bgColor, 'ansi16m', () => makeDynamicStyles(wrapAnsi16m, 'rgb', rgb2rgb, true));
return styles;
}
// Make the export immutable
Object.defineProperty(module, 'exports', {
enumerable: true,
get: assembleStyles
});

View File

@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,56 @@
{
"name": "ansi-styles",
"version": "4.3.0",
"description": "ANSI escape codes for styling strings in the terminal",
"license": "MIT",
"repository": "chalk/ansi-styles",
"funding": "https://github.com/chalk/ansi-styles?sponsor=1",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
},
"engines": {
"node": ">=8"
},
"scripts": {
"test": "xo && ava && tsd",
"screenshot": "svg-term --command='node screenshot' --out=screenshot.svg --padding=3 --width=55 --height=3 --at=1000 --no-cursor"
},
"files": [
"index.js",
"index.d.ts"
],
"keywords": [
"ansi",
"styles",
"color",
"colour",
"colors",
"terminal",
"console",
"cli",
"string",
"tty",
"escape",
"formatting",
"rgb",
"256",
"shell",
"xterm",
"log",
"logging",
"command-line",
"text"
],
"dependencies": {
"color-convert": "^2.0.1"
},
"devDependencies": {
"@types/color-convert": "^1.9.0",
"ava": "^2.3.0",
"svg-term-cli": "^2.1.1",
"tsd": "^0.11.0",
"xo": "^0.25.3"
}
}

View File

@@ -0,0 +1,152 @@
# ansi-styles [![Build Status](https://travis-ci.org/chalk/ansi-styles.svg?branch=master)](https://travis-ci.org/chalk/ansi-styles)
> [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code#Colors_and_Styles) for styling strings in the terminal
You probably want the higher-level [chalk](https://github.com/chalk/chalk) module for styling your strings.
<img src="screenshot.svg" width="900">
## Install
```
$ npm install ansi-styles
```
## Usage
```js
const style = require('ansi-styles');
console.log(`${style.green.open}Hello world!${style.green.close}`);
// Color conversion between 16/256/truecolor
// NOTE: If conversion goes to 16 colors or 256 colors, the original color
// may be degraded to fit that color palette. This means terminals
// that do not support 16 million colors will best-match the
// original color.
console.log(style.bgColor.ansi.hsl(120, 80, 72) + 'Hello world!' + style.bgColor.close);
console.log(style.color.ansi256.rgb(199, 20, 250) + 'Hello world!' + style.color.close);
console.log(style.color.ansi16m.hex('#abcdef') + 'Hello world!' + style.color.close);
```
## API
Each style has an `open` and `close` property.
## Styles
### Modifiers
- `reset`
- `bold`
- `dim`
- `italic` *(Not widely supported)*
- `underline`
- `inverse`
- `hidden`
- `strikethrough` *(Not widely supported)*
### Colors
- `black`
- `red`
- `green`
- `yellow`
- `blue`
- `magenta`
- `cyan`
- `white`
- `blackBright` (alias: `gray`, `grey`)
- `redBright`
- `greenBright`
- `yellowBright`
- `blueBright`
- `magentaBright`
- `cyanBright`
- `whiteBright`
### Background colors
- `bgBlack`
- `bgRed`
- `bgGreen`
- `bgYellow`
- `bgBlue`
- `bgMagenta`
- `bgCyan`
- `bgWhite`
- `bgBlackBright` (alias: `bgGray`, `bgGrey`)
- `bgRedBright`
- `bgGreenBright`
- `bgYellowBright`
- `bgBlueBright`
- `bgMagentaBright`
- `bgCyanBright`
- `bgWhiteBright`
## Advanced usage
By default, you get a map of styles, but the styles are also available as groups. They are non-enumerable so they don't show up unless you access them explicitly. This makes it easier to expose only a subset in a higher-level module.
- `style.modifier`
- `style.color`
- `style.bgColor`
###### Example
```js
console.log(style.color.green.open);
```
Raw escape codes (i.e. without the CSI escape prefix `\u001B[` and render mode postfix `m`) are available under `style.codes`, which returns a `Map` with the open codes as keys and close codes as values.
###### Example
```js
console.log(style.codes.get(36));
//=> 39
```
## [256 / 16 million (TrueColor) support](https://gist.github.com/XVilka/8346728)
`ansi-styles` uses the [`color-convert`](https://github.com/Qix-/color-convert) package to allow for converting between various colors and ANSI escapes, with support for 256 and 16 million colors.
The following color spaces from `color-convert` are supported:
- `rgb`
- `hex`
- `keyword`
- `hsl`
- `hsv`
- `hwb`
- `ansi`
- `ansi256`
To use these, call the associated conversion function with the intended output, for example:
```js
style.color.ansi.rgb(100, 200, 15); // RGB to 16 color ansi foreground code
style.bgColor.ansi.rgb(100, 200, 15); // RGB to 16 color ansi background code
style.color.ansi256.hsl(120, 100, 60); // HSL to 256 color ansi foreground code
style.bgColor.ansi256.hsl(120, 100, 60); // HSL to 256 color ansi foreground code
style.color.ansi16m.hex('#C0FFEE'); // Hex (RGB) to 16 million color foreground code
style.bgColor.ansi16m.hex('#C0FFEE'); // Hex (RGB) to 16 million color background code
```
## Related
- [ansi-escapes](https://github.com/sindresorhus/ansi-escapes) - ANSI escape codes for manipulating the terminal
## Maintainers
- [Sindre Sorhus](https://github.com/sindresorhus)
- [Josh Junon](https://github.com/qix-)
## For enterprise
Available as part of the Tidelift Subscription.
The maintainers of `ansi-styles` and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-ansi-styles?utm_source=npm-ansi-styles&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)

View File

@@ -0,0 +1,415 @@
/**
Basic foreground colors.
[More colors here.](https://github.com/chalk/chalk/blob/master/readme.md#256-and-truecolor-color-support)
*/
declare type ForegroundColor =
| 'black'
| 'red'
| 'green'
| 'yellow'
| 'blue'
| 'magenta'
| 'cyan'
| 'white'
| 'gray'
| 'grey'
| 'blackBright'
| 'redBright'
| 'greenBright'
| 'yellowBright'
| 'blueBright'
| 'magentaBright'
| 'cyanBright'
| 'whiteBright';
/**
Basic background colors.
[More colors here.](https://github.com/chalk/chalk/blob/master/readme.md#256-and-truecolor-color-support)
*/
declare type BackgroundColor =
| 'bgBlack'
| 'bgRed'
| 'bgGreen'
| 'bgYellow'
| 'bgBlue'
| 'bgMagenta'
| 'bgCyan'
| 'bgWhite'
| 'bgGray'
| 'bgGrey'
| 'bgBlackBright'
| 'bgRedBright'
| 'bgGreenBright'
| 'bgYellowBright'
| 'bgBlueBright'
| 'bgMagentaBright'
| 'bgCyanBright'
| 'bgWhiteBright';
/**
Basic colors.
[More colors here.](https://github.com/chalk/chalk/blob/master/readme.md#256-and-truecolor-color-support)
*/
declare type Color = ForegroundColor | BackgroundColor;
declare type Modifiers =
| 'reset'
| 'bold'
| 'dim'
| 'italic'
| 'underline'
| 'inverse'
| 'hidden'
| 'strikethrough'
| 'visible';
declare namespace chalk {
/**
Levels:
- `0` - All colors disabled.
- `1` - Basic 16 colors support.
- `2` - ANSI 256 colors support.
- `3` - Truecolor 16 million colors support.
*/
type Level = 0 | 1 | 2 | 3;
interface Options {
/**
Specify the color support for Chalk.
By default, color support is automatically detected based on the environment.
Levels:
- `0` - All colors disabled.
- `1` - Basic 16 colors support.
- `2` - ANSI 256 colors support.
- `3` - Truecolor 16 million colors support.
*/
level?: Level;
}
/**
Return a new Chalk instance.
*/
type Instance = new (options?: Options) => Chalk;
/**
Detect whether the terminal supports color.
*/
interface ColorSupport {
/**
The color level used by Chalk.
*/
level: Level;
/**
Return whether Chalk supports basic 16 colors.
*/
hasBasic: boolean;
/**
Return whether Chalk supports ANSI 256 colors.
*/
has256: boolean;
/**
Return whether Chalk supports Truecolor 16 million colors.
*/
has16m: boolean;
}
interface ChalkFunction {
/**
Use a template string.
@remarks Template literals are unsupported for nested calls (see [issue #341](https://github.com/chalk/chalk/issues/341))
@example
```
import chalk = require('chalk');
log(chalk`
CPU: {red ${cpu.totalPercent}%}
RAM: {green ${ram.used / ram.total * 100}%}
DISK: {rgb(255,131,0) ${disk.used / disk.total * 100}%}
`);
```
@example
```
import chalk = require('chalk');
log(chalk.red.bgBlack`2 + 3 = {bold ${2 + 3}}`)
```
*/
(text: TemplateStringsArray, ...placeholders: unknown[]): string;
(...text: unknown[]): string;
}
interface Chalk extends ChalkFunction {
/**
Return a new Chalk instance.
*/
Instance: Instance;
/**
The color support for Chalk.
By default, color support is automatically detected based on the environment.
Levels:
- `0` - All colors disabled.
- `1` - Basic 16 colors support.
- `2` - ANSI 256 colors support.
- `3` - Truecolor 16 million colors support.
*/
level: Level;
/**
Use HEX value to set text color.
@param color - Hexadecimal value representing the desired color.
@example
```
import chalk = require('chalk');
chalk.hex('#DEADED');
```
*/
hex(color: string): Chalk;
/**
Use keyword color value to set text color.
@param color - Keyword value representing the desired color.
@example
```
import chalk = require('chalk');
chalk.keyword('orange');
```
*/
keyword(color: string): Chalk;
/**
Use RGB values to set text color.
*/
rgb(red: number, green: number, blue: number): Chalk;
/**
Use HSL values to set text color.
*/
hsl(hue: number, saturation: number, lightness: number): Chalk;
/**
Use HSV values to set text color.
*/
hsv(hue: number, saturation: number, value: number): Chalk;
/**
Use HWB values to set text color.
*/
hwb(hue: number, whiteness: number, blackness: number): Chalk;
/**
Use a [Select/Set Graphic Rendition](https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters) (SGR) [color code number](https://en.wikipedia.org/wiki/ANSI_escape_code#3/4_bit) to set text color.
30 <= code && code < 38 || 90 <= code && code < 98
For example, 31 for red, 91 for redBright.
*/
ansi(code: number): Chalk;
/**
Use a [8-bit unsigned number](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit) to set text color.
*/
ansi256(index: number): Chalk;
/**
Use HEX value to set background color.
@param color - Hexadecimal value representing the desired color.
@example
```
import chalk = require('chalk');
chalk.bgHex('#DEADED');
```
*/
bgHex(color: string): Chalk;
/**
Use keyword color value to set background color.
@param color - Keyword value representing the desired color.
@example
```
import chalk = require('chalk');
chalk.bgKeyword('orange');
```
*/
bgKeyword(color: string): Chalk;
/**
Use RGB values to set background color.
*/
bgRgb(red: number, green: number, blue: number): Chalk;
/**
Use HSL values to set background color.
*/
bgHsl(hue: number, saturation: number, lightness: number): Chalk;
/**
Use HSV values to set background color.
*/
bgHsv(hue: number, saturation: number, value: number): Chalk;
/**
Use HWB values to set background color.
*/
bgHwb(hue: number, whiteness: number, blackness: number): Chalk;
/**
Use a [Select/Set Graphic Rendition](https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters) (SGR) [color code number](https://en.wikipedia.org/wiki/ANSI_escape_code#3/4_bit) to set background color.
30 <= code && code < 38 || 90 <= code && code < 98
For example, 31 for red, 91 for redBright.
Use the foreground code, not the background code (for example, not 41, nor 101).
*/
bgAnsi(code: number): Chalk;
/**
Use a [8-bit unsigned number](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit) to set background color.
*/
bgAnsi256(index: number): Chalk;
/**
Modifier: Resets the current color chain.
*/
readonly reset: Chalk;
/**
Modifier: Make text bold.
*/
readonly bold: Chalk;
/**
Modifier: Emitting only a small amount of light.
*/
readonly dim: Chalk;
/**
Modifier: Make text italic. (Not widely supported)
*/
readonly italic: Chalk;
/**
Modifier: Make text underline. (Not widely supported)
*/
readonly underline: Chalk;
/**
Modifier: Inverse background and foreground colors.
*/
readonly inverse: Chalk;
/**
Modifier: Prints the text, but makes it invisible.
*/
readonly hidden: Chalk;
/**
Modifier: Puts a horizontal line through the center of the text. (Not widely supported)
*/
readonly strikethrough: Chalk;
/**
Modifier: Prints the text only when Chalk has a color support level > 0.
Can be useful for things that are purely cosmetic.
*/
readonly visible: Chalk;
readonly black: Chalk;
readonly red: Chalk;
readonly green: Chalk;
readonly yellow: Chalk;
readonly blue: Chalk;
readonly magenta: Chalk;
readonly cyan: Chalk;
readonly white: Chalk;
/*
Alias for `blackBright`.
*/
readonly gray: Chalk;
/*
Alias for `blackBright`.
*/
readonly grey: Chalk;
readonly blackBright: Chalk;
readonly redBright: Chalk;
readonly greenBright: Chalk;
readonly yellowBright: Chalk;
readonly blueBright: Chalk;
readonly magentaBright: Chalk;
readonly cyanBright: Chalk;
readonly whiteBright: Chalk;
readonly bgBlack: Chalk;
readonly bgRed: Chalk;
readonly bgGreen: Chalk;
readonly bgYellow: Chalk;
readonly bgBlue: Chalk;
readonly bgMagenta: Chalk;
readonly bgCyan: Chalk;
readonly bgWhite: Chalk;
/*
Alias for `bgBlackBright`.
*/
readonly bgGray: Chalk;
/*
Alias for `bgBlackBright`.
*/
readonly bgGrey: Chalk;
readonly bgBlackBright: Chalk;
readonly bgRedBright: Chalk;
readonly bgGreenBright: Chalk;
readonly bgYellowBright: Chalk;
readonly bgBlueBright: Chalk;
readonly bgMagentaBright: Chalk;
readonly bgCyanBright: Chalk;
readonly bgWhiteBright: Chalk;
}
}
/**
Main Chalk object that allows to chain styles together.
Call the last one as a method with a string argument.
Order doesn't matter, and later styles take precedent in case of a conflict.
This simply means that `chalk.red.yellow.green` is equivalent to `chalk.green`.
*/
declare const chalk: chalk.Chalk & chalk.ChalkFunction & {
supportsColor: chalk.ColorSupport | false;
Level: chalk.Level;
Color: Color;
ForegroundColor: ForegroundColor;
BackgroundColor: BackgroundColor;
Modifiers: Modifiers;
stderr: chalk.Chalk & {supportsColor: chalk.ColorSupport | false};
};
export = chalk;

View File

@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,68 @@
{
"name": "chalk",
"version": "4.1.2",
"description": "Terminal string styling done right",
"license": "MIT",
"repository": "chalk/chalk",
"funding": "https://github.com/chalk/chalk?sponsor=1",
"main": "source",
"engines": {
"node": ">=10"
},
"scripts": {
"test": "xo && nyc ava && tsd",
"bench": "matcha benchmark.js"
},
"files": [
"source",
"index.d.ts"
],
"keywords": [
"color",
"colour",
"colors",
"terminal",
"console",
"cli",
"string",
"str",
"ansi",
"style",
"styles",
"tty",
"formatting",
"rgb",
"256",
"shell",
"xterm",
"log",
"logging",
"command-line",
"text"
],
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"devDependencies": {
"ava": "^2.4.0",
"coveralls": "^3.0.7",
"execa": "^4.0.0",
"import-fresh": "^3.1.0",
"matcha": "^0.7.0",
"nyc": "^15.0.0",
"resolve-from": "^5.0.0",
"tsd": "^0.7.4",
"xo": "^0.28.2"
},
"xo": {
"rules": {
"unicorn/prefer-string-slice": "off",
"unicorn/prefer-includes": "off",
"@typescript-eslint/member-ordering": "off",
"no-redeclare": "off",
"unicorn/string-content": "off",
"unicorn/better-regex": "off"
}
}
}

View File

@@ -0,0 +1,341 @@
<h1 align="center">
<br>
<br>
<img width="320" src="media/logo.svg" alt="Chalk">
<br>
<br>
<br>
</h1>
> Terminal string styling done right
[![Build Status](https://travis-ci.org/chalk/chalk.svg?branch=master)](https://travis-ci.org/chalk/chalk) [![Coverage Status](https://coveralls.io/repos/github/chalk/chalk/badge.svg?branch=master)](https://coveralls.io/github/chalk/chalk?branch=master) [![npm dependents](https://badgen.net/npm/dependents/chalk)](https://www.npmjs.com/package/chalk?activeTab=dependents) [![Downloads](https://badgen.net/npm/dt/chalk)](https://www.npmjs.com/package/chalk) [![](https://img.shields.io/badge/unicorn-approved-ff69b4.svg)](https://www.youtube.com/watch?v=9auOCbH5Ns4) [![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/xojs/xo) ![TypeScript-ready](https://img.shields.io/npm/types/chalk.svg) [![run on repl.it](https://repl.it/badge/github/chalk/chalk)](https://repl.it/github/chalk/chalk)
<img src="https://cdn.jsdelivr.net/gh/chalk/ansi-styles@8261697c95bf34b6c7767e2cbe9941a851d59385/screenshot.svg" width="900">
<br>
---
<div align="center">
<p>
<p>
<sup>
Sindre Sorhus' open source work is supported by the community on <a href="https://github.com/sponsors/sindresorhus">GitHub Sponsors</a> and <a href="https://stakes.social/0x44d871aebF0126Bf646753E2C976Aa7e68A66c15">Dev</a>
</sup>
</p>
<sup>Special thanks to:</sup>
<br>
<br>
<a href="https://standardresume.co/tech">
<img src="https://sindresorhus.com/assets/thanks/standard-resume-logo.svg" width="160"/>
</a>
<br>
<br>
<a href="https://retool.com/?utm_campaign=sindresorhus">
<img src="https://sindresorhus.com/assets/thanks/retool-logo.svg" width="230"/>
</a>
<br>
<br>
<a href="https://doppler.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=chalk&utm_source=github">
<div>
<img src="https://dashboard.doppler.com/imgs/logo-long.svg" width="240" alt="Doppler">
</div>
<b>All your environment variables, in one place</b>
<div>
<span>Stop struggling with scattered API keys, hacking together home-brewed tools,</span>
<br>
<span>and avoiding access controls. Keep your team and servers in sync with Doppler.</span>
</div>
</a>
<br>
<a href="https://uibakery.io/?utm_source=chalk&utm_medium=sponsor&utm_campaign=github">
<div>
<img src="https://sindresorhus.com/assets/thanks/uibakery-logo.jpg" width="270" alt="UI Bakery">
</div>
</a>
</p>
</div>
---
<br>
## Highlights
- Expressive API
- Highly performant
- Ability to nest styles
- [256/Truecolor color support](#256-and-truecolor-color-support)
- Auto-detects color support
- Doesn't extend `String.prototype`
- Clean and focused
- Actively maintained
- [Used by ~50,000 packages](https://www.npmjs.com/browse/depended/chalk) as of January 1, 2020
## Install
```console
$ npm install chalk
```
## Usage
```js
const chalk = require('chalk');
console.log(chalk.blue('Hello world!'));
```
Chalk comes with an easy to use composable API where you just chain and nest the styles you want.
```js
const chalk = require('chalk');
const log = console.log;
// Combine styled and normal strings
log(chalk.blue('Hello') + ' World' + chalk.red('!'));
// Compose multiple styles using the chainable API
log(chalk.blue.bgRed.bold('Hello world!'));
// Pass in multiple arguments
log(chalk.blue('Hello', 'World!', 'Foo', 'bar', 'biz', 'baz'));
// Nest styles
log(chalk.red('Hello', chalk.underline.bgBlue('world') + '!'));
// Nest styles of the same type even (color, underline, background)
log(chalk.green(
'I am a green line ' +
chalk.blue.underline.bold('with a blue substring') +
' that becomes green again!'
));
// ES2015 template literal
log(`
CPU: ${chalk.red('90%')}
RAM: ${chalk.green('40%')}
DISK: ${chalk.yellow('70%')}
`);
// ES2015 tagged template literal
log(chalk`
CPU: {red ${cpu.totalPercent}%}
RAM: {green ${ram.used / ram.total * 100}%}
DISK: {rgb(255,131,0) ${disk.used / disk.total * 100}%}
`);
// Use RGB colors in terminal emulators that support it.
log(chalk.keyword('orange')('Yay for orange colored text!'));
log(chalk.rgb(123, 45, 67).underline('Underlined reddish color'));
log(chalk.hex('#DEADED').bold('Bold gray!'));
```
Easily define your own themes:
```js
const chalk = require('chalk');
const error = chalk.bold.red;
const warning = chalk.keyword('orange');
console.log(error('Error!'));
console.log(warning('Warning!'));
```
Take advantage of console.log [string substitution](https://nodejs.org/docs/latest/api/console.html#console_console_log_data_args):
```js
const name = 'Sindre';
console.log(chalk.green('Hello %s'), name);
//=> 'Hello Sindre'
```
## API
### chalk.`<style>[.<style>...](string, [string...])`
Example: `chalk.red.bold.underline('Hello', 'world');`
Chain [styles](#styles) and call the last one as a method with a string argument. Order doesn't matter, and later styles take precedent in case of a conflict. This simply means that `chalk.red.yellow.green` is equivalent to `chalk.green`.
Multiple arguments will be separated by space.
### chalk.level
Specifies the level of color support.
Color support is automatically detected, but you can override it by setting the `level` property. You should however only do this in your own code as it applies globally to all Chalk consumers.
If you need to change this in a reusable module, create a new instance:
```js
const ctx = new chalk.Instance({level: 0});
```
| Level | Description |
| :---: | :--- |
| `0` | All colors disabled |
| `1` | Basic color support (16 colors) |
| `2` | 256 color support |
| `3` | Truecolor support (16 million colors) |
### chalk.supportsColor
Detect whether the terminal [supports color](https://github.com/chalk/supports-color). Used internally and handled for you, but exposed for convenience.
Can be overridden by the user with the flags `--color` and `--no-color`. For situations where using `--color` is not possible, use the environment variable `FORCE_COLOR=1` (level 1), `FORCE_COLOR=2` (level 2), or `FORCE_COLOR=3` (level 3) to forcefully enable color, or `FORCE_COLOR=0` to forcefully disable. The use of `FORCE_COLOR` overrides all other color support checks.
Explicit 256/Truecolor mode can be enabled using the `--color=256` and `--color=16m` flags, respectively.
### chalk.stderr and chalk.stderr.supportsColor
`chalk.stderr` contains a separate instance configured with color support detected for `stderr` stream instead of `stdout`. Override rules from `chalk.supportsColor` apply to this too. `chalk.stderr.supportsColor` is exposed for convenience.
## Styles
### Modifiers
- `reset` - Resets the current color chain.
- `bold` - Make text bold.
- `dim` - Emitting only a small amount of light.
- `italic` - Make text italic. *(Not widely supported)*
- `underline` - Make text underline. *(Not widely supported)*
- `inverse`- Inverse background and foreground colors.
- `hidden` - Prints the text, but makes it invisible.
- `strikethrough` - Puts a horizontal line through the center of the text. *(Not widely supported)*
- `visible`- Prints the text only when Chalk has a color level > 0. Can be useful for things that are purely cosmetic.
### Colors
- `black`
- `red`
- `green`
- `yellow`
- `blue`
- `magenta`
- `cyan`
- `white`
- `blackBright` (alias: `gray`, `grey`)
- `redBright`
- `greenBright`
- `yellowBright`
- `blueBright`
- `magentaBright`
- `cyanBright`
- `whiteBright`
### Background colors
- `bgBlack`
- `bgRed`
- `bgGreen`
- `bgYellow`
- `bgBlue`
- `bgMagenta`
- `bgCyan`
- `bgWhite`
- `bgBlackBright` (alias: `bgGray`, `bgGrey`)
- `bgRedBright`
- `bgGreenBright`
- `bgYellowBright`
- `bgBlueBright`
- `bgMagentaBright`
- `bgCyanBright`
- `bgWhiteBright`
## Tagged template literal
Chalk can be used as a [tagged template literal](https://exploringjs.com/es6/ch_template-literals.html#_tagged-template-literals).
```js
const chalk = require('chalk');
const miles = 18;
const calculateFeet = miles => miles * 5280;
console.log(chalk`
There are {bold 5280 feet} in a mile.
In {bold ${miles} miles}, there are {green.bold ${calculateFeet(miles)} feet}.
`);
```
Blocks are delimited by an opening curly brace (`{`), a style, some content, and a closing curly brace (`}`).
Template styles are chained exactly like normal Chalk styles. The following three statements are equivalent:
```js
console.log(chalk.bold.rgb(10, 100, 200)('Hello!'));
console.log(chalk.bold.rgb(10, 100, 200)`Hello!`);
console.log(chalk`{bold.rgb(10,100,200) Hello!}`);
```
Note that function styles (`rgb()`, `hsl()`, `keyword()`, etc.) may not contain spaces between parameters.
All interpolated values (`` chalk`${foo}` ``) are converted to strings via the `.toString()` method. All curly braces (`{` and `}`) in interpolated value strings are escaped.
## 256 and Truecolor color support
Chalk supports 256 colors and [Truecolor](https://gist.github.com/XVilka/8346728) (16 million colors) on supported terminal apps.
Colors are downsampled from 16 million RGB values to an ANSI color format that is supported by the terminal emulator (or by specifying `{level: n}` as a Chalk option). For example, Chalk configured to run at level 1 (basic color support) will downsample an RGB value of #FF0000 (red) to 31 (ANSI escape for red).
Examples:
- `chalk.hex('#DEADED').underline('Hello, world!')`
- `chalk.keyword('orange')('Some orange text')`
- `chalk.rgb(15, 100, 204).inverse('Hello!')`
Background versions of these models are prefixed with `bg` and the first level of the module capitalized (e.g. `keyword` for foreground colors and `bgKeyword` for background colors).
- `chalk.bgHex('#DEADED').underline('Hello, world!')`
- `chalk.bgKeyword('orange')('Some orange text')`
- `chalk.bgRgb(15, 100, 204).inverse('Hello!')`
The following color models can be used:
- [`rgb`](https://en.wikipedia.org/wiki/RGB_color_model) - Example: `chalk.rgb(255, 136, 0).bold('Orange!')`
- [`hex`](https://en.wikipedia.org/wiki/Web_colors#Hex_triplet) - Example: `chalk.hex('#FF8800').bold('Orange!')`
- [`keyword`](https://www.w3.org/wiki/CSS/Properties/color/keywords) (CSS keywords) - Example: `chalk.keyword('orange').bold('Orange!')`
- [`hsl`](https://en.wikipedia.org/wiki/HSL_and_HSV) - Example: `chalk.hsl(32, 100, 50).bold('Orange!')`
- [`hsv`](https://en.wikipedia.org/wiki/HSL_and_HSV) - Example: `chalk.hsv(32, 100, 100).bold('Orange!')`
- [`hwb`](https://en.wikipedia.org/wiki/HWB_color_model) - Example: `chalk.hwb(32, 0, 50).bold('Orange!')`
- [`ansi`](https://en.wikipedia.org/wiki/ANSI_escape_code#3/4_bit) - Example: `chalk.ansi(31).bgAnsi(93)('red on yellowBright')`
- [`ansi256`](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit) - Example: `chalk.bgAnsi256(194)('Honeydew, more or less')`
## Windows
If you're on Windows, do yourself a favor and use [Windows Terminal](https://github.com/microsoft/terminal) instead of `cmd.exe`.
## Origin story
[colors.js](https://github.com/Marak/colors.js) used to be the most popular string styling module, but it has serious deficiencies like extending `String.prototype` which causes all kinds of [problems](https://github.com/yeoman/yo/issues/68) and the package is unmaintained. Although there are other packages, they either do too much or not enough. Chalk is a clean and focused alternative.
## chalk for enterprise
Available as part of the Tidelift Subscription.
The maintainers of chalk and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-chalk?utm_source=npm-chalk&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
## Related
- [chalk-cli](https://github.com/chalk/chalk-cli) - CLI for this module
- [ansi-styles](https://github.com/chalk/ansi-styles) - ANSI escape codes for styling strings in the terminal
- [supports-color](https://github.com/chalk/supports-color) - Detect whether a terminal supports color
- [strip-ansi](https://github.com/chalk/strip-ansi) - Strip ANSI escape codes
- [strip-ansi-stream](https://github.com/chalk/strip-ansi-stream) - Strip ANSI escape codes from a stream
- [has-ansi](https://github.com/chalk/has-ansi) - Check if a string has ANSI escape codes
- [ansi-regex](https://github.com/chalk/ansi-regex) - Regular expression for matching ANSI escape codes
- [wrap-ansi](https://github.com/chalk/wrap-ansi) - Wordwrap a string with ANSI escape codes
- [slice-ansi](https://github.com/chalk/slice-ansi) - Slice a string with ANSI escape codes
- [color-convert](https://github.com/qix-/color-convert) - Converts colors between different models
- [chalk-animation](https://github.com/bokub/chalk-animation) - Animate strings in the terminal
- [gradient-string](https://github.com/bokub/gradient-string) - Apply color gradients to strings
- [chalk-pipe](https://github.com/LitoMore/chalk-pipe) - Create chalk style schemes with simpler style strings
- [terminal-link](https://github.com/sindresorhus/terminal-link) - Create clickable links in the terminal
## Maintainers
- [Sindre Sorhus](https://github.com/sindresorhus)
- [Josh Junon](https://github.com/qix-)

View File

@@ -0,0 +1,229 @@
'use strict';
const ansiStyles = require('ansi-styles');
const {stdout: stdoutColor, stderr: stderrColor} = require('supports-color');
const {
stringReplaceAll,
stringEncaseCRLFWithFirstIndex
} = require('./util');
const {isArray} = Array;
// `supportsColor.level` → `ansiStyles.color[name]` mapping
const levelMapping = [
'ansi',
'ansi',
'ansi256',
'ansi16m'
];
const styles = Object.create(null);
const applyOptions = (object, options = {}) => {
if (options.level && !(Number.isInteger(options.level) && options.level >= 0 && options.level <= 3)) {
throw new Error('The `level` option should be an integer from 0 to 3');
}
// Detect level if not set manually
const colorLevel = stdoutColor ? stdoutColor.level : 0;
object.level = options.level === undefined ? colorLevel : options.level;
};
class ChalkClass {
constructor(options) {
// eslint-disable-next-line no-constructor-return
return chalkFactory(options);
}
}
const chalkFactory = options => {
const chalk = {};
applyOptions(chalk, options);
chalk.template = (...arguments_) => chalkTag(chalk.template, ...arguments_);
Object.setPrototypeOf(chalk, Chalk.prototype);
Object.setPrototypeOf(chalk.template, chalk);
chalk.template.constructor = () => {
throw new Error('`chalk.constructor()` is deprecated. Use `new chalk.Instance()` instead.');
};
chalk.template.Instance = ChalkClass;
return chalk.template;
};
function Chalk(options) {
return chalkFactory(options);
}
for (const [styleName, style] of Object.entries(ansiStyles)) {
styles[styleName] = {
get() {
const builder = createBuilder(this, createStyler(style.open, style.close, this._styler), this._isEmpty);
Object.defineProperty(this, styleName, {value: builder});
return builder;
}
};
}
styles.visible = {
get() {
const builder = createBuilder(this, this._styler, true);
Object.defineProperty(this, 'visible', {value: builder});
return builder;
}
};
const usedModels = ['rgb', 'hex', 'keyword', 'hsl', 'hsv', 'hwb', 'ansi', 'ansi256'];
for (const model of usedModels) {
styles[model] = {
get() {
const {level} = this;
return function (...arguments_) {
const styler = createStyler(ansiStyles.color[levelMapping[level]][model](...arguments_), ansiStyles.color.close, this._styler);
return createBuilder(this, styler, this._isEmpty);
};
}
};
}
for (const model of usedModels) {
const bgModel = 'bg' + model[0].toUpperCase() + model.slice(1);
styles[bgModel] = {
get() {
const {level} = this;
return function (...arguments_) {
const styler = createStyler(ansiStyles.bgColor[levelMapping[level]][model](...arguments_), ansiStyles.bgColor.close, this._styler);
return createBuilder(this, styler, this._isEmpty);
};
}
};
}
const proto = Object.defineProperties(() => {}, {
...styles,
level: {
enumerable: true,
get() {
return this._generator.level;
},
set(level) {
this._generator.level = level;
}
}
});
const createStyler = (open, close, parent) => {
let openAll;
let closeAll;
if (parent === undefined) {
openAll = open;
closeAll = close;
} else {
openAll = parent.openAll + open;
closeAll = close + parent.closeAll;
}
return {
open,
close,
openAll,
closeAll,
parent
};
};
const createBuilder = (self, _styler, _isEmpty) => {
const builder = (...arguments_) => {
if (isArray(arguments_[0]) && isArray(arguments_[0].raw)) {
// Called as a template literal, for example: chalk.red`2 + 3 = {bold ${2+3}}`
return applyStyle(builder, chalkTag(builder, ...arguments_));
}
// Single argument is hot path, implicit coercion is faster than anything
// eslint-disable-next-line no-implicit-coercion
return applyStyle(builder, (arguments_.length === 1) ? ('' + arguments_[0]) : arguments_.join(' '));
};
// We alter the prototype because we must return a function, but there is
// no way to create a function with a different prototype
Object.setPrototypeOf(builder, proto);
builder._generator = self;
builder._styler = _styler;
builder._isEmpty = _isEmpty;
return builder;
};
const applyStyle = (self, string) => {
if (self.level <= 0 || !string) {
return self._isEmpty ? '' : string;
}
let styler = self._styler;
if (styler === undefined) {
return string;
}
const {openAll, closeAll} = styler;
if (string.indexOf('\u001B') !== -1) {
while (styler !== undefined) {
// Replace any instances already present with a re-opening code
// otherwise only the part of the string until said closing code
// will be colored, and the rest will simply be 'plain'.
string = stringReplaceAll(string, styler.close, styler.open);
styler = styler.parent;
}
}
// We can move both next actions out of loop, because remaining actions in loop won't have
// any/visible effect on parts we add here. Close the styling before a linebreak and reopen
// after next line to fix a bleed issue on macOS: https://github.com/chalk/chalk/pull/92
const lfIndex = string.indexOf('\n');
if (lfIndex !== -1) {
string = stringEncaseCRLFWithFirstIndex(string, closeAll, openAll, lfIndex);
}
return openAll + string + closeAll;
};
let template;
const chalkTag = (chalk, ...strings) => {
const [firstString] = strings;
if (!isArray(firstString) || !isArray(firstString.raw)) {
// If chalk() was called by itself or with a string,
// return the string itself as a string.
return strings.join(' ');
}
const arguments_ = strings.slice(1);
const parts = [firstString.raw[0]];
for (let i = 1; i < firstString.length; i++) {
parts.push(
String(arguments_[i - 1]).replace(/[{}\\]/g, '\\$&'),
String(firstString.raw[i])
);
}
if (template === undefined) {
template = require('./templates');
}
return template(chalk, parts.join(''));
};
Object.defineProperties(Chalk.prototype, styles);
const chalk = Chalk(); // eslint-disable-line new-cap
chalk.supportsColor = stdoutColor;
chalk.stderr = Chalk({level: stderrColor ? stderrColor.level : 0}); // eslint-disable-line new-cap
chalk.stderr.supportsColor = stderrColor;
module.exports = chalk;

View File

@@ -0,0 +1,134 @@
'use strict';
const TEMPLATE_REGEX = /(?:\\(u(?:[a-f\d]{4}|\{[a-f\d]{1,6}\})|x[a-f\d]{2}|.))|(?:\{(~)?(\w+(?:\([^)]*\))?(?:\.\w+(?:\([^)]*\))?)*)(?:[ \t]|(?=\r?\n)))|(\})|((?:.|[\r\n\f])+?)/gi;
const STYLE_REGEX = /(?:^|\.)(\w+)(?:\(([^)]*)\))?/g;
const STRING_REGEX = /^(['"])((?:\\.|(?!\1)[^\\])*)\1$/;
const ESCAPE_REGEX = /\\(u(?:[a-f\d]{4}|{[a-f\d]{1,6}})|x[a-f\d]{2}|.)|([^\\])/gi;
const ESCAPES = new Map([
['n', '\n'],
['r', '\r'],
['t', '\t'],
['b', '\b'],
['f', '\f'],
['v', '\v'],
['0', '\0'],
['\\', '\\'],
['e', '\u001B'],
['a', '\u0007']
]);
function unescape(c) {
const u = c[0] === 'u';
const bracket = c[1] === '{';
if ((u && !bracket && c.length === 5) || (c[0] === 'x' && c.length === 3)) {
return String.fromCharCode(parseInt(c.slice(1), 16));
}
if (u && bracket) {
return String.fromCodePoint(parseInt(c.slice(2, -1), 16));
}
return ESCAPES.get(c) || c;
}
function parseArguments(name, arguments_) {
const results = [];
const chunks = arguments_.trim().split(/\s*,\s*/g);
let matches;
for (const chunk of chunks) {
const number = Number(chunk);
if (!Number.isNaN(number)) {
results.push(number);
} else if ((matches = chunk.match(STRING_REGEX))) {
results.push(matches[2].replace(ESCAPE_REGEX, (m, escape, character) => escape ? unescape(escape) : character));
} else {
throw new Error(`Invalid Chalk template style argument: ${chunk} (in style '${name}')`);
}
}
return results;
}
function parseStyle(style) {
STYLE_REGEX.lastIndex = 0;
const results = [];
let matches;
while ((matches = STYLE_REGEX.exec(style)) !== null) {
const name = matches[1];
if (matches[2]) {
const args = parseArguments(name, matches[2]);
results.push([name].concat(args));
} else {
results.push([name]);
}
}
return results;
}
function buildStyle(chalk, styles) {
const enabled = {};
for (const layer of styles) {
for (const style of layer.styles) {
enabled[style[0]] = layer.inverse ? null : style.slice(1);
}
}
let current = chalk;
for (const [styleName, styles] of Object.entries(enabled)) {
if (!Array.isArray(styles)) {
continue;
}
if (!(styleName in current)) {
throw new Error(`Unknown Chalk style: ${styleName}`);
}
current = styles.length > 0 ? current[styleName](...styles) : current[styleName];
}
return current;
}
module.exports = (chalk, temporary) => {
const styles = [];
const chunks = [];
let chunk = [];
// eslint-disable-next-line max-params
temporary.replace(TEMPLATE_REGEX, (m, escapeCharacter, inverse, style, close, character) => {
if (escapeCharacter) {
chunk.push(unescape(escapeCharacter));
} else if (style) {
const string = chunk.join('');
chunk = [];
chunks.push(styles.length === 0 ? string : buildStyle(chalk, styles)(string));
styles.push({inverse, styles: parseStyle(style)});
} else if (close) {
if (styles.length === 0) {
throw new Error('Found extraneous } in Chalk template literal');
}
chunks.push(buildStyle(chalk, styles)(chunk.join('')));
chunk = [];
styles.pop();
} else {
chunk.push(character);
}
});
chunks.push(chunk.join(''));
if (styles.length > 0) {
const errMessage = `Chalk template literal is missing ${styles.length} closing bracket${styles.length === 1 ? '' : 's'} (\`}\`)`;
throw new Error(errMessage);
}
return chunks.join('');
};

View File

@@ -0,0 +1,39 @@
'use strict';
const stringReplaceAll = (string, substring, replacer) => {
let index = string.indexOf(substring);
if (index === -1) {
return string;
}
const substringLength = substring.length;
let endIndex = 0;
let returnValue = '';
do {
returnValue += string.substr(endIndex, index - endIndex) + substring + replacer;
endIndex = index + substringLength;
index = string.indexOf(substring, endIndex);
} while (index !== -1);
returnValue += string.substr(endIndex);
return returnValue;
};
const stringEncaseCRLFWithFirstIndex = (string, prefix, postfix, index) => {
let endIndex = 0;
let returnValue = '';
do {
const gotCR = string[index - 1] === '\r';
returnValue += string.substr(endIndex, (gotCR ? index - 1 : index) - endIndex) + prefix + (gotCR ? '\r\n' : '\n') + postfix;
endIndex = index + 1;
index = string.indexOf('\n', endIndex);
} while (index !== -1);
returnValue += string.substr(endIndex);
return returnValue;
};
module.exports = {
stringReplaceAll,
stringEncaseCRLFWithFirstIndex
};

View File

@@ -0,0 +1,54 @@
# 1.0.0 - 2016-01-07
- Removed: unused speed test
- Added: Automatic routing between previously unsupported conversions
([#27](https://github.com/Qix-/color-convert/pull/27))
- Removed: `xxx2xxx()` and `xxx2xxxRaw()` functions
([#27](https://github.com/Qix-/color-convert/pull/27))
- Removed: `convert()` class
([#27](https://github.com/Qix-/color-convert/pull/27))
- Changed: all functions to lookup dictionary
([#27](https://github.com/Qix-/color-convert/pull/27))
- Changed: `ansi` to `ansi256`
([#27](https://github.com/Qix-/color-convert/pull/27))
- Fixed: argument grouping for functions requiring only one argument
([#27](https://github.com/Qix-/color-convert/pull/27))
# 0.6.0 - 2015-07-23
- Added: methods to handle
[ANSI](https://en.wikipedia.org/wiki/ANSI_escape_code#Colors) 16/256 colors:
- rgb2ansi16
- rgb2ansi
- hsl2ansi16
- hsl2ansi
- hsv2ansi16
- hsv2ansi
- hwb2ansi16
- hwb2ansi
- cmyk2ansi16
- cmyk2ansi
- keyword2ansi16
- keyword2ansi
- ansi162rgb
- ansi162hsl
- ansi162hsv
- ansi162hwb
- ansi162cmyk
- ansi162keyword
- ansi2rgb
- ansi2hsl
- ansi2hsv
- ansi2hwb
- ansi2cmyk
- ansi2keyword
([#18](https://github.com/harthur/color-convert/pull/18))
# 0.5.3 - 2015-06-02
- Fixed: hsl2hsv does not return `NaN` anymore when using `[0,0,0]`
([#15](https://github.com/harthur/color-convert/issues/15))
---
Check out commit logs for older releases

View File

@@ -0,0 +1,21 @@
Copyright (c) 2011-2016 Heather Arthur <fayearthur@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,68 @@
# color-convert
[![Build Status](https://travis-ci.org/Qix-/color-convert.svg?branch=master)](https://travis-ci.org/Qix-/color-convert)
Color-convert is a color conversion library for JavaScript and node.
It converts all ways between `rgb`, `hsl`, `hsv`, `hwb`, `cmyk`, `ansi`, `ansi16`, `hex` strings, and CSS `keyword`s (will round to closest):
```js
var convert = require('color-convert');
convert.rgb.hsl(140, 200, 100); // [96, 48, 59]
convert.keyword.rgb('blue'); // [0, 0, 255]
var rgbChannels = convert.rgb.channels; // 3
var cmykChannels = convert.cmyk.channels; // 4
var ansiChannels = convert.ansi16.channels; // 1
```
# Install
```console
$ npm install color-convert
```
# API
Simply get the property of the _from_ and _to_ conversion that you're looking for.
All functions have a rounded and unrounded variant. By default, return values are rounded. To get the unrounded (raw) results, simply tack on `.raw` to the function.
All 'from' functions have a hidden property called `.channels` that indicates the number of channels the function expects (not including alpha).
```js
var convert = require('color-convert');
// Hex to LAB
convert.hex.lab('DEADBF'); // [ 76, 21, -2 ]
convert.hex.lab.raw('DEADBF'); // [ 75.56213190997677, 20.653827952644754, -2.290532499330533 ]
// RGB to CMYK
convert.rgb.cmyk(167, 255, 4); // [ 35, 0, 98, 0 ]
convert.rgb.cmyk.raw(167, 255, 4); // [ 34.509803921568626, 0, 98.43137254901961, 0 ]
```
### Arrays
All functions that accept multiple arguments also support passing an array.
Note that this does **not** apply to functions that convert from a color that only requires one value (e.g. `keyword`, `ansi256`, `hex`, etc.)
```js
var convert = require('color-convert');
convert.rgb.hex(123, 45, 67); // '7B2D43'
convert.rgb.hex([123, 45, 67]); // '7B2D43'
```
## Routing
Conversions that don't have an _explicitly_ defined conversion (in [conversions.js](conversions.js)), but can be converted by means of sub-conversions (e.g. XYZ -> **RGB** -> CMYK), are automatically routed together. This allows just about any color model supported by `color-convert` to be converted to any other model, so long as a sub-conversion path exists. This is also true for conversions requiring more than one step in between (e.g. LCH -> **LAB** -> **XYZ** -> **RGB** -> Hex).
Keep in mind that extensive conversions _may_ result in a loss of precision, and exist only to be complete. For a list of "direct" (single-step) conversions, see [conversions.js](conversions.js).
# Contribute
If there is a new model you would like to support, or want to add a direct conversion between two existing models, please send us a pull request.
# License
Copyright &copy; 2011-2016, Heather Arthur and Josh Junon. Licensed under the [MIT License](LICENSE).

View File

@@ -0,0 +1,839 @@
/* MIT license */
/* eslint-disable no-mixed-operators */
const cssKeywords = require('color-name');
// NOTE: conversions should only return primitive values (i.e. arrays, or
// values that give correct `typeof` results).
// do not use box values types (i.e. Number(), String(), etc.)
const reverseKeywords = {};
for (const key of Object.keys(cssKeywords)) {
reverseKeywords[cssKeywords[key]] = key;
}
const convert = {
rgb: {channels: 3, labels: 'rgb'},
hsl: {channels: 3, labels: 'hsl'},
hsv: {channels: 3, labels: 'hsv'},
hwb: {channels: 3, labels: 'hwb'},
cmyk: {channels: 4, labels: 'cmyk'},
xyz: {channels: 3, labels: 'xyz'},
lab: {channels: 3, labels: 'lab'},
lch: {channels: 3, labels: 'lch'},
hex: {channels: 1, labels: ['hex']},
keyword: {channels: 1, labels: ['keyword']},
ansi16: {channels: 1, labels: ['ansi16']},
ansi256: {channels: 1, labels: ['ansi256']},
hcg: {channels: 3, labels: ['h', 'c', 'g']},
apple: {channels: 3, labels: ['r16', 'g16', 'b16']},
gray: {channels: 1, labels: ['gray']}
};
module.exports = convert;
// Hide .channels and .labels properties
for (const model of Object.keys(convert)) {
if (!('channels' in convert[model])) {
throw new Error('missing channels property: ' + model);
}
if (!('labels' in convert[model])) {
throw new Error('missing channel labels property: ' + model);
}
if (convert[model].labels.length !== convert[model].channels) {
throw new Error('channel and label counts mismatch: ' + model);
}
const {channels, labels} = convert[model];
delete convert[model].channels;
delete convert[model].labels;
Object.defineProperty(convert[model], 'channels', {value: channels});
Object.defineProperty(convert[model], 'labels', {value: labels});
}
convert.rgb.hsl = function (rgb) {
const r = rgb[0] / 255;
const g = rgb[1] / 255;
const b = rgb[2] / 255;
const min = Math.min(r, g, b);
const max = Math.max(r, g, b);
const delta = max - min;
let h;
let s;
if (max === min) {
h = 0;
} else if (r === max) {
h = (g - b) / delta;
} else if (g === max) {
h = 2 + (b - r) / delta;
} else if (b === max) {
h = 4 + (r - g) / delta;
}
h = Math.min(h * 60, 360);
if (h < 0) {
h += 360;
}
const l = (min + max) / 2;
if (max === min) {
s = 0;
} else if (l <= 0.5) {
s = delta / (max + min);
} else {
s = delta / (2 - max - min);
}
return [h, s * 100, l * 100];
};
convert.rgb.hsv = function (rgb) {
let rdif;
let gdif;
let bdif;
let h;
let s;
const r = rgb[0] / 255;
const g = rgb[1] / 255;
const b = rgb[2] / 255;
const v = Math.max(r, g, b);
const diff = v - Math.min(r, g, b);
const diffc = function (c) {
return (v - c) / 6 / diff + 1 / 2;
};
if (diff === 0) {
h = 0;
s = 0;
} else {
s = diff / v;
rdif = diffc(r);
gdif = diffc(g);
bdif = diffc(b);
if (r === v) {
h = bdif - gdif;
} else if (g === v) {
h = (1 / 3) + rdif - bdif;
} else if (b === v) {
h = (2 / 3) + gdif - rdif;
}
if (h < 0) {
h += 1;
} else if (h > 1) {
h -= 1;
}
}
return [
h * 360,
s * 100,
v * 100
];
};
convert.rgb.hwb = function (rgb) {
const r = rgb[0];
const g = rgb[1];
let b = rgb[2];
const h = convert.rgb.hsl(rgb)[0];
const w = 1 / 255 * Math.min(r, Math.min(g, b));
b = 1 - 1 / 255 * Math.max(r, Math.max(g, b));
return [h, w * 100, b * 100];
};
convert.rgb.cmyk = function (rgb) {
const r = rgb[0] / 255;
const g = rgb[1] / 255;
const b = rgb[2] / 255;
const k = Math.min(1 - r, 1 - g, 1 - b);
const c = (1 - r - k) / (1 - k) || 0;
const m = (1 - g - k) / (1 - k) || 0;
const y = (1 - b - k) / (1 - k) || 0;
return [c * 100, m * 100, y * 100, k * 100];
};
function comparativeDistance(x, y) {
/*
See https://en.m.wikipedia.org/wiki/Euclidean_distance#Squared_Euclidean_distance
*/
return (
((x[0] - y[0]) ** 2) +
((x[1] - y[1]) ** 2) +
((x[2] - y[2]) ** 2)
);
}
convert.rgb.keyword = function (rgb) {
const reversed = reverseKeywords[rgb];
if (reversed) {
return reversed;
}
let currentClosestDistance = Infinity;
let currentClosestKeyword;
for (const keyword of Object.keys(cssKeywords)) {
const value = cssKeywords[keyword];
// Compute comparative distance
const distance = comparativeDistance(rgb, value);
// Check if its less, if so set as closest
if (distance < currentClosestDistance) {
currentClosestDistance = distance;
currentClosestKeyword = keyword;
}
}
return currentClosestKeyword;
};
convert.keyword.rgb = function (keyword) {
return cssKeywords[keyword];
};
convert.rgb.xyz = function (rgb) {
let r = rgb[0] / 255;
let g = rgb[1] / 255;
let b = rgb[2] / 255;
// Assume sRGB
r = r > 0.04045 ? (((r + 0.055) / 1.055) ** 2.4) : (r / 12.92);
g = g > 0.04045 ? (((g + 0.055) / 1.055) ** 2.4) : (g / 12.92);
b = b > 0.04045 ? (((b + 0.055) / 1.055) ** 2.4) : (b / 12.92);
const x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);
const y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);
const z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);
return [x * 100, y * 100, z * 100];
};
convert.rgb.lab = function (rgb) {
const xyz = convert.rgb.xyz(rgb);
let x = xyz[0];
let y = xyz[1];
let z = xyz[2];
x /= 95.047;
y /= 100;
z /= 108.883;
x = x > 0.008856 ? (x ** (1 / 3)) : (7.787 * x) + (16 / 116);
y = y > 0.008856 ? (y ** (1 / 3)) : (7.787 * y) + (16 / 116);
z = z > 0.008856 ? (z ** (1 / 3)) : (7.787 * z) + (16 / 116);
const l = (116 * y) - 16;
const a = 500 * (x - y);
const b = 200 * (y - z);
return [l, a, b];
};
convert.hsl.rgb = function (hsl) {
const h = hsl[0] / 360;
const s = hsl[1] / 100;
const l = hsl[2] / 100;
let t2;
let t3;
let val;
if (s === 0) {
val = l * 255;
return [val, val, val];
}
if (l < 0.5) {
t2 = l * (1 + s);
} else {
t2 = l + s - l * s;
}
const t1 = 2 * l - t2;
const rgb = [0, 0, 0];
for (let i = 0; i < 3; i++) {
t3 = h + 1 / 3 * -(i - 1);
if (t3 < 0) {
t3++;
}
if (t3 > 1) {
t3--;
}
if (6 * t3 < 1) {
val = t1 + (t2 - t1) * 6 * t3;
} else if (2 * t3 < 1) {
val = t2;
} else if (3 * t3 < 2) {
val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
} else {
val = t1;
}
rgb[i] = val * 255;
}
return rgb;
};
convert.hsl.hsv = function (hsl) {
const h = hsl[0];
let s = hsl[1] / 100;
let l = hsl[2] / 100;
let smin = s;
const lmin = Math.max(l, 0.01);
l *= 2;
s *= (l <= 1) ? l : 2 - l;
smin *= lmin <= 1 ? lmin : 2 - lmin;
const v = (l + s) / 2;
const sv = l === 0 ? (2 * smin) / (lmin + smin) : (2 * s) / (l + s);
return [h, sv * 100, v * 100];
};
convert.hsv.rgb = function (hsv) {
const h = hsv[0] / 60;
const s = hsv[1] / 100;
let v = hsv[2] / 100;
const hi = Math.floor(h) % 6;
const f = h - Math.floor(h);
const p = 255 * v * (1 - s);
const q = 255 * v * (1 - (s * f));
const t = 255 * v * (1 - (s * (1 - f)));
v *= 255;
switch (hi) {
case 0:
return [v, t, p];
case 1:
return [q, v, p];
case 2:
return [p, v, t];
case 3:
return [p, q, v];
case 4:
return [t, p, v];
case 5:
return [v, p, q];
}
};
convert.hsv.hsl = function (hsv) {
const h = hsv[0];
const s = hsv[1] / 100;
const v = hsv[2] / 100;
const vmin = Math.max(v, 0.01);
let sl;
let l;
l = (2 - s) * v;
const lmin = (2 - s) * vmin;
sl = s * vmin;
sl /= (lmin <= 1) ? lmin : 2 - lmin;
sl = sl || 0;
l /= 2;
return [h, sl * 100, l * 100];
};
// http://dev.w3.org/csswg/css-color/#hwb-to-rgb
convert.hwb.rgb = function (hwb) {
const h = hwb[0] / 360;
let wh = hwb[1] / 100;
let bl = hwb[2] / 100;
const ratio = wh + bl;
let f;
// Wh + bl cant be > 1
if (ratio > 1) {
wh /= ratio;
bl /= ratio;
}
const i = Math.floor(6 * h);
const v = 1 - bl;
f = 6 * h - i;
if ((i & 0x01) !== 0) {
f = 1 - f;
}
const n = wh + f * (v - wh); // Linear interpolation
let r;
let g;
let b;
/* eslint-disable max-statements-per-line,no-multi-spaces */
switch (i) {
default:
case 6:
case 0: r = v; g = n; b = wh; break;
case 1: r = n; g = v; b = wh; break;
case 2: r = wh; g = v; b = n; break;
case 3: r = wh; g = n; b = v; break;
case 4: r = n; g = wh; b = v; break;
case 5: r = v; g = wh; b = n; break;
}
/* eslint-enable max-statements-per-line,no-multi-spaces */
return [r * 255, g * 255, b * 255];
};
convert.cmyk.rgb = function (cmyk) {
const c = cmyk[0] / 100;
const m = cmyk[1] / 100;
const y = cmyk[2] / 100;
const k = cmyk[3] / 100;
const r = 1 - Math.min(1, c * (1 - k) + k);
const g = 1 - Math.min(1, m * (1 - k) + k);
const b = 1 - Math.min(1, y * (1 - k) + k);
return [r * 255, g * 255, b * 255];
};
convert.xyz.rgb = function (xyz) {
const x = xyz[0] / 100;
const y = xyz[1] / 100;
const z = xyz[2] / 100;
let r;
let g;
let b;
r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986);
g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415);
b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570);
// Assume sRGB
r = r > 0.0031308
? ((1.055 * (r ** (1.0 / 2.4))) - 0.055)
: r * 12.92;
g = g > 0.0031308
? ((1.055 * (g ** (1.0 / 2.4))) - 0.055)
: g * 12.92;
b = b > 0.0031308
? ((1.055 * (b ** (1.0 / 2.4))) - 0.055)
: b * 12.92;
r = Math.min(Math.max(0, r), 1);
g = Math.min(Math.max(0, g), 1);
b = Math.min(Math.max(0, b), 1);
return [r * 255, g * 255, b * 255];
};
convert.xyz.lab = function (xyz) {
let x = xyz[0];
let y = xyz[1];
let z = xyz[2];
x /= 95.047;
y /= 100;
z /= 108.883;
x = x > 0.008856 ? (x ** (1 / 3)) : (7.787 * x) + (16 / 116);
y = y > 0.008856 ? (y ** (1 / 3)) : (7.787 * y) + (16 / 116);
z = z > 0.008856 ? (z ** (1 / 3)) : (7.787 * z) + (16 / 116);
const l = (116 * y) - 16;
const a = 500 * (x - y);
const b = 200 * (y - z);
return [l, a, b];
};
convert.lab.xyz = function (lab) {
const l = lab[0];
const a = lab[1];
const b = lab[2];
let x;
let y;
let z;
y = (l + 16) / 116;
x = a / 500 + y;
z = y - b / 200;
const y2 = y ** 3;
const x2 = x ** 3;
const z2 = z ** 3;
y = y2 > 0.008856 ? y2 : (y - 16 / 116) / 7.787;
x = x2 > 0.008856 ? x2 : (x - 16 / 116) / 7.787;
z = z2 > 0.008856 ? z2 : (z - 16 / 116) / 7.787;
x *= 95.047;
y *= 100;
z *= 108.883;
return [x, y, z];
};
convert.lab.lch = function (lab) {
const l = lab[0];
const a = lab[1];
const b = lab[2];
let h;
const hr = Math.atan2(b, a);
h = hr * 360 / 2 / Math.PI;
if (h < 0) {
h += 360;
}
const c = Math.sqrt(a * a + b * b);
return [l, c, h];
};
convert.lch.lab = function (lch) {
const l = lch[0];
const c = lch[1];
const h = lch[2];
const hr = h / 360 * 2 * Math.PI;
const a = c * Math.cos(hr);
const b = c * Math.sin(hr);
return [l, a, b];
};
convert.rgb.ansi16 = function (args, saturation = null) {
const [r, g, b] = args;
let value = saturation === null ? convert.rgb.hsv(args)[2] : saturation; // Hsv -> ansi16 optimization
value = Math.round(value / 50);
if (value === 0) {
return 30;
}
let ansi = 30
+ ((Math.round(b / 255) << 2)
| (Math.round(g / 255) << 1)
| Math.round(r / 255));
if (value === 2) {
ansi += 60;
}
return ansi;
};
convert.hsv.ansi16 = function (args) {
// Optimization here; we already know the value and don't need to get
// it converted for us.
return convert.rgb.ansi16(convert.hsv.rgb(args), args[2]);
};
convert.rgb.ansi256 = function (args) {
const r = args[0];
const g = args[1];
const b = args[2];
// We use the extended greyscale palette here, with the exception of
// black and white. normal palette only has 4 greyscale shades.
if (r === g && g === b) {
if (r < 8) {
return 16;
}
if (r > 248) {
return 231;
}
return Math.round(((r - 8) / 247) * 24) + 232;
}
const ansi = 16
+ (36 * Math.round(r / 255 * 5))
+ (6 * Math.round(g / 255 * 5))
+ Math.round(b / 255 * 5);
return ansi;
};
convert.ansi16.rgb = function (args) {
let color = args % 10;
// Handle greyscale
if (color === 0 || color === 7) {
if (args > 50) {
color += 3.5;
}
color = color / 10.5 * 255;
return [color, color, color];
}
const mult = (~~(args > 50) + 1) * 0.5;
const r = ((color & 1) * mult) * 255;
const g = (((color >> 1) & 1) * mult) * 255;
const b = (((color >> 2) & 1) * mult) * 255;
return [r, g, b];
};
convert.ansi256.rgb = function (args) {
// Handle greyscale
if (args >= 232) {
const c = (args - 232) * 10 + 8;
return [c, c, c];
}
args -= 16;
let rem;
const r = Math.floor(args / 36) / 5 * 255;
const g = Math.floor((rem = args % 36) / 6) / 5 * 255;
const b = (rem % 6) / 5 * 255;
return [r, g, b];
};
convert.rgb.hex = function (args) {
const integer = ((Math.round(args[0]) & 0xFF) << 16)
+ ((Math.round(args[1]) & 0xFF) << 8)
+ (Math.round(args[2]) & 0xFF);
const string = integer.toString(16).toUpperCase();
return '000000'.substring(string.length) + string;
};
convert.hex.rgb = function (args) {
const match = args.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);
if (!match) {
return [0, 0, 0];
}
let colorString = match[0];
if (match[0].length === 3) {
colorString = colorString.split('').map(char => {
return char + char;
}).join('');
}
const integer = parseInt(colorString, 16);
const r = (integer >> 16) & 0xFF;
const g = (integer >> 8) & 0xFF;
const b = integer & 0xFF;
return [r, g, b];
};
convert.rgb.hcg = function (rgb) {
const r = rgb[0] / 255;
const g = rgb[1] / 255;
const b = rgb[2] / 255;
const max = Math.max(Math.max(r, g), b);
const min = Math.min(Math.min(r, g), b);
const chroma = (max - min);
let grayscale;
let hue;
if (chroma < 1) {
grayscale = min / (1 - chroma);
} else {
grayscale = 0;
}
if (chroma <= 0) {
hue = 0;
} else
if (max === r) {
hue = ((g - b) / chroma) % 6;
} else
if (max === g) {
hue = 2 + (b - r) / chroma;
} else {
hue = 4 + (r - g) / chroma;
}
hue /= 6;
hue %= 1;
return [hue * 360, chroma * 100, grayscale * 100];
};
convert.hsl.hcg = function (hsl) {
const s = hsl[1] / 100;
const l = hsl[2] / 100;
const c = l < 0.5 ? (2.0 * s * l) : (2.0 * s * (1.0 - l));
let f = 0;
if (c < 1.0) {
f = (l - 0.5 * c) / (1.0 - c);
}
return [hsl[0], c * 100, f * 100];
};
convert.hsv.hcg = function (hsv) {
const s = hsv[1] / 100;
const v = hsv[2] / 100;
const c = s * v;
let f = 0;
if (c < 1.0) {
f = (v - c) / (1 - c);
}
return [hsv[0], c * 100, f * 100];
};
convert.hcg.rgb = function (hcg) {
const h = hcg[0] / 360;
const c = hcg[1] / 100;
const g = hcg[2] / 100;
if (c === 0.0) {
return [g * 255, g * 255, g * 255];
}
const pure = [0, 0, 0];
const hi = (h % 1) * 6;
const v = hi % 1;
const w = 1 - v;
let mg = 0;
/* eslint-disable max-statements-per-line */
switch (Math.floor(hi)) {
case 0:
pure[0] = 1; pure[1] = v; pure[2] = 0; break;
case 1:
pure[0] = w; pure[1] = 1; pure[2] = 0; break;
case 2:
pure[0] = 0; pure[1] = 1; pure[2] = v; break;
case 3:
pure[0] = 0; pure[1] = w; pure[2] = 1; break;
case 4:
pure[0] = v; pure[1] = 0; pure[2] = 1; break;
default:
pure[0] = 1; pure[1] = 0; pure[2] = w;
}
/* eslint-enable max-statements-per-line */
mg = (1.0 - c) * g;
return [
(c * pure[0] + mg) * 255,
(c * pure[1] + mg) * 255,
(c * pure[2] + mg) * 255
];
};
convert.hcg.hsv = function (hcg) {
const c = hcg[1] / 100;
const g = hcg[2] / 100;
const v = c + g * (1.0 - c);
let f = 0;
if (v > 0.0) {
f = c / v;
}
return [hcg[0], f * 100, v * 100];
};
convert.hcg.hsl = function (hcg) {
const c = hcg[1] / 100;
const g = hcg[2] / 100;
const l = g * (1.0 - c) + 0.5 * c;
let s = 0;
if (l > 0.0 && l < 0.5) {
s = c / (2 * l);
} else
if (l >= 0.5 && l < 1.0) {
s = c / (2 * (1 - l));
}
return [hcg[0], s * 100, l * 100];
};
convert.hcg.hwb = function (hcg) {
const c = hcg[1] / 100;
const g = hcg[2] / 100;
const v = c + g * (1.0 - c);
return [hcg[0], (v - c) * 100, (1 - v) * 100];
};
convert.hwb.hcg = function (hwb) {
const w = hwb[1] / 100;
const b = hwb[2] / 100;
const v = 1 - b;
const c = v - w;
let g = 0;
if (c < 1) {
g = (v - c) / (1 - c);
}
return [hwb[0], c * 100, g * 100];
};
convert.apple.rgb = function (apple) {
return [(apple[0] / 65535) * 255, (apple[1] / 65535) * 255, (apple[2] / 65535) * 255];
};
convert.rgb.apple = function (rgb) {
return [(rgb[0] / 255) * 65535, (rgb[1] / 255) * 65535, (rgb[2] / 255) * 65535];
};
convert.gray.rgb = function (args) {
return [args[0] / 100 * 255, args[0] / 100 * 255, args[0] / 100 * 255];
};
convert.gray.hsl = function (args) {
return [0, 0, args[0]];
};
convert.gray.hsv = convert.gray.hsl;
convert.gray.hwb = function (gray) {
return [0, 100, gray[0]];
};
convert.gray.cmyk = function (gray) {
return [0, 0, 0, gray[0]];
};
convert.gray.lab = function (gray) {
return [gray[0], 0, 0];
};
convert.gray.hex = function (gray) {
const val = Math.round(gray[0] / 100 * 255) & 0xFF;
const integer = (val << 16) + (val << 8) + val;
const string = integer.toString(16).toUpperCase();
return '000000'.substring(string.length) + string;
};
convert.rgb.gray = function (rgb) {
const val = (rgb[0] + rgb[1] + rgb[2]) / 3;
return [val / 255 * 100];
};

View File

@@ -0,0 +1,81 @@
const conversions = require('./conversions');
const route = require('./route');
const convert = {};
const models = Object.keys(conversions);
function wrapRaw(fn) {
const wrappedFn = function (...args) {
const arg0 = args[0];
if (arg0 === undefined || arg0 === null) {
return arg0;
}
if (arg0.length > 1) {
args = arg0;
}
return fn(args);
};
// Preserve .conversion property if there is one
if ('conversion' in fn) {
wrappedFn.conversion = fn.conversion;
}
return wrappedFn;
}
function wrapRounded(fn) {
const wrappedFn = function (...args) {
const arg0 = args[0];
if (arg0 === undefined || arg0 === null) {
return arg0;
}
if (arg0.length > 1) {
args = arg0;
}
const result = fn(args);
// We're assuming the result is an array here.
// see notice in conversions.js; don't use box types
// in conversion functions.
if (typeof result === 'object') {
for (let len = result.length, i = 0; i < len; i++) {
result[i] = Math.round(result[i]);
}
}
return result;
};
// Preserve .conversion property if there is one
if ('conversion' in fn) {
wrappedFn.conversion = fn.conversion;
}
return wrappedFn;
}
models.forEach(fromModel => {
convert[fromModel] = {};
Object.defineProperty(convert[fromModel], 'channels', {value: conversions[fromModel].channels});
Object.defineProperty(convert[fromModel], 'labels', {value: conversions[fromModel].labels});
const routes = route(fromModel);
const routeModels = Object.keys(routes);
routeModels.forEach(toModel => {
const fn = routes[toModel];
convert[fromModel][toModel] = wrapRounded(fn);
convert[fromModel][toModel].raw = wrapRaw(fn);
});
});
module.exports = convert;

View File

@@ -0,0 +1,48 @@
{
"name": "color-convert",
"description": "Plain color conversion functions",
"version": "2.0.1",
"author": "Heather Arthur <fayearthur@gmail.com>",
"license": "MIT",
"repository": "Qix-/color-convert",
"scripts": {
"pretest": "xo",
"test": "node test/basic.js"
},
"engines": {
"node": ">=7.0.0"
},
"keywords": [
"color",
"colour",
"convert",
"converter",
"conversion",
"rgb",
"hsl",
"hsv",
"hwb",
"cmyk",
"ansi",
"ansi16"
],
"files": [
"index.js",
"conversions.js",
"route.js"
],
"xo": {
"rules": {
"default-case": 0,
"no-inline-comments": 0,
"operator-linebreak": 0
}
},
"devDependencies": {
"chalk": "^2.4.2",
"xo": "^0.24.0"
},
"dependencies": {
"color-name": "~1.1.4"
}
}

View File

@@ -0,0 +1,97 @@
const conversions = require('./conversions');
/*
This function routes a model to all other models.
all functions that are routed have a property `.conversion` attached
to the returned synthetic function. This property is an array
of strings, each with the steps in between the 'from' and 'to'
color models (inclusive).
conversions that are not possible simply are not included.
*/
function buildGraph() {
const graph = {};
// https://jsperf.com/object-keys-vs-for-in-with-closure/3
const models = Object.keys(conversions);
for (let len = models.length, i = 0; i < len; i++) {
graph[models[i]] = {
// http://jsperf.com/1-vs-infinity
// micro-opt, but this is simple.
distance: -1,
parent: null
};
}
return graph;
}
// https://en.wikipedia.org/wiki/Breadth-first_search
function deriveBFS(fromModel) {
const graph = buildGraph();
const queue = [fromModel]; // Unshift -> queue -> pop
graph[fromModel].distance = 0;
while (queue.length) {
const current = queue.pop();
const adjacents = Object.keys(conversions[current]);
for (let len = adjacents.length, i = 0; i < len; i++) {
const adjacent = adjacents[i];
const node = graph[adjacent];
if (node.distance === -1) {
node.distance = graph[current].distance + 1;
node.parent = current;
queue.unshift(adjacent);
}
}
}
return graph;
}
function link(from, to) {
return function (args) {
return to(from(args));
};
}
function wrapConversion(toModel, graph) {
const path = [graph[toModel].parent, toModel];
let fn = conversions[graph[toModel].parent][toModel];
let cur = graph[toModel].parent;
while (graph[cur].parent) {
path.unshift(graph[cur].parent);
fn = link(conversions[graph[cur].parent][cur], fn);
cur = graph[cur].parent;
}
fn.conversion = path;
return fn;
}
module.exports = function (fromModel) {
const graph = deriveBFS(fromModel);
const conversion = {};
const models = Object.keys(graph);
for (let len = models.length, i = 0; i < len; i++) {
const toModel = models[i];
const node = graph[toModel];
if (node.parent === null) {
// No possible conversion, or this node is the source model.
continue;
}
conversion[toModel] = wrapConversion(toModel, graph);
}
return conversion;
};

View File

@@ -0,0 +1,8 @@
The MIT License (MIT)
Copyright (c) 2015 Dmitry Ivanov
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,11 @@
A JSON with color names and its values. Based on http://dev.w3.org/csswg/css-color/#named-colors.
[![NPM](https://nodei.co/npm/color-name.png?mini=true)](https://nodei.co/npm/color-name/)
```js
var colors = require('color-name');
colors.red //[255,0,0]
```
<a href="LICENSE"><img src="https://upload.wikimedia.org/wikipedia/commons/0/0c/MIT_logo.svg" width="120"/></a>

View File

@@ -0,0 +1,152 @@
'use strict'
module.exports = {
"aliceblue": [240, 248, 255],
"antiquewhite": [250, 235, 215],
"aqua": [0, 255, 255],
"aquamarine": [127, 255, 212],
"azure": [240, 255, 255],
"beige": [245, 245, 220],
"bisque": [255, 228, 196],
"black": [0, 0, 0],
"blanchedalmond": [255, 235, 205],
"blue": [0, 0, 255],
"blueviolet": [138, 43, 226],
"brown": [165, 42, 42],
"burlywood": [222, 184, 135],
"cadetblue": [95, 158, 160],
"chartreuse": [127, 255, 0],
"chocolate": [210, 105, 30],
"coral": [255, 127, 80],
"cornflowerblue": [100, 149, 237],
"cornsilk": [255, 248, 220],
"crimson": [220, 20, 60],
"cyan": [0, 255, 255],
"darkblue": [0, 0, 139],
"darkcyan": [0, 139, 139],
"darkgoldenrod": [184, 134, 11],
"darkgray": [169, 169, 169],
"darkgreen": [0, 100, 0],
"darkgrey": [169, 169, 169],
"darkkhaki": [189, 183, 107],
"darkmagenta": [139, 0, 139],
"darkolivegreen": [85, 107, 47],
"darkorange": [255, 140, 0],
"darkorchid": [153, 50, 204],
"darkred": [139, 0, 0],
"darksalmon": [233, 150, 122],
"darkseagreen": [143, 188, 143],
"darkslateblue": [72, 61, 139],
"darkslategray": [47, 79, 79],
"darkslategrey": [47, 79, 79],
"darkturquoise": [0, 206, 209],
"darkviolet": [148, 0, 211],
"deeppink": [255, 20, 147],
"deepskyblue": [0, 191, 255],
"dimgray": [105, 105, 105],
"dimgrey": [105, 105, 105],
"dodgerblue": [30, 144, 255],
"firebrick": [178, 34, 34],
"floralwhite": [255, 250, 240],
"forestgreen": [34, 139, 34],
"fuchsia": [255, 0, 255],
"gainsboro": [220, 220, 220],
"ghostwhite": [248, 248, 255],
"gold": [255, 215, 0],
"goldenrod": [218, 165, 32],
"gray": [128, 128, 128],
"green": [0, 128, 0],
"greenyellow": [173, 255, 47],
"grey": [128, 128, 128],
"honeydew": [240, 255, 240],
"hotpink": [255, 105, 180],
"indianred": [205, 92, 92],
"indigo": [75, 0, 130],
"ivory": [255, 255, 240],
"khaki": [240, 230, 140],
"lavender": [230, 230, 250],
"lavenderblush": [255, 240, 245],
"lawngreen": [124, 252, 0],
"lemonchiffon": [255, 250, 205],
"lightblue": [173, 216, 230],
"lightcoral": [240, 128, 128],
"lightcyan": [224, 255, 255],
"lightgoldenrodyellow": [250, 250, 210],
"lightgray": [211, 211, 211],
"lightgreen": [144, 238, 144],
"lightgrey": [211, 211, 211],
"lightpink": [255, 182, 193],
"lightsalmon": [255, 160, 122],
"lightseagreen": [32, 178, 170],
"lightskyblue": [135, 206, 250],
"lightslategray": [119, 136, 153],
"lightslategrey": [119, 136, 153],
"lightsteelblue": [176, 196, 222],
"lightyellow": [255, 255, 224],
"lime": [0, 255, 0],
"limegreen": [50, 205, 50],
"linen": [250, 240, 230],
"magenta": [255, 0, 255],
"maroon": [128, 0, 0],
"mediumaquamarine": [102, 205, 170],
"mediumblue": [0, 0, 205],
"mediumorchid": [186, 85, 211],
"mediumpurple": [147, 112, 219],
"mediumseagreen": [60, 179, 113],
"mediumslateblue": [123, 104, 238],
"mediumspringgreen": [0, 250, 154],
"mediumturquoise": [72, 209, 204],
"mediumvioletred": [199, 21, 133],
"midnightblue": [25, 25, 112],
"mintcream": [245, 255, 250],
"mistyrose": [255, 228, 225],
"moccasin": [255, 228, 181],
"navajowhite": [255, 222, 173],
"navy": [0, 0, 128],
"oldlace": [253, 245, 230],
"olive": [128, 128, 0],
"olivedrab": [107, 142, 35],
"orange": [255, 165, 0],
"orangered": [255, 69, 0],
"orchid": [218, 112, 214],
"palegoldenrod": [238, 232, 170],
"palegreen": [152, 251, 152],
"paleturquoise": [175, 238, 238],
"palevioletred": [219, 112, 147],
"papayawhip": [255, 239, 213],
"peachpuff": [255, 218, 185],
"peru": [205, 133, 63],
"pink": [255, 192, 203],
"plum": [221, 160, 221],
"powderblue": [176, 224, 230],
"purple": [128, 0, 128],
"rebeccapurple": [102, 51, 153],
"red": [255, 0, 0],
"rosybrown": [188, 143, 143],
"royalblue": [65, 105, 225],
"saddlebrown": [139, 69, 19],
"salmon": [250, 128, 114],
"sandybrown": [244, 164, 96],
"seagreen": [46, 139, 87],
"seashell": [255, 245, 238],
"sienna": [160, 82, 45],
"silver": [192, 192, 192],
"skyblue": [135, 206, 235],
"slateblue": [106, 90, 205],
"slategray": [112, 128, 144],
"slategrey": [112, 128, 144],
"snow": [255, 250, 250],
"springgreen": [0, 255, 127],
"steelblue": [70, 130, 180],
"tan": [210, 180, 140],
"teal": [0, 128, 128],
"thistle": [216, 191, 216],
"tomato": [255, 99, 71],
"turquoise": [64, 224, 208],
"violet": [238, 130, 238],
"wheat": [245, 222, 179],
"white": [255, 255, 255],
"whitesmoke": [245, 245, 245],
"yellow": [255, 255, 0],
"yellowgreen": [154, 205, 50]
};

View File

@@ -0,0 +1,28 @@
{
"name": "color-name",
"version": "1.1.4",
"description": "A list of color names and its values",
"main": "index.js",
"files": [
"index.js"
],
"scripts": {
"test": "node test.js"
},
"repository": {
"type": "git",
"url": "git@github.com:colorjs/color-name.git"
},
"keywords": [
"color-name",
"color",
"color-keyword",
"keyword"
],
"author": "DY <dfcreative@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/colorjs/color-name/issues"
},
"homepage": "https://github.com/colorjs/color-name"
}

View File

@@ -0,0 +1,39 @@
/**
Check if [`argv`](https://nodejs.org/docs/latest/api/process.html#process_process_argv) has a specific flag.
@param flag - CLI flag to look for. The `--` prefix is optional.
@param argv - CLI arguments. Default: `process.argv`.
@returns Whether the flag exists.
@example
```
// $ ts-node foo.ts -f --unicorn --foo=bar -- --rainbow
// foo.ts
import hasFlag = require('has-flag');
hasFlag('unicorn');
//=> true
hasFlag('--unicorn');
//=> true
hasFlag('f');
//=> true
hasFlag('-f');
//=> true
hasFlag('foo=bar');
//=> true
hasFlag('foo');
//=> false
hasFlag('rainbow');
//=> false
```
*/
declare function hasFlag(flag: string, argv?: string[]): boolean;
export = hasFlag;

View File

@@ -0,0 +1,8 @@
'use strict';
module.exports = (flag, argv = process.argv) => {
const prefix = flag.startsWith('-') ? '' : (flag.length === 1 ? '-' : '--');
const position = argv.indexOf(prefix + flag);
const terminatorPosition = argv.indexOf('--');
return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
};

View File

@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,46 @@
{
"name": "has-flag",
"version": "4.0.0",
"description": "Check if argv has a specific flag",
"license": "MIT",
"repository": "sindresorhus/has-flag",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
},
"engines": {
"node": ">=8"
},
"scripts": {
"test": "xo && ava && tsd"
},
"files": [
"index.js",
"index.d.ts"
],
"keywords": [
"has",
"check",
"detect",
"contains",
"find",
"flag",
"cli",
"command-line",
"argv",
"process",
"arg",
"args",
"argument",
"arguments",
"getopt",
"minimist",
"optimist"
],
"devDependencies": {
"ava": "^1.4.1",
"tsd": "^0.7.2",
"xo": "^0.24.0"
}
}

View File

@@ -0,0 +1,89 @@
# has-flag [![Build Status](https://travis-ci.org/sindresorhus/has-flag.svg?branch=master)](https://travis-ci.org/sindresorhus/has-flag)
> Check if [`argv`](https://nodejs.org/docs/latest/api/process.html#process_process_argv) has a specific flag
Correctly stops looking after an `--` argument terminator.
---
<div align="center">
<b>
<a href="https://tidelift.com/subscription/pkg/npm-has-flag?utm_source=npm-has-flag&utm_medium=referral&utm_campaign=readme">Get professional support for this package with a Tidelift subscription</a>
</b>
<br>
<sub>
Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies.
</sub>
</div>
---
## Install
```
$ npm install has-flag
```
## Usage
```js
// foo.js
const hasFlag = require('has-flag');
hasFlag('unicorn');
//=> true
hasFlag('--unicorn');
//=> true
hasFlag('f');
//=> true
hasFlag('-f');
//=> true
hasFlag('foo=bar');
//=> true
hasFlag('foo');
//=> false
hasFlag('rainbow');
//=> false
```
```
$ node foo.js -f --unicorn --foo=bar -- --rainbow
```
## API
### hasFlag(flag, [argv])
Returns a boolean for whether the flag exists.
#### flag
Type: `string`
CLI flag to look for. The `--` prefix is optional.
#### argv
Type: `string[]`<br>
Default: `process.argv`
CLI arguments.
## Security
To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
## License
MIT © [Sindre Sorhus](https://sindresorhus.com)

View File

@@ -0,0 +1,15 @@
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@@ -0,0 +1,654 @@
semver(1) -- The semantic versioner for npm
===========================================
## Install
```bash
npm install semver
````
## Usage
As a node module:
```js
const semver = require('semver')
semver.valid('1.2.3') // '1.2.3'
semver.valid('a.b.c') // null
semver.clean(' =v1.2.3 ') // '1.2.3'
semver.satisfies('1.2.3', '1.x || >=2.5.0 || 5.0.0 - 7.2.3') // true
semver.gt('1.2.3', '9.8.7') // false
semver.lt('1.2.3', '9.8.7') // true
semver.minVersion('>=1.0.0') // '1.0.0'
semver.valid(semver.coerce('v2')) // '2.0.0'
semver.valid(semver.coerce('42.6.7.9.3-alpha')) // '42.6.7'
```
You can also just load the module for the function that you care about if
you'd like to minimize your footprint.
```js
// load the whole API at once in a single object
const semver = require('semver')
// or just load the bits you need
// all of them listed here, just pick and choose what you want
// classes
const SemVer = require('semver/classes/semver')
const Comparator = require('semver/classes/comparator')
const Range = require('semver/classes/range')
// functions for working with versions
const semverParse = require('semver/functions/parse')
const semverValid = require('semver/functions/valid')
const semverClean = require('semver/functions/clean')
const semverInc = require('semver/functions/inc')
const semverDiff = require('semver/functions/diff')
const semverMajor = require('semver/functions/major')
const semverMinor = require('semver/functions/minor')
const semverPatch = require('semver/functions/patch')
const semverPrerelease = require('semver/functions/prerelease')
const semverCompare = require('semver/functions/compare')
const semverRcompare = require('semver/functions/rcompare')
const semverCompareLoose = require('semver/functions/compare-loose')
const semverCompareBuild = require('semver/functions/compare-build')
const semverSort = require('semver/functions/sort')
const semverRsort = require('semver/functions/rsort')
// low-level comparators between versions
const semverGt = require('semver/functions/gt')
const semverLt = require('semver/functions/lt')
const semverEq = require('semver/functions/eq')
const semverNeq = require('semver/functions/neq')
const semverGte = require('semver/functions/gte')
const semverLte = require('semver/functions/lte')
const semverCmp = require('semver/functions/cmp')
const semverCoerce = require('semver/functions/coerce')
// working with ranges
const semverSatisfies = require('semver/functions/satisfies')
const semverMaxSatisfying = require('semver/ranges/max-satisfying')
const semverMinSatisfying = require('semver/ranges/min-satisfying')
const semverToComparators = require('semver/ranges/to-comparators')
const semverMinVersion = require('semver/ranges/min-version')
const semverValidRange = require('semver/ranges/valid')
const semverOutside = require('semver/ranges/outside')
const semverGtr = require('semver/ranges/gtr')
const semverLtr = require('semver/ranges/ltr')
const semverIntersects = require('semver/ranges/intersects')
const semverSimplifyRange = require('semver/ranges/simplify')
const semverRangeSubset = require('semver/ranges/subset')
```
As a command-line utility:
```
$ semver -h
A JavaScript implementation of the https://semver.org/ specification
Copyright Isaac Z. Schlueter
Usage: semver [options] <version> [<version> [...]]
Prints valid versions sorted by SemVer precedence
Options:
-r --range <range>
Print versions that match the specified range.
-i --increment [<level>]
Increment a version by the specified level. Level can
be one of: major, minor, patch, premajor, preminor,
prepatch, or prerelease. Default level is 'patch'.
Only one version may be specified.
--preid <identifier>
Identifier to be used to prefix premajor, preminor,
prepatch or prerelease version increments.
-l --loose
Interpret versions and ranges loosely
-n <0|1>
This is the base to be used for the prerelease identifier.
-p --include-prerelease
Always include prerelease versions in range matching
-c --coerce
Coerce a string into SemVer if possible
(does not imply --loose)
--rtl
Coerce version strings right to left
--ltr
Coerce version strings left to right (default)
Program exits successfully if any valid version satisfies
all supplied ranges, and prints all satisfying versions.
If no satisfying versions are found, then exits failure.
Versions are printed in ascending order, so supplying
multiple versions to the utility will just sort them.
```
## Versions
A "version" is described by the `v2.0.0` specification found at
<https://semver.org/>.
A leading `"="` or `"v"` character is stripped off and ignored.
## Ranges
A `version range` is a set of `comparators` that specify versions
that satisfy the range.
A `comparator` is composed of an `operator` and a `version`. The set
of primitive `operators` is:
* `<` Less than
* `<=` Less than or equal to
* `>` Greater than
* `>=` Greater than or equal to
* `=` Equal. If no operator is specified, then equality is assumed,
so this operator is optional but MAY be included.
For example, the comparator `>=1.2.7` would match the versions
`1.2.7`, `1.2.8`, `2.5.3`, and `1.3.9`, but not the versions `1.2.6`
or `1.1.0`. The comparator `>1` is equivalent to `>=2.0.0` and
would match the versions `2.0.0` and `3.1.0`, but not the versions
`1.0.1` or `1.1.0`.
Comparators can be joined by whitespace to form a `comparator set`,
which is satisfied by the **intersection** of all of the comparators
it includes.
A range is composed of one or more comparator sets, joined by `||`. A
version matches a range if and only if every comparator in at least
one of the `||`-separated comparator sets is satisfied by the version.
For example, the range `>=1.2.7 <1.3.0` would match the versions
`1.2.7`, `1.2.8`, and `1.2.99`, but not the versions `1.2.6`, `1.3.0`,
or `1.1.0`.
The range `1.2.7 || >=1.2.9 <2.0.0` would match the versions `1.2.7`,
`1.2.9`, and `1.4.6`, but not the versions `1.2.8` or `2.0.0`.
### Prerelease Tags
If a version has a prerelease tag (for example, `1.2.3-alpha.3`) then
it will only be allowed to satisfy comparator sets if at least one
comparator with the same `[major, minor, patch]` tuple also has a
prerelease tag.
For example, the range `>1.2.3-alpha.3` would be allowed to match the
version `1.2.3-alpha.7`, but it would *not* be satisfied by
`3.4.5-alpha.9`, even though `3.4.5-alpha.9` is technically "greater
than" `1.2.3-alpha.3` according to the SemVer sort rules. The version
range only accepts prerelease tags on the `1.2.3` version.
Version `3.4.5` *would* satisfy the range because it does not have a
prerelease flag, and `3.4.5` is greater than `1.2.3-alpha.7`.
The purpose of this behavior is twofold. First, prerelease versions
frequently are updated very quickly, and contain many breaking changes
that are (by the author's design) not yet fit for public consumption.
Therefore, by default, they are excluded from range-matching
semantics.
Second, a user who has opted into using a prerelease version has
indicated the intent to use *that specific* set of
alpha/beta/rc versions. By including a prerelease tag in the range,
the user is indicating that they are aware of the risk. However, it
is still not appropriate to assume that they have opted into taking a
similar risk on the *next* set of prerelease versions.
Note that this behavior can be suppressed (treating all prerelease
versions as if they were normal versions, for range-matching)
by setting the `includePrerelease` flag on the options
object to any
[functions](https://github.com/npm/node-semver#functions) that do
range matching.
#### Prerelease Identifiers
The method `.inc` takes an additional `identifier` string argument that
will append the value of the string as a prerelease identifier:
```javascript
semver.inc('1.2.3', 'prerelease', 'beta')
// '1.2.4-beta.0'
```
command-line example:
```bash
$ semver 1.2.3 -i prerelease --preid beta
1.2.4-beta.0
```
Which then can be used to increment further:
```bash
$ semver 1.2.4-beta.0 -i prerelease
1.2.4-beta.1
```
#### Prerelease Identifier Base
The method `.inc` takes an optional parameter 'identifierBase' string
that will let you let your prerelease number as zero-based or one-based.
Set to `false` to omit the prerelease number altogether.
If you do not specify this parameter, it will default to zero-based.
```javascript
semver.inc('1.2.3', 'prerelease', 'beta', '1')
// '1.2.4-beta.1'
```
```javascript
semver.inc('1.2.3', 'prerelease', 'beta', false)
// '1.2.4-beta'
```
command-line example:
```bash
$ semver 1.2.3 -i prerelease --preid beta -n 1
1.2.4-beta.1
```
```bash
$ semver 1.2.3 -i prerelease --preid beta -n false
1.2.4-beta
```
### Advanced Range Syntax
Advanced range syntax desugars to primitive comparators in
deterministic ways.
Advanced ranges may be combined in the same way as primitive
comparators using white space or `||`.
#### Hyphen Ranges `X.Y.Z - A.B.C`
Specifies an inclusive set.
* `1.2.3 - 2.3.4` := `>=1.2.3 <=2.3.4`
If a partial version is provided as the first version in the inclusive
range, then the missing pieces are replaced with zeroes.
* `1.2 - 2.3.4` := `>=1.2.0 <=2.3.4`
If a partial version is provided as the second version in the
inclusive range, then all versions that start with the supplied parts
of the tuple are accepted, but nothing that would be greater than the
provided tuple parts.
* `1.2.3 - 2.3` := `>=1.2.3 <2.4.0-0`
* `1.2.3 - 2` := `>=1.2.3 <3.0.0-0`
#### X-Ranges `1.2.x` `1.X` `1.2.*` `*`
Any of `X`, `x`, or `*` may be used to "stand in" for one of the
numeric values in the `[major, minor, patch]` tuple.
* `*` := `>=0.0.0` (Any non-prerelease version satisfies, unless
`includePrerelease` is specified, in which case any version at all
satisfies)
* `1.x` := `>=1.0.0 <2.0.0-0` (Matching major version)
* `1.2.x` := `>=1.2.0 <1.3.0-0` (Matching major and minor versions)
A partial version range is treated as an X-Range, so the special
character is in fact optional.
* `""` (empty string) := `*` := `>=0.0.0`
* `1` := `1.x.x` := `>=1.0.0 <2.0.0-0`
* `1.2` := `1.2.x` := `>=1.2.0 <1.3.0-0`
#### Tilde Ranges `~1.2.3` `~1.2` `~1`
Allows patch-level changes if a minor version is specified on the
comparator. Allows minor-level changes if not.
* `~1.2.3` := `>=1.2.3 <1.(2+1).0` := `>=1.2.3 <1.3.0-0`
* `~1.2` := `>=1.2.0 <1.(2+1).0` := `>=1.2.0 <1.3.0-0` (Same as `1.2.x`)
* `~1` := `>=1.0.0 <(1+1).0.0` := `>=1.0.0 <2.0.0-0` (Same as `1.x`)
* `~0.2.3` := `>=0.2.3 <0.(2+1).0` := `>=0.2.3 <0.3.0-0`
* `~0.2` := `>=0.2.0 <0.(2+1).0` := `>=0.2.0 <0.3.0-0` (Same as `0.2.x`)
* `~0` := `>=0.0.0 <(0+1).0.0` := `>=0.0.0 <1.0.0-0` (Same as `0.x`)
* `~1.2.3-beta.2` := `>=1.2.3-beta.2 <1.3.0-0` Note that prereleases in
the `1.2.3` version will be allowed, if they are greater than or
equal to `beta.2`. So, `1.2.3-beta.4` would be allowed, but
`1.2.4-beta.2` would not, because it is a prerelease of a
different `[major, minor, patch]` tuple.
#### Caret Ranges `^1.2.3` `^0.2.5` `^0.0.4`
Allows changes that do not modify the left-most non-zero element in the
`[major, minor, patch]` tuple. In other words, this allows patch and
minor updates for versions `1.0.0` and above, patch updates for
versions `0.X >=0.1.0`, and *no* updates for versions `0.0.X`.
Many authors treat a `0.x` version as if the `x` were the major
"breaking-change" indicator.
Caret ranges are ideal when an author may make breaking changes
between `0.2.4` and `0.3.0` releases, which is a common practice.
However, it presumes that there will *not* be breaking changes between
`0.2.4` and `0.2.5`. It allows for changes that are presumed to be
additive (but non-breaking), according to commonly observed practices.
* `^1.2.3` := `>=1.2.3 <2.0.0-0`
* `^0.2.3` := `>=0.2.3 <0.3.0-0`
* `^0.0.3` := `>=0.0.3 <0.0.4-0`
* `^1.2.3-beta.2` := `>=1.2.3-beta.2 <2.0.0-0` Note that prereleases in
the `1.2.3` version will be allowed, if they are greater than or
equal to `beta.2`. So, `1.2.3-beta.4` would be allowed, but
`1.2.4-beta.2` would not, because it is a prerelease of a
different `[major, minor, patch]` tuple.
* `^0.0.3-beta` := `>=0.0.3-beta <0.0.4-0` Note that prereleases in the
`0.0.3` version *only* will be allowed, if they are greater than or
equal to `beta`. So, `0.0.3-pr.2` would be allowed.
When parsing caret ranges, a missing `patch` value desugars to the
number `0`, but will allow flexibility within that value, even if the
major and minor versions are both `0`.
* `^1.2.x` := `>=1.2.0 <2.0.0-0`
* `^0.0.x` := `>=0.0.0 <0.1.0-0`
* `^0.0` := `>=0.0.0 <0.1.0-0`
A missing `minor` and `patch` values will desugar to zero, but also
allow flexibility within those values, even if the major version is
zero.
* `^1.x` := `>=1.0.0 <2.0.0-0`
* `^0.x` := `>=0.0.0 <1.0.0-0`
### Range Grammar
Putting all this together, here is a Backus-Naur grammar for ranges,
for the benefit of parser authors:
```bnf
range-set ::= range ( logical-or range ) *
logical-or ::= ( ' ' ) * '||' ( ' ' ) *
range ::= hyphen | simple ( ' ' simple ) * | ''
hyphen ::= partial ' - ' partial
simple ::= primitive | partial | tilde | caret
primitive ::= ( '<' | '>' | '>=' | '<=' | '=' ) partial
partial ::= xr ( '.' xr ( '.' xr qualifier ? )? )?
xr ::= 'x' | 'X' | '*' | nr
nr ::= '0' | ['1'-'9'] ( ['0'-'9'] ) *
tilde ::= '~' partial
caret ::= '^' partial
qualifier ::= ( '-' pre )? ( '+' build )?
pre ::= parts
build ::= parts
parts ::= part ( '.' part ) *
part ::= nr | [-0-9A-Za-z]+
```
## Functions
All methods and classes take a final `options` object argument. All
options in this object are `false` by default. The options supported
are:
- `loose`: Be more forgiving about not-quite-valid semver strings.
(Any resulting output will always be 100% strict compliant, of
course.) For backwards compatibility reasons, if the `options`
argument is a boolean value instead of an object, it is interpreted
to be the `loose` param.
- `includePrerelease`: Set to suppress the [default
behavior](https://github.com/npm/node-semver#prerelease-tags) of
excluding prerelease tagged versions from ranges unless they are
explicitly opted into.
Strict-mode Comparators and Ranges will be strict about the SemVer
strings that they parse.
* `valid(v)`: Return the parsed version, or null if it's not valid.
* `inc(v, release, options, identifier, identifierBase)`:
Return the version incremented by the release
type (`major`, `premajor`, `minor`, `preminor`, `patch`,
`prepatch`, or `prerelease`), or null if it's not valid
* `premajor` in one call will bump the version up to the next major
version and down to a prerelease of that major version.
`preminor`, and `prepatch` work the same way.
* If called from a non-prerelease version, `prerelease` will work the
same as `prepatch`. It increments the patch version and then makes a
prerelease. If the input version is already a prerelease it simply
increments it.
* `identifier` can be used to prefix `premajor`, `preminor`,
`prepatch`, or `prerelease` version increments. `identifierBase`
is the base to be used for the `prerelease` identifier.
* `prerelease(v)`: Returns an array of prerelease components, or null
if none exist. Example: `prerelease('1.2.3-alpha.1') -> ['alpha', 1]`
* `major(v)`: Return the major version number.
* `minor(v)`: Return the minor version number.
* `patch(v)`: Return the patch version number.
* `intersects(r1, r2, loose)`: Return true if the two supplied ranges
or comparators intersect.
* `parse(v)`: Attempt to parse a string as a semantic version, returning either
a `SemVer` object or `null`.
### Comparison
* `gt(v1, v2)`: `v1 > v2`
* `gte(v1, v2)`: `v1 >= v2`
* `lt(v1, v2)`: `v1 < v2`
* `lte(v1, v2)`: `v1 <= v2`
* `eq(v1, v2)`: `v1 == v2` This is true if they're logically equivalent,
even if they're not the same string. You already know how to
compare strings.
* `neq(v1, v2)`: `v1 != v2` The opposite of `eq`.
* `cmp(v1, comparator, v2)`: Pass in a comparison string, and it'll call
the corresponding function above. `"==="` and `"!=="` do simple
string comparison, but are included for completeness. Throws if an
invalid comparison string is provided.
* `compare(v1, v2)`: Return `0` if `v1 == v2`, or `1` if `v1` is greater, or `-1` if
`v2` is greater. Sorts in ascending order if passed to `Array.sort()`.
* `rcompare(v1, v2)`: The reverse of `compare`. Sorts an array of versions
in descending order when passed to `Array.sort()`.
* `compareBuild(v1, v2)`: The same as `compare` but considers `build` when two versions
are equal. Sorts in ascending order if passed to `Array.sort()`.
* `compareLoose(v1, v2)`: Short for ``compare(v1, v2, { loose: true })`.
* `diff(v1, v2)`: Returns the difference between two versions by the release type
(`major`, `premajor`, `minor`, `preminor`, `patch`, `prepatch`, or `prerelease`),
or null if the versions are the same.
### Sorting
* `sort(versions)`: Returns a sorted array of versions based on the `compareBuild`
function.
* `rsort(versions)`: The reverse of `sort`. Returns an array of versions based on
the `compareBuild` function in descending order.
### Comparators
* `intersects(comparator)`: Return true if the comparators intersect
### Ranges
* `validRange(range)`: Return the valid range or null if it's not valid
* `satisfies(version, range)`: Return true if the version satisfies the
range.
* `maxSatisfying(versions, range)`: Return the highest version in the list
that satisfies the range, or `null` if none of them do.
* `minSatisfying(versions, range)`: Return the lowest version in the list
that satisfies the range, or `null` if none of them do.
* `minVersion(range)`: Return the lowest version that can match
the given range.
* `gtr(version, range)`: Return `true` if the version is greater than all the
versions possible in the range.
* `ltr(version, range)`: Return `true` if the version is less than all the
versions possible in the range.
* `outside(version, range, hilo)`: Return true if the version is outside
the bounds of the range in either the high or low direction. The
`hilo` argument must be either the string `'>'` or `'<'`. (This is
the function called by `gtr` and `ltr`.)
* `intersects(range)`: Return true if any of the range comparators intersect.
* `simplifyRange(versions, range)`: Return a "simplified" range that
matches the same items in the `versions` list as the range specified. Note
that it does *not* guarantee that it would match the same versions in all
cases, only for the set of versions provided. This is useful when
generating ranges by joining together multiple versions with `||`
programmatically, to provide the user with something a bit more
ergonomic. If the provided range is shorter in string-length than the
generated range, then that is returned.
* `subset(subRange, superRange)`: Return `true` if the `subRange` range is
entirely contained by the `superRange` range.
Note that, since ranges may be non-contiguous, a version might not be
greater than a range, less than a range, *or* satisfy a range! For
example, the range `1.2 <1.2.9 || >2.0.0` would have a hole from `1.2.9`
until `2.0.0`, so version `1.2.10` would not be greater than the
range (because `2.0.1` satisfies, which is higher), nor less than the
range (since `1.2.8` satisfies, which is lower), and it also does not
satisfy the range.
If you want to know if a version satisfies or does not satisfy a
range, use the `satisfies(version, range)` function.
### Coercion
* `coerce(version, options)`: Coerces a string to semver if possible
This aims to provide a very forgiving translation of a non-semver string to
semver. It looks for the first digit in a string and consumes all
remaining characters which satisfy at least a partial semver (e.g., `1`,
`1.2`, `1.2.3`) up to the max permitted length (256 characters). Longer
versions are simply truncated (`4.6.3.9.2-alpha2` becomes `4.6.3`). All
surrounding text is simply ignored (`v3.4 replaces v3.3.1` becomes
`3.4.0`). Only text which lacks digits will fail coercion (`version one`
is not valid). The maximum length for any semver component considered for
coercion is 16 characters; longer components will be ignored
(`10000000000000000.4.7.4` becomes `4.7.4`). The maximum value for any
semver component is `Number.MAX_SAFE_INTEGER || (2**53 - 1)`; higher value
components are invalid (`9999999999999999.4.7.4` is likely invalid).
If the `options.rtl` flag is set, then `coerce` will return the right-most
coercible tuple that does not share an ending index with a longer coercible
tuple. For example, `1.2.3.4` will return `2.3.4` in rtl mode, not
`4.0.0`. `1.2.3/4` will return `4.0.0`, because the `4` is not a part of
any other overlapping SemVer tuple.
If the `options.includePrerelease` flag is set, then the `coerce` result will contain
prerelease and build parts of a version. For example, `1.2.3.4-rc.1+rev.2`
will preserve prerelease `rc.1` and build `rev.2` in the result.
### Clean
* `clean(version)`: Clean a string to be a valid semver if possible
This will return a cleaned and trimmed semver version. If the provided
version is not valid a null will be returned. This does not work for
ranges.
ex.
* `s.clean(' = v 2.1.5foo')`: `null`
* `s.clean(' = v 2.1.5foo', { loose: true })`: `'2.1.5-foo'`
* `s.clean(' = v 2.1.5-foo')`: `null`
* `s.clean(' = v 2.1.5-foo', { loose: true })`: `'2.1.5-foo'`
* `s.clean('=v2.1.5')`: `'2.1.5'`
* `s.clean(' =v2.1.5')`: `'2.1.5'`
* `s.clean(' 2.1.5 ')`: `'2.1.5'`
* `s.clean('~1.0.0')`: `null`
## Constants
As a convenience, helper constants are exported to provide information about what `node-semver` supports:
### `RELEASE_TYPES`
- major
- premajor
- minor
- preminor
- patch
- prepatch
- prerelease
```
const semver = require('semver');
if (semver.RELEASE_TYPES.includes(arbitraryUserInput)) {
console.log('This is a valid release type!');
} else {
console.warn('This is NOT a valid release type!');
}
```
### `SEMVER_SPEC_VERSION`
2.0.0
```
const semver = require('semver');
console.log('We are currently using the semver specification version:', semver.SEMVER_SPEC_VERSION);
```
## Exported Modules
<!--
TODO: Make sure that all of these items are documented (classes aren't,
eg), and then pull the module name into the documentation for that specific
thing.
-->
You may pull in just the part of this semver utility that you need if you
are sensitive to packing and tree-shaking concerns. The main
`require('semver')` export uses getter functions to lazily load the parts
of the API that are used.
The following modules are available:
* `require('semver')`
* `require('semver/classes')`
* `require('semver/classes/comparator')`
* `require('semver/classes/range')`
* `require('semver/classes/semver')`
* `require('semver/functions/clean')`
* `require('semver/functions/cmp')`
* `require('semver/functions/coerce')`
* `require('semver/functions/compare')`
* `require('semver/functions/compare-build')`
* `require('semver/functions/compare-loose')`
* `require('semver/functions/diff')`
* `require('semver/functions/eq')`
* `require('semver/functions/gt')`
* `require('semver/functions/gte')`
* `require('semver/functions/inc')`
* `require('semver/functions/lt')`
* `require('semver/functions/lte')`
* `require('semver/functions/major')`
* `require('semver/functions/minor')`
* `require('semver/functions/neq')`
* `require('semver/functions/parse')`
* `require('semver/functions/patch')`
* `require('semver/functions/prerelease')`
* `require('semver/functions/rcompare')`
* `require('semver/functions/rsort')`
* `require('semver/functions/satisfies')`
* `require('semver/functions/sort')`
* `require('semver/functions/valid')`
* `require('semver/ranges/gtr')`
* `require('semver/ranges/intersects')`
* `require('semver/ranges/ltr')`
* `require('semver/ranges/max-satisfying')`
* `require('semver/ranges/min-satisfying')`
* `require('semver/ranges/min-version')`
* `require('semver/ranges/outside')`
* `require('semver/ranges/simplify')`
* `require('semver/ranges/subset')`
* `require('semver/ranges/to-comparators')`
* `require('semver/ranges/valid')`

View File

@@ -0,0 +1,188 @@
#!/usr/bin/env node
// Standalone semver comparison program.
// Exits successfully and prints matching version(s) if
// any supplied version is valid and passes all tests.
const argv = process.argv.slice(2)
let versions = []
const range = []
let inc = null
const version = require('../package.json').version
let loose = false
let includePrerelease = false
let coerce = false
let rtl = false
let identifier
let identifierBase
const semver = require('../')
const parseOptions = require('../internal/parse-options')
let reverse = false
let options = {}
const main = () => {
if (!argv.length) {
return help()
}
while (argv.length) {
let a = argv.shift()
const indexOfEqualSign = a.indexOf('=')
if (indexOfEqualSign !== -1) {
const value = a.slice(indexOfEqualSign + 1)
a = a.slice(0, indexOfEqualSign)
argv.unshift(value)
}
switch (a) {
case '-rv': case '-rev': case '--rev': case '--reverse':
reverse = true
break
case '-l': case '--loose':
loose = true
break
case '-p': case '--include-prerelease':
includePrerelease = true
break
case '-v': case '--version':
versions.push(argv.shift())
break
case '-i': case '--inc': case '--increment':
switch (argv[0]) {
case 'major': case 'minor': case 'patch': case 'prerelease':
case 'premajor': case 'preminor': case 'prepatch':
inc = argv.shift()
break
default:
inc = 'patch'
break
}
break
case '--preid':
identifier = argv.shift()
break
case '-r': case '--range':
range.push(argv.shift())
break
case '-n':
identifierBase = argv.shift()
if (identifierBase === 'false') {
identifierBase = false
}
break
case '-c': case '--coerce':
coerce = true
break
case '--rtl':
rtl = true
break
case '--ltr':
rtl = false
break
case '-h': case '--help': case '-?':
return help()
default:
versions.push(a)
break
}
}
options = parseOptions({ loose, includePrerelease, rtl })
versions = versions.map((v) => {
return coerce ? (semver.coerce(v, options) || { version: v }).version : v
}).filter((v) => {
return semver.valid(v)
})
if (!versions.length) {
return fail()
}
if (inc && (versions.length !== 1 || range.length)) {
return failInc()
}
for (let i = 0, l = range.length; i < l; i++) {
versions = versions.filter((v) => {
return semver.satisfies(v, range[i], options)
})
if (!versions.length) {
return fail()
}
}
versions
.sort((a, b) => semver[reverse ? 'rcompare' : 'compare'](a, b, options))
.map(v => semver.clean(v, options))
.map(v => inc ? semver.inc(v, inc, options, identifier, identifierBase) : v)
.forEach(v => console.log(v))
}
const failInc = () => {
console.error('--inc can only be used on a single version with no range')
fail()
}
const fail = () => process.exit(1)
const help = () => console.log(
`SemVer ${version}
A JavaScript implementation of the https://semver.org/ specification
Copyright Isaac Z. Schlueter
Usage: semver [options] <version> [<version> [...]]
Prints valid versions sorted by SemVer precedence
Options:
-r --range <range>
Print versions that match the specified range.
-i --increment [<level>]
Increment a version by the specified level. Level can
be one of: major, minor, patch, premajor, preminor,
prepatch, or prerelease. Default level is 'patch'.
Only one version may be specified.
--preid <identifier>
Identifier to be used to prefix premajor, preminor,
prepatch or prerelease version increments.
-l --loose
Interpret versions and ranges loosely
-p --include-prerelease
Always include prerelease versions in range matching
-c --coerce
Coerce a string into SemVer if possible
(does not imply --loose)
--rtl
Coerce version strings right to left
--ltr
Coerce version strings left to right (default)
-n <base>
Base number to be used for the prerelease identifier.
Can be either 0 or 1, or false to omit the number altogether.
Defaults to 0.
Program exits successfully if any valid version satisfies
all supplied ranges, and prints all satisfying versions.
If no satisfying versions are found, then exits failure.
Versions are printed in ascending order, so supplying
multiple versions to the utility will just sort them.`)
main()

View File

@@ -0,0 +1,141 @@
const ANY = Symbol('SemVer ANY')
// hoisted class for cyclic dependency
class Comparator {
static get ANY () {
return ANY
}
constructor (comp, options) {
options = parseOptions(options)
if (comp instanceof Comparator) {
if (comp.loose === !!options.loose) {
return comp
} else {
comp = comp.value
}
}
comp = comp.trim().split(/\s+/).join(' ')
debug('comparator', comp, options)
this.options = options
this.loose = !!options.loose
this.parse(comp)
if (this.semver === ANY) {
this.value = ''
} else {
this.value = this.operator + this.semver.version
}
debug('comp', this)
}
parse (comp) {
const r = this.options.loose ? re[t.COMPARATORLOOSE] : re[t.COMPARATOR]
const m = comp.match(r)
if (!m) {
throw new TypeError(`Invalid comparator: ${comp}`)
}
this.operator = m[1] !== undefined ? m[1] : ''
if (this.operator === '=') {
this.operator = ''
}
// if it literally is just '>' or '' then allow anything.
if (!m[2]) {
this.semver = ANY
} else {
this.semver = new SemVer(m[2], this.options.loose)
}
}
toString () {
return this.value
}
test (version) {
debug('Comparator.test', version, this.options.loose)
if (this.semver === ANY || version === ANY) {
return true
}
if (typeof version === 'string') {
try {
version = new SemVer(version, this.options)
} catch (er) {
return false
}
}
return cmp(version, this.operator, this.semver, this.options)
}
intersects (comp, options) {
if (!(comp instanceof Comparator)) {
throw new TypeError('a Comparator is required')
}
if (this.operator === '') {
if (this.value === '') {
return true
}
return new Range(comp.value, options).test(this.value)
} else if (comp.operator === '') {
if (comp.value === '') {
return true
}
return new Range(this.value, options).test(comp.semver)
}
options = parseOptions(options)
// Special cases where nothing can possibly be lower
if (options.includePrerelease &&
(this.value === '<0.0.0-0' || comp.value === '<0.0.0-0')) {
return false
}
if (!options.includePrerelease &&
(this.value.startsWith('<0.0.0') || comp.value.startsWith('<0.0.0'))) {
return false
}
// Same direction increasing (> or >=)
if (this.operator.startsWith('>') && comp.operator.startsWith('>')) {
return true
}
// Same direction decreasing (< or <=)
if (this.operator.startsWith('<') && comp.operator.startsWith('<')) {
return true
}
// same SemVer and both sides are inclusive (<= or >=)
if (
(this.semver.version === comp.semver.version) &&
this.operator.includes('=') && comp.operator.includes('=')) {
return true
}
// opposite directions less than
if (cmp(this.semver, '<', comp.semver, options) &&
this.operator.startsWith('>') && comp.operator.startsWith('<')) {
return true
}
// opposite directions greater than
if (cmp(this.semver, '>', comp.semver, options) &&
this.operator.startsWith('<') && comp.operator.startsWith('>')) {
return true
}
return false
}
}
module.exports = Comparator
const parseOptions = require('../internal/parse-options')
const { safeRe: re, t } = require('../internal/re')
const cmp = require('../functions/cmp')
const debug = require('../internal/debug')
const SemVer = require('./semver')
const Range = require('./range')

View File

@@ -0,0 +1,5 @@
module.exports = {
SemVer: require('./semver.js'),
Range: require('./range.js'),
Comparator: require('./comparator.js'),
}

View File

@@ -0,0 +1,540 @@
// hoisted class for cyclic dependency
class Range {
constructor (range, options) {
options = parseOptions(options)
if (range instanceof Range) {
if (
range.loose === !!options.loose &&
range.includePrerelease === !!options.includePrerelease
) {
return range
} else {
return new Range(range.raw, options)
}
}
if (range instanceof Comparator) {
// just put it in the set and return
this.raw = range.value
this.set = [[range]]
this.format()
return this
}
this.options = options
this.loose = !!options.loose
this.includePrerelease = !!options.includePrerelease
// First reduce all whitespace as much as possible so we do not have to rely
// on potentially slow regexes like \s*. This is then stored and used for
// future error messages as well.
this.raw = range
.trim()
.split(/\s+/)
.join(' ')
// First, split on ||
this.set = this.raw
.split('||')
// map the range to a 2d array of comparators
.map(r => this.parseRange(r.trim()))
// throw out any comparator lists that are empty
// this generally means that it was not a valid range, which is allowed
// in loose mode, but will still throw if the WHOLE range is invalid.
.filter(c => c.length)
if (!this.set.length) {
throw new TypeError(`Invalid SemVer Range: ${this.raw}`)
}
// if we have any that are not the null set, throw out null sets.
if (this.set.length > 1) {
// keep the first one, in case they're all null sets
const first = this.set[0]
this.set = this.set.filter(c => !isNullSet(c[0]))
if (this.set.length === 0) {
this.set = [first]
} else if (this.set.length > 1) {
// if we have any that are *, then the range is just *
for (const c of this.set) {
if (c.length === 1 && isAny(c[0])) {
this.set = [c]
break
}
}
}
}
this.format()
}
format () {
this.range = this.set
.map((comps) => comps.join(' ').trim())
.join('||')
.trim()
return this.range
}
toString () {
return this.range
}
parseRange (range) {
// memoize range parsing for performance.
// this is a very hot path, and fully deterministic.
const memoOpts =
(this.options.includePrerelease && FLAG_INCLUDE_PRERELEASE) |
(this.options.loose && FLAG_LOOSE)
const memoKey = memoOpts + ':' + range
const cached = cache.get(memoKey)
if (cached) {
return cached
}
const loose = this.options.loose
// `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4`
const hr = loose ? re[t.HYPHENRANGELOOSE] : re[t.HYPHENRANGE]
range = range.replace(hr, hyphenReplace(this.options.includePrerelease))
debug('hyphen replace', range)
// `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5`
range = range.replace(re[t.COMPARATORTRIM], comparatorTrimReplace)
debug('comparator trim', range)
// `~ 1.2.3` => `~1.2.3`
range = range.replace(re[t.TILDETRIM], tildeTrimReplace)
debug('tilde trim', range)
// `^ 1.2.3` => `^1.2.3`
range = range.replace(re[t.CARETTRIM], caretTrimReplace)
debug('caret trim', range)
// At this point, the range is completely trimmed and
// ready to be split into comparators.
let rangeList = range
.split(' ')
.map(comp => parseComparator(comp, this.options))
.join(' ')
.split(/\s+/)
// >=0.0.0 is equivalent to *
.map(comp => replaceGTE0(comp, this.options))
if (loose) {
// in loose mode, throw out any that are not valid comparators
rangeList = rangeList.filter(comp => {
debug('loose invalid filter', comp, this.options)
return !!comp.match(re[t.COMPARATORLOOSE])
})
}
debug('range list', rangeList)
// if any comparators are the null set, then replace with JUST null set
// if more than one comparator, remove any * comparators
// also, don't include the same comparator more than once
const rangeMap = new Map()
const comparators = rangeList.map(comp => new Comparator(comp, this.options))
for (const comp of comparators) {
if (isNullSet(comp)) {
return [comp]
}
rangeMap.set(comp.value, comp)
}
if (rangeMap.size > 1 && rangeMap.has('')) {
rangeMap.delete('')
}
const result = [...rangeMap.values()]
cache.set(memoKey, result)
return result
}
intersects (range, options) {
if (!(range instanceof Range)) {
throw new TypeError('a Range is required')
}
return this.set.some((thisComparators) => {
return (
isSatisfiable(thisComparators, options) &&
range.set.some((rangeComparators) => {
return (
isSatisfiable(rangeComparators, options) &&
thisComparators.every((thisComparator) => {
return rangeComparators.every((rangeComparator) => {
return thisComparator.intersects(rangeComparator, options)
})
})
)
})
)
})
}
// if ANY of the sets match ALL of its comparators, then pass
test (version) {
if (!version) {
return false
}
if (typeof version === 'string') {
try {
version = new SemVer(version, this.options)
} catch (er) {
return false
}
}
for (let i = 0; i < this.set.length; i++) {
if (testSet(this.set[i], version, this.options)) {
return true
}
}
return false
}
}
module.exports = Range
const LRU = require('../internal/lrucache')
const cache = new LRU()
const parseOptions = require('../internal/parse-options')
const Comparator = require('./comparator')
const debug = require('../internal/debug')
const SemVer = require('./semver')
const {
safeRe: re,
t,
comparatorTrimReplace,
tildeTrimReplace,
caretTrimReplace,
} = require('../internal/re')
const { FLAG_INCLUDE_PRERELEASE, FLAG_LOOSE } = require('../internal/constants')
const isNullSet = c => c.value === '<0.0.0-0'
const isAny = c => c.value === ''
// take a set of comparators and determine whether there
// exists a version which can satisfy it
const isSatisfiable = (comparators, options) => {
let result = true
const remainingComparators = comparators.slice()
let testComparator = remainingComparators.pop()
while (result && remainingComparators.length) {
result = remainingComparators.every((otherComparator) => {
return testComparator.intersects(otherComparator, options)
})
testComparator = remainingComparators.pop()
}
return result
}
// comprised of xranges, tildes, stars, and gtlt's at this point.
// already replaced the hyphen ranges
// turn into a set of JUST comparators.
const parseComparator = (comp, options) => {
debug('comp', comp, options)
comp = replaceCarets(comp, options)
debug('caret', comp)
comp = replaceTildes(comp, options)
debug('tildes', comp)
comp = replaceXRanges(comp, options)
debug('xrange', comp)
comp = replaceStars(comp, options)
debug('stars', comp)
return comp
}
const isX = id => !id || id.toLowerCase() === 'x' || id === '*'
// ~, ~> --> * (any, kinda silly)
// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0-0
// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0-0
// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0-0
// ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0-0
// ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0-0
// ~0.0.1 --> >=0.0.1 <0.1.0-0
const replaceTildes = (comp, options) => {
return comp
.trim()
.split(/\s+/)
.map((c) => replaceTilde(c, options))
.join(' ')
}
const replaceTilde = (comp, options) => {
const r = options.loose ? re[t.TILDELOOSE] : re[t.TILDE]
return comp.replace(r, (_, M, m, p, pr) => {
debug('tilde', comp, _, M, m, p, pr)
let ret
if (isX(M)) {
ret = ''
} else if (isX(m)) {
ret = `>=${M}.0.0 <${+M + 1}.0.0-0`
} else if (isX(p)) {
// ~1.2 == >=1.2.0 <1.3.0-0
ret = `>=${M}.${m}.0 <${M}.${+m + 1}.0-0`
} else if (pr) {
debug('replaceTilde pr', pr)
ret = `>=${M}.${m}.${p}-${pr
} <${M}.${+m + 1}.0-0`
} else {
// ~1.2.3 == >=1.2.3 <1.3.0-0
ret = `>=${M}.${m}.${p
} <${M}.${+m + 1}.0-0`
}
debug('tilde return', ret)
return ret
})
}
// ^ --> * (any, kinda silly)
// ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0-0
// ^2.0, ^2.0.x --> >=2.0.0 <3.0.0-0
// ^1.2, ^1.2.x --> >=1.2.0 <2.0.0-0
// ^1.2.3 --> >=1.2.3 <2.0.0-0
// ^1.2.0 --> >=1.2.0 <2.0.0-0
// ^0.0.1 --> >=0.0.1 <0.0.2-0
// ^0.1.0 --> >=0.1.0 <0.2.0-0
const replaceCarets = (comp, options) => {
return comp
.trim()
.split(/\s+/)
.map((c) => replaceCaret(c, options))
.join(' ')
}
const replaceCaret = (comp, options) => {
debug('caret', comp, options)
const r = options.loose ? re[t.CARETLOOSE] : re[t.CARET]
const z = options.includePrerelease ? '-0' : ''
return comp.replace(r, (_, M, m, p, pr) => {
debug('caret', comp, _, M, m, p, pr)
let ret
if (isX(M)) {
ret = ''
} else if (isX(m)) {
ret = `>=${M}.0.0${z} <${+M + 1}.0.0-0`
} else if (isX(p)) {
if (M === '0') {
ret = `>=${M}.${m}.0${z} <${M}.${+m + 1}.0-0`
} else {
ret = `>=${M}.${m}.0${z} <${+M + 1}.0.0-0`
}
} else if (pr) {
debug('replaceCaret pr', pr)
if (M === '0') {
if (m === '0') {
ret = `>=${M}.${m}.${p}-${pr
} <${M}.${m}.${+p + 1}-0`
} else {
ret = `>=${M}.${m}.${p}-${pr
} <${M}.${+m + 1}.0-0`
}
} else {
ret = `>=${M}.${m}.${p}-${pr
} <${+M + 1}.0.0-0`
}
} else {
debug('no pr')
if (M === '0') {
if (m === '0') {
ret = `>=${M}.${m}.${p
}${z} <${M}.${m}.${+p + 1}-0`
} else {
ret = `>=${M}.${m}.${p
}${z} <${M}.${+m + 1}.0-0`
}
} else {
ret = `>=${M}.${m}.${p
} <${+M + 1}.0.0-0`
}
}
debug('caret return', ret)
return ret
})
}
const replaceXRanges = (comp, options) => {
debug('replaceXRanges', comp, options)
return comp
.split(/\s+/)
.map((c) => replaceXRange(c, options))
.join(' ')
}
const replaceXRange = (comp, options) => {
comp = comp.trim()
const r = options.loose ? re[t.XRANGELOOSE] : re[t.XRANGE]
return comp.replace(r, (ret, gtlt, M, m, p, pr) => {
debug('xRange', comp, ret, gtlt, M, m, p, pr)
const xM = isX(M)
const xm = xM || isX(m)
const xp = xm || isX(p)
const anyX = xp
if (gtlt === '=' && anyX) {
gtlt = ''
}
// if we're including prereleases in the match, then we need
// to fix this to -0, the lowest possible prerelease value
pr = options.includePrerelease ? '-0' : ''
if (xM) {
if (gtlt === '>' || gtlt === '<') {
// nothing is allowed
ret = '<0.0.0-0'
} else {
// nothing is forbidden
ret = '*'
}
} else if (gtlt && anyX) {
// we know patch is an x, because we have any x at all.
// replace X with 0
if (xm) {
m = 0
}
p = 0
if (gtlt === '>') {
// >1 => >=2.0.0
// >1.2 => >=1.3.0
gtlt = '>='
if (xm) {
M = +M + 1
m = 0
p = 0
} else {
m = +m + 1
p = 0
}
} else if (gtlt === '<=') {
// <=0.7.x is actually <0.8.0, since any 0.7.x should
// pass. Similarly, <=7.x is actually <8.0.0, etc.
gtlt = '<'
if (xm) {
M = +M + 1
} else {
m = +m + 1
}
}
if (gtlt === '<') {
pr = '-0'
}
ret = `${gtlt + M}.${m}.${p}${pr}`
} else if (xm) {
ret = `>=${M}.0.0${pr} <${+M + 1}.0.0-0`
} else if (xp) {
ret = `>=${M}.${m}.0${pr
} <${M}.${+m + 1}.0-0`
}
debug('xRange return', ret)
return ret
})
}
// Because * is AND-ed with everything else in the comparator,
// and '' means "any version", just remove the *s entirely.
const replaceStars = (comp, options) => {
debug('replaceStars', comp, options)
// Looseness is ignored here. star is always as loose as it gets!
return comp
.trim()
.replace(re[t.STAR], '')
}
const replaceGTE0 = (comp, options) => {
debug('replaceGTE0', comp, options)
return comp
.trim()
.replace(re[options.includePrerelease ? t.GTE0PRE : t.GTE0], '')
}
// This function is passed to string.replace(re[t.HYPHENRANGE])
// M, m, patch, prerelease, build
// 1.2 - 3.4.5 => >=1.2.0 <=3.4.5
// 1.2.3 - 3.4 => >=1.2.0 <3.5.0-0 Any 3.4.x will do
// 1.2 - 3.4 => >=1.2.0 <3.5.0-0
// TODO build?
const hyphenReplace = incPr => ($0,
from, fM, fm, fp, fpr, fb,
to, tM, tm, tp, tpr) => {
if (isX(fM)) {
from = ''
} else if (isX(fm)) {
from = `>=${fM}.0.0${incPr ? '-0' : ''}`
} else if (isX(fp)) {
from = `>=${fM}.${fm}.0${incPr ? '-0' : ''}`
} else if (fpr) {
from = `>=${from}`
} else {
from = `>=${from}${incPr ? '-0' : ''}`
}
if (isX(tM)) {
to = ''
} else if (isX(tm)) {
to = `<${+tM + 1}.0.0-0`
} else if (isX(tp)) {
to = `<${tM}.${+tm + 1}.0-0`
} else if (tpr) {
to = `<=${tM}.${tm}.${tp}-${tpr}`
} else if (incPr) {
to = `<${tM}.${tm}.${+tp + 1}-0`
} else {
to = `<=${to}`
}
return `${from} ${to}`.trim()
}
const testSet = (set, version, options) => {
for (let i = 0; i < set.length; i++) {
if (!set[i].test(version)) {
return false
}
}
if (version.prerelease.length && !options.includePrerelease) {
// Find the set of versions that are allowed to have prereleases
// For example, ^1.2.3-pr.1 desugars to >=1.2.3-pr.1 <2.0.0
// That should allow `1.2.3-pr.2` to pass.
// However, `1.2.4-alpha.notready` should NOT be allowed,
// even though it's within the range set by the comparators.
for (let i = 0; i < set.length; i++) {
debug(set[i].semver)
if (set[i].semver === Comparator.ANY) {
continue
}
if (set[i].semver.prerelease.length > 0) {
const allowed = set[i].semver
if (allowed.major === version.major &&
allowed.minor === version.minor &&
allowed.patch === version.patch) {
return true
}
}
}
// Version has a -pre, but it's not one of the ones we like.
return false
}
return true
}

View File

@@ -0,0 +1,302 @@
const debug = require('../internal/debug')
const { MAX_LENGTH, MAX_SAFE_INTEGER } = require('../internal/constants')
const { safeRe: re, t } = require('../internal/re')
const parseOptions = require('../internal/parse-options')
const { compareIdentifiers } = require('../internal/identifiers')
class SemVer {
constructor (version, options) {
options = parseOptions(options)
if (version instanceof SemVer) {
if (version.loose === !!options.loose &&
version.includePrerelease === !!options.includePrerelease) {
return version
} else {
version = version.version
}
} else if (typeof version !== 'string') {
throw new TypeError(`Invalid version. Must be a string. Got type "${typeof version}".`)
}
if (version.length > MAX_LENGTH) {
throw new TypeError(
`version is longer than ${MAX_LENGTH} characters`
)
}
debug('SemVer', version, options)
this.options = options
this.loose = !!options.loose
// this isn't actually relevant for versions, but keep it so that we
// don't run into trouble passing this.options around.
this.includePrerelease = !!options.includePrerelease
const m = version.trim().match(options.loose ? re[t.LOOSE] : re[t.FULL])
if (!m) {
throw new TypeError(`Invalid Version: ${version}`)
}
this.raw = version
// these are actually numbers
this.major = +m[1]
this.minor = +m[2]
this.patch = +m[3]
if (this.major > MAX_SAFE_INTEGER || this.major < 0) {
throw new TypeError('Invalid major version')
}
if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) {
throw new TypeError('Invalid minor version')
}
if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) {
throw new TypeError('Invalid patch version')
}
// numberify any prerelease numeric ids
if (!m[4]) {
this.prerelease = []
} else {
this.prerelease = m[4].split('.').map((id) => {
if (/^[0-9]+$/.test(id)) {
const num = +id
if (num >= 0 && num < MAX_SAFE_INTEGER) {
return num
}
}
return id
})
}
this.build = m[5] ? m[5].split('.') : []
this.format()
}
format () {
this.version = `${this.major}.${this.minor}.${this.patch}`
if (this.prerelease.length) {
this.version += `-${this.prerelease.join('.')}`
}
return this.version
}
toString () {
return this.version
}
compare (other) {
debug('SemVer.compare', this.version, this.options, other)
if (!(other instanceof SemVer)) {
if (typeof other === 'string' && other === this.version) {
return 0
}
other = new SemVer(other, this.options)
}
if (other.version === this.version) {
return 0
}
return this.compareMain(other) || this.comparePre(other)
}
compareMain (other) {
if (!(other instanceof SemVer)) {
other = new SemVer(other, this.options)
}
return (
compareIdentifiers(this.major, other.major) ||
compareIdentifiers(this.minor, other.minor) ||
compareIdentifiers(this.patch, other.patch)
)
}
comparePre (other) {
if (!(other instanceof SemVer)) {
other = new SemVer(other, this.options)
}
// NOT having a prerelease is > having one
if (this.prerelease.length && !other.prerelease.length) {
return -1
} else if (!this.prerelease.length && other.prerelease.length) {
return 1
} else if (!this.prerelease.length && !other.prerelease.length) {
return 0
}
let i = 0
do {
const a = this.prerelease[i]
const b = other.prerelease[i]
debug('prerelease compare', i, a, b)
if (a === undefined && b === undefined) {
return 0
} else if (b === undefined) {
return 1
} else if (a === undefined) {
return -1
} else if (a === b) {
continue
} else {
return compareIdentifiers(a, b)
}
} while (++i)
}
compareBuild (other) {
if (!(other instanceof SemVer)) {
other = new SemVer(other, this.options)
}
let i = 0
do {
const a = this.build[i]
const b = other.build[i]
debug('build compare', i, a, b)
if (a === undefined && b === undefined) {
return 0
} else if (b === undefined) {
return 1
} else if (a === undefined) {
return -1
} else if (a === b) {
continue
} else {
return compareIdentifiers(a, b)
}
} while (++i)
}
// preminor will bump the version up to the next minor release, and immediately
// down to pre-release. premajor and prepatch work the same way.
inc (release, identifier, identifierBase) {
switch (release) {
case 'premajor':
this.prerelease.length = 0
this.patch = 0
this.minor = 0
this.major++
this.inc('pre', identifier, identifierBase)
break
case 'preminor':
this.prerelease.length = 0
this.patch = 0
this.minor++
this.inc('pre', identifier, identifierBase)
break
case 'prepatch':
// If this is already a prerelease, it will bump to the next version
// drop any prereleases that might already exist, since they are not
// relevant at this point.
this.prerelease.length = 0
this.inc('patch', identifier, identifierBase)
this.inc('pre', identifier, identifierBase)
break
// If the input is a non-prerelease version, this acts the same as
// prepatch.
case 'prerelease':
if (this.prerelease.length === 0) {
this.inc('patch', identifier, identifierBase)
}
this.inc('pre', identifier, identifierBase)
break
case 'major':
// If this is a pre-major version, bump up to the same major version.
// Otherwise increment major.
// 1.0.0-5 bumps to 1.0.0
// 1.1.0 bumps to 2.0.0
if (
this.minor !== 0 ||
this.patch !== 0 ||
this.prerelease.length === 0
) {
this.major++
}
this.minor = 0
this.patch = 0
this.prerelease = []
break
case 'minor':
// If this is a pre-minor version, bump up to the same minor version.
// Otherwise increment minor.
// 1.2.0-5 bumps to 1.2.0
// 1.2.1 bumps to 1.3.0
if (this.patch !== 0 || this.prerelease.length === 0) {
this.minor++
}
this.patch = 0
this.prerelease = []
break
case 'patch':
// If this is not a pre-release version, it will increment the patch.
// If it is a pre-release it will bump up to the same patch version.
// 1.2.0-5 patches to 1.2.0
// 1.2.0 patches to 1.2.1
if (this.prerelease.length === 0) {
this.patch++
}
this.prerelease = []
break
// This probably shouldn't be used publicly.
// 1.0.0 'pre' would become 1.0.0-0 which is the wrong direction.
case 'pre': {
const base = Number(identifierBase) ? 1 : 0
if (!identifier && identifierBase === false) {
throw new Error('invalid increment argument: identifier is empty')
}
if (this.prerelease.length === 0) {
this.prerelease = [base]
} else {
let i = this.prerelease.length
while (--i >= 0) {
if (typeof this.prerelease[i] === 'number') {
this.prerelease[i]++
i = -2
}
}
if (i === -1) {
// didn't increment anything
if (identifier === this.prerelease.join('.') && identifierBase === false) {
throw new Error('invalid increment argument: identifier already exists')
}
this.prerelease.push(base)
}
}
if (identifier) {
// 1.2.0-beta.1 bumps to 1.2.0-beta.2,
// 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0
let prerelease = [identifier, base]
if (identifierBase === false) {
prerelease = [identifier]
}
if (compareIdentifiers(this.prerelease[0], identifier) === 0) {
if (isNaN(this.prerelease[1])) {
this.prerelease = prerelease
}
} else {
this.prerelease = prerelease
}
}
break
}
default:
throw new Error(`invalid increment argument: ${release}`)
}
this.raw = this.format()
if (this.build.length) {
this.raw += `+${this.build.join('.')}`
}
return this
}
}
module.exports = SemVer

View File

@@ -0,0 +1,6 @@
const parse = require('./parse')
const clean = (version, options) => {
const s = parse(version.trim().replace(/^[=v]+/, ''), options)
return s ? s.version : null
}
module.exports = clean

Some files were not shown because too many files have changed in this diff Show More