157 lines
6.0 KiB
JavaScript
157 lines
6.0 KiB
JavaScript
import { createRequire } from "node:module";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
import pc from "picocolors";
|
|
import { normalizePackageName, getShorthandName } from "./plugin-naming.js";
|
|
import { WhitespacePluginError, MissingPluginError } from "./plugin-errors.js";
|
|
import { resolveFromNpxCache } from "@commitlint/resolve-extends";
|
|
const require = createRequire(import.meta.url);
|
|
const __dirname = path.resolve(fileURLToPath(import.meta.url), "..");
|
|
const dynamicImport = async (id) => {
|
|
const imported = await import(path.isAbsolute(id) ? pathToFileURL(id).toString() : id);
|
|
return ("default" in imported && imported.default) || imported;
|
|
};
|
|
function sanitizeErrorMessage(message) {
|
|
return message
|
|
.replace(/\/[^/]+\/node_modules/g, "...")
|
|
.replace(/\\[^\\]+\\node_modules/g, "...");
|
|
}
|
|
function findPackageJson(dir) {
|
|
let current = dir;
|
|
const root = path.parse(dir).root;
|
|
while (current !== root) {
|
|
const pkgPath = path.join(current, "package.json");
|
|
if (fs.existsSync(pkgPath)) {
|
|
return pkgPath;
|
|
}
|
|
current = path.dirname(current);
|
|
}
|
|
return null;
|
|
}
|
|
function normalizeOptions(options) {
|
|
if (typeof options === "boolean") {
|
|
return { debug: options };
|
|
}
|
|
return options;
|
|
}
|
|
export default async function loadPlugin(plugins, pluginName, options = {}) {
|
|
const normalized = normalizeOptions(options);
|
|
const { debug = false, searchPaths = [] } = normalized;
|
|
for (const searchPath of searchPaths) {
|
|
if (typeof searchPath !== "string" || !path.isAbsolute(searchPath)) {
|
|
throw new Error(`Invalid searchPath "${searchPath}": must be an absolute path`);
|
|
}
|
|
if (!fs.existsSync(searchPath)) {
|
|
throw new Error(`Invalid searchPath "${searchPath}": directory does not exist`);
|
|
}
|
|
if (!fs.statSync(searchPath).isDirectory()) {
|
|
throw new Error(`Invalid searchPath "${searchPath}": must be a directory, not a file`);
|
|
}
|
|
}
|
|
const longName = normalizePackageName(pluginName);
|
|
const shortName = getShorthandName(longName);
|
|
if (pluginName.match(/\s+/u)) {
|
|
throw new WhitespacePluginError(pluginName, {
|
|
pluginName: longName,
|
|
});
|
|
}
|
|
const pluginKey = longName === pluginName ? shortName : pluginName;
|
|
if (!plugins[pluginKey]) {
|
|
let plugin;
|
|
let resolvedPath;
|
|
// Try to load from npx cache directories using require.resolve
|
|
const npxResolvedPath = resolveFromNpxCache(longName);
|
|
if (npxResolvedPath) {
|
|
try {
|
|
plugin = await dynamicImport(npxResolvedPath);
|
|
resolvedPath = npxResolvedPath;
|
|
}
|
|
catch (err) {
|
|
if (debug) {
|
|
console.debug(`Failed to load plugin ${longName} from npx cache: ${err.message}`);
|
|
}
|
|
}
|
|
}
|
|
// Try to load from additional search paths (extended config's node_modules)
|
|
if (!plugin) {
|
|
for (const searchPath of searchPaths) {
|
|
try {
|
|
resolvedPath = require.resolve(longName, { paths: [searchPath] });
|
|
plugin = await dynamicImport(resolvedPath);
|
|
break;
|
|
}
|
|
catch (err) {
|
|
if (debug) {
|
|
console.debug(`Failed to load plugin ${longName} from ${searchPath}: ${err.message}`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Try default resolution as last resort
|
|
if (!plugin) {
|
|
try {
|
|
plugin = await dynamicImport(longName);
|
|
// Try to resolve path for debug logging
|
|
try {
|
|
resolvedPath = require.resolve(longName);
|
|
}
|
|
catch {
|
|
// Ignore - path not critical
|
|
}
|
|
}
|
|
catch (err) {
|
|
let resolutionError;
|
|
try {
|
|
resolvedPath = require.resolve(longName);
|
|
}
|
|
catch (resolveErr) {
|
|
resolutionError = resolveErr;
|
|
}
|
|
if (resolutionError) {
|
|
// Resolution failed - throw MissingPluginError
|
|
if (debug) {
|
|
console.debug(`Failed to resolve plugin ${longName}: ${resolutionError.message}`);
|
|
}
|
|
throw new MissingPluginError(pluginName, sanitizeErrorMessage(resolutionError.message), {
|
|
pluginName: longName,
|
|
commitlintPath: path.resolve(__dirname, "../.."),
|
|
});
|
|
}
|
|
// Resolution succeeded but import failed - rethrow original error
|
|
throw err;
|
|
}
|
|
}
|
|
// This step is costly, so skip if debug is disabled
|
|
if (debug) {
|
|
let version = null;
|
|
if (resolvedPath) {
|
|
try {
|
|
const pkgPath = findPackageJson(path.dirname(resolvedPath));
|
|
if (pkgPath) {
|
|
version = require(pkgPath).version;
|
|
}
|
|
}
|
|
catch {
|
|
// Do nothing
|
|
}
|
|
}
|
|
const loadedPluginAndVersion = version
|
|
? `${longName}@${version}`
|
|
: `${longName}, version unknown`;
|
|
const fromPath = resolvedPath ? ` (from ${resolvedPath})` : "";
|
|
console.log(pc.blue(`Loaded plugin ${pluginName} (${loadedPluginAndVersion})${fromPath}`));
|
|
}
|
|
if (plugin) {
|
|
plugins[pluginKey] = plugin;
|
|
}
|
|
else {
|
|
throw new MissingPluginError(pluginName, "Plugin loaded but is undefined", {
|
|
pluginName: longName,
|
|
commitlintPath: path.resolve(__dirname, "../.."),
|
|
});
|
|
}
|
|
}
|
|
return plugins;
|
|
}
|
|
//# sourceMappingURL=load-plugin.js.map
|