feat(create_release): add --keep-latest flag to delete previous releases #104

Closed
opened 2026-08-19 00:09:23 +03:00 by slaid098 · 0 comments
Owner

Контекст

Мастер-копия create_release.py (используется voice_assistant и — после перевода — video_uniq через curl-download) создаёт релиз и загружает asset, удаляя только existing release с тем же tag (идемпотентность). При новой версии (например v0.1.23) предыдущий релиз v0.1.22 остаётся в списке Releases. Пользователь хочет: при создании нового релиза автоматически удалять все предыдущие, оставляя только последний — чтобы в UI Forgejo всегда был один актуальный релиз.

Сейчас удаление предыдущих релизов делается вручную (оркестратор через Python urllib, curl DELETE запрещён permission-правилами). Цель — автоматизировать в CI.

Задача

  1. Добавить функцию delete_previous_releases(repo: str, keep_id: int) -> None в create_release.py:
    • GET /api/v1/repos/{repo}/releases → список всех релизов
    • Для каждого rel['id'] != keep_id → DELETE /api/v1/repos/{repo}/releases/{rel['id']}
    • Логирование: _warn(f"deleting previous release {tag} (id={id})") для каждого удалённого
    • Тихая обработка пустого списка / ошибок (не падать если релиз уже удалён)
  2. Добавить argparse-флаг --keep-latest (по умолчанию False — обратно совместимо, существующие репо без флага не меняют поведение).
  3. В main(): после успешного create_release(repo, tag, body) (когда есть release_id), если args.keep_latest → delete_previous_releases(repo, int(release_id)).
  4. Безопасность: удаление ПОСЛЕ успешного POST. Если создание упало — старые релизы целы (можно разобраться).
  5. Теги НЕ удалять (отдельная задача, вне scope — теги в Forgejo UI на отдельной странице, Releases не засоряют; удаление тегов из CI требует push-прав и рискует удалить текущий тег).

Контракты

  • delete_previous_releases(repo, keep_id) использует существующий _api() helper (GET + DELETE).
  • Флаг --keep-latest — opt-in: python ..\scripts\create_release.py --keep-latest.
  • Без флага — поведение не меняется (0.1.0 voice_assistant остаётся как есть).
  • Возвращает None. Не raises (ошибки логирует через _warn, не падают CI).

Инварианты

  • Идемпотентность сохраняется: повторный запуск с тем же тегом удаляет existing release того же tag (через delete_existing_release) + если --keep-latest удаляет все остальные.
  • Порядок в main(): delete_existing_release(repo, tag) → create_release(repo, tag, body) → if keep_latest: delete_previous_releases(repo, release_id) → upload_asset(...).
  • Asset upload НЕ зависит от keep-latest — загружается всегда.
  • release_id валиден (проверка уже есть: if not release_id: _die).

Граничные случаи

  • 0 предыдущих релизов (первый релиз репо) — delete_previous_releases ничего не делает, warn не печатает.
  • 1 предыдущий релиз — удаляется, остаётся только новый.
  • N предыдущих релизов — удаляются все в цикле.
  • DELETE неудачен (404, 403) — _api() уже вызывает _die на HTTPError. Решение: для delete_previous использовать мягкую обработку (try/except HTTPError → _warn, не падать). Иначе один битый релиз заблокирует весь CI. Рекомендация: добавить параметр soft: bool = False в _api() или отдельный _api_soft() для delete-цикла.
  • Только что созданный релиз (release_id) — исключён из удаления (rel['id'] != keep_id).
  • Draft/prerelease релизы — удаляются тоже (фильтра нет, все кроме keep_id). Если нужна фильтрация — отдельный параметр, но вне scope.

Влияние на связанные компоненты

  • .opencode/scripts/create_release.py — +1 функция delete_previous_releases, +argparse, +3 строки в main().
  • .opencode/scripts/release_notes.py — без изменений.
  • voice_assistant/.github/workflows/release.yml — опционально добавить --keep-latest (отдельная задача пользователя, не этого issue).
  • video_uniq/.github/workflows/release.yml — опционально добавить --keep-latest (после перевода video_uniq на curl-download, отдельная задача).

Вне scope

  • Удаление тегов (git push --delete — требует push-прав, рискует текущим тегом).
  • Фильтрация по draft/prerelease (удалять все кроме keep_id).
  • Универсальный --keep-latest N (оставить N последних) — только boolean --keep-latest (1 последний).
  • Unit-тесты (мок urllib) — опционально, не блокирует.
  • Перевод video_uniq на curl-download мастер-копии — отдельный issue в video_uniq.

Критерии приемки

  1. python create_release.py --help показывает --keep-latest флаг.
  2. Без флага — поведение не изменилось (voice_assistant v0.1.0 остаётся).
  3. С флагом --keep-latest — после успешного POST нового релиза все остальные релизы удаляются (остаётся только созданный).
  4. Если POST упал — старые релизы целы (удаление после POST, не до).
  5. DELETE 404/403 на отдельном релизе — не падает CI, логирует warn, продолжает цикл.
  6. ruff check .opencode/scripts/create_release.py — чисто.
  7. mypy .opencode/scripts/create_release.py (если применимо) — 0 errors.
