confluence-cli 1.1.0 → 1.2.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.
@@ -64,5 +64,4 @@ jobs:
64
64
  uses: cycjimmy/semantic-release-action@v3
65
65
  env:
66
66
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
67
- NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
68
67
  NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/CHANGELOG.md CHANGED
@@ -1,3 +1,31 @@
1
+ # [1.2.0](https://github.com/pchuri/confluence-cli/compare/v1.1.0...v1.2.0) (2025-06-27)
2
+
3
+
4
+ ### Features
5
+
6
+ * implement page creation and update capabilities ([#2](https://github.com/pchuri/confluence-cli/issues/2)) ([b814ddf](https://github.com/pchuri/confluence-cli/commit/b814ddfd056aeac83cc7eb5d8d6db47ba9c70cdf))
7
+
8
+ # [1.2.0](https://github.com/pchuri/confluence-cli/compare/v1.1.0...v1.2.0) (2025-06-27)
9
+
10
+
11
+ ### Features
12
+
13
+ * **page management**: add page creation and update capabilities ([NEW])
14
+ - `confluence create` - Create new pages with support for Markdown, HTML, and Storage formats
15
+ - `confluence update` - Update existing page content and titles
16
+ - `confluence edit` - Export page content for editing workflow
17
+ - Support for reading content from files or inline
18
+ - Markdown to Confluence Storage format conversion
19
+ * **content formats**: support multiple input formats
20
+ - Markdown format with automatic conversion
21
+ - HTML format with Storage format wrapping
22
+ - Native Confluence Storage format
23
+ * **examples**: add sample files and demo scripts for new features
24
+
25
+ ### Breaking Changes
26
+
27
+ * None - all new features are additive
28
+
1
29
  # [1.1.0](https://github.com/pchuri/confluence-cli/compare/v1.0.0...v1.1.0) (2025-06-26)
2
30
 
3
31
 
package/README.md CHANGED
@@ -8,6 +8,9 @@ A powerful command-line interface for Atlassian Confluence that allows you to re
8
8
  - 🔍 **Search** - Find pages using Confluence's powerful search
9
9
  - â„šī¸ **Page info** - Get detailed information about pages
10
10
  - 🏠 **List spaces** - View all available Confluence spaces
11
+ - âœī¸ **Create pages** - Create new pages with support for Markdown, HTML, or Storage format
12
+ - 📝 **Update pages** - Update existing page content and titles
13
+ - đŸ› ī¸ **Edit workflow** - Export page content for editing and re-import
11
14
  - 🔧 **Easy setup** - Simple configuration with environment variables or interactive setup
12
15
 
13
16
  ## Installation
@@ -38,6 +41,16 @@ npx confluence-cli
38
41
  confluence search "my search term"
39
42
  ```
40
43
 
44
+ 4. **Create a new page:**
45
+ ```bash
46
+ confluence create "My New Page" SPACEKEY --content "Hello World!"
47
+ ```
48
+
49
+ 5. **Update a page:**
50
+ ```bash
51
+ confluence update 123456789 --content "Updated content"
52
+ ```
53
+
41
54
  ## Configuration
42
55
 
43
56
  ### Option 1: Interactive Setup
@@ -65,6 +78,79 @@ export CONFLUENCE_API_TOKEN="your-api-token"
65
78
  # Read by page ID
66
79
  confluence read 123456789
67
80
 
81
+ # Read in HTML format
82
+ confluence read 123456789 --format html
83
+
84
+ # Read by URL
85
+ confluence read "https://your-domain.atlassian.net/wiki/spaces/SPACE/pages/123456789/Page+Title"
86
+ ```
87
+
88
+ ### Create a New Page
89
+ ```bash
90
+ # Create with inline content
91
+ confluence create "My New Page" SPACEKEY --content "This is my page content"
92
+
93
+ # Create from a file
94
+ confluence create "Documentation" SPACEKEY --file ./content.md --format markdown
95
+
96
+ # Create with HTML content
97
+ confluence create "Rich Content" SPACEKEY --file ./content.html --format html
98
+
99
+ # Create with Storage format (Confluence native)
100
+ confluence create "Advanced Page" SPACEKEY --file ./content.xml --format storage
101
+ ```
102
+
103
+ ### Create a Child Page
104
+ ```bash
105
+ # Find parent page first
106
+ confluence find "Project Documentation" --space MYTEAM
107
+
108
+ # Create child page with inline content
109
+ confluence create-child "Meeting Notes" 123456789 --content "This is a child page"
110
+
111
+ # Create child page from file
112
+ confluence create-child "Technical Specifications" 123456789 --file ./content.md --format markdown
113
+
114
+ # Create child page with HTML content
115
+ confluence create-child "Report Summary" 123456789 --file ./content.html --format html
116
+ ```
117
+
118
+ ### Find Pages
119
+ ```bash
120
+ # Find page by title
121
+ confluence find "Project Documentation"
122
+
123
+ # Find page by title in specific space
124
+ confluence find "Project Documentation" --space MYTEAM
125
+ ```
126
+
127
+ ### Update an Existing Page
128
+ ```bash
129
+ # Update content only
130
+ confluence update 123456789 --content "Updated content"
131
+
132
+ # Update content from file
133
+ confluence update 123456789 --file ./updated-content.md --format markdown
134
+
135
+ # Update both title and content
136
+ confluence update 123456789 --title "New Title" --content "New content"
137
+
138
+ # Update from HTML file
139
+ confluence update 123456789 --file ./content.html --format html
140
+ ```
141
+
142
+ ### Edit Workflow
143
+ ```bash
144
+ # Export page content for editing
145
+ confluence edit 123456789 --output ./page-content.xml
146
+
147
+ # Edit the file with your preferred editor
148
+ vim ./page-content.xml
149
+
150
+ # Update the page with your changes
151
+ confluence update 123456789 --file ./page-content.xml --format storage
152
+ ```
153
+
68
154
  # Read with HTML format
69
155
  confluence read 123456789 --format html
70
156
 
@@ -166,7 +252,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
166
252
 
167
253
  ## Roadmap
168
254
 
169
- - [ ] Create and update pages
255
+ - [x] **Create and update pages** ✅
170
256
  - [ ] Page templates
171
257
  - [ ] Bulk operations
172
258
  - [ ] Export pages to different formats
package/bin/confluence.js CHANGED
@@ -135,4 +135,202 @@ program
135
135
  }
136
136
  });
