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

fix: scratch creation partial success #1086

Merged
merged 5 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
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 = e instanceof SfError ? e : 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
16 changes: 9 additions & 7 deletions test/unit/org/scratchOrgSettingsGeneratorTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ function createStubs() {
id: '1',
})
);
sandbox.stub(ZipWriter.prototype, 'buffer').get(() => 'mybuffer');
sandbox.stub(ZipWriter.prototype, 'buffer').get(() => 'myBuffer');
getUsernameStub = sandbox.stub(scratchOrg, 'getUsername').returns(adminTestData.username);
}

Expand Down Expand Up @@ -266,7 +266,7 @@ describe('scratchOrgSettingsGenerator', () => {
id: '1',
})
);
sandbox.stub(ZipWriter.prototype, 'buffer').get(() => 'mybuffer');
sandbox.stub(ZipWriter.prototype, 'buffer').get(() => 'myBuffer');
getUsernameStub = sandbox.stub(scratchOrg, 'getUsername').returns(adminTestData.username);
getConnectionStub = fakeConnection(sandbox, scratchOrg, deployId, 'SucceededPartial');
});
Expand Down Expand Up @@ -307,6 +307,7 @@ describe('scratchOrgSettingsGenerator', () => {
problem: 'settings/True.settings is not a valid metadata object. Check the name and casing of the file',
},
},
username: scratchOrg.getUsername(),
});
}
});
Expand All @@ -328,7 +329,7 @@ describe('scratchOrgSettingsGenerator', () => {
id: '1',
})
);
sandbox.stub(ZipWriter.prototype, 'buffer').get(() => 'mybuffer');
sandbox.stub(ZipWriter.prototype, 'buffer').get(() => 'myBuffer');
getUsernameStub = sandbox.stub(scratchOrg, 'getUsername').returns(adminTestData.username);
getConnectionStub = fakeConnection(sandbox, scratchOrg, deployId, 'Failed');
});
Expand Down Expand Up @@ -369,6 +370,7 @@ describe('scratchOrgSettingsGenerator', () => {
problem: 'settings/True.settings is not a valid metadata object. Check the name and casing of the file',
},
},
username: scratchOrg.getUsername(),
});
}
});
Expand All @@ -390,7 +392,7 @@ describe('scratchOrgSettingsGenerator', () => {
id: '1',
})
);
sandbox.stub(ZipWriter.prototype, 'buffer').get(() => 'mybuffer');
sandbox.stub(ZipWriter.prototype, 'buffer').get(() => 'myBuffer');
getUsernameStub = sandbox.stub(scratchOrg, 'getUsername').returns(adminTestData.username);
getConnectionStub = fakeConnection(sandbox, scratchOrg, deployId, ['InProgress', 'Succeeded']);
});
Expand All @@ -399,7 +401,7 @@ describe('scratchOrgSettingsGenerator', () => {
sandbox.restore();
});

it('tries to deploy the settings to the org pools untill succeded', async () => {
it('tries to deploy the settings to the org pools until succeeded', async () => {
const scratchDef = {
...TEMPLATE_SCRATCH_ORG_INFO,
settings: {
Expand Down Expand Up @@ -495,7 +497,7 @@ describe('scratchOrgSettingsGenerator', () => {
id: '1',
})
);
sandbox.stub(ZipWriter.prototype, 'buffer').get(() => 'mybuffer');
sandbox.stub(ZipWriter.prototype, 'buffer').get(() => 'myBuffer');
getUsernameStub = sandbox.stub(scratchOrg, 'getUsername').returns(adminTestData.username);
getConnectionStub = fakeConnection(sandbox, scratchOrg, deployId, 'InProgress');
});
Expand All @@ -505,7 +507,7 @@ describe('scratchOrgSettingsGenerator', () => {
sandbox.restore();
});

it('tries to deploy the settings to the org pools untill timeouts', async () => {
it('tries to deploy the settings to the org pools until timeouts', async () => {
const timeout = 10 * 60 * 1000; // 10 minutes
const frequency = 1000;
const settings = new SettingsGenerator();
Expand Down
Loading