@salesforce/core
Version:
Core libraries to interact with SFDX projects, orgs, and APIs.
736 lines • 36 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.isNamedPackagingDirectory = exports.isPackagingDirectory = exports.SfProject = exports.SfProjectJson = void 0;
/*
* Copyright (c) 2020, 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_path_1 = require("node:path");
const fs = __importStar(require("node:fs"));
const kit_1 = require("@salesforce/kit");
const ts_types_1 = require("@salesforce/ts-types");
const sfdcUrl_1 = require("./util/sfdcUrl");
const configAggregator_1 = require("./config/configAggregator");
const configFile_1 = require("./config/configFile");
const validator_1 = require("./schema/validator");
const internal_1 = require("./util/internal");
const sfError_1 = require("./sfError");
const messages_1 = require("./messages");
const findUppercaseKeys_1 = require("./util/findUppercaseKeys");
;
const messages = new messages_1.Messages('@salesforce/core', 'config', new Map([["unknownConfigKey", "Unknown config name: %s."], ["deprecatedConfigKey", "Deprecated config name: %s. Please use %s instead."], ["invalidWrite", "The writeSync method is not allowed on SfdxConfig. Use the async write method instead."], ["invalidConfigValue", "Invalid config value: %s."], ["invalidInstanceUrl", "Specify a valid Salesforce instance URL."], ["invalidApiVersion", "Specify a valid Salesforce API version, for example, 42.0."], ["invalidCustomOrgMetadataTemplates", "Specify a valid repository URL or directory for the custom org metadata templates."], ["invalidIsvDebuggerSid", "Specify a valid Debugger SID."], ["invalidIsvDebuggerUrl", "Specify a valid Debugger URL."], ["invalidNumberConfigValue", "Specify a valid positive integer, for example, 150000."], ["invalidBooleanConfigValue", "The config value can only be set to true or false."], ["invalidProjectWorkspace", "This directory does not contain a valid Salesforce DX project."], ["schemaValidationError", "The config file \"%s\" is not schema valid.\nDue to: %s"], ["schemaValidationError.actions", ["Fix the invalid entries at %s."]], ["missingDefaultPath", "In sfdx-project.json, be sure to specify which package directory (path) is the default. Example: `[{ \"path\": \"packageDirectory1\", \"default\": true }, { \"path\": \"packageDirectory2\" }]`"], ["missingPackageDirectory", "The path \"%s\", specified in sfdx-project.json, does not exist. Be sure this directory is included in your project root."], ["invalidPackageDirectory", "The path \"%s\", specified in sfdx-project.json, must be indicated as a relative path to the project root."], ["multipleDefaultPaths", "In sfdx-project.json, indicate only one package directory (path) as the default."], ["singleNonDefaultPackage", "The sfdx-project.json file must include one, and only one, default package directory (path). Because your sfdx-project.json file contains only one package directory, it must be the default. Remove the `\"default\": false` key and try again."], ["target-org", "Username or alias of the org that all commands run against by default. (sf only)"], ["target-dev-hub", "Username or alias of your default Dev Hub org. (sf only)"], ["defaultUsername", "Username or alias of the org that all commands run against by default. (sfdx only)"], ["defaultDevHubUsername", "Username or alias of your default Dev Hub org. (sfdx only)"], ["isvDebuggerSid", "ISV debugger SID (sfdx only)"], ["isvDebuggerUrl", "ISV debugger URL (sfdx only)"], ["org-isv-debugger-sid", "ISV debugger SID."], ["org-isv-debugger-url", "ISV debugger URL."], ["apiVersion", "API version of your project. Default: API version of your Dev Hub org. (sfdx only)"], ["org-api-version", "API version of your project. Default: API version of your Dev Hub org."], ["disableTelemetry", "Disables the collection of usage and user environment information, etc. Default: false. (sfdx only)"], ["disable-telemetry", "Disables the collection of usage and user environment information, etc. Default: false."], ["maxQueryLimit", "Maximum number of Salesforce records returned by a CLI command. Default: 10,000. (sfdx only)"], ["org-max-query-limit", "Maximum number of Salesforce records returned by a CLI command. Default: 10,000."], ["restDeploy", "Whether deployments use the Metadata REST API (true) or SOAP API (false, default value). (sfdx only)"], ["instanceUrl", "URL of the Salesforce instance hosting your org. Default: https://login.salesforce.com. (sfdx only)"], ["org-instance-url", "URL of the Salesforce instance hosting your org. Default: https://login.salesforce.com."], ["customOrgMetadataTemplates", "A valid repository URL or directory for the custom org metadata templates."], ["org-custom-metadata-templates", "A valid repository URL or directory for the custom org metadata templates."], ["org-capitalize-record-types", "Whether record types are capitalized on scratch org creation."], ["invalidId", "The given id %s is not a valid 15 or 18 character Salesforce ID."]]));
/**
* The sfdx-project.json config object. This file determines if a folder is a valid sfdx project.
*
* *Note:* Any non-standard (not owned by Salesforce) properties stored in sfdx-project.json should
* be in a top level property that represents your project.
* Plugins should store their configuration @see SfProject.getPluginConfiguration and @see SfProject.setPluginConfiguration
*
* @example reading a standard property
* ```
* const project = await SfProject.resolve();
* const projectJson = await project.resolveProjectConfig();
* const namespace = projectJson.get('namespace');
* ```
*
* ```
* @example writing
* const project = await SfProject.resolve();
* const projectJson = await project.resolveProjectConfig();
* projectJson.set('namespace', 'new');
* await projectJson.write();
* ```
*
* **See** [force:project:create](https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_ws_create_new.htm)
*/
class SfProjectJson extends configFile_1.ConfigFile {
/** json properties that are uppercase, or allow uppercase keys inside them */
static BLOCKLIST = ['packageAliases', 'plugins'];
static getFileName() {
return internal_1.SFDX_PROJECT_JSON;
}
static getDefaultOptions(isGlobal = false) {
const options = configFile_1.ConfigFile.getDefaultOptions(isGlobal, SfProjectJson.getFileName());
options.isState = false;
return options;
}
async read() {
const contents = await super.read();
this.validateKeys();
return contents;
}
/** force a reread of the project contents if you know they may have been modified */
refreshSync() {
super.readSync(false, true);
return this;
}
readSync() {
const contents = super.readSync();
this.validateKeys();
return contents;
}
async write() {
this.validateKeys();
return super.write();
}
writeSync() {
this.validateKeys();
return super.writeSync();
}
// eslint-disable-next-line class-methods-use-this
getDefaultOptions(options) {
return { ...{ isState: false }, ...(options ?? {}) };
}
/**
* Validates sfdx-project.json against the schema.
*
* Set the `SFDX_PROJECT_JSON_VALIDATION` environment variable to `true` to throw an error when schema validation fails.
* A warning is logged by default when the file is invalid.
*
* ***See*** [sfdx-project.schema.json] ((https://github.com/forcedotcom/schemas/blob/main/sfdx-project.schema.json)
*/
async schemaValidate() {
if (!this.hasRead) {
// read calls back into this method after necessarily setting this.hasRead=true
await this.read();
}
try {
const projectJsonSchemaPath = require.resolve('@salesforce/schemas/sfdx-project.schema.json');
const validator = new validator_1.SchemaValidator(this.logger, projectJsonSchemaPath);
await validator.validate(this.getContents());
}
catch (err) {
const error = err;
// Don't throw errors if the global isn't valid, but still warn the user.
if (kit_1.env.getBoolean('SFDX_PROJECT_JSON_VALIDATION', false) && !this.options.isGlobal) {
throw messages.createError('schemaValidationError', [this.getPath(), error.message], [this.getPath()], error);
}
else {
this.logger.warn(messages.getMessage('schemaValidationError', [this.getPath(), error.message]));
}
}
}
/**
* Returns the `packageDirectories` within sfdx-project.json, first reading
* and validating the file if necessary.
*/
// eslint-disable-next-line @typescript-eslint/require-await
async getPackageDirectories() {
return this.getPackageDirectoriesSync();
}
/**
* Validates sfdx-project.json against the schema.
*
* Set the `SFDX_PROJECT_JSON_VALIDATION` environment variable to `true` to throw an error when schema validation fails.
* A warning is logged by default when the file is invalid.
*
* ***See*** [sfdx-project.schema.json] ((https://github.com/forcedotcom/schemas/blob/main/sfdx-project.schema.json)
*/
schemaValidateSync() {
if (!this.hasRead) {
// read calls back into this method after necessarily setting this.hasRead=true
this.readSync();
}
try {
const projectJsonSchemaPath = require.resolve('@salesforce/schemas/sfdx-project.schema.json');
const validator = new validator_1.SchemaValidator(this.logger, projectJsonSchemaPath);
validator.validateSync(this.getContents());
}
catch (err) {
const error = err;
// Don't throw errors if the global isn't valid, but still warn the user.
if (kit_1.env.getBoolean('SFDX_PROJECT_JSON_VALIDATION', false) && !this.options.isGlobal) {
throw messages.createError('schemaValidationError', [this.getPath(), error.message], [this.getPath()], error);
}
else {
this.logger.warn(messages.getMessage('schemaValidationError', [this.getPath(), error.message]));
}
}
}
/**
* Returns a read-only list of `packageDirectories` within sfdx-project.json, first reading
* and validating the file if necessary. i.e. modifying this array will not affect the
* sfdx-project.json file.
*/
getPackageDirectoriesSync() {
const contents = this.getContents();
// This has to be done on the fly so it won't be written back to the file
// This is a fast operation so no need to cache it so it stays immutable.
const packageDirs = (contents.packageDirectories || []).map((packageDir) => {
if ((0, node_path_1.isAbsolute)(packageDir.path)) {
throw messages.createError('invalidPackageDirectory', [packageDir.path]);
}
const regex = node_path_1.sep === '/' ? /\\/g : /\//g;
// Change packageDir paths to have path separators that match the OS
const path = packageDir.path.replace(regex, node_path_1.sep);
// Normalize and remove any ending path separators
const name = (0, node_path_1.normalize)(path).replace(new RegExp(`\\${node_path_1.sep}$`), '');
// Always end in a path sep for standardization on folder paths
const fullPath = `${(0, node_path_1.dirname)(this.getPath())}${node_path_1.sep}${name}${node_path_1.sep}`;
if (!this.doesPackageExist(fullPath)) {
throw messages.createError('missingPackageDirectory', [packageDir.path]);
}
return Object.assign({}, packageDir, { name, path, fullPath });
});
// If we only have one package entry, it must be the default even if not explicitly labelled
if (packageDirs.length === 1) {
if (packageDirs[0].default === false) {
// we have one package but it is explicitly labelled as default=false
throw messages.createError('singleNonDefaultPackage');
}
// add default=true to the package
packageDirs[0].default = true;
}
const defaultDirs = packageDirs.filter((packageDir) => packageDir.default);
// Don't throw about a missing default path if we are in the global file.
// Package directories are not really meant to be set at the global level.
if (defaultDirs.length === 0 && !this.isGlobal()) {
throw messages.createError('missingDefaultPath');
}
else if (defaultDirs.length > 1) {
throw messages.createError('multipleDefaultPaths');
}
return packageDirs;
}
/**
* Returns a read-only list of `packageDirectories` within sfdx-project.json, first reading
* and validating the file if necessary. i.e. modifying this array will not affect the
* sfdx-project.json file.
*
* There can be multiple packages in packageDirectories that point to the same directory.
* This method only returns one packageDirectory entry per unique directory path. This is
* useful when doing source operations based on directories but probably not as useful
* for packaging operations that want to do something for each package entry.
*/
getUniquePackageDirectories() {
const visited = new Set();
const uniqueValues = [];
// Keep original order defined in sfdx-project.json
this.getPackageDirectoriesSync().forEach((packageDir) => {
if (!visited.has(packageDir.name)) {
visited.add(packageDir.name);
uniqueValues.push(packageDir);
}
});
return uniqueValues;
}
/**
* Get a list of the unique package names from within sfdx-project.json. Use {@link SfProject.getUniquePackageDirectories}
* for data other than the names.
*/
getUniquePackageNames() {
return this.getUniquePackageDirectories().map((pkgDir) => pkgDir.name);
}
/**
* Has package directories defined in the project.
*/
hasPackages() {
return this.getContents()?.packageDirectories?.length > 0;
}
/**
* Has multiple package directories (MPD) defined in the project.
*/
hasMultiplePackages() {
return this.getContents()?.packageDirectories?.length > 1;
}
/**
* Has at least one package alias defined in the project.
*/
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type, @typescript-eslint/require-await
async hasPackageAliases() {
return Object.keys(this.getContents().packageAliases ?? {}).length > 0;
}
/**
* Get package aliases defined in the project.
*/
getPackageAliases() {
return this.getContents().packageAliases;
}
/**
* Add a package alias to the project.
* If the alias already exists, it will be overwritten.
*
* @param alias
* @param id
*/
addPackageAlias(alias, id) {
// TODO: validate id (e.g. 04t, 0Ho)
if (!/^.{15,18}$/.test(id)) {
throw messages.createError('invalidId', [id]);
}
const newAliases = { ...(this.getContents().packageAliases ?? {}), [alias]: id };
this.contents.set('packageAliases', newAliases);
}
/**
* Add a package directory to the project.
* If the package directory already exists, the new directory
* properties will be merged with the existing properties.
*
* @param packageDir
*/
addPackageDirectory(packageDir) {
const dirIndex = this.getContents().packageDirectories.findIndex(findPackageDir(packageDir));
// merge new package dir with existing entry, if present
const packageDirEntry = Object.assign({}, dirIndex > -1 ? this.getContents().packageDirectories[dirIndex] : packageDir, packageDir);
const modifiedPackagesDirs = dirIndex > -1
? // replace the matching entry with the new entry
this.getContents().packageDirectories.map((pd, i) => (i === dirIndex ? packageDir : pd))
: // add the new entry to the end of the list
[...(this.getContents()?.packageDirectories ?? []), packageDirEntry];
this.set('packageDirectories', modifiedPackagesDirs);
}
// keep it because testSetup stubs it!
// eslint-disable-next-line class-methods-use-this
doesPackageExist(packagePath) {
return fs.existsSync(packagePath);
}
validateKeys() {
(0, findUppercaseKeys_1.ensureNoUppercaseKeys)(this.getPath())(SfProjectJson.BLOCKLIST)(this.toObject());
}
}
exports.SfProjectJson = SfProjectJson;
/**
* Represents an SFDX project directory. This directory contains a {@link SfProjectJson} config file as well as
* a hidden .sfdx folder that contains all the other local project config files.
*
* ```
* const project = await SfProject.resolve();
* const projectJson = await project.resolveProjectConfig();
* console.log(projectJson.sfdcLoginUrl);
* ```
*/
class SfProject {
path;
// Cache of SfProject instances per path.
static instances = new Map();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
projectConfig;
// Dynamically referenced in retrieveSfProjectJson
sfProjectJson;
sfProjectJsonGlobal;
packageDirectories;
activePackage;
packageAliases;
/**
* Do not directly construct instances of this class -- use {@link SfProject.resolve} instead.
*
* @ignore
*/
constructor(path) {
this.path = path;
}
/**
* Clear the cache to force reading from disk.
*
* *NOTE: Only call this method if you must and you know what you are doing.*
*/
static clearInstances() {
SfProject.instances.clear();
}
/**
* Get a Project from a given path or from the working directory.
*
* @param path The path of the project.
*
* **Throws** *{@link SfError}{ name: 'InvalidProjectWorkspaceError' }* If the current folder is not located in a workspace.
*/
static async resolve(path) {
const resolvedPath = await this.resolveProjectPath(path ?? process.cwd());
return this.getMemoizedInstance(resolvedPath);
}
/**
* Get a Project from a given path or from the working directory.
*
* @param path The path of the project.
*
* **Throws** *{@link SfError}{ name: 'InvalidProjectWorkspaceError' }* If the current folder is not located in a workspace.
*/
static getInstance(path) {
// Store instance based on the path of the actual project.
const resolvedPath = this.resolveProjectPathSync(path ?? process.cwd());
return this.getMemoizedInstance(resolvedPath);
}
/**
* Performs an upward directory search for an sfdx project file. Returns the absolute path to the project.
*
* @param dir The directory path to start traversing from.
*
* **Throws** *{@link SfError}{ name: 'InvalidProjectWorkspaceError' }* If the current folder is not located in a workspace.
*
* **See** {@link traverseForFile}
*
* **See** [process.cwd()](https://nodejs.org/api/process.html#process_process_cwd)
*/
static async resolveProjectPath(dir) {
return (0, internal_1.resolveProjectPath)(dir);
}
/**
* Performs a synchronous upward directory search for an sfdx project file. Returns the absolute path to the project.
*
* @param dir The directory path to start traversing from.
*
* **Throws** *{@link SfError}{ name: 'InvalidProjectWorkspaceError' }* If the current folder is not located in a workspace.
*
* **See** {@link traverseForFileSync}
*
* **See** [process.cwd()](https://nodejs.org/api/process.html#process_process_cwd)
*/
static resolveProjectPathSync(dir) {
return (0, internal_1.resolveProjectPathSync)(dir);
}
/** shared method for resolve and getInstance.
* Cannot be a module-level function because instances is private */
static getMemoizedInstance(path) {
if (!SfProject.instances.has(path)) {
const project = new SfProject(path);
SfProject.instances.set(path, project);
}
return (0, ts_types_1.ensure)(SfProject.instances.get(path));
}
/**
* Returns the project path.
*/
getPath() {
return this.path;
}
/**
* Get the sfdx-project.json config. The global sfdx-project.json is used for user defaults
* that are not checked in to the project specific file.
*
* *Note:* When reading values from {@link SfProjectJson}, it is recommended to use
* {@link SfProject.resolveProjectConfig} instead.
*
* @param isGlobal True to get the global project file, otherwise the local project config.
*/
async retrieveSfProjectJson(isGlobal = false) {
const options = SfProjectJson.getDefaultOptions(isGlobal);
if (isGlobal) {
if (!this.sfProjectJsonGlobal) {
this.sfProjectJsonGlobal = await SfProjectJson.create(options);
}
return this.sfProjectJsonGlobal;
}
else {
options.rootFolder = this.getPath();
if (!this.sfProjectJson) {
this.sfProjectJson = await SfProjectJson.create(options);
}
return this.sfProjectJson;
}
}
/**
* Get the sfdx-project.json config. The global sfdx-project.json is used for user defaults
* that are not checked in to the project specific file.
*
* *Note:* When reading values from {@link SfProjectJson}, it is recommended to use
* {@link SfProject.resolveProjectConfig} instead.
*
* This is the sync method of {@link SfProject.resolveSfProjectJson}
*
* @param isGlobal True to get the global project file, otherwise the local project config.
*/
getSfProjectJson(isGlobal = false) {
const options = SfProjectJson.getDefaultOptions(isGlobal);
if (isGlobal) {
if (!this.sfProjectJsonGlobal) {
this.sfProjectJsonGlobal = new SfProjectJson(options);
this.sfProjectJsonGlobal.readSync();
}
return this.sfProjectJsonGlobal;
}
else {
options.rootFolder = this.getPath();
if (!this.sfProjectJson) {
this.sfProjectJson = new SfProjectJson(options);
this.sfProjectJson.readSync();
}
return this.sfProjectJson;
}
}
/**
* Returns a read-only list of `packageDirectories` within sfdx-project.json, first reading
* and validating the file if necessary. i.e. modifying this array will not affect the
* sfdx-project.json file.
*/
getPackageDirectories() {
if (!this.packageDirectories) {
this.packageDirectories = this.getSfProjectJson().getPackageDirectoriesSync();
}
return this.packageDirectories;
}
/**
* Returns a read-only list of `packageDirectories` within sfdx-project.json, first reading
* and validating the file if necessary. i.e. modifying this array will not affect the
* sfdx-project.json file.
*
* There can be multiple packages in packageDirectories that point to the same directory.
* This method only returns one packageDirectory entry per unique directory path. This is
* useful when doing source operations based on directories but probably not as useful
* for packaging operations that want to do something for each package entry.
*/
getUniquePackageDirectories() {
return this.getSfProjectJson().getUniquePackageDirectories();
}
/**
* Get a list of the unique package names from within sfdx-project.json. Use {@link SfProject.getUniquePackageDirectories}
* for data other than the names.
*/
getUniquePackageNames() {
return this.getSfProjectJson().getUniquePackageNames();
}
/**
* Returns the package from a file path.
*
* @param path A file path. E.g. /Users/jsmith/projects/ebikes-lwc/force-app/apex/my-cls.cls
*/
getPackageFromPath(path) {
const packageDirs = this.getPackageDirectories();
// resolve the given path
const fullPath = (0, node_path_1.resolve)(path);
const match = packageDirs.find((packageDir) => {
// fullPath will not have a trailing slash, so remove it from packageDir.fullPath, if it exists
const fullPathSansTrailingSep = packageDir.fullPath.replace(/(\\|\/)$/, '');
return ((0, node_path_1.basename)(path) === packageDir.path ||
fullPath === fullPathSansTrailingSep ||
fullPath.includes(packageDir.fullPath));
});
return match;
}
/**
* Returns the package name, E.g. 'force-app', from a file path.
*
* @param path A file path. E.g. /Users/jsmith/projects/ebikes-lwc/force-app/apex/my-cls.cls
*/
getPackageNameFromPath(path) {
const packageDir = this.getPackageFromPath(path);
if (!packageDir)
return undefined;
return (0, exports.isNamedPackagingDirectory)(packageDir) ? packageDir.package : packageDir.path;
}
/**
* Returns the package directory.
*
* @param packageName Name of the package directory. E.g., 'force-app'
*/
getPackage(packageName) {
const packageDirs = this.getPackageDirectories();
return packageDirs.find((packageDir) => packageDir.name === packageName);
}
/**
* Returns the package directory.
*
* @param packageName Name of the package directory. E.g., 'force-app'
*/
findPackage(predicate) {
return this.getPackageDirectories().find(predicate);
}
/**
* Returns the absolute path of the package directory ending with the path separator.
* E.g., /Users/jsmith/projects/ebikes-lwc/force-app/
*
* @param packageName Name of the package directory. E.g., 'force-app'
*/
getPackagePath(packageName) {
const packageDir = this.getPackage(packageName);
return packageDir?.fullPath;
}
/**
* Has package directories defined in the project.
*/
hasPackages() {
return this.getSfProjectJson().hasPackages();
}
/**
* Has multiple package directories (MPD) defined in the project.
*/
hasMultiplePackages() {
return this.getSfProjectJson().hasMultiplePackages();
}
/**
* Get the currently activated package on the project. This has no implication on sfdx-project.json
* but is useful for keeping track of package and source specific options in a process.
*/
getActivePackage() {
return this.activePackage;
}
/**
* Set the currently activated package on the project. This has no implication on sfdx-project.json
* but is useful for keeping track of package and source specific options in a process.
*
* @param packageName The package name to activate. E.g. 'force-app'
*/
setActivePackage(packageName) {
if (packageName == null) {
this.activePackage = null;
}
else {
this.activePackage = this.getPackage(packageName);
}
}
/**
* Get the project's default package directory defined in sfdx-project.json using first 'default: true'
* found. The first entry is returned if no default is specified.
*/
getDefaultPackage() {
if (!this.hasPackages()) {
throw new sfError_1.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];
}
/**
* The project config is resolved from local and global {@link SfProjectJson},
* {@link ConfigAggregator}, and a set of defaults. It is recommended to use
* this when reading values from SfProjectJson.
*
* The global {@link SfProjectJson} is used to allow the user to provide default values they
* may not want checked into their project's source.
*
* @returns A resolved config object that contains a bunch of different
* properties, including some 3rd party custom properties.
*/
async resolveProjectConfig() {
if (!this.projectConfig) {
// Do fs operations in parallel
const [global, local, configAggregator] = await Promise.all([
this.retrieveSfProjectJson(true),
this.retrieveSfProjectJson(),
configAggregator_1.ConfigAggregator.create(),
]);
await Promise.all([global.read(), local.read()]);
this.projectConfig = (0, kit_1.defaults)(local.toObject(), global.toObject());
// Add fields in sfdx-config.json
Object.assign(this.projectConfig, configAggregator.getConfig());
// we don't have a login url yet, so use instanceUrl from config or default
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (!this.projectConfig.sfdcLoginUrl) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
this.projectConfig.sfdcLoginUrl = configAggregator.getConfig()['org-instance-url'] ?? sfdcUrl_1.SfdcUrl.PRODUCTION;
}
// LEGACY - Allow override of sfdcLoginUrl via env var FORCE_SFDC_LOGIN_URL
if (process.env.FORCE_SFDC_LOGIN_URL) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
this.projectConfig.sfdcLoginUrl = process.env.FORCE_SFDC_LOGIN_URL;
}
// Allow override of signupTargetLoginUrl via env var SFDX_SCRATCH_ORG_CREATION_LOGIN_URL
if (process.env.SFDX_SCRATCH_ORG_CREATION_LOGIN_URL) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
this.projectConfig.signupTargetLoginUrl = process.env.SFDX_SCRATCH_ORG_CREATION_LOGIN_URL;
}
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return this.projectConfig;
}
async hasPackageAliases() {
return this.getSfProjectJson().hasPackageAliases();
}
/**
* Returns a read-only list of `packageDirectories` within sfdx-project.json, first reading
* and validating the file if necessary. i.e. modifying this array will not affect the
* sfdx-project.json file.
*/
getPackageAliases() {
if (!this.packageAliases) {
this.packageAliases = this.getSfProjectJson().getPackageAliases();
}
return this.packageAliases;
}
getPackageIdFromAlias(alias) {
const packageAliases = this.getPackageAliases();
return packageAliases ? packageAliases[alias] : undefined;
}
getAliasesFromPackageId(id) {
if (!/^.{15,18}$/.test(id)) {
throw messages.createError('invalidId', [id]);
}
return Object.entries(this.getPackageAliases() ?? {})
.filter(([, value]) => value?.startsWith(id))
.map(([key]) => key);
}
/**
* retrieve the configuration for a named plugin from sfdx-project.json.plugins.pluginName
*
* @example
* ```
* const project = await SfProject.resolve();
* const pluginConfig = await project.getPluginConfiguration('myPlugin');
* ```
*
* optionally pass a type parameter for your plugin configuration's schema
* */
async getPluginConfiguration(pluginName) {
await this.retrieveSfProjectJson();
const plugins = this.sfProjectJson.get('plugins');
if (!plugins) {
throw new sfError_1.SfError('No plugins defined in sfdx-project.json', 'NoPluginsDefined');
}
if (!plugins[pluginName]) {
throw new sfError_1.SfError(`No configuration defined in sfdx-project.json for plugin ${pluginName}`, 'PluginNotFound');
}
return plugins[pluginName];
}
/**
* set the configuration for a named plugin from sfdx-project.json.plugins.pluginName, overwriting existing configuration
*
* @example
* ```
* const project = await SfProject.resolve();
* const pluginConfig = await project.setPluginConfiguration('myPlugin', {foo: 'bar', myLimit: 25});
* ```
*
* optionally pass a type parameter for your plugin configuration's schema
* */
async setPluginConfiguration(pluginName, config) {
await this.retrieveSfProjectJson();
const plugins = this.getSfProjectJson().get('plugins') ?? {};
const modified = { ...plugins, [pluginName]: config };
this.sfProjectJson.set('plugins', modified);
this.sfProjectJson.writeSync();
}
}
exports.SfProject = SfProject;
/** differentiate between the Base PackageDir (path, maybe default) and the Packaging version (package and maybe a LOT of other fields) by whether is has the `package` property */
const isPackagingDirectory = (packageDir) => isPackagingDir(packageDir);
exports.isPackagingDirectory = isPackagingDirectory;
/** differentiate between the Base PackageDir (path, maybe default) and the Packaging version (package and maybe a LOT of other fields) by whether is has the `package` property */
const isNamedPackagingDirectory = (packageDir) => isPackagingDir(packageDir);
exports.isNamedPackagingDirectory = isNamedPackagingDirectory;
const isPackagingDir = (packageDir) => 'package' in packageDir && typeof packageDir.package === 'string';
/**
* there is no notion of uniqueness in package directory entries
* so an attempt of matching an existing entry is a bit convoluted
*/
const findPackageDir = (target) => (potentialMatch) =>
// an entry w/o a package or id is considered a directory entry for which a package has yet to be created
// find a matching dir entry that where path is the same and id and package are not present
(potentialMatch.path === target.path && !('id' in potentialMatch) && !(0, exports.isPackagingDirectory)(potentialMatch)) ||
// if that fails, then find a matching dir entry package is present and is same as the new entry
((0, exports.isPackagingDirectory)(target) && (0, exports.isPackagingDirectory)(potentialMatch) && target.package === potentialMatch.package);
//# sourceMappingURL=sfProject.js.map