@flowscripter/dynamic-cli-framework 1.1.5 → 1.1.6

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/index.ts CHANGED
@@ -64,6 +64,7 @@ export { default as ShutdownServiceProvider } from "./src/service/shutdown/Shutd
64
64
 
65
65
  export { SYNTAX_HIGHLIGHTER_SERVICE_ID } from "./src/api/service/core/SyntaxHighlighterService.ts";
66
66
  export type { default as SyntaxHighlighterService } from "./src/api/service/core/SyntaxHighlighterService.ts";
67
+ export type { ColorScheme } from "./src/api/service/core/SyntaxHighlighterService.ts";
67
68
  export { default as DefaultSyntaxHighlighterService } from "./src/service/syntaxHighlighter/DefaultSyntaxHighlighterService.ts";
68
69
  export { default as SyntaxHighlighterServiceProvider } from "./src/service/syntaxHighlighter/SyntaxHighlighterServiceProvider.ts";
69
70
 
package/package.json CHANGED
@@ -18,7 +18,7 @@
18
18
  ],
19
19
  "module": "index.ts",
20
20
  "type": "module",
21
- "version": "1.1.5",
21
+ "version": "1.1.6",
22
22
  "publishConfig": {
23
23
  "access": "public"
24
24
  },
@@ -13,10 +13,25 @@ export default interface Argument {
13
13
  readonly type: ArgumentValueTypeName;
14
14
 
15
15
  /**
16
- * Optional list of values that the value must match.
16
+ * Optional list of values that the value must match. This is not supported for {@link ArgumentValueTypeName.BOOLEAN}.
17
17
  */
18
18
  readonly allowableValues?: ReadonlyArray<ArgumentSingleValueType>;
19
19
 
20
+ /**
21
+ * Optional (default is `false`) for {@link ArgumentValueTypeName.STRING} when comparing a value against {@link allowableValues}.
22
+ */
23
+ readonly isCaseInsensitive?: boolean;
24
+
25
+ /**
26
+ * Optional for {@link ArgumentValueTypeName.INTEGER} or {@link ArgumentValueTypeName.NUMBER} when validating a value.
27
+ */
28
+ readonly minValueInclusive?: number;
29
+
30
+ /**
31
+ * Optional for {@link ArgumentValueTypeName.INTEGER} or {@link ArgumentValueTypeName.NUMBER} when validating a value.
32
+ */
33
+ readonly maxValueInclusive?: number;
34
+
20
35
  /**
21
36
  * Optional configuration key to use for the argument. Must consist of alphanumeric non-whitespace uppercase
22
37
  * ASCII or `_` characters. Must not start with a digit.
@@ -48,7 +48,7 @@ export enum ArgumentValueTypeName {
48
48
  NUMBER = 1,
49
49
  INTEGER = 2,
50
50
  BOOLEAN = 3,
51
- SECRET,
51
+ SECRET = 4,
52
52
  }
53
53
 
54
54
  /**
@@ -21,7 +21,7 @@ export default interface SubCommand extends Command {
21
21
  readonly positionals: ReadonlyArray<Positional>;
22
22
 
23
23
  /**
24
- * Optional grouping topic of the command to structuring of help output.
24
+ * Optional grouping topic of the command for structuring of help output.
25
25
  */
26
26
  readonly helpTopic?: string;
27
27
 
@@ -3,7 +3,6 @@ import type Option from "../api/argument/Option.ts";
3
3
  import type SubCommand from "../api/command/SubCommand.ts";
4
4
  import type GlobalCommand from "../api/command/GlobalCommand.ts";
5
5
  import type GroupCommand from "../api/command/GroupCommand.ts";
6
- import { distance } from "fastest-levenshtein";
7
6
  import {
8
7
  type ArgumentSingleValueType,
9
8
  type ArgumentValues,
@@ -12,6 +11,7 @@ import {
12
11
  import type Context from "../api/Context.ts";
13
12
  import type { HelpSection } from "../util/helpHelper.ts";
14
13
  import {
14
+ findPossibleCommandNames,
15
15
  getCommandArgsHelpSections,
16
16
  getCommandExamplesHelpSections,
17
17
  getGlobalArgumentHelpEntry,
@@ -44,34 +44,6 @@ abstract class MultiCommandCliAbstractHelpCommand {
44
44
  this.#commandRegistry = commandRegistry;
45
45
  }
46
46
 
47
- #findPossibleCommandNames(
48
- commandName: string,
49
- groupCommands: ReadonlyArray<GroupCommand>,
50
- subCommands: ReadonlyArray<SubCommand>,
51
- ): string[] {
52
- const levenCommandArray = new Array<[number, string]>();
53
- subCommands.forEach((subCommand) => {
54
- levenCommandArray.push([
55
- distance(subCommand.name, commandName),
56
- subCommand.name,
57
- ]);
58
- });
59
- groupCommands.forEach((groupCommand) => {
60
- groupCommand.memberSubCommands.forEach((memberCommand) => {
61
- const memberName = `${groupCommand.name}:${memberCommand.name}`;
62
- levenCommandArray.push([
63
- distance(memberName, commandName),
64
- memberName,
65
- ]);
66
- });
67
- });
68
- return levenCommandArray
69
- .sort((a, b) => a[0] - b[0])
70
- .slice(0, 2)
71
- .filter((value) => value[0] < 3)
72
- .map((value) => value[1]);
73
- }
74
-
75
47
  #getGlobalCommandsHelpSection(
76
48
  context: Context,
77
49
  title: string,
@@ -273,7 +245,7 @@ abstract class MultiCommandCliAbstractHelpCommand {
273
245
  );
274
246
 
275
247
  // look for other possible matches
276
- const possibleCommandNames = this.#findPossibleCommandNames(
248
+ const possibleCommandNames = findPossibleCommandNames(
277
249
  commandName,
278
250
  groupCommands,
279
251
  subCommands,
@@ -36,6 +36,7 @@ function validateValueType(
36
36
  value: ArgumentSingleValueType,
37
37
  argumentName: string,
38
38
  isDefaultValue: boolean,
39
+ isRangeValue: boolean,
39
40
  ): void {
40
41
  switch (type) {
41
42
  case ArgumentValueTypeName.BOOLEAN:
@@ -51,7 +52,7 @@ function validateValueType(
51
52
  if ((typeof value !== "number") || !Number.isFinite(value)) {
52
53
  throw new Error(
53
54
  `Specified ${
54
- isDefaultValue ? "default" : "allowable"
55
+ isDefaultValue ? "default" : isRangeValue ? "range" : "allowable"
55
56
  } value: '${value}' for argument: '${argumentName}' should be a finite number`,
56
57
  );
57
58
  }
@@ -60,7 +61,7 @@ function validateValueType(
60
61
  if ((typeof value !== "number") || !Number.isInteger(value)) {
61
62
  throw new Error(
62
63
  `Specified ${
63
- isDefaultValue ? "default" : "allowable"
64
+ isDefaultValue ? "default" : isRangeValue ? "range" : "allowable"
64
65
  } value: '${value}' for argument: '${argumentName}' should be an integer`,
65
66
  );
66
67
  }
@@ -112,10 +113,73 @@ function validateArgument(argument: Argument, name: string): void {
112
113
  );
113
114
  }
114
115
  if (argument.allowableValues) {
116
+ if (argument.type === ArgumentValueTypeName.BOOLEAN) {
117
+ throw new Error(
118
+ `Illegal type: '${argument.type}' for argument: '${name}' with allowable values specified`,
119
+ );
120
+ }
115
121
  argument.allowableValues.forEach((value) => {
116
- validateValueType(argument.type, value, name, false);
122
+ validateValueType(argument.type, value, name, false, false);
117
123
  });
118
124
  }
125
+ if (argument.isCaseInsensitive !== undefined) {
126
+ if (
127
+ (argument.type === ArgumentValueTypeName.BOOLEAN) &&
128
+ (argument.isCaseInsensitive === false)
129
+ ) {
130
+ throw new Error(
131
+ `Illegal type: '${argument.type}' for argument: '${name}' must always be case insensitive`,
132
+ );
133
+ } else if (argument.type !== ArgumentValueTypeName.STRING) {
134
+ throw new Error(
135
+ `Illegal type: '${argument.type}' for argument: '${name}' with case insensitive specified`,
136
+ );
137
+ }
138
+ }
139
+ if (argument.minValueInclusive !== undefined) {
140
+ if (
141
+ (argument.type !== ArgumentValueTypeName.NUMBER) &&
142
+ (argument.type !== ArgumentValueTypeName.INTEGER)
143
+ ) {
144
+ throw new Error(
145
+ `Illegal type: '${argument.type}' for argument: '${name}' with min value specified`,
146
+ );
147
+ }
148
+ if (argument.allowableValues) {
149
+ throw new Error(
150
+ `Illegal combination of allowable values and min value for argument: '${name}'`,
151
+ );
152
+ }
153
+ validateValueType(
154
+ argument.type,
155
+ argument.minValueInclusive,
156
+ name,
157
+ false,
158
+ true,
159
+ );
160
+ }
161
+ if (argument.maxValueInclusive !== undefined) {
162
+ if (
163
+ (argument.type !== ArgumentValueTypeName.NUMBER) &&
164
+ (argument.type !== ArgumentValueTypeName.INTEGER)
165
+ ) {
166
+ throw new Error(
167
+ `Illegal type: '${argument.type}' for argument: '${name}' with max value specified`,
168
+ );
169
+ }
170
+ if (argument.allowableValues) {
171
+ throw new Error(
172
+ `Illegal combination of allowable values and max value for argument: '${name}'`,
173
+ );
174
+ }
175
+ validateValueType(
176
+ argument.type,
177
+ argument.maxValueInclusive,
178
+ name,
179
+ false,
180
+ true,
181
+ );
182
+ }
119
183
  }
120
184
 
121
185
  function validateSubCommandArgument(
@@ -12,6 +12,7 @@ import type {
12
12
  import getLogger from "../util/logger.ts";
13
13
  import {
14
14
  printCommandExecutionError,
15
+ printNoCommandRecognisedError,
15
16
  printNoCommandSpecifiedError,
16
17
  printParseResultError,
17
18
  printUnusedArgsWarning,
@@ -690,7 +691,15 @@ export async function run(
690
691
  }
691
692
 
692
693
  // give up if we haven't successfully parsed a non-modifier Command by now
693
- await printNoCommandSpecifiedError(context);
694
+ if (availableArgs.length > 0) {
695
+ await printNoCommandRecognisedError(
696
+ context,
697
+ commandRegistry,
698
+ availableArgs.flat(),
699
+ );
700
+ } else {
701
+ await printNoCommandSpecifiedError(context);
702
+ }
694
703
  return {
695
704
  runState: RunState.NO_COMMAND,
696
705
  };
@@ -111,15 +111,54 @@ function validatePrimitiveValue(
111
111
  }
112
112
 
113
113
  // check if the value is valid
114
- if (argument.allowableValues && !argument.allowableValues.includes(value)) {
115
- return {
116
- invalidArgument: {
117
- argument,
118
- value,
119
- reason: InvalidArgumentReason.ILLEGAL_VALUE,
120
- },
121
- };
114
+
115
+ if (argument.allowableValues) {
116
+ let searchValue = convertedValue;
117
+ let allowableValues = argument.allowableValues;
118
+
119
+ if (
120
+ (argument.type === ArgumentValueTypeName.STRING) &&
121
+ argument.isCaseInsensitive
122
+ ) {
123
+ searchValue = convertedValue.toLowerCase();
124
+ allowableValues = argument.allowableValues.map((v) =>
125
+ (v as string).toLowerCase()
126
+ );
127
+ }
128
+ if (!allowableValues.includes(searchValue)) {
129
+ return {
130
+ invalidArgument: {
131
+ argument,
132
+ value,
133
+ reason: InvalidArgumentReason.ILLEGAL_VALUE,
134
+ },
135
+ };
136
+ }
137
+ }
138
+ if (argument.minValueInclusive !== undefined) {
139
+ if (convertedValue < argument.minValueInclusive) {
140
+ return {
141
+ invalidArgument: {
142
+ argument,
143
+ value,
144
+ reason: InvalidArgumentReason.ILLEGAL_VALUE,
145
+ },
146
+ };
147
+ }
122
148
  }
149
+
150
+ if (argument.maxValueInclusive !== undefined) {
151
+ if (convertedValue > argument.maxValueInclusive) {
152
+ return {
153
+ invalidArgument: {
154
+ argument,
155
+ value,
156
+ reason: InvalidArgumentReason.ILLEGAL_VALUE,
157
+ },
158
+ };
159
+ }
160
+ }
161
+
123
162
  return { validValue: convertedValue };
124
163
  }
125
164
 
@@ -23,6 +23,7 @@ export default class LogLevelCommand implements GlobalModifierCommand {
23
23
  "WARN",
24
24
  "ERROR",
25
25
  ],
26
+ isCaseInsensitive: true,
26
27
  defaultValue: "INFO",
27
28
  configurationKey: "LOG_LEVEL",
28
29
  };
@@ -44,7 +45,7 @@ export default class LogLevelCommand implements GlobalModifierCommand {
44
45
  ): Promise<void> {
45
46
  const logLevel = argumentValue as string;
46
47
 
47
- switch (logLevel) {
48
+ switch (logLevel.toUpperCase()) {
48
49
  case "DEBUG":
49
50
  this.#printerServiceProvider.printerService!.setLevel(
50
51
  Level.DEBUG,
@@ -1,3 +1,4 @@
1
+ import { distance } from "fastest-levenshtein";
1
2
  import type GlobalCommand from "../api/command/GlobalCommand.ts";
2
3
  import type GlobalCommandArgument from "../api/argument/GlobalCommandArgument.ts";
3
4
  import {
@@ -267,6 +268,20 @@ export function getGlobalArgumentHelpEntry(
267
268
  notesItems.push("number value");
268
269
  break;
269
270
  }
271
+ if (
272
+ (argument.minValueInclusive !== undefined) ||
273
+ (argument.maxValueInclusive !== undefined)
274
+ ) {
275
+ let range = "..";
276
+
277
+ if (argument.minValueInclusive !== undefined) {
278
+ range = argument.minValueInclusive + range;
279
+ }
280
+ if (argument.maxValueInclusive !== undefined) {
281
+ range += argument.maxValueInclusive;
282
+ }
283
+ notesItems.push(range);
284
+ }
270
285
  }
271
286
 
272
287
  if (argument.defaultValue !== undefined) {
@@ -399,6 +414,21 @@ function getOptionHelpEntry(
399
414
  }
400
415
  }
401
416
  }
417
+ if (
418
+ (option.minValueInclusive !== undefined) ||
419
+ (option.maxValueInclusive !== undefined)
420
+ ) {
421
+ let range = "..";
422
+
423
+ if (option.minValueInclusive !== undefined) {
424
+ range = option.minValueInclusive + range;
425
+ }
426
+ if (option.maxValueInclusive !== undefined) {
427
+ range += option.maxValueInclusive;
428
+ }
429
+ notesItems.push(range);
430
+ }
431
+
402
432
  if (option.defaultValue !== undefined) {
403
433
  notesItems.push(
404
434
  `default: ${
@@ -480,6 +510,20 @@ function getPositionalHelpEntry(
480
510
  break;
481
511
  }
482
512
  }
513
+ if (
514
+ (positional.minValueInclusive !== undefined) ||
515
+ (positional.maxValueInclusive !== undefined)
516
+ ) {
517
+ let range = "..";
518
+
519
+ if (positional.minValueInclusive !== undefined) {
520
+ range = positional.minValueInclusive + range;
521
+ }
522
+ if (positional.maxValueInclusive !== undefined) {
523
+ range += positional.maxValueInclusive;
524
+ }
525
+ notesItems.push(range);
526
+ }
483
527
 
484
528
  if (positional.isVarargOptional) {
485
529
  notesItems.push("optional");
@@ -633,7 +677,7 @@ export function getMultiCommandAppSyntax(
633
677
  let argOptional = false;
634
678
  let argValueOptional = false;
635
679
  let multipleArg = false;
636
- const commandClauses = [];
680
+ const commandClauses: string[] = [];
637
681
  if (globalCommands.length > 0) {
638
682
  commandClauses.push(`<${globalPrefix}command>`);
639
683
  arg = globalCommands.some((
@@ -749,3 +793,31 @@ export function getMultiCommandAppSyntax(
749
793
  }
750
794
  return syntax;
751
795
  }
796
+
797
+ export function findPossibleCommandNames(
798
+ commandName: string,
799
+ groupCommands: ReadonlyArray<GroupCommand>,
800
+ subCommands: ReadonlyArray<SubCommand>,
801
+ ): string[] {
802
+ const levenCommandArray = new Array<[number, string]>();
803
+ subCommands.forEach((subCommand) => {
804
+ levenCommandArray.push([
805
+ distance(subCommand.name, commandName),
806
+ subCommand.name,
807
+ ]);
808
+ });
809
+ groupCommands.forEach((groupCommand) => {
810
+ groupCommand.memberSubCommands.forEach((memberCommand) => {
811
+ const memberName = `${groupCommand.name}:${memberCommand.name}`;
812
+ levenCommandArray.push([
813
+ distance(memberName, commandName),
814
+ memberName,
815
+ ]);
816
+ });
817
+ });
818
+ return levenCommandArray
819
+ .sort((a, b) => a[0] - b[0])
820
+ .slice(0, 2)
821
+ .filter((value) => value[0] < 3)
822
+ .map((value) => value[1]);
823
+ }
@@ -10,6 +10,8 @@ import {
10
10
  } from "../runtime/command/CommandTypeGuards.ts";
11
11
  import type Context from "../api/Context.ts";
12
12
  import { getInvalidArgumentString } from "../runtime/values/argumentValueValidation.ts";
13
+ import CommandRegistry from "../runtime/registry/CommandRegistry.ts";
14
+ import { findPossibleCommandNames } from "./helpHelper.ts";
13
15
 
14
16
  function getCommandString(
15
17
  printerService: PrinterService,
@@ -91,6 +93,45 @@ export async function printNoCommandSpecifiedError(context: Context) {
91
93
  await printerService.error("No command specified\n\n");
92
94
  }
93
95
 
96
+ export async function printNoCommandRecognisedError(
97
+ context: Context,
98
+ commandRegistry: CommandRegistry,
99
+ availableArgs: ReadonlyArray<string>,
100
+ ) {
101
+ const printerService = context.getServiceById(
102
+ PRINTER_SERVICE_ID,
103
+ ) as PrinterService;
104
+ await printerService.error("No command recognised\n\n");
105
+
106
+ if (availableArgs.length > 0) {
107
+ const groupCommands = commandRegistry.getGroupCommands();
108
+ const subCommands = commandRegistry.getSubCommands();
109
+
110
+ const allPossibleCommandNames: string[] = [];
111
+ for (const arg of availableArgs) {
112
+ // look for other possible matches
113
+ const possibleCommandNames = findPossibleCommandNames(
114
+ arg,
115
+ groupCommands,
116
+ subCommands,
117
+ );
118
+ // add the possible command names to the list if not already present
119
+ for (const possibleCommandName of possibleCommandNames) {
120
+ if (!allPossibleCommandNames.includes(possibleCommandName)) {
121
+ allPossibleCommandNames.push(possibleCommandName);
122
+ }
123
+ }
124
+ }
125
+
126
+ if (allPossibleCommandNames.length > 0) {
127
+ await printerService.print(
128
+ `Possible matches: ${allPossibleCommandNames.join(", ")}\n\n`,
129
+ Icon.INFORMATION,
130
+ );
131
+ }
132
+ }
133
+ }
134
+
94
135
  export async function printUnusedArgsWarning(
95
136
  context: Context,
96
137
  overallUnusedArgs: ReadonlyArray<string>,
@@ -90,6 +90,77 @@ describe("CommandValidator tests", () => {
90
90
  .toThrow();
91
91
  });
92
92
 
93
+ test(
94
+ "SubCommand validation fails due to boolean argument type not supporting allowable values",
95
+ () => {
96
+ const command = getSubCommand("command", [{
97
+ name: "foo",
98
+ type: ArgumentValueTypeName.BOOLEAN,
99
+ allowableValues: ["goo"],
100
+ }], []);
101
+
102
+ expect(() => new CommandValidator(getCLIConfig()).validate(command))
103
+ .toThrow();
104
+ },
105
+ );
106
+
107
+ test(
108
+ "SubCommand validation fails due to boolean argument type not supporting case sensitivity",
109
+ () => {
110
+ const command = getSubCommand("command", [{
111
+ name: "foo",
112
+ type: ArgumentValueTypeName.BOOLEAN,
113
+ isCaseInsensitive: true,
114
+ }], []);
115
+
116
+ expect(() => new CommandValidator(getCLIConfig()).validate(command))
117
+ .toThrow();
118
+ },
119
+ );
120
+
121
+ test(
122
+ "SubCommand validation fails due to number argument type not supporting case insensitivity",
123
+ () => {
124
+ const command = getSubCommand("command", [{
125
+ name: "foo",
126
+ type: ArgumentValueTypeName.NUMBER,
127
+ isCaseInsensitive: false,
128
+ }], []);
129
+
130
+ expect(() => new CommandValidator(getCLIConfig()).validate(command))
131
+ .toThrow();
132
+ },
133
+ );
134
+
135
+ test(
136
+ "SubCommand validation fails due to string argument type not supporting value range",
137
+ () => {
138
+ const command = getSubCommand("command", [{
139
+ name: "foo",
140
+ type: ArgumentValueTypeName.STRING,
141
+ minValueInclusive: 1,
142
+ }], []);
143
+
144
+ expect(() => new CommandValidator(getCLIConfig()).validate(command))
145
+ .toThrow();
146
+ },
147
+ );
148
+
149
+ test(
150
+ "SubCommand validation fails due to number argument type not supporting value range and allowable values",
151
+ () => {
152
+ const command = getSubCommand("command", [{
153
+ name: "foo",
154
+ type: ArgumentValueTypeName.NUMBER,
155
+ allowableValues: [1],
156
+ minValueInclusive: 1,
157
+ }], []);
158
+
159
+ expect(() => new CommandValidator(getCLIConfig()).validate(command))
160
+ .toThrow();
161
+ },
162
+ );
163
+
93
164
  test(
94
165
  "SubCommand validation fails due to option default value" +
95
166
  " not matching any values specified in argument valid values",
@@ -106,6 +177,38 @@ describe("CommandValidator tests", () => {
106
177
  },
107
178
  );
108
179
 
180
+ test(
181
+ "SubCommand validation fails due to option default value" +
182
+ " not matching any values specified in argument valid values with case sensitivity",
183
+ () => {
184
+ const command = getSubCommand("command", [{
185
+ name: "foo",
186
+ type: ArgumentValueTypeName.STRING,
187
+ defaultValue: "bar",
188
+ allowableValues: ["BAR"],
189
+ }], []);
190
+
191
+ expect(() => new CommandValidator(getCLIConfig()).validate(command))
192
+ .toThrow();
193
+ },
194
+ );
195
+
196
+ test(
197
+ "SubCommand validation successed with option default value" +
198
+ " matching value specified in argument valid values with case insensitivity",
199
+ () => {
200
+ const command = getSubCommand("command", [{
201
+ name: "foo",
202
+ type: ArgumentValueTypeName.STRING,
203
+ defaultValue: "bar",
204
+ allowableValues: ["BAR"],
205
+ isCaseInsensitive: true,
206
+ }], []);
207
+
208
+ new CommandValidator(getCLIConfig()).validate(command);
209
+ },
210
+ );
211
+
109
212
  test(
110
213
  "SubCommand validation fails due to the type of option defaultValue" +
111
214
  " not matching the type specified in argument type",
@@ -689,6 +689,42 @@ describe("runner tests", () => {
689
689
  expect(hasRun).toBeFalse();
690
690
  });
691
691
 
692
+ test("No command recognised", async () => {
693
+ const streamString = new StreamString();
694
+ let hasRun = false;
695
+ const option = {
696
+ name: "foo",
697
+ type: ArgumentValueTypeName.STRING,
698
+ shortAlias: "f",
699
+ };
700
+ const subCommand = getSubCommand("command", [option], []);
701
+
702
+ subCommand.execute = (): Promise<void> => {
703
+ hasRun = true;
704
+ return Promise.resolve();
705
+ };
706
+
707
+ const commandRegistry = new DefaultCommandRegistry([subCommand]);
708
+ const runResult = await run(
709
+ ["blah", "comman", "ommand"],
710
+ commandRegistry,
711
+ getServiceProviderRegistry(),
712
+ undefined,
713
+ getContext(streamString),
714
+ );
715
+
716
+ expect(runResult.runState).toEqual(RunState.NO_COMMAND);
717
+ expectStringIncludes(
718
+ streamString.getString(),
719
+ "No command recognised",
720
+ );
721
+ expectStringIncludes(
722
+ streamString.getString(),
723
+ "Possible matches: command\n",
724
+ );
725
+ expect(hasRun).toBeFalse();
726
+ });
727
+
692
728
  test("Unknown arg warning in non-global run", async () => {
693
729
  const streamString = new StreamString();
694
730
  let hasRun = false;
@@ -286,7 +286,7 @@ describe("argumentValueValidation tests", () => {
286
286
  }]);
287
287
  });
288
288
 
289
- test("Invalid option argument value", () => {
289
+ test("Invalid option argument value - not an allowable value", () => {
290
290
  let option: Option = {
291
291
  name: "foo",
292
292
  type: ArgumentValueTypeName.STRING,
@@ -312,6 +312,27 @@ describe("argumentValueValidation tests", () => {
312
312
  }]);
313
313
  });
314
314
 
315
+ test("Invalid option argument value - not within range", () => {
316
+ const option: Option = {
317
+ name: "foo",
318
+ type: ArgumentValueTypeName.INTEGER,
319
+ minValueInclusive: 1,
320
+ };
321
+ const invalidArguments: Array<InvalidArgument> = [];
322
+ expect(validateOptionValue(option, "1", invalidArguments), "1");
323
+ expect(invalidArguments).toEqual([]);
324
+
325
+ expect(
326
+ validateOptionValue(option, "0", invalidArguments),
327
+ ).toBeUndefined();
328
+ expect(invalidArguments).toEqual([{
329
+ argument: option,
330
+ name: "foo",
331
+ value: "0",
332
+ reason: InvalidArgumentReason.ILLEGAL_VALUE,
333
+ }]);
334
+ });
335
+
315
336
  test("Complex options", () => {
316
337
  const option: ComplexOption = {
317
338
  name: "foo",
@@ -45,7 +45,7 @@ describe("LogLevelCommand tests", () => {
45
45
  Level.INFO,
46
46
  );
47
47
 
48
- await logLevelCommand.execute(context, "ERROR");
48
+ await logLevelCommand.execute(context, "eRror");
49
49
 
50
50
  expect(
51
51
  printerServiceProvider.printerService!.getLevel(),