Files
cf7-spam-to-telegram/docs/superpowers/specs/2026-04-21-cf7stg-self-hosted-updates-design-DRAFT.md
T
Vladimir Bryzgalov f7d81a4517 docs(spec): DRAFT design for self-hosted updates from Gitea (1.2.0)
Brainstorm session ran out of context — saving fully-formed design as
DRAFT so the next session can resume from spec self-review + finalize +
writing-plans without redoing the brainstorm.

Beads epic: cf7stg-5wy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 18:46:35 +05:00

14 KiB
Raw Blame History

CF7 Spam → Telegram — Self-Hosted Updates from Gitea (1.2.0) — DRAFT

Статус: DRAFT — дизайн обсуждён в брейнсторме, пользователь подтвердил разделы 1 и 2. Не финализировано (нет финального spec-документа без суффикса DRAFT, нет implementation plan, нет beads-подзадач).

Что нужно сделать в новой сессии:

  1. Перечитать этот документ.
  2. Спросить пользователя: «По дизайну self-hosted updates (1.2.0) всё ок? Если да — финализирую spec (без DRAFT), пробегусь по self-review, попрошу глянуть, потом writing-plans».
  3. Если ок — git mv ...DRAFT.md в финальное имя 2026-04-21-cf7stg-self-hosted-updates-design.md, повторить self-review, попросить пользователя глянуть финал, потом invoke superpowers:writing-plans.

Контекст

Trigger: только что выпустили v1.1.0 (silent drop). Деплой 1.1.0 на домработница.рус сделан вручную через rsync. Этот же плагин будет деплоиться на другие сайты (washanyanya.ru, потенциально другие). Хочется, чтобы WP-сайты сами получали обновления через стандартный «Плагины → Обновления», без rsync и без знания внутренностей.

Решено в брейнсторме:

  • Gitea репо bryzgalov/cf7-spam-to-telegram сделан публичным (пользователь подтвердил). В коде нет секретов.
  • Используем Plugin Update Checker v5 от YahnisElsts, vendored в includes/lib/plugin-update-checker/.
  • В режиме «Generic JSON server»: PUC ходит в статичный wp-plugin-info.json в корне репо (через Gitea raw URL).
  • Zip-релизы хранятся как Gitea Release assets (создаются вручную через скрипт release.sh, чистые благодаря .gitattributes export-ignore).

Scope

В scope:

  • Vendored PUC v5 в includes/lib/plugin-update-checker/.
  • Файл wp-plugin-info.json в корне репо (Generic JSON для PUC).
  • Инициализация PUC в Plugin::boot() через register_update_checker().
  • .gitattributes с export-ignore для всех dev-файлов.
  • Bash-скрипт release.sh (bump version → commit → tag → push → build clean zip → Gitea Release + asset upload).
  • Минимальные тесты на инвариант (PUC файл существует, JSON валидный, version в JSON совпадает с константой плагина).
  • Версия 1.2.0 + changelog.

Вне scope:

  • Кастомный GiteaApi extends Api (не нужен — публичный JSON).
  • Update-сервер / прокси (не нужен — статичный JSON через raw URL).
  • CI/Gitea Actions для авто-сборки релизов (вручную через release.sh достаточно).
  • License key management.
  • Авто-rollback на старую версию.
  • Уведомления о доступном обновлении куда-либо кроме стандартной WP-админки.

Архитектура

Локальная разработка
  ↓ ./release.sh 1.2.0
git push origin main --tags
git archive --worktree-attributes → cf7-spam-to-telegram-1.2.0.zip
POST Gitea API /releases  → создаёт Release объект
POST Gitea API /releases/{id}/assets  → uploads zip

WP-сайт (плагин 1.2.x уже установлен)
  ↓ Plugin::boot() → register_update_checker()
  ↓ PUC раз в 12 часов (стандартный WP transient)
GET https://git.netranking.ru/bryzgalov/cf7-spam-to-telegram/raw/branch/main/wp-plugin-info.json
  ↓ парсит, сравнивает version с CF7STG_VERSION
  ↓ если новее → WP «Плагины → Обновления» показывает кнопку «Обновить»
WP качает zip по download_url (Gitea release asset, public, без auth)
  ↓ распаковывает в wp-content/plugins/cf7-spam-to-telegram/
Плагин обновлён.

Изменяемые файлы

