@repo-toolkit/confluence 0.9.0 → 0.11.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 +140 -17
- package/cli.js +1381 -273
- package/index.d.ts +288 -34
- package/index.js +1165 -230
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -7,10 +7,14 @@ images referenced from the markdown are uploaded as Confluence attachments and
|
|
|
7
7
|
inline-rendered as `<ac:image><ri:attachment />` macros; remote images stay as
|
|
8
8
|
`<ac:image><ri:url />`.
|
|
9
9
|
|
|
10
|
-
The package is GitHub-Action compatible: when invoked with no CLI flags it
|
|
11
|
-
`INPUT_*` environment variables (the same shape as the
|
|
12
|
-
action's `action.yml`), so you can drop it
|
|
13
|
-
standalone CLI.
|
|
10
|
+
The package is GitHub-Action compatible: when invoked with no CLI flags it
|
|
11
|
+
reads `INPUT_*` environment variables (the same shape as the
|
|
12
|
+
`Bhacaz/docs-as-code-confluence` action's `action.yml`), so you can drop it
|
|
13
|
+
into a `node20` action or run it as a standalone CLI.
|
|
14
|
+
|
|
15
|
+
The long-form guide (credentials, Mermaid, Markdown subset, optimistic
|
|
16
|
+
concurrency, non-pruning behavior, raw-HTML safety) lives at
|
|
17
|
+
<https://repo-toolkit.pages.dev/docs/packages/confluence>.
|
|
14
18
|
|
|
15
19
|
## Installation
|
|
16
20
|
|
|
@@ -18,18 +22,42 @@ standalone CLI.
|
|
|
18
22
|
pnpm add -D @repo-toolkit/confluence
|
|
19
23
|
```
|
|
20
24
|
|
|
25
|
+
## Configuration precedence
|
|
26
|
+
|
|
27
|
+
Every option is resolved independently in this order, with later sources
|
|
28
|
+
overriding earlier ones only for the option they supply:
|
|
29
|
+
|
|
30
|
+
1. CLI flag for that option
|
|
31
|
+
2. `--config` file value for that option
|
|
32
|
+
3. `CONFLUENCE_*` environment variable for that option (higher specificity)
|
|
33
|
+
4. `INPUT_*` environment variable for that option (GitHub Actions form)
|
|
34
|
+
5. built-in default
|
|
35
|
+
|
|
36
|
+
Supplying `--api-token` or any other flag does **not** disable environment
|
|
37
|
+
resolution for the options you left unset. This means you can keep credentials
|
|
38
|
+
in the environment and still override, say, `--folder` on the command line.
|
|
39
|
+
|
|
40
|
+
For secret-free required fields (`folder`, `username`, `baseUrl`, `spaceKey`,
|
|
41
|
+
`parentPageId`) the CLI can prompt on a TTY when `--interactive` is set and
|
|
42
|
+
`--dry-run` is not. The API token is **never** prompted interactively (it
|
|
43
|
+
would echo to the terminal); provide it via a secret file or an environment
|
|
44
|
+
variable instead.
|
|
45
|
+
|
|
21
46
|
## CLI
|
|
22
47
|
|
|
23
48
|
```sh
|
|
24
49
|
repo-toolkit-confluence \
|
|
25
50
|
--folder docs \
|
|
26
51
|
--username user@example.com \
|
|
27
|
-
--api-token
|
|
52
|
+
--api-token-file /run/secrets/confluence_token \
|
|
28
53
|
--confluence-base-url https://mydomain.atlassian.net/wiki \
|
|
29
54
|
--space-key ENG \
|
|
30
55
|
--parent-page-id 123456789
|
|
31
56
|
```
|
|
32
57
|
|
|
58
|
+
Prefer `--api-token-file` (or `CONFLUENCE_API_TOKEN_FILE`) over `--api-token`
|
|
59
|
+
to keep the token out of argv / process listings.
|
|
60
|
+
|
|
33
61
|
Flags:
|
|
34
62
|
|
|
35
63
|
- `--config <path>` — JSON/`.mjs`/`.cjs` config file with any of the options below
|
|
@@ -37,13 +65,48 @@ Flags:
|
|
|
37
65
|
- `--folder <path>` — documentation folder (required)
|
|
38
66
|
- `--username <value>` — Confluence username/email (required)
|
|
39
67
|
- `--api-token <value>` — Confluence API token (required; alias: `--password`)
|
|
68
|
+
- `--api-token-file <path>` — file whose contents become the API token; a
|
|
69
|
+
single trailing newline and surrounding whitespace are stripped (alias:
|
|
70
|
+
`--password-file`). Resolved relative to `--cwd`.
|
|
40
71
|
- `--confluence-base-url <url>` — URL with `/wiki` (required; alias: `--base-url`)
|
|
41
72
|
- `--space-key <key>` — Confluence space key (required; resolved to a `spaceId`)
|
|
42
73
|
- `--parent-page-id <id>` — numeric Confluence page id (required)
|
|
43
74
|
- `--version-message <text>` — version-message suffix appended to every PUT
|
|
44
75
|
- `--skip-unchanged` / `--no-skip-unchanged` — skip pages whose body is unchanged (default: `skip`)
|
|
45
|
-
- `--dry-run` — walk the doc tree and
|
|
46
|
-
|
|
76
|
+
- `--dry-run` — walk the doc tree and validate every markdown file and local
|
|
77
|
+
image source (same preflight as a real sync) then log the plan. No API
|
|
78
|
+
mutation calls; credentials are not required under `--dry-run`.
|
|
79
|
+
- `--render-html-blocks` — render ` ```html ` fenced blocks as inline HTML via
|
|
80
|
+
the Confluence `html` macro instead of a code box (default: `false`). **Unsafe
|
|
81
|
+
for untrusted Markdown** — see the raw HTML section of the website guide.
|
|
82
|
+
- `-i, --interactive` — prompt interactively (on a real TTY) for missing
|
|
83
|
+
non-secret required fields. The API token is never prompted.
|
|
84
|
+
|
|
85
|
+
### Environment variables
|
|
86
|
+
|
|
87
|
+
`CONFLUENCE_*` (higher specificity) and the `INPUT_*` GitHub Actions form are
|
|
88
|
+
both read for every option. Boolean env values accept `true|1|yes|on` /
|
|
89
|
+
`false|0|no|off` (empty string is falsy); any other value exits nonzero with
|
|
90
|
+
`Invalid boolean value for <ENV_NAME>: <raw>`.
|
|
91
|
+
|
|
92
|
+
| Option | `CONFLUENCE_*` | `INPUT_*` (Actions form) |
|
|
93
|
+
| ----------------------- | ------------------------------- | --------------------------------------------- |
|
|
94
|
+
| folder | `CONFLUENCE_FOLDER` | `INPUT_FOLDER` |
|
|
95
|
+
| username | `CONFLUENCE_USERNAME` | `INPUT_USERNAME` |
|
|
96
|
+
| apiToken | `CONFLUENCE_API_TOKEN` | `INPUT_API-TOKEN`, `INPUT_PASSWORD` |
|
|
97
|
+
| apiTokenFile | `CONFLUENCE_API_TOKEN_FILE` | `INPUT_API-TOKEN-FILE`, `INPUT_PASSWORD-FILE` |
|
|
98
|
+
| baseUrl | `CONFLUENCE_BASE_URL` | `INPUT_CONFLUENCE-BASE-URL` |
|
|
99
|
+
| spaceKey | `CONFLUENCE_SPACE_KEY` | `INPUT_SPACE-KEY` |
|
|
100
|
+
| parentPageId | `CONFLUENCE_PARENT_PAGE_ID` | `INPUT_PARENT-PAGE-ID` |
|
|
101
|
+
| versionMessage | `CONFLUENCE_VERSION_MESSAGE` | `INPUT_VERSION-MESSAGE` |
|
|
102
|
+
| skipUnchanged (bool) | `CONFLUENCE_SKIP_UNCHANGED` | `INPUT_SKIP-UNCHANGED` |
|
|
103
|
+
| dryRun (bool) | `CONFLUENCE_DRY_RUN` | `INPUT_DRY-RUN` |
|
|
104
|
+
| renderHtmlBlocks (bool) | `CONFLUENCE_RENDER_HTML_BLOCKS` | `INPUT_RENDER-HTML-BLOCKS` |
|
|
105
|
+
|
|
106
|
+
Errors, `--help`, and all log lines never print the supplied token value. The
|
|
107
|
+
secret-file loader wraps fs errors via `Error.cause` (`Failed to read
|
|
108
|
+
apiTokenFile at <path>`, `apiTokenFile at <path> is empty`) without revealing
|
|
109
|
+
the file contents.
|
|
47
110
|
|
|
48
111
|
## JavaScript API
|
|
49
112
|
|
|
@@ -53,7 +116,7 @@ import { syncConfluenceToDocs } from '@repo-toolkit/confluence';
|
|
|
53
116
|
await syncConfluenceToDocs({
|
|
54
117
|
folder: 'docs',
|
|
55
118
|
username: 'user@example.com',
|
|
56
|
-
apiToken: process.env.
|
|
119
|
+
apiToken: process.env.CONFLUENCE_API_TOKEN!,
|
|
57
120
|
baseUrl: 'https://mydomain.atlassian.net/wiki',
|
|
58
121
|
spaceKey: 'ENG',
|
|
59
122
|
parentPageId: '123456789',
|
|
@@ -61,25 +124,70 @@ await syncConfluenceToDocs({
|
|
|
61
124
|
});
|
|
62
125
|
```
|
|
63
126
|
|
|
127
|
+
`resolveConfluenceSyncPlan(options)` resolves the same plan without starting a
|
|
128
|
+
sync; it is the documented way to validate options up front.
|
|
129
|
+
|
|
130
|
+
`syncConfluenceToDocs` runs a **local preflight** before any remote mutation:
|
|
131
|
+
every markdown file is read and converted to storage HTML, and every local
|
|
132
|
+
image source is validated against the same root-confined, regular-file, and
|
|
133
|
+
size checks the upload path applies. On the first defect it throws an
|
|
134
|
+
`LocalSyncValidationAggregateError` listing every failing entry (zero API
|
|
135
|
+
calls). On a remote failure mid-run it throws a `SyncMutationError` carrying
|
|
136
|
+
`.changes` (pages that succeeded), `.failure` (the failing entry + its error),
|
|
137
|
+
and `.unprocessed` (remaining entries). On success it returns a
|
|
138
|
+
`SyncResult` whose `.changes` lists every created/updated/unchanged page.
|
|
139
|
+
`validateLocalSync(entries, plan)` runs the same local preflight standalone —
|
|
140
|
+
useful for CI gates or `--dry-run`-equivalent validation. Leaf pages with no
|
|
141
|
+
attachment-bearing placeholders are created in a single POST with their final
|
|
142
|
+
rendered body; pages with local images or Mermaid blocks are created first and
|
|
143
|
+
have attachments uploaded against their page id, then their body is PUT.
|
|
144
|
+
|
|
145
|
+
`syncConfluenceToDocs` and the lower-level rewriters depend only on the narrow
|
|
146
|
+
`ConfluenceGateway` and `AttachmentGateway` interfaces (which
|
|
147
|
+
`ConfluenceClient` implements). Supply your own gateway object as `client` and
|
|
148
|
+
the bundled HTTP `username`/`apiToken`/`baseUrl` are no longer required — the
|
|
149
|
+
gateway owns all remote work. Typed fakes implementing the interface are
|
|
150
|
+
accepted by `syncConfluenceToDocs({ client })` without `as unknown` casts.
|
|
151
|
+
`spaceKey` and `parentPageId` remain required even with a custom gateway.
|
|
152
|
+
|
|
64
153
|
## GitHub Action usage
|
|
65
154
|
|
|
66
|
-
The CLI auto-detects the GitHub Actions
|
|
67
|
-
supplied.
|
|
68
|
-
resulting `dist/
|
|
155
|
+
The CLI auto-detects the GitHub Actions `INPUT_*` environment when no flags are
|
|
156
|
+
supplied. Bundle the **CLI entrypoint** (`src/cli.ts`) — not the library entry
|
|
157
|
+
(`src/index.ts`) — and ship the resulting `dist/cli.js` as the action
|
|
158
|
+
entrypoint. With `tsup` this is the second entry in `tsup.config.ts`, which is
|
|
159
|
+
emitted separately with a `#!/usr/bin/env node` banner.
|
|
160
|
+
|
|
161
|
+
With `@vercel/ncc`:
|
|
162
|
+
|
|
163
|
+
```sh
|
|
164
|
+
ncc build packages/confluence/src/cli.ts -o action-dist
|
|
165
|
+
# ships action-dist/index.js — reference it as the action main
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Example `action.yml`:
|
|
69
169
|
|
|
70
170
|
```yaml
|
|
71
171
|
runs:
|
|
72
172
|
using: 'node20'
|
|
73
|
-
main: 'dist/index.js'
|
|
173
|
+
main: 'action-dist/index.js'
|
|
74
174
|
inputs:
|
|
75
175
|
folder: { required: true }
|
|
76
176
|
username: { required: true }
|
|
77
177
|
password: { required: true }
|
|
178
|
+
password-file: { required: false }
|
|
78
179
|
confluence-base-url: { required: true }
|
|
79
180
|
space-key: { required: true }
|
|
80
181
|
parent-page-id: { required: true }
|
|
182
|
+
version-message: { required: false }
|
|
183
|
+
dry-run: { required: false, default: 'false' }
|
|
184
|
+
skip-unchanged: { required: false, default: 'true' }
|
|
185
|
+
render-html-blocks: { required: false, default: 'false' }
|
|
81
186
|
```
|
|
82
187
|
|
|
188
|
+
A runnable smoke fixture lives under `packages/confluence/action-fixture/` and
|
|
189
|
+
demonstrates starting a sync with mocked `INPUT_*` inputs (no network).
|
|
190
|
+
|
|
83
191
|
## Notes
|
|
84
192
|
|
|
85
193
|
- Authentication uses HTTP Basic with an API token (not your account password).
|
|
@@ -87,9 +195,24 @@ inputs:
|
|
|
87
195
|
(`/wiki/rest/api/content/{pageId}/child/attachment`) with
|
|
88
196
|
`X-Atlassian-Token: no-check`, because v2 has no multipart upload contract
|
|
89
197
|
yet. Page, space, and attachment-list calls use v2.
|
|
90
|
-
- Pages are reconciled
|
|
91
|
-
|
|
198
|
+
- Pages are reconciled additively (non-pruning): sync never deletes Confluence
|
|
199
|
+
pages or attachments that are absent locally. Each PUT supplies
|
|
200
|
+
`version.number = current + 1`, so the server rejects concurrent writes with
|
|
92
201
|
HTTP 409.
|
|
93
|
-
- All HTML output is entity-escaped; the only place raw markup lands in
|
|
94
|
-
page body is inside fenced code blocks
|
|
95
|
-
neutralized
|
|
202
|
+
- All inline HTML output is entity-escaped; the only place raw markup lands in
|
|
203
|
+
the page body is inside fenced code blocks (where the `]]>` CDATA terminator
|
|
204
|
+
is neutralized) and, when `--render-html-blocks` is on, inside an
|
|
205
|
+
`ac:structured-macro ac:name="html"` body. The latter is an active-content
|
|
206
|
+
boundary — do not feed untrusted Markdown.
|
|
207
|
+
- Inline rendering is a single-pass tokenizer: inline code spans are protected
|
|
208
|
+
first (their contents are never parsed as Markdown or re-escaped), then
|
|
209
|
+
links/images, emphasis, and backslash escapes are resolved against the raw
|
|
210
|
+
text and emitted as escaped XML exactly once. Links/images parse URLs across
|
|
211
|
+
balanced nested parentheses and validate against an **allowlist**
|
|
212
|
+
(`http`/`https`/`mailto`/`tel` + scheme-less relative); other schemes
|
|
213
|
+
(`javascript:`/`data:`/`file:`/`vbscript:`/custom) collapse to their
|
|
214
|
+
label/alt text. C0/DEL control characters and percent-encoded blocked
|
|
215
|
+
schemes are rejected.
|
|
216
|
+
- The Markdown subset (CommonMark flanking emphasis, intraword `*` vs
|
|
217
|
+
non-intraword `_`, nested emphasis/links, backslash escapes) and Mermaid
|
|
218
|
+
rendering requirements/fallback are documented in the website guide.
|