Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .fvmrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"flutter": "3.41.3"
"flutter": "3.47.5"
}
78 changes: 78 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: ci

# Runs in the Flutter Base template and in every project created from it.
on:
push:
branches: [main]
pull_request:

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

env:
# Use the SDK installed by flutter-action instead of the FVM link (.fvm/),
# which only exists on developer machines.
MELOS_SDK_PATH: auto

jobs:
verify:
name: Format, analyze and test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Read the pinned Flutter version
id: fvm
run: echo "version=$(jq -r '.flutter' .fvmrc)" >> "$GITHUB_OUTPUT"

- uses: subosito/flutter-action@v2
with:
channel: stable
flutter-version: ${{ steps.fvm.outputs.version }}
cache: true

- name: Install Melos
run: dart pub global activate melos

- name: Bootstrap the workspace
run: melos bootstrap

- name: Format, analyze and test
run: melos run verify

build-android:
name: Build Android (debug)
runs-on: ubuntu-latest
needs: verify
steps:
- uses: actions/checkout@v4

- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"

- name: Read the pinned Flutter version
id: fvm
run: echo "version=$(jq -r '.flutter' .fvmrc)" >> "$GITHUB_OUTPUT"

- uses: subosito/flutter-action@v2
with:
channel: stable
flutter-version: ${{ steps.fvm.outputs.version }}
cache: true

# compileSdk 37 (android/build.properties) is newer than the runner image.
- name: Install Android SDK platform 37
run: yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --install "platforms;android-37.0" > /dev/null

- name: Install Melos
run: dart pub global activate melos

- name: Bootstrap the workspace
run: melos bootstrap

- name: Build the dev flavor
working-directory: app
run: flutter build apk --debug -t lib/main/env/main_dev.dart --dart-define-from-file=env/.dev
81 changes: 48 additions & 33 deletions .github/workflows/sonar-qube-scann.yml
Original file line number Diff line number Diff line change
@@ -1,67 +1,82 @@
name: sonarqube

# ────────────────────────────────────────────────────────────────
# CI TRIGGERS
# · push on main → historical baseline
# · pull_request PRs → quality gate before merge
# ────────────────────────────────────────────────────────────────
# Project-only automation. It is skipped in the Flutter Base template and runs
# in projects created from it: the `gate` job reads flutter_base.json, and
# `melos run init` switches its "state" from "template" to "initialized".
#
# Required repository secrets once the project is initialized:
# SONAR_TOKEN SonarQube project token
# SONAR_URL SonarQube server URL
# Optional:
# SSH_PRIVATE_KEY only needed for git-based pub dependencies
on:
push:
branches: [main]
pull_request:
types: [opened, synchronize, reopened]

jobs:
sonarQubeTrigger:
name: Sonarqube Trigger
gate:
name: Check repository state
runs-on: ubuntu-latest
outputs:
initialized: ${{ steps.state.outputs.initialized }}
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: flutter_base.json
sparse-checkout-cone-mode: false

- name: Read flutter_base.json
id: state
run: |
state=$(jq -r '.state' flutter_base.json)
echo "Repository state: $state"
if [ "$state" = "initialized" ]; then
echo "initialized=true" >> "$GITHUB_OUTPUT"
else
echo "initialized=false" >> "$GITHUB_OUTPUT"
echo "::notice::Template repository: SonarQube is disabled until 'melos run init' initializes a project."
fi

sonarqube:
name: SonarQube scan
needs: gate
if: needs.gate.outputs.initialized == 'true'
runs-on: ubuntu-latest
env:
MELOS_SDK_PATH: auto
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
steps:
# 1 — Checkout the repo
- name: Checkout code
uses: actions/checkout@v4
- uses: actions/checkout@v4

# 2 — SSH agent for any Git-based pub dependencies
- name: Start ssh-agent
- name: Start ssh-agent for git-based pub dependencies
if: env.SSH_PRIVATE_KEY != ''
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}

# 3 — Read the pinned Flutter version from .fvmrc
- name: Read Flutter version
- name: Read the pinned Flutter version
id: fvm
run: echo "version=$(jq -r '.flutter' .fvmrc)" >> "$GITHUB_OUTPUT"

# 4 — Install Flutter SDK at the pinned version
- name: Set up Flutter
uses: subosito/flutter-action@v2
- uses: subosito/flutter-action@v2
with:
channel: stable
flutter-version: ${{ steps.fvm.outputs.version }}
cache: true

# 5 — Install Melos
- name: Install Melos
run: dart pub global activate melos

# 6 — Resolve workspace dependencies
- name: Bootstrap workspace
- name: Bootstrap the workspace
run: melos bootstrap

# 7 — Static analysis
- name: Analyze
run: melos run analyze

