Extract logging from DI patterns, only use @tryghost/logging package

refs: https://github.com/TryGhost/Toolbox/issues/146

Switched to @tryghost/logging instead of passing around the library. The main sticking points of this change are jobs. When jobs are launched we don't want them to use a separate @tryghost/logging instance because they would start parallel rotation jobs. @tryghost/logging v2.x passes all logs to the parent process if run in a child process, so that we can use the same patterns in jobs and the rest of the codebase.
This commit is contained in:
Sam Lord 2021-12-06 18:00:55 +00:00 committed by GitHub
parent 928a30bd98
commit 97451a93cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 111 additions and 126 deletions

View File

@ -50,16 +50,14 @@ function notifyServerReady(error) {
/**
* Get the Database into a ready state
* - DatabaseStateManager handles doing all this for us
* - Passing logging makes it output state messages
*
* @param {object} options
* @param {object} options.config
* @param {object} options.logging
*/
async function initDatabase({config, logging}) {
async function initDatabase({config}) {
const DatabaseStateManager = require('./server/data/db/state-manager');
const dbStateManager = new DatabaseStateManager({knexMigratorFilePath: config.get('paths:appRoot')});
await dbStateManager.makeReady({logging});
await dbStateManager.makeReady();
}
/**
@ -383,7 +381,7 @@ async function bootGhost({backend = true, frontend = true, server = true} = {})
// Step 3 - Get the DB ready
debug('Begin: Get DB ready');
await initDatabase({config, logging});
await initDatabase({config});
bootLogger.log('database ready');
debug('End: Get DB ready');

View File

@ -1,4 +1,5 @@
const errors = require('@tryghost/errors');
const logging = require('@tryghost/logging');
const fs = require('fs-extra');
const path = require('path');
const MessageFormat = require('intl-messageformat');
@ -12,17 +13,15 @@ const get = require('lodash/get');
class I18n {
/**
* @param {objec} [options]
* @param {object} [options]
* @param {string} basePath - the base path to the translations directory
* @param {string} [locale] - a locale string
* @param {{dot|fulltext}} [stringMode] - which mode our translation keys use
* @param {{object}} [logging] - logging method
*/
constructor(options = {}) {
this._basePath = options.basePath || __dirname;
this._locale = options.locale || this.defaultLocale();
this._stringMode = options.stringMode || 'dot';
this._logging = options.logging || console;
this._strings = null;
}
@ -35,8 +34,8 @@ class I18n {
}
/**
* Need to call init after this
*/
* Need to call init after this
*/
get basePath() {
return this._basePath;
}
@ -262,34 +261,34 @@ class I18n {
}
_handleUninitialisedError(key) {
this._logging.warn(`i18n was used before it was initialised with key ${key}`);
logging.warn(`i18n was used before it was initialised with key ${key}`);
this.init();
}
_handleFormatError(err) {
this._logging.error(err.message);
logging.error(err.message);
}
_handleFallbackToDefault() {
this._logging.warn(`i18n is falling back to ${this.defaultLocale()}.json.`);
logging.warn(`i18n is falling back to ${this.defaultLocale()}.json.`);
}
_handleMissingFileError(locale) {
this._logging.warn(`i18n was unable to find ${locale}.json.`);
logging.warn(`i18n was unable to find ${locale}.json.`);
}
_handleInvalidFileError(locale, err) {
this._logging.error(new errors.IncorrectUsageError({
logging.error(new errors.IncorrectUsageError({
err,
message: `i18n was unable to parse ${locale}.json. Please check that it is valid JSON.`
}));
}
_handleEmptyKeyError() {
this._logging.warn('i18n.t() was called without a key');
logging.warn('i18n.t() was called without a key');
}
_handleMissingKeyError(key) {
this._logging.error(new errors.IncorrectUsageError({
logging.error(new errors.IncorrectUsageError({
message: `i18n.t() was called with a key that could not be found: ${key}`
}));
}

View File

@ -1,7 +1,6 @@
const config = require('../../../../shared/config');
const logging = require('@tryghost/logging');
const ThemeI18n = require('./theme-i18n');
module.exports = new ThemeI18n({logging, basePath: config.getContentPath('themes')});
module.exports = new ThemeI18n({basePath: config.getContentPath('themes')});
module.exports.ThemeI18n = ThemeI18n;

View File

@ -1,4 +1,5 @@
const errors = require('@tryghost/errors');
const logging = require('@tryghost/logging');
const I18n = require('./i18n');
class ThemeI18n extends I18n {
@ -39,23 +40,23 @@ class ThemeI18n extends I18n {
}
_handleFallbackToDefault() {
this._logging.warn(`Theme translations falling back to locales/${this.defaultLocale()}.json.`);
logging.warn(`Theme translations falling back to locales/${this.defaultLocale()}.json.`);
}
_handleMissingFileError(locale) {
if (locale !== this.defaultLocale()) {
this._logging.warn(`Theme translations file locales/${locale}.json not found.`);
logging.warn(`Theme translations file locales/${locale}.json not found.`);
}
}
_handleInvalidFileError(locale, err) {
this._logging.error(new errors.IncorrectUsageError({
logging.error(new errors.IncorrectUsageError({
err,
message: `Theme translations unable to parse locales/${locale}.json. Please check that it is valid JSON.`
}));
}
_handleEmptyKeyError() {
this._logging.warn('Theme translations {{t}} helper called without a translation key.');
logging.warn('Theme translations {{t}} helper called without a translation key.');
}
_handleMissingKeyError() {

View File

@ -15,8 +15,7 @@ const Twitter = require('../../services/twitter-embed');
const twitter = new Twitter({
config: {
bearerToken: config.get('twitter').privateReadOnlyToken
},
logging: require('@tryghost/logging')
}
});
oembed.registerProvider(nft);

View File

@ -1,5 +1,6 @@
const KnexMigrator = require('knex-migrator');
const errors = require('@tryghost/errors');
const logging = require('@tryghost/logging');
const states = {
READY: 0,
@ -8,7 +9,7 @@ const states = {
ERROR: 3
};
const printState = ({state, logging}) => {
const printState = ({state}) => {
if (state === states.READY) {
logging.info('Database is in a ready state.');
}
@ -67,13 +68,11 @@ class DatabaseStateManager {
}
}
async makeReady({logging}) {
async makeReady() {
try {
let state = await this.getState();
if (logging) {
printState({state, logging});
}
printState({state});
if (state === states.READY) {
return;
@ -89,9 +88,7 @@ class DatabaseStateManager {
state = await this.getState();
if (logging) {
printState({state, logging});
}
printState({state});
} catch (error) {
let errorToThrow = error;
if (!errors.utils.isGhostError(error)) {

View File

@ -1,9 +1,8 @@
const debug = require('@tryghost/debug')('utils:image-size-cache');
const errors = require('@tryghost/errors');
const logging = require('@tryghost/logging');
class CachedImageSizeFromUrl {
constructor({logging, imageSize}) {
this.logging = logging;
constructor({imageSize}) {
this.imageSize = imageSize;
this.cache = new Map();
}
@ -37,7 +36,7 @@ class CachedImageSizeFromUrl {
return this.cache.get(url);
}).catch((err) => {
debug('Cached image (error):', url);
this.logging.error(err);
logging.error(err);
// in case of error we just attach the url
this.cache.set(url, url);

View File

@ -4,10 +4,10 @@ const Gravatar = require('./gravatar');
const ImageSize = require('./image-size');
class ImageUtils {
constructor({config, logging, urlUtils, settingsCache, storageUtils, storage, validator, request}) {
constructor({config, urlUtils, settingsCache, storageUtils, storage, validator, request}) {
this.blogIcon = new BlogIcon({config, urlUtils, settingsCache, storageUtils});
this.imageSize = new ImageSize({config, storage, storageUtils, validator, urlUtils, request});
this.cachedImageSizeFromUrl = new CachedImageSizeFromUrl({logging, imageSize: this.imageSize});
this.cachedImageSizeFromUrl = new CachedImageSizeFromUrl({imageSize: this.imageSize});
this.gravatar = new Gravatar({config, request});
}
}

View File

@ -4,8 +4,7 @@ const storage = require('../../adapters/storage');
const storageUtils = require('../../adapters/storage/utils');
const validator = require('@tryghost/validator');
const config = require('../../../shared/config');
const logging = require('@tryghost/logging');
const settingsCache = require('../../../shared/settings-cache');
const ImageUtils = require('./image-utils');
module.exports = new ImageUtils({config, logging, urlUtils, settingsCache, storageUtils, storage, validator, request});
module.exports = new ImageUtils({config, urlUtils, settingsCache, storageUtils, storage, validator, request});

View File

@ -31,18 +31,6 @@ if (parentPort) {
(async () => {
const updateCheck = require('./update-check');
const logging = {
info(message) {
postParentPortMessage(message);
},
warn(message) {
postParentPortMessage(message);
},
error(message) {
postParentPortMessage(message);
}
};
// INIT required services
const models = require('./models');
models.init();
@ -54,7 +42,7 @@ if (parentPort) {
await settings.init();
// Finished INIT
await updateCheck({logging});
await updateCheck();
postParentPortMessage(`Ran update check`);

View File

@ -1,5 +1,4 @@
const config = require('../../../shared/config');
const logging = require('@tryghost/logging');
const db = require('../../data/db');
const settings = require('../../../shared/settings-cache');
const {EmailAnalyticsService} = require('@tryghost/email-analytics-service');
@ -9,11 +8,10 @@ const queries = require('./lib/queries');
module.exports = new EmailAnalyticsService({
config,
logging,
settings,
eventProcessor: new EventProcessor({db, logging}),
eventProcessor: new EventProcessor({db}),
providers: [
new MailgunProvider({config, settings, logging})
new MailgunProvider({config, settings})
],
queries
});

View File

@ -28,24 +28,6 @@ if (parentPort) {
const config = require('../../../../shared/config');
const db = require('../../../data/db');
const logging = {
info(message) {
if (parentPort) {
parentPort.postMessage(message);
}
},
warn(message) {
if (parentPort) {
parentPort.postMessage(message);
}
},
error(message) {
if (parentPort) {
parentPort.postMessage(message);
}
}
};
const settingsRows = await db.knex('settings')
.whereIn('key', ['mailgun_api_key', 'mailgun_domain', 'mailgun_base_url']);
@ -69,10 +51,9 @@ if (parentPort) {
const emailAnalyticsService = new EmailAnalyticsService({
config,
settings,
logging,
eventProcessor: new EventProcessor({db, logging}),
eventProcessor: new EventProcessor({db}),
providers: [
new MailgunProvider({config, settings, logging})
new MailgunProvider({config, settings})
],
queries
});

View File

@ -1,12 +1,10 @@
const settingsCache = require('../../../shared/settings-cache');
const mailService = require('../../services/mail');
const logging = require('@tryghost/logging');
const urlUtils = require('../../../shared/url-utils');
const Invites = require('./invites');
module.exports = new Invites({
settingsCache,
logging,
mailService,
urlUtils
});

View File

@ -1,5 +1,6 @@
const security = require('@tryghost/security');
const tpl = require('@tryghost/tpl');
const logging = require('@tryghost/logging');
const messages = {
invitedByName: '{invitedByName} has invited you to join {blogName}',
@ -10,9 +11,8 @@ const messages = {
};
class Invites {
constructor({settingsCache, logging, mailService, urlUtils}) {
constructor({settingsCache, mailService, urlUtils}) {
this.settingsCache = settingsCache;
this.logging = logging;
this.mailService = mailService;
this.urlUtils = urlUtils;
}
@ -80,7 +80,7 @@ class Invites {
});
const helpText = tpl(messages.errorSendingEmail.help);
err.message = `${errorMessage} ${helpText}`;
this.logging.warn(err.message);
logging.warn(err.message);
}
return Promise.reject(err);

View File

@ -17,6 +17,6 @@ const workerMessageHandler = ({name, message}) => {
logging.info(`Worker for job ${name} sent a message: ${message}`);
};
const jobManager = new JobManager({logging, errorHandler, workerMessageHandler});
const jobManager = new JobManager({errorHandler, workerMessageHandler});
module.exports = jobManager;

View File

@ -207,7 +207,6 @@ function createApiInstance(config) {
Settings: models.Settings
},
stripeAPIService: stripeService.api,
logger: logging,
offersAPI: offersService.api,
labsService: labsService
});

View File

@ -1,4 +1,5 @@
const errors = require('@tryghost/errors');
const logging = require('@tryghost/logging');
const tpl = require('@tryghost/tpl');
const {URL} = require('url');
const crypto = require('crypto');
@ -22,7 +23,6 @@ class MembersConfigProvider {
this._settingsCache = options.settingsCache;
this._config = options.config;
this._urlUtils = options.urlUtils;
this._logging = options.logging;
this._ghostVersion = options.ghostVersion;
}
@ -193,12 +193,12 @@ class MembersConfigProvider {
getAuthSecret() {
const hexSecret = this._settingsCache.get('members_email_auth_secret');
if (!hexSecret) {
this._logging.warn('Could not find members_email_auth_secret, using dynamically generated secret');
logging.warn('Could not find members_email_auth_secret, using dynamically generated secret');
return crypto.randomBytes(64);
}
const secret = Buffer.from(hexSecret, 'hex');
if (secret.length < 64) {
this._logging.warn('members_email_auth_secret not large enough (64 bytes), using dynamically generated secret');
logging.warn('members_email_auth_secret not large enough (64 bytes), using dynamically generated secret');
return crypto.randomBytes(64);
}
return secret;
@ -236,7 +236,7 @@ class MembersConfigProvider {
let publicKey = this._settingsCache.get('members_public_key');
if (!privateKey || !publicKey) {
this._logging.warn('Could not find members_private_key, using dynamically generated keypair');
logging.warn('Could not find members_private_key, using dynamically generated keypair');
const keypair = createKeypair({bits: 1024});
privateKey = keypair.private;
publicKey = keypair.public;

View File

@ -36,7 +36,6 @@ const membersConfig = new MembersConfigProvider({
config,
settingsCache,
urlUtils,
logging,
ghostVersion
});

View File

@ -1,5 +1,4 @@
const _ = require('lodash');
const logging = require('@tryghost/logging');
const StripeAPIService = require('@tryghost/members-stripe-service');
const config = require('../../../shared/config');
@ -9,7 +8,6 @@ const events = require('../../lib/common/events');
const {getConfig} = require('./config');
const api = new StripeAPIService({
logger: logging,
config: {}
});

View File

@ -1,4 +1,5 @@
const {extract} = require('oembed-parser');
const logging = require('@tryghost/logging');
/**
* @typedef {import('./oembed').ICustomProvider} ICustomProvider
@ -68,7 +69,7 @@ class TwitterOEmbedProvider {
oembedData.tweet_data = body.data;
oembedData.tweet_data.includes = body.includes;
} catch (err) {
this.dependencies.logging.error(err);
logging.error(err);
}
}

View File

@ -58,28 +58,28 @@
"@tryghost/adapter-manager": "0.2.24",
"@tryghost/admin-api-schema": "2.6.1",
"@tryghost/bookshelf-plugins": "0.3.5",
"@tryghost/bootstrap-socket": "0.2.14",
"@tryghost/bootstrap-socket": "0.2.15",
"@tryghost/color-utils": "0.1.5",
"@tryghost/config-url-helpers": "0.1.3",
"@tryghost/constants": "1.0.0",
"@tryghost/custom-theme-settings-service": "0.3.1",
"@tryghost/debug": "0.1.9",
"@tryghost/email-analytics-provider-mailgun": "1.0.6",
"@tryghost/email-analytics-provider-mailgun": "1.0.7",
"@tryghost/email-analytics-service": "1.0.5",
"@tryghost/errors": "1.0.4",
"@tryghost/errors": "1.1.1",
"@tryghost/express-dynamic-redirects": "0.2.2",
"@tryghost/helpers": "1.1.54",
"@tryghost/image-transform": "1.0.24",
"@tryghost/job-manager": "0.8.16",
"@tryghost/job-manager": "0.8.17",
"@tryghost/kg-card-factory": "3.1.0",
"@tryghost/kg-default-atoms": "3.1.0",
"@tryghost/kg-default-cards": "5.9.5",
"@tryghost/kg-markdown-html-renderer": "5.1.0",
"@tryghost/kg-mobiledoc-html-renderer": "5.3.1",
"@tryghost/limit-service": "1.0.6",
"@tryghost/logging": "1.0.2",
"@tryghost/logging": "2.0.0",
"@tryghost/magic-link": "1.0.14",
"@tryghost/members-api": "2.8.4",
"@tryghost/members-api": "2.8.6",
"@tryghost/members-csv": "1.2.0",
"@tryghost/members-importer": "0.3.5",
"@tryghost/members-offers": "0.10.3",

View File

@ -1256,12 +1256,12 @@
resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-transaction-events/-/bookshelf-transaction-events-0.1.7.tgz#3833a01df655efb11894f228ddbc66b788fb6be6"
integrity sha512-j1hbi+NPaUFhHYAVCWJoUmmIrnD5iSR3UP4nvYnYwCAHQ0Xu4Rc3+kIpdcrnSvwIEJNtVxNCaSarJTK/D/a0JQ==
"@tryghost/bootstrap-socket@0.2.14":
version "0.2.14"
resolved "https://registry.yarnpkg.com/@tryghost/bootstrap-socket/-/bootstrap-socket-0.2.14.tgz#d03caf3f51cd2acf37f0c3ee5cd511a953993197"
integrity sha512-95dYoV8EfQg7g2iF750xLJv/Yli1HhcZb3Zl6Nty8rX1GhZ8vFn57Agmh180MyFhO87oXRzihYRn18LAcEH6Ng==
"@tryghost/bootstrap-socket@0.2.15":
version "0.2.15"
resolved "https://registry.yarnpkg.com/@tryghost/bootstrap-socket/-/bootstrap-socket-0.2.15.tgz#02b6a3f1408acfd8df88f6a72e197c7e5ffc3bc2"
integrity sha512-OSWF9JVjfoufUyDi5MTNJb/Zd10yferDSR3cdfxK6Nu/zc8cjzmlKgxtHtsUn7E/mLmERDt4iNk9hC4r1utcIg==
dependencies:
"@tryghost/logging" "^1.0.2"
"@tryghost/logging" "^2.0.0"
"@tryghost/bunyan-rotating-filestream@0.0.7", "@tryghost/bunyan-rotating-filestream@^0.0.7":
version "0.0.7"
@ -1333,13 +1333,13 @@
"@elastic/elasticsearch" "^7.15.0"
"@tryghost/debug" "^0.1.9"
"@tryghost/email-analytics-provider-mailgun@1.0.6":
version "1.0.6"
resolved "https://registry.yarnpkg.com/@tryghost/email-analytics-provider-mailgun/-/email-analytics-provider-mailgun-1.0.6.tgz#7897699cb9054c8c35844bd38c5994103cbf1569"
integrity sha512-41owQfyrDke937FUk68osbi4n+6TZI4EfcH8+Kf6MkGfLy8xt42idk7QCQE+l++YKcENOkknSce+biAkGtmnkw==
"@tryghost/email-analytics-provider-mailgun@1.0.7":
version "1.0.7"
resolved "https://registry.yarnpkg.com/@tryghost/email-analytics-provider-mailgun/-/email-analytics-provider-mailgun-1.0.7.tgz#e8b0da0f9f137ad90b7eb8ee031be88a66811e06"
integrity sha512-OD4spZ0FmRPcIL5+DXE5N+HV26eyIC39yZwXKoQTZFPfU7LvTbbF/hJOFXC/CK4+DT8gd5flXZ32v0o6FUIinA==
dependencies:
"@tryghost/email-analytics-service" "^1.0.5"
"@tryghost/logging" "^1.0.2"
"@tryghost/logging" "^2.0.0"
mailgun-js "^0.22.0"
moment "^2.29.1"
@ -1351,10 +1351,10 @@
"@tryghost/debug" "^0.1.9"
lodash "^4.17.20"
"@tryghost/errors@1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@tryghost/errors/-/errors-1.0.4.tgz#9692cfd4c27a269ddbce77d93d02cfa8c52cc7ad"
integrity sha512-ImfwkOc54Ur9zjYgPaG3Y9wFeH00eJnqClYBZSB5/K0T6nqTXnIOyFIq33vZkmXuxQLX1xRb82OeWYxHxgFWDA==
"@tryghost/errors@1.1.1", "@tryghost/errors@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@tryghost/errors/-/errors-1.1.1.tgz#dc38e40591b018b0ac6b5742f7bab5d379c67e60"
integrity sha512-na0qB5sdy1BWgquzn+m530ohJ3fTeF451xUTR7I8b76TBEL9snnIkXCv5Qdjmnevmgod7aAGsHi2syyKFlvEvQ==
dependencies:
lodash "^4.17.21"
uuid "^8.3.2"
@ -1415,13 +1415,13 @@
optionalDependencies:
sharp "^0.29.0"
"@tryghost/job-manager@0.8.16":
version "0.8.16"
resolved "https://registry.yarnpkg.com/@tryghost/job-manager/-/job-manager-0.8.16.tgz#bd97b86fa72d564f08d7f00b5783e18685aab84c"
integrity sha512-4xfrkMdn2UHI/BFERAckoIsTgPepekvCDXykU/OLrkwbkqrChj+G6gL3EIGjjilSB6wzmJQ0BNinOn2o6M+NJQ==
"@tryghost/job-manager@0.8.17":
version "0.8.17"
resolved "https://registry.yarnpkg.com/@tryghost/job-manager/-/job-manager-0.8.17.tgz#356d91de13a48122e7bd178d038a9f759a499e22"
integrity sha512-srtey6RC89K5e2ai5zdTldpoKUvTdKilrUyuAc40tvpzBOct8K8o+mTIN2Ak+Yw8pre00WO/qcnuUTkTca4I5w==
dependencies:
"@breejs/later" "^4.0.2"
"@tryghost/logging" "^1.0.2"
"@tryghost/logging" "^2.0.0"
bree "^6.2.0"
cron-validate "^1.4.3"
fastq "^1.11.0"
@ -1489,7 +1489,7 @@
lodash "^4.17.21"
luxon "^1.26.0"
"@tryghost/logging@1.0.2", "@tryghost/logging@^1.0.0", "@tryghost/logging@^1.0.2":
"@tryghost/logging@1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@tryghost/logging/-/logging-1.0.2.tgz#8a8828ee5a22abfeb7a7aca67900b3cd129b37d8"
integrity sha512-D/KFMb40dA/1dExe2nbw7m1SFY5B8ghJHPYfLYXAK+Qi4GGO0c9D+3zbWcPoyCfveNW+7Uwgexwg+nAWMH+vmg==
@ -1505,6 +1505,38 @@
json-stringify-safe "^5.0.1"
lodash "^4.17.21"
"@tryghost/logging@2.0.0", "@tryghost/logging@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@tryghost/logging/-/logging-2.0.0.tgz#587c579d703ef15fe468b8bb8efdd9cdfefb90ef"
integrity sha512-eWKtiHWDtVVf+xn+ecKb8mUepFDK1RXOhl1tFMF1b7eFASn5WIDLFSMH9Xl9gyCi6dnsXp5fAm2G3baZ77bPZg==
dependencies:
"@tryghost/bunyan-rotating-filestream" "^0.0.7"
"@tryghost/elasticsearch" "^1.0.0"
"@tryghost/pretty-stream" "^0.1.2"
"@tryghost/root-utils" "^0.3.7"
bunyan "^1.8.15"
bunyan-loggly "^1.4.2"
fs-extra "^10.0.0"
gelf-stream "^1.1.1"
json-stringify-safe "^5.0.1"
lodash "^4.17.21"
"@tryghost/logging@^1.0.0":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@tryghost/logging/-/logging-1.0.1.tgz#26f8dc45f70550efed583244ee79ff7721e4dc58"
integrity sha512-e01V4kYlsqKuJsSw3VPWSrGBF2PaZ4NMXhh9jokVksB9EXOgX1DUElHM0nu1vKcewYvQJnMxkPoOSi3SvxeA7w==
dependencies:
"@tryghost/bunyan-rotating-filestream" "^0.0.7"
"@tryghost/elasticsearch" "^1.0.0"
"@tryghost/pretty-stream" "^0.1.2"
"@tryghost/root-utils" "^0.3.7"
bunyan "^1.8.15"
bunyan-loggly "^1.4.2"
fs-extra "^10.0.0"
gelf-stream "^1.1.1"
json-stringify-safe "^5.0.1"
lodash "^4.17.21"
"@tryghost/magic-link@1.0.14", "@tryghost/magic-link@^1.0.14":
version "1.0.14"
resolved "https://registry.yarnpkg.com/@tryghost/magic-link/-/magic-link-1.0.14.tgz#3636a023de4f2ecbd59dff1b56cde029106590f0"
@ -1538,15 +1570,15 @@
"@tryghost/domain-events" "^0.1.3"
"@tryghost/member-events" "^0.3.1"
"@tryghost/members-api@2.8.4":
version "2.8.4"
resolved "https://registry.yarnpkg.com/@tryghost/members-api/-/members-api-2.8.4.tgz#e61303eb47d3d4bfdf18cb754f1fea472562c08e"
integrity sha512-dngeDd29z4DjK/RLQHVOCKbsuP8Io39l9dLXWSiP9cdVTqjE5ax53GccDZOPLhD2H/fU3GAes37Sxzd7jPOtnw==
"@tryghost/members-api@2.8.6":
version "2.8.6"
resolved "https://registry.yarnpkg.com/@tryghost/members-api/-/members-api-2.8.6.tgz#c960df76b1a5e2cb52b15822971c92e02f4feb77"
integrity sha512-g1vbdZGmk/yXtvgjXH8pmUf+P8IDxhMgsPaXHUJJ8qyS2Lzrp51mbANbyIyp5TsoNpUwk3V/pfj1lzgtkswF0A==
dependencies:
"@tryghost/debug" "^0.1.2"
"@tryghost/domain-events" "^0.1.3"
"@tryghost/errors" "^1.1.0"
"@tryghost/logging" "^1.0.2"
"@tryghost/errors" "^1.1.1"
"@tryghost/logging" "^2.0.0"
"@tryghost/magic-link" "^1.0.14"
"@tryghost/member-analytics-service" "^0.1.4"
"@tryghost/member-events" "^0.3.1"