Skip to content

Commit

Permalink
Merge pull request #1086 from forcedotcom/sm/scratch-org-partial-comp…
Browse files Browse the repository at this point in the history
…lete

fix: scratch creation partial success
  • Loading branch information
shetzel authored Jun 14, 2024
2 parents b51f2a6 + 6c7f7f5 commit d5f2555
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 66 deletions.
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
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

0 comments on commit d5f2555

Please sign in to comment.