From 065766bc110da177855d53bca44e0fdf3bf04d4f Mon Sep 17 00:00:00 2001 From: lvgx Date: Mon, 18 May 2026 12:22:45 +0800 Subject: [PATCH] feat(search): add start offset pagination Expose the Confluence REST search start offset through the search command so users can request later pages of results. Validate the start value at the CLI layer, pass it through the client, and keep displayed result numbering aligned with the requested offset. Add coverage for both the client request parameter and the CLI help output. --- README.md | 5 ++++- bin/confluence.js | 10 ++++++++-- lib/confluence-client.js | 5 +++-- tests/cli-entry.test.js | 10 ++++++++++ tests/confluence-client.test.js | 12 ++++++++++++ 5 files changed, 37 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8338137..835c9b5 100644 --- a/README.md +++ b/README.md @@ -369,6 +369,9 @@ confluence search "search term" # Limit results confluence search "search term" --limit 5 + +# Paginate results +confluence search "search term" --limit 5 --start 5 ``` ### List or Download Attachments @@ -697,7 +700,7 @@ confluence stats | `init` | Initialize CLI configuration | `--read-only` | | `read ` | Read page content | `--format ` | | `info ` | Get page information | `--format ` | -| `search ` | Search for pages | `--limit ` | +| `search ` | Search for pages | `--limit `, `--start ` | | `spaces` | List available spaces | `--limit `, `--all` | | `find ` | Find a page by its title | `--space <spaceKey>` | | `children <pageId>` | List child pages of a page | `--recursive`, `--max-depth <number>`, `--format <list\|tree\|json>`, `--show-url`, `--show-id` | diff --git a/bin/confluence.js b/bin/confluence.js index 5c2d178..782a3ca 100755 --- a/bin/confluence.js +++ b/bin/confluence.js @@ -166,9 +166,15 @@ program .command('search <query>') .description('Search for Confluence pages') .option('-l, --limit <limit>', 'Limit number of results', '10') + .option('--start <start>', 'Start index for results', '0') .option('--cql', 'Pass query as raw CQL instead of text search') .action(withClient('search', async ({ client, analytics }, query, options) => { - const results = await client.search(query, parseInt(options.limit), options.cql); + const start = parseInt(options.start, 10); + if (Number.isNaN(start) || start < 0) { + throw new Error('Start must be a non-negative number.'); + } + + const results = await client.search(query, parseInt(options.limit), options.cql, start); if (results.length === 0) { console.log(chalk.yellow('No results found.')); @@ -178,7 +184,7 @@ program console.log(chalk.blue(`Found ${results.length} results:`)); results.forEach((result, index) => { - console.log(`${index + 1}. ${chalk.green(result.title)} (ID: ${result.id})`); + console.log(`${start + index + 1}. ${chalk.green(result.title)} (ID: ${result.id})`); if (result.excerpt) { console.log(` ${chalk.gray(result.excerpt)}`); } diff --git a/lib/confluence-client.js b/lib/confluence-client.js index 1a0f60d..7668f12 100644 --- a/lib/confluence-client.js +++ b/lib/confluence-client.js @@ -415,12 +415,13 @@ class ConfluenceClient { /** * Search for pages */ - async search(query, limit = 10, rawCql = false) { + async search(query, limit = 10, rawCql = false, start = 0) { const cql = rawCql ? query : `text ~ "${this.escapeCql(query)}"`; const response = await this.client.get('/search', { params: { cql, - limit: limit + limit, + start } }); diff --git a/tests/cli-entry.test.js b/tests/cli-entry.test.js index f8ff53d..69105cf 100644 --- a/tests/cli-entry.test.js +++ b/tests/cli-entry.test.js @@ -16,6 +16,16 @@ describe('CLI entry point', () => { ).trim(); expect(output).toMatch(/^\d+\.\d+\.\d+$/); }); + + test('search --help documents start pagination option', () => { + const output = execFileSync( + process.execPath, + [path.resolve(__dirname, '../bin/index.js'), 'search', '--help'], + { encoding: 'utf8' } + ); + expect(output).toContain('--start <start>'); + expect(output).toContain('Start index for results'); + }); }); describe('create/create-child --type validation', () => { diff --git a/tests/confluence-client.test.js b/tests/confluence-client.test.js index 640d157..d7565cb 100644 --- a/tests/confluence-client.test.js +++ b/tests/confluence-client.test.js @@ -1134,6 +1134,18 @@ describe('ConfluenceClient', () => { mock.restore(); }); + test('should respect start parameter', async () => { + const mock = new MockAdapter(client.client); + mock.onGet('/search').reply((config) => { + expect(config.params.start).toBe(20); + return [200, { results: [] }]; + }); + + await client.search('test', 10, false, 20); + + mock.restore(); + }); + test('should escape backslashes before double quotes', async () => { const mock = new MockAdapter(client.client); mock.onGet('/search').reply((config) => {