Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into sm/project-type-conso…
Browse files Browse the repository at this point in the history
…lidation
  • Loading branch information
mshanemc committed Jun 17, 2024
2 parents 5f3321e + 883bca4 commit 8a22ca1
Show file tree
Hide file tree
Showing 15 changed files with 1,301 additions and 3,859 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/bundle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ jobs:
node scripts/build.js
- name: Post Bundling Update
run: |
node scripts/postBundlingUpdate.js
node scripts/postBundlingUpdate.js
2 changes: 1 addition & 1 deletion .github/workflows/failureNotifications.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
steps:
- name: Announce Failure
id: slack
uses: slackapi/slack-github-action@v1.24.0
uses: slackapi/slack-github-action@v1.26.0
env:
# for non-CLI-team-owned plugins, you can send this anywhere you like
SLACK_WEBHOOK_URL: ${{ secrets.CLI_ALERTS_SLACK_WEBHOOK }}
Expand Down
4,902 changes: 1,135 additions & 3,767 deletions CHANGELOG.md

Large diffs are not rendered by default.

16 changes: 8 additions & 8 deletions examples/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -370,11 +370,11 @@ brace-expansion@^2.0.1:
balanced-match "^1.0.0"

braces@~3.0.2:
version "3.0.2"
resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
version "3.0.3"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
dependencies:
fill-range "^7.0.1"
fill-range "^7.1.1"

[email protected]:
version "1.3.1"
Expand Down Expand Up @@ -762,10 +762,10 @@ figures@^3.0.0:
dependencies:
escape-string-regexp "^1.0.5"

fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
fill-range@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
dependencies:
to-regex-range "^5.0.1"

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@salesforce/core",
"version": "7.3.12-qa.1",
"version": "7.4.1",
"description": "Core libraries to interact with SFDX projects, orgs, and APIs.",
"main": "lib/index",
"types": "lib/index.d.ts",
Expand Down Expand Up @@ -83,7 +83,7 @@
"benchmark": "^2.1.4",
"chai-string": "^1.5.0",
"ts-node": "^10.9.2",
"ts-patch": "^3.1.1",
"ts-patch": "^3.2.0",
"typescript": "^5.4.5"
},
"repository": {
Expand Down
19 changes: 14 additions & 5 deletions src/lifecycleEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import { Logger } from './logger/logger';

// Data of any type can be passed to the callback. Can be cast to any type that is given in emit().
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type callback = (data: any) => Promise<void>;
export type callback = (data: any) => Promise<void>;
type ListenerMap = Map<string, callback>;
export type UniqueListenerMap = Map<string, ListenerMap>;

declare const global: {
salesforceCoreLifecycle?: Lifecycle;
Expand Down Expand Up @@ -49,7 +51,7 @@ export class Lifecycle {

private constructor(
private readonly listeners: Dictionary<callback[]> = {},
private readonly uniqueListeners: Map<string, Map<string, callback>> = new Map<string, Map<string, callback>>()
private readonly uniqueListeners: UniqueListenerMap = new Map<string, Map<string, callback>>()
) {}

/**
Expand Down Expand Up @@ -89,8 +91,11 @@ export class Lifecycle {
) {
const oldInstance = global.salesforceCoreLifecycle;
// use the newer version and transfer any listeners from the old version
// object spread keeps them from being references
global.salesforceCoreLifecycle = new Lifecycle({ ...oldInstance.listeners }, oldInstance.uniqueListeners);
// object spread and the clone fn keep them from being references
global.salesforceCoreLifecycle = new Lifecycle(
{ ...oldInstance.listeners },
cloneUniqueListeners(oldInstance.uniqueListeners)
);
// clean up any listeners on the old version
Object.keys(oldInstance.listeners).map((eventName) => {
oldInstance.removeAllListeners(eventName);
Expand Down Expand Up @@ -176,7 +181,7 @@ export class Lifecycle {
if (uniqueListenerIdentifier) {
if (!this.uniqueListeners.has(eventName)) {
// nobody is listening to the event yet
this.uniqueListeners.set(eventName, new Map<string, callback>([[uniqueListenerIdentifier, cb]]));
this.uniqueListeners.set(eventName, new Map([[uniqueListenerIdentifier, cb]]));
} else if (!this.uniqueListeners.get(eventName)?.has(uniqueListenerIdentifier)) {
// the unique listener identifier is not already registered
this.uniqueListeners.get(eventName)?.set(uniqueListenerIdentifier, cb);
Expand Down Expand Up @@ -231,3 +236,7 @@ export class Lifecycle {
}
}
}

const cloneListeners: (listeners: ListenerMap) => ListenerMap = (listeners) => new Map(Array.from(listeners.entries()));
export const cloneUniqueListeners = (uniqueListeners: UniqueListenerMap): UniqueListenerMap =>
new Map(Array.from(uniqueListeners.entries()).map(([key, value]) => [key, cloneListeners(value)]));
110 changes: 63 additions & 47 deletions src/org/scratchOrgCreate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { ConfigAggregator } from '../config/configAggregator';
import { OrgConfigProperties } from '../org/orgConfigProperties';
import { SfProject } from '../sfProject';
import { StateAggregator } from '../stateAggregator/stateAggregator';
import { SfError } from '../sfError';
import { Org } from './org';
import {
authorizeScratchOrg,
Expand Down Expand Up @@ -148,6 +149,15 @@ export const scratchOrgResume = async (jobId: string): Promise<ScratchOrgCreateR
retry: 0,
});

await setExitCodeIfError(68)(
scratchOrgAuthInfo.handleAliasAndDefaultSettings({
alias,
setDefault: setDefault ?? false,
setDefaultDevHub: false,
setTracksSource: tracksSource ?? true,
})
);

const scratchOrg = await Org.create({ aliasOrUsername: username });

const configAggregator = await ConfigAggregator.create();
Expand All @@ -160,23 +170,19 @@ export const scratchOrgResume = async (jobId: string): Promise<ScratchOrgCreateR
capitalizeRecordTypes,
});
await settingsGenerator.extract({ ...soi, ...definitionjson });
const [authInfo] = await Promise.all([
resolveUrl(scratchOrgAuthInfo),
deploySettings(
scratchOrg,
settingsGenerator,
apiVersion ??
configAggregator.getPropertyValue(OrgConfigProperties.ORG_API_VERSION) ??
(await scratchOrg.retrieveMaxApiVersion())
),
]);
const [authInfo] = await setExitCodeIfError(68)(
Promise.all([
resolveUrl(scratchOrgAuthInfo),
deploySettings(
scratchOrg,
settingsGenerator,
apiVersion ??
configAggregator.getPropertyValue(OrgConfigProperties.ORG_API_VERSION) ??
(await scratchOrg.retrieveMaxApiVersion())
),
])
);

await scratchOrgAuthInfo.handleAliasAndDefaultSettings({
alias,
setDefault: setDefault ?? false,
setDefaultDevHub: false,
setTracksSource: tracksSource ?? true,
});
cache.unset(soi.Id ?? jobId);
const authFields = authInfo.getFields();

Expand Down Expand Up @@ -287,42 +293,43 @@ export const scratchOrgCreate = async (options: ScratchOrgCreateOptions): Promis
retry: retry || 0,
});

// anything after this point (org is created and auth'd) is potentially recoverable with the resume scratch command.
await setExitCodeIfError(68)(
scratchOrgAuthInfo.handleAliasAndDefaultSettings({
...{
alias,
setDefault,
setDefaultDevHub: false,
setTracksSource: tracksSource === false ? false : true,
},
})
);

// we'll need this scratch org connection later;
const scratchOrg = await Org.create({
aliasOrUsername: soi.Username ?? soi.SignupUsername,
});
const scratchOrg = await Org.create({ aliasOrUsername: soi.Username ?? soi.SignupUsername });
const username = scratchOrg.getUsername();
logger.debug(`scratch org username ${username}`);

await emit({ stage: 'deploy settings', scratchOrgInfo: soi });

const configAggregator = await ConfigAggregator.create();
const [authInfo] = await setExitCodeIfError(68)(
Promise.all([
resolveUrl(scratchOrgAuthInfo),
deploySettings(
scratchOrg,
settingsGenerator,
apiversion ??
configAggregator.getPropertyValue(OrgConfigProperties.ORG_API_VERSION) ??
(await scratchOrg.retrieveMaxApiVersion()),
// some of our "wait" time has already been used. Calculate how much remains that we can spend on the deployment.
Duration.milliseconds(wait.milliseconds - (Date.now() - startTimestamp))
),
])
);

const [authInfo] = await Promise.all([
resolveUrl(scratchOrgAuthInfo),
deploySettings(
scratchOrg,
settingsGenerator,
apiversion ??
configAggregator.getPropertyValue(OrgConfigProperties.ORG_API_VERSION) ??
(await scratchOrg.retrieveMaxApiVersion()),
// some of our "wait" time has already been used. Calculate how much remains that we can spend on the deployment.
Duration.milliseconds(wait.milliseconds - (Date.now() - startTimestamp))
),
]);

await scratchOrgAuthInfo.handleAliasAndDefaultSettings({
...{
alias,
setDefault,
setDefaultDevHub: false,
setTracksSource: tracksSource === false ? false : true,
},
});
cache.unset(scratchOrgInfoId);
const authFields = authInfo.getFields();
await Promise.all([emit({ stage: 'done', scratchOrgInfo: soi }), cache.write(), emitPostOrgCreate(authFields)]);

return {
username,
scratchOrgInfo: soi,
Expand All @@ -345,9 +352,18 @@ const getSignupTargetLoginUrl = async (): Promise<string | undefined> => {
async function getCapitalizeRecordTypesConfig(): Promise<boolean | undefined> {
const configAgg = await ConfigAggregator.create();
const value = configAgg.getInfo('org-capitalize-record-types').value as string | undefined;

if (value !== undefined) return toBoolean(value);

// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
return value as undefined;
return value !== undefined ? toBoolean(value) : undefined;
}

/** wrap an async function, intercept error and set the given exit code */
const setExitCodeIfError =
(exitCode: number) =>
async <P>(p: Promise<P>): Promise<P> => {
try {
return await p;
} catch (e) {
const sfError = SfError.wrap(e);
sfError.exitCode = exitCode;
throw sfError;
}
};
14 changes: 8 additions & 6 deletions src/org/scratchOrgInfoApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ export const deploySettings = async (
};

/**
* Makes sure the scratch org's instanceUrl is resolvable (that is, DNS is ready)
*
* @param scratchOrgAuthInfo an AuthInfo class from the scratch org
* @returns AuthInfo
Expand All @@ -435,13 +436,14 @@ export const resolveUrl = async (scratchOrgAuthInfo: AuthInfo): Promise<AuthInfo
const logger = await Logger.child('scratchOrgInfoApi-resolveUrl');
const { instanceUrl } = scratchOrgAuthInfo.getFields();
if (!instanceUrl) {
const sfError = new SfError('Org does not have instanceUrl');
sfError.setData({
orgId: scratchOrgAuthInfo.getFields().orgId,
username: scratchOrgAuthInfo.getFields().username,
instanceUrl,
throw SfError.create({
message: 'Org does not have instanceUrl',
data: {
orgId: scratchOrgAuthInfo.getFields().orgId,
username: scratchOrgAuthInfo.getFields().username,
instanceUrl,
},
});
throw sfError;
}
logger.debug(`processScratchOrgInfoResult - resultData.instanceUrl: ${instanceUrl}`);
const options = {
Expand Down
11 changes: 5 additions & 6 deletions src/org/scratchOrgSettingsGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,12 +329,11 @@ export default class SettingsGenerator {
const failures = (Array.isArray(componentFailures) ? componentFailures : [componentFailures])
.map((failure) => `[${failure.problemType}] ${failure.fullName} : ${failure.problem} `)
.join('\n');
const error = new SfError(
`A scratch org was created with username ${username}, but the settings failed to deploy due to: \n${failures}`,
'ProblemDeployingSettings'
);
error.setData(result);
throw error;
throw SfError.create({
message: `A scratch org was created with username ${username}, but the settings failed to deploy due to: \n${failures}`,
name: 'ProblemDeployingSettings',
data: { ...result, username },
});
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/sfProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,9 @@ export class SfProject {
*/
public getDefaultPackage(): NamedPackageDir {
if (!this.hasPackages()) {
throw new SfError('The sfdx-project.json does not have any packageDirectories defined.');
throw new SfError('The sfdx-project.json does not have any packageDirectories defined.', 'NoPackageDirectories', [
`Check ${this.getPath()} for packageDirectories.`,
]);
}
const defaultPackage = this.findPackage((packageDir) => packageDir.default === true);
return defaultPackage ?? this.getPackageDirectories()[0];
Expand Down
2 changes: 2 additions & 0 deletions src/testSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,8 @@ export const restoreContext = (testContext: TestContext): void => {
testContext.configStubs = {};
// Give each test run a clean StateAggregator
StateAggregator.clearInstance();
// @ts-expect-error accessing a private property
SfProject.instances.clear();
// Allow each test to have their own config aggregator
// @ts-ignore clear for testing.
delete ConfigAggregator.instance;
Expand Down
28 changes: 27 additions & 1 deletion test/unit/lifecycleEventsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import { Duration, sleep } from '@salesforce/kit/lib/duration';
import { spyMethod } from '@salesforce/ts-sinon';
import * as chai from 'chai';
import { Lifecycle } from '../../src/lifecycleEvents';
import { Lifecycle, callback, cloneUniqueListeners } from '../../src/lifecycleEvents';
import { TestContext } from '../../src/testSetup';
import { Logger } from '../../src/logger/logger';

Expand Down Expand Up @@ -257,3 +257,29 @@ describe('lifecycleEvents', () => {
lifecycle2.removeAllListeners('test7');
});
});

describe('listener map cloning', () => {
const cb = (): Promise<void> => Promise.resolve();
it('clones map, breaking event name reference', () => {
const map1 = new Map<string, Map<string, callback>>();
map1.set('evt', new Map([['uniqueId', cb]]));

const map2 = cloneUniqueListeners(map1);
chai.expect(map2).to.deep.equal(map1);
map1.delete('evt');
chai.expect(map1.has('evt')).to.be.false;
chai.expect(map2.has('evt')).to.be.true;
});
it('clones map, breaking uniqueId reference', () => {
const map1 = new Map<string, Map<string, callback>>();
map1.set('evt', new Map([['uniqueId', cb]]));

const map2 = cloneUniqueListeners(map1);
chai.expect(map2).to.deep.equal(map1);
map2.get('evt')?.set('uniqueId2', cb);
chai.expect(map1.has('evt')).to.be.true;
chai.expect(map2.has('evt')).to.be.true;
chai.expect(map1.get('evt')?.has('uniqueId2')).to.be.false;
chai.expect(map2.get('evt')?.has('uniqueId2')).to.be.true;
});
});
Loading

2 comments on commit 8a22ca1

@svc-cli-bot
Copy link
Contributor

Choose a reason for hiding this comment

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

Logger Benchmarks - ubuntu-latest

Benchmark suite Current: 8a22ca1 Previous: 6c7f7f5 Ratio
Child logger creation 474988 ops/sec (±1.33%) 454710 ops/sec (±2.07%) 0.96
Logging a string on root logger 787698 ops/sec (±11.99%) 794718 ops/sec (±6.50%) 1.01
Logging an object on root logger 616533 ops/sec (±6.85%) 580886 ops/sec (±7.23%) 0.94
Logging an object with a message on root logger 5320 ops/sec (±215.03%) 8493 ops/sec (±203.68%) 1.60
Logging an object with a redacted prop on root logger 457319 ops/sec (±11.79%) 402892 ops/sec (±15.86%) 0.88
Logging a nested 3-level object on root logger 376829 ops/sec (±9.11%) 365370 ops/sec (±7.33%) 0.97

This comment was automatically generated by workflow using github-action-benchmark.

@svc-cli-bot
Copy link
Contributor

Choose a reason for hiding this comment

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

Logger Benchmarks - windows-latest

Benchmark suite Current: 8a22ca1 Previous: 6c7f7f5 Ratio
Child logger creation 349014 ops/sec (±0.37%) 327550 ops/sec (±0.70%) 0.94
Logging a string on root logger 793860 ops/sec (±7.25%) 738739 ops/sec (±5.82%) 0.93
Logging an object on root logger 628105 ops/sec (±6.36%) 579951 ops/sec (±5.64%) 0.92
Logging an object with a message on root logger 4511 ops/sec (±210.16%) 8942 ops/sec (±190.82%) 1.98
Logging an object with a redacted prop on root logger 454907 ops/sec (±11.59%) 389863 ops/sec (±12.21%) 0.86
Logging a nested 3-level object on root logger 327353 ops/sec (±5.60%) 299001 ops/sec (±6.86%) 0.91

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.