Skip to content

ci: publish changed package versions automatically - #3

Merged
qiyinxi merged 1 commit into
mainfrom
ci/auto-publish-version-change
Aug 2, 2026
Merged

qiyinxi merged 1 commit into
mainfrom
ci/auto-publish-version-change

Conversation

@qiyinxi

@qiyinxi qiyinxi commented Aug 2, 2026 •

Copy link
Copy Markdown
Member

Summary

  • publish distributions automatically when their pyproject version changes on main
  • build all selected packages once and upload them in core → SRA → M7A → meta order
  • retain manual/tag recovery and make partial reruns safe
  • document the release side effects and package mapping in AGENTS.md

Validation

  • full unittest suite
  • four-distribution build
  • release-plan and GitHub-output contract tests

No package version file is changed by this PR, so merging it does not publish a release.

Summary by Sourcery

在 main 分支上,当四个发行包的 pyproject 版本发生变化时,自动发布到 PyPI,并在保持手动/基于标签的恢复路径的同时,强制执行依赖安全的发布顺序。

New Features:

  • 当任意一个四个包的 pyproject.toml 文件在 main 上发生变化时触发发布工作流,自动选择相较于上一提交版本号发生变化的发行包。
  • 引入发布规划机制,检测跨包的版本变化,并按照固定顺序安排发布:core → SRA adapter → M7A adapter → meta。
  • 为 automas-script-hsr、SRA adapter、M7A adapter 和 automas-hsr 添加按包划分的发布任务和环境,使用共享构建任务生成的工件以及 PyPI Trusted Publishing。

Bug Fixes:

  • 通过要求相对上一条 main 提交必须有版本变更并跳过未变化的发行包,防止非预期发布。
  • 使部分重跑安全:允许发布任务在保留“新发布必须升级版本”要求的同时跳过已存在的工件。

Enhancements:

  • 优化发布工作流的并发控制,使 PyPI 发布在全局范围内串行执行。
  • 重构构建任务,只运行一次测试,并仅构建选中的发行包,同时为每个包执行元数据检查和冒烟测试。
  • 扩展 release.py,以便为手动/标签触发和自动 main 推送两种情况计算发布计划,并输出结构化的 GitHub 输出,描述将发布哪些包。

Documentation:

  • 在 AGENTS.md 中记录基于版本变化的自动发布、发行包到 pyproject 文件的映射以及发布顺序规则。
  • 更新发布文档,描述自动和恢复发布的环境使用方式、四个项目的 PyPI Trusted Publishing 配置,以及自动发布与手动/标签触发发布的行为差异。

Tests:

  • 添加单元测试,覆盖发布计划解析、版本未变更时的行为、按依赖顺序选择发布包,以及为所选包生成 GitHub 输出等场景。
Original summary in English

Summary by Sourcery

Automate PyPI publishing for the four distributions on main when their pyproject versions change, and enforce a dependency-safe release order while preserving manual/tag-based recovery paths.

New Features:

  • Trigger the publish workflow on main when any of the four package pyproject.toml files change, automatically selecting distributions whose versions differ from the previous commit.
  • Introduce a release planning mechanism that detects version changes across packages and schedules them in a fixed core → SRA adapter → M7A adapter → meta publication order.
  • Add per-package publish jobs and environments for automas-script-hsr, SRA adapter, M7A adapter, and automas-hsr, using artifacts from a shared build job and PyPI Trusted Publishing.

Bug Fixes:

  • Prevent unintended releases by requiring version changes relative to the previous main commit and skipping unchanged distributions.
  • Make partial reruns safe by allowing publish jobs to skip already-existing artifacts while preserving the requirement to bump versions for new releases.

Enhancements:

  • Refine the publish workflow concurrency to serialize PyPI releases globally.
  • Restructure the build job to run tests once and build only the selected distributions, with metadata checks and smoke tests per package.
  • Extend release.py to compute release plans for both manual/tag and automatic main pushes, and emit structured GitHub outputs describing which packages will be published.

Documentation:

  • Document automatic version-change-based publishing, distribution mapping to pyproject files, and release sequencing rules in AGENTS.md.
  • Update release documentation to describe environment usage for automatic and recovery publishing, PyPI Trusted Publishing setup for all four projects, and the behavior of automatic vs manual/tag-triggered releases.

Tests:

  • Add unit tests covering release plan resolution, unchanged-version behavior, dependency-ordered selection, and GitHub output generation for selected packages.

@sourcery-ai

sourcery-ai Bot commented Aug 2, 2026 •

Copy link
Copy Markdown

