ghcr-manager 0.9.6 → 0.9.8
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/CHANGELOG.md +63 -0
- package/LICENSE +1 -1
- package/README.md +45 -63
- package/dist/cleanup-summary/_cleanup-summary-markdown.d.ts +0 -1
- package/dist/cleanup-summary/_cleanup-summary-markdown.js +149 -39
- package/dist/cleanup-summary/_cleanup-summary.d.ts +22 -10
- package/dist/cleanup-summary/_cleanup-summary.js +26 -11
- package/dist/cleanup-summary/index.d.ts +1 -1
- package/dist/cli/_cleanup-command.js +82 -23
- package/dist/cli/_json-output.d.ts +1 -0
- package/dist/cli/_json-output.js +11 -0
- package/dist/cli/_tag-selector-resolver.js +36 -13
- package/dist/cli/index.js +1 -5
- package/dist/core/_types.d.ts +9 -6
- package/dist/core/_types.js +8 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/db/_cleanup-run-writer.d.ts +1 -1
- package/dist/db/_cleanup-run-writer.js +28 -7
- package/dist/db/_db-merge-cleanup-copy.js +4 -2
- package/dist/db/_db-merge-scan-copy.js +3 -2
- package/dist/db/_manifest-reachability.js +47 -8
- package/dist/db/_scan-writer.js +0 -9
- package/dist/db/index.d.ts +2 -1
- package/dist/db/index.js +1 -0
- package/dist/db/planner/_planner-direct-target-root-options.d.ts +11 -0
- package/dist/db/planner/_planner-direct-target-root-options.js +1 -0
- package/dist/db/planner/_planner-direct-target-root-tag-filters.d.ts +9 -0
- package/dist/db/planner/_planner-direct-target-root-tag-filters.js +42 -0
- package/dist/db/planner/_planner-direct-target-roots-combined-sql.d.ts +7 -0
- package/dist/db/planner/_planner-direct-target-roots-combined-sql.js +198 -0
- package/dist/db/planner/_planner-direct-target-roots-combined.d.ts +4 -0
- package/dist/db/planner/_planner-direct-target-roots-combined.js +10 -0
- package/dist/db/planner/_planner-direct-target-roots-tagged.d.ts +4 -0
- package/dist/db/planner/_planner-direct-target-roots-tagged.js +125 -0
- package/dist/db/planner/_planner-direct-target-roots.d.ts +2 -11
- package/dist/db/planner/_planner-direct-target-roots.js +8 -192
- package/dist/db/planner/_planner-direct-target-tags.d.ts +1 -1
- package/dist/db/planner/_planner-direct-target-tags.js +7 -6
- package/dist/db/planner/_planner-output.js +34 -13
- package/dist/db/planner/_planner-plan-artifacts-blocked-roots-sql.d.ts +1 -0
- package/dist/db/planner/_planner-plan-artifacts-blocked-roots-sql.js +65 -0
- package/dist/db/planner/_planner-plan-artifacts-closure-sql.d.ts +1 -0
- package/dist/db/planner/_planner-plan-artifacts-closure-sql.js +195 -0
- package/dist/db/planner/_planner-plan-artifacts-supported-untag-only-sql.d.ts +1 -0
- package/dist/db/planner/_planner-plan-artifacts-supported-untag-only-sql.js +86 -0
- package/dist/db/planner/_planner-plan-artifacts.js +26 -128
- package/dist/db/planner/_planner-repository.d.ts +2 -1
- package/dist/db/planner/_planner-repository.js +3 -1
- package/dist/db/planner/_planner-sql.js +13 -2
- package/dist/db/planner/_planner-types.d.ts +23 -8
- package/dist/db/planner/_planner-types.js +14 -3
- package/dist/db/planner/index.d.ts +2 -1
- package/dist/db/planner/index.js +1 -0
- package/dist/execute/_plan-executor.d.ts +1 -1
- package/dist/execute/_plan-executor.js +38 -16
- package/dist/execute/_types.d.ts +2 -19
- package/dist/execute/_untag-client.d.ts +2 -2
- package/dist/execute/_untag-client.js +1 -42
- package/dist/execute/index.d.ts +1 -1
- package/dist/ingest/github/_manifest-kind.d.ts +7 -1
- package/dist/ingest/github/_manifest-kind.js +21 -6
- package/package.json +16 -10
- package/resources/sql/schema/001_schema.sql +17 -4
- package/dist/cli/_untag-command.d.ts +0 -1
- package/dist/cli/_untag-command.js +0 -57
- package/resources/sql/views/002_v_missing_digests.sql +0 -32
- package/resources/sql/views/003_v_scan_root_manifests.sql +0 -44
- package/resources/sql/views/004_v_digest_tag_relations.sql +0 -50
- package/resources/sql/views/005_v_cleanup_root_closure_members.sql +0 -101
- package/resources/sql/views/006_v_cleanup_blocking_overlaps.sql +0 -42
- package/resources/sql/views/007_v_cleanup_root_decision_readable.sql +0 -67
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,69 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.9.8] - 2026-06-03
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Added dedicated graph-matrix GHCR scenarios and workflows to exercise shared-image, multi-arch, cosign, and
|
|
15
|
+
attestation cleanup cases in isolation.
|
|
16
|
+
- Added a local manifest-graph visualizer with browser UI for `ghcr-manager` SQLite databases, including manifest
|
|
17
|
+
details, zoom controls, one-hop expansion, and scan-to-scan compare mode.
|
|
18
|
+
- Added a separately publishable npm package, `ghcr-manager-visualizer`, plus user-facing visualizer documentation.
|
|
19
|
+
- Added repo-local manual visualizer demo scripts for seeding and updating GHCR packages during graph investigation.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Cleanup planning was reworked around the current graph model, including direct SQL-backed tagged/untagged root
|
|
24
|
+
selection, graph-scoped closure walking, and refined `untag-only` vs `fully-deletable` decisions for complex
|
|
25
|
+
multi-arch, cosign, and attestation shapes.
|
|
26
|
+
- Large planner SQL bodies were split into smaller internal modules, and direct-target root selection logic was split
|
|
27
|
+
into smaller planner helpers.
|
|
28
|
+
- Orphaned digest-tag resolution now uses a direct latest-scan query instead of relying on older helper views.
|
|
29
|
+
- Manifest platform display now derives from descriptor data in the visualizer instead of relying on manifest-level
|
|
30
|
+
platform fields.
|
|
31
|
+
- Cross-architecture terminology is now consistently named `multi-arch` across runtime, tests, and docs.
|
|
32
|
+
- The root action and public CLI surface are now centered on `scan` and `cleanup` only.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- Fixed digest-tag helper-edge direction and root-detection behavior so helper-tagged artifacts no longer interfere with
|
|
37
|
+
normal cleanup root semantics.
|
|
38
|
+
- Fixed shared-graph cleanup handling so selected indexes and helper-linked artifacts are deleted or retained according
|
|
39
|
+
to surviving real tags instead of simplistic descendant-only closure rules.
|
|
40
|
+
|
|
41
|
+
### Removed
|
|
42
|
+
|
|
43
|
+
- The public `untag` CLI command, root-action mode, and dedicated direct-untag workflow coverage were removed. Internal
|
|
44
|
+
tag detachment for partial-tag cleanup matches remains part of `cleanup`.
|
|
45
|
+
- Several older cleanup helper views were removed after the planner rewrite moved the live logic into direct SQL query
|
|
46
|
+
paths.
|
|
47
|
+
|
|
48
|
+
## [0.9.7] - 2026-05-23
|
|
49
|
+
|
|
50
|
+
### Added
|
|
51
|
+
|
|
52
|
+
- The root action now prepares `cleanup` and `untag` CLI arguments through `tools/prepare-action-args.mjs`, keeping
|
|
53
|
+
printed and executed argument lists aligned.
|
|
54
|
+
- Cleanup planning now traverses recursively beyond `sha256-*` helper-tag manifest links as well, if deeper helper
|
|
55
|
+
chains ever occur.
|
|
56
|
+
|
|
57
|
+
### Changed
|
|
58
|
+
|
|
59
|
+
- Cleanup dry-run output and GitHub step summaries were reworked to explain the plan more clearly, including a filters
|
|
60
|
+
table and clearer counts for tags, images, and cross-arch manifests.
|
|
61
|
+
- Informational manifest classification was tuned so only real multi-arch roots are labeled `multi_arch_manifest`, while
|
|
62
|
+
helper-tagged indexes remain `index_manifest`.
|
|
63
|
+
- `merge-run-artifacts` now uses a simpler current-run download flow with direct artifact download handling.
|
|
64
|
+
- Cleanup selected-tag audit and DB-merge metadata handling were tightened alongside the summary/output refactor.
|
|
65
|
+
|
|
66
|
+
### Fixed
|
|
67
|
+
|
|
68
|
+
- `delete-orphaned-images` now carries orphaned `sha256-*` digest-tag targets through planner selection instead of
|
|
69
|
+
dropping them at the normal non-digest tag boundary.
|
|
70
|
+
- Fully deletable cleanup execution now deletes the planned closure package versions instead of deleting only the root
|
|
71
|
+
package version.
|
|
72
|
+
|
|
10
73
|
## [0.9.6] - 2026-05-21
|
|
11
74
|
|
|
12
75
|
### Added
|
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# ghcr-manager
|
|
2
2
|
|
|
3
|
-
[](https://github.com/gh-workflow/ghcr-manager/releases)
|
|
4
|
-
[](https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/immutable-releases)
|
|
5
3
|
[](https://github.com/marketplace/actions/ghcr-manager)
|
|
6
|
-
[](https://github.com/ghcr-manager/ghcr-manager/releases)
|
|
5
|
+
[](https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/immutable-releases)
|
|
6
|
+
[](https://github.com/ghcr-manager/ghcr-manager/actions/workflows/ci_change-validation.yml)
|
|
7
7
|
|
|
8
8
|
Inspect, review, and manage GitHub Container Registry packages.
|
|
9
9
|
|
|
@@ -12,7 +12,6 @@ Inspect, review, and manage GitHub Container Registry packages.
|
|
|
12
12
|
- scanning one GHCR package into a SQLite database artifact
|
|
13
13
|
- running cleanup with a GitHub step summary and optional DB artifact
|
|
14
14
|
- previewing cleanup decisions with `dry-run` before making changes
|
|
15
|
-
- directly removing selected tags with `untag`
|
|
16
15
|
|
|
17
16
|
## Quick Start
|
|
18
17
|
|
|
@@ -33,7 +32,7 @@ jobs:
|
|
|
33
32
|
|
|
34
33
|
- name: Preview GHCR cleanup
|
|
35
34
|
id: ghcr-manager
|
|
36
|
-
uses:
|
|
35
|
+
uses: ghcr-manager/ghcr-manager@0.9.8
|
|
37
36
|
with:
|
|
38
37
|
command: cleanup
|
|
39
38
|
token: ${{ github.token }}
|
|
@@ -55,16 +54,14 @@ After the run:
|
|
|
55
54
|
|
|
56
55
|
## Commands
|
|
57
56
|
|
|
58
|
-
The action supports
|
|
57
|
+
The action supports two commands:
|
|
59
58
|
|
|
60
59
|
- `cleanup`: Cleans using filters; use `dry-run` to preview the result
|
|
61
|
-
- `untag`: Removes one or more tags directly
|
|
62
60
|
- `scan`: Scans one package and uploads the resulting DB artifact
|
|
63
61
|
|
|
64
62
|
### Purpose of commands
|
|
65
63
|
|
|
66
64
|
- `cleanup`: Normal entry point for registry maintenance
|
|
67
|
-
- `untag`: Works directly without a full package scan
|
|
68
65
|
- `scan`: For investigation and audit
|
|
69
66
|
|
|
70
67
|
## Common Usage
|
|
@@ -72,7 +69,7 @@ The action supports three commands:
|
|
|
72
69
|
### Preview cleanup
|
|
73
70
|
|
|
74
71
|
```yaml
|
|
75
|
-
- uses:
|
|
72
|
+
- uses: ghcr-manager/ghcr-manager@0.9.8
|
|
76
73
|
with:
|
|
77
74
|
command: cleanup
|
|
78
75
|
token: ${{ github.token }}
|
|
@@ -82,7 +79,7 @@ The action supports three commands:
|
|
|
82
79
|
delete-tags: |
|
|
83
80
|
pr-.*
|
|
84
81
|
use-regex: true
|
|
85
|
-
older-than:
|
|
82
|
+
older-than: 30 days
|
|
86
83
|
keep-n-tagged: "5"
|
|
87
84
|
exclude-tags: |
|
|
88
85
|
latest
|
|
@@ -93,7 +90,7 @@ The action supports three commands:
|
|
|
93
90
|
### Apply cleanup
|
|
94
91
|
|
|
95
92
|
```yaml
|
|
96
|
-
- uses:
|
|
93
|
+
- uses: ghcr-manager/ghcr-manager@0.9.8
|
|
97
94
|
with:
|
|
98
95
|
command: cleanup
|
|
99
96
|
token: ${{ github.token }}
|
|
@@ -109,26 +106,10 @@ If `scan-after-cleanup` is `true`, `cleanup` performs a second scan so the uploa
|
|
|
109
106
|
|
|
110
107
|
Note: the second scan only runs if cleanup actually makes changes.
|
|
111
108
|
|
|
112
|
-
### Remove selected tags directly
|
|
113
|
-
|
|
114
|
-
```yaml
|
|
115
|
-
- uses: gh-workflow/ghcr-manager@0.9.6
|
|
116
|
-
with:
|
|
117
|
-
command: untag
|
|
118
|
-
token: ${{ github.token }}
|
|
119
|
-
owner: OWNER
|
|
120
|
-
package: PACKAGE
|
|
121
|
-
delete-tags: |
|
|
122
|
-
old-tag
|
|
123
|
-
test-tag
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
`untag` does not use a scan DB and does not support DB artifact upload.
|
|
127
|
-
|
|
128
109
|
### Scan one package
|
|
129
110
|
|
|
130
111
|
```yaml
|
|
131
|
-
- uses:
|
|
112
|
+
- uses: ghcr-manager/ghcr-manager@0.9.8
|
|
132
113
|
with:
|
|
133
114
|
command: scan
|
|
134
115
|
token: ${{ github.token }}
|
|
@@ -142,45 +123,48 @@ Note: the second scan only runs if cleanup actually makes changes.
|
|
|
142
123
|
|
|
143
124
|
<!-- markdownlint-disable MD013 MD060 -->
|
|
144
125
|
|
|
145
|
-
| Input | Description | Cmds | Required
|
|
146
|
-
| ---------------------------- | ----------------------------------- | ---- |
|
|
147
|
-
| `command` | `scan
|
|
148
|
-
| `token` | GitHub token for API calls | all | Yes
|
|
149
|
-
| `owner` | Package owner | all | Yes
|
|
150
|
-
| `package` | Package name | all | Yes
|
|
151
|
-
| `db-path` | Local SQLite DB path | s,c | No
|
|
152
|
-
| `upload-artifacts` | Upload DB and summary artifacts | s,c | No
|
|
153
|
-
| `scan-after-cleanup` | Run a second scan after cleanup | c | No
|
|
154
|
-
| `db-artifact-retention-days` | Override artifact retention days | s,c | No
|
|
155
|
-
| `delete-tags` | Newline-separated tags to delete | c
|
|
156
|
-
| `exclude-tags` | Newline-separated tags to exclude | c | No
|
|
157
|
-
| `keep-n-tagged` | Keep newest tagged roots | c | No
|
|
158
|
-
| `keep-n-untagged` | Keep newest untagged roots | c | No
|
|
159
|
-
| `delete-untagged` | Delete untagged roots | c | No
|
|
160
|
-
| `delete-ghost-images` | Delete ghost multi-arch roots | c | No
|
|
161
|
-
| `delete-partial-images` | Delete partial multi-arch roots | c | No
|
|
162
|
-
| `delete-orphaned-images` | Delete orphaned digest-derived tags | c | No
|
|
163
|
-
| `older-than` | Age cutoff for cleanup selectors | c | No
|
|
164
|
-
| `use-regex` | Use regex for cleanup tag selectors | c | No
|
|
165
|
-
| `dry-run` | Show changes without mutating GHCR | c
|
|
166
|
-
| `log-level` | CLI log level | all | No
|
|
126
|
+
| Input | Description | Cmds | Required | Default |
|
|
127
|
+
| ---------------------------- | ----------------------------------- | ---- | -------- | ------------------------------ |
|
|
128
|
+
| `command` | `scan` or `cleanup` | all | Yes | |
|
|
129
|
+
| `token` | GitHub token for API calls | all | Yes | `${{ github.token }}` |
|
|
130
|
+
| `owner` | Package owner | all | Yes | |
|
|
131
|
+
| `package` | Package name | all | Yes | |
|
|
132
|
+
| `db-path` | Local SQLite DB path | s,c | No | |
|
|
133
|
+
| `upload-artifacts` | Upload DB and summary artifacts | s,c | No | `false` |
|
|
134
|
+
| `scan-after-cleanup` | Run a second scan after cleanup | c | No | `false` |
|
|
135
|
+
| `db-artifact-retention-days` | Override artifact retention days | s,c | No | `${{ github.retention_days }}` |
|
|
136
|
+
| `delete-tags` | Newline-separated tags to delete | c | No | |
|
|
137
|
+
| `exclude-tags` | Newline-separated tags to exclude | c | No | |
|
|
138
|
+
| `keep-n-tagged` | Keep newest tagged roots | c | No | |
|
|
139
|
+
| `keep-n-untagged` | Keep newest untagged roots | c | No | |
|
|
140
|
+
| `delete-untagged` | Delete untagged roots | c | No | `false` |
|
|
141
|
+
| `delete-ghost-images` | Delete ghost multi-arch roots | c | No | `false` |
|
|
142
|
+
| `delete-partial-images` | Delete partial multi-arch roots | c | No | `false` |
|
|
143
|
+
| `delete-orphaned-images` | Delete orphaned digest-derived tags | c | No | `false` |
|
|
144
|
+
| `older-than` | Age cutoff for cleanup selectors | c | No | |
|
|
145
|
+
| `use-regex` | Use regex for cleanup tag selectors | c | No | `false` |
|
|
146
|
+
| `dry-run` | Show changes without mutating GHCR | c | No | `false` |
|
|
147
|
+
| `log-level` | CLI log level | all | No | `info` |
|
|
167
148
|
|
|
168
149
|
<!-- markdownlint-enable MD013 MD060 -->
|
|
169
150
|
|
|
170
|
-
`Cmds`: `s` = `scan`, `c` = `cleanup
|
|
151
|
+
`Cmds`: `s` = `scan`, `c` = `cleanup`
|
|
171
152
|
|
|
172
|
-
Cleanup notes:
|
|
153
|
+
Cleanup command notes:
|
|
173
154
|
|
|
174
155
|
- Tagged selector families may be combined with `delete-untagged`.
|
|
175
156
|
- `exclude-tags` requires at least one tagged selector family.
|
|
176
157
|
- `delete-untagged` and `keep-n-untagged` cannot be combined.
|
|
158
|
+
- `older-than` takes one integer plus one unit.
|
|
159
|
+
- Supported `older-than` units: `minutes`, `hours`, `days`, `weeks`, `months`, `years`.
|
|
160
|
+
- Example values: `30 days`, `2 hours`, `1 month`.
|
|
177
161
|
|
|
178
162
|
## Outputs
|
|
179
163
|
|
|
180
|
-
| Output | Description
|
|
181
|
-
| ------------------- |
|
|
182
|
-
| `db-path` | SQLite DB path on the runner
|
|
183
|
-
| `summary-json-path` | Summary JSON file path for `cleanup`
|
|
164
|
+
| Output | Description |
|
|
165
|
+
| ------------------- | ------------------------------------ |
|
|
166
|
+
| `db-path` | SQLite DB path on the runner |
|
|
167
|
+
| `summary-json-path` | Summary JSON file path for `cleanup` |
|
|
184
168
|
|
|
185
169
|
## Artifacts
|
|
186
170
|
|
|
@@ -188,7 +172,6 @@ When artifacts are enabled:
|
|
|
188
172
|
|
|
189
173
|
- `scan` always uploads one SQLite DB artifact
|
|
190
174
|
- `cleanup` optionally uploads the DB artifact and a cleanup summary JSON artifact
|
|
191
|
-
- `untag` uploads no artifacts
|
|
192
175
|
|
|
193
176
|
Current naming:
|
|
194
177
|
|
|
@@ -199,12 +182,11 @@ Current naming:
|
|
|
199
182
|
|
|
200
183
|
## Documentation Map
|
|
201
184
|
|
|
202
|
-
- [
|
|
203
|
-
- [
|
|
204
|
-
- [
|
|
205
|
-
- [
|
|
206
|
-
|
|
207
|
-
- [docs/cli-usage.md](docs/cli-usage.md): companion CLI usage
|
|
185
|
+
- [GitHub Action usage](docs/action-usage.md): action commands, including `cleanup` and `scan`
|
|
186
|
+
- [Visualizer](docs/visualizer.md): local graph inspection and scan-to-scan comparison
|
|
187
|
+
- [Multi-package workflows](docs/db-merge-workflows.md): cleaning up multiple packages with one combined DB
|
|
188
|
+
- [SQLite schema guide](docs/schema-description.md): practical explanation of the SQLite schema
|
|
189
|
+
- [CLI usage](docs/cli-usage.md): companion CLI usage
|
|
208
190
|
|
|
209
191
|
## Acknowledgment
|
|
210
192
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { ManifestKinds } from "../core/index.js";
|
|
2
|
+
import { DeletePlanValidationStatuses } from "../db/index.js";
|
|
1
3
|
const _DEFAULT_MAX_DIRECT_TARGET_TAGS = 100;
|
|
2
4
|
const _DEFAULT_MAX_ROOTS_PER_SECTION = 100;
|
|
3
|
-
const
|
|
5
|
+
const _DEFAULT_MAX_TAG_TEXT_LENGTH = 40;
|
|
4
6
|
export function renderCleanupSummaryMarkdown(summary, options) {
|
|
5
7
|
const maxDirectTargetTags = options.maxDirectTargetTags ?? _DEFAULT_MAX_DIRECT_TARGET_TAGS;
|
|
6
8
|
const maxRootsPerSection = options.maxRootsPerSection ?? _DEFAULT_MAX_ROOTS_PER_SECTION;
|
|
7
|
-
const maxTagsPerRoot = options.maxTagsPerRoot ?? _DEFAULT_MAX_TAGS_PER_ROOT;
|
|
8
9
|
const lines = [
|
|
9
10
|
"## Cleanup Summary",
|
|
10
11
|
"",
|
|
@@ -12,31 +13,62 @@ export function renderCleanupSummaryMarkdown(summary, options) {
|
|
|
12
13
|
"| --- | --- |",
|
|
13
14
|
`| 📦 Package | \`${_escapeInlineCode(`${summary.owner}/${summary.packageName}`)}\` |`,
|
|
14
15
|
`| ⚙️ Mode | ${summary.dryRun ? "Cleanup dry-run" : "Cleanup"} |`,
|
|
15
|
-
`| 🏷️
|
|
16
|
-
`|
|
|
17
|
-
`|
|
|
18
|
-
`|
|
|
19
|
-
`|
|
|
16
|
+
`| 🏷️ Selected tags | ${summary.directTargetTags.length} |`,
|
|
17
|
+
`| 🔖 Deleted tags | ${summary.changes.deletedTags} |`,
|
|
18
|
+
`| 🖼️ Deleted images | ${summary.changes.deletedImages} |`,
|
|
19
|
+
`| 📚 Deleted multi-arch manifests | ${summary.changes.deletedMultiArchManifests} |`,
|
|
20
|
+
`| 🧱 Deleted indexes | ${summary.changes.deletedIndexes} |`,
|
|
21
|
+
`| 📄 Deleted total | ${summary.changes.deletedTotal} |`,
|
|
22
|
+
`| 🔗 Tag-only updates | ${summary.untagOnlyRoots.length} |`,
|
|
23
|
+
`| 🛡️ Blocked items | ${summary.blockedRoots.length} |`,
|
|
20
24
|
""
|
|
21
25
|
];
|
|
22
|
-
lines.push(...
|
|
26
|
+
lines.push(..._renderPlannedDeleteBreakdown(summary));
|
|
27
|
+
lines.push(..._renderPlannerInputs(summary.plannerInputs));
|
|
23
28
|
lines.push(..._renderDirectTargetTags(summary.directTargetTags, maxDirectTargetTags));
|
|
24
|
-
lines.push(..._renderRootSection("🗑️
|
|
25
|
-
lines.push(..._renderRootSection("🔗
|
|
26
|
-
lines.push(..._renderRootSection("🛡️ Blocked
|
|
27
|
-
if (!summary.dryRun && (summary.
|
|
29
|
+
lines.push(..._renderRootSection("🗑️ Deleted items", summary.fullyDeletableRoots, maxRootsPerSection));
|
|
30
|
+
lines.push(..._renderRootSection("🔗 Tags removed only", summary.untagOnlyRoots, maxRootsPerSection));
|
|
31
|
+
lines.push(..._renderRootSection("🛡️ Blocked items", summary.blockedRoots, maxRootsPerSection));
|
|
32
|
+
if (!summary.dryRun && (summary.deletedPackageVersionCount > 0 || summary.detachedTagCount > 0)) {
|
|
28
33
|
lines.push(..._renderLiveEffects(summary));
|
|
29
34
|
}
|
|
30
35
|
return `${lines.join("\n").trimEnd()}\n`;
|
|
31
36
|
}
|
|
32
|
-
function
|
|
37
|
+
function _renderPlannedDeleteBreakdown(summary) {
|
|
38
|
+
const rows = [
|
|
39
|
+
{ label: "Images", count: summary.changes.deletedImages },
|
|
40
|
+
{ label: "Multi-arch manifests", count: summary.changes.deletedMultiArchManifests },
|
|
41
|
+
{ label: "Artifact manifests", count: summary.changes.deletedArtifactManifests },
|
|
42
|
+
{ label: "Signatures", count: summary.changes.deletedSignatures },
|
|
43
|
+
{ label: "Attestations", count: summary.changes.deletedAttestations },
|
|
44
|
+
{ label: "Generic indexes", count: summary.changes.deletedIndexes }
|
|
45
|
+
].filter((row) => row.count > 0);
|
|
46
|
+
if (rows.length === 0) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
33
49
|
return [
|
|
34
|
-
|
|
35
|
-
|
|
50
|
+
"<details>",
|
|
51
|
+
"<summary>📦 Deleted item breakdown</summary>",
|
|
36
52
|
"",
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
|
|
53
|
+
"| Type | Count |",
|
|
54
|
+
"| --- | --- |",
|
|
55
|
+
...rows.map((row) => `| ${row.label} | ${row.count} |`),
|
|
56
|
+
"",
|
|
57
|
+
"</details>",
|
|
58
|
+
""
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
function _renderPlannerInputs(plannerInputs) {
|
|
62
|
+
const rows = _getPlannerInputRows(plannerInputs);
|
|
63
|
+
const patternLines = _getPlannerPatternLines(plannerInputs);
|
|
64
|
+
return [
|
|
65
|
+
"<details>",
|
|
66
|
+
"<summary>⚙️ Cleanup filter</summary>",
|
|
67
|
+
"",
|
|
68
|
+
"| Filter | Value |",
|
|
69
|
+
"| --- | --- |",
|
|
70
|
+
...(rows.length > 0 ? rows : ["| (none) | No cleanup filters recorded |"]),
|
|
71
|
+
...(patternLines.length > 0 ? ["", ...patternLines] : []),
|
|
40
72
|
"",
|
|
41
73
|
"</details>",
|
|
42
74
|
""
|
|
@@ -47,23 +79,24 @@ function _renderDirectTargetTags(tags, maxDirectTargetTags) {
|
|
|
47
79
|
return [];
|
|
48
80
|
}
|
|
49
81
|
const visibleTags = tags.slice(0, maxDirectTargetTags).map((tag) => `- \`${_escapeInlineCode(tag)}\``);
|
|
50
|
-
const lines = ["<details>", "<summary>🏷️
|
|
82
|
+
const lines = ["<details>", "<summary>🏷️ Selected tags</summary>", "", ...visibleTags];
|
|
51
83
|
if (tags.length > maxDirectTargetTags) {
|
|
52
|
-
lines.push("", `_Showing first ${maxDirectTargetTags} of ${tags.length}
|
|
84
|
+
lines.push("", `_Showing first ${maxDirectTargetTags} of ${tags.length} selected tags._`);
|
|
53
85
|
}
|
|
54
86
|
lines.push("", "</details>", "");
|
|
55
87
|
return lines;
|
|
56
88
|
}
|
|
57
|
-
function _renderRootSection(title, roots, maxRootsPerSection
|
|
89
|
+
function _renderRootSection(title, roots, maxRootsPerSection) {
|
|
58
90
|
if (roots.length === 0) {
|
|
59
91
|
return [];
|
|
60
92
|
}
|
|
61
93
|
const lines = ["<details>", `<summary>${title}</summary>`, ""];
|
|
62
|
-
lines.push("| Version | Digest | Tags |
|
|
63
|
-
lines.push("| --- | --- | --- | --- |");
|
|
94
|
+
lines.push("| Version | Type | Digest | Tags | Outcome |");
|
|
95
|
+
lines.push("| --- | --- | --- | --- | --- |");
|
|
64
96
|
for (const root of roots.slice(0, maxRootsPerSection)) {
|
|
65
|
-
lines.push(`| ${root.versionId} | \`${_escapeInlineCode(_shortDigest(root.digest))}\` | ${_escapeMarkdown(_formatTags(root
|
|
97
|
+
lines.push(`| ${root.versionId} | ${_escapeMarkdown(_describeManifestKind(root.manifestKind))} | \`${_escapeInlineCode(_shortDigest(root.digest))}\` | ${_escapeMarkdown(_formatTags(root))} | ${_escapeMarkdown(_formatReason(root))} |`);
|
|
66
98
|
}
|
|
99
|
+
lines.push("", "_Tag lists may be truncated for table width._");
|
|
67
100
|
if (roots.length > maxRootsPerSection) {
|
|
68
101
|
lines.push("", `_Showing first ${maxRootsPerSection} of ${roots.length} ${title.toLowerCase()}._`);
|
|
69
102
|
}
|
|
@@ -72,33 +105,32 @@ function _renderRootSection(title, roots, maxRootsPerSection, maxTagsPerRoot) {
|
|
|
72
105
|
}
|
|
73
106
|
function _renderLiveEffects(summary) {
|
|
74
107
|
const lines = ["### Applied changes", ""];
|
|
75
|
-
lines.push(`- Deleted package versions: ${summary.
|
|
76
|
-
lines.push(`- Detached tags: ${summary.
|
|
77
|
-
if (summary.unsupportedUntagRoots.length > 0) {
|
|
78
|
-
lines.push(`- Unsupported untag roots: ${summary.unsupportedUntagRoots.length}`);
|
|
79
|
-
}
|
|
108
|
+
lines.push(`- Deleted package versions: ${summary.deletedPackageVersionCount}`);
|
|
109
|
+
lines.push(`- Detached tags: ${summary.detachedTagCount}`);
|
|
80
110
|
lines.push("");
|
|
81
111
|
return lines;
|
|
82
112
|
}
|
|
83
|
-
function _formatTags(root
|
|
113
|
+
function _formatTags(root) {
|
|
84
114
|
const tags = root.rootTags.length > 0 ? root.rootTags : root.matchedTags;
|
|
85
115
|
if (tags.length === 0) {
|
|
86
116
|
return "(untagged)";
|
|
87
117
|
}
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
118
|
+
const joinedTags = tags.join(", ");
|
|
119
|
+
if (joinedTags.length <= _DEFAULT_MAX_TAG_TEXT_LENGTH) {
|
|
120
|
+
return joinedTags;
|
|
121
|
+
}
|
|
122
|
+
return `${joinedTags.slice(0, _DEFAULT_MAX_TAG_TEXT_LENGTH - 3)}...`;
|
|
91
123
|
}
|
|
92
124
|
function _formatReason(root) {
|
|
93
|
-
if (root.validationStatus ===
|
|
94
|
-
const blocking = root.blockingDigest ? _shortDigest(root.blockingDigest) : "another
|
|
125
|
+
if (root.validationStatus === DeletePlanValidationStatuses.blocked) {
|
|
126
|
+
const blocking = root.blockingDigest ? _shortDigest(root.blockingDigest) : "another item";
|
|
95
127
|
const overlap = root.overlapDigest ? ` via ${_shortDigest(root.overlapDigest)}` : "";
|
|
96
|
-
return `Blocked by ${blocking}${overlap}`;
|
|
128
|
+
return `Blocked by retained item ${blocking}${overlap}`;
|
|
97
129
|
}
|
|
98
|
-
if (root.validationStatus ===
|
|
99
|
-
return "
|
|
130
|
+
if (root.validationStatus === DeletePlanValidationStatuses.untagOnly) {
|
|
131
|
+
return "Remove selected tags, keep item";
|
|
100
132
|
}
|
|
101
|
-
return "
|
|
133
|
+
return "Delete item and descendants";
|
|
102
134
|
}
|
|
103
135
|
function _shortDigest(value) {
|
|
104
136
|
if (!value.startsWith("sha256:") || value.length <= 20) {
|
|
@@ -112,3 +144,81 @@ function _escapeInlineCode(value) {
|
|
|
112
144
|
function _escapeMarkdown(value) {
|
|
113
145
|
return value.replaceAll("|", "\\|").replaceAll("\n", " ");
|
|
114
146
|
}
|
|
147
|
+
function _getPlannerInputRows(plannerInputs) {
|
|
148
|
+
const rows = [];
|
|
149
|
+
for (const [key, value] of Object.entries(plannerInputs)) {
|
|
150
|
+
rows.push(`| ${_escapeMarkdown(_plannerInputLabel(key))} | ${_escapeMarkdown(_formatPlannerInputValue(value))} |`);
|
|
151
|
+
}
|
|
152
|
+
return rows;
|
|
153
|
+
}
|
|
154
|
+
function _plannerInputLabel(key) {
|
|
155
|
+
switch (key) {
|
|
156
|
+
case "deleteTags":
|
|
157
|
+
return "Delete tags";
|
|
158
|
+
case "excludeTags":
|
|
159
|
+
return "Exclude tags";
|
|
160
|
+
case "useRegex":
|
|
161
|
+
return "Use regex";
|
|
162
|
+
case "deleteUntagged":
|
|
163
|
+
return "Delete untagged";
|
|
164
|
+
case "keepNTagged":
|
|
165
|
+
return "Keep newest tagged";
|
|
166
|
+
case "keepNUntagged":
|
|
167
|
+
return "Keep newest untagged";
|
|
168
|
+
case "olderThan":
|
|
169
|
+
return "Older than";
|
|
170
|
+
case "cutoffTimestamp":
|
|
171
|
+
return "Cutoff timestamp";
|
|
172
|
+
case "deleteGhostImages":
|
|
173
|
+
return "Delete ghost images";
|
|
174
|
+
case "deletePartialImages":
|
|
175
|
+
return "Delete partial images";
|
|
176
|
+
case "deleteOrphanedImages":
|
|
177
|
+
return "Delete orphaned images";
|
|
178
|
+
default:
|
|
179
|
+
return key;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function _formatPlannerInputValue(value) {
|
|
183
|
+
if (Array.isArray(value)) {
|
|
184
|
+
if (value.length === 0) {
|
|
185
|
+
return "(none)";
|
|
186
|
+
}
|
|
187
|
+
return value.length === 1 ? "1 pattern" : `${value.length} patterns`;
|
|
188
|
+
}
|
|
189
|
+
if (typeof value === "boolean") {
|
|
190
|
+
return value ? "yes" : "no";
|
|
191
|
+
}
|
|
192
|
+
return String(value);
|
|
193
|
+
}
|
|
194
|
+
function _getPlannerPatternLines(plannerInputs) {
|
|
195
|
+
const lines = [];
|
|
196
|
+
for (const [key, value] of Object.entries(plannerInputs)) {
|
|
197
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
lines.push(`- ${_plannerInputLabel(key)}:`);
|
|
201
|
+
for (const item of value) {
|
|
202
|
+
lines.push(` - \`${_escapeInlineCode(String(item))}\``);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return lines;
|
|
206
|
+
}
|
|
207
|
+
function _describeManifestKind(manifestKind) {
|
|
208
|
+
switch (manifestKind) {
|
|
209
|
+
case ManifestKinds.imageManifest:
|
|
210
|
+
return "image";
|
|
211
|
+
case ManifestKinds.multiArchManifest:
|
|
212
|
+
return "multi-arch";
|
|
213
|
+
case ManifestKinds.indexManifest:
|
|
214
|
+
return "index";
|
|
215
|
+
case ManifestKinds.signatureManifest:
|
|
216
|
+
return "signature";
|
|
217
|
+
case ManifestKinds.attestationManifest:
|
|
218
|
+
return "attestation";
|
|
219
|
+
case ManifestKinds.artifactManifest:
|
|
220
|
+
return "artifact";
|
|
221
|
+
default:
|
|
222
|
+
return "item";
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -1,23 +1,35 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ManifestKind } from "../core/index.js";
|
|
2
|
+
import type { DeletePlan, DeletePlanSelectionMode, DeletePlanSelectionReason, DeletePlanValidationReasonCode, DeletePlanValidationStatus } from "../db/index.js";
|
|
2
3
|
import type { DeleteExecutionSummary } from "../execute/index.js";
|
|
3
4
|
export interface CleanupSummaryRoot {
|
|
4
5
|
versionId: number;
|
|
5
6
|
digest: string;
|
|
6
|
-
manifestKind?:
|
|
7
|
+
manifestKind?: ManifestKind;
|
|
7
8
|
rootTags: string[];
|
|
8
9
|
matchedTags: string[];
|
|
9
10
|
selectionMode: DeletePlanSelectionMode;
|
|
10
11
|
selectionReason: DeletePlanSelectionReason;
|
|
11
|
-
validationStatus:
|
|
12
|
-
validationReasonCode:
|
|
12
|
+
validationStatus: DeletePlanValidationStatus;
|
|
13
|
+
validationReasonCode: DeletePlanValidationReasonCode;
|
|
13
14
|
validationReason: string;
|
|
14
15
|
blockingVersionId?: number;
|
|
15
16
|
blockingDigest?: string;
|
|
16
17
|
overlapDigest?: string;
|
|
17
|
-
overlapManifestKind?:
|
|
18
|
+
overlapManifestKind?: ManifestKind;
|
|
18
19
|
}
|
|
19
20
|
export interface CleanupSummaryAffectedManifest {
|
|
20
21
|
digest: string;
|
|
22
|
+
manifestKind?: ManifestKind;
|
|
23
|
+
}
|
|
24
|
+
export interface CleanupSummaryChanges {
|
|
25
|
+
deletedTags: number;
|
|
26
|
+
deletedImages: number;
|
|
27
|
+
deletedIndexes: number;
|
|
28
|
+
deletedMultiArchManifests: number;
|
|
29
|
+
deletedArtifactManifests: number;
|
|
30
|
+
deletedAttestations: number;
|
|
31
|
+
deletedSignatures: number;
|
|
32
|
+
deletedTotal: number;
|
|
21
33
|
}
|
|
22
34
|
export interface CleanupSummary {
|
|
23
35
|
command: "cleanup";
|
|
@@ -32,13 +44,13 @@ export interface CleanupSummary {
|
|
|
32
44
|
untagOnlyRoots: CleanupSummaryRoot[];
|
|
33
45
|
blockedRoots: CleanupSummaryRoot[];
|
|
34
46
|
affectedManifests: CleanupSummaryAffectedManifest[];
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
47
|
+
changes: CleanupSummaryChanges;
|
|
48
|
+
deletedPackageVersionCount: DeleteExecutionSummary["deletedPackageVersionCount"];
|
|
49
|
+
detachedTagCount: DeleteExecutionSummary["detachedTagCount"];
|
|
38
50
|
}
|
|
39
51
|
export declare function buildCleanupSummary(plan: DeletePlan, options: {
|
|
40
52
|
dryRun: boolean;
|
|
41
|
-
|
|
42
|
-
|
|
53
|
+
rootTagsByVersionId: ReadonlyMap<number, string[]>;
|
|
54
|
+
changes: CleanupSummaryChanges;
|
|
43
55
|
executionSummary?: DeleteExecutionSummary;
|
|
44
56
|
}): CleanupSummary;
|