@salesforce/core
Version:
Core libraries to interact with SFDX projects, orgs, and APIs.
240 lines • 13.6 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.getScratchOrgInfoPayload = exports.generateScratchOrgInfo = exports.getAncestorIds = void 0;
/*
* Copyright (c) 2021, 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
*/
const node_fs_1 = require("node:fs");
const kit_1 = require("@salesforce/kit");
const ts_types_1 = require("@salesforce/ts-types");
const sfProject_1 = require("../sfProject");
const webOAuthServer_1 = require("../webOAuthServer");
const messages_1 = require("../messages");
const sfError_1 = require("../sfError");
const scratchOrgFeatureDeprecation_1 = require("./scratchOrgFeatureDeprecation");
const authInfo_1 = require("./authInfo");
;
const messages = new messages_1.Messages('@salesforce/core', 'scratchOrgInfoGenerator', new Map([["Package2AncestorsIdsKeyNotSupportedError", "The package2AncestorIds key is no longer supported in a scratch org definition. Ancestors defined in sfdx-project.json will be included in the scratch org.\","], ["InvalidAncestorVersionFormatError", "The ancestor versionNumber must be in the format major.minor.patch but the value found is %s\","], ["NoMatchingAncestorError", "The ancestor for ancestorVersion %s can't be found. Package ID %s.\","], ["NoMatchingAncestorIdError", "The ancestor for ancestorId [%s] can't be found. Package ID %s.\""], ["AncestorNotReleasedError", "The ancestor package version [%s] specified in the sfdx-project.json file may exist hasn\u2019t been promoted and released. Release the ancestor package version before specifying it as the ancestor in a new package or patch version.\","], ["AncestorIdVersionMismatchError", "The ancestorVersion in sfdx-project.json is not the version expected for the ancestorId you supplied. ancestorVersion %s. ancestorID %s.\""], ["unsupportedSnapshotOrgCreateOptions", "Org snapshots don\u2019t support one or more options you specified: %s"]]));
const SNAPSHOT_UNSUPPORTED_OPTIONS = [
'features',
'orgPreferences',
'edition',
'sourceOrg',
'settingsPath',
'releaseVersion',
];
// A validator function to ensure any options parameters entered by the user adhere
// to a allowlist of valid option settings. Because org:create allows options to be
// input either key=value pairs or within the definition file, this validator is
// executed within the ctor and also after parsing/normalization of the definition file.
const optionsValidator = (key, scratchOrgInfoPayload) => {
if (key.toLowerCase() === 'durationdays') {
throw new sfError_1.SfError('unrecognizedScratchOrgOption', 'durationDays');
}
if (key.toLowerCase() === 'snapshot') {
const foundInvalidFields = SNAPSHOT_UNSUPPORTED_OPTIONS.filter((invalidField) => invalidField in scratchOrgInfoPayload);
if (foundInvalidFields.length > 0) {
throw new sfError_1.SfError(messages.getMessage('unsupportedSnapshotOrgCreateOptions', [foundInvalidFields.join(', ')]), 'orgSnapshot');
}
}
};
/**
* Generates the package2AncestorIds scratch org property
*
* @param scratchOrgInfo - the scratchOrgInfo passed in by the user
* @param projectJson - sfProjectJson
* @param hubOrg - the hub org, in case we need to do queries
*/
const getAncestorIds = async (scratchOrgInfo, projectJson, hubOrg) => {
if (Reflect.has(scratchOrgInfo, 'package2AncestorIds')) {
throw new sfError_1.SfError(messages.getMessage('Package2AncestorsIdsKeyNotSupportedError'), 'DeprecationError');
}
const packagesWithAncestors = (await projectJson.getPackageDirectories())
.filter(sfProject_1.isPackagingDirectory)
// check that the package has any ancestor types (id or version)
.filter((packageDir) => packageDir.ancestorId ?? packageDir.ancestorVersion);
if (packagesWithAncestors.length === 0) {
return '';
}
const ancestorIds = await Promise.all(packagesWithAncestors.map(async (packageDir) => {
// ancestorID can be 05i, or 04t, alias; OR "ancestorVersion": "4.6.0.1"
// according to docs, 05i is not ok: https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev2gp_config_file.htm
// package can be an ID, but not according to docs
const packageAliases = projectJson.get('packageAliases');
const packageId = packageAliases[(0, ts_types_1.ensureString)(packageDir.package)] ?? packageDir.package;
// Handle HIGHEST and NONE in ancestor(Version|Id).
// Precedence chain: NONE -> HIGHEST -> ancestorVersion & ancestoryId
if (packageDir.ancestorVersion === 'NONE' || packageDir.ancestorId === 'NONE') {
return '';
}
else if (packageDir.ancestorVersion === 'HIGHEST' || packageDir.ancestorId === 'HIGHEST') {
const query = 'SELECT Id FROM Package2Version ' +
`WHERE Package2Id = '${packageId}' AND IsReleased = True AND IsDeprecated = False AND PatchVersion = 0 ` +
'ORDER BY MajorVersion Desc, MinorVersion Desc, PatchVersion Desc, BuildNumber Desc LIMIT 1';
try {
return (await hubOrg.getConnection().singleRecordQuery(query, { tooling: true })).Id;
}
catch (err) {
if (packageDir.ancestorVersion === 'HIGHEST') {
throw new sfError_1.SfError(messages.getMessage('NoMatchingAncestorError', [packageDir.ancestorVersion, packageDir.package]), 'NoMatchingAncestorError', [messages.getMessage('AncestorNotReleasedError', [packageDir.ancestorVersion])]);
}
else {
throw new sfError_1.SfError(messages.getMessage('NoMatchingAncestorIdError', [packageDir.ancestorId, packageDir.package]), 'NoMatchingAncestorIdError', [messages.getMessage('AncestorNotReleasedError', [packageDir.ancestorId])]);
}
}
}
if (packageDir.ancestorVersion) {
if (!/^[0-9]+.[0-9]+.[0-9]+(.[0-9]+)?$/.test(packageDir.ancestorVersion)) {
throw messages.createError('InvalidAncestorVersionFormatError', [packageDir.ancestorVersion]);
}
const [major, minor, patch] = packageDir.ancestorVersion.split('.');
let releasedAncestor;
try {
releasedAncestor = await hubOrg
.getConnection()
.singleRecordQuery(`SELECT Id, IsReleased FROM Package2Version WHERE Package2Id = '${packageId}' AND MajorVersion = ${major} AND MinorVersion = ${minor} AND PatchVersion = ${patch} and IsReleased = true`, { tooling: true });
}
catch (err) {
throw new sfError_1.SfError(messages.getMessage('NoMatchingAncestorError', [packageDir.ancestorVersion, packageDir.package]), 'NoMatchingAncestorError', [messages.getMessage('AncestorNotReleasedError', [packageDir.ancestorVersion])]);
}
if (packageDir.ancestorId && packageDir.ancestorId !== releasedAncestor.Id) {
throw messages.createError('AncestorIdVersionMismatchError', [
packageDir.ancestorVersion,
packageDir.ancestorId,
]);
}
return releasedAncestor.Id;
}
if (packageDir?.ancestorId?.startsWith('05i')) {
// if it's already a 05i return it, otherwise query for it
return packageDir.ancestorId;
}
if (packageDir?.ancestorId?.startsWith('04t')) {
// query for the Id
return (await hubOrg
.getConnection()
.singleRecordQuery(`SELECT Id FROM Package2Version WHERE SubscriberPackageVersionId = '${packageDir.ancestorId}'`, { tooling: true })).Id;
}
// ancestorID can be an alias; get it from projectJson
if (packageDir.ancestorId && packageAliases?.[packageDir.ancestorId]) {
return packageAliases[packageDir.ancestorId];
}
throw new sfError_1.SfError(`Invalid ancestorId ${packageDir.ancestorId}`, 'InvalidAncestorId');
}));
// strip out '' due to NONE
return Array.from(new Set(ancestorIds.filter((id) => id !== ''))).join(';');
};
exports.getAncestorIds = getAncestorIds;
/**
* Takes in a scratchOrgInfo and fills in the missing fields
*
* @param hubOrg the environment hub org
* @param scratchOrgInfoPayload - the scratchOrgInfo passed in by the user
* @param nonamespace create the scratch org with no namespace
* @param ignoreAncestorIds true if the sfdx-project.json ancestorId keys should be ignored
*/
const generateScratchOrgInfo = async ({ hubOrg, scratchOrgInfoPayload, nonamespace, ignoreAncestorIds, }) => {
let sfProject;
try {
sfProject = await sfProject_1.SfProjectJson.create({});
}
catch (e) {
// project is not required
}
const { namespace: originalNamespace, ...payload } = scratchOrgInfoPayload;
const namespace = originalNamespace ?? sfProject?.get('namespace');
return {
...payload,
orgName: scratchOrgInfoPayload.orgName ?? 'Company',
// we already have the info, and want to get rid of configApi, so this doesn't use that
connectedAppCallbackUrl: `http://localhost:${await webOAuthServer_1.WebOAuthServer.determineOauthPort()}/OauthRedirect`,
...(!nonamespace && namespace ? { namespace } : {}),
// Use the Hub org's client ID value, if one wasn't provided to us, or the default
connectedAppConsumerKey: scratchOrgInfoPayload.connectedAppConsumerKey ??
hubOrg.getConnection().getAuthInfoFields().clientId ??
authInfo_1.DEFAULT_CONNECTED_APP_INFO.clientId,
package2AncestorIds: !ignoreAncestorIds && sfProject?.hasPackages()
? await (0, exports.getAncestorIds)(scratchOrgInfoPayload, sfProject, hubOrg)
: '',
};
};
exports.generateScratchOrgInfo = generateScratchOrgInfo;
/**
* Returns a valid signup json
*
* definitionjson org definition in JSON format
* definitionfile path to an org definition file
* connectedAppConsumerKey The connected app consumer key. May be null for JWT OAuth flow.
* durationdays duration of the scratch org (in days) (default:1, min:1, max:30)
* nonamespace create the scratch org with no namespace
* noancestors do not include second-generation package ancestors in the scratch org
* orgConfig overrides definitionjson
*
* @returns scratchOrgInfoPayload: ScratchOrgInfoPayload;
ignoreAncestorIds: boolean;
warnings: string[];
*/
const getScratchOrgInfoPayload = async (options) => {
let warnings = [];
// orgConfig input overrides definitionjson (-j option; hidden/deprecated) overrides definitionfile (-f option)
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const scratchOrgInfoPayload = {
...(options.definitionfile ? await parseDefinitionFile(options.definitionfile) : {}),
...(options.definitionjson ? JSON.parse(options.definitionjson) : {}),
...(options.orgConfig ?? {}),
};
// scratchOrgInfoPayload must be heads down camelcase.
Object.keys(scratchOrgInfoPayload).forEach((key) => {
if (key[0].toUpperCase() === key[0]) {
throw new sfError_1.SfError('InvalidJsonCasing', key);
}
});
// Now run the fully resolved user input against the validator
Object.keys(scratchOrgInfoPayload).forEach((key) => {
optionsValidator(key, scratchOrgInfoPayload);
});
if (options.connectedAppConsumerKey) {
scratchOrgInfoPayload.connectedAppConsumerKey = options.connectedAppConsumerKey;
}
scratchOrgInfoPayload.durationDays = options.durationDays;
// Throw warnings for deprecated scratch org features.
const scratchOrgFeatureDeprecation = new scratchOrgFeatureDeprecation_1.ScratchOrgFeatureDeprecation();
// convert various supported array and string formats to a semi-colon-delimited string
if (scratchOrgInfoPayload.features) {
if (typeof scratchOrgInfoPayload.features === 'string') {
scratchOrgInfoPayload.features = scratchOrgInfoPayload.features.split(/[;,]/);
}
warnings = scratchOrgFeatureDeprecation.getFeatureWarnings(scratchOrgInfoPayload.features);
scratchOrgInfoPayload.features = scratchOrgInfoPayload.features.map((feature) => feature.trim());
scratchOrgInfoPayload.features = scratchOrgFeatureDeprecation
.filterDeprecatedFeatures(scratchOrgInfoPayload.features)
.join(';');
}
return {
scratchOrgInfoPayload,
// Ignore ancestor ids only when 'nonamespace' or 'noancestors' options are specified
ignoreAncestorIds: options.nonamespace ?? options.noancestors ?? false,
warnings,
};
};
exports.getScratchOrgInfoPayload = getScratchOrgInfoPayload;
const parseDefinitionFile = async (definitionFile) => {
try {
const fileData = await node_fs_1.promises.readFile(definitionFile, 'utf8');
const defFileContents = (0, kit_1.parseJson)(fileData);
// remove key '$schema' from the definition file
delete defFileContents['$schema'];
return defFileContents;
}
catch (err) {
const error = err;
if (error.name === 'JsonParseError') {
throw new sfError_1.SfError(`An error occurred parsing ${definitionFile}`);
}
throw sfError_1.SfError.wrap(error);
}
};
//# sourceMappingURL=scratchOrgInfoGenerator.js.map
;