Skip to content

Commit

Permalink
feat(cli): initial collect command (#45)
Browse files Browse the repository at this point in the history
Co-authored-by: Michael <michael.hladky@push-based.io>
Co-authored-by: Michael Hladky <10064416+BioPhoton@users.noreply.github.com>
  • Loading branch information
3 people committed Sep 8, 2023
1 parent 37ea0a5 commit ba048be
Show file tree
Hide file tree
Showing 28 changed files with 531 additions and 332 deletions.
10 changes: 10 additions & 0 deletions package-lock.json

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

3 changes: 2 additions & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"chalk": "^5.3.0",
"yargs": "^17.7.2",
"zod": "^3.22.1",
"@quality-metrics/models": "^0.0.1"
"@quality-metrics/models": "^0.0.1",
"@quality-metrics/utils": "^0.0.1"
}
}
6 changes: 3 additions & 3 deletions packages/cli/src/lib/cli.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { join } from 'path';
import { describe, expect, it } from 'vitest';
import { yargsCli } from './cli';
import { join } from 'path';
import { yargsGlobalOptionsDefinition } from './options';
import { middlewares } from './middlewares';
import { CommandBase } from './implementation/base-command-config';
import { getDirname } from './implementation/utils';
import { middlewares } from './middlewares';
import { yargsGlobalOptionsDefinition } from './options';

const __dirname = getDirname(import.meta.url);
const withDirName = (path: string) => join(__dirname, path);
Expand Down
28 changes: 22 additions & 6 deletions packages/cli/src/lib/cli.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { CoreConfig } from '@quality-metrics/models';
import chalk from 'chalk';
import yargs, {
Argv,
CommandModule,
MiddlewareFunction,
Options,
ParserConfigurationOptions,
} from 'yargs';
import chalk from 'chalk';
import { CoreConfig } from '@quality-metrics/models';
import { logErrorBeforeThrow } from './implementation/utils';

