clerc 0.6.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -12,7 +12,15 @@ class SingleCommandError extends Error {
12
12
  super("Single command mode enabled.");
13
13
  }
14
14
  }
15
+ class SingleCommandAliasError extends Error {
16
+ constructor() {
17
+ super("Single command cannot have alias.");
18
+ }
19
+ }
15
20
  class CommandExistsError extends Error {
21
+ constructor(name) {
22
+ super(`Command "${name}" exists.`);
23
+ }
16
24
  }
17
25
  class CommonCommandExistsError extends Error {
18
26
  constructor() {
@@ -34,32 +42,44 @@ class SubcommandExistsError extends Error {
34
42
  super(`Command "${name}" cannot exist with its subcommand`);
35
43
  }
36
44
  }
37
-
38
- const resolveFlagAlias = (_command) => Object.entries((_command == null ? void 0 : _command.flags) || {}).reduce((acc, [name, command]) => {
39
- if (command.alias) {
40
- const item = utils.mustArray(command.alias).map(utils.kebabCase);
41
- acc[utils.kebabCase(name)] = item;
45
+ class MultipleCommandsMatchedError extends Error {
46
+ constructor(name) {
47
+ super(`Multiple commands matched: ${name}`);
42
48
  }
43
- return acc;
44
- }, {});
45
- const resolveFlagDefault = (_command) => Object.entries((_command == null ? void 0 : _command.flags) || {}).reduce((acc, [name, command]) => {
46
- const item = command.default;
47
- if (item) {
48
- acc[name] = item;
49
+ }
50
+ class CommandNameConflictError extends Error {
51
+ constructor(n1, n2) {
52
+ super(`Command name ${n1} conflicts with ${n2}. Maybe caused by alias.`);
49
53
  }
50
- return acc;
51
- }, {});
54
+ }
55
+
52
56
  function resolveCommand(commands, name) {
53
57
  if (name === SingleCommand) {
54
58
  return commands[SingleCommand];
55
59
  }
56
- const nameArr = Array.isArray(name) ? name : name.split(" ");
57
- const nameString = nameArr.join(" ");
58
- const possibleCommands = Object.values(commands).filter(
59
- (c) => utils.arrayStartsWith(nameArr, c.name.split(" ")) || utils.mustArray(c.alias || []).map(String).includes(nameString)
60
- );
60
+ const nameArr = utils.mustArray(name);
61
+ const commandsMap = /* @__PURE__ */ new Map();
62
+ for (const command of Object.values(commands)) {
63
+ if (command.alias) {
64
+ const aliases = utils.mustArray(command.alias);
65
+ for (const alias of aliases) {
66
+ if (alias in commands) {
67
+ throw new CommandNameConflictError(commands[alias].name, command.name);
68
+ }
69
+ commandsMap.set(alias.split(" "), command);
70
+ }
71
+ }
72
+ commandsMap.set(command.name.split(" "), command);
73
+ }
74
+ const possibleCommands = [];
75
+ commandsMap.forEach((v, k) => {
76
+ if (utils.arrayStartsWith(nameArr, k)) {
77
+ possibleCommands.push(v);
78
+ }
79
+ });
61
80
  if (possibleCommands.length > 1) {
62
- throw new Error(`Multiple commands found with name "${nameString}"`);
81
+ const matchedCommandNames = possibleCommands.map((c) => c.name).join(", ");
82
+ throw new MultipleCommandsMatchedError(matchedCommandNames);
63
83
  }
64
84
  return possibleCommands[0];
65
85
  }
@@ -71,7 +91,10 @@ function resolveSubcommandsByParent(commands, parent, depth = Infinity) {
71
91
  });
72
92
  }
73
93
  const resolveRootCommands = (commands) => resolveSubcommandsByParent(commands, "", 1);
74
- function resolveParametersBeforeFlag(argv) {
94
+ function resolveParametersBeforeFlag(argv, isSingleCommand) {
95
+ if (isSingleCommand) {
96
+ return [];
97
+ }
75
98
  const parameters = [];
76
99
  for (const arg of argv) {
77
100
  if (arg.startsWith("-")) {
@@ -92,6 +115,65 @@ function compose(inspectors) {
92
115
  };
93
116
  }
94
117
 
118
+ const { stringify } = JSON;
119
+ function parseParameters(parameters) {
120
+ const parsedParameters = [];
121
+ let hasOptional;
122
+ let hasSpread;
123
+ for (const parameter of parameters) {
124
+ if (hasSpread) {
125
+ throw new Error(`Invalid parameter: Spread parameter ${stringify(hasSpread)} must be last`);
126
+ }
127
+ const firstCharacter = parameter[0];
128
+ const lastCharacter = parameter[parameter.length - 1];
129
+ let required;
130
+ if (firstCharacter === "<" && lastCharacter === ">") {
131
+ required = true;
132
+ if (hasOptional) {
133
+ throw new Error(`Invalid parameter: Required parameter ${stringify(parameter)} cannot come after optional parameter ${stringify(hasOptional)}`);
134
+ }
135
+ }
136
+ if (firstCharacter === "[" && lastCharacter === "]") {
137
+ required = false;
138
+ hasOptional = parameter;
139
+ }
140
+ if (required === void 0) {
141
+ throw new Error(`Invalid parameter: ${stringify(parameter)}. Must be wrapped in <> (required parameter) or [] (optional parameter)`);
142
+ }
143
+ let name = parameter.slice(1, -1);
144
+ const spread = name.slice(-3) === "...";
145
+ if (spread) {
146
+ hasSpread = parameter;
147
+ name = name.slice(0, -3);
148
+ }
149
+ parsedParameters.push({
150
+ name,
151
+ required,
152
+ spread
153
+ });
154
+ }
155
+ return parsedParameters;
156
+ }
157
+ function mapParametersToArguments(mapping, parameters, cliArguments) {
158
+ for (let i = 0; i < parameters.length; i += 1) {
159
+ const { name, required, spread } = parameters[i];
160
+ const camelCaseName = utils.camelCase(name);
161
+ if (camelCaseName in mapping) {
162
+ throw new Error(`Invalid parameter: ${stringify(name)} is used more than once.`);
163
+ }
164
+ const value = spread ? cliArguments.slice(i) : cliArguments[i];
165
+ if (spread) {
166
+ i = parameters.length;
167
+ }
168
+ if (required && (!value || spread && value.length === 0)) {
169
+ console.error(`Error: Missing required parameter ${stringify(name)}
170
+ `);
171
+ return process.exit(1);
172
+ }
173
+ mapping[camelCaseName] = value;
174
+ }
175
+ }
176
+
95
177
  var __accessCheck = (obj, member, msg) => {
96
178
  if (!member.has(obj))
97
179
  throw TypeError("Cannot " + msg);
@@ -153,12 +235,14 @@ const _Clerc = class {
153
235
  __privateSet(this, _version, version);
154
236
  return this;
155
237
  }
156
- command(name, description, options = {}) {
238
+ command(nameOrCommand, description, options) {
239
+ const checkIsCommandObject = (nameOrCommand2) => !(typeof nameOrCommand2 === "string" || nameOrCommand2 === SingleCommand);
240
+ const isCommandObject = checkIsCommandObject(nameOrCommand);
241
+ const name = !isCommandObject ? nameOrCommand : nameOrCommand.name;
157
242
  if (__privateGet(this, _commands)[name]) {
158
243
  if (name === SingleCommand) {
159
- throw new CommandExistsError("Single command already exists");
244
+ throw new CommandExistsError("SingleCommand");
160
245
  }
161
- throw new CommandExistsError(`Command "${name === SingleCommand ? "[SingleCommand]" : name}" already exists`);
162
246
  }
163
247
  if (__privateGet(this, _isSingleCommand, isSingleCommand_get)) {
164
248
  throw new SingleCommandError();
@@ -166,6 +250,9 @@ const _Clerc = class {
166
250
  if (name === SingleCommand && __privateGet(this, _hasCommands, hasCommands_get)) {
167
251
  throw new CommonCommandExistsError();
168
252
  }
253
+ if (name === SingleCommand && (isCommandObject ? nameOrCommand : options).alias) {
254
+ throw new SingleCommandAliasError();
255
+ }
169
256
  if (name !== SingleCommand) {
170
257
  const splitedName = name.split(" ");
171
258
  const existedCommandNames = Object.keys(__privateGet(this, _commands)).filter((name2) => typeof name2 === "string").map((name2) => name2.split(" "));
@@ -176,7 +263,10 @@ const _Clerc = class {
176
263
  throw new SubcommandExistsError(splitedName.join(" "));
177
264
  }
178
265
  }
179
- __privateGet(this, _commands)[name] = { name, description, ...options };
266
+ __privateGet(this, _commands)[name] = !isCommandObject ? { name, description, ...options } : nameOrCommand;
267
+ if (isCommandObject && nameOrCommand.handler) {
268
+ this.on(nameOrCommand.name, nameOrCommand.handler);
269
+ }
180
270
  return this;
181
271
  }
182
272
  on(name, handler) {
@@ -191,23 +281,48 @@ const _Clerc = class {
191
281
  return this;
192
282
  }
193
283
  parse(argv = resolveArgv()) {
194
- const name = resolveParametersBeforeFlag(argv);
284
+ const name = resolveParametersBeforeFlag(argv, __privateGet(this, _isSingleCommand, isSingleCommand_get));
195
285
  const stringName = name.join(" ");
196
286
  const getCommand = () => __privateGet(this, _isSingleCommand, isSingleCommand_get) ? __privateGet(this, _commands)[SingleCommand] : resolveCommand(__privateGet(this, _commands), name);
197
287
  const getContext = () => {
198
288
  const command = getCommand();
199
289
  const isCommandResolved = !!command;
200
- const parsedWithType = typeFlag.typeFlag((command == null ? void 0 : command.flags) || {}, [...argv]);
201
- const { _: args, flags } = parsedWithType;
202
- const parameters = __privateGet(this, _isSingleCommand, isSingleCommand_get) || !isCommandResolved ? args : args.slice(command.name.split(" ").length);
290
+ const parsed = typeFlag.typeFlag((command == null ? void 0 : command.flags) || {}, [...argv]);
291
+ const { _: args, flags } = parsed;
292
+ let parameters = __privateGet(this, _isSingleCommand, isSingleCommand_get) || !isCommandResolved ? args : args.slice(command.name.split(" ").length);
293
+ let commandParameters = (command == null ? void 0 : command.parameters) || [];
294
+ const hasEof = commandParameters.indexOf("--");
295
+ const eofParameters = commandParameters.slice(hasEof + 1) || [];
296
+ const mapping = /* @__PURE__ */ Object.create(null);
297
+ if (hasEof > -1 && eofParameters.length > 0) {
298
+ commandParameters = commandParameters.slice(0, hasEof);
299
+ const eofArguments = parsed._["--"];
300
+ parameters = parameters.slice(0, -eofArguments.length || void 0);
301
+ mapParametersToArguments(
302
+ mapping,
303
+ parseParameters(commandParameters),
304
+ parameters
305
+ );
306
+ mapParametersToArguments(
307
+ mapping,
308
+ parseParameters(eofParameters),
309
+ eofArguments
310
+ );
311
+ } else {
312
+ mapParametersToArguments(
313
+ mapping,
314
+ parseParameters(commandParameters),
315
+ parameters
316
+ );
317
+ }
203
318
  const context = {
204
319
  name: command == null ? void 0 : command.name,
205
320
  resolved: isCommandResolved,
206
321
  isSingleCommand: __privateGet(this, _isSingleCommand, isSingleCommand_get),
207
- raw: parsedWithType,
208
- parameters,
322
+ raw: parsed,
323
+ parameters: mapping,
209
324
  flags,
210
- unknownFlags: parsedWithType.unknownFlags,
325
+ unknownFlags: parsed.unknownFlags,
211
326
  cli: this
212
327
  };
213
328
  return context;
@@ -221,8 +336,8 @@ const _Clerc = class {
221
336
  __privateGet(this, _commandEmitter).emit(command.name, handlerContext);
222
337
  };
223
338
  const inspectors = [...__privateGet(this, _inspectors), emitHandler];
224
- const inspector = compose(inspectors);
225
- inspector(getContext);
339
+ const callInspector = compose(inspectors);
340
+ callInspector(getContext);
226
341
  return this;
227
342
  }
228
343
  };
@@ -245,23 +360,30 @@ hasCommands_get = function() {
245
360
  const definePlugin = (p) => p;
246
361
  const defineHandler = (_cli, _key, handler) => handler;
247
362
  const defineInspector = (_cli, inspector) => inspector;
363
+ const defineCommand = (c) => c;
364
+ const defineCommandWithHandler = (c) => c;
248
365
 
249
366
  exports.Clerc = Clerc;
250
367
  exports.CommandExistsError = CommandExistsError;
368
+ exports.CommandNameConflictError = CommandNameConflictError;
251
369
  exports.CommonCommandExistsError = CommonCommandExistsError;
370
+ exports.MultipleCommandsMatchedError = MultipleCommandsMatchedError;
252
371
  exports.NoSuchCommandError = NoSuchCommandError;
253
372
  exports.ParentCommandExistsError = ParentCommandExistsError;
254
373
  exports.SingleCommand = SingleCommand;
374
+ exports.SingleCommandAliasError = SingleCommandAliasError;
255
375
  exports.SingleCommandError = SingleCommandError;
256
376
  exports.SubcommandExistsError = SubcommandExistsError;
257
377
  exports.compose = compose;
378
+ exports.defineCommand = defineCommand;
379
+ exports.defineCommandWithHandler = defineCommandWithHandler;
258
380
  exports.defineHandler = defineHandler;
259
381
  exports.defineInspector = defineInspector;
260
382
  exports.definePlugin = definePlugin;
383
+ exports.mapParametersToArguments = mapParametersToArguments;
384
+ exports.parseParameters = parseParameters;
261
385
  exports.resolveArgv = resolveArgv;
262
386
  exports.resolveCommand = resolveCommand;
263
- exports.resolveFlagAlias = resolveFlagAlias;
264
- exports.resolveFlagDefault = resolveFlagDefault;
265
387
  exports.resolveParametersBeforeFlag = resolveParametersBeforeFlag;
266
388
  exports.resolveRootCommands = resolveRootCommands;
267
389
  exports.resolveSubcommandsByParent = resolveSubcommandsByParent;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _clerc_utils from '@clerc/utils';
2
- import { MaybeArray, Dict, LiteralUnion } from '@clerc/utils';
2
+ import { MaybeArray, Dict, CamelCase, LiteralUnion } from '@clerc/utils';
3
3
 
4
4
  declare const DOUBLE_DASH = "--";
5
5
  type TypeFunction<ReturnType = any> = (value: any) => ReturnType;
@@ -76,16 +76,24 @@ type FlagOptions = FlagSchema & {
76
76
  type Flag = FlagOptions & {
77
77
  name: string;
78
78
  };
79
- interface CommandOptions<A extends MaybeArray<string> = MaybeArray<string>, F extends Dict<FlagOptions> = Dict<FlagOptions>> {
79
+ declare interface CommandCustomProperties {
80
+ }
81
+ interface CommandOptions<P extends string[] = string[], A extends MaybeArray<string> = MaybeArray<string>, F extends Dict<FlagOptions> = Dict<FlagOptions>> extends CommandCustomProperties {
80
82
  alias?: A;
83
+ parameters?: P;
81
84
  flags?: F;
82
85
  examples?: [string, string][];
83
86
  notes?: string[];
84
87
  }
85
- type Command<N extends string | SingleCommandType = string, D extends string = string, Options extends CommandOptions = CommandOptions> = Options & {
88
+ type Command<N extends string | SingleCommandType = string, O extends CommandOptions = CommandOptions> = O & {
86
89
  name: N;
87
- description: D;
90
+ description: string;
88
91
  };
92
+ type CommandWithHandler<N extends string | SingleCommandType = string, O extends CommandOptions = CommandOptions> = Command<N, O> & {
93
+ handler?: HandlerInCommand<Record<N, Command<N, O>>, N>;
94
+ };
95
+ type StripBrackets<Parameter extends string> = (Parameter extends `<${infer ParameterName}>` | `[${infer ParameterName}]` ? (ParameterName extends `${infer SpreadName}...` ? SpreadName : ParameterName) : never);
96
+ type ParameterType<Parameter extends string> = (Parameter extends `<${infer _ParameterName}...>` | `[${infer _ParameterName}...]` ? string[] : Parameter extends `<${infer _ParameterName}>` ? string : Parameter extends `[${infer _ParameterName}]` ? string | undefined : never);
89
97
  type CommandRecord = Dict<Command> & {
90
98
  [SingleCommand]?: Command;
91
99
  };
@@ -94,17 +102,23 @@ type MakeEventMap<T extends CommandRecord> = {
94
102
  };
95
103
  type PossibleInputKind = string | number | boolean | Dict<any>;
96
104
  type NonNullableFlag<T extends Dict<FlagOptions> | undefined> = T extends undefined ? {} : NonNullable<T>;
105
+ type NonNullableParameters<T extends string[] | undefined> = T extends undefined ? [] : NonNullable<T>;
97
106
  interface HandlerContext<C extends CommandRecord = CommandRecord, N extends keyof C = keyof C> {
98
107
  name?: N;
99
108
  resolved: boolean;
100
109
  isSingleCommand: boolean;
101
- raw: ParsedFlags;
102
- parameters: PossibleInputKind[];
110
+ raw: TypeFlag<NonNullableFlag<C[N]["flags"]>>;
111
+ parameters: {
112
+ [Parameter in [...NonNullableParameters<C[N]["parameters"]>][number] as CamelCase<StripBrackets<Parameter>>]: ParameterType<Parameter>;
113
+ };
103
114
  unknownFlags: ParsedFlags["unknownFlags"];
104
115
  flags: TypeFlag<NonNullableFlag<C[N]["flags"]>>["flags"];
105
116
  cli: Clerc<C>;
106
117
  }
107
118
  type Handler<C extends CommandRecord = CommandRecord, K extends keyof C = keyof C> = (ctx: HandlerContext<C, K>) => void;
119
+ type HandlerInCommand<C extends CommandRecord = CommandRecord, K extends keyof C = keyof C> = (ctx: HandlerContext<C, K> & {
120
+ name: K;
121
+ }) => void;
108
122
  type FallbackType<T, U> = {} extends T ? U : T;
109
123
  type InspectorContext<C extends CommandRecord = CommandRecord> = HandlerContext<C> & {
110
124
  flags: FallbackType<TypeFlag<NonNullableFlag<C[keyof C]["flags"]>>["flags"], Dict<any>>;
@@ -196,7 +210,8 @@ declare class Clerc<C extends CommandRecord = {}> {
196
210
  * })
197
211
  * ```
198
212
  */
199
- command<N extends string | SingleCommandType, D extends string, O extends CommandOptions>(name: N, description: D, options?: O): this & Clerc<C & Record<N, Command<N, D, O>>>;
213
+ command<N extends string | SingleCommandType, O extends CommandOptions<[...P], A, F>, P extends string[] = string[], A extends MaybeArray<string> = MaybeArray<string>, F extends Dict<FlagOptions> = Dict<FlagOptions>>(c: CommandWithHandler<N, O & CommandOptions<[...P], A, F>>): this & Clerc<C & Record<N, Command<N, O>>>;
214
+ command<N extends string | SingleCommandType, P extends string[], O extends CommandOptions<[...P]>>(name: N, description: string, options?: O & CommandOptions<[...P]>): this & Clerc<C & Record<N, Command<N, O>>>;
200
215
  /**
201
216
  * Register a handler
202
217
  * @param name
@@ -252,12 +267,18 @@ declare class Clerc<C extends CommandRecord = {}> {
252
267
 
253
268
  declare const definePlugin: <T extends Clerc<{}>, U extends Clerc<{}>>(p: Plugin<T, U>) => Plugin<T, U>;
254
269
  declare const defineHandler: <C extends Clerc<{}>, K extends keyof C["_commands"]>(_cli: C, _key: K, handler: Handler<C["_commands"], K>) => Handler<C["_commands"], K>;
255
- declare const defineInspector: <C extends Clerc<{}>>(_cli: C, inspector: Inspector<C["_commands"]>) => Inspector<C["_commands"]>;
270
+ declare const defineInspector: <C extends Clerc<{}>>(_cli: C, inspector: Inspector<C["_commands"]>) => Inspector<C["_commands"]>;
271
+ declare const defineCommand: <N extends string | typeof SingleCommand, P extends string[], O extends CommandOptions<[...P], MaybeArray<string>, Dict<FlagOptions>>>(c: Command<N, O>) => Command<N, O>;
272
+ declare const defineCommandWithHandler: <N extends string | typeof SingleCommand, O extends CommandOptions<[...P], A, F>, P extends string[] = string[], A extends MaybeArray<string> = MaybeArray<string>, F extends Dict<FlagOptions> = Dict<FlagOptions>>(c: CommandWithHandler<N, O & CommandOptions<[...P], A, F>>) => CommandWithHandler<N, O & CommandOptions<[...P], A, F>>;
256
273
 
257
274
  declare class SingleCommandError extends Error {
258
275
  constructor();
259
276
  }
277
+ declare class SingleCommandAliasError extends Error {
278
+ constructor();
279
+ }
260
280
  declare class CommandExistsError extends Error {
281
+ constructor(name: string);
261
282
  }
262
283
  declare class CommonCommandExistsError extends Error {
263
284
  constructor();
@@ -270,15 +291,27 @@ declare class ParentCommandExistsError extends Error {
270
291
  }
271
292
  declare class SubcommandExistsError extends Error {
272
293
  constructor(name: string);
294
+ }
295
+ declare class MultipleCommandsMatchedError extends Error {
296
+ constructor(name: string);
297
+ }
298
+ declare class CommandNameConflictError extends Error {
299
+ constructor(n1: string, n2: string);
273
300
  }
274
301
 
275
- declare const resolveFlagAlias: (_command: Command) => Dict<string[]>;
276
- declare const resolveFlagDefault: (_command: Command) => Dict<any>;
277
302
  declare function resolveCommand(commands: CommandRecord, name: string | string[] | SingleCommandType): Command | undefined;
278
- declare function resolveSubcommandsByParent(commands: CommandRecord, parent: string | string[], depth?: number): Command<string, string, CommandOptions<_clerc_utils.MaybeArray<string>, Dict<FlagOptions>>>[];
279
- declare const resolveRootCommands: (commands: CommandRecord) => Command<string, string, CommandOptions<_clerc_utils.MaybeArray<string>, Dict<FlagOptions>>>[];
280
- declare function resolveParametersBeforeFlag(argv: string[]): string[];
303
+ declare function resolveSubcommandsByParent(commands: CommandRecord, parent: string | string[], depth?: number): Command<string, CommandOptions<string[], _clerc_utils.MaybeArray<string>, _clerc_utils.Dict<FlagOptions>>>[];
304
+ declare const resolveRootCommands: (commands: CommandRecord) => Command<string, CommandOptions<string[], _clerc_utils.MaybeArray<string>, _clerc_utils.Dict<FlagOptions>>>[];
305
+ declare function resolveParametersBeforeFlag(argv: string[], isSingleCommand: boolean): string[];
281
306
  declare const resolveArgv: () => string[];
282
307
  declare function compose(inspectors: Inspector[]): (getCtx: () => InspectorContext) => void;
283
308
 
284
- export { Clerc, Command, CommandExistsError, CommandOptions, CommandRecord, CommonCommandExistsError, FallbackType, Flag, FlagOptions, Handler, HandlerContext, Inspector, InspectorContext, MakeEventMap, NoSuchCommandError, ParentCommandExistsError, Plugin, PossibleInputKind, SingleCommand, SingleCommandError, SingleCommandType, SubcommandExistsError, compose, defineHandler, defineInspector, definePlugin, resolveArgv, resolveCommand, resolveFlagAlias, resolveFlagDefault, resolveParametersBeforeFlag, resolveRootCommands, resolveSubcommandsByParent };
309
+ interface ParsedParameter {
310
+ name: string;
311
+ required: boolean;
312
+ spread: boolean;
313
+ }
314
+ declare function parseParameters(parameters: string[]): ParsedParameter[];
315
+ declare function mapParametersToArguments(mapping: Record<string, string | string[]>, parameters: ParsedParameter[], cliArguments: string[]): undefined;
316
+
317
+ export { Clerc, Command, CommandCustomProperties, CommandExistsError, CommandNameConflictError, CommandOptions, CommandRecord, CommandWithHandler, CommonCommandExistsError, FallbackType, Flag, FlagOptions, Handler, HandlerContext, HandlerInCommand, Inspector, InspectorContext, MakeEventMap, MultipleCommandsMatchedError, NoSuchCommandError, ParentCommandExistsError, Plugin, PossibleInputKind, SingleCommand, SingleCommandAliasError, SingleCommandError, SingleCommandType, SubcommandExistsError, compose, defineCommand, defineCommandWithHandler, defineHandler, defineInspector, definePlugin, mapParametersToArguments, parseParameters, resolveArgv, resolveCommand, resolveParametersBeforeFlag, resolveRootCommands, resolveSubcommandsByParent };
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { LiteEmit } from 'lite-emit';
2
2
  import { typeFlag } from 'type-flag';
3
- import { mustArray, kebabCase, arrayStartsWith } from '@clerc/utils';
3
+ import { mustArray, arrayStartsWith, camelCase } from '@clerc/utils';
4
4
  import { isNode, isDeno } from 'is-platform';
5
5
 
6
6
  class SingleCommandError extends Error {
@@ -8,7 +8,15 @@ class SingleCommandError extends Error {
8
8
  super("Single command mode enabled.");
9
9
  }
10
10
  }
11
+ class SingleCommandAliasError extends Error {
12
+ constructor() {
13
+ super("Single command cannot have alias.");
14
+ }
15
+ }
11
16
  class CommandExistsError extends Error {
17
+ constructor(name) {
18
+ super(`Command "${name}" exists.`);
19
+ }
12
20
  }
13
21
  class CommonCommandExistsError extends Error {
14
22
  constructor() {
@@ -30,32 +38,44 @@ class SubcommandExistsError extends Error {
30
38
  super(`Command "${name}" cannot exist with its subcommand`);
31
39
  }
32
40
  }
41
+ class MultipleCommandsMatchedError extends Error {
42
+ constructor(name) {
43
+ super(`Multiple commands matched: ${name}`);
44
+ }
45
+ }
46
+ class CommandNameConflictError extends Error {
47
+ constructor(n1, n2) {
48
+ super(`Command name ${n1} conflicts with ${n2}. Maybe caused by alias.`);
49
+ }
50
+ }
33
51
 
34
- const resolveFlagAlias = (_command) => Object.entries((_command == null ? void 0 : _command.flags) || {}).reduce((acc, [name, command]) => {
35
- if (command.alias) {
36
- const item = mustArray(command.alias).map(kebabCase);
37
- acc[kebabCase(name)] = item;
38
- }
39
- return acc;
40
- }, {});
41
- const resolveFlagDefault = (_command) => Object.entries((_command == null ? void 0 : _command.flags) || {}).reduce((acc, [name, command]) => {
42
- const item = command.default;
43
- if (item) {
44
- acc[name] = item;
45
- }
46
- return acc;
47
- }, {});
48
52
  function resolveCommand(commands, name) {
49
53
  if (name === SingleCommand) {
50
54
  return commands[SingleCommand];
51
55
  }
52
- const nameArr = Array.isArray(name) ? name : name.split(" ");
53
- const nameString = nameArr.join(" ");
54
- const possibleCommands = Object.values(commands).filter(
55
- (c) => arrayStartsWith(nameArr, c.name.split(" ")) || mustArray(c.alias || []).map(String).includes(nameString)
56
- );
56
+ const nameArr = mustArray(name);
57
+ const commandsMap = /* @__PURE__ */ new Map();
58
+ for (const command of Object.values(commands)) {
59
+ if (command.alias) {
60
+ const aliases = mustArray(command.alias);
61
+ for (const alias of aliases) {
62
+ if (alias in commands) {
63
+ throw new CommandNameConflictError(commands[alias].name, command.name);
64
+ }
65
+ commandsMap.set(alias.split(" "), command);
66
+ }
67
+ }
68
+ commandsMap.set(command.name.split(" "), command);
69
+ }
70
+ const possibleCommands = [];
71
+ commandsMap.forEach((v, k) => {
72
+ if (arrayStartsWith(nameArr, k)) {
73
+ possibleCommands.push(v);
74
+ }
75
+ });
57
76
  if (possibleCommands.length > 1) {
58
- throw new Error(`Multiple commands found with name "${nameString}"`);
77
+ const matchedCommandNames = possibleCommands.map((c) => c.name).join(", ");
78
+ throw new MultipleCommandsMatchedError(matchedCommandNames);
59
79
  }
60
80
  return possibleCommands[0];
61
81
  }
@@ -67,7 +87,10 @@ function resolveSubcommandsByParent(commands, parent, depth = Infinity) {
67
87
  });
68
88
  }
69
89
  const resolveRootCommands = (commands) => resolveSubcommandsByParent(commands, "", 1);
70
- function resolveParametersBeforeFlag(argv) {
90
+ function resolveParametersBeforeFlag(argv, isSingleCommand) {
91
+ if (isSingleCommand) {
92
+ return [];
93
+ }
71
94
  const parameters = [];
72
95
  for (const arg of argv) {
73
96
  if (arg.startsWith("-")) {
@@ -88,6 +111,65 @@ function compose(inspectors) {
88
111
  };
89
112
  }
90
113
 
114
+ const { stringify } = JSON;
115
+ function parseParameters(parameters) {
116
+ const parsedParameters = [];
117
+ let hasOptional;
118
+ let hasSpread;
119
+ for (const parameter of parameters) {
120
+ if (hasSpread) {
121
+ throw new Error(`Invalid parameter: Spread parameter ${stringify(hasSpread)} must be last`);
122
+ }
123
+ const firstCharacter = parameter[0];
124
+ const lastCharacter = parameter[parameter.length - 1];
125
+ let required;
126
+ if (firstCharacter === "<" && lastCharacter === ">") {
127
+ required = true;
128
+ if (hasOptional) {
129
+ throw new Error(`Invalid parameter: Required parameter ${stringify(parameter)} cannot come after optional parameter ${stringify(hasOptional)}`);
130
+ }
131
+ }
132
+ if (firstCharacter === "[" && lastCharacter === "]") {
133
+ required = false;
134
+ hasOptional = parameter;
135
+ }
136
+ if (required === void 0) {
137
+ throw new Error(`Invalid parameter: ${stringify(parameter)}. Must be wrapped in <> (required parameter) or [] (optional parameter)`);
138
+ }
139
+ let name = parameter.slice(1, -1);
140
+ const spread = name.slice(-3) === "...";
141
+ if (spread) {
142
+ hasSpread = parameter;
143
+ name = name.slice(0, -3);
144
+ }
145
+ parsedParameters.push({
146
+ name,
147
+ required,
148
+ spread
149
+ });
150
+ }
151
+ return parsedParameters;
152
+ }
153
+ function mapParametersToArguments(mapping, parameters, cliArguments) {
154
+ for (let i = 0; i < parameters.length; i += 1) {
155
+ const { name, required, spread } = parameters[i];
156
+ const camelCaseName = camelCase(name);
157
+ if (camelCaseName in mapping) {
158
+ throw new Error(`Invalid parameter: ${stringify(name)} is used more than once.`);
159
+ }
160
+ const value = spread ? cliArguments.slice(i) : cliArguments[i];
161
+ if (spread) {
162
+ i = parameters.length;
163
+ }
164
+ if (required && (!value || spread && value.length === 0)) {
165
+ console.error(`Error: Missing required parameter ${stringify(name)}
166
+ `);
167
+ return process.exit(1);
168
+ }
169
+ mapping[camelCaseName] = value;
170
+ }
171
+ }
172
+
91
173
  var __accessCheck = (obj, member, msg) => {
92
174
  if (!member.has(obj))
93
175
  throw TypeError("Cannot " + msg);
@@ -149,12 +231,14 @@ const _Clerc = class {
149
231
  __privateSet(this, _version, version);
150
232
  return this;
151
233
  }
152
- command(name, description, options = {}) {
234
+ command(nameOrCommand, description, options) {
235
+ const checkIsCommandObject = (nameOrCommand2) => !(typeof nameOrCommand2 === "string" || nameOrCommand2 === SingleCommand);
236
+ const isCommandObject = checkIsCommandObject(nameOrCommand);
237
+ const name = !isCommandObject ? nameOrCommand : nameOrCommand.name;
153
238
  if (__privateGet(this, _commands)[name]) {
154
239
  if (name === SingleCommand) {
155
- throw new CommandExistsError("Single command already exists");
240
+ throw new CommandExistsError("SingleCommand");
156
241
  }
157
- throw new CommandExistsError(`Command "${name === SingleCommand ? "[SingleCommand]" : name}" already exists`);
158
242
  }
159
243
  if (__privateGet(this, _isSingleCommand, isSingleCommand_get)) {
160
244
  throw new SingleCommandError();
@@ -162,6 +246,9 @@ const _Clerc = class {
162
246
  if (name === SingleCommand && __privateGet(this, _hasCommands, hasCommands_get)) {
163
247
  throw new CommonCommandExistsError();
164
248
  }
249
+ if (name === SingleCommand && (isCommandObject ? nameOrCommand : options).alias) {
250
+ throw new SingleCommandAliasError();
251
+ }
165
252
  if (name !== SingleCommand) {
166
253
  const splitedName = name.split(" ");
167
254
  const existedCommandNames = Object.keys(__privateGet(this, _commands)).filter((name2) => typeof name2 === "string").map((name2) => name2.split(" "));
@@ -172,7 +259,10 @@ const _Clerc = class {
172
259
  throw new SubcommandExistsError(splitedName.join(" "));
173
260
  }
174
261
  }
175
- __privateGet(this, _commands)[name] = { name, description, ...options };
262
+ __privateGet(this, _commands)[name] = !isCommandObject ? { name, description, ...options } : nameOrCommand;
263
+ if (isCommandObject && nameOrCommand.handler) {
264
+ this.on(nameOrCommand.name, nameOrCommand.handler);
265
+ }
176
266
  return this;
177
267
  }
178
268
  on(name, handler) {
@@ -187,23 +277,48 @@ const _Clerc = class {
187
277
  return this;
188
278
  }
189
279
  parse(argv = resolveArgv()) {
190
- const name = resolveParametersBeforeFlag(argv);
280
+ const name = resolveParametersBeforeFlag(argv, __privateGet(this, _isSingleCommand, isSingleCommand_get));
191
281
  const stringName = name.join(" ");
192
282
  const getCommand = () => __privateGet(this, _isSingleCommand, isSingleCommand_get) ? __privateGet(this, _commands)[SingleCommand] : resolveCommand(__privateGet(this, _commands), name);
193
283
  const getContext = () => {
194
284
  const command = getCommand();
195
285
  const isCommandResolved = !!command;
196
- const parsedWithType = typeFlag((command == null ? void 0 : command.flags) || {}, [...argv]);
197
- const { _: args, flags } = parsedWithType;
198
- const parameters = __privateGet(this, _isSingleCommand, isSingleCommand_get) || !isCommandResolved ? args : args.slice(command.name.split(" ").length);
286
+ const parsed = typeFlag((command == null ? void 0 : command.flags) || {}, [...argv]);
287
+ const { _: args, flags } = parsed;
288
+ let parameters = __privateGet(this, _isSingleCommand, isSingleCommand_get) || !isCommandResolved ? args : args.slice(command.name.split(" ").length);
289
+ let commandParameters = (command == null ? void 0 : command.parameters) || [];
290
+ const hasEof = commandParameters.indexOf("--");
291
+ const eofParameters = commandParameters.slice(hasEof + 1) || [];
292
+ const mapping = /* @__PURE__ */ Object.create(null);
293
+ if (hasEof > -1 && eofParameters.length > 0) {
294
+ commandParameters = commandParameters.slice(0, hasEof);
295
+ const eofArguments = parsed._["--"];
296
+ parameters = parameters.slice(0, -eofArguments.length || void 0);
297
+ mapParametersToArguments(
298
+ mapping,
299
+ parseParameters(commandParameters),
300
+ parameters
301
+ );
302
+ mapParametersToArguments(
303
+ mapping,
304
+ parseParameters(eofParameters),
305
+ eofArguments
306
+ );
307
+ } else {
308
+ mapParametersToArguments(
309
+ mapping,
310
+ parseParameters(commandParameters),
311
+ parameters
312
+ );
313
+ }
199
314
  const context = {
200
315
  name: command == null ? void 0 : command.name,
201
316
  resolved: isCommandResolved,
202
317
  isSingleCommand: __privateGet(this, _isSingleCommand, isSingleCommand_get),
203
- raw: parsedWithType,
204
- parameters,
318
+ raw: parsed,
319
+ parameters: mapping,
205
320
  flags,
206
- unknownFlags: parsedWithType.unknownFlags,
321
+ unknownFlags: parsed.unknownFlags,
207
322
  cli: this
208
323
  };
209
324
  return context;
@@ -217,8 +332,8 @@ const _Clerc = class {
217
332
  __privateGet(this, _commandEmitter).emit(command.name, handlerContext);
218
333
  };
219
334
  const inspectors = [...__privateGet(this, _inspectors), emitHandler];
220
- const inspector = compose(inspectors);
221
- inspector(getContext);
335
+ const callInspector = compose(inspectors);
336
+ callInspector(getContext);
222
337
  return this;
223
338
  }
224
339
  };
@@ -241,5 +356,7 @@ hasCommands_get = function() {
241
356
  const definePlugin = (p) => p;
242
357
  const defineHandler = (_cli, _key, handler) => handler;
243
358
  const defineInspector = (_cli, inspector) => inspector;
359
+ const defineCommand = (c) => c;
360
+ const defineCommandWithHandler = (c) => c;
244
361
 
245
- export { Clerc, CommandExistsError, CommonCommandExistsError, NoSuchCommandError, ParentCommandExistsError, SingleCommand, SingleCommandError, SubcommandExistsError, compose, defineHandler, defineInspector, definePlugin, resolveArgv, resolveCommand, resolveFlagAlias, resolveFlagDefault, resolveParametersBeforeFlag, resolveRootCommands, resolveSubcommandsByParent };
362
+ export { Clerc, CommandExistsError, CommandNameConflictError, CommonCommandExistsError, MultipleCommandsMatchedError, NoSuchCommandError, ParentCommandExistsError, SingleCommand, SingleCommandAliasError, SingleCommandError, SubcommandExistsError, compose, defineCommand, defineCommandWithHandler, defineHandler, defineInspector, definePlugin, mapParametersToArguments, parseParameters, resolveArgv, resolveCommand, resolveParametersBeforeFlag, resolveRootCommands, resolveSubcommandsByParent };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clerc",
3
- "version": "0.6.1",
3
+ "version": "0.8.0",
4
4
  "author": "Ray <nn_201312@163.com> (https://github.com/so1ve)",
5
5
  "description": "Clerc is a simple and easy-to-use cli framework.",
6
6
  "keywords": [
@@ -39,7 +39,7 @@
39
39
  "access": "public"
40
40
  },
41
41
  "dependencies": {
42
- "@clerc/utils": "0.6.1",
42
+ "@clerc/utils": "npm:@clerc/toolkit@0.8.0",
43
43
  "is-platform": "^0.2.0",
44
44
  "lite-emit": "^1.4.0",
45
45
  "type-flag": "^3.0.0"