🐛 Fixed all known filter limitations (#10159)

refs #10105, closes #10108, closes https://github.com/TryGhost/Ghost/issues/9950, refs https://github.com/TryGhost/Ghost/issues/9923, refs https://github.com/TryGhost/Ghost/issues/9916, refs https://github.com/TryGhost/Ghost/issues/9574, refs https://github.com/TryGhost/Ghost/issues/6345, refs https://github.com/TryGhost/Ghost/issues/6309, refs https://github.com/TryGhost/Ghost/issues/6158, refs https://github.com/TryGhost/GQL/issues/16

- removed GQL dependency
- replaced GQL with our brand new NQL implementation
- fixed all known filter limitations
- GQL suffered from some underlying filter bugs, which NQL tried to fix
- the bugs were mostly in how we query the database for relation filtering
- the underlying problem was caused by a too simple implementation of querying the relations
- mongo-knex has implemented a more robust and complex filtering mechanism for relations
- replaced logic in our bookshelf filter plugin
- we pass the custom, default and override filters from Ghost to NQL, which then are getting parsed and merged into a mongo JSON object. The mongo JSON is getting attached by mongo-knex.

NQL: https://github.com/NexesJS/NQL
mongo-knex: https://github.com/NexesJS/mongo-knex
This commit is contained in:
Katharina Irrgang 2018-12-11 11:53:40 +01:00 committed by GitHub
parent 48923ac327
commit 9d7c3bd726
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 621 additions and 1163 deletions

View File

@ -12,21 +12,20 @@ module.exports = {
browse(apiConfig, frame) {
debug('browse');
// CASE: the content api endpoints for pages forces the model layer to return static pages only.
// we have to enforce the filter.
/**
* CASE:
*
* - the content api endpoints for pages forces the model layer to return static pages only
* - we have to enforce the filter
*
* @TODO: https://github.com/TryGhost/Ghost/issues/10268
*/
if (frame.options.filter) {
if (frame.options.filter.match(/page:\w+\+?/)) {
frame.options.filter = frame.options.filter.replace(/page:\w+\+?/, '');
}
if (frame.options.filter) {
frame.options.filter = frame.options.filter + '+page:true';
} else {
frame.options.filter = 'page:true';
}
frame.options.filter = `${frame.options.filter}+page:true`;
} else {
frame.options.filter = 'page:true';
}
removeMobiledocFormat(frame);
debug(frame.options);

View File

@ -32,24 +32,25 @@ module.exports = {
* - user exists? admin api access
*/
if (utils.isContentAPI(frame)) {
// CASE: the content api endpoints for posts should only return non page type resources
/**
* CASE:
*
* - the content api endpoints for posts should only return non page type resources
* - we have to enforce the filter
*
* @TODO: https://github.com/TryGhost/Ghost/issues/10268
*/
if (frame.options.filter) {
if (frame.options.filter.match(/page:\w+\+?/)) {
frame.options.filter = frame.options.filter.replace(/page:\w+\+?/, '');
}
if (frame.options.filter) {
frame.options.filter = frame.options.filter + '+page:false';
} else {
frame.options.filter = 'page:false';
}
frame.options.filter = `${frame.options.filter}+page:false`;
} else {
frame.options.filter = 'page:false';
}
// CASE: the content api endpoint for posts should not return mobiledoc
removeMobiledocFormat(frame);
// CASE: Members needs to have the tags to check if its allowed access
if (labs.isSet('members')) {
// CASE: Members needs to have the tags to check if its allowed access
includeTags(frame);
}
}

View File

@ -9,7 +9,6 @@ const _ = require('lodash'),
bookshelf = require('bookshelf'),
moment = require('moment'),
Promise = require('bluebird'),
gql = require('ghost-gql'),
ObjectId = require('bson-objectid'),
debug = require('ghost-ignition').debug('models:base'),
config = require('../../config'),
@ -1012,6 +1011,7 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
fetchAll: function (options) {
options = options || {};
const nql = require('@nexes/nql');
const modelName = options.modelName;
const tableNames = {
Post: 'posts',
@ -1072,8 +1072,8 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
query.select(toSelect);
}
// filter data
gql.knexify(query, gql.parse(filter));
// @NOTE: We can't use the filter plugin, because we are not using bookshelf.
nql(filter).querySQL(query);
return query.then((objects) => {
debug('fetched', modelName, filter);

View File

@ -1,210 +1,95 @@
var _ = require('lodash'),
gql = require('ghost-gql'),
common = require('../../lib/common'),
filter,
filterUtils;
const debug = require('ghost-ignition').debug('models:plugins:filter');
const common = require('../../lib/common');
filterUtils = {
/**
* ## Combine Filters
* Util to combine the enforced, default and custom filters such that they behave accordingly
* @param {String|Object} enforced - filters which must ALWAYS be applied
* @param {String|Object} defaults - filters which must be applied if a matching filter isn't provided
* @param {...String|Object} [custom] - custom filters which are additional
* @returns {*}
*/
combineFilters: function combineFilters(enforced, defaults, custom /* ...custom */) {
custom = Array.prototype.slice.call(arguments, 2);
// Ensure everything has been run through the gql parser
try {
enforced = enforced ? (_.isString(enforced) ? gql.parse(enforced) : enforced) : null;
defaults = defaults ? (_.isString(defaults) ? gql.parse(defaults) : defaults) : null;
custom = _.map(custom, function (arg) {
return _.isString(arg) ? gql.parse(arg) : arg;
});
} catch (err) {
throw new common.errors.ValidationError({
err: err,
property: 'filter',
context: common.i18n.t('errors.models.plugins.filter.errorParsing'),
help: common.i18n.t('errors.models.plugins.filter.forInformationRead', {url: 'https://api.ghost.org/docs/filter'})
});
}
// Merge custom filter options into a single set of statements
custom = gql.json.mergeStatements.apply(this, custom);
// if there is no enforced or default statements, return just the custom statements;
if (!enforced && !defaults) {
return custom;
}
// Reduce custom filters based on enforced filters
if (custom && !_.isEmpty(custom.statements) && enforced && !_.isEmpty(enforced.statements)) {
custom.statements = gql.json.rejectStatements(custom.statements, function (customStatement) {
return gql.json.findStatement(enforced.statements, customStatement, 'prop');
});
}
// Reduce default filters based on custom filters
if (defaults && !_.isEmpty(defaults.statements) && custom && !_.isEmpty(custom.statements)) {
defaults.statements = gql.json.rejectStatements(defaults.statements, function (defaultStatement) {
return gql.json.findStatement(custom.statements, defaultStatement, 'prop');
});
}
// Merge enforced and defaults
enforced = gql.json.mergeStatements(enforced, defaults);
if (_.isEmpty(custom.statements)) {
return enforced;
}
if (_.isEmpty(enforced.statements)) {
return custom;
}
return {
statements: [
{group: enforced.statements},
{group: custom.statements, func: 'and'}
]
};
const RELATIONS = {
tags: {
tableName: 'tags',
type: 'manyToMany',
joinTable: 'posts_tags',
joinFrom: 'post_id',
joinTo: 'tag_id'
},
authors: {
tableName: 'users',
tableNameAs: 'authors',
type: 'manyToMany',
joinTable: 'posts_authors',
joinFrom: 'post_id',
joinTo: 'author_id'
}
};
filter = function filter(Bookshelf) {
var Model = Bookshelf.Model.extend({
const EXPANSIONS = [{
key: 'primary_tag',
replacement: 'tags.slug',
expansion: 'posts_tags.sort_order:0+tags.visibility:public'
}, {
key: 'primary_author',
replacement: 'authors.slug',
expansion: 'posts_authors.sort_order:0+authors.visibility:public'
}, {
key: 'authors',
replacement: 'authors.slug'
}, {
key: 'author',
replacement: 'authors.slug'
}, {
key: 'tag',
replacement: 'tags.slug'
}, {
key: 'tags',
replacement: 'tags.slug'
}];
const filter = function filter(Bookshelf) {
const Model = Bookshelf.Model.extend({
// Cached copy of the filters setup for this model instance
_filters: null,
// Override these on the various models
enforcedFilters: function enforcedFilters() {
},
defaultFilters: function defaultFilters() {
},
extraFilters: function extraFilters() {
},
preProcessFilters: function preProcessFilters() {
this._filters.statements = gql.json.replaceStatements(this._filters.statements, {prop: /primary_tag/}, function (statement) {
statement.prop = 'tags.slug';
return {
group: [
statement,
{prop: 'posts_tags.sort_order', op: '=', value: 0},
{prop: 'tags.visibility', op: '=', value: 'public'}
]
};
});
this._filters.statements = gql.json.replaceStatements(this._filters.statements, {prop: /primary_author/}, function (statement) {
statement.prop = 'authors.slug';
return {
group: [
statement,
{prop: 'posts_authors.sort_order', op: '=', value: 0},
{prop: 'authors.visibility', op: '=', value: 'public'}
]
};
});
},
enforcedFilters() {},
defaultFilters() {},
extraFilters() {},
/**
* ## Post process Filters
* Post Process filters looking for joins etc
* @TODO refactor this
* @param {object} options
*/
postProcessFilters: function postProcessFilters(options) {
var joinTables = this._filters.joins;
if (joinTables && joinTables.indexOf('tags') > -1) {
// We need to use leftOuterJoin to insure we still include posts which don't have tags in the result
// The where clause should restrict which items are returned
this
.query('leftOuterJoin', 'posts_tags', 'posts_tags.post_id', '=', 'posts.id')
.query('leftOuterJoin', 'tags', 'posts_tags.tag_id', '=', 'tags.id');
// We need to add a group by to counter the double left outer join
// TODO improve on the group by handling
options.groups = options.groups || [];
options.groups.push('posts.id');
}
if (joinTables && joinTables.indexOf('authors') > -1) {
// We need to use leftOuterJoin to insure we still include posts which don't have tags in the result
// The where clause should restrict which items are returned
this
.query('leftOuterJoin', 'posts_authors', 'posts_authors.post_id', '=', 'posts.id')
.query('leftOuterJoin', 'users as authors', 'posts_authors.author_id', '=', 'authors.id');
// We need to add a group by to counter the double left outer join
// TODO improve on the group by handling
options.groups = options.groups || [];
options.groups.push('posts.id');
}
/**
* @deprecated: `author`, will be removed in Ghost 3.0
*/
if (joinTables && joinTables.indexOf('author') > -1) {
this
.query('join', 'users as author', 'author.id', '=', 'posts.author_id');
}
},
/**
* ## fetchAndCombineFilters
* Helper method, uses the combineFilters util to apply filters to the current model instance
* based on options and the set enforced/default filters for this resource
* @param {Object} options
* @returns {Bookshelf.Model}
*/
fetchAndCombineFilters: function fetchAndCombineFilters(options) {
options = options || {};
this._filters = filterUtils.combineFilters(
this.enforcedFilters(options),
this.defaultFilters(options),
options.filter,
this.extraFilters(options)
);
return this;
},
/**
* ## Apply Filters
* Method which makes the necessary query builder calls (through knex) for the filters set
* on this model instance
* @param {Object} options
* @returns {Bookshelf.Model}
* Method which makes the necessary query builder calls (through knex) for the filters set on this model
* instance.
*/
applyDefaultAndCustomFilters: function applyDefaultAndCustomFilters(options) {
var self = this;
const nql = require('@nexes/nql');
// @TODO figure out a better place/way to trigger loading filters
if (!this._filters) {
this.fetchAndCombineFilters(options);
}
let custom = options.filter;
let extra = this.extraFilters(options);
let overrides = this.enforcedFilters(options);
let defaults = this.defaultFilters(options);
if (this._filters) {
if (this.debug) {
gql.json.printStatements(this._filters.statements);
debug('custom', custom);
debug('extra', extra);
debug('enforced', overrides);
debug('default', defaults);
if (extra) {
if (custom) {
custom = `${custom}+${extra}`;
} else {
custom = extra;
}
this.preProcessFilters(options);
this.query(function (qb) {
gql.knexify(qb, self._filters);
});
// Replaces processGQLResult
this.postProcessFilters(options);
}
return this;
try {
this.query((qb) => {
nql(custom, {
relations: RELATIONS,
expansions: EXPANSIONS,
overrides: overrides,
defaults: defaults
}).querySQL(qb);
});
} catch (err) {
throw new common.errors.BadRequestError({
message: common.i18n.t('errors.models.plugins.filter.errorParsing'),
err: err
});
}
}
});

View File

@ -199,6 +199,16 @@ pagination = function pagination(bookshelf) {
throw err;
});
}).catch((err) => {
// CASE: SQL syntax is incorrect
if (err.errno === 1054 || err.errno === 1) {
throw new common.errors.BadRequestError({
message: common.i18n.t('errors.models.general.sql'),
err: err
});
}
throw err;
});
}
});

View File

@ -587,12 +587,12 @@ Post = ghostBookshelf.Model.extend({
// CASE: if the filter contains an `IN` operator, we should return the posts first, which match both tags
if (options.filter && options.filter.match(/(tags|tag):\s?\[.*\]/)) {
order = `count(tags.id) DESC, ${order}`;
order = `(SELECT count(*) FROM posts_tags WHERE post_id = posts.id) DESC, ${order}`;
}
// CASE: if the filter contains an `IN` operator, we should return the posts first, which match both authors
if (options.filter && options.filter.match(/(authors|author):\s?\[.*\]/)) {
order = `count(authors.id) DESC, ${order}`;
order = `(SELECT count(*) FROM posts_authors WHERE post_id = posts.id) DESC, ${order}`;
}
return order;

View File

@ -4,6 +4,18 @@ var _ = require('lodash'),
parseContext = require('./parse-context'),
_private = {};
/**
* @TODO:
*
* - remove if we drop `extraFilters` (see e.g. post model)
* - we currently accept `?status={value}` in the API
* - we currently accept `?staticPages={value}` in the API
* - but instead people should use the `?filter=status:{value}`
*
* This function protects against:
*
* - public context cannot fetch draft/scheduled posts
*/
_private.applyStatusRules = function applyStatusRules(docName, method, opts) {
var err = new common.errors.NoPermissionError({message: common.i18n.t('errors.permissions.applyStatusRules.error', {docName: docName})});

View File

@ -2,8 +2,20 @@ const _ = require('lodash'),
nql = require('@nexes/nql'),
debug = require('ghost-ignition').debug('services:url:generator'),
localUtils = require('./utils'),
aliases = {author: 'authors.slug', tags: 'tags.slug', tag: 'tags.slug', authors: 'authors.slug'};
// @TODO: merge with filter plugin
EXPANSIONS = [{
key: 'author',
replacement: 'authors.slug'
}, {
key: 'tags',
replacement: 'tags.slug'
}, {
key: 'tag',
replacement: 'tags.slug'
}, {
key: 'authors',
replacement: 'authors.slug'
}];
class UrlGenerator {
constructor(router, queue, resources, urls, position) {
@ -18,7 +30,7 @@ class UrlGenerator {
// CASE: routers can define custom filters, but not required.
if (this.router.getFilter()) {
this.filter = this.router.getFilter();
this.nql = nql(this.filter, {aliases});
this.nql = nql(this.filter, {expansions: EXPANSIONS});
debug('filter', this.filter);
}

View File

@ -212,6 +212,9 @@
"reason": " Reason: {reason}."
},
"models": {
"general": {
"sql": "Could not understand request."
},
"subscriber": {
"notEnoughPermission": "You do not have permission to perform this action"
},

View File

@ -298,6 +298,98 @@ describe('Post API', function () {
});
});
it('can retrieve posts filtered by related tag', function (done) {
request.get(localUtils.API.getApiQuery(`posts/?filter=tags.slug:[kitchen-sink]`))
.set('Authorization', 'Bearer ' + ownerAccessToken)
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
var jsonResponse = res.body;
should.exist(jsonResponse.posts);
testUtils.API.checkResponse(jsonResponse, 'posts');
jsonResponse.posts.should.have.length(2);
testUtils.API.checkResponse(jsonResponse.posts[0], 'post');
testUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
jsonResponse.posts[0].slug.should.equal('ghostly-kitchen-sink');
done();
});
});
it('can retrieve posts filtered by related author', function (done) {
request.get(localUtils.API.getApiQuery(`posts/?filter=authors.slug:joe-bloggs`))
.set('Authorization', 'Bearer ' + ownerAccessToken)
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
var jsonResponse = res.body;
should.exist(jsonResponse.posts);
testUtils.API.checkResponse(jsonResponse, 'posts');
jsonResponse.posts.should.have.length(4);
testUtils.API.checkResponse(jsonResponse.posts[0], 'post');
testUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
jsonResponse.posts[0].slug.should.equal('not-so-short-bit-complex');
done();
});
});
it('can retrieve posts filtered by primary_tag', function (done) {
request.get(localUtils.API.getApiQuery(`posts/?filter=primary_tag:kitchen-sink`))
.set('Authorization', 'Bearer ' + ownerAccessToken)
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
var jsonResponse = res.body;
should.exist(jsonResponse.posts);
testUtils.API.checkResponse(jsonResponse, 'posts');
jsonResponse.posts.should.have.length(2);
testUtils.API.checkResponse(jsonResponse.posts[0], 'post');
testUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
jsonResponse.posts[0].slug.should.equal('ghostly-kitchen-sink');
done();
});
});
it('can retrieve posts filtered by primary_author', function (done) {
request.get(localUtils.API.getApiQuery(`posts/?filter=primary_author:joe-bloggs`))
.set('Authorization', 'Bearer ' + ownerAccessToken)
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
var jsonResponse = res.body;
should.exist(jsonResponse.posts);
testUtils.API.checkResponse(jsonResponse, 'posts');
jsonResponse.posts.should.have.length(4);
testUtils.API.checkResponse(jsonResponse.posts[0], 'post');
testUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
jsonResponse.posts[0].slug.should.equal('not-so-short-bit-complex');
done();
});
});
it('can retrieve just featured posts', function (done) {
request.get(localUtils.API.getApiQuery('posts/?filter=featured:true'))
.set('Authorization', 'Bearer ' + ownerAccessToken)

View File

@ -132,6 +132,35 @@ describe('Public API', function () {
});
});
it('browse posts with inverse filters', function (done) {
request.get(localUtils.API.getApiQuery('posts/?client_id=ghost-admin&client_secret=not_available&filter=tag:-[bacon,pollo,getting-started]&include=tags'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
const jsonResponse = res.body;
should.not.exist(res.headers['x-cache-invalidate']);
should.exist(jsonResponse.posts);
testUtils.API.checkResponse(jsonResponse, 'posts');
testUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
jsonResponse.posts.should.have.length(2);
jsonResponse.posts[0].slug.should.eql('not-so-short-bit-complex');
jsonResponse.posts[0].tags.length.should.eql(0);
jsonResponse.posts[1].slug.should.eql('short-and-sweet');
jsonResponse.posts[1].tags.length.should.eql(1);
jsonResponse.posts[1].tags[0].slug.should.eql('chorizo');
done();
});
});
it('browse posts with author filter', function (done) {
request.get(localUtils.API.getApiQuery('posts/?client_id=ghost-admin&client_secret=not_available&filter=authors:[joe-bloggs,pat,ghost]&include=authors'))
.expect('Content-Type', /json/)
@ -836,7 +865,7 @@ describe('Public API', function () {
// Each user should have the correct count
_.find(jsonResponse.users, {slug:'joe-bloggs'}).count.posts.should.eql(4);
_.find(jsonResponse.users, {slug:'contributor'}).count.posts.should.eql(0);
_.find(jsonResponse.users, {slug:'slimer-mcectoplasm'}).count.posts.should.eql(0);
_.find(jsonResponse.users, {slug:'slimer-mcectoplasm'}).count.posts.should.eql(1);
_.find(jsonResponse.users, {slug:'jimothy-bogendath'}).count.posts.should.eql(0);
_.find(jsonResponse.users, {slug: 'smith-wellingsworth'}).count.posts.should.eql(0);
_.find(jsonResponse.users, {slug:'ghost'}).count.posts.should.eql(7);
@ -850,8 +879,8 @@ describe('Public API', function () {
ids.should.eql([
testUtils.DataGenerator.Content.users[1].id,
testUtils.DataGenerator.Content.users[2].id,
testUtils.DataGenerator.Content.users[3].id,
testUtils.DataGenerator.Content.users[7].id,
testUtils.DataGenerator.Content.users[3].id,
testUtils.DataGenerator.Content.users[0].id
]);

View File

@ -147,7 +147,7 @@ describe('Authors Content API V2', function () {
// Each user should have the correct count
_.find(jsonResponse.authors, {slug:'joe-bloggs'}).count.posts.should.eql(4);
_.find(jsonResponse.authors, {slug:'contributor'}).count.posts.should.eql(0);
_.find(jsonResponse.authors, {slug:'slimer-mcectoplasm'}).count.posts.should.eql(0);
_.find(jsonResponse.authors, {slug:'slimer-mcectoplasm'}).count.posts.should.eql(1);
_.find(jsonResponse.authors, {slug:'jimothy-bogendath'}).count.posts.should.eql(0);
_.find(jsonResponse.authors, {slug: 'smith-wellingsworth'}).count.posts.should.eql(0);
_.find(jsonResponse.authors, {slug:'ghost'}).count.posts.should.eql(7);
@ -161,8 +161,8 @@ describe('Authors Content API V2', function () {
ids.should.eql([
testUtils.DataGenerator.Content.users[1].id,
testUtils.DataGenerator.Content.users[2].id,
testUtils.DataGenerator.Content.users[3].id,
testUtils.DataGenerator.Content.users[7].id,
testUtils.DataGenerator.Content.users[3].id,
testUtils.DataGenerator.Content.users[0].id
]);

View File

@ -49,6 +49,26 @@ describe('Pages', function () {
});
});
it('browse pages with page:false', function () {
const key = localUtils.getValidKey();
return request.get(localUtils.API.getApiQuery(`pages/?key=${key}&filter=page:false`))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
res.headers.vary.should.eql('Accept-Encoding');
should.exist(res.headers['access-control-allow-origin']);
should.not.exist(res.headers['x-cache-invalidate']);
const jsonResponse = res.body;
should.exist(jsonResponse.pages);
should.exist(jsonResponse.meta);
jsonResponse.pages.should.have.length(0);
});
});
it('read page', function () {
const key = localUtils.getValidKey();
return request.get(localUtils.API.getApiQuery(`pages/${testUtils.DataGenerator.Content.posts[5].id}/?key=${key}`))

View File

@ -168,6 +168,28 @@ describe('Posts', function () {
should.exist(jsonResponse.posts);
testUtils.API.checkResponse(jsonResponse, 'posts');
testUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
jsonResponse.posts.should.have.length(0);
done();
});
});
it('browse posts with basic page filter should not return pages', function (done) {
request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&filter=(page:true,page:false)`))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
const jsonResponse = res.body;
should.not.exist(res.headers['x-cache-invalidate']);
should.exist(jsonResponse.posts);
testUtils.API.checkResponse(jsonResponse, 'posts');
testUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
jsonResponse.posts.should.have.length(11);
jsonResponse.posts.forEach((post) => {
post.page.should.be.false();
@ -178,7 +200,7 @@ describe('Posts', function () {
});
it('browse posts with author filter', function (done) {
request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&filter=authors:[joe-bloggs,pat,ghost]&include=authors`))
request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&filter=authors:[joe-bloggs,pat,ghost,slimer-mcectoplasm]&include=authors`))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
@ -198,12 +220,18 @@ describe('Posts', function () {
// We should have 2 matching items
jsonResponse.posts.should.be.an.Array().with.lengthOf(11);
// The API orders by number of matched authors.
jsonResponse.posts[0].slug.should.eql('not-so-short-bit-complex');
// Each post must either have the author 'joe-bloggs' or 'ghost', 'pat' is non existing author
const authors = _.map(jsonResponse.posts, function (post) {
const primaryAuthors = _.map(jsonResponse.posts, function (post) {
return post.primary_author.slug;
});
authors.should.matchAny(/joe-bloggs|ghost'/);
primaryAuthors.should.matchAny(/joe-bloggs|ghost'/);
_.filter(primaryAuthors, (value) => {return value === 'ghost';}).length.should.eql(7);
_.filter(primaryAuthors, (value) => {return value === 'joe-bloggs';}).length.should.eql(4);
done();
});
});

View File

@ -28,7 +28,7 @@ describe('Unit: v2/utils/serializers/input/pages', function () {
frame.options.filter.should.eql('status:published+tag:eins+page:true');
});
it('remove existing page filter', function () {
it('combine filters', function () {
const apiConfig = {};
const frame = {
options: {
@ -38,10 +38,10 @@ describe('Unit: v2/utils/serializers/input/pages', function () {
};
serializers.input.pages.browse(apiConfig, frame);
frame.options.filter.should.eql('tag:eins+page:true');
frame.options.filter.should.eql('page:false+tag:eins+page:true');
});
it('remove existing page filter', function () {
it('combine filters', function () {
const apiConfig = {};
const frame = {
options: {
@ -51,7 +51,7 @@ describe('Unit: v2/utils/serializers/input/pages', function () {
};
serializers.input.pages.browse(apiConfig, frame);
frame.options.filter.should.eql('page:true');
frame.options.filter.should.eql('page:false+page:true');
});
it('remove mobiledoc option from formats', function () {

View File

@ -49,7 +49,7 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
frame.options.filter.should.eql('status:published+tag:eins+page:false');
});
it('remove existing page filter', function () {
it('combine filters', function () {
const apiConfig = {};
const frame = {
options: {
@ -62,10 +62,10 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
};
serializers.input.posts.browse(apiConfig, frame);
frame.options.filter.should.eql('tag:eins+page:false');
frame.options.filter.should.eql('page:true+tag:eins+page:false');
});
it('remove existing page filter', function () {
it('combine filters', function () {
const apiConfig = {};
const frame = {
options: {
@ -78,7 +78,23 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
};
serializers.input.posts.browse(apiConfig, frame);
frame.options.filter.should.eql('page:false');
frame.options.filter.should.eql('page:true+page:false');
});
it('combine filters', function () {
const apiConfig = {};
const frame = {
options: {
context: {
user: 0,
api_key_id: 1
},
filter: '(page:true,page:false)'
}
};
serializers.input.posts.browse(apiConfig, frame);
frame.options.filter.should.eql('(page:true,page:false)+page:false');
});
it('remove mobiledoc option from formats', function () {

View File

@ -1,663 +0,0 @@
var should = require('should'),
sinon = require('sinon'),
rewire = require('rewire'),
_ = require('lodash'),
filter = rewire('../../../../server/models/plugins/filter'),
models = require('../../../../server/models'),
ghostBookshelf,
sandbox = sinon.sandbox.create();
describe('Filter', function () {
before(function () {
models.init();
ghostBookshelf = _.cloneDeep(models.Base);
});
beforeEach(function () {
// re-initialise the plugin with the rewired version
filter(ghostBookshelf);
});
afterEach(function () {
sandbox.restore();
filter = rewire('../../../../server/models/plugins/filter');
});
describe('Base Model', function () {
describe('Enforced & Default Filters', function () {
it('should add filter functions to prototype', function () {
ghostBookshelf.Model.prototype.enforcedFilters.should.be.a.Function();
ghostBookshelf.Model.prototype.defaultFilters.should.be.a.Function();
});
it('filter functions should return undefined', function () {
should(ghostBookshelf.Model.prototype.enforcedFilters()).be.undefined();
should(ghostBookshelf.Model.prototype.defaultFilters()).be.undefined();
});
});
describe('Fetch And Combine Filters', function () {
var filterUtils;
beforeEach(function () {
filterUtils = filter.__get__('filterUtils');
filterUtils.combineFilters = sandbox.stub();
});
it('should add function to prototype', function () {
ghostBookshelf.Model.prototype.fetchAndCombineFilters.should.be.a.Function();
});
it('should set _filters to be the result of combineFilters', function () {
filterUtils.combineFilters.returns({
statements: [
{prop: 'page', op: '=', value: true}
]
});
var result = ghostBookshelf.Model.prototype.fetchAndCombineFilters();
result._filters.should.eql({
statements: [
{prop: 'page', op: '=', value: true}
]
});
});
it('should call combineFilters with undefined x4 if passed no options', function () {
var result = ghostBookshelf.Model.prototype.fetchAndCombineFilters();
filterUtils.combineFilters.calledOnce.should.be.true();
filterUtils.combineFilters.firstCall.args.should.eql([undefined, undefined, undefined, undefined]);
should(result._filters).be.undefined();
});
it('should call combineFilters with enforced filters if set', function () {
var filterSpy = sandbox.stub(ghostBookshelf.Model.prototype, 'enforcedFilters')
.returns('status:published'),
result;
result = ghostBookshelf.Model.prototype.fetchAndCombineFilters();
filterSpy.calledOnce.should.be.true();
filterUtils.combineFilters.calledOnce.should.be.true();
filterUtils.combineFilters.firstCall.args.should.eql(['status:published', undefined, undefined, undefined]);
should(result._filters).be.undefined();
});
it('should call combineFilters with default filters if set', function () {
var filterSpy = sandbox.stub(ghostBookshelf.Model.prototype, 'defaultFilters')
.returns('page:false'),
result;
result = ghostBookshelf.Model.prototype.fetchAndCombineFilters();
filterSpy.calledOnce.should.be.true();
filterUtils.combineFilters.calledOnce.should.be.true();
filterUtils.combineFilters.firstCall.args.should.eql([undefined, 'page:false', undefined, undefined]);
should(result._filters).be.undefined();
});
it('should call combineFilters with custom filters if set', function () {
var result = ghostBookshelf.Model.prototype.fetchAndCombineFilters({
filter: 'tag:photo'
});
filterUtils.combineFilters.calledOnce.should.be.true();
filterUtils.combineFilters.firstCall.args.should.eql([undefined, undefined, 'tag:photo', undefined]);
should(result._filters).be.undefined();
});
it('should call combineFilters with old-style custom filters if set', function () {
sandbox.stub(ghostBookshelf.Model.prototype, 'extraFilters').returns('author:cameron');
const result = ghostBookshelf.Model.prototype.fetchAndCombineFilters({});
filterUtils.combineFilters.calledOnce.should.be.true();
filterUtils.combineFilters.firstCall.args.should.eql([undefined, undefined, undefined, 'author:cameron']);
should(result._filters).be.undefined();
});
it('should call combineFilters with enforced and defaults if set', function () {
var filterSpy = sandbox.stub(ghostBookshelf.Model.prototype, 'enforcedFilters')
.returns('status:published'),
filterSpy2 = sandbox.stub(ghostBookshelf.Model.prototype, 'defaultFilters')
.returns('page:false'),
result;
result = ghostBookshelf.Model.prototype.fetchAndCombineFilters();
filterSpy.calledOnce.should.be.true();
filterSpy2.calledOnce.should.be.true();
filterUtils.combineFilters.calledOnce.should.be.true();
filterUtils.combineFilters.firstCall.args.should.eql(['status:published', 'page:false', undefined, undefined]);
should(result._filters).be.undefined();
});
it('should call combineFilters with all values if set', function () {
sandbox.stub(ghostBookshelf.Model.prototype, 'defaultFilters').returns('page:false');
sandbox.stub(ghostBookshelf.Model.prototype, 'enforcedFilters').returns('status:published');
sandbox.stub(ghostBookshelf.Model.prototype, 'extraFilters').returns('author:cameron');
const result = ghostBookshelf.Model.prototype.fetchAndCombineFilters({
filter: 'tag:photo'
});
ghostBookshelf.Model.prototype.enforcedFilters.calledOnce.should.be.true();
ghostBookshelf.Model.prototype.defaultFilters.calledOnce.should.be.true();
filterUtils.combineFilters.calledOnce.should.be.true();
filterUtils.combineFilters.firstCall.args
.should.eql(['status:published', 'page:false', 'tag:photo', 'author:cameron']);
should(result._filters).be.undefined();
});
});
describe('Apply Default and Custom Filters', function () {
var fetchSpy,
restoreGQL,
filterGQL;
beforeEach(function () {
filterGQL = {};
fetchSpy = sandbox.stub(ghostBookshelf.Model.prototype, 'fetchAndCombineFilters');
filterGQL.knexify = sandbox.stub();
filterGQL.json = {
printStatements: sandbox.stub(),
replaceStatements: sandbox.stub().returnsArg(0)
};
restoreGQL = filter.__set__('gql', filterGQL);
});
afterEach(function () {
restoreGQL();
});
it('should call fetchAndCombineFilters if _filters not set', function () {
var result = ghostBookshelf.Model.prototype.applyDefaultAndCustomFilters();
fetchSpy.calledOnce.should.be.true();
should(result._filters).be.null();
});
it('should NOT call fetchAndCombineFilters if _filters IS set', function () {
ghostBookshelf.Model.prototype._filters = 'test';
var result = ghostBookshelf.Model.prototype.applyDefaultAndCustomFilters();
fetchSpy.called.should.be.false();
result._filters.should.eql('test');
});
it('should call knexify with the filters that are set', function () {
ghostBookshelf.Model.prototype._filters = {
statements: [
{prop: 'title', op: '=', value: 'Hello Word'}
]
};
ghostBookshelf.Model.prototype.applyDefaultAndCustomFilters();
fetchSpy.called.should.be.false();
filterGQL.knexify.called.should.be.true();
filterGQL.knexify.firstCall.args[1].should.eql({
statements: [
{prop: 'title', op: '=', value: 'Hello Word'}
]
});
});
it('should print statements in debug mode', function () {
ghostBookshelf.Model.prototype.debug = true;
ghostBookshelf.Model.prototype._filters = {
statements: [
{prop: 'tags', op: 'IN', value: ['photo', 'video']}
]
};
ghostBookshelf.Model.prototype.applyDefaultAndCustomFilters();
filterGQL.json.printStatements.calledOnce.should.be.true();
filterGQL.json.printStatements.firstCall.args[0].should.eql([
{prop: 'tags', op: 'IN', value: ['photo', 'video']}
]);
});
});
describe('Pre Process Filters', function () {
it('should not have tests yet, as this needs to be removed');
});
describe('Post Process Filters', function () {
it('should not have tests yet, as this needs to be removed');
});
});
describe('Utils', function () {
describe('Combine Filters', function () {
var gql, combineFilters, parseSpy, mergeSpy, findSpy, rejectSpy;
beforeEach(function () {
combineFilters = filter.__get__('filterUtils').combineFilters;
gql = filter.__get__('gql');
parseSpy = sandbox.spy(gql, 'parse');
mergeSpy = sandbox.spy(gql.json, 'mergeStatements');
findSpy = sandbox.spy(gql.json, 'findStatement');
rejectSpy = sandbox.spy(gql.json, 'rejectStatements');
});
it('should return empty statement object when there are no filters', function () {
combineFilters().should.eql({statements: []});
parseSpy.called.should.be.false();
mergeSpy.calledOnce.should.be.true();
findSpy.called.should.be.false();
rejectSpy.called.should.be.false();
});
describe('Single filter rules', function () {
it('should return enforced filters if only those are set', function () {
combineFilters('status:published').should.eql({
statements: [
{prop: 'status', op: '=', value: 'published'}
]
});
parseSpy.calledOnce.should.be.true();
mergeSpy.calledTwice.should.be.true();
findSpy.called.should.be.false();
rejectSpy.called.should.be.false();
});
it('should return default filters if only those are set (undefined)', function () {
combineFilters(undefined, 'page:false').should.eql({
statements: [
{prop: 'page', op: '=', value: false}
]
});
parseSpy.calledOnce.should.be.true();
mergeSpy.calledTwice.should.be.true();
findSpy.called.should.be.false();
rejectSpy.called.should.be.false();
});
it('should return default filters if only those are set (null)', function () {
combineFilters(null, 'page:false').should.eql({
statements: [
{prop: 'page', op: '=', value: false}
]
});
parseSpy.calledOnce.should.be.true();
mergeSpy.calledTwice.should.be.true();
findSpy.called.should.be.false();
rejectSpy.called.should.be.false();
});
it('should return custom filters if only those are set', function () {
combineFilters(null, null, 'tags:[photo,video]').should.eql({
statements: [
{prop: 'tags', op: 'IN', value: ['photo', 'video']}
]
});
parseSpy.calledOnce.should.be.true();
mergeSpy.calledOnce.should.be.true();
findSpy.called.should.be.false();
rejectSpy.called.should.be.false();
});
it('does NOT call parse on enforced filter if it is NOT a string', function () {
var statement = {
statements: [
{prop: 'page', op: '=', value: false}
]
};
combineFilters(statement, null, null).should.eql({
statements: [
{prop: 'page', op: '=', value: false}
]
});
parseSpy.calledOnce.should.be.false();
mergeSpy.calledOnce.should.be.false();
findSpy.called.should.be.false();
rejectSpy.called.should.be.false();
});
it('does NOT call parse on default filter if it is NOT a string', function () {
var statement = {
statements: [
{prop: 'page', op: '=', value: false}
]
};
combineFilters(null, statement, null).should.eql({
statements: [
{prop: 'page', op: '=', value: false}
]
});
parseSpy.calledOnce.should.be.false();
mergeSpy.calledOnce.should.be.false();
findSpy.called.should.be.false();
rejectSpy.called.should.be.false();
});
it('does NOT call parse on custom filter if it is NOT a string', function () {
var statement = {
statements: [
{prop: 'page', op: '=', value: false}
]
};
combineFilters(null, null, statement).should.eql({
statements: [
{prop: 'page', op: '=', value: false}
]
});
parseSpy.calledOnce.should.be.false();
mergeSpy.calledOnce.should.be.true();
findSpy.called.should.be.false();
rejectSpy.called.should.be.false();
});
});
describe('Combo filter rules', function () {
it('should merge enforced and default filters if both are provided', function () {
combineFilters('status:published', 'page:false').should.eql({
statements: [
{prop: 'status', op: '=', value: 'published'},
{prop: 'page', op: '=', value: false, func: 'and'}
]
});
parseSpy.calledTwice.should.be.true();
mergeSpy.calledTwice.should.be.true();
findSpy.called.should.be.false();
rejectSpy.called.should.be.false();
});
it('should merge custom filters if more than one is provided', function () {
combineFilters(null, null, 'tag:photo', 'featured:true').should.eql({
statements: [
{prop: 'tag', op: '=', value: 'photo'},
{prop: 'featured', op: '=', value: true, func: 'and'}
]
});
parseSpy.calledTwice.should.be.true();
mergeSpy.calledOnce.should.be.true();
findSpy.called.should.be.false();
rejectSpy.called.should.be.false();
});
it('should try to reduce custom filters if custom and enforced are provided', function () {
combineFilters('status:published', null, 'tag:photo').should.eql({
statements: [
{
group: [
{prop: 'status', op: '=', value: 'published'}
]
},
{
group: [
{prop: 'tag', op: '=', value: 'photo'}
], func: 'and'
}
]
});
parseSpy.calledTwice.should.be.true();
mergeSpy.calledTwice.should.be.true();
rejectSpy.calledOnce.should.be.true();
rejectSpy.firstCall.args[0].should.eql([{op: '=', value: 'photo', prop: 'tag'}]);
findSpy.calledOnce.should.be.true();
findSpy.getCall(0).args.should.eql([
[{op: '=', value: 'published', prop: 'status'}],
{op: '=', value: 'photo', prop: 'tag'},
'prop'
]);
});
it('should actually reduce custom filters if one matches enforced', function () {
combineFilters('status:published', null, 'tag:photo,status:draft').should.eql({
statements: [
{
group: [
{prop: 'status', op: '=', value: 'published'}
]
},
{
group: [
{prop: 'tag', op: '=', value: 'photo'}
], func: 'and'
}
]
});
parseSpy.calledTwice.should.be.true();
mergeSpy.calledTwice.should.be.true();
rejectSpy.calledOnce.should.be.true();
rejectSpy.firstCall.args[0].should.eql([{op: '=', value: 'photo', prop: 'tag'},
{op: '=', value: 'draft', prop: 'status', func: 'or'}]);
findSpy.calledTwice.should.be.true();
findSpy.getCall(0).args.should.eql([
[{op: '=', value: 'published', prop: 'status'}],
{op: '=', value: 'photo', prop: 'tag'},
'prop'
]);
findSpy.getCall(1).args.should.eql([
[{op: '=', value: 'published', prop: 'status'}],
{op: '=', value: 'draft', prop: 'status', func: 'or'},
'prop'
]);
});
it('should return only enforced if custom filters are reduced to nothing', function () {
combineFilters('status:published', null, 'status:draft').should.eql({
statements: [
{prop: 'status', op: '=', value: 'published'}
]
});
parseSpy.calledTwice.should.be.true();
mergeSpy.calledTwice.should.be.true();
rejectSpy.calledOnce.should.be.true();
rejectSpy.firstCall.args[0].should.eql([{op: '=', value: 'draft', prop: 'status'}]);
findSpy.calledOnce.should.be.true();
findSpy.getCall(0).args.should.eql([
[{op: '=', value: 'published', prop: 'status'}],
{op: '=', value: 'draft', prop: 'status'},
'prop'
]);
});
it('should try to reduce default filters if default and custom are provided', function () {
combineFilters(null, 'page:false', 'tag:photo').should.eql({
statements: [
{
group: [
{prop: 'page', op: '=', value: false}
]
},
{
group: [
{prop: 'tag', op: '=', value: 'photo'}
], func: 'and'
}
]
});
parseSpy.calledTwice.should.be.true();
mergeSpy.calledTwice.should.be.true();
rejectSpy.calledOnce.should.be.true();
rejectSpy.firstCall.args[0].should.eql([{op: '=', value: false, prop: 'page'}]);
findSpy.calledOnce.should.be.true();
findSpy.firstCall.args.should.eql([
[{op: '=', prop: 'tag', value: 'photo'}],
{op: '=', prop: 'page', value: false},
'prop'
]);
});
it('should actually reduce default filters if one matches custom', function () {
combineFilters(null, 'page:false,author:cameron', 'tag:photo+page:true').should.eql({
statements: [
{
group: [
// currently has func: or needs fixing
{prop: 'author', op: '=', value: 'cameron'}
]
},
{
group: [
{prop: 'tag', op: '=', value: 'photo'},
{prop: 'page', op: '=', value: true, func: 'and'}
], func: 'and'
}
]
});
parseSpy.calledTwice.should.be.true();
mergeSpy.calledTwice.should.be.true();
rejectSpy.calledOnce.should.be.true();
rejectSpy.firstCall.args[0].should.eql([
{op: '=', value: false, prop: 'page'},
{op: '=', value: 'cameron', prop: 'author'}
]);
findSpy.calledTwice.should.be.true();
findSpy.firstCall.args.should.eql([
[
{op: '=', prop: 'tag', value: 'photo'},
{func: 'and', op: '=', prop: 'page', value: true}
],
{op: '=', prop: 'page', value: false},
'prop'
]);
findSpy.secondCall.args.should.eql([
[
{op: '=', prop: 'tag', value: 'photo'},
{func: 'and', op: '=', prop: 'page', value: true}
],
{op: '=', prop: 'author', value: 'cameron'},
'prop'
]);
});
it('should return only custom if default filters are reduced to nothing', function () {
combineFilters(null, 'page:false', 'tag:photo,page:true').should.eql({
statements: [
{prop: 'tag', op: '=', value: 'photo'},
{prop: 'page', op: '=', value: true, func: 'or'}
]
});
parseSpy.calledTwice.should.be.true();
mergeSpy.calledTwice.should.be.true();
rejectSpy.calledOnce.should.be.true();
rejectSpy.firstCall.args[0].should.eql([{op: '=', value: false, prop: 'page'}]);
findSpy.calledOnce.should.be.true();
findSpy.firstCall.args.should.eql([
[
{op: '=', prop: 'tag', value: 'photo'},
{func: 'or', op: '=', prop: 'page', value: true}
],
{op: '=', prop: 'page', value: false},
'prop'
]);
});
it('should return a merger of enforced and defaults plus custom filters if provided', function () {
combineFilters('status:published', 'page:false', 'tag:photo').should.eql({
statements: [
{
group: [
{prop: 'status', op: '=', value: 'published'},
{prop: 'page', op: '=', value: false, func: 'and'}
]
},
{
group: [
{prop: 'tag', op: '=', value: 'photo'}
], func: 'and'
}
]
});
parseSpy.calledThrice.should.be.true();
mergeSpy.calledTwice.should.be.true();
rejectSpy.calledTwice.should.be.true();
rejectSpy.firstCall.args[0].should.eql([{op: '=', value: 'photo', prop: 'tag'}]);
rejectSpy.secondCall.args[0].should.eql([{op: '=', value: false, prop: 'page', func: 'and'}]);
findSpy.calledTwice.should.be.true();
findSpy.firstCall.args.should.eql([
[{op: '=', prop: 'status', value: 'published'}],
{op: '=', prop: 'tag', value: 'photo'},
'prop'
]);
findSpy.secondCall.args.should.eql([
[{op: '=', prop: 'tag', value: 'photo'}],
{func: 'and', op: '=', prop: 'page', value: false},
'prop'
]);
});
it('should handle getting enforced, default and multiple custom filters', function () {
combineFilters('status:published', 'page:false', 'tag:[photo,video],author:cameron', 'status:draft,page:false').should.eql({
statements: [
{
group: [
{prop: 'status', op: '=', value: 'published'}
]
},
{
group: [
{prop: 'tag', op: 'IN', value: ['photo', 'video']},
{prop: 'author', op: '=', value: 'cameron', func: 'or'},
{prop: 'page', op: '=', value: false, func: 'or'}
], func: 'and'
}
]
});
parseSpy.callCount.should.eql(4);
mergeSpy.calledTwice.should.be.true();
rejectSpy.callCount.should.eql(2);
rejectSpy.getCall(0).args[0].should.eql([{op: 'IN', value: ['photo', 'video'], prop: 'tag'},
{op: '=', value: 'cameron', prop: 'author', func: 'or'},
{op: '=', value: 'draft', prop: 'status', func: 'and'},
{op: '=', value: false, prop: 'page', func: 'or'}]);
rejectSpy.getCall(1).args[0].should.eql([{op: '=', value: false, prop: 'page'}]);
findSpy.callCount.should.eql(5);
findSpy.getCall(0).args.should.eql([
[{op: '=', value: 'published', prop: 'status'}],
{op: 'IN', prop: 'tag', value: ['photo', 'video']},
'prop'
]);
findSpy.getCall(1).args.should.eql([
[{op: '=', value: 'published', prop: 'status'}],
{prop: 'author', op: '=', value: 'cameron', func: 'or'},
'prop'
]);
findSpy.getCall(2).args.should.eql([
[{op: '=', value: 'published', prop: 'status'}],
{op: '=', value: 'draft', prop: 'status', func: 'and'},
'prop'
]);
findSpy.getCall(3).args.should.eql([
[{op: '=', value: 'published', prop: 'status'}],
{prop: 'page', op: '=', value: false, func: 'or'},
'prop'
]);
findSpy.getCall(4).args.should.eql([
[
{op: 'IN', value: ['photo', 'video'], prop: 'tag'},
{op: '=', value: 'cameron', prop: 'author', func: 'or'},
{op: '=', value: false, prop: 'page', func: 'or'}
],
{op: '=', value: false, prop: 'page'},
'prop'
]);
});
});
});
});
});

View File

@ -1,21 +1,16 @@
/* eslint no-invalid-this:0 */
const should = require('should'),
url = require('url'),
sinon = require('sinon'),
_ = require('lodash'),
Promise = require('bluebird'),
testUtils = require('../../utils'),
knex = require('../../../server/data/db').knex,
db = require('../../../server/data/db'),
urlService = require('../../../server/services/url'),
schema = require('../../../server/data/schema'),
models = require('../../../server/models'),
common = require('../../../server/lib/common'),
security = require('../../../server/lib/security'),
sandbox = sinon.sandbox.create(),
userIdFor = testUtils.users.ids,
context = testUtils.context;
const _ = require('lodash');
const should = require('should');
const sinon = require('sinon');
const Promise = require('bluebird');
const testUtils = require('../../utils');
const knex = require('../../../server/data/db').knex;
const urlService = require('../../../server/services/url');
const schema = require('../../../server/data/schema');
const models = require('../../../server/models');
const common = require('../../../server/lib/common');
const security = require('../../../server/lib/security');
const sandbox = sinon.sandbox.create();
describe('Unit: models/post', function () {
const mockDb = require('mock-knex');
@ -51,22 +46,22 @@ describe('Unit: models/post', function () {
withRelated: ['tags']
}).then(() => {
queries.length.should.eql(2);
queries[0].sql.should.eql('select count(distinct posts.id) as aggregate from `posts` left outer join `posts_tags` on `posts_tags`.`post_id` = `posts`.`id` left outer join `tags` on `posts_tags`.`tag_id` = `tags`.`id` where (`posts`.`page` = ? and `posts`.`status` = ?) and (`tags`.`slug` in (?, ?) and `posts`.`id` != ?)');
queries[0].sql.should.eql('select count(distinct posts.id) as aggregate from `posts` where ((`posts`.`id` != ? and `posts`.`id` in (select `posts_tags`.`post_id` from `posts_tags` inner join `tags` on `tags`.`id` = `posts_tags`.`tag_id` where `tags`.`slug` in (?, ?))) and (`posts`.`page` = ? and `posts`.`status` = ?))');
queries[0].bindings.should.eql([
false,
'published',
testUtils.filterData.data.posts[3].id,
'photo',
'video',
testUtils.filterData.data.posts[3].id
false,
'published'
]);
queries[1].sql.should.eql('select `posts`.* from `posts` left outer join `posts_tags` on `posts_tags`.`post_id` = `posts`.`id` left outer join `tags` on `posts_tags`.`tag_id` = `tags`.`id` where (`posts`.`page` = ? and `posts`.`status` = ?) and (`tags`.`slug` in (?, ?) and `posts`.`id` != ?) group by `posts`.`id` order by count(tags.id) DESC, CASE WHEN posts.status = \'scheduled\' THEN 1 WHEN posts.status = \'draft\' THEN 2 ELSE 3 END ASC,CASE WHEN posts.status != \'draft\' THEN posts.published_at END DESC,posts.updated_at DESC,posts.id DESC limit ?');
queries[1].sql.should.eql('select `posts`.* from `posts` where ((`posts`.`id` != ? and `posts`.`id` in (select `posts_tags`.`post_id` from `posts_tags` inner join `tags` on `tags`.`id` = `posts_tags`.`tag_id` where `tags`.`slug` in (?, ?))) and (`posts`.`page` = ? and `posts`.`status` = ?)) order by (SELECT count(*) FROM posts_tags WHERE post_id = posts.id) DESC, CASE WHEN posts.status = \'scheduled\' THEN 1 WHEN posts.status = \'draft\' THEN 2 ELSE 3 END ASC,CASE WHEN posts.status != \'draft\' THEN posts.published_at END DESC,posts.updated_at DESC,posts.id DESC limit ?');
queries[1].bindings.should.eql([
false,
'published',
testUtils.filterData.data.posts[3].id,
'photo',
'video',
testUtils.filterData.data.posts[3].id,
false,
'published',
3
]);
});
@ -86,22 +81,22 @@ describe('Unit: models/post', function () {
withRelated: ['authors', 'tags']
}).then(() => {
queries.length.should.eql(2);
queries[0].sql.should.eql('select count(distinct posts.id) as aggregate from `posts` left outer join `posts_tags` on `posts_tags`.`post_id` = `posts`.`id` left outer join `tags` on `posts_tags`.`tag_id` = `tags`.`id` left outer join `posts_authors` on `posts_authors`.`post_id` = `posts`.`id` left outer join `users` as `authors` on `posts_authors`.`author_id` = `authors`.`id` where (`posts`.`page` = ? and `posts`.`status` = ?) and (`authors`.`slug` in (?, ?) and (`tags`.`slug` = ? or `posts`.`feature_image` is not null))');
queries[0].sql.should.eql('select count(distinct posts.id) as aggregate from `posts` where (((`posts`.`feature_image` is not null or `posts`.`id` in (select `posts_tags`.`post_id` from `posts_tags` inner join `tags` on `tags`.`id` = `posts_tags`.`tag_id` where `tags`.`slug` = ?)) and `posts`.`id` in (select `posts_authors`.`post_id` from `posts_authors` inner join `users` as `authors` on `authors`.`id` = `posts_authors`.`author_id` where `authors`.`slug` in (?, ?))) and (`posts`.`page` = ? and `posts`.`status` = ?))');
queries[0].bindings.should.eql([
false,
'published',
'hash-audio',
'leslie',
'pat',
'hash-audio'
false,
'published'
]);
queries[1].sql.should.eql('select `posts`.* from `posts` left outer join `posts_tags` on `posts_tags`.`post_id` = `posts`.`id` left outer join `tags` on `posts_tags`.`tag_id` = `tags`.`id` left outer join `posts_authors` on `posts_authors`.`post_id` = `posts`.`id` left outer join `users` as `authors` on `posts_authors`.`author_id` = `authors`.`id` where (`posts`.`page` = ? and `posts`.`status` = ?) and (`authors`.`slug` in (?, ?) and (`tags`.`slug` = ? or `posts`.`feature_image` is not null)) group by `posts`.`id`, `posts`.`id` order by count(authors.id) DESC, CASE WHEN posts.status = \'scheduled\' THEN 1 WHEN posts.status = \'draft\' THEN 2 ELSE 3 END ASC,CASE WHEN posts.status != \'draft\' THEN posts.published_at END DESC,posts.updated_at DESC,posts.id DESC limit ?');
queries[1].sql.should.eql('select `posts`.* from `posts` where (((`posts`.`feature_image` is not null or `posts`.`id` in (select `posts_tags`.`post_id` from `posts_tags` inner join `tags` on `tags`.`id` = `posts_tags`.`tag_id` where `tags`.`slug` = ?)) and `posts`.`id` in (select `posts_authors`.`post_id` from `posts_authors` inner join `users` as `authors` on `authors`.`id` = `posts_authors`.`author_id` where `authors`.`slug` in (?, ?))) and (`posts`.`page` = ? and `posts`.`status` = ?)) order by (SELECT count(*) FROM posts_authors WHERE post_id = posts.id) DESC, CASE WHEN posts.status = \'scheduled\' THEN 1 WHEN posts.status = \'draft\' THEN 2 ELSE 3 END ASC,CASE WHEN posts.status != \'draft\' THEN posts.published_at END DESC,posts.updated_at DESC,posts.id DESC limit ?');
queries[1].bindings.should.eql([
false,
'published',
'hash-audio',
'leslie',
'pat',
'hash-audio',
false,
'published',
15
]);
});
@ -122,18 +117,18 @@ describe('Unit: models/post', function () {
withRelated: ['tags']
}).then(() => {
queries.length.should.eql(2);
queries[0].sql.should.eql('select count(distinct posts.id) as aggregate from `posts` where (`posts`.`page` = ? and `posts`.`status` = ?) and (`posts`.`published_at` > ?)');
queries[0].sql.should.eql('select count(distinct posts.id) as aggregate from `posts` where (`posts`.`published_at` > ? and (`posts`.`page` = ? and `posts`.`status` = ?))');
queries[0].bindings.should.eql([
'2015-07-20',
false,
'published',
'2015-07-20'
'published'
]);
queries[1].sql.should.eql('select `posts`.* from `posts` where (`posts`.`page` = ? and `posts`.`status` = ?) and (`posts`.`published_at` > ?) order by CASE WHEN posts.status = \'scheduled\' THEN 1 WHEN posts.status = \'draft\' THEN 2 ELSE 3 END ASC,CASE WHEN posts.status != \'draft\' THEN posts.published_at END DESC,posts.updated_at DESC,posts.id DESC limit ?');
queries[1].sql.should.eql('select `posts`.* from `posts` where (`posts`.`published_at` > ? and (`posts`.`page` = ? and `posts`.`status` = ?)) order by CASE WHEN posts.status = \'scheduled\' THEN 1 WHEN posts.status = \'draft\' THEN 2 ELSE 3 END ASC,CASE WHEN posts.status != \'draft\' THEN posts.published_at END DESC,posts.updated_at DESC,posts.id DESC limit ?');
queries[1].bindings.should.eql([
'2015-07-20',
false,
'published',
'2015-07-20',
5
]);
});
@ -154,22 +149,20 @@ describe('Unit: models/post', function () {
withRelated: ['tags']
}).then(() => {
queries.length.should.eql(2);
queries[0].sql.should.eql('select count(distinct posts.id) as aggregate from `posts` left outer join `posts_tags` on `posts_tags`.`post_id` = `posts`.`id` left outer join `tags` on `posts_tags`.`tag_id` = `tags`.`id` where (`posts`.`page` = ? and `posts`.`status` = ?) and ((`tags`.`slug` = ? and `posts_tags`.`sort_order` = ? and `tags`.`visibility` = ?))');
queries[0].sql.should.eql('select count(distinct posts.id) as aggregate from `posts` where ((`posts`.`id` in (select `posts_tags`.`post_id` from `posts_tags` inner join `tags` on `tags`.`id` = `posts_tags`.`tag_id` and `posts_tags`.`sort_order` = 0 where `tags`.`slug` = ? and `tags`.`visibility` = ?)) and (`posts`.`page` = ? and `posts`.`status` = ?))');
queries[0].bindings.should.eql([
false,
'published',
'photo',
0,
'public'
'public',
false,
'published'
]);
queries[1].sql.should.eql('select `posts`.* from `posts` left outer join `posts_tags` on `posts_tags`.`post_id` = `posts`.`id` left outer join `tags` on `posts_tags`.`tag_id` = `tags`.`id` where (`posts`.`page` = ? and `posts`.`status` = ?) and ((`tags`.`slug` = ? and `posts_tags`.`sort_order` = ? and `tags`.`visibility` = ?)) group by `posts`.`id` order by CASE WHEN posts.status = \'scheduled\' THEN 1 WHEN posts.status = \'draft\' THEN 2 ELSE 3 END ASC,CASE WHEN posts.status != \'draft\' THEN posts.published_at END DESC,posts.updated_at DESC,posts.id DESC limit ?');
queries[1].sql.should.eql('select `posts`.* from `posts` where ((`posts`.`id` in (select `posts_tags`.`post_id` from `posts_tags` inner join `tags` on `tags`.`id` = `posts_tags`.`tag_id` and `posts_tags`.`sort_order` = 0 where `tags`.`slug` = ? and `tags`.`visibility` = ?)) and (`posts`.`page` = ? and `posts`.`status` = ?)) order by CASE WHEN posts.status = \'scheduled\' THEN 1 WHEN posts.status = \'draft\' THEN 2 ELSE 3 END ASC,CASE WHEN posts.status != \'draft\' THEN posts.published_at END DESC,posts.updated_at DESC,posts.id DESC limit ?');
queries[1].bindings.should.eql([
'photo',
'public',
false,
'published',
'photo',
0,
'public',
15
]);
});
@ -189,22 +182,20 @@ describe('Unit: models/post', function () {
withRelated: ['authors']
}).then(() => {
queries.length.should.eql(2);
queries[0].sql.should.eql('select count(distinct posts.id) as aggregate from `posts` left outer join `posts_authors` on `posts_authors`.`post_id` = `posts`.`id` left outer join `users` as `authors` on `posts_authors`.`author_id` = `authors`.`id` where (`posts`.`page` = ? and `posts`.`status` = ?) and ((`authors`.`slug` = ? and `posts_authors`.`sort_order` = ? and `authors`.`visibility` = ?))');
queries[0].sql.should.eql('select count(distinct posts.id) as aggregate from `posts` where ((`posts`.`id` in (select `posts_authors`.`post_id` from `posts_authors` inner join `users` as `authors` on `authors`.`id` = `posts_authors`.`author_id` and `posts_authors`.`sort_order` = 0 where `authors`.`slug` = ? and `authors`.`visibility` = ?)) and (`posts`.`page` = ? and `posts`.`status` = ?))');
queries[0].bindings.should.eql([
false,
'published',
'leslie',
0,
'public'
'public',
false,
'published'
]);
queries[1].sql.should.eql('select `posts`.* from `posts` left outer join `posts_authors` on `posts_authors`.`post_id` = `posts`.`id` left outer join `users` as `authors` on `posts_authors`.`author_id` = `authors`.`id` where (`posts`.`page` = ? and `posts`.`status` = ?) and ((`authors`.`slug` = ? and `posts_authors`.`sort_order` = ? and `authors`.`visibility` = ?)) group by `posts`.`id` order by CASE WHEN posts.status = \'scheduled\' THEN 1 WHEN posts.status = \'draft\' THEN 2 ELSE 3 END ASC,CASE WHEN posts.status != \'draft\' THEN posts.published_at END DESC,posts.updated_at DESC,posts.id DESC limit ?');
queries[1].sql.should.eql('select `posts`.* from `posts` where ((`posts`.`id` in (select `posts_authors`.`post_id` from `posts_authors` inner join `users` as `authors` on `authors`.`id` = `posts_authors`.`author_id` and `posts_authors`.`sort_order` = 0 where `authors`.`slug` = ? and `authors`.`visibility` = ?)) and (`posts`.`page` = ? and `posts`.`status` = ?)) order by CASE WHEN posts.status = \'scheduled\' THEN 1 WHEN posts.status = \'draft\' THEN 2 ELSE 3 END ASC,CASE WHEN posts.status != \'draft\' THEN posts.published_at END DESC,posts.updated_at DESC,posts.id DESC limit ?');
queries[1].bindings.should.eql([
'leslie',
'public',
false,
'published',
'leslie',
0,
'public',
15
]);
});
@ -234,20 +225,20 @@ describe('Unit: models/post', function () {
}
}).then(() => {
queries.length.should.eql(2);
queries[0].sql.should.eql('select count(distinct posts.id) as aggregate from `posts` where (`posts`.`page` = ?) and (`posts`.`status` in (?, ?) and `posts`.`status` = ?)');
queries[0].sql.should.eql('select count(distinct posts.id) as aggregate from `posts` where ((`posts`.`status` in (?, ?) and `posts`.`status` = ?) and (`posts`.`page` = ?))');
queries[0].bindings.should.eql([
false,
'published',
'draft',
'published'
'published',
false,
]);
queries[1].sql.should.eql('select `posts`.* from `posts` where (`posts`.`page` = ?) and (`posts`.`status` in (?, ?) and `posts`.`status` = ?) order by CASE WHEN posts.status = \'scheduled\' THEN 1 WHEN posts.status = \'draft\' THEN 2 ELSE 3 END ASC,CASE WHEN posts.status != \'draft\' THEN posts.published_at END DESC,posts.updated_at DESC,posts.id DESC');
queries[1].sql.should.eql('select `posts`.* from `posts` where ((`posts`.`status` in (?, ?) and `posts`.`status` = ?) and (`posts`.`page` = ?)) order by CASE WHEN posts.status = \'scheduled\' THEN 1 WHEN posts.status = \'draft\' THEN 2 ELSE 3 END ASC,CASE WHEN posts.status != \'draft\' THEN posts.published_at END DESC,posts.updated_at DESC,posts.id DESC');
queries[1].bindings.should.eql([
false,
'published',
'draft',
'published'
'published',
false,
]);
});
});

View File

@ -761,25 +761,25 @@ DataGenerator.forKnex = (function () {
id: ObjectId.generate(),
post_id: DataGenerator.Content.posts[1].id,
tag_id: DataGenerator.Content.tags[0].id,
sort_order: 2
sort_order: 0
},
{
id: ObjectId.generate(),
post_id: DataGenerator.Content.posts[1].id,
tag_id: DataGenerator.Content.tags[1].id,
sort_order: 3
sort_order: 1
},
{
id: ObjectId.generate(),
post_id: DataGenerator.Content.posts[2].id,
tag_id: DataGenerator.Content.tags[2].id,
sort_order: 4
sort_order: 0
},
{
id: ObjectId.generate(),
post_id: DataGenerator.Content.posts[3].id,
tag_id: DataGenerator.Content.tags[3].id,
sort_order: 5
sort_order: 0
}
];
@ -820,6 +820,12 @@ DataGenerator.forKnex = (function () {
author_id: DataGenerator.Content.users[0].id,
sort_order: 0
},
{
id: ObjectId.generate(),
post_id: DataGenerator.Content.posts[4].id,
author_id: _.find(DataGenerator.Content.users, {slug: 'slimer-mcectoplasm'}).id,
sort_order: 1
},
{
id: ObjectId.generate(),
post_id: DataGenerator.Content.posts[5].id,

View File

@ -32,7 +32,7 @@
"cli": "^1.9.0"
},
"dependencies": {
"@nexes/nql": "0.1.0",
"@nexes/nql": "0.2.1",
"amperize": "0.3.8",
"analytics-node": "2.4.1",
"archiver": "1.3.0",
@ -60,7 +60,6 @@
"express-session": "1.15.6",
"extract-zip": "1.6.7",
"fs-extra": "3.0.1",
"ghost-gql": "0.0.11",
"ghost-ignition": "2.9.6",
"ghost-storage-base": "0.0.3",
"glob": "5.0.15",

410
yarn.lock
View File

@ -2,21 +2,31 @@
# yarn lockfile v1
"@nexes/mongo-knex@0.0.1":
version "0.0.1"
resolved "https://registry.yarnpkg.com/@nexes/mongo-knex/-/mongo-knex-0.0.1.tgz#141c8ca380e95c1460fe2abcd777d7fd2bb14e8f"
"@nexes/mongo-knex@0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@nexes/mongo-knex/-/mongo-knex-0.3.0.tgz#e0078f8ddf7fcd4d907fe5cf2390644218753cf0"
dependencies:
debug "^4.1.0"
lodash "^4.17.10"
"@nexes/mongo-utils@0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@nexes/mongo-utils/-/mongo-utils-0.2.0.tgz#28b483e88f2433ee269aa6b91f7339b7ab1e7d45"
dependencies:
bluebird "^3.5.3"
ghost-ignition "^2.9.6"
lodash "^4.17.11"
"@nexes/nql-lang@0.0.1":
version "0.0.1"
resolved "https://registry.yarnpkg.com/@nexes/nql-lang/-/nql-lang-0.0.1.tgz#a13c023873f9bc11b9e4e284449c6cfbeccc8011"
"@nexes/nql@0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@nexes/nql/-/nql-0.1.0.tgz#b63614d5874bebb387b58d3f1b9fb9602115cf62"
"@nexes/nql@0.2.1":
version "0.2.1"
resolved "https://registry.yarnpkg.com/@nexes/nql/-/nql-0.2.1.tgz#cc314f77d9fcbb0d321669db3dd74de880b0c694"
dependencies:
"@nexes/mongo-knex" "0.0.1"
"@nexes/mongo-knex" "0.3.0"
"@nexes/mongo-utils" "0.2.0"
"@nexes/nql-lang" "0.0.1"
mingo "2.2.2"
@ -27,11 +37,11 @@
component-type "^1.2.1"
join-component "^1.1.0"
"@sinonjs/formatio@3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-3.0.0.tgz#9d282d81030a03a03fa0c5ce31fd8786a4da311a"
"@sinonjs/commons@^1.0.2":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.3.0.tgz#50a2754016b6f30a994ceda6d9a0a8c36adda849"
dependencies:
"@sinonjs/samsam" "2.1.0"
type-detect "4.0.8"
"@sinonjs/formatio@^2.0.0":
version "2.0.0"
@ -39,11 +49,19 @@
dependencies:
samsam "1.3.0"
"@sinonjs/samsam@2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-2.1.0.tgz#b8b8f5b819605bd63601a6ede459156880f38ea3"
"@sinonjs/formatio@^3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-3.1.0.tgz#6ac9d1eb1821984d84c4996726e45d1646d8cce5"
dependencies:
"@sinonjs/samsam" "^2 || ^3"
"@sinonjs/samsam@^2 || ^3":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-3.0.2.tgz#304fb33bd5585a0b2df8a4c801fcb47fa84d8e43"
dependencies:
"@sinonjs/commons" "^1.0.2"
array-from "^2.1.1"
lodash.get "^4.4.2"
"@tryghost/extract-zip@1.6.6":
version "1.6.6"
@ -104,6 +122,15 @@ ajv@^5.2.3, ajv@^5.3.0:
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.3.0"
ajv@^6.5.5:
version "6.6.1"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.6.1.tgz#6360f5ed0d80f232cc2b294c362d5dc2e538dd61"
dependencies:
fast-deep-equal "^2.0.1"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
align-text@^0.1.1, align-text@^0.1.3:
version "0.1.4"
resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117"
@ -155,7 +182,7 @@ analytics-node@2.4.1:
ansi-escapes@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.1.0.tgz#f73207bb81207d75fd6c83f125af26eea378ca30"
resolved "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz#f73207bb81207d75fd6c83f125af26eea378ca30"
ansi-regex@^2.0.0:
version "2.1.1"
@ -196,7 +223,7 @@ archiver-utils@^1.3.0:
archiver@1.3.0, archiver@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/archiver/-/archiver-1.3.0.tgz#4f2194d6d8f99df3f531e6881f14f15d55faaf22"
resolved "http://registry.npmjs.org/archiver/-/archiver-1.3.0.tgz#4f2194d6d8f99df3f531e6881f14f15d55faaf22"
dependencies:
archiver-utils "^1.3.0"
async "^2.0.0"
@ -250,7 +277,7 @@ array-find-index@^1.0.1:
array-flatten@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
resolved "http://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
array-from@^2.1.1:
version "2.1.1"
@ -260,13 +287,7 @@ array-slice@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-1.1.0.tgz#e368ea15f89bc7069f7ffb89aec3a6c7d4ac22d4"
array-union@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39"
dependencies:
array-uniq "^1.0.1"
array-uniq@^1.0.1, array-uniq@^1.0.2:
array-uniq@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
@ -274,10 +295,6 @@ array-unique@^0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
arrify@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
asn1@~0.2.3:
version "0.2.4"
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
@ -547,10 +564,14 @@ block-stream@*:
dependencies:
inherits "~2.0.0"
bluebird@3.5.2, bluebird@^3.0.5, bluebird@^3.4.1, bluebird@^3.4.3, bluebird@^3.4.6, bluebird@^3.5.1:
bluebird@3.5.2:
version "3.5.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.2.tgz#1be0908e054a751754549c270489c1505d4ab15a"
bluebird@^3.0.5, bluebird@^3.4.1, bluebird@^3.4.3, bluebird@^3.4.6, bluebird@^3.5.1, bluebird@^3.5.3:
version "3.5.3"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7"
body-parser@1.18.3:
version "1.18.3"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4"
@ -585,7 +606,7 @@ bookshelf-relations@0.2.1:
bookshelf@0.13.3:
version "0.13.3"
resolved "https://registry.yarnpkg.com/bookshelf/-/bookshelf-0.13.3.tgz#aa73d9159b6cac92830dc1ff37325490c3c6dfba"
resolved "http://registry.npmjs.org/bookshelf/-/bookshelf-0.13.3.tgz#aa73d9159b6cac92830dc1ff37325490c3c6dfba"
dependencies:
babel-runtime "^6.26.0"
bluebird "^3.4.3"
@ -690,11 +711,11 @@ builtin-modules@^1.0.0:
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
bunyan-loggly@^1.3.1:
version "1.3.5"
resolved "https://registry.yarnpkg.com/bunyan-loggly/-/bunyan-loggly-1.3.5.tgz#857bbabe9a2f26c3b03eeab1db9e86c092227bde"
version "1.4.0"
resolved "https://registry.yarnpkg.com/bunyan-loggly/-/bunyan-loggly-1.4.0.tgz#a10b61d7b32422e1c648031185d8e0413427db8f"
dependencies:
json-stringify-safe "^5.0.1"
node-loggly-bulk "^2.2.2"
node-loggly-bulk "^2.2.4"
bunyan@1.8.12:
version "1.8.12"
@ -746,7 +767,7 @@ caller@1.0.1:
callsites@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca"
resolved "http://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca"
camelcase-keys@^2.0.0:
version "2.1.0"
@ -773,8 +794,8 @@ caniuse-api@^1.5.2:
lodash.uniq "^4.5.0"
caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
version "1.0.30000899"
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000899.tgz#f66d667d507c2aa19603a4a3763d71aa89cc360f"
version "1.0.30000918"
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000918.tgz#650a34372ced440a79fa600644667802c6a4b9c2"
caseless@~0.12.0:
version "0.12.0"
@ -853,7 +874,7 @@ cheerio@0.22.0:
lodash.reject "^4.4.0"
lodash.some "^4.4.0"
chownr@^1.0.1:
chownr@^1.0.1, chownr@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494"
@ -930,7 +951,7 @@ code-point-at@^1.0.0:
coffee-script@~1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.3.3.tgz#150d6b4cb522894369efed6a2101c20bc7f4a4f4"
resolved "http://registry.npmjs.org/coffee-script/-/coffee-script-1.3.3.tgz#150d6b4cb522894369efed6a2101c20bc7f4a4f4"
coffeescript@~1.10.0:
version "1.10.0"
@ -959,7 +980,7 @@ color-name@^1.0.0:
color-string@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/color-string/-/color-string-0.3.0.tgz#27d46fb67025c5c2fa25993bfbf579e47841b991"
resolved "http://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz#27d46fb67025c5c2fa25993bfbf579e47841b991"
dependencies:
color-name "^1.0.0"
@ -995,19 +1016,19 @@ colormin@^1.0.5:
colors@0.5.x:
version "0.5.1"
resolved "https://registry.yarnpkg.com/colors/-/colors-0.5.1.tgz#7d0023eaeb154e8ee9fce75dcb923d0ed1667774"
resolved "http://registry.npmjs.org/colors/-/colors-0.5.1.tgz#7d0023eaeb154e8ee9fce75dcb923d0ed1667774"
colors@^1.1.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.2.tgz#2df8ff573dfbf255af562f8ce7181d6b971a359b"
version "1.3.3"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.3.tgz#39e005d546afe01e01f9c4ca8fa50f686a01205d"
colors@~0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/colors/-/colors-0.6.2.tgz#2423fe6678ac0c5dae8852e5d0e5be08c997abcc"
resolved "http://registry.npmjs.org/colors/-/colors-0.6.2.tgz#2423fe6678ac0c5dae8852e5d0e5be08c997abcc"
colors@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
resolved "http://registry.npmjs.org/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
combined-stream@^1.0.6, combined-stream@~1.0.6:
version "1.0.7"
@ -1166,8 +1187,8 @@ copy-descriptor@^0.1.0:
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
core-js@^2.4.0, core-js@^2.5.0:
version "2.5.7"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e"
version "2.6.0"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.0.tgz#1e30793e9ee5782b307e37ffa22da0eacddd84d4"
core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2"
@ -1333,6 +1354,12 @@ debug@^3.1.0:
dependencies:
ms "^2.1.1"
debug@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.0.tgz#373687bffa678b38b1cd91f861b63850035ddc87"
dependencies:
ms "^2.1.1"
decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
@ -1388,18 +1415,6 @@ defined@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
del@^2.0.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8"
dependencies:
globby "^5.0.0"
is-path-cwd "^1.0.0"
is-path-in-cwd "^1.0.0"
object-assign "^4.0.1"
pify "^2.0.0"
pinkie-promise "^2.0.0"
rimraf "^2.2.8"
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
@ -1488,13 +1503,9 @@ dom-serializer@0, dom-serializer@~0.1.0:
domelementtype "~1.1.1"
entities "~1.1.1"
domelementtype@1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.2.1.tgz#578558ef23befac043a1abb0db07635509393479"
domelementtype@^1.3.0:
version "1.3.0"
resolved "http://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2"
domelementtype@1, domelementtype@^1.3.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
domelementtype@~1.1.1:
version "1.1.3"
@ -1574,10 +1585,10 @@ ee-first@1.1.1:
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
electron-to-chromium@^1.2.7:
version "1.3.82"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.82.tgz#7d13ae4437d2a783de3f4efba96b186c540b67b1"
version "1.3.90"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.90.tgz#b4c51b8303beff18f2b74817402bf4898e09558a"
ember-rfc176-data@^0.3.3:
ember-rfc176-data@^0.3.5:
version "0.3.5"
resolved "https://registry.yarnpkg.com/ember-rfc176-data/-/ember-rfc176-data-0.3.5.tgz#f630e550572c81a5e5c7220f864c0f06eee9e977"
@ -1662,10 +1673,10 @@ escodegen@^1.8.1:
source-map "~0.6.1"
eslint-plugin-ember@^5.0.3:
version "5.2.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-ember/-/eslint-plugin-ember-5.2.0.tgz#fa436e0497dfc01d1d38608229cd616e7c5b6067"
version "5.4.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-ember/-/eslint-plugin-ember-5.4.0.tgz#2980a4389119b37d0450fff8e82d59c9aab126d0"
dependencies:
ember-rfc176-data "^0.3.3"
ember-rfc176-data "^0.3.5"
snake-case "^2.1.0"
eslint-plugin-ghost@0.0.27:
@ -1818,6 +1829,10 @@ expand-template@^1.0.2:
version "1.1.1"
resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-1.1.1.tgz#981f188c0c3a87d2e28f559bc541426ff94f21dd"
expand-template@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
expand-tilde@^2.0.0, expand-tilde@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502"
@ -1924,7 +1939,7 @@ extend@^3.0.0, extend@~3.0.2:
extendr@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/extendr/-/extendr-2.0.1.tgz#d8ab375fcbb833e4ba2cd228540f04e4aa07de90"
resolved "http://registry.npmjs.org/extendr/-/extendr-2.0.1.tgz#d8ab375fcbb833e4ba2cd228540f04e4aa07de90"
dependencies:
typechecker "~2.0.1"
@ -1970,6 +1985,10 @@ fast-deep-equal@^1.0.0:
version "1.1.0"
resolved "http://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614"
fast-deep-equal@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
fast-json-stable-stringify@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
@ -2076,8 +2095,8 @@ findup-sync@~0.3.0:
glob "~5.0.0"
fined@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/fined/-/fined-1.1.0.tgz#b37dc844b76a2f5e7081e884f7c0ae344f153476"
version "1.1.1"
resolved "https://registry.yarnpkg.com/fined/-/fined-1.1.1.tgz#95d88ff329123dd1a6950fdfcd321f746271e01f"
dependencies:
expand-tilde "^2.0.2"
is-plain-object "^2.0.3"
@ -2090,12 +2109,12 @@ flagged-respawn@^1.0.0:
resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-1.0.0.tgz#4e79ae9b2eb38bf86b3bb56bf3e0a56aa5fcabd7"
flat-cache@^1.2.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.0.tgz#d3030b32b38154f4e3b7e9c709f490f7ef97c481"
version "1.3.4"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.4.tgz#2c2ef77525cc2929007dfffa1dd314aa9c9dee6f"
dependencies:
circular-json "^0.3.1"
del "^2.0.2"
graceful-fs "^4.1.2"
rimraf "~2.6.2"
write "^0.2.1"
flatten@^1.0.2:
@ -2166,7 +2185,7 @@ fs-extra@3.0.1, fs-extra@^3.0.1:
fs-extra@^0.26.2:
version "0.26.7"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.26.7.tgz#9ae1fdd94897798edab76d0918cf42d0c3184fa9"
resolved "http://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz#9ae1fdd94897798edab76d0918cf42d0c3184fa9"
dependencies:
graceful-fs "^4.1.2"
jsonfile "^2.1.0"
@ -2262,12 +2281,6 @@ getsetdeep@~2.0.0:
dependencies:
typechecker "~2.0.1"
ghost-gql@0.0.11:
version "0.0.11"
resolved "https://registry.yarnpkg.com/ghost-gql/-/ghost-gql-0.0.11.tgz#f0bc85305d0be80e5131011bf48b0ffd7c058b6b"
dependencies:
lodash "^4.17.4"
ghost-ignition@2.9.2:
version "2.9.2"
resolved "https://registry.yarnpkg.com/ghost-ignition/-/ghost-ignition-2.9.2.tgz#e68de88fa4a20fc09c833fe3278bdc20dca6d525"
@ -2285,7 +2298,7 @@ ghost-ignition@2.9.2:
prettyjson "^1.1.3"
uuid "^3.0.0"
ghost-ignition@2.9.6:
ghost-ignition@2.9.6, ghost-ignition@^2.9.6:
version "2.9.6"
resolved "https://registry.yarnpkg.com/ghost-ignition/-/ghost-ignition-2.9.6.tgz#cc8358f0a356bae490e5abeca3c3bda8383352fe"
dependencies:
@ -2410,24 +2423,13 @@ global-prefix@^1.0.1:
which "^1.2.14"
globals@^11.0.1:
version "11.8.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.8.0.tgz#c1ef45ee9bed6badf0663c5cb90e8d1adec1321d"
version "11.9.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.9.0.tgz#bde236808e987f290768a93d065060d78e6ab249"
globals@^9.18.0:
version "9.18.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
globby@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d"
dependencies:
array-union "^1.0.1"
arrify "^1.0.0"
glob "^7.0.3"
object-assign "^4.0.1"
pify "^2.0.0"
pinkie-promise "^2.0.0"
globule@^1.0.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.1.tgz#5dffb1b191f22d20797a9369b49eab4e9839696d"
@ -2456,12 +2458,12 @@ got@7.1.0, got@^7.1.0:
url-to-options "^1.0.1"
graceful-fs@^4.1.0, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9:
version "4.1.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
version "4.1.15"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
graceful-fs@~1.2.0:
version "1.2.3"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-1.2.3.tgz#15a4806a57547cb2d2dbf27f42e89a8c3451b364"
resolved "http://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz#15a4806a57547cb2d2dbf27f42e89a8c3451b364"
"graceful-readlink@>= 1.0.0":
version "1.0.1"
@ -2491,7 +2493,7 @@ grunt-cli@1.3.1:
grunt-cli@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/grunt-cli/-/grunt-cli-1.2.0.tgz#562b119ebb069ddb464ace2845501be97b35b6a8"
resolved "http://registry.npmjs.org/grunt-cli/-/grunt-cli-1.2.0.tgz#562b119ebb069ddb464ace2845501be97b35b6a8"
dependencies:
findup-sync "~0.3.0"
grunt-known-options "~1.1.0"
@ -2684,7 +2686,7 @@ grunt@1.0.3:
grunt@~0.4.0:
version "0.4.5"
resolved "https://registry.yarnpkg.com/grunt/-/grunt-0.4.5.tgz#56937cd5194324adff6d207631832a9d6ba4e7f0"
resolved "http://registry.npmjs.org/grunt/-/grunt-0.4.5.tgz#56937cd5194324adff6d207631832a9d6ba4e7f0"
dependencies:
async "~0.1.22"
coffee-script "~1.3.3"
@ -2759,10 +2761,10 @@ har-schema@^2.0.0:
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
har-validator@~5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.0.tgz#44657f5688a22cfd4b72486e81b3a3fb11742c29"
version "5.1.3"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080"
dependencies:
ajv "^5.3.0"
ajv "^6.5.5"
har-schema "^2.0.0"
has-ansi@^2.0.0:
@ -3174,22 +3176,6 @@ is-object@^1.0.1, is-object@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470"
is-path-cwd@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d"
is-path-in-cwd@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz#5ac48b345ef675339bd6c7a48a912110b241cf52"
dependencies:
is-path-inside "^1.0.0"
is-path-inside@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036"
dependencies:
path-is-inside "^1.0.1"
is-plain-obj@^1.0.0, is-plain-obj@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
@ -3381,12 +3367,16 @@ jsbn@~0.1.0:
jsesc@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b"
resolved "http://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b"
json-schema-traverse@^0.3.0:
version "0.3.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340"
json-schema-traverse@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
json-schema@0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
@ -3623,14 +3613,14 @@ liftoff@2.5.0, liftoff@~2.5.0:
resolve "^1.1.7"
linkify-it@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.0.3.tgz#d94a4648f9b1c179d64fa97291268bdb6ce9434f"
version "2.1.0"
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.1.0.tgz#c4caf38a6cd7ac2212ef3c7d2bde30a91561f9db"
dependencies:
uc.micro "^1.0.1"
livereload-js@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-2.3.0.tgz#c3ab22e8aaf5bf3505d80d098cbad67726548c9a"
version "2.4.0"
resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-2.4.0.tgz#447c31cf1ea9ab52fc20db615c5ddf678f78009c"
load-json-file@^1.0.0:
version "1.1.0"
@ -3825,7 +3815,7 @@ lodash@4.17.10:
version "4.17.10"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
lodash@4.17.11, lodash@^4.13.1, lodash@^4.14.2, lodash@^4.16.4, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.7.0, lodash@^4.8.0, lodash@~4.17.10, lodash@~4.17.5:
lodash@4.17.11, lodash@^4.13.1, lodash@^4.14.2, lodash@^4.16.4, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.7.0, lodash@^4.8.0, lodash@~4.17.10, lodash@~4.17.5:
version "4.17.11"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
@ -3876,17 +3866,17 @@ lowercase-keys@^1.0.0:
lru-cache@2:
version "2.7.3"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952"
resolved "http://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952"
lru-cache@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-3.2.0.tgz#71789b3b7f5399bec8565dda38aa30d2a097efee"
resolved "http://registry.npmjs.org/lru-cache/-/lru-cache-3.2.0.tgz#71789b3b7f5399bec8565dda38aa30d2a097efee"
dependencies:
pseudomap "^1.0.1"
lru-cache@^4.0.0, lru-cache@^4.0.1:
version "4.1.3"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c"
version "4.1.5"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
dependencies:
pseudomap "^1.0.2"
yallist "^2.1.2"
@ -4044,7 +4034,7 @@ mime@^1.4.1:
mime@~1.2.11:
version "1.2.11"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.2.11.tgz#58203eed86e3a5ef17aed2b7d9ebd47f0a60dd10"
resolved "http://registry.npmjs.org/mime/-/mime-1.2.11.tgz#58203eed86e3a5ef17aed2b7d9ebd47f0a60dd10"
mimelib@~0.2.15:
version "0.2.19"
@ -4097,16 +4087,16 @@ minimist@~0.0.1:
version "0.0.10"
resolved "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
minipass@^2.2.1, minipass@^2.3.3:
minipass@^2.2.1, minipass@^2.3.4:
version "2.3.5"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848"
dependencies:
safe-buffer "^5.1.2"
yallist "^3.0.0"
minizlib@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.1.tgz#6734acc045a46e61d596a43bb9d9cd326e19cc42"
minizlib@^1.1.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614"
dependencies:
minipass "^2.2.1"
@ -4315,10 +4305,10 @@ netjet@1.3.0:
posthtml "^0.9.0"
nise@^1.2.0:
version "1.4.6"
resolved "https://registry.yarnpkg.com/nise/-/nise-1.4.6.tgz#76cc3915925056ae6c405dd8ad5d12bde570c19f"
version "1.4.7"
resolved "https://registry.yarnpkg.com/nise/-/nise-1.4.7.tgz#180d723df5071a3d3fc0e83fe8eb0c02b7db1f59"
dependencies:
"@sinonjs/formatio" "3.0.0"
"@sinonjs/formatio" "^3.1.0"
just-extend "^3.0.0"
lolex "^2.3.2"
path-to-regexp "^1.7.0"
@ -4345,14 +4335,14 @@ nock@9.4.0:
semver "^5.5.0"
node-abi@^2.2.0:
version "2.4.5"
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.4.5.tgz#1fd1fb66641bf3c4dcf55a5490ba10c467ead80c"
version "2.5.0"
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.5.0.tgz#942e1a78bce764bc0c1672d5821e492b9d032052"
dependencies:
semver "^5.4.1"
node-fetch@^2.1.2:
version "2.2.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.2.0.tgz#4ee79bde909262f9775f731e3656d0db55ced5b5"
version "2.3.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.3.0.tgz#1a1d940bbfb916a1d3e0219f037e89e71f8c5fa5"
node-forge@^0.7.6:
version "0.7.6"
@ -4395,7 +4385,7 @@ node-jose@1.1.0:
node-forge "^0.7.6"
uuid "^3.3.2"
node-loggly-bulk@^2.2.2:
node-loggly-bulk@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/node-loggly-bulk/-/node-loggly-bulk-2.2.4.tgz#bdd8638d97c43ecf1e1831ca98b250968fa6dee9"
dependencies:
@ -4634,7 +4624,7 @@ optionator@^0.8.1, optionator@^0.8.2:
os-homedir@^1.0.0, os-homedir@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
resolved "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
os-locale@^1.4.0:
version "1.4.0"
@ -4644,7 +4634,7 @@ os-locale@^1.4.0:
os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
resolved "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
osenv@0, osenv@^0.1.4:
version "0.1.5"
@ -4669,7 +4659,7 @@ p-timeout@^1.1.1:
pako@~0.2.0:
version "0.2.9"
resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75"
resolved "http://registry.npmjs.org/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75"
parse-filepath@^1.0.1:
version "1.0.2"
@ -4728,9 +4718,9 @@ path-exists@^2.0.0:
path-is-absolute@^1.0.0, path-is-absolute@^1.0.1, path-is-absolute@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
resolved "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
path-is-inside@^1.0.1, path-is-inside@^1.0.2:
path-is-inside@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
@ -5076,11 +5066,11 @@ prebuild-install@^2.3.0:
which-pm-runs "^1.0.0"
prebuild-install@^5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.2.1.tgz#87ba8cf17c65360a75eefeb3519e87973bf9791d"
version "5.2.2"
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.2.2.tgz#237888f21bfda441d0ee5f5612484390bccd4046"
dependencies:
detect-libc "^1.0.3"
expand-template "^1.0.2"
expand-template "^2.0.3"
github-from-package "0.0.0"
minimist "^1.2.0"
mkdirp "^0.5.1"
@ -5106,14 +5096,14 @@ prepend-http@^1.0.0, prepend-http@^1.0.1:
pretty-bytes@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-1.0.4.tgz#0a22e8210609ad35542f8c8d5d2159aff0751c84"
resolved "http://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz#0a22e8210609ad35542f8c8d5d2159aff0751c84"
dependencies:
get-stdin "^4.0.1"
meow "^3.1.0"
pretty-bytes@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-4.0.2.tgz#b2bf82e7350d65c6c33aa95aaa5a4f6327f61cd9"
resolved "http://registry.npmjs.org/pretty-bytes/-/pretty-bytes-4.0.2.tgz#b2bf82e7350d65c6c33aa95aaa5a4f6327f61cd9"
prettyjson@^1.1.3:
version "1.2.1"
@ -5131,8 +5121,8 @@ process-nextick-args@~2.0.0:
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
progress@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.1.tgz#c9242169342b1c29d275889c95734621b1952e31"
version "2.0.3"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
promise-wtf@^1.2.4:
version "1.2.4"
@ -5191,6 +5181,10 @@ punycode@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
punycode@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
punycode@~1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.2.4.tgz#54008ac972aec74175def9cba6df7fa9d3918740"
@ -5208,10 +5202,14 @@ q@^1.1.2:
version "1.5.1"
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
qs@6.5.2, qs@^6.4.0, qs@^6.5.1, qs@~6.5.2:
qs@6.5.2, qs@~6.5.2:
version "6.5.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
qs@^6.4.0, qs@^6.5.1:
version "6.6.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.6.0.tgz#a99c0f69a8d26bf7ef012f871cdabb0aee4424c2"
query-string@^4.1.0:
version "4.3.4"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb"
@ -5280,7 +5278,7 @@ readable-stream@1.1.x, readable-stream@~1.1.9:
isarray "0.0.1"
string_decoder "~0.10.x"
readable-stream@2.3.6, readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.5:
readable-stream@2.3.6, readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.5, readable-stream@~2.3.6:
version "2.3.6"
resolved "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
dependencies:
@ -5413,7 +5411,7 @@ require-dir@^0.3.2:
require-uncached@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3"
resolved "http://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3"
dependencies:
caller-path "^0.1.0"
resolve-from "^1.0.0"
@ -5435,11 +5433,11 @@ resolve-url@^0.2.1:
resolve@1.1.x, resolve@~1.1.0:
version "1.1.7"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
resolved "http://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
resolve@1.7.1:
version "1.7.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3"
resolved "http://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3"
dependencies:
path-parse "^1.0.5"
@ -5526,7 +5524,7 @@ safe-json-stringify@~1:
safe-regex@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"
resolved "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"
dependencies:
ret "~0.1.10"
@ -5721,7 +5719,7 @@ simple-concat@^1.0.0:
simple-dom@0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/simple-dom/-/simple-dom-0.3.2.tgz#0663d10f1556f1500551d518f56e3aba0871371d"
resolved "http://registry.npmjs.org/simple-dom/-/simple-dom-0.3.2.tgz#0663d10f1556f1500551d518f56e3aba0871371d"
simple-get@^2.7.0:
version "2.8.1"
@ -5843,7 +5841,7 @@ source-map-url@^0.4.0:
source-map@^0.4.4:
version "0.4.4"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"
resolved "http://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"
dependencies:
amdefine ">=0.0.4"
@ -5857,13 +5855,13 @@ source-map@^0.6.1, source-map@~0.6.1:
source-map@~0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d"
resolved "http://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d"
dependencies:
amdefine ">=0.0.4"
spdx-correct@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.2.tgz#19bb409e91b47b1ad54159243f7312a858db3c2e"
version "3.1.0"
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4"
dependencies:
spdx-expression-parse "^3.0.0"
spdx-license-ids "^3.0.0"
@ -5880,8 +5878,8 @@ spdx-expression-parse@^3.0.0:
spdx-license-ids "^3.0.0"
spdx-license-ids@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.1.tgz#e2a303236cac54b04031fa7a5a79c7e701df852f"
version "3.0.2"
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.2.tgz#a59efc09784c2a5bada13cfeaf5c75dd214044d2"
split-string@^3.0.1, split-string@^3.0.2:
version "3.1.0"
@ -5896,14 +5894,14 @@ split2@^2.1.0:
through2 "^2.0.2"
sprintf-js@^1.0.3:
version "1.1.1"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.1.tgz#36be78320afe5801f6cea3ee78b6e5aab940ea0c"
version "1.1.2"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673"
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
resolved "http://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
sqlite3@4.0.3, sqlite3@^4.0.0:
sqlite3@4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-4.0.3.tgz#da8c167a87941657fd22e27b248aa371e192b715"
dependencies:
@ -5911,6 +5909,14 @@ sqlite3@4.0.3, sqlite3@^4.0.0:
node-pre-gyp "^0.10.3"
request "^2.87.0"
sqlite3@^4.0.0:
version "4.0.4"
resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-4.0.4.tgz#1f75e3ededad6e26f7dd819929460ce44a49dfcd"
dependencies:
nan "~2.10.0"
node-pre-gyp "^0.10.3"
request "^2.87.0"
sqlstring@2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/sqlstring/-/sqlstring-2.3.1.tgz#475393ff9e91479aea62dcaf0ca3d14983a7fb40"
@ -5923,8 +5929,8 @@ srcset@^1.0.0:
number-is-nan "^1.0.0"
sshpk@^1.7.0:
version "1.15.1"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.15.1.tgz#b79a089a732e346c6e0714830f36285cd38191a2"
version "1.15.2"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.15.2.tgz#c946d6bd9b1a39d0e8635763f5242d6ed6dcb629"
dependencies:
asn1 "~0.2.3"
assert-plus "^1.0.0"
@ -5994,11 +6000,17 @@ string-width@^1.0.1:
string_decoder@0.10, string_decoder@~0.10.x:
version "0.10.31"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
resolved "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
string_decoder@^1.1.1, string_decoder@~1.1.1:
string_decoder@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d"
dependencies:
safe-buffer "~5.1.0"
string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
resolved "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
dependencies:
safe-buffer "~5.1.0"
@ -6130,20 +6142,20 @@ tar-stream@^1.1.2, tar-stream@^1.5.0:
tar@^2.0.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1"
resolved "http://registry.npmjs.org/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1"
dependencies:
block-stream "*"
fstream "^1.0.2"
inherits "2"
tar@^4, tar@^4.4.6:
version "4.4.6"
resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.6.tgz#63110f09c00b4e60ac8bcfe1bf3c8660235fbc9b"
version "4.4.8"
resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.8.tgz#b19eec3fde2a96e64666df9fdb40c5ca1bc3747d"
dependencies:
chownr "^1.0.1"
chownr "^1.1.1"
fs-minipass "^1.2.5"
minipass "^2.3.3"
minizlib "^1.1.0"
minipass "^2.3.4"
minizlib "^1.1.1"
mkdirp "^0.5.0"
safe-buffer "^5.1.2"
yallist "^3.0.2"
@ -6168,15 +6180,15 @@ text-table@~0.2.0:
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
through2@^2.0.2, through2@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be"
version "2.0.5"
resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
dependencies:
readable-stream "^2.1.5"
readable-stream "~2.3.6"
xtend "~4.0.1"
through2@~0.2.1:
version "0.2.3"
resolved "https://registry.yarnpkg.com/through2/-/through2-0.2.3.tgz#eb3284da4ea311b6cc8ace3653748a52abf25a3f"
resolved "http://registry.npmjs.org/through2/-/through2-0.2.3.tgz#eb3284da4ea311b6cc8ace3653748a52abf25a3f"
dependencies:
readable-stream "~1.1.9"
xtend "~2.1.1"
@ -6273,7 +6285,7 @@ type-check@~0.3.2:
dependencies:
prelude-ls "~1.1.2"
type-detect@^4.0.0, type-detect@^4.0.5:
type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5:
version "4.0.8"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
@ -6366,15 +6378,15 @@ underscore@, underscore@^1.8.3:
underscore@1.1.x:
version "1.1.7"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.1.7.tgz#40bab84bad19d230096e8d6ef628bff055d83db0"
resolved "http://registry.npmjs.org/underscore/-/underscore-1.1.7.tgz#40bab84bad19d230096e8d6ef628bff055d83db0"
underscore@1.7.0, underscore@~1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.7.0.tgz#6bbaf0877500d36be34ecaa584e0db9fef035209"
resolved "http://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz#6bbaf0877500d36be34ecaa584e0db9fef035209"
underscore@~1.8.3:
version "1.8.3"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"
resolved "http://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"
unidecode@0.1.8:
version "0.1.8"
@ -6412,6 +6424,12 @@ unset-value@^1.0.0:
has-value "^0.3.1"
isobject "^3.0.0"
uri-js@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
dependencies:
punycode "^2.1.0"
uri-path@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/uri-path/-/uri-path-1.0.0.tgz#9747f018358933c31de0fccfd82d138e67262e32"
@ -6495,7 +6513,7 @@ walkdir@^0.0.11:
watchr@~2.3.3:
version "2.3.10"
resolved "https://registry.yarnpkg.com/watchr/-/watchr-2.3.10.tgz#2fe0af537071cae6a776d4523356f8f3a230b7ce"
resolved "http://registry.npmjs.org/watchr/-/watchr-2.3.10.tgz#2fe0af537071cae6a776d4523356f8f3a230b7ce"
dependencies:
bal-util "~1.18.0"
@ -6612,8 +6630,8 @@ yallist@^2.1.2:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
yallist@^3.0.0, yallist@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9"
version "3.0.3"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9"
yargs@^3.19.0:
version "3.32.0"