diff --git a/.flywheel.yml b/.flywheel.yml new file mode 100644 index 0000000..5548a83 --- /dev/null +++ b/.flywheel.yml @@ -0,0 +1,28 @@ +# Flywheel release configuration. +# +# `release: production` cuts a release on every push to the managed branch. +# `auto_merge` lists the conventional-commit types that merge without review; +# every other type gates on a human. `!`-suffixed types are separate entries, +# so `fix` does not imply `fix!`. +# +# Every version-bumping type — feat, fix, perf, and any breaking variant — +# routes to human review, so a release is never cut unattended. Non-bumping +# types auto-merge once required checks pass. +# +# `chore` covers dependency bumps, so listing it means Dependabot updates can +# land unreviewed. Flywheel gates that separately: those PRs auto-merge only +# once the App private key is also registered in the Dependabot secret store. +flywheel: + streams: + - name: main-line + branches: + - name: main + release: production + auto_merge: [chore, docs, style, test, ci, build, refactor] + + release_files: + # release-please owned pubspec.yaml under its `dart` release type; + # flywheel keeps it current for the same reader — pub.dev. + - path: pubspec.yaml + pattern: '^version: .*' + replacement: 'version: ${version}' diff --git a/.github/workflows/auto-merge-release.yml b/.github/workflows/auto-merge-release.yml deleted file mode 100644 index 0d01cb4..0000000 --- a/.github/workflows/auto-merge-release.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Auto-merge release-please - -on: - pull_request_target: - types: [opened, synchronize, reopened, labeled] - -jobs: - auto-merge: - if: "contains(github.event.pull_request.labels.*.name, 'autorelease: pending')" - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - steps: - - run: gh pr merge --auto --squash "$PR_URL" - env: - GH_TOKEN: ${{ secrets.RELEASE_PLEASE_PAT }} - PR_URL: ${{ github.event.pull_request.html_url }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 83b960e..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: CI - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - lint: - uses: repentsinner/bug-free-happiness/.github/workflows/spec-lint.yml@v1 - - analyze: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: dart-lang/setup-dart@v1 - with: - sdk: '3.10.0' - - run: dart pub get - - run: sudo apt-get update && sudo apt-get install -y libudev-dev - - run: dart analyze --fatal-infos - - run: dart format --output=none --set-exit-if-changed . - - run: dart test - - test-macos: - runs-on: macos-latest - steps: - - uses: actions/checkout@v4 - - uses: dart-lang/setup-dart@v1 - with: - sdk: '3.10.0' - - run: dart pub get - - run: dart test diff --git a/.github/workflows/flywheel-pr.yml b/.github/workflows/flywheel-pr.yml new file mode 100644 index 0000000..49befa7 --- /dev/null +++ b/.github/workflows/flywheel-pr.yml @@ -0,0 +1,35 @@ +name: Flywheel — PR + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review, edited] + +concurrency: + group: flywheel-pr-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + conduct: + # Only run on 'edited' events when a human triggered the edit. Bot-driven + # edits (the Flywheel App rewriting titles/bodies, push-flow upserting + # promotion PR bodies) would otherwise flap conduct <-> push-flow writes on + # every promotion-source push. + if: | + github.event.pull_request.draft == false && + (github.event.action != 'edited' || github.event.sender.type == 'User') + # The degraded empty-key path (Dependabot PRs, where the App key is absent) + # posts the conventional-commit check with the built-in token, so the token + # needs checks:write. + permissions: + contents: read + checks: write + runs-on: ubuntu-latest + steps: + # SHA-pinned, not @v2: this action receives a GitHub App private key, + # and a floating tag lets its repository repoint that credential at new + # code without review. Bump the pin deliberately. + - uses: point-source/flywheel@59758d1940ebe054ce9fdd4b7a7dc79a0cf235e0 # v2.1.0 + with: + event: pull_request + app-id: ${{ vars.FLYWHEEL_GH_APP_ID }} + app-private-key: ${{ secrets.FLYWHEEL_GH_APP_PRIVATE_KEY }} diff --git a/.github/workflows/flywheel-push.yml b/.github/workflows/flywheel-push.yml new file mode 100644 index 0000000..e4f1c86 --- /dev/null +++ b/.github/workflows/flywheel-push.yml @@ -0,0 +1,27 @@ +name: Flywheel — Push + +on: + push: + branches: ["**"] + +concurrency: + group: flywheel-push-${{ github.ref_name }} + cancel-in-progress: false + +jobs: + release: + # Flywheel mints its own App installation token for every write, so the + # built-in token needs read only. Without this block the job inherits the + # repository default, which on older repos is write on everything. + permissions: + contents: read + runs-on: ubuntu-latest + steps: + # SHA-pinned, not @v2: this action receives a GitHub App private key, + # and a floating tag lets its repository repoint that credential at new + # code without review. Bump the pin deliberately. + - uses: point-source/flywheel@59758d1940ebe054ce9fdd4b7a7dc79a0cf235e0 # v2.1.0 + with: + event: push + app-id: ${{ vars.FLYWHEEL_GH_APP_ID }} + app-private-key: ${{ secrets.FLYWHEEL_GH_APP_PRIVATE_KEY }} diff --git a/.github/workflows/governance-lint.yml b/.github/workflows/governance-lint.yml new file mode 100644 index 0000000..4198aff --- /dev/null +++ b/.github/workflows/governance-lint.yml @@ -0,0 +1,16 @@ +name: Governance Lint + +on: + push: + branches: [main] + pull_request: + merge_group: # required for merge-queue compatibility + +jobs: + # Job id `governance` makes the check-run name `governance / lint`, the + # context registered as a required status check. Renaming it means + # re-pointing the ruleset entry. + governance: + uses: repentsinner/symphonize/.github/workflows/governance-lint.yml@notation--v0 + with: + readme-type: "library" diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml new file mode 100644 index 0000000..b402e1d --- /dev/null +++ b/.github/workflows/quality.yml @@ -0,0 +1,44 @@ +# quality — everything this repository verifies, behind one required check. +# +# GitHub names a check run after the JOB, so the job id `quality` is the +# context registered in the branch ruleset. The work is one job because it +# is one setup: splitting analyze, format and test would pay for the Dart +# SDK and libudev three times over for no extra signal. +name: quality + +on: + pull_request: + merge_group: # required for merge-queue compatibility + +jobs: + quality: + runs-on: ubuntu-latest + steps: + # Classify the triggering commit so Flywheel's own commits + # (chore(release):, back-merge) skip the suite. A step-level `if:` + # that evaluates false still reports `success`, so the required check + # stays satisfied rather than hanging — which is why this is a single + # job with guarded steps and not a job-level `if:`. + - id: classify + uses: point-source/flywheel/classify@59758d1940ebe054ce9fdd4b7a7dc79a0cf235e0 # v2.1.0 + - uses: actions/checkout@v4 + if: steps.classify.outputs.derived_release_commit != 'true' + - uses: dart-lang/setup-dart@v1 + if: steps.classify.outputs.derived_release_commit != 'true' + with: + sdk: '3.10.0' + - name: Install libudev + if: steps.classify.outputs.derived_release_commit != 'true' + run: sudo apt-get update && sudo apt-get install -y libudev-dev + - name: Resolve dependencies + if: steps.classify.outputs.derived_release_commit != 'true' + run: dart pub get + - name: Analyze + if: steps.classify.outputs.derived_release_commit != 'true' + run: dart analyze --fatal-infos + - name: Format + if: steps.classify.outputs.derived_release_commit != 'true' + run: dart format --output=none --set-exit-if-changed . + - name: Test + if: steps.classify.outputs.derived_release_commit != 'true' + run: dart test diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml deleted file mode 100644 index a596251..0000000 --- a/.github/workflows/release-please.yml +++ /dev/null @@ -1,21 +0,0 @@ -on: - push: - branches: - - main - workflow_dispatch: - -permissions: - contents: write - pull-requests: write - -name: release-please - -jobs: - release-please: - runs-on: ubuntu-latest - steps: - - uses: googleapis/release-please-action@v4 - with: - config-file: release-please-config.json - manifest-file: .release-please-manifest.json - token: ${{ secrets.RELEASE_PLEASE_PAT }} diff --git a/.release-please-manifest.json b/.release-please-manifest.json deleted file mode 100644 index 18e45d5..0000000 --- a/.release-please-manifest.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - ".": "0.1.5" -} diff --git a/SPEC.md b/SPEC.md index d0cf06a..baae2b0 100644 --- a/SPEC.md +++ b/SPEC.md @@ -1,13 +1,17 @@ # Spec: hidapi Dart bindings -## Problem +## Problem §spec:problem + +*Status: complete* No Dart package provides complete, cross-platform access to USB and Bluetooth HID devices. Developers resort to platform channels (Flutter only) or incomplete FFI wrappers that require prebuilt binaries or system-installed libraries. -## Scope +## Scope §spec:scope + +*Status: complete* This package wraps the upstream [hidapi](https://github.com/libusb/hidapi) C library. It shall expose every public function, struct, and enum from @@ -20,9 +24,11 @@ consuming packages. --- -## 1. API surface +## API surface §spec:api-surface -*Status: complete — PR #1, #4, 2026-02-22* +*Status: complete* + +Delivered in PR #1, #4, 2026-02-22. The package shall expose an idiomatic Dart API that maps 1:1 to every public function, struct, and enum in upstream `hidapi/hidapi.h`. @@ -43,9 +49,11 @@ Deviations from the C API: --- -## 2. Platform support +## Platform support §spec:platform-support + +*Status: complete* -*Status: complete — PR #1, 2026-02-22* +Delivered in PR #1, 2026-02-22. The package shall support every platform supported by upstream hidapi. The build hook shall select the correct backend source file, frameworks, @@ -53,9 +61,11 @@ and link libraries as defined by the upstream build system. --- -## 3. Native source acquisition +## Native source acquisition §spec:native-source-acquisition -*Status: complete — PR #2, 2026-02-22* +*Status: complete* + +Delivered in PR #2, 2026-02-22. Git submodules are not viable: `dart pub get` does not recursively init submodules, so consumers that depend on this package via pub.dev or a git @@ -74,9 +84,11 @@ Instead, the package acquires native source via a Dart 3.10 build hook --- -## 4. Testing +## Testing §spec:testing + +*Status: complete* -*Status: complete — PR #1, #7, 2026-02-22* +Delivered in PR #1, #7, 2026-02-22. HID devices require physical hardware. CI runners have none, so the test strategy splits into two tiers: @@ -89,9 +101,11 @@ strategy splits into two tiers: --- -## 5. CI/CD +## CI/CD §spec:ci-cd + +*Status: complete* -*Status: complete — PR #1, #5, #7, #8, #9, #11, 2026-02-22* +Delivered in PR #1, #5, #7, #8, #9, #11, 2026-02-22. This is a single-maintainer project. Releases should not require manual steps beyond merging a PR. pub.dev requires semver; conventional commits @@ -109,7 +123,7 @@ let release-please derive the correct version bump automatically. --- -## 6. macOS exclusive device access +## macOS exclusive device access §spec:macos-exclusive-device-access *Status: in progress* @@ -138,7 +152,7 @@ imports. --- -## 7. Publishing +## Publishing §spec:publishing *Status: not started* diff --git a/release-please-config.json b/release-please-config.json deleted file mode 100644 index dac44f7..0000000 --- a/release-please-config.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "packages": { - ".": { - "release-type": "dart", - "bump-minor-pre-major": true, - "bump-patch-for-minor-pre-major": true - } - }, - "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json" -}