Files
wp-multi-city/docs/superpowers/specs/2026-05-17-s7-release-puc-design.md
2026-05-17 00:48:44 +05:00

17 KiB
Raw Permalink Blame History

S7 — README + Gitea release + plugin-update-checker

  • Beads: cp-7cf (parent epic cp-cw4)
  • Depends on: cp-la7 (S6 — CI + QA checklist, закрыт)
  • Дата: 2026-05-17

Цель

Закрыть эпик cp-cw4 (MVP-ядро) поставкой плагина в Gitea как self-hosted WordPress plugin с автоматическими обновлениями через Plugin Update Checker (PUC). Эталон flow — уже работающий cf7-spam-to-telegram (PUC v5.6 + release.sh + wp-plugin-info.json + Gitea releases).

Версия для первого релиза — 1.0.0 (MVP завершён, semantic).

S7 — infrastructure + docs only. Один новый приватный метод в Plugin (PUC bootstrap). Без новых функциональных классов.

Артефакты

wp-multi-city/
├── wp-multi-city.php                    ← MODIFY: bump → 1.0.0, header Tested up to/Requires
├── includes/class-plugin.php            ← MODIFY: + register_update_checker()
├── includes/lib/plugin-update-checker/  ← CREATE: vendored PUC v5.6 (copy из cf7stg)
├── wp-plugin-info.json                  ← CREATE: метаданные для PUC
├── readme.txt                           ← CREATE: WP.org формат, источник changelog для release.sh
├── CHANGELOG.md                         ← CREATE: human-readable mirror
├── README.md                            ← MODIFY: + Quick Start + Hook API + Migration guide
├── release.sh                           ← CREATE: адаптация cf7stg/release.sh
├── .gitattributes                       ← CREATE: export-ignore для git archive
└── tests/
    ├── PluginBootS7Test.php             ← CREATE: boot() вызывает register_update_checker
    ├── UpdateCheckerIntegrationTest.php ← CREATE: PucFactory loadable
    └── ZipArchiveBuildTest.php          ← CREATE: git archive integrity

Plugin Update Checker integration

Vendor

Скопировать includes/lib/plugin-update-checker/ (~5 МБ, PUC v5.6 от Yahnis Elsts) из cf7-spam-to-telegram. Vendored код самодостаточен — нет composer-зависимости на проде. Namespace YahnisElsts\PluginUpdateChecker\v5p6\* версионирован — не конфликтует с другими плагинами, использующими PUC.

Bootstrap в Plugin::boot()

private static function register_update_checker(): void
{
    $loader = WPMC_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/wp-multi-city/raw/branch/main/wp-plugin-info.json',
        WPMC_PLUGIN_FILE,
        'wp-multi-city'
    );
}

Вызов добавляется в Plugin::boot() ПОСЛЕ всех *::register() вызовов; защищён существующим $booted idempotency-flag-ом (двойной boot → второй require_once no-op, второй buildUpdateChecker не выполнится из-за $booted guard).

wp-plugin-info.json

{
  "name": "WP Multi City",
  "slug": "wp-multi-city",
  "version": "1.0.0",
  "download_url": "https://git.netranking.ru/bryzgalov/wp-multi-city/releases/download/v1.0.0/wp-multi-city-1.0.0.zip",
  "homepage": "https://git.netranking.ru/bryzgalov/wp-multi-city",
  "requires": "6.0",
  "tested": "6.9",
  "requires_php": "8.0",
  "last_updated": "2026-05-17 00:00:00",
  "author": "Vladimir Bryzgalov",
  "sections": {
    "description": "Мультигородская подсистема: taxonomy town, [shortcode_dmr] для подстановок, URL-роутер /{city}/{path}/, ACF-driven page clones.",
    "changelog": "<h4>1.0.0</h4><ul><li>Первый релиз. MVP-ядро S1S7.</li></ul>"
  }
}

release.sh перегенерирует download_url, version, last_updated, sections.changelog (HTML из readme.txt) после каждого релиза.

release.sh

Адаптация cf7-spam-to-telegram/release.sh (9 шагов). Изменения:

REPO_OWNER="bryzgalov"
REPO_NAME="wp-multi-city"
GITEA_BASE="https://git.netranking.ru"

STEP 2 — bump:

