create-confluence-sync 1.0.4 → 1.0.5
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 +1 -1
- package/src/api.js +27 -0
- package/src/git.js +19 -0
- package/src/hook.js +9 -4
- package/src/sync.js +47 -3
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -184,6 +184,32 @@ export function createApiClient(config) {
|
|
|
184
184
|
};
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
async function movePage(pageId, newParentId, title, body, currentVersion) {
|
|
188
|
+
const payload = {
|
|
189
|
+
type: 'page',
|
|
190
|
+
title,
|
|
191
|
+
version: { number: currentVersion + 1 },
|
|
192
|
+
body: {
|
|
193
|
+
storage: {
|
|
194
|
+
value: body,
|
|
195
|
+
representation: 'storage',
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
ancestors: [{ id: String(newParentId) }],
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const data = await jsonRequest(
|
|
202
|
+
baseUrl, token, 'PUT',
|
|
203
|
+
`/rest/api/content/${pageId}`,
|
|
204
|
+
payload
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
id: String(data.id),
|
|
209
|
+
version: data.version.number,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
187
213
|
async function downloadAttachment(downloadPath) {
|
|
188
214
|
const { buffer } = await request(baseUrl, token, 'GET', downloadPath);
|
|
189
215
|
return buffer;
|
|
@@ -201,6 +227,7 @@ export function createApiClient(config) {
|
|
|
201
227
|
getPageAttachments,
|
|
202
228
|
createPage,
|
|
203
229
|
updatePage,
|
|
230
|
+
movePage,
|
|
204
231
|
deletePage,
|
|
205
232
|
downloadAttachment,
|
|
206
233
|
};
|
package/src/git.js
CHANGED
|
@@ -23,6 +23,24 @@ function getDeletedFiles(projectRoot) {
|
|
|
23
23
|
return output.split('\n').filter(f => f.endsWith('.xhtml'));
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
function getRenamedFiles(projectRoot) {
|
|
27
|
+
let output;
|
|
28
|
+
try {
|
|
29
|
+
output = exec(projectRoot, 'git -c core.quotePath=false diff --name-status -M HEAD~1 HEAD');
|
|
30
|
+
} catch {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
if (!output) return [];
|
|
34
|
+
const renames = [];
|
|
35
|
+
for (const line of output.split('\n')) {
|
|
36
|
+
const match = line.match(/^R\d+\t(.+)\t(.+)$/);
|
|
37
|
+
if (match && match[2].endsWith('.xhtml')) {
|
|
38
|
+
renames.push({ from: match[1], to: match[2] });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return renames;
|
|
42
|
+
}
|
|
43
|
+
|
|
26
44
|
function getChangedFilesUncommitted(projectRoot) {
|
|
27
45
|
const output = exec(projectRoot, 'git diff --name-only HEAD');
|
|
28
46
|
if (!output) return [];
|
|
@@ -123,6 +141,7 @@ function initRepo(projectRoot) {
|
|
|
123
141
|
export {
|
|
124
142
|
getChangedFiles,
|
|
125
143
|
getDeletedFiles,
|
|
144
|
+
getRenamedFiles,
|
|
126
145
|
getChangedFilesUncommitted,
|
|
127
146
|
createBranch,
|
|
128
147
|
switchBranch,
|
package/src/hook.js
CHANGED
|
@@ -6,6 +6,7 @@ import { pull, push, detectHidden } from './sync.js';
|
|
|
6
6
|
import {
|
|
7
7
|
getChangedFiles,
|
|
8
8
|
getDeletedFiles,
|
|
9
|
+
getRenamedFiles,
|
|
9
10
|
getCurrentBranch,
|
|
10
11
|
createBranch,
|
|
11
12
|
switchBranch,
|
|
@@ -60,9 +61,13 @@ async function main() {
|
|
|
60
61
|
// Create API client
|
|
61
62
|
const apiClient = createApiClient(config);
|
|
62
63
|
|
|
63
|
-
// Detect
|
|
64
|
+
// Detect renames (must happen before detectHidden to exclude renamed files)
|
|
65
|
+
const renames = getRenamedFiles(projectRoot);
|
|
66
|
+
const renamedFromPaths = new Set(renames.map(r => r.from.replace(/\\/g, '/')));
|
|
67
|
+
|
|
68
|
+
// Detect hidden (only check files deleted in this commit, excluding renames)
|
|
64
69
|
const deletedFiles = getDeletedFiles(projectRoot);
|
|
65
|
-
const hiddenCount = detectHidden(tree, projectRoot, deletedFiles);
|
|
70
|
+
const hiddenCount = detectHidden(tree, projectRoot, deletedFiles, renamedFromPaths);
|
|
66
71
|
if (hiddenCount > 0) {
|
|
67
72
|
log(`Hidden: ${hiddenCount} pages marked as hidden`);
|
|
68
73
|
}
|
|
@@ -102,11 +107,11 @@ async function main() {
|
|
|
102
107
|
deleteBranch(projectRoot, 'confluence/pull');
|
|
103
108
|
|
|
104
109
|
// --- Push (Local -> Confluence) ---
|
|
105
|
-
const pushResult = await push(apiClient, tree, projectRoot, config.space, changedFiles);
|
|
110
|
+
const pushResult = await push(apiClient, tree, projectRoot, config.space, changedFiles, renames);
|
|
106
111
|
saveTree(projectRoot, tree);
|
|
107
112
|
commitAll(projectRoot, `${PREFIX} push to Confluence + update tree`);
|
|
108
113
|
|
|
109
|
-
log(`Push: ${pushResult.created} created, ${pushResult.updated} updated`);
|
|
114
|
+
log(`Push: ${pushResult.created} created, ${pushResult.updated} updated, ${pushResult.moved} moved/renamed`);
|
|
110
115
|
log('Sync complete!');
|
|
111
116
|
}
|
|
112
117
|
|
package/src/sync.js
CHANGED
|
@@ -161,9 +161,49 @@ export async function pull(apiClient, tree, projectRoot, spaceKey) {
|
|
|
161
161
|
return { created: created.length, updated: updated.length, deleted: deleted.length, renamed };
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
export async function push(apiClient, tree, projectRoot, spaceKey, changedFiles) {
|
|
164
|
+
export async function push(apiClient, tree, projectRoot, spaceKey, changedFiles, renames = []) {
|
|
165
165
|
let created = 0;
|
|
166
166
|
let updated = 0;
|
|
167
|
+
let moved = 0;
|
|
168
|
+
|
|
169
|
+
// --- Handle renames/moves before the main loop ---
|
|
170
|
+
const renamedFromPaths = new Set(renames.map(r => r.from.replace(/\\/g, '/')));
|
|
171
|
+
const renamedToPaths = new Set(renames.map(r => r.to.replace(/\\/g, '/')));
|
|
172
|
+
|
|
173
|
+
for (const { from, to } of renames) {
|
|
174
|
+
const fromNorm = from.replace(/\\/g, '/');
|
|
175
|
+
const toNorm = to.replace(/\\/g, '/');
|
|
176
|
+
const existing = getPageByPath(tree, fromNorm);
|
|
177
|
+
|
|
178
|
+
if (!existing) continue;
|
|
179
|
+
|
|
180
|
+
const pageId = existing.pageId;
|
|
181
|
+
const newTitle = path.basename(toNorm).replace(/\.xhtml$/, '');
|
|
182
|
+
const newParentId = pathToParentId(tree, toNorm);
|
|
183
|
+
|
|
184
|
+
const absolutePath = path.resolve(projectRoot, toNorm);
|
|
185
|
+
const body = await fs.readFile(absolutePath, 'utf-8');
|
|
186
|
+
|
|
187
|
+
let result;
|
|
188
|
+
const parentChanged = newParentId && newParentId !== existing.parentId;
|
|
189
|
+
|
|
190
|
+
if (parentChanged) {
|
|
191
|
+
result = await apiClient.movePage(pageId, newParentId, newTitle, body, existing.version);
|
|
192
|
+
console.log(`Moved: ${fromNorm} → ${toNorm}`);
|
|
193
|
+
} else {
|
|
194
|
+
result = await apiClient.updatePage(pageId, newTitle, body, existing.version);
|
|
195
|
+
console.log(`Renamed: ${fromNorm} → ${toNorm}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
addPage(tree, pageId, {
|
|
199
|
+
title: newTitle,
|
|
200
|
+
fsName: sanitizeName(newTitle),
|
|
201
|
+
path: toNorm,
|
|
202
|
+
parentId: parentChanged ? newParentId : existing.parentId,
|
|
203
|
+
version: result.version,
|
|
204
|
+
});
|
|
205
|
+
moved++;
|
|
206
|
+
}
|
|
167
207
|
|
|
168
208
|
// Sort by path depth — create parents before children
|
|
169
209
|
const sorted = [...changedFiles].sort((a, b) => {
|
|
@@ -173,6 +213,9 @@ export async function push(apiClient, tree, projectRoot, spaceKey, changedFiles)
|
|
|
173
213
|
});
|
|
174
214
|
|
|
175
215
|
for (let filePath of sorted) {
|
|
216
|
+
// Skip files that are part of a rename operation
|
|
217
|
+
const normalizedCheck = filePath.replace(/\\/g, '/');
|
|
218
|
+
if (renamedToPaths.has(normalizedCheck)) continue;
|
|
176
219
|
let normalized = filePath.replace(/\\/g, '/');
|
|
177
220
|
let absolutePath = path.resolve(projectRoot, normalized);
|
|
178
221
|
|
|
@@ -227,16 +270,17 @@ export async function push(apiClient, tree, projectRoot, spaceKey, changedFiles)
|
|
|
227
270
|
|
|
228
271
|
tree.lastSync = new Date().toISOString();
|
|
229
272
|
|
|
230
|
-
return { created, updated };
|
|
273
|
+
return { created, updated, moved };
|
|
231
274
|
}
|
|
232
275
|
|
|
233
|
-
export function detectHidden(tree, projectRoot, deletedFiles) {
|
|
276
|
+
export function detectHidden(tree, projectRoot, deletedFiles, excludePaths = new Set()) {
|
|
234
277
|
let count = 0;
|
|
235
278
|
|
|
236
279
|
if (deletedFiles && deletedFiles.length > 0) {
|
|
237
280
|
// Fast path: only check files we know were deleted in the commit
|
|
238
281
|
for (const filePath of deletedFiles) {
|
|
239
282
|
const normalized = filePath.replace(/\\/g, '/');
|
|
283
|
+
if (excludePaths.has(normalized)) continue;
|
|
240
284
|
const page = getPageByPath(tree, normalized);
|
|
241
285
|
if (page && !page.hidden) {
|
|
242
286
|
console.log(`Hidden: ${normalized}`);
|