Skip to content

Commit

Permalink
feat(plugin-js-packages): add runner config integration test, restruc…
Browse files Browse the repository at this point in the history
…ture runner
  • Loading branch information
Tlacenka committed Mar 18, 2024
1 parent 456793e commit cae4ca8
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 67 deletions.
127 changes: 60 additions & 67 deletions packages/plugin-js-packages/src/lib/runner/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import { writeFile } from 'node:fs/promises';
import { dirname, join } from 'node:path';
import type { AuditOutput, RunnerConfig } from '@code-pushup/models';
import { dirname } from 'node:path';
import type {
AuditOutput,
IssueSeverity,
RunnerConfig,
} from '@code-pushup/models';
import {
ensureDirectoryExists,
executeProcess,
readJsonFile,
} from '@code-pushup/utils';
import {
FinalJSPackagesPluginConfig,
PackageCommand,
PackageAuditLevel,
PackageDependency,
PackageManager,
packageDependencies,
} from '../config';
import { auditResultToAuditOutput } from './audit/transform';
Expand All @@ -18,55 +23,6 @@ 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(
process.cwd(),
'node_modules',
'.code-pushup',
'js-packages',
);

const { packageManager, checks, auditLevelMapping } =
await readJsonFile<FinalJSPackagesPluginConfig>(PLUGIN_CONFIG_PATH);

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,
...getCommandArgs(check, dep, join(outputPath, outputFilename)),
],
alwaysResolve: true, // npm outdated returns exit code 1 when outdated dependencies are found
});

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);
}
}),
),
);
const auditOutputs = results
.filter(
(x): x is PromiseFulfilledResult<AuditOutput> => x.status === 'fulfilled',
)
.map(x => x.value);

await ensureDirectoryExists(dirname(RUNNER_OUTPUT_PATH));
await writeFile(RUNNER_OUTPUT_PATH, JSON.stringify(auditOutputs));
}

export async function createRunnerConfig(
scriptPath: string,
config: FinalJSPackagesPluginConfig,
Expand All @@ -81,27 +37,64 @@ export async function createRunnerConfig(
};
}

function getCommandArgs(
check: PackageCommand,
dep: PackageDependency,
outputPath: string,
export async function executeRunner(): Promise<void> {
const { packageManager, checks, auditLevelMapping } =
await readJsonFile<FinalJSPackagesPluginConfig>(PLUGIN_CONFIG_PATH);

const auditResults = checks.includes('audit')
? await processAudit(packageManager, auditLevelMapping)
: [];

const outdatedResults = checks.includes('outdated')
? await processOutdated(packageManager)
: [];
const checkResults = [...auditResults, ...outdatedResults];

await ensureDirectoryExists(dirname(RUNNER_OUTPUT_PATH));
await writeFile(RUNNER_OUTPUT_PATH, JSON.stringify(checkResults));
}

async function processOutdated(packageManager: PackageManager) {
const { stdout } = await executeProcess({
command: packageManager,
args: ['outdated', '--json', '--long'],
alwaysResolve: true, // npm outdated returns exit code 1 when outdated dependencies are found
});

const outdatedResult = JSON.parse(stdout) as NpmOutdatedResultJson;
return packageDependencies.map(dep =>
outdatedResultToAuditOutput(outdatedResult, dep),
);
}

async function processAudit(
packageManager: PackageManager,
auditLevelMapping: Record<PackageAuditLevel, IssueSeverity>,
) {
return check === 'audit'
? [
...createAuditFlags(dep),
'--json',
'--audit-level=none',
'>',
outputPath,
]
: ['--json', '--long', '>', outputPath];
const auditResults = await Promise.allSettled(
packageDependencies.map<Promise<AuditOutput>>(async dep => {
const { stdout } = await executeProcess({
command: packageManager,
args: ['audit', ...getNpmAuditOptions(dep)],
});

const auditResult = JSON.parse(stdout) as NpmAuditResultJson;
return auditResultToAuditOutput(auditResult, dep, auditLevelMapping);
}),
);
return auditResults
.filter(
(x): x is PromiseFulfilledResult<AuditOutput> => x.status === 'fulfilled',
)
.map(x => x.value);
}

function createAuditFlags(currentDep: PackageDependency) {
return [
function getNpmAuditOptions(currentDep: PackageDependency) {
const flags = [
`--include=${currentDep}`,
...packageDependencies
.filter(dep => dep !== currentDep)
.map(dep => `--omit=${dep}`),
];
return [...flags, '--json', '--audit-level=none'];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { describe, expect, it } from 'vitest';
import { RunnerConfig } from '@code-pushup/models';
import { readJsonFile, removeDirectoryIfExists } from '@code-pushup/utils';
import { createRunnerConfig } from '.';
import { FinalJSPackagesPluginConfig } from '../config';
import { defaultAuditLevelMapping } from '../constants';
import { PLUGIN_CONFIG_PATH, WORKDIR } from './constants';

describe('createRunnerConfig', () => {
it('should create a valid runner config', async () => {
const runnerConfig = await createRunnerConfig('executeRunner.ts', {
packageManager: 'npm',
checks: ['audit'],
auditLevelMapping: defaultAuditLevelMapping,
});
expect(runnerConfig).toStrictEqual<RunnerConfig>({
command: 'node',
args: ['executeRunner.ts'],
outputFile: expect.stringContaining('runner-output.json'),
});
});

it('should provide plugin config to runner in JSON file', async () => {
await removeDirectoryIfExists(WORKDIR);
const pluginConfig: FinalJSPackagesPluginConfig = {
packageManager: 'yarn-classic',
checks: ['outdated'],
auditLevelMapping: { ...defaultAuditLevelMapping, moderate: 'error' },
};
await createRunnerConfig('executeRunner.ts', pluginConfig);
const config = await readJsonFile<FinalJSPackagesPluginConfig>(
PLUGIN_CONFIG_PATH,
);
expect(config).toStrictEqual(pluginConfig);
});
});

0 comments on commit cae4ca8

Please sign in to comment.