Skip to content

Commit

Permalink
feat(ci): move in code from github-action for issues, git diff and co…
Browse files Browse the repository at this point in the history
…mmands
  • Loading branch information
matejchalk committed Oct 15, 2024
1 parent a9c51d9 commit bad8723
Show file tree
Hide file tree
Showing 12 changed files with 944 additions and 0 deletions.
2 changes: 2 additions & 0 deletions packages/ci/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
"version": "0.0.1",
"description": "CI automation logic for Code PushUp (provider-agnostic)",
"dependencies": {
"@code-pushup/models": "0.51.0",
"@code-pushup/utils": "0.51.0",
"glob": "^10.4.5",
"simple-git": "^3.20.0",
"yaml": "^2.5.1"
}
}
29 changes: 29 additions & 0 deletions packages/ci/src/lib/cli/commands/collect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { executeProcess } from '@code-pushup/utils';
import type { CommandContext } from '../context';
import {
type PersistedCliFiles,
persistCliOptions,
persistedCliFiles,
} from '../persist';

export async function collect({
bin,
config,
directory,
silent,
project,
}: CommandContext): Promise<PersistedCliFiles> {
const { stdout } = await executeProcess({
command: bin,
args: [
...(config ? [`--config=${config}`] : []),
...persistCliOptions({ directory, project }),
],
cwd: directory,
});
if (!silent) {
console.info(stdout);
}

return persistedCliFiles({ directory, project });
}
36 changes: 36 additions & 0 deletions packages/ci/src/lib/cli/commands/compare.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { executeProcess } from '@code-pushup/utils';
import type { CommandContext } from '../context';
import {
type PersistedCliFiles,
persistCliOptions,
persistedCliFiles,
} from '../persist';

type CompareOptions = {
before: string;
after: string;
label?: string;
};

export async function compare(
{ before, after, label }: CompareOptions,
{ bin, config, directory, silent, project }: CommandContext,
): Promise<PersistedCliFiles> {
const { stdout } = await executeProcess({
command: bin,
args: [
'compare',
`--before=${before}`,
`--after=${after}`,
...(label ? [`--label=${label}`] : []),
...(config ? [`--config=${config}`] : []),
...persistCliOptions({ directory, project }),
],
cwd: directory,
});
if (!silent) {
console.info(stdout);
}

return persistedCliFiles({ directory, isDiff: true, project });
}
28 changes: 28 additions & 0 deletions packages/ci/src/lib/cli/commands/merge-diffs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { executeProcess } from '@code-pushup/utils';
import type { CommandContext } from '../context';
import {
type PersistedCliFiles,
persistCliOptions,
persistedCliFiles,
} from '../persist';

export async function mergeDiffs(
files: string[],
{ bin, config, directory, silent }: CommandContext,
): Promise<PersistedCliFiles<'md'>> {
const { stdout } = await executeProcess({
command: bin,
args: [
'merge-diffs',
...files.map(file => `--files=${file}`),
...(config ? [`--config=${config}`] : []),
...persistCliOptions({ directory }),
],
cwd: directory,
});
if (!silent) {
console.info(stdout);
}

return persistedCliFiles({ directory, isDiff: true, formats: ['md'] });
}
18 changes: 18 additions & 0 deletions packages/ci/src/lib/cli/commands/print-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { executeProcess } from '@code-pushup/utils';
import type { CommandContext } from '../context';

export async function printConfig({
bin,
config,
directory,
silent,
}: CommandContext): Promise<void> {
const { stdout } = await executeProcess({
command: bin,
args: [...(config ? [`--config=${config}`] : []), 'print-config'],
cwd: directory,
});
if (!silent) {
console.info(stdout);
}
}
22 changes: 22 additions & 0 deletions packages/ci/src/lib/cli/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { CIConfig } from '../config';
import type { ProjectConfig } from '../monorepo';

export type CommandContext = Pick<
CIConfig,
'bin' | 'config' | 'directory' | 'silent'
> & {
project?: string;
};

export function createCommandContext(
config: CIConfig,
project: ProjectConfig | null | undefined,
): CommandContext {
return {
project: project?.name,
bin: project?.bin ?? config.bin,
directory: project?.directory ?? config.directory,
config: config.config,
silent: config.silent,
};
}
10 changes: 10 additions & 0 deletions packages/ci/src/lib/cli/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export { collect } from './commands/collect';
export { compare } from './commands/compare';
export { mergeDiffs } from './commands/merge-diffs';
export { printConfig } from './commands/print-config';
export { createCommandContext, type CommandContext } from './context';
export {
findPersistedFiles,
projectToFilename,
type PersistedCliFiles,
} from './persist';
102 changes: 102 additions & 0 deletions packages/ci/src/lib/cli/persist.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import path from 'node:path';
import {
DEFAULT_PERSIST_FILENAME,
DEFAULT_PERSIST_FORMAT,
DEFAULT_PERSIST_OUTPUT_DIR,
type Format,
} from '@code-pushup/models';

export type PersistedCliFiles<T extends Format = Format> =
PersistedCliFilesFormats<T> & {
artifactData: {
rootDir: string;
files: string[];
};
};