Файл Что
includes/lib/plugin-update-checker/ (новая папка, ~50 файлов) Распакованный PUC v5 release с https://github.com/YahnisElsts/plugin-update-checker. Закоммичен целиком.
wp-plugin-info.json (новый, корень репо) Статичная metadata, формат Generic JSON для PUC.
includes/class-plugin.php В boot() добавляется self::register_update_checker(). Новый private method.
.gitattributes (изменяется) export-ignore правила для всех dev-файлов.
release.sh (новый, корень, chmod +x) Bash-скрипт релиза.
cf7-spam-to-telegram.php Bump до 1.2.0 (header Version: + константа CF7STG_VERSION).
readme.txt Bump Stable tag: 1.2.0 + Changelog = 1.2.0 =.
.gitignore Добавляется dist/.

wp-plugin-info.json — структура

{
    "name": "CF7 Spam → Telegram",
    "slug": "cf7-spam-to-telegram",
    "version": "1.2.0",
    "download_url": "https://git.netranking.ru/bryzgalov/cf7-spam-to-telegram/releases/download/v1.2.0/cf7-spam-to-telegram-1.2.0.zip",
    "homepage": "https://git.netranking.ru/bryzgalov/cf7-spam-to-telegram",
    "requires": "6.0",
    "tested": "6.9",
    "requires_php": "7.4",
    "last_updated": "2026-04-21 17:00:00",
    "author": "Vladimir Bryzgalov",
    "sections": {
        "description": "Forwards CF7 spam submissions to Telegram with heuristic classification (client/unclear/spam/drop labels).",
        "changelog": "<извлечённый из readme.txt блок == Changelog == ==>"
    }
}

Plugin::boot() изменение

public function boot(): void
{
    Settings::register_classifier_filters();
    Dispatcher::register_hooks();
    (new CF7Source())->register();
    self::register_update_checker();   // ← новый

    if (is_admin()) {
        (new Settings_Page())->register();
        (new Dashboard_Widget())->register();
    }
}

private static function register_update_checker(): void
{
    $loader = CF7STG_PLUGIN_DIR . 'includes/lib/plugin-update-checker/plugin-update-checker.php';
    if (!is_file($loader)) return;          // не падаем, если папка отсутствует
    require_once $loader;
    if (!class_exists('YahnisElsts\\PluginUpdateChecker\\v5\\PucFactory')) return;

    \YahnisElsts\PluginUpdateChecker\v5\PucFactory::buildUpdateChecker(
        'https://git.netranking.ru/bryzgalov/cf7-spam-to-telegram/raw/branch/main/wp-plugin-info.json',
        CF7STG_PLUGIN_FILE,
        'cf7-spam-to-telegram'
    );
}

release.sh (главные шаги)

release.sh 1.2.0  →
  1. validate (semver, clean tree, на main, есть ~/.gitea-token)
  2. bump version в cf7-spam-to-telegram.php (header + define) и readme.txt (Stable tag)
  3. update wp-plugin-info.json (version, download_url, last_updated, sections.changelog ← из readme.txt)
  4. git add + git commit -m "release: 1.2.0" + git tag v1.2.0 + git push origin main --tags
  5. mkdir -p dist
     git archive --worktree-attributes --format=zip --prefix=cf7-spam-to-telegram/ -o dist/cf7-spam-to-telegram-1.2.0.zip v1.2.0
  6. POST /api/v1/repos/.../releases  (создать Release объект)
  7. POST /api/v1/repos/.../releases/{id}/assets  (загрузить zip как asset)
  8. echo URLs (release page, asset URL, PUC JSON URL)

Token хранится в ~/.gitea-token (chmod 600), не в репо. Создание токена — единожды, в Gitea UI: user/settings/applications → scope write:repository.

.gitattributes правила

# Исключаем dev-файлы из git archive (release zip)
tests/                 export-ignore
docs/                  export-ignore
composer.json          export-ignore
composer.lock          export-ignore
phpunit.xml            export-ignore
.phpunit.result.cache  export-ignore
.beads/                export-ignore
.claude/               export-ignore
CLAUDE.md              export-ignore
AGENTS.md              export-ignore
release.sh             export-ignore
dist/                  export-ignore
.gitignore             export-ignore
.gitattributes         export-ignore
.DS_Store              export-ignore

ВАЖНО: includes/lib/plugin-update-checker/ НЕ исключаем — она нужна в zip!

Тесты

