UNPKG

@salesforce/plugin-org

Version:

Commands to interact with Salesforce orgs

201 lines 9.25 kB
/* * 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 */ import { Flags, loglevel, SfCommand } from '@salesforce/sf-plugins-core'; import { AuthInfo, Connection, Org, SfError, Messages, Logger } from '@salesforce/core'; import ansis from 'ansis'; import { OrgListUtil, identifyActiveOrgByStatus } from '../../shared/orgListUtil.js'; import { getStyledObject } from '../../shared/orgHighlighter.js'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('@salesforce/plugin-org', 'list'); export const defaultOrgEmoji = '🍁'; export const defaultHubEmoji = '🌳'; export class OrgListCommand extends SfCommand { static summary = messages.getMessage('summary'); static examples = messages.getMessages('examples'); static aliases = ['force:org:list']; static deprecateAliases = true; static flags = { verbose: Flags.boolean({ summary: messages.getMessage('flags.verbose.summary'), }), all: Flags.boolean({ summary: messages.getMessage('flags.all.summary'), }), clean: Flags.boolean({ summary: messages.getMessage('flags.clean.summary'), }), 'no-prompt': Flags.boolean({ char: 'p', summary: messages.getMessage('flags.no-prompt.summary'), relationships: [ { type: 'some', flags: [{ name: 'clean', when: async (flags) => Promise.resolve(flags['clean'] === true) }], }, ], aliases: ['noprompt'], deprecateAliases: true, }), 'skip-connection-status': Flags.boolean({ summary: messages.getMessage('flags.skip-connection-status.summary'), aliases: ['skipconnectionstatus'], deprecateAliases: true, }), loglevel, }; flags; async run() { const [{ flags }, fileNames] = await Promise.all([this.parse(OrgListCommand), getAuthFileNames()]); this.flags = flags; const metaConfigs = await OrgListUtil.readLocallyValidatedMetaConfigsGroupedByOrgType(fileNames, flags['skip-connection-status']); const groupedSortedOrgs = { devHubs: metaConfigs.devHubs.map(decorateWithDefaultStatus).sort(comparator), other: metaConfigs.other.map(decorateWithDefaultStatus).sort(comparator), sandboxes: metaConfigs.sandboxes.map(decorateWithDefaultStatus).sort(comparator), nonScratchOrgs: metaConfigs.nonScratchOrgs.map(decorateWithDefaultStatus).sort(comparator), scratchOrgs: metaConfigs.scratchOrgs.map(decorateWithDefaultStatus).sort(comparator), expiredScratchOrgs: metaConfigs.scratchOrgs.filter((org) => !identifyActiveOrgByStatus(org)), }; if (flags.clean && groupedSortedOrgs.expiredScratchOrgs.length > 0) { await this.cleanScratchOrgs(groupedSortedOrgs.expiredScratchOrgs, !flags['no-prompt']); } if (groupedSortedOrgs.expiredScratchOrgs.length > 10 && !flags.clean) { this.warn(messages.getMessage('deleteOrgs', [groupedSortedOrgs.expiredScratchOrgs.length])); } const result = { other: groupedSortedOrgs.other, sandboxes: groupedSortedOrgs.sandboxes, nonScratchOrgs: groupedSortedOrgs.nonScratchOrgs, devHubs: groupedSortedOrgs.devHubs, scratchOrgs: flags.all ? groupedSortedOrgs.scratchOrgs : groupedSortedOrgs.scratchOrgs.filter(identifyActiveOrgByStatus), }; this.printOrgTable({ devHubs: result.devHubs, other: result.other, sandboxes: result.sandboxes, scratchOrgs: result.scratchOrgs, skipconnectionstatus: flags['skip-connection-status'], }); this.info(` Legend: ${defaultHubEmoji}=Default DevHub, ${defaultOrgEmoji}=Default Org ${flags.all ? '' : ' Use --all to see expired and deleted scratch orgs'}`); return result; } async cleanScratchOrgs(scratchOrgs, prompt) { if (prompt && (await this.confirm({ message: messages.getMessage('prompt', [scratchOrgs.length]) })) === false) { return; } await Promise.all(scratchOrgs.map(async (fields) => { try { const authInfo = await AuthInfo.create({ username: fields.username }); const connection = await Connection.create({ authInfo, configAggregator: { // Force an api version to prevent connection check with the server for expired orgs. getInfo: () => ({ value: '47.0' }), }, }); const org = await Org.create({ aliasOrUsername: fields.username, connection }); await org.remove(); } catch (e) { this.warn(messages.getMessage('cleanWarning', [fields.username, this.config.bin, fields.username])); if (e instanceof Error) { const logger = await Logger.child('org:list'); logger.debug(`Error cleaning org ${fields.username}: ${e.message}`); } } })); } printOrgTable({ devHubs, scratchOrgs, other, sandboxes, skipconnectionstatus, }) { if (!devHubs.length && !other.length && !sandboxes.length) { this.info(messages.getMessage('noResultsFound')); return; } const allOrgs = [ ...devHubs .map(addType('DevHub')) .map(colorEveryFieldButConnectedStatus(ansis.cyanBright)) .map((row) => getStyledObject(row)) .map(statusToEmoji), ...other .map(colorEveryFieldButConnectedStatus(ansis.magentaBright)) .map((row) => getStyledObject(row)) .map(statusToEmoji), ...sandboxes .map(addType('Sandbox')) .map(colorEveryFieldButConnectedStatus(ansis.yellowBright)) .map((row) => getStyledObject(row)) .map(statusToEmoji), ...scratchOrgs .map((row) => ({ ...row, type: 'Scratch' })) .map(convertScratchOrgStatus) .map((row) => getStyledObject(row)) .map(statusToEmoji), ]; this.table({ data: allOrgs.map((row) => ({ ' ': row.defaultMarker, ...('type' in row ? { Type: row.type } : {}), Alias: row.alias, Username: row.username, 'Org ID': row.orgId, ...(skipconnectionstatus ? {} : { Status: row.connectedStatus }), ...(this.flags.verbose ? { 'Instance URL': row.instanceUrl, Namespace: row.namespacePrefix, ...('devHubOrgId' in row ? { 'Dev Hub ID': row.devHubOrgId } : {}), ...('createdDate' in row ? { Created: row.createdDate } : {}), } : {}), ...('expirationDate' in row ? { Expires: row.expirationDate } : {}), })), ...(this.flags.verbose ? { overflow: 'wrap' } : {}), }); } } const decorateWithDefaultStatus = (val) => ({ ...val, ...(val.isDefaultDevHubUsername ? { defaultMarker: '(D)' } : {}), ...(val.isDefaultUsername ? { defaultMarker: '(U)' } : {}), ...(val.isDefaultDevHubUsername && val.isDefaultUsername ? { defaultMarker: '(D),(U)' } : {}), }); const statusToEmoji = (val) => ({ ...val, defaultMarker: val.defaultMarker?.replace('(D)', defaultHubEmoji)?.replace('(U)', defaultOrgEmoji), }); const EMPTIES_LAST = 'zzzzzzzzzz'; // sort by alias then username const comparator = (a, b) => { const aliasCompareResult = (a.alias ?? EMPTIES_LAST).localeCompare(b.alias ?? EMPTIES_LAST); return aliasCompareResult !== 0 ? aliasCompareResult : (a.username ?? EMPTIES_LAST).localeCompare(b.username); }; const getAuthFileNames = async () => { try { return ((await AuthInfo.listAllAuthorizations()) ?? []).map((auth) => auth.username); } catch (err) { const error = err; if (error.name === 'NoAuthInfoFound') { throw new SfError(messages.getMessage('noOrgsFound'), 'noOrgsFound', [messages.getMessage('noOrgsFoundAction')]); } else { throw error; } } }; const addType = (type) => (val) => ({ ...val, type }); const colorEveryFieldButConnectedStatus = (colorFn) => (row) => Object.fromEntries(Object.entries(row).map(([key, val]) => [ key, typeof val === 'string' && key !== 'connectedStatus' ? colorFn(val) : val, ]) // TS is not smart enough to know this didn't change any types ); const convertScratchOrgStatus = (row) => ({ ...row, connectedStatus: row.status }); //# sourceMappingURL=list.js.map