Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accept wildcard version in resolved issue id #97

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions src/cli/cmds/resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const {
excludeResolved,
allIssuesFromReport,
validateResolvedIssues,
resolutionIdMatchesIssueId,
} = require('../../issues/utils');
const {UsageError} = require('../../errors');
const handleCrash = require('../handleCrash');
Expand Down Expand Up @@ -77,22 +78,36 @@ exports.handler = async (argv) => {
Object.assign(issue, {id: getUniqueIssueId(issue)}),
);

const issueToResolve = resolvableIssues.find(({id}) => id === issueId);
const issuesToResolve = resolvableIssues.filter(({id}) =>
resolutionIdMatchesIssueId(issueId, id),
);

if (!issueToResolve) {
if (issuesToResolve.length === 0) {
throw new UsageError('Issue not found in current audit results.');
}

logger.log(`Resolving issue ${issueId}:`);
logger.log(`${logger.SEVERITY_ICONS[issueToResolve.severity]} ${issueToResolve.title}`);
logger.log(`${logger.SEVERITY_ICONS[issuesToResolve[0].severity]} ${issuesToResolve[0].title}`);
logger.log('');

const allIssuePaths = issuesToResolve.reduce(
(agg, i) => [
...agg,
...i.findings.paths.map((p) => ({path: p, package: `${i.name}@${i.version || i.range}`})),
],
[],
);

const selectedPaths = await prompts(
{
name: 'paths',
type: 'multiselect',
message: 'Select paths to resolve',
choices: issueToResolve.findings.paths.map((p) => ({title: p, value: p, selected: false})),
choices: allIssuePaths.map((p) => ({
title: issuesToResolve.length > 1 ? `${p.package}: ${p.path}` : p.path,
value: p.path,
selected: false,
})),
hint: ' - Space to select. Return to submit. "a" to select/deselect all.',
instructions: false,
min: 1,
Expand Down
16 changes: 9 additions & 7 deletions src/graph/generatePnpmGraph.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
const {processDependenciesForPackage, processPlaceholders, makeNode} = require('./utils');
const {
processDependenciesForPackage,
processPlaceholders,
makeNode,
SEMVER_REGEXP,
} = require('./utils');

const parsePath = (path) => {
// parse pnpm lockfile package names like:
Expand All @@ -10,12 +15,9 @@ const parsePath = (path) => {
// /@nestjs/schematics/9.1.0(typescript@5.0.3)
// /ts-node/10.9.1(@types/node@14.18.36)(typescript@4.9.3)
// see https://github.com/pnpm/pnpm/pull/5810
const results = path.match(
// basically /^\/(.*?)\/(SEMVER)(.*?)$/
/^\/(.*?)\/((0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?)(.*?)$/,
);
const name = results[1];
const version = results[2];
const results = path.match(new RegExp(`^/(.*?)/(${SEMVER_REGEXP.source})(.*?)$`));
const name = results?.[1];
const version = results?.[2];

return {name, version};
};
Expand Down
4 changes: 4 additions & 0 deletions src/graph/utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
const https = require('https');
const semverLib = require('semver');

const SEMVER_REGEXP =
/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?/;

const parseDependencyString = (depstring) => {
const parts = depstring.split('@');
let semver = parts.pop();
Expand Down Expand Up @@ -374,6 +377,7 @@ const getRegistryDataMultiple = async (packages, onProgress = () => {}) => {
};

module.exports = {
SEMVER_REGEXP,
makeNode,
parseDependencyString,
processDependenciesForPackage,
Expand Down
39 changes: 33 additions & 6 deletions src/issues/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const https = require('https');
const semverSatisfies = require('semver/functions/satisfies');
const {aggregateDependencies} = require('../charts/utils');
const {UsageError} = require('../errors');
const {SEMVER_REGEXP} = require('../graph/utils');

const getPathsForPackage = (packageGraph, packageName, semver) => {
const parse = (node, currentPath = [], depth = 0, seenNodes = []) => {
Expand Down Expand Up @@ -149,12 +150,34 @@ const makeSandwormIssueId = ({code, name, version, specifier}) =>

const getUniqueIssueId = (issue) => issue.githubAdvisoryId || issue.sandwormIssueId || issue.id;

const resolutionIdMatchesIssueId = (resolutionId, issueId) => {
if (typeof resolutionId !== 'string' || typeof issueId !== 'string') {
return false;
}

if (resolutionId === issueId) {
return true;
}

if (resolutionId.includes('*')) {
const [start, end] = resolutionId.split('*');
if (issueId.startsWith(start) && issueId.endsWith(end)) {
const wildcardContent = issueId.replace(start, '').replace(end, '');
if (wildcardContent.match(SEMVER_REGEXP)) {
return true;
}
}
}

return false;
};

const excludeResolved = (issues = [], resolved = []) => {
const filteredIssues = [];

issues.forEach((issue) => {
const issueId = getUniqueIssueId(issue);
const matchingResolutions = resolved.filter(({id}) => id === issueId);
const matchingResolutions = resolved.filter(({id}) => resolutionIdMatchesIssueId(id, issueId));
const matchingResolvedPaths = matchingResolutions.reduce(
(agg, {paths}) => agg.concat(paths),
[],
Expand Down Expand Up @@ -186,11 +209,11 @@ const validateResolvedIssues = (resolvedIssues = [], currentIssues = []) => {
throw new UsageError('Issue paths must be array');
}

const currentIssue = currentIssues.find(
(issue) => resolvedIssue.id === getUniqueIssueId(issue),
const matchingIssues = currentIssues.filter((issue) =>
resolutionIdMatchesIssueId(resolvedIssue.id, getUniqueIssueId(issue)),
);

if (!currentIssue) {
if (matchingIssues.length === 0) {
return [
...agg,
`Issue ${resolvedIssue.id} is not present in the latest audit, you can remove it from your resolution file`,
Expand All @@ -200,10 +223,13 @@ const validateResolvedIssues = (resolvedIssues = [], currentIssues = []) => {
return [
...agg,
...resolvedIssue.paths
.filter((path) => !currentIssue.findings.paths.includes(path))
.filter(
(path) =>
!matchingIssues.reduce((ag, i) => [...ag, ...i.findings.paths], []).includes(path),
)
.map(
(path) =>
`Issue ${resolvedIssue.id} with path ${path} is not present in the latest audit, you can remove it from your resolution file`,
`Path ${path} for issue ${resolvedIssue.id} is not present in the latest audit, you can remove it from your resolution file`,
),
];
}, []);
Expand All @@ -218,4 +244,5 @@ module.exports = {
excludeResolved,
allIssuesFromReport,
validateResolvedIssues,
resolutionIdMatchesIssueId,
};