Skip to content

Commit 38657e8

Browse files
authored
Merge pull request #58 from salesforcecli/sm/filtering-orgs-by-auth-files
fix: list command shows orgs that have userId
2 parents 5b62c40 + 18ba048 commit 38657e8

File tree

7 files changed

+402
-263
lines changed

7 files changed

+402
-263
lines changed

messages/display.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@
55
"sfdx force:org:display -u [email protected]",
66
"sfdx force:org:display -u TestOrg1 --json",
77
"sfdx force:org:display -u TestOrg1 --json > tmp/MyOrgDesc.json"
8-
]
8+
],
9+
"noScratchOrgInfoError": "No information for scratch org with ID %s found in Dev Hub %s.",
10+
"noScratchOrgInfoAction": "First check that you can access your Dev Hub. Then check that the ScratchOrgInfo standard object in your Dev Hub contains a record for your scratch org. In Setup, navigate to the Dev Hub page and click the Scratch Org Infos tab. If you find your scratch org, try again."
911
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"devDependencies": {
1616
"@oclif/dev-cli": "^1",
1717
"@oclif/plugin-command-snapshot": "^2.0.0",
18-
"@salesforce/cli-plugins-testkit": "^0.0.14",
18+
"@salesforce/cli-plugins-testkit": "^0.0.15",
1919
"@salesforce/dev-config": "^2.1.0",
2020
"@salesforce/dev-scripts": "0.9.1",
2121
"@salesforce/plugin-command-reference": "^1.3.0",

src/commands/force/org/display.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import * as os from 'os';
99
import { flags, FlagsConfig, SfdxCommand } from '@salesforce/command';
10-
import { AuthInfo, Messages, sfdc } from '@salesforce/core';
10+
import { AuthInfo, Messages, sfdc, SfdxError } from '@salesforce/core';
1111

1212
import { OrgDisplayReturn, ScratchOrgFields } from '../../../shared/orgTypes';
1313
import { getAliasByUsername, camelCaseToTitleCase } from '../../../shared/utils';
@@ -90,14 +90,22 @@ export class OrgDisplayCommand extends SfdxCommand {
9090
const result = (
9191
await OrgListUtil.retrieveScratchOrgInfoFromDevHub(hubOrg.getUsername(), [sfdc.trimTo15(orgId)])
9292
)[0];
93-
return {
94-
status: result.Status,
95-
expirationDate: result.ExpirationDate,
96-
createdBy: result.CreatedBy?.Username,
97-
edition: result.Edition ?? undefined, // null for snapshot orgs, possibly others. Marking it undefined keeps it out of json output
98-
namespace: result.Namespace ?? undefined, // may be null on server
99-
orgName: result.OrgName,
100-
createdDate: result.CreatedDate,
101-
};
93+
94+
if (result) {
95+
return {
96+
status: result.Status,
97+
expirationDate: result.ExpirationDate,
98+
createdBy: result.CreatedBy?.Username,
99+
edition: result.Edition ?? undefined, // null for snapshot orgs, possibly others. Marking it undefined keeps it out of json output
100+
namespace: result.Namespace ?? undefined, // may be null on server
101+
orgName: result.OrgName,
102+
createdDate: result.CreatedDate,
103+
};
104+
}
105+
throw new SfdxError(
106+
messages.getMessage('noScratchOrgInfoError', [sfdc.trimTo15(orgId), hubOrg.getUsername()]),
107+
'NoScratchInfo',
108+
[messages.getMessage('noScratchOrgInfoAction')]
109+
);
102110
}
103111
}

src/shared/orgListUtil.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,34 @@ export class OrgListUtil {
103103
* @param fileNames All the filenames in the global hidden folder
104104
*/
105105
public static async readAuthFiles(fileNames: string[]): Promise<AuthInfo[]> {
106+
const orgFileNames = (await fs.readdir(Global.DIR)).filter((filename) => filename.match(/^00D.{15}\.json$/g));
107+
106108
const allAuths: AuthInfo[] = await Promise.all(
107109
fileNames.map(async (fileName) => {
108110
try {
109111
const orgUsername = basename(fileName, '.json');
110-
return AuthInfo.create({ username: orgUsername });
112+
const auth = await AuthInfo.create({ username: orgUsername });
113+
114+
const userId = auth?.getFields().userId;
115+
116+
// no userid? Definitely an org primary user
117+
if (!userId) {
118+
return auth;
119+
}
120+
const orgId = auth.getFields().orgId;
121+
122+
const orgFileName = `${orgId}.json`;
123+
// if userId, it could be created from password:generate command. If <orgId>.json doesn't exist, it's also not a secondary user auth file
124+
if (orgId && !orgFileNames.includes(orgFileName)) {
125+
return auth;
126+
}
127+
// Theory: within <orgId>.json, if the userId is the first entry, that's the primary username.
128+
if (orgFileNames.includes(orgFileName)) {
129+
const usernames = ((await fs.readJson(join(Global.DIR, orgFileName))) as { usernames: string[] }).usernames;
130+
if (usernames && usernames[0] === auth.getFields().username) {
131+
return auth;
132+
}
133+
}
111134
} catch (error) {
112135
const err = error as SfdxError;
113136
const logger = await OrgListUtil.retrieveLogger();
@@ -116,8 +139,7 @@ export class OrgListUtil {
116139
}
117140
})
118141
);
119-
// AuthInfos that have a userId are from user create, not an "org-level" auth. Omit them
120-
return allAuths.filter((authInfo) => !!authInfo && !authInfo.getFields().userId);
142+
return allAuths.filter((auth) => !!auth);
121143
}
122144

123145
/**

test/nut/commands/force/org/org.nut.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ describe('Org Command NUT', () => {
4444
let aliasedUsername: string;
4545
let defaultUserOrgId: string;
4646
let aliasUserOrgId: string;
47-
before(() => {
48-
session = TestSession.create({
47+
before(async () => {
48+
session = await TestSession.create({
4949
project: { name: 'forceOrgList' },
5050
setupCommands: [
5151
'sfdx force:org:create -f config/project-scratch-def.json --setdefaultusername --wait 10',

test/shared/orgListUtil.test.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,4 +214,74 @@ describe('orgListUtil tests', () => {
214214
expect(checkNonScratchOrgIsDevHub.called).to.be.false;
215215
});
216216
});
217+
218+
describe('auth file reading tests', () => {
219+
// mock reading 2 org files
220+
beforeEach(() => {
221+
stubMethod($$.SANDBOX, fs, 'readdir').resolves(['00D000000000000001.json', '00D000000000000002.json']);
222+
});
223+
224+
afterEach(async () => {
225+
$$.SANDBOX.restore();
226+
});
227+
228+
it('will return an org with userId without an org file', async () => {
229+
stubMethod($$.SANDBOX, AuthInfo, 'create').resolves({
230+
getFields: () => ({ ...orgAuthConfigFields, userId: '005xxxxxxxxxxxxx', orgId: '00D000000000000003' }),
231+
getConnectionOptions: () => ({ accessToken: orgAuthConfigFields.accessToken }),
232+
isJwt: () => false,
233+
isOauth: () => false,
234+
getUsername: () => orgAuthConfigFields.username,
235+
});
236+
const authFiles = await OrgListUtil.readAuthFiles([`${orgAuthConfigFields.username}.json`]);
237+
expect(authFiles.length).to.equal(1);
238+
expect(authFiles[0].getFields()).to.have.property('username').equals(orgAuthConfigFields.username);
239+
});
240+
241+
it('will return an org with userId with an org file where the userid is primary', async () => {
242+
stubMethod($$.SANDBOX, AuthInfo, 'create').resolves({
243+
getFields: () => ({ ...orgAuthConfigFields, userId: '005xxxxxxxxxxxxx', orgId: '00D000000000000001' }),
244+
getConnectionOptions: () => ({ accessToken: orgAuthConfigFields.accessToken }),
245+
isJwt: () => false,
246+
isOauth: () => false,
247+
getUsername: () => orgAuthConfigFields.username,
248+
});
249+
stubMethod($$.SANDBOX, fs, 'readJson').resolves({
250+
usernames: [orgAuthConfigFields.username, '[email protected]'],
251+
});
252+
const authFiles = await OrgListUtil.readAuthFiles([`${orgAuthConfigFields.username}.json`]);
253+
expect(authFiles.length).to.equal(1);
254+
expect(authFiles[0].getFields()).to.have.property('username').equals(orgAuthConfigFields.username);
255+
});
256+
257+
it('will NOT return an org with userId with an org file where the userid is NOT listed', async () => {
258+
stubMethod($$.SANDBOX, AuthInfo, 'create').resolves({
259+
getFields: () => ({ ...orgAuthConfigFields, userId: '005xxxxxxxxxxxxx', orgId: '00D000000000000001' }),
260+
getConnectionOptions: () => ({ accessToken: orgAuthConfigFields.accessToken }),
261+
isJwt: () => false,
262+
isOauth: () => false,
263+
getUsername: () => orgAuthConfigFields.username,
264+
});
265+
stubMethod($$.SANDBOX, fs, 'readJson').resolves({
266+
usernames: ['[email protected]'],
267+
});
268+
const authFiles = await OrgListUtil.readAuthFiles([`${orgAuthConfigFields.username}.json`]);
269+
expect(authFiles.length).to.equal(0);
270+
});
271+
272+
it('will NOT return an org with userId with an org file where the userid is listed but not first', async () => {
273+
stubMethod($$.SANDBOX, AuthInfo, 'create').resolves({
274+
getFields: () => ({ ...orgAuthConfigFields, userId: '005xxxxxxxxxxxxx', orgId: '00D000000000000001' }),
275+
getConnectionOptions: () => ({ accessToken: orgAuthConfigFields.accessToken }),
276+
isJwt: () => false,
277+
isOauth: () => false,
278+
getUsername: () => orgAuthConfigFields.username,
279+
});
280+
stubMethod($$.SANDBOX, fs, 'readJson').resolves({
281+
usernames: ['[email protected]', orgAuthConfigFields.username],
282+
});
283+
const authFiles = await OrgListUtil.readAuthFiles([`${orgAuthConfigFields.username}.json`]);
284+
expect(authFiles.length).to.equal(0);
285+
});
286+
});
217287
});

0 commit comments

Comments
 (0)