mkdocs2confluence 0.12.0__tar.gz → 0.13.0__tar.gz
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.
- {mkdocs2confluence-0.12.0/src/mkdocs2confluence.egg-info → mkdocs2confluence-0.13.0}/PKG-INFO +25 -2
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/README.md +23 -1
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/pyproject.toml +5 -1
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0/src/mkdocs2confluence.egg-info}/PKG-INFO +25 -2
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs2confluence.egg-info/SOURCES.txt +12 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs2confluence.egg-info/requires.txt +1 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/cli.py +68 -3
- mkdocs2confluence-0.13.0/src/mkdocs_to_confluence/compiler/__init__.py +6 -0
- mkdocs2confluence-0.13.0/src/mkdocs_to_confluence/compiler/models.py +17 -0
- mkdocs2confluence-0.13.0/src/mkdocs_to_confluence/compiler/page.py +110 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/loader/config.py +18 -0
- mkdocs2confluence-0.13.0/src/mkdocs_to_confluence/publisher/changelog.py +131 -0
- mkdocs2confluence-0.13.0/src/mkdocs_to_confluence/publisher/executor.py +378 -0
- mkdocs2confluence-0.13.0/src/mkdocs_to_confluence/publisher/models.py +60 -0
- mkdocs2confluence-0.13.0/src/mkdocs_to_confluence/publisher/pipeline.py +46 -0
- mkdocs2confluence-0.13.0/src/mkdocs_to_confluence/publisher/planner.py +308 -0
- mkdocs2confluence-0.13.0/src/mkdocs_to_confluence/skill_installer.py +99 -0
- mkdocs2confluence-0.13.0/src/mkdocs_to_confluence/skills/mkdocs-changelog/SKILL.md +78 -0
- mkdocs2confluence-0.13.0/tests/test_changelog_config.py +62 -0
- mkdocs2confluence-0.13.0/tests/test_changelog_publish.py +194 -0
- mkdocs2confluence-0.13.0/tests/test_skill_installer.py +126 -0
- mkdocs2confluence-0.12.0/src/mkdocs_to_confluence/publisher/pipeline.py +0 -838
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/LICENSE +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/setup.cfg +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs2confluence.egg-info/dependency_links.txt +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs2confluence.egg-info/entry_points.txt +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs2confluence.egg-info/top_level.txt +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/__init__.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/emitter/__init__.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/emitter/xhtml.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/ir/__init__.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/ir/document.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/ir/nodes.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/ir/treeutil.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/loader/__init__.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/loader/extra_css.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/loader/nav.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/loader/page.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/parser/__init__.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/parser/markdown.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/pdf/__init__.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/pdf/generator.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/pdf/render.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/preprocess/__init__.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/preprocess/abbrevs.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/preprocess/fence.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/preprocess/frontmatter.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/preprocess/icons.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/preprocess/includes.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/preprocess/linkdefs.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/preview/__init__.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/preview/render.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/preview/server.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/publisher/__init__.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/publisher/client.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/publisher/http_retry.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/sync/__init__.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/sync/anchoring.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/sync/command.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/sync/comments.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/sync/github.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/sync/platform.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/sync/state.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/transforms/__init__.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/transforms/abbrevs.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/transforms/assets.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/transforms/editlink.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/transforms/footer.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/transforms/images.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/transforms/internallinks.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/transforms/mermaid.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_abbrevs.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_children_macro.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_cli.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_editlink.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_emitter.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_extra_css.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_footer.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_frontmatter.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_icons.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_images.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_internallinks.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_ir.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_linkdefs.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_loader.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_mermaid.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_page_loader.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_parser.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_pdf.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_preprocess.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_preview.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_publish_client.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_publish_config.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_publish_pipeline.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_server.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_sync_anchoring.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_sync_command.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_sync_comments.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_sync_github.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_sync_state.py +0 -0
- {mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/tests/test_treeutil.py +0 -0
{mkdocs2confluence-0.12.0/src/mkdocs2confluence.egg-info → mkdocs2confluence-0.13.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mkdocs2confluence
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.0
|
|
4
4
|
Summary: Publish MkDocs Material pages to Confluence Cloud — admonitions, Mermaid diagrams, tabs, page properties and more
|
|
5
5
|
Author: Anders Hybertz
|
|
6
6
|
License: GPL-3.0-or-later
|
|
@@ -25,6 +25,7 @@ Description-Content-Type: text/markdown
|
|
|
25
25
|
License-File: LICENSE
|
|
26
26
|
Requires-Dist: PyYAML>=6.0.3
|
|
27
27
|
Requires-Dist: httpx>=0.27
|
|
28
|
+
Requires-Dist: idna>=3.15
|
|
28
29
|
Requires-Dist: tinycss2>=1.5.1
|
|
29
30
|
Provides-Extra: pdf
|
|
30
31
|
Requires-Dist: weasyprint>=60.0; extra == "pdf"
|
|
@@ -152,10 +153,26 @@ confluence:
|
|
|
152
153
|
parent_page_id: "123456" # optional root page
|
|
153
154
|
mermaid_render: kroki # "kroki" (default) | "kroki:https://your-kroki" | "none"
|
|
154
155
|
full_width: true # default: true
|
|
156
|
+
changelog: CHANGELOG.md # optional: publish as a top-level "What's New" page
|
|
155
157
|
```
|
|
156
158
|
|
|
157
159
|
The `confluence:` block is also accepted under `extra:` for MkDocs strict-mode compatibility. The API token is read from `token:` in `mkdocs.yml`, then `CONFLUENCE_API_TOKEN`, then `MK2CONF_TOKEN`.
|
|
158
160
|
|
|
161
|
+
### Changelog / What's New page
|
|
162
|
+
|
|
163
|
+
Set `changelog:` to a Markdown file path (relative to `docs_dir`) to have mk2conf publish it as a permanent top-level page on every full `mk2conf publish` run. The page title comes from YAML front matter `title:`; it defaults to `"What's New"` if absent.
|
|
164
|
+
|
|
165
|
+
```yaml
|
|
166
|
+
confluence:
|
|
167
|
+
changelog: CHANGELOG.md # relative to docs_dir
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
- The page does **not** need to appear in `nav:` — it is always placed at the top level of the space (or under `parent_page_id` if set).
|
|
171
|
+
- If it also appears in `nav:`, it is published once; no duplication.
|
|
172
|
+
- `--prune` never deletes it — it is a pinned page, not a nav-derived page.
|
|
173
|
+
- Partial runs (`--page` / `--section`) skip the changelog page, consistent with other publish behaviour.
|
|
174
|
+
- Omit the key, or set it to an empty string, to disable the feature entirely.
|
|
175
|
+
|
|
159
176
|
**Your first publish:**
|
|
160
177
|
|
|
161
178
|
```bash
|
|
@@ -181,7 +198,13 @@ mk2conf publish # go live
|
|
|
181
198
|
|
|
182
199
|

|
|
183
200
|
|
|
184
|
-
Pipeline stages: **loader → preprocess → IR → transforms → emitter → publisher**.
|
|
201
|
+
Pipeline stages: **loader → preprocess → IR → transforms → emitter → publisher**.
|
|
202
|
+
|
|
203
|
+
The publisher is split into two phases:
|
|
204
|
+
- `planner.py` builds a nav-ordered publish plan, compiles pages, and makes the read-side API calls needed to decide create vs update vs skip.
|
|
205
|
+
- `executor.py` applies that plan, performs the write-side API calls, uploads attachments, and wires parent/child relationships in nav order so parent pages always exist before their children.
|
|
206
|
+
|
|
207
|
+
`publisher/pipeline.py` remains a compatibility facade that re-exports the public publish surface used by the CLI and tests.
|
|
185
208
|
|
|
186
209
|
---
|
|
187
210
|
|
|
@@ -111,10 +111,26 @@ confluence:
|
|
|
111
111
|
parent_page_id: "123456" # optional root page
|
|
112
112
|
mermaid_render: kroki # "kroki" (default) | "kroki:https://your-kroki" | "none"
|
|
113
113
|
full_width: true # default: true
|
|
114
|
+
changelog: CHANGELOG.md # optional: publish as a top-level "What's New" page
|
|
114
115
|
```
|
|
115
116
|
|
|
116
117
|
The `confluence:` block is also accepted under `extra:` for MkDocs strict-mode compatibility. The API token is read from `token:` in `mkdocs.yml`, then `CONFLUENCE_API_TOKEN`, then `MK2CONF_TOKEN`.
|
|
117
118
|
|
|
119
|
+
### Changelog / What's New page
|
|
120
|
+
|
|
121
|
+
Set `changelog:` to a Markdown file path (relative to `docs_dir`) to have mk2conf publish it as a permanent top-level page on every full `mk2conf publish` run. The page title comes from YAML front matter `title:`; it defaults to `"What's New"` if absent.
|
|
122
|
+
|
|
123
|
+
```yaml
|
|
124
|
+
confluence:
|
|
125
|
+
changelog: CHANGELOG.md # relative to docs_dir
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
- The page does **not** need to appear in `nav:` — it is always placed at the top level of the space (or under `parent_page_id` if set).
|
|
129
|
+
- If it also appears in `nav:`, it is published once; no duplication.
|
|
130
|
+
- `--prune` never deletes it — it is a pinned page, not a nav-derived page.
|
|
131
|
+
- Partial runs (`--page` / `--section`) skip the changelog page, consistent with other publish behaviour.
|
|
132
|
+
- Omit the key, or set it to an empty string, to disable the feature entirely.
|
|
133
|
+
|
|
118
134
|
**Your first publish:**
|
|
119
135
|
|
|
120
136
|
```bash
|
|
@@ -140,7 +156,13 @@ mk2conf publish # go live
|
|
|
140
156
|
|
|
141
157
|

|
|
142
158
|
|
|
143
|
-
Pipeline stages: **loader → preprocess → IR → transforms → emitter → publisher**.
|
|
159
|
+
Pipeline stages: **loader → preprocess → IR → transforms → emitter → publisher**.
|
|
160
|
+
|
|
161
|
+
The publisher is split into two phases:
|
|
162
|
+
- `planner.py` builds a nav-ordered publish plan, compiles pages, and makes the read-side API calls needed to decide create vs update vs skip.
|
|
163
|
+
- `executor.py` applies that plan, performs the write-side API calls, uploads attachments, and wires parent/child relationships in nav order so parent pages always exist before their children.
|
|
164
|
+
|
|
165
|
+
`publisher/pipeline.py` remains a compatibility facade that re-exports the public publish surface used by the CLI and tests.
|
|
144
166
|
|
|
145
167
|
---
|
|
146
168
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mkdocs2confluence"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.13.0"
|
|
4
4
|
description = "Publish MkDocs Material pages to Confluence Cloud — admonitions, Mermaid diagrams, tabs, page properties and more"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "GPL-3.0-or-later" }
|
|
@@ -32,6 +32,7 @@ classifiers = [
|
|
|
32
32
|
dependencies = [
|
|
33
33
|
"PyYAML>=6.0.3",
|
|
34
34
|
"httpx>=0.27",
|
|
35
|
+
"idna>=3.15",
|
|
35
36
|
"tinycss2>=1.5.1",
|
|
36
37
|
]
|
|
37
38
|
|
|
@@ -66,6 +67,9 @@ build-backend = "setuptools.build_meta"
|
|
|
66
67
|
[tool.setuptools.packages.find]
|
|
67
68
|
where = ["src"]
|
|
68
69
|
|
|
70
|
+
[tool.setuptools.package-data]
|
|
71
|
+
"mkdocs_to_confluence" = ["skills/**/*.md"]
|
|
72
|
+
|
|
69
73
|
[tool.ruff]
|
|
70
74
|
line-length = 120
|
|
71
75
|
|
{mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0/src/mkdocs2confluence.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mkdocs2confluence
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.0
|
|
4
4
|
Summary: Publish MkDocs Material pages to Confluence Cloud — admonitions, Mermaid diagrams, tabs, page properties and more
|
|
5
5
|
Author: Anders Hybertz
|
|
6
6
|
License: GPL-3.0-or-later
|
|
@@ -25,6 +25,7 @@ Description-Content-Type: text/markdown
|
|
|
25
25
|
License-File: LICENSE
|
|
26
26
|
Requires-Dist: PyYAML>=6.0.3
|
|
27
27
|
Requires-Dist: httpx>=0.27
|
|
28
|
+
Requires-Dist: idna>=3.15
|
|
28
29
|
Requires-Dist: tinycss2>=1.5.1
|
|
29
30
|
Provides-Extra: pdf
|
|
30
31
|
Requires-Dist: weasyprint>=60.0; extra == "pdf"
|
|
@@ -152,10 +153,26 @@ confluence:
|
|
|
152
153
|
parent_page_id: "123456" # optional root page
|
|
153
154
|
mermaid_render: kroki # "kroki" (default) | "kroki:https://your-kroki" | "none"
|
|
154
155
|
full_width: true # default: true
|
|
156
|
+
changelog: CHANGELOG.md # optional: publish as a top-level "What's New" page
|
|
155
157
|
```
|
|
156
158
|
|
|
157
159
|
The `confluence:` block is also accepted under `extra:` for MkDocs strict-mode compatibility. The API token is read from `token:` in `mkdocs.yml`, then `CONFLUENCE_API_TOKEN`, then `MK2CONF_TOKEN`.
|
|
158
160
|
|
|
161
|
+
### Changelog / What's New page
|
|
162
|
+
|
|
163
|
+
Set `changelog:` to a Markdown file path (relative to `docs_dir`) to have mk2conf publish it as a permanent top-level page on every full `mk2conf publish` run. The page title comes from YAML front matter `title:`; it defaults to `"What's New"` if absent.
|
|
164
|
+
|
|
165
|
+
```yaml
|
|
166
|
+
confluence:
|
|
167
|
+
changelog: CHANGELOG.md # relative to docs_dir
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
- The page does **not** need to appear in `nav:` — it is always placed at the top level of the space (or under `parent_page_id` if set).
|
|
171
|
+
- If it also appears in `nav:`, it is published once; no duplication.
|
|
172
|
+
- `--prune` never deletes it — it is a pinned page, not a nav-derived page.
|
|
173
|
+
- Partial runs (`--page` / `--section`) skip the changelog page, consistent with other publish behaviour.
|
|
174
|
+
- Omit the key, or set it to an empty string, to disable the feature entirely.
|
|
175
|
+
|
|
159
176
|
**Your first publish:**
|
|
160
177
|
|
|
161
178
|
```bash
|
|
@@ -181,7 +198,13 @@ mk2conf publish # go live
|
|
|
181
198
|
|
|
182
199
|

|
|
183
200
|
|
|
184
|
-
Pipeline stages: **loader → preprocess → IR → transforms → emitter → publisher**.
|
|
201
|
+
Pipeline stages: **loader → preprocess → IR → transforms → emitter → publisher**.
|
|
202
|
+
|
|
203
|
+
The publisher is split into two phases:
|
|
204
|
+
- `planner.py` builds a nav-ordered publish plan, compiles pages, and makes the read-side API calls needed to decide create vs update vs skip.
|
|
205
|
+
- `executor.py` applies that plan, performs the write-side API calls, uploads attachments, and wires parent/child relationships in nav order so parent pages always exist before their children.
|
|
206
|
+
|
|
207
|
+
`publisher/pipeline.py` remains a compatibility facade that re-exports the public publish surface used by the CLI and tests.
|
|
185
208
|
|
|
186
209
|
---
|
|
187
210
|
|
{mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs2confluence.egg-info/SOURCES.txt
RENAMED
|
@@ -9,6 +9,10 @@ src/mkdocs2confluence.egg-info/requires.txt
|
|
|
9
9
|
src/mkdocs2confluence.egg-info/top_level.txt
|
|
10
10
|
src/mkdocs_to_confluence/__init__.py
|
|
11
11
|
src/mkdocs_to_confluence/cli.py
|
|
12
|
+
src/mkdocs_to_confluence/skill_installer.py
|
|
13
|
+
src/mkdocs_to_confluence/compiler/__init__.py
|
|
14
|
+
src/mkdocs_to_confluence/compiler/models.py
|
|
15
|
+
src/mkdocs_to_confluence/compiler/page.py
|
|
12
16
|
src/mkdocs_to_confluence/emitter/__init__.py
|
|
13
17
|
src/mkdocs_to_confluence/emitter/xhtml.py
|
|
14
18
|
src/mkdocs_to_confluence/ir/__init__.py
|
|
@@ -36,9 +40,14 @@ src/mkdocs_to_confluence/preview/__init__.py
|
|
|
36
40
|
src/mkdocs_to_confluence/preview/render.py
|
|
37
41
|
src/mkdocs_to_confluence/preview/server.py
|
|
38
42
|
src/mkdocs_to_confluence/publisher/__init__.py
|
|
43
|
+
src/mkdocs_to_confluence/publisher/changelog.py
|
|
39
44
|
src/mkdocs_to_confluence/publisher/client.py
|
|
45
|
+
src/mkdocs_to_confluence/publisher/executor.py
|
|
40
46
|
src/mkdocs_to_confluence/publisher/http_retry.py
|
|
47
|
+
src/mkdocs_to_confluence/publisher/models.py
|
|
41
48
|
src/mkdocs_to_confluence/publisher/pipeline.py
|
|
49
|
+
src/mkdocs_to_confluence/publisher/planner.py
|
|
50
|
+
src/mkdocs_to_confluence/skills/mkdocs-changelog/SKILL.md
|
|
42
51
|
src/mkdocs_to_confluence/sync/__init__.py
|
|
43
52
|
src/mkdocs_to_confluence/sync/anchoring.py
|
|
44
53
|
src/mkdocs_to_confluence/sync/command.py
|
|
@@ -55,6 +64,8 @@ src/mkdocs_to_confluence/transforms/images.py
|
|
|
55
64
|
src/mkdocs_to_confluence/transforms/internallinks.py
|
|
56
65
|
src/mkdocs_to_confluence/transforms/mermaid.py
|
|
57
66
|
tests/test_abbrevs.py
|
|
67
|
+
tests/test_changelog_config.py
|
|
68
|
+
tests/test_changelog_publish.py
|
|
58
69
|
tests/test_children_macro.py
|
|
59
70
|
tests/test_cli.py
|
|
60
71
|
tests/test_editlink.py
|
|
@@ -78,6 +89,7 @@ tests/test_publish_client.py
|
|
|
78
89
|
tests/test_publish_config.py
|
|
79
90
|
tests/test_publish_pipeline.py
|
|
80
91
|
tests/test_server.py
|
|
92
|
+
tests/test_skill_installer.py
|
|
81
93
|
tests/test_sync_anchoring.py
|
|
82
94
|
tests/test_sync_command.py
|
|
83
95
|
tests/test_sync_comments.py
|
|
@@ -265,6 +265,33 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
265
265
|
help="Suppress progress output.",
|
|
266
266
|
)
|
|
267
267
|
|
|
268
|
+
# --- install-skill ---
|
|
269
|
+
is_ = sub.add_parser(
|
|
270
|
+
"install-skill",
|
|
271
|
+
help="Install the mkdocs-changelog AI skill into detected tool directories.",
|
|
272
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
273
|
+
epilog=(
|
|
274
|
+
"Detected targets (all installed when no --tool given):\n"
|
|
275
|
+
" hermes ~/.hermes/skills/tooling/mkdocs-changelog/SKILL.md\n"
|
|
276
|
+
" github-skills .github/skills/tooling/mkdocs-changelog/SKILL.md\n"
|
|
277
|
+
" claude .claude/commands/changelog.md (frontmatter stripped)\n"
|
|
278
|
+
" copilot .github/instructions/mk2conf-changelog.instructions.md\n"
|
|
279
|
+
" cursor .cursor/rules/mk2conf-changelog.mdc\n"
|
|
280
|
+
"\n"
|
|
281
|
+
"Examples:\n"
|
|
282
|
+
" mk2conf install-skill\n"
|
|
283
|
+
" mk2conf install-skill --tool claude\n"
|
|
284
|
+
" mk2conf install-skill --tool hermes\n"
|
|
285
|
+
),
|
|
286
|
+
)
|
|
287
|
+
is_.add_argument(
|
|
288
|
+
"--tool",
|
|
289
|
+
metavar="NAME",
|
|
290
|
+
default=None,
|
|
291
|
+
choices=["hermes", "github-skills", "claude", "copilot", "cursor"],
|
|
292
|
+
help="Install only to a specific tool (hermes, github-skills, claude, copilot, cursor).",
|
|
293
|
+
)
|
|
294
|
+
|
|
268
295
|
return parser
|
|
269
296
|
|
|
270
297
|
|
|
@@ -288,6 +315,8 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
288
315
|
_cmd_pdf(args)
|
|
289
316
|
elif args.command == "sync-comments":
|
|
290
317
|
_cmd_sync_comments(args)
|
|
318
|
+
elif args.command == "install-skill":
|
|
319
|
+
_cmd_install_skill(args)
|
|
291
320
|
except (ValueError, FileNotFoundError) as exc:
|
|
292
321
|
print(f"error: {exc}", file=sys.stderr)
|
|
293
322
|
sys.exit(1)
|
|
@@ -499,6 +528,8 @@ def _cmd_publish(args: argparse.Namespace) -> None:
|
|
|
499
528
|
sys.exit(1)
|
|
500
529
|
nav_nodes = [node]
|
|
501
530
|
|
|
531
|
+
partial = bool(getattr(args, "page", None) or getattr(args, "section", None))
|
|
532
|
+
|
|
502
533
|
if args.dry_run:
|
|
503
534
|
# When a section is given, show what node was matched so the user can
|
|
504
535
|
# verify the section resolved correctly (section vs. leaf page).
|
|
@@ -513,8 +544,14 @@ def _cmd_publish(args: argparse.Namespace) -> None:
|
|
|
513
544
|
print(f"Dry run: would publish {len(pages)} page(s) to {conf_config.base_url}")
|
|
514
545
|
for page in pages:
|
|
515
546
|
print(f" {page.docs_path} → '{page.title}'")
|
|
547
|
+
if conf_config.changelog_file and not partial:
|
|
548
|
+
from mkdocs_to_confluence.publisher.changelog import _extract_title
|
|
549
|
+
cl_path = config.docs_dir / conf_config.changelog_file
|
|
550
|
+
cl_title = _extract_title(cl_path) or "What's New"
|
|
551
|
+
print(f" {conf_config.changelog_file} → '{cl_title}' (changelog, top-level)")
|
|
516
552
|
return
|
|
517
553
|
|
|
554
|
+
from mkdocs_to_confluence.publisher.changelog import publish_changelog
|
|
518
555
|
from mkdocs_to_confluence.publisher.client import ConfluenceClient, ConfluenceError
|
|
519
556
|
from mkdocs_to_confluence.publisher.pipeline import execute_publish, plan_publish
|
|
520
557
|
|
|
@@ -535,9 +572,6 @@ def _cmd_publish(args: argparse.Namespace) -> None:
|
|
|
535
572
|
nav_nodes, client, config, conf_config,
|
|
536
573
|
space_id=space_id, quiet=args.quiet, full_nav_nodes=all_nav_nodes,
|
|
537
574
|
)
|
|
538
|
-
# --prune is silently disabled for partial publishes (--page / --section)
|
|
539
|
-
# because published_ids would only cover the subset, not the full nav.
|
|
540
|
-
partial = bool(getattr(args, "page", None) or getattr(args, "section", None))
|
|
541
575
|
report = execute_publish(
|
|
542
576
|
plan, client, dry_run=False, space_id=space_id,
|
|
543
577
|
space_key=conf_config.space_key,
|
|
@@ -546,6 +580,13 @@ def _cmd_publish(args: argparse.Namespace) -> None:
|
|
|
546
580
|
prune=getattr(args, "prune", False) and not partial,
|
|
547
581
|
quiet=args.quiet,
|
|
548
582
|
)
|
|
583
|
+
# Changelog is a pinned top-level page — always publish on full runs,
|
|
584
|
+
# skip on partial runs (--page / --section) like all other publish behaviour.
|
|
585
|
+
if not partial:
|
|
586
|
+
publish_changelog(
|
|
587
|
+
config, conf_config, client, space_id,
|
|
588
|
+
space_key=conf_config.space_key, quiet=args.quiet,
|
|
589
|
+
)
|
|
549
590
|
except ConfluenceError as exc:
|
|
550
591
|
print(f"error: {exc}", file=sys.stderr)
|
|
551
592
|
sys.exit(1)
|
|
@@ -599,6 +640,30 @@ def _cmd_publish(args: argparse.Namespace) -> None:
|
|
|
599
640
|
sys.exit(1)
|
|
600
641
|
|
|
601
642
|
|
|
643
|
+
def _cmd_install_skill(args: argparse.Namespace) -> None:
|
|
644
|
+
from mkdocs_to_confluence.skill_installer import install_skill
|
|
645
|
+
|
|
646
|
+
installed = install_skill(tool=getattr(args, "tool", None))
|
|
647
|
+
|
|
648
|
+
if not installed:
|
|
649
|
+
print(
|
|
650
|
+
"No AI tool directories detected and no --tool specified.\n"
|
|
651
|
+
"Run with --tool to install to a specific tool, e.g.:\n"
|
|
652
|
+
" mk2conf install-skill --tool claude",
|
|
653
|
+
file=sys.stderr,
|
|
654
|
+
)
|
|
655
|
+
sys.exit(1)
|
|
656
|
+
|
|
657
|
+
for tool_name, dest in installed:
|
|
658
|
+
print(f" installed [{tool_name}] {dest}")
|
|
659
|
+
|
|
660
|
+
if any(name == "fallback" for name, _ in installed):
|
|
661
|
+
print(
|
|
662
|
+
"\nNo AI tool directories were detected. Skill written to .mk2conf/changelog-skill.md.\n"
|
|
663
|
+
"Copy its contents to your AI tool's custom instructions or skill directory.",
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
|
|
602
667
|
def _cmd_pdf(args: argparse.Namespace) -> None:
|
|
603
668
|
# On macOS with uv/non-system Python, Homebrew libs are not on the dyld search
|
|
604
669
|
# path. Re-exec once with DYLD_LIBRARY_PATH set — the sentinel prevents loops.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Typed models for compiler outputs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class CompileResult:
|
|
11
|
+
"""Result of compiling a single MkDocs page to Confluence storage XHTML."""
|
|
12
|
+
|
|
13
|
+
xhtml: str
|
|
14
|
+
attachments: list[Path] = field(default_factory=list)
|
|
15
|
+
labels: tuple[str, ...] = ()
|
|
16
|
+
confluence_status: str | None = None
|
|
17
|
+
version_message: str | None = None
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Page compilation pipeline for MkDocs-to-Confluence."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from mkdocs_to_confluence.compiler.models import CompileResult
|
|
6
|
+
from mkdocs_to_confluence.emitter.xhtml import emit
|
|
7
|
+
from mkdocs_to_confluence.ir.nodes import ChildrenMacro, FrontMatter, SourceFooter
|
|
8
|
+
from mkdocs_to_confluence.loader.config import MkDocsConfig
|
|
9
|
+
from mkdocs_to_confluence.loader.nav import NavNode
|
|
10
|
+
from mkdocs_to_confluence.loader.page import load_page
|
|
11
|
+
from mkdocs_to_confluence.parser.markdown import parse
|
|
12
|
+
from mkdocs_to_confluence.preprocess.abbrevs import (
|
|
13
|
+
extract_abbreviations,
|
|
14
|
+
strip_abbreviation_defs,
|
|
15
|
+
)
|
|
16
|
+
from mkdocs_to_confluence.preprocess.frontmatter import extract_front_matter
|
|
17
|
+
from mkdocs_to_confluence.preprocess.icons import strip_icon_shortcodes
|
|
18
|
+
from mkdocs_to_confluence.preprocess.includes import (
|
|
19
|
+
preprocess_includes,
|
|
20
|
+
strip_html_comments,
|
|
21
|
+
strip_unsupported_html,
|
|
22
|
+
)
|
|
23
|
+
from mkdocs_to_confluence.preprocess.linkdefs import (
|
|
24
|
+
collect_link_defs,
|
|
25
|
+
expand_link_refs,
|
|
26
|
+
strip_link_defs,
|
|
27
|
+
)
|
|
28
|
+
from mkdocs_to_confluence.transforms.abbrevs import apply_abbreviations
|
|
29
|
+
from mkdocs_to_confluence.transforms.assets import resolve_local_assets
|
|
30
|
+
from mkdocs_to_confluence.transforms.editlink import attach_source_url
|
|
31
|
+
from mkdocs_to_confluence.transforms.footer import build_source_footer
|
|
32
|
+
from mkdocs_to_confluence.transforms.internallinks import resolve_internal_links
|
|
33
|
+
from mkdocs_to_confluence.transforms.mermaid import DEFAULT_KROKI_URL, render_mermaid_diagrams
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def compile_page(
|
|
37
|
+
node: NavNode,
|
|
38
|
+
config: MkDocsConfig,
|
|
39
|
+
link_map: dict[str, str] | None = None,
|
|
40
|
+
*,
|
|
41
|
+
is_section_index: bool = False,
|
|
42
|
+
quiet: bool = False,
|
|
43
|
+
) -> CompileResult:
|
|
44
|
+
"""Run the full compile pipeline for one page and return a typed result."""
|
|
45
|
+
if node.source_path is None:
|
|
46
|
+
return CompileResult(xhtml="")
|
|
47
|
+
|
|
48
|
+
raw = load_page(node)
|
|
49
|
+
|
|
50
|
+
preprocessed = preprocess_includes(
|
|
51
|
+
raw,
|
|
52
|
+
source_path=node.source_path,
|
|
53
|
+
docs_dir=config.docs_dir,
|
|
54
|
+
)
|
|
55
|
+
preprocessed = strip_unsupported_html(preprocessed)
|
|
56
|
+
preprocessed = strip_html_comments(preprocessed)
|
|
57
|
+
preprocessed = strip_icon_shortcodes(preprocessed)
|
|
58
|
+
front_matter, preprocessed = extract_front_matter(preprocessed)
|
|
59
|
+
abbrevs = extract_abbreviations(preprocessed)
|
|
60
|
+
preprocessed = strip_abbreviation_defs(preprocessed)
|
|
61
|
+
link_defs = collect_link_defs(preprocessed)
|
|
62
|
+
preprocessed = expand_link_refs(preprocessed, link_defs)
|
|
63
|
+
preprocessed = strip_link_defs(preprocessed)
|
|
64
|
+
ir_nodes = parse(preprocessed)
|
|
65
|
+
if is_section_index:
|
|
66
|
+
ir_nodes = ir_nodes + (ChildrenMacro(),)
|
|
67
|
+
ir_nodes = apply_abbreviations(ir_nodes, abbrevs, page_text=preprocessed)
|
|
68
|
+
ir_nodes, attachments = resolve_local_assets(
|
|
69
|
+
ir_nodes,
|
|
70
|
+
page_path=node.source_path,
|
|
71
|
+
docs_dir=config.docs_dir,
|
|
72
|
+
)
|
|
73
|
+
mermaid_render = config.confluence.mermaid_render if config.confluence else "kroki"
|
|
74
|
+
if mermaid_render != "none":
|
|
75
|
+
kroki_url = (
|
|
76
|
+
mermaid_render[len("kroki:"):] if mermaid_render.startswith("kroki:") else DEFAULT_KROKI_URL
|
|
77
|
+
)
|
|
78
|
+
ir_nodes, mermaid_attachments = render_mermaid_diagrams(ir_nodes, kroki_url, quiet=quiet)
|
|
79
|
+
attachments = attachments + mermaid_attachments
|
|
80
|
+
effective_link_map = link_map if link_map is not None else {}
|
|
81
|
+
if node.docs_path:
|
|
82
|
+
ir_nodes = resolve_internal_links(ir_nodes, effective_link_map, node.docs_path)
|
|
83
|
+
if front_matter is not None:
|
|
84
|
+
ir_nodes = (front_matter,) + ir_nodes
|
|
85
|
+
edit_url = config.page_edit_url(node.docs_path or "")
|
|
86
|
+
site_url = config.page_site_url(node.docs_path or "")
|
|
87
|
+
if site_url:
|
|
88
|
+
ir_nodes = attach_source_url(ir_nodes, "", site_url)
|
|
89
|
+
if edit_url:
|
|
90
|
+
abs_path = str(config.docs_dir / (node.docs_path or ""))
|
|
91
|
+
footer = build_source_footer(edit_url, abs_path)
|
|
92
|
+
ir_nodes = ir_nodes + (footer,)
|
|
93
|
+
|
|
94
|
+
labels: tuple[str, ...] = ()
|
|
95
|
+
confluence_status: str | None = None
|
|
96
|
+
version_message: str | None = None
|
|
97
|
+
for node_item in ir_nodes:
|
|
98
|
+
if isinstance(node_item, FrontMatter):
|
|
99
|
+
labels = node_item.labels
|
|
100
|
+
confluence_status = node_item.confluence_status
|
|
101
|
+
if isinstance(node_item, SourceFooter) and node_item.commit_sha and node_item.commit_summary:
|
|
102
|
+
version_message = f"{node_item.commit_sha}: {node_item.commit_summary}"
|
|
103
|
+
|
|
104
|
+
return CompileResult(
|
|
105
|
+
xhtml=emit(ir_nodes),
|
|
106
|
+
attachments=attachments,
|
|
107
|
+
labels=labels,
|
|
108
|
+
confluence_status=confluence_status,
|
|
109
|
+
version_message=version_message,
|
|
110
|
+
)
|
{mkdocs2confluence-0.12.0 → mkdocs2confluence-0.13.0}/src/mkdocs_to_confluence/loader/config.py
RENAMED
|
@@ -34,6 +34,7 @@ class ConfluenceConfig:
|
|
|
34
34
|
github_token: str | None = None # GitHub PAT (falls back to GITHUB_TOKEN env var)
|
|
35
35
|
github_base_branch: str = "main" # base branch for review PRs
|
|
36
36
|
allow_any_host: bool = False # set True to allow non-Atlassian Cloud base_url hosts
|
|
37
|
+
changelog_file: str | None = None # path relative to docs_dir; None means disabled
|
|
37
38
|
|
|
38
39
|
|
|
39
40
|
@dataclass(frozen=True)
|
|
@@ -259,6 +260,22 @@ def load_config(mkdocs_yml: Path) -> MkDocsConfig:
|
|
|
259
260
|
if not token:
|
|
260
261
|
token = os.environ.get("MK2CONF_TOKEN", "")
|
|
261
262
|
|
|
263
|
+
# changelog (optional) — path relative to docs_dir
|
|
264
|
+
changelog_file: str | None = None
|
|
265
|
+
raw_changelog = raw_conf.get("changelog")
|
|
266
|
+
if raw_changelog is not None:
|
|
267
|
+
cl_str = str(raw_changelog).strip()
|
|
268
|
+
if cl_str:
|
|
269
|
+
candidate = (docs_dir / cl_str).resolve()
|
|
270
|
+
try:
|
|
271
|
+
candidate.relative_to(docs_dir)
|
|
272
|
+
except ValueError:
|
|
273
|
+
raise ConfigError(
|
|
274
|
+
f"mkdocs.yml: 'confluence.changelog' path {cl_str!r} "
|
|
275
|
+
"escapes docs_dir. The path must be relative to the docs directory."
|
|
276
|
+
)
|
|
277
|
+
changelog_file = cl_str
|
|
278
|
+
|
|
262
279
|
confluence = ConfluenceConfig(
|
|
263
280
|
base_url=base_url.rstrip("/"),
|
|
264
281
|
space_key=space_key,
|
|
@@ -273,6 +290,7 @@ def load_config(mkdocs_yml: Path) -> MkDocsConfig:
|
|
|
273
290
|
else os.environ.get("GITHUB_TOKEN") or None),
|
|
274
291
|
github_base_branch=str(raw_conf.get("github_base_branch", "main")),
|
|
275
292
|
allow_any_host=allow_any_host,
|
|
293
|
+
changelog_file=changelog_file,
|
|
276
294
|
)
|
|
277
295
|
|
|
278
296
|
# --- extra_css (optional) ---
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""Compile and publish the standalone changelog page."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
import yaml
|
|
11
|
+
|
|
12
|
+
from mkdocs_to_confluence.loader.nav import NavNode
|
|
13
|
+
from mkdocs_to_confluence.publisher.executor import _upload_assets
|
|
14
|
+
from mkdocs_to_confluence.publisher.planner import _xhtml_hash, compile_page
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from mkdocs_to_confluence.loader.config import ConfluenceConfig, MkDocsConfig
|
|
18
|
+
from mkdocs_to_confluence.publisher.client import ConfluenceClient
|
|
19
|
+
|
|
20
|
+
_FRONT_MATTER_RE = re.compile(r"\A---\s*\n(.*?\n?)---\s*\n?", re.DOTALL)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _extract_title(source_path: Path) -> str | None:
|
|
24
|
+
"""Return the ``title`` value from YAML front matter, or ``None`` if absent."""
|
|
25
|
+
try:
|
|
26
|
+
raw = source_path.read_text(encoding="utf-8")
|
|
27
|
+
except OSError:
|
|
28
|
+
return None
|
|
29
|
+
m = _FRONT_MATTER_RE.match(raw)
|
|
30
|
+
if not m:
|
|
31
|
+
return None
|
|
32
|
+
try:
|
|
33
|
+
fm: object = yaml.safe_load(m.group(1))
|
|
34
|
+
except yaml.YAMLError:
|
|
35
|
+
return None
|
|
36
|
+
if not isinstance(fm, dict):
|
|
37
|
+
return None
|
|
38
|
+
val = fm.get("title")
|
|
39
|
+
return str(val).strip() if val else None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def publish_changelog(
|
|
43
|
+
config: MkDocsConfig,
|
|
44
|
+
conf_config: ConfluenceConfig,
|
|
45
|
+
client: ConfluenceClient,
|
|
46
|
+
space_id: str,
|
|
47
|
+
*,
|
|
48
|
+
space_key: str | None = None,
|
|
49
|
+
quiet: bool = False,
|
|
50
|
+
) -> None:
|
|
51
|
+
"""Compile and publish the changelog page if ``conf_config.changelog_file`` is set."""
|
|
52
|
+
if not conf_config.changelog_file:
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
changelog_path = config.docs_dir / conf_config.changelog_file
|
|
56
|
+
if not changelog_path.exists():
|
|
57
|
+
print(
|
|
58
|
+
f" [warn] changelog: file not found: {changelog_path}",
|
|
59
|
+
file=sys.stderr,
|
|
60
|
+
)
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
title = _extract_title(changelog_path) or "What's New"
|
|
64
|
+
|
|
65
|
+
node = NavNode(
|
|
66
|
+
title=title,
|
|
67
|
+
docs_path=str(changelog_path.relative_to(config.docs_dir)),
|
|
68
|
+
source_path=changelog_path,
|
|
69
|
+
level=0,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if not quiet:
|
|
73
|
+
print(f" compiling '{title}' (changelog)")
|
|
74
|
+
|
|
75
|
+
xhtml, attachments, labels, confluence_status, version_message = compile_page(
|
|
76
|
+
node, config, quiet=quiet
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
xhtml_hash = _xhtml_hash(xhtml)
|
|
80
|
+
existing = client.find_page(space_id, title)
|
|
81
|
+
|
|
82
|
+
if existing is not None and client.get_content_hash(str(existing["id"])) == xhtml_hash:
|
|
83
|
+
if not quiet:
|
|
84
|
+
print(f" unchanged '{title}' (changelog)")
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
parent_id = conf_config.parent_page_id
|
|
88
|
+
|
|
89
|
+
if existing is None:
|
|
90
|
+
page = client.create_page(space_id, title, xhtml, parent_id=parent_id)
|
|
91
|
+
page_id = str(page["id"])
|
|
92
|
+
# Do NOT stamp as managed: _prune_orphans skips unmanaged pages, so
|
|
93
|
+
# this ensures --prune never deletes the changelog page.
|
|
94
|
+
if not quiet:
|
|
95
|
+
print(f" created '{title}' (changelog)")
|
|
96
|
+
else:
|
|
97
|
+
page_id = str(existing["id"])
|
|
98
|
+
version: int = existing["version"]["number"]
|
|
99
|
+
client.update_page(
|
|
100
|
+
page_id, title, xhtml, version + 1,
|
|
101
|
+
parent_id=parent_id,
|
|
102
|
+
version_message=version_message,
|
|
103
|
+
)
|
|
104
|
+
if not quiet:
|
|
105
|
+
print(f" updated '{title}' (changelog)")
|
|
106
|
+
|
|
107
|
+
if attachments:
|
|
108
|
+
_upload_assets(page_id, attachments, config.docs_dir, client, quiet=quiet)
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
client.set_content_hash(page_id, xhtml_hash)
|
|
112
|
+
except Exception:
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
if labels:
|
|
116
|
+
try:
|
|
117
|
+
client.set_page_labels(page_id, labels)
|
|
118
|
+
except Exception:
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
if conf_config.full_width:
|
|
122
|
+
try:
|
|
123
|
+
client.set_page_full_width(page_id)
|
|
124
|
+
except Exception:
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
if confluence_status:
|
|
128
|
+
try:
|
|
129
|
+
client.set_page_status(page_id, confluence_status, space_key=space_key)
|
|
130
|
+
except Exception:
|
|
131
|
+
pass
|