# 8 — Run tests (each package)
- name: Run tests
run: melos exec --dir-exists=test -- flutter test

# 9 — Install SonarScanner CLI (needed by full_coverage.py)
- name: Setup Sonarqube Scanner
- name: Set up SonarScanner
uses: warchant/setup-sonar-scanner@v8

# 10 — Generate coverage & run SonarQube
- name: Generate coverage & run SonarQube
- name: Generate coverage and run SonarQube
run: python3 coverage/full_coverage.py --ci
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_URL: ${{ secrets.SONAR_URL }}
SONAR_HOST_URL: ${{ secrets.SONAR_URL }}
81 changes: 53 additions & 28 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,21 @@ A **reusable company template**, not a product. Teams clone it to start new Flut
here is inherited by future projects, so optimize for reuse and clear boundaries, and keep
product-specific assumptions out of the base modules.

If you are working in a **project cloned from this template**, the same rules apply, but the example
features (auth, onboarding, home) and the placeholders are yours to replace.
See [docs/development/bootstrap-customization.md](docs/development/bootstrap-customization.md).
### Template or project? Check `flutter_base.json`

`flutter_base.json` → `"state"` says which one you are in:

- **`"template"`**: this is the Flutter Base itself. Keep project-specific values (real app names, bundle IDs,
endpoints, secrets) out of it. Project-only automation (the SonarQube workflow) is switched off.
- **`"initialized"`**: a project created from the base with `melos run init`. The example features (auth,
onboarding, home) and the remaining manual placeholders are yours to replace.

Never edit `flutter_base.json` or the identity values by hand to switch state. The lifecycle, parameters and
manual steps are in [docs/development/project-initialization.md](docs/development/project-initialization.md).

## Architecture in one screen

A Dart pub workspace, managed with Melos 7, with one Flutter app and three local path packages, layered like this:
A Dart pub workspace, managed with Melos 8, with one Flutter app and three local path packages, layered like this:

