confluence-cli 1.1.0 â 1.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/.github/workflows/ci.yml +0 -1
- package/CHANGELOG.md +56 -0
- package/README.md +87 -1
- package/bin/confluence.js +198 -0
- package/examples/create-child-page-example.sh +65 -0
- package/examples/demo-page-management.sh +68 -0
- package/examples/sample-page.md +30 -0
- package/lib/confluence-client.js +206 -0
- package/package.json +8 -7
- package/tests/confluence-client.test.js +21 -0
package/.github/workflows/ci.yml
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,59 @@
|
|
|
1
|
+
# [1.3.0](https://github.com/pchuri/confluence-cli/compare/v1.2.0...v1.3.0) (2025-06-27)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* improve format handling based on production testing ([820f9cd](https://github.com/pchuri/confluence-cli/commit/820f9cdc7e59b6aa4b676eda6cff7e22865ec8fb))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* implement page creation and update capabilities ([3c43b19](https://github.com/pchuri/confluence-cli/commit/3c43b19765f94318d01fea3a22b324ada00a77d1))
|
|
12
|
+
|
|
13
|
+
# [1.2.1](https://github.com/pchuri/confluence-cli/compare/v1.2.0...v1.2.1) (2025-06-27)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* **format handling**: improve compatibility across Confluence instances
|
|
19
|
+
- Switch from 'html' macro to 'markdown' macro for better compatibility
|
|
20
|
+
- Change HTML processing to direct Storage format (no macro wrapper)
|
|
21
|
+
- Add markdownToNativeStorage method for alternative conversion
|
|
22
|
+
- Fix issues discovered during production testing in real Confluence environments
|
|
23
|
+
|
|
24
|
+
# [1.2.0](https://github.com/pchuri/confluence-cli/compare/v1.1.0...v1.2.0) (2025-06-27)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Features
|
|
28
|
+
|
|
29
|
+
* implement page creation and update capabilities ([#2](https://github.com/pchuri/confluence-cli/issues/2)) ([b814ddf](https://github.com/pchuri/confluence-cli/commit/b814ddfd056aeac83cc7eb5d8d6db47ba9c70cdf))
|
|
30
|
+
|
|
31
|
+
# [1.2.0](https://github.com/pchuri/confluence-cli/compare/v1.1.0...v1.2.0) (2025-06-27)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
### Features
|
|
35
|
+
|
|
36
|
+
* **page management**: add page creation and update capabilities ([NEW])
|
|
37
|
+
- `confluence create` - Create new pages with support for Markdown, HTML, and Storage formats
|
|
38
|
+
- `confluence update` - Update existing page content and titles
|
|
39
|
+
- `confluence edit` - Export page content for editing workflow
|
|
40
|
+
- Support for reading content from files or inline
|
|
41
|
+
- Markdown to Confluence Storage format conversion
|
|
42
|
+
* **content formats**: support multiple input formats
|
|
43
|
+
<<<<<<< HEAD
|
|
44
|
+
- Markdown format with automatic conversion using `markdown` macro
|
|
45
|
+
- HTML format with direct Storage format integration
|
|
46
|
+
=======
|
|
47
|
+
- Markdown format with automatic conversion
|
|
48
|
+
- HTML format with Storage format wrapping
|
|
49
|
+
>>>>>>> origin/main
|
|
50
|
+
- Native Confluence Storage format
|
|
51
|
+
* **examples**: add sample files and demo scripts for new features
|
|
52
|
+
|
|
53
|
+
### Breaking Changes
|
|
54
|
+
|
|
55
|
+
* None - all new features are additive
|
|
56
|
+
|
|
1
57
|
# [1.1.0](https://github.com/pchuri/confluence-cli/compare/v1.0.0...v1.1.0) (2025-06-26)
|
|
2
58
|
|
|
3
59
|
|
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
|
-
- [
|
|
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*
|
package/lib/confluence-client.js
CHANGED
|
@@ -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,210 @@ 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
|
+
// Use Confluence's markdown macro instead of HTML
|
|
143
|
+
return `<ac:structured-macro ac:name="markdown">
|
|
144
|
+
<ac:parameter ac:name="atlassian-macro-output-type">BLOCK</ac:parameter>
|
|
145
|
+
<ac:plain-text-body><![CDATA[${markdown}]]></ac:plain-text-body>
|
|
146
|
+
</ac:structured-macro>`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Convert markdown to Confluence storage format using native storage format
|
|
151
|
+
*/
|
|
152
|
+
markdownToNativeStorage(markdown) {
|
|
153
|
+
// Convert markdown to HTML first
|
|
154
|
+
const html = this.markdown.render(markdown);
|
|
155
|
+
|
|
156
|
+
// Simple HTML to Storage format conversion
|
|
157
|
+
// This is a basic implementation - for full support, we'd need a more sophisticated converter
|
|
158
|
+
let storage = html
|
|
159
|
+
.replace(/<h1>/g, '<h1>')
|
|
160
|
+
.replace(/<\/h1>/g, '</h1>')
|
|
161
|
+
.replace(/<h2>/g, '<h2>')
|
|
162
|
+
.replace(/<\/h2>/g, '</h2>')
|
|
163
|
+
.replace(/<h3>/g, '<h3>')
|
|
164
|
+
.replace(/<\/h3>/g, '</h3>')
|
|
165
|
+
.replace(/<p>/g, '<p>')
|
|
166
|
+
.replace(/<\/p>/g, '</p>')
|
|
167
|
+
.replace(/<strong>/g, '<strong>')
|
|
168
|
+
.replace(/<\/strong>/g, '</strong>')
|
|
169
|
+
.replace(/<em>/g, '<em>')
|
|
170
|
+
.replace(/<\/em>/g, '</em>')
|
|
171
|
+
.replace(/<ul>/g, '<ul>')
|
|
172
|
+
.replace(/<\/ul>/g, '</ul>')
|
|
173
|
+
.replace(/<ol>/g, '<ol>')
|
|
174
|
+
.replace(/<\/ol>/g, '</ol>')
|
|
175
|
+
.replace(/<li>/g, '<li>')
|
|
176
|
+
.replace(/<\/li>/g, '</li>')
|
|
177
|
+
.replace(/<code>/g, '<code>')
|
|
178
|
+
.replace(/<\/code>/g, '</code>')
|
|
179
|
+
.replace(/<pre><code>/g, '<ac:structured-macro ac:name="code"><ac:plain-text-body><![CDATA[')
|
|
180
|
+
.replace(/<\/code><\/pre>/g, ']]></ac:plain-text-body></ac:structured-macro>');
|
|
181
|
+
|
|
182
|
+
return storage;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Create a new Confluence page
|
|
187
|
+
*/
|
|
188
|
+
async createPage(title, spaceKey, content, format = 'storage') {
|
|
189
|
+
let storageContent = content;
|
|
190
|
+
|
|
191
|
+
if (format === 'markdown') {
|
|
192
|
+
storageContent = this.markdownToStorage(content);
|
|
193
|
+
} else if (format === 'html') {
|
|
194
|
+
// Convert HTML directly to storage format (no macro wrapper)
|
|
195
|
+
storageContent = content;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const pageData = {
|
|
199
|
+
type: 'page',
|
|
200
|
+
title: title,
|
|
201
|
+
space: {
|
|
202
|
+
key: spaceKey
|
|
203
|
+
},
|
|
204
|
+
body: {
|
|
205
|
+
storage: {
|
|
206
|
+
value: storageContent,
|
|
207
|
+
representation: 'storage'
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const response = await this.client.post('/content', pageData);
|
|
213
|
+
return response.data;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Create a new Confluence page as a child of another page
|
|
218
|
+
*/
|
|
219
|
+
async createChildPage(title, spaceKey, parentId, content, format = 'storage') {
|
|
220
|
+
let storageContent = content;
|
|
221
|
+
|
|
222
|
+
if (format === 'markdown') {
|
|
223
|
+
storageContent = this.markdownToStorage(content);
|
|
224
|
+
} else if (format === 'html') {
|
|
225
|
+
// Convert HTML directly to storage format (no macro wrapper)
|
|
226
|
+
storageContent = content;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const pageData = {
|
|
230
|
+
type: 'page',
|
|
231
|
+
title: title,
|
|
232
|
+
space: {
|
|
233
|
+
key: spaceKey
|
|
234
|
+
},
|
|
235
|
+
ancestors: [
|
|
236
|
+
{
|
|
237
|
+
id: parentId
|
|
238
|
+
}
|
|
239
|
+
],
|
|
240
|
+
body: {
|
|
241
|
+
storage: {
|
|
242
|
+
value: storageContent,
|
|
243
|
+
representation: 'storage'
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const response = await this.client.post('/content', pageData);
|
|
249
|
+
return response.data;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Update an existing Confluence page
|
|
254
|
+
*/
|
|
255
|
+
async updatePage(pageId, title, content, format = 'storage') {
|
|
256
|
+
// First, get the current page to get the version number
|
|
257
|
+
const currentPage = await this.client.get(`/content/${pageId}`);
|
|
258
|
+
const currentVersion = currentPage.data.version.number;
|
|
259
|
+
|
|
260
|
+
let storageContent = content;
|
|
261
|
+
|
|
262
|
+
if (format === 'markdown') {
|
|
263
|
+
storageContent = this.markdownToStorage(content);
|
|
264
|
+
} else if (format === 'html') {
|
|
265
|
+
// Convert HTML directly to storage format (no macro wrapper)
|
|
266
|
+
storageContent = content;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const pageData = {
|
|
270
|
+
id: pageId,
|
|
271
|
+
type: 'page',
|
|
272
|
+
title: title || currentPage.data.title,
|
|
273
|
+
space: currentPage.data.space,
|
|
274
|
+
body: {
|
|
275
|
+
storage: {
|
|
276
|
+
value: storageContent,
|
|
277
|
+
representation: 'storage'
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
version: {
|
|
281
|
+
number: currentVersion + 1
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const response = await this.client.put(`/content/${pageId}`, pageData);
|
|
286
|
+
return response.data;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Get page content for editing
|
|
291
|
+
*/
|
|
292
|
+
async getPageForEdit(pageIdOrUrl) {
|
|
293
|
+
const pageId = this.extractPageId(pageIdOrUrl);
|
|
294
|
+
|
|
295
|
+
const response = await this.client.get(`/content/${pageId}`, {
|
|
296
|
+
params: {
|
|
297
|
+
expand: 'body.storage,version,space'
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
id: response.data.id,
|
|
303
|
+
title: response.data.title,
|
|
304
|
+
content: response.data.body.storage.value,
|
|
305
|
+
version: response.data.version.number,
|
|
306
|
+
space: response.data.space
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Search for a page by title and space
|
|
312
|
+
*/
|
|
313
|
+
async findPageByTitle(title, spaceKey = null) {
|
|
314
|
+
let cql = `title = "${title}"`;
|
|
315
|
+
if (spaceKey) {
|
|
316
|
+
cql += ` AND space = "${spaceKey}"`;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const response = await this.client.get('/search', {
|
|
320
|
+
params: {
|
|
321
|
+
cql: cql,
|
|
322
|
+
limit: 1,
|
|
323
|
+
expand: 'content.space'
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
if (response.data.results.length === 0) {
|
|
328
|
+
throw new Error(`Page not found: "${title}"`);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const result = response.data.results[0];
|
|
332
|
+
const content = result.content || result;
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
id: content.id,
|
|
336
|
+
title: content.title,
|
|
337
|
+
space: content.space || { key: spaceKey || 'Unknown', name: 'Unknown' },
|
|
338
|
+
url: content._links?.webui || ''
|
|
339
|
+
};
|
|
340
|
+
}
|
|
135
341
|
}
|
|
136
342
|
|
|
137
343
|
module.exports = ConfluenceClient;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "confluence-cli",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "A command-line interface for Atlassian Confluence",
|
|
3
|
+
"version": "1.3.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
|
-
"
|
|
30
|
-
"
|
|
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="markdown">');
|
|
36
|
+
expect(result).toContain('Hello World');
|
|
37
|
+
expect(result).toContain('**test**');
|
|
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
|
});
|