Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
d25523e
refactor(cli): adopt history CLI arguments
BioPhoton Apr 12, 2024
c16f061
refactor(cli): adopt history CLI arguments
BioPhoton Apr 13, 2024
5a09d6f
wip
BioPhoton Apr 13, 2024
83dc094
wip
BioPhoton Apr 13, 2024
6c2e845
wip
BioPhoton Apr 13, 2024
fc6fba4
wip
BioPhoton Apr 13, 2024
a29c7a7
wip
BioPhoton Apr 13, 2024
314de2e
wip
BioPhoton Apr 13, 2024
92fdddf
wip
BioPhoton Apr 13, 2024
b46b07f
wip
BioPhoton Apr 13, 2024
9a77783
wip
BioPhoton Apr 13, 2024
6cb1afa
wip
BioPhoton Apr 13, 2024
4378b37
wip
BioPhoton Apr 13, 2024
66a6d86
wip
BioPhoton Apr 13, 2024
06933bb
wip
BioPhoton Apr 13, 2024
800b30f
wip
BioPhoton Apr 13, 2024
4194d4c
wip
BioPhoton Apr 13, 2024
2297c8f
wip
BioPhoton Apr 13, 2024
43cf82f
wip
BioPhoton Apr 13, 2024
a0769c3
wip
BioPhoton Apr 13, 2024
9df7e31
wip
BioPhoton Apr 13, 2024
ea64964
wip
BioPhoton Apr 13, 2024
4dd53be
Merge branch 'main' into add-semver-tag-to-history-command
BioPhoton Apr 13, 2024
bbd2b9d
fix lint
BioPhoton Apr 14, 2024
54d9c84
fix tests
BioPhoton Apr 14, 2024
6719fcd
cleanup
BioPhoton Apr 14, 2024
b35ba48
fix tests
BioPhoton Apr 14, 2024
a6319ab
fix lint
BioPhoton Apr 14, 2024
43b0578
Update packages/cli/src/lib/history/history.options.ts
BioPhoton Apr 16, 2024
e5a38f0
Update packages/cli/src/lib/history/utils.unit.test.ts
BioPhoton Apr 16, 2024
c75cc81
Update testing/test-utils/src/lib/utils/git.ts
BioPhoton Apr 16, 2024
71fb7e9
Update packages/utils/src/lib/semver.ts
BioPhoton Apr 16, 2024
0abc6f6
refactor(cli): improve docs and tests for history command
BioPhoton Apr 16, 2024
da832e3
refactor(cli): use package 'semver' instead of 'compare-versions'
BioPhoton Apr 16, 2024
896fddc
refactor(utils): improve semver logic
BioPhoton Apr 16, 2024
134f48d
Merge branch 'main' into add-semver-tag-to-history-command
BioPhoton Apr 16, 2024
11aa822
Merge branch 'main' into add-semver-tag-to-history-command
BioPhoton Apr 27, 2024
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
7 changes: 3 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"esbuild": "^0.19.12",
"multi-progress-bars": "^5.0.3",
"parse-lcov": "^1.0.4",
"semver": "^7.6.0",
"simple-git": "^3.20.0",
"vscode-material-icons": "^0.1.0",
"yargs": "^17.7.2",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/docs/custom-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,7 @@ We will extend the file-size example from above to calculate the score based on

Let's extend the options object with a `budget` property and use it in the runner config:

