Skip to content

Commit 296e1ff

Browse files
committed
✨ feat(core)!: disallow users to add commands in inspectors; remove limit of commands must before flags
1 parent 9cc5c89 commit 296e1ff

File tree

2 files changed

+53
-66
lines changed

2 files changed

+53
-66
lines changed

packages/core/src/cli.ts

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import {
4040
isValidName,
4141
resolveArgv,
4242
resolveCommand,
43-
resolveParametersBeforeFlag,
43+
stripFlags,
4444
} from "./utils";
4545
import { mapParametersToArguments, parseParameters } from "./parameters";
4646
import { locales } from "./locales";
@@ -76,30 +76,30 @@ export class Clerc<C extends Commands = {}> {
7676
},
7777
};
7878

79-
private constructor (name?: string, description?: string, version?: string) {
79+
private constructor(name?: string, description?: string, version?: string) {
8080
this.#name = name || this.#name;
8181
this.#description = description || this.#description;
8282
this.#version = version || this.#version;
8383
this.#locale = detectLocale();
8484
this.#addCoreLocales();
8585
}
8686

87-
get #hasRootOrAlias () {
87+
get #hasRootOrAlias() {
8888
return this.#usedNames.has(Root);
8989
}
9090

91-
get #hasRoot () {
91+
get #hasRoot() {
9292
return Object.prototype.hasOwnProperty.call(this._commands, Root);
9393
}
9494

