From 34a9aad73167c678e12b4903706aae1af2b380a2 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Tue, 14 Apr 2026 16:39:42 +0100 Subject: [PATCH 1/3] Fixed bookshelf-pagination smart count false positive on subquery JOINs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hasMultiTableSource ran a JOIN regex against the full compiled SQL, so any query with `where id in (select ... inner join ...)` fell back to `count(distinct posts.id)` even though the outer query was still single table. On a 267k-post Ghost site filtered by author this caused a ~20% regression in the count aggregate for no benefit — the weedout step already guaranteed distinct rows. Switched the detection to walk knex's query-builder AST (`_statements` and `_single.table`) so joins, unions, derived/raw FROM sources and comma-separated FROM lists are read as structured data. Subquery JOINs live under a `where` grouping and are correctly ignored. Since bookshelf is built on knex, the builder always exposes `_statements` / `_single`, so no SQL fallback is needed: the whole check collapses to a ~30-line AST walk with no regexes, no toSQL() call and no paren-stripping. --- .../lib/bookshelf-pagination.js | 68 ++++----- .../test/pagination.test.js | 134 ++++++++++++------ 2 files changed, 118 insertions(+), 84 deletions(-) diff --git a/packages/bookshelf-pagination/lib/bookshelf-pagination.js b/packages/bookshelf-pagination/lib/bookshelf-pagination.js index a7669db67..c9168ee6b 100644 --- a/packages/bookshelf-pagination/lib/bookshelf-pagination.js +++ b/packages/bookshelf-pagination/lib/bookshelf-pagination.js @@ -14,46 +14,40 @@ const messages = { let defaults; let paginationUtils; -const JOIN_KEYWORD_REGEX = /\bjoin\b/i; -const FROM_CLAUSE_REGEX = /\bfrom\b([\s\S]*?)(?:\bwhere\b|\bgroup\s+by\b|\border\s+by\b|\blimit\b|\boffset\b|$)/i; - -function getCompiledSql(queryBuilder) { - const compiledQuery = queryBuilder.toSQL(); - - if (Array.isArray(compiledQuery)) { - return compiledQuery - .map((query) => { - return query && query.sql ? query.sql : ''; - }) - .join(' '); +// Smart count only uses count(*) for single-table queries. Multi-table shapes +// — outer JOINs, UNIONs, derived/raw FROM sources, or comma-separated FROM +// lists — can duplicate base rows, so fetchPage must use count(distinct id). +// +// Crucially, JOINs that appear inside a subquery (eg. `where id in (select ... +// inner join ...)`) do NOT count — the outer row set is still unique per base +// row, so count(*) is both safe and materially faster. Walking knex's AST +// instead of the compiled SQL gives us exactly this scoping for free. +function hasMultiTableSource(queryBuilder) { + for (const statement of queryBuilder._statements) { + // Any outer join duplicates the base row set. + if (statement.grouping === 'join') { + return true; + } + // UNION combines multiple SELECTs — the outer row set comes + // from more than one source, so count(*) is unsafe. + if (statement.grouping === 'union') { + return true; + } } - return compiledQuery && compiledQuery.sql ? compiledQuery.sql : ''; -} - -function extractFromClause(sql) { - const fromClauseMatch = sql.match(FROM_CLAUSE_REGEX); - - return fromClauseMatch ? fromClauseMatch[1] : ''; -} - -function hasJoinKeyword(sql) { - return JOIN_KEYWORD_REGEX.test(sql); -} - -function hasCommaSeparatedFromSources(sql) { - return extractFromClause(sql).includes(','); -} - -// Smart count only uses count(*) for single-table queries. -// This check flags multi-table sources in two forms: -// 1) explicit JOIN keywords (join, left join, join raw, etc.) -// 2) old-style comma-separated FROM lists (eg `from posts, tags`) -// Both can duplicate base rows, so fetchPage should use count(distinct id). -function hasMultiTableSource(queryBuilder) { - const sql = getCompiledSql(queryBuilder); + // For a bookshelf Model the outer FROM is normally a plain table name + // string. Anything else — a QueryBuilder (derived table) or a Raw (eg. + // `fromRaw('a, b')`) — can carry duplicate-producing shape that we can't + // introspect further, so treat it as multi-source. + const table = queryBuilder._single.table; + if (table && typeof table !== 'string') { + return true; + } + if (typeof table === 'string' && table.includes(',')) { + return true; + } - return hasJoinKeyword(sql) || hasCommaSeparatedFromSources(sql); + return false; } /** diff --git a/packages/bookshelf-pagination/test/pagination.test.js b/packages/bookshelf-pagination/test/pagination.test.js index fc4e1f52b..6c3c80d58 100644 --- a/packages/bookshelf-pagination/test/pagination.test.js +++ b/packages/bookshelf-pagination/test/pagination.test.js @@ -16,7 +16,8 @@ function createBookshelf({countRows, fetchResult, selectError, fetchError} = {}) }; const countQuery = { - _sql: 'select * from `posts`', + _statements: [], + _single: {table: 'posts'}, clone() { modelState.countCloned = true; return countQuery; @@ -35,9 +36,6 @@ function createBookshelf({countRows, fetchResult, selectError, fetchError} = {}) return Promise.reject(selectError); } return Promise.resolve(countRows || [{aggregate: 1}]); - }, - toSQL() { - return {sql: countQuery._sql}; } }; @@ -79,6 +77,25 @@ function createBookshelf({countRows, fetchResult, selectError, fetchError} = {}) return {bookshelf, modelState}; } +// Install `state` onto the fake query builder returned by `model.query()` +// so that countQuery._statements / _single reflect the scenario under test. +// Only the keys present on `state` are written. +function stubCountQuery(model, state) { + const originalQuery = model.query; + model.query = function () { + const qb = originalQuery.apply(this, arguments); + if (arguments.length === 0) { + if (state.statements !== undefined) { + qb._statements = state.statements; + } + if (state.single !== undefined) { + qb._single = state.single; + } + } + return qb; + }; +} + describe('@tryghost/bookshelf-pagination', function () { it('internal parseOptions handles bad and all limits', function () { assert.deepEqual(paginationPlugin.paginationUtils.parseOptions({limit: 'bad', page: 'bad'}), { @@ -211,63 +228,86 @@ describe('@tryghost/bookshelf-pagination', function () { assert.equal(modelState.rawCalls[0], 'count(*) as aggregate'); }); - it('useSmartCount uses count(*) when SQL has commas outside FROM', async function () { + it('useSmartCount uses count(*) when a subquery-with-JOIN appears in WHERE', async function () { + // Regression: JOIN inside a `where id in (subquery)` is a `where` + // grouping on the outer query, not a `join` — the outer row set is + // still unique per base row, so count(*) is safe. const {bookshelf, modelState} = createBookshelf({countRows: [{aggregate: 1}]}); const model = new bookshelf.Model(); - // Simulate a typical single-table query with multiple selected columns - const originalQuery = model.query; - model.query = function () { - const qb = originalQuery.apply(this, arguments); - if (arguments.length === 0) { - qb._sql = 'select `posts`.`id`, `posts`.`title` from `posts` where `posts`.`status` = ?'; - } - return qb; - }; + stubCountQuery(model, { + statements: [{grouping: 'where', type: 'whereIn', column: 'posts.id'}], + single: {table: 'posts'} + }); await model.fetchPage({page: 1, limit: 10, useSmartCount: true}); assert.equal(modelState.rawCalls[0], 'count(*) as aggregate'); }); - for (const [joinType, sql] of [ - ['leftJoin', 'select * from `posts` left join `tags` on `posts`.`id` = `tags`.`post_id`'], - ['rightJoin', 'select * from `posts` right join `tags` on `posts`.`id` = `tags`.`post_id`'], - ['innerJoin', 'select * from `posts` inner join `tags` on `posts`.`id` = `tags`.`post_id`'], - ['joinRaw', 'select * from `posts` LEFT JOIN tags ON posts.id = tags.post_id'] - ]) { - it(`useSmartCount uses distinct count when ${joinType} is present`, async function () { - const {bookshelf, modelState} = createBookshelf({countRows: [{aggregate: 1}]}); - const model = new bookshelf.Model(); - - // Simulate a JOIN in the compiled SQL - const originalQuery = model.query; - model.query = function () { - const qb = originalQuery.apply(this, arguments); - if (arguments.length === 0) { - qb._sql = sql; - } - return qb; - }; - - await model.fetchPage({page: 1, limit: 10, useSmartCount: true}); - - assert.equal(modelState.rawCalls[0], 'count(distinct posts.id) as aggregate'); + it('useSmartCount uses distinct count when the query has a join grouping', async function () { + const {bookshelf, modelState} = createBookshelf({countRows: [{aggregate: 1}]}); + const model = new bookshelf.Model(); + + stubCountQuery(model, { + statements: [{grouping: 'join', type: 'inner', table: 'authors'}], + single: {table: 'posts'} }); - } - it('useSmartCount uses distinct count when comma-separated FROM sources are present', async function () { + await model.fetchPage({page: 1, limit: 10, useSmartCount: true}); + + assert.equal(modelState.rawCalls[0], 'count(distinct posts.id) as aggregate'); + }); + + it('useSmartCount uses distinct count when the FROM source is a derived table', async function () { const {bookshelf, modelState} = createBookshelf({countRows: [{aggregate: 1}]}); const model = new bookshelf.Model(); - const originalQuery = model.query; - model.query = function () { - const qb = originalQuery.apply(this, arguments); - if (arguments.length === 0) { - qb._sql = 'select * from `posts`, `tags` where `posts`.`id` = `tags`.`post_id`'; - } - return qb; - }; + stubCountQuery(model, { + statements: [], + single: {table: {fakeBuilder: true}} + }); + + await model.fetchPage({page: 1, limit: 10, useSmartCount: true}); + + assert.equal(modelState.rawCalls[0], 'count(distinct posts.id) as aggregate'); + }); + + it('useSmartCount uses count(*) when the query has only a CTE (with) grouping', async function () { + const {bookshelf, modelState} = createBookshelf({countRows: [{aggregate: 1}]}); + const model = new bookshelf.Model(); + + stubCountQuery(model, { + statements: [{grouping: 'with', alias: 'cte'}], + single: {table: 'posts'} + }); + + await model.fetchPage({page: 1, limit: 10, useSmartCount: true}); + + assert.equal(modelState.rawCalls[0], 'count(*) as aggregate'); + }); + + it('useSmartCount uses distinct count when the query has a union grouping', async function () { + const {bookshelf, modelState} = createBookshelf({countRows: [{aggregate: 1}]}); + const model = new bookshelf.Model(); + + stubCountQuery(model, { + statements: [{grouping: 'union', all: false}], + single: {table: 'posts'} + }); + + await model.fetchPage({page: 1, limit: 10, useSmartCount: true}); + + assert.equal(modelState.rawCalls[0], 'count(distinct posts.id) as aggregate'); + }); + + it('useSmartCount uses distinct count when FROM has comma-separated tables', async function () { + const {bookshelf, modelState} = createBookshelf({countRows: [{aggregate: 1}]}); + const model = new bookshelf.Model(); + + stubCountQuery(model, { + single: {table: 'posts, tags'} + }); await model.fetchPage({page: 1, limit: 10, useSmartCount: true}); From 7bb1424c9164dcea24353067d825bcde9344aec9 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Tue, 14 Apr 2026 16:53:27 +0100 Subject: [PATCH 2/3] Added real bookshelf + knex + sqlite integration tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The stub-based smart-count tests were testing our assumptions about knex's internal AST (`_statements` / `_single.table`), not knex itself — if knex shipped a version that reorganised those fields, the tests would keep passing and production would break. This replaces the 7 AST-stub tests with two layers of real-knex coverage: 1. Direct `hasMultiTableSource` tests against real knex QueryBuilders (no DB connection) — cover every branch including edge cases like UNION and a comma-containing `_single.table` string that can't be exercised cleanly end-to-end. 2. End-to-end `fetchPage` tests against a real bookshelf model backed by an in-memory sqlite database. Each test asserts both the chosen count aggregate (via knex's `query` event) and that the resulting total matches what the fetch actually returns — so "we picked count(*)" and "count(*) gives the right answer on this shape" are verified together. `hasMultiTableSource` is now exposed on `paginationUtils` so the direct tests can call it; it's used the same way `parseOptions` and `formatResponse` already are. Adds `bookshelf@1.2.0`, `knex@3.2.9` (matches the monorepo) and `sqlite3@5.1.7` as devDependencies. 31 tests, 100% line/branch/function coverage on `lib/bookshelf-pagination.js`. --- .../lib/bookshelf-pagination.js | 1 + packages/bookshelf-pagination/package.json | 5 +- .../test/pagination-integration.test.js | 283 ++++++++ .../test/pagination.test.js | 126 +--- yarn.lock | 650 +++++++++++++++++- 5 files changed, 916 insertions(+), 149 deletions(-) create mode 100644 packages/bookshelf-pagination/test/pagination-integration.test.js diff --git a/packages/bookshelf-pagination/lib/bookshelf-pagination.js b/packages/bookshelf-pagination/lib/bookshelf-pagination.js index c9168ee6b..a4d20956b 100644 --- a/packages/bookshelf-pagination/lib/bookshelf-pagination.js +++ b/packages/bookshelf-pagination/lib/bookshelf-pagination.js @@ -68,6 +68,7 @@ defaults = { * @api private */ paginationUtils = { + hasMultiTableSource: hasMultiTableSource, /** * ### Parse Options * Take the given options and ensure they are valid pagination options, else use the defaults diff --git a/packages/bookshelf-pagination/package.json b/packages/bookshelf-pagination/package.json index 31b510020..cc6c865ce 100644 --- a/packages/bookshelf-pagination/package.json +++ b/packages/bookshelf-pagination/package.json @@ -23,7 +23,10 @@ "access": "public" }, "devDependencies": { - "sinon": "21.1.2" + "bookshelf": "1.2.0", + "knex": "3.2.9", + "sinon": "21.1.2", + "sqlite3": "5.1.7" }, "dependencies": { "@tryghost/errors": "^3.0.3", diff --git a/packages/bookshelf-pagination/test/pagination-integration.test.js b/packages/bookshelf-pagination/test/pagination-integration.test.js new file mode 100644 index 000000000..e6c7e71f7 --- /dev/null +++ b/packages/bookshelf-pagination/test/pagination-integration.test.js @@ -0,0 +1,283 @@ +// End-to-end tests for the pagination plugin against a real bookshelf model +// backed by an in-memory sqlite database. These pin the plugin's assumptions +// about knex's query-builder AST (`_statements`, `_single.table`) to the +// versions of knex and bookshelf actually installed, and validate that the +// chosen count aggregate returns the same row count as the matching fetch. +const assert = require('node:assert/strict'); +const knex = require('knex'); +const bookshelf = require('bookshelf'); +const paginationPlugin = require('../lib/bookshelf-pagination'); + +const {hasMultiTableSource} = paginationPlugin.paginationUtils; + +async function setupDatabase() { + const db = knex({ + client: 'sqlite3', + useNullAsDefault: true, + connection: ':memory:' + }); + + await db.schema.createTable('authors', (t) => { + t.string('id').primary(); + t.string('name'); + }); + await db.schema.createTable('posts', (t) => { + t.string('id').primary(); + t.string('title'); + t.string('status'); + t.string('author_id'); + }); + await db.schema.createTable('tags', (t) => { + t.string('id').primary(); + t.string('name'); + }); + await db.schema.createTable('posts_tags', (t) => { + t.string('post_id'); + t.string('tag_id'); + }); + + await db('authors').insert([ + {id: 'a1', name: 'Alice'}, + {id: 'a2', name: 'Bob'} + ]); + await db('posts').insert([ + {id: 'p1', title: 'one', status: 'published', author_id: 'a1'}, + {id: 'p2', title: 'two', status: 'published', author_id: 'a1'}, + {id: 'p3', title: 'three', status: 'draft', author_id: 'a2'} + ]); + await db('tags').insert([ + {id: 't1', name: 'tech'}, + {id: 't2', name: 'news'} + ]); + // p1 has two tags — the outer-join cases below will duplicate p1 into + // two physical rows, which is what makes count(*) vs count(distinct) + // observable at the result level. + await db('posts_tags').insert([ + {post_id: 'p1', tag_id: 't1'}, + {post_id: 'p1', tag_id: 't2'}, + {post_id: 'p2', tag_id: 't1'} + ]); + + // Capture every compiled SQL query so each test can assert which count + // aggregate the plugin chose by inspecting the count query directly. + const queries = []; + db.on('query', (q) => { + queries.push(q.sql); + }); + + const bk = bookshelf(db); + paginationPlugin(bk); + + const Post = bk.model('Post', {tableName: 'posts', idAttribute: 'id'}); + + return {db, Post, queries}; +} + +function countQuerySql(queries) { + return queries.find(sql => typeof sql === 'string' && /\bcount\(/i.test(sql)); +} + +describe('hasMultiTableSource against real knex builders', function () { + // These exercise hasMultiTableSource directly using real knex + // QueryBuilders (no DB connection). They pin the plugin's assumptions + // about knex's internal AST shape — `_statements` / `_single.table` — + // to the version of knex installed, and cover branches that are hard + // to drive end-to-end through fetchPage. + let db; + + beforeEach(function () { + db = knex({client: 'sqlite3', useNullAsDefault: true}); + }); + + afterEach(async function () { + await db.destroy(); + }); + + it('returns false for a plain single-table query', function () { + assert.equal(hasMultiTableSource(db('posts').where('status', 'published')), false); + }); + + it('returns false when a JOIN is nested inside a WHERE subquery', function () { + const qb = db('posts').whereIn('posts.id', function () { + this.select('post_id') + .from('posts_tags') + .innerJoin('users', 'users.id', 'posts_tags.author_id') + .where('users.id', 1); + }); + assert.equal(hasMultiTableSource(qb), false); + }); + + it('returns true for an outer innerJoin', function () { + assert.equal(hasMultiTableSource(db('posts').innerJoin('tags', 'tags.id', 'posts.id')), true); + }); + + it('returns true for leftJoin, rightJoin and joinRaw', function () { + assert.equal(hasMultiTableSource(db('posts').leftJoin('tags', 'tags.id', 'posts.id')), true); + assert.equal(hasMultiTableSource(db('posts').joinRaw('right join tags on tags.id = posts.id')), true); + }); + + it('returns true for a UNION query', function () { + const qb = db('posts').select('id').where('status', 'published').union(function () { + this.select('id').from('posts').where('status', 'draft'); + }); + assert.equal(hasMultiTableSource(qb), true); + }); + + it('returns true for a derived table in FROM', function () { + const derived = db('posts').where('status', 'published').as('sub'); + assert.equal(hasMultiTableSource(db.queryBuilder().from(derived)), true); + }); + + it('returns true for fromRaw with multiple tables', function () { + assert.equal(hasMultiTableSource(db.queryBuilder().fromRaw('`posts`, `tags`')), true); + }); + + it('returns true when the table string itself contains a comma', function () { + // Real knex accepts this shape; it only fails at execution time. + // The detection must still classify it as multi-source. + assert.equal(hasMultiTableSource(db('posts, tags')), true); + }); + + it('returns false for a CTE-only query with a single-table outer FROM', function () { + const qb = db('posts') + .with('published_ids', db('posts').select('id').where('status', 'published')) + .whereIn('posts.id', db('published_ids').select('id')); + assert.equal(hasMultiTableSource(qb), false); + }); +}); + +function usesCountStar(sql) { + return /\bcount\(\*\) as aggregate\b/i.test(sql); +} + +function usesCountDistinct(sql) { + return /\bcount\(distinct posts\.id\) as aggregate\b/i.test(sql); +} + +describe('@tryghost/bookshelf-pagination (integration)', function () { + let db; + let Post; + let queries; + + beforeEach(async function () { + ({db, Post, queries} = await setupDatabase()); + }); + + afterEach(async function () { + if (db) { + await db.destroy(); + } + }); + + it('useSmartCount picks count(*) for a plain single-table filter', async function () { + const result = await Post.forge() + .where('author_id', 'a1') + .fetchPage({page: 1, limit: 10, useSmartCount: true}); + + const countSql = countQuerySql(queries); + assert.ok(usesCountStar(countSql), `expected count(*), got: ${countSql}`); + assert.equal(result.pagination.total, 2); + assert.equal(result.collection.length, 2); + }); + + it('useSmartCount picks count(*) when a JOIN is nested inside a WHERE subquery', async function () { + // Regression: `where id in (select … inner join …)` must stay on + // the count(*) path because the outer query is still single-table. + const result = await Post.forge() + .query(function (qb) { + qb.whereIn('posts.id', function () { + this.select('posts_tags.post_id') + .from('posts_tags') + .innerJoin('posts as p2', 'p2.id', 'posts_tags.post_id') + .innerJoin('authors', 'authors.id', 'p2.author_id') + .where('authors.name', 'Alice'); + }); + }) + .fetchPage({page: 1, limit: 10, useSmartCount: true}); + + const countSql = countQuerySql(queries); + assert.ok(usesCountStar(countSql), `expected count(*), got: ${countSql}`); + assert.equal(result.pagination.total, 2); + assert.equal(result.collection.length, 2); + }); + + it('useSmartCount picks count(distinct) when an outer INNER JOIN duplicates rows', async function () { + // p1 has two tags, so posts × posts_tags produces three physical + // rows for the two published posts. count(*) would report 3; + // count(distinct posts.id) must report 2. + const result = await Post.forge() + .query(function (qb) { + qb.innerJoin('posts_tags', 'posts_tags.post_id', 'posts.id') + .where('posts.status', 'published'); + }) + .fetchPage({page: 1, limit: 10, useSmartCount: true}); + + const countSql = countQuerySql(queries); + assert.ok(usesCountDistinct(countSql), `expected count(distinct), got: ${countSql}`); + assert.equal(result.pagination.total, 2); + }); + + it('useSmartCount picks count(distinct) for a joinRaw', async function () { + const result = await Post.forge() + .query(function (qb) { + qb.joinRaw('inner join `posts_tags` on `posts_tags`.`post_id` = `posts`.`id`') + .where('posts.status', 'published'); + }) + .fetchPage({page: 1, limit: 10, useSmartCount: true}); + + const countSql = countQuerySql(queries); + assert.ok(usesCountDistinct(countSql), `expected count(distinct), got: ${countSql}`); + assert.equal(result.pagination.total, 2); + }); + + it('useSmartCount picks count(distinct) when the FROM source is a derived table', async function () { + const result = await Post.forge() + .query(function (qb) { + qb.from(db('posts').where('status', 'published').as('posts')); + }) + .fetchPage({page: 1, limit: 10, useSmartCount: true}); + + const countSql = countQuerySql(queries); + assert.ok(usesCountDistinct(countSql), `expected count(distinct), got: ${countSql}`); + assert.equal(result.pagination.total, 2); + }); + + it('useSmartCount picks count(*) when the query has only a CTE (with) clause', async function () { + const result = await Post.forge() + .query(function (qb) { + qb.with('published_ids', db('posts').select('id').where('status', 'published')) + .whereIn('posts.id', db('published_ids').select('id')); + }) + .fetchPage({page: 1, limit: 10, useSmartCount: true}); + + const countSql = countQuerySql(queries); + assert.ok(usesCountStar(countSql), `expected count(*), got: ${countSql}`); + assert.equal(result.pagination.total, 2); + }); + + it('useBasicCount skips the smart-count check entirely', async function () { + // useBasicCount forces count(*) even on a join — the caller opts + // in to "I know what I'm doing" and accepts row duplication. + // p1 has two tags, so count(*) reports 3 rows for 2 published posts. + const result = await Post.forge() + .query(function (qb) { + qb.innerJoin('posts_tags', 'posts_tags.post_id', 'posts.id') + .where('posts.status', 'published'); + }) + .fetchPage({page: 1, limit: 10, useBasicCount: true}); + + const countSql = countQuerySql(queries); + assert.ok(usesCountStar(countSql), `expected count(*), got: ${countSql}`); + assert.equal(result.pagination.total, 3); + }); + + it('default (no count option) uses count(distinct)', async function () { + const result = await Post.forge() + .where('status', 'published') + .fetchPage({page: 1, limit: 10}); + + const countSql = countQuerySql(queries); + assert.ok(usesCountDistinct(countSql), `expected count(distinct), got: ${countSql}`); + assert.equal(result.pagination.total, 2); + }); +}); diff --git a/packages/bookshelf-pagination/test/pagination.test.js b/packages/bookshelf-pagination/test/pagination.test.js index 6c3c80d58..ceaade0a9 100644 --- a/packages/bookshelf-pagination/test/pagination.test.js +++ b/packages/bookshelf-pagination/test/pagination.test.js @@ -77,25 +77,6 @@ function createBookshelf({countRows, fetchResult, selectError, fetchError} = {}) return {bookshelf, modelState}; } -// Install `state` onto the fake query builder returned by `model.query()` -// so that countQuery._statements / _single reflect the scenario under test. -// Only the keys present on `state` are written. -function stubCountQuery(model, state) { - const originalQuery = model.query; - model.query = function () { - const qb = originalQuery.apply(this, arguments); - if (arguments.length === 0) { - if (state.statements !== undefined) { - qb._statements = state.statements; - } - if (state.single !== undefined) { - qb._single = state.single; - } - } - return qb; - }; -} - describe('@tryghost/bookshelf-pagination', function () { it('internal parseOptions handles bad and all limits', function () { assert.deepEqual(paginationPlugin.paginationUtils.parseOptions({limit: 'bad', page: 'bad'}), { @@ -195,7 +176,7 @@ describe('@tryghost/bookshelf-pagination', function () { }); }); - it('supports useBasicCount and transacting', async function () { + it('passes the transacting option through to the count query', async function () { const {bookshelf, modelState} = createBookshelf({countRows: [{aggregate: 1}]}); const model = new bookshelf.Model(); @@ -207,111 +188,6 @@ describe('@tryghost/bookshelf-pagination', function () { }); assert.equal(modelState.transacting, 'trx'); - assert.equal(modelState.rawCalls[0], 'count(*) as aggregate'); - }); - - it('uses distinct count query by default', async function () { - const {bookshelf, modelState} = createBookshelf({countRows: [{aggregate: 1}]}); - const model = new bookshelf.Model(); - - await model.fetchPage({page: 2, limit: 10}); - - assert.equal(modelState.rawCalls[0], 'count(distinct posts.id) as aggregate'); - }); - - it('useSmartCount uses count(*) when no JOINs present', async function () { - const {bookshelf, modelState} = createBookshelf({countRows: [{aggregate: 1}]}); - const model = new bookshelf.Model(); - - await model.fetchPage({page: 1, limit: 10, useSmartCount: true}); - - assert.equal(modelState.rawCalls[0], 'count(*) as aggregate'); - }); - - it('useSmartCount uses count(*) when a subquery-with-JOIN appears in WHERE', async function () { - // Regression: JOIN inside a `where id in (subquery)` is a `where` - // grouping on the outer query, not a `join` — the outer row set is - // still unique per base row, so count(*) is safe. - const {bookshelf, modelState} = createBookshelf({countRows: [{aggregate: 1}]}); - const model = new bookshelf.Model(); - - stubCountQuery(model, { - statements: [{grouping: 'where', type: 'whereIn', column: 'posts.id'}], - single: {table: 'posts'} - }); - - await model.fetchPage({page: 1, limit: 10, useSmartCount: true}); - - assert.equal(modelState.rawCalls[0], 'count(*) as aggregate'); - }); - - it('useSmartCount uses distinct count when the query has a join grouping', async function () { - const {bookshelf, modelState} = createBookshelf({countRows: [{aggregate: 1}]}); - const model = new bookshelf.Model(); - - stubCountQuery(model, { - statements: [{grouping: 'join', type: 'inner', table: 'authors'}], - single: {table: 'posts'} - }); - - await model.fetchPage({page: 1, limit: 10, useSmartCount: true}); - - assert.equal(modelState.rawCalls[0], 'count(distinct posts.id) as aggregate'); - }); - - it('useSmartCount uses distinct count when the FROM source is a derived table', async function () { - const {bookshelf, modelState} = createBookshelf({countRows: [{aggregate: 1}]}); - const model = new bookshelf.Model(); - - stubCountQuery(model, { - statements: [], - single: {table: {fakeBuilder: true}} - }); - - await model.fetchPage({page: 1, limit: 10, useSmartCount: true}); - - assert.equal(modelState.rawCalls[0], 'count(distinct posts.id) as aggregate'); - }); - - it('useSmartCount uses count(*) when the query has only a CTE (with) grouping', async function () { - const {bookshelf, modelState} = createBookshelf({countRows: [{aggregate: 1}]}); - const model = new bookshelf.Model(); - - stubCountQuery(model, { - statements: [{grouping: 'with', alias: 'cte'}], - single: {table: 'posts'} - }); - - await model.fetchPage({page: 1, limit: 10, useSmartCount: true}); - - assert.equal(modelState.rawCalls[0], 'count(*) as aggregate'); - }); - - it('useSmartCount uses distinct count when the query has a union grouping', async function () { - const {bookshelf, modelState} = createBookshelf({countRows: [{aggregate: 1}]}); - const model = new bookshelf.Model(); - - stubCountQuery(model, { - statements: [{grouping: 'union', all: false}], - single: {table: 'posts'} - }); - - await model.fetchPage({page: 1, limit: 10, useSmartCount: true}); - - assert.equal(modelState.rawCalls[0], 'count(distinct posts.id) as aggregate'); - }); - - it('useSmartCount uses distinct count when FROM has comma-separated tables', async function () { - const {bookshelf, modelState} = createBookshelf({countRows: [{aggregate: 1}]}); - const model = new bookshelf.Model(); - - stubCountQuery(model, { - single: {table: 'posts, tags'} - }); - - await model.fetchPage({page: 1, limit: 10, useSmartCount: true}); - - assert.equal(modelState.rawCalls[0], 'count(distinct posts.id) as aggregate'); }); it('falls back to zero total when aggregate row is missing', async function () { diff --git a/yarn.lock b/yarn.lock index 485fe341b..e8d598133 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1817,6 +1817,11 @@ "@eslint/core" "^0.17.0" levn "^0.4.1" +"@gar/promisify@^1.0.1": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" + integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== + "@glimmer/interfaces@0.94.6": version "0.94.6" resolved "https://registry.yarnpkg.com/@glimmer/interfaces/-/interfaces-0.94.6.tgz#a4a2877730f37587326cab361de81cc0da71a823" @@ -2109,6 +2114,22 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@npmcli/fs@^1.0.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.1.tgz#72f719fe935e687c56a4faecf3c03d06ba593257" + integrity sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ== + dependencies: + "@gar/promisify" "^1.0.1" + semver "^7.3.5" + +"@npmcli/move-file@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" + integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== + dependencies: + mkdirp "^1.0.4" + rimraf "^3.0.2" + "@nx/devkit@22.6.5": version "22.6.5" resolved "https://registry.yarnpkg.com/@nx/devkit/-/devkit-22.6.5.tgz#929988fe36d806e0da64bce27502e6e53a265cab" @@ -2841,6 +2862,11 @@ dependencies: tslib "^2.8.0" +"@tootallnate/once@1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== + "@tryghost/bunyan-rotating-filestream@0.0.7": version "0.0.7" resolved "https://registry.yarnpkg.com/@tryghost/bunyan-rotating-filestream/-/bunyan-rotating-filestream-0.0.7.tgz#3957de91e4e9b58999f0bbe19242080543dcfc4a" @@ -3353,6 +3379,11 @@ dependencies: argparse "^2.0.1" +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -3390,6 +3421,28 @@ address@^1.0.1: resolved "https://registry.yarnpkg.com/address/-/address-1.2.2.tgz#2b5248dac5485a6390532c6a517fda2e3faac89e" integrity sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA== +agent-base@6, agent-base@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +agentkeepalive@^4.1.3: + version "4.6.0" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz#35f73e94b3f40bf65f105219c623ad19c136ea6a" + integrity sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ== + dependencies: + humanize-ms "^1.2.1" + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + ajv@^6.12.4, ajv@^6.14.0: version "6.14.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.14.0.tgz#fd067713e228210636ebb08c60bd3765d6dbe73a" @@ -3460,6 +3513,11 @@ append-field@^1.0.0: resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56" integrity sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw== +"aproba@^1.0.3 || ^2.0.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.1.0.tgz#75500a190313d95c64e871e7e4284c6ac219f0b1" + integrity sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew== + archiver-utils@^5.0.0, archiver-utils@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-5.0.2.tgz#63bc719d951803efc72cf961a56ef810760dd14d" @@ -3486,6 +3544,14 @@ archiver@7.0.1: tar-stream "^3.0.0" zip-stream "^6.0.1" +are-we-there-yet@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz#679df222b278c64f2cdba1175cdc00b0d96164bd" + integrity sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + arg@^4.1.0: version "4.1.3" resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" @@ -3722,6 +3788,13 @@ bcryptjs@3.0.3: resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-3.0.3.tgz#4b93d6a398c48bfc9f32ee65d301174a8a8ea56f" integrity sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g== +bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + bintrees@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.2.tgz#49f896d6e858a4a499df85c38fb399b9aff840f8" @@ -3736,7 +3809,7 @@ bl@^4.0.3: inherits "^2.0.4" readable-stream "^3.4.0" -bluebird@^3.1.1: +bluebird@^3.1.1, bluebird@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== @@ -3756,6 +3829,16 @@ body-parser@^2.2.1: raw-body "^3.0.1" type-is "^2.0.1" +bookshelf@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/bookshelf/-/bookshelf-1.2.0.tgz#cb972aa2316405d3a4af9cb1e2814895ab23283e" + integrity sha512-rm04YpHkLej6bkNezKUQjzuXV30rbyEHQoaKvfQ3fOyLYxPeB18uBL+h2t6SmeXjfsB+aReMmbhkMF/lUTbtMA== + dependencies: + bluebird "^3.7.2" + create-error "~0.3.1" + inflection "^1.12.0" + lodash "^4.17.15" + bowser@^2.11.0: version "2.14.1" resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.14.1.tgz#4ea39bf31e305184522d7ad7bfd91389e4f0cb79" @@ -3891,6 +3974,30 @@ cac@^6.7.14: resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== +cacache@^15.2.0: + version "15.3.0" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" + integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== + dependencies: + "@npmcli/fs" "^1.0.0" + "@npmcli/move-file" "^1.0.1" + chownr "^2.0.0" + fs-minipass "^2.0.0" + glob "^7.1.4" + infer-owner "^1.0.4" + lru-cache "^6.0.0" + minipass "^3.1.1" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.2" + mkdirp "^1.0.3" + p-map "^4.0.0" + promise-inflight "^1.0.1" + rimraf "^3.0.2" + ssri "^8.0.1" + tar "^6.0.2" + unique-filename "^1.1.1" + cacheable-lookup@7.0.0, cacheable-lookup@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz#3476a8215d046e5a3202a9209dd13fec1f933a27" @@ -3981,6 +4088,16 @@ check-error@^2.1.1: resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.3.tgz#2427361117b70cca8dc89680ead32b157019caf5" integrity sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA== +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + ci-info@^3.3.0: version "3.9.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" @@ -3998,6 +4115,11 @@ clean-regexp@^1.0.0: dependencies: escape-string-regexp "^1.0.5" +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + cli-cursor@3.1.0, cli-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" @@ -4050,6 +4172,11 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +color-support@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + colorette@2.0.19: version "2.0.19" resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" @@ -4139,6 +4266,11 @@ concat-stream@^2.0.0: readable-stream "^3.0.2" typedarray "^0.0.6" +console-control-strings@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== + consolidate@^0.15.1: version "0.15.1" resolved "https://registry.yarnpkg.com/consolidate/-/consolidate-0.15.1.tgz#21ab043235c71a07d45d9aad98593b0dba56bab7" @@ -4222,6 +4354,11 @@ crc32-stream@^6.0.0: crc-32 "^1.2.0" readable-stream "^4.0.0" +create-error@~0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/create-error/-/create-error-0.3.1.tgz#69810245a629e654432bf04377360003a5351a23" + integrity sha512-n/Q4aSCtYuuDneEW5Q+nd0IIZwbwmX/oF6wKcDUhXGJNwhmp2WHEoWKz7X+/H7rBtjimInW7f0ceouxU0SmuzQ== + create-require@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" @@ -4308,11 +4445,23 @@ decompress-response@^10.0.0: dependencies: mimic-response "^4.0.0" +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + deep-eql@^5.0.1: version "5.0.2" resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-5.0.2.tgz#4b756d8d770a9257300825d52a2c2cff99c3a341" integrity sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q== +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + deep-is@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" @@ -4335,11 +4484,21 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== + depd@^2.0.0, depd@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== +detect-libc@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad" + integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== + detect-port@^1.5.1: version "1.6.1" resolved "https://registry.yarnpkg.com/detect-port/-/detect-port-1.6.1.tgz#45e4073997c5f292b957cb678fb0bb8ed4250a67" @@ -4468,6 +4627,13 @@ encodeurl@^2.0.0: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== +encoding@^0.1.12: + version "0.1.13" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== + dependencies: + iconv-lite "^0.6.2" + end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.5" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.5.tgz#7344d711dea40e0b74abc2ed49778743ccedb08c" @@ -4490,6 +4656,16 @@ enquirer@~2.3.6: dependencies: ansi-colors "^4.1.1" +env-paths@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== + +err-code@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" + integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== + error-ex@^1.3.1: version "1.3.4" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.4.tgz#b3a8d8bb6f92eecc1629e3e27d3c8607a8a32414" @@ -4957,6 +5133,11 @@ events@^3.3.0: resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== +expand-template@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== + expect-type@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.3.0.tgz#0d58ed361877a31bbc4dd6cf71bbfef7faf6bd68" @@ -5121,6 +5302,11 @@ file-entry-cache@^8.0.0: dependencies: flat-cache "^4.0.0" +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + finalhandler@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-2.1.1.tgz#a2c517a6559852bcdb06d1f8bd7f51b68fad8099" @@ -5268,6 +5454,13 @@ fs-extra@11.3.4: jsonfile "^6.0.1" universalify "^2.0.0" +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -5283,6 +5476,20 @@ function-bind@^1.1.2: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== +gauge@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.4.tgz#52ff0652f2bbf607a989793d53b751bef2328dce" + integrity sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.3" + console-control-strings "^1.1.0" + has-unicode "^2.0.1" + signal-exit "^3.0.7" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.5" + gelf-stream@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/gelf-stream/-/gelf-stream-1.1.1.tgz#9cea9b6386ac301c741838ca3cb91e66dbfbf669" @@ -5361,6 +5568,11 @@ getopts@2.3.0: resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.3.0.tgz#71e5593284807e03e2427449d4f6712a268666f4" integrity sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA== +github-from-package@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" + integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== + glob-parent@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" @@ -5448,7 +5660,7 @@ got@14.6.6: responselike "^4.0.2" type-fest "^4.26.1" -graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4: +graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -5475,6 +5687,11 @@ has-tostringtag@^1.0.2: dependencies: has-symbols "^1.0.3" +has-unicode@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== + hasown@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" @@ -5502,7 +5719,7 @@ html-tags@^3.3.1: resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce" integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ== -http-cache-semantics@^4.2.0: +http-cache-semantics@^4.1.0, http-cache-semantics@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz#205f4db64f8562b76a4ff9235aa5279839a09dd5" integrity sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ== @@ -5518,6 +5735,15 @@ http-errors@^2.0.0, http-errors@^2.0.1, http-errors@~2.0.1: statuses "~2.0.2" toidentifier "~1.0.1" +http-proxy-agent@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" + integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== + dependencies: + "@tootallnate/once" "1" + agent-base "6" + debug "4" + http2-wrapper@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-2.2.1.tgz#310968153dcdedb160d8b72114363ef5fce1f64a" @@ -5526,6 +5752,14 @@ http2-wrapper@^2.2.1: quick-lru "^5.1.1" resolve-alpn "^1.2.0" +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + human-interval@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/human-interval/-/human-interval-2.0.1.tgz#655baf606c7067bb26042dcae14ec777b099af15" @@ -5533,6 +5767,20 @@ human-interval@^2.0.1: dependencies: numbered "^1.1.0" +humanize-ms@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" + integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== + dependencies: + ms "^2.0.0" + +iconv-lite@^0.6.2: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + iconv-lite@^0.7.0, iconv-lite@~0.7.0: version "0.7.2" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.7.2.tgz#d0bdeac3f12b4835b7359c2ad89c422a4d1cc72e" @@ -5573,6 +5821,16 @@ indent-string@^4.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== +infer-owner@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" + integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== + +inflection@^1.12.0: + version "1.13.4" + resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.13.4.tgz#65aa696c4e2da6225b148d7a154c449366633a32" + integrity sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw== + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -5591,11 +5849,21 @@ ini@^2.0.0: resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== +ini@~1.3.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + interpret@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== +ip-address@^10.0.1: + version "10.1.0" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-10.1.0.tgz#d8dcffb34d0e02eb241427444a6e23f5b0595aa4" + integrity sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q== + ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" @@ -5666,6 +5934,11 @@ is-invalid-path@^0.1.0: dependencies: is-glob "^2.0.0" +is-lambda@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" + integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== + is-node-process@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/is-node-process/-/is-node-process-1.2.0.tgz#ea02a1b90ddb3934a19aea414e88edef7e11d134" @@ -6160,6 +6433,13 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + magic-string@^0.30.17: version "0.30.21" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91" @@ -6197,6 +6477,28 @@ make-error@^1.1.1: resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== +make-fetch-happen@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz#53085a09e7971433e6765f7971bf63f4e05cb968" + integrity sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg== + dependencies: + agentkeepalive "^4.1.3" + cacache "^15.2.0" + http-cache-semantics "^4.1.0" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + is-lambda "^1.0.1" + lru-cache "^6.0.0" + minipass "^3.1.3" + minipass-collect "^1.0.2" + minipass-fetch "^1.3.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^0.6.2" + promise-retry "^2.0.1" + socks-proxy-agent "^6.0.0" + ssri "^8.0.0" + makeerror@1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" @@ -6278,6 +6580,11 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + mimic-response@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-4.0.0.tgz#35468b19e7c75d10f5165ea25e75a5ceea7cf70f" @@ -6335,16 +6642,85 @@ minimatch@^9.0.4: dependencies: brace-expansion "^2.0.2" -minimist@^1.2.0, minimist@^1.2.6: +minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== +minipass-collect@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" + integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== + dependencies: + minipass "^3.0.0" + +minipass-fetch@^1.3.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-1.4.1.tgz#d75e0091daac1b0ffd7e9d41629faff7d0c1f1b6" + integrity sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw== + dependencies: + minipass "^3.1.0" + minipass-sized "^1.0.3" + minizlib "^2.0.0" + optionalDependencies: + encoding "^0.1.12" + +minipass-flush@^1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.7.tgz#145c383d5ae294b36030aa80d4e872d08bebcb73" + integrity sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA== + dependencies: + minipass "^3.0.0" + +minipass-pipeline@^1.2.2, minipass-pipeline@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" + integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== + dependencies: + minipass "^3.0.0" + +minipass-sized@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/minipass-sized/-/minipass-sized-1.0.3.tgz#70ee5a7c5052070afacfbc22977ea79def353b70" + integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== + dependencies: + minipass "^3.0.0" + +minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: + version "3.3.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== + dependencies: + yallist "^4.0.0" + +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== + "minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: version "7.1.3" resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.3.tgz#79389b4eb1bb2d003a9bba87d492f2bd37bdc65b" integrity sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A== +minizlib@^2.0.0, minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + +mkdirp@^1.0.3, mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + mkdirp@~0.5.1: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" @@ -6379,7 +6755,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.1.3: +ms@^2.0.0, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -6413,6 +6789,11 @@ nanoid@^3.3.11: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== +napi-build-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz#13c22c0187fcfccce1461844136372a47ddc027e" + integrity sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -6433,6 +6814,11 @@ ncp@~2.0.0: resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3" integrity sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA== +negotiator@^0.6.2: + version "0.6.4" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7" + integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w== + negotiator@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" @@ -6455,6 +6841,34 @@ nock@14.0.12: json-stringify-safe "^5.0.1" propagate "^2.0.0" +node-abi@^3.3.0: + version "3.89.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.89.0.tgz#eea98bf89d4534743bbbf2defa9f4f9bd3bdccfd" + integrity sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA== + dependencies: + semver "^7.3.5" + +node-addon-api@^7.0.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558" + integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ== + +node-gyp@8.x: + version "8.4.1" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.4.1.tgz#3d49308fc31f768180957d6b5746845fbd429937" + integrity sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w== + dependencies: + env-paths "^2.2.0" + glob "^7.1.4" + graceful-fs "^4.2.6" + make-fetch-happen "^9.1.0" + nopt "^5.0.0" + npmlog "^6.0.0" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.2" + which "^2.0.2" + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -6493,6 +6907,13 @@ nodemailer@8.0.5: resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-8.0.5.tgz#2076fb2b5c1ccfe1c88f6e1aa47c0229ea642e0c" integrity sha512-0PF8Yb1yZuQfQbq+5/pZJrtF6WQcjTd5/S4JOHs9PGFxuTqoB/icwuB44pOdURHJbRKX1PPoJZtY7R4VUoCC8w== +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" + normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -6520,6 +6941,16 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" +npmlog@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830" + integrity sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg== + dependencies: + are-we-there-yet "^3.0.0" + console-control-strings "^1.1.0" + gauge "^4.0.3" + set-blocking "^2.0.0" + numbered@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/numbered/-/numbered-1.1.0.tgz#9fcd79564c73a84b9574e8370c3d8e58fe3c133c" @@ -6687,6 +7118,13 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + p-timeout@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" @@ -6835,6 +7273,24 @@ postcss@^8.5.6: picocolors "^1.1.1" source-map-js "^1.2.1" +prebuild-install@^7.1.1: + version "7.1.3" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.3.tgz#d630abad2b147443f20a212917beae68b8092eec" + integrity sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug== + dependencies: + detect-libc "^2.0.0" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" + napi-build-utils "^2.0.0" + node-abi "^3.3.0" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^4.0.0" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -6875,6 +7331,19 @@ prom-client@15.1.3: "@opentelemetry/api" "^1.4.0" tdigest "^0.1.1" +promise-inflight@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" + integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== + +promise-retry@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" + integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== + dependencies: + err-code "^2.0.2" + retry "^0.12.0" + propagate@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" @@ -6958,6 +7427,16 @@ raw-body@^3.0.1: iconv-lite "~0.7.0" unpipe "~1.0.0" +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + react-is@^18.3.1: version "18.3.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" @@ -6995,7 +7474,7 @@ readable-stream@^2.0.5: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0: +readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -7130,6 +7609,11 @@ restore-cursor@^3.1.0: onetime "^5.1.0" signal-exit "^3.0.2" +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== + reusify@^1.0.4: version "1.1.0" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" @@ -7209,16 +7693,16 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" +safe-buffer@^5.0.1, safe-buffer@~5.2.0, safe-buffer@~5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@~5.2.0, safe-buffer@~5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - safe-json-stringify@~1: version "1.2.0" resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz#356e44bc98f1f93ce45df14bcd7c01cda86e0afd" @@ -7293,6 +7777,11 @@ serve-static@^2.2.0: parseurl "^1.3.3" send "^1.2.0" +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + setprototypeof@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" @@ -7355,7 +7844,7 @@ siginfo@^2.0.0: resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== -signal-exit@^3.0.2: +signal-exit@^3.0.2, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== @@ -7365,6 +7854,20 @@ signal-exit@^4.0.1: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" + integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== + dependencies: + decompress-response "^6.0.0" + once "^1.3.1" + simple-concat "^1.0.0" + simple-html-tokenizer@^0.5.11: version "0.5.11" resolved "https://registry.yarnpkg.com/simple-html-tokenizer/-/simple-html-tokenizer-0.5.11.tgz#4c5186083c164ba22a7b477b7687ac056ad6b1d9" @@ -7385,6 +7888,11 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +smart-buffer@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== + smol-toml@1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/smol-toml/-/smol-toml-1.6.1.tgz#4fceb5f7c4b86c2544024ef686e12ff0983465be" @@ -7398,6 +7906,23 @@ snake-case@^3.0.3: dot-case "^3.0.4" tslib "^2.0.3" +socks-proxy-agent@^6.0.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz#2687a31f9d7185e38d530bef1944fe1f1496d6ce" + integrity sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ== + dependencies: + agent-base "^6.0.2" + debug "^4.3.3" + socks "^2.6.2" + +socks@^2.6.2: + version "2.8.7" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.7.tgz#e2fb1d9a603add75050a2067db8c381a0b5669ea" + integrity sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A== + dependencies: + ip-address "^10.0.1" + smart-buffer "^4.2.0" + source-map-js@^1.2.0, source-map-js@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" @@ -7452,6 +7977,25 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== +sqlite3@5.1.7: + version "5.1.7" + resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-5.1.7.tgz#59ca1053c1ab38647396586edad019b1551041b7" + integrity sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog== + dependencies: + bindings "^1.5.0" + node-addon-api "^7.0.0" + prebuild-install "^7.1.1" + tar "^6.1.11" + optionalDependencies: + node-gyp "8.x" + +ssri@^8.0.0, ssri@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" + integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== + dependencies: + minipass "^3.1.1" + stack-utils@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" @@ -7507,7 +8051,7 @@ strict-event-emitter@^0.5.1: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -7577,6 +8121,11 @@ strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + strip-literal@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/strip-literal/-/strip-literal-3.1.0.tgz#222b243dd2d49c0bcd0de8906adbd84177196032" @@ -7662,17 +8211,17 @@ tapable@^2.3.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.2.tgz#86755feabad08d82a26b891db044808c6ad00f15" integrity sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA== -tar-stream@^3.0.0: - version "3.1.8" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-3.1.8.tgz#a26f5b26c34dfd4936a4f8a9e694a8f5102af13d" - integrity sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ== +tar-fs@^2.0.0: + version "2.1.4" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.4.tgz#800824dbf4ef06ded9afea4acafe71c67c76b930" + integrity sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ== dependencies: - b4a "^1.6.4" - bare-fs "^4.5.5" - fast-fifo "^1.2.0" - streamx "^2.15.0" + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" -tar-stream@~2.2.0: +tar-stream@^2.1.4, tar-stream@~2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== @@ -7683,6 +8232,28 @@ tar-stream@~2.2.0: inherits "^2.0.3" readable-stream "^3.1.1" +tar-stream@^3.0.0: + version "3.1.8" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-3.1.8.tgz#a26f5b26c34dfd4936a4f8a9e694a8f5102af13d" + integrity sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ== + dependencies: + b4a "^1.6.4" + bare-fs "^4.5.5" + fast-fifo "^1.2.0" + streamx "^2.15.0" + +tar@^6.0.2, tar@^6.1.11, tar@^6.1.2: + version "6.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" + integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^5.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + tarn@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/tarn/-/tarn-3.0.2.tgz#73b6140fbb881b71559c4f8bfde3d9a4b3d27693" @@ -7845,6 +8416,13 @@ tslib@^2.0.3, tslib@^2.3.0, tslib@^2.4.0, tslib@^2.6.2, tslib@^2.8.0, tslib@^2.8 resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + dependencies: + safe-buffer "^5.0.1" + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -7969,6 +8547,20 @@ unidecode@1.1.0: resolved "https://registry.yarnpkg.com/unidecode/-/unidecode-1.1.0.tgz#0b5f5ddd9b7b1ce3dbc7a24c72150edd63a83a8c" integrity sha512-GIp57N6DVVJi8dpeIU6/leJGdv7W65ZSXFLFiNmxvexXkc0nXdqUvhA/qL9KqBKsILxMwg5MnmYNOIDJLb5JVA== +unique-filename@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" + integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== + dependencies: + unique-slug "^2.0.0" + +unique-slug@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" + integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== + dependencies: + imurmurhash "^0.1.4" + universalify@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" @@ -8100,7 +8692,7 @@ wcwidth@^1.0.0, wcwidth@^1.0.1: dependencies: defaults "^1.0.3" -which@^2.0.1: +which@^2.0.1, which@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== @@ -8115,6 +8707,13 @@ why-is-node-running@^2.3.0: siginfo "^2.0.0" stackback "0.0.2" +wide-align@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + word-wrap@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" @@ -8180,6 +8779,11 @@ yallist@^3.0.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + yaml@^1.10.0: version "1.10.3" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.3.tgz#76e407ed95c42684fb8e14641e5de62fe65bbcb3" From c4ed10b55e08859446c6c1c1bd9503677e70119a Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Tue, 14 Apr 2026 17:06:08 +0100 Subject: [PATCH 3/3] Trimmed fetchPage e2e suite to the two load-bearing cases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The direct hasMultiTableSource describe block already exercises every query shape against real knex — the e2e suite was 1-for-1 duplication for six of its eight tests. Kept only the two that carry unique weight: 1. An inner join that duplicates rows (p1 × two tags → 3 physical rows for 2 published posts). This is the only shape where count(*) and count(distinct) actually produce different totals, so it's the only e2e test that would catch a "we picked the wrong aggregate" bug at the result level. 2. A subquery JOIN in WHERE — the named regression this PR is about, kept as an explicit end-to-end guard. Dropped the plain-filter, joinRaw, derived-table, CTE, useBasicCount and default-distinct cases from the e2e suite; all are already covered more thoroughly by the direct hasMultiTableSource tests above. --- .../test/pagination-integration.test.js | 104 +++--------------- 1 file changed, 16 insertions(+), 88 deletions(-) diff --git a/packages/bookshelf-pagination/test/pagination-integration.test.js b/packages/bookshelf-pagination/test/pagination-integration.test.js index e6c7e71f7..7b9ff0627 100644 --- a/packages/bookshelf-pagination/test/pagination-integration.test.js +++ b/packages/bookshelf-pagination/test/pagination-integration.test.js @@ -154,7 +154,12 @@ function usesCountDistinct(sql) { return /\bcount\(distinct posts\.id\) as aggregate\b/i.test(sql); } -describe('@tryghost/bookshelf-pagination (integration)', function () { +describe('fetchPage end-to-end against real bookshelf + sqlite', function () { + // These two tests are the only fetchPage scenarios where the choice of + // count aggregate actually changes the returned total (inner-join row + // duplication) or is the named regression the PR is about (subquery + // JOIN in WHERE). The remaining query shapes are covered by the direct + // `hasMultiTableSource` suite above, which is faster and more exhaustive. let db; let Post; let queries; @@ -169,39 +174,7 @@ describe('@tryghost/bookshelf-pagination (integration)', function () { } }); - it('useSmartCount picks count(*) for a plain single-table filter', async function () { - const result = await Post.forge() - .where('author_id', 'a1') - .fetchPage({page: 1, limit: 10, useSmartCount: true}); - - const countSql = countQuerySql(queries); - assert.ok(usesCountStar(countSql), `expected count(*), got: ${countSql}`); - assert.equal(result.pagination.total, 2); - assert.equal(result.collection.length, 2); - }); - - it('useSmartCount picks count(*) when a JOIN is nested inside a WHERE subquery', async function () { - // Regression: `where id in (select … inner join …)` must stay on - // the count(*) path because the outer query is still single-table. - const result = await Post.forge() - .query(function (qb) { - qb.whereIn('posts.id', function () { - this.select('posts_tags.post_id') - .from('posts_tags') - .innerJoin('posts as p2', 'p2.id', 'posts_tags.post_id') - .innerJoin('authors', 'authors.id', 'p2.author_id') - .where('authors.name', 'Alice'); - }); - }) - .fetchPage({page: 1, limit: 10, useSmartCount: true}); - - const countSql = countQuerySql(queries); - assert.ok(usesCountStar(countSql), `expected count(*), got: ${countSql}`); - assert.equal(result.pagination.total, 2); - assert.equal(result.collection.length, 2); - }); - - it('useSmartCount picks count(distinct) when an outer INNER JOIN duplicates rows', async function () { + it('inner join that duplicates rows picks count(distinct) and returns the distinct total', async function () { // p1 has two tags, so posts × posts_tags produces three physical // rows for the two published posts. count(*) would report 3; // count(distinct posts.id) must report 2. @@ -217,67 +190,22 @@ describe('@tryghost/bookshelf-pagination (integration)', function () { assert.equal(result.pagination.total, 2); }); - it('useSmartCount picks count(distinct) for a joinRaw', async function () { + it('subquery JOIN in WHERE picks count(*) — the Ghost regression this PR fixes', async function () { const result = await Post.forge() .query(function (qb) { - qb.joinRaw('inner join `posts_tags` on `posts_tags`.`post_id` = `posts`.`id`') - .where('posts.status', 'published'); - }) - .fetchPage({page: 1, limit: 10, useSmartCount: true}); - - const countSql = countQuerySql(queries); - assert.ok(usesCountDistinct(countSql), `expected count(distinct), got: ${countSql}`); - assert.equal(result.pagination.total, 2); - }); - - it('useSmartCount picks count(distinct) when the FROM source is a derived table', async function () { - const result = await Post.forge() - .query(function (qb) { - qb.from(db('posts').where('status', 'published').as('posts')); - }) - .fetchPage({page: 1, limit: 10, useSmartCount: true}); - - const countSql = countQuerySql(queries); - assert.ok(usesCountDistinct(countSql), `expected count(distinct), got: ${countSql}`); - assert.equal(result.pagination.total, 2); - }); - - it('useSmartCount picks count(*) when the query has only a CTE (with) clause', async function () { - const result = await Post.forge() - .query(function (qb) { - qb.with('published_ids', db('posts').select('id').where('status', 'published')) - .whereIn('posts.id', db('published_ids').select('id')); + qb.whereIn('posts.id', function () { + this.select('posts_tags.post_id') + .from('posts_tags') + .innerJoin('posts as p2', 'p2.id', 'posts_tags.post_id') + .innerJoin('authors', 'authors.id', 'p2.author_id') + .where('authors.name', 'Alice'); + }); }) .fetchPage({page: 1, limit: 10, useSmartCount: true}); const countSql = countQuerySql(queries); assert.ok(usesCountStar(countSql), `expected count(*), got: ${countSql}`); assert.equal(result.pagination.total, 2); - }); - - it('useBasicCount skips the smart-count check entirely', async function () { - // useBasicCount forces count(*) even on a join — the caller opts - // in to "I know what I'm doing" and accepts row duplication. - // p1 has two tags, so count(*) reports 3 rows for 2 published posts. - const result = await Post.forge() - .query(function (qb) { - qb.innerJoin('posts_tags', 'posts_tags.post_id', 'posts.id') - .where('posts.status', 'published'); - }) - .fetchPage({page: 1, limit: 10, useBasicCount: true}); - - const countSql = countQuerySql(queries); - assert.ok(usesCountStar(countSql), `expected count(*), got: ${countSql}`); - assert.equal(result.pagination.total, 3); - }); - - it('default (no count option) uses count(distinct)', async function () { - const result = await Post.forge() - .where('status', 'published') - .fetchPage({page: 1, limit: 10}); - - const countSql = countQuerySql(queries); - assert.ok(usesCountDistinct(countSql), `expected count(distinct), got: ${countSql}`); - assert.equal(result.pagination.total, 2); + assert.equal(result.collection.length, 2); }); });