Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -697,7 +700,7 @@ confluence stats
| `init` | Initialize CLI configuration | `--read-only` |
| `read <pageId_or_url>` | Read page content | `--format <html\|text\|storage\|markdown>` |
| `info <pageId_or_url>` | Get page information | `--format <text\|json>` |
| `search <query>` | Search for pages | `--limit <number>` |
| `search <query>` | Search for pages | `--limit <number>`, `--start <number>` |
| `spaces` | List available spaces | `--limit <number>`, `--all` |
| `find <title>` | 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` |
Expand Down
10 changes: 8 additions & 2 deletions bin/confluence.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.'));
Expand All @@ -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)}`);
}
Expand Down
5 changes: 3 additions & 2 deletions lib/confluence-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
});

Expand Down
10 changes: 10 additions & 0 deletions tests/cli-entry.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
12 changes: 12 additions & 0 deletions tests/confluence-client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down