diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..38ff03f --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,21 @@ +version: 2 +updates: + - package-ecosystem: "pub" + directory: "/" + schedule: + interval: "weekly" + + - package-ecosystem: "pub" + directory: "/packages/formbricks_flutter" + schedule: + interval: "weekly" + + - package-ecosystem: "pub" + directory: "/apps/playground" + schedule: + interval: "weekly" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/analyze.yml b/.github/workflows/analyze.yml new file mode 100644 index 0000000..1c50720 --- /dev/null +++ b/.github/workflows/analyze.yml @@ -0,0 +1,50 @@ +name: Analyze + +on: + workflow_dispatch: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened] + merge_group: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + analyze: + name: Format and Analyze + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Harden the runner + uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + with: + egress-policy: audit + + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: Set up Flutter + uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0 + with: + channel: stable + flutter-version-file: .fvmrc + cache: true + + - name: Install dependencies + run: make deps-lockfile + + - name: Check formatting + run: make format-check + + - name: Analyze + run: make analyze-ci diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..243a3c7 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,96 @@ +name: Build + +on: + workflow_dispatch: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened] + merge_group: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + android: + name: Build Android + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - name: Harden the runner + uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + with: + egress-policy: audit + + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: Set up Flutter + uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0 + with: + channel: stable + flutter-version-file: .fvmrc + cache: true + + - name: Restore Gradle cache + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('pubspec.lock', 'apps/playground/android/**/*.gradle*', 'apps/playground/android/gradle/wrapper/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Install dependencies + run: make deps-lockfile + + - name: Build Android debug APK + run: make build-android + + ios: + name: Build iOS + runs-on: macos-latest + timeout-minutes: 30 + + steps: + - name: Harden the runner + uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + with: + egress-policy: audit + + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: Set up Flutter + uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0 + with: + channel: stable + flutter-version-file: .fvmrc + cache: true + + - name: Restore CocoaPods cache + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: | + apps/playground/ios/Pods + ~/Library/Caches/CocoaPods + key: ${{ runner.os }}-pods-${{ hashFiles('apps/playground/ios/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-pods- + + - name: Install dependencies + run: make deps-lockfile + + - name: Build iOS debug app + run: make build-ios-no-codesign diff --git a/.github/workflows/pana.yml b/.github/workflows/pana.yml new file mode 100644 index 0000000..b006dc6 --- /dev/null +++ b/.github/workflows/pana.yml @@ -0,0 +1,50 @@ +name: Pana + +on: + workflow_dispatch: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened] + merge_group: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + pana: + name: Pub Points + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - name: Harden the runner + uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + with: + egress-policy: audit + + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: Set up Flutter + uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0 + with: + channel: stable + flutter-version-file: .fvmrc + cache: true + + - name: Install dependencies + run: make deps-lockfile + + - name: Install pana + run: make pana-install + + - name: Run pana + run: make pana diff --git a/.github/workflows/pub-publish-dryrun.yml b/.github/workflows/pub-publish-dryrun.yml new file mode 100644 index 0000000..0af9352 --- /dev/null +++ b/.github/workflows/pub-publish-dryrun.yml @@ -0,0 +1,47 @@ +name: Pub Publish Dry Run + +on: + workflow_dispatch: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened] + merge_group: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + dry-run: + name: Pub Publish Dry Run + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Harden the runner + uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + with: + egress-policy: audit + + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: Set up Flutter + uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0 + with: + channel: stable + flutter-version-file: .fvmrc + cache: true + + - name: Install dependencies + run: make deps-lockfile + + - name: Run publish dry run + run: make pub-publish-dry-run diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..322da95 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,73 @@ +name: Release + +on: + workflow_dispatch: + push: + tags: + - "*.*.*" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: read + id-token: write + +jobs: + release: + name: Release Flutter SDK + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - name: Harden the runner + uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + with: + egress-policy: audit + + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: Set up Dart for pub.dev OIDC + uses: dart-lang/setup-dart@65eb853c7ba17dde3be364c3d2858773e7144260 # v1.7.2 + + - name: Set up Flutter + uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0 + with: + channel: stable + flutter-version-file: .fvmrc + cache: true + + - name: Install dependencies + run: make deps-lockfile + + - name: Check formatting + run: make format-check + + - name: Analyze + run: make analyze-ci + + - name: Run SDK package tests with coverage + run: make test-sdk-coverage + + - name: Run playground tests + run: make test-playground + + - name: Run publish dry run + run: make pub-publish-dry-run + + - name: Publish to pub.dev + if: github.event_name == 'push' && github.ref_type == 'tag' + env: + PUB_PUBLISH_ENABLED: ${{ vars.PUB_PUBLISH_ENABLED }} + run: | + # Fail-safe: publishing is irreversible, so it's gated on PUB_PUBLISH_ENABLED. + # First resolve the pub.dev name (taken; latest 0.0.7), bump version, drop publish_to: none. + if [ "$PUB_PUBLISH_ENABLED" != "true" ]; then + echo "::notice::Publish skipped — set repository variable PUB_PUBLISH_ENABLED=true to enable (see prerequisites in release.yml)." + exit 0 + fi + make pub-publish-force diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml new file mode 100644 index 0000000..b86e77b --- /dev/null +++ b/.github/workflows/sonarqube.yml @@ -0,0 +1,58 @@ +name: SonarQube + +on: + workflow_dispatch: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened] + merge_group: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + pull-requests: read # required for SonarQube analysis and quality gate PR comments/decorations + +jobs: + sonarqube: + name: SonarQube + # SONAR_TOKEN isn't available to Dependabot or fork PRs — skip them. + if: github.actor != 'dependabot[bot]' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - name: Harden the runner + uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + with: + egress-policy: audit + + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Set up Flutter + uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0 + with: + channel: stable + flutter-version-file: .fvmrc + cache: true + + - name: Install dependencies + run: make deps-lockfile + + - name: Run tests with coverage + run: make test-sdk-coverage + + - name: SonarQube Scan + uses: SonarSource/sonarqube-scan-action@7006c4492b2e0ee0f816d36501671557c97f5995 # v8.1.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: https://sonarcloud.io diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..9959a90 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,80 @@ +name: Tests + +on: + workflow_dispatch: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened] + merge_group: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +# The declared floor (Flutter 3.27) isn't run here — dev tooling (melos, flutter_lints) +# needs a newer SDK. It's validated by the dry-run + pana jobs instead. +jobs: + test: + name: Unit Tests (${{ matrix.id }}) + runs-on: ubuntu-latest + timeout-minutes: 20 + continue-on-error: ${{ matrix.experimental }} + + strategy: + fail-fast: false + matrix: + include: + # Pinned dev SDK (.fvmrc) — the gate. + - id: pinned + channel: stable + flutter-version-file: .fvmrc + deps-target: deps-lockfile + experimental: false + # Latest beta — non-blocking forward canary. + - id: beta + channel: beta + flutter-version-file: "" + deps-target: deps + experimental: true + + steps: + - name: Harden the runner + uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + with: + egress-policy: audit + + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: Set up Flutter + uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0 + with: + channel: ${{ matrix.channel }} + flutter-version-file: ${{ matrix.flutter-version-file }} + cache: true + + - name: Install dependencies + env: + DEPS_TARGET: ${{ matrix.deps-target }} + run: make "$DEPS_TARGET" + + - name: Run SDK package tests + run: make test-sdk-machine + + - name: Run playground app tests + run: make test-playground + + - name: Upload test reports + if: failure() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: flutter-test-reports-${{ matrix.id }} + path: test-results/ + if-no-files-found: ignore diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ee0dd58 --- /dev/null +++ b/Makefile @@ -0,0 +1,153 @@ +.DEFAULT_GOAL := help + +SDK_DIR := packages/formbricks_flutter +PLAYGROUND_DIR := apps/playground +TEST_RESULTS_DIR := test-results +PLATFORMS := ios android +PLATFORM_GOAL := $(firstword $(filter $(PLATFORMS),$(MAKECMDGOALS))) +PLATFORM ?= +RUN_PLATFORM := $(or $(PLATFORM),$(PLATFORM_GOAL),ios) +BUILD_PLATFORM := $(or $(PLATFORM),$(PLATFORM_GOAL),android) + +ifeq ($(shell command -v fvm >/dev/null 2>&1 && echo yes),yes) +FLUTTER := fvm flutter +DART := fvm dart +else +FLUTTER := flutter +DART := dart +endif + +MELOS := $(DART) run melos + +.PHONY: help deps deps-lockfile get doctor analyze analyze-ci lint format format-check check +.PHONY: test test-sdk-machine test-playground coverage test-coverage test-sdk-coverage +.PHONY: build build-all build-android build-ios build-ios-no-codesign run run-ios run-android devices emulators +.PHONY: pana-install pana pub-publish-dry-run pub-publish-force +.PHONY: ios android + +help: + @printf "Usage:\n" + @printf " make test\n" + @printf " make build [android|ios]\n" + @printf " make run [android|ios]\n\n" + @printf "Common targets:\n" + @printf " deps Fetch workspace dependencies\n" + @printf " deps-lockfile Fetch dependencies without changing pubspec.lock\n" + @printf " doctor Show Flutter doctor output\n" + @printf " analyze Analyze all packages\n" + @printf " analyze-ci Analyze with Flutter CI flags\n" + @printf " format Format Dart code\n" + @printf " format-check Check Dart formatting\n" + @printf " test Run tests in packages that have test/\n" + @printf " test-sdk-machine Run SDK tests and write machine JSON output\n" + @printf " test-playground Run playground tests\n" + @printf " coverage Run tests with coverage\n" + @printf " test-sdk-coverage Run SDK tests with coverage\n" + @printf " check Run format-check, analyze, and test\n\n" + @printf "Playground targets:\n" + @printf " build Build Android debug APK\n" + @printf " build android Build Android debug APK\n" + @printf " build ios Build iOS simulator debug app\n" + @printf " build-ios-no-codesign Build iOS debug app without code signing\n" + @printf " build-all Build Android and iOS debug artifacts\n" + @printf " run Run on iOS simulator\n" + @printf " run android Run on Android emulator\n" + @printf " run ios Run on iOS simulator\n" + @printf " devices List connected devices\n" + @printf " emulators List configured emulators\n\n" + @printf "Publish targets:\n" + @printf " pana-install Install pana globally\n" + @printf " pana Run pana for the SDK package\n" + @printf " pub-publish-dry-run Run pub publish dry-run for the SDK package\n" + @printf " pub-publish-force Publish the SDK package to pub.dev\n" + +deps get: + $(FLUTTER) pub get + +deps-lockfile: + $(FLUTTER) pub get --enforce-lockfile + +doctor: + $(FLUTTER) doctor -v + +analyze lint: + $(DART) analyze --fatal-infos . + +analyze-ci: + $(FLUTTER) analyze --fatal-infos --fatal-warnings + +format: + $(DART) format . + +format-check: + $(DART) format --output=none --set-exit-if-changed . + +check: format-check analyze test + +test: + $(MELOS) exec --concurrency 1 --dir-exists=test -- "$(FLUTTER) test" + +test-sdk-machine: + mkdir -p $(TEST_RESULTS_DIR) + cd $(SDK_DIR) && $(FLUTTER) test --machine > ../../$(TEST_RESULTS_DIR)/formbricks_flutter.json + +test-playground: + cd $(PLAYGROUND_DIR) && $(FLUTTER) test + +coverage test-coverage: + $(MELOS) exec --concurrency 1 --dir-exists=test -- "$(FLUTTER) test --coverage" + +test-sdk-coverage: + cd $(SDK_DIR) && $(FLUTTER) test --coverage + +build: + $(MAKE) --no-print-directory build-$(BUILD_PLATFORM) + +build-all: build-android build-ios + +build-android: + cd $(PLAYGROUND_DIR) && $(FLUTTER) build apk --debug $(BUILD_ARGS) + +build-ios: + cd $(PLAYGROUND_DIR) && $(FLUTTER) build ios --simulator --debug $(BUILD_ARGS) + +build-ios-no-codesign: + cd $(PLAYGROUND_DIR) && $(FLUTTER) build ios --no-codesign --debug + +run: + ./tool/run.sh $(RUN_PLATFORM) $(RUN_ARGS) + +run-ios: + ./tool/run.sh ios $(RUN_ARGS) + +run-android: + ./tool/run.sh android $(RUN_ARGS) + +devices: + $(FLUTTER) devices + +emulators: + $(FLUTTER) emulators + +pana-install: + $(DART) pub global activate pana + +pana: + $(DART) pub global run pana --no-warning --exit-code-threshold 30 $(SDK_DIR) + +pub-publish-dry-run: + cd $(SDK_DIR) && $(FLUTTER) pub publish --dry-run + +pub-publish-force: + cd $(SDK_DIR) && $(FLUTTER) pub publish --force + +ifeq ($(firstword $(MAKECMDGOALS)),run) +ios android: + @: +else ifeq ($(firstword $(MAKECMDGOALS)),build) +ios android: + @: +else +ios: run-ios +android: run-android +endif diff --git a/README.md b/README.md index 2f4162d..cb101f1 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,81 @@ surveys inside a WebView backed by `{appUrl}/js/surveys.umd.cjs`. > `welcome()`. The public API, survey rendering, CI, and pub.dev publishing land > in follow-up work — see [Roadmap](#roadmap). +## Quick Start + +Run commands from the repo root unless a step says otherwise. + +### 1. Install platform prerequisites + +FVM installs the Flutter SDK for this project, but you still need the native +tooling for the platform you want to run: + +- **iOS simulator:** macOS with Xcode installed. Also run + `xcode-select --install` once so command-line tools are available. +- **Android emulator:** Android Studio with the Android SDK, Platform Tools, + and at least one Android Virtual Device (AVD) created in Device Manager. + +If you only plan to run one platform, you only need that platform's tooling. + +### 2. Install FVM + +FVM reads `.fvmrc` and downloads the exact Flutter version used by this repo. +On macOS, Homebrew is the easiest install path: + +```bash +brew tap leoafarias/fvm +brew install fvm +``` + +If you already have Dart on your machine, this also works: + +```bash +dart pub global activate fvm +``` + +### 3. Install the pinned Flutter SDK + +```bash +fvm install +make doctor +``` + +`fvm install` downloads the Flutter version from `.fvmrc` (currently `3.44.0`). +`make doctor` checks the local iOS/Android tooling with the pinned Flutter SDK. +Fix the red `✗` items for the platform you want to run, then run the doctor +command again. + +### 4. Fetch dependencies + +```bash +make deps +``` + +This resolves the whole Dart pub workspace from the root `pubspec.yaml` and +uses the shared `pubspec.lock`. + +### 5. Run the playground app + +```bash +make run # iOS simulator, default +make run android # Android emulator +``` + +The Makefile delegates to `tool/run.sh`, boots a simulator/emulator when it can, +then runs `apps/playground`. Once the app is running, press `r` for hot reload, +`R` for hot restart, and `q` to quit. + +You should see a **"Welcome to Formbricks"** header and six SDK-test buttons +(track / setUserId / setAttributes ×2 / setLanguage / logout). They are inert +stubs — each shows a "not wired to the SDK yet" snackbar — until the SDK API +lands in follow-up work. + ## Repository layout ``` flutter/ ├── pubspec.yaml # pub workspace root + Melos script config (never published) +├── Makefile # daily dev + CI command entry point ├── analysis_options.yaml # shared analyzer + lint rules for every package ├── sonar-project.properties # SonarCloud config (finalised in a follow-up) ├── LICENSE # MIT @@ -58,24 +128,47 @@ Uses **Dart pub workspaces** (Dart ≥ 3.6) + **[Melos](https://melos.invertase. ### Common commands -All run from the repo root. The Flutter version is pinned with **fvm** (see -[Toolchain](#toolchain)), so prefix Flutter/Dart calls with `fvm` to use the -exact pinned SDK: +All run from the repo root. Use the Makefile for normal development; it uses +`fvm flutter` / `fvm dart` when FVM is installed and falls back to `flutter` / +`dart` from `PATH` otherwise. + +```bash +make help # list available targets +make deps # resolve the whole workspace (one lockfile) +make format # format Dart code +make format-check # CI-style formatting check +make analyze # analyze all packages +make test # run tests in packages that have test/ +make coverage # run tests with coverage +make check # format-check + analyze + test +``` + +Important daily commands: ```bash -fvm flutter pub get # resolve the whole workspace (one lockfile) -fvm dart run melos run analyze # dart analyze --fatal-infos across all packages -fvm dart run melos run format # dart format . -fvm dart run melos run format-check # CI: fail if unformatted -fvm dart run melos run test --no-select # flutter test in every package -fvm dart run melos run test-coverage --no-select +make deps +make run +make run android +make format +make analyze +make test +make check ``` -> - `--no-select` skips Melos's interactive package picker — required in CI and -> any non-TTY shell. -> - `fvm dart run melos` runs the workspace's pinned Melos under the pinned SDK. -> If you prefer the global `melos` binary (`dart pub global activate melos`), -> run it as `fvm exec melos run …` so it still uses the pinned Flutter. +CI/parity targets are also available when you need to reproduce workflow steps: + +```bash +make deps-lockfile +make analyze-ci +make test-sdk-machine +make test-sdk-coverage +make test-playground +make build-android +make build-ios-no-codesign +make pana-install +make pana +make pub-publish-dry-run +``` ## Conventions @@ -100,30 +193,21 @@ These are locked in for all follow-up work (full rationale in the RN repo's `http`'s `MockClient` — no real network. - **Targets.** iOS + Android only for v1. A `kIsWeb` guard throws on Flutter Web. -## Toolchain - -The exact Flutter version is pinned in **`.fvmrc`** (currently `3.44.0`, which -ships Dart 3.12) and managed with [fvm](https://fvm.app). Pinning means every -contributor and CI run uses a byte-identical SDK — no "works on my machine" -version drift. +## Toolchain details -First-time setup: +The exact Flutter version is pinned in **`.fvmrc`** and managed with +[fvm](https://fvm.app). Pinning means every contributor and CI run uses the same +SDK version. -```bash -dart pub global activate fvm # install the fvm tool (one-time, global) -fvm install # download the version from .fvmrc into fvm's cache -fvm flutter pub get # resolve the workspace -``` - -- fvm itself needs a Dart/Flutter on `PATH` only to bootstrap; it then downloads - and isolates the pinned Flutter under `~/fvm/versions/` — you don't clone - Flutter by hand. `.fvm/flutter_sdk` (a symlink to the active version) and the - version cache are git-ignored; only `.fvmrc` is committed. -- Editors: point your editor's Flutter/Dart SDK path at `.fvm/flutter_sdk` so - analysis uses the pinned SDK. -- Bumping the version: `fvm use --force`, commit the changed `.fvmrc`. -- fvm docs: . Flutter floor enforced by pubspecs: ≥ 3.22 / Dart - ≥ 3.12. +- FVM downloads Flutter into its own cache under `~/fvm/versions/`; you do not + clone Flutter by hand. +- `.fvm/flutter_sdk` is a local symlink to the active SDK and is git-ignored. + Only `.fvmrc` is committed. +- Editors should use `.fvm/flutter_sdk` as the Flutter/Dart SDK path so analysis + and code completion use the pinned SDK. +- Bump the repo's Flutter version with `fvm use --force`, then commit + the changed `.fvmrc`. +- Floors: published SDK = Flutter ≥ 3.27 / Dart ≥ 3.6; dev tooling pins a newer SDK via `.fvmrc`. ## Running the demo app @@ -132,41 +216,50 @@ project. Note that `flutter run` cannot boot a simulator on its own: with no device running it falls back to the macOS desktop target (which this app does not support), so a simulator/emulator must be started first. -**Easiest — one command** (boots the device if needed, then runs): +### One-command run ```bash -./tool/run.sh # iOS simulator (default) -./tool/run.sh android # Android emulator -# or via Melos (package.json-style scripts, see Monorepo tooling): -melos run ios -melos run android +make run # iOS simulator (default) +make run android # Android emulator ``` -**Manual CLI:** a simulator/emulator must be booted *first* — `flutter run` -never boots one itself. Start a device, then target it by name: +Or call the underlying script directly after `make deps`: + +```bash +./tool/run.sh +./tool/run.sh android +``` + +### Device checks + +Use these commands when the run script cannot find a device: + +```bash +make emulators # configured simulators/emulators Flutter can launch +make devices # currently running simulators/emulators/devices +``` + +For Android, if no emulator appears in `make emulators`, create an AVD in +Android Studio's Device Manager first. + +### Manual CLI run + +A simulator/emulator must be booted *first* — `flutter run` never boots one +itself. Start a device, then target it by id or name substring: ```bash fvm flutter emulators --launch apple_ios_simulator # iOS sim # or: fvm flutter emulators --launch Pixel_9a # Android emulator cd apps/playground -fvm flutter run -d iphone # iOS; -d emulator for Android. - # -d matches a device NAME/id substring, NOT the - # platform — `-d ios` will NOT match, and with no - # device booted this falls back to the - # (unsupported) macOS desktop target. +fvm flutter run -d iphone # iOS, if the simulator name contains "iphone" +fvm flutter run -d emulator # Android, if the emulator id contains "emulator" ``` -> First Android build is slow — Gradle downloads the NDK + CMake (~3 GB, -> one-time) before compiling. Subsequent builds reuse them. +`-d` matches a device id or name substring, not a platform name. For example, +`-d ios` does not mean "run on iOS". -Once running, the `flutter run` session is interactive: press **`r`** for hot -reload, **`R`** for hot restart, **`q`** to quit. `./tool/run.sh` keeps that -session in your terminal, so hot reload works there too. - -You should see a **"Welcome to Formbricks"** header and six SDK-test buttons -(track / setUserId / setAttributes ×2 / setLanguage / logout). They are inert -stubs — each shows a "not wired to the SDK yet" snackbar — until the SDK API -lands in follow-up work. +First Android build is slow because Gradle downloads the NDK and CMake +(approximately 3 GB, one-time). Subsequent builds reuse them. ## Roadmap diff --git a/apps/playground/android/.gitignore b/apps/playground/android/.gitignore index be3943c..6214e2f 100644 --- a/apps/playground/android/.gitignore +++ b/apps/playground/android/.gitignore @@ -1,8 +1,6 @@ -gradle-wrapper.jar /.gradle +/.kotlin/sessions /captures/ -/gradlew -/gradlew.bat /local.properties GeneratedPluginRegistrant.java .cxx/ diff --git a/apps/playground/android/gradle/wrapper/gradle-wrapper.jar b/apps/playground/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..13372ae Binary files /dev/null and b/apps/playground/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/apps/playground/android/gradlew b/apps/playground/android/gradlew new file mode 100755 index 0000000..9d82f78 --- /dev/null +++ b/apps/playground/android/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/apps/playground/android/gradlew.bat b/apps/playground/android/gradlew.bat new file mode 100644 index 0000000..aec9973 --- /dev/null +++ b/apps/playground/android/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/apps/playground/lib/main.dart b/apps/playground/lib/main.dart index c18f602..7e03aba 100644 --- a/apps/playground/lib/main.dart +++ b/apps/playground/lib/main.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:formbricks_flutter/formbricks_flutter.dart'; const kWelcomeMessage = 'Welcome to Formbricks'; diff --git a/packages/formbricks_flutter/pubspec.yaml b/packages/formbricks_flutter/pubspec.yaml index 453e994..1734080 100644 --- a/packages/formbricks_flutter/pubspec.yaml +++ b/packages/formbricks_flutter/pubspec.yaml @@ -17,8 +17,9 @@ publish_to: none resolution: workspace environment: - sdk: ^3.12.0 - flutter: ">=3.22.0" + # Floor capped at Flutter 3.27 / Dart 3.6 — the lowest pub workspaces allow. + sdk: ^3.6.0 + flutter: ">=3.27.0" dependencies: flutter: diff --git a/pubspec.lock b/pubspec.lock index 20f56c8..ccb256a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -442,4 +442,4 @@ packages: version: "2.2.4" sdks: dart: ">=3.12.0 <4.0.0" - flutter: ">=3.22.0" + flutter: ">=3.27.0" diff --git a/sonar-project.properties b/sonar-project.properties index 3fedff2..036b390 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,6 +1,5 @@ -# SonarQube / SonarCloud configuration for the Formbricks Flutter monorepo. -# Full SonarQube wiring (coverage upload, CI gate) is finalised in a follow-up; -# this file gives the analysis a correct source/test map to build on. +# SonarCloud config. Dart analyzed natively; LCOV coverage from the SonarQube +# workflow. No hard quality gate yet. sonar.projectKey=formbricks_flutter sonar.organization=formbricks sonar.sourceEncoding=UTF-8 @@ -10,11 +9,9 @@ sonar.sources=packages/formbricks_flutter/lib,apps/playground/lib sonar.tests=packages/formbricks_flutter/test,apps/playground/test sonar.test.inclusions=**/*_test.dart -# The demo app is excluded from quality metrics — it is QA scaffolding, not -# shipped SDK code (mirrors how RN excludes apps/playground). +# Exclude the demo app from metrics — it's QA scaffolding, not shipped code. sonar.exclusions=**/build/**,**/.dart_tool/**,**/*.g.dart,**/*.freezed.dart,**/android/**,**/ios/**,apps/playground/** sonar.coverage.exclusions=**/build/**,**/.dart_tool/**,apps/playground/**,**/*_test.dart -# Coverage report (LCOV) produced by `flutter test --coverage`. -# Generated under each package's coverage/ dir; wired up in a follow-up. +# LCOV coverage from `flutter test --coverage` (SonarQube workflow). sonar.dart.lcov.reportPaths=packages/formbricks_flutter/coverage/lcov.info