tests/UpdateCheckerIntegrationTest.php (extends Cf7stgTestCase):

  • test_puc_loader_file_exists()includes/lib/plugin-update-checker/plugin-update-checker.php существует.
  • test_puc_factory_class_loadable_after_require() — после require_once лоадера класс YahnisElsts\PluginUpdateChecker\v5\PucFactory доступен.
  • test_wp_plugin_info_json_is_valid()wp-plugin-info.json парсится, содержит обязательные ключи name, slug, version, download_url, sections.
  • test_wp_plugin_info_json_version_matches_plugin_constant()wp-plugin-info.json::version совпадает с CF7STG_VERSION из cf7-spam-to-telegram.php (regex extract). Защита: release.sh забыл обновить JSON — тест поймает.
  • test_wp_plugin_info_json_download_url_matches_version()download_url содержит /releases/download/v{version}/.

tests/ZipArchiveBuildTest.php (опционально, требует git в $PATH):

  • test_git_archive_includes_runtime_dirs()git archive HEAD --worktree-attributes -o /tmp/test.zip распаковывается, содержит cf7-spam-to-telegram.php, includes/, admin/, includes/lib/plugin-update-checker/plugin-update-checker.php.
  • test_git_archive_excludes_dev_dirs() — НЕТ tests/, docs/, composer.json, release.sh.

Error handling

  • Отсутствие PUC папки на сайте: register_update_checker делает is_file + class_exists → плагин не падает, обновлений просто нет (как до 1.2.0).
  • Недоступность Gitea (network/down/4xx): PUC сам логирует через error_log, ничего не показывает в админке. Следующая итерация (12ч) повторит. Юзер не видит «обновлений» — всё.
  • Bad JSON в wp-plugin-info.json: PUC скипает, error_log.
  • Версия в JSON совпадает с локальной → no update notice. Норм.

Rollout

  1. Локально: создать токен в Gitea (один раз), сохранить в ~/.gitea-token.
  2. Локально: подготовить v1.2.0 (vendor PUC, добавить файлы, написать release.sh, тесты).
  3. PHPUnit зелёный → ./release.sh 1.2.0 → tag + push + Release + asset.
  4. На домработница.рус (где сейчас 1.1.0) — один последний раз через rsync обновить до 1.2.0. Активировать плагин (auto-seed уже идемпотентный). PUC начинает работать.
  5. Все будущие версии — ./release.sh 1.x.y, и через 12ч (или после ручного «Проверить обновления») WP-сайты сами обновятся.

Версионирование

  • 1.1.01.2.0 (semver minor: новая фича, обратная совместимость).
  • Changelog в readme.txt:
    = 1.2.0 =
    * New: Self-hosted plugin updates from Gitea via plugin-update-checker (Yahnis Elsts v5).
      WP «Плагины → Обновления» теперь показывает обновления автоматически (раз в 12ч),
      скачивая чистый zip из Gitea Releases. Источник:
      https://git.netranking.ru/bryzgalov/cf7-spam-to-telegram/raw/branch/main/wp-plugin-info.json.
    * Dev: release.sh — однострочный релиз (bump version → commit → tag → push →
      build clean zip via git archive --worktree-attributes → Gitea Release + asset upload).
    * Dev: .gitattributes export-ignore для tests/, docs/, composer.*, phpunit.xml,
      .beads/, .claude/, CLAUDE.md, AGENTS.md, release.sh — git archive собирает
      чистый zip без dev-файлов.
    

Открытые вопросы для следующей сессии

Эти моменты НЕ обсуждены с пользователем явно — могут потребовать одного-двух уточнений перед finalizing spec:

  1. Какую точно версию PUC v5 vendor'им? Скачать последний release с https://github.com/YahnisElsts/plugin-update-checker/releases (на 2026-04-21 это видимо 5.x.x). В spec зафиксировать конкретную версию для воспроизводимости.
  2. Уважает ли Gitea raw URL HTTPS на git.netranking.ru cache headers? PUC ставит If-Modified-Since, важно чтобы сервер не ломал. Проверить вручную через curl -I https://git.netranking.ru/.../raw/branch/main/wp-plugin-info.json — если есть Last-Modified / ETag — отлично.
  3. Минификация PUC? Vendored PUC ~50 файлов / ~300KB. Не критично, но можно опционально удалить examples/, tests/, docs/ из vendored папки. Рассмотреть как nit.
  4. Поддержка Gitea Actions — пользователь не запрашивал, но если есть желание авто-релизов на push tag — это +1 эпик в будущем.