From c3b18d8a6a0de9a75f0710bfe5458651d7a0c431 Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Wed, 4 Aug 2021 15:22:13 -0700 Subject: [PATCH] feat: separate tables for scratch orgs (#100) * feat: separate tables for scratch orgs * chore: keep up with core changes * chore: update core --- package.json | 2 +- schemas/env-display.json | 112 +++++++++++-------------- schemas/env-list.json | 116 +++++++++++--------------- schemas/env-open.json | 6 +- src/commands/env/display.ts | 12 +-- src/commands/env/list.ts | 134 ++++++++++++++++++------------ test/commands/env/display.test.ts | 25 +++--- test/commands/env/list.test.ts | 29 +++---- yarn.lock | 8 +- 9 files changed, 217 insertions(+), 227 deletions(-) diff --git a/package.json b/package.json index 0e999bdf..a44dc5a1 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "bugs": "https://github.com/forcedotcom/cli/issues", "dependencies": { "@oclif/core": "^0.5.26", - "@salesforce/core": "3.3.5", + "@salesforce/core": "3.3.8", "cli-ux": "^5.6.3", "open": "^8.2.0", "tslib": "^2" diff --git a/schemas/env-display.json b/schemas/env-display.json index a689e9dc..b1918303 100644 --- a/schemas/env-display.json +++ b/schemas/env-display.json @@ -1,21 +1,53 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/SfOrg", + "$ref": "#/definitions/OrgAuthorization", "definitions": { - "SfOrg": { + "OrgAuthorization": { "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Optional%3CAnyJson%3E" - }, "properties": { - "alias": { + "orgId": { "type": "string" }, "username": { "type": "string" }, - "orgId": { - "type": "string" + "oauthMethod": { + "type": "string", + "enum": [ + "jwt", + "web", + "token", + "unknown" + ] + }, + "aliases": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "null" + } + ] + }, + "configs": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "null" + } + ] + }, + "isScratchOrg": { + "type": "boolean" }, "instanceUrl": { "type": "string" @@ -23,66 +55,16 @@ "accessToken": { "type": "string" }, - "oauthMethod": { - "type": "string", - "enum": ["jwt", "web", "token", "unknown"] - }, "error": { "type": "string" } - } - }, - "Optional": { - "anyOf": [ - { - "$ref": "#/definitions/AnyJson" - }, - { - "not": {} - } - ], - "description": "A union type for either the parameterized type `T` or `undefined` -- the opposite of {@link NonOptional } ." - }, - "AnyJson": { - "anyOf": [ - { - "$ref": "#/definitions/JsonPrimitive" - }, - { - "$ref": "#/definitions/JsonCollection" - } - ], - "description": "Any valid JSON value." - }, - "JsonPrimitive": { - "type": ["null", "boolean", "number", "string"], - "description": "Any valid JSON primitive value." - }, - "JsonCollection": { - "anyOf": [ - { - "$ref": "#/definitions/JsonMap" - }, - { - "$ref": "#/definitions/JsonArray" - } - ], - "description": "Any valid JSON collection value." - }, - "JsonMap": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Optional%3CAnyJson%3E" - }, - "properties": {}, - "description": "Any JSON-compatible object." - }, - "JsonArray": { - "type": "array", - "items": { - "$ref": "#/definitions/AnyJson" }, - "description": "Any JSON-compatible array." + "required": [ + "orgId", + "username", + "oauthMethod" + ], + "additionalProperties": false } } -} +} \ No newline at end of file diff --git a/schemas/env-list.json b/schemas/env-list.json index 11ac4ad5..ad977790 100644 --- a/schemas/env-list.json +++ b/schemas/env-list.json @@ -1,27 +1,59 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/SfOrgs", + "$ref": "#/definitions/Environments", "definitions": { - "SfOrgs": { + "Environments": { "type": "array", "items": { - "$ref": "#/definitions/SfOrg" + "$ref": "#/definitions/OrgAuthorization" } }, - "SfOrg": { + "OrgAuthorization": { "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Optional%3CAnyJson%3E" - }, "properties": { - "alias": { + "orgId": { "type": "string" }, "username": { "type": "string" }, - "orgId": { - "type": "string" + "oauthMethod": { + "type": "string", + "enum": [ + "jwt", + "web", + "token", + "unknown" + ] + }, + "aliases": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "null" + } + ] + }, + "configs": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "null" + } + ] + }, + "isScratchOrg": { + "type": "boolean" }, "instanceUrl": { "type": "string" @@ -29,66 +61,16 @@ "accessToken": { "type": "string" }, - "oauthMethod": { - "type": "string", - "enum": ["jwt", "web", "token", "unknown"] - }, "error": { "type": "string" } - } - }, - "Optional": { - "anyOf": [ - { - "$ref": "#/definitions/AnyJson" - }, - { - "not": {} - } - ], - "description": "A union type for either the parameterized type `T` or `undefined` -- the opposite of {@link NonOptional } ." - }, - "AnyJson": { - "anyOf": [ - { - "$ref": "#/definitions/JsonPrimitive" - }, - { - "$ref": "#/definitions/JsonCollection" - } - ], - "description": "Any valid JSON value." - }, - "JsonPrimitive": { - "type": ["null", "boolean", "number", "string"], - "description": "Any valid JSON primitive value." - }, - "JsonCollection": { - "anyOf": [ - { - "$ref": "#/definitions/JsonMap" - }, - { - "$ref": "#/definitions/JsonArray" - } - ], - "description": "Any valid JSON collection value." - }, - "JsonMap": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Optional%3CAnyJson%3E" - }, - "properties": {}, - "description": "Any JSON-compatible object." - }, - "JsonArray": { - "type": "array", - "items": { - "$ref": "#/definitions/AnyJson" }, - "description": "Any JSON-compatible array." + "required": [ + "orgId", + "username", + "oauthMethod" + ], + "additionalProperties": false } } -} +} \ No newline at end of file diff --git a/schemas/env-open.json b/schemas/env-open.json index 693c3abc..7432d27e 100644 --- a/schemas/env-open.json +++ b/schemas/env-open.json @@ -9,8 +9,10 @@ "type": "string" } }, - "required": ["url"], + "required": [ + "url" + ], "additionalProperties": false } } -} +} \ No newline at end of file diff --git a/src/commands/env/display.ts b/src/commands/env/display.ts index af039192..367b5b66 100644 --- a/src/commands/env/display.ts +++ b/src/commands/env/display.ts @@ -7,7 +7,7 @@ import { Command, Flags } from '@oclif/core'; import { cli } from 'cli-ux'; -import { AuthInfo, SfOrg, Messages, SfdxError } from '@salesforce/core'; +import { AuthInfo, OrgAuthorization, Messages, SfdxError } from '@salesforce/core'; Messages.importMessagesDirectory(__dirname); const messages = Messages.loadMessages('@salesforce/plugin-env', 'display'); @@ -23,13 +23,13 @@ export default class EnvDisplay extends Command { }), }; - // TODO: Change SfOrg type to a more generalized auth type once we have Functions envs integrated. + // TODO: Change OrgAuthorization type to a more generalized auth type once we have Functions envs integrated. - public async run(): Promise { + public async run(): Promise { const { flags } = await this.parse(EnvDisplay); - let authorizations: SfOrg[]; - let foundAuthorization: SfOrg; + let authorizations: OrgAuthorization[]; + let foundAuthorization: OrgAuthorization; try { if (await AuthInfo.hasAuthentications()) { @@ -44,7 +44,7 @@ export default class EnvDisplay extends Command { foundAuthorization = authorizations.find((auth) => auth.username === targetEnv) ?? - authorizations.find((auth) => auth.alias === targetEnv); + authorizations.find((auth) => auth.aliases?.includes(targetEnv)); if (foundAuthorization) { const columns = { diff --git a/src/commands/env/list.ts b/src/commands/env/list.ts index f6cc4db6..7357474a 100644 --- a/src/commands/env/list.ts +++ b/src/commands/env/list.ts @@ -7,12 +7,12 @@ import { Command, Flags } from '@oclif/core'; import { cli, Table } from 'cli-ux'; -import { AuthInfo, SfOrg, Messages, SfdxError, ConfigAggregator } from '@salesforce/core'; +import { AuthInfo, OrgAuthorization, Messages, SfdxError } from '@salesforce/core'; Messages.importMessagesDirectory(__dirname); const messages = Messages.loadMessages('@salesforce/plugin-env', 'list'); -export type SfOrgs = SfOrg[]; +export type Environments = OrgAuthorization[]; export default class EnvList extends Command { public static readonly summary = messages.getMessage('summary'); @@ -48,67 +48,91 @@ export default class EnvList extends Command { summary: messages.getMessage('flags.sort.summary'), }), }; + private flags!: { + json: boolean; + extended: boolean; + columns: string[]; + csv: boolean; + filter: string; + 'no-header': boolean; + 'no-truncate': boolean; + output: string; + sort: string; + }; - public async run(): Promise { - const { flags } = await this.parse(EnvList); - - let authorizations: Array; - const config = (await ConfigAggregator.create()).getConfigInfo(); + public async run(): Promise { + this.flags = (await this.parse(EnvList)).flags; + if (!(await AuthInfo.hasAuthentications())) throw messages.createError('error.NoAuthsAvailable'); + const envs = [] as Environments; try { - if (await AuthInfo.hasAuthentications()) { - authorizations = await AuthInfo.listAllAuthorizations(); - for (const auth of authorizations) { - auth.configs = config.filter((c) => c.value === auth.alias || c.value === auth.username).map((c) => c.key); - } - const hasErrors = authorizations.some((auth) => !!auth.error); - const columns = { - alias: { - get: (row) => row.alias ?? '', - }, - username: {}, - orgId: { - header: 'Org ID', - }, - instanceUrl: { - header: 'Instance URL', - }, - oauthMethod: { - header: 'OAuth Method', - }, - configs: { - header: 'Config', - get: (row: { configs?: string[] }) => (row.configs ? row.configs.join(', ') : ''), - }, - } as Table.table.Columns>; - if (hasErrors) { - columns.error = { - get: (row: { error?: string }) => row.error ?? '', - } as Table.table.Columns>; - } - - if (!flags.json) { - cli.table(authorizations, columns, { - title: 'Authenticated Envs', - extended: flags.extended, - columns: flags.columns?.join(','), - csv: flags.csv, - filter: flags.filter, - 'no-header': flags['no-header'], - 'no-truncate': flags['no-truncate'], - output: flags.output, - sort: flags.sort, - }); - } - } else { - throw messages.createError('error.NoAuthsAvailable'); - } + const orgs = await this.handleSfOrgs(); + envs.push(...orgs); } catch (error) { const err = error as SfdxError; cli.log(messages.getMessage('error.NoResultsFound')); cli.error(err); } - return authorizations; + return envs; + } + + private async handleSfOrgs(): Promise { + const auths = await AuthInfo.listAllAuthorizations(); + + const grouped = { + nonScratchOrgs: [] as OrgAuthorization[], + scratchOrgs: [] as OrgAuthorization[], + }; + for (const auth of auths) { + if (auth.isScratchOrg) { + grouped.scratchOrgs = grouped.scratchOrgs.concat(auth); + } else { + grouped.nonScratchOrgs = grouped.nonScratchOrgs.concat(auth); + } + } + + const buildSfTable = (orgs: OrgAuthorization[], title: string): void => { + if (!orgs.length) return; + const hasErrors = orgs.some((auth) => !!auth.error); + const columns = { + aliases: { + get: (row: { aliases?: string[] }) => (row.aliases ? row.aliases.join(', ') : ''), + }, + username: {}, + orgId: { header: 'Org ID' }, + instanceUrl: { header: 'Instance URL' }, + oauthMethod: { header: 'OAuth Method' }, + configs: { + header: 'Config', + get: (row: { configs?: string[] }) => (row.configs ? row.configs.join(', ') : ''), + }, + } as Table.table.Columns>; + if (hasErrors) { + columns.error = { + get: (row: { error?: string }) => row.error ?? '', + } as Table.table.Columns>; + } + + cli.table(orgs, columns, { + title, + extended: this.flags.extended, + columns: this.flags.columns?.join(','), + csv: this.flags.csv, + filter: this.flags.filter, + 'no-header': this.flags['no-header'], + 'no-truncate': this.flags['no-truncate'], + output: this.flags.output, + sort: this.flags.sort, + }); + this.log(); + }; + + if (!this.flags.json) { + buildSfTable(grouped.nonScratchOrgs, 'Salesforce Orgs'); + buildSfTable(grouped.scratchOrgs, 'Scratch Orgs'); + } + + return auths; } } diff --git a/test/commands/env/display.test.ts b/test/commands/env/display.test.ts index 7791091b..a44d13cb 100644 --- a/test/commands/env/display.test.ts +++ b/test/commands/env/display.test.ts @@ -11,25 +11,24 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ import { expect, test } from '@oclif/test'; -import { AuthInfo, SfOrg } from '@salesforce/core'; +import { AuthInfo, OrgAuthorization } from '@salesforce/core'; -const expectedSfOrgs: Array> = [ +const expectedSfOrgs: Array> = [ { orgId: '00Dxx12345678912345', accessToken: '00Dxx12345678912345!fdkjlfsakjlsafdjldijafsjklsdfjklafdsjkl', instanceUrl: 'https://some.salesforce.com', oauthMethod: 'jwt', username: 'some-user@some.salesforce.com', - timestamp: '2021-07-28T18:04:08.652Z', + aliases: [], }, { orgId: '00Dxx54321987654321', accessToken: '00Dxx54321987654321!lasfdlkjasfdljkerwklj;afsdlkjdhk;f', instanceUrl: 'https://some.other.salesforce.com', - alias: 'someOtherAlias', + aliases: ['someOtherAlias'], oauthMethod: 'web', username: 'some-other-user@some.other.salesforce.com', - timestamp: '2021-07-28T18:04:08.652Z', error: 'some auth error', }, ]; @@ -37,22 +36,22 @@ const expectedSfOrgs: Array> = [ describe('display unit tests', () => { test .stub(AuthInfo, 'hasAuthentications', async (): Promise => true) - .stub(AuthInfo, 'listAllAuthorizations', async (): Promise>> => expectedSfOrgs) + .stub(AuthInfo, 'listAllAuthorizations', async (): Promise>> => expectedSfOrgs) .stdout() .command(['env:display', '--target-env', expectedSfOrgs[0].username, '--json']) .it('should fetch requested username with json output', (ctx) => { - const sfOrgs = JSON.parse(ctx.stdout) as Array>; + const sfOrgs = JSON.parse(ctx.stdout) as Array>; expect(sfOrgs).to.be.deep.equal(expectedSfOrgs[0]); }); test .stub(AuthInfo, 'hasAuthentications', async (): Promise => true) - .stub(AuthInfo, 'listAllAuthorizations', async (): Promise>> => expectedSfOrgs) + .stub(AuthInfo, 'listAllAuthorizations', async (): Promise>> => expectedSfOrgs) .stdout() .command(['env:display', '--target-env', expectedSfOrgs[0].username]) .it('should fetch requested username with human output', (ctx) => { const stdout = ctx.stdout; expectedSfOrgs.slice(0, 1).forEach((sfOrg) => { - expect(stdout).to.not.include(sfOrg.alias); + expect(stdout).to.not.include(sfOrg.aliases[0]); expect(stdout).to.include(sfOrg.orgId); expect(stdout).to.include(sfOrg.username); expect(stdout).to.include(sfOrg.oauthMethod); @@ -62,13 +61,13 @@ describe('display unit tests', () => { }); test .stub(AuthInfo, 'hasAuthentications', async (): Promise => true) - .stub(AuthInfo, 'listAllAuthorizations', async (): Promise>> => expectedSfOrgs) + .stub(AuthInfo, 'listAllAuthorizations', async (): Promise>> => expectedSfOrgs) .stdout() - .command(['env:display', '--target-env', expectedSfOrgs[1].alias]) + .command(['env:display', '--target-env', expectedSfOrgs[1].aliases[0]]) .it('should fetch requested alias with human output', (ctx) => { const stdout = ctx.stdout; expectedSfOrgs.slice(1).forEach((sfOrg) => { - expect(stdout).to.include(sfOrg.alias); + expect(stdout).to.include(sfOrg.aliases[0]); expect(stdout).to.include(sfOrg.orgId); expect(stdout).to.include(sfOrg.username); expect(stdout).to.include(sfOrg.oauthMethod); @@ -79,7 +78,7 @@ describe('display unit tests', () => { // this test will start to fail when env plugin incorporates more than authorization environment test .stub(AuthInfo, 'hasAuthentications', async (): Promise => true) - .stub(AuthInfo, 'listAllAuthorizations', async (): Promise>> => expectedSfOrgs) + .stub(AuthInfo, 'listAllAuthorizations', async (): Promise>> => expectedSfOrgs) .stdout() .stderr() .command(['env:display']) diff --git a/test/commands/env/list.test.ts b/test/commands/env/list.test.ts index 7c39ad04..dc34a689 100644 --- a/test/commands/env/list.test.ts +++ b/test/commands/env/list.test.ts @@ -5,14 +5,14 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ import { expect, test } from '@oclif/test'; -import { AuthInfo, SfOrg } from '@salesforce/core'; +import { AuthInfo, OrgAuthorization } from '@salesforce/core'; -const expectedSfOrgs: SfOrg[] = [ +const expectedSfOrgs = [ { orgId: '00Dxx12345678912345', accessToken: '00Dxx12345678912345!fdkjlfsakjlsafdjldijafsjklsdfjklafdsjkl', instanceUrl: 'https://some.salesforce.com', - alias: 'someAlias', + aliases: ['someAlias'], oauthMethod: 'jwt', username: 'some-user@some.salesforce.com', }, @@ -20,9 +20,10 @@ const expectedSfOrgs: SfOrg[] = [ orgId: '00Dxx54321987654321', accessToken: '00Dxx54321987654321!lasfdlkjasfdljkerwklj;afsdlkjdhk;f', instanceUrl: 'https://some.other.salesforce.com', - alias: 'someOtherAlias', + aliases: ['someOtherAlias'], oauthMethod: 'web', username: 'some-other-user@some.other.salesforce.com', + devhubUsername: 'some-user@some.salesforce.com', error: 'some auth error', }, ]; @@ -30,23 +31,23 @@ const expectedSfOrgs: SfOrg[] = [ describe('list unit tests', () => { test .stub(AuthInfo, 'hasAuthentications', async (): Promise => true) - .stub(AuthInfo, 'listAllAuthorizations', async (): Promise => expectedSfOrgs) + .stub(AuthInfo, 'listAllAuthorizations', async () => expectedSfOrgs) .stdout() .command(['env:list', '--json']) .it('should fetch active orgs with json output', (ctx) => { - const sfOrgs = JSON.parse(ctx.stdout) as SfOrg[]; + const sfOrgs = JSON.parse(ctx.stdout) as OrgAuthorization[]; expect(sfOrgs).to.be.deep.equal(expectedSfOrgs); }); test .stub(AuthInfo, 'hasAuthentications', async (): Promise => true) - .stub(AuthInfo, 'listAllAuthorizations', async (): Promise => expectedSfOrgs) + .stub(AuthInfo, 'listAllAuthorizations', async () => expectedSfOrgs) .stdout() .command(['env:list']) .it('should fetch active orgs with human output', (ctx) => { const stdout = ctx.stdout; expect(stdout).to.be.ok; expectedSfOrgs.forEach((sfOrg) => { - expect(stdout).to.include(sfOrg.alias); + expect(stdout).to.include(sfOrg.aliases); expect(stdout).to.include(sfOrg.orgId); expect(stdout).to.include(sfOrg.username); expect(stdout).to.include(sfOrg.oauthMethod); @@ -55,14 +56,14 @@ describe('list unit tests', () => { }); test .stub(AuthInfo, 'hasAuthentications', async (): Promise => true) - .stub(AuthInfo, 'listAllAuthorizations', async (): Promise => expectedSfOrgs) + .stub(AuthInfo, 'listAllAuthorizations', async () => expectedSfOrgs) .stdout() .command(['env:list', '--columns', 'org Id,username']) .it('should fetch active orgs with human output and display selected columns', (ctx) => { const stdout = ctx.stdout; expect(stdout).to.be.ok; expectedSfOrgs.forEach((sfOrg) => { - expect(stdout).to.not.include(sfOrg.alias); + expect(stdout).to.not.include(sfOrg.aliases); expect(stdout).to.include(sfOrg.orgId); expect(stdout).to.include(sfOrg.username); expect(stdout).to.not.include(sfOrg.oauthMethod); @@ -71,9 +72,9 @@ describe('list unit tests', () => { }); test .stub(AuthInfo, 'hasAuthentications', async (): Promise => true) - .stub(AuthInfo, 'listAllAuthorizations', async (): Promise => expectedSfOrgs) + .stub(AuthInfo, 'listAllAuthorizations', async () => expectedSfOrgs) .stdout() - .command(['env:list', '--filter', 'alias=someAlias']) + .command(['env:list', '--filter', 'aliases=someAlias']) .it('should fetch active orgs with human output and filtered data', (ctx) => { const stdout = ctx.stdout; expect(stdout).to.be.ok; @@ -82,9 +83,9 @@ describe('list unit tests', () => { }); test .stub(AuthInfo, 'hasAuthentications', async (): Promise => true) - .stub(AuthInfo, 'listAllAuthorizations', async (): Promise => expectedSfOrgs) + .stub(AuthInfo, 'listAllAuthorizations', async () => expectedSfOrgs) .stdout() - .command(['env:list', '--sort', '-alias']) + .command(['env:list', '--sort', '-aliases']) .it('should fetch active orgs with human output and sorted results', (ctx) => { const stdout = ctx.stdout.replace(/\n/g, ''); expect(stdout).to.be.ok; diff --git a/yarn.lock b/yarn.lock index 63262024..b053da60 100644 --- a/yarn.lock +++ b/yarn.lock @@ -669,10 +669,10 @@ chalk "^2.4.2" cli-ux "^4.9.3" -"@salesforce/core@3.3.5": - version "3.3.5" - resolved "https://registry.npmjs.org/@salesforce/core/-/core-3.3.5.tgz#caa40e618c7c53d158336ce2d9a292d340982c0b" - integrity sha512-PwhErCgZVj8ho0svqL37XCWWngY7m58+28p+Lxm451uZ11NywrY7cgP6k7WE6vs5js81lBTvToSjesEWAZKPKg== +"@salesforce/core@3.3.8": + version "3.3.8" + resolved "https://registry.npmjs.org/@salesforce/core/-/core-3.3.8.tgz#43ca69f58ae7ec763a3523b94b7a5f14062c3ec8" + integrity sha512-+J2JAnKDsMogpmJuBQtcid8Ol8ASu1Dl+tMPUgNLMdc+EXIzxINx8uE+aIvtmx0bhKyIZIrN0PLFKzWnSOGXYw== dependencies: "@salesforce/bunyan" "^2.0.0" "@salesforce/kit" "^1.5.8"