export type PersistedCliFilesFormats<T extends Format = Format> = {
[F in T as `${F}FilePath`]: string;
};

export function persistCliOptions({
directory,
project,
}: {
directory: string;
project?: string;
}): string[] {
return [
`--persist.outputDir=${path.join(directory, DEFAULT_PERSIST_OUTPUT_DIR)}`,
`--persist.filename=${createFilename(project)}`,
...DEFAULT_PERSIST_FORMAT.map(format => `--persist.format=${format}`),
];
}

export function persistedCliFiles<TFormat extends Format = Format>({
directory,
isDiff,
project,
formats,
}: {
directory: string;
isDiff?: boolean;
project?: string;
formats?: TFormat[];
}): PersistedCliFiles<TFormat> {
const rootDir = path.join(directory, DEFAULT_PERSIST_OUTPUT_DIR);
const filename = isDiff
? `${createFilename(project)}-diff`
: createFilename(project);
const filePaths = (formats ?? DEFAULT_PERSIST_FORMAT).reduce(
(acc, format) => ({
...acc,
[`${format}FilePath`]: path.join(rootDir, `${filename}.${format}`),
}),
// eslint-disable-next-line @typescript-eslint/prefer-reduce-type-parameter, @typescript-eslint/consistent-type-assertions
{} as PersistedCliFilesFormats,
);
const files = Object.values(filePaths);

return {
...filePaths,
artifactData: {
rootDir,
files,
},
};
}

export function findPersistedFiles(
rootDir: string,
files: string[],
project?: string,
): PersistedCliFiles {
const filename = createFilename(project);
const filePaths = DEFAULT_PERSIST_FORMAT.reduce((acc, format) => {
const matchedFile = files.find(file => file === `${filename}.${format}`);
if (!matchedFile) {
return acc;
}
return { ...acc, [`${format}FilePath`]: path.join(rootDir, matchedFile) };
// eslint-disable-next-line @typescript-eslint/prefer-reduce-type-parameter, @typescript-eslint/consistent-type-assertions
}, {} as PersistedCliFilesFormats);
return {
...filePaths,
artifactData: {
rootDir,
files: Object.values(filePaths),
},
};
}

export function projectToFilename(project: string): string {
return project.replace(/[/\\\s]+/g, '-').replace(/@/g, '');
}

function createFilename(project: string | undefined): string {
if (!project) {
return DEFAULT_PERSIST_FILENAME;
}
const prefix = projectToFilename(project);
return `${prefix}-${DEFAULT_PERSIST_FILENAME}`;
}
96 changes: 96 additions & 0 deletions packages/ci/src/lib/git.integration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { mkdir, rename, rm, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { type SimpleGit, simpleGit } from 'simple-git';
import { type ChangedFiles, listChangedFiles } from './git';

describe('git diff', () => {
const workDir = join('tmp', 'git-utils-test');

let git: SimpleGit;

beforeAll(async () => {
await rm(workDir, { recursive: true, force: true });
await mkdir(workDir, { recursive: true });

git = simpleGit(workDir);
await git.init();
await git.addConfig('user.name', 'John Doe');
await git.addConfig('user.email', 'john.doe@example.com');
await git.branch(['-M', 'main']);

await writeFile(join(workDir, 'LICENSE'), 'MIT License\n\n...');
await writeFile(
join(workDir, 'index.js'),
'export const sum = values => values.reduce((acc, val) => acc + val, 0)\n',
);
await writeFile(
join(workDir, 'package.json'),
JSON.stringify(
{ name: 'sum', type: 'module', main: 'index.js' },
null,
2,
),
);
await git.add('.');
await git.commit('Initial commit');

await git.checkoutLocalBranch('testing');
await mkdir(join(workDir, 'src'));
await mkdir(join(workDir, 'test'));
await rename(join(workDir, 'index.js'), join(workDir, 'src/index.js'));
await writeFile(
join(workDir, 'test/index.test.js'),
[
"import assert from 'node:assert'",
"import test from 'node:test'",
"import { sum } from '../src/index.js'",
'',
"test('should sum all numbers', () => {",
' assert.strictEqual(sum([1, 2, 3, 4]), 10)',
'})',
]
.map(line => `${line}\n`)
.join(''),
);
await writeFile(
join(workDir, 'package.json'),
JSON.stringify(
{
name: 'sum',
type: 'module',
main: 'src/index.js',
scripts: { test: 'node --test' },
},
null,
2,
),
);
await git.add('.');
await git.commit('Unit test');
});

afterAll(async () => {
await rm(workDir, { recursive: true, force: true });
});

it('should list added, modified and renamed files', async () => {
await expect(
listChangedFiles({ base: 'main', head: 'testing' }, git),
).resolves.toEqual({
'package.json': {
lineChanges: [
{ prev: { line: 4, count: 1 }, curr: { line: 4, count: 4 } },
],
},
'src/index.js': {
originalFile: 'index.js',
lineChanges: [],
},
'test/index.test.js': {
lineChanges: [
{ prev: { line: 0, count: 0 }, curr: { line: 1, count: 7 } },
],
},
} satisfies ChangedFiles);
});
});
Loading

0 comments on commit bad8723

Please sign in to comment.