**file-size plugin form section [RunnerFunction](#RunnerFunction)**
**file-size plugin from section [RunnerFunction](#RunnerFunction)**

```typescript
// file-size.plugin.ts
Expand Down
91 changes: 54 additions & 37 deletions packages/cli/src/lib/history/history-command.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,63 @@
import chalk from 'chalk';
import { ArgumentsCamelCase, CommandModule } from 'yargs';
import { HistoryOptions, getHashes, history } from '@code-pushup/core';
import { getCurrentBranchOrTag, safeCheckout, ui } from '@code-pushup/utils';
import { CommandModule } from 'yargs';
import { HistoryOptions, history } from '@code-pushup/core';
import {
LogResult,
getCurrentBranchOrTag,
getHashes,
getSemverTags,
safeCheckout,
ui,
} from '@code-pushup/utils';
import { CLI_NAME } from '../constants';
import { yargsOnlyPluginsOptionsDefinition } from '../implementation/only-plugins.options';
import { HistoryCliOptions } from './history.model';
import { yargsHistoryOptionsDefinition } from './history.options';
import { normalizeHashOptions } from './utils';

const command = 'history';
async function handler(args: unknown) {
ui().logger.info(chalk.bold(CLI_NAME));
ui().logger.info(chalk.gray(`Run ${command}`));

const currentBranch = await getCurrentBranchOrTag();
const { targetBranch: rawTargetBranch, ...opt } = args as HistoryCliOptions &
HistoryOptions;
const {
targetBranch,
from,
to,
maxCount,
onlySemverTags,
...historyOptions
} = await normalizeHashOptions({
...opt,
targetBranch: rawTargetBranch ?? currentBranch,
});

const filterOptions = { targetBranch, from, to, maxCount };
const results: LogResult[] = onlySemverTags
? await getSemverTags(filterOptions)
: await getHashes(filterOptions);

try {
// run history logic
const reports = await history(
{
targetBranch,
...historyOptions,
},
results.map(({ hash }) => hash),
);

ui().logger.log(`Reports: ${reports.length}`);
} finally {
// go back to initial branch
await safeCheckout(currentBranch);
}
}

export function yargsHistoryCommandObject() {
const command = 'history';
return {
command,
describe: 'Collect reports for commit history',
Expand All @@ -23,38 +72,6 @@ export function yargsHistoryCommandObject() {
);
return yargs;
},
handler: async <T>(args: ArgumentsCamelCase<T>) => {
ui().logger.info(chalk.bold(CLI_NAME));
ui().logger.info(chalk.gray(`Run ${command}`));

const currentBranch = await getCurrentBranchOrTag();
const {
targetBranch = currentBranch,
forceCleanStatus,
maxCount,
from,
to,
...restOptions
} = args as unknown as HistoryCliOptions & HistoryOptions;

// determine history to walk
const commits: string[] = await getHashes({ maxCount, from, to });
try {
// run history logic
const reports = await history(
{
...restOptions,
targetBranch,
forceCleanStatus,
},
commits,
);

ui().logger.log(`Reports: ${reports.length}`);
} finally {
// go back to initial branch
await safeCheckout(currentBranch);
}
},
handler,
} satisfies CommandModule;
}
32 changes: 11 additions & 21 deletions packages/cli/src/lib/history/history-command.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,20 @@ vi.mock('simple-git', async () => {
return {
...actual,
simpleGit: () => ({
branch: () => Promise.resolve('dummy'),
raw: () => Promise.resolve('main'),
tag: () => Promise.resolve(`5\n 4\n 3\n 2\n 1`),
show: ([_, __, tag]: string) =>
Promise.resolve(`release v${tag}\n ${tag}`),
checkout: () => Promise.resolve(),
log: ({ maxCount }: { maxCount: number } = { maxCount: 1 }) =>
Promise.resolve({
all: [
{ hash: 'commit-6' },
{ hash: 'commit-5' },
{ hash: 'commit-4' },
{ hash: 'commit-4--release-v2' },
{ hash: 'commit-3' },
{ hash: 'commit-2' },
{ hash: 'commit-2--release-v1' },
{ hash: 'commit-1' },
].slice(-maxCount),
}),
Expand All @@ -53,7 +59,7 @@ vi.mock('simple-git', async () => {
});

describe('history-command', () => {
it('should return the last 5 commits', async () => {
it('should pass targetBranch and forceCleanStatus to core history logic', async () => {
await yargsCli(['history', '--config=/test/code-pushup.config.ts'], {
...DEFAULT_CLI_CONFIGURATION,
commands: [yargsHistoryCommandObject()],
Expand All @@ -62,27 +68,11 @@ describe('history-command', () => {
expect(history).toHaveBeenCalledWith(
expect.objectContaining({
targetBranch: 'main',
forceCleanStatus: false,
}),
['commit-1', 'commit-2', 'commit-3', 'commit-4', 'commit-5'],
expect.any(Array),
);

expect(safeCheckout).toHaveBeenCalledTimes(1);
});

it('should have 2 commits to crawl in history if maxCount is set to 2', async () => {
await yargsCli(
['history', '--config=/test/code-pushup.config.ts', '--maxCount=2'],
{
...DEFAULT_CLI_CONFIGURATION,
commands: [yargsHistoryCommandObject()],
},
).parseAsync();

expect(history).toHaveBeenCalledWith(expect.any(Object), [
'commit-1',
'commit-2',
]);

expect(safeCheckout).toHaveBeenCalledTimes(1);
});
});
1 change: 1 addition & 0 deletions packages/cli/src/lib/history/history.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ import { HistoryOnlyOptions } from '@code-pushup/core';

export type HistoryCliOptions = {
targetBranch?: string;
onlySemverTags?: boolean;
} & Pick<LogOptions, 'maxCount' | 'from' | 'to'> &
HistoryOnlyOptions;
6 changes: 5 additions & 1 deletion packages/cli/src/lib/history/history.options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ export function yargsHistoryOptionsDefinition(): Record<
targetBranch: {
describe: 'Branch to crawl history',
type: 'string',
default: 'main',
},
onlySemverTags: {
describe: 'Skip commits not tagged with a semantic version',
type: 'boolean',
default: false,
},
forceCleanStatus: {
describe:
Expand Down
36 changes: 36 additions & 0 deletions packages/cli/src/lib/history/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { HistoryOptions } from '@code-pushup/core';
import { getHashFromTag, isSemver } from '@code-pushup/utils';
import { HistoryCliOptions } from './history.model';

export async function normalizeHashOptions(
processArgs: HistoryCliOptions & HistoryOptions,
): Promise<HistoryCliOptions & HistoryOptions> {
const {
onlySemverTags,
// overwritten
maxCount,
...opt
} = processArgs;

// eslint-disable-next-line functional/no-let, prefer-const
let { from, to, ...processOptions } = opt;
// if no semver filter is used resolve hash of tags, as hashes are used to collect history
if (!onlySemverTags) {
if (from && isSemver(from)) {
const { hash } = await getHashFromTag(from);
from = hash;
}
if (to && isSemver(to)) {
const { hash } = await getHashFromTag(to);
to = hash;
}
}

return {
...processOptions,
onlySemverTags,
maxCount: maxCount && maxCount > 0 ? maxCount : undefined,
from,
to,
};
}
120 changes: 120 additions & 0 deletions packages/cli/src/lib/history/utils.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { describe, expect, vi } from 'vitest';
import { type HistoryOptions } from '@code-pushup/core';
import { HistoryCliOptions } from './history.model';
import { normalizeHashOptions } from './utils';

vi.mock('simple-git', async () => {
const actual = await vi.importActual('simple-git');
const orderedTagsHistory = ['2.0.0', '1.0.0'];
return {
...actual,
simpleGit: () => ({
branch: () => Promise.resolve('dummy'),
raw: () => Promise.resolve('main'),
tag: () => Promise.resolve(orderedTagsHistory.join('\n')),
show: ([_, __, tag]: string) =>
orderedTagsHistory.includes(tag || '')
? Promise.resolve(`${tag}\ncommit--release-v${tag}`)
: Promise.reject('NOT FOUND TAG'),
checkout: () => Promise.resolve(),
log: ({ maxCount }: { maxCount: number } = { maxCount: 1 }) =>
Promise.resolve({
all: [
{ hash: 'commit-6' },
{ hash: 'commit-5' },
{ hash: `commit--release-v${orderedTagsHistory.at(0)}` },
{ hash: 'commit-3' },
{ hash: `commit--release-v${orderedTagsHistory.at(1)}` },
{ hash: 'commit-1' },
].slice(-maxCount),
}),
}),
};
});

describe('normalizeHashOptions', () => {
it('should forwards other options', async () => {
await expect(
normalizeHashOptions({
test: 42,
} as unknown as HistoryCliOptions & HistoryOptions),
).resolves.toEqual(
expect.objectContaining({
test: 42,
}),
);
});

it('should set "maxCount" to undefined if "0" is passed', async () => {
await expect(
normalizeHashOptions({ maxCount: 0 } as HistoryCliOptions &
HistoryOptions),
).resolves.toEqual(
expect.objectContaining({
maxCount: undefined,
}),
);
});

it('should forward hashes "from" and "to" as is if "onlySemverTags" is false', async () => {
await expect(
normalizeHashOptions({
from: 'commit-3',
to: 'commit-1',
} as HistoryCliOptions & HistoryOptions),
).resolves.toEqual(
expect.objectContaining({
from: 'commit-3',
to: 'commit-1',
}),
);
});

it('should transform tags "from" and "to" to commit hashes if "onlySemverTags" is false', async () => {
await expect(
normalizeHashOptions({
onlySemverTags: false,
from: '2.0.0',
to: '1.0.0',
} as HistoryCliOptions & HistoryOptions),
).resolves.toEqual(
expect.objectContaining({
onlySemverTags: false,
from: 'commit--release-v2.0.0',
to: 'commit--release-v1.0.0',
}),
);
});

it('should forward tags "from" and "to" if "onlySemverTags" is true', async () => {
await expect(
normalizeHashOptions({
onlySemverTags: true,
from: '2.0.0',
to: '1.0.0',
} as HistoryCliOptions & HistoryOptions),
).resolves.toEqual(
expect.objectContaining({
onlySemverTags: true,
from: '2.0.0',
to: '1.0.0',
}),
);
});

it('should forward hashes "from" and "to" if "onlySemverTags" is true', async () => {
await expect(
normalizeHashOptions({
onlySemverTags: true,
from: 'commit-3',
to: 'commit-1',
} as HistoryCliOptions & HistoryOptions),
).resolves.toEqual(
expect.objectContaining({
onlySemverTags: true,
from: 'commit-3',
to: 'commit-1',
}),
);
});
});
Loading