```
app ──► domain ──► common
Expand All @@ -28,6 +36,8 @@ app ──► domain ──► common
| `modules/data/` | Data access | Repository **implementations**, Dio `NetworkConfig`, interceptors, `Preferences` (shared_preferences) |
| `modules/common/` | Shared utilities with no feature knowledge | `ResultType`, `Resource`, `Failure`, platform/permission abstractions, analytics interface, validators |

`tool/project_init/` is a fifth workspace member: the pure-Dart project initializer (tooling, not an app layer).

- **State management:** `flutter_bloc` Cubits, which live in `domain` (not in `app`). Async screens use
`BaseCubit<T>`, which emits `Resource<T>` (`RLoading`/`RSuccess`/`RError`).
- **Result flow:** repository returns `Future<ResultType<T>>` (`TSuccess`/`TError`) → service → cubit → `Resource<T>` → widget via `BlocBuilder`.
Expand All @@ -37,19 +47,23 @@ app ──► domain ──► common
- **Persistence:** the `Preferences` interface over `SharedPreferences` (in `data`).
- **Errors:** the sealed `Failure` hierarchy plus the `DioException.toFailure()` mapper (in `common`).
- **Flavors:** `dev` / `qa` / `prod` entrypoints + `flutter_dotenv`. Read [docs/architecture/overview.md § Environments](docs/architecture/overview.md#environments-and-flavors) before touching env handling. It has known inconsistencies.
- **Project identity** (name, package, bundle IDs) is kept in a few files the initializer owns: `flutter_base.json`,
`app/android/build.properties`, `app/ios/Flutter/AppIdentity.xcconfig` (see the repository map).

Deeper docs:
- [docs/architecture/overview.md](docs/architecture/overview.md): layers, data flow, DI, navigation, env
- [docs/architecture/modules.md](docs/architecture/modules.md): **module boundaries, dependency rules, creating a module**
- [docs/development/feature-guide.md](docs/development/feature-guide.md): how to add a feature end to end
- [docs/development/testing.md](docs/development/testing.md): testing strategy and commands
- [docs/development/bootstrap-customization.md](docs/development/bootstrap-customization.md): what to change in a new project
- [docs/development/project-initialization.md](docs/development/project-initialization.md): **template vs project, `melos run init`, toolchain requirements**
- [docs/development/bootstrap-customization.md](docs/development/bootstrap-customization.md): what to keep, customize or replace in a new project
- [docs/architecture/known-issues.md](docs/architecture/known-issues.md): verified defects and tech debt. **Read it before "fixing" something that looks wrong.**

## Repository map

```
app/ Flutter application (package name: app)
flutter_base.json repository state (template | initialized) + current identity. Written only by the initializer
app/ Flutter application (Dart package `app` in the template; init renames it)
lib/main.dart prod entrypoint
lib/main/env/ main_dev.dart, main_qa.dart, env_config.dart (Flavor, FlavorConfig, Environment)
lib/main/init.dart composition root: dotenv load, GetIt registration, runApp
Expand All @@ -64,39 +78,48 @@ app/ Flutter application (package name: app)
resources/ Dimen, Images (part files of resources.dart), locale/*.arb + generated/
env/ dotenv files bundled as assets (.dev, .env.example)
test/ app tests
android/ ios/ web/ linux/ platform projects
android/build.properties Android identity (applicationId, namespace, label) + SDK levels
ios/Flutter/AppIdentity.xcconfig iOS identity (APP_BUNDLE_ID, APP_DISPLAY_NAME). Flavor xcconfigs: ios/dev.xcconfig, ios/qa.xcconfig
android/ ios/ web/ linux/ platform projects (iOS uses Swift Package Manager, no CocoaPods)
modules/domain/lib/ bloc/, services/, repositories/ (interfaces), models/, env/, init.dart
modules/data/lib/ repositories/ (impls), network/, preferences/, data_sources/ (placeholders), init.dart
modules/common/lib/ core/, devices/, analytics/, ui/, validators/, init.dart
tool/project_init/ the initializer (bin/init.dart, lib/src/, tests that initialize a copy of this repo)
pubspec.yaml (root) pub workspace root (`workspace:` members) + the `melos:` scripts; one shared pubspec.lock
.fvmrc pinned Flutter SDK version (FVM). CI reads it too
.github/workflows/ sonar-qube-scann.yml: analyze + tests + coverage/SonarQube on every PR and push to main
.github/workflows/ ci.yml: format + analyze + test + Android build (template and project)
sonar-qube-scann.yml: coverage + SonarQube, project-only (skipped in the template)
coverage/full_coverage.py multi-package LCOV merge + SonarQube upload
sonar-project.properties SonarQube config (placeholders)
addModule.py pulls a module from rootstrap/flutter-modules (see known-issues)
```

## Commands

Prerequisites: FVM (`dart pub global activate fvm`, then `fvm install` installs the Flutter version pinned in
`.fvmrc`, 3.41.3) and Melos 7 (`dart pub global activate melos`). Packages require Dart `>=3.6.0` and Flutter `>=3.41.0`.
Run Flutter through FVM (`fvm flutter …`) so you use the pinned SDK. The commands below say `flutter` for brevity.
Toolchain: Flutter **3.47.5** (Dart **3.13.4**) pinned in `.fvmrc` (`dart pub global activate fvm`, then `fvm install`),
Melos **8.9** (`dart pub global activate melos`), JDK **17**, Android SDK platform **37** (compileSdk; install it with
`sdkmanager "platforms;android-37.0"`), and Xcode with the iOS 15+ SDK. CocoaPods is not needed. Run Flutter through
FVM (`fvm flutter …`) so you use the pinned SDK; the commands below say `flutter` for brevity.

| Task | Command (from repo root unless noted) |
|---|---|
| Initialize a project (template only) | `melos run init` (prompts), or `dart tool/project_init/bin/init.dart --name "My App" --package-name my_app --bundle-id com.company.myapp`. Add `--dry-run` to preview. |
| Install deps for the whole workspace | `melos bootstrap` |
| Check toolchain | `melos doctor` |
| Static analysis (CI gate) | `melos run analyze` (runs `dart analyze . --fatal-infos` per package) |
| Format check | `melos run format` (runs `dart format --set-exit-if-changed .` per package, and fails on unformatted code) |
| Analyze + format | `melos run lint:all` |
| Regenerate l10n after editing `.arb` | `cd app && dart run intl_utils:generate` |
| build_runner | `melos run pub:runner`. No generators are configured today (see known-issues #12). |
| Everything CI checks | `melos run verify` (format, analyze, test) |
| Static analysis | `melos analyze` (same as `melos run analyze`: `dart analyze . --fatal-infos` per package) |
| Format (fails if a file changed) | `melos format` (same as `melos run format`) |
| Test | `melos run test` (`flutter test` in Flutter packages, `dart test` in pure-Dart ones) |
| Regenerate l10n after editing `.arb` | `melos run gen:l10n` |
| build_runner | `melos run pub:runner` (only packages that depend on build_runner; none use generators today) |
| Run (dev) | `cd app && flutter run -t lib/main/env/main_dev.dart --dart-define-from-file=env/.dev` |
| Run web | `melos run run:web` (uses the **prod** entrypoint `lib/main.dart` with `env/.dev`) |
| Test (CI gate) | `melos exec --dir-exists=test -- flutter test` (runs every package that has a `test/` directory) |
| Run web (dev) | `melos run run:web` |
| Check toolchain | `melos doctor` |
| Coverage + Sonar | `python3 coverage/full_coverage.py --dry-run` (drop `--dry-run` to execute; `--ci` for non-interactive) |
| Build Android | `cd app && flutter build appbundle -t lib/main.dart --dart-define-from-file=<env file>` |
| Build iOS | `cd app && flutter build ipa --release -t lib/main.dart --dart-define-from-file=<env file>` |
| Build Android | `cd app && flutter build apk --debug -t lib/main/env/main_dev.dart --dart-define-from-file=env/.dev` (release: `flutter build appbundle -t lib/main.dart --dart-define-from-file=<env file>`) |
| Build iOS | `cd app && flutter build ios --simulator --debug -t lib/main/env/main_dev.dart --dart-define-from-file=env/.dev` (release: `flutter build ipa --release -t lib/main.dart --dart-define-from-file=<env file>`) |

`analyze`, `format` and `test` are root scripts that shadow Melos's built-in commands of the same name, so
`melos analyze` and `melos run analyze` run the same thing. Don't make such a script call the built-in: it recurses.

Flavors: iOS has `Dev`/`QA`/`Runner` schemes (`--flavor dev|qa` works on iOS). Android has **no**
`productFlavors`, so select the environment with `-t <entrypoint>` only. Details are in overview.md.
Expand Down Expand Up @@ -124,6 +147,9 @@ Flavors: iOS has `Dev`/`QA`/`Runner` schemes (`--flavor dev|qa` works on iOS). A
9. **Don't introduce a second pattern** (Riverpod, Provider-only state, another HTTP client, another DI
container, freezed/json_serializable) without an explicit architecture decision.
10. **Don't edit generated or platform-generated files** (see below).
Identity files (`flutter_base.json`, `android/build.properties` identity keys, `ios/Flutter/AppIdentity.xcconfig`)
are changed by the initializer. If you change a template file the initializer edits, update
`tool/project_init/lib/src/plan.dart` too; its tests fail if the two drift apart.
11. **Behavior changes need tests.** See [testing.md](docs/development/testing.md). Only `common` and `domain`
have a few, so create the package's `test/` directory as the guide describes rather than skipping. CI picks it up automatically.
12. **Report pre-existing problems; don't silently fix them** in unrelated changes. Record them in
Expand All @@ -133,6 +159,7 @@ Flavors: iOS has `Dev`/`QA`/`Runner` schemes (`--flavor dev|qa` works on iOS). A
- `app/lib/presentation/resources/locale/generated/**`: intl_utils output. Edit the `.arb` files and regenerate.
- `**/generated_plugin_registrant.*`, `app/linux/flutter/generated_*`, `ios/Flutter/Generated.xcconfig`: Flutter tool output.
- `pubspec.lock` (a single workspace lock at the root, git-ignored), `.dart_tool/`, `.fvm/`, `build/`, `coverage/lcov*.info`.
- `flutter_base.json`: written by the initializer only.
- If you add build_runner generators: `*.g.dart`, `*.freezed.dart`, `*.mocks.dart` (already excluded in Sonar and coverage).

## Before implementing
Expand All @@ -148,18 +175,16 @@ Flavors: iOS has `Dev`/`QA`/`Runner` schemes (`--flavor dev|qa` works on iOS). A

## Definition of done

- [ ] `melos run format` passes (run `dart format .` in the package to fix)
- [ ] `melos run analyze` passes (it is `--fatal-infos`, so infos fail too)
- [ ] `melos exec --dir-exists=test -- flutter test` passes
- [ ] `.arb` edited in all locales and `dart run intl_utils:generate` output committed
- [ ] `melos run verify` passes (format, analyze with fatal infos, test). `format` rewrites files, so commit the result
- [ ] In the template, `flutter_base.json` still says `"template"` and no project-specific values were added
- [ ] `.arb` edited in all locales and `melos run gen:l10n` output committed
- [ ] New code registered in the right `init.dart`; no forbidden imports (the rules in § Rules and modules.md)
- [ ] App still launches on the dev entrypoint; `flutter build` succeeds for platforms touched
- [ ] Docs updated if you changed a boundary, command, extension point, or env handling
- [ ] PR follows `.github/pull_request_template.md` (description, issue link, preview)

CI (`.github/workflows/sonar-qube-scann.yml`) is meant to run analyze, the tests and coverage/SonarQube on
every PR, but it currently fails before any of them run (missing `SSH_PRIVATE_KEY` secret). It also has no
format or build step. Run the whole checklist locally (known-issues #3).
CI (`.github/workflows/ci.yml`) runs `melos run verify` and an Android debug build on every PR, in the template and
in projects. iOS is not built in CI, so build it locally when you touch iOS or shared native configuration.

## Delivery system

Expand Down
Loading
Loading