Skip to content

Commit

Permalink
feat(plugin-js-packages): implement runner for npm outdated
Browse files Browse the repository at this point in the history
  • Loading branch information
Tlacenka committed Mar 18, 2024
1 parent 6aa55a2 commit 7cb623b
Show file tree
Hide file tree
Showing 5 changed files with 399 additions and 14 deletions.
49 changes: 35 additions & 14 deletions packages/plugin-js-packages/src/lib/runner/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ import {
} from '@code-pushup/utils';
import {
FinalJSPackagesPluginConfig,
PackageCommand,
PackageDependency,
packageDependencies,
} from '../config';
import { auditResultToAuditOutput } from './audit/transform';
import { NpmAuditResultJson } from './audit/types';
import { PLUGIN_CONFIG_PATH, RUNNER_OUTPUT_PATH } from './constants';
import { outdatedResultToAuditOutput } from './outdated/transform';
import { NpmOutdatedResultJson } from './outdated/types';

export async function executeRunner(): Promise<void> {
const outputPath = join(
Expand All @@ -29,22 +32,28 @@ export async function executeRunner(): Promise<void> {
const results = await Promise.allSettled(
checks.flatMap(check =>
packageDependencies.map<Promise<AuditOutput>>(async dep => {
const outputFilename = `${packageManager}-${check}-${dep}.json`;

await executeProcess({
command: 'npm',
args: [
check,
...createAuditFlags(dep),
'--json',
'--audit-level=none',
'>',
join(outputPath, `${packageManager}-${check}-${dep}.json`),
...getCommandArgs(check, dep, join(outputPath, outputFilename)),
],
alwaysResolve: true, // npm outdated returns exit code 1 when outdated dependencies are found
});

const auditResult = await readJsonFile<NpmAuditResultJson>(
join(outputPath, `${packageManager}-${check}-${dep}.json`),
);
return auditResultToAuditOutput(auditResult, dep, auditLevelMapping);
if (check === 'audit') {
const auditResult = await readJsonFile<NpmAuditResultJson>(
join(outputPath, outputFilename),
);
return auditResultToAuditOutput(auditResult, dep, auditLevelMapping);
} else {
const outdatedResult = await readJsonFile<NpmOutdatedResultJson>(
join(outputPath, outputFilename),
);
return outdatedResultToAuditOutput(outdatedResult, dep);
}
}),
),
);
Expand Down Expand Up @@ -72,13 +81,25 @@ export async function createRunnerConfig(
};
}

function createAuditFlags(currentDep: PackageDependency) {
if (currentDep === 'optional') {
return packageDependencies.map(dep => `--include=${dep}`);
}
function getCommandArgs(
check: PackageCommand,
dep: PackageDependency,
outputPath: string,
) {
return check === 'audit'
? [
...createAuditFlags(dep),
'--json',
'--audit-level=none',
'>',
outputPath,
]
: ['--json', '--long', '>', outputPath];
}

function createAuditFlags(currentDep: PackageDependency) {
return [
`--include${currentDep}`,
`--include=${currentDep}`,
...packageDependencies
.filter(dep => dep !== currentDep)
.map(dep => `--omit=${dep}`),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { IssueSeverity } from '@code-pushup/models';
import { VersionType } from './types';

export const outdatedSeverity: Record<VersionType, IssueSeverity> = {
major: 'error',
minor: 'warning',
patch: 'info',
};
102 changes: 102 additions & 0 deletions packages/plugin-js-packages/src/lib/runner/outdated/transform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { Issue } from '@code-pushup/models';
import { objectToEntries, pluralize } from '@code-pushup/utils';
import { PackageDependency } from '../../config';
import { outdatedSeverity } from './constants';
import {
NormalizedOutdatedEntries,
NormalizedVersionOverview,
NpmOutdatedResultJson,
PackageVersion,
VersionType,
} from './types';

export function outdatedResultToAuditOutput(
result: NpmOutdatedResultJson,
dependenciesType: PackageDependency,
) {
// current might be missing in some cases
// https://stackoverflow.com/questions/42267101/npm-outdated-command-shows-missing-in-current-version
const validDependencies: NormalizedOutdatedEntries = objectToEntries(result)
.filter(
(entry): entry is [string, NormalizedVersionOverview] =>
entry[1].current != null,
)
.filter(([, detail]) =>
dependenciesType === 'prod'
? detail.type === 'dependencies'
: detail.type.startsWith(dependenciesType),
);
const outdatedDependencies = validDependencies.filter(
([, versions]) => versions.current !== versions.wanted,
);

const issues =
outdatedDependencies.length === 0
? []
: outdatedToIssues(outdatedDependencies);

return {
slug: `npm-outdated-${dependenciesType}`,
score: outdatedDependencies.length === 0 ? 1 : 0,
value: outdatedDependencies.length,
displayValue: outdatedToDisplayValue(outdatedDependencies.length),
...(issues.length > 0 && { details: { issues } }),
};
}

function outdatedToDisplayValue(outdatedDeps: number) {
return outdatedDeps === 0
? 'passed'
: `${outdatedDeps} outdated ${
outdatedDeps === 1 ? 'dependency' : pluralize('dependency')
}`;
}

export function outdatedToIssues(
dependencies: NormalizedOutdatedEntries,
): Issue[] {
return dependencies.map<Issue>(([name, versions]) => {
const outdatedLevel = getOutdatedLevel(versions.current, versions.wanted);
const packageDocumentation =
versions.homepage == null
? ''
: ` Package documentation [here](${versions.homepage})`;

return {
message: `Package ${name} requires a ${outdatedLevel} update from **${versions.current}** to **${versions.wanted}**.${packageDocumentation}`,
severity: outdatedSeverity[outdatedLevel],
};
});
}

export function getOutdatedLevel(
currentFullVersion: string,
wantedFullVersion: string,
): VersionType {
const current = splitPackageVersion(currentFullVersion);
const wanted = splitPackageVersion(wantedFullVersion);

if (current.major < wanted.major) {
return 'major';
}

if (current.minor < wanted.minor) {
return 'minor';
}

if (current.patch < wanted.patch) {
return 'patch';
}

throw new Error('Package is not outdated.');
}

export function splitPackageVersion(fullVersion: string): PackageVersion {
const [major, minor, patch] = fullVersion.split('.').map(Number);

if (major == null || minor == null || patch == null) {
throw new Error(`Invalid version description ${fullVersion}`);
}

return { major, minor, patch };
}
Loading

0 comments on commit 7cb623b

Please sign in to comment.