审阅者指南(Reviewer's Guide)

在 main 分支上当包版本变更时引入自动 PyPI 发布;重构发布规划以支持按依赖顺序的多发行版;并文档化新的发布行为和环境。

文件级变更

Change Details Files
在 main 上当发行版版本变化时启用自动发布工作流触发,并简化工作流并发控制。
  • 在 main 上为 push 分支和 pyproject.toml 路径添加过滤器,以便在包版本发生变化时触发发布
  • 将并发组从按 ref/包 维度改为单一的 pypi-publish 组,以串行化所有发布
.github/workflows/publish.yml
重构构建任务以计算多包发布计划,将测试/构建以是否存在目标为门槛,并针对每个发行版分别构建/上传制品。
  • 用 resolve_release_plan 替换单目标发布解析,该函数可基于事件类型和 Git 历史返回多个 ReleaseTarget 对象
  • 新增 project_version_at 以从特定 Git 修订版本读取先前版本,并用其在 main push 上检测版本变更
  • 新增 resolve_changed_releases 以仅选择版本发生变化的发行版,并遵守固定的 RELEASE_ORDER 和 PACKAGE_OUTPUT_KEYS 映射
  • 将 GitHub 输出改为按发行版的 selected/version/artifact 标志,加上 has_targets/packages 汇总,并相应调整 main() 的打印
  • 更新构建任务,仅在 has_targets 为 true 时运行测试,并分别对 script、SRA adapter、M7A adapter 和 meta 发行版进行构建、twine-check、冒烟测试以及上传制品
scripts/release.py
.github/workflows/publish.yml
将发布拆分为四个特定环境的任务,带有依赖顺序和安全的重跑语义。
  • 创建 publish_script、publish_sra、publish_m7a 和 publish_meta 任务,它们各自下载其制品并通过 gh-action-pypi-publish 发布
  • 使用 needs 和 always() 条件,以及结果检查加上 *_selected 输出,来强制执行 core → SRA → M7A → meta 的顺序,并允许局部重跑而不会重复发布
  • 为每个任务分配固定的 GitHub 环境(pypi-script-hsr、pypi-adapter-sra、pypi-adapter-m7a、pypi-hsr),并在发布时启用 skip-existing
.github/workflows/publish.yml
扩展发布规划单元测试,以覆盖多目标解析和 GitHub 输出协议。
  • 新增 resolve_changed_releases 的测试,用于验证其处理未变更版本、依赖顺序以及单包变更的行为
  • 新增 _write_github_outputs 的测试,以确保仅选中的包被标记、has_targets 正确,以及制品命名包含预期包名
  • 在测试模块中导入新的符号 RELEASE_ORDER、_write_github_outputs 和 resolve_changed_releases
tests/test_release.py
记录基于版本的自动发布、包到版本文件的映射以及环境/发布协议。
  • 在 AGENTS.md 中新增章节,描述 main 上的版本变更如何为四个发行版自动触发测试、构建和发布,以及代理在版本号提升和恢复路径上必须遵守的规则
  • 更新 docs/release.md,描述四个 PyPI Trusted Publishing 发布者、自动版本变更发布流程、安全重跑以及包含环境名称约束的手动/打标签恢复语义
AGENTS.md
docs/release.md

提示和命令

与 Sourcery 交互

  • 触发新审阅: 在 Pull Request 上评论 @sourcery-ai review。
  • 继续讨论: 直接回复 Sourcery 的审阅评论。
  • 从审阅评论生成 GitHub Issue: 通过回复审阅评论,请求 Sourcery 根据该评论创建一个 issue。你也可以在某条审阅评论下回复 @sourcery-ai issue 来从该评论创建一个 issue。
  • 生成 Pull Request 标题: 在 Pull Request 标题的任意位置写上 @sourcery-ai 以在任何时候生成标题。你也可以在 Pull Request 上评论 @sourcery-ai title 以(重新)生成标题。
  • 生成 Pull Request 摘要: 在 Pull Request 正文的任意位置写上 @sourcery-ai summary,即可在你想要的位置随时生成 PR 摘要。你也可以在 Pull Request 上评论 @sourcery-ai summary 以(重新)生成摘要。
  • 生成审阅者指南: 在 Pull Request 上评论 @sourcery-ai guide 以随时(重新)生成审阅者指南。
  • 解决所有 Sourcery 评论: 在 Pull Request 上评论 @sourcery-ai resolve 以解决所有 Sourcery 评论。如果你已经处理了所有评论且不想再看到它们,这会很有用。
  • 忽略所有 Sourcery 审阅: 在 Pull Request 上评论 @sourcery-ai dismiss 以忽略所有现有的 Sourcery 审阅。尤其在你想从头开始一次新审阅时很有用——别忘了再评论 @sourcery-ai review 来触发新审阅!

自定义你的体验

访问你的 控制面板 以:

  • 启用或禁用审阅功能,例如 Sourcery 生成的 Pull Request 摘要、审阅者指南等。
  • 更改审阅语言。
  • 添加、删除或编辑自定义审阅指令。
  • 调整其他审阅设置。

获取帮助

Original review guide in English

Reviewer's Guide

Introduce automatic PyPI publishing on main when package versions change, refactor release planning to support multiple distributions in dependency order, and document the new release behavior and environments.

File-Level Changes

Change Details Files
Enable automatic publish workflow triggers on main when distribution versions change and simplify workflow concurrency.
  • Add push branch and pyproject.toml path filters on main to trigger releases when package versions change
  • Change concurrency group from per-ref/package to a single pypi-publish group to serialize all releases
.github/workflows/publish.yml
Refactor the build job to compute a multi-package release plan, gate tests/builds on having targets, and build/upload artifacts per distribution.
  • Replace single-target release resolution with resolve_release_plan that can return multiple ReleaseTarget objects based on event type and Git history
  • Add project_version_at to read previous versions from a specific Git revision and use it to detect version changes on main pushes
  • Add resolve_changed_releases to select only distributions whose versions changed, respecting a fixed RELEASE_ORDER and PACKAGE_OUTPUT_KEYS mapping
  • Change GitHub outputs to per-distribution selected/version/artifact flags plus has_targets/packages summary, and adjust main() printing accordingly
  • Update the build job to run tests only when has_targets is true and to build, twine-check, smoke-test, and upload artifacts separately for script, SRA adapter, M7A adapter, and meta distributions
scripts/release.py
.github/workflows/publish.yml
Split publishing into four environment-specific jobs with dependency ordering and safe rerun semantics.
  • Create publish_script, publish_sra, publish_m7a, and publish_meta jobs that each download their artifacts and publish via gh-action-pypi-publish
  • Use needs and always() conditions with result checks plus *_selected outputs to enforce core → SRA → M7A → meta ordering and allow partial reruns without double-publishing
  • Assign fixed GitHub environments (pypi-script-hsr, pypi-adapter-sra, pypi-adapter-m7a, pypi-hsr) per job and enable skip-existing in publishing
.github/workflows/publish.yml
Extend release planning unit tests to cover multi-target resolution and GitHub output contract.
  • Add tests for resolve_changed_releases handling unchanged versions, dependency order, and single-package changes
  • Add tests for _write_github_outputs to ensure only selected packages are marked, has_targets is correct, and artifact naming includes the expected package
  • Import new symbols RELEASE_ORDER, _write_github_outputs, and resolve_changed_releases into the test module
tests/test_release.py
Document automatic version-based publishing, package-to-version-file mapping, and environment/publishing contracts.
  • Add AGENTS.md section describing how version changes on main trigger automatic testing, building, and publishing for the four distributions and rules agents must follow around version bumps and recovery paths
  • Update docs/release.md to describe four PyPI Trusted Publishing publishers, automatic version-change publishing flow, safe reruns, and manual/tag recovery semantics including environment name constraints
AGENTS.md
docs/release.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 3 个问题,并留下了一些更高层次的反馈:

  • 新的发布作业中包含了相当多重复的逻辑(构建、twine 检查、冒烟测试、制品上传);建议将这些步骤抽取到一个可复用的 workflow 或按包参数化的 composite action 中,以减少重复并降低未来改动出错的概率。
  • 现在固定的并发组 pypi-publish 会将所有来自标签和 main 分支的发布串行化;如果不同发布之间可以接受并行执行,你可能希望按包或按 ref 来限定并发组的 key,以避免不必要的排队。
  • publish_sra、publish_m7a 和 publish_meta 的串联 needs/if 条件相当复杂;可以考虑将依赖逻辑封装到一个辅助表达式中(例如使用 workflow 级别的 env 或单一的可复用条件),这样可以让发布顺序更易于理解和维护。
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new publish jobs have quite a bit of duplicated logic (build, twine check, smoke test, artifact upload); consider extracting these into a reusable workflow or composite action parametrized by package to reduce repetition and make future changes less error-prone.
- The fixed concurrency group `pypi-publish` now serializes all releases across tags and main; if parallelism between unrelated releases is acceptable, you may want to scope the group key by package or ref to avoid unnecessary queuing.
- The chained `needs`/`if` conditions for `publish_sra`, `publish_m7a`, and `publish_meta` are fairly complex; encapsulating the dependency logic in a helper expression (e.g., via workflow-level `env` or a single reusable condition) would make the publication order easier to reason about and maintain.

## Individual Comments

### Comment 1
<location path="tests/test_release.py" line_range="58-62" />
<code_context>
                 ref="refs/tags/v0.1.0",
             )
