@rushstack/lockfile-explorer
Version:
Rush Lockfile Explorer: The UI for solving version conflicts quickly in a large monorepo
145 lines • 8.2 kB
JavaScript
;
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CheckAction = void 0;
const node_path_1 = __importDefault(require("node:path"));
const js_yaml_1 = __importDefault(require("js-yaml"));
const semver_1 = __importDefault(require("semver"));
const rush_sdk_1 = require("@rushstack/rush-sdk");
const ts_command_line_1 = require("@rushstack/ts-command-line");
const terminal_1 = require("@rushstack/terminal");
const node_core_library_1 = require("@rushstack/node-core-library");
const lockfile_lint_schema_json_1 = __importDefault(require("../../../schemas/lockfile-lint.schema.json"));
const common_1 = require("../../../constants/common");
const shrinkwrap_1 = require("../../../utils/shrinkwrap");
class CheckAction extends ts_command_line_1.CommandLineAction {
constructor(parser) {
super({
actionName: 'check',
summary: 'Check and report dependency issues in your workspace',
documentation: 'This command applies the policies that are configured in ' +
common_1.LOCKFILE_LINT_JSON_FILENAME +
', reporting any problems found in your PNPM workspace.'
});
this._terminal = parser.globalTerminal;
this._checkedProjects = new Set();
this._docMap = new Map();
}
async _checkVersionCompatibilityAsync(shrinkwrapFileMajorVersion, packages, dependencyPath, requiredVersions, checkedDependencyPaths) {
var _a;
if (packages && packages[dependencyPath] && !checkedDependencyPaths.has(dependencyPath)) {
checkedDependencyPaths.add(dependencyPath);
const { name, version } = (0, shrinkwrap_1.parseDependencyPath)(shrinkwrapFileMajorVersion, dependencyPath);
if (name in requiredVersions && !semver_1.default.satisfies(version, requiredVersions[name])) {
throw new Error(`The version of "${name}" should match "${requiredVersions[name]}";` +
` actual version is "${version}"`);
}
await Promise.all(Object.entries((_a = packages[dependencyPath].dependencies) !== null && _a !== void 0 ? _a : {}).map(async ([dependencyPackageName, dependencyPackageVersion]) => {
await this._checkVersionCompatibilityAsync(shrinkwrapFileMajorVersion, packages, (0, shrinkwrap_1.splicePackageWithVersion)(shrinkwrapFileMajorVersion, dependencyPackageName, dependencyPackageVersion), requiredVersions, checkedDependencyPaths);
}));
}
}
async _searchAndValidateDependenciesAsync(project, requiredVersions) {
this._terminal.writeLine(`Checking project "${project.packageName}"`);
const projectFolder = project.projectFolder;
const subspace = project.subspace;
const shrinkwrapFilename = subspace.getCommittedShrinkwrapFilePath();
let doc;
if (this._docMap.has(shrinkwrapFilename)) {
doc = this._docMap.get(shrinkwrapFilename);
}
else {
const pnpmLockfileText = await node_core_library_1.FileSystem.readFileAsync(shrinkwrapFilename);
doc = js_yaml_1.default.load(pnpmLockfileText);
this._docMap.set(shrinkwrapFilename, doc);
}
const { importers, lockfileVersion, packages } = doc;
const shrinkwrapFileMajorVersion = (0, shrinkwrap_1.getShrinkwrapFileMajorVersion)(lockfileVersion);
const checkedDependencyPaths = new Set();
await Promise.all(Object.entries(importers).map(async ([relativePath, { dependencies }]) => {
var _a;
if (node_path_1.default.resolve(projectFolder, relativePath) === projectFolder) {
const dependenciesEntries = Object.entries(dependencies !== null && dependencies !== void 0 ? dependencies : {});
for (const [dependencyName, dependencyValue] of dependenciesEntries) {
const fullDependencyPath = (0, shrinkwrap_1.splicePackageWithVersion)(shrinkwrapFileMajorVersion, dependencyName, typeof dependencyValue === 'string'
? dependencyValue
: dependencyValue.version);
if (fullDependencyPath.includes('link:')) {
const dependencyProject = this._rushConfiguration.getProjectByName(dependencyName);
if (dependencyProject && !((_a = this._checkedProjects) === null || _a === void 0 ? void 0 : _a.has(dependencyProject))) {
this._checkedProjects.add(project);
await this._searchAndValidateDependenciesAsync(dependencyProject, requiredVersions);
}
}
else {
await this._checkVersionCompatibilityAsync(shrinkwrapFileMajorVersion, packages, fullDependencyPath, requiredVersions, checkedDependencyPaths);
}
}
}
}));
}
async _performVersionRestrictionCheckAsync(requiredVersions, projectName) {
var _a;
try {
const project = (_a = this._rushConfiguration) === null || _a === void 0 ? void 0 : _a.getProjectByName(projectName);
if (!project) {
throw new Error(`Specified project "${projectName}" does not exist in ${common_1.LOCKFILE_LINT_JSON_FILENAME}`);
}
this._checkedProjects.add(project);
await this._searchAndValidateDependenciesAsync(project, requiredVersions);
return undefined;
}
catch (e) {
return e.message;
}
}
async onExecuteAsync() {
const rushConfiguration = rush_sdk_1.RushConfiguration.tryLoadFromDefaultLocation();
if (!rushConfiguration) {
throw new Error('The "lockfile-explorer check" must be executed in a folder that is under a Rush workspace folder');
}
this._rushConfiguration = rushConfiguration;
const lintingFile = node_path_1.default.resolve(this._rushConfiguration.commonFolder, 'config', common_1.LOCKFILE_EXPLORER_FOLDERNAME, common_1.LOCKFILE_LINT_JSON_FILENAME);
const { rules } = await node_core_library_1.JsonFile.loadAndValidateAsync(lintingFile, node_core_library_1.JsonSchema.fromLoadedObject(lockfile_lint_schema_json_1.default));
const issues = [];
await node_core_library_1.Async.forEachAsync(rules, async ({ requiredVersions, project, rule }) => {
switch (rule) {
case 'restrict-versions': {
const message = await this._performVersionRestrictionCheckAsync(requiredVersions, project);
if (message) {
issues.push({ project, rule, message });
}
break;
}
default: {
throw new Error('Unsupported rule name: ' + rule);
}
}
}, { concurrency: 50 });
if (issues.length > 0) {
this._terminal.writeLine();
// Deterministic order
for (const issue of issues.sort((a, b) => {
let diff = a.project.localeCompare(b.project);
if (diff !== 0) {
return diff;
}
diff = a.rule.localeCompare(b.rule);
if (diff !== 0) {
return diff;
}
return a.message.localeCompare(b.message);
})) {
this._terminal.writeLine(terminal_1.Colorize.red('PROBLEM: ') + terminal_1.Colorize.cyan(`[${issue.rule}] `) + issue.message + '\n');
}
throw new node_core_library_1.AlreadyReportedError();
}
this._terminal.writeLine(terminal_1.Colorize.green('SUCCESS: ') + 'All checks passed.');
}
}
exports.CheckAction = CheckAction;
//# sourceMappingURL=CheckAction.js.map