cli
cli copied to clipboard
feat(cli): add prompts to history commands
User story
As a user of the CLI on a local setup, I want to have as less setup and reading tie as possible.
When using the history command npx code-pusup history I could get prompted for the different options and avoid reading the docs completely.
The CLI prompt could list branches and filter options and automatically derive it from the code base.
Acceptance criteria
Use the library @inquirer/prompts to get beautifully looking prompt messages in the terminal or CI.
The following changes are required:
- [ ] add
interactiveflag to theGlobalCLIOptions- [ ] add docs to global options in main readme
- [ ] add prompts to history command
- [ ] if CLI argument
targetBranchis NOT used prompt user for branch name. use the@inquirer/prompts#inputhelper - [ ] prompt user if he want to list commit or tag. use the
selecteprompt helper - [ ] if CLI argument
maxCountis NOT used prompt user for number of commits the history should include - [ ] if CLI argument
fromis NOT used prompt user for number of commits the history should include. use the@inquirer/prompts#selecthelper- [ ] if
tagsis selected list only tagged commits matching thesemverpattern - [ ] if
commitis selected list all commits. - [ ] filter items by
maxCount
- [ ] if
- [ ] if CLI argument
tois NOT used prompt user for number of commits the history should include. use the@inquirer/prompts#selecthelper- [ ] if
tagsis selected list only tagged commits matching thesemverpattern - [ ] if
commitis selected list all commits. - [ ] filter items by
maxCount - [ ] filter items by excluded by
from
- [ ] if
- [ ] if CLI argument
- [ ] all prompts are integration tested (input into process) e.g. https://github.com/SBoudrias/Inquirer.js/blob/3374a2bebb355ea242a13e873418962323452734/packages/testing/src/index.mts#L45
Implementation details
import {confirm, input, select} from '@inquirer/prompts';
import simpleGit, {LogOptions} from 'simple-git';
import {getMergedSemverTagsFromBranch} from '@code-pushup/utils';
import {HistoryCliOnlyOptions} from "../history/history.model";
async function promptTargetBranch() {
const summary = await simpleGit().branch(['-r']);
return select({
message: 'Select a branch:',
choices: summary.all.map(branch => ({value: branch})),
default: 'origin/main',
});
}
function promptCommitTypeFilter() {
return confirm({
message: 'Do you want to by semver tagged commits?',
});
}
async function promptMaxCount() {
const prompResult = await input({
message:
'How many commits/tags should the history include? (Leave empty for all)',
validate: (v: string | number) => v === '' || !Number.isNaN(Number(v)),
transformer: (v: string) => (v === '' ? '-1' : v),
});
return Number(prompResult);
}
function promptFrom(tagsOrCommits: string[], {semverTag}: { semverTag: boolean, maxCount?: number }) {
return select({
message: `Select a ${semverTag ? 'tag' : 'commit'} from which the history should start crawling:`,
choices: tagsOrCommits.map(tagOrCommit => ({value: tagOrCommit})),
});
}
async function promptTo(tagsOrCommits: string[], {semverTag, maxCount, from}: {
semverTag: boolean,
maxCount: number,
from: string
}) {
const toIndex = tagsOrCommits.indexOf(from);
const filteredTagsOrCommits = tagsOrCommits.slice(
Math.min(maxCount, toIndex + 1),
);
if (filteredTagsOrCommits.length > maxCount) {
const toNeeded = await confirm({
message: `Do you want to specify until where the history should crawl?`,
default: false,
});
if (toNeeded) {
return (await select({
message: `Select a ${semverTag ? 'tag' : 'commit'} until which the history should crawl:`,
choices: filteredTagsOrCommits.map(tagOrCommit => ({
value: tagOrCommit,
})),
}));
}
}
return '';
}
async function filterByCommitType(targetBranch: string, {semverTag = true}: { semverTag: boolean }) {
return semverTag ?
await getMergedSemverTagsFromBranch(targetBranch) :
(await simpleGit().log()).all.map(({hash}) => hash)
}
export async function historyPrompt<
O extends Partial<LogOptions> & HistoryCliOnlyOptions,
>(options?: O) {
const {targetBranch, maxCount = -1, from, to, semverTag} = options ?? {};
// 1. branch name
const targetBranchInput = targetBranch ?? await promptTargetBranch();
// 2. list tags only or all commits
const semverTagInput = semverTag ?? await promptCommitTypeFilter();
// 3. number of history walks
const maxCountInput = maxCount < 0 ? await promptMaxCount() : maxCount;
const tagsOrCommits = await filterByCommitType(targetBranchInput, {semverTag: semverTagInput});
// 4. select start
const fromInput = from ?? await promptFrom(tagsOrCommits, {semverTag: semverTagInput, maxCount: maxCountInput});
// 5. select optional end
// eslint-disable-next-line functional/no-let
const toInput = to ?? await promptTo(tagsOrCommits, {
semverTag: semverTagInput,
from: fromInput,
maxCount: maxCountInput
});
// create partial history options
return {
...(targetBranchInput === '' ? {} : {branch: targetBranchInput}),
...(fromInput === '' ? {} : {from: fromInput}),
...(toInput === '' ? {} : {to: toInput}),
...(maxCountInput >= 0 ? {maxCount: maxCountInput} : {}),
};
}