@oclif/core 1.14.2 → 1.16.1

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 CHANGED
@@ -2,6 +2,27 @@
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.1](https://github.com/oclif/core/compare/v1.16.0...v1.16.1) (2022-09-08)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * support environment variables for boolean flags ([#488](https://github.com/oclif/core/issues/488)) ([#490](https://github.com/oclif/core/issues/490)) ([506945c](https://github.com/oclif/core/commit/506945c6ea2f8b75f0d56ad1f6e62a3717384a42)), closes [#487](https://github.com/oclif/core/issues/487)
11
+
12
+ ## [1.16.0](https://github.com/oclif/core/compare/v1.15.0...v1.16.0) (2022-08-24)
13
+
14
+
15
+ ### Features
16
+
17
+ * support complex flag relationships ([#468](https://github.com/oclif/core/issues/468)) ([222d1f6](https://github.com/oclif/core/commit/222d1f67012557ac0707077d6c8840966dbf00cb))
18
+
19
+ ## [1.15.0](https://github.com/oclif/core/compare/v1.14.2...v1.15.0) (2022-08-23)
20
+
21
+
22
+ ### Features
23
+
24
+ * add InferredFlags type ([#473](https://github.com/oclif/core/issues/473)) ([ee5ce65](https://github.com/oclif/core/commit/ee5ce651899c0ef586d425567ef3b78468dca627))
25
+
5
26
  ### [1.14.2](https://github.com/oclif/core/compare/v1.14.1...v1.14.2) (2022-08-18)
6
27
 
7
28
 
@@ -660,6 +660,7 @@ async function toCached(c, plugin) {
660
660
  multiple: flag.multiple,
661
661
  options: flag.options,
662
662
  dependsOn: flag.dependsOn,
663
+ relationships: flag.relationships,
663
664
  exclusive: flag.exclusive,
664
665
  default: await defaultToCached(flag),
665
666
  };
package/lib/flags.d.ts CHANGED
@@ -1,19 +1,13 @@
1
- import { OptionFlag, Definition, BooleanFlag, EnumFlagOptions, Default } from './interfaces';
2
- export declare function build<T>(defaults: {
3
- parse: OptionFlag<T>['parse'];
4
- } & Partial<OptionFlag<T>>): Definition<T>;
5
- export declare function build(defaults: Partial<OptionFlag<string>>): Definition<string>;
6
- export declare function option<T>(options: {
7
- parse: OptionFlag<T>['parse'];
8
- } & Partial<OptionFlag<T>>): OptionFlag<T | undefined>;
9
- export declare function _enum<T = string>(opts: EnumFlagOptions<T> & {
1
+ import { OptionFlag, BooleanFlag, EnumFlagOptions, Default } from './interfaces';
2
+ export { boolean, integer, url, directory, file, string, build, option, custom } from './parser';
3
+ export declare function _enum<T = string>(opts: EnumFlagOptions<T, true> & {
10
4
  multiple: true;
11
5
  } & ({
12
6
  required: true;
13
7
  } | {
14
- default: Default<T>;
8
+ default: Default<T[]>;
15
9
  })): OptionFlag<T[]>;
16
- export declare function _enum<T = string>(opts: EnumFlagOptions<T> & {
10
+ export declare function _enum<T = string>(opts: EnumFlagOptions<T, true> & {
17
11
  multiple: true;
18
12
  }): OptionFlag<T[] | undefined>;
19
13
  export declare function _enum<T = string>(opts: EnumFlagOptions<T> & ({
@@ -23,8 +17,5 @@ export declare function _enum<T = string>(opts: EnumFlagOptions<T> & ({
23
17
  })): OptionFlag<T>;
24
18
  export declare function _enum<T = string>(opts: EnumFlagOptions<T>): OptionFlag<T | undefined>;
25
19
  export { _enum as enum };
26
- declare const stringFlag: Definition<string>;
27
- export { stringFlag as string };
28
- export { boolean, integer, url, directory, file } from './parser';
29
20
  export declare const version: (opts?: Partial<BooleanFlag<boolean>>) => BooleanFlag<void>;
30
21
  export declare const help: (opts?: Partial<BooleanFlag<boolean>>) => BooleanFlag<void>;
package/lib/flags.js CHANGED
@@ -1,17 +1,19 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.help = exports.version = exports.file = exports.directory = exports.url = exports.integer = exports.boolean = exports.string = exports.enum = exports._enum = exports.option = exports.build = void 0;
4
- const Parser = require("./parser");
5
- function build(defaults) {
6
- return Parser.flags.build(defaults);
7
- }
8
- exports.build = build;
9
- function option(options) {
10
- return build(options)();
11
- }
12
- exports.option = option;
3
+ exports.help = exports.version = exports.enum = exports._enum = exports.custom = exports.option = exports.build = exports.string = exports.file = exports.directory = exports.url = exports.integer = exports.boolean = void 0;
4
+ const parser_1 = require("./parser");
5
+ var parser_2 = require("./parser");
6
+ Object.defineProperty(exports, "boolean", { enumerable: true, get: function () { return parser_2.boolean; } });
7
+ Object.defineProperty(exports, "integer", { enumerable: true, get: function () { return parser_2.integer; } });
8
+ Object.defineProperty(exports, "url", { enumerable: true, get: function () { return parser_2.url; } });
9
+ Object.defineProperty(exports, "directory", { enumerable: true, get: function () { return parser_2.directory; } });
10
+ Object.defineProperty(exports, "file", { enumerable: true, get: function () { return parser_2.file; } });
11
+ Object.defineProperty(exports, "string", { enumerable: true, get: function () { return parser_2.string; } });
12
+ Object.defineProperty(exports, "build", { enumerable: true, get: function () { return parser_2.build; } });
13
+ Object.defineProperty(exports, "option", { enumerable: true, get: function () { return parser_2.option; } });
14
+ Object.defineProperty(exports, "custom", { enumerable: true, get: function () { return parser_2.custom; } });
13
15
  function _enum(opts) {
14
- return build({
16
+ return (0, parser_1.custom)({
15
17
  async parse(input) {
16
18
  if (!opts.options.includes(input))
17
19
  throw new Error(`Expected --${this.name}=${input} to be one of: ${opts.options.join(', ')}`);
@@ -23,16 +25,8 @@ function _enum(opts) {
23
25
  }
24
26
  exports._enum = _enum;
25
27
  exports.enum = _enum;
26
- const stringFlag = build({});
27
- exports.string = stringFlag;
28
- var parser_1 = require("./parser");
29
- Object.defineProperty(exports, "boolean", { enumerable: true, get: function () { return parser_1.boolean; } });
30
- Object.defineProperty(exports, "integer", { enumerable: true, get: function () { return parser_1.integer; } });
31
- Object.defineProperty(exports, "url", { enumerable: true, get: function () { return parser_1.url; } });
32
- Object.defineProperty(exports, "directory", { enumerable: true, get: function () { return parser_1.directory; } });
33
- Object.defineProperty(exports, "file", { enumerable: true, get: function () { return parser_1.file; } });
34
28
  const version = (opts = {}) => {
35
- return Parser.flags.boolean({
29
+ return (0, parser_1.boolean)({
36
30
  description: 'Show CLI version.',
37
31
  ...opts,
38
32
  parse: async (_, cmd) => {
@@ -43,7 +37,7 @@ const version = (opts = {}) => {
43
37
  };
44
38
  exports.version = version;
45
39
  const help = (opts = {}) => {
46
- return Parser.flags.boolean({
40
+ return (0, parser_1.boolean)({
47
41
  description: 'Show CLI help.',
48
42
  ...opts,
49
43
  parse: async (_, cmd) => {
@@ -0,0 +1,34 @@
1
+ import { FlagInput } from './parser';
2
+ /**
3
+ * Infer the flags that are returned by Command.parse. This is useful for when you want to assign the flags as a class property.
4
+ *
5
+ * @example
6
+ * export type StatusFlags = Interfaces.InferredFlags<typeof Status.flags & typeof Status.globalFlags>
7
+ *
8
+ * export abstract class BaseCommand extends Command {
9
+ * static enableJsonFlag = true
10
+ *
11
+ * static globalFlags = {
12
+ * config: Flags.string({
13
+ * description: 'specify config file',
14
+ * }),
15
+ * }
16
+ * }
17
+ *
18
+ * export default class Status extends BaseCommand {
19
+ * static flags = {
20
+ * force: Flags.boolean({char: 'f', description: 'a flag'}),
21
+ * }
22
+ *
23
+ * public flags!: StatusFlags
24
+ *
25
+ * public async run(): Promise<StatusFlags> {
26
+ * const result = await this.parse(Status)
27
+ * this.flags = result.flags
28
+ * return result.flags
29
+ * }
30
+ * }
31
+ */
32
+ export declare type InferredFlags<T> = T extends FlagInput<infer F> ? F & {
33
+ json: boolean | undefined;
34
+ } : unknown;
@@ -1 +1,2 @@
1
1
  "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -11,3 +11,4 @@ export { PJSON } from './pjson';
11
11
  export { Plugin, PluginOptions, Options } from './plugin';
12
12
  export { Topic } from './topic';
13
13
  export { TSConfig } from './ts-config';
14
+ export { InferredFlags } from './flags';
@@ -78,14 +78,20 @@ declare type MetadataFlag = {
78
78
  };
79
79
  export declare type ListItem = [string, string | undefined];
80
80
  export declare type List = ListItem[];
81
- export declare type DefaultContext<T> = {
82
- options: OptionFlag<T>;
83
- flags: {
84
- [k: string]: string;
85
- };
81
+ export declare type DefaultContext<T, P> = {
82
+ options: P & OptionFlag<T>;
83
+ flags: Record<string, string>;
84
+ };
85
+ export declare type Default<T, P = Record<string, unknown>> = T | ((context: DefaultContext<T, P>) => Promise<T>);
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[];
86
94
  };
87
- export declare type Default<T> = T | ((context: DefaultContext<T>) => Promise<T>);
88
- export declare type DefaultHelp<T> = T | ((context: DefaultContext<T>) => Promise<string | undefined>);
89
95
  export declare type FlagProps = {
90
96
  name: string;
91
97
  char?: AlphabetLowercase | AlphabetUppercase;
@@ -109,10 +115,34 @@ export declare type FlagProps = {
109
115
  * Shows this flag in a separate list in the help.
110
116
  */
111
117
  helpGroup?: string;
118
+ /**
119
+ * Accept an environment variable as input
120
+ */
121
+ env?: string;
122
+ /**
123
+ * If true, the flag will not be shown in the help.
124
+ */
112
125
  hidden?: boolean;
126
+ /**
127
+ * If true, the flag will be required.
128
+ */
113
129
  required?: boolean;
130
+ /**
131
+ * List of flags that this flag depends on.
132
+ */
114
133
  dependsOn?: string[];
134
+ /**
135
+ * List of flags that cannot be used with this flag.
136
+ */
115
137
  exclusive?: string[];
138
+ /**
139
+ * Exactly one of these flags must be provided.
140
+ */
141
+ exactlyOne?: string[];
142
+ /**
143
+ * Define complex relationships between flags.
144
+ */
145
+ relationships?: Relationship[];
116
146
  };
117
147
  export declare type BooleanFlagProps = FlagProps & {
118
148
  type: 'boolean';
@@ -122,48 +152,60 @@ export declare type OptionFlagProps = FlagProps & {
122
152
  type: 'option';
123
153
  helpValue?: string;
124
154
  options?: string[];
125
- multiple: boolean;
155
+ multiple?: boolean;
126
156
  };
127
- export declare type FlagBase<T, I> = FlagProps & {
128
- exactlyOne?: string[];
129
- /**
130
- * also accept an environment variable as input
131
- */
132
- env?: string;
133
- parse(input: I, context: any): Promise<T>;
157
+ export declare type FlagParser<T, I, P = any> = (input: I, context: any, opts: P & OptionFlag<T>) => Promise<T>;
158
+ export declare type FlagBase<T, I, P = any> = FlagProps & {
159
+ parse: FlagParser<T, I, P>;
134
160
  };
135
161
  export declare type BooleanFlag<T> = FlagBase<T, boolean> & BooleanFlagProps & {
136
162
  /**
137
- * specifying a default of false is the same not specifying a default
163
+ * specifying a default of false is the same as not specifying a default
138
164
  */
139
165
  default?: Default<boolean>;
140
166
  };
141
- export declare type OptionFlag<T> = FlagBase<T, string> & OptionFlagProps & {
142
- default?: Default<T | undefined>;
167
+ export declare type CustomOptionFlag<T, P = any, M = false> = FlagBase<T, string, P> & OptionFlagProps & {
143
168
  defaultHelp?: DefaultHelp<T>;
144
169
  input: string[];
170
+ default?: M extends true ? Default<T[] | undefined, P> : Default<T | undefined, P>;
145
171
  };
146
- export declare type Definition<T> = {
147
- (options: {
172
+ export declare type OptionFlag<T> = FlagBase<T, string> & OptionFlagProps & {
173
+ defaultHelp?: DefaultHelp<T>;
174
+ input: string[];
175
+ } & ({
176
+ default?: Default<T | undefined>;
177
+ multiple: false;
178
+ } | {
179
+ default?: Default<T[] | undefined>;
180
+ multiple: true;
181
+ });
182
+ export declare type Definition<T, P = Record<string, unknown>> = {
183
+ (options: P & {
148
184
  multiple: true;
149
185
  } & ({
150
186
  required: true;
151
187
  } | {
152
- default: Default<T>;
188
+ default: Default<T[]>;
153
189
  }) & Partial<OptionFlag<T>>): OptionFlag<T[]>;
154
- (options: {
190
+ (options: P & {
155
191
  multiple: true;
156
- } & Partial<OptionFlag<T[]>>): OptionFlag<T[] | undefined>;
157
- (options: ({
192
+ } & Partial<OptionFlag<T>>): OptionFlag<T[] | undefined>;
193
+ (options: P & ({
158
194
  required: true;
159
195
  } | {
160
196
  default: Default<T>;
161
197
  }) & Partial<OptionFlag<T>>): OptionFlag<T>;
162
- (options?: Partial<OptionFlag<T>>): OptionFlag<T | undefined>;
198
+ (options?: P & Partial<OptionFlag<T>>): OptionFlag<T | undefined>;
163
199
  };
164
- export declare type EnumFlagOptions<T> = Partial<OptionFlag<T>> & {
200
+ export declare type EnumFlagOptions<T, M = false> = Partial<CustomOptionFlag<T, any, M>> & {
165
201
  options: T[];
166
- };
202
+ } & ({
203
+ default?: Default<T | undefined>;
204
+ multiple?: false;
205
+ } | {
206
+ default?: Default<T[] | undefined>;
207
+ multiple: true;
208
+ });
167
209
  export declare type Flag<T> = BooleanFlag<T> | OptionFlag<T>;
168
210
  export declare type Input<TFlags extends FlagOutput, GFlags extends FlagOutput> = {
169
211
  flags?: FlagInput<TFlags>;
@@ -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
+ }
@@ -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;
@@ -1,96 +1,59 @@
1
1
  /// <reference types="node" />
2
2
  import { URL } from 'url';
3
- import { Definition, OptionFlag, BooleanFlag, Default } from '../interfaces';
3
+ import { Definition, OptionFlag, BooleanFlag } from '../interfaces';
4
+ import { FlagParser, CustomOptionFlag } from '../interfaces/parser';
5
+ /**
6
+ * Create a custom flag.
7
+ *
8
+ * @example
9
+ * type Id = string
10
+ * type IdOpts = { startsWith: string; length: number };
11
+ *
12
+ * export const myFlag = custom<Id, IdOpts>({
13
+ * parse: async (input, opts) => {
14
+ * if (input.startsWith(opts.startsWith) && input.length === opts.length) {
15
+ * return input
16
+ * }
17
+ *
18
+ * throw new Error('Invalid id')
19
+ * },
20
+ * })
21
+ */
22
+ export declare function custom<T, P = Record<string, unknown>>(defaults: {
23
+ parse: FlagParser<T, string, P>;
24
+ multiple: true;
25
+ } & Partial<CustomOptionFlag<T, P, true>>): Definition<T, P>;
26
+ export declare function custom<T, P = Record<string, unknown>>(defaults: {
27
+ parse: FlagParser<T, string, P>;
28
+ } & Partial<CustomOptionFlag<T, P>>): Definition<T, P>;
29
+ export declare function custom<T = string, P = Record<string, unknown>>(defaults: Partial<CustomOptionFlag<T, P>>): Definition<T, P>;
30
+ /**
31
+ * @deprecated Use Flags.custom instead.
32
+ */
4
33
  export declare function build<T>(defaults: {
5
34
  parse: OptionFlag<T>['parse'];
6
35
  } & Partial<OptionFlag<T>>): Definition<T>;
7
36
  export declare function build(defaults: Partial<OptionFlag<string>>): Definition<string>;
8
37
  export declare function boolean<T = boolean>(options?: Partial<BooleanFlag<T>>): BooleanFlag<T>;
9
- export declare function integer(opts: Partial<OptionFlag<number>> & {
10
- min?: number;
11
- max?: number;
12
- } & {
13
- multiple: true;
14
- } & ({
15
- required: true;
16
- } | {
17
- default: Default<number>;
18
- })): OptionFlag<number[]>;
19
- export declare function integer(opts: Partial<OptionFlag<number>> & {
20
- min?: number;
21
- max?: number;
22
- } & {
23
- multiple: true;
24
- }): OptionFlag<number[] | undefined>;
25
- export declare function integer(opts: Partial<OptionFlag<number>> & {
26
- min?: number;
27
- max?: number;
28
- } & ({
29
- required: true;
30
- } | {
31
- default: Default<number>;
32
- })): OptionFlag<number>;
33
- export declare function integer(opts?: Partial<OptionFlag<number>> & {
34
- min?: number;
35
- max?: number;
36
- }): OptionFlag<number | undefined>;
37
- export declare function directory(opts: Partial<OptionFlag<string>> & {
38
- exists?: boolean;
39
- } & {
40
- multiple: true;
41
- } & ({
42
- required: true;
43
- } | {
44
- default: Default<string>;
45
- })): OptionFlag<string[]>;
46
- export declare function directory(opts: Partial<OptionFlag<string>> & {
47
- exists?: boolean;
48
- } & {
49
- multiple: true;
50
- }): OptionFlag<string[] | undefined>;
51
- export declare function directory(opts: {
52
- exists?: boolean;
53
- } & Partial<OptionFlag<string>> & ({
54
- required: true;
55
- } | {
56
- default: Default<string>;
57
- })): OptionFlag<string>;
58
- export declare function directory(opts?: {
59
- exists?: boolean;
60
- } & Partial<OptionFlag<string>>): OptionFlag<string | undefined>;
61
- export declare function file(opts: Partial<OptionFlag<string>> & {
62
- exists?: boolean;
63
- } & {
64
- multiple: true;
65
- } & ({
66
- required: true;
67
- } | {
68
- default: Default<string>;
69
- })): OptionFlag<string[]>;
70
- export declare function file(opts: Partial<OptionFlag<string>> & {
71
- exists?: boolean;
72
- } & {
73
- multiple: true;
74
- }): OptionFlag<string[] | undefined>;
75
- export declare function file(opts: {
76
- exists?: boolean;
77
- } & Partial<OptionFlag<string>> & ({
78
- required: true;
79
- } | {
80
- default: Default<string>;
81
- })): OptionFlag<string>;
82
- export declare function file(opts?: {
83
- exists?: boolean;
84
- } & Partial<OptionFlag<string>>): OptionFlag<string | undefined>;
38
+ export declare const integer: Definition<number, {
39
+ min?: number | undefined;
40
+ max?: number | undefined;
41
+ }>;
42
+ export declare const directory: Definition<string, {
43
+ exists?: boolean | undefined;
44
+ }>;
45
+ export declare const file: Definition<string, {
46
+ exists?: boolean | undefined;
47
+ }>;
85
48
  /**
86
49
  * Initializes a string as a URL. Throws an error
87
50
  * if the string is not a valid URL.
88
51
  */
89
- export declare const url: Definition<URL>;
52
+ export declare const url: Definition<URL, Record<string, unknown>>;
90
53
  export declare function option<T>(options: {
91
54
  parse: OptionFlag<T>['parse'];
92
- } & Partial<OptionFlag<T>>): OptionFlag<T | undefined>;
93
- declare const stringFlag: Definition<string>;
55
+ } & Partial<CustomOptionFlag<T>>): OptionFlag<T | undefined>;
56
+ declare const stringFlag: Definition<string, Record<string, unknown>>;
94
57
  export { stringFlag as string };
95
58
  export declare const defaultFlags: {
96
59
  color: BooleanFlag<boolean>;
@@ -1,13 +1,25 @@
1
1
  "use strict";
2
- // tslint:disable interface-over-type-literal
3
2
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.defaultFlags = exports.string = exports.option = exports.url = exports.file = exports.directory = exports.integer = exports.boolean = exports.build = void 0;
3
+ exports.defaultFlags = exports.string = exports.option = exports.url = exports.file = exports.directory = exports.integer = exports.boolean = exports.build = exports.custom = void 0;
5
4
  const url_1 = require("url");
6
5
  const fs = require("fs");
6
+ function custom(defaults) {
7
+ return (options = {}) => {
8
+ return {
9
+ parse: async (i, _context, _opts) => i,
10
+ ...defaults,
11
+ ...options,
12
+ input: [],
13
+ multiple: Boolean(options.multiple === undefined ? defaults.multiple : options.multiple),
14
+ type: 'option',
15
+ };
16
+ };
17
+ }
18
+ exports.custom = custom;
7
19
  function build(defaults) {
8
20
  return (options = {}) => {
9
21
  return {
10
- parse: async (i, _) => i,
22
+ parse: async (i, _context) => i,
11
23
  ...defaults,
12
24
  ...options,
13
25
  input: [],
@@ -26,53 +38,37 @@ function boolean(options = {}) {
26
38
  };
27
39
  }
28
40
  exports.boolean = boolean;
29
- function integer(opts = {}) {
30
- return build({
31
- ...opts,
32
- parse: async (input) => {
33
- if (!/^-?\d+$/.test(input))
34
- throw new Error(`Expected an integer but received: ${input}`);
35
- const num = Number.parseInt(input, 10);
36
- if (opts.min !== undefined && num < opts.min)
37
- throw new Error(`Expected an integer greater than or equal to ${opts.min} but received: ${input}`);
38
- if (opts.max !== undefined && num > opts.max)
39
- throw new Error(`Expected an integer less than or equal to ${opts.max} but received: ${input}`);
40
- return opts.parse ? opts.parse(input, 1) : num;
41
- },
42
- })();
43
- }
44
- exports.integer = integer;
45
- function directory(opts = {}) {
46
- return build({
47
- ...opts,
48
- parse: async (input) => {
49
- if (opts.exists) {
50
- // 2nd "context" arg is required but unused
51
- return opts.parse ? opts.parse(await dirExists(input), true) : dirExists(input);
52
- }
53
- return opts.parse ? opts.parse(input, true) : input;
54
- },
55
- })();
56
- }
57
- exports.directory = directory;
58
- function file(opts = {}) {
59
- return build({
60
- ...opts,
61
- parse: async (input) => {
62
- if (opts.exists) {
63
- // 2nd "context" arg is required but unused
64
- return opts.parse ? opts.parse(await fileExists(input), true) : fileExists(input);
65
- }
66
- return opts.parse ? opts.parse(input, true) : input;
67
- },
68
- })();
69
- }
70
- exports.file = file;
41
+ exports.integer = custom({
42
+ parse: async (input, _, opts) => {
43
+ if (!/^-?\d+$/.test(input))
44
+ throw new Error(`Expected an integer but received: ${input}`);
45
+ const num = Number.parseInt(input, 10);
46
+ if (opts.min !== undefined && num < opts.min)
47
+ throw new Error(`Expected an integer greater than or equal to ${opts.min} but received: ${input}`);
48
+ if (opts.max !== undefined && num > opts.max)
49
+ throw new Error(`Expected an integer less than or equal to ${opts.max} but received: ${input}`);
50
+ return num;
51
+ },
52
+ });
53
+ exports.directory = custom({
54
+ parse: async (input, _, opts) => {
55
+ if (opts.exists)
56
+ return dirExists(input);
57
+ return input;
58
+ },
59
+ });
60
+ exports.file = custom({
61
+ parse: async (input, _, opts) => {
62
+ if (opts.exists)
63
+ return fileExists(input);
64
+ return input;
65
+ },
66
+ });
71
67
  /**
72
68
  * Initializes a string as a URL. Throws an error
73
69
  * if the string is not a valid URL.
74
70
  */
75
- exports.url = build({
71
+ exports.url = custom({
76
72
  parse: async (input) => {
77
73
  try {
78
74
  return new url_1.URL(input);
@@ -83,10 +79,10 @@ exports.url = build({
83
79
  },
84
80
  });
85
81
  function option(options) {
86
- return build(options)();
82
+ return custom(options)();
87
83
  }
88
84
  exports.option = option;
89
- const stringFlag = build({});
85
+ const stringFlag = custom({});
90
86
  exports.string = stringFlag;
91
87
  exports.defaultFlags = {
92
88
  color: boolean({ allowNo: true }),
@@ -1,4 +1,3 @@
1
- /// <reference types="node" />
2
1
  import * as args from './args';
3
2
  import * as flags from './flags';
4
3
  import { Input, ParserOutput } from '../interfaces';
@@ -8,5 +7,4 @@ export { flagUsages } from './help';
8
7
  export declare function parse<TFlags, GFlags, TArgs extends {
9
8
  [name: string]: string;
10
9
  }>(argv: string[], options: Input<TFlags, GFlags>): Promise<ParserOutput<TFlags, GFlags, TArgs>>;
11
- declare const boolean: typeof flags.boolean, integer: typeof flags.integer, url: import("../interfaces").Definition<import("url").URL>, directory: typeof flags.directory, file: typeof flags.file;
12
- export { boolean, integer, url, directory, file };
10
+ export { boolean, integer, url, directory, file, string, build, option, custom } from './flags';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.file = exports.directory = exports.url = exports.integer = exports.boolean = exports.parse = exports.flagUsages = exports.flags = exports.args = void 0;
3
+ exports.custom = exports.option = exports.build = exports.string = exports.file = exports.directory = exports.url = exports.integer = exports.boolean = exports.parse = exports.flagUsages = exports.flags = exports.args = void 0;
4
4
  const args = require("./args");
5
5
  exports.args = args;
6
6
  const deps_1 = require("./deps");
@@ -27,13 +27,17 @@ async function parse(argv, options) {
27
27
  };
28
28
  const parser = new parse_1.Parser(input);
29
29
  const output = await parser.parse();
30
- m.validate({ input, output });
30
+ await m.validate({ input, output });
31
31
  return output;
32
32
  }
33
33
  exports.parse = parse;
34
- const { boolean, integer, url, directory, file } = flags;
35
- exports.boolean = boolean;
36
- exports.integer = integer;
37
- exports.url = url;
38
- exports.directory = directory;
39
- exports.file = file;
34
+ var flags_1 = require("./flags");
35
+ Object.defineProperty(exports, "boolean", { enumerable: true, get: function () { return flags_1.boolean; } });
36
+ Object.defineProperty(exports, "integer", { enumerable: true, get: function () { return flags_1.integer; } });
37
+ Object.defineProperty(exports, "url", { enumerable: true, get: function () { return flags_1.url; } });
38
+ Object.defineProperty(exports, "directory", { enumerable: true, get: function () { return flags_1.directory; } });
39
+ Object.defineProperty(exports, "file", { enumerable: true, get: function () { return flags_1.file; } });
40
+ Object.defineProperty(exports, "string", { enumerable: true, get: function () { return flags_1.string; } });
41
+ Object.defineProperty(exports, "build", { enumerable: true, get: function () { return flags_1.build; } });
42
+ Object.defineProperty(exports, "option", { enumerable: true, get: function () { return flags_1.option; } });
43
+ Object.defineProperty(exports, "custom", { enumerable: true, get: function () { return flags_1.custom; } });
@@ -149,13 +149,13 @@ class Parser {
149
149
  flags[token.flag] = true;
150
150
  }
151
151
  // eslint-disable-next-line no-await-in-loop
152
- flags[token.flag] = await flag.parse(flags[token.flag], this.context);
152
+ flags[token.flag] = await flag.parse(flags[token.flag], this.context, flag);
153
153
  }
154
154
  else {
155
155
  const input = token.input;
156
156
  this._validateOptions(flag, input);
157
157
  // eslint-disable-next-line no-await-in-loop
158
- const value = flag.parse ? await flag.parse(input, this.context) : input;
158
+ const value = flag.parse ? await flag.parse(input, this.context, flag) : input;
159
159
  if (flag.multiple) {
160
160
  flags[token.flag] = flags[token.flag] || [];
161
161
  flags[token.flag].push(value);
@@ -169,12 +169,18 @@ class Parser {
169
169
  const flag = this.input.flags[k];
170
170
  if (flags[k])
171
171
  continue;
172
- if (flag.type === 'option' && flag.env) {
172
+ if (flag.env) {
173
173
  const input = process.env[flag.env];
174
- if (input) {
175
- this._validateOptions(flag, input);
176
- // eslint-disable-next-line no-await-in-loop
177
- flags[k] = await flag.parse(input, this.context);
174
+ if (flag.type === 'option') {
175
+ if (input) {
176
+ this._validateOptions(flag, input);
177
+ // eslint-disable-next-line no-await-in-loop
178
+ flags[k] = await flag.parse(input, this.context, flag);
179
+ }
180
+ }
181
+ else if (flag.type === 'boolean') {
182
+ // eslint-disable-next-line no-negated-condition
183
+ flags[k] = input !== undefined ? ['true', 'TRUE', '1', 'yes', 'YES', 'y', 'Y'].includes(input) : false;
178
184
  }
179
185
  }
180
186
  if (!(k in flags) && flag.default !== undefined) {
@@ -2,4 +2,4 @@ import { ParserInput, ParserOutput } from '../interfaces';
2
2
  export declare function validate(parse: {
3
3
  input: ParserInput;
4
4
  output: ParserOutput;
5
- }): void;
5
+ }): Promise<void>;
@@ -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("../errors");
5
- const errors_2 = require("./errors");
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 errors_2.UnexpectedArgsError({ parse, args: extras });
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 errors_2.InvalidArgsSpecError({ parse, args: parse.input.args });
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 errors_2.RequiredArgsError({ parse, args: missingRequiredArgs });
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
- throw new errors_1.CLIError(`Exactly one of the following must be provided: ${[
40
- ...new Set(flag.exactlyOne?.map(flag => `--${flag}`)),
41
- ].join(', ')}`);
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 validateFlags() {
45
- for (const [name, flag] of Object.entries(parse.input.flags)) {
46
- if (parse.output.flags[name] !== undefined) {
47
- for (const also of flag.dependsOn || []) {
48
- if (!parse.output.flags[also]) {
49
- throw new errors_1.CLIError(`--${also}= must also be provided when using --${name}=`);
50
- }
51
- }
52
- for (const also of flag.exclusive || []) {
53
- // do not enforce exclusivity for flags that were defaulted
54
- if (parse.output.metadata.flags[also] &&
55
- parse.output.metadata.flags[also].setFromDefault)
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
- else if (flag.required) {
71
- throw new errors_2.RequiredFlagError({ parse, flag });
72
- }
73
- else if (flag.exactlyOne && flag.exactlyOne.length > 0) {
74
- validateAcrossFlags(flag);
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;
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": "1.14.2",
4
+ "version": "1.16.1",
5
5
  "author": "Salesforce",
6
6
  "bugs": "https://github.com/oclif/core/issues",
7
7
  "dependencies": {
@@ -74,6 +74,7 @@
74
74
  "shx": "^0.3.4",
75
75
  "sinon": "^11.1.2",
76
76
  "ts-node": "^9.1.1",
77
+ "tsd": "^0.22.0",
77
78
  "typescript": "4.5.5"
78
79
  },
79
80
  "engines": {