sed -i.bak -E \
    -e "s|^([[:space:]]*\\*[[:space:]]*Version:[[:space:]]*).*$|\\1${VERSION}|" \
    -e "s|define\\('WPMC_VERSION',[[:space:]]*'[^']+'\\)|define('WPMC_VERSION', '${VERSION}')|" \
    wp-multi-city.php
rm wp-multi-city.php.bak

grep -q "^ \* Version: ${VERSION}$" wp-multi-city.php
grep -q "define('WPMC_VERSION', '${VERSION}')" wp-multi-city.php

sed -i.bak -E "s|^(Stable tag:[[:space:]]*).*$|\\1${VERSION}|" readme.txt
rm readme.txt.bak
grep -q "^Stable tag: ${VERSION}$" readme.txt

STEP 4 — zip prefix wp-multi-city/, sanity-list (in/out):

# MUST be in zip:
unzip -p ... wp-multi-city/wp-multi-city.php | grep "^ \* Version: ${VERSION}$"
unzip -p ... wp-multi-city/wp-multi-city.php | grep "WPMC_VERSION', '${VERSION}'"
grep "wp-multi-city/includes/lib/plugin-update-checker/plugin-update-checker.php" <<< "$ZIP_LISTING"
grep "wp-multi-city/wp-plugin-info.json" <<< "$ZIP_LISTING"

# MUST NOT be in zip:
for dev in tests/ docs/ composer.json composer.lock phpunit.xml.dist patchwork.json \
           release.sh CLAUDE.md AGENTS.md .gitattributes .gitignore .gitea/ ; do
    grep "wp-multi-city/${dev}" <<< "$ZIP_LISTING" && fail "dev file in zip: $dev"
done

Шаги 1, 3, 5–9 (валидация → git push → Gitea API create release → upload zip → regenerate wp-plugin-info.json → JSON commit + push → summary) — копия cf7stg без структурных изменений. Только ${REPO_NAME} подставляется в URL.

Prerequisites (валидируются в STEP 1)

  • ~/.gitea-token со scope write:repository (re-use от cf7stg).
  • Чистый git tree на main, in sync с origin/main.
  • readme.txt содержит секцию = ${VERSION} = (fail-fast перед side effects).
  • Команды в PATH: git curl jq unzip awk sed.

.gitattributes

# Exclude dev files from `git archive` (release.sh build)
/tests/                  export-ignore
/docs/                   export-ignore
/phpunit.xml.dist        export-ignore
/patchwork.json          export-ignore
/composer.json           export-ignore
/composer.lock           export-ignore
/.phpunit.result.cache   export-ignore
/.beads/                 export-ignore
/.claude/                export-ignore
/.gitea/                 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

# NOTE: includes/lib/plugin-update-checker/ MUST NOT be excluded —
# required at runtime on target WP site.

Документация

Три файла, три аудитории:

Файл Формат Аудитория Назначение
README.md Markdown Разработчик в Gitea Quick Start, Hook API, Migration guide, roadmap
readme.txt WP.org release.sh + WP admin Stable tag, Tested up to, Changelog в формате = 1.0.0 =
CHANGELOG.md Markdown Human readers Mirror readme.txt changelog в Markdown

readme.txt — единственный источник правды для версии и changelog (release.sh парсит и Stable tag, и блоки = X.Y.Z =). CHANGELOG.md ведётся вручную при bump.

README.md — целевая структура

# WP Multi City

Мультигородская подсистема для WordPress: один сайт обслуживает несколько городов
по схеме `/{city}/{path}/` с подстановкой контента, ссылок и шорткодов.

## Требования
- WordPress 6.0+
- PHP 8.0+
- Advanced Custom Fields Pro

## Установка
1. Скачать zip с https://git.netranking.ru/bryzgalov/wp-multi-city/releases
2. WP admin → Plugins → Add new → Upload (или распаковать в wp-content/plugins/)
3. Активировать. Обновления приходят автоматически (PUC опрашивает Gitea hourly).

## Quick Start (5 минут)
1. WP admin → Шорткоды → Города → создать term `tyumen`, `spb`.
2. WP admin → WPMultiCity → main_town_slug = `spb`.
3. WP admin → Шорткоды → Добавить пост, repeater row town=tyumen text="Тюменский тариф".
4. Создать страницу с `[shortcode_dmr id=42]` → открыть на `/tyumen/<slug>/` → видна подстановка.

## Hook API
### Процедурные функции (wpmc_*)
- `wpmc_current_town(): ?string` — текущий town slug из URL.
- `wpmc_get_term_by_slug(string $slug): ?\WP_Term`
- `wpmc_alt_get_field(string $key, $post_id): mixed` — get_field с fallback.
- `wpmc_remove_domain_link(string $url): string`
- `wpmc_main_town_slug(): ?string`

### WP-фильтры (от плагина)
- `the_title`/`single_cat_title``do_shortcode` (priority 9999)
- `home_url`/`post_link`/`acf/load_value``LinkFilter::add_town_prefix`
- `rest_url``RestUrlFilter::preserve_host`

### Bypass URL-фильтров
\`\`\`php
$canonical = \WPMultiCity\Helpers::without_url_filters(
    fn() => get_permalink($post_id)
);
\`\`\`

## Migration guide (из темы washanyanya)
1. Закомментировать `require inc/town-functions.php` в теме.
2. Активировать wp-multi-city.
3. ACF Pro field group из темы → re-import в новую location (CPT shortcode-town).
4. Заменить `current_town()``wpmc_current_town()` (или прописать алиас).
5. Проверить: `/tyumen/about/` отдаёт правильный page; меню префиксится; `[shortcode_dmr]` рендерит.

## Testing
[существующая секция: composer install; vendor/bin/phpunit; docs/QA-CHECKLIST.md]

## CI
[существующая секция: .gitea/workflows/test.yml]

## Roadmap MVP
| Этап | Содержание | Beads | Статус |
| S1 | Скелет | cp-8ci | done |
| S2 | Taxonomy + CPT + ACF | cp-q7g | done |
| S3 | [shortcode_dmr] + хелперы | cp-arq | done |
| S4 | URL-роутер | cp-83m | done |
| S5 | Глобальные фильтры | cp-i17 | done |
| S6 | CI + QA checklist | cp-la7 | done |
| S7 | Gitea release + PUC | cp-7cf | done |

Эпик `cp-cw4` — CLOSED.

readme.txt — целевая структура

=== WP Multi City ===
Contributors: vbryzgalov
Tags: multisite, multi-city, taxonomy, acf, shortcode
Requires at least: 6.0
Tested up to: 6.9
Requires PHP: 8.0
Stable tag: 1.0.0
License: GPLv2 or later

== Description ==
Мультигородская подсистема: одна WP-инсталляция обслуживает несколько городов
по схеме /{city}/{path}/ с подстановками контента и URL.

== Installation ==
1. Загрузить wp-multi-city в /wp-content/plugins/.
2. Активировать в WP admin.
3. Требуется ACF Pro.

== Changelog ==

= 1.0.0 =
* Первый релиз. MVP-ядро завершено (S1–S7 эпика cp-cw4).
* taxonomy town + CPT shortcode-town + ACF list_shortcode repeater.
* [shortcode_dmr id=N] для подстановок по городу.
* URL-роутер /{city}/{path}/ с поддержкой page_unic/main_page_alt/page_no_rep.
* Глобальные фильтры the_title, home_url, post_link, acf/load_value, rest_url.
* Self-hosted updates через Gitea + plugin-update-checker v5.6.

CHANGELOG.md — целевая структура

# Changelog

All notable changes to wp-multi-city. Format mirrors `readme.txt`.

## [1.0.0] — 2026-05-17

### Added
- Первый релиз. MVP-ядро завершено (S1–S7 эпика cp-cw4).
- `taxonomy town` + CPT `shortcode-town` + ACF `list_shortcode` repeater.
- `[shortcode_dmr id=N]` для подстановок по городу.
- URL-роутер `/{city}/{path}/` (page_unic, main_page_alt, page_no_rep).
- Глобальные фильтры (the_title, home_url, post_link, acf/load_value, rest_url).
- Self-hosted updates через Gitea + plugin-update-checker v5.6.

wp-multi-city.php — изменения

/**
 * Plugin Name: WP Multi City
 * Description: Мультигородская подсистема: taxonomy town, шорткоды, URL-роутер /{city}/{path}/, ACF page-clones.
 * Version: 1.0.0
 * Author: Vladimir Bryzgalov
 * Requires PHP: 8.0
 * Requires at least: 6.0
 * Tested up to: 6.9
 * License: GPLv2 or later
 * Text Domain: wp-multi-city
 */
...
defined('WPMC_VERSION')     || define('WPMC_VERSION', '1.0.0');

Тестовый план

tests/PluginBootS7Test.php (2 теста)

  1. boot() загружает PUC factory: после Plugin::instance()->boot() class_exists('YahnisElsts\\PluginUpdateChecker\\v5\\PucFactory') возвращает true.
  2. boot() идемпотентен для PUC: двойной boot() не бросает (защита от double-require + double-buildUpdateChecker — гарантируется $booted flag-ом).

tests/UpdateCheckerIntegrationTest.php (2 теста)

  1. tests/UpdateCheckerIntegrationTest::test_puc_loader_exists: файл includes/lib/plugin-update-checker/plugin-update-checker.php существует.
  2. tests/UpdateCheckerIntegrationTest::test_puc_factory_loadable: после require_once PUC loader-а, PucFactory класс загружается.

tests/ZipArchiveBuildTest.php (2 теста)

  1. git archive HEAD --worktree-attributes --format=zip --prefix=wp-multi-city/:
    • В zip присутствуют: wp-multi-city.php, includes/lib/plugin-update-checker/plugin-update-checker.php, wp-plugin-info.json, readme.txt, README.md.
    • В zip отсутствуют: tests/, docs/, composer.json, phpunit.xml.dist, patchwork.json, release.sh, CLAUDE.md, .gitattributes, .gitea/.
  2. wp-plugin-info.json — валидный JSON с обязательными ключами (slug=wp-multi-city, version, download_url содержит git.netranking.ru/bryzgalov/wp-multi-city).

Итого: 98 + 6 = 104 теста / ~185 assertions.

Acceptance verification

  1. vendor/bin/phpunit — 104 теста зелёные.
  2. git archive HEAD --worktree-attributes --format=zip --prefix=wp-multi-city/ -o /tmp/wpmc-test.zip && unzip -l /tmp/wpmc-test.zip — никаких dev-файлов в архиве, PUC loader на месте, wp-plugin-info.json на месте.
  3. wp-multi-city.php header: Version: 1.0.0, константа WPMC_VERSION = '1.0.0'.
  4. readme.txt валидный: Stable tag: 1.0.0, секция = 1.0.0 = существует, awk извлечение возвращает не-пустой changelog body.
  5. README обновлён: Quick Start, Hook API, Migration guide на месте; roadmap S7 = done; эпик cp-cw4 помечен CLOSED.
  6. PHP-lint clean: find includes -name '*.php' -not -path '*/lib/*' -exec php -l {} \;.
  7. (Manual, after S7 merge) Gitea репо bryzgalov/wp-multi-city создан; ./release.sh 1.0.0 отрабатывает все 9 шагов без ошибок; в Gitea появляется release v1.0.0 с zip-asset; URL https://git.netranking.ru/bryzgalov/wp-multi-city/raw/branch/main/wp-plugin-info.json отдаёт обновлённый JSON.

Gitea репозиторий — ручные шаги (вне плана)

  1. В Gitea UI (git.netranking.ru/bryzgalov) → New Repository → name wp-multi-city, public/private по выбору, без README (он у нас уже есть локально).
  2. На локальной машине после merge S7:
    cd wp-multi-city
    git remote add origin https://git.netranking.ru/bryzgalov/wp-multi-city.git
    git push -u origin main
    
  3. ~/.gitea-token уже существует от cf7stg релизов — используется как есть. Token scope: write:repository.
  4. После первого push: ./release.sh 1.0.0 создаёт релиз v1.0.0.

Что НЕ входит в S7

  • Программное создание Gitea-репозитория (ручной шаг через UI).
  • PHP matrix в CI (8.0/8.1/8.2/8.3) — отложено.
  • Lint job (phpcs/phpstan) в CI — отложено.
  • Composer-based PUC (vendor-в-git проще: нет composer install на проде).
  • Migration guide для тем кроме washanyanya (универсальный — out of MVP).
  • Translations / .pot файл — out of MVP.

Open questions

Нет. Все решения зафиксированы.