From 825fe821682326939be3aec9ba2df60964c7441d Mon Sep 17 00:00:00 2001 From: Eric Rumsey Date: Wed, 14 May 2025 16:22:35 -0500 Subject: [PATCH] Initial commit Includes first version of Bridagier command parser --- .gitignore | 34 ++++++++ README.md | 15 ++++ bun.lock | 76 ++++++++++++++++ index.ts | 1 + package.json | 15 ++++ src/lib/brigadier/demoCommands.ts | 88 +++++++++++++++++++ src/lib/brigadier/errors.ts | 82 ++++++++++++++++++ src/lib/brigadier/index.ts | 2 + src/lib/brigadier/parser.ts | 139 ++++++++++++++++++++++++++++++ src/lib/brigadier/tree.ts | 13 +++ src/lib/brigadier/types.ts | 49 +++++++++++ src/lib/util/typeCheck.ts | 6 ++ tsconfig.json | 38 ++++++++ 13 files changed, 558 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 bun.lock create mode 100644 index.ts create mode 100644 package.json create mode 100644 src/lib/brigadier/demoCommands.ts create mode 100644 src/lib/brigadier/errors.ts create mode 100644 src/lib/brigadier/index.ts create mode 100644 src/lib/brigadier/parser.ts create mode 100644 src/lib/brigadier/tree.ts create mode 100644 src/lib/brigadier/types.ts create mode 100644 src/lib/util/typeCheck.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a14702c --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..bbbfde1 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# cli-framework + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.2.9. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..436547b --- /dev/null +++ b/bun.lock @@ -0,0 +1,76 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "cli-framework", + "devDependencies": { + "@maxmorozoff/try-catch-tuple": "^0.1.2", + "@maxmorozoff/try-catch-tuple-ts-plugin": "^0.0.1", + "@types/bun": "latest", + "ts-patch": "^3.3.0", + }, + "peerDependencies": { + "typescript": "^5.8.3", + }, + }, + }, + "packages": { + "@maxmorozoff/try-catch-tuple": ["@maxmorozoff/try-catch-tuple@0.1.2", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-stFwucLpAkJPkLIlBGsXB/Q9A3kSTFoioqqlz7V5r6pwsysQ1/+vpVKEutmbjSztoIOdy+iTn/iSSKyJXb+ebQ=="], + + "@maxmorozoff/try-catch-tuple-ts-plugin": ["@maxmorozoff/try-catch-tuple-ts-plugin@0.0.1", "", { "peerDependencies": { "ts-patch": "^3.3.0", "typescript": "^5.0.0" }, "optionalPeers": ["ts-patch"] }, "sha512-OeQpI8YfkuB1gFXSCyogsllfoMmEYnZvIRsSV+MWOxk/amrpA4rKjpbmqaHpVVo3YKFLqTLB8RLDFgVkoWBGhQ=="], + + "@types/bun": ["@types/bun@1.2.13", "", { "dependencies": { "bun-types": "1.2.13" } }, "sha512-u6vXep/i9VBxoJl3GjZsl/BFIsvML8DfVDO0RYLEwtSZSp981kEO1V5NwRcO1CPJ7AmvpbnDCiMKo3JvbDEjAg=="], + + "@types/node": ["@types/node@22.14.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "bun-types": ["bun-types@1.2.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-rRjA1T6n7wto4gxhAO/ErZEtOXyEZEmnIHQfl0Dt1QQSB4QV0iP6BZ9/YB5fZaHFQ2dwHFrmPaRQ9GGMX01k9Q=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "global-prefix": ["global-prefix@4.0.0", "", { "dependencies": { "ini": "^4.1.3", "kind-of": "^6.0.3", "which": "^4.0.0" } }, "sha512-w0Uf9Y9/nyHinEk5vMJKRie+wa4kR5hmDbEhGGds/kG1PwGLLHKRoNMeJOyCQjjBkANlnScqgzcFwGHgmgLkVA=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + + "ini": ["ini@4.1.3", "", {}, "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg=="], + + "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], + + "isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="], + + "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], + + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], + + "resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="], + + "semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], + + "ts-patch": ["ts-patch@3.3.0", "", { "dependencies": { "chalk": "^4.1.2", "global-prefix": "^4.0.0", "minimist": "^1.2.8", "resolve": "^1.22.2", "semver": "^7.6.3", "strip-ansi": "^6.0.1" }, "bin": { "ts-patch": "bin/ts-patch.js", "tspc": "bin/tspc.js" } }, "sha512-zAOzDnd5qsfEnjd9IGy1IRuvA7ygyyxxdxesbhMdutt8AHFjD8Vw8hU2rMF89HX1BKRWFYqKHrO8Q6lw0NeUZg=="], + + "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="], + } +} diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..2a5e4b8 --- /dev/null +++ b/index.ts @@ -0,0 +1 @@ +console.log("Hello via Bun!"); diff --git a/package.json b/package.json new file mode 100644 index 0000000..e11ec31 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "cli-framework", + "module": "index.ts", + "type": "module", + "private": true, + "devDependencies": { + "@maxmorozoff/try-catch-tuple": "^0.1.2", + "@maxmorozoff/try-catch-tuple-ts-plugin": "^0.0.1", + "@types/bun": "latest", + "ts-patch": "^3.3.0" + }, + "peerDependencies": { + "typescript": "^5.8.3" + } +} diff --git a/src/lib/brigadier/demoCommands.ts b/src/lib/brigadier/demoCommands.ts new file mode 100644 index 0000000..654445f --- /dev/null +++ b/src/lib/brigadier/demoCommands.ts @@ -0,0 +1,88 @@ +import { type ParseArgsOptionsConfig } from 'util' +import type { BrigadierTreeCommand, BrigadierTree } from "./types"; + +const commandObj: + { _opts?: ParseArgsOptionsConfig } & + Record = {} + +commandObj._opts = { + verbose: { + type: "boolean", + short: 'v', + }, + silent: { + type: "boolean", + short: "s", + }, +} + +commandObj.run = { + command: "run", + options: { + speed: { + type: "string", + } + } +} + +commandObj.walk = { + command: "walk", + options: { + fancy: { + type: "boolean", + short: "f" + } + } +} + +commandObj.stand = { + command: "stand", +} + +commandObj.sit = { + command: "sit", +} + +commandObj.say = { + command: "say", + value: { + required: true, + inputType: "string", + dataType: "string[]", + }, + options: { + accent: { + type: 'string', + default: "American", + } + }, + subcommands: [ + { + command: "talk", + value: { + required: true, + inputType: "string", + dataType: "string[]" + }, + }, + { + command: "whisper", + value: { + required: true, + inputType: "string", + dataType: "string[]" + } + }, + { + command: "yell", + value: { + required: true, + inputType: "string", + dataType: "string[]" + } + } + ] +} + +const { _opts, ...commands } = commandObj +export const main: BrigadierTree = { _opts, commands: [...Object.values(commands)] } diff --git a/src/lib/brigadier/errors.ts b/src/lib/brigadier/errors.ts new file mode 100644 index 0000000..720d72b --- /dev/null +++ b/src/lib/brigadier/errors.ts @@ -0,0 +1,82 @@ +export class BrigadierInternalError extends Error { + constructor(error: Error | undefined = undefined) { + super() + + this.name = this.constructor.name; + this.message = "The argument parser experience an internal error." + if (error) this.cause = error + } +} + +export class BrigadierConfigError extends Error { + constructor(message: string, error: Error | undefined = undefined) { + super(message) + + this.name = this.constructor.name; + if (error) this.cause = error + } +} + +export class BrigadierOptionError extends Error { + #defaultCode = "ERR_PARSE_ARGS_BRIGADIER_PARSE_OPTION"; + #defaultMessages = { + helpPrimitive: `Run '--help' for more information on how to use this command.`, + get generic() { + return `There was an error processing on of the given options. ${this.helpPrimitive}`; + }, + get unknownOption() { + return `Unknown option. ${this.helpPrimitive}`; + }, + get invalidOptionValue() { + return `An option has an invalid value. ${this.helpPrimitive}`; + }, + get missingOptionValue() { + return `An option value is missing. ${this.helpPrimitive}`; + }, + }; + option: string | undefined = undefined; + code: string = this.#defaultCode; + + constructor(message: string, error: Error) { + super(message); + + if ("code" in error && typeof error.code === "string") + this.#switchOnCodes(error.code); + + this.name = this.constructor.name; + if (error) this.cause = error; + } + + #switchOnCodes(code: string) { + if (code === "ERR_PARSE_ARGS_UNKNOWN_OPTION") { + this.message = this.#unknownOption(this.message); + this.code = code; + return; + } + if (code === "ERR_PARSE_ARGS_INVALID_OPTION_VALUE") { + this.message = this.#invalidOptionValue(this.message); + this.code = code; + return; + } + } + + #unknownOption(message: string) { + const index = 18; + if (typeof message !== "string" || message[index - 3] !== "'") + return this.#defaultMessages.unknownOption; + this.option = this.#extractOptionString(message, index, ". "); + return `Unknown option '--${this.option}'. ${this.#defaultMessages.helpPrimitive}`; + } + + #invalidOptionValue(message: string) { + const index = 10; + if (typeof message !== "string" || message[index - 3] !== "'") + return this.#defaultMessages.invalidOptionValue; + this.option = this.#extractOptionString(message, index, "' "); + return `Option '--${this.option}' has an invalid value. ${this.#defaultMessages.helpPrimitive}`; + } + + #extractOptionString(message: string, index: number, testString: string) { + return message.slice(index, message.indexOf(testString) - 1); + } +} diff --git a/src/lib/brigadier/index.ts b/src/lib/brigadier/index.ts new file mode 100644 index 0000000..9c79b59 --- /dev/null +++ b/src/lib/brigadier/index.ts @@ -0,0 +1,2 @@ +export { main as parser } from "./parser"; +export { treeUtils as tree } from "./tree" diff --git a/src/lib/brigadier/parser.ts b/src/lib/brigadier/parser.ts new file mode 100644 index 0000000..e08d54e --- /dev/null +++ b/src/lib/brigadier/parser.ts @@ -0,0 +1,139 @@ +import { tryCatch } from "@maxmorozoff/try-catch-tuple"; +import { + parseArgs, + type ParseArgsConfig, + type ParseArgsOptionsConfig, +} from "util"; +import { assertArrayOfStrings } from "../util/typeCheck"; +import { BrigadierConfigError, BrigadierInternalError, BrigadierOptionError } from "./errors"; +import type { BrigadierTree, BrigadierParserOverrides, BrigadierTreeCommand, BrigadierOutput } from "./types"; +import { treeUtils } from "./tree"; + +const defaultOverrides: BrigadierParserOverrides = { + allowPositionals: true, + allowNegative: true, + tokens: false, + strict: true, +}; + +function findNextTree(tree: Array, command: string) { + const nextCommand = tree.filter((obj) => obj.command === command) + return nextCommand[0]?.subcommands +} + +function findCommands(tree: Array | undefined, args: Array, out: Array = []) { + const item = args[0] + if (!item || !tree) return out + + const [commands, commandError] = tryCatch(() => treeUtils.commandArray(tree)) + if (commandError) throw commandError + + if (item.slice(0, 0) === "-" || !commands.includes(item)) return out + + const [newTree, newTreeError] = tryCatch(() => findNextTree(tree, item)) + if (newTreeError) throw newTreeError + + const newArgs = args.slice(1) + const newOut = [...out, item] + + return findCommands(newTree, newArgs, newOut) +} + +function findOpts(tree: Array, commands: Array, opts: ParseArgsOptionsConfig = {}) { + const item = commands[0] + if (!item) throw new TypeError("The first item in the array is missing.") + + const commandObj = tree.filter((obj) => obj.command === item) + const thisOpts = commandObj[0]?.options ? commandObj[0].options : {} + const nextOpts = Object.assign(opts, thisOpts) + + if (commands.length === 1) { + const config = nextOpts + return config + } + + const nextTree = commandObj[0]?.subcommands + if (!nextTree) throw new BrigadierConfigError(`The '${item}' command was expected to have subcommands.`) + return findOpts(nextTree, commands.slice(1), nextOpts) + +} + +function setParsedArgsConfig( + args: Array, + options: ParseArgsOptionsConfig, + overrides: BrigadierParserOverrides | undefined, + commands: Array +) { + if (!assertArrayOfStrings(args)) + throw new TypeError(`Parameter "args" must be an array of strings`); + const obj = + overrides === undefined + ? defaultOverrides + : Object.assign({}, defaultOverrides, overrides); + + const configObj: ParseArgsConfig = { + args, + options, + ...obj, + }; + return { config: configObj, commands: commands }; +} + +function generateParsedArgsConfig(tree: BrigadierTree, args: Array, overrides: BrigadierParserOverrides | undefined = undefined) { + const [commandsArray, commandsError] = tryCatch(() => findCommands(tree.commands, args.slice(2))) + if (commandsError) throw commandsError + if (!commandsArray[0]) { + const opts = tree._opts + return setParsedArgsConfig(args, opts, overrides, commandsArray) + } + + const [commandOpts, commandOptsError] = tryCatch(() => findOpts(tree.commands, commandsArray)) + if (commandOptsError) throw commandOptsError + const opts = Object.assign({}, tree._opts, commandOpts) + return setParsedArgsConfig(args, opts, overrides, commandsArray) +} + +function getParsedArgs(opts: { config: ParseArgsConfig, commands: Array }) { + const [result, error] = tryCatch(() => { + return parseArgs(opts.config); + }); + if (error) throw new BrigadierOptionError(error.message, error); + return { ...result, commands: opts.commands }; +} + +function buildPositionalsArray(commands: Array, positionals: Array) { + const testSet = new Set(commands) + return positionals.filter(str => !testSet.has(str)) +} + +function handleErr(error: Error) { + if (error instanceof BrigadierOptionError || error instanceof BrigadierConfigError) return error + return new BrigadierInternalError() +} + +function buildOutputObject(tree: BrigadierTree, args: Array, overrides: BrigadierParserOverrides | undefined = undefined) { + try { + const [config, configError] = tryCatch(() => generateParsedArgsConfig(tree, args, overrides)) + if (configError) throw configError + + const [parsedArgs, parsedError] = tryCatch(() => getParsedArgs(config)) + if (parsedError) throw parsedError + + const programPaths = { + bun: parsedArgs.positionals[0] ? parsedArgs.positionals[0] : "", + path: parsedArgs.positionals[1] ? parsedArgs.positionals[1] : "", + } + const command = config.commands[0] ? { command: config.commands[0] } : {} + const subcommands = config.commands[1] ? { subcommands: config.commands.slice(1) } : {} + const [positionals, positionalsError] = tryCatch(() => buildPositionalsArray(config.commands, parsedArgs.positionals.slice(2))) + if (positionalsError) throw positionalsError + const positionalsObj = positionals.length > 0 ? { positionals } : {} + const values = parsedArgs.values ? parsedArgs.values : {} + + const out: BrigadierOutput = Object.assign({}, programPaths, command, subcommands, positionalsObj, { values }) + return out + } catch (error) { + throw handleErr(error as Error) + } +} +export const main = buildOutputObject; diff --git a/src/lib/brigadier/tree.ts b/src/lib/brigadier/tree.ts new file mode 100644 index 0000000..154ff48 --- /dev/null +++ b/src/lib/brigadier/tree.ts @@ -0,0 +1,13 @@ +import { demoTree } from "."; +import type { BrigadierTreeCommand } from "./types"; + + +function commandArray(array: Array) { + const out: Array = [] + array.forEach((obj) => out.push(obj.command)) + return out +} + +export const treeUtils = { + commandArray +} diff --git a/src/lib/brigadier/types.ts b/src/lib/brigadier/types.ts new file mode 100644 index 0000000..178fb55 --- /dev/null +++ b/src/lib/brigadier/types.ts @@ -0,0 +1,49 @@ +import { type ParseArgsOptionsConfig } from 'util' + +export type BrigadierCommandValue = + "string" | + "number" | + "boolean" | + "string[]" | + "number[]" | + "boolean[]" | + "mixed[]" + +export type BrigadierTreeCommand = { + command: string, + value?: { + required: boolean, + inputType: "string" | "boolean", + dataType: BrigadierCommandValue, + default?: string | boolean + } + options?: ParseArgsOptionsConfig, + subcommands?: Array +} + +export type BrigadierTree = { + _opts: ParseArgsOptionsConfig, + commands: Array +} + +export type BrigadierOutput = { + bun: string, + path: string, + command?: string, + subcommands?: Array, + positionals?: Array, + values?: Record +} + +export type BrigadierParserOverrides = { + strict?: boolean; + allowPositionals?: boolean; + allowNegative?: boolean; + tokens?: boolean; +}; + +export type BrigadierInput = BrigadierTree & { + args: Array + overrides?: BrigadierParserOverrides; +}; + diff --git a/src/lib/util/typeCheck.ts b/src/lib/util/typeCheck.ts new file mode 100644 index 0000000..ec972a4 --- /dev/null +++ b/src/lib/util/typeCheck.ts @@ -0,0 +1,6 @@ +export function assertArrayOfStrings(arr: unknown) { + const arrName = Object.keys({ arr })[0]; + if (!Array.isArray(arr)) return false; + if (!arr.every((item: unknown) => typeof item === "string")) return false; + return true; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..306d71b --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,38 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false, + + // Plugins + "plugins": [ + { + "name": "@maxmorozoff/try-catch-tuple-ts-plugin", + // --- Optional Configuration for LSP --- + "errorLevel": "error", + "allowIgnoredError": true, + "checkWrappedCalls": true} + ] + } +}