UNPKG

@salesforce/command

Version:
489 lines 20.4 kB
"use strict"; /* * Copyright (c) 2020, salesforce.com, inc. * All rights reserved. * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ Object.defineProperty(exports, "__esModule", { value: true }); exports.buildSfdxFlags = exports.optionalBuiltinFlags = exports.requiredBuiltinFlags = exports.flags = void 0; const url_1 = require("url"); const core_1 = require("@oclif/core"); const core_2 = require("@salesforce/core"); const kit_1 = require("@salesforce/kit"); const ts_types_1 = require("@salesforce/ts-types"); core_2.Messages.importMessagesDirectory(__dirname); const messages = core_2.Messages.load('@salesforce/command', 'flags', [ 'error.UnknownBuiltinFlagType', 'error.FormattingMessageArrayValue', 'error.FormattingMessageArrayOption', 'error.FormattingMessageDate', 'error.FormattingMessageId', 'error.FormattingMessageId', 'error.InvalidFlagType', 'flags.json.description.long', 'flags.json.description', 'error.InvalidLoggerLevel', 'error.InvalidApiVersion', 'flags.apiversion.description', 'flags.apiversion.description.long', 'flags.concise.description', 'flags.long.description.long', 'flags.loglevel.description', 'flags.loglevel.description.long', 'flags.quiet.description', 'flags.quiet.description.long', 'flags.targetdevhubusername.description', 'flags.targetdevhubusername.description.long', 'flags.targetusername.description', 'flags.targetusername.description.long', 'flags.verbose.description', 'flags.verbose.description.long', 'error.InvalidFlagName', 'error.InvalidFlagChar', 'error.MissingOrInvalidFlagDescription', 'error.InvalidLongDescriptionFormat', ]); function validateValue(isValid, value, kind, correct) { if (isValid) return value; throw messages.createError('error.InvalidFlagType', [value, kind, correct ?? '']); } function toValidatorFn(validator) { return (val) => { if ((0, ts_types_1.isString)(validator)) return new RegExp(validator).test(val); if ((0, ts_types_1.isInstance)(validator, RegExp)) return validator.test(val); if ((0, ts_types_1.isFunction)(validator)) return !!validator(val); return true; }; } function merge(kind, flag, describable) { if ((0, ts_types_1.has)(flag, 'validate') && (0, ts_types_1.hasFunction)(flag, 'parse')) { const parse = flag.parse.bind(flag); flag.parse = (val, ctx) => { validateValue(toValidatorFn(flag.validate)(val), val, kind); return parse(val, ctx); }; } // @ts-ignore return { kind, ...flag, description: describable.description, longDescription: describable.longDescription, }; } function option(kind, options, parse) { const flag = core_1.Flags.option({ ...options, parse }); return merge(kind, flag, options); } // oclif function buildBoolean(options) { const flag = core_1.Flags.boolean(options); return merge('boolean', flag, options); } function buildEnum(options) { return { kind: 'enum', ...core_1.Flags.enum(options), options: options.options, description: options.description, longDescription: options.longDescription, }; } function buildHelp(options) { const flag = core_1.Flags.help(options); return merge('help', core_1.Flags.help(options), { description: (0, ts_types_1.ensure)(flag.description), }); } function buildFilepath(options) { return option('filepath', options, (val) => Promise.resolve(validateValue(core_2.sfdc.validatePathDoesNotContainInvalidChars(val), val, 'filepath'))); } function buildDirectory(options) { return option('directory', options, (val) => Promise.resolve(validateValue(core_2.sfdc.validatePathDoesNotContainInvalidChars(val), val, 'directory'))); } function validateBounds(kind, value, bounds, extract) { if (bounds.min != null && value < extract(bounds.min)) { throw new core_2.SfError(`Expected ${kind} greater than or equal to ${extract(bounds.min)} but received ${value}`, 'InvalidFlagNumericBoundsError'); } if (bounds.max != null && value > extract(bounds.max)) { throw new core_2.SfError(`Expected ${kind} less than or equal to ${extract(bounds.max)} but received ${value}`, 'InvalidFlagNumericBoundsError'); } return value; } function buildInteger(options) { const kind = 'integer'; return option(kind, options, async (val) => { const parsed = (0, kit_1.toNumber)(val); validateValue(Number.isInteger(parsed), val, kind); return validateBounds(kind, parsed, options, (t) => t); }); } function buildOption(options) { const optsFlag = core_1.Flags.option(options); return merge('option', optsFlag, options); } function buildString(options) { return option('string', options, options.parse ?? ((val) => Promise.resolve(val))); } function buildVersion(options) { const flag = core_1.Flags.version(options); return merge('version', flag, { description: (0, ts_types_1.ensure)(flag.description), }); } // sfdx function validateArrayValues(kind, raw, vals, validator) { validateValue(vals.every(toValidatorFn(validator)), raw, kind, ` ${messages.getMessage('error.FormattingMessageArrayValue')}`); } function validateArrayOptions(kind, raw, vals, allowed) { validateValue(allowed.size === 0 || vals.every((t) => allowed.has(t)), raw, kind, ` ${messages.getMessage('error.FormattingMessageArrayOption', [Array.from(allowed).toString()])}`); } const convertArrayFlagToArray = (flagValue, delimiter = ',') => { // don't split on delimiter if it's inside a single or double-quoted substring // eslint-disable-next-line no-useless-escape const regex = new RegExp(`"(.*?)"|\'(.*?)\'|${delimiter}`); return flagValue .split(regex) .filter((i) => !!i) .map((i) => i.trim()); }; function buildMappedArray(kind, options) { const { options: values, ...rest } = options; const allowed = new Set(values); return option(kind, rest, (val) => { const vals = convertArrayFlagToArray(val, options.delimiter); validateArrayValues(kind, val, vals, options.validate); const mappedVals = vals.map(options.map); validateArrayOptions(kind, val, mappedVals, allowed); return Promise.resolve(mappedVals); }); } function buildStringArray(kind, options) { const { options: values, ...rest } = options; const allowed = new Set(values); return option(kind, rest, (val) => { const vals = convertArrayFlagToArray(val, options.delimiter); validateArrayValues(kind, val, vals, options.validate); validateArrayOptions(kind, val, vals, allowed); return Promise.resolve(vals); }); } function buildArray(options) { const kind = 'array'; return 'map' in options ? buildMappedArray(kind, options) : buildStringArray(kind, options); } function buildDate(options) { const kind = 'date'; return option(kind, options, (val) => { const parsed = Date.parse(val); validateValue(!isNaN(parsed), val, kind, ` ${messages.getMessage('error.FormattingMessageDate')}`); return Promise.resolve(new Date(parsed)); }); } function buildDatetime(options) { const kind = 'datetime'; return option(kind, options, (val) => { const parsed = Date.parse(val); validateValue(!isNaN(parsed), val, kind, ` ${messages.getMessage('error.FormattingMessageDate')}`); return Promise.resolve(new Date(parsed)); }); } function buildEmail(options) { return option('email', options, (val) => Promise.resolve(validateValue(core_2.sfdc.validateEmail(val), val, 'email'))); } function buildId(options) { return option('id', options, (val) => Promise.resolve(validateValue(core_2.sfdc.validateSalesforceId(val), val, 'id', ` ${messages.getMessage('error.FormattingMessageId')}`))); } function buildMilliseconds(options) { const kind = 'milliseconds'; return option(kind, options, async (val) => { const parsed = (0, kit_1.toNumber)(val); validateValue(Number.isInteger(parsed), val, kind); return Promise.resolve(kit_1.Duration.milliseconds(validateBounds(kind, parsed, options, (v) => ((0, ts_types_1.isNumber)(v) ? v : v[kind])))); }); } function buildMinutes(options) { const kind = 'minutes'; return option(kind, options, async (val) => { const parsed = (0, kit_1.toNumber)(val); validateValue(Number.isInteger(parsed), val, kind); return Promise.resolve(kit_1.Duration.minutes(validateBounds(kind, parsed, options, (v) => ((0, ts_types_1.isNumber)(v) ? v : v[kind])))); }); } function buildNumber(options) { const kind = 'number'; return option(kind, options, async (val) => { const parsed = (0, kit_1.toNumber)(val); validateValue(isFinite(parsed), val, kind); return validateBounds(kind, parsed, options, (t) => t); }); } function buildSeconds(options) { const kind = 'seconds'; return option(kind, options, async (val) => { const parsed = (0, kit_1.toNumber)(val); validateValue(Number.isInteger(parsed), val, kind); return Promise.resolve(kit_1.Duration.seconds(validateBounds(kind, parsed, options, (v) => ((0, ts_types_1.isNumber)(v) ? v : v[kind])))); }); } function buildUrl(options) { return option('url', options, (val) => { try { return Promise.resolve(new url_1.URL(val)); } catch (err) { const correct = ` ${messages.getMessage('error.FormattingMessageId')}`; throw messages.createError('error.InvalidFlagType', [val, 'url', correct || '']); } }); } function buildBuiltin(options = {}) { return { ...options, type: 'builtin' }; } exports.flags = { // oclif /** * A flag type whose presence indicates a `true` boolean value. Produces false when not present. */ boolean: buildBoolean, /** * A flag type with a fixed enumeration of possible option values. Produces a validated string from the `options` list. */ enum: buildEnum, /** * A flag type useful for overriding the short `char` trigger for emitting CLI help. Emits help and exits the CLI. */ help: buildHelp, /** * A flag type that accepts basic integer values. For floats, binary, octal, and hex, see {@link flags.number}. * Produces an integer `number`. */ integer: buildInteger, /** * A flag type for custom string processing. Accepts a `parse` function that converts a `string` value to a type `T`. * Produces a type `T`. */ option: buildOption, /** * A flag type for returning a raw `string` value without further preprocessing. Produces a string. */ string: buildString, /** * A flag type for emitting CLI version information. Emits the CLI version and exits the CLI. */ version: buildVersion, /** * A flag type for valid file paths. Produces a validated string. * * **See** [@salesforce/core#sfdc.validatePathDoesNotContainInvalidChars](https://forcedotcom.github.io/sfdx-core/globals.html#sfdc), e.g. "this/is/my/path". */ filepath: buildFilepath, /** * A flag type for valid directory paths. Produces a validated string. * * **See** [@salesforce/core#sfdc.validatePathDoesNotContainInvalidChars](https://forcedotcom.github.io/sfdx-core/globals.html#sfdc), e.g. "this/is/my/path". */ directory: buildDirectory, // sfdx /** * A flag type for a delimited list of strings with the delimiter defaulting to `,`, e.g., "one,two,three". Accepts * an optional `delimiter` `string` and/or a custom `map` function for converting parsed `string` values into * a type `T`. Produces a parsed (and possibly mapped) array of type `T` where `T` defaults to `string` if no * custom `map` function was provided. */ array: buildArray, /** * A flag type for a valid date, e.g., "01-02-2000" or "01/02/2000 01:02:34". Produces a parsed `Date`. */ date: buildDate, /** * A flag type for a valid datetime, e.g., "01-02-2000" or "01/02/2000 01:02:34". Produces a parsed `Date`. */ datetime: buildDatetime, /** * A flag type for valid email addresses. Produces a validated string. * * **See** [@salesforce/core#sfdc.validateEmail](https://forcedotcom.github.io/sfdx-core/globals.html#sfdc), e.g., "me@my.org". */ email: buildEmail, /** * A flag type for valid Salesforce IDs. Produces a validated string. * * **See** [@salesforce/core#sfdc.validateSalesforceId](https://forcedotcom.github.io/sfdx-core/globals.html#sfdc), e.g., "00Dxxxxxxxxxxxx". */ id: buildId, /** * A flag type for a valid `Duration` in milliseconds, e.g., "5000". */ milliseconds: buildMilliseconds, /** * A flag type for a valid `Duration` in minutes, e.g., "2". */ minutes: buildMinutes, /** * A flag type for valid integer or floating point number, e.g., "42". Additionally supports binary, octal, and hex * notation. Produces a parsed `number`. */ number: buildNumber, /** * A flag type for a valid `Duration` in seconds, e.g., "5". */ seconds: buildSeconds, /** * A flag type for a valid url, e.g., "http://www.salesforce.com". Produces a parsed `URL` instance. */ url: buildUrl, // builtins /** * Declares a flag definition to be one of the builtin types, for automatic configuration. */ builtin: buildBuiltin, }; exports.requiredBuiltinFlags = { json() { return exports.flags.boolean({ description: messages.getMessage('flags.json.description'), longDescription: messages.getMessage('flags.json.description.long'), }); }, loglevel() { return exports.flags.enum({ options: core_2.Logger.LEVEL_NAMES.concat(core_2.Logger.LEVEL_NAMES.map((l) => l.toUpperCase())), default: core_2.LoggerLevel[core_2.Logger.DEFAULT_LEVEL].toLowerCase(), required: false, description: messages.getMessage('flags.loglevel.description'), longDescription: messages.getMessage('flags.loglevel.description.long'), parse: (val) => { val = val.toLowerCase(); if (core_2.Logger.LEVEL_NAMES.includes(val)) return Promise.resolve(val); throw messages.createError('error.InvalidLoggerLevel', [val]); }, }); }, }; function resolve(opts, key, def) { return (0, ts_types_1.hasString)(opts, key) ? opts[key] : def; } exports.optionalBuiltinFlags = { apiversion(opts) { return Object.assign(opts ?? {}, exports.flags.string({ description: resolve(opts, 'description', messages.getMessage('flags.apiversion.description')), longDescription: resolve(opts, 'longDescription', messages.getMessage('flags.apiversion.description.long')), parse: (val) => { if (core_2.sfdc.validateApiVersion(val)) return Promise.resolve(val); throw messages.createError('error.InvalidApiVersion', [val]); }, })); }, concise(opts) { return Object.assign(opts ?? {}, exports.flags.boolean({ description: resolve(opts, 'description', messages.getMessage('flags.concise.description')), longDescription: resolve(opts, 'longDescription', messages.getMessage('flags.long.description.long')), })); }, quiet(opts) { return Object.assign(opts ?? {}, exports.flags.boolean({ description: resolve(opts, 'description', messages.getMessage('flags.quiet.description')), longDescription: resolve(opts, 'longDescription', messages.getMessage('flags.quiet.description.long')), })); }, targetdevhubusername(opts) { return Object.assign(opts ?? {}, exports.flags.string({ char: 'v', description: resolve(opts, 'description', messages.getMessage('flags.targetdevhubusername.description')), longDescription: resolve(opts, 'longDescription', messages.getMessage('flags.targetdevhubusername.description.long')), })); }, targetusername(opts) { return Object.assign(opts ?? {}, exports.flags.string({ char: 'u', description: resolve(opts, 'description', messages.getMessage('flags.targetusername.description')), longDescription: resolve(opts, 'longDescription', messages.getMessage('flags.targetusername.description.long')), })); }, verbose(opts) { return Object.assign(opts ?? {}, exports.flags.boolean({ description: resolve(opts, 'description', messages.getMessage('flags.verbose.description')), longDescription: resolve(opts, 'longDescription', messages.getMessage('flags.verbose.description.long')), })); }, }; /** * Validate the custom flag configuration. This includes: * * - The flag name is in all lowercase. * - A string description is provided. * - If a char attribute is provided, it is one alphabetical character in length. * - If a long description is provided, it is a string. * * @param {SfdxFlagDefinition} flag The flag configuration. * @param {string} key The flag name. * @throws SfError If the criteria is not meet. */ function validateCustomFlag(key, flag) { if (!/^(?!(?:[-]|[0-9]*$))[a-z0-9-]+$/.test(key)) { throw messages.createError('error.InvalidFlagName', [key]); } if (flag.char && (flag.char.length !== 1 || !/[a-zA-Z]/.test(flag.char))) { throw messages.createError('error.InvalidFlagChar', [key]); } if (!flag.description || !(0, ts_types_1.isString)(flag.description)) { throw messages.createError('error.MissingOrInvalidFlagDescription', [key]); } if (flag.longDescription !== undefined && !(0, ts_types_1.isString)(flag.longDescription)) { throw messages.createError('error.InvalidLongDescriptionFormat', [key]); } return flag; } // eslint-disable-next-line @typescript-eslint/ban-types function isBuiltin(flag) { return (0, ts_types_1.hasString)(flag, 'type') && flag.type === 'builtin'; } /** * Builds flags for a command given a configuration object. Supports the following use cases: * 1. Enabling common SFDX flags. E.g., { verbose: true } * 2. Defining typed flags. E.g., { myFlag: Flags.array({ char: '-a' }) } * 3. Defining custom typed flags. E.g., { myFlag: Flags.custom({ parse: (val) => parseInt(val, 10) }) } * * @param {FlagsConfig} flagsConfig The configuration object for a flag. @see {@link FlagsConfig} * @param options Extra configuration options. * @returns {flags.Output} The flags for the command. * @ignore */ function buildSfdxFlags(flagsConfig, options) { // Required flag options for all SFDX commands const output = { json: exports.requiredBuiltinFlags.json(), loglevel: exports.requiredBuiltinFlags.loglevel(), }; if (options.targetdevhubusername) output.targetdevhubusername = exports.optionalBuiltinFlags.targetdevhubusername(); if (options.targetusername) output.targetusername = exports.optionalBuiltinFlags.targetusername(); if (options.targetdevhubusername || options.targetusername) output.apiversion = exports.optionalBuiltinFlags.apiversion(); // Process configuration for custom and builtin flags (0, ts_types_1.definiteEntriesOf)(flagsConfig).forEach(([key, flag]) => { if (isBuiltin(flag)) { if (!(0, ts_types_1.isKeyOf)(exports.optionalBuiltinFlags, key)) { throw messages.createError('error.UnknownBuiltinFlagType', [key]); } // @ts-ignore output[key] = exports.optionalBuiltinFlags[key](flag); } else { // @ts-ignore output[key] = validateCustomFlag(key, flag); } }); return output; } exports.buildSfdxFlags = buildSfdxFlags; //# sourceMappingURL=sfdxFlags.js.map