Skip to content
Open
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
53 changes: 53 additions & 0 deletions .cursor/rules/ci-cd.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
description: CI/CD pipeline conventions and constraints
globs: ".github/**,scripts/**"
alwaysApply: false
---

# CI/CD Conventions

## Pipeline Overview (GitHub Actions)

Triggers: push to `master`, version tags, PRs, manual dispatch.

```
format -> lint -> pub-dry-run
|
+-------------+-------------+
| | |
test-flutter test-android test-ios
| | |
build-android build-ios |
| | |
+-------------+-------------+
|
release (tags only)
```

## Pinned Versions

- Flutter: `3.22.3`
- Gradle: `8.7`
- Xcode: `15.4`
- Container: `ghcr.io/cirruslabs/flutter:3.22.3`

Do **not** change these without testing the full pipeline.

## Scripts (`scripts/`)

| Script | Purpose |
|--------|---------|
| `check_format.sh` | `dart format --line-length=80 --set-exit-if-changed .` |
| `assert_export.sh` | Ensures all public files are exported |
| `check_coverage.dart` | Validates test coverage thresholds |
| `check_release_version.dart` | Validates tag matches pubspec version |
| `check_gradle_version.dart` | Validates Gradle wrapper version |
| `setup_gradle.sh` | Downloads and configures Gradle wrapper |
| `ios_unit_tests.sh` | Runs iOS unit tests via xcodebuild |

## Rules

- All CI scripts must be executable and use `#!/bin/sh -xe` for traceability
- Never add `--no-analyze` or skip lint steps
- Coverage thresholds are enforced - don't lower them without team approval
- Android tests use Kover for coverage, Dart uses lcov, iOS uses xccov
35 changes: 35 additions & 0 deletions .cursor/rules/dart-conventions.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
description: Dart coding conventions for the Usercentrics Flutter SDK
globs: "**/*.dart"
alwaysApply: false
---

# Dart Conventions

## Formatting

- Line length: **80 characters** (enforced by `dart format --line-length=80`)
- Use `flutter_lints` rules from `analysis_options.yaml`
- Missing enum constants in switch statements are treated as **errors**

## Code Style

- Public API classes use **static methods** delegating to platform interface (see `Usercentrics` class)
- Internal code lives under `lib/src/internal/` and is **not exported**
- All public models are exported via barrel file `lib/src/model/model.dart`
- Custom exceptions implement `Exception` (not `Error`)
- Use `@visibleForTesting` for test-only accessors
- Use `@Deprecated('Use [replacement] instead')` with migration guidance

## File Organization

- One class/enum per file, file name matches class in snake_case
- Barrel exports via `bridge.dart`, `model.dart`, `internal.dart`
- Bridges: `lib/src/internal/bridge/<method_name>_bridge.dart`
- Serializers: `lib/src/internal/serializer/<type>_serializer.dart`

## Public API Rules

- Every public file in `lib/src/` **must** be exported (enforced by `scripts/assert_export.sh`)
- Document all public APIs with `///` doc comments
- Include parameter descriptions for non-obvious args
35 changes: 35 additions & 0 deletions .cursor/rules/kotlin-conventions.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
description: Kotlin coding conventions for the Android plugin
globs: "**/*.kt"
alwaysApply: false
---

# Kotlin Conventions (Android Plugin)

## Project Setup

- Package: `com.usercentrics.sdk.flutter`
- Kotlin: 1.9.24, JVM target: 1.8
- Min SDK: 16, Compile SDK: 31
- Dependencies managed in `android/build.gradle`

## Architecture

- `UsercentricsPlugin.kt` - Main Flutter plugin entry point
- `api/` - Proxy abstractions (`UsercentricsProxy`, `UsercentricsBannerProxy`, `FlutterResult`, `FlutterMethodCall`)
- `bridge/` - One bridge per SDK method, extending `MethodBridge`
- `serializer/` - One serializer per data type, converting to/from `Map`

## Patterns

- Each bridge handles a single MethodChannel call and returns results via `FlutterResult`
- Serializers convert between native Usercentrics SDK types and Flutter-compatible Maps
- Use `io.mockk` for mocking in tests
- Tests live in `android/src/test/java/`

## Code Style

- Follow Kotlin coding conventions
- Prefer `val` over `var`
- Use data classes for pure data holders
- Keep bridge classes focused on single method handling
38 changes: 38 additions & 0 deletions .cursor/rules/platform-bridge-pattern.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
description: Platform bridge pattern used across Dart, Kotlin, and Swift
globs: "**/bridge/**"
alwaysApply: false
---

# Platform Bridge Pattern

## Overview

Every SDK method is implemented as a dedicated Bridge class on all 3 platforms. This keeps each method's serialization and channel logic isolated.

## Adding a New Bridge

When adding a new SDK method, you must create **3 bridge files** + update barrel exports:

### 1. Dart (`lib/src/internal/bridge/`)
- Create `<method_name>_bridge.dart`
- Add export to `bridge.dart` barrel file
- Wire it in `method_channel_usercentrics.dart`

