From c9723ee50cbb142f3a6fc7789a92865b8fd6994b Mon Sep 17 00:00:00 2001 From: Allan Oricil Date: Fri, 28 Jul 2023 11:47:16 -0300 Subject: [PATCH 1/7] feat: add --sfdx-url-stdin flag --- command-snapshot.json | 11 +++++- messages/sfdxurl.store.md | 8 +++++ src/commands/org/login/sfdx-url.ts | 24 ++++++++++--- src/stdin.ts | 25 ++++++++++++++ test/commands/org/login/login.sfdx-url.nut.ts | 8 +++++ .../commands/org/login/login.sfdx-url.test.ts | 34 ++++++++++++++++++- 6 files changed, 103 insertions(+), 7 deletions(-) create mode 100644 src/stdin.ts diff --git a/command-snapshot.json b/command-snapshot.json index 6c698b13..bb48f2de 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -82,8 +82,17 @@ { "command": "org:login:sfdx-url", "plugin": "@salesforce/plugin-auth", - "flags": ["alias", "json", "loglevel", "no-prompt", "set-default", "set-default-dev-hub", "sfdx-url-file"], "alias": ["force:auth:sfdxurl:store", "auth:sfdxurl:store"], + "flags": [ + "alias", + "json", + "loglevel", + "no-prompt", + "set-default", + "set-default-dev-hub", + "sfdx-url-file", + "sfdx-url-stdin" + ], "flagChars": ["a", "d", "f", "p", "s"], "flagAliases": [ "noprompt", diff --git a/messages/sfdxurl.store.md b/messages/sfdxurl.store.md index 706da034..af8e3d02 100644 --- a/messages/sfdxurl.store.md +++ b/messages/sfdxurl.store.md @@ -22,6 +22,10 @@ You can also create a JSON file that has a top-level property named sfdxAuthUrl Path to a file that contains the Salesforce DX authorization URL. +# flags.sfdx-url-stdin.summary + +Read sfdx auth url from stdin + # examples - Authorize an org using the SFDX authorization URL in the files/authFile.json file: @@ -31,3 +35,7 @@ Path to a file that contains the Salesforce DX authorization URL. - Similar to previous example, but set the org as your default and give it an alias MyDefaultOrg: <%= config.bin %> <%= command.id %> --sfdx-url-file files/authFile.json --set-default --alias MyDefaultOrg + +- Authorize an org reading the SFDX authorization URL from stdin: + + echo 'force://PlatformCLI::CoffeeAndBacon@su0503.my.salesforce.com' | <%= config.bin %> <%= command.id %> --sfdx-url-stdin --set-default --alias MyDefaultOrg diff --git a/src/commands/org/login/sfdx-url.ts b/src/commands/org/login/sfdx-url.ts index 003b4e9e..87240247 100644 --- a/src/commands/org/login/sfdx-url.ts +++ b/src/commands/org/login/sfdx-url.ts @@ -11,6 +11,7 @@ import { AuthFields, AuthInfo, Messages } from '@salesforce/core'; import { AnyJson } from '@salesforce/ts-types'; import { parseJson } from '@salesforce/kit'; import { AuthBaseCommand } from '../../../authBaseCommand'; +import { read } from '../../../stdin'; Messages.importMessagesDirectory(__dirname); const messages = Messages.loadMessages('@salesforce/plugin-auth', 'sfdxurl.store'); @@ -22,6 +23,7 @@ type AuthJson = AnyJson & { result?: AnyJson & { sfdxAuthUrl: string }; sfdxAuthUrl: string; }; + export default class LoginSfdxUrl extends AuthBaseCommand { public static readonly summary = messages.getMessage('summary'); public static readonly description = messages.getMessage('description', [AUTH_URL_FORMAT]); @@ -29,12 +31,18 @@ export default class LoginSfdxUrl extends AuthBaseCommand { public static aliases = ['force:auth:sfdxurl:store', 'auth:sfdxurl:store']; public static readonly flags = { + 'sfdx-url-stdin': Flags.boolean({ + summary: messages.getMessage('flags.sfdx-url-stdin.summary'), + exclusive: ['sfdx-url-file'], + exactlyOne: ['sfdx-url-file'], + }), 'sfdx-url-file': Flags.file({ char: 'f', summary: messages.getMessage('flags.sfdx-url-file.summary'), - required: true, deprecateAliases: true, aliases: ['sfdxurlfile'], + exclusive: ['sfdx-url-stdin'], + exactlyOne: ['sfdx-url-stdin'], }), 'set-default-dev-hub': Flags.boolean({ char: 'd', @@ -67,15 +75,21 @@ export default class LoginSfdxUrl extends AuthBaseCommand { public async run(): Promise { const { flags } = await this.parse(LoginSfdxUrl); - if (await this.shouldExitCommand(flags['no-prompt'])) return {}; - const authFile = flags['sfdx-url-file']; + if (await this.shouldExitCommand(flags['no-prompt'])) return {}; - const sfdxAuthUrl = authFile.endsWith('.json') ? await getUrlFromJson(authFile) : await readFile(authFile, 'utf8'); + const sfdxUrlFile = flags['sfdx-url-file']; + const sfdxAuthUrl = flags['sfdx-url-stdin'] + ? await read() + : sfdxUrlFile + ? sfdxUrlFile.endsWith('.json') + ? await getUrlFromJson(sfdxUrlFile) + : await readFile(sfdxUrlFile, 'utf8') + : null; if (!sfdxAuthUrl) { throw new Error( - `Error getting the auth URL from file ${authFile}. Please ensure it meets the description shown in the documentation for this command.` + 'Error retrieving the auth URL. Please ensure it meets the description shown in the documentation for this command.' ); } diff --git a/src/stdin.ts b/src/stdin.ts new file mode 100644 index 00000000..342d2273 --- /dev/null +++ b/src/stdin.ts @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023, 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 + */ +export function read(): Promise { + return new Promise((resolve) => { + const stdin = process.openStdin(); + stdin.setEncoding('utf-8'); + + let data = ''; + stdin.on('data', (chunk) => { + data += chunk; + }); + + stdin.on('end', () => { + resolve(data); + }); + + if (stdin.isTTY) { + resolve(''); + } + }); +} diff --git a/test/commands/org/login/login.sfdx-url.nut.ts b/test/commands/org/login/login.sfdx-url.nut.ts index 63d2f6bd..47590429 100644 --- a/test/commands/org/login/login.sfdx-url.nut.ts +++ b/test/commands/org/login/login.sfdx-url.nut.ts @@ -56,4 +56,12 @@ describe('org:login:sfdx-url NUTs', () => { const output = getString(result, 'shellOutput.stdout'); expect(output).to.include(`Successfully authorized ${username} with org ID`); }); + + it('should authorize an org using sfdx-url (human readable)', () => { + env.setString('TESTKIT_EXECUTABLE_PATH', `echo '${authUrl}' | ${env.getString('TESTKIT_EXECUTABLE_PATH')}`); + const command = 'org:login:sfdx-url -d --sfdx-url-stdin'; + const result = execCmd(command, { ensureExitCode: 0 }); + const output = getString(result, 'shellOutput.stdout'); + expect(output).to.include(`Successfully authorized ${username} with org ID`); + }); }); diff --git a/test/commands/org/login/login.sfdx-url.test.ts b/test/commands/org/login/login.sfdx-url.test.ts index a6081303..7eb511ac 100644 --- a/test/commands/org/login/login.sfdx-url.test.ts +++ b/test/commands/org/login/login.sfdx-url.test.ts @@ -13,6 +13,7 @@ import { Config } from '@oclif/core'; import { StubbedType, stubInterface } from '@salesforce/ts-sinon'; import { SfCommand } from '@salesforce/sf-plugins-core'; import LoginSfdxUrl from '../../../../src/commands/org/login/sfdx-url'; +import * as stdin from '../../../../src/stdin'; interface Options { authInfoCreateFails?: boolean; @@ -91,7 +92,7 @@ describe('org:login:sfdx-url', () => { const response = await store.run(); expect.fail(`Should have thrown an error. Response: ${JSON.stringify(response)}`); } catch (e) { - expect((e as Error).message).to.includes('Error getting the auth URL from file'); + expect((e as Error).message).to.includes('Error retrieving the auth URL'); } }); @@ -213,4 +214,35 @@ describe('org:login:sfdx-url', () => { await store.run(); expect(authInfoStub.save.callCount).to.equal(1); }); + + it('should return auth fields when reading auth url from stdin', async () => { + await prepareStubs({ fileDoesNotExist: true }); + $$.SANDBOX.stub(stdin, 'read').resolves('force://PlatformCLI::CoffeeAndBacon@su0503.my.salesforce.com'); + const store = new LoginSfdxUrl(['--sfdx-url-stdin', '--json'], {} as Config); + const response = await store.run(); + expect(response.username).to.equal(testData.username); + }); + + it('should throw error when passing both sfdx-url-stdin and sfdx-url-file', async () => { + const store = new LoginSfdxUrl(['--sfdx-url-stdin', '--sfdx-url-file', 'path/to/key.txt', '--json'], {} as Config); + + try { + const response = await store.run(); + expect.fail(`Should have thrown an error. Response: ${JSON.stringify(response)}`); + } catch (e) { + expect((e as Error).message).to.includes('--sfdx-url-file cannot also be provided when using --sfdx-url-stdin'); + } + }); + + it('should throw error when not passing sfdx-url-stdin and sfdx-url-file', async () => { + const store = new LoginSfdxUrl(['--json'], {} as Config); + + try { + const response = await store.run(); + expect.fail(`Should have thrown an error. Response: ${JSON.stringify(response)}`); + } catch (e) { + expect((e as Error).message).to.includes('Exactly one of the following must be provided: --sfdx-url-file'); + expect((e as Error).message).to.includes('Exactly one of the following must be provided: --sfdx-url-stdin'); + } + }); }); From 947bf2da8b7905e7217881fa09d85ae724ae464c Mon Sep 17 00:00:00 2001 From: Allan Oricil Date: Sun, 30 Jul 2023 15:15:14 -0300 Subject: [PATCH 2/7] test: add new test to ensure cli throws error when piping empty string --- test/commands/org/login/login.sfdx-url.test.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/commands/org/login/login.sfdx-url.test.ts b/test/commands/org/login/login.sfdx-url.test.ts index 7eb511ac..6e5d77ef 100644 --- a/test/commands/org/login/login.sfdx-url.test.ts +++ b/test/commands/org/login/login.sfdx-url.test.ts @@ -223,6 +223,19 @@ describe('org:login:sfdx-url', () => { expect(response.username).to.equal(testData.username); }); + it('should throw error when piping empty string to stdin', async () => { + await prepareStubs({ fileDoesNotExist: true }); + $$.SANDBOX.stub(stdin, 'read').resolves(''); + const store = new LoginSfdxUrl(['--sfdx-url-stdin', '--json'], {} as Config); + + try { + const response = await store.run(); + expect.fail(`Should have thrown an error. Response: ${JSON.stringify(response)}`); + } catch (e) { + expect((e as Error).message).to.includes('Error retrieving the auth URL'); + } + }); + it('should throw error when passing both sfdx-url-stdin and sfdx-url-file', async () => { const store = new LoginSfdxUrl(['--sfdx-url-stdin', '--sfdx-url-file', 'path/to/key.txt', '--json'], {} as Config); From 7ab77f6dd8ed1452ee26289464f3a137ad250186 Mon Sep 17 00:00:00 2001 From: Allan Oricil Date: Mon, 31 Jul 2023 12:33:52 -0300 Subject: [PATCH 3/7] test: add new test to ensure cli throws error when stdin resolves to undefined --- test/commands/org/login/login.sfdx-url.test.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/commands/org/login/login.sfdx-url.test.ts b/test/commands/org/login/login.sfdx-url.test.ts index 6e5d77ef..03a15e28 100644 --- a/test/commands/org/login/login.sfdx-url.test.ts +++ b/test/commands/org/login/login.sfdx-url.test.ts @@ -236,6 +236,19 @@ describe('org:login:sfdx-url', () => { } }); + it('should throw error when not piping anything to stdin', async () => { + await prepareStubs({ fileDoesNotExist: true }); + $$.SANDBOX.stub(stdin, 'read').resolves(undefined); + const store = new LoginSfdxUrl(['--sfdx-url-stdin', '--json'], {} as Config); + + try { + const response = await store.run(); + expect.fail(`Should have thrown an error. Response: ${JSON.stringify(response)}`); + } catch (e) { + expect((e as Error).message).to.includes('Error retrieving the auth URL'); + } + }); + it('should throw error when passing both sfdx-url-stdin and sfdx-url-file', async () => { const store = new LoginSfdxUrl(['--sfdx-url-stdin', '--sfdx-url-file', 'path/to/key.txt', '--json'], {} as Config); From c8dd957971b873468204add5942f4d0059852872 Mon Sep 17 00:00:00 2001 From: Allan Oricil Date: Mon, 31 Jul 2023 15:02:09 -0300 Subject: [PATCH 4/7] test: add new test to ensure cli ignores stdin value when --sfdx-url-stdin is not used --- test/commands/org/login/login.sfdx-url.test.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/commands/org/login/login.sfdx-url.test.ts b/test/commands/org/login/login.sfdx-url.test.ts index 03a15e28..028a27aa 100644 --- a/test/commands/org/login/login.sfdx-url.test.ts +++ b/test/commands/org/login/login.sfdx-url.test.ts @@ -8,7 +8,7 @@ import * as fs from 'fs/promises'; import { AuthFields, AuthInfo, Global, Mode } from '@salesforce/core'; import { MockTestOrgData, TestContext } from '@salesforce/core/lib/testSetup'; -import { expect } from 'chai'; +import { expect, assert } from 'chai'; import { Config } from '@oclif/core'; import { StubbedType, stubInterface } from '@salesforce/ts-sinon'; import { SfCommand } from '@salesforce/sf-plugins-core'; @@ -249,6 +249,16 @@ describe('org:login:sfdx-url', () => { } }); + it('should ignore stdin when not using --sfdx-url-stdin', async () => { + await prepareStubs(); + $$.SANDBOX.stub(stdin, 'read').resolves('force://foo::bar@su0503.my.salesforce.com'); + const parseSfdxAuthUrlSpy = $$.SANDBOX.spy(AuthInfo, 'parseSfdxAuthUrl'); + const store = new LoginSfdxUrl(['-f', keyPathTxt, '--json'], {} as Config); + await store.run(); + + assert.isTrue(parseSfdxAuthUrlSpy.calledWith('force://PlatformCLI::CoffeeAndBacon@su0503.my.salesforce.com')); + }); + it('should throw error when passing both sfdx-url-stdin and sfdx-url-file', async () => { const store = new LoginSfdxUrl(['--sfdx-url-stdin', '--sfdx-url-file', 'path/to/key.txt', '--json'], {} as Config); From 94ebcb6d0b38760923ad991c43931ef102de6b18 Mon Sep 17 00:00:00 2001 From: Allan Oricil Date: Mon, 14 Aug 2023 22:06:54 -0300 Subject: [PATCH 5/7] feat: throws error using string from message store --- messages/sfdxurl.store.md | 4 ++++ src/commands/org/login/sfdx-url.ts | 4 +--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/messages/sfdxurl.store.md b/messages/sfdxurl.store.md index af8e3d02..b27c7293 100644 --- a/messages/sfdxurl.store.md +++ b/messages/sfdxurl.store.md @@ -26,6 +26,10 @@ Path to a file that contains the Salesforce DX authorization URL. Read sfdx auth url from stdin +# errors.missingAuthUrl + +Error retrieving the auth URL. Please ensure it meets the description shown in the documentation for this command. + # examples - Authorize an org using the SFDX authorization URL in the files/authFile.json file: diff --git a/src/commands/org/login/sfdx-url.ts b/src/commands/org/login/sfdx-url.ts index 87240247..007e3101 100644 --- a/src/commands/org/login/sfdx-url.ts +++ b/src/commands/org/login/sfdx-url.ts @@ -88,9 +88,7 @@ export default class LoginSfdxUrl extends AuthBaseCommand { : null; if (!sfdxAuthUrl) { - throw new Error( - 'Error retrieving the auth URL. Please ensure it meets the description shown in the documentation for this command.' - ); + throw messages.createError('errors.missingAuthUrl'); } const oauth2Options = AuthInfo.parseSfdxAuthUrl(sfdxAuthUrl); From 68ed60788ebb7c022be348f93f848054e9629c10 Mon Sep 17 00:00:00 2001 From: Allan Oricil Date: Mon, 14 Aug 2023 22:07:29 -0300 Subject: [PATCH 6/7] test: assert error message using string from message store --- test/commands/org/login/login.sfdx-url.test.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/commands/org/login/login.sfdx-url.test.ts b/test/commands/org/login/login.sfdx-url.test.ts index 028a27aa..7e188ac4 100644 --- a/test/commands/org/login/login.sfdx-url.test.ts +++ b/test/commands/org/login/login.sfdx-url.test.ts @@ -6,7 +6,7 @@ */ import * as fs from 'fs/promises'; -import { AuthFields, AuthInfo, Global, Mode } from '@salesforce/core'; +import { AuthFields, AuthInfo, Global, Mode, Messages } from '@salesforce/core'; import { MockTestOrgData, TestContext } from '@salesforce/core/lib/testSetup'; import { expect, assert } from 'chai'; import { Config } from '@oclif/core'; @@ -15,6 +15,9 @@ import { SfCommand } from '@salesforce/sf-plugins-core'; import LoginSfdxUrl from '../../../../src/commands/org/login/sfdx-url'; import * as stdin from '../../../../src/stdin'; +Messages.importMessagesDirectory(__dirname); +const messages = Messages.loadMessages('@salesforce/plugin-auth', 'sfdxurl.store'); + interface Options { authInfoCreateFails?: boolean; existingAuth?: boolean; @@ -92,7 +95,7 @@ describe('org:login:sfdx-url', () => { const response = await store.run(); expect.fail(`Should have thrown an error. Response: ${JSON.stringify(response)}`); } catch (e) { - expect((e as Error).message).to.includes('Error retrieving the auth URL'); + expect((e as Error).message).to.includes(messages.getMessage('errors.missingAuthUrl')); } }); @@ -232,7 +235,7 @@ describe('org:login:sfdx-url', () => { const response = await store.run(); expect.fail(`Should have thrown an error. Response: ${JSON.stringify(response)}`); } catch (e) { - expect((e as Error).message).to.includes('Error retrieving the auth URL'); + expect((e as Error).message).to.includes(messages.getMessage('errors.missingAuthUrl')); } }); @@ -245,7 +248,7 @@ describe('org:login:sfdx-url', () => { const response = await store.run(); expect.fail(`Should have thrown an error. Response: ${JSON.stringify(response)}`); } catch (e) { - expect((e as Error).message).to.includes('Error retrieving the auth URL'); + expect((e as Error).message).to.includes(messages.getMessage('errors.missingAuthUrl')); } }); From 5b0d76da9b5a7aee402bb1fe04c79e30649603b4 Mon Sep 17 00:00:00 2001 From: Allan Oricil Date: Mon, 14 Aug 2023 22:09:06 -0300 Subject: [PATCH 7/7] test: improves nut test to prove sfdx-url comes from stdin --- test/commands/org/login/login.sfdx-url.nut.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/commands/org/login/login.sfdx-url.nut.ts b/test/commands/org/login/login.sfdx-url.nut.ts index 47590429..63a99c41 100644 --- a/test/commands/org/login/login.sfdx-url.nut.ts +++ b/test/commands/org/login/login.sfdx-url.nut.ts @@ -6,6 +6,7 @@ */ import { execCmd, prepareForAuthUrl, TestSession } from '@salesforce/cli-plugins-testkit'; import { expect } from 'chai'; +import * as exec from 'shelljs'; import { Env } from '@salesforce/kit'; import { ensureString, getString } from '@salesforce/ts-types'; import { AuthFields } from '@salesforce/core'; @@ -58,10 +59,7 @@ describe('org:login:sfdx-url NUTs', () => { }); it('should authorize an org using sfdx-url (human readable)', () => { - env.setString('TESTKIT_EXECUTABLE_PATH', `echo '${authUrl}' | ${env.getString('TESTKIT_EXECUTABLE_PATH')}`); - const command = 'org:login:sfdx-url -d --sfdx-url-stdin'; - const result = execCmd(command, { ensureExitCode: 0 }); - const output = getString(result, 'shellOutput.stdout'); - expect(output).to.include(`Successfully authorized ${username} with org ID`); + const res = exec.cat(authUrl).exec('bin/dev org:login:sfdx-url -d --sfdx-url-stdin', { silent: true }); + expect(res.stdout).to.include(`Successfully authorized ${username} with org ID`); }); });