diff --git a/.github/workflows/runtime-tests.yml b/.github/workflows/runtime-tests.yml index 3542627..024e36b 100644 --- a/.github/workflows/runtime-tests.yml +++ b/.github/workflows/runtime-tests.yml @@ -40,9 +40,9 @@ jobs: - run: npm install ./dist/apps/cli/herodevs-cli-0.0.0-local.tgz - - run: npx @herodevs/cli --version + - run: npx hd --version - - run: npx @herodevs/cli report committers + - run: npx hd report committers via-sea: runs-on: ubuntu-latest diff --git a/README.md b/README.md index d83dc9b..d57e252 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,56 @@ -# HeroDevs CLI -- `@herodevs/cli` +# HeroDevs CLI - `@herodevs/cli` -## Installation +## Node Setup & Installation -``` -npm install -g @herodevs/cli -``` - -## Usage +Node versions 14+ -after global installation +No installation: ``` -@herodevs/cli ____ +npx @herodevs/cli ... ``` -or +Global installation: ``` -hd ____ +npm install -g @herodevs/cli +hd ... +# or +hdcli ... ``` -without installation: +## Non-Node or Node < 14 Setup & Installation -``` -npx @herodevs/cli ____ +Navigate [here](https://github.com/herodevs/cli/releases) to download the version for your operating system from the most recent release. + +## Available Commands + +```bash +# Initializes your project to use NES libraries +hdcli nes init ``` -## Commands +```bash +# Shows a list of committers in git repository +hdcli report committers +``` -Get a list of committers within a git repository +```bash +# Shows diagnostic information about your project +hdcli report diagnostics +``` +```bash +# Initializes the project for the lines-of-code tracker +hdcli tracker init ``` -hd report committers + +```bash +# Runs a lines-of-code tracker to gather project +hdcli tracker run ``` + +## Tutorials + +- [Configure your project to consume a Never-Ending Support package](docs/nes-init.md) +- [Get an audit of the users who have committed to a project](docs/git-audit.md) diff --git a/apps/cli/package.json b/apps/cli/package.json index 8952082..629b31b 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,7 +1,10 @@ { "name": "@herodevs/cli", "version": "0.0.0", - "bin": "./src/main.js", + "bin": { + "hd": "./src/main.js", + "hdcli": "./src/main.js" + }, "dependencies": { "@apollo/client": "^3.10.1", "@inquirer/prompts": "^5.0.2", diff --git a/apps/cli/src/lib/cli.ts b/apps/cli/src/lib/cli.ts index 735ed2b..fd9187f 100644 --- a/apps/cli/src/lib/cli.ts +++ b/apps/cli/src/lib/cli.ts @@ -12,9 +12,10 @@ export function cli(): void { const packageVersion = pkg.version; yargs - .scriptName(packageName) + .scriptName('hdcli') .usage('Usage: $0 [options]') .middleware(() => ensureVersionIsUpToDate(packageName, packageVersion)) .command(commands) + .showHelpOnFail(false) .parse(hideBin(process.argv)); } diff --git a/apps/cli/src/lib/ensure-version.ts b/apps/cli/src/lib/ensure-version.ts index d203c43..6e12c4c 100644 --- a/apps/cli/src/lib/ensure-version.ts +++ b/apps/cli/src/lib/ensure-version.ts @@ -42,17 +42,13 @@ async function getLatestVersion(pkgName: string) { export async function isVersionUpToDate( packageName: string, - packageVersion: string, - quietIfSuccessful = false + packageVersion: string ): Promise { if (packageVersion.startsWith('0.0.0')) { return true; } const latestVersion = await getLatestVersion(packageName); if (latestVersion === packageVersion) { - if (!quietIfSuccessful) { - console.log(`${packageName}@${latestVersion} is up to date`); - } return true; } console.log( diff --git a/apps/cli/src/lib/get-commands.spec.ts b/apps/cli/src/lib/get-commands.spec.ts index ce16ca6..47027c4 100644 --- a/apps/cli/src/lib/get-commands.spec.ts +++ b/apps/cli/src/lib/get-commands.spec.ts @@ -4,7 +4,8 @@ describe('getCommands', () => { it('should return the commands', () => { const result = getCommands(); const commandNames = result.map((command) => command.command); - expect(commandNames.length).toEqual(3); + expect(commandNames.length).toEqual(4); + expect(commandNames).toContain('$0'); // default command expect(commandNames).toContain('nes '); expect(commandNames).toContain('report '); expect(commandNames).toContain('tracker '); diff --git a/apps/cli/src/lib/get-commands.ts b/apps/cli/src/lib/get-commands.ts index 17fb503..c553e4f 100644 --- a/apps/cli/src/lib/get-commands.ts +++ b/apps/cli/src/lib/get-commands.ts @@ -1,10 +1,11 @@ +import * as yargs from 'yargs'; +import { CommandModule } from 'yargs'; import { reportCommittersCommand } from '@herodevs/report-committers'; import { reportDiagnosticsCommand } from '@herodevs/report-diagnostics'; import { trackerInitCommand } from '@herodevs/tracker-init'; import { trackerRunCommand } from '@herodevs/tracker-run'; import { createGroupCommand } from './create-group-command'; import { nesInitCommand } from '@herodevs/nes-init'; -import { CommandModule } from 'yargs'; // eslint-disable-next-line @typescript-eslint/no-explicit-any export function getCommands(): CommandModule[] { @@ -38,5 +39,14 @@ export function getCommands(): CommandModule[] { 'Invalid tracker command' ); - return [nesCommand, reportCommand, trackerCommand]; + return [defaultCommand, nesCommand, reportCommand, trackerCommand]; } + +const defaultCommand: CommandModule = { + command: '$0', + describe: false, + handler: (): void => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + yargs.showHelp('log'); + }, +}; diff --git a/docs/git-audit-screenshot.png b/docs/git-audit-screenshot.png new file mode 100644 index 0000000..7c56078 Binary files /dev/null and b/docs/git-audit-screenshot.png differ diff --git a/docs/git-audit.md b/docs/git-audit.md new file mode 100644 index 0000000..95b9c50 --- /dev/null +++ b/docs/git-audit.md @@ -0,0 +1,9 @@ +# Git Audit + +Get a list of committers in a project. + +![screenshot of running a git audit](git-audit-screenshot.png) + +## Installation + +Follow the instructions [here](installation-and-running.md) to install the HeroDevs CLI. diff --git a/docs/installation-and-running.md b/docs/installation-and-running.md new file mode 100644 index 0000000..4a63249 --- /dev/null +++ b/docs/installation-and-running.md @@ -0,0 +1,40 @@ +# Installation and running the HeroDevs CLI + +## Installation + +Do you have NodeJS installed with version 14 or higher? + +- Yes + - No need to install. +- No + - Get the HeroDevs CLI installer [here](https://github.com/herodevs/cli/releases). + - Download the version for your operating system. + - Unzip the download into your project's root directory. + - The command that you run is based upon your operating system. + - linux: `hdcli` + - mac: `hdcli` + - windows: `hdcli.exe` + +## Run + +Run the appropriate command based upon your environment. Noting that `...` represents the command and arguments you wish to run + +### NodeJS v14+ + +```bash +npx @herodevs/cli@latest ... +``` + +### Without NodeJS v14+: + +linux/mac: + +```bash +hdcli ... +``` + +windows: + +```bash +hdcli.exe ... +``` diff --git a/docs/nes-init-1.png b/docs/nes-init-1.png new file mode 100644 index 0000000..08ddf5c Binary files /dev/null and b/docs/nes-init-1.png differ diff --git a/docs/nes-init-2.png b/docs/nes-init-2.png new file mode 100644 index 0000000..f1a021f Binary files /dev/null and b/docs/nes-init-2.png differ diff --git a/docs/nes-init-3.png b/docs/nes-init-3.png new file mode 100644 index 0000000..9c98b36 Binary files /dev/null and b/docs/nes-init-3.png differ diff --git a/docs/nes-init-4.png b/docs/nes-init-4.png new file mode 100644 index 0000000..2563357 Binary files /dev/null and b/docs/nes-init-4.png differ diff --git a/docs/nes-init-5.png b/docs/nes-init-5.png new file mode 100644 index 0000000..e774a8d Binary files /dev/null and b/docs/nes-init-5.png differ diff --git a/docs/nes-init.md b/docs/nes-init.md new file mode 100644 index 0000000..4b91797 --- /dev/null +++ b/docs/nes-init.md @@ -0,0 +1,35 @@ +# NES Init + +Configure a project to use a [Never-Ending Support](https://www.herodevs.com/support#NES-Products) product. + +## Installation + +Follow the instructions [here](installation-and-running.md) to install the HeroDevs CLI. + +## Running the command + +``` +hdcli nes init +``` + +## Steps/procedure + +Confirm all changes have been committed to source control/git. + +![screenshot of confirmation](nes-init-1.png) + +Enter your access token. + +![screenshot of entering access token](nes-init-2.png) + +Select your product. + +![screenshot of entering access token](nes-init-3.png) + +Select your version. + +![screenshot of entering access token](nes-init-4.png) + +CONGRATULATIONS! Your product is now configured to use a NES product. + +![screenshot of entering access token](nes-init-5.png) diff --git a/libs/nes/init/src/lib/get-product-choices.spec.ts b/libs/nes/init/src/lib/get-product-choices.spec.ts index 210eef7..51544a3 100644 --- a/libs/nes/init/src/lib/get-product-choices.spec.ts +++ b/libs/nes/init/src/lib/get-product-choices.spec.ts @@ -18,8 +18,12 @@ describe('getProductChoices', () => { const mockTrains: any[] = [ { name: 'b', + products: [{ name: 'b' }], + }, + { + name: 'a', + products: [{ name: 'a' }], }, - { name: 'a' }, ]; getReleaseTrainsMock.mockResolvedValue(mockTrains); diff --git a/libs/nes/init/src/lib/get-product-choices.ts b/libs/nes/init/src/lib/get-product-choices.ts index 2b8d708..df17185 100644 --- a/libs/nes/init/src/lib/get-product-choices.ts +++ b/libs/nes/init/src/lib/get-product-choices.ts @@ -6,13 +6,25 @@ import { getReleaseTrains } from './get-release-trains'; export async function getProductChoices( accessToken: string, types: ProjectType[] -): Promise[]> { +): Promise[]> { const releaseTrains = await getReleaseTrains(accessToken, types); - return releaseTrains - .map((rt) => ({ - name: rt.name, - value: rt, + const products = releaseTrains.reduce((acc, rt) => { + rt.products.forEach((product) => { + if (acc[product.name]) { + acc[product.name].push(rt); + } else { + acc[product.name] = [rt]; + } + }); + + return acc; + }, {} as { [key: string]: ReleaseTrain[] }); + + return Object.entries(products) + .map(([key, value]) => ({ + name: key, + value, })) .sort(sortByName); } diff --git a/libs/nes/init/src/lib/init.spec.ts b/libs/nes/init/src/lib/init.spec.ts index f268273..259e39e 100644 --- a/libs/nes/init/src/lib/init.spec.ts +++ b/libs/nes/init/src/lib/init.spec.ts @@ -5,6 +5,7 @@ import { getPackageChoices } from './get-package-choices'; import { checkbox, confirm, password, select } from '@inquirer/prompts'; import { configureProject } from './configure-project'; import { Choice, ReleaseTrain } from './models'; +import { sortByName } from '@herodevs/utility'; jest.mock('./verify-project-type'); jest.mock('@inquirer/prompts'); @@ -32,7 +33,7 @@ describe('nesInitCommand', () => { let getProductChoicesMock: jest.Mock; let getPackageChoicesMock: jest.Mock; let mockReleaseTrains: ReleaseTrain[] = []; - let mockReleaseTrainChoices: Choice[] = []; + let mockReleaseTrainChoices: Choice[] = []; let configureProjectMock: jest.Mock; beforeEach(() => { @@ -74,8 +75,52 @@ describe('nesInitCommand', () => { }, ], }, + { + id: 3, + key: 'release-train-3', + name: 'release train 3', + products: [ + { + id: 333, + key: 'vue_essentials', + name: 'Vue 3 Essentials', + }, + ], + entries: [ + { + packageVersion: { + id: 444, + name: '4.5.6', + fqns: '@neverendingsupport/vue2@4.5.6', + origination: { + name: 'vue', + type: 'npm', + version: '4.5.6', + }, + }, + }, + ], + }, ]; - mockReleaseTrainChoices = mockReleaseTrains.map((rt) => ({ name: rt.name, value: rt })); + const products = mockReleaseTrains.reduce((acc, rt) => { + rt.products.forEach((product) => { + if (acc[product.name]) { + acc[product.name].push(rt); + } else { + acc[product.name] = [rt]; + } + }); + + return acc; + }, {} as { [key: string]: ReleaseTrain[] }); + + mockReleaseTrainChoices = Object.entries(products) + .map(([key, value]) => ({ + name: key, + value, + })) + .sort(sortByName); + const packageChoices = mockReleaseTrains[0].entries.map((e) => ({ name: e.packageVersion, value: e, @@ -87,7 +132,7 @@ describe('nesInitCommand', () => { }); confirmMock.mockReturnValue(Promise.resolve(true)); passwordMock.mockReturnValue(Promise.resolve('abc123')); - selectMock.mockReturnValue(Promise.resolve(mockReleaseTrains[0])); + selectMock.mockReturnValue(Promise.resolve(mockReleaseTrainChoices[0].value)); checkboxMock.mockReturnValue(Promise.resolve(packageChoices.map((c) => c.value))); getProductChoicesMock.mockReturnValue(Promise.resolve(mockReleaseTrainChoices)); getPackageChoicesMock.mockReturnValue(packageChoices); @@ -150,6 +195,7 @@ describe('nesInitCommand', () => { message: 'select a product', choices: mockReleaseTrainChoices, pageSize: mockReleaseTrainChoices.length, + loop: false, }); }); diff --git a/libs/nes/init/src/lib/init.ts b/libs/nes/init/src/lib/init.ts index e1a776f..7989750 100644 --- a/libs/nes/init/src/lib/init.ts +++ b/libs/nes/init/src/lib/init.ts @@ -45,25 +45,46 @@ async function run(args: ArgumentsCamelCase): Promise { const productList = await getProductChoices(accessToken, projectType.types); spinner.stop(); - const releaseTrain = await select({ - message: 'select a product', - choices: productList, - pageSize: productList.length, // no scrolling - }); + const releaseTrains = + productList.length === 1 + ? productList[0].value + : await select({ + message: 'select a product', + choices: productList, + pageSize: productList.length, // no scrolling + loop: false, + }); + + const releaseTrain = + releaseTrains.length === 1 + ? releaseTrains[0] + : await select({ + message: 'select a release', + choices: releaseTrains.map((rt) => ({ + name: rt.name, + value: rt, + })), + pageSize: releaseTrains.length, // no scrolling + loop: false, + }); const packageList = getPackageChoices(releaseTrain).map((p) => ({ ...p, checked: true, })); - const packages = await checkbox({ - message: `select the package(s)`, - choices: packageList, - loop: false, - pageSize: packageList.length, // no scrolling - }); + const packages = + packageList.length === 1 + ? [packageList[0].value] + : await checkbox({ + message: `select the package(s)`, + choices: packageList, + pageSize: packageList.length, // no scrolling + loop: false, + }); console.log('configuring your project...'); + configureProject(accessToken, projectType.types, packages); console.log('Your project is now configured to access your NES product'); diff --git a/libs/report/committers/src/lib/committers.spec.ts b/libs/report/committers/src/lib/committers.spec.ts index 5682162..d5297b7 100644 --- a/libs/report/committers/src/lib/committers.spec.ts +++ b/libs/report/committers/src/lib/committers.spec.ts @@ -29,7 +29,7 @@ describe('reportCommittersCommand', () => { }); it('should set alias(es)', () => { - expect(reportCommittersCommand.aliases).toEqual([]); + expect(reportCommittersCommand.aliases).toEqual(['git']); }); }); diff --git a/libs/report/committers/src/lib/committers.ts b/libs/report/committers/src/lib/committers.ts index 1824906..4823252 100644 --- a/libs/report/committers/src/lib/committers.ts +++ b/libs/report/committers/src/lib/committers.ts @@ -18,7 +18,7 @@ interface Options { export const reportCommittersCommand: CommandModule = { command: 'committers', describe: 'show git committers', - aliases: [], + aliases: ['git'], builder: { startDate: { alias: 's', diff --git a/package.json b/package.json index 1be79aa..78c83e4 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "license": "MIT", "scripts": { "hd": "npm run build && node ./dist/apps/cli/src/main.js", + "hd:no-build": "node ./dist/apps/cli/src/main.js", "build": "nx run-many --target=build --all", "test": "nx run-many --target=test --all", "x": "nx affected:test --watch",