### 2. Kotlin (`android/src/main/kotlin/.../bridge/`)
- Create `<MethodName>Bridge.kt` extending `MethodBridge`
- Register in `UsercentricsPlugin.kt`

### 3. Swift (`ios/Classes/Bridge/`)
- Create `<MethodName>Bridge.swift` conforming to `MethodBridge`
- Register in `SwiftUsercentricsPlugin.swift`

### 4. Tests
- Add tests for each platform's bridge implementation

## MethodChannel Contract

- Channel name must match across all 3 platforms
- Arguments are passed as `Map<String, dynamic>` (Dart) / `Map<String, Any?>` (Kotlin) / `[String: Any?]` (Swift)
- Results are returned as Maps serialized by the corresponding Serializer
- Errors use `FlutterError` with code, message, and details
39 changes: 39 additions & 0 deletions .cursor/rules/project-overview.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
description: Usercentrics Flutter SDK project overview and architecture
alwaysApply: true
---

# Usercentrics Flutter SDK

This is a **Flutter plugin** (`usercentrics_sdk`) providing a Consent Management Platform (CMP) for mobile apps. It wraps native Usercentrics SDKs for Android and iOS.

## Architecture

- **`lib/`** - Public Dart API and internal implementation
- `lib/src/model/` - Data models (exported publicly)
- `lib/src/platform/` - Platform interface (abstract contract)
- `lib/src/internal/bridge/` - Method channel bridge implementations
- `lib/src/internal/serializer/` - JSON serialization/deserialization
- `lib/src/internal/platform/` - MethodChannel platform implementation
- **`android/`** - Kotlin plugin (mirrors bridge/serializer pattern)
- **`ios/`** - Swift/ObjC plugin (mirrors bridge/serializer pattern)
- **`example/`** - Demo Flutter app
- **`test/`** - Dart unit tests
- **`scripts/`** - CI helper scripts

## Key Patterns

- Entry point: `Usercentrics` static class delegates to `UsercentricsPlatform`
- All platform communication goes through Flutter MethodChannel
- Each SDK method has a dedicated Bridge class on all 3 platforms (Dart, Kotlin, Swift)
- Each data type has a dedicated Serializer class on all 3 platforms
- Version must stay in sync across: `pubspec.yaml`, `android/build.gradle`, `ios/usercentrics_sdk.podspec`, `CHANGELOG.md`

## Tech Stack

| Layer | Language | Build System |
|-------|----------|-------------|
| Dart | Dart (SDK >=2.17.1 <4.0.0) | pub |
| Android | Kotlin 1.9.24 | Gradle 8.7 |
| iOS | Swift 5.3 | CocoaPods |
| CI | GitHub Actions | Flutter 3.22.3 |
53 changes: 53 additions & 0 deletions .cursor/rules/security-snyk.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
description: Security best practices and Snyk compliance rules
alwaysApply: true
---

# Security & Snyk Compliance

## Dependency Management

- **Never** pin to wildcard versions (`any`) - always use bounded ranges
- Keep dependencies minimal; this plugin only depends on `flutter` SDK at runtime
- Dev dependencies (`flutter_test`, `test`, `flutter_lints`) must also use version constraints
- Regularly check for outdated deps: `flutter pub outdated`
- Native deps (Gradle, CocoaPods) must also use explicit version pins

## Snyk Scans - What Gets Checked

Snyk analyzes these manifests in the pipeline:
- `pubspec.yaml` / `pubspec.lock` - Dart/Flutter dependencies
- `android/build.gradle` - Android/Gradle dependencies
- `ios/usercentrics_sdk.podspec` - iOS CocoaPods dependencies
- `example/pubspec.yaml` - Example app dependencies
- `example/android/build.gradle` - Example Android dependencies
- `example/ios/Podfile` / `Podfile.lock` - Example iOS dependencies

## Vulnerability Rules

- **Never** ignore Snyk findings without a documented reason
- Address critical/high severity vulns before merging to master
- If a transitive dependency has a vuln, upgrade the direct parent dependency
- For native SDK vulns (Usercentrics Android/iOS), coordinate with Usercentrics team

## Secrets & Credentials

- **Never** commit API keys, secrets, tokens, or credentials
- `settingsId` and `ruleSetId` in example code are public demo IDs only
- Files that must never be committed: `local.properties`, `key.properties`, `.env`, `*.keystore`, `*.jks`, `*.p12`, `*.mobileprovision`
- The `android/key.properties` pattern is already in `.gitignore` - keep it there

## Code Security Patterns

- Validate all data coming from MethodChannel before use
- Serializers must handle null/missing fields gracefully (no crashes on malformed data)
- Never log sensitive user consent data at production log levels
- Use `UsercentricsLoggerLevel.none` as default, only `debug`/`warning`/`error` when explicitly set
- ProGuard rules (`proguard-rules.pro`) must be maintained for release builds

## Secure CI/CD

