diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index a7111e5..02e863b 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -5,6 +5,8 @@ # # Секреты (Settings → Actions → Secrets): # RELEASE_TOKEN (необязательно) — токен для релиза; без него — встроенный токен job (права на запись в репозиторий) +# Переменные (Settings → Actions → Variables): +# PUBLIC_URL (необязательно) — внешний адрес Gitea для ссылок в тексте релиза; по умолчанию https://gitea.hsrv.site # ANDROID_KEYSTORE_BASE64, ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_ALIAS, ANDROID_KEY_PASSWORD — подпись APK name: release @@ -37,6 +39,7 @@ jobs: env: GITEA_API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }} GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }} + PUBLIC_URL: ${{ vars.PUBLIC_URL || 'https://gitea.hsrv.site' }} TAG: ${{ github.ref_name }} VERSION: ${{ steps.v.outputs.PIPU_VERSION }} PRERELEASE: ${{ steps.v.outputs.PIPU_PRERELEASE }} @@ -60,6 +63,7 @@ jobs: # для scripts/release.mjs GITEA_API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }} GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }} + PUBLIC_URL: ${{ vars.PUBLIC_URL || 'https://gitea.hsrv.site' }} TAG: ${{ github.ref_name }} VERSION: ${{ needs.release.outputs.version }} VERSION_CODE: ${{ needs.release.outputs.version_code }} @@ -141,3 +145,8 @@ jobs: - name: Статус в релизе — сборка не удалась if: failure() run: node scripts/release.mjs android failure + + # срабатывает при отмене из интерфейса; если раннер убит целиком, статус обновит следующий запуск + - name: Статус в релизе — сборка отменена + if: cancelled() + run: node scripts/release.mjs android cancelled diff --git a/docs/ci-cd.md b/docs/ci-cd.md index 9b124b6..5a86046 100644 --- a/docs/ci-cd.md +++ b/docs/ci-cd.md @@ -106,6 +106,10 @@ container: | `RELEASE_TOKEN` | Необязательно. Создание и редактирование релиза, загрузка ассетов (scope `write:repository`). Без него используется встроенный токен job (`GITHUB_TOKEN`), у которого есть права на запись в этот репозиторий | | `ANDROID_KEYSTORE_BASE64`, `ANDROID_KEYSTORE_PASSWORD`, `ANDROID_KEY_ALIAS`, `ANDROID_KEY_PASSWORD` | Подпись APK. **Ключ один на всю жизнь приложения** — хранить резервную копию вне Gitea. Без них финальный релиз падает; `-rc` собирается с отладочной подписью. | +Переменная репозитория `PUBLIC_URL` (Settings → Actions → Variables) — внешний адрес Gitea для ссылок +в тексте релиза, по умолчанию `https://gitea.hsrv.site`. Нужна потому, что job обращается к API по внутреннему +адресу, и ссылки из ответа API ведут туда же. + ## Деплой Автодеплой не обязателен. На сервере в клоне репозитория: `deploy/update.sh [vX.Y.Z]` — checkout тега, diff --git a/scripts/release.mjs b/scripts/release.mjs index 678bfa8..5fa4523 100644 --- a/scripts/release.mjs +++ b/scripts/release.mjs @@ -2,10 +2,12 @@ // Релиз в Gitea (docs/ci-cd.md#релиз): сначала создаётся релиз с заметками, потом сборка Android // докладывает в него APK и переписывает блок статуса в тексте. // node scripts/release.mjs create — создать релиз по тегу (при перезапуске — обновить) -// node scripts/release.mjs android building|failure — статус сборки APK в тексте релиза +// node scripts/release.mjs android building|failure|cancelled — статус сборки APK в тексте релиза // node scripts/release.mjs android success — приложить APK и .sha256, статус «собран» // Окружение: GITEA_API (/api/v1/repos//), GITEA_TOKEN, TAG, VERSION, VERSION_CODE, // PRERELEASE (true|false), GITHUB_RUN_ID; для success — SIGNING (release|debug). +// PUBLIC_URL — внешний адрес Gitea для ссылок в тексте (job ходит в API по внутреннему адресу, и html_url +// из ответа указывает на него же). import { execFileSync } from 'node:child_process'; import { createHash } from 'node:crypto'; @@ -57,7 +59,12 @@ export function renderAndroidStatus({ status, runUrl, versionCode, apk, signing ? ['⚠️ Подписан отладочным ключом: не установится поверх версии с релизной подписью.'] : []), ] - : status === 'failure' + : status === 'cancelled' + ? [ + `⏹ **Android:** сборка APK отменена — [лог сборки](${runUrl}).`, + 'APK появится здесь после перезапуска сборки.', + ] + : status === 'failure' ? [ `❌ **Android:** сборка APK не удалась — [лог сборки](${runUrl}).`, 'Серверная часть релиза от этого не зависит; APK появится здесь после перезапуска сборки.', @@ -182,9 +189,11 @@ function commitsBetween(prevTag, tag) { async function context() { const repo = await api('GET', ''); + const publicUrl = process.env.PUBLIC_URL?.replace(/\/+$/, ''); + const repoUrl = publicUrl ? `${publicUrl}/${repo.full_name}` : repo.html_url; const tag = env('TAG'); const release = await api('GET', `/releases/tags/${encodeURIComponent(tag)}`); - return { repoUrl: repo.html_url, tag, release, runUrl: `${repo.html_url}/actions/runs/${env('GITHUB_RUN_ID')}` }; + return { repoUrl, tag, release, runUrl: `${repoUrl}/actions/runs/${env('GITHUB_RUN_ID')}` }; } async function create() { @@ -242,8 +251,10 @@ async function android(status, apkPath) { async function main() { const [cmd, ...args] = process.argv.slice(2); if (cmd === 'create') return create(); - if (cmd === 'android' && ['building', 'success', 'failure'].includes(args[0])) return android(args[0], args[1]); - throw new Error('использование: release.mjs create | android building|failure | android success '); + if (cmd === 'android' && ['building', 'success', 'failure', 'cancelled'].includes(args[0])) { + return android(args[0], args[1]); + } + throw new Error('использование: release.mjs create | android building|failure|cancelled | android success '); } if (process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1])) { diff --git a/scripts/release.test.mjs b/scripts/release.test.mjs index 2d72e99..0c20dc5 100644 --- a/scripts/release.test.mjs +++ b/scripts/release.test.mjs @@ -59,6 +59,7 @@ test('android status blocks', () => { assert.match(debug, /отладочным ключом/); assert.match(renderAndroidStatus({ status: 'failure', runUrl: 'https://run' }), /не удалась — \[лог сборки\]/); + assert.match(renderAndroidStatus({ status: 'cancelled', runUrl: 'https://run' }), /отменена — \[лог сборки\]/); }); test('replaces only the status block and is idempotent', () => {