+
+    def test_unchanged_versions_do_not_publish(self) -> None:
+        previous = {package: self._version(package) for package in PACKAGE_CONFIG}
+        self.assertEqual(resolve_changed_releases(previous), ())
+
+    def test_changed_versions_follow_dependency_order(self) -> None:
</code_context>
<issue_to_address>
**suggestion (testing):** Add tests for `resolve_changed_releases` handling newly added packages (missing previous version entries).

`resolve_changed_releases` is only tested with a fully populated `previous` mapping. Because it uses `previous_versions.get(package)`, new packages without an entry should be treated as `None` and published. Please add a test where one package is omitted from `previous`, verify that it’s selected for release, and confirm the dependency order is preserved. This will help prevent regressions in how missing entries are handled.

```suggestion
    def test_unchanged_versions_do_not_publish(self) -> None:
        previous = {package: self._version(package) for package in PACKAGE_CONFIG}
        self.assertEqual(resolve_changed_releases(previous), ())

    def test_new_package_without_previous_version_is_published(self) -> None:
        # Omit one package from the previous mapping to simulate a newly added package
        new_package = next(iter(PACKAGE_CONFIG))
        previous = {
            package: self._version(package)
            for package in PACKAGE_CONFIG
            if package != new_package
        }

        targets = resolve_changed_releases(previous)
        target_packages = tuple(target.package for target in targets)

        # The new package (missing in previous) should be selected for release
        self.assertIn(new_package, target_packages)

        # The result order should follow RELEASE_ORDER, restricted to the selected packages
        expected_order = tuple(
            package for package in RELEASE_ORDER if package == new_package
        )
        self.assertEqual(target_packages, expected_order)

    def test_changed_versions_follow_dependency_order(self) -> None:
```
</issue_to_address>

### Comment 2
<location path="tests/test_release.py" line_range="76-85" />
<code_context>
+    def test_github_outputs_mark_only_selected_packages(self) -> None:
</code_context>
<issue_to_address>
**suggestion (testing):** Add coverage for `_write_github_outputs` when no targets are selected and for multiple selected targets.

The existing test only covers the case where a single package (`m7a`) is selected. Please add: (1) a test with `targets` empty, asserting `has_targets == "false"`, `packages` is empty, all `*_selected` flags are `false`, and `*_version`/`*_artifact` fields are empty; and (2) a test with multiple selected packages, asserting `packages` includes all selected packages in order and each package’s `*_selected`, `*_version`, and `*_artifact` values are set correctly. This will more completely validate the GitHub outputs the workflow relies on.

Suggested implementation:

