confluence-cli 1.8.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +26 -0
- package/bin/confluence.js +185 -0
- package/lib/confluence-client.js +129 -0
- package/package.json +7 -1
- package/tests/confluence-client.test.js +22 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [1.10.0](https://github.com/pchuri/confluence-cli/compare/v1.9.0...v1.10.0) (2025-12-05)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* export page with attachments ([#18](https://github.com/pchuri/confluence-cli/issues/18)) ([bdd9da4](https://github.com/pchuri/confluence-cli/commit/bdd9da474f13a8b6f96e64836443f65f846257a2))
|
|
7
|
+
|
|
8
|
+
# [1.9.0](https://github.com/pchuri/confluence-cli/compare/v1.8.0...v1.9.0) (2025-12-04)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* add attachments list and download command ([#17](https://github.com/pchuri/confluence-cli/issues/17)) ([fb3d4f8](https://github.com/pchuri/confluence-cli/commit/fb3d4f81a3926fec832a39c78f4eda3b4a22130a))
|
|
14
|
+
|
|
1
15
|
# [1.8.0](https://github.com/pchuri/confluence-cli/compare/v1.7.0...v1.8.0) (2025-09-28)
|
|
2
16
|
|
|
3
17
|
|
package/README.md
CHANGED
|
@@ -10,6 +10,8 @@ A powerful command-line interface for Atlassian Confluence that allows you to re
|
|
|
10
10
|
- 🏠 **List spaces** - View all available Confluence spaces
|
|
11
11
|
- ✏️ **Create pages** - Create new pages with support for Markdown, HTML, or Storage format
|
|
12
12
|
- 📝 **Update pages** - Update existing page content and titles
|
|
13
|
+
- 📎 **Attachments** - List or download page attachments
|
|
14
|
+
- 📦 **Export** - Save a page and its attachments to a local folder
|
|
13
15
|
- 🛠️ **Edit workflow** - Export page content for editing and re-import
|
|
14
16
|
- 🔧 **Easy setup** - Simple configuration with environment variables or interactive setup
|
|
15
17
|
|
|
@@ -107,6 +109,30 @@ confluence search "search term"
|
|
|
107
109
|
confluence search "search term" --limit 5
|
|
108
110
|
```
|
|
109
111
|
|
|
112
|
+
### List or Download Attachments
|
|
113
|
+
```bash
|
|
114
|
+
# List all attachments on a page
|
|
115
|
+
confluence attachments 123456789
|
|
116
|
+
|
|
117
|
+
# Filter by filename and limit the number returned
|
|
118
|
+
confluence attachments 123456789 --pattern "*.png" --limit 5
|
|
119
|
+
|
|
120
|
+
# Download matching attachments to a directory
|
|
121
|
+
confluence attachments 123456789 --pattern "*.png" --download --dest ./downloads
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Export a Page with Attachments
|
|
125
|
+
```bash
|
|
126
|
+
# Export page content (markdown by default) and all attachments
|
|
127
|
+
confluence export 123456789 --dest ./exports
|
|
128
|
+
|
|
129
|
+
# Custom content format/filename and attachment filtering
|
|
130
|
+
confluence export 123456789 --format html --file content.html --pattern "*.png"
|
|
131
|
+
|
|
132
|
+
# Skip attachments if you only need the content file
|
|
133
|
+
confluence export 123456789 --skip-attachments
|
|
134
|
+
```
|
|
135
|
+
|
|
110
136
|
### List Spaces
|
|
111
137
|
```bash
|
|
112
138
|
confluence spaces
|
package/bin/confluence.js
CHANGED
|
@@ -337,6 +337,191 @@ program
|
|
|
337
337
|
}
|
|
338
338
|
});
|
|
339
339
|
|
|
340
|
+
// Attachments command
|
|
341
|
+
program
|
|
342
|
+
.command('attachments <pageId>')
|
|
343
|
+
.description('List or download attachments for a page')
|
|
344
|
+
.option('-l, --limit <limit>', 'Maximum number of attachments to fetch (default: all)')
|
|
345
|
+
.option('-p, --pattern <glob>', 'Filter attachments by filename (e.g., "*.png")')
|
|
346
|
+
.option('-d, --download', 'Download matching attachments')
|
|
347
|
+
.option('--dest <directory>', 'Directory to save downloads (default: current directory)', '.')
|
|
348
|
+
.action(async (pageId, options) => {
|
|
349
|
+
const analytics = new Analytics();
|
|
350
|
+
try {
|
|
351
|
+
const config = getConfig();
|
|
352
|
+
const client = new ConfluenceClient(config);
|
|
353
|
+
const maxResults = options.limit ? parseInt(options.limit, 10) : null;
|
|
354
|
+
const pattern = options.pattern ? options.pattern.trim() : null;
|
|
355
|
+
|
|
356
|
+
if (options.limit && (Number.isNaN(maxResults) || maxResults <= 0)) {
|
|
357
|
+
throw new Error('Limit must be a positive number.');
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const attachments = await client.getAllAttachments(pageId, { maxResults });
|
|
361
|
+
const filtered = pattern ? attachments.filter(att => client.matchesPattern(att.title, pattern)) : attachments;
|
|
362
|
+
|
|
363
|
+
if (filtered.length === 0) {
|
|
364
|
+
console.log(chalk.yellow('No attachments found.'));
|
|
365
|
+
analytics.track('attachments', true);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
console.log(chalk.blue(`Found ${filtered.length} attachment${filtered.length === 1 ? '' : 's'}:`));
|
|
370
|
+
filtered.forEach((att, index) => {
|
|
371
|
+
const sizeKb = att.fileSize ? `${Math.max(1, Math.round(att.fileSize / 1024))} KB` : 'unknown size';
|
|
372
|
+
const typeLabel = att.mediaType || 'unknown';
|
|
373
|
+
console.log(`${index + 1}. ${chalk.green(att.title)} (ID: ${att.id})`);
|
|
374
|
+
console.log(` Type: ${chalk.gray(typeLabel)} • Size: ${chalk.gray(sizeKb)} • Version: ${chalk.gray(att.version)}`);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
if (options.download) {
|
|
378
|
+
const fs = require('fs');
|
|
379
|
+
const path = require('path');
|
|
380
|
+
const destDir = path.resolve(options.dest || '.');
|
|
381
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
382
|
+
|
|
383
|
+
const uniquePathFor = (dir, filename) => {
|
|
384
|
+
const parsed = path.parse(filename);
|
|
385
|
+
let attempt = path.join(dir, filename);
|
|
386
|
+
let counter = 1;
|
|
387
|
+
while (fs.existsSync(attempt)) {
|
|
388
|
+
const suffix = ` (${counter})`;
|
|
389
|
+
const nextName = `${parsed.name}${suffix}${parsed.ext}`;
|
|
390
|
+
attempt = path.join(dir, nextName);
|
|
391
|
+
counter += 1;
|
|
392
|
+
}
|
|
393
|
+
return attempt;
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
const writeStream = (stream, targetPath) => new Promise((resolve, reject) => {
|
|
397
|
+
const writer = fs.createWriteStream(targetPath);
|
|
398
|
+
stream.pipe(writer);
|
|
399
|
+
stream.on('error', reject);
|
|
400
|
+
writer.on('error', reject);
|
|
401
|
+
writer.on('finish', resolve);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
let downloaded = 0;
|
|
405
|
+
for (const attachment of filtered) {
|
|
406
|
+
const targetPath = uniquePathFor(destDir, attachment.title);
|
|
407
|
+
const dataStream = await client.downloadAttachment(pageId, attachment.id);
|
|
408
|
+
await writeStream(dataStream, targetPath);
|
|
409
|
+
downloaded += 1;
|
|
410
|
+
console.log(`⬇️ ${chalk.green(attachment.title)} -> ${chalk.gray(targetPath)}`);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
console.log(chalk.green(`Downloaded ${downloaded} attachment${downloaded === 1 ? '' : 's'} to ${destDir}`));
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
analytics.track('attachments', true);
|
|
417
|
+
} catch (error) {
|
|
418
|
+
analytics.track('attachments', false);
|
|
419
|
+
console.error(chalk.red('Error:'), error.message);
|
|
420
|
+
process.exit(1);
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// Export page content with attachments
|
|
425
|
+
program
|
|
426
|
+
.command('export <pageId>')
|
|
427
|
+
.description('Export a page to a directory with its attachments')
|
|
428
|
+
.option('--format <format>', 'Content format (html, text, markdown)', 'markdown')
|
|
429
|
+
.option('--dest <directory>', 'Base directory to export into', '.')
|
|
430
|
+
.option('--file <filename>', 'Content filename (default: page.<ext>)')
|
|
431
|
+
.option('--attachments-dir <name>', 'Subdirectory for attachments', 'attachments')
|
|
432
|
+
.option('--pattern <glob>', 'Filter attachments by filename (e.g., "*.png")')
|
|
433
|
+
.option('--skip-attachments', 'Do not download attachments')
|
|
434
|
+
.action(async (pageId, options) => {
|
|
435
|
+
const analytics = new Analytics();
|
|
436
|
+
try {
|
|
437
|
+
const config = getConfig();
|
|
438
|
+
const client = new ConfluenceClient(config);
|
|
439
|
+
const fs = require('fs');
|
|
440
|
+
const path = require('path');
|
|
441
|
+
|
|
442
|
+
const format = (options.format || 'markdown').toLowerCase();
|
|
443
|
+
const formatExt = { markdown: 'md', html: 'html', text: 'txt' };
|
|
444
|
+
const contentExt = formatExt[format] || 'txt';
|
|
445
|
+
|
|
446
|
+
const pageInfo = await client.getPageInfo(pageId);
|
|
447
|
+
const content = await client.readPage(pageId, format);
|
|
448
|
+
|
|
449
|
+
const baseDir = path.resolve(options.dest || '.');
|
|
450
|
+
const folderName = sanitizeTitle(pageInfo.title || 'page');
|
|
451
|
+
const exportDir = path.join(baseDir, folderName);
|
|
452
|
+
fs.mkdirSync(exportDir, { recursive: true });
|
|
453
|
+
|
|
454
|
+
const contentFile = options.file || `page.${contentExt}`;
|
|
455
|
+
const contentPath = path.join(exportDir, contentFile);
|
|
456
|
+
fs.writeFileSync(contentPath, content);
|
|
457
|
+
|
|
458
|
+
console.log(chalk.green('✅ Page exported'));
|
|
459
|
+
console.log(`Title: ${chalk.blue(pageInfo.title)}`);
|
|
460
|
+
console.log(`Content: ${chalk.gray(contentPath)}`);
|
|
461
|
+
|
|
462
|
+
if (!options.skipAttachments) {
|
|
463
|
+
const pattern = options.pattern ? options.pattern.trim() : null;
|
|
464
|
+
const attachments = await client.getAllAttachments(pageId);
|
|
465
|
+
const filtered = pattern ? attachments.filter(att => client.matchesPattern(att.title, pattern)) : attachments;
|
|
466
|
+
|
|
467
|
+
if (filtered.length === 0) {
|
|
468
|
+
console.log(chalk.yellow('No attachments to download.'));
|
|
469
|
+
} else {
|
|
470
|
+
const attachmentsDirName = options.attachmentsDir || 'attachments';
|
|
471
|
+
const attachmentsDir = path.join(exportDir, attachmentsDirName);
|
|
472
|
+
fs.mkdirSync(attachmentsDir, { recursive: true });
|
|
473
|
+
|
|
474
|
+
const uniquePathFor = (dir, filename) => {
|
|
475
|
+
const parsed = path.parse(filename);
|
|
476
|
+
let attempt = path.join(dir, filename);
|
|
477
|
+
let counter = 1;
|
|
478
|
+
while (fs.existsSync(attempt)) {
|
|
479
|
+
const suffix = ` (${counter})`;
|
|
480
|
+
const nextName = `${parsed.name}${suffix}${parsed.ext}`;
|
|
481
|
+
attempt = path.join(dir, nextName);
|
|
482
|
+
counter += 1;
|
|
483
|
+
}
|
|
484
|
+
return attempt;
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
const writeStream = (stream, targetPath) => new Promise((resolve, reject) => {
|
|
488
|
+
const writer = fs.createWriteStream(targetPath);
|
|
489
|
+
stream.pipe(writer);
|
|
490
|
+
stream.on('error', reject);
|
|
491
|
+
writer.on('error', reject);
|
|
492
|
+
writer.on('finish', resolve);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
let downloaded = 0;
|
|
496
|
+
for (const attachment of filtered) {
|
|
497
|
+
const targetPath = uniquePathFor(attachmentsDir, attachment.title);
|
|
498
|
+
const dataStream = await client.downloadAttachment(pageId, attachment.id);
|
|
499
|
+
await writeStream(dataStream, targetPath);
|
|
500
|
+
downloaded += 1;
|
|
501
|
+
console.log(`⬇️ ${chalk.green(attachment.title)} -> ${chalk.gray(targetPath)}`);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
console.log(chalk.green(`Downloaded ${downloaded} attachment${downloaded === 1 ? '' : 's'} to ${attachmentsDir}`));
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
analytics.track('export', true);
|
|
509
|
+
} catch (error) {
|
|
510
|
+
analytics.track('export', false);
|
|
511
|
+
console.error(chalk.red('Error:'), error.message);
|
|
512
|
+
process.exit(1);
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
function sanitizeTitle(value) {
|
|
517
|
+
const fallback = 'page';
|
|
518
|
+
if (!value || typeof value !== 'string') {
|
|
519
|
+
return fallback;
|
|
520
|
+
}
|
|
521
|
+
const cleaned = value.replace(/[\\/:*?"<>|]/g, ' ').trim();
|
|
522
|
+
return cleaned || fallback;
|
|
523
|
+
}
|
|
524
|
+
|
|
340
525
|
// Copy page tree command
|
|
341
526
|
program
|
|
342
527
|
.command('copy-tree <sourcePageId> <targetParentId> [newTitle]')
|
package/lib/confluence-client.js
CHANGED
|
@@ -167,6 +167,76 @@ class ConfluenceClient {
|
|
|
167
167
|
}));
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
/**
|
|
171
|
+
* List attachments for a page with pagination support
|
|
172
|
+
*/
|
|
173
|
+
async listAttachments(pageIdOrUrl, options = {}) {
|
|
174
|
+
const pageId = this.extractPageId(pageIdOrUrl);
|
|
175
|
+
const limit = this.parsePositiveInt(options.limit, 50);
|
|
176
|
+
const start = this.parsePositiveInt(options.start, 0);
|
|
177
|
+
const params = {
|
|
178
|
+
limit,
|
|
179
|
+
start
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
if (options.filename) {
|
|
183
|
+
params.filename = options.filename;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const response = await this.client.get(`/content/${pageId}/child/attachment`, { params });
|
|
187
|
+
const results = Array.isArray(response.data.results)
|
|
188
|
+
? response.data.results.map((item) => this.normalizeAttachment(item))
|
|
189
|
+
: [];
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
results,
|
|
193
|
+
nextStart: this.parseNextStart(response.data?._links?.next)
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Fetch all attachments for a page, honoring an optional maxResults cap
|
|
199
|
+
*/
|
|
200
|
+
async getAllAttachments(pageIdOrUrl, options = {}) {
|
|
201
|
+
const pageSize = this.parsePositiveInt(options.pageSize || options.limit, 50);
|
|
202
|
+
const maxResults = this.parsePositiveInt(options.maxResults, null);
|
|
203
|
+
const filename = options.filename;
|
|
204
|
+
let start = this.parsePositiveInt(options.start, 0);
|
|
205
|
+
const attachments = [];
|
|
206
|
+
|
|
207
|
+
let hasNext = true;
|
|
208
|
+
while (hasNext) {
|
|
209
|
+
const page = await this.listAttachments(pageIdOrUrl, {
|
|
210
|
+
limit: pageSize,
|
|
211
|
+
start,
|
|
212
|
+
filename
|
|
213
|
+
});
|
|
214
|
+
attachments.push(...page.results);
|
|
215
|
+
|
|
216
|
+
if (maxResults && attachments.length >= maxResults) {
|
|
217
|
+
return attachments.slice(0, maxResults);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
hasNext = page.nextStart !== null && page.nextStart !== undefined;
|
|
221
|
+
if (hasNext) {
|
|
222
|
+
start = page.nextStart;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return attachments;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Download an attachment's data stream
|
|
231
|
+
*/
|
|
232
|
+
async downloadAttachment(pageIdOrUrl, attachmentId, options = {}) {
|
|
233
|
+
const pageId = this.extractPageId(pageIdOrUrl);
|
|
234
|
+
const response = await this.client.get(`/content/${pageId}/child/attachment/${attachmentId}/data`, {
|
|
235
|
+
responseType: options.responseType || 'stream'
|
|
236
|
+
});
|
|
237
|
+
return response.data;
|
|
238
|
+
}
|
|
239
|
+
|
|
170
240
|
/**
|
|
171
241
|
* Convert markdown to Confluence storage format
|
|
172
242
|
*/
|
|
@@ -915,6 +985,65 @@ class ConfluenceClient {
|
|
|
915
985
|
return this.globToRegExp(pattern).test(title);
|
|
916
986
|
});
|
|
917
987
|
}
|
|
988
|
+
|
|
989
|
+
matchesPattern(value, patterns) {
|
|
990
|
+
if (!patterns) {
|
|
991
|
+
return true;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
const list = Array.isArray(patterns) ? patterns.filter(Boolean) : [patterns];
|
|
995
|
+
if (list.length === 0) {
|
|
996
|
+
return true;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
return list.some((pattern) => this.globToRegExp(pattern).test(value));
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
normalizeAttachment(raw) {
|
|
1003
|
+
return {
|
|
1004
|
+
id: raw.id,
|
|
1005
|
+
title: raw.title,
|
|
1006
|
+
mediaType: raw.metadata?.mediaType || raw.type || '',
|
|
1007
|
+
fileSize: raw.extensions?.fileSize || 0,
|
|
1008
|
+
version: raw.version?.number || 1,
|
|
1009
|
+
downloadLink: this.toAbsoluteUrl(raw._links?.download)
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
toAbsoluteUrl(pathOrUrl) {
|
|
1014
|
+
if (!pathOrUrl) {
|
|
1015
|
+
return null;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
if (pathOrUrl.startsWith('http://') || pathOrUrl.startsWith('https://')) {
|
|
1019
|
+
return pathOrUrl;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
const normalized = pathOrUrl.startsWith('/') ? pathOrUrl : `/${pathOrUrl}`;
|
|
1023
|
+
return `https://${this.domain}${normalized}`;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
parseNextStart(nextLink) {
|
|
1027
|
+
if (!nextLink) {
|
|
1028
|
+
return null;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
const match = nextLink.match(/[?&]start=(\d+)/);
|
|
1032
|
+
if (!match) {
|
|
1033
|
+
return null;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
const value = parseInt(match[1], 10);
|
|
1037
|
+
return Number.isNaN(value) ? null : value;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
parsePositiveInt(value, fallback) {
|
|
1041
|
+
const parsed = parseInt(value, 10);
|
|
1042
|
+
if (Number.isNaN(parsed) || parsed < 0) {
|
|
1043
|
+
return fallback;
|
|
1044
|
+
}
|
|
1045
|
+
return parsed;
|
|
1046
|
+
}
|
|
918
1047
|
}
|
|
919
1048
|
|
|
920
1049
|
module.exports = ConfluenceClient;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "confluence-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "A command-line interface for Atlassian Confluence with page creation and editing capabilities",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -35,6 +35,12 @@
|
|
|
35
35
|
"eslint": "^8.55.0",
|
|
36
36
|
"jest": "^29.7.0"
|
|
37
37
|
},
|
|
38
|
+
"overrides": {
|
|
39
|
+
"js-yaml": "^4.1.1",
|
|
40
|
+
"@istanbuljs/load-nyc-config": {
|
|
41
|
+
"js-yaml": "^3.14.2"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
38
44
|
"engines": {
|
|
39
45
|
"node": ">=14.0.0"
|
|
40
46
|
},
|
|
@@ -274,4 +274,26 @@ describe('ConfluenceClient', () => {
|
|
|
274
274
|
expect(client.shouldExcludePage('production', patterns)).toBe(false);
|
|
275
275
|
});
|
|
276
276
|
});
|
|
277
|
+
|
|
278
|
+
describe('attachments', () => {
|
|
279
|
+
test('should have required methods for attachment handling', () => {
|
|
280
|
+
expect(typeof client.listAttachments).toBe('function');
|
|
281
|
+
expect(typeof client.getAllAttachments).toBe('function');
|
|
282
|
+
expect(typeof client.downloadAttachment).toBe('function');
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test('matchesPattern should respect glob patterns', () => {
|
|
286
|
+
expect(client.matchesPattern('report.png', '*.png')).toBe(true);
|
|
287
|
+
expect(client.matchesPattern('report.png', '*.jpg')).toBe(false);
|
|
288
|
+
expect(client.matchesPattern('report.png', ['*.jpg', 'report.*'])).toBe(true);
|
|
289
|
+
expect(client.matchesPattern('report.png', null)).toBe(true);
|
|
290
|
+
expect(client.matchesPattern('report.png', [])).toBe(true);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test('parseNextStart should read start query param when present', () => {
|
|
294
|
+
expect(client.parseNextStart('/rest/api/content/1/child/attachment?start=25')).toBe(25);
|
|
295
|
+
expect(client.parseNextStart('/rest/api/content/1/child/attachment?limit=50')).toBeNull();
|
|
296
|
+
expect(client.parseNextStart(null)).toBeNull();
|
|
297
|
+
});
|
|
298
|
+
});
|
|
277
299
|
});
|