confluence-cli 2.2.0 → 2.3.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/README.md CHANGED
@@ -694,8 +694,8 @@ confluence stats
694
694
  | `spaces` | List available spaces | `--limit <number>` |
695
695
  | `find <title>` | Find a page by its title | `--space <spaceKey>` |
696
696
  | `children <pageId>` | List child pages of a page | `--recursive`, `--max-depth <number>`, `--format <list\|tree\|json>`, `--show-url`, `--show-id` |
697
- | `create <title> <spaceKey>` | Create a new page | `--content <string>`, `--file <path>`, `--format <storage\|html\|markdown>`|
698
- | `create-child <title> <parentId>` | Create a child page | `--content <string>`, `--file <path>`, `--format <storage\|html\|markdown>` |
697
+ | `create <title> <spaceKey>` | Create a new page or folder | `--content <string>`, `--file <path>`, `--format <storage\|html\|markdown>`, `--type <page\|folder>` |
698
+ | `create-child <title> <parentId>` | Create a child page or folder | `--content <string>`, `--file <path>`, `--format <storage\|html\|markdown>`, `--type <page\|folder>` |
699
699
  | `copy-tree <sourcePageId> <targetParentId> [newTitle]` | Copy page tree with all children | `--max-depth <number>`, `--exclude <patterns>`, `--delay-ms <ms>`, `--copy-suffix <text>`, `--dry-run`, `--fail-on-error`, `--quiet` |
700
700
  | `update <pageId>` | Update a page's title or content | `--title <string>`, `--content <string>`, `--file <path>`, `--format <storage\|html\|markdown>` |
701
701
  | `move <pageId_or_url> <newParentId_or_url>` | Move a page to a new parent location | `--title <string>` |
@@ -752,6 +752,10 @@ confluence move 123456789 987654321 --title "New Title"
752
752
  confluence attachment-upload 123456789 --file ./report.pdf
753
753
  confluence attachment-delete 123456789 998877 --yes
754
754
 
755
+ # Create a folder (no content body required)
756
+ confluence create "Engineering Docs" MYSPACE --type folder
757
+ confluence create-child "Sub-folder" 123456789 --type folder
758
+
755
759
  # View usage statistics
756
760
  confluence stats
757
761
 
package/bin/confluence.js CHANGED
@@ -22,6 +22,20 @@ function assertNonEmpty(value, label) {
22
22
  }
23
23
  }
24
24
 