137
137
 
138
+ // Create command
139
+ program
140
+ .command('create <title> <spaceKey>')
141
+ .description('Create a new Confluence page')
142
+ .option('-f, --file <file>', 'Read content from file')
143
+ .option('-c, --content <content>', 'Page content as string')
144
+ .option('--format <format>', 'Content format (storage, html, markdown)', 'storage')
145
+ .action(async (title, spaceKey, options) => {
146
+ const analytics = new Analytics();
147
+ try {
148
+ const config = getConfig();
149
+ const client = new ConfluenceClient(config);
150
+
151
+ let content = '';
152
+
153
+ if (options.file) {
154
+ const fs = require('fs');
155
+ if (!fs.existsSync(options.file)) {
156
+ throw new Error(`File not found: ${options.file}`);
157
+ }
158
+ content = fs.readFileSync(options.file, 'utf8');
159
+ } else if (options.content) {
160
+ content = options.content;
161
+ } else {
162
+ throw new Error('Either --file or --content option is required');
163
+ }
164
+
165
+ const result = await client.createPage(title, spaceKey, content, options.format);
166
+
167
+ console.log(chalk.green('✅ Page created successfully!'));
168
+ console.log(`Title: ${chalk.blue(result.title)}`);
169
+ console.log(`ID: ${chalk.blue(result.id)}`);
170
+ console.log(`Space: ${chalk.blue(result.space.name)} (${result.space.key})`);
171
+ console.log(`URL: ${chalk.gray(`https://${config.domain}/wiki${result._links.webui}`)}`);
172
+
173
+ analytics.track('create', true);
174
+ } catch (error) {
175
+ analytics.track('create', false);
176
+ console.error(chalk.red('Error:'), error.message);
177
+ process.exit(1);
178
+ }
179
+ });
180
+
181
+ // Create child page command
182
+ program
183
+ .command('create-child <title> <parentId>')
184
+ .description('Create a new Confluence page as a child of another page')
185
+ .option('-f, --file <file>', 'Read content from file')
186
+ .option('-c, --content <content>', 'Page content as string')
187
+ .option('--format <format>', 'Content format (storage, html, markdown)', 'storage')
188
+ .action(async (title, parentId, options) => {
189
+ const analytics = new Analytics();
190
+ try {
191
+ const config = getConfig();
192
+ const client = new ConfluenceClient(config);
193
+
194
+ // Get parent page info to get space key
195
+ const parentInfo = await client.getPageInfo(parentId);
196
+ const spaceKey = parentInfo.space.key;
197
+
198
+ let content = '';
199
+
200
+ if (options.file) {
201
+ const fs = require('fs');
202
+ if (!fs.existsSync(options.file)) {
203
+ throw new Error(`File not found: ${options.file}`);
204
+ }
205
+ content = fs.readFileSync(options.file, 'utf8');
206
+ } else if (options.content) {
207
+ content = options.content;
208
+ } else {
209
+ throw new Error('Either --file or --content option is required');
210
+ }
211
+
212
+ const result = await client.createChildPage(title, spaceKey, parentId, content, options.format);
213
+
214
+ console.log(chalk.green('✅ Child page created successfully!'));
215
+ console.log(`Title: ${chalk.blue(result.title)}`);
216
+ console.log(`ID: ${chalk.blue(result.id)}`);
217
+ console.log(`Parent: ${chalk.blue(parentInfo.title)} (${parentId})`);
218
+ console.log(`Space: ${chalk.blue(result.space.name)} (${result.space.key})`);
219
+ console.log(`URL: ${chalk.gray(`https://${config.domain}/wiki${result._links.webui}`)}`);
220
+
221
+ analytics.track('create_child', true);
222
+ } catch (error) {
223
+ analytics.track('create_child', false);
224
+ console.error(chalk.red('Error:'), error.message);
225
+ process.exit(1);
226
+ }
227
+ });
228
+
229
+ // Update command
230
+ program
231
+ .command('update <pageId>')
232
+ .description('Update an existing Confluence page')
233
+ .option('-t, --title <title>', 'New page title (optional)')
234
+ .option('-f, --file <file>', 'Read content from file')
235
+ .option('-c, --content <content>', 'Page content as string')
236
+ .option('--format <format>', 'Content format (storage, html, markdown)', 'storage')
237
+ .action(async (pageId, options) => {
238
+ const analytics = new Analytics();
239
+ try {
240
+ const config = getConfig();
241
+ const client = new ConfluenceClient(config);
242
+
243
+ let content = '';
244
+
245
+ if (options.file) {
246
+ const fs = require('fs');
247
+ if (!fs.existsSync(options.file)) {
248
+ throw new Error(`File not found: ${options.file}`);
249
+ }
250
+ content = fs.readFileSync(options.file, 'utf8');
251
+ } else if (options.content) {
252
+ content = options.content;
253
+ } else {
254
+ throw new Error('Either --file or --content option is required');
255
+ }
256
+
257
+ const result = await client.updatePage(pageId, options.title, content, options.format);
258
+
259
+ console.log(chalk.green('✅ Page updated successfully!'));
260
+ console.log(`Title: ${chalk.blue(result.title)}`);
261
+ console.log(`ID: ${chalk.blue(result.id)}`);
262
+ console.log(`Version: ${chalk.blue(result.version.number)}`);
263
+ console.log(`URL: ${chalk.gray(`https://${config.domain}/wiki${result._links.webui}`)}`);
264
+
265
+ analytics.track('update', true);
266
+ } catch (error) {
267
+ analytics.track('update', false);
268
+ console.error(chalk.red('Error:'), error.message);
269
+ process.exit(1);
270
+ }
271
+ });
272
+
273
+ // Edit command - opens page content for editing
274
+ program
275
+ .command('edit <pageId>')
276
+ .description('Get page content for editing')
277
+ .option('-o, --output <file>', 'Save content to file')
278
+ .action(async (pageId, options) => {
279
+ const analytics = new Analytics();
280
+ try {
281
+ const config = getConfig();
282
+ const client = new ConfluenceClient(config);
283
+ const pageData = await client.getPageForEdit(pageId);
284
+
285
+ console.log(chalk.blue('Page Information:'));
286
+ console.log(`Title: ${chalk.green(pageData.title)}`);
287
+ console.log(`ID: ${chalk.green(pageData.id)}`);
288
+ console.log(`Version: ${chalk.green(pageData.version)}`);
289
+ console.log(`Space: ${chalk.green(pageData.space.name)} (${pageData.space.key})`);
290
+ console.log('');
291
+
292
+ if (options.output) {
293
+ const fs = require('fs');
294
+ fs.writeFileSync(options.output, pageData.content);
295
+ console.log(chalk.green(`✅ Content saved to: ${options.output}`));
296
+ console.log(chalk.yellow('💡 Edit the file and use "confluence update" to save changes'));
297
+ } else {
298
+ console.log(chalk.blue('Page Content:'));
299
+ console.log(pageData.content);
300
+ }
301
+
302
+ analytics.track('edit', true);
303
+ } catch (error) {
304
+ analytics.track('edit', false);
305
+ console.error(chalk.red('Error:'), error.message);
306
+ process.exit(1);
307
+ }
308
+ });
309
+
310
+ // Find page by title command
311
+ program
312
+ .command('find <title>')
313
+ .description('Find a page by title')
314
+ .option('-s, --space <spaceKey>', 'Limit search to specific space')
315
+ .action(async (title, options) => {
316
+ const analytics = new Analytics();
317
+ try {
318
+ const config = getConfig();
319
+ const client = new ConfluenceClient(config);
320
+ const pageInfo = await client.findPageByTitle(title, options.space);
321
+
322
+ console.log(chalk.blue('Page found:'));
323
+ console.log(`Title: ${chalk.green(pageInfo.title)}`);
324
+ console.log(`ID: ${chalk.green(pageInfo.id)}`);
325
+ console.log(`Space: ${chalk.green(pageInfo.space.name)} (${pageInfo.space.key})`);
326
+ console.log(`URL: ${chalk.gray(`https://${config.domain}/wiki${pageInfo.url}`)}`);
327
+
328
+ analytics.track('find', true);
329
+ } catch (error) {
330
+ analytics.track('find', false);
331
+ console.error(chalk.red('Error:'), error.message);
332
+ process.exit(1);
333
+ }
334
+ });
335
+
138
336
  program.parse();
@@ -0,0 +1,65 @@
1
+ #!/bin/bash
2
+
3
+ # ė‹¤ė œ Project Documentation íŽ˜ė´ė§€ í•˜ėœ„ė— í…ŒėŠ¤íŠ¸ íŽ˜ė´ė§€ ėƒė„ą 똈렜
4
+ # ė´ 늤íŦëĻŊ트는 ėŧë°˜ė ė¸ Confluence ė„¤ė •ė—ė„œ ėž‘ë™í•Šë‹ˆë‹¤.
5
+
6
+ echo "🔍 Project Documentation íŽ˜ė´ė§€ í•˜ėœ„ė— í…ŒėŠ¤íŠ¸ íŽ˜ė´ė§€ ėƒė„ą"
7
+ echo "=============================================================="
8
+
9
+ # 1ë‹¨ęŗ„: ëļ€ëǍ íŽ˜ė´ė§€ ė°žę¸°
10
+ echo ""
11
+ echo "1ī¸âƒŖ ëļ€ëǍ íŽ˜ė´ė§€ ė°žę¸°..."
12
+ echo "ė‹¤í–‰: confluence find \"Project Documentation\" --space MYTEAM"
13
+ echo ""
14
+
15
+ # ė‹¤ė œ ė‹¤í–‰í•  때는 ė•„ëž˜ ėŖŧė„ė„ í•´ė œí•˜ė„¸ėš”
16
+ # confluence find "Project Documentation" --space MYTEAM
17
+
18
+ echo "📝 ėœ„ ëĒ…ë šė–´ 결ęŗŧė—ė„œ íŽ˜ė´ė§€ IDëĨŧ í™•ė¸í•˜ė„¸ėš” (똈: 123456789)"
19
+ echo ""
20
+
21
+ # 2ë‹¨ęŗ„: íŽ˜ė´ė§€ ė •ëŗ´ í™•ė¸
22
+ echo "2ī¸âƒŖ íŽ˜ė´ė§€ ė •ëŗ´ í™•ė¸..."
23
+ echo "ė‹¤í–‰: confluence info [íŽ˜ė´ė§€ID]"
24
+ echo "ė˜ˆė‹œ: confluence info 123456789"
25
+ echo ""
26
+
27
+ # 3ë‹¨ęŗ„: íŽ˜ė´ė§€ ë‚´ėšŠ ėŊ기 (ė„ íƒė‚Ŧ항)
28
+ echo "3ī¸âƒŖ íŽ˜ė´ė§€ ë‚´ėšŠ í™•ė¸ (ė„ íƒė‚Ŧ항)..."
29
+ echo "ė‹¤í–‰: confluence read [íŽ˜ė´ė§€ID] | head -20"
30
+ echo "ė˜ˆė‹œ: confluence read 123456789 | head -20"
31
+ echo ""
32
+
33
+ # 4ë‹¨ęŗ„: í…ŒėŠ¤íŠ¸ íŽ˜ė´ė§€ ėƒė„ą
34
+ echo "4ī¸âƒŖ í•˜ėœ„ í…ŒėŠ¤íŠ¸ íŽ˜ė´ė§€ ėƒė„ą..."
35
+ echo ""
36
+
37
+ # 간단한 í…ėŠ¤íŠ¸ ėŊ˜í…ė¸ ëĄœ í…ŒėŠ¤íŠ¸ íŽ˜ė´ė§€ ėƒė„ą
38
+ echo "📄 방법 1: 간단한 í…ėŠ¤íŠ¸ ėŊ˜í…ė¸ ëĄœ ėƒė„ą"
39
+ echo 'confluence create-child "Test Page - $(date +%Y%m%d)" [ëļ€ëĒ¨íŽ˜ė´ė§€ID] --content "ė´ę˛ƒė€ CLI로 ėƒė„ąëœ í…ŒėŠ¤íŠ¸ íŽ˜ė´ė§€ėž…ë‹ˆë‹¤. ėƒė„ą ė‹œę°„: $(date)"'
40
+ echo ""
41
+
42
+ # 마íŦë‹¤ėš´ 파ėŧė—ė„œ í…ŒėŠ¤íŠ¸ íŽ˜ė´ė§€ ėƒė„ą
43
+ echo "📄 방법 2: 마íŦë‹¤ėš´ 파ėŧė—ė„œ ėƒė„ą"
44
+ echo "confluence create-child \"Test Documentation - $(date +%Y%m%d)\" [ëļ€ëĒ¨íŽ˜ė´ė§€ID] --file ./sample-page.md --format markdown"
45
+ echo ""
46
+
47
+ # HTML ėŊ˜í…ė¸ ëĄœ ėƒė„ą
48
+ echo "📄 방법 3: HTML ėŊ˜í…ė¸ ëĄœ ėƒė„ą"
49
+ echo 'confluence create-child "Test HTML Page" [ëļ€ëĒ¨íŽ˜ė´ė§€ID] --content "<h1>í…ŒėŠ¤íŠ¸ íŽ˜ė´ė§€</h1><p>ė´ę˛ƒė€ <strong>HTML</strong>로 ėž‘ė„ąëœ í…ŒėŠ¤íŠ¸ íŽ˜ė´ė§€ėž…ë‹ˆë‹¤.</p>" --format html'
50
+ echo ""
51
+
52
+ echo "💡 ė‹¤ė œ ė‚ŦėšŠ 똈렜:"
53
+ echo "=============================="
54
+ echo "# 1. ëļ€ëǍ íŽ˜ė´ė§€ ID ė°žę¸°"
55
+ echo 'PARENT_ID=$(confluence find "Project Documentation" --space MYTEAM | grep "ID:" | cut -d" " -f2)'
56
+ echo ""
57
+ echo "# 2. í…ŒėŠ¤íŠ¸ íŽ˜ė´ė§€ ėƒė„ą"
58
+ echo 'confluence create-child "í…ŒėŠ¤íŠ¸ íŽ˜ė´ė§€ - $(date +%Y%m%d_%H%M)" $PARENT_ID --content "CLI í…ŒėŠ¤íŠ¸ėšŠ íŽ˜ė´ė§€ėž…ë‹ˆë‹¤."'
59
+ echo ""
60
+
61
+ echo "âš ī¸ ėŖŧė˜ė‚Ŧ항:"
62
+ echo "- confluence CLI가 ė˜Ŧ바ëĨ´ę˛Œ ė„¤ė •ë˜ė–´ ėžˆė–´ė•ŧ 합니다 (confluence init)"
63
+ echo "- 해당 Confluence ė¸ėŠ¤í„´ėŠ¤ė— 대한 ė ė ˆí•œ ęļŒí•œė´ ėžˆė–´ė•ŧ 합니다"
64
+ echo "- íŽ˜ė´ė§€ ėƒė„ą ęļŒí•œė´ ėžˆëŠ”ė§€ í™•ė¸í•˜ė„¸ėš”"
65
+ echo "- í…ŒėŠ¤íŠ¸ í›„ė—ëŠ” ëļˆí•„ėš”í•œ íŽ˜ė´ė§€ëĨŧ ė •ëĻŦí•˜ė„¸ėš”"
@@ -0,0 +1,68 @@
1
+ #!/bin/bash
2
+
3
+ # Confluence CLI Demo Script
4
+ # This script demonstrates the new page creation and update features
5
+
6
+ echo "🚀 Confluence CLI - Page Creation Demo"
7
+ echo "======================================"
8
+
9
+ # Note: Replace with your actual space key and ensure you have confluence-cli configured
10
+ SPACE_KEY="MYTEAM" # Change this to your space key
11
+
12
+ echo ""
13
+ echo "1. Creating a page from Markdown file..."
14
+ echo "confluence create \"Sample Page from CLI\" $SPACE_KEY --file ./sample-page.md --format markdown"
15
+
16
+ echo ""
17
+ echo "2. Creating a page with inline content..."
18
+ echo "confluence create \"Quick Note\" $SPACE_KEY --content \"This is a quick note created from the CLI\" --format storage"
19
+
20
+ echo ""
21
+ echo "3. Updating a page (replace 123456789 with actual page ID)..."
22
+ echo "confluence update 123456789 --content \"This page has been updated via CLI\""
23
+
24
+ echo ""
25
+ echo "4. Getting page content for editing..."
26
+ echo "confluence edit 123456789 --output ./page-backup.xml"
27
+
28
+ echo ""
29
+ echo "5. Searching for pages..."
30
+ echo "confluence search \"CLI\""
31
+
32
+ echo ""
33
+ echo "6. Listing all spaces..."
34
+ echo "confluence spaces"
35
+
36
+ echo ""
37
+ echo "7. Finding a page by title..."
38
+ echo "confluence find \"Project Documentation\" --space MYTEAM"
39
+
40
+ echo ""
41
+ echo "8. Creating a child page under a parent page..."
42
+ echo "confluence create-child \"Test Page\" 123456789 --content \"This is a test page created as a child\""
43
+
44
+ echo ""
45
+ echo "9. Creating a child page from Markdown file..."
46
+ echo "confluence create-child \"Test Documentation\" 123456789 --file ./sample-page.md --format markdown"
47
+
48
+ echo ""
49
+ echo "💡 Tips:"
50
+ echo "- Use --format markdown to create pages from Markdown files"
51
+ echo "- Use --format html for HTML content"
52
+ echo "- Use --format storage for Confluence native format"
53
+ echo "- Always backup important pages before updating"
54
+ echo "- Use 'confluence find' to get page IDs by title"
55
+ echo "- Child pages inherit permissions from their parent"
56
+
57
+ echo ""
58
+ echo "📝 Edit workflow:"
59
+ echo "1. confluence edit [pageId] --output page.xml"
60
+ echo "2. Edit the file with your preferred editor"
61
+ echo "3. confluence update [pageId] --file page.xml --format storage"
62
+
63
+ echo ""
64
+ echo "🔍 Finding and creating child pages workflow:"
65
+ echo "1. confluence find \"Project Documentation\" --space MYTEAM"
66
+ echo "2. Note the page ID from the result"
67
+ echo "3. confluence create-child \"Meeting Notes\" [parentId] --content \"Child content\""
68
+ echo "4. Or use: confluence create-child \"Technical Docs\" [parentId] --file ./content.md --format markdown"
@@ -0,0 +1,30 @@
1
+ # Welcome to Confluence CLI
2
+
3
+ This is a sample page created using the **Confluence CLI** tool.
4
+
5
+ ## Features
6
+
7
+ - Easy page creation from Markdown files
8
+ - Support for multiple content formats
9
+ - Command-line interface for automation
10
+
11
+ ## Code Example
12
+
13
+ ```javascript
14
+ const client = new ConfluenceClient(config);
15
+ await client.createPage('My Page', 'SPACE', content, 'markdown');
16
+ ```
17
+
18
+ ## Lists
19
+
20
+ 1. First item
21
+ 2. Second item
22
+ 3. Third item
23
+
24
+ - Bullet point one
25
+ - Bullet point two
26
+ - Bullet point three
27
+
28
+ ---
29
+
30
+ *Created with â¤ī¸ using confluence-cli*
@@ -1,11 +1,13 @@
1
1
  const axios = require('axios');
2
2
  const { convert } = require('html-to-text');
3
+ const MarkdownIt = require('markdown-it');
3
4
 
4
5
  class ConfluenceClient {
5
6
  constructor(config) {
6
7
  this.baseURL = `https://${config.domain}/rest/api`;
7
8
  this.token = config.token;
8
9
  this.domain = config.domain;
10
+ this.markdown = new MarkdownIt();
9
11
 
10
12
  this.client = axios.create({
11
13
  baseURL: this.baseURL,
@@ -132,6 +134,187 @@ class ConfluenceClient {
132
134
  type: space.type
133
135
  }));
134
136
  }
137
+
138
+ /**
139
+ * Convert markdown to Confluence storage format
140
+ */
141
+ markdownToStorage(markdown) {
142
+ // Convert markdown to HTML first
143
+ const html = this.markdown.render(markdown);
144
+
145
+ // Basic conversion to Confluence storage format
146
+ // This is a simplified version - Confluence has a specific storage format
147
+ return `<ac:structured-macro ac:name="html">
148
+ <ac:parameter ac:name="atlassian-macro-output-type">BLOCK</ac:parameter>
149
+ <ac:plain-text-body><![CDATA[${html}]]></ac:plain-text-body>
150
+ </ac:structured-macro>`;
151
+ }
152
+
153
+ /**
154
+ * Create a new Confluence page
155
+ */
156
+ async createPage(title, spaceKey, content, format = 'storage') {
157
+ let storageContent = content;
158
+
159
+ if (format === 'markdown') {
160
+ storageContent = this.markdownToStorage(content);
161
+ } else if (format === 'html') {
162
+ // Wrap HTML in storage format
163
+ storageContent = `<ac:structured-macro ac:name="html">
164
+ <ac:parameter ac:name="atlassian-macro-output-type">BLOCK</ac:parameter>
165
+ <ac:plain-text-body><![CDATA[${content}]]></ac:plain-text-body>
166
+ </ac:structured-macro>`;
167
+ }
168
+
169
+ const pageData = {
170
+ type: 'page',
171
+ title: title,
172
+ space: {
173
+ key: spaceKey
174
+ },
175
+ body: {
176
+ storage: {
177
+ value: storageContent,
178
+ representation: 'storage'
179
+ }
180
+ }
181
+ };
182
+
183
+ const response = await this.client.post('/content', pageData);
184
+ return response.data;
185
+ }
186
+
187
+ /**
188
+ * Create a new Confluence page as a child of another page
189
+ */
190
+ async createChildPage(title, spaceKey, parentId, content, format = 'storage') {
191
+ let storageContent = content;
192
+
193
+ if (format === 'markdown') {
194
+ storageContent = this.markdownToStorage(content);
195
+ } else if (format === 'html') {
196
+ // Wrap HTML in storage format
197
+ storageContent = `<ac:structured-macro ac:name="html">
198
+ <ac:parameter ac:name="atlassian-macro-output-type">BLOCK</ac:parameter>
199
+ <ac:plain-text-body><![CDATA[${content}]]></ac:plain-text-body>
200
+ </ac:structured-macro>`;
201
+ }
202
+
203
+ const pageData = {
204
+ type: 'page',
205
+ title: title,
206
+ space: {
207
+ key: spaceKey
208
+ },
209
+ ancestors: [
210
+ {
211
+ id: parentId
212
+ }
213
+ ],
214
+ body: {
215
+ storage: {
216
+ value: storageContent,
217
+ representation: 'storage'
218
+ }
219
+ }
220
+ };
221
+
222
+ const response = await this.client.post('/content', pageData);
223
+ return response.data;
224
+ }
225
+
226
+ /**
227
+ * Update an existing Confluence page
228
+ */
229
+ async updatePage(pageId, title, content, format = 'storage') {
230
+ // First, get the current page to get the version number
231
+ const currentPage = await this.client.get(`/content/${pageId}`);
232
+ const currentVersion = currentPage.data.version.number;
233
+
234
+ let storageContent = content;
235
+
236
+ if (format === 'markdown') {
237
+ storageContent = this.markdownToStorage(content);
238
+ } else if (format === 'html') {
239
+ // Wrap HTML in storage format
240
+ storageContent = `<ac:structured-macro ac:name="html">
241
+ <ac:parameter ac:name="atlassian-macro-output-type">BLOCK</ac:parameter>
242
+ <ac:plain-text-body><![CDATA[${content}]]></ac:plain-text-body>
243
+ </ac:structured-macro>`;
244
+ }
245
+
246
+ const pageData = {
247
+ id: pageId,
248
+ type: 'page',
249
+ title: title || currentPage.data.title,
250
+ space: currentPage.data.space,
251
+ body: {
252
+ storage: {
253
+ value: storageContent,
254
+ representation: 'storage'
255
+ }
256
+ },
257
+ version: {
258
+ number: currentVersion + 1
259
+ }
260
+ };
261
+
262
+ const response = await this.client.put(`/content/${pageId}`, pageData);
263
+ return response.data;
264
+ }
265
+
266
+ /**
267
+ * Get page content for editing
268
+ */
269
+ async getPageForEdit(pageIdOrUrl) {
270
+ const pageId = this.extractPageId(pageIdOrUrl);
271
+
272
+ const response = await this.client.get(`/content/${pageId}`, {
273
+ params: {
274
+ expand: 'body.storage,version,space'
275
+ }
276
+ });
277
+
278
+ return {
279
+ id: response.data.id,
280
+ title: response.data.title,
281
+ content: response.data.body.storage.value,
282
+ version: response.data.version.number,
283
+ space: response.data.space
284
+ };
285
+ }
286
+
287
+ /**
288
+ * Search for a page by title and space
289
+ */
290
+ async findPageByTitle(title, spaceKey = null) {
291
+ let cql = `title = "${title}"`;
292
+ if (spaceKey) {
293
+ cql += ` AND space = "${spaceKey}"`;
294
+ }
295
+
296
+ const response = await this.client.get('/search', {
297
+ params: {
298
+ cql: cql,
299
+ limit: 1,
300
+ expand: 'content.space'
301
+ }
302
+ });
303
+
304
+ if (response.data.results.length === 0) {
305
+ throw new Error(`Page not found: "${title}"`);
306
+ }
307
+
308
+ const result = response.data.results[0];
309
+ const content = result.content || result;
310
+
311
+ return {
312
+ id: content.id,
313
+ title: content.title,
314
+ space: content.space || { key: spaceKey || 'Unknown', name: 'Unknown' },
315
+ url: content._links?.webui || ''
316
+ };
317
+ }
135
318
  }
136
319
 
137
320
  module.exports = ConfluenceClient;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "confluence-cli",
3
- "version": "1.1.0",
4
- "description": "A command-line interface for Atlassian Confluence",
3
+ "version": "1.2.0",
4
+ "description": "A command-line interface for Atlassian Confluence with page creation and editing capabilities",
5
5
  "main": "index.js",
6
6
  "bin": {
7
7
  "confluence": "bin/index.js"
@@ -22,17 +22,18 @@
22
22
  "author": "Your Name",
23
23
  "license": "MIT",
24
24
  "dependencies": {
25
- "commander": "^11.1.0",
26
25
  "axios": "^1.6.2",
27
26
  "chalk": "^4.1.2",
27
+ "commander": "^11.1.0",
28
+ "html-to-text": "^9.0.5",
28
29
  "inquirer": "^8.2.6",
29
- "ora": "^5.4.1",
30
- "html-to-text": "^9.0.5"
30
+ "markdown-it": "^14.1.0",
31
+ "ora": "^5.4.1"
31
32
  },
32
33
  "devDependencies": {
34
+ "@types/node": "^20.10.0",
33
35
  "eslint": "^8.55.0",
34
- "jest": "^29.7.0",
35
- "@types/node": "^20.10.0"
36
+ "jest": "^29.7.0"
36
37
  },
37
38
  "engines": {
38
39
  "node": ">=14.0.0"
@@ -26,4 +26,25 @@ describe('ConfluenceClient', () => {
26
26
  expect(() => client.extractPageId(displayUrl)).toThrow('Display URLs not yet supported');
27
27
  });
28
28
  });
29
+
30
+ describe('markdownToStorage', () => {
31
+ test('should convert markdown to Confluence storage format', () => {
32
+ const markdown = '# Hello World\n\nThis is a **test** page.';
33
+ const result = client.markdownToStorage(markdown);
34
+
35
+ expect(result).toContain('<ac:structured-macro ac:name="html">');
36
+ expect(result).toContain('<h1>Hello World</h1>');
37
+ expect(result).toContain('<strong>test</strong>');
38
+ });
39
+ });
40
+
41
+ describe('page creation and updates', () => {
42
+ test('should have required methods for page management', () => {
43
+ expect(typeof client.createPage).toBe('function');
44
+ expect(typeof client.updatePage).toBe('function');
45
+ expect(typeof client.getPageForEdit).toBe('function');
46
+ expect(typeof client.createChildPage).toBe('function');
47
+ expect(typeof client.findPageByTitle).toBe('function');
48
+ });
49
+ });
29
50
  });