ci / api / api (push) Successful in 50s
ci / db / db (push) Successful in 50s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Failing after 49s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m13s
ci / ts / ts (packages + web) (push) Canceled after 2m4s
ci / desktop / rust (desktop proxy) (push) Successful in 5m33s
release / release (версия и заметки) (push) Successful in 9s
release / android (APK в релиз) (push) Failing after 4m6s
release / desktop (Linux и Windows в релиз) (push) Successful in 17m20s
Co-authored-by: Cursor <cursoragent@cursor.com>
148 lines
6.5 KiB
JavaScript
148 lines
6.5 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { test } from 'node:test';
|
|
|
|
import {
|
|
groupCommits,
|
|
renderAndroidStatus,
|
|
renderDesktopStatus,
|
|
renderNotes,
|
|
replaceAndroidStatus,
|
|
replaceDesktopStatus,
|
|
desktopStatusMarks,
|
|
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('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.deb', size: 4 * 1024 * 1024 }],
|
|
});
|
|
assert.match(ok, /`pipu-music-1\.4\.0-linux\.deb` \(4,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', () => {
|
|
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' }),
|
|
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, /\[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, /pipu-music-0\.2\.0-windows-setup\.exe/);
|
|
assert.match(notes, /pipu-music-0\.2\.0-linux\.deb/);
|
|
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/);
|
|
});
|