@repo-toolkit/confluence 0.20.0 → 0.21.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/README.md +90 -12
- package/cli.js +564 -6
- package/index.d.ts +130 -1
- package/index.js +548 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -13,8 +13,8 @@ reads `INPUT_*` environment variables (the same shape as the
|
|
|
13
13
|
into a `node20` action or run it as a standalone CLI.
|
|
14
14
|
|
|
15
15
|
The long-form guide (credentials, Mermaid, Markdown subset, optimistic
|
|
16
|
-
concurrency,
|
|
17
|
-
<https://repo-toolkit.pages.dev/docs/packages/confluence>.
|
|
16
|
+
concurrency, managed ownership/pruning/clean, parent summary, dry-run, raw-HTML
|
|
17
|
+
safety) lives at <https://repo-toolkit.pages.dev/docs/packages/confluence>.
|
|
18
18
|
|
|
19
19
|
## Installation
|
|
20
20
|
|
|
@@ -80,10 +80,15 @@ Flags:
|
|
|
80
80
|
- `--skip-unchanged` / `--no-skip-unchanged` — skip pages whose body is unchanged (default: `skip`)
|
|
81
81
|
- `--dry-run` — walk the doc tree and validate every markdown file and local
|
|
82
82
|
image source (same preflight as a real sync) then log the plan. No API
|
|
83
|
-
mutation calls; credentials are not required under `--dry-run`.
|
|
83
|
+
mutation calls; credentials are not required under `--dry-run`. Dry-run can
|
|
84
|
+
show clean/prune and parent-summary intent but cannot list/count remote
|
|
85
|
+
deletion candidates, fetch parent content, or provide mapped remote links
|
|
86
|
+
because it makes zero API calls.
|
|
84
87
|
- `--render-html-blocks` — render ` ```html ` fenced blocks as inline HTML via
|
|
85
88
|
the Confluence `html` macro instead of a code box (default: `false`). **Unsafe
|
|
86
89
|
for untrusted Markdown** — see the raw HTML section of the website guide.
|
|
90
|
+
- `--clean` — move all page descendants to trash before recreation (default: `false`). **WARNING: destructive — all page descendants, including manual/unlabeled pages, are moved to trash before recreation; `parentPageId` itself is retained and never deleted.** Pages are moved to trash (recoverable), never purged.
|
|
91
|
+
- `--update-parent-page` / `--no-update-parent-page` — update the parent page summary region (default: `true`). Use `--no-update-parent-page` to opt out.
|
|
87
92
|
- `-i, --interactive` — prompt interactively (on a real TTY) for missing
|
|
88
93
|
non-secret required fields. The API token is never prompted.
|
|
89
94
|
|
|
@@ -109,6 +114,8 @@ both read for every option. Boolean env values accept `true|1|yes|on` /
|
|
|
109
114
|
| skipUnchanged (bool) | `CONFLUENCE_SKIP_UNCHANGED` | `INPUT_SKIP-UNCHANGED` |
|
|
110
115
|
| dryRun (bool) | `CONFLUENCE_DRY_RUN` | `INPUT_DRY-RUN` |
|
|
111
116
|
| renderHtmlBlocks (bool) | `CONFLUENCE_RENDER_HTML_BLOCKS` | `INPUT_RENDER-HTML-BLOCKS` |
|
|
117
|
+
| clean (bool) | `CONFLUENCE_CLEAN` | `INPUT_CLEAN` |
|
|
118
|
+
| updateParentPage (bool) | `CONFLUENCE_UPDATE_PARENT_PAGE` | `INPUT_UPDATE-PARENT-PAGE` |
|
|
112
119
|
|
|
113
120
|
Errors, `--help`, and all log lines never print the supplied token value. The
|
|
114
121
|
secret-file loader wraps fs errors via `Error.cause` (`Failed to read
|
|
@@ -133,10 +140,55 @@ Root-file behavior: for `sentence-case-parent` and `sentence-case-parents` the p
|
|
|
133
140
|
|
|
134
141
|
### Migration and uniqueness notes
|
|
135
142
|
|
|
136
|
-
> **Changing the strategy changes title-based identity.** Sync
|
|
143
|
+
> **Changing the strategy changes title-based identity.** Sync looks up Confluence pages by title under parent. Switching the strategy therefore seeks a new title and may **create a new page while leaving the old page untouched** — it does not rename or move existing pages. Previously labeled pages created under the old strategy carry the `repo-toolkit-confluence` ownership label and are **pruned (moved to trash) after a successful sync** under the new strategy because they become stale labeled descendants. Pages created before the labeling feature or otherwise unlabeled remain unlabeled and are **never pruned automatically** — they require manual cleanup in Confluence. Use `--dry-run` to preview generated titles (`would sync <path> as "<title>"`) before switching strategies in production.
|
|
137
144
|
|
|
138
145
|
Path-based strategies reduce predictable local collisions (for example, repeated basenames such as `credentials.md` or `README.md` in separate subtrees) but cannot guarantee uniqueness against unrelated or manually created pages already present in the target Confluence space. Existing spaces may still cause Confluence API conflicts if a generated title collides with an unrelated page under the same parent. Do not truncate or hash titles locally; if Confluence imposes a remote title-length limit, the existing API error behavior applies.
|
|
139
146
|
|
|
147
|
+
## Managed ownership and reconciliation
|
|
148
|
+
|
|
149
|
+
Every generated folder page and Markdown leaf page created, updated, or otherwise mapped by a successful sync carries the fixed global Confluence label `repo-toolkit-confluence` (`CONFLUENCE_MANAGED_LABEL`, prefix `global`). The label is additive — existing labels are preserved — and is added only when absent, so repeated unchanged syncs issue no redundant label POST. Adoption is explicit: an existing unlabeled page found by title-under-parent becomes managed once the sync maps it and successfully adds the marker; removing that page's local source later makes it eligible for pruning because it now carries the marker.
|
|
150
|
+
|
|
151
|
+
Default pruning (`clean: false`, the default) runs only after every local page has synced and its ownership label has been verified or added successfully. It enumerates all descendants of `parentPageId` (never the parent itself), and deletes stale pages deepest-first (children before parents). A page is stale only when it has the exact global `repo-toolkit-confluence` marker and its id is absent from the current mapped-id set. Pruning is **label-gated and target-subtree-scoped** — only labeled descendants of the configured `parentPageId` are candidates; space-wide label queries are not used. Unlabeled/manual pages are never deleted by default pruning. Removing the marker from a managed page opts it out of pruning until a future sync maps/adopts it again and re-applies the label. If a stale labeled ancestor contains an unlabeled, non-page, inaccessible, or otherwise retained descendant, that ancestor is blocked and reported as `blocked` rather than risking collateral deletion; safe stale siblings are still deleted. Pruning deletes by page id, moves pages to trash (recoverable), and never purges (`purge=true` is never sent). The target `parentPageId` itself is always an external anchor — it is never labeled and never deleted by either pruning mode.
|
|
152
|
+
|
|
153
|
+
Explicit clean (`clean: true`, default `false` via API/config/CLI/`CONFLUENCE_CLEAN`/`INPUT_CLEAN`) runs **before** creation after successful local preflight and moves **every page descendant** of `parentPageId` to trash, regardless of label, deepest-first, then recreates and labels the local hierarchy without a second prune pass. No destructive reset occurs when `clean` is omitted. An empty local tree with `clean: true` still performs the clean and leaves no safely deletable page descendants. Label lookup is not required for clean; it is deliberately stronger than managed pruning. A stale stale-ancestor protection still applies: pages with unsupported types or incomplete inventories fail closed before any deletion. Partial clean or prune failures abort and return structured evidence (`ReconciliationError` with phase `clean`/`prune`, `completed`, `failure`, `unprocessed`).
|
|
154
|
+
|
|
155
|
+
If sync or labeling fails before pruning, no stale-page deletions occur.
|
|
156
|
+
|
|
157
|
+
## Parent page summary
|
|
158
|
+
|
|
159
|
+
`updateParentPage` defaults to `true` via API/config, `--update-parent-page` (positive CLI), `CONFLUENCE_UPDATE_PARENT_PAGE`, and `INPUT_UPDATE-PARENT-PAGE` (GitHub Action). Use `--no-update-parent-page` or set `updateParentPage: false` / `CONFLUENCE_UPDATE_PARENT_PAGE=false` / `INPUT_UPDATE-PARENT-PAGE=false` to opt out.
|
|
160
|
+
|
|
161
|
+
After child-page sync, labeling, and clean/prune reconciliation succeed, the tool fetches the current target parent page, preserves its title and every byte outside one tool-managed region bounded by `<!-- repo-toolkit-confluence:parent-summary:start -->` / `<!-- repo-toolkit-confluence:parent-summary:end -->`, and updates that region with `version.number = current + 1`. The target parent itself remains unlabeled and is never deleted. On first run the region is appended; later runs replace it in place. Setting opt-out leaves both existing managed and manual parent content untouched — it does not remove a previously generated region. The PUT is skipped when the reconstructed body is byte-equal to the current body; no wall-clock timestamp, version, or per-run counters are included so identical deployments are idempotent. Malformed or duplicate markers fail closed without rewriting the parent. If any earlier phase fails, the parent summary is left unchanged; a parent GET/PUT failure after successful child reconciliation surfaces as `ParentSummaryError` (phase `parent-summary`) with child evidence and the failed parent id.
|
|
162
|
+
|
|
163
|
+
The generated region contains:
|
|
164
|
+
|
|
165
|
+
- `Synced documentation` heading with source provenance — when `repositoryUrl` resolves, the link is included; otherwise a generic `maintained by repo-toolkit-confluence` statement is rendered without exposing runner paths.
|
|
166
|
+
- Deterministic statistics derived from the validated local plan and final mapping: Markdown page count, generated directory-page count, total managed child-page count, maximum documentation depth, local attachment-reference count, and Mermaid-block count (zero values render explicitly for an empty tree).
|
|
167
|
+
- A nested deterministic directory-style tree for every generated directory and Markdown page, in local relative-path order, displaying the resolved Confluence title, distinguishing directory pages from Markdown leaves, and linking each item to its mapped Confluence page via id-backed storage links.
|
|
168
|
+
- Stable guidance that generated descendants carry the `repo-toolkit-confluence` label, missing labeled pages are pruned after successful sync, unlabeled pages are preserved by default, and explicit clean moves all safely deletable page descendants to trash.
|
|
169
|
+
|
|
170
|
+
With an empty local tree, provenance/guidance, zero statistics, and an explicit `No managed child pages` tree state are rendered. No child-page bodies, headings, excerpts, attachment names, secrets, credentials, local absolute paths, or unbounded remote metadata are copied into the parent.
|
|
171
|
+
|
|
172
|
+
Dry-run with `updateParentPage: true` prints locally known parent-summary statistics and title tree but cannot fetch parent content or provide mapped remote links because it makes zero API calls.
|
|
173
|
+
|
|
174
|
+
## Dry run
|
|
175
|
+
|
|
176
|
+
`--dry-run` walks the documentation tree, runs the same local preflight (read and convert every Markdown file, validate every local image source), and prints the plan without any API mutation calls. Credentials are not required under `--dry-run`. Dry-run preserves zero API calls — `dryRun + clean` logs that a remote clean would be requested and `dry-run` states that managed stale-page pruning would run during a real sync, but it cannot enumerate or count remote deletion candidates. With `updateParentPage` enabled, dry-run can show local parent-summary statistics and title tree, but cannot claim the parent would be unchanged, fetch parent content, or emit remote id-backed links.
|
|
177
|
+
|
|
178
|
+
## Permissions and concurrency
|
|
179
|
+
|
|
180
|
+
Required Confluence permissions and API scopes for a real sync:
|
|
181
|
+
|
|
182
|
+
- Read descendants of the configured `parentPageId` (`GET /wiki/api/v2/pages/{id}/descendants`, paginated, same-origin cursor).
|
|
183
|
+
- Read and add labels on generated pages (`GET /wiki/api/v2/pages/{id}/labels`, `POST /wiki/rest/api/content/{id}/label` with global `repo-toolkit-confluence`).
|
|
184
|
+
- Create and update pages and attachments under the target (`POST`/`PUT` pages, `GET`/`PUT` page bodies, attachment upload via v1 multipart `POST /wiki/rest/api/content/{pageId}/child/attachment` with `X-Atlassian-Token: no-check`).
|
|
185
|
+
- Read and update the target parent page body (`GET /wiki/api/v2/pages/{id}`, `PUT` with `version.number = current + 1`).
|
|
186
|
+
- Delete (move to trash) pages when pruning or cleaning (`DELETE /wiki/api/v2/pages/{id}` without `purge=true`). Pages are recoverable from Confluence trash.
|
|
187
|
+
|
|
188
|
+
Concurrent sync/clean jobs against the same `parentPageId` are unsupported and dangerous. Callers must serialize deployments for the same target; the tool does not add distributed locking. Two syncs racing on the same page are detected via optimistic concurrency (server returns HTTP 409 on version conflict) and the loser exits nonzero — rerun to reconcile.
|
|
189
|
+
|
|
190
|
+
Downstream `egose/actions/confluence` wiring for `clean` and `update-parent-page` is a named release follow-up if that repository requires separately declared inputs (this package already accepts them via CLI/config/`CONFLUENCE_*`/`INPUT_*`; the Action example below lists them).
|
|
191
|
+
|
|
140
192
|
## JavaScript API
|
|
141
193
|
|
|
142
194
|
```ts
|
|
@@ -151,11 +203,15 @@ await syncConfluenceToDocs({
|
|
|
151
203
|
parentPageId: '123456789',
|
|
152
204
|
versionMessage: 'chore(docs): sync',
|
|
153
205
|
pageTitleStrategy: 'sentence-case-parents', // optional; default: 'filename-stem'
|
|
206
|
+
clean: false, // optional; default: false — when true, trash every page descendant before recreation
|
|
207
|
+
updateParentPage: true, // optional; default: true — set false to skip parent summary update
|
|
154
208
|
});
|
|
155
209
|
```
|
|
156
210
|
|
|
157
211
|
`resolveConfluenceSyncPlan(options)` resolves the same plan without starting a
|
|
158
|
-
sync; it
|
|
212
|
+
sync; it validates `pageTitleStrategy`, fills `clean` (default `false`) and
|
|
213
|
+
`updateParentPage` (default `true`), and is the documented way to validate
|
|
214
|
+
options up front.
|
|
159
215
|
|
|
160
216
|
`syncConfluenceToDocs` runs a **local preflight** before any remote mutation:
|
|
161
217
|
every markdown file is read and converted to storage HTML, and every local
|
|
@@ -164,8 +220,16 @@ size checks the upload path applies. On the first defect it throws an
|
|
|
164
220
|
`LocalSyncValidationAggregateError` listing every failing entry (zero API
|
|
165
221
|
calls). On a remote failure mid-run it throws a `SyncMutationError` carrying
|
|
166
222
|
`.changes` (pages that succeeded), `.failure` (the failing entry + its error),
|
|
167
|
-
and `.unprocessed` (remaining entries).
|
|
168
|
-
`
|
|
223
|
+
and `.unprocessed` (remaining entries). Pruning/clean failures throw
|
|
224
|
+
`ReconciliationError` with `.phase` (`clean` or `prune`), `.completed`,
|
|
225
|
+
`.failure` (failed `pageId` + error), and `.unprocessed`. Parent summary
|
|
226
|
+
failures throw `ParentSummaryError` (phase `parent-summary`) after child
|
|
227
|
+
reconciliation succeeded, carrying `.changes`, `.labelsAdded`,
|
|
228
|
+
`.cleanDeletions`, `.pruneDeletions`, `.blocked`, and `.failure`. On success it
|
|
229
|
+
returns a `SyncResult` with `.changes` (created/updated/unchanged pages),
|
|
230
|
+
`.labelsAdded` (ids that received the ownership marker), `.cleanDeletions`
|
|
231
|
+
(pre-sync trash ids), `.pruneDeletions` (post-sync stale ids), `.blocked`
|
|
232
|
+
(retained stale ancestors), and `.parentStatus` (`updated`|`unchanged`|`skipped`).
|
|
169
233
|
`validateLocalSync(entries, plan)` runs the same local preflight standalone —
|
|
170
234
|
useful for CI gates or `--dry-run`-equivalent validation. Leaf pages with no
|
|
171
235
|
attachment-bearing placeholders are created in a single POST with their final
|
|
@@ -180,6 +244,11 @@ gateway owns all remote work. Typed fakes implementing the interface are
|
|
|
180
244
|
accepted by `syncConfluenceToDocs({ client })` without `as unknown` casts.
|
|
181
245
|
`spaceKey` and `parentPageId` remain required even with a custom gateway.
|
|
182
246
|
|
|
247
|
+
Exported constants and helpers: `CONFLUENCE_MANAGED_LABEL`
|
|
248
|
+
(`repo-toolkit-confluence`), `planStalePruning` / `planCleanDeletions` (pure
|
|
249
|
+
deepest-first planners), plus `ReconciliationError`, `ParentSummaryError`,
|
|
250
|
+
`SyncMutationError`, `LocalSyncValidationAggregateError`.
|
|
251
|
+
|
|
183
252
|
## GitHub Action usage
|
|
184
253
|
|
|
185
254
|
The CLI auto-detects the GitHub Actions `INPUT_*` environment when no flags are
|
|
@@ -214,10 +283,20 @@ inputs:
|
|
|
214
283
|
dry-run: { required: false, default: 'false' }
|
|
215
284
|
skip-unchanged: { required: false, default: 'true' }
|
|
216
285
|
render-html-blocks: { required: false, default: 'false' }
|
|
286
|
+
clean: { required: false, default: 'false' }
|
|
287
|
+
update-parent-page: { required: false, default: 'true' }
|
|
217
288
|
```
|
|
218
289
|
|
|
290
|
+
The `clean` input is destructive — when `true`, every page descendant of
|
|
291
|
+
`parentPageId`, including manual/unlabeled pages, is moved to trash before
|
|
292
|
+
recreation. The `update-parent-page` input controls the parent summary region
|
|
293
|
+
(default on); set `false` to skip parent updates and leave existing managed and
|
|
294
|
+
manual parent content untouched. If `egose/actions/confluence` requires
|
|
295
|
+
separately declared inputs, wire both there as a release follow-up.
|
|
296
|
+
|
|
219
297
|
A runnable smoke fixture lives under `packages/confluence/action-fixture/` and
|
|
220
|
-
demonstrates starting a sync with mocked `INPUT_*` inputs (no network)
|
|
298
|
+
demonstrates starting a sync with mocked `INPUT_*` inputs (no network), proving
|
|
299
|
+
`INPUT_CLEAN` and `INPUT_UPDATE-PARENT-PAGE` reach planning via dry-run.
|
|
221
300
|
|
|
222
301
|
## Notes
|
|
223
302
|
|
|
@@ -226,10 +305,9 @@ demonstrates starting a sync with mocked `INPUT_*` inputs (no network).
|
|
|
226
305
|
(`/wiki/rest/api/content/{pageId}/child/attachment`) with
|
|
227
306
|
`X-Atlassian-Token: no-check`, because v2 has no multipart upload contract
|
|
228
307
|
yet. Page, space, and attachment-list calls use v2.
|
|
229
|
-
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
HTTP 409.
|
|
308
|
+
- Each PUT supplies `version.number = current + 1`, so the server rejects
|
|
309
|
+
concurrent writes with HTTP 409. See optimistic concurrency in the website
|
|
310
|
+
guide.
|
|
233
311
|
- All inline HTML output is entity-escaped; the only place raw markup lands in
|
|
234
312
|
the page body is inside fenced code blocks (where the `]]>` CDATA terminator
|
|
235
313
|
is neutralized) and, when `--render-html-blocks` is on, inside an
|