Skip to content

Commit 1b8bbd2

Browse files
committed
feat(plugin-js-packages): log initializer and runner steps
1 parent 075c4f7 commit 1b8bbd2

File tree

11 files changed

+185
-27
lines changed

11 files changed

+185
-27
lines changed

packages/plugin-js-packages/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"dependencies": {
4040
"@code-pushup/models": "0.97.0",
4141
"@code-pushup/utils": "0.97.0",
42+
"ansis": "^3.3.2",
4243
"build-md": "^0.4.1",
4344
"semver": "^7.6.0",
4445
"zod": "^4.0.5"

packages/plugin-js-packages/src/lib/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import type { IssueSeverity } from '@code-pushup/models';
22
import type { DependencyGroup, PackageAuditLevel } from './config.js';
33
import type { DependencyGroupLong } from './runner/outdated/types.js';
44

5+
export const JS_PACKAGES_PLUGIN_SLUG = 'js-packages';
6+
export const JS_PACKAGES_PLUGIN_TITLE = 'JS packages';
7+
58
export const defaultAuditLevelMapping: Record<
69
PackageAuditLevel,
710
IssueSeverity
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { pluginMetaLogFormatter } from '@code-pushup/utils';
2+
import { JS_PACKAGES_PLUGIN_TITLE } from './constants.js';
3+
4+
export const formatMetaLog = pluginMetaLogFormatter(JS_PACKAGES_PLUGIN_TITLE);

packages/plugin-js-packages/src/lib/js-packages-plugin.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
1+
import ansis from 'ansis';
12
import { createRequire } from 'node:module';
23
import type { Audit, Group, PluginConfig } from '@code-pushup/models';
4+
import { logger, pluralizeToken } from '@code-pushup/utils';
35
import {
46
type DependencyGroup,
57
type JSPackagesPluginConfig,
68
type PackageCommand,
79
type PackageManagerId,
810
dependencyGroups,
911
} from './config.js';
10-
import { dependencyDocs, dependencyGroupWeights } from './constants.js';
12+
import {
13+
JS_PACKAGES_PLUGIN_SLUG,
14+
JS_PACKAGES_PLUGIN_TITLE,
15+
dependencyDocs,
16+
dependencyGroupWeights,
17+
} from './constants.js';
18+
import { formatMetaLog } from './format.js';
1119
import { packageManagers } from './package-managers/package-managers.js';
1220
import { createRunnerFunction } from './runner/runner.js';
1321
import { normalizeConfig } from './utils.js';
@@ -44,17 +52,26 @@ export async function jsPackagesPlugin(
4452
'../../package.json',
4553
) as typeof import('../../package.json');
4654

55+
const audits = createAudits(packageManager.slug, checks, depGroups);
56+
const groups = createGroups(packageManager.slug, checks, depGroups);
57+
58+
logger.info(
59+
formatMetaLog(
60+
`Created ${pluralizeToken('audit', audits.length)} and ${pluralizeToken('group', groups.length)} for ${ansis.bold(packageManager.name)} package manager`,
61+
),
62+
);
63+
4764
return {
48-
slug: 'js-packages',
49-
title: 'JS Packages',
65+
slug: JS_PACKAGES_PLUGIN_SLUG,
66+
title: JS_PACKAGES_PLUGIN_TITLE,
5067
icon: packageManager.icon,
5168
description:
5269
'This plugin runs audit to uncover vulnerabilities and lists outdated dependencies. It supports npm, yarn classic, yarn modern, and pnpm package managers.',
5370
docsUrl: packageManager.docs.homepage,
5471
packageName: packageJson.name,
5572
version: packageJson.version,
56-
audits: createAudits(packageManager.slug, checks, depGroups),
57-
groups: createGroups(packageManager.slug, checks, depGroups),
73+
audits,
74+
groups,
5875
runner: createRunnerFunction({
5976
...jsPackagesPluginConfigRest,
6077
checks,

packages/plugin-js-packages/src/lib/js-packages-plugin.unit.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ describe('jsPackagesPlugin', () => {
2323
await expect(jsPackagesPlugin()).resolves.toStrictEqual(
2424
expect.objectContaining({
2525
slug: 'js-packages',
26-
title: 'JS Packages',
26+
title: 'JS packages',
2727
audits: expect.arrayContaining([
2828
expect.objectContaining({ slug: 'npm-audit-prod' }),
2929
expect.objectContaining({ slug: 'npm-audit-dev' }),
@@ -49,7 +49,7 @@ describe('jsPackagesPlugin', () => {
4949
).resolves.toStrictEqual(
5050
expect.objectContaining({
5151
slug: 'js-packages',
52-
title: 'JS Packages',
52+
title: 'JS packages',
5353
audits: expect.any(Array),
5454
groups: expect.any(Array),
5555
runner: expect.any(Function),

packages/plugin-js-packages/src/lib/package-managers/derive-package-manager.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { readFile } from 'node:fs/promises';
22
import path from 'node:path';
3-
import { fileExists } from '@code-pushup/utils';
3+
import { fileExists, logger } from '@code-pushup/utils';
44
import type { PackageManagerId } from '../config.js';
5+
import { formatMetaLog } from '../format.js';
56
import { deriveYarnVersion } from './derive-yarn.js';
7+
import { packageManagers } from './package-managers.js';
68

79
export async function derivePackageManagerInPackageJson(
810
currentDir = process.cwd(),
@@ -35,17 +37,27 @@ export async function derivePackageManager(
3537
const pkgManagerFromPackageJson =
3638
await derivePackageManagerInPackageJson(currentDir);
3739
if (pkgManagerFromPackageJson) {
40+
logDerivedPackageManager(
41+
pkgManagerFromPackageJson,
42+
'packageManager field in package.json',
43+
);
3844
return pkgManagerFromPackageJson;
3945
}
4046

4147
// Check for lock files
4248
if (await fileExists(path.join(currentDir, 'package-lock.json'))) {
49+
logDerivedPackageManager('npm', 'existence of package-lock.json file');
4350
return 'npm';
4451
} else if (await fileExists(path.join(currentDir, 'pnpm-lock.yaml'))) {
52+
logDerivedPackageManager('pnpm', 'existence of pnpm-lock.yaml file');
4553
return 'pnpm';
4654
} else if (await fileExists(path.join(currentDir, 'yarn.lock'))) {
4755
const yarnVersion = await deriveYarnVersion();
4856
if (yarnVersion) {
57+
logDerivedPackageManager(
58+
yarnVersion,
59+
'existence of yarn.lock file and yarn -v command',
60+
);
4961
return yarnVersion;
5062
}
5163
}
@@ -54,3 +66,15 @@ export async function derivePackageManager(
5466
'Could not detect package manager. Please provide it in the js-packages plugin config.',
5567
);
5668
}
69+
70+
function logDerivedPackageManager(
71+
id: PackageManagerId,
72+
sourceDescription: string,
73+
): void {
74+
const pm = packageManagers[id];
75+
logger.info(
76+
formatMetaLog(
77+
`Inferred ${pm.name} package manager from ${sourceDescription}`,
78+
),
79+
);
80+
}

packages/plugin-js-packages/src/lib/package-managers/npm/npm.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const npmDependencyOptions: Record<DependencyGroup, string[]> = {
1313

1414
export const npmPackageManager: PackageManager = {
1515
slug: 'npm',
16-
name: 'NPM',
16+
name: 'npm',
1717
command: 'npm',
1818
icon: 'npm',
1919
docs: {

packages/plugin-js-packages/src/lib/package-managers/yarn-modern/yarn-modern.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const yarnModernEnvironmentOptions: Record<DependencyGroup, string> = {
1313

1414
export const yarnModernPackageManager: PackageManager = {
1515
slug: 'yarn-modern',
16-
name: 'yarn-modern',
16+
name: 'Yarn v2+',
1717
command: 'yarn',
1818
icon: 'yarn',
1919
docs: {

packages/plugin-js-packages/src/lib/runner/runner.ts

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,28 @@
11
import path from 'node:path';
2-
import type { RunnerFunction } from '@code-pushup/models';
2+
import type {
3+
RunnerFunction,
4+
TableColumnObject,
5+
TableRowObject,
6+
} from '@code-pushup/models';
37
import {
48
asyncSequential,
9+
capitalize,
510
executeProcess,
11+
formatAsciiTable,
12+
logger,
613
objectFromEntries,
14+
objectToEntries,
15+
pluralizeToken,
716
} from '@code-pushup/utils';
817
import {
918
type AuditSeverity,
1019
type DependencyGroup,
1120
type FinalJSPackagesPluginConfig,
21+
type PackageAuditLevel,
1222
type PackageJsonPath,
1323
type PackageManagerId,
1424
dependencyGroups,
25+
packageAuditLevels,
1526
} from '../config.js';
1627
import { dependencyGroupToLong } from '../constants.js';
1728
import { packageManagers } from '../package-managers/package-managers.js';
@@ -54,6 +65,8 @@ async function processOutdated(
5465
depGroups: DependencyGroup[],
5566
packageJsonPath: PackageJsonPath,
5667
) {
68+
logger.info('Looking for outdated packages ...');
69+
5770
const pm = packageManagers[id];
5871
const { stdout } = await executeProcess({
5972
command: pm.command,
@@ -62,9 +75,13 @@ async function processOutdated(
6275
ignoreExitCode: true, // outdated returns exit code 1 when outdated dependencies are found
6376
});
6477

78+
const normalizedResult = pm.outdated.unifyResult(stdout);
79+
logger.info(
80+
`Detected ${pluralizeToken('outdated package', normalizedResult.length)} in total`,
81+
);
82+
6583
const depTotals = await getTotalDependencies(packageJsonPath);
6684

67-
const normalizedResult = pm.outdated.unifyResult(stdout);
6885
return depGroups.map(depGroup =>
6986
outdatedResultToAuditOutput(
7087
normalizedResult,
@@ -88,6 +105,10 @@ async function processAudit(
88105
supportedAuditDepGroups.includes(group),
89106
);
90107

108+
logger.info(
109+
`Auditing packages for ${pluralizeToken('dependency group', compatibleAuditDepGroups.length)} (${compatibleAuditDepGroups.join(', ')}) ...`,
110+
);
111+
91112
const auditResults = await asyncSequential(
92113
compatibleAuditDepGroups,
93114
async (depGroup): Promise<[DependencyGroup, AuditResult]> => {
@@ -104,6 +125,8 @@ async function processAudit(
104125
const resultsMap = objectFromEntries(auditResults);
105126
const uniqueResults = pm.audit.postProcessResult?.(resultsMap) ?? resultsMap;
106127

128+
logAuditSummary(uniqueResults);
129+
107130
return compatibleAuditDepGroups.map(depGroup =>
108131
auditResultToAuditOutput(
109132
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -114,3 +137,66 @@ async function processAudit(
114137
),
115138
);
116139
}
140+
141+
function logAuditSummary(
142+
results: Partial<Record<DependencyGroup, AuditResult>>,
143+
): void {
144+
const { totalCount, countsPerLevel } = aggregateAuditResults(results);
145+
const formattedLevels = objectToEntries(countsPerLevel)
146+
.filter(([, count]) => count > 0)
147+
.map(([level, count]) => `${count} ${level}`)
148+
.join(', ');
149+
150+
logger.info(
151+
[
152+
`Found ${pluralizeToken('vulnerability', totalCount)} in total`,
153+
formattedLevels && `(${formattedLevels})`,
154+
]
155+
.filter(Boolean)
156+
.join(' '),
157+
);
158+
159+
if (!logger.isVerbose()) {
160+
return;
161+
}
162+
logger.debug(
163+
formatAsciiTable({
164+
columns: [
165+
{ key: 'depGroup', label: 'Dep. group' },
166+
...[...packageAuditLevels, 'total'].map(
167+
(level): TableColumnObject => ({
168+
key: level,
169+
label: capitalize(level),
170+
align: 'right',
171+
}),
172+
),
173+
],
174+
rows: objectToEntries(results).map(
175+
([depGroup, result]): TableRowObject => ({
176+
depGroup,
177+
...result?.summary,
178+
}),
179+
),
180+
}),
181+
);
182+
}
183+
184+
function aggregateAuditResults(
185+
results: Partial<Record<DependencyGroup, AuditResult>>,
186+
) {
187+
const totalCount = Object.values(results).reduce(
188+
(acc, { vulnerabilities }) => acc + vulnerabilities.length,
189+
0,
190+
);
191+
const countsPerLevel = Object.values(results).reduce<
192+
Record<PackageAuditLevel, number>
193+
>(
194+
(acc, { summary }) =>
195+
objectFromEntries(
196+
packageAuditLevels.map(level => [level, acc[level] + summary[level]]),
197+
),
198+
objectFromEntries(packageAuditLevels.map(level => [level, 0])),
199+
);
200+
201+
return { totalCount, countsPerLevel };
202+
}

packages/plugin-js-packages/src/lib/runner/utils.ts

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
import path from 'node:path';
12
import {
3+
logger,
24
objectFromEntries,
5+
objectToEntries,
36
objectToKeys,
7+
pluralizeToken,
48
readJsonFile,
59
} from '@code-pushup/utils';
610
import type { AuditResult, Vulnerability } from './audit/types.js';
@@ -54,19 +58,38 @@ export function filterAuditResult(
5458
export async function getTotalDependencies(
5559
packageJsonPath: string,
5660
): Promise<DependencyTotals> {
57-
const parsedDeps = await readJsonFile<PackageJson>(packageJsonPath);
61+
const formattedPath = path.relative(process.cwd(), packageJsonPath);
5862

59-
const mergedDeps = objectFromEntries(
60-
dependencyGroupLong.map(group => {
61-
const deps = parsedDeps[group];
62-
return [group, deps == null ? [] : objectToKeys(deps)];
63-
}),
64-
);
63+
return logger.task(
64+
`Counting direct dependencies in ${formattedPath}`,
65+
async () => {
66+
const parsedDeps = await readJsonFile<PackageJson>(packageJsonPath);
67+
68+
const mergedDeps = objectFromEntries(
69+
dependencyGroupLong.map(group => {
70+
const deps = parsedDeps[group];
71+
return [group, deps == null ? [] : objectToKeys(deps)];
72+
}),
73+
);
74+
75+
const depTotals = objectFromEntries(
76+
objectToKeys(mergedDeps).map(deps => [
77+
deps,
78+
new Set(mergedDeps[deps]).size,
79+
]),
80+
);
6581

66-
return objectFromEntries(
67-
objectToKeys(mergedDeps).map(deps => [
68-
deps,
69-
new Set(mergedDeps[deps]).size,
70-
]),
82+
const depTotal = Object.values(depTotals).reduce(
83+
(acc, count) => acc + count,
84+
0,
85+
);
86+
const groupsSummary = objectToEntries(depTotals)
87+
.filter(([, count]) => count > 0)
88+
.map(([group, count]) => `${count} ${group}`)
89+
.join(', ');
90+
const message = `Found ${pluralizeToken('direct dependency', depTotal)} in ${formattedPath} (${groupsSummary})`;
91+
92+
return { message, result: depTotals };
93+
},
7194
);
7295
}

0 commit comments

Comments
 (0)