diff --git a/package.json b/package.json index 7982ed8..582899d 100644 --- a/package.json +++ b/package.json @@ -127,6 +127,7 @@ "ts-loader": "^8.0.18", "typescript": "^4.0.0", "vsce": "^1.74.0", + "warnings-to-errors-webpack-plugin": "^2.0.1", "webpack": "^5.25.0", "webpack-cli": "^4.5.0" }, diff --git a/src/lib/commitlint.ts b/src/lib/commitlint.ts index 7c38890..d88bdf5 100644 --- a/src/lib/commitlint.ts +++ b/src/lib/commitlint.ts @@ -2,10 +2,11 @@ * @since 2020-04-28 14:37 * @author vivaxy */ -import load from '@commitlint/load'; +import load from '../modules/@commitlint/load/lib/load'; import rules from '@commitlint/rules'; import { RulesConfig, RuleConfigSeverity } from '@commitlint/types/lib/rules'; import { Commit } from '@commitlint/types/lib/parse'; +import { appendLine } from './output'; class Commitlint { private ruleConfigs: Partial = {}; @@ -17,6 +18,7 @@ class Commitlint { return rules; } catch (e) { // catch if `Cannot find module "@commitlint/config-conventional"` happens. + appendLine(`[warning]: ${e.message}`); return {}; } } diff --git a/src/modules/@commitlint/load/lib/load.d.ts b/src/modules/@commitlint/load/lib/load.d.ts new file mode 100644 index 0000000..18412d7 --- /dev/null +++ b/src/modules/@commitlint/load/lib/load.d.ts @@ -0,0 +1,6 @@ +import { UserConfig, LoadOptions, QualifiedConfig } from '@commitlint/types'; +export default function load( + seed?: UserConfig, + options?: LoadOptions, +): Promise; +//# sourceMappingURL=load.d.ts.map diff --git a/src/modules/@commitlint/load/lib/load.js b/src/modules/@commitlint/load/lib/load.js new file mode 100644 index 0000000..7c616c5 --- /dev/null +++ b/src/modules/@commitlint/load/lib/load.js @@ -0,0 +1,123 @@ +/** + * @author @commitlint/load + */ +'use strict'; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const path_1 = __importDefault(require('path')); +const merge_1 = __importDefault(require('lodash/merge')); +const mergeWith_1 = __importDefault(require('lodash/mergeWith')); +const pick_1 = __importDefault(require('lodash/pick')); +const union_1 = __importDefault(require('lodash/union')); +const resolve_from_1 = __importDefault(require('resolve-from')); +const execute_rule_1 = __importDefault(require('@commitlint/execute-rule')); +const resolve_extends_1 = __importDefault( + require('@commitlint/resolve-extends'), +); +const load_plugin_1 = __importDefault(require('./utils/load-plugin')); +const load_config_1 = require('@commitlint/load/lib/utils/load-config'); +const load_parser_opts_1 = require('@commitlint/load/lib/utils/load-parser-opts'); +const pick_config_1 = require('@commitlint/load/lib/utils/pick-config'); +const w = (_, b) => (Array.isArray(b) ? b : undefined); +async function load(seed = {}, options = {}) { + const cwd = typeof options.cwd === 'undefined' ? process.cwd() : options.cwd; + const loaded = await load_config_1.loadConfig(cwd, options.file); + const base = + loaded && loaded.filepath ? path_1.default.dirname(loaded.filepath) : cwd; + // TODO: validate loaded.config against UserConfig type + // Might amount to breaking changes, defer until 9.0.0 + // Merge passed config with file based options + const config = pick_config_1.pickConfig( + merge_1.default({}, loaded ? loaded.config : null, seed), + ); + const opts = merge_1.default( + { extends: [], rules: {}, formatter: '@commitlint/format' }, + pick_1.default(config, 'extends', 'plugins', 'ignores', 'defaultIgnores'), + ); + // Resolve parserPreset key + if (typeof config.parserPreset === 'string') { + const resolvedParserPreset = resolve_from_1.default( + base, + config.parserPreset, + ); + const _require = eval('require'); + config.parserPreset = { + name: config.parserPreset, + path: resolvedParserPreset, + parserOpts: _require(resolvedParserPreset), + }; + } + // Resolve extends key + const extended = resolve_extends_1.default(opts, { + prefix: 'commitlint-config', + cwd: base, + parserPreset: config.parserPreset, + }); + const preset = pick_config_1.pickConfig( + mergeWith_1.default(extended, config, w), + ); + preset.plugins = {}; + // TODO: check if this is still necessary with the new factory based conventional changelog parsers + // config.extends = Array.isArray(config.extends) ? config.extends : []; + // Resolve parser-opts from preset + if (typeof preset.parserPreset === 'object') { + preset.parserPreset.parserOpts = await load_parser_opts_1.loadParserOpts( + preset.parserPreset.name, + // TODO: fix the types for factory based conventional changelog parsers + preset.parserPreset, + ); + } + // Resolve config-relative formatter module + if (typeof config.formatter === 'string') { + preset.formatter = + resolve_from_1.default.silent(base, config.formatter) || config.formatter; + } + // Read plugins from extends + if (Array.isArray(extended.plugins)) { + config.plugins = union_1.default(config.plugins, extended.plugins || []); + } + // resolve plugins + if (Array.isArray(config.plugins)) { + config.plugins.forEach((plugin) => { + if (typeof plugin === 'string') { + load_plugin_1.default( + preset.plugins, + plugin, + process.env.DEBUG === 'true', + ); + } else { + preset.plugins.local = plugin; + } + }); + } + const rules = preset.rules ? preset.rules : {}; + const qualifiedRules = ( + await Promise.all( + Object.entries(rules || {}).map((entry) => execute_rule_1.default(entry)), + ) + ).reduce((registry, item) => { + const [key, value] = item; + registry[key] = value; + return registry; + }, {}); + const helpUrl = + typeof config.helpUrl === 'string' + ? config.helpUrl + : 'https://github.com/conventional-changelog/commitlint/#what-is-commitlint'; + return { + extends: preset.extends, + formatter: preset.formatter, + parserPreset: preset.parserPreset, + ignores: preset.ignores, + defaultIgnores: preset.defaultIgnores, + plugins: preset.plugins, + rules: qualifiedRules, + helpUrl, + }; +} +exports.default = load; +//# sourceMappingURL=load.js.map diff --git a/src/modules/@commitlint/load/lib/utils/load-plugin.d.ts b/src/modules/@commitlint/load/lib/utils/load-plugin.d.ts new file mode 100644 index 0000000..fa3f150 --- /dev/null +++ b/src/modules/@commitlint/load/lib/utils/load-plugin.d.ts @@ -0,0 +1,7 @@ +import { PluginRecords } from '@commitlint/types'; +export default function loadPlugin( + plugins: PluginRecords, + pluginName: string, + debug?: boolean, +): PluginRecords; +//# sourceMappingURL=load-plugin.d.ts.map diff --git a/src/modules/@commitlint/load/lib/utils/load-plugin.js b/src/modules/@commitlint/load/lib/utils/load-plugin.js new file mode 100644 index 0000000..502970e --- /dev/null +++ b/src/modules/@commitlint/load/lib/utils/load-plugin.js @@ -0,0 +1,73 @@ +/** + * @author @commitlint/load + */ +'use strict'; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const path_1 = __importDefault(require('path')); +const chalk_1 = __importDefault(require('chalk')); +const plugin_naming_1 = require('@commitlint/load/lib/utils/plugin-naming'); +const plugin_errors_1 = require('@commitlint/load/lib/utils/plugin-errors'); +function loadPlugin(plugins, pluginName, debug = false) { + const _require = eval('require'); + const longName = plugin_naming_1.normalizePackageName(pluginName); + const shortName = plugin_naming_1.getShorthandName(longName); + let plugin = null; + if (pluginName.match(/\s+/u)) { + throw new plugin_errors_1.WhitespacePluginError(pluginName, { + pluginName: longName, + }); + } + const pluginKey = longName === pluginName ? shortName : pluginName; + if (!plugins[pluginKey]) { + try { + plugin = _require(longName); + } catch (pluginLoadErr) { + try { + // Check whether the plugin exists + _require.resolve(longName); + } catch (error) { + // If the plugin can't be resolved, display the missing plugin error (usually a config or install error) + console.error( + chalk_1.default.red(`Failed to load plugin ${longName}.`), + ); + throw new plugin_errors_1.MissingPluginError( + pluginName, + error.message, + { + pluginName: longName, + commitlintPath: path_1.default.resolve(__dirname, '../..'), + }, + ); + } + // Otherwise, the plugin exists and is throwing on module load for some reason, so print the stack trace. + throw pluginLoadErr; + } + // This step is costly, so skip if debug is disabled + if (debug) { + const resolvedPath = _require.resolve(longName); + let version = null; + try { + version = _require(`${longName}/package.json`).version; + } catch (e) { + // Do nothing + } + const loadedPluginAndVersion = version + ? `${longName}@${version}` + : `${longName}, version unknown`; + console.log( + chalk_1.default.blue( + `Loaded plugin ${pluginName} (${loadedPluginAndVersion}) (from ${resolvedPath})`, + ), + ); + } + plugins[pluginKey] = plugin; + } + return plugins; +} +exports.default = loadPlugin; +//# sourceMappingURL=load-plugin.js.map diff --git a/src/modules/@commitlint/resolve-extends/lib/index.js b/src/modules/@commitlint/resolve-extends/lib/index.js new file mode 100644 index 0000000..eb4fef8 --- /dev/null +++ b/src/modules/@commitlint/resolve-extends/lib/index.js @@ -0,0 +1,134 @@ +/** + * @author @commitlint/resolve-extends + */ +'use strict'; +var __rest = + (this && this.__rest) || + function (s, e) { + var t = {}; + for (var p in s) + if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === 'function') + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if ( + e.indexOf(p[i]) < 0 && + Object.prototype.propertyIsEnumerable.call(s, p[i]) + ) + t[p[i]] = s[p[i]]; + } + return t; + }; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const path_1 = __importDefault(require('path')); +require('resolve-global'); +const resolve_from_1 = __importDefault(require('resolve-from')); +const merge_1 = __importDefault(require('lodash/merge')); +const mergeWith_1 = __importDefault(require('lodash/mergeWith')); +const importFresh = require('import-fresh'); +function resolveExtends(config = {}, context = {}) { + const { extends: e } = config; + const extended = loadExtends(config, context).reduce( + (r, _a) => { + var { extends: _ } = _a, + c = __rest(_a, ['extends']); + return mergeWith_1.default(r, c, (objValue, srcValue) => { + if (Array.isArray(objValue)) { + return srcValue; + } + }); + }, + e ? { extends: e } : {}, + ); + return merge_1.default({}, extended, config); +} +exports.default = resolveExtends; +function loadExtends(config = {}, context = {}) { + const { extends: e } = config; + const ext = e ? (Array.isArray(e) ? e : [e]) : []; + return ext.reduce((configs, raw) => { + const _require = eval('require'); + const load = context.require || _require; + const resolved = resolveConfig(raw, context); + const c = load(resolved); + const cwd = path_1.default.dirname(resolved); + const ctx = merge_1.default({}, context, { cwd }); + // Resolve parser preset if none was present before + if ( + !context.parserPreset && + typeof c === 'object' && + typeof c.parserPreset === 'string' + ) { + const resolvedParserPreset = resolve_from_1.default(cwd, c.parserPreset); + const parserPreset = { + name: c.parserPreset, + path: `./${path_1.default.relative( + process.cwd(), + resolvedParserPreset, + )}` + .split(path_1.default.sep) + .join('/'), + parserOpts: _require(resolvedParserPreset), + }; + ctx.parserPreset = parserPreset; + config.parserPreset = parserPreset; + } + return [...configs, ...loadExtends(c, ctx), c]; + }, []); +} +function getId(raw = '', prefix = '') { + const first = raw.charAt(0); + const scoped = first === '@'; + const relative = first === '.'; + const absolute = path_1.default.isAbsolute(raw); + if (scoped) { + return raw.includes('/') ? raw : [raw, prefix].filter(String).join('/'); + } + return relative || absolute ? raw : [prefix, raw].filter(String).join('-'); +} +function resolveConfig(raw, context = {}) { + const resolve = context.resolve || resolveId; + const id = getId(raw, context.prefix); + try { + return resolve(id, context); + } catch (err) { + const legacy = getId(raw, 'conventional-changelog-lint-config'); + const resolved = resolve(legacy, context); + console.warn( + `Resolving ${raw} to legacy config ${legacy}. To silence this warning raise an issue at 'npm repo ${legacy}' to rename to ${id}.`, + ); + return resolved; + } +} +function resolveId(id, context = {}) { + const cwd = context.cwd || process.cwd(); + const localPath = resolveFromSilent(cwd, id); + if (typeof localPath === 'string') { + return localPath; + } + const resolveGlobal = context.resolveGlobal || resolveGlobalSilent; + const globalPath = resolveGlobal(id); + if (typeof globalPath === 'string') { + return globalPath; + } + const err = new Error(`Cannot find module "${id}" from "${cwd}"`); + err.code = 'MODULE_NOT_FOUND'; + throw err; +} +function resolveFromSilent(cwd, id) { + try { + return resolve_from_1.default(cwd, id); + } catch (err) {} +} +function resolveGlobalSilent(id) { + try { + const resolveGlobal = importFresh('resolve-global'); + return resolveGlobal(id); + } catch (err) {} +} +//# sourceMappingURL=index.js.map diff --git a/src/modules/import-fresh/index.d.ts b/src/modules/import-fresh/index.d.ts new file mode 100644 index 0000000..708737b --- /dev/null +++ b/src/modules/import-fresh/index.d.ts @@ -0,0 +1,28 @@ +/** +Import a module while bypassing the cache. + +@example +``` +// foo.js +let i = 0; +module.exports = () => ++i; + +// index.js +import importFresh = require('import-fresh'); + +require('./foo')(); +//=> 1 + +require('./foo')(); +//=> 2 + +importFresh('./foo')(); +//=> 1 + +importFresh('./foo')(); +//=> 1 +``` +*/ +declare function importFresh(moduleId: string): unknown; + +export = importFresh; diff --git a/src/modules/import-fresh/index.js b/src/modules/import-fresh/index.js new file mode 100644 index 0000000..fb0cda3 --- /dev/null +++ b/src/modules/import-fresh/index.js @@ -0,0 +1,35 @@ +/** + * @author sindresorhus/import-fresh + */ +'use strict'; +const path = require('path'); +const resolveFrom = require('resolve-from'); +const parentModule = require('parent-module'); + +module.exports = (moduleId) => { + if (typeof moduleId !== 'string') { + throw new TypeError('Expected a string'); + } + + const parentPath = parentModule(__filename); + + const filePath = resolveFrom(path.dirname(parentPath), moduleId); + + const oldModule = require.cache[filePath]; + // Delete itself from module parent + if (oldModule && oldModule.parent) { + let i = oldModule.parent.children.length; + + while (i--) { + if (oldModule.parent.children[i].id === filePath) { + oldModule.parent.children.splice(i, 1); + } + } + } + + delete require.cache[filePath]; // Delete module from cache + + const parent = require.cache[parentPath]; // If `filePath` and `parentPath` are the same, cache will already be deleted so we won't get a memory leak in next step + const _require = eval('require'); + return parent === undefined ? _require(filePath) : parent.require(filePath); // In case cache doesn't have parent, fall back to normal require +}; diff --git a/src/modules/resolve-global/index.d.ts b/src/modules/resolve-global/index.d.ts new file mode 100644 index 0000000..ee3c604 --- /dev/null +++ b/src/modules/resolve-global/index.d.ts @@ -0,0 +1,28 @@ +declare const resolveGlobal: { + /** + Resolve the path of a globally installed module. + + @param moduleId - What you would use in `require()`. + @returns The resolved path. Throws if the module can't be found. + + @example + ``` + // $ npm install --global cat-names + import resolveGlobal = require('resolve-global'); + + console.log(resolveGlobal('cat-names')); + //=> '/usr/local/lib/node_modules/cat-names' + ``` + */ + (moduleId: string): string; + + /** + Resolve the path of a globally installed module. + + @param moduleId - What you would use in `require()`. + @returns The resolved path. Returns `undefined` instead of throwing if the module can't be found. + */ + silent(moduleId: string): string | undefined; +}; + +export = resolveGlobal; diff --git a/src/modules/resolve-global/index.js b/src/modules/resolve-global/index.js new file mode 100644 index 0000000..8e495c5 --- /dev/null +++ b/src/modules/resolve-global/index.js @@ -0,0 +1,25 @@ +/** + * @author sindresorhus/resolve-global + */ +'use strict'; +const path = require('path'); +const globalDirs = require('global-dirs'); + +const resolveGlobal = (moduleId) => { + const _require = eval('require'); + try { + return _require.resolve(path.join(globalDirs.yarn.packages, moduleId)); + } catch (_) { + return _require.resolve(path.join(globalDirs.npm.packages, moduleId)); + } +}; + +module.exports = resolveGlobal; + +module.exports.silent = (moduleId) => { + try { + return resolveGlobal(moduleId); + } catch (_) { + return undefined; + } +}; diff --git a/webpack.config.js b/webpack.config.js index 20a0317..bc32185 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -2,6 +2,7 @@ const path = require('path'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); +const WarningsToErrorsPlugin = require('warnings-to-errors-webpack-plugin'); /**@type {import('webpack').Configuration}*/ const config = { @@ -25,6 +26,14 @@ const config = { resolve: { // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader extensions: ['.ts', '.js'], + alias: { + '@commitlint/resolve-extends': path.resolve( + __dirname, + 'src/modules/@commitlint/resolve-extends/lib/', + ), + 'import-fresh': path.resolve(__dirname, 'src/modules/import-fresh'), + 'resolve-global': path.resolve(__dirname, 'src/modules/resolve-global'), + }, }, module: { rules: [ @@ -47,10 +56,7 @@ const config = { optimization: { minimize: false, }, - stats: { - warnings: false, - }, - plugins: [new CleanWebpackPlugin()], + plugins: [new WarningsToErrorsPlugin(), new CleanWebpackPlugin()], }; module.exports = config; diff --git a/yarn.lock b/yarn.lock index 54aefb1..8f92b92 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3667,6 +3667,11 @@ vsce@^1.74.0: yauzl "^2.3.1" yazl "^2.2.2" +warnings-to-errors-webpack-plugin@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/warnings-to-errors-webpack-plugin/-/warnings-to-errors-webpack-plugin-2.0.1.tgz#87e6cc85b7995a434d2886a4889fc3fded9b0214" + integrity sha512-PhaAMVZzINqmBB7MPSfQhUZtC8VIbJRVmDOK4He5YdKfLLkhTFEWuaT2vKU/6Crbby3UEB+4PQiJSpZ30qXmZw== + watchpack@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.1.1.tgz#e99630550fca07df9f90a06056987baa40a689c7"