## Контекст Мастер-копия `create_release.py` (используется voice_assistant и — после перевода — video_uniq через curl-download) создаёт релиз и загружает asset, удаляя только existing release с тем же tag (идемпотентность). При новой версии (например v0.1.23) предыдущий релиз v0.1.22 остаётся в списке Releases. Пользователь хочет: при создании нового релиза автоматически удалять все предыдущие, оставляя только последний — чтобы в UI Forgejo всегда был один актуальный релиз. Сейчас удаление предыдущих релизов делается вручную (оркестратор через Python urllib, curl DELETE запрещён permission-правилами). Цель — автоматизировать в CI. ## Задача 1. Добавить функцию `delete_previous_releases(repo: str, keep_id: int) -> None` в `create_release.py`: - `GET /api/v1/repos/{repo}/releases` → список всех релизов - Для каждого `rel['id'] != keep_id` → `DELETE /api/v1/repos/{repo}/releases/{rel['id']}` - Логирование: `_warn(f"deleting previous release {tag} (id={id})")` для каждого удалённого - Тихая обработка пустого списка / ошибок (не падать если релиз уже удалён) 2. Добавить argparse-флаг `--keep-latest` (по умолчанию `False` — обратно совместимо, существующие репо без флага не меняют поведение). 3. В `main()`: после успешного `create_release(repo, tag, body)` (когда есть `release_id`), если `args.keep_latest` → `delete_previous_releases(repo, int(release_id))`. 4. Безопасность: удаление ПОСЛЕ успешного POST. Если создание упало — старые релизы целы (можно разобраться). 5. Теги НЕ удалять (отдельная задача, вне scope — теги в Forgejo UI на отдельной странице, Releases не засоряют; удаление тегов из CI требует push-прав и рискует удалить текущий тег). ## Контракты - `delete_previous_releases(repo, keep_id)` использует существующий `_api()` helper (GET + DELETE). - Флаг `--keep-latest` — opt-in: `python ..\scripts\create_release.py --keep-latest`. - Без флага — поведение не меняется (0.1.0 voice_assistant остаётся как есть). - Возвращает None. Не raises (ошибки логирует через `_warn`, не падают CI). ## Инварианты - Идемпотентность сохраняется: повторный запуск с тем же тегом удаляет existing release того же tag (через `delete_existing_release`) + если `--keep-latest` удаляет все остальные. - Порядок в main(): `delete_existing_release(repo, tag)` → `create_release(repo, tag, body)` → `if keep_latest: delete_previous_releases(repo, release_id)` → `upload_asset(...)`. - Asset upload НЕ зависит от keep-latest — загружается всегда. - release_id валиден (проверка уже есть: `if not release_id: _die`). ## Граничные случаи - **0 предыдущих релизов** (первый релиз репо) — `delete_previous_releases` ничего не делает, warn не печатает. - **1 предыдущий релиз** — удаляется, остаётся только новый. - **N предыдущих релизов** — удаляются все в цикле. - **DELETE неудачен** (404, 403) — `_api()` уже вызывает `_die` на HTTPError. Решение: для delete_previous использовать мягкую обработку (try/except HTTPError → `_warn`, не падать). Иначе один битый релиз заблокирует весь CI. Рекомендация: добавить параметр `soft: bool = False` в `_api()` или отдельный `_api_soft()` для delete-цикла. - **Только что созданный релиз** (release_id) — исключён из удаления (`rel['id'] != keep_id`). - **Draft/prerelease релизы** — удаляются тоже (фильтра нет, все кроме keep_id). Если нужна фильтрация — отдельный параметр, но вне scope. ## Влияние на связанные компоненты - `.opencode/scripts/create_release.py` — +1 функция `delete_previous_releases`, +argparse, +3 строки в main(). - `.opencode/scripts/release_notes.py` — без изменений. - `voice_assistant/.github/workflows/release.yml` — опционально добавить `--keep-latest` (отдельная задача пользователя, не этого issue). - `video_uniq/.github/workflows/release.yml` — опционально добавить `--keep-latest` (после перевода video_uniq на curl-download, отдельная задача). ## Вне scope - Удаление тегов (`git push --delete` — требует push-прав, рискует текущим тегом). - Фильтрация по draft/prerelease (удалять все кроме keep_id). - Универсальный `--keep-latest N` (оставить N последних) — только boolean `--keep-latest` (1 последний). - Unit-тесты (мок urllib) — опционально, не блокирует. - Перевод video_uniq на curl-download мастер-копии — отдельный issue в video_uniq. ## Критерии приемки 1. `python create_release.py --help` показывает `--keep-latest` флаг. 2. Без флага — поведение не изменилось (voice_assistant v0.1.0 остаётся). 3. С флагом `--keep-latest` — после успешного POST нового релиза все остальные релизы удаляются (остаётся только созданный). 4. Если POST упал — старые релизы целы (удаление после POST, не до). 5. DELETE 404/403 на отдельном релизе — не падает CI, логирует warn, продолжает цикл. 6. `ruff check .opencode/scripts/create_release.py` — чисто. 7. `mypy .opencode/scripts/create_release.py` (если применимо) — 0 errors.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
slaid098/opencode-config#104
No description provided.