/**
* returns configurable yargs CLI for code-pushup
Expand Down Expand Up @@ -40,6 +41,8 @@ export function yargsCli(

// setup yargs
cli
.help()
.alias('h', 'help')
.parserConfiguration({
'strip-dashed': true,
} satisfies Partial<ParserConfigurationOptions>)
Expand All @@ -57,12 +60,25 @@ export function yargsCli(
}

// add middlewares
middlewares.forEach(({ middlewareFunction, applyBeforeValidation }) =>
cli.middleware(middlewareFunction, applyBeforeValidation),
);
middlewares.forEach(({ middlewareFunction, applyBeforeValidation }) => {
cli.middleware(
logErrorBeforeThrow(middlewareFunction),
applyBeforeValidation,
);
});

// add commands
commands.forEach(commandObj => cli.command(commandObj));
commands.forEach(commandObj => {
cli.command({
...commandObj,
...(commandObj.handler && {
handler: logErrorBeforeThrow(commandObj.handler),
}),
...(typeof commandObj.builder === 'function' && {
builder: logErrorBeforeThrow(commandObj.builder),
}),
});
});

// return CLI object
return cli as unknown as Argv<CoreConfig>;
Expand Down
41 changes: 41 additions & 0 deletions packages/cli/src/lib/collect/_command-object-config.mock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const { runnerOutputSchema } = require('@quality-metrics/models');
module.exports = {
persist: { outputPath: 'command-object-config-out.json' },
plugins: [
{
audits: [
{
slug: 'command-object-audit-slug',
title: 'audit title',
description: 'audit description',
label: 'mock audit label',
docsUrl: 'http://www.my-docs.dev',
},
],
runner: {
command: 'bash',
args: [
'-c',
`echo '${JSON.stringify(
runnerOutputSchema.parse({
audits: [
{
slug: 'command-object-audit-slug',
value: 0,
score: 0,
},
],
}),
)}' > command-object-config-out.json`,
],
outputPath: 'command-object-config-out.json',
},
groups: [],
meta: {
slug: 'command-object-plugin',
name: 'command-object plugin',
},
},
],
categories: [],
};
90 changes: 90 additions & 0 deletions packages/cli/src/lib/collect/command-object.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { CoreConfig, PluginConfig, Report } from '@quality-metrics/models';
import { CollectOptions } from '@quality-metrics/utils';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { yargsCli } from '../cli';
import { getDirname } from '../implementation/utils';
import { middlewares } from '../middlewares';
import { yargsGlobalOptionsDefinition } from '../options';
import { yargsCollectCommandObject } from './command-object';

const outputPath = 'collect-command-object.json';
const dummyConfig: CoreConfig = {
persist: { outputPath },
plugins: [mockPlugin()],
categories: [],
};

describe('collect-command-object', () => {
it('should parse arguments correctly', async () => {
const args = ['collect', '--verbose', '--configPath', ''];
const cli = yargsCli([], { options: yargsGlobalOptionsDefinition() })
.config(dummyConfig)
.command(yargsCollectCommandObject());
const parsedArgv = (await cli.parseAsync(
args,
)) as unknown as CollectOptions;
const { persist } = parsedArgv;
const { outputPath: outPath } = persist;
expect(outPath).toBe(outputPath);
return Promise.resolve(void 0);
});

it('should execute middleware correctly', async () => {
const args = [
'collect',
'--configPath',
join(
getDirname(import.meta.url),
'..',
'implementation',
'mock',
'config-middleware-config.mock.mjs',
),
];
await yargsCli([], { middlewares })
.config(dummyConfig)
.command(yargsCollectCommandObject())
.parseAsync(args);
const report = JSON.parse(readFileSync(outputPath).toString()) as Report;
expect(report.plugins[0]?.meta.slug).toBe('collect-command-object');
expect(report.plugins[0]?.audits[0]?.slug).toBe(
'command-object-audit-slug',
);
});
});

function mockPlugin(): PluginConfig {
return {
audits: [
{
slug: 'command-object-audit-slug',
title: 'audit title',
description: 'audit description',
label: 'mock audit label',
docsUrl: 'http://www.my-docs.dev',
},
],
runner: {
command: 'bash',
args: [
'-c',
`echo '${JSON.stringify({
audits: [
{
slug: 'command-object-audit-slug',
value: 0,
score: 0,
},
],
})}' > ${outputPath}`,
],
outputPath,
},
groups: [],
meta: {
slug: 'collect-command-object',
name: 'collect command object',
},
} satisfies PluginConfig;
}
18 changes: 18 additions & 0 deletions packages/cli/src/lib/collect/command-object.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { collect, CollectOptions } from '@quality-metrics/utils';
import { writeFile } from 'fs/promises';
import { CommandModule } from 'yargs';

export function yargsCollectCommandObject() {
const handler = async (args: CollectOptions): Promise<void> => {
const collectOutput = await collect(args);

const { persist } = args;
await writeFile(persist.outputPath, JSON.stringify(collectOutput, null, 2));
};

return {
command: 'collect',
describe: 'Run Plugins and collect results',
handler: handler as unknown as CommandModule['handler'],
} satisfies CommandModule;
}
3 changes: 2 additions & 1 deletion packages/cli/src/lib/commands.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CommandModule } from 'yargs';
import { yargsCollectCommandObject } from './collect/command-object';

export const commands: CommandModule[] = [];
export const commands: CommandModule[] = [yargsCollectCommandObject()];
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { join } from 'path';
import { configMiddleware, ConfigParseError } from './config-middleware';
import { expect } from 'vitest';
import { configMiddleware, ConfigParseError } from './config-middleware';
import { getDirname } from './utils';

const __dirname = getDirname(import.meta.url);
Expand Down
9 changes: 7 additions & 2 deletions packages/cli/src/lib/implementation/config-middleware.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { existsSync } from 'node:fs';
import { bundleRequire } from 'bundle-require';
import { stat } from 'fs/promises';

import { GlobalCliArgs, globalCliArgsSchema } from '@quality-metrics/models';
import { CommandBase, commandBaseSchema } from './base-command-config';
Expand All @@ -15,7 +15,12 @@ export async function configMiddleware<T = unknown>(
): Promise<CommandBase> {
const globalCfg: GlobalCliArgs = globalCliArgsSchema.parse(processArgs);
const { configPath } = globalCfg;
if (!existsSync(configPath)) {
try {
const stats = await stat(configPath);
if (!stats.isFile) {
throw new ConfigParseError(configPath);
}
} catch (err) {
throw new ConfigParseError(configPath);
}

Expand Down
18 changes: 18 additions & 0 deletions packages/cli/src/lib/implementation/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,21 @@ import { fileURLToPath } from 'url';

export const getDirname = (import_meta_url: string) =>
dirname(fileURLToPath(import_meta_url));

// log error and flush stdout so that Yargs doesn't supress it
// related issue: https://github.com/yargs/yargs/issues/2118
export function logErrorBeforeThrow<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
T extends (...args: any[]) => any,
>(fn: T): T {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (async (...args: any[]) => {
try {
return await fn(...args);
} catch (err) {
console.error(err);
await new Promise(resolve => process.stdout.write('', resolve));
throw err;
}
}) as T;
}
2 changes: 1 addition & 1 deletion packages/cli/src/lib/middlewares.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { configMiddleware } from './implementation/config-middleware';
import { MiddlewareFunction } from 'yargs';
import { configMiddleware } from './implementation/config-middleware';

export const middlewares: {
middlewareFunction: MiddlewareFunction;
Expand Down
27 changes: 17 additions & 10 deletions packages/models/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
export { CategoryConfig, categoryConfigSchema } from './lib/category-config';
export {
unrefinedCoreConfigSchema,
refineCoreConfig,
coreConfigSchema,
CoreConfig,
coreConfigSchema,
refineCoreConfig,
unrefinedCoreConfigSchema,
} from './lib/core-config';
export { uploadConfigSchema, UploadConfig } from './lib/upload-config';
export { GlobalCliArgs, globalCliArgsSchema } from './lib/global-cli-options';
export { PersistConfig, persistConfigSchema } from './lib/persist-config';
export {
pluginConfigSchema,
AuditGroup,
AuditMetadata,
Issue,
PluginConfig,
RunnerOutput,
auditGroupSchema,
auditMetadataSchema,
issueSchema,
pluginConfigSchema,
runnerOutputSchema,
} from './lib/plugin-config';
export {
runnerOutputAuditRefsPresentInPluginConfigs,
reportSchema,
PluginOutput,
PluginReport,
Report,
runnerOutputAuditRefsPresentInPluginConfigs,
} from './lib/report';
export { persistConfigSchema, PersistConfig } from './lib/persist-config';
export { categoryConfigSchema, CategoryConfig } from './lib/category-config';
export { globalCliArgsSchema, GlobalCliArgs } from './lib/global-cli-options';
export { UploadConfig, uploadConfigSchema } from './lib/upload-config';
Loading

0 comments on commit ba048be

Please sign in to comment.