mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 13:16:24 +00:00
fix(release): refresh backend/uv.lock when bumping the version (#5859)
* fix(release): refresh backend/uv.lock when bumping the version `scripts/bump_version.sh` rewrote backend/pyproject.toml, frontend/package.json and the Helm chart's `version`/`appVersion`, but `backend/uv.lock` also records the root package version (uv keeps its PEP 440 form, so `2.1.0-rc0` is stored as `2.1.0rc0`). The documented release step left the lock pinning the previous version, and that is not cosmetic drift: `uv lock --check` (lint CI) fails on a stale lock and `uv sync --locked` (e2e / plugin workflows) refuses the tree, so the release commit could not pass CI. RELEASING.md only mentioned the manual `cd backend && uv lock` step for release candidates. The script now refreshes the lock via `uv lock` and refuses to start without `uv` — it exits before editing anything rather than leaving a half-bumped tree whose lock CI rejects. `uv lock` stays the authority for the normalized prerelease form; the lock diff for a bump is a single line. RELEASING.md and AGENTS.md now describe the lock as part of the bump. Tests: backend/tests/test_bump_version_lockfile_sync.py (red on main, green here) pins both halves — the lock moves with the sources, and a missing uv is an error instead of a silent no-op. * docs(changelog): record the release bump lockfile fix (#5859) --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
parent
64e499be98
commit
65ed502e54
@ -231,9 +231,11 @@ These apply repo-wide; module guides own the module-specific detail.
|
||||
relying on the platform locale.
|
||||
- **Version sources must stay in lockstep** — a release version must match identically in
|
||||
`backend/pyproject.toml`, `frontend/package.json`, and `deploy/helm/deer-flow/Chart.yaml`
|
||||
(`version` + `appVersion`). Pushing a `v*` git tag triggers CI that runs
|
||||
`scripts/verify_versions.sh` and **blocks all publishing** if any source drifts. Before
|
||||
bumping a version, run `scripts/bump_version.sh <ver>` (aligns all four at once) and
|
||||
(`version` + `appVersion`), and `backend/uv.lock` must record the same version for the root
|
||||
package (uv stores its PEP 440 form, e.g. `2.1.0rc0`). Pushing a `v*` git tag triggers CI
|
||||
that runs `scripts/verify_versions.sh` and **blocks all publishing** if any source drifts.
|
||||
Before bumping a version, run `scripts/bump_version.sh <ver>` (aligns the four fields and
|
||||
refreshes `backend/uv.lock` at once — it needs `uv` on `PATH`) and
|
||||
`scripts/verify_versions.sh <ver>` to catch drift early. See [RELEASING.md](RELEASING.md).
|
||||
- **Don't edit `CLAUDE.md`** — it only contains `@AGENTS.md`. All agent guidance changes
|
||||
belong here in `AGENTS.md`; `CLAUDE.md` is a thin import shim.
|
||||
|
||||
53
CHANGELOG.md
53
CHANGELOG.md
@ -2404,6 +2404,57 @@ This release closes that milestone with **772 merged pull requests**.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **release:** Bumping the version no longer leaves `backend/uv.lock` behind.
|
||||
`scripts/bump_version.sh` rewrote `backend/pyproject.toml`, `frontend/package.json`
|
||||
and the Helm chart, but the lockfile records the root package's own version too
|
||||
(uv keeps its PEP 440 form, so `2.1.0-rc0` is stored as `2.1.0rc0`). The
|
||||
documented release step therefore produced a commit whose lock CI rejects:
|
||||
`uv lock --check` fails on the stale lock and `uv sync --locked` refuses the
|
||||
tree, and with pre-commit installed it broke a step earlier on the
|
||||
`uv-lock-check` hook. The script now refreshes the lock with `uv lock` and exits
|
||||
before editing anything when `uv` is missing, instead of leaving a half-bumped
|
||||
working tree behind. Only the root package's version line moves. ([#5859])
|
||||
- **config:** A `config.yaml` edit that lands while the previous edit is still
|
||||
being loaded is no longer lost until the next edit. `get_app_config()`'s
|
||||
loader parsed the file and then hashed it again to record the cache
|
||||
signature, so a write between those two reads left the cache holding the
|
||||
older content under the newer content's signature — a state the signature
|
||||
comparison can never detect. The loader now reads the file once and signs
|
||||
the bytes it parsed; a write that races the load just triggers one more
|
||||
reload on the next call. ([#5848])
|
||||
- **config:** `request_admission.requests_per_minute` and `max_queue_size` now
|
||||
accept `$VAR` environment references like every other field. Both are strict
|
||||
integers so a bool or float is still rejected, but `$VAR` substitution always
|
||||
produces a string, so `requests_per_minute: $RPM` failed the whole config load
|
||||
with "Input should be a valid integer" even when `RPM=60`. A decimal literal
|
||||
delivered as a string is now converted before the strict check; any other
|
||||
string is still rejected. ([#5838])
|
||||
- **scheduler:** Pausing a scheduled task no longer loses the pause when a
|
||||
dispatch is in flight on SQLite. `release_dispatch_lease` guards on the lease
|
||||
owner — which pausing clears — but read the row without taking SQLite's
|
||||
writer, so a stale read passed the guard and wrote the task back to
|
||||
`enabled` with `next_run_at` untouched, leaving the scheduler firing a task
|
||||
the API had reported as paused. The read now takes the writer first, as every
|
||||
other mutating path in that repository does. PostgreSQL was unaffected.
|
||||
([#5777])
|
||||
- **mcp:** Lazy MCP initialization no longer runs tool discovery twice when
|
||||
discovery itself raises a `RuntimeError` such as `McpTaskConfigurationError`.
|
||||
The `asyncio.run` fallback in `get_cached_mcp_tools()` was meant only for
|
||||
`get_event_loop()` failing, but it also caught discovery errors and
|
||||
re-spawned every stdio server (and re-fetched OAuth tokens) before giving
|
||||
up; inside a running loop it also logged a misleading "asyncio.run() cannot
|
||||
be called from a running event loop" traceback instead of the real cause.
|
||||
- **uploads:** Deleting an uploaded document no longer deletes the converted
|
||||
Markdown beside it. Conversion names a companion after the document's stem
|
||||
and falls back to a `_N` suffix when that name is taken, so the `.md` next to
|
||||
a document can belong to another document sharing the stem, or to the user:
|
||||
uploading `a.docx` and `a.pdf` produced `a.md` and `a_1.md`, and deleting
|
||||
`a.pdf` destroyed `a.docx`'s companion. Companions now survive their
|
||||
document, stay listed, and can be deleted on their own. ([#5673])
|
||||
- **subagents:** Recognize zero-byte regular deliverables in remote sandbox
|
||||
acceptance checks. Readable empty files now satisfy `exists` and
|
||||
`file_written` and deterministically fail `non-empty`, instead of remaining
|
||||
UNVERIFIED. ([#5559])
|
||||
- **frontend:** Keep the `…` (kebab) menu on project chat rows inside the
|
||||
sidebar. In the sidebar's grouped Projects mode the indented nested menus
|
||||
kept `SidebarMenu`'s `w-full` while carrying an extra `ml-4`, so they were
|
||||
@ -6026,3 +6077,5 @@ with **180 merged pull requests** since the first 2.0 milestone tag.
|
||||
[#5844]: https://github.com/bytedance/deer-flow/pull/5844
|
||||
[#5845]: https://github.com/bytedance/deer-flow/pull/5845
|
||||
[#5848]: https://github.com/bytedance/deer-flow/pull/5848
|
||||
[#5859]: https://github.com/bytedance/deer-flow/pull/5859
|
||||
|
||||
|
||||
@ -2120,6 +2120,46 @@
|
||||
|
||||
### 修复
|
||||
|
||||
- **发布:** 版本升级不再把 `backend/uv.lock` 落下。`scripts/bump_version.sh` 会改写
|
||||
`backend/pyproject.toml`、`frontend/package.json` 与 Helm chart,但 lockfile 同样记录了
|
||||
根包自身的版本(uv 保留其 PEP 440 形式,因此 `2.1.0-rc0` 存为 `2.1.0rc0`),于是文档给出的
|
||||
发布步骤产出的提交会被 lock 相关 CI 拦下:`uv lock --check` 判其过期,`uv sync --locked`
|
||||
也会拒绝该工作区;装了 pre-commit 时还会更早在 `uv-lock-check` 钩子上失败。现在该脚本会用
|
||||
`uv lock` 刷新 lockfile,并在缺少 `uv` 时于修改任何文件之前退出,而不是留下一个只改一半的
|
||||
工作区。实际改动仅涉及根包的那一行版本号。([#5859])
|
||||
- **配置:** 在上一次编辑仍在加载时落盘的 `config.yaml` 编辑,不再要等到下一次编辑才生效。
|
||||
`get_app_config()` 的加载器先解析文件,再重新读取一遍来计算缓存签名,因此夹在两次读取
|
||||
之间的写入会让缓存以较新内容的签名保存较旧的内容,而签名比较永远无法发现这种状态。
|
||||
现在加载器只读取文件一次,并对解析的那份字节计算签名;与加载竞争的写入只会在下一次
|
||||
调用时多触发一次重载。([#5848])
|
||||
- **配置:** `request_admission.requests_per_minute` 与 `max_queue_size` 现在与其他字段一样
|
||||
接受 `$VAR` 环境变量引用。这两个字段是严格整数,布尔值与浮点数仍会被拒绝;但 `$VAR`
|
||||
替换得到的永远是字符串,因此即使 `RPM=60`,`requests_per_minute: $RPM` 也会让整个配置
|
||||
加载失败并报 "Input should be a valid integer"。现在以字符串形式到达的十进制整数字面量会在
|
||||
严格校验之前被转换;其他字符串仍会被拒绝。([#5838])
|
||||
- **调度器:** 在 SQLite 上,调度分发进行中暂停计划任务时不再丢失暂停状态。
|
||||
`release_dispatch_lease` 依据租约持有者做校验(暂停会清除该字段),但读取任务行时没有先获取
|
||||
SQLite 的写锁,因此过期的读取会通过校验,并把任务状态写回 `enabled` 且不改动 `next_run_at`,
|
||||
导致接口已回复“已暂停”的任务仍被继续触发。现在该读取会像该仓储中其他写入路径一样先获取写锁。
|
||||
PostgreSQL 不受影响。([#5777])
|
||||
- **mcp:** MCP 延迟初始化在工具发现本身抛出 `RuntimeError`(例如
|
||||
`McpTaskConfigurationError`)时,不再把发现流程跑两遍。`get_cached_mcp_tools()` 里的
|
||||
`asyncio.run` 兜底只为 `get_event_loop()` 失败而设,却同时捕获了发现阶段的错误,于是在
|
||||
放弃之前会重新拉起每一个 stdio 服务器(并重新获取 OAuth 令牌);在运行中的事件循环里,
|
||||
它记录的还是误导性的 "asyncio.run() cannot be called from a running event loop"
|
||||
堆栈,而不是真正的原因。
|
||||
- **上传:** 删除已上传的文档时,不再连带删除其旁边转换生成的 Markdown。转换以文档主干名
|
||||
命名配套文件,名称被占用时回退为 `_N` 后缀,因此文档旁的 `.md` 可能属于主干名相同的另一个
|
||||
文档,或属于用户自己:上传 `a.docx` 与 `a.pdf` 会生成 `a.md` 与 `a_1.md`,删除 `a.pdf`
|
||||
却会销毁 `a.docx` 的配套文件。现在配套文件会保留、继续出现在列表中,可单独删除。([#5673])
|
||||
- **nginx:** 把 600 秒读取超时扩展到其余两个会等待 Gateway 的 location,它们在线程路由的修复
|
||||
之后仍沿用 nginx 默认的 60 秒。`/api/` 兜底 location 之后:无状态的 `POST /api/runs/wait`
|
||||
阻塞在同一套运行完成等待上,并在客户端断开时取消该运行,因此等待超过 60 秒的 API 调用方会
|
||||
同时收到 504 **并且**运行被取消;输入框的 `POST /api/input-polish` 则等待一次性模型调用。
|
||||
`/api/skills` 之后:安装 `.skill` 压缩包会对其中每个文件各做一次 LLM 安全扫描,自定义技能的
|
||||
编辑与回滚各再做一次,它们都没有自己的超时;此前只有同级的 `/api/skills/install/upload`
|
||||
拿到了更长的超时,因此同样的安装经由 `POST /api/skills/install` 会在 60 秒失败。
|
||||
Docker、本地开发与 Helm 配置均已应用。([#5524])
|
||||
- **前端:** 项目会话行上的 `…`(kebab)菜单不再溢出侧边栏。在侧边栏的按项目分组模式下,
|
||||
缩进的嵌套菜单继承了 `SidebarMenu` 的 `w-full` 又额外带着 `ml-4`,实际宽度是"100% + 16px",
|
||||
其绝对定位的 `right-1` 操作按钮因此落到侧边栏边缘之外——活跃项目分组下的行被裁剪,Archived
|
||||
@ -5135,3 +5175,4 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子
|
||||
[#5844]: https://github.com/bytedance/deer-flow/pull/5844
|
||||
[#5845]: https://github.com/bytedance/deer-flow/pull/5845
|
||||
[#5848]: https://github.com/bytedance/deer-flow/pull/5848
|
||||
[#5859]: https://github.com/bytedance/deer-flow/pull/5859
|
||||
|
||||
28
RELEASING.md
28
RELEASING.md
@ -32,8 +32,11 @@ distinguishes it from a release.
|
||||
|
||||
## Helper scripts
|
||||
|
||||
- `scripts/bump_version.sh <version>` — set all four fields at once, then
|
||||
self-verify. Tolerates a leading `v` (e.g. `v2.1.0`).
|
||||
- `scripts/bump_version.sh <version>` — set all four fields at once, refresh
|
||||
`backend/uv.lock`, then self-verify. Tolerates a leading `v` (e.g. `v2.1.0`).
|
||||
Needs `uv` on `PATH` (`backend/uv.lock` pins the root package version too, and
|
||||
lint CI runs `uv lock --check`); the script fails before editing anything when
|
||||
`uv` is missing.
|
||||
```bash
|
||||
scripts/bump_version.sh 2.1.0
|
||||
```
|
||||
@ -47,7 +50,8 @@ distinguishes it from a release.
|
||||
|
||||
## Release procedure
|
||||
|
||||
1. **Bump the version** across all sources:
|
||||
1. **Bump the version** across all sources (this refreshes
|
||||
`backend/uv.lock` too):
|
||||
```bash
|
||||
scripts/bump_version.sh 2.1.0
|
||||
```
|
||||
@ -74,13 +78,13 @@ distinguishes it from a release.
|
||||
|
||||
Release-candidate tags must include the same prerelease suffix in all four
|
||||
version fields. For example, before tagging `v2.1.0-rc0`, run
|
||||
`bash scripts/bump_version.sh 2.1.0-rc0`, refresh `backend/uv.lock` with
|
||||
`cd backend && uv lock`, and run `bash scripts/verify_versions.sh 2.1.0-rc0`
|
||||
from the repository root. Commit the version and lockfile changes before
|
||||
creating the tag. Python lockfiles normalize this version to `2.1.0rc0`;
|
||||
the source version fields checked by the release gate retain `2.1.0-rc0`.
|
||||
Re-running a failed workflow on an unchanged tag does not pick up a later
|
||||
version-fix commit.
|
||||
`bash scripts/bump_version.sh 2.1.0-rc0` and run
|
||||
`bash scripts/verify_versions.sh 2.1.0-rc0` from the repository root. Commit
|
||||
the version and lockfile changes before creating the tag. Python lockfiles
|
||||
normalize this version to `2.1.0rc0` — `bump_version.sh` leaves the normalizing
|
||||
to `uv lock` — while the source version fields checked by the release gate
|
||||
retain `2.1.0-rc0`. Re-running a failed workflow on an unchanged tag does not
|
||||
pick up a later version-fix commit.
|
||||
|
||||
## What CI publishes on a `v*` tag
|
||||
|
||||
@ -148,6 +152,10 @@ first job. It runs `scripts/verify_versions.sh` against the tag (minus the
|
||||
`v`). If any of the four version sources doesn't match the tag, the verify job
|
||||
fails and **all** publish jobs are skipped — no images, no chart.
|
||||
|
||||
The gate covers those four fields. `backend/uv.lock` is refreshed to the same
|
||||
version by `scripts/bump_version.sh` and checked by `uv lock --check` in lint CI,
|
||||
which fails on a stale lock.
|
||||
|
||||
When it fails, the job annotation names the offending file and suggests the
|
||||
fix:
|
||||
|
||||
|
||||
175
backend/tests/test_bump_version_lockfile_sync.py
Normal file
175
backend/tests/test_bump_version_lockfile_sync.py
Normal file
@ -0,0 +1,175 @@
|
||||
"""Regression test: ``scripts/bump_version.sh`` must refresh ``backend/uv.lock``.
|
||||
|
||||
``RELEASING.md`` tells releasers that step 1 of a release is
|
||||
``scripts/bump_version.sh <version>`` — "set all four fields at once, then
|
||||
self-verify" — and ``verify_versions.sh`` agrees that the tree is consistent
|
||||
afterwards. But ``backend/uv.lock`` records the root package's own version too
|
||||
(uv normalizes ``2.1.0-rc0`` to ``2.1.0rc0``), and the script only rewrote
|
||||
``pyproject.toml`` / ``package.json`` / ``Chart.yaml``. The documented release
|
||||
step therefore left the lock pinned to the *previous* version.
|
||||
|
||||
Measured on ``3a862780`` (``main``, version ``2.1.0-rc0``) before this fix::
|
||||
|
||||
$ scripts/bump_version.sh 2.2.0 # documented step, exits 0, self-verifies
|
||||
$ sed -n '/name = "deer-flow"/,+1p' backend/uv.lock
|
||||
name = "deer-flow"
|
||||
version = "2.1.0rc0" # still the old version
|
||||
$ cd backend && uv lock --check # .github/workflows/lint-check.yml:59
|
||||
error: The lockfile at `uv.lock` needs to be updated, but `--check` was provided.
|
||||
|
||||
The release commit that the script claims is verified turns PR CI red, and
|
||||
``uv sync --locked`` (``e2e-tests.yml``, ``jev-plugin-package.yml``) refuses the
|
||||
tree. ``RELEASING.md`` only mentions the manual ``cd backend && uv lock`` step in
|
||||
the pre-release section, so a normal release hit this every time.
|
||||
|
||||
The script now refreshes the lock itself and refuses to run without ``uv`` —
|
||||
better an early error than a half-bumped tree whose lock CI rejects.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from support.shell import find_script_bash
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
SCRIPT_BASH = find_script_bash()
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
SCRIPT_BASH is None or os.name == "nt",
|
||||
reason="the fake `uv` on PATH is an extensionless shebang script; Windows does not execute those",
|
||||
)
|
||||
|
||||
# Minimal versions of the three files bump_version.sh rewrites, plus the lock
|
||||
# entry uv writes for the root ("virtual") package. Only the fields the script
|
||||
# touches are present, so a stray rewrite shows up as a diff.
|
||||
PYPROJECT = '[project]\nname = "deer-flow"\nversion = "2.1.0-rc0"\n'
|
||||
PACKAGE_JSON = '{\n "name": "deer-flow-frontend",\n "version": "2.1.0-rc0",\n "private": true\n}\n'
|
||||
CHART = 'apiVersion: v2\nname: deer-flow\nversion: 2.1.0-rc0\nappVersion: "2.1.0-rc0"\n'
|
||||
UV_LOCK = 'version = 1\n\n[[package]]\nname = "deer-flow"\nversion = "2.1.0rc0"\nsource = { virtual = "." }\n'
|
||||
|
||||
# Emulates the uv behavior the release flow depends on: `uv lock` records the
|
||||
# pyproject version in the root lock entry, in uv's PEP 440 form (for these
|
||||
# inputs that only means dropping the `-`). The invocation is logged so the test
|
||||
# can tell whether the script called uv at all.
|
||||
FAKE_UV = """#!/usr/bin/env python3
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
log = Path(__file__).with_name("uv-calls.log")
|
||||
with log.open("a", encoding="utf-8") as handle:
|
||||
handle.write("uv " + " ".join(sys.argv[1:]) + "\\n")
|
||||
|
||||
args = sys.argv[1:]
|
||||
if args != ["lock"]:
|
||||
sys.exit(f"fake uv: unsupported invocation {args}")
|
||||
|
||||
root = Path.cwd()
|
||||
lock_path = root / "uv.lock"
|
||||
lock_text = lock_path.read_text(encoding="utf-8")
|
||||
pyproject = (root / "pyproject.toml").read_text(encoding="utf-8")
|
||||
declared = re.search(r'(?m)^version\\s*=\\s*"([^"]+)"', pyproject).group(1)
|
||||
normalized = declared.replace("-", "")
|
||||
recorded = re.search(r'(?m)^name = "deer-flow"\\nversion = "([^"]+)"', lock_text).group(1)
|
||||
|
||||
lock_path.write_text(lock_text.replace(f'version = "{recorded}"', f'version = "{normalized}"', 1), encoding="utf-8")
|
||||
"""
|
||||
|
||||
|
||||
def _write_sandbox(root: Path) -> Path:
|
||||
"""Materialize a repo-shaped tree the scripts can run against."""
|
||||
(root / "scripts").mkdir(parents=True)
|
||||
(root / "backend").mkdir()
|
||||
(root / "frontend").mkdir()
|
||||
(root / "deploy" / "helm" / "deer-flow").mkdir(parents=True)
|
||||
|
||||
for name in ("bump_version.sh", "verify_versions.sh"):
|
||||
shutil.copy2(REPO_ROOT / "scripts" / name, root / "scripts" / name)
|
||||
|
||||
(root / "backend" / "pyproject.toml").write_text(PYPROJECT, encoding="utf-8")
|
||||
(root / "backend" / "uv.lock").write_text(UV_LOCK, encoding="utf-8")
|
||||
(root / "frontend" / "package.json").write_text(PACKAGE_JSON, encoding="utf-8")
|
||||
(root / "deploy" / "helm" / "deer-flow" / "Chart.yaml").write_text(CHART, encoding="utf-8")
|
||||
return root
|
||||
|
||||
|
||||
def _install_fake_uv(root: Path) -> Path:
|
||||
"""Put a fake ``uv`` and the log it appends to on PATH."""
|
||||
bin_dir = root / "fake-bin"
|
||||
bin_dir.mkdir()
|
||||
uv = bin_dir / "uv"
|
||||
uv.write_text(FAKE_UV, encoding="utf-8")
|
||||
uv.chmod(uv.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
||||
(bin_dir / "uv-calls.log").write_text("", encoding="utf-8")
|
||||
return bin_dir
|
||||
|
||||
|
||||
def _run_bump(root: Path, version: str, path_prefix: Path | None = None, path: str | None = None) -> subprocess.CompletedProcess[str]:
|
||||
env = dict(os.environ)
|
||||
if path is not None:
|
||||
env["PATH"] = path
|
||||
elif path_prefix is not None:
|
||||
env["PATH"] = f"{path_prefix}{os.pathsep}{env.get('PATH', '')}"
|
||||
return subprocess.run(
|
||||
[SCRIPT_BASH, str(root / "scripts" / "bump_version.sh"), version],
|
||||
cwd=root,
|
||||
env=env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
|
||||
|
||||
def _lock_version(root: Path) -> str:
|
||||
lock = (root / "backend" / "uv.lock").read_text(encoding="utf-8")
|
||||
return lock.split('version = "', 2)[1].split('"', 1)[0]
|
||||
|
||||
|
||||
def test_bump_version_refreshes_the_lockfile(tmp_path: Path) -> None:
|
||||
"""After the documented release step the lock records the new version."""
|
||||
root = _write_sandbox(tmp_path / "repo")
|
||||
bin_dir = _install_fake_uv(root)
|
||||
|
||||
result = _run_bump(root, "2.2.0", path_prefix=bin_dir)
|
||||
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert _lock_version(root) == "2.2.0", "backend/uv.lock still pins the previous version"
|
||||
assert (root / "backend" / "pyproject.toml").read_text(encoding="utf-8") == PYPROJECT.replace("2.1.0-rc0", "2.2.0")
|
||||
invocations = (bin_dir / "uv-calls.log").read_text(encoding="utf-8").splitlines()
|
||||
assert invocations and all(line.startswith("uv lock") for line in invocations), f"bump_version.sh never invoked uv lock: {invocations}"
|
||||
|
||||
|
||||
def test_bump_version_normalizes_a_prerelease_into_the_lock(tmp_path: Path) -> None:
|
||||
"""A prerelease keeps its `-rc` form in the sources and PEP 440 in the lock."""
|
||||
root = _write_sandbox(tmp_path / "repo")
|
||||
bin_dir = _install_fake_uv(root)
|
||||
|
||||
result = _run_bump(root, "2.1.0-rc1", path_prefix=bin_dir)
|
||||
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert (root / "backend" / "pyproject.toml").read_text(encoding="utf-8") == PYPROJECT.replace("2.1.0-rc0", "2.1.0-rc1")
|
||||
assert _lock_version(root) == "2.1.0rc1", "uv normalizes the prerelease in the lock, and the script must let it"
|
||||
|
||||
|
||||
def test_bump_version_refuses_to_run_without_uv(tmp_path: Path) -> None:
|
||||
"""Without uv the script fails before touching anything, instead of half-bumping."""
|
||||
root = _write_sandbox(tmp_path / "repo")
|
||||
# Keep every PATH entry that does not provide a `uv`, so the script still
|
||||
# finds awk/grep/sed/python3 and can only fail on the missing uv itself.
|
||||
without_uv = [entry for entry in os.environ.get("PATH", "").split(os.pathsep) if entry and not (Path(entry) / "uv").exists()]
|
||||
path = os.pathsep.join(without_uv)
|
||||
assert shutil.which("uv", path=path) is None, "test setup: uv is still reachable on PATH"
|
||||
assert shutil.which("awk", path=path) is not None, "test setup: awk must stay reachable"
|
||||
|
||||
result = _run_bump(root, "2.2.0", path=path)
|
||||
|
||||
assert result.returncode != 0, "a missing uv must be an error: the lock would stay stale"
|
||||
assert "uv" in (result.stderr + result.stdout)
|
||||
assert (root / "backend" / "pyproject.toml").read_text(encoding="utf-8") == PYPROJECT, "the script edited files before checking for uv"
|
||||
assert _lock_version(root) == "2.1.0rc0"
|
||||
@ -6,9 +6,13 @@
|
||||
#
|
||||
# Updates:
|
||||
# backend/pyproject.toml (version = "...")
|
||||
# backend/uv.lock (root package version, via `uv lock`)
|
||||
# frontend/package.json ("version": "...")
|
||||
# deploy/helm/deer-flow/Chart.yaml (version: + appVersion:)
|
||||
#
|
||||
# Requires `uv` on PATH: backend/uv.lock pins the root package version too, and
|
||||
# lint CI runs `uv lock --check`, so the lock has to move with the sources.
|
||||
#
|
||||
# This does NOT edit CHANGELOG.md or create/push a git tag — keep those manual.
|
||||
# After running, commit and tag v<version> to trigger the release workflows
|
||||
# (container.yaml + chart.yaml), which gate on scripts/verify_versions.sh.
|
||||
@ -39,6 +43,17 @@ for f in "$PYPROJECT" "$PACKAGE" "$CHART"; do
|
||||
fi
|
||||
done
|
||||
|
||||
# backend/uv.lock records the root package's version as well (uv normalizes a
|
||||
# prerelease, so `2.1.0-rc0` is stored as `2.1.0rc0`). Leaving it behind is not
|
||||
# a cosmetic drift: `uv lock --check` in lint CI fails on the stale lock, and
|
||||
# `uv sync --locked` refuses the tree — so the release step would produce a
|
||||
# commit whose own CI is red. Refresh it here, and fail before editing anything
|
||||
# when uv is missing rather than leaving a half-bumped tree behind.
|
||||
if ! command -v uv >/dev/null 2>&1; then
|
||||
echo "error: 'uv' is required to refresh backend/uv.lock in lockstep; install it from https://docs.astral.sh/uv/ and re-run" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
python3 - "$PYPROJECT" "$PACKAGE" "$CHART" "$VERSION" <<'PY'
|
||||
import re
|
||||
import sys
|
||||
@ -79,8 +94,14 @@ with open(chart, "w") as f:
|
||||
f.write(new)
|
||||
PY
|
||||
|
||||
if ! (cd "$ROOT/backend" && uv lock); then
|
||||
echo "error: 'uv lock' failed; backend/uv.lock still pins the previous version. Fix the resolution error and re-run, or revert the version edits above." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Bumped version to $VERSION in:"
|
||||
echo " backend/pyproject.toml"
|
||||
echo " backend/uv.lock"
|
||||
echo " frontend/package.json"
|
||||
echo " deploy/helm/deer-flow/Chart.yaml (version + appVersion)"
|
||||
echo
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user