Releases on git.crt.fyi from a workflow: a v* tag push runs .forgejo/workflows/release.yml (tag = package.json version and every version copy, the full CI, Linux AppImage + deb and Windows NSIS on the VDS runners, privacy scan with the private terms, one Forgejo release with short CHANGELOG-summary notes, the installers and SHA256SUMS, older release entries deleted, tags kept); workflow_dispatch dry run makes a draft test-<sha> and deletes it; desktop.yml folded in, ci.yml and release.yml share .forgejo/actions/linux-setup and ci-run; tools/forgejo-release.ts (notes, upsert, prune, delete; uploads over 100 MB go to the origin because Cloudflare refuses them); desktop-release.ts upload-forgejo adds the notarized DMG and updater bundle from the Mac; docs/desktop-release.md release order
This commit is contained in:
parent
e5f148e534
commit
2590b2e828
11 changed files with 871 additions and 229 deletions
78
tests/forgejo-release.test.ts
Normal file
78
tests/forgejo-release.test.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import { describe, expect, test } from 'bun:test';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { anchor, changelogSection, isTestTag, mergeSums, pruneList, type Release, sectionSummary, shortNotes } from '../tools/forgejo-release';
|
||||
|
||||
const CL = `# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
Next things.
|
||||
|
||||
- a
|
||||
|
||||
## 0.2.0 · 2026-10-12
|
||||
|
||||
Summary line one
|
||||
continues here. Second sentence.
|
||||
|
||||
- **Bullet.** detail
|
||||
more detail
|
||||
|
||||
## 0.1.0 · 2026-10-10
|
||||
|
||||
- First bullet text
|
||||
wraps here:
|
||||
- nested
|
||||
- second
|
||||
`;
|
||||
|
||||
const rel = (id: number, tag: string, draft = false): Release => ({ id, tag_name: tag, name: tag, draft, prerelease: false, body: '', assets: [] });
|
||||
|
||||
describe('forgejo release notes', () => {
|
||||
test('anchor matches Forgejo heading ids', () => {
|
||||
expect(anchor('0.6.0 · 2026-10-12')).toBe('0-6-0-2026-10-12');
|
||||
expect(anchor('Unreleased')).toBe('unreleased');
|
||||
});
|
||||
test('section and summary', () => {
|
||||
expect(changelogSection(CL, '0.2.0')!.heading).toBe('0.2.0 · 2026-10-12');
|
||||
expect(sectionSummary(changelogSection(CL, '0.2.0')!.body)).toBe('Summary line one continues here. Second sentence.');
|
||||
expect(sectionSummary(changelogSection(CL, '0.1.0')!.body)).toBe('First bullet text wraps here.');
|
||||
expect(changelogSection(CL, '0.3.0')).toBeNull();
|
||||
});
|
||||
test('short notes: summary, install, links; unknown version falls back to Unreleased', () => {
|
||||
const n = shortNotes(CL, '0.2.0');
|
||||
expect(n.split('\n')[0]).toBe('Summary line one continues here. Second sentence.');
|
||||
expect(n).toContain('npx storebound@0.2.0 init');
|
||||
expect(n).toContain('/src/tag/v0.2.0/CHANGELOG.md#0-2-0-2026-10-12');
|
||||
expect(shortNotes(CL, '0.3.0')).toContain('CHANGELOG.md#unreleased');
|
||||
});
|
||||
test('the real CHANGELOG gives at most 8 short lines for every version', () => {
|
||||
const real = readFileSync(join(import.meta.dir, '..', 'CHANGELOG.md'), 'utf8');
|
||||
for (const v of [...real.matchAll(/^## (\d+\.\d+\.\d+)/gm)].map(m => m[1]!)) {
|
||||
const lines = shortNotes(real, v).split('\n');
|
||||
expect(lines.length).toBeLessThanOrEqual(8);
|
||||
expect(lines.every(l => l.length <= 200)).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('forgejo release assets and pruning', () => {
|
||||
test('SHA256SUMS merge: new entries win, sorted', () => {
|
||||
const a = 'a'.repeat(64), b = 'b'.repeat(64), c = 'c'.repeat(64);
|
||||
expect(mergeSums(`${a} x.deb\n${a} y.exe\n`, [{ file: 'y.exe', sha256: b }, { file: 'm.dmg', sha256: c }]))
|
||||
.toBe(`${c} m.dmg\n${a} x.deb\n${b} y.exe\n`);
|
||||
expect(mergeSums('', [])).toBe('\n');
|
||||
});
|
||||
test('prune keeps only the given tag, never when it is missing or a draft', () => {
|
||||
const rs = [rel(3, 'v0.8.0'), rel(2, 'v0.7.0'), rel(1, 'test-abcdef1', true)];
|
||||
expect(pruneList(rs, 'v0.8.0').remove.map(r => r.tag_name)).toEqual(['v0.7.0', 'test-abcdef1']);
|
||||
expect(pruneList(rs, 'v0.9.0').problem).toContain('nothing is deleted');
|
||||
expect(pruneList([rel(4, 'v1.0.0', true), ...rs], 'v1.0.0').problem).toContain('draft');
|
||||
});
|
||||
test('only test-<sha> tags may be deleted', () => {
|
||||
expect(isTestTag('test-0a1b2c3')).toBe(true);
|
||||
expect(isTestTag('v0.8.0')).toBe(false);
|
||||
expect(isTestTag('test-latest')).toBe(false);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue