[@dark-sun] ci(release): FF-промоушн dev→testnet→main вместо merge -X theirs
Релизный флоу переведён на линейную fast-forward модель — устраняет регулярные конфликты на 20 package.json при релизе. Корень проблемы: publish-alpha.sh/publish-prod.sh бампали версию НА КАЖДОЙ ветке (alpha-N на testnet, чистую на main) через `git merge -X theirs` + back-merge. Два независимых bump-коммита за цикл + merge'и плодили расхождение веток → конфликты (особенно при гонке push/pull). Новая модель: - Версию бампает lerna ОДИН раз на dev (scripts/cut-release.sh). - Тот же коммит едет вверх по FF: scripts/promote.sh testnet|main (server-side fast-forward push, рабочее дерево не трогается). - testnet/main не несут своих коммитов → ветки не диверджатся → конфликты структурно невозможны. release.yaml: - Триггер: push в testnet/main с изменением lerna.json (вместо тега v*). Окружение определяет ВЕТКА (main→prod, testnet→staging), не суффикс -alpha. - Версия читается из закоммиченного lerna.json (едет с коммитом по FF). - Гейты npm-publish/доки: branch == main (вместо !contains '-alpha'). - Образы/webhook по-прежнему версия-тегированы → playbooks/приёмник деплоя не затрагиваются. Удалены publish-alpha.sh/publish-prod.sh и мёртвые дубли тех же merge-X-theirs скриптов (root sync:main, components/contracts production/testnet/docs-publish). Документация — scripts/RELEASE.md + CLAUDE.md PR-flow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
# Релизный флоу: FF-промоушн dev → testnet → main
|
||||
|
||||
Линейная модель без merge и без конфликтов. Версию бампает `lerna` один раз на
|
||||
`dev`, и тот же коммит едет вверх по **fast-forward**. `testnet` и `main` не
|
||||
несут собственных коммитов — это указатели на протестированный коммит `dev`,
|
||||
поэтому ветки никогда не диверджатся и merge-конфликтов не бывает.
|
||||
|
||||
## Как релизить
|
||||
|
||||
```bash
|
||||
# 1. Нарезать релиз на dev (бамп версии lerna, БЕЗ деплоя)
|
||||
scripts/cut-release.sh
|
||||
|
||||
# 2. Промоут на staging (деплой в testnet)
|
||||
scripts/promote.sh testnet
|
||||
|
||||
# 3. Промоут в production (деплой main + npm publish + доки)
|
||||
scripts/promote.sh main
|
||||
```
|
||||
|
||||
- `cut-release.sh` — строго на `dev`. Считает версию `vYYYY.M.D` (повторный
|
||||
релиз того же дня → суффикс `-N`), вызывает `lerna version` (бамп всех
|
||||
package.json + lerna.json, коммит `chore(release): publish`, тег, push `dev`).
|
||||
**Деплой не запускает** — CI не слушает `dev`.
|
||||
- `promote.sh testnet|main` — server-side **fast-forward** push
|
||||
`origin/<source>` → `origin/<target>`, рабочее дерево не трогается.
|
||||
|
||||
## Что триггерит деплой
|
||||
|
||||
`.github/workflows/release.yaml` слушает **push в `testnet`/`main` с изменением
|
||||
`lerna.json`**. lerna.json меняется только при релизном бампе (на `dev`), и этот
|
||||
коммит входит в диапазон FF-промоушна — поэтому деплоятся именно релизы, а не
|
||||
обычные feature-пуши.
|
||||
|
||||
Окружение определяет **ветка**, не суффикс версии:
|
||||
|
||||
| Ветка | Окружение | Деплой-webhook | npm publish / доки |
|
||||
|-----------|----------------------|------------------------|--------------------|
|
||||
| `testnet` | staging | `TESTNET_WEBHOOK_URL` | нет |
|
||||
| `main` | production | `PRODUCTION_WEBHOOK_URL` | да (`main`) |
|
||||
|
||||
Версия для образов (`dicoop/*:vX`), webhook-payload и `lerna publish
|
||||
from-package` читается из закоммиченного `lerna.json` — едет с коммитом по FF.
|
||||
Для приёмника деплоя (playbooks) ничего не меняется: webhook по-прежнему
|
||||
получает версию-строку, образы тегаются версией.
|
||||
|
||||
## Инварианты (не нарушать)
|
||||
|
||||
- **В `testnet`/`main` — только FF-промоушн, никаких прямых коммитов.** Прямой
|
||||
коммит сделает ветку не-FF; `promote.sh` тогда отвергнется push'ем (это гард,
|
||||
а не баг — выровняй ветку).
|
||||
- **Версия бампается только на `dev`** (`cut-release.sh`). Никаких повторных
|
||||
бампов на `testnet`/`main` — иначе вернутся расхождение и конфликты.
|
||||
- Тег (`vX`), который вешает `lerna version`, — маркер версии и основа для
|
||||
GitHub Release (`scripts/publish-release.sh`); триггером деплоя он **не**
|
||||
является.
|
||||
|
||||
## Откуда ушли (история)
|
||||
|
||||
Старые `publish-alpha.sh`/`publish-prod.sh` бампали версию **на каждой ветке**
|
||||
(`-alpha-N` на testnet, чистую на main) через `git merge -X theirs` +
|
||||
back-merge. Два независимых бампа за цикл + merge'и плодили расхождение → 20
|
||||
package.json регулярно конфликтовали (особенно при гонке push/pull). FF-модель
|
||||
это устраняет конструктивно.
|
||||
Executable
+47
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Режет релиз СТРОГО на dev. Версия бампается ОДИН раз (lerna) и дальше едет
|
||||
# вверх по fast-forward dev → testnet → main без повторных бампов и merge —
|
||||
# отсюда ноль конфликтов (см. scripts/RELEASE.md).
|
||||
#
|
||||
# Сам по себе cut НЕ деплоит: CI триггерится push'ем в testnet/main, не в dev.
|
||||
# Деплой запускается промоушном: scripts/promote.sh testnet → scripts/promote.sh main.
|
||||
|
||||
git fetch origin
|
||||
|
||||
# Режем от свежего origin/dev.
|
||||
git checkout dev
|
||||
git pull --ff-only origin dev
|
||||
|
||||
# Версия: vYYYY.M.D (без лидирующих нулей), для повторных релизов того же дня — суффикс -N.
|
||||
BASE_VERSION="v$(date +%Y).$(date +%-m).$(date +%-d)"
|
||||
|
||||
MAX_SUFFIX=0
|
||||
HAS_BASE=0
|
||||
for TAG in $(git tag --list | grep "^$BASE_VERSION" | sort -V); do
|
||||
if [[ "$TAG" == "$BASE_VERSION" ]]; then
|
||||
HAS_BASE=1
|
||||
elif [[ "$TAG" =~ ^${BASE_VERSION}-([0-9]+)$ ]]; then
|
||||
SUFFIX="${BASH_REMATCH[1]}"
|
||||
(( SUFFIX > MAX_SUFFIX )) && MAX_SUFFIX=$SUFFIX
|
||||
fi
|
||||
done
|
||||
|
||||
if (( HAS_BASE == 0 )) && (( MAX_SUFFIX == 0 )); then
|
||||
VERSION="$BASE_VERSION"
|
||||
else
|
||||
VERSION="$BASE_VERSION-$((MAX_SUFFIX + 1))"
|
||||
fi
|
||||
|
||||
echo "Режем релиз на dev: $VERSION"
|
||||
|
||||
# lerna сам бампит все package.json + lerna.json, коммитит chore(release): publish,
|
||||
# вешает тег и пушит dev. Тег нужен как маркер версии и для GitHub Release —
|
||||
# триггером деплоя он больше НЕ является (деплой по push в ветку).
|
||||
lerna version "$VERSION" --yes --no-push=false --no-git-tag-version=false --force-publish
|
||||
|
||||
echo
|
||||
echo "Готово. Версия $VERSION закоммичена на dev."
|
||||
echo "Дальше: scripts/promote.sh testnet (staging-деплой)"
|
||||
echo "потом: scripts/promote.sh main (production-деплой + npm publish + доки)"
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Fast-forward промоушн БЕЗ merge: target-ветка просто двигается на коммит
|
||||
# source-ветки. testnet и main не несут собственных коммитов — это указатели на
|
||||
# протестированный коммит dev. Ветки не диверджатся → конфликты невозможны.
|
||||
#
|
||||
# scripts/promote.sh testnet # dev → testnet (staging-деплой)
|
||||
# scripts/promote.sh main # testnet → main (production-деплой)
|
||||
#
|
||||
# Если push отвергнут как non-fast-forward — это ГАРД, не баг: значит в target
|
||||
# попал прямой коммит, и target обогнал source. Выровняй вручную (в testnet/main
|
||||
# прямых коммитов быть не должно — только FF-промоушн).
|
||||
|
||||
TARGET="$1"
|
||||
case "$TARGET" in
|
||||
testnet) SOURCE="dev" ;;
|
||||
main) SOURCE="testnet" ;;
|
||||
*)
|
||||
echo "Использование: $0 <testnet|main>"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
git fetch origin
|
||||
|
||||
echo "FF-промоушн: origin/$SOURCE → origin/$TARGET"
|
||||
|
||||
# Server-side fast-forward: двигаем удалённый $TARGET на коммит удалённого $SOURCE.
|
||||
# Рабочее дерево не трогаем (никаких checkout/merge). Без --force: сервер
|
||||
# отвергнет, если это не fast-forward.
|
||||
git push origin "origin/${SOURCE}:refs/heads/${TARGET}"
|
||||
|
||||
if [ "$TARGET" = "main" ]; then
|
||||
ENV_LABEL="PRODUCTION (+ npm publish + доки)"
|
||||
else
|
||||
ENV_LABEL="staging/testnet"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Готово. origin/$TARGET = origin/$SOURCE."
|
||||
echo "CI задеплоит: $ENV_LABEL."
|
||||
@@ -1,41 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Получаем дату в формате vYYYY.M.D без лидирующих нулей
|
||||
BASE_VERSION="v$(date +%Y).$(date +%-m).$(date +%-d)"
|
||||
|
||||
# Получаем все теги с сегодняшней датой
|
||||
TAGS=($(git tag --list | grep "^$BASE_VERSION" | sort -V))
|
||||
|
||||
MAX_SUFFIX=0
|
||||
HAS_BASE=0
|
||||
|
||||
for TAG in "${TAGS[@]}"; do
|
||||
if [[ "$TAG" == "$BASE_VERSION" ]]; then
|
||||
HAS_BASE=1
|
||||
elif [[ "$TAG" =~ ^${BASE_VERSION}-(.*)$ ]]; then
|
||||
SUFFIX=${TAG##*-}
|
||||
if [[ $SUFFIX =~ ^[0-9]+$ ]] && (( SUFFIX > MAX_SUFFIX )); then
|
||||
MAX_SUFFIX=$SUFFIX
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Для тестнета всегда добавляем суффикс alpha-N
|
||||
ALPHA_SUFFIX="alpha-$((MAX_SUFFIX+1))"
|
||||
VERSION="$BASE_VERSION-$ALPHA_SUFFIX"
|
||||
|
||||
echo "Версионируем тестнет: $VERSION"
|
||||
|
||||
# Переходим на testnet и мержим dev
|
||||
git checkout -f testnet
|
||||
git merge -X theirs dev
|
||||
|
||||
# Версионируем с alpha суффиксом
|
||||
lerna version "$VERSION" --yes --no-push=false --no-git-tag-version=false --force-publish
|
||||
|
||||
git push --follow-tags
|
||||
|
||||
# Возвращаемся на dev и мержим testnet
|
||||
git checkout dev
|
||||
git merge testnet
|
||||
@@ -1,43 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Получаем дату в формате vYYYY.M.D без лидирующих нулей
|
||||
BASE_VERSION="v$(date +%Y).$(date +%-m).$(date +%-d)"
|
||||
|
||||
# Получаем все теги с сегодняшней датой
|
||||
TAGS=($(git tag --list | grep "^$BASE_VERSION" | sort -V))
|
||||
|
||||
MAX_SUFFIX=0
|
||||
HAS_BASE=0
|
||||
|
||||
for TAG in "${TAGS[@]}"; do
|
||||
if [[ "$TAG" == "$BASE_VERSION" ]]; then
|
||||
HAS_BASE=1
|
||||
elif [[ "$TAG" =~ ^${BASE_VERSION}-(.*)$ ]]; then
|
||||
SUFFIX=${TAG##*-}
|
||||
if [[ $SUFFIX =~ ^[0-9]+$ ]] && (( SUFFIX > MAX_SUFFIX )); then
|
||||
MAX_SUFFIX=$SUFFIX
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if (( HAS_BASE == 0 )) && (( MAX_SUFFIX == 0 )); then
|
||||
VERSION="$BASE_VERSION"
|
||||
else
|
||||
VERSION="$BASE_VERSION-$((MAX_SUFFIX+1))"
|
||||
fi
|
||||
|
||||
echo "Версионируем: $VERSION"
|
||||
|
||||
# Переходим на main и мержим testnet
|
||||
git checkout -f main
|
||||
git merge -X theirs testnet
|
||||
|
||||
# Только версия и тег, без публикации
|
||||
lerna version "$VERSION" --yes --no-push=false --no-git-tag-version=false --force-publish
|
||||
|
||||
git push --follow-tags
|
||||
|
||||
# Возвращаемся на dev и мержим main
|
||||
git checkout dev
|
||||
git merge main
|
||||
Reference in New Issue
Block a user