Skip to content

Commit bad8723

Browse files
committed
feat(ci): move in code from github-action for issues, git diff and commands
1 parent a9c51d9 commit bad8723

File tree

12 files changed

+944
-0
lines changed

12 files changed

+944
-0
lines changed

packages/ci/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
"version": "0.0.1",
44
"description": "CI automation logic for Code PushUp (provider-agnostic)",
55
"dependencies": {
6+
"@code-pushup/models": "0.51.0",
67
"@code-pushup/utils": "0.51.0",
78
"glob": "^10.4.5",
9+
"simple-git": "^3.20.0",
810
"yaml": "^2.5.1"
911
}
1012
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { executeProcess } from '@code-pushup/utils';
2+
import type { CommandContext } from '../context';
3+
import {
4+
type PersistedCliFiles,
5+
persistCliOptions,
6+
persistedCliFiles,
7+
} from '../persist';
8+
9+
export async function collect({
10+
bin,
11+
config,
12+
directory,
13+
silent,
14+
project,
15+
}: CommandContext): Promise<PersistedCliFiles> {
16+
const { stdout } = await executeProcess({
17+
command: bin,
18+
args: [
19+
...(config ? [`--config=${config}`] : []),
20+
...persistCliOptions({ directory, project }),
21+
],
22+
cwd: directory,
23+
});
24+
if (!silent) {
25+
console.info(stdout);
26+
}
27+
28+
return persistedCliFiles({ directory, project });
29+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { executeProcess } from '@code-pushup/utils';
2+
import type { CommandContext } from '../context';
3+
import {
4+
type PersistedCliFiles,
5+
persistCliOptions,
6+
persistedCliFiles,
7+
} from '../persist';
8+
9+
type CompareOptions = {
10+
before: string;
11+
after: string;
12+
label?: string;
13+
};
14+
15+
export async function compare(
16+
{ before, after, label }: CompareOptions,
17+
{ bin, config, directory, silent, project }: CommandContext,
18+
): Promise<PersistedCliFiles> {
19+
const { stdout } = await executeProcess({
20+
command: bin,
21+
args: [
22+
'compare',
23+
`--before=${before}`,
24+
`--after=${after}`,
25+
...(label ? [`--label=${label}`] : []),
26+
...(config ? [`--config=${config}`] : []),
27+
...persistCliOptions({ directory, project }),
28+
],
29+
cwd: directory,
30+
});
31+
if (!silent) {
32+
console.info(stdout);
33+
}
34+
35+
return persistedCliFiles({ directory, isDiff: true, project });
36+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { executeProcess } from '@code-pushup/utils';
2+
import type { CommandContext } from '../context';
3+
import {
4+
type PersistedCliFiles,
5+
persistCliOptions,
6+
persistedCliFiles,
7+
} from '../persist';
8+
9+
export async function mergeDiffs(
10+
files: string[],
11+
{ bin, config, directory, silent }: CommandContext,
12+
): Promise<PersistedCliFiles<'md'>> {
13+
const { stdout } = await executeProcess({
14+
command: bin,
15+
args: [
16+
'merge-diffs',
17+
...files.map(file => `--files=${file}`),
18+
...(config ? [`--config=${config}`] : []),
19+
...persistCliOptions({ directory }),
20+
],
21+
cwd: directory,
22+
});
23+
if (!silent) {
24+
console.info(stdout);
25+
}
26+
27+
return persistedCliFiles({ directory, isDiff: true, formats: ['md'] });
28+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { executeProcess } from '@code-pushup/utils';
2+
import type { CommandContext } from '../context';
3+
4+
export async function printConfig({
5+
bin,
6+
config,
7+
directory,
8+
silent,
9+
}: CommandContext): Promise<void> {
10+
const { stdout } = await executeProcess({
11+
command: bin,
12+
args: [...(config ? [`--config=${config}`] : []), 'print-config'],
13+
cwd: directory,
14+
});
15+
if (!silent) {
16+
console.info(stdout);
17+
}
18+
}

packages/ci/src/lib/cli/context.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type { CIConfig } from '../config';
2+
import type { ProjectConfig } from '../monorepo';
3+
4+
export type CommandContext = Pick<
5+
CIConfig,
6+
'bin' | 'config' | 'directory' | 'silent'
7+
> & {
8+
project?: string;
9+
};
10+
11+
export function createCommandContext(
12+
config: CIConfig,
13+
project: ProjectConfig | null | undefined,
14+
): CommandContext {
15+
return {
16+
project: project?.name,
17+
bin: project?.bin ?? config.bin,
18+
directory: project?.directory ?? config.directory,
19+
config: config.config,
20+
silent: config.silent,
21+
};
22+
}

packages/ci/src/lib/cli/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export { collect } from './commands/collect';
2+
export { compare } from './commands/compare';
3+
export { mergeDiffs } from './commands/merge-diffs';
4+
export { printConfig } from './commands/print-config';
5+
export { createCommandContext, type CommandContext } from './context';
6+
export {
7+
findPersistedFiles,
8+
projectToFilename,
9+
type PersistedCliFiles,
10+
} from './persist';

packages/ci/src/lib/cli/persist.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import path from 'node:path';
2+
import {
3+
DEFAULT_PERSIST_FILENAME,
4+
DEFAULT_PERSIST_FORMAT,
5+
DEFAULT_PERSIST_OUTPUT_DIR,
6+
type Format,
7+
} from '@code-pushup/models';
8+
9+
export type PersistedCliFiles<T extends Format = Format> =
10+
PersistedCliFilesFormats<T> & {
11+
artifactData: {
12+
rootDir: string;
13+
files: string[];
14+
};
15+
};
16+
17+
export type PersistedCliFilesFormats<T extends Format = Format> = {
18+
[F in T as `${F}FilePath`]: string;
19+
};
20+
21+
export function persistCliOptions({
22+
directory,
23+
project,
24+
}: {
25+
directory: string;
26+
project?: string;
27+
}): string[] {
28+
return [
29+
`--persist.outputDir=${path.join(directory, DEFAULT_PERSIST_OUTPUT_DIR)}`,
30+
`--persist.filename=${createFilename(project)}`,
31+
...DEFAULT_PERSIST_FORMAT.map(format => `--persist.format=${format}`),
32+
];
33+
}
34+
35+
export function persistedCliFiles<TFormat extends Format = Format>({
36+
directory,
37+
isDiff,
38+
project,
39+
formats,
40+
}: {
41+
directory: string;
42+
isDiff?: boolean;
43+
project?: string;
44+
formats?: TFormat[];
45+
}): PersistedCliFiles<TFormat> {
46+
const rootDir = path.join(directory, DEFAULT_PERSIST_OUTPUT_DIR);
47+
const filename = isDiff
48+
? `${createFilename(project)}-diff`
49+
: createFilename(project);
50+
const filePaths = (formats ?? DEFAULT_PERSIST_FORMAT).reduce(
51+
(acc, format) => ({
52+
...acc,
53+
[`${format}FilePath`]: path.join(rootDir, `${filename}.${format}`),
54+
}),
55+
// eslint-disable-next-line @typescript-eslint/prefer-reduce-type-parameter, @typescript-eslint/consistent-type-assertions
56+
{} as PersistedCliFilesFormats,
57+
);
58+
const files = Object.values(filePaths);
59+
60+
return {
61+
...filePaths,
62+
artifactData: {
63+
rootDir,
64+
files,
65+
},
66+
};
67+
}
68+
69+
export function findPersistedFiles(
70+
rootDir: string,
71+
files: string[],
72+
project?: string,
73+
): PersistedCliFiles {
74+
const filename = createFilename(project);
75+
const filePaths = DEFAULT_PERSIST_FORMAT.reduce((acc, format) => {
76+
const matchedFile = files.find(file => file === `${filename}.${format}`);
77+
if (!matchedFile) {
78+
return acc;
79+
}
80+
return { ...acc, [`${format}FilePath`]: path.join(rootDir, matchedFile) };
81+
// eslint-disable-next-line @typescript-eslint/prefer-reduce-type-parameter, @typescript-eslint/consistent-type-assertions
82+
}, {} as PersistedCliFilesFormats);
83+
return {
84+
...filePaths,
85+
artifactData: {
86+
rootDir,
87+
files: Object.values(filePaths),
88+
},
89+
};
90+
}
91+
92+
export function projectToFilename(project: string): string {
93+
return project.replace(/[/\\\s]+/g, '-').replace(/@/g, '');
94+
}
95+
96+
function createFilename(project: string | undefined): string {
97+
if (!project) {
98+
return DEFAULT_PERSIST_FILENAME;
99+
}
100+
const prefix = projectToFilename(project);
101+
return `${prefix}-${DEFAULT_PERSIST_FILENAME}`;
102+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { mkdir, rename, rm, writeFile } from 'node:fs/promises';
2+
import { join } from 'node:path';
3+
import { type SimpleGit, simpleGit } from 'simple-git';
4+
import { type ChangedFiles, listChangedFiles } from './git';
5+
6+
describe('git diff', () => {
7+
const workDir = join('tmp', 'git-utils-test');
8+
9+
let git: SimpleGit;
10+
11+
beforeAll(async () => {
12+
await rm(workDir, { recursive: true, force: true });
13+
await mkdir(workDir, { recursive: true });
14+
15+
git = simpleGit(workDir);
16+
await git.init();
17+
await git.addConfig('user.name', 'John Doe');
18+
await git.addConfig('user.email', 'john.doe@example.com');
19+
await git.branch(['-M', 'main']);
20+
21+
await writeFile(join(workDir, 'LICENSE'), 'MIT License\n\n...');
22+
await writeFile(
23+
join(workDir, 'index.js'),
24+
'export const sum = values => values.reduce((acc, val) => acc + val, 0)\n',
25+
);
26+
await writeFile(
27+
join(workDir, 'package.json'),
28+
JSON.stringify(
29+
{ name: 'sum', type: 'module', main: 'index.js' },
30+
null,
31+
2,
32+
),
33+
);
34+
await git.add('.');
35+
await git.commit('Initial commit');
36+
37+
await git.checkoutLocalBranch('testing');
38+
await mkdir(join(workDir, 'src'));
39+
await mkdir(join(workDir, 'test'));
40+
await rename(join(workDir, 'index.js'), join(workDir, 'src/index.js'));
41+
await writeFile(
42+
join(workDir, 'test/index.test.js'),
43+
[
44+
"import assert from 'node:assert'",
45+
"import test from 'node:test'",
46+
"import { sum } from '../src/index.js'",
47+
'',
48+
"test('should sum all numbers', () => {",
49+
' assert.strictEqual(sum([1, 2, 3, 4]), 10)',
50+
'})',
51+
]
52+
.map(line => `${line}\n`)
53+
.join(''),
54+
);
55+
await writeFile(
56+
join(workDir, 'package.json'),
57+
JSON.stringify(
58+
{
59+
name: 'sum',
60+
type: 'module',
61+
main: 'src/index.js',
62+
scripts: { test: 'node --test' },
63+
},
64+
null,
65+
2,
66+
),
67+
);
68+
await git.add('.');
69+
await git.commit('Unit test');
70+
});
71+
72+
afterAll(async () => {
73+
await rm(workDir, { recursive: true, force: true });
74+
});
75+
76+
it('should list added, modified and renamed files', async () => {
77+
await expect(
78+
listChangedFiles({ base: 'main', head: 'testing' }, git),
79+
).resolves.toEqual({
80+
'package.json': {
81+
lineChanges: [
82+
{ prev: { line: 4, count: 1 }, curr: { line: 4, count: 4 } },
83+
],
84+
},
85+
'src/index.js': {
86+
originalFile: 'index.js',
87+
lineChanges: [],
88+
},
89+
'test/index.test.js': {
90+
lineChanges: [
91+
{ prev: { line: 0, count: 0 }, curr: { line: 1, count: 7 } },
92+
],
93+
},
94+
} satisfies ChangedFiles);
95+
});
96+
});

0 commit comments

Comments
 (0)