confluence-cli 1.0.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.
- package/.github/ISSUE_TEMPLATE/feedback.md +37 -0
- package/.github/workflows/ci.yml +10 -1
- package/CHANGELOG.md +50 -0
- package/README.md +123 -5
- package/bin/confluence.js +226 -0
- package/docs/PROMOTION.md +63 -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/analytics.js +87 -0
- package/lib/confluence-client.js +183 -0
- package/package.json +10 -9
- package/tests/confluence-client.test.js +21 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: General Feedback
|
|
3
|
+
about: Share your thoughts, suggestions, or general feedback about confluence-cli
|
|
4
|
+
title: '[FEEDBACK] '
|
|
5
|
+
labels: feedback, enhancement
|
|
6
|
+
assignees: ''
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 📝 Your Feedback
|
|
11
|
+
|
|
12
|
+
Thank you for taking the time to share your thoughts about confluence-cli!
|
|
13
|
+
|
|
14
|
+
### What did you try to accomplish?
|
|
15
|
+
A clear description of what you were trying to do with confluence-cli.
|
|
16
|
+
|
|
17
|
+
### How was your experience?
|
|
18
|
+
Tell us about your experience using the tool:
|
|
19
|
+
- What worked well?
|
|
20
|
+
- What was confusing or difficult?
|
|
21
|
+
- What features are you missing?
|
|
22
|
+
|
|
23
|
+
### Your environment
|
|
24
|
+
- OS: [e.g. macOS, Windows, Linux]
|
|
25
|
+
- Node.js version: [e.g. 18.17.0]
|
|
26
|
+
- confluence-cli version: [e.g. 1.0.1]
|
|
27
|
+
- Confluence instance: [e.g. Cloud, Server, Data Center]
|
|
28
|
+
|
|
29
|
+
### Feature requests or improvements
|
|
30
|
+
What would make confluence-cli more useful for you?
|
|
31
|
+
|
|
32
|
+
### Additional context
|
|
33
|
+
Anything else you'd like to share about your experience with confluence-cli?
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
💡 **Tip**: You can also join our [Discussions](https://github.com/pchuri/confluence-cli/discussions) for general questions and community chat!
|
package/.github/workflows/ci.yml
CHANGED
|
@@ -38,15 +38,24 @@ jobs:
|
|
|
38
38
|
needs: [test, security]
|
|
39
39
|
runs-on: ubuntu-latest
|
|
40
40
|
if: github.ref == 'refs/heads/main'
|
|
41
|
+
permissions:
|
|
42
|
+
contents: write
|
|
43
|
+
issues: write
|
|
44
|
+
pull-requests: write
|
|
41
45
|
|
|
42
46
|
steps:
|
|
43
47
|
- uses: actions/checkout@v3
|
|
48
|
+
with:
|
|
49
|
+
fetch-depth: 0
|
|
50
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
44
51
|
|
|
45
52
|
- name: Setup Node.js
|
|
46
53
|
uses: actions/setup-node@v3
|
|
47
54
|
with:
|
|
48
55
|
node-version: '18'
|
|
49
56
|
registry-url: 'https://registry.npmjs.org'
|
|
57
|
+
env:
|
|
58
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
50
59
|
|
|
51
60
|
- run: npm ci
|
|
52
61
|
- run: npm test
|
|
@@ -55,4 +64,4 @@ jobs:
|
|
|
55
64
|
uses: cycjimmy/semantic-release-action@v3
|
|
56
65
|
env:
|
|
57
66
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
58
|
-
|
|
67
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,53 @@
|
|
|
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
|
+
|
|
29
|
+
# [1.1.0](https://github.com/pchuri/confluence-cli/compare/v1.0.0...v1.1.0) (2025-06-26)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
### Features
|
|
33
|
+
|
|
34
|
+
* add analytics tracking to spaces command ([265e8f4](https://github.com/pchuri/confluence-cli/commit/265e8f42b5ba86fb50398e8b1fcfd1d85fcc54d9))
|
|
35
|
+
* add community feedback and analytics infrastructure ([a7ff6e8](https://github.com/pchuri/confluence-cli/commit/a7ff6e87cdc92d98f3d927ee98fac9e33aedbaae))
|
|
36
|
+
|
|
37
|
+
# 1.0.0 (2025-06-26)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
### Bug Fixes
|
|
41
|
+
|
|
42
|
+
* add explicit permissions for GitHub Actions ([fa36b29](https://github.com/pchuri/confluence-cli/commit/fa36b2974b1261c144a415ced324383b35a938fb))
|
|
43
|
+
* add NODE_AUTH_TOKEN for npm authentication ([2031314](https://github.com/pchuri/confluence-cli/commit/2031314ad01fc1d9b4f9557a3d1321a046cad8f3))
|
|
44
|
+
* resolve eslint errors and npm publish warnings ([b93285e](https://github.com/pchuri/confluence-cli/commit/b93285ee098d96c8b750dbf2be5a93f28f44706c))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
### Features
|
|
48
|
+
|
|
49
|
+
* initial release of confluence-cli ([ec04e06](https://github.com/pchuri/confluence-cli/commit/ec04e06bb0c785dcff84dabcafeeb60bf9e1658f))
|
|
50
|
+
|
|
1
51
|
# Confluence CLI Changelog
|
|
2
52
|
|
|
3
53
|
All notable changes to this project will be documented in this file.
|
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
|
|
|
@@ -91,6 +177,11 @@ confluence search "search term" --limit 5
|
|
|
91
177
|
confluence spaces
|
|
92
178
|
```
|
|
93
179
|
|
|
180
|
+
### View Usage Statistics
|
|
181
|
+
```bash
|
|
182
|
+
confluence stats
|
|
183
|
+
```
|
|
184
|
+
|
|
94
185
|
## Commands
|
|
95
186
|
|
|
96
187
|
| Command | Description | Options |
|
|
@@ -100,6 +191,7 @@ confluence spaces
|
|
|
100
191
|
| `info <pageId>` | Get page information | - |
|
|
101
192
|
| `search <query>` | Search for pages | `--limit <number>` |
|
|
102
193
|
| `spaces` | List all spaces | - |
|
|
194
|
+
| `stats` | View your usage statistics | - |
|
|
103
195
|
|
|
104
196
|
## Examples
|
|
105
197
|
|
|
@@ -121,6 +213,9 @@ confluence search "API documentation" --limit 3
|
|
|
121
213
|
|
|
122
214
|
# List all spaces
|
|
123
215
|
confluence spaces
|
|
216
|
+
|
|
217
|
+
# View usage statistics
|
|
218
|
+
confluence stats
|
|
124
219
|
```
|
|
125
220
|
|
|
126
221
|
## Development
|
|
@@ -157,7 +252,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
|
157
252
|
|
|
158
253
|
## Roadmap
|
|
159
254
|
|
|
160
|
-
- [
|
|
255
|
+
- [x] **Create and update pages** ✅
|
|
161
256
|
- [ ] Page templates
|
|
162
257
|
- [ ] Bulk operations
|
|
163
258
|
- [ ] Export pages to different formats
|
|
@@ -165,13 +260,36 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
|
165
260
|
- [ ] Page attachments management
|
|
166
261
|
- [ ] Comments and reviews
|
|
167
262
|
|
|
168
|
-
## Support
|
|
263
|
+
## Support & Feedback
|
|
264
|
+
|
|
265
|
+
### 💬 We'd love to hear from you!
|
|
169
266
|
|
|
170
|
-
|
|
267
|
+
Your feedback helps make confluence-cli better for everyone. Here's how you can share your thoughts:
|
|
171
268
|
|
|
269
|
+
#### 🐛 Found a bug?
|
|
172
270
|
1. Check the [Issues](https://github.com/pchuri/confluence-cli/issues) page
|
|
173
|
-
2. Create a new
|
|
174
|
-
|
|
271
|
+
2. Create a new [bug report](https://github.com/pchuri/confluence-cli/issues/new?template=bug_report.md)
|
|
272
|
+
|
|
273
|
+
#### 💡 Have a feature idea?
|
|
274
|
+
1. Create a [feature request](https://github.com/pchuri/confluence-cli/issues/new?template=feature_request.md)
|
|
275
|
+
2. Join our [Discussions](https://github.com/pchuri/confluence-cli/discussions) to chat with the community
|
|
276
|
+
|
|
277
|
+
#### 📝 General feedback?
|
|
278
|
+
- Share your experience with a [feedback issue](https://github.com/pchuri/confluence-cli/issues/new?template=feedback.md)
|
|
279
|
+
- Rate us on [NPM](https://www.npmjs.com/package/confluence-cli)
|
|
280
|
+
- Star the repo if you find it useful! ⭐
|
|
281
|
+
|
|
282
|
+
#### 🤝 Want to contribute?
|
|
283
|
+
Check out our [Contributing Guide](CONTRIBUTING.md) - all contributions are welcome!
|
|
284
|
+
|
|
285
|
+
### 📈 Usage Analytics
|
|
286
|
+
|
|
287
|
+
To help us understand how confluence-cli is being used and improve it, we collect anonymous usage statistics. This includes:
|
|
288
|
+
- Command usage frequency (no personal data)
|
|
289
|
+
- Error patterns (to fix bugs faster)
|
|
290
|
+
- Feature adoption metrics
|
|
291
|
+
|
|
292
|
+
You can opt-out anytime by setting: `export CONFLUENCE_CLI_ANALYTICS=false`
|
|
175
293
|
|
|
176
294
|
---
|
|
177
295
|
|
package/bin/confluence.js
CHANGED
|
@@ -4,6 +4,7 @@ const { program } = require('commander');
|
|
|
4
4
|
const chalk = require('chalk');
|
|
5
5
|
const ConfluenceClient = require('../lib/confluence-client');
|
|
6
6
|
const { getConfig, initConfig } = require('../lib/config');
|
|
7
|
+
const Analytics = require('../lib/analytics');
|
|
7
8
|
|
|
8
9
|
program
|
|
9
10
|
.name('confluence')
|
|
@@ -24,12 +25,15 @@ program
|
|
|
24
25
|
.description('Read a Confluence page by ID or URL')
|
|
25
26
|
.option('-f, --format <format>', 'Output format (html, text)', 'text')
|
|
26
27
|
.action(async (pageId, options) => {
|
|
28
|
+
const analytics = new Analytics();
|
|
27
29
|
try {
|
|
28
30
|
const config = getConfig();
|
|
29
31
|
const client = new ConfluenceClient(config);
|
|
30
32
|
const content = await client.readPage(pageId, options.format);
|
|
31
33
|
console.log(content);
|
|
34
|
+
analytics.track('read', true);
|
|
32
35
|
} catch (error) {
|
|
36
|
+
analytics.track('read', false);
|
|
33
37
|
console.error(chalk.red('Error:'), error.message);
|
|
34
38
|
process.exit(1);
|
|
35
39
|
}
|
|
@@ -40,6 +44,7 @@ program
|
|
|
40
44
|
.command('info <pageId>')
|
|
41
45
|
.description('Get information about a Confluence page')
|
|
42
46
|
.action(async (pageId) => {
|
|
47
|
+
const analytics = new Analytics();
|
|
43
48
|
try {
|
|
44
49
|
const config = getConfig();
|
|
45
50
|
const client = new ConfluenceClient(config);
|
|
@@ -52,7 +57,9 @@ program
|
|
|
52
57
|
if (info.space) {
|
|
53
58
|
console.log(`Space: ${chalk.green(info.space.name)} (${info.space.key})`);
|
|
54
59
|
}
|
|
60
|
+
analytics.track('info', true);
|
|
55
61
|
} catch (error) {
|
|
62
|
+
analytics.track('info', false);
|
|
56
63
|
console.error(chalk.red('Error:'), error.message);
|
|
57
64
|
process.exit(1);
|
|
58
65
|
}
|
|
@@ -64,6 +71,7 @@ program
|
|
|
64
71
|
.description('Search for Confluence pages')
|
|
65
72
|
.option('-l, --limit <limit>', 'Limit number of results', '10')
|
|
66
73
|
.action(async (query, options) => {
|
|
74
|
+
const analytics = new Analytics();
|
|
67
75
|
try {
|
|
68
76
|
const config = getConfig();
|
|
69
77
|
const client = new ConfluenceClient(config);
|
|
@@ -71,6 +79,7 @@ program
|
|
|
71
79
|
|
|
72
80
|
if (results.length === 0) {
|
|
73
81
|
console.log(chalk.yellow('No results found.'));
|
|
82
|
+
analytics.track('search', true);
|
|
74
83
|
return;
|
|
75
84
|
}
|
|
76
85
|
|
|
@@ -81,7 +90,9 @@ program
|
|
|
81
90
|
console.log(` ${chalk.gray(result.excerpt)}`);
|
|
82
91
|
}
|
|
83
92
|
});
|
|
93
|
+
analytics.track('search', true);
|
|
84
94
|
} catch (error) {
|
|
95
|
+
analytics.track('search', false);
|
|
85
96
|
console.error(chalk.red('Error:'), error.message);
|
|
86
97
|
process.exit(1);
|
|
87
98
|
}
|
|
@@ -92,6 +103,7 @@ program
|
|
|
92
103
|
.command('spaces')
|
|
93
104
|
.description('List all Confluence spaces')
|
|
94
105
|
.action(async () => {
|
|
106
|
+
const analytics = new Analytics();
|
|
95
107
|
try {
|
|
96
108
|
const config = getConfig();
|
|
97
109
|
const client = new ConfluenceClient(config);
|
|
@@ -101,7 +113,221 @@ program
|
|
|
101
113
|
spaces.forEach(space => {
|
|
102
114
|
console.log(`${chalk.green(space.key)} - ${space.name}`);
|
|
103
115
|
});
|
|
116
|
+
analytics.track('spaces', true);
|
|
104
117
|
} catch (error) {
|
|
118
|
+
analytics.track('spaces', false);
|
|
119
|
+
console.error(chalk.red('Error:'), error.message);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Stats command
|
|
125
|
+
program
|
|
126
|
+
.command('stats')
|
|
127
|
+
.description('Show usage statistics')
|
|
128
|
+
.action(async () => {
|
|
129
|
+
try {
|
|
130
|
+
const analytics = new Analytics();
|
|
131
|
+
analytics.showStats();
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.error(chalk.red('Error:'), error.message);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
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);
|
|
105
331
|
console.error(chalk.red('Error:'), error.message);
|
|
106
332
|
process.exit(1);
|
|
107
333
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# GitHub Repository Topics
|
|
2
|
+
|
|
3
|
+
When setting up your GitHub repository, add these topics for better discoverability:
|
|
4
|
+
|
|
5
|
+
## Suggested Topics:
|
|
6
|
+
- `confluence`
|
|
7
|
+
- `atlassian`
|
|
8
|
+
- `cli`
|
|
9
|
+
- `command-line`
|
|
10
|
+
- `wiki`
|
|
11
|
+
- `documentation`
|
|
12
|
+
- `nodejs`
|
|
13
|
+
- `javascript`
|
|
14
|
+
- `api-client`
|
|
15
|
+
- `automation`
|
|
16
|
+
- `developer-tools`
|
|
17
|
+
- `productivity`
|
|
18
|
+
|
|
19
|
+
## How to add topics:
|
|
20
|
+
1. Go to https://github.com/pchuri/confluence-cli
|
|
21
|
+
2. Click the ⚙️ gear icon next to "About"
|
|
22
|
+
3. Add the topics above in the "Topics" field
|
|
23
|
+
4. Add a description: "A powerful command-line interface for Atlassian Confluence"
|
|
24
|
+
5. Add website: https://www.npmjs.com/package/confluence-cli
|
|
25
|
+
|
|
26
|
+
## Social Media Promotion Ideas:
|
|
27
|
+
|
|
28
|
+
### Twitter/X Posts:
|
|
29
|
+
```
|
|
30
|
+
🚀 Just released confluence-cli v1.0!
|
|
31
|
+
|
|
32
|
+
A powerful CLI tool for @Atlassian Confluence:
|
|
33
|
+
✅ Read pages from terminal
|
|
34
|
+
✅ Search documentation
|
|
35
|
+
✅ List spaces
|
|
36
|
+
✅ Works with environment variables
|
|
37
|
+
|
|
38
|
+
Perfect for automation & DevOps workflows!
|
|
39
|
+
|
|
40
|
+
#CLI #Confluence #DevTools #OpenSource
|
|
41
|
+
https://github.com/pchuri/confluence-cli
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### LinkedIn Post:
|
|
45
|
+
```
|
|
46
|
+
Excited to share my latest open-source project: confluence-cli! 🎉
|
|
47
|
+
|
|
48
|
+
As someone who works with Confluence daily, I built this CLI tool to streamline documentation workflows. Now you can:
|
|
49
|
+
|
|
50
|
+
• Read Confluence pages from your terminal
|
|
51
|
+
• Search across your wiki content
|
|
52
|
+
• Integrate with CI/CD pipelines
|
|
53
|
+
• Automate documentation tasks
|
|
54
|
+
|
|
55
|
+
Available on NPM and fully open-source. Would love your feedback!
|
|
56
|
+
|
|
57
|
+
#OpenSource #CLI #Confluence #DevTools #Automation
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Dev.to Article Ideas:
|
|
61
|
+
1. "Building a CLI Tool for Confluence: From Bash Functions to NPM Package"
|
|
62
|
+
2. "How to Automate Your Documentation Workflow with Confluence CLI"
|
|
63
|
+
3. "Open Source Journey: Lessons Learned Building confluence-cli"
|
|
@@ -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/analytics.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const os = require('os');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Simple anonymous usage analytics
|
|
7
|
+
* Helps understand which features are most used
|
|
8
|
+
*/
|
|
9
|
+
class Analytics {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.enabled = process.env.CONFLUENCE_CLI_ANALYTICS !== 'false';
|
|
12
|
+
this.configDir = path.join(os.homedir(), '.confluence-cli');
|
|
13
|
+
this.statsFile = path.join(this.configDir, 'stats.json');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Track command usage (anonymous)
|
|
18
|
+
*/
|
|
19
|
+
track(command, success = true) {
|
|
20
|
+
if (!this.enabled) return;
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
let stats = {};
|
|
24
|
+
|
|
25
|
+
// Read existing stats
|
|
26
|
+
if (fs.existsSync(this.statsFile)) {
|
|
27
|
+
stats = JSON.parse(fs.readFileSync(this.statsFile, 'utf8'));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Initialize stats structure
|
|
31
|
+
if (!stats.commands) stats.commands = {};
|
|
32
|
+
if (!stats.firstUsed) stats.firstUsed = new Date().toISOString();
|
|
33
|
+
stats.lastUsed = new Date().toISOString();
|
|
34
|
+
|
|
35
|
+
// Track command usage
|
|
36
|
+
const commandKey = `${command}_${success ? 'success' : 'error'}`;
|
|
37
|
+
stats.commands[commandKey] = (stats.commands[commandKey] || 0) + 1;
|
|
38
|
+
|
|
39
|
+
// Create directory if it doesn't exist
|
|
40
|
+
if (!fs.existsSync(this.configDir)) {
|
|
41
|
+
fs.mkdirSync(this.configDir, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Save stats
|
|
45
|
+
fs.writeFileSync(this.statsFile, JSON.stringify(stats, null, 2));
|
|
46
|
+
} catch (error) {
|
|
47
|
+
// Silently fail - analytics should never break the main functionality
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get usage statistics
|
|
53
|
+
*/
|
|
54
|
+
getStats() {
|
|
55
|
+
if (!fs.existsSync(this.statsFile)) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
return JSON.parse(fs.readFileSync(this.statsFile, 'utf8'));
|
|
61
|
+
} catch (error) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Show usage stats to user
|
|
68
|
+
*/
|
|
69
|
+
showStats() {
|
|
70
|
+
const stats = this.getStats();
|
|
71
|
+
if (!stats) {
|
|
72
|
+
console.log('No usage statistics available.');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
console.log('📊 Usage Statistics:');
|
|
77
|
+
console.log(`First used: ${new Date(stats.firstUsed).toLocaleDateString()}`);
|
|
78
|
+
console.log(`Last used: ${new Date(stats.lastUsed).toLocaleDateString()}`);
|
|
79
|
+
console.log('\nCommand usage:');
|
|
80
|
+
|
|
81
|
+
Object.entries(stats.commands).forEach(([command, count]) => {
|
|
82
|
+
console.log(` ${command}: ${count} times`);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = Analytics;
|
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,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,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "confluence-cli",
|
|
3
|
-
"version": "1.
|
|
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
|
-
"confluence": "
|
|
7
|
+
"confluence": "bin/index.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node bin/confluence.js",
|
|
@@ -22,24 +22,25 @@
|
|
|
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"
|
|
39
40
|
},
|
|
40
41
|
"repository": {
|
|
41
42
|
"type": "git",
|
|
42
|
-
"url": "https://github.com/pchuri/confluence-cli.git"
|
|
43
|
+
"url": "git+https://github.com/pchuri/confluence-cli.git"
|
|
43
44
|
},
|
|
44
45
|
"bugs": {
|
|
45
46
|
"url": "https://github.com/pchuri/confluence-cli/issues"
|
|
@@ -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
|
});
|