confluence-cli 2.2.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/bin/confluence.js +37 -13
- package/lib/confluence-client.js +36 -30
- package/lib/macro-converter.js +89 -4
- package/lib/storage-walker.js +1 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/plugins/confluence/skills/confluence/SKILL.md +10 -7
package/README.md
CHANGED
|
@@ -694,8 +694,8 @@ confluence stats
|
|
|
694
694
|
| `spaces` | List available spaces | `--limit <number>` |
|
|
695
695
|
| `find <title>` | Find a page by its title | `--space <spaceKey>` |
|
|
696
696
|
| `children <pageId>` | List child pages of a page | `--recursive`, `--max-depth <number>`, `--format <list\|tree\|json>`, `--show-url`, `--show-id` |
|
|
697
|
-
| `create <title> <spaceKey>` | Create a new page | `--content <string>`, `--file <path>`, `--format <storage\|html\|markdown
|
|
698
|
-
| `create-child <title> <parentId>` | Create a child page | `--content <string>`, `--file <path>`, `--format <storage\|html\|markdown>` |
|
|
697
|
+
| `create <title> <spaceKey>` | Create a new page or folder | `--content <string>`, `--file <path>`, `--format <storage\|html\|markdown>`, `--type <page\|folder>` |
|
|
698
|
+
| `create-child <title> <parentId>` | Create a child page or folder | `--content <string>`, `--file <path>`, `--format <storage\|html\|markdown>`, `--type <page\|folder>` |
|
|
699
699
|
| `copy-tree <sourcePageId> <targetParentId> [newTitle]` | Copy page tree with all children | `--max-depth <number>`, `--exclude <patterns>`, `--delay-ms <ms>`, `--copy-suffix <text>`, `--dry-run`, `--fail-on-error`, `--quiet` |
|
|
700
700
|
| `update <pageId>` | Update a page's title or content | `--title <string>`, `--content <string>`, `--file <path>`, `--format <storage\|html\|markdown>` |
|
|
701
701
|
| `move <pageId_or_url> <newParentId_or_url>` | Move a page to a new parent location | `--title <string>` |
|
|
@@ -752,6 +752,10 @@ confluence move 123456789 987654321 --title "New Title"
|
|
|
752
752
|
confluence attachment-upload 123456789 --file ./report.pdf
|
|
753
753
|
confluence attachment-delete 123456789 998877 --yes
|
|
754
754
|
|
|
755
|
+
# Create a folder (no content body required)
|
|
756
|
+
confluence create "Engineering Docs" MYSPACE --type folder
|
|
757
|
+
confluence create-child "Sub-folder" 123456789 --type folder
|
|
758
|
+
|
|
755
759
|
# View usage statistics
|
|
756
760
|
confluence stats
|
|
757
761
|
|
package/bin/confluence.js
CHANGED
|
@@ -22,6 +22,20 @@ function assertNonEmpty(value, label) {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
const VALID_TYPES = ['page', 'folder'];
|
|
26
|
+
|
|
27
|
+
function assertValidType(type) {
|
|
28
|
+
if (!VALID_TYPES.includes(type)) {
|
|
29
|
+
throw new Error(`Invalid type "${type}". Valid: ${VALID_TYPES.join(', ')}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function assertNoBodyForFolder(type, options) {
|
|
34
|
+
if (type === 'folder' && (options.file || options.content)) {
|
|
35
|
+
throw new Error('--file/--content is not allowed with --type folder (folders have no body).');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
25
39
|
function handleCommandError(analytics, commandName, error) {
|
|
26
40
|
analytics.track(commandName, false);
|
|
27
41
|
console.error(chalk.red('Error:'), error.message);
|
|
@@ -219,15 +233,18 @@ program
|
|
|
219
233
|
// Create command
|
|
220
234
|
program
|
|
221
235
|
.command('create <title> <spaceKey>')
|
|
222
|
-
.description('Create a new Confluence page')
|
|
236
|
+
.description('Create a new Confluence page or folder')
|
|
223
237
|
.option('-f, --file <file>', 'Read content from file')
|
|
224
238
|
.option('-c, --content <content>', 'Page content as string')
|
|
225
239
|
.option('--format <format>', 'Content format (storage, html, markdown)', 'storage')
|
|
240
|
+
.option('--type <type>', 'Content type (page, folder)', 'page')
|
|
226
241
|
.action(async (title, spaceKey, options) => {
|
|
227
242
|
const analytics = new Analytics();
|
|
228
243
|
try {
|
|
229
244
|
assertNonEmpty(title, 'title');
|
|
230
245
|
assertNonEmpty(spaceKey, 'spaceKey');
|
|
246
|
+
assertValidType(options.type);
|
|
247
|
+
assertNoBodyForFolder(options.type, options);
|
|
231
248
|
|
|
232
249
|
const config = getConfig(getProfileName());
|
|
233
250
|
assertWritable(config);
|
|
@@ -243,13 +260,14 @@ program
|
|
|
243
260
|
content = fs.readFileSync(options.file, 'utf8');
|
|
244
261
|
} else if (options.content) {
|
|
245
262
|
content = options.content;
|
|
246
|
-
} else {
|
|
263
|
+
} else if (options.type !== 'folder') {
|
|
247
264
|
throw new Error('Either --file or --content option is required');
|
|
248
265
|
}
|
|
249
266
|
|
|
250
|
-
const result = await client.createPage(title, spaceKey, content, options.format);
|
|
251
|
-
|
|
252
|
-
|
|
267
|
+
const result = await client.createPage(title, spaceKey, content, options.format, options.type);
|
|
268
|
+
|
|
269
|
+
const label = options.type === 'folder' ? 'Folder' : 'Page';
|
|
270
|
+
console.log(chalk.green(`✅ ${label} created successfully!`));
|
|
253
271
|
console.log(`Title: ${chalk.blue(result.title)}`);
|
|
254
272
|
console.log(`ID: ${chalk.blue(result.id)}`);
|
|
255
273
|
console.log(`Space: ${chalk.blue(result.space.name)} (${result.space.key})`);
|
|
@@ -264,15 +282,18 @@ program
|
|
|
264
282
|
// Create child page command
|
|
265
283
|
program
|
|
266
284
|
.command('create-child <title> <parentId>')
|
|
267
|
-
.description('Create a new Confluence page as a child of another page')
|
|
285
|
+
.description('Create a new Confluence page or folder as a child of another page')
|
|
268
286
|
.option('-f, --file <file>', 'Read content from file')
|
|
269
287
|
.option('-c, --content <content>', 'Page content as string')
|
|
270
288
|
.option('--format <format>', 'Content format (storage, html, markdown)', 'storage')
|
|
289
|
+
.option('--type <type>', 'Content type (page, folder)', 'page')
|
|
271
290
|
.action(async (title, parentId, options) => {
|
|
272
291
|
const analytics = new Analytics();
|
|
273
292
|
try {
|
|
274
293
|
assertNonEmpty(title, 'title');
|
|
275
294
|
assertNonEmpty(parentId, 'parentId');
|
|
295
|
+
assertValidType(options.type);
|
|
296
|
+
assertNoBodyForFolder(options.type, options);
|
|
276
297
|
|
|
277
298
|
const config = getConfig(getProfileName());
|
|
278
299
|
assertWritable(config);
|
|
@@ -281,9 +302,9 @@ program
|
|
|
281
302
|
// Get parent page info to get space key
|
|
282
303
|
const parentInfo = await client.getPageInfo(parentId);
|
|
283
304
|
const spaceKey = parentInfo.space.key;
|
|
284
|
-
|
|
305
|
+
|
|
285
306
|
let content = '';
|
|
286
|
-
|
|
307
|
+
|
|
287
308
|
if (options.file) {
|
|
288
309
|
const fs = require('fs');
|
|
289
310
|
if (!fs.existsSync(options.file)) {
|
|
@@ -292,13 +313,14 @@ program
|
|
|
292
313
|
content = fs.readFileSync(options.file, 'utf8');
|
|
293
314
|
} else if (options.content) {
|
|
294
315
|
content = options.content;
|
|
295
|
-
} else {
|
|
316
|
+
} else if (options.type !== 'folder') {
|
|
296
317
|
throw new Error('Either --file or --content option is required');
|
|
297
318
|
}
|
|
298
|
-
|
|
299
|
-
const result = await client.createChildPage(title, spaceKey, parentId, content, options.format);
|
|
300
|
-
|
|
301
|
-
|
|
319
|
+
|
|
320
|
+
const result = await client.createChildPage(title, spaceKey, parentId, content, options.format, options.type);
|
|
321
|
+
|
|
322
|
+
const label = options.type === 'folder' ? 'Folder' : 'Child page';
|
|
323
|
+
console.log(chalk.green(`✅ ${label} created successfully!`));
|
|
302
324
|
console.log(`Title: ${chalk.blue(result.title)}`);
|
|
303
325
|
console.log(`ID: ${chalk.blue(result.id)}`);
|
|
304
326
|
console.log(`Parent: ${chalk.blue(parentInfo.title)} (${parentId})`);
|
|
@@ -2084,6 +2106,8 @@ module.exports = {
|
|
|
2084
2106
|
sanitizeFilename,
|
|
2085
2107
|
assertWritable,
|
|
2086
2108
|
assertNonEmpty,
|
|
2109
|
+
assertValidType,
|
|
2110
|
+
assertNoBodyForFolder,
|
|
2087
2111
|
handleCommandError,
|
|
2088
2112
|
},
|
|
2089
2113
|
};
|
package/lib/confluence-client.js
CHANGED
|
@@ -1207,29 +1207,32 @@ class ConfluenceClient {
|
|
|
1207
1207
|
/**
|
|
1208
1208
|
* Create a new Confluence page
|
|
1209
1209
|
*/
|
|
1210
|
-
async createPage(title, spaceKey, content, format = 'storage') {
|
|
1211
|
-
let storageContent = content;
|
|
1212
|
-
|
|
1213
|
-
if (format === 'markdown') {
|
|
1214
|
-
storageContent = this.markdownToStorage(content);
|
|
1215
|
-
} else if (format === 'html') {
|
|
1216
|
-
// Convert HTML directly to storage format (no macro wrapper)
|
|
1217
|
-
storageContent = content;
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1210
|
+
async createPage(title, spaceKey, content, format = 'storage', type = 'page') {
|
|
1220
1211
|
const pageData = {
|
|
1221
|
-
type:
|
|
1212
|
+
type: type,
|
|
1222
1213
|
title: title,
|
|
1223
1214
|
space: {
|
|
1224
1215
|
key: spaceKey
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1216
|
+
}
|
|
1217
|
+
};
|
|
1218
|
+
|
|
1219
|
+
if (type !== 'folder') {
|
|
1220
|
+
let storageContent = content;
|
|
1221
|
+
|
|
1222
|
+
if (format === 'markdown') {
|
|
1223
|
+
storageContent = this.markdownToStorage(content);
|
|
1224
|
+
} else if (format === 'html') {
|
|
1225
|
+
// Convert HTML directly to storage format (no macro wrapper)
|
|
1226
|
+
storageContent = content;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
pageData.body = {
|
|
1227
1230
|
storage: {
|
|
1228
1231
|
value: storageContent,
|
|
1229
1232
|
representation: 'storage'
|
|
1230
1233
|
}
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1233
1236
|
|
|
1234
1237
|
const response = await this.client.post('/content', pageData);
|
|
1235
1238
|
return response.data;
|
|
@@ -1238,18 +1241,9 @@ class ConfluenceClient {
|
|
|
1238
1241
|
/**
|
|
1239
1242
|
* Create a new Confluence page as a child of another page
|
|
1240
1243
|
*/
|
|
1241
|
-
async createChildPage(title, spaceKey, parentId, content, format = 'storage') {
|
|
1242
|
-
let storageContent = content;
|
|
1243
|
-
|
|
1244
|
-
if (format === 'markdown') {
|
|
1245
|
-
storageContent = this.markdownToStorage(content);
|
|
1246
|
-
} else if (format === 'html') {
|
|
1247
|
-
// Convert HTML directly to storage format (no macro wrapper)
|
|
1248
|
-
storageContent = content;
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1244
|
+
async createChildPage(title, spaceKey, parentId, content, format = 'storage', type = 'page') {
|
|
1251
1245
|
const pageData = {
|
|
1252
|
-
type:
|
|
1246
|
+
type: type,
|
|
1253
1247
|
title: title,
|
|
1254
1248
|
space: {
|
|
1255
1249
|
key: spaceKey
|
|
@@ -1258,14 +1252,26 @@ class ConfluenceClient {
|
|
|
1258
1252
|
{
|
|
1259
1253
|
id: parentId
|
|
1260
1254
|
}
|
|
1261
|
-
]
|
|
1262
|
-
|
|
1255
|
+
]
|
|
1256
|
+
};
|
|
1257
|
+
|
|
1258
|
+
if (type !== 'folder') {
|
|
1259
|
+
let storageContent = content;
|
|
1260
|
+
|
|
1261
|
+
if (format === 'markdown') {
|
|
1262
|
+
storageContent = this.markdownToStorage(content);
|
|
1263
|
+
} else if (format === 'html') {
|
|
1264
|
+
// Convert HTML directly to storage format (no macro wrapper)
|
|
1265
|
+
storageContent = content;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
pageData.body = {
|
|
1263
1269
|
storage: {
|
|
1264
1270
|
value: storageContent,
|
|
1265
1271
|
representation: 'storage'
|
|
1266
1272
|
}
|
|
1267
|
-
}
|
|
1268
|
-
}
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1269
1275
|
|
|
1270
1276
|
const response = await this.client.post('/content', pageData);
|
|
1271
1277
|
return response.data;
|
package/lib/macro-converter.js
CHANGED
|
@@ -9,6 +9,25 @@ const CALLOUT_MARKERS = ['info', 'warning', 'note'];
|
|
|
9
9
|
// and survives editor / formatter / lint passes that strip invisible chars.
|
|
10
10
|
const STASH_DELIM = '\uE000';
|
|
11
11
|
|
|
12
|
+
// Inline HTML tags that markdown has no native syntax for. The walker emits
|
|
13
|
+
// them as raw HTML on storage\u2192markdown, so they must round-trip back through
|
|
14
|
+
// markdownToStorage; without this whitelist, MarkdownIt (html: false) escapes
|
|
15
|
+
// them to literal `<...>` and the formatting is lost.
|
|
16
|
+
//
|
|
17
|
+
// The lookahead `(?=[\s/>])` after the tag name rejects strings that look
|
|
18
|
+
// like markdown autolinks (e.g. `<u@example.com>`, `<sub:foo>`) \u2014 a plain
|
|
19
|
+
// `\b` word boundary would let these through and break linkify.
|
|
20
|
+
//
|
|
21
|
+
// The body alternation `"[^"]*"|'[^']*'|[^>]` makes the match quote-aware
|
|
22
|
+
// so a literal `>` inside a quoted attribute value (e.g.
|
|
23
|
+
// `<mark title="1>0">`) does not terminate the tag prematurely.
|
|
24
|
+
const PASSTHROUGH_TAG_RE = /<\/?(?:u|sub|sup|mark)(?=[\s/>])(?:"[^"]*"|'[^']*'|[^>])*>/gi;
|
|
25
|
+
// Single-backtick inline code spans. Block-level code (fenced + indented) is
|
|
26
|
+
// detected via MarkdownIt's tokenizer in `_findCodeRanges` because a regex
|
|
27
|
+
// can't reliably distinguish a 4-space-indented code block from a list-item
|
|
28
|
+
// continuation that happens to align to four spaces.
|
|
29
|
+
const INLINE_CODE_RE = /`[^`\n]+`/g;
|
|
30
|
+
|
|
12
31
|
class MacroConverter {
|
|
13
32
|
constructor({ isCloud = false, webUrlPrefix = '', buildUrl = null, linkStyle = null } = {}) {
|
|
14
33
|
this._isCloud = isCloud;
|
|
@@ -57,13 +76,79 @@ class MacroConverter {
|
|
|
57
76
|
}
|
|
58
77
|
|
|
59
78
|
markdownToStorage(markdown) {
|
|
60
|
-
|
|
61
|
-
return this.htmlToConfluenceStorage(html);
|
|
79
|
+
return this.htmlToConfluenceStorage(this._renderMarkdownToHtml(markdown));
|
|
62
80
|
}
|
|
63
81
|
|
|
64
82
|
markdownToNativeStorage(markdown) {
|
|
65
|
-
|
|
66
|
-
|
|
83
|
+
return this.htmlToConfluenceStorage(this._renderMarkdownToHtml(markdown));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Pre-stashes whitelisted inline HTML so MarkdownIt won't escape it, renders,
|
|
87
|
+
// then restores. Code regions (fenced, indented, and inline) are skipped so a
|
|
88
|
+
// literal `<u>` typed inside a code block survives MarkdownIt's escape and
|
|
89
|
+
// round-trips as text rather than as a real tag — which would otherwise be
|
|
90
|
+
// smuggled past the escape and dropped by `convertCodeBlock`'s text-only
|
|
91
|
+
// child collection in html-to-storage.
|
|
92
|
+
_renderMarkdownToHtml(markdown) {
|
|
93
|
+
const codeRanges = this._findCodeRanges(markdown);
|
|
94
|
+
const htmlStash = [];
|
|
95
|
+
const stashHtml = (text) => text.replace(PASSTHROUGH_TAG_RE, (m) => {
|
|
96
|
+
htmlStash.push(m);
|
|
97
|
+
return `${STASH_DELIM}H${htmlStash.length - 1}${STASH_DELIM}`;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
let src = '';
|
|
101
|
+
let pos = 0;
|
|
102
|
+
for (const [start, end] of codeRanges) {
|
|
103
|
+
src += stashHtml(markdown.slice(pos, start));
|
|
104
|
+
src += markdown.slice(start, end);
|
|
105
|
+
pos = end;
|
|
106
|
+
}
|
|
107
|
+
src += stashHtml(markdown.slice(pos));
|
|
108
|
+
|
|
109
|
+
const html = this.markdown.render(src);
|
|
110
|
+
return html.replace(
|
|
111
|
+
new RegExp(`${STASH_DELIM}H(\\d+)${STASH_DELIM}`, 'g'),
|
|
112
|
+
(m, i) => htmlStash[+i] ?? m,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Returns merged, sorted character ranges covering all code regions in the
|
|
117
|
+
// markdown source — fenced and indented blocks via MarkdownIt's tokenizer
|
|
118
|
+
// (which correctly distinguishes them from list-item continuations) and
|
|
119
|
+
// single-backtick inline spans via regex (MarkdownIt parses these into
|
|
120
|
+
// `code_inline` tokens but does not expose source positions for them).
|
|
121
|
+
_findCodeRanges(markdown) {
|
|
122
|
+
const tokens = this.markdown.parse(markdown, {});
|
|
123
|
+
const lineStarts = [0];
|
|
124
|
+
for (let i = 0; i < markdown.length; i++) {
|
|
125
|
+
if (markdown[i] === '\n') lineStarts.push(i + 1);
|
|
126
|
+
}
|
|
127
|
+
const lineToChar = (n) => (n < lineStarts.length ? lineStarts[n] : markdown.length);
|
|
128
|
+
|
|
129
|
+
const ranges = [];
|
|
130
|
+
for (const tok of tokens) {
|
|
131
|
+
if ((tok.type === 'code_block' || tok.type === 'fence') && tok.map) {
|
|
132
|
+
ranges.push([lineToChar(tok.map[0]), lineToChar(tok.map[1])]);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
INLINE_CODE_RE.lastIndex = 0;
|
|
136
|
+
let m;
|
|
137
|
+
while ((m = INLINE_CODE_RE.exec(markdown)) !== null) {
|
|
138
|
+
ranges.push([m.index, m.index + m[0].length]);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
ranges.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
|
|
142
|
+
const merged = [];
|
|
143
|
+
for (const r of ranges) {
|
|
144
|
+
const last = merged[merged.length - 1];
|
|
145
|
+
if (last && r[0] <= last[1]) {
|
|
146
|
+
last[1] = Math.max(last[1], r[1]);
|
|
147
|
+
} else {
|
|
148
|
+
merged.push([r[0], r[1]]);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return merged;
|
|
67
152
|
}
|
|
68
153
|
|
|
69
154
|
htmlToConfluenceStorage(html) {
|
package/lib/storage-walker.js
CHANGED
|
@@ -200,6 +200,7 @@ class StorageWalker {
|
|
|
200
200
|
case 'blockquote':
|
|
201
201
|
return this.handleBlockquote(node);
|
|
202
202
|
case 'details': case 'summary':
|
|
203
|
+
case 'u': case 'sub': case 'sup': case 'mark':
|
|
203
204
|
return `<${tag}>` + this.walkNodes(node.children) + `</${tag}>`;
|
|
204
205
|
case 'ac:structured-macro':
|
|
205
206
|
return this.handleMacro(node);
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "confluence-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "confluence-cli",
|
|
9
|
-
"version": "2.
|
|
9
|
+
"version": "2.3.1",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"axios": "^1.15.0",
|
package/package.json
CHANGED
|
@@ -255,10 +255,10 @@ confluence children 123456789 --recursive --format tree --show-id
|
|
|
255
255
|
|
|
256
256
|
### `create <title> <spaceKey>`
|
|
257
257
|
|
|
258
|
-
Create a new top-level page in a space.
|
|
258
|
+
Create a new top-level page or folder in a space.
|
|
259
259
|
|
|
260
260
|
```sh
|
|
261
|
-
confluence create <title> <spaceKey> [--content <string>] [--file <path>] [--format storage|html|markdown]
|
|
261
|
+
confluence create <title> <spaceKey> [--content <string>] [--file <path>] [--format storage|html|markdown] [--type page|folder]
|
|
262
262
|
```
|
|
263
263
|
|
|
264
264
|
| Option | Default | Description |
|
|
@@ -266,31 +266,34 @@ confluence create <title> <spaceKey> [--content <string>] [--file <path>] [--for
|
|
|
266
266
|
| `--content` | — | Inline content string |
|
|
267
267
|
| `--file` | — | Path to content file |
|
|
268
268
|
| `--format` | `storage` | Content format |
|
|
269
|
+
| `--type` | `page` | Content type — `page` (default) or `folder`. Folders have no body. |
|
|
269
270
|
|
|
270
|
-
Either `--content` or `--file` is required.
|
|
271
|
+
Either `--content` or `--file` is required for pages. Folders take no content — passing `--content` or `--file` with `--type folder` is rejected.
|
|
271
272
|
|
|
272
273
|
```sh
|
|
273
274
|
confluence create "Project Overview" MYSPACE --content "# Hello" --format markdown
|
|
274
275
|
confluence create "Release Notes" MYSPACE --file ./notes.md --format markdown
|
|
276
|
+
confluence create "Engineering Docs" MYSPACE --type folder
|
|
275
277
|
```
|
|
276
278
|
|
|
277
|
-
Outputs the new page ID and URL on success.
|
|
279
|
+
Outputs the new page (or folder) ID and URL on success.
|
|
278
280
|
|
|
279
281
|
---
|
|
280
282
|
|
|
281
283
|
### `create-child <title> <parentId>`
|
|
282
284
|
|
|
283
|
-
Create a child page under an existing page. Inherits the parent's space automatically.
|
|
285
|
+
Create a child page or folder under an existing page. Inherits the parent's space automatically.
|
|
284
286
|
|
|
285
287
|
```sh
|
|
286
|
-
confluence create-child <title> <parentId> [--content <string>] [--file <path>] [--format storage|html|markdown]
|
|
288
|
+
confluence create-child <title> <parentId> [--content <string>] [--file <path>] [--format storage|html|markdown] [--type page|folder]
|
|
287
289
|
```
|
|
288
290
|
|
|
289
|
-
Options are identical to `create`. Either `--content` or `--file` is required.
|
|
291
|
+
Options are identical to `create`. Either `--content` or `--file` is required for pages; folders take no content.
|
|
290
292
|
|
|
291
293
|
```sh
|
|
292
294
|
confluence create-child "Chapter 1" 123456789 --content "Content here" --format markdown
|
|
293
295
|
confluence create-child "API Guide" 123456789 --file ./api.md --format markdown
|
|
296
|
+
confluence create-child "Sub-folder" 123456789 --type folder
|
|
294
297
|
```
|
|
295
298
|
|
|
296
299
|
---
|