UNPKG

@rushstack/lockfile-explorer

Version:

Rush Lockfile Explorer: The UI for solving version conflicts quickly in a large monorepo

145 lines 8.2 kB
"use strict"; // 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