import VersionList from '../version/VersionList';
import {ucwords} from "../utils";
/**
* An Environment instance holds all metadata about the environment,
* such as whether it is experimental, available to the public, or if the environment
* includes both electron and recorder modules. You can use the Environment instance
* to add/remove modules, get the version lists for a module, and more.
*/
class Environment {
constructor(props = {}) {
if (!props.value) {
throw new Error('Environment value required, none provided');
}
// set defaults
this.value = props.value;
this.label = props.label || ucwords(props.value.split('-').join(' '));
this.description = props.description;
this.modules = props.modules || [];
this.target = props.target;
this.origin = props.origin;
this.experimental = props.experiment === undefined ? true : props.experimental;
this.stagingApi = props.stagingApi === undefined ? false : props.stagingApi;
this.public = props.public === undefined ? false : props.public;
this.static = props.static === undefined ? false : props.static;
this.parent = props.parent;
this.userIds = props.userIds
// override using inherited props
for (let key of Object.keys(props)) {
this[key] = props[key];
}
}
/**
* Return the environment value.
* @returns {string}
*/
getValue () {
return this.value;
}
/**
* Return the human readable environment label.
* @returns {string}
*/
getLabel () {
return this.label;
}
/**
* Get the {@link Environment} value property for this environment's parent.
* @returns {*}
*/
getParent () {
return this.parent;
}
/**
* Returns the description field, empty by default but modifiable via the build panel / build server API.
* @returns {*}
*/
getDescription () {
return this.description;
}
/**
* Returns the user IDs array.
*
* @returns {Array} An array of user IDs.
*/
getUserIds () {
return this.userIds
}
/**
* Adds a user ID to the array of user IDs.
*
* @param {string} userId - The user ID to add.
* @returns {Environment} The current instance.
*/
addUserId (userId) {
if (this.userIds === undefined) {
this.userIds = []
} else if (this.userIds.some(id => id == userId)) {
return this
}
this.userIds.push(userId)
return this
}
/**
* Removes a user ID from the array of user IDs.
*
* @param {string} userId - The user ID to remove.
* @returns {Environment} The current instance.
*/
removeUserId (userId) {
this.userIds = this.userIds.filter(id => id !== userId)
return this
}
/**
* Sets the array of user IDs.
*
* @param {Array} userIds - An array of user IDs.
* @returns {Environment} The current instance.
*/
setUserIds (userIds) {
this.userIds = userIds
return this
}
/**
* Checks if a given user ID is allowed.
* Use loose comparison for string vs int id.
*
* @param {string} userId - The user ID to check.
* @returns {boolean} `true` if the user ID is allowed, `false` otherwise.
*/
isUserIdAllowed (userId) {
if (this.userIds === undefined) {
return true
}
return this.userIds.some(id => id == userId)
}
/**
* Return the supported modules.
* @returns {string[]}
*/
getModules () {
return this.modules;
}
/**
* Return true if the environment has a {@link VersionList} for the specified module.
*
* This is used to determine if the app should download both electron and recorder updates for
* each environment. Some environments don't need a recorder branch, in which case it can default
* to sandbox in most cases.
*
* @param module
* @returns {boolean}
*/
includesModule (module) {
return this.modules.includes(module.toLowerCase());
}
/**
* Add a module and perform a type safety check to prevent corrupt additions.
* @param module
* @returns {Environment}
*/
addModule (module) {
if (typeof module !== 'string') {
throw new Error('Cannot add module value that is not a string to environments')
}
if (this.includesModule(module)) {
console.warn(`Could not add duplicate module to ${this.value}: ${module}`);
return this;
}
this.modules.push(module.toLowerCase());
return this;
}
/**
* Remove a module from the {@link Environment}.
* @param module
* @returns {Environment}
*/
removeModule (module) {
if (typeof module !== 'string') {
throw new Error('Cannot remove module value that is not a string from environments')
}
if (!this.includesModule(module)) {
console.warn(`Could not remove module from ${this.value}, module is already not included: ${module}`);
return this;
}
this.modules = this.modules.filter(mod => mod !== module);
return this;
}
/**
* Return true if the {@link Environment} has a target override to control the storage path.
* @returns {boolean}
*/
hasTarget () {
return this.target !== undefined && this.target !== null && typeof target === 'string';
}
/**
* Return the target storage path (if any).
* @returns {string}
*/
getTarget () {
return this.target || this.value;
}
/**
* Return the origin branch hash (if any).
* @returns {string}
*/
getOrigin () {
return this.origin;
}
/**
* Return true if the {@link Environment} is experimental.
* This is true for all feature-based dynamic environments & static sandboxes.
* @returns {boolean}
*/
isExperimental () {
return this.experimental === true;
}
usesStagingApi () {
return this.stagingApi === true;
}
/**
* Return true if the {@link Environment} is publicly accessible.
*
* Environments like sandbox, and any non-static branches should
* essentially _never_ be public.
* @returns {boolean}
*/
isPublic () {
return this.public === true;
}
/**
* Return true if the {@link Environment} is static.
* A static environment is immutable -- it will never be deleted by
* webhooks and they are not deployed based on feature branches.
*
* A non-static environment should be seen as ephemeral -- it will be
* deleted when the correlating PR is closed or merged.
*
* @returns {boolean}
*/
isStatic () {
return this.static === true;
}
/**
* Set an {@link Environment}'s experimental state to on/off (true/false).
* If an environment is experimental, it will behave like a sandbox environment.
* @param experimental
* @returns {Environment}
*/
setExperimental (experimental) {
this.experiment = experimental;
return this;
}
setStagingApi (stagingApi) {
this.stagingApi = stagingApi;
return this;
}
/**
* Set an {@link Environment}'s public state.
* Cannot set a public environment to non-public.
*
* Non-public builds should never be discoverable or distributed to
* end-users for stability and confidentiality purposes.
* @param isPublic
* @returns {Environment}
*/
setPublic (isPublic) {
if (this.public && !isPublic) {
throw new Error('Cannot set a public environment as non-public')
}
this.public = isPublic;
return this;
}
/**
* Set an {@link Environment}'s static state.
*
* Static environments will never be removed or unpublished by CI/CD. They will
* only be continuously updated.
*
* Non-static environments will be treated as dynamic/ephemeral, and clients
* should expect such behavior.
* @param isStatic
* @returns {Environment}
*/
setStatic (isStatic) {
if (this.static && !isStatic) {
throw new Error('Cannot set a static environment as non-static');
}
this.static = isStatic;
return this;
}
/**
* Set the description for the environment.
* @param description
* @returns {Environment}
*/
setDescription (description) {
this.description = description;
return this;
}
/**
* Return the {@link VersionList} from this environment for the specified app module.
* @param module
* @param url
* @param fetchOpts
* @returns {Promise<VersionList>}
*/
async getVersionList (module, {url, fetchOpts = {}} = {}) {
const versionList = new VersionList({
host: `cdn.medal.tv/${this.getTarget()}/${module}`.split('//').join('/'),
environment: this.getValue(),
module,
})
return versionList.load(url, fetchOpts);
}
/**
* Convert to a parsed JSON object.
* @returns {Object}
*/
toJson () {
return JSON.parse(JSON.stringify(this))
}
}
export default Environment;