- CI uses `ghcr.io/cirruslabs/flutter:3.22.3` container - keep this image version explicit
- Release job uses `id-token: write` for OIDC-based pub.dev publishing (no long-lived tokens)
- Never add `--no-verify` or skip security checks in CI scripts
- Gradle and Xcode versions are pinned in CI - don't use `latest`
34 changes: 34 additions & 0 deletions .cursor/rules/serialization.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
description: Serialization pattern for data transfer across platform channels
globs: "**/serializer/**,**/Serializer/**"
alwaysApply: false
---

# Serialization Pattern

## Overview

Each data type has a dedicated Serializer on all 3 platforms (Dart, Kotlin, Swift) that converts between native types and MethodChannel-compatible Maps/Dictionaries.

## Adding a New Serializer

### Dart (`lib/src/internal/serializer/`)
- File: `<type>_serializer.dart`
- Export in `serializer.dart` barrel
- Convert `Map<String, dynamic>` to/from model class

### Kotlin (`android/.../serializer/`)
- File: `<Type>Serializer.kt`
- Convert `Map<String, Any?>` to/from native SDK type

### Swift (`ios/Classes/Serializer/`)
- File: `<Type>Serializer.swift`
- Convert `[String: Any?]` to/from native SDK type

## Rules

- Handle null/missing keys defensively - never force-unwrap without a fallback
- Keep serialization logic pure (no side effects, no network calls)
- Enum serialization uses string values, not ordinals
- Nested objects delegate to their own serializer
- Test round-trip: serialize -> deserialize -> assert structural equality
34 changes: 34 additions & 0 deletions .cursor/rules/swift-conventions.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
description: Swift coding conventions for the iOS plugin
globs: "**/*.swift"
alwaysApply: false
---

# Swift Conventions (iOS Plugin)

## Project Setup

- Swift 5.3, iOS deployment target: 11.0
- Pod: `usercentrics_sdk`, depends on `UsercentricsUI`
- Plugin registration via ObjC (`UsercentricsPlugin.m/.h`)

## Architecture

- `SwiftUsercentricsPlugin.swift` - Main plugin logic
- `API/` - Proxy abstractions (`UsercentricsProxy`, `UsercentricsBannerProxy`, `FlutterAssetProvider`)
- `Bridge/` - One bridge per SDK method, conforming to `MethodBridge`
- `Serializer/` - One serializer per data type

## Patterns

- Each bridge class handles one MethodChannel call
- Serializers convert native Usercentrics types to Flutter-compatible dictionaries
- Tests live in `example/ios/RunnerTests/` using XCTest
- `UCFlutterFlag` controls feature flags

## Code Style

- Follow Swift API design guidelines
- Use `struct` for value types, `class` for reference types
- Keep bridge classes single-responsibility
- Prefer guard-let over nested if-let
34 changes: 34 additions & 0 deletions .cursor/rules/testing.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
description: Testing conventions across all platforms
globs: "**/test/**,**/test_*,**/*_test.dart,**/*Test.kt,**/*Tests.swift"
alwaysApply: false
---

# Testing Conventions

## Dart Tests (`test/`)

- Framework: `flutter_test` + `test`
- Run: `flutter test --reporter=expanded --coverage`
- Coverage check: `dart scripts/check_coverage.dart coverage/lcov.info`
- Structure mirrors `lib/src/` (e.g. `test/internal/bridge/` maps to `lib/src/internal/bridge/`)
- Name files `<subject>_test.dart`

## Android Tests (`android/src/test/java/`)

- Framework: JUnit 4 + MockK
- Run via Gradle: `./gradlew :usercentrics_sdk:testDebugUnitTest`
- Coverage: Kover (`koverHtmlReportDebug`)

## iOS Tests (`example/ios/RunnerTests/`)

- Framework: XCTest
- Run: `scripts/ios_unit_tests.sh`
- Coverage: `xcrun xccov view --report TestResults.xcresult`

## Guidelines

- Every bridge and serializer should have corresponding tests on all platforms
- Mock native SDK dependencies, never call real Usercentrics APIs in tests
- Test serialization round-trips (serialize -> deserialize -> assert equality)
- Test error/exception paths, not just happy paths
28 changes: 28 additions & 0 deletions .cursor/rules/versioning.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
description: Version management rules - version must stay in sync across files
globs: "pubspec.yaml,android/build.gradle,ios/usercentrics_sdk.podspec,CHANGELOG.md"
alwaysApply: false
---

# Version Management

## Synced Version Files

The SDK version **must** be identical across these 4 files:

1. `pubspec.yaml` - `version: X.Y.Z`
2. `android/build.gradle` - `def usercentrics_version = "X.Y.Z"`
3. `ios/usercentrics_sdk.podspec` - `s.version = 'X.Y.Z'`
4. `CHANGELOG.md` - new entry header `## X.Y.Z`

## Release Process

- CI validates version on tag push via `scripts/check_release_version.dart`
- Tags follow `[0-9]+.[0-9]+.[0-9]+*` pattern (semver, optional pre-release suffix)
- Publishing is automated via `flutter pub publish -f` using OIDC token

## Rules

- When bumping version, **always** update all 4 files in the same commit
- Native SDK dependency version (`UsercentricsUI`) matches the plugin version
- Never use `-SNAPSHOT` or other mutable version suffixes in release commits
Loading