UNPKG

@salesforce/core

Version:

Core libraries to interact with SFDX projects, orgs, and APIs.

240 lines 13.6 kB
"use strict"; 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