From 2b810625e7a4aec5a2993977f57641ec353665f3 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Thu, 3 Aug 2023 17:55:15 -0500 Subject: [PATCH 1/9] feat: more tables by org type --- messages/list.md | 2 +- src/commands/org/list.ts | 64 ++++++++++++++++++++++++--------------- src/shared/orgListUtil.ts | 10 ++++++ 3 files changed, 50 insertions(+), 26 deletions(-) diff --git a/messages/list.md b/messages/list.md index fb4e5b4b..d986cfa5 100644 --- a/messages/list.md +++ b/messages/list.md @@ -58,7 +58,7 @@ Use one of the "org login" commands or "org create scratch" to add or create a s # noResultsFound -No non-scratch orgs found. +No %s found. # cleanWarning diff --git a/src/commands/org/list.ts b/src/commands/org/list.ts index a82c42af..7ea8f4ef 100644 --- a/src/commands/org/list.ts +++ b/src/commands/org/list.ts @@ -16,9 +16,16 @@ Messages.importMessagesDirectory(__dirname); const messages = Messages.loadMessages('@salesforce/plugin-org', 'list'); export type OrgListResult = { + /** + * @deprecated + * preserved for backward json compatibility. Duplicates devHubs, sandboxes, regularOrgs, which should be preferred*/ nonScratchOrgs: ExtendedAuthFields[]; scratchOrgs: FullyPopulatedScratchOrgFields[]; + sandboxes: ExtendedAuthFields[]; + regularOrgs: ExtendedAuthFields[]; + devHubs: ExtendedAuthFields[]; }; + export class OrgListCommand extends SfCommand { public static readonly summary = messages.getMessage('summary'); public static readonly examples = messages.getMessages('examples'); @@ -56,6 +63,9 @@ export class OrgListCommand extends SfCommand { this.flags = flags; const metaConfigs = await OrgListUtil.readLocallyValidatedMetaConfigsGroupedByOrgType(fileNames, flags); const groupedSortedOrgs = { + devHubs: metaConfigs.devHubs.map(decorateWithDefaultStatus).sort(comparator), + regularOrgs: metaConfigs.regularOrgs.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)), @@ -70,13 +80,19 @@ export class OrgListCommand extends SfCommand { } const result = { + regularOrgs: groupedSortedOrgs.regularOrgs, + sandboxes: groupedSortedOrgs.sandboxes, nonScratchOrgs: groupedSortedOrgs.nonScratchOrgs, + devHubs: groupedSortedOrgs.devHubs, scratchOrgs: flags.all ? groupedSortedOrgs.scratchOrgs : groupedSortedOrgs.scratchOrgs.filter(identifyActiveOrgByStatus), }; - this.printOrgTable(result.nonScratchOrgs, flags['skip-connection-status']); + // this.printOrgTable(result.nonScratchOrgs, flags['skip-connection-status']); + this.printOrgTable(result.devHubs, flags['skip-connection-status'], 'DevHubs'); + this.printOrgTable(result.regularOrgs, flags['skip-connection-status'], 'Orgs'); + this.printOrgTable(result.sandboxes, flags['skip-connection-status'], 'Sandboxes'); this.printScratchOrgTable(result.scratchOrgs); @@ -112,10 +128,11 @@ export class OrgListCommand extends SfCommand { ); } - protected printOrgTable(nonScratchOrgs: ExtendedAuthFields[], skipconnectionstatus: boolean): void { + protected printOrgTable(nonScratchOrgs: ExtendedAuthFields[], skipconnectionstatus: boolean, title: string): void { if (!nonScratchOrgs.length) { - this.log(messages.getMessage('noResultsFound')); + this.info(messages.getMessage('noResultsFound', [title])); } else { + this.info(title); const rows = nonScratchOrgs .map((row) => getStyledObject(row)) .map((org) => @@ -126,32 +143,28 @@ export class OrgListCommand extends SfCommand { ) ); - this.table( - rows, - { - defaultMarker: { - header: '', - get: (data): string => data.defaultMarker ?? '', - }, - alias: { - header: 'ALIAS', - get: (data): string => data.alias ?? '', - }, - username: { header: 'USERNAME' }, - orgId: { header: 'ORG ID' }, - ...(!skipconnectionstatus ? { connectedStatus: { header: 'CONNECTED STATUS' } } : {}), + this.table(rows, { + defaultMarker: { + header: '', + get: (data): string => data.defaultMarker ?? '', }, - { - title: 'Non-scratch orgs', - } - ); + alias: { + header: 'ALIAS', + get: (data): string => data.alias ?? '', + }, + username: { header: 'USERNAME' }, + orgId: { header: 'ORG ID' }, + ...(!skipconnectionstatus ? { connectedStatus: { header: 'CONNECTED STATUS' } } : {}), + }); } + this.log(); } private printScratchOrgTable(scratchOrgs: FullyPopulatedScratchOrgFields[]): void { if (scratchOrgs.length === 0) { - this.log(messages.getMessage('noActiveScratchOrgs')); + this.info(messages.getMessage('noActiveScratchOrgs')); } else { + this.info(this.flags.all ? 'Scratch Orgs' : 'Active Scratch Orgs (use --all to see all)'); // One or more rows are available. // we only need a few of the props for our table. Oclif table doesn't like extra props non-string props. const rows = scratchOrgs @@ -195,12 +208,13 @@ export class OrgListCommand extends SfCommand { } : {}), expirationDate: { header: 'EXPIRATION DATE' }, - }, - { - title: 'Scratch orgs', } + // { + // title: 'Scratch orgs', + // } ); } + this.log(); } } diff --git a/src/shared/orgListUtil.ts b/src/shared/orgListUtil.ts index e979861b..eb1d11f0 100644 --- a/src/shared/orgListUtil.ts +++ b/src/shared/orgListUtil.ts @@ -38,6 +38,9 @@ type OrgGroups = { type OrgGroupsFullyPopulated = { nonScratchOrgs: ExtendedAuthFields[]; scratchOrgs: FullyPopulatedScratchOrgFields[]; + regularOrgs: ExtendedAuthFields[]; + sandboxes: ExtendedAuthFields[]; + devHubs: ExtendedAuthFields[]; }; type ExtendedScratchOrgInfo = Record & @@ -93,6 +96,9 @@ export class OrgListUtil { return { nonScratchOrgs, scratchOrgs, + sandboxes: nonScratchOrgs.filter(sandboxFilter), + regularOrgs: nonScratchOrgs.filter(regularOrgFilter), + devHubs: nonScratchOrgs.filter(devHubFilter), }; } @@ -367,3 +373,7 @@ const removeRestrictedInfoFromConfig = ( config: AuthFieldsFromFS, properties: string[] = ['refreshToken', 'clientSecret'] ): AuthFieldsFromFS => omit>(config, properties); + +const sandboxFilter = (org: AuthFieldsFromFS): boolean => Boolean(org.isSandbox); +const regularOrgFilter = (org: AuthFieldsFromFS): boolean => !org.isSandbox && !org.isDevHub; +const devHubFilter = (org: AuthFieldsFromFS): boolean => Boolean(org.isDevHub); From 58358574fe8c008acc28db5d56763fd54e7e39a0 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Fri, 4 Aug 2023 13:23:34 -0500 Subject: [PATCH 2/9] feat: color coding --- messages/list.md | 2 +- src/commands/org/list.ts | 184 +++++++++++++++++++++++---------------- 2 files changed, 111 insertions(+), 75 deletions(-) diff --git a/messages/list.md b/messages/list.md index d986cfa5..7378a289 100644 --- a/messages/list.md +++ b/messages/list.md @@ -58,7 +58,7 @@ Use one of the "org login" commands or "org create scratch" to add or create a s # noResultsFound -No %s found. +No Orgs found. # cleanWarning diff --git a/src/commands/org/list.ts b/src/commands/org/list.ts index 7ea8f4ef..77fe92b4 100644 --- a/src/commands/org/list.ts +++ b/src/commands/org/list.ts @@ -8,6 +8,7 @@ import { Flags, loglevel, SfCommand } from '@salesforce/sf-plugins-core'; import { AuthInfo, ConfigAggregator, ConfigInfo, Connection, Org, SfError, Messages, Logger } from '@salesforce/core'; import { Interfaces } from '@oclif/core'; +import * as chalk from 'chalk'; import { OrgListUtil, identifyActiveOrgByStatus } from '../../shared/orgListUtil'; import { getStyledObject } from '../../shared/orgHighlighter'; import { ExtendedAuthFields, FullyPopulatedScratchOrgFields } from '../../shared/orgTypes'; @@ -89,13 +90,15 @@ export class OrgListCommand extends SfCommand { : groupedSortedOrgs.scratchOrgs.filter(identifyActiveOrgByStatus), }; - // this.printOrgTable(result.nonScratchOrgs, flags['skip-connection-status']); - this.printOrgTable(result.devHubs, flags['skip-connection-status'], 'DevHubs'); - this.printOrgTable(result.regularOrgs, flags['skip-connection-status'], 'Orgs'); - this.printOrgTable(result.sandboxes, flags['skip-connection-status'], 'Sandboxes'); - + this.printOrgTable({ + devHubs: result.devHubs, + regularOrgs: result.regularOrgs, + sandboxes: result.sandboxes, + skipconnectionstatus: flags['skip-connection-status'], + }); this.printScratchOrgTable(result.scratchOrgs); + this.info('Legend: (D)=Default DevHub, (U)=Default Org'); return result; } @@ -128,35 +131,61 @@ export class OrgListCommand extends SfCommand { ); } - protected printOrgTable(nonScratchOrgs: ExtendedAuthFields[], skipconnectionstatus: boolean, title: string): void { - if (!nonScratchOrgs.length) { - this.info(messages.getMessage('noResultsFound', [title])); - } else { - this.info(title); - const rows = nonScratchOrgs - .map((row) => getStyledObject(row)) - .map((org) => - Object.fromEntries( - Object.entries(org).filter(([key]) => - ['defaultMarker', 'alias', 'username', 'orgId', 'connectedStatus'].includes(key) - ) - ) - ); + protected printOrgTable({ + devHubs, + regularOrgs, + sandboxes, + skipconnectionstatus, + }: { + devHubs: ExtendedAuthFields[]; + regularOrgs: ExtendedAuthFields[]; + sandboxes: ExtendedAuthFields[]; + skipconnectionstatus: boolean; + }): void { + if (!devHubs.length && !regularOrgs.length && !sandboxes.length) { + this.info(messages.getMessage('noResultsFound')); + return; + } + this.log(); + this.info('Non-scratch orgs'); + const nonScratchOrgs = [ + ...devHubs + .map(addType('DevHub')) + .map(colorEveryFieldButConnectedStatus(chalk.cyanBright)) + .map((row) => getStyledObject(row)), - this.table(rows, { + ...regularOrgs.map(colorEveryFieldButConnectedStatus(chalk.magentaBright)).map((row) => getStyledObject(row)), + + ...sandboxes + .map(addType('Sandbox')) + .map(colorEveryFieldButConnectedStatus(chalk.yellowBright)) + .map((row) => getStyledObject(row)), + ]; + + this.table( + nonScratchOrgs.map((org) => + Object.fromEntries( + Object.entries(org).filter(([key]) => + ['type', 'defaultMarker', 'alias', 'username', 'orgId', 'connectedStatus'].includes(key) + ) + ) + ), + { defaultMarker: { header: '', - get: (data): string => data.defaultMarker ?? '', + }, + type: { + header: 'Type', }, alias: { - header: 'ALIAS', - get: (data): string => data.alias ?? '', + header: 'Alias', }, - username: { header: 'USERNAME' }, - orgId: { header: 'ORG ID' }, - ...(!skipconnectionstatus ? { connectedStatus: { header: 'CONNECTED STATUS' } } : {}), - }); - } + username: { header: 'Username' }, + orgId: { header: 'Org ID' }, + ...(!skipconnectionstatus ? { connectedStatus: { header: 'Status' } } : {}), + } + ); + this.log(); } @@ -165,54 +194,31 @@ export class OrgListCommand extends SfCommand { this.info(messages.getMessage('noActiveScratchOrgs')); } else { this.info(this.flags.all ? 'Scratch Orgs' : 'Active Scratch Orgs (use --all to see all)'); + // One or more rows are available. // we only need a few of the props for our table. Oclif table doesn't like extra props non-string props. const rows = scratchOrgs .map(getStyledObject) - .map((org) => - Object.fromEntries( - Object.entries(org).filter(([key]) => - [ - 'defaultMarker', - 'alias', - 'username', - 'orgId', - 'status', - 'expirationDate', - 'devHubOrgId', - 'createdDate', - 'instanceUrl', - ].includes(key) - ) - ) - ); - this.table( - rows, - { - defaultMarker: { - header: '', - get: (data): string => data.defaultMarker ?? '', - }, - alias: { - header: 'ALIAS', - get: (data): string => data.alias ?? '', - }, - username: { header: 'USERNAME' }, - orgId: { header: 'ORG ID' }, - ...(this.flags.all || this.flags.verbose ? { status: { header: 'STATUS' } } : {}), - ...(this.flags.verbose - ? { - devHubOrgId: { header: 'DEV HUB' }, - createdDate: { header: 'CREATED DATE' }, - instanceUrl: { header: 'INSTANCE URL' }, - } - : {}), - expirationDate: { header: 'EXPIRATION DATE' }, - } - // { - // title: 'Scratch orgs', - // } - ); + .map((org) => Object.fromEntries(Object.entries(org).filter(scratchOrgFieldFilter))); + this.table(rows, { + defaultMarker: { + header: '', + }, + alias: { + header: 'Alias', + }, + username: { header: 'Username' }, + orgId: { header: 'Org ID' }, + ...(this.flags.all || this.flags.verbose ? { status: { header: 'Status' } } : {}), + ...(this.flags.verbose + ? { + devHubOrgId: { header: 'Dev Hub ID' }, + instanceUrl: { header: 'Instance URL' }, + createdDate: { header: 'Created', get: (data): string => data.createdDate?.split('T')[0] ?? '' }, + } + : {}), + expirationDate: { header: 'Expires' }, + }); } this.log(); } @@ -226,8 +232,9 @@ const decorateWithDefaultStatus = (a: T, b: T): number => { - const aliasCompareResult = (a.alias ?? '').localeCompare(b.alias ?? ''); - return aliasCompareResult !== 0 ? aliasCompareResult : (a.username ?? '').localeCompare(b.username); + const emptiesLast = Array(10).fill('z').join(''); + const aliasCompareResult = (a.alias ?? emptiesLast).localeCompare(b.alias ?? emptiesLast); + return aliasCompareResult !== 0 ? aliasCompareResult : (a.username ?? emptiesLast).localeCompare(b.username); }; const getAuthFileNames = async (): Promise => { @@ -242,3 +249,32 @@ const getAuthFileNames = async (): Promise => { } } }; + +type ExtendedAuthFieldsWithType = ExtendedAuthFields & { type?: string }; +const addType = + (type: string) => + (val: ExtendedAuthFields): ExtendedAuthFieldsWithType => ({ ...val, type }); + +const colorEveryFieldButConnectedStatus = + (colorFn: chalk.Chalk) => + (row: ExtendedAuthFieldsWithType): ExtendedAuthFieldsWithType => + 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 + ) as ExtendedAuthFieldsWithType; + +const scratchOrgFieldFilter = ([key]: [string, string]): boolean => + [ + 'defaultMarker', + 'alias', + 'username', + 'orgId', + 'status', + 'expirationDate', + 'devHubOrgId', + 'createdDate', + 'instanceUrl', + ].includes(key); From ec974b8578b1786397a8c5c198dea0fdcd5a3923 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Fri, 4 Aug 2023 14:59:54 -0500 Subject: [PATCH 3/9] feat: pretty org list table --- src/commands/org/list.ts | 101 +++++++++++++++-------------- src/shared/orgHighlighter.ts | 16 ++--- test/shared/orgHighlighter.test.ts | 4 +- test/shared/orgListMock.ts | 9 +++ 4 files changed, 73 insertions(+), 57 deletions(-) diff --git a/src/commands/org/list.ts b/src/commands/org/list.ts index 77fe92b4..01c6a3d7 100644 --- a/src/commands/org/list.ts +++ b/src/commands/org/list.ts @@ -16,6 +16,9 @@ import { ExtendedAuthFields, FullyPopulatedScratchOrgFields } from '../../shared Messages.importMessagesDirectory(__dirname); const messages = Messages.loadMessages('@salesforce/plugin-org', 'list'); +const defaultOrgEmoji = '🍁'; +const defaultHubEmoji = '🌳'; + export type OrgListResult = { /** * @deprecated @@ -94,11 +97,18 @@ export class OrgListCommand extends SfCommand { devHubs: result.devHubs, regularOrgs: result.regularOrgs, sandboxes: result.sandboxes, + scratchOrgs: result.scratchOrgs, skipconnectionstatus: flags['skip-connection-status'], }); - this.printScratchOrgTable(result.scratchOrgs); + // this.printScratchOrgTable(result.scratchOrgs); + + this.log( + ` +Legend: ${defaultHubEmoji}=Default DevHub, ${defaultOrgEmoji}=Default Org ${ + this.flags.all ? ' Use --all to see expired and deleted scratch orgs' : '' + }` + ); - this.info('Legend: (D)=Default DevHub, (U)=Default Org'); return result; } @@ -133,6 +143,7 @@ export class OrgListCommand extends SfCommand { protected printOrgTable({ devHubs, + scratchOrgs, regularOrgs, sandboxes, skipconnectionstatus, @@ -140,36 +151,40 @@ export class OrgListCommand extends SfCommand { devHubs: ExtendedAuthFields[]; regularOrgs: ExtendedAuthFields[]; sandboxes: ExtendedAuthFields[]; + scratchOrgs: FullyPopulatedScratchOrgFields[]; skipconnectionstatus: boolean; }): void { if (!devHubs.length && !regularOrgs.length && !sandboxes.length) { this.info(messages.getMessage('noResultsFound')); return; } - this.log(); - this.info('Non-scratch orgs'); - const nonScratchOrgs = [ + const allOrgs: Array = [ ...devHubs .map(addType('DevHub')) .map(colorEveryFieldButConnectedStatus(chalk.cyanBright)) - .map((row) => getStyledObject(row)), + .map((row) => getStyledObject(row)) + .map(statusToEmoji), - ...regularOrgs.map(colorEveryFieldButConnectedStatus(chalk.magentaBright)).map((row) => getStyledObject(row)), + ...regularOrgs + .map(colorEveryFieldButConnectedStatus(chalk.magentaBright)) + .map((row) => getStyledObject(row)) + .map(statusToEmoji), ...sandboxes .map(addType('Sandbox')) .map(colorEveryFieldButConnectedStatus(chalk.yellowBright)) - .map((row) => getStyledObject(row)), + .map((row) => getStyledObject(row)) + .map(statusToEmoji), + + ...scratchOrgs + .map((row) => ({ ...row, type: 'Scratch' })) + .map(convertScratchOrgStatus) + .map((row) => getStyledObject(row)) + .map(statusToEmoji), ]; this.table( - nonScratchOrgs.map((org) => - Object.fromEntries( - Object.entries(org).filter(([key]) => - ['type', 'defaultMarker', 'alias', 'username', 'orgId', 'connectedStatus'].includes(key) - ) - ) - ), + allOrgs.map((org) => Object.fromEntries(Object.entries(org).filter(fieldFilter))), { defaultMarker: { header: '', @@ -183,44 +198,19 @@ export class OrgListCommand extends SfCommand { username: { header: 'Username' }, orgId: { header: 'Org ID' }, ...(!skipconnectionstatus ? { connectedStatus: { header: 'Status' } } : {}), - } - ); - - this.log(); - } - - private printScratchOrgTable(scratchOrgs: FullyPopulatedScratchOrgFields[]): void { - if (scratchOrgs.length === 0) { - this.info(messages.getMessage('noActiveScratchOrgs')); - } else { - this.info(this.flags.all ? 'Scratch Orgs' : 'Active Scratch Orgs (use --all to see all)'); - - // One or more rows are available. - // we only need a few of the props for our table. Oclif table doesn't like extra props non-string props. - const rows = scratchOrgs - .map(getStyledObject) - .map((org) => Object.fromEntries(Object.entries(org).filter(scratchOrgFieldFilter))); - this.table(rows, { - defaultMarker: { - header: '', - }, - alias: { - header: 'Alias', - }, - username: { header: 'Username' }, - orgId: { header: 'Org ID' }, - ...(this.flags.all || this.flags.verbose ? { status: { header: 'Status' } } : {}), ...(this.flags.verbose ? { - devHubOrgId: { header: 'Dev Hub ID' }, instanceUrl: { header: 'Instance URL' }, - createdDate: { header: 'Created', get: (data): string => data.createdDate?.split('T')[0] ?? '' }, + devHubOrgId: { header: 'Dev Hub ID' }, + createdDate: { + header: 'Created', + get: (data): string => (data.createdDate as string)?.split('T')?.[0] ?? '', + }, } : {}), expirationDate: { header: 'Expires' }, - }); - } - this.log(); + } + ); } } @@ -228,6 +218,12 @@ const decorateWithDefaultStatus = (val: T): T => ({ + ...val, + defaultMarker: val.defaultMarker?.replace('(D)', defaultHubEmoji)?.replace('(U)', defaultOrgEmoji), }); // sort by alias then username @@ -251,6 +247,7 @@ const getAuthFileNames = async (): Promise => { }; type ExtendedAuthFieldsWithType = ExtendedAuthFields & { type?: string }; + const addType = (type: string) => (val: ExtendedAuthFields): ExtendedAuthFieldsWithType => ({ ...val, type }); @@ -266,15 +263,23 @@ const colorEveryFieldButConnectedStatus = // TS is not smart enough to know this didn't change any types ) as ExtendedAuthFieldsWithType; -const scratchOrgFieldFilter = ([key]: [string, string]): boolean => +const fieldFilter = ([key]: [string, string]): boolean => [ 'defaultMarker', 'alias', 'username', 'orgId', 'status', + 'connectedStatus', 'expirationDate', 'devHubOrgId', 'createdDate', 'instanceUrl', + 'type', + 'createdDate', ].includes(key); + +const convertScratchOrgStatus = ( + row: FullyPopulatedScratchOrgFields +): FullyPopulatedScratchOrgFields & { connectedStatus: string } => + ({ ...row, connectedStatus: row.status } as FullyPopulatedScratchOrgFields & { connectedStatus: string }); diff --git a/src/shared/orgHighlighter.ts b/src/shared/orgHighlighter.ts index 01392650..62265a04 100644 --- a/src/shared/orgHighlighter.ts +++ b/src/shared/orgHighlighter.ts @@ -19,6 +19,7 @@ const styledProperties = new Map>([ 'connectedStatus', new Map([ ['Connected', chalk.green], + ['Active', chalk.green], ['else', chalk.red], ]), ], @@ -34,11 +35,10 @@ export const getStyledValue = (key: string, value: string): string => { return chalkMethod(value); }; -export const getStyledObject = ( - objectToStyle: ExtendedAuthFields | FullyPopulatedScratchOrgFields | Record -): Record => { - const clonedObject = { ...objectToStyle }; - return Object.fromEntries( - Object.entries(clonedObject).map(([key, value]) => [key, getStyledValue(key, value as string)]) - ); -}; +export const getStyledObject = (objectToStyle: T): T => + Object.fromEntries( + Object.entries(objectToStyle).map(([key, value]) => [ + key, + typeof value === 'string' ? getStyledValue(key, value) : value, + ]) + ) as T; diff --git a/test/shared/orgHighlighter.test.ts b/test/shared/orgHighlighter.test.ts index eb27a9a1..9096cb47 100644 --- a/test/shared/orgHighlighter.test.ts +++ b/test/shared/orgHighlighter.test.ts @@ -6,6 +6,7 @@ */ import { expect } from 'chai'; import * as chalk from 'chalk'; +import { ExtendedAuthFields } from '../../src/shared/orgTypes'; import { getStyledObject, getStyledValue } from '../../src/shared/orgHighlighter'; describe('highlights value from key-value pair', () => { @@ -26,7 +27,8 @@ describe('highlights object with green, red, and non-colored', () => { status: 'Active', otherProp: 'foo', connectedStatus: 'Not found', - }; + // I know it's not, but it's a test + } as unknown as ExtendedAuthFields; expect(getStyledObject(object)).to.deep.equal({ status: chalk.green('Active'), otherProp: 'foo', diff --git a/test/shared/orgListMock.ts b/test/shared/orgListMock.ts index 2e7a6605..9eed6fd5 100644 --- a/test/shared/orgListMock.ts +++ b/test/shared/orgListMock.ts @@ -96,6 +96,15 @@ class OrgListMock { connectedStatus: 'Connected', }, ], + devHubs: [ + { + username: 'foo@example.com', + isDevHub: true, + connectedStatus: 'Connected', + }, + ], + sandboxes: [], + regularOrgs: [], }; public static get devHubUsername(): string { From 59f8debefcea8be232bf78a723c641bee95d5096 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Fri, 4 Aug 2023 15:01:43 -0500 Subject: [PATCH 4/9] chore: schemas --- schemas/org-list.json | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/schemas/org-list.json b/schemas/org-list.json index 81c2dc68..5451d49a 100644 --- a/schemas/org-list.json +++ b/schemas/org-list.json @@ -9,16 +9,35 @@ "type": "array", "items": { "$ref": "#/definitions/ExtendedAuthFields" - } + }, + "deprecated": "preserved for backward json compatibility. Duplicates devHubs, sandboxes, regularOrgs, which should be preferred" }, "scratchOrgs": { "type": "array", "items": { "$ref": "#/definitions/FullyPopulatedScratchOrgFields" } + }, + "sandboxes": { + "type": "array", + "items": { + "$ref": "#/definitions/ExtendedAuthFields" + } + }, + "regularOrgs": { + "type": "array", + "items": { + "$ref": "#/definitions/ExtendedAuthFields" + } + }, + "devHubs": { + "type": "array", + "items": { + "$ref": "#/definitions/ExtendedAuthFields" + } } }, - "required": ["nonScratchOrgs", "scratchOrgs"], + "required": ["nonScratchOrgs", "scratchOrgs", "sandboxes", "regularOrgs", "devHubs"], "additionalProperties": false }, "ExtendedAuthFields": { From 05e8876f7b68ecbff385a3ad4b62a516b6f2b7c8 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Tue, 8 Aug 2023 15:43:59 -0500 Subject: [PATCH 5/9] refactor: pr comments --- src/commands/org/list.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/commands/org/list.ts b/src/commands/org/list.ts index 01c6a3d7..7477f1ce 100644 --- a/src/commands/org/list.ts +++ b/src/commands/org/list.ts @@ -100,7 +100,6 @@ export class OrgListCommand extends SfCommand { scratchOrgs: result.scratchOrgs, skipconnectionstatus: flags['skip-connection-status'], }); - // this.printScratchOrgTable(result.scratchOrgs); this.log( ` @@ -226,11 +225,12 @@ const statusToEmoji = (a: T, b: T): number => { - const emptiesLast = Array(10).fill('z').join(''); - const aliasCompareResult = (a.alias ?? emptiesLast).localeCompare(b.alias ?? emptiesLast); - return aliasCompareResult !== 0 ? aliasCompareResult : (a.username ?? emptiesLast).localeCompare(b.username); + 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 (): Promise => { From 7318c46783f72b8bd2c552b1185e084d21b806ce Mon Sep 17 00:00:00 2001 From: mshanemc Date: Tue, 8 Aug 2023 15:56:51 -0500 Subject: [PATCH 6/9] refactor: other instead of regularOrgs --- src/commands/org/list.ts | 16 ++++++++-------- src/shared/orgListUtil.ts | 4 ++-- test/shared/orgListMock.ts | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/commands/org/list.ts b/src/commands/org/list.ts index 7477f1ce..9c5f8e85 100644 --- a/src/commands/org/list.ts +++ b/src/commands/org/list.ts @@ -26,7 +26,7 @@ export type OrgListResult = { nonScratchOrgs: ExtendedAuthFields[]; scratchOrgs: FullyPopulatedScratchOrgFields[]; sandboxes: ExtendedAuthFields[]; - regularOrgs: ExtendedAuthFields[]; + other: ExtendedAuthFields[]; devHubs: ExtendedAuthFields[]; }; @@ -68,7 +68,7 @@ export class OrgListCommand extends SfCommand { const metaConfigs = await OrgListUtil.readLocallyValidatedMetaConfigsGroupedByOrgType(fileNames, flags); const groupedSortedOrgs = { devHubs: metaConfigs.devHubs.map(decorateWithDefaultStatus).sort(comparator), - regularOrgs: metaConfigs.regularOrgs.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), @@ -84,7 +84,7 @@ export class OrgListCommand extends SfCommand { } const result = { - regularOrgs: groupedSortedOrgs.regularOrgs, + other: groupedSortedOrgs.other, sandboxes: groupedSortedOrgs.sandboxes, nonScratchOrgs: groupedSortedOrgs.nonScratchOrgs, devHubs: groupedSortedOrgs.devHubs, @@ -95,7 +95,7 @@ export class OrgListCommand extends SfCommand { this.printOrgTable({ devHubs: result.devHubs, - regularOrgs: result.regularOrgs, + other: result.other, sandboxes: result.sandboxes, scratchOrgs: result.scratchOrgs, skipconnectionstatus: flags['skip-connection-status'], @@ -143,17 +143,17 @@ Legend: ${defaultHubEmoji}=Default DevHub, ${defaultOrgEmoji}=Default Org ${ protected printOrgTable({ devHubs, scratchOrgs, - regularOrgs, + other, sandboxes, skipconnectionstatus, }: { devHubs: ExtendedAuthFields[]; - regularOrgs: ExtendedAuthFields[]; + other: ExtendedAuthFields[]; sandboxes: ExtendedAuthFields[]; scratchOrgs: FullyPopulatedScratchOrgFields[]; skipconnectionstatus: boolean; }): void { - if (!devHubs.length && !regularOrgs.length && !sandboxes.length) { + if (!devHubs.length && !other.length && !sandboxes.length) { this.info(messages.getMessage('noResultsFound')); return; } @@ -164,7 +164,7 @@ Legend: ${defaultHubEmoji}=Default DevHub, ${defaultOrgEmoji}=Default Org ${ .map((row) => getStyledObject(row)) .map(statusToEmoji), - ...regularOrgs + ...other .map(colorEveryFieldButConnectedStatus(chalk.magentaBright)) .map((row) => getStyledObject(row)) .map(statusToEmoji), diff --git a/src/shared/orgListUtil.ts b/src/shared/orgListUtil.ts index eb1d11f0..16dd6258 100644 --- a/src/shared/orgListUtil.ts +++ b/src/shared/orgListUtil.ts @@ -38,7 +38,7 @@ type OrgGroups = { type OrgGroupsFullyPopulated = { nonScratchOrgs: ExtendedAuthFields[]; scratchOrgs: FullyPopulatedScratchOrgFields[]; - regularOrgs: ExtendedAuthFields[]; + other: ExtendedAuthFields[]; sandboxes: ExtendedAuthFields[]; devHubs: ExtendedAuthFields[]; }; @@ -97,7 +97,7 @@ export class OrgListUtil { nonScratchOrgs, scratchOrgs, sandboxes: nonScratchOrgs.filter(sandboxFilter), - regularOrgs: nonScratchOrgs.filter(regularOrgFilter), + other: nonScratchOrgs.filter(regularOrgFilter), devHubs: nonScratchOrgs.filter(devHubFilter), }; } diff --git a/test/shared/orgListMock.ts b/test/shared/orgListMock.ts index 9eed6fd5..7e551773 100644 --- a/test/shared/orgListMock.ts +++ b/test/shared/orgListMock.ts @@ -104,7 +104,7 @@ class OrgListMock { }, ], sandboxes: [], - regularOrgs: [], + other: [], }; public static get devHubUsername(): string { From a1c3366fd958ff12eff3cac6dc3edf9e08fa70e1 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Tue, 8 Aug 2023 15:57:28 -0500 Subject: [PATCH 7/9] chore: schema --- schemas/org-list.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/schemas/org-list.json b/schemas/org-list.json index 5451d49a..41bf056e 100644 --- a/schemas/org-list.json +++ b/schemas/org-list.json @@ -24,7 +24,7 @@ "$ref": "#/definitions/ExtendedAuthFields" } }, - "regularOrgs": { + "other": { "type": "array", "items": { "$ref": "#/definitions/ExtendedAuthFields" @@ -37,7 +37,7 @@ } } }, - "required": ["nonScratchOrgs", "scratchOrgs", "sandboxes", "regularOrgs", "devHubs"], + "required": ["nonScratchOrgs", "scratchOrgs", "sandboxes", "other", "devHubs"], "additionalProperties": false }, "ExtendedAuthFields": { From 5060b0a059275110d6d771f9c6fb74e7baafaa7f Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 9 Aug 2023 13:22:09 -0500 Subject: [PATCH 8/9] test: nut changes for new org-list table --- src/commands/org/list.ts | 4 ++-- test/nut/listAndDisplay.nut.ts | 17 +++++++++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/commands/org/list.ts b/src/commands/org/list.ts index 9c5f8e85..95972954 100644 --- a/src/commands/org/list.ts +++ b/src/commands/org/list.ts @@ -16,8 +16,8 @@ import { ExtendedAuthFields, FullyPopulatedScratchOrgFields } from '../../shared Messages.importMessagesDirectory(__dirname); const messages = Messages.loadMessages('@salesforce/plugin-org', 'list'); -const defaultOrgEmoji = '🍁'; -const defaultHubEmoji = '🌳'; +export const defaultOrgEmoji = '🍁'; +export const defaultHubEmoji = '🌳'; export type OrgListResult = { /** diff --git a/test/nut/listAndDisplay.nut.ts b/test/nut/listAndDisplay.nut.ts index 78288df2..ffcbb7f2 100644 --- a/test/nut/listAndDisplay.nut.ts +++ b/test/nut/listAndDisplay.nut.ts @@ -11,7 +11,7 @@ import { expect, config, assert } from 'chai'; import { TestSession } from '@salesforce/cli-plugins-testkit'; import { execCmd } from '@salesforce/cli-plugins-testkit'; import { getString } from '@salesforce/ts-types'; -import { OrgListResult } from '../../src/commands/org/list'; +import { OrgListResult, defaultHubEmoji, defaultOrgEmoji } from '../../src/commands/org/list'; import { OrgOpenOutput } from '../../src/commands/org/open'; import { OrgDisplayReturn } from '../../src/shared/orgTypes'; @@ -27,11 +27,11 @@ const verifyHumanResults = ( expect(lines.length).to.have.greaterThan(0); const devHubLine = lines.find((line) => line.includes(hubOrgUsername)); assert(devHubLine); - expect(devHubLine).to.include('(D)'); + expect(devHubLine).to.include(defaultHubEmoji); expect(devHubLine).to.include('Connected'); const defaultUserLine = lines.find((line) => line.includes(defaultUsername)); assert(defaultUserLine); - expect(defaultUserLine).to.include('(U)'); + expect(defaultUserLine).to.include(defaultOrgEmoji); const aliasUserLine = lines.find((line) => line.includes(aliasedUsername)); assert(aliasUserLine); expect(aliasUserLine).to.include('anAlias'); @@ -100,8 +100,8 @@ describe('Org Command NUT', () => { expect(listResult.scratchOrgs).to.have.length(2); const scratchOrgs = listResult.scratchOrgs; expect(scratchOrgs.map((scratchOrg) => getString(scratchOrg, 'username'))).to.deep.equal([ - defaultUsername, aliasedUsername, + defaultUsername, ]); expect(scratchOrgs.find((org) => org.username === defaultUsername)).to.include({ defaultMarker: '(U)', @@ -121,6 +121,15 @@ describe('Org Command NUT', () => { }, JSON.stringify(listResult.nonScratchOrgs[0]) ); + expect(listResult.devHubs[0]).to.include( + { + username: hubOrgUsername, + defaultMarker: '(D)', + isDevHub: true, + connectedStatus: 'Connected', + }, + JSON.stringify(listResult.nonScratchOrgs[0]) + ); }); it('should list orgs - skip-connection-status', () => { const listResult = execCmd('org:list --skip-connection-status --json', { ensureExitCode: 0 }) From def136bfb3aa1d647307ce38a67aa00f49caf60d Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 9 Aug 2023 13:33:03 -0500 Subject: [PATCH 9/9] style: legend color, spacing, output --- src/commands/org/list.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/org/list.ts b/src/commands/org/list.ts index 95972954..08fc8e99 100644 --- a/src/commands/org/list.ts +++ b/src/commands/org/list.ts @@ -101,10 +101,10 @@ export class OrgListCommand extends SfCommand { skipconnectionstatus: flags['skip-connection-status'], }); - this.log( + this.info( ` Legend: ${defaultHubEmoji}=Default DevHub, ${defaultOrgEmoji}=Default Org ${ - this.flags.all ? ' Use --all to see expired and deleted scratch orgs' : '' + flags.all ? '' : ' Use --all to see expired and deleted scratch orgs' }` );