@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 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
@@ -72,6 +72,7 @@ class Command {
72
72
  /** An order-dependent object of arguments for the command */
73
73
  static args = {};
74
74
  static baseFlags;
75
+ static constraints;
75
76
  /**
76
77
  * Emit deprecation warning when a command alias is used
77
78
  */
@@ -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 {};
@@ -156,6 +156,7 @@ export type OclifConfiguration = {
156
156
  default?: number;
157
157
  failedFlagParsing?: number;
158
158
  failedFlagValidation?: number;
159
+ violatedFlagConstraint?: number;
159
160
  invalidArgsSpec?: number;
160
161
  nonExistentFlag?: number;
161
162
  requiredArgs?: number;
@@ -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
+ }
@@ -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;
@@ -13,6 +13,7 @@ async function parse(argv, options) {
13
13
  '--': options['--'],
14
14
  args: (options.args ?? {}),
15
15
  argv,
16
+ constraints: options.constraints,
16
17
  context: options.context,
17
18
  flags: (options.flags ?? {}),
18
19
  strict: options.strict !== false,
@@ -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
- return validateFlags();
285
+ await validateFlags();
286
+ validateConstraints();
274
287
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@oclif/core",
3
3
  "description": "base library for oclif CLIs",
4
- "version": "4.10.6",
4
+ "version": "4.11.0",
5
5
  "author": "Salesforce",
6
6
  "bugs": "https://github.com/oclif/core/issues",
7
7
  "dependencies": {