```python
    def test_github_outputs_mark_only_selected_packages(self) -> None:
        previous = {package: self._version(package) for package in PACKAGE_CONFIG}
        previous["automas-hsr-adapter-m7a"] = "previous-version"
        targets = resolve_changed_releases(previous)

        with TemporaryDirectory() as temp_dir:
            output_path = Path(temp_dir) / "github-output.txt"
            _write_github_outputs(targets, output_path)
            values = dict(
                line.split("=", 1)
                for line in output_path.read_text(encoding="utf-8").splitlines()
            )

        # has_targets and packages reflect the single selected package
        self.assertEqual(values["has_targets"], "true")
        self.assertEqual(values["packages"], "automas-hsr-adapter-m7a")

        # Only the selected package is marked as selected; all others are false
        for package in PACKAGE_CONFIG:
            key_prefix = package.replace("-", "_")
            if package == "automas-hsr-adapter-m7a":
                self.assertEqual(values[f"{key_prefix}_selected"], "true")
                self.assertNotEqual(values[f"{key_prefix}_version"], "")
                self.assertNotEqual(values[f"{key_prefix}_artifact"], "")
            else:
                self.assertEqual(values[f"{key_prefix}_selected"], "false")
                self.assertEqual(values[f"{key_prefix}_version"], "")
                self.assertEqual(values[f"{key_prefix}_artifact"], "")

    def test_github_outputs_with_no_selected_targets(self) -> None:
        previous = {package: self._version(package) for package in PACKAGE_CONFIG}
        # Ensure there are no changed releases
        targets = resolve_changed_releases(previous)
        self.assertEqual(targets, ())

        with TemporaryDirectory() as temp_dir:
            output_path = Path(temp_dir) / "github-output.txt"
            _write_github_outputs(targets, output_path)
            values = dict(
                line.split("=", 1)
                for line in output_path.read_text(encoding="utf-8").splitlines()
            )

        # No targets selected
        self.assertEqual(values["has_targets"], "false")
        self.assertEqual(values["packages"], "")

        # All packages are unselected and have empty version/artifact fields
        for package in PACKAGE_CONFIG:
            key_prefix = package.replace("-", "_")
            self.assertEqual(values[f"{key_prefix}_selected"], "false")
            self.assertEqual(values[f"{key_prefix}_version"], "")
            self.assertEqual(values[f"{key_prefix}_artifact"], "")

    def test_github_outputs_with_multiple_selected_targets(self) -> None:
        # Pick two packages from PACKAGE_CONFIG to mark as changed
        packages = list(PACKAGE_CONFIG.keys())
        # Guard against pathological configs
        self.assertGreaterEqual(len(packages), 2)
        first, second = packages[0], packages[1]

        previous = {package: self._version(package) for package in PACKAGE_CONFIG}
        previous[first] = "previous-version-1"
        previous[second] = "previous-version-2"

        targets = resolve_changed_releases(previous)

        with TemporaryDirectory() as temp_dir:
            output_path = Path(temp_dir) / "github-output.txt"
            _write_github_outputs(targets, output_path)
            values = dict(
                line.split("=", 1)
                for line in output_path.read_text(encoding="utf-8").splitlines()
            )

        selected_packages = tuple(target.package for target in targets)
        self.assertGreaterEqual(len(selected_packages), 2)

        # The packages output lists all selected packages in order
        self.assertEqual(
            values["packages"],
            ",".join(selected_packages),
        )
        self.assertEqual(values["has_targets"], "true")

        # Each selected package has *_selected true and non-empty version/artifact
        selected_set = set(selected_packages)
        for package in PACKAGE_CONFIG:
            key_prefix = package.replace("-", "_")
            if package in selected_set:
                self.assertEqual(values[f"{key_prefix}_selected"], "true")
                self.assertNotEqual(values[f"{key_prefix}_version"], "")
                self.assertNotEqual(values[f"{key_prefix}_artifact"], "")
            else:
                self.assertEqual(values[f"{key_prefix}_selected"], "false")
                self.assertEqual(values[f"{key_prefix}_version"], "")
                self.assertEqual(values[f"{key_prefix}_artifact"], "")

```

These changes assume `_write_github_outputs` uses the following conventions:
1. A `has_targets` output of `"true"`/`"false"`.
2. A `packages` output that is a comma-separated list of package names matching `target.package`.
3. Per-package outputs named using `package.replace("-", "_")` as a prefix, e.g. `automas_hsr_adapter_m7a_selected`, `automas_hsr_adapter_m7a_version`, and `automas_hsr_adapter_m7a_artifact`, with `"true"`/`"false"` for `*_selected` and empty strings for unselected packages' `*_version`/`*_artifact`.

If your actual output naming or formatting differs (for example, if you use short names like `m7a_selected` or a different separator than a comma for `packages`), please adjust the key constructions and assertions accordingly to match the real `_write_github_outputs` behavior.
</issue_to_address>

### Comment 3
<location path="tests/test_release.py" line_range="7-13" />
<code_context>
+from tempfile import TemporaryDirectory

-from scripts.release import PACKAGE_CONFIG, project_version, resolve_release
+from scripts.release import (
+    PACKAGE_CONFIG,
+    RELEASE_ORDER,
+    _write_github_outputs,
+    project_version,
+    resolve_changed_releases,
+    resolve_release,
+)

</code_context>
<issue_to_address>
**issue (testing):** Consider adding tests for the new `resolve_release_plan` logic, especially error paths and automatic main releases.

Right now `resolve_release_plan` (automatic main-branch flow with `before_sha` plus manual/tag releases) isn’t covered by dedicated tests. Please add tests that:
1) Confirm `workflow_dispatch` and tag refs delegate to `resolve_release`.
2) Assert unsupported `event_name`/`ref` combinations raise the documented `ValueError`.
3) Check that missing or blank `before_sha` for `push` on `refs/heads/main` raises the correct error.
These will validate the new entry point and guard against misconfiguration.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Original comment in English

Hey - I've found 3 issues, and left some high level feedback:

  • The new publish jobs have quite a bit of duplicated logic (build, twine check, smoke test, artifact upload); consider extracting these into a reusable workflow or composite action parametrized by package to reduce repetition and make future changes less error-prone.
  • The fixed concurrency group pypi-publish now serializes all releases across tags and main; if parallelism between unrelated releases is acceptable, you may want to scope the group key by package or ref to avoid unnecessary queuing.
  • The chained needs/if conditions for publish_sra, publish_m7a, and publish_meta are fairly complex; encapsulating the dependency logic in a helper expression (e.g., via workflow-level env or a single reusable condition) would make the publication order easier to reason about and maintain.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new publish jobs have quite a bit of duplicated logic (build, twine check, smoke test, artifact upload); consider extracting these into a reusable workflow or composite action parametrized by package to reduce repetition and make future changes less error-prone.
- The fixed concurrency group `pypi-publish` now serializes all releases across tags and main; if parallelism between unrelated releases is acceptable, you may want to scope the group key by package or ref to avoid unnecessary queuing.
- The chained `needs`/`if` conditions for `publish_sra`, `publish_m7a`, and `publish_meta` are fairly complex; encapsulating the dependency logic in a helper expression (e.g., via workflow-level `env` or a single reusable condition) would make the publication order easier to reason about and maintain.

## Individual Comments

### Comment 1
<location path="tests/test_release.py" line_range="58-62" />
<code_context>
                 ref="refs/tags/v0.1.0",
             )
+
+    def test_unchanged_versions_do_not_publish(self) -> None:
+        previous = {package: self._version(package) for package in PACKAGE_CONFIG}
+        self.assertEqual(resolve_changed_releases(previous), ())
+
+    def test_changed_versions_follow_dependency_order(self) -> None:
</code_context>
<issue_to_address>
**suggestion (testing):** Add tests for `resolve_changed_releases` handling newly added packages (missing previous version entries).

