create-confluence-sync 1.0.2 → 1.0.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-confluence-sync",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Bidirectional Confluence Server documentation sync via Git. Edit XHTML locally, commit, auto-sync with Confluence.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/agents-md.js CHANGED
@@ -1,168 +1,97 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
 
4
- /**
5
- * Build a tree structure from flat pages map.
6
- * Returns array of root nodes, each with nested `children`.
7
- */
8
- function buildPageTree(pages) {
9
- const nodes = {};
10
- const roots = [];
11
-
12
- for (const [id, page] of Object.entries(pages)) {
13
- nodes[id] = { id, ...page, children: [] };
14
- }
15
-
16
- for (const [id, node] of Object.entries(nodes)) {
17
- if (node.parentId && nodes[node.parentId]) {
18
- nodes[node.parentId].children.push(node);
19
- } else {
20
- roots.push(node);
21
- }
22
- }
23
-
24
- // Sort children alphabetically by title at each level
25
- const sortChildren = (nodeList) => {
26
- nodeList.sort((a, b) => a.title.localeCompare(b.title));
27
- for (const node of nodeList) {
28
- sortChildren(node.children);
29
- }
30
- };
31
- sortChildren(roots);
32
-
33
- return roots;
34
- }
35
-
36
- /**
37
- * Render a tree of pages as an indented directory listing.
38
- * Only visible (non-hidden) pages are shown.
39
- */
40
- function renderTree(roots, space, indent = '') {
41
- const lines = [];
42
-
43
- for (let i = 0; i < roots.length; i++) {
44
- const node = roots[i];
45
- if (node.hidden) continue;
46
-
47
- const isLast = i === roots.length - 1;
48
- const connector = isLast ? '\u2514\u2500\u2500 ' : '\u251c\u2500\u2500 ';
49
- const childIndent = indent + (isLast ? ' ' : '\u2502 ');
50
-
51
- lines.push(`${indent}${connector}${node.title}/`);
52
- lines.push(`${childIndent}\u251c\u2500\u2500 ${node.title}.xhtml`);
53
-
54
- const visibleChildren = node.children.filter((c) => !c.hidden);
55
- const childLines = renderTree(visibleChildren, space, childIndent);
56
- if (childLines.length > 0) {
57
- lines.push(...childLines);
58
- }
59
- }
60
-
61
- return lines;
62
- }
63
-
64
- /**
65
- * Generate the full AGENTS.md content.
66
- */
67
4
  function buildContent(config, tree) {
68
5
  const space = tree.space || config.space || 'SPACE';
69
6
  const baseUrl = tree.baseUrl || config.baseUrl || config.url || '';
70
7
  const lastSync = tree.lastSync || 'never';
71
8
 
72
- const roots = buildPageTree(tree.pages || {});
73
- const visibleRoots = roots.filter((r) => !r.hidden);
74
- const treeLines = renderTree(visibleRoots, space);
75
- const treeBlock =
76
- treeLines.length > 0
77
- ? `docs/${space}/\n${treeLines.join('\n')}`
78
- : `docs/${space}/ (empty)`;
79
-
80
9
  return `# AGENTS.md
81
10
 
82
- > Auto-generated by confluence-sync. Do not edit manually.
83
- > Re-generated on every sync.
11
+ > Автоматически сгенерировано confluence-sync. Не редактировать вручную.
12
+ > Пересоздаётся при каждой синхронизации.
84
13
 
85
- ## What is this project
14
+ ## Что это за проект
86
15
 
87
- This repository is a **bidirectional documentation sync** between a local file system and **Confluence**.
88
- All interaction happens through files and git commits no direct Confluence API calls needed.
16
+ Этот репозиторий **двусторонняя синхронизация документации** между локальной файловой системой и **Confluence**.
17
+ Вся работа происходит через файлы и git-коммитыпрямые вызовы Confluence API не нужны.
89
18
 
90
- | Parameter | Value |
91
- |-----------|-------|
19
+ | Параметр | Значение |
20
+ |----------|----------|
92
21
  | Confluence URL | ${baseUrl} |
93
- | Space | ${space} |
94
- | Last synced | ${lastSync} |
22
+ | Пространство | ${space} |
23
+ | Последняя синхронизация | ${lastSync} |
95
24
 
96
25
  ---
97
26
 
98
- ## File structure
27
+ ## Структура файлов
99
28
 
100
29
  \`\`\`
101
30
  project-root/
102
- \u251c\u2500\u2500 docs/${space}/ <- documentation mirror of Confluence space
103
- \u251c\u2500\u2500 .confluence/ <- internal data, DO NOT TOUCH
104
- \u2502 \u251c\u2500\u2500 config.json <- connection config (in .gitignore)
105
- \u2502 \u2514\u2500\u2500 tree.json <- structure map (page IDs, versions, hidden flags)
106
- \u251c\u2500\u2500 AGENTS.md <- this file
31
+ \u251c\u2500\u2500 docs/${space}/ <- зеркало пространства Confluence
32
+ \u251c\u2500\u2500 .confluence/ <- внутренние данные, НЕ ТРОГАТЬ
33
+ \u2502 \u251c\u2500\u2500 config.json <- конфигурация подключения (в .gitignore)
34
+ \u2502 \u2514\u2500\u2500 tree.json <- карта структуры (ID страниц, версии, скрытые флаги)
35
+ \u251c\u2500\u2500 AGENTS.md <- этот файл
107
36
  \u2514\u2500\u2500 .gitignore
108
37
  \`\`\`
109
38
 
110
39
  ### docs/${space}/
111
40
 
112
- This folder mirrors the Confluence space hierarchy:
41
+ Эта папка зеркалирует иерархию пространства Confluence:
113
42
 
114
- - **Folder** = Confluence page
115
- - **Nesting** = parent-child relationship
116
- - **File \`{title}.xhtml\`** inside the folder = page content
43
+ - **Папка** = страница Confluence
44
+ - **Вложенность** = отношение parent-child
45
+ - **Файл \`{название}.xhtml\`** внутри папки = контент страницы
117
46
 
118
47
  ### .confluence/
119
48
 
120
- Internal sync data. **Never modify these files manually.**
49
+ Внутренние данные синхронизации. **Никогда не редактировать вручную.**
121
50
 
122
- - \`tree.json\` — map of all pages: their IDs, versions, paths, hidden flags
123
- - \`config.json\` — connection credentials (excluded from git)
51
+ - \`tree.json\` — карта всех страниц: ID, версии, пути, скрытые флаги
52
+ - \`config.json\` — учётные данные подключения (исключён из git)
124
53
 
125
54
  ---
126
55
 
127
- ## File format
56
+ ## Формат файлов
128
57
 
129
- Pages are stored in **Confluence Storage Format** (XHTML with Confluence-specific tags).
58
+ Страницы хранятся в **Confluence Storage Format** (XHTML с Confluence-специфичными тегами).
130
59
 
131
- ### Basic markup
60
+ ### Базовая разметка
132
61
 
133
62
  \`\`\`xhtml
134
- <h2>Heading</h2>
135
- <p>Text with <strong>bold</strong> and <em>italic</em>.</p>
63
+ <h2>Заголовок</h2>
64
+ <p>Текст с <strong>жирным</strong> и <em>курсивом</em>.</p>
136
65
 
137
66
  <ul>
138
- <li>Item 1</li>
139
- <li>Item 2</li>
67
+ <li>Пункт 1</li>
68
+ <li>Пункт 2</li>
140
69
  </ul>
141
70
 
142
71
  <ol>
143
- <li>First</li>
144
- <li>Second</li>
72
+ <li>Первый</li>
73
+ <li>Второй</li>
145
74
  </ol>
146
75
  \`\`\`
147
76
 
148
- ### Tables
77
+ ### Таблицы
149
78
 
150
79
  \`\`\`xhtml
151
80
  <table>
152
81
  <tbody>
153
82
  <tr>
154
- <th>Column A</th>
155
- <th>Column B</th>
83
+ <th>Колонка A</th>
84
+ <th>Колонка B</th>
156
85
  </tr>
157
86
  <tr>
158
- <td>Value 1</td>
159
- <td>Value 2</td>
87
+ <td>Значение 1</td>
88
+ <td>Значение 2</td>
160
89
  </tr>
161
90
  </tbody>
162
91
  </table>
163
92
  \`\`\`
164
93
 
165
- ### Code blocks (Confluence macro)
94
+ ### Блоки кода (макрос Confluence)
166
95
 
167
96
  \`\`\`xhtml
168
97
  <ac:structured-macro ac:name="code">
@@ -175,86 +104,109 @@ Pages are stored in **Confluence Storage Format** (XHTML with Confluence-specifi
175
104
  </ac:structured-macro>
176
105
  \`\`\`
177
106
 
178
- ### Info/warning panels
107
+ ### Информационные панели
179
108
 
180
109
  \`\`\`xhtml
181
110
  <ac:structured-macro ac:name="info">
182
111
  <ac:rich-text-body>
183
- <p>This is an informational note.</p>
112
+ <p>Это информационная заметка.</p>
184
113
  </ac:rich-text-body>
185
114
  </ac:structured-macro>
186
115
  \`\`\`
187
116
 
188
117
  ---
189
118
 
190
- ## Rules
119
+ ## Правила
191
120
 
192
- ### MANDATORY
121
+ ### ОБЯЗАТЕЛЬНО
193
122
 
194
- - Work **only through files and git** — edit \`.xhtml\` files, then \`git commit\`
195
- - Create/edit \`.xhtml\` files in the correct folder under \`docs/${space}/\`
196
- - Commit your changes synchronization is automatic (post-commit hook)
197
- - Follow valid XHTML format (Confluence Storage Format)
123
+ - Работать **только через файлы и git** — редактировать \`.xhtml\` файлы, затем \`git commit\`
124
+ - Создавать/редактировать \`.xhtml\` файлы в правильной папке внутри \`docs/${space}/\`
125
+ - Коммитить изменениясинхронизация автоматическая (post-commit hook)
126
+ - Соблюдать валидный XHTML формат (Confluence Storage Format)
127
+ - Можно создать голый \`.xhtml\` файл без папки — sync автоматически обернёт его в одноимённую папку
198
128
 
199
- ### FORBIDDEN
129
+ ### ЗАПРЕЩЕНО
200
130
 
201
- - **DO NOT** touch the \`.confluence/\` folder or any files inside it
202
- - **DO NOT** call the Confluence API directly
203
- - **DO NOT** bypass the sync flow
204
- - **DO NOT** modify git hooks (\`.git/hooks/\`)
205
- - **DO NOT** commit \`.confluence/config.json\` (it contains credentials)
131
+ - **НЕ ТРОГАТЬ** папку \`.confluence/\` и любые файлы внутри неё
132
+ - **НЕ ВЫЗЫВАТЬ** Confluence API напрямую
133
+ - **НЕ ОБХОДИТЬ** sync flow
134
+ - **НЕ ИЗМЕНЯТЬ** git hooks (\`.git/hooks/\`)
135
+ - **НЕ КОММИТИТЬ** \`.confluence/config.json\` (содержит учётные данные)
206
136
 
207
137
  ---
208
138
 
209
- ## How to create a new page
139
+ ## CLI команды
140
+
141
+ \`\`\`bash
142
+ npx create-confluence-sync sync # ручная синхронизация
143
+ npx create-confluence-sync sync <file> # push конкретного файла
144
+ npx create-confluence-sync restore # восстановить скрытые страницы (интерактивно)
145
+ npx create-confluence-sync restore "Название" # восстановить по имени
146
+ npx create-confluence-sync spaces # список пространств
147
+ npx create-confluence-sync page <id> # содержимое страницы
148
+ npx create-confluence-sync tree # remote дерево
149
+ npx create-confluence-sync local-tree # локальный tree.json
150
+ npx create-confluence-sync delete <id> # удалить страницу
151
+ \`\`\`
210
152
 
211
- 1. Identify the parent folder where the page should appear in the Confluence hierarchy
212
- 2. Create a folder with the page title: \`docs/${space}/{parent}/New Page/\`
213
- 3. Create a file with the same name: \`docs/${space}/{parent}/New Page/New Page.xhtml\`
214
- 4. Write content in XHTML format (see examples above)
215
- 5. Stage and commit:
216
- \`\`\`
217
- git add "docs/${space}/{parent}/New Page/"
218
- git commit -m "Add page: New Page"
219
- \`\`\`
220
- 6. The page will be automatically created in Confluence
153
+ ---
154
+
155
+ ## Как найти нужную страницу
156
+
157
+ - **По файловой системе**: просмотреть \`docs/${space}/\` — структура папок = структура Confluence
158
+ - **По tree.json**: выполнить \`npx create-confluence-sync local-tree\` — вывод JSON со всеми страницами, их ID, путями и статусами
159
+ - **По содержимому**: использовать grep/поиск по \`.xhtml\` файлам в \`docs/${space}/\`
160
+ - **По ID**: \`npx create-confluence-sync page <id>\` — получить содержимое конкретной страницы из Confluence
161
+
162
+ ---
221
163
 
222
- ## How to edit a page
164
+ ## Как создать новую страницу
223
165
 
224
- 1. Find the \`.xhtml\` file for the page you want to edit
225
- 2. Modify the content (keep valid XHTML)
226
- 3. Commit:
166
+ 1. Определить родительскую папку в иерархии Confluence
167
+ 2. Создать папку с названием страницы: \`docs/${space}/{parent}/Новая страница/\`
168
+ 3. Создать файл с тем же именем: \`docs/${space}/{parent}/Новая страница/Новая страница.xhtml\`
169
+ 4. Написать контент в XHTML формате (см. примеры выше)
170
+ 5. Закоммитить:
227
171
  \`\`\`
228
- git add "docs/${space}/path/to/Page.xhtml"
229
- git commit -m "Update page: Page"
172
+ git add "docs/${space}/{parent}/Новая страница/"
173
+ git commit -m "Добавить страницу: Новая страница"
230
174
  \`\`\`
231
- 4. Changes will be automatically pushed to Confluence
175
+ 6. Страница будет автоматически создана в Confluence
232
176
 
233
- ## How to hide (virtually delete) a page
177
+ > Также можно просто создать файл \`Новая страница.xhtml\` без папки — sync автоматически обернёт его в папку \`Новая страница/\`.
234
178
 
235
- Hiding removes the page from your local tree without deleting it from Confluence.
179
+ ## Как редактировать страницу
236
180
 
237
- 1. Delete the folder/file from the file system
238
- 2. Commit:
181
+ 1. Найти \`.xhtml\` файл нужной страницы
182
+ 2. Изменить контент (сохраняя валидный XHTML)
183
+ 3. Закоммитить:
239
184
  \`\`\`
240
- git add -A
241
- git commit -m "Hide page: Page Name"
185
+ git add "docs/${space}/путь/к/Страница.xhtml"
186
+ git commit -m "Обновить страницу: Страница"
242
187
  \`\`\`
243
- 3. The file disappears locally, but the Confluence page remains intact
244
- 4. The page will not be pulled back on future syncs
245
- 5. To restore: set \`hidden: false\` for the page in \`.confluence/tree.json\` — this is the **only** allowed manual edit of \`.confluence/\` files — the page will reappear on next sync
188
+ 4. Изменения будут автоматически отправлены в Confluence
246
189
 
247
- ---
190
+ ## Как скрыть страницу (виртуальное удаление)
248
191
 
249
- ## Current page tree
192
+ Скрытие убирает страницу из локального дерева без удаления из Confluence.
250
193
 
251
- \`\`\`
252
- ${treeBlock}
253
- \`\`\`
194
+ 1. Удалить папку/файл из файловой системы
195
+ 2. Закоммитить:
196
+ \`\`\`
197
+ git add -A
198
+ git commit -m "Скрыть страницу: Название"
199
+ \`\`\`
200
+ 3. Файл исчезнет локально, но страница в Confluence останется нетронутой
201
+ 4. Страница не будет скачиваться при будущих синхронизациях
202
+ 5. Для восстановления использовать команду:
203
+ \`\`\`
204
+ npx create-confluence-sync restore
205
+ \`\`\`
254
206
 
255
207
  ---
256
208
 
257
- *Generated by confluence-sync*
209
+ *Сгенерировано confluence-sync*
258
210
  `;
259
211
  }
260
212
 
package/src/sync.js CHANGED
@@ -165,7 +165,14 @@ export async function push(apiClient, tree, projectRoot, spaceKey, changedFiles)
165
165
  let created = 0;
166
166
  let updated = 0;
167
167
 
168
- for (let filePath of changedFiles) {
168
+ // Sort by path depth — create parents before children
169
+ const sorted = [...changedFiles].sort((a, b) => {
170
+ const depthA = a.replace(/\\/g, '/').split('/').length;
171
+ const depthB = b.replace(/\\/g, '/').split('/').length;
172
+ return depthA - depthB;
173
+ });
174
+
175
+ for (let filePath of sorted) {
169
176
  let normalized = filePath.replace(/\\/g, '/');
170
177
  let absolutePath = path.resolve(projectRoot, normalized);
171
178