95-
get _name () { return this.#name; }
96-
get _description () { return this.#description; }
97-
get _version () { return this.#version; }
98-
get _inspectors () { return this.#inspectors; }
99-
get _commands () { return this.#commands; }
95+
get _name() { return this.#name; }
96+
get _description() { return this.#description; }
97+
get _version() { return this.#version; }
98+
get _inspectors() { return this.#inspectors; }
99+
get _commands() { return this.#commands; }
100100

101-
#addCoreLocales () { this.i18n.add(locales); }
102-
#otherMethodCaled () { this.#isOtherMethodCalled = true; }
101+
#addCoreLocales() { this.i18n.add(locales); }
102+
#otherMethodCaled() { this.#isOtherMethodCalled = true; }
103103

104104
/**
105105
* Create a new cli
@@ -109,7 +109,7 @@ export class Clerc<C extends Commands = {}> {
109109
* const cli = Clerc.create()
110110
* ```
111111
*/
112-
static create (name?: string, description?: string, version?: string) {
112+
static create(name?: string, description?: string, version?: string) {
113113
return new Clerc(name, description, version);
114114
}
115115

@@ -123,7 +123,7 @@ export class Clerc<C extends Commands = {}> {
123123
* .name("test")
124124
* ```
125125
*/
126-
name (name: string) {
126+
name(name: string) {
127127
this.#otherMethodCaled();
128128
this.#name = name;
129129
return this;
@@ -139,7 +139,7 @@ export class Clerc<C extends Commands = {}> {
139139
* .description("test cli")
140140
* ```
141141
*/
142-
description (description: string) {
142+
description(description: string) {
143143
this.#otherMethodCaled();
144144
this.#description = description;
145145
return this;
@@ -155,7 +155,7 @@ export class Clerc<C extends Commands = {}> {
155155
* .version("1.0.0")
156156
* ```
157157
*/
158-
version (version: string) {
158+
version(version: string) {
159159
this.#otherMethodCaled();
160160
this.#version = version;
161161
return this;
@@ -173,7 +173,7 @@ export class Clerc<C extends Commands = {}> {
173173
* .command(...)
174174
* ```
175175
*/
176-
locale (locale: string) {
176+
locale(locale: string) {
177177
if (this.#isOtherMethodCalled) { throw new LocaleNotCalledFirstError(this.i18n.t); }
178178
this.#locale = locale;
179179
return this;
@@ -191,7 +191,7 @@ export class Clerc<C extends Commands = {}> {
191191
* .command(...)
192192
* ```
193193
*/
194-
fallbackLocale (fallbackLocale: string) {
194+
fallbackLocale(fallbackLocale: string) {
195195
if (this.#isOtherMethodCalled) { throw new LocaleNotCalledFirstError(this.i18n.t); }
196196
this.#defaultLocale = fallbackLocale;
197197
return this;
@@ -207,7 +207,7 @@ export class Clerc<C extends Commands = {}> {
207207
* .errorHandler((err) => { console.log(err); })
208208
* ```
209209
*/
210-
errorHandler (handler: (err: any) => void) {
210+
errorHandler(handler: (err: any) => void) {
211211
this.#errorHandlers.push(handler);
212212
return this;
213213
}
@@ -246,12 +246,12 @@ export class Clerc<C extends Commands = {}> {
246246
*/
247247
command<N extends string | RootType, O extends CommandOptions<[...P], A, F>, P extends string[] = string[], A extends MaybeArray<string | RootType> = MaybeArray<string | RootType>, F extends Flags = Flags>(c: CommandWithHandler<N, O & CommandOptions<[...P], A, F>>): this & Clerc<C & Record<N, Command<N, O>>>;
248248
command<N extends string | RootType, O extends CommandOptions<[...P], A, F>, P extends string[] = string[], A extends MaybeArray<string | RootType> = MaybeArray<string | RootType>, F extends Flags = Flags>(name: N, description: string, options?: O & CommandOptions<[...P], A, F>): this & Clerc<C & Record<N, Command<N, O>>>;
249-
command (nameOrCommand: any, description?: any, options: any = {}) {
249+
command(nameOrCommand: any, description?: any, options: any = {}) {
250250
this.#callWithErrorHandling(() => this.#command(nameOrCommand, description, options));
251251
return this;
252252
}
253253

254-
#command (nameOrCommand: any, description?: any, options: any = {}) {
254+
#command(nameOrCommand: any, description?: any, options: any = {}) {
255255
this.#otherMethodCaled();
256256
const { t } = this.i18n;
257257
const checkIsCommandObject = (nameOrCommand: any): nameOrCommand is CommandWithHandler => !(typeof nameOrCommand === "string" || nameOrCommand === Root);
@@ -329,7 +329,7 @@ export class Clerc<C extends Commands = {}> {
329329
* })
330330
* ```
331331
*/
332-
inspector (inspector: Inspector) {
332+
inspector(inspector: Inspector) {
333333
this.#otherMethodCaled();
334334
this.#inspectors.push(inspector);
335335
return this;
@@ -345,7 +345,7 @@ export class Clerc<C extends Commands = {}> {
345345
* .parse(process.argv.slice(2)) // Optional
346346
* ```
347347
*/
348-
parse (optionsOrArgv: string[] | ParseOptions = resolveArgv()) {
348+
parse(optionsOrArgv: string[] | ParseOptions = resolveArgv()) {
349349
this.#otherMethodCaled();
350350
const { argv, run }: ParseOptions = Array.isArray(optionsOrArgv)
351351
? {
@@ -364,7 +364,7 @@ export class Clerc<C extends Commands = {}> {
364364
return this;
365365
}
366366

367-
#validateMeta () {
367+
#validateMeta() {
368368
const { t } = this.i18n;
369369
if (!this.#name) {
370370
throw new NameNotSetError(t);
@@ -377,7 +377,7 @@ export class Clerc<C extends Commands = {}> {
377377
}
378378
}
379379

380-
#getContext (getCommand: () => ReturnType<typeof resolveCommand>) {
380+
#getContext(getCommand: () => ReturnType<typeof resolveCommand>) {
381381
const argv = this.#argv!;
382382
const { t } = this.i18n;
383383
const [command, called] = getCommand();
@@ -433,7 +433,7 @@ export class Clerc<C extends Commands = {}> {
433433
return context;
434434
}
435435

436-
#callWithErrorHandling (fn: () => void) {
436+
#callWithErrorHandling(fn: () => void) {
437437
try {
438438
fn();
439439
} catch (e) {
@@ -445,22 +445,21 @@ export class Clerc<C extends Commands = {}> {
445445
}
446446
}
447447

448-
#runMatchedCommand () {
448+
#runMatchedCommand() {
449449
this.#otherMethodCaled();
450450
const { t } = this.i18n;
451451
const argv = this.#argv;
452452
if (!argv) {
453453
throw new Error(t("core.cliParseMustBeCalled"));
454454
}
455-
const name = resolveParametersBeforeFlag(argv);
456-
const stringName = name.join(" ");
457-
const getCommand = () => resolveCommand(this.#commands, name, t);
455+
const getCommand = () => resolveCommand(this.#commands, argv!, t);
458456
const getContext = () => this.#getContext(getCommand);
459457
const emitHandler: Inspector = {
460458
enforce: "post",
461459
fn: () => {
462460
const [command] = getCommand();
463461
const handlerContext = getContext();
462+
const stringName = stripFlags(argv).join(" ");
464463
if (!command) {
465464
if (stringName) {
466465
throw new NoSuchCommandError(stringName, t);
@@ -473,7 +472,7 @@ export class Clerc<C extends Commands = {}> {
473472
};
474473
const inspectors = [...this.#inspectors, emitHandler];
475474
const callInspector = compose(inspectors);
476-
callInspector(getContext);
475+
callInspector(getContext());
477476
}
478477

479478
/**
@@ -486,7 +485,7 @@ export class Clerc<C extends Commands = {}> {
486485
* .runMatchedCommand()
487486
* ```
488487
*/
489-
runMatchedCommand () {
488+
runMatchedCommand() {
490489
this.#callWithErrorHandling(() => this.#runMatchedCommand());
491490
process.title = this.#name;
492491
return this;

packages/core/src/utils.ts

Lines changed: 23 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import { IS_DENO, IS_ELECTRON, IS_NODE } from "is-platform";
21
import { arrayEquals, arrayStartsWith, toArray } from "@clerc/utils";
2+
import { IS_DENO, IS_ELECTRON, IS_NODE } from "is-platform";
3+
import { typeFlag } from "type-flag";
34

45
import type { RootType } from "./cli";
56
import { Root } from "./cli";
67
import type { Command, CommandAlias, CommandType, Commands, Inspector, InspectorContext, InspectorFn, InspectorObject, TranslateFn } from "./types";
78
import { CommandNameConflictError } from "./errors";
89

9-
function setCommand (commandsMap: Map<string[] | RootType, CommandAlias>, commands: Commands, command: Command, t: TranslateFn) {
10+
function setCommand(commandsMap: Map<string[] | RootType, CommandAlias>, commands: Commands, command: Command, t: TranslateFn) {
1011
if (command.alias) {
1112
const aliases = toArray(command.alias);
1213
for (const alias of aliases) {
@@ -18,7 +19,7 @@ function setCommand (commandsMap: Map<string[] | RootType, CommandAlias>, comman
1819
}
1920
}
2021

21-
export function resolveFlattenCommands (commands: Commands, t: TranslateFn) {
22+
export function resolveFlattenCommands(commands: Commands, t: TranslateFn) {
2223
const commandsMap = new Map<string[] | RootType, CommandAlias>();
2324
if (commands[Root]) {
2425
commandsMap.set(Root, commands[Root]);
@@ -31,27 +32,23 @@ export function resolveFlattenCommands (commands: Commands, t: TranslateFn) {
3132
return commandsMap;
3233
}
3334

34-
export function resolveCommand (commands: Commands, name: CommandType | string[], t: TranslateFn): [Command<string | RootType> | undefined, string[] | RootType | undefined] {
35-
if (name === Root) { return [commands[Root], Root]; }
36-
const nameArr = toArray(name) as string[];
35+
export function resolveCommand(commands: Commands, argv: string[], t: TranslateFn): [Command<string | RootType> | undefined, string[] | RootType | undefined] {
3736
const commandsMap = resolveFlattenCommands(commands, t);
38-
let current: Command | undefined;
39-
let currentName: string[] | RootType | undefined;
40-
commandsMap.forEach((v, k) => {
41-
if (k === Root) {
42-
current = commands[Root];
43-
currentName = Root;
44-
return;
45-
}
46-
if (arrayStartsWith(nameArr, k) && (!currentName || currentName === Root || k.length > currentName.length)) {
47-
current = v;
48-
currentName = k;
37+
for (const [name, command] of commandsMap.entries()) {
38+
const parsed = typeFlag(command?.flags || {}, [...argv]);
39+
const { _: args } = parsed;
40+
if (name === Root) { continue; }
41+
if (arrayStartsWith(args, name)) {
42+
return [command, name];
4943
}
50-
});
51-
return [current, currentName];
44+
}
45+
if (commandsMap.has(Root)) {
46+
return [commandsMap.get(Root)!, Root];
47+
}
48+
return [undefined, undefined];
5249
}
5350

54-
export function resolveCommandStrict (commands: Commands, name: CommandType | string[], t: TranslateFn): [Command<string | RootType> | undefined, string[] | RootType | undefined] {
51+
export function resolveCommandStrict(commands: Commands, name: CommandType | string[], t: TranslateFn): [Command<string | RootType> | undefined, string[] | RootType | undefined] {
5552
if (name === Root) { return [commands[Root], Root]; }
5653
const nameArr = toArray(name) as string[];
5754
const commandsMap = resolveFlattenCommands(commands, t);
@@ -69,7 +66,7 @@ export function resolveCommandStrict (commands: Commands, name: CommandType | st
6966
return [current, currentName];
7067
}
7168

72-
export function resolveSubcommandsByParent (commands: Commands, parent: string | string[], depth = Infinity) {
69+
export function resolveSubcommandsByParent(commands: Commands, parent: string | string[], depth = Infinity) {
7370
const parentArr = parent === ""
7471
? []
7572
: Array.isArray(parent)
@@ -84,17 +81,6 @@ export function resolveSubcommandsByParent (commands: Commands, parent: string |
8481

8582
export const resolveRootCommands = (commands: Commands) => resolveSubcommandsByParent(commands, "", 1);
8683

87-
export function resolveParametersBeforeFlag (argv: string[]) {
88-
const parameters = [];
89-
for (const arg of argv) {
90-
if (arg.startsWith("-")) {
91-
break;
92-
}
93-
parameters.push(arg);
94-
}
95-
return parameters;
96-
}
97-
9884
export const resolveArgv = (): string[] =>
9985
IS_NODE
10086
? process.argv.slice(IS_ELECTRON ? 1 : 2)
@@ -103,7 +89,7 @@ export const resolveArgv = (): string[] =>
10389
? Deno.args
10490
: [];
10591

106-
export function compose (inspectors: Inspector[]) {
92+
export function compose(inspectors: Inspector[]) {
10793
const inspectorMap = {
10894
pre: [] as InspectorFn[],
10995
normal: [] as InspectorFn[],
@@ -127,13 +113,13 @@ export function compose (inspectors: Inspector[]) {
127113
...inspectorMap.post,
128114
];
129115

130-
return (getCtx: () => InspectorContext) => {
116+
return (ctx: InspectorContext) => {
131117
const callbacks: (() => void)[] = [];
132118
let called = 0;
133119
const dispatch = (i: number) => {
134120
called = i;
135121
const inspector = mergedInspectorFns[i];
136-
const cb = inspector(getCtx(), dispatch.bind(null, i + 1));
122+
const cb = inspector(ctx, dispatch.bind(null, i + 1));
137123
if (cb) {
138124
callbacks.push(cb);
139125
}
@@ -165,3 +151,5 @@ export const formatCommandName = (name: string | string[] | RootType) => Array.i
165151
export const detectLocale = () => process.env.CLERC_LOCALE
166152
? process.env.CLERC_LOCALE
167153
: Intl.DateTimeFormat().resolvedOptions().locale;
154+
155+
export const stripFlags = (argv: string[]) => argv.filter(arg => !arg.startsWith("-"));

0 commit comments

Comments
 (0)