forked from eva/focus-flow
Merge branch 'earthly-deployment-improvements'
This commit is contained in:
commit
f29fb3779e
6 changed files with 319 additions and 35 deletions
13
.earthlyignore
Normal file
13
.earthlyignore
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# SPDX-FileCopyrightText: 2026 FocusFlow contributors
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
.dart_tool/
|
||||
.git/
|
||||
.idea/
|
||||
build/
|
||||
builds/
|
||||
coverage/
|
||||
doc/api/
|
||||
apps/focus_flow_flutter/.dart_tool/
|
||||
apps/focus_flow_flutter/.plugin_symlinks/
|
||||
apps/focus_flow_flutter/build/
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -1,9 +1,13 @@
|
|||
# SPDX-FileCopyrightText: 2026 FocusFlow contributors
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
# Dart
|
||||
.dart_tool/
|
||||
.packages
|
||||
pubspec.lock
|
||||
!apps/focus_flow_flutter/pubspec.lock
|
||||
build/
|
||||
builds/
|
||||
coverage/
|
||||
doc/api/
|
||||
|
||||
|
|
|
|||
102
Earthfile
Normal file
102
Earthfile
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
# SPDX-FileCopyrightText: 2026 FocusFlow contributors
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
VERSION 0.8
|
||||
|
||||
# Earthly build pipeline for FocusFlow.
|
||||
#
|
||||
# The primary application artifact is the Flutter Linux desktop release bundle.
|
||||
# The +build target packages that bundle into builds/focus_flow_linux_x64.zip
|
||||
# and also exports the unzipped runnable bundle under builds/focus_flow_linux_x64.
|
||||
|
||||
setup:
|
||||
FROM ubuntu:24.04
|
||||
USER root
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
ENV FLUTTER_VERSION=3.44.3
|
||||
ENV FLUTTER_HOME=/opt/flutter
|
||||
ENV PATH=/opt/flutter/bin:/opt/flutter/bin/cache/dart-sdk/bin:$PATH
|
||||
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
clang \
|
||||
cmake \
|
||||
curl \
|
||||
dbus-x11 \
|
||||
git \
|
||||
libgtk-3-dev \
|
||||
liblzma-dev \
|
||||
libstdc++-12-dev \
|
||||
libglu1-mesa \
|
||||
ninja-build \
|
||||
pkg-config \
|
||||
unzip \
|
||||
xz-utils \
|
||||
xvfb \
|
||||
zip \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN curl -fsSL "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_${FLUTTER_VERSION}-stable.tar.xz" -o /tmp/flutter.tar.xz \
|
||||
&& mkdir -p /opt \
|
||||
&& tar -xJf /tmp/flutter.tar.xz -C /opt \
|
||||
&& rm /tmp/flutter.tar.xz
|
||||
WORKDIR /workspace
|
||||
RUN git config --global --add safe.directory /opt/flutter
|
||||
RUN flutter config --no-analytics
|
||||
RUN flutter config --enable-linux-desktop
|
||||
RUN flutter --version
|
||||
|
||||
code:
|
||||
FROM +setup
|
||||
COPY . .
|
||||
RUN dart pub get
|
||||
RUN cd apps/focus_flow_flutter && flutter pub get
|
||||
|
||||
format:
|
||||
FROM +code
|
||||
RUN dart format --set-exit-if-changed packages apps scripts tool test
|
||||
|
||||
dart-analyze:
|
||||
FROM +code
|
||||
RUN dart analyze
|
||||
|
||||
flutter-analyze:
|
||||
FROM +code
|
||||
RUN cd apps/focus_flow_flutter && flutter analyze
|
||||
|
||||
reuse:
|
||||
FROM +code
|
||||
RUN dart run tool/check_reuse.dart
|
||||
|
||||
docs:
|
||||
FROM +code
|
||||
RUN dart run tool/check_docs.dart --skip-dartdoc
|
||||
|
||||
pre-commit:
|
||||
FROM +code
|
||||
RUN .githooks/pre-commit
|
||||
|
||||
lint:
|
||||
FROM +code
|
||||
BUILD +format
|
||||
BUILD +dart-analyze
|
||||
BUILD +flutter-analyze
|
||||
BUILD +reuse
|
||||
BUILD +docs
|
||||
BUILD +pre-commit
|
||||
|
||||
build:
|
||||
FROM +code
|
||||
RUN cd apps/focus_flow_flutter && flutter build linux --release
|
||||
RUN rm -rf builds \
|
||||
&& mkdir -p builds/focus_flow_linux_x64 \
|
||||
&& cp -R apps/focus_flow_flutter/build/linux/x64/release/bundle/. builds/focus_flow_linux_x64/
|
||||
RUN cd builds && zip -qr focus_flow_linux_x64.zip focus_flow_linux_x64
|
||||
SAVE ARTIFACT --keep-ts builds AS LOCAL builds
|
||||
|
||||
run:
|
||||
FROM +build
|
||||
RUN test -x builds/focus_flow_linux_x64/focus_flow_flutter
|
||||
RUN status=0; timeout 10s xvfb-run -a builds/focus_flow_linux_x64/focus_flow_flutter || status=$?; if [ "$status" = "124" ]; then exit 0; fi; exit "$status"
|
||||
|
||||
all:
|
||||
BUILD +lint
|
||||
BUILD +build
|
||||
92
README.md
92
README.md
|
|
@ -1,9 +1,26 @@
|
|||
<!-- SPDX-FileCopyrightText: 2026 FocusFlow contributors -->
|
||||
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
|
||||
|
||||
# ADHD Scheduler Workspace
|
||||
|
||||
SQLite-first Dart workspace for the ADHD scheduler backend, persistence
|
||||
adapters, notifications, exports, backups, integration tests, and CI scripts.
|
||||
|
||||
## Test
|
||||
## Local Workflow
|
||||
|
||||
Set up the workspace and local SQLite path:
|
||||
|
||||
```sh
|
||||
scripts/bootstrap_dev.sh
|
||||
```
|
||||
|
||||
Run the local app during development:
|
||||
|
||||
```sh
|
||||
scripts/dev.sh
|
||||
```
|
||||
|
||||
Run the full local quality gate:
|
||||
|
||||
```sh
|
||||
scripts/test.sh
|
||||
|
|
@ -12,18 +29,17 @@ scripts/test.sh
|
|||
The test gate runs analyzer, tests with coverage, combines LCOV output, and
|
||||
fails below 80% package `lib/` coverage.
|
||||
|
||||
## Development
|
||||
Package a local desktop release with the host toolchain:
|
||||
|
||||
```sh
|
||||
scripts/bootstrap_dev.sh
|
||||
scripts/dev.sh
|
||||
scripts/package_release.sh --platform current --output build/releases
|
||||
```
|
||||
|
||||
The shell scripts prepare the default SQLite path and delegate to the Dart
|
||||
development runner.
|
||||
Equivalent Dart entry points:
|
||||
|
||||
```sh
|
||||
dart run scripts/dev.dart
|
||||
dart run scripts/build.dart --platform current --output build/releases
|
||||
```
|
||||
|
||||
The dev script prints the SQLite path, starts `dart run build_runner watch`, and
|
||||
|
|
@ -42,6 +58,70 @@ Optional flags:
|
|||
dart run scripts/dev.dart --device linux --sqlite /tmp/scheduler.sqlite
|
||||
```
|
||||
|
||||
## Earthly Workflow
|
||||
|
||||
Earthly is the supported containerized build path going forward. It builds a
|
||||
Linux desktop Flutter bundle and exports a runnable application under `builds/`.
|
||||
|
||||
Install prerequisites on the host:
|
||||
|
||||
- Earthly
|
||||
- Docker or another Earthly-compatible container runtime
|
||||
|
||||
Set up the Earthly build image:
|
||||
|
||||
```sh
|
||||
earthly +setup
|
||||
```
|
||||
|
||||
Copy code and resolve Dart/Flutter dependencies inside Earthly:
|
||||
|
||||
```sh
|
||||
earthly +code
|
||||
```
|
||||
|
||||
Run all style and metadata checks:
|
||||
|
||||
```sh
|
||||
earthly +lint
|
||||
```
|
||||
|
||||
Build and package the runnable Linux desktop app:
|
||||
|
||||
```sh
|
||||
earthly +build
|
||||
```
|
||||
|
||||
Run lint, build, and package in one command:
|
||||
|
||||
```sh
|
||||
earthly +all
|
||||
```
|
||||
|
||||
Smoke-run the Earthly-built Linux desktop app under a virtual display:
|
||||
|
||||
```sh
|
||||
earthly +run
|
||||
```
|
||||
|
||||
Earthly outputs:
|
||||
|
||||
```sh
|
||||
builds/focus_flow_linux_x64/
|
||||
builds/focus_flow_linux_x64.zip
|
||||
```
|
||||
|
||||
Useful focused lint targets:
|
||||
|
||||
```sh
|
||||
earthly +format
|
||||
earthly +dart-analyze
|
||||
earthly +flutter-analyze
|
||||
earthly +reuse
|
||||
earthly +docs
|
||||
earthly +pre-commit
|
||||
```
|
||||
|
||||
## Flutter UI
|
||||
|
||||
The provisional UI app lives outside the root Dart workspace at
|
||||
|
|
|
|||
|
|
@ -116,28 +116,73 @@ Future<List<String>> _trackedDartFilesMissingDeclarationDocs() async {
|
|||
|
||||
/// Returns tracked Dart files in stable path order.
|
||||
///
|
||||
/// Git is the source of truth so local build outputs, generated docs, and
|
||||
/// editor scratch files do not affect the documentation gate.
|
||||
/// Git is preferred so local build outputs, generated docs, and editor scratch
|
||||
/// files do not affect the documentation gate. Container build contexts may omit
|
||||
/// `.git`, so the tool falls back to a conservative walk over source folders.
|
||||
Future<List<String>> _trackedDartFiles() async {
|
||||
final result = await Process.run(
|
||||
'git',
|
||||
<String>['ls-files', '*.dart'],
|
||||
runInShell: Platform.isWindows,
|
||||
);
|
||||
if (result.exitCode != 0) {
|
||||
throw ProcessException(
|
||||
'git',
|
||||
<String>['ls-files', '*.dart'],
|
||||
'Unable to list tracked Dart files.',
|
||||
result.exitCode,
|
||||
);
|
||||
if (result.exitCode == 0) {
|
||||
return (result.stdout as String)
|
||||
.split('\n')
|
||||
.where((path) => path.trim().isNotEmpty)
|
||||
.toList(growable: false)
|
||||
..sort();
|
||||
}
|
||||
|
||||
return (result.stdout as String)
|
||||
.split('\n')
|
||||
.where((path) => path.trim().isNotEmpty)
|
||||
.toList(growable: false)
|
||||
..sort();
|
||||
return _walkDartFiles();
|
||||
}
|
||||
|
||||
/// Walks source folders for Dart files when Git metadata is unavailable.
|
||||
///
|
||||
/// This fallback supports Earthly and other copied build contexts while still
|
||||
/// avoiding generated output and tool caches that are not hand-maintained docs.
|
||||
List<String> _walkDartFiles() {
|
||||
final files = <String>[];
|
||||
for (final entity in Directory.current.listSync(recursive: true)) {
|
||||
if (entity is! File) {
|
||||
continue;
|
||||
}
|
||||
final path = entity.path.replaceAll('\\', '/').replaceFirst('./', '');
|
||||
if (!path.endsWith('.dart') ||
|
||||
!_isDartAuditScope(path) ||
|
||||
_isIgnoredGeneratedPath(path)) {
|
||||
continue;
|
||||
}
|
||||
files.add(path);
|
||||
}
|
||||
return files..sort();
|
||||
}
|
||||
|
||||
/// Reports whether [path] belongs to source folders covered by Dartdoc policy.
|
||||
///
|
||||
/// The root package, packages, scripts, tools, tests, and Flutter app are the
|
||||
/// source surfaces where contributors add hand-maintained Dart code.
|
||||
bool _isDartAuditScope(String path) {
|
||||
return path.startsWith('packages/') ||
|
||||
path.startsWith('apps/focus_flow_flutter/') ||
|
||||
path.startsWith('scripts/') ||
|
||||
path.startsWith('tool/') ||
|
||||
path.startsWith('test/');
|
||||
}
|
||||
|
||||
/// Reports whether [path] belongs to generated output or local tooling caches.
|
||||
///
|
||||
/// The fallback walker skips these paths because they are not source files and
|
||||
/// usually are not present in Git's tracked file list.
|
||||
bool _isIgnoredGeneratedPath(String path) {
|
||||
return path.startsWith('.dart_tool/') ||
|
||||
path.startsWith('.git/') ||
|
||||
path.startsWith('build/') ||
|
||||
path.startsWith('builds/') ||
|
||||
path.startsWith('coverage/') ||
|
||||
path.startsWith('doc/api/') ||
|
||||
path.contains('/.dart_tool/') ||
|
||||
path.contains('/build/') ||
|
||||
path.contains('/.plugin_symlinks/');
|
||||
}
|
||||
|
||||
/// Finds tracked Dart files that are missing file-level library docs.
|
||||
|
|
|
|||
|
|
@ -137,27 +137,60 @@ List<String> _spdxHeaderFailures() {
|
|||
|
||||
/// Returns tracked repository files in stable path order.
|
||||
///
|
||||
/// Git is used so ignored build output and local editor files do not affect the
|
||||
/// metadata check.
|
||||
/// Git is preferred so ignored build output and local editor files do not affect
|
||||
/// the metadata check. Container build contexts may omit `.git`, so the checker
|
||||
/// falls back to a conservative tree walk over the compliance scope.
|
||||
List<String> _trackedFiles() {
|
||||
final result = Process.runSync(
|
||||
'git',
|
||||
<String>['ls-files'],
|
||||
runInShell: Platform.isWindows,
|
||||
);
|
||||
if (result.exitCode != 0) {
|
||||
throw ProcessException(
|
||||
'git',
|
||||
<String>['ls-files'],
|
||||
'Unable to list tracked files.',
|
||||
result.exitCode,
|
||||
);
|
||||
if (result.exitCode == 0) {
|
||||
return (result.stdout as String)
|
||||
.split('\n')
|
||||
.where((path) => path.trim().isNotEmpty)
|
||||
.toList(growable: false)
|
||||
..sort();
|
||||
}
|
||||
return (result.stdout as String)
|
||||
.split('\n')
|
||||
.where((path) => path.trim().isNotEmpty)
|
||||
.toList(growable: false)
|
||||
..sort();
|
||||
|
||||
return _walkComplianceFiles();
|
||||
}
|
||||
|
||||
/// Walks source-controlled compliance paths when Git metadata is unavailable.
|
||||
///
|
||||
/// This path is used by Earthly and other copied build contexts. Generated
|
||||
/// output and tool caches are skipped explicitly so stale local artifacts do not
|
||||
/// create false failures.
|
||||
List<String> _walkComplianceFiles() {
|
||||
final files = <String>[];
|
||||
for (final entity in Directory.current.listSync(recursive: true)) {
|
||||
if (entity is! File) {
|
||||
continue;
|
||||
}
|
||||
final path = entity.path.replaceAll('\\', '/').replaceFirst('./', '');
|
||||
if (_isIgnoredGeneratedPath(path) || !_isComplianceScope(path)) {
|
||||
continue;
|
||||
}
|
||||
files.add(path);
|
||||
}
|
||||
return files..sort();
|
||||
}
|
||||
|
||||
/// Reports whether [path] belongs to generated output or local tooling caches.
|
||||
///
|
||||
/// These paths are intentionally ignored by the fallback scanner because they
|
||||
/// are not source files and are normally excluded by Git.
|
||||
bool _isIgnoredGeneratedPath(String path) {
|
||||
return path.startsWith('.dart_tool/') ||
|
||||
path.startsWith('.git/') ||
|
||||
path.startsWith('build/') ||
|
||||
path.startsWith('builds/') ||
|
||||
path.startsWith('coverage/') ||
|
||||
path.startsWith('doc/api/') ||
|
||||
path.contains('/.dart_tool/') ||
|
||||
path.contains('/build/') ||
|
||||
path.contains('/.plugin_symlinks/');
|
||||
}
|
||||
|
||||
/// Reports whether [path] should carry an inline SPDX header.
|
||||
|
|
@ -171,7 +204,10 @@ bool _requiresInlineSpdxHeader(String path) {
|
|||
if (path.endsWith('.g.dart') || path.endsWith('LICENSE.md')) {
|
||||
return false;
|
||||
}
|
||||
return path.endsWith('.dart') ||
|
||||
return path == '.earthlyignore' ||
|
||||
path == '.gitignore' ||
|
||||
path == 'Earthfile' ||
|
||||
path.endsWith('.dart') ||
|
||||
path.endsWith('.yaml') ||
|
||||
path.endsWith('.yml') ||
|
||||
path.endsWith('.sh') ||
|
||||
|
|
@ -187,8 +223,12 @@ bool _isComplianceScope(String path) {
|
|||
path.startsWith('apps/focus_flow_flutter/') ||
|
||||
path.startsWith('scripts/') ||
|
||||
path.startsWith('tool/') ||
|
||||
path == '.earthlyignore' ||
|
||||
path == '.gitignore' ||
|
||||
path == 'AGENTS.md' ||
|
||||
path == 'Earthfile' ||
|
||||
path == 'Focus Flow Contributors.md' ||
|
||||
path == 'README.md' ||
|
||||
path == 'pubspec.yaml' ||
|
||||
path == 'analysis_options.yaml' ||
|
||||
path == '.github/workflows/ci.yml';
|
||||
|
|
|
|||
Loading…
Reference in a new issue