Skip to content

Commit b814ddf

Browse files
authored
feat: implement page creation and update capabilities (#2)
✨ New Features: - Add page creation with support for Markdown, HTML, and Storage formats - Add page update functionality with version management - Add child page creation for hierarchical structure - Add page search by title functionality - Add editing workflow (export/edit/import) 🔧 Technical Implementation: - Markdown to Confluence Storage format conversion using markdown-it - Multiple content format support (markdown, html, storage) - File input/output support for content management - Enhanced error handling and analytics tracking 📝 CLI Commands Added: - confluence create: Create new pages - confluence create-child: Create child pages under parent pages - confluence update: Update existing page content and titles - confluence edit: Export page content for editing - confluence find: Find pages by title with space filtering �� Examples & Documentation: - Add sample Markdown file for testing - Add comprehensive demo scripts - Update README with detailed usage examples - Add CHANGELOG entry for v1.2.0 🧪 Testing: - Add tests for new functionality - All existing tests pass - CLI help commands working correctly This completes the first major roadmap item: 'Create and update pages' ✅
1 parent 52b974a commit b814ddf

10 files changed

Lines changed: 732 additions & 12 deletions

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,24 @@
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+
* **page management**: add page creation and update capabilities ([NEW])
7+
- `confluence create` - Create new pages with support for Markdown, HTML, and Storage formats
8+
- `confluence update` - Update existing page content and titles
9+
- `confluence edit` - Export page content for editing workflow
10+
- Support for reading content from files or inline
11+
- Markdown to Confluence Storage format conversion
12+
* **content formats**: support multiple input formats
13+
- Markdown format with automatic conversion
14+
- HTML format with Storage format wrapping
15+
- Native Confluence Storage format
16+
* **examples**: add sample files and demo scripts for new features
17+
18+
### Breaking Changes
19+
20+
* None - all new features are additive
21+
122
# [1.1.0](https://github.com/pchuri/confluence-cli/compare/v1.0.0...v1.1.0) (2025-06-26)
223

324

README.md

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ A powerful command-line interface for Atlassian Confluence that allows you to re
88
- 🔍 **Search** - Find pages using Confluence's powerful search
99
- ℹ️ **Page info** - Get detailed information about pages
1010
- 🏠 **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
1114
- 🔧 **Easy setup** - Simple configuration with environment variables or interactive setup
1215

1316
## Installation
@@ -38,6 +41,16 @@ npx confluence-cli
3841
confluence search "my search term"
3942
```
4043

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+
4154
## Configuration
4255

4356
### Option 1: Interactive Setup
@@ -65,6 +78,79 @@ export CONFLUENCE_API_TOKEN="your-api-token"
6578
# Read by page ID
6679
confluence read 123456789
6780

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+
68154
# Read with HTML format
69155
confluence read 123456789 --format html
70156

@@ -166,7 +252,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
166252

167253
## Roadmap
168254

169-
- [ ] Create and update pages
255+
- [x] **Create and update pages**
170256
- [ ] Page templates
171257
- [ ] Bulk operations
172258
- [ ] Export pages to different formats

bin/confluence.js

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,202 @@ program
135135
}
136136
});
137137

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+
138336
program.parse();

0 commit comments

Comments
 (0)