mkdocs2confluence 0.5.27__tar.gz → 0.6.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.5.27/src/mkdocs2confluence.egg-info → mkdocs2confluence-0.6.0}/PKG-INFO +19 -3
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/README.md +18 -2
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/pyproject.toml +1 -1
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0/src/mkdocs2confluence.egg-info}/PKG-INFO +19 -3
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/__init__.py +1 -1
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/cli.py +13 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/publisher/client.py +64 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/publisher/pipeline.py +49 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/tests/test_publish_client.py +98 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/tests/test_publish_pipeline.py +136 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/LICENSE +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/setup.cfg +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs2confluence.egg-info/SOURCES.txt +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs2confluence.egg-info/dependency_links.txt +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs2confluence.egg-info/entry_points.txt +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs2confluence.egg-info/requires.txt +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs2confluence.egg-info/top_level.txt +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/emitter/__init__.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/emitter/xhtml.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/ir/__init__.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/ir/document.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/ir/nodes.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/ir/treeutil.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/loader/__init__.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/loader/config.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/loader/extra_css.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/loader/nav.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/loader/page.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/parser/__init__.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/parser/markdown.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/preprocess/__init__.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/preprocess/abbrevs.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/preprocess/fence.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/preprocess/frontmatter.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/preprocess/icons.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/preprocess/includes.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/preprocess/linkdefs.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/preview/__init__.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/preview/render.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/publisher/__init__.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/transforms/__init__.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/transforms/abbrevs.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/transforms/assets.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/transforms/editlink.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/transforms/images.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/transforms/internallinks.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/transforms/mermaid.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/tests/test_abbrevs.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/tests/test_editlink.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/tests/test_emitter.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/tests/test_extra_css.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/tests/test_frontmatter.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/tests/test_icons.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/tests/test_images.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/tests/test_internallinks.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/tests/test_ir.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/tests/test_linkdefs.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/tests/test_loader.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/tests/test_mermaid.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/tests/test_page_loader.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/tests/test_parser.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/tests/test_preprocess.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/tests/test_preview.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/tests/test_publish_config.py +0 -0
- {mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/tests/test_treeutil.py +0 -0
{mkdocs2confluence-0.5.27/src/mkdocs2confluence.egg-info → mkdocs2confluence-0.6.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mkdocs2confluence
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.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
|
|
@@ -40,6 +40,7 @@ Dynamic: license-file
|
|
|
40
40
|
|
|
41
41
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
42
42
|
[](https://www.python.org/downloads/)
|
|
43
|
+
[](https://pypi.org/project/mkdocs2confluence/)
|
|
43
44
|
[](https://github.com/jeckyl2010/mkdocs2confluence/releases/latest)
|
|
44
45
|
[](https://github.com/jeckyl2010/mkdocs2confluence/actions/workflows/ci.yml)
|
|
45
46
|
[](https://github.com/jeckyl2010/mkdocs2confluence/actions/workflows/release.yml)
|
|
@@ -132,7 +133,7 @@ The `--html` flag renders Confluence macros as visual HTML panels so you can rev
|
|
|
132
133
|
Compile all pages listed in `nav:` and publish them to Confluence Cloud.
|
|
133
134
|
|
|
134
135
|
```
|
|
135
|
-
mk2conf publish [--config PATH] [--page PATH] [--section PATH] [--dry-run] [--report FILE]
|
|
136
|
+
mk2conf publish [--config PATH] [--page PATH] [--section PATH] [--dry-run] [--report FILE] [--prune]
|
|
136
137
|
```
|
|
137
138
|
|
|
138
139
|
| Flag | Default | Description |
|
|
@@ -142,6 +143,7 @@ mk2conf publish [--config PATH] [--page PATH] [--section PATH] [--dry-run] [--re
|
|
|
142
143
|
| `--section PATH` | *(whole nav)* | Publish only a nav subtree (e.g. `Guide` or `Guide/Setup`) |
|
|
143
144
|
| `--dry-run` | off | Print the publish plan without making any API calls |
|
|
144
145
|
| `--report FILE` | *(none)* | Write a JSON publish report to `FILE` |
|
|
146
|
+
| `--prune` | off | Delete managed Confluence pages no longer in `nav:` (see [Orphan pruning](#orphan-pruning)) |
|
|
145
147
|
|
|
146
148
|
#### Configuration
|
|
147
149
|
|
|
@@ -169,6 +171,20 @@ The API token is read from (in priority order):
|
|
|
169
171
|
- Pages with `ready: false` in their YAML front matter are **skipped**, even if listed in the nav.
|
|
170
172
|
- Section nodes (nav groups without a page) become empty parent pages in Confluence, mirroring the nav hierarchy.
|
|
171
173
|
- All locally linked assets are uploaded as Confluence page attachments automatically.
|
|
174
|
+
- **Smart update detection** — before calling the Confluence update API, mk2conf compares a `sha256` hash of the compiled output against the hash stored from the previous run (kept as a hidden Confluence page property `mk2conf-content-hash`). Pages whose content has not changed are skipped entirely — no version bump, no watcher notification.
|
|
175
|
+
- **Orphan pruning** — every page created by mk2conf is stamped with a hidden `mk2conf-managed` property. Pass `--prune` to automatically delete managed pages that have been removed from `nav:`. Manually-created Confluence pages are never deleted.
|
|
176
|
+
|
|
177
|
+
#### Orphan pruning
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
mk2conf publish --prune
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
After each publish run, `--prune` fetches all descendants of the configured `parent_page_id`, diffs them against the pages just published, and deletes any managed orphans — i.e. pages that carry the `mk2conf-managed` stamp but are no longer in the MkDocs nav.
|
|
184
|
+
|
|
185
|
+
> **Safety:** Only pages stamped by mk2conf are eligible for deletion. Any page created directly in Confluence will never be touched, even if it sits inside the managed hierarchy.
|
|
186
|
+
>
|
|
187
|
+
> **Partial runs:** `--prune` is silently ignored when `--page` or `--section` is used, because a partial publish would only cover a subset of the nav and would incorrectly identify out-of-scope pages as orphans.
|
|
172
188
|
|
|
173
189
|
#### Mermaid rendering
|
|
174
190
|
|
|
@@ -319,7 +335,7 @@ Any unrecognised block is preserved as a visible `warning` macro — no content
|
|
|
319
335
|
|
|
320
336
|
## Roadmap
|
|
321
337
|
|
|
322
|
-
- [
|
|
338
|
+
- [x] **Delete orphaned pages** — `--prune` detects and removes managed Confluence pages that have been removed from `nav:`. Manually-created pages are never deleted.
|
|
323
339
|
|
|
324
340
|
---
|
|
325
341
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
4
4
|
[](https://www.python.org/downloads/)
|
|
5
|
+
[](https://pypi.org/project/mkdocs2confluence/)
|
|
5
6
|
[](https://github.com/jeckyl2010/mkdocs2confluence/releases/latest)
|
|
6
7
|
[](https://github.com/jeckyl2010/mkdocs2confluence/actions/workflows/ci.yml)
|
|
7
8
|
[](https://github.com/jeckyl2010/mkdocs2confluence/actions/workflows/release.yml)
|
|
@@ -94,7 +95,7 @@ The `--html` flag renders Confluence macros as visual HTML panels so you can rev
|
|
|
94
95
|
Compile all pages listed in `nav:` and publish them to Confluence Cloud.
|
|
95
96
|
|
|
96
97
|
```
|
|
97
|
-
mk2conf publish [--config PATH] [--page PATH] [--section PATH] [--dry-run] [--report FILE]
|
|
98
|
+
mk2conf publish [--config PATH] [--page PATH] [--section PATH] [--dry-run] [--report FILE] [--prune]
|
|
98
99
|
```
|
|
99
100
|
|
|
100
101
|
| Flag | Default | Description |
|
|
@@ -104,6 +105,7 @@ mk2conf publish [--config PATH] [--page PATH] [--section PATH] [--dry-run] [--re
|
|
|
104
105
|
| `--section PATH` | *(whole nav)* | Publish only a nav subtree (e.g. `Guide` or `Guide/Setup`) |
|
|
105
106
|
| `--dry-run` | off | Print the publish plan without making any API calls |
|
|
106
107
|
| `--report FILE` | *(none)* | Write a JSON publish report to `FILE` |
|
|
108
|
+
| `--prune` | off | Delete managed Confluence pages no longer in `nav:` (see [Orphan pruning](#orphan-pruning)) |
|
|
107
109
|
|
|
108
110
|
#### Configuration
|
|
109
111
|
|
|
@@ -131,6 +133,20 @@ The API token is read from (in priority order):
|
|
|
131
133
|
- Pages with `ready: false` in their YAML front matter are **skipped**, even if listed in the nav.
|
|
132
134
|
- Section nodes (nav groups without a page) become empty parent pages in Confluence, mirroring the nav hierarchy.
|
|
133
135
|
- All locally linked assets are uploaded as Confluence page attachments automatically.
|
|
136
|
+
- **Smart update detection** — before calling the Confluence update API, mk2conf compares a `sha256` hash of the compiled output against the hash stored from the previous run (kept as a hidden Confluence page property `mk2conf-content-hash`). Pages whose content has not changed are skipped entirely — no version bump, no watcher notification.
|
|
137
|
+
- **Orphan pruning** — every page created by mk2conf is stamped with a hidden `mk2conf-managed` property. Pass `--prune` to automatically delete managed pages that have been removed from `nav:`. Manually-created Confluence pages are never deleted.
|
|
138
|
+
|
|
139
|
+
#### Orphan pruning
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
mk2conf publish --prune
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
After each publish run, `--prune` fetches all descendants of the configured `parent_page_id`, diffs them against the pages just published, and deletes any managed orphans — i.e. pages that carry the `mk2conf-managed` stamp but are no longer in the MkDocs nav.
|
|
146
|
+
|
|
147
|
+
> **Safety:** Only pages stamped by mk2conf are eligible for deletion. Any page created directly in Confluence will never be touched, even if it sits inside the managed hierarchy.
|
|
148
|
+
>
|
|
149
|
+
> **Partial runs:** `--prune` is silently ignored when `--page` or `--section` is used, because a partial publish would only cover a subset of the nav and would incorrectly identify out-of-scope pages as orphans.
|
|
134
150
|
|
|
135
151
|
#### Mermaid rendering
|
|
136
152
|
|
|
@@ -281,7 +297,7 @@ Any unrecognised block is preserved as a visible `warning` macro — no content
|
|
|
281
297
|
|
|
282
298
|
## Roadmap
|
|
283
299
|
|
|
284
|
-
- [
|
|
300
|
+
- [x] **Delete orphaned pages** — `--prune` detects and removes managed Confluence pages that have been removed from `nav:`. Manually-created pages are never deleted.
|
|
285
301
|
|
|
286
302
|
---
|
|
287
303
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mkdocs2confluence"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.6.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" }
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.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.6.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
|
|
@@ -40,6 +40,7 @@ Dynamic: license-file
|
|
|
40
40
|
|
|
41
41
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
42
42
|
[](https://www.python.org/downloads/)
|
|
43
|
+
[](https://pypi.org/project/mkdocs2confluence/)
|
|
43
44
|
[](https://github.com/jeckyl2010/mkdocs2confluence/releases/latest)
|
|
44
45
|
[](https://github.com/jeckyl2010/mkdocs2confluence/actions/workflows/ci.yml)
|
|
45
46
|
[](https://github.com/jeckyl2010/mkdocs2confluence/actions/workflows/release.yml)
|
|
@@ -132,7 +133,7 @@ The `--html` flag renders Confluence macros as visual HTML panels so you can rev
|
|
|
132
133
|
Compile all pages listed in `nav:` and publish them to Confluence Cloud.
|
|
133
134
|
|
|
134
135
|
```
|
|
135
|
-
mk2conf publish [--config PATH] [--page PATH] [--section PATH] [--dry-run] [--report FILE]
|
|
136
|
+
mk2conf publish [--config PATH] [--page PATH] [--section PATH] [--dry-run] [--report FILE] [--prune]
|
|
136
137
|
```
|
|
137
138
|
|
|
138
139
|
| Flag | Default | Description |
|
|
@@ -142,6 +143,7 @@ mk2conf publish [--config PATH] [--page PATH] [--section PATH] [--dry-run] [--re
|
|
|
142
143
|
| `--section PATH` | *(whole nav)* | Publish only a nav subtree (e.g. `Guide` or `Guide/Setup`) |
|
|
143
144
|
| `--dry-run` | off | Print the publish plan without making any API calls |
|
|
144
145
|
| `--report FILE` | *(none)* | Write a JSON publish report to `FILE` |
|
|
146
|
+
| `--prune` | off | Delete managed Confluence pages no longer in `nav:` (see [Orphan pruning](#orphan-pruning)) |
|
|
145
147
|
|
|
146
148
|
#### Configuration
|
|
147
149
|
|
|
@@ -169,6 +171,20 @@ The API token is read from (in priority order):
|
|
|
169
171
|
- Pages with `ready: false` in their YAML front matter are **skipped**, even if listed in the nav.
|
|
170
172
|
- Section nodes (nav groups without a page) become empty parent pages in Confluence, mirroring the nav hierarchy.
|
|
171
173
|
- All locally linked assets are uploaded as Confluence page attachments automatically.
|
|
174
|
+
- **Smart update detection** — before calling the Confluence update API, mk2conf compares a `sha256` hash of the compiled output against the hash stored from the previous run (kept as a hidden Confluence page property `mk2conf-content-hash`). Pages whose content has not changed are skipped entirely — no version bump, no watcher notification.
|
|
175
|
+
- **Orphan pruning** — every page created by mk2conf is stamped with a hidden `mk2conf-managed` property. Pass `--prune` to automatically delete managed pages that have been removed from `nav:`. Manually-created Confluence pages are never deleted.
|
|
176
|
+
|
|
177
|
+
#### Orphan pruning
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
mk2conf publish --prune
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
After each publish run, `--prune` fetches all descendants of the configured `parent_page_id`, diffs them against the pages just published, and deletes any managed orphans — i.e. pages that carry the `mk2conf-managed` stamp but are no longer in the MkDocs nav.
|
|
184
|
+
|
|
185
|
+
> **Safety:** Only pages stamped by mk2conf are eligible for deletion. Any page created directly in Confluence will never be touched, even if it sits inside the managed hierarchy.
|
|
186
|
+
>
|
|
187
|
+
> **Partial runs:** `--prune` is silently ignored when `--page` or `--section` is used, because a partial publish would only cover a subset of the nav and would incorrectly identify out-of-scope pages as orphans.
|
|
172
188
|
|
|
173
189
|
#### Mermaid rendering
|
|
174
190
|
|
|
@@ -319,7 +335,7 @@ Any unrecognised block is preserved as a visible `warning` macro — no content
|
|
|
319
335
|
|
|
320
336
|
## Roadmap
|
|
321
337
|
|
|
322
|
-
- [
|
|
338
|
+
- [x] **Delete orphaned pages** — `--prune` detects and removes managed Confluence pages that have been removed from `nav:`. Manually-created pages are never deleted.
|
|
323
339
|
|
|
324
340
|
---
|
|
325
341
|
|
|
@@ -115,6 +115,15 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
115
115
|
default=None,
|
|
116
116
|
help="Write a JSON publish report to FILE after the run.",
|
|
117
117
|
)
|
|
118
|
+
publish.add_argument(
|
|
119
|
+
"--prune",
|
|
120
|
+
action="store_true",
|
|
121
|
+
help=(
|
|
122
|
+
"Delete managed Confluence pages that are no longer in the MkDocs nav. "
|
|
123
|
+
"Only pages stamped by mk2conf are eligible — manually-created pages are never deleted. "
|
|
124
|
+
"Ignored when --page or --section is used."
|
|
125
|
+
),
|
|
126
|
+
)
|
|
118
127
|
|
|
119
128
|
return parser
|
|
120
129
|
|
|
@@ -308,10 +317,14 @@ def _cmd_publish(args: argparse.Namespace) -> None:
|
|
|
308
317
|
)
|
|
309
318
|
sys.exit(1)
|
|
310
319
|
plan = plan_publish(nav_nodes, client, config, conf_config, space_id=space_id)
|
|
320
|
+
# --prune is silently disabled for partial publishes (--page / --section)
|
|
321
|
+
# because published_ids would only cover the subset, not the full nav.
|
|
322
|
+
partial = bool(getattr(args, "page", None) or getattr(args, "section", None))
|
|
311
323
|
report = execute_publish(
|
|
312
324
|
plan, client, dry_run=False, space_id=space_id,
|
|
313
325
|
docs_dir=config.docs_dir, full_width=conf_config.full_width,
|
|
314
326
|
root_page_id=conf_config.parent_page_id,
|
|
327
|
+
prune=getattr(args, "prune", False) and not partial,
|
|
315
328
|
)
|
|
316
329
|
except ConfluenceError as exc:
|
|
317
330
|
print(f"error: {exc}", file=sys.stderr)
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/publisher/client.py
RENAMED
|
@@ -9,12 +9,20 @@ from __future__ import annotations
|
|
|
9
9
|
import base64
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from typing import Any, cast
|
|
12
|
+
from urllib.parse import parse_qs, urlparse
|
|
12
13
|
|
|
13
14
|
import httpx
|
|
14
15
|
|
|
15
16
|
from mkdocs_to_confluence.loader.config import ConfluenceConfig
|
|
16
17
|
|
|
17
18
|
|
|
19
|
+
def _extract_cursor(next_url: str) -> str:
|
|
20
|
+
"""Extract the ``cursor`` query parameter from a pagination ``next`` URL."""
|
|
21
|
+
qs = parse_qs(urlparse(next_url).query)
|
|
22
|
+
cursors = qs.get("cursor", [])
|
|
23
|
+
return cursors[0] if cursors else ""
|
|
24
|
+
|
|
25
|
+
|
|
18
26
|
class ConfluenceError(RuntimeError):
|
|
19
27
|
"""Raised when the Confluence API returns an unexpected response."""
|
|
20
28
|
|
|
@@ -409,3 +417,59 @@ class ConfluenceClient:
|
|
|
409
417
|
headers={"X-Atlassian-Token": "no-check"},
|
|
410
418
|
)
|
|
411
419
|
self._raise_for_status(resp, f"upload_attachment({filename!r})")
|
|
420
|
+
|
|
421
|
+
# ── Orphan detection ───────────────────────────────────────────────────────
|
|
422
|
+
|
|
423
|
+
def stamp_managed(self, page_id: str) -> None:
|
|
424
|
+
"""Mark *page_id* as managed by mk2conf via a v2 content property.
|
|
425
|
+
|
|
426
|
+
The property ``mk2conf-managed`` is set to ``true`` on first publish and
|
|
427
|
+
never updated. It is used by :meth:`is_managed` to distinguish pages
|
|
428
|
+
created by mk2conf from manually-created Confluence pages.
|
|
429
|
+
|
|
430
|
+
Errors are swallowed — this is a best-effort stamp and must never block
|
|
431
|
+
a publish.
|
|
432
|
+
"""
|
|
433
|
+
url = self._v2(f"/pages/{page_id}/properties/mk2conf-managed")
|
|
434
|
+
get_resp = self._http.get(url)
|
|
435
|
+
if get_resp.status_code == 200:
|
|
436
|
+
return # already stamped
|
|
437
|
+
self._http.post(
|
|
438
|
+
self._v2(f"/pages/{page_id}/properties"),
|
|
439
|
+
json={"key": "mk2conf-managed", "value": True},
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
def get_descendant_ids(self, page_id: str) -> list[str]:
|
|
443
|
+
"""Return all descendant page IDs under *page_id* at all depths.
|
|
444
|
+
|
|
445
|
+
Uses ``GET /wiki/api/v2/pages/{id}/descendants?depth=all``.
|
|
446
|
+
Paginates automatically via cursor. Returns page IDs only — callers
|
|
447
|
+
that need to filter by managed status use :meth:`is_managed`.
|
|
448
|
+
"""
|
|
449
|
+
ids: list[str] = []
|
|
450
|
+
url = self._v2(f"/pages/{page_id}/descendants")
|
|
451
|
+
params: dict[str, str | int] = {"depth": "all", "limit": 250}
|
|
452
|
+
while True:
|
|
453
|
+
resp = self._http.get(url, params=params)
|
|
454
|
+
self._raise_for_status(resp, f"get_descendant_ids({page_id!r})")
|
|
455
|
+
data = resp.json()
|
|
456
|
+
for item in data.get("results", []):
|
|
457
|
+
if item.get("type") == "page":
|
|
458
|
+
ids.append(str(item["id"]))
|
|
459
|
+
next_url = data.get("_links", {}).get("next")
|
|
460
|
+
if not next_url:
|
|
461
|
+
break
|
|
462
|
+
# next_url is an absolute path — extract cursor and re-request
|
|
463
|
+
url = self._v2(f"/pages/{page_id}/descendants")
|
|
464
|
+
params = {"depth": "all", "limit": 250, "cursor": _extract_cursor(next_url)}
|
|
465
|
+
return ids
|
|
466
|
+
|
|
467
|
+
def is_managed(self, page_id: str) -> bool:
|
|
468
|
+
"""Return ``True`` if *page_id* has the ``mk2conf-managed`` property."""
|
|
469
|
+
resp = self._http.get(self._v2(f"/pages/{page_id}/properties/mk2conf-managed"))
|
|
470
|
+
return resp.status_code == 200
|
|
471
|
+
|
|
472
|
+
def delete_page(self, page_id: str) -> None:
|
|
473
|
+
"""Permanently delete *page_id* from Confluence."""
|
|
474
|
+
resp = self._http.delete(self._v2(f"/pages/{page_id}"))
|
|
475
|
+
self._raise_for_status(resp, f"delete_page({page_id!r})")
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/publisher/pipeline.py
RENAMED
|
@@ -88,6 +88,7 @@ class PublishReport:
|
|
|
88
88
|
skipped: int = 0
|
|
89
89
|
assets_uploaded: int = 0
|
|
90
90
|
assets_skipped: int = 0
|
|
91
|
+
pruned: int = 0
|
|
91
92
|
errors: list[tuple[str, str]] = field(default_factory=list)
|
|
92
93
|
|
|
93
94
|
@property
|
|
@@ -99,6 +100,8 @@ class PublishReport:
|
|
|
99
100
|
f"Published: {self.created} created, {self.updated} updated, {self.skipped} skipped",
|
|
100
101
|
f"Assets: {self.assets_uploaded} uploaded, {self.assets_skipped} skipped",
|
|
101
102
|
]
|
|
103
|
+
if self.pruned:
|
|
104
|
+
lines.append(f"Pruned: {self.pruned} orphaned page(s) deleted")
|
|
102
105
|
if self.errors:
|
|
103
106
|
lines.append(f"Errors: {len(self.errors)}")
|
|
104
107
|
for title, msg in self.errors:
|
|
@@ -463,6 +466,7 @@ def execute_publish(
|
|
|
463
466
|
docs_dir: Path,
|
|
464
467
|
full_width: bool = True,
|
|
465
468
|
root_page_id: str | None = None,
|
|
469
|
+
prune: bool = False,
|
|
466
470
|
) -> PublishReport:
|
|
467
471
|
"""Execute the publish plan.
|
|
468
472
|
|
|
@@ -563,6 +567,10 @@ def execute_publish(
|
|
|
563
567
|
)
|
|
564
568
|
action.page_id = str(page["id"])
|
|
565
569
|
report.created += 1
|
|
570
|
+
try:
|
|
571
|
+
client.stamp_managed(action.page_id)
|
|
572
|
+
except Exception:
|
|
573
|
+
pass # non-fatal
|
|
566
574
|
elif action.action == "update":
|
|
567
575
|
assert action.page_id is not None
|
|
568
576
|
assert action.version is not None
|
|
@@ -598,6 +606,10 @@ def execute_publish(
|
|
|
598
606
|
)
|
|
599
607
|
action.page_id = str(page["id"])
|
|
600
608
|
report.created += 1
|
|
609
|
+
try:
|
|
610
|
+
client.stamp_managed(action.page_id)
|
|
611
|
+
except Exception:
|
|
612
|
+
pass # non-fatal
|
|
601
613
|
except Exception as exc:
|
|
602
614
|
report.errors.append((action.title, str(exc)))
|
|
603
615
|
# Do NOT `continue` — all post-execute blocks below are guarded by
|
|
@@ -645,4 +657,41 @@ def execute_publish(
|
|
|
645
657
|
for name, msg in asset_errors:
|
|
646
658
|
report.errors.append((f"{action.title} / {name}", msg))
|
|
647
659
|
|
|
660
|
+
if prune and root_page_id:
|
|
661
|
+
published_ids = {a.page_id for a in plan if a.page_id}
|
|
662
|
+
_prune_orphans(client, root_page_id, published_ids, report)
|
|
663
|
+
|
|
648
664
|
return report
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
def _prune_orphans(
|
|
668
|
+
client: ConfluenceClient,
|
|
669
|
+
root_page_id: str,
|
|
670
|
+
published_ids: set[str],
|
|
671
|
+
report: PublishReport,
|
|
672
|
+
) -> None:
|
|
673
|
+
"""Delete managed descendant pages that are no longer in the publish plan.
|
|
674
|
+
|
|
675
|
+
Only pages that carry the ``mk2conf-managed`` property are eligible for
|
|
676
|
+
deletion — manually-created Confluence pages are left untouched.
|
|
677
|
+
"""
|
|
678
|
+
try:
|
|
679
|
+
all_descendants = client.get_descendant_ids(root_page_id)
|
|
680
|
+
except Exception as exc:
|
|
681
|
+
print(f" [warn] prune: could not fetch descendants — {exc}")
|
|
682
|
+
return
|
|
683
|
+
|
|
684
|
+
orphan_candidates = [pid for pid in all_descendants if pid not in published_ids]
|
|
685
|
+
if not orphan_candidates:
|
|
686
|
+
return
|
|
687
|
+
|
|
688
|
+
print(f"\nPruning: checking {len(orphan_candidates)} orphan candidate(s)...")
|
|
689
|
+
for page_id in orphan_candidates:
|
|
690
|
+
try:
|
|
691
|
+
if not client.is_managed(page_id):
|
|
692
|
+
continue
|
|
693
|
+
client.delete_page(page_id)
|
|
694
|
+
report.pruned += 1
|
|
695
|
+
print(f" deleted orphan page {page_id}")
|
|
696
|
+
except Exception as exc:
|
|
697
|
+
print(f" [warn] prune: failed to delete page {page_id} — {exc}")
|
|
@@ -490,3 +490,101 @@ def test_upload_attachment_error_raises(tmp_path: Path) -> None:
|
|
|
490
490
|
client._client = httpx.Client(transport=transport) # type: ignore[assignment]
|
|
491
491
|
with pytest.raises(ConfluenceError, match="403"):
|
|
492
492
|
client.upload_attachment("99", img, "img.png")
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
# ── Orphan detection ───────────────────────────────────────────────────────────
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def test_stamp_managed_posts_when_absent() -> None:
|
|
499
|
+
transport = _MockTransport(
|
|
500
|
+
httpx.Response(404, text="not found"), # GET — absent
|
|
501
|
+
httpx.Response(200, json={}), # POST — create
|
|
502
|
+
)
|
|
503
|
+
config = _make_config()
|
|
504
|
+
with ConfluenceClient(config) as client:
|
|
505
|
+
client._client = httpx.Client(transport=transport) # type: ignore[assignment]
|
|
506
|
+
client.stamp_managed("42")
|
|
507
|
+
assert transport.requests[0].method == "GET"
|
|
508
|
+
assert "/properties/mk2conf-managed" in str(transport.requests[0].url)
|
|
509
|
+
assert transport.requests[1].method == "POST"
|
|
510
|
+
body = json.loads(transport.requests[1].content)
|
|
511
|
+
assert body["key"] == "mk2conf-managed"
|
|
512
|
+
assert body["value"] is True
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def test_stamp_managed_skips_when_already_stamped() -> None:
|
|
516
|
+
transport = _MockTransport(httpx.Response(200, json={"key": "mk2conf-managed", "value": True}))
|
|
517
|
+
config = _make_config()
|
|
518
|
+
with ConfluenceClient(config) as client:
|
|
519
|
+
client._client = httpx.Client(transport=transport) # type: ignore[assignment]
|
|
520
|
+
client.stamp_managed("42")
|
|
521
|
+
assert len(transport.requests) == 1 # only the GET, no POST
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
def test_get_descendant_ids_returns_page_ids() -> None:
|
|
525
|
+
payload = {
|
|
526
|
+
"results": [
|
|
527
|
+
{"id": "10", "type": "page"},
|
|
528
|
+
{"id": "11", "type": "page"},
|
|
529
|
+
],
|
|
530
|
+
"_links": {},
|
|
531
|
+
}
|
|
532
|
+
transport = _MockTransport(_json_response(payload))
|
|
533
|
+
config = _make_config()
|
|
534
|
+
with ConfluenceClient(config) as client:
|
|
535
|
+
client._client = httpx.Client(transport=transport) # type: ignore[assignment]
|
|
536
|
+
ids = client.get_descendant_ids("99")
|
|
537
|
+
assert ids == ["10", "11"]
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def test_get_descendant_ids_paginates() -> None:
|
|
541
|
+
page1 = {
|
|
542
|
+
"results": [{"id": "10", "type": "page"}],
|
|
543
|
+
"_links": {"next": "/wiki/api/v2/pages/99/descendants?cursor=abc&depth=all"},
|
|
544
|
+
}
|
|
545
|
+
page2 = {
|
|
546
|
+
"results": [{"id": "11", "type": "page"}],
|
|
547
|
+
"_links": {},
|
|
548
|
+
}
|
|
549
|
+
transport = _MockTransport(_json_response(page1), _json_response(page2))
|
|
550
|
+
config = _make_config()
|
|
551
|
+
with ConfluenceClient(config) as client:
|
|
552
|
+
client._client = httpx.Client(transport=transport) # type: ignore[assignment]
|
|
553
|
+
ids = client.get_descendant_ids("99")
|
|
554
|
+
assert ids == ["10", "11"]
|
|
555
|
+
assert len(transport.requests) == 2
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
def test_is_managed_returns_true_when_present() -> None:
|
|
559
|
+
transport = _MockTransport(httpx.Response(200, json={"key": "mk2conf-managed", "value": True}))
|
|
560
|
+
config = _make_config()
|
|
561
|
+
with ConfluenceClient(config) as client:
|
|
562
|
+
client._client = httpx.Client(transport=transport) # type: ignore[assignment]
|
|
563
|
+
assert client.is_managed("42") is True
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def test_is_managed_returns_false_when_absent() -> None:
|
|
567
|
+
transport = _MockTransport(httpx.Response(404, text="not found"))
|
|
568
|
+
config = _make_config()
|
|
569
|
+
with ConfluenceClient(config) as client:
|
|
570
|
+
client._client = httpx.Client(transport=transport) # type: ignore[assignment]
|
|
571
|
+
assert client.is_managed("42") is False
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
def test_delete_page_sends_delete_request() -> None:
|
|
575
|
+
transport = _MockTransport(httpx.Response(204, text=""))
|
|
576
|
+
config = _make_config()
|
|
577
|
+
with ConfluenceClient(config) as client:
|
|
578
|
+
client._client = httpx.Client(transport=transport) # type: ignore[assignment]
|
|
579
|
+
client.delete_page("42")
|
|
580
|
+
assert transport.requests[0].method == "DELETE"
|
|
581
|
+
assert "/pages/42" in str(transport.requests[0].url)
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def test_delete_page_raises_on_error() -> None:
|
|
585
|
+
transport = _MockTransport(httpx.Response(403, text="forbidden"))
|
|
586
|
+
config = _make_config()
|
|
587
|
+
with ConfluenceClient(config) as client:
|
|
588
|
+
client._client = httpx.Client(transport=transport) # type: ignore[assignment]
|
|
589
|
+
with pytest.raises(ConfluenceError):
|
|
590
|
+
client.delete_page("42")
|
|
@@ -998,3 +998,139 @@ class TestExecutePublish:
|
|
|
998
998
|
assert section_action.page_id is not None
|
|
999
999
|
assert section_action.page_id != "old-space-stale-id"
|
|
1000
1000
|
assert child_action.parent_id == section_action.page_id
|
|
1001
|
+
|
|
1002
|
+
|
|
1003
|
+
class TestPruneOrphans:
|
|
1004
|
+
"""Tests for orphaned page detection and pruning."""
|
|
1005
|
+
|
|
1006
|
+
def test_prune_deletes_managed_orphan(self, tmp_path: Path) -> None:
|
|
1007
|
+
"""A managed descendant not in published_ids must be deleted."""
|
|
1008
|
+
from mkdocs_to_confluence.publisher.pipeline import execute_publish
|
|
1009
|
+
|
|
1010
|
+
docs_dir = tmp_path / "docs"
|
|
1011
|
+
docs_dir.mkdir()
|
|
1012
|
+
page = _make_page_node("My Page", tmp_path, docs_dir)
|
|
1013
|
+
action = PageAction(node=page, title="My Page", action="create", parent_id="ROOT")
|
|
1014
|
+
|
|
1015
|
+
client = _make_execute_client()
|
|
1016
|
+
client.get_descendant_ids.return_value = ["orphan-99"]
|
|
1017
|
+
client.is_managed.return_value = True
|
|
1018
|
+
|
|
1019
|
+
report = execute_publish(
|
|
1020
|
+
[action], client, space_id="~42", docs_dir=docs_dir,
|
|
1021
|
+
root_page_id="ROOT", prune=True,
|
|
1022
|
+
)
|
|
1023
|
+
|
|
1024
|
+
client.delete_page.assert_called_once_with("orphan-99")
|
|
1025
|
+
assert report.pruned == 1
|
|
1026
|
+
|
|
1027
|
+
def test_prune_skips_unmanaged_orphan(self, tmp_path: Path) -> None:
|
|
1028
|
+
"""A manually-created page (no mk2conf-managed stamp) must not be deleted."""
|
|
1029
|
+
from mkdocs_to_confluence.publisher.pipeline import execute_publish
|
|
1030
|
+
|
|
1031
|
+
docs_dir = tmp_path / "docs"
|
|
1032
|
+
docs_dir.mkdir()
|
|
1033
|
+
page = _make_page_node("My Page", tmp_path, docs_dir)
|
|
1034
|
+
action = PageAction(node=page, title="My Page", action="create", parent_id="ROOT")
|
|
1035
|
+
|
|
1036
|
+
client = _make_execute_client()
|
|
1037
|
+
client.get_descendant_ids.return_value = ["manual-page-55"]
|
|
1038
|
+
client.is_managed.return_value = False
|
|
1039
|
+
|
|
1040
|
+
report = execute_publish(
|
|
1041
|
+
[action], client, space_id="~42", docs_dir=docs_dir,
|
|
1042
|
+
root_page_id="ROOT", prune=True,
|
|
1043
|
+
)
|
|
1044
|
+
|
|
1045
|
+
client.delete_page.assert_not_called()
|
|
1046
|
+
assert report.pruned == 0
|
|
1047
|
+
|
|
1048
|
+
def test_prune_skips_published_pages(self, tmp_path: Path) -> None:
|
|
1049
|
+
"""Published pages that are descendants must not be deleted."""
|
|
1050
|
+
from mkdocs_to_confluence.publisher.pipeline import execute_publish
|
|
1051
|
+
|
|
1052
|
+
docs_dir = tmp_path / "docs"
|
|
1053
|
+
docs_dir.mkdir()
|
|
1054
|
+
page = _make_page_node("My Page", tmp_path, docs_dir)
|
|
1055
|
+
action = PageAction(node=page, title="My Page", action="create", parent_id="ROOT")
|
|
1056
|
+
|
|
1057
|
+
client = _make_execute_client()
|
|
1058
|
+
# The newly created page has an id set by create_page side_effect (101)
|
|
1059
|
+
# We simulate get_descendant_ids returning that same id
|
|
1060
|
+
client.get_descendant_ids.return_value = ["101"]
|
|
1061
|
+
|
|
1062
|
+
report = execute_publish(
|
|
1063
|
+
[action], client, space_id="~42", docs_dir=docs_dir,
|
|
1064
|
+
root_page_id="ROOT", prune=True,
|
|
1065
|
+
)
|
|
1066
|
+
|
|
1067
|
+
client.is_managed.assert_not_called()
|
|
1068
|
+
client.delete_page.assert_not_called()
|
|
1069
|
+
assert report.pruned == 0
|
|
1070
|
+
|
|
1071
|
+
def test_prune_disabled_by_default(self, tmp_path: Path) -> None:
|
|
1072
|
+
"""Without prune=True, descendant IDs are never fetched."""
|
|
1073
|
+
from mkdocs_to_confluence.publisher.pipeline import execute_publish
|
|
1074
|
+
|
|
1075
|
+
docs_dir = tmp_path / "docs"
|
|
1076
|
+
docs_dir.mkdir()
|
|
1077
|
+
page = _make_page_node("My Page", tmp_path, docs_dir)
|
|
1078
|
+
action = PageAction(node=page, title="My Page", action="create", parent_id="ROOT")
|
|
1079
|
+
|
|
1080
|
+
client = _make_execute_client()
|
|
1081
|
+
|
|
1082
|
+
execute_publish(
|
|
1083
|
+
[action], client, space_id="~42", docs_dir=docs_dir,
|
|
1084
|
+
root_page_id="ROOT",
|
|
1085
|
+
)
|
|
1086
|
+
|
|
1087
|
+
client.get_descendant_ids.assert_not_called()
|
|
1088
|
+
|
|
1089
|
+
def test_prune_disabled_when_no_root_page_id(self, tmp_path: Path) -> None:
|
|
1090
|
+
"""prune=True without root_page_id does nothing."""
|
|
1091
|
+
from mkdocs_to_confluence.publisher.pipeline import execute_publish
|
|
1092
|
+
|
|
1093
|
+
docs_dir = tmp_path / "docs"
|
|
1094
|
+
docs_dir.mkdir()
|
|
1095
|
+
page = _make_page_node("My Page", tmp_path, docs_dir)
|
|
1096
|
+
action = PageAction(node=page, title="My Page", action="create", parent_id=None)
|
|
1097
|
+
|
|
1098
|
+
client = _make_execute_client()
|
|
1099
|
+
|
|
1100
|
+
execute_publish(
|
|
1101
|
+
[action], client, space_id="~42", docs_dir=docs_dir,
|
|
1102
|
+
prune=True, # no root_page_id
|
|
1103
|
+
)
|
|
1104
|
+
|
|
1105
|
+
client.get_descendant_ids.assert_not_called()
|
|
1106
|
+
|
|
1107
|
+
def test_stamp_managed_called_on_create(self, tmp_path: Path) -> None:
|
|
1108
|
+
"""stamp_managed must be called for each newly created page."""
|
|
1109
|
+
from mkdocs_to_confluence.publisher.pipeline import execute_publish
|
|
1110
|
+
|
|
1111
|
+
docs_dir = tmp_path / "docs"
|
|
1112
|
+
docs_dir.mkdir()
|
|
1113
|
+
page = _make_page_node("My Page", tmp_path, docs_dir)
|
|
1114
|
+
action = PageAction(node=page, title="My Page", action="create", parent_id="ROOT")
|
|
1115
|
+
|
|
1116
|
+
client = _make_execute_client()
|
|
1117
|
+
|
|
1118
|
+
execute_publish(
|
|
1119
|
+
[action], client, space_id="~42", docs_dir=docs_dir, root_page_id="ROOT",
|
|
1120
|
+
)
|
|
1121
|
+
|
|
1122
|
+
client.stamp_managed.assert_called_once()
|
|
1123
|
+
|
|
1124
|
+
def test_report_str_shows_pruned_count(self) -> None:
|
|
1125
|
+
from mkdocs_to_confluence.publisher.pipeline import PublishReport
|
|
1126
|
+
|
|
1127
|
+
r = PublishReport(created=2, updated=1, skipped=0, pruned=3)
|
|
1128
|
+
out = str(r)
|
|
1129
|
+
assert "3 orphaned page(s) deleted" in out
|
|
1130
|
+
|
|
1131
|
+
def test_report_str_omits_pruned_when_zero(self) -> None:
|
|
1132
|
+
from mkdocs_to_confluence.publisher.pipeline import PublishReport
|
|
1133
|
+
|
|
1134
|
+
r = PublishReport(created=2, updated=1, skipped=0, pruned=0)
|
|
1135
|
+
out = str(r)
|
|
1136
|
+
assert "Pruned" not in out
|
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs2confluence.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs2confluence.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs2confluence.egg-info/requires.txt
RENAMED
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs2confluence.egg-info/top_level.txt
RENAMED
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/emitter/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/emitter/xhtml.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/ir/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/ir/document.py
RENAMED
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/ir/treeutil.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/loader/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/loader/config.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/loader/extra_css.py
RENAMED
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/loader/page.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/parser/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/parser/markdown.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/preprocess/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/preprocess/abbrevs.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/preprocess/fence.py
RENAMED
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/preprocess/icons.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/preprocess/includes.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/preprocess/linkdefs.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/preview/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/preview/render.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/publisher/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/transforms/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/transforms/abbrevs.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/transforms/assets.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/transforms/editlink.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/transforms/images.py
RENAMED
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.5.27 → mkdocs2confluence-0.6.0}/src/mkdocs_to_confluence/transforms/mermaid.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|