`resolve_changed_releases` is only tested with a fully populated `previous` mapping. Because it uses `previous_versions.get(package)`, new packages without an entry should be treated as `None` and published. Please add a test where one package is omitted from `previous`, verify that it’s selected for release, and confirm the dependency order is preserved. This will help prevent regressions in how missing entries are handled.

```suggestion
    def test_unchanged_versions_do_not_publish(self) -> None:
        previous = {package: self._version(package) for package in PACKAGE_CONFIG}
        self.assertEqual(resolve_changed_releases(previous), ())

    def test_new_package_without_previous_version_is_published(self) -> None:
        # Omit one package from the previous mapping to simulate a newly added package
        new_package = next(iter(PACKAGE_CONFIG))
        previous = {
            package: self._version(package)
            for package in PACKAGE_CONFIG
            if package != new_package
        }

        targets = resolve_changed_releases(previous)
        target_packages = tuple(target.package for target in targets)

        # The new package (missing in previous) should be selected for release
        self.assertIn(new_package, target_packages)

        # The result order should follow RELEASE_ORDER, restricted to the selected packages
        expected_order = tuple(
            package for package in RELEASE_ORDER if package == new_package
        )
        self.assertEqual(target_packages, expected_order)

    def test_changed_versions_follow_dependency_order(self) -> None:
```
</issue_to_address>

### Comment 2
<location path="tests/test_release.py" line_range="76-85" />
<code_context>
+    def test_github_outputs_mark_only_selected_packages(self) -> None:
</code_context>
<issue_to_address>
**suggestion (testing):** Add coverage for `_write_github_outputs` when no targets are selected and for multiple selected targets.

The existing test only covers the case where a single package (`m7a`) is selected. Please add: (1) a test with `targets` empty, asserting `has_targets == "false"`, `packages` is empty, all `*_selected` flags are `false`, and `*_version`/`*_artifact` fields are empty; and (2) a test with multiple selected packages, asserting `packages` includes all selected packages in order and each package’s `*_selected`, `*_version`, and `*_artifact` values are set correctly. This will more completely validate the GitHub outputs the workflow relies on.

Suggested implementation:

```python
    def test_github_outputs_mark_only_selected_packages(self) -> None:
        previous = {package: self._version(package) for package in PACKAGE_CONFIG}
        previous["automas-hsr-adapter-m7a"] = "previous-version"
        targets = resolve_changed_releases(previous)

        with TemporaryDirectory() as temp_dir:
            output_path = Path(temp_dir) / "github-output.txt"
            _write_github_outputs(targets, output_path)
            values = dict(
                line.split("=", 1)
                for line in output_path.read_text(encoding="utf-8").splitlines()
            )

        # has_targets and packages reflect the single selected package
        self.assertEqual(values["has_targets"], "true")
        self.assertEqual(values["packages"], "automas-hsr-adapter-m7a")

        # Only the selected package is marked as selected; all others are false
        for package in PACKAGE_CONFIG:
            key_prefix = package.replace("-", "_")
            if package == "automas-hsr-adapter-m7a":
                self.assertEqual(values[f"{key_prefix}_selected"], "true")
                self.assertNotEqual(values[f"{key_prefix}_version"], "")
                self.assertNotEqual(values[f"{key_prefix}_artifact"], "")
            else:
                self.assertEqual(values[f"{key_prefix}_selected"], "false")
                self.assertEqual(values[f"{key_prefix}_version"], "")
                self.assertEqual(values[f"{key_prefix}_artifact"], "")

    def test_github_outputs_with_no_selected_targets(self) -> None:
        previous = {package: self._version(package) for package in PACKAGE_CONFIG}
        # Ensure there are no changed releases
        targets = resolve_changed_releases(previous)
        self.assertEqual(targets, ())

        with TemporaryDirectory() as temp_dir:
            output_path = Path(temp_dir) / "github-output.txt"
            _write_github_outputs(targets, output_path)
            values = dict(
                line.split("=", 1)
                for line in output_path.read_text(encoding="utf-8").splitlines()
            )

        # No targets selected
        self.assertEqual(values["has_targets"], "false")
        self.assertEqual(values["packages"], "")

        # All packages are unselected and have empty version/artifact fields
        for package in PACKAGE_CONFIG:
            key_prefix = package.replace("-", "_")
            self.assertEqual(values[f"{key_prefix}_selected"], "false")
            self.assertEqual(values[f"{key_prefix}_version"], "")
            self.assertEqual(values[f"{key_prefix}_artifact"], "")

    def test_github_outputs_with_multiple_selected_targets(self) -> None:
        # Pick two packages from PACKAGE_CONFIG to mark as changed
        packages = list(PACKAGE_CONFIG.keys())
        # Guard against pathological configs
        self.assertGreaterEqual(len(packages), 2)
        first, second = packages[0], packages[1]

        previous = {package: self._version(package) for package in PACKAGE_CONFIG}
        previous[first] = "previous-version-1"
        previous[second] = "previous-version-2"

        targets = resolve_changed_releases(previous)

        with TemporaryDirectory() as temp_dir:
            output_path = Path(temp_dir) / "github-output.txt"
            _write_github_outputs(targets, output_path)
            values = dict(
                line.split("=", 1)
                for line in output_path.read_text(encoding="utf-8").splitlines()
            )

        selected_packages = tuple(target.package for target in targets)
        self.assertGreaterEqual(len(selected_packages), 2)

        # The packages output lists all selected packages in order
        self.assertEqual(
            values["packages"],
            ",".join(selected_packages),
        )
        self.assertEqual(values["has_targets"], "true")

        # Each selected package has *_selected true and non-empty version/artifact
        selected_set = set(selected_packages)
        for package in PACKAGE_CONFIG:
            key_prefix = package.replace("-", "_")
            if package in selected_set:
                self.assertEqual(values[f"{key_prefix}_selected"], "true")
                self.assertNotEqual(values[f"{key_prefix}_version"], "")
                self.assertNotEqual(values[f"{key_prefix}_artifact"], "")
            else:
                self.assertEqual(values[f"{key_prefix}_selected"], "false")
                self.assertEqual(values[f"{key_prefix}_version"], "")
                self.assertEqual(values[f"{key_prefix}_artifact"], "")

```