25
+ const VALID_TYPES = ['page', 'folder'];
26
+
27
+ function assertValidType(type) {
28
+ if (!VALID_TYPES.includes(type)) {
29
+ throw new Error(`Invalid type "${type}". Valid: ${VALID_TYPES.join(', ')}`);
30
+ }
31
+ }
32
+
33
+ function assertNoBodyForFolder(type, options) {
34
+ if (type === 'folder' && (options.file || options.content)) {
35
+ throw new Error('--file/--content is not allowed with --type folder (folders have no body).');
36
+ }
37
+ }
38
+
25
39
  function handleCommandError(analytics, commandName, error) {
26
40
  analytics.track(commandName, false);
27
41
  console.error(chalk.red('Error:'), error.message);
@@ -219,15 +233,18 @@ program
219
233
  // Create command
220
234
  program
221
235
  .command('create <title> <spaceKey>')
222
- .description('Create a new Confluence page')
236
+ .description('Create a new Confluence page or folder')
223
237
  .option('-f, --file <file>', 'Read content from file')
224
238
  .option('-c, --content <content>', 'Page content as string')
225
239
  .option('--format <format>', 'Content format (storage, html, markdown)', 'storage')
240
+ .option('--type <type>', 'Content type (page, folder)', 'page')
226
241
  .action(async (title, spaceKey, options) => {
227
242
  const analytics = new Analytics();
228
243
  try {
229
244
  assertNonEmpty(title, 'title');
230
245
  assertNonEmpty(spaceKey, 'spaceKey');
246
+ assertValidType(options.type);
247
+ assertNoBodyForFolder(options.type, options);
231
248
 
232
249
  const config = getConfig(getProfileName());
233
250
  assertWritable(config);
@@ -243,13 +260,14 @@ program
243
260
  content = fs.readFileSync(options.file, 'utf8');
244
261
  } else if (options.content) {
245
262
  content = options.content;
246
- } else {
263
+ } else if (options.type !== 'folder') {
247
264
  throw new Error('Either --file or --content option is required');
248
265
  }
249
266
 
250
- const result = await client.createPage(title, spaceKey, content, options.format);
251
-
252
- console.log(chalk.green(' Page created successfully!'));
267
+ const result = await client.createPage(title, spaceKey, content, options.format, options.type);
268
+
269
+ const label = options.type === 'folder' ? 'Folder' : 'Page';
270
+ console.log(chalk.green(`✅ ${label} created successfully!`));
253
271
  console.log(`Title: ${chalk.blue(result.title)}`);
254
272
  console.log(`ID: ${chalk.blue(result.id)}`);
255
273
  console.log(`Space: ${chalk.blue(result.space.name)} (${result.space.key})`);
@@ -264,15 +282,18 @@ program
264
282
  // Create child page command
265
283
  program
266
284
  .command('create-child <title> <parentId>')
267
- .description('Create a new Confluence page as a child of another page')
285
+ .description('Create a new Confluence page or folder as a child of another page')
268
286
  .option('-f, --file <file>', 'Read content from file')
269
287
  .option('-c, --content <content>', 'Page content as string')
270
288
  .option('--format <format>', 'Content format (storage, html, markdown)', 'storage')
289
+ .option('--type <type>', 'Content type (page, folder)', 'page')
271
290
  .action(async (title, parentId, options) => {
272
291
  const analytics = new Analytics();
273
292
  try {
274
293
  assertNonEmpty(title, 'title');
275
294
  assertNonEmpty(parentId, 'parentId');
295
+ assertValidType(options.type);
296
+ assertNoBodyForFolder(options.type, options);
276
297
 
277
298
  const config = getConfig(getProfileName());
278
299
  assertWritable(config);
@@ -281,9 +302,9 @@ program
281
302
  // Get parent page info to get space key
282
303
  const parentInfo = await client.getPageInfo(parentId);
283
304
  const spaceKey = parentInfo.space.key;
284
-
305
+
285
306
  let content = '';
286
-
307
+
287
308
  if (options.file) {
288
309
  const fs = require('fs');
289
310
  if (!fs.existsSync(options.file)) {
@@ -292,13 +313,14 @@ program
292
313
  content = fs.readFileSync(options.file, 'utf8');
293
314
  } else if (options.content) {
294
315
  content = options.content;
295
- } else {
316
+ } else if (options.type !== 'folder') {
296
317
  throw new Error('Either --file or --content option is required');
297
318
  }
298
-
299
- const result = await client.createChildPage(title, spaceKey, parentId, content, options.format);
300
-
301
- console.log(chalk.green(' Child page created successfully!'));
319
+
320
+ const result = await client.createChildPage(title, spaceKey, parentId, content, options.format, options.type);
321
+
322
+ const label = options.type === 'folder' ? 'Folder' : 'Child page';
323
+ console.log(chalk.green(`✅ ${label} created successfully!`));
302
324
  console.log(`Title: ${chalk.blue(result.title)}`);
303
325
  console.log(`ID: ${chalk.blue(result.id)}`);
304
326
  console.log(`Parent: ${chalk.blue(parentInfo.title)} (${parentId})`);
@@ -2084,6 +2106,8 @@ module.exports = {
2084
2106
  sanitizeFilename,
2085
2107
  assertWritable,
2086
2108
  assertNonEmpty,
2109
+ assertValidType,
2110
+ assertNoBodyForFolder,
2087
2111
  handleCommandError,
2088
2112
  },
2089
2113
  };
@@ -1207,29 +1207,32 @@ class ConfluenceClient {
1207
1207
  /**
1208
1208
  * Create a new Confluence page
1209
1209
  */
1210
- async createPage(title, spaceKey, content, format = 'storage') {
1211
- let storageContent = content;
1212
-
1213
- if (format === 'markdown') {
1214
- storageContent = this.markdownToStorage(content);
1215
- } else if (format === 'html') {
1216
- // Convert HTML directly to storage format (no macro wrapper)
1217
- storageContent = content;
1218
- }
1219
-
1210
+ async createPage(title, spaceKey, content, format = 'storage', type = 'page') {
1220
1211
  const pageData = {
1221
- type: 'page',
1212
+ type: type,
1222
1213
  title: title,
1223
1214
  space: {
1224
1215
  key: spaceKey
1225
- },
1226
- body: {
1216
+ }
1217
+ };
1218
+
1219
+ if (type !== 'folder') {
1220
+ let storageContent = content;
1221
+
1222
+ if (format === 'markdown') {
1223
+ storageContent = this.markdownToStorage(content);
1224
+ } else if (format === 'html') {
1225
+ // Convert HTML directly to storage format (no macro wrapper)
1226
+ storageContent = content;
1227
+ }
1228
+
1229
+ pageData.body = {
1227
1230
  storage: {
1228
1231
  value: storageContent,
1229
1232
  representation: 'storage'
1230
1233
  }
1231
- }
1232
- };
1234
+ };
1235
+ }
1233
1236
 
1234
1237
  const response = await this.client.post('/content', pageData);
1235
1238
  return response.data;
@@ -1238,18 +1241,9 @@ class ConfluenceClient {
1238
1241
  /**
1239
1242
  * Create a new Confluence page as a child of another page
1240
1243
  */
1241
- async createChildPage(title, spaceKey, parentId, content, format = 'storage') {
1242
- let storageContent = content;
1243
-
1244
- if (format === 'markdown') {
1245
- storageContent = this.markdownToStorage(content);
1246
- } else if (format === 'html') {
1247
- // Convert HTML directly to storage format (no macro wrapper)
1248
- storageContent = content;
1249
- }
1250
-
1244
+ async createChildPage(title, spaceKey, parentId, content, format = 'storage', type = 'page') {
1251
1245
  const pageData = {
1252
- type: 'page',
1246
+ type: type,
1253
1247
  title: title,
1254
1248
  space: {
1255
1249
  key: spaceKey
@@ -1258,14 +1252,26 @@ class ConfluenceClient {
1258
1252
  {
1259
1253
  id: parentId
1260
1254
  }
1261
- ],
1262
- body: {
1255
+ ]
1256
+ };
1257
+
1258
+ if (type !== 'folder') {
1259
+ let storageContent = content;
1260
+
1261
+ if (format === 'markdown') {
1262
+ storageContent = this.markdownToStorage(content);
1263
+ } else if (format === 'html') {
1264
+ // Convert HTML directly to storage format (no macro wrapper)
1265
+ storageContent = content;
1266
+ }
1267
+
1268
+ pageData.body = {
1263
1269
  storage: {
1264
1270
  value: storageContent,
1265
1271
  representation: 'storage'
1266
1272
  }
1267
- }
1268
- };
1273
+ };
1274
+ }
1269
1275
 
1270
1276
  const response = await this.client.post('/content', pageData);
1271
1277
  return response.data;
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "confluence-cli",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "confluence-cli",
9
- "version": "2.2.0",
9
+ "version": "2.3.0",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "axios": "^1.15.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "confluence-cli",
3
- "version": "2.2.0",
3
+ "version": "2.3.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": {
@@ -255,10 +255,10 @@ confluence children 123456789 --recursive --format tree --show-id
255
255
 
256
256
  ### `create <title> <spaceKey>`
257
257
 
258
- Create a new top-level page in a space.
258
+ Create a new top-level page or folder in a space.
259
259
 
260
260
  ```sh
261
- confluence create <title> <spaceKey> [--content <string>] [--file <path>] [--format storage|html|markdown]
261
+ confluence create <title> <spaceKey> [--content <string>] [--file <path>] [--format storage|html|markdown] [--type page|folder]
262
262
  ```
263
263
 
264
264
  | Option | Default | Description |
@@ -266,31 +266,34 @@ confluence create <title> <spaceKey> [--content <string>] [--file <path>] [--for
266
266
  | `--content` | — | Inline content string |
267
267
  | `--file` | — | Path to content file |
268
268
  | `--format` | `storage` | Content format |
269
+ | `--type` | `page` | Content type — `page` (default) or `folder`. Folders have no body. |
269
270
 
270
- Either `--content` or `--file` is required.
271
+ Either `--content` or `--file` is required for pages. Folders take no content — passing `--content` or `--file` with `--type folder` is rejected.
271
272
 
272
273
  ```sh
273
274
  confluence create "Project Overview" MYSPACE --content "# Hello" --format markdown
274
275
  confluence create "Release Notes" MYSPACE --file ./notes.md --format markdown
276
+ confluence create "Engineering Docs" MYSPACE --type folder
275
277
  ```
276
278
 
277
- Outputs the new page ID and URL on success.
279
+ Outputs the new page (or folder) ID and URL on success.
278
280
 
279
281
  ---
280
282
 
281
283
  ### `create-child <title> <parentId>`
282
284
 
283
- Create a child page under an existing page. Inherits the parent's space automatically.
285
+ Create a child page or folder under an existing page. Inherits the parent's space automatically.
284
286
 
285
287
  ```sh
286
- confluence create-child <title> <parentId> [--content <string>] [--file <path>] [--format storage|html|markdown]
288
+ confluence create-child <title> <parentId> [--content <string>] [--file <path>] [--format storage|html|markdown] [--type page|folder]
287
289
  ```
288
290
 
289
- Options are identical to `create`. Either `--content` or `--file` is required.
291
+ Options are identical to `create`. Either `--content` or `--file` is required for pages; folders take no content.
290
292
 
291
293
  ```sh
292
294
  confluence create-child "Chapter 1" 123456789 --content "Content here" --format markdown
293
295
  confluence create-child "API Guide" 123456789 --file ./api.md --format markdown
296
+ confluence create-child "Sub-folder" 123456789 --type folder
294
297
  ```
295
298
 
296
299
  ---