commander-jsx
Advanced tools
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.Fragment = exports.jsxDEV = exports.jsxs = exports.jsx = void 0; | ||
| const tslib_1 = require("tslib"); | ||
| const web_utility_1 = require("web-utility"); | ||
| /** | ||
| * JSX runtime for CommanderJSX | ||
| * @see {@link https://github.com/reactjs/rfcs/blob/createlement-rfc/text/0000-create-element-changes.md} | ||
| * @see {@link https://babeljs.io/docs/babel-plugin-transform-react-jsx} | ||
| */ | ||
| const jsx = (type, _a) => { | ||
| var { children } = _a, props = tslib_1.__rest(_a, ["children"]); | ||
| return new type(Object.assign(Object.assign({}, props), { children: (0, web_utility_1.makeArray)(children) })); | ||
| }; | ||
| exports.jsx = jsx; | ||
| exports.jsxs = exports.jsx; | ||
| exports.jsxDEV = exports.jsx; | ||
| /** | ||
| * Fragment support (not typically used in CommanderJSX, but required by JSX runtime) | ||
| */ | ||
| const Fragment = ({ children }) => (0, web_utility_1.makeArray)(children); | ||
| exports.Fragment = Fragment; |
| import { Command, CommandChildren, CommandMeta } from './dist/Command'; | ||
| declare global { | ||
| namespace JSX { | ||
| interface ElementType<T = any> { | ||
| new (meta: CommandMeta<T>): Command<T>; | ||
| } | ||
| interface Element extends Command<any> { | ||
| } | ||
| interface IntrinsicAttributes { | ||
| children?: CommandChildren; | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * JSX runtime for CommanderJSX | ||
| * @see {@link https://github.com/reactjs/rfcs/blob/createlement-rfc/text/0000-create-element-changes.md} | ||
| * @see {@link https://babeljs.io/docs/babel-plugin-transform-react-jsx} | ||
| */ | ||
| export declare const jsx: <T>(type: { | ||
| new (meta: CommandMeta<T>): Command<T>; | ||
| }, { children, ...props }: CommandMeta<T> & { | ||
| children?: CommandChildren<T>; | ||
| }) => Command<T>; | ||
| export declare const jsxs: <T>(type: { | ||
| new (meta: CommandMeta<T>): Command<T>; | ||
| }, { children, ...props }: CommandMeta<T> & { | ||
| children?: CommandChildren<T>; | ||
| }) => Command<T>; | ||
| export declare const jsxDEV: <T>(type: { | ||
| new (meta: CommandMeta<T>): Command<T>; | ||
| }, { children, ...props }: CommandMeta<T> & { | ||
| children?: CommandChildren<T>; | ||
| }) => Command<T>; | ||
| /** | ||
| * Fragment support (not typically used in CommanderJSX, but required by JSX runtime) | ||
| */ | ||
| export declare const Fragment: ({ children }: JSX.IntrinsicAttributes) => Command<any>[] | CommandChildren<any>[]; |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.Fragment = exports.jsxDEV = exports.jsxs = exports.jsx = void 0; | ||
| const tslib_1 = require("tslib"); | ||
| const web_utility_1 = require("web-utility"); | ||
| /** | ||
| * JSX runtime for CommanderJSX | ||
| * @see {@link https://github.com/reactjs/rfcs/blob/createlement-rfc/text/0000-create-element-changes.md} | ||
| * @see {@link https://babeljs.io/docs/babel-plugin-transform-react-jsx} | ||
| */ | ||
| const jsx = (type, _a) => { | ||
| var { children } = _a, props = tslib_1.__rest(_a, ["children"]); | ||
| return new type(Object.assign(Object.assign({}, props), { children: (0, web_utility_1.makeArray)(children) })); | ||
| }; | ||
| exports.jsx = jsx; | ||
| exports.jsxs = exports.jsx; | ||
| exports.jsxDEV = exports.jsx; | ||
| /** | ||
| * Fragment support (not typically used in CommanderJSX, but required by JSX runtime) | ||
| */ | ||
| const Fragment = ({ children }) => (0, web_utility_1.makeArray)(children); | ||
| exports.Fragment = Fragment; |
| import { packageOf } from '@tech_query/node-toolkit'; | ||
| import { OptionData, Data, parseArguments } from './parser'; | ||
| import { createTable } from './creator'; | ||
| export interface Option { | ||
| shortcut?: string; | ||
| parameters?: string; | ||
| pattern?: RegExp; | ||
| description?: string; | ||
| } | ||
| export type Options<T> = Record<keyof T, Option>; | ||
| export type Executor<T> = (options: OptionData<T>, ...data: Data[]) => any; | ||
| export type CommandChildren<T = any> = Command<T> | Array<CommandChildren<T>>; | ||
| export interface CommandMeta<T> extends Option { | ||
| name?: string; | ||
| version?: string; | ||
| options?: Options<T>; | ||
| children?: CommandChildren<T>; | ||
| executor?: Executor<T>; | ||
| } | ||
| const PresetOption = { | ||
| version: { shortcut: 'v', description: 'show Version number' }, | ||
| help: { shortcut: 'h', description: 'show Help information' } | ||
| }; | ||
| export class Command<T = any> implements CommandMeta<T> { | ||
| name = ''; | ||
| parameters = ''; | ||
| description = ''; | ||
| version = ''; | ||
| options: Options<T> = {} as Options<T>; | ||
| parent?: Command<T>; | ||
| children: Command<T>[] = []; | ||
| executor?: Executor<T>; | ||
| constructor(meta: CommandMeta<T>) { | ||
| Object.assign(this, meta); | ||
| for (const command of this.children) command.parent = this; | ||
| this.addPreset(); | ||
| } | ||
| static nameOf(meta: Record<string, any>, commandPath: string) { | ||
| if (typeof meta.bin != 'object') return meta.name as string; | ||
| commandPath = commandPath.replaceAll('\\', '/'); | ||
| return Object.entries(meta.bin as Record<string, string>).find(([name, path]) => | ||
| commandPath.endsWith(path.replace(/^\.\//, '')) | ||
| )?.[0]; | ||
| } | ||
| static execute<T>(command: Command<T>, args: string[]) { | ||
| const { data, options } = parseArguments<T>(args); | ||
| if (!command.parent && (!command.name || !command.version || !command.description)) { | ||
| const [_, commandPath] = process.argv; | ||
| const { meta } = packageOf(commandPath); | ||
| command.name ||= this.nameOf(meta, commandPath) || ''; | ||
| command.description ||= meta.description || ''; | ||
| if ((command.version ||= meta.version || '')) | ||
| (command.options as Record<keyof typeof PresetOption, any>).version = | ||
| PresetOption.version; | ||
| } | ||
| command.execute(options as OptionData<T>, ...data); | ||
| } | ||
| execute(options: OptionData<T>, ...data: Data[]): void { | ||
| const command = this.children.find(({ name }) => name === data[0]); | ||
| if (command instanceof Command) return command.execute(options, ...data.slice(1)); | ||
| options = this.checkPattern(this.replaceShortcut(options)); | ||
| if ('version' in options) { | ||
| console.log(this.version); | ||
| } else if ('help' in options) { | ||
| this.showHelp(); | ||
| } else if (this.executor instanceof Function) { | ||
| this.executor(options, ...data); | ||
| } else { | ||
| throw ReferenceError(`Unknown "${data[0]}" command`); | ||
| } | ||
| } | ||
| protected replaceShortcut(options: OptionData<T>) { | ||
| const map: Record<string, keyof T> = Object.fromEntries( | ||
| Object.entries<Option>(this.options).map(([key, { shortcut }]) => [shortcut, key]) | ||
| ), | ||
| data: OptionData<T> = {} as OptionData<T>; | ||
| for (const key in options) | ||
| if (key in map) data[map[key]] = options[key]; | ||
| else data[key] = options[key]; | ||
| return data; | ||
| } | ||
| protected checkPattern(options: OptionData<T>) { | ||
| for (const key in options) { | ||
| const option = this.options[key]; | ||
| if (!option) throw ReferenceError(`Unknown "${key}" option`); | ||
| if (option.pattern?.test(options[key] + '') === false) | ||
| throw SyntaxError(`"${key}=${options[key]}" doesn't match ${option.pattern}`); | ||
| } | ||
| return options; | ||
| } | ||
| protected addPreset() { | ||
| const { name, options, children } = this, | ||
| { version, help } = PresetOption; | ||
| Object.assign(this.options, { | ||
| ...(this.version ? { version } : {}), | ||
| help, | ||
| ...options | ||
| }); | ||
| if (name !== 'help' && !children.find(({ name }) => name === 'help')) | ||
| children.push( | ||
| new Command({ | ||
| name: 'help', | ||
| ...help, | ||
| parameters: '[command]', | ||
| executor: (_, command) => this.showHelp(command as string) | ||
| }) | ||
| ); | ||
| } | ||
| showHelp(command?: string) { | ||
| if (!command) return console.log(this + ''); | ||
| const that = this.children.find(({ name }) => name === command); | ||
| if (that) console.log(that + ''); | ||
| } | ||
| *getParentNames() { | ||
| let that: Command | undefined = this; | ||
| while ((that = that.parent)) yield that.name; | ||
| } | ||
| toString() { | ||
| const result = [ | ||
| [this.parameters, this.name, ...this.getParentNames()].reverse().join(' ').trim(), | ||
| this.description, | ||
| 'Options:\n' + this.toOptionString() | ||
| ]; | ||
| if (this.children[0]) result.push('Commands:\n' + this.toChildrenString()); | ||
| return result.join('\n\n'); | ||
| } | ||
| toOptionString() { | ||
| return createTable( | ||
| Object.entries<Option>(this.options as Options<T>) | ||
| .sort(([A], [B]) => A.localeCompare(B)) | ||
| .map(([name, { shortcut, parameters = '', description = '' }]) => [ | ||
| '', | ||
| `${shortcut ? `-${shortcut}, ` : ''}${name[1] ? '-' : ''}-${name}`, | ||
| parameters, | ||
| description | ||
| ]) | ||
| ); | ||
| } | ||
| toChildrenString() { | ||
| return createTable( | ||
| this.children | ||
| .sort(({ name: A }, { name: B }) => A.localeCompare(B)) | ||
| .map(({ name, parameters = '', description = '' }) => [ | ||
| '', | ||
| name, | ||
| parameters, | ||
| description | ||
| ]) | ||
| ); | ||
| } | ||
| } |
| export function createTable(list: string[][]) { | ||
| const counts = list.reduce((counts, row) => { | ||
| for (const [index, { length }] of row.entries()) | ||
| if (index + 1 < row.length) counts[index] = Math.max(counts[index], length); | ||
| return counts; | ||
| }, Array(list[0].length).fill(0)); | ||
| return list | ||
| .map(row => | ||
| row | ||
| .map((column, index) => column.padEnd(counts[index], ' ')) | ||
| .join(' ') | ||
| .trimEnd() | ||
| ) | ||
| .join('\n'); | ||
| } |
| export * from './parser'; | ||
| export * from './Command'; | ||
| export * from './creator'; |
| export type PrimitiveData = string | number | boolean | null; | ||
| export type Data = PrimitiveData | Record<string, PrimitiveData> | PrimitiveData[]; | ||
| export function parseData(raw: string): Data { | ||
| try { | ||
| return JSON.parse((raw = raw.trim())); | ||
| } catch { | ||
| return raw.includes(',') ? (raw.split(',').map(parseData) as PrimitiveData[]) : raw; | ||
| } | ||
| } | ||
| export type OptionData<T> = Record<keyof T, Data>; | ||
| export function parseArguments<T>(list: string[]) { | ||
| const options: OptionData<T> = {} as OptionData<T>, | ||
| data: Data[] = []; | ||
| let lastKey = ''; | ||
| for (const item of list) | ||
| if (item.startsWith('--')) { | ||
| lastKey = item.slice(2); | ||
| options[lastKey as keyof T] = true; | ||
| } else if (item.startsWith('-')) { | ||
| if (item[2]) for (const k of item.slice(1)) options[k as keyof T] = true; | ||
| else { | ||
| lastKey = item[1]; | ||
| options[lastKey as keyof T] = true; | ||
| } | ||
| } else if (lastKey) { | ||
| options[lastKey as keyof T] = parseData(item); | ||
| lastKey = ''; | ||
| } else { | ||
| data.push(parseData(item)); | ||
| } | ||
| return { options, data }; | ||
| } |
| import { makeArray } from 'web-utility'; | ||
| import { Command, CommandChildren, CommandMeta } from './dist/Command'; | ||
| declare global { | ||
| namespace JSX { | ||
| interface ElementType<T = any> { | ||
| new (meta: CommandMeta<T>): Command<T>; | ||
| } | ||
| interface Element extends Command<any> {} | ||
| interface IntrinsicAttributes { | ||
| children?: CommandChildren; | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * JSX runtime for CommanderJSX | ||
| * @see {@link https://github.com/reactjs/rfcs/blob/createlement-rfc/text/0000-create-element-changes.md} | ||
| * @see {@link https://babeljs.io/docs/babel-plugin-transform-react-jsx} | ||
| */ | ||
| export const jsx = <T>( | ||
| type: { new (meta: CommandMeta<T>): Command<T> }, | ||
| { children, ...props }: CommandMeta<T> & { children?: CommandChildren<T> } | ||
| ): Command<T> => | ||
| new type({ | ||
| ...props, | ||
| children: makeArray(children) | ||
| } as CommandMeta<T>); | ||
| export const jsxs = jsx; | ||
| export const jsxDEV = jsx; | ||
| /** | ||
| * Fragment support (not typically used in CommanderJSX, but required by JSX runtime) | ||
| */ | ||
| export const Fragment = ({ children }: JSX.IntrinsicAttributes) => makeArray(children); |
@@ -10,2 +10,3 @@ import { OptionData, Data } from './parser'; | ||
| export type Executor<T> = (options: OptionData<T>, ...data: Data[]) => any; | ||
| export type CommandChildren<T = any> = Command<T> | Array<CommandChildren<T>>; | ||
| export interface CommandMeta<T> extends Option { | ||
@@ -15,3 +16,3 @@ name?: string; | ||
| options?: Options<T>; | ||
| children?: Command<T>[]; | ||
| children?: CommandChildren<T>; | ||
| executor?: Executor<T>; | ||
@@ -18,0 +19,0 @@ } |
+4
-7
@@ -33,4 +33,3 @@ "use strict"; | ||
| const { data, options } = (0, parser_1.parseArguments)(args); | ||
| if (!command.parent && | ||
| (!command.name || !command.version || !command.description)) { | ||
| if (!command.parent && (!command.name || !command.version || !command.description)) { | ||
| const [_, commandPath] = process.argv; | ||
@@ -41,3 +40,4 @@ const { meta } = (0, node_toolkit_1.packageOf)(commandPath); | ||
| if ((command.version || (command.version = meta.version || ''))) | ||
| command.options.version = PresetOption.version; | ||
| command.options.version = | ||
| PresetOption.version; | ||
| } | ||
@@ -104,6 +104,3 @@ command.execute(options, ...data); | ||
| const result = [ | ||
| [this.parameters, this.name, ...this.getParentNames()] | ||
| .reverse() | ||
| .join(' ') | ||
| .trim(), | ||
| [this.parameters, this.name, ...this.getParentNames()].reverse().join(' ').trim(), | ||
| this.description, | ||
@@ -110,0 +107,0 @@ 'Options:\n' + this.toOptionString() |
+0
-11
@@ -1,12 +0,1 @@ | ||
| import { Command, CommandMeta } from './Command'; | ||
| declare global { | ||
| namespace JSX { | ||
| interface IntrinsicElements<T> { | ||
| [key: string]: Command<T>; | ||
| } | ||
| } | ||
| } | ||
| export declare function createCommand<T>(component: { | ||
| new (meta: CommandMeta<T>): Command<T>; | ||
| }, props: CommandMeta<T>, ...children: Command<T>[]): Command<T>; | ||
| export declare function createTable(list: string[][]): string; |
+1
-6
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.createTable = exports.createCommand = void 0; | ||
| function createCommand(component, props, ...children) { | ||
| return new component(Object.assign(Object.assign({}, props), { children })); | ||
| } | ||
| exports.createCommand = createCommand; | ||
| exports.createTable = createTable; | ||
| function createTable(list) { | ||
@@ -22,2 +18,1 @@ const counts = list.reduce((counts, row) => { | ||
| } | ||
| exports.createTable = createTable; |
+3
-6
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.parseArguments = exports.parseData = void 0; | ||
| exports.parseData = parseData; | ||
| exports.parseArguments = parseArguments; | ||
| function parseData(raw) { | ||
@@ -9,8 +10,5 @@ try { | ||
| catch (_a) { | ||
| return raw.includes(',') | ||
| ? raw.split(',').map(parseData) | ||
| : raw; | ||
| return raw.includes(',') ? raw.split(',').map(parseData) : raw; | ||
| } | ||
| } | ||
| exports.parseData = parseData; | ||
| function parseArguments(list) { | ||
@@ -42,2 +40,1 @@ const options = {}, data = []; | ||
| } | ||
| exports.parseArguments = parseArguments; |
+21
-18
| { | ||
| "name": "commander-jsx", | ||
| "version": "0.6.9", | ||
| "version": "0.7.0", | ||
| "license": "LGPL-3.0", | ||
@@ -24,24 +24,26 @@ "description": "Command-line Arguments Parser with JSX support", | ||
| "main": "dist/index.js", | ||
| "module": "source/index.ts", | ||
| "source": "source/dist/index.ts", | ||
| "types": "dist/index.d.ts", | ||
| "dependencies": { | ||
| "@tech_query/node-toolkit": "^1.2.1", | ||
| "tslib": "^2.6.1" | ||
| "@tech_query/node-toolkit": "2.0.0-alpha.0", | ||
| "tslib": "^2.8.1", | ||
| "web-utility": "^4.6.3" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/jest": "^29.5.3", | ||
| "@types/node": "^18.17.5", | ||
| "husky": "^8.0.3", | ||
| "jest": "^29.6.2", | ||
| "lint-staged": "^14.0.0", | ||
| "open-cli": "^7.2.0", | ||
| "prettier": "^3.0.2", | ||
| "ts-jest": "^29.1.1", | ||
| "ts-node": "^10.9.1", | ||
| "typedoc": "^0.24.8", | ||
| "typedoc-plugin-mdn-links": "^3.0.3", | ||
| "typescript": "~5.1.6" | ||
| "@types/jest": "^29.5.14", | ||
| "@types/node": "^22.18.13", | ||
| "husky": "^9.1.7", | ||
| "jest": "^29.7.0", | ||
| "lint-staged": "^16.2.6", | ||
| "open-cli": "^8.0.0", | ||
| "prettier": "^3.6.2", | ||
| "ts-jest": "^29.4.5", | ||
| "ts-node": "^10.9.2", | ||
| "typedoc": "^0.28.14", | ||
| "typedoc-plugin-mdn-links": "^5.0.10", | ||
| "typescript": "~5.9.3" | ||
| }, | ||
| "prettier": { | ||
| "tabWidth": 4, | ||
| "printWidth": 100, | ||
| "trailingComma": "none", | ||
@@ -58,5 +60,6 @@ "arrowParens": "avoid", | ||
| "scripts": { | ||
| "prepare": "husky install", | ||
| "prepare": "husky", | ||
| "test": "lint-staged && jest", | ||
| "build": "rm -rf dist/ docs/ && tsc && typedoc source/", | ||
| "parcel": "tsc && mv dist/jsx-runtime.* . && cp jsx-runtime.js jsx-dev-runtime.js && mv dist/dist/* dist/ && rm -rf dist/dist", | ||
| "build": "rm -rf dist/ docs/ && npm run parcel && typedoc", | ||
| "start": "typedoc source/ && open-cli docs/index.html", | ||
@@ -63,0 +66,0 @@ "prepublishOnly": "npm test && npm run build" |
+22
-6
@@ -10,8 +10,15 @@ # CommanderJSX | ||
| ## Versions | ||
| | SemVer | status | JSX | | ||
| | :-----: | :----------: | :--------------: | | ||
| | `>=0.7` | ✅developing | import source | | ||
| | `<0.7` | ❌deprecated | factory function | | ||
| ## Example | ||
| `index.tsx` | ||
| ### `index.tsx` | ||
| ```JavaScript | ||
| import { Command, createCommand } from 'commander-jsx'; | ||
| import { Command } from 'commander-jsx'; | ||
@@ -48,3 +55,3 @@ Command.execute( | ||
| `tsconfig.json` | ||
| ### `tsconfig.json` | ||
@@ -56,4 +63,4 @@ ```JSON | ||
| "moduleResolution": "Node", | ||
| "jsx": "react", | ||
| "jsxFactory": "createCommand", | ||
| "jsx": "react-jsx", | ||
| "jsxImportSource": "commander-jsx", | ||
| "target": "ES2017", | ||
@@ -65,4 +72,6 @@ "outDir": "dist/" | ||
| Then, run `git help` in your terminal, it'll outputs: | ||
| ## Usage | ||
| Run `git help` in your terminal, it'll outputs: | ||
| ```text | ||
@@ -82,2 +91,9 @@ git [command] [options] | ||
| ## Typical cases | ||
| 1. https://github.com/idea2app/Git-utility | ||
| 2. https://github.com/TechQuery/Web-fetch | ||
| 3. https://github.com/TechQuery/KoApache | ||
| 4. https://github.com/TechQuery/fs-match | ||
| [1]: https://facebook.github.io/jsx/ | ||
@@ -84,0 +100,0 @@ [2]: https://libraries.io/npm/commander-jsx |
+4
-3
@@ -6,4 +6,4 @@ { | ||
| "esModuleInterop": true, | ||
| "jsx": "react", | ||
| "jsxFactory": "createCommand", | ||
| "jsx": "react-jsx", | ||
| "jsxImportSource": "../source", | ||
| "strict": true, | ||
@@ -16,3 +16,3 @@ "target": "ES2017", | ||
| }, | ||
| "include": ["source/*.ts"], | ||
| "include": ["source/**/*.ts"], | ||
| "typedocOptions": { | ||
@@ -23,4 +23,5 @@ "name": "CommanderJSX", | ||
| "readme": "./ReadMe.md", | ||
| "entryPoints": ["source/dist/index.ts", "source/jsx-runtime.ts"], | ||
| "plugin": ["typedoc-plugin-mdn-links"] | ||
| } | ||
| } |
| import { packageOf } from '@tech_query/node-toolkit'; | ||
| import { OptionData, Data, parseArguments } from './parser'; | ||
| import { createTable } from './creator'; | ||
| export interface Option { | ||
| shortcut?: string; | ||
| parameters?: string; | ||
| pattern?: RegExp; | ||
| description?: string; | ||
| } | ||
| export type Options<T> = Record<keyof T, Option>; | ||
| export type Executor<T> = (options: OptionData<T>, ...data: Data[]) => any; | ||
| export interface CommandMeta<T> extends Option { | ||
| name?: string; | ||
| version?: string; | ||
| options?: Options<T>; | ||
| children?: Command<T>[]; | ||
| executor?: Executor<T>; | ||
| } | ||
| const PresetOption = { | ||
| version: { shortcut: 'v', description: 'show Version number' }, | ||
| help: { shortcut: 'h', description: 'show Help information' } | ||
| }; | ||
| export class Command<T = any> implements CommandMeta<T> { | ||
| name = ''; | ||
| parameters = ''; | ||
| description = ''; | ||
| version = ''; | ||
| options: Options<T> = {} as Options<T>; | ||
| parent?: Command<T>; | ||
| children: Command<T>[] = []; | ||
| executor?: Executor<T>; | ||
| constructor(meta: CommandMeta<T>) { | ||
| Object.assign(this, meta); | ||
| for (const command of this.children) command.parent = this; | ||
| this.addPreset(); | ||
| } | ||
| static nameOf(meta: Record<string, any>, commandPath: string) { | ||
| if (typeof meta.bin != 'object') return meta.name as string; | ||
| commandPath = commandPath.replaceAll('\\', '/'); | ||
| return Object.entries(meta.bin as Record<string, string>).find( | ||
| ([name, path]) => commandPath.endsWith(path.replace(/^\.\//, '')) | ||
| )?.[0]; | ||
| } | ||
| static execute<T>(command: Command<T>, args: string[]) { | ||
| const { data, options } = parseArguments<T>(args); | ||
| if ( | ||
| !command.parent && | ||
| (!command.name || !command.version || !command.description) | ||
| ) { | ||
| const [_, commandPath] = process.argv; | ||
| const { meta } = packageOf(commandPath); | ||
| command.name ||= this.nameOf(meta, commandPath) || ''; | ||
| command.description ||= meta.description || ''; | ||
| if ((command.version ||= meta.version || '')) | ||
| ( | ||
| command.options as Record<keyof typeof PresetOption, any> | ||
| ).version = PresetOption.version; | ||
| } | ||
| command.execute(options as OptionData<T>, ...data); | ||
| } | ||
| execute(options: OptionData<T>, ...data: Data[]): void { | ||
| const command = this.children.find(({ name }) => name === data[0]); | ||
| if (command instanceof Command) | ||
| return command.execute(options, ...data.slice(1)); | ||
| options = this.checkPattern(this.replaceShortcut(options)); | ||
| if ('version' in options) { | ||
| console.log(this.version); | ||
| } else if ('help' in options) { | ||
| this.showHelp(); | ||
| } else if (this.executor instanceof Function) { | ||
| this.executor(options, ...data); | ||
| } else { | ||
| throw ReferenceError(`Unknown "${data[0]}" command`); | ||
| } | ||
| } | ||
| protected replaceShortcut(options: OptionData<T>) { | ||
| const map: Record<string, keyof T> = Object.fromEntries( | ||
| Object.entries<Option>(this.options).map( | ||
| ([key, { shortcut }]) => [shortcut, key] | ||
| ) | ||
| ), | ||
| data: OptionData<T> = {} as OptionData<T>; | ||
| for (const key in options) | ||
| if (key in map) data[map[key]] = options[key]; | ||
| else data[key] = options[key]; | ||
| return data; | ||
| } | ||
| protected checkPattern(options: OptionData<T>) { | ||
| for (const key in options) { | ||
| const option = this.options[key]; | ||
| if (!option) throw ReferenceError(`Unknown "${key}" option`); | ||
| if (option.pattern?.test(options[key] + '') === false) | ||
| throw SyntaxError( | ||
| `"${key}=${options[key]}" doesn't match ${option.pattern}` | ||
| ); | ||
| } | ||
| return options; | ||
| } | ||
| protected addPreset() { | ||
| const { name, options, children } = this, | ||
| { version, help } = PresetOption; | ||
| Object.assign(this.options, { | ||
| ...(this.version ? { version } : {}), | ||
| help, | ||
| ...options | ||
| }); | ||
| if (name !== 'help' && !children.find(({ name }) => name === 'help')) | ||
| children.push( | ||
| new Command({ | ||
| name: 'help', | ||
| ...help, | ||
| parameters: '[command]', | ||
| executor: (_, command) => this.showHelp(command as string) | ||
| }) | ||
| ); | ||
| } | ||
| showHelp(command?: string) { | ||
| if (!command) return console.log(this + ''); | ||
| const that = this.children.find(({ name }) => name === command); | ||
| if (that) console.log(that + ''); | ||
| } | ||
| *getParentNames() { | ||
| let that: Command | undefined = this; | ||
| while ((that = that.parent)) yield that.name; | ||
| } | ||
| toString() { | ||
| const result = [ | ||
| [this.parameters, this.name, ...this.getParentNames()] | ||
| .reverse() | ||
| .join(' ') | ||
| .trim(), | ||
| this.description, | ||
| 'Options:\n' + this.toOptionString() | ||
| ]; | ||
| if (this.children[0]) | ||
| result.push('Commands:\n' + this.toChildrenString()); | ||
| return result.join('\n\n'); | ||
| } | ||
| toOptionString() { | ||
| return createTable( | ||
| Object.entries<Option>(this.options as Options<T>) | ||
| .sort(([A], [B]) => A.localeCompare(B)) | ||
| .map( | ||
| ([ | ||
| name, | ||
| { shortcut, parameters = '', description = '' } | ||
| ]) => [ | ||
| '', | ||
| `${shortcut ? `-${shortcut}, ` : ''}${ | ||
| name[1] ? '-' : '' | ||
| }-${name}`, | ||
| parameters, | ||
| description | ||
| ] | ||
| ) | ||
| ); | ||
| } | ||
| toChildrenString() { | ||
| return createTable( | ||
| this.children | ||
| .sort(({ name: A }, { name: B }) => A.localeCompare(B)) | ||
| .map(({ name, parameters = '', description = '' }) => [ | ||
| '', | ||
| name, | ||
| parameters, | ||
| description | ||
| ]) | ||
| ); | ||
| } | ||
| } |
| import { Command, CommandMeta } from './Command'; | ||
| declare global { | ||
| namespace JSX { | ||
| interface IntrinsicElements<T> { | ||
| [key: string]: Command<T>; | ||
| } | ||
| } | ||
| } | ||
| export function createCommand<T>( | ||
| component: { new (meta: CommandMeta<T>): Command<T> }, | ||
| props: CommandMeta<T>, | ||
| ...children: Command<T>[] | ||
| ) { | ||
| return new component({ ...props, children }); | ||
| } | ||
| export function createTable(list: string[][]) { | ||
| const counts = list.reduce((counts, row) => { | ||
| for (const [index, { length }] of row.entries()) | ||
| if (index + 1 < row.length) | ||
| counts[index] = Math.max(counts[index], length); | ||
| return counts; | ||
| }, Array(list[0].length).fill(0)); | ||
| return list | ||
| .map(row => | ||
| row | ||
| .map((column, index) => column.padEnd(counts[index], ' ')) | ||
| .join(' ') | ||
| .trimEnd() | ||
| ) | ||
| .join('\n'); | ||
| } |
| export * from './parser'; | ||
| export * from './Command'; | ||
| export * from './creator'; |
| export type PrimitiveData = string | number | boolean | null; | ||
| export type Data = | ||
| | PrimitiveData | ||
| | Record<string, PrimitiveData> | ||
| | PrimitiveData[]; | ||
| export function parseData(raw: string): Data { | ||
| try { | ||
| return JSON.parse((raw = raw.trim())); | ||
| } catch { | ||
| return raw.includes(',') | ||
| ? (raw.split(',').map(parseData) as PrimitiveData[]) | ||
| : raw; | ||
| } | ||
| } | ||
| export type OptionData<T> = Record<keyof T, Data>; | ||
| export function parseArguments<T>(list: string[]) { | ||
| const options: OptionData<T> = {} as OptionData<T>, | ||
| data: Data[] = []; | ||
| let lastKey = ''; | ||
| for (const item of list) | ||
| if (item.startsWith('--')) { | ||
| lastKey = item.slice(2); | ||
| options[lastKey as keyof T] = true; | ||
| } else if (item.startsWith('-')) { | ||
| if (item[2]) | ||
| for (const k of item.slice(1)) options[k as keyof T] = true; | ||
| else { | ||
| lastKey = item[1]; | ||
| options[lastKey as keyof T] = true; | ||
| } | ||
| } else if (lastKey) { | ||
| options[lastKey as keyof T] = parseData(item); | ||
| lastKey = ''; | ||
| } else { | ||
| data.push(parseData(item)); | ||
| } | ||
| return { options, data }; | ||
| } |
AI-detected possible typosquat
Supply chain riskAI has identified this package as a potential typosquat of a more popular package. This suggests that the package may be intentionally mimicking another package's name, description, or other metadata.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
26474
16.06%19
26.67%581
9.83%98
19.51%3
50%2
Infinity%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
Updated