These changes assume `_write_github_outputs` uses the following conventions:
1. A `has_targets` output of `"true"`/`"false"`.
2. A `packages` output that is a comma-separated list of package names matching `target.package`.
3. Per-package outputs named using `package.replace("-", "_")` as a prefix, e.g. `automas_hsr_adapter_m7a_selected`, `automas_hsr_adapter_m7a_version`, and `automas_hsr_adapter_m7a_artifact`, with `"true"`/`"false"` for `*_selected` and empty strings for unselected packages' `*_version`/`*_artifact`.

If your actual output naming or formatting differs (for example, if you use short names like `m7a_selected` or a different separator than a comma for `packages`), please adjust the key constructions and assertions accordingly to match the real `_write_github_outputs` behavior.
</issue_to_address>

### Comment 3
<location path="tests/test_release.py" line_range="7-13" />
<code_context>
+from tempfile import TemporaryDirectory

-from scripts.release import PACKAGE_CONFIG, project_version, resolve_release
+from scripts.release import (
+    PACKAGE_CONFIG,
+    RELEASE_ORDER,
+    _write_github_outputs,
+    project_version,
+    resolve_changed_releases,
+    resolve_release,
+)

</code_context>
<issue_to_address>
**issue (testing):** Consider adding tests for the new `resolve_release_plan` logic, especially error paths and automatic main releases.

Right now `resolve_release_plan` (automatic main-branch flow with `before_sha` plus manual/tag releases) isn’t covered by dedicated tests. Please add tests that:
1) Confirm `workflow_dispatch` and tag refs delegate to `resolve_release`.
2) Assert unsupported `event_name`/`ref` combinations raise the documented `ValueError`.
3) Check that missing or blank `before_sha` for `push` on `refs/heads/main` raises the correct error.
These will validate the new entry point and guard against misconfiguration.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread tests/test_release.py
Comment on lines +58 to +62
def test_unchanged_versions_do_not_publish(self) -> None:
previous = {package: self._version(package) for package in PACKAGE_CONFIG}
self.assertEqual(resolve_changed_releases(previous), ())

def test_changed_versions_follow_dependency_order(self) -> None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): 添加针对 resolve_changed_releases 处理新加入包(缺少上一版本记录)的测试。

目前 resolve_changed_releases 仅在 previous 映射完整填充的情况下进行了测试。由于它使用的是 previous_versions.get(package),对于在 previous 中没有条目的新包应该被视为 None 并进行发布。请添加一个测试,其中从 previous 中省略一个包,验证该包会被选中发布,并确认依赖顺序仍然被正确保持。这将有助于防止在处理缺失条目时出现回归问题。

Suggested change
def test_unchanged_versions_do_not_publish(self) -> None:
previous = {package: self._version(package) for package in PACKAGE_CONFIG}
self.assertEqual(resolve_changed_releases(previous), ())
def test_changed_versions_follow_dependency_order(self) -> None:
def test_unchanged_versions_do_not_publish(self) -> None:
previous = {package: self._version(package) for package in PACKAGE_CONFIG}
self.assertEqual(resolve_changed_releases(previous), ())
def test_new_package_without_previous_version_is_published(self) -> None:
# Omit one package from the previous mapping to simulate a newly added package
new_package = next(iter(PACKAGE_CONFIG))
previous = {
package: self._version(package)
for package in PACKAGE_CONFIG
if package != new_package
}
targets = resolve_changed_releases(previous)
target_packages = tuple(target.package for target in targets)
# The new package (missing in previous) should be selected for release
self.assertIn(new_package, target_packages)
# The result order should follow RELEASE_ORDER, restricted to the selected packages
expected_order = tuple(
package for package in RELEASE_ORDER if package == new_package
)
self.assertEqual(target_packages, expected_order)
def test_changed_versions_follow_dependency_order(self) -> None:
Original comment in English

suggestion (testing): Add tests for resolve_changed_releases handling newly added packages (missing previous version entries).

resolve_changed_releases is only tested with a fully populated previous mapping. Because it uses previous_versions.get(package), new packages without an entry should be treated as None and published. Please add a test where one package is omitted from previous, verify that it’s selected for release, and confirm the dependency order is preserved. This will help prevent regressions in how missing entries are handled.

Suggested change
def test_unchanged_versions_do_not_publish(self) -> None:
previous = {package: self._version(package) for package in PACKAGE_CONFIG}
self.assertEqual(resolve_changed_releases(previous), ())
def test_changed_versions_follow_dependency_order(self) -> None:
def test_unchanged_versions_do_not_publish(self) -> None:
previous = {package: self._version(package) for package in PACKAGE_CONFIG}
self.assertEqual(resolve_changed_releases(previous), ())
def test_new_package_without_previous_version_is_published(self) -> None:
# Omit one package from the previous mapping to simulate a newly added package
new_package = next(iter(PACKAGE_CONFIG))
previous = {
package: self._version(package)
for package in PACKAGE_CONFIG
if package != new_package
}
targets = resolve_changed_releases(previous)
target_packages = tuple(target.package for target in targets)
# The new package (missing in previous) should be selected for release
self.assertIn(new_package, target_packages)
# The result order should follow RELEASE_ORDER, restricted to the selected packages
expected_order = tuple(
package for package in RELEASE_ORDER if package == new_package
)
self.assertEqual(target_packages, expected_order)
def test_changed_versions_follow_dependency_order(self) -> None:

Comment thread tests/test_release.py
Comment on lines +76 to +85
def test_github_outputs_mark_only_selected_packages(self) -> None:
previous = {package: self._version(package) for package in PACKAGE_CONFIG}
previous["automas-hsr-adapter-m7a"] = "previous-version"
targets = resolve_changed_releases(previous)

