mirror of
https://github.com/versia-pub/server.git
synced 2026-03-13 05:49:16 +01:00
Leave CLI as broken
This commit is contained in:
parent
b69f20ccf4
commit
f4fd16179c
6 changed files with 350 additions and 77 deletions
|
|
@ -1,10 +1,23 @@
|
|||
export interface CliParameter {
|
||||
name: string;
|
||||
// If not positioned, the argument will need to be called with --name value instead of just value
|
||||
/* Like -v for --version */
|
||||
shortName?: string;
|
||||
/**
|
||||
* If not positioned, the argument will need to be called with --name value instead of just value
|
||||
* @default true
|
||||
*/
|
||||
positioned?: boolean;
|
||||
// Whether the argument needs a value (requires positioned to be false)
|
||||
/* Whether the argument needs a value (requires positioned to be false) */
|
||||
needsValue?: boolean;
|
||||
optional?: true;
|
||||
type: "string" | "number" | "boolean" | "array";
|
||||
type: CliParameterType;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export enum CliParameterType {
|
||||
STRING = "string",
|
||||
NUMBER = "number",
|
||||
BOOLEAN = "boolean",
|
||||
ARRAY = "array",
|
||||
EMPTY = "empty",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { CliParameter } from "./cli-builder.type";
|
||||
import { CliParameterType, type CliParameter } from "./cli-builder.type";
|
||||
import chalk from "chalk";
|
||||
|
||||
export function startsWithArray(fullArray: any[], startArray: any[]) {
|
||||
|
|
@ -193,21 +193,21 @@ export class CliBuilder {
|
|||
if (value instanceof CliCommand) {
|
||||
writeBuffer += `${" ".repeat(depth)}${chalk.blue(key)}|${chalk.underline(value.description)}\n`;
|
||||
const positionedArgs = value.argTypes.filter(
|
||||
arg => arg.positioned
|
||||
arg => arg.positioned ?? true
|
||||
);
|
||||
const unpositionedArgs = value.argTypes.filter(
|
||||
arg => !arg.positioned
|
||||
arg => !(arg.positioned ?? true)
|
||||
);
|
||||
|
||||
for (const arg of unpositionedArgs) {
|
||||
for (const arg of positionedArgs) {
|
||||
writeBuffer += `${" ".repeat(depth + 1)}${chalk.green(
|
||||
arg.name
|
||||
)}|${
|
||||
arg.description ?? "(no description)"
|
||||
} ${arg.optional ? chalk.gray("(optional)") : ""}\n`;
|
||||
}
|
||||
for (const arg of positionedArgs) {
|
||||
writeBuffer += `${" ".repeat(depth + 1)}${chalk.yellow("--" + arg.name)}|${
|
||||
for (const arg of unpositionedArgs) {
|
||||
writeBuffer += `${" ".repeat(depth + 1)}${chalk.yellow("--" + arg.name)}${arg.shortName ? ", " + chalk.yellow("-" + arg.shortName) : ""}|${
|
||||
arg.description ?? "(no description)"
|
||||
} ${arg.optional ? chalk.gray("(optional)") : ""}\n`;
|
||||
}
|
||||
|
|
@ -253,15 +253,38 @@ export class CliBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
/* type CliParametersToType<T extends CliParameter[]> = {
|
||||
[K in T[number]["name"]]: T[number]["type"] extends CliParameterType.STRING
|
||||
? string
|
||||
: T[number]["type"] extends CliParameterType.NUMBER
|
||||
? number
|
||||
: T[number]["type"] extends CliParameterType.BOOLEAN
|
||||
? boolean
|
||||
: T[number]["type"] extends CliParameterType.ARRAY
|
||||
? string[]
|
||||
: T[number]["type"] extends CliParameterType.EMPTY
|
||||
? never
|
||||
: never;
|
||||
};
|
||||
|
||||
type ExecuteFunction<T extends CliParameter[]> = (
|
||||
args: CliParametersToType<T>
|
||||
) => void; */
|
||||
|
||||
type ExecuteFunction<T> = (
|
||||
instance: CliCommand,
|
||||
args: Partial<T>
|
||||
) => Promise<void> | void;
|
||||
|
||||
/**
|
||||
* A command that can be executed from the command line
|
||||
* @param categories Example: `["user", "create"]` for the command `./cli user create --name John`
|
||||
*/
|
||||
export class CliCommand {
|
||||
export class CliCommand<T = any> {
|
||||
constructor(
|
||||
public categories: string[],
|
||||
public argTypes: CliParameter[],
|
||||
private execute: (args: Record<string, any>) => void,
|
||||
private execute: ExecuteFunction<T>,
|
||||
public description?: string,
|
||||
public example?: string
|
||||
) {}
|
||||
|
|
@ -271,13 +294,17 @@ export class CliCommand {
|
|||
* formatted with Chalk and with emojis
|
||||
*/
|
||||
displayHelp() {
|
||||
const positionedArgs = this.argTypes.filter(arg => arg.positioned);
|
||||
const unpositionedArgs = this.argTypes.filter(arg => !arg.positioned);
|
||||
const positionedArgs = this.argTypes.filter(
|
||||
arg => arg.positioned ?? true
|
||||
);
|
||||
const unpositionedArgs = this.argTypes.filter(
|
||||
arg => !(arg.positioned ?? true)
|
||||
);
|
||||
const helpMessage = `
|
||||
${chalk.green("📚 Command:")} ${chalk.yellow(this.categories.join(" "))}
|
||||
${this.description ? `${chalk.cyan(this.description)}\n` : ""}
|
||||
${chalk.magenta("🔧 Arguments:")}
|
||||
${unpositionedArgs
|
||||
${positionedArgs
|
||||
.map(
|
||||
arg =>
|
||||
`${chalk.bold(arg.name)}: ${chalk.blue(arg.description ?? "(no description)")} ${
|
||||
|
|
@ -285,10 +312,10 @@ ${unpositionedArgs
|
|||
}`
|
||||
)
|
||||
.join("\n")}
|
||||
${positionedArgs
|
||||
${unpositionedArgs
|
||||
.map(
|
||||
arg =>
|
||||
`--${chalk.bold(arg.name)}: ${chalk.blue(arg.description ?? "(no description)")} ${
|
||||
`--${chalk.bold(arg.name)}${arg.shortName ? `, -${arg.shortName}` : ""}: ${chalk.blue(arg.description ?? "(no description)")} ${
|
||||
arg.optional ? chalk.gray("(optional)") : ""
|
||||
}`
|
||||
)
|
||||
|
|
@ -328,6 +355,20 @@ ${positionedArgs
|
|||
i++;
|
||||
currentParameter = null;
|
||||
}
|
||||
} else if (arg.startsWith("-")) {
|
||||
const shortName = arg.substring(1);
|
||||
const argType = this.argTypes.find(
|
||||
argType => argType.shortName === shortName
|
||||
);
|
||||
if (argType && !argType.needsValue) {
|
||||
parsedArgs[argType.name] = true;
|
||||
} else if (argType && argType.needsValue) {
|
||||
parsedArgs[argType.name] = this.castArgValue(
|
||||
argsWithoutCategories[i + 1],
|
||||
argType.type
|
||||
);
|
||||
i++;
|
||||
}
|
||||
} else if (currentParameter) {
|
||||
parsedArgs[currentParameter.name] = this.castArgValue(
|
||||
arg,
|
||||
|
|
@ -352,13 +393,13 @@ ${positionedArgs
|
|||
|
||||
private castArgValue(value: string, type: CliParameter["type"]): any {
|
||||
switch (type) {
|
||||
case "string":
|
||||
case CliParameterType.STRING:
|
||||
return value;
|
||||
case "number":
|
||||
case CliParameterType.NUMBER:
|
||||
return Number(value);
|
||||
case "boolean":
|
||||
case CliParameterType.BOOLEAN:
|
||||
return value === "true";
|
||||
case "array":
|
||||
case CliParameterType.ARRAY:
|
||||
return value.split(",");
|
||||
default:
|
||||
return value;
|
||||
|
|
@ -370,6 +411,6 @@ ${positionedArgs
|
|||
*/
|
||||
run(argsWithoutCategories: string[]) {
|
||||
const args = this.parseArgs(argsWithoutCategories);
|
||||
this.execute(args);
|
||||
void this.execute(this, args as any);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import { CliCommand, CliBuilder, startsWithArray } from "..";
|
||||
import { describe, beforeEach, it, expect, jest, spyOn } from "bun:test";
|
||||
import stripAnsi from "strip-ansi";
|
||||
import { CliParameterType } from "../cli-builder.type";
|
||||
|
||||
describe("startsWithArray", () => {
|
||||
it("should return true when fullArray starts with startArray", () => {
|
||||
|
|
@ -36,10 +37,27 @@ describe("CliCommand", () => {
|
|||
cliCommand = new CliCommand(
|
||||
["category1", "category2"],
|
||||
[
|
||||
{ name: "arg1", type: "string", needsValue: true },
|
||||
{ name: "arg2", type: "number", needsValue: true },
|
||||
{ name: "arg3", type: "boolean", needsValue: false },
|
||||
{ name: "arg4", type: "array", needsValue: true },
|
||||
{
|
||||
name: "arg1",
|
||||
type: CliParameterType.STRING,
|
||||
needsValue: true,
|
||||
},
|
||||
{
|
||||
name: "arg2",
|
||||
shortName: "a",
|
||||
type: CliParameterType.NUMBER,
|
||||
needsValue: true,
|
||||
},
|
||||
{
|
||||
name: "arg3",
|
||||
type: CliParameterType.BOOLEAN,
|
||||
needsValue: false,
|
||||
},
|
||||
{
|
||||
name: "arg4",
|
||||
type: CliParameterType.ARRAY,
|
||||
needsValue: true,
|
||||
},
|
||||
],
|
||||
() => {
|
||||
// Do nothing
|
||||
|
|
@ -65,13 +83,34 @@ describe("CliCommand", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("should cast argument values correctly", () => {
|
||||
expect(cliCommand["castArgValue"]("42", "number")).toBe(42);
|
||||
expect(cliCommand["castArgValue"]("true", "boolean")).toBe(true);
|
||||
expect(cliCommand["castArgValue"]("value1,value2", "array")).toEqual([
|
||||
it("should parse short names for arguments too", () => {
|
||||
const args = cliCommand["parseArgs"]([
|
||||
"--arg1",
|
||||
"value1",
|
||||
"value2",
|
||||
"-a",
|
||||
"42",
|
||||
"--arg3",
|
||||
"--arg4",
|
||||
"value1,value2",
|
||||
]);
|
||||
expect(args).toEqual({
|
||||
arg1: "value1",
|
||||
arg2: 42,
|
||||
arg3: true,
|
||||
arg4: ["value1", "value2"],
|
||||
});
|
||||
});
|
||||
|
||||
it("should cast argument values correctly", () => {
|
||||
expect(cliCommand["castArgValue"]("42", CliParameterType.NUMBER)).toBe(
|
||||
42
|
||||
);
|
||||
expect(
|
||||
cliCommand["castArgValue"]("true", CliParameterType.BOOLEAN)
|
||||
).toBe(true);
|
||||
expect(
|
||||
cliCommand["castArgValue"]("value1,value2", CliParameterType.ARRAY)
|
||||
).toEqual(["value1", "value2"]);
|
||||
});
|
||||
|
||||
it("should run the execute function with the parsed parameters", () => {
|
||||
|
|
@ -79,10 +118,26 @@ describe("CliCommand", () => {
|
|||
cliCommand = new CliCommand(
|
||||
["category1", "category2"],
|
||||
[
|
||||
{ name: "arg1", type: "string", needsValue: true },
|
||||
{ name: "arg2", type: "number", needsValue: true },
|
||||
{ name: "arg3", type: "boolean", needsValue: false },
|
||||
{ name: "arg4", type: "array", needsValue: true },
|
||||
{
|
||||
name: "arg1",
|
||||
type: CliParameterType.STRING,
|
||||
needsValue: true,
|
||||
},
|
||||
{
|
||||
name: "arg2",
|
||||
type: CliParameterType.NUMBER,
|
||||
needsValue: true,
|
||||
},
|
||||
{
|
||||
name: "arg3",
|
||||
type: CliParameterType.BOOLEAN,
|
||||
needsValue: false,
|
||||
},
|
||||
{
|
||||
name: "arg4",
|
||||
type: CliParameterType.ARRAY,
|
||||
needsValue: true,
|
||||
},
|
||||
],
|
||||
mockExecute
|
||||
);
|
||||
|
|
@ -109,13 +164,29 @@ describe("CliCommand", () => {
|
|||
cliCommand = new CliCommand(
|
||||
["category1", "category2"],
|
||||
[
|
||||
{ name: "arg1", type: "string", needsValue: true },
|
||||
{ name: "arg2", type: "number", needsValue: true },
|
||||
{ name: "arg3", type: "boolean", needsValue: false },
|
||||
{ name: "arg4", type: "array", needsValue: true },
|
||||
{
|
||||
name: "arg1",
|
||||
type: CliParameterType.STRING,
|
||||
needsValue: true,
|
||||
},
|
||||
{
|
||||
name: "arg2",
|
||||
type: CliParameterType.NUMBER,
|
||||
needsValue: true,
|
||||
},
|
||||
{
|
||||
name: "arg3",
|
||||
type: CliParameterType.BOOLEAN,
|
||||
needsValue: false,
|
||||
},
|
||||
{
|
||||
name: "arg4",
|
||||
type: CliParameterType.ARRAY,
|
||||
needsValue: true,
|
||||
},
|
||||
{
|
||||
name: "arg5",
|
||||
type: "string",
|
||||
type: CliParameterType.STRING,
|
||||
needsValue: true,
|
||||
positioned: true,
|
||||
},
|
||||
|
|
@ -153,31 +224,31 @@ describe("CliCommand", () => {
|
|||
[
|
||||
{
|
||||
name: "arg1",
|
||||
type: "string",
|
||||
type: CliParameterType.STRING,
|
||||
needsValue: true,
|
||||
description: "Argument 1",
|
||||
optional: true,
|
||||
},
|
||||
{
|
||||
name: "arg2",
|
||||
type: "number",
|
||||
type: CliParameterType.NUMBER,
|
||||
needsValue: true,
|
||||
description: "Argument 2",
|
||||
},
|
||||
{
|
||||
name: "arg3",
|
||||
type: "boolean",
|
||||
type: CliParameterType.BOOLEAN,
|
||||
needsValue: false,
|
||||
description: "Argument 3",
|
||||
optional: true,
|
||||
positioned: true,
|
||||
positioned: false,
|
||||
},
|
||||
{
|
||||
name: "arg4",
|
||||
type: "array",
|
||||
type: CliParameterType.ARRAY,
|
||||
needsValue: true,
|
||||
description: "Argument 4",
|
||||
positioned: true,
|
||||
positioned: false,
|
||||
},
|
||||
],
|
||||
() => {
|
||||
|
|
@ -260,7 +331,7 @@ describe("CliBuilder", () => {
|
|||
[
|
||||
{
|
||||
name: "arg1",
|
||||
type: "string",
|
||||
type: CliParameterType.STRING,
|
||||
needsValue: true,
|
||||
positioned: false,
|
||||
},
|
||||
|
|
@ -352,20 +423,28 @@ describe("CliBuilder", () => {
|
|||
[
|
||||
{
|
||||
name: "name",
|
||||
type: "string",
|
||||
type: CliParameterType.STRING,
|
||||
needsValue: true,
|
||||
description: "Name of new item",
|
||||
},
|
||||
{
|
||||
name: "delete-previous",
|
||||
type: "number",
|
||||
type: CliParameterType.NUMBER,
|
||||
needsValue: false,
|
||||
positioned: true,
|
||||
positioned: false,
|
||||
optional: true,
|
||||
description: "Also delete the previous item",
|
||||
},
|
||||
{ name: "arg3", type: "boolean", needsValue: false },
|
||||
{ name: "arg4", type: "array", needsValue: true },
|
||||
{
|
||||
name: "arg3",
|
||||
type: CliParameterType.BOOLEAN,
|
||||
needsValue: false,
|
||||
},
|
||||
{
|
||||
name: "arg4",
|
||||
type: CliParameterType.ARRAY,
|
||||
needsValue: true,
|
||||
},
|
||||
],
|
||||
() => {
|
||||
// Do nothing
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue