diff --git a/.fvmrc b/.fvmrc new file mode 100644 index 0000000..16a96ef --- /dev/null +++ b/.fvmrc @@ -0,0 +1,3 @@ +{ + "flutter": "3.47.2" +} diff --git a/.github/workflows/dependency-freshness.yml b/.github/workflows/dependency-freshness.yml new file mode 100644 index 0000000..e3f46ac --- /dev/null +++ b/.github/workflows/dependency-freshness.yml @@ -0,0 +1,57 @@ +name: dependency freshness + +# Every dependency on its ecosystem's newest STABLE release, enforced on push +# as well as at commit time. The local pre-commit hook only sees the manifests +# in a commit and can be skipped entirely by a fresh clone that never ran +# `pre-commit install`; this runs `--all` over the whole repo. +# +# `--strict` is CI-only. Offline the gate degrades to its cached answer and +# passes, because `--no-verify` is banned locally and a hook that hard-failed +# on a flaky connection would leave no way to commit. A runner is always +# online, so here an undeterminable answer is a real failure. +# +# Expect this to go red for reasons this repo's commit did not cause: an +# upstream release overnight is enough. That is the chosen behaviour -- the +# alternative is a gate that only notices staleness when someone remembers to +# look. + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +jobs: + dependency-freshness: + name: dependencies on newest stable + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # The gate lives in the shared utils repo so every repo resolves the + # same "latest stable" the same way. Checking it out beats vendoring a + # copy, which is exactly how a gate and its survey start disagreeing. + - name: Check out the shared gate + uses: actions/checkout@v4 + with: + repository: kuhyx/utils + path: .utils + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: Install the gate's two runtime dependencies + run: python -m pip install --upgrade pyyaml packaging + + - name: Cache registry lookups + uses: actions/cache@v4 + with: + path: ~/.cache/dep-freshness + key: dep-freshness-${{ hashFiles('**/pubspec.yaml', '**/pyproject.toml', + '**/requirements*.txt', '**/package.json', '**/Cargo.toml', '**/go.mod') }} + restore-keys: dep-freshness- + + - name: Every dependency is on newest stable + run: bash .utils/scripts/check_dependency_freshness.sh --all --strict diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index adb01a4..25ba5be 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -171,3 +171,24 @@ repos: entry: scripts/check_md_naming.sh language: system files: \.(md|markdown)$ + + # Every dependency on its ecosystem's newest STABLE release, exact-pinned + # so the question is mechanically answerable at all. + # + # `files:` is what keeps this cheap: a commit touching only source makes + # ZERO network requests, and manifest commits mostly hit a 6h disk cache. + # + # Runs WITHOUT --strict on purpose. Offline it degrades to the cached + # answer and passes with a loud banner; `--no-verify` is banned here, so + # a hook that hard-failed on a flaky connection would leave no way to + # commit at all. CI, always online, is the strict authority. + - id: dependency-freshness + name: dependencies on newest stable + entry: scripts/check_dependency_freshness.sh + language: system + pass_filenames: true + files: >- + (pubspec\.(yaml|lock)|pyproject\.toml|requirements.*\.txt|uv\.lock + |package\.json|package-lock\.json|pnpm-lock\.yaml + |Cargo\.(toml|lock)|go\.(mod|sum) + |\.fvmrc|\.nvmrc|\.python-version)$ diff --git a/phone_app/android/app/build.gradle.kts b/phone_app/android/app/build.gradle.kts index 6b0517e..b3917e8 100644 --- a/phone_app/android/app/build.gradle.kts +++ b/phone_app/android/app/build.gradle.kts @@ -21,7 +21,12 @@ android { // Pinned explicitly rather than delegated to the Flutter Gradle plugin: // full-screen-intent and foreground-service semantics both changed at // API 34/35, and the alarm path cannot be reasoned about without a floor. - compileSdk = 36 + // Pinned above `flutter.compileSdkVersion` (36 on Flutter 3.47.2): + // flutter_secure_storage 11.0.0, which arrives with crdt_sync_flutter + // v0.3.1, publishes AAR metadata demanding API 37 or later. That in + // turn needs AGP > 9.1.0, which needs Gradle >= 9.5.0 -- see + // settings.gradle.kts and gradle-wrapper.properties. + compileSdk = 37 ndkVersion = flutter.ndkVersion compileOptions { diff --git a/phone_app/android/gradle/wrapper/gradle-wrapper.properties b/phone_app/android/gradle/wrapper/gradle-wrapper.properties index 2d428bf..ffcd530 100644 --- a/phone_app/android/gradle/wrapper/gradle-wrapper.properties +++ b/phone_app/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-all.zip diff --git a/phone_app/android/settings.gradle.kts b/phone_app/android/settings.gradle.kts index c21f0c5..c00b1b8 100644 --- a/phone_app/android/settings.gradle.kts +++ b/phone_app/android/settings.gradle.kts @@ -19,7 +19,7 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "9.0.1" apply false + id("com.android.application") version "9.3.2" apply false id("org.jetbrains.kotlin.android") version "2.3.20" apply false } diff --git a/phone_app/pubspec.lock b/phone_app/pubspec.lock index d1236f0..08b7384 100644 --- a/phone_app/pubspec.lock +++ b/phone_app/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: "direct main" description: name: android_intent_plus - sha256: "2329378af63f49b985cb2e110ac784d08374f1e2b1984be77ba9325b1c8cce11" + sha256: "4d4bbd619d698cbee3c400ba89246ecb0f525146b0ae05389f62ede47abd5bec" url: "https://pub.dev" source: hosted - version: "5.3.1" + version: "6.1.0" archive: dependency: transitive description: @@ -93,11 +93,11 @@ packages: dependency: "direct main" description: path: crdt_sync_dart - ref: "crdt_sync_dart-v0.10.0" - resolved-ref: "5f64a8959b1db342da1f73157b1148aa0dfa38a8" + ref: "crdt_sync_dart-v0.11.1" + resolved-ref: "978d32af3af66f0d6500c4c6c0c6df5370ebe9a2" url: "https://github.com/kuhyx/utils" source: git - version: "0.10.0" + version: "0.11.1" crypto: dependency: transitive description: @@ -130,6 +130,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + ffi_leak_tracker: + dependency: transitive + description: + name: ffi_leak_tracker + sha256: "4093d4ef9ca06ffe2786e73bfb25e22aa92112b9bb4ec941f11e3e6b61489a97" + url: "https://pub.dev" + source: hosted + version: "0.1.2" file: dependency: transitive description: @@ -163,50 +171,50 @@ packages: dependency: "direct main" description: name: flutter_secure_storage - sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" + sha256: "15e8c8fe269fdf7d469b23008ab3df521c8b826ed345820532364c31bdebace6" url: "https://pub.dev" source: hosted - version: "9.2.4" + version: "11.0.0" + flutter_secure_storage_darwin: + dependency: transitive + description: + name: flutter_secure_storage_darwin + sha256: ac6d76a752de0cd738334eb4b21743fc4943f449f5b6e308f18838b048c02ac0 + url: "https://pub.dev" + source: hosted + version: "0.4.0" flutter_secure_storage_linux: dependency: transitive description: name: flutter_secure_storage_linux - sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688 + sha256: "76fa9c841b3b1619fc5b5bc36efc7d158fa2356f223b6caeb1d0c80a54168546" url: "https://pub.dev" source: hosted - version: "1.2.3" - flutter_secure_storage_macos: - dependency: transitive - description: - name: flutter_secure_storage_macos - sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" - url: "https://pub.dev" - source: hosted - version: "3.1.3" + version: "3.0.2" flutter_secure_storage_platform_interface: dependency: transitive description: name: flutter_secure_storage_platform_interface - sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + sha256: "788060052712555182aba55ecb5f8b6e5cb9cfe8f776c83249a61fe3ce877db4" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "2.0.3" flutter_secure_storage_web: dependency: transitive description: name: flutter_secure_storage_web - sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + sha256: "073a62b3aeb866ab4ce795f960413948e51e5a42a9b0c8333b6daf5bb3208a1c" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "2.1.1" flutter_secure_storage_windows: dependency: transitive description: name: flutter_secure_storage_windows - sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + sha256: "471951813a97006d899db4948acc654a4f28c440083ea08178935ce20b173ec1" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "4.2.2" flutter_test: dependency: "direct dev" description: flutter @@ -221,11 +229,11 @@ packages: dependency: "direct main" description: path: github_device_auth - ref: "github_device_auth-v0.3.0" - resolved-ref: "254e3b00c7f41a54c588ec32564017f96f328252" + ref: "github_device_auth-v0.3.1" + resolved-ref: "11c752fe6508117d4d2db1c0744090221d5f6cb8" url: "https://github.com/kuhyx/utils" source: git - version: "0.3.0" + version: "0.3.1" google_identity_services_web: dependency: transitive description: @@ -322,14 +330,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" json_annotation: dependency: transitive description: @@ -611,11 +611,11 @@ packages: dependency: "direct main" description: path: sync_settings_ui - ref: "sync_settings_ui-v0.1.0" - resolved-ref: "23c693da4e6fd6004df09829a7d75365dc086c2c" + ref: "sync_settings_ui-v0.4.1" + resolved-ref: "51700aa89eaf1789ae98491aaa854c54d35b4170" url: "https://github.com/kuhyx/utils" source: git - version: "0.1.0" + version: "0.4.1" term_glyph: dependency: transitive description: @@ -748,10 +748,10 @@ packages: dependency: transitive description: name: win32 - sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + sha256: a0b93865d5644f11cf6a8c3f6db909f1ec168958b5805f6cc684adea957cd63d url: "https://pub.dev" source: hosted - version: "5.15.0" + version: "6.4.0" xdg_directories: dependency: transitive description: diff --git a/phone_app/pubspec.yaml b/phone_app/pubspec.yaml index b18cbf8..0aa6119 100644 --- a/phone_app/pubspec.yaml +++ b/phone_app/pubspec.yaml @@ -8,29 +8,29 @@ environment: sdk: ^3.12.2 dependencies: - android_intent_plus: ^5.0.0 + android_intent_plus: 6.1.0 crdt_sync: git: url: https://github.com/kuhyx/utils - ref: crdt_sync_dart-v0.10.0 + ref: crdt_sync_dart-v0.11.1 path: crdt_sync_dart - cupertino_icons: ^1.0.8 + cupertino_icons: 1.0.9 flutter: sdk: flutter - flutter_secure_storage: ^9.0.0 + flutter_secure_storage: 11.0.0 # One-tap sign-in for the sync account. App-side only: crdt_sync stays pure # Dart so it keeps working on Linux desktop and headless. github_device_auth: git: url: https://github.com/kuhyx/utils - ref: github_device_auth-v0.3.0 + ref: github_device_auth-v0.3.1 path: github_device_auth - google_sign_in: ^7.2.0 - http: ^1.2.0 - path: ^1.9.0 - path_provider: ^2.1.0 - shared_preferences: ^2.3.0 + google_sign_in: 7.2.0 + http: 1.6.0 + path: 1.9.1 + path_provider: 2.1.6 + shared_preferences: 2.5.5 # Shared "Sync settings" screen (Firebase + optional local backup), reused # across every kuhy app that syncs. GitHub mirror stays app-local (see @@ -38,18 +38,18 @@ dependencies: sync_settings_ui: git: url: https://github.com/kuhyx/utils - ref: sync_settings_ui-v0.1.0 + ref: sync_settings_ui-v0.4.1 path: sync_settings_ui - url_launcher: ^6.3.0 - uuid: ^4.6.0 + url_launcher: 6.3.2 + uuid: 4.6.0 dev_dependencies: # Generates the Android launcher icons from assets/icon/ (see # python_pkg/app_icons in testsAndMisc for the shared style). - flutter_launcher_icons: ^0.14.4 + flutter_launcher_icons: 0.14.4 flutter_test: sdk: flutter - very_good_analysis: ^10.2.0 + very_good_analysis: ^10.3.0 flutter: uses-material-design: true diff --git a/pyproject.toml b/pyproject.toml index 6614f25..0bade7d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,12 +8,12 @@ version = "1.0.0" description = "Phone-driven wake alarm with fan ramp and smart-plug control" requires-python = ">=3.10" dependencies = [ - "gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.4.1#subdirectory=gatelock", + "gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.7.2#subdirectory=gatelock", # Firebase transport for the alarm read. _sync.py imports it lazily and # falls back to its stdlib GitHub path if the import fails, so a broken # install degrades the read instead of killing the per-minute trigger. - "crdt-sync @ git+https://github.com/kuhyx/utils@crdt-sync-v0.5.1#subdirectory=crdt-sync", - "python-kasa>=0.7", + "crdt-sync @ git+https://github.com/kuhyx/utils@crdt-sync-v0.9.1#subdirectory=crdt-sync", + "python-kasa==0.10.2", ] # Package discovery must be explicit: phone_app/ is a Flutter app, not a Python diff --git a/requirements.txt b/requirements.txt index e8d75bd..077a095 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,18 +4,18 @@ # optional at runtime — their modules degrade gracefully if missing (the # alarm read falls back to GitHub, smart-plug control disables itself) — but # both are required here for mypy/pylint/tests. -bandit>=1.7.0 -codespell>=2.2.0 -coverage>=7.4.0 -crdt-sync @ git+https://github.com/kuhyx/utils@crdt-sync-v0.5.1#subdirectory=crdt-sync -gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.4.1#subdirectory=gatelock -mypy>=1.8.0 -pre-commit>=3.6.0 -pylint>=3.0.0 -pytest>=8.0.0 -pytest-cov>=4.1.0 -pytest-randomly>=3.15.0 -pytest-sugar>=1.0.0 -pytest-xdist>=3.5.0 -python-kasa>=0.7 -ruff>=0.8.0 +bandit==1.9.4 +codespell==2.4.3 +coverage==7.15.4 +crdt-sync @ git+https://github.com/kuhyx/utils@crdt-sync-v0.9.1#subdirectory=crdt-sync +gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.7.2#subdirectory=gatelock +mypy==2.3.1 +pre-commit==4.6.2 +pylint==4.0.7 +pytest==9.1.1 +pytest-cov==7.1.0 +pytest-randomly==4.1.0 +pytest-sugar==1.1.1 +pytest-xdist==3.8.0 +python-kasa==0.10.2 +ruff==0.16.5 diff --git a/scripts/check_dependency_freshness.sh b/scripts/check_dependency_freshness.sh new file mode 100755 index 0000000..5cb0d78 --- /dev/null +++ b/scripts/check_dependency_freshness.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# ============================================================================ +# Fail if any dependency in the commit is behind its ecosystem's newest stable. +# +# Thin delegate to the shared gate in ~/utils, which owns the registry +# adapters, the pre-release rules and the allowlist semantics. Copying that +# logic here is what lets one repo's idea of "current" drift from every other +# repo's -- so this script only locates the shared checker and forwards its +# arguments. +# +# Usage: +# scripts/check_dependency_freshness.sh [ ...] # pre-commit +# scripts/check_dependency_freshness.sh --all --strict # CI +# +# Exit: 0 current | 1 behind | 2 allowlist wrong | 3 undeterminable (offline) +# ============================================================================ + +set -euo pipefail + +readonly SHARED_GATE="${UTILS_ROOT:-$HOME/utils}/scripts/check_dependency_freshness.sh" + +main() { + if [[ ! -x "$SHARED_GATE" ]]; then + echo "Error: shared dependency-freshness gate not found at $SHARED_GATE" >&2 + echo " Clone github.com/kuhyx/utils to ~/utils, or set" >&2 + echo " UTILS_ROOT to where it lives." >&2 + exit 1 + fi + + exec bash "$SHARED_GATE" "$@" +} + +main "$@"