Files
pipu-music/scripts/release.test.mjs
T
mrleo1nidandClaude Opus 5.5 a239650075
ci / ts / ts (packages + web) (push) Successful in 1m24s
release / release (версия и заметки) (push) Successful in 45s
release / android (APK в релиз) (push) Failing after 12m0s
fix(release): public links in release notes and a cancelled status
The job reaches the Gitea API at its internal address, so html_url in the
response pointed at http://10.10.1.129:3000 and every link in the notes was
unreachable from outside. Build links from PUBLIC_URL (repo variable,
defaults to https://gitea.hsrv.site). A cancelled Android build now shows
"cancelled" instead of staying at "building".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 05:09:59 +03:00

113 lines
4.7 KiB
JavaScript

import assert from 'node:assert/strict';
import { test } from 'node:test';
import {
groupCommits,
renderAndroidStatus,
renderNotes,
replaceAndroidStatus,
STATUS_END,
STATUS_START,
} from './release.mjs';
const sha = (n) => String(n).repeat(40).slice(0, 40);
const repoUrl = 'https://git.example/owner/pipu-music';
test('groups conventional commits by section in a fixed order', () => {
const groups = groupCommits([
{ sha: sha(1), subject: 'fix(api): handle empty token' },
{ sha: sha(2), subject: 'feat: add wave' },
{ sha: sha(3), subject: 'ci(release): build arm64 only' },
{ sha: sha(4), subject: 'build(deploy): pin images' },
{ sha: sha(5), subject: 'Update README' },
{ sha: sha(6), subject: 'chore: bump deps' },
]);
assert.deepEqual(
groups.sections.map(([title, items]) => [title, items.map((i) => i.text)]),
[
['Новое', ['add wave']],
['Исправления', ['handle empty token']],
['Сборка и CI', ['build arm64 only', 'pin images']],
['Прочее', ['Update README', 'bump deps']],
],
);
assert.equal(groups.sections[1][1][0].scope, 'api');
assert.deepEqual(groups.breaking, []);
});
test('marks breaking changes', () => {
const groups = groupCommits([{ sha: sha(1), subject: 'feat(api)!: drop v0 endpoints' }]);
assert.equal(groups.breaking.length, 1);
assert.equal(groups.sections[0][0], 'Новое');
});
test('android status blocks', () => {
const building = renderAndroidStatus({ status: 'building', runUrl: 'https://run' });
assert.ok(building.startsWith(STATUS_START) && building.endsWith(STATUS_END));
assert.match(building, /собирается — \[ход сборки\]\(https:\/\/run\)/);
const ok = renderAndroidStatus({
status: 'success',
versionCode: '10299',
signing: 'release',
apk: { name: 'pipu-music-0.1.2.apk', size: 25 * 1024 * 1024 + 300_000, sha256: 'ab'.repeat(32) },
});
assert.match(ok, /`pipu-music-0\.1\.2\.apk` \(25,3 МБ\), versionCode `10299`/);
assert.doesNotMatch(ok, /отладочным/);
const debug = renderAndroidStatus({ status: 'success', signing: 'debug', apk: { name: 'a.apk', size: 1, sha256: 'x' } });
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', () => {
const body = `intro\n\n${renderAndroidStatus({ status: 'building', runUrl: 'u' })}\n\n## Что изменилось\n`;
const failed = renderAndroidStatus({ status: 'failure', runUrl: 'u' });
const once = replaceAndroidStatus(body, failed);
assert.equal(replaceAndroidStatus(once, failed), once);
assert.ok(once.startsWith('intro\n\n') && once.endsWith('\n\n## Что изменилось\n'));
assert.doesNotMatch(once, /собирается/);
assert.equal(once.split(STATUS_START).length, 2);
});
test('adds a status block when the body has none', () => {
const block = renderAndroidStatus({ status: 'failure', runUrl: 'u' });
assert.ok(replaceAndroidStatus('hand-written notes', block).startsWith(block));
});
test('release notes', () => {
const notes = renderNotes({
version: '0.2.0',
tag: 'v0.2.0',
prevTag: 'v0.1.7',
prerelease: false,
repoUrl,
groups: groupCommits([{ sha: sha(1), subject: 'feat(web): library screen' }]),
androidStatus: renderAndroidStatus({ 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, /\[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, /"version":"0\.2\.0"/);
assert.match(notes, /pipu-music-0\.2\.0\.apk/);
assert.match(notes, /src\/tag\/v0\.2\.0\/docs\/install-guide\.md/);
assert.doesNotMatch(notes, /Предварительная версия/);
});
test('release notes for a first prerelease without changes', () => {
const notes = renderNotes({
version: '0.1.0-rc.1',
tag: 'v0.1.0-rc.1',
prevTag: null,
prerelease: true,
repoUrl,
groups: groupCommits([]),
androidStatus: renderAndroidStatus({ status: 'building', runUrl: 'u' }),
});
assert.match(notes, /Предварительная версия/);
assert.match(notes, /Изменений с прошлой версии нет/);
assert.match(notes, /commits\/tag\/v0\.1\.0-rc\.1/);
});