build: Melos, a Dart check and every path for the single repository
- Melos 8.9 as the root's dev dependency, with scripts across the workspace (analyze, test:dart, test:flutter, generate, check) and its filters (--diff, --include-dependents); cli_util overridden to 0.5.2 for it, since Learn's flutter_launcher_icons asks for 0.4 and uses only its logger, unchanged in 0.5. - tool/check.dart in place of tool/check.sh: packages, formatting of our own files, analysis, the tools' tests, then every package's tests through Melos (--changed-since=<commit> for only what changed and what depends on it). tool/pre-push is the hook: the signature check, then the checks. CI generates every server's code and runs the same check. - The images build from the root with a workspace narrowed to what they need (tool/workspace_only.sh); compose, the deploy's migration test, the snap, the release script, the editor's launch files and Learn's asset tools (run from apps/learn) follow the new layout. - The project's source is named as project-halkyon/halkyon (source link, snap, F-Droid listing, release uploads); the AGPL packages carry the licence's own text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
9643412be9
commit
af1dbee509
1 changed files with 3771 additions and 792 deletions
134
tool/check.dart
Normal file
134
tool/check.dart
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
// The checks every push has to pass: the packages the lock names, formatting,
|
||||
// static analysis (infos are fatal too), and every package's tests. The
|
||||
// pre-push hook (tool/pre-push) runs it, and so does CI on every push
|
||||
// (.forgejo/workflows/ci.yaml).
|
||||
//
|
||||
// dart run tool/check.dart everything
|
||||
// dart run tool/check.dart --changed-since=origin/main
|
||||
// the tests of what changed since then, and of what
|
||||
// depends on it
|
||||
//
|
||||
// The tests run through Melos' scripts (`test:dart`, `test:flutter`, the root
|
||||
// pubspec), Melos itself a dependency of the root.
|
||||
// It checks the working tree, so uncommitted changes count as well.
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:args/args.dart';
|
||||
|
||||
import 'src/check_files.dart';
|
||||
|
||||
Future<void> main(List<String> arguments) async {
|
||||
final options =
|
||||
(ArgParser()
|
||||
..addOption(
|
||||
'changed-since',
|
||||
help:
|
||||
'Test only the packages changed since this commit, and those '
|
||||
'depending on them.',
|
||||
)
|
||||
..addFlag('help', abbr: 'h', negatable: false))
|
||||
.parse(arguments);
|
||||
if (options.flag('help')) {
|
||||
stdout.writeln(
|
||||
'Usage: dart run tool/check.dart [--changed-since=<commit>]',
|
||||
);
|
||||
return;
|
||||
}
|
||||
final changedSince = options.option('changed-since');
|
||||
|
||||
Directory.current = (await _output('git', [
|
||||
'rev-parse',
|
||||
'--show-toplevel',
|
||||
])).trim();
|
||||
if ((await Process.run('git', ['diff', '--quiet', 'HEAD'])).exitCode != 0) {
|
||||
stdout.writeln(
|
||||
'Note: the working tree has uncommitted changes; they are checked too.',
|
||||
);
|
||||
}
|
||||
|
||||
// The packages the checked-out pubspecs and lock name, fetched first: after
|
||||
// a merge or a switch of branch the workspace's resolution can still be the
|
||||
// one from before, and the analysis then failed on packages it couldn't
|
||||
// find (2026-10-13, 2026-10-17). The lock decides (--enforce-lockfile), so a
|
||||
// check never changes it.
|
||||
await _step('Packages', [
|
||||
('flutter', ['pub', 'get', '--enforce-lockfile']),
|
||||
], quiet: true);
|
||||
|
||||
final own = (await _output('git', [
|
||||
'ls-files',
|
||||
'*.dart',
|
||||
])).split('\n').where(isOwnDartFile).toList();
|
||||
await _step('Formatting', [
|
||||
('dart', ['format', '--output=none', '--set-exit-if-changed', ...own]),
|
||||
]);
|
||||
|
||||
await _step('Analysis', [
|
||||
('dart', ['analyze', '--fatal-infos']),
|
||||
]);
|
||||
|
||||
await _step('Tools', [
|
||||
('dart', ['test', 'test/']),
|
||||
]);
|
||||
|
||||
// The sync tests need `serverpod start` running (`requires-server`); run
|
||||
// them by hand with `flutter test -t requires-server` in Learn's app.
|
||||
final filters = melosFilters(changedSince: changedSince);
|
||||
await _step('Tests', [
|
||||
('dart', ['run', 'melos', 'run', 'test:dart', ...filters]),
|
||||
('dart', ['run', 'melos', 'run', 'test:flutter', ...filters]),
|
||||
]);
|
||||
|
||||
stdout.writeln('✓ All checks passed.');
|
||||
}
|
||||
|
||||
/// Runs [commands] one after another as the step [name], and ends the check
|
||||
/// at the first that fails, with its exit code.
|
||||
Future<void> _step(
|
||||
String name,
|
||||
List<(String, List<String>)> commands, {
|
||||
bool quiet = false,
|
||||
}) async {
|
||||
stdout.writeln('▸ $name');
|
||||
for (final (executable, arguments) in commands) {
|
||||
final code = await _run(executable, arguments, quiet: quiet);
|
||||
if (code != 0) {
|
||||
stderr.writeln('✗ $name failed ($executable ${arguments.first}).');
|
||||
exit(code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs [executable] and returns its exit code; its output is passed on as
|
||||
/// it comes, or if [quiet] only should it fail.
|
||||
Future<int> _run(
|
||||
String executable,
|
||||
List<String> arguments, {
|
||||
bool quiet = false,
|
||||
}) async {
|
||||
if (quiet) {
|
||||
final result = await Process.run(executable, arguments);
|
||||
if (result.exitCode != 0) {
|
||||
stderr
|
||||
..write(result.stdout)
|
||||
..write(result.stderr);
|
||||
}
|
||||
return result.exitCode;
|
||||
}
|
||||
final process = await Process.start(
|
||||
executable,
|
||||
arguments,
|
||||
mode: ProcessStartMode.inheritStdio,
|
||||
);
|
||||
return await process.exitCode;
|
||||
}
|
||||
|
||||
/// What [executable] writes, failing the check if it fails.
|
||||
Future<String> _output(String executable, List<String> arguments) async {
|
||||
final result = await Process.run(executable, arguments);
|
||||
if (result.exitCode != 0) {
|
||||
stderr.write(result.stderr);
|
||||
exit(result.exitCode);
|
||||
}
|
||||
return result.stdout as String;
|
||||
}
|
||||
Loading…
Reference in a new issue