UNPKG

@salesforce/plugin-telemetry

Version:

Command usage and error telemetry for the Salesforce CLI

206 lines 9.38 kB
/* * Copyright 2025, Salesforce, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { Flags, Parser } from '@oclif/core'; import { AsyncCreatable } from '@salesforce/kit'; import { isNumber } from '@salesforce/ts-types'; import { parseVarArgs } from '@salesforce/sf-plugins-core'; import { debug } from './debugger.js'; import { getRelevantEnvs } from './gatherEnvs.js'; export class CommandExecution extends AsyncCreatable { status; specifiedFlags = []; specifiedFlagFullNames = []; deprecatedFlagsUsed = []; deprecatedCommandUsed; command; argv; config; orgId; devhubId; agentPseudoTypeUsed; orgApiVersion; devhubApiVersion; argKeys = []; constructor(options) { super(options); this.command = options.command; this.argv = options.argv; this.config = options.config; } /** * @deprecated. Will always return en empty string. */ static async resolveVCSInfo() { return Promise.resolve(''); } toJson() { const pluginInfo = this.getPluginInfo(); const envs = getRelevantEnvs(); return { eventName: 'COMMAND_EXECUTION', // System information platform: this.config.platform, shell: this.config.shell, arch: this.config.arch, nodeEnv: process.env.NODE_ENV, nodeVersion: process.version, processUptime: process.uptime() * 1000, // CLI information version: this.config.version, channel: this.config.channel, executable: this.config.bin, origin: this.config.userAgent, plugin: pluginInfo.name, // eslint-disable-next-line camelcase plugin_version: pluginInfo.version, command: this.command.id, // As the user specified, including short names specifiedFlags: this.specifiedFlags.sort().join(' '), // Flags the user specified, only the full names specifiedFlagFullNames: this.specifiedFlagFullNames.sort().join(' '), agentPseudoTypeUsed: this.agentPseudoTypeUsed ?? false, deprecatedFlagsUsed: this.deprecatedFlagsUsed.sort().join(' '), deprecatedCommandUsed: this.deprecatedCommandUsed, sfdxEnv: process.env.SFDX_ENV, s3HostOverride: process.env.SF_S3_HOST ?? process.env.SFDX_S3_HOST, npmRegistryOverride: process.env.SF_NPM_REGISTRY ?? process.env.SFDX_NPM_REGISTRY, tool: process.env.SFDX_TOOL, // Execution information date: new Date().toUTCString(), // Don't log status or timestamp as a number, otherwise vscode will think it is a metric status: isNumber(this.status) ? this.status.toString() : undefined, timestamp: String(Date.now()), // Salesforce Information orgId: this.orgId, devhubId: this.devhubId, orgApiVersion: this.orgApiVersion, devhubApiVersion: this.devhubApiVersion, specifiedEnvs: envs.specifiedEnvs.join(' '), uniqueEnvs: envs.uniqueEnvs.join(' '), argKeys: this.argKeys.sort().join(' '), }; } getPluginInfo() { return { name: this.command.plugin?.name, version: this.command.plugin?.version, }; } getCommandName() { return this.command.id; } async init() { const argv = this.argv; const flagDefinitions = { ...this.command.flags, ...this.command.baseFlags, ...(this.command.enableJsonFlag ? { json: Flags.boolean() } : {}), }; // slice off node or bin path, and the executable path, and then remove anything that's been processed as a flag const typedCommand = process.argv .splice(2) .filter((arg) => !argv.includes(arg)) .join(':'); if (Boolean(this.command.deprecationOptions) || (typedCommand !== this.command.id && this.command.aliases?.includes(typedCommand) && this.command.deprecateAliases)) { // check the deprecationOptions for cases where we've used OCLIF to point to a replacement // check the aliases and deprecated aliases for where we're deprecating in place this.deprecatedCommandUsed = typedCommand; } let flags = {}; try { const parseResult = await Parser.parse(argv, { flags: flagDefinitions, args: this.command.args, // @ts-expect-error because varargs is not on SfCommand but is on SfdxCommand strict: this.command.strict ?? !this.command.varargs, }); flags = parseResult.flags; this.agentPseudoTypeUsed = flags['metadata']?.some((metadata) => metadata.toLowerCase().startsWith('agent')) ?? false; this.argKeys = [...new Set(Object.keys(parseVarArgs(parseResult.args, parseResult.argv)))]; } catch (error) { debug('Error parsing flags'); } this.orgId = flags['target-org'] ? flags['target-org'].getOrgId() : null; this.devhubId = flags['target-dev-hub'] ? flags['target-dev-hub'].getOrgId() : null; this.orgApiVersion = flags['target-org'] ? flags['target-org'].getConnection().getApiVersion() : null; this.devhubApiVersion = flags['target-dev-hub'] ? flags['target-dev-hub'].getConnection().getApiVersion() : null; this.determineSpecifiedFlags(argv, flags, flagDefinitions); } determineSpecifiedFlags(argv, flags, flagDefinitions) { // Help won't be in the parsed flags const shortHelp = argv.find((arg) => /^-h$/.test(arg)); const fullHelp = argv.find((arg) => /^--help$/.test(arg)); if (Boolean(shortHelp) || fullHelp) { if (shortHelp) { this.specifiedFlags.push('h'); } else { this.specifiedFlags.push('help'); } this.specifiedFlagFullNames.push('help'); // All other flags don't matter if help is specified, so end here. } else { Object.keys(flags).forEach((flagName) => { const shortCode = flagDefinitions[flagName] && flagDefinitions[flagName].char; // Oclif will include the flag if there is a default, but we only want to add it if the // user specified it, so confirm in the argv list. if (shortCode && argv.find((arg) => new RegExp(`^-${shortCode}(=.*)?$`).test(arg))) { this.specifiedFlags.push(shortCode); this.specifiedFlagFullNames.push(flagName); } else if (argv.find((arg) => new RegExp(`^--${flagName}(=.*)?$`).test(arg))) { this.specifiedFlags.push(flagName); this.specifiedFlagFullNames.push(flagName); } else { // we can't find the flag as the key (long name) or short char, so it must be an alias // get present flags in argv, that is everything starting with `-`, then strip dashes from it. // e.g. ['-u', 'test', '--json'] -> [ 'u', undefined, 'json' ] const argvFlags = this.argv.map((a) => a.match(/-([a-zA-Z]+)/g)).map((a) => a?.[0].replace('-', '')); const possibleAliases = [ ...(flagDefinitions[flagName].aliases ?? []), // charAliases is optional. Ensure compatibility with commands using oclif/core < 3 where `charAliases` isn't supported. ...(flagDefinitions[flagName].charAliases ?? []), ]; // if you have a flag that sets a default value and has aliases // this check will ensure it only gets captured if the user specified it using aliases const specifiedFlag = possibleAliases.find((a) => argvFlags.includes(a)); if (specifiedFlag) { // save flag with their original name instead of the typed alias this.specifiedFlagFullNames.push(flagName); this.specifiedFlags.push(flagName); // user typed a deprecated alias if (flagDefinitions[flagName].deprecateAliases) { this.deprecatedFlagsUsed.push(specifiedFlag); } } } }); } } } //# sourceMappingURL=commandExecution.js.map