Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow stdin for sfdx-url #886

Merged
16 changes: 13 additions & 3 deletions command-snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,16 +82,26 @@
{
"command": "org:login:sfdx-url",
"plugin": "@salesforce/plugin-auth",
"flags": ["alias", "json", "loglevel", "no-prompt", "set-default", "set-default-dev-hub", "sfdx-url-file"],
"flags": [
"alias",
"json",
"loglevel",
"no-prompt",
"set-default",
"set-default-dev-hub",
"sfdx-url-file",
"sfdx-url-stdin"
],
"alias": ["force:auth:sfdxurl:store", "auth:sfdxurl:store"],
"flagChars": ["a", "d", "f", "p", "s"],
"flagChars": ["a", "d", "f", "p", "s", "u"],
"flagAliases": [
"noprompt",
"setalias",
"setdefaultdevhub",
"setdefaultdevhubusername",
"setdefaultusername",
"sfdxurlfile"
"sfdxurlfile",
"sfdxurlstdin"
]
},
{
Expand Down
12 changes: 11 additions & 1 deletion messages/sfdxurl.store.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# summary

Authorize an org using a Salesforce DX authorization URL stored in a file.
Authorize an org using a Salesforce DX authorization URL stored in a file or through standard input (stdin).

# description

Expand All @@ -18,10 +18,16 @@ NOTE: The "<%= config.bin %> org display --verbose" command displays the refresh

You can also create a JSON file that has a top-level property named sfdxAuthUrl whose value is the authorization URL. Finally, you can create a normal text file that includes just the URL and nothing else.

Alternatively, you can pipe the SFDX authorization URL through standard input by using the --sfdx-url-stdin flag and providing the '-' character as the value.

# flags.sfdx-url-file.summary

Path to a file that contains the Salesforce DX authorization URL.

# flags.sfdx-url-stdin.summary

Provide '-' as the value to pipe the Salesforce DX authorization URL through standard input (stdin).
jshackell-sfdc marked this conversation as resolved.
Show resolved Hide resolved
k-capehart marked this conversation as resolved.
Show resolved Hide resolved

# examples

- Authorize an org using the SFDX authorization URL in the files/authFile.json file:
Expand All @@ -31,3 +37,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

- Pipe the SFDX authorization URL from stdin by specifying the '-' value.

<%= "\n $ echo url | " + config.bin %> <%= command.id %> --sfdx-url-stdin -
30 changes: 22 additions & 8 deletions src/commands/org/login/sfdx-url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,18 @@ export default class LoginSfdxUrl extends AuthBaseCommand<AuthFields> {
'sfdx-url-file': Flags.file({
char: 'f',
summary: messages.getMessage('flags.sfdx-url-file.summary'),
required: true,
required: false,
Copy link
Contributor

@mdonnalley mdonnalley Jan 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add this to both sfdx-url-file and sfdx-url-stdin to ensure that people provide one of those

exactlyOne: ['sfdx-url-file', 'sfdx-url-stdin']

Doing that would allow you to drop the validation logic you have on lines 88-96

We should also add exclusive: ['sfdx-url-stdin'] to make sure that people don't provide both

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mdonnalley I added these suggestions but kept the validation logic, otherwise I get a compiler error that sfdxAuthUrl could be undefined. I think that's because its not smart enough to know that one of the flags are guaranteed to be provided. Let me know if you know a work-around, but I think that's consistent with other implementations.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@k-capehart I think exclusive is implied in exactlyOne so there's not actually a need for both - you only need exactlyOne on both flags

Sorry about that - should have tested it all out before asking you to make a change!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mdonnalley Oh makes sense, that explains the repetition in error messages I was seeing. I removed it 👍

deprecateAliases: true,
aliases: ['sfdxurlfile'],
}),
'sfdx-url-stdin': Flags.file({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add exclusive: ['sfdx-url-file'] to make sure that people don't provide both

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mdonnalley, question: It sounds to me like --sfdx-url-stdin can take only one value (-), and if it's present then the user is doing the piping-through-stdin thing. So wouldn't it make more sense to make --sfdx-url-stdin a Boolean flag? Or maybe I'm missing something.

Copy link
Author

@k-capehart k-capehart Jan 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can provide some context, this is what I based the solution on: oclif/core#761 (comment)

In this case, the value should always be - but that is consistent with CLI standards because oclif requires the - value in order to read from standard input.

char: 'u',
summary: messages.getMessage('flags.sfdx-url-stdin.summary'),
required: false,
deprecateAliases: true,
aliases: ['sfdxurlstdin'],
allowStdin: 'only',
}),
'set-default-dev-hub': Flags.boolean({
char: 'd',
summary: commonMessages.getMessage('flags.set-default-dev-hub.summary'),
Expand Down Expand Up @@ -71,15 +79,21 @@ export default class LoginSfdxUrl extends AuthBaseCommand<AuthFields> {
if (await this.shouldExitCommand(flags['no-prompt'])) return {};

const authFile = flags['sfdx-url-file'];
const authStdin = flags['sfdx-url-stdin'];
let sfdxAuthUrl: string;

const sfdxAuthUrl = authFile.endsWith('.json')
? await getUrlFromJson(authFile)
: await fs.readFile(authFile, 'utf8');
if (authFile) {
sfdxAuthUrl = authFile.endsWith('.json') ? await getUrlFromJson(authFile) : await fs.readFile(authFile, 'utf8');

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.`
);
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.`
);
}
} else if (authStdin) {
sfdxAuthUrl = authStdin;
} else {
throw new Error('Please include either the --sfdx-url-stdin or --sfdx-url-file flags.');
}

const oauth2Options = AuthInfo.parseSfdxAuthUrl(sfdxAuthUrl);
Expand Down
29 changes: 28 additions & 1 deletion test/commands/org/login/login.sfdx-url.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { AuthFields, AuthInfo, Global, Mode } from '@salesforce/core';
import { MockTestOrgData, TestContext } from '@salesforce/core/lib/testSetup.js';
import { expect } from 'chai';
import { Config } from '@oclif/core';
import { StubbedType, stubInterface } from '@salesforce/ts-sinon';
import { StubbedType, stubInterface, stubMethod } from '@salesforce/ts-sinon';
import { SfCommand } from '@salesforce/sf-plugins-core';
import LoginSfdxUrl from '../../../../src/commands/org/login/sfdx-url.js';

Expand Down Expand Up @@ -213,4 +213,31 @@ describe('org:login:sfdx-url', () => {
await store.run();
expect(authInfoStub.save.called);
});

it('should error out when neither file or url are provided', async () => {
await prepareStubs();
const store = new LoginSfdxUrl([], {} 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('Please include either the --sfdx-url-stdin or --sfdx-url-file flags.');
}
});

it('should return auth fields when using stdin', async () => {
await prepareStubs();
const sfdxAuthUrl = 'force://PlatformCLI::CoffeeAndBacon@su0503.my.salesforce.com';
const flagOutput = {
flags: {
'no-prompt': false,
'sfdx-url-file': '',
'sfdx-url-stdin': sfdxAuthUrl,
},
};
const store = new LoginSfdxUrl(['-u', '-'], {} as Config);
stubMethod($$.SANDBOX, store, 'parse').resolves(flagOutput);
const response = await store.run();
expect(response.username).to.equal(testData.username);
});
});