build(release): a Dart release tool for every app
tool/release.dart releases one app: `dart run tool/release.dart learn
[major | minor | patch] [--dry-run]`, each app configured in Dart beside
it (apps/learn/release.dart). Every step and check of tool/release.sh
is kept: the repository as pushed, resuming after a release commit and
tag, Flutter as CI pins it, the tools, the key and its certificate pin,
secrets asked for without echo and never written down, CI green on the
newest commit, the signed release commit and tag by a key deploys
accept, the APK built from a checkout of the tag and checked, the snap,
the signed checksums, pushing only after both builds passed, the
release on Forgejo, the F-Droid repository, the Snap Store once a
channel is set.
New: tags per app (learn-v0.19.0, Learn's v… counting as its
history); only commits touching the app decide its version (its folder,
the shared packages it uses by path, the root pubspec and lock, its
release files, where its packages were before the move); --dry-run
reads all of it, lists every step and the notes, and what a release
would refuse, changing nothing. The pure parts are tested in
test/release/. Learn releases build 21 with tool/release.sh, then
switches.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-19 21:12:31 +02:00
|
|
|
import 'package:test/test.dart';
|
|
|
|
|
|
|
|
|
|
import '../../tool/src/release/app_release.dart';
|
|
|
|
|
import '../../tool/src/release/notes.dart';
|
|
|
|
|
import '../../tool/src/release/version.dart';
|
|
|
|
|
import 'fixtures.dart';
|
|
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
|
const certificate =
|
|
|
|
|
'27e243fcbdc826ff98ab7dbb10436c98c3bea24b2a015e94e4cfc7d1fc2a006c';
|
|
|
|
|
const fingerprint =
|
|
|
|
|
'E8082788756F6B7E682AA22FCEAD24B53E2A8B0912AD66D65E8C2D4F07BB94B0';
|
|
|
|
|
const signer = '0123456789ABCDEF0123456789ABCDEF01234567';
|
|
|
|
|
|
|
|
|
|
String notes({
|
|
|
|
|
AppRelease app = testApp,
|
|
|
|
|
String? previousTag = 'notes-v0.18.0',
|
|
|
|
|
List<Commit> commits = const [],
|
feat(app): what's new after an update, drafted by the release tool (ADR 0030, D61)
halkyon_app's WhatsNew reads what each release changed from the app's
assets/whats_new/ (<version>.<language>.txt, English and German, a change
a line): after an update a sheet lists every release since the one the
device showed last, newest first, once; Settings shows the running
version's again, above About. Nothing on a fresh install.
tool/release.dart drafts the missing texts from the commits that count
after "Go on?" (German marked TRANSLATE:), opens them in the editor until
they fit (no mark left, F-Droid's 500 characters) and commits them with
the version. The English is the Forgejo notes' What's new, each language
F-Droid's changelog of the build, written at publishing and copied in by
tool/fdroid_repo.sh. Learn and Train bundle their texts, Learn's 0.22.0
and Train's 0.1.0 written to start.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-31 19:36:20 +01:00
|
|
|
String? whatsNew,
|
build(release): a Dart release tool for every app
tool/release.dart releases one app: `dart run tool/release.dart learn
[major | minor | patch] [--dry-run]`, each app configured in Dart beside
it (apps/learn/release.dart). Every step and check of tool/release.sh
is kept: the repository as pushed, resuming after a release commit and
tag, Flutter as CI pins it, the tools, the key and its certificate pin,
secrets asked for without echo and never written down, CI green on the
newest commit, the signed release commit and tag by a key deploys
accept, the APK built from a checkout of the tag and checked, the snap,
the signed checksums, pushing only after both builds passed, the
release on Forgejo, the F-Droid repository, the Snap Store once a
channel is set.
New: tags per app (learn-v0.19.0, Learn's v… counting as its
history); only commits touching the app decide its version (its folder,
the shared packages it uses by path, the root pubspec and lock, its
release files, where its packages were before the move); --dry-run
reads all of it, lists every step and the notes, and what a release
would refuse, changing nothing. The pure parts are tested in
test/release/. Learn releases build 21 with tool/release.sh, then
switches.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-19 21:12:31 +02:00
|
|
|
}) => releaseNotes(
|
|
|
|
|
app: app,
|
|
|
|
|
version: const Version(0, 19, 0),
|
|
|
|
|
previousTag: previousTag,
|
|
|
|
|
commits: commits,
|
|
|
|
|
certificate: certificate,
|
|
|
|
|
fdroidFingerprint: fingerprint,
|
|
|
|
|
signer: signer,
|
feat(app): what's new after an update, drafted by the release tool (ADR 0030, D61)
halkyon_app's WhatsNew reads what each release changed from the app's
assets/whats_new/ (<version>.<language>.txt, English and German, a change
a line): after an update a sheet lists every release since the one the
device showed last, newest first, once; Settings shows the running
version's again, above About. Nothing on a fresh install.
tool/release.dart drafts the missing texts from the commits that count
after "Go on?" (German marked TRANSLATE:), opens them in the editor until
they fit (no mark left, F-Droid's 500 characters) and commits them with
the version. The English is the Forgejo notes' What's new, each language
F-Droid's changelog of the build, written at publishing and copied in by
tool/fdroid_repo.sh. Learn and Train bundle their texts, Learn's 0.22.0
and Train's 0.1.0 written to start.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-31 19:36:20 +01:00
|
|
|
whatsNew: whatsNew,
|
build(release): a Dart release tool for every app
tool/release.dart releases one app: `dart run tool/release.dart learn
[major | minor | patch] [--dry-run]`, each app configured in Dart beside
it (apps/learn/release.dart). Every step and check of tool/release.sh
is kept: the repository as pushed, resuming after a release commit and
tag, Flutter as CI pins it, the tools, the key and its certificate pin,
secrets asked for without echo and never written down, CI green on the
newest commit, the signed release commit and tag by a key deploys
accept, the APK built from a checkout of the tag and checked, the snap,
the signed checksums, pushing only after both builds passed, the
release on Forgejo, the F-Droid repository, the Snap Store once a
channel is set.
New: tags per app (learn-v0.19.0, Learn's v… counting as its
history); only commits touching the app decide its version (its folder,
the shared packages it uses by path, the root pubspec and lock, its
release files, where its packages were before the move); --dry-run
reads all of it, lists every step and the notes, and what a release
would refuse, changing nothing. The pure parts are tested in
test/release/. Learn releases build 21 with tool/release.sh, then
switches.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-19 21:12:31 +02:00
|
|
|
);
|
|
|
|
|
|
feat(app): what's new after an update, drafted by the release tool (ADR 0030, D61)
halkyon_app's WhatsNew reads what each release changed from the app's
assets/whats_new/ (<version>.<language>.txt, English and German, a change
a line): after an update a sheet lists every release since the one the
device showed last, newest first, once; Settings shows the running
version's again, above About. Nothing on a fresh install.
tool/release.dart drafts the missing texts from the commits that count
after "Go on?" (German marked TRANSLATE:), opens them in the editor until
they fit (no mark left, F-Droid's 500 characters) and commits them with
the version. The English is the Forgejo notes' What's new, each language
F-Droid's changelog of the build, written at publishing and copied in by
tool/fdroid_repo.sh. Learn and Train bundle their texts, Learn's 0.22.0
and Train's 0.1.0 written to start.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-31 19:36:20 +01:00
|
|
|
test("with what's new, the notes give it in place of the commits", () {
|
|
|
|
|
final text = notes(
|
|
|
|
|
commits: const [Commit(hash: 'a', subject: 'feat: tiles')],
|
|
|
|
|
whatsNew: '- Tiles on the first start.\n\n- Flags.\n',
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(
|
|
|
|
|
text,
|
|
|
|
|
contains(
|
|
|
|
|
"the project's instance.\n"
|
|
|
|
|
'\n'
|
|
|
|
|
"### What's new\n"
|
|
|
|
|
'\n'
|
|
|
|
|
'- Tiles on the first start.\n'
|
|
|
|
|
'- Flags.\n'
|
|
|
|
|
'\n'
|
|
|
|
|
'### Installing\n',
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
expect(text, isNot(contains('### Features')));
|
|
|
|
|
});
|
|
|
|
|
|
build(release): a Dart release tool for every app
tool/release.dart releases one app: `dart run tool/release.dart learn
[major | minor | patch] [--dry-run]`, each app configured in Dart beside
it (apps/learn/release.dart). Every step and check of tool/release.sh
is kept: the repository as pushed, resuming after a release commit and
tag, Flutter as CI pins it, the tools, the key and its certificate pin,
secrets asked for without echo and never written down, CI green on the
newest commit, the signed release commit and tag by a key deploys
accept, the APK built from a checkout of the tag and checked, the snap,
the signed checksums, pushing only after both builds passed, the
release on Forgejo, the F-Droid repository, the Snap Store once a
channel is set.
New: tags per app (learn-v0.19.0, Learn's v… counting as its
history); only commits touching the app decide its version (its folder,
the shared packages it uses by path, the root pubspec and lock, its
release files, where its packages were before the move); --dry-run
reads all of it, lists every step and the notes, and what a release
would refuse, changing nothing. The pure parts are tested in
test/release/. Learn releases build 21 with tool/release.sh, then
switches.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-19 21:12:31 +02:00
|
|
|
test("a release's notes list its features and fixes, oldest first, "
|
|
|
|
|
'without their types and scopes', () {
|
|
|
|
|
final text = notes(
|
|
|
|
|
commits: const [
|
|
|
|
|
Commit(hash: 'a', subject: 'feat(app): tiles on the first start'),
|
|
|
|
|
Commit(hash: 'b', subject: 'docs: why'),
|
|
|
|
|
Commit(hash: 'c', subject: 'fix(sync)!: no lost rating'),
|
|
|
|
|
Commit(hash: 'd', subject: 'perf: faster library'),
|
|
|
|
|
Commit(hash: 'e', subject: 'feat: flags'),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(
|
|
|
|
|
text,
|
|
|
|
|
startsWith(
|
|
|
|
|
'Halkyon Notes 0.19.0 for Android and Linux. Both connect to '
|
|
|
|
|
"notes.halkyon.eu, the project's instance.\n"
|
|
|
|
|
'\n'
|
|
|
|
|
'### Features\n'
|
|
|
|
|
'\n'
|
|
|
|
|
'- tiles on the first start\n'
|
|
|
|
|
'- flags\n'
|
|
|
|
|
'\n'
|
|
|
|
|
'### Fixes\n'
|
|
|
|
|
'\n'
|
|
|
|
|
'- no lost rating\n'
|
|
|
|
|
'- faster library\n'
|
|
|
|
|
'\n'
|
|
|
|
|
'### Installing\n',
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('they say how to install and check every file', () {
|
|
|
|
|
final text = notes(
|
|
|
|
|
commits: const [Commit(hash: 'a', subject: 'fix: x')],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(
|
|
|
|
|
text,
|
|
|
|
|
contains(
|
|
|
|
|
'- **Android:** `halkyon-notes-0.19.0.apk`, one APK for every phone, '
|
|
|
|
|
"signed with the app's key; the SHA-256 of its certificate is "
|
|
|
|
|
'`$certificate`.\n',
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
expect(
|
|
|
|
|
text,
|
|
|
|
|
contains(
|
|
|
|
|
'- **F-Droid:** the same APK in the repository '
|
|
|
|
|
'https://halkyon.app/fdroid/repo?fingerprint=$fingerprint\n',
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
expect(
|
|
|
|
|
text,
|
|
|
|
|
contains(
|
|
|
|
|
'- **Linux:** `halkyon-notes_0.19.0_amd64.snap`: `sudo snap install '
|
|
|
|
|
'--dangerous halkyon-notes_0.19.0_amd64.snap` (not in the Snap Store '
|
|
|
|
|
'yet).\n',
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
expect(
|
|
|
|
|
text,
|
|
|
|
|
endsWith(
|
|
|
|
|
"`SHA256SUMS` lists the files' checksums, signed with the GPG key "
|
|
|
|
|
'`$signer` (`SHA256SUMS.asc`).',
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('a snap in the Store is installed from its channel', () {
|
|
|
|
|
final text = notes(
|
|
|
|
|
app: testApp.copyWith(
|
|
|
|
|
snap: const SnapRelease(
|
|
|
|
|
name: 'halkyon-notes',
|
|
|
|
|
part: 'halkyon-notes',
|
|
|
|
|
directory: 'snap',
|
|
|
|
|
storeChannel: 'edge',
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(
|
|
|
|
|
text,
|
|
|
|
|
contains(
|
|
|
|
|
'- **Linux:** `halkyon-notes_0.19.0_amd64.snap`, also in the Snap '
|
|
|
|
|
'Store: `snap install halkyon-notes --edge`.\n',
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('the first release says so, one without features or fixes too', () {
|
|
|
|
|
expect(notes(previousTag: null), contains('\n\nThe first release.\n\n'));
|
|
|
|
|
expect(
|
|
|
|
|
notes(
|
|
|
|
|
commits: const [Commit(hash: 'a', subject: 'docs: x')],
|
|
|
|
|
),
|
|
|
|
|
contains('\n\nNo features or fixes since notes-v0.18.0.\n\n'),
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
}
|