Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion LICENSE.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright (c) 2022, Salesforce.com, Inc.
Copyright (c) 2023, Salesforce.com, Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Expand Down
11 changes: 10 additions & 1 deletion src/compatibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import { Flags } from '@oclif/core';
import { Lifecycle, Messages } from '@salesforce/core';
import { orgApiVersionFlag } from './flags/orgApiVersion';
import { optionalOrgFlag, requiredHubFlag, requiredOrgFlag } from './flags/orgFlags';
import { optionalHubFlag, optionalOrgFlag, requiredHubFlag, requiredOrgFlag } from './flags/orgFlags';

/**
* Adds an alias for the deprecated sfdx-style "apiversion" and provides a warning if it is used
Expand Down Expand Up @@ -77,6 +77,15 @@ export const requiredHubFlagWithDeprecations = requiredHubFlag({
required: true,
});

/**
* @deprecated
*/
export const optionalHubFlagWithDeprecations = optionalHubFlag({
aliases: ['targetdevhubusername'],
deprecateAliases: true,
required: false,
});

export type ArrayWithDeprecationOptions = {
// prevent invalid options from being passed
multiple?: true;
Expand Down
3 changes: 2 additions & 1 deletion src/exported.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export * from './types';
export { SfCommand, SfCommandInterface, StandardColors } from './sfCommand';
export * from './compatibility';
// custom flags
import { requiredOrgFlag, requiredHubFlag, optionalOrgFlag } from './flags/orgFlags';
import { requiredOrgFlag, requiredHubFlag, optionalOrgFlag, optionalHubFlag } from './flags/orgFlags';
import { salesforceIdFlag } from './flags/salesforceId';
import { orgApiVersionFlag } from './flags/orgApiVersion';
import { durationFlag } from './flags/duration';
Expand All @@ -35,4 +35,5 @@ export const Flags = {
requiredOrg: requiredOrgFlag,
requiredHub: requiredHubFlag,
optionalOrg: optionalOrgFlag,
optionalHub: optionalHubFlag,
};
77 changes: 64 additions & 13 deletions src/flags/orgFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import { Messages, Org, ConfigAggregator, OrgConfigProperties } from '@salesforc
Messages.importMessagesDirectory(__dirname);
const messages = Messages.loadMessages('@salesforce/sf-plugins-core', 'messages');

const maybeGetOrg = async (input?: string): Promise<Org | undefined> => {
export async function maybeGetOrg(input: string): Promise<Org>;
export async function maybeGetOrg(input: undefined): Promise<undefined>;
export async function maybeGetOrg(input?: string | undefined): Promise<Org | undefined>;
export async function maybeGetOrg(input?: string | undefined): Promise<Org | undefined> {
try {
return await Org.create({ aliasOrUsername: input });
} catch (e) {
Expand All @@ -20,30 +23,48 @@ const maybeGetOrg = async (input?: string): Promise<Org | undefined> => {
throw e;
}
}
}

export const maybeGetHub = async (input?: string): Promise<Org | undefined> => {
const org = await maybeGetOrg(input ?? (await getDefaultHub(false)));
if (org) {
return ensureDevHub(org, input ?? org.getUsername());
} else {
return undefined;
}
};

const getOrgOrThrow = async (input?: string): Promise<Org> => {
export const getOrgOrThrow = async (input?: string): Promise<Org> => {
const org = await maybeGetOrg(input);
if (!org) {
throw messages.createError('errors.NoDefaultEnv');
}
return org;
};

const getHubOrThrow = async (aliasOrUsername?: string): Promise<Org> => {
if (!aliasOrUsername) {
// check config for a default
const config = await ConfigAggregator.create();
aliasOrUsername = config.getInfo(OrgConfigProperties.TARGET_DEV_HUB)?.value as string;
if (!aliasOrUsername) {
throw messages.createError('errors.NoDefaultDevHub');
}
}
const org = await Org.create({ aliasOrUsername });
const ensureDevHub = async (org: Org, aliasOrUsername?: string): Promise<Org> => {
if (await org.determineIfDevHubOrg()) {
return org;
}
throw messages.createError('errors.NotADevHub', [aliasOrUsername]);
throw messages.createError('errors.NotADevHub', [aliasOrUsername ?? org.getUsername()]);
};

async function getDefaultHub(throwIfNotFound: false): Promise<string | undefined>;
async function getDefaultHub(throwIfNotFound: true): Promise<string>;
async function getDefaultHub(throwIfNotFound: boolean): Promise<string | undefined> {
// check config for a default
const config = await ConfigAggregator.create();
const aliasOrUsername = config.getInfo(OrgConfigProperties.TARGET_DEV_HUB)?.value as string;
if (throwIfNotFound && !aliasOrUsername) {
throw messages.createError('errors.NoDefaultDevHub');
}
return aliasOrUsername;
}

export const getHubOrThrow = async (aliasOrUsername?: string): Promise<Org> => {
const resolved = aliasOrUsername ?? (await getDefaultHub(true));
const org = await Org.create({ aliasOrUsername: resolved, isDevHub: true });
return ensureDevHub(org, resolved);
};

/**
Expand Down Expand Up @@ -134,3 +155,33 @@ export const requiredHubFlag = Flags.custom({
defaultHelp: async () => (await getHubOrThrow())?.getUsername(),
required: true,
});

/**
* An optional org that, if present, must be a devHub
* Will throw if the specified org does not exist
* Will default to the default dev hub if one is not specified
* Will NOT throw if no default deb hub exists and none is specified
*
* @example
*
* ```
* import { Flags } from '@salesforce/sf-plugins-core';
* public static flags = {
* // setting length or prefix
* 'target-org': optionalHubFlag(),
* // adding properties
* 'flag2': optionalHubFlag({
* description: 'flag2 description',
* char: 'h'
* }),
* }
* ```
*/
export const optionalHubFlag = Flags.custom({
char: 'v',
summary: messages.getMessage('flags.targetDevHubOrg.summary'),
parse: async (input: string | undefined) => maybeGetHub(input),
default: async () => maybeGetHub(),
defaultHelp: async () => (await maybeGetHub())?.getUsername(),
required: false,
});
164 changes: 164 additions & 0 deletions test/unit/flags/orgFlags.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*
* Copyright (c) 2020, 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
*/
import { Org, SfError, OrgConfigProperties } from '@salesforce/core';
import { MockTestOrgData, shouldThrow, TestContext } from '@salesforce/core/lib/testSetup';
import { assert, expect, config } from 'chai';
import { getHubOrThrow, getOrgOrThrow, maybeGetHub, maybeGetOrg } from '../../../src/flags/orgFlags';

config.truncateThreshold = 0;

describe('org flags', () => {
const $$ = new TestContext();
const testOrg = new MockTestOrgData();
const testHub = new MockTestOrgData();
// set these into the "cache" to avoid "server checks"
testOrg.isDevHub = false;
testHub.isDevHub = true;

beforeEach(async () => {
await $$.stubAuths(testOrg, testHub);
});
afterEach(async () => {
$$.restore();
});

describe('requiredOrg', () => {
it('has input, returns org', async () => {
const retrieved = await getOrgOrThrow(testOrg.username);
expect(retrieved.getOrgId()).to.equal(testOrg.orgId);
});
// skipped tests are waiting for a fix to core/testSetup https://github.com/forcedotcom/sfdx-core/pull/748
it.skip('has input, no org found => throw', async () => {
try {
await shouldThrow(getOrgOrThrow('[email protected]'));
} catch (e) {
assert(e instanceof SfError);
expect(e).to.have.property('name', 'NamedOrgNotFound');
}
});
it('no input, uses default', async () => {
await $$.stubConfig({ [OrgConfigProperties.TARGET_ORG]: testOrg.username });
expect(await getOrgOrThrow()).to.be.instanceOf(Org);
});
it('no input, no default => throw', async () => {
await $$.stubConfig({});
try {
await shouldThrow(getOrgOrThrow());
} catch (e) {
assert(e instanceof SfError);
expect(e).to.have.property('name', 'NoDefaultEnvError');
}
});
});
describe('optionalOrg', () => {
it('has input, returns org', async () => {
const retrieved = await maybeGetOrg(testOrg.username);
expect(retrieved.getOrgId()).to.equal(testOrg.orgId);
});
it.skip('has input, no org => throw', async () => {
try {
await shouldThrow(maybeGetOrg('[email protected]'));
} catch (e) {
assert(e instanceof SfError);
expect(e).to.have.property('name', 'NamedOrgNotFound');
}
});
it('no input, uses default', async () => {
await $$.stubConfig({ [OrgConfigProperties.TARGET_ORG]: testOrg.username });
expect(await maybeGetOrg()).to.be.instanceOf(Org);
});
it('no input, no default => ok', async () => {
expect(await maybeGetOrg()).to.be.undefined;
});
});
describe('requiredHub', () => {
it('has input, returns org', async () => {
expect(await getHubOrThrow(testHub.username)).to.be.instanceOf(Org);
});
it('has input, finds org that is not a hub => throw', async () => {
try {
await shouldThrow(getHubOrThrow(testOrg.username));
} catch (e) {
assert(e instanceof SfError);
expect(e).to.have.property('name', 'NotADevHubError');
}
});
it.skip('has input, no org => throw', async () => {
try {
await shouldThrow(maybeGetHub('[email protected]'));
} catch (e) {
assert(e instanceof SfError);
expect(e).to.have.property('name', 'NamedOrgNotFound');
}
});
it('no input, uses default', async () => {
await $$.stubConfig({ [OrgConfigProperties.TARGET_DEV_HUB]: testHub.username });
const retrieved = await getHubOrThrow();
expect(retrieved).to.be.instanceOf(Org);
expect(retrieved.getOrgId()).to.equal(testHub.orgId);
});
it('no input, uses default but is not a hub => throw', async () => {
await $$.stubConfig({ [OrgConfigProperties.TARGET_DEV_HUB]: testOrg.username });
try {
await shouldThrow(getHubOrThrow());
} catch (e) {
assert(e instanceof SfError);
expect(e).to.have.property('name', 'NotADevHubError');
}
});
it('no input, no default => throws', async () => {
await $$.stubConfig({});
try {
await shouldThrow(getHubOrThrow());
} catch (e) {
assert(e instanceof SfError);
expect(e).to.have.property('name', 'NoDefaultDevHubError');
}
});
});
describe('optionalHub', () => {
it('has input, returns org', async () => {
const retrieved = await maybeGetHub(testHub.username);
expect(retrieved).to.be.instanceOf(Org);
});
it('has input, finds org that is not a hub => throw', async () => {
try {
await shouldThrow(maybeGetHub(testOrg.username));
} catch (e) {
assert(e instanceof SfError);
expect(e).to.have.property('name', 'NotADevHubError');
}
});
it.skip('has input, no org => throws', async () => {
try {
await shouldThrow(maybeGetHub('[email protected]'));
} catch (e) {
assert(e instanceof SfError);
expect(e).to.have.property('name', 'NamedOrgNotFound');
}
});
it('no input, uses default', async () => {
await $$.stubConfig({ [OrgConfigProperties.TARGET_DEV_HUB]: testHub.username });
const retrieved = await maybeGetHub();
expect(retrieved).to.be.instanceOf(Org);
expect(retrieved.getOrgId()).to.equal(testHub.orgId);
});
it('no input, uses default but is not a hub => throw', async () => {
await $$.stubConfig({ [OrgConfigProperties.TARGET_DEV_HUB]: testOrg.username });
try {
await shouldThrow(maybeGetHub());
} catch (e) {
assert(e instanceof SfError);
expect(e).to.have.property('name', 'NotADevHubError');
}
});
it('no input, no default => ok', async () => {
await $$.stubConfig({});
expect(await maybeGetHub()).to.be.undefined;
});
});
});
Loading