with TemporaryDirectory() as temp_dir:
output_path = Path(temp_dir) / "github-output.txt"
_write_github_outputs(targets, output_path)
values = dict(
line.split("=", 1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): 为 _write_github_outputs 增加在未选择任何目标以及选择多个目标时的测试覆盖。

现有测试只覆盖了单个包(m7a)被选中的情况。请补充:(1)targets 为空的测试,断言 has_targets == "false",packages 为空,所有 *_selected 标记为 false,并且 *_version/*_artifact 字段为空;以及(2)多个包被选中的测试,断言 packages 按顺序包含所有选中包,并且每个包的 *_selected、*_version 和 *_artifact 值都被正确设置。这样可以更完整地验证该工作流依赖的 GitHub 输出。

建议的实现如下:

    def test_github_outputs_mark_only_selected_packages(self) -> None:
        previous = {package: self._version(package) for package in PACKAGE_CONFIG}
        previous["automas-hsr-adapter-m7a"] = "previous-version"
        targets = resolve_changed_releases(previous)

        with TemporaryDirectory() as temp_dir:
            output_path = Path(temp_dir) / "github-output.txt"
            _write_github_outputs(targets, output_path)
            values = dict(
                line.split("=", 1)
                for line in output_path.read_text(encoding="utf-8").splitlines()
            )

        # has_targets and packages reflect the single selected package
        self.assertEqual(values["has_targets"], "true")
        self.assertEqual(values["packages"], "automas-hsr-adapter-m7a")

        # Only the selected package is marked as selected; all others are false
        for package in PACKAGE_CONFIG:
            key_prefix = package.replace("-", "_")
            if package == "automas-hsr-adapter-m7a":
                self.assertEqual(values[f"{key_prefix}_selected"], "true")
                self.assertNotEqual(values[f"{key_prefix}_version"], "")
                self.assertNotEqual(values[f"{key_prefix}_artifact"], "")
            else:
                self.assertEqual(values[f"{key_prefix}_selected"], "false")
                self.assertEqual(values[f"{key_prefix}_version"], "")
                self.assertEqual(values[f"{key_prefix}_artifact"], "")

    def test_github_outputs_with_no_selected_targets(self) -> None:
        previous = {package: self._version(package) for package in PACKAGE_CONFIG}
        # Ensure there are no changed releases
        targets = resolve_changed_releases(previous)
        self.assertEqual(targets, ())

        with TemporaryDirectory() as temp_dir:
            output_path = Path(temp_dir) / "github-output.txt"
            _write_github_outputs(targets, output_path)
            values = dict(
                line.split("=", 1)
                for line in output_path.read_text(encoding="utf-8").splitlines()
            )

        # No targets selected
        self.assertEqual(values["has_targets"], "false")
        self.assertEqual(values["packages"], "")

        # All packages are unselected and have empty version/artifact fields
        for package in PACKAGE_CONFIG:
            key_prefix = package.replace("-", "_")
            self.assertEqual(values[f"{key_prefix}_selected"], "false")
            self.assertEqual(values[f"{key_prefix}_version"], "")
            self.assertEqual(values[f"{key_prefix}_artifact"], "")

    def test_github_outputs_with_multiple_selected_targets(self) -> None:
        # Pick two packages from PACKAGE_CONFIG to mark as changed
        packages = list(PACKAGE_CONFIG.keys())
        # Guard against pathological configs
        self.assertGreaterEqual(len(packages), 2)
        first, second = packages[0], packages[1]

        previous = {package: self._version(package) for package in PACKAGE_CONFIG}
        previous[first] = "previous-version-1"
        previous[second] = "previous-version-2"

        targets = resolve_changed_releases(previous)

        with TemporaryDirectory() as temp_dir:
            output_path = Path(temp_dir) / "github-output.txt"
            _write_github_outputs(targets, output_path)
            values = dict(
                line.split("=", 1)
                for line in output_path.read_text(encoding="utf-8").splitlines()
            )

        selected_packages = tuple(target.package for target in targets)
        self.assertGreaterEqual(len(selected_packages), 2)

        # The packages output lists all selected packages in order
        self.assertEqual(
            values["packages"],
            ",".join(selected_packages),
        )
        self.assertEqual(values["has_targets"], "true")

        # Each selected package has *_selected true and non-empty version/artifact
        selected_set = set(selected_packages)
        for package in PACKAGE_CONFIG:
            key_prefix = package.replace("-", "_")
            if package in selected_set:
                self.assertEqual(values[f"{key_prefix}_selected"], "true")
                self.assertNotEqual(values[f"{key_prefix}_version"], "")
                self.assertNotEqual(values[f"{key_prefix}_artifact"], "")
            else:
                self.assertEqual(values[f"{key_prefix}_selected"], "false")
                self.assertEqual(values[f"{key_prefix}_version"], "")
                self.assertEqual(values[f"{key_prefix}_artifact"], "")

这些改动假设 _write_github_outputs 使用以下约定:

  1. has_targets 输出为 "true"/"false"。
  2. packages 输出为逗号分隔的包名列表,与 target.package 匹配。
  3. 逐包的输出名称以 package.replace("-", "_") 作为前缀,例如 automas_hsr_adapter_m7a_selected、automas_hsr_adapter_m7a_version 和 automas_hsr_adapter_m7a_artifact,其中 *_selected 使用 "true"/"false",未选中包的 *_version/*_artifact 使用空字符串。

如果你实际的输出命名或格式不同(例如使用类似 m7a_selected 的短名称,或者在 packages 中使用不同的分隔符),请相应调整 key 构造和断言,以匹配真实的 _write_github_outputs 行为。

Original comment in English

suggestion (testing): Add coverage for _write_github_outputs when no targets are selected and for multiple selected targets.

The existing test only covers the case where a single package (m7a) is selected. Please add: (1) a test with targets empty, asserting has_targets == "false", packages is empty, all *_selected flags are false, and *_version/*_artifact fields are empty; and (2) a test with multiple selected packages, asserting packages includes all selected packages in order and each package’s *_selected, *_version, and *_artifact values are set correctly. This will more completely validate the GitHub outputs the workflow relies on.

Suggested implementation:

    def test_github_outputs_mark_only_selected_packages(self) -> None:
        previous = {package: self._version(package) for package in PACKAGE_CONFIG}
        previous["automas-hsr-adapter-m7a"] = "previous-version"
        targets = resolve_changed_releases(previous)

        with TemporaryDirectory() as temp_dir:
            output_path = Path(temp_dir) / "github-output.txt"
            _write_github_outputs(targets, output_path)
            values = dict(
                line.split("=", 1)
                for line in output_path.read_text(encoding="utf-8").splitlines()
            )

        # has_targets and packages reflect the single selected package
        self.assertEqual(values["has_targets"], "true")
        self.assertEqual(values["packages"], "automas-hsr-adapter-m7a")

        # Only the selected package is marked as selected; all others are false
        for package in PACKAGE_CONFIG:
            key_prefix = package.replace("-", "_")
            if package == "automas-hsr-adapter-m7a":
                self.assertEqual(values[f"{key_prefix}_selected"], "true")
                self.assertNotEqual(values[f"{key_prefix}_version"], "")
                self.assertNotEqual(values[f"{key_prefix}_artifact"], "")
            else:
                self.assertEqual(values[f"{key_prefix}_selected"], "false")
                self.assertEqual(values[f"{key_prefix}_version"], "")
                self.assertEqual(values[f"{key_prefix}_artifact"], "")

    def test_github_outputs_with_no_selected_targets(self) -> None:
        previous = {package: self._version(package) for package in PACKAGE_CONFIG}
        # Ensure there are no changed releases
        targets = resolve_changed_releases(previous)
        self.assertEqual(targets, ())

        with TemporaryDirectory() as temp_dir:
            output_path = Path(temp_dir) / "github-output.txt"
            _write_github_outputs(targets, output_path)
            values = dict(
                line.split("=", 1)
                for line in output_path.read_text(encoding="utf-8").splitlines()
            )

        # No targets selected
        self.assertEqual(values["has_targets"], "false")
        self.assertEqual(values["packages"], "")

        # All packages are unselected and have empty version/artifact fields
        for package in PACKAGE_CONFIG:
            key_prefix = package.replace("-", "_")
            self.assertEqual(values[f"{key_prefix}_selected"], "false")
            self.assertEqual(values[f"{key_prefix}_version"], "")
            self.assertEqual(values[f"{key_prefix}_artifact"], "")

    def test_github_outputs_with_multiple_selected_targets(self) -> None:
        # Pick two packages from PACKAGE_CONFIG to mark as changed
        packages = list(PACKAGE_CONFIG.keys())
        # Guard against pathological configs
        self.assertGreaterEqual(len(packages), 2)
        first, second = packages[0], packages[1]

        previous = {package: self._version(package) for package in PACKAGE_CONFIG}
        previous[first] = "previous-version-1"
        previous[second] = "previous-version-2"

        targets = resolve_changed_releases(previous)

        with TemporaryDirectory() as temp_dir:
            output_path = Path(temp_dir) / "github-output.txt"
            _write_github_outputs(targets, output_path)
            values = dict(
                line.split("=", 1)
                for line in output_path.read_text(encoding="utf-8").splitlines()
            )

        selected_packages = tuple(target.package for target in targets)
        self.assertGreaterEqual(len(selected_packages), 2)

        # The packages output lists all selected packages in order
        self.assertEqual(
            values["packages"],
            ",".join(selected_packages),
        )
        self.assertEqual(values["has_targets"], "true")

        # Each selected package has *_selected true and non-empty version/artifact
        selected_set = set(selected_packages)
        for package in PACKAGE_CONFIG:
            key_prefix = package.replace("-", "_")
            if package in selected_set:
                self.assertEqual(values[f"{key_prefix}_selected"], "true")
                self.assertNotEqual(values[f"{key_prefix}_version"], "")
                self.assertNotEqual(values[f"{key_prefix}_artifact"], "")
            else:
                self.assertEqual(values[f"{key_prefix}_selected"], "false")
                self.assertEqual(values[f"{key_prefix}_version"], "")
                self.assertEqual(values[f"{key_prefix}_artifact"], "")

These changes assume _write_github_outputs uses the following conventions:

  1. A has_targets output of "true"/"false".
  2. A packages output that is a comma-separated list of package names matching target.package.
  3. Per-package outputs named using package.replace("-", "_") as a prefix, e.g. automas_hsr_adapter_m7a_selected, automas_hsr_adapter_m7a_version, and automas_hsr_adapter_m7a_artifact, with "true"/"false" for *_selected and empty strings for unselected packages' *_version/*_artifact.

If your actual output naming or formatting differs (for example, if you use short names like m7a_selected or a different separator than a comma for packages), please adjust the key constructions and assertions accordingly to match the real _write_github_outputs behavior.

Comment thread tests/test_release.py
Comment on lines +7 to +13
from scripts.release import (
PACKAGE_CONFIG,
RELEASE_ORDER,
_write_github_outputs,
project_version,
resolve_changed_releases,
resolve_release,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (testing): 请考虑为新的 resolve_release_plan 逻辑添加测试,尤其是错误路径以及自动 main 分支发布的相关场景。

目前 resolve_release_plan(在 main 分支上基于 before_sha 的自动流程,外加手动/标签发布)还没有专门测试覆盖。请添加以下测试:
1)确认 workflow_dispatch 和标签 ref 会委托给 resolve_release。
2)断言不支持的 event_name/ref 组合会抛出已记录的 ValueError。
3)检查在 refs/heads/main 上 push 时,缺失或为空的 before_sha 会抛出正确的错误。
这些测试将验证新的入口逻辑,并防止配置错误。

Original comment in English

issue (testing): Consider adding tests for the new resolve_release_plan logic, especially error paths and automatic main releases.

Right now resolve_release_plan (automatic main-branch flow with before_sha plus manual/tag releases) isn’t covered by dedicated tests. Please add tests that:

  1. Confirm workflow_dispatch and tag refs delegate to resolve_release.
  2. Assert unsupported event_name/ref combinations raise the documented ValueError.
  3. Check that missing or blank before_sha for push on refs/heads/main raises the correct error.
    These will validate the new entry point and guard against misconfiguration.

@qiyinxi
qiyinxi merged commit f6dabf4 into main Aug 2, 2026
3 checks passed
@qiyinxi
qiyinxi deleted the ci/auto-publish-version-change branch August 2, 2026 06:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant