Compare commits

...
2 Commits
Author SHA1 Message Date
mrleo1nidandCursor eb8031b126 ci(release): cross-compile Windows NSIS on the Linux runner
ci / ts / ts (packages + web) (push) Failing after 1m41s
release / release (версия и заметки) (push) Successful in 7s
release / android (APK в релиз) (push) Successful in 6m0s
ci / desktop / rust (desktop proxy) (push) Successful in 10m12s
release / desktop (Linux и Windows в релиз) (push) Successful in 20m8s
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-22 00:43:16 +03:00
mrleo1nidandCursor e4b841a8d8 ci(release): attach Linux and Windows desktop installers
ci / ts / ts (packages + web) (push) Failing after 1m42s
release / android (APK в релиз) (push) Successful in 7m49s
release / release (версия и заметки) (push) Successful in 8s
ci / desktop / rust (desktop proxy) (push) Successful in 7m35s
release / desktop (Linux в релиз) (push) Canceled after 2m48s
release / desktop (Windows в релиз) (push) Canceled after 0s
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-22 00:32:33 +03:00
8 changed files with 286 additions and 29 deletions

No files matched your search

+2 -2
View File
@@ -1,5 +1,5 @@
# Rust-часть десктопа: прокси и проверки URL. Полная сборка инсталлятора — локально # Rust-часть десктопа: прокси и проверки URL. Установщики — в release.yml
# (`pnpm --filter @pipu/desktop bundle`): на раннере нет Windows и WebKitGTK. # (Linux AppImage/deb и Windows NSIS через cargo-xwin на ubuntu-latest).
name: ci / desktop name: ci / desktop
on: on:
+134 -3
View File
@@ -1,6 +1,7 @@
# Релиз по тегу vMAJOR.MINOR.PATCH[-rc.N]: Gitea Release с заметками → APK. # Релиз по тегу vMAJOR.MINOR.PATCH[-rc.N]: Gitea Release с заметками → APK, Linux и Windows.
# Релиз создаётся сразу (статус Android — «собирается»); сборка APK докладывает в него файлы # Релиз создаётся сразу (статусы Android/десктоп — «собирается»); сборки докладывают файлы
# и переписывает статус: собран / не удалась (scripts/release.mjs). # и переписывают статус: собран / не удалась (scripts/release.mjs).
# Один Ubuntu-раннер: сначала APK, потом десктоп (Linux native + Windows через cargo-xwin).
# Docker-образы не публикуются: сервер собирает их сам из клонированного репозитория (deploy/update.sh). # Docker-образы не публикуются: сервер собирает их сам из клонированного репозитория (deploy/update.sh).
# #
# Секреты (Settings → Actions → Secrets): # Секреты (Settings → Actions → Secrets):
@@ -197,3 +198,133 @@ jobs:
- name: Статус в релизе — сборка отменена - name: Статус в релизе — сборка отменена
if: cancelled() if: cancelled()
run: node scripts/release.mjs android cancelled run: node scripts/release.mjs android cancelled
# После Android: на одном хосте параллельный Tauri + Gradle валит раннер.
# Windows NSIS — cargo-xwin + makensis на этом же Ubuntu (виндового раннера нет).
desktop:
name: desktop (Linux и Windows в релиз)
needs: [release, android]
if: always() && needs.release.result == 'success'
timeout-minutes: 90
runs-on: ubuntu-latest
defaults:
run:
shell: bash
env:
PIPU_VERSION: ${{ needs.release.outputs.version }}
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 }}
APPIMAGE_EXTRACT_AND_RUN: "1"
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
CARGO_BUILD_JOBS: "2"
XWIN_CACHE_DIR: /opt/ci-cache/xwin
steps:
- uses: actions/checkout@v4
- name: Статус в релизе — собирается
run: |
node scripts/release.mjs desktop linux building
node scripts/release.mjs desktop windows building
- name: Rust
run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev build-essential curl wget file \
libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev \
libdbus-1-dev libsecret-1-dev patchelf libfuse2 \
nsis lld llvm clang
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
- uses: actions/cache@v4
with:
path: |
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git/db
~/.cargo/bin/cargo-xwin
key: cargo-desktop-${{ hashFiles('apps/desktop/src-tauri/Cargo.toml') }}
restore-keys: cargo-desktop-
- run: pnpm install --frozen-lockfile
- name: Сборка AppImage и deb
id: linux
working-directory: apps/desktop
run: |
set -euo pipefail
pnpm bundle:linux
bundle=src-tauri/target/release/bundle
shopt -s nullglob
for f in "$bundle"/appimage/*.AppImage; do
cp "$f" "$GITHUB_WORKSPACE/pipu-music-${PIPU_VERSION}-linux.AppImage"
done
for f in "$bundle"/deb/*.deb; do
cp "$f" "$GITHUB_WORKSPACE/pipu-music-${PIPU_VERSION}-linux.deb"
done
ls -lh "$GITHUB_WORKSPACE"/pipu-music-${PIPU_VERSION}-linux.*
rm -rf src-tauri/target/release
- name: Linux в релиз
id: linux-upload
run: |
set -euo pipefail
files=()
for f in pipu-music-${PIPU_VERSION}-linux.AppImage pipu-music-${PIPU_VERSION}-linux.deb; do
[ -f "$f" ] && files+=("$f")
done
if [ ${#files[@]} -eq 0 ]; then
echo "::error::нет AppImage и deb после сборки"
exit 1
fi
node scripts/release.mjs desktop linux success "${files[@]}"
- name: Windows toolchain
if: ${{ !cancelled() }}
run: |
set -euo pipefail
rustup target add x86_64-pc-windows-msvc
if ! command -v cargo-xwin >/dev/null; then
cargo install --locked cargo-xwin
fi
if [ ! -d /opt/ci-cache ]; then
echo "XWIN_CACHE_DIR=$HOME/.cache/xwin" >> "$GITHUB_ENV"
mkdir -p "$HOME/.cache/xwin"
else
mkdir -p /opt/ci-cache/xwin
fi
- name: Сборка NSIS
id: win
if: ${{ !cancelled() }}
working-directory: apps/desktop
run: |
set -euo pipefail
pnpm bundle:windows:cross
bundle=src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis
shopt -s nullglob
copied=
for f in "$bundle"/*-setup.exe; do
cp "$f" "$GITHUB_WORKSPACE/pipu-music-${PIPU_VERSION}-windows-setup.exe"
copied=1
done
if [ -z "$copied" ]; then
echo "::error::нет NSIS setup.exe после сборки в $bundle"
ls -la "$bundle" || true
exit 1
fi
ls -lh "$GITHUB_WORKSPACE/pipu-music-${PIPU_VERSION}-windows-setup.exe"
- name: Windows в релиз
id: win-upload
if: ${{ !cancelled() && steps.win.outcome == 'success' }}
run: node scripts/release.mjs desktop windows success "pipu-music-${PIPU_VERSION}-windows-setup.exe"
- name: Статус Linux — сборка не удалась
if: failure() && steps.linux-upload.outcome != 'success'
run: node scripts/release.mjs desktop linux failure
- name: Статус Windows — сборка не удалась
if: failure() && steps.win-upload.outcome != 'success'
run: node scripts/release.mjs desktop windows failure
- name: Статус в релизе — сборка отменена
if: cancelled()
run: |
node scripts/release.mjs desktop linux cancelled
node scripts/release.mjs desktop windows cancelled
+8 -6
View File
@@ -5,12 +5,14 @@ import { fileURLToPath } from 'node:url';
const root = dirname(fileURLToPath(import.meta.url)); const root = dirname(fileURLToPath(import.meta.url));
const conf = JSON.parse(readFileSync(resolve(root, 'src-tauri/tauri.conf.json'), 'utf8')); const conf = JSON.parse(readFileSync(resolve(root, 'src-tauri/tauri.conf.json'), 'utf8'));
let version = '0.0.0'; let version = process.env.PIPU_VERSION ?? '0.0.0';
try { if (!process.env.PIPU_VERSION) {
const tag = execFileSync('git', ['describe', '--tags', '--abbrev=0'], { encoding: 'utf8' }).trim(); try {
version = tag.replace(/^v/, '').replace(/-rc\.\d+$/, ''); const tag = execFileSync('git', ['describe', '--tags', '--abbrev=0'], { encoding: 'utf8' }).trim();
} catch { version = tag.replace(/^v/, '');
// local untagged build } catch {
// local untagged build
}
} }
conf.version = version; conf.version = version;
writeFileSync(resolve(root, 'src-tauri/tauri.release.json'), JSON.stringify(conf, null, 2)); writeFileSync(resolve(root, 'src-tauri/tauri.release.json'), JSON.stringify(conf, null, 2));
+3
View File
@@ -6,6 +6,9 @@
"scripts": { "scripts": {
"dev": "tauri dev", "dev": "tauri dev",
"bundle": "node inject-version.mjs && tauri build --config src-tauri/tauri.release.json", "bundle": "node inject-version.mjs && tauri build --config src-tauri/tauri.release.json",
"bundle:linux": "node inject-version.mjs && tauri build --config src-tauri/tauri.release.json --bundles appimage,deb",
"bundle:windows": "node inject-version.mjs && tauri build --config src-tauri/tauri.release.json --bundles nsis",
"bundle:windows:cross": "node inject-version.mjs && tauri build --config src-tauri/tauri.release.json --runner cargo-xwin --target x86_64-pc-windows-msvc --bundles nsis",
"tauri": "tauri", "tauri": "tauri",
"icons": "tauri icon ../web/public/favicon.svg" "icons": "tauri icon ../web/public/favicon.svg"
}, },
+3 -2
View File
@@ -103,8 +103,9 @@
Android-приложение обновляется само: при запуске оно сверяется с сервером и предлагает новый APK. Android-приложение обновляется само: при запуске оно сверяется с сервером и предлагает новый APK.
Раздать приложение — ссылка `https://<ваш-домен>/download` (без входа; `/android` перенаправляет сюда): Раздать приложение — ссылка `https://<ваш-домен>/download` (без входа; `/android` перенаправляет сюда):
сборка под систему посетителя (APK, Windows, Linux, macOS), остальные платформы ниже, адрес сервера сборка под систему посетителя, остальные платформы ниже, адрес сервера для копирования и короткая инструкция.
для копирования и короткая инструкция. Та же страница — внизу экрана входа и в меню профиля. APK, Linux (AppImage, deb) и Windows (NSIS) прикладываются к релизу Gitea автоматически. Та же страница — внизу экрана входа
и в меню профиля.
## 7. Наблюдение ## 7. Наблюдение
+1 -1
View File
@@ -14,4 +14,4 @@ pnpm --filter @pipu/desktop bundle
На Windows получается NSIS-установщик, на Linux — AppImage, deb и rpm. Зависимости Linux: WebKitGTK 4.1, AppIndicator, libsecret, GStreamer. На Windows получается NSIS-установщик, на Linux — AppImage, deb и rpm. Зависимости Linux: WebKitGTK 4.1, AppIndicator, libsecret, GStreamer.
Готовые файлы с последнего релиза Gitea сервер отдаёт на публичной странице `/download`: посетителю сразу предлагается сборка его ОС, остальные платформы и Android — ниже. Релиз по тегу собирает всё на Linux-раннере: AppImage/`.deb` и Windows NSIS через `cargo-xwin` (`pipu-music-<версия>-windows-setup.exe`). Подпись Windows-установщика на Linux нет. Страница `/download` на сервере отдаёт те файлы, что есть в последнем релизе Gitea.
+100 -15
View File
@@ -11,7 +11,7 @@
import { execFileSync } from 'node:child_process'; import { execFileSync } from 'node:child_process';
import { createHash } from 'node:crypto'; import { createHash } from 'node:crypto';
import { existsSync, readFileSync } from 'node:fs'; import { existsSync, readFileSync, writeFileSync } from 'node:fs';
import { basename, resolve } from 'node:path'; import { basename, resolve } from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
@@ -29,6 +29,12 @@ const COMMIT_RE = /^(\w+)(?:\(([^)]+)\))?(!)?:\s*(.+)$/;
export const STATUS_START = '<!-- android-status -->'; export const STATUS_START = '<!-- android-status -->';
export const STATUS_END = '<!-- /android-status -->'; export const STATUS_END = '<!-- /android-status -->';
export const DESKTOP_PLATFORMS = ['linux', 'windows'];
export function desktopStatusMarks(platform) {
if (!DESKTOP_PLATFORMS.includes(platform)) throw new Error(`unknown desktop platform: ${platform}`);
return { start: `<!-- desktop-${platform}-status -->`, end: `<!-- /desktop-${platform}-status -->` };
}
/** Раскладывает коммиты (Conventional Commits) по разделам заметок. */ /** Раскладывает коммиты (Conventional Commits) по разделам заметок. */
export function groupCommits(commits) { export function groupCommits(commits) {
@@ -76,16 +82,62 @@ export function renderAndroidStatus({ status, runUrl, versionCode, apk, signing
return [STATUS_START, ...lines.map((l) => `> ${l}`), STATUS_END].join('\n'); return [STATUS_START, ...lines.map((l) => `> ${l}`), STATUS_END].join('\n');
} }
/** Заменяет блок статуса в тексте релиза (или дописывает его в начало, если блока нет). */ /** Блок статуса сборки десктопа в тексте релиза (отдельный для linux и windows). */
export function replaceAndroidStatus(body, block) { export function renderDesktopStatus({ platform, status, runUrl, files }) {
const start = body.indexOf(STATUS_START); const label = platform === 'windows' ? 'Windows' : 'Linux';
const end = body.indexOf(STATUS_END); const pending = platform === 'windows' ? 'NSIS-установщик' : 'AppImage и .deb';
if (start === -1 || end === -1 || end < start) return `${block}\n\n${body}`; const lines =
return body.slice(0, start) + block + body.slice(end + STATUS_END.length); status === 'success'
? [
`✅ **Десктоп (${label}):** собрано — ${files.map((f) => `\`${f.name}\` (${formatSize(f.size)})`).join(', ')}.`,
]
: status === 'cancelled'
? [
`⏹ **Десктоп (${label}):** сборка отменена — [лог сборки](${runUrl}).`,
'Файлы появятся здесь после перезапуска сборки.',
]
: status === 'failure'
? [
`❌ **Десктоп (${label}):** сборка не удалась — [лог сборки](${runUrl}).`,
'Остальные клиенты от этого не зависят; установщик появится после перезапуска сборки.',
]
: [
`⏳ **Десктоп (${label}):** собирается — [ход сборки](${runUrl}).`,
`${pending} появится в этом релизе автоматически.`,
];
const { start, end } = desktopStatusMarks(platform);
return [start, ...lines.map((l) => `> ${l}`), end].join('\n');
} }
/** Текст релиза: статус Android, изменения, обновление сервера, установка APK. */ function replaceBlock(body, startMark, endMark, block) {
export function renderNotes({ version, tag, prevTag, prerelease, repoUrl, groups, androidStatus }) { const start = body.indexOf(startMark);
const end = body.indexOf(endMark);
if (start === -1 || end === -1 || end < start) return `${block}\n\n${body}`;
return body.slice(0, start) + block + body.slice(end + endMark.length);
}
/** Заменяет блок статуса Android в тексте релиза (или дописывает его в начало, если блока нет). */
export function replaceAndroidStatus(body, block) {
return replaceBlock(body, STATUS_START, STATUS_END, block);
}
export function replaceDesktopStatus(body, platform, block) {
const { start, end } = desktopStatusMarks(platform);
return replaceBlock(body, start, end, block);
}
/** Текст релиза: статусы сборок, изменения, обновление сервера, установка клиентов. */
export function renderNotes({
version,
tag,
prevTag,
prerelease,
repoUrl,
groups,
androidStatus,
desktopLinuxStatus,
desktopWindowsStatus,
}) {
const link = (it) => `([${it.sha.slice(0, 7)}](${repoUrl}/commit/${it.sha}))`; const link = (it) => `([${it.sha.slice(0, 7)}](${repoUrl}/commit/${it.sha}))`;
const item = (it) => `- ${it.scope ? `**${it.scope}:** ` : ''}${it.text} ${link(it)}`; const item = (it) => `- ${it.scope ? `**${it.scope}:** ` : ''}${it.text} ${link(it)}`;
const out = []; const out = [];
@@ -93,7 +145,10 @@ export function renderNotes({ version, tag, prevTag, prerelease, repoUrl, groups
if (prerelease) { if (prerelease) {
out.push('> 🧪 **Предварительная версия** — для проверки перед релизом, не для основной установки.', ''); out.push('> 🧪 **Предварительная версия** — для проверки перед релизом, не для основной установки.', '');
} }
out.push(androidStatus, '', '## Что изменилось', ''); out.push(androidStatus, '');
if (desktopLinuxStatus) out.push(desktopLinuxStatus, '');
if (desktopWindowsStatus) out.push(desktopWindowsStatus, '');
out.push('## Что изменилось', '');
if (groups.breaking.length) { if (groups.breaking.length) {
out.push('### ⚠️ Несовместимые изменения', '', ...groups.breaking.map(item), ''); out.push('### ⚠️ Несовместимые изменения', '', ...groups.breaking.map(item), '');
} }
@@ -124,11 +179,14 @@ export function renderNotes({ version, tag, prevTag, prerelease, repoUrl, groups
'', '',
`Ожидается \`"version":"${version}"\`.`, `Ожидается \`"version":"${version}"\`.`,
'', '',
'## Android', '## Клиенты',
'', '',
`- **Установка или обновление:** скачайте \`pipu-music-${version}.apk\` ниже и откройте его на телефоне;`, 'Скачайте с этой страницы или с `/download` на сервере (подскажет сборку под вашу систему).',
' Android попросит разрешить установку из этого источника. Обновление ставится поверх, данные сохраняются.', '',
`- **Проверка файла:** \`sha256sum pipu-music-${version}.apk\` должен совпасть с \`pipu-music-${version}.apk.sha256\`.`, `- **Android:** \`pipu-music-${version}.apk\` — откройте на телефоне; Android попросит разрешить установку из этого источника.`,
`- **Windows:** \`pipu-music-${version}-windows-setup.exe\`.`,
`- **Linux:** \`pipu-music-${version}-linux.AppImage\` (сделайте исполняемым) или \`.deb\`.`,
`- **Проверка APK:** \`sha256sum pipu-music-${version}.apk\` должен совпасть с \`pipu-music-${version}.apk.sha256\`.`,
'', '',
`Первая установка сервера — [инструкция](${repoUrl}/src/tag/${tag}/docs/install-guide.md).`, `Первая установка сервера — [инструкция](${repoUrl}/src/tag/${tag}/docs/install-guide.md).`,
'', '',
@@ -202,6 +260,8 @@ async function create() {
repoUrl, repoUrl,
groups: groupCommits(commitsBetween(prevTag, tag)), groups: groupCommits(commitsBetween(prevTag, tag)),
androidStatus: renderAndroidStatus({ status: 'building', runUrl }), androidStatus: renderAndroidStatus({ status: 'building', runUrl }),
desktopLinuxStatus: renderDesktopStatus({ platform: 'linux', status: 'building', runUrl }),
desktopWindowsStatus: renderDesktopStatus({ platform: 'windows', status: 'building', runUrl }),
}); });
const fields = { name: `Pipu Music ${env('VERSION')}`, body, prerelease, draft: false }; const fields = { name: `Pipu Music ${env('VERSION')}`, body, prerelease, draft: false };
const saved = release const saved = release
@@ -242,13 +302,38 @@ async function android(status, apkPath) {
console.log(`статус Android: ${status}`); console.log(`статус Android: ${status}`);
} }
async function desktop(platform, status, paths) {
const { release, runUrl } = await context();
if (!release) throw new Error(`релиз для ${env('TAG')} не найден — сначала node scripts/release.mjs create`);
const files = [];
if (status === 'success') {
if (!paths.length) throw new Error('desktop success: укажите файлы');
for (const file of paths) {
const data = readFileSync(file);
files.push({ name: basename(file), size: data.length });
const sum = `${file}.sha256`;
if (!existsSync(sum)) writeFileSync(sum, `${createHash('sha256').update(data).digest('hex')}\n`);
await upload(release, file);
if (existsSync(sum)) await upload(release, sum);
}
}
const block = renderDesktopStatus({ platform, status, runUrl, files });
await api('PATCH', `/releases/${release.id}`, { json: { body: replaceDesktopStatus(release.body ?? '', platform, block) } });
console.log(`статус десктопа (${platform}): ${status}`);
}
async function main() { async function main() {
const [cmd, ...args] = process.argv.slice(2); const [cmd, ...args] = process.argv.slice(2);
if (cmd === 'create') return create(); if (cmd === 'create') return create();
if (cmd === 'android' && ['building', 'success', 'failure', 'cancelled'].includes(args[0])) { if (cmd === 'android' && ['building', 'success', 'failure', 'cancelled'].includes(args[0])) {
return android(args[0], args[1]); return android(args[0], args[1]);
} }
throw new Error('использование: release.mjs create | android building|failure|cancelled | android success <file.apk>'); if (cmd === 'desktop' && DESKTOP_PLATFORMS.includes(args[0]) && ['building', 'success', 'failure', 'cancelled'].includes(args[1])) {
return desktop(args[0], args[1], args.slice(2));
}
throw new Error(
'использование: release.mjs create | android building|failure|cancelled | android success <file.apk> | desktop linux|windows building|failure|cancelled | desktop linux|windows success <file...>',
);
} }
if (process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1])) { if (process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1])) {
+35
View File
@@ -4,8 +4,11 @@ import { test } from 'node:test';
import { import {
groupCommits, groupCommits,
renderAndroidStatus, renderAndroidStatus,
renderDesktopStatus,
renderNotes, renderNotes,
replaceAndroidStatus, replaceAndroidStatus,
replaceDesktopStatus,
desktopStatusMarks,
STATUS_END, STATUS_END,
STATUS_START, STATUS_START,
} from './release.mjs'; } from './release.mjs';
@@ -77,6 +80,34 @@ test('adds a status block when the body has none', () => {
assert.ok(replaceAndroidStatus('hand-written notes', block).startsWith(block)); assert.ok(replaceAndroidStatus('hand-written notes', block).startsWith(block));
}); });
test('desktop status blocks', () => {
const linuxMarks = desktopStatusMarks('linux');
const winMarks = desktopStatusMarks('windows');
const building = renderDesktopStatus({ platform: 'linux', status: 'building', runUrl: 'https://run' });
assert.ok(building.startsWith(linuxMarks.start) && building.endsWith(linuxMarks.end));
assert.match(building, /Десктоп \(Linux\):.*собирается — \[ход сборки\]\(https:\/\/run\)/);
const ok = renderDesktopStatus({
platform: 'linux',
status: 'success',
files: [{ name: 'pipu-music-1.4.0-linux.AppImage', size: 40 * 1024 * 1024 }],
});
assert.match(ok, /`pipu-music-1\.4\.0-linux\.AppImage` \(40,0 МБ\)/);
assert.equal(replaceDesktopStatus(`a\n\n${building}\n\nb`, 'linux', ok).includes(building), false);
const winBuilding = renderDesktopStatus({ platform: 'windows', status: 'building', runUrl: 'https://run' });
assert.ok(winBuilding.startsWith(winMarks.start) && winBuilding.endsWith(winMarks.end));
const winOk = renderDesktopStatus({
platform: 'windows',
status: 'success',
files: [{ name: 'pipu-music-1.4.0-windows-setup.exe', size: 12 * 1024 * 1024 }],
});
const both = `${building}\n\n${winBuilding}`;
const patched = replaceDesktopStatus(both, 'windows', winOk);
assert.match(patched, /Linux/);
assert.match(patched, /windows-setup\.exe/);
assert.doesNotMatch(patched, /Десктоп \(Windows\):.*собирается/);
});
test('release notes', () => { test('release notes', () => {
const notes = renderNotes({ const notes = renderNotes({
version: '0.2.0', version: '0.2.0',
@@ -86,12 +117,16 @@ test('release notes', () => {
repoUrl, repoUrl,
groups: groupCommits([{ sha: sha(1), subject: 'feat(web): library screen' }]), groups: groupCommits([{ sha: sha(1), subject: 'feat(web): library screen' }]),
androidStatus: renderAndroidStatus({ status: 'building', runUrl: 'https://run' }), androidStatus: renderAndroidStatus({ status: 'building', runUrl: 'https://run' }),
desktopLinuxStatus: renderDesktopStatus({ platform: 'linux', status: 'building', runUrl: 'https://run' }),
desktopWindowsStatus: renderDesktopStatus({ platform: 'windows', status: 'building', runUrl: 'https://run' }),
}); });
assert.match(notes, /### Новое\n\n- \*\*web:\*\* library screen \(\[1111111\]\(https:\/\/git\.example\/owner\/pipu-music\/commit\/1{40}\)\)/); assert.match(notes, /### Новое\n\n- \*\*web:\*\* library screen \(\[1111111\]\(https:\/\/git\.example\/owner\/pipu-music\/commit\/1{40}\)\)/);
assert.match(notes, /\[v0\.1\.7\.\.\.v0\.2\.0\]\(https:\/\/git\.example\/owner\/pipu-music\/compare\/v0\.1\.7\.\.\.v0\.2\.0\)/); assert.match(notes, /\[v0\.1\.7\.\.\.v0\.2\.0\]\(https:\/\/git\.example\/owner\/pipu-music\/compare\/v0\.1\.7\.\.\.v0\.2\.0\)/);
assert.match(notes, /\.\/update\.sh v0\.2\.0/); assert.match(notes, /\.\/update\.sh v0\.2\.0/);
assert.match(notes, /"version":"0\.2\.0"/); assert.match(notes, /"version":"0\.2\.0"/);
assert.match(notes, /pipu-music-0\.2\.0\.apk/); assert.match(notes, /pipu-music-0\.2\.0\.apk/);
assert.match(notes, /pipu-music-0\.2\.0-windows-setup\.exe/);
assert.match(notes, /pipu-music-0\.2\.0-linux\.AppImage/);
assert.match(notes, /src\/tag\/v0\.2\.0\/docs\/install-guide\.md/); assert.match(notes, /src\/tag\/v0\.2\.0\/docs\/install-guide\.md/);
assert.doesNotMatch(notes, /Предварительная версия/); assert.doesNotMatch(notes, /Предварительная версия/);
}); });