chore(lint): LeanCode's strict rules and a pre-push check
The workspace root's analysis_options.yaml includes leancode_lint 27 and enables its analyzer plugin (rules for blocs, Equatable and Flutter layout); the server and the app include that file, while the generated client keeps its own options. Plugins can only be enabled at the root of a pub workspace and only reach packages whose options include the root file, which was verified before settling on this layout. Most rules are warnings, casts, inference and raw types are strict, and the check treats infos as fatal too. lints and flutter_lints are no longer needed. tool/check.sh checks formatting, runs dart analyze --fatal-infos and the server tests; .git/hooks/pre-push links to it, so every push has to pass it. AGENTS.md describes the checklist and how to install the hook in a fresh clone. Fixes for the new rules: package imports, run() returns Future<void>, the server setup as one cascade, a primary constructor for the app widget, a const and a redundant argument. The workspace root keeps its conventional name `_` with a documented ignore. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
549406b154
commit
41e968c53e
12 changed files with 134 additions and 92 deletions
|
|
@ -28,15 +28,14 @@ Only when the server cannot be started at all, fall back to the CLI in the serve
|
||||||
- `serverpod generate` to regenerate the client and the generated server code.
|
- `serverpod generate` to regenerate the client and the generated server code.
|
||||||
- `serverpod create-migration` after changing a model with a `table` (add `--force` for destructive changes). It only writes the migration; `serverpod start` applies pending migrations when it boots the server.
|
- `serverpod create-migration` after changing a model with a `table` (add `--force` for destructive changes). It only writes the migration; `serverpod start` applies pending migrations when it boots the server.
|
||||||
|
|
||||||
Tests need no Docker. `config/test.yaml` sets `database.dataPath`, so Serverpod starts and manages the test database (an embedded PostgreSQL) itself, and the project's `docker-compose.yaml` is not used for it. Just run `dart test` in the server package.
|
Tests need no Docker. `config/test.yaml` sets `database.dataPath`, so Serverpod starts and manages the test database (an embedded PostgreSQL) itself. Just run `dart test` in the server package.
|
||||||
|
|
||||||
Checklist after doing changes, in this order:
|
Checklist after doing changes, in this order:
|
||||||
|
|
||||||
- `dart analyze` (CLI)
|
- `dart format` (CLI) on the files you changed
|
||||||
- `dart format` (CLI)
|
|
||||||
- `create_migration` and `apply_migrations` (MCP - only if necessary)
|
- `create_migration` and `apply_migrations` (MCP - only if necessary)
|
||||||
|
- `tool/check.sh` (CLI): format check, `dart analyze --fatal-infos`, server tests. The pre-push hook runs the same script, so a push only goes through when it passes.
|
||||||
- Do `serverpod` MCP `hot_restart` if required (hot reload is done automatically). Will also hot restart Flutter app
|
- Do `serverpod` MCP `hot_restart` if required (hot reload is done automatically). Will also hot restart Flutter app
|
||||||
- Run tests, if applicable (`dart test` in the server package)
|
|
||||||
- Check `serverpod` MCP `tail_server_logs` and `tail_flutter_logs` for any issues.
|
- Check `serverpod` MCP `tail_server_logs` and `tail_flutter_logs` for any issues.
|
||||||
|
|
||||||
If the user asks you to test the app:
|
If the user asks you to test the app:
|
||||||
|
|
@ -63,6 +62,8 @@ A spaced-repetition learning app: studysets of flashcards, reviewed on an FSRS s
|
||||||
- **Before keeping a framework default, name the alternatives** and recommend one, especially where a choice becomes expensive once users or data exist.
|
- **Before keeping a framework default, name the alternatives** and recommend one, especially where a choice becomes expensive once users or data exist.
|
||||||
- Every decision carries its reason (in `NEXT.md` or an ADR under `docs/adr/`).
|
- Every decision carries its reason (in `NEXT.md` or an ADR under `docs/adr/`).
|
||||||
- **Commits** are GPG-signed and carry a DCO `Signed-off-by` trailer (`git commit -s`; a local `prepare-commit-msg` hook adds it automatically).
|
- **Commits** are GPG-signed and carry a DCO `Signed-off-by` trailer (`git commit -s`; a local `prepare-commit-msg` hook adds it automatically).
|
||||||
|
- **Pushes** pass `tool/check.sh` first, through a local `pre-push` hook (in a fresh clone: `ln -s ../../tool/check.sh .git/hooks/pre-push`).
|
||||||
|
- **Lints**: LeanCode's strict set (`leancode_lint`), configured once in the root `analysis_options.yaml`, which also enables its analyzer plugin. Plugins can only be enabled at the root of a pub workspace and only reach packages whose options include that file, so the server and the app include it; the generated client doesn't. Every `// ignore:` gets a comment saying why (`document_ignores`).
|
||||||
|
|
||||||
## App architecture
|
## App architecture
|
||||||
|
|
||||||
|
|
|
||||||
11
analysis_options.yaml
Normal file
11
analysis_options.yaml
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Lint rules for the whole pub workspace: LeanCode's strict set (leancode_lint), which
|
||||||
|
# raises most rules to warnings and turns on strict casts, inference and raw types, plus
|
||||||
|
# its analyzer plugin with rules for blocs, Equatable and Flutter layout.
|
||||||
|
#
|
||||||
|
# Analyzer plugins can only be enabled here, at the root of a pub workspace, and they only
|
||||||
|
# apply to packages whose analysis_options.yaml includes this file. The server and the app
|
||||||
|
# include it; the generated client keeps its own options, because it is never edited.
|
||||||
|
include: package:leancode_lint/analysis_options.yaml
|
||||||
|
|
||||||
|
plugins:
|
||||||
|
leancode_lint: ^27.0.0
|
||||||
|
|
@ -1,34 +1,9 @@
|
||||||
# This file configures the analyzer, which statically analyzes Dart code to
|
# The workspace's lint rules (see ../analysis_options.yaml), minus the platform folders.
|
||||||
# check for errors, warnings, and lints.
|
|
||||||
#
|
#
|
||||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
# If this app ever leaves the pub workspace, it has to enable the analyzer plugin itself:
|
||||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
# include package:leancode_lint/analysis_options.yaml and add the `plugins:` section here.
|
||||||
# invoked from the command line by running `flutter analyze`.
|
include: ../analysis_options.yaml
|
||||||
|
|
||||||
# The following line activates a set of recommended lints for Flutter apps,
|
|
||||||
# packages, and plugins designed to encourage good coding practices.
|
|
||||||
include: package:flutter_lints/flutter.yaml
|
|
||||||
|
|
||||||
linter:
|
|
||||||
# The lint rules applied to this project can be customized in the
|
|
||||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
|
||||||
# included above or to enable additional rules. A list of all available lints
|
|
||||||
# and their documentation is published at
|
|
||||||
# https://dart-lang.github.io/linter/lints/index.html.
|
|
||||||
#
|
|
||||||
# Instead of disabling a lint rule for the entire project in the
|
|
||||||
# section below, it can also be suppressed for a single line of code
|
|
||||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
|
||||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
|
||||||
# producing the lint.
|
|
||||||
rules:
|
|
||||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
|
||||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
|
||||||
# Additional information about this file can be found at
|
|
||||||
# https://dart.dev/guides/language/analysis-options
|
|
||||||
|
|
||||||
formatter:
|
|
||||||
trailing_commas: preserve
|
|
||||||
analyzer:
|
analyzer:
|
||||||
exclude:
|
exclude:
|
||||||
- build/**
|
- build/**
|
||||||
|
|
@ -38,3 +13,6 @@ analyzer:
|
||||||
- windows/**
|
- windows/**
|
||||||
- macos/**
|
- macos/**
|
||||||
- linux/**
|
- linux/**
|
||||||
|
|
||||||
|
formatter:
|
||||||
|
trailing_commas: preserve
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
// ignore: depend_on_referenced_packages
|
// ignore: depend_on_referenced_packages
|
||||||
import 'package:flutter_driver/driver_extension.dart';
|
import 'package:flutter_driver/driver_extension.dart';
|
||||||
|
|
||||||
import 'main.dart' as app;
|
import 'package:halkyon_learn_flutter/main.dart' as app;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
enableFlutterDriverExtension(
|
enableFlutterDriverExtension(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'client.dart';
|
import 'package:halkyon_learn_flutter/client.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
@ -18,17 +18,14 @@ ThemeData _buildTheme(Brightness brightness) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class HalkyonLearnApp extends StatelessWidget {
|
class const HalkyonLearnApp({super.key}) extends StatelessWidget {
|
||||||
const HalkyonLearnApp({super.key});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'Halkyon Learn',
|
title: 'Halkyon Learn',
|
||||||
theme: _buildTheme(Brightness.light),
|
theme: _buildTheme(Brightness.light),
|
||||||
darkTheme: _buildTheme(Brightness.dark),
|
darkTheme: _buildTheme(Brightness.dark),
|
||||||
themeMode: ThemeMode.system,
|
home: Scaffold(appBar: AppBar(title: const Text('Halkyon Learn'))),
|
||||||
home: Scaffold(appBar: AppBar(title: Text('Halkyon Learn'))),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,6 @@ dev_dependencies:
|
||||||
# Used for agents to drive the app through the Dart MCP
|
# Used for agents to drive the app through the Dart MCP
|
||||||
flutter_driver:
|
flutter_driver:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_lints: '>=3.0.0 <7.0.0'
|
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,10 @@
|
||||||
# This file configures the static analysis results for your project (errors,
|
# The workspace's lint rules (see ../analysis_options.yaml), minus generated code.
|
||||||
# warnings, and lints).
|
include: ../analysis_options.yaml
|
||||||
#
|
|
||||||
# This enables the 'recommended' set of lints from `package:lints`.
|
|
||||||
# This set helps identify many issues that may lead to problems when running
|
|
||||||
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
|
|
||||||
# style and format.
|
|
||||||
#
|
|
||||||
# If you want a smaller set of lints you can change this to specify
|
|
||||||
# 'package:lints/core.yaml'. These are just the most critical lints
|
|
||||||
# (the recommended set includes the core lints).
|
|
||||||
# The core lints are also what is used by pub.dev for scoring packages.
|
|
||||||
|
|
||||||
include: package:lints/recommended.yaml
|
|
||||||
|
|
||||||
# Uncomment the following section to specify additional rules.
|
|
||||||
|
|
||||||
linter:
|
|
||||||
rules:
|
|
||||||
unawaited_futures: true
|
|
||||||
avoid_print: true
|
|
||||||
|
|
||||||
analyzer:
|
analyzer:
|
||||||
exclude:
|
exclude:
|
||||||
- lib/src/generated/**
|
- lib/src/generated/**
|
||||||
- test/integration/test_tools/serverpod_test_tools.dart
|
- test/integration/test_tools/serverpod_test_tools.dart
|
||||||
# For more information about the core and recommended set of lints, see
|
|
||||||
# https://dart.dev/go/core-lints
|
|
||||||
|
|
||||||
# For additional information about configuring this file, see
|
|
||||||
# https://dart.dev/guides/language/analysis-options
|
|
||||||
|
|
||||||
formatter:
|
formatter:
|
||||||
trailing_commas: preserve
|
trailing_commas: preserve
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,28 @@
|
||||||
|
import 'package:halkyon_learn_server/src/generated/serverpod.dart';
|
||||||
import 'package:serverpod_auth_idp_server/core.dart';
|
import 'package:serverpod_auth_idp_server/core.dart';
|
||||||
|
|
||||||
import 'src/generated/serverpod.dart';
|
|
||||||
|
|
||||||
/// The starting point of the Serverpod server.
|
/// The starting point of the Serverpod server.
|
||||||
void run(List<String> args) async {
|
Future<void> run(List<String> args) async {
|
||||||
// Initialize Serverpod. The generated Serverpod class is already connected
|
// Initialize Serverpod. The generated Serverpod class is already connected
|
||||||
// with your project's generated code.
|
// with your project's generated code.
|
||||||
final pod = Serverpod(args);
|
final pod = Serverpod(args)
|
||||||
|
// Initialize authentication services for the server.
|
||||||
// Initialize authentication services for the server.
|
// Token managers will be used to validate and issue authentication keys,
|
||||||
// Token managers will be used to validate and issue authentication keys,
|
// and the identity providers will be the authentication options available for users.
|
||||||
// and the identity providers will be the authentication options available for users.
|
..initializeAuthServices(
|
||||||
pod.initializeAuthServices(
|
tokenManagerBuilders: [
|
||||||
tokenManagerBuilders: [
|
// Server-side sessions: revocable immediately, scopes live on the session row.
|
||||||
// Server-side sessions: revocable immediately, scopes live on the session row.
|
// No absolute lifetime (anonymous session cannot be restored). The inactivity
|
||||||
// No absolute lifetime (anonymous session cannot be restored). The inactivity
|
// timeout matches the pruning policy for abandoned accounts.
|
||||||
// timeout matches the pruning policy for abandoned accounts.
|
ServerSideSessionsConfigFromPasswords(
|
||||||
ServerSideSessionsConfigFromPasswords(
|
defaultSessionInactivityTimeout: const Duration(days: 90),
|
||||||
defaultSessionInactivityTimeout: const Duration(days: 90),
|
),
|
||||||
),
|
],
|
||||||
],
|
identityProviderBuilders: [],
|
||||||
identityProviderBuilders: [],
|
)
|
||||||
);
|
// Files live in Postgres until the self-hosted S3-compatible storage arrives later.
|
||||||
|
..addCloudStorage(DatabaseCloudStorage('private'))
|
||||||
// Files live in Postgres until the self-hosted S3-compatible storage arrives later.
|
..addCloudStorage(DatabaseCloudStorage('public'));
|
||||||
pod.addCloudStorage(DatabaseCloudStorage('private'));
|
|
||||||
pod.addCloudStorage(DatabaseCloudStorage('public'));
|
|
||||||
|
|
||||||
// Start the server.
|
// Start the server.
|
||||||
await pod.start();
|
await pod.start();
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ dependencies:
|
||||||
serverpod: 4.0.2
|
serverpod: 4.0.2
|
||||||
serverpod_auth_idp_server: 4.0.2
|
serverpod_auth_idp_server: 4.0.2
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
lints: '>=3.0.0 <7.0.0'
|
|
||||||
serverpod_test: 4.0.2
|
serverpod_test: 4.0.2
|
||||||
test: ^1.25.5
|
test: ^1.25.5
|
||||||
|
|
||||||
|
|
|
||||||
40
pubspec.lock
40
pubspec.lock
|
|
@ -9,6 +9,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "103.0.0"
|
version: "103.0.0"
|
||||||
|
analysis_server_plugin:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: analysis_server_plugin
|
||||||
|
sha256: c58b2a05b260a023eefd0848496ff6b113a318c6c95dad4215165a4bd54ae600
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.18"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -17,6 +25,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "13.3.0"
|
version: "13.3.0"
|
||||||
|
analyzer_plugin:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: analyzer_plugin
|
||||||
|
sha256: c2a75baf1657171cc7872db96a12b07e385873cf7a73f2b4f7638e9d0701ee53
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.14.12"
|
||||||
animations:
|
animations:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -193,6 +209,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.4.1"
|
version: "3.4.1"
|
||||||
|
dart_style:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dart_style
|
||||||
|
sha256: "82ade9fc4273f29ed673e33166944465225b4f7fc5d4aaef48605cc751c18fc1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.13"
|
||||||
dbus:
|
dbus:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -559,6 +583,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.2"
|
version: "3.0.2"
|
||||||
|
leancode_lint:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: leancode_lint
|
||||||
|
sha256: "23148aecd1a7b9cbf1c435dc3423a86875f15f76ff020e13fa901138d428b444"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "27.0.0"
|
||||||
lints:
|
lints:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1420,6 +1452,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.4"
|
version: "3.1.4"
|
||||||
|
yaml_edit:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: yaml_edit
|
||||||
|
sha256: "07c9e63ba42519745182b88ca12264a7ba2484d8239958778dfe4d44fe760488"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.4"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.13.0 <4.0.0"
|
dart: ">=3.13.0 <4.0.0"
|
||||||
flutter: ">=3.44.4 <4.0.0"
|
flutter: ">=3.44.4 <4.0.0"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
name: _
|
# `_` is the conventional name of a pub workspace's root, which is never published or
|
||||||
|
# imported, so the naming lint doesn't apply to it.
|
||||||
|
name: _ # ignore: package_names
|
||||||
publish_to: none
|
publish_to: none
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
|
@ -9,3 +11,6 @@ workspace:
|
||||||
- halkyon_learn_server
|
- halkyon_learn_server
|
||||||
- halkyon_learn_flutter
|
- halkyon_learn_flutter
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
# Referenced by analysis_options.yaml in this directory.
|
||||||
|
leancode_lint: ^27.0.0
|
||||||
|
|
|
||||||
39
tool/check.sh
Executable file
39
tool/check.sh
Executable file
|
|
@ -0,0 +1,39 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# The checks every push has to pass: formatting, static analysis (infos are fatal too) and
|
||||||
|
# the server tests. The pre-push hook runs this script, and a CI runner can run it later.
|
||||||
|
#
|
||||||
|
# Install the hook in a fresh clone: ln -s ../../tool/check.sh .git/hooks/pre-push
|
||||||
|
# Skip it for one push: git push --no-verify
|
||||||
|
#
|
||||||
|
# It checks the working tree, so uncommitted changes count as well.
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
|
||||||
|
if ! git diff --quiet HEAD; then
|
||||||
|
echo 'Note: the working tree has uncommitted changes; they are checked too.'
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Our own Dart files. Generated code is left out: the client package as a whole, and the
|
||||||
|
# server's generated sources and test tools. (A loop instead of mapfile: macOS ships bash 3.)
|
||||||
|
own=()
|
||||||
|
while IFS= read -r file; do own+=("$file"); done < <(
|
||||||
|
git ls-files '*.dart' |
|
||||||
|
grep -v -E '^halkyon_learn_client/|/lib/src/generated/|/serverpod_test_tools\.dart$'
|
||||||
|
)
|
||||||
|
|
||||||
|
echo '▸ Formatting'
|
||||||
|
if [ ${#own[@]} -gt 0 ]; then
|
||||||
|
dart format --output=none --set-exit-if-changed "${own[@]}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo '▸ Analysis'
|
||||||
|
dart analyze --fatal-infos
|
||||||
|
|
||||||
|
echo '▸ Server tests'
|
||||||
|
if git ls-files 'halkyon_learn_server/test/*_test.dart' | grep -q .; then
|
||||||
|
(cd halkyon_learn_server && dart test)
|
||||||
|
else
|
||||||
|
echo 'No server tests yet.'
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo '✓ All checks passed.'
|
||||||
Loading…
Reference in a new issue