Skip to content

Commit

Permalink
feat(ci): move in main run functions from github-action and adapt
Browse files Browse the repository at this point in the history
  • Loading branch information
matejchalk committed Oct 15, 2024
1 parent b61d674 commit 697948e
Show file tree
Hide file tree
Showing 4 changed files with 273 additions and 9 deletions.
4 changes: 2 additions & 2 deletions packages/ci/src/lib/comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ export async function commentOnPR(

if (prevComment) {
const updatedComment = await api.updateComment(prevComment.id, body);
logger.debug(`Updated body of comment ${updatedComment.url}`);
logger.info(`Updated body of comment ${updatedComment.url}`);
return updatedComment.id;
}

const createdComment = await api.createComment(body);
logger.debug(`Created new comment ${createdComment.url}`);
logger.info(`Created new comment ${createdComment.url}`);
return createdComment.id;
}

Expand Down
1 change: 1 addition & 0 deletions packages/ci/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const DEFAULT_SETTINGS: Settings = {
config: null,
directory: process.cwd(),
silent: false,
debug: false,
detectNewIssues: true,
logger: console,
};
22 changes: 19 additions & 3 deletions packages/ci/src/lib/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export type Options = {
config?: string | null;
directory?: string;
silent?: boolean;
debug?: boolean;
detectNewIssues?: boolean;
logger?: Logger;
};
Expand All @@ -22,7 +23,7 @@ export type GitRefs = {

export type ProviderAPIClient = {
maxCommentChars: number;
downloadReportArtifact: () => Promise<string>;
downloadReportArtifact?: () => Promise<string>;
listComments: () => Promise<Comment[]>;
updateComment: (id: number, body: string) => Promise<Comment>;
createComment: (body: string) => Promise<Comment>;
Expand All @@ -46,10 +47,25 @@ export type Logger = {
debug: (message: string) => void;
};

export type RunResult = {
export type RunResult = StandaloneRunResult | MonorepoRunResult;

export type StandaloneRunResult = Omit<ProjectRunResult, 'name'> & {
mode: 'standalone';
commentId?: number;
};

export type MonorepoRunResult = {
mode: 'monorepo';
projects: ProjectRunResult[];
commentId?: number;
diffArtifact?: ArtifactData;
};

export type ProjectRunResult = {
name: string;
artifacts: {
report: ArtifactData;
diff: ArtifactData;
diff?: ArtifactData;
};
newIssues?: SourceFileIssue[];
};
Expand Down
255 changes: 251 additions & 4 deletions packages/ci/src/lib/run.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,264 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import { type SimpleGit, simpleGit } from 'simple-git';
import type { Report, ReportsDiff } from '@code-pushup/models';
import { stringifyError } from '@code-pushup/utils';
import {
type CommandContext,
type PersistedCliFiles,
collect,
compare,
createCommandContext,
mergeDiffs,
printConfig,
} from './cli';
import { commentOnPR } from './comment';
import { DEFAULT_SETTINGS } from './constants';
import { listChangedFiles } from './git';
import { type SourceFileIssue, filterRelevantIssues } from './issues';
import type {
GitBranch,
GitRefs,
Logger,
Options,
ProjectRunResult,
ProviderAPIClient,
RunResult as RunOutput,
RunResult,
Settings,
} from './models';
import { type ProjectConfig, listMonorepoProjects } from './monorepo';

export function runInCI(
// eslint-disable-next-line max-lines-per-function
export async function runInCI(
refs: GitRefs,
api: ProviderAPIClient,
options?: Options,
): Promise<RunOutput> {
git: SimpleGit = simpleGit(),
): Promise<RunResult> {
const settings: Settings = { ...DEFAULT_SETTINGS, ...options };
// TODO: implement main flow
const logger = settings.logger;

if (settings.monorepo) {
logger.info('Running Code PushUp in monorepo mode');
const projects = await listMonorepoProjects(settings);
const projectResults = await projects.reduce<Promise<ProjectRunResult[]>>(
async (acc, project) => [
...(await acc),
await runOnProject({ project, settings, refs, api, git }),
],
Promise.resolve([]),
);
const diffJsonPaths = projectResults
.map(({ artifacts: { diff } }) =>
diff?.files.find(file => file.endsWith('.json')),
)
.filter(file => file != null);
if (diffJsonPaths.length > 0) {
const { mdFilePath, artifactData: diffArtifact } = await mergeDiffs(
diffJsonPaths,
createCommandContext(settings, projects[0]),
);
logger.debug(`Merged ${diffJsonPaths.length} diffs into ${mdFilePath}`);
const commentId = await commentOnPR(mdFilePath, api, logger);
return {
mode: 'monorepo',
projects: projectResults,
commentId,
diffArtifact,
};
}
}

logger.info('Running Code PushUp in standalone project mode');
const { artifacts, newIssues } = await runOnProject({
project: null,
settings,
api,
refs,
git,
});
const commentMdPath = artifacts.diff?.files.find(file =>
file.endsWith('.md'),
);
if (commentMdPath) {
const commentId = await commentOnPR(commentMdPath, api, logger);
return {
mode: 'standalone',
artifacts,
commentId,
newIssues,
};
}
return { mode: 'standalone', artifacts, newIssues };
}

// eslint-disable-next-line max-lines-per-function
async function runOnProject(args: {
project: ProjectConfig | null;
refs: GitRefs;
api: ProviderAPIClient;
settings: Settings;
git: SimpleGit;
}): Promise<ProjectRunResult> {
const {
project,
refs: { head, base },
settings,
git,
} = args;
const logger = settings.logger;

const ctx = createCommandContext(settings, project);

if (project) {
logger.info(`Running Code PushUp on monorepo project ${project.name}`);
}

const { jsonFilePath: currReportPath, artifactData: reportArtifact } =
await collect(ctx);
const currReport = await fs.readFile(currReportPath, 'utf8');
logger.debug(`Collected current report at ${currReportPath}`);

const noDiffOutput = {
name: project?.name ?? '-',
artifacts: {
report: reportArtifact,
},
} satisfies ProjectRunResult;

if (base == null) {
return noDiffOutput;
}

logger.info(
`PR detected, preparing to compare base branch ${base.ref} to head ${head.ref}`,
);

const prevReport = await collectPreviousReport({ ...args, base, ctx });
if (!prevReport) {
return noDiffOutput;
}

const reportsDir = path.join(settings.directory, '.code-pushup');
const currPath = path.join(reportsDir, 'curr-report.json');
const prevPath = path.join(reportsDir, 'prev-report.json');
await fs.writeFile(currPath, currReport);
await fs.writeFile(prevPath, prevReport);
logger.debug(`Saved reports to ${currPath} and ${prevPath}`);

const comparisonFiles = await compare(
{ before: prevPath, after: currPath, label: project?.name },
ctx,
);
logger.info('Compared reports and generated diff files');
logger.debug(
`Generated diff files at ${comparisonFiles.jsonFilePath} and ${comparisonFiles.mdFilePath}`,
);

const diffOutput = {
...noDiffOutput,
artifacts: {
...noDiffOutput.artifacts,
diff: comparisonFiles.artifactData,
},
} satisfies ProjectRunResult;

if (!settings.detectNewIssues) {
return diffOutput;
}

const newIssues = await findNewIssues({
base,
currReport,
prevReport,
comparisonFiles,
logger,
git,
});

return { ...diffOutput, newIssues };
}

async function collectPreviousReport(args: {
base: GitBranch;
api: ProviderAPIClient;
settings: Settings;
ctx: CommandContext;
git: SimpleGit;
}): Promise<string | null> {
const { base, api, settings, ctx, git } = args;
const logger = settings.logger;

const cachedBaseReport = await api.downloadReportArtifact?.();
if (api.downloadReportArtifact != null) {
logger.info(
`Previous report artifact ${cachedBaseReport ? 'found' : 'not found'}`,
);
if (cachedBaseReport) {
logger.debug(
`Previous report artifact downloaded to ${cachedBaseReport}`,
);
}
}

if (cachedBaseReport) {
return fs.readFile(cachedBaseReport, 'utf8');
} else {
await git.fetch('origin', base.ref, ['--depth=1']);
await git.checkout(['-f', base.ref]);
logger.info(`Switched to base branch ${base.ref}`);

try {
await printConfig({ ...ctx, silent: !settings.debug });
logger.debug(
`Executing print-config verified code-pushup installed in base branch ${base.ref}`,
);
} catch (error) {
logger.debug(`Error from print-config - ${stringifyError(error)}`);
logger.info(
`Executing print-config failed, assuming code-pushup not installed in base branch ${base.ref} and skipping comparison`,
);
return null;
}

const { jsonFilePath: prevReportPath } = await collect(ctx);
const prevReport = await fs.readFile(prevReportPath, 'utf8');
logger.debug(`Collected previous report at ${prevReportPath}`);

await git.checkout(['-f', '-']);
logger.info('Switched back to PR branch');

return prevReport;
}
}

async function findNewIssues(args: {
base: GitBranch;
currReport: string;
prevReport: string;
comparisonFiles: PersistedCliFiles;
logger: Logger;
git: SimpleGit;
}): Promise<SourceFileIssue[]> {
const { base, currReport, prevReport, comparisonFiles, logger, git } = args;

await git.fetch('origin', base.ref, ['--depth=1']);
const reportsDiff = await fs.readFile(comparisonFiles.jsonFilePath, 'utf8');
const changedFiles = await listChangedFiles(
{ base: 'FETCH_HEAD', head: 'HEAD' },
git,
);
const issues = filterRelevantIssues({
currReport: JSON.parse(currReport) as Report,
prevReport: JSON.parse(prevReport) as Report,
reportsDiff: JSON.parse(reportsDiff) as ReportsDiff,
changedFiles,
});
logger.debug(
`Found ${issues.length} relevant issues for ${
Object.keys(changedFiles).length
} changed files and created GitHub annotations`,
);

return issues;
}

0 comments on commit 697948e

Please sign in to comment.