Confluence support for docs-as-code, extracted from docToolchain.
The project publishes AsciiDoctor-generated HTML to Confluence and exports Confluence content back to AsciiDoc. It is usable as a plain library and, on top of that, through a CLI and a Maven plugin.
Early.
dtc-confluence-core is a move of the Confluence code from docToolchain 3.4.2.
That code is Groovy and is being ported to Java class by class; the Spock test suite stays in place throughout to verify each step.
dtc-confluence-cli runs the publisher without a build tool and is usable today, against Confluence Data Center.
dtc-confluence-core-
The library, with no binding to any build tool.
dtc-confluence-cli-
Command line tool.
dtc-confluence-maven-plugin-
Maven integration (planned).
- Confluence Data Center 9.2
-
Primary target, tested against a real instance. Uses REST API v1 under
/rest/api. - Confluence Cloud
-
Through REST API v2 under
/api/v2. Present, but not yet verified against a real instance.
|
Note
|
REST API v2 exists in Cloud only.
On Data Center |
Build it, then install from the directory the build lays out:
./mvnw package
./dtc-confluence-cli/target/install/install.shThat copies the launcher to $HOME/.local/bin/dtc-confluence and the jar below $HOME/.local/lib.
Pass --prefix DIR to install elsewhere, --force to replace an existing installation.
Nothing outside the prefix is touched, so removing those two paths uninstalls it.
dtc-confluence-cli/target/install.zip holds the same layout for copying to another machine; the launcher runs from the unpacked directory without installing.
Running it needs a Java runtime of version 17 or newer, which the launcher looks for in JAVA_HOME and then on the PATH.
Start with a configuration:
dtc-confluence init --api https://confluence.example.com --space SPACEKEYThat writes .dtc-confluence.yaml - hidden, because it configures the tool rather than describing the documentation.
It is also the first name looked for when no --config is given; .dtc-confluence.yml, dtc-confluence.yaml, dtc-confluence.yml and finally docToolchain’s docToolchainConfig.groovy follow, so an existing docToolchain project keeps working unconverted.
# Read the token from wherever you keep it, rather than typing it:
export CONFLUENCE_BEARER_TOKEN=$(pass show confluence/token) # or op, gopass, security...
dtc-confluence verify -d docs # check URL and credentials
dtc-confluence publish -d docs --dry-run # say what would change, write nothing
dtc-confluence publish -d docs # publish the configured inputsA dry run (-n) reads everything a real run reads and writes nothing.
It reports one line per page - would create, would update (with the version it would write), unchanged (the page already carries the hash of this text), would fail (a page of that title exists under another parent, which Confluence refuses) - and ends with a count.
Attachments and labels are not compared: they are written after the page they belong to, so a dry run only says how many would travel with a new page.
would fail is the case worth knowing about, because a Confluence title is unique per space: a page of that title already exists somewhere else in the space, so the new one cannot be created beside it.
The report says where that page hangs, links to it, and says whether this publisher wrote it - a page carrying the hash of an earlier run is almost certainly the same page, moved or renamed since.
For that case --move (confluence.moveExistingPages) writes it back under the parent this document asks for, instead of failing.
A page without that hash was written by somebody else and is never moved; rename the heading or set confluence.pagePrefix to keep the titles apart.
The other half of the same defect is a heading that was renamed: the new title creates a new page and the old one stays where it was, so the space ends up holding both. Every run - dry or not - now names the pages below the document that an earlier run wrote and this one did not touch, with a link to each. They are not deleted: what to do with them is a decision, and this tool is not the one to take it. Both halves go away once a page is identified by its id rather than by its title, which is what the publish manifest is for.
CONFLUENCE_CREDENTIALS=user:token is the alternative where the instance wants basic authentication.
Credentials are read from the environment rather than taken as options, which keeps them out of the process arguments - ps shows a command line to every user on the machine.
It does not keep them out of your shell history: a token typed into an interactive export is recorded there like anything else, which is why the example reads it from a secret store instead.
The other way round, export fetches a page and everything below it and writes AsciiDoc:
dtc-confluence export -d docs # needs pandoc on the PATHIt reads its settings from confluence.export:
confluence:
export:
destDir: build/confluence-export # relative to --doc-dir
rootPageId: '451974352' # or rootPageTitle: 'The page to start at'
downloadAttachments: true # the default
stripChapterNumbering: true # "5.2.4. Title" becomes "Title"
stripPagePrefixRegex: '' # shortens file names, e.g. '^PROJ_'Below destDir it writes docs/ with one file per page in folders mirroring the page tree, docs/images/ with the attachments, and the _menu.adoc and _config.adoc a microsite reads.
Publishing needs a JRE and the jar; exporting needs pandoc as well, because the conversion back to AsciiDoc goes through it.
The last command, wipe, deletes every page in the configured space and refuses to run without --yes-delete-every-page.
See Design decisions.
./mvnw verifyBuilds with JDK 25, targets Java 17. Maven comes from the bundled wrapper, so no local Maven installation is required.
The build does not lint workflows, YAML, XML or shell scripts; CI does, through super-linter. To run the same checks before pushing:
./tools/lint.shIt calls the tools directly rather than through super-linter, which publishes no arm64 image and takes minutes to start under emulation. The configuration files are shared with CI, so findings match. Tools that are not installed are reported and skipped.