diff --git a/src/bot/parse/parsePullRequestBotCommandLine.namedArgs.spec.ts b/src/bot/parse/parsePullRequestBotCommandLine.namedArgs.spec.ts index 7da5a91..7d0d19a 100644 --- a/src/bot/parse/parsePullRequestBotCommandLine.namedArgs.spec.ts +++ b/src/bot/parse/parsePullRequestBotCommandLine.namedArgs.spec.ts @@ -14,6 +14,7 @@ type DataProvider = { suitName: string; commandLine: string; expectedResponse: SkipEvent | ParsedCommand | Error; + repo?: string; }; const dataProvider: DataProvider[] = [ @@ -24,7 +25,7 @@ const dataProvider: DataProvider[] = [ "bench", { commandStart: ['"$PIPELINE_SCRIPTS_DIR/commands/bench/bench.sh"'], - gitlab: { job: { tags: ["bench-bot"], variables: {} } }, + gitlab: { job: { tags: ["weights-vm"], variables: {} } }, }, {}, '"$PIPELINE_SCRIPTS_DIR/commands/bench/bench.sh" --subcommand=runtime --runtime=polkadot --target_dir=polkadot --pallet=pallet_referenda', @@ -38,11 +39,12 @@ const dataProvider: DataProvider[] = [ "bench", { commandStart: ['"$PIPELINE_SCRIPTS_DIR/commands/bench/bench.sh"'], - gitlab: { job: { tags: ["bench-bot"], variables: {} } }, + gitlab: { job: { tags: ["weights-vm"], variables: {} } }, }, { PIPELINE_SCRIPTS_REF: "branch" }, '"$PIPELINE_SCRIPTS_DIR/commands/bench/bench.sh" --subcommand=xcm --runtime=bridge-hub-kusama --runtime_dir=bridge-hubs --target_dir=cumulus --pallet=pallet_name', ), + repo: "cumulus", }, { suitName: "unrelated to bot comment returns nothing (ignores)", @@ -50,8 +52,8 @@ const dataProvider: DataProvider[] = [ expectedResponse: new SkipEvent("Not a command"), }, { - suitName: "try-runtime-bot with default preset mentioned explicitly", - commandLine: "bot try-runtime -v RUST_LOG=remote-ext=debug,runtime=trace -v SECOND=val default --chain=kusama", + suitName: "try-runtime-bot testing default without mentioning preset name", + commandLine: "bot try-runtime -v RUST_LOG=remote-ext=debug,runtime=trace -v SECOND=val --chain=kusama", expectedResponse: new GenericCommand( "try-runtime", { @@ -63,21 +65,28 @@ const dataProvider: DataProvider[] = [ ), }, { - suitName: "try-runtime-bot testing default without mentioning preset name", - commandLine: "bot try-runtime -v RUST_LOG=remote-ext=debug,runtime=trace -v SECOND=val --chain=kusama", + suitName: "try-runtime-bot testing default without any args", + commandLine: "bot try-runtime", expectedResponse: new GenericCommand( "try-runtime", { commandStart: ['"$PIPELINE_SCRIPTS_DIR/commands/try-runtime/try-runtime.sh"'], gitlab: { job: { tags: ["linux-docker-vm-c2"], variables: {} } }, }, - { RUST_LOG: "remote-ext=debug,runtime=trace", SECOND: "val" }, - '"$PIPELINE_SCRIPTS_DIR/commands/try-runtime/try-runtime.sh" --chain=kusama --target_path=. --chain_node=polkadot', + {}, + '"$PIPELINE_SCRIPTS_DIR/commands/try-runtime/try-runtime.sh" --chain=polkadot --target_path=. --chain_node=polkadot', ), }, { - suitName: "try-runtime-bot testing default without any args", - commandLine: "bot try-runtime", + suitName: "try-runtime-bot testing wrong presets", + commandLine: "bot try-runtime unbelievable", + expectedResponse: new Error( + `Unknown subcommand of "try-runtime". Refer to [help docs](http://cmd-bot.docs.com/static/docs/latest.html) and/or [source code](https://github.com/paritytech/command-bot-scripts).`, + ), + }, + { + suitName: "try-runtime-bot testing trappist", + commandLine: "bot try-runtime trappist", expectedResponse: new GenericCommand( "try-runtime", { @@ -85,8 +94,25 @@ const dataProvider: DataProvider[] = [ gitlab: { job: { tags: ["linux-docker-vm-c2"], variables: {} } }, }, {}, - '"$PIPELINE_SCRIPTS_DIR/commands/try-runtime/try-runtime.sh" --chain=polkadot --target_path=. --chain_node=polkadot', + '"$PIPELINE_SCRIPTS_DIR/commands/try-runtime/try-runtime.sh" --chain=trappist --chain_node=trappist-node --target_path=. --live_uri=rococo-trappist', ), + repo: "trappist", + }, + { + suitName: "try-runtime-bot testing trappist with existing but unsupported preset [default]", + commandLine: "bot try-runtime", + expectedResponse: new Error( + `Missing arguments for command "try-runtime". Refer to [help docs](http://cmd-bot.docs.com/static/docs/latest.html) and/or [source code](https://github.com/paritytech/command-bot-scripts).`, + ), + repo: "trappist", + }, + { + suitName: "try-runtime-bot testing trappist with existing but unsupported preset [polkadot]", + commandLine: "bot try-runtime polkadot", + expectedResponse: new Error( + `Unknown subcommand of "try-runtime". Refer to [help docs](http://cmd-bot.docs.com/static/docs/latest.html) and/or [source code](https://github.com/paritytech/command-bot-scripts).`, + ), + repo: "trappist", }, { suitName: "fmt, no args should be allowed and return config", @@ -142,7 +168,7 @@ const dataProvider: DataProvider[] = [ }, { suitName: "command, with 'default' preset should add properly", - commandLine: "bot sample default --input=bla", + commandLine: "bot sample --input=bla", expectedResponse: new GenericCommand( "sample", { @@ -168,8 +194,8 @@ const dataProvider: DataProvider[] = [ }, /* - Help cases - */ + Help cases + */ { suitName: "help", commandLine: "bot help", @@ -184,14 +210,14 @@ const dataProvider: DataProvider[] = [ { suitName: "clear", commandLine: "bot clear", expectedResponse: new CleanCommand() }, /* - Cancel cases - */ + Cancel cases + */ { suitName: "cancel no-taskId", commandLine: "bot cancel", expectedResponse: new CancelCommand("") }, { suitName: "cancel with taskId", commandLine: "bot cancel 123123", expectedResponse: new CancelCommand("123123") }, /* - Ignore cases -*/ + Ignore cases + */ { suitName: "empty command line returns nothing (ignores)", commandLine: "", @@ -220,8 +246,8 @@ const dataProvider: DataProvider[] = [ }, /* - Expected Error cases - */ + Expected Error cases + */ { suitName: "bench-bot --pallet should validate the matching rule", commandLine: "bot bench polkadot-pallet --pallet=00034", @@ -240,6 +266,7 @@ const dataProvider: DataProvider[] = [ suitName: "bench-bot, no required args, should return error", commandLine: "bot bench cumulus-bridge-hubs", expectedResponse: new Error(`required option '--pallet ' not specified`), + repo: "cumulus", }, { suitName: "sample without required arg should return error", @@ -257,21 +284,21 @@ const dataProvider: DataProvider[] = [ suitName: "nonexistent command, should return proper error", commandLine: "bot nope 123123", expectedResponse: new Error( - 'Unknown command "nope"; Available ones are bench-all, bench-overhead, bench-vm, bench, fmt, merge, rebase, sample, try-runtime, update-ui. Refer to [help docs](http://cmd-bot.docs.com/static/docs/latest.html) and/or [source code](https://github.com/paritytech/command-bot-scripts).', + 'Unknown command "nope". Refer to [help docs](http://cmd-bot.docs.com/static/docs/latest.html) and/or [source code](https://github.com/paritytech/command-bot-scripts).', ), }, { suitName: "not provided command, returns proper error", commandLine: "bot $", expectedResponse: new Error( - 'Unknown command "$"; Available ones are bench-all, bench-overhead, bench-vm, bench, fmt, merge, rebase, sample, try-runtime, update-ui. Refer to [help docs](http://cmd-bot.docs.com/static/docs/latest.html) and/or [source code](https://github.com/paritytech/command-bot-scripts).', + 'Unknown command "$". Refer to [help docs](http://cmd-bot.docs.com/static/docs/latest.html) and/or [source code](https://github.com/paritytech/command-bot-scripts).', ), }, { suitName: "non existed config must return error with explanation", commandLine: "bot xz", expectedResponse: new Error( - `Unknown command "xz"; Available ones are bench-all, bench-overhead, bench-vm, bench, fmt, merge, rebase, sample, try-runtime, update-ui. Refer to [help docs](http://cmd-bot.docs.com/static/docs/latest.html) and/or [source code](https://github.com/paritytech/command-bot-scripts).`, + `Unknown command "xz". Refer to [help docs](http://cmd-bot.docs.com/static/docs/latest.html) and/or [source code](https://github.com/paritytech/command-bot-scripts).`, ), }, { @@ -291,9 +318,9 @@ const dataProvider: DataProvider[] = [ ]; describe("parsePullRequestBotCommandLine", () => { - for (const { suitName, commandLine, expectedResponse } of dataProvider) { - test(`test commandLine: ${commandLine} [${suitName}]`, async () => { - const res = await parsePullRequestBotCommandLine(commandLine, { logger }, "polkadot"); + for (const { suitName, commandLine, expectedResponse, repo } of dataProvider) { + test(`test commandLine: "${commandLine}" [${suitName}] ${repo ? "repo: [" + repo + "]" : ""}`, async () => { + const res = await parsePullRequestBotCommandLine(commandLine, { logger }, repo || "polkadot"); expect(res).toEqual(expectedResponse); }); } diff --git a/src/bot/parse/parsePullRequestBotCommandLine.spec.ts b/src/bot/parse/parsePullRequestBotCommandLine.spec.ts index c4cc9b9..db0f73f 100644 --- a/src/bot/parse/parsePullRequestBotCommandLine.spec.ts +++ b/src/bot/parse/parsePullRequestBotCommandLine.spec.ts @@ -34,7 +34,7 @@ const dataProvider: DataProvider[] = [ "bench", { commandStart: ['"$PIPELINE_SCRIPTS_DIR/commands/bench/bench.sh"'], - gitlab: { job: { tags: ["bench-bot"], variables: {} } }, + gitlab: { job: { tags: ["weights-vm"], variables: {} } }, }, { PIPELINE_SCRIPTS_REF: "hello-is-this-even-used" }, '"$PIPELINE_SCRIPTS_DIR/commands/bench/bench.sh" runtime kusama-dev pallet_referenda', @@ -130,21 +130,21 @@ const dataProvider: DataProvider[] = [ suitName: "nonexistent command, should return proper error", commandLine: "bot nope 123123", expectedResponse: new Error( - 'Unknown command "nope"; Available ones are bench-all, bench-overhead, bench-vm, bench, fmt, merge, rebase, sample, try-runtime, update-ui. Refer to [help docs](http://cmd-bot.docs.com/static/docs/latest.html) and/or [source code](https://github.com/paritytech/command-bot-scripts).', + 'Unknown command "nope". Refer to [help docs](http://cmd-bot.docs.com/static/docs/latest.html) and/or [source code](https://github.com/paritytech/command-bot-scripts).', ), }, { suitName: "not provided command, returns proper error", commandLine: "bot $", expectedResponse: new Error( - 'Unknown command "$"; Available ones are bench-all, bench-overhead, bench-vm, bench, fmt, merge, rebase, sample, try-runtime, update-ui. Refer to [help docs](http://cmd-bot.docs.com/static/docs/latest.html) and/or [source code](https://github.com/paritytech/command-bot-scripts).', + 'Unknown command "$". Refer to [help docs](http://cmd-bot.docs.com/static/docs/latest.html) and/or [source code](https://github.com/paritytech/command-bot-scripts).', ), }, { suitName: "non existed config must return error with explanation", commandLine: "bot xz", expectedResponse: new Error( - `Unknown command "xz"; Available ones are bench-all, bench-overhead, bench-vm, bench, fmt, merge, rebase, sample, try-runtime, update-ui. Refer to [help docs](http://cmd-bot.docs.com/static/docs/latest.html) and/or [source code](https://github.com/paritytech/command-bot-scripts).`, + `Unknown command "xz". Refer to [help docs](http://cmd-bot.docs.com/static/docs/latest.html) and/or [source code](https://github.com/paritytech/command-bot-scripts).`, ), }, ]; diff --git a/src/command-configs/__mocks__/fetchCommandsConfiguration.ts b/src/command-configs/__mocks__/fetchCommandsConfiguration.ts index 2321047..84a4c4e 100644 --- a/src/command-configs/__mocks__/fetchCommandsConfiguration.ts +++ b/src/command-configs/__mocks__/fetchCommandsConfiguration.ts @@ -33,17 +33,30 @@ export const cmd: CommandConfigs = { trappist: { description: "Pallet Benchmark for Trappist", repos: ["trappist"], - args: { runtime: { label: "Runtime", type_one_of: ["trappist", "stout"] } }, + args: { + runtime: { label: "Runtime", type_one_of: ["trappist", "stout"] }, + target_dir: { label: "Target Directory", type_string: "trappist" }, + }, }, }, }, }, + "bench-bm": { + $schema: "../../node_modules/command-bot/src/schema/schema.cmd.json", + command: { + description: "This is a testing for `bench` command running on legacy BM machines", + configuration: { + gitlab: { job: { tags: ["weights-vm"], variables: {} } }, + commandStart: ['"$PIPELINE_SCRIPTS_DIR/commands/bench-bm/bench-bm.sh"'], + }, + }, + }, "bench-overhead": { $schema: "../../node_modules/command-bot/src/schema/schema.cmd.json", command: { description: "Run benchmarks overhead and commit back results to PR", configuration: { - gitlab: { job: { tags: ["bench-bot"], variables: {} } }, + gitlab: { job: { tags: ["weights-vm"], variables: {} } }, commandStart: ['"$PIPELINE_SCRIPTS_DIR/commands/bench-overhead/bench-overhead.sh"'], }, presets: { @@ -76,22 +89,12 @@ export const cmd: CommandConfigs = { }, }, }, - "bench-vm": { - $schema: "../../node_modules/command-bot/src/schema/schema.cmd.json", - command: { - description: "This is a testing for `bench` command running on VM machine", - configuration: { - gitlab: { job: { tags: ["weights-vm"], variables: {} } }, - commandStart: ['"$PIPELINE_SCRIPTS_DIR/commands/bench-vm/bench-vm.sh"'], - }, - }, - }, bench: { $schema: "../../node_modules/command-bot/src/schema/schema.cmd.json", command: { description: "Runs `benchmark pallet` or `benchmark overhead` against your PR and commits back updated weights", configuration: { - gitlab: { job: { tags: ["bench-bot"], variables: {} } }, + gitlab: { job: { tags: ["weights-vm"], variables: {} } }, commandStart: ['"$PIPELINE_SCRIPTS_DIR/commands/bench/bench.sh"'], }, presets: { @@ -202,6 +205,7 @@ export const cmd: CommandConfigs = { subcommand: { label: "Subcommand", type_one_of: ["runtime", "xcm"] }, runtime: { label: "Runtime", type_one_of: ["trappist", "stout"] }, pallet: { label: "Pallet", type_rule: "^([a-z_]+)([:]{2}[a-z_]+)?$", example: "pallet_name" }, + target_dir: { label: "Target Directory", type_string: "trappist" }, }, }, }, @@ -226,6 +230,7 @@ export const cmd: CommandConfigs = { gitlab: { job: { tags: [""] } }, commandStart: ['"$PIPELINE_SCRIPTS_DIR/commands/merge/merge.sh"'], }, + presets: { default: { description: "merge PR", repos: ["substrate", "polkadot", "cumulus"] } }, }, }, rebase: { @@ -234,9 +239,10 @@ export const cmd: CommandConfigs = { description: "create a merge commit from the target branch into the PR. Read more: https://github.com/paritytech/parity-processbot/", configuration: { - gitlab: { job: { tags: [""] } }, + gitlab: { job: { tags: [""], variables: {} } }, commandStart: ['"$PIPELINE_SCRIPTS_DIR/commands/rebase/rebase.sh"'], }, + presets: { default: { description: "pull latest from the base", repos: ["substrate", "polkadot", "cumulus"] } }, }, }, sample: { @@ -291,6 +297,7 @@ export const cmd: CommandConfigs = { chain: { label: "Chain", type_one_of: ["trappist"] }, chain_node: { label: "Chain Node", type_string: "trappist-node" }, target_path: { label: "Target Path", type_string: "." }, + live_uri: { label: "Live Node URI ID", type_string: "rococo-trappist" }, }, }, }, diff --git a/src/command-configs/renderHelpPage.ts b/src/command-configs/renderHelpPage.ts index 31af60b..db8d043 100644 --- a/src/command-configs/renderHelpPage.ts +++ b/src/command-configs/renderHelpPage.ts @@ -2,6 +2,7 @@ import path from "path"; import * as pug from "pug"; import { CommandConfigs } from "src/command-configs/types"; +import { getSupportedRepoNames } from "src/command-configs/utils"; import { Config } from "src/config"; import { CmdJson } from "src/schema/schema.cmd"; @@ -19,16 +20,7 @@ export function renderHelpPage(params: { const preparedConfigs = prepareConfigs(commandConfigs); - // getting list of possible repos, to be able to filter out relevant commands - const reposSet = new Set(); - for (const cmdConfig of Object.values(preparedConfigs)) { - for (const preset of Object.values(cmdConfig.command.presets ?? {})) { - for (const repo of preset.repos ?? []) { - reposSet.add(repo); - } - } - } - const repos = [...reposSet]; + const repos = getSupportedRepoNames(preparedConfigs).repos; // TODO: depends on headBranch, if overridden: add `-v PIPELINE_SCRIPTS_REF=branch` to all command examples same for PATCH_repo=xxx // TODO: Simplify the PIPELINE_SCRIPTS_REF to something more rememberable */ diff --git a/src/command-configs/utils.spec.ts b/src/command-configs/utils.spec.ts new file mode 100644 index 0000000..9c93fe1 --- /dev/null +++ b/src/command-configs/utils.spec.ts @@ -0,0 +1,54 @@ +import { CommandConfigs } from "src/command-configs/types"; +import { getSupportedRepoNames } from "src/command-configs/utils"; +import { CmdJson } from "src/schema/schema.cmd"; + +type DataProvider = { + suitName: string; + configs: CommandConfigs; + result: { repos: string[]; includesGenericPresets: boolean }; + presetName?: string; +}; + +function stubPresets(presets?: CmdJson["command"]["presets"]): CommandConfigs { + return { + cmd: { + command: { configuration: { gitlab: { job: { tags: [""] } } }, presets: presets ? { ...presets } : undefined }, + }, + }; +} + +const dataProvider: DataProvider[] = [ + { + suitName: "test common preset without repos", + configs: stubPresets({ common: { description: "common", args: { arg1: { label: "1" } } } }), + result: { repos: [], includesGenericPresets: true }, + }, + { + suitName: "test repo specific preset with polkadot repo, no common presets", + configs: stubPresets({ + repoSpecific: { repos: ["polkadot"], description: "polkadot", args: { arg1: { label: "1" } } }, + }), + result: { repos: ["polkadot"], includesGenericPresets: false }, + }, + { + suitName: "test repo with both specific preset with polkadot repo and common one", + configs: stubPresets({ + common: { description: "common", args: { arg1: { label: "1" } } }, + repoSpecific: { repos: ["polkadot"], description: "1" }, + }), + result: { repos: ["polkadot"], includesGenericPresets: true }, + }, + { + suitName: "test command without presets", + configs: stubPresets(), + result: { repos: [], includesGenericPresets: true }, + }, +]; + +describe("getSupportedRepoNames", () => { + for (const { suitName, configs, result, presetName } of dataProvider) { + test(`test commandLine: [${suitName}]`, () => { + expect(getSupportedRepoNames(configs, presetName)).toEqual(result); + }); + } +}); diff --git a/src/command-configs/utils.ts b/src/command-configs/utils.ts new file mode 100644 index 0000000..bac9ab6 --- /dev/null +++ b/src/command-configs/utils.ts @@ -0,0 +1,30 @@ +import { CommandConfigs } from "src/command-configs/types"; + +// getting list of possible repos from command configs +export function getSupportedRepoNames( + commandConfigs: CommandConfigs, + commandName?: string, +): { repos: string[]; includesGenericPresets: boolean } { + let includesGenericPresets = false; + const reposSet = new Set(); + const commands = commandName ? [commandConfigs[commandName]] : Object.values(commandConfigs); + for (const cmdConfig of commands) { + const presets = Object.values(cmdConfig.command.presets ?? {}); + + if (presets.length === 0) { + includesGenericPresets = true; + } + + for (const preset of presets) { + const repos = preset.repos ?? []; + // if repos are not specified, then this command is supported by all repos + if (repos.length === 0) { + includesGenericPresets = true; + } + for (const repo of preset.repos ?? []) { + reposSet.add(repo); + } + } + } + return { repos: Array.from(reposSet), includesGenericPresets }; +} diff --git a/src/commander/commander.ts b/src/commander/commander.ts index 2ab8f1c..0cb456c 100644 --- a/src/commander/commander.ts +++ b/src/commander/commander.ts @@ -4,6 +4,7 @@ import { botPullRequestIgnoreCommands } from "src/bot"; import { CancelCommand, CleanCommand, GenericCommand, HelpCommand, ParsedCommand } from "src/bot/parse/ParsedCommand"; import { SkipEvent } from "src/bot/types"; import { CommandConfigs } from "src/command-configs/types"; +import { getSupportedRepoNames } from "src/command-configs/utils"; import { getVariablesOption, variablesExitOverride } from "src/commander/getVariablesOption"; import { config } from "src/config"; import { LoggerContext } from "src/logger"; @@ -38,6 +39,38 @@ export function getCommanderFromConfiguration( let parsedCommand: ParseResults["parsedCommand"] = undefined; const helpStr = `Refer to [help docs](${docsPath}) and/or [source code](${config.pipelineScripts.repository}).`; + const exitOverride = (commandKey: string) => (e: CommanderError) => { + if (e.code === "commander.excessArguments") { + throw new Error(((e as CommanderError).message = `Unknown subcommand of "${commandKey}". ${helpStr}`)); + } + throw new Error((e as CommanderError).message.replace("error: ", "")); + }; + const addPresetOptions = (cfg: { + presetCommand: Command; + presetConfig: NonNullable[keyof NonNullable]; + commandKey: string; + commandConfig: CmdJson; + actionCallBack: (genericCommand: GenericCommand) => void; + }): Command => { + const { presetConfig, presetCommand, commandConfig, commandKey, actionCallBack } = cfg; + if (presetConfig.description) presetCommand.description(presetConfig.description); + + if (presetConfig.args) { + for (const [argKey, argConfig] of Object.entries(presetConfig.args)) { + const option = convertOption(argKey, argConfig); + presetCommand.addOption(option); + } + } + + presetCommand.exitOverride(exitOverride(commandKey)).action((commandOptions: OptionValues, cmd: Command) => { + // extract global variable from the rest of the default options + const { variable: variables, ...rest } = cmd.optsWithGlobals() as { [key: string]: Record }; + actionCallBack(getGenericCommand({ commandKey, commandConfig, commandOptions: rest, variables })); + }); + + return presetCommand; + }; + root.addHelpCommand(false); root .command("help") @@ -75,56 +108,70 @@ export function getCommanderFromConfiguration( } } + root.addOption(getVariablesOption()).exitOverride(variablesExitOverride); + for (const [commandKey, commandConfig] of Object.entries(commandConfigs)) { - let command = new Command(commandKey); - command - .addOption(getVariablesOption()) - .exitOverride(variablesExitOverride) - .action((opts: OptionValues, cmd: Command) => { - const variables = cmd.optsWithGlobals().variable as Record; - const childCommands = cmd.commands - .map((childCommand) => childCommand.name()) - .filter((name) => name !== "default"); - - parsedCommand = - childCommands.length > 0 - ? new Error(`Missing arguments for command "${cmd.name()}". ${helpStr}`) - : getGenericCommand({ commandKey, commandConfig, variables }); - }); + const { repos: supportedRepos, includesGenericPresets } = getSupportedRepoNames(commandConfigs, commandKey); + + // skip creating command if the current repo doesn't support it + if (!includesGenericPresets && !supportedRepos.includes(repo)) { + continue; + } + + const command = new Command(commandKey); + command.exitOverride(exitOverride(commandKey)).action((opts: OptionValues, cmd: Command) => { + const variables = cmd.optsWithGlobals().variable as Record; + const childCommands = cmd.commands + .map((childCommand) => childCommand.name()) + .filter((name) => name !== "default"); + + parsedCommand = + childCommands.length > 0 + ? new Error(`Missing arguments for command "${cmd.name()}". ${helpStr}`) + : getGenericCommand({ commandKey, commandConfig, variables }); + }); if (commandConfig.command.description) command.description(commandConfig.command.description); if (commandConfig.command.presets) { for (const [presetKey, presetConfig] of Object.entries(commandConfig.command.presets)) { - const presetCommand = new Command(presetKey); - // if a current preset is default, then we'll add options to the current command - if (presetKey === "default") { - command = addPresetOptions({ - presetConfig, - presetCommand: command, - commandKey, - commandConfig, - actionCallBack: (genericCommand) => { - parsedCommand = genericCommand; - }, - }); - } else { - // keep adding presets as subcommands, including the default one - command.addCommand( + // add only presets which allowed in current current repository + if (presetConfig.repos?.includes(repo)) { + const presetCommand = new Command(presetKey); + + // if a current preset is default, then we'll add options to the current command + if (presetKey === "default") { addPresetOptions({ presetConfig, - presetCommand, + presetCommand: command, // `command` mutates inside by adding options commandKey, commandConfig, actionCallBack: (genericCommand) => { parsedCommand = genericCommand; }, - }), - ); + }); + } else { + // keep adding presets as subcommands, excluding the default one + command.addCommand( + addPresetOptions({ + presetConfig, + presetCommand, + commandKey, + commandConfig, + actionCallBack: (genericCommand) => { + parsedCommand = genericCommand; + }, + }), + ); + } } } } + // validates non-existent presets + command.allowExcessArguments(false); + command.allowUnknownOption(false); + root.addCommand(command); } @@ -135,11 +182,7 @@ export function getCommanderFromConfiguration( if (command.args.length > 0) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment unknownCommand = command.args[0]; - parsedCommand = new Error( - `Unknown command "${unknownCommand || ""}"; Available ones are ${Object.keys(commandConfigs).join( - ", ", - )}. ${helpStr}`, - ); + parsedCommand = new Error(`Unknown command "${unknownCommand || ""}". ${helpStr}`); } }); @@ -167,36 +210,6 @@ export function getCommanderFromConfiguration( return root; } -function addPresetOptions(cfg: { - presetCommand: Command; - presetConfig: NonNullable[keyof NonNullable]; - commandKey: string; - commandConfig: CmdJson; - actionCallBack: (genericCommand: GenericCommand) => void; -}): Command { - const { presetConfig, presetCommand, commandConfig, commandKey, actionCallBack } = cfg; - if (presetConfig.description) presetCommand.description(presetConfig.description); - - if (presetConfig.args) { - for (const [argKey, argConfig] of Object.entries(presetConfig.args)) { - const option = convertOption(argKey, argConfig); - presetCommand.addOption(option); - } - } - - presetCommand - .exitOverride((e) => { - throw new Error((e as CommanderError).message.replace("error: ", "")); - }) - .action((commandOptions: OptionValues, cmd: Command) => { - // extract global variable from the rest of the default options - const { variable: variables, ...rest } = cmd.optsWithGlobals() as { [key: string]: Record }; - actionCallBack(getGenericCommand({ commandKey, commandConfig, commandOptions: rest, variables })); - }); - - return presetCommand; -} - function convertOption( argKey: string, argConfig: { diff --git a/src/test/github-non-pipeline-cases.spec.ts b/src/test/github-non-pipeline-cases.spec.ts index 87ace3f..dfad206 100644 --- a/src/test/github-non-pipeline-cases.spec.ts +++ b/src/test/github-non-pipeline-cases.spec.ts @@ -53,7 +53,7 @@ const commandsDataProvider: CommandDataProviderItem[] = [ commandLine: "testbot hrlp", // intentional typo expected: { startMessage: - '@somedev123 Unknown command "hrlp"; Available ones are bench-all, bench-bm, bench-overhead, bench, fmt, merge, rebase, sample, try-runtime, update-ui. Refer to [help docs](http://localhost:3000/static/docs/latest.html?repo=command-bot-test) and/or [source code](https://github.com/paritytech/command-bot-scripts).', + '@somedev123 Unknown command "hrlp". Refer to [help docs](http://localhost:3000/static/docs/latest.html?repo=command-bot-test) and/or [source code](https://github.com/paritytech/command-bot-scripts).', }, }, { @@ -61,7 +61,7 @@ const commandsDataProvider: CommandDataProviderItem[] = [ commandLine: "testbot bench-all", // command-bot-test repo is not in a list of repos: [...] array expected: { startMessage: - '@somedev123 Missing arguments for command "bench-all". Refer to [help docs](http://localhost:3000/static/docs/latest.html?repo=command-bot-test) and/or [source code](https://github.com/paritytech/command-bot-scripts).', + '@somedev123 Unknown command "bench-all". Refer to [help docs](http://localhost:3000/static/docs/latest.html?repo=command-bot-test) and/or [source code](https://github.com/paritytech/command-bot-scripts).', }, }, { @@ -69,6 +69,15 @@ const commandsDataProvider: CommandDataProviderItem[] = [ commandLine: "testbot cancel", expected: { startMessage: "@somedev123 No task is being executed for this pull request" }, }, + + { + suitName: "[fmt] command with falsy args - won't be passed", + commandLine: "testbot fmt 1", + expected: { + startMessage: + '@somedev123 Unknown subcommand of "fmt". Refer to [help docs](http://localhost:3000/static/docs/latest.html?repo=command-bot-test) and/or [source code](https://github.com/paritytech/command-bot-scripts).', + }, + }, // TODO: add test for clean after moving to opstooling-testing // { // suitName: "[clean] command", diff --git a/src/test/github-positive-scenario.spec.ts b/src/test/github-positive-scenario.spec.ts index 1a9b2c9..bec49bd 100644 --- a/src/test/github-positive-scenario.spec.ts +++ b/src/test/github-positive-scenario.spec.ts @@ -44,20 +44,10 @@ const commandsDataProvider: CommandDataProviderItem[] = [ '@somedev123 Command `"$PIPELINE_SCRIPTS_DIR/commands/sample/sample.sh" --input=helloworld` has finished. Result: https://example.com/foo/bar/-/jobs/6 has finished. If any artifacts were generated, you can download them from https://example.com/foo/bar/-/jobs/6/artifacts/download', }, }, - { - suitName: "[fmt] command with falsy args - won't be passed", - commandLine: "testbot fmt 1", - taskId: 2, - expected: { - startMessage: - 'Preparing command ""$PIPELINE_SCRIPTS_DIR/commands/fmt/fmt.sh"". This comment will be updated later.', - finishMessage: '@somedev123 Command `"$PIPELINE_SCRIPTS_DIR/commands/fmt/fmt.sh"` has finished.', - }, - }, { suitName: "[fmt] command no args", commandLine: "testbot fmt", - taskId: 3, + taskId: 2, expected: { startMessage: 'Preparing command ""$PIPELINE_SCRIPTS_DIR/commands/fmt/fmt.sh"". This comment will be updated later.', @@ -67,7 +57,7 @@ const commandsDataProvider: CommandDataProviderItem[] = [ { suitName: "[bench-bot] command", commandLine: "testbot bench polkadot-pallet --runtime=kusama --pallet=pallet_referenda", - taskId: 4, + taskId: 3, expected: { startMessage: 'Preparing command ""$PIPELINE_SCRIPTS_DIR/commands/bench/bench.sh" --subcommand=runtime --runtime=kusama --target_dir=polkadot --pallet=pallet_referenda". This comment will be updated later.', @@ -78,7 +68,7 @@ const commandsDataProvider: CommandDataProviderItem[] = [ { suitName: "[try-runtime] command", commandLine: "testbot try-runtime -v RUST_LOG=remote-ext=debug,runtime=trace --chain=kusama", - taskId: 5, + taskId: 4, expected: { startMessage: 'Preparing command ""$PIPELINE_SCRIPTS_DIR/commands/try-runtime/try-runtime.sh" --chain=kusama --target_path=. --chain_node=polkadot',