UNPKG

20.4 kBJavaScriptView Raw
1"use strict";
2/*
3 * Copyright (c) 2020, salesforce.com, inc.
4 * All rights reserved.
5 * Licensed under the BSD 3-Clause license.
6 * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
7 */
8Object.defineProperty(exports, "__esModule", { value: true });
9exports.buildSfdxFlags = exports.optionalBuiltinFlags = exports.requiredBuiltinFlags = exports.flags = void 0;
10const url_1 = require("url");
11const core_1 = require("@oclif/core");
12const core_2 = require("@salesforce/core");
13const kit_1 = require("@salesforce/kit");
14const ts_types_1 = require("@salesforce/ts-types");
15core_2.Messages.importMessagesDirectory(__dirname);
16const messages = core_2.Messages.load('@salesforce/command', 'flags', [
17 'error.UnknownBuiltinFlagType',
18 'error.FormattingMessageArrayValue',
19 'error.FormattingMessageArrayOption',
20 'error.FormattingMessageDate',
21 'error.FormattingMessageId',
22 'error.FormattingMessageId',
23 'error.InvalidFlagType',
24 'flags.json.description.long',
25 'flags.json.description',
26 'error.InvalidLoggerLevel',
27 'error.InvalidApiVersion',
28 'flags.apiversion.description',
29 'flags.apiversion.description.long',
30 'flags.concise.description',
31 'flags.long.description.long',
32 'flags.loglevel.description',
33 'flags.loglevel.description.long',
34 'flags.quiet.description',
35 'flags.quiet.description.long',
36 'flags.targetdevhubusername.description',
37 'flags.targetdevhubusername.description.long',
38 'flags.targetusername.description',
39 'flags.targetusername.description.long',
40 'flags.verbose.description',
41 'flags.verbose.description.long',
42 'error.InvalidFlagName',
43 'error.InvalidFlagChar',
44 'error.MissingOrInvalidFlagDescription',
45 'error.InvalidLongDescriptionFormat',
46]);
47function validateValue(isValid, value, kind, correct) {
48 if (isValid)
49 return value;
50 throw messages.createError('error.InvalidFlagType', [value, kind, correct || '']);
51}
52function toValidatorFn(validator) {
53 return (val) => {
54 if ((0, ts_types_1.isString)(validator))
55 return new RegExp(validator).test(val);
56 if ((0, ts_types_1.isInstance)(validator, RegExp))
57 return validator.test(val);
58 if ((0, ts_types_1.isFunction)(validator))
59 return !!validator(val);
60 return true;
61 };
62}
63function merge(kind, flag, describable) {
64 if ((0, ts_types_1.has)(flag, 'validate') && (0, ts_types_1.hasFunction)(flag, 'parse')) {
65 const parse = flag.parse.bind(flag);
66 flag.parse = (val, ctx) => {
67 validateValue(toValidatorFn(flag.validate)(val), val, kind);
68 return parse(val, ctx);
69 };
70 }
71 return {
72 kind,
73 ...flag,
74 description: describable.description,
75 longDescription: describable.longDescription,
76 };
77}
78function option(kind, options, parse) {
79 const flag = core_1.Flags.option({ ...options, parse });
80 return merge(kind, flag, options);
81}
82// oclif
83function buildBoolean(options) {
84 const flag = core_1.Flags.boolean(options);
85 return merge('boolean', flag, options);
86}
87function buildEnum(options) {
88 return {
89 kind: 'enum',
90 ...core_1.Flags.enum(options),
91 options: options.options,
92 description: options.description,
93 longDescription: options.longDescription,
94 };
95}
96function buildHelp(options) {
97 const flag = core_1.Flags.help(options);
98 return merge('help', core_1.Flags.help(options), {
99 description: (0, ts_types_1.ensure)(flag.description),
100 });
101}
102function buildFilepath(options) {
103 return option('filepath', options, (val) => {
104 return Promise.resolve(validateValue(core_2.sfdc.validatePathDoesNotContainInvalidChars(val), val, 'filepath'));
105 });
106}
107function buildDirectory(options) {
108 return option('directory', options, (val) => {
109 return Promise.resolve(validateValue(core_2.sfdc.validatePathDoesNotContainInvalidChars(val), val, 'directory'));
110 });
111}
112function validateBounds(kind, value, bounds, extract) {
113 if (bounds.min != null && value < extract(bounds.min)) {
114 throw new core_2.SfError(`Expected ${kind} greater than or equal to ${extract(bounds.min)} but received ${value}`, 'InvalidFlagNumericBoundsError');
115 }
116 if (bounds.max != null && value > extract(bounds.max)) {
117 throw new core_2.SfError(`Expected ${kind} less than or equal to ${extract(bounds.max)} but received ${value}`, 'InvalidFlagNumericBoundsError');
118 }
119 return value;
120}
121function buildInteger(options) {
122 const kind = 'integer';
123 return option(kind, options, async (val) => {
124 const parsed = (0, kit_1.toNumber)(val);
125 validateValue(Number.isInteger(parsed), val, kind);
126 return validateBounds(kind, parsed, options, (t) => t);
127 });
128}
129function buildOption(options) {
130 return merge('option', core_1.Flags.option(options), options);
131}
132function buildString(options) {
133 return merge('string', core_1.Flags.string(options), options);
134}
135function buildVersion(options) {
136 const flag = core_1.Flags.version(options);
137 return merge('version', flag, {
138 description: (0, ts_types_1.ensure)(flag.description),
139 });
140}
141// sfdx
142function validateArrayValues(kind, raw, vals, validator) {
143 validateValue(vals.every(toValidatorFn(validator)), raw, kind, ` ${messages.getMessage('error.FormattingMessageArrayValue')}`);
144}
145function validateArrayOptions(kind, raw, vals, allowed) {
146 validateValue(allowed.size === 0 || vals.every((t) => allowed.has(t)), raw, kind, ` ${messages.getMessage('error.FormattingMessageArrayOption', [Array.from(allowed).toString()])}`);
147}
148const convertArrayFlagToArray = (flagValue, delimiter = ',') => {
149 // don't split on delimiter if it's inside a single or double-quoted substring
150 // eslint-disable-next-line no-useless-escape
151 const regex = new RegExp(`"(.*?)"|\'(.*?)\'|${delimiter}`);
152 return flagValue
153 .split(regex)
154 .filter((i) => !!i)
155 .map((i) => i.trim());
156};
157function buildMappedArray(kind, options) {
158 const { options: values, ...rest } = options;
159 const allowed = new Set(values);
160 return option(kind, rest, (val) => {
161 const vals = convertArrayFlagToArray(val, options.delimiter);
162 validateArrayValues(kind, val, vals, options.validate);
163 const mappedVals = vals.map(options.map);
164 validateArrayOptions(kind, val, mappedVals, allowed);
165 return Promise.resolve(mappedVals);
166 });
167}
168function buildStringArray(kind, options) {
169 const { options: values, ...rest } = options;
170 const allowed = new Set(values);
171 return option(kind, rest, (val) => {
172 const vals = convertArrayFlagToArray(val, options.delimiter);
173 validateArrayValues(kind, val, vals, options.validate);
174 validateArrayOptions(kind, val, vals, allowed);
175 return Promise.resolve(vals);
176 });
177}
178function buildArray(options) {
179 const kind = 'array';
180 return 'map' in options ? buildMappedArray(kind, options) : buildStringArray(kind, options);
181}
182function buildDate(options) {
183 const kind = 'date';
184 return option(kind, options, (val) => {
185 const parsed = Date.parse(val);
186 validateValue(!isNaN(parsed), val, kind, ` ${messages.getMessage('error.FormattingMessageDate')}`);
187 return Promise.resolve(new Date(parsed));
188 });
189}
190function buildDatetime(options) {
191 const kind = 'datetime';
192 return option(kind, options, (val) => {
193 const parsed = Date.parse(val);
194 validateValue(!isNaN(parsed), val, kind, ` ${messages.getMessage('error.FormattingMessageDate')}`);
195 return Promise.resolve(new Date(parsed));
196 });
197}
198function buildEmail(options) {
199 return option('email', options, (val) => {
200 return Promise.resolve(validateValue(core_2.sfdc.validateEmail(val), val, 'email'));
201 });
202}
203function buildId(options) {
204 return option('id', options, (val) => {
205 return Promise.resolve(validateValue(core_2.sfdc.validateSalesforceId(val), val, 'id', ` ${messages.getMessage('error.FormattingMessageId')}`));
206 });
207}
208function buildMilliseconds(options) {
209 const kind = 'milliseconds';
210 return option(kind, options, async (val) => {
211 const parsed = (0, kit_1.toNumber)(val);
212 validateValue(Number.isInteger(parsed), val, kind);
213 return Promise.resolve(kit_1.Duration.milliseconds(validateBounds(kind, parsed, options, (v) => ((0, ts_types_1.isNumber)(v) ? v : v[kind]))));
214 });
215}
216function buildMinutes(options) {
217 const kind = 'minutes';
218 return option(kind, options, async (val) => {
219 const parsed = (0, kit_1.toNumber)(val);
220 validateValue(Number.isInteger(parsed), val, kind);
221 return Promise.resolve(kit_1.Duration.minutes(validateBounds(kind, parsed, options, (v) => ((0, ts_types_1.isNumber)(v) ? v : v[kind]))));
222 });
223}
224function buildNumber(options) {
225 const kind = 'number';
226 return option(kind, options, async (val) => {
227 const parsed = (0, kit_1.toNumber)(val);
228 validateValue(isFinite(parsed), val, kind);
229 return validateBounds(kind, parsed, options, (t) => t);
230 });
231}
232function buildSeconds(options) {
233 const kind = 'seconds';
234 return option(kind, options, async (val) => {
235 const parsed = (0, kit_1.toNumber)(val);
236 validateValue(Number.isInteger(parsed), val, kind);
237 return Promise.resolve(kit_1.Duration.seconds(validateBounds(kind, parsed, options, (v) => ((0, ts_types_1.isNumber)(v) ? v : v[kind]))));
238 });
239}
240function buildUrl(options) {
241 return option('url', options, (val) => {
242 try {
243 return Promise.resolve(new url_1.URL(val));
244 }
245 catch (err) {
246 const correct = ` ${messages.getMessage('error.FormattingMessageId')}`;
247 throw messages.createError('error.InvalidFlagType', [val, 'url', correct || '']);
248 }
249 });
250}
251function buildBuiltin(options = {}) {
252 return { ...options, type: 'builtin' };
253}
254exports.flags = {
255 // oclif
256 /**
257 * A flag type whose presence indicates a `true` boolean value. Produces false when not present.
258 */
259 boolean: buildBoolean,
260 /**
261 * A flag type with a fixed enumeration of possible option values. Produces a validated string from the `options` list.
262 */
263 enum: buildEnum,
264 /**
265 * A flag type useful for overriding the short `char` trigger for emitting CLI help. Emits help and exits the CLI.
266 */
267 help: buildHelp,
268 /**
269 * A flag type that accepts basic integer values. For floats, binary, octal, and hex, see {@link flags.number}.
270 * Produces an integer `number`.
271 */
272 integer: buildInteger,
273 /**
274 * A flag type for custom string processing. Accepts a `parse` function that converts a `string` value to a type `T`.
275 * Produces a type `T`.
276 */
277 option: buildOption,
278 /**
279 * A flag type for returning a raw `string` value without further preprocessing. Produces a string.
280 */
281 string: buildString,
282 /**
283 * A flag type for emitting CLI version information. Emits the CLI version and exits the CLI.
284 */
285 version: buildVersion,
286 /**
287 * A flag type for valid file paths. Produces a validated string.
288 *
289 * **See** [@salesforce/core#sfdc.validatePathDoesNotContainInvalidChars](https://forcedotcom.github.io/sfdx-core/globals.html#sfdc), e.g. "this/is/my/path".
290 */
291 filepath: buildFilepath,
292 /**
293 * A flag type for valid directory paths. Produces a validated string.
294 *
295 * **See** [@salesforce/core#sfdc.validatePathDoesNotContainInvalidChars](https://forcedotcom.github.io/sfdx-core/globals.html#sfdc), e.g. "this/is/my/path".
296 */
297 directory: buildDirectory,
298 // sfdx
299 /**
300 * A flag type for a delimited list of strings with the delimiter defaulting to `,`, e.g., "one,two,three". Accepts
301 * an optional `delimiter` `string` and/or a custom `map` function for converting parsed `string` values into
302 * a type `T`. Produces a parsed (and possibly mapped) array of type `T` where `T` defaults to `string` if no
303 * custom `map` function was provided.
304 */
305 array: buildArray,
306 /**
307 * A flag type for a valid date, e.g., "01-02-2000" or "01/02/2000 01:02:34". Produces a parsed `Date`.
308 */
309 date: buildDate,
310 /**
311 * A flag type for a valid datetime, e.g., "01-02-2000" or "01/02/2000 01:02:34". Produces a parsed `Date`.
312 */
313 datetime: buildDatetime,
314 /**
315 * A flag type for valid email addresses. Produces a validated string.
316 *
317 * **See** [@salesforce/core#sfdc.validateEmail](https://forcedotcom.github.io/sfdx-core/globals.html#sfdc), e.g., "me@my.org".
318 */
319 email: buildEmail,
320 /**
321 * A flag type for valid Salesforce IDs. Produces a validated string.
322 *
323 * **See** [@salesforce/core#sfdc.validateSalesforceId](https://forcedotcom.github.io/sfdx-core/globals.html#sfdc), e.g., "00Dxxxxxxxxxxxx".
324 */
325 id: buildId,
326 /**
327 * A flag type for a valid `Duration` in milliseconds, e.g., "5000".
328 */
329 milliseconds: buildMilliseconds,
330 /**
331 * A flag type for a valid `Duration` in minutes, e.g., "2".
332 */
333 minutes: buildMinutes,
334 /**
335 * A flag type for valid integer or floating point number, e.g., "42". Additionally supports binary, octal, and hex
336 * notation. Produces a parsed `number`.
337 */
338 number: buildNumber,
339 /**
340 * A flag type for a valid `Duration` in seconds, e.g., "5".
341 */
342 seconds: buildSeconds,
343 /**
344 * A flag type for a valid url, e.g., "http://www.salesforce.com". Produces a parsed `URL` instance.
345 */
346 url: buildUrl,
347 // builtins
348 /**
349 * Declares a flag definition to be one of the builtin types, for automatic configuration.
350 */
351 builtin: buildBuiltin,
352};
353exports.requiredBuiltinFlags = {
354 json() {
355 return exports.flags.boolean({
356 description: messages.getMessage('flags.json.description'),
357 longDescription: messages.getMessage('flags.json.description.long'),
358 });
359 },
360 loglevel() {
361 return exports.flags.enum({
362 options: core_2.Logger.LEVEL_NAMES.concat(core_2.Logger.LEVEL_NAMES.map((l) => l.toUpperCase())),
363 default: core_2.LoggerLevel[core_2.Logger.DEFAULT_LEVEL].toLowerCase(),
364 required: false,
365 description: messages.getMessage('flags.loglevel.description'),
366 longDescription: messages.getMessage('flags.loglevel.description.long'),
367 parse: (val) => {
368 val = val.toLowerCase();
369 if (core_2.Logger.LEVEL_NAMES.includes(val))
370 return Promise.resolve(val);
371 throw messages.createError('error.InvalidLoggerLevel', [val]);
372 },
373 });
374 },
375};
376function resolve(opts, key, def) {
377 return (0, ts_types_1.hasString)(opts, key) ? opts[key] : def;
378}
379exports.optionalBuiltinFlags = {
380 apiversion(opts) {
381 return Object.assign(opts || {}, exports.flags.string({
382 description: resolve(opts, 'description', messages.getMessage('flags.apiversion.description')),
383 longDescription: resolve(opts, 'longDescription', messages.getMessage('flags.apiversion.description.long')),
384 parse: (val) => {
385 if (core_2.sfdc.validateApiVersion(val))
386 return Promise.resolve(val);
387 throw messages.createError('error.InvalidApiVersion', [val]);
388 },
389 }));
390 },
391 concise(opts) {
392 return Object.assign(opts || {}, exports.flags.boolean({
393 description: resolve(opts, 'description', messages.getMessage('flags.concise.description')),
394 longDescription: resolve(opts, 'longDescription', messages.getMessage('flags.long.description.long')),
395 }));
396 },
397 quiet(opts) {
398 return Object.assign(opts || {}, exports.flags.boolean({
399 description: resolve(opts, 'description', messages.getMessage('flags.quiet.description')),
400 longDescription: resolve(opts, 'longDescription', messages.getMessage('flags.quiet.description.long')),
401 }));
402 },
403 targetdevhubusername(opts) {
404 return Object.assign(opts || {}, exports.flags.string({
405 char: 'v',
406 description: resolve(opts, 'description', messages.getMessage('flags.targetdevhubusername.description')),
407 longDescription: resolve(opts, 'longDescription', messages.getMessage('flags.targetdevhubusername.description.long')),
408 }));
409 },
410 targetusername(opts) {
411 return Object.assign(opts || {}, exports.flags.string({
412 char: 'u',
413 description: resolve(opts, 'description', messages.getMessage('flags.targetusername.description')),
414 longDescription: resolve(opts, 'longDescription', messages.getMessage('flags.targetusername.description.long')),
415 }));
416 },
417 verbose(opts) {
418 return Object.assign(opts || {}, exports.flags.boolean({
419 description: resolve(opts, 'description', messages.getMessage('flags.verbose.description')),
420 longDescription: resolve(opts, 'longDescription', messages.getMessage('flags.verbose.description.long')),
421 }));
422 },
423};
424/**
425 * Validate the custom flag configuration. This includes:
426 *
427 * - The flag name is in all lowercase.
428 * - A string description is provided.
429 * - If a char attribute is provided, it is one alphabetical character in length.
430 * - If a long description is provided, it is a string.
431 *
432 * @param {SfdxFlagDefinition} flag The flag configuration.
433 * @param {string} key The flag name.
434 * @throws SfError If the criteria is not meet.
435 */
436function validateCustomFlag(key, flag) {
437 if (!/^(?!(?:[-]|[0-9]*$))[a-z0-9-]+$/.test(key)) {
438 throw messages.createError('error.InvalidFlagName', [key]);
439 }
440 if (flag.char && (flag.char.length !== 1 || !/[a-zA-Z]/.test(flag.char))) {
441 throw messages.createError('error.InvalidFlagChar', [key]);
442 }
443 if (!flag.description || !(0, ts_types_1.isString)(flag.description)) {
444 throw messages.createError('error.MissingOrInvalidFlagDescription', [key]);
445 }
446 if (flag.longDescription !== undefined && !(0, ts_types_1.isString)(flag.longDescription)) {
447 throw messages.createError('error.InvalidLongDescriptionFormat', [key]);
448 }
449 return flag;
450}
451// eslint-disable-next-line @typescript-eslint/ban-types
452function isBuiltin(flag) {
453 return (0, ts_types_1.hasString)(flag, 'type') && flag.type === 'builtin';
454}
455/**
456 * Builds flags for a command given a configuration object. Supports the following use cases:
457 * 1. Enabling common SFDX flags. E.g., { verbose: true }
458 * 2. Defining typed flags. E.g., { myFlag: Flags.array({ char: '-a' }) }
459 * 3. Defining custom typed flags. E.g., { myFlag: Flags.custom({ parse: (val) => parseInt(val, 10) }) }
460 *
461 * @param {FlagsConfig} flagsConfig The configuration object for a flag. @see {@link FlagsConfig}
462 * @param options Extra configuration options.
463 * @returns {flags.Output} The flags for the command.
464 * @ignore
465 */
466function buildSfdxFlags(flagsConfig, options
467// tslint:disable-next-line:no-any matches oclif
468) {
469 const output = {};
470 // Required flag options for all SFDX commands
471 output.json = exports.requiredBuiltinFlags.json();
472 output.loglevel = exports.requiredBuiltinFlags.loglevel();
473 if (options.targetdevhubusername)
474 output.targetdevhubusername = exports.optionalBuiltinFlags.targetdevhubusername();
475 if (options.targetusername)
476 output.targetusername = exports.optionalBuiltinFlags.targetusername();
477 if (options.targetdevhubusername || options.targetusername)
478 output.apiversion = exports.optionalBuiltinFlags.apiversion();
479 // Process configuration for custom and builtin flags
480 (0, ts_types_1.definiteEntriesOf)(flagsConfig).forEach(([key, flag]) => {
481 if (isBuiltin(flag)) {
482 if (!(0, ts_types_1.isKeyOf)(exports.optionalBuiltinFlags, key)) {
483 throw messages.createError('error.UnknownBuiltinFlagType', [key]);
484 }
485 output[key] = exports.optionalBuiltinFlags[key](flag);
486 }
487 else {
488 output[key] = validateCustomFlag(key, flag);
489 }
490 });
491 return output;
492}
493exports.buildSfdxFlags = buildSfdxFlags;
494//# sourceMappingURL=sfdxFlags.js.map
\No newline at end of file