0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-03 23:00:14 -05:00
ghost/core/shared/labs.js
Naz c65e62b7df Promoted email-only feature to general availability
closes https://github.com/TryGhost/Team/issues/1125
refs 3c822e0457

- Email-only is not considered a general availability feature and can be used without special flags.
- It allows to publish a new post type "email only" that only goes out as an email newletter and is available through an undescoverable URL (does not appear anywhere publicly similarly to preview posts) on the site.
2021-10-07 12:08:56 +02:00

106 lines
3.5 KiB
JavaScript

const _ = require('lodash');
const Promise = require('bluebird');
const SafeString = require('express-hbs').SafeString;
const errors = require('@tryghost/errors');
const logging = require('@tryghost/logging');
const tpl = require('@tryghost/tpl');
const settingsCache = require('./settings-cache');
const config = require('./config');
const messages = {
errorMessage: 'The \\{\\{{helperName}\\}\\} helper is not available.',
errorContext: 'The {flagName} flag must be enabled in labs if you wish to use the \\{\\{{helperName}\\}\\} helper.',
errorHelp: 'See {url}'
};
// NOTE: this allowlist is meant to be used to filter out any unexpected
// input for the "labs" setting value
const BETA_FEATURES = [
'activitypub',
'matchHelper',
'multipleProducts'
];
const ALPHA_FEATURES = [
'oauthLogin',
'customThemeSettings',
'membersActivity',
'offers'
];
module.exports.WRITABLE_KEYS_ALLOWLIST = [...BETA_FEATURES, ...ALPHA_FEATURES];
module.exports.getAll = () => {
const labs = _.cloneDeep(settingsCache.get('labs')) || {};
ALPHA_FEATURES.forEach((alphaKey) => {
if (labs[alphaKey] && !(config.get('enableDeveloperExperiments') || process.env.NODE_ENV.match(/^testing/))) {
delete labs[alphaKey];
}
});
labs.members = settingsCache.get('members_signup_access') !== 'none';
return labs;
};
/**
* @param {string} flag
* @returns {boolean}
*/
module.exports.isSet = function isSet(flag) {
const labsConfig = module.exports.getAll();
return !!(labsConfig && labsConfig[flag] && labsConfig[flag] === true);
};
/**
*
* @param {object} options
* @param {string} options.flagKey the internal lookup key of the flag e.g. labs.isSet(matchHelper)
* @param {string} options.flagName the user-facing name of the flag e.g. Match helper
* @param {string} options.helperName Name of the helper to be enabled/disabled
* @param {string} [options.errorMessage] Optional replacement error message
* @param {string} [options.errorContext] Optional replacement context message
* @param {string} [options.errorHelp] Optional replacement help message
* @param {string} [options.helpUrl] Url to show in the help message
* @param {string} [options.async] is the helper async?
* @param {function} callback
* @returns {Promise<Handlebars.SafeString>|Handlebars.SafeString}
*/
module.exports.enabledHelper = function enabledHelper(options, callback) {
const errDetails = {};
let errString;
if (module.exports.isSet(options.flagKey) === true) {
// helper is active, use the callback
return callback();
}
// Else, the helper is not active and we need to handle this as an error
errDetails.message = tpl(options.errorMessage || messages.errorMessage, {helperName: options.helperName}),
errDetails.context = tpl(options.errorContext || messages.errorContext, {
helperName: options.helperName,
flagName: options.flagName
});
errDetails.help = tpl(options.errorHelp || messages.errorHelp, {url: options.helpUrl});
logging.error(new errors.DisabledFeatureError(errDetails));
errString = new SafeString(`<script>console.error("${_.values(errDetails).join(' ')}");</script>`);
if (options.async) {
return Promise.resolve(errString);
}
return errString;
};
module.exports.enabledMiddleware = flag => (req, res, next) => {
if (module.exports.isSet(flag) === true) {
return next();
} else {
return next(new errors.NotFoundError());
}
};