From edb529378ec15a06e0ec8a69e9949b18d793fb60 Mon Sep 17 00:00:00 2001 From: NGPixel Date: Sun, 10 Apr 2022 00:12:19 -0400 Subject: [PATCH] refactor: fix models + storage modules --- dev.code-workspace | 8 +- package.json | 27 +- server/core/auth.js | 12 +- server/core/kernel.js | 3 +- server/core/servers.js | 6 + server/db/migrations/3.0.0.js | 94 ++- server/index.js | 6 +- server/master.js | 2 +- server/models/analytics.js | 3 +- server/models/authentication.js | 82 +- server/models/commentProviders.js | 62 +- server/models/editors.js | 65 -- server/models/pages.js | 55 +- server/models/renderers.js | 88 +-- server/models/sites.js | 108 +++ server/models/storage.js | 217 +++--- server/models/tags.js | 6 +- server/models/users.js | 6 +- server/modules/editor/api/definition.yml | 6 - server/modules/editor/ckeditor/definition.yml | 6 - server/modules/editor/code/definition.yml | 6 - server/modules/editor/markdown/definition.yml | 6 - server/modules/editor/redirect/definition.yml | 6 - server/modules/editor/wysiwyg/definition.yml | 6 - server/modules/storage/azure/definition.yml | 42 +- server/modules/storage/box/definition.yml | 10 - server/modules/storage/box/storage.js | 26 - server/modules/storage/db/definition.yml | 25 + server/modules/storage/db/storage.js | 14 + .../storage/digitalocean/definition.yml | 45 -- .../modules/storage/digitalocean/storage.js | 3 - server/modules/storage/disk/definition.yml | 32 +- server/modules/storage/disk/storage.js | 5 +- server/modules/storage/dropbox/definition.yml | 9 - server/modules/storage/dropbox/storage.js | 26 - server/modules/storage/gcs/definition.yml | 65 ++ server/modules/storage/gcs/storage.js | 164 ++++ server/modules/storage/gdrive/definition.yml | 9 - server/modules/storage/gdrive/storage.js | 26 - server/modules/storage/git/definition.yml | 97 ++- server/modules/storage/git/storage.js | 53 +- server/modules/storage/github/definition.yml | 49 ++ server/modules/storage/github/storage.js | 211 +++++ .../modules/storage/onedrive/definition.yml | 9 - server/modules/storage/onedrive/storage.js | 26 - server/modules/storage/s3/common.js | 168 ---- server/modules/storage/s3/definition.yml | 156 +++- server/modules/storage/s3/storage.js | 167 +++- .../modules/storage/s3generic/definition.yml | 57 -- server/modules/storage/s3generic/storage.js | 3 - server/modules/storage/sftp/definition.yml | 57 +- server/modules/storage/sftp/storage.js | 7 +- yarn.lock | 720 +++++++++++++----- 53 files changed, 1912 insertions(+), 1255 deletions(-) create mode 100644 server/models/sites.js delete mode 100644 server/modules/editor/api/definition.yml delete mode 100644 server/modules/editor/ckeditor/definition.yml delete mode 100644 server/modules/editor/code/definition.yml delete mode 100644 server/modules/editor/markdown/definition.yml delete mode 100644 server/modules/editor/redirect/definition.yml delete mode 100644 server/modules/editor/wysiwyg/definition.yml delete mode 100644 server/modules/storage/box/definition.yml delete mode 100644 server/modules/storage/box/storage.js create mode 100644 server/modules/storage/db/definition.yml create mode 100644 server/modules/storage/db/storage.js delete mode 100644 server/modules/storage/digitalocean/definition.yml delete mode 100644 server/modules/storage/digitalocean/storage.js delete mode 100644 server/modules/storage/dropbox/definition.yml delete mode 100644 server/modules/storage/dropbox/storage.js create mode 100644 server/modules/storage/gcs/definition.yml create mode 100644 server/modules/storage/gcs/storage.js delete mode 100644 server/modules/storage/gdrive/definition.yml delete mode 100644 server/modules/storage/gdrive/storage.js create mode 100644 server/modules/storage/github/definition.yml create mode 100644 server/modules/storage/github/storage.js delete mode 100644 server/modules/storage/onedrive/definition.yml delete mode 100644 server/modules/storage/onedrive/storage.js delete mode 100644 server/modules/storage/s3/common.js delete mode 100644 server/modules/storage/s3generic/definition.yml delete mode 100644 server/modules/storage/s3generic/storage.js diff --git a/dev.code-workspace b/dev.code-workspace index 3a3de14b..b155abd3 100644 --- a/dev.code-workspace +++ b/dev.code-workspace @@ -1,10 +1,12 @@ { "folders": [ { - "path": "ux" + "name": "server", + "path": "server" }, { - "path": "server" + "name": "ux", + "path": "ux" } ], "settings": { @@ -13,4 +15,4 @@ "src/i18n/locales" ] } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 3fa77c29..be0d4e66 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "dev": true, "scripts": { "start": "node server", - "dev": "node dev", + "dev": "nodemon server", + "dev-legacy": "node dev", "test": "eslint --format codeframe --ext .js,.vue . && pug-lint server/views && jest", "cypress:open": "cypress open" }, @@ -119,12 +120,12 @@ "moment-timezone": "0.5.31", "ms": "2.1.3", "multer": "1.4.4", - "nanoid": "3.2.0", + "nanoid": "3.3.2", "node-2fa": "1.1.2", "node-cache": "5.1.2", "nodemailer": "6.7.3", "objection": "3.0.1", - "passport": "0.4.1", + "passport": "0.5.2", "passport-auth0": "1.4.2", "passport-azure-ad": "4.3.1", "passport-cas": "0.1.1", @@ -135,13 +136,13 @@ "passport-gitlab2": "5.0.0", "passport-google-oauth20": "2.0.0", "passport-jwt": "4.0.0", - "passport-ldapauth": "2.1.4", + "passport-ldapauth": "3.0.1", "passport-local": "1.0.0", "passport-microsoft": "0.1.0", "passport-oauth2": "1.6.1", "passport-okta-oauth": "0.0.1", - "passport-openidconnect": "0.0.2", - "passport-saml": "1.3.5", + "passport-openidconnect": "0.1.1", + "passport-saml": "3.2.1", "passport-slack-oauth2": "1.1.1", "passport-twitch-oauth": "1.0.0", "pem-jwk": "2.0.0", @@ -241,7 +242,7 @@ "filepond-plugin-file-validate-type": "1.2.6", "filesize.js": "2.0.0", "graphql-persisted-document-loader": "2.0.0", - "graphql-tag": "2.11.0", + "graphql-tag": "2.12.6", "hammerjs": "2.0.8", "html-webpack-plugin": "4.5.0", "html-webpack-pug-plugin": "2.0.0", @@ -256,6 +257,7 @@ "mini-css-extract-plugin": "0.11.3", "moment-duration-format": "2.3.2", "moment-timezone-data-webpack-plugin": "1.3.0", + "nodemon": "2.0.15", "offline-plugin": "5.0.7", "optimize-css-assets-webpack-plugin": "5.0.4", "pako": "1.0.11", @@ -265,7 +267,7 @@ "postcss-import": "12.0.1", "postcss-loader": "3.0.0", "postcss-preset-env": "6.7.0", - "postcss-selector-parser": "6.0.9", + "postcss-selector-parser": "6.0.10", "prismjs": "1.22.0", "pug-lint": "2.6.0", "pug-loader": "2.4.0", @@ -371,5 +373,14 @@ "type": "opencollective", "url": "https://opencollective.com/wikijs", "logo": "https://opencollective.com/opencollective/logo.txt" + }, + "nodemonConfig": { + "env": { + "NODE_ENV": "development" + }, + "ext": "js,json,graphql,gql", + "watch": [ + "server/" + ] } } diff --git a/server/core/auth.js b/server/core/auth.js index 38f0b3b8..37bd2cf1 100644 --- a/server/core/auth.js +++ b/server/core/auth.js @@ -66,7 +66,7 @@ module.exports = { // Load JWT passport.use('jwt', new passportJWT.Strategy({ jwtFromRequest: securityHelper.extractJWT, - secretOrKey: WIKI.config.certs.public, + secretOrKey: WIKI.config.auth.certs.public, audience: WIKI.config.auth.audience, issuer: 'urn:wiki.js', algorithms: ['RS256'] @@ -76,13 +76,13 @@ module.exports = { // Load enabled strategies const enabledStrategies = await WIKI.models.authentication.getStrategies() - for (let idx in enabledStrategies) { + for (const idx in enabledStrategies) { const stg = enabledStrategies[idx] try { - const strategy = require(`../modules/authentication/${stg.strategyKey}/authentication.js`) + const strategy = require(`../modules/authentication/${stg.module}/authentication.js`) - stg.config.callbackURL = `${WIKI.config.host}/login/${stg.key}/callback` - stg.config.key = stg.key; + stg.config.callbackURL = `${WIKI.config.host}/login/${stg.id}/callback` + stg.config.key = stg.id strategy.init(passport, stg.config) strategy.config = stg.config @@ -92,7 +92,7 @@ module.exports = { } WIKI.logger.info(`Authentication Strategy ${stg.displayName}: [ OK ]`) } catch (err) { - WIKI.logger.error(`Authentication Strategy ${stg.displayName} (${stg.key}): [ FAILED ]`) + WIKI.logger.error(`Authentication Strategy ${stg.displayName} (${stg.id}): [ FAILED ]`) WIKI.logger.error(err) } } diff --git a/server/core/kernel.js b/server/core/kernel.js index dbbcd30c..f2a067b9 100644 --- a/server/core/kernel.js +++ b/server/core/kernel.js @@ -67,7 +67,6 @@ module.exports = { await WIKI.models.analytics.refreshProvidersFromDisk() await WIKI.models.authentication.refreshStrategiesFromDisk() await WIKI.models.commentProviders.refreshProvidersFromDisk() - await WIKI.models.editors.refreshEditorsFromDisk() await WIKI.models.renderers.refreshRenderersFromDisk() await WIKI.models.storage.refreshTargetsFromDisk() @@ -76,7 +75,7 @@ module.exports = { await WIKI.auth.activateStrategies() await WIKI.models.commentProviders.initProvider() await WIKI.models.storage.initTargets() - WIKI.scheduler.start() + // WIKI.scheduler.start() await WIKI.models.subscribeToNotifications() }, diff --git a/server/core/servers.js b/server/core/servers.js index 74aa5bae..782a386b 100644 --- a/server/core/servers.js +++ b/server/core/servers.js @@ -4,6 +4,7 @@ const https = require('https') const { ApolloServer } = require('apollo-server-express') const Promise = require('bluebird') const _ = require('lodash') +const { ApolloServerPluginLandingPageGraphQLPlayground, ApolloServerPluginLandingPageProductionDefault } = require('apollo-server-core') /* global WIKI */ @@ -123,6 +124,11 @@ module.exports = { uploads: false, context: ({ req, res }) => ({ req, res }), plugins: [ + process.env.NODE_ENV === 'development' ? ApolloServerPluginLandingPageGraphQLPlayground({ + footer: false + }) : ApolloServerPluginLandingPageProductionDefault({ + footer: false + }) // ApolloServerPluginDrainHttpServer({ httpServer: this.servers.http }) // ...(this.servers.https && ApolloServerPluginDrainHttpServer({ httpServer: this.servers.https })) ] diff --git a/server/db/migrations/3.0.0.js b/server/db/migrations/3.0.0.js index 3fbae790..bd2e3411 100644 --- a/server/db/migrations/3.0.0.js +++ b/server/db/migrations/3.0.0.js @@ -71,6 +71,12 @@ exports.up = async knex => { table.jsonb('autoEnrollGroups').notNullable().defaultTo('[]') table.jsonb('hideOnSites').notNullable().defaultTo('[]') }) + .createTable('commentProviders', table => { + table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()')) + table.string('module').notNullable() + table.boolean('isEnabled').notNullable().defaultTo(false) + table.json('config').notNullable() + }) // COMMENTS ---------------------------- .createTable('comments', table => { table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()')) @@ -140,6 +146,7 @@ exports.up = async knex => { table.timestamp('publishEndDate') table.string('action').defaultTo('updated') table.text('content') + table.string('editor').notNullable() table.string('contentType').notNullable() table.jsonb('extra').notNullable().defaultTo('{}') table.jsonb('tags').defaultTo('[]') @@ -166,6 +173,7 @@ exports.up = async knex => { table.text('content') table.text('render') table.jsonb('toc') + table.string('editor').notNullable() table.string('contentType').notNullable() table.jsonb('extra').notNullable().defaultTo('{}') table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now()) @@ -279,6 +287,9 @@ exports.up = async knex => { .table('assetFolders', table => { table.uuid('parentId').references('id').inTable('assetFolders').index() }) + .table('commentProviders', table => { + table.uuid('siteId').notNullable().references('id').inTable('sites') + }) .table('comments', table => { table.uuid('pageId').notNullable().references('id').inTable('pages').index() table.uuid('authorId').notNullable().references('id').inTable('users').index() @@ -306,6 +317,9 @@ exports.up = async knex => { table.uuid('pageId').notNullable().references('id').inTable('pages').onDelete('CASCADE') table.string('localeCode', 5).references('code').inTable('locales') }) + .table('renderers', table => { + table.uuid('siteId').notNullable().references('id').inTable('sites') + }) .table('storage', table => { table.uuid('siteId').notNullable().references('id').inTable('sites') }) @@ -324,9 +338,50 @@ exports.up = async knex => { // DEFAULT DATA // ===================================== + // -> GENERATE IDS + + const groupAdminId = uuid() + const groupGuestId = '10000000-0000-4000-0000-000000000001' + const siteId = uuid() + const authModuleId = uuid() + const userAdminId = uuid() + const userGuestId = uuid() + // -> SYSTEM CONFIG + WIKI.logger.info('Generating certificates...') + const secret = crypto.randomBytes(32).toString('hex') + const certs = crypto.generateKeyPairSync('rsa', { + modulusLength: 2048, + publicKeyEncoding: { + type: 'pkcs1', + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs1', + format: 'pem', + cipher: 'aes-256-cbc', + passphrase: secret + } + }) + await knex('settings').insert([ + { + key: 'auth', + value: { + audience: 'urn:wiki.js', + tokenExpiration: '30m', + tokenRenewal: '14d', + certs: { + jwk: pem2jwk(certs.publicKey), + public: certs.publicKey, + private: certs.privateKey + }, + secret, + rootAdminUserId: userAdminId, + guestUserId: userGuestId + } + }, { key: 'mail', value: { @@ -367,12 +422,6 @@ exports.up = async knex => { uploadScanSVG: true } }, - { - key: 'system', - value: { - sessionSecret: crypto.randomBytes(32).toString('hex') - } - }, { key: 'update', value: { @@ -393,39 +442,11 @@ exports.up = async knex => { // -> DEFAULT SITE - WIKI.logger.info('Generating certificates...') - const secret = crypto.randomBytes(32).toString('hex') - const certs = crypto.generateKeyPairSync('rsa', { - modulusLength: 2048, - publicKeyEncoding: { - type: 'pkcs1', - format: 'pem' - }, - privateKeyEncoding: { - type: 'pkcs1', - format: 'pem', - cipher: 'aes-256-cbc', - passphrase: secret - } - }) - - const siteId = uuid() await knex('sites').insert({ id: siteId, hostname: '*', isEnabled: true, config: { - auth: { - audience: 'urn:wiki.js', - tokenExpiration: '30m', - tokenRenewal: '14d', - certs: { - jwk: pem2jwk(certs.publicKey), - public: certs.publicKey, - private: certs.privateKey - }, - secret - }, title: 'My Wiki Site', description: '', company: '', @@ -471,8 +492,6 @@ exports.up = async knex => { // -> DEFAULT GROUPS - const groupAdminId = uuid() - const groupGuestId = '10000000-0000-4000-0000-000000000001' await knex('groups').insert([ { id: groupAdminId, @@ -503,7 +522,6 @@ exports.up = async knex => { // -> AUTHENTICATION MODULE - const authModuleId = uuid() await knex('authentication').insert({ id: authModuleId, module: 'local', @@ -513,8 +531,6 @@ exports.up = async knex => { // -> USERS - const userAdminId = uuid() - const userGuestId = uuid() await knex('users').insert([ { id: userAdminId, diff --git a/server/index.js b/server/index.js index f282d755..617f50e0 100644 --- a/server/index.js +++ b/server/index.js @@ -22,7 +22,11 @@ let WIKI = { Error: require('./helpers/error'), configSvc: require('./core/config'), kernel: require('./core/kernel'), - startedAt: DateTime.utc() + startedAt: DateTime.utc(), + storage: { + defs: [], + modules: [] + } } global.WIKI = WIKI diff --git a/server/master.js b/server/master.js index 3d334e68..6ea6778f 100644 --- a/server/master.js +++ b/server/master.js @@ -77,7 +77,7 @@ module.exports = async () => { app.use(cookieParser()) app.use(session({ - secret: WIKI.config.system.sessionSecret, + secret: WIKI.config.auth.secret, resave: false, saveUninitialized: false, store: new KnexSessionStore({ diff --git a/server/models/analytics.js b/server/models/analytics.js index ec8a72a4..05b2c6d6 100644 --- a/server/models/analytics.js +++ b/server/models/analytics.js @@ -12,7 +12,6 @@ const commonHelper = require('../helpers/common') */ module.exports = class Analytics extends Model { static get tableName() { return 'analytics' } - static get idColumn() { return 'key' } static get jsonSchema () { return { @@ -52,7 +51,7 @@ module.exports = class Analytics extends Model { WIKI.logger.info(`Loaded ${WIKI.data.analytics.length} analytics module definitions: [ OK ]`) } catch (err) { - WIKI.logger.error(`Failed to scan or load new analytics providers: [ FAILED ]`) + WIKI.logger.error(`Failed to scan or load analytics providers: [ FAILED ]`) WIKI.logger.error(err) } } diff --git a/server/models/authentication.js b/server/models/authentication.js index 8d6ac9e3..ba6b49d4 100644 --- a/server/models/authentication.js +++ b/server/models/authentication.js @@ -12,15 +12,15 @@ const commonHelper = require('../helpers/common') */ module.exports = class Authentication extends Model { static get tableName() { return 'authentication' } - static get idColumn() { return 'key' } static get jsonSchema () { return { type: 'object', - required: ['key'], + required: ['module'], properties: { - key: {type: 'string'}, + id: { type: 'string' }, + module: { type: 'string' }, selfRegistration: {type: 'boolean'} } } @@ -43,79 +43,23 @@ module.exports = class Authentication extends Model { })) } - static async getStrategiesForLegacyClient() { - const strategies = await WIKI.models.authentication.query().select('key', 'selfRegistration') - let formStrategies = [] - let socialStrategies = [] - - for (let stg of strategies) { - const stgInfo = _.find(WIKI.data.authentication, ['key', stg.key]) || {} - if (stgInfo.useForm) { - formStrategies.push({ - key: stg.key, - title: stgInfo.title - }) - } else { - socialStrategies.push({ - ...stgInfo, - ...stg, - icon: await fs.readFile(path.join(WIKI.ROOTPATH, `assets/svg/auth-icon-${stg.key}.svg`), 'utf8').catch(err => { - if (err.code === 'ENOENT') { - return null - } - throw err - }) - }) - } - } - - return { - formStrategies, - socialStrategies - } - } - static async refreshStrategiesFromDisk() { try { - const dbStrategies = await WIKI.models.authentication.query() - // -> Fetch definitions from disk - const authDirs = await fs.readdir(path.join(WIKI.SERVERPATH, 'modules/authentication')) + const authenticationDirs = await fs.readdir(path.join(WIKI.SERVERPATH, 'modules/authentication')) WIKI.data.authentication = [] - for (let dir of authDirs) { - const defRaw = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/authentication', dir, 'definition.yml'), 'utf8') - const def = yaml.safeLoad(defRaw) - WIKI.data.authentication.push({ - ...def, - props: commonHelper.parseModuleProps(def.props) - }) - } - - for (const strategy of dbStrategies) { - const strategyDef = _.find(WIKI.data.authentication, ['key', strategy.strategyKey]) - if (!strategyDef) { - await WIKI.models.authentication.query().delete().where('key', strategy.key) - WIKI.logger.info(`Authentication strategy ${strategy.strategyKey} was removed from disk: [ REMOVED ]`) - continue - } - strategy.config = _.transform(strategyDef.props, (result, value, key) => { - if (!_.has(result, key)) { - _.set(result, key, value.default) - } - return result - }, strategy.config) - - // Fix pre-2.5 strategies displayName - if (!strategy.displayName) { - await WIKI.models.authentication.query().patch({ - displayName: strategyDef.title - }).where('key', strategy.key) - } + for (const dir of authenticationDirs) { + const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/authentication', dir, 'definition.yml'), 'utf8') + const defParsed = yaml.load(def) + defParsed.key = dir + defParsed.props = commonHelper.parseModuleProps(defParsed.props) + WIKI.data.analytics.push(defParsed) + WIKI.logger.debug(`Loaded authentication module definition ${dir}: [ OK ]`) } - WIKI.logger.info(`Loaded ${WIKI.data.authentication.length} authentication strategies: [ OK ]`) + WIKI.logger.info(`Loaded ${WIKI.data.analytics.length} authentication module definitions: [ OK ]`) } catch (err) { - WIKI.logger.error(`Failed to scan or load new authentication providers: [ FAILED ]`) + WIKI.logger.error(`Failed to scan or load authentication providers: [ FAILED ]`) WIKI.logger.error(err) } } diff --git a/server/models/commentProviders.js b/server/models/commentProviders.js index fce652cc..25ced366 100644 --- a/server/models/commentProviders.js +++ b/server/models/commentProviders.js @@ -36,65 +36,27 @@ module.exports = class CommentProvider extends Model { static async getProviders(isEnabled) { const providers = await WIKI.models.commentProviders.query().where(_.isBoolean(isEnabled) ? { isEnabled } : {}) - return _.sortBy(providers, ['key']) + return _.sortBy(providers, ['module']) } static async refreshProvidersFromDisk() { - let trx try { - const dbProviders = await WIKI.models.commentProviders.query() - // -> Fetch definitions from disk - const commentDirs = await fs.readdir(path.join(WIKI.SERVERPATH, 'modules/comments')) - let diskProviders = [] - for (let dir of commentDirs) { + const commentsDirs = await fs.readdir(path.join(WIKI.SERVERPATH, 'modules/comments')) + WIKI.data.commentProviders = [] + for (const dir of commentsDirs) { const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/comments', dir, 'definition.yml'), 'utf8') - diskProviders.push(yaml.safeLoad(def)) + const defParsed = yaml.load(def) + defParsed.key = dir + defParsed.props = commonHelper.parseModuleProps(defParsed.props) + WIKI.data.commentProviders.push(defParsed) + WIKI.logger.debug(`Loaded comments provider module definition ${dir}: [ OK ]`) } - WIKI.data.commentProviders = diskProviders.map(provider => ({ - ...provider, - props: commonHelper.parseModuleProps(provider.props) - })) - let newProviders = [] - for (let provider of WIKI.data.commentProviders) { - if (!_.some(dbProviders, ['key', provider.key])) { - newProviders.push({ - key: provider.key, - isEnabled: provider.key === 'default', - config: _.transform(provider.props, (result, value, key) => { - _.set(result, key, value.default) - return result - }, {}) - }) - } else { - const providerConfig = _.get(_.find(dbProviders, ['key', provider.key]), 'config', {}) - await WIKI.models.commentProviders.query().patch({ - config: _.transform(provider.props, (result, value, key) => { - if (!_.has(result, key)) { - _.set(result, key, value.default) - } - return result - }, providerConfig) - }).where('key', provider.key) - } - } - if (newProviders.length > 0) { - trx = await WIKI.models.Objection.transaction.start(WIKI.models.knex) - for (let provider of newProviders) { - await WIKI.models.commentProviders.query(trx).insert(provider) - } - await trx.commit() - WIKI.logger.info(`Loaded ${newProviders.length} new comment providers: [ OK ]`) - } else { - WIKI.logger.info(`No new comment providers found: [ SKIPPED ]`) - } + WIKI.logger.info(`Loaded ${WIKI.data.commentProviders.length} comments providers module definitions: [ OK ]`) } catch (err) { - WIKI.logger.error(`Failed to scan or load new comment providers: [ FAILED ]`) + WIKI.logger.error(`Failed to scan or load comments providers: [ FAILED ]`) WIKI.logger.error(err) - if (trx) { - trx.rollback() - } } } @@ -102,7 +64,7 @@ module.exports = class CommentProvider extends Model { const commentProvider = await WIKI.models.commentProviders.query().findOne('isEnabled', true) if (commentProvider) { WIKI.data.commentProvider = { - ..._.find(WIKI.data.commentProviders, ['key', commentProvider.key]), + ..._.find(WIKI.data.commentProviders, ['key', commentProvider.module]), head: '', bodyStart: '', bodyEnd: '', diff --git a/server/models/editors.js b/server/models/editors.js index 8a05ba98..092e1969 100644 --- a/server/models/editors.js +++ b/server/models/editors.js @@ -1,9 +1,4 @@ const Model = require('objection').Model -const fs = require('fs-extra') -const path = require('path') -const _ = require('lodash') -const yaml = require('js-yaml') -const commonHelper = require('../helpers/common') /* global WIKI */ @@ -34,66 +29,6 @@ module.exports = class Editor extends Model { return WIKI.models.editors.query() } - static async refreshEditorsFromDisk() { - let trx - try { - const dbEditors = await WIKI.models.editors.query() - - // -> Fetch definitions from disk - const editorDirs = await fs.readdir(path.join(WIKI.SERVERPATH, 'modules/editor')) - let diskEditors = [] - for (let dir of editorDirs) { - const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/editor', dir, 'definition.yml'), 'utf8') - diskEditors.push(yaml.safeLoad(def)) - } - WIKI.data.editors = diskEditors.map(editor => ({ - ...editor, - props: commonHelper.parseModuleProps(editor.props) - })) - - // -> Insert new editors - let newEditors = [] - for (let editor of WIKI.data.editors) { - if (!_.some(dbEditors, ['key', editor.key])) { - newEditors.push({ - key: editor.key, - isEnabled: false, - config: _.transform(editor.props, (result, value, key) => { - _.set(result, key, value.default) - return result - }, {}) - }) - } else { - const editorConfig = _.get(_.find(dbEditors, ['key', editor.key]), 'config', {}) - await WIKI.models.editors.query().patch({ - config: _.transform(editor.props, (result, value, key) => { - if (!_.has(result, key)) { - _.set(result, key, value.default) - } - return result - }, editorConfig) - }).where('key', editor.key) - } - } - if (newEditors.length > 0) { - trx = await WIKI.models.Objection.transaction.start(WIKI.models.knex) - for (let editor of newEditors) { - await WIKI.models.editors.query(trx).insert(editor) - } - await trx.commit() - WIKI.logger.info(`Loaded ${newEditors.length} new editors: [ OK ]`) - } else { - WIKI.logger.info(`No new editors found: [ SKIPPED ]`) - } - } catch (err) { - WIKI.logger.error(`Failed to scan or load new editors: [ FAILED ]`) - WIKI.logger.error(err) - if (trx) { - trx.rollback() - } - } - } - static async getDefaultEditor(contentType) { // TODO - hardcoded for now switch (contentType) { diff --git a/server/models/pages.js b/server/models/pages.js index 72c063fa..50e2c539 100644 --- a/server/models/pages.js +++ b/server/models/pages.js @@ -41,7 +41,7 @@ module.exports = class Page extends Model { hash: {type: 'string'}, title: {type: 'string'}, description: {type: 'string'}, - isPublished: {type: 'boolean'}, + publishState: {type: 'string'}, privateNS: {type: 'string'}, publishStartDate: {type: 'string'}, publishEndDate: {type: 'string'}, @@ -96,14 +96,6 @@ module.exports = class Page extends Model { to: 'users.id' } }, - editor: { - relation: Model.BelongsToOneRelation, - modelClass: require('./editors'), - join: { - from: 'pages.editorKey', - to: 'editors.key' - } - }, locale: { relation: Model.BelongsToOneRelation, modelClass: require('./locales'), @@ -143,16 +135,14 @@ module.exports = class Page extends Model { creatorId: 'uint', creatorName: 'string', description: 'string', - editorKey: 'string', - isPrivate: 'boolean', - isPublished: 'boolean', + editor: 'string', + publishState: 'string', publishEndDate: 'string', publishStartDate: 'string', render: 'string', tags: [ { - tag: 'string', - title: 'string' + tag: 'string' } ], extra: { @@ -301,10 +291,9 @@ module.exports = class Page extends Model { creatorId: opts.user.id, contentType: _.get(_.find(WIKI.data.editors, ['key', opts.editor]), `contentType`, 'text'), description: opts.description, - editorKey: opts.editor, - hash: pageHelper.generateHash({ path: opts.path, locale: opts.locale, privateNS: opts.isPrivate ? 'TODO' : '' }), - isPrivate: opts.isPrivate, - isPublished: opts.isPublished, + editor: opts.editor, + hash: pageHelper.generateHash({ path: opts.path, locale: opts.locale }), + publishState: opts.publishState, localeCode: opts.locale, path: opts.path, publishEndDate: opts.publishEndDate || '', @@ -319,8 +308,7 @@ module.exports = class Page extends Model { const page = await WIKI.models.pages.getPageFromDb({ path: opts.path, locale: opts.locale, - userId: opts.user.id, - isPrivate: opts.isPrivate + userId: opts.user.id }) // -> Save Tags @@ -389,7 +377,6 @@ module.exports = class Page extends Model { // -> Create version snapshot await WIKI.models.pageHistory.addVersion({ ...ogPage, - isPublished: ogPage.isPublished === true || ogPage.isPublished === 1, action: opts.action ? opts.action : 'updated', versionDate: ogPage.updatedAt }) @@ -426,7 +413,7 @@ module.exports = class Page extends Model { authorId: opts.user.id, content: opts.content, description: opts.description, - isPublished: opts.isPublished === true || opts.isPublished === 1, + publishState: opts.publishState, publishEndDate: opts.publishEndDate || '', publishStartDate: opts.publishStartDate || '', title: opts.title, @@ -500,7 +487,7 @@ module.exports = class Page extends Model { throw new Error('Invalid Page Id') } - if (ogPage.editorKey === opts.editor) { + if (ogPage.editor === opts.editor) { throw new Error('Page is already using this editor. Nothing to convert.') } @@ -631,7 +618,6 @@ module.exports = class Page extends Model { if (shouldConvert) { await WIKI.models.pageHistory.addVersion({ ...ogPage, - isPublished: ogPage.isPublished === true || ogPage.isPublished === 1, action: 'updated', versionDate: ogPage.updatedAt }) @@ -640,7 +626,7 @@ module.exports = class Page extends Model { // -> Update page await WIKI.models.pages.query().patch({ contentType: targetContentType, - editorKey: opts.editor, + editor: opts.editor, ...(convertedContent ? { content: convertedContent } : {}) }).where('id', ogPage.id) const page = await WIKI.models.pages.getPageFromDb(ogPage.id) @@ -721,7 +707,7 @@ module.exports = class Page extends Model { versionDate: page.updatedAt }) - const destinationHash = pageHelper.generateHash({ path: opts.destinationPath, locale: opts.destinationLocale, privateNS: opts.isPrivate ? 'TODO' : '' }) + const destinationHash = pageHelper.generateHash({ path: opts.destinationPath, locale: opts.destinationLocale }) // -> Move page const destinationTitle = (page.title === page.path ? opts.destinationPath : page.title) @@ -991,9 +977,7 @@ module.exports = class Page extends Model { 'pages.hash', 'pages.title', 'pages.description', - 'pages.isPrivate', - 'pages.isPublished', - 'pages.privateNS', + 'pages.publishState', 'pages.publishStartDate', 'pages.publishEndDate', 'pages.content', @@ -1002,7 +986,7 @@ module.exports = class Page extends Model { 'pages.contentType', 'pages.createdAt', 'pages.updatedAt', - 'pages.editorKey', + 'pages.editor', 'pages.localeCode', 'pages.authorId', 'pages.creatorId', @@ -1018,7 +1002,7 @@ module.exports = class Page extends Model { .joinRelated('creator') .withGraphJoined('tags') .modifyGraph('tags', builder => { - builder.select('tag', 'title') + builder.select('tag') }) .where(queryModeID ? { 'pages.id': opts @@ -1066,17 +1050,16 @@ module.exports = class Page extends Model { creatorId: page.creatorId, creatorName: page.creatorName, description: page.description, - editorKey: page.editorKey, + editor: page.editor, extra: { css: _.get(page, 'extra.css', ''), js: _.get(page, 'extra.js', '') }, - isPrivate: page.isPrivate === 1 || page.isPrivate === true, - isPublished: page.isPublished === 1 || page.isPublished === true, + publishState: page.publishState, publishEndDate: page.publishEndDate, publishStartDate: page.publishStartDate, render: page.render, - tags: page.tags.map(t => _.pick(t, ['tag', 'title'])), + tags: page.tags.map(t => _.pick(t, ['tag'])), title: page.title, toc: _.isString(page.toc) ? page.toc : JSON.stringify(page.toc), updatedAt: page.updatedAt @@ -1090,7 +1073,7 @@ module.exports = class Page extends Model { * @returns {Promise} Promise of the Page Model Instance */ static async getPageFromCache(opts) { - const pageHash = pageHelper.generateHash({ path: opts.path, locale: opts.locale, privateNS: opts.isPrivate ? 'TODO' : '' }) + const pageHash = pageHelper.generateHash({ path: opts.path, locale: opts.locale }) const cachePath = path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `cache/${pageHash}.bin`) try { diff --git a/server/models/renderers.js b/server/models/renderers.js index 379bc76a..948991bf 100644 --- a/server/models/renderers.js +++ b/server/models/renderers.js @@ -13,15 +13,15 @@ const commonHelper = require('../helpers/common') */ module.exports = class Renderer extends Model { static get tableName() { return 'renderers' } - static get idColumn() { return 'key' } static get jsonSchema () { return { type: 'object', - required: ['key', 'isEnabled'], + required: ['module', 'isEnabled'], properties: { - key: {type: 'string'}, + id: {type: 'string'}, + module: {type: 'string'}, isEnabled: {type: 'boolean'} } } @@ -36,77 +36,35 @@ module.exports = class Renderer extends Model { } static async fetchDefinitions() { - const rendererDirs = await fs.readdir(path.join(WIKI.SERVERPATH, 'modules/rendering')) - let diskRenderers = [] - for (let dir of rendererDirs) { - const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/rendering', dir, 'definition.yml'), 'utf8') - diskRenderers.push(yaml.safeLoad(def)) - } - WIKI.data.renderers = diskRenderers.map(renderer => ({ - ...renderer, - props: commonHelper.parseModuleProps(renderer.props) - })) - } - - static async refreshRenderersFromDisk() { - let trx try { - const dbRenderers = await WIKI.models.renderers.query() - // -> Fetch definitions from disk - await WIKI.models.renderers.fetchDefinitions() - - // -> Insert new Renderers - let newRenderers = [] - for (let renderer of WIKI.data.renderers) { - if (!_.some(dbRenderers, ['key', renderer.key])) { - newRenderers.push({ - key: renderer.key, - isEnabled: _.get(renderer, 'enabledDefault', true), - config: _.transform(renderer.props, (result, value, key) => { - _.set(result, key, value.default) - return result - }, {}) - }) - } else { - const rendererConfig = _.get(_.find(dbRenderers, ['key', renderer.key]), 'config', {}) - await WIKI.models.renderers.query().patch({ - config: _.transform(renderer.props, (result, value, key) => { - if (!_.has(result, key)) { - _.set(result, key, value.default) - } - return result - }, rendererConfig) - }).where('key', renderer.key) - } - } - if (newRenderers.length > 0) { - trx = await WIKI.models.Objection.transaction.start(WIKI.models.knex) - for (let renderer of newRenderers) { - await WIKI.models.renderers.query(trx).insert(renderer) - } - await trx.commit() - WIKI.logger.info(`Loaded ${newRenderers.length} new renderers: [ OK ]`) - } else { - WIKI.logger.info(`No new renderers found: [ SKIPPED ]`) + const renderersDirs = await fs.readdir(path.join(WIKI.SERVERPATH, 'modules/rendering')) + WIKI.data.renderers = [] + for (const dir of renderersDirs) { + const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/rendering', dir, 'definition.yml'), 'utf8') + const defParsed = yaml.load(def) + defParsed.key = dir + defParsed.props = commonHelper.parseModuleProps(defParsed.props) + WIKI.data.renderers.push(defParsed) + WIKI.logger.debug(`Loaded renderers module definition ${dir}: [ OK ]`) } - // -> Delete removed Renderers - for (const renderer of dbRenderers) { - if (!_.some(WIKI.data.renderers, ['key', renderer.key])) { - await WIKI.models.renderers.query().where('key', renderer.key).del() - WIKI.logger.info(`Removed renderer ${renderer.key} because it is no longer present in the modules folder: [ OK ]`) - } - } + WIKI.logger.info(`Loaded ${WIKI.data.renderers.length} renderers module definitions: [ OK ]`) } catch (err) { - WIKI.logger.error(`Failed to scan or load new renderers: [ FAILED ]`) + WIKI.logger.error(`Failed to scan or load renderers providers: [ FAILED ]`) WIKI.logger.error(err) - if (trx) { - trx.rollback() - } } } + static async refreshRenderersFromDisk() { + // const dbRenderers = await WIKI.models.renderers.query() + + // -> Fetch definitions from disk + await WIKI.models.renderers.fetchDefinitions() + + // TODO: Merge existing configs with updated modules + } + static async getRenderingPipeline(contentType) { const renderersDb = await WIKI.models.renderers.query().where('isEnabled', true) if (renderersDb && renderersDb.length > 0) { diff --git a/server/models/sites.js b/server/models/sites.js new file mode 100644 index 00000000..f53a72f5 --- /dev/null +++ b/server/models/sites.js @@ -0,0 +1,108 @@ +const Model = require('objection').Model +const crypto = require('crypto') +const pem2jwk = require('pem-jwk').pem2jwk +const _ = require('lodash') + +/* global WIKI */ + +/** + * Site model + */ +module.exports = class Site extends Model { + static get tableName () { return 'sites' } + + static get jsonSchema () { + return { + type: 'object', + required: ['hostname'], + + properties: { + id: { type: 'string' }, + hostname: { type: 'string' }, + isEnabled: { type: 'boolean', default: false } + } + } + } + + static get jsonAttributes () { + return ['config'] + } + + static async createSite (hostname, config) { + const newSite = await WIKI.models.sites.query().insertAndFetch({ + hostname, + isEnabled: true, + config: _.defaultsDeep(config, { + title: 'My Wiki Site', + description: '', + company: '', + contentLicense: '', + defaults: { + timezone: 'America/New_York', + dateFormat: 'YYYY-MM-DD', + timeFormat: '12h' + }, + features: { + ratings: false, + ratingsMode: 'off', + comments: false, + contributions: false, + profile: true, + search: true + }, + logoUrl: '', + logoText: true, + robots: { + index: true, + follow: true + }, + locale: 'en', + localeNamespacing: false, + localeNamespaces: [], + theme: { + dark: false, + colorPrimary: '#1976d2', + colorSecondary: '#02c39a', + colorAccent: '#f03a47', + colorHeader: '#000000', + colorSidebar: '#1976d2', + injectCSS: '', + injectHead: '', + injectBody: '', + sidebarPosition: 'left', + tocPosition: 'right', + showSharingMenu: true, + showPrintBtn: true + } + }) + }) + + await WIKI.models.storage.query().insert({ + module: 'db', + siteId: newSite.id, + isEnabled: true, + contentTypes: { + activeTypes: ['pages', 'images', 'documents', 'others', 'large'], + largeThreshold: '5MB' + }, + assetDelivery: { + streaming: true, + directAccess: false + }, + state: { + current: 'ok' + } + }) + + return newSite + } + + static async updateSite (id, patch) { + return WIKI.models.sites.query().findById(id).patch(patch) + } + + static async deleteSite (id) { + await WIKI.models.storage.query().delete().where('siteId', id) + return WIKI.models.sites.query().deleteById(id) + } +} diff --git a/server/models/storage.js b/server/models/storage.js index a982b7ec..c20ac6b2 100644 --- a/server/models/storage.js +++ b/server/models/storage.js @@ -17,93 +17,45 @@ module.exports = class Storage extends Model { static get jsonSchema () { return { type: 'object', - required: ['key', 'isEnabled'], + required: ['module', 'isEnabled', 'siteId'], properties: { - key: {type: 'string'}, + module: {type: 'string'}, isEnabled: {type: 'boolean'}, - mode: {type: 'string'} + SVGAnimatedInteger: {type: 'string'} } } } static get jsonAttributes() { - return ['config', 'state'] + return ['contentTypes', 'assetDelivery', 'versioning', 'schedule', 'config', 'state'] } - static async getTargets() { - return WIKI.models.storage.query() + static async getTargets ({ siteId }) { + return WIKI.models.storage.query().where(builder => { + if (siteId) { + builder.where('siteId', siteId) + } + }) } - static async refreshTargetsFromDisk() { + static async refreshTargetsFromDisk () { let trx try { - const dbTargets = await WIKI.models.storage.query() - // -> Fetch definitions from disk const storageDirs = await fs.readdir(path.join(WIKI.SERVERPATH, 'modules/storage')) - let diskTargets = [] - for (let dir of storageDirs) { + WIKI.storage.defs = [] + for (const dir of storageDirs) { const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/storage', dir, 'definition.yml'), 'utf8') - diskTargets.push(yaml.safeLoad(def)) - } - WIKI.data.storage = diskTargets.map(target => ({ - ...target, - isAvailable: _.get(target, 'isAvailable', false), - props: commonHelper.parseModuleProps(target.props) - })) - - // -> Insert new targets - let newTargets = [] - for (let target of WIKI.data.storage) { - if (!_.some(dbTargets, ['key', target.key])) { - newTargets.push({ - key: target.key, - isEnabled: false, - mode: target.defaultMode || 'push', - syncInterval: target.schedule || 'P0D', - config: _.transform(target.props, (result, value, key) => { - _.set(result, key, value.default) - return result - }, {}), - state: { - status: 'pending', - message: '', - lastAttempt: null - } - }) - } else { - const targetConfig = _.get(_.find(dbTargets, ['key', target.key]), 'config', {}) - await WIKI.models.storage.query().patch({ - config: _.transform(target.props, (result, value, key) => { - if (!_.has(result, key)) { - _.set(result, key, value.default) - } - return result - }, targetConfig) - }).where('key', target.key) - } - } - if (newTargets.length > 0) { - trx = await WIKI.models.Objection.transaction.start(WIKI.models.knex) - for (let target of newTargets) { - await WIKI.models.storage.query(trx).insert(target) - } - await trx.commit() - WIKI.logger.info(`Loaded ${newTargets.length} new storage targets: [ OK ]`) - } else { - WIKI.logger.info(`No new storage targets found: [ SKIPPED ]`) - } - - // -> Delete removed targets - for (const target of dbTargets) { - if (!_.some(WIKI.data.storage, ['key', target.key])) { - await WIKI.models.storage.query().where('key', target.key).del() - WIKI.logger.info(`Removed target ${target.key} because it is no longer present in the modules folder: [ OK ]`) - } + const defParsed = yaml.load(def) + defParsed.key = dir + defParsed.isLoaded = false + WIKI.storage.defs.push(defParsed) + WIKI.logger.debug(`Loaded storage module definition ${dir}: [ OK ]`) } + WIKI.logger.info(`Loaded ${WIKI.storage.defs.length} storage module definitions: [ OK ]`) } catch (err) { - WIKI.logger.error(`Failed to scan or load new storage providers: [ FAILED ]`) + WIKI.logger.error('Failed to scan or load new storage providers: [ FAILED ]') WIKI.logger.error(err) if (trx) { trx.rollback() @@ -111,66 +63,91 @@ module.exports = class Storage extends Model { } } + /** + * Ensure a storage module is loaded + */ + static async ensureModule (moduleName) { + if (!_.has(WIKI.storage.modules, moduleName)) { + try { + WIKI.storage.modules[moduleName] = require(`../modules/storage/${moduleName}/storage`) + WIKI.logger.debug(`Activated storage module ${moduleName}: [ OK ]`) + return true + } catch (err) { + WIKI.logger.warn(`Failed to load storage module ${moduleName}: [ FAILED ]`) + WIKI.logger.warn(err) + return false + } + } else { + return true + } + } + /** * Initialize active storage targets */ - static async initTargets() { - this.targets = await WIKI.models.storage.query().where('isEnabled', true).orderBy('key') + static async initTargets () { + const dbTargets = await WIKI.models.storage.query().where('isEnabled', true) + const activeModules = _.uniq(dbTargets.map(t => t.module)) try { // -> Stop and delete existing jobs - const prevjobs = _.remove(WIKI.scheduler.jobs, job => job.name === `sync-storage`) - if (prevjobs.length > 0) { - prevjobs.forEach(job => job.stop()) + // const prevjobs = _.remove(WIKI.scheduler.jobs, job => job.name === 'sync-storage') + // if (prevjobs.length > 0) { + // prevjobs.forEach(job => job.stop()) + // } + + // -> Load active modules + for (const md of activeModules) { + this.ensureModule(md) } // -> Initialize targets - for (let target of this.targets) { - const targetDef = _.find(WIKI.data.storage, ['key', target.key]) - target.fn = require(`../modules/storage/${target.key}/storage`) - target.fn.config = target.config - target.fn.mode = target.mode - try { - await target.fn.init() - - // -> Save succeeded init state - await WIKI.models.storage.query().patch({ - state: { - status: 'operational', - message: '', - lastAttempt: new Date().toISOString() - } - }).where('key', target.key) - - // -> Set recurring sync job - if (targetDef.schedule && target.syncInterval !== `P0D`) { - WIKI.scheduler.registerJob({ - name: `sync-storage`, - immediate: false, - schedule: target.syncInterval, - repeat: true - }, target.key) - } - - // -> Set internal recurring sync job - if (targetDef.internalSchedule && targetDef.internalSchedule !== `P0D`) { - WIKI.scheduler.registerJob({ - name: `sync-storage`, - immediate: false, - schedule: target.internalSchedule, - repeat: true - }, target.key) - } - } catch (err) { - // -> Save initialization error - await WIKI.models.storage.query().patch({ - state: { - status: 'error', - message: err.message, - lastAttempt: new Date().toISOString() - } - }).where('key', target.key) - } - } + // for (const target of this.targets) { + // const targetDef = _.find(WIKI.data.storage, ['key', target.key]) + // target.fn = require(`../modules/storage/${target.key}/storage`) + // target.fn.config = target.config + // target.fn.mode = target.mode + // try { + // await target.fn.init() + + // // -> Save succeeded init state + // await WIKI.models.storage.query().patch({ + // state: { + // status: 'operational', + // message: '', + // lastAttempt: new Date().toISOString() + // } + // }).where('key', target.key) + + // // -> Set recurring sync job + // if (targetDef.schedule && target.syncInterval !== 'P0D') { + // WIKI.scheduler.registerJob({ + // name: 'sync-storage', + // immediate: false, + // schedule: target.syncInterval, + // repeat: true + // }, target.key) + // } + + // // -> Set internal recurring sync job + // if (targetDef.internalSchedule && targetDef.internalSchedule !== 'P0D') { + // WIKI.scheduler.registerJob({ + // name: 'sync-storage', + // immediate: false, + // schedule: target.internalSchedule, + // repeat: true + // }, target.key) + // } + // } catch (err) { + // // -> Save initialization error + // await WIKI.models.storage.query().patch({ + // state: { + // status: 'error', + // message: err.message, + // lastAttempt: new Date().toISOString() + // } + // }).where('key', target.key) + // } + // } } catch (err) { WIKI.logger.warn(err) throw err diff --git a/server/models/tags.js b/server/models/tags.js index 0794fb64..5b9dba58 100644 --- a/server/models/tags.js +++ b/server/models/tags.js @@ -17,7 +17,6 @@ module.exports = class Tag extends Model { properties: { id: {type: 'integer'}, tag: {type: 'string'}, - title: {type: 'string'}, createdAt: {type: 'string'}, updatedAt: {type: 'string'} @@ -59,10 +58,7 @@ module.exports = class Tag extends Model { // Create missing tags - const newTags = _.filter(tags, t => !_.some(existingTags, ['tag', t])).map(t => ({ - tag: t, - title: t - })) + const newTags = _.filter(tags, t => !_.some(existingTags, ['tag', t])).map(t => ({ tag: t })) if (newTags.length > 0) { if (WIKI.config.db.type === 'postgres') { const createdTags = await WIKI.models.tags.query().insert(newTags) diff --git a/server/models/users.js b/server/models/users.js index facb158a..490ea3ea 100644 --- a/server/models/users.js +++ b/server/models/users.js @@ -861,7 +861,7 @@ module.exports = class User extends Model { * Logout the current user */ static async logout (context) { - if (!context.req.user || context.req.user.id === 2) { + if (!context.req.user || context.req.user.id === WIKI.config.auth.guestUserId) { return '/' } const usr = await WIKI.models.users.query().findById(context.req.user.id).select('providerKey') @@ -870,7 +870,7 @@ module.exports = class User extends Model { } static async getGuestUser () { - const user = await WIKI.models.users.query().findById(2).withGraphJoined('groups').modifyGraph('groups', builder => { + const user = await WIKI.models.users.query().findById(WIKI.config.auth.guestUserId).withGraphJoined('groups').modifyGraph('groups', builder => { builder.select('groups.id', 'permissions') }) if (!user) { @@ -882,7 +882,7 @@ module.exports = class User extends Model { } static async getRootUser () { - let user = await WIKI.models.users.query().findById(1) + let user = await WIKI.models.users.query().findById(WIKI.config.auth.rootAdminUserId) if (!user) { WIKI.logger.error('CRITICAL ERROR: Root Administrator user is missing!') process.exit(1) diff --git a/server/modules/editor/api/definition.yml b/server/modules/editor/api/definition.yml deleted file mode 100644 index 6a81522d..00000000 --- a/server/modules/editor/api/definition.yml +++ /dev/null @@ -1,6 +0,0 @@ -key: api -title: API Docs -description: REST / GraphQL Editor -contentType: yml -author: requarks.io -props: {} diff --git a/server/modules/editor/ckeditor/definition.yml b/server/modules/editor/ckeditor/definition.yml deleted file mode 100644 index f36f704c..00000000 --- a/server/modules/editor/ckeditor/definition.yml +++ /dev/null @@ -1,6 +0,0 @@ -key: ckeditor -title: Visual Editor -description: Rich-text WYSIWYG Editor -contentType: html -author: requarks.io -props: {} diff --git a/server/modules/editor/code/definition.yml b/server/modules/editor/code/definition.yml deleted file mode 100644 index 40604b05..00000000 --- a/server/modules/editor/code/definition.yml +++ /dev/null @@ -1,6 +0,0 @@ -key: code -title: Code -description: Raw HTML editor -contentType: html -author: requarks.io -props: {} diff --git a/server/modules/editor/markdown/definition.yml b/server/modules/editor/markdown/definition.yml deleted file mode 100644 index 25304ae4..00000000 --- a/server/modules/editor/markdown/definition.yml +++ /dev/null @@ -1,6 +0,0 @@ -key: markdown -title: Markdown -description: Basic Markdown editor -contentType: markdown -author: requarks.io -props: {} diff --git a/server/modules/editor/redirect/definition.yml b/server/modules/editor/redirect/definition.yml deleted file mode 100644 index ad410522..00000000 --- a/server/modules/editor/redirect/definition.yml +++ /dev/null @@ -1,6 +0,0 @@ -key: redirect -title: Redirection -description: Redirect the user -contentType: redirect -author: requarks.io -props: {} diff --git a/server/modules/editor/wysiwyg/definition.yml b/server/modules/editor/wysiwyg/definition.yml deleted file mode 100644 index e84ec3f5..00000000 --- a/server/modules/editor/wysiwyg/definition.yml +++ /dev/null @@ -1,6 +0,0 @@ -key: wysiwyg -title: WYSIWYG -description: Advanced Visual HTML Builder -contentType: html -author: requarks.io -props: {} diff --git a/server/modules/storage/azure/definition.yml b/server/modules/storage/azure/definition.yml index 13eb92e0..4b8f1301 100644 --- a/server/modules/storage/azure/definition.yml +++ b/server/modules/storage/azure/definition.yml @@ -1,44 +1,56 @@ -key: azure title: Azure Blob Storage -description: Azure Blob Storage by Microsoft provides massively scalable object storage for unstructured data. -author: requarks.io -logo: https://static.requarks.io/logo/azure.svg -website: https://azure.microsoft.com/services/storage/blobs/ -isAvailable: true -supportedModes: - - push -defaultMode: push -schedule: false +icon: '/_assets/icons/ultraviolet-azure.svg' +banner: '/_assets/storage/azure.jpg' +description: Azure Blob Storage is Microsoft's object storage solution for the cloud. Blob storage is optimized for storing massive amounts of unstructured data. +vendor: Microsoft Corporation +website: 'https://azure.microsoft.com' +assetDelivery: + isStreamingSupported: true + isDirectAccessSupported: true + defaultStreamingEnabled: true + defaultDirectAccessEnabled: true +contentTypes: + defaultTypesEnabled: ['images', 'documents', 'others', 'large'] + defaultLargeThreshold: '5MB' +versioning: + isSupported: false + defaultEnabled: false +sync: false props: accountName: type: String title: Account Name default: '' hint: Your unique account name. + icon: 3d-touch order: 1 accountKey: type: String title: Account Access Key default: '' hint: Either key 1 or key 2. + icon: key sensitive: true order: 2 containerName: type: String title: Container Name - default: 'wiki' + default: wiki hint: Will automatically be created if it doesn't exist yet. + icon: shipping-container order: 3 storageTier: type: String title: Storage Tier hint: Represents the access tier on a blob. Use Cool for lower storage costs but at higher retrieval costs. + icon: scan-stock order: 4 - default: 'Cool' + default: cool enum: - - 'Hot' - - 'Cool' + - hot|Hot + - cool|Cool actions: - handler: exportAll - label: Export All + label: Export All DB Assets to Azure hint: Output all content from the DB to Azure Blog Storage, overwriting any existing data. If you enabled Azure Blog Storage after content was created or you temporarily disabled it, you'll want to execute this action to add the missing content. + icon: this-way-up diff --git a/server/modules/storage/box/definition.yml b/server/modules/storage/box/definition.yml deleted file mode 100644 index 30276a39..00000000 --- a/server/modules/storage/box/definition.yml +++ /dev/null @@ -1,10 +0,0 @@ -key: box -title: Box -description: Box is a cloud content management and file sharing service for businesses. -author: requarks.io -logo: https://static.requarks.io/logo/box.svg -website: https://www.box.com/platform -props: - clientId: String - clientSecret: String - rootFolder: String diff --git a/server/modules/storage/box/storage.js b/server/modules/storage/box/storage.js deleted file mode 100644 index 8ae6cc29..00000000 --- a/server/modules/storage/box/storage.js +++ /dev/null @@ -1,26 +0,0 @@ -module.exports = { - async activated() { - - }, - async deactivated() { - - }, - async init() { - - }, - async created() { - - }, - async updated() { - - }, - async deleted() { - - }, - async renamed() { - - }, - async getLocalLocation () { - - } -} diff --git a/server/modules/storage/db/definition.yml b/server/modules/storage/db/definition.yml new file mode 100644 index 00000000..73d8b167 --- /dev/null +++ b/server/modules/storage/db/definition.yml @@ -0,0 +1,25 @@ +title: 'Database' +icon: '/_assets/icons/ultraviolet-database.svg' +banner: '/_assets/storage/database.jpg' +description: 'The local PostgreSQL database can store any assets. It is however not recommended to store large files directly in the database as this can cause performance issues.' +vendor: 'Wiki.js' +website: 'https://js.wiki' +assetDelivery: + isStreamingSupported: true + isDirectAccessSupported: false + defaultStreamingEnabled: true + defaultDirectAccessEnabled: false +contentTypes: + defaultTypesEnabled: ['pages', 'images', 'documents', 'others', 'large'] + defaultLargeThreshold: '5MB' +versioning: + isSupported: true + defaultEnabled: false +sync: false +props: {} +actions: + - handler: purge + label: Purge All Assets + hint: Delete all asset data from the database (not the metadata). Useful if you moved assets to another storage target and want to reduce the size of the database. + warn: This is a destructive action! Make sure all asset files are properly stored on another storage module! This action cannot be undone! + icon: explosion diff --git a/server/modules/storage/db/storage.js b/server/modules/storage/db/storage.js new file mode 100644 index 00000000..e4d24964 --- /dev/null +++ b/server/modules/storage/db/storage.js @@ -0,0 +1,14 @@ +module.exports = { + async activated () { }, + async deactivated () { }, + async init () { }, + async created (page) { }, + async updated (page) { }, + async deleted (page) { }, + async renamed (page) { }, + async assetUploaded (asset) { }, + async assetDeleted (asset) { }, + async assetRenamed (asset) { }, + async getLocalLocation () { }, + async exportAll () { } +} diff --git a/server/modules/storage/digitalocean/definition.yml b/server/modules/storage/digitalocean/definition.yml deleted file mode 100644 index eb95d3f1..00000000 --- a/server/modules/storage/digitalocean/definition.yml +++ /dev/null @@ -1,45 +0,0 @@ -key: digitalocean -title: DigitalOcean Spaces -description: DigitalOcean provides developers and businesses a reliable, easy-to-use cloud computing platform of virtual servers (Droplets), object storage (Spaces) and more. -author: andrewsim -logo: https://static.requarks.io/logo/digitalocean.svg -website: https://www.digitalocean.com/products/spaces/ -isAvailable: true -supportedModes: - - push -defaultMode: push -schedule: false -props: - endpoint: - type: String - title: Endpoint - hint: The DigitalOcean spaces endpoint that has the form ${REGION}.digitaloceanspaces.com - default: nyc3.digitaloceanspaces.com - enum: - - ams3.digitaloceanspaces.com - - fra1.digitaloceanspaces.com - - nyc3.digitaloceanspaces.com - - sfo2.digitaloceanspaces.com - - sgp1.digitaloceanspaces.com - order: 1 - bucket: - type: String - title: Space Unique Name - hint: The unique space name to create (e.g. wiki-johndoe) - order: 2 - accessKeyId: - type: String - title: Access Key ID - hint: The Access Key (Generated in API > Tokens/Keys > Spaces access keys). - order: 3 - secretAccessKey : - type: String - title: Access Key Secret - hint: The Access Key Secret for the Access Key ID you created above. - sensitive: true - order: 4 -actions: - - handler: exportAll - label: Export All - hint: Output all content from the DB to DigitalOcean Spaces, overwriting any existing data. If you enabled DigitalOcean Spaces after content was created or you temporarily disabled it, you'll want to execute this action to add the missing content. - diff --git a/server/modules/storage/digitalocean/storage.js b/server/modules/storage/digitalocean/storage.js deleted file mode 100644 index 672fb6bc..00000000 --- a/server/modules/storage/digitalocean/storage.js +++ /dev/null @@ -1,3 +0,0 @@ -const S3CompatibleStorage = require('../s3/common') - -module.exports = new S3CompatibleStorage('Digitalocean') diff --git a/server/modules/storage/disk/definition.yml b/server/modules/storage/disk/definition.yml index 357ab5cd..1f8a4916 100644 --- a/server/modules/storage/disk/definition.yml +++ b/server/modules/storage/disk/definition.yml @@ -1,34 +1,46 @@ -key: disk title: Local File System -description: Local storage on disk or network shares. -author: requarks.io -logo: https://static.requarks.io/logo/local-fs.svg -website: https://wiki.js.org -isAvailable: true -supportedModes: - - push -defaultMode: push -schedule: false +icon: '/_assets/icons/ultraviolet-hdd.svg' +banner: '/_assets/storage/disk.jpg' +description: Store files on the local file system or over network attached storage. Note that you must use replicated storage if using high-availability instances. +vendor: Wiki.js +website: 'https://js.wiki' +assetDelivery: + isStreamingSupported: true + isDirectAccessSupported: false + defaultStreamingEnabled: true + defaultDirectAccessEnabled: false +contentTypes: + defaultTypesEnabled: ['images', 'documents', 'others', 'large'] + defaultLargeThreshold: '5MB' +versioning: + isSupported: false + defaultEnabled: false +sync: false internalSchedule: P1D props: path: type: String title: Path hint: Absolute path without a trailing slash (e.g. /home/wiki/backup, C:\wiki\backup) + icon: symlink-directory order: 1 createDailyBackups: type: Boolean default: false title: Create Daily Backups hint: A tar.gz archive containing all content will be created daily in subfolder named _daily. Archives are kept for a month. + icon: archive-folder order: 2 actions: - handler: dump label: Dump all content to disk hint: Output all content from the DB to the local disk. If you enabled this module after content was created or you temporarily disabled this module, you'll want to execute this action to add the missing files. + icon: downloads - handler: backup label: Create Backup hint: Will create a manual backup archive at this point in time, in a subfolder named _manual, from the contents currently on disk. + icon: archive-folder - handler: importAll label: Import Everything hint: Will import all content currently in the local disk folder. + icon: database-daily-import diff --git a/server/modules/storage/disk/storage.js b/server/modules/storage/disk/storage.js index 1c3a7c40..8f4edce6 100644 --- a/server/modules/storage/disk/storage.js +++ b/server/modules/storage/disk/storage.js @@ -127,15 +127,12 @@ module.exports = { // -> Pages await pipeline( - WIKI.models.knex.column('id', 'path', 'localeCode', 'title', 'description', 'contentType', 'content', 'isPublished', 'updatedAt', 'createdAt', 'editorKey').select().from('pages').where({ + WIKI.models.knex.column('path', 'localeCode', 'title', 'description', 'contentType', 'content', 'isPublished', 'updatedAt', 'createdAt').select().from('pages').where({ isPrivate: false }).stream(), new stream.Transform({ objectMode: true, transform: async (page, enc, cb) => { - const pageObject = await WIKI.models.pages.query().findById(page.id) - page.tags = await pageObject.$relatedQuery('tags') - let fileName = `${page.path}.${pageHelper.getFileExtension(page.contentType)}` if (WIKI.config.lang.code !== page.localeCode) { fileName = `${page.localeCode}/${fileName}` diff --git a/server/modules/storage/dropbox/definition.yml b/server/modules/storage/dropbox/definition.yml deleted file mode 100644 index 74959570..00000000 --- a/server/modules/storage/dropbox/definition.yml +++ /dev/null @@ -1,9 +0,0 @@ -key: dropbox -title: Dropbox -description: Dropbox is a file hosting service that offers cloud storage, file synchronization, personal cloud, and client software. -author: requarks.io -logo: https://static.requarks.io/logo/dropbox.svg -website: https://dropbox.com -props: - appKey: String - appSecret: String diff --git a/server/modules/storage/dropbox/storage.js b/server/modules/storage/dropbox/storage.js deleted file mode 100644 index 8ae6cc29..00000000 --- a/server/modules/storage/dropbox/storage.js +++ /dev/null @@ -1,26 +0,0 @@ -module.exports = { - async activated() { - - }, - async deactivated() { - - }, - async init() { - - }, - async created() { - - }, - async updated() { - - }, - async deleted() { - - }, - async renamed() { - - }, - async getLocalLocation () { - - } -} diff --git a/server/modules/storage/gcs/definition.yml b/server/modules/storage/gcs/definition.yml new file mode 100644 index 00000000..47a9a614 --- /dev/null +++ b/server/modules/storage/gcs/definition.yml @@ -0,0 +1,65 @@ +title: Google Cloud Storage +icon: '/_assets/icons/ultraviolet-google.svg' +banner: '/_assets/storage/gcs.jpg' +description: Google Cloud Storage is an online file storage web service for storing and accessing data on Google Cloud Platform infrastructure. +vendor: Alphabet Inc. +website: 'https://cloud.google.com' +assetDelivery: + isStreamingSupported: true + isDirectAccessSupported: true + defaultStreamingEnabled: true + defaultDirectAccessEnabled: true +contentTypes: + defaultTypesEnabled: ['images', 'documents', 'others', 'large'] + defaultLargeThreshold: '5MB' +versioning: + isSupported: false + defaultEnabled: false +sync: false +props: + accountName: + type: String + title: Project ID + hint: The project ID from the Google Developer's Console (e.g. grape-spaceship-123). + icon: 3d-touch + default: '' + order: 1 + credentialsJSON: + type: String + title: JSON Credentials + hint: Contents of the JSON credentials file for the service account having Cloud Storage permissions. + icon: key + default: '' + multiline: true + sensitive: true + order: 2 + bucket: + type: String + title: Unique bucket name + hint: The unique bucket name to create (e.g. wiki-johndoe). + icon: open-box + order: 3 + storageTier: + type: String + title: Storage Tier + hint: Select the storage class to use when uploading new assets. + icon: scan-stock + order: 4 + default: STANDARD + enum: + - STANDARD|Standard + - NEARLINE|Nearline + - COLDLINE|Coldline + - ARCHIVE|Archive + apiEndpoint: + type: String + title: API Endpoint + hint: The API endpoint of the service used to make requests. + icon: api + default: storage.google.com + order: 5 +actions: + - handler: exportAll + label: Export All DB Assets to GCS + hint: Output all content from the DB to Google Cloud Storage, overwriting any existing data. If you enabled Google Cloud Storage after content was created or you temporarily disabled it, you'll want to execute this action to add the missing content. + icon: this-way-up diff --git a/server/modules/storage/gcs/storage.js b/server/modules/storage/gcs/storage.js new file mode 100644 index 00000000..f7320146 --- /dev/null +++ b/server/modules/storage/gcs/storage.js @@ -0,0 +1,164 @@ +const { BlobServiceClient, StorageSharedKeyCredential } = require('@azure/storage-blob') +const stream = require('stream') +const Promise = require('bluebird') +const pipeline = Promise.promisify(stream.pipeline) +const pageHelper = require('../../../helpers/page.js') +const _ = require('lodash') + +/* global WIKI */ + +const getFilePath = (page, pathKey) => { + const fileName = `${page[pathKey]}.${pageHelper.getFileExtension(page.contentType)}` + const withLocaleCode = WIKI.config.lang.namespacing && WIKI.config.lang.code !== page.localeCode + return withLocaleCode ? `${page.localeCode}/${fileName}` : fileName +} + +module.exports = { + async activated() { + + }, + async deactivated() { + + }, + async init() { + WIKI.logger.info(`(STORAGE/AZURE) Initializing...`) + const { accountName, accountKey, containerName } = this.config + this.client = new BlobServiceClient( + `https://${accountName}.blob.core.windows.net`, + new StorageSharedKeyCredential(accountName, accountKey) + ) + this.container = this.client.getContainerClient(containerName) + try { + await this.container.create() + } catch (err) { + if (err.statusCode !== 409) { + WIKI.logger.warn(err) + throw err + } + } + WIKI.logger.info(`(STORAGE/AZURE) Initialization completed.`) + }, + async created (page) { + WIKI.logger.info(`(STORAGE/AZURE) Creating file ${page.path}...`) + const filePath = getFilePath(page, 'path') + const pageContent = page.injectMetadata() + const blockBlobClient = this.container.getBlockBlobClient(filePath) + await blockBlobClient.upload(pageContent, pageContent.length, { tier: this.config.storageTier }) + }, + async updated (page) { + WIKI.logger.info(`(STORAGE/AZURE) Updating file ${page.path}...`) + const filePath = getFilePath(page, 'path') + const pageContent = page.injectMetadata() + const blockBlobClient = this.container.getBlockBlobClient(filePath) + await blockBlobClient.upload(pageContent, pageContent.length, { tier: this.config.storageTier }) + }, + async deleted (page) { + WIKI.logger.info(`(STORAGE/AZURE) Deleting file ${page.path}...`) + const filePath = getFilePath(page, 'path') + const blockBlobClient = this.container.getBlockBlobClient(filePath) + await blockBlobClient.delete({ + deleteSnapshots: 'include' + }) + }, + async renamed(page) { + WIKI.logger.info(`(STORAGE/${this.storageName}) Renaming file ${page.path} to ${page.destinationPath}...`) + let sourceFilePath = getFilePath(page, 'path') + let destinationFilePath = getFilePath(page, 'destinationPath') + if (WIKI.config.lang.namespacing) { + if (WIKI.config.lang.code !== page.localeCode) { + sourceFilePath = `${page.localeCode}/${sourceFilePath}` + } + if (WIKI.config.lang.code !== page.destinationLocaleCode) { + destinationFilePath = `${page.destinationLocaleCode}/${destinationFilePath}` + } + } + const sourceBlockBlobClient = this.container.getBlockBlobClient(sourceFilePath) + const destBlockBlobClient = this.container.getBlockBlobClient(destinationFilePath) + await destBlockBlobClient.syncCopyFromURL(sourceBlockBlobClient.url) + await sourceBlockBlobClient.delete({ + deleteSnapshots: 'include' + }) + }, + /** + * ASSET UPLOAD + * + * @param {Object} asset Asset to upload + */ + async assetUploaded (asset) { + WIKI.logger.info(`(STORAGE/AZURE) Creating new file ${asset.path}...`) + const blockBlobClient = this.container.getBlockBlobClient(asset.path) + await blockBlobClient.upload(asset.data, asset.data.length, { tier: this.config.storageTier }) + }, + /** + * ASSET DELETE + * + * @param {Object} asset Asset to delete + */ + async assetDeleted (asset) { + WIKI.logger.info(`(STORAGE/AZURE) Deleting file ${asset.path}...`) + const blockBlobClient = this.container.getBlockBlobClient(asset.path) + await blockBlobClient.delete({ + deleteSnapshots: 'include' + }) + }, + /** + * ASSET RENAME + * + * @param {Object} asset Asset to rename + */ + async assetRenamed (asset) { + WIKI.logger.info(`(STORAGE/AZURE) Renaming file from ${asset.path} to ${asset.destinationPath}...`) + const sourceBlockBlobClient = this.container.getBlockBlobClient(asset.path) + const destBlockBlobClient = this.container.getBlockBlobClient(asset.destinationPath) + await destBlockBlobClient.syncCopyFromURL(sourceBlockBlobClient.url) + await sourceBlockBlobClient.delete({ + deleteSnapshots: 'include' + }) + }, + async getLocalLocation () { + + }, + /** + * HANDLERS + */ + async exportAll() { + WIKI.logger.info(`(STORAGE/AZURE) Exporting all content to Azure Blob Storage...`) + + // -> Pages + await pipeline( + WIKI.models.knex.column('path', 'localeCode', 'title', 'description', 'contentType', 'content', 'isPublished', 'updatedAt', 'createdAt').select().from('pages').where({ + isPrivate: false + }).stream(), + new stream.Transform({ + objectMode: true, + transform: async (page, enc, cb) => { + const filePath = getFilePath(page, 'path') + WIKI.logger.info(`(STORAGE/AZURE) Adding page ${filePath}...`) + const pageContent = pageHelper.injectPageMetadata(page) + const blockBlobClient = this.container.getBlockBlobClient(filePath) + await blockBlobClient.upload(pageContent, pageContent.length, { tier: this.config.storageTier }) + cb() + } + }) + ) + + // -> Assets + const assetFolders = await WIKI.models.assetFolders.getAllPaths() + + await pipeline( + WIKI.models.knex.column('filename', 'folderId', 'data').select().from('assets').join('assetData', 'assets.id', '=', 'assetData.id').stream(), + new stream.Transform({ + objectMode: true, + transform: async (asset, enc, cb) => { + const filename = (asset.folderId && asset.folderId > 0) ? `${_.get(assetFolders, asset.folderId)}/${asset.filename}` : asset.filename + WIKI.logger.info(`(STORAGE/AZURE) Adding asset ${filename}...`) + const blockBlobClient = this.container.getBlockBlobClient(filename) + await blockBlobClient.upload(asset.data, asset.data.length, { tier: this.config.storageTier }) + cb() + } + }) + ) + + WIKI.logger.info('(STORAGE/AZURE) All content has been pushed to Azure Blob Storage.') + } +} diff --git a/server/modules/storage/gdrive/definition.yml b/server/modules/storage/gdrive/definition.yml deleted file mode 100644 index b19ea06b..00000000 --- a/server/modules/storage/gdrive/definition.yml +++ /dev/null @@ -1,9 +0,0 @@ -key: gdrive -title: Google Drive -description: Google Drive is a file storage and synchronization service developed by Google. -author: requarks.io -logo: https://static.requarks.io/logo/google-drive.svg -website: https://www.google.com/drive/ -props: - clientId: String - clientSecret: String diff --git a/server/modules/storage/gdrive/storage.js b/server/modules/storage/gdrive/storage.js deleted file mode 100644 index 8ae6cc29..00000000 --- a/server/modules/storage/gdrive/storage.js +++ /dev/null @@ -1,26 +0,0 @@ -module.exports = { - async activated() { - - }, - async deactivated() { - - }, - async init() { - - }, - async created() { - - }, - async updated() { - - }, - async deleted() { - - }, - async renamed() { - - }, - async getLocalLocation () { - - } -} diff --git a/server/modules/storage/git/definition.yml b/server/modules/storage/git/definition.yml index 63c095a2..de0647c1 100644 --- a/server/modules/storage/git/definition.yml +++ b/server/modules/storage/git/definition.yml @@ -1,108 +1,151 @@ -key: git title: Git -description: Git is a version control system for tracking changes in computer files and coordinating work on those files among multiple people. -author: requarks.io -logo: https://static.requarks.io/logo/git-alt.svg -website: https://git-scm.com/ -isAvailable: true -supportedModes: - - sync - - push - - pull -defaultMode: sync -schedule: PT5M +icon: '/_assets/icons/ultraviolet-git.svg' +banner: '/_assets/storage/git.jpg' +description: Git is a version control system for tracking changes in computer files and coordinating work on those files among multiple people. If using GitHub, use the GitHub module instead! +vendor: Software Freedom Conservancy, Inc. +website: 'https://git-scm.com' +assetDelivery: + isStreamingSupported: true + isDirectAccessSupported: false + defaultStreamingEnabled: true + defaultDirectAccessEnabled: false +contentTypes: + defaultTypesEnabled: ['pages', 'images', 'documents', 'others', 'large'] + defaultLargeThreshold: '5MB' +versioning: + isSupported: true + defaultEnabled: true + isForceEnabled: true +sync: + supportedModes: + - sync + - push + - pull + defaultMode: sync + schedule: PT5M props: authType: type: String default: 'ssh' title: Authentication Type hint: Use SSH for maximum security. + icon: security-configuration enum: - - 'basic' - - 'ssh' + - basic|Basic + - ssh|SSH + enumDisplay: buttons order: 1 repoUrl: type: String title: Repository URI - hint: Git-compliant URI (e.g. git@github.com:org/repo.git for ssh, https://github.com/org/repo.git for basic) + hint: Git-compliant URI (e.g. git@server.com:org/repo.git for ssh, https://server.com/org/repo.git for basic) + icon: dns order: 2 branch: type: String - default: 'master' + default: 'main' + title: Branch hint: The branch to use during pull / push + icon: code-fork order: 3 sshPrivateKeyMode: type: String title: SSH Private Key Mode - hint: SSH Authentication Only - The mode to use to load the private key. Fill in the corresponding field below. + hint: The mode to use to load the private key. Fill in the corresponding field below. + icon: grand-master-key order: 11 - default: 'path' + default: inline enum: - - 'path' - - 'contents' + - path|File Path + - inline|Inline Contents + enumDisplay: buttons + if: + - { key: 'authType', eq: 'ssh' } sshPrivateKeyPath: type: String - title: A - SSH Private Key Path - hint: SSH Authentication Only - Absolute path to the key. The key must NOT be passphrase-protected. Mode must be set to path to use this option. + title: SSH Private Key Path + hint: Absolute path to the key. The key must NOT be passphrase-protected. + icon: key order: 12 + if: + - { key: 'authType', eq: 'ssh' } + - { key: 'sshPrivateKeyMode', eq: 'path' } sshPrivateKeyContent: type: String - title: B - SSH Private Key Contents - hint: SSH Authentication Only - Paste the contents of the private key. The key must NOT be passphrase-protected. Mode must be set to contents to use this option. + title: SSH Private Key Contents + hint: Paste the contents of the private key. The key must NOT be passphrase-protected. + icon: key multiline: true sensitive: true order: 13 + if: + - { key: 'sshPrivateKeyMode', eq: 'inline' } verifySSL: type: Boolean default: true title: Verify SSL Certificate hint: Some hosts requires SSL certificate checking to be disabled. Leave enabled for proper security. + icon: security-ssl order: 14 basicUsername: type: String title: Username hint: Basic Authentication Only + icon: test-account order: 20 + if: + - { key: 'authType', eq: 'basic' } basicPassword: type: String title: Password / PAT hint: Basic Authentication Only + icon: password sensitive: true order: 21 + if: + - { key: 'authType', eq: 'basic' } defaultEmail: type: String title: Default Author Email default: 'name@company.com' hint: 'Used as fallback in case the author of the change is not present.' - order: 22 + icon: email + order: 30 defaultName: type: String title: Default Author Name default: 'John Smith' hint: 'Used as fallback in case the author of the change is not present.' - order: 23 + icon: customer + order: 31 localRepoPath: type: String title: Local Repository Path default: './data/repo' hint: 'Path where the local git repository will be created.' - order: 30 + icon: symlink-directory + order: 32 gitBinaryPath: type: String title: Git Binary Path default: '' hint: Optional - Absolute path to the Git binary, when not available in PATH. Leave empty to use the default PATH location (recommended). + icon: run-command order: 50 actions: - handler: syncUntracked label: Add Untracked Changes hint: Output all content from the DB to the local Git repository to ensure all untracked content is saved. If you enabled Git after content was created or you temporarily disabled Git, you'll want to execute this action to add the missing untracked changes. + icon: database-daily-export - handler: sync label: Force Sync hint: Will trigger an immediate sync operation, regardless of the current sync schedule. The sync direction is respected. + icon: synchronize - handler: importAll label: Import Everything hint: Will import all content currently in the local Git repository, regardless of the latest commit state. Useful for importing content from the remote repository created before git was enabled. + icon: database-daily-import - handler: purge label: Purge Local Repository hint: If you have unrelated merge histories, clearing the local repository can resolve this issue. This will not affect the remote repository or perform any commit. + icon: trash diff --git a/server/modules/storage/git/storage.js b/server/modules/storage/git/storage.js index ee8ce503..e9cf0b46 100644 --- a/server/modules/storage/git/storage.js +++ b/server/modules/storage/git/storage.js @@ -73,7 +73,7 @@ module.exports = { mode: 0o600 }) } catch (err) { - WIKI.logger.error(err) + console.error(err) throw err } } @@ -142,9 +142,7 @@ module.exports = { if (_.get(diff, 'files', []).length > 0) { let filesToProcess = [] for (const f of diff.files) { - const fMoved = f.file.split(' => ') - const fName = fMoved.length === 2 ? fMoved[1] : fMoved[0] - const fPath = path.join(this.repoPath, fName) + const fPath = path.join(this.repoPath, f.file) let fStats = { size: 0 } try { fStats = await fs.stat(fPath) @@ -161,8 +159,7 @@ module.exports = { path: fPath, stats: fStats }, - oldPath: fMoved[0], - relPath: fName + relPath: f.file }) } await this.processFiles(filesToProcess, rootUser) @@ -177,25 +174,11 @@ module.exports = { async processFiles(files, user) { for (const item of files) { const contentType = pageHelper.getContentType(item.relPath) - const fileExists = await fs.pathExists(item.file.path) + const fileExists = await fs.pathExists(item.file) if (!item.binary && contentType) { // -> Page - if (fileExists && !item.importAll && item.relPath !== item.oldPath) { - // Page was renamed by git, so rename in DB - WIKI.logger.info(`(STORAGE/GIT) Page marked as renamed: from ${item.oldPath} to ${item.relPath}`) - - const contentPath = pageHelper.getPagePath(item.oldPath) - const contentDestinationPath = pageHelper.getPagePath(item.relPath) - await WIKI.models.pages.movePage({ - user: user, - path: contentPath.path, - destinationPath: contentDestinationPath.path, - locale: contentPath.locale, - destinationLocale: contentPath.locale, - skipStorage: true - }) - } else if (!fileExists && !item.importAll && item.deletions > 0 && item.insertions === 0) { + if (!fileExists && item.deletions > 0 && item.insertions === 0) { // Page was deleted by git, can safely mark as deleted in DB WIKI.logger.info(`(STORAGE/GIT) Page marked as deleted: ${item.relPath}`) @@ -224,23 +207,7 @@ module.exports = { } else { // -> Asset - if (fileExists && !item.importAll && ((item.before === item.after) || (item.deletions === 0 && item.insertions === 0))) { - // Asset was renamed by git, so rename in DB - WIKI.logger.info(`(STORAGE/GIT) Asset marked as renamed: from ${item.oldPath} to ${item.relPath}`) - - const fileHash = assetHelper.generateHash(item.relPath) - const assetToRename = await WIKI.models.assets.query().findOne({ hash: fileHash }) - if (assetToRename) { - await WIKI.models.assets.query().patch({ - filename: item.relPath, - hash: fileHash - }).findById(assetToRename.id) - await assetToRename.deleteAssetCache() - } else { - WIKI.logger.info(`(STORAGE/GIT) Asset was not found in the DB, nothing to rename: ${item.relPath}`) - } - continue - } else if (!fileExists && !item.importAll && ((item.before > 0 && item.after === 0) || (item.deletions > 0 && item.insertions === 0))) { + if (!fileExists && ((item.before > 0 && item.after === 0) || (item.deletions > 0 && item.insertions === 0))) { // Asset was deleted by git, can safely mark as deleted in DB WIKI.logger.info(`(STORAGE/GIT) Asset marked as deleted: ${item.relPath}`) @@ -427,8 +394,7 @@ module.exports = { relPath, file, deletions: 0, - insertions: 0, - importAll: true + insertions: 0 }], rootUser) } cb() @@ -445,15 +411,12 @@ module.exports = { // -> Pages await pipeline( - WIKI.models.knex.column('id', 'path', 'localeCode', 'title', 'description', 'contentType', 'content', 'isPublished', 'updatedAt', 'createdAt', 'editorKey').select().from('pages').where({ + WIKI.models.knex.column('path', 'localeCode', 'title', 'description', 'contentType', 'content', 'isPublished', 'updatedAt', 'createdAt').select().from('pages').where({ isPrivate: false }).stream(), new stream.Transform({ objectMode: true, transform: async (page, enc, cb) => { - const pageObject = await WIKI.models.pages.query().findById(page.id) - page.tags = await pageObject.$relatedQuery('tags') - let fileName = `${page.path}.${pageHelper.getFileExtension(page.contentType)}` if (WIKI.config.lang.namespacing && WIKI.config.lang.code !== page.localeCode) { fileName = `${page.localeCode}/${fileName}` diff --git a/server/modules/storage/github/definition.yml b/server/modules/storage/github/definition.yml new file mode 100644 index 00000000..80496b15 --- /dev/null +++ b/server/modules/storage/github/definition.yml @@ -0,0 +1,49 @@ +title: GitHub +icon: '/_assets/icons/ultraviolet-github.svg' +banner: '/_assets/storage/github.jpg' +description: Millions of developers and companies build, ship, and maintain their software on GitHub - the largest and most advanced development platform in the world. +vendor: GitHub, Inc. +website: 'https://github.com' +assetDelivery: + isStreamingSupported: false + isDirectAccessSupported: false + defaultStreamingEnabled: false + defaultDirectAccessEnabled: false +contentTypes: + defaultTypesEnabled: ['pages', 'images', 'documents', 'others', 'large'] + defaultLargeThreshold: '5MB' +versioning: + isSupported: true + defaultEnabled: true + isForceEnabled: true +sync: false +setup: + handler: github + defaultValues: + accountType: org + org: '' + publicUrl: https:// +props: + appName: + readOnly: true + type: String + title: App Name + hint: Name of the generated app in GitHub. + icon: 3d-touch + repoFullName: + readOnly: true + type: String + title: GitHub Repository + hint: The GitHub repository used for content synchronization. + icon: github + repoDefaultBranch: + readOnly: true + type: String + title: Default Branch + hint: The repository default branch. + icon: code-fork +actions: + - handler: exportAll + label: Export All DB Assets to GitHub + hint: Output all content from the DB to GitHub, overwriting any existing data. If you enabled GitHub after content was created or you temporarily disabled it, you'll want to execute this action to add the missing content. + icon: this-way-up diff --git a/server/modules/storage/github/storage.js b/server/modules/storage/github/storage.js new file mode 100644 index 00000000..d00a9486 --- /dev/null +++ b/server/modules/storage/github/storage.js @@ -0,0 +1,211 @@ +const { Octokit, App } = require('octokit') + +/* global WIKI */ + +module.exports = { + async activated () { }, + async deactivated () { }, + async init () { }, + + /** + * SETUP FUNCTIONS + */ + async setup (id, state) { + try { + switch (state.step) { + // -------------------------------------------- + // -> VALIDATE CALLBACK CODE AFTER APP CREATION + // -------------------------------------------- + case 'connect': { + const gh = new Octokit({ + userAgent: 'wikijs' + }) + const resp = await gh.request('POST /app-manifests/{code}/conversions', { + code: state.code + }) + if (resp.status > 200 && resp.status < 300) { + await WIKI.models.storage.query().patch({ + config: { + appId: resp.data.id, + appName: resp.data.name, + appSlug: resp.data.slug, + appClientId: resp.data.client_id, + appClientSecret: resp.data.client_secret, + appWebhookSecret: resp.data.webhook_secret, + appPem: resp.data.pem, + appPermissions: resp.data.permissions, + appEvents: resp.data.events, + ownerLogin: resp.data.owner?.login, + ownerId: resp.data.owner?.id + }, + state: { + current: 'ok', + setup: 'pendinginstall' + } + }).where('id', id) + return { + nextStep: 'installApp', + url: `https://github.com/apps/${resp.data.slug}/installations/new/permissions?target_id=${resp.data.owner?.id}` + } + } else { + throw new Error('GitHub refused the code or could not be reached.') + } + } + // ----------------------- + // VERIFY APP INSTALLATION + // ----------------------- + case 'verify': { + const tgt = await WIKI.models.storage.query().findById(id) + if (!tgt) { + throw new Error('Invalid Target ID') + } + + const ghApp = new App({ + appId: tgt.config.appId, + privateKey: tgt.config.appPem, + Octokit: Octokit.defaults({ + userAgent: 'wikijs' + }), + oauth: { + clientId: tgt.config.appClientId, + clientSecret: tgt.config.appClientSecret + }, + webhooks: { + secret: tgt.config.appWebhookSecret + } + }) + + // -> Find Installation ID + + let installId = null + let installTotal = 0 + for await (const { installation } of ghApp.eachInstallation.iterator()) { + if (installTotal < 1) { + installId = installation.id + WIKI.logger.debug(`Using GitHub App installation ID ${installId}`) + } + installTotal++ + } + if (installTotal < 1) { + throw new Error('App is not installed on any GitHub account!') + } else if (installTotal > 1) { + WIKI.logger.warn(`GitHub App ${tgt.config.appName} is installed on more than 1 account. Only the first one ${installId} will be used.`) + } + + // -> Fetch Repository Info + + let repo = null + let repoTotal = 0 + for await (const { repository } of ghApp.eachRepository.iterator({ installationId: installId })) { + if (repository.archived || repository.disabled) { + WIKI.logger.debug(`Skipping GitHub Repository ${repo.id} because of it is archived or disabled.`) + continue + } + if (repoTotal < 1) { + repo = repository + WIKI.logger.debug(`Using GitHub Repository ${repo.id}`) + } + repoTotal++ + } + if (repoTotal < 1) { + throw new Error('App is not installed on any GitHub repository!') + } else if (repoTotal > 1) { + WIKI.logger.warn(`GitHub App ${tgt.config.appName} is installed on more than 1 repository. Only the first one (${repo.full_name}) will be used.`) + } + + // -> Save install/repo info + + await WIKI.models.storage.query().patch({ + isEnabled: true, + config: { + ...tgt.config, + installId, + repoId: repo.id, + repoName: repo.name, + repoOwner: repo.owner?.login, + repoDefaultBranch: repo.default_branch, + repoFullName: repo.full_name + }, + state: { + current: 'ok', + setup: 'configured' + } + }).where('id', id) + + return { + nextStep: 'completed' + } + } + default: { + throw new Error('Invalid Setup Step') + } + } + } catch (err) { + WIKI.logger.warn('GitHub Storage Module Setup Failed:') + WIKI.logger.warn(err) + throw err + } + }, + async setupDestroy (id) { + try { + const tgt = await WIKI.models.storage.query().findById(id) + if (!tgt) { + throw new Error('Invalid Target ID') + } + + WIKI.logger.info('Resetting GitHub storage configuration...') + + const ghApp = new App({ + appId: tgt.config.appId, + privateKey: tgt.config.appPem, + Octokit: Octokit.defaults({ + userAgent: 'wikijs' + }), + oauth: { + clientId: tgt.config.appClientId, + clientSecret: tgt.config.appClientSecret + }, + webhooks: { + secret: tgt.config.appWebhookSecret + } + }) + + // -> Reset storage module config + + await WIKI.models.storage.query().patch({ + isEnabled: false, + config: {}, + state: { + current: 'ok', + setup: 'notconfigured' + } + }).where('id', id) + + // -> Try to delete installation on GitHub + + if (tgt.config.installId) { + try { + await ghApp.octokit.request('DELETE /app/installations/{installation_id}', { + installation_id: tgt.config.installId + }) + WIKI.logger.info('Deleted GitHub installation successfully.') + } catch (err) { + WIKI.logger.warn('Could not delete GitHub installation automatically. Please remove the installation on GitHub.') + } + } + } catch (err) { + WIKI.logger.warn('GitHub Storage Module Destroy Failed:') + WIKI.logger.warn(err) + throw err + } + }, + async created (page) { }, + async updated (page) { }, + async deleted (page) { }, + async renamed (page) { }, + async assetUploaded (asset) { }, + async assetDeleted (asset) { }, + async assetRenamed (asset) { }, + async getLocalLocation () { }, + async exportAll () { } +} diff --git a/server/modules/storage/onedrive/definition.yml b/server/modules/storage/onedrive/definition.yml deleted file mode 100644 index b8cad109..00000000 --- a/server/modules/storage/onedrive/definition.yml +++ /dev/null @@ -1,9 +0,0 @@ -key: onedrive -title: OneDrive -description: OneDrive is a file hosting service operated by Microsoft as part of its suite of Office Online services. -author: requarks.io -logo: https://static.requarks.io/logo/onedrive.svg -website: https://onedrive.live.com/about/ -props: - clientId: String - clientSecret: String diff --git a/server/modules/storage/onedrive/storage.js b/server/modules/storage/onedrive/storage.js deleted file mode 100644 index 8ae6cc29..00000000 --- a/server/modules/storage/onedrive/storage.js +++ /dev/null @@ -1,26 +0,0 @@ -module.exports = { - async activated() { - - }, - async deactivated() { - - }, - async init() { - - }, - async created() { - - }, - async updated() { - - }, - async deleted() { - - }, - async renamed() { - - }, - async getLocalLocation () { - - } -} diff --git a/server/modules/storage/s3/common.js b/server/modules/storage/s3/common.js deleted file mode 100644 index fa1bc5dc..00000000 --- a/server/modules/storage/s3/common.js +++ /dev/null @@ -1,168 +0,0 @@ -const S3 = require('aws-sdk/clients/s3') -const stream = require('stream') -const Promise = require('bluebird') -const pipeline = Promise.promisify(stream.pipeline) -const _ = require('lodash') -const pageHelper = require('../../../helpers/page.js') - -/* global WIKI */ - -/** - * Deduce the file path given the `page` object and the object's key to the page's path. - */ -const getFilePath = (page, pathKey) => { - const fileName = `${page[pathKey]}.${pageHelper.getFileExtension(page.contentType)}` - const withLocaleCode = WIKI.config.lang.namespacing && WIKI.config.lang.code !== page.localeCode - return withLocaleCode ? `${page.localeCode}/${fileName}` : fileName -} - -/** - * Can be used with S3 compatible storage. - */ -module.exports = class S3CompatibleStorage { - constructor(storageName) { - this.storageName = storageName - this.bucketName = "" - } - async activated() { - // not used - } - async deactivated() { - // not used - } - async init() { - WIKI.logger.info(`(STORAGE/${this.storageName}) Initializing...`) - const { accessKeyId, secretAccessKey, bucket } = this.config - const s3Config = { - accessKeyId, - secretAccessKey, - params: { Bucket: bucket }, - apiVersions: '2006-03-01' - } - - if (!_.isNil(this.config.region)) { - s3Config.region = this.config.region - } - if (!_.isNil(this.config.endpoint)) { - s3Config.endpoint = this.config.endpoint - } - if (!_.isNil(this.config.sslEnabled)) { - s3Config.sslEnabled = this.config.sslEnabled - } - if (!_.isNil(this.config.s3ForcePathStyle)) { - s3Config.s3ForcePathStyle = this.config.s3ForcePathStyle - } - if (!_.isNil(this.config.s3BucketEndpoint)) { - s3Config.s3BucketEndpoint = this.config.s3BucketEndpoint - } - - this.s3 = new S3(s3Config) - this.bucketName = bucket - - // determine if a bucket exists and you have permission to access it - await this.s3.headBucket().promise() - - WIKI.logger.info(`(STORAGE/${this.storageName}) Initialization completed.`) - } - async created(page) { - WIKI.logger.info(`(STORAGE/${this.storageName}) Creating file ${page.path}...`) - const filePath = getFilePath(page, 'path') - await this.s3.putObject({ Key: filePath, Body: page.injectMetadata() }).promise() - } - async updated(page) { - WIKI.logger.info(`(STORAGE/${this.storageName}) Updating file ${page.path}...`) - const filePath = getFilePath(page, 'path') - await this.s3.putObject({ Key: filePath, Body: page.injectMetadata() }).promise() - } - async deleted(page) { - WIKI.logger.info(`(STORAGE/${this.storageName}) Deleting file ${page.path}...`) - const filePath = getFilePath(page, 'path') - await this.s3.deleteObject({ Key: filePath }).promise() - } - async renamed(page) { - WIKI.logger.info(`(STORAGE/${this.storageName}) Renaming file ${page.path} to ${page.destinationPath}...`) - let sourceFilePath = getFilePath(page, 'path') - let destinationFilePath = getFilePath(page, 'destinationPath') - if (WIKI.config.lang.namespacing) { - if (WIKI.config.lang.code !== page.localeCode) { - sourceFilePath = `${page.localeCode}/${sourceFilePath}` - } - if (WIKI.config.lang.code !== page.destinationLocaleCode) { - destinationFilePath = `${page.destinationLocaleCode}/${destinationFilePath}` - } - } - await this.s3.copyObject({ CopySource: `${this.bucketName}/${sourceFilePath}`, Key: destinationFilePath }).promise() - await this.s3.deleteObject({ Key: sourceFilePath }).promise() - } - /** - * ASSET UPLOAD - * - * @param {Object} asset Asset to upload - */ - async assetUploaded (asset) { - WIKI.logger.info(`(STORAGE/${this.storageName}) Creating new file ${asset.path}...`) - await this.s3.putObject({ Key: asset.path, Body: asset.data }).promise() - } - /** - * ASSET DELETE - * - * @param {Object} asset Asset to delete - */ - async assetDeleted (asset) { - WIKI.logger.info(`(STORAGE/${this.storageName}) Deleting file ${asset.path}...`) - await this.s3.deleteObject({ Key: asset.path }).promise() - } - /** - * ASSET RENAME - * - * @param {Object} asset Asset to rename - */ - async assetRenamed (asset) { - WIKI.logger.info(`(STORAGE/${this.storageName}) Renaming file from ${asset.path} to ${asset.destinationPath}...`) - await this.s3.copyObject({ CopySource: `${this.bucketName}/${asset.path}`, Key: asset.destinationPath }).promise() - await this.s3.deleteObject({ Key: asset.path }).promise() - } - async getLocalLocation () { - - } - /** - * HANDLERS - */ - async exportAll() { - WIKI.logger.info(`(STORAGE/${this.storageName}) Exporting all content to the cloud provider...`) - - // -> Pages - await pipeline( - WIKI.models.knex.column('path', 'localeCode', 'title', 'description', 'contentType', 'content', 'isPublished', 'updatedAt', 'createdAt').select().from('pages').where({ - isPrivate: false - }).stream(), - new stream.Transform({ - objectMode: true, - transform: async (page, enc, cb) => { - const filePath = getFilePath(page, 'path') - WIKI.logger.info(`(STORAGE/${this.storageName}) Adding page ${filePath}...`) - await this.s3.putObject({ Key: filePath, Body: pageHelper.injectPageMetadata(page) }).promise() - cb() - } - }) - ) - - // -> Assets - const assetFolders = await WIKI.models.assetFolders.getAllPaths() - - await pipeline( - WIKI.models.knex.column('filename', 'folderId', 'data').select().from('assets').join('assetData', 'assets.id', '=', 'assetData.id').stream(), - new stream.Transform({ - objectMode: true, - transform: async (asset, enc, cb) => { - const filename = (asset.folderId && asset.folderId > 0) ? `${_.get(assetFolders, asset.folderId)}/${asset.filename}` : asset.filename - WIKI.logger.info(`(STORAGE/${this.storageName}) Adding asset ${filename}...`) - await this.s3.putObject({ Key: filename, Body: asset.data }).promise() - cb() - } - }) - ) - - WIKI.logger.info(`(STORAGE/${this.storageName}) All content has been pushed to the cloud provider.`) - } -} diff --git a/server/modules/storage/s3/definition.yml b/server/modules/storage/s3/definition.yml index 0a0289d8..d55ee7d1 100644 --- a/server/modules/storage/s3/definition.yml +++ b/server/modules/storage/s3/definition.yml @@ -1,37 +1,159 @@ -key: s3 -title: Amazon S3 -description: Amazon S3 is a cloud computing web service offered by Amazon Web Services which provides object storage. -author: andrewsim -logo: https://static.requarks.io/logo/aws-s3.svg -website: https://aws.amazon.com/s3/ -isAvailable: true -supportedModes: - - push -defaultMode: push -schedule: false +title: AWS S3 / DigitalOcean Spaces +icon: '/_assets/icons/ultraviolet-amazon-web-services.svg' +banner: '/_assets/storage/s3.jpg' +description: Amazon Simple Storage Service (Amazon S3) is an object storage service offering industry-leading scalability, data availability, security, and performance. +vendor: Amazon.com, Inc. +website: 'https://aws.amazon.com' +assetDelivery: + isStreamingSupported: true + isDirectAccessSupported: true + defaultStreamingEnabled: true + defaultDirectAccessEnabled: true +contentTypes: + defaultTypesEnabled: ['images', 'documents', 'others', 'large'] + defaultLargeThreshold: '5MB' +versioning: + isSupported: false + defaultEnabled: false +sync: false props: - region: + mode: + type: String + title: Mode + hint: Select a preset configuration mode or define a custom one. + icon: tune + default: aws + order: 1 + enum: + - aws|AWS S3 + - do|DigitalOcean Spaces + - custom|Custom + awsRegion: type: String title: Region hint: The AWS datacenter region where the bucket will be created. - order: 1 + icon: geography + default: us-east-1 + enum: + - af-south-1|af-south-1 - Africa (Cape Town) + - ap-east-1|ap-east-1 - Asia Pacific (Hong Kong) + - ap-southeast-3|ap-southeast-3 - Asia Pacific (Jakarta) + - ap-south-1|ap-south-1 - Asia Pacific (Mumbai) + - ap-northeast-3|ap-northeast-3 - Asia Pacific (Osaka) + - ap-northeast-2|ap-northeast-2 - Asia Pacific (Seoul) + - ap-southeast-1|ap-southeast-1 - Asia Pacific (Singapore) + - ap-southeast-2|ap-southeast-2 - Asia Pacific (Sydney) + - ap-northeast-1|ap-northeast-1 - Asia Pacific (Tokyo) + - ca-central-1|ca-central-1 - Canada (Central) + - cn-north-1|cn-north-1 - China (Beijing) + - cn-northwest-1|cn-northwest-1 - China (Ningxia) + - eu-central-1|eu-central-1 - Europe (Frankfurt) + - eu-west-1|eu-west-1 - Europe (Ireland) + - eu-west-2|eu-west-2 - Europe (London) + - eu-south-1|eu-south-1 - Europe (Milan) + - eu-west-3|eu-west-3 - Europe (Paris) + - eu-north-1|eu-north-1 - Europe (Stockholm) + - me-south-1|me-south-1 - Middle East (Bahrain) + - sa-east-1|sa-east-1 - South America (São Paulo) + - us-east-1|us-east-1 - US East (N. Virginia) + - us-east-2|us-east-2 - US East (Ohio) + - us-west-1|us-west-1 - US West (N. California) + - us-west-2|us-west-2 - US West (Oregon) + order: 2 + if: + - { key: 'mode', eq: 'aws' } + doRegion: + type: String + title: Region + hint: The DigitalOcean Spaces region + icon: geography + default: nyc3 + enum: + - ams3|Amsterdam + - fra1|Frankfurt + - nyc3|New York + - sfo2|San Francisco 2 + - sfo3|San Francisco 3 + - sgp1|Singapore + order: 2 + if: + - { key: 'mode', eq: 'do' } + endpoint: + type: String + title: Endpoint URI + hint: The full S3-compliant endpoint URI. + icon: dns + default: https://service.region.example.com + order: 2 + if: + - { key: 'mode', eq: 'custom' } bucket: type: String title: Unique bucket name hint: The unique bucket name to create (e.g. wiki-johndoe). - order: 2 + icon: open-box + order: 3 accessKeyId: type: String title: Access Key ID hint: The Access Key. - order: 3 + icon: 3d-touch + order: 4 secretAccessKey: type: String title: Secret Access Key hint: The Secret Access Key for the Access Key ID you created above. + icon: key sensitive: true - order: 4 + order: 5 + storageTier: + type: String + title: Storage Tier + hint: The storage tier to use when adding files. + icon: scan-stock + order: 6 + default: STANDARD + enum: + - STANDARD|Standard + - STANDARD_IA|Standard Infrequent Access + - INTELLIGENT_TIERING|Intelligent Tiering + - ONEZONE_IA|One Zone Infrequent Access + - REDUCED_REDUNDANCY|Reduced Redundancy + - GLACIER_IR|Glacier Instant Retrieval + - GLACIER|Glacier Flexible Retrieval + - DEEP_ARCHIVE|Glacier Deep Archive + - OUTPOSTS|Outposts + if: + - { key: 'mode', eq: 'aws' } + sslEnabled: + type: Boolean + title: Use SSL + hint: Whether to enable SSL for requests + icon: secure + default: true + order: 10 + if: + - { key: 'mode', eq: 'custom' } + s3ForcePathStyle: + type: Boolean + title: Force Path Style for S3 objects + hint: Whether to force path style URLs for S3 objects. + icon: filtration + default: false + order: 11 + if: + - { key: 'mode', eq: 'custom' } + s3BucketEndpoint: + type: Boolean + title: Single Bucket Endpoint + hint: Whether the provided endpoint addresses an individual bucket. + icon: swipe-right + default: false + order: 12 + if: + - { key: 'mode', eq: 'custom' } actions: - handler: exportAll - label: Export All + label: Export All DB Assets to S3 hint: Output all content from the DB to S3, overwriting any existing data. If you enabled S3 after content was created or you temporarily disabled it, you'll want to execute this action to add the missing content. + icon: this-way-up diff --git a/server/modules/storage/s3/storage.js b/server/modules/storage/s3/storage.js index ea428b79..c34e2451 100644 --- a/server/modules/storage/s3/storage.js +++ b/server/modules/storage/s3/storage.js @@ -1,3 +1,166 @@ -const S3CompatibleStorage = require('./common') +const S3 = require('aws-sdk/clients/s3') +const stream = require('stream') +const Promise = require('bluebird') +const pipeline = Promise.promisify(stream.pipeline) +const _ = require('lodash') +const pageHelper = require('../../../helpers/page.js') -module.exports = new S3CompatibleStorage('S3') +/* global WIKI */ + +/** + * Deduce the file path given the `page` object and the object's key to the page's path. + */ +const getFilePath = (page, pathKey) => { + const fileName = `${page[pathKey]}.${pageHelper.getFileExtension(page.contentType)}` + const withLocaleCode = WIKI.config.lang.namespacing && WIKI.config.lang.code !== page.localeCode + return withLocaleCode ? `${page.localeCode}/${fileName}` : fileName +} + +/** + * Can be used with S3 compatible storage. + */ +module.exports = class S3CompatibleStorage { + constructor(storageName) { + this.storageName = storageName + } + async activated() { + // not used + } + async deactivated() { + // not used + } + async init() { + WIKI.logger.info(`(STORAGE/${this.storageName}) Initializing...`) + const { accessKeyId, secretAccessKey, bucket } = this.config + const s3Config = { + accessKeyId, + secretAccessKey, + params: { Bucket: bucket }, + apiVersions: '2006-03-01' + } + + if (!_.isNil(this.config.region)) { + s3Config.region = this.config.region + } + if (!_.isNil(this.config.endpoint)) { + s3Config.endpoint = this.config.endpoint + } + if (!_.isNil(this.config.sslEnabled)) { + s3Config.sslEnabled = this.config.sslEnabled + } + if (!_.isNil(this.config.s3ForcePathStyle)) { + s3Config.s3ForcePathStyle = this.config.s3ForcePathStyle + } + if (!_.isNil(this.config.s3BucketEndpoint)) { + s3Config.s3BucketEndpoint = this.config.s3BucketEndpoint + } + + this.s3 = new S3(s3Config) + + // determine if a bucket exists and you have permission to access it + await this.s3.headBucket().promise() + + WIKI.logger.info(`(STORAGE/${this.storageName}) Initialization completed.`) + } + async created(page) { + WIKI.logger.info(`(STORAGE/${this.storageName}) Creating file ${page.path}...`) + const filePath = getFilePath(page, 'path') + await this.s3.putObject({ Key: filePath, Body: page.injectMetadata() }).promise() + } + async updated(page) { + WIKI.logger.info(`(STORAGE/${this.storageName}) Updating file ${page.path}...`) + const filePath = getFilePath(page, 'path') + await this.s3.putObject({ Key: filePath, Body: page.injectMetadata() }).promise() + } + async deleted(page) { + WIKI.logger.info(`(STORAGE/${this.storageName}) Deleting file ${page.path}...`) + const filePath = getFilePath(page, 'path') + await this.s3.deleteObject({ Key: filePath }).promise() + } + async renamed(page) { + WIKI.logger.info(`(STORAGE/${this.storageName}) Renaming file ${page.path} to ${page.destinationPath}...`) + let sourceFilePath = getFilePath(page, 'path') + let destinationFilePath = getFilePath(page, 'destinationPath') + if (WIKI.config.lang.namespacing) { + if (WIKI.config.lang.code !== page.localeCode) { + sourceFilePath = `${page.localeCode}/${sourceFilePath}` + } + if (WIKI.config.lang.code !== page.destinationLocaleCode) { + destinationFilePath = `${page.destinationLocaleCode}/${destinationFilePath}` + } + } + await this.s3.copyObject({ CopySource: sourceFilePath, Key: destinationFilePath }).promise() + await this.s3.deleteObject({ Key: sourceFilePath }).promise() + } + /** + * ASSET UPLOAD + * + * @param {Object} asset Asset to upload + */ + async assetUploaded (asset) { + WIKI.logger.info(`(STORAGE/${this.storageName}) Creating new file ${asset.path}...`) + await this.s3.putObject({ Key: asset.path, Body: asset.data }).promise() + } + /** + * ASSET DELETE + * + * @param {Object} asset Asset to delete + */ + async assetDeleted (asset) { + WIKI.logger.info(`(STORAGE/${this.storageName}) Deleting file ${asset.path}...`) + await this.s3.deleteObject({ Key: asset.path }).promise() + } + /** + * ASSET RENAME + * + * @param {Object} asset Asset to rename + */ + async assetRenamed (asset) { + WIKI.logger.info(`(STORAGE/${this.storageName}) Renaming file from ${asset.path} to ${asset.destinationPath}...`) + await this.s3.copyObject({ CopySource: asset.path, Key: asset.destinationPath }).promise() + await this.s3.deleteObject({ Key: asset.path }).promise() + } + async getLocalLocation () { + + } + /** + * HANDLERS + */ + async exportAll() { + WIKI.logger.info(`(STORAGE/${this.storageName}) Exporting all content to the cloud provider...`) + + // -> Pages + await pipeline( + WIKI.models.knex.column('path', 'localeCode', 'title', 'description', 'contentType', 'content', 'isPublished', 'updatedAt', 'createdAt').select().from('pages').where({ + isPrivate: false + }).stream(), + new stream.Transform({ + objectMode: true, + transform: async (page, enc, cb) => { + const filePath = getFilePath(page, 'path') + WIKI.logger.info(`(STORAGE/${this.storageName}) Adding page ${filePath}...`) + await this.s3.putObject({ Key: filePath, Body: pageHelper.injectPageMetadata(page) }).promise() + cb() + } + }) + ) + + // -> Assets + const assetFolders = await WIKI.models.assetFolders.getAllPaths() + + await pipeline( + WIKI.models.knex.column('filename', 'folderId', 'data').select().from('assets').join('assetData', 'assets.id', '=', 'assetData.id').stream(), + new stream.Transform({ + objectMode: true, + transform: async (asset, enc, cb) => { + const filename = (asset.folderId && asset.folderId > 0) ? `${_.get(assetFolders, asset.folderId)}/${asset.filename}` : asset.filename + WIKI.logger.info(`(STORAGE/${this.storageName}) Adding asset ${filename}...`) + await this.s3.putObject({ Key: filename, Body: asset.data }).promise() + cb() + } + }) + ) + + WIKI.logger.info(`(STORAGE/${this.storageName}) All content has been pushed to the cloud provider.`) + } +} diff --git a/server/modules/storage/s3generic/definition.yml b/server/modules/storage/s3generic/definition.yml deleted file mode 100644 index c7dbd1db..00000000 --- a/server/modules/storage/s3generic/definition.yml +++ /dev/null @@ -1,57 +0,0 @@ -key: s3generic -title: S3 Generic -description: Generic storage module for S3-compatible services. -author: requarks.io -logo: https://static.requarks.io/logo/aws-s3-alt.svg -website: https://wiki.js.org -isAvailable: true -supportedModes: - - push -defaultMode: push -schedule: false -props: - endpoint: - type: String - title: Endpoint URI - hint: The full S3-compliant endpoint URI. - default: https://service.region.example.com - order: 1 - bucket: - type: String - title: Unique bucket name - hint: The unique bucket name to create (e.g. wiki-johndoe) - order: 2 - accessKeyId: - type: String - title: Access Key ID - hint: The Access Key ID. - order: 3 - secretAccessKey: - type: String - title: Access Key Secret - hint: The Access Key Secret for the Access Key ID above. - sensitive: true - order: 4 - sslEnabled: - type: Boolean - title: Use SSL - hint: Whether to enable SSL for requests - default: true - order: 5 - s3ForcePathStyle: - type: Boolean - title: Force Path Style for S3 objects - hint: Whether to force path style URLs for S3 objects. - default: false - order: 6 - s3BucketEndpoint: - type: Boolean - title: Single Bucket Endpoint - hint: Whether the provided endpoint addresses an individual bucket. - default: false - order: 7 -actions: - - handler: exportAll - label: Export All - hint: Output all content from the DB to the external service, overwriting any existing data. If you enabled this module after content was created or you temporarily disabled it, you'll want to execute this action to add the missing content. - diff --git a/server/modules/storage/s3generic/storage.js b/server/modules/storage/s3generic/storage.js deleted file mode 100644 index 5e2a1b0e..00000000 --- a/server/modules/storage/s3generic/storage.js +++ /dev/null @@ -1,3 +0,0 @@ -const S3CompatibleStorage = require('../s3/common') - -module.exports = new S3CompatibleStorage('S3Generic') diff --git a/server/modules/storage/sftp/definition.yml b/server/modules/storage/sftp/definition.yml index 13341d8f..7c93ae42 100644 --- a/server/modules/storage/sftp/definition.yml +++ b/server/modules/storage/sftp/definition.yml @@ -1,71 +1,94 @@ -key: sftp -title: SFTP -description: SFTP (SSH File Transfer Protocol) is a secure file transfer protocol. It runs over the SSH protocol. It supports the full security and authentication functionality of SSH. -author: requarks.io -logo: https://static.requarks.io/logo/ssh.svg -website: https://www.ssh.com/ssh/sftp -isAvailable: true -supportedModes: - - push -defaultMode: push -schedule: false +title: 'SFTP' +icon: '/_assets/icons/ultraviolet-nas.svg' +banner: '/_assets/storage/ssh.jpg' +description: 'Store files over a remote connection using the SSH File Transfer Protocol.' +vendor: 'Wiki.js' +website: 'https://js.wiki' +assetDelivery: + isStreamingSupported: false + isDirectAccessSupported: false + defaultStreamingEnabled: false + defaultDirectAccessEnabled: false +contentTypes: + defaultTypesEnabled: ['pages', 'images', 'documents', 'others', 'large'] + defaultLargeThreshold: '5MB' +versioning: + isSupported: false + defaultEnabled: false +sync: false props: host: type: String title: Host default: '' hint: Hostname or IP of the remote SSH server. + icon: dns order: 1 port: type: Number title: Port default: 22 hint: SSH port of the remote server. + icon: ethernet-off order: 2 authMode: type: String title: Authentication Method default: 'privateKey' hint: Whether to use Private Key or Password-based authentication. A private key is highly recommended for best security. + icon: grand-master-key enum: - - privateKey - - password + - privateKey|Private Key + - password|Password + enumDisplay: buttons order: 3 username: type: String title: Username default: '' hint: Username for authentication. + icon: test-account order: 4 privateKey: type: String title: Private Key Contents default: '' - hint: (Private Key Authentication Only) - Contents of the private key + hint: Contents of the private key + icon: key multiline: true sensitive: true order: 5 + if: + - { key: 'authMode', eq: 'privateKey' } passphrase: type: String title: Private Key Passphrase default: '' - hint: (Private Key Authentication Only) - Passphrase if the private key is encrypted, leave empty otherwise + hint: Passphrase if the private key is encrypted, leave empty otherwise + icon: password sensitive: true order: 6 + if: + - { key: 'authMode', eq: 'privateKey' } password: type: String title: Password default: '' - hint: (Password-based Authentication Only) - Password for authentication + hint: Password for authentication + icon: password sensitive: true order: 6 + if: + - { key: 'authMode', eq: 'password' } basePath: type: String title: Base Directory Path default: '/root/wiki' hint: Base directory where files will be transferred to. The path must already exists and be writable by the user. + icon: symlink-directory actions: - handler: exportAll - label: Export All + label: Export All DB Assets to Remote hint: Output all content from the DB to the remote SSH server, overwriting any existing data. If you enabled SFTP after content was created or you temporarily disabled it, you'll want to execute this action to add the missing content. + icon: this-way-up diff --git a/server/modules/storage/sftp/storage.js b/server/modules/storage/sftp/storage.js index 49d5ae1d..e1f99af4 100644 --- a/server/modules/storage/sftp/storage.js +++ b/server/modules/storage/sftp/storage.js @@ -155,12 +155,7 @@ module.exports = { const folderPaths = _.dropRight(filePath.split('/')) for (let i = 1; i <= folderPaths.length; i++) { const folderSection = _.take(folderPaths, i).join('/') - const folderDir = path.posix.join(this.config.basePath, folderSection) - try { - await this.sftp.readdir(folderDir) - } catch (err) { - await this.sftp.mkdir(folderDir) - } + await this.sftp.mkdir(path.posix.join(this.config.basePath, folderSection)) } } catch (err) {} } diff --git a/yarn.lock b/yarn.lock index d0f33192..b5736131 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2930,6 +2930,11 @@ dependencies: any-observable "^0.3.0" +"@sindresorhus/is@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" + integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== + "@sinonjs/commons@^1.7.0": version "1.7.0" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.7.0.tgz#f90ffc52a2e519f018b13b6c4da03cbff36ebed6" @@ -2944,6 +2949,13 @@ dependencies: "@sinonjs/commons" "^1.7.0" +"@szmarczak/http-timer@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" + integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== + dependencies: + defer-to-connect "^1.0.1" + "@tokenizer/token@^0.1.0", "@tokenizer/token@^0.1.1": version "0.1.1" resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.1.1.tgz#f0d92c12f87079ddfd1b29f614758b9696bc29e3" @@ -3100,15 +3112,6 @@ "@types/qs" "*" "@types/range-parser" "*" -"@types/express@*": - version "4.17.1" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.1.tgz#4cf7849ae3b47125a567dfee18bfca4254b88c5c" - integrity sha512-VfH/XCP0QbQk5B5puLqTLEeFgR8lfCJHZJKkInZ9mkYd+u8byX0kztXEQxEk4wZXJs8HI+7km2ALXjn4YKcX9w== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "*" - "@types/serve-static" "*" - "@types/express@4.17.13": version "4.17.13" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" @@ -3189,10 +3192,10 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= -"@types/ldapjs@^1.0.0": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@types/ldapjs/-/ldapjs-1.0.4.tgz#06774665035fbb277133d8cde800d18c7993707f" - integrity sha512-TXOYipuauiZV+nRslqgm02+wP007GNN7ZFHZtXe8GhnRJw2zxCOtVDi3ZnKTBxbZhFz3xPFSwJ5bCIRmXDMqTg== +"@types/ldapjs@^2.2.2": + version "2.2.2" + resolved "https://registry.yarnpkg.com/@types/ldapjs/-/ldapjs-2.2.2.tgz#cf79510d8dc34e5579442c2743f8a228427eb99c" + integrity sha512-U5HdnwIZ5uZa+f3usxdqgyfNmOROxOxXvQdQtsu6sKo8fte5vej9br2csHxPvXreAbAO1bs8/rdEzvCLpi67nQ== dependencies: "@types/node" "*" @@ -3233,13 +3236,6 @@ resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== -"@types/passport@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@types/passport/-/passport-1.0.1.tgz#bf082e29497d09410e13c260903571a489bf9c2a" - integrity sha512-oK87JjN8i8kmqb0RN0sCUB/ZjrWf3b8U45eAzZVy1ssYYgBrMOuALmvoqp7MglsilXAjxum+LS29VQqeQx6ddA== - dependencies: - "@types/express" "*" - "@types/prettier@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.0.0.tgz#dc85454b953178cc6043df5208b9e949b54a3bc4" @@ -3680,6 +3676,11 @@ dependencies: tslib "^2.3.0" +"@xmldom/xmldom@^0.7.0", "@xmldom/xmldom@^0.7.5": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.7.5.tgz#09fa51e356d07d0be200642b0e4f91d8e6dd408d" + integrity sha512-V3BIhmY36fXZ1OtVcI9W+FxQqxVLsPKcNjWigIaa81dLC9IolJl5Mt4Cvhmr0flUnjSpTdrbMTSbXqYqV5dT6A== + "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" @@ -3700,6 +3701,11 @@ abbrev@1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== +abstract-logging@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/abstract-logging/-/abstract-logging-2.0.1.tgz#6b0c371df212db7129b57d2e7fcf282b8bf1c839" + integrity sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA== + accepts@^1.3.5, accepts@~1.3.5, accepts@~1.3.7: version "1.3.7" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" @@ -3890,6 +3896,13 @@ animejs@^2.2.0: resolved "https://registry.yarnpkg.com/animejs/-/animejs-2.2.0.tgz#35eefdfc535b81949c9cb06f0b3e60c02e6fdc80" integrity sha1-Ne79/FNbgZScnLBvCz5gwC5v3IA= +ansi-align@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" + integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== + dependencies: + string-width "^4.1.0" + ansi-colors@^3.0.0, ansi-colors@^3.2.1: version "3.2.4" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" @@ -3932,6 +3945,11 @@ ansi-regex@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" @@ -4402,11 +4420,6 @@ asn1.js@^5.0.1: inherits "^2.0.1" minimalistic-assert "^1.0.0" -asn1@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" - integrity sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y= - asn1@^0.2.4: version "0.2.6" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" @@ -4426,11 +4439,6 @@ assert-never@^1.2.1: resolved "https://registry.yarnpkg.com/assert-never/-/assert-never-1.2.1.tgz#11f0e363bf146205fb08193b5c7b90f4d1cf44fe" integrity sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw== -assert-plus@0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.1.5.tgz#ee74009413002d84cec7219c6ac811812e723160" - integrity sha1-7nQAlBMALYTOxyGcasgRgS5yMWA= - assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" @@ -4918,6 +4926,20 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= +boxen@^5.0.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50" + integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ== + dependencies: + ansi-align "^3.0.0" + camelcase "^6.2.0" + chalk "^4.1.0" + cli-boxes "^2.2.1" + string-width "^4.2.2" + type-fest "^0.20.2" + widest-line "^3.1.0" + wrap-ansi "^7.0.0" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -5156,16 +5178,6 @@ bunyan@^1.8.14: mv "~2" safe-json-stringify "~1" -bunyan@^1.8.3: - version "1.8.12" - resolved "https://registry.yarnpkg.com/bunyan/-/bunyan-1.8.12.tgz#f150f0f6748abdd72aeae84f04403be2ef113797" - integrity sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c= - optionalDependencies: - dtrace-provider "~0.8" - moment "^2.10.6" - mv "~2" - safe-json-stringify "~1" - busboy@^0.2.11: version "0.2.14" resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.2.14.tgz#6c2a622efcf47c57bbbe1e2a9c37ad36c7925453" @@ -5276,6 +5288,19 @@ cache-manager@2.10.2: lodash.clonedeep "4.5.0" lru-cache "4.0.0" +cacheable-request@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" + integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^3.0.0" + lowercase-keys "^2.0.0" + normalize-url "^4.1.0" + responselike "^1.0.2" + cachedir@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8" @@ -5344,6 +5369,11 @@ camelcase@^6.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.0.0.tgz#5259f7c30e35e278f1bdc2a4d91230b37cad981e" integrity sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w== +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + caniuse-api@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-2.0.0.tgz#b1ddb5a5966b16f48dc4998444d4bbc6c7d9d834" @@ -5532,7 +5562,7 @@ chokidar@2.1.8, chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" -chokidar@3.5.3: +chokidar@3.5.3, chokidar@^3.5.2: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -5674,6 +5704,11 @@ clean-webpack-plugin@3.0.0: "@types/webpack" "^4.4.31" del "^4.1.1" +cli-boxes@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" + integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== + cli-cursor@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" @@ -5778,6 +5813,13 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" +clone-response@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" + integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= + dependencies: + mimic-response "^1.0.0" + clone@2.x: version "2.1.2" resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" @@ -6024,6 +6066,18 @@ config-chain@^1.1.12: ini "^1.3.4" proto-list "~1.2.1" +configstore@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" + integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== + dependencies: + dot-prop "^5.2.0" + graceful-fs "^4.1.2" + make-dir "^3.0.0" + unique-string "^2.0.0" + write-file-atomic "^3.0.0" + xdg-basedir "^4.0.0" + connect-session-knex@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/connect-session-knex/-/connect-session-knex-2.1.1.tgz#3543417a0c2bb6700b219e3c88f4e1b4b304f09a" @@ -6346,6 +6400,11 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" +crypto-random-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" + integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== + css-b64-images@~0.2.5: version "0.2.5" resolved "https://registry.yarnpkg.com/css-b64-images/-/css-b64-images-0.2.5.tgz#42005d83204b2b4a5d93b6b1a5644133b5927a02" @@ -7181,7 +7240,7 @@ dagre@^0.8.4, dagre@^0.8.5: graphlib "^2.1.8" lodash "^4.17.15" -dashdash@^1.12.0, dashdash@^1.14.0: +dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= @@ -7238,7 +7297,7 @@ debug@4.3.2: dependencies: ms "2.1.2" -debug@4.3.4, debug@^4.3.3: +debug@4.3.4, debug@^4.3.2, debug@^4.3.3: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -7252,6 +7311,13 @@ debug@^3.1.0, debug@^3.2.6: dependencies: ms "^2.1.1" +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + debug@^4.0.1, debug@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" @@ -7281,6 +7347,13 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= +decompress-response@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" + integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= + dependencies: + mimic-response "^1.0.0" + deep-extend@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.5.1.tgz#b894a9dd90d3023fbf1c55a394fb858eb2066f1f" @@ -7306,6 +7379,11 @@ deepmerge@^4.2.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== +defer-to-connect@^1.0.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" + integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== + define-properties@^1.1.2, define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" @@ -7610,6 +7688,13 @@ dot-prop@^4.1.1: dependencies: is-obj "^1.0.0" +dot-prop@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" + integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== + dependencies: + is-obj "^2.0.0" + dotize@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/dotize/-/dotize-0.3.0.tgz#b7a5c46835f6bd4087731354308f0d160dbceeac" @@ -7622,6 +7707,11 @@ dtrace-provider@~0.8: dependencies: nan "^2.14.0" +duplexer3@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" + integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= + duplexer@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" @@ -7951,6 +8041,11 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== +escape-goat@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" + integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== + escape-html@^1.0.3, escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -8510,11 +8605,6 @@ extract-zip@^1.7.0: mkdirp "^0.5.4" yauzl "^2.10.0" -extsprintf@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.2.0.tgz#5ad946c22f5b32ba7f8cd7426711c6e8a3fc2529" - integrity sha1-WtlGwi9bMrp/jNdCZxHG6KP8JSk= - extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -9036,7 +9126,7 @@ get-package-type@^0.1.0: resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-stream@^4.0.0: +get-stream@^4.0.0, get-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== @@ -9050,6 +9140,13 @@ get-stream@^5.0.0: dependencies: pump "^3.0.0" +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -9157,6 +9254,13 @@ global-dirs@^2.0.1: dependencies: ini "^1.3.5" +global-dirs@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.0.tgz#70a76fe84ea315ab37b1f5576cbde7d48ef72686" + integrity sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA== + dependencies: + ini "2.0.0" + global-modules@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" @@ -9235,6 +9339,23 @@ good-listener@^1.2.2: dependencies: delegate "^3.1.2" +got@^9.6.0: + version "9.6.0" + resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" + integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== + dependencies: + "@sindresorhus/is" "^0.14.0" + "@szmarczak/http-timer" "^1.1.2" + cacheable-request "^6.0.0" + decompress-response "^3.3.0" + duplexer3 "^0.1.4" + get-stream "^4.1.0" + lowercase-keys "^1.0.1" + mimic-response "^1.0.1" + p-cancelable "^1.0.0" + to-readable-stream "^1.0.0" + url-parse-lax "^3.0.0" + graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0: version "4.2.2" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.2.tgz#6f0952605d0140c1cfdb138ed005775b92d67b02" @@ -9275,22 +9396,22 @@ graphql-rate-limit-directive@2.0.2: resolved "https://registry.yarnpkg.com/graphql-rate-limit-directive/-/graphql-rate-limit-directive-2.0.2.tgz#15a578f8f8c577eef527d9397a8f632235e0fe8c" integrity sha512-Q4EvMv3xUt3woSMQMsHEJqm3K74JKd3JnIYr/YlSztkAp2pUNlVuI9AokB27qAc47OavWw1TUIXPau1ePPznew== -graphql-tag@2.11.0, graphql-tag@^2.11.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.11.0.tgz#1deb53a01c46a7eb401d6cb59dec86fa1cccbffd" - integrity sha512-VmsD5pJqWJnQZMUeRwrDhfgoyqcfwEkvtpANqcoUG8/tOLkwNgU9mzub/Mc78OJMhHjx7gfAMTxzdG43VGg3bA== +graphql-tag@2.12.6, graphql-tag@^2.12.3: + version "2.12.6" + resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.6.tgz#d441a569c1d2537ef10ca3d1633b48725329b5f1" + integrity sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg== + dependencies: + tslib "^2.1.0" graphql-tag@^2.0.0, graphql-tag@^2.4.2: version "2.10.1" resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.1.tgz#10aa41f1cd8fae5373eaf11f1f67260a3cad5e02" integrity sha512-jApXqWBzNXQ8jYa/HLkZJaVw9jgwNqZkywa2zfFn16Iv1Zb7ELNHkJaXHR7Quvd5SIGsy6Ny7SUKATgnu05uEg== -graphql-tag@^2.12.3: - version "2.12.6" - resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.6.tgz#d441a569c1d2537ef10ca3d1633b48725329b5f1" - integrity sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg== - dependencies: - tslib "^2.1.0" +graphql-tag@^2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.11.0.tgz#1deb53a01c46a7eb401d6cb59dec86fa1cccbffd" + integrity sha512-VmsD5pJqWJnQZMUeRwrDhfgoyqcfwEkvtpANqcoUG8/tOLkwNgU9mzub/Mc78OJMhHjx7gfAMTxzdG43VGg3bA== graphql-tools@8.2.5: version "8.2.5" @@ -9418,6 +9539,11 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" +has-yarn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" + integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== + has@^1.0.0, has@^1.0.1, has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -9627,6 +9753,11 @@ htmlparser2@^6.0.0: domutils "^2.4.4" entities "^2.0.0" +http-cache-semantics@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" + integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + http-errors@1.7.2: version "1.7.2" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" @@ -9765,6 +9896,11 @@ iferr@^0.1.5: resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= +ignore-by-default@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk= + ignore-loader@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/ignore-loader/-/ignore-loader-0.1.2.tgz#d81f240376d0ba4f0d778972c3ad25874117a463" @@ -9837,6 +9973,11 @@ import-from@^2.1.0: dependencies: resolve-from "^3.0.0" +import-lazy@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" + integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= + import-local@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" @@ -9901,6 +10042,11 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= +ini@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" + integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== + ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" @@ -10179,6 +10325,14 @@ is-installed-globally@^0.3.2: global-dirs "^2.0.1" is-path-inside "^3.0.1" +is-installed-globally@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" + integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== + dependencies: + global-dirs "^3.0.0" + is-path-inside "^3.0.2" + is-invalid-path@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/is-invalid-path/-/is-invalid-path-0.1.0.tgz#307a855b3cf1a938b44ea70d2c61106053714f34" @@ -10186,6 +10340,11 @@ is-invalid-path@^0.1.0: dependencies: is-glob "^2.0.0" +is-npm@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8" + integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA== + is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" @@ -10203,6 +10362,11 @@ is-obj@^1.0.0: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + is-observable@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-observable/-/is-observable-1.1.0.tgz#b3e986c8f44de950867cab5403f5a3465005975e" @@ -10234,6 +10398,11 @@ is-path-inside@^3.0.1: resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017" integrity sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg== +is-path-inside@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + is-plain-obj@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" @@ -10338,6 +10507,11 @@ is-wsl@^2.2.0: dependencies: is-docker "^2.0.0" +is-yarn-global@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" + integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== + isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" @@ -10914,6 +11088,11 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= +json-buffer@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" + integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= + json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -11065,6 +11244,13 @@ keygrip@~1.0.2: resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.0.3.tgz#399d709f0aed2bab0a059e0cdd3a5023a053e1dc" integrity sha512-/PpesirAIfaklxUzp4Yb7xBper9MwP6hNRA6BGGUFCgbJ+BM5CKBtsoxinNXkLHAr+GXS1/lSlF2rP7cv5Fl+g== +keyv@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" + integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== + dependencies: + json-buffer "3.0.0" + khroma@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/khroma/-/khroma-1.1.0.tgz#cc17723eb719c5245ea66d23dd577d5695452db5" @@ -11156,6 +11342,13 @@ last-call-webpack-plugin@^3.0.0: lodash "^4.17.5" webpack-sources "^1.1.0" +latest-version@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" + integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== + dependencies: + package-json "^6.3.0" + lazy-ass@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" @@ -11168,40 +11361,36 @@ lcid@^1.0.0: dependencies: invert-kv "^1.0.0" -ldap-filter@0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/ldap-filter/-/ldap-filter-0.2.2.tgz#f2b842be0b86da3352798505b31ebcae590d77d0" - integrity sha1-8rhCvguG2jNSeYUFsx68rlkNd9A= +ldap-filter@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/ldap-filter/-/ldap-filter-0.3.3.tgz#2b14c68a2a9d4104dbdbc910a1ca85fd189e9797" + integrity sha1-KxTGiiqdQQTb28kQocqF/Riel5c= dependencies: - assert-plus "0.1.5" + assert-plus "^1.0.0" -ldapauth-fork@^4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/ldapauth-fork/-/ldapauth-fork-4.3.2.tgz#bc408f0d4fb8d07e5e9a7287c7310fdceea226f9" - integrity sha512-XaO/kLaY9XGH/O58qTgtGtS0u7Qfq9IMDYWBvjRDuNfh7PbqOA5JXwF7DeKW6kIWQ842fGoIpB/cZmJ0SaJFbQ== +ldapauth-fork@^5.0.1: + version "5.0.2" + resolved "https://registry.yarnpkg.com/ldapauth-fork/-/ldapauth-fork-5.0.2.tgz#abe6a99eac578b7730e1331254643d78dffae6fa" + integrity sha512-fWrrBwJ162rzQIIqfPsfCHy/861kEalQNIu16gmwOMr5cmdfjNkw7XfTlzCTJHybnFg9oW9WaX4DGXa0xiGPmA== dependencies: - "@types/ldapjs" "^1.0.0" - "@types/node" "*" + "@types/ldapjs" "^2.2.2" bcryptjs "^2.4.0" - ldapjs "^1.0.2" - lru-cache "^5.1.1" + ldapjs "^2.2.1" + lru-cache "^6.0.0" -ldapjs@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/ldapjs/-/ldapjs-1.0.2.tgz#544ff7032b7b83c68f0701328d9297aa694340f9" - integrity sha1-VE/3Ayt7g8aPBwEyjZKXqmlDQPk= +ldapjs@^2.2.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/ldapjs/-/ldapjs-2.3.2.tgz#a599d081519f70462941cc33a50e9354c32f35b7" + integrity sha512-FU+GR/qbQ96WUZ2DUb7FzaEybYvv3240wTVPcbsdELB3o4cK92zGVjntsh68siVkLeCmlCcsd/cIQzyGXSS7LA== dependencies: - asn1 "0.2.3" + abstract-logging "^2.0.0" + asn1 "^0.2.4" assert-plus "^1.0.0" backoff "^2.5.0" - bunyan "^1.8.3" - dashdash "^1.14.0" - ldap-filter "0.2.2" + ldap-filter "^0.3.3" once "^1.4.0" - vasync "^1.6.4" + vasync "^2.2.0" verror "^1.8.1" - optionalDependencies: - dtrace-provider "~0.8" leven@^3.1.0: version "3.1.0" @@ -11542,6 +11731,16 @@ lower-case@^2.0.1: dependencies: tslib "^1.10.0" +lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" + integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== + +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + lru-cache@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.0.0.tgz#b5cbf01556c16966febe54ceec0fb4dc90df6c28" @@ -11926,6 +12125,11 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-response@^1.0.0, mimic-response@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + mini-css-extract-plugin@0.11.3: version "0.11.3" resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.11.3.tgz#15b0910a7f32e62ffde4a7430cfefbd700724ea6" @@ -12128,7 +12332,7 @@ moment@2.29.2: resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.2.tgz#00910c60b20843bcba52d37d58c628b47b1f20e4" integrity sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg== -"moment@>= 2.9.0", moment@^2.10.2, moment@^2.10.6, moment@^2.19.2, moment@^2.22.1: +"moment@>= 2.9.0", moment@^2.10.2, moment@^2.19.2, moment@^2.22.1: version "2.24.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== @@ -12203,10 +12407,10 @@ nan@^2.14.1, nan@^2.15.0: resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== -nanoid@3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c" - integrity sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA== +nanoid@3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.2.tgz#c89622fafb4381cd221421c69ec58547a1eec557" + integrity sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA== nanomatch@^1.2.9: version "1.2.13" @@ -12442,7 +12646,23 @@ nodemailer@6.7.3: resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.7.3.tgz#b73f9a81b9c8fa8acb4ea14b608f5e725ea8e018" integrity sha512-KUdDsspqx89sD4UUyUKzdlUOper3hRkDVkrKh/89G+d9WKsU5ox51NWS4tB1XR5dPUdR4SP0E3molyEfOvSa3g== -nopt@1.0.10: +nodemon@2.0.15: + version "2.0.15" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.15.tgz#504516ce3b43d9dc9a955ccd9ec57550a31a8d4e" + integrity sha512-gdHMNx47Gw7b3kWxJV64NI+Q5nfl0y5DgDbiVtShiwa7Z0IZ07Ll4RLFo6AjrhzMtoEZn5PDE3/c2AbVsiCkpA== + dependencies: + chokidar "^3.5.2" + debug "^3.2.7" + ignore-by-default "^1.0.1" + minimatch "^3.0.4" + pstree.remy "^1.1.8" + semver "^5.7.1" + supports-color "^5.5.0" + touch "^3.1.0" + undefsafe "^2.0.5" + update-notifier "^5.1.0" + +nopt@1.0.10, nopt@~1.0.10: version "1.0.10" resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4= @@ -12506,6 +12726,11 @@ normalize-url@^3.0.0: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== +normalize-url@^4.1.0: + version "4.5.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" + integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== + notp@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/notp/-/notp-2.0.3.tgz#a9fd11e25cfe1ccb39fc6689544ee4c10ef9a577" @@ -12834,6 +13059,11 @@ ospath@^1.2.2: resolved "https://registry.yarnpkg.com/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b" integrity sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs= +p-cancelable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" + integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== + p-each-series@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.1.0.tgz#961c8dd3f195ea96c747e636b262b800a6b1af48" @@ -12908,6 +13138,16 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +package-json@^6.3.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" + integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== + dependencies: + got "^9.6.0" + registry-auth-token "^4.0.0" + registry-url "^5.0.0" + semver "^6.2.0" + packet-reader@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74" @@ -13118,14 +13358,12 @@ passport-jwt@4.0.0: jsonwebtoken "^8.2.0" passport-strategy "^1.0.0" -passport-ldapauth@2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/passport-ldapauth/-/passport-ldapauth-2.1.4.tgz#2259e4e5d2d9c2b3d50a04f6640a941effda8da9" - integrity sha512-VeVL69ZK+cpJe0DKMSGuwcf7k+V4dr0U0Y7ZhXL785pcRb5gRA6qYZfIH+XTsAzwqTK9l0Dn3Ds4weOZ1jKkLQ== +passport-ldapauth@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/passport-ldapauth/-/passport-ldapauth-3.0.1.tgz#1432e8469de18bd46b5b39a46a866b416c1ddded" + integrity sha512-TRRx3BHi8GC8MfCT9wmghjde/EGeKjll7zqHRRfGRxXbLcaDce2OftbQrFG7/AWaeFhR6zpZHtBQ/IkINdLVjQ== dependencies: - "@types/node" "*" - "@types/passport" "^1.0.0" - ldapauth-fork "^4.3.2" + ldapauth-fork "^5.0.1" passport-strategy "^1.0.0" passport-local@1.0.0: @@ -13200,29 +13438,26 @@ passport-okta-oauth@0.0.1: pkginfo "0.2.x" uid2 "0.0.3" -passport-openidconnect@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/passport-openidconnect/-/passport-openidconnect-0.0.2.tgz#e488f8bdb386c9a9fd39c91d5ab8c880156e8153" - integrity sha1-5Ij4vbOGyan9OckdWrjIgBVugVM= +passport-openidconnect@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/passport-openidconnect/-/passport-openidconnect-0.1.1.tgz#83921ff5f87f634079f65262dada834af1972244" + integrity sha512-r0QJiWEzwCg2MeCIXVP5G6YxVRqnEsZ2HpgKRthZ9AiQHJrgGUytXpsdcGF9BRwd3yMrEesb/uG/Yxb86rrY0g== dependencies: oauth "0.9.x" passport-strategy "1.x.x" - request "^2.75.0" - webfinger "0.4.x" -passport-saml@1.3.5: - version "1.3.5" - resolved "https://registry.yarnpkg.com/passport-saml/-/passport-saml-1.3.5.tgz#747f2c8bb8b9fed41e8cd14586df5aa83e8a8996" - integrity sha512-HFamiqgGiMRCbUBm3wx02WYWKb6ojke0WJHrg4QXI8tx35HrTmDiY8MksUXhouJtEkfcRwXjBL2cSEpRQ6+PgQ== +passport-saml@3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/passport-saml/-/passport-saml-3.2.1.tgz#c489a61a4c2dd93ddec1d53952a595b9f33e15e8" + integrity sha512-Y8aD94B6MTLht57BlBrDauEgvtWjuSeINKk7NadXlpT/OBmsoGGYPpb0FJeBtdyGX4GEbZARAkxvBEqsL8E7XQ== dependencies: - debug "^3.1.0" - passport-strategy "*" - q "^1.5.0" - xml-crypto "^1.4.0" - xml-encryption "1.2.1" - xml2js "0.4.x" - xmlbuilder "^11.0.0" - xmldom "0.1.x" + "@xmldom/xmldom" "^0.7.5" + debug "^4.3.2" + passport-strategy "^1.0.0" + xml-crypto "^2.1.3" + xml-encryption "^2.0.0" + xml2js "^0.4.23" + xmlbuilder "^15.1.1" passport-slack-oauth2@1.1.1: version "1.1.1" @@ -13232,7 +13467,7 @@ passport-slack-oauth2@1.1.1: passport-oauth2 "^1.5.0" pkginfo "^0.4.1" -passport-strategy@*, passport-strategy@1.x.x, passport-strategy@^1.0.0: +passport-strategy@1.x.x, passport-strategy@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" integrity sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ= @@ -13244,7 +13479,15 @@ passport-twitch-oauth@1.0.0: dependencies: passport-oauth2 "^1.2.0" -passport@0.4.1, passport@^0.4.1: +passport@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/passport/-/passport-0.5.2.tgz#0cb38dd8a71552c8390dfa6a9a6f7f3909954bcf" + integrity sha512-w9n/Ot5I7orGD4y+7V3EFJCQEznE5RxHamUxcqLT2QoJY0f2JdN8GyHonYFvN0Vz+L6lUJfVhrk2aZz2LbuREw== + dependencies: + passport-strategy "1.x.x" + pause "0.0.1" + +passport@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/passport/-/passport-0.4.1.tgz#941446a21cb92fc688d97a0861c38ce9f738f270" integrity sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg== @@ -14405,10 +14648,10 @@ postcss-selector-not@^4.0.0: balanced-match "^1.0.0" postcss "^7.0.2" -postcss-selector-parser@6.0.9: - version "6.0.9" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz#ee71c3b9ff63d9cd130838876c13a2ec1a992b2f" - integrity sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ== +postcss-selector-parser@6.0.10: + version "6.0.10" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d" + integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w== dependencies: cssesc "^3.0.0" util-deprecate "^1.0.2" @@ -14588,6 +14831,11 @@ prepend-http@^1.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= +prepend-http@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" + integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= + prettier@^1.18.2: version "1.19.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" @@ -14723,6 +14971,11 @@ psl@^1.1.33: resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== +pstree.remy@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" + integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== + public-encrypt@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" @@ -14974,7 +15227,14 @@ punycode@^1.2.4, punycode@^1.4.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= -q@^1.1.2, q@^1.5.0: +pupa@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62" + integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== + dependencies: + escape-goat "^2.0.0" + +q@^1.1.2: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= @@ -15109,7 +15369,7 @@ raw-loader@4.0.2: loader-utils "^2.0.0" schema-utils "^3.0.0" -rc@^1.2.7: +rc@^1.2.7, rc@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== @@ -15439,6 +15699,20 @@ regexpu-core@^4.7.1: unicode-match-property-ecmascript "^1.0.4" unicode-match-property-value-ecmascript "^1.2.0" +registry-auth-token@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250" + integrity sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw== + dependencies: + rc "^1.2.8" + +registry-url@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" + integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== + dependencies: + rc "^1.2.8" + regjsgen@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.0.tgz#a7634dc08f89209c2049adda3525711fb97265dd" @@ -15565,7 +15839,7 @@ request@2.88.2, request@^2.88.2: tunnel-agent "^0.6.0" uuid "^3.3.2" -request@^2.72.0, request@^2.75.0: +request@^2.72.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== @@ -15710,6 +15984,13 @@ resolve@^1.20.0, resolve@^1.9.0: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +responselike@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" + integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= + dependencies: + lowercase-keys "^1.0.0" + restore-cursor@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" @@ -15950,7 +16231,7 @@ sax@1.2.1: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" integrity sha1-e45lYZCyKOgaZq6nSEgNgozS03o= -sax@>=0.1.1, sax@>=0.6.0, sax@^1.2.4, sax@~1.2.4: +sax@>=0.6.0, sax@^1.2.4, sax@~1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== @@ -16033,7 +16314,14 @@ select@^1.1.2: resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0= -"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: +semver-diff@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" + integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== + dependencies: + semver "^6.3.0" + +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -16043,14 +16331,14 @@ semver@7.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== -semver@7.3.6, semver@^7.3.5: +semver@7.3.6, semver@^7.3.4, semver@^7.3.5: version "7.3.6" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.6.tgz#5d73886fb9c0c6602e79440b97165c29581cbb2b" integrity sha512-HZWqcgwLsjaX1HBD31msI/rXktuIhS+lWvdE4kN9z+8IVT4Itc7vqU2WvYsyD6/sjYCt4dEKH/m1M3dwI9CC5w== dependencies: lru-cache "^7.4.0" -semver@^6.0.0, semver@^6.1.0, semver@^6.3.0: +semver@^6.0.0, semver@^6.1.0, semver@^6.2.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== @@ -16550,11 +16838,6 @@ stealthy-require@^1.1.1: resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= -step@0.0.x: - version "0.0.6" - resolved "https://registry.yarnpkg.com/step/-/step-0.0.6.tgz#143e7849a5d7d3f4a088fe29af94915216eeede2" - integrity sha1-FD54SaXX0/SgiP4pr5SRUhbu7eI= - stream-browserify@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" @@ -16631,6 +16914,15 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" +string-width@^4.0.0, string-width@^4.2.2: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.1.0.tgz#ba846d1daa97c3c596155308063e075ed1c99aff" @@ -16728,6 +17020,13 @@ strip-ansi@^6.0.0: dependencies: ansi-regex "^5.0.0" +strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-bom@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" @@ -16829,7 +17128,7 @@ supports-color@^2.0.0: resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= -supports-color@^5.3.0, supports-color@^5.4.0: +supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== @@ -17130,6 +17429,11 @@ to-object-path@^0.3.0: dependencies: kind-of "^3.0.2" +to-readable-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" + integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== + to-regex-range@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" @@ -17183,6 +17487,13 @@ token-types@^2.0.0: "@tokenizer/token" "^0.1.0" ieee754 "^1.1.13" +touch@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" + integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== + dependencies: + nopt "~1.0.10" + tough-cookie@^2.3.3, tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" @@ -17358,6 +17669,11 @@ type-detect@4.0.8: resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + type-fest@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.5.2.tgz#d6ef42a0356c6cd45f49485c3b6281fc148e48a2" @@ -17423,6 +17739,11 @@ uid2@0.0.3, uid2@0.0.x: resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82" integrity sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I= +undefsafe@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" + integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== + underscore@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8" @@ -17500,6 +17821,13 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" +unique-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" + integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== + dependencies: + crypto-random-string "^2.0.0" + units-css@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/units-css/-/units-css-0.4.0.tgz#d6228653a51983d7c16ff28f8b9dc3b1ffed3a07" @@ -17551,6 +17879,26 @@ upath@^1.1.1: resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== +update-notifier@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-5.1.0.tgz#4ab0d7c7f36a231dd7316cf7729313f0214d9ad9" + integrity sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw== + dependencies: + boxen "^5.0.0" + chalk "^4.1.0" + configstore "^5.0.1" + has-yarn "^2.1.0" + import-lazy "^2.1.0" + is-ci "^2.0.0" + is-installed-globally "^0.4.0" + is-npm "^5.0.0" + is-yarn-global "^0.3.0" + latest-version "^5.1.0" + pupa "^2.1.1" + semver "^7.3.4" + semver-diff "^3.1.1" + xdg-basedir "^4.0.0" + upper-case@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" @@ -17577,6 +17925,13 @@ url-loader@4.1.1: mime-types "^2.1.27" schema-utils "^3.0.0" +url-parse-lax@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" + integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= + dependencies: + prepend-http "^2.0.0" + url@0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" @@ -17724,12 +18079,12 @@ vary@^1, vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= -vasync@^1.6.4: - version "1.6.4" - resolved "https://registry.yarnpkg.com/vasync/-/vasync-1.6.4.tgz#dfe93616ad0e7ae801b332a9d88bfc5cdc8e1d1f" - integrity sha1-3+k2Fq0OeugBszKp2Iv8XNyOHR8= +vasync@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/vasync/-/vasync-2.2.1.tgz#d881379ff3685e4affa8e775cf0fd369262a201b" + integrity sha512-Hq72JaTpcTFdWiNA4Y22Amej2GH3BFmBaKPPlDZ4/oC8HNn2ISHLkFrJU4Ds8R3jcUi7oo5Y9jcMHKjES+N9wQ== dependencies: - verror "1.6.0" + verror "1.10.0" velocity-animate@1.5.2: version "1.5.2" @@ -17750,13 +18105,6 @@ verror@1.10.0, verror@^1.10.0, verror@^1.8.1: core-util-is "1.0.2" extsprintf "^1.2.0" -verror@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.6.0.tgz#7d13b27b1facc2e2da90405eb5ea6e5bdd252ea5" - integrity sha1-fROyex+swuLakEBetepuW90lLqU= - dependencies: - extsprintf "1.2.0" - viewport-dimensions@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/viewport-dimensions/-/viewport-dimensions-0.2.0.tgz#de740747db5387fd1725f5175e91bac76afdf36c" @@ -17987,14 +18335,6 @@ watchpack@^1.7.4: chokidar "^3.4.1" watchpack-chokidar2 "^2.0.0" -webfinger@0.4.x: - version "0.4.2" - resolved "https://registry.yarnpkg.com/webfinger/-/webfinger-0.4.2.tgz#3477a6d97799461896039fcffc650b73468ee76d" - integrity sha1-NHem2XeZRhiWA5/P/GULc0aO520= - dependencies: - step "0.0.x" - xml2js "0.1.x" - webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" @@ -18224,6 +18564,13 @@ wide-align@^1.1.0: dependencies: string-width "^1.0.2 || 2" +widest-line@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" + integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== + dependencies: + string-width "^4.0.0" + wildcard@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" @@ -18364,36 +18711,33 @@ ws@^7.2.3: resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.5.tgz#abb1370d4626a5a9cd79d8de404aa18b3465d10d" integrity sha512-C34cIU4+DB2vMyAbmEKossWq2ZQDr6QEyuuCzWrM9zfw1sGc0mYiJ0UnG9zzNykt49C2Fi34hvr2vssFQRS6EA== -xml-crypto@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/xml-crypto/-/xml-crypto-1.4.0.tgz#de1cec8cd31cbd689cd90d3d6e8a27d4ae807de7" - integrity sha512-K8FRdRxICVulK4WhiTUcJrRyAIJFPVOqxfurA3x/JlmXBTxy+SkEENF6GeRt7p/rB6WSOUS9g0gXNQw5n+407g== +xdg-basedir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" + integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== + +xml-crypto@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/xml-crypto/-/xml-crypto-2.1.3.tgz#6a7272b610ea3e4ea7f13e9e4876f1b20cbc32c8" + integrity sha512-MpXZwnn9JK0mNPZ5mnFIbNnQa+8lMGK4NtnX2FlJMfMWR60sJdFO9X72yO6ji068pxixzk53O7x0/iSKh6IhyQ== dependencies: - xmldom "0.1.27" - xpath "0.0.27" + "@xmldom/xmldom" "^0.7.0" + xpath "0.0.32" -xml-encryption@1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/xml-encryption/-/xml-encryption-1.2.1.tgz#e6d18817c4309fd07ca7793cca93c3fd06745baa" - integrity sha512-hn5w3l5p2+nGjlmM0CAhMChDzVGhW+M37jH35Z+GJIipXbn9PUlAIRZ6I5Wm7ynlqZjFrMAr83d/CIp9VZJMTA== +xml-encryption@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/xml-encryption/-/xml-encryption-2.0.0.tgz#d4e1eb3ec1f2c5d2a2a0a6e23d199237e8b4bf83" + integrity sha512-4Av83DdvAgUQQMfi/w8G01aJshbEZP9ewjmZMpS9t3H+OCZBDvyK4GJPnHGfWiXlArnPbYvR58JB9qF2x9Ds+Q== dependencies: + "@xmldom/xmldom" "^0.7.0" escape-html "^1.0.3" - node-forge "^0.10.0" - xmldom "~0.1.15" - xpath "0.0.27" + xpath "0.0.32" xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== -xml2js@0.1.x: - version "0.1.14" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.1.14.tgz#5274e67f5a64c5f92974cd85139e0332adc6b90c" - integrity sha1-UnTmf1pkxfkpdM2FE54DMq3GuQw= - dependencies: - sax ">=0.1.1" - xml2js@0.4.19: version "0.4.19" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" @@ -18410,7 +18754,7 @@ xml2js@0.4.4: sax "0.6.x" xmlbuilder ">=1.0.0" -xml2js@0.4.x, xml2js@^0.4.19: +xml2js@^0.4.19: version "0.4.22" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.22.tgz#4fa2d846ec803237de86f30aa9b5f70b6600de02" integrity sha512-MWTbxAQqclRSTnehWWe5nMKzI3VmJ8ltiJEco8akcC6j3miOhjjfzKum5sId+CWhfxdOs/1xauYr8/ZDBtQiRw== @@ -18419,12 +18763,25 @@ xml2js@0.4.x, xml2js@^0.4.19: util.promisify "~1.0.0" xmlbuilder "~11.0.0" +xml2js@^0.4.23: + version "0.4.23" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" + integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== + dependencies: + sax ">=0.6.0" + xmlbuilder "~11.0.0" + xmlbuilder@>=1.0.0: version "13.0.2" resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-13.0.2.tgz#02ae33614b6a047d1c32b5389c1fdacb2bce47a7" integrity sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ== -xmlbuilder@^11.0.0, xmlbuilder@~11.0.0: +xmlbuilder@^15.1.1: + version "15.1.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" + integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg== + +xmlbuilder@~11.0.0: version "11.0.1" resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== @@ -18439,15 +18796,10 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== -xmldom@0.1.27, xmldom@0.1.x, xmldom@~0.1.15: - version "0.1.27" - resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9" - integrity sha1-1QH5ezvbQDr4757MIFcxh6rawOk= - -xpath@0.0.27: - version "0.0.27" - resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.27.tgz#dd3421fbdcc5646ac32c48531b4d7e9d0c2cfa92" - integrity sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ== +xpath@0.0.32: + version "0.0.32" + resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.32.tgz#1b73d3351af736e17ec078d6da4b8175405c48af" + integrity sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw== xss@1.0.11: version "1.0.11"