@oclif/core 1.15.0 → 1.16.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/CHANGELOG.md +7 -0
- package/lib/config/config.js +1 -0
- package/lib/interfaces/parser.d.ts +12 -0
- package/lib/parser/errors.d.ts +11 -0
- package/lib/parser/errors.js +14 -2
- package/lib/parser/index.js +1 -1
- package/lib/parser/validate.d.ts +1 -1
- package/lib/parser/validate.js +115 -40
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
## [1.16.0](https://github.com/oclif/core/compare/v1.15.0...v1.16.0) (2022-08-24)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* support complex flag relationships ([#468](https://github.com/oclif/core/issues/468)) ([222d1f6](https://github.com/oclif/core/commit/222d1f67012557ac0707077d6c8840966dbf00cb))
|
|
11
|
+
|
|
5
12
|
## [1.15.0](https://github.com/oclif/core/compare/v1.14.2...v1.15.0) (2022-08-23)
|
|
6
13
|
|
|
7
14
|
|
package/lib/config/config.js
CHANGED
|
@@ -84,6 +84,14 @@ export declare type DefaultContext<T, P> = {
|
|
|
84
84
|
};
|
|
85
85
|
export declare type Default<T, P = Record<string, unknown>> = T | ((context: DefaultContext<T, P>) => Promise<T>);
|
|
86
86
|
export declare type DefaultHelp<T, P = Record<string, unknown>> = T | ((context: DefaultContext<T, P>) => Promise<string | undefined>);
|
|
87
|
+
export declare type FlagRelationship = string | {
|
|
88
|
+
name: string;
|
|
89
|
+
when: (flags: Record<string, unknown>) => Promise<boolean>;
|
|
90
|
+
};
|
|
91
|
+
export declare type Relationship = {
|
|
92
|
+
type: 'all' | 'some' | 'none';
|
|
93
|
+
flags: FlagRelationship[];
|
|
94
|
+
};
|
|
87
95
|
export declare type FlagProps = {
|
|
88
96
|
name: string;
|
|
89
97
|
char?: AlphabetLowercase | AlphabetUppercase;
|
|
@@ -131,6 +139,10 @@ export declare type FlagProps = {
|
|
|
131
139
|
* Exactly one of these flags must be provided.
|
|
132
140
|
*/
|
|
133
141
|
exactlyOne?: string[];
|
|
142
|
+
/**
|
|
143
|
+
* Define complex relationships between flags.
|
|
144
|
+
*/
|
|
145
|
+
relationships?: Relationship[];
|
|
134
146
|
};
|
|
135
147
|
export declare type BooleanFlagProps = FlagProps & {
|
|
136
148
|
type: 'boolean';
|
package/lib/parser/errors.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { CLIError } from '../errors';
|
|
2
2
|
import { ParserArg, CLIParseErrorOptions, OptionFlag, Flag } from '../interfaces';
|
|
3
3
|
export { CLIError } from '../errors';
|
|
4
|
+
export declare type Validation = {
|
|
5
|
+
name: string;
|
|
6
|
+
status: 'success' | 'failed';
|
|
7
|
+
validationFn: string;
|
|
8
|
+
reason?: string;
|
|
9
|
+
};
|
|
4
10
|
export declare class CLIParseError extends CLIError {
|
|
5
11
|
parse: CLIParseErrorOptions['parse'];
|
|
6
12
|
constructor(options: CLIParseErrorOptions & {
|
|
@@ -37,3 +43,8 @@ export declare class FlagInvalidOptionError extends CLIParseError {
|
|
|
37
43
|
export declare class ArgInvalidOptionError extends CLIParseError {
|
|
38
44
|
constructor(arg: ParserArg<any>, input: string);
|
|
39
45
|
}
|
|
46
|
+
export declare class FailedFlagValidationError extends CLIParseError {
|
|
47
|
+
constructor({ parse, failed }: CLIParseErrorOptions & {
|
|
48
|
+
failed: Validation[];
|
|
49
|
+
});
|
|
50
|
+
}
|
package/lib/parser/errors.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ArgInvalidOptionError = exports.FlagInvalidOptionError = exports.UnexpectedArgsError = exports.RequiredFlagError = exports.RequiredArgsError = exports.InvalidArgsSpecError = exports.CLIParseError = exports.CLIError = void 0;
|
|
3
|
+
exports.FailedFlagValidationError = exports.ArgInvalidOptionError = exports.FlagInvalidOptionError = exports.UnexpectedArgsError = exports.RequiredFlagError = exports.RequiredArgsError = exports.InvalidArgsSpecError = exports.CLIParseError = exports.CLIError = void 0;
|
|
4
4
|
const errors_1 = require("../errors");
|
|
5
5
|
const deps_1 = require("./deps");
|
|
6
|
+
const util_1 = require("../config/util");
|
|
6
7
|
var errors_2 = require("../errors");
|
|
7
8
|
Object.defineProperty(exports, "CLIError", { enumerable: true, get: function () { return errors_2.CLIError; } });
|
|
8
9
|
// eslint-disable-next-line new-cap
|
|
@@ -10,7 +11,8 @@ const m = (0, deps_1.default)()
|
|
|
10
11
|
// eslint-disable-next-line node/no-missing-require
|
|
11
12
|
.add('help', () => require('./help'))
|
|
12
13
|
// eslint-disable-next-line node/no-missing-require
|
|
13
|
-
.add('list', () => require('./list'))
|
|
14
|
+
.add('list', () => require('./list'))
|
|
15
|
+
.add('chalk', () => require('chalk'));
|
|
14
16
|
class CLIParseError extends errors_1.CLIError {
|
|
15
17
|
constructor(options) {
|
|
16
18
|
options.message += '\nSee more help with --help';
|
|
@@ -76,3 +78,13 @@ class ArgInvalidOptionError extends CLIParseError {
|
|
|
76
78
|
}
|
|
77
79
|
}
|
|
78
80
|
exports.ArgInvalidOptionError = ArgInvalidOptionError;
|
|
81
|
+
class FailedFlagValidationError extends CLIParseError {
|
|
82
|
+
constructor({ parse, failed }) {
|
|
83
|
+
const reasons = failed.map(r => r.reason);
|
|
84
|
+
const deduped = (0, util_1.uniq)(reasons);
|
|
85
|
+
const errString = deduped.length === 1 ? 'error' : 'errors';
|
|
86
|
+
const message = `The following ${errString} occurred:\n ${m.chalk.dim(deduped.join('\n '))}`;
|
|
87
|
+
super({ parse, message });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
exports.FailedFlagValidationError = FailedFlagValidationError;
|
package/lib/parser/index.js
CHANGED
package/lib/parser/validate.d.ts
CHANGED
package/lib/parser/validate.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.validate = void 0;
|
|
4
|
-
const errors_1 = require("
|
|
5
|
-
const
|
|
6
|
-
function validate(parse) {
|
|
4
|
+
const errors_1 = require("./errors");
|
|
5
|
+
const util_1 = require("../config/util");
|
|
6
|
+
async function validate(parse) {
|
|
7
7
|
function validateArgs() {
|
|
8
8
|
const maxArgs = parse.input.args.length;
|
|
9
9
|
if (parse.input.strict && parse.output.argv.length > maxArgs) {
|
|
10
10
|
const extras = parse.output.argv.slice(maxArgs);
|
|
11
|
-
throw new
|
|
11
|
+
throw new errors_1.UnexpectedArgsError({ parse, args: extras });
|
|
12
12
|
}
|
|
13
13
|
const missingRequiredArgs = [];
|
|
14
14
|
let hasOptional = false;
|
|
@@ -19,63 +19,138 @@ function validate(parse) {
|
|
|
19
19
|
else if (hasOptional) {
|
|
20
20
|
// (required arg) check whether an optional has occurred before
|
|
21
21
|
// optionals should follow required, not before
|
|
22
|
-
throw new
|
|
22
|
+
throw new errors_1.InvalidArgsSpecError({ parse, args: parse.input.args });
|
|
23
23
|
}
|
|
24
24
|
if (arg.required && !parse.output.argv[index] && parse.output.argv[index] !== 0) {
|
|
25
25
|
missingRequiredArgs.push(arg);
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
if (missingRequiredArgs.length > 0) {
|
|
29
|
-
throw new
|
|
29
|
+
throw new errors_1.RequiredArgsError({ parse, args: missingRequiredArgs });
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
+
async function validateFlags() {
|
|
33
|
+
const promises = Object.entries(parse.input.flags).map(async ([name, flag]) => {
|
|
34
|
+
const results = [];
|
|
35
|
+
if (parse.output.flags[name] !== undefined) {
|
|
36
|
+
results.push(...await validateRelationships(name, flag), await validateDependsOn(name, flag.dependsOn ?? []), await validateExclusive(name, flag.exclusive ?? []), await validateExactlyOne(name, flag.exactlyOne ?? []));
|
|
37
|
+
}
|
|
38
|
+
else if (flag.required) {
|
|
39
|
+
results.push({ status: 'failed', name, validationFn: 'required', reason: `Missing required flag ${name}` });
|
|
40
|
+
}
|
|
41
|
+
else if (flag.exactlyOne && flag.exactlyOne.length > 0) {
|
|
42
|
+
results.push(validateAcrossFlags(flag));
|
|
43
|
+
}
|
|
44
|
+
return results;
|
|
45
|
+
});
|
|
46
|
+
const results = (await Promise.all(promises)).flat();
|
|
47
|
+
const failed = results.filter(r => r.status === 'failed');
|
|
48
|
+
if (failed.length > 0)
|
|
49
|
+
throw new errors_1.FailedFlagValidationError({ parse, failed });
|
|
50
|
+
}
|
|
51
|
+
async function resolveFlags(flags) {
|
|
52
|
+
const promises = flags.map(async (flag) => {
|
|
53
|
+
if (typeof flag === 'string') {
|
|
54
|
+
return [flag, parse.output.flags[flag]];
|
|
55
|
+
}
|
|
56
|
+
const result = await flag.when(parse.output.flags);
|
|
57
|
+
return result ? [flag.name, parse.output.flags[flag.name]] : null;
|
|
58
|
+
});
|
|
59
|
+
const resolved = await Promise.all(promises);
|
|
60
|
+
return Object.fromEntries(resolved.filter(r => r !== null));
|
|
61
|
+
}
|
|
62
|
+
function getPresentFlags(flags) {
|
|
63
|
+
return Object.keys(flags).reduce((acc, key) => {
|
|
64
|
+
if (flags[key])
|
|
65
|
+
acc.push(key);
|
|
66
|
+
return acc;
|
|
67
|
+
}, []);
|
|
68
|
+
}
|
|
32
69
|
function validateAcrossFlags(flag) {
|
|
70
|
+
const base = { name: flag.name, validationFn: 'validateAcrossFlags' };
|
|
33
71
|
const intersection = Object.entries(parse.input.flags)
|
|
34
72
|
.map(entry => entry[0]) // array of flag names
|
|
35
73
|
.filter(flagName => parse.output.flags[flagName] !== undefined) // with values
|
|
36
74
|
.filter(flagName => flag.exactlyOne && flag.exactlyOne.includes(flagName)); // and in the exactlyOne list
|
|
37
75
|
if (intersection.length === 0) {
|
|
38
76
|
// the command's exactlyOne may or may not include itself, so we'll use Set to add + de-dupe
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
77
|
+
const deduped = (0, util_1.uniq)(flag.exactlyOne?.map(flag => `--${flag}`) ?? []).join(', ');
|
|
78
|
+
const reason = `Exactly one of the following must be provided: ${deduped}`;
|
|
79
|
+
return { ...base, status: 'failed', reason };
|
|
42
80
|
}
|
|
81
|
+
return { ...base, status: 'success' };
|
|
43
82
|
}
|
|
44
|
-
function
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
continue;
|
|
57
|
-
if (parse.output.metadata.flags[name] &&
|
|
58
|
-
parse.output.metadata.flags[name].setFromDefault)
|
|
59
|
-
continue;
|
|
60
|
-
if (parse.output.flags[also]) {
|
|
61
|
-
throw new errors_1.CLIError(`--${also}= cannot also be provided when using --${name}=`);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
for (const also of flag.exactlyOne || []) {
|
|
65
|
-
if (also !== name && parse.output.flags[also]) {
|
|
66
|
-
throw new errors_1.CLIError(`--${also}= cannot also be provided when using --${name}=`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
83
|
+
async function validateExclusive(name, flags) {
|
|
84
|
+
const base = { name, validationFn: 'validateExclusive' };
|
|
85
|
+
const resolved = await resolveFlags(flags);
|
|
86
|
+
const keys = getPresentFlags(resolved);
|
|
87
|
+
for (const flag of keys) {
|
|
88
|
+
// do not enforce exclusivity for flags that were defaulted
|
|
89
|
+
if (parse.output.metadata.flags && parse.output.metadata.flags[flag]?.setFromDefault)
|
|
90
|
+
continue;
|
|
91
|
+
if (parse.output.metadata.flags && parse.output.metadata.flags[name]?.setFromDefault)
|
|
92
|
+
continue;
|
|
93
|
+
if (parse.output.flags[flag]) {
|
|
94
|
+
return { ...base, status: 'failed', reason: `--${flag}=${parse.output.flags[flag]} cannot also be provided when using --${name}` };
|
|
69
95
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
96
|
+
}
|
|
97
|
+
return { ...base, status: 'success' };
|
|
98
|
+
}
|
|
99
|
+
async function validateExactlyOne(name, flags) {
|
|
100
|
+
const base = { name, validationFn: 'validateExactlyOne' };
|
|
101
|
+
const resolved = await resolveFlags(flags);
|
|
102
|
+
const keys = getPresentFlags(resolved);
|
|
103
|
+
for (const flag of keys) {
|
|
104
|
+
if (flag !== name && parse.output.flags[flag]) {
|
|
105
|
+
return { ...base, status: 'failed', reason: `--${flag} cannot also be provided when using --${name}` };
|
|
75
106
|
}
|
|
76
107
|
}
|
|
108
|
+
return { ...base, status: 'success' };
|
|
109
|
+
}
|
|
110
|
+
async function validateDependsOn(name, flags) {
|
|
111
|
+
const base = { name, validationFn: 'validateDependsOn' };
|
|
112
|
+
const resolved = await resolveFlags(flags);
|
|
113
|
+
const foundAll = Object.values(resolved).every(Boolean);
|
|
114
|
+
if (!foundAll) {
|
|
115
|
+
const formattedFlags = Object.keys(resolved).map(f => `--${f}`).join(', ');
|
|
116
|
+
return { ...base, status: 'failed', reason: `All of the following must be provided when using --${name}: ${formattedFlags}` };
|
|
117
|
+
}
|
|
118
|
+
return { ...base, status: 'success' };
|
|
119
|
+
}
|
|
120
|
+
async function validateSome(name, flags) {
|
|
121
|
+
const base = { name, validationFn: 'validateSome' };
|
|
122
|
+
const resolved = await resolveFlags(flags);
|
|
123
|
+
const foundAtLeastOne = Object.values(resolved).some(Boolean);
|
|
124
|
+
if (!foundAtLeastOne) {
|
|
125
|
+
const formattedFlags = Object.keys(resolved).map(f => `--${f}`).join(', ');
|
|
126
|
+
return { ...base, status: 'failed', reason: `One of the following must be provided when using --${name}: ${formattedFlags}` };
|
|
127
|
+
}
|
|
128
|
+
return { ...base, status: 'success' };
|
|
129
|
+
}
|
|
130
|
+
async function validateRelationships(name, flag) {
|
|
131
|
+
if (!flag.relationships)
|
|
132
|
+
return [];
|
|
133
|
+
const results = await Promise.all(flag.relationships.map(async (relationship) => {
|
|
134
|
+
const flags = relationship.flags ?? [];
|
|
135
|
+
const results = [];
|
|
136
|
+
switch (relationship.type) {
|
|
137
|
+
case 'all':
|
|
138
|
+
results.push(await validateDependsOn(name, flags));
|
|
139
|
+
break;
|
|
140
|
+
case 'some':
|
|
141
|
+
results.push(await validateSome(name, flags));
|
|
142
|
+
break;
|
|
143
|
+
case 'none':
|
|
144
|
+
results.push(await validateExclusive(name, flags));
|
|
145
|
+
break;
|
|
146
|
+
default:
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
return results;
|
|
150
|
+
}));
|
|
151
|
+
return results.flat();
|
|
77
152
|
}
|
|
78
153
|
validateArgs();
|
|
79
|
-
validateFlags();
|
|
154
|
+
await validateFlags();
|
|
80
155
|
}
|
|
81
156
|
exports.validate = validate;
|