UNPKG

ghost

Version:

The professional publishing platform

145 lines (121 loc) 4.81 kB
// Feature flags behaviour in tests: // By default, all flags listed in GA_FEATURES, BETA_FEATURES, and ALPHA_FEATURES // are globally enabled during E2E tests. This ensures flagged code paths are tested // automatically. // For more details, see the E2E testing documentation: // https://www.notion.so/ghost/End-to-end-Testing-6a2ef073b1754b18aff42e24a632a007 const _ = require('lodash'); 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}' }; // flags in this list always return `true`, allows quick global enable prior to full flag removal const GA_FEATURES = [ 'audienceFeedback', 'i18n', 'themeErrorsNotification', 'announcementBar', 'customFonts', 'contentVisibility' ]; // These features are considered publicly available and can be enabled/disabled by users const PUBLIC_BETA_FEATURES = [ 'ActivityPub', 'superEditors', 'editorExcerpt', 'additionalPaymentMethods' ]; // These features are considered private they live in the private tab of the labs settings page // Which is only visible if the developer experiments flag is enabled const PRIVATE_FEATURES = [ 'stripeAutomaticTax', 'webmentions', 'trafficAnalytics', 'importMemberTier', 'urlCache', 'mailEvents', 'lexicalIndicators', 'ui60', 'contentVisibilityAlpha', 'explore', 'emailCustomization' ]; module.exports.GA_KEYS = [...GA_FEATURES]; module.exports.WRITABLE_KEYS_ALLOWLIST = [...PUBLIC_BETA_FEATURES, ...PRIVATE_FEATURES]; module.exports.getAll = () => { const labs = _.cloneDeep(settingsCache.get('labs')) || {}; GA_FEATURES.forEach((gaKey) => { labs[gaKey] = true; }); const labsConfig = config.get('labs') || {}; Object.keys(labsConfig).forEach((key) => { labs[key] = labsConfig[key]; }); labs.members = settingsCache.get('members_signup_access') !== 'none'; return labs; }; module.exports.getAllFlags = function () { return [...GA_FEATURES, ...PUBLIC_BETA_FEATURES, ...PRIVATE_FEATURES]; }; /** * @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}); // eslint-disable-next-line no-restricted-syntax logging.error(new errors.DisabledFeatureError({ message: errDetails.message, context: errDetails.context, help: errDetails.help })); const {SafeString} = require('express-hbs'); errString = new SafeString(`<script>console.error("${_.values(errDetails).join(' ')}");</script>`); if (options.async) { return Promise.resolve(errString); } return errString; }; module.exports.enabledMiddleware = flag => function labsEnabledMw(req, res, next) { if (module.exports.isSet(flag) === true) { return next(); } else { return next(new errors.NotFoundError()); } };