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

369 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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()`
```php
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`
```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 шагов). Изменения:
```bash
REPO_OWNER="bryzgalov"
REPO_NAME="wp-multi-city"
GITEA_BASE="https://git.netranking.ru"
```
**STEP 2 — bump:**
```bash
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):
```bash
# 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 — целевая структура
```markdown
# 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 — целевая структура
```markdown
# 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 — изменения
```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 теста)
3. `tests/UpdateCheckerIntegrationTest::test_puc_loader_exists`: файл `includes/lib/plugin-update-checker/plugin-update-checker.php` существует.
4. `tests/UpdateCheckerIntegrationTest::test_puc_factory_loadable`: после `require_once` PUC loader-а, `PucFactory` класс загружается.
### `tests/ZipArchiveBuildTest.php` (2 теста)
5. `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/`.
6. `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:
```bash
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
Нет. Все решения зафиксированы.