@oclif/core 4.10.6 → 4.11.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/lib/command.d.ts +2 -1
- package/lib/command.js +1 -0
- package/lib/constraints.d.ts +217 -0
- package/lib/constraints.js +638 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +2 -1
- package/lib/interfaces/parser.d.ts +34 -0
- package/lib/interfaces/pjson.d.ts +1 -0
- package/lib/parser/errors.d.ts +5 -0
- package/lib/parser/errors.js +11 -1
- package/lib/parser/index.js +1 -0
- package/lib/parser/validate.js +14 -1
- package/package.json +1 -1
package/lib/command.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Config } from './config';
|
|
|
2
2
|
import { PrettyPrintableError } from './errors';
|
|
3
3
|
import { LoadOptions } from './interfaces/config';
|
|
4
4
|
import { CommandError } from './interfaces/errors';
|
|
5
|
-
import { ArgInput, ArgOutput, ArgProps, BooleanFlagProps, Deprecation, FlagInput, FlagOutput, Arg as IArg, Flag as IFlag, Input, OptionFlagProps, ParserOutput } from './interfaces/parser';
|
|
5
|
+
import { ArgInput, ArgOutput, ArgProps, BooleanFlagProps, Constraint, Deprecation, FlagInput, FlagOutput, Arg as IArg, Flag as IFlag, Input, OptionFlagProps, ParserOutput } from './interfaces/parser';
|
|
6
6
|
import { Plugin } from './interfaces/plugin';
|
|
7
7
|
/**
|
|
8
8
|
* An abstract class which acts as the base for each command
|
|
@@ -17,6 +17,7 @@ export declare abstract class Command {
|
|
|
17
17
|
/** An order-dependent object of arguments for the command */
|
|
18
18
|
static args: ArgInput;
|
|
19
19
|
static baseFlags: FlagInput;
|
|
20
|
+
static constraints: Constraint[];
|
|
20
21
|
/**
|
|
21
22
|
* Emit deprecation warning when a command alias is used
|
|
22
23
|
*/
|
package/lib/command.js
CHANGED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { Constraint, FlagGroup, FlagOutput, MultiFlagTester, SingleFlagTester } from './interfaces/parser';
|
|
2
|
+
import { Validation } from './parser/errors';
|
|
3
|
+
/**
|
|
4
|
+
* Establish a constraint on a single flag.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* flag('foo').is.requiredAny()
|
|
8
|
+
*
|
|
9
|
+
* @param flagName The flag to constrain
|
|
10
|
+
*/
|
|
11
|
+
export declare function flag(flagName: string): ConstraintImpl;
|
|
12
|
+
/**
|
|
13
|
+
* Establish a constraint on multiple flags.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* flags('foo', 'bar').are.mutuallyExclusive()
|
|
17
|
+
*
|
|
18
|
+
* @param flagNames The flags to constrain
|
|
19
|
+
*/
|
|
20
|
+
export declare function flags(...flagNames: string[]): ConstraintImpl;
|
|
21
|
+
/**
|
|
22
|
+
* Declare a set of flags to be evaluated as one instead of separately.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* flag('foo').is.dependentOn(combinationOf('bar', 'baz'))
|
|
26
|
+
*
|
|
27
|
+
* @param flagNames Flags to be combined
|
|
28
|
+
*/
|
|
29
|
+
export declare function combinationOf(...flagNames: string[]): FlagGroup;
|
|
30
|
+
declare class ConstraintImpl implements Constraint {
|
|
31
|
+
/**
|
|
32
|
+
* No-op chain property allowing constraints to be more human-readable.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* flags('foo', 'bar').are.mutuallyExclusive()
|
|
36
|
+
*/
|
|
37
|
+
readonly are: ConstraintImpl;
|
|
38
|
+
/**
|
|
39
|
+
* No-op chain property allowing constraints to be more human-readable.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* flag('foo').is.dependentOn('bar')
|
|
43
|
+
*/
|
|
44
|
+
readonly is: ConstraintImpl;
|
|
45
|
+
private readonly constrainedFlags;
|
|
46
|
+
private constraintApplicatorFunctionHolder;
|
|
47
|
+
private topLevelCondition;
|
|
48
|
+
private underConstructionCondition;
|
|
49
|
+
constructor(constrainedFlags: string[]);
|
|
50
|
+
/**
|
|
51
|
+
* Chain property allowing constraint conditions to be combined with logical AND.
|
|
52
|
+
*
|
|
53
|
+
* By default, logical operators are evaluated left-to-right, but groups can be created with additional {@code when}/{@code unless} clauses.
|
|
54
|
+
*
|
|
55
|
+
* @example <caption> when someFn returns true AND someOtherFn returns true, using --foo requires --bar to be used as well.</caption>
|
|
56
|
+
* flag('foo').is.dependentOn('bar').when.thisIsTrue(someFn).and.thisIsTrue(someOtherFn)
|
|
57
|
+
*
|
|
58
|
+
* @example <caption>--foo requires --bar when (fnA returns true AND fnB returns true) OR fnC returns true</caption>
|
|
59
|
+
* flag('foo').is.dependentOn('bar').when.thisIsTrue(fnA).and.thisIsTrue(fnB).or.thisIsTrue(fnC)
|
|
60
|
+
*
|
|
61
|
+
* @example <caption>--foo requires --bar when fnA returns true AND (fnB returns true OR fnC returns true)</caption>
|
|
62
|
+
* flag('foo').is.dependentOn('bar').when.thisIsTrue(fnA).and.when.thisIsTrue(fnB).or.thisIsTrue(fnC)
|
|
63
|
+
*/
|
|
64
|
+
get and(): ConstraintImpl;
|
|
65
|
+
/**
|
|
66
|
+
* Chain property allowing constraint conditions to be combined with logical OR.
|
|
67
|
+
*
|
|
68
|
+
* By default, logical operators are evaluated left-to-right, but groups can be created with additional {@code when}/{@code unless} clauses.
|
|
69
|
+
*
|
|
70
|
+
* @example <caption> when EITHER someFn OR someOtherFn return true, using --foo requires --bar to be used as well.</caption>
|
|
71
|
+
* flag('foo').is.dependentOn('bar').when.thisIsTrue(someFn).or.thisIsTrue(someOtherFn)
|
|
72
|
+
*
|
|
73
|
+
* @example <caption>--foo requires --bar when (fnA returns true AND fnB returns true) OR fnC returns true</caption>
|
|
74
|
+
* flag('foo').is.dependentOn('bar').when.thisIsTrue(fnA).and.thisIsTrue(fnB).or.thisIsTrue(fnC)
|
|
75
|
+
*
|
|
76
|
+
* @example <caption>--foo requires --bar when fnA returns true AND (fnB returns true OR fnC returns true)</caption>
|
|
77
|
+
* flag('foo').is.dependentOn('bar').when.thisIsTrue(fnA).and.when.thisIsTrue(fnB).or.thisIsTrue(fnC)
|
|
78
|
+
*
|
|
79
|
+
*/
|
|
80
|
+
get or(): ConstraintImpl;
|
|
81
|
+
/**
|
|
82
|
+
* Chain property allowing constraints to be conditional upon a certain criterion NOT being met.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* flag('foo').is.dependentOn('bar').unless.thisIsTrue(someFn)
|
|
86
|
+
*/
|
|
87
|
+
get unless(): ConstraintImpl;
|
|
88
|
+
/**
|
|
89
|
+
* Chain property allowing constraints to be conditional upon a certain criterion being met.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* flag('foo').is.dependentOn('bar').when.thisIsTrue(someFn)
|
|
93
|
+
*/
|
|
94
|
+
get when(): ConstraintImpl;
|
|
95
|
+
_evaluateAgainstFlags(flags: FlagOutput): Validation;
|
|
96
|
+
/**
|
|
97
|
+
* Chain method allowing constraint to be made conditional upon EVERY established criterion being true.
|
|
98
|
+
*
|
|
99
|
+
* @example <caption>If --flagA is 'someVal' AND --flagB is 'someOtherVal', then using --foo requires using --bar too</caption>
|
|
100
|
+
* flag('foo').is.dependentOn('bar').when.allFlagCriteriaSatisfied({
|
|
101
|
+
* flagA: (v) => v === 'someVal',
|
|
102
|
+
* flagB: (v) => v !== 'someOtherVal'
|
|
103
|
+
* })
|
|
104
|
+
*
|
|
105
|
+
* @param criterionTester An object whose keys are flag names and whose values are functions that accept the
|
|
106
|
+
* value of that flag and return a boolean.
|
|
107
|
+
*/
|
|
108
|
+
allFlagCriteriaSatisfied(criterionTester: SingleFlagTester): ConstraintImpl;
|
|
109
|
+
/**
|
|
110
|
+
* Chain method allowing constraint to be made conditional upon ANY established criterion being true.
|
|
111
|
+
*
|
|
112
|
+
* @example <caption>If --flagA is 'someVal' OR --flagB is 'someOtherVal', then using --foo requires using --bar too</caption>
|
|
113
|
+
* flag('foo').is.dependentOn('bar').when.anyFlagCriterionSatisfied({
|
|
114
|
+
* flagA: (v) => v === 'someVal',
|
|
115
|
+
* flagB: (v) => v !== 'someOtherVal'
|
|
116
|
+
* })
|
|
117
|
+
*
|
|
118
|
+
* @param criterionTester An object whose keys are flag names and whose values are functions that accept the
|
|
119
|
+
* value of that flag and return a boolean.
|
|
120
|
+
*/
|
|
121
|
+
anyFlagCriterionSatisfied(criterionTester: SingleFlagTester): ConstraintImpl;
|
|
122
|
+
/**
|
|
123
|
+
* Chain method allowing the constrained flags to require the presence of at least one of the flags specified here.
|
|
124
|
+
*
|
|
125
|
+
* @example <caption>If --foo is used, then EITHER --bar OR --baz must be used as well</caption>
|
|
126
|
+
* flag('foo').is.dependentOn('bar', 'baz')
|
|
127
|
+
*
|
|
128
|
+
* @example <caption>If --foo is used, then BOTH --bar AND --baz must be used as well</caption>
|
|
129
|
+
* flag('foo').is.dependentOn(combinationOf('bar', 'baz'))
|
|
130
|
+
*
|
|
131
|
+
* @example <caption>If --foo is used, then EITHER --bar OR the combination of --baz1 and --baz2 must be used as well</caption>
|
|
132
|
+
* flag('foo').is.dependentOn('bar', combinationOf('baz1', 'baz2'))
|
|
133
|
+
*
|
|
134
|
+
* @param dependencyFlagGroups
|
|
135
|
+
*/
|
|
136
|
+
dependentOn(...dependencyFlagGroups: FlagGroup[]): ConstraintImpl;
|
|
137
|
+
/**
|
|
138
|
+
* Chain method allowing the constrained flags to be made exclusive with the flags provided here.
|
|
139
|
+
*
|
|
140
|
+
* @example <caption>Neither --foo1 nor --foo2 can be used with --bar OR --baz</caption>
|
|
141
|
+
* flags('foo1', 'foo2').are.exclusiveWith('bar', 'baz')
|
|
142
|
+
*
|
|
143
|
+
* @example <caption>Neither --foo1 nor --foo2 can be used with the combination of --bar and --baz, but may be used with --bar or --baz separately</caption>
|
|
144
|
+
* flags('foo1', 'foo2').are.exclusiveWith(combinationOf('bar', 'baz'))
|
|
145
|
+
*
|
|
146
|
+
* @example <caption>Neither --foo1 nor --foo2 can be used with --bar, or with the combination of --baz1 and --baz2</caption>
|
|
147
|
+
* flags('foo1', 'foo2').are.exclusiveWith('bar', combinationOf('baz1', 'baz2'))
|
|
148
|
+
*
|
|
149
|
+
* @param exclusionFlagGroups
|
|
150
|
+
*/
|
|
151
|
+
exclusiveWith(...exclusionFlagGroups: FlagGroup[]): ConstraintImpl;
|
|
152
|
+
/**
|
|
153
|
+
* Establish a group of flags as mutually dependent, meaning that they must either be used together or not at all.
|
|
154
|
+
*
|
|
155
|
+
* @example <caption>--foo cannot be used without --bar, and vice versa</caption>
|
|
156
|
+
* flags('foo', 'bar').are.mutuallyDependent()
|
|
157
|
+
*/
|
|
158
|
+
mutuallyDependent(): ConstraintImpl;
|
|
159
|
+
/**
|
|
160
|
+
* Establish a group of flags as mutually exclusive, meaning that at most one of them can be used simultaneously.
|
|
161
|
+
*
|
|
162
|
+
* @example <caption>--foo and --bar cannot both be used at the same time</caption>
|
|
163
|
+
* flags('foo', 'bar').are.mutuallyExclusive()
|
|
164
|
+
*/
|
|
165
|
+
mutuallyExclusive(): ConstraintImpl;
|
|
166
|
+
/**
|
|
167
|
+
* Establish a group of flags as being collectively required.
|
|
168
|
+
*
|
|
169
|
+
* @example <caption>--foo and --bar are both always required</caption>
|
|
170
|
+
* flags('foo', 'bar').are.requiredAll()
|
|
171
|
+
*/
|
|
172
|
+
requiredAll(): ConstraintImpl;
|
|
173
|
+
/**
|
|
174
|
+
* Establish that at least one of the constrained flags must always be used.
|
|
175
|
+
*
|
|
176
|
+
* @example <caption>Must use at least one of --foo, --bar, or --baz</caption>
|
|
177
|
+
* flags('foo', 'bar', 'baz').are.requiredAny()
|
|
178
|
+
*/
|
|
179
|
+
requiredAny(): ConstraintImpl;
|
|
180
|
+
/**
|
|
181
|
+
* Establish that at least N of the specified flags must be used.
|
|
182
|
+
*
|
|
183
|
+
* @example <caption>At least 2 of the 3 flags --foo, --bar, and --baz must be used</caption>
|
|
184
|
+
* flags('foo', 'bar', 'baz').are.requiredAtLeastN(2)
|
|
185
|
+
*
|
|
186
|
+
* @param n
|
|
187
|
+
*/
|
|
188
|
+
requiredAtLeastN(n: number): ConstraintImpl;
|
|
189
|
+
/**
|
|
190
|
+
* Establish that at most N of the specified flags must be used.
|
|
191
|
+
*
|
|
192
|
+
* @example <caption>No more than 2 of the 3 flags --foo, --bar, and --baz may be used</caption>
|
|
193
|
+
* flags('foo', 'bar', 'baz').are.requiredAtMostN(2)
|
|
194
|
+
*
|
|
195
|
+
* @param n
|
|
196
|
+
*/
|
|
197
|
+
requiredAtMostN(n: number): ConstraintImpl;
|
|
198
|
+
/**
|
|
199
|
+
* Establish that exactly N of the specified flags must be used.
|
|
200
|
+
*
|
|
201
|
+
* @example <caption>Exactly 2 of the 3 flags --foo, --bar, and --baz must be used</caption>
|
|
202
|
+
* flags('foo', 'bar', 'baz').are.requiredExactlyN(2)
|
|
203
|
+
*
|
|
204
|
+
* @param n
|
|
205
|
+
*/
|
|
206
|
+
requiredExactlyN(n: number): ConstraintImpl;
|
|
207
|
+
/**
|
|
208
|
+
* Chain method allowing the constraint to be made contingent on the return of a method that accepts all flags.
|
|
209
|
+
*
|
|
210
|
+
* @example <caption>--foo1 and --foo2 are required if --bar is equal to --baz</caption>
|
|
211
|
+
* flags('foo1', 'foo2').are.requiredAll().when.thisIsTrue((flags) => flags.bar === flags.baz)
|
|
212
|
+
*
|
|
213
|
+
* @param flagTester A method that accepts the flag values mapped by their name, and returns a boolean
|
|
214
|
+
*/
|
|
215
|
+
thisIsTrue(flagTester: MultiFlagTester): ConstraintImpl;
|
|
216
|
+
}
|
|
217
|
+
export {};
|
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.flag = flag;
|
|
4
|
+
exports.flags = flags;
|
|
5
|
+
exports.combinationOf = combinationOf;
|
|
6
|
+
/**
|
|
7
|
+
* Establish a constraint on a single flag.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* flag('foo').is.requiredAny()
|
|
11
|
+
*
|
|
12
|
+
* @param flagName The flag to constrain
|
|
13
|
+
*/
|
|
14
|
+
function flag(flagName) {
|
|
15
|
+
return new ConstraintImpl([flagName]);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Establish a constraint on multiple flags.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* flags('foo', 'bar').are.mutuallyExclusive()
|
|
22
|
+
*
|
|
23
|
+
* @param flagNames The flags to constrain
|
|
24
|
+
*/
|
|
25
|
+
function flags(...flagNames) {
|
|
26
|
+
return new ConstraintImpl(flagNames);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Declare a set of flags to be evaluated as one instead of separately.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* flag('foo').is.dependentOn(combinationOf('bar', 'baz'))
|
|
33
|
+
*
|
|
34
|
+
* @param flagNames Flags to be combined
|
|
35
|
+
*/
|
|
36
|
+
function combinationOf(...flagNames) {
|
|
37
|
+
return {
|
|
38
|
+
flags: flagNames,
|
|
39
|
+
type: 'all',
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
class ConstraintImpl {
|
|
43
|
+
/**
|
|
44
|
+
* No-op chain property allowing constraints to be more human-readable.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* flags('foo', 'bar').are.mutuallyExclusive()
|
|
48
|
+
*/
|
|
49
|
+
are = this;
|
|
50
|
+
/**
|
|
51
|
+
* No-op chain property allowing constraints to be more human-readable.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* flag('foo').is.dependentOn('bar')
|
|
55
|
+
*/
|
|
56
|
+
is = this;
|
|
57
|
+
constrainedFlags;
|
|
58
|
+
constraintApplicatorFunctionHolder = new ConstraintApplicatorFunctionHolder();
|
|
59
|
+
topLevelCondition;
|
|
60
|
+
underConstructionCondition;
|
|
61
|
+
constructor(constrainedFlags) {
|
|
62
|
+
this.constrainedFlags = constrainedFlags;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Chain property allowing constraint conditions to be combined with logical AND.
|
|
66
|
+
*
|
|
67
|
+
* By default, logical operators are evaluated left-to-right, but groups can be created with additional {@code when}/{@code unless} clauses.
|
|
68
|
+
*
|
|
69
|
+
* @example <caption> when someFn returns true AND someOtherFn returns true, using --foo requires --bar to be used as well.</caption>
|
|
70
|
+
* flag('foo').is.dependentOn('bar').when.thisIsTrue(someFn).and.thisIsTrue(someOtherFn)
|
|
71
|
+
*
|
|
72
|
+
* @example <caption>--foo requires --bar when (fnA returns true AND fnB returns true) OR fnC returns true</caption>
|
|
73
|
+
* flag('foo').is.dependentOn('bar').when.thisIsTrue(fnA).and.thisIsTrue(fnB).or.thisIsTrue(fnC)
|
|
74
|
+
*
|
|
75
|
+
* @example <caption>--foo requires --bar when fnA returns true AND (fnB returns true OR fnC returns true)</caption>
|
|
76
|
+
* flag('foo').is.dependentOn('bar').when.thisIsTrue(fnA).and.when.thisIsTrue(fnB).or.thisIsTrue(fnC)
|
|
77
|
+
*/
|
|
78
|
+
get and() {
|
|
79
|
+
if (this.topLevelCondition === undefined) {
|
|
80
|
+
throw new Error(`Misconfigured constraint on ${createFlagString(this.constrainedFlags)}: 'and' requires a 'when' or 'unless'.`);
|
|
81
|
+
}
|
|
82
|
+
if (this.underConstructionCondition) {
|
|
83
|
+
throw new Error(`Misconfigured constraint on ${createFlagString(this.constrainedFlags)}: 'and' cannot directly follow '${this.underConstructionCondition.getName()}'`);
|
|
84
|
+
}
|
|
85
|
+
this.topLevelCondition = new AndCondition(this.topLevelCondition);
|
|
86
|
+
this.underConstructionCondition = this.topLevelCondition;
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Chain property allowing constraint conditions to be combined with logical OR.
|
|
91
|
+
*
|
|
92
|
+
* By default, logical operators are evaluated left-to-right, but groups can be created with additional {@code when}/{@code unless} clauses.
|
|
93
|
+
*
|
|
94
|
+
* @example <caption> when EITHER someFn OR someOtherFn return true, using --foo requires --bar to be used as well.</caption>
|
|
95
|
+
* flag('foo').is.dependentOn('bar').when.thisIsTrue(someFn).or.thisIsTrue(someOtherFn)
|
|
96
|
+
*
|
|
97
|
+
* @example <caption>--foo requires --bar when (fnA returns true AND fnB returns true) OR fnC returns true</caption>
|
|
98
|
+
* flag('foo').is.dependentOn('bar').when.thisIsTrue(fnA).and.thisIsTrue(fnB).or.thisIsTrue(fnC)
|
|
99
|
+
*
|
|
100
|
+
* @example <caption>--foo requires --bar when fnA returns true AND (fnB returns true OR fnC returns true)</caption>
|
|
101
|
+
* flag('foo').is.dependentOn('bar').when.thisIsTrue(fnA).and.when.thisIsTrue(fnB).or.thisIsTrue(fnC)
|
|
102
|
+
*
|
|
103
|
+
*/
|
|
104
|
+
get or() {
|
|
105
|
+
if (this.topLevelCondition === undefined) {
|
|
106
|
+
throw new Error(`Misconfigured constraint on ${createFlagString(this.constrainedFlags)}: 'or' requires a 'when' or 'unless'.`);
|
|
107
|
+
}
|
|
108
|
+
if (this.underConstructionCondition) {
|
|
109
|
+
throw new Error(`Misconfigured constraint on ${createFlagString(this.constrainedFlags)}: 'or' cannot directly follow '${this.underConstructionCondition.getName()}'`);
|
|
110
|
+
}
|
|
111
|
+
this.topLevelCondition = new OrCondition(this.topLevelCondition);
|
|
112
|
+
this.underConstructionCondition = this.topLevelCondition;
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Chain property allowing constraints to be conditional upon a certain criterion NOT being met.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* flag('foo').is.dependentOn('bar').unless.thisIsTrue(someFn)
|
|
120
|
+
*/
|
|
121
|
+
get unless() {
|
|
122
|
+
const newUnless = new UnlessCondition();
|
|
123
|
+
if (this.topLevelCondition === undefined) {
|
|
124
|
+
this.topLevelCondition = newUnless;
|
|
125
|
+
}
|
|
126
|
+
// istanbul ignore else - All cases covered
|
|
127
|
+
if (this.underConstructionCondition === undefined) {
|
|
128
|
+
this.underConstructionCondition = newUnless;
|
|
129
|
+
}
|
|
130
|
+
else if (this.underConstructionCondition instanceof UnaryOpCondition) {
|
|
131
|
+
this.underConstructionCondition.setCondition(newUnless);
|
|
132
|
+
this.underConstructionCondition = newUnless;
|
|
133
|
+
}
|
|
134
|
+
else if (this.underConstructionCondition instanceof BinaryCondition) {
|
|
135
|
+
this.underConstructionCondition.setRight(newUnless);
|
|
136
|
+
this.underConstructionCondition = newUnless;
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
throw new TypeError('UNKNOWN CONDITION TYPE');
|
|
140
|
+
}
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Chain property allowing constraints to be conditional upon a certain criterion being met.
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* flag('foo').is.dependentOn('bar').when.thisIsTrue(someFn)
|
|
148
|
+
*/
|
|
149
|
+
get when() {
|
|
150
|
+
const newWhen = new WhenCondition();
|
|
151
|
+
if (this.topLevelCondition === undefined) {
|
|
152
|
+
this.topLevelCondition = newWhen;
|
|
153
|
+
}
|
|
154
|
+
// istanbul ignore else - All cases covered
|
|
155
|
+
if (this.underConstructionCondition === undefined) {
|
|
156
|
+
this.underConstructionCondition = newWhen;
|
|
157
|
+
}
|
|
158
|
+
else if (this.underConstructionCondition instanceof UnaryOpCondition) {
|
|
159
|
+
this.underConstructionCondition.setCondition(newWhen);
|
|
160
|
+
this.underConstructionCondition = newWhen;
|
|
161
|
+
}
|
|
162
|
+
else if (this.underConstructionCondition instanceof BinaryCondition) {
|
|
163
|
+
this.underConstructionCondition.setRight(newWhen);
|
|
164
|
+
this.underConstructionCondition = newWhen;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
throw new TypeError('UNKNOWN CONDITION TYPE');
|
|
168
|
+
}
|
|
169
|
+
return this;
|
|
170
|
+
}
|
|
171
|
+
_evaluateAgainstFlags(flags) {
|
|
172
|
+
let conditionSatisfied = false;
|
|
173
|
+
try {
|
|
174
|
+
conditionSatisfied = this.topLevelCondition ? this.topLevelCondition.isSatisfied(flags) : true;
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
return {
|
|
178
|
+
name: this.constrainedFlags.join(','),
|
|
179
|
+
reason: `Error evaluating constraint conditions on ${createFlagString(this.constrainedFlags)}: ${error.message}`,
|
|
180
|
+
status: 'failed',
|
|
181
|
+
validationFn: 'constraintCondition',
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
const applicationResult = this.constraintApplicatorFunctionHolder.applyConstraintApplicatorFunction(flags);
|
|
186
|
+
return {
|
|
187
|
+
name: this.constrainedFlags.join(','),
|
|
188
|
+
reason: applicationResult,
|
|
189
|
+
status: conditionSatisfied && applicationResult !== '' ? 'failed' : 'success',
|
|
190
|
+
validationFn: this.constraintApplicatorFunctionHolder.constraintType ?? '',
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
// istanbul ignore next
|
|
195
|
+
return {
|
|
196
|
+
name: this.constrainedFlags.join(','),
|
|
197
|
+
reason: `Error evaluating constraint on ${createFlagString(this.constrainedFlags)}: ${error.message}`,
|
|
198
|
+
status: 'failed',
|
|
199
|
+
validationFn: 'constraintApplication',
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Chain method allowing constraint to be made conditional upon EVERY established criterion being true.
|
|
205
|
+
*
|
|
206
|
+
* @example <caption>If --flagA is 'someVal' AND --flagB is 'someOtherVal', then using --foo requires using --bar too</caption>
|
|
207
|
+
* flag('foo').is.dependentOn('bar').when.allFlagCriteriaSatisfied({
|
|
208
|
+
* flagA: (v) => v === 'someVal',
|
|
209
|
+
* flagB: (v) => v !== 'someOtherVal'
|
|
210
|
+
* })
|
|
211
|
+
*
|
|
212
|
+
* @param criterionTester An object whose keys are flag names and whose values are functions that accept the
|
|
213
|
+
* value of that flag and return a boolean.
|
|
214
|
+
*/
|
|
215
|
+
allFlagCriteriaSatisfied(criterionTester) {
|
|
216
|
+
// istanbul ignore else - All cases covered
|
|
217
|
+
if (this.underConstructionCondition === undefined) {
|
|
218
|
+
throw new Error(`Misconfigured constraint condition on ${createFlagString(this.constrainedFlags)}: allFlagCriteriaSatisfied must immediately follow a when/unless/and/or`);
|
|
219
|
+
}
|
|
220
|
+
else if (this.underConstructionCondition instanceof UnaryOpCondition) {
|
|
221
|
+
this.underConstructionCondition.setCondition(new AllFlagCriteriaSatisfiedCondition(criterionTester));
|
|
222
|
+
this.underConstructionCondition = undefined;
|
|
223
|
+
}
|
|
224
|
+
else if (this.underConstructionCondition instanceof BinaryCondition) {
|
|
225
|
+
this.underConstructionCondition.setRight(new AllFlagCriteriaSatisfiedCondition(criterionTester));
|
|
226
|
+
this.underConstructionCondition = undefined;
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
throw new TypeError('UNKNOWN CONDITION TYPE');
|
|
230
|
+
}
|
|
231
|
+
return this;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Chain method allowing constraint to be made conditional upon ANY established criterion being true.
|
|
235
|
+
*
|
|
236
|
+
* @example <caption>If --flagA is 'someVal' OR --flagB is 'someOtherVal', then using --foo requires using --bar too</caption>
|
|
237
|
+
* flag('foo').is.dependentOn('bar').when.anyFlagCriterionSatisfied({
|
|
238
|
+
* flagA: (v) => v === 'someVal',
|
|
239
|
+
* flagB: (v) => v !== 'someOtherVal'
|
|
240
|
+
* })
|
|
241
|
+
*
|
|
242
|
+
* @param criterionTester An object whose keys are flag names and whose values are functions that accept the
|
|
243
|
+
* value of that flag and return a boolean.
|
|
244
|
+
*/
|
|
245
|
+
anyFlagCriterionSatisfied(criterionTester) {
|
|
246
|
+
// istanbul ignore else - All cases covered
|
|
247
|
+
if (this.underConstructionCondition === undefined) {
|
|
248
|
+
throw new Error(`Misconfigured constraint condition on ${createFlagString(this.constrainedFlags)}: anyFlagCriterionSatisfied must immediately follow a when/unless/and/or`);
|
|
249
|
+
}
|
|
250
|
+
else if (this.underConstructionCondition instanceof UnaryOpCondition) {
|
|
251
|
+
this.underConstructionCondition.setCondition(new AnyFlagCriterionSatisfiedCondition(criterionTester));
|
|
252
|
+
this.underConstructionCondition = undefined;
|
|
253
|
+
}
|
|
254
|
+
else if (this.underConstructionCondition instanceof BinaryCondition) {
|
|
255
|
+
this.underConstructionCondition.setRight(new AnyFlagCriterionSatisfiedCondition(criterionTester));
|
|
256
|
+
this.underConstructionCondition = undefined;
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
throw new TypeError('UNKNOWN CONDITION TYPE');
|
|
260
|
+
}
|
|
261
|
+
return this;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Chain method allowing the constrained flags to require the presence of at least one of the flags specified here.
|
|
265
|
+
*
|
|
266
|
+
* @example <caption>If --foo is used, then EITHER --bar OR --baz must be used as well</caption>
|
|
267
|
+
* flag('foo').is.dependentOn('bar', 'baz')
|
|
268
|
+
*
|
|
269
|
+
* @example <caption>If --foo is used, then BOTH --bar AND --baz must be used as well</caption>
|
|
270
|
+
* flag('foo').is.dependentOn(combinationOf('bar', 'baz'))
|
|
271
|
+
*
|
|
272
|
+
* @example <caption>If --foo is used, then EITHER --bar OR the combination of --baz1 and --baz2 must be used as well</caption>
|
|
273
|
+
* flag('foo').is.dependentOn('bar', combinationOf('baz1', 'baz2'))
|
|
274
|
+
*
|
|
275
|
+
* @param dependencyFlagGroups
|
|
276
|
+
*/
|
|
277
|
+
dependentOn(...dependencyFlagGroups) {
|
|
278
|
+
this.constraintApplicatorFunctionHolder.setConstraintApplicator('dependentOn', (flags) => {
|
|
279
|
+
const foundConstraintFlags = filterFlagsPresentInInput(this.constrainedFlags, flags);
|
|
280
|
+
if (foundConstraintFlags.length === 0) {
|
|
281
|
+
return '';
|
|
282
|
+
}
|
|
283
|
+
for (const dependencyFlagGroup of dependencyFlagGroups) {
|
|
284
|
+
if (typeof dependencyFlagGroup === 'string') {
|
|
285
|
+
if (dependencyFlagGroup in flags && flags[dependencyFlagGroup] !== undefined) {
|
|
286
|
+
return '';
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
const foundFlagsInDependencyGroup = filterFlagsPresentInInput(dependencyFlagGroup.flags, flags);
|
|
291
|
+
if (foundFlagsInDependencyGroup.length === dependencyFlagGroup.flags.length) {
|
|
292
|
+
return '';
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
const multipleConstrainedFlags = this.constrainedFlags.length > 1;
|
|
297
|
+
const header = multipleConstrainedFlags
|
|
298
|
+
? `Flags ${createFlagString(this.constrainedFlags)} require`
|
|
299
|
+
: `Flag ${createFlagString(this.constrainedFlags)} requires`;
|
|
300
|
+
return `${header} at least one of the following${this.topLevelCondition ? ' under current circumstances:' : ':'} ${createFlagString(dependencyFlagGroups)}.`;
|
|
301
|
+
});
|
|
302
|
+
return this;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Chain method allowing the constrained flags to be made exclusive with the flags provided here.
|
|
306
|
+
*
|
|
307
|
+
* @example <caption>Neither --foo1 nor --foo2 can be used with --bar OR --baz</caption>
|
|
308
|
+
* flags('foo1', 'foo2').are.exclusiveWith('bar', 'baz')
|
|
309
|
+
*
|
|
310
|
+
* @example <caption>Neither --foo1 nor --foo2 can be used with the combination of --bar and --baz, but may be used with --bar or --baz separately</caption>
|
|
311
|
+
* flags('foo1', 'foo2').are.exclusiveWith(combinationOf('bar', 'baz'))
|
|
312
|
+
*
|
|
313
|
+
* @example <caption>Neither --foo1 nor --foo2 can be used with --bar, or with the combination of --baz1 and --baz2</caption>
|
|
314
|
+
* flags('foo1', 'foo2').are.exclusiveWith('bar', combinationOf('baz1', 'baz2'))
|
|
315
|
+
*
|
|
316
|
+
* @param exclusionFlagGroups
|
|
317
|
+
*/
|
|
318
|
+
exclusiveWith(...exclusionFlagGroups) {
|
|
319
|
+
this.constraintApplicatorFunctionHolder.setConstraintApplicator('exclusiveWith', (flags) => {
|
|
320
|
+
const foundConstraintFlags = filterFlagsPresentInInput(this.constrainedFlags, flags);
|
|
321
|
+
if (foundConstraintFlags.length === 0) {
|
|
322
|
+
return '';
|
|
323
|
+
}
|
|
324
|
+
let exclusionGroupFound = false;
|
|
325
|
+
for (const exclusionFlagGroup of exclusionFlagGroups) {
|
|
326
|
+
if (typeof exclusionFlagGroup === 'string') {
|
|
327
|
+
if (exclusionFlagGroup in flags && flags[exclusionFlagGroup] !== undefined) {
|
|
328
|
+
exclusionGroupFound = true;
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
const foundFlagsInExclusionGroup = filterFlagsPresentInInput(exclusionFlagGroup.flags, flags);
|
|
334
|
+
if (foundFlagsInExclusionGroup.length === exclusionFlagGroup.flags.length) {
|
|
335
|
+
exclusionGroupFound = true;
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (!exclusionGroupFound) {
|
|
341
|
+
return '';
|
|
342
|
+
}
|
|
343
|
+
const multipleConstrainedFlags = this.constrainedFlags.length > 1;
|
|
344
|
+
const header = multipleConstrainedFlags ? 'Flags' : 'Flag';
|
|
345
|
+
return `${header} ${createFlagString(this.constrainedFlags)} cannot be used with any of the following${this.topLevelCondition ? ' under current circumstances:' : ':'} ${createFlagString(exclusionFlagGroups)}.`;
|
|
346
|
+
});
|
|
347
|
+
return this;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Establish a group of flags as mutually dependent, meaning that they must either be used together or not at all.
|
|
351
|
+
*
|
|
352
|
+
* @example <caption>--foo cannot be used without --bar, and vice versa</caption>
|
|
353
|
+
* flags('foo', 'bar').are.mutuallyDependent()
|
|
354
|
+
*/
|
|
355
|
+
mutuallyDependent() {
|
|
356
|
+
this.constraintApplicatorFunctionHolder.setConstraintApplicator('mutuallyDependent', (flags) => {
|
|
357
|
+
const foundFlags = filterFlagsPresentInInput(this.constrainedFlags, flags);
|
|
358
|
+
if (foundFlags.length === 0 || foundFlags.length === this.constrainedFlags.length) {
|
|
359
|
+
return '';
|
|
360
|
+
}
|
|
361
|
+
return `The following flags are mutually dependent${this.topLevelCondition ? ' under current circumstances:' : ':'} ${createFlagString(this.constrainedFlags)}. Found only ${createFlagString(foundFlags)}.`;
|
|
362
|
+
});
|
|
363
|
+
return this;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Establish a group of flags as mutually exclusive, meaning that at most one of them can be used simultaneously.
|
|
367
|
+
*
|
|
368
|
+
* @example <caption>--foo and --bar cannot both be used at the same time</caption>
|
|
369
|
+
* flags('foo', 'bar').are.mutuallyExclusive()
|
|
370
|
+
*/
|
|
371
|
+
mutuallyExclusive() {
|
|
372
|
+
this.constraintApplicatorFunctionHolder.setConstraintApplicator('mutuallyExclusive', (flags) => {
|
|
373
|
+
const foundFlags = filterFlagsPresentInInput(this.constrainedFlags, flags);
|
|
374
|
+
if (foundFlags.length <= 1) {
|
|
375
|
+
return '';
|
|
376
|
+
}
|
|
377
|
+
return `The following flags are mutually exclusive${this.topLevelCondition ? ' under current circumstances:' : ':'} ${createFlagString(this.constrainedFlags)}. Found: ${createFlagString(foundFlags)}.`;
|
|
378
|
+
});
|
|
379
|
+
return this;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Establish a group of flags as being collectively required.
|
|
383
|
+
*
|
|
384
|
+
* @example <caption>--foo and --bar are both always required</caption>
|
|
385
|
+
* flags('foo', 'bar').are.requiredAll()
|
|
386
|
+
*/
|
|
387
|
+
requiredAll() {
|
|
388
|
+
this.constraintApplicatorFunctionHolder.setConstraintApplicator('requiredAll', (flags) => {
|
|
389
|
+
const foundFlags = filterFlagsPresentInInput(this.constrainedFlags, flags);
|
|
390
|
+
if (foundFlags.length === this.constrainedFlags.length) {
|
|
391
|
+
return '';
|
|
392
|
+
}
|
|
393
|
+
const requirement = this.constrainedFlags.length > 1 ? 'These flags are required' : 'This flag is required';
|
|
394
|
+
const findings = foundFlags.length > 0 ? `Found only: ${createFlagString(foundFlags)}.` : 'Found none.';
|
|
395
|
+
return `${requirement}${this.topLevelCondition ? ' under current circumstances:' : ':'} ${createFlagString(this.constrainedFlags)}. ${findings}`;
|
|
396
|
+
});
|
|
397
|
+
return this;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Establish that at least one of the constrained flags must always be used.
|
|
401
|
+
*
|
|
402
|
+
* @example <caption>Must use at least one of --foo, --bar, or --baz</caption>
|
|
403
|
+
* flags('foo', 'bar', 'baz').are.requiredAny()
|
|
404
|
+
*/
|
|
405
|
+
requiredAny() {
|
|
406
|
+
this.constraintApplicatorFunctionHolder.setConstraintApplicator('requiredAny', (flags) => {
|
|
407
|
+
const foundFlags = filterFlagsPresentInInput(this.constrainedFlags, flags);
|
|
408
|
+
if (foundFlags.length > 0) {
|
|
409
|
+
return '';
|
|
410
|
+
}
|
|
411
|
+
return `Must provide at least one of these flags${this.topLevelCondition ? ' under current circumstances:' : ':'} ${createFlagString(this.constrainedFlags)}.`;
|
|
412
|
+
});
|
|
413
|
+
return this;
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Establish that at least N of the specified flags must be used.
|
|
417
|
+
*
|
|
418
|
+
* @example <caption>At least 2 of the 3 flags --foo, --bar, and --baz must be used</caption>
|
|
419
|
+
* flags('foo', 'bar', 'baz').are.requiredAtLeastN(2)
|
|
420
|
+
*
|
|
421
|
+
* @param n
|
|
422
|
+
*/
|
|
423
|
+
requiredAtLeastN(n) {
|
|
424
|
+
this.constraintApplicatorFunctionHolder.setConstraintApplicator(`requiredAtLeast${n}`, (flags) => required(n, 'AT_LEAST_N', this.constrainedFlags, flags, this.topLevelCondition !== undefined));
|
|
425
|
+
return this;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Establish that at most N of the specified flags must be used.
|
|
429
|
+
*
|
|
430
|
+
* @example <caption>No more than 2 of the 3 flags --foo, --bar, and --baz may be used</caption>
|
|
431
|
+
* flags('foo', 'bar', 'baz').are.requiredAtMostN(2)
|
|
432
|
+
*
|
|
433
|
+
* @param n
|
|
434
|
+
*/
|
|
435
|
+
requiredAtMostN(n) {
|
|
436
|
+
this.constraintApplicatorFunctionHolder.setConstraintApplicator(`requiredAtMost${n}`, (flags) => required(n, 'AT_MOST_N', this.constrainedFlags, flags, this.topLevelCondition !== undefined));
|
|
437
|
+
return this;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Establish that exactly N of the specified flags must be used.
|
|
441
|
+
*
|
|
442
|
+
* @example <caption>Exactly 2 of the 3 flags --foo, --bar, and --baz must be used</caption>
|
|
443
|
+
* flags('foo', 'bar', 'baz').are.requiredExactlyN(2)
|
|
444
|
+
*
|
|
445
|
+
* @param n
|
|
446
|
+
*/
|
|
447
|
+
requiredExactlyN(n) {
|
|
448
|
+
this.constraintApplicatorFunctionHolder.setConstraintApplicator(`requiredExactly${n}`, (flags) => required(n, 'EXACTLY_N', this.constrainedFlags, flags, this.topLevelCondition !== undefined));
|
|
449
|
+
return this;
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Chain method allowing the constraint to be made contingent on the return of a method that accepts all flags.
|
|
453
|
+
*
|
|
454
|
+
* @example <caption>--foo1 and --foo2 are required if --bar is equal to --baz</caption>
|
|
455
|
+
* flags('foo1', 'foo2').are.requiredAll().when.thisIsTrue((flags) => flags.bar === flags.baz)
|
|
456
|
+
*
|
|
457
|
+
* @param flagTester A method that accepts the flag values mapped by their name, and returns a boolean
|
|
458
|
+
*/
|
|
459
|
+
thisIsTrue(flagTester) {
|
|
460
|
+
// istanbul ignore else - All cases covered
|
|
461
|
+
if (this.underConstructionCondition === undefined) {
|
|
462
|
+
throw new Error(`Misconfigured constraint condition on ${createFlagString(this.constrainedFlags)}: thisIsTrue must immediately follow a when/unless/and/or`);
|
|
463
|
+
}
|
|
464
|
+
else if (this.underConstructionCondition instanceof UnaryOpCondition) {
|
|
465
|
+
this.underConstructionCondition.setCondition(new ThisIsTrueCondition(flagTester));
|
|
466
|
+
this.underConstructionCondition = undefined;
|
|
467
|
+
}
|
|
468
|
+
else if (this.underConstructionCondition instanceof BinaryCondition) {
|
|
469
|
+
this.underConstructionCondition.setRight(new ThisIsTrueCondition(flagTester));
|
|
470
|
+
this.underConstructionCondition = undefined;
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
throw new TypeError('UNKNOWN CONDITION TYPE');
|
|
474
|
+
}
|
|
475
|
+
return this;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
class ConstraintApplicatorFunctionHolder {
|
|
479
|
+
constraintType;
|
|
480
|
+
constraintApplicatorFunction;
|
|
481
|
+
applyConstraintApplicatorFunction(flags) {
|
|
482
|
+
return (this.constraintApplicatorFunction ??
|
|
483
|
+
function () {
|
|
484
|
+
return '';
|
|
485
|
+
})(flags);
|
|
486
|
+
}
|
|
487
|
+
setConstraintApplicator(constraintType, constraintFunction) {
|
|
488
|
+
if (this.constraintApplicatorFunction) {
|
|
489
|
+
// This error is meant to be seen by the developer of the command, not its user.
|
|
490
|
+
throw new Error(`Misconfigured Constraint: Cannot apply multiple kinds of constraint within one statement: ${this.constraintType}, ${constraintType}. Use multiple constraint expressions instead.`);
|
|
491
|
+
}
|
|
492
|
+
this.constraintType = constraintType;
|
|
493
|
+
this.constraintApplicatorFunction = constraintFunction;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
const Requirement = {
|
|
497
|
+
AT_LEAST_N: {
|
|
498
|
+
fn: (n, other) => other >= n,
|
|
499
|
+
label: 'at least',
|
|
500
|
+
},
|
|
501
|
+
AT_MOST_N: {
|
|
502
|
+
fn: (n, other) => other <= n,
|
|
503
|
+
label: 'at most',
|
|
504
|
+
},
|
|
505
|
+
EXACTLY_N: {
|
|
506
|
+
fn: (n, other) => other === n,
|
|
507
|
+
label: 'exactly',
|
|
508
|
+
},
|
|
509
|
+
};
|
|
510
|
+
function required(n, requirementType, soughtFlags, providedFlags, hasConditions) {
|
|
511
|
+
const foundFlags = filterFlagsPresentInInput(soughtFlags, providedFlags);
|
|
512
|
+
if (Requirement[requirementType].fn(n, foundFlags.length)) {
|
|
513
|
+
return '';
|
|
514
|
+
}
|
|
515
|
+
return `Must provide ${Requirement[requirementType].label} ${n} of the following${hasConditions ? ' under current circumstances:' : ':'} ${createFlagString(soughtFlags)}. Found ${foundFlags.length}.`;
|
|
516
|
+
}
|
|
517
|
+
function filterFlagsPresentInInput(flagsToSeek, flags) {
|
|
518
|
+
return flagsToSeek.filter((f) => f in flags && flags[f] !== undefined);
|
|
519
|
+
}
|
|
520
|
+
function createFlagString(flags) {
|
|
521
|
+
const processedGroups = flags.map((f) => {
|
|
522
|
+
if (typeof f === 'string') {
|
|
523
|
+
return `--${f}`;
|
|
524
|
+
}
|
|
525
|
+
return `combination of ${f.flags.map((f) => `--${f}`).join(' and ')}`;
|
|
526
|
+
});
|
|
527
|
+
return processedGroups.join(', ');
|
|
528
|
+
}
|
|
529
|
+
class Condition {
|
|
530
|
+
}
|
|
531
|
+
class UnaryOpCondition extends Condition {
|
|
532
|
+
condition;
|
|
533
|
+
setCondition(condition) {
|
|
534
|
+
// istanbul ignore if - should be unreachable
|
|
535
|
+
if (this.condition) {
|
|
536
|
+
throw new Error(`Duplicate conditions applied to '${this.getName()}' clause: '${this.condition.getName()}' and '${condition.getName()}'.`);
|
|
537
|
+
}
|
|
538
|
+
this.condition = condition;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
class WhenCondition extends UnaryOpCondition {
|
|
542
|
+
getName() {
|
|
543
|
+
return 'when';
|
|
544
|
+
}
|
|
545
|
+
isSatisfied(flags) {
|
|
546
|
+
if (this.condition) {
|
|
547
|
+
return this.condition.isSatisfied(flags);
|
|
548
|
+
}
|
|
549
|
+
throw new Error("'when' expression without any conditions");
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
class UnlessCondition extends UnaryOpCondition {
|
|
553
|
+
getName() {
|
|
554
|
+
return 'unless';
|
|
555
|
+
}
|
|
556
|
+
isSatisfied(flags) {
|
|
557
|
+
if (this.condition) {
|
|
558
|
+
return !this.condition.isSatisfied(flags);
|
|
559
|
+
}
|
|
560
|
+
throw new Error("'unless' expression without any conditions");
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
class AllFlagCriteriaSatisfiedCondition extends Condition {
|
|
564
|
+
tester;
|
|
565
|
+
constructor(tester) {
|
|
566
|
+
super();
|
|
567
|
+
this.tester = tester;
|
|
568
|
+
}
|
|
569
|
+
getName() {
|
|
570
|
+
return 'allFlagCriteriaSatisfied';
|
|
571
|
+
}
|
|
572
|
+
isSatisfied(flags) {
|
|
573
|
+
for (const testedFlag of Object.keys(this.tester)) {
|
|
574
|
+
if (!this.tester[testedFlag](flags[testedFlag])) {
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return true;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
class AnyFlagCriterionSatisfiedCondition extends Condition {
|
|
582
|
+
tester;
|
|
583
|
+
constructor(tester) {
|
|
584
|
+
super();
|
|
585
|
+
this.tester = tester;
|
|
586
|
+
}
|
|
587
|
+
getName() {
|
|
588
|
+
return 'anyFlagCriterionSatisfied';
|
|
589
|
+
}
|
|
590
|
+
isSatisfied(flags) {
|
|
591
|
+
for (const testedFlag of Object.keys(this.tester)) {
|
|
592
|
+
if (this.tester[testedFlag](flags[testedFlag])) {
|
|
593
|
+
return true;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return false;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
class ThisIsTrueCondition extends Condition {
|
|
600
|
+
tester;
|
|
601
|
+
constructor(tester) {
|
|
602
|
+
super();
|
|
603
|
+
this.tester = tester;
|
|
604
|
+
}
|
|
605
|
+
getName() {
|
|
606
|
+
return 'thisIsTrue';
|
|
607
|
+
}
|
|
608
|
+
isSatisfied(flags) {
|
|
609
|
+
return this.tester(flags);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
class BinaryCondition extends Condition {
|
|
613
|
+
left;
|
|
614
|
+
right;
|
|
615
|
+
constructor(left) {
|
|
616
|
+
super();
|
|
617
|
+
this.left = left;
|
|
618
|
+
}
|
|
619
|
+
setRight(right) {
|
|
620
|
+
this.right = right;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
class AndCondition extends BinaryCondition {
|
|
624
|
+
getName() {
|
|
625
|
+
return 'and';
|
|
626
|
+
}
|
|
627
|
+
isSatisfied(flags) {
|
|
628
|
+
return this.left.isSatisfied(flags) && (this.right ? this.right.isSatisfied(flags) : true);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
class OrCondition extends BinaryCondition {
|
|
632
|
+
getName() {
|
|
633
|
+
return 'or';
|
|
634
|
+
}
|
|
635
|
+
isSatisfied(flags) {
|
|
636
|
+
return this.left.isSatisfied(flags) || (this.right ? this.right.isSatisfied(flags) : false);
|
|
637
|
+
}
|
|
638
|
+
}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * as Args from './args';
|
|
2
2
|
export { Command } from './command';
|
|
3
3
|
export { Config, Plugin } from './config';
|
|
4
|
+
export * as Constraints from './constraints';
|
|
4
5
|
export * as Errors from './errors';
|
|
5
6
|
export { handle } from './errors/handle';
|
|
6
7
|
export { execute } from './execute';
|
package/lib/index.js
CHANGED
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.ux = exports.toStandardizedId = exports.toConfiguredId = exports.settings = exports.Performance = exports.Parser = exports.ModuleLoader = exports.getLogger = exports.Interfaces = exports.loadHelpClass = exports.HelpBase = exports.Help = exports.CommandHelp = exports.flush = exports.Flags = exports.execute = exports.handle = exports.Errors = exports.Plugin = exports.Config = exports.Command = exports.Args = void 0;
|
|
36
|
+
exports.ux = exports.toStandardizedId = exports.toConfiguredId = exports.settings = exports.Performance = exports.Parser = exports.ModuleLoader = exports.getLogger = exports.Interfaces = exports.loadHelpClass = exports.HelpBase = exports.Help = exports.CommandHelp = exports.flush = exports.Flags = exports.execute = exports.handle = exports.Errors = exports.Constraints = exports.Plugin = exports.Config = exports.Command = exports.Args = void 0;
|
|
37
37
|
const util_1 = require("./util/util");
|
|
38
38
|
function checkCWD() {
|
|
39
39
|
try {
|
|
@@ -69,6 +69,7 @@ Object.defineProperty(exports, "Command", { enumerable: true, get: function () {
|
|
|
69
69
|
var config_1 = require("./config");
|
|
70
70
|
Object.defineProperty(exports, "Config", { enumerable: true, get: function () { return config_1.Config; } });
|
|
71
71
|
Object.defineProperty(exports, "Plugin", { enumerable: true, get: function () { return config_1.Plugin; } });
|
|
72
|
+
exports.Constraints = __importStar(require("./constraints"));
|
|
72
73
|
exports.Errors = __importStar(require("./errors"));
|
|
73
74
|
var handle_1 = require("./errors/handle");
|
|
74
75
|
Object.defineProperty(exports, "handle", { enumerable: true, get: function () { return handle_1.handle; } });
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Command } from '../command';
|
|
2
|
+
import { Validation } from '../parser/errors';
|
|
2
3
|
import { AlphabetLowercase, AlphabetUppercase } from './alphabet';
|
|
3
4
|
export type FlagOutput = {
|
|
4
5
|
[name: string]: any;
|
|
@@ -456,6 +457,7 @@ export type Flag<T> = BooleanFlag<T> | OptionFlag<T>;
|
|
|
456
457
|
export type Input<TFlags extends FlagOutput, BFlags extends FlagOutput, AFlags extends ArgOutput> = {
|
|
457
458
|
flags?: FlagInput<TFlags>;
|
|
458
459
|
baseFlags?: FlagInput<BFlags>;
|
|
460
|
+
constraints?: Constraint[] | undefined;
|
|
459
461
|
enableJsonFlag?: true | false;
|
|
460
462
|
args?: ArgInput<AFlags>;
|
|
461
463
|
strict?: boolean | undefined;
|
|
@@ -465,6 +467,7 @@ export type Input<TFlags extends FlagOutput, BFlags extends FlagOutput, AFlags e
|
|
|
465
467
|
export type ParserInput = {
|
|
466
468
|
argv: string[];
|
|
467
469
|
flags: FlagInput<any>;
|
|
470
|
+
constraints: Constraint[] | undefined;
|
|
468
471
|
args: ArgInput<any>;
|
|
469
472
|
strict: boolean;
|
|
470
473
|
context: ParserContext | undefined;
|
|
@@ -483,4 +486,35 @@ export type ArgInput<T extends ArgOutput = {
|
|
|
483
486
|
}> = {
|
|
484
487
|
[P in keyof T]: Arg<T[P]>;
|
|
485
488
|
};
|
|
489
|
+
export type SingleFlagTester = {
|
|
490
|
+
[key: string]: (val: any) => boolean;
|
|
491
|
+
};
|
|
492
|
+
export type MultiFlagTester = (flags: FlagOutput) => boolean;
|
|
493
|
+
type SimpleFlagGroup = string;
|
|
494
|
+
type ComplexFlagGroup = {
|
|
495
|
+
type: 'all';
|
|
496
|
+
flags: string[];
|
|
497
|
+
};
|
|
498
|
+
export type FlagGroup = SimpleFlagGroup | ComplexFlagGroup;
|
|
499
|
+
export interface Constraint {
|
|
500
|
+
_evaluateAgainstFlags(flags: FlagOutput): Validation;
|
|
501
|
+
allFlagCriteriaSatisfied(criterionTester: SingleFlagTester): Constraint;
|
|
502
|
+
and: Constraint;
|
|
503
|
+
anyFlagCriterionSatisfied(criterionTester: SingleFlagTester): Constraint;
|
|
504
|
+
are: Constraint;
|
|
505
|
+
dependentOn(...dependencyFlagGroups: FlagGroup[]): Constraint;
|
|
506
|
+
exclusiveWith(...exclusionFlagGroups: FlagGroup[]): Constraint;
|
|
507
|
+
is: Constraint;
|
|
508
|
+
mutuallyDependent(): Constraint;
|
|
509
|
+
mutuallyExclusive(): Constraint;
|
|
510
|
+
or: Constraint;
|
|
511
|
+
requiredAll(): Constraint;
|
|
512
|
+
requiredAny(): Constraint;
|
|
513
|
+
requiredAtLeastN(n: number): Constraint;
|
|
514
|
+
requiredAtMostN(n: number): Constraint;
|
|
515
|
+
requiredExactlyN(n: number): Constraint;
|
|
516
|
+
thisIsTrue(flagTester: MultiFlagTester): Constraint;
|
|
517
|
+
unless: Constraint;
|
|
518
|
+
when: Constraint;
|
|
519
|
+
}
|
|
486
520
|
export {};
|
package/lib/parser/errors.d.ts
CHANGED
|
@@ -51,3 +51,8 @@ export declare class FailedFlagValidationError extends CLIParseError {
|
|
|
51
51
|
failed: Validation[];
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
|
+
export declare class ViolatedFlagConstraintError extends CLIParseError {
|
|
55
|
+
constructor({ exit, failed, parse }: CLIParseErrorOptions & {
|
|
56
|
+
failed: Validation[];
|
|
57
|
+
});
|
|
58
|
+
}
|
package/lib/parser/errors.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.FailedFlagValidationError = exports.ArgInvalidOptionError = exports.FlagInvalidOptionError = exports.NonExistentFlagsError = exports.UnexpectedArgsError = exports.RequiredArgsError = exports.InvalidArgsSpecError = exports.CLIParseError = exports.CLIError = void 0;
|
|
6
|
+
exports.ViolatedFlagConstraintError = exports.FailedFlagValidationError = exports.ArgInvalidOptionError = exports.FlagInvalidOptionError = exports.NonExistentFlagsError = exports.UnexpectedArgsError = exports.RequiredArgsError = exports.InvalidArgsSpecError = exports.CLIParseError = exports.CLIError = void 0;
|
|
7
7
|
const cache_1 = __importDefault(require("../cache"));
|
|
8
8
|
const errors_1 = require("../errors");
|
|
9
9
|
const util_1 = require("../util/util");
|
|
@@ -105,3 +105,13 @@ class FailedFlagValidationError extends CLIParseError {
|
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
exports.FailedFlagValidationError = FailedFlagValidationError;
|
|
108
|
+
class ViolatedFlagConstraintError extends CLIParseError {
|
|
109
|
+
constructor({ exit, failed, parse }) {
|
|
110
|
+
const reasons = failed.map((r) => r.reason);
|
|
111
|
+
const deduped = (0, util_1.uniq)(reasons);
|
|
112
|
+
const errString = deduped.length === 1 ? 'error' : 'errors';
|
|
113
|
+
const message = `The following ${errString} occurred:\n ${(0, theme_1.colorize)('dim', deduped.join('\n'))}`;
|
|
114
|
+
super({ exit: cache_1.default.getInstance().get('exitCodes')?.violatedFlagConstraint ?? exit, message, parse });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
exports.ViolatedFlagConstraintError = ViolatedFlagConstraintError;
|
package/lib/parser/index.js
CHANGED
package/lib/parser/validate.js
CHANGED
|
@@ -118,6 +118,18 @@ async function validate(parse) {
|
|
|
118
118
|
parse,
|
|
119
119
|
});
|
|
120
120
|
}
|
|
121
|
+
function validateConstraints() {
|
|
122
|
+
if (parse.input.constraints) {
|
|
123
|
+
const validations = parse.input.constraints.map((c) => c._evaluateAgainstFlags(parse.output.flags));
|
|
124
|
+
const failed = validations.filter((v) => v.status === 'failed');
|
|
125
|
+
if (failed.length > 0) {
|
|
126
|
+
throw new errors_1.ViolatedFlagConstraintError({
|
|
127
|
+
failed,
|
|
128
|
+
parse,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
121
133
|
async function resolveFlags(flags) {
|
|
122
134
|
if (cachedResolvedFlags)
|
|
123
135
|
return cachedResolvedFlags;
|
|
@@ -270,5 +282,6 @@ async function validate(parse) {
|
|
|
270
282
|
});
|
|
271
283
|
}
|
|
272
284
|
validateArgs();
|
|
273
|
-
|
|
285
|
+
await validateFlags();
|
|
286
|
+
validateConstraints();
|
|
274
287
|
}
|