Skip to content
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
33 changes: 15 additions & 18 deletions packages/ci/src/lib/run-monorepo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { readFile } from 'node:fs/promises';
import type { CoreConfig } from '@code-pushup/models';
import {
type ExcludeNullableProps,
asyncSequential,
hasNoNullableProps,
} from '@code-pushup/utils';
import {
Expand Down Expand Up @@ -97,10 +98,7 @@ function runProjectsIndividually(
env.settings.logger.info(
`Running on ${projects.length} projects individually`,
);
return projects.reduce<Promise<ProjectRunResult[]>>(
async (acc, project) => [...(await acc), await runOnProject(project, env)],
Promise.resolve([]),
);
return asyncSequential(projects, project => runOnProject(project, env));
}

async function runProjectsInBulk(
Expand All @@ -120,8 +118,9 @@ async function runProjectsInBulk(

await collectMany(runManyCommand, env);

const currProjectReports = await Promise.all(
projects.map(async (project): Promise<ProjectReport> => {
const currProjectReports = await asyncSequential(
projects,
async (project): Promise<ProjectReport> => {
const ctx = createCommandContext(settings, project);
const config = await printPersistConfig(ctx);
const reports = await saveOutputFiles({
Expand All @@ -131,7 +130,7 @@ async function runProjectsInBulk(
settings,
});
return { project, reports, config, ctx };
}),
},
);
logger.debug(
`Loaded ${currProjectReports.length} persist configs by running print-config command for each project`,
Expand Down Expand Up @@ -186,14 +185,11 @@ async function compareProjectsInBulk(
}))
.filter(hasNoNullableProps);

const projectComparisons = await projectsToCompare.reduce<
Promise<Record<string, ProjectRunResult>>
>(
async (acc, args) => ({
...(await acc),
[args.project.name]: await compareReports(args),
}),
Promise.resolve({}),
const projectComparisons = Object.fromEntries(
await asyncSequential(projectsToCompare, async args => [
args.project.name,
await compareReports(args),
]),
);

return finalizeProjectReports(currProjectReports, projectComparisons);
Expand Down Expand Up @@ -227,12 +223,13 @@ async function collectPreviousReports(
}

return runInBaseBranch(base, env, async () => {
const uncachedProjectConfigs = await Promise.all(
uncachedProjectReports.map(async args => ({
const uncachedProjectConfigs = await asyncSequential(
uncachedProjectReports,
async args => ({
name: args.project.name,
ctx: args.ctx,
config: await checkPrintConfig(args),
})),
}),
);

const validProjectConfigs =
Expand Down
18 changes: 11 additions & 7 deletions packages/plugin-eslint/src/lib/meta/versions/legacy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import type { ESLint, Linter } from 'eslint';
import { distinct, exists, toArray, ui } from '@code-pushup/utils';
import {
asyncSequential,
distinct,
exists,
toArray,
ui,
} from '@code-pushup/utils';
import type { ESLintTarget } from '../../config.js';
import { setupESLint } from '../../setup.js';
import { type RuleData, isRuleOff, optionsFromRuleEntry } from '../parse.js';
Expand All @@ -10,12 +16,10 @@ export async function loadRulesForLegacyConfig({
}: ESLintTarget): Promise<RuleData[]> {
const eslint = await setupESLint(eslintrc);

const configs = await toArray(patterns).reduce(
async (acc, pattern) => [
...(await acc),
(await eslint.calculateConfigForFile(pattern)) as Linter.LegacyConfig,
],
Promise.resolve<Linter.LegacyConfig[]>([]),
const configs = await asyncSequential(
toArray(patterns),
pattern =>
eslint.calculateConfigForFile(pattern) as Promise<Linter.LegacyConfig>,
);

const rulesIds = distinct(
Expand Down
7 changes: 2 additions & 5 deletions packages/plugin-eslint/src/lib/runner/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
RunnerFilesPaths,
} from '@code-pushup/models';
import {
asyncSequential,
createRunnerFiles,
ensureDirectoryExists,
filePathToCliArg,
Expand All @@ -17,7 +18,6 @@ import {
import type { ESLintPluginRunnerConfig, ESLintTarget } from '../config.js';
import { lint } from './lint.js';
import { lintResultsToAudits, mergeLinterOutputs } from './transform.js';
import type { LinterOutput } from './types.js';

export async function executeRunner({
runnerConfigPath,
Expand All @@ -28,10 +28,7 @@ export async function executeRunner({

ui().logger.log(`ESLint plugin executing ${targets.length} lint targets`);

const linterOutputs = await targets.reduce(
async (acc, target) => [...(await acc), await lint(target)],
Promise.resolve<LinterOutput[]>([]),
);
const linterOutputs = await asyncSequential(targets, lint);
const lintResults = mergeLinterOutputs(linterOutputs);
const failedAudits = lintResultsToAudits(lintResults);

Expand Down
2 changes: 1 addition & 1 deletion packages/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ export {
safeCheckout,
toGitPath,
} from './lib/git/git.js';
export { groupByStatus } from './lib/group-by-status.js';
export {
hasNoNullableProps,
isPromiseFulfilledResult,
Expand All @@ -72,6 +71,7 @@ export { logMultipleResults } from './lib/log-results.js';
export { isVerbose, link, ui, type CliUi, type Column } from './lib/logging.js';
export { mergeConfigs } from './lib/merge-configs.js';
export { getProgressBar, type ProgressBar } from './lib/progress.js';
export { asyncSequential, groupByStatus } from './lib/promises.js';
export { generateRandomId } from './lib/random.js';
export {
CODE_PUSHUP_DOMAIN,
Expand Down
20 changes: 0 additions & 20 deletions packages/utils/src/lib/group-by-status.unit.test.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,18 @@
{ fulfilled: [], rejected: [] },
);
}

export async function asyncSequential<TInput, TOutput>(

Check warning on line 17 in packages/utils/src/lib/promises.ts

View workflow job for this annotation

GitHub Actions / Code PushUp

<✓> JSDoc coverage | Functions coverage

Missing functions documentation for asyncSequential
items: TInput[],
work: (item: TInput) => Promise<TOutput>,
): Promise<TOutput[]> {
// for-loop used instead of reduce for performance
const results: TOutput[] = [];
// eslint-disable-next-line functional/no-loop-statements
for (const item of items) {
const result = await work(item);
// eslint-disable-next-line functional/immutable-data
results.push(result);
}
return results;
}
52 changes: 52 additions & 0 deletions packages/utils/src/lib/promises.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { describe } from 'vitest';
import { asyncSequential, groupByStatus } from './promises.js';

describe('groupByStatus', () => {
it('should group results by status', () => {
const results = [
{ status: 'fulfilled', value: 'first' },
{ status: 'rejected', reason: 'second' },
{ status: 'fulfilled', value: 'third' },
] as PromiseSettledResult<string>[];
const grouped = groupByStatus(results);
expect(grouped).toEqual({
fulfilled: [
{ status: 'fulfilled', value: 'first' },
{ status: 'fulfilled', value: 'third' },
],
rejected: [{ status: 'rejected', reason: 'second' }],
});
});
});

describe('asyncSequential', () => {
it('should map async function to array', async () => {
await expect(
asyncSequential(['a', 'b', 'c'], x => Promise.resolve(x.toUpperCase())),
).resolves.toEqual(['A', 'B', 'C']);
});

it('should wait for previous item to resolve before processing next item', async () => {
let counter = 0;
const work = vi.fn().mockImplementation(
() =>
new Promise(resolve => {
counter++;
setTimeout(() => {
resolve(counter);
}, 10);
}),
);

const items = Array.from({ length: 4 });

await expect(asyncSequential(items, work)).resolves.toEqual([1, 2, 3, 4]);

counter = 0;
const sequentialResult = await asyncSequential(items, work); // [1, 2, 3, 4]
counter = 0;
const parallelResult = await Promise.all(items.map(work)); // [4, 4, 4, 4]

expect(sequentialResult).not.toEqual(parallelResult);
});
});