Skip to content

Commit 1998aeb

Browse files
TMHSDigitalclaude
andcommitted
ci: count the Claude marketplace as shipped, and fail when the change probe errors
build() ships .claude-plugin/marketplace.json with dist-relative sources, but SHIPPED (--list-paths) and release_notes.py only knew the copied FILES, so a marketplace-only edit never cut the patch release that republishes plugin-dist. A new REWRITTEN tuple joins SHIPPED and the release-notes classifier, and a test builds the dist and fails on any file it ships that SHIPPED does not list (falsified: dropping REWRITTEN fails it). release.yml also read plugin-content-changed.sh's exit 2 (path list unreadable) as 'no change' inside an elif; it now captures the code and fails the job. Closes #535 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpkeXRvikhJZSVGBFM91Dv Signed-off-by: TM Hospitality Strategies <TMHospitalityStrategies@gmail.com>
1 parent d87acd3 commit 1998aeb

4 files changed

Lines changed: 41 additions & 4 deletions

File tree

‎.github/scripts/release_notes.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from pathlib import Path
2929

3030
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent / "scripts"))
31-
from build_plugin_dist import DIRS, FILES # noqa: E402 (the one list of shipped paths)
31+
from build_plugin_dist import DIRS, FILES, REWRITTEN # noqa: E402 (the one list of shipped paths)
3232

3333
REPO_URL = "https://github.com/TMHSDigital/Blender-Developer-Tools"
3434
GROUPS = (
@@ -40,7 +40,7 @@
4040
# change here is a change a plugin user receives, including the manifests and
4141
# the opt-in live-session plugin.
4242
USER_DIRS = tuple(d + "/" for d in DIRS)
43-
USER_FILES = frozenset(FILES)
43+
USER_FILES = frozenset(FILES + REWRITTEN)
4444
NO_USER_CHANGES = ("No changes to the plugin content (skills, rules, snippets, templates, "
4545
"manifests, MCP config) in this release.")
4646

‎.github/workflows/release.yml‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,28 @@ jobs:
7676
# .github/scripts/bump-kind.sh owns the patterns (pages.yml shares it).
7777
# [skip ci] (guarded on the job) remains an optional belt-and-suspenders override.
7878
kind=$(git log "$range" --format='%B%x00' | bash .github/scripts/bump-kind.sh)
79+
# 0 = plugin content changed, 1 = it did not, anything else = the
80+
# script could not read the shipped-path list. Inside an `elif` the
81+
# error would read as "no change" and skip the release silently (#535).
82+
plugin_changed=1
83+
if [ -n "$last_tag" ]; then
84+
set +e
85+
bash .github/scripts/plugin-content-changed.sh "$last_tag"
86+
plugin_changed=$?
87+
set -e
88+
if [ "$plugin_changed" -gt 1 ]; then
89+
echo "::error::plugin-content-changed.sh failed (exit $plugin_changed); cannot decide whether to release"
90+
exit 1
91+
fi
92+
fi
7993
bump="patch"
8094
release="false"
8195
if [ -z "$last_tag" ]; then
8296
# Initial release: no prior tag -> cut the first release at the current VERSION.
8397
release="true"
8498
elif [ "$kind" != "none" ]; then
8599
bump="$kind"; release="true"
86-
elif bash .github/scripts/plugin-content-changed.sh "$last_tag"; then
100+
elif [ "$plugin_changed" -eq 0 ]; then
87101
# No feat/fix subject, but skills/rules/snippets/templates/claude
88102
# changed: ship a patch so a docs: correction still reaches plugin
89103
# users instead of waiting for the next feat/fix (#350).

‎scripts/build_plugin_dist.py‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,16 @@
4545
".codex-plugin/plugin.json", "assets/favicon.svg")
4646
# marketplace.json entry -> source inside the dist tree.
4747
DIST_SOURCES = {"blender-developer-tools": "./", "blender-live-session": "./live-session"}
48+
# Repo files the dist ships after rewriting rather than copying: build() writes
49+
# the Claude marketplace with dist-relative sources. They are not in FILES (a
50+
# plain copy would ship the repo's sources) but they are still shipped (#535).
51+
REWRITTEN = (".claude-plugin/marketplace.json",)
4852
# Every repo path the plugin ships. release.yml's plugin-content-changed.sh and
4953
# release_notes.py read this (--list-paths), so a change to any shipped file,
5054
# manifests and mcp.json included, counts as a plugin change (#494).
51-
SHIPPED = DIRS + FILES
55+
SHIPPED = DIRS + FILES + REWRITTEN
56+
# Files build() generates with no repo source (the dist README).
57+
GENERATED = ("README.md",)
5258
MAX_BYTES = 2_000_000
5359
IGNORE = shutil.ignore_patterns("__pycache__", "*.pyc")
5460

‎tests/test_build_plugin_dist.py‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,23 @@ def test_refuses_git_checkout_even_with_force(self):
5353
self.assertIn(".git", err)
5454
self.assertTrue((Path(td) / ".git").is_dir())
5555

56+
def test_every_built_file_is_listed_as_shipped(self):
57+
# --list-paths decides whether a release ships plugin changes; a file
58+
# the dist carries but SHIPPED omits never triggers one (#535).
59+
with tempfile.TemporaryDirectory() as td:
60+
out = Path(td) / "dist"
61+
code, _ = run(["--out", str(out)])
62+
self.assertEqual(code, 0)
63+
dirs = tuple(d + "/" for d in b.DIRS)
64+
unlisted = [
65+
rel for rel in (p.relative_to(out).as_posix()
66+
for p in out.rglob("*") if p.is_file())
67+
if not rel.startswith(dirs) and rel not in b.SHIPPED
68+
and rel not in b.GENERATED
69+
]
70+
self.assertEqual(unlisted, [])
71+
self.assertIn(".claude-plugin/marketplace.json", b.SHIPPED)
72+
5673
def test_builds_into_missing_dir(self):
5774
with tempfile.TemporaryDirectory() as td:
5875
out = Path(td) / "dist"

0 commit comments

Comments
 (0)