diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index e796a3a..1b8751d 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -2,122 +2,63 @@ name: Dart on: push: - branches: [ "main" ] + branches: ["main"] pull_request: - branches: [ "main" ] + branches: ["main"] env: - FLUTTER_VERSION: "3.35.0" + FLUTTER_VERSION: "3.47.5" jobs: - discover: + quality: runs-on: ubuntu-latest - outputs: - packages: ${{ steps.packages.outputs.packages }} steps: - uses: actions/checkout@v4 - with: - fetch-depth: 0 - uses: subosito/flutter-action@v2 with: flutter-version: ${{ env.FLUTTER_VERSION }} cache: true - - name: Install melos - run: dart pub global activate melos - - name: Bootstrap packages - run: melos bootstrap - - name: Get packages - id: packages - run: | - # Determine the base ref for comparison - if [ "${{ github.event_name }}" == "pull_request" ]; then - BASE_REF="origin/${{ github.base_ref }}" - else - BASE_REF="HEAD~1" - fi - - - # Get packages that have changes using melos diff - packages=$(melos list --diff="$BASE_REF" --json | jq -c "[.[] | select(.name != \"flutter_forge_workspace\") | .name]" 2>/dev/null || echo '[]') - if [ "$packages" = "" ] || [ "$packages" = "null" ]; then - packages="[]" - fi - echo "packages=$packages" >> $GITHUB_OUTPUT - echo "Changed packages: $packages" - - build: - needs: discover - runs-on: ubuntu-latest - if: needs.discover.outputs.packages != '[]' - strategy: - matrix: - package: ${{ fromJson(needs.discover.outputs.packages) }} - steps: - - uses: actions/checkout@v4 - - - uses: subosito/flutter-action@v2 + - uses: actions/setup-python@v5 with: - flutter-version: ${{ env.FLUTTER_VERSION }} - cache: true - - - name: Install dependencies - run: | - dart pub global activate melos - melos bootstrap --scope="${{ matrix.package }}" - - - name: Format package - # Brick templates are mustache, not parseable Dart, and `dart format` - # has no exclude of its own - so filter the paths before it sees them. - run: | - melos exec --scope="${{ matrix.package }}" -- \ - "find . -name '*.dart' -not -path '*/__brick__/*' -not -path '*/.dart_tool/*' \ - -exec dart format --set-exit-if-changed {} +" - - - name: Analyze package - run: melos exec --scope="${{ matrix.package }}" -- dart analyze --fatal-infos + python-version: "3.x" + - name: Resolve workspace dependencies + run: flutter pub get --enforce-lockfile + - name: Verify coverage gate behavior + run: python3 -m unittest discover -s tools/tests + - name: Check formatting and analyze workspace + run: dart run melos run lint + - name: Require 100% coverage in every package + run: python3 tools/coverage.py + - name: Save test logs and coverage reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: workspace-coverage + path: | + coverage/ + packages/**/coverage/ + plugins/**/coverage/ + tools/**/coverage/ - test: - needs: [ discover, build ] - runs-on: ubuntu-latest - if: needs.discover.outputs.packages != '[]' - strategy: - matrix: - package: ${{ fromJson(needs.discover.outputs.packages) }} + native-permissions: + runs-on: macos-latest + timeout-minutes: 30 steps: - uses: actions/checkout@v4 - - - uses: subosito/flutter-action@v2 + - uses: actions/setup-java@v4 with: - flutter-version: ${{ env.FLUTTER_VERSION }} - cache: true - - - name: Install dependencies - run: | - dart pub global activate melos - melos bootstrap --scope="${{ matrix.package }}" - - - name: Run tests - id: test-step - run: | - # Try Flutter test first (will only run if package has test dir and is Flutter package) - if melos exec --scope="${{ matrix.package }}" --dir-exists=test --flutter -- flutter test --coverage 2>/dev/null; then - echo "has_tests=true" >> $GITHUB_OUTPUT - # Try Dart test (will only run if package has test dir and is not Flutter package) - elif melos exec --scope="${{ matrix.package }}" --dir-exists=test --no-flutter -- dart test --coverage 2>/dev/null; then - echo "has_tests=true" >> $GITHUB_OUTPUT - else - echo "No tests to run for ${{ matrix.package }}" - echo "has_tests=false" >> $GITHUB_OUTPUT - fi - - # Get package path for coverage upload - package_path=$(melos list --scope="${{ matrix.package }}" --json | jq -r ".[0].path") - echo "package_path=$package_path" >> $GITHUB_OUTPUT - - - name: Upload coverage reports to Codecov - if: steps.test-step.outputs.has_tests == 'true' - uses: codecov/codecov-action@v5 + distribution: temurin + java-version: "21" + - uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Require 100% native permission-plugin coverage + run: python3 plugins/contact_permission/native_tests/run.py + - name: Save native coverage reports + if: always() + uses: actions/upload-artifact@v4 with: - token: ${{ secrets.CODECOV_TOKEN }} - flags: ${{ matrix.package }} - files: ${{ steps.test-step.outputs.package_path }}/coverage/lcov.info + name: native-permission-coverage + path: | + plugins/contact_permission/native_tests/coverage/ + plugins/contact_permission/native_tests/android/build/reports/ diff --git a/.github/workflows/nonstop-template.yml b/.github/workflows/nonstop-template.yml index f28caf1..b1a0ff9 100644 --- a/.github/workflows/nonstop-template.yml +++ b/.github/workflows/nonstop-template.yml @@ -1,10 +1,10 @@ name: NonStop generated template on: pull_request: - paths: ['packages/nonstop_cli/**', '.github/workflows/nonstop-template.yml'] + paths: ['packages/nonstop_cli/**', 'packages/cli_core/**', 'pubspec.yaml', 'pubspec.lock', '.github/workflows/nonstop-template.yml'] push: branches: [main] - paths: ['packages/nonstop_cli/**', '.github/workflows/nonstop-template.yml'] + paths: ['packages/nonstop_cli/**', 'packages/cli_core/**', 'pubspec.yaml', 'pubspec.lock', '.github/workflows/nonstop-template.yml'] permissions: contents: read jobs: @@ -17,9 +17,9 @@ jobs: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: - flutter-version: '3.44.0' + flutter-version: '3.47.5' cache: true - - run: dart pub get + - run: flutter pub get --enforce-lockfile - run: dart analyze --fatal-infos lib test tool - run: dart test --exclude-tags version-verify generated: @@ -36,7 +36,7 @@ jobs: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: - flutter-version: '3.44.0' + flutter-version: '3.47.5' cache: true - - run: dart pub get + - run: flutter pub get --enforce-lockfile - run: dart run tool/verify_template.dart ${{ matrix.profile }} diff --git a/.gitignore b/.gitignore index fb343ae..4a3f0ab 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,10 @@ full_coverage_test.dart **/example/linux/ **/example/macos/ **/example/windows/ + +# Keep a reproducible resolution for the native Dart workspace. +!/pubspec.lock + +# Python coverage-gate test caches. +__pycache__/ +*.pyc diff --git a/README.md b/README.md index 256e930..fedd890 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,12 @@ optional-module configurations. --- +## Development and tests + +Use Flutter 3.47.5 and run `flutter pub get --enforce-lockfile` from the repository root. Melos configuration is in `pubspec.yaml`. + +Run `python3 tools/coverage.py` (or `dart run melos run test`) to test every workspace package and require 100% executable Dart line coverage. See [testing and dependency maintenance](docs/testing.md) for focused runs, generated-template validation, and coverage scope. + ## 🔗 Connect with NonStop
diff --git a/analysis_options.yaml b/analysis_options.yaml index d705176..9e85879 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -2,9 +2,9 @@ include: package:flutter_lints/flutter.yaml analyzer: exclude: - - lib/generated/** - - test/** - - packages/nonstop_cli/bricks/** + - packages/nonstop_cli/bricks/**/__brick__/** + - "**/coverage/**" + - "**/build/**" linter: rules: constant_identifier_names: false diff --git a/docs/coverage-report.md b/docs/coverage-report.md new file mode 100644 index 0000000..1177408 --- /dev/null +++ b/docs/coverage-report.md @@ -0,0 +1,76 @@ +# Coverage and dependency verification + +Verified on 2026-09-21 after updating from `origin/main` at `2d1b5ac`, using Flutter 3.47.5 and Dart 3.13.4. + +The final fresh `python3 tools/coverage.py` run exited successfully: **24/24 workspace packages passed, with 11,116/11,116 executable Dart lines covered (100%)**. This includes libraries, examples, command entrypoints, Mason hooks, repository tools, and the generated-template verification script. + +## Workspace results + +| Package | Covered / executable lines | Coverage | +| --- | ---: | ---: | +| `flutter_forge` | 92 / 92 | 100% | +| `cli_core` | 85 / 85 | 100% | +| `connectivity_wrapper_example` | 129 / 129 | 100% | +| `connectivity_wrapper` | 160 / 160 | 100% | +| `dzod_example` | 2,808 / 2,808 | 100% | +| `dzod` | 4,507 / 4,507 | 100% | +| `example_html_rich_text` | 46 / 46 | 100% | +| `html_rich_text` | 73 / 73 | 100% | +| `morse_tap_example` | 367 / 367 | 100% | +| `morse_tap` | 415 / 415 | 100% | +| `flutter_app_for_mono_repo_hooks` | 17 / 17 | 100% | +| `flutter_package_for_mono_repo_hooks` | 16 / 16 | 100% | +| `flutter_plugin_for_mono_repo_hooks` | 17 / 17 | 100% | +| `flutter_project_with_mono_repo_hooks` | 103 / 103 | 100% | +| `nonstop_cli` | 759 / 759 | 100% | +| `ns_firebase_utils` | 246 / 246 | 100% | +| `ns_intl_phone_input_example` | 53 / 53 | 100% | +| `ns_intl_phone_input` | 282 / 282 | 100% | +| `ns_utils` | 583 / 583 | 100% | +| `timer_button_example` | 38 / 38 | 100% | +| `timer_button` | 79 / 79 | 100% | +| `contact_permission_example` | 30 / 30 | 100% | +| `contact_permission` | 16 / 16 | 100% | +| `readme_sync` | 195 / 195 | 100% | +| **Total** | **11,116 / 11,116** | **100%** | + +## Generated templates and native sources + +Mustache templates were rendered before validation. All 512 module selections passed the template contract checks. Six representative generated workspaces passed analysis, tests, 100% handwritten Dart line coverage, and web builds: + +| Profile | Covered / executable lines | Web build | +| --- | ---: | --- | +| Full | 2,396 / 2,396 | Passed | +| Default | 2,393 / 2,393 | Passed | +| Minimal | 766 / 766 | Passed | +| Authentication | 950 / 950 | Passed | +| Firestore | 781 / 781 | Passed | +| Network | 1,120 / 1,120 | Passed | + +The full profile's existing temporary application received standard Flutter web platform scaffolding before its build; its application code, dependencies, and tests were preserved. + +The contact-permission plugin's production native sources also passed their unit coverage gates: **Android Kotlin 58/58**, **Apple Swift/Objective-C 44/44**. A disposable Flutter Android host built a debug APK with the upgraded plugin in both supported Kotlin modes. Native unit tests use SDK boundary doubles; actual OS permission dialogs and a real iOS embedding build were not tested. + +## Dependency upgrades and remaining constraints + +Dart/Flutter dependencies, generated-template dependencies, Node development tooling, and the Android plugin/tooling were upgraded. The workspace now uses native Dart pub workspaces and Melos 8, with a shared checked-in Dart lockfile. The pnpm lockfile is updated, and its audit reported zero vulnerabilities. + +`flutter pub outdated --json` reported **no resolvable upgrades**. Eleven direct/transitive packages have newer releases blocked by the current dependency graph. The key constraints are Flutter's `test_api` pin (holding analyzer/test and related tooling), `fake_cloud_firestore`'s Equatable 2 requirement, and AndroidX Core 1.19's compile SDK 37 requirement (this plugin uses SDK 36). These dependencies remain at their latest compatible versions. + +## Reproduce and inspect + +```sh +flutter pub get --enforce-lockfile +dart run melos run lint +python3 -m unittest discover -s tools/tests +python3 tools/coverage.py +python3 plugins/contact_permission/native_tests/run.py +# From packages/nonstop_cli, repeat for each profile above: +dart run tool/verify_template.dart full +``` + +The coverage gate's 12 regression tests passed. Formatting and strict analysis passed. The CLI's separate generated-version build check remains intentionally tagged `version-verify`; the unit coverage result includes all executable CLI/tool lines and the separate template contract/build checks above passed. + +Fresh workspace details are in `coverage/summary.json`; each package retains its LCOV report and test log under `coverage/`. Native details are in `plugins/contact_permission/native_tests/coverage/summary.json`. Reports and build outputs are ignored by Git and regenerated by CI. The recorded percentages are executable **line** coverage, not branch coverage. + +See [testing and dependency maintenance](testing.md) for gate behavior, SDK requirements, compatibility changes, and focused commands. diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 0000000..7b9480f --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,58 @@ +# Tests and dependency maintenance + +The [verified coverage report](coverage-report.md) records the package totals, generated-template builds, native coverage, and dependency compatibility limits. + +Use Flutter 3.47.5 (Dart 3.13.4), Python 3.10 or newer, and Node.js 24 or newer. The repository uses a native Dart pub workspace and Melos 8; configuration lives in the root `pubspec.yaml`. + +```sh +flutter pub get --enforce-lockfile +dart run melos run lint +python3 -m unittest discover -s tools/tests +python3 tools/coverage.py +# Equivalent full coverage command: +dart run melos run test +``` + +The coverage command discovers workspace packages rather than selecting only folders that already have tests. It includes libraries, executable entrypoints, example applications, Mason hook packages, README synchronization, repository version/bundle tools, and the generated-template verification script. Every package must have real unit or widget tests and a nonempty report. The gate requires every executable Dart line to be hit; it compares integer counts rather than a rounded percentage. Compile-time constants and export-only libraries do not contain executable lines. + +Production libraries are loaded alongside an existing test suite so a never-imported library cannot silently disappear. Missing source records fail unless the file contains only recognized declarations without executable code. Temporary coverage entrypoints are restored or removed after the run. Each Flutter run clears its generated test kernel cache to avoid stale source metadata. Flutter coverage is collected without VM package filters, then restricted to the package’s owned files; this avoids a Dart 3.13.4 source-report crash and includes entrypoints outside `lib/`. The generated runner lives under `coverage/` and uses the installed Flutter tool without modifying the SDK. Raw logs and LCOV reports are written to each package's `coverage/` directory; the complete result is `coverage/summary.json`. These generated reports are ignored by Git. + +Run a focused package while developing: + +```sh +python3 tools/coverage.py --package ns_utils +python3 tools/coverage.py --package packages/timer_button/example +``` + +`--report-only` inspects existing reports and writes `summary-existing.json`; it does not establish a fresh passing result. + +Mustache brick source is not runnable Dart until rendered. The separate generated-template workflow renders representative module combinations, analyzes them, and requires 100% handwritten Dart coverage inside the generated workspace. The template contract suite additionally validates every module selection. Hook bundles exclude local tests/build artifacts and workspace-only `resolution` metadata. + +## Dependencies + +The shared root `pubspec.lock` and `pnpm-lock.yaml` are checked in. CI enforces the Dart lockfile so dependency resolution cannot drift between package jobs. When intentionally upgrading dependencies, run: + +```sh +flutter pub upgrade --major-versions +flutter pub outdated +pnpm install --frozen-lockfile +pnpm audit +``` + +After an upgrade, rerun the full coverage gate, strict analysis, and generated-template checks. Some newest releases cannot coexist with Flutter SDK pins or upstream package constraints: the current graph uses Equatable 2.1 (required by fake_cloud_firestore) and analyzer 13.3/test 1.31.1 (compatible with Flutter's test_api pin). Do not force incompatible versions with dependency overrides. + +The development-only `tmp` override selects a patched release because all-contributors-cli's transitive external-editor dependency requests an obsolete version. Its temporary-file creation and cleanup API is smoke-tested during the upgrade. + +The Melos upgrade moves workspace configuration from `melos.yaml` into the root `pubspec.yaml`; run bootstrap and scripts from the repository root. DZod string refinements now correctly return `Schema` instead of throwing an invalid cast. Place string-specific checks before those refinements, for example `z.string().min(3).startsWith('abc')`. + +## Native permission-plugin tests + +Run the Android Kotlin and Apple Swift/Objective-C unit suites on macOS with Xcode command-line tools and Java 21: + +```sh +python3 plugins/contact_permission/native_tests/run.py +``` + +Use `--platform android` for the JVM suite alone or `--platform apple` for the Apple suite. The checked-in Gradle wrapper resolves the JVM test tooling. Both suites compile the production plugin files and require 100% measured native line coverage; reports are under `plugins/contact_permission/native_tests/coverage/` and the Android test build directory. + +Platform test doubles make permission states, callbacks, registration, and lifecycle behavior deterministic. These unit tests do not open OS permission dialogs or establish device-level integration coverage. The separate `native-permissions` CI job runs both suites on macOS. diff --git a/melos.yaml b/melos.yaml deleted file mode 100644 index 0e84cb6..0000000 --- a/melos.yaml +++ /dev/null @@ -1,61 +0,0 @@ -name: flutter_forge_workspace - -packages: - - plugins/* - - plugins/*/* - - packages/* - - packages/*/* - - packages/nonstop_cli/bricks/flutter_app_for_mono_repo/hooks - - packages/nonstop_cli/bricks/flutter_package_for_mono_repo/hooks - - packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks - - packages/nonstop_cli/bricks/flutter_plugin_for_mono_repo/hooks - - packages/dzod/example - -command: - version: - workspaceChangelog: false - fetchTags: true - updateGitTagRefs: false - linkToCommits: false - hooks: - preCommit: melos run update_nonstop_cli_version && melos run update_nonstop_cli_bundles - -scripts: - lint: - description: "Run format and analyze all packages" - # Brick sources are mustache templates, not valid Dart, so the formatter - # has to skip them - `dart format` has no exclude of its own. - run: | - find . -name '*.dart' -not -path '*/__brick__/*' -not -path '*/.dart_tool/*' \ - -exec dart format --set-exit-if-changed {} + && - dart analyze --fatal-infos . - exec: - concurrency: 5 - failFast: true - fix: - description: "Apply Dart automated fixes to all packages" - run: dart fix --apply - exec: - concurrency: 5 - packageFilters: - dirExists: lib - test: - description: "Run tests in all packages" - run: | - if [ -f "pubspec.yaml" ] && grep -q "flutter:" pubspec.yaml; then - flutter test - else - dart test - fi - exec: - concurrency: 1 - failFast: false - packageFilters: - dirExists: ["test"] - update_nonstop_cli_version: - run: dart run tools/update_nonstop_cli_version.dart - update_nonstop_cli_bundles: - run: dart run tools/update_nonstop_cli_bundles.dart - sync:readme: - description: "Sync README sections across all packages" - run: dart run tools/readme_sync/readme_sync.dart diff --git a/package.json b/package.json index 1da712a..1b9505e 100644 --- a/package.json +++ b/package.json @@ -15,14 +15,17 @@ "commitlint": "commitlint --from=HEAD~1" }, "devDependencies": { - "@commitlint/cli": "^19.6.0", - "@commitlint/config-conventional": "^19.6.0", + "@commitlint/cli": "^21.2.3", + "@commitlint/config-conventional": "^21.2.3", "all-contributors-cli": "^6.26.1", "husky": "^9.1.7", - "prettier": "^3.4.2", - "typescript": "^5.7.3" + "prettier": "^3.9.8", + "typescript": "^7.0.2" }, "dependencies": { - "@changesets/cli": "^2.27.12" + "@changesets/cli": "^3.0.3" + }, + "overrides": { + "tmp": "^0.2.7" } } diff --git a/packages/cli_core/lib/src/file_utils.dart b/packages/cli_core/lib/src/file_utils.dart index f4b8f07..4dd440f 100644 --- a/packages/cli_core/lib/src/file_utils.dart +++ b/packages/cli_core/lib/src/file_utils.dart @@ -1,5 +1,6 @@ import 'dart:io'; import 'package:path/path.dart' as p; +import 'package:yaml/yaml.dart'; /// Utility methods for common file system operations. /// @@ -81,35 +82,30 @@ class FileUtils { await file.writeAsString(content); } - /// Check if the current directory is part of a mono-repo. + /// Check the current directory and its ancestors for a mono-repo root. /// - /// First tries to detect using the melos command. - /// Falls back to checking for melos.yaml in current and parent directories. - /// - /// [path] - Optional path to check. Defaults to current directory. + /// Recognizes legacy `melos.yaml` files and native Dart pub workspaces. + /// [path] defaults to the current directory. static Future isMonoRepo([String? path]) async { - final workingDir = path ?? Directory.current.path; + var directory = p.normalize(p.absolute(path ?? Directory.current.path)); + while (true) { + if (await File(p.join(directory, 'melos.yaml')).exists()) return true; - // Try with melos command first - try { - final melosResult = await Process.run( - 'melos', - ['list', '--json'], - workingDirectory: workingDir, - runInShell: true, - ); - if (melosResult.exitCode == 0 && - melosResult.stdout.toString().isNotEmpty) { - return true; + final pubspec = File(p.join(directory, 'pubspec.yaml')); + if (await pubspec.exists()) { + try { + final configuration = loadYaml(await pubspec.readAsString()); + if (configuration is Map && configuration['workspace'] is List) { + return true; + } + } on YamlException { + // An unrelated or unfinished pubspec does not identify a workspace. + } } - } catch (_) { - // Ignore melos command failures and fall back to file check - } - // Check for melos.yaml in current and parent directories - final currentMelosFile = File(p.join(workingDir, 'melos.yaml')); - final parentMelosFile = File(p.join(p.dirname(workingDir), 'melos.yaml')); - - return currentMelosFile.existsSync() || parentMelosFile.existsSync(); + final parent = p.dirname(directory); + if (parent == directory) return false; + directory = parent; + } } } diff --git a/packages/cli_core/lib/src/logger_extension.dart b/packages/cli_core/lib/src/logger_extension.dart index 06592b6..87ea6df 100644 --- a/packages/cli_core/lib/src/logger_extension.dart +++ b/packages/cli_core/lib/src/logger_extension.dart @@ -11,7 +11,7 @@ int Function() stdoutTerminalColumnsResolver = defaultTerminalColumnsForTest; @visibleForTesting int defaultTerminalColumnsForTest() { if (stdout.hasTerminal) { - return stdout.terminalColumns; // coverage:ignore-line + return stdout.terminalColumns; } return fallbackStdoutTerminalColumns; } diff --git a/packages/cli_core/pubspec.yaml b/packages/cli_core/pubspec.yaml index 656f810..0647958 100644 --- a/packages/cli_core/pubspec.yaml +++ b/packages/cli_core/pubspec.yaml @@ -12,7 +12,7 @@ topics: - flutter environment: - sdk: '>=3.0.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: mason: ^0.1.2 @@ -20,9 +20,12 @@ dependencies: meta: ^1.15.0 path: ^1.8.0 universal_io: ^2.2.2 + yaml: ^3.1.4 dev_dependencies: test: ^1.24.9 - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 mocktail: ^1.0.4 build_runner: ^2.4.12 + +resolution: workspace diff --git a/packages/cli_core/test/file_utils_test.dart b/packages/cli_core/test/file_utils_test.dart index dc5762e..b9c79a6 100644 --- a/packages/cli_core/test/file_utils_test.dart +++ b/packages/cli_core/test/file_utils_test.dart @@ -94,10 +94,46 @@ void main() { }); test('defaults to current directory when no path is provided', () async { - // No argument — exercises the Directory.current fallback branch. - final result = await FileUtils.isMonoRepo(); - expect(result, isA()); + final original = Directory.current; + try { + Directory.current = tempDir; + await File('melos.yaml').writeAsString('name: test_workspace'); + expect(await FileUtils.isMonoRepo(), isTrue); + } finally { + Directory.current = original; + } }); + + test('recognizes a pub workspace from a deeply nested member', () async { + await File(p.join(tempDir.path, 'pubspec.yaml')).writeAsString( + 'name: root\nworkspace:\n - packages/example\n', + ); + final nested = + await Directory(p.join(tempDir.path, 'packages', 'example', 'lib')) + .create(recursive: true); + expect(await FileUtils.isMonoRepo(nested.path), isTrue); + }); + + test('recognizes an empty native workspace at its root', () async { + await File(p.join(tempDir.path, 'pubspec.yaml')) + .writeAsString('workspace: []\n'); + expect(await FileUtils.isMonoRepo(tempDir.path), isTrue); + }); + + for (final contents in [ + 'name: regular_package\n# workspace: []\n', + 'workspace: not-a-list\n', + 'just a scalar', + 'workspace: [unfinished', + ]) { + test( + 'does not mistake unrelated or invalid YAML for a workspace: $contents', + () async { + await File(p.join(tempDir.path, 'pubspec.yaml')) + .writeAsString(contents); + expect(await FileUtils.isMonoRepo(tempDir.path), isFalse); + }); + } }); test('readYamlFile throws when file is missing', () { diff --git a/packages/cli_core/test/logger_extension_test.dart b/packages/cli_core/test/logger_extension_test.dart index 433997c..5b65fb9 100644 --- a/packages/cli_core/test/logger_extension_test.dart +++ b/packages/cli_core/test/logger_extension_test.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:cli_core/cli_core.dart'; import 'package:cli_core/src/logger_extension.dart' as ext; import 'package:mason_logger/mason_logger.dart'; @@ -6,6 +8,8 @@ import 'package:test/test.dart'; class _FakeLogger extends Mock implements Logger {} +class _MockStdout extends Mock implements Stdout {} + void main() { group('LoggerX.created', () { test('forwards message to info', () { @@ -70,5 +74,14 @@ void main() { expect(ext.defaultTerminalColumnsForTest(), ext.fallbackStdoutTerminalColumns); }); + + test('default resolver reads the attached terminal width', () { + final output = _MockStdout(); + when(() => output.hasTerminal).thenReturn(true); + when(() => output.terminalColumns).thenReturn(132); + IOOverrides.runZoned(() { + expect(ext.defaultTerminalColumnsForTest(), 132); + }, stdout: () => output); + }); }); } diff --git a/packages/connectivity_wrapper/example/lib/screens/network_aware_widget_screen.dart b/packages/connectivity_wrapper/example/lib/screens/network_aware_widget_screen.dart index b2a1036..4754826 100644 --- a/packages/connectivity_wrapper/example/lib/screens/network_aware_widget_screen.dart +++ b/packages/connectivity_wrapper/example/lib/screens/network_aware_widget_screen.dart @@ -18,11 +18,11 @@ class NetworkAwareWidgetScreen extends StatelessWidget { const TextField( decoration: InputDecoration(labelText: 'Email'), ), - const Spacer(), + const SizedBox(height: 10), const TextField( decoration: InputDecoration(labelText: 'Password'), ), - const Spacer(), + const SizedBox(height: 10), ConnectivityWidgetWrapper( stacked: false, offlineWidget: ElevatedButton( diff --git a/packages/connectivity_wrapper/example/lib/screens/scaffold_example_screen.dart b/packages/connectivity_wrapper/example/lib/screens/scaffold_example_screen.dart index 8fa8e65..bbccdef 100644 --- a/packages/connectivity_wrapper/example/lib/screens/scaffold_example_screen.dart +++ b/packages/connectivity_wrapper/example/lib/screens/scaffold_example_screen.dart @@ -68,7 +68,7 @@ class _ScaffoldExampleScreenState extends State { }, ), const Divider(), - const Spacer(), + const SizedBox(height: 10), CheckboxListTile( title: const Text(Strings.customHeight), value: _customHeight, @@ -87,7 +87,7 @@ class _ScaffoldExampleScreenState extends State { }, ), const Divider(), - const Spacer(), + const SizedBox(height: 10), CheckboxListTile( title: const Text(Strings.customMessage), value: _customMessage, @@ -106,11 +106,11 @@ class _ScaffoldExampleScreenState extends State { }, ), const Divider(), - const Spacer(), + const SizedBox(height: 10), const ListTile( title: Text(Strings.customAlignment), ), - const Spacer(), + const SizedBox(height: 10), IconButton( icon: const Icon(Icons.arrow_upward), onPressed: () { @@ -120,7 +120,7 @@ class _ScaffoldExampleScreenState extends State { }, ), const Divider(), - const Spacer(), + const SizedBox(height: 10), IconButton( icon: const Icon(Icons.center_focus_strong), onPressed: () { @@ -130,7 +130,7 @@ class _ScaffoldExampleScreenState extends State { }, ), const Divider(), - const Spacer(), + const SizedBox(height: 10), IconButton( icon: const Icon(Icons.arrow_downward), onPressed: () { @@ -140,7 +140,7 @@ class _ScaffoldExampleScreenState extends State { }, ), const Divider(), - const Spacer(), + const SizedBox(height: 10), CheckboxListTile( title: const Text(Strings.userInteraction), value: _disableInteraction, @@ -153,7 +153,7 @@ class _ScaffoldExampleScreenState extends State { }); }, ), - const Spacer(), + const SizedBox(height: 10), ], ), ), diff --git a/packages/connectivity_wrapper/example/lib/utils/utils.dart b/packages/connectivity_wrapper/example/lib/utils/utils.dart index 4bc4987..25bfe72 100644 --- a/packages/connectivity_wrapper/example/lib/utils/utils.dart +++ b/packages/connectivity_wrapper/example/lib/utils/utils.dart @@ -8,7 +8,7 @@ class AppRoutes { } } -showSnackBar( +void showSnackBar( BuildContext context, { required String title, Color? color, diff --git a/packages/connectivity_wrapper/example/pubspec.yaml b/packages/connectivity_wrapper/example/pubspec.yaml index 63ea078..32ec936 100644 --- a/packages/connectivity_wrapper/example/pubspec.yaml +++ b/packages/connectivity_wrapper/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none version: 0.0.2+1 environment: - sdk: '>=2.18.4 <3.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: flutter: @@ -16,42 +16,11 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 flutter: uses-material-design: true assets: - assets/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +resolution: workspace diff --git a/packages/connectivity_wrapper/example/test/example_test.dart b/packages/connectivity_wrapper/example/test/example_test.dart new file mode 100644 index 0000000..c94e7ca --- /dev/null +++ b/packages/connectivity_wrapper/example/test/example_test.dart @@ -0,0 +1,193 @@ +import 'package:connectivity_wrapper/connectivity_wrapper.dart'; +import 'package:connectivity_wrapper_example/main.dart' as app; +import 'package:connectivity_wrapper_example/screens/custom_offline_widget_screen.dart'; +import 'package:connectivity_wrapper_example/screens/network_aware_widget_screen.dart'; +import 'package:connectivity_wrapper_example/screens/scaffold_example_screen.dart'; +import 'package:connectivity_wrapper_example/utils/strings.dart'; +import 'package:connectivity_wrapper_example/utils/ui_helper.dart' as ui; +import 'package:connectivity_wrapper_example/utils/utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + var connected = true; + setUp(() { + connected = true; + ConnectivityWrapper.instance.networkChecker = () async => connected; + ConnectivityWrapper.instance.checkInterval = + const Duration(milliseconds: 20); + }); + tearDown(() { + ConnectivityWrapper.instance.networkChecker = null; + ConnectivityWrapper.instance.checkInterval = const Duration(seconds: 2); + }); + + Future close(WidgetTester tester) async { + await tester.pumpWidget(const SizedBox()); + await tester.pump(); + expect(ConnectivityWrapper.instance.hasListeners, isFalse); + } + + Future pumpScreen(WidgetTester tester, Widget screen) async { + await tester.binding.setSurfaceSize(const Size(900, 1200)); + addTearDown(() => tester.binding.setSurfaceSize(null)); + await tester + .pumpWidget(ConnectivityAppWrapper(app: MaterialApp(home: screen))); + await tester.pumpAndSettle(); + } + + testWidgets('entry point navigates to each demonstration', (tester) async { + await tester.binding.setSurfaceSize(const Size(900, 1200)); + addTearDown(() => tester.binding.setSurfaceSize(null)); + app.main(); + await tester.pumpAndSettle(); + expect(find.text('Connectivity Wrapper Example'), findsOneWidget); + for (final title in [ + Strings.example1, + Strings.example2, + Strings.example3 + ]) { + await tester.tap(find.text(title)); + await tester.pumpAndSettle(); + expect(find.text(title), findsOneWidget); + await tester.pageBack(); + await tester.pumpAndSettle(); + } + expect(tester.takeException(), isNull); + await close(tester); + }); + + testWidgets('menu checks current connection and shows both status messages', + (tester) async { + app.main(); + await tester.pumpAndSettle(); + await tester.tap(find.text(Strings.example4)); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 300)); + expect(find.text('You Are Connected'), findsOneWidget); + expect(tester.widget(find.byType(SnackBar)).backgroundColor, + Colors.green); + await tester.pump(const Duration(seconds: 2)); + await tester.pumpAndSettle(); + connected = false; + await tester.tap(find.text(Strings.example4)); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 300)); + expect(find.text('You Are Not Connected'), findsOneWidget); + expect(tester.widget(find.byType(SnackBar)).backgroundColor, + Colors.red); + await close(tester); + }); + + testWidgets( + 'scaffold controls customize and reset each offline presentation option', + (tester) async { + await pumpScreen(tester, const ScaffoldExampleScreen()); + ConnectivityWidgetWrapper wrapper() => + tester.widget(find.byType(ConnectivityWidgetWrapper)); + expect(wrapper().alignment, Alignment.bottomCenter); + final controls = { + Strings.customDecoration: (enabled) => + expect(wrapper().decoration, enabled ? isA() : isNull), + Strings.customHeight: (enabled) => + expect(wrapper().height, enabled ? 150 : isNull), + Strings.customMessage: (enabled) { + expect(wrapper().message, enabled ? Strings.offlineMessage : isNull); + expect(wrapper().messageStyle?.fontSize, enabled ? 40 : isNull); + }, + Strings.userInteraction: (enabled) => + expect(wrapper().disableInteraction, enabled), + }; + for (final entry in controls.entries) { + final finder = find.widgetWithText(CheckboxListTile, entry.key); + tester.widget(finder).onChanged!(null); + await tester.pump(); + entry.value(false); + await tester.ensureVisible(finder); + await tester.tap(finder); + await tester.pumpAndSettle(); + entry.value(true); + await tester.tap(finder); + await tester.pumpAndSettle(); + entry.value(false); + } + for (final entry in { + Icons.arrow_upward: Alignment.topCenter, + Icons.center_focus_strong: Alignment.center, + Icons.arrow_downward: Alignment.bottomCenter + }.entries) { + await tester.ensureVisible(find.byIcon(entry.key)); + await tester.tap(find.byIcon(entry.key)); + await tester.pumpAndSettle(); + expect(wrapper().alignment, entry.value); + } + await close(tester); + }); + + testWidgets('custom offline screen renders its illustration and reconnects', + (tester) async { + await pumpScreen(tester, const CustomOfflineWidgetScreen()); + expect(find.text(' Item 0'), findsOneWidget); + connected = false; + await tester.pump(const Duration(milliseconds: 40)); + await tester.pump(const Duration(milliseconds: 600)); + expect(find.byType(OfflineWidget), findsOneWidget); + expect(find.text(Strings.offlineMessage), findsOneWidget); + expect(tester.widget(find.byType(Image)).image, + const AssetImage('assets/dog.gif')); + connected = true; + await tester.pump(const Duration(milliseconds: 40)); + await tester.pump(const Duration(milliseconds: 600)); + expect(find.byType(OfflineWidget), findsNothing); + await close(tester); + }); + + testWidgets('network-aware form disables sign in while offline', + (tester) async { + await pumpScreen(tester, const NetworkAwareWidgetScreen()); + await tester.enterText(find.byType(TextField).first, 'a@example.com'); + await tester.enterText(find.byType(TextField).last, 'secret'); + await tester.tap(find.text('Sign In')); + expect( + tester + .widget( + find.widgetWithText(ElevatedButton, 'Sign In')) + .onPressed, + isNotNull); + connected = false; + await tester.pump(const Duration(milliseconds: 40)); + await tester.pump(const Duration(milliseconds: 600)); + expect(find.text('Sign In'), findsNothing); + expect( + tester + .widget( + find.widgetWithText(ElevatedButton, 'Connecting')) + .onPressed, + isNull); + await close(tester); + }); + + testWidgets('snackbar and spacer helpers respect custom and default options', + (tester) async { + late BuildContext context; + await tester + .pumpWidget(MaterialApp(home: Scaffold(body: Builder(builder: (value) { + context = value; + return const Column(children: [ui.Spacer(), ui.Spacer(size: 12)]); + })))); + final paddings = tester + .widgetList(find.descendant( + of: find.byType(ui.Spacer), matching: find.byType(Padding))) + .map((widget) => widget.padding); + expect(paddings, [const EdgeInsets.all(5), const EdgeInsets.all(12)]); + const style = TextStyle(fontSize: 22, color: Colors.pink); + showSnackBar(context, + title: 'custom', color: Colors.blue, milliseconds: 500, style: style); + await tester.pumpAndSettle(); + final bar = tester.widget(find.byType(SnackBar)); + expect(bar.duration, const Duration(milliseconds: 500)); + expect(bar.backgroundColor, Colors.blue); + expect(tester.widget(find.text('custom')).style, style); + await tester.pumpWidget(const SizedBox()); + }); +} diff --git a/packages/connectivity_wrapper/lib/connectivity_wrapper.dart b/packages/connectivity_wrapper/lib/connectivity_wrapper.dart index bb6ab99..5e77f3a 100644 --- a/packages/connectivity_wrapper/lib/connectivity_wrapper.dart +++ b/packages/connectivity_wrapper/lib/connectivity_wrapper.dart @@ -8,7 +8,7 @@ /// (usually on timeout, default 10 sec) /// a false boolean is pushed to the same list. /// -library connectivity_wrapper; +library; import 'dart:async'; import 'dart:io'; @@ -38,8 +38,8 @@ enum ConnectivityStatus { CONNECTED, DISCONNECTED } /// connectivity status of the device. /// class ConnectivityWrapper { - static List get _defaultAddresses => (kIsWeb) - ? [] // coverage:ignore-line + static List _defaultAddresses(bool isWeb) => isWeb + ? [] : List.unmodifiable( [ AddressCheckOptions( @@ -81,9 +81,12 @@ class ConnectivityWrapper { ], ); - List addresses = _defaultAddresses; + List addresses; + final bool _isWeb; - ConnectivityWrapper._() { + ConnectivityWrapper._({bool isWeb = kIsWeb}) + : _isWeb = isWeb, + addresses = _defaultAddresses(isWeb) { _statusController.onListen = () { _maybeEmitStatusUpdate(); }; @@ -97,6 +100,15 @@ class ConnectivityWrapper { /// static final ConnectivityWrapper instance = ConnectivityWrapper._(); + /// Creates an isolated checker with a selected platform for unit tests. + @visibleForTesting + factory ConnectivityWrapper.forTesting({bool isWeb = false}) => + ConnectivityWrapper._(isWeb: isWeb); + + /// Optional deterministic network check for tests. Reset after use. + @visibleForTesting + Future Function()? networkChecker; + /// [isHostReachable] is a function that checks if a host is reachable. /// /// [options] is the options to use for the address check. @@ -113,18 +125,18 @@ class ConnectivityWrapper { options.address ?? options.hostname, options.port, timeout: options.timeout, - ) - ..destroy(); + ); return AddressCheckResult( options, isSuccess: true, ); } catch (e) { - sock?.destroy(); // coverage:ignore-line return AddressCheckResult( options, isSuccess: false, ); + } finally { + sock?.destroy(); } } @@ -138,8 +150,10 @@ class ConnectivityWrapper { /// to the network. /// Future get isConnected async { + final checker = networkChecker; + if (checker != null) return checker(); bool connected = await _checkWebConnection(); - if (kIsWeb) return connected; + if (_isWeb) return connected; if (!connected) return connected; List> requests = []; @@ -206,9 +220,8 @@ class ConnectivityWrapper { ConnectivityStatus? get lastStatus => _lastStatus; - _maybeEmitStatusUpdate([Timer? timer]) async { + Future _maybeEmitStatusUpdate() async { _timerHandle?.cancel(); - timer?.cancel(); // coverage:ignore-line var currentStatus = await connectionStatus; diff --git a/packages/connectivity_wrapper/lib/src/providers/connectivity_provider.dart b/packages/connectivity_wrapper/lib/src/providers/connectivity_provider.dart index bbb439b..45e33ce 100644 --- a/packages/connectivity_wrapper/lib/src/providers/connectivity_provider.dart +++ b/packages/connectivity_wrapper/lib/src/providers/connectivity_provider.dart @@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; /// which extends [ChangeNotifier]. /// class ConnectivityProvider extends ChangeNotifier { + late final StreamSubscription _subscription; StreamController connectivityController = StreamController(); @@ -24,10 +25,17 @@ class ConnectivityProvider extends ChangeNotifier { _updateConnectivityStatus(); } - _updateConnectivityStatus() async { - ConnectivityWrapper.instance.onStatusChange + void _updateConnectivityStatus() { + _subscription = ConnectivityWrapper.instance.onStatusChange .listen((ConnectivityStatus connectivityStatus) { connectivityController.add(connectivityStatus); }); } + + @override + void dispose() { + _subscription.cancel(); + connectivityController.close(); + super.dispose(); + } } diff --git a/packages/connectivity_wrapper/lib/src/widgets/connectivity_app_wrapper_widget.dart b/packages/connectivity_wrapper/lib/src/widgets/connectivity_app_wrapper_widget.dart index 5caa9ef..b5e77cd 100644 --- a/packages/connectivity_wrapper/lib/src/widgets/connectivity_app_wrapper_widget.dart +++ b/packages/connectivity_wrapper/lib/src/widgets/connectivity_app_wrapper_widget.dart @@ -19,7 +19,11 @@ class ConnectivityAppWrapper extends StatelessWidget { Widget build(BuildContext context) { return StreamProvider( initialData: ConnectivityStatus.CONNECTED, - create: (context) => ConnectivityProvider().connectivityStream, + create: (context) { + final provider = ConnectivityProvider(); + provider.connectivityController.onCancel = provider.dispose; + return provider.connectivityStream; + }, child: app, ); } diff --git a/packages/connectivity_wrapper/pubspec.yaml b/packages/connectivity_wrapper/pubspec.yaml index e252136..1887c39 100644 --- a/packages/connectivity_wrapper/pubspec.yaml +++ b/packages/connectivity_wrapper/pubspec.yaml @@ -10,16 +10,18 @@ screenshots: path: screenshots/icon.png environment: - sdk: ">=2.18.4 <4.0.0" + sdk: '>=3.6.0 <4.0.0' flutter: ">=1.17.0" dependencies: flutter: sdk: flutter provider: ^6.1.2 - connectivity_plus: ^6.1.2 # To check connectivity for web only + connectivity_plus: ^7.3.1 # To check connectivity for web only dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^5.0.0 \ No newline at end of file + flutter_lints: ^6.0.0 + +resolution: workspace diff --git a/packages/connectivity_wrapper/test/connectivity_provider_test.dart b/packages/connectivity_wrapper/test/connectivity_provider_test.dart index b8a189e..fa6e0a7 100644 --- a/packages/connectivity_wrapper/test/connectivity_provider_test.dart +++ b/packages/connectivity_wrapper/test/connectivity_provider_test.dart @@ -29,10 +29,6 @@ void main() { .setMockMethodCallHandler(_connectivityChannel, null); }); - // Both tests are combined so we make a single provider, exercise initial - // emission + forwarded emission, and tear down after both are observed. - // Closing the controller early conflicts with the provider's leaked - // subscription to the singleton wrapper. test('emits initial CONNECTED then forwards wrapper status', () async { final provider = ConnectivityProvider(); @@ -55,10 +51,8 @@ void main() { } finally { poller.cancel(); await sub.cancel(); - // Intentionally do not close provider.connectivityController here — - // the provider's internal subscription to the singleton wrapper is not - // exposed and may still push events; the controller will be GC'd once - // the test ends. + provider.dispose(); + expect(ConnectivityWrapper.instance.hasListeners, isFalse); } }); } diff --git a/packages/connectivity_wrapper/test/connectivity_wrapper_test.dart b/packages/connectivity_wrapper/test/connectivity_wrapper_test.dart index 0e0163b..99cfd0d 100644 --- a/packages/connectivity_wrapper/test/connectivity_wrapper_test.dart +++ b/packages/connectivity_wrapper/test/connectivity_wrapper_test.dart @@ -30,6 +30,52 @@ void main() { tearDown(_resetConnectivityMock); + test('isolated web checker uses transport status without socket addresses', + () async { + final checker = ConnectivityWrapper.forTesting(isWeb: true); + expect(checker.addresses, isEmpty); + _mockConnectivity(['wifi']); + expect(await checker.isConnected, isTrue); + _mockConnectivity(['none']); + expect(await checker.isConnected, isFalse); + expect(checker.lastTryResults, isEmpty); + }); + + test('injected checks determine status without accessing the network', + () async { + final checker = ConnectivityWrapper.forTesting(); + expect(checker.addresses, isNotEmpty); + checker.networkChecker = () async => true; + expect(await checker.connectionStatus, ConnectivityStatus.CONNECTED); + checker.networkChecker = () async => false; + expect(await checker.connectionStatus, ConnectivityStatus.DISCONNECTED); + expect(checker.lastTryResults, isEmpty); + }); + + test('polling publishes status changes and suppresses repeated values', + () async { + final checker = ConnectivityWrapper.forTesting(); + checker.checkInterval = const Duration(milliseconds: 5); + var checks = 0; + checker.networkChecker = () async => ++checks >= 3; + final statuses = []; + final reconnected = Completer(); + final subscription = checker.onStatusChange.listen((status) { + statuses.add(status); + if (status == ConnectivityStatus.CONNECTED) reconnected.complete(); + }); + try { + await reconnected.future.timeout(const Duration(seconds: 2)); + expect(statuses, + [ConnectivityStatus.DISCONNECTED, ConnectivityStatus.CONNECTED]); + expect(checks, greaterThanOrEqualTo(3)); + } finally { + await subscription.cancel(); + } + expect(checker.hasListeners, isFalse); + expect(checker.lastStatus, isNull); + }); + group('ConnectivityWrapper singleton', () { test('instance returns the same object', () { expect(ConnectivityWrapper.instance, same(ConnectivityWrapper.instance)); diff --git a/packages/dzod/CHANGELOG.md b/packages/dzod/CHANGELOG.md index 9129da7..877dccc 100644 --- a/packages/dzod/CHANGELOG.md +++ b/packages/dzod/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased + +- Fix `startsWith`, `endsWith`, `contains`, `date`, and `datetime` throwing invalid casts when constructing a schema. They now return `Schema`, consistent with other refinements. Apply string-specific checks first, for example `z.string().min(3).startsWith('abc')`. +- Await inner asynchronous validation before applying an asynchronous refinement. +- Upgrade dependencies and add regression tests for all executable library lines. + ## 0.2.1 - **FIX**: replace deprecated Flutter 3.35 APIs in dzod example app. @@ -46,4 +52,3 @@ - **DOCS**(dzod): fix README examples to match actual API implementation. - **DOCS**(dzod): update test coverage badge to 99.3%. - **BREAKING** **REFACTOR**(dzod): rename zod_bhai package to dzod. - diff --git a/packages/dzod/example/analysis_options.yaml b/packages/dzod/example/analysis_options.yaml index 0d29021..55b180c 100644 --- a/packages/dzod/example/analysis_options.yaml +++ b/packages/dzod/example/analysis_options.yaml @@ -7,6 +7,9 @@ # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. +analyzer: + exclude: + - build/** include: package:flutter_lints/flutter.yaml linter: diff --git a/packages/dzod/example/lib/examples/advanced/coercion_example.dart b/packages/dzod/example/lib/examples/advanced/coercion_example.dart index 1ff8f0f..65058e1 100644 --- a/packages/dzod/example/lib/examples/advanced/coercion_example.dart +++ b/packages/dzod/example/lib/examples/advanced/coercion_example.dart @@ -5,6 +5,8 @@ import '../../widgets/result_display.dart'; import '../../widgets/schema_display.dart'; import '../../widgets/validation_card.dart'; +enum _CoercionType { number, boolean, date, list, advanced } + class CoercionExample extends StatefulWidget { const CoercionExample({super.key}); @@ -16,31 +18,29 @@ class _CoercionExampleState extends State { final _formKey = GlobalKey(); final _inputController = TextEditingController(); - String _selectedType = 'number'; + _CoercionType _selectedType = _CoercionType.number; bool _strictMode = false; // Example 13: Automatic Type Coercion Schema get _currentSchema { switch (_selectedType) { - case 'number': + case _CoercionType.number: return _strictMode ? z.coerce.number(strict: true) : z.coerce.number(); - case 'boolean': + case _CoercionType.boolean: return _strictMode ? z.coerce.boolean(strict: true) : z.coerce.boolean(); - case 'date': + case _CoercionType.date: return _strictMode ? z.coerce.date(strict: true) : z.coerce.date(); - case 'list': + case _CoercionType.list: return _strictMode ? z.coerce.list(strict: true) : z.coerce.list(); - case 'advanced': + case _CoercionType.advanced: // Advanced number coercion with additional validation return z.coerce .number(strict: false) .transform((value) => double.parse(value.toStringAsFixed(2))) .refine((value) => value >= 0 && value <= 100, message: 'Must be between 0 and 100'); - default: - return z.string(); } } @@ -76,7 +76,7 @@ class _CoercionExampleState extends State { } Widget _buildExampleButtons() { - final examples = _examples[_selectedType] ?? []; + final examples = _examples[_selectedType.name] ?? []; return Wrap( spacing: 8, @@ -95,11 +95,11 @@ class _CoercionExampleState extends State { } IconData _getExampleIcon(String example) { - if (_selectedType == 'number') { + if (_selectedType == _CoercionType.number) { if (RegExp(r'^\d+\.?\d*$').hasMatch(example)) { return Icons.check_circle; } - } else if (_selectedType == 'boolean') { + } else if (_selectedType == _CoercionType.boolean) { if (['true', '1', 'yes', 'on'].contains(example.toLowerCase())) { return Icons.toggle_on; } else if (['false', '0', 'no', 'off', ''] @@ -115,11 +115,11 @@ class _CoercionExampleState extends State { // For demonstration, we'll parse the input based on type switch (_selectedType) { - case 'number': + case _CoercionType.number: // Try to parse as number final num = double.tryParse(text); return num ?? text; - case 'boolean': + case _CoercionType.boolean: // Various boolean representations if (text.toLowerCase() == 'true' || text == '1' || @@ -134,23 +134,17 @@ class _CoercionExampleState extends State { return false; } return text; - case 'date': + case _CoercionType.date: // Try to parse as date try { return DateTime.parse(text); } catch (_) { return text; } - case 'list': - // Try JSON parse first - try { - final parsed = text.contains('[') ? text : '[$text]'; - return parsed; - } catch (_) { - // Fall back to comma-separated - return text.split(',').map((e) => e.trim()).toList(); - } - default: + case _CoercionType.list: + // Keep the input in the bracketed form expected by the demo. + return text.contains('[') ? text : '[$text]'; + case _CoercionType.advanced: return text; } } @@ -169,7 +163,7 @@ class _CoercionExampleState extends State { Row( children: [ Expanded( - child: DropdownButtonFormField( + child: DropdownButtonFormField<_CoercionType>( initialValue: _selectedType, onChanged: (value) { setState(() { @@ -184,23 +178,23 @@ class _CoercionExampleState extends State { ), items: const [ DropdownMenuItem( - value: 'number', + value: _CoercionType.number, child: Text('Number Coercion'), ), DropdownMenuItem( - value: 'boolean', + value: _CoercionType.boolean, child: Text('Boolean Coercion'), ), DropdownMenuItem( - value: 'date', + value: _CoercionType.date, child: Text('Date Coercion'), ), DropdownMenuItem( - value: 'list', + value: _CoercionType.list, child: Text('List Coercion'), ), DropdownMenuItem( - value: 'advanced', + value: _CoercionType.advanced, child: Text('Advanced Number'), ), ], @@ -213,7 +207,7 @@ class _CoercionExampleState extends State { const Text('Strict Mode'), Switch( value: _strictMode, - onChanged: _selectedType != 'advanced' + onChanged: _selectedType != _CoercionType.advanced ? (value) { setState(() { _strictMode = value; @@ -250,7 +244,7 @@ class _CoercionExampleState extends State { ), const SizedBox(height: 8), Text( - _descriptions[_selectedType] ?? '', + _descriptions[_selectedType.name] ?? '', style: Theme.of(context).textTheme.bodySmall, ), if (_strictMode) ...[ @@ -275,7 +269,7 @@ class _CoercionExampleState extends State { controller: _inputController, decoration: InputDecoration( labelText: 'Input Value', - hintText: 'Enter value to coerce to $_selectedType', + hintText: 'Enter value to coerce to ${_selectedType.name}', border: const OutlineInputBorder(), prefixIcon: const Icon(Icons.input), ), diff --git a/packages/dzod/example/lib/examples/advanced/discriminated_union_example.dart b/packages/dzod/example/lib/examples/advanced/discriminated_union_example.dart index ba75845..51bb471 100644 --- a/packages/dzod/example/lib/examples/advanced/discriminated_union_example.dart +++ b/packages/dzod/example/lib/examples/advanced/discriminated_union_example.dart @@ -5,6 +5,8 @@ import '../../widgets/result_display.dart'; import '../../widgets/schema_display.dart'; import '../../widgets/validation_card.dart'; +enum _MessageType { text, image, video, audio } + class DiscriminatedUnionExample extends StatefulWidget { const DiscriminatedUnionExample({super.key}); @@ -15,7 +17,7 @@ class DiscriminatedUnionExample extends StatefulWidget { class _DiscriminatedUnionExampleState extends State { final _formKey = GlobalKey(); - String _selectedType = 'text'; + _MessageType _selectedType = _MessageType.text; // Controllers for different message types final _textController = TextEditingController(); @@ -55,12 +57,12 @@ class _DiscriminatedUnionExampleState extends State { Map _buildMessageData() { switch (_selectedType) { - case 'text': + case _MessageType.text: return { 'type': 'text', 'content': _textController.text, }; - case 'image': + case _MessageType.image: final data = { 'type': 'image', 'url': _imageUrlController.text, @@ -69,38 +71,36 @@ class _DiscriminatedUnionExampleState extends State { data['alt'] = _imageAltController.text; } return data; - case 'video': + case _MessageType.video: return { 'type': 'video', 'url': _videoUrlController.text, 'duration': double.tryParse(_videoDurationController.text) ?? 0, }; - case 'audio': + case _MessageType.audio: return { 'type': 'audio', 'url': _audioUrlController.text, 'duration': double.tryParse(_audioDurationController.text) ?? 0, }; - default: - return {}; } } void _fillExample() { setState(() { switch (_selectedType) { - case 'text': + case _MessageType.text: _textController.text = 'Hello, this is a text message!'; break; - case 'image': + case _MessageType.image: _imageUrlController.text = 'https://example.com/image.jpg'; _imageAltController.text = 'A beautiful sunset'; break; - case 'video': + case _MessageType.video: _videoUrlController.text = 'https://example.com/video.mp4'; _videoDurationController.text = '120.5'; break; - case 'audio': + case _MessageType.audio: _audioUrlController.text = 'https://example.com/audio.mp3'; _audioDurationController.text = '180'; break; @@ -122,7 +122,7 @@ class _DiscriminatedUnionExampleState extends State { Widget _buildMessageTypeForm() { switch (_selectedType) { - case 'text': + case _MessageType.text: return TextFormField( controller: _textController, maxLines: 3, @@ -134,7 +134,7 @@ class _DiscriminatedUnionExampleState extends State { ), ); - case 'image': + case _MessageType.image: return Column( children: [ TextFormField( @@ -159,7 +159,7 @@ class _DiscriminatedUnionExampleState extends State { ], ); - case 'video': + case _MessageType.video: return Column( children: [ TextFormField( @@ -185,7 +185,7 @@ class _DiscriminatedUnionExampleState extends State { ], ); - case 'audio': + case _MessageType.audio: return Column( children: [ TextFormField( @@ -210,17 +210,15 @@ class _DiscriminatedUnionExampleState extends State { ), ], ); - - default: - return const SizedBox.shrink(); } } @override Widget build(BuildContext context) { // Use extended schema if audio is selected - final currentSchema = - _selectedType == 'audio' ? extendedMessageSchema : messageSchema; + final currentSchema = _selectedType == _MessageType.audio + ? extendedMessageSchema + : messageSchema; return ValidationCard( title: 'Example 10: Discriminated Unions', @@ -232,7 +230,7 @@ class _DiscriminatedUnionExampleState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ // Message type selector - DropdownButtonFormField( + DropdownButtonFormField<_MessageType>( initialValue: _selectedType, onChanged: (value) { setState(() { @@ -247,7 +245,7 @@ class _DiscriminatedUnionExampleState extends State { ), items: const [ DropdownMenuItem( - value: 'text', + value: _MessageType.text, child: Row( children: [ Icon(Icons.text_fields, size: 20), @@ -257,7 +255,7 @@ class _DiscriminatedUnionExampleState extends State { ), ), DropdownMenuItem( - value: 'image', + value: _MessageType.image, child: Row( children: [ Icon(Icons.image, size: 20), @@ -267,7 +265,7 @@ class _DiscriminatedUnionExampleState extends State { ), ), DropdownMenuItem( - value: 'video', + value: _MessageType.video, child: Row( children: [ Icon(Icons.videocam, size: 20), @@ -277,7 +275,7 @@ class _DiscriminatedUnionExampleState extends State { ), ), DropdownMenuItem( - value: 'audio', + value: _MessageType.audio, child: Row( children: [ Icon(Icons.audiotrack, size: 20), diff --git a/packages/dzod/example/lib/examples/advanced/recursive_schema_example.dart b/packages/dzod/example/lib/examples/advanced/recursive_schema_example.dart index 82cb724..7fa836f 100644 --- a/packages/dzod/example/lib/examples/advanced/recursive_schema_example.dart +++ b/packages/dzod/example/lib/examples/advanced/recursive_schema_example.dart @@ -224,8 +224,8 @@ class _RecursiveSchemaExampleState extends State { return currentDepth; } - final children = node['children'] as List?; - if (children == null || children.isEmpty) { + final children = node['children']; + if (children is! List || children.isEmpty) { return currentDepth; } diff --git a/packages/dzod/example/lib/examples/async/api_validation_example.dart b/packages/dzod/example/lib/examples/async/api_validation_example.dart index e010a32..62147f9 100644 --- a/packages/dzod/example/lib/examples/async/api_validation_example.dart +++ b/packages/dzod/example/lib/examples/async/api_validation_example.dart @@ -50,17 +50,12 @@ class _ApiValidationExampleState extends State { _result = null; }); - try { - final result = await apiSchema.validateAsync(_urlController.text); - setState(() { - _result = result; - _isValidating = false; - }); - } catch (e) { - setState(() { - _isValidating = false; - }); - } + final result = await apiSchema.validateAsync(_urlController.text); + if (!mounted) return; + setState(() { + _result = result; + _isValidating = false; + }); } void _fillValidExample() { diff --git a/packages/dzod/example/lib/examples/async/database_validation_example.dart b/packages/dzod/example/lib/examples/async/database_validation_example.dart index e4444ce..364ba52 100644 --- a/packages/dzod/example/lib/examples/async/database_validation_example.dart +++ b/packages/dzod/example/lib/examples/async/database_validation_example.dart @@ -76,17 +76,12 @@ class _DatabaseValidationExampleState extends State { 'username': _usernameController.text, }; - try { - final result = await userSchema.validateAsync(data); - setState(() { - _result = result; - _isValidating = false; - }); - } catch (e) { - setState(() { - _isValidating = false; - }); - } + final result = await userSchema.validateAsync(data); + if (!mounted) return; + setState(() { + _result = result; + _isValidating = false; + }); } void _fillAvailableExample() { diff --git a/packages/dzod/example/lib/examples/complex/array_advanced_example.dart b/packages/dzod/example/lib/examples/complex/array_advanced_example.dart index 7869191..5acd7e7 100644 --- a/packages/dzod/example/lib/examples/complex/array_advanced_example.dart +++ b/packages/dzod/example/lib/examples/complex/array_advanced_example.dart @@ -68,8 +68,6 @@ class _ArrayAdvancedExampleState extends State { Schema _getCurrentSchema() { switch (_selectedArrayType) { - case 'base': - return _baseArraySchema; case 'range': return _baseArraySchema.min(1).max(10); case 'exactLength': diff --git a/packages/dzod/example/lib/examples/complex/enum_example.dart b/packages/dzod/example/lib/examples/complex/enum_example.dart index 3efdf10..e0fba17 100644 --- a/packages/dzod/example/lib/examples/complex/enum_example.dart +++ b/packages/dzod/example/lib/examples/complex/enum_example.dart @@ -59,8 +59,6 @@ class _EnumExampleState extends State { Schema _getCurrentSchema() { switch (_selectedEnumType) { - case 'basic': - return _basicRoleSchema; case 'excluded': return _basicRoleSchema.exclude(['guest']); case 'extended': diff --git a/packages/dzod/example/lib/examples/complex/object_manipulation_example.dart b/packages/dzod/example/lib/examples/complex/object_manipulation_example.dart index 06ae0f6..14b6813 100644 --- a/packages/dzod/example/lib/examples/complex/object_manipulation_example.dart +++ b/packages/dzod/example/lib/examples/complex/object_manipulation_example.dart @@ -82,8 +82,6 @@ class _ObjectManipulationExampleState extends State { Schema _getCurrentSchema() { switch (_selectedManipulation) { - case 'base': - return _baseUserSchema; case 'picked': return _baseUserSchema.pick(['name', 'email']); case 'omitted': diff --git a/packages/dzod/example/lib/examples/complex/tuple_example.dart b/packages/dzod/example/lib/examples/complex/tuple_example.dart index 93e5d0f..39db095 100644 --- a/packages/dzod/example/lib/examples/complex/tuple_example.dart +++ b/packages/dzod/example/lib/examples/complex/tuple_example.dart @@ -52,7 +52,6 @@ class _TupleExampleState extends State { } void _clearInput() { - _tupleFormKey.currentState?.reset(); _element1Controller.clear(); _element2Controller.clear(); _element3Controller.clear(); @@ -76,8 +75,6 @@ class _TupleExampleState extends State { Schema _getCurrentSchema() { switch (_selectedTupleType) { - case 'basic': - return _basicTupleSchema; case 'withRest': return _basicTupleSchema.rest(z.string()); case 'exactLength': diff --git a/packages/dzod/example/lib/examples/real_world/authentication_schema_example.dart b/packages/dzod/example/lib/examples/real_world/authentication_schema_example.dart index 5626094..b1ca060 100644 --- a/packages/dzod/example/lib/examples/real_world/authentication_schema_example.dart +++ b/packages/dzod/example/lib/examples/real_world/authentication_schema_example.dart @@ -5,6 +5,8 @@ import '../../widgets/result_display.dart'; import '../../widgets/schema_display.dart'; import '../../widgets/validation_card.dart'; +enum _AuthMethod { email, oauth, apikey } + class AuthenticationSchemaExample extends StatefulWidget { const AuthenticationSchemaExample({super.key}); @@ -16,7 +18,7 @@ class AuthenticationSchemaExample extends StatefulWidget { class _AuthenticationSchemaExampleState extends State { final _formKey = GlobalKey(); - String _selectedMethod = 'email'; + _AuthMethod _selectedMethod = _AuthMethod.email; // Controllers for different auth methods final _emailController = TextEditingController(); @@ -56,42 +58,40 @@ class _AuthenticationSchemaExampleState Map _buildAuthData() { switch (_selectedMethod) { - case 'email': + case _AuthMethod.email: return { 'method': 'email', 'email': _emailController.text, 'password': _passwordController.text, }; - case 'oauth': + case _AuthMethod.oauth: return { 'method': 'oauth', 'provider': _providerController.text, 'token': _tokenController.text, }; - case 'apikey': + case _AuthMethod.apikey: return { 'method': 'apikey', 'key': _apiKeyController.text, 'secret': _apiSecretController.text, }; - default: - return {}; } } void _fillValidExample() { setState(() { switch (_selectedMethod) { - case 'email': + case _AuthMethod.email: _emailController.text = 'user@example.com'; _passwordController.text = 'SecurePass123!'; break; - case 'oauth': + case _AuthMethod.oauth: _providerController.text = 'google'; _tokenController.text = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'; break; - case 'apikey': + case _AuthMethod.apikey: _apiKeyController.text = '12345678901234567890123456789012'; _apiSecretController.text = '1234567890123456789012345678901234567890123456789012345678901234'; @@ -103,15 +103,15 @@ class _AuthenticationSchemaExampleState void _fillInvalidExample() { setState(() { switch (_selectedMethod) { - case 'email': + case _AuthMethod.email: _emailController.text = 'invalid-email'; _passwordController.text = 'weak'; break; - case 'oauth': + case _AuthMethod.oauth: _providerController.text = 'google'; _tokenController.text = 'invalid-jwt-token'; break; - case 'apikey': + case _AuthMethod.apikey: _apiKeyController.text = 'too-short'; _apiSecretController.text = 'not-hex'; break; @@ -131,7 +131,7 @@ class _AuthenticationSchemaExampleState Widget _buildAuthMethodForm() { switch (_selectedMethod) { - case 'email': + case _AuthMethod.email: return Column( children: [ TextFormField( @@ -183,7 +183,7 @@ class _AuthenticationSchemaExampleState ], ); - case 'oauth': + case _AuthMethod.oauth: return Column( children: [ DropdownButtonFormField( @@ -220,7 +220,7 @@ class _AuthenticationSchemaExampleState ], ); - case 'apikey': + case _AuthMethod.apikey: return Column( children: [ TextFormField( @@ -265,32 +265,26 @@ class _AuthenticationSchemaExampleState ), ], ); - - default: - return const SizedBox.shrink(); } } - Widget _buildMethodIcon(String method) { + Widget _buildMethodIcon(_AuthMethod method) { IconData icon; Color? color; switch (method) { - case 'email': + case _AuthMethod.email: icon = Icons.email; color = Colors.blue; break; - case 'oauth': + case _AuthMethod.oauth: icon = Icons.account_circle; color = Colors.orange; break; - case 'apikey': + case _AuthMethod.apikey: icon = Icons.vpn_key; color = Colors.green; break; - default: - icon = Icons.lock; - color = null; } return Icon(icon, color: color); @@ -308,7 +302,7 @@ class _AuthenticationSchemaExampleState crossAxisAlignment: CrossAxisAlignment.start, children: [ // Auth method selector - DropdownButtonFormField( + DropdownButtonFormField<_AuthMethod>( initialValue: _selectedMethod, onChanged: (value) { setState(() { @@ -321,16 +315,16 @@ class _AuthenticationSchemaExampleState border: OutlineInputBorder(), prefixIcon: Icon(Icons.security), ), - items: ['email', 'oauth', 'apikey'].map((method) { + items: _AuthMethod.values.map((method) { return DropdownMenuItem( value: method, child: Row( children: [ _buildMethodIcon(method), const SizedBox(width: 8), - Text(method == 'email' + Text(method == _AuthMethod.email ? 'Email/Password' - : method == 'oauth' + : method == _AuthMethod.oauth ? 'OAuth Provider' : 'API Key'), ], diff --git a/packages/dzod/example/lib/examples/schema_composition/json_schema_generation_example.dart b/packages/dzod/example/lib/examples/schema_composition/json_schema_generation_example.dart index 3c285e8..c3c2565 100644 --- a/packages/dzod/example/lib/examples/schema_composition/json_schema_generation_example.dart +++ b/packages/dzod/example/lib/examples/schema_composition/json_schema_generation_example.dart @@ -91,12 +91,8 @@ class _JsonSchemaGenerationExampleState } String get _jsonSchema { - try { - final schema = _currentSchema.toJsonSchema(); - return const JsonEncoder.withIndent(' ').convert(schema); - } catch (e) { - return 'Error generating JSON Schema: $e'; - } + final schema = _currentSchema.toJsonSchema(); + return const JsonEncoder.withIndent(' ').convert(schema); } void _copyToClipboard() { diff --git a/packages/dzod/example/lib/main.dart b/packages/dzod/example/lib/main.dart index e00b7ca..852051c 100644 --- a/packages/dzod/example/lib/main.dart +++ b/packages/dzod/example/lib/main.dart @@ -177,6 +177,7 @@ class _ValidationShowcasePageState extends State { Container( padding: const EdgeInsets.all(16), child: DropdownButtonFormField( + isExpanded: true, initialValue: _selectedCategory, onChanged: (value) { setState(() { diff --git a/packages/dzod/example/pubspec.yaml b/packages/dzod/example/pubspec.yaml index 11bfa9e..188f4b3 100644 --- a/packages/dzod/example/pubspec.yaml +++ b/packages/dzod/example/pubspec.yaml @@ -5,7 +5,7 @@ publish_to: 'none' version: 2.0.0 environment: - sdk: '>=2.18.4 <4.0.0' + sdk: '>=3.6.0 <4.0.0' flutter: ">=1.17.0" dependencies: @@ -17,7 +17,9 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^2.0.3 + flutter_lints: ^6.0.0 flutter: - uses-material-design: true \ No newline at end of file + uses-material-design: true + +resolution: workspace diff --git a/packages/dzod/example/test/showcase_test.dart b/packages/dzod/example/test/showcase_test.dart new file mode 100644 index 0000000..0f61677 --- /dev/null +++ b/packages/dzod/example/test/showcase_test.dart @@ -0,0 +1,235 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:dzod_example/main.dart' as app; +import 'package:dzod_example/widgets/schema_display.dart'; +import 'package:dzod_example/widgets/validation_card.dart'; +import 'package:dzod_example/examples/advanced/coercion_example.dart'; +import 'package:dzod_example/examples/advanced/discriminated_union_example.dart'; +import 'package:dzod_example/examples/advanced/pipeline_example.dart'; +import 'package:dzod_example/examples/advanced/recursive_schema_example.dart'; +import 'package:dzod_example/examples/async/api_validation_example.dart'; +import 'package:dzod_example/examples/async/database_validation_example.dart'; +import 'package:dzod_example/examples/basic/boolean_null_example.dart'; +import 'package:dzod_example/examples/basic/number_validations_example.dart'; +import 'package:dzod_example/examples/basic/string_validation_example.dart'; +import 'package:dzod_example/examples/basic/user_schema_example.dart'; +import 'package:dzod_example/examples/complex/array_advanced_example.dart'; +import 'package:dzod_example/examples/complex/enum_example.dart'; +import 'package:dzod_example/examples/complex/object_manipulation_example.dart'; +import 'package:dzod_example/examples/complex/record_example.dart'; +import 'package:dzod_example/examples/complex/tuple_example.dart'; +import 'package:dzod_example/examples/error_handling/error_code_system_example.dart'; +import 'package:dzod_example/examples/error_handling/error_formatting_example.dart'; +import 'package:dzod_example/examples/real_world/authentication_schema_example.dart'; +import 'package:dzod_example/examples/schema_composition/introspection_example.dart'; +import 'package:dzod_example/examples/schema_composition/json_schema_generation_example.dart'; +import 'package:dzod_example/examples/security/security_best_practices_example.dart'; + +Future settle(WidgetTester tester) async { + for (var i = 0; i < 5; i++) { + await tester.pump(const Duration(seconds: 1)); + } + expect(tester.takeException(), isNull); +} + +Future pressButtons(WidgetTester tester) async { + final buttons = tester + .widgetList( + find.byWidgetPredicate((w) => w is ButtonStyleButton)) + .toList(); + for (final button in buttons) { + button.onPressed?.call(); + await settle(tester); + tester + .widget(find.byType(ValidationCard)) + .onValidate + ?.call(); + await settle(tester); + for (final label in ['Check URL', 'Validate Async', 'Validate Securely']) { + final control = find.ancestor( + of: find.text(label), + matching: find.byWidgetPredicate((w) => w is ButtonStyleButton)); + if (control.evaluate().isNotEmpty) { + tester.widget(control).onPressed?.call(); + await settle(tester); + } + } + for (final icon + in tester.widgetList(find.byType(IconButton)).toList()) { + icon.onPressed?.call(); + await settle(tester); + } + } + for (final chip + in tester.widgetList(find.byType(ActionChip)).toList()) { + chip.onPressed?.call(); + await settle(tester); + } + for (final toggle + in tester.widgetList(find.byType(Switch)).toList()) { + toggle.onChanged?.call(!toggle.value); + await settle(tester); + } + for (final radio in tester + .widgetList>(find.byType(RadioGroup)) + .toList()) { + radio.onChanged(true); + await settle(tester); + radio.onChanged(false); + await settle(tester); + } + for (final checkbox in tester + .widgetList(find.byType(CheckboxListTile)) + .toList()) { + checkbox.onChanged?.call(true); + await settle(tester); + checkbox.onChanged?.call(false); + await settle(tester); + } + for (final slider + in tester.widgetList(find.byType(Slider)).toList()) { + slider.onChanged?.call(slider.min); + await settle(tester); + } +} + +void main() { + final examples = [ + const CoercionExample(), + const DiscriminatedUnionExample(), + const PipelineExample(), + const RecursiveSchemaExample(), + const ApiValidationExample(), + const DatabaseValidationExample(), + const BooleanNullExample(), + const NumberValidationsExample(), + const StringValidationExample(), + const UserSchemaExample(), + const ArrayAdvancedExample(), + const EnumExample(), + const ObjectManipulationExample(), + const RecordExample(), + const TupleExample(), + const ErrorCodeSystemExample(), + const ErrorFormattingExample(), + const AuthenticationSchemaExample(), + const IntrospectionExample(), + const JsonSchemaGenerationExample(), + const SecurityBestPracticesExample(), + ]; + + for (final example in examples) { + testWidgets( + '${example.runtimeType} renders and validates every selectable example', + (tester) async { + tester.view.physicalSize = const Size(1600, 1600); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.resetPhysicalSize); + addTearDown(tester.view.resetDevicePixelRatio); + await tester.pumpWidget(MaterialApp( + home: Scaffold(body: SingleChildScrollView(child: example)))); + await settle(tester); + expect(find.byType(ValidationCard), findsOneWidget); + expect(find.byType(SchemaDisplay), findsWidgets); + await pressButtons(tester); + final dropdownCount = + find.byWidgetPredicate((w) => w is DropdownButton).evaluate().length; + for (var index = 0; index < dropdownCount; index++) { + dynamic dropdown = tester.widget( + find.byWidgetPredicate((w) => w is DropdownButton).at(index)); + final values = + (dropdown.items as List).map((dynamic item) => item.value).toList(); + for (final value in values) { + dropdown = tester.widget( + find.byWidgetPredicate((w) => w is DropdownButton).at(index)); + dropdown.onChanged(value); + await settle(tester); + expect(find.byType(ValidationCard), findsOneWidget); + await pressButtons(tester); + final nestedDropdowns = tester + .widgetList(find.byWidgetPredicate((w) => w is DropdownButton)) + .toList(); + for (final nested in nestedDropdowns.skip(1)) { + final dynamic control = nested; + for (final dynamic item in control.items) { + control.onChanged(item.value); + await settle(tester); + } + } + final fields = tester + .widgetList(find.byType(TextFormField)) + .toList(); + for (final field in fields) { + field.controller?.text = 'invalid input'; + field.onChanged?.call('invalid input'); + field.validator?.call('invalid input'); + } + await settle(tester); + await pressButtons(tester); + } + } + for (final toggle in tester + .widgetList(find.byType(SwitchListTile)) + .toList()) { + toggle.onChanged?.call(!toggle.value); + await settle(tester); + } + for (final field in tester + .widgetList(find.byType(TextFormField)) + .toList()) { + field.controller?.text = 'invalid input'; + field.onChanged?.call('invalid input'); + field.validator?.call('invalid input'); + } + await settle(tester); + await pressButtons(tester); + expect(find.byType(ValidationCard), findsOneWidget); + await tester.pumpWidget(const SizedBox.shrink()); + await settle(tester); + }); + } + + testWidgets('record editor adds numeric values and rejects invalid values', + (tester) async { + tester.view.physicalSize = const Size(1600, 1600); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.resetPhysicalSize); + addTearDown(tester.view.resetDevicePixelRatio); + await tester.pumpWidget(const MaterialApp( + home: Scaffold(body: SingleChildScrollView(child: RecordExample())))); + final fields = + tester.widgetList(find.byType(TextFormField)).toList(); + fields[0].controller!.text = 'count'; + fields[1].controller!.text = '42'; + await tester.tap(find.byTooltip('Add entry')); + await settle(tester); + expect(find.text('count: 42'), findsOneWidget); + expect(fields[0].controller!.text, isEmpty); + expect(fields[1].controller!.text, isEmpty); + }); + + testWidgets('app entry point builds the showcase', (tester) async { + tester.view.physicalSize = const Size(1600, 1600); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.resetPhysicalSize); + addTearDown(tester.view.resetDevicePixelRatio); + app.main(); + await tester.pumpAndSettle(); + expect(find.byType(app.ValidationShowcasePage), findsOneWidget); + dynamic categories = + tester.widget(find.byWidgetPredicate((w) => w is DropdownButton).first); + final values = + (categories.items as List).map((dynamic item) => item.value).toList(); + for (final value in values) { + categories = tester + .widget(find.byWidgetPredicate((w) => w is DropdownButton).first); + categories.onChanged(value); + await settle(tester); + for (final tile + in tester.widgetList(find.byType(ListTile)).toList()) { + tile.onTap?.call(); + await settle(tester); + } + } + }); +} diff --git a/packages/dzod/lib/dzod.dart b/packages/dzod/lib/dzod.dart index 7593eb6..7aa3f6e 100644 --- a/packages/dzod/lib/dzod.dart +++ b/packages/dzod/lib/dzod.dart @@ -1,4 +1,3 @@ -// coverage:ignore-file // ignore_for_file: unused_import library; diff --git a/packages/dzod/lib/src/core/error_utils.dart b/packages/dzod/lib/src/core/error_utils.dart index 6ac8768..ad6b2be 100644 --- a/packages/dzod/lib/src/core/error_utils.dart +++ b/packages/dzod/lib/src/core/error_utils.dart @@ -12,9 +12,7 @@ typedef ErrorGroupingFunction = T Function(ValidationError error); typedef ErrorComparator = int Function(ValidationError a, ValidationError b); /// Comprehensive error filtering and grouping utilities -class ErrorUtils { - const ErrorUtils._(); - +abstract final class ErrorUtils { // ==================== FILTERING UTILITIES ==================== /// Filters errors by error code diff --git a/packages/dzod/lib/src/core/json_schema.dart b/packages/dzod/lib/src/core/json_schema.dart index 5e34121..8f46e88 100644 --- a/packages/dzod/lib/src/core/json_schema.dart +++ b/packages/dzod/lib/src/core/json_schema.dart @@ -143,11 +143,6 @@ class JsonSchemaGenerator { ...schemaMap, }; - // Add definitions if any were collected - if (context.definitions.isNotEmpty) { - result['definitions'] = context.definitions; - } - return result; } @@ -331,9 +326,6 @@ class JsonSchemaGenerator { if (constraints['exclusiveMaximum'] != null) { result['exclusiveMaximum'] = constraints['exclusiveMaximum']; } - if (constraints['multipleOf'] != null) { - result['multipleOf'] = constraints['multipleOf']; - } } catch (e) { // Ignore constraint extraction errors } @@ -563,46 +555,22 @@ class JsonSchemaGenerator { /// Generate JSON Schema for UnionSchema Map _generateUnionSchema( UnionSchema schema, JsonSchemaContext context) { - try { - // Union schemas use anyOf in JSON Schema - // Since we can't access private _schemas field directly, we'll use a fallback - // In a real implementation, you'd need access to the schemas list - return { - 'anyOf': [ - // This would need actual access to schema._schemas - // schema._schemas.map((s) => _generateSchema(s, context)).toList() - ], - 'description': 'Union schema - requires access to constituent schemas', - }; - } catch (e) { - // Fallback - return { - 'description': 'Union schema (schemas not accessible)', - }; - } + // Constituent schemas are not exposed yet. + return { + 'anyOf': >[], + 'description': 'Union schema - requires access to constituent schemas', + }; } /// Generate JSON Schema for IntersectionSchema Map _generateIntersectionSchema( IntersectionSchema schema, JsonSchemaContext context) { - try { - // Intersection schemas use allOf in JSON Schema - // Since we can't access private _schemas field directly, we'll use a fallback - // In a real implementation, you'd need access to the schemas list - return { - 'allOf': [ - // This would need actual access to schema._schemas - // schema._schemas.map((s) => _generateSchema(s, context)).toList() - ], - 'description': - 'Intersection schema - requires access to constituent schemas', - }; - } catch (e) { - // Fallback - return { - 'description': 'Intersection schema (schemas not accessible)', - }; - } + // Constituent schemas are not exposed yet. + return { + 'allOf': >[], + 'description': + 'Intersection schema - requires access to constituent schemas', + }; } } diff --git a/packages/dzod/lib/src/core/parser.dart b/packages/dzod/lib/src/core/parser.dart index 2696bb3..c37c05a 100644 --- a/packages/dzod/lib/src/core/parser.dart +++ b/packages/dzod/lib/src/core/parser.dart @@ -3,9 +3,7 @@ import 'schema.dart'; import 'validation_result.dart'; /// Utility class for parsing and validation operations -class Parser { - const Parser._(); - +abstract final class Parser { /// Parses a value using a schema, throwing an exception if validation fails static T parse(Schema schema, dynamic input, [List path = const []]) { diff --git a/packages/dzod/lib/src/core/schema.dart b/packages/dzod/lib/src/core/schema.dart index 45f35ca..3d5eba8 100644 --- a/packages/dzod/lib/src/core/schema.dart +++ b/packages/dzod/lib/src/core/schema.dart @@ -446,7 +446,7 @@ class AsyncRefineSchema extends Schema { @override Future> validateAsync(dynamic input, [List path = const []]) async { - final result = _schema.validate(input, path); + final result = await _schema.validateAsync(input, path); if (result.isSuccess) { final value = result.data as T; final isValid = await _validator(value); diff --git a/packages/dzod/lib/src/core/schema_composition.dart b/packages/dzod/lib/src/core/schema_composition.dart index 63cbddf..438923d 100644 --- a/packages/dzod/lib/src/core/schema_composition.dart +++ b/packages/dzod/lib/src/core/schema_composition.dart @@ -532,9 +532,7 @@ class _AnySchema extends Schema { } /// Utility functions for schema composition -class SchemaUtils { - const SchemaUtils._(); - +abstract final class SchemaUtils { /// Create a union of multiple schemas static Schema union(List> schemas) { return const SchemaComposer().union(schemas); diff --git a/packages/dzod/lib/src/schemas/advanced/coercion_schema.dart b/packages/dzod/lib/src/schemas/advanced/coercion_schema.dart index d1b703a..fc87a14 100644 --- a/packages/dzod/lib/src/schemas/advanced/coercion_schema.dart +++ b/packages/dzod/lib/src/schemas/advanced/coercion_schema.dart @@ -226,9 +226,7 @@ class CoercionSchema extends Schema { } /// Coercion utilities for common type conversions -class CoercionUtils { - const CoercionUtils._(); - +abstract final class CoercionUtils { /// Coerces input to string with advanced parsing options static String coerceToString( dynamic input, { @@ -458,9 +456,7 @@ class CoercionUtils { ); if (numValue is int) return numValue; - if (numValue is double) return numValue.round(); - - throw FormatException('Cannot convert $numValue to integer'); + return numValue.round(); } /// Coerces input to double with advanced precision and validation @@ -485,10 +481,7 @@ class CoercionUtils { strict: strict, ); - if (numValue is double) return numValue; - if (numValue is int) return numValue.toDouble(); - - throw FormatException('Cannot convert $numValue to double'); + return numValue.toDouble(); } /// Coerces input to boolean diff --git a/packages/dzod/lib/src/schemas/collections/array_schema.dart b/packages/dzod/lib/src/schemas/collections/array_schema.dart index 7699168..9f634b0 100644 --- a/packages/dzod/lib/src/schemas/collections/array_schema.dart +++ b/packages/dzod/lib/src/schemas/collections/array_schema.dart @@ -65,7 +65,7 @@ class ArraySchema extends Schema> { ); } - if (_minLength != null && array.length < _minLength!) { + if (_minLength != null && array.length < _minLength) { return ValidationResult.failure( ValidationErrorCollection.single( ValidationError.constraintViolation( @@ -79,7 +79,7 @@ class ArraySchema extends Schema> { ); } - if (_maxLength != null && array.length > _maxLength!) { + if (_maxLength != null && array.length > _maxLength) { return ValidationResult.failure( ValidationErrorCollection.single( ValidationError.constraintViolation( @@ -161,7 +161,7 @@ class ArraySchema extends Schema> { ); } - if (_minLength != null && array.length < _minLength!) { + if (_minLength != null && array.length < _minLength) { return ValidationResult.failure( ValidationErrorCollection.single( ValidationError.constraintViolation( @@ -175,7 +175,7 @@ class ArraySchema extends Schema> { ); } - if (_maxLength != null && array.length > _maxLength!) { + if (_maxLength != null && array.length > _maxLength) { return ValidationResult.failure( ValidationErrorCollection.single( ValidationError.constraintViolation( diff --git a/packages/dzod/lib/src/schemas/collections/record_schema.dart b/packages/dzod/lib/src/schemas/collections/record_schema.dart index 7db176a..fc76738 100644 --- a/packages/dzod/lib/src/schemas/collections/record_schema.dart +++ b/packages/dzod/lib/src/schemas/collections/record_schema.dart @@ -62,7 +62,7 @@ class RecordSchema extends Schema> { Map inputMap = input; // Length validations - if (_minEntries != null && inputMap.length < _minEntries!) { + if (_minEntries != null && inputMap.length < _minEntries) { return ValidationResult.failure( ValidationErrorCollection.single( ValidationError.constraintViolation( @@ -76,7 +76,7 @@ class RecordSchema extends Schema> { ); } - if (_maxEntries != null && inputMap.length > _maxEntries!) { + if (_maxEntries != null && inputMap.length > _maxEntries) { return ValidationResult.failure( ValidationErrorCollection.single( ValidationError.constraintViolation( @@ -95,7 +95,7 @@ class RecordSchema extends Schema> { // Check required keys if (_requiredKeys != null) { - for (final requiredKey in _requiredKeys!) { + for (final requiredKey in _requiredKeys) { if (!inputMap.containsKey(requiredKey)) { errors.add( ValidationError.constraintViolation( @@ -124,7 +124,7 @@ class RecordSchema extends Schema> { // Validate key K validatedKey; if (_keySchema != null) { - final keyResult = _keySchema!.validate(key, [...path, 'key']); + final keyResult = _keySchema.validate(key, [...path, 'key']); if (keyResult.isSuccess) { validatedKey = keyResult.data as K; } else { @@ -164,7 +164,7 @@ class RecordSchema extends Schema> { // Validate value V validatedValue; if (_valueSchema != null) { - final valueResult = _valueSchema!.validate( + final valueResult = _valueSchema.validate( value, [...path, validatedKey.toString()], ); diff --git a/packages/dzod/lib/src/schemas/collections/tuple_schema.dart b/packages/dzod/lib/src/schemas/collections/tuple_schema.dart index 3f699f1..c1fe3d4 100644 --- a/packages/dzod/lib/src/schemas/collections/tuple_schema.dart +++ b/packages/dzod/lib/src/schemas/collections/tuple_schema.dart @@ -89,7 +89,7 @@ class TupleSchema> extends Schema { // Validate rest elements if rest schema exists if (_restSchema != null) { for (int i = _elementSchemas.length; i < array.length; i++) { - final elementResult = _restSchema!.validate( + final elementResult = _restSchema.validate( array[i], [...path, i.toString()], ); diff --git a/packages/dzod/lib/src/schemas/object/object_schema.dart b/packages/dzod/lib/src/schemas/object/object_schema.dart index 90e9f11..2165225 100644 --- a/packages/dzod/lib/src/schemas/object/object_schema.dart +++ b/packages/dzod/lib/src/schemas/object/object_schema.dart @@ -131,7 +131,7 @@ class ObjectSchema extends Schema> { case ObjectMode.passthrough: if (_catchall != null) { - final catchallResult = _catchall!.validate(value, [...path, key]); + final catchallResult = _catchall.validate(value, [...path, key]); if (catchallResult.isSuccess) { result[key] = catchallResult.data; } else { @@ -235,7 +235,7 @@ class ObjectSchema extends Schema> { case ObjectMode.passthrough: if (_catchall != null) { final catchallResult = - await _catchall!.validateAsync(value, [...path, key]); + await _catchall.validateAsync(value, [...path, key]); if (catchallResult.isSuccess) { result[key] = catchallResult.data; } else { diff --git a/packages/dzod/lib/src/schemas/primitive/number_schema.dart b/packages/dzod/lib/src/schemas/primitive/number_schema.dart index 74beba7..3462670 100644 --- a/packages/dzod/lib/src/schemas/primitive/number_schema.dart +++ b/packages/dzod/lib/src/schemas/primitive/number_schema.dart @@ -94,7 +94,7 @@ class NumberSchema extends Schema { } // Range validations - if (_min != null && value < _min!) { + if (_min != null && value < _min) { return ValidationResult.failure( ValidationErrorCollection.single( ValidationError.constraintViolation( @@ -108,7 +108,7 @@ class NumberSchema extends Schema { ); } - if (_max != null && value > _max!) { + if (_max != null && value > _max) { return ValidationResult.failure( ValidationErrorCollection.single( ValidationError.constraintViolation( diff --git a/packages/dzod/lib/src/schemas/primitive/string_schema.dart b/packages/dzod/lib/src/schemas/primitive/string_schema.dart index 0059127..b2fc03d 100644 --- a/packages/dzod/lib/src/schemas/primitive/string_schema.dart +++ b/packages/dzod/lib/src/schemas/primitive/string_schema.dart @@ -104,7 +104,7 @@ class StringSchema extends Schema { ); } - if (_minLength != null && value.length < _minLength!) { + if (_minLength != null && value.length < _minLength) { return ValidationResult.failure( ValidationErrorCollection.single( ValidationError.constraintViolation( @@ -118,7 +118,7 @@ class StringSchema extends Schema { ); } - if (_maxLength != null && value.length > _maxLength!) { + if (_maxLength != null && value.length > _maxLength) { return ValidationResult.failure( ValidationErrorCollection.single( ValidationError.constraintViolation( @@ -133,15 +133,15 @@ class StringSchema extends Schema { } // Pattern validation - if (_pattern != null && !_pattern!.hasMatch(value)) { + if (_pattern != null && !_pattern.hasMatch(value)) { return ValidationResult.failure( ValidationErrorCollection.single( ValidationError.constraintViolation( path: path, received: value, - constraint: 'match pattern ${_pattern!.pattern}', + constraint: 'match pattern ${_pattern.pattern}', code: 'pattern_mismatch', - context: {'pattern': _pattern!.pattern}, + context: {'pattern': _pattern.pattern}, ), ), ); @@ -373,30 +373,30 @@ class StringSchema extends Schema { } /// Checks if string starts with the given prefix - StringSchema startsWith(String prefix) { + Schema startsWith(String prefix) { return refine( (value) => value.startsWith(prefix), message: 'must start with "$prefix"', code: 'starts_with', - ) as StringSchema; + ); } /// Checks if string ends with the given suffix - StringSchema endsWith(String suffix) { + Schema endsWith(String suffix) { return refine( (value) => value.endsWith(suffix), message: 'must end with "$suffix"', code: 'ends_with', - ) as StringSchema; + ); } /// Checks if string contains the given substring - StringSchema contains(String substring) { + Schema contains(String substring) { return refine( (value) => value.contains(substring), message: 'must contain "$substring"', code: 'contains', - ) as StringSchema; + ); } /// Checks if string is non-empty @@ -409,21 +409,21 @@ class StringSchema extends Schema { } /// Checks if string is a valid date - StringSchema date() { + Schema date() { return refine( (value) => DateTime.tryParse(value) != null, message: 'must be a valid date', code: 'invalid_date', - ) as StringSchema; + ); } /// Checks if string is a valid datetime - StringSchema datetime() { + Schema datetime() { return refine( (value) => DateTime.tryParse(value) != null, message: 'must be a valid datetime', code: 'invalid_datetime', - ) as StringSchema; + ); } /// Checks if string is a valid IP address @@ -837,7 +837,7 @@ class StringSchema extends Schema { if (_minLength != null) constraints.add('min: $_minLength'); if (_maxLength != null) constraints.add('max: $_maxLength'); if (_exactLength != null) constraints.add('length: $_exactLength'); - if (_pattern != null) constraints.add('pattern: ${_pattern!.pattern}'); + if (_pattern != null) constraints.add('pattern: ${_pattern.pattern}'); if (_isEmail) constraints.add('email'); if (_isUrl) constraints.add('url'); if (_isUuid) constraints.add('uuid'); diff --git a/packages/dzod/pubspec.yaml b/packages/dzod/pubspec.yaml index 126c378..cbed978 100644 --- a/packages/dzod/pubspec.yaml +++ b/packages/dzod/pubspec.yaml @@ -7,7 +7,7 @@ repository: https://github.com/nonstopio/flutter_forge/tree/main/packages/dzod issue_tracker: https://github.com/nonstopio/flutter_forge/issues environment: - sdk: '>=3.0.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' flutter: ">=1.17.0" dependencies: @@ -17,8 +17,10 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^2.0.3 + flutter_lints: ^6.0.0 test: ^1.24.0 mockito: ^5.4.0 -# No flutter-specific assets needed for this package +# No flutter-specific assets needed for this package + +resolution: workspace diff --git a/packages/dzod/test/remaining_behavior_test.dart b/packages/dzod/test/remaining_behavior_test.dart new file mode 100644 index 0000000..6a7725b --- /dev/null +++ b/packages/dzod/test/remaining_behavior_test.dart @@ -0,0 +1,126 @@ +import 'package:dzod/dzod.dart'; +import 'package:test/test.dart'; + +class _ThrowingString { + int attempts = 0; + + @override + String toString() { + if (attempts++ == 0) throw const FormatException('first conversion failed'); + return 'recovered'; + } +} + +class _ExclusiveNumber extends NumberSchema { + @override + num get exclusiveMinimum => 1; + + @override + num get exclusiveMaximum => 10; +} + +void main() { + test('async refinement awaits the preceding async transformation', () async { + final schema = z + .string() + .transformAsync((value) async => value.length) + .refineAsync((length) async => length > 2, message: 'too short'); + expect((await schema.validateAsync('hello')).data, 5); + expect((await schema.validateAsync('a')).errors!.first!.message, + contains('too short')); + expect((await schema.validateAsync(12)).isFailure, isTrue); + }); + + test('content refinements validate their actual public APIs', () { + final cases = <(Schema, String, String, String)>[ + (z.string().startsWith('pre'), 'prefix', 'suffix', 'starts_with'), + (z.string().endsWith('fix'), 'suffix', 'other', 'ends_with'), + (z.string().contains('middle'), 'a middle b', 'other', 'contains'), + (z.string().date(), '2025-01-01', 'invalid', 'invalid_date'), + ( + z.string().datetime(), + '2025-01-01T12:30:00', + 'invalid', + 'invalid_datetime' + ), + ]; + for (final (schema, valid, invalid, code) in cases) { + expect(schema.parse(valid), valid); + expect(schema.validate(invalid).errors!.first!.code, code); + expect(schema.validate(42).isFailure, isTrue); + } + expect(z.string().min(4).startsWith('a').validate('a').isFailure, isTrue); + }); + + test('exact constraint getters describe the configured constraints', () { + expect(z.string().length(3).exactLength, 3); + expect(z.array(z.string()).length(2).exactItems, 2); + expect(z.number().exact(7).exactValue, 7); + }); + + test('JSON generation respects exclusive numeric constraints', () { + expect( + _ExclusiveNumber().toJsonSchema(), containsPair('exclusiveMinimum', 1)); + expect(_ExclusiveNumber().toJsonSchema(), + containsPair('exclusiveMaximum', 10)); + }); + + test('pipeline factory preserves metadata and validates all stages', () { + final pipeline = PipelineExtension.pipeline( + [z.string().trim(), z.string().min(3)], + description: 'trim then validate', + metadata: {'purpose': 'test'}, + ); + expect(pipeline.parse(' abc '), 'abc'); + expect(pipeline.validate(' a ').isFailure, isTrue); + expect(pipeline.description, 'trim then validate'); + expect(pipeline.metadata, {'purpose': 'test'}); + expect(() => PipelineExtension.pipeline([]), throwsArgumentError); + }); + + test( + 'string fallback tries strategies in order and retries ultimate conversion', + () { + final attempts = []; + expect( + CoercionUtils.coerceToStringAdvanced( + _ThrowingString(), + fallbackStrategies: [ + (_) { + attempts.add('first'); + throw const FormatException(); + }, + (_) { + attempts.add('second'); + return 'fallback'; + }, + ], + ), + 'fallback', + ); + expect(attempts, ['first', 'second']); + expect( + CoercionUtils.coerceToStringAdvanced(_ThrowingString()), 'recovered'); + expect( + () => CoercionUtils.coerceToStringAdvanced(_ThrowingString(), + strict: true), + throwsFormatException); + }); + + test('number and generic coercion continue after a failing fallback', () { + expect( + CoercionUtils.coerceToNumberAdvanced('bad', fallbackStrategies: [ + (_) => throw const FormatException(), + (_) => 12, + ]), + 12); + expect( + CoercionUtils.coerceToType('bad', + primaryCoercer: (_) => throw const FormatException(), + fallbackStrategies: [ + (_) => throw const FormatException(), + (_) => 13, + ]), + 13); + }); +} diff --git a/packages/dzod/test/schemas/primitive/string_schema_test.dart b/packages/dzod/test/schemas/primitive/string_schema_test.dart index 7d7a654..471fc52 100644 --- a/packages/dzod/test/schemas/primitive/string_schema_test.dart +++ b/packages/dzod/test/schemas/primitive/string_schema_test.dart @@ -301,19 +301,7 @@ void main() { group('String content validation', () { test('should validate strings that start with prefix', () { final baseSchema = z.string(); - try { - // This will throw due to incorrect cast, but we need coverage - baseSchema.startsWith('hello'); - } catch (e) { - // Expected due to type cast issue - } - - // Test the functionality manually to ensure it works - final startsWithSchema = baseSchema.refine( - (value) => value.startsWith('hello'), - message: 'must start with "hello"', - code: 'starts_with', - ); + final startsWithSchema = baseSchema.startsWith('hello'); expect(startsWithSchema.validate('hello world').isSuccess, true); expect(startsWithSchema.validate('hello').isSuccess, true); @@ -325,19 +313,7 @@ void main() { test('should validate strings that end with suffix', () { final baseSchema = z.string(); - try { - // This will throw due to incorrect cast, but we need coverage - baseSchema.endsWith('world'); - } catch (e) { - // Expected due to type cast issue - } - - // Test the functionality manually to ensure it works - final endsWithSchema = baseSchema.refine( - (value) => value.endsWith('world'), - message: 'must end with "world"', - code: 'ends_with', - ); + final endsWithSchema = baseSchema.endsWith('world'); expect(endsWithSchema.validate('hello world').isSuccess, true); expect(endsWithSchema.validate('world').isSuccess, true); @@ -349,19 +325,7 @@ void main() { test('should validate strings that contain substring', () { final baseSchema = z.string(); - try { - // This will throw due to incorrect cast, but we need coverage - baseSchema.contains('test'); - } catch (e) { - // Expected due to type cast issue - } - - // Test the functionality manually to ensure it works - final containsSchema = baseSchema.refine( - (value) => value.contains('test'), - message: 'must contain "test"', - code: 'contains', - ); + final containsSchema = baseSchema.contains('test'); expect(containsSchema.validate('testing').isSuccess, true); expect(containsSchema.validate('test').isSuccess, true); @@ -374,19 +338,7 @@ void main() { test('should validate date strings', () { final baseSchema = z.string(); - try { - // This will throw due to incorrect cast, but we need coverage - baseSchema.date(); - } catch (e) { - // Expected due to type cast issue - } - - // Test the functionality manually to ensure it works - final dateSchema = baseSchema.refine( - (value) => DateTime.tryParse(value) != null, - message: 'must be a valid date', - code: 'invalid_date', - ); + final dateSchema = baseSchema.date(); expect(dateSchema.validate('2023-12-25').isSuccess, true); expect(dateSchema.validate('2023-12-25T10:00:00').isSuccess, true); @@ -399,19 +351,7 @@ void main() { test('should validate datetime strings', () { final baseSchema = z.string(); - try { - // This will throw due to incorrect cast, but we need coverage - baseSchema.datetime(); - } catch (e) { - // Expected due to type cast issue - } - - // Test the functionality manually to ensure it works - final datetimeSchema = baseSchema.refine( - (value) => DateTime.tryParse(value) != null, - message: 'must be a valid datetime', - code: 'invalid_datetime', - ); + final datetimeSchema = baseSchema.datetime(); expect(datetimeSchema.validate('2023-12-25T10:00:00').isSuccess, true); expect(datetimeSchema.validate('2023-12-25').isSuccess, true); @@ -783,18 +723,9 @@ void main() { }); }); - group('Coverage Tests for Cast Issue Methods', () { - // These tests are specifically designed to cover the lambda functions - // that are currently uncovered due to type casting issues - + group('String content refinement regressions', () { test('should execute startsWith validation logic', () { - // Create a test that directly exercises the validation logic in line 378 - final schema = z.string().refine( - (value) => - value.startsWith('hello'), // This matches line 378 exactly - message: 'must start with "hello"', - code: 'starts_with', - ); + final schema = z.string().startsWith('hello'); expect(schema.validate('hello world').isSuccess, true); expect(schema.validate('hello').isSuccess, true); @@ -802,13 +733,7 @@ void main() { }); test('should execute endsWith validation logic', () { - // Create a test that directly exercises the validation logic in line 387 - final schema = z.string().refine( - (value) => - value.endsWith('world'), // This matches line 387 exactly - message: 'must end with "world"', - code: 'ends_with', - ); + final schema = z.string().endsWith('world'); expect(schema.validate('hello world').isSuccess, true); expect(schema.validate('world').isSuccess, true); @@ -816,13 +741,7 @@ void main() { }); test('should execute contains validation logic', () { - // Create a test that directly exercises the validation logic in line 396 - final schema = z.string().refine( - (value) => - value.contains('test'), // This matches line 396 exactly - message: 'must contain "test"', - code: 'contains', - ); + final schema = z.string().contains('test'); expect(schema.validate('testing').isSuccess, true); expect(schema.validate('test').isSuccess, true); @@ -830,14 +749,7 @@ void main() { }); test('should execute date validation logic', () { - // Create a test that directly exercises the validation logic in line 414 - final schema = z.string().refine( - (value) => - DateTime.tryParse(value) != - null, // This matches line 414 exactly - message: 'must be a valid date', - code: 'invalid_date', - ); + final schema = z.string().date(); expect(schema.validate('2023-12-25').isSuccess, true); expect(schema.validate('2023-12-25T10:00:00').isSuccess, true); @@ -845,14 +757,7 @@ void main() { }); test('should execute datetime validation logic', () { - // Create a test that directly exercises the validation logic in line 423 - final schema = z.string().refine( - (value) => - DateTime.tryParse(value) != - null, // This matches line 423 exactly - message: 'must be a valid datetime', - code: 'invalid_datetime', - ); + final schema = z.string().datetime(); expect(schema.validate('2023-12-25T10:00:00').isSuccess, true); expect(schema.validate('2023-12-25').isSuccess, true); diff --git a/packages/html_rich_text/example/analysis_options.yaml b/packages/html_rich_text/example/analysis_options.yaml index 0d29021..55b180c 100644 --- a/packages/html_rich_text/example/analysis_options.yaml +++ b/packages/html_rich_text/example/analysis_options.yaml @@ -7,6 +7,9 @@ # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. +analyzer: + exclude: + - build/** include: package:flutter_lints/flutter.yaml linter: diff --git a/packages/html_rich_text/example/pubspec.yaml b/packages/html_rich_text/example/pubspec.yaml index 2b246b4..4fae48b 100644 --- a/packages/html_rich_text/example/pubspec.yaml +++ b/packages/html_rich_text/example/pubspec.yaml @@ -19,7 +19,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.1.0 environment: - sdk: ">=3.2.2 <4.0.0" + sdk: '>=3.6.0 <4.0.0' # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -45,7 +45,7 @@ dev_dependencies: # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -88,3 +88,5 @@ flutter: # # For details regarding fonts from package dependencies, # see https://flutter.dev/to/font-from-package + +resolution: workspace diff --git a/packages/html_rich_text/example/test/app_test.dart b/packages/html_rich_text/example/test/app_test.dart new file mode 100644 index 0000000..800bb86 --- /dev/null +++ b/packages/html_rich_text/example/test/app_test.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:html_rich_text/html_rich_text.dart'; +import 'package:example_html_rich_text/main.dart' as app; + +void main() { + testWidgets('demo renders every card and responds to all link examples', + (tester) async { + tester.view.physicalSize = const Size(1200, 3000); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.resetPhysicalSize); + addTearDown(tester.view.resetDevicePixelRatio); + app.main(); + await tester.pumpAndSettle(); + expect(find.text('HTML Rich Text Examples'), findsOneWidget); + expect(find.byType(Card), findsNWidgets(12)); + final links = tester + .widgetList(find.byType(HtmlRichText)) + .where((widget) => widget.onLinkTap != null) + .toList(); + expect(links, hasLength(3)); + for (var index = 0; index < links.length; index++) { + links[index].onLinkTap!('https://example.com'); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 300)); + expect( + find.text('${[ + 'Tapped:', + 'Opening:', + 'Link clicked:' + ][index]} https://example.com'), + findsOneWidget); + await tester.pump(const Duration(seconds: 3)); + await tester.pumpAndSettle(); + } + }); +} diff --git a/packages/html_rich_text/pubspec.yaml b/packages/html_rich_text/pubspec.yaml index 8308d68..7ada17f 100644 --- a/packages/html_rich_text/pubspec.yaml +++ b/packages/html_rich_text/pubspec.yaml @@ -9,7 +9,7 @@ screenshots: path: html_rich_text.png environment: - sdk: ">=3.2.2 <4.0.0" + sdk: '>=3.6.0 <4.0.0' dependencies: flutter: @@ -18,7 +18,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -56,3 +56,5 @@ flutter: # # For details regarding fonts in packages, see # https://flutter.dev/to/font-from-package + +resolution: workspace diff --git a/packages/morse_tap/example/analysis_options.yaml b/packages/morse_tap/example/analysis_options.yaml index 0d29021..55b180c 100644 --- a/packages/morse_tap/example/analysis_options.yaml +++ b/packages/morse_tap/example/analysis_options.yaml @@ -7,6 +7,9 @@ # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. +analyzer: + exclude: + - build/** include: package:flutter_lints/flutter.yaml linter: diff --git a/packages/morse_tap/example/lib/main.dart b/packages/morse_tap/example/lib/main.dart index d7ff5b1..401b1ff 100644 --- a/packages/morse_tap/example/lib/main.dart +++ b/packages/morse_tap/example/lib/main.dart @@ -309,7 +309,8 @@ class _MorseTapDetectorExampleState extends State { const SizedBox(height: 16), // Morse tap detector - Expanded( + SizedBox( + height: 220, child: MorseTapDetector( expectedMorseCode: _targets[_currentTarget]!, hapticConfig: _hapticConfig, @@ -474,7 +475,7 @@ class _MorseTextInputExampleState extends State { const SizedBox(height: 16), // Text input mode - Expanded( + SizedBox( child: MorseTextInput( controller: _autoConvert ? _textController : _morseController, autoConvertToText: _autoConvert, @@ -598,7 +599,7 @@ class _StringExtensionExampleState extends State { const SizedBox(height: 16), // Outputs - Expanded( + SizedBox( child: Column( children: [ // Morse code output diff --git a/packages/morse_tap/example/main.dart b/packages/morse_tap/example/main.dart index 0a38ac5..6ff7883 100644 --- a/packages/morse_tap/example/main.dart +++ b/packages/morse_tap/example/main.dart @@ -1,749 +1,6 @@ -import 'package:flutter/material.dart'; -import 'package:morse_tap/morse_tap.dart'; -import 'lib/haptic_config_modal.dart'; +// Keep the original example entrypoint working while sharing the maintained app. +import 'package:morse_tap_example/main.dart' as app; -void main() { - runApp(const MorseTapExampleApp()); -} +export 'package:morse_tap_example/main.dart' hide main; -class MorseTapExampleApp extends StatelessWidget { - const MorseTapExampleApp({super.key}); - - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Morse Tap Example', - theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true), - home: const HomePage(), - ); - } -} - -class HomePage extends StatefulWidget { - const HomePage({super.key}); - - @override - State createState() => _HomePageState(); -} - -class _HomePageState extends State { - int _currentPage = 0; - final PageController _pageController = PageController(); - - final List _pages = [ - const MorseTapDetectorExample(), - const MorseTextInputExample(), - const StringExtensionExample(), - ]; - - final List _pageTitles = [ - 'Tap Detector', - 'Text Input', - 'String Extensions', - ]; - - @override - void dispose() { - _pageController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(_pageTitles[_currentPage]), - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - ), - body: PageView( - controller: _pageController, - onPageChanged: (index) { - setState(() { - _currentPage = index; - }); - }, - children: _pages, - ), - bottomNavigationBar: BottomNavigationBar( - currentIndex: _currentPage, - onTap: (index) { - setState(() { - _currentPage = index; - }); - _pageController.animateToPage( - index, - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - ); - }, - items: const [ - BottomNavigationBarItem( - icon: Icon(Icons.touch_app), - label: 'Tap Detector', - ), - BottomNavigationBarItem( - icon: Icon(Icons.text_fields), - label: 'Text Input', - ), - BottomNavigationBarItem(icon: Icon(Icons.code), label: 'Extensions'), - ], - ), - ); - } -} - -class MorseTapDetectorExample extends StatefulWidget { - const MorseTapDetectorExample({super.key}); - - @override - State createState() => - _MorseTapDetectorExampleState(); -} - -class _MorseTapDetectorExampleState extends State { - String _message = 'Use gestures to input SOS (... --- ...)'; - String _currentTarget = 'SOS'; - Color _buttonColor = Colors.blue; - String _gestureHint = ''; - String _currentSequence = ''; - HapticConfig _hapticConfig = HapticConfig.defaultConfig; - - final Map _targets = { - 'SOS': '... --- ...', - 'HELLO': '.... . .-.. .-.. ---', - 'OK': '--- -.-', - 'YES': '-.-- . ...', - 'NO': '-. ---', - }; - - void _onCorrectSequence() { - setState(() { - _message = '✅ Perfect! You tapped $_currentTarget correctly!'; - _buttonColor = Colors.green; - _gestureHint = ''; - }); - - Future.delayed(const Duration(seconds: 2), () { - if (mounted) { - setState(() { - _message = - 'Use gestures to input $_currentTarget (${_targets[_currentTarget]})'; - _buttonColor = Colors.blue; - }); - } - }); - } - - void _onIncorrectSequence() { - setState(() { - _message = '❌ Wrong sequence. Try again!'; - _buttonColor = Colors.red; - _gestureHint = ''; - }); - - Future.delayed(const Duration(seconds: 1), () { - if (mounted) { - setState(() { - _message = - 'Use gestures to input $_currentTarget (${_targets[_currentTarget]})'; - _buttonColor = Colors.blue; - }); - } - }); - } - - void _onTimeout() { - setState(() { - _message = '⏰ Sequence timed out. Try again!'; - _buttonColor = Colors.orange; - _gestureHint = ''; - }); - - Future.delayed(const Duration(seconds: 1), () { - if (mounted) { - setState(() { - _message = - 'Use gestures to input $_currentTarget (${_targets[_currentTarget]})'; - _buttonColor = Colors.blue; - }); - } - }); - } - - void _onDotAdded() { - setState(() { - _gestureHint = 'Added dot (•)'; - }); - } - - void _onDashAdded() { - setState(() { - _gestureHint = 'Added dash (—)'; - }); - } - - void _onSpaceAdded() { - setState(() { - _gestureHint = 'Added space'; - }); - } - - void _showHapticConfig() async { - final newConfig = await HapticConfigModal.show( - context, - initialConfig: _hapticConfig, - ); - - if (newConfig != null) { - setState(() { - _hapticConfig = newConfig; - }); - } - } - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Card( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - 'Morse Tap Detector', - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 8), - const Text( - 'Tap the button below using Morse code patterns. Short taps are dots (•), long taps are dashes (—).', - ), - const SizedBox(height: 16), - Text( - 'Target: $_currentTarget', - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - Text( - 'Morse: ${_targets[_currentTarget]}', - style: const TextStyle( - fontSize: 14, - fontFamily: 'monospace', - ), - ), - if (_currentSequence.isNotEmpty) ...[ - const SizedBox(height: 8), - Text( - 'Current: $_currentSequence', - style: TextStyle( - fontSize: 14, - fontFamily: 'monospace', - color: - _targets[_currentTarget]!.startsWith( - _currentSequence, - ) - ? Colors.green - : Colors.orange, - ), - ), - ], - ], - ), - ), - ), - - const SizedBox(height: 16), - - // Target selection - SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: _targets.keys.map((target) { - final isSelected = target == _currentTarget; - return Padding( - padding: const EdgeInsets.only(right: 8.0), - child: FilterChip( - label: Text(target), - selected: isSelected, - onSelected: (selected) { - if (selected) { - setState(() { - _currentTarget = target; - _message = - 'Use gestures to input $target (${_targets[target]})'; - _buttonColor = Colors.blue; - _gestureHint = ''; - _currentSequence = ''; - }); - } - }, - ), - ); - }).toList(), - ), - ), - - const SizedBox(height: 16), - - // Haptic configuration button - Row( - children: [ - Expanded( - child: OutlinedButton.icon( - onPressed: _showHapticConfig, - icon: const Icon(Icons.vibration), - label: Text( - 'Haptic Settings ${_hapticConfig.enabled ? "(Enabled)" : "(Disabled)"}', - ), - ), - ), - ], - ), - - const SizedBox(height: 24), - - // Morse tap detector - Expanded( - child: MorseTapDetector( - expectedMorseCode: _targets[_currentTarget]!, - hapticConfig: _hapticConfig, - onCorrectSequence: _onCorrectSequence, - onIncorrectSequence: _onIncorrectSequence, - onInputTimeout: _onTimeout, - onSequenceChange: (sequence) { - setState(() { - _currentSequence = sequence; - }); - }, - onDotAdded: _onDotAdded, - onDashAdded: _onDashAdded, - onSpaceAdded: _onSpaceAdded, - child: AnimatedContainer( - duration: const Duration(milliseconds: 300), - decoration: BoxDecoration( - color: _buttonColor, - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: _buttonColor.withValues(alpha: 0.3), - blurRadius: 8, - offset: const Offset(0, 4), - ), - ], - ), - child: const Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.touch_app, size: 48, color: Colors.white), - SizedBox(height: 12), - Text( - 'TAP HERE', - style: TextStyle( - color: Colors.white, - fontSize: 24, - fontWeight: FontWeight.bold, - ), - ), - SizedBox(height: 8), - Text( - '1 tap = • | 2 taps = — | Hold = space', - style: TextStyle(color: Colors.white70, fontSize: 14), - ), - ], - ), - ), - ), - ), - ), - - const SizedBox(height: 16), - - // Status message - Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.grey[100], - borderRadius: BorderRadius.circular(8), - ), - child: Column( - children: [ - Text( - _message, - textAlign: TextAlign.center, - style: const TextStyle(fontSize: 16), - ), - if (_gestureHint.isNotEmpty) ...[ - const SizedBox(height: 8), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 4, - ), - decoration: BoxDecoration( - color: Colors.blue[50], - border: Border.all(color: Colors.blue[200]!), - borderRadius: BorderRadius.circular(16), - ), - child: Text( - _gestureHint, - style: TextStyle( - fontSize: 12, - color: Colors.blue[700], - fontWeight: FontWeight.w500, - ), - ), - ), - ], - ], - ), - ), - ], - ), - ); - } -} - -class MorseTextInputExample extends StatefulWidget { - const MorseTextInputExample({super.key}); - - @override - State createState() => _MorseTextInputExampleState(); -} - -class _MorseTextInputExampleState extends State { - final TextEditingController _textController = TextEditingController(); - final TextEditingController _morseController = TextEditingController(); - bool _autoConvert = true; - - @override - void dispose() { - _textController.dispose(); - _morseController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Card( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - 'Morse Text Input', - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 8), - const Text( - 'Tap in the input area to create Morse code. The widget converts your taps to text in real-time.', - ), - const SizedBox(height: 12), - Row( - children: [ - Checkbox( - value: _autoConvert, - onChanged: (value) { - setState(() { - _autoConvert = value ?? true; - }); - }, - ), - const Text('Auto-convert to text'), - ], - ), - ], - ), - ), - ), - - const SizedBox(height: 16), - - // Text input mode - Expanded( - child: MorseTextInput( - controller: _autoConvert ? _textController : _morseController, - autoConvertToText: _autoConvert, - showMorsePreview: true, - onTextChanged: (text) { - // Optional: handle text changes - }, - decoration: InputDecoration( - labelText: _autoConvert ? 'Converted Text' : 'Morse Code', - helperText: _autoConvert - ? 'Text appears here as you tap' - : 'Raw Morse code appears here', - ), - ), - ), - - const SizedBox(height: 16), - - // Instructions - Card( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - 'Instructions:', - style: TextStyle(fontWeight: FontWeight.bold), - ), - const SizedBox(height: 8), - const Text('• Single tap = dot (•)'), - const Text('• Double tap = dash (—)'), - const Text('• Long press = space between letters'), - const Text('• Auto-completion after 1.2 seconds'), - const SizedBox(height: 12), - const Text( - 'Try tapping "HELLO" or "SOS"!', - style: TextStyle( - fontWeight: FontWeight.w500, - fontStyle: FontStyle.italic, - ), - ), - ], - ), - ), - ), - ], - ), - ); - } -} - -class StringExtensionExample extends StatefulWidget { - const StringExtensionExample({super.key}); - - @override - State createState() => _StringExtensionExampleState(); -} - -class _StringExtensionExampleState extends State { - final TextEditingController _inputController = TextEditingController( - text: 'HELLO WORLD', - ); - String _morseOutput = ''; - String _backToText = ''; - - @override - void initState() { - super.initState(); - _updateOutputs(); - _inputController.addListener(_updateOutputs); - } - - @override - void dispose() { - _inputController.dispose(); - super.dispose(); - } - - void _updateOutputs() { - final input = _inputController.text; - setState(() { - _morseOutput = input.toMorseCode(); - _backToText = _morseOutput.fromMorseCode(); - }); - } - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Card( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: const Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'String Extensions', - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - ), - SizedBox(height: 8), - Text( - 'Demonstrates the extension methods available on String for Morse code conversion.', - ), - ], - ), - ), - ), - - const SizedBox(height: 16), - - // Input field - TextField( - controller: _inputController, - decoration: const InputDecoration( - labelText: 'Input Text', - border: OutlineInputBorder(), - helperText: 'Type any text to see the Morse code conversion', - ), - textCapitalization: TextCapitalization.characters, - ), - - const SizedBox(height: 16), - - // Outputs - Expanded( - child: Column( - children: [ - // Morse code output - Card( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - const Icon(Icons.code, size: 16), - const SizedBox(width: 8), - const Text( - '.toMorseCode()', - style: TextStyle( - fontFamily: 'monospace', - fontWeight: FontWeight.bold, - ), - ), - ], - ), - const SizedBox(height: 8), - Container( - width: double.infinity, - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Colors.grey[100], - borderRadius: BorderRadius.circular(8), - ), - child: SelectableText( - _morseOutput.isEmpty - ? 'Enter text above...' - : _morseOutput, - style: const TextStyle( - fontFamily: 'monospace', - fontSize: 16, - ), - ), - ), - ], - ), - ), - ), - - const SizedBox(height: 12), - - // Back to text output - Card( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - const Icon(Icons.text_fields, size: 16), - const SizedBox(width: 8), - const Text( - '.fromMorseCode()', - style: TextStyle( - fontFamily: 'monospace', - fontWeight: FontWeight.bold, - ), - ), - ], - ), - const SizedBox(height: 8), - Container( - width: double.infinity, - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Colors.grey[100], - borderRadius: BorderRadius.circular(8), - ), - child: SelectableText( - _backToText.isEmpty - ? 'Converted text appears here...' - : _backToText, - style: const TextStyle(fontSize: 16), - ), - ), - ], - ), - ), - ), - - const SizedBox(height: 12), - - // Validation indicators - Card( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - 'Validation Methods:', - style: TextStyle(fontWeight: FontWeight.bold), - ), - const SizedBox(height: 8), - Row( - children: [ - Icon( - _inputController.text.isValidMorseInput() - ? Icons.check_circle - : Icons.cancel, - color: _inputController.text.isValidMorseInput() - ? Colors.green - : Colors.red, - size: 16, - ), - const SizedBox(width: 8), - const Text('.isValidMorseInput()'), - ], - ), - const SizedBox(height: 4), - Row( - children: [ - Icon( - _morseOutput.isValidMorseSequence() - ? Icons.check_circle - : Icons.cancel, - color: _morseOutput.isValidMorseSequence() - ? Colors.green - : Colors.red, - size: 16, - ), - const SizedBox(width: 8), - const Text('.isValidMorseSequence()'), - ], - ), - ], - ), - ), - ), - ], - ), - ), - ], - ), - ); - } -} +void main() => app.main(); diff --git a/packages/morse_tap/example/pubspec.yaml b/packages/morse_tap/example/pubspec.yaml index b321ebd..9309f96 100644 --- a/packages/morse_tap/example/pubspec.yaml +++ b/packages/morse_tap/example/pubspec.yaml @@ -45,7 +45,7 @@ dev_dependencies: # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -88,3 +88,5 @@ flutter: # # For details regarding fonts from package dependencies, # see https://flutter.dev/to/font-from-package + +resolution: workspace diff --git a/packages/morse_tap/example/test/example_test.dart b/packages/morse_tap/example/test/example_test.dart new file mode 100644 index 0000000..0327faf --- /dev/null +++ b/packages/morse_tap/example/test/example_test.dart @@ -0,0 +1,323 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:morse_tap/morse_tap.dart'; +import 'package:morse_tap_example/haptic_config_modal.dart'; +import 'package:morse_tap_example/main.dart' as app; + +void main() { + setUp(() { + HapticUtils.debugHapticSupportedOverride = true; + }); + tearDown(() { + HapticUtils.debugHapticSupportedOverride = null; + }); + + testWidgets( + 'entry point navigates between all examples without layout errors', + (tester) async { + await tester.binding.setSurfaceSize(const Size(900, 1200)); + addTearDown(() => tester.binding.setSurfaceSize(null)); + app.main(); + await tester.pumpAndSettle(); + expect(find.text('Morse Tap Detector'), findsOneWidget); + await tester.tap(find.text('Text Input').last); + await tester.pumpAndSettle(); + expect(find.text('Morse Text Input'), findsOneWidget); + await tester.tap(find.text('Extensions')); + await tester.pumpAndSettle(); + expect(find.text('String Extensions'), findsOneWidget); + expect(tester.takeException(), isNull); + await tester.pumpWidget(const SizedBox()); + }, + ); + + testWidgets( + 'detector presents gestures, sequence feedback, and timed resets', + (tester) async { + await tester.binding.setSurfaceSize(const Size(900, 1400)); + addTearDown(() => tester.binding.setSurfaceSize(null)); + await tester.pumpWidget( + const MaterialApp(home: Scaffold(body: app.MorseTapDetectorExample())), + ); + MorseTapDetector detector() => + tester.widget(find.byType(MorseTapDetector)); + detector().onDotAdded!(); + detector().onSequenceChange!('.'); + await tester.pumpAndSettle(); + expect(find.text('Added dot (•)'), findsOneWidget); + expect( + tester.widget(find.text('Current: .')).style?.color, + Colors.green, + ); + detector().onDashAdded!(); + detector().onSequenceChange!('-'); + await tester.pumpAndSettle(); + expect(find.text('Added dash (—)'), findsOneWidget); + expect( + tester.widget(find.text('Current: -')).style?.color, + Colors.orange, + ); + detector().onSpaceAdded!(); + await tester.pump(); + expect(find.text('Added space'), findsOneWidget); + detector().onCorrectSequence(); + await tester.pump(); + expect(find.text('✅ Perfect! You tapped SOS correctly!'), findsOneWidget); + await tester.pump(const Duration(seconds: 2)); + expect( + find.text('Use gestures to input SOS (... --- ...)'), + findsOneWidget, + ); + detector().onIncorrectSequence!(); + await tester.pump(); + expect(find.text('❌ Wrong sequence. Try again!'), findsOneWidget); + await tester.pump(const Duration(seconds: 1)); + expect( + find.text('Use gestures to input SOS (... --- ...)'), + findsOneWidget, + ); + detector().onInputTimeout!(); + await tester.pump(); + expect(find.text('⏰ Sequence timed out. Try again!'), findsOneWidget); + await tester.pump(const Duration(seconds: 1)); + await tester.tap(find.text('HELLO')); + await tester.pumpAndSettle(); + expect(detector().expectedMorseCode, '.... . .-.. .-.. ---'); + expect(find.text('Target: HELLO'), findsOneWidget); + await tester.tap(find.text('HELLO')); + await tester.pumpAndSettle(); + expect(find.text('Target: HELLO'), findsOneWidget); + detector().onCorrectSequence(); + detector().onIncorrectSequence!(); + detector().onInputTimeout!(); + await tester.pumpWidget(const SizedBox()); + await tester.pump(const Duration(seconds: 2)); + expect(tester.takeException(), isNull); + }, + ); + + testWidgets( + 'detector settings save a new configuration and cancel preserves it', + (tester) async { + await tester.binding.setSurfaceSize(const Size(900, 1200)); + addTearDown(() => tester.binding.setSurfaceSize(null)); + await tester.pumpWidget( + const MaterialApp(home: Scaffold(body: app.MorseTapDetectorExample())), + ); + await tester.tap(find.text('Haptic Settings (Enabled)')); + await tester.pumpAndSettle(); + await tester.tap(find.text('Disabled')); + await tester.pumpAndSettle(); + await tester.tap(find.text('Save')); + await tester.pumpAndSettle(); + expect(find.text('Haptic Settings (Disabled)'), findsOneWidget); + expect( + tester + .widget(find.byType(MorseTapDetector)) + .hapticConfig + ?.enabled, + isFalse, + ); + await tester.tap(find.text('Haptic Settings (Disabled)')); + await tester.pumpAndSettle(); + await tester.tap(find.text('Reset')); + await tester.pumpAndSettle(); + await tester.tap(find.text('Cancel')); + await tester.pumpAndSettle(); + expect(find.text('Haptic Settings (Disabled)'), findsOneWidget); + }, + ); + + testWidgets('text input selects its controller and conversion mode', ( + tester, + ) async { + await tester.binding.setSurfaceSize(const Size(900, 1200)); + addTearDown(() => tester.binding.setSurfaceSize(null)); + await tester.pumpWidget( + const MaterialApp(home: Scaffold(body: app.MorseTextInputExample())), + ); + MorseTextInput input() => tester.widget(find.byType(MorseTextInput)); + expect(input().autoConvertToText, isTrue); + final textController = input().controller; + input().onTextChanged?.call('SOS'); + expect(find.text('Converted Text'), findsOneWidget); + await tester.tap(find.byType(Checkbox)); + await tester.pumpAndSettle(); + expect(input().autoConvertToText, isFalse); + expect(input().controller, isNot(same(textController))); + expect(find.text('Morse Code'), findsOneWidget); + tester.widget(find.byType(Checkbox)).onChanged!(null); + await tester.pumpAndSettle(); + expect(input().autoConvertToText, isTrue); + expect(input().controller, same(textController)); + await tester.pumpWidget(const SizedBox()); + }); + + testWidgets( + 'string example converts input and shows empty and invalid states', + (tester) async { + await tester.binding.setSurfaceSize(const Size(900, 1400)); + addTearDown(() => tester.binding.setSurfaceSize(null)); + await tester.pumpWidget( + const MaterialApp(home: Scaffold(body: app.StringExtensionExample())), + ); + expect( + find.text('.... . .-.. .-.. --- / .-- --- .-. .-.. -..'), + findsOneWidget, + ); + await tester.enterText(find.byType(TextField), 'SOS'); + await tester.pumpAndSettle(); + expect(find.text('... --- ...'), findsOneWidget); + expect( + tester + .widgetList(find.byType(SelectableText)) + .last + .data, + 'SOS', + ); + await tester.enterText(find.byType(TextField), '🙂'); + await tester.pumpAndSettle(); + expect(find.byIcon(Icons.cancel), findsWidgets); + await tester.enterText(find.byType(TextField), ''); + await tester.pumpAndSettle(); + expect(find.text('Enter text above...'), findsOneWidget); + expect(find.text('Converted text appears here...'), findsOneWidget); + await tester.pumpWidget(const SizedBox()); + }, + ); + + testWidgets( + 'haptic modal updates every intensity and returns the saved configuration', + (tester) async { + await tester.binding.setSurfaceSize(const Size(1000, 1800)); + addTearDown(() => tester.binding.setSurfaceSize(null)); + final calls = []; + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( + SystemChannels.platform, + (call) async { + calls.add(call); + return null; + }, + ); + addTearDown( + () => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( + SystemChannels.platform, + null, + ), + ); + HapticConfig? saved; + HapticConfig? result; + await tester.pumpWidget( + MaterialApp( + home: Builder( + builder: (context) => Scaffold( + body: TextButton( + onPressed: () async { + result = await showDialog( + context: context, + builder: (_) => HapticConfigModal( + initialConfig: HapticConfig.defaultConfig, + onConfigChanged: (config) => saved = config, + ), + ); + }, + child: const Text('configure'), + ), + ), + ), + ), + ); + await tester.tap(find.text('configure')); + await tester.pumpAndSettle(); + await tester.tap(find.text('Strong')); + await tester.pumpAndSettle(); + expect( + tester + .widget(find.widgetWithText(FilterChip, 'Strong')) + .selected, + isTrue, + ); + for (var i = 0; i < 6; i++) { + final dropdown = tester + .widget>( + find.byType(DropdownButtonFormField).at(i), + ); + dropdown.onChanged!(HapticFeedbackType.vibrate); + await tester.pump(); + } + await tester.tap(find.byTooltip('Test haptic').first); + await tester.pump(const Duration(milliseconds: 150)); + expect( + calls.any( + (call) => + call.method == 'HapticFeedback.vibrate' && call.arguments == null, + ), + isTrue, + ); + await tester.tap(find.text('Save')); + await tester.pumpAndSettle(); + expect(saved, same(result)); + expect( + saved, + const HapticConfig( + enabled: true, + dotIntensity: HapticFeedbackType.vibrate, + dashIntensity: HapticFeedbackType.vibrate, + spaceIntensity: HapticFeedbackType.vibrate, + correctSequenceIntensity: HapticFeedbackType.vibrate, + incorrectSequenceIntensity: HapticFeedbackType.vibrate, + timeoutIntensity: HapticFeedbackType.vibrate, + ), + ); + }, + ); + + testWidgets( + 'haptic switch toggles settings and unsupported platforms disable it', + (tester) async { + await tester.pumpWidget( + const MaterialApp( + home: HapticConfigModal(initialConfig: HapticConfig.defaultConfig), + ), + ); + expect( + find.text('Provide tactile feedback for gestures'), + findsOneWidget, + ); + await tester.tap(find.byType(SwitchListTile)); + await tester.pumpAndSettle(); + expect(find.text('Quick Presets'), findsNothing); + await tester.tap(find.byType(SwitchListTile)); + await tester.pumpAndSettle(); + expect(find.text('Quick Presets'), findsOneWidget); + HapticUtils.debugHapticSupportedOverride = false; + await tester.pumpWidget( + const MaterialApp( + home: HapticConfigModal( + key: ValueKey('unsupported'), + initialConfig: HapticConfig.defaultConfig, + ), + ), + ); + expect(find.text('Not supported on this platform'), findsOneWidget); + expect( + tester.widget(find.byType(SwitchListTile)).onChanged, + isNull, + ); + expect( + tester + .widget( + find + .byWidgetPredicate( + (widget) => + widget is IconButton && widget.tooltip == 'Test haptic', + ) + .first, + ) + .onPressed, + isNull, + ); + }, + ); +} diff --git a/packages/morse_tap/example/test/legacy_entrypoint_test.dart b/packages/morse_tap/example/test/legacy_entrypoint_test.dart new file mode 100644 index 0000000..bf9dba4 --- /dev/null +++ b/packages/morse_tap/example/test/legacy_entrypoint_test.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:morse_tap_example/main.dart' as maintained; + +import '../main.dart' as legacy; + +void main() { + testWidgets('legacy entrypoint launches the maintained example', ( + tester, + ) async { + legacy.main(); + await tester.pumpAndSettle(); + expect(find.byType(legacy.MorseTapExampleApp), findsOneWidget); + expect(find.byType(maintained.MorseTapExampleApp), findsOneWidget); + expect(find.text('Morse Tap Detector'), findsOneWidget); + await tester.pumpWidget(const SizedBox.shrink()); + }); +} diff --git a/packages/morse_tap/lib/src/utils/haptic_utils.dart b/packages/morse_tap/lib/src/utils/haptic_utils.dart index 8ea6645..e8ac31b 100644 --- a/packages/morse_tap/lib/src/utils/haptic_utils.dart +++ b/packages/morse_tap/lib/src/utils/haptic_utils.dart @@ -10,9 +10,7 @@ import 'platform_utils_io.dart' /// /// Provides safe execution of haptic feedback with platform detection, /// error handling, and user-friendly descriptions for haptic types. -class HapticUtils { - HapticUtils._(); // coverage:ignore-line - +abstract final class HapticUtils { /// Map of haptic feedback types to user-friendly display names static final Map hapticTypeNames = { HapticFeedbackType.lightImpact: 'Light', @@ -50,7 +48,7 @@ class HapticUtils { if (debugHapticSupportedOverride != null) { return debugHapticSupportedOverride!; } - if (kIsWeb) return false; // coverage:ignore-line + if (kIsWeb) return false; return PlatformUtils.isHapticSupported; } diff --git a/packages/morse_tap/pubspec.yaml b/packages/morse_tap/pubspec.yaml index d9981a8..a02cf03 100644 --- a/packages/morse_tap/pubspec.yaml +++ b/packages/morse_tap/pubspec.yaml @@ -27,7 +27,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -65,3 +65,5 @@ flutter: # # For details regarding fonts in packages, see # https://flutter.dev/to/font-from-package + +resolution: workspace diff --git a/packages/morse_tap/test/platform_web_test.dart b/packages/morse_tap/test/platform_web_test.dart new file mode 100644 index 0000000..717a034 --- /dev/null +++ b/packages/morse_tap/test/platform_web_test.dart @@ -0,0 +1,8 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:morse_tap/src/utils/platform_utils_web.dart' as web; + +void main() { + test('web implementation consistently disables haptics', () { + expect(web.PlatformUtils.isHapticSupported, isFalse); + }); +} diff --git a/packages/nonstop_cli/README.md b/packages/nonstop_cli/README.md index f562af1..e9699da 100644 --- a/packages/nonstop_cli/README.md +++ b/packages/nonstop_cli/README.md @@ -148,7 +148,7 @@ youtube/ ├── 🔌 plugins/ ├── 📋 analysis_options.yaml ├── 📖 README.md -├── ⚙️ melos.yaml +├── ⚙️ pubspec.yaml native pub workspace + Melos scripts └── 📄 pubspec.yaml ``` diff --git a/packages/nonstop_cli/bin/nonstop.dart b/packages/nonstop_cli/bin/nonstop.dart index 2f671ea..46bf240 100644 --- a/packages/nonstop_cli/bin/nonstop.dart +++ b/packages/nonstop_cli/bin/nonstop.dart @@ -3,16 +3,6 @@ import 'dart:io'; import 'package:nonstop_cli/command_runner.dart'; Future main(List args) async { - await _flushThenExit(await NonstopCliCommandRunner().run(args)); -} - -/// Flushes the stdout and stderr streams, then exits the program with the given -/// status code. -/// -/// This returns a Future that will never complete, since the program will have -/// exited already. This is useful to prevent Future chains from proceeding -/// after you've decided to exit. -Future _flushThenExit(int status) { - return Future.wait([stdout.close(), stderr.close()]) - .then((_) => exit(status)); + exitCode = await NonstopCliCommandRunner().run(args); + await Future.wait([stdout.flush(), stderr.flush()]); } diff --git a/packages/nonstop_cli/bin/ns.dart b/packages/nonstop_cli/bin/ns.dart index 2f671ea..b7d5961 100644 --- a/packages/nonstop_cli/bin/ns.dart +++ b/packages/nonstop_cli/bin/ns.dart @@ -1,18 +1,3 @@ -import 'dart:io'; +import 'nonstop.dart' as nonstop; -import 'package:nonstop_cli/command_runner.dart'; - -Future main(List args) async { - await _flushThenExit(await NonstopCliCommandRunner().run(args)); -} - -/// Flushes the stdout and stderr streams, then exits the program with the given -/// status code. -/// -/// This returns a Future that will never complete, since the program will have -/// exited already. This is useful to prevent Future chains from proceeding -/// after you've decided to exit. -Future _flushThenExit(int status) { - return Future.wait([stdout.close(), stderr.close()]) - .then((_) => exit(status)); -} +Future main(List args) => nonstop.main(args); diff --git a/packages/nonstop_cli/bin/nsio.dart b/packages/nonstop_cli/bin/nsio.dart index 2f671ea..b7d5961 100644 --- a/packages/nonstop_cli/bin/nsio.dart +++ b/packages/nonstop_cli/bin/nsio.dart @@ -1,18 +1,3 @@ -import 'dart:io'; +import 'nonstop.dart' as nonstop; -import 'package:nonstop_cli/command_runner.dart'; - -Future main(List args) async { - await _flushThenExit(await NonstopCliCommandRunner().run(args)); -} - -/// Flushes the stdout and stderr streams, then exits the program with the given -/// status code. -/// -/// This returns a Future that will never complete, since the program will have -/// exited already. This is useful to prevent Future chains from proceeding -/// after you've decided to exit. -Future _flushThenExit(int status) { - return Future.wait([stdout.close(), stderr.close()]) - .then((_) => exit(status)); -} +Future main(List args) => nonstop.main(args); diff --git a/packages/nonstop_cli/bricks/flutter_app_for_mono_repo/hooks/commands/flutter_app_create_command.dart b/packages/nonstop_cli/bricks/flutter_app_for_mono_repo/hooks/commands/flutter_app_create_command.dart index ec2a347..35c6a37 100644 --- a/packages/nonstop_cli/bricks/flutter_app_for_mono_repo/hooks/commands/flutter_app_create_command.dart +++ b/packages/nonstop_cli/bricks/flutter_app_for_mono_repo/hooks/commands/flutter_app_create_command.dart @@ -1,7 +1,12 @@ -import 'package:cli_core/cli_core.dart' show BaseFlutterCommand; +import 'package:cli_core/cli_core.dart' show BaseFlutterCommand, CliCommand; import 'package:mason/mason.dart'; -final class FlutterAppCreateCommand extends BaseFlutterCommand { +final class FlutterAppCreateCommand extends CliCommand { + FlutterAppCreateCommand({BaseFlutterCommand? flutter}) + : _flutter = flutter ?? BaseFlutterCommand(); + + final BaseFlutterCommand _flutter; + @override Future run(HookContext context) async { final String name = context.vars['name']; @@ -9,7 +14,7 @@ final class FlutterAppCreateCommand extends BaseFlutterCommand { final String orgName = context.vars['org_name'] ?? 'com.example'; final appName = name.snakeCase; - await createFlutterProject( + await _flutter.createFlutterProject( context: context, name: appName, description: description, @@ -19,7 +24,7 @@ final class FlutterAppCreateCommand extends BaseFlutterCommand { final isMonoRepo = context.vars['is_mono_repo'] ?? false; if (isMonoRepo) { - await removeAnalysisOptions( + await _flutter.removeAnalysisOptions( context: context, projectPath: appName, ); diff --git a/packages/nonstop_cli/bricks/flutter_app_for_mono_repo/hooks/post_gen.dart b/packages/nonstop_cli/bricks/flutter_app_for_mono_repo/hooks/post_gen.dart index 7a86471..61c9554 100644 --- a/packages/nonstop_cli/bricks/flutter_app_for_mono_repo/hooks/post_gen.dart +++ b/packages/nonstop_cli/bricks/flutter_app_for_mono_repo/hooks/post_gen.dart @@ -1,12 +1,13 @@ import 'dart:async'; +import 'package:cli_core/cli_core.dart'; import 'package:mason/mason.dart'; import 'commands/flutter_app_create_command.dart'; -Future run(HookContext context) async { +Future run(HookContext context, {BaseFlutterCommand? flutter}) async { final commands = [ - FlutterAppCreateCommand(), + FlutterAppCreateCommand(flutter: flutter), ]; for (final command in commands) { diff --git a/packages/nonstop_cli/bricks/flutter_app_for_mono_repo/hooks/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_app_for_mono_repo/hooks/pubspec.yaml index 24cc4ce..ce42c94 100644 --- a/packages/nonstop_cli/bricks/flutter_app_for_mono_repo/hooks/pubspec.yaml +++ b/packages/nonstop_cli/bricks/flutter_app_for_mono_repo/hooks/pubspec.yaml @@ -1,7 +1,7 @@ name: flutter_app_for_mono_repo_hooks environment: - sdk: '>=3.0.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: mason: ^0.1.2 @@ -9,5 +9,8 @@ dependencies: cli_core: ^0.0.2 dev_dependencies: - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 test: ^1.21.0 + mocktail: ^1.0.5 + +resolution: workspace diff --git a/packages/nonstop_cli/bricks/flutter_app_for_mono_repo/hooks/test/hooks_test.dart b/packages/nonstop_cli/bricks/flutter_app_for_mono_repo/hooks/test/hooks_test.dart new file mode 100644 index 0000000..cc2f321 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_app_for_mono_repo/hooks/test/hooks_test.dart @@ -0,0 +1,98 @@ +import 'dart:io'; + +import 'package:cli_core/cli_core.dart'; +import 'package:mason/mason.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:test/test.dart'; + +import '../commands/flutter_app_create_command.dart'; +import '../post_gen.dart' as post; +import '../pre_gen.dart' as pre; + +class _Context extends Mock implements HookContext {} + +final class _Flutter extends BaseFlutterCommand { + final creations = >[]; + final removals = []; + + @override + Future createFlutterProject({ + required HookContext context, + required String name, + required String description, + required String outputPath, + String template = 'app', + List platforms = const ['ios', 'android', 'web'], + String? orgName, + }) async { + creations.add({ + 'context': context, + 'name': name, + 'description': description, + 'outputPath': outputPath, + 'template': template, + 'platforms': platforms, + 'orgName': orgName + }); + } + + @override + Future removeAnalysisOptions( + {required HookContext context, required String projectPath}) async { + removals.add(projectPath); + } +} + +void main() { + test('default command constructs without external work', () { + expect(FlutterAppCreateCommand(), isA()); + }); + + test('pre-generation discovers a native workspace', () async { + final temporary = await Directory.systemTemp.createTemp('hook_pre_'); + final original = Directory.current; + try { + Directory.current = temporary; + final context = _Context(); + final variables = {}; + when(() => context.vars).thenReturn(variables); + await pre.run(context); + expect(variables['is_mono_repo'], isFalse); + await File('pubspec.yaml').writeAsString('name: root\nworkspace: []\n'); + await pre.run(context); + expect(variables['is_mono_repo'], isTrue); + } finally { + Directory.current = original; + await temporary.delete(recursive: true); + } + }); + + for (final mono in [true, false, null]) { + for (final org in ['io.nonstop', null]) { + test('post-generation creates app (workspace=$mono, organization=$org)', + () async { + final context = _Context(); + when(() => context.vars).thenReturn({ + 'name': 'Sample App', + 'description': 'An example', + if (mono != null) 'is_mono_repo': mono, + if (org != null) 'org_name': org, + }); + final flutter = _Flutter(); + await post.run(context, flutter: flutter); + expect(flutter.creations, [ + { + 'context': context, + 'name': 'sample_app', + 'description': 'An example', + 'outputPath': '.', + 'template': 'app', + 'platforms': ['ios', 'android', 'web'], + 'orgName': org ?? 'com.example' + }, + ]); + expect(flutter.removals, mono == true ? ['sample_app'] : isEmpty); + }); + } + } +} diff --git a/packages/nonstop_cli/bricks/flutter_package_for_mono_repo/hooks/commands/flutter_package_create_command.dart b/packages/nonstop_cli/bricks/flutter_package_for_mono_repo/hooks/commands/flutter_package_create_command.dart index 4f63b0d..baa25b6 100644 --- a/packages/nonstop_cli/bricks/flutter_package_for_mono_repo/hooks/commands/flutter_package_create_command.dart +++ b/packages/nonstop_cli/bricks/flutter_package_for_mono_repo/hooks/commands/flutter_package_create_command.dart @@ -1,14 +1,19 @@ -import 'package:cli_core/cli_core.dart' show BaseFlutterCommand; +import 'package:cli_core/cli_core.dart' show BaseFlutterCommand, CliCommand; import 'package:mason/mason.dart'; -final class FlutterPackageCreateCommand extends BaseFlutterCommand { +final class FlutterPackageCreateCommand extends CliCommand { + FlutterPackageCreateCommand({BaseFlutterCommand? flutter}) + : _flutter = flutter ?? BaseFlutterCommand(); + + final BaseFlutterCommand _flutter; + @override Future run(HookContext context) async { final String name = context.vars['name']; final String description = context.vars['description']; final appName = name.snakeCase; - await createFlutterProject( + await _flutter.createFlutterProject( context: context, name: appName, description: description, @@ -18,7 +23,7 @@ final class FlutterPackageCreateCommand extends BaseFlutterCommand { final isMonoRepo = context.vars['is_mono_repo'] ?? false; if (isMonoRepo) { - await removeAnalysisOptions( + await _flutter.removeAnalysisOptions( context: context, projectPath: appName, ); diff --git a/packages/nonstop_cli/bricks/flutter_package_for_mono_repo/hooks/post_gen.dart b/packages/nonstop_cli/bricks/flutter_package_for_mono_repo/hooks/post_gen.dart index 875f193..6ef42a0 100644 --- a/packages/nonstop_cli/bricks/flutter_package_for_mono_repo/hooks/post_gen.dart +++ b/packages/nonstop_cli/bricks/flutter_package_for_mono_repo/hooks/post_gen.dart @@ -1,12 +1,13 @@ import 'dart:async'; +import 'package:cli_core/cli_core.dart'; import 'package:mason/mason.dart'; import 'commands/flutter_package_create_command.dart'; -Future run(HookContext context) async { +Future run(HookContext context, {BaseFlutterCommand? flutter}) async { final commands = [ - FlutterPackageCreateCommand(), + FlutterPackageCreateCommand(flutter: flutter), ]; for (final command in commands) { diff --git a/packages/nonstop_cli/bricks/flutter_package_for_mono_repo/hooks/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_package_for_mono_repo/hooks/pubspec.yaml index 57e06ab..6ab4efc 100644 --- a/packages/nonstop_cli/bricks/flutter_package_for_mono_repo/hooks/pubspec.yaml +++ b/packages/nonstop_cli/bricks/flutter_package_for_mono_repo/hooks/pubspec.yaml @@ -1,7 +1,7 @@ name: flutter_package_for_mono_repo_hooks environment: - sdk: '>=3.0.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: mason: ^0.1.2 @@ -9,5 +9,8 @@ dependencies: cli_core: ^0.0.2 dev_dependencies: - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 test: ^1.21.0 + mocktail: ^1.0.5 + +resolution: workspace diff --git a/packages/nonstop_cli/bricks/flutter_package_for_mono_repo/hooks/test/hooks_test.dart b/packages/nonstop_cli/bricks/flutter_package_for_mono_repo/hooks/test/hooks_test.dart new file mode 100644 index 0000000..56b31ab --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_package_for_mono_repo/hooks/test/hooks_test.dart @@ -0,0 +1,99 @@ +import 'dart:io'; + +import 'package:cli_core/cli_core.dart'; +import 'package:mason/mason.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:test/test.dart'; + +import '../commands/flutter_package_create_command.dart'; +import '../post_gen.dart' as post; +import '../pre_gen.dart' as pre; + +class _Context extends Mock implements HookContext {} + +final class _Flutter extends BaseFlutterCommand { + final creations = >[]; + final removals = []; + + @override + Future createFlutterProject({ + required HookContext context, + required String name, + required String description, + required String outputPath, + String template = 'app', + List platforms = const ['ios', 'android', 'web'], + String? orgName, + }) async { + creations.add({ + 'context': context, + 'name': name, + 'description': description, + 'outputPath': outputPath, + 'template': template, + 'platforms': platforms, + 'orgName': orgName + }); + } + + @override + Future removeAnalysisOptions( + {required HookContext context, required String projectPath}) async { + removals.add(projectPath); + } +} + +void main() { + test('default command constructs without external work', () { + expect(FlutterPackageCreateCommand(), isA()); + }); + + test('pre-generation discovers a native workspace', () async { + final temporary = await Directory.systemTemp.createTemp('hook_pre_'); + final original = Directory.current; + try { + Directory.current = temporary; + final context = _Context(); + final variables = {}; + when(() => context.vars).thenReturn(variables); + await pre.run(context); + expect(variables['is_mono_repo'], isFalse); + await File('pubspec.yaml').writeAsString('name: root\nworkspace: []\n'); + await pre.run(context); + expect(variables['is_mono_repo'], isTrue); + } finally { + Directory.current = original; + await temporary.delete(recursive: true); + } + }); + + for (final mono in [true, false, null]) { + for (final org in ['io.nonstop', null]) { + test( + 'post-generation creates package (workspace=$mono, organization=$org)', + () async { + final context = _Context(); + when(() => context.vars).thenReturn({ + 'name': 'Sample App', + 'description': 'An example', + if (mono != null) 'is_mono_repo': mono, + if (org != null) 'org_name': org, + }); + final flutter = _Flutter(); + await post.run(context, flutter: flutter); + expect(flutter.creations, [ + { + 'context': context, + 'name': 'sample_app', + 'description': 'An example', + 'outputPath': '.', + 'template': 'package', + 'platforms': ['ios', 'android', 'web'], + 'orgName': null + }, + ]); + expect(flutter.removals, mono == true ? ['sample_app'] : isEmpty); + }); + } + } +} diff --git a/packages/nonstop_cli/bricks/flutter_plugin_for_mono_repo/hooks/commands/flutter_plugin_create_command.dart b/packages/nonstop_cli/bricks/flutter_plugin_for_mono_repo/hooks/commands/flutter_plugin_create_command.dart index 33794d9..11eb51e 100644 --- a/packages/nonstop_cli/bricks/flutter_plugin_for_mono_repo/hooks/commands/flutter_plugin_create_command.dart +++ b/packages/nonstop_cli/bricks/flutter_plugin_for_mono_repo/hooks/commands/flutter_plugin_create_command.dart @@ -1,7 +1,12 @@ -import 'package:cli_core/cli_core.dart' show BaseFlutterCommand; +import 'package:cli_core/cli_core.dart' show BaseFlutterCommand, CliCommand; import 'package:mason/mason.dart'; -final class FlutterPluginCreateCommand extends BaseFlutterCommand { +final class FlutterPluginCreateCommand extends CliCommand { + FlutterPluginCreateCommand({BaseFlutterCommand? flutter}) + : _flutter = flutter ?? BaseFlutterCommand(); + + final BaseFlutterCommand _flutter; + @override Future run(HookContext context) async { final String name = context.vars['name']; @@ -9,7 +14,7 @@ final class FlutterPluginCreateCommand extends BaseFlutterCommand { final String orgName = context.vars['org_name'] ?? 'com.example'; final appName = name.snakeCase; - await createFlutterProject( + await _flutter.createFlutterProject( context: context, name: appName, description: description, @@ -20,7 +25,7 @@ final class FlutterPluginCreateCommand extends BaseFlutterCommand { final isMonoRepo = context.vars['is_mono_repo'] ?? false; if (isMonoRepo) { - await removeAnalysisOptions( + await _flutter.removeAnalysisOptions( context: context, projectPath: appName, ); diff --git a/packages/nonstop_cli/bricks/flutter_plugin_for_mono_repo/hooks/post_gen.dart b/packages/nonstop_cli/bricks/flutter_plugin_for_mono_repo/hooks/post_gen.dart index d5d7af2..ad47179 100644 --- a/packages/nonstop_cli/bricks/flutter_plugin_for_mono_repo/hooks/post_gen.dart +++ b/packages/nonstop_cli/bricks/flutter_plugin_for_mono_repo/hooks/post_gen.dart @@ -1,12 +1,13 @@ import 'dart:async'; +import 'package:cli_core/cli_core.dart'; import 'package:mason/mason.dart'; import 'commands/flutter_plugin_create_command.dart'; -Future run(HookContext context) async { +Future run(HookContext context, {BaseFlutterCommand? flutter}) async { final commands = [ - FlutterPluginCreateCommand(), + FlutterPluginCreateCommand(flutter: flutter), ]; for (final command in commands) { diff --git a/packages/nonstop_cli/bricks/flutter_plugin_for_mono_repo/hooks/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_plugin_for_mono_repo/hooks/pubspec.yaml index e7bcc99..07a2f79 100644 --- a/packages/nonstop_cli/bricks/flutter_plugin_for_mono_repo/hooks/pubspec.yaml +++ b/packages/nonstop_cli/bricks/flutter_plugin_for_mono_repo/hooks/pubspec.yaml @@ -1,7 +1,7 @@ name: flutter_plugin_for_mono_repo_hooks environment: - sdk: '>=3.0.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: mason: ^0.1.2 @@ -9,5 +9,8 @@ dependencies: cli_core: ^0.0.2 dev_dependencies: - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 test: ^1.21.0 + mocktail: ^1.0.5 + +resolution: workspace diff --git a/packages/nonstop_cli/bricks/flutter_plugin_for_mono_repo/hooks/test/hooks_test.dart b/packages/nonstop_cli/bricks/flutter_plugin_for_mono_repo/hooks/test/hooks_test.dart new file mode 100644 index 0000000..ae6f277 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_plugin_for_mono_repo/hooks/test/hooks_test.dart @@ -0,0 +1,99 @@ +import 'dart:io'; + +import 'package:cli_core/cli_core.dart'; +import 'package:mason/mason.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:test/test.dart'; + +import '../commands/flutter_plugin_create_command.dart'; +import '../post_gen.dart' as post; +import '../pre_gen.dart' as pre; + +class _Context extends Mock implements HookContext {} + +final class _Flutter extends BaseFlutterCommand { + final creations = >[]; + final removals = []; + + @override + Future createFlutterProject({ + required HookContext context, + required String name, + required String description, + required String outputPath, + String template = 'app', + List platforms = const ['ios', 'android', 'web'], + String? orgName, + }) async { + creations.add({ + 'context': context, + 'name': name, + 'description': description, + 'outputPath': outputPath, + 'template': template, + 'platforms': platforms, + 'orgName': orgName + }); + } + + @override + Future removeAnalysisOptions( + {required HookContext context, required String projectPath}) async { + removals.add(projectPath); + } +} + +void main() { + test('default command constructs without external work', () { + expect(FlutterPluginCreateCommand(), isA()); + }); + + test('pre-generation discovers a native workspace', () async { + final temporary = await Directory.systemTemp.createTemp('hook_pre_'); + final original = Directory.current; + try { + Directory.current = temporary; + final context = _Context(); + final variables = {}; + when(() => context.vars).thenReturn(variables); + await pre.run(context); + expect(variables['is_mono_repo'], isFalse); + await File('pubspec.yaml').writeAsString('name: root\nworkspace: []\n'); + await pre.run(context); + expect(variables['is_mono_repo'], isTrue); + } finally { + Directory.current = original; + await temporary.delete(recursive: true); + } + }); + + for (final mono in [true, false, null]) { + for (final org in ['io.nonstop', null]) { + test( + 'post-generation creates plugin (workspace=$mono, organization=$org)', + () async { + final context = _Context(); + when(() => context.vars).thenReturn({ + 'name': 'Sample App', + 'description': 'An example', + if (mono != null) 'is_mono_repo': mono, + if (org != null) 'org_name': org, + }); + final flutter = _Flutter(); + await post.run(context, flutter: flutter); + expect(flutter.creations, [ + { + 'context': context, + 'name': 'sample_app', + 'description': 'An example', + 'outputPath': '.', + 'template': 'plugin', + 'platforms': ['ios', 'android', 'web'], + 'orgName': org ?? 'com.example' + }, + ]); + expect(flutter.removals, mono == true ? ['sample_app'] : isEmpty); + }); + } + } +} diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/.github/workflows/quality.yml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/.github/workflows/quality.yml index a64bb2f..d758248 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/.github/workflows/quality.yml +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/.github/workflows/quality.yml @@ -9,7 +9,7 @@ jobs: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: - flutter-version: '3.44.0' + flutter-version: '3.47.5' cache: true - run: dart pub get - run: dart run melos bootstrap diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/README.md b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/README.md index fd71fce..2002624 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/README.md +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/README.md @@ -38,6 +38,9 @@ packages, packages depend on nothing above them. ## Getting started +Use Flutter 3.47.5 or newer. The root `pubspec.yaml` declares the native Dart +workspace and Melos scripts; member packages share a single dependency lockfile. + ```sh dart pub get dart run melos bootstrap diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/pubspec.yaml index 03d4754..8111289 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/pubspec.yaml +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/pubspec.yaml @@ -5,13 +5,13 @@ version: 0.1.0+1 environment: sdk: ^3.8.0 - flutter: ">=3.29.0" + flutter: ">=3.47.5" dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.8 - go_router: ^15.1.3 + go_router: ^18.0.1 bloc: ^9.0.0 core: path: ../../packages/core @@ -23,7 +23,7 @@ dependencies: path: ../../packages/localization utils: path: ../../packages/utils -{{#firebase}} firebase_core: ^3.14.0 +{{#firebase}} firebase_core: ^4.15.0 {{/firebase}}{{#network}} network: path: ../../packages/network {{/network}}{{#analytics}} analytics: @@ -44,18 +44,16 @@ dependencies: dev_dependencies: {{#firebase}} mocktail: ^1.0.4 plugin_platform_interface: ^2.1.8 - firebase_core_platform_interface: ^6.0.3 -{{/firebase}}{{#auth}} firebase_auth_platform_interface: ^7.7.3 -{{/auth}}{{#analytics}} firebase_analytics_platform_interface: ^4.4.3 + firebase_core_platform_interface: ^8.1.1 +{{/firebase}}{{#auth}} firebase_auth_platform_interface: ^9.1.0 +{{/auth}}{{#analytics}} firebase_analytics_platform_interface: ^6.0.7 {{/analytics}}{{#crashlytics}} firebase_crashlytics_platform_interface: ^3.8.10 -{{/crashlytics}}{{#feature_flags}} firebase_remote_config_platform_interface: ^2.0.0 +{{/crashlytics}}{{#feature_flags}} firebase_remote_config_platform_interface: ^3.0.7 {{/feature_flags}}{{#notifications}} firebase_messaging_platform_interface: ^4.6.10 {{/notifications}} flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 -dependency_overrides: - intl: ^0.20.2 flutter: uses-material-design: true @@ -66,3 +64,5 @@ flutter: fonts: - asset: packages/firebase_ui_auth/fonts/SocialIcons.ttf {{/auth}} + +resolution: workspace diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/pubspec.yaml index d862aa5..9a05991 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/pubspec.yaml +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{auth_dir}}/pubspec.yaml @@ -5,18 +5,18 @@ publish_to: 'none' environment: sdk: ^3.8.0 - flutter: ">=3.29.0" + flutter: ">=3.47.5" dependencies: flutter: sdk: flutter equatable: ^2.0.7 - firebase_auth: ^5.6.0 - firebase_core: ^3.14.0 - firebase_ui_auth: ^1.17.0 - firebase_ui_oauth_apple: ^1.3.2 - firebase_ui_oauth_google: ^1.4.2 - go_router: ^15.1.3 + firebase_auth: ^6.7.0 + firebase_core: ^4.15.0 + firebase_ui_auth: ^3.1.1 + firebase_ui_oauth_apple: ^2.1.1 + firebase_ui_oauth_google: ^2.1.1 + go_router: ^18.0.1 json_annotation: ^4.9.0 core: path: ../../packages/core @@ -26,7 +26,7 @@ dependencies: path: ../../packages/di localization: path: ../../packages/localization -{{#firestore}} cloud_firestore: ^5.6.0 +{{#firestore}} cloud_firestore: ^6.10.0 {{/firestore}}{{#network}} network: path: ../../packages/network {{/network}}{{#analytics}} analytics: @@ -36,6 +36,8 @@ dev_dependencies: mocktail: ^1.0.4 flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 flutter: + +resolution: workspace diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/pubspec.yaml index aa88eaf..9c748b0 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/pubspec.yaml +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/features/{{dashboard_dir}}/pubspec.yaml @@ -5,12 +5,12 @@ publish_to: 'none' environment: sdk: ^3.8.0 - flutter: ">=3.29.0" + flutter: ">=3.47.5" dependencies: flutter: sdk: flutter - go_router: ^15.1.3 + go_router: ^18.0.1 core: path: ../../packages/core design_system: @@ -27,6 +27,8 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 flutter: + +resolution: workspace diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/melos.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/melos.yaml deleted file mode 100644 index 5814dac..0000000 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/melos.yaml +++ /dev/null @@ -1,98 +0,0 @@ -name: {{ name.snakeCase() }}_workspace - -############################################################################### -# MONOREPO CONFIGURATION -############################################################################### -# apps/ - runnable Flutter applications -# features/ - vertical slices (ui + state + data) that an app composes -# packages/ - horizontal capabilities shared by features (network, design, ...) -# plugins/ - platform channels and native integrations -packages: - - apps/* - - features/* - - packages/* - - plugins/* - -command: - bootstrap: - dependencies: - json_annotation: ^4.9.0 - dev_dependencies: - build_runner: ^2.4.15 - flutter_lints: ^5.0.0 - json_serializable: ^6.9.5 - -############################################################################### -# SCRIPTS -############################################################################### -scripts: - clean:flutter: - description: "Run flutter clean in all Flutter packages" - run: flutter clean - exec: - concurrency: 4 - packageFilters: - flutter: true - - lint: - description: "Check formatting and analyze all workspace code" - run: dart run tool/check.dart - - fix: - description: "Apply Dart automated fixes to all packages" - run: dart fix --apply - exec: - concurrency: 5 - packageFilters: - dirExists: lib - - fix:dry: - description: "Show Dart automated fixes without applying them" - run: dart fix --dry-run - exec: - concurrency: 5 - packageFilters: - dirExists: lib - - generate: - description: "Run build_runner in every package that needs codegen" - run: dart run build_runner build --delete-conflicting-outputs - exec: - concurrency: 1 - packageFilters: - dependsOn: build_runner - - generate:i69n: - description: "Regenerate localization strings from messages.i69n.yaml" - run: | - dart run build_runner build --delete-conflicting-outputs && - dart format lib/messages.i69n.dart - exec: - concurrency: 1 - packageFilters: - scope: "localization" - - test: - description: "Run all tests (Dart and Flutter)" - run: dart run melos run test:select --no-select - - coverage: - description: "Run every test and require 100% handwritten line coverage" - run: dart run tool/coverage.dart - - test:select: - description: "Run tests in Flutter packages" - run: flutter test --coverage - exec: - concurrency: 5 - failFast: true - packageFilters: - dirExists: test - -############################################################################### -# IDE CONFIGURATION -############################################################################### -ide: - intellij: - enabled: true - moduleNamePrefix: '' diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/observer/core_route_observer.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/observer/core_route_observer.dart index f09a084..5ddf12e 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/observer/core_route_observer.dart +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/lib/observer/core_route_observer.dart @@ -55,7 +55,7 @@ class TalkerRouteLog extends TalkerLog { AnsiPen get pen => AnsiPen()..xterm(135); @override - String get key => TalkerLogType.route.key; + String get key => TalkerKey.route; static String _createMessage(Route route, RouteLogType type) { final buffer = StringBuffer(); diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/pubspec.yaml index a87edca..3fe6a25 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/pubspec.yaml +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/pubspec.yaml @@ -5,7 +5,7 @@ publish_to: 'none' environment: sdk: ^3.8.0 - flutter: ">=3.29.0" + flutter: ">=3.47.5" dependencies: flutter: @@ -13,25 +13,25 @@ dependencies: bloc: ^9.0.0 equatable: ^2.0.5 flutter_bloc: ^9.1.1 - go_router: ^15.1.3 + go_router: ^18.0.1 intl: ^0.20.2 json_annotation: ^4.9.0 - talker_flutter: ^4.4.1 + talker_flutter: ^5.1.20 di: path: ../di localization: path: ../localization -{{#firebase}} firebase_core: ^3.14.0 -{{/firebase}}{{#auth}} firebase_auth: ^5.3.4 -{{/auth}}{{#firestore}} cloud_firestore: ^5.5.3 - cloud_functions: ^5.2.3 +{{#firebase}} firebase_core: ^4.15.0 +{{/firebase}}{{#auth}} firebase_auth: ^6.7.0 +{{/auth}}{{#firestore}} cloud_firestore: ^6.10.0 + cloud_functions: ^6.5.0 {{/firestore}} dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 -dependency_overrides: - intl: ^0.20.2 flutter: + +resolution: workspace diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/test/core_test.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/test/core_test.dart index b4ed6c9..d03f759 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/test/core_test.dart +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/core/test/core_test.dart @@ -198,7 +198,7 @@ void main() { } routes.didReplace(); final log = TalkerRouteLog(route: unnamed, type: RouteLogType.push); - expect(log.key, TalkerLogType.route.key); + expect(log.key, TalkerKey.route); expect(log.pen, isA()); await bloc.close(); }, diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/components/network_url_image.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/components/network_url_image.dart index a80c2d0..d5a798b 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/components/network_url_image.dart +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/lib/components/network_url_image.dart @@ -20,7 +20,7 @@ class NetworkUrlImage extends StatelessWidget { @override Widget build(final BuildContext context) => CachedNetworkImage( imageUrl: url, - placeholder: (final context, final __) { + placeholder: (final context, final _) { final theme = Theme.of(context); return Container( diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/pubspec.yaml index f77567f..5267544 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/pubspec.yaml +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/design_system/pubspec.yaml @@ -5,14 +5,14 @@ publish_to: 'none' environment: sdk: ^3.8.0 - flutter: ">=3.29.0" + flutter: ">=3.47.5" dependencies: flutter: sdk: flutter - cached_network_image: ^3.4.1 + cached_network_image: ^4.0.0 flutter_svg: ^2.2.0 - google_fonts: ^6.2.1 + google_fonts: ^8.2.1 iconsax_flutter: ^1.0.1 loader_overlay: ^5.0.0 toastification: ^3.0.3 @@ -29,6 +29,8 @@ dev_dependencies: mocktail: ^1.0.4 flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 flutter: + +resolution: workspace diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/di/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/di/pubspec.yaml index 381b1d0..11bc3e9 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/di/pubspec.yaml +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/di/pubspec.yaml @@ -5,16 +5,18 @@ publish_to: 'none' environment: sdk: ^3.8.0 - flutter: ">=3.29.0" + flutter: ">=3.47.5" dependencies: flutter: sdk: flutter - get_it: ^8.0.3 + get_it: ^9.3.0 dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 flutter: + +resolution: workspace diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/pubspec.yaml index 76ebee1..15b116e 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/pubspec.yaml +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/localization/pubspec.yaml @@ -5,7 +5,7 @@ publish_to: 'none' environment: sdk: ^3.8.0 - flutter: ">=3.29.0" + flutter: ">=3.47.5" dependencies: flutter: @@ -15,7 +15,9 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 build_runner: ^2.4.15 flutter: + +resolution: workspace diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/utils/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/utils/pubspec.yaml index ba5d039..185022a 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/utils/pubspec.yaml +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/utils/pubspec.yaml @@ -5,7 +5,7 @@ publish_to: 'none' environment: sdk: ^3.8.0 - flutter: ">=3.29.0" + flutter: ">=3.47.5" dependencies: flutter: @@ -15,6 +15,8 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 flutter: + +resolution: workspace diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/client/firebase_analytics_client.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/client/firebase_analytics_client.dart index e37882a..7998d14 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/client/firebase_analytics_client.dart +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/client/firebase_analytics_client.dart @@ -144,7 +144,7 @@ class FirebaseAnalyticsClient implements AnalyticsClient { }) async { final eventParameters = { PredefinedParameters.screenName: screenName, - if (screenClass != null) PredefinedParameters.screenClass: screenClass, + PredefinedParameters.screenClass: ?screenClass, ...?parameters, }; @@ -165,7 +165,7 @@ class FirebaseAnalyticsClient implements AnalyticsClient { Map? parameters, }) async { final eventParameters = { - if (loginMethod != null) PredefinedParameters.method: loginMethod, + PredefinedParameters.method: ?loginMethod, ...?parameters, }; @@ -178,7 +178,7 @@ class FirebaseAnalyticsClient implements AnalyticsClient { Map? parameters, }) async { final eventParameters = { - if (signUpMethod != null) PredefinedParameters.method: signUpMethod, + PredefinedParameters.method: ?signUpMethod, ...?parameters, }; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/utils/analytics_helper.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/utils/analytics_helper.dart index 0ad1254..8ab2b16 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/utils/analytics_helper.dart +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/utils/analytics_helper.dart @@ -138,7 +138,7 @@ class AnalyticsHelper { AnalyticsEvents.feature.buttonPressed, parameters: { 'button_name': buttonName, - if (screenName != null) 'screen_name': screenName, + 'screen_name': ?screenName, 'timestamp': DateTime.now().toIso8601String(), ...?parameters, }, @@ -154,7 +154,7 @@ class AnalyticsHelper { AnalyticsEvents.error.appError, parameters: { 'error_type': errorType, - if (errorMessage != null) 'error_message': errorMessage, + 'error_message': ?errorMessage, 'timestamp': DateTime.now().toIso8601String(), ...?parameters, }, diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/pubspec.yaml index 4e26573..e30c6c3 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/pubspec.yaml +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{analytics_dir}}/pubspec.yaml @@ -5,12 +5,12 @@ publish_to: 'none' environment: sdk: ^3.8.0 - flutter: ">=3.29.0" + flutter: ">=3.47.5" dependencies: flutter: sdk: flutter - firebase_analytics: ^11.3.5 + firebase_analytics: ^12.6.0 core: path: ../core di: @@ -20,6 +20,8 @@ dev_dependencies: mocktail: ^1.0.4 flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 flutter: + +resolution: workspace diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/pubspec.yaml index 433f1f2..e2c7cd4 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/pubspec.yaml +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/pubspec.yaml @@ -5,13 +5,13 @@ publish_to: 'none' environment: sdk: ^3.8.0 - flutter: ">=3.29.0" + flutter: ">=3.47.5" dependencies: flutter: sdk: flutter - firebase_core: ^3.14.0 - firebase_crashlytics: ^4.1.4 + firebase_core: ^4.15.0 + firebase_crashlytics: ^5.4.0 core: path: ../core di: @@ -21,6 +21,8 @@ dev_dependencies: mocktail: ^1.0.4 flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 flutter: + +resolution: workspace diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/pubspec.yaml index f2f8c42..79f08cb 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/pubspec.yaml +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{developer_dir}}/pubspec.yaml @@ -5,13 +5,13 @@ publish_to: 'none' environment: sdk: ^3.8.0 - flutter: ">=3.29.0" + flutter: ">=3.47.5" dependencies: flutter: sdk: flutter - go_router: ^15.1.3 - talker_flutter: ^4.4.1 + go_router: ^18.0.1 + talker_flutter: ^5.1.20 core: path: ../core design_system: @@ -24,6 +24,8 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 flutter: + +resolution: workspace diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/pubspec.yaml index 2ae6a61..5047d8c 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/pubspec.yaml +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/pubspec.yaml @@ -5,13 +5,13 @@ publish_to: 'none' environment: sdk: ^3.8.0 - flutter: ">=3.29.0" + flutter: ">=3.47.5" dependencies: flutter: sdk: flutter - firebase_core: ^3.14.0 - firebase_remote_config: ^5.1.6 + firebase_core: ^4.15.0 + firebase_remote_config: ^6.7.0 core: path: ../core di: @@ -21,6 +21,8 @@ dev_dependencies: mocktail: ^1.0.4 flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 flutter: + +resolution: workspace diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/pubspec.yaml index 61fa86c..3cb218c 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/pubspec.yaml +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{network_dir}}/pubspec.yaml @@ -5,15 +5,15 @@ publish_to: 'none' environment: sdk: ^3.8.0 - flutter: ">=3.29.0" + flutter: ">=3.47.5" dependencies: flutter: sdk: flutter dio: ^5.7.0 json_annotation: ^4.9.0 - talker_dio_logger: ^4.9.1 - talker_flutter: ^4.4.1 + talker_dio_logger: ^5.1.20 + talker_flutter: ^5.1.20 core: path: ../core localization: @@ -25,8 +25,10 @@ dev_dependencies: mocktail: ^1.0.4 flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 build_runner: ^2.4.15 json_serializable: ^6.9.5 flutter: + +resolution: workspace diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/pubspec.yaml index 2c8c8f3..5836e4e 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/pubspec.yaml +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/packages/{{notifications_dir}}/pubspec.yaml @@ -5,16 +5,16 @@ publish_to: 'none' environment: sdk: ^3.8.0 - flutter: ">=3.29.0" + flutter: ">=3.47.5" dependencies: shared_preferences: ^2.5.3 flutter: sdk: flutter app_badge_plus: ^1.1.2 - device_info_plus: ^11.1.1 - firebase_core: ^3.14.0 - firebase_messaging: ^15.1.6 + device_info_plus: ^13.2.0 + firebase_core: ^4.15.0 + firebase_messaging: ^16.7.0 json_annotation: ^4.9.0 core: path: ../core @@ -27,9 +27,11 @@ dev_dependencies: mocktail: ^1.0.4 flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 build_runner: ^2.4.15 json_serializable: ^6.9.5 flutter: uses-material-design: true + +resolution: workspace diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/pubspec.yaml index 162dae0..ab655d7 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/pubspec.yaml +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/pubspec.yaml @@ -6,6 +6,95 @@ environment: sdk: '>=3.8.0 <4.0.0' dev_dependencies: - flutter_lints: ^5.0.0 - melos: ^6.2.0 + flutter_lints: ^6.0.0 + melos: ^8.8.0 path: ^1.9.1 + +workspace: + - apps/{{ name.snakeCase() }} + - packages/core + - packages/design_system + - packages/di + - packages/localization + - packages/utils +{{#network}} - packages/network +{{/network}}{{#notifications}} - packages/notifications +{{/notifications}}{{#analytics}} - packages/analytics +{{/analytics}}{{#crashlytics}} - packages/crashlytics +{{/crashlytics}}{{#feature_flags}} - packages/feature_flags +{{/feature_flags}}{{#developer}} - packages/developer +{{/developer}}{{#auth}} - features/auth +{{/auth}}{{#dashboard}} - features/dashboard +{{/dashboard}} +melos: + scripts: + clean:flutter: + description: "Run flutter clean in all Flutter packages" + exec: + command: flutter clean + concurrency: 4 + packageFilters: + flutter: true + + lint: + description: "Check formatting and analyze all workspace code" + run: dart run tool/check.dart + + fix: + description: "Apply Dart automated fixes to all packages" + exec: + command: dart fix --apply + concurrency: 5 + packageFilters: + dirExists: lib + + fix:dry: + description: "Show Dart automated fixes without applying them" + exec: + command: dart fix --dry-run + concurrency: 5 + packageFilters: + dirExists: lib + + generate: + description: "Run build_runner in every package that needs codegen" + exec: + command: dart run build_runner build --delete-conflicting-outputs + concurrency: 1 + packageFilters: + dependsOn: build_runner + + generate:i69n: + description: "Regenerate localization strings from messages.i69n.yaml" + exec: + command: | + dart run build_runner build --delete-conflicting-outputs && + dart format lib/messages.i69n.dart + concurrency: 1 + packageFilters: + scope: "localization" + + test: + description: "Run all tests (Dart and Flutter)" + run: dart run melos run test:select --no-select + + coverage: + description: "Run every test and require 100% handwritten line coverage" + run: dart run tool/coverage.dart + + test:select: + description: "Run tests in Flutter packages" + exec: + command: flutter test --coverage + concurrency: 5 + failFast: true + packageFilters: + dirExists: test + + ############################################################################### + # IDE CONFIGURATION + ############################################################################### + ide: + intellij: + enabled: true + moduleNamePrefix: '' diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/tool/check.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/tool/check.dart index b125563..d6e12fc 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/tool/check.dart +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/tool/check.dart @@ -36,7 +36,7 @@ Future main() async { sources.sort(); for (final arguments in [ ['format', '--output=none', '--set-exit-if-changed', ...sources], - ['analyze', '--fatal-infos', '.'], + ['analyze', '--fatal-infos', ...sources], ]) { final process = await Process.start('dart', arguments, mode: ProcessStartMode.inheritStdio); diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/tool/coverage.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/tool/coverage.dart index 31310cd..817a28f 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/tool/coverage.dart +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/__brick__/{{ name.snakeCase() }}/tool/coverage.dart @@ -75,7 +75,8 @@ Future main(List args) async { '// ignore_for_file: unused_import', for (final source in sources) "import 'package:$name/${p.relative(source.path, from: p.join(package.path, 'lib')).replaceAll(r'\', '/')}';", - 'void main() {}', + "import 'package:flutter_test/flutter_test.dart';", + 'void main() { TestWidgetsFlutterBinding.ensureInitialized(); }', ].join('\n'), ); try { diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/checked_process.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/checked_process.dart index d787477..36cedd5 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/checked_process.dart +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/checked_process.dart @@ -2,6 +2,13 @@ import 'dart:io'; import 'package:mason/mason.dart'; +typedef ProcessRunner = Future Function( + String executable, + List arguments, { + String? workingDirectory, + bool runInShell, +}); + /// Runs [executable] and throws when it fails. /// /// `cli_core`'s `trackOperation` reports success as soon as the future @@ -17,12 +24,13 @@ Future runChecked( required String executable, required List arguments, String? workingDirectory, + ProcessRunner runProcess = Process.run, }) async { final progress = context.logger.progress(startMessage); final ProcessResult result; try { - result = await Process.run( + result = await runProcess( executable, arguments, workingDirectory: workingDirectory, diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/flutter_create_command.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/flutter_create_command.dart index 7219430..e952dac 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/flutter_create_command.dart +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/flutter_create_command.dart @@ -12,6 +12,10 @@ import 'checked_process.dart'; /// This runs in pre-gen, so the brick's own `lib/` and `pubspec.yaml` are /// written over the counter-app that `flutter create` produces. final class FlutterCreateCommand extends CliCommand { + FlutterCreateCommand({this.runProcess = Process.run}); + + final ProcessRunner runProcess; + @override Future run(HookContext context) async { final String name = context.vars['name']; @@ -24,6 +28,7 @@ final class FlutterCreateCommand extends CliCommand { await runChecked( context, + runProcess: runProcess, startMessage: 'Creating Flutter app: $appName', endMessage: 'Flutter app created', executable: 'flutter', diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/format_command.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/format_command.dart index ad82202..21755c4 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/format_command.dart +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/format_command.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:cli_core/cli_core.dart' show CliCommand; import 'package:mason/mason.dart'; @@ -9,12 +11,17 @@ import 'checked_process.dart'; /// which modules were selected. Running the formatter once here means a fresh /// project passes its own `melos lint` whatever the answers were. final class FormatCommand extends CliCommand { + FormatCommand({this.runProcess = Process.run}); + + final ProcessRunner runProcess; + @override Future run(HookContext context) async { final String name = context.vars['name']; await runChecked( context, + runProcess: runProcess, startMessage: 'Formatting generated code', endMessage: 'Code formatted', executable: 'dart', diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/melos_command.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/melos_command.dart index afd29a1..58e7126 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/melos_command.dart +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/commands/melos_command.dart @@ -1,9 +1,15 @@ +import 'dart:io'; + import 'package:cli_core/cli_core.dart' show CliCommand; import 'package:mason/mason.dart'; import 'checked_process.dart'; final class MelosCommand extends CliCommand { + MelosCommand({this.runProcess = Process.run}); + + final ProcessRunner runProcess; + @override Future run(HookContext context) async { final String name = context.vars['name']; @@ -11,6 +17,7 @@ final class MelosCommand extends CliCommand { await runChecked( context, + runProcess: runProcess, startMessage: 'Installing workspace tooling', endMessage: 'Workspace tooling installed', executable: 'dart', @@ -20,6 +27,7 @@ final class MelosCommand extends CliCommand { await runChecked( context, + runProcess: runProcess, startMessage: 'Running melos bootstrap', endMessage: 'Dependencies installed', executable: 'dart', diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/post_gen.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/post_gen.dart index 2db5689..39e2cd3 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/post_gen.dart +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/post_gen.dart @@ -1,15 +1,18 @@ import 'dart:async'; +import 'dart:io'; import 'package:mason/mason.dart'; +import 'commands/checked_process.dart'; import 'commands/format_command.dart'; import 'commands/melos_command.dart'; import 'commands/next_steps_command.dart'; -Future run(HookContext context) async { +Future run(HookContext context, + {ProcessRunner runProcess = Process.run}) async { final commands = [ - FormatCommand(), - MelosCommand(), + MelosCommand(runProcess: runProcess), + FormatCommand(runProcess: runProcess), NextStepsCommand(), ]; diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/pre_gen.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/pre_gen.dart index a85eaba..2280a9a 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/pre_gen.dart +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/pre_gen.dart @@ -1,12 +1,15 @@ import 'dart:async'; +import 'dart:io'; import 'package:cli_core/cli_core.dart'; import 'package:mason/mason.dart'; +import 'commands/checked_process.dart'; import 'commands/flutter_create_command.dart'; import 'commands/module_vars.dart'; -Future run(HookContext context) async { +Future run(HookContext context, + {ProcessRunner runProcess = Process.run}) async { context.vars['is_mono_repo'] = await FileUtils.isMonoRepo(); // Turn the module answers into the `*_dir` path variables the brick uses to @@ -15,5 +18,5 @@ Future run(HookContext context) async { // The app is scaffolded before generation so the brick's `lib/` and // `pubspec.yaml` land on top of what `flutter create` produced. - await FlutterCreateCommand().run(context); + await FlutterCreateCommand(runProcess: runProcess).run(context); } diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/pubspec.yaml b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/pubspec.yaml index b4df3ed..a9e4162 100644 --- a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/pubspec.yaml +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/pubspec.yaml @@ -1,7 +1,7 @@ name: flutter_project_with_mono_repo_hooks environment: - sdk: '>=3.0.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: mason: ^0.1.2 @@ -10,5 +10,8 @@ dependencies: path: ^1.8.0 dev_dependencies: - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 test: ^1.21.0 + mocktail: ^1.0.5 + +resolution: workspace diff --git a/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/test/hooks_test.dart b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/test/hooks_test.dart new file mode 100644 index 0000000..87f6756 --- /dev/null +++ b/packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks/test/hooks_test.dart @@ -0,0 +1,315 @@ +import 'dart:io'; + +import 'package:mason/mason.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:test/test.dart'; + +import '../commands/checked_process.dart'; +import '../commands/flutter_create_command.dart'; +import '../commands/format_command.dart'; +import '../commands/melos_command.dart'; +import '../commands/module_vars.dart'; +import '../commands/next_steps_command.dart'; +import '../post_gen.dart' as post; +import '../pre_gen.dart' as pre; + +class _Context extends Mock implements HookContext {} + +class _Logger extends Mock implements Logger {} + +class _Progress extends Mock implements Progress {} + +void main() { + late _Context context; + late _Logger logger; + late _Progress progress; + late Map variables; + late Directory temporary; + late Directory original; + + setUp(() async { + context = _Context(); + logger = _Logger(); + progress = _Progress(); + variables = {'name': 'Sample App', 'description': 'An example'}; + when(() => context.vars).thenReturn(variables); + when(() => context.logger).thenReturn(logger); + when(() => logger.progress(any())).thenReturn(progress); + temporary = await Directory.systemTemp.createTemp('mono_hook_'); + original = Directory.current; + Directory.current = temporary; + }); + + tearDown(() async { + Directory.current = original; + await temporary.delete(recursive: true); + }); + + test('default commands use the system process runner', () { + expect(FlutterCreateCommand().runProcess, Process.run); + expect(FormatCommand().runProcess, Process.run); + expect(MelosCommand().runProcess, Process.run); + }); + + group('checked process', () { + test('waits for success before completing progress', () async { + var ran = false; + await runChecked( + context, + startMessage: 'Start', + endMessage: 'Done', + executable: 'example', + arguments: ['--flag'], + workingDirectory: 'workspace', + runProcess: (executable, arguments, + {workingDirectory, runInShell = false}) async { + expect(executable, 'example'); + expect(arguments, ['--flag']); + expect(workingDirectory, 'workspace'); + expect(runInShell, isTrue); + verifyNever(() => progress.complete(any())); + ran = true; + return ProcessResult(1, 0, 'success', ''); + }, + ); + expect(ran, isTrue); + verify(() => progress.complete('Done')).called(1); + verifyNever(() => progress.fail(any())); + }); + + test('process launch failures fail progress and retain original exception', + () async { + final error = ProcessException('missing', [], 'missing executable'); + await expectLater( + runChecked( + context, + startMessage: 'Start', + endMessage: 'Done', + executable: 'missing', + arguments: [], + runProcess: (_, __, {workingDirectory, runInShell = false}) async => + throw error, + ), + throwsA(same(error))); + verify(() => progress.fail('Start failed: $error')).called(1); + verifyNever(() => progress.complete(any())); + }); + + for (final output in [ + ('stdout detail', ' stderr detail ', 'stderr detail'), + (' stdout detail ', ' ', 'stdout detail'), + ('', '', ''), + ]) { + test('nonzero exits surface available output ${output.$3}', () async { + await expectLater( + runChecked( + context, + startMessage: 'Start', + endMessage: 'Done', + executable: 'example', + arguments: ['--flag'], + runProcess: (_, __, + {workingDirectory, runInShell = false}) async => + ProcessResult(1, 9, output.$1, output.$2), + ), + throwsA(isA() + .having((e) => e.executable, 'executable', 'example') + .having((e) => e.arguments, 'arguments', ['--flag']).having( + (e) => e.errorCode, 'exit', 9))); + verify(() => progress.fail('Start failed (exit 9)')).called(1); + verifyNever(() => progress.complete(any())); + if (output.$3.isNotEmpty) { + verify(() => logger.err(output.$3)).called(1); + } else { + verifyNever(() => logger.err(any())); + } + }); + } + }); + + test( + 'pre-generation detects workspace, resolves modules and removes obsolete scaffold', + () async { + await File('pubspec.yaml').writeAsString('name: parent\nworkspace: []\n'); + variables.addAll( + {'org_name': 'io.nonstop', 'notifications': true, 'developer': true}); + await pre.run(context, runProcess: (executable, arguments, + {workingDirectory, runInShell = false}) async { + expect(variables['is_mono_repo'], isTrue); + expect(variables['network_dir'], 'network'); + expect(variables['feature_flags_dir'], 'feature_flags'); + expect(executable, 'flutter'); + expect(arguments, [ + 'create', + 'sample_app', + '--template=app', + '--platforms=ios,android,web', + '--description=An example', + '--org=io.nonstop' + ]); + expect(workingDirectory, 'sample_app/apps'); + expect(await Directory(workingDirectory!).exists(), isTrue); + await Directory('sample_app/apps/sample_app/test') + .create(recursive: true); + await File('sample_app/apps/sample_app/analysis_options.yaml') + .writeAsString('old lints'); + await File('sample_app/apps/sample_app/test/widget_test.dart') + .writeAsString('old test'); + return ProcessResult(1, 0, '', ''); + }); + expect( + await File('sample_app/apps/sample_app/analysis_options.yaml').exists(), + isFalse); + expect( + await Directory('sample_app/apps/sample_app/test').exists(), isFalse); + verify(() => progress.complete('Flutter app created')).called(1); + }); + + test( + 'Flutter creation defaults organization and preserves non-scaffold tests', + () async { + final command = FlutterCreateCommand(runProcess: (executable, arguments, + {workingDirectory, runInShell = false}) async { + expect(arguments.last, '--org=com.example'); + await Directory('sample_app/apps/sample_app/test') + .create(recursive: true); + await File('sample_app/apps/sample_app/test/custom_test.dart') + .writeAsString('keep'); + return ProcessResult(1, 0, '', ''); + }); + await command.run(context); + expect( + await File('sample_app/apps/sample_app/test/custom_test.dart') + .readAsString(), + 'keep'); + }); + + test('Flutter creation tolerates a scaffold with no test directory', + () async { + await FlutterCreateCommand( + runProcess: (_, __, {workingDirectory, runInShell = false}) async => + ProcessResult(1, 0, '', '')).run(context); + expect(await Directory('sample_app/apps').exists(), isTrue); + }); + + test('post-generation bootstraps before formatting and prints setup guidance', + () async { + variables['firebase'] = true; + final calls = >[]; + await post.run(context, runProcess: (executable, arguments, + {workingDirectory, runInShell = false}) async { + calls.add([executable, arguments, workingDirectory, runInShell]); + return ProcessResult(1, 0, '', ''); + }); + expect(calls, [ + [ + 'dart', + ['pub', 'get'], + 'sample_app', + true + ], + [ + 'dart', + ['run', 'melos', 'bootstrap'], + 'sample_app', + true + ], + [ + 'dart', + ['format', '.'], + 'sample_app', + true + ], + ]); + verify(() => + logger.warn('Firebase modules are enabled but not configured yet.')) + .called(1); + verify(() => logger.info( + ' cd sample_app/apps/sample_app && flutterfire configure')).called(1); + verify(() => logger.info(' dart run melos run coverage')).called(1); + verify(() => logger.info(' cd sample_app/apps/sample_app && flutter run')) + .called(1); + }); + + test('failed dependency setup prevents formatting and next steps', () async { + final calls = []; + await expectLater( + post.run(context, runProcess: (executable, arguments, + {workingDirectory, runInShell = false}) async { + calls.add(arguments.first); + return ProcessResult(1, 1, '', 'dependency resolution failed'); + }), + throwsA(isA())); + expect(calls, ['pub']); + verifyNever(() => logger.info(any())); + }); + + test('next steps omit Firebase setup when no Firebase modules are enabled', + () async { + await NextStepsCommand().run(context); + verifyNever(() => logger.warn(any())); + verify(() => logger.info('Verify the workspace (from sample_app):')) + .called(1); + verify(() => logger.info(' dart run melos run lint')).called(1); + }); + + group('module variables', () { + test('missing flags disable all optional modules and Firebase files', () { + resolveModuleVars(context); + for (final module in optionalModules) { + expect(variables[module], isFalse); + expect(variables['${module}_dir'], ''); + } + for (final flag in [ + 'firestore', + 'firebase', + 'emulators', + 'firebase_sdk_mocks' + ]) { + expect(variables[flag], isFalse); + } + for (final file in [ + 'firebase_options_file', + 'notification_lifecycle_file', + 'notification_lifecycle_test_file', + 'firebase_bootstrap_test_file', + 'auth_token_provider_test_file' + ]) { + expect(variables[file], ''); + } + }); + + test( + 'enabled modules derive required dependencies and generated file names', + () { + variables.addAll({for (final module in optionalModules) module: true}); + variables.addAll( + {'network': false, 'feature_flags': false, 'firestore': true}); + resolveModuleVars(context); + for (final module in optionalModules) { + expect(variables[module], isTrue); + expect(variables['${module}_dir'], module); + } + for (final flag in ['firebase', 'emulators', 'firebase_sdk_mocks']) { + expect(variables[flag], isTrue); + } + expect(variables['firebase_options_file'], 'firebase_options.dart'); + expect(variables['notification_lifecycle_file'], + 'notification_lifecycle.dart'); + expect(variables['notification_lifecycle_test_file'], + 'notification_lifecycle_test.dart'); + expect(variables['firebase_bootstrap_test_file'], + 'firebase_bootstrap_test.dart'); + expect(variables['auth_token_provider_test_file'], + 'token_provider_test.dart'); + }); + + test('Firestore alone needs Firebase and emulators without SDK mocks', () { + variables['firestore'] = true; + resolveModuleVars(context); + expect(variables['firebase'], isTrue); + expect(variables['emulators'], isTrue); + expect(variables['firebase_sdk_mocks'], isFalse); + }); + }); +} diff --git a/packages/nonstop_cli/lib/commands/create/flutter_app_for_mono_repo_bundle.dart b/packages/nonstop_cli/lib/commands/create/flutter_app_for_mono_repo_bundle.dart index 138767a..236fa55 100644 --- a/packages/nonstop_cli/lib/commands/create/flutter_app_for_mono_repo_bundle.dart +++ b/packages/nonstop_cli/lib/commands/create/flutter_app_for_mono_repo_bundle.dart @@ -21,13 +21,13 @@ final flutterAppForMonoRepoBundle = MasonBundle.fromJson({ { "path": "commands/flutter_app_create_command.dart", "data": - "aW1wb3J0ICdwYWNrYWdlOmNsaV9jb3JlL2NsaV9jb3JlLmRhcnQnIHNob3cgQmFzZUZsdXR0ZXJDb21tYW5kOwppbXBvcnQgJ3BhY2thZ2U6bWFzb24vbWFzb24uZGFydCc7CgpmaW5hbCBjbGFzcyBGbHV0dGVyQXBwQ3JlYXRlQ29tbWFuZCBleHRlbmRzIEJhc2VGbHV0dGVyQ29tbWFuZCB7CiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IHJ1bihIb29rQ29udGV4dCBjb250ZXh0KSBhc3luYyB7CiAgICBmaW5hbCBTdHJpbmcgbmFtZSA9IGNvbnRleHQudmFyc1snbmFtZSddOwogICAgZmluYWwgU3RyaW5nIGRlc2NyaXB0aW9uID0gY29udGV4dC52YXJzWydkZXNjcmlwdGlvbiddOwogICAgZmluYWwgU3RyaW5nIG9yZ05hbWUgPSBjb250ZXh0LnZhcnNbJ29yZ19uYW1lJ10gPz8gJ2NvbS5leGFtcGxlJzsKICAgIGZpbmFsIGFwcE5hbWUgPSBuYW1lLnNuYWtlQ2FzZTsKCiAgICBhd2FpdCBjcmVhdGVGbHV0dGVyUHJvamVjdCgKICAgICAgY29udGV4dDogY29udGV4dCwKICAgICAgbmFtZTogYXBwTmFtZSwKICAgICAgZGVzY3JpcHRpb246IGRlc2NyaXB0aW9uLAogICAgICBvdXRwdXRQYXRoOiAnLicsCiAgICAgIG9yZ05hbWU6IG9yZ05hbWUsCiAgICApOwoKICAgIGZpbmFsIGlzTW9ub1JlcG8gPSBjb250ZXh0LnZhcnNbJ2lzX21vbm9fcmVwbyddID8/IGZhbHNlOwogICAgaWYgKGlzTW9ub1JlcG8pIHsKICAgICAgYXdhaXQgcmVtb3ZlQW5hbHlzaXNPcHRpb25zKAogICAgICAgIGNvbnRleHQ6IGNvbnRleHQsCiAgICAgICAgcHJvamVjdFBhdGg6IGFwcE5hbWUsCiAgICAgICk7CiAgICB9CiAgfQp9Cg==", + "aW1wb3J0ICdwYWNrYWdlOmNsaV9jb3JlL2NsaV9jb3JlLmRhcnQnIHNob3cgQmFzZUZsdXR0ZXJDb21tYW5kLCBDbGlDb21tYW5kOwppbXBvcnQgJ3BhY2thZ2U6bWFzb24vbWFzb24uZGFydCc7CgpmaW5hbCBjbGFzcyBGbHV0dGVyQXBwQ3JlYXRlQ29tbWFuZCBleHRlbmRzIENsaUNvbW1hbmQgewogIEZsdXR0ZXJBcHBDcmVhdGVDb21tYW5kKHtCYXNlRmx1dHRlckNvbW1hbmQ/IGZsdXR0ZXJ9KQogICAgICA6IF9mbHV0dGVyID0gZmx1dHRlciA/PyBCYXNlRmx1dHRlckNvbW1hbmQoKTsKCiAgZmluYWwgQmFzZUZsdXR0ZXJDb21tYW5kIF9mbHV0dGVyOwoKICBAb3ZlcnJpZGUKICBGdXR1cmU8dm9pZD4gcnVuKEhvb2tDb250ZXh0IGNvbnRleHQpIGFzeW5jIHsKICAgIGZpbmFsIFN0cmluZyBuYW1lID0gY29udGV4dC52YXJzWyduYW1lJ107CiAgICBmaW5hbCBTdHJpbmcgZGVzY3JpcHRpb24gPSBjb250ZXh0LnZhcnNbJ2Rlc2NyaXB0aW9uJ107CiAgICBmaW5hbCBTdHJpbmcgb3JnTmFtZSA9IGNvbnRleHQudmFyc1snb3JnX25hbWUnXSA/PyAnY29tLmV4YW1wbGUnOwogICAgZmluYWwgYXBwTmFtZSA9IG5hbWUuc25ha2VDYXNlOwoKICAgIGF3YWl0IF9mbHV0dGVyLmNyZWF0ZUZsdXR0ZXJQcm9qZWN0KAogICAgICBjb250ZXh0OiBjb250ZXh0LAogICAgICBuYW1lOiBhcHBOYW1lLAogICAgICBkZXNjcmlwdGlvbjogZGVzY3JpcHRpb24sCiAgICAgIG91dHB1dFBhdGg6ICcuJywKICAgICAgb3JnTmFtZTogb3JnTmFtZSwKICAgICk7CgogICAgZmluYWwgaXNNb25vUmVwbyA9IGNvbnRleHQudmFyc1snaXNfbW9ub19yZXBvJ10gPz8gZmFsc2U7CiAgICBpZiAoaXNNb25vUmVwbykgewogICAgICBhd2FpdCBfZmx1dHRlci5yZW1vdmVBbmFseXNpc09wdGlvbnMoCiAgICAgICAgY29udGV4dDogY29udGV4dCwKICAgICAgICBwcm9qZWN0UGF0aDogYXBwTmFtZSwKICAgICAgKTsKICAgIH0KICB9Cn0K", "type": "text" }, { "path": "post_gen.dart", "data": - "aW1wb3J0ICdkYXJ0OmFzeW5jJzsKCmltcG9ydCAncGFja2FnZTptYXNvbi9tYXNvbi5kYXJ0JzsKCmltcG9ydCAnY29tbWFuZHMvZmx1dHRlcl9hcHBfY3JlYXRlX2NvbW1hbmQuZGFydCc7CgpGdXR1cmU8dm9pZD4gcnVuKEhvb2tDb250ZXh0IGNvbnRleHQpIGFzeW5jIHsKICBmaW5hbCBjb21tYW5kcyA9IFsKICAgIEZsdXR0ZXJBcHBDcmVhdGVDb21tYW5kKCksCiAgXTsKCiAgZm9yIChmaW5hbCBjb21tYW5kIGluIGNvbW1hbmRzKSB7CiAgICBhd2FpdCBjb21tYW5kLnJ1bihjb250ZXh0KTsKICB9Cn0K", + "aW1wb3J0ICdkYXJ0OmFzeW5jJzsKCmltcG9ydCAncGFja2FnZTpjbGlfY29yZS9jbGlfY29yZS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOm1hc29uL21hc29uLmRhcnQnOwoKaW1wb3J0ICdjb21tYW5kcy9mbHV0dGVyX2FwcF9jcmVhdGVfY29tbWFuZC5kYXJ0JzsKCkZ1dHVyZTx2b2lkPiBydW4oSG9va0NvbnRleHQgY29udGV4dCwge0Jhc2VGbHV0dGVyQ29tbWFuZD8gZmx1dHRlcn0pIGFzeW5jIHsKICBmaW5hbCBjb21tYW5kcyA9IFsKICAgIEZsdXR0ZXJBcHBDcmVhdGVDb21tYW5kKGZsdXR0ZXI6IGZsdXR0ZXIpLAogIF07CgogIGZvciAoZmluYWwgY29tbWFuZCBpbiBjb21tYW5kcykgewogICAgYXdhaXQgY29tbWFuZC5ydW4oY29udGV4dCk7CiAgfQp9Cg==", "type": "text" }, { @@ -39,7 +39,7 @@ final flutterAppForMonoRepoBundle = MasonBundle.fromJson({ { "path": "pubspec.yaml", "data": - "bmFtZTogZmx1dHRlcl9hcHBfZm9yX21vbm9fcmVwb19ob29rcwoKZW52aXJvbm1lbnQ6CiAgc2RrOiAnPj0zLjAuMCA8NC4wLjAnCgpkZXBlbmRlbmNpZXM6CiAgbWFzb246IF4wLjEuMgogIG1hc29uX2xvZ2dlcjogXjAuMy4xCiAgY2xpX2NvcmU6IF4wLjAuMgoKZGV2X2RlcGVuZGVuY2llczoKICBmbHV0dGVyX2xpbnRzOiBeNS4wLjAKICB0ZXN0OiBeMS4yMS4wCg==", + "bmFtZTogZmx1dHRlcl9hcHBfZm9yX21vbm9fcmVwb19ob29rcwoKZW52aXJvbm1lbnQ6CiAgc2RrOiAnPj0zLjYuMCA8NC4wLjAnCgpkZXBlbmRlbmNpZXM6CiAgbWFzb246IF4wLjEuMgogIG1hc29uX2xvZ2dlcjogXjAuMy4xCiAgY2xpX2NvcmU6IF4wLjAuMgoKZGV2X2RlcGVuZGVuY2llczoKICBmbHV0dGVyX2xpbnRzOiBeNi4wLjAKICB0ZXN0OiBeMS4yMS4wCiAgbW9ja3RhaWw6IF4xLjAuNQoK", "type": "text" } ], diff --git a/packages/nonstop_cli/lib/commands/create/flutter_package_for_mono_repo_bundle.dart b/packages/nonstop_cli/lib/commands/create/flutter_package_for_mono_repo_bundle.dart index 71588d0..daa4103 100644 --- a/packages/nonstop_cli/lib/commands/create/flutter_package_for_mono_repo_bundle.dart +++ b/packages/nonstop_cli/lib/commands/create/flutter_package_for_mono_repo_bundle.dart @@ -21,13 +21,13 @@ final flutterPackageForMonoRepoBundle = MasonBundle.fromJson({ { "path": "commands/flutter_package_create_command.dart", "data": - "aW1wb3J0ICdwYWNrYWdlOmNsaV9jb3JlL2NsaV9jb3JlLmRhcnQnIHNob3cgQmFzZUZsdXR0ZXJDb21tYW5kOwppbXBvcnQgJ3BhY2thZ2U6bWFzb24vbWFzb24uZGFydCc7CgpmaW5hbCBjbGFzcyBGbHV0dGVyUGFja2FnZUNyZWF0ZUNvbW1hbmQgZXh0ZW5kcyBCYXNlRmx1dHRlckNvbW1hbmQgewogIEBvdmVycmlkZQogIEZ1dHVyZTx2b2lkPiBydW4oSG9va0NvbnRleHQgY29udGV4dCkgYXN5bmMgewogICAgZmluYWwgU3RyaW5nIG5hbWUgPSBjb250ZXh0LnZhcnNbJ25hbWUnXTsKICAgIGZpbmFsIFN0cmluZyBkZXNjcmlwdGlvbiA9IGNvbnRleHQudmFyc1snZGVzY3JpcHRpb24nXTsKICAgIGZpbmFsIGFwcE5hbWUgPSBuYW1lLnNuYWtlQ2FzZTsKCiAgICBhd2FpdCBjcmVhdGVGbHV0dGVyUHJvamVjdCgKICAgICAgY29udGV4dDogY29udGV4dCwKICAgICAgbmFtZTogYXBwTmFtZSwKICAgICAgZGVzY3JpcHRpb246IGRlc2NyaXB0aW9uLAogICAgICBvdXRwdXRQYXRoOiAnLicsCiAgICAgIHRlbXBsYXRlOiAncGFja2FnZScsCiAgICApOwoKICAgIGZpbmFsIGlzTW9ub1JlcG8gPSBjb250ZXh0LnZhcnNbJ2lzX21vbm9fcmVwbyddID8/IGZhbHNlOwogICAgaWYgKGlzTW9ub1JlcG8pIHsKICAgICAgYXdhaXQgcmVtb3ZlQW5hbHlzaXNPcHRpb25zKAogICAgICAgIGNvbnRleHQ6IGNvbnRleHQsCiAgICAgICAgcHJvamVjdFBhdGg6IGFwcE5hbWUsCiAgICAgICk7CiAgICB9CiAgfQp9Cg==", + "aW1wb3J0ICdwYWNrYWdlOmNsaV9jb3JlL2NsaV9jb3JlLmRhcnQnIHNob3cgQmFzZUZsdXR0ZXJDb21tYW5kLCBDbGlDb21tYW5kOwppbXBvcnQgJ3BhY2thZ2U6bWFzb24vbWFzb24uZGFydCc7CgpmaW5hbCBjbGFzcyBGbHV0dGVyUGFja2FnZUNyZWF0ZUNvbW1hbmQgZXh0ZW5kcyBDbGlDb21tYW5kIHsKICBGbHV0dGVyUGFja2FnZUNyZWF0ZUNvbW1hbmQoe0Jhc2VGbHV0dGVyQ29tbWFuZD8gZmx1dHRlcn0pCiAgICAgIDogX2ZsdXR0ZXIgPSBmbHV0dGVyID8/IEJhc2VGbHV0dGVyQ29tbWFuZCgpOwoKICBmaW5hbCBCYXNlRmx1dHRlckNvbW1hbmQgX2ZsdXR0ZXI7CgogIEBvdmVycmlkZQogIEZ1dHVyZTx2b2lkPiBydW4oSG9va0NvbnRleHQgY29udGV4dCkgYXN5bmMgewogICAgZmluYWwgU3RyaW5nIG5hbWUgPSBjb250ZXh0LnZhcnNbJ25hbWUnXTsKICAgIGZpbmFsIFN0cmluZyBkZXNjcmlwdGlvbiA9IGNvbnRleHQudmFyc1snZGVzY3JpcHRpb24nXTsKICAgIGZpbmFsIGFwcE5hbWUgPSBuYW1lLnNuYWtlQ2FzZTsKCiAgICBhd2FpdCBfZmx1dHRlci5jcmVhdGVGbHV0dGVyUHJvamVjdCgKICAgICAgY29udGV4dDogY29udGV4dCwKICAgICAgbmFtZTogYXBwTmFtZSwKICAgICAgZGVzY3JpcHRpb246IGRlc2NyaXB0aW9uLAogICAgICBvdXRwdXRQYXRoOiAnLicsCiAgICAgIHRlbXBsYXRlOiAncGFja2FnZScsCiAgICApOwoKICAgIGZpbmFsIGlzTW9ub1JlcG8gPSBjb250ZXh0LnZhcnNbJ2lzX21vbm9fcmVwbyddID8/IGZhbHNlOwogICAgaWYgKGlzTW9ub1JlcG8pIHsKICAgICAgYXdhaXQgX2ZsdXR0ZXIucmVtb3ZlQW5hbHlzaXNPcHRpb25zKAogICAgICAgIGNvbnRleHQ6IGNvbnRleHQsCiAgICAgICAgcHJvamVjdFBhdGg6IGFwcE5hbWUsCiAgICAgICk7CiAgICB9CiAgfQp9Cg==", "type": "text" }, { "path": "post_gen.dart", "data": - "aW1wb3J0ICdkYXJ0OmFzeW5jJzsKCmltcG9ydCAncGFja2FnZTptYXNvbi9tYXNvbi5kYXJ0JzsKCmltcG9ydCAnY29tbWFuZHMvZmx1dHRlcl9wYWNrYWdlX2NyZWF0ZV9jb21tYW5kLmRhcnQnOwoKRnV0dXJlPHZvaWQ+IHJ1bihIb29rQ29udGV4dCBjb250ZXh0KSBhc3luYyB7CiAgZmluYWwgY29tbWFuZHMgPSBbCiAgICBGbHV0dGVyUGFja2FnZUNyZWF0ZUNvbW1hbmQoKSwKICBdOwoKICBmb3IgKGZpbmFsIGNvbW1hbmQgaW4gY29tbWFuZHMpIHsKICAgIGF3YWl0IGNvbW1hbmQucnVuKGNvbnRleHQpOwogIH0KfQo=", + "aW1wb3J0ICdkYXJ0OmFzeW5jJzsKCmltcG9ydCAncGFja2FnZTpjbGlfY29yZS9jbGlfY29yZS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOm1hc29uL21hc29uLmRhcnQnOwoKaW1wb3J0ICdjb21tYW5kcy9mbHV0dGVyX3BhY2thZ2VfY3JlYXRlX2NvbW1hbmQuZGFydCc7CgpGdXR1cmU8dm9pZD4gcnVuKEhvb2tDb250ZXh0IGNvbnRleHQsIHtCYXNlRmx1dHRlckNvbW1hbmQ/IGZsdXR0ZXJ9KSBhc3luYyB7CiAgZmluYWwgY29tbWFuZHMgPSBbCiAgICBGbHV0dGVyUGFja2FnZUNyZWF0ZUNvbW1hbmQoZmx1dHRlcjogZmx1dHRlciksCiAgXTsKCiAgZm9yIChmaW5hbCBjb21tYW5kIGluIGNvbW1hbmRzKSB7CiAgICBhd2FpdCBjb21tYW5kLnJ1bihjb250ZXh0KTsKICB9Cn0K", "type": "text" }, { @@ -39,7 +39,7 @@ final flutterPackageForMonoRepoBundle = MasonBundle.fromJson({ { "path": "pubspec.yaml", "data": - "bmFtZTogZmx1dHRlcl9wYWNrYWdlX2Zvcl9tb25vX3JlcG9faG9va3MKCmVudmlyb25tZW50OgogIHNkazogJz49My4wLjAgPDQuMC4wJwoKZGVwZW5kZW5jaWVzOgogIG1hc29uOiBeMC4xLjIKICBtYXNvbl9sb2dnZXI6IF4wLjMuMQogIGNsaV9jb3JlOiBeMC4wLjIKCmRldl9kZXBlbmRlbmNpZXM6CiAgZmx1dHRlcl9saW50czogXjUuMC4wCiAgdGVzdDogXjEuMjEuMAo=", + "bmFtZTogZmx1dHRlcl9wYWNrYWdlX2Zvcl9tb25vX3JlcG9faG9va3MKCmVudmlyb25tZW50OgogIHNkazogJz49My42LjAgPDQuMC4wJwoKZGVwZW5kZW5jaWVzOgogIG1hc29uOiBeMC4xLjIKICBtYXNvbl9sb2dnZXI6IF4wLjMuMQogIGNsaV9jb3JlOiBeMC4wLjIKCmRldl9kZXBlbmRlbmNpZXM6CiAgZmx1dHRlcl9saW50czogXjYuMC4wCiAgdGVzdDogXjEuMjEuMAogIG1vY2t0YWlsOiBeMS4wLjUKCg==", "type": "text" } ], diff --git a/packages/nonstop_cli/lib/commands/create/flutter_plugin_for_mono_repo_bundle.dart b/packages/nonstop_cli/lib/commands/create/flutter_plugin_for_mono_repo_bundle.dart index 40ccb2f..02f9ba6 100644 --- a/packages/nonstop_cli/lib/commands/create/flutter_plugin_for_mono_repo_bundle.dart +++ b/packages/nonstop_cli/lib/commands/create/flutter_plugin_for_mono_repo_bundle.dart @@ -21,13 +21,13 @@ final flutterPluginForMonoRepoBundle = MasonBundle.fromJson({ { "path": "commands/flutter_plugin_create_command.dart", "data": - "aW1wb3J0ICdwYWNrYWdlOmNsaV9jb3JlL2NsaV9jb3JlLmRhcnQnIHNob3cgQmFzZUZsdXR0ZXJDb21tYW5kOwppbXBvcnQgJ3BhY2thZ2U6bWFzb24vbWFzb24uZGFydCc7CgpmaW5hbCBjbGFzcyBGbHV0dGVyUGx1Z2luQ3JlYXRlQ29tbWFuZCBleHRlbmRzIEJhc2VGbHV0dGVyQ29tbWFuZCB7CiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IHJ1bihIb29rQ29udGV4dCBjb250ZXh0KSBhc3luYyB7CiAgICBmaW5hbCBTdHJpbmcgbmFtZSA9IGNvbnRleHQudmFyc1snbmFtZSddOwogICAgZmluYWwgU3RyaW5nIGRlc2NyaXB0aW9uID0gY29udGV4dC52YXJzWydkZXNjcmlwdGlvbiddOwogICAgZmluYWwgU3RyaW5nIG9yZ05hbWUgPSBjb250ZXh0LnZhcnNbJ29yZ19uYW1lJ10gPz8gJ2NvbS5leGFtcGxlJzsKICAgIGZpbmFsIGFwcE5hbWUgPSBuYW1lLnNuYWtlQ2FzZTsKCiAgICBhd2FpdCBjcmVhdGVGbHV0dGVyUHJvamVjdCgKICAgICAgY29udGV4dDogY29udGV4dCwKICAgICAgbmFtZTogYXBwTmFtZSwKICAgICAgZGVzY3JpcHRpb246IGRlc2NyaXB0aW9uLAogICAgICBvdXRwdXRQYXRoOiAnLicsCiAgICAgIHRlbXBsYXRlOiAncGx1Z2luJywKICAgICAgb3JnTmFtZTogb3JnTmFtZSwKICAgICk7CgogICAgZmluYWwgaXNNb25vUmVwbyA9IGNvbnRleHQudmFyc1snaXNfbW9ub19yZXBvJ10gPz8gZmFsc2U7CiAgICBpZiAoaXNNb25vUmVwbykgewogICAgICBhd2FpdCByZW1vdmVBbmFseXNpc09wdGlvbnMoCiAgICAgICAgY29udGV4dDogY29udGV4dCwKICAgICAgICBwcm9qZWN0UGF0aDogYXBwTmFtZSwKICAgICAgKTsKICAgIH0KICB9Cn0K", + "aW1wb3J0ICdwYWNrYWdlOmNsaV9jb3JlL2NsaV9jb3JlLmRhcnQnIHNob3cgQmFzZUZsdXR0ZXJDb21tYW5kLCBDbGlDb21tYW5kOwppbXBvcnQgJ3BhY2thZ2U6bWFzb24vbWFzb24uZGFydCc7CgpmaW5hbCBjbGFzcyBGbHV0dGVyUGx1Z2luQ3JlYXRlQ29tbWFuZCBleHRlbmRzIENsaUNvbW1hbmQgewogIEZsdXR0ZXJQbHVnaW5DcmVhdGVDb21tYW5kKHtCYXNlRmx1dHRlckNvbW1hbmQ/IGZsdXR0ZXJ9KQogICAgICA6IF9mbHV0dGVyID0gZmx1dHRlciA/PyBCYXNlRmx1dHRlckNvbW1hbmQoKTsKCiAgZmluYWwgQmFzZUZsdXR0ZXJDb21tYW5kIF9mbHV0dGVyOwoKICBAb3ZlcnJpZGUKICBGdXR1cmU8dm9pZD4gcnVuKEhvb2tDb250ZXh0IGNvbnRleHQpIGFzeW5jIHsKICAgIGZpbmFsIFN0cmluZyBuYW1lID0gY29udGV4dC52YXJzWyduYW1lJ107CiAgICBmaW5hbCBTdHJpbmcgZGVzY3JpcHRpb24gPSBjb250ZXh0LnZhcnNbJ2Rlc2NyaXB0aW9uJ107CiAgICBmaW5hbCBTdHJpbmcgb3JnTmFtZSA9IGNvbnRleHQudmFyc1snb3JnX25hbWUnXSA/PyAnY29tLmV4YW1wbGUnOwogICAgZmluYWwgYXBwTmFtZSA9IG5hbWUuc25ha2VDYXNlOwoKICAgIGF3YWl0IF9mbHV0dGVyLmNyZWF0ZUZsdXR0ZXJQcm9qZWN0KAogICAgICBjb250ZXh0OiBjb250ZXh0LAogICAgICBuYW1lOiBhcHBOYW1lLAogICAgICBkZXNjcmlwdGlvbjogZGVzY3JpcHRpb24sCiAgICAgIG91dHB1dFBhdGg6ICcuJywKICAgICAgdGVtcGxhdGU6ICdwbHVnaW4nLAogICAgICBvcmdOYW1lOiBvcmdOYW1lLAogICAgKTsKCiAgICBmaW5hbCBpc01vbm9SZXBvID0gY29udGV4dC52YXJzWydpc19tb25vX3JlcG8nXSA/PyBmYWxzZTsKICAgIGlmIChpc01vbm9SZXBvKSB7CiAgICAgIGF3YWl0IF9mbHV0dGVyLnJlbW92ZUFuYWx5c2lzT3B0aW9ucygKICAgICAgICBjb250ZXh0OiBjb250ZXh0LAogICAgICAgIHByb2plY3RQYXRoOiBhcHBOYW1lLAogICAgICApOwogICAgfQogIH0KfQo=", "type": "text" }, { "path": "post_gen.dart", "data": - "aW1wb3J0ICdkYXJ0OmFzeW5jJzsKCmltcG9ydCAncGFja2FnZTptYXNvbi9tYXNvbi5kYXJ0JzsKCmltcG9ydCAnY29tbWFuZHMvZmx1dHRlcl9wbHVnaW5fY3JlYXRlX2NvbW1hbmQuZGFydCc7CgpGdXR1cmU8dm9pZD4gcnVuKEhvb2tDb250ZXh0IGNvbnRleHQpIGFzeW5jIHsKICBmaW5hbCBjb21tYW5kcyA9IFsKICAgIEZsdXR0ZXJQbHVnaW5DcmVhdGVDb21tYW5kKCksCiAgXTsKCiAgZm9yIChmaW5hbCBjb21tYW5kIGluIGNvbW1hbmRzKSB7CiAgICBhd2FpdCBjb21tYW5kLnJ1bihjb250ZXh0KTsKICB9Cn0K", + "aW1wb3J0ICdkYXJ0OmFzeW5jJzsKCmltcG9ydCAncGFja2FnZTpjbGlfY29yZS9jbGlfY29yZS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOm1hc29uL21hc29uLmRhcnQnOwoKaW1wb3J0ICdjb21tYW5kcy9mbHV0dGVyX3BsdWdpbl9jcmVhdGVfY29tbWFuZC5kYXJ0JzsKCkZ1dHVyZTx2b2lkPiBydW4oSG9va0NvbnRleHQgY29udGV4dCwge0Jhc2VGbHV0dGVyQ29tbWFuZD8gZmx1dHRlcn0pIGFzeW5jIHsKICBmaW5hbCBjb21tYW5kcyA9IFsKICAgIEZsdXR0ZXJQbHVnaW5DcmVhdGVDb21tYW5kKGZsdXR0ZXI6IGZsdXR0ZXIpLAogIF07CgogIGZvciAoZmluYWwgY29tbWFuZCBpbiBjb21tYW5kcykgewogICAgYXdhaXQgY29tbWFuZC5ydW4oY29udGV4dCk7CiAgfQp9Cg==", "type": "text" }, { @@ -39,7 +39,7 @@ final flutterPluginForMonoRepoBundle = MasonBundle.fromJson({ { "path": "pubspec.yaml", "data": - "bmFtZTogZmx1dHRlcl9wbHVnaW5fZm9yX21vbm9fcmVwb19ob29rcwoKZW52aXJvbm1lbnQ6CiAgc2RrOiAnPj0zLjAuMCA8NC4wLjAnCgpkZXBlbmRlbmNpZXM6CiAgbWFzb246IF4wLjEuMgogIG1hc29uX2xvZ2dlcjogXjAuMy4xCiAgY2xpX2NvcmU6IF4wLjAuMgoKZGV2X2RlcGVuZGVuY2llczoKICBmbHV0dGVyX2xpbnRzOiBeNS4wLjAKICB0ZXN0OiBeMS4yMS4wCg==", + "bmFtZTogZmx1dHRlcl9wbHVnaW5fZm9yX21vbm9fcmVwb19ob29rcwoKZW52aXJvbm1lbnQ6CiAgc2RrOiAnPj0zLjYuMCA8NC4wLjAnCgpkZXBlbmRlbmNpZXM6CiAgbWFzb246IF4wLjEuMgogIG1hc29uX2xvZ2dlcjogXjAuMy4xCiAgY2xpX2NvcmU6IF4wLjAuMgoKZGV2X2RlcGVuZGVuY2llczoKICBmbHV0dGVyX2xpbnRzOiBeNi4wLjAKICB0ZXN0OiBeMS4yMS4wCiAgbW9ja3RhaWw6IF4xLjAuNQoK", "type": "text" } ], diff --git a/packages/nonstop_cli/lib/commands/create/flutter_project_with_mono_repo_bundle.dart b/packages/nonstop_cli/lib/commands/create/flutter_project_with_mono_repo_bundle.dart index 18aa27c..f07b36b 100644 --- a/packages/nonstop_cli/lib/commands/create/flutter_project_with_mono_repo_bundle.dart +++ b/packages/nonstop_cli/lib/commands/create/flutter_project_with_mono_repo_bundle.dart @@ -8,7 +8,7 @@ final flutterProjectWithMonoRepoBundle = MasonBundle.fromJson({ { "path": "{{ name.snakeCase() }}/.github/workflows/quality.yml", "data": - "bmFtZTogVGVtcGxhdGUgcXVhbGl0eQpvbjogW3B1c2gsIHB1bGxfcmVxdWVzdF0KcGVybWlzc2lvbnM6CiAgY29udGVudHM6IHJlYWQKam9iczoKICBxdWFsaXR5OgogICAgcnVucy1vbjogdWJ1bnR1LWxhdGVzdAogICAgc3RlcHM6CiAgICAgIC0gdXNlczogYWN0aW9ucy9jaGVja291dEB2NAogICAgICAtIHVzZXM6IHN1Ym9zaXRvL2ZsdXR0ZXItYWN0aW9uQHYyCiAgICAgICAgd2l0aDoKICAgICAgICAgIGZsdXR0ZXItdmVyc2lvbjogJzMuNDQuMCcKICAgICAgICAgIGNhY2hlOiB0cnVlCiAgICAgIC0gcnVuOiBkYXJ0IHB1YiBnZXQKICAgICAgLSBydW46IGRhcnQgcnVuIG1lbG9zIGJvb3RzdHJhcAogICAgICAtIHJ1bjogZGFydCBydW4gbWVsb3MgcnVuIGxpbnQKICAgICAgLSBydW46IGRhcnQgcnVuIG1lbG9zIHJ1biBjb3ZlcmFnZQogICAgICAtIHJ1bjogZmx1dHRlciBidWlsZCB3ZWIKICAgICAgICB3b3JraW5nLWRpcmVjdG9yeTogYXBwcy97e25hbWUuc25ha2VDYXNlKCl9fQogICAgICAtIHVzZXM6IGFjdGlvbnMvdXBsb2FkLWFydGlmYWN0QHY0CiAgICAgICAgaWY6IGFsd2F5cygpCiAgICAgICAgd2l0aDoKICAgICAgICAgIG5hbWU6IGNvdmVyYWdlCiAgICAgICAgICBwYXRoOiBjb3ZlcmFnZS9sY292LmluZm8K", + "bmFtZTogVGVtcGxhdGUgcXVhbGl0eQpvbjogW3B1c2gsIHB1bGxfcmVxdWVzdF0KcGVybWlzc2lvbnM6CiAgY29udGVudHM6IHJlYWQKam9iczoKICBxdWFsaXR5OgogICAgcnVucy1vbjogdWJ1bnR1LWxhdGVzdAogICAgc3RlcHM6CiAgICAgIC0gdXNlczogYWN0aW9ucy9jaGVja291dEB2NAogICAgICAtIHVzZXM6IHN1Ym9zaXRvL2ZsdXR0ZXItYWN0aW9uQHYyCiAgICAgICAgd2l0aDoKICAgICAgICAgIGZsdXR0ZXItdmVyc2lvbjogJzMuNDcuNScKICAgICAgICAgIGNhY2hlOiB0cnVlCiAgICAgIC0gcnVuOiBkYXJ0IHB1YiBnZXQKICAgICAgLSBydW46IGRhcnQgcnVuIG1lbG9zIGJvb3RzdHJhcAogICAgICAtIHJ1bjogZGFydCBydW4gbWVsb3MgcnVuIGxpbnQKICAgICAgLSBydW46IGRhcnQgcnVuIG1lbG9zIHJ1biBjb3ZlcmFnZQogICAgICAtIHJ1bjogZmx1dHRlciBidWlsZCB3ZWIKICAgICAgICB3b3JraW5nLWRpcmVjdG9yeTogYXBwcy97e25hbWUuc25ha2VDYXNlKCl9fQogICAgICAtIHVzZXM6IGFjdGlvbnMvdXBsb2FkLWFydGlmYWN0QHY0CiAgICAgICAgaWY6IGFsd2F5cygpCiAgICAgICAgd2l0aDoKICAgICAgICAgIG5hbWU6IGNvdmVyYWdlCiAgICAgICAgICBwYXRoOiBjb3ZlcmFnZS9sY292LmluZm8K", "type": "text" }, { @@ -80,7 +80,7 @@ final flutterProjectWithMonoRepoBundle = MasonBundle.fromJson({ { "path": "{{ name.snakeCase() }}/apps/{{ name.snakeCase() }}/pubspec.yaml", "data": - "bmFtZToge3tuYW1lLnNuYWtlQ2FzZSgpfX0KZGVzY3JpcHRpb246ICJ7e3tkZXNjcmlwdGlvbn19fSIKcHVibGlzaF90bzogJ25vbmUnCnZlcnNpb246IDAuMS4wKzEKCmVudmlyb25tZW50OgogIHNkazogXjMuOC4wCiAgZmx1dHRlcjogIj49My4yOS4wIgoKZGVwZW5kZW5jaWVzOgogIGZsdXR0ZXI6CiAgICBzZGs6IGZsdXR0ZXIKICBjdXBlcnRpbm9faWNvbnM6IF4xLjAuOAogIGdvX3JvdXRlcjogXjE1LjEuMwogIGJsb2M6IF45LjAuMAogIGNvcmU6CiAgICBwYXRoOiAuLi8uLi9wYWNrYWdlcy9jb3JlCiAgZGVzaWduX3N5c3RlbToKICAgIHBhdGg6IC4uLy4uL3BhY2thZ2VzL2Rlc2lnbl9zeXN0ZW0KICBkaToKICAgIHBhdGg6IC4uLy4uL3BhY2thZ2VzL2RpCiAgbG9jYWxpemF0aW9uOgogICAgcGF0aDogLi4vLi4vcGFja2FnZXMvbG9jYWxpemF0aW9uCiAgdXRpbHM6CiAgICBwYXRoOiAuLi8uLi9wYWNrYWdlcy91dGlscwp7eyNmaXJlYmFzZX19ICBmaXJlYmFzZV9jb3JlOiBeMy4xNC4wCnt7L2ZpcmViYXNlfX17eyNuZXR3b3JrfX0gIG5ldHdvcms6CiAgICBwYXRoOiAuLi8uLi9wYWNrYWdlcy9uZXR3b3JrCnt7L25ldHdvcmt9fXt7I2FuYWx5dGljc319ICBhbmFseXRpY3M6CiAgICBwYXRoOiAuLi8uLi9wYWNrYWdlcy9hbmFseXRpY3MKe3svYW5hbHl0aWNzfX17eyNjcmFzaGx5dGljc319ICBjcmFzaGx5dGljczoKICAgIHBhdGg6IC4uLy4uL3BhY2thZ2VzL2NyYXNobHl0aWNzCnt7L2NyYXNobHl0aWNzfX17eyNmZWF0dXJlX2ZsYWdzfX0gIGZlYXR1cmVfZmxhZ3M6CiAgICBwYXRoOiAuLi8uLi9wYWNrYWdlcy9mZWF0dXJlX2ZsYWdzCnt7L2ZlYXR1cmVfZmxhZ3N9fXt7I25vdGlmaWNhdGlvbnN9fSAgbm90aWZpY2F0aW9uczoKICAgIHBhdGg6IC4uLy4uL3BhY2thZ2VzL25vdGlmaWNhdGlvbnMKe3svbm90aWZpY2F0aW9uc319e3sjZGV2ZWxvcGVyfX0gIGRldmVsb3BlcjoKICAgIHBhdGg6IC4uLy4uL3BhY2thZ2VzL2RldmVsb3Blcgp7ey9kZXZlbG9wZXJ9fXt7I2F1dGh9fSAgYXV0aDoKICAgIHBhdGg6IC4uLy4uL2ZlYXR1cmVzL2F1dGgKe3svYXV0aH19e3sjZGFzaGJvYXJkfX0gIGRhc2hib2FyZDoKICAgIHBhdGg6IC4uLy4uL2ZlYXR1cmVzL2Rhc2hib2FyZAp7ey9kYXNoYm9hcmR9fQpkZXZfZGVwZW5kZW5jaWVzOgp7eyNmaXJlYmFzZX19ICBtb2NrdGFpbDogXjEuMC40CiAgcGx1Z2luX3BsYXRmb3JtX2ludGVyZmFjZTogXjIuMS44CiAgZmlyZWJhc2VfY29yZV9wbGF0Zm9ybV9pbnRlcmZhY2U6IF42LjAuMwp7ey9maXJlYmFzZX19e3sjYXV0aH19ICBmaXJlYmFzZV9hdXRoX3BsYXRmb3JtX2ludGVyZmFjZTogXjcuNy4zCnt7L2F1dGh9fXt7I2FuYWx5dGljc319ICBmaXJlYmFzZV9hbmFseXRpY3NfcGxhdGZvcm1faW50ZXJmYWNlOiBeNC40LjMKe3svYW5hbHl0aWNzfX17eyNjcmFzaGx5dGljc319ICBmaXJlYmFzZV9jcmFzaGx5dGljc19wbGF0Zm9ybV9pbnRlcmZhY2U6IF4zLjguMTAKe3svY3Jhc2hseXRpY3N9fXt7I2ZlYXR1cmVfZmxhZ3N9fSAgZmlyZWJhc2VfcmVtb3RlX2NvbmZpZ19wbGF0Zm9ybV9pbnRlcmZhY2U6IF4yLjAuMAp7ey9mZWF0dXJlX2ZsYWdzfX17eyNub3RpZmljYXRpb25zfX0gIGZpcmViYXNlX21lc3NhZ2luZ19wbGF0Zm9ybV9pbnRlcmZhY2U6IF40LjYuMTAKe3svbm90aWZpY2F0aW9uc319ICBmbHV0dGVyX3Rlc3Q6CiAgICBzZGs6IGZsdXR0ZXIKICBmbHV0dGVyX2xpbnRzOiBeNS4wLjAKCmRlcGVuZGVuY3lfb3ZlcnJpZGVzOgogIGludGw6IF4wLjIwLjIKCmZsdXR0ZXI6CiAgdXNlcy1tYXRlcmlhbC1kZXNpZ246IHRydWUKe3sjYXV0aH19ICBmb250czoKICAgICMgZmlyZWJhc2VfdWlfYXV0aCBzaGlwcyB0aGlzIGZvbnQgYnV0IG5ldmVyIHJlZ2lzdGVycyBpdCwgc28gdGhlIEdvb2dsZQogICAgIyBhbmQgQXBwbGUgYnV0dG9ucyByZW5kZXIgYXMgdG9mdSB1bmxlc3MgdGhlIGFwcCBkZWNsYXJlcyBpdCBoZXJlLgogICAgLSBmYW1pbHk6IFNvY2lhbEljb25zCiAgICAgIGZvbnRzOgogICAgICAgIC0gYXNzZXQ6IHBhY2thZ2VzL2ZpcmViYXNlX3VpX2F1dGgvZm9udHMvU29jaWFsSWNvbnMudHRmCnt7L2F1dGh9fQo=", + "bmFtZToge3tuYW1lLnNuYWtlQ2FzZSgpfX0KZGVzY3JpcHRpb246ICJ7e3tkZXNjcmlwdGlvbn19fSIKcHVibGlzaF90bzogJ25vbmUnCnZlcnNpb246IDAuMS4wKzEKCmVudmlyb25tZW50OgogIHNkazogXjMuOC4wCiAgZmx1dHRlcjogIj49My40Ny41IgoKZGVwZW5kZW5jaWVzOgogIGZsdXR0ZXI6CiAgICBzZGs6IGZsdXR0ZXIKICBjdXBlcnRpbm9faWNvbnM6IF4xLjAuOAogIGdvX3JvdXRlcjogXjE4LjAuMQogIGJsb2M6IF45LjAuMAogIGNvcmU6CiAgICBwYXRoOiAuLi8uLi9wYWNrYWdlcy9jb3JlCiAgZGVzaWduX3N5c3RlbToKICAgIHBhdGg6IC4uLy4uL3BhY2thZ2VzL2Rlc2lnbl9zeXN0ZW0KICBkaToKICAgIHBhdGg6IC4uLy4uL3BhY2thZ2VzL2RpCiAgbG9jYWxpemF0aW9uOgogICAgcGF0aDogLi4vLi4vcGFja2FnZXMvbG9jYWxpemF0aW9uCiAgdXRpbHM6CiAgICBwYXRoOiAuLi8uLi9wYWNrYWdlcy91dGlscwp7eyNmaXJlYmFzZX19ICBmaXJlYmFzZV9jb3JlOiBeNC4xNS4wCnt7L2ZpcmViYXNlfX17eyNuZXR3b3JrfX0gIG5ldHdvcms6CiAgICBwYXRoOiAuLi8uLi9wYWNrYWdlcy9uZXR3b3JrCnt7L25ldHdvcmt9fXt7I2FuYWx5dGljc319ICBhbmFseXRpY3M6CiAgICBwYXRoOiAuLi8uLi9wYWNrYWdlcy9hbmFseXRpY3MKe3svYW5hbHl0aWNzfX17eyNjcmFzaGx5dGljc319ICBjcmFzaGx5dGljczoKICAgIHBhdGg6IC4uLy4uL3BhY2thZ2VzL2NyYXNobHl0aWNzCnt7L2NyYXNobHl0aWNzfX17eyNmZWF0dXJlX2ZsYWdzfX0gIGZlYXR1cmVfZmxhZ3M6CiAgICBwYXRoOiAuLi8uLi9wYWNrYWdlcy9mZWF0dXJlX2ZsYWdzCnt7L2ZlYXR1cmVfZmxhZ3N9fXt7I25vdGlmaWNhdGlvbnN9fSAgbm90aWZpY2F0aW9uczoKICAgIHBhdGg6IC4uLy4uL3BhY2thZ2VzL25vdGlmaWNhdGlvbnMKe3svbm90aWZpY2F0aW9uc319e3sjZGV2ZWxvcGVyfX0gIGRldmVsb3BlcjoKICAgIHBhdGg6IC4uLy4uL3BhY2thZ2VzL2RldmVsb3Blcgp7ey9kZXZlbG9wZXJ9fXt7I2F1dGh9fSAgYXV0aDoKICAgIHBhdGg6IC4uLy4uL2ZlYXR1cmVzL2F1dGgKe3svYXV0aH19e3sjZGFzaGJvYXJkfX0gIGRhc2hib2FyZDoKICAgIHBhdGg6IC4uLy4uL2ZlYXR1cmVzL2Rhc2hib2FyZAp7ey9kYXNoYm9hcmR9fQpkZXZfZGVwZW5kZW5jaWVzOgp7eyNmaXJlYmFzZX19ICBtb2NrdGFpbDogXjEuMC40CiAgcGx1Z2luX3BsYXRmb3JtX2ludGVyZmFjZTogXjIuMS44CiAgZmlyZWJhc2VfY29yZV9wbGF0Zm9ybV9pbnRlcmZhY2U6IF44LjEuMQp7ey9maXJlYmFzZX19e3sjYXV0aH19ICBmaXJlYmFzZV9hdXRoX3BsYXRmb3JtX2ludGVyZmFjZTogXjkuMS4wCnt7L2F1dGh9fXt7I2FuYWx5dGljc319ICBmaXJlYmFzZV9hbmFseXRpY3NfcGxhdGZvcm1faW50ZXJmYWNlOiBeNi4wLjcKe3svYW5hbHl0aWNzfX17eyNjcmFzaGx5dGljc319ICBmaXJlYmFzZV9jcmFzaGx5dGljc19wbGF0Zm9ybV9pbnRlcmZhY2U6IF4zLjguMTAKe3svY3Jhc2hseXRpY3N9fXt7I2ZlYXR1cmVfZmxhZ3N9fSAgZmlyZWJhc2VfcmVtb3RlX2NvbmZpZ19wbGF0Zm9ybV9pbnRlcmZhY2U6IF4zLjAuNwp7ey9mZWF0dXJlX2ZsYWdzfX17eyNub3RpZmljYXRpb25zfX0gIGZpcmViYXNlX21lc3NhZ2luZ19wbGF0Zm9ybV9pbnRlcmZhY2U6IF40LjYuMTAKe3svbm90aWZpY2F0aW9uc319ICBmbHV0dGVyX3Rlc3Q6CiAgICBzZGs6IGZsdXR0ZXIKICBmbHV0dGVyX2xpbnRzOiBeNi4wLjAKCgpmbHV0dGVyOgogIHVzZXMtbWF0ZXJpYWwtZGVzaWduOiB0cnVlCnt7I2F1dGh9fSAgZm9udHM6CiAgICAjIGZpcmViYXNlX3VpX2F1dGggc2hpcHMgdGhpcyBmb250IGJ1dCBuZXZlciByZWdpc3RlcnMgaXQsIHNvIHRoZSBHb29nbGUKICAgICMgYW5kIEFwcGxlIGJ1dHRvbnMgcmVuZGVyIGFzIHRvZnUgdW5sZXNzIHRoZSBhcHAgZGVjbGFyZXMgaXQgaGVyZS4KICAgIC0gZmFtaWx5OiBTb2NpYWxJY29ucwogICAgICBmb250czoKICAgICAgICAtIGFzc2V0OiBwYWNrYWdlcy9maXJlYmFzZV91aV9hdXRoL2ZvbnRzL1NvY2lhbEljb25zLnR0Zgp7ey9hdXRofX0KCnJlc29sdXRpb246IHdvcmtzcGFjZQo=", "type": "text" }, { @@ -264,7 +264,7 @@ final flutterProjectWithMonoRepoBundle = MasonBundle.fromJson({ { "path": "{{ name.snakeCase() }}/features/{{auth_dir}}/pubspec.yaml", "data": - "bmFtZTogYXV0aApkZXNjcmlwdGlvbjogIkZpcmViYXNlIEF1dGhlbnRpY2F0aW9uIChlbWFpbCwgR29vZ2xlLCBBcHBsZSkgd2l0aCBnb19yb3V0ZXIgaW50ZWdyYXRpb24gZm9yIHt7bmFtZS50aXRsZUNhc2UoKX19LiIKdmVyc2lvbjogMC4xLjAKcHVibGlzaF90bzogJ25vbmUnCgplbnZpcm9ubWVudDoKICBzZGs6IF4zLjguMAogIGZsdXR0ZXI6ICI+PTMuMjkuMCIKCmRlcGVuZGVuY2llczoKICBmbHV0dGVyOgogICAgc2RrOiBmbHV0dGVyCiAgZXF1YXRhYmxlOiBeMi4wLjcKICBmaXJlYmFzZV9hdXRoOiBeNS42LjAKICBmaXJlYmFzZV9jb3JlOiBeMy4xNC4wCiAgZmlyZWJhc2VfdWlfYXV0aDogXjEuMTcuMAogIGZpcmViYXNlX3VpX29hdXRoX2FwcGxlOiBeMS4zLjIKICBmaXJlYmFzZV91aV9vYXV0aF9nb29nbGU6IF4xLjQuMgogIGdvX3JvdXRlcjogXjE1LjEuMwogIGpzb25fYW5ub3RhdGlvbjogXjQuOS4wCiAgY29yZToKICAgIHBhdGg6IC4uLy4uL3BhY2thZ2VzL2NvcmUKICBkZXNpZ25fc3lzdGVtOgogICAgcGF0aDogLi4vLi4vcGFja2FnZXMvZGVzaWduX3N5c3RlbQogIGRpOgogICAgcGF0aDogLi4vLi4vcGFja2FnZXMvZGkKICBsb2NhbGl6YXRpb246CiAgICBwYXRoOiAuLi8uLi9wYWNrYWdlcy9sb2NhbGl6YXRpb24Ke3sjZmlyZXN0b3JlfX0gIGNsb3VkX2ZpcmVzdG9yZTogXjUuNi4wCnt7L2ZpcmVzdG9yZX19e3sjbmV0d29ya319ICBuZXR3b3JrOgogICAgcGF0aDogLi4vLi4vcGFja2FnZXMvbmV0d29yawp7ey9uZXR3b3JrfX17eyNhbmFseXRpY3N9fSAgYW5hbHl0aWNzOgogICAgcGF0aDogLi4vLi4vcGFja2FnZXMvYW5hbHl0aWNzCnt7L2FuYWx5dGljc319CmRldl9kZXBlbmRlbmNpZXM6CiAgbW9ja3RhaWw6IF4xLjAuNAogIGZsdXR0ZXJfdGVzdDoKICAgIHNkazogZmx1dHRlcgogIGZsdXR0ZXJfbGludHM6IF41LjAuMAoKZmx1dHRlcjoK", + "bmFtZTogYXV0aApkZXNjcmlwdGlvbjogIkZpcmViYXNlIEF1dGhlbnRpY2F0aW9uIChlbWFpbCwgR29vZ2xlLCBBcHBsZSkgd2l0aCBnb19yb3V0ZXIgaW50ZWdyYXRpb24gZm9yIHt7bmFtZS50aXRsZUNhc2UoKX19LiIKdmVyc2lvbjogMC4xLjAKcHVibGlzaF90bzogJ25vbmUnCgplbnZpcm9ubWVudDoKICBzZGs6IF4zLjguMAogIGZsdXR0ZXI6ICI+PTMuNDcuNSIKCmRlcGVuZGVuY2llczoKICBmbHV0dGVyOgogICAgc2RrOiBmbHV0dGVyCiAgZXF1YXRhYmxlOiBeMi4wLjcKICBmaXJlYmFzZV9hdXRoOiBeNi43LjAKICBmaXJlYmFzZV9jb3JlOiBeNC4xNS4wCiAgZmlyZWJhc2VfdWlfYXV0aDogXjMuMS4xCiAgZmlyZWJhc2VfdWlfb2F1dGhfYXBwbGU6IF4yLjEuMQogIGZpcmViYXNlX3VpX29hdXRoX2dvb2dsZTogXjIuMS4xCiAgZ29fcm91dGVyOiBeMTguMC4xCiAganNvbl9hbm5vdGF0aW9uOiBeNC45LjAKICBjb3JlOgogICAgcGF0aDogLi4vLi4vcGFja2FnZXMvY29yZQogIGRlc2lnbl9zeXN0ZW06CiAgICBwYXRoOiAuLi8uLi9wYWNrYWdlcy9kZXNpZ25fc3lzdGVtCiAgZGk6CiAgICBwYXRoOiAuLi8uLi9wYWNrYWdlcy9kaQogIGxvY2FsaXphdGlvbjoKICAgIHBhdGg6IC4uLy4uL3BhY2thZ2VzL2xvY2FsaXphdGlvbgp7eyNmaXJlc3RvcmV9fSAgY2xvdWRfZmlyZXN0b3JlOiBeNi4xMC4wCnt7L2ZpcmVzdG9yZX19e3sjbmV0d29ya319ICBuZXR3b3JrOgogICAgcGF0aDogLi4vLi4vcGFja2FnZXMvbmV0d29yawp7ey9uZXR3b3JrfX17eyNhbmFseXRpY3N9fSAgYW5hbHl0aWNzOgogICAgcGF0aDogLi4vLi4vcGFja2FnZXMvYW5hbHl0aWNzCnt7L2FuYWx5dGljc319CmRldl9kZXBlbmRlbmNpZXM6CiAgbW9ja3RhaWw6IF4xLjAuNAogIGZsdXR0ZXJfdGVzdDoKICAgIHNkazogZmx1dHRlcgogIGZsdXR0ZXJfbGludHM6IF42LjAuMAoKZmx1dHRlcjoKCnJlc29sdXRpb246IHdvcmtzcGFjZQo=", "type": "text" }, { @@ -359,7 +359,7 @@ final flutterProjectWithMonoRepoBundle = MasonBundle.fromJson({ { "path": "{{ name.snakeCase() }}/features/{{dashboard_dir}}/pubspec.yaml", "data": - "bmFtZTogZGFzaGJvYXJkCmRlc2NyaXB0aW9uOiAiQm90dG9tLW5hdmlnYXRpb24gc2hlbGwgYW5kIHN0YXJ0ZXIgdGFicyBmb3Ige3tuYW1lLnRpdGxlQ2FzZSgpfX0uIgp2ZXJzaW9uOiAwLjEuMApwdWJsaXNoX3RvOiAnbm9uZScKCmVudmlyb25tZW50OgogIHNkazogXjMuOC4wCiAgZmx1dHRlcjogIj49My4yOS4wIgoKZGVwZW5kZW5jaWVzOgogIGZsdXR0ZXI6CiAgICBzZGs6IGZsdXR0ZXIKICBnb19yb3V0ZXI6IF4xNS4xLjMKICBjb3JlOgogICAgcGF0aDogLi4vLi4vcGFja2FnZXMvY29yZQogIGRlc2lnbl9zeXN0ZW06CiAgICBwYXRoOiAuLi8uLi9wYWNrYWdlcy9kZXNpZ25fc3lzdGVtCiAgZGk6CiAgICBwYXRoOiAuLi8uLi9wYWNrYWdlcy9kaQogIGxvY2FsaXphdGlvbjoKICAgIHBhdGg6IC4uLy4uL3BhY2thZ2VzL2xvY2FsaXphdGlvbgp7eyNhdXRofX0gIGF1dGg6CiAgICBwYXRoOiAuLi9hdXRoCnt7L2F1dGh9fXt7I2RldmVsb3Blcn19ICBkZXZlbG9wZXI6CiAgICBwYXRoOiAuLi8uLi9wYWNrYWdlcy9kZXZlbG9wZXIKe3svZGV2ZWxvcGVyfX0KZGV2X2RlcGVuZGVuY2llczoKICBmbHV0dGVyX3Rlc3Q6CiAgICBzZGs6IGZsdXR0ZXIKICBmbHV0dGVyX2xpbnRzOiBeNS4wLjAKCmZsdXR0ZXI6Cg==", + "bmFtZTogZGFzaGJvYXJkCmRlc2NyaXB0aW9uOiAiQm90dG9tLW5hdmlnYXRpb24gc2hlbGwgYW5kIHN0YXJ0ZXIgdGFicyBmb3Ige3tuYW1lLnRpdGxlQ2FzZSgpfX0uIgp2ZXJzaW9uOiAwLjEuMApwdWJsaXNoX3RvOiAnbm9uZScKCmVudmlyb25tZW50OgogIHNkazogXjMuOC4wCiAgZmx1dHRlcjogIj49My40Ny41IgoKZGVwZW5kZW5jaWVzOgogIGZsdXR0ZXI6CiAgICBzZGs6IGZsdXR0ZXIKICBnb19yb3V0ZXI6IF4xOC4wLjEKICBjb3JlOgogICAgcGF0aDogLi4vLi4vcGFja2FnZXMvY29yZQogIGRlc2lnbl9zeXN0ZW06CiAgICBwYXRoOiAuLi8uLi9wYWNrYWdlcy9kZXNpZ25fc3lzdGVtCiAgZGk6CiAgICBwYXRoOiAuLi8uLi9wYWNrYWdlcy9kaQogIGxvY2FsaXphdGlvbjoKICAgIHBhdGg6IC4uLy4uL3BhY2thZ2VzL2xvY2FsaXphdGlvbgp7eyNhdXRofX0gIGF1dGg6CiAgICBwYXRoOiAuLi9hdXRoCnt7L2F1dGh9fXt7I2RldmVsb3Blcn19ICBkZXZlbG9wZXI6CiAgICBwYXRoOiAuLi8uLi9wYWNrYWdlcy9kZXZlbG9wZXIKe3svZGV2ZWxvcGVyfX0KZGV2X2RlcGVuZGVuY2llczoKICBmbHV0dGVyX3Rlc3Q6CiAgICBzZGs6IGZsdXR0ZXIKICBmbHV0dGVyX2xpbnRzOiBeNi4wLjAKCmZsdXR0ZXI6CgpyZXNvbHV0aW9uOiB3b3Jrc3BhY2UK", "type": "text" }, { @@ -375,12 +375,6 @@ final flutterProjectWithMonoRepoBundle = MasonBundle.fromJson({ "e3sjYXV0aH19aW1wb3J0ICdwYWNrYWdlOmF1dGgvYXV0aC5kYXJ0JyBhcyBhdXRoOwp7ey9hdXRofX0KaW1wb3J0ICdwYWNrYWdlOmNvcmUvY29yZS5kYXJ0JyBhcyBjb3JlOwppbXBvcnQgJ3BhY2thZ2U6ZGFzaGJvYXJkL2Rhc2hib2FyZC5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmRpL2RpLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Zmx1dHRlci9tYXRlcmlhbC5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXJfdGVzdC9mbHV0dGVyX3Rlc3QuZGFydCc7CmltcG9ydCAncGFja2FnZTpnb19yb3V0ZXIvZ29fcm91dGVyLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6bG9jYWxpemF0aW9uL2xvY2FsaXphdGlvbi5kYXJ0JzsKCnt7I2F1dGh9fWNsYXNzIF9BdXRoIGltcGxlbWVudHMgYXV0aC5BdXRoU2VydmljZSB7CiAgaW50IHNpZ25PdXRzID0gMDsKICBAb3ZlcnJpZGUKICBTdHJpbmcgZ2V0IHVpZCA9PiAndGVzdCc7CiAgQG92ZXJyaWRlCiAgYm9vbCBnZXQgaXNTaWduZWRJbiA9PiB0cnVlOwogIEBvdmVycmlkZQogIEZ1dHVyZTx2b2lkPiBzaWduT3V0KCkgYXN5bmMgewogICAgc2lnbk91dHMrKzsKICB9Cn0KCnt7L2F1dGh9fXZvaWQgbWFpbigpIHsKICBzZXRVcChjb3JlLmluaXQpOwogIHRlYXJEb3duKGRpLnJlc2V0KTsKCiAgdGVzdFdpZGdldHMoJ3RhYnMgbmF2aWdhdGUgYW5kIHByb2ZpbGUgc2lnbi1vdXQgcmVxdWlyZXMgY29uZmlybWF0aW9uJywgKAogICAgdGVzdGVyLAogICkgYXN5bmMgewp7eyNhdXRofX0gICAgZmluYWwgc2VydmljZSA9IF9BdXRoKCk7CiAgICBkaS5yZWdpc3RlcjxhdXRoLkF1dGhTZXJ2aWNlPihzZXJ2aWNlKTsKe3svYXV0aH19ICAgIGZpbmFsIHJvdXRlciA9IEdvUm91dGVyKAogICAgICBpbml0aWFsTG9jYXRpb246IERhc2hib2FyZFJvdXRlci5ob21lLAogICAgICByb3V0ZXM6IFsKICAgICAgICBEYXNoYm9hcmRSb3V0ZXIuY3JlYXRlU2hlbGxSb3V0ZSgpLAp7eyNhdXRofX0gICAgICAgIEdvUm91dGUoCiAgICAgICAgICBwYXRoOiBhdXRoLkF1dGhSb3V0ZXMuc2lnbkluLAogICAgICAgICAgYnVpbGRlcjogKF8sIF8pID0+IGNvbnN0IFRleHQoJ1NpZ25lZCBvdXQnKSwKICAgICAgICApLAp7ey9hdXRofX0gICAgICBdLAogICAgKTsKICAgIGFkZFRlYXJEb3duKHJvdXRlci5kaXNwb3NlKTsKICAgIGF3YWl0IHRlc3Rlci5wdW1wV2lkZ2V0KE1hdGVyaWFsQXBwLnJvdXRlcihyb3V0ZXJDb25maWc6IHJvdXRlcikpOwogICAgYXdhaXQgdGVzdGVyLnB1bXBBbmRTZXR0bGUoKTsKICAgIGV4cGVjdCgKICAgICAgcm91dGVyLnJvdXRlSW5mb3JtYXRpb25Qcm92aWRlci52YWx1ZS51cmkucGF0aCwKICAgICAgRGFzaGJvYXJkUm91dGVyLmhvbWUsCiAgICApOwogICAgYXdhaXQgdGVzdGVyLnRhcChmaW5kLnRleHQoJ0V4cGxvcmUnKS5sYXN0KTsKICAgIGF3YWl0IHRlc3Rlci5wdW1wQW5kU2V0dGxlKCk7CiAgICBleHBlY3QoCiAgICAgIHJvdXRlci5yb3V0ZUluZm9ybWF0aW9uUHJvdmlkZXIudmFsdWUudXJpLnBhdGgsCiAgICAgIERhc2hib2FyZFJvdXRlci5leHBsb3JlLAogICAgKTsKICAgIGF3YWl0IHRlc3Rlci50YXAoZmluZC50ZXh0KHN0cmluZ3MubmF2LnByb2ZpbGUpLmxhc3QpOwogICAgYXdhaXQgdGVzdGVyLnB1bXBBbmRTZXR0bGUoKTsKICAgIGV4cGVjdCgKICAgICAgcm91dGVyLnJvdXRlSW5mb3JtYXRpb25Qcm92aWRlci52YWx1ZS51cmkucGF0aCwKICAgICAgRGFzaGJvYXJkUm91dGVyLnByb2ZpbGUsCiAgICApOwogICAgYXdhaXQgdGVzdGVyLnRhcChmaW5kLnRleHQoc3RyaW5ncy5wcm9maWxlLnNldHRpbmdzKSk7Cnt7I2F1dGh9fSAgICBhd2FpdCB0ZXN0ZXIudGFwKGZpbmQudGV4dChzdHJpbmdzLmF1dGguc2lnbl9vdXQpKTsKICAgIGF3YWl0IHRlc3Rlci5wdW1wQW5kU2V0dGxlKCk7CiAgICBhd2FpdCB0ZXN0ZXIudGFwKGZpbmQudGV4dChzdHJpbmdzLmdlbmVyaWMuY2FuY2VsKSk7CiAgICBhd2FpdCB0ZXN0ZXIucHVtcEFuZFNldHRsZSgpOwogICAgZXhwZWN0KHNlcnZpY2Uuc2lnbk91dHMsIDApOwogICAgYXdhaXQgdGVzdGVyLnRhcChmaW5kLnRleHQoc3RyaW5ncy5hdXRoLnNpZ25fb3V0KSk7CiAgICBhd2FpdCB0ZXN0ZXIucHVtcEFuZFNldHRsZSgpOwogICAgYXdhaXQgdGVzdGVyLnRhcChmaW5kLndpZGdldFdpdGhUZXh0KEZpbGxlZEJ1dHRvbiwgc3RyaW5ncy5hdXRoLnNpZ25fb3V0KSk7CiAgICBhd2FpdCB0ZXN0ZXIucHVtcEFuZFNldHRsZSgpOwogICAgZXhwZWN0KHNlcnZpY2Uuc2lnbk91dHMsIDEpOwogICAgZXhwZWN0KGZpbmQudGV4dCgnU2lnbmVkIG91dCcpLCBmaW5kc09uZVdpZGdldCk7Cnt7L2F1dGh9fSAgfSk7CgogIHRlc3RXaWRnZXRzKCdwbGFjZWhvbGRlciBzdXBwb3J0cyBhbiBhY3Rpb24nLCAodGVzdGVyKSBhc3luYyB7CiAgICB2YXIgY2FsbHMgPSAwOwogICAgYXdhaXQgdGVzdGVyLnB1bXBXaWRnZXQoCiAgICAgIE1hdGVyaWFsQXBwKAogICAgICAgIGhvbWU6IFNjYWZmb2xkKAogICAgICAgICAgYm9keTogUGxhY2Vob2xkZXJUYWIoCiAgICAgICAgICAgIGljb246IEljb25zLmFkZCwKICAgICAgICAgICAgdGl0bGU6ICdUaXRsZScsCiAgICAgICAgICAgIHN1YnRpdGxlOiAnU3VidGl0bGUnLAogICAgICAgICAgICBhY3Rpb246IFRleHRCdXR0b24oCiAgICAgICAgICAgICAgb25QcmVzc2VkOiAoKSB7CiAgICAgICAgICAgICAgICBjYWxscysrOwogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgY2hpbGQ6IGNvbnN0IFRleHQoJ1N0YXJ0JyksCiAgICAgICAgICAgICksCiAgICAgICAgICApLAogICAgICAgICksCiAgICAgICksCiAgICApOwogICAgYXdhaXQgdGVzdGVyLnRhcChmaW5kLnRleHQoJ1N0YXJ0JykpOwogICAgZXhwZWN0KGNhbGxzLCAxKTsKICB9KTsKfQo=", "type": "text" }, - { - "path": "{{ name.snakeCase() }}/melos.yaml", - "data": - "bmFtZToge3sgbmFtZS5zbmFrZUNhc2UoKSB9fV93b3Jrc3BhY2UKCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKIyBNT05PUkVQTyBDT05GSUdVUkFUSU9OCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKIyBhcHBzLyAgICAgLSBydW5uYWJsZSBGbHV0dGVyIGFwcGxpY2F0aW9ucwojIGZlYXR1cmVzLyAtIHZlcnRpY2FsIHNsaWNlcyAodWkgKyBzdGF0ZSArIGRhdGEpIHRoYXQgYW4gYXBwIGNvbXBvc2VzCiMgcGFja2FnZXMvIC0gaG9yaXpvbnRhbCBjYXBhYmlsaXRpZXMgc2hhcmVkIGJ5IGZlYXR1cmVzIChuZXR3b3JrLCBkZXNpZ24sIC4uLikKIyBwbHVnaW5zLyAgLSBwbGF0Zm9ybSBjaGFubmVscyBhbmQgbmF0aXZlIGludGVncmF0aW9ucwpwYWNrYWdlczoKICAtIGFwcHMvKgogIC0gZmVhdHVyZXMvKgogIC0gcGFja2FnZXMvKgogIC0gcGx1Z2lucy8qCgpjb21tYW5kOgogIGJvb3RzdHJhcDoKICAgIGRlcGVuZGVuY2llczoKICAgICAganNvbl9hbm5vdGF0aW9uOiBeNC45LjAKICAgIGRldl9kZXBlbmRlbmNpZXM6CiAgICAgIGJ1aWxkX3J1bm5lcjogXjIuNC4xNQogICAgICBmbHV0dGVyX2xpbnRzOiBeNS4wLjAKICAgICAganNvbl9zZXJpYWxpemFibGU6IF42LjkuNQoKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwojIFNDUklQVFMKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwpzY3JpcHRzOgogIGNsZWFuOmZsdXR0ZXI6CiAgICBkZXNjcmlwdGlvbjogIlJ1biBmbHV0dGVyIGNsZWFuIGluIGFsbCBGbHV0dGVyIHBhY2thZ2VzIgogICAgcnVuOiBmbHV0dGVyIGNsZWFuCiAgICBleGVjOgogICAgICBjb25jdXJyZW5jeTogNAogICAgcGFja2FnZUZpbHRlcnM6CiAgICAgIGZsdXR0ZXI6IHRydWUKCiAgbGludDoKICAgIGRlc2NyaXB0aW9uOiAiQ2hlY2sgZm9ybWF0dGluZyBhbmQgYW5hbHl6ZSBhbGwgd29ya3NwYWNlIGNvZGUiCiAgICBydW46IGRhcnQgcnVuIHRvb2wvY2hlY2suZGFydAoKICBmaXg6CiAgICBkZXNjcmlwdGlvbjogIkFwcGx5IERhcnQgYXV0b21hdGVkIGZpeGVzIHRvIGFsbCBwYWNrYWdlcyIKICAgIHJ1bjogZGFydCBmaXggLS1hcHBseQogICAgZXhlYzoKICAgICAgY29uY3VycmVuY3k6IDUKICAgIHBhY2thZ2VGaWx0ZXJzOgogICAgICBkaXJFeGlzdHM6IGxpYgoKICBmaXg6ZHJ5OgogICAgZGVzY3JpcHRpb246ICJTaG93IERhcnQgYXV0b21hdGVkIGZpeGVzIHdpdGhvdXQgYXBwbHlpbmcgdGhlbSIKICAgIHJ1bjogZGFydCBmaXggLS1kcnktcnVuCiAgICBleGVjOgogICAgICBjb25jdXJyZW5jeTogNQogICAgcGFja2FnZUZpbHRlcnM6CiAgICAgIGRpckV4aXN0czogbGliCgogIGdlbmVyYXRlOgogICAgZGVzY3JpcHRpb246ICJSdW4gYnVpbGRfcnVubmVyIGluIGV2ZXJ5IHBhY2thZ2UgdGhhdCBuZWVkcyBjb2RlZ2VuIgogICAgcnVuOiBkYXJ0IHJ1biBidWlsZF9ydW5uZXIgYnVpbGQgLS1kZWxldGUtY29uZmxpY3Rpbmctb3V0cHV0cwogICAgZXhlYzoKICAgICAgY29uY3VycmVuY3k6IDEKICAgIHBhY2thZ2VGaWx0ZXJzOgogICAgICBkZXBlbmRzT246IGJ1aWxkX3J1bm5lcgoKICBnZW5lcmF0ZTppNjluOgogICAgZGVzY3JpcHRpb246ICJSZWdlbmVyYXRlIGxvY2FsaXphdGlvbiBzdHJpbmdzIGZyb20gbWVzc2FnZXMuaTY5bi55YW1sIgogICAgcnVuOiB8CiAgICAgIGRhcnQgcnVuIGJ1aWxkX3J1bm5lciBidWlsZCAtLWRlbGV0ZS1jb25mbGljdGluZy1vdXRwdXRzICYmCiAgICAgIGRhcnQgZm9ybWF0IGxpYi9tZXNzYWdlcy5pNjluLmRhcnQKICAgIGV4ZWM6CiAgICAgIGNvbmN1cnJlbmN5OiAxCiAgICBwYWNrYWdlRmlsdGVyczoKICAgICAgc2NvcGU6ICJsb2NhbGl6YXRpb24iCgogIHRlc3Q6CiAgICBkZXNjcmlwdGlvbjogIlJ1biBhbGwgdGVzdHMgKERhcnQgYW5kIEZsdXR0ZXIpIgogICAgcnVuOiBkYXJ0IHJ1biBtZWxvcyBydW4gdGVzdDpzZWxlY3QgLS1uby1zZWxlY3QKCiAgY292ZXJhZ2U6CiAgICBkZXNjcmlwdGlvbjogIlJ1biBldmVyeSB0ZXN0IGFuZCByZXF1aXJlIDEwMCUgaGFuZHdyaXR0ZW4gbGluZSBjb3ZlcmFnZSIKICAgIHJ1bjogZGFydCBydW4gdG9vbC9jb3ZlcmFnZS5kYXJ0CgogIHRlc3Q6c2VsZWN0OgogICAgZGVzY3JpcHRpb246ICJSdW4gdGVzdHMgaW4gRmx1dHRlciBwYWNrYWdlcyIKICAgIHJ1bjogZmx1dHRlciB0ZXN0IC0tY292ZXJhZ2UKICAgIGV4ZWM6CiAgICAgIGNvbmN1cnJlbmN5OiA1CiAgICAgIGZhaWxGYXN0OiB0cnVlCiAgICBwYWNrYWdlRmlsdGVyczoKICAgICAgZGlyRXhpc3RzOiB0ZXN0CgojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiMgSURFIENPTkZJR1VSQVRJT04KIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwppZGU6CiAgaW50ZWxsaWo6CiAgICBlbmFibGVkOiB0cnVlCiAgICBtb2R1bGVOYW1lUHJlZml4OiAnJwo=", - "type": "text" - }, { "path": "{{ name.snakeCase() }}/packages/core/lib/bloc/global_event.dart", "data": @@ -532,7 +526,7 @@ final flutterProjectWithMonoRepoBundle = MasonBundle.fromJson({ "path": "{{ name.snakeCase() }}/packages/core/lib/observer/core_route_observer.dart", "data": - "aW1wb3J0ICdwYWNrYWdlOmNvcmUvY29yZS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmRpL2RpLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Zmx1dHRlci9tYXRlcmlhbC5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOnRhbGtlcl9mbHV0dGVyL3RhbGtlcl9mbHV0dGVyLmRhcnQnOwoKY2xhc3MgQ29yZVJvdXRlT2JzZXJ2ZXIgZXh0ZW5kcyBOYXZpZ2F0b3JPYnNlcnZlciB7CiAgQ29yZVJvdXRlT2JzZXJ2ZXIoe0xvZ2dlcj8gbG9nZ2VyfSkgOiBfbG9nZ2VyID0gbG9nZ2VyID8/IGRpLmdldDxMb2dnZXI+KCk7CgogIGZpbmFsIExvZ2dlciBfbG9nZ2VyOwoKICBAb3ZlcnJpZGUKICB2b2lkIGRpZFB1c2goUm91dGUgcm91dGUsIFJvdXRlPyBwcmV2aW91c1JvdXRlKSB7CiAgICBzdXBlci5kaWRQdXNoKHJvdXRlLCBwcmV2aW91c1JvdXRlKTsKICAgIGlmIChyb3V0ZS5zZXR0aW5ncy5uYW1lID09IG51bGwpIHsKICAgICAgcmV0dXJuOwogICAgfQogICAgX2xvZ2dlci5kKFRhbGtlclJvdXRlTG9nLl9jcmVhdGVNZXNzYWdlKHJvdXRlLCBSb3V0ZUxvZ1R5cGUucHVzaCkpOwogIH0KCiAgQG92ZXJyaWRlCiAgdm9pZCBkaWRQb3AoUm91dGUgcm91dGUsIFJvdXRlPyBwcmV2aW91c1JvdXRlKSB7CiAgICBzdXBlci5kaWRQb3Aocm91dGUsIHByZXZpb3VzUm91dGUpOwogICAgaWYgKHJvdXRlLnNldHRpbmdzLm5hbWUgPT0gbnVsbCkgewogICAgICByZXR1cm47CiAgICB9CiAgICBfbG9nZ2VyLmQoVGFsa2VyUm91dGVMb2cuX2NyZWF0ZU1lc3NhZ2Uocm91dGUsIFJvdXRlTG9nVHlwZS5wb3ApKTsKICB9CgogIEBvdmVycmlkZQogIHZvaWQgZGlkUmVtb3ZlKFJvdXRlPGR5bmFtaWM+IHJvdXRlLCBSb3V0ZTxkeW5hbWljPj8gcHJldmlvdXNSb3V0ZSkgewogICAgc3VwZXIuZGlkUmVtb3ZlKHJvdXRlLCBwcmV2aW91c1JvdXRlKTsKICAgIGlmIChyb3V0ZS5zZXR0aW5ncy5uYW1lID09IG51bGwpIHsKICAgICAgcmV0dXJuOwogICAgfQogICAgX2xvZ2dlci5kKFRhbGtlclJvdXRlTG9nLl9jcmVhdGVNZXNzYWdlKHJvdXRlLCBSb3V0ZUxvZ1R5cGUucmVtb3ZlKSk7CiAgfQoKICBAb3ZlcnJpZGUKICB2b2lkIGRpZFJlcGxhY2Uoe1JvdXRlPGR5bmFtaWM+PyBuZXdSb3V0ZSwgUm91dGU8ZHluYW1pYz4/IG9sZFJvdXRlfSkgewogICAgc3VwZXIuZGlkUmVwbGFjZShuZXdSb3V0ZTogbmV3Um91dGUsIG9sZFJvdXRlOiBvbGRSb3V0ZSk7CiAgICBpZiAobmV3Um91dGU/LnNldHRpbmdzLm5hbWUgPT0gbnVsbCkgewogICAgICByZXR1cm47CiAgICB9CiAgICBfbG9nZ2VyLmQoVGFsa2VyUm91dGVMb2cuX2NyZWF0ZU1lc3NhZ2UobmV3Um91dGUhLCBSb3V0ZUxvZ1R5cGUucmVwbGFjZSkpOwogIH0KfQoKZW51bSBSb3V0ZUxvZ1R5cGUgeyBwdXNoLCBwb3AsIHJlbW92ZSwgcmVwbGFjZSB9CgpjbGFzcyBUYWxrZXJSb3V0ZUxvZyBleHRlbmRzIFRhbGtlckxvZyB7CiAgVGFsa2VyUm91dGVMb2coe3JlcXVpcmVkIFJvdXRlIHJvdXRlLCByZXF1aXJlZCBSb3V0ZUxvZ1R5cGUgdHlwZX0pCiAgICA6IHN1cGVyKF9jcmVhdGVNZXNzYWdlKHJvdXRlLCB0eXBlKSk7CgogIEBvdmVycmlkZQogIEFuc2lQZW4gZ2V0IHBlbiA9PiBBbnNpUGVuKCkuLnh0ZXJtKDEzNSk7CgogIEBvdmVycmlkZQogIFN0cmluZyBnZXQga2V5ID0+IFRhbGtlckxvZ1R5cGUucm91dGUua2V5OwoKICBzdGF0aWMgU3RyaW5nIF9jcmVhdGVNZXNzYWdlKFJvdXRlPGR5bmFtaWM+IHJvdXRlLCBSb3V0ZUxvZ1R5cGUgdHlwZSkgewogICAgZmluYWwgYnVmZmVyID0gU3RyaW5nQnVmZmVyKCk7CiAgICBidWZmZXIud3JpdGUodHlwZS5uYW1lKTsKICAgIGJ1ZmZlci53cml0ZSgnIHJvdXRlIG5hbWVkICcpOwogICAgYnVmZmVyLndyaXRlKHJvdXRlLnNldHRpbmdzLm5hbWUgPz8gJ251bGwnKTsKCiAgICByZXR1cm4gYnVmZmVyLnRvU3RyaW5nKCk7CiAgfQp9Cg==", + "aW1wb3J0ICdwYWNrYWdlOmNvcmUvY29yZS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmRpL2RpLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Zmx1dHRlci9tYXRlcmlhbC5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOnRhbGtlcl9mbHV0dGVyL3RhbGtlcl9mbHV0dGVyLmRhcnQnOwoKY2xhc3MgQ29yZVJvdXRlT2JzZXJ2ZXIgZXh0ZW5kcyBOYXZpZ2F0b3JPYnNlcnZlciB7CiAgQ29yZVJvdXRlT2JzZXJ2ZXIoe0xvZ2dlcj8gbG9nZ2VyfSkgOiBfbG9nZ2VyID0gbG9nZ2VyID8/IGRpLmdldDxMb2dnZXI+KCk7CgogIGZpbmFsIExvZ2dlciBfbG9nZ2VyOwoKICBAb3ZlcnJpZGUKICB2b2lkIGRpZFB1c2goUm91dGUgcm91dGUsIFJvdXRlPyBwcmV2aW91c1JvdXRlKSB7CiAgICBzdXBlci5kaWRQdXNoKHJvdXRlLCBwcmV2aW91c1JvdXRlKTsKICAgIGlmIChyb3V0ZS5zZXR0aW5ncy5uYW1lID09IG51bGwpIHsKICAgICAgcmV0dXJuOwogICAgfQogICAgX2xvZ2dlci5kKFRhbGtlclJvdXRlTG9nLl9jcmVhdGVNZXNzYWdlKHJvdXRlLCBSb3V0ZUxvZ1R5cGUucHVzaCkpOwogIH0KCiAgQG92ZXJyaWRlCiAgdm9pZCBkaWRQb3AoUm91dGUgcm91dGUsIFJvdXRlPyBwcmV2aW91c1JvdXRlKSB7CiAgICBzdXBlci5kaWRQb3Aocm91dGUsIHByZXZpb3VzUm91dGUpOwogICAgaWYgKHJvdXRlLnNldHRpbmdzLm5hbWUgPT0gbnVsbCkgewogICAgICByZXR1cm47CiAgICB9CiAgICBfbG9nZ2VyLmQoVGFsa2VyUm91dGVMb2cuX2NyZWF0ZU1lc3NhZ2Uocm91dGUsIFJvdXRlTG9nVHlwZS5wb3ApKTsKICB9CgogIEBvdmVycmlkZQogIHZvaWQgZGlkUmVtb3ZlKFJvdXRlPGR5bmFtaWM+IHJvdXRlLCBSb3V0ZTxkeW5hbWljPj8gcHJldmlvdXNSb3V0ZSkgewogICAgc3VwZXIuZGlkUmVtb3ZlKHJvdXRlLCBwcmV2aW91c1JvdXRlKTsKICAgIGlmIChyb3V0ZS5zZXR0aW5ncy5uYW1lID09IG51bGwpIHsKICAgICAgcmV0dXJuOwogICAgfQogICAgX2xvZ2dlci5kKFRhbGtlclJvdXRlTG9nLl9jcmVhdGVNZXNzYWdlKHJvdXRlLCBSb3V0ZUxvZ1R5cGUucmVtb3ZlKSk7CiAgfQoKICBAb3ZlcnJpZGUKICB2b2lkIGRpZFJlcGxhY2Uoe1JvdXRlPGR5bmFtaWM+PyBuZXdSb3V0ZSwgUm91dGU8ZHluYW1pYz4/IG9sZFJvdXRlfSkgewogICAgc3VwZXIuZGlkUmVwbGFjZShuZXdSb3V0ZTogbmV3Um91dGUsIG9sZFJvdXRlOiBvbGRSb3V0ZSk7CiAgICBpZiAobmV3Um91dGU/LnNldHRpbmdzLm5hbWUgPT0gbnVsbCkgewogICAgICByZXR1cm47CiAgICB9CiAgICBfbG9nZ2VyLmQoVGFsa2VyUm91dGVMb2cuX2NyZWF0ZU1lc3NhZ2UobmV3Um91dGUhLCBSb3V0ZUxvZ1R5cGUucmVwbGFjZSkpOwogIH0KfQoKZW51bSBSb3V0ZUxvZ1R5cGUgeyBwdXNoLCBwb3AsIHJlbW92ZSwgcmVwbGFjZSB9CgpjbGFzcyBUYWxrZXJSb3V0ZUxvZyBleHRlbmRzIFRhbGtlckxvZyB7CiAgVGFsa2VyUm91dGVMb2coe3JlcXVpcmVkIFJvdXRlIHJvdXRlLCByZXF1aXJlZCBSb3V0ZUxvZ1R5cGUgdHlwZX0pCiAgICA6IHN1cGVyKF9jcmVhdGVNZXNzYWdlKHJvdXRlLCB0eXBlKSk7CgogIEBvdmVycmlkZQogIEFuc2lQZW4gZ2V0IHBlbiA9PiBBbnNpUGVuKCkuLnh0ZXJtKDEzNSk7CgogIEBvdmVycmlkZQogIFN0cmluZyBnZXQga2V5ID0+IFRhbGtlcktleS5yb3V0ZTsKCiAgc3RhdGljIFN0cmluZyBfY3JlYXRlTWVzc2FnZShSb3V0ZTxkeW5hbWljPiByb3V0ZSwgUm91dGVMb2dUeXBlIHR5cGUpIHsKICAgIGZpbmFsIGJ1ZmZlciA9IFN0cmluZ0J1ZmZlcigpOwogICAgYnVmZmVyLndyaXRlKHR5cGUubmFtZSk7CiAgICBidWZmZXIud3JpdGUoJyByb3V0ZSBuYW1lZCAnKTsKICAgIGJ1ZmZlci53cml0ZShyb3V0ZS5zZXR0aW5ncy5uYW1lID8/ICdudWxsJyk7CgogICAgcmV0dXJuIGJ1ZmZlci50b1N0cmluZygpOwogIH0KfQo=", "type": "text" }, { @@ -551,7 +545,7 @@ final flutterProjectWithMonoRepoBundle = MasonBundle.fromJson({ { "path": "{{ name.snakeCase() }}/packages/core/pubspec.yaml", "data": - "bmFtZTogY29yZQpkZXNjcmlwdGlvbjogIkNyb3NzLWN1dHRpbmcgZm91bmRhdGlvbiBmb3Ige3tuYW1lLnRpdGxlQ2FzZSgpfX06IGxvZ2dpbmcsIERJIHdpcmluZywgcm91dGluZyBjb250cmFjdHMsIGVycm9yIHR5cGVzIGFuZCBCTG9DIHBsdW1iaW5nLiIKdmVyc2lvbjogMC4xLjAKcHVibGlzaF90bzogJ25vbmUnCgplbnZpcm9ubWVudDoKICBzZGs6IF4zLjguMAogIGZsdXR0ZXI6ICI+PTMuMjkuMCIKCmRlcGVuZGVuY2llczoKICBmbHV0dGVyOgogICAgc2RrOiBmbHV0dGVyCiAgYmxvYzogXjkuMC4wCiAgZXF1YXRhYmxlOiBeMi4wLjUKICBmbHV0dGVyX2Jsb2M6IF45LjEuMQogIGdvX3JvdXRlcjogXjE1LjEuMwogIGludGw6IF4wLjIwLjIKICBqc29uX2Fubm90YXRpb246IF40LjkuMAogIHRhbGtlcl9mbHV0dGVyOiBeNC40LjEKICBkaToKICAgIHBhdGg6IC4uL2RpCiAgbG9jYWxpemF0aW9uOgogICAgcGF0aDogLi4vbG9jYWxpemF0aW9uCnt7I2ZpcmViYXNlfX0gIGZpcmViYXNlX2NvcmU6IF4zLjE0LjAKe3svZmlyZWJhc2V9fXt7I2F1dGh9fSAgZmlyZWJhc2VfYXV0aDogXjUuMy40Cnt7L2F1dGh9fXt7I2ZpcmVzdG9yZX19ICBjbG91ZF9maXJlc3RvcmU6IF41LjUuMwogIGNsb3VkX2Z1bmN0aW9uczogXjUuMi4zCnt7L2ZpcmVzdG9yZX19CmRldl9kZXBlbmRlbmNpZXM6CiAgZmx1dHRlcl90ZXN0OgogICAgc2RrOiBmbHV0dGVyCiAgZmx1dHRlcl9saW50czogXjUuMC4wCgpkZXBlbmRlbmN5X292ZXJyaWRlczoKICBpbnRsOiBeMC4yMC4yCgpmbHV0dGVyOgo=", + "bmFtZTogY29yZQpkZXNjcmlwdGlvbjogIkNyb3NzLWN1dHRpbmcgZm91bmRhdGlvbiBmb3Ige3tuYW1lLnRpdGxlQ2FzZSgpfX06IGxvZ2dpbmcsIERJIHdpcmluZywgcm91dGluZyBjb250cmFjdHMsIGVycm9yIHR5cGVzIGFuZCBCTG9DIHBsdW1iaW5nLiIKdmVyc2lvbjogMC4xLjAKcHVibGlzaF90bzogJ25vbmUnCgplbnZpcm9ubWVudDoKICBzZGs6IF4zLjguMAogIGZsdXR0ZXI6ICI+PTMuNDcuNSIKCmRlcGVuZGVuY2llczoKICBmbHV0dGVyOgogICAgc2RrOiBmbHV0dGVyCiAgYmxvYzogXjkuMC4wCiAgZXF1YXRhYmxlOiBeMi4wLjUKICBmbHV0dGVyX2Jsb2M6IF45LjEuMQogIGdvX3JvdXRlcjogXjE4LjAuMQogIGludGw6IF4wLjIwLjIKICBqc29uX2Fubm90YXRpb246IF40LjkuMAogIHRhbGtlcl9mbHV0dGVyOiBeNS4xLjIwCiAgZGk6CiAgICBwYXRoOiAuLi9kaQogIGxvY2FsaXphdGlvbjoKICAgIHBhdGg6IC4uL2xvY2FsaXphdGlvbgp7eyNmaXJlYmFzZX19ICBmaXJlYmFzZV9jb3JlOiBeNC4xNS4wCnt7L2ZpcmViYXNlfX17eyNhdXRofX0gIGZpcmViYXNlX2F1dGg6IF42LjcuMAp7ey9hdXRofX17eyNmaXJlc3RvcmV9fSAgY2xvdWRfZmlyZXN0b3JlOiBeNi4xMC4wCiAgY2xvdWRfZnVuY3Rpb25zOiBeNi41LjAKe3svZmlyZXN0b3JlfX0KZGV2X2RlcGVuZGVuY2llczoKICBmbHV0dGVyX3Rlc3Q6CiAgICBzZGs6IGZsdXR0ZXIKICBmbHV0dGVyX2xpbnRzOiBeNi4wLjAKCgpmbHV0dGVyOgoKcmVzb2x1dGlvbjogd29ya3NwYWNlCg==", "type": "text" }, { @@ -563,7 +557,7 @@ final flutterProjectWithMonoRepoBundle = MasonBundle.fromJson({ { "path": "{{ name.snakeCase() }}/packages/core/test/core_test.dart", "data": - "aW1wb3J0ICdwYWNrYWdlOmNvcmUvZGV2ZWxvcGVyL2VtdWxhdG9ycy5kYXJ0JyBhcyBlbXVsYXRvcnM7Cnt7I2ZpcmVzdG9yZX19aW1wb3J0ICdwYWNrYWdlOmNsb3VkX2ZpcmVzdG9yZS9jbG91ZF9maXJlc3RvcmUuZGFydCc7Cnt7L2ZpcmVzdG9yZX19CmltcG9ydCAncGFja2FnZTpjb3JlL2NvcmUuZGFydCc7CmltcG9ydCAncGFja2FnZTpjb3JlL2xvZ2dlci90YWxrZXJfbG9nZ2VyX2ltcGwuZGFydCc7CmltcG9ydCAncGFja2FnZTpkaS9kaS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvbWF0ZXJpYWwuZGFydCc7CmltcG9ydCAncGFja2FnZTpmbHV0dGVyX2Jsb2MvZmx1dHRlcl9ibG9jLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Zmx1dHRlcl90ZXN0L2ZsdXR0ZXJfdGVzdC5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOnRhbGtlcl9mbHV0dGVyL3RhbGtlcl9mbHV0dGVyLmRhcnQnIGhpZGUgVGFsa2VyUm91dGVMb2c7CgpjbGFzcyBfQ291bnRlciBleHRlbmRzIEJsb2M8aW50LCBpbnQ+IHsKICBfQ291bnRlcigpIDogc3VwZXIoMCkgewogICAgb248aW50PigoZXZlbnQsIGVtaXQpID0+IGVtaXQoZXZlbnQpKTsKICB9Cn0KCnZvaWQgbWFpbigpIHsKe3sjZW11bGF0b3JzfX0gIHRlc3QoJ2VtdWxhdG9yIHNldHVwIGZhaWx1cmVzIGFyZSBzdXJmYWNlZCBiZWZvcmUgRmlyZWJhc2UgaW5pdGlhbGl6YXRpb24nLCAoKSBhc3luYyB7CiAgICBhd2FpdCBleHBlY3RMYXRlcihlbXVsYXRvcnMuaW5pdCgpLCB0aHJvd3NBKGFueXRoaW5nKSk7CiAgfSk7Cnt7L2VtdWxhdG9yc319Cnt7XmVtdWxhdG9yc319ICB0ZXN0KCdlbXVsYXRvciBzZXR1cCBpcyBhIG5vLW9wIHdpdGhvdXQgZW11bGF0ZWQgc2VydmljZXMnLCBlbXVsYXRvcnMuaW5pdCk7Cnt7L2VtdWxhdG9yc319CiAgc2V0VXAoaW5pdCk7CiAgdGVhckRvd24oZGkucmVzZXQpOwoKICB0ZXN0KAogICAgJ2V2ZW50IGJhc2UgZGVmaW5lcyB2YWx1ZSBlcXVhbGl0eSBhbmQgREkgZXhwb3NlcyBpdHMgY29uZmlndXJlZCBjb250YWluZXInLAogICAgKCkgewogICAgICBmaW5hbCBldmVudCA9IFJlZnJlc2hQcm9maWxlKCk7CiAgICAgIGV4cGVjdChldmVudC5wcm9wcywgaXNFbXB0eSk7CiAgICAgIGV4cGVjdChldmVudCwgUmVmcmVzaFByb2ZpbGUoKSk7CiAgICAgIGV4cGVjdChkaS5nZXRJdC5pc1JlZ2lzdGVyZWQ8TG9nZ2VyPigpLCBpc1RydWUpOwogICAgfSwKICApOwoKICB0ZXN0V2lkZ2V0cygnY29udGV4dCBldmVudCBleHRlbnNpb24gYnJvYWRjYXN0cyB0byB0aGUgb3duZWQgY2hhbm5lbCcsICgKICAgIHRlc3RlciwKICApIGFzeW5jIHsKICAgIGxhdGUgQnVpbGRDb250ZXh0IGNvbnRleHQ7CiAgICBhd2FpdCB0ZXN0ZXIucHVtcFdpZGdldCgKICAgICAgR2xvYmFsRXZlbnRDaGFubmVsUHJvdmlkZXIoCiAgICAgICAgY2hpbGQ6IEJ1aWxkZXIoCiAgICAgICAgICBidWlsZGVyOiAoY3R4KSB7CiAgICAgICAgICAgIGNvbnRleHQgPSBjdHg7CiAgICAgICAgICAgIHJldHVybiBjb25zdCBTaXplZEJveCgpOwogICAgICAgICAgfSwKICAgICAgICApLAogICAgICApLAogICAgKTsKICAgIGNvbnRleHQuZmlyZShjb25zdCBSZWZyZXNoUHJvZmlsZSgpKTsKICAgIGF3YWl0IHRlc3Rlci5wdW1wKCk7CiAgICBleHBlY3QoCiAgICAgIGNvbnRleHQucmVhZDxHbG9iYWxFdmVudENoYW5uZWw+KCkuc3RhdGUuY3VycmVudCwKICAgICAgY29uc3QgUmVmcmVzaFByb2ZpbGUoKSwKICAgICk7CiAgfSk7CgogIHRlc3QoJ2V2ZW50IGNoYW5uZWwgYm91bmRzIGhpc3Rvcnkgd2hpbGUgcmV0YWluaW5nIHRvdGFsIGNvdW50cycsICgpIGFzeW5jIHsKICAgIGZpbmFsIGNoYW5uZWwgPSBHbG9iYWxFdmVudENoYW5uZWwobWF4UmVjZW50RXZlbnRzOiAyKTsKICAgIGZpbmFsIHN0YXRlcyA9IDxHbG9iYWxFdmVudFN0YXRlPltdOwogICAgZmluYWwgc3Vic2NyaXB0aW9uID0gY2hhbm5lbC5zdHJlYW0ubGlzdGVuKHN0YXRlcy5hZGQpOwogICAgZm9yICh2YXIgaSA9IDA7IGkgPCAzOyBpKyspIHsKICAgICAgY2hhbm5lbC5hZGQoY29uc3QgRmlyZUdsb2JhbEV2ZW50KGV2ZW50VHlwZTogUmVmcmVzaFByb2ZpbGUoKSkpOwogICAgfQogICAgYXdhaXQgcHVtcEV2ZW50UXVldWUoKTsKICAgIGV4cGVjdChzdGF0ZXMsIGhhc0xlbmd0aCgzKSk7CiAgICBleHBlY3QoY2hhbm5lbC5zdGF0ZS5oaXN0b3J5LCBoYXNMZW5ndGgoMikpOwogICAgZXhwZWN0KGNoYW5uZWwuc3RhdGUuZXZlbnRDb3VudHNbUmVmcmVzaFByb2ZpbGVdLCAzKTsKICAgIGV4cGVjdChjaGFubmVsLnN0YXRlLmN1cnJlbnQsIGNvbnN0IFJlZnJlc2hQcm9maWxlKCkpOwogICAgZXhwZWN0KGNoYW5uZWwuc3RhdGUuY29weVdpdGgoKSwgY2hhbm5lbC5zdGF0ZSk7CiAgICBleHBlY3QoY2hhbm5lbC5zdGF0ZS5jb3B5V2l0aChtYXhSZWNlbnRFdmVudHM6IDkpLm1heFJlY2VudEV2ZW50cywgOSk7CiAgICBleHBlY3QoY29uc3QgRmlyZUdsb2JhbEV2ZW50KGV2ZW50VHlwZTogUmVmcmVzaFByb2ZpbGUoKSkucHJvcHMsIFsKICAgICAgY29uc3QgUmVmcmVzaFByb2ZpbGUoKSwKICAgIF0pOwogICAgYXdhaXQgc3Vic2NyaXB0aW9uLmNhbmNlbCgpOwogICAgYXdhaXQgY2hhbm5lbC5jbG9zZSgpOwogIH0pOwoKICB0ZXN0V2lkZ2V0cygKICAgICdwcm92aWRlciwgYnVpbGRlciwgbGlzdGVuZXIgYW5kIGNvbnN1bWVyIHNoYXJlIHRoZSBldmVudCBjaGFubmVsJywKICAgICh0ZXN0ZXIpIGFzeW5jIHsKICAgICAgdmFyIGxpc3RlbmVycyA9IDA7CiAgICAgIGF3YWl0IHRlc3Rlci5wdW1wV2lkZ2V0KAogICAgICAgIE1hdGVyaWFsQXBwKAogICAgICAgICAgaG9tZTogR2xvYmFsRXZlbnRDaGFubmVsUHJvdmlkZXIoCiAgICAgICAgICAgIGxhenk6IGZhbHNlLAogICAgICAgICAgICBjaGlsZDogR2xvYmFsRXZlbnRDaGFubmVsTGlzdGVuZXIoCiAgICAgICAgICAgICAgbGlzdGVuZXI6IChfLCBzdGF0ZSkgewogICAgICAgICAgICAgICAgbGlzdGVuZXJzKys7CiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICBjaGlsZDogR2xvYmFsRXZlbnRDaGFubmVsQ29uc3VtZXIoCiAgICAgICAgICAgICAgICBsaXN0ZW5lcjogKF8sIHN0YXRlKSB7CiAgICAgICAgICAgICAgICAgIGxpc3RlbmVycysrOwogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgIGJ1aWxkZXI6IChfLCBzdGF0ZSkgPT4gR2xvYmFsRXZlbnRDaGFubmVsQnVpbGRlcigKICAgICAgICAgICAgICAgICAgYnVpbGRlcjogKF8sIHN0YXRlKSA9PgogICAgICAgICAgICAgICAgICAgICAgVGV4dCgnRXZlbnRzOiAke3N0YXRlLmhpc3RvcnkubGVuZ3RofScpLAogICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICApLAogICAgICAgICAgICApLAogICAgICAgICAgKSwKICAgICAgICApLAogICAgICApOwogICAgICBleHBlY3QoZmluZC50ZXh0KCdFdmVudHM6IDAnKSwgZmluZHNPbmVXaWRnZXQpOwogICAgICBCbG9jUHJvdmlkZXIub2Y8R2xvYmFsRXZlbnRDaGFubmVsPigKICAgICAgICB0ZXN0ZXIuZWxlbWVudChmaW5kLnRleHQoJ0V2ZW50czogMCcpKSwKICAgICAgKS5hZGQoY29uc3QgRmlyZUdsb2JhbEV2ZW50KGV2ZW50VHlwZTogUmVmcmVzaFByb2ZpbGUoKSkpOwogICAgICBhd2FpdCB0ZXN0ZXIucHVtcEFuZFNldHRsZSgpOwogICAgICBleHBlY3QoZmluZC50ZXh0KCdFdmVudHM6IDEnKSwgZmluZHNPbmVXaWRnZXQpOwogICAgICBleHBlY3QobGlzdGVuZXJzLCAyKTsKICAgICAgYXdhaXQgdGVzdGVyLnB1bXBXaWRnZXQoY29uc3QgU2l6ZWRCb3goKSk7CiAgICB9LAogICk7CgogIHRlc3QoJ2RhdGUgYW5kIHN0cmluZyBoZWxwZXJzIGhhbmRsZSBudWxsIGFuZCB2YWxpZCBpbnB1dCcsICgpIHsKICAgIGZpbmFsIGRhdGUgPSBEYXRlVGltZSgyMDI2LCAxLCAxNSwgMTMsIDE0LCAxNSk7CiAgICBleHBlY3QoRGF0ZVRpbWVDb252ZXJ0ZXIudG9WaWV3Rm9ybWF0KG51bGwpLCAnJyk7CiAgICBleHBlY3QoRGF0ZVRpbWVDb252ZXJ0ZXIudG9WaWV3Rm9ybWF0KGRhdGUpLCAnSmFudWFyeSAxNSwgMjAyNicpOwogICAgZXhwZWN0KERhdGVUaW1lQ29udmVydGVyLnRvVmlld0Zvcm1hdFdpdGhUaW1lKG51bGwpLCAnJyk7CiAgICBleHBlY3QoCiAgICAgIERhdGVUaW1lQ29udmVydGVyLnRvVmlld0Zvcm1hdFdpdGhUaW1lKGRhdGUpLAogICAgICAnSmFudWFyeSAxNSwgMjAyNiwgMToxNCBQTScsCiAgICApOwogICAgZXhwZWN0KERhdGVUaW1lQ29udmVydGVyLnRvVmlld0Zvcm1hdFRpbWUobnVsbCksICcnKTsKICAgIGV4cGVjdChEYXRlVGltZUNvbnZlcnRlci50b1ZpZXdGb3JtYXRUaW1lKGRhdGUpLCAnMToxNDoxNSBQTScpOwogICAgZXhwZWN0KChudWxsIGFzIFN0cmluZz8pLmFzQm9vbCwgaXNGYWxzZSk7CiAgICBmb3IgKGZpbmFsIHZhbHVlIGluIFsnVFJVRScsICd0cnVlJywgJzEnXSkgewogICAgICBleHBlY3QodmFsdWUuYXNCb29sLCBpc1RydWUpOwogICAgfQogICAgZXhwZWN0KCdmYWxzZScuYXNCb29sLCBpc0ZhbHNlKTsKICAgIGV4cGVjdChDb3JlRXhjZXB0aW9uKG1lc3NhZ2U6ICdtZXNzYWdlJykubWVzc2FnZSwgJ21lc3NhZ2UnKTsKICAgIGV4cGVjdChVc2VyTm90Rm91bmRFeGNlcHRpb24oKS5tZXNzYWdlLCBpc05vdEVtcHR5KTsKICB9KTsKCiAgdGVzdCgKICAgICd0aW1lc3RhbXAgYWRhcHRlcnMgYWNjZXB0IGRvY3VtZW50ZWQgZm9ybXMgYW5kIHJlamVjdCBpbnZhbGlkIHNoYXBlcycsCiAgICAoKSB7CiAgICAgIC8vIEV4ZXJjaXNlIHJ1bnRpbWUgY29uc3RydWN0aW9uIGFzIHdlbGwgYXMgYW5ub3RhdGlvbi1zdHlsZSBjb25zdCB1c2UuCiAgICAgIC8vIGlnbm9yZTogcHJlZmVyX2NvbnN0X2NvbnN0cnVjdG9ycwogICAgICBmaW5hbCBjb252ZXJ0ZXIgPSBUaW1lc3RhbXBDb252ZXJ0ZXIoKTsKICAgICAgLy8gaWdub3JlOiBwcmVmZXJfY29uc3RfY29uc3RydWN0b3JzCiAgICAgIGZpbmFsIG51bGxhYmxlID0gVGltZXN0YW1wQ29udmVydGVyTnVsbGFibGUoKTsKICAgICAgZmluYWwgZGF0ZSA9IERhdGVUaW1lLnV0YygyMDI2KTsKICAgICAgZXhwZWN0KGNvbnZlcnRlci5mcm9tSnNvbihkYXRlLnRvSXNvODYwMVN0cmluZygpKS50b1V0YygpLCBkYXRlKTsKe3sjZmlyZXN0b3JlfX0gICAgICBleHBlY3QoY29udmVydGVyLmZyb21Kc29uKFRpbWVzdGFtcC5mcm9tRGF0ZShkYXRlKSkudG9VdGMoKSwgZGF0ZSk7Cnt7L2ZpcmVzdG9yZX19CiAgICAgIGV4cGVjdCgKICAgICAgICBjb252ZXJ0ZXIuZnJvbUpzb24oewogICAgICAgICAgJ19zZWNvbmRzJzogMSwKICAgICAgICAgICdfbmFub3NlY29uZHMnOiA1MDAwMDAwMDAsCiAgICAgICAgfSkubWlsbGlzZWNvbmRzU2luY2VFcG9jaCwKICAgICAgICAxNTAwLAogICAgICApOwogICAgICBleHBlY3QoY29udmVydGVyLnRvSnNvbihkYXRlKSwgZGF0ZS50b0lzbzg2MDFTdHJpbmcoKSk7CiAgICAgIGV4cGVjdChjb252ZXJ0ZXIudG9Kc29uKG51bGwpLCBpc051bGwpOwogICAgICBleHBlY3QobnVsbGFibGUuZnJvbUpzb24obnVsbCksIGlzTnVsbCk7CiAgICAgIGV4cGVjdChudWxsYWJsZS50b0pzb24obnVsbCksIGlzTnVsbCk7CiAgICAgIGV4cGVjdChudWxsYWJsZS50b0pzb24oZGF0ZSksIGRhdGUudG9Jc284NjAxU3RyaW5nKCkpOwogICAgICBleHBlY3QoKCkgPT4gY29udmVydGVyLmZyb21Kc29uKG51bGwpLCB0aHJvd3NBcmd1bWVudEVycm9yKTsKICAgIH0sCiAgKTsKCiAgdGVzdCgnbG9nZ2VyIGZvcndhcmRzIGV2ZXJ5IGxldmVsIGFuZCBleHBvc2VzIGl0cyBhZGFwdGVyJywgKCkgewogICAgZmluYWwgdGFsa2VyID0gVGFsa2VyKHNldHRpbmdzOiBUYWxrZXJTZXR0aW5ncyh1c2VDb25zb2xlTG9nczogZmFsc2UpKTsKICAgIGZpbmFsIGxvZ2dlciA9IFRhbGtlckxvZ2dlckltcGwodGFsa2VyKTsKICAgIGxvZ2dlci5kKCdkZWJ1ZycpOwogICAgbG9nZ2VyLmkoJ2luZm8nKTsKICAgIGxvZ2dlci53KCd3YXJuaW5nJyk7CiAgICBsb2dnZXIuZSgnZXJyb3InKTsKICAgIGV4cGVjdChsb2dnZXIubG9nZ2VyLCBzYW1lKHRhbGtlcikpOwogICAgZXhwZWN0KHRhbGtlci5oaXN0b3J5LCBoYXNMZW5ndGgoNCkpOwogIH0pOwoKICB0ZXN0KAogICAgJ29ic2VydmVycyBzdXBwb3J0IGV2ZW50LCB0cmFuc2l0aW9uLCBlcnJvciBhbmQgcm91dGUgbGlmZWN5Y2xlcycsCiAgICAoKSBhc3luYyB7CiAgICAgIGZpbmFsIGJsb2MgPSBfQ291bnRlcigpOwogICAgICBmaW5hbCBvYnNlcnZlciA9IENvcmVCbG9jT2JzZXJ2ZXIoKTsKICAgICAgb2JzZXJ2ZXIub25FdmVudChibG9jLCAxKTsKICAgICAgb2JzZXJ2ZXIub25UcmFuc2l0aW9uKAogICAgICAgIGJsb2MsCiAgICAgICAgY29uc3QgVHJhbnNpdGlvbihjdXJyZW50U3RhdGU6IDAsIGV2ZW50OiAxLCBuZXh0U3RhdGU6IDEpLAogICAgICApOwogICAgICBvYnNlcnZlci5vbkVycm9yKGJsb2MsIFN0YXRlRXJyb3IoJ3Rlc3QnKSwgU3RhY2tUcmFjZS5jdXJyZW50KTsKICAgICAgZmluYWwgcm91dGVzID0gQ29yZVJvdXRlT2JzZXJ2ZXIoKTsKICAgICAgZmluYWwgbmFtZWQgPSBNYXRlcmlhbFBhZ2VSb3V0ZTx2b2lkPigKICAgICAgICBidWlsZGVyOiAoXykgPT4gY29uc3QgU2l6ZWRCb3goKSwKICAgICAgICBzZXR0aW5nczogY29uc3QgUm91dGVTZXR0aW5ncyhuYW1lOiAnL3Rlc3QnLCBhcmd1bWVudHM6ICdhcmcnKSwKICAgICAgKTsKICAgICAgZmluYWwgdW5uYW1lZCA9IE1hdGVyaWFsUGFnZVJvdXRlPHZvaWQ+KGJ1aWxkZXI6IChfKSA9PiBjb25zdCBTaXplZEJveCgpKTsKICAgICAgZm9yIChmaW5hbCByb3V0ZSBpbiBbbmFtZWQsIHVubmFtZWRdKSB7CiAgICAgICAgcm91dGVzLmRpZFB1c2gocm91dGUsIG51bGwpOwogICAgICAgIHJvdXRlcy5kaWRQb3Aocm91dGUsIG51bGwpOwogICAgICAgIHJvdXRlcy5kaWRSZW1vdmUocm91dGUsIG51bGwpOwogICAgICAgIHJvdXRlcy5kaWRSZXBsYWNlKG5ld1JvdXRlOiByb3V0ZSk7CiAgICAgIH0KICAgICAgcm91dGVzLmRpZFJlcGxhY2UoKTsKICAgICAgZmluYWwgbG9nID0gVGFsa2VyUm91dGVMb2cocm91dGU6IHVubmFtZWQsIHR5cGU6IFJvdXRlTG9nVHlwZS5wdXNoKTsKICAgICAgZXhwZWN0KGxvZy5rZXksIFRhbGtlckxvZ1R5cGUucm91dGUua2V5KTsKICAgICAgZXhwZWN0KGxvZy5wZW4sIGlzQTxBbnNpUGVuPigpKTsKICAgICAgYXdhaXQgYmxvYy5jbG9zZSgpOwogICAgfSwKICApOwp9Cg==", + "aW1wb3J0ICdwYWNrYWdlOmNvcmUvZGV2ZWxvcGVyL2VtdWxhdG9ycy5kYXJ0JyBhcyBlbXVsYXRvcnM7Cnt7I2ZpcmVzdG9yZX19aW1wb3J0ICdwYWNrYWdlOmNsb3VkX2ZpcmVzdG9yZS9jbG91ZF9maXJlc3RvcmUuZGFydCc7Cnt7L2ZpcmVzdG9yZX19CmltcG9ydCAncGFja2FnZTpjb3JlL2NvcmUuZGFydCc7CmltcG9ydCAncGFja2FnZTpjb3JlL2xvZ2dlci90YWxrZXJfbG9nZ2VyX2ltcGwuZGFydCc7CmltcG9ydCAncGFja2FnZTpkaS9kaS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvbWF0ZXJpYWwuZGFydCc7CmltcG9ydCAncGFja2FnZTpmbHV0dGVyX2Jsb2MvZmx1dHRlcl9ibG9jLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Zmx1dHRlcl90ZXN0L2ZsdXR0ZXJfdGVzdC5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOnRhbGtlcl9mbHV0dGVyL3RhbGtlcl9mbHV0dGVyLmRhcnQnIGhpZGUgVGFsa2VyUm91dGVMb2c7CgpjbGFzcyBfQ291bnRlciBleHRlbmRzIEJsb2M8aW50LCBpbnQ+IHsKICBfQ291bnRlcigpIDogc3VwZXIoMCkgewogICAgb248aW50PigoZXZlbnQsIGVtaXQpID0+IGVtaXQoZXZlbnQpKTsKICB9Cn0KCnZvaWQgbWFpbigpIHsKe3sjZW11bGF0b3JzfX0gIHRlc3QoJ2VtdWxhdG9yIHNldHVwIGZhaWx1cmVzIGFyZSBzdXJmYWNlZCBiZWZvcmUgRmlyZWJhc2UgaW5pdGlhbGl6YXRpb24nLCAoKSBhc3luYyB7CiAgICBhd2FpdCBleHBlY3RMYXRlcihlbXVsYXRvcnMuaW5pdCgpLCB0aHJvd3NBKGFueXRoaW5nKSk7CiAgfSk7Cnt7L2VtdWxhdG9yc319Cnt7XmVtdWxhdG9yc319ICB0ZXN0KCdlbXVsYXRvciBzZXR1cCBpcyBhIG5vLW9wIHdpdGhvdXQgZW11bGF0ZWQgc2VydmljZXMnLCBlbXVsYXRvcnMuaW5pdCk7Cnt7L2VtdWxhdG9yc319CiAgc2V0VXAoaW5pdCk7CiAgdGVhckRvd24oZGkucmVzZXQpOwoKICB0ZXN0KAogICAgJ2V2ZW50IGJhc2UgZGVmaW5lcyB2YWx1ZSBlcXVhbGl0eSBhbmQgREkgZXhwb3NlcyBpdHMgY29uZmlndXJlZCBjb250YWluZXInLAogICAgKCkgewogICAgICBmaW5hbCBldmVudCA9IFJlZnJlc2hQcm9maWxlKCk7CiAgICAgIGV4cGVjdChldmVudC5wcm9wcywgaXNFbXB0eSk7CiAgICAgIGV4cGVjdChldmVudCwgUmVmcmVzaFByb2ZpbGUoKSk7CiAgICAgIGV4cGVjdChkaS5nZXRJdC5pc1JlZ2lzdGVyZWQ8TG9nZ2VyPigpLCBpc1RydWUpOwogICAgfSwKICApOwoKICB0ZXN0V2lkZ2V0cygnY29udGV4dCBldmVudCBleHRlbnNpb24gYnJvYWRjYXN0cyB0byB0aGUgb3duZWQgY2hhbm5lbCcsICgKICAgIHRlc3RlciwKICApIGFzeW5jIHsKICAgIGxhdGUgQnVpbGRDb250ZXh0IGNvbnRleHQ7CiAgICBhd2FpdCB0ZXN0ZXIucHVtcFdpZGdldCgKICAgICAgR2xvYmFsRXZlbnRDaGFubmVsUHJvdmlkZXIoCiAgICAgICAgY2hpbGQ6IEJ1aWxkZXIoCiAgICAgICAgICBidWlsZGVyOiAoY3R4KSB7CiAgICAgICAgICAgIGNvbnRleHQgPSBjdHg7CiAgICAgICAgICAgIHJldHVybiBjb25zdCBTaXplZEJveCgpOwogICAgICAgICAgfSwKICAgICAgICApLAogICAgICApLAogICAgKTsKICAgIGNvbnRleHQuZmlyZShjb25zdCBSZWZyZXNoUHJvZmlsZSgpKTsKICAgIGF3YWl0IHRlc3Rlci5wdW1wKCk7CiAgICBleHBlY3QoCiAgICAgIGNvbnRleHQucmVhZDxHbG9iYWxFdmVudENoYW5uZWw+KCkuc3RhdGUuY3VycmVudCwKICAgICAgY29uc3QgUmVmcmVzaFByb2ZpbGUoKSwKICAgICk7CiAgfSk7CgogIHRlc3QoJ2V2ZW50IGNoYW5uZWwgYm91bmRzIGhpc3Rvcnkgd2hpbGUgcmV0YWluaW5nIHRvdGFsIGNvdW50cycsICgpIGFzeW5jIHsKICAgIGZpbmFsIGNoYW5uZWwgPSBHbG9iYWxFdmVudENoYW5uZWwobWF4UmVjZW50RXZlbnRzOiAyKTsKICAgIGZpbmFsIHN0YXRlcyA9IDxHbG9iYWxFdmVudFN0YXRlPltdOwogICAgZmluYWwgc3Vic2NyaXB0aW9uID0gY2hhbm5lbC5zdHJlYW0ubGlzdGVuKHN0YXRlcy5hZGQpOwogICAgZm9yICh2YXIgaSA9IDA7IGkgPCAzOyBpKyspIHsKICAgICAgY2hhbm5lbC5hZGQoY29uc3QgRmlyZUdsb2JhbEV2ZW50KGV2ZW50VHlwZTogUmVmcmVzaFByb2ZpbGUoKSkpOwogICAgfQogICAgYXdhaXQgcHVtcEV2ZW50UXVldWUoKTsKICAgIGV4cGVjdChzdGF0ZXMsIGhhc0xlbmd0aCgzKSk7CiAgICBleHBlY3QoY2hhbm5lbC5zdGF0ZS5oaXN0b3J5LCBoYXNMZW5ndGgoMikpOwogICAgZXhwZWN0KGNoYW5uZWwuc3RhdGUuZXZlbnRDb3VudHNbUmVmcmVzaFByb2ZpbGVdLCAzKTsKICAgIGV4cGVjdChjaGFubmVsLnN0YXRlLmN1cnJlbnQsIGNvbnN0IFJlZnJlc2hQcm9maWxlKCkpOwogICAgZXhwZWN0KGNoYW5uZWwuc3RhdGUuY29weVdpdGgoKSwgY2hhbm5lbC5zdGF0ZSk7CiAgICBleHBlY3QoY2hhbm5lbC5zdGF0ZS5jb3B5V2l0aChtYXhSZWNlbnRFdmVudHM6IDkpLm1heFJlY2VudEV2ZW50cywgOSk7CiAgICBleHBlY3QoY29uc3QgRmlyZUdsb2JhbEV2ZW50KGV2ZW50VHlwZTogUmVmcmVzaFByb2ZpbGUoKSkucHJvcHMsIFsKICAgICAgY29uc3QgUmVmcmVzaFByb2ZpbGUoKSwKICAgIF0pOwogICAgYXdhaXQgc3Vic2NyaXB0aW9uLmNhbmNlbCgpOwogICAgYXdhaXQgY2hhbm5lbC5jbG9zZSgpOwogIH0pOwoKICB0ZXN0V2lkZ2V0cygKICAgICdwcm92aWRlciwgYnVpbGRlciwgbGlzdGVuZXIgYW5kIGNvbnN1bWVyIHNoYXJlIHRoZSBldmVudCBjaGFubmVsJywKICAgICh0ZXN0ZXIpIGFzeW5jIHsKICAgICAgdmFyIGxpc3RlbmVycyA9IDA7CiAgICAgIGF3YWl0IHRlc3Rlci5wdW1wV2lkZ2V0KAogICAgICAgIE1hdGVyaWFsQXBwKAogICAgICAgICAgaG9tZTogR2xvYmFsRXZlbnRDaGFubmVsUHJvdmlkZXIoCiAgICAgICAgICAgIGxhenk6IGZhbHNlLAogICAgICAgICAgICBjaGlsZDogR2xvYmFsRXZlbnRDaGFubmVsTGlzdGVuZXIoCiAgICAgICAgICAgICAgbGlzdGVuZXI6IChfLCBzdGF0ZSkgewogICAgICAgICAgICAgICAgbGlzdGVuZXJzKys7CiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICBjaGlsZDogR2xvYmFsRXZlbnRDaGFubmVsQ29uc3VtZXIoCiAgICAgICAgICAgICAgICBsaXN0ZW5lcjogKF8sIHN0YXRlKSB7CiAgICAgICAgICAgICAgICAgIGxpc3RlbmVycysrOwogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgIGJ1aWxkZXI6IChfLCBzdGF0ZSkgPT4gR2xvYmFsRXZlbnRDaGFubmVsQnVpbGRlcigKICAgICAgICAgICAgICAgICAgYnVpbGRlcjogKF8sIHN0YXRlKSA9PgogICAgICAgICAgICAgICAgICAgICAgVGV4dCgnRXZlbnRzOiAke3N0YXRlLmhpc3RvcnkubGVuZ3RofScpLAogICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICApLAogICAgICAgICAgICApLAogICAgICAgICAgKSwKICAgICAgICApLAogICAgICApOwogICAgICBleHBlY3QoZmluZC50ZXh0KCdFdmVudHM6IDAnKSwgZmluZHNPbmVXaWRnZXQpOwogICAgICBCbG9jUHJvdmlkZXIub2Y8R2xvYmFsRXZlbnRDaGFubmVsPigKICAgICAgICB0ZXN0ZXIuZWxlbWVudChmaW5kLnRleHQoJ0V2ZW50czogMCcpKSwKICAgICAgKS5hZGQoY29uc3QgRmlyZUdsb2JhbEV2ZW50KGV2ZW50VHlwZTogUmVmcmVzaFByb2ZpbGUoKSkpOwogICAgICBhd2FpdCB0ZXN0ZXIucHVtcEFuZFNldHRsZSgpOwogICAgICBleHBlY3QoZmluZC50ZXh0KCdFdmVudHM6IDEnKSwgZmluZHNPbmVXaWRnZXQpOwogICAgICBleHBlY3QobGlzdGVuZXJzLCAyKTsKICAgICAgYXdhaXQgdGVzdGVyLnB1bXBXaWRnZXQoY29uc3QgU2l6ZWRCb3goKSk7CiAgICB9LAogICk7CgogIHRlc3QoJ2RhdGUgYW5kIHN0cmluZyBoZWxwZXJzIGhhbmRsZSBudWxsIGFuZCB2YWxpZCBpbnB1dCcsICgpIHsKICAgIGZpbmFsIGRhdGUgPSBEYXRlVGltZSgyMDI2LCAxLCAxNSwgMTMsIDE0LCAxNSk7CiAgICBleHBlY3QoRGF0ZVRpbWVDb252ZXJ0ZXIudG9WaWV3Rm9ybWF0KG51bGwpLCAnJyk7CiAgICBleHBlY3QoRGF0ZVRpbWVDb252ZXJ0ZXIudG9WaWV3Rm9ybWF0KGRhdGUpLCAnSmFudWFyeSAxNSwgMjAyNicpOwogICAgZXhwZWN0KERhdGVUaW1lQ29udmVydGVyLnRvVmlld0Zvcm1hdFdpdGhUaW1lKG51bGwpLCAnJyk7CiAgICBleHBlY3QoCiAgICAgIERhdGVUaW1lQ29udmVydGVyLnRvVmlld0Zvcm1hdFdpdGhUaW1lKGRhdGUpLAogICAgICAnSmFudWFyeSAxNSwgMjAyNiwgMToxNCBQTScsCiAgICApOwogICAgZXhwZWN0KERhdGVUaW1lQ29udmVydGVyLnRvVmlld0Zvcm1hdFRpbWUobnVsbCksICcnKTsKICAgIGV4cGVjdChEYXRlVGltZUNvbnZlcnRlci50b1ZpZXdGb3JtYXRUaW1lKGRhdGUpLCAnMToxNDoxNSBQTScpOwogICAgZXhwZWN0KChudWxsIGFzIFN0cmluZz8pLmFzQm9vbCwgaXNGYWxzZSk7CiAgICBmb3IgKGZpbmFsIHZhbHVlIGluIFsnVFJVRScsICd0cnVlJywgJzEnXSkgewogICAgICBleHBlY3QodmFsdWUuYXNCb29sLCBpc1RydWUpOwogICAgfQogICAgZXhwZWN0KCdmYWxzZScuYXNCb29sLCBpc0ZhbHNlKTsKICAgIGV4cGVjdChDb3JlRXhjZXB0aW9uKG1lc3NhZ2U6ICdtZXNzYWdlJykubWVzc2FnZSwgJ21lc3NhZ2UnKTsKICAgIGV4cGVjdChVc2VyTm90Rm91bmRFeGNlcHRpb24oKS5tZXNzYWdlLCBpc05vdEVtcHR5KTsKICB9KTsKCiAgdGVzdCgKICAgICd0aW1lc3RhbXAgYWRhcHRlcnMgYWNjZXB0IGRvY3VtZW50ZWQgZm9ybXMgYW5kIHJlamVjdCBpbnZhbGlkIHNoYXBlcycsCiAgICAoKSB7CiAgICAgIC8vIEV4ZXJjaXNlIHJ1bnRpbWUgY29uc3RydWN0aW9uIGFzIHdlbGwgYXMgYW5ub3RhdGlvbi1zdHlsZSBjb25zdCB1c2UuCiAgICAgIC8vIGlnbm9yZTogcHJlZmVyX2NvbnN0X2NvbnN0cnVjdG9ycwogICAgICBmaW5hbCBjb252ZXJ0ZXIgPSBUaW1lc3RhbXBDb252ZXJ0ZXIoKTsKICAgICAgLy8gaWdub3JlOiBwcmVmZXJfY29uc3RfY29uc3RydWN0b3JzCiAgICAgIGZpbmFsIG51bGxhYmxlID0gVGltZXN0YW1wQ29udmVydGVyTnVsbGFibGUoKTsKICAgICAgZmluYWwgZGF0ZSA9IERhdGVUaW1lLnV0YygyMDI2KTsKICAgICAgZXhwZWN0KGNvbnZlcnRlci5mcm9tSnNvbihkYXRlLnRvSXNvODYwMVN0cmluZygpKS50b1V0YygpLCBkYXRlKTsKe3sjZmlyZXN0b3JlfX0gICAgICBleHBlY3QoY29udmVydGVyLmZyb21Kc29uKFRpbWVzdGFtcC5mcm9tRGF0ZShkYXRlKSkudG9VdGMoKSwgZGF0ZSk7Cnt7L2ZpcmVzdG9yZX19CiAgICAgIGV4cGVjdCgKICAgICAgICBjb252ZXJ0ZXIuZnJvbUpzb24oewogICAgICAgICAgJ19zZWNvbmRzJzogMSwKICAgICAgICAgICdfbmFub3NlY29uZHMnOiA1MDAwMDAwMDAsCiAgICAgICAgfSkubWlsbGlzZWNvbmRzU2luY2VFcG9jaCwKICAgICAgICAxNTAwLAogICAgICApOwogICAgICBleHBlY3QoY29udmVydGVyLnRvSnNvbihkYXRlKSwgZGF0ZS50b0lzbzg2MDFTdHJpbmcoKSk7CiAgICAgIGV4cGVjdChjb252ZXJ0ZXIudG9Kc29uKG51bGwpLCBpc051bGwpOwogICAgICBleHBlY3QobnVsbGFibGUuZnJvbUpzb24obnVsbCksIGlzTnVsbCk7CiAgICAgIGV4cGVjdChudWxsYWJsZS50b0pzb24obnVsbCksIGlzTnVsbCk7CiAgICAgIGV4cGVjdChudWxsYWJsZS50b0pzb24oZGF0ZSksIGRhdGUudG9Jc284NjAxU3RyaW5nKCkpOwogICAgICBleHBlY3QoKCkgPT4gY29udmVydGVyLmZyb21Kc29uKG51bGwpLCB0aHJvd3NBcmd1bWVudEVycm9yKTsKICAgIH0sCiAgKTsKCiAgdGVzdCgnbG9nZ2VyIGZvcndhcmRzIGV2ZXJ5IGxldmVsIGFuZCBleHBvc2VzIGl0cyBhZGFwdGVyJywgKCkgewogICAgZmluYWwgdGFsa2VyID0gVGFsa2VyKHNldHRpbmdzOiBUYWxrZXJTZXR0aW5ncyh1c2VDb25zb2xlTG9nczogZmFsc2UpKTsKICAgIGZpbmFsIGxvZ2dlciA9IFRhbGtlckxvZ2dlckltcGwodGFsa2VyKTsKICAgIGxvZ2dlci5kKCdkZWJ1ZycpOwogICAgbG9nZ2VyLmkoJ2luZm8nKTsKICAgIGxvZ2dlci53KCd3YXJuaW5nJyk7CiAgICBsb2dnZXIuZSgnZXJyb3InKTsKICAgIGV4cGVjdChsb2dnZXIubG9nZ2VyLCBzYW1lKHRhbGtlcikpOwogICAgZXhwZWN0KHRhbGtlci5oaXN0b3J5LCBoYXNMZW5ndGgoNCkpOwogIH0pOwoKICB0ZXN0KAogICAgJ29ic2VydmVycyBzdXBwb3J0IGV2ZW50LCB0cmFuc2l0aW9uLCBlcnJvciBhbmQgcm91dGUgbGlmZWN5Y2xlcycsCiAgICAoKSBhc3luYyB7CiAgICAgIGZpbmFsIGJsb2MgPSBfQ291bnRlcigpOwogICAgICBmaW5hbCBvYnNlcnZlciA9IENvcmVCbG9jT2JzZXJ2ZXIoKTsKICAgICAgb2JzZXJ2ZXIub25FdmVudChibG9jLCAxKTsKICAgICAgb2JzZXJ2ZXIub25UcmFuc2l0aW9uKAogICAgICAgIGJsb2MsCiAgICAgICAgY29uc3QgVHJhbnNpdGlvbihjdXJyZW50U3RhdGU6IDAsIGV2ZW50OiAxLCBuZXh0U3RhdGU6IDEpLAogICAgICApOwogICAgICBvYnNlcnZlci5vbkVycm9yKGJsb2MsIFN0YXRlRXJyb3IoJ3Rlc3QnKSwgU3RhY2tUcmFjZS5jdXJyZW50KTsKICAgICAgZmluYWwgcm91dGVzID0gQ29yZVJvdXRlT2JzZXJ2ZXIoKTsKICAgICAgZmluYWwgbmFtZWQgPSBNYXRlcmlhbFBhZ2VSb3V0ZTx2b2lkPigKICAgICAgICBidWlsZGVyOiAoXykgPT4gY29uc3QgU2l6ZWRCb3goKSwKICAgICAgICBzZXR0aW5nczogY29uc3QgUm91dGVTZXR0aW5ncyhuYW1lOiAnL3Rlc3QnLCBhcmd1bWVudHM6ICdhcmcnKSwKICAgICAgKTsKICAgICAgZmluYWwgdW5uYW1lZCA9IE1hdGVyaWFsUGFnZVJvdXRlPHZvaWQ+KGJ1aWxkZXI6IChfKSA9PiBjb25zdCBTaXplZEJveCgpKTsKICAgICAgZm9yIChmaW5hbCByb3V0ZSBpbiBbbmFtZWQsIHVubmFtZWRdKSB7CiAgICAgICAgcm91dGVzLmRpZFB1c2gocm91dGUsIG51bGwpOwogICAgICAgIHJvdXRlcy5kaWRQb3Aocm91dGUsIG51bGwpOwogICAgICAgIHJvdXRlcy5kaWRSZW1vdmUocm91dGUsIG51bGwpOwogICAgICAgIHJvdXRlcy5kaWRSZXBsYWNlKG5ld1JvdXRlOiByb3V0ZSk7CiAgICAgIH0KICAgICAgcm91dGVzLmRpZFJlcGxhY2UoKTsKICAgICAgZmluYWwgbG9nID0gVGFsa2VyUm91dGVMb2cocm91dGU6IHVubmFtZWQsIHR5cGU6IFJvdXRlTG9nVHlwZS5wdXNoKTsKICAgICAgZXhwZWN0KGxvZy5rZXksIFRhbGtlcktleS5yb3V0ZSk7CiAgICAgIGV4cGVjdChsb2cucGVuLCBpc0E8QW5zaVBlbj4oKSk7CiAgICAgIGF3YWl0IGJsb2MuY2xvc2UoKTsKICAgIH0sCiAgKTsKfQo=", "type": "text" }, { @@ -619,7 +613,7 @@ final flutterProjectWithMonoRepoBundle = MasonBundle.fromJson({ "path": "{{ name.snakeCase() }}/packages/design_system/lib/components/network_url_image.dart", "data": - "aW1wb3J0ICdkYXJ0Om1hdGgnOwoKaW1wb3J0ICdwYWNrYWdlOmNhY2hlZF9uZXR3b3JrX2ltYWdlL2NhY2hlZF9uZXR3b3JrX2ltYWdlLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Zmx1dHRlci9tYXRlcmlhbC5kYXJ0JzsKCmNsYXNzIE5ldHdvcmtVcmxJbWFnZSBleHRlbmRzIFN0YXRlbGVzc1dpZGdldCB7CiAgZmluYWwgU3RyaW5nIHVybDsKICBmaW5hbCBkb3VibGU/IHdpZHRoOwogIGZpbmFsIGRvdWJsZT8gaGVpZ2h0OwogIGZpbmFsIEJveEZpdD8gYm94Rml0OwoKICBjb25zdCBOZXR3b3JrVXJsSW1hZ2UoewogICAgc3VwZXIua2V5LAogICAgcmVxdWlyZWQgdGhpcy51cmwsCiAgICB0aGlzLndpZHRoLAogICAgdGhpcy5oZWlnaHQsCiAgICB0aGlzLmJveEZpdCA9IEJveEZpdC5jb3ZlciwKICB9KTsKCiAgQG92ZXJyaWRlCiAgV2lkZ2V0IGJ1aWxkKGZpbmFsIEJ1aWxkQ29udGV4dCBjb250ZXh0KSA9PiBDYWNoZWROZXR3b3JrSW1hZ2UoCiAgICBpbWFnZVVybDogdXJsLAogICAgcGxhY2Vob2xkZXI6IChmaW5hbCBjb250ZXh0LCBmaW5hbCBfXykgewogICAgICBmaW5hbCB0aGVtZSA9IFRoZW1lLm9mKGNvbnRleHQpOwoKICAgICAgcmV0dXJuIENvbnRhaW5lcigKICAgICAgICBjb2xvcjogdGhlbWUuY29sb3JTY2hlbWUuc3VyZmFjZSwKICAgICAgICB3aWR0aDogd2lkdGgsCiAgICAgICAgaGVpZ2h0OiBoZWlnaHQsCiAgICAgICAgY2hpbGQ6IEljb24oCiAgICAgICAgICBJY29ucy5pbWFnZSwKICAgICAgICAgIHNpemU6IG1pbih3aWR0aCA/PyAzMiwgaGVpZ2h0ID8/IDMyKSwKICAgICAgICAgIGNvbG9yOiB0aGVtZS5jYXJkQ29sb3IsCiAgICAgICAgKSwKICAgICAgKTsKICAgIH0sCiAgICB3aWR0aDogd2lkdGgsCiAgICBoZWlnaHQ6IGhlaWdodCwKICAgIGZpdDogYm94Rml0LAogICAgZmFkZU91dER1cmF0aW9uOiBjb25zdCBEdXJhdGlvbigpLAogICAgZmFkZUluRHVyYXRpb246IGNvbnN0IER1cmF0aW9uKCksCiAgICBlcnJvcldpZGdldDogKGZpbmFsIGNvbnRleHQsIGZpbmFsIHVybCwgZmluYWwgZXJyb3IpID0+CiAgICAgICAgSWNvbihJY29ucy5pbWFnZSwgc2l6ZTogMzIpLAogICk7Cn0K", + "aW1wb3J0ICdkYXJ0Om1hdGgnOwoKaW1wb3J0ICdwYWNrYWdlOmNhY2hlZF9uZXR3b3JrX2ltYWdlL2NhY2hlZF9uZXR3b3JrX2ltYWdlLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Zmx1dHRlci9tYXRlcmlhbC5kYXJ0JzsKCmNsYXNzIE5ldHdvcmtVcmxJbWFnZSBleHRlbmRzIFN0YXRlbGVzc1dpZGdldCB7CiAgZmluYWwgU3RyaW5nIHVybDsKICBmaW5hbCBkb3VibGU/IHdpZHRoOwogIGZpbmFsIGRvdWJsZT8gaGVpZ2h0OwogIGZpbmFsIEJveEZpdD8gYm94Rml0OwoKICBjb25zdCBOZXR3b3JrVXJsSW1hZ2UoewogICAgc3VwZXIua2V5LAogICAgcmVxdWlyZWQgdGhpcy51cmwsCiAgICB0aGlzLndpZHRoLAogICAgdGhpcy5oZWlnaHQsCiAgICB0aGlzLmJveEZpdCA9IEJveEZpdC5jb3ZlciwKICB9KTsKCiAgQG92ZXJyaWRlCiAgV2lkZ2V0IGJ1aWxkKGZpbmFsIEJ1aWxkQ29udGV4dCBjb250ZXh0KSA9PiBDYWNoZWROZXR3b3JrSW1hZ2UoCiAgICBpbWFnZVVybDogdXJsLAogICAgcGxhY2Vob2xkZXI6IChmaW5hbCBjb250ZXh0LCBmaW5hbCBfKSB7CiAgICAgIGZpbmFsIHRoZW1lID0gVGhlbWUub2YoY29udGV4dCk7CgogICAgICByZXR1cm4gQ29udGFpbmVyKAogICAgICAgIGNvbG9yOiB0aGVtZS5jb2xvclNjaGVtZS5zdXJmYWNlLAogICAgICAgIHdpZHRoOiB3aWR0aCwKICAgICAgICBoZWlnaHQ6IGhlaWdodCwKICAgICAgICBjaGlsZDogSWNvbigKICAgICAgICAgIEljb25zLmltYWdlLAogICAgICAgICAgc2l6ZTogbWluKHdpZHRoID8/IDMyLCBoZWlnaHQgPz8gMzIpLAogICAgICAgICAgY29sb3I6IHRoZW1lLmNhcmRDb2xvciwKICAgICAgICApLAogICAgICApOwogICAgfSwKICAgIHdpZHRoOiB3aWR0aCwKICAgIGhlaWdodDogaGVpZ2h0LAogICAgZml0OiBib3hGaXQsCiAgICBmYWRlT3V0RHVyYXRpb246IGNvbnN0IER1cmF0aW9uKCksCiAgICBmYWRlSW5EdXJhdGlvbjogY29uc3QgRHVyYXRpb24oKSwKICAgIGVycm9yV2lkZ2V0OiAoZmluYWwgY29udGV4dCwgZmluYWwgdXJsLCBmaW5hbCBlcnJvcikgPT4KICAgICAgICBJY29uKEljb25zLmltYWdlLCBzaXplOiAzMiksCiAgKTsKfQo=", "type": "text" }, { @@ -721,7 +715,7 @@ final flutterProjectWithMonoRepoBundle = MasonBundle.fromJson({ { "path": "{{ name.snakeCase() }}/packages/design_system/pubspec.yaml", "data": - "bmFtZTogZGVzaWduX3N5c3RlbQpkZXNjcmlwdGlvbjogIlRoZW1lLCBjb21wb25lbnRzIGFuZCBkaWFsb2dzIHNoYXJlZCBhY3Jvc3Mge3tuYW1lLnRpdGxlQ2FzZSgpfX0uIgp2ZXJzaW9uOiAwLjEuMApwdWJsaXNoX3RvOiAnbm9uZScKCmVudmlyb25tZW50OgogIHNkazogXjMuOC4wCiAgZmx1dHRlcjogIj49My4yOS4wIgoKZGVwZW5kZW5jaWVzOgogIGZsdXR0ZXI6CiAgICBzZGs6IGZsdXR0ZXIKICBjYWNoZWRfbmV0d29ya19pbWFnZTogXjMuNC4xCiAgZmx1dHRlcl9zdmc6IF4yLjIuMAogIGdvb2dsZV9mb250czogXjYuMi4xCiAgaWNvbnNheF9mbHV0dGVyOiBeMS4wLjEKICBsb2FkZXJfb3ZlcmxheTogXjUuMC4wCiAgdG9hc3RpZmljYXRpb246IF4zLjAuMwogIGNvcmU6CiAgICBwYXRoOiAuLi9jb3JlCiAgZGk6CiAgICBwYXRoOiAuLi9kaQogIGxvY2FsaXphdGlvbjoKICAgIHBhdGg6IC4uL2xvY2FsaXphdGlvbgp7eyNuZXR3b3JrfX0gIG5ldHdvcms6CiAgICBwYXRoOiAuLi9uZXR3b3JrCnt7L25ldHdvcmt9fQpkZXZfZGVwZW5kZW5jaWVzOgogIG1vY2t0YWlsOiBeMS4wLjQKICBmbHV0dGVyX3Rlc3Q6CiAgICBzZGs6IGZsdXR0ZXIKICBmbHV0dGVyX2xpbnRzOiBeNS4wLjAKCmZsdXR0ZXI6Cg==", + "bmFtZTogZGVzaWduX3N5c3RlbQpkZXNjcmlwdGlvbjogIlRoZW1lLCBjb21wb25lbnRzIGFuZCBkaWFsb2dzIHNoYXJlZCBhY3Jvc3Mge3tuYW1lLnRpdGxlQ2FzZSgpfX0uIgp2ZXJzaW9uOiAwLjEuMApwdWJsaXNoX3RvOiAnbm9uZScKCmVudmlyb25tZW50OgogIHNkazogXjMuOC4wCiAgZmx1dHRlcjogIj49My40Ny41IgoKZGVwZW5kZW5jaWVzOgogIGZsdXR0ZXI6CiAgICBzZGs6IGZsdXR0ZXIKICBjYWNoZWRfbmV0d29ya19pbWFnZTogXjQuMC4wCiAgZmx1dHRlcl9zdmc6IF4yLjIuMAogIGdvb2dsZV9mb250czogXjguMi4xCiAgaWNvbnNheF9mbHV0dGVyOiBeMS4wLjEKICBsb2FkZXJfb3ZlcmxheTogXjUuMC4wCiAgdG9hc3RpZmljYXRpb246IF4zLjAuMwogIGNvcmU6CiAgICBwYXRoOiAuLi9jb3JlCiAgZGk6CiAgICBwYXRoOiAuLi9kaQogIGxvY2FsaXphdGlvbjoKICAgIHBhdGg6IC4uL2xvY2FsaXphdGlvbgp7eyNuZXR3b3JrfX0gIG5ldHdvcms6CiAgICBwYXRoOiAuLi9uZXR3b3JrCnt7L25ldHdvcmt9fQpkZXZfZGVwZW5kZW5jaWVzOgogIG1vY2t0YWlsOiBeMS4wLjQKICBmbHV0dGVyX3Rlc3Q6CiAgICBzZGs6IGZsdXR0ZXIKICBmbHV0dGVyX2xpbnRzOiBeNi4wLjAKCmZsdXR0ZXI6CgpyZXNvbHV0aW9uOiB3b3Jrc3BhY2UK", "type": "text" }, { @@ -774,7 +768,7 @@ final flutterProjectWithMonoRepoBundle = MasonBundle.fromJson({ { "path": "{{ name.snakeCase() }}/packages/di/pubspec.yaml", "data": - "bmFtZTogZGkKZGVzY3JpcHRpb246ICJTZXJ2aWNlIGxvY2F0b3IgYWJzdHJhY3Rpb24gZm9yIHt7bmFtZS50aXRsZUNhc2UoKX19LCBiYWNrZWQgYnkgR2V0SXQuIgp2ZXJzaW9uOiAwLjEuMApwdWJsaXNoX3RvOiAnbm9uZScKCmVudmlyb25tZW50OgogIHNkazogXjMuOC4wCiAgZmx1dHRlcjogIj49My4yOS4wIgoKZGVwZW5kZW5jaWVzOgogIGZsdXR0ZXI6CiAgICBzZGs6IGZsdXR0ZXIKICBnZXRfaXQ6IF44LjAuMwoKZGV2X2RlcGVuZGVuY2llczoKICBmbHV0dGVyX3Rlc3Q6CiAgICBzZGs6IGZsdXR0ZXIKICBmbHV0dGVyX2xpbnRzOiBeNS4wLjAKCmZsdXR0ZXI6Cg==", + "bmFtZTogZGkKZGVzY3JpcHRpb246ICJTZXJ2aWNlIGxvY2F0b3IgYWJzdHJhY3Rpb24gZm9yIHt7bmFtZS50aXRsZUNhc2UoKX19LCBiYWNrZWQgYnkgR2V0SXQuIgp2ZXJzaW9uOiAwLjEuMApwdWJsaXNoX3RvOiAnbm9uZScKCmVudmlyb25tZW50OgogIHNkazogXjMuOC4wCiAgZmx1dHRlcjogIj49My40Ny41IgoKZGVwZW5kZW5jaWVzOgogIGZsdXR0ZXI6CiAgICBzZGs6IGZsdXR0ZXIKICBnZXRfaXQ6IF45LjMuMAoKZGV2X2RlcGVuZGVuY2llczoKICBmbHV0dGVyX3Rlc3Q6CiAgICBzZGs6IGZsdXR0ZXIKICBmbHV0dGVyX2xpbnRzOiBeNi4wLjAKCmZsdXR0ZXI6CgpyZXNvbHV0aW9uOiB3b3Jrc3BhY2UK", "type": "text" }, { @@ -839,7 +833,7 @@ final flutterProjectWithMonoRepoBundle = MasonBundle.fromJson({ { "path": "{{ name.snakeCase() }}/packages/localization/pubspec.yaml", "data": - "bmFtZTogbG9jYWxpemF0aW9uCmRlc2NyaXB0aW9uOiAiVHlwZS1zYWZlIHN0cmluZ3MgZm9yIHt7bmFtZS50aXRsZUNhc2UoKX19LCBnZW5lcmF0ZWQgZnJvbSBZQU1MIHdpdGggaTY5bi4iCnZlcnNpb246IDAuMS4wCnB1Ymxpc2hfdG86ICdub25lJwoKZW52aXJvbm1lbnQ6CiAgc2RrOiBeMy44LjAKICBmbHV0dGVyOiAiPj0zLjI5LjAiCgpkZXBlbmRlbmNpZXM6CiAgZmx1dHRlcjoKICAgIHNkazogZmx1dHRlcgogIGk2OW46IF4zLjEuMAoKZGV2X2RlcGVuZGVuY2llczoKICBmbHV0dGVyX3Rlc3Q6CiAgICBzZGs6IGZsdXR0ZXIKICBmbHV0dGVyX2xpbnRzOiBeNS4wLjAKICBidWlsZF9ydW5uZXI6IF4yLjQuMTUKCmZsdXR0ZXI6Cg==", + "bmFtZTogbG9jYWxpemF0aW9uCmRlc2NyaXB0aW9uOiAiVHlwZS1zYWZlIHN0cmluZ3MgZm9yIHt7bmFtZS50aXRsZUNhc2UoKX19LCBnZW5lcmF0ZWQgZnJvbSBZQU1MIHdpdGggaTY5bi4iCnZlcnNpb246IDAuMS4wCnB1Ymxpc2hfdG86ICdub25lJwoKZW52aXJvbm1lbnQ6CiAgc2RrOiBeMy44LjAKICBmbHV0dGVyOiAiPj0zLjQ3LjUiCgpkZXBlbmRlbmNpZXM6CiAgZmx1dHRlcjoKICAgIHNkazogZmx1dHRlcgogIGk2OW46IF4zLjEuMAoKZGV2X2RlcGVuZGVuY2llczoKICBmbHV0dGVyX3Rlc3Q6CiAgICBzZGs6IGZsdXR0ZXIKICBmbHV0dGVyX2xpbnRzOiBeNi4wLjAKICBidWlsZF9ydW5uZXI6IF4yLjQuMTUKCmZsdXR0ZXI6CgpyZXNvbHV0aW9uOiB3b3Jrc3BhY2UK", "type": "text" }, { @@ -877,7 +871,7 @@ final flutterProjectWithMonoRepoBundle = MasonBundle.fromJson({ { "path": "{{ name.snakeCase() }}/packages/utils/pubspec.yaml", "data": - "bmFtZTogdXRpbHMKZGVzY3JpcHRpb246ICJTaGFyZWQsIGRlcGVuZGVuY3ktbGlnaHQgaGVscGVycyBmb3Ige3tuYW1lLnRpdGxlQ2FzZSgpfX0uIgp2ZXJzaW9uOiAwLjEuMApwdWJsaXNoX3RvOiAnbm9uZScKCmVudmlyb25tZW50OgogIHNkazogXjMuOC4wCiAgZmx1dHRlcjogIj49My4yOS4wIgoKZGVwZW5kZW5jaWVzOgogIGZsdXR0ZXI6CiAgICBzZGs6IGZsdXR0ZXIKICBpbnRsOiBeMC4yMC4yCgpkZXZfZGVwZW5kZW5jaWVzOgogIGZsdXR0ZXJfdGVzdDoKICAgIHNkazogZmx1dHRlcgogIGZsdXR0ZXJfbGludHM6IF41LjAuMAoKZmx1dHRlcjoK", + "bmFtZTogdXRpbHMKZGVzY3JpcHRpb246ICJTaGFyZWQsIGRlcGVuZGVuY3ktbGlnaHQgaGVscGVycyBmb3Ige3tuYW1lLnRpdGxlQ2FzZSgpfX0uIgp2ZXJzaW9uOiAwLjEuMApwdWJsaXNoX3RvOiAnbm9uZScKCmVudmlyb25tZW50OgogIHNkazogXjMuOC4wCiAgZmx1dHRlcjogIj49My40Ny41IgoKZGVwZW5kZW5jaWVzOgogIGZsdXR0ZXI6CiAgICBzZGs6IGZsdXR0ZXIKICBpbnRsOiBeMC4yMC4yCgpkZXZfZGVwZW5kZW5jaWVzOgogIGZsdXR0ZXJfdGVzdDoKICAgIHNkazogZmx1dHRlcgogIGZsdXR0ZXJfbGludHM6IF42LjAuMAoKZmx1dHRlcjoKCnJlc29sdXRpb246IHdvcmtzcGFjZQo=", "type": "text" }, { @@ -905,7 +899,7 @@ final flutterProjectWithMonoRepoBundle = MasonBundle.fromJson({ "path": "{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/client/firebase_analytics_client.dart", "data": - "aW1wb3J0ICdwYWNrYWdlOmFuYWx5dGljcy9zcmMvY2xpZW50L2FuYWx5dGljc19jbGllbnQuZGFydCc7CmltcG9ydCAncGFja2FnZTphbmFseXRpY3Mvc3JjL2NvbmZpZy9hbmFseXRpY3NfY29uZmlnLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6YW5hbHl0aWNzL3NyYy9tb2RlbHMvYW5hbHl0aWNzX2V2ZW50LmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Y29yZS9jb3JlLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6ZmlyZWJhc2VfYW5hbHl0aWNzL2ZpcmViYXNlX2FuYWx5dGljcy5kYXJ0JzsKCmNsYXNzIEZpcmViYXNlQW5hbHl0aWNzQ2xpZW50IGltcGxlbWVudHMgQW5hbHl0aWNzQ2xpZW50IHsKICBGaXJlYmFzZUFuYWx5dGljc0NsaWVudCgKICAgIHRoaXMuX2NvbmZpZywgewogICAgcmVxdWlyZWQgRmlyZWJhc2VBbmFseXRpY3MgYW5hbHl0aWNzLAogICAgcmVxdWlyZWQgTG9nZ2VyIGxvZ2dlciwKICB9KSA6IF9hbmFseXRpY3MgPSBhbmFseXRpY3MsCiAgICAgICBfbG9nZ2VyID0gbG9nZ2VyLAogICAgICAgX2VuYWJsZWQgPSBfY29uZmlnLmVuYWJsZUFuYWx5dGljczsKCiAgZmluYWwgQW5hbHl0aWNzQ29uZmlnIF9jb25maWc7CiAgZmluYWwgRmlyZWJhc2VBbmFseXRpY3MgX2FuYWx5dGljczsKICBmaW5hbCBMb2dnZXIgX2xvZ2dlcjsKICBib29sIF9lbmFibGVkOwoKICBGdXR1cmU8dm9pZD4gaW5pdGlhbGl6ZSgpIGFzeW5jIHsKICAgIGF3YWl0IF9hbmFseXRpY3Muc2V0QW5hbHl0aWNzQ29sbGVjdGlvbkVuYWJsZWQoX2VuYWJsZWQpOwoKICAgIGlmIChfY29uZmlnLnVzZXJJZCAhPSBudWxsKSB7CiAgICAgIGF3YWl0IF9hbmFseXRpY3Muc2V0VXNlcklkKGlkOiBfY29uZmlnLnVzZXJJZCk7CiAgICAgIF9sb2dnZXIuZCgn8J+UjSBBbmFseXRpY3MgdXNlciBJRCBzZXQ6ICR7X2NvbmZpZy51c2VySWR9Jyk7CiAgICB9CgogICAgaWYgKF9jb25maWcuZGVmYXVsdFVzZXJQcm9wZXJ0aWVzICE9IG51bGwpIHsKICAgICAgZm9yIChmaW5hbCBlbnRyeSBpbiBfY29uZmlnLmRlZmF1bHRVc2VyUHJvcGVydGllcyEuZW50cmllcykgewogICAgICAgIGF3YWl0IF9hbmFseXRpY3Muc2V0VXNlclByb3BlcnR5KG5hbWU6IGVudHJ5LmtleSwgdmFsdWU6IGVudHJ5LnZhbHVlKTsKICAgICAgfQogICAgICBfbG9nZ2VyLmQoJ/CflI0gQW5hbHl0aWNzIGRlZmF1bHQgdXNlciBwcm9wZXJ0aWVzIHNldCcpOwogICAgfQoKICAgIGlmIChfY29uZmlnLmVuYWJsZURlYnVnTG9nZ2luZykgewogICAgICBfbG9nZ2VyLmQoJ/CflI0gRmlyZWJhc2UgQW5hbHl0aWNzIGluaXRpYWxpemVkIHdpdGggZGVidWcgbG9nZ2luZyBlbmFibGVkJyk7CiAgICB9IGVsc2UgewogICAgICBfbG9nZ2VyLmkoJ/CflI0gRmlyZWJhc2UgQW5hbHl0aWNzIGluaXRpYWxpemVkJyk7CiAgICB9CiAgfQoKICBAb3ZlcnJpZGUKICBGdXR1cmU8dm9pZD4gbG9nRXZlbnQoewogICAgcmVxdWlyZWQgU3RyaW5nIG5hbWUsCiAgICBNYXA8U3RyaW5nLCBkeW5hbWljPj8gcGFyYW1ldGVycywKICB9KSBhc3luYyB7CiAgICB0cnkgewogICAgICBpZiAoIV9lbmFibGVkKSB7CiAgICAgICAgaWYgKF9jb25maWcuZW5hYmxlRGVidWdMb2dnaW5nKSB7CiAgICAgICAgICBfbG9nZ2VyLmQoJ/CflI0gQW5hbHl0aWNzIGRpc2FibGVkLCBza2lwcGluZyBldmVudDogJG5hbWUnKTsKICAgICAgICB9CiAgICAgICAgcmV0dXJuOwogICAgICB9CgogICAgICBhd2FpdCBfYW5hbHl0aWNzLmxvZ0V2ZW50KAogICAgICAgIG5hbWU6IG5hbWUsCiAgICAgICAgcGFyYW1ldGVyczogcGFyYW1ldGVycyA9PSBudWxsCiAgICAgICAgICAgID8gbnVsbAogICAgICAgICAgICA6IHsKICAgICAgICAgICAgICAgIGZvciAoZmluYWwgZW50cnkgaW4gcGFyYW1ldGVycy5lbnRyaWVzKQogICAgICAgICAgICAgICAgICBpZiAoZW50cnkudmFsdWUgIT0gbnVsbCkgZW50cnkua2V5OiBlbnRyeS52YWx1ZSBhcyBPYmplY3QsCiAgICAgICAgICAgICAgfSwKICAgICAgKTsKCiAgICAgIGlmIChfY29uZmlnLmVuYWJsZURlYnVnTG9nZ2luZykgewogICAgICAgIF9sb2dnZXIuZCgKICAgICAgICAgICfwn5SNIEFuYWx5dGljcyBldmVudCBsb2dnZWQ6ICRuYW1lIHdpdGggcGFyYW1ldGVyczogJHBhcmFtZXRlcnMnLAogICAgICAgICk7CiAgICAgIH0KICAgIH0gY2F0Y2ggKGUsIHMpIHsKICAgICAgX2xvZ2dlci5lKCdGYWlsZWQgdG8gbG9nIGFuYWx5dGljcyBldmVudDogJG5hbWUnLCBlLCBzKTsKICAgIH0KICB9CgogIEBvdmVycmlkZQogIEZ1dHVyZTx2b2lkPiBsb2dDdXN0b21FdmVudChBbmFseXRpY3NFdmVudCBldmVudCkgYXN5bmMgewogICAgYXdhaXQgbG9nRXZlbnQobmFtZTogZXZlbnQubmFtZSwgcGFyYW1ldGVyczogZXZlbnQucGFyYW1ldGVycyk7CiAgfQoKICBAb3ZlcnJpZGUKICBGdXR1cmU8dm9pZD4gc2V0VXNlcklkKFN0cmluZz8gdXNlcklkKSBhc3luYyB7CiAgICB0cnkgewogICAgICBhd2FpdCBfYW5hbHl0aWNzLnNldFVzZXJJZChpZDogdXNlcklkKTsKCiAgICAgIGlmIChfY29uZmlnLmVuYWJsZURlYnVnTG9nZ2luZykgewogICAgICAgIF9sb2dnZXIuZCgn8J+UjSBBbmFseXRpY3MgdXNlciBJRCBzZXQ6ICR1c2VySWQnKTsKICAgICAgfQogICAgfSBjYXRjaCAoZSwgcykgewogICAgICBfbG9nZ2VyLmUoJ0ZhaWxlZCB0byBzZXQgYW5hbHl0aWNzIHVzZXIgSUQ6ICR1c2VySWQnLCBlLCBzKTsKICAgIH0KICB9CgogIEBvdmVycmlkZQogIEZ1dHVyZTx2b2lkPiBzZXRVc2VyUHJvcGVydHkoewogICAgcmVxdWlyZWQgU3RyaW5nIG5hbWUsCiAgICByZXF1aXJlZCBTdHJpbmc/IHZhbHVlLAogIH0pIGFzeW5jIHsKICAgIHRyeSB7CiAgICAgIGF3YWl0IF9hbmFseXRpY3Muc2V0VXNlclByb3BlcnR5KG5hbWU6IG5hbWUsIHZhbHVlOiB2YWx1ZSk7CgogICAgICBpZiAoX2NvbmZpZy5lbmFibGVEZWJ1Z0xvZ2dpbmcpIHsKICAgICAgICBfbG9nZ2VyLmQoJ/CflI0gQW5hbHl0aWNzIHVzZXIgcHJvcGVydHkgc2V0OiAkbmFtZSA9ICR2YWx1ZScpOwogICAgICB9CiAgICB9IGNhdGNoIChlLCBzKSB7CiAgICAgIF9sb2dnZXIuZSgnRmFpbGVkIHRvIHNldCBhbmFseXRpY3MgdXNlciBwcm9wZXJ0eTogJG5hbWUnLCBlLCBzKTsKICAgIH0KICB9CgogIEBvdmVycmlkZQogIEZ1dHVyZTx2b2lkPiByZXNldEFuYWx5dGljc0RhdGEoKSBhc3luYyB7CiAgICB0cnkgewogICAgICBhd2FpdCBfYW5hbHl0aWNzLnJlc2V0QW5hbHl0aWNzRGF0YSgpOwoKICAgICAgaWYgKF9jb25maWcuZW5hYmxlRGVidWdMb2dnaW5nKSB7CiAgICAgICAgX2xvZ2dlci5kKCfwn5SNIEFuYWx5dGljcyBkYXRhIHJlc2V0Jyk7CiAgICAgIH0KICAgIH0gY2F0Y2ggKGUsIHMpIHsKICAgICAgX2xvZ2dlci5lKCdGYWlsZWQgdG8gcmVzZXQgYW5hbHl0aWNzIGRhdGEnLCBlLCBzKTsKICAgIH0KICB9CgogIEBvdmVycmlkZQogIEZ1dHVyZTx2b2lkPiBzZXRBbmFseXRpY3NDb2xsZWN0aW9uRW5hYmxlZChib29sIGVuYWJsZWQpIGFzeW5jIHsKICAgIHRyeSB7CiAgICAgIGF3YWl0IF9hbmFseXRpY3Muc2V0QW5hbHl0aWNzQ29sbGVjdGlvbkVuYWJsZWQoZW5hYmxlZCk7CiAgICAgIF9lbmFibGVkID0gZW5hYmxlZDsKCiAgICAgIGlmIChfY29uZmlnLmVuYWJsZURlYnVnTG9nZ2luZykgewogICAgICAgIF9sb2dnZXIuZCgKICAgICAgICAgICfwn5SNIEFuYWx5dGljcyBjb2xsZWN0aW9uICR7ZW5hYmxlZCA/ICdlbmFibGVkJyA6ICdkaXNhYmxlZCd9JywKICAgICAgICApOwogICAgICB9CiAgICB9IGNhdGNoIChlLCBzKSB7CiAgICAgIF9sb2dnZXIuZSgnRmFpbGVkIHRvIHNldCBhbmFseXRpY3MgY29sbGVjdGlvbiBlbmFibGVkOiAkZW5hYmxlZCcsIGUsIHMpOwogICAgfQogIH0KCiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IGxvZ1NjcmVlblZpZXcoewogICAgcmVxdWlyZWQgU3RyaW5nIHNjcmVlbk5hbWUsCiAgICBTdHJpbmc/IHNjcmVlbkNsYXNzLAogICAgTWFwPFN0cmluZywgZHluYW1pYz4/IHBhcmFtZXRlcnMsCiAgfSkgYXN5bmMgewogICAgZmluYWwgZXZlbnRQYXJhbWV0ZXJzID0gPFN0cmluZywgZHluYW1pYz57CiAgICAgIFByZWRlZmluZWRQYXJhbWV0ZXJzLnNjcmVlbk5hbWU6IHNjcmVlbk5hbWUsCiAgICAgIGlmIChzY3JlZW5DbGFzcyAhPSBudWxsKSBQcmVkZWZpbmVkUGFyYW1ldGVycy5zY3JlZW5DbGFzczogc2NyZWVuQ2xhc3MsCiAgICAgIC4uLj9wYXJhbWV0ZXJzLAogICAgfTsKCiAgICBhd2FpdCBsb2dFdmVudCgKICAgICAgbmFtZTogUHJlZGVmaW5lZEV2ZW50cy5zY3JlZW5WaWV3LAogICAgICBwYXJhbWV0ZXJzOiBldmVudFBhcmFtZXRlcnMsCiAgICApOwogIH0KCiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IGxvZ0FwcE9wZW4oe01hcDxTdHJpbmcsIGR5bmFtaWM+PyBwYXJhbWV0ZXJzfSkgYXN5bmMgewogICAgYXdhaXQgbG9nRXZlbnQobmFtZTogUHJlZGVmaW5lZEV2ZW50cy5hcHBPcGVuLCBwYXJhbWV0ZXJzOiBwYXJhbWV0ZXJzKTsKICB9CgogIEBvdmVycmlkZQogIEZ1dHVyZTx2b2lkPiBsb2dMb2dpbih7CiAgICBTdHJpbmc/IGxvZ2luTWV0aG9kLAogICAgTWFwPFN0cmluZywgZHluYW1pYz4/IHBhcmFtZXRlcnMsCiAgfSkgYXN5bmMgewogICAgZmluYWwgZXZlbnRQYXJhbWV0ZXJzID0gPFN0cmluZywgZHluYW1pYz57CiAgICAgIGlmIChsb2dpbk1ldGhvZCAhPSBudWxsKSBQcmVkZWZpbmVkUGFyYW1ldGVycy5tZXRob2Q6IGxvZ2luTWV0aG9kLAogICAgICAuLi4/cGFyYW1ldGVycywKICAgIH07CgogICAgYXdhaXQgbG9nRXZlbnQobmFtZTogUHJlZGVmaW5lZEV2ZW50cy5sb2dpbiwgcGFyYW1ldGVyczogZXZlbnRQYXJhbWV0ZXJzKTsKICB9CgogIEBvdmVycmlkZQogIEZ1dHVyZTx2b2lkPiBsb2dTaWduVXAoewogICAgU3RyaW5nPyBzaWduVXBNZXRob2QsCiAgICBNYXA8U3RyaW5nLCBkeW5hbWljPj8gcGFyYW1ldGVycywKICB9KSBhc3luYyB7CiAgICBmaW5hbCBldmVudFBhcmFtZXRlcnMgPSA8U3RyaW5nLCBkeW5hbWljPnsKICAgICAgaWYgKHNpZ25VcE1ldGhvZCAhPSBudWxsKSBQcmVkZWZpbmVkUGFyYW1ldGVycy5tZXRob2Q6IHNpZ25VcE1ldGhvZCwKICAgICAgLi4uP3BhcmFtZXRlcnMsCiAgICB9OwoKICAgIGF3YWl0IGxvZ0V2ZW50KG5hbWU6IFByZWRlZmluZWRFdmVudHMuc2lnblVwLCBwYXJhbWV0ZXJzOiBldmVudFBhcmFtZXRlcnMpOwogIH0KCiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IGxvZ1B1cmNoYXNlKHsKICAgIHJlcXVpcmVkIFN0cmluZyBjdXJyZW5jeSwKICAgIHJlcXVpcmVkIGRvdWJsZSB2YWx1ZSwKICAgIE1hcDxTdHJpbmcsIGR5bmFtaWM+PyBwYXJhbWV0ZXJzLAogIH0pIGFzeW5jIHsKICAgIGZpbmFsIGV2ZW50UGFyYW1ldGVycyA9IDxTdHJpbmcsIGR5bmFtaWM+ewogICAgICBQcmVkZWZpbmVkUGFyYW1ldGVycy5jdXJyZW5jeTogY3VycmVuY3ksCiAgICAgIFByZWRlZmluZWRQYXJhbWV0ZXJzLnZhbHVlOiB2YWx1ZSwKICAgICAgLi4uP3BhcmFtZXRlcnMsCiAgICB9OwoKICAgIGF3YWl0IGxvZ0V2ZW50KAogICAgICBuYW1lOiBQcmVkZWZpbmVkRXZlbnRzLnB1cmNoYXNlLAogICAgICBwYXJhbWV0ZXJzOiBldmVudFBhcmFtZXRlcnMsCiAgICApOwogIH0KCiAgQG92ZXJyaWRlCiAgdm9pZCBkaXNwb3NlKCkgewogICAgLy8gRmlyZWJhc2UgQW5hbHl0aWNzIGRvZXNuJ3QgbmVlZCBleHBsaWNpdCBkaXNwb3NhbAogICAgaWYgKF9jb25maWcuZW5hYmxlRGVidWdMb2dnaW5nKSB7CiAgICAgIF9sb2dnZXIuZCgn8J+UjSBGaXJlYmFzZSBBbmFseXRpY3MgY2xpZW50IGRpc3Bvc2VkJyk7CiAgICB9CiAgfQp9Cg==", + "aW1wb3J0ICdwYWNrYWdlOmFuYWx5dGljcy9zcmMvY2xpZW50L2FuYWx5dGljc19jbGllbnQuZGFydCc7CmltcG9ydCAncGFja2FnZTphbmFseXRpY3Mvc3JjL2NvbmZpZy9hbmFseXRpY3NfY29uZmlnLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6YW5hbHl0aWNzL3NyYy9tb2RlbHMvYW5hbHl0aWNzX2V2ZW50LmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Y29yZS9jb3JlLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6ZmlyZWJhc2VfYW5hbHl0aWNzL2ZpcmViYXNlX2FuYWx5dGljcy5kYXJ0JzsKCmNsYXNzIEZpcmViYXNlQW5hbHl0aWNzQ2xpZW50IGltcGxlbWVudHMgQW5hbHl0aWNzQ2xpZW50IHsKICBGaXJlYmFzZUFuYWx5dGljc0NsaWVudCgKICAgIHRoaXMuX2NvbmZpZywgewogICAgcmVxdWlyZWQgRmlyZWJhc2VBbmFseXRpY3MgYW5hbHl0aWNzLAogICAgcmVxdWlyZWQgTG9nZ2VyIGxvZ2dlciwKICB9KSA6IF9hbmFseXRpY3MgPSBhbmFseXRpY3MsCiAgICAgICBfbG9nZ2VyID0gbG9nZ2VyLAogICAgICAgX2VuYWJsZWQgPSBfY29uZmlnLmVuYWJsZUFuYWx5dGljczsKCiAgZmluYWwgQW5hbHl0aWNzQ29uZmlnIF9jb25maWc7CiAgZmluYWwgRmlyZWJhc2VBbmFseXRpY3MgX2FuYWx5dGljczsKICBmaW5hbCBMb2dnZXIgX2xvZ2dlcjsKICBib29sIF9lbmFibGVkOwoKICBGdXR1cmU8dm9pZD4gaW5pdGlhbGl6ZSgpIGFzeW5jIHsKICAgIGF3YWl0IF9hbmFseXRpY3Muc2V0QW5hbHl0aWNzQ29sbGVjdGlvbkVuYWJsZWQoX2VuYWJsZWQpOwoKICAgIGlmIChfY29uZmlnLnVzZXJJZCAhPSBudWxsKSB7CiAgICAgIGF3YWl0IF9hbmFseXRpY3Muc2V0VXNlcklkKGlkOiBfY29uZmlnLnVzZXJJZCk7CiAgICAgIF9sb2dnZXIuZCgn8J+UjSBBbmFseXRpY3MgdXNlciBJRCBzZXQ6ICR7X2NvbmZpZy51c2VySWR9Jyk7CiAgICB9CgogICAgaWYgKF9jb25maWcuZGVmYXVsdFVzZXJQcm9wZXJ0aWVzICE9IG51bGwpIHsKICAgICAgZm9yIChmaW5hbCBlbnRyeSBpbiBfY29uZmlnLmRlZmF1bHRVc2VyUHJvcGVydGllcyEuZW50cmllcykgewogICAgICAgIGF3YWl0IF9hbmFseXRpY3Muc2V0VXNlclByb3BlcnR5KG5hbWU6IGVudHJ5LmtleSwgdmFsdWU6IGVudHJ5LnZhbHVlKTsKICAgICAgfQogICAgICBfbG9nZ2VyLmQoJ/CflI0gQW5hbHl0aWNzIGRlZmF1bHQgdXNlciBwcm9wZXJ0aWVzIHNldCcpOwogICAgfQoKICAgIGlmIChfY29uZmlnLmVuYWJsZURlYnVnTG9nZ2luZykgewogICAgICBfbG9nZ2VyLmQoJ/CflI0gRmlyZWJhc2UgQW5hbHl0aWNzIGluaXRpYWxpemVkIHdpdGggZGVidWcgbG9nZ2luZyBlbmFibGVkJyk7CiAgICB9IGVsc2UgewogICAgICBfbG9nZ2VyLmkoJ/CflI0gRmlyZWJhc2UgQW5hbHl0aWNzIGluaXRpYWxpemVkJyk7CiAgICB9CiAgfQoKICBAb3ZlcnJpZGUKICBGdXR1cmU8dm9pZD4gbG9nRXZlbnQoewogICAgcmVxdWlyZWQgU3RyaW5nIG5hbWUsCiAgICBNYXA8U3RyaW5nLCBkeW5hbWljPj8gcGFyYW1ldGVycywKICB9KSBhc3luYyB7CiAgICB0cnkgewogICAgICBpZiAoIV9lbmFibGVkKSB7CiAgICAgICAgaWYgKF9jb25maWcuZW5hYmxlRGVidWdMb2dnaW5nKSB7CiAgICAgICAgICBfbG9nZ2VyLmQoJ/CflI0gQW5hbHl0aWNzIGRpc2FibGVkLCBza2lwcGluZyBldmVudDogJG5hbWUnKTsKICAgICAgICB9CiAgICAgICAgcmV0dXJuOwogICAgICB9CgogICAgICBhd2FpdCBfYW5hbHl0aWNzLmxvZ0V2ZW50KAogICAgICAgIG5hbWU6IG5hbWUsCiAgICAgICAgcGFyYW1ldGVyczogcGFyYW1ldGVycyA9PSBudWxsCiAgICAgICAgICAgID8gbnVsbAogICAgICAgICAgICA6IHsKICAgICAgICAgICAgICAgIGZvciAoZmluYWwgZW50cnkgaW4gcGFyYW1ldGVycy5lbnRyaWVzKQogICAgICAgICAgICAgICAgICBpZiAoZW50cnkudmFsdWUgIT0gbnVsbCkgZW50cnkua2V5OiBlbnRyeS52YWx1ZSBhcyBPYmplY3QsCiAgICAgICAgICAgICAgfSwKICAgICAgKTsKCiAgICAgIGlmIChfY29uZmlnLmVuYWJsZURlYnVnTG9nZ2luZykgewogICAgICAgIF9sb2dnZXIuZCgKICAgICAgICAgICfwn5SNIEFuYWx5dGljcyBldmVudCBsb2dnZWQ6ICRuYW1lIHdpdGggcGFyYW1ldGVyczogJHBhcmFtZXRlcnMnLAogICAgICAgICk7CiAgICAgIH0KICAgIH0gY2F0Y2ggKGUsIHMpIHsKICAgICAgX2xvZ2dlci5lKCdGYWlsZWQgdG8gbG9nIGFuYWx5dGljcyBldmVudDogJG5hbWUnLCBlLCBzKTsKICAgIH0KICB9CgogIEBvdmVycmlkZQogIEZ1dHVyZTx2b2lkPiBsb2dDdXN0b21FdmVudChBbmFseXRpY3NFdmVudCBldmVudCkgYXN5bmMgewogICAgYXdhaXQgbG9nRXZlbnQobmFtZTogZXZlbnQubmFtZSwgcGFyYW1ldGVyczogZXZlbnQucGFyYW1ldGVycyk7CiAgfQoKICBAb3ZlcnJpZGUKICBGdXR1cmU8dm9pZD4gc2V0VXNlcklkKFN0cmluZz8gdXNlcklkKSBhc3luYyB7CiAgICB0cnkgewogICAgICBhd2FpdCBfYW5hbHl0aWNzLnNldFVzZXJJZChpZDogdXNlcklkKTsKCiAgICAgIGlmIChfY29uZmlnLmVuYWJsZURlYnVnTG9nZ2luZykgewogICAgICAgIF9sb2dnZXIuZCgn8J+UjSBBbmFseXRpY3MgdXNlciBJRCBzZXQ6ICR1c2VySWQnKTsKICAgICAgfQogICAgfSBjYXRjaCAoZSwgcykgewogICAgICBfbG9nZ2VyLmUoJ0ZhaWxlZCB0byBzZXQgYW5hbHl0aWNzIHVzZXIgSUQ6ICR1c2VySWQnLCBlLCBzKTsKICAgIH0KICB9CgogIEBvdmVycmlkZQogIEZ1dHVyZTx2b2lkPiBzZXRVc2VyUHJvcGVydHkoewogICAgcmVxdWlyZWQgU3RyaW5nIG5hbWUsCiAgICByZXF1aXJlZCBTdHJpbmc/IHZhbHVlLAogIH0pIGFzeW5jIHsKICAgIHRyeSB7CiAgICAgIGF3YWl0IF9hbmFseXRpY3Muc2V0VXNlclByb3BlcnR5KG5hbWU6IG5hbWUsIHZhbHVlOiB2YWx1ZSk7CgogICAgICBpZiAoX2NvbmZpZy5lbmFibGVEZWJ1Z0xvZ2dpbmcpIHsKICAgICAgICBfbG9nZ2VyLmQoJ/CflI0gQW5hbHl0aWNzIHVzZXIgcHJvcGVydHkgc2V0OiAkbmFtZSA9ICR2YWx1ZScpOwogICAgICB9CiAgICB9IGNhdGNoIChlLCBzKSB7CiAgICAgIF9sb2dnZXIuZSgnRmFpbGVkIHRvIHNldCBhbmFseXRpY3MgdXNlciBwcm9wZXJ0eTogJG5hbWUnLCBlLCBzKTsKICAgIH0KICB9CgogIEBvdmVycmlkZQogIEZ1dHVyZTx2b2lkPiByZXNldEFuYWx5dGljc0RhdGEoKSBhc3luYyB7CiAgICB0cnkgewogICAgICBhd2FpdCBfYW5hbHl0aWNzLnJlc2V0QW5hbHl0aWNzRGF0YSgpOwoKICAgICAgaWYgKF9jb25maWcuZW5hYmxlRGVidWdMb2dnaW5nKSB7CiAgICAgICAgX2xvZ2dlci5kKCfwn5SNIEFuYWx5dGljcyBkYXRhIHJlc2V0Jyk7CiAgICAgIH0KICAgIH0gY2F0Y2ggKGUsIHMpIHsKICAgICAgX2xvZ2dlci5lKCdGYWlsZWQgdG8gcmVzZXQgYW5hbHl0aWNzIGRhdGEnLCBlLCBzKTsKICAgIH0KICB9CgogIEBvdmVycmlkZQogIEZ1dHVyZTx2b2lkPiBzZXRBbmFseXRpY3NDb2xsZWN0aW9uRW5hYmxlZChib29sIGVuYWJsZWQpIGFzeW5jIHsKICAgIHRyeSB7CiAgICAgIGF3YWl0IF9hbmFseXRpY3Muc2V0QW5hbHl0aWNzQ29sbGVjdGlvbkVuYWJsZWQoZW5hYmxlZCk7CiAgICAgIF9lbmFibGVkID0gZW5hYmxlZDsKCiAgICAgIGlmIChfY29uZmlnLmVuYWJsZURlYnVnTG9nZ2luZykgewogICAgICAgIF9sb2dnZXIuZCgKICAgICAgICAgICfwn5SNIEFuYWx5dGljcyBjb2xsZWN0aW9uICR7ZW5hYmxlZCA/ICdlbmFibGVkJyA6ICdkaXNhYmxlZCd9JywKICAgICAgICApOwogICAgICB9CiAgICB9IGNhdGNoIChlLCBzKSB7CiAgICAgIF9sb2dnZXIuZSgnRmFpbGVkIHRvIHNldCBhbmFseXRpY3MgY29sbGVjdGlvbiBlbmFibGVkOiAkZW5hYmxlZCcsIGUsIHMpOwogICAgfQogIH0KCiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IGxvZ1NjcmVlblZpZXcoewogICAgcmVxdWlyZWQgU3RyaW5nIHNjcmVlbk5hbWUsCiAgICBTdHJpbmc/IHNjcmVlbkNsYXNzLAogICAgTWFwPFN0cmluZywgZHluYW1pYz4/IHBhcmFtZXRlcnMsCiAgfSkgYXN5bmMgewogICAgZmluYWwgZXZlbnRQYXJhbWV0ZXJzID0gPFN0cmluZywgZHluYW1pYz57CiAgICAgIFByZWRlZmluZWRQYXJhbWV0ZXJzLnNjcmVlbk5hbWU6IHNjcmVlbk5hbWUsCiAgICAgIFByZWRlZmluZWRQYXJhbWV0ZXJzLnNjcmVlbkNsYXNzOiA/c2NyZWVuQ2xhc3MsCiAgICAgIC4uLj9wYXJhbWV0ZXJzLAogICAgfTsKCiAgICBhd2FpdCBsb2dFdmVudCgKICAgICAgbmFtZTogUHJlZGVmaW5lZEV2ZW50cy5zY3JlZW5WaWV3LAogICAgICBwYXJhbWV0ZXJzOiBldmVudFBhcmFtZXRlcnMsCiAgICApOwogIH0KCiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IGxvZ0FwcE9wZW4oe01hcDxTdHJpbmcsIGR5bmFtaWM+PyBwYXJhbWV0ZXJzfSkgYXN5bmMgewogICAgYXdhaXQgbG9nRXZlbnQobmFtZTogUHJlZGVmaW5lZEV2ZW50cy5hcHBPcGVuLCBwYXJhbWV0ZXJzOiBwYXJhbWV0ZXJzKTsKICB9CgogIEBvdmVycmlkZQogIEZ1dHVyZTx2b2lkPiBsb2dMb2dpbih7CiAgICBTdHJpbmc/IGxvZ2luTWV0aG9kLAogICAgTWFwPFN0cmluZywgZHluYW1pYz4/IHBhcmFtZXRlcnMsCiAgfSkgYXN5bmMgewogICAgZmluYWwgZXZlbnRQYXJhbWV0ZXJzID0gPFN0cmluZywgZHluYW1pYz57CiAgICAgIFByZWRlZmluZWRQYXJhbWV0ZXJzLm1ldGhvZDogP2xvZ2luTWV0aG9kLAogICAgICAuLi4/cGFyYW1ldGVycywKICAgIH07CgogICAgYXdhaXQgbG9nRXZlbnQobmFtZTogUHJlZGVmaW5lZEV2ZW50cy5sb2dpbiwgcGFyYW1ldGVyczogZXZlbnRQYXJhbWV0ZXJzKTsKICB9CgogIEBvdmVycmlkZQogIEZ1dHVyZTx2b2lkPiBsb2dTaWduVXAoewogICAgU3RyaW5nPyBzaWduVXBNZXRob2QsCiAgICBNYXA8U3RyaW5nLCBkeW5hbWljPj8gcGFyYW1ldGVycywKICB9KSBhc3luYyB7CiAgICBmaW5hbCBldmVudFBhcmFtZXRlcnMgPSA8U3RyaW5nLCBkeW5hbWljPnsKICAgICAgUHJlZGVmaW5lZFBhcmFtZXRlcnMubWV0aG9kOiA/c2lnblVwTWV0aG9kLAogICAgICAuLi4/cGFyYW1ldGVycywKICAgIH07CgogICAgYXdhaXQgbG9nRXZlbnQobmFtZTogUHJlZGVmaW5lZEV2ZW50cy5zaWduVXAsIHBhcmFtZXRlcnM6IGV2ZW50UGFyYW1ldGVycyk7CiAgfQoKICBAb3ZlcnJpZGUKICBGdXR1cmU8dm9pZD4gbG9nUHVyY2hhc2UoewogICAgcmVxdWlyZWQgU3RyaW5nIGN1cnJlbmN5LAogICAgcmVxdWlyZWQgZG91YmxlIHZhbHVlLAogICAgTWFwPFN0cmluZywgZHluYW1pYz4/IHBhcmFtZXRlcnMsCiAgfSkgYXN5bmMgewogICAgZmluYWwgZXZlbnRQYXJhbWV0ZXJzID0gPFN0cmluZywgZHluYW1pYz57CiAgICAgIFByZWRlZmluZWRQYXJhbWV0ZXJzLmN1cnJlbmN5OiBjdXJyZW5jeSwKICAgICAgUHJlZGVmaW5lZFBhcmFtZXRlcnMudmFsdWU6IHZhbHVlLAogICAgICAuLi4/cGFyYW1ldGVycywKICAgIH07CgogICAgYXdhaXQgbG9nRXZlbnQoCiAgICAgIG5hbWU6IFByZWRlZmluZWRFdmVudHMucHVyY2hhc2UsCiAgICAgIHBhcmFtZXRlcnM6IGV2ZW50UGFyYW1ldGVycywKICAgICk7CiAgfQoKICBAb3ZlcnJpZGUKICB2b2lkIGRpc3Bvc2UoKSB7CiAgICAvLyBGaXJlYmFzZSBBbmFseXRpY3MgZG9lc24ndCBuZWVkIGV4cGxpY2l0IGRpc3Bvc2FsCiAgICBpZiAoX2NvbmZpZy5lbmFibGVEZWJ1Z0xvZ2dpbmcpIHsKICAgICAgX2xvZ2dlci5kKCfwn5SNIEZpcmViYXNlIEFuYWx5dGljcyBjbGllbnQgZGlzcG9zZWQnKTsKICAgIH0KICB9Cn0K", "type": "text" }, { @@ -966,7 +960,7 @@ final flutterProjectWithMonoRepoBundle = MasonBundle.fromJson({ "path": "{{ name.snakeCase() }}/packages/{{analytics_dir}}/lib/src/utils/analytics_helper.dart", "data": - "aW1wb3J0ICdwYWNrYWdlOmFuYWx5dGljcy9zcmMvY2xpZW50L2FuYWx5dGljc19jbGllbnQuZGFydCc7CmltcG9ydCAncGFja2FnZTphbmFseXRpY3Mvc3JjL21vZGVscy9hbmFseXRpY3NfZXZlbnQuZGFydCc7CmltcG9ydCAncGFja2FnZTphbmFseXRpY3Mvc3JjL21vZGVscy9hbmFseXRpY3NfZXZlbnRzLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Y29yZS9jb3JlLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6ZGkvZGkuZGFydCc7CgpjbGFzcyBBbmFseXRpY3NIZWxwZXIgewogIHN0YXRpYyBBbmFseXRpY3NDbGllbnQ/IGdldCBfYW5hbHl0aWNzQ2xpZW50ID0+IF9nZXRBbmFseXRpY3NDbGllbnQoKTsKCiAgc3RhdGljIExvZ2dlcj8gZ2V0IF9sb2cgPT4gZGkuaGFzPExvZ2dlcj4oKSA/IGRpLmdldDxMb2dnZXI+KCkgOiBudWxsOwoKICBzdGF0aWMgQW5hbHl0aWNzQ2xpZW50PyBfZ2V0QW5hbHl0aWNzQ2xpZW50KCkgewogICAgdHJ5IHsKICAgICAgcmV0dXJuIGRpLmdldDxBbmFseXRpY3NDbGllbnQ+KCk7CiAgICB9IGNhdGNoIChlKSB7CiAgICAgIF9sb2c/LncoJ0FuYWx5dGljcyBjbGllbnQgbm90IGF2YWlsYWJsZTogJGUnKTsKICAgICAgcmV0dXJuIG51bGw7CiAgICB9CiAgfQoKICAvLy8gTG9nIGEgY3VzdG9tIGV2ZW50IHdpdGggb3B0aW9uYWwgcGFyYW1ldGVycwogIHN0YXRpYyBGdXR1cmU8dm9pZD4gbG9nRXZlbnQoCiAgICBTdHJpbmcgbmFtZSwgewogICAgTWFwPFN0cmluZywgZHluYW1pYz4/IHBhcmFtZXRlcnMsCiAgfSkgYXN5bmMgewogICAgZmluYWwgY2xpZW50ID0gX2FuYWx5dGljc0NsaWVudDsKICAgIGlmIChjbGllbnQgPT0gbnVsbCkgcmV0dXJuOwoKICAgIHRyeSB7CiAgICAgIGF3YWl0IGNsaWVudC5sb2dFdmVudChuYW1lOiBuYW1lLCBwYXJhbWV0ZXJzOiBwYXJhbWV0ZXJzKTsKICAgIH0gY2F0Y2ggKGUsIHMpIHsKICAgICAgX2xvZz8uZSgnRmFpbGVkIHRvIGxvZyBhbmFseXRpY3MgZXZlbnQ6ICRuYW1lJywgZSwgcyk7CiAgICB9CiAgfQoKICAvLy8gTG9nIHVzZXIgc2lnbiBpbgogIHN0YXRpYyBGdXR1cmU8dm9pZD4gbG9nU2lnbkluKHsKICAgIFN0cmluZz8gbWV0aG9kLAogICAgTWFwPFN0cmluZywgZHluYW1pYz4/IHBhcmFtZXRlcnMsCiAgfSkgYXN5bmMgewogICAgZmluYWwgY2xpZW50ID0gX2FuYWx5dGljc0NsaWVudDsKICAgIGlmIChjbGllbnQgPT0gbnVsbCkgcmV0dXJuOwoKICAgIHRyeSB7CiAgICAgIGF3YWl0IGNsaWVudC5sb2dMb2dpbihsb2dpbk1ldGhvZDogbWV0aG9kLCBwYXJhbWV0ZXJzOiBwYXJhbWV0ZXJzKTsKICAgIH0gY2F0Y2ggKGUsIHMpIHsKICAgICAgX2xvZz8uZSgnRmFpbGVkIHRvIGxvZyBzaWduIGluIGV2ZW50JywgZSwgcyk7CiAgICB9CiAgfQoKICAvLy8gTG9nIHVzZXIgc2lnbiB1cAogIHN0YXRpYyBGdXR1cmU8dm9pZD4gbG9nU2lnblVwKHsKICAgIFN0cmluZz8gbWV0aG9kLAogICAgTWFwPFN0cmluZywgZHluYW1pYz4/IHBhcmFtZXRlcnMsCiAgfSkgYXN5bmMgewogICAgZmluYWwgY2xpZW50ID0gX2FuYWx5dGljc0NsaWVudDsKICAgIGlmIChjbGllbnQgPT0gbnVsbCkgcmV0dXJuOwoKICAgIHRyeSB7CiAgICAgIGF3YWl0IGNsaWVudC5sb2dTaWduVXAoc2lnblVwTWV0aG9kOiBtZXRob2QsIHBhcmFtZXRlcnM6IHBhcmFtZXRlcnMpOwogICAgfSBjYXRjaCAoZSwgcykgewogICAgICBfbG9nPy5lKCdGYWlsZWQgdG8gbG9nIHNpZ24gdXAgZXZlbnQnLCBlLCBzKTsKICAgIH0KICB9CgogIC8vLyBTZXQgdXNlciBJRCBmb3IgYW5hbHl0aWNzCiAgc3RhdGljIEZ1dHVyZTx2b2lkPiBzZXRVc2VySWQoU3RyaW5nPyB1c2VySWQpIGFzeW5jIHsKICAgIGZpbmFsIGNsaWVudCA9IF9hbmFseXRpY3NDbGllbnQ7CiAgICBpZiAoY2xpZW50ID09IG51bGwpIHJldHVybjsKCiAgICB0cnkgewogICAgICBhd2FpdCBjbGllbnQuc2V0VXNlcklkKHVzZXJJZCk7CiAgICB9IGNhdGNoIChlLCBzKSB7CiAgICAgIF9sb2c/LmUoJ0ZhaWxlZCB0byBzZXQgYW5hbHl0aWNzIHVzZXIgSUQnLCBlLCBzKTsKICAgIH0KICB9CgogIC8vLyBTZXQgdXNlciBwcm9wZXJ0eQogIHN0YXRpYyBGdXR1cmU8dm9pZD4gc2V0VXNlclByb3BlcnR5KHsKICAgIHJlcXVpcmVkIFN0cmluZyBuYW1lLAogICAgcmVxdWlyZWQgU3RyaW5nPyB2YWx1ZSwKICB9KSBhc3luYyB7CiAgICBmaW5hbCBjbGllbnQgPSBfYW5hbHl0aWNzQ2xpZW50OwogICAgaWYgKGNsaWVudCA9PSBudWxsKSByZXR1cm47CgogICAgdHJ5IHsKICAgICAgYXdhaXQgY2xpZW50LnNldFVzZXJQcm9wZXJ0eShuYW1lOiBuYW1lLCB2YWx1ZTogdmFsdWUpOwogICAgfSBjYXRjaCAoZSwgcykgewogICAgICBfbG9nPy5lKCdGYWlsZWQgdG8gc2V0IGFuYWx5dGljcyB1c2VyIHByb3BlcnR5OiAkbmFtZScsIGUsIHMpOwogICAgfQogIH0KCiAgLy8vIExvZyBhcHAgb3BlbiBldmVudAogIHN0YXRpYyBGdXR1cmU8dm9pZD4gbG9nQXBwT3Blbih7TWFwPFN0cmluZywgZHluYW1pYz4/IHBhcmFtZXRlcnN9KSBhc3luYyB7CiAgICBmaW5hbCBjbGllbnQgPSBfYW5hbHl0aWNzQ2xpZW50OwogICAgaWYgKGNsaWVudCA9PSBudWxsKSByZXR1cm47CgogICAgdHJ5IHsKICAgICAgYXdhaXQgY2xpZW50LmxvZ0FwcE9wZW4ocGFyYW1ldGVyczogcGFyYW1ldGVycyk7CiAgICB9IGNhdGNoIChlLCBzKSB7CiAgICAgIF9sb2c/LmUoJ0ZhaWxlZCB0byBsb2cgYXBwIG9wZW4gZXZlbnQnLCBlLCBzKTsKICAgIH0KICB9CgogIC8vLyBMb2cgY3VzdG9tIGFuYWx5dGljcyBldmVudAogIHN0YXRpYyBGdXR1cmU8dm9pZD4gbG9nQ3VzdG9tRXZlbnQoQW5hbHl0aWNzRXZlbnQgZXZlbnQpIGFzeW5jIHsKICAgIGZpbmFsIGNsaWVudCA9IF9hbmFseXRpY3NDbGllbnQ7CiAgICBpZiAoY2xpZW50ID09IG51bGwpIHJldHVybjsKCiAgICB0cnkgewogICAgICBhd2FpdCBjbGllbnQubG9nQ3VzdG9tRXZlbnQoZXZlbnQpOwogICAgfSBjYXRjaCAoZSwgcykgewogICAgICBfbG9nPy5lKCdGYWlsZWQgdG8gbG9nIGN1c3RvbSBhbmFseXRpY3MgZXZlbnQ6ICR7ZXZlbnQubmFtZX0nLCBlLCBzKTsKICAgIH0KICB9CgogIC8vLyBDb21tb24gYnVzaW5lc3MgZXZlbnRzCiAgc3RhdGljIEZ1dHVyZTx2b2lkPiBsb2dGZWF0dXJlVXNlZCgKICAgIFN0cmluZyBmZWF0dXJlTmFtZSwgewogICAgTWFwPFN0cmluZywgZHluYW1pYz4/IHBhcmFtZXRlcnMsCiAgfSkgYXN5bmMgewogICAgYXdhaXQgbG9nRXZlbnQoCiAgICAgIEFuYWx5dGljc0V2ZW50cy5mZWF0dXJlLnVzZWQsCiAgICAgIHBhcmFtZXRlcnM6IHsKICAgICAgICAnZmVhdHVyZV9uYW1lJzogZmVhdHVyZU5hbWUsCiAgICAgICAgJ3RpbWVzdGFtcCc6IERhdGVUaW1lLm5vdygpLnRvSXNvODYwMVN0cmluZygpLAogICAgICAgIC4uLj9wYXJhbWV0ZXJzLAogICAgICB9LAogICAgKTsKICB9CgogIHN0YXRpYyBGdXR1cmU8dm9pZD4gbG9nQnV0dG9uUHJlc3NlZCgKICAgIFN0cmluZyBidXR0b25OYW1lLCB7CiAgICBTdHJpbmc/IHNjcmVlbk5hbWUsCiAgICBNYXA8U3RyaW5nLCBkeW5hbWljPj8gcGFyYW1ldGVycywKICB9KSBhc3luYyB7CiAgICBhd2FpdCBsb2dFdmVudCgKICAgICAgQW5hbHl0aWNzRXZlbnRzLmZlYXR1cmUuYnV0dG9uUHJlc3NlZCwKICAgICAgcGFyYW1ldGVyczogewogICAgICAgICdidXR0b25fbmFtZSc6IGJ1dHRvbk5hbWUsCiAgICAgICAgaWYgKHNjcmVlbk5hbWUgIT0gbnVsbCkgJ3NjcmVlbl9uYW1lJzogc2NyZWVuTmFtZSwKICAgICAgICAndGltZXN0YW1wJzogRGF0ZVRpbWUubm93KCkudG9Jc284NjAxU3RyaW5nKCksCiAgICAgICAgLi4uP3BhcmFtZXRlcnMsCiAgICAgIH0sCiAgICApOwogIH0KCiAgc3RhdGljIEZ1dHVyZTx2b2lkPiBsb2dBcHBFcnJvcigKICAgIFN0cmluZyBlcnJvclR5cGUsIHsKICAgIFN0cmluZz8gZXJyb3JNZXNzYWdlLAogICAgTWFwPFN0cmluZywgZHluYW1pYz4/IHBhcmFtZXRlcnMsCiAgfSkgYXN5bmMgewogICAgYXdhaXQgbG9nRXZlbnQoCiAgICAgIEFuYWx5dGljc0V2ZW50cy5lcnJvci5hcHBFcnJvciwKICAgICAgcGFyYW1ldGVyczogewogICAgICAgICdlcnJvcl90eXBlJzogZXJyb3JUeXBlLAogICAgICAgIGlmIChlcnJvck1lc3NhZ2UgIT0gbnVsbCkgJ2Vycm9yX21lc3NhZ2UnOiBlcnJvck1lc3NhZ2UsCiAgICAgICAgJ3RpbWVzdGFtcCc6IERhdGVUaW1lLm5vdygpLnRvSXNvODYwMVN0cmluZygpLAogICAgICAgIC4uLj9wYXJhbWV0ZXJzLAogICAgICB9LAogICAgKTsKICB9Cn0K", + "aW1wb3J0ICdwYWNrYWdlOmFuYWx5dGljcy9zcmMvY2xpZW50L2FuYWx5dGljc19jbGllbnQuZGFydCc7CmltcG9ydCAncGFja2FnZTphbmFseXRpY3Mvc3JjL21vZGVscy9hbmFseXRpY3NfZXZlbnQuZGFydCc7CmltcG9ydCAncGFja2FnZTphbmFseXRpY3Mvc3JjL21vZGVscy9hbmFseXRpY3NfZXZlbnRzLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6Y29yZS9jb3JlLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6ZGkvZGkuZGFydCc7CgpjbGFzcyBBbmFseXRpY3NIZWxwZXIgewogIHN0YXRpYyBBbmFseXRpY3NDbGllbnQ/IGdldCBfYW5hbHl0aWNzQ2xpZW50ID0+IF9nZXRBbmFseXRpY3NDbGllbnQoKTsKCiAgc3RhdGljIExvZ2dlcj8gZ2V0IF9sb2cgPT4gZGkuaGFzPExvZ2dlcj4oKSA/IGRpLmdldDxMb2dnZXI+KCkgOiBudWxsOwoKICBzdGF0aWMgQW5hbHl0aWNzQ2xpZW50PyBfZ2V0QW5hbHl0aWNzQ2xpZW50KCkgewogICAgdHJ5IHsKICAgICAgcmV0dXJuIGRpLmdldDxBbmFseXRpY3NDbGllbnQ+KCk7CiAgICB9IGNhdGNoIChlKSB7CiAgICAgIF9sb2c/LncoJ0FuYWx5dGljcyBjbGllbnQgbm90IGF2YWlsYWJsZTogJGUnKTsKICAgICAgcmV0dXJuIG51bGw7CiAgICB9CiAgfQoKICAvLy8gTG9nIGEgY3VzdG9tIGV2ZW50IHdpdGggb3B0aW9uYWwgcGFyYW1ldGVycwogIHN0YXRpYyBGdXR1cmU8dm9pZD4gbG9nRXZlbnQoCiAgICBTdHJpbmcgbmFtZSwgewogICAgTWFwPFN0cmluZywgZHluYW1pYz4/IHBhcmFtZXRlcnMsCiAgfSkgYXN5bmMgewogICAgZmluYWwgY2xpZW50ID0gX2FuYWx5dGljc0NsaWVudDsKICAgIGlmIChjbGllbnQgPT0gbnVsbCkgcmV0dXJuOwoKICAgIHRyeSB7CiAgICAgIGF3YWl0IGNsaWVudC5sb2dFdmVudChuYW1lOiBuYW1lLCBwYXJhbWV0ZXJzOiBwYXJhbWV0ZXJzKTsKICAgIH0gY2F0Y2ggKGUsIHMpIHsKICAgICAgX2xvZz8uZSgnRmFpbGVkIHRvIGxvZyBhbmFseXRpY3MgZXZlbnQ6ICRuYW1lJywgZSwgcyk7CiAgICB9CiAgfQoKICAvLy8gTG9nIHVzZXIgc2lnbiBpbgogIHN0YXRpYyBGdXR1cmU8dm9pZD4gbG9nU2lnbkluKHsKICAgIFN0cmluZz8gbWV0aG9kLAogICAgTWFwPFN0cmluZywgZHluYW1pYz4/IHBhcmFtZXRlcnMsCiAgfSkgYXN5bmMgewogICAgZmluYWwgY2xpZW50ID0gX2FuYWx5dGljc0NsaWVudDsKICAgIGlmIChjbGllbnQgPT0gbnVsbCkgcmV0dXJuOwoKICAgIHRyeSB7CiAgICAgIGF3YWl0IGNsaWVudC5sb2dMb2dpbihsb2dpbk1ldGhvZDogbWV0aG9kLCBwYXJhbWV0ZXJzOiBwYXJhbWV0ZXJzKTsKICAgIH0gY2F0Y2ggKGUsIHMpIHsKICAgICAgX2xvZz8uZSgnRmFpbGVkIHRvIGxvZyBzaWduIGluIGV2ZW50JywgZSwgcyk7CiAgICB9CiAgfQoKICAvLy8gTG9nIHVzZXIgc2lnbiB1cAogIHN0YXRpYyBGdXR1cmU8dm9pZD4gbG9nU2lnblVwKHsKICAgIFN0cmluZz8gbWV0aG9kLAogICAgTWFwPFN0cmluZywgZHluYW1pYz4/IHBhcmFtZXRlcnMsCiAgfSkgYXN5bmMgewogICAgZmluYWwgY2xpZW50ID0gX2FuYWx5dGljc0NsaWVudDsKICAgIGlmIChjbGllbnQgPT0gbnVsbCkgcmV0dXJuOwoKICAgIHRyeSB7CiAgICAgIGF3YWl0IGNsaWVudC5sb2dTaWduVXAoc2lnblVwTWV0aG9kOiBtZXRob2QsIHBhcmFtZXRlcnM6IHBhcmFtZXRlcnMpOwogICAgfSBjYXRjaCAoZSwgcykgewogICAgICBfbG9nPy5lKCdGYWlsZWQgdG8gbG9nIHNpZ24gdXAgZXZlbnQnLCBlLCBzKTsKICAgIH0KICB9CgogIC8vLyBTZXQgdXNlciBJRCBmb3IgYW5hbHl0aWNzCiAgc3RhdGljIEZ1dHVyZTx2b2lkPiBzZXRVc2VySWQoU3RyaW5nPyB1c2VySWQpIGFzeW5jIHsKICAgIGZpbmFsIGNsaWVudCA9IF9hbmFseXRpY3NDbGllbnQ7CiAgICBpZiAoY2xpZW50ID09IG51bGwpIHJldHVybjsKCiAgICB0cnkgewogICAgICBhd2FpdCBjbGllbnQuc2V0VXNlcklkKHVzZXJJZCk7CiAgICB9IGNhdGNoIChlLCBzKSB7CiAgICAgIF9sb2c/LmUoJ0ZhaWxlZCB0byBzZXQgYW5hbHl0aWNzIHVzZXIgSUQnLCBlLCBzKTsKICAgIH0KICB9CgogIC8vLyBTZXQgdXNlciBwcm9wZXJ0eQogIHN0YXRpYyBGdXR1cmU8dm9pZD4gc2V0VXNlclByb3BlcnR5KHsKICAgIHJlcXVpcmVkIFN0cmluZyBuYW1lLAogICAgcmVxdWlyZWQgU3RyaW5nPyB2YWx1ZSwKICB9KSBhc3luYyB7CiAgICBmaW5hbCBjbGllbnQgPSBfYW5hbHl0aWNzQ2xpZW50OwogICAgaWYgKGNsaWVudCA9PSBudWxsKSByZXR1cm47CgogICAgdHJ5IHsKICAgICAgYXdhaXQgY2xpZW50LnNldFVzZXJQcm9wZXJ0eShuYW1lOiBuYW1lLCB2YWx1ZTogdmFsdWUpOwogICAgfSBjYXRjaCAoZSwgcykgewogICAgICBfbG9nPy5lKCdGYWlsZWQgdG8gc2V0IGFuYWx5dGljcyB1c2VyIHByb3BlcnR5OiAkbmFtZScsIGUsIHMpOwogICAgfQogIH0KCiAgLy8vIExvZyBhcHAgb3BlbiBldmVudAogIHN0YXRpYyBGdXR1cmU8dm9pZD4gbG9nQXBwT3Blbih7TWFwPFN0cmluZywgZHluYW1pYz4/IHBhcmFtZXRlcnN9KSBhc3luYyB7CiAgICBmaW5hbCBjbGllbnQgPSBfYW5hbHl0aWNzQ2xpZW50OwogICAgaWYgKGNsaWVudCA9PSBudWxsKSByZXR1cm47CgogICAgdHJ5IHsKICAgICAgYXdhaXQgY2xpZW50LmxvZ0FwcE9wZW4ocGFyYW1ldGVyczogcGFyYW1ldGVycyk7CiAgICB9IGNhdGNoIChlLCBzKSB7CiAgICAgIF9sb2c/LmUoJ0ZhaWxlZCB0byBsb2cgYXBwIG9wZW4gZXZlbnQnLCBlLCBzKTsKICAgIH0KICB9CgogIC8vLyBMb2cgY3VzdG9tIGFuYWx5dGljcyBldmVudAogIHN0YXRpYyBGdXR1cmU8dm9pZD4gbG9nQ3VzdG9tRXZlbnQoQW5hbHl0aWNzRXZlbnQgZXZlbnQpIGFzeW5jIHsKICAgIGZpbmFsIGNsaWVudCA9IF9hbmFseXRpY3NDbGllbnQ7CiAgICBpZiAoY2xpZW50ID09IG51bGwpIHJldHVybjsKCiAgICB0cnkgewogICAgICBhd2FpdCBjbGllbnQubG9nQ3VzdG9tRXZlbnQoZXZlbnQpOwogICAgfSBjYXRjaCAoZSwgcykgewogICAgICBfbG9nPy5lKCdGYWlsZWQgdG8gbG9nIGN1c3RvbSBhbmFseXRpY3MgZXZlbnQ6ICR7ZXZlbnQubmFtZX0nLCBlLCBzKTsKICAgIH0KICB9CgogIC8vLyBDb21tb24gYnVzaW5lc3MgZXZlbnRzCiAgc3RhdGljIEZ1dHVyZTx2b2lkPiBsb2dGZWF0dXJlVXNlZCgKICAgIFN0cmluZyBmZWF0dXJlTmFtZSwgewogICAgTWFwPFN0cmluZywgZHluYW1pYz4/IHBhcmFtZXRlcnMsCiAgfSkgYXN5bmMgewogICAgYXdhaXQgbG9nRXZlbnQoCiAgICAgIEFuYWx5dGljc0V2ZW50cy5mZWF0dXJlLnVzZWQsCiAgICAgIHBhcmFtZXRlcnM6IHsKICAgICAgICAnZmVhdHVyZV9uYW1lJzogZmVhdHVyZU5hbWUsCiAgICAgICAgJ3RpbWVzdGFtcCc6IERhdGVUaW1lLm5vdygpLnRvSXNvODYwMVN0cmluZygpLAogICAgICAgIC4uLj9wYXJhbWV0ZXJzLAogICAgICB9LAogICAgKTsKICB9CgogIHN0YXRpYyBGdXR1cmU8dm9pZD4gbG9nQnV0dG9uUHJlc3NlZCgKICAgIFN0cmluZyBidXR0b25OYW1lLCB7CiAgICBTdHJpbmc/IHNjcmVlbk5hbWUsCiAgICBNYXA8U3RyaW5nLCBkeW5hbWljPj8gcGFyYW1ldGVycywKICB9KSBhc3luYyB7CiAgICBhd2FpdCBsb2dFdmVudCgKICAgICAgQW5hbHl0aWNzRXZlbnRzLmZlYXR1cmUuYnV0dG9uUHJlc3NlZCwKICAgICAgcGFyYW1ldGVyczogewogICAgICAgICdidXR0b25fbmFtZSc6IGJ1dHRvbk5hbWUsCiAgICAgICAgJ3NjcmVlbl9uYW1lJzogP3NjcmVlbk5hbWUsCiAgICAgICAgJ3RpbWVzdGFtcCc6IERhdGVUaW1lLm5vdygpLnRvSXNvODYwMVN0cmluZygpLAogICAgICAgIC4uLj9wYXJhbWV0ZXJzLAogICAgICB9LAogICAgKTsKICB9CgogIHN0YXRpYyBGdXR1cmU8dm9pZD4gbG9nQXBwRXJyb3IoCiAgICBTdHJpbmcgZXJyb3JUeXBlLCB7CiAgICBTdHJpbmc/IGVycm9yTWVzc2FnZSwKICAgIE1hcDxTdHJpbmcsIGR5bmFtaWM+PyBwYXJhbWV0ZXJzLAogIH0pIGFzeW5jIHsKICAgIGF3YWl0IGxvZ0V2ZW50KAogICAgICBBbmFseXRpY3NFdmVudHMuZXJyb3IuYXBwRXJyb3IsCiAgICAgIHBhcmFtZXRlcnM6IHsKICAgICAgICAnZXJyb3JfdHlwZSc6IGVycm9yVHlwZSwKICAgICAgICAnZXJyb3JfbWVzc2FnZSc6ID9lcnJvck1lc3NhZ2UsCiAgICAgICAgJ3RpbWVzdGFtcCc6IERhdGVUaW1lLm5vdygpLnRvSXNvODYwMVN0cmluZygpLAogICAgICAgIC4uLj9wYXJhbWV0ZXJzLAogICAgICB9LAogICAgKTsKICB9Cn0K", "type": "text" }, { @@ -978,7 +972,7 @@ final flutterProjectWithMonoRepoBundle = MasonBundle.fromJson({ { "path": "{{ name.snakeCase() }}/packages/{{analytics_dir}}/pubspec.yaml", "data": - "bmFtZTogYW5hbHl0aWNzCmRlc2NyaXB0aW9uOiAiRXZlbnQgdHJhY2tpbmcgZm9yIHt7bmFtZS50aXRsZUNhc2UoKX19IGJlaGluZCBhIHN3YXBwYWJsZSBjbGllbnQgaW50ZXJmYWNlLiIKdmVyc2lvbjogMC4xLjAKcHVibGlzaF90bzogJ25vbmUnCgplbnZpcm9ubWVudDoKICBzZGs6IF4zLjguMAogIGZsdXR0ZXI6ICI+PTMuMjkuMCIKCmRlcGVuZGVuY2llczoKICBmbHV0dGVyOgogICAgc2RrOiBmbHV0dGVyCiAgZmlyZWJhc2VfYW5hbHl0aWNzOiBeMTEuMy41CiAgY29yZToKICAgIHBhdGg6IC4uL2NvcmUKICBkaToKICAgIHBhdGg6IC4uL2RpCgpkZXZfZGVwZW5kZW5jaWVzOgogIG1vY2t0YWlsOiBeMS4wLjQKICBmbHV0dGVyX3Rlc3Q6CiAgICBzZGs6IGZsdXR0ZXIKICBmbHV0dGVyX2xpbnRzOiBeNS4wLjAKCmZsdXR0ZXI6Cg==", + "bmFtZTogYW5hbHl0aWNzCmRlc2NyaXB0aW9uOiAiRXZlbnQgdHJhY2tpbmcgZm9yIHt7bmFtZS50aXRsZUNhc2UoKX19IGJlaGluZCBhIHN3YXBwYWJsZSBjbGllbnQgaW50ZXJmYWNlLiIKdmVyc2lvbjogMC4xLjAKcHVibGlzaF90bzogJ25vbmUnCgplbnZpcm9ubWVudDoKICBzZGs6IF4zLjguMAogIGZsdXR0ZXI6ICI+PTMuNDcuNSIKCmRlcGVuZGVuY2llczoKICBmbHV0dGVyOgogICAgc2RrOiBmbHV0dGVyCiAgZmlyZWJhc2VfYW5hbHl0aWNzOiBeMTIuNi4wCiAgY29yZToKICAgIHBhdGg6IC4uL2NvcmUKICBkaToKICAgIHBhdGg6IC4uL2RpCgpkZXZfZGVwZW5kZW5jaWVzOgogIG1vY2t0YWlsOiBeMS4wLjQKICBmbHV0dGVyX3Rlc3Q6CiAgICBzZGs6IGZsdXR0ZXIKICBmbHV0dGVyX2xpbnRzOiBeNi4wLjAKCmZsdXR0ZXI6CgpyZXNvbHV0aW9uOiB3b3Jrc3BhY2UK", "type": "text" }, { @@ -1081,7 +1075,7 @@ final flutterProjectWithMonoRepoBundle = MasonBundle.fromJson({ "path": "{{ name.snakeCase() }}/packages/{{crashlytics_dir}}/pubspec.yaml", "data": - "bmFtZTogY3Jhc2hseXRpY3MKZGVzY3JpcHRpb246ICJDcmFzaCBhbmQgbm9uLWZhdGFsIGVycm9yIHJlcG9ydGluZyBmb3Ige3tuYW1lLnRpdGxlQ2FzZSgpfX0uIgp2ZXJzaW9uOiAwLjEuMApwdWJsaXNoX3RvOiAnbm9uZScKCmVudmlyb25tZW50OgogIHNkazogXjMuOC4wCiAgZmx1dHRlcjogIj49My4yOS4wIgoKZGVwZW5kZW5jaWVzOgogIGZsdXR0ZXI6CiAgICBzZGs6IGZsdXR0ZXIKICBmaXJlYmFzZV9jb3JlOiBeMy4xNC4wCiAgZmlyZWJhc2VfY3Jhc2hseXRpY3M6IF40LjEuNAogIGNvcmU6CiAgICBwYXRoOiAuLi9jb3JlCiAgZGk6CiAgICBwYXRoOiAuLi9kaQoKZGV2X2RlcGVuZGVuY2llczoKICBtb2NrdGFpbDogXjEuMC40CiAgZmx1dHRlcl90ZXN0OgogICAgc2RrOiBmbHV0dGVyCiAgZmx1dHRlcl9saW50czogXjUuMC4wCgpmbHV0dGVyOgo=", + "bmFtZTogY3Jhc2hseXRpY3MKZGVzY3JpcHRpb246ICJDcmFzaCBhbmQgbm9uLWZhdGFsIGVycm9yIHJlcG9ydGluZyBmb3Ige3tuYW1lLnRpdGxlQ2FzZSgpfX0uIgp2ZXJzaW9uOiAwLjEuMApwdWJsaXNoX3RvOiAnbm9uZScKCmVudmlyb25tZW50OgogIHNkazogXjMuOC4wCiAgZmx1dHRlcjogIj49My40Ny41IgoKZGVwZW5kZW5jaWVzOgogIGZsdXR0ZXI6CiAgICBzZGs6IGZsdXR0ZXIKICBmaXJlYmFzZV9jb3JlOiBeNC4xNS4wCiAgZmlyZWJhc2VfY3Jhc2hseXRpY3M6IF41LjQuMAogIGNvcmU6CiAgICBwYXRoOiAuLi9jb3JlCiAgZGk6CiAgICBwYXRoOiAuLi9kaQoKZGV2X2RlcGVuZGVuY2llczoKICBtb2NrdGFpbDogXjEuMC40CiAgZmx1dHRlcl90ZXN0OgogICAgc2RrOiBmbHV0dGVyCiAgZmx1dHRlcl9saW50czogXjYuMC4wCgpmbHV0dGVyOgoKcmVzb2x1dGlvbjogd29ya3NwYWNlCg==", "type": "text" }, { @@ -1160,7 +1154,7 @@ final flutterProjectWithMonoRepoBundle = MasonBundle.fromJson({ { "path": "{{ name.snakeCase() }}/packages/{{developer_dir}}/pubspec.yaml", "data": - "bmFtZTogZGV2ZWxvcGVyCmRlc2NyaXB0aW9uOiAiSW4tYXBwIGRldmVsb3BlciB0b29scyBmb3Ige3tuYW1lLnRpdGxlQ2FzZSgpfX06IGxvZ3MsIGZsYWdzIGFuZCBlbnZpcm9ubWVudCBpbnNwZWN0aW9uLiIKdmVyc2lvbjogMC4xLjAKcHVibGlzaF90bzogJ25vbmUnCgplbnZpcm9ubWVudDoKICBzZGs6IF4zLjguMAogIGZsdXR0ZXI6ICI+PTMuMjkuMCIKCmRlcGVuZGVuY2llczoKICBmbHV0dGVyOgogICAgc2RrOiBmbHV0dGVyCiAgZ29fcm91dGVyOiBeMTUuMS4zCiAgdGFsa2VyX2ZsdXR0ZXI6IF40LjQuMQogIGNvcmU6CiAgICBwYXRoOiAuLi9jb3JlCiAgZGVzaWduX3N5c3RlbToKICAgIHBhdGg6IC4uL2Rlc2lnbl9zeXN0ZW0KICBkaToKICAgIHBhdGg6IC4uL2RpCiAgZmVhdHVyZV9mbGFnczoKICAgIHBhdGg6IC4uL2ZlYXR1cmVfZmxhZ3MKCmRldl9kZXBlbmRlbmNpZXM6CiAgZmx1dHRlcl90ZXN0OgogICAgc2RrOiBmbHV0dGVyCiAgZmx1dHRlcl9saW50czogXjUuMC4wCgpmbHV0dGVyOgo=", + "bmFtZTogZGV2ZWxvcGVyCmRlc2NyaXB0aW9uOiAiSW4tYXBwIGRldmVsb3BlciB0b29scyBmb3Ige3tuYW1lLnRpdGxlQ2FzZSgpfX06IGxvZ3MsIGZsYWdzIGFuZCBlbnZpcm9ubWVudCBpbnNwZWN0aW9uLiIKdmVyc2lvbjogMC4xLjAKcHVibGlzaF90bzogJ25vbmUnCgplbnZpcm9ubWVudDoKICBzZGs6IF4zLjguMAogIGZsdXR0ZXI6ICI+PTMuNDcuNSIKCmRlcGVuZGVuY2llczoKICBmbHV0dGVyOgogICAgc2RrOiBmbHV0dGVyCiAgZ29fcm91dGVyOiBeMTguMC4xCiAgdGFsa2VyX2ZsdXR0ZXI6IF41LjEuMjAKICBjb3JlOgogICAgcGF0aDogLi4vY29yZQogIGRlc2lnbl9zeXN0ZW06CiAgICBwYXRoOiAuLi9kZXNpZ25fc3lzdGVtCiAgZGk6CiAgICBwYXRoOiAuLi9kaQogIGZlYXR1cmVfZmxhZ3M6CiAgICBwYXRoOiAuLi9mZWF0dXJlX2ZsYWdzCgpkZXZfZGVwZW5kZW5jaWVzOgogIGZsdXR0ZXJfdGVzdDoKICAgIHNkazogZmx1dHRlcgogIGZsdXR0ZXJfbGludHM6IF42LjAuMAoKZmx1dHRlcjoKCnJlc29sdXRpb246IHdvcmtzcGFjZQo=", "type": "text" }, { @@ -1230,7 +1224,7 @@ final flutterProjectWithMonoRepoBundle = MasonBundle.fromJson({ "path": "{{ name.snakeCase() }}/packages/{{feature_flags_dir}}/pubspec.yaml", "data": - "bmFtZTogZmVhdHVyZV9mbGFncwpkZXNjcmlwdGlvbjogIlJlbW90ZS1jb25maWcgYmFja2VkIGZlYXR1cmUgZmxhZ3MgZm9yIHt7bmFtZS50aXRsZUNhc2UoKX19LiIKdmVyc2lvbjogMC4xLjAKcHVibGlzaF90bzogJ25vbmUnCgplbnZpcm9ubWVudDoKICBzZGs6IF4zLjguMAogIGZsdXR0ZXI6ICI+PTMuMjkuMCIKCmRlcGVuZGVuY2llczoKICBmbHV0dGVyOgogICAgc2RrOiBmbHV0dGVyCiAgZmlyZWJhc2VfY29yZTogXjMuMTQuMAogIGZpcmViYXNlX3JlbW90ZV9jb25maWc6IF41LjEuNgogIGNvcmU6CiAgICBwYXRoOiAuLi9jb3JlCiAgZGk6CiAgICBwYXRoOiAuLi9kaQoKZGV2X2RlcGVuZGVuY2llczoKICBtb2NrdGFpbDogXjEuMC40CiAgZmx1dHRlcl90ZXN0OgogICAgc2RrOiBmbHV0dGVyCiAgZmx1dHRlcl9saW50czogXjUuMC4wCgpmbHV0dGVyOgo=", + "bmFtZTogZmVhdHVyZV9mbGFncwpkZXNjcmlwdGlvbjogIlJlbW90ZS1jb25maWcgYmFja2VkIGZlYXR1cmUgZmxhZ3MgZm9yIHt7bmFtZS50aXRsZUNhc2UoKX19LiIKdmVyc2lvbjogMC4xLjAKcHVibGlzaF90bzogJ25vbmUnCgplbnZpcm9ubWVudDoKICBzZGs6IF4zLjguMAogIGZsdXR0ZXI6ICI+PTMuNDcuNSIKCmRlcGVuZGVuY2llczoKICBmbHV0dGVyOgogICAgc2RrOiBmbHV0dGVyCiAgZmlyZWJhc2VfY29yZTogXjQuMTUuMAogIGZpcmViYXNlX3JlbW90ZV9jb25maWc6IF42LjcuMAogIGNvcmU6CiAgICBwYXRoOiAuLi9jb3JlCiAgZGk6CiAgICBwYXRoOiAuLi9kaQoKZGV2X2RlcGVuZGVuY2llczoKICBtb2NrdGFpbDogXjEuMC40CiAgZmx1dHRlcl90ZXN0OgogICAgc2RrOiBmbHV0dGVyCiAgZmx1dHRlcl9saW50czogXjYuMC4wCgpmbHV0dGVyOgoKcmVzb2x1dGlvbjogd29ya3NwYWNlCg==", "type": "text" }, { @@ -1390,7 +1384,7 @@ final flutterProjectWithMonoRepoBundle = MasonBundle.fromJson({ { "path": "{{ name.snakeCase() }}/packages/{{network_dir}}/pubspec.yaml", "data": - "bmFtZTogbmV0d29yawpkZXNjcmlwdGlvbjogIkhUVFAgbGF5ZXIgZm9yIHt7bmFtZS50aXRsZUNhc2UoKX19OiBEaW8gY2xpZW50LCBpbnRlcmNlcHRvcnMsIHR5cGVkIHJlc3BvbnNlcyBhbmQgZXJyb3IgbWFwcGluZy4iCnZlcnNpb246IDAuMS4wCnB1Ymxpc2hfdG86ICdub25lJwoKZW52aXJvbm1lbnQ6CiAgc2RrOiBeMy44LjAKICBmbHV0dGVyOiAiPj0zLjI5LjAiCgpkZXBlbmRlbmNpZXM6CiAgZmx1dHRlcjoKICAgIHNkazogZmx1dHRlcgogIGRpbzogXjUuNy4wCiAganNvbl9hbm5vdGF0aW9uOiBeNC45LjAKICB0YWxrZXJfZGlvX2xvZ2dlcjogXjQuOS4xCiAgdGFsa2VyX2ZsdXR0ZXI6IF40LjQuMQogIGNvcmU6CiAgICBwYXRoOiAuLi9jb3JlCiAgbG9jYWxpemF0aW9uOgogICAgcGF0aDogLi4vbG9jYWxpemF0aW9uCiAgZGk6CiAgICBwYXRoOiAuLi9kaQoKZGV2X2RlcGVuZGVuY2llczoKICBtb2NrdGFpbDogXjEuMC40CiAgZmx1dHRlcl90ZXN0OgogICAgc2RrOiBmbHV0dGVyCiAgZmx1dHRlcl9saW50czogXjUuMC4wCiAgYnVpbGRfcnVubmVyOiBeMi40LjE1CiAganNvbl9zZXJpYWxpemFibGU6IF42LjkuNQoKZmx1dHRlcjoK", + "bmFtZTogbmV0d29yawpkZXNjcmlwdGlvbjogIkhUVFAgbGF5ZXIgZm9yIHt7bmFtZS50aXRsZUNhc2UoKX19OiBEaW8gY2xpZW50LCBpbnRlcmNlcHRvcnMsIHR5cGVkIHJlc3BvbnNlcyBhbmQgZXJyb3IgbWFwcGluZy4iCnZlcnNpb246IDAuMS4wCnB1Ymxpc2hfdG86ICdub25lJwoKZW52aXJvbm1lbnQ6CiAgc2RrOiBeMy44LjAKICBmbHV0dGVyOiAiPj0zLjQ3LjUiCgpkZXBlbmRlbmNpZXM6CiAgZmx1dHRlcjoKICAgIHNkazogZmx1dHRlcgogIGRpbzogXjUuNy4wCiAganNvbl9hbm5vdGF0aW9uOiBeNC45LjAKICB0YWxrZXJfZGlvX2xvZ2dlcjogXjUuMS4yMAogIHRhbGtlcl9mbHV0dGVyOiBeNS4xLjIwCiAgY29yZToKICAgIHBhdGg6IC4uL2NvcmUKICBsb2NhbGl6YXRpb246CiAgICBwYXRoOiAuLi9sb2NhbGl6YXRpb24KICBkaToKICAgIHBhdGg6IC4uL2RpCgpkZXZfZGVwZW5kZW5jaWVzOgogIG1vY2t0YWlsOiBeMS4wLjQKICBmbHV0dGVyX3Rlc3Q6CiAgICBzZGs6IGZsdXR0ZXIKICBmbHV0dGVyX2xpbnRzOiBeNi4wLjAKICBidWlsZF9ydW5uZXI6IF4yLjQuMTUKICBqc29uX3NlcmlhbGl6YWJsZTogXjYuOS41CgpmbHV0dGVyOgoKcmVzb2x1dGlvbjogd29ya3NwYWNlCg==", "type": "text" }, { @@ -1560,7 +1554,7 @@ final flutterProjectWithMonoRepoBundle = MasonBundle.fromJson({ "path": "{{ name.snakeCase() }}/packages/{{notifications_dir}}/pubspec.yaml", "data": - "bmFtZTogbm90aWZpY2F0aW9ucwpkZXNjcmlwdGlvbjogIkZpcmViYXNlIENsb3VkIE1lc3NhZ2luZywgcGVybWlzc2lvbnMgYW5kIGRldmljZS10b2tlbiBtYW5hZ2VtZW50IGZvciB7e25hbWUudGl0bGVDYXNlKCl9fS4iCnZlcnNpb246IDAuMS4wCnB1Ymxpc2hfdG86ICdub25lJwoKZW52aXJvbm1lbnQ6CiAgc2RrOiBeMy44LjAKICBmbHV0dGVyOiAiPj0zLjI5LjAiCgpkZXBlbmRlbmNpZXM6CiAgc2hhcmVkX3ByZWZlcmVuY2VzOiBeMi41LjMKICBmbHV0dGVyOgogICAgc2RrOiBmbHV0dGVyCiAgYXBwX2JhZGdlX3BsdXM6IF4xLjEuMgogIGRldmljZV9pbmZvX3BsdXM6IF4xMS4xLjEKICBmaXJlYmFzZV9jb3JlOiBeMy4xNC4wCiAgZmlyZWJhc2VfbWVzc2FnaW5nOiBeMTUuMS42CiAganNvbl9hbm5vdGF0aW9uOiBeNC45LjAKICBjb3JlOgogICAgcGF0aDogLi4vY29yZQogIGRpOgogICAgcGF0aDogLi4vZGkKe3sjbmV0d29ya319ICBuZXR3b3JrOgogICAgcGF0aDogLi4vbmV0d29yawp7ey9uZXR3b3JrfX0KZGV2X2RlcGVuZGVuY2llczoKICBtb2NrdGFpbDogXjEuMC40CiAgZmx1dHRlcl90ZXN0OgogICAgc2RrOiBmbHV0dGVyCiAgZmx1dHRlcl9saW50czogXjUuMC4wCiAgYnVpbGRfcnVubmVyOiBeMi40LjE1CiAganNvbl9zZXJpYWxpemFibGU6IF42LjkuNQoKZmx1dHRlcjoKICB1c2VzLW1hdGVyaWFsLWRlc2lnbjogdHJ1ZQo=", + "bmFtZTogbm90aWZpY2F0aW9ucwpkZXNjcmlwdGlvbjogIkZpcmViYXNlIENsb3VkIE1lc3NhZ2luZywgcGVybWlzc2lvbnMgYW5kIGRldmljZS10b2tlbiBtYW5hZ2VtZW50IGZvciB7e25hbWUudGl0bGVDYXNlKCl9fS4iCnZlcnNpb246IDAuMS4wCnB1Ymxpc2hfdG86ICdub25lJwoKZW52aXJvbm1lbnQ6CiAgc2RrOiBeMy44LjAKICBmbHV0dGVyOiAiPj0zLjQ3LjUiCgpkZXBlbmRlbmNpZXM6CiAgc2hhcmVkX3ByZWZlcmVuY2VzOiBeMi41LjMKICBmbHV0dGVyOgogICAgc2RrOiBmbHV0dGVyCiAgYXBwX2JhZGdlX3BsdXM6IF4xLjEuMgogIGRldmljZV9pbmZvX3BsdXM6IF4xMy4yLjAKICBmaXJlYmFzZV9jb3JlOiBeNC4xNS4wCiAgZmlyZWJhc2VfbWVzc2FnaW5nOiBeMTYuNy4wCiAganNvbl9hbm5vdGF0aW9uOiBeNC45LjAKICBjb3JlOgogICAgcGF0aDogLi4vY29yZQogIGRpOgogICAgcGF0aDogLi4vZGkKe3sjbmV0d29ya319ICBuZXR3b3JrOgogICAgcGF0aDogLi4vbmV0d29yawp7ey9uZXR3b3JrfX0KZGV2X2RlcGVuZGVuY2llczoKICBtb2NrdGFpbDogXjEuMC40CiAgZmx1dHRlcl90ZXN0OgogICAgc2RrOiBmbHV0dGVyCiAgZmx1dHRlcl9saW50czogXjYuMC4wCiAgYnVpbGRfcnVubmVyOiBeMi40LjE1CiAganNvbl9zZXJpYWxpemFibGU6IF42LjkuNQoKZmx1dHRlcjoKICB1c2VzLW1hdGVyaWFsLWRlc2lnbjogdHJ1ZQoKcmVzb2x1dGlvbjogd29ya3NwYWNlCg==", "type": "text" }, { @@ -1593,25 +1587,25 @@ final flutterProjectWithMonoRepoBundle = MasonBundle.fromJson({ { "path": "{{ name.snakeCase() }}/pubspec.yaml", "data": - "bmFtZToge3sgbmFtZS5zbmFrZUNhc2UoKSB9fV93b3Jrc3BhY2UKZGVzY3JpcHRpb246ICJ7e3tkZXNjcmlwdGlvbn19fSIKcHVibGlzaF90bzogJ25vbmUnCgplbnZpcm9ubWVudDoKICBzZGs6ICc+PTMuOC4wIDw0LjAuMCcKCmRldl9kZXBlbmRlbmNpZXM6CiAgZmx1dHRlcl9saW50czogXjUuMC4wCiAgbWVsb3M6IF42LjIuMAogIHBhdGg6IF4xLjkuMQo=", + "bmFtZToge3sgbmFtZS5zbmFrZUNhc2UoKSB9fV93b3Jrc3BhY2UKZGVzY3JpcHRpb246ICJ7e3tkZXNjcmlwdGlvbn19fSIKcHVibGlzaF90bzogJ25vbmUnCgplbnZpcm9ubWVudDoKICBzZGs6ICc+PTMuOC4wIDw0LjAuMCcKCmRldl9kZXBlbmRlbmNpZXM6CiAgZmx1dHRlcl9saW50czogXjYuMC4wCiAgbWVsb3M6IF44LjguMAogIHBhdGg6IF4xLjkuMQoKd29ya3NwYWNlOgogIC0gYXBwcy97eyBuYW1lLnNuYWtlQ2FzZSgpIH19CiAgLSBwYWNrYWdlcy9jb3JlCiAgLSBwYWNrYWdlcy9kZXNpZ25fc3lzdGVtCiAgLSBwYWNrYWdlcy9kaQogIC0gcGFja2FnZXMvbG9jYWxpemF0aW9uCiAgLSBwYWNrYWdlcy91dGlscwp7eyNuZXR3b3JrfX0gIC0gcGFja2FnZXMvbmV0d29yawp7ey9uZXR3b3JrfX17eyNub3RpZmljYXRpb25zfX0gIC0gcGFja2FnZXMvbm90aWZpY2F0aW9ucwp7ey9ub3RpZmljYXRpb25zfX17eyNhbmFseXRpY3N9fSAgLSBwYWNrYWdlcy9hbmFseXRpY3MKe3svYW5hbHl0aWNzfX17eyNjcmFzaGx5dGljc319ICAtIHBhY2thZ2VzL2NyYXNobHl0aWNzCnt7L2NyYXNobHl0aWNzfX17eyNmZWF0dXJlX2ZsYWdzfX0gIC0gcGFja2FnZXMvZmVhdHVyZV9mbGFncwp7ey9mZWF0dXJlX2ZsYWdzfX17eyNkZXZlbG9wZXJ9fSAgLSBwYWNrYWdlcy9kZXZlbG9wZXIKe3svZGV2ZWxvcGVyfX17eyNhdXRofX0gIC0gZmVhdHVyZXMvYXV0aAp7ey9hdXRofX17eyNkYXNoYm9hcmR9fSAgLSBmZWF0dXJlcy9kYXNoYm9hcmQKe3svZGFzaGJvYXJkfX0KbWVsb3M6CiAgc2NyaXB0czoKICAgIGNsZWFuOmZsdXR0ZXI6CiAgICAgIGRlc2NyaXB0aW9uOiAiUnVuIGZsdXR0ZXIgY2xlYW4gaW4gYWxsIEZsdXR0ZXIgcGFja2FnZXMiCiAgICAgIGV4ZWM6CiAgICAgICAgY29tbWFuZDogZmx1dHRlciBjbGVhbgogICAgICAgIGNvbmN1cnJlbmN5OiA0CiAgICAgIHBhY2thZ2VGaWx0ZXJzOgogICAgICAgIGZsdXR0ZXI6IHRydWUKCiAgICBsaW50OgogICAgICBkZXNjcmlwdGlvbjogIkNoZWNrIGZvcm1hdHRpbmcgYW5kIGFuYWx5emUgYWxsIHdvcmtzcGFjZSBjb2RlIgogICAgICBydW46IGRhcnQgcnVuIHRvb2wvY2hlY2suZGFydAoKICAgIGZpeDoKICAgICAgZGVzY3JpcHRpb246ICJBcHBseSBEYXJ0IGF1dG9tYXRlZCBmaXhlcyB0byBhbGwgcGFja2FnZXMiCiAgICAgIGV4ZWM6CiAgICAgICAgY29tbWFuZDogZGFydCBmaXggLS1hcHBseQogICAgICAgIGNvbmN1cnJlbmN5OiA1CiAgICAgIHBhY2thZ2VGaWx0ZXJzOgogICAgICAgIGRpckV4aXN0czogbGliCgogICAgZml4OmRyeToKICAgICAgZGVzY3JpcHRpb246ICJTaG93IERhcnQgYXV0b21hdGVkIGZpeGVzIHdpdGhvdXQgYXBwbHlpbmcgdGhlbSIKICAgICAgZXhlYzoKICAgICAgICBjb21tYW5kOiBkYXJ0IGZpeCAtLWRyeS1ydW4KICAgICAgICBjb25jdXJyZW5jeTogNQogICAgICBwYWNrYWdlRmlsdGVyczoKICAgICAgICBkaXJFeGlzdHM6IGxpYgoKICAgIGdlbmVyYXRlOgogICAgICBkZXNjcmlwdGlvbjogIlJ1biBidWlsZF9ydW5uZXIgaW4gZXZlcnkgcGFja2FnZSB0aGF0IG5lZWRzIGNvZGVnZW4iCiAgICAgIGV4ZWM6CiAgICAgICAgY29tbWFuZDogZGFydCBydW4gYnVpbGRfcnVubmVyIGJ1aWxkIC0tZGVsZXRlLWNvbmZsaWN0aW5nLW91dHB1dHMKICAgICAgICBjb25jdXJyZW5jeTogMQogICAgICBwYWNrYWdlRmlsdGVyczoKICAgICAgICBkZXBlbmRzT246IGJ1aWxkX3J1bm5lcgoKICAgIGdlbmVyYXRlOmk2OW46CiAgICAgIGRlc2NyaXB0aW9uOiAiUmVnZW5lcmF0ZSBsb2NhbGl6YXRpb24gc3RyaW5ncyBmcm9tIG1lc3NhZ2VzLmk2OW4ueWFtbCIKICAgICAgZXhlYzoKICAgICAgICBjb21tYW5kOiB8CiAgICAgICAgICBkYXJ0IHJ1biBidWlsZF9ydW5uZXIgYnVpbGQgLS1kZWxldGUtY29uZmxpY3Rpbmctb3V0cHV0cyAmJgogICAgICAgICAgZGFydCBmb3JtYXQgbGliL21lc3NhZ2VzLmk2OW4uZGFydAogICAgICAgIGNvbmN1cnJlbmN5OiAxCiAgICAgIHBhY2thZ2VGaWx0ZXJzOgogICAgICAgIHNjb3BlOiAibG9jYWxpemF0aW9uIgoKICAgIHRlc3Q6CiAgICAgIGRlc2NyaXB0aW9uOiAiUnVuIGFsbCB0ZXN0cyAoRGFydCBhbmQgRmx1dHRlcikiCiAgICAgIHJ1bjogZGFydCBydW4gbWVsb3MgcnVuIHRlc3Q6c2VsZWN0IC0tbm8tc2VsZWN0CgogICAgY292ZXJhZ2U6CiAgICAgIGRlc2NyaXB0aW9uOiAiUnVuIGV2ZXJ5IHRlc3QgYW5kIHJlcXVpcmUgMTAwJSBoYW5kd3JpdHRlbiBsaW5lIGNvdmVyYWdlIgogICAgICBydW46IGRhcnQgcnVuIHRvb2wvY292ZXJhZ2UuZGFydAoKICAgIHRlc3Q6c2VsZWN0OgogICAgICBkZXNjcmlwdGlvbjogIlJ1biB0ZXN0cyBpbiBGbHV0dGVyIHBhY2thZ2VzIgogICAgICBleGVjOgogICAgICAgIGNvbW1hbmQ6IGZsdXR0ZXIgdGVzdCAtLWNvdmVyYWdlCiAgICAgICAgY29uY3VycmVuY3k6IDUKICAgICAgICBmYWlsRmFzdDogdHJ1ZQogICAgICBwYWNrYWdlRmlsdGVyczoKICAgICAgICBkaXJFeGlzdHM6IHRlc3QKCiAgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogICMgSURFIENPTkZJR1VSQVRJT04KICAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiAgaWRlOgogICAgaW50ZWxsaWo6CiAgICAgIGVuYWJsZWQ6IHRydWUKICAgICAgbW9kdWxlTmFtZVByZWZpeDogJycK", "type": "text" }, { "path": "{{ name.snakeCase() }}/README.md", "data": - "IyB7eyBuYW1lLnRpdGxlQ2FzZSgpIH19Cgp7e3tkZXNjcmlwdGlvbn19fQoKWyFbbm9uc3RvcF9jbGldKGh0dHBzOi8vaW1nLnNoaWVsZHMuaW8vYmFkZ2Uvc3RhcnRlZCUyMHdpdGgtbm9uc3RvcF9jbGktMTY2QzRFLnN2Zz9zdHlsZT1mbGF0LXNxdWFyZSldKGh0dHBzOi8vcHViLmRldi9wYWNrYWdlcy9ub25zdG9wX2NsaSkKWyFbbWVsb3NdKGh0dHBzOi8vaW1nLnNoaWVsZHMuaW8vYmFkZ2UvbWFpbnRhaW5lZCUyMHdpdGgtbWVsb3MtZjcwMGZmLnN2Zz9zdHlsZT1mbGF0LXNxdWFyZSldKGh0dHBzOi8vZ2l0aHViLmNvbS9pbnZlcnRhc2UvbWVsb3MpCgojIyBMYXlvdXQKCmBgYAphcHBzL3t7bmFtZS5zbmFrZUNhc2UoKX19ICAgICAgdGhlIGFwcGxpY2F0aW9uIHNoZWxsOiBib290c3RyYXAsIHJvdXRlciwgc3BsYXNoCmZlYXR1cmVzLyAgICAgICAgICAgICAgICAgICAgICB2ZXJ0aWNhbCBzbGljZXMgKHVpICsgc3RhdGUgKyBkYXRhKQpwYWNrYWdlcy8gICAgICAgICAgICAgICAgICAgICAgc2hhcmVkIGNhcGFiaWxpdGllcwpwbHVnaW5zLyAgICAgICAgICAgICAgICAgICAgICAgbmF0aXZlIGludGVncmF0aW9ucwpgYGAKCkRlcGVuZGVuY2llcyBwb2ludCBpbndhcmQ6IGFwcHMgZGVwZW5kIG9uIGZlYXR1cmVzLCBmZWF0dXJlcyBkZXBlbmQgb24KcGFja2FnZXMsIHBhY2thZ2VzIGRlcGVuZCBvbiBub3RoaW5nIGFib3ZlIHRoZW0uCgojIyMgV2hhdCBpcyBhbHJlYWR5IHdpcmVkCgp8IE1vZHVsZSB8IFJvbGUgfAp8IC0tLSB8IC0tLSB8CnwgYHBhY2thZ2VzL2NvcmVgIHwgbG9nZ2VyLCBESSBib290c3RyYXAsIHJvdXRlIG9ic2VydmVycywgZXJyb3IgdHlwZXMsIEJMb0Mgb2JzZXJ2ZXIgfAp8IGBwYWNrYWdlcy9kaWAgfCBzZXJ2aWNlIGxvY2F0b3IgKEdldEl0KSBiZWhpbmQgYSBzbWFsbCBpbnRlcmZhY2UgfAp8IGBwYWNrYWdlcy9kZXNpZ25fc3lzdGVtYCB8IE1hdGVyaWFsIDMgdGhlbWUsIHNoYXJlZCBjb21wb25lbnRzLCB0b2FzdHMsIGxvYWRlcnMgfAp8IGBwYWNrYWdlcy9sb2NhbGl6YXRpb25gIHwgdHlwZS1zYWZlIHN0cmluZ3MgZ2VuZXJhdGVkIGZyb20gWUFNTCAoaTY5bikgfAp8IGBwYWNrYWdlcy91dGlsc2AgfCBkZXBlbmRlbmN5LWxpZ2h0IGhlbHBlcnMgfAp7eyNuZXR3b3JrfX18IGBwYWNrYWdlcy9uZXR3b3JrYCB8IERpbyBjbGllbnQsIGF1dGggKyBsb2dnaW5nIGludGVyY2VwdG9ycywgdHlwZWQgZXJyb3JzIHwKe3svbmV0d29ya319e3sjYW5hbHl0aWNzfX18IGBwYWNrYWdlcy9hbmFseXRpY3NgIHwgZXZlbnQgdHJhY2tpbmcgYmVoaW5kIGEgc3dhcHBhYmxlIGNsaWVudCB8Cnt7L2FuYWx5dGljc319e3sjY3Jhc2hseXRpY3N9fXwgYHBhY2thZ2VzL2NyYXNobHl0aWNzYCB8IGNyYXNoIGFuZCBub24tZmF0YWwgcmVwb3J0aW5nIHwKe3svY3Jhc2hseXRpY3N9fXt7I25vdGlmaWNhdGlvbnN9fXwgYHBhY2thZ2VzL25vdGlmaWNhdGlvbnNgIHwgRkNNLCBwZXJtaXNzaW9ucywgZGV2aWNlLXRva2VuIHJlZ2lzdHJhdGlvbiB8Cnt7L25vdGlmaWNhdGlvbnN9fXt7I2ZlYXR1cmVfZmxhZ3N9fXwgYHBhY2thZ2VzL2ZlYXR1cmVfZmxhZ3NgIHwgUmVtb3RlIENvbmZpZyBmbGFncyB3aXRoIGEgd2lkZ2V0IHdyYXBwZXIgfAp7ey9mZWF0dXJlX2ZsYWdzfX17eyNkZXZlbG9wZXJ9fXwgYHBhY2thZ2VzL2RldmVsb3BlcmAgfCBpbi1hcHAgZGV2IHRvb2xzLCByZWFjaGFibGUgYnkgYSBoaWRkZW4gNS10YXAgZ2VzdHVyZSB8Cnt7L2RldmVsb3Blcn19e3sjYXV0aH19fCBgZmVhdHVyZXMvYXV0aGAgfCBGaXJlYmFzZSBBdXRoIChlbWFpbCwgR29vZ2xlLCBBcHBsZSkgKyByb3V0ZSBndWFyZHMgfAp7ey9hdXRofX17eyNkYXNoYm9hcmR9fXwgYGZlYXR1cmVzL2Rhc2hib2FyZGAgfCBib3R0b20tbmF2aWdhdGlvbiBzaGVsbCB3aXRoIHN0YXJ0ZXIgdGFicyB8Cnt7L2Rhc2hib2FyZH19CgojIyBHZXR0aW5nIHN0YXJ0ZWQKCmBgYHNoCmRhcnQgcHViIGdldApkYXJ0IHJ1biBtZWxvcyBib290c3RyYXAKYGBgCnt7I2ZpcmViYXNlfX0KIyMjIEZpcmViYXNlCgpUaGlzIHByb2plY3QgdXNlcyBGaXJlYmFzZSwgc28gcnVuIHRoaXMgYmVmb3JlIHRob3NlIGZlYXR1cmVzIHdpbGwgd29yay4KVGhlIHJlc3Qgb2YgdGhlIGFwcCBzdGlsbCBib290cyB3aXRob3V0IGl0OgoKYGBgc2gKZGFydCBwdWIgZ2xvYmFsIGFjdGl2YXRlIGZsdXR0ZXJmaXJlX2NsaQpjZCBhcHBzL3t7bmFtZS5zbmFrZUNhc2UoKX19CmZsdXR0ZXJmaXJlIGNvbmZpZ3VyZQpgYGAKe3svZmlyZWJhc2V9fQojIyMgUnVuCgpgYGBzaApjZCBhcHBzL3t7bmFtZS5zbmFrZUNhc2UoKX19CmZsdXR0ZXIgcnVuIC0tZGFydC1kZWZpbmU9QkFTRV9VUkw9aHR0cHM6Ly9hcGkuZXhhbXBsZS5jb20KYGBgCgpFdmVyeSBlbnZpcm9ubWVudCB2YWx1ZSBpcyBhIGAtLWRhcnQtZGVmaW5lYCByZWFkIGluCmBwYWNrYWdlcy9jb3JlL2xpYi9jb25zdGFudHMvZW52aXJvbm1lbnQuZGFydGAuIEFkZCBuZXcgb25lcyB0aGVyZSByYXRoZXIgdGhhbgpzY2F0dGVyaW5nIGBTdHJpbmcuZnJvbUVudmlyb25tZW50YCBhcm91bmQgdGhlIGNvZGViYXNlLgp7eyNmaXJlYmFzZX19ClRvIHJ1biBhZ2FpbnN0IHRoZSBGaXJlYmFzZSBlbXVsYXRvciBzdWl0ZSwgYWRkCmAtLWRhcnQtZGVmaW5lPVVTRV9FTVVMQVRPUlM9dHJ1ZWAuCnt7L2ZpcmViYXNlfX0KIyMgRGF5LXRvLWRheQoKYGBgc2gKZGFydCBydW4gbWVsb3MgcnVuIGxpbnQgICAgICAgIyBmb3JtYXQtY2hlY2sgYW5kIGFuYWx5emUKZGFydCBydW4gbWVsb3MgcnVuIHRlc3QgICAgICAgIyBydW4gZXZlcnkgcGFja2FnZSdzIHRlc3RzCmRhcnQgcnVuIG1lbG9zIHJ1biBjb3ZlcmFnZSAgICMgdGVzdHMgKyBzdHJpY3QgMTAwJSBsaW5lLWNvdmVyYWdlIGdhdGUKZGFydCBydW4gbWVsb3MgcnVuIGdlbmVyYXRlICAgIyByZWJ1aWxkIHNlcmlhbGl6ZXJzCmRhcnQgcnVuIG1lbG9zIHJ1biBnZW5lcmF0ZTppNjluICAjIHJlYnVpbGQgbG9jYWxpemF0aW9uCmBgYAoKIyMgQWRkaW5nIHRvIHRoZSBtb25vcmVwbwoKYGBgc2gKbm9uc3RvcCBjcmVhdGUgbXlfZmVhdHVyZSAtLXRlbXBsYXRlIHBhY2thZ2UgLW8gZmVhdHVyZXMKbm9uc3RvcCBjcmVhdGUgbXlfcGFja2FnZSAtLXRlbXBsYXRlIHBhY2thZ2UgLW8gcGFja2FnZXMKbm9uc3RvcCBjcmVhdGUgbXlfc2Vjb25kX2FwcCAtLXRlbXBsYXRlIGFwcCAtbyBhcHBzCm5vbnN0b3AgY3JlYXRlIG15X3BsdWdpbiAtLXRlbXBsYXRlIHBsdWdpbiAtbyBwbHVnaW5zCmBgYAoKIyMgV2hlcmUgdG8gc3RhcnQKCjEuIFJlcGxhY2UgdGhlIHt7I2Rhc2hib2FyZH19cGxhY2Vob2xkZXIgdGFicyBpbiBgZmVhdHVyZXMvZGFzaGJvYXJkYHt7L2Rhc2hib2FyZH19e3teZGFzaGJvYXJkfX1wbGFjZWhvbGRlciBob21lIHNjcmVlbiBpbiB0aGUgYXBwIHJvdXRlcnt7L2Rhc2hib2FyZH19LgoyLiBQdXQgeW91ciBwcm9kdWN0IHN0cmluZ3MgaW4gYHBhY2thZ2VzL2xvY2FsaXphdGlvbi9saWIvbWVzc2FnZXMuaTY5bi55YW1sYC4KMy4gU2V0IHRoZSBwYWxldHRlIGluIGBwYWNrYWdlcy9kZXNpZ25fc3lzdGVtL2xpYi9nZW5lcmF0ZWQvdGhlbWUuZGFydGAuCjQuIEFkZCBmZWF0dXJlIG1vZHVsZXMgYW5kIHNwcmVhZCB0aGVpciByb3V0ZXMgaW50bwogICBgYXBwcy97e25hbWUuc25ha2VDYXNlKCl9fS9saWIvcm91dGVyL3JvdXRlci5kYXJ0YC4KCiMjIEFyY2hpdGVjdHVyZSBhbmQgdGVzdGluZwoKVGhlIHN0YXJ0ZXIgc2VwYXJhdGVzIHRoZSBhcHAgdmlldywgcm91dGluZywgc3RhcnR1cCBhbmQgZm9yZWdyb3VuZCBsaWZlY3ljbGUuCkNsaWVudHMgYWNjZXB0IGluamVjdGVkIFNES3MsIGxvZ2dlcnMgYW5kIHRyYW5zcG9ydHM7IGZlYXR1cmUgY29kZSBjb25zdW1lcyBzbWFsbAppbnRlcmZhY2VzIHJhdGhlciB0aGFuIHN1YmNsYXNzaW5nIHBsYXRmb3JtIFNES3MuIFNlZSBbYXJjaGl0ZWN0dXJlXShkb2NzL2FyY2hpdGVjdHVyZS5tZCkKZm9yIHRoZSBTT0xJRCBib3VuZGFyaWVzIGFuZCBhbiBleGFtcGxlIG9mIGFkZGluZyBhIHRlc3RlZCBmZWF0dXJlLgoKRXZlcnkgZ2VuZXJhdGVkIHBhY2thZ2UgaGFzIHRlc3RzLiBVbml0IGFuZCB3aWRnZXQgdGVzdHMgcnVuIHdpdGhvdXQgYSBGaXJlYmFzZQphY2NvdW50IG9yIG5ldHdvcmsgc2VydmljZTsgRmlyZWJhc2UgaW50ZWdyYXRpb24gdGVzdHMgdXNlIG9mZmxpbmUgcGxhdGZvcm0gZG91Ymxlcy4KVGhlIHN0cmljdCBnYXRlIGltcG9ydHMgb3RoZXJ3aXNlLXVubG9hZGVkIGxpYnJhcmllcywgcnVucyBhbGwgc3VpdGVzLCBtZXJnZXMgdGhlaXIKTENPViByZWNvcmRzIGFuZCByZXF1aXJlcyAqKjEwMCUgZXhlY3V0YWJsZS1saW5lIGNvdmVyYWdlIG9mIHdvcmtzcGFjZSBgbGliL2AgY29kZSoqLgpJdCBpbmNsdWRlcyBlbnRyeXBvaW50cywgc3RhcnR1cCwgdGhlbWUgYW5kIGVycm9yIHBhdGhzLiBPbmx5IGNvbXBpbGVyLWdlbmVyYXRlZApgLmcuZGFydGAsIGAuZnJlZXplZC5kYXJ0YCBhbmQgYC5pNjluLmRhcnRgIGZpbGVzIGFyZSBleGNsdWRlZC4KCmBgYHNoCmRhcnQgcnVuIHRvb2wvY292ZXJhZ2UuZGFydAojIEluc3BlY3QgZXhpc3RpbmcgcmVwb3J0cyB3aXRob3V0IHJlcnVubmluZyB0ZXN0cyAobm90IGEgdmFsaWRhdGlvbiBydW4pOgpkYXJ0IHJ1biB0b29sL2NvdmVyYWdlLmRhcnQgLS1yZXBvcnQtb25seQpgYGAKClRoZSBnYXRlIGZhaWxzIGZvciBmYWlsZWQgdGVzdHMsIG1pc3NpbmcgdGVzdHMvcmVwb3J0cywgb3IgdW5jb3ZlcmVkIGxpbmVzLgpUaGUgbWVyZ2VkIHJlcG9ydCBpcyBgY292ZXJhZ2UvbGNvdi5pbmZvYC4gRG8gbm90IHJ1biB0d28gY292ZXJhZ2Ugam9icyBpbiB0aGUKc2FtZSBjaGVja291dDogZWFjaCBvd25zIGEgdGVtcG9yYXJ5IGB0ZXN0L2NvdmVyYWdlX2ltcG9ydHNfdGVzdC5kYXJ0YCBmaXh0dXJlLApyZW1vdmVkIG9uIG5vcm1hbCBjb21wbGV0aW9uLiBBZnRlciBhbiBpbnRlcnJ1cHRlZCBydW4sIGluc3BlY3QgYW5kIHJlbW92ZSBvbmx5CnRoYXQgZ2VuZXJhdGVkIGZpeHR1cmUgYmVmb3JlIHJldHJ5aW5nLgoKQ292ZXJhZ2UgaXMgbm90IGEgcHJvb2Ygb2YgY29ycmVjdG5lc3MsIGJyYW5jaCBjb21wbGV0ZW5lc3MsIHNlY3VyaXR5LCBvciBuYXRpdmUKcGx1Z2luIGNvbXBhdGliaWxpdHkuIEtlZXAgYXNzZXJ0aW9ucyBhYm91dCBiZWhhdmlvciwgYW5kIGFkZCBkZXZpY2UgaW50ZWdyYXRpb24KdGVzdHMgZm9yIHlvdXIgRmlyZWJhc2UgcHJvamVjdCwgbmF0aXZlIHBlcm1pc3Npb25zLCBwcm92aWRlciBzaWduLWluIGFuZCBiYWNrZW5kLgpUaGUgZ2VuZXJhdGVkIEdpdEh1YiBBY3Rpb25zIHdvcmtmbG93IGNoZWNrcyBhbmFseXNpcywgdGVzdHMsIGNvdmVyYWdlIGFuZCBhIHdlYiBidWlsZC4KCnt7I2F1dGh9fSMjIyBBdXRoZW50aWNhdGlvbiBhbmQgZGVtbyBtb2RlCgpgR29BdXRoUm91dGVgIGRlbmllcyBhY2Nlc3Mgd2hlbiBhdXRoZW50aWNhdGlvbiBpcyBtaXNzaW5nIG9yIHNpZ25lZCBvdXQuCnt7I2Rhc2hib2FyZH19VGhlIHN0YXJ0ZXIgZGFzaGJvYXJkIGV4cGxpY2l0bHkgYWxsb3dzICoqdW5jb25maWd1cmVkIGRlbW8gbW9kZSoqIHNvCnlvdSBjYW4gZXhwbG9yZSBpdCBiZWZvcmUgYGZsdXR0ZXJmaXJlIGNvbmZpZ3VyZWAuIEl0IGNvbnRhaW5zIG5vIHByb3RlY3RlZCBkYXRhLgpEbyBub3QgdXNlIGBhbGxvd1VuY29uZmlndXJlZDogdHJ1ZWAgZm9yIHJlYWwgcHJvdGVjdGVkIHJvdXRlcy4Ke3svZGFzaGJvYXJkfX1DbGllbnQtc2lkZSBndWFyZHMgYXJlIG5hdmlnYXRpb24gaGVscGVyczsgZW5mb3JjZSBhdXRob3JpemF0aW9uIGluCmJhY2tlbmQgZW5kcG9pbnRzIGFuZCBGaXJlYmFzZSBzZWN1cml0eSBydWxlcy4Ke3svYXV0aH19Cnt7I25vdGlmaWNhdGlvbnN9fSMjIyBOb3RpZmljYXRpb25zCgpUaGUgU0RLIGNsaWVudCBvd25zIHN1YnNjcmlwdGlvbnMsIG5vdCBuYXZpZ2F0aW9uIG9yIHRvYXN0IHdpZGdldHMuIFRoZSBhcHAKc3VwcGxpZXMgY2FsbGJhY2tzLCBxdWV1ZXMgY29sZC1zdGFydCByb3V0ZXMgdW50aWwgdGhlIHJvdXRlciBpcyByZWFkeSwgYW5kIG93bnMKZm9yZWdyb3VuZCBsaWZlY3ljbGUgaGFuZGxpbmcuIERldmljZSBJRHMgYXJlIHJhbmRvbSBwZXIgaW5zdGFsbGF0aW9uIGFuZApwZXJzaXN0ZWQgbG9jYWxseTsgdGhleSBhcmUgbm90IGhhcmR3YXJlIGZpbmdlcnByaW50cy4KCkltcGxlbWVudCB0aGUgYFBPU1QgL2RldmljZS10b2tlbnMvbWVgIGFuZCBgREVMRVRFIC9kZXZpY2UtdG9rZW5zL21lLzpkZXZpY2VJZGAKYmFja2VuZCBlbmRwb2ludHMgYmVmb3JlIGVuYWJsaW5nIHRva2VuIHJlZ2lzdHJhdGlvbiBpbiBwcm9kdWN0aW9uLiBDb25maWd1cmUKbmF0aXZlIGNhcGFiaWxpdGllcywgRmlyZWJhc2UgbWVzc2FnaW5nIGFuZCB5b3VyIGFwcCdzIHBlcm1pc3Npb24gZXhwbGFuYXRpb24uCnt7L25vdGlmaWNhdGlvbnN9fQo=", + "IyB7eyBuYW1lLnRpdGxlQ2FzZSgpIH19Cgp7e3tkZXNjcmlwdGlvbn19fQoKWyFbbm9uc3RvcF9jbGldKGh0dHBzOi8vaW1nLnNoaWVsZHMuaW8vYmFkZ2Uvc3RhcnRlZCUyMHdpdGgtbm9uc3RvcF9jbGktMTY2QzRFLnN2Zz9zdHlsZT1mbGF0LXNxdWFyZSldKGh0dHBzOi8vcHViLmRldi9wYWNrYWdlcy9ub25zdG9wX2NsaSkKWyFbbWVsb3NdKGh0dHBzOi8vaW1nLnNoaWVsZHMuaW8vYmFkZ2UvbWFpbnRhaW5lZCUyMHdpdGgtbWVsb3MtZjcwMGZmLnN2Zz9zdHlsZT1mbGF0LXNxdWFyZSldKGh0dHBzOi8vZ2l0aHViLmNvbS9pbnZlcnRhc2UvbWVsb3MpCgojIyBMYXlvdXQKCmBgYAphcHBzL3t7bmFtZS5zbmFrZUNhc2UoKX19ICAgICAgdGhlIGFwcGxpY2F0aW9uIHNoZWxsOiBib290c3RyYXAsIHJvdXRlciwgc3BsYXNoCmZlYXR1cmVzLyAgICAgICAgICAgICAgICAgICAgICB2ZXJ0aWNhbCBzbGljZXMgKHVpICsgc3RhdGUgKyBkYXRhKQpwYWNrYWdlcy8gICAgICAgICAgICAgICAgICAgICAgc2hhcmVkIGNhcGFiaWxpdGllcwpwbHVnaW5zLyAgICAgICAgICAgICAgICAgICAgICAgbmF0aXZlIGludGVncmF0aW9ucwpgYGAKCkRlcGVuZGVuY2llcyBwb2ludCBpbndhcmQ6IGFwcHMgZGVwZW5kIG9uIGZlYXR1cmVzLCBmZWF0dXJlcyBkZXBlbmQgb24KcGFja2FnZXMsIHBhY2thZ2VzIGRlcGVuZCBvbiBub3RoaW5nIGFib3ZlIHRoZW0uCgojIyMgV2hhdCBpcyBhbHJlYWR5IHdpcmVkCgp8IE1vZHVsZSB8IFJvbGUgfAp8IC0tLSB8IC0tLSB8CnwgYHBhY2thZ2VzL2NvcmVgIHwgbG9nZ2VyLCBESSBib290c3RyYXAsIHJvdXRlIG9ic2VydmVycywgZXJyb3IgdHlwZXMsIEJMb0Mgb2JzZXJ2ZXIgfAp8IGBwYWNrYWdlcy9kaWAgfCBzZXJ2aWNlIGxvY2F0b3IgKEdldEl0KSBiZWhpbmQgYSBzbWFsbCBpbnRlcmZhY2UgfAp8IGBwYWNrYWdlcy9kZXNpZ25fc3lzdGVtYCB8IE1hdGVyaWFsIDMgdGhlbWUsIHNoYXJlZCBjb21wb25lbnRzLCB0b2FzdHMsIGxvYWRlcnMgfAp8IGBwYWNrYWdlcy9sb2NhbGl6YXRpb25gIHwgdHlwZS1zYWZlIHN0cmluZ3MgZ2VuZXJhdGVkIGZyb20gWUFNTCAoaTY5bikgfAp8IGBwYWNrYWdlcy91dGlsc2AgfCBkZXBlbmRlbmN5LWxpZ2h0IGhlbHBlcnMgfAp7eyNuZXR3b3JrfX18IGBwYWNrYWdlcy9uZXR3b3JrYCB8IERpbyBjbGllbnQsIGF1dGggKyBsb2dnaW5nIGludGVyY2VwdG9ycywgdHlwZWQgZXJyb3JzIHwKe3svbmV0d29ya319e3sjYW5hbHl0aWNzfX18IGBwYWNrYWdlcy9hbmFseXRpY3NgIHwgZXZlbnQgdHJhY2tpbmcgYmVoaW5kIGEgc3dhcHBhYmxlIGNsaWVudCB8Cnt7L2FuYWx5dGljc319e3sjY3Jhc2hseXRpY3N9fXwgYHBhY2thZ2VzL2NyYXNobHl0aWNzYCB8IGNyYXNoIGFuZCBub24tZmF0YWwgcmVwb3J0aW5nIHwKe3svY3Jhc2hseXRpY3N9fXt7I25vdGlmaWNhdGlvbnN9fXwgYHBhY2thZ2VzL25vdGlmaWNhdGlvbnNgIHwgRkNNLCBwZXJtaXNzaW9ucywgZGV2aWNlLXRva2VuIHJlZ2lzdHJhdGlvbiB8Cnt7L25vdGlmaWNhdGlvbnN9fXt7I2ZlYXR1cmVfZmxhZ3N9fXwgYHBhY2thZ2VzL2ZlYXR1cmVfZmxhZ3NgIHwgUmVtb3RlIENvbmZpZyBmbGFncyB3aXRoIGEgd2lkZ2V0IHdyYXBwZXIgfAp7ey9mZWF0dXJlX2ZsYWdzfX17eyNkZXZlbG9wZXJ9fXwgYHBhY2thZ2VzL2RldmVsb3BlcmAgfCBpbi1hcHAgZGV2IHRvb2xzLCByZWFjaGFibGUgYnkgYSBoaWRkZW4gNS10YXAgZ2VzdHVyZSB8Cnt7L2RldmVsb3Blcn19e3sjYXV0aH19fCBgZmVhdHVyZXMvYXV0aGAgfCBGaXJlYmFzZSBBdXRoIChlbWFpbCwgR29vZ2xlLCBBcHBsZSkgKyByb3V0ZSBndWFyZHMgfAp7ey9hdXRofX17eyNkYXNoYm9hcmR9fXwgYGZlYXR1cmVzL2Rhc2hib2FyZGAgfCBib3R0b20tbmF2aWdhdGlvbiBzaGVsbCB3aXRoIHN0YXJ0ZXIgdGFicyB8Cnt7L2Rhc2hib2FyZH19CgojIyBHZXR0aW5nIHN0YXJ0ZWQKClVzZSBGbHV0dGVyIDMuNDcuNSBvciBuZXdlci4gVGhlIHJvb3QgYHB1YnNwZWMueWFtbGAgZGVjbGFyZXMgdGhlIG5hdGl2ZSBEYXJ0CndvcmtzcGFjZSBhbmQgTWVsb3Mgc2NyaXB0czsgbWVtYmVyIHBhY2thZ2VzIHNoYXJlIGEgc2luZ2xlIGRlcGVuZGVuY3kgbG9ja2ZpbGUuCgpgYGBzaApkYXJ0IHB1YiBnZXQKZGFydCBydW4gbWVsb3MgYm9vdHN0cmFwCmBgYAp7eyNmaXJlYmFzZX19CiMjIyBGaXJlYmFzZQoKVGhpcyBwcm9qZWN0IHVzZXMgRmlyZWJhc2UsIHNvIHJ1biB0aGlzIGJlZm9yZSB0aG9zZSBmZWF0dXJlcyB3aWxsIHdvcmsuClRoZSByZXN0IG9mIHRoZSBhcHAgc3RpbGwgYm9vdHMgd2l0aG91dCBpdDoKCmBgYHNoCmRhcnQgcHViIGdsb2JhbCBhY3RpdmF0ZSBmbHV0dGVyZmlyZV9jbGkKY2QgYXBwcy97e25hbWUuc25ha2VDYXNlKCl9fQpmbHV0dGVyZmlyZSBjb25maWd1cmUKYGBgCnt7L2ZpcmViYXNlfX0KIyMjIFJ1bgoKYGBgc2gKY2QgYXBwcy97e25hbWUuc25ha2VDYXNlKCl9fQpmbHV0dGVyIHJ1biAtLWRhcnQtZGVmaW5lPUJBU0VfVVJMPWh0dHBzOi8vYXBpLmV4YW1wbGUuY29tCmBgYAoKRXZlcnkgZW52aXJvbm1lbnQgdmFsdWUgaXMgYSBgLS1kYXJ0LWRlZmluZWAgcmVhZCBpbgpgcGFja2FnZXMvY29yZS9saWIvY29uc3RhbnRzL2Vudmlyb25tZW50LmRhcnRgLiBBZGQgbmV3IG9uZXMgdGhlcmUgcmF0aGVyIHRoYW4Kc2NhdHRlcmluZyBgU3RyaW5nLmZyb21FbnZpcm9ubWVudGAgYXJvdW5kIHRoZSBjb2RlYmFzZS4Ke3sjZmlyZWJhc2V9fQpUbyBydW4gYWdhaW5zdCB0aGUgRmlyZWJhc2UgZW11bGF0b3Igc3VpdGUsIGFkZApgLS1kYXJ0LWRlZmluZT1VU0VfRU1VTEFUT1JTPXRydWVgLgp7ey9maXJlYmFzZX19CiMjIERheS10by1kYXkKCmBgYHNoCmRhcnQgcnVuIG1lbG9zIHJ1biBsaW50ICAgICAgICMgZm9ybWF0LWNoZWNrIGFuZCBhbmFseXplCmRhcnQgcnVuIG1lbG9zIHJ1biB0ZXN0ICAgICAgICMgcnVuIGV2ZXJ5IHBhY2thZ2UncyB0ZXN0cwpkYXJ0IHJ1biBtZWxvcyBydW4gY292ZXJhZ2UgICAjIHRlc3RzICsgc3RyaWN0IDEwMCUgbGluZS1jb3ZlcmFnZSBnYXRlCmRhcnQgcnVuIG1lbG9zIHJ1biBnZW5lcmF0ZSAgICMgcmVidWlsZCBzZXJpYWxpemVycwpkYXJ0IHJ1biBtZWxvcyBydW4gZ2VuZXJhdGU6aTY5biAgIyByZWJ1aWxkIGxvY2FsaXphdGlvbgpgYGAKCiMjIEFkZGluZyB0byB0aGUgbW9ub3JlcG8KCmBgYHNoCm5vbnN0b3AgY3JlYXRlIG15X2ZlYXR1cmUgLS10ZW1wbGF0ZSBwYWNrYWdlIC1vIGZlYXR1cmVzCm5vbnN0b3AgY3JlYXRlIG15X3BhY2thZ2UgLS10ZW1wbGF0ZSBwYWNrYWdlIC1vIHBhY2thZ2VzCm5vbnN0b3AgY3JlYXRlIG15X3NlY29uZF9hcHAgLS10ZW1wbGF0ZSBhcHAgLW8gYXBwcwpub25zdG9wIGNyZWF0ZSBteV9wbHVnaW4gLS10ZW1wbGF0ZSBwbHVnaW4gLW8gcGx1Z2lucwpgYGAKCiMjIFdoZXJlIHRvIHN0YXJ0CgoxLiBSZXBsYWNlIHRoZSB7eyNkYXNoYm9hcmR9fXBsYWNlaG9sZGVyIHRhYnMgaW4gYGZlYXR1cmVzL2Rhc2hib2FyZGB7ey9kYXNoYm9hcmR9fXt7XmRhc2hib2FyZH19cGxhY2Vob2xkZXIgaG9tZSBzY3JlZW4gaW4gdGhlIGFwcCByb3V0ZXJ7ey9kYXNoYm9hcmR9fS4KMi4gUHV0IHlvdXIgcHJvZHVjdCBzdHJpbmdzIGluIGBwYWNrYWdlcy9sb2NhbGl6YXRpb24vbGliL21lc3NhZ2VzLmk2OW4ueWFtbGAuCjMuIFNldCB0aGUgcGFsZXR0ZSBpbiBgcGFja2FnZXMvZGVzaWduX3N5c3RlbS9saWIvZ2VuZXJhdGVkL3RoZW1lLmRhcnRgLgo0LiBBZGQgZmVhdHVyZSBtb2R1bGVzIGFuZCBzcHJlYWQgdGhlaXIgcm91dGVzIGludG8KICAgYGFwcHMve3tuYW1lLnNuYWtlQ2FzZSgpfX0vbGliL3JvdXRlci9yb3V0ZXIuZGFydGAuCgojIyBBcmNoaXRlY3R1cmUgYW5kIHRlc3RpbmcKClRoZSBzdGFydGVyIHNlcGFyYXRlcyB0aGUgYXBwIHZpZXcsIHJvdXRpbmcsIHN0YXJ0dXAgYW5kIGZvcmVncm91bmQgbGlmZWN5Y2xlLgpDbGllbnRzIGFjY2VwdCBpbmplY3RlZCBTREtzLCBsb2dnZXJzIGFuZCB0cmFuc3BvcnRzOyBmZWF0dXJlIGNvZGUgY29uc3VtZXMgc21hbGwKaW50ZXJmYWNlcyByYXRoZXIgdGhhbiBzdWJjbGFzc2luZyBwbGF0Zm9ybSBTREtzLiBTZWUgW2FyY2hpdGVjdHVyZV0oZG9jcy9hcmNoaXRlY3R1cmUubWQpCmZvciB0aGUgU09MSUQgYm91bmRhcmllcyBhbmQgYW4gZXhhbXBsZSBvZiBhZGRpbmcgYSB0ZXN0ZWQgZmVhdHVyZS4KCkV2ZXJ5IGdlbmVyYXRlZCBwYWNrYWdlIGhhcyB0ZXN0cy4gVW5pdCBhbmQgd2lkZ2V0IHRlc3RzIHJ1biB3aXRob3V0IGEgRmlyZWJhc2UKYWNjb3VudCBvciBuZXR3b3JrIHNlcnZpY2U7IEZpcmViYXNlIGludGVncmF0aW9uIHRlc3RzIHVzZSBvZmZsaW5lIHBsYXRmb3JtIGRvdWJsZXMuClRoZSBzdHJpY3QgZ2F0ZSBpbXBvcnRzIG90aGVyd2lzZS11bmxvYWRlZCBsaWJyYXJpZXMsIHJ1bnMgYWxsIHN1aXRlcywgbWVyZ2VzIHRoZWlyCkxDT1YgcmVjb3JkcyBhbmQgcmVxdWlyZXMgKioxMDAlIGV4ZWN1dGFibGUtbGluZSBjb3ZlcmFnZSBvZiB3b3Jrc3BhY2UgYGxpYi9gIGNvZGUqKi4KSXQgaW5jbHVkZXMgZW50cnlwb2ludHMsIHN0YXJ0dXAsIHRoZW1lIGFuZCBlcnJvciBwYXRocy4gT25seSBjb21waWxlci1nZW5lcmF0ZWQKYC5nLmRhcnRgLCBgLmZyZWV6ZWQuZGFydGAgYW5kIGAuaTY5bi5kYXJ0YCBmaWxlcyBhcmUgZXhjbHVkZWQuCgpgYGBzaApkYXJ0IHJ1biB0b29sL2NvdmVyYWdlLmRhcnQKIyBJbnNwZWN0IGV4aXN0aW5nIHJlcG9ydHMgd2l0aG91dCByZXJ1bm5pbmcgdGVzdHMgKG5vdCBhIHZhbGlkYXRpb24gcnVuKToKZGFydCBydW4gdG9vbC9jb3ZlcmFnZS5kYXJ0IC0tcmVwb3J0LW9ubHkKYGBgCgpUaGUgZ2F0ZSBmYWlscyBmb3IgZmFpbGVkIHRlc3RzLCBtaXNzaW5nIHRlc3RzL3JlcG9ydHMsIG9yIHVuY292ZXJlZCBsaW5lcy4KVGhlIG1lcmdlZCByZXBvcnQgaXMgYGNvdmVyYWdlL2xjb3YuaW5mb2AuIERvIG5vdCBydW4gdHdvIGNvdmVyYWdlIGpvYnMgaW4gdGhlCnNhbWUgY2hlY2tvdXQ6IGVhY2ggb3ducyBhIHRlbXBvcmFyeSBgdGVzdC9jb3ZlcmFnZV9pbXBvcnRzX3Rlc3QuZGFydGAgZml4dHVyZSwKcmVtb3ZlZCBvbiBub3JtYWwgY29tcGxldGlvbi4gQWZ0ZXIgYW4gaW50ZXJydXB0ZWQgcnVuLCBpbnNwZWN0IGFuZCByZW1vdmUgb25seQp0aGF0IGdlbmVyYXRlZCBmaXh0dXJlIGJlZm9yZSByZXRyeWluZy4KCkNvdmVyYWdlIGlzIG5vdCBhIHByb29mIG9mIGNvcnJlY3RuZXNzLCBicmFuY2ggY29tcGxldGVuZXNzLCBzZWN1cml0eSwgb3IgbmF0aXZlCnBsdWdpbiBjb21wYXRpYmlsaXR5LiBLZWVwIGFzc2VydGlvbnMgYWJvdXQgYmVoYXZpb3IsIGFuZCBhZGQgZGV2aWNlIGludGVncmF0aW9uCnRlc3RzIGZvciB5b3VyIEZpcmViYXNlIHByb2plY3QsIG5hdGl2ZSBwZXJtaXNzaW9ucywgcHJvdmlkZXIgc2lnbi1pbiBhbmQgYmFja2VuZC4KVGhlIGdlbmVyYXRlZCBHaXRIdWIgQWN0aW9ucyB3b3JrZmxvdyBjaGVja3MgYW5hbHlzaXMsIHRlc3RzLCBjb3ZlcmFnZSBhbmQgYSB3ZWIgYnVpbGQuCgp7eyNhdXRofX0jIyMgQXV0aGVudGljYXRpb24gYW5kIGRlbW8gbW9kZQoKYEdvQXV0aFJvdXRlYCBkZW5pZXMgYWNjZXNzIHdoZW4gYXV0aGVudGljYXRpb24gaXMgbWlzc2luZyBvciBzaWduZWQgb3V0Lgp7eyNkYXNoYm9hcmR9fVRoZSBzdGFydGVyIGRhc2hib2FyZCBleHBsaWNpdGx5IGFsbG93cyAqKnVuY29uZmlndXJlZCBkZW1vIG1vZGUqKiBzbwp5b3UgY2FuIGV4cGxvcmUgaXQgYmVmb3JlIGBmbHV0dGVyZmlyZSBjb25maWd1cmVgLiBJdCBjb250YWlucyBubyBwcm90ZWN0ZWQgZGF0YS4KRG8gbm90IHVzZSBgYWxsb3dVbmNvbmZpZ3VyZWQ6IHRydWVgIGZvciByZWFsIHByb3RlY3RlZCByb3V0ZXMuCnt7L2Rhc2hib2FyZH19Q2xpZW50LXNpZGUgZ3VhcmRzIGFyZSBuYXZpZ2F0aW9uIGhlbHBlcnM7IGVuZm9yY2UgYXV0aG9yaXphdGlvbiBpbgpiYWNrZW5kIGVuZHBvaW50cyBhbmQgRmlyZWJhc2Ugc2VjdXJpdHkgcnVsZXMuCnt7L2F1dGh9fQp7eyNub3RpZmljYXRpb25zfX0jIyMgTm90aWZpY2F0aW9ucwoKVGhlIFNESyBjbGllbnQgb3ducyBzdWJzY3JpcHRpb25zLCBub3QgbmF2aWdhdGlvbiBvciB0b2FzdCB3aWRnZXRzLiBUaGUgYXBwCnN1cHBsaWVzIGNhbGxiYWNrcywgcXVldWVzIGNvbGQtc3RhcnQgcm91dGVzIHVudGlsIHRoZSByb3V0ZXIgaXMgcmVhZHksIGFuZCBvd25zCmZvcmVncm91bmQgbGlmZWN5Y2xlIGhhbmRsaW5nLiBEZXZpY2UgSURzIGFyZSByYW5kb20gcGVyIGluc3RhbGxhdGlvbiBhbmQKcGVyc2lzdGVkIGxvY2FsbHk7IHRoZXkgYXJlIG5vdCBoYXJkd2FyZSBmaW5nZXJwcmludHMuCgpJbXBsZW1lbnQgdGhlIGBQT1NUIC9kZXZpY2UtdG9rZW5zL21lYCBhbmQgYERFTEVURSAvZGV2aWNlLXRva2Vucy9tZS86ZGV2aWNlSWRgCmJhY2tlbmQgZW5kcG9pbnRzIGJlZm9yZSBlbmFibGluZyB0b2tlbiByZWdpc3RyYXRpb24gaW4gcHJvZHVjdGlvbi4gQ29uZmlndXJlCm5hdGl2ZSBjYXBhYmlsaXRpZXMsIEZpcmViYXNlIG1lc3NhZ2luZyBhbmQgeW91ciBhcHAncyBwZXJtaXNzaW9uIGV4cGxhbmF0aW9uLgp7ey9ub3RpZmljYXRpb25zfX0K", "type": "text" }, { "path": "{{ name.snakeCase() }}/tool/check.dart", "data": - "aW1wb3J0ICdkYXJ0OmlvJzsKaW1wb3J0ICdwYWNrYWdlOnBhdGgvcGF0aC5kYXJ0JyBhcyBwOwoKLy8vIFBvcnRhYmxlLCBub24tbXV0YXRpbmcgZm9ybWF0IGFuZCBhbmFseXplciBjaGVja3MgZnJvbSB0aGUgd29ya3NwYWNlIHJvb3QuCkZ1dHVyZTx2b2lkPiBtYWluKCkgYXN5bmMgewogIGZpbmFsIHNvdXJjZXMgPSA8U3RyaW5nPltdOwogIHZvaWQgY29sbGVjdChEaXJlY3RvcnkgZGlyZWN0b3J5KSB7CiAgICBpZiAoIWRpcmVjdG9yeS5leGlzdHNTeW5jKCkpIHsKICAgICAgcmV0dXJuOwogICAgfQogICAgZm9yIChmaW5hbCBlbnRyeSBpbiBkaXJlY3RvcnkubGlzdFN5bmMoZm9sbG93TGlua3M6IGZhbHNlKSkgewogICAgICBpZiAoZW50cnkgaXMgRGlyZWN0b3J5KSB7CiAgICAgICAgaWYgKCFbJ2J1aWxkJywgJy5kYXJ0X3Rvb2wnLCAnLnN5bWxpbmtzJ10KICAgICAgICAgICAgLmNvbnRhaW5zKHAuYmFzZW5hbWUoZW50cnkucGF0aCkpKSB7CiAgICAgICAgICBjb2xsZWN0KGVudHJ5KTsKICAgICAgICB9CiAgICAgIH0gZWxzZSBpZiAoZW50cnkgaXMgRmlsZSAmJgogICAgICAgICAgZW50cnkucGF0aC5lbmRzV2l0aCgnLmRhcnQnKSAmJgogICAgICAgICAgIVJlZ0V4cChyJ1wuKGd8ZnJlZXplZHxpNjluKVwuZGFydCQnKS5oYXNNYXRjaChlbnRyeS5wYXRoKSkgewogICAgICAgIHNvdXJjZXMuYWRkKGVudHJ5LnBhdGgpOwogICAgICB9CiAgICB9CiAgfQoKICBjb2xsZWN0KERpcmVjdG9yeSgndG9vbCcpKTsKICBmb3IgKGZpbmFsIGFyZWEgaW4gWydhcHBzJywgJ2ZlYXR1cmVzJywgJ3BhY2thZ2VzJywgJ3BsdWdpbnMnXSkgewogICAgZmluYWwgZGlyZWN0b3J5ID0gRGlyZWN0b3J5KGFyZWEpOwogICAgaWYgKCFkaXJlY3RvcnkuZXhpc3RzU3luYygpKSB7CiAgICAgIGNvbnRpbnVlOwogICAgfQogICAgZm9yIChmaW5hbCBwYWNrYWdlIGluIGRpcmVjdG9yeS5saXN0U3luYygpLndoZXJlVHlwZTxEaXJlY3Rvcnk+KCkpIHsKICAgICAgY29sbGVjdChEaXJlY3RvcnkocC5qb2luKHBhY2thZ2UucGF0aCwgJ2xpYicpKSk7CiAgICAgIGNvbGxlY3QoRGlyZWN0b3J5KHAuam9pbihwYWNrYWdlLnBhdGgsICd0ZXN0JykpKTsKICAgIH0KICB9CiAgc291cmNlcy5zb3J0KCk7CiAgZm9yIChmaW5hbCBhcmd1bWVudHMgaW4gWwogICAgWydmb3JtYXQnLCAnLS1vdXRwdXQ9bm9uZScsICctLXNldC1leGl0LWlmLWNoYW5nZWQnLCAuLi5zb3VyY2VzXSwKICAgIFsnYW5hbHl6ZScsICctLWZhdGFsLWluZm9zJywgJy4nXSwKICBdKSB7CiAgICBmaW5hbCBwcm9jZXNzID0gYXdhaXQgUHJvY2Vzcy5zdGFydCgnZGFydCcsIGFyZ3VtZW50cywKICAgICAgICBtb2RlOiBQcm9jZXNzU3RhcnRNb2RlLmluaGVyaXRTdGRpbyk7CiAgICBmaW5hbCByZXN1bHQgPSBhd2FpdCBwcm9jZXNzLmV4aXRDb2RlOwogICAgaWYgKHJlc3VsdCAhPSAwKSB7CiAgICAgIGV4aXRDb2RlID0gcmVzdWx0OwogICAgICByZXR1cm47CiAgICB9CiAgfQp9Cg==", + "aW1wb3J0ICdkYXJ0OmlvJzsKaW1wb3J0ICdwYWNrYWdlOnBhdGgvcGF0aC5kYXJ0JyBhcyBwOwoKLy8vIFBvcnRhYmxlLCBub24tbXV0YXRpbmcgZm9ybWF0IGFuZCBhbmFseXplciBjaGVja3MgZnJvbSB0aGUgd29ya3NwYWNlIHJvb3QuCkZ1dHVyZTx2b2lkPiBtYWluKCkgYXN5bmMgewogIGZpbmFsIHNvdXJjZXMgPSA8U3RyaW5nPltdOwogIHZvaWQgY29sbGVjdChEaXJlY3RvcnkgZGlyZWN0b3J5KSB7CiAgICBpZiAoIWRpcmVjdG9yeS5leGlzdHNTeW5jKCkpIHsKICAgICAgcmV0dXJuOwogICAgfQogICAgZm9yIChmaW5hbCBlbnRyeSBpbiBkaXJlY3RvcnkubGlzdFN5bmMoZm9sbG93TGlua3M6IGZhbHNlKSkgewogICAgICBpZiAoZW50cnkgaXMgRGlyZWN0b3J5KSB7CiAgICAgICAgaWYgKCFbJ2J1aWxkJywgJy5kYXJ0X3Rvb2wnLCAnLnN5bWxpbmtzJ10KICAgICAgICAgICAgLmNvbnRhaW5zKHAuYmFzZW5hbWUoZW50cnkucGF0aCkpKSB7CiAgICAgICAgICBjb2xsZWN0KGVudHJ5KTsKICAgICAgICB9CiAgICAgIH0gZWxzZSBpZiAoZW50cnkgaXMgRmlsZSAmJgogICAgICAgICAgZW50cnkucGF0aC5lbmRzV2l0aCgnLmRhcnQnKSAmJgogICAgICAgICAgIVJlZ0V4cChyJ1wuKGd8ZnJlZXplZHxpNjluKVwuZGFydCQnKS5oYXNNYXRjaChlbnRyeS5wYXRoKSkgewogICAgICAgIHNvdXJjZXMuYWRkKGVudHJ5LnBhdGgpOwogICAgICB9CiAgICB9CiAgfQoKICBjb2xsZWN0KERpcmVjdG9yeSgndG9vbCcpKTsKICBmb3IgKGZpbmFsIGFyZWEgaW4gWydhcHBzJywgJ2ZlYXR1cmVzJywgJ3BhY2thZ2VzJywgJ3BsdWdpbnMnXSkgewogICAgZmluYWwgZGlyZWN0b3J5ID0gRGlyZWN0b3J5KGFyZWEpOwogICAgaWYgKCFkaXJlY3RvcnkuZXhpc3RzU3luYygpKSB7CiAgICAgIGNvbnRpbnVlOwogICAgfQogICAgZm9yIChmaW5hbCBwYWNrYWdlIGluIGRpcmVjdG9yeS5saXN0U3luYygpLndoZXJlVHlwZTxEaXJlY3Rvcnk+KCkpIHsKICAgICAgY29sbGVjdChEaXJlY3RvcnkocC5qb2luKHBhY2thZ2UucGF0aCwgJ2xpYicpKSk7CiAgICAgIGNvbGxlY3QoRGlyZWN0b3J5KHAuam9pbihwYWNrYWdlLnBhdGgsICd0ZXN0JykpKTsKICAgIH0KICB9CiAgc291cmNlcy5zb3J0KCk7CiAgZm9yIChmaW5hbCBhcmd1bWVudHMgaW4gWwogICAgWydmb3JtYXQnLCAnLS1vdXRwdXQ9bm9uZScsICctLXNldC1leGl0LWlmLWNoYW5nZWQnLCAuLi5zb3VyY2VzXSwKICAgIFsnYW5hbHl6ZScsICctLWZhdGFsLWluZm9zJywgLi4uc291cmNlc10sCiAgXSkgewogICAgZmluYWwgcHJvY2VzcyA9IGF3YWl0IFByb2Nlc3Muc3RhcnQoJ2RhcnQnLCBhcmd1bWVudHMsCiAgICAgICAgbW9kZTogUHJvY2Vzc1N0YXJ0TW9kZS5pbmhlcml0U3RkaW8pOwogICAgZmluYWwgcmVzdWx0ID0gYXdhaXQgcHJvY2Vzcy5leGl0Q29kZTsKICAgIGlmIChyZXN1bHQgIT0gMCkgewogICAgICBleGl0Q29kZSA9IHJlc3VsdDsKICAgICAgcmV0dXJuOwogICAgfQogIH0KfQo=", "type": "text" }, { "path": "{{ name.snakeCase() }}/tool/coverage.dart", "data": - "aW1wb3J0ICdkYXJ0OmlvJzsKaW1wb3J0ICdwYWNrYWdlOnBhdGgvcGF0aC5kYXJ0JyBhcyBwOwoKLy8vIFJ1biBmcm9tIHRoZSB3b3Jrc3BhY2Ugcm9vdDogZGFydCBydW4gdG9vbC9jb3ZlcmFnZS5kYXJ0IFstLXJlcG9ydC1vbmx5XS4KLy8vIE9ubHkgY29tcGlsZXItZ2VuZXJhdGVkIHNlcmlhbGl6ZXJzL3RyYW5zbGF0aW9ucyBhcmUgZXhjbHVkZWQuCkZ1dHVyZTx2b2lkPiBtYWluKExpc3Q8U3RyaW5nPiBhcmdzKSBhc3luYyB7CiAgZmluYWwgcm9vdCA9IERpcmVjdG9yeShEaXJlY3RvcnkuY3VycmVudC5yZXNvbHZlU3ltYm9saWNMaW5rc1N5bmMoKSk7CiAgZmluYWwgbWVyZ2VkID0gPFN0cmluZywgTWFwPGludCwgaW50Pj57fTsKICB2YXIgZmFpbGVkID0gZmFsc2U7CiAgZmluYWwgcmVwb3J0T25seSA9IGFyZ3MuY29udGFpbnMoJy0tcmVwb3J0LW9ubHknKTsKICBmaW5hbCBuYW1lcyA9IDxTdHJpbmc+W107CiAgZm9yIChmaW5hbCBhcmVhIGluIFsnYXBwcycsICdmZWF0dXJlcycsICdwYWNrYWdlcycsICdwbHVnaW5zJ10pIHsKICAgIGZpbmFsIGRpcmVjdG9yeSA9IERpcmVjdG9yeSgnJHtyb290LnBhdGh9LyRhcmVhJyk7CiAgICBpZiAoIWRpcmVjdG9yeS5leGlzdHNTeW5jKCkpIGNvbnRpbnVlOwogICAgZm9yIChmaW5hbCBwYWNrYWdlIGluIGRpcmVjdG9yeS5saXN0U3luYygpLndoZXJlVHlwZTxEaXJlY3Rvcnk+KCkpIHsKICAgICAgZmluYWwgbWFuaWZlc3QgPSBGaWxlKCcke3BhY2thZ2UucGF0aH0vcHVic3BlYy55YW1sJyk7CiAgICAgIGlmIChtYW5pZmVzdC5leGlzdHNTeW5jKCkpIHsKICAgICAgICBmaW5hbCBuYW1lID0gUmVnRXhwKAogICAgICAgICAgcidebmFtZTpccyooXFMrKScsCiAgICAgICAgICBtdWx0aUxpbmU6IHRydWUsCiAgICAgICAgKS5maXJzdE1hdGNoKG1hbmlmZXN0LnJlYWRBc1N0cmluZ1N5bmMoKSkhLmdyb3VwKDEpITsKICAgICAgICBuYW1lcy5hZGQoUmVnRXhwLmVzY2FwZShuYW1lKSk7CiAgICAgIH0KICAgIH0KICB9CiAgZmluYWwgY292ZXJhZ2VQYWNrYWdlcyA9ICdeKCR7bmFtZXMuam9pbignfCcpfSlcJCc7CiAgZm9yIChmaW5hbCBhcmVhIGluIFsnYXBwcycsICdmZWF0dXJlcycsICdwYWNrYWdlcycsICdwbHVnaW5zJ10pIHsKICAgIGZpbmFsIGRpcmVjdG9yeSA9IERpcmVjdG9yeSgnJHtyb290LnBhdGh9LyRhcmVhJyk7CiAgICBpZiAoIWRpcmVjdG9yeS5leGlzdHNTeW5jKCkpIGNvbnRpbnVlOwogICAgZmluYWwgcGFja2FnZXMgPSBkaXJlY3RvcnkubGlzdFN5bmMoKS53aGVyZVR5cGU8RGlyZWN0b3J5PigpLnRvTGlzdCgpCiAgICAgIC4uc29ydCgoYSwgYikgPT4gYS5wYXRoLmNvbXBhcmVUbyhiLnBhdGgpKTsKICAgIGZvciAoZmluYWwgcGFja2FnZSBpbiBwYWNrYWdlcykgewogICAgICBmaW5hbCBtYW5pZmVzdCA9IEZpbGUoJyR7cGFja2FnZS5wYXRofS9wdWJzcGVjLnlhbWwnKTsKICAgICAgaWYgKCFtYW5pZmVzdC5leGlzdHNTeW5jKCkpIGNvbnRpbnVlOwogICAgICBmaW5hbCBuYW1lID0gUmVnRXhwKAogICAgICAgIHInXm5hbWU6XHMqKFxTKyknLAogICAgICAgIG11bHRpTGluZTogdHJ1ZSwKICAgICAgKS5maXJzdE1hdGNoKG1hbmlmZXN0LnJlYWRBc1N0cmluZ1N5bmMoKSkhLmdyb3VwKDEpITsKICAgICAgZmluYWwgdGVzdERpcmVjdG9yeSA9IERpcmVjdG9yeSgnJHtwYWNrYWdlLnBhdGh9L3Rlc3QnKTsKICAgICAgaWYgKCF0ZXN0RGlyZWN0b3J5LmV4aXN0c1N5bmMoKSB8fAogICAgICAgICAgIXRlc3REaXJlY3RvcnkKICAgICAgICAgICAgICAubGlzdFN5bmMocmVjdXJzaXZlOiB0cnVlKQogICAgICAgICAgICAgIC53aGVyZVR5cGU8RmlsZT4oKQogICAgICAgICAgICAgIC5hbnkoKGYpID0+IGYucGF0aC5lbmRzV2l0aCgnX3Rlc3QuZGFydCcpKSkgewogICAgICAgIHN0ZGVyci53cml0ZWxuKCckYXJlYS8kbmFtZTogbWlzc2luZyB0ZXN0cycpOwogICAgICAgIGZhaWxlZCA9IHRydWU7CiAgICAgICAgY29udGludWU7CiAgICAgIH0KICAgICAgZmluYWwgcmVwb3J0ID0gRmlsZSgnJHtwYWNrYWdlLnBhdGh9L2NvdmVyYWdlL2xjb3YuaW5mbycpOwogICAgICBpZiAoIXJlcG9ydE9ubHkpIHsKICAgICAgICBmaW5hbCBpbXBvcnRzID0gRmlsZSgKICAgICAgICAgICcke3Rlc3REaXJlY3RvcnkucGF0aH0vY292ZXJhZ2VfaW1wb3J0c190ZXN0LmRhcnQnLAogICAgICAgICk7CiAgICAgICAgaWYgKGltcG9ydHMuZXhpc3RzU3luYygpKSB7CiAgICAgICAgICB0aHJvdyBTdGF0ZUVycm9yKCdSZWZ1c2luZyB0byBvdmVyd3JpdGUgJHtpbXBvcnRzLnBhdGh9Jyk7CiAgICAgICAgfQogICAgICAgIGZpbmFsIHNvdXJjZXMgPSBEaXJlY3RvcnkoJyR7cGFja2FnZS5wYXRofS9saWInKQogICAgICAgICAgICAubGlzdFN5bmMocmVjdXJzaXZlOiB0cnVlKQogICAgICAgICAgICAud2hlcmVUeXBlPEZpbGU+KCkKICAgICAgICAgICAgLndoZXJlKChmKSA9PiBmLnBhdGguZW5kc1dpdGgoJy5kYXJ0JykpCiAgICAgICAgICAgIC53aGVyZSgKICAgICAgICAgICAgICAoZikgPT4gIVJlZ0V4cChyJ1wuKGd8ZnJlZXplZHxpNjluKVwuZGFydCQnKS5oYXNNYXRjaChmLnBhdGgpLAogICAgICAgICAgICApCiAgICAgICAgICAgIC53aGVyZSgKICAgICAgICAgICAgICAoZikgPT4gIVJlZ0V4cCgKICAgICAgICAgICAgICAgIHInXnBhcnQgb2YgJywKICAgICAgICAgICAgICAgIG11bHRpTGluZTogdHJ1ZSwKICAgICAgICAgICAgICApLmhhc01hdGNoKGYucmVhZEFzU3RyaW5nU3luYygpKSwKICAgICAgICAgICAgKQogICAgICAgICAgICAudG9MaXN0KCkKICAgICAgICAgIC4uc29ydCgoYSwgYikgPT4gYS5wYXRoLmNvbXBhcmVUbyhiLnBhdGgpKTsKICAgICAgICBpbXBvcnRzLndyaXRlQXNTdHJpbmdTeW5jKAogICAgICAgICAgWwogICAgICAgICAgICAnLy8gR2VuZXJhdGVkIHRlbXBvcmFyaWx5IGJ5IHRvb2wvY292ZXJhZ2UuZGFydC4nLAogICAgICAgICAgICAnLy8gaWdub3JlX2Zvcl9maWxlOiB1bnVzZWRfaW1wb3J0JywKICAgICAgICAgICAgZm9yIChmaW5hbCBzb3VyY2UgaW4gc291cmNlcykKICAgICAgICAgICAgICAiaW1wb3J0ICdwYWNrYWdlOiRuYW1lLyR7cC5yZWxhdGl2ZShzb3VyY2UucGF0aCwgZnJvbTogcC5qb2luKHBhY2thZ2UucGF0aCwgJ2xpYicpKS5yZXBsYWNlQWxsKHInXCcsICcvJyl9JzsiLAogICAgICAgICAgICAndm9pZCBtYWluKCkge30nLAogICAgICAgICAgXS5qb2luKCdcbicpLAogICAgICAgICk7CiAgICAgICAgdHJ5IHsKICAgICAgICAgIGlmIChyZXBvcnQuZXhpc3RzU3luYygpKSByZXBvcnQuZGVsZXRlU3luYygpOwogICAgICAgICAgZmluYWwgcHJvY2VzcyA9IGF3YWl0IFByb2Nlc3Muc3RhcnQoCiAgICAgICAgICAgICdmbHV0dGVyJywKICAgICAgICAgICAgWyd0ZXN0JywgJy0tY292ZXJhZ2UnLCAnLS1jb3ZlcmFnZS1wYWNrYWdlPSRjb3ZlcmFnZVBhY2thZ2VzJ10sCiAgICAgICAgICAgIHdvcmtpbmdEaXJlY3Rvcnk6IHBhY2thZ2UucGF0aCwKICAgICAgICAgICAgbW9kZTogUHJvY2Vzc1N0YXJ0TW9kZS5pbmhlcml0U3RkaW8sCiAgICAgICAgICApOwogICAgICAgICAgaWYgKGF3YWl0IHByb2Nlc3MuZXhpdENvZGUgIT0gMCkgZmFpbGVkID0gdHJ1ZTsKICAgICAgICB9IGZpbmFsbHkgewogICAgICAgICAgaW1wb3J0cy5kZWxldGVTeW5jKCk7CiAgICAgICAgfQogICAgICB9CiAgICAgIGlmICghcmVwb3J0LmV4aXN0c1N5bmMoKSkgewogICAgICAgIHN0ZGVyci53cml0ZWxuKCckYXJlYS8kbmFtZTogbWlzc2luZyBjb3ZlcmFnZSByZXBvcnQnKTsKICAgICAgICBmYWlsZWQgPSB0cnVlOwogICAgICAgIGNvbnRpbnVlOwogICAgICB9CiAgICAgIFN0cmluZz8gc291cmNlOwogICAgICBmb3IgKGZpbmFsIGxpbmUgaW4gcmVwb3J0LnJlYWRBc0xpbmVzU3luYygpKSB7CiAgICAgICAgaWYgKGxpbmUuc3RhcnRzV2l0aCgnU0Y6JykpIHsKICAgICAgICAgIGZpbmFsIHBhdGggPSBsaW5lLnN1YnN0cmluZygzKTsKICAgICAgICAgIHZhciBhYnNvbHV0ZSA9IHAubm9ybWFsaXplKAogICAgICAgICAgICAgIHAuaXNBYnNvbHV0ZShwYXRoKSA/IHBhdGggOiBwLmpvaW4ocGFja2FnZS5wYXRoLCBwYXRoKSk7CiAgICAgICAgICAvLyBWTSByZXBvcnRzIG1heSB1c2UgL3ZhciB3aGlsZSB0aGUgd29ya3NwYWNlIHJlc29sdmVzIHRvIC9wcml2YXRlL3ZhcgogICAgICAgICAgLy8gb24gbWFjT1MgKG9yIGFub3RoZXIgc3ltbGluayBvbiBMaW51eCkuIE1lcmdlIHRoZSBzYW1lIHJlYWwgc291cmNlLgogICAgICAgICAgZmluYWwgc291cmNlRmlsZSA9IEZpbGUoYWJzb2x1dGUpOwogICAgICAgICAgaWYgKHNvdXJjZUZpbGUuZXhpc3RzU3luYygpKSB7CiAgICAgICAgICAgIGFic29sdXRlID0gc291cmNlRmlsZS5yZXNvbHZlU3ltYm9saWNMaW5rc1N5bmMoKTsKICAgICAgICAgIH0KICAgICAgICAgIHNvdXJjZSA9IHAuaXNXaXRoaW4ocm9vdC5wYXRoLCBhYnNvbHV0ZSkKICAgICAgICAgICAgICA/IHAucmVsYXRpdmUoYWJzb2x1dGUsIGZyb206IHJvb3QucGF0aCkucmVwbGFjZUFsbChyJ1wnLCAnLycpCiAgICAgICAgICAgICAgOiBudWxsOwogICAgICAgICAgaWYgKHNvdXJjZSAhPSBudWxsICYmCiAgICAgICAgICAgICAgIVJlZ0V4cChyJ14oYXBwc3xmZWF0dXJlc3xwYWNrYWdlc3xwbHVnaW5zKS9bXi9dKy9saWIvJykKICAgICAgICAgICAgICAgICAgLmhhc01hdGNoKHNvdXJjZSkpIHsKICAgICAgICAgICAgc291cmNlID0gbnVsbDsKICAgICAgICAgIH0KICAgICAgICAgIGlmIChzb3VyY2UgIT0gbnVsbCAmJgogICAgICAgICAgICAgIFJlZ0V4cChyJ1wuKGd8ZnJlZXplZHxpNjluKVwuZGFydCQnKS5oYXNNYXRjaChzb3VyY2UpKSB7CiAgICAgICAgICAgIHNvdXJjZSA9IG51bGw7CiAgICAgICAgICB9CiAgICAgICAgfSBlbHNlIGlmIChsaW5lLnN0YXJ0c1dpdGgoJ0RBOicpICYmIHNvdXJjZSAhPSBudWxsKSB7CiAgICAgICAgICBmaW5hbCBmaWVsZHMgPSBsaW5lLnN1YnN0cmluZygzKS5zcGxpdCgnLCcpOwogICAgICAgICAgZmluYWwgbnVtYmVyID0gaW50LnBhcnNlKGZpZWxkc1swXSk7CiAgICAgICAgICBmaW5hbCBoaXRzID0gaW50LnBhcnNlKGZpZWxkc1sxXSk7CiAgICAgICAgICBmaW5hbCBsaW5lcyA9IG1lcmdlZC5wdXRJZkFic2VudChzb3VyY2UsICgpID0+IHt9KTsKICAgICAgICAgIGxpbmVzW251bWJlcl0gPSAobGluZXNbbnVtYmVyXSA/PyAwKSArIGhpdHM7CiAgICAgICAgfQogICAgICB9CiAgICB9CiAgfQogIHZhciBjb3ZlcmVkID0gMDsKICB2YXIgdG90YWwgPSAwOwogIGZpbmFsIG91dHB1dCA9IFN0cmluZ0J1ZmZlcigpOwogIGZvciAoZmluYWwgc291cmNlIGluIG1lcmdlZC5rZXlzLnRvTGlzdCgpLi5zb3J0KCkpIHsKICAgIGZpbmFsIGxpbmVzID0gbWVyZ2VkW3NvdXJjZV0hOwogICAgZmluYWwgbWlzc2luZyA9IGxpbmVzLmVudHJpZXMKICAgICAgICAud2hlcmUoKGUpID0+IGUudmFsdWUgPT0gMCkKICAgICAgICAubWFwKChlKSA9PiBlLmtleSkKICAgICAgICAudG9MaXN0KCkKICAgICAgLi5zb3J0KCk7CiAgICBjb3ZlcmVkICs9IGxpbmVzLmxlbmd0aCAtIG1pc3NpbmcubGVuZ3RoOwogICAgdG90YWwgKz0gbGluZXMubGVuZ3RoOwogICAgb3V0cHV0LndyaXRlbG4oJ1NGOiRzb3VyY2UnKTsKICAgIGZvciAoZmluYWwgbGluZSBpbiBsaW5lcy5rZXlzLnRvTGlzdCgpLi5zb3J0KCkpIHsKICAgICAgb3V0cHV0LndyaXRlbG4oJ0RBOiRsaW5lLCR7bGluZXNbbGluZV19Jyk7CiAgICB9CiAgICBvdXRwdXQud3JpdGVsbignTEY6JHtsaW5lcy5sZW5ndGh9Jyk7CiAgICBvdXRwdXQud3JpdGVsbignTEg6JHtsaW5lcy5sZW5ndGggLSBtaXNzaW5nLmxlbmd0aH0nKTsKICAgIG91dHB1dC53cml0ZWxuKCdlbmRfb2ZfcmVjb3JkJyk7CiAgICBpZiAobWlzc2luZy5pc05vdEVtcHR5KSB7CiAgICAgIHN0ZG91dC53cml0ZWxuKCckc291cmNlOiB1bmNvdmVyZWQgbGluZXMgJHttaXNzaW5nLmpvaW4oIiwgIil9Jyk7CiAgICB9CiAgfQogIERpcmVjdG9yeSgnY292ZXJhZ2UnKS5jcmVhdGVTeW5jKHJlY3Vyc2l2ZTogdHJ1ZSk7CiAgRmlsZSgnY292ZXJhZ2UvbGNvdi5pbmZvJykud3JpdGVBc1N0cmluZ1N5bmMob3V0cHV0LnRvU3RyaW5nKCkpOwogIHN0ZG91dC53cml0ZWxuKAogICAgJ0NvdmVyYWdlOiAkY292ZXJlZC8kdG90YWwgZXhlY3V0YWJsZSBsaW5lcyAnCiAgICAnKCR7dG90YWwgPT0gMCA/ICIwLjAwIiA6ICgxMDAgKiBjb3ZlcmVkIC8gdG90YWwpLnRvU3RyaW5nQXNGaXhlZCgyKX0lKScsCiAgKTsKICBpZiAoZmFpbGVkIHx8IHRvdGFsID09IDAgfHwgY292ZXJlZCAhPSB0b3RhbCkgZXhpdENvZGUgPSAxOwp9Cg==", + "aW1wb3J0ICdkYXJ0OmlvJzsKaW1wb3J0ICdwYWNrYWdlOnBhdGgvcGF0aC5kYXJ0JyBhcyBwOwoKLy8vIFJ1biBmcm9tIHRoZSB3b3Jrc3BhY2Ugcm9vdDogZGFydCBydW4gdG9vbC9jb3ZlcmFnZS5kYXJ0IFstLXJlcG9ydC1vbmx5XS4KLy8vIE9ubHkgY29tcGlsZXItZ2VuZXJhdGVkIHNlcmlhbGl6ZXJzL3RyYW5zbGF0aW9ucyBhcmUgZXhjbHVkZWQuCkZ1dHVyZTx2b2lkPiBtYWluKExpc3Q8U3RyaW5nPiBhcmdzKSBhc3luYyB7CiAgZmluYWwgcm9vdCA9IERpcmVjdG9yeShEaXJlY3RvcnkuY3VycmVudC5yZXNvbHZlU3ltYm9saWNMaW5rc1N5bmMoKSk7CiAgZmluYWwgbWVyZ2VkID0gPFN0cmluZywgTWFwPGludCwgaW50Pj57fTsKICB2YXIgZmFpbGVkID0gZmFsc2U7CiAgZmluYWwgcmVwb3J0T25seSA9IGFyZ3MuY29udGFpbnMoJy0tcmVwb3J0LW9ubHknKTsKICBmaW5hbCBuYW1lcyA9IDxTdHJpbmc+W107CiAgZm9yIChmaW5hbCBhcmVhIGluIFsnYXBwcycsICdmZWF0dXJlcycsICdwYWNrYWdlcycsICdwbHVnaW5zJ10pIHsKICAgIGZpbmFsIGRpcmVjdG9yeSA9IERpcmVjdG9yeSgnJHtyb290LnBhdGh9LyRhcmVhJyk7CiAgICBpZiAoIWRpcmVjdG9yeS5leGlzdHNTeW5jKCkpIGNvbnRpbnVlOwogICAgZm9yIChmaW5hbCBwYWNrYWdlIGluIGRpcmVjdG9yeS5saXN0U3luYygpLndoZXJlVHlwZTxEaXJlY3Rvcnk+KCkpIHsKICAgICAgZmluYWwgbWFuaWZlc3QgPSBGaWxlKCcke3BhY2thZ2UucGF0aH0vcHVic3BlYy55YW1sJyk7CiAgICAgIGlmIChtYW5pZmVzdC5leGlzdHNTeW5jKCkpIHsKICAgICAgICBmaW5hbCBuYW1lID0gUmVnRXhwKAogICAgICAgICAgcidebmFtZTpccyooXFMrKScsCiAgICAgICAgICBtdWx0aUxpbmU6IHRydWUsCiAgICAgICAgKS5maXJzdE1hdGNoKG1hbmlmZXN0LnJlYWRBc1N0cmluZ1N5bmMoKSkhLmdyb3VwKDEpITsKICAgICAgICBuYW1lcy5hZGQoUmVnRXhwLmVzY2FwZShuYW1lKSk7CiAgICAgIH0KICAgIH0KICB9CiAgZmluYWwgY292ZXJhZ2VQYWNrYWdlcyA9ICdeKCR7bmFtZXMuam9pbignfCcpfSlcJCc7CiAgZm9yIChmaW5hbCBhcmVhIGluIFsnYXBwcycsICdmZWF0dXJlcycsICdwYWNrYWdlcycsICdwbHVnaW5zJ10pIHsKICAgIGZpbmFsIGRpcmVjdG9yeSA9IERpcmVjdG9yeSgnJHtyb290LnBhdGh9LyRhcmVhJyk7CiAgICBpZiAoIWRpcmVjdG9yeS5leGlzdHNTeW5jKCkpIGNvbnRpbnVlOwogICAgZmluYWwgcGFja2FnZXMgPSBkaXJlY3RvcnkubGlzdFN5bmMoKS53aGVyZVR5cGU8RGlyZWN0b3J5PigpLnRvTGlzdCgpCiAgICAgIC4uc29ydCgoYSwgYikgPT4gYS5wYXRoLmNvbXBhcmVUbyhiLnBhdGgpKTsKICAgIGZvciAoZmluYWwgcGFja2FnZSBpbiBwYWNrYWdlcykgewogICAgICBmaW5hbCBtYW5pZmVzdCA9IEZpbGUoJyR7cGFja2FnZS5wYXRofS9wdWJzcGVjLnlhbWwnKTsKICAgICAgaWYgKCFtYW5pZmVzdC5leGlzdHNTeW5jKCkpIGNvbnRpbnVlOwogICAgICBmaW5hbCBuYW1lID0gUmVnRXhwKAogICAgICAgIHInXm5hbWU6XHMqKFxTKyknLAogICAgICAgIG11bHRpTGluZTogdHJ1ZSwKICAgICAgKS5maXJzdE1hdGNoKG1hbmlmZXN0LnJlYWRBc1N0cmluZ1N5bmMoKSkhLmdyb3VwKDEpITsKICAgICAgZmluYWwgdGVzdERpcmVjdG9yeSA9IERpcmVjdG9yeSgnJHtwYWNrYWdlLnBhdGh9L3Rlc3QnKTsKICAgICAgaWYgKCF0ZXN0RGlyZWN0b3J5LmV4aXN0c1N5bmMoKSB8fAogICAgICAgICAgIXRlc3REaXJlY3RvcnkKICAgICAgICAgICAgICAubGlzdFN5bmMocmVjdXJzaXZlOiB0cnVlKQogICAgICAgICAgICAgIC53aGVyZVR5cGU8RmlsZT4oKQogICAgICAgICAgICAgIC5hbnkoKGYpID0+IGYucGF0aC5lbmRzV2l0aCgnX3Rlc3QuZGFydCcpKSkgewogICAgICAgIHN0ZGVyci53cml0ZWxuKCckYXJlYS8kbmFtZTogbWlzc2luZyB0ZXN0cycpOwogICAgICAgIGZhaWxlZCA9IHRydWU7CiAgICAgICAgY29udGludWU7CiAgICAgIH0KICAgICAgZmluYWwgcmVwb3J0ID0gRmlsZSgnJHtwYWNrYWdlLnBhdGh9L2NvdmVyYWdlL2xjb3YuaW5mbycpOwogICAgICBpZiAoIXJlcG9ydE9ubHkpIHsKICAgICAgICBmaW5hbCBpbXBvcnRzID0gRmlsZSgKICAgICAgICAgICcke3Rlc3REaXJlY3RvcnkucGF0aH0vY292ZXJhZ2VfaW1wb3J0c190ZXN0LmRhcnQnLAogICAgICAgICk7CiAgICAgICAgaWYgKGltcG9ydHMuZXhpc3RzU3luYygpKSB7CiAgICAgICAgICB0aHJvdyBTdGF0ZUVycm9yKCdSZWZ1c2luZyB0byBvdmVyd3JpdGUgJHtpbXBvcnRzLnBhdGh9Jyk7CiAgICAgICAgfQogICAgICAgIGZpbmFsIHNvdXJjZXMgPSBEaXJlY3RvcnkoJyR7cGFja2FnZS5wYXRofS9saWInKQogICAgICAgICAgICAubGlzdFN5bmMocmVjdXJzaXZlOiB0cnVlKQogICAgICAgICAgICAud2hlcmVUeXBlPEZpbGU+KCkKICAgICAgICAgICAgLndoZXJlKChmKSA9PiBmLnBhdGguZW5kc1dpdGgoJy5kYXJ0JykpCiAgICAgICAgICAgIC53aGVyZSgKICAgICAgICAgICAgICAoZikgPT4gIVJlZ0V4cChyJ1wuKGd8ZnJlZXplZHxpNjluKVwuZGFydCQnKS5oYXNNYXRjaChmLnBhdGgpLAogICAgICAgICAgICApCiAgICAgICAgICAgIC53aGVyZSgKICAgICAgICAgICAgICAoZikgPT4gIVJlZ0V4cCgKICAgICAgICAgICAgICAgIHInXnBhcnQgb2YgJywKICAgICAgICAgICAgICAgIG11bHRpTGluZTogdHJ1ZSwKICAgICAgICAgICAgICApLmhhc01hdGNoKGYucmVhZEFzU3RyaW5nU3luYygpKSwKICAgICAgICAgICAgKQogICAgICAgICAgICAudG9MaXN0KCkKICAgICAgICAgIC4uc29ydCgoYSwgYikgPT4gYS5wYXRoLmNvbXBhcmVUbyhiLnBhdGgpKTsKICAgICAgICBpbXBvcnRzLndyaXRlQXNTdHJpbmdTeW5jKAogICAgICAgICAgWwogICAgICAgICAgICAnLy8gR2VuZXJhdGVkIHRlbXBvcmFyaWx5IGJ5IHRvb2wvY292ZXJhZ2UuZGFydC4nLAogICAgICAgICAgICAnLy8gaWdub3JlX2Zvcl9maWxlOiB1bnVzZWRfaW1wb3J0JywKICAgICAgICAgICAgZm9yIChmaW5hbCBzb3VyY2UgaW4gc291cmNlcykKICAgICAgICAgICAgICAiaW1wb3J0ICdwYWNrYWdlOiRuYW1lLyR7cC5yZWxhdGl2ZShzb3VyY2UucGF0aCwgZnJvbTogcC5qb2luKHBhY2thZ2UucGF0aCwgJ2xpYicpKS5yZXBsYWNlQWxsKHInXCcsICcvJyl9JzsiLAogICAgICAgICAgICAiaW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXJfdGVzdC9mbHV0dGVyX3Rlc3QuZGFydCc7IiwKICAgICAgICAgICAgJ3ZvaWQgbWFpbigpIHsgVGVzdFdpZGdldHNGbHV0dGVyQmluZGluZy5lbnN1cmVJbml0aWFsaXplZCgpOyB9JywKICAgICAgICAgIF0uam9pbignXG4nKSwKICAgICAgICApOwogICAgICAgIHRyeSB7CiAgICAgICAgICBpZiAocmVwb3J0LmV4aXN0c1N5bmMoKSkgcmVwb3J0LmRlbGV0ZVN5bmMoKTsKICAgICAgICAgIGZpbmFsIHByb2Nlc3MgPSBhd2FpdCBQcm9jZXNzLnN0YXJ0KAogICAgICAgICAgICAnZmx1dHRlcicsCiAgICAgICAgICAgIFsndGVzdCcsICctLWNvdmVyYWdlJywgJy0tY292ZXJhZ2UtcGFja2FnZT0kY292ZXJhZ2VQYWNrYWdlcyddLAogICAgICAgICAgICB3b3JraW5nRGlyZWN0b3J5OiBwYWNrYWdlLnBhdGgsCiAgICAgICAgICAgIG1vZGU6IFByb2Nlc3NTdGFydE1vZGUuaW5oZXJpdFN0ZGlvLAogICAgICAgICAgKTsKICAgICAgICAgIGlmIChhd2FpdCBwcm9jZXNzLmV4aXRDb2RlICE9IDApIGZhaWxlZCA9IHRydWU7CiAgICAgICAgfSBmaW5hbGx5IHsKICAgICAgICAgIGltcG9ydHMuZGVsZXRlU3luYygpOwogICAgICAgIH0KICAgICAgfQogICAgICBpZiAoIXJlcG9ydC5leGlzdHNTeW5jKCkpIHsKICAgICAgICBzdGRlcnIud3JpdGVsbignJGFyZWEvJG5hbWU6IG1pc3NpbmcgY292ZXJhZ2UgcmVwb3J0Jyk7CiAgICAgICAgZmFpbGVkID0gdHJ1ZTsKICAgICAgICBjb250aW51ZTsKICAgICAgfQogICAgICBTdHJpbmc/IHNvdXJjZTsKICAgICAgZm9yIChmaW5hbCBsaW5lIGluIHJlcG9ydC5yZWFkQXNMaW5lc1N5bmMoKSkgewogICAgICAgIGlmIChsaW5lLnN0YXJ0c1dpdGgoJ1NGOicpKSB7CiAgICAgICAgICBmaW5hbCBwYXRoID0gbGluZS5zdWJzdHJpbmcoMyk7CiAgICAgICAgICB2YXIgYWJzb2x1dGUgPSBwLm5vcm1hbGl6ZSgKICAgICAgICAgICAgICBwLmlzQWJzb2x1dGUocGF0aCkgPyBwYXRoIDogcC5qb2luKHBhY2thZ2UucGF0aCwgcGF0aCkpOwogICAgICAgICAgLy8gVk0gcmVwb3J0cyBtYXkgdXNlIC92YXIgd2hpbGUgdGhlIHdvcmtzcGFjZSByZXNvbHZlcyB0byAvcHJpdmF0ZS92YXIKICAgICAgICAgIC8vIG9uIG1hY09TIChvciBhbm90aGVyIHN5bWxpbmsgb24gTGludXgpLiBNZXJnZSB0aGUgc2FtZSByZWFsIHNvdXJjZS4KICAgICAgICAgIGZpbmFsIHNvdXJjZUZpbGUgPSBGaWxlKGFic29sdXRlKTsKICAgICAgICAgIGlmIChzb3VyY2VGaWxlLmV4aXN0c1N5bmMoKSkgewogICAgICAgICAgICBhYnNvbHV0ZSA9IHNvdXJjZUZpbGUucmVzb2x2ZVN5bWJvbGljTGlua3NTeW5jKCk7CiAgICAgICAgICB9CiAgICAgICAgICBzb3VyY2UgPSBwLmlzV2l0aGluKHJvb3QucGF0aCwgYWJzb2x1dGUpCiAgICAgICAgICAgICAgPyBwLnJlbGF0aXZlKGFic29sdXRlLCBmcm9tOiByb290LnBhdGgpLnJlcGxhY2VBbGwocidcJywgJy8nKQogICAgICAgICAgICAgIDogbnVsbDsKICAgICAgICAgIGlmIChzb3VyY2UgIT0gbnVsbCAmJgogICAgICAgICAgICAgICFSZWdFeHAocideKGFwcHN8ZmVhdHVyZXN8cGFja2FnZXN8cGx1Z2lucykvW14vXSsvbGliLycpCiAgICAgICAgICAgICAgICAgIC5oYXNNYXRjaChzb3VyY2UpKSB7CiAgICAgICAgICAgIHNvdXJjZSA9IG51bGw7CiAgICAgICAgICB9CiAgICAgICAgICBpZiAoc291cmNlICE9IG51bGwgJiYKICAgICAgICAgICAgICBSZWdFeHAocidcLihnfGZyZWV6ZWR8aTY5bilcLmRhcnQkJykuaGFzTWF0Y2goc291cmNlKSkgewogICAgICAgICAgICBzb3VyY2UgPSBudWxsOwogICAgICAgICAgfQogICAgICAgIH0gZWxzZSBpZiAobGluZS5zdGFydHNXaXRoKCdEQTonKSAmJiBzb3VyY2UgIT0gbnVsbCkgewogICAgICAgICAgZmluYWwgZmllbGRzID0gbGluZS5zdWJzdHJpbmcoMykuc3BsaXQoJywnKTsKICAgICAgICAgIGZpbmFsIG51bWJlciA9IGludC5wYXJzZShmaWVsZHNbMF0pOwogICAgICAgICAgZmluYWwgaGl0cyA9IGludC5wYXJzZShmaWVsZHNbMV0pOwogICAgICAgICAgZmluYWwgbGluZXMgPSBtZXJnZWQucHV0SWZBYnNlbnQoc291cmNlLCAoKSA9PiB7fSk7CiAgICAgICAgICBsaW5lc1tudW1iZXJdID0gKGxpbmVzW251bWJlcl0gPz8gMCkgKyBoaXRzOwogICAgICAgIH0KICAgICAgfQogICAgfQogIH0KICB2YXIgY292ZXJlZCA9IDA7CiAgdmFyIHRvdGFsID0gMDsKICBmaW5hbCBvdXRwdXQgPSBTdHJpbmdCdWZmZXIoKTsKICBmb3IgKGZpbmFsIHNvdXJjZSBpbiBtZXJnZWQua2V5cy50b0xpc3QoKS4uc29ydCgpKSB7CiAgICBmaW5hbCBsaW5lcyA9IG1lcmdlZFtzb3VyY2VdITsKICAgIGZpbmFsIG1pc3NpbmcgPSBsaW5lcy5lbnRyaWVzCiAgICAgICAgLndoZXJlKChlKSA9PiBlLnZhbHVlID09IDApCiAgICAgICAgLm1hcCgoZSkgPT4gZS5rZXkpCiAgICAgICAgLnRvTGlzdCgpCiAgICAgIC4uc29ydCgpOwogICAgY292ZXJlZCArPSBsaW5lcy5sZW5ndGggLSBtaXNzaW5nLmxlbmd0aDsKICAgIHRvdGFsICs9IGxpbmVzLmxlbmd0aDsKICAgIG91dHB1dC53cml0ZWxuKCdTRjokc291cmNlJyk7CiAgICBmb3IgKGZpbmFsIGxpbmUgaW4gbGluZXMua2V5cy50b0xpc3QoKS4uc29ydCgpKSB7CiAgICAgIG91dHB1dC53cml0ZWxuKCdEQTokbGluZSwke2xpbmVzW2xpbmVdfScpOwogICAgfQogICAgb3V0cHV0LndyaXRlbG4oJ0xGOiR7bGluZXMubGVuZ3RofScpOwogICAgb3V0cHV0LndyaXRlbG4oJ0xIOiR7bGluZXMubGVuZ3RoIC0gbWlzc2luZy5sZW5ndGh9Jyk7CiAgICBvdXRwdXQud3JpdGVsbignZW5kX29mX3JlY29yZCcpOwogICAgaWYgKG1pc3NpbmcuaXNOb3RFbXB0eSkgewogICAgICBzdGRvdXQud3JpdGVsbignJHNvdXJjZTogdW5jb3ZlcmVkIGxpbmVzICR7bWlzc2luZy5qb2luKCIsICIpfScpOwogICAgfQogIH0KICBEaXJlY3RvcnkoJ2NvdmVyYWdlJykuY3JlYXRlU3luYyhyZWN1cnNpdmU6IHRydWUpOwogIEZpbGUoJ2NvdmVyYWdlL2xjb3YuaW5mbycpLndyaXRlQXNTdHJpbmdTeW5jKG91dHB1dC50b1N0cmluZygpKTsKICBzdGRvdXQud3JpdGVsbigKICAgICdDb3ZlcmFnZTogJGNvdmVyZWQvJHRvdGFsIGV4ZWN1dGFibGUgbGluZXMgJwogICAgJygke3RvdGFsID09IDAgPyAiMC4wMCIgOiAoMTAwICogY292ZXJlZCAvIHRvdGFsKS50b1N0cmluZ0FzRml4ZWQoMil9JSknLAogICk7CiAgaWYgKGZhaWxlZCB8fCB0b3RhbCA9PSAwIHx8IGNvdmVyZWQgIT0gdG90YWwpIGV4aXRDb2RlID0gMTsKfQo=", "type": "text" } ], @@ -1619,25 +1613,25 @@ final flutterProjectWithMonoRepoBundle = MasonBundle.fromJson({ { "path": "commands/checked_process.dart", "data": - "aW1wb3J0ICdkYXJ0OmlvJzsKCmltcG9ydCAncGFja2FnZTptYXNvbi9tYXNvbi5kYXJ0JzsKCi8vLyBSdW5zIFtleGVjdXRhYmxlXSBhbmQgdGhyb3dzIHdoZW4gaXQgZmFpbHMuCi8vLwovLy8gYGNsaV9jb3JlYCdzIGB0cmFja09wZXJhdGlvbmAgcmVwb3J0cyBzdWNjZXNzIGFzIHNvb24gYXMgdGhlIGZ1dHVyZQovLy8gY29tcGxldGVzLCBzbyBhIG5vbi16ZXJvIGV4aXQgY29kZSBzaG93cyBhIGdyZWVuIHRpY2sgYW5kIGdlbmVyYXRpb24KLy8vIGNhcnJpZXMgb24gdG8gcHJvZHVjZSBhIGhhbGYtYnVpbHQgcHJvamVjdC4gRXZlcnkgc3RlcCBoZXJlIGlzIGxvYWQtYmVhcmluZwovLy8gLSBpZiBgZmx1dHRlciBjcmVhdGVgIG9yIGBtZWxvcyBib290c3RyYXBgIGZhaWxzIHRoZXJlIGlzIG5vdGhpbmcgd29ydGgKLy8vIGNvbnRpbnVpbmcgZm9yIC0gc28gdGhlIGV4aXQgY29kZSBpcyBjaGVja2VkIGFuZCB0aGUgY2hpbGQncyBzdGRlcnIgaXMKLy8vIHN1cmZhY2VkLgpGdXR1cmU8dm9pZD4gcnVuQ2hlY2tlZCgKICBIb29rQ29udGV4dCBjb250ZXh0LCB7CiAgcmVxdWlyZWQgU3RyaW5nIHN0YXJ0TWVzc2FnZSwKICByZXF1aXJlZCBTdHJpbmcgZW5kTWVzc2FnZSwKICByZXF1aXJlZCBTdHJpbmcgZXhlY3V0YWJsZSwKICByZXF1aXJlZCBMaXN0PFN0cmluZz4gYXJndW1lbnRzLAogIFN0cmluZz8gd29ya2luZ0RpcmVjdG9yeSwKfSkgYXN5bmMgewogIGZpbmFsIHByb2dyZXNzID0gY29udGV4dC5sb2dnZXIucHJvZ3Jlc3Moc3RhcnRNZXNzYWdlKTsKCiAgZmluYWwgUHJvY2Vzc1Jlc3VsdCByZXN1bHQ7CiAgdHJ5IHsKICAgIHJlc3VsdCA9IGF3YWl0IFByb2Nlc3MucnVuKAogICAgICBleGVjdXRhYmxlLAogICAgICBhcmd1bWVudHMsCiAgICAgIHdvcmtpbmdEaXJlY3Rvcnk6IHdvcmtpbmdEaXJlY3RvcnksCiAgICAgIHJ1bkluU2hlbGw6IHRydWUsCiAgICApOwogIH0gY2F0Y2ggKGVycm9yKSB7CiAgICBwcm9ncmVzcy5mYWlsKCckc3RhcnRNZXNzYWdlIGZhaWxlZDogJGVycm9yJyk7CiAgICByZXRocm93OwogIH0KCiAgaWYgKHJlc3VsdC5leGl0Q29kZSAhPSAwKSB7CiAgICBwcm9ncmVzcy5mYWlsKCckc3RhcnRNZXNzYWdlIGZhaWxlZCAoZXhpdCAke3Jlc3VsdC5leGl0Q29kZX0pJyk7CiAgICBmaW5hbCBkZXRhaWxzID0gJyR7cmVzdWx0LnN0ZGVycn0nLnRyaW0oKS5pc05vdEVtcHR5CiAgICAgICAgPyAnJHtyZXN1bHQuc3RkZXJyfScudHJpbSgpCiAgICAgICAgOiAnJHtyZXN1bHQuc3Rkb3V0fScudHJpbSgpOwogICAgaWYgKGRldGFpbHMuaXNOb3RFbXB0eSkgY29udGV4dC5sb2dnZXIuZXJyKGRldGFpbHMpOwogICAgdGhyb3cgUHJvY2Vzc0V4Y2VwdGlvbigKICAgICAgZXhlY3V0YWJsZSwKICAgICAgYXJndW1lbnRzLAogICAgICAnZXhpdGVkIHdpdGggJHtyZXN1bHQuZXhpdENvZGV9JywKICAgICAgcmVzdWx0LmV4aXRDb2RlLAogICAgKTsKICB9CgogIHByb2dyZXNzLmNvbXBsZXRlKGVuZE1lc3NhZ2UpOwp9Cg==", + "aW1wb3J0ICdkYXJ0OmlvJzsKCmltcG9ydCAncGFja2FnZTptYXNvbi9tYXNvbi5kYXJ0JzsKCnR5cGVkZWYgUHJvY2Vzc1J1bm5lciA9IEZ1dHVyZTxQcm9jZXNzUmVzdWx0PiBGdW5jdGlvbigKICBTdHJpbmcgZXhlY3V0YWJsZSwKICBMaXN0PFN0cmluZz4gYXJndW1lbnRzLCB7CiAgU3RyaW5nPyB3b3JraW5nRGlyZWN0b3J5LAogIGJvb2wgcnVuSW5TaGVsbCwKfSk7CgovLy8gUnVucyBbZXhlY3V0YWJsZV0gYW5kIHRocm93cyB3aGVuIGl0IGZhaWxzLgovLy8KLy8vIGBjbGlfY29yZWAncyBgdHJhY2tPcGVyYXRpb25gIHJlcG9ydHMgc3VjY2VzcyBhcyBzb29uIGFzIHRoZSBmdXR1cmUKLy8vIGNvbXBsZXRlcywgc28gYSBub24temVybyBleGl0IGNvZGUgc2hvd3MgYSBncmVlbiB0aWNrIGFuZCBnZW5lcmF0aW9uCi8vLyBjYXJyaWVzIG9uIHRvIHByb2R1Y2UgYSBoYWxmLWJ1aWx0IHByb2plY3QuIEV2ZXJ5IHN0ZXAgaGVyZSBpcyBsb2FkLWJlYXJpbmcKLy8vIC0gaWYgYGZsdXR0ZXIgY3JlYXRlYCBvciBgbWVsb3MgYm9vdHN0cmFwYCBmYWlscyB0aGVyZSBpcyBub3RoaW5nIHdvcnRoCi8vLyBjb250aW51aW5nIGZvciAtIHNvIHRoZSBleGl0IGNvZGUgaXMgY2hlY2tlZCBhbmQgdGhlIGNoaWxkJ3Mgc3RkZXJyIGlzCi8vLyBzdXJmYWNlZC4KRnV0dXJlPHZvaWQ+IHJ1bkNoZWNrZWQoCiAgSG9va0NvbnRleHQgY29udGV4dCwgewogIHJlcXVpcmVkIFN0cmluZyBzdGFydE1lc3NhZ2UsCiAgcmVxdWlyZWQgU3RyaW5nIGVuZE1lc3NhZ2UsCiAgcmVxdWlyZWQgU3RyaW5nIGV4ZWN1dGFibGUsCiAgcmVxdWlyZWQgTGlzdDxTdHJpbmc+IGFyZ3VtZW50cywKICBTdHJpbmc/IHdvcmtpbmdEaXJlY3RvcnksCiAgUHJvY2Vzc1J1bm5lciBydW5Qcm9jZXNzID0gUHJvY2Vzcy5ydW4sCn0pIGFzeW5jIHsKICBmaW5hbCBwcm9ncmVzcyA9IGNvbnRleHQubG9nZ2VyLnByb2dyZXNzKHN0YXJ0TWVzc2FnZSk7CgogIGZpbmFsIFByb2Nlc3NSZXN1bHQgcmVzdWx0OwogIHRyeSB7CiAgICByZXN1bHQgPSBhd2FpdCBydW5Qcm9jZXNzKAogICAgICBleGVjdXRhYmxlLAogICAgICBhcmd1bWVudHMsCiAgICAgIHdvcmtpbmdEaXJlY3Rvcnk6IHdvcmtpbmdEaXJlY3RvcnksCiAgICAgIHJ1bkluU2hlbGw6IHRydWUsCiAgICApOwogIH0gY2F0Y2ggKGVycm9yKSB7CiAgICBwcm9ncmVzcy5mYWlsKCckc3RhcnRNZXNzYWdlIGZhaWxlZDogJGVycm9yJyk7CiAgICByZXRocm93OwogIH0KCiAgaWYgKHJlc3VsdC5leGl0Q29kZSAhPSAwKSB7CiAgICBwcm9ncmVzcy5mYWlsKCckc3RhcnRNZXNzYWdlIGZhaWxlZCAoZXhpdCAke3Jlc3VsdC5leGl0Q29kZX0pJyk7CiAgICBmaW5hbCBkZXRhaWxzID0gJyR7cmVzdWx0LnN0ZGVycn0nLnRyaW0oKS5pc05vdEVtcHR5CiAgICAgICAgPyAnJHtyZXN1bHQuc3RkZXJyfScudHJpbSgpCiAgICAgICAgOiAnJHtyZXN1bHQuc3Rkb3V0fScudHJpbSgpOwogICAgaWYgKGRldGFpbHMuaXNOb3RFbXB0eSkgY29udGV4dC5sb2dnZXIuZXJyKGRldGFpbHMpOwogICAgdGhyb3cgUHJvY2Vzc0V4Y2VwdGlvbigKICAgICAgZXhlY3V0YWJsZSwKICAgICAgYXJndW1lbnRzLAogICAgICAnZXhpdGVkIHdpdGggJHtyZXN1bHQuZXhpdENvZGV9JywKICAgICAgcmVzdWx0LmV4aXRDb2RlLAogICAgKTsKICB9CgogIHByb2dyZXNzLmNvbXBsZXRlKGVuZE1lc3NhZ2UpOwp9Cg==", "type": "text" }, { "path": "commands/flutter_create_command.dart", "data": - "aW1wb3J0ICdkYXJ0OmFzeW5jJzsKaW1wb3J0ICdkYXJ0OmlvJzsKCmltcG9ydCAncGFja2FnZTpjbGlfY29yZS9jbGlfY29yZS5kYXJ0JyBzaG93IENsaUNvbW1hbmQsIEZpbGVVdGlsczsKaW1wb3J0ICdwYWNrYWdlOm1hc29uL21hc29uLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6cGF0aC9wYXRoLmRhcnQnIGFzIHA7CgppbXBvcnQgJ2NoZWNrZWRfcHJvY2Vzcy5kYXJ0JzsKCi8vLyBTY2FmZm9sZHMgdGhlIGFwcGxpY2F0aW9uIHdpdGggYGZsdXR0ZXIgY3JlYXRlYC4KLy8vCi8vLyBUaGlzIHJ1bnMgaW4gcHJlLWdlbiwgc28gdGhlIGJyaWNrJ3Mgb3duIGBsaWIvYCBhbmQgYHB1YnNwZWMueWFtbGAgYXJlCi8vLyB3cml0dGVuIG92ZXIgdGhlIGNvdW50ZXItYXBwIHRoYXQgYGZsdXR0ZXIgY3JlYXRlYCBwcm9kdWNlcy4KZmluYWwgY2xhc3MgRmx1dHRlckNyZWF0ZUNvbW1hbmQgZXh0ZW5kcyBDbGlDb21tYW5kIHsKICBAb3ZlcnJpZGUKICBGdXR1cmU8dm9pZD4gcnVuKEhvb2tDb250ZXh0IGNvbnRleHQpIGFzeW5jIHsKICAgIGZpbmFsIFN0cmluZyBuYW1lID0gY29udGV4dC52YXJzWyduYW1lJ107CiAgICBmaW5hbCBTdHJpbmcgZGVzY3JpcHRpb24gPSBjb250ZXh0LnZhcnNbJ2Rlc2NyaXB0aW9uJ107CiAgICBmaW5hbCBTdHJpbmcgb3JnTmFtZSA9IGNvbnRleHQudmFyc1snb3JnX25hbWUnXSA/PyAnY29tLmV4YW1wbGUnOwogICAgZmluYWwgYXBwTmFtZSA9IG5hbWUuc25ha2VDYXNlOwogICAgZmluYWwgYXBwc1BhdGggPSBwLm5vcm1hbGl6ZSgnJGFwcE5hbWUvYXBwcycpOwoKICAgIGF3YWl0IEZpbGVVdGlscy5lbnN1cmVEaXJlY3RvcnkoYXBwc1BhdGgpOwoKICAgIGF3YWl0IHJ1bkNoZWNrZWQoCiAgICAgIGNvbnRleHQsCiAgICAgIHN0YXJ0TWVzc2FnZTogJ0NyZWF0aW5nIEZsdXR0ZXIgYXBwOiAkYXBwTmFtZScsCiAgICAgIGVuZE1lc3NhZ2U6ICdGbHV0dGVyIGFwcCBjcmVhdGVkJywKICAgICAgZXhlY3V0YWJsZTogJ2ZsdXR0ZXInLAogICAgICBhcmd1bWVudHM6IFsKICAgICAgICAnY3JlYXRlJywKICAgICAgICBhcHBOYW1lLAogICAgICAgICctLXRlbXBsYXRlPWFwcCcsCiAgICAgICAgJy0tcGxhdGZvcm1zPWlvcyxhbmRyb2lkLHdlYicsCiAgICAgICAgJy0tZGVzY3JpcHRpb249JGRlc2NyaXB0aW9uJywKICAgICAgICAnLS1vcmc9JG9yZ05hbWUnLAogICAgICBdLAogICAgICB3b3JraW5nRGlyZWN0b3J5OiBhcHBzUGF0aCwKICAgICk7CgogICAgZmluYWwgYXBwUGF0aCA9IHAuam9pbihhcHBzUGF0aCwgYXBwTmFtZSk7CgogICAgLy8gVGhlIHdvcmtzcGFjZSBvd25zIGxpbnQgY29uZmlndXJhdGlvbi4KICAgIGF3YWl0IEZpbGVVdGlscy5kZWxldGVGaWxlKHAuam9pbihhcHBQYXRoLCAnYW5hbHlzaXNfb3B0aW9ucy55YW1sJykpOwoKICAgIC8vIGBmbHV0dGVyIGNyZWF0ZWAgbGVhdmVzIGEgd2lkZ2V0IHRlc3QgZm9yIHRoZSBjb3VudGVyIGFwcCBpdCBqdXN0IHdyb3RlOwogICAgLy8gdGhlIGJyaWNrIHJlcGxhY2VzIHRoYXQgYXBwLCBzbyB0aGUgdGVzdCB3b3VsZCBub3QgY29tcGlsZS4KICAgIGF3YWl0IEZpbGVVdGlscy5kZWxldGVGaWxlKHAuam9pbihhcHBQYXRoLCAndGVzdCcsICd3aWRnZXRfdGVzdC5kYXJ0JykpOwogICAgZmluYWwgdGVzdERpciA9IERpcmVjdG9yeShwLmpvaW4oYXBwUGF0aCwgJ3Rlc3QnKSk7CiAgICBpZiAodGVzdERpci5leGlzdHNTeW5jKCkgJiYgdGVzdERpci5saXN0U3luYygpLmlzRW1wdHkpIHsKICAgICAgYXdhaXQgdGVzdERpci5kZWxldGUoKTsKICAgIH0KICB9Cn0K", + "aW1wb3J0ICdkYXJ0OmFzeW5jJzsKaW1wb3J0ICdkYXJ0OmlvJzsKCmltcG9ydCAncGFja2FnZTpjbGlfY29yZS9jbGlfY29yZS5kYXJ0JyBzaG93IENsaUNvbW1hbmQsIEZpbGVVdGlsczsKaW1wb3J0ICdwYWNrYWdlOm1hc29uL21hc29uLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6cGF0aC9wYXRoLmRhcnQnIGFzIHA7CgppbXBvcnQgJ2NoZWNrZWRfcHJvY2Vzcy5kYXJ0JzsKCi8vLyBTY2FmZm9sZHMgdGhlIGFwcGxpY2F0aW9uIHdpdGggYGZsdXR0ZXIgY3JlYXRlYC4KLy8vCi8vLyBUaGlzIHJ1bnMgaW4gcHJlLWdlbiwgc28gdGhlIGJyaWNrJ3Mgb3duIGBsaWIvYCBhbmQgYHB1YnNwZWMueWFtbGAgYXJlCi8vLyB3cml0dGVuIG92ZXIgdGhlIGNvdW50ZXItYXBwIHRoYXQgYGZsdXR0ZXIgY3JlYXRlYCBwcm9kdWNlcy4KZmluYWwgY2xhc3MgRmx1dHRlckNyZWF0ZUNvbW1hbmQgZXh0ZW5kcyBDbGlDb21tYW5kIHsKICBGbHV0dGVyQ3JlYXRlQ29tbWFuZCh7dGhpcy5ydW5Qcm9jZXNzID0gUHJvY2Vzcy5ydW59KTsKCiAgZmluYWwgUHJvY2Vzc1J1bm5lciBydW5Qcm9jZXNzOwoKICBAb3ZlcnJpZGUKICBGdXR1cmU8dm9pZD4gcnVuKEhvb2tDb250ZXh0IGNvbnRleHQpIGFzeW5jIHsKICAgIGZpbmFsIFN0cmluZyBuYW1lID0gY29udGV4dC52YXJzWyduYW1lJ107CiAgICBmaW5hbCBTdHJpbmcgZGVzY3JpcHRpb24gPSBjb250ZXh0LnZhcnNbJ2Rlc2NyaXB0aW9uJ107CiAgICBmaW5hbCBTdHJpbmcgb3JnTmFtZSA9IGNvbnRleHQudmFyc1snb3JnX25hbWUnXSA/PyAnY29tLmV4YW1wbGUnOwogICAgZmluYWwgYXBwTmFtZSA9IG5hbWUuc25ha2VDYXNlOwogICAgZmluYWwgYXBwc1BhdGggPSBwLm5vcm1hbGl6ZSgnJGFwcE5hbWUvYXBwcycpOwoKICAgIGF3YWl0IEZpbGVVdGlscy5lbnN1cmVEaXJlY3RvcnkoYXBwc1BhdGgpOwoKICAgIGF3YWl0IHJ1bkNoZWNrZWQoCiAgICAgIGNvbnRleHQsCiAgICAgIHJ1blByb2Nlc3M6IHJ1blByb2Nlc3MsCiAgICAgIHN0YXJ0TWVzc2FnZTogJ0NyZWF0aW5nIEZsdXR0ZXIgYXBwOiAkYXBwTmFtZScsCiAgICAgIGVuZE1lc3NhZ2U6ICdGbHV0dGVyIGFwcCBjcmVhdGVkJywKICAgICAgZXhlY3V0YWJsZTogJ2ZsdXR0ZXInLAogICAgICBhcmd1bWVudHM6IFsKICAgICAgICAnY3JlYXRlJywKICAgICAgICBhcHBOYW1lLAogICAgICAgICctLXRlbXBsYXRlPWFwcCcsCiAgICAgICAgJy0tcGxhdGZvcm1zPWlvcyxhbmRyb2lkLHdlYicsCiAgICAgICAgJy0tZGVzY3JpcHRpb249JGRlc2NyaXB0aW9uJywKICAgICAgICAnLS1vcmc9JG9yZ05hbWUnLAogICAgICBdLAogICAgICB3b3JraW5nRGlyZWN0b3J5OiBhcHBzUGF0aCwKICAgICk7CgogICAgZmluYWwgYXBwUGF0aCA9IHAuam9pbihhcHBzUGF0aCwgYXBwTmFtZSk7CgogICAgLy8gVGhlIHdvcmtzcGFjZSBvd25zIGxpbnQgY29uZmlndXJhdGlvbi4KICAgIGF3YWl0IEZpbGVVdGlscy5kZWxldGVGaWxlKHAuam9pbihhcHBQYXRoLCAnYW5hbHlzaXNfb3B0aW9ucy55YW1sJykpOwoKICAgIC8vIGBmbHV0dGVyIGNyZWF0ZWAgbGVhdmVzIGEgd2lkZ2V0IHRlc3QgZm9yIHRoZSBjb3VudGVyIGFwcCBpdCBqdXN0IHdyb3RlOwogICAgLy8gdGhlIGJyaWNrIHJlcGxhY2VzIHRoYXQgYXBwLCBzbyB0aGUgdGVzdCB3b3VsZCBub3QgY29tcGlsZS4KICAgIGF3YWl0IEZpbGVVdGlscy5kZWxldGVGaWxlKHAuam9pbihhcHBQYXRoLCAndGVzdCcsICd3aWRnZXRfdGVzdC5kYXJ0JykpOwogICAgZmluYWwgdGVzdERpciA9IERpcmVjdG9yeShwLmpvaW4oYXBwUGF0aCwgJ3Rlc3QnKSk7CiAgICBpZiAodGVzdERpci5leGlzdHNTeW5jKCkgJiYgdGVzdERpci5saXN0U3luYygpLmlzRW1wdHkpIHsKICAgICAgYXdhaXQgdGVzdERpci5kZWxldGUoKTsKICAgIH0KICB9Cn0K", "type": "text" }, { "path": "commands/format_command.dart", "data": - "aW1wb3J0ICdwYWNrYWdlOmNsaV9jb3JlL2NsaV9jb3JlLmRhcnQnIHNob3cgQ2xpQ29tbWFuZDsKaW1wb3J0ICdwYWNrYWdlOm1hc29uL21hc29uLmRhcnQnOwoKaW1wb3J0ICdjaGVja2VkX3Byb2Nlc3MuZGFydCc7CgovLy8gRm9ybWF0cyB0aGUgZ2VuZXJhdGVkIHdvcmtzcGFjZS4KLy8vCi8vLyBUZW1wbGF0ZXMgYXJlIHJlbmRlcmVkIGZyb20gbXVzdGFjaGUsIHNvIHRoZSBleGFjdCBsaW5lIGJyZWFrcyBkZXBlbmQgb24KLy8vIHdoaWNoIG1vZHVsZXMgd2VyZSBzZWxlY3RlZC4gUnVubmluZyB0aGUgZm9ybWF0dGVyIG9uY2UgaGVyZSBtZWFucyBhIGZyZXNoCi8vLyBwcm9qZWN0IHBhc3NlcyBpdHMgb3duIGBtZWxvcyBsaW50YCB3aGF0ZXZlciB0aGUgYW5zd2VycyB3ZXJlLgpmaW5hbCBjbGFzcyBGb3JtYXRDb21tYW5kIGV4dGVuZHMgQ2xpQ29tbWFuZCB7CiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IHJ1bihIb29rQ29udGV4dCBjb250ZXh0KSBhc3luYyB7CiAgICBmaW5hbCBTdHJpbmcgbmFtZSA9IGNvbnRleHQudmFyc1snbmFtZSddOwoKICAgIGF3YWl0IHJ1bkNoZWNrZWQoCiAgICAgIGNvbnRleHQsCiAgICAgIHN0YXJ0TWVzc2FnZTogJ0Zvcm1hdHRpbmcgZ2VuZXJhdGVkIGNvZGUnLAogICAgICBlbmRNZXNzYWdlOiAnQ29kZSBmb3JtYXR0ZWQnLAogICAgICBleGVjdXRhYmxlOiAnZGFydCcsCiAgICAgIGFyZ3VtZW50czogWydmb3JtYXQnLCAnLiddLAogICAgICB3b3JraW5nRGlyZWN0b3J5OiBuYW1lLnNuYWtlQ2FzZSwKICAgICk7CiAgfQp9Cg==", + "aW1wb3J0ICdkYXJ0OmlvJzsKCmltcG9ydCAncGFja2FnZTpjbGlfY29yZS9jbGlfY29yZS5kYXJ0JyBzaG93IENsaUNvbW1hbmQ7CmltcG9ydCAncGFja2FnZTptYXNvbi9tYXNvbi5kYXJ0JzsKCmltcG9ydCAnY2hlY2tlZF9wcm9jZXNzLmRhcnQnOwoKLy8vIEZvcm1hdHMgdGhlIGdlbmVyYXRlZCB3b3Jrc3BhY2UuCi8vLwovLy8gVGVtcGxhdGVzIGFyZSByZW5kZXJlZCBmcm9tIG11c3RhY2hlLCBzbyB0aGUgZXhhY3QgbGluZSBicmVha3MgZGVwZW5kIG9uCi8vLyB3aGljaCBtb2R1bGVzIHdlcmUgc2VsZWN0ZWQuIFJ1bm5pbmcgdGhlIGZvcm1hdHRlciBvbmNlIGhlcmUgbWVhbnMgYSBmcmVzaAovLy8gcHJvamVjdCBwYXNzZXMgaXRzIG93biBgbWVsb3MgbGludGAgd2hhdGV2ZXIgdGhlIGFuc3dlcnMgd2VyZS4KZmluYWwgY2xhc3MgRm9ybWF0Q29tbWFuZCBleHRlbmRzIENsaUNvbW1hbmQgewogIEZvcm1hdENvbW1hbmQoe3RoaXMucnVuUHJvY2VzcyA9IFByb2Nlc3MucnVufSk7CgogIGZpbmFsIFByb2Nlc3NSdW5uZXIgcnVuUHJvY2VzczsKCiAgQG92ZXJyaWRlCiAgRnV0dXJlPHZvaWQ+IHJ1bihIb29rQ29udGV4dCBjb250ZXh0KSBhc3luYyB7CiAgICBmaW5hbCBTdHJpbmcgbmFtZSA9IGNvbnRleHQudmFyc1snbmFtZSddOwoKICAgIGF3YWl0IHJ1bkNoZWNrZWQoCiAgICAgIGNvbnRleHQsCiAgICAgIHJ1blByb2Nlc3M6IHJ1blByb2Nlc3MsCiAgICAgIHN0YXJ0TWVzc2FnZTogJ0Zvcm1hdHRpbmcgZ2VuZXJhdGVkIGNvZGUnLAogICAgICBlbmRNZXNzYWdlOiAnQ29kZSBmb3JtYXR0ZWQnLAogICAgICBleGVjdXRhYmxlOiAnZGFydCcsCiAgICAgIGFyZ3VtZW50czogWydmb3JtYXQnLCAnLiddLAogICAgICB3b3JraW5nRGlyZWN0b3J5OiBuYW1lLnNuYWtlQ2FzZSwKICAgICk7CiAgfQp9Cg==", "type": "text" }, { "path": "commands/melos_command.dart", "data": - "aW1wb3J0ICdwYWNrYWdlOmNsaV9jb3JlL2NsaV9jb3JlLmRhcnQnIHNob3cgQ2xpQ29tbWFuZDsKaW1wb3J0ICdwYWNrYWdlOm1hc29uL21hc29uLmRhcnQnOwoKaW1wb3J0ICdjaGVja2VkX3Byb2Nlc3MuZGFydCc7CgpmaW5hbCBjbGFzcyBNZWxvc0NvbW1hbmQgZXh0ZW5kcyBDbGlDb21tYW5kIHsKICBAb3ZlcnJpZGUKICBGdXR1cmU8dm9pZD4gcnVuKEhvb2tDb250ZXh0IGNvbnRleHQpIGFzeW5jIHsKICAgIGZpbmFsIFN0cmluZyBuYW1lID0gY29udGV4dC52YXJzWyduYW1lJ107CiAgICBmaW5hbCBhcHBOYW1lID0gbmFtZS5zbmFrZUNhc2U7CgogICAgYXdhaXQgcnVuQ2hlY2tlZCgKICAgICAgY29udGV4dCwKICAgICAgc3RhcnRNZXNzYWdlOiAnSW5zdGFsbGluZyB3b3Jrc3BhY2UgdG9vbGluZycsCiAgICAgIGVuZE1lc3NhZ2U6ICdXb3Jrc3BhY2UgdG9vbGluZyBpbnN0YWxsZWQnLAogICAgICBleGVjdXRhYmxlOiAnZGFydCcsCiAgICAgIGFyZ3VtZW50czogWydwdWInLCAnZ2V0J10sCiAgICAgIHdvcmtpbmdEaXJlY3Rvcnk6IGFwcE5hbWUsCiAgICApOwoKICAgIGF3YWl0IHJ1bkNoZWNrZWQoCiAgICAgIGNvbnRleHQsCiAgICAgIHN0YXJ0TWVzc2FnZTogJ1J1bm5pbmcgbWVsb3MgYm9vdHN0cmFwJywKICAgICAgZW5kTWVzc2FnZTogJ0RlcGVuZGVuY2llcyBpbnN0YWxsZWQnLAogICAgICBleGVjdXRhYmxlOiAnZGFydCcsCiAgICAgIGFyZ3VtZW50czogWydydW4nLCAnbWVsb3MnLCAnYm9vdHN0cmFwJ10sCiAgICAgIHdvcmtpbmdEaXJlY3Rvcnk6IGFwcE5hbWUsCiAgICApOwogIH0KfQo=", + "aW1wb3J0ICdkYXJ0OmlvJzsKCmltcG9ydCAncGFja2FnZTpjbGlfY29yZS9jbGlfY29yZS5kYXJ0JyBzaG93IENsaUNvbW1hbmQ7CmltcG9ydCAncGFja2FnZTptYXNvbi9tYXNvbi5kYXJ0JzsKCmltcG9ydCAnY2hlY2tlZF9wcm9jZXNzLmRhcnQnOwoKZmluYWwgY2xhc3MgTWVsb3NDb21tYW5kIGV4dGVuZHMgQ2xpQ29tbWFuZCB7CiAgTWVsb3NDb21tYW5kKHt0aGlzLnJ1blByb2Nlc3MgPSBQcm9jZXNzLnJ1bn0pOwoKICBmaW5hbCBQcm9jZXNzUnVubmVyIHJ1blByb2Nlc3M7CgogIEBvdmVycmlkZQogIEZ1dHVyZTx2b2lkPiBydW4oSG9va0NvbnRleHQgY29udGV4dCkgYXN5bmMgewogICAgZmluYWwgU3RyaW5nIG5hbWUgPSBjb250ZXh0LnZhcnNbJ25hbWUnXTsKICAgIGZpbmFsIGFwcE5hbWUgPSBuYW1lLnNuYWtlQ2FzZTsKCiAgICBhd2FpdCBydW5DaGVja2VkKAogICAgICBjb250ZXh0LAogICAgICBydW5Qcm9jZXNzOiBydW5Qcm9jZXNzLAogICAgICBzdGFydE1lc3NhZ2U6ICdJbnN0YWxsaW5nIHdvcmtzcGFjZSB0b29saW5nJywKICAgICAgZW5kTWVzc2FnZTogJ1dvcmtzcGFjZSB0b29saW5nIGluc3RhbGxlZCcsCiAgICAgIGV4ZWN1dGFibGU6ICdkYXJ0JywKICAgICAgYXJndW1lbnRzOiBbJ3B1YicsICdnZXQnXSwKICAgICAgd29ya2luZ0RpcmVjdG9yeTogYXBwTmFtZSwKICAgICk7CgogICAgYXdhaXQgcnVuQ2hlY2tlZCgKICAgICAgY29udGV4dCwKICAgICAgcnVuUHJvY2VzczogcnVuUHJvY2VzcywKICAgICAgc3RhcnRNZXNzYWdlOiAnUnVubmluZyBtZWxvcyBib290c3RyYXAnLAogICAgICBlbmRNZXNzYWdlOiAnRGVwZW5kZW5jaWVzIGluc3RhbGxlZCcsCiAgICAgIGV4ZWN1dGFibGU6ICdkYXJ0JywKICAgICAgYXJndW1lbnRzOiBbJ3J1bicsICdtZWxvcycsICdib290c3RyYXAnXSwKICAgICAgd29ya2luZ0RpcmVjdG9yeTogYXBwTmFtZSwKICAgICk7CiAgfQp9Cg==", "type": "text" }, { @@ -1655,19 +1649,19 @@ final flutterProjectWithMonoRepoBundle = MasonBundle.fromJson({ { "path": "post_gen.dart", "data": - "aW1wb3J0ICdkYXJ0OmFzeW5jJzsKCmltcG9ydCAncGFja2FnZTptYXNvbi9tYXNvbi5kYXJ0JzsKCmltcG9ydCAnY29tbWFuZHMvZm9ybWF0X2NvbW1hbmQuZGFydCc7CmltcG9ydCAnY29tbWFuZHMvbWVsb3NfY29tbWFuZC5kYXJ0JzsKaW1wb3J0ICdjb21tYW5kcy9uZXh0X3N0ZXBzX2NvbW1hbmQuZGFydCc7CgpGdXR1cmU8dm9pZD4gcnVuKEhvb2tDb250ZXh0IGNvbnRleHQpIGFzeW5jIHsKICBmaW5hbCBjb21tYW5kcyA9IFsKICAgIEZvcm1hdENvbW1hbmQoKSwKICAgIE1lbG9zQ29tbWFuZCgpLAogICAgTmV4dFN0ZXBzQ29tbWFuZCgpLAogIF07CgogIGZvciAoZmluYWwgY29tbWFuZCBpbiBjb21tYW5kcykgewogICAgYXdhaXQgY29tbWFuZC5ydW4oY29udGV4dCk7CiAgfQp9Cg==", + "aW1wb3J0ICdkYXJ0OmFzeW5jJzsKaW1wb3J0ICdkYXJ0OmlvJzsKCmltcG9ydCAncGFja2FnZTptYXNvbi9tYXNvbi5kYXJ0JzsKCmltcG9ydCAnY29tbWFuZHMvY2hlY2tlZF9wcm9jZXNzLmRhcnQnOwppbXBvcnQgJ2NvbW1hbmRzL2Zvcm1hdF9jb21tYW5kLmRhcnQnOwppbXBvcnQgJ2NvbW1hbmRzL21lbG9zX2NvbW1hbmQuZGFydCc7CmltcG9ydCAnY29tbWFuZHMvbmV4dF9zdGVwc19jb21tYW5kLmRhcnQnOwoKRnV0dXJlPHZvaWQ+IHJ1bihIb29rQ29udGV4dCBjb250ZXh0LAogICAge1Byb2Nlc3NSdW5uZXIgcnVuUHJvY2VzcyA9IFByb2Nlc3MucnVufSkgYXN5bmMgewogIGZpbmFsIGNvbW1hbmRzID0gWwogICAgTWVsb3NDb21tYW5kKHJ1blByb2Nlc3M6IHJ1blByb2Nlc3MpLAogICAgRm9ybWF0Q29tbWFuZChydW5Qcm9jZXNzOiBydW5Qcm9jZXNzKSwKICAgIE5leHRTdGVwc0NvbW1hbmQoKSwKICBdOwoKICBmb3IgKGZpbmFsIGNvbW1hbmQgaW4gY29tbWFuZHMpIHsKICAgIGF3YWl0IGNvbW1hbmQucnVuKGNvbnRleHQpOwogIH0KfQo=", "type": "text" }, { "path": "pre_gen.dart", "data": - "aW1wb3J0ICdkYXJ0OmFzeW5jJzsKCmltcG9ydCAncGFja2FnZTpjbGlfY29yZS9jbGlfY29yZS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOm1hc29uL21hc29uLmRhcnQnOwoKaW1wb3J0ICdjb21tYW5kcy9mbHV0dGVyX2NyZWF0ZV9jb21tYW5kLmRhcnQnOwppbXBvcnQgJ2NvbW1hbmRzL21vZHVsZV92YXJzLmRhcnQnOwoKRnV0dXJlPHZvaWQ+IHJ1bihIb29rQ29udGV4dCBjb250ZXh0KSBhc3luYyB7CiAgY29udGV4dC52YXJzWydpc19tb25vX3JlcG8nXSA9IGF3YWl0IEZpbGVVdGlscy5pc01vbm9SZXBvKCk7CgogIC8vIFR1cm4gdGhlIG1vZHVsZSBhbnN3ZXJzIGludG8gdGhlIGAqX2RpcmAgcGF0aCB2YXJpYWJsZXMgdGhlIGJyaWNrIHVzZXMgdG8KICAvLyBpbmNsdWRlIG9yIHNraXAgd2hvbGUgZGlyZWN0b3JpZXMsIGFuZCBkZXJpdmUgYGZpcmViYXNlYC4KICByZXNvbHZlTW9kdWxlVmFycyhjb250ZXh0KTsKCiAgLy8gVGhlIGFwcCBpcyBzY2FmZm9sZGVkIGJlZm9yZSBnZW5lcmF0aW9uIHNvIHRoZSBicmljaydzIGBsaWIvYCBhbmQKICAvLyBgcHVic3BlYy55YW1sYCBsYW5kIG9uIHRvcCBvZiB3aGF0IGBmbHV0dGVyIGNyZWF0ZWAgcHJvZHVjZWQuCiAgYXdhaXQgRmx1dHRlckNyZWF0ZUNvbW1hbmQoKS5ydW4oY29udGV4dCk7Cn0K", + "aW1wb3J0ICdkYXJ0OmFzeW5jJzsKaW1wb3J0ICdkYXJ0OmlvJzsKCmltcG9ydCAncGFja2FnZTpjbGlfY29yZS9jbGlfY29yZS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOm1hc29uL21hc29uLmRhcnQnOwoKaW1wb3J0ICdjb21tYW5kcy9jaGVja2VkX3Byb2Nlc3MuZGFydCc7CmltcG9ydCAnY29tbWFuZHMvZmx1dHRlcl9jcmVhdGVfY29tbWFuZC5kYXJ0JzsKaW1wb3J0ICdjb21tYW5kcy9tb2R1bGVfdmFycy5kYXJ0JzsKCkZ1dHVyZTx2b2lkPiBydW4oSG9va0NvbnRleHQgY29udGV4dCwKICAgIHtQcm9jZXNzUnVubmVyIHJ1blByb2Nlc3MgPSBQcm9jZXNzLnJ1bn0pIGFzeW5jIHsKICBjb250ZXh0LnZhcnNbJ2lzX21vbm9fcmVwbyddID0gYXdhaXQgRmlsZVV0aWxzLmlzTW9ub1JlcG8oKTsKCiAgLy8gVHVybiB0aGUgbW9kdWxlIGFuc3dlcnMgaW50byB0aGUgYCpfZGlyYCBwYXRoIHZhcmlhYmxlcyB0aGUgYnJpY2sgdXNlcyB0bwogIC8vIGluY2x1ZGUgb3Igc2tpcCB3aG9sZSBkaXJlY3RvcmllcywgYW5kIGRlcml2ZSBgZmlyZWJhc2VgLgogIHJlc29sdmVNb2R1bGVWYXJzKGNvbnRleHQpOwoKICAvLyBUaGUgYXBwIGlzIHNjYWZmb2xkZWQgYmVmb3JlIGdlbmVyYXRpb24gc28gdGhlIGJyaWNrJ3MgYGxpYi9gIGFuZAogIC8vIGBwdWJzcGVjLnlhbWxgIGxhbmQgb24gdG9wIG9mIHdoYXQgYGZsdXR0ZXIgY3JlYXRlYCBwcm9kdWNlZC4KICBhd2FpdCBGbHV0dGVyQ3JlYXRlQ29tbWFuZChydW5Qcm9jZXNzOiBydW5Qcm9jZXNzKS5ydW4oY29udGV4dCk7Cn0K", "type": "text" }, { "path": "pubspec.yaml", "data": - "bmFtZTogZmx1dHRlcl9wcm9qZWN0X3dpdGhfbW9ub19yZXBvX2hvb2tzCgplbnZpcm9ubWVudDoKICBzZGs6ICc+PTMuMC4wIDw0LjAuMCcKCmRlcGVuZGVuY2llczoKICBtYXNvbjogXjAuMS4yCiAgbWFzb25fbG9nZ2VyOiBeMC4zLjEKICBjbGlfY29yZTogXjAuMC4yCiAgcGF0aDogXjEuOC4wCgpkZXZfZGVwZW5kZW5jaWVzOgogIGZsdXR0ZXJfbGludHM6IF41LjAuMAogIHRlc3Q6IF4xLjIxLjAK", + "bmFtZTogZmx1dHRlcl9wcm9qZWN0X3dpdGhfbW9ub19yZXBvX2hvb2tzCgplbnZpcm9ubWVudDoKICBzZGs6ICc+PTMuNi4wIDw0LjAuMCcKCmRlcGVuZGVuY2llczoKICBtYXNvbjogXjAuMS4yCiAgbWFzb25fbG9nZ2VyOiBeMC4zLjEKICBjbGlfY29yZTogXjAuMC4yCiAgcGF0aDogXjEuOC4wCgpkZXZfZGVwZW5kZW5jaWVzOgogIGZsdXR0ZXJfbGludHM6IF42LjAuMAogIHRlc3Q6IF4xLjIxLjAKICBtb2NrdGFpbDogXjEuMC41Cgo=", "type": "text" } ], diff --git a/packages/nonstop_cli/lib/commands/doctor/command.dart b/packages/nonstop_cli/lib/commands/doctor/command.dart index 6645ad0..e503a84 100644 --- a/packages/nonstop_cli/lib/commands/doctor/command.dart +++ b/packages/nonstop_cli/lib/commands/doctor/command.dart @@ -6,9 +6,12 @@ import 'package:nonstop_cli/utils/utils.dart'; final class DoctorCommand extends Command { DoctorCommand({ required Logger logger, - }) : _logger = logger; + Doctor? doctor, + }) : _logger = logger, + _doctor = doctor ?? Doctor(logger: logger); final Logger _logger; + final Doctor _doctor; @override final String description = 'Show information about the installed tooling.'; @@ -22,8 +25,7 @@ final class DoctorCommand extends Command { @override Future run() async { _logger.logSignature(); - Doctor doctor = Doctor(logger: _logger); - final bool success = await doctor.diagnose(); + final bool success = await _doctor.diagnose(); return success ? ExitCode.success.code : ExitCode.tempFail.code; } } diff --git a/packages/nonstop_cli/lib/commands/doctor/src/utils/comand_version_validator.dart b/packages/nonstop_cli/lib/commands/doctor/src/utils/comand_version_validator.dart index e5c4733..36f8ff7 100644 --- a/packages/nonstop_cli/lib/commands/doctor/src/utils/comand_version_validator.dart +++ b/packages/nonstop_cli/lib/commands/doctor/src/utils/comand_version_validator.dart @@ -8,7 +8,7 @@ Future commandVersionValidator( List arguments = const ['--version'], }) async { try { - final result = Process.runSync(command, arguments); + final result = validator.runProcess(command, arguments); if (result.exitCode != 0) { return ValidationResult( ValidationType.missing, diff --git a/packages/nonstop_cli/lib/commands/doctor/src/utils/doctor_validator.dart b/packages/nonstop_cli/lib/commands/doctor/src/utils/doctor_validator.dart index c64e45a..d7a2b2a 100644 --- a/packages/nonstop_cli/lib/commands/doctor/src/utils/doctor_validator.dart +++ b/packages/nonstop_cli/lib/commands/doctor/src/utils/doctor_validator.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:nonstop_cli/commands/doctor/src/utils/utils.dart'; enum ValidationType { crash, missing, partial, notAvailable, success } @@ -6,7 +7,10 @@ enum ValidationType { crash, missing, partial, notAvailable, success } enum ValidationMessageType { error, warning, information } abstract class DoctorValidator { - DoctorValidator(this.title); + DoctorValidator(this.title, {this.runProcess = Process.runSync}); + + /// Executes tooling checks; injectable for isolated diagnostics and tests. + final ProcessResult Function(String, List) runProcess; final String title; diff --git a/packages/nonstop_cli/lib/commands/doctor/src/validators/dart_validator.dart b/packages/nonstop_cli/lib/commands/doctor/src/validators/dart_validator.dart index 5f1f94c..527e030 100644 --- a/packages/nonstop_cli/lib/commands/doctor/src/validators/dart_validator.dart +++ b/packages/nonstop_cli/lib/commands/doctor/src/validators/dart_validator.dart @@ -1,7 +1,7 @@ import 'package:nonstop_cli/commands/doctor/src/utils/utils.dart'; class DartValidator extends DoctorValidator { - DartValidator() : super('Dart'); + DartValidator({super.runProcess}) : super('Dart'); @override String get installHelp => 'https://dart.dev/get-dart'; diff --git a/packages/nonstop_cli/lib/commands/doctor/src/validators/flutter_validator.dart b/packages/nonstop_cli/lib/commands/doctor/src/validators/flutter_validator.dart index 579e8d8..d6e1598 100644 --- a/packages/nonstop_cli/lib/commands/doctor/src/validators/flutter_validator.dart +++ b/packages/nonstop_cli/lib/commands/doctor/src/validators/flutter_validator.dart @@ -1,7 +1,7 @@ import 'package:nonstop_cli/commands/doctor/src/utils/utils.dart'; class FlutterValidator extends DoctorValidator { - FlutterValidator() : super('Flutter'); + FlutterValidator({super.runProcess}) : super('Flutter'); @override String get installHelp => 'https://flutter.dev/docs/get-started/install'; diff --git a/packages/nonstop_cli/lib/commands/doctor/src/validators/melos_validator.dart b/packages/nonstop_cli/lib/commands/doctor/src/validators/melos_validator.dart index e4814ca..281ad44 100644 --- a/packages/nonstop_cli/lib/commands/doctor/src/validators/melos_validator.dart +++ b/packages/nonstop_cli/lib/commands/doctor/src/validators/melos_validator.dart @@ -1,7 +1,7 @@ import 'package:nonstop_cli/commands/doctor/src/utils/utils.dart'; class MelosValidator extends DoctorValidator { - MelosValidator() : super('Melos'); + MelosValidator({super.runProcess}) : super('Melos'); @override String get installHelp => diff --git a/packages/nonstop_cli/pubspec.yaml b/packages/nonstop_cli/pubspec.yaml index ecdefe7..3bf64d3 100644 --- a/packages/nonstop_cli/pubspec.yaml +++ b/packages/nonstop_cli/pubspec.yaml @@ -17,31 +17,34 @@ screenshots: path: _images/cli.png environment: - sdk: ^3.0.0 + sdk: '>=3.6.0 <4.0.0' dependencies: args: ^2.5.0 - cli_completion: ^0.5.1 + cli_completion: ^0.6.0 cli_core: ^0.0.2 mason: ^0.1.0 mason_logger: ^0.3.1 meta: ^1.15.0 path: ^1.9.0 process_run: ^1.2.1+1 - pub_updater: ^0.5.0 + pub_updater: ^0.6.0 universal_io: ^2.2.2 yaml_edit: ^2.2.1 dev_dependencies: - analyzer: ^8.4.0 + analyzer: ^13.0.0 build_runner: ^2.4.12 build_verify: ^3.1.0 build_version: ^2.1.1 - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 mocktail: ^1.0.4 test: ^1.25.8 + yaml: ^3.1.4 executables: nonstop: ns: nsio: + +resolution: workspace diff --git a/packages/nonstop_cli/test/coverage_gate_test.dart b/packages/nonstop_cli/test/coverage_gate_test.dart index b7ac52e..085f8da 100644 --- a/packages/nonstop_cli/test/coverage_gate_test.dart +++ b/packages/nonstop_cli/test/coverage_gate_test.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:io'; +import 'dart:isolate'; import 'package:nonstop_cli/commands/create/flutter_project_with_mono_repo_bundle.dart'; import 'package:path/path.dart' as p; @@ -8,7 +9,11 @@ import 'package:test/test.dart'; void main() { late Directory workspace; late File script; - final packages = p.absolute('.dart_tool/package_config.json'); + late String packages; + + setUpAll(() async { + packages = (await Isolate.packageConfig)!.toFilePath(); + }); File write(String name, String contents) { final file = File(p.join(workspace.path, name)); diff --git a/packages/nonstop_cli/test/entrypoints_test.dart b/packages/nonstop_cli/test/entrypoints_test.dart new file mode 100644 index 0000000..8e98f9f --- /dev/null +++ b/packages/nonstop_cli/test/entrypoints_test.dart @@ -0,0 +1,55 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:mocktail/mocktail.dart'; +import 'package:test/test.dart'; + +import '../bin/nonstop.dart' as nonstop; +import '../bin/ns.dart' as ns; +import '../bin/nsio.dart' as nsio; + +class _Stdout extends Mock implements Stdout {} + +void main() { + for (final entry + in {'nonstop': nonstop.main, 'ns': ns.main, 'nsio': nsio.main}.entries) { + for (final success in [true, false]) { + test( + '${entry.key} sets the exit status and waits for both output streams (${success ? 'success' : 'usage error'})', + () async { + final output = _Stdout(); + final errors = _Stdout(); + final outputFlushed = Completer(); + final errorsFlushed = Completer(); + for (final stream in [output, errors]) { + when(() => stream.hasTerminal).thenReturn(false); + when(() => stream.supportsAnsiEscapes).thenReturn(false); + } + when(() => output.flush()).thenAnswer((_) => outputFlushed.future); + when(() => errors.flush()).thenAnswer((_) => errorsFlushed.future); + final originalExitCode = exitCode; + try { + await IOOverrides.runZoned(() async { + var finished = false; + final future = entry + .value(success ? ['completion'] : ['--invalid-option']) + .then((_) => finished = true); + await Future.delayed(Duration.zero); + expect(exitCode, success ? 0 : 64); + expect(finished, isFalse); + outputFlushed.complete(); + await Future.delayed(Duration.zero); + expect(finished, isFalse); + errorsFlushed.complete(); + await future; + expect(finished, isTrue); + verify(() => output.flush()).called(1); + verify(() => errors.flush()).called(1); + }, stdout: () => output, stderr: () => errors); + } finally { + exitCode = originalExitCode; + } + }); + } + } +} diff --git a/packages/nonstop_cli/test/quality_gate_test.dart b/packages/nonstop_cli/test/quality_gate_test.dart new file mode 100644 index 0000000..6af5701 --- /dev/null +++ b/packages/nonstop_cli/test/quality_gate_test.dart @@ -0,0 +1,61 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:isolate'; + +import 'package:nonstop_cli/commands/create/flutter_project_with_mono_repo_bundle.dart'; +import 'package:path/path.dart' as p; +import 'package:test/test.dart'; + +void main() { + test( + 'quality checks every owned source and never analyzes copied SDK packages', + () async { + final workspace = await Directory.systemTemp.createTemp('nonstop-quality-'); + addTearDown(() => workspace.delete(recursive: true)); + File write(String relative, String contents) { + final file = File(p.join(workspace.path, relative)); + file.parent.createSync(recursive: true); + return file..writeAsStringSync(contents); + } + + final bundled = flutterProjectWithMonoRepoBundle.files + .singleWhere((file) => file.path.endsWith('/tool/check.dart')); + final script = + write('tool/check.dart', utf8.decode(base64Decode(bundled.data))); + write('packages/example/lib/example.dart', 'void example() {}'); + write('packages/example/test/example_test.dart', 'void main() {}'); + write('packages/example/lib/example.g.dart', 'generated code'); + write('build/ios/SourcePackages/vendor/test/vendor_test.dart', + 'external code'); + write('packages/example/lib/build/generated.dart', 'build output'); + write('packages/example/lib/.dart_tool/cached.dart', 'cached code'); + write('packages/example/lib/.symlinks/vendor.dart', 'linked dependency'); + final executable = write('bin/dart', r'''#!/bin/sh +printf '%s\n' "$@" >> "$CAPTURE_FILE" +printf '%s\n' END >> "$CAPTURE_FILE" +'''); + expect(Process.runSync('chmod', ['+x', executable.path]).exitCode, 0); + final capture = p.join(workspace.path, 'commands.txt'); + final configuration = (await Isolate.packageConfig)!.toFilePath(); + final result = await Process.run( + Platform.resolvedExecutable, + ['--packages=$configuration', script.path], + workingDirectory: workspace.path, + environment: {'PATH': executable.parent.path, 'CAPTURE_FILE': capture}, + ); + expect(result.exitCode, 0, reason: '${result.stdout}\n${result.stderr}'); + final commands = File(capture).readAsStringSync().split('END\n'); + final format = commands[0].trim().split('\n'); + final analyze = commands[1].trim().split('\n'); + expect( + format.take(3), ['format', '--output=none', '--set-exit-if-changed']); + expect(analyze.take(2), ['analyze', '--fatal-infos']); + const sources = [ + 'packages/example/lib/example.dart', + 'packages/example/test/example_test.dart', + 'tool/check.dart', + ]; + expect(format.skip(3), sources); + expect(analyze.skip(2), sources); + }, skip: Platform.isWindows ? 'The process shim uses a POSIX shell.' : false); +} diff --git a/packages/nonstop_cli/test/src/command_runner_test.dart b/packages/nonstop_cli/test/src/command_runner_test.dart index bc68df1..b67ce75 100644 --- a/packages/nonstop_cli/test/src/command_runner_test.dart +++ b/packages/nonstop_cli/test/src/command_runner_test.dart @@ -52,6 +52,29 @@ void main() { verify(() => logger.info(updatePrompt)).called(1); }); + test('verbose mode sets the log level and records explicit command flags', + () async { + expect( + await commandRunner + .run(['--verbose', 'create', '--template', 'app', '--help']), + 0); + verify(() => logger.level = Level.verbose).called(1); + verify(() => logger.detail(' - template: app')).called(1); + verify(() => logger.detail(' - help: true')).called(1); + }); + + test('empty arguments print root usage', () async { + expect(await commandRunner.run([]), 0); + verify(() => logger.info(commandRunner.usage)).called(1); + }); + + test('an unavailable update service does not fail the command', () async { + when(() => pubUpdater.getLatestVersion(any())) + .thenThrow(StateError('offline')); + expect(await commandRunner.run(['--version']), 0); + verify(() => logger.info(packageVersion)).called(1); + }); + test( 'Does not show update message when the shell calls the ' 'completion command', diff --git a/packages/nonstop_cli/test/src/commands/create_behavior_test.dart b/packages/nonstop_cli/test/src/commands/create_behavior_test.dart new file mode 100644 index 0000000..f753504 --- /dev/null +++ b/packages/nonstop_cli/test/src/commands/create_behavior_test.dart @@ -0,0 +1,286 @@ +import 'dart:io'; + +import 'package:args/command_runner.dart'; +import 'package:mason/mason.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:nonstop_cli/commands/create/command.dart'; +import 'package:nonstop_cli/commands/create/modules.dart'; +import 'package:nonstop_cli/commands/create/templates.dart'; +import 'package:nonstop_cli/template.dart'; +import 'package:test/test.dart'; + +class _Logger extends Mock implements Logger {} + +class _Stdin extends Mock implements Stdin {} + +class _Stdout extends Mock implements Stdout {} + +class _Generator extends Mock implements MasonGenerator {} + +class _Hooks extends Mock implements GeneratorHooks {} + +class _Template extends Mock implements Template {} + +T withTerminal(T Function() body, {bool input = true, bool output = true}) { + final stdin = _Stdin(); + final stdout = _Stdout(); + when(() => stdin.hasTerminal).thenReturn(input); + when(() => stdout.hasTerminal).thenReturn(output); + when(() => stdout.supportsAnsiEscapes).thenReturn(false); + return IOOverrides.runZoned(body, stdin: () => stdin, stdout: () => stdout); +} + +CreateCommand commandFor(Logger logger, List arguments) { + final command = CreateCommand(logger: logger); + CommandRunner('nonstop', 'test runner').addCommand(command); + command.argResultOverrides = command.argParser.parse(arguments); + return command; +} + +void main() { + setUpAll(() { + registerFallbackValue(DirectoryGeneratorTarget(Directory('.'))); + registerFallbackValue(Directory('.')); + registerFallbackValue(_Logger()); + }); + + test('templates select the correct bundled project type', () { + final logger = _Logger(); + final expected = { + 'mono': FlutterProjectWithMonoRepoTemplate, + 'package': FlutterPackageForMonoRepoTemplate, + 'app': FlutterAppForMonoRepoTemplate, + 'plugin': FlutterPluginForMonoRepoTemplate, + }; + for (final entry in expected.entries) { + final command = commandFor(logger, ['--template', entry.key, 'my_app']); + expect(command.template.runtimeType, entry.value); + expect(command.name, 'create'); + expect(command.invocation, 'nonstop create [arguments]'); + expect(command.outputDirectory.path, '.'); + } + }); + + test('all templates show output directory and getting-started instructions', + () async { + final logger = _Logger(); + for (final template in CreateCommand(logger: logger).templates) { + await template.onGenerateComplete(logger, Directory('output/my_app')); + expect(template.help, isNotEmpty); + } + verify(() => logger.info(any(that: contains('output/my_app/README.md')))) + .called(4); + verify(() => logger.info(any(that: contains('Created a Flutter')))) + .called(4); + }); + + group('input validation', () { + for (final args in >[ + [], + ['first', 'second'], + ['Invalid'], + ['has-hyphen'] + ]) { + test('rejects invalid project arguments $args', () { + final command = commandFor(_Logger(), args); + expect(() => command.projectName, throwsA(isA())); + }); + } + for (final character in ['"', r'\', r'$']) { + test('rejects unsafe description character $character', () { + final command = commandFor( + _Logger(), ['--description', 'has $character', 'my_app']); + expect( + () => command.projectDescription, throwsA(isA())); + }); + } + test('rejects invalid organization name', () { + final command = + commandFor(_Logger(), ['--defaults', '--org', 'invalid', 'my_app']); + expect(command.getTemplateVars, throwsA(isA())); + }); + test('maps explicit arguments without prompting', () { + final logger = _Logger(); + final command = commandFor(logger, [ + '--template', + 'app', + '--description', + 'My application', + '--org', + 'com.example', + '--output-directory', + 'destination', + 'my_app', + ]); + expect(withTerminal(command.getTemplateVars), { + 'name': 'my_app', + 'description': 'My application', + 'org_name': 'com.example', + }); + expect(command.outputDirectory.path, 'destination'); + verifyNever( + () => logger.prompt(any(), defaultValue: any(named: 'defaultValue'))); + }); + test('uses defaults when either input or output is redirected', () { + for (final input in [true, false]) { + final logger = _Logger(); + final command = commandFor(logger, ['my_app']); + final vars = + withTerminal(command.getTemplateVars, input: input, output: !input); + expect(vars['org_name'], 'com.example'); + expect(vars['description'], contains('Melos-managed project')); + verifyNever(() => + logger.prompt(any(), defaultValue: any(named: 'defaultValue'))); + } + }); + }); + + group('interactive inputs', () { + test('trims answers for description and organization', () { + final logger = _Logger(); + when(() => logger.prompt(any(), defaultValue: any(named: 'defaultValue'))) + .thenAnswer((call) => + (call.positionalArguments.first as String).startsWith('Describe') + ? ' Our project ' + : ' io.nonstop '); + final command = commandFor(logger, ['--template', 'app', 'my_app']); + expect(withTerminal(command.getTemplateVars), { + 'name': 'my_app', + 'description': 'Our project', + 'org_name': 'io.nonstop', + }); + }); + test('empty answers retain defaults', () { + final logger = _Logger(); + when(() => logger.prompt(any(), defaultValue: any(named: 'defaultValue'))) + .thenReturn(' '); + final command = commandFor(logger, ['--template', 'app', 'my_app']); + final vars = withTerminal(command.getTemplateVars); + expect(vars['org_name'], 'com.example'); + expect(vars['description'], contains('Melos-managed project')); + }); + test('failed prompts retain defaults and explain the fallback', () { + final logger = _Logger(); + when(() => logger.prompt(any(), defaultValue: any(named: 'defaultValue'))) + .thenThrow(StateError('no tty')); + final command = commandFor(logger, ['--template', 'app', 'my_app']); + final vars = withTerminal(command.getTemplateVars); + expect(vars['org_name'], 'com.example'); + verify(() => logger.detail( + any(that: contains('Prompt unavailable, using default:')))).called(2); + }); + test('module picker respects explicit choices and renders every label', () { + final logger = _Logger(); + when(() => logger.chooseAny( + any(), + choices: any(named: 'choices'), + defaultValues: any(named: 'defaultValues'), + display: any(named: 'display'), + )).thenAnswer((call) { + final choices = call.namedArguments[#choices] as List; + final defaults = + call.namedArguments[#defaultValues] as List; + final display = + call.namedArguments[#display] as String Function(TemplateModule); + expect(choices.map((m) => m.key), isNot(contains('auth'))); + expect(defaults.every((m) => m.defaultValue), isTrue); + for (final module in choices) { + expect(display(module), contains(module.label)); + expect(display(module), contains(module.description)); + expect(module.toString(), '${module.label} — ${module.description}'); + } + return choices.where((m) => m.key == 'firestore').toList(); + }); + final command = commandFor(logger, ['--no-auth', 'my_app']); + final modules = withTerminal(command.selectedModules); + expect(modules['auth'], isFalse); + expect(modules['firestore'], isTrue); + expect( + modules.entries + .where((e) => e.key != 'firestore') + .map((e) => e.value), + everyElement(isFalse)); + }); + test('failed module picker retains explicit flags and recommended defaults', + () { + final logger = _Logger(); + when(() => logger.chooseAny( + any(), + choices: any(named: 'choices'), + defaultValues: any(named: 'defaultValues'), + display: any(named: 'display'), + )).thenThrow(StateError('no tty')); + final command = commandFor(logger, ['--no-auth', 'my_app']); + final modules = withTerminal(command.selectedModules); + expect(modules['auth'], isFalse); + expect(modules['network'], isTrue); + verify(() => logger.detail(any( + that: contains('Module picker unavailable, using defaults:')))) + .called(1); + }); + }); + + test( + 'generation passes updated variables through hooks and overwrites skeleton files', + () async { + final logger = _Logger(); + final generator = _Generator(); + final hooks = _Hooks(); + final template = _Template(); + final events = []; + when(() => generator.hooks).thenReturn(hooks); + when(() => hooks.preGen( + vars: any(named: 'vars'), + onVarsChanged: any(named: 'onVarsChanged'), + workingDirectory: any(named: 'workingDirectory'), + logger: logger)).thenAnswer((call) async { + events.add('pre'); + expect(call.namedArguments[#workingDirectory], 'destination'); + final vars = call.namedArguments[#vars] as Map; + expect(vars['name'], 'my_app'); + (call.namedArguments[#onVarsChanged] as void Function( + Map))({...vars, 'prepared': true}); + }); + when(() => generator.generate(any(), + vars: any(named: 'vars'), + logger: logger, + fileConflictResolution: FileConflictResolution.overwrite)) + .thenAnswer((call) async { + events.add('generate'); + expect( + (call.positionalArguments.first as DirectoryGeneratorTarget).dir.path, + 'destination'); + expect((call.namedArguments[#vars] as Map)['prepared'], isTrue); + return []; + }); + when(() => hooks.postGen( + vars: any(named: 'vars'), + onVarsChanged: any(named: 'onVarsChanged'), + workingDirectory: any(named: 'workingDirectory'), + logger: logger)).thenAnswer((call) async { + events.add('post'); + final vars = call.namedArguments[#vars] as Map; + expect(vars['prepared'], isTrue); + (call.namedArguments[#onVarsChanged] as void Function( + Map))({...vars, 'finished': true}); + }); + when(() => template.onGenerateComplete(logger, any())) + .thenAnswer((call) async { + events.add('summary'); + expect((call.positionalArguments[1] as Directory).path, + 'destination/my_app'); + }); + final command = + commandFor(logger, ['--defaults', '-o', 'destination', 'my_app']); + expect(await command.runCreate(generator, template), 0); + expect(events, ['pre', 'generate', 'post', 'summary']); + final running = CreateCommand( + logger: logger, generatorFromBundle: (_) async => generator); + running.argResultOverrides = + running.argParser.parse(['--defaults', '-o', 'destination', 'my_app']); + expect(await running.run(), 0); + expect(events.skip(4), ['pre', 'generate', 'post']); + verify(() => logger.info(any(that: contains('Created a Flutter project')))) + .called(1); + }); +} diff --git a/packages/nonstop_cli/test/src/commands/doctor_command_test.dart b/packages/nonstop_cli/test/src/commands/doctor_command_test.dart new file mode 100644 index 0000000..3d341ab --- /dev/null +++ b/packages/nonstop_cli/test/src/commands/doctor_command_test.dart @@ -0,0 +1,216 @@ +import 'dart:io'; + +import 'package:mason_logger/mason_logger.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:nonstop_cli/commands/doctor/command.dart'; +import 'package:nonstop_cli/commands/doctor/src/doctor.dart'; +import 'package:nonstop_cli/commands/doctor/src/utils/utils.dart'; +import 'package:nonstop_cli/commands/doctor/src/validators/validators.dart'; +import 'package:test/test.dart'; + +class _Logger extends Mock implements Logger {} + +class _Progress extends Mock implements Progress {} + +class _Validator extends DoctorValidator { + _Validator(this.result) : super('Example'); + + final ValidationResult result; + + @override + String get installHelp => 'https://example.test/install'; + + @override + String extractVersion(String output) => output; + + @override + Future validate() async => result; +} + +void main() { + group('tool validators', () { + test('parse known versions and reject unrelated output', () { + final cases = { + DartValidator(): 'Dart SDK version: 3.12.0 (stable)', + FlutterValidator(): 'Flutter 3.44.2 • channel stable', + MelosValidator(): '7.0.0\n', + }; + const versions = { + 'Dart': '3.12.0', + 'Flutter': '3.44.2', + 'Melos': '7.0.0' + }; + for (final entry in cases.entries) { + expect( + entry.key.extractVersion(entry.value), versions[entry.key.title]); + expect(entry.key.extractVersion('unrecognized'), 'Unknown'); + expect(entry.key.installInstructions, + 'Install ${entry.key.title} from ${entry.key.installHelp}'); + expect(Uri.parse(entry.key.installHelp).scheme, 'https'); + } + }); + + test('invoke each tool with --version and return its parsed version', + () async { + final calls = []; + ProcessResult run(String command, List arguments) { + expect(arguments, ['--version']); + calls.add(command); + return ProcessResult( + 1, + 0, + switch (command) { + 'dart' => 'Dart SDK version: 3.12.0', + 'flutter' => 'Flutter 3.44.2', + _ => '7.0.0', + }, + ''); + } + + final validators = [ + DartValidator(runProcess: run), + FlutterValidator(runProcess: run), + MelosValidator(runProcess: run), + ]; + for (final validator in validators) { + final result = await validator.validate(); + expect(result.type, ValidationType.success); + expect(result.statusInfo, endsWith(' is installed')); + expect(result.messages, isEmpty); + } + expect(calls, ['dart', 'flutter', 'melos']); + }); + + test('nonzero exit gives installation help and command output', () async { + final validator = DartValidator( + runProcess: (_, __) => ProcessResult(1, 127, 'not on PATH', ''), + ); + final result = await validator.validate(); + expect(result.type, ValidationType.missing); + expect(result.messages, + contains(const ValidationMessage.error('Command not found'))); + expect(result.messages, + contains(const ValidationMessage.warning('not on PATH'))); + expect(result.messages, + contains(ValidationMessage(validator.installInstructions))); + }); + + test('process exceptions retain the failure code and installation help', + () async { + final validator = MelosValidator(runProcess: (_, __) { + throw const ProcessException('melos', [], 'No executable', 2); + }); + final result = await validator.validate(); + expect(result.type, ValidationType.missing); + expect( + result.messages, + contains(const ValidationMessage.error( + 'Command not found: [2] No executable'))); + expect(result.messages, + contains(ValidationMessage(validator.installInstructions))); + }); + + test('unexpected errors are diagnosed as crashes with a stack trace', + () async { + final validator = FlutterValidator(runProcess: (_, __) { + throw StateError('broken runner'); + }); + final result = await validator.validate(); + expect(result.type, ValidationType.crash); + expect(result.statusInfo, UserMessages.doctorCrash); + expect(result.messages, hasLength(3)); + expect(result.messages[1].message, contains('broken runner')); + expect( + result.messages.last.message, contains('doctor_command_test.dart')); + }); + }); + + group('diagnosis', () { + for (final type in ValidationType.values) { + test('reports $type and maps it to the correct exit code', () async { + final logger = _Logger(); + final progress = _Progress(); + when(() => logger.progress(any())).thenReturn(progress); + final result = ValidationResult(type, const [ + ValidationMessage.error('error detail'), + ValidationMessage.warning('warning detail'), + ValidationMessage('information detail'), + ]); + final doctor = Doctor(logger: logger) + ..validators = [_Validator(result)]; + final command = DoctorCommand(logger: logger, doctor: doctor); + expect(command.name, 'doctor'); + expect(command.invocation, 'nonstop doctor'); + expect(command.description, contains('installed tooling')); + expect( + await command.run(), + type == ValidationType.crash || type == ValidationType.missing + ? 75 + : 0); + final summary = 'Example ${result.typeStr} ${result.leadingIcon}'; + if (type == ValidationType.success) { + verify(() => progress.complete(summary)).called(1); + verify(() => logger.info(UserMessages.summarizeDoctorCheckup(0))) + .called(1); + } else { + verify(() => progress.fail(summary)).called(1); + verify(() => logger.info(UserMessages.summarizeDoctorCheckup(1))) + .called(1); + } + verify(() => logger.err(' 🚩 error detail')).called(1); + verify(() => logger.alert(' ⚠️ warning detail')).called(1); + verify(() => logger.info(' 👉 information detail')).called(1); + }); + } + + test('uses specific status information and counts multiple issues', + () async { + final logger = _Logger(); + final progress = _Progress(); + when(() => logger.progress(any())).thenReturn(progress); + final doctor = Doctor(logger: logger); + expect( + doctor.validators.map((v) => v.title), ['Flutter', 'Dart', 'Melos']); + doctor.validators = [ + _Validator(ValidationResult(ValidationType.partial, [], + statusInfo: 'needs setup')), + _Validator(ValidationResult(ValidationType.notAvailable, [])), + _Validator(ValidationResult(ValidationType.success, [], + statusInfo: '1.2.3 installed')), + ]; + expect(await doctor.diagnose(), isTrue); + verify(() => progress.fail('Example needs setup ⌛')).called(1); + verify(() => progress.complete('Example 1.2.3 installed ✅')).called(1); + verify(() => logger.info('❗ Doctor found issues in 2 categories.')) + .called(1); + }); + }); + + test('validation message values preserve type, equality and display', () { + const info = ValidationMessage('detail'); + const error = ValidationMessage.error('detail'); + const warning = ValidationMessage.warning('detail'); + expect(info.isInformation, isTrue); + expect(info.isError, isFalse); + expect(info.isWarning, isFalse); + expect(error.isError, isTrue); + expect(warning.isWarning, isTrue); + expect(info, const ValidationMessage('detail')); + expect(info, isNot(error)); + expect(info, isNot(const ValidationMessage('different'))); + expect(info, isNot('detail')); + expect(info.hashCode, const ValidationMessage('detail').hashCode); + final uniqueMessages = {info, error}; + uniqueMessages.add(const ValidationMessage('detail')); + expect(uniqueMessages, hasLength(2)); + expect(info.toString(), ' 👉 detail'); + expect(error.toString(), ' 🚩 detail'); + expect(warning.toString(), ' ⚠️ detail'); + expect(const ValidationMessage('').toString(), ''); + final result = ValidationResult.crash(StateError('oops')); + expect(result.messages, hasLength(2)); + expect(result.toString(), contains('ValidationType.crash')); + expect(UserMessages.exceptionMessage(Exception('unknown')), + 'Command not found'); + }); +} diff --git a/packages/nonstop_cli/test/template_contract_test.dart b/packages/nonstop_cli/test/template_contract_test.dart index d02b975..0754389 100644 --- a/packages/nonstop_cli/test/template_contract_test.dart +++ b/packages/nonstop_cli/test/template_contract_test.dart @@ -1,12 +1,14 @@ import 'dart:convert'; import 'dart:io'; +import 'package:analyzer/dart/analysis/features.dart'; import 'package:analyzer/dart/analysis/utilities.dart'; import 'package:analyzer/dart/ast/ast.dart'; import 'package:mason/mason.dart'; import 'package:nonstop_cli/commands/create/flutter_project_with_mono_repo_bundle.dart'; import 'package:path/path.dart' as path; import 'package:test/test.dart'; +import 'package:yaml/yaml.dart'; import '../bricks/flutter_project_with_mono_repo/hooks/commands/module_vars.dart'; @@ -55,16 +57,21 @@ void main() { .listSync(recursive: true) .whereType() .where((file) => - file.path.endsWith('.dart') || - path.basename(file.path) == 'pubspec.yaml') + !path.split(path.relative(file.path, from: root)).any((part) => + ['test', 'coverage', '.dart_tool', 'build'].contains(part)) && + (file.path.endsWith('.dart') || + path.basename(file.path) == 'pubspec.yaml')) .map((file) => path.relative(file.path, from: root)) .toSet(); final bundled = flutterProjectWithMonoRepoBundle.hooks; expect(bundled.map((file) => file.path).toSet(), expected); for (final file in bundled) { - expect(base64Decode(file.data), - File(path.join(root, file.path)).readAsBytesSync(), - reason: file.path); + final source = File(path.join(root, file.path)); + final expectedBytes = file.path == 'pubspec.yaml' + ? utf8.encode(source.readAsStringSync().replaceAll( + RegExp(r'^resolution:\s*workspace\s*$', multiLine: true), '')) + : source.readAsBytesSync(); + expect(base64Decode(file.data), expectedBytes, reason: file.path); } }); @@ -98,12 +105,41 @@ void main() { reason: 'mask $mask'); expect( target.files.containsKey('contract_app/tool/coverage.dart'), isTrue); + final manifests = { + for (final entry in target.files.entries + .where((entry) => entry.key.endsWith('/pubspec.yaml'))) + entry.key: loadYaml(utf8.decode(entry.value)) as YamlMap, + }; + final workspace = manifests['contract_app/pubspec.yaml']!; + expect(workspace['melos'], isA()); + final members = (workspace['workspace'] as YamlList).cast(); + expect( + members.toSet(), + manifests.keys + .where((file) => file != 'contract_app/pubspec.yaml') + .map((file) => path.posix + .relative(path.posix.dirname(file), from: 'contract_app')) + .toSet(), + reason: 'mask $mask: workspace members must match generated packages', + ); + for (final member in members) { + expect(manifests['contract_app/$member/pubspec.yaml']!['resolution'], + 'workspace', + reason: '$member, mask $mask'); + } for (final entry in target.files.entries .where((entry) => entry.key.endsWith('.dart'))) { final source = utf8.decode(entry.value); expect(source, isNot(contains('{{')), reason: '${entry.key}, mask $mask: unresolved template'); - final parsed = parseString(content: source, throwIfDiagnostics: false); + final parsed = parseString( + content: source, + // Parse the language version promised by the generated workspace, + // rather than unreleased language experiments in a newer analyzer. + featureSet: FeatureSet.latestLanguageVersion() + .restrictToVersion(Version(3, 8, 0)), + throwIfDiagnostics: false, + ); expect(parsed.errors, isEmpty, reason: '${entry.key}, mask $mask'); for (final directive in parsed.unit.directives.whereType()) { diff --git a/packages/nonstop_cli/test/verify_template_test.dart b/packages/nonstop_cli/test/verify_template_test.dart new file mode 100644 index 0000000..24a4904 --- /dev/null +++ b/packages/nonstop_cli/test/verify_template_test.dart @@ -0,0 +1,239 @@ +import 'dart:io'; + +import 'package:mocktail/mocktail.dart'; +import 'package:path/path.dart' as p; +import 'package:test/test.dart'; + +import '../tool/verify_template.dart' as verifier; + +class _Stdout extends Mock implements Stdout {} + +void main() { + late Directory root; + + setUp(() async { + root = await Directory.systemTemp.createTemp('verify-template-test-'); + }); + + tearDown(() => root.delete(recursive: true)); + + const modules = [ + 'network', + 'auth', + 'firestore', + 'notifications', + 'analytics', + 'crashlytics', + 'feature-flags', + 'developer', + 'dashboard', + ]; + for (final profile in [ + 'full', + 'default', + 'minimal', + 'auth-only', + 'firestore-only', + 'network-only', + ]) { + test('$profile renders its flags then lints, measures coverage and builds', + () async { + final output = Directory(p.join(root.path, 'generated')); + final workspace = p.join(output.path, 'verified_app'); + final cli = p.join(root.path, 'cli'); + final calls = <(String, List, String)>[]; + final messages = []; + var temporaryDirectories = 0; + + await verifier.main( + [profile], + script: File(p.join(cli, 'tool', 'verify_template.dart')).uri, + createTemporaryDirectory: (prefix) async { + expect(prefix, 'nonstop-verify-'); + temporaryDirectories++; + return output; + }, + writeLine: messages.add, + runProcess: (executable, arguments, directory) async { + calls.add((executable, arguments, directory)); + return 0; + }, + ); + + expect(temporaryDirectories, 1); + final expectedCalls = [ + ( + 'dart', + [ + 'run', + 'bin/nonstop.dart', + 'create', + 'verified_app', + '--defaults', + if (profile != 'default') + for (final module in modules) + '--${profile == 'full' || profile == '$module-only' ? '' : 'no-'}$module', + '--output-directory', + output.path, + ], + cli, + ), + ('dart', ['run', 'melos', 'run', 'lint'], workspace), + ('dart', ['run', 'melos', 'run', 'coverage'], workspace), + ( + 'flutter', + ['build', 'web'], + p.join(workspace, 'apps', 'verified_app') + ), + ]; + expect(calls, hasLength(expectedCalls.length)); + for (var index = 0; index < expectedCalls.length; index++) { + expect(calls[index].$1, expectedCalls[index].$1); + expect(calls[index].$2, expectedCalls[index].$2); + expect(calls[index].$3, expectedCalls[index].$3); + } + expect(messages, [ + 'Verifying $profile in $workspace', + 'Verified $profile: analysis, tests, 100% coverage, web build.', + ]); + }); + } + + test( + 'no arguments uses full with real script, temporary directory and stdout', + () async { + final output = _Stdout(); + final calls = <(String, List, String)>[]; + Directory? generated; + try { + await IOOverrides.runZoned(() async { + await verifier.main([], + runProcess: (executable, arguments, directory) async { + calls.add((executable, arguments, directory)); + if (calls.length == 1) { + generated = Directory(arguments.last); + expect(generated!.existsSync(), isTrue); + expect(p.basename(generated!.path), startsWith('nonstop-verify-')); + expect(directory, + p.dirname(p.dirname(File.fromUri(Platform.script).path))); + } + return 0; + }); + }, stdout: () => output); + expect(calls, hasLength(4)); + expect(calls.first.$2.sublist(5, 14), [ + for (final module in modules) '--$module', + ]); + expect(generated!.existsSync(), isTrue, + reason: 'The workspace remains available for diagnosis.'); + verify(() => output.writeln( + 'Verifying full in ${p.join(generated!.path, 'verified_app')}')) + .called(1); + verify(() => output.writeln( + 'Verified full: analysis, tests, 100% coverage, web build.')) + .called(1); + } finally { + if (generated != null) await generated!.delete(recursive: true); + } + }); + + for (final arguments in [ + ['unknown'], + ['full', 'minimal'], + ]) { + test('rejects $arguments before filesystem or process work', () async { + var sideEffects = 0; + await expectLater( + verifier.main( + arguments, + createTemporaryDirectory: (_) async { + sideEffects++; + return root; + }, + writeLine: (_) => sideEffects++, + runProcess: (_, __, ___) async { + sideEffects++; + return 0; + }, + ), + arguments.length == 1 ? throwsArgumentError : throwsStateError, + ); + expect(sideEffects, 0); + }); + } + + for (var failedStep = 0; failedStep < 4; failedStep++) { + test( + 'failure at step $failedStep stops later steps and retains diagnostics', + () async { + final calls = <(String, List, String)>[]; + final messages = []; + await expectLater( + verifier.main( + ['minimal'], + createTemporaryDirectory: (_) async => root, + writeLine: messages.add, + runProcess: (executable, arguments, directory) async { + calls.add((executable, arguments, directory)); + return calls.length == failedStep + 1 ? 37 : 0; + }, + ), + throwsA(isA() + .having((error) => error.errorCode, 'errorCode', 37) + .having((error) => error.executable, 'executable', + failedStep == 3 ? 'flutter' : 'dart') + .having((error) => error.arguments, 'arguments', + predicate>((args) => args == calls.last.$2)) + .having( + (error) => error.message, + 'message', + predicate((message) => + message == 'Verification failed in ${calls.last.$3}'))), + ); + expect(calls, hasLength(failedStep + 1)); + expect(messages, hasLength(1)); + expect(messages.single, startsWith('Verifying minimal in ')); + expect(root.existsSync(), isTrue); + }); + } + + test('process launch errors propagate without reporting verification success', + () async { + final failure = ProcessException('dart', [], 'Cannot start'); + final messages = []; + var calls = 0; + await expectLater( + verifier.main( + ['default'], + createTemporaryDirectory: (_) async => root, + writeLine: messages.add, + runProcess: (_, __, ___) async { + calls++; + throw failure; + }, + ), + throwsA(same(failure)), + ); + expect(calls, 1); + expect(messages, hasLength(1)); + }); + + test('real process adapter passes arguments, working directory and exit code', + () async { + final script = File(p.join(root.path, 'probe.dart')); + await script.writeAsString(''' +import 'dart:io'; +void main(List args) { + File('result.txt').writeAsStringSync(args.single); + exitCode = 37; +} +'''); + final result = await verifier.runInheritedProcess( + Platform.resolvedExecutable, + [script.path, 'argument with spaces'], + root.path); + expect(result, 37); + expect(await File(p.join(root.path, 'result.txt')).readAsString(), + 'argument with spaces'); + }); +} diff --git a/packages/nonstop_cli/tool/verify_template.dart b/packages/nonstop_cli/tool/verify_template.dart index cfd290c..9929f00 100644 --- a/packages/nonstop_cli/tool/verify_template.dart +++ b/packages/nonstop_cli/tool/verify_template.dart @@ -3,9 +3,26 @@ import 'dart:io'; import 'package:nonstop_cli/commands/create/modules.dart'; import 'package:path/path.dart' as p; +typedef VerificationProcessRunner = Future Function( + String executable, List arguments, String directory); + +/// Runs a verification step with the same terminal streams as this command. +Future runInheritedProcess( + String executable, List arguments, String directory) async { + final process = await Process.start(executable, arguments, + workingDirectory: directory, mode: ProcessStartMode.inheritStdio); + return process.exitCode; +} + /// Run from packages/nonstop_cli: dart run tool/verify_template.dart [profile]. /// Leaves its temporary workspace available for diagnosis, never edits a repo. -Future main(List args) async { +Future main( + List args, { + VerificationProcessRunner runProcess = runInheritedProcess, + Uri? script, + Future Function(String prefix)? createTemporaryDirectory, + void Function(String message)? writeLine, +}) async { final profile = args.isEmpty ? 'full' : args.single; const profiles = [ 'full', @@ -18,16 +35,17 @@ Future main(List args) async { if (!profiles.contains(profile)) { throw ArgumentError('Choose one of $profiles'); } - final cli = p.dirname(p.dirname(File.fromUri(Platform.script).path)); - final output = await Directory.systemTemp.createTemp('nonstop-verify-'); + final cli = + p.dirname(p.dirname(File.fromUri(script ?? Platform.script).path)); + final output = await (createTemporaryDirectory ?? + Directory.systemTemp.createTemp)('nonstop-verify-'); + final report = writeLine ?? stdout.writeln; const name = 'verified_app'; final workspace = p.join(output.path, name); - stdout.writeln('Verifying $profile in $workspace'); + report('Verifying $profile in $workspace'); Future run( String executable, List arguments, String directory) async { - final process = await Process.start(executable, arguments, - workingDirectory: directory, mode: ProcessStartMode.inheritStdio); - final result = await process.exitCode; + final result = await runProcess(executable, arguments, directory); if (result != 0) { throw ProcessException( executable, arguments, 'Verification failed in $directory', result); @@ -57,6 +75,5 @@ Future main(List args) async { await run('dart', ['run', 'melos', 'run', 'lint'], workspace); await run('dart', ['run', 'melos', 'run', 'coverage'], workspace); await run('flutter', ['build', 'web'], p.join(workspace, 'apps', name)); - stdout - .writeln('Verified $profile: analysis, tests, 100% coverage, web build.'); + report('Verified $profile: analysis, tests, 100% coverage, web build.'); } diff --git a/packages/ns_firebase_utils/lib/analytics/analytics_service.dart b/packages/ns_firebase_utils/lib/analytics/analytics_service.dart index fdcc955..c636c0f 100644 --- a/packages/ns_firebase_utils/lib/analytics/analytics_service.dart +++ b/packages/ns_firebase_utils/lib/analytics/analytics_service.dart @@ -15,7 +15,8 @@ class AppAnalytics implements FirebaseAnalytics { final Map _userInfo = {}; /// Constructor initializes the Firebase Analytics instance - AppAnalytics() : _firebaseAnalytics = FirebaseAnalytics.instance; + AppAnalytics({FirebaseAnalytics? firebaseAnalytics}) + : _firebaseAnalytics = firebaseAnalytics ?? FirebaseAnalytics.instance; /// Sets user information for analytics tracking Future setUser({ @@ -71,6 +72,7 @@ class AppAnalytics implements FirebaseAnalytics { String? category, Map? parameters, AnalyticsCallOptions? callOptions, + List? items, }) { // Sanitize event name and category by replacing spaces with underscores name = name?.replaceAll(RegExp(r' '), '_'); @@ -114,6 +116,7 @@ class AppAnalytics implements FirebaseAnalytics { name: eventName, parameters: newParameters, callOptions: callOptions, + items: items, ); } diff --git a/packages/ns_firebase_utils/lib/api/firebase_api.dart b/packages/ns_firebase_utils/lib/api/firebase_api.dart index 710c994..bc6cc3a 100644 --- a/packages/ns_firebase_utils/lib/api/firebase_api.dart +++ b/packages/ns_firebase_utils/lib/api/firebase_api.dart @@ -7,9 +7,11 @@ SetOptions mergeOption = SetOptions( class FirebaseApi { final String path; late CollectionReference ref; + final FirebaseFirestore _firestore; - FirebaseApi(this.path) { - ref = FirebaseFirestore.instance.collection(path); + FirebaseApi(this.path, {FirebaseFirestore? firestore}) + : _firestore = firestore ?? FirebaseFirestore.instance { + ref = _firestore.collection(path); } Future getDataCollection() { @@ -40,7 +42,7 @@ class FirebaseApi { } WriteBatch batch() { - return FirebaseFirestore.instance.batch(); + return _firestore.batch(); } Future updateDocument( diff --git a/packages/ns_firebase_utils/lib/api/firebase_group_api.dart b/packages/ns_firebase_utils/lib/api/firebase_group_api.dart index 84d8606..3fad8ef 100644 --- a/packages/ns_firebase_utils/lib/api/firebase_group_api.dart +++ b/packages/ns_firebase_utils/lib/api/firebase_group_api.dart @@ -8,7 +8,8 @@ class FirebaseGroupApi { final String collection; late Query> query; - FirebaseGroupApi(this.collection) { - query = FirebaseFirestore.instance.collectionGroup(collection); + FirebaseGroupApi(this.collection, {FirebaseFirestore? firestore}) { + query = + (firestore ?? FirebaseFirestore.instance).collectionGroup(collection); } } diff --git a/packages/ns_firebase_utils/lib/src.dart b/packages/ns_firebase_utils/lib/src.dart index ff24a80..cc3b85d 100644 --- a/packages/ns_firebase_utils/lib/src.dart +++ b/packages/ns_firebase_utils/lib/src.dart @@ -1,4 +1,4 @@ -library ns_firebase_utils; +library; import 'package:firebase_core/firebase_core.dart'; import 'package:ns_firebase_utils/analytics/analytics_service.dart'; diff --git a/packages/ns_firebase_utils/lib/storage/firebase_storage_service.dart b/packages/ns_firebase_utils/lib/storage/firebase_storage_service.dart index baffdae..fd4df54 100644 --- a/packages/ns_firebase_utils/lib/storage/firebase_storage_service.dart +++ b/packages/ns_firebase_utils/lib/storage/firebase_storage_service.dart @@ -10,10 +10,12 @@ class FirebaseStorageService { String path, { Function(String)? onProgress, Map customMetadata = const {}, + FirebaseStorage? storage, }) async { appLogsNS("uploadTask start"); var stopwatch = Stopwatch()..start(); - Reference storageReference = FirebaseStorage.instance.ref().child(path); + Reference storageReference = + (storage ?? FirebaseStorage.instance).ref().child(path); final UploadTask uploadTask = storageReference.putFile( file, SettableMetadata( diff --git a/packages/ns_firebase_utils/lib/ui/firestore/query_builder.dart b/packages/ns_firebase_utils/lib/ui/firestore/query_builder.dart index 4138621..8dcdc81 100644 --- a/packages/ns_firebase_utils/lib/ui/firestore/query_builder.dart +++ b/packages/ns_firebase_utils/lib/ui/firestore/query_builder.dart @@ -487,6 +487,8 @@ class FirestoreListView extends FirestoreQueryBuilder { addAutomaticKeepAlives: addAutomaticKeepAlives, addRepaintBoundaries: addRepaintBoundaries, addSemanticIndexes: addSemanticIndexes, + // Preserve support for Flutter releases before scrollCacheExtent. + // ignore: deprecated_member_use cacheExtent: cacheExtent, semanticChildCount: semanticChildCount, dragStartBehavior: dragStartBehavior, diff --git a/packages/ns_firebase_utils/pubspec.yaml b/packages/ns_firebase_utils/pubspec.yaml index 9684b56..c4b3fac 100644 --- a/packages/ns_firebase_utils/pubspec.yaml +++ b/packages/ns_firebase_utils/pubspec.yaml @@ -7,27 +7,30 @@ repository: https://github.com/nonstopio/flutter_forge/tree/main/packages/ns_fir issue_tracker: https://github.com/nonstopio/flutter_forge/issues environment: - sdk: '>=2.17.1 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: flutter: sdk: flutter - cloud_firestore: ^5.5.0 - cloud_functions: ^5.1.5 - fake_cloud_firestore: ^3.1.0 - firebase_analytics: ^11.3.5 - firebase_auth: ^5.3.3 - firebase_core: ^3.8.0 - firebase_crashlytics: ^4.1.5 - firebase_dynamic_links: ^6.0.10 - firebase_messaging: ^15.1.5 - firebase_remote_config: ^5.1.5 - firebase_storage: ^12.3.6 + cloud_firestore: ^6.10.0 + cloud_functions: ^6.5.0 + fake_cloud_firestore: ^4.3.0 + firebase_analytics: ^12.6.0 + firebase_auth: ^6.7.0 + firebase_core: ^4.15.0 + firebase_crashlytics: ^5.4.0 + firebase_messaging: ^16.7.0 + firebase_remote_config: ^6.7.0 + firebase_storage: ^13.6.0 dev_dependencies: + firebase_core_platform_interface: ^8.1.1 + mocktail: ^1.0.4 flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 flutter: + +resolution: workspace diff --git a/packages/ns_firebase_utils/test/analytics_test.dart b/packages/ns_firebase_utils/test/analytics_test.dart new file mode 100644 index 0000000..0aa8c15 --- /dev/null +++ b/packages/ns_firebase_utils/test/analytics_test.dart @@ -0,0 +1,213 @@ +import 'package:firebase_analytics/firebase_analytics.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_core_platform_interface/test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:ns_firebase_utils/analytics/analytics_service.dart'; +import 'package:ns_firebase_utils/api/firebase_api.dart'; +import 'package:ns_firebase_utils/api/firebase_group_api.dart'; +import 'package:ns_firebase_utils/src.dart'; +import 'package:ns_firebase_utils/utils/custom_exception.dart'; + +class MockAnalytics extends Mock implements FirebaseAnalytics {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + setupFirebaseCoreMocks(); + + setUp(() { + NSFirebase.instance = NSFirebase(); + }); + + test('initialization stores release information and configures log callbacks', + () async { + expect(NSFirebase.instance.isInitialized, isFalse); + expect(NSFirebase.instance.buildNumber, isEmpty); + expect(NSFirebase.instance.version, isEmpty); + final logs = []; + final errors = []; + await NSFirebase.instance.init( + printLogs: true, + buildNumber: '42', + version: '1.2', + appLogsFunction: (message, [detail = '']) => logs.add(message), + errorLogsFunction: (message, [error, trace = StackTrace.empty]) => + errors.add(message), + ); + expect(NSFirebase.instance.isInitialized, isTrue); + expect(NSFirebase.instance.buildNumber, '42'); + expect(NSFirebase.instance.version, '1.2'); + expect(logs.single.toString(), contains('Initialized default app')); + errorLogsNS('failure'); + expect(errors, ['failure']); + expect(analytics, isA()); + expect(FirebaseApi('users').ref.path, 'users'); + expect(FirebaseGroupApi('items').query, isNotNull); + await initializeDefault( + name: 'secondary', + options: const FirebaseOptions( + apiKey: 'key', + appId: 'app', + messagingSenderId: 'sender', + projectId: 'project'), + ); + expect(Firebase.app('secondary').name, 'secondary'); + expect(logs.last.toString(), contains('secondary')); + }); + + test('events normalize names, enrich metadata, and omit null values', + () async { + await NSFirebase.instance + .init(printLogs: false, buildNumber: '7', version: '2'); + final backend = MockAnalytics(); + when(() => backend.logEvent( + name: any(named: 'name'), + parameters: any(named: 'parameters'), + callOptions: any(named: 'callOptions'))).thenAnswer((_) async {}); + when(() => backend.setUserId( + id: any(named: 'id'), + callOptions: any(named: 'callOptions'))).thenAnswer((_) async {}); + when(() => backend.setUserProperty( + name: any(named: 'name'), + value: any(named: 'value'), + callOptions: any(named: 'callOptions'))).thenAnswer((_) async {}); + final service = AppAnalytics(firebaseAnalytics: backend); + await service.logEvent(name: 'without_parameters'); + final noParameters = verify(() => backend.logEvent( + name: 'without_parameters', + parameters: captureAny(named: 'parameters'), + callOptions: null)).captured.single as Map; + expect(noParameters['version'], '2-7'); + await service.log(eventName: 'app started'); + final anonymous = verify(() => backend.logEvent( + name: 'app_started', + parameters: captureAny(named: 'parameters'), + callOptions: null)).captured.single as Map; + expect(anonymous['version'], '2-7'); + expect(anonymous.containsKey('id'), isFalse); + expect(DateTime.tryParse(anonymous['date_time'] as String), isNotNull); + await service.setUser(id: 'alice', email: 'a@example.com'); + verify(() => backend.setUserId(id: 'alice', callOptions: null)).called(1); + verify(() => backend.setUserProperty( + name: 'email', value: 'a@example.com', callOptions: null)).called(1); + await service.logEvent( + name: 'button clicked', + category: 'main screen', + parameters: {'missing': null, 'count': 3}); + final identified = verify(() => backend.logEvent( + name: 'button_clicked_main_screen', + parameters: captureAny(named: 'parameters'), + callOptions: null)).captured.single as Map; + expect(identified, containsPair('id', 'alice')); + expect(identified, containsPair('email', 'a@example.com')); + expect(identified, containsPair('count', 3)); + expect(identified.containsKey('missing'), isFalse); + await service.logEvent(name: 'override', parameters: { + 'version': 'custom', + 'id': 'bob', + 'email': 'b@example.com' + }); + final overridden = verify(() => backend.logEvent( + name: 'override', + parameters: captureAny(named: 'parameters'), + callOptions: null)).captured.single as Map; + expect(overridden['version'], 'custom'); + expect(overridden['id'], 'bob'); + expect(overridden['email'], 'b@example.com'); + final items = [AnalyticsEventItem(itemId: 'sku-1', itemName: 'Book')]; + final options = AnalyticsCallOptions(global: true); + when(() => backend.logEvent( + name: 'purchase', + items: items, + parameters: any(named: 'parameters'), + callOptions: options, + )).thenAnswer((_) async {}); + await service.logEvent( + name: 'purchase', items: items, callOptions: options); + verify(() => backend.logEvent( + name: 'purchase', + items: items, + parameters: any(named: 'parameters'), + callOptions: options, + )).called(1); + }); + + test( + 'analytics methods forward arguments and require initialization for app open', + () async { + final backend = MockAnalytics(); + final service = AppAnalytics(firebaseAnalytics: backend); + expect( + () => service.logAppOpen(), + throwsA(isA() + .having((e) => e.code, 'code', 'not_initialized'))); + await NSFirebase.instance + .init(printLogs: false, buildNumber: '1', version: '1'); + const parameters = {'source': 'test'}; + final options = AnalyticsCallOptions(global: true); + when(() => backend.logAppOpen(callOptions: options, parameters: parameters)) + .thenAnswer((_) async {}); + when(() => backend.logJoinGroup( + groupId: 'group', + callOptions: options, + parameters: parameters)).thenAnswer((_) async {}); + when(() => backend.logLogin( + loginMethod: 'password', + callOptions: options, + parameters: parameters)).thenAnswer((_) async {}); + when(() => backend.logSignUp(signUpMethod: 'email', parameters: parameters)) + .thenAnswer((_) async {}); + when(() => backend.logTutorialBegin(parameters: parameters)) + .thenAnswer((_) async {}); + when(() => backend.logTutorialComplete(parameters: parameters)) + .thenAnswer((_) async {}); + when(() => backend.logScreenView( + screenName: 'home', + screenClass: 'Home', + callOptions: options, + parameters: parameters)).thenAnswer((_) async {}); + when(() => backend.setUserId(id: 'user', callOptions: options)) + .thenAnswer((_) async {}); + when(() => backend.setUserProperty( + name: 'tier', + value: 'free', + callOptions: options)).thenAnswer((_) async {}); + await service.logAppOpen(callOptions: options, parameters: parameters); + await service.logJoinGroup( + groupId: 'group', callOptions: options, parameters: parameters); + await service.logLogin( + loginMethod: 'password', callOptions: options, parameters: parameters); + await service.logSignUp(signUpMethod: 'email', parameters: parameters); + await service.logTutorialBegin(parameters: parameters); + await service.logTutorialComplete(parameters: parameters); + await service.logScreenView( + screenName: 'home', + screenClass: 'Home', + callOptions: options, + parameters: parameters); + await service.setUserId(id: 'user', callOptions: options); + await service.setUserProperty( + name: 'tier', value: 'free', callOptions: options); + verifyInOrder([ + () => backend.logAppOpen(callOptions: options, parameters: parameters), + () => backend.logJoinGroup( + groupId: 'group', callOptions: options, parameters: parameters), + () => backend.logLogin( + loginMethod: 'password', + callOptions: options, + parameters: parameters), + () => backend.logSignUp(signUpMethod: 'email', parameters: parameters), + () => backend.logTutorialBegin(parameters: parameters), + () => backend.logTutorialComplete(parameters: parameters), + () => backend.logScreenView( + screenName: 'home', + screenClass: 'Home', + callOptions: options, + parameters: parameters), + () => backend.setUserId(id: 'user', callOptions: options), + () => backend.setUserProperty( + name: 'tier', value: 'free', callOptions: options), + ]); + expect(() => service.resetAnalyticsData(), throwsNoSuchMethodError); + }); +} diff --git a/packages/ns_firebase_utils/test/firebase_api_test.dart b/packages/ns_firebase_utils/test/firebase_api_test.dart new file mode 100644 index 0000000..89b49a5 --- /dev/null +++ b/packages/ns_firebase_utils/test/firebase_api_test.dart @@ -0,0 +1,45 @@ +import 'package:fake_cloud_firestore/fake_cloud_firestore.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:ns_firebase_utils/api/firebase_api.dart'; +import 'package:ns_firebase_utils/api/firebase_group_api.dart' as group; + +void main() { + test( + 'CRUD, snapshots, document references and batches share the injected store', + () async { + final firestore = FakeFirebaseFirestore(); + final api = FirebaseApi('users', firestore: firestore); + expect(api.path, 'users'); + expect((await api.getDataCollection()).docs, isEmpty); + final document = + await api.addDocument(id: 'alice', data: {'name': 'Alice'}); + expect(document.id, 'alice'); + expect((await api.getDocumentById('alice')).data(), {'name': 'Alice'}); + expect(api.getDocumentRef(id: 'alice').path, 'users/alice'); + expect(api.getDocumentRef().id, isNotEmpty); + await api.updateDocument(id: 'alice', data: {'age': 30}); + expect((await api.getDocumentById('alice')).data(), + {'name': 'Alice', 'age': 30}); + expect((await api.streamDataCollection().first).docs, hasLength(1)); + final generated = await api.addDocument(data: {'name': 'Generated'}); + expect(generated.id, isNotEmpty); + final batch = api.batch(); + batch.set(api.getDocumentRef(id: 'batch'), {'batched': true}); + await batch.commit(); + expect((await api.getDataCollection()).docs, hasLength(3)); + await api.removeDocument('alice'); + expect((await api.getDocumentById('alice')).exists, isFalse); + }); + + test('collection groups collect matching nested collections', () async { + final firestore = FakeFirebaseFirestore(); + await firestore.doc('users/a/items/one').set({'value': 1}); + await firestore.doc('users/b/items/two').set({'value': 2}); + final api = group.FirebaseGroupApi('items', firestore: firestore); + expect(api.collection, 'items'); + expect((await api.query.get()).docs.map((doc) => doc.data()['value']), + unorderedEquals([1, 2])); + expect(group.mergeOption.merge, isTrue); + expect(mergeOption.merge, isTrue); + }); +} diff --git a/packages/ns_firebase_utils/test/query_builder_test.dart b/packages/ns_firebase_utils/test/query_builder_test.dart new file mode 100644 index 0000000..6a5c6da --- /dev/null +++ b/packages/ns_firebase_utils/test/query_builder_test.dart @@ -0,0 +1,321 @@ +// Query and snapshot interfaces are mocked only at the Firestore transport boundary. +// ignore_for_file: subtype_of_sealed_class + +import 'dart:async'; + +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:ns_firebase_utils/ui/firestore/query_builder.dart'; + +typedef Json = Map; + +class MockQuery extends Mock implements Query {} + +class MockQuerySnapshot extends Mock implements QuerySnapshot {} + +class MockDocument extends Mock implements QueryDocumentSnapshot {} + +/// Controls each server subscription independently so cancellation, pagination, +/// late results, and failures can be asserted without network timing. +class QueryHarness { + final query = MockQuery(); + final limits = []; + final streams = >>[]; + + QueryHarness() { + when(() => query.limit(any())).thenAnswer((invocation) { + limits.add(invocation.positionalArguments.single as int); + final stream = StreamController>.broadcast(); + streams.add(stream); + return query; + }); + when(() => query.snapshots()).thenAnswer((_) => streams.last.stream); + addTearDown(() async { + for (final stream in streams) { + await stream.close(); + } + }); + } + + void emit(int count) { + final docs = List.generate(count, (index) { + final doc = MockDocument(); + when(() => doc.id).thenReturn('$index'); + when(() => doc.data()).thenReturn({'name': 'item $index'}); + return doc; + }); + final snapshot = MockQuerySnapshot(); + when(() => snapshot.docs).thenReturn(docs); + when(() => snapshot.size).thenReturn(count); + streams.last.add(snapshot); + } +} + +void main() { + testWidgets( + 'query pages preserve progress, restart on query change, and cancel subscriptions', + (tester) async { + final first = QueryHarness(); + final second = QueryHarness(); + late FirestoreQueryBuilderSnapshot snapshot; + final child = Text('stable child'); + Widget build(QueryHarness harness, int pageSize) => MaterialApp( + home: FirestoreQueryBuilder( + query: harness.query, + pageSize: pageSize, + child: child, + builder: (context, value, passedChild) { + snapshot = value; + expect(passedChild, same(child)); + return Text(value.docs.map((doc) => doc.id).join(',')); + }, + )); + await tester.pumpWidget(build(first, 2)); + expect(first.limits, [3]); + expect(snapshot.isFetching, isTrue); + expect(snapshot.isFetchingMore, isFalse); + expect(snapshot.hasData, isFalse); + expect(snapshot.hasError, isFalse); + expect(snapshot.error, isNull); + expect(snapshot.stackTrace, isNull); + snapshot.fetchMore(); + expect(first.limits, [3]); + first.emit(3); + await tester.pumpAndSettle(); + expect(snapshot.hasMore, isTrue); + expect(snapshot.hasData, isTrue); + expect(snapshot.isFetching, isFalse); + expect(find.text('0,1'), findsOneWidget); + snapshot.fetchMore(); + snapshot.fetchMore(); + await tester.pumpAndSettle(); + expect(first.limits, [3, 5]); + expect(first.streams.first.hasListener, isFalse); + expect(snapshot.isFetchingMore, isTrue); + first.emit(5); + await tester.pumpAndSettle(); + expect(snapshot.isFetchingMore, isFalse); + expect(find.text('0,1,2,3'), findsOneWidget); + await tester.pumpWidget(build(first, 3)); + expect(first.limits, [3, 5, 7]); + expect(snapshot.isFetching, isTrue); + first.emit(3); + await tester.pumpAndSettle(); + expect(snapshot.hasMore, isFalse); + snapshot.fetchMore(); + await tester.pumpWidget(build(first, 3)); + expect(first.limits, [3, 5, 7]); + await tester.pumpWidget(build(second, 3)); + expect(second.limits, [4]); + expect(first.streams.last.hasListener, isFalse); + second.emit(1); + await tester.pumpAndSettle(); + expect(find.text('0'), findsOneWidget); + await tester.pumpWidget(const SizedBox()); + expect(second.streams.single.hasListener, isFalse); + }); + + testWidgets( + 'query failures preserve existing data and a later success clears errors', + (tester) async { + final harness = QueryHarness(); + late FirestoreQueryBuilderSnapshot snapshot; + await tester.pumpWidget(MaterialApp( + home: FirestoreQueryBuilder( + query: harness.query, + pageSize: 2, + builder: (context, value, child) { + snapshot = value; + return const SizedBox(); + }, + ))); + final initialError = StateError('offline'); + final trace = StackTrace.current; + harness.streams.last.addError(initialError, trace); + await tester.pumpAndSettle(); + expect(snapshot.isFetching, isFalse); + expect(snapshot.hasError, isTrue); + expect(snapshot.error, same(initialError)); + expect(snapshot.stackTrace, same(trace)); + expect(snapshot.hasData, isFalse); + expect(snapshot.hasMore, isFalse); + harness.emit(3); + await tester.pumpAndSettle(); + expect(snapshot.hasError, isFalse); + expect(snapshot.error, isNull); + expect(snapshot.stackTrace, isNull); + expect(snapshot.docs, hasLength(2)); + snapshot.fetchMore(); + final nextError = StateError('next page failed'); + harness.streams.last.addError(nextError, trace); + await tester.pumpAndSettle(); + expect(snapshot.isFetchingMore, isFalse); + expect(snapshot.hasError, isTrue); + expect(snapshot.error, same(nextError)); + expect(snapshot.hasData, isTrue); + expect(snapshot.docs, hasLength(2)); + expect(snapshot.hasMore, isFalse); + }); + + for (final usePageView in [false, true]) { + for (final customBuilders in [false, true]) { + testWidgets( + '${usePageView ? 'page' : 'list'} view renders ${customBuilders ? 'custom' : 'default'} loading and empty states', + (tester) async { + final harness = QueryHarness(); + final loading = + customBuilders ? (BuildContext _) => const Text('loading') : null; + final empty = customBuilders ? const Text('empty') : null; + final widget = usePageView + ? FirestorePageView( + query: harness.query, + loadingBuilder: loading, + emptyBuilder: empty, + itemBuilder: (_, doc, index) => Text(doc.id)) + : FirestoreListView( + query: harness.query, + loadingBuilder: loading, + emptyBuilder: empty, + itemBuilder: (_, doc) => Text(doc.id)); + await tester.pumpWidget(MaterialApp(home: widget)); + expect( + customBuilders + ? find.text('loading') + : find.byType(CircularProgressIndicator), + findsOneWidget); + harness.emit(0); + await tester.pumpAndSettle(); + expect(find.text(customBuilders ? 'empty' : 'No Data'), findsOneWidget); + }); + } + } + + testWidgets( + 'list view paginates from its last visible item and forwards scrolling options', + (tester) async { + final harness = QueryHarness(); + final controller = ScrollController(); + addTearDown(controller.dispose); + await tester.pumpWidget(MaterialApp( + home: FirestoreListView( + query: harness.query, + pageSize: 2, + itemBuilder: (_, doc) => Text(doc.data()['name'] as String), + controller: controller, + primary: false, + physics: const ClampingScrollPhysics(), + shrinkWrap: true, + padding: const EdgeInsets.all(8), + itemExtent: 40, + reverse: true, + addAutomaticKeepAlives: false, + addRepaintBoundaries: false, + addSemanticIndexes: false, + cacheExtent: 100, + semanticChildCount: 2, + dragStartBehavior: DragStartBehavior.down, + keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, + restorationId: 'items', + clipBehavior: Clip.none, + ))); + harness.emit(3); + await tester.pumpAndSettle(); + expect(harness.limits, [3, 5]); + harness.emit(4); + await tester.pumpAndSettle(); + expect(find.text('item 3'), findsOneWidget); + final view = tester.widget(find.byType(ListView)); + expect(view.controller, same(controller)); + expect(view.reverse, isTrue); + expect(view.primary, isFalse); + expect(view.physics, isA()); + expect(view.shrinkWrap, isTrue); + expect(view.padding, const EdgeInsets.all(8)); + expect(view.itemExtent, 40); + // Verify the compatibility parameter exposed by FirestoreListView. + // ignore: deprecated_member_use + expect(view.cacheExtent, 100); + expect(view.semanticChildCount, 2); + expect(view.dragStartBehavior, DragStartBehavior.down); + expect( + view.keyboardDismissBehavior, ScrollViewKeyboardDismissBehavior.onDrag); + expect(view.restorationId, 'items'); + expect(view.clipBehavior, Clip.none); + final delegate = view.childrenDelegate as SliverChildBuilderDelegate; + expect(delegate.addAutomaticKeepAlives, isFalse); + expect(delegate.addRepaintBoundaries, isFalse); + expect(delegate.addSemanticIndexes, isFalse); + }); + + testWidgets('page view paginates while swiping and reports item indexes', + (tester) async { + final harness = QueryHarness(); + final controller = PageController(); + addTearDown(controller.dispose); + await tester.pumpWidget(MaterialApp( + home: FirestorePageView( + query: harness.query, + pageSize: 2, + itemBuilder: (_, doc, index) => Text('${doc.data()['name']}:$index'), + controller: controller, + scrollDirection: Axis.horizontal, + physics: const ClampingScrollPhysics(), + dragStartBehavior: DragStartBehavior.down, + restorationId: 'pages', + clipBehavior: Clip.none, + ))); + harness.emit(3); + await tester.pumpAndSettle(); + expect(find.text('item 0:0'), findsOneWidget); + await tester.drag(find.byType(PageView), const Offset(-800, 0)); + await tester.pumpAndSettle(); + expect(harness.limits, [3, 5]); + harness.emit(3); + await tester.pumpAndSettle(); + controller.jumpToPage(2); + await tester.pumpAndSettle(); + expect(find.text('item 2:2'), findsOneWidget); + final view = tester.widget(find.byType(PageView)); + expect(view.controller, same(controller)); + expect(view.scrollDirection, Axis.horizontal); + expect(view.physics, isA()); + expect(view.dragStartBehavior, DragStartBehavior.down); + expect(view.restorationId, 'pages'); + expect(view.clipBehavior, Clip.none); + }); + + for (final usePageView in [false, true]) { + testWidgets( + '${usePageView ? 'page' : 'list'} view passes failures to the error builder', + (tester) async { + final harness = QueryHarness(); + final error = StateError('offline'); + final trace = StackTrace.current; + Widget onError( + BuildContext context, Object received, StackTrace receivedTrace) { + expect(received, same(error)); + expect(receivedTrace, same(trace)); + return const Text('failed'); + } + + final widget = usePageView + ? FirestorePageView( + query: harness.query, + errorBuilder: onError, + itemBuilder: (context, doc, index) => Text(doc.id)) + : FirestoreListView( + query: harness.query, + errorBuilder: onError, + itemBuilder: (_, doc) => Text(doc.id)); + await tester.pumpWidget(MaterialApp(home: widget)); + harness.emit(1); + await tester.pumpAndSettle(); + harness.streams.last.addError(error, trace); + await tester.pumpAndSettle(); + expect(find.text('failed'), findsOneWidget); + }); + } +} diff --git a/packages/ns_firebase_utils/test/reactive_widget_test.dart b/packages/ns_firebase_utils/test/reactive_widget_test.dart new file mode 100644 index 0000000..c3fba8f --- /dev/null +++ b/packages/ns_firebase_utils/test/reactive_widget_test.dart @@ -0,0 +1,56 @@ +import 'package:fake_cloud_firestore/fake_cloud_firestore.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:ns_firebase_utils/reactive_widget/base_reactive_widget.dart'; + +void main() { + testWidgets('reactive builder selects data, waiting and fallback states', + (tester) async { + for (final waiting in [const Text('waiting'), null]) { + final builder = getReactiveBuilder( + onData: (data) => Text('data:$data'), + onFallback: const Text('fallback'), + onWaiting: waiting); + for (final snapshot in [ + const AsyncSnapshot.nothing(), + const AsyncSnapshot.waiting(), + const AsyncSnapshot.withData(ConnectionState.active, 'hello'), + const AsyncSnapshot.nothing().inState(ConnectionState.active), + AsyncSnapshot.withError( + ConnectionState.done, StateError('failed')) + ]) { + await tester.pumpWidget(MaterialApp( + home: Builder(builder: (context) => builder(context, snapshot)))); + final expected = snapshot.hasData + ? 'data:hello' + : snapshot.hasError || + snapshot.connectionState == ConnectionState.active || + waiting == null + ? 'fallback' + : 'waiting'; + expect(find.text(expected), findsOneWidget); + } + } + }); + + testWidgets( + 'document changes rebuild reactive widgets and missing documents use fallback', + (tester) async { + final ref = FakeFirebaseFirestore().doc('items/one'); + await tester.pumpWidget(MaterialApp( + home: ReactiveWidget>( + reactiveRef: ReactiveRef(ref), + fallbackValue: const {'name': 'fallback'}, + waitingWidget: const Text('waiting'), + widgetBuilder: (data) => Text(data?['name'] as String? ?? 'null'), + ))); + await tester.pumpAndSettle(); + expect(find.text('fallback'), findsOneWidget); + await ref.set({'name': 'created'}); + await tester.pumpAndSettle(); + expect(find.text('created'), findsOneWidget); + await ref.delete(); + await tester.pumpAndSettle(); + expect(find.text('fallback'), findsOneWidget); + }); +} diff --git a/packages/ns_firebase_utils/test/storage_test.dart b/packages/ns_firebase_utils/test/storage_test.dart new file mode 100644 index 0000000..0e1fcea --- /dev/null +++ b/packages/ns_firebase_utils/test/storage_test.dart @@ -0,0 +1,68 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:firebase_storage/firebase_storage.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:ns_firebase_utils/storage/firebase_storage_service.dart'; + +class MockStorage extends Mock implements FirebaseStorage {} + +class MockReference extends Mock implements Reference {} + +class MockUploadTask extends Mock implements UploadTask {} + +class MockSnapshot extends Mock implements TaskSnapshot {} + +void main() { + setUpAll(() { + registerFallbackValue(File('fallback')); + registerFallbackValue(SettableMetadata()); + registerFallbackValue(() {}); + }); + + for (final reportProgress in [true, false]) { + test('uploads file and metadata with progress reporting $reportProgress', + () async { + final storage = MockStorage(); + final root = MockReference(); + final reference = MockReference(); + final task = MockUploadTask(); + final snapshot = MockSnapshot(); + final events = StreamController.broadcast(); + addTearDown(events.close); + when(() => storage.ref()).thenReturn(root); + when(() => root.child('avatars/alice.png')).thenReturn(reference); + when(() => reference.putFile(any(), any())).thenAnswer((_) => task); + when(() => task.snapshotEvents).thenAnswer((_) => events.stream); + when(() => snapshot.bytesTransferred).thenReturn(25); + when(() => snapshot.totalBytes).thenReturn(100); + when(() => snapshot.state).thenReturn(TaskState.success); + when(() => task.whenComplete(any())).thenAnswer((invocation) async { + events.add(snapshot); + await Future.delayed(Duration.zero); + (invocation.positionalArguments.single as void Function())(); + return snapshot; + }); + when(() => reference.getDownloadURL()) + .thenAnswer((_) async => 'https://example.test/alice.png'); + final file = File('avatar.png'); + final progress = []; + final result = await FirebaseStorageService.uploadFile( + file, + 'avatars/alice.png', + customMetadata: {'owner': 'alice'}, + onProgress: reportProgress ? progress.add : null, + storage: storage, + ); + expect(result, 'https://example.test/alice.png'); + expect(progress, reportProgress ? ['25.00'] : isEmpty); + final metadata = verify(() => reference.putFile(file, captureAny())) + .captured + .single as SettableMetadata; + expect(metadata.contentLanguage, 'en'); + expect(metadata.customMetadata, {'owner': 'alice'}); + verify(() => reference.getDownloadURL()).called(1); + }); + } +} diff --git a/packages/ns_firebase_utils/test/utilities_test.dart b/packages/ns_firebase_utils/test/utilities_test.dart new file mode 100644 index 0000000..4d6d4f0 --- /dev/null +++ b/packages/ns_firebase_utils/test/utilities_test.dart @@ -0,0 +1,34 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:ns_firebase_utils/extensions/map.dart'; +import 'package:ns_firebase_utils/src.dart'; +import 'package:ns_firebase_utils/utils/custom_exception.dart'; + +void main() { + test('map helpers return typed values and defaults for invalid data', () { + appLogsNS('default logger'); + errorLogsNS('default error logger'); + final timestamp = Timestamp.fromMillisecondsSinceEpoch(1234); + final values = { + 'time': timestamp, + 'point': const GeoPoint(12, 34), + 'invalid': 1 + }; + expect(values.getTimestamp('time'), timestamp); + expect(values.getTimestamp('absent'), isNull); + expect(values.getTimestamp('invalid', defaultValue: timestamp), timestamp); + expect(values.getGeoPoint('point'), const GeoPoint(12, 34)); + expect(values.getGeoPoint('absent'), const GeoPoint(0, 0)); + expect(values.getGeoPoint('invalid'), const GeoPoint(0, 0)); + }); + test('custom exceptions retain their diagnostic fields', () { + final error = + CustomException(code: 'failure', message: 'message', details: 42); + expect(error.code, 'failure'); + expect(error.message, 'message'); + expect(error.details, 42); + expect(error.toString(), 'NSFException(failure, message, 42)'); + expect(CustomException(code: 'empty').toString(), + 'NSFException(empty, null, null)'); + }); +} diff --git a/packages/ns_intl_phone_input/example/lib/home/home_screen.dart b/packages/ns_intl_phone_input/example/lib/home/home_screen.dart index 4395d38..c97511a 100644 --- a/packages/ns_intl_phone_input/example/lib/home/home_screen.dart +++ b/packages/ns_intl_phone_input/example/lib/home/home_screen.dart @@ -59,7 +59,7 @@ class _HomeScreenState extends State { textEditingController: _phoneNumberController, /// Enable or disable validation - enableValidation: false, + enableValidation: true, autovalidateMode: AutovalidateMode.always, /// Country selection options for UI experience diff --git a/packages/ns_intl_phone_input/example/pubspec.yaml b/packages/ns_intl_phone_input/example/pubspec.yaml index f966abb..716dac2 100644 --- a/packages/ns_intl_phone_input/example/pubspec.yaml +++ b/packages/ns_intl_phone_input/example/pubspec.yaml @@ -19,7 +19,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev version: 1.0.1 environment: - sdk: ">=3.2.2 <4.0.0" + sdk: '>=3.6.0 <4.0.0' # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -46,7 +46,7 @@ dev_dependencies: # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -88,3 +88,5 @@ flutter: # # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages + +resolution: workspace diff --git a/packages/ns_intl_phone_input/example/test/app_test.dart b/packages/ns_intl_phone_input/example/test/app_test.dart new file mode 100644 index 0000000..8b24ec5 --- /dev/null +++ b/packages/ns_intl_phone_input/example/test/app_test.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:ns_intl_phone_input/ns_intl_phone_input.dart'; +import 'package:ns_intl_phone_input_example/home/home_screen.dart'; +import 'package:ns_intl_phone_input_example/main.dart' as app; + +void main() { + testWidgets('form example sets a sample number, submits and clears', + (tester) async { + await tester.pumpWidget(const MaterialApp(home: HomeScreen())); + await tester.pumpAndSettle(); + await tester.tap(find.text('Set Sample Phone Number')); + await tester.pumpAndSettle(); + expect( + tester + .widget(find.byType(TextField).last) + .controller! + .text + .replaceAll(RegExp(r'\D'), ''), + samplePhoneNumber); + await tester.tap(find.text('Submit')); + await tester.pumpAndSettle(); + expect(find.text('Validated'), findsOneWidget); + await tester.tap(find.text('Clear')); + await tester.pumpAndSettle(); + expect( + tester.widget(find.byType(TextField).last).controller!.text, + isEmpty); + await tester.pump(const Duration(seconds: 5)); + await tester.pumpAndSettle(); + await tester.tap(find.text('Submit')); + await tester.pumpAndSettle(); + expect(find.text('Not Validated'), findsOneWidget); + await tester.pump(const Duration(seconds: 5)); + }); + testWidgets('phone example accepts phone input', (tester) async { + app.main(); + await tester.pumpAndSettle(); + expect(find.text('NsIntlPhoneInput Example'), findsOneWidget); + await tester.enterText(find.byType(TextField).last, '987654321'); + await tester.pumpAndSettle(); + expect( + tester + .widget(find.byType(TextField).last) + .controller! + .text + .replaceAll(RegExp(r'\D'), ''), + '987654321'); + final phone = + tester.widget(find.byType(NsIntlPhoneInput)); + phone.textEditingController.initialPhone( + phoneNumber: samplePhoneNumber, intlDialCode: sampleCountryCode); + await tester.pumpAndSettle(); + final messages = []; + final originalDebugPrint = debugPrint; + debugPrint = (message, {wrapWidth}) => messages.add(message); + try { + phone.onPhoneChange(CountrySelection( + selectedCountry: phone.textEditingController.selectedCountry!, + formattedPhoneNumber: samplePhoneNumber, + unformattedPhoneNumber: samplePhoneNumber)); + expect(messages.single, contains(samplePhoneNumber)); + } finally { + debugPrint = originalDebugPrint; + } + }); +} diff --git a/packages/ns_intl_phone_input/lib/ns_intl_phone_input.dart b/packages/ns_intl_phone_input/lib/ns_intl_phone_input.dart index 66ebfce..b5c6ce6 100644 --- a/packages/ns_intl_phone_input/lib/ns_intl_phone_input.dart +++ b/packages/ns_intl_phone_input/lib/ns_intl_phone_input.dart @@ -1,4 +1,4 @@ -library ns_intl_phone_input; +library; export 'ns_intl_phone_input.dart'; export 'src/src.dart'; diff --git a/packages/ns_intl_phone_input/lib/src/presentation/ns_intl_phone_input.dart b/packages/ns_intl_phone_input/lib/src/presentation/ns_intl_phone_input.dart index 6207efa..f5e9c0b 100644 --- a/packages/ns_intl_phone_input/lib/src/presentation/ns_intl_phone_input.dart +++ b/packages/ns_intl_phone_input/lib/src/presentation/ns_intl_phone_input.dart @@ -54,7 +54,7 @@ class _NsIntlPhoneInputState extends State }); } - void _notifyListeners(text) { + void _notifyListeners(String text) { final unMastedValue = NSIntlPhoneHelper.getUnMaskedPhoneNumber( phoneNumber: text, ); diff --git a/packages/ns_intl_phone_input/pubspec.yaml b/packages/ns_intl_phone_input/pubspec.yaml index 6b32170..e37b8a1 100644 --- a/packages/ns_intl_phone_input/pubspec.yaml +++ b/packages/ns_intl_phone_input/pubspec.yaml @@ -6,19 +6,19 @@ repository: https://github.com/nonstopio/flutter_forge/tree/main/packages/ns_int issue_tracker: https://github.com/nonstopio/flutter_forge/tree/main/packages/ns_intl_phone_input/ environment: - sdk: ">=2.17.0 <4.0.0" + sdk: '>=3.6.0 <4.0.0' flutter: ">=1.17.0" dependencies: flutter: sdk: flutter mask_text_input_formatter: ^2.9.0 - equatable: ^2.0.7 + equatable: ^2.1.0 dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 # The following section is specific to Flutter packages. flutter: @@ -26,3 +26,5 @@ flutter: - family: "AppleColorEmoji" fonts: - asset: assets/fonts/apple_color_emoji.ttf + +resolution: workspace diff --git a/packages/ns_utils/lib/extensions/map.dart b/packages/ns_utils/lib/extensions/map.dart index 90a7160..586840e 100644 --- a/packages/ns_utils/lib/extensions/map.dart +++ b/packages/ns_utils/lib/extensions/map.dart @@ -142,7 +142,7 @@ extension MapExtensions on Map { } } -dynamic toEncodable(object) { +dynamic toEncodable(dynamic object) { if (object is String || object is num || object is Map || diff --git a/packages/ns_utils/lib/services/device_service/device_service.dart b/packages/ns_utils/lib/services/device_service/device_service.dart index 13d9b0a..a23727a 100644 --- a/packages/ns_utils/lib/services/device_service/device_service.dart +++ b/packages/ns_utils/lib/services/device_service/device_service.dart @@ -1,8 +1,6 @@ import 'package:flutter/services.dart'; -class DeviceService { - DeviceService._(); - +abstract final class DeviceService { static Future setOrientationToPortrait() async { await SystemChrome.setPreferredOrientations([ DeviceOrientation.portraitUp, diff --git a/packages/ns_utils/lib/src.dart b/packages/ns_utils/lib/src.dart index bcfdbc4..34e0f43 100644 --- a/packages/ns_utils/lib/src.dart +++ b/packages/ns_utils/lib/src.dart @@ -1,4 +1,4 @@ -library ns_utils; +library; import 'services/shared_preferences/sp_service.dart'; diff --git a/packages/ns_utils/lib/utils/sizes.dart b/packages/ns_utils/lib/utils/sizes.dart index 285f63f..fbf5ff6 100644 --- a/packages/ns_utils/lib/utils/sizes.dart +++ b/packages/ns_utils/lib/utils/sizes.dart @@ -12,9 +12,7 @@ double screenHeight = _defaultSize.height; ///Adapting screen and font size. ///Let your UI display a reasonable layout on different screen sizes! /// -class Sizes { - Sizes._(); // coverage:ignore-line - +abstract final class Sizes { static bool initialized = false; ///initialize sizes diff --git a/packages/ns_utils/lib/utils/validators.dart b/packages/ns_utils/lib/utils/validators.dart index 11529a7..0eba6cb 100644 --- a/packages/ns_utils/lib/utils/validators.dart +++ b/packages/ns_utils/lib/utils/validators.dart @@ -19,9 +19,7 @@ const String emptyMessage = "Can't be Empty"; const String passwordValidationMsg = "Minimum 6 characters,include upper and " "lower case letters,numbers,symbols(@,#,\$,%)"; -class ValidatorUtil { - ValidatorUtil._(); - +abstract final class ValidatorUtil { static bool isFormValid(GlobalKey? formKey) { try { final FormState? form = formKey?.currentState; diff --git a/packages/ns_utils/pubspec.yaml b/packages/ns_utils/pubspec.yaml index d867d79..a99c6bf 100644 --- a/packages/ns_utils/pubspec.yaml +++ b/packages/ns_utils/pubspec.yaml @@ -7,21 +7,21 @@ repository: https://github.com/nonstopio/flutter_forge/tree/main/packages/ns_uti issue_tracker: https://github.com/nonstopio/flutter_forge/issues environment: - sdk: '>=2.18.4 <4.0.0' + sdk: '>=3.6.0 <4.0.0' flutter: ">=1.17.0" dependencies: after_layout: ^1.2.0 - cached_network_image: ^3.3.0 - device_info_plus: ^9.0.3 + cached_network_image: ^4.0.0 + device_info_plus: ^13.2.0 flutter_screenutil: ^5.9.0 flutter_spinkit: ^5.2.0 - intl: ^0.18.1 - package_info_plus: ^4.1.0 + intl: ^0.20.3 + package_info_plus: ^10.2.1 recase: ^4.1.0 shared_preferences: ^2.2.1 visibility_detector: ^0.4.0+2 - objectid: ^3.0.0 + objectid: ^4.0.3 flutter: @@ -30,5 +30,6 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 +resolution: workspace diff --git a/packages/ns_utils/test/services/platform_services_test.dart b/packages/ns_utils/test/services/platform_services_test.dart new file mode 100644 index 0000000..1dd4772 --- /dev/null +++ b/packages/ns_utils/test/services/platform_services_test.dart @@ -0,0 +1,66 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:ns_utils/services/device_service/device_service.dart'; +import 'package:ns_utils/services/statusbar_service/statusbar_service.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final calls = []; + setUp(() { + calls.clear(); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(SystemChannels.platform, (call) async { + calls.add(call); + return null; + }); + }); + tearDown(() => TestDefaultBinaryMessengerBinding + .instance.defaultBinaryMessenger + .setMockMethodCallHandler(SystemChannels.platform, null)); + test('portrait orientation enables both portrait rotations', () async { + await DeviceService.setOrientationToPortrait(); + expect(calls.single.method, 'SystemChrome.setPreferredOrientations'); + expect(calls.single.arguments, + ['DeviceOrientation.portraitUp', 'DeviceOrientation.portraitDown']); + }); + test('status bar can be hidden, shown and restored', () async { + await StatusBarService.hideStatusBar(); + await StatusBarService().showStatusBar(); + StatusBarService.resetStatusBarColor(); + await Future.delayed(Duration.zero); + expect(calls.map((call) => call.method), [ + 'SystemChrome.setEnabledSystemUIOverlays', + 'SystemChrome.setEnabledSystemUIOverlays', + 'SystemChrome.restoreSystemUIOverlays' + ]); + expect(calls[0].arguments, ['SystemUiOverlay.bottom']); + expect( + calls[1].arguments, ['SystemUiOverlay.top', 'SystemUiOverlay.bottom']); + }); + test('status color handles a platform exception during scheduling', () async { + final messages = []; + final originalDebugPrint = debugPrint; + debugPrint = (message, {wrapWidth}) => messages.add(message); + addTearDown(() => debugPrint = originalDebugPrint); + await runZoned(() => StatusBarService.changeStatusColor(Colors.blue), + zoneSpecification: ZoneSpecification( + createTimer: (self, parent, zone, duration, callback) { + throw PlatformException(code: 'unavailable'); + })); + expect(messages.single, contains('unavailable')); + }); + testWidgets('status color change completes its delayed overlay update', + (tester) async { + final future = StatusBarService.changeStatusColor(Colors.red); + await tester.pump(const Duration(milliseconds: 500)); + await tester.pump(const Duration(milliseconds: 500)); + await future; + final styles = calls + .where((call) => call.method == 'SystemChrome.setSystemUIOverlayStyle') + .toList(); + expect(styles, isNotEmpty); + expect(styles.last.arguments['statusBarBrightness'], 'Brightness.light'); + }); +} diff --git a/packages/ns_utils/test/utils/validators_test.dart b/packages/ns_utils/test/utils/validators_test.dart new file mode 100644 index 0000000..63a9992 --- /dev/null +++ b/packages/ns_utils/test/utils/validators_test.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:ns_utils/utils/validators.dart'; + +void main() { + test('email validation handles empty, valid, invalid and custom errors', () { + expect(ValidatorUtil.validateEmail(' '), enterEmail); + expect(ValidatorUtil.validateEmail('alice@example.com'), isNull); + expect(ValidatorUtil.validateEmail('bad'), enterValidEmail); + expect( + ValidatorUtil.validateEmail('bad', errorMessage: 'custom'), 'custom'); + }); + test('password and name validators apply their documented patterns', () { + expect(ValidatorUtil.validatePassword(' '), enterPassword); + expect(ValidatorUtil.validatePassword('Abc123@'), isNull); + expect(ValidatorUtil.validatePassword('abc'), passwordValidationMsg); + expect(ValidatorUtil.validateName('', 'name'), '$enter name'); + expect(ValidatorUtil.validateName("O'Brien", 'name'), isNull); + expect(ValidatorUtil.validateName('123', 'name'), '$enterValid name'); + expect(ValidatorUtil.validateName('123', 'id', pattern: r'^\d+$'), isNull); + }); + test('generic validators distinguish blank values and mismatching patterns', + () { + expect(ValidatorUtil.validatePattern(' ', label: 'id', pattern: r'^\d+$'), + emptyMessage); + expect(ValidatorUtil.validatePattern('12', label: 'id', pattern: r'^\d+$'), + isNull); + expect(ValidatorUtil.validatePattern('bad', label: 'id', pattern: r'^\d+$'), + '$enterValid id'); + expect( + ValidatorUtil.validatePattern('bad', + label: 'id', pattern: r'^\d+$', errorMessage: 'custom'), + 'custom'); + expect(ValidatorUtil.validateEmptyCheck(' '), emptyMessage); + expect(ValidatorUtil.validateEmptyCheck('ok'), isNull); + expect(ValidatorUtil.validateEmpty('', 'name'), '${enter}name'); + expect(ValidatorUtil.validateEmpty('ok', 'name'), isNull); + }); + testWidgets( + 'form validation saves only valid forms and handles missing or throwing forms', + (tester) async { + expect(ValidatorUtil.isFormValid(null), isFalse); + expect(ValidatorUtil.isFormValid(GlobalKey()), isFalse); + final key = GlobalKey(); + var saved = false; + var mode = 'valid'; + await tester.pumpWidget(MaterialApp( + home: Scaffold( + body: Form( + key: key, + child: TextFormField( + validator: (_) { + if (mode == 'throw') throw Exception('validation failed'); + return mode == 'invalid' ? 'invalid' : null; + }, + onSaved: (_) => saved = true, + ))))); + expect(ValidatorUtil.isFormValid(key), isTrue); + expect(saved, isTrue); + saved = false; + mode = 'invalid'; + expect(ValidatorUtil.isFormValid(key), isFalse); + expect(saved, isFalse); + mode = 'throw'; + expect(ValidatorUtil.isFormValid(key), isFalse); + }); +} diff --git a/packages/ns_utils/test/widgets/custom_error_widget_test.dart b/packages/ns_utils/test/widgets/custom_error_widget_test.dart new file mode 100644 index 0000000..322aea4 --- /dev/null +++ b/packages/ns_utils/test/widgets/custom_error_widget_test.dart @@ -0,0 +1,47 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:ns_utils/widgets/custom_error_widget.dart'; + +class _ImageBundle extends CachingAssetBundle { + @override + Future load(String key) async { + if (key == 'AssetManifest.bin') { + return const StandardMessageCodec().encodeMessage({})!; + } + return ByteData.sublistView(base64Decode( + 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+a3foAAAAASUVORK5CYII=')); + } +} + +void main() { + for (final showDetails in [true, false]) { + testWidgets('error screen renders details=$showDetails and restarts on tap', + (tester) async { + var restarts = 0; + await tester.pumpWidget(ScreenUtilInit( + designSize: const Size(800, 600), + builder: (_, child) => MaterialApp( + home: DefaultAssetBundle( + bundle: _ImageBundle(), + child: CustomErrorWidget( + errorDetails: + FlutterErrorDetails(exception: StateError('broken')), + showErrorDetails: showDetails, + logoAsset: 'logo.png', + onRestart: (_) => restarts++))))); + await tester.pumpAndSettle(); + if (showDetails) { + expect(find.textContaining('broken'), findsWidgets); + } else { + expect(find.text('Abnormal Behavior!'), findsOneWidget); + expect(find.byType(Image), findsOneWidget); + } + await tester.ensureVisible(find.text('Restart App!')); + await tester.tap(find.text('Restart App!')); + expect(restarts, 1); + }); + } +} diff --git a/packages/timer_button/example/pubspec.yaml b/packages/timer_button/example/pubspec.yaml index 6272284..a36a7db 100644 --- a/packages/timer_button/example/pubspec.yaml +++ b/packages/timer_button/example/pubspec.yaml @@ -19,7 +19,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.2.0 environment: - sdk: '>=3.1.2 <4.0.0' + sdk: '>=3.6.0 <4.0.0' # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -47,7 +47,7 @@ dev_dependencies: # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -90,3 +90,5 @@ flutter: # # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages + +resolution: workspace diff --git a/packages/timer_button/example/test/app_test.dart b/packages/timer_button/example/test/app_test.dart new file mode 100644 index 0000000..405d18c --- /dev/null +++ b/packages/timer_button/example/test/app_test.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:timer_button/timer_button.dart'; +import 'package:timer_button_example/main.dart' as app; + +void main() { + testWidgets('all demo buttons count down and restart after activation', + (tester) async { + app.main(); + await tester.pumpAndSettle(); + expect(find.byType(TimerButton), findsNWidgets(4)); + for (var seconds = 0; seconds < 5; seconds++) { + await tester.pump(const Duration(seconds: 1)); + } + expect(find.text('Custom: 0'), findsOneWidget); + for (final label in [ + 'Try Again', + 'Outlined: Try Again', + 'Text: Try Again', + 'Custom: 0' + ]) { + await tester.tap(find.text(label)); + await tester.pump(); + } + expect(find.text('Custom: 5'), findsOneWidget); + expect(tester.widget(find.byType(ElevatedButton)).onPressed, + isNull); + await tester.pumpWidget(const SizedBox.shrink()); + }); +} diff --git a/packages/timer_button/pubspec.yaml b/packages/timer_button/pubspec.yaml index 9cc565e..8003f8e 100644 --- a/packages/timer_button/pubspec.yaml +++ b/packages/timer_button/pubspec.yaml @@ -10,7 +10,7 @@ screenshots: path: screenshots/demo.png environment: - sdk: '>=3.0.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dependencies: flutter: @@ -19,4 +19,6 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 + +resolution: workspace diff --git a/packages/timer_button/test/timer_button_test.dart b/packages/timer_button/test/timer_button_test.dart index 0b63f8b..e4bf392 100644 --- a/packages/timer_button/test/timer_button_test.dart +++ b/packages/timer_button/test/timer_button_test.dart @@ -747,8 +747,8 @@ void main() { group('Constants', () { test('should have correct constant values', () { - // These are private constants in the library, but we can test their effects - expect(1, equals(1)); // aSec + expect(aSec, 1); + expect(labelSplitter, ' | '); }); }); } diff --git a/plugins/contact_permission/CHANGELOG.md b/plugins/contact_permission/CHANGELOG.md index 81734eb..0a97317 100644 --- a/plugins/contact_permission/CHANGELOG.md +++ b/plugins/contact_permission/CHANGELOG.md @@ -26,3 +26,9 @@ ## 1.0.0 * initial release. +## Unreleased + +- Add native JVM and Swift/Objective-C unit tests with 100% executable line coverage. +- Require Flutter 3.44 / Dart 3.12 and Android API 24 / iOS 15 or newer. +- Update Android build tooling to AGP 9.4.1 / Gradle 9.7.1, use the Flutter-managed Kotlin configuration, and declare AndroidX/coroutine dependencies explicitly. +- Use AndroidX Core 1.18.0, the latest version compatible with compile SDK 36 (Core 1.19.0 requires SDK 37). diff --git a/plugins/contact_permission/android/build.gradle b/plugins/contact_permission/android/build.gradle index 430b0b6..8887fa1 100644 --- a/plugins/contact_permission/android/build.gradle +++ b/plugins/contact_permission/android/build.gradle @@ -1,18 +1,13 @@ -package plugins.contact_permission.android - group 'com.nonstopio.contact.permission.contact_permission' version '1.0-SNAPSHOT' buildscript { - ext.kotlin_version = '1.6.10' repositories { google() mavenCentral() } - dependencies { - classpath 'com.android.tools.build:gradle:7.1.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath 'com.android.tools.build:gradle:9.4.1' } } @@ -23,26 +18,31 @@ allprojects { } } +// Flutter 3.44+ configures Kotlin for plugins, including AGP's built-in mode. apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' android { - compileSdkVersion 31 + namespace 'com.nonstopio.contact.permission.contact_permission' + compileSdk 36 compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = '1.8' + defaultConfig { + minSdk 24 } +} - sourceSets { - main.java.srcDirs += 'src/main/kotlin' +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 } +} - defaultConfig { - minSdkVersion 16 - } +dependencies { + // 1.19 requires compileSdk 37; Flutter 3.47 targets compileSdk 36. + implementation 'androidx.core:core:1.18.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.11.0' } diff --git a/plugins/contact_permission/android/gradle/wrapper/gradle-wrapper.jar b/plugins/contact_permission/android/gradle/wrapper/gradle-wrapper.jar index 41d9927..eddabd2 100644 Binary files a/plugins/contact_permission/android/gradle/wrapper/gradle-wrapper.jar and b/plugins/contact_permission/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/plugins/contact_permission/android/gradle/wrapper/gradle-wrapper.properties b/plugins/contact_permission/android/gradle/wrapper/gradle-wrapper.properties index 41dfb87..e36aab4 100644 --- a/plugins/contact_permission/android/gradle/wrapper/gradle-wrapper.properties +++ b/plugins/contact_permission/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists + +distributionSha256Sum=acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a diff --git a/plugins/contact_permission/android/gradlew b/plugins/contact_permission/android/gradlew index 1b6c787..249efbb 100755 --- a/plugins/contact_permission/android/gradlew +++ b/plugins/contact_permission/android/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,10 +15,12 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -27,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +82,11 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -133,22 +132,29 @@ 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. + if ! command -v java >/dev/null 2>&1 + then + 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 fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -165,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -193,18 +198,27 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/plugins/contact_permission/android/gradlew.bat b/plugins/contact_permission/android/gradlew.bat index 107acd3..a51ec4f 100644 --- a/plugins/contact_permission/android/gradlew.bat +++ b/plugins/contact_permission/android/gradlew.bat @@ -13,19 +13,22 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,15 +43,15 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute -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. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -56,34 +59,24 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -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. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :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 %* - -: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 +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -:omega +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/plugins/contact_permission/android/settings.gradle b/plugins/contact_permission/android/settings.gradle index ef13c69..4119f39 100644 --- a/plugins/contact_permission/android/settings.gradle +++ b/plugins/contact_permission/android/settings.gradle @@ -1,3 +1 @@ -package plugins.contact_permission.android - rootProject.name = 'contact_permission' diff --git a/plugins/contact_permission/example/pubspec.yaml b/plugins/contact_permission/example/pubspec.yaml index 49568ef..5962640 100644 --- a/plugins/contact_permission/example/pubspec.yaml +++ b/plugins/contact_permission/example/pubspec.yaml @@ -6,7 +6,7 @@ description: Demonstrates how to use the contact_permission plugin. publish_to: 'none' # Remove this line if you wish to publish to pub.dev environment: - sdk: '>=2.18.6 <3.0.0' + sdk: '>=3.6.0 <4.0.0' # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -39,7 +39,7 @@ dev_dependencies: # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -82,3 +82,5 @@ flutter: # # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages + +resolution: workspace diff --git a/plugins/contact_permission/example/test/app_test.dart b/plugins/contact_permission/example/test/app_test.dart new file mode 100644 index 0000000..877e3ff --- /dev/null +++ b/plugins/contact_permission/example/test/app_test.dart @@ -0,0 +1,34 @@ +import 'package:contact_permission_example/main.dart' as app; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + testWidgets('check and request show denied and granted permission states', + (tester) async { + final methods = []; + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler( + const MethodChannel('nonstopio_contact_permission'), (call) async { + methods.add(call.method); + return call.method == 'requestPermission'; + }); + addTearDown(() => TestDefaultBinaryMessengerBinding + .instance.defaultBinaryMessenger + .setMockMethodCallHandler( + const MethodChannel('nonstopio_contact_permission'), null)); + app.main(); + await tester.pumpAndSettle(); + expect(find.text('Permission granted: null'), findsOneWidget); + await tester.tap(find.text('Check Permission')); + await tester.pumpAndSettle(); + expect(find.byIcon(Icons.close), findsOneWidget); + expect(find.text('Permission granted: false'), findsOneWidget); + await tester.tap(find.text('Request Permission')); + await tester.pumpAndSettle(); + expect(find.byIcon(Icons.check), findsOneWidget); + expect(find.text('Permission granted: true'), findsOneWidget); + expect(methods, ['isPermissionGranted', 'requestPermission']); + }); +} diff --git a/plugins/contact_permission/ios/contact_permission.podspec b/plugins/contact_permission/ios/contact_permission.podspec index 1b62ed8..84b7c18 100644 --- a/plugins/contact_permission/ios/contact_permission.podspec +++ b/plugins/contact_permission/ios/contact_permission.podspec @@ -15,7 +15,7 @@ A new Flutter plugin project. s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.platform = :ios, '9.0' + s.platform = :ios, '15.0' # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } diff --git a/plugins/contact_permission/lib/contact_permission_method_channel.dart b/plugins/contact_permission/lib/contact_permission_method_channel.dart index 0695125..493f6a6 100644 --- a/plugins/contact_permission/lib/contact_permission_method_channel.dart +++ b/plugins/contact_permission/lib/contact_permission_method_channel.dart @@ -11,8 +11,9 @@ class MethodChannelContactPermission extends ContactPermissionPlatform { @override Future isPermissionGranted() async { - final version = - await methodChannel.invokeMethod('isPermissionGranted'); + final version = await methodChannel.invokeMethod( + 'isPermissionGranted', + ); return version ?? false; } diff --git a/plugins/contact_permission/native_tests/.gitignore b/plugins/contact_permission/native_tests/.gitignore new file mode 100644 index 0000000..64b1931 --- /dev/null +++ b/plugins/contact_permission/native_tests/.gitignore @@ -0,0 +1,5 @@ +coverage/ +android/.gradle/ +android/.kotlin/ +android/build/ +android/gradle-run.log diff --git a/plugins/contact_permission/native_tests/README.md b/plugins/contact_permission/native_tests/README.md new file mode 100644 index 0000000..1ce4b00 --- /dev/null +++ b/plugins/contact_permission/native_tests/README.md @@ -0,0 +1,41 @@ +# Native unit coverage + +Run both native suites on macOS with Xcode command line tools, Python 3, and JDK 21: + +```sh +python3 plugins/contact_permission/native_tests/run.py +``` + +Linux can run the Android suite alone: + +```sh +python3 plugins/contact_permission/native_tests/run.py --platform android +``` + +The Gradle wrapper pins Gradle 9.7.1 with its distribution SHA-256. The JVM suite +uses Kotlin 2.4.20 and JaCoCo 0.8.15. The Apple suite uses the installed Xcode Swift +and Clang compilers with LLVM source coverage. Both commands fail unless every +executable line in the production native sources is covered. Reports are written +under `native_tests/coverage` and `native_tests/android/build/reports/jacoco`. + +The tests compile the actual Kotlin, Swift, and Objective-C production files. +Android, Flutter embedding, and Apple Contacts SDK boundaries +are replaced by deterministic test doubles. The JVM cases cover engine and +activity lifecycle, granted/denied/missing permission results, method dispatch, +main-queue replies, and single completion. The Apple cases cover all authorization +states, successful/denied/error permission requests, method dispatch, channel +registration, and Objective-C-to-Swift registration forwarding. Android callbacks +run through the real kotlinx.coroutines implementation with its official test +dispatcher, so tests control the main queue without launching Android UI threads. + +Only production classes/files enter the native coverage denominator; test doubles +and test harness code do not. These are native unit tests: they do **not** validate +system permission dialogs, real Android/iOS embedding integration, or OS behavior. +No test requests real contact access. Device integration tests remain a separate +validation boundary. + +The production plugin now uses Android API 24 and iOS 15 deployment minima, +matching the [Flutter 3.47 support matrix](https://docs.flutter.dev/reference/supported-platforms). +Its Android build follows the [Flutter plugin Kotlin migration](https://docs.flutter.dev/release/breaking-changes/migrate-to-built-in-kotlin/for-plugin-authors). +AndroidX Core stays at 1.18.0 because 1.19.0 requires compile SDK 37, above this +plugin's compile SDK 36. AGP 9.4.1 and kotlinx.coroutines 1.11.0 are pinned. diff --git a/plugins/contact_permission/native_tests/android/build.gradle.kts b/plugins/contact_permission/native_tests/android/build.gradle.kts new file mode 100644 index 0000000..3a5c62a --- /dev/null +++ b/plugins/contact_permission/native_tests/android/build.gradle.kts @@ -0,0 +1,43 @@ +plugins { + kotlin("jvm") version "2.4.20" + jacoco +} + +repositories { mavenCentral() } + +dependencies { + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0") + testImplementation(kotlin("test-junit")) + testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.11.0") +} + +kotlin { jvmToolchain(21) } + +sourceSets { + main { + kotlin.srcDirs("../../android/src/main/kotlin", "src/fakes/kotlin") + } +} + +jacoco { toolVersion = "0.8.15" } + +tasks.test { finalizedBy(tasks.jacocoTestReport) } + +val productionClasses = fileTree(layout.buildDirectory.dir("classes/kotlin/main")) { + include("com/nonstopio/contact/permission/contact_permission/**") +} + +tasks.jacocoTestReport { + dependsOn(tasks.test) + classDirectories.setFrom(productionClasses) + sourceDirectories.setFrom(files("../../android/src/main/kotlin")) + reports { xml.required = true; html.required = true } +} + +tasks.jacocoTestCoverageVerification { + dependsOn(tasks.jacocoTestReport) + classDirectories.setFrom(productionClasses) + violationRules { + rule { limit { counter = "LINE"; minimum = "1.0".toBigDecimal() } } + } +} diff --git a/plugins/contact_permission/native_tests/android/gradle/wrapper/gradle-wrapper.jar b/plugins/contact_permission/native_tests/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..eddabd2 Binary files /dev/null and b/plugins/contact_permission/native_tests/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/plugins/contact_permission/native_tests/android/gradle/wrapper/gradle-wrapper.properties b/plugins/contact_permission/native_tests/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..760c459 --- /dev/null +++ b/plugins/contact_permission/native_tests/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,10 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionSha256Sum=acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip +networkTimeout=10000 +retries=0 +retryBackOffMs=500 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/plugins/contact_permission/native_tests/android/gradlew b/plugins/contact_permission/native_tests/android/gradlew new file mode 100755 index 0000000..249efbb --- /dev/null +++ b/plugins/contact_permission/native_tests/android/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# gradlew start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh gradlew +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# 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 + if ! command -v java >/dev/null 2>&1 + then + 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 +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/plugins/contact_permission/native_tests/android/gradlew.bat b/plugins/contact_permission/native_tests/android/gradlew.bat new file mode 100644 index 0000000..a51ec4f --- /dev/null +++ b/plugins/contact_permission/native_tests/android/gradlew.bat @@ -0,0 +1,82 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem gradlew startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@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="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:execute +@rem Setup the command line + + + +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel + +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/plugins/contact_permission/native_tests/android/settings.gradle.kts b/plugins/contact_permission/native_tests/android/settings.gradle.kts new file mode 100644 index 0000000..579c84d --- /dev/null +++ b/plugins/contact_permission/native_tests/android/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "contact-permission-native-tests" diff --git a/plugins/contact_permission/native_tests/android/src/fakes/kotlin/android/Manifest.kt b/plugins/contact_permission/native_tests/android/src/fakes/kotlin/android/Manifest.kt new file mode 100644 index 0000000..6ed4b3d --- /dev/null +++ b/plugins/contact_permission/native_tests/android/src/fakes/kotlin/android/Manifest.kt @@ -0,0 +1,2 @@ +package android +object Manifest { object permission { const val READ_CONTACTS = "android.permission.READ_CONTACTS" } } diff --git a/plugins/contact_permission/native_tests/android/src/fakes/kotlin/android/app/Activity.kt b/plugins/contact_permission/native_tests/android/src/fakes/kotlin/android/app/Activity.kt new file mode 100644 index 0000000..e768ea8 --- /dev/null +++ b/plugins/contact_permission/native_tests/android/src/fakes/kotlin/android/app/Activity.kt @@ -0,0 +1,3 @@ +package android.app +import android.content.Context +open class Activity : Context() diff --git a/plugins/contact_permission/native_tests/android/src/fakes/kotlin/android/content/Context.kt b/plugins/contact_permission/native_tests/android/src/fakes/kotlin/android/content/Context.kt new file mode 100644 index 0000000..1773e66 --- /dev/null +++ b/plugins/contact_permission/native_tests/android/src/fakes/kotlin/android/content/Context.kt @@ -0,0 +1,3 @@ +package android.content +open class Context { var permission = -1 } +class Intent diff --git a/plugins/contact_permission/native_tests/android/src/fakes/kotlin/android/content/pm/PackageManager.kt b/plugins/contact_permission/native_tests/android/src/fakes/kotlin/android/content/pm/PackageManager.kt new file mode 100644 index 0000000..d87067c --- /dev/null +++ b/plugins/contact_permission/native_tests/android/src/fakes/kotlin/android/content/pm/PackageManager.kt @@ -0,0 +1,2 @@ +package android.content.pm +object PackageManager { const val PERMISSION_GRANTED = 0 } diff --git a/plugins/contact_permission/native_tests/android/src/fakes/kotlin/androidx/annotation/NonNull.kt b/plugins/contact_permission/native_tests/android/src/fakes/kotlin/androidx/annotation/NonNull.kt new file mode 100644 index 0000000..6228552 --- /dev/null +++ b/plugins/contact_permission/native_tests/android/src/fakes/kotlin/androidx/annotation/NonNull.kt @@ -0,0 +1,2 @@ +package androidx.annotation +annotation class NonNull diff --git a/plugins/contact_permission/native_tests/android/src/fakes/kotlin/androidx/core/app/ActivityCompat.kt b/plugins/contact_permission/native_tests/android/src/fakes/kotlin/androidx/core/app/ActivityCompat.kt new file mode 100644 index 0000000..8f849a5 --- /dev/null +++ b/plugins/contact_permission/native_tests/android/src/fakes/kotlin/androidx/core/app/ActivityCompat.kt @@ -0,0 +1,9 @@ +package androidx.core.app +import android.app.Activity +object ActivityCompat { + data class Request(val activity: Activity, val permissions: Array, val code: Int) + val requests = mutableListOf() + fun requestPermissions(activity: Activity, permissions: Array, code: Int) { + requests.add(Request(activity, permissions, code)) + } +} diff --git a/plugins/contact_permission/native_tests/android/src/fakes/kotlin/androidx/core/content/ContextCompat.kt b/plugins/contact_permission/native_tests/android/src/fakes/kotlin/androidx/core/content/ContextCompat.kt new file mode 100644 index 0000000..397e0ee --- /dev/null +++ b/plugins/contact_permission/native_tests/android/src/fakes/kotlin/androidx/core/content/ContextCompat.kt @@ -0,0 +1,3 @@ +package androidx.core.content +import android.content.Context +object ContextCompat { fun checkSelfPermission(context: Context, permission: String) = context.permission } diff --git a/plugins/contact_permission/native_tests/android/src/fakes/kotlin/io/flutter/embedding/engine/plugins/FlutterPlugin.kt b/plugins/contact_permission/native_tests/android/src/fakes/kotlin/io/flutter/embedding/engine/plugins/FlutterPlugin.kt new file mode 100644 index 0000000..bb0e2d7 --- /dev/null +++ b/plugins/contact_permission/native_tests/android/src/fakes/kotlin/io/flutter/embedding/engine/plugins/FlutterPlugin.kt @@ -0,0 +1,8 @@ +package io.flutter.embedding.engine.plugins +import android.content.Context +import io.flutter.plugin.common.BinaryMessenger +interface FlutterPlugin { + class FlutterPluginBinding(val binaryMessenger: BinaryMessenger, val applicationContext: Context) + fun onAttachedToEngine(flutterPluginBinding: FlutterPluginBinding) + fun onDetachedFromEngine(binding: FlutterPluginBinding) +} diff --git a/plugins/contact_permission/native_tests/android/src/fakes/kotlin/io/flutter/embedding/engine/plugins/activity/ActivityAware.kt b/plugins/contact_permission/native_tests/android/src/fakes/kotlin/io/flutter/embedding/engine/plugins/activity/ActivityAware.kt new file mode 100644 index 0000000..ae6527c --- /dev/null +++ b/plugins/contact_permission/native_tests/android/src/fakes/kotlin/io/flutter/embedding/engine/plugins/activity/ActivityAware.kt @@ -0,0 +1,15 @@ +package io.flutter.embedding.engine.plugins.activity +import android.app.Activity +import io.flutter.plugin.common.PluginRegistry +interface ActivityAware { + fun onAttachedToActivity(binding: ActivityPluginBinding) + fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) + fun onDetachedFromActivityForConfigChanges() + fun onDetachedFromActivity() +} +class ActivityPluginBinding(val activity: Activity) { + val permissionListeners = mutableListOf() + val activityListeners = mutableListOf() + fun addRequestPermissionsResultListener(listener: PluginRegistry.RequestPermissionsResultListener) { permissionListeners.add(listener) } + fun addActivityResultListener(listener: PluginRegistry.ActivityResultListener) { activityListeners.add(listener) } +} diff --git a/plugins/contact_permission/native_tests/android/src/fakes/kotlin/io/flutter/plugin/common/Methods.kt b/plugins/contact_permission/native_tests/android/src/fakes/kotlin/io/flutter/plugin/common/Methods.kt new file mode 100644 index 0000000..6f559f9 --- /dev/null +++ b/plugins/contact_permission/native_tests/android/src/fakes/kotlin/io/flutter/plugin/common/Methods.kt @@ -0,0 +1,15 @@ +package io.flutter.plugin.common +interface BinaryMessenger +class MethodCall(val method: String, val arguments: Any? = null) +class MethodChannel(val messenger: BinaryMessenger, val name: String) { + companion object { var latest: MethodChannel? = null } + init { latest = this } + var handler: MethodCallHandler? = null + fun setMethodCallHandler(value: MethodCallHandler?) { handler = value } + interface MethodCallHandler { fun onMethodCall(call: MethodCall, result: Result) } + interface Result { fun success(result: Any?); fun error(code: String, message: String?, details: Any?); fun notImplemented() } +} +interface PluginRegistry { + interface ActivityResultListener { fun onActivityResult(requestCode: Int, resultCode: Int, data: android.content.Intent?): Boolean } + interface RequestPermissionsResultListener { fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray): Boolean } +} diff --git a/plugins/contact_permission/native_tests/android/src/test/kotlin/ContactPermissionPluginTest.kt b/plugins/contact_permission/native_tests/android/src/test/kotlin/ContactPermissionPluginTest.kt new file mode 100644 index 0000000..da4b0b7 --- /dev/null +++ b/plugins/contact_permission/native_tests/android/src/test/kotlin/ContactPermissionPluginTest.kt @@ -0,0 +1,122 @@ +import android.Manifest +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import androidx.core.app.ActivityCompat +import com.nonstopio.contact.permission.contact_permission.ContactPermissionPlugin +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestCoroutineScheduler +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain +import kotlin.test.* + +@OptIn(ExperimentalCoroutinesApi::class) +class ContactPermissionPluginTest { + private lateinit var scheduler: TestCoroutineScheduler + private class Result : MethodChannel.Result { + val values = mutableListOf() + val errors = mutableListOf() + var unsupported = false + override fun success(result: Any?) { values.add(result) } + override fun error(code: String, message: String?, details: Any?) { + assertEquals("UNAVAILABLE", code) + assertNull(details) + errors.add(message) + } + override fun notImplemented() { unsupported = true } + } + + @BeforeTest fun reset() { + ActivityCompat.requests.clear() + scheduler = TestCoroutineScheduler() + Dispatchers.setMain(StandardTestDispatcher(scheduler)) + } + + @AfterTest fun restoreDispatcher() { Dispatchers.resetMain() } + + private fun attach(plugin: ContactPermissionPlugin, context: Context): FlutterPlugin.FlutterPluginBinding { + val binding = FlutterPlugin.FlutterPluginBinding(object : BinaryMessenger {}, context) + plugin.onAttachedToEngine(binding) + assertEquals("nonstopio_contact_permission", MethodChannel.latest!!.name) + assertSame(plugin, MethodChannel.latest!!.handler) + return binding + } + + @Test fun unavailableContextsAndUnknownMethod() { + val plugin = ContactPermissionPlugin() + for (method in listOf("isPermissionGranted", "requestPermission")) { + val result = Result() + plugin.onMethodCall(MethodCall(method), result) + assertEquals(listOf("Context is null"), result.errors) + } + val result = Result() + plugin.onMethodCall(MethodCall("unknown"), result) + assertTrue(result.unsupported) + } + + @Test fun grantedDeniedAndMissingActivity() { + val plugin = ContactPermissionPlugin() + val context = Context() + val binding = attach(plugin, context) + for (granted in listOf(false, true)) { + context.permission = if (granted) PackageManager.PERMISSION_GRANTED else -1 + val result = Result() + plugin.onMethodCall(MethodCall("isPermissionGranted"), result) + assertEquals(listOf(granted), result.values) + val request = Result() + plugin.onMethodCall(MethodCall("requestPermission"), request) + if (granted) assertEquals(listOf(true), request.values) + else assertEquals(listOf("Activity is null"), request.errors) + } + plugin.onDetachedFromEngine(binding) + assertNull(MethodChannel.latest!!.handler) + } + + @Test fun activityLifecycleAndPermissionCallbacks() { + val plugin = ContactPermissionPlugin() + attach(plugin, Context()) + val activity = ActivityPluginBinding(Activity()) + plugin.onAttachedToActivity(activity) + assertEquals(listOf(plugin), activity.permissionListeners) + assertEquals(listOf(plugin), activity.activityListeners) + assertFalse(plugin.onActivityResult(0, 0, Intent())) + assertFalse(plugin.onRequestPermissionsResult(99, emptyArray(), intArrayOf(0))) + assertTrue(plugin.onRequestPermissionsResult(0, emptyArray(), intArrayOf(0))) + scheduler.runCurrent() + for (results in listOf(intArrayOf(0), intArrayOf(-1), intArrayOf(), intArrayOf(0, 0))) { + val result = Result() + plugin.onMethodCall(MethodCall("requestPermission"), result) + val request = ActivityCompat.requests.last() + assertSame(activity.activity, request.activity) + assertContentEquals(arrayOf(Manifest.permission.READ_CONTACTS), request.permissions) + assertEquals(0, request.code) + assertTrue(plugin.onRequestPermissionsResult(0, request.permissions, results)) + assertTrue(result.values.isEmpty(), "Reply must be dispatched on the main queue") + scheduler.runCurrent() + assertEquals(listOf(results.contentEquals(intArrayOf(0))), result.values) + plugin.onRequestPermissionsResult(0, request.permissions, results) + scheduler.runCurrent() + assertEquals(1, result.values.size, "A result is completed only once") + } + plugin.onDetachedFromActivityForConfigChanges() + val detached = Result() + plugin.onMethodCall(MethodCall("requestPermission"), detached) + assertEquals(listOf("Activity is null"), detached.errors) + val replacement = ActivityPluginBinding(Activity()) + plugin.onReattachedToActivityForConfigChanges(replacement) + assertEquals(listOf(plugin), replacement.permissionListeners) + assertEquals(listOf(plugin), replacement.activityListeners) + plugin.onDetachedFromActivity() + val noActivity = Result() + plugin.onMethodCall(MethodCall("requestPermission"), noActivity) + assertEquals(listOf("Activity is null"), noActivity.errors) + } +} diff --git a/plugins/contact_permission/native_tests/objc/Flutter/Flutter.h b/plugins/contact_permission/native_tests/objc/Flutter/Flutter.h new file mode 100644 index 0000000..78f5a33 --- /dev/null +++ b/plugins/contact_permission/native_tests/objc/Flutter/Flutter.h @@ -0,0 +1,6 @@ +#import +@protocol FlutterPluginRegistrar +@end +@protocol FlutterPlugin ++ (void)registerWithRegistrar:(NSObject*)registrar; +@end diff --git a/plugins/contact_permission/native_tests/objc/contact_permission-Swift.h b/plugins/contact_permission/native_tests/objc/contact_permission-Swift.h new file mode 100644 index 0000000..3eaa82d --- /dev/null +++ b/plugins/contact_permission/native_tests/objc/contact_permission-Swift.h @@ -0,0 +1,4 @@ +#import +@interface SwiftContactPermissionPlugin : NSObject ++ (void)registerWithRegistrar:(NSObject*)registrar; +@end diff --git a/plugins/contact_permission/native_tests/objc/main.m b/plugins/contact_permission/native_tests/objc/main.m new file mode 100644 index 0000000..1b69477 --- /dev/null +++ b/plugins/contact_permission/native_tests/objc/main.m @@ -0,0 +1,23 @@ +#import "ContactPermissionPlugin.h" +#import "contact_permission-Swift.h" + +static NSObject* registered; +@interface Registrar : NSObject +@end +@implementation Registrar +@end +@implementation SwiftContactPermissionPlugin ++ (void)registerWithRegistrar:(NSObject*)registrar { + registered = registrar; +} +@end + +int main(void) { + @autoreleasepool { + Registrar* registrar = [Registrar new]; + [ContactPermissionPlugin registerWithRegistrar:registrar]; + NSCAssert(registered == registrar, @"Registration must be forwarded unchanged"); + puts("Objective-C registration check passed"); + } + return 0; +} diff --git a/plugins/contact_permission/native_tests/run.py b/plugins/contact_permission/native_tests/run.py new file mode 100644 index 0000000..8d0d753 --- /dev/null +++ b/plugins/contact_permission/native_tests/run.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +"""Compile native production sources against deterministic SDK boundary fakes.""" +import argparse +import json +import os +from pathlib import Path +import subprocess +import sys +import xml.etree.ElementTree as ET + +HERE = Path(__file__).resolve().parent +PLUGIN = HERE.parent +OUTPUT = HERE / 'coverage' + + +def run(command, cwd=HERE, env=None): + result = subprocess.run(command, cwd=cwd, env=env, text=True, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + with (OUTPUT / 'commands.log').open('a') as log: + log.write('$ ' + ' '.join(map(str, command)) + '\n' + result.stdout) + if result.returncode: + raise RuntimeError(result.stdout) + return result.stdout + + +def android(): + run(['./gradlew', 'test', 'jacocoTestCoverageVerification', '--no-daemon', + '--console=plain'], cwd=HERE / 'android') + report = HERE / 'android/build/reports/jacoco/test/jacocoTestReport.xml' + tree = ET.parse(report) + lines = tree.find("./counter[@type='LINE']") + missed, covered = int(lines.attrib['missed']), int(lines.attrib['covered']) + assert covered > 0 and missed == 0, f'Android uncovered: {missed}' + return {'platform': 'Android Kotlin', 'covered': covered, 'lines': covered + missed} + + +def apple(): + if sys.platform != 'darwin': + raise RuntimeError('Apple native coverage requires macOS and Xcode command line tools') + build = OUTPUT / 'apple' + build.mkdir(exist_ok=True) + for module in ['Contacts', 'Flutter', 'UIKit']: + run(['xcrun', 'swiftc', '-swift-version', '5', '-emit-module', '-emit-library', + '-module-name', module, str(HERE / 'swift' / f'{module}.swift'), + '-emit-module-path', str(build / f'{module}.swiftmodule'), + '-o', str(build / f'lib{module}.dylib')]) + swift_sources = [PLUGIN / 'ios/Classes/ContactPermissionHandler.swift', + PLUGIN / 'ios/Classes/SwiftContactPermissionPlugin.swift'] + binary = build / 'swift_tests' + run(['xcrun', 'swiftc', '-swift-version', '5', '-profile-generate', + '-profile-coverage-mapping', '-I', str(build), '-L', str(build), + '-lContacts', '-lFlutter', '-lUIKit', '-Xlinker', '-rpath', '-Xlinker', str(build), + *map(str, swift_sources), str(HERE / 'swift/main.swift'), '-o', str(binary)]) + objc_source = PLUGIN / 'ios/Classes/ContactPermissionPlugin.m' + objc_binary = build / 'objc_tests' + run(['xcrun', 'clang', '-fprofile-instr-generate', '-fcoverage-mapping', + '-fobjc-arc', '-framework', 'Foundation', '-I', str(HERE / 'objc'), + '-I', str(PLUGIN / 'ios/Classes'), str(objc_source), str(HERE / 'objc/main.m'), + '-o', str(objc_binary)]) + covered = total = 0 + for label, executable, sources in [('swift', binary, swift_sources), + ('objc', objc_binary, [objc_source])]: + profile = build / f'{label}.profraw' + data = build / f'{label}.profdata' + run([str(executable)], env={**os.environ, 'LLVM_PROFILE_FILE': str(profile)}) + run(['xcrun', 'llvm-profdata', 'merge', '-sparse', str(profile), '-o', str(data)]) + report = json.loads(run(['xcrun', 'llvm-cov', 'export', str(executable), + '-instr-profile=' + str(data), *map(str, sources)])) + (build / f'{label}-coverage.json').write_text(json.dumps(report, indent=2)) + for file in report['data'][0]['files']: + lines = file['summary']['lines'] + covered += lines['covered'] + total += lines['count'] + assert lines['covered'] == lines['count'], f"Uncovered native lines: {file['filename']}" + assert total > 0 + return {'platform': 'Apple Swift/Objective-C', 'covered': covered, 'lines': total} + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--platform', choices=['all', 'android', 'apple'], default='all') + args = parser.parse_args() + OUTPUT.mkdir(exist_ok=True) + (OUTPUT / 'commands.log').write_text('') + results = [] + if args.platform in ['all', 'android']: + results.append(android()) + if args.platform in ['all', 'apple']: + results.append(apple()) + (OUTPUT / 'summary.json').write_text(json.dumps(results, indent=2) + '\n') + for result in results: + print(f"{result['platform']}: {result['covered']}/{result['lines']} executable lines (100%)") + + +if __name__ == '__main__': + main() diff --git a/plugins/contact_permission/native_tests/swift/Contacts.swift b/plugins/contact_permission/native_tests/swift/Contacts.swift new file mode 100644 index 0000000..f88895b --- /dev/null +++ b/plugins/contact_permission/native_tests/swift/Contacts.swift @@ -0,0 +1,22 @@ +import Foundation + +public enum CNEntityType { case contacts } +public enum CNAuthorizationStatus { case notDetermined, restricted, denied, authorized, limited } + +/// Test double for the SDK boundary. Production sources are compiled unchanged. +public final class CNContactStore { + public static var status: CNAuthorizationStatus = .notDetermined + public static var granted = false + public static var error: Error? + public static var requests = 0 + public init() {} + public static func authorizationStatus(for entity: CNEntityType) -> CNAuthorizationStatus { + precondition(entity == .contacts) + return status + } + public func requestAccess(for entity: CNEntityType, completionHandler: (Bool, Error?) -> Void) { + precondition(entity == .contacts) + Self.requests += 1 + completionHandler(Self.granted, Self.error) + } +} diff --git a/plugins/contact_permission/native_tests/swift/Flutter.swift b/plugins/contact_permission/native_tests/swift/Flutter.swift new file mode 100644 index 0000000..0bfee7c --- /dev/null +++ b/plugins/contact_permission/native_tests/swift/Flutter.swift @@ -0,0 +1,24 @@ +@_exported import Foundation + +public typealias FlutterResult = (Any?) -> Void +public let FlutterMethodNotImplemented = NSObject() +public protocol FlutterPluginRegistrar { + func messenger() -> NSObject + func addMethodCallDelegate(_ instance: FlutterPlugin, channel: FlutterMethodChannel) +} +public protocol FlutterPlugin { + static func register(with registrar: FlutterPluginRegistrar) + func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) +} +public final class FlutterMethodChannel { + public let name: String + public let binaryMessenger: NSObject + public init(name: String, binaryMessenger: NSObject) { + self.name = name + self.binaryMessenger = binaryMessenger + } +} +public final class FlutterMethodCall { + public let method: String + public init(method: String) { self.method = method } +} diff --git a/plugins/contact_permission/native_tests/swift/UIKit.swift b/plugins/contact_permission/native_tests/swift/UIKit.swift new file mode 100644 index 0000000..ff2b4e2 --- /dev/null +++ b/plugins/contact_permission/native_tests/swift/UIKit.swift @@ -0,0 +1 @@ +@_exported import Foundation diff --git a/plugins/contact_permission/native_tests/swift/main.swift b/plugins/contact_permission/native_tests/swift/main.swift new file mode 100644 index 0000000..392f68b --- /dev/null +++ b/plugins/contact_permission/native_tests/swift/main.swift @@ -0,0 +1,55 @@ +import Foundation +import Contacts +import Flutter + +final class Registrar: FlutterPluginRegistrar { + let binaryMessenger = NSObject() + var plugin: FlutterPlugin? + var channel: FlutterMethodChannel? + func messenger() -> NSObject { binaryMessenger } + func addMethodCallDelegate(_ instance: FlutterPlugin, channel: FlutterMethodChannel) { + plugin = instance + self.channel = channel + } +} + +let registrar = Registrar() +SwiftContactPermissionPlugin.register(with: registrar) +precondition(registrar.channel!.name == "nonstopio_contact_permission") +precondition(registrar.channel!.binaryMessenger === registrar.binaryMessenger) +let plugin = registrar.plugin! + +for status in [CNAuthorizationStatus.authorized, .denied, .restricted, .notDetermined, .limited] { + CNContactStore.status = status + var responses = [Bool]() + plugin.handle(FlutterMethodCall(method: "isPermissionGranted")) { result in + responses.append(result as! Bool) + } + precondition(responses == [status == .authorized]) +} + +for granted in [true, false] { + CNContactStore.granted = granted + CNContactStore.error = nil + var responses = [Bool]() + plugin.handle(FlutterMethodCall(method: "requestPermission")) { result in + responses.append(result as! Bool) + } + precondition(responses == [granted]) +} + +CNContactStore.error = NSError(domain: "contacts-test", code: 1) +CNContactStore.granted = true +var responses = [Bool]() +plugin.handle(FlutterMethodCall(method: "requestPermission")) { result in + responses.append(result as! Bool) +} +precondition(responses == [false]) +precondition(CNContactStore.requests == 3) + +var unsupported = false +plugin.handle(FlutterMethodCall(method: "unknown")) { result in + unsupported = (result as AnyObject) === FlutterMethodNotImplemented +} +precondition(unsupported) +print("Swift native permission checks passed") diff --git a/plugins/contact_permission/pubspec.yaml b/plugins/contact_permission/pubspec.yaml index 637cac9..d8dd941 100644 --- a/plugins/contact_permission/pubspec.yaml +++ b/plugins/contact_permission/pubspec.yaml @@ -7,8 +7,8 @@ repository: https://github.com/nonstopio/flutter_forge/tree/main/plugins/contact issue_tracker: https://github.com/nonstopio/flutter_forge/issues environment: - sdk: '>=2.18.6 <3.0.0' - flutter: ">=2.5.0" + sdk: '>=3.12.0 <4.0.0' + flutter: ">=3.44.0" dependencies: flutter: @@ -18,7 +18,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -73,3 +73,5 @@ flutter: # # For details regarding fonts in packages, see # https://flutter.dev/custom-fonts/#from-packages + +resolution: workspace diff --git a/plugins/contact_permission/test/contact_permission_method_channel_test.dart b/plugins/contact_permission/test/contact_permission_method_channel_test.dart index 7a9d6f7..80f6779 100644 --- a/plugins/contact_permission/test/contact_permission_method_channel_test.dart +++ b/plugins/contact_permission/test/contact_permission_method_channel_test.dart @@ -1,13 +1,45 @@ +import 'package:contact_permission/contact_permission_method_channel.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { - group('ContactPermissionMethodChannel', () { - test('demo test should pass', () { - expect(true, isTrue); + TestWidgetsFlutterBinding.ensureInitialized(); + final platform = MethodChannelContactPermission(); + final messenger = + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger; + final calls = []; + tearDown(() { + messenger.setMockMethodCallHandler(platform.methodChannel, null); + calls.clear(); + }); + + for (final response in [true, false, null]) { + test('permission methods handle native response $response', () async { + messenger.setMockMethodCallHandler(platform.methodChannel, (call) async { + calls.add(call); + return response; + }); + expect(await platform.isPermissionGranted(), response ?? false); + expect(await platform.requestPermission(), response ?? false); + expect(calls.map((call) => call.method), [ + 'isPermissionGranted', + 'requestPermission', + ]); + expect(calls.every((call) => call.arguments == null), isTrue); }); + } - test('basic arithmetic test', () { - expect(2 + 2, equals(4)); + test('propagates native platform errors', () async { + messenger.setMockMethodCallHandler(platform.methodChannel, (_) async { + throw PlatformException(code: 'unavailable'); }); + await expectLater( + platform.isPermissionGranted(), + throwsA(isA()), + ); + await expectLater( + platform.requestPermission(), + throwsA(isA()), + ); }); } diff --git a/plugins/contact_permission/test/contact_permission_test.dart b/plugins/contact_permission/test/contact_permission_test.dart index 90f8a80..92dd54e 100644 --- a/plugins/contact_permission/test/contact_permission_test.dart +++ b/plugins/contact_permission/test/contact_permission_test.dart @@ -1,13 +1,51 @@ +import 'package:contact_permission/contact_permission.dart'; +import 'package:contact_permission/contact_permission_platform_interface.dart'; import 'package:flutter_test/flutter_test.dart'; +class TestPermissionPlatform extends ContactPermissionPlatform { + bool granted = false; + bool requested = false; + bool fail = false; + + @override + Future isPermissionGranted() { + if (fail) throw StateError('platform unavailable'); + return Future.value(granted); + } + + @override + Future requestPermission() { + requested = true; + if (fail) throw StateError('platform unavailable'); + return Future.value(granted); + } +} + void main() { - group('ContactPermission', () { - test('demo test should pass', () { - expect(true, isTrue); - }); - - test('basic arithmetic test', () { - expect(2 + 2, equals(4)); - }); + late ContactPermissionPlatform original; + late TestPermissionPlatform platform; + setUp(() { + original = ContactPermissionPlatform.instance; + platform = TestPermissionPlatform(); + ContactPermissionPlatform.instance = platform; + }); + tearDown(() => ContactPermissionPlatform.instance = original); + + test( + 'delegates permission checks and requests to the registered platform', + () async { + for (final granted in [false, true]) { + platform.granted = granted; + expect(await ContactPermission.isPermissionGranted(), granted); + expect(await ContactPermission.requestPermission(), granted); + expect(platform.requested, isTrue); + } + }, + ); + + test('returns false when platform calls throw synchronously', () async { + platform.fail = true; + expect(await ContactPermission.isPermissionGranted(), isFalse); + expect(await ContactPermission.requestPermission(), isFalse); }); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ebc7cb5..b9ee753 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,20 +4,23 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + tmp: ^0.2.7 + importers: .: dependencies: '@changesets/cli': - specifier: ^2.27.12 - version: 2.29.5 + specifier: ^3.0.3 + version: 3.0.3 devDependencies: '@commitlint/cli': - specifier: ^19.6.0 - version: 19.8.1(@types/node@24.1.0)(typescript@5.8.3) + specifier: ^21.2.3 + version: 21.2.3(@types/node@26.6.2)(conventional-commits-parser@7.1.2)(typescript@7.0.2) '@commitlint/config-conventional': - specifier: ^19.6.0 - version: 19.8.1 + specifier: ^21.2.3 + version: 21.2.3 all-contributors-cli: specifier: ^6.26.1 version: 6.26.1 @@ -25,193 +28,335 @@ importers: specifier: ^9.1.7 version: 9.1.7 prettier: - specifier: ^3.4.2 - version: 3.6.2 + specifier: ^3.9.8 + version: 3.9.8 typescript: - specifier: ^5.7.3 - version: 5.8.3 + specifier: ^7.0.2 + version: 7.0.2 packages: - '@babel/code-frame@7.27.1': - resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.27.1': - resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} - '@babel/runtime@7.27.6': - resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==} + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} engines: {node: '>=6.9.0'} - '@changesets/apply-release-plan@7.0.12': - resolution: {integrity: sha512-EaET7As5CeuhTzvXTQCRZeBUcisoYPDDcXvgTE/2jmmypKp0RC7LxKj/yzqeh/1qFTZI7oDGFcL1PHRuQuketQ==} + '@changesets/apply-release-plan@8.1.1': + resolution: {integrity: sha512-nk2Hbq3M9NeHMdaIRBDvawKrQJzXB9sGyBUE5yWnIKtvdcNCPj75zBekJYr0gTT2ecJH3eFeTrc8eisPImcv0Q==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/assemble-release-plan@6.0.9': - resolution: {integrity: sha512-tPgeeqCHIwNo8sypKlS3gOPmsS3wP0zHt67JDuL20P4QcXiw/O4Hl7oXiuLnP9yg+rXLQ2sScdV1Kkzde61iSQ==} + '@changesets/assemble-release-plan@7.0.0': + resolution: {integrity: sha512-oEW8BxdA604kGGtDSCiHr5w9Tv4UWe9I2k61IBNZzCOE1kbYaJj4v+lFQNgcEZFkUc2pV/+hASErGDvpJOZCTg==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/changelog-git@0.2.1': - resolution: {integrity: sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==} + '@changesets/changelog-git@1.0.0': + resolution: {integrity: sha512-3Dst2Ime2Op5nd4XmWJLPIgp11ZFqJqSkVug9izK6TDcIV4YlhPS4ECbEVR+eGI0bk0r1ItogD4j2Oli87bJrA==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/cli@2.29.5': - resolution: {integrity: sha512-0j0cPq3fgxt2dPdFsg4XvO+6L66RC0pZybT9F4dG5TBrLA3jA/1pNkdTXH9IBBVHkgsKrNKenI3n1mPyPlIydg==} + '@changesets/cli@3.0.3': + resolution: {integrity: sha512-L1i67bMfkK6iC/kDejuNO4pFgTRcWIObsD6Qof2woP1I4M8tj17DGlcpRaTzTv1DYSP5EMwWdEtR8X0Pj5Jg3Q==} + engines: {node: ^22.11 || ^24 || >=26, npm: '>=10.9.0', pnpm: '>=10.0.0', yarn: '>=4.5.2'} hasBin: true - '@changesets/config@3.1.1': - resolution: {integrity: sha512-bd+3Ap2TKXxljCggI0mKPfzCQKeV/TU4yO2h2C6vAihIo8tzseAn2e7klSuiyYYXvgu53zMN1OeYMIQkaQoWnA==} - - '@changesets/errors@0.2.0': - resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} + '@changesets/config@4.0.1': + resolution: {integrity: sha512-PxpxSQLQSLDjceAgRq+4FWlU+HflDIw03mmPVipxoQx9xrXFUjmqTy3SE+n/2t+DqPdpPiLWVr5NRiLZvjyLwg==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/get-dependents-graph@2.1.3': - resolution: {integrity: sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ==} + '@changesets/errors@1.0.0': + resolution: {integrity: sha512-ElN/mEzn6zmETgjwf5MclCMa9ef59sAR0lfO8VSYIsiRvbC2FbLB/92EoYw10Sl0kGixxHFiJZUSv7dA+YpR8g==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/get-release-plan@4.0.13': - resolution: {integrity: sha512-DWG1pus72FcNeXkM12tx+xtExyH/c9I1z+2aXlObH3i9YA7+WZEVaiHzHl03thpvAgWTRaH64MpfHxozfF7Dvg==} + '@changesets/format@0.1.2': + resolution: {integrity: sha512-Caez5XtNXCFS/G5bwyav3wuXL0tMxVd2ZGbaumWbzN08tyzO21asCw7JZhNtVsAZDCvDRUzZN+Iit9SyRITSYA==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/get-version-range-type@0.4.0': - resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} + '@changesets/get-dependents-graph@3.0.0': + resolution: {integrity: sha512-ji/t5wFA1zREKXRUePE6Qi+Qu2UgxCeSSGQrphezwvQZrp49B7sJ+8+wvM0tA7zPeSxYKCojDy3WWgrl+s+awg==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/git@3.0.4': - resolution: {integrity: sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==} + '@changesets/git@4.0.1': + resolution: {integrity: sha512-6vWpIAC4LkpmlqaIu37ViT5enyd9+uBwAiGqCGhASvkSHBgx4PrtTGXWTMFYpDmZbjgyonyVgMciPrW4MqtJsA==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/logger@0.1.1': - resolution: {integrity: sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==} + '@changesets/parse@1.0.0': + resolution: {integrity: sha512-P0iaMb9p9CRYZiTgAllEIF9AUMQHIy1G72tKlcIqJp61icZSsKQNiOPdxAMZG8m/DvZwt/oz5xEbTpike//dWg==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/parse@0.4.1': - resolution: {integrity: sha512-iwksMs5Bf/wUItfcg+OXrEpravm5rEd9Bf4oyIPL4kVTmJQ7PNDSd6MDYkpSJR1pn7tz/k8Zf2DhTCqX08Ou+Q==} + '@changesets/pre@3.0.0': + resolution: {integrity: sha512-Zm/6YliV/a2oeWTqHJf6KxLrQwgcK1i/BRDl2m0EKZvbnxV5fG9QRhwJJGshjsZTUTS6dkfURQ2K6aAvgNw/3Q==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/pre@2.0.2': - resolution: {integrity: sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==} + '@changesets/read@1.0.1': + resolution: {integrity: sha512-nzvSC6RcTiWf/dsuwZFXpLzGGsRHYCL68U3uHV7srsulU93aVWY7u2GEJiDZ+4GIIElfaW5EqtKC0UHroY+LTQ==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/read@0.6.5': - resolution: {integrity: sha512-UPzNGhsSjHD3Veb0xO/MwvasGe8eMyNrR/sT9gR8Q3DhOQZirgKhhXv/8hVsI0QpPjR004Z9iFxoJU6in3uGMg==} + '@changesets/should-skip-package@1.0.0': + resolution: {integrity: sha512-pwqoJmbONn1XgXmZXPEExgAaT+HdZLjALFTDgIm+PnS5KeO2nLtzA2/Q+4aMFY14kFMuXKG30MObhvDzWgzDgg==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/should-skip-package@0.1.2': - resolution: {integrity: sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==} + '@changesets/types@7.0.0': + resolution: {integrity: sha512-c5GoiQyt3pxiXjrWSNoP8/GRf4kG+VnKzovx1OQM8dYYALlSwgedmkPmJ+ZqGxqwg9D3Bkj85Uo4KLd5BN3A0w==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/types@4.1.0': - resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} + '@changesets/write@1.0.1': + resolution: {integrity: sha512-q/ThtP9gcnEP6xlv7LrY26C2mHqdGBti/MmOVngt/M/oIGYkssmQGxPK9WzBNt2juVcH/vml2WQ+ra8LXYOTaA==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/types@6.1.0': - resolution: {integrity: sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==} + '@clack/core@1.5.1': + resolution: {integrity: sha512-iHTrHA8MtVuLl2TfZySmcKv1qO2PoyC9Z7pfSDozEuV5vtY3/wcOPKJXlqJ5Oq2Cx5DDGQGAMVx6HZfRRoVEbQ==} + engines: {node: '>= 20.12.0'} - '@changesets/write@0.4.0': - resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} + '@clack/prompts@1.8.1': + resolution: {integrity: sha512-dlT1m5e/0yUL0kRNcQn7yGLVThkgbB0Ga/1AmfDDC/8ik6AIiSf2QLQO2zPYvefsHP0aFgxO93cVLCCfDp7kzQ==} + engines: {node: '>= 20.12.0'} - '@commitlint/cli@19.8.1': - resolution: {integrity: sha512-LXUdNIkspyxrlV6VDHWBmCZRtkEVRpBKxi2Gtw3J54cGWhLCTouVD/Q6ZSaSvd2YaDObWK8mDjrz3TIKtaQMAA==} - engines: {node: '>=v18'} + '@commitlint/cli@21.2.3': + resolution: {integrity: sha512-5yOX6IRjcrIReCxpyAd04hgI/yhUMpVBJ/ouwFV0wk9yx5+fYRDWZWJ5h55Tbedgfol+5m2mU5nqzbB8g3v0vQ==} + engines: {node: '>=22.12.0'} hasBin: true - '@commitlint/config-conventional@19.8.1': - resolution: {integrity: sha512-/AZHJL6F6B/G959CsMAzrPKKZjeEiAVifRyEwXxcT6qtqbPwGw+iQxmNS+Bu+i09OCtdNRW6pNpBvgPrtMr9EQ==} - engines: {node: '>=v18'} - - '@commitlint/config-validator@19.8.1': - resolution: {integrity: sha512-0jvJ4u+eqGPBIzzSdqKNX1rvdbSU1lPNYlfQQRIFnBgLy26BtC0cFnr7c/AyuzExMxWsMOte6MkTi9I3SQ3iGQ==} - engines: {node: '>=v18'} + '@commitlint/config-conventional@21.2.3': + resolution: {integrity: sha512-EP2n5DN2GwRWNVrLX0Jx3mpQlW5PfFi9HnD9OJ8hN/H0xFqpCFyHeGDhg+0P8/21iMMcdkcQgbNqmVklebqYsQ==} + engines: {node: '>=22.12.0'} - '@commitlint/ensure@19.8.1': - resolution: {integrity: sha512-mXDnlJdvDzSObafjYrOSvZBwkD01cqB4gbnnFuVyNpGUM5ijwU/r/6uqUmBXAAOKRfyEjpkGVZxaDsCVnHAgyw==} - engines: {node: '>=v18'} - - '@commitlint/execute-rule@19.8.1': - resolution: {integrity: sha512-YfJyIqIKWI64Mgvn/sE7FXvVMQER/Cd+s3hZke6cI1xgNT/f6ZAz5heND0QtffH+KbcqAwXDEE1/5niYayYaQA==} - engines: {node: '>=v18'} - - '@commitlint/format@19.8.1': - resolution: {integrity: sha512-kSJj34Rp10ItP+Eh9oCItiuN/HwGQMXBnIRk69jdOwEW9llW9FlyqcWYbHPSGofmjsqeoxa38UaEA5tsbm2JWw==} - engines: {node: '>=v18'} + '@commitlint/config-validator@21.2.3': + resolution: {integrity: sha512-JQ13v0GafSM4fBYX8UowSy++07Was20auq0NcTdnzSEq6/05Cq5GBUMIfKkqjzyBwC9sJFmPpKWgLLhH7cygkw==} + engines: {node: '>=22.12.0'} - '@commitlint/is-ignored@19.8.1': - resolution: {integrity: sha512-AceOhEhekBUQ5dzrVhDDsbMaY5LqtN8s1mqSnT2Kz1ERvVZkNihrs3Sfk1Je/rxRNbXYFzKZSHaPsEJJDJV8dg==} - engines: {node: '>=v18'} + '@commitlint/ensure@21.2.3': + resolution: {integrity: sha512-gEjNKgRocbODI5/sMx2Qc9XOf2/zFTNV2aEK1QZ4gKDKxDC/VR7us3QtIX1C5d5xxHxx0I/tEbYbdSP1Am2LwA==} + engines: {node: '>=22.12.0'} - '@commitlint/lint@19.8.1': - resolution: {integrity: sha512-52PFbsl+1EvMuokZXLRlOsdcLHf10isTPlWwoY1FQIidTsTvjKXVXYb7AvtpWkDzRO2ZsqIgPK7bI98x8LRUEw==} - engines: {node: '>=v18'} + '@commitlint/execute-rule@21.0.1': + resolution: {integrity: sha512-RifH+FmImozKBE6mozhF4K3r2RRKP7SMi/Q/zLCmExtp5e05lhHOUYqGBlFBAGNHaZxU/WYw1XuugYK9jQzqnA==} + engines: {node: '>=22.12.0'} - '@commitlint/load@19.8.1': - resolution: {integrity: sha512-9V99EKG3u7z+FEoe4ikgq7YGRCSukAcvmKQuTtUyiYPnOd9a2/H9Ak1J9nJA1HChRQp9OA/sIKPugGS+FK/k1A==} - engines: {node: '>=v18'} + '@commitlint/format@21.2.3': + resolution: {integrity: sha512-KKwCjXYqA8r295uhLnulDXL9yekdXmj2yp2Q0XsBPhxQ2iPuvRE8F43j7g7yexo69wTdmUQmFz15jpderluzUQ==} + engines: {node: '>=22.12.0'} - '@commitlint/message@19.8.1': - resolution: {integrity: sha512-+PMLQvjRXiU+Ae0Wc+p99EoGEutzSXFVwQfa3jRNUZLNW5odZAyseb92OSBTKCu+9gGZiJASt76Cj3dLTtcTdg==} - engines: {node: '>=v18'} + '@commitlint/is-ignored@21.2.3': + resolution: {integrity: sha512-Ng4nRj/LBAOvc+GQu7bsFW4/fL89LvZ14OOC/ERLbUpnG6QBwWRzY/1oXGwPs3C8YHWjJObin4kLYo2YeEn70w==} + engines: {node: '>=22.12.0'} - '@commitlint/parse@19.8.1': - resolution: {integrity: sha512-mmAHYcMBmAgJDKWdkjIGq50X4yB0pSGpxyOODwYmoexxxiUCy5JJT99t1+PEMK7KtsCtzuWYIAXYAiKR+k+/Jw==} - engines: {node: '>=v18'} + '@commitlint/lint@21.2.3': + resolution: {integrity: sha512-lUZcSuVLLEDkhge9ERk8Rp+FXQtrUhCK+h328BFnzKXrTuXGdqiYMEpTlNWdZaOrjyMhMJaz354ZaWEGJxcLeg==} + engines: {node: '>=22.12.0'} - '@commitlint/read@19.8.1': - resolution: {integrity: sha512-03Jbjb1MqluaVXKHKRuGhcKWtSgh3Jizqy2lJCRbRrnWpcM06MYm8th59Xcns8EqBYvo0Xqb+2DoZFlga97uXQ==} - engines: {node: '>=v18'} + '@commitlint/load@21.2.3': + resolution: {integrity: sha512-cDrL8lOo23Lk7knGzQdjbFcq7z2JgvjwV6PhbESVvEc+Fe21MeLjPKBJi8nKjg7j+jh79CVbVSUbZc104thXRw==} + engines: {node: '>=22.12.0'} - '@commitlint/resolve-extends@19.8.1': - resolution: {integrity: sha512-GM0mAhFk49I+T/5UCYns5ayGStkTt4XFFrjjf0L4S26xoMTSkdCf9ZRO8en1kuopC4isDFuEm7ZOm/WRVeElVg==} - engines: {node: '>=v18'} + '@commitlint/message@21.2.0': + resolution: {integrity: sha512-YxGoiXD/HXNXLJPrQwE5poXa+XH0CBEm+mdvbHQP0g6MV/dmJyUFCzPNzZbxL93GvZ70TmtTK0Z0/IBpAqHv8g==} + engines: {node: '>=22.12.0'} - '@commitlint/rules@19.8.1': - resolution: {integrity: sha512-Hnlhd9DyvGiGwjfjfToMi1dsnw1EXKGJNLTcsuGORHz6SS9swRgkBsou33MQ2n51/boIDrbsg4tIBbRpEWK2kw==} - engines: {node: '>=v18'} + '@commitlint/parse@21.2.3': + resolution: {integrity: sha512-Jt61QpF1xA88damCELe8I0FlDZPSpXNc8IhN7z5kZ0EcDYPjdZ+ZHPvsSXl69r2MIlLnNKeHhOn2cmVpziFE9w==} + engines: {node: '>=22.12.0'} - '@commitlint/to-lines@19.8.1': - resolution: {integrity: sha512-98Mm5inzbWTKuZQr2aW4SReY6WUukdWXuZhrqf1QdKPZBCCsXuG87c+iP0bwtD6DBnmVVQjgp4whoHRVixyPBg==} - engines: {node: '>=v18'} + '@commitlint/read@21.2.3': + resolution: {integrity: sha512-szAepuLA1vTTr7fuudEg7jYyVHudf+FH+vptU91bhxE1WnE8TKrfTAi44HVllz8XpI8Pvp4GjR/iP5Nshgh+og==} + engines: {node: '>=22.12.0'} - '@commitlint/top-level@19.8.1': - resolution: {integrity: sha512-Ph8IN1IOHPSDhURCSXBz44+CIu+60duFwRsg6HqaISFHQHbmBtxVw4ZrFNIYUzEP7WwrNPxa2/5qJ//NK1FGcw==} - engines: {node: '>=v18'} + '@commitlint/resolve-extends@21.2.3': + resolution: {integrity: sha512-bqJdmAtXgAQel+l02uIsF7rHgKgx1AGZdGBjam7uK4XW54ZAQtB6XAIUXezYprQiCcRMpBh4fHs5JodXK4mVZA==} + engines: {node: '>=22.12.0'} - '@commitlint/types@19.8.1': - resolution: {integrity: sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==} - engines: {node: '>=v18'} + '@commitlint/rules@21.2.3': + resolution: {integrity: sha512-5+YnI/LuTx3JDl9GjqfP2z9x96M8KKDrd+4jOel/T0L1BB3oAmBlSN8xppf63QsudiVEgR8C0gRoAMrPVrms6g==} + engines: {node: '>=22.12.0'} - '@manypkg/find-root@1.1.0': - resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} + '@commitlint/to-lines@21.0.1': + resolution: {integrity: sha512-bd1BFII7p1EQZre9Kaj+kKaMFP3cFCdt21K7DItVux9XP5WjLgJ0/Uy1pJJh9aPwVJ6SKg62PxqlZaHI8hQAXw==} + engines: {node: '>=22.12.0'} - '@manypkg/get-packages@1.1.3': - resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} + '@commitlint/top-level@21.2.0': + resolution: {integrity: sha512-Y5gmQ+KxzqCrBFJfLvFEPvvwD3LDiNZoTT2yeFBm96M8qhmqSzQc5DvX3rheAaAMjyIvMXOCLS/mWfdpONsjyQ==} + engines: {node: '>=22.12.0'} - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} + '@commitlint/types@21.2.3': + resolution: {integrity: sha512-sGEA473TlCvhCGNoTBY9OXZVWdscIXdGt0dm8EpB+2MRfmt8LNh/X56P/T3nCtEB+eH5qGEa3odphSAoUxSfSQ==} + engines: {node: '>=22.12.0'} - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - - '@types/conventional-commits-parser@5.0.1': - resolution: {integrity: sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==} - - '@types/node@12.20.55': - resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} - - '@types/node@24.1.0': - resolution: {integrity: sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==} - - JSONStream@1.3.5: - resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} - hasBin: true + '@conventional-changelog/git-client@3.1.2': + resolution: {integrity: sha512-jZqwnJwf7nboIlAcw/mkOjVa6DexCcUOgT2oOQgkoi3z9vR8tGFkcMy2BFcYwjhL9sYcDDXkRQDayiDieCoW7A==} + engines: {node: '>=22'} + peerDependencies: + conventional-commits-filter: ^6.0.1 + conventional-commits-parser: ^7.1.2 + peerDependenciesMeta: + conventional-commits-filter: + optional: true + conventional-commits-parser: + optional: true - ajv@8.17.1: - resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + '@conventional-changelog/template@1.4.0': + resolution: {integrity: sha512-aalGyl7dbB5PArRebDIX43ZvBlXrYm9uWzGJ26t+4SzJVPsOuvfILGGbw5X4yX7i50YEmJ8zvbiWnqH/AAnZqg==} + engines: {node: '>=22'} + + '@manypkg/find-root@3.1.0': + resolution: {integrity: sha512-BcSqCyKhBVZ5YkSzOiheMCV41kqAFptW6xGqYSTjkVTl9XQpr+pqHhwgGCOHQtjDCv7Is6EFyA14Sm5GVbVABA==} + engines: {node: '>=20.0.0'} + + '@manypkg/get-packages@3.1.0': + resolution: {integrity: sha512-0TbBVyvPrP7xGYBI/cP8UP+yl/z+HtbTttAD7FMAJgn/kXOTwh5/60TsqP9ZYY710forNfyV0N8P/IE/ujGZJg==} + engines: {node: '>=20.0.0'} + + '@manypkg/tools@2.1.2': + resolution: {integrity: sha512-6QEf6yqFbETdwGITKq57aYoPfX/3K8XFNwsAlx0C1M7o8cb79sv1M3w+tWuWvIcSbNqrLF7OD7YpZMVVz335hQ==} + engines: {node: '>=20.0.0'} + + '@pnpm/deps.graph-sequencer@1100.0.1': + resolution: {integrity: sha512-pOr5+q1fLYKwFN3LAJuGZEnfXDcQ73zqgDHMtGy+K+uIoUqyY+6MeDCWFwfu+4EFuq76I5EPFofoNAI+Bmmq4A==} + engines: {node: '>=22.13'} + + '@simple-libs/child-process-utils@2.0.0': + resolution: {integrity: sha512-dvNoRKLijXnD0XoJAz94pbNuB5GQgDr55UhpSPhffDkTT0Cmcqh9jSCOtwfT2d4H6MI9E7c4SgtMuJXZ6F3c6A==} + engines: {node: '>=22'} + + '@simple-libs/stream-utils@2.0.0': + resolution: {integrity: sha512-fCTuZK4QBa+39Oz9l4OGfJfz+GpwCp3AqO7Zch3to99xHPgstVsRFpeQ8LNd2o1Gv8raL2mCFwiaHh7bFSp5DQ==} + engines: {node: '>=22'} + + '@types/node@26.6.2': + resolution: {integrity: sha512-X1P21scMv4zGKLYqjdGjaKa7COa0RKVYYZZN/NfvLQ1JegxFhdhpZG/Lyn8AXx6CDUavKAd11v6BvfpkDByK8g==} + + '@typescript/typescript-aix-ppc64@7.0.2': + resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [aix] + + '@typescript/typescript-darwin-arm64@7.0.2': + resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] + + '@typescript/typescript-darwin-x64@7.0.2': + resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] + + '@typescript/typescript-freebsd-arm64@7.0.2': + resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [freebsd] + + '@typescript/typescript-freebsd-x64@7.0.2': + resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [freebsd] + + '@typescript/typescript-linux-arm64@7.0.2': + resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + + '@typescript/typescript-linux-arm@7.0.2': + resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} + engines: {node: '>=16.20.0'} + cpu: [arm] + os: [linux] + + '@typescript/typescript-linux-loong64@7.0.2': + resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} + engines: {node: '>=16.20.0'} + cpu: [loong64] + os: [linux] + + '@typescript/typescript-linux-mips64el@7.0.2': + resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} + engines: {node: '>=16.20.0'} + cpu: [mips64el] + os: [linux] + + '@typescript/typescript-linux-ppc64@7.0.2': + resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [linux] + + '@typescript/typescript-linux-riscv64@7.0.2': + resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} + engines: {node: '>=16.20.0'} + cpu: [riscv64] + os: [linux] + + '@typescript/typescript-linux-s390x@7.0.2': + resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} + engines: {node: '>=16.20.0'} + cpu: [s390x] + os: [linux] + + '@typescript/typescript-linux-x64@7.0.2': + resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + + '@typescript/typescript-netbsd-arm64@7.0.2': + resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [netbsd] + + '@typescript/typescript-netbsd-x64@7.0.2': + resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [netbsd] + + '@typescript/typescript-openbsd-arm64@7.0.2': + resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [openbsd] + + '@typescript/typescript-openbsd-x64@7.0.2': + resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [openbsd] + + '@typescript/typescript-sunos-x64@7.0.2': + resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [sunos] + + '@typescript/typescript-win32-arm64@7.0.2': + resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] + + '@typescript/typescript-win32-x64@7.0.2': + resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] + + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} all-contributors-cli@6.26.1: resolution: {integrity: sha512-Ymgo3FJACRBEd1eE653FD1J/+uD0kqpUNYfr9zNC1Qby0LgbhDBzB3EF6uvkAbYpycStkk41J+0oo37Lc02yEw==} engines: {node: '>=4'} hasBin: true - ansi-colors@4.1.3: - resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} - engines: {node: '>=6'} - ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} @@ -220,33 +365,31 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} + ansi-regex@6.3.0: + resolution: {integrity: sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==} + engines: {node: '>=12'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - array-ify@1.0.0: - resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} - - array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} + argue-cli@3.2.0: + resolution: {integrity: sha512-VipTB0gXgGIFO2Rg9yEVN5wLt2AurJcZqDbgmYSwPwsykhLrQhs240/bfceev4w68lI8JshoOJIk9uW7tFzROw==} + engines: {node: '>=22'} async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} - better-path-resolve@1.0.0: - resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} - engines: {node: '>=4'} - - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} + cac@7.0.0: + resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} + engines: {node: '>=20.19.0'} callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} @@ -260,17 +403,9 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chalk@5.4.1: - resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} - ci-info@3.9.0: - resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} - engines: {node: '>=8'} - cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} @@ -282,9 +417,9 @@ packages: cliui@6.0.0: resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} - cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} + cliui@9.0.1: + resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} + engines: {node: '>=20'} color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} @@ -293,32 +428,29 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - compare-func@2.0.0: - resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} + conventional-changelog-angular@9.4.0: + resolution: {integrity: sha512-HdxRxuS8bBXVIuo4V82gvSwAXT0vYQUizrjs/izmPg5JdDstr8v8I5hduGL3iQbG+o310dUDxC4+LetuS5hu9w==} + engines: {node: '>=22'} - conventional-changelog-angular@7.0.0: - resolution: {integrity: sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==} - engines: {node: '>=16'} + conventional-changelog-conventionalcommits@10.4.0: + resolution: {integrity: sha512-Rriac6ZrAlVm6cy9Bz4NSp+WMHpwNXoPIYex+HjCgduAVUSbnew29DQjQw0C4g9u3HtSYzGiGY+pdBXAZo+4aA==} + engines: {node: '>=22'} - conventional-changelog-conventionalcommits@7.0.2: - resolution: {integrity: sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==} - engines: {node: '>=16'} - - conventional-commits-parser@5.0.0: - resolution: {integrity: sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==} - engines: {node: '>=16'} + conventional-commits-parser@7.1.2: + resolution: {integrity: sha512-O+x4N2yH+ijvqWlIyTHsXTAP+algNWgGbjY2duCe8w2vUMvUB95cLRslCPfTMQyLAKlet3bhZTdu6ozn4M+QJQ==} + engines: {node: '>=22'} hasBin: true - cosmiconfig-typescript-loader@6.1.0: - resolution: {integrity: sha512-tJ1w35ZRUiM5FeTzT7DtYWAFFv37ZLqSRkGi2oeCK1gPhvaWjkAtfXvLmvE1pRfxxp9aQo6ba/Pvg1dKj05D4g==} + cosmiconfig-typescript-loader@6.3.0: + resolution: {integrity: sha512-Akr82WH1Wfqatyiqpj8HDkO2o2KmJRu1FhKfSNJP3K4IdXwHfEyL7MOb62i1AGQVLtIQM+iCE9CGOtrfhR+mmA==} engines: {node: '>=v18'} peerDependencies: '@types/node': '*' cosmiconfig: '>=9' typescript: '>=5' - cosmiconfig@9.0.0: - resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} + cosmiconfig@9.0.2: + resolution: {integrity: sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==} engines: {node: '>=14'} peerDependencies: typescript: '>=4.9.5' @@ -326,46 +458,28 @@ packages: typescript: optional: true - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - - dargs@8.1.0: - resolution: {integrity: sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==} - engines: {node: '>=12'} - decamelize@1.2.0: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} - detect-indent@6.1.0: - resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} - engines: {node: '>=8'} - didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} - dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - - dot-prop@5.3.0: - resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} - engines: {node: '>=8'} + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - enquirer@2.4.1: - resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} - engines: {node: '>=8.6'} - env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} - error-ex@1.3.2: - resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + es-toolkit@1.52.0: + resolution: {integrity: sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==} escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} @@ -375,14 +489,6 @@ packages: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} - esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - - extendable-error@0.1.7: - resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} - external-editor@3.1.0: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} @@ -390,70 +496,53 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-glob@3.3.3: - resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} - engines: {node: '>=8.6.0'} + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} - fast-uri@3.0.6: - resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==} + fast-uri@3.1.8: + resolution: {integrity: sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==} - fastq@1.19.1: - resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + fast-wrap-ansi@0.2.2: + resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} - find-up@7.0.0: - resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==} - engines: {node: '>=18'} - - fs-extra@7.0.1: - resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} - engines: {node: '>=6 <7 || >=8'} - - fs-extra@8.1.0: - resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} - engines: {node: '>=6 <7 || >=8'} - get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - git-raw-commits@4.0.0: - resolution: {integrity: sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==} - engines: {node: '>=16'} - hasBin: true - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - - global-directory@4.0.1: - resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} + get-east-asian-width@1.7.0: + resolution: {integrity: sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==} engines: {node: '>=18'} - globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + global-directory@5.0.0: + resolution: {integrity: sha512-1pgFdhK3J2LeM+dVf2Pd424yHx2ou338lC0ErNP2hPx4j8eW1Sp0XqSjNxtk6Tc4Kr5wlWtSvz8cn2yb7/SG/w==} + engines: {node: '>=20'} has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - human-id@4.1.1: - resolution: {integrity: sha512-3gKm/gCSUipeLsRYZbbdA1BD83lBoWUkZ7G9VFrhWPAU76KwYo5KR8V28bpoPm/ygy0x5/GCbpRQdY7VLYCoIg==} + human-id@4.2.1: + resolution: {integrity: sha512-zPGsiS+dWoTZtZ4AtpA9Y+BdSFSNWvnouNlWNoUFyAM6xHOHmdCvqO3k8AIbdamCOv4gUFUVNPf6rJFfc4UiJw==} hasBin: true husky@9.1.7: @@ -465,20 +554,16 @@ packages: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} - ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} - import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} - import-meta-resolve@4.1.0: - resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==} + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} - ini@4.1.1: - resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + ini@6.0.0: + resolution: {integrity: sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==} + engines: {node: ^20.17.0 || >=22.9.0} inquirer@7.3.3: resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==} @@ -487,54 +572,26 @@ packages: is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - is-obj@2.0.0: - resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} - engines: {node: '>=8'} - - is-subdir@1.2.0: - resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} - engines: {node: '>=4'} - - is-text-path@2.0.0: - resolution: {integrity: sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==} - engines: {node: '>=8'} - - is-windows@1.0.2: - resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} - engines: {node: '>=0.10.0'} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} - jiti@2.4.2: - resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + jju@1.4.0: + resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} - hasBin: true - - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + js-yaml@4.3.2: + resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==} hasBin: true json-fixer@1.6.15: @@ -547,12 +604,11 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - jsonfile@4.0.0: - resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} - jsonparse@1.3.1: - resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} - engines: {'0': node >= 0.2.0} + launch-editor@2.14.1: + resolution: {integrity: sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==} lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -561,63 +617,13 @@ packages: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} - locate-path@7.2.0: - resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - lodash.camelcase@4.3.0: - resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} - - lodash.isplainobject@4.0.6: - resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} - - lodash.kebabcase@4.1.1: - resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} - - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - - lodash.mergewith@4.6.2: - resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} - - lodash.snakecase@4.1.1: - resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} - - lodash.startcase@4.4.0: - resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} - - lodash.uniq@4.5.0: - resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} - - lodash.upperfirst@4.3.1: - resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==} - - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - - meow@12.1.1: - resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==} - engines: {node: '>=16.10'} - - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - mri@1.2.0: - resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} - engines: {node: '>=4'} - mute-stream@0.0.8: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} @@ -634,43 +640,20 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} - os-tmpdir@1.0.2: - resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} - engines: {node: '>=0.10.0'} - - outdent@0.5.0: - resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} - - p-filter@2.1.0: - resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} - engines: {node: '>=8'} - p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} - p-limit@4.0.0: - resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} - p-locate@6.0.0: - resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - p-map@2.1.0: - resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} - engines: {node: '>=6'} - p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} - package-manager-detector@0.2.11: - resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} + package-manager-detector@1.8.0: + resolution: {integrity: sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==} parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} @@ -684,18 +667,6 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} - path-exists@5.0.0: - resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - pegjs@0.10.0: resolution: {integrity: sha512-qI5+oFNEGi3L5HAxDwN2LA4Gg7irF70Zs25edhjld9QemOgp0CbvMtbFcMvFtEo1OityPrcCzkQFB8JP/hxgow==} engines: {node: '>=0.10'} @@ -704,13 +675,9 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - - pify@4.0.1: - resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} - engines: {node: '>=6'} + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} pify@5.0.0: resolution: {integrity: sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==} @@ -721,21 +688,11 @@ packages: engines: {node: '>=10.13.0'} hasBin: true - prettier@3.6.2: - resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + prettier@3.9.8: + resolution: {integrity: sha512-WRFq3Wn3WId7LLROfMLdH7xaFr2jR62wU8nLO6rQUOLOxNZUviyJQs1M0iIhLexSFy+L+w0ch66wtoO2jRjG0A==} engines: {node: '>=14'} hasBin: true - quansync@0.2.10: - resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==} - - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - - read-yaml-file@1.1.0: - resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} - engines: {node: '>=6'} - require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -759,17 +716,10 @@ packages: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} - reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - run-async@2.4.1: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} engines: {node: '>=0.12.0'} - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - rxjs@6.6.7: resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} engines: {npm: '>=2.0.0'} @@ -777,80 +727,62 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - semver@7.7.2: - resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} engines: {node: '>=10'} hasBin: true set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} + shell-quote@1.10.0: + resolution: {integrity: sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==} + engines: {node: '>= 0.4'} signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - - spawndamnit@3.0.1: - resolution: {integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==} - - split2@4.2.0: - resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} - engines: {node: '>= 10.x'} - - sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string-width@8.2.2: + resolution: {integrity: sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==} + engines: {node: '>=20'} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} - term-size@2.2.1: - resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} - engines: {node: '>=8'} - - text-extensions@2.4.0: - resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==} - engines: {node: '>=8'} - through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - tinyexec@1.0.1: - resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} + tinyexec@1.3.1: + resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} + engines: {node: '>=18'} - tmp@0.0.33: - resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} - engines: {node: '>=0.6.0'} + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} + tmp@0.2.7: + resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==} + engines: {node: '>=14.14'} tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -862,21 +794,13 @@ packages: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} - typescript@5.8.3: - resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} - engines: {node: '>=14.17'} + typescript@7.0.2: + resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} + engines: {node: '>=16.20.0'} hasBin: true - undici-types@7.8.0: - resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} - - unicorn-magic@0.1.0: - resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} - engines: {node: '>=18'} - - universalify@0.1.2: - resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} - engines: {node: '>= 4.0.0'} + undici-types@8.9.0: + resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==} webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -887,18 +811,13 @@ packages: which-module@2.0.1: resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} y18n@4.0.3: resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} @@ -907,349 +826,377 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} + yaml@2.9.1: + resolution: {integrity: sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==} + engines: {node: '>= 14.6'} + hasBin: true + yargs-parser@18.1.3: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} engines: {node: '>=6'} - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} + yargs-parser@22.0.0: + resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} yargs@15.4.1: resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} engines: {node: '>=8'} - yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} - - yocto-queue@1.2.1: - resolution: {integrity: sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==} - engines: {node: '>=12.20'} + yargs@18.1.0: + resolution: {integrity: sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} snapshots: - '@babel/code-frame@7.27.1': + '@babel/code-frame@7.29.7': dependencies: - '@babel/helper-validator-identifier': 7.27.1 + '@babel/helper-validator-identifier': 7.29.7 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/helper-validator-identifier@7.27.1': {} + '@babel/helper-validator-identifier@7.29.7': {} - '@babel/runtime@7.27.6': {} + '@babel/runtime@7.29.7': {} - '@changesets/apply-release-plan@7.0.12': + '@changesets/apply-release-plan@8.1.1': dependencies: - '@changesets/config': 3.1.1 - '@changesets/get-version-range-type': 0.4.0 - '@changesets/git': 3.0.4 - '@changesets/should-skip-package': 0.1.2 - '@changesets/types': 6.1.0 - '@manypkg/get-packages': 1.1.3 - detect-indent: 6.1.0 - fs-extra: 7.0.1 - lodash.startcase: 4.4.0 - outdent: 0.5.0 - prettier: 2.8.8 - resolve-from: 5.0.0 - semver: 7.7.2 - - '@changesets/assemble-release-plan@6.0.9': - dependencies: - '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.3 - '@changesets/should-skip-package': 0.1.2 - '@changesets/types': 6.1.0 - '@manypkg/get-packages': 1.1.3 - semver: 7.7.2 - - '@changesets/changelog-git@0.2.1': - dependencies: - '@changesets/types': 6.1.0 - - '@changesets/cli@2.29.5': - dependencies: - '@changesets/apply-release-plan': 7.0.12 - '@changesets/assemble-release-plan': 6.0.9 - '@changesets/changelog-git': 0.2.1 - '@changesets/config': 3.1.1 - '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.3 - '@changesets/get-release-plan': 4.0.13 - '@changesets/git': 3.0.4 - '@changesets/logger': 0.1.1 - '@changesets/pre': 2.0.2 - '@changesets/read': 0.6.5 - '@changesets/should-skip-package': 0.1.2 - '@changesets/types': 6.1.0 - '@changesets/write': 0.4.0 - '@manypkg/get-packages': 1.1.3 - ansi-colors: 4.1.3 - ci-info: 3.9.0 - enquirer: 2.4.1 - external-editor: 3.1.0 - fs-extra: 7.0.1 - mri: 1.2.0 - p-limit: 2.3.0 - package-manager-detector: 0.2.11 - picocolors: 1.1.1 - resolve-from: 5.0.0 - semver: 7.7.2 - spawndamnit: 3.0.1 - term-size: 2.2.1 + '@changesets/config': 4.0.1 + '@changesets/format': 0.1.2 + '@changesets/git': 4.0.1 + '@changesets/should-skip-package': 1.0.0 + '@changesets/types': 7.0.0 + import-meta-resolve: 4.2.0 + jsonc-parser: 3.3.1 + semver: 7.8.5 - '@changesets/config@3.1.1': + '@changesets/assemble-release-plan@7.0.0': dependencies: - '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.3 - '@changesets/logger': 0.1.1 - '@changesets/types': 6.1.0 - '@manypkg/get-packages': 1.1.3 - fs-extra: 7.0.1 - micromatch: 4.0.8 + '@changesets/errors': 1.0.0 + '@changesets/get-dependents-graph': 3.0.0 + '@changesets/should-skip-package': 1.0.0 + '@changesets/types': 7.0.0 + semver: 7.8.5 - '@changesets/errors@0.2.0': + '@changesets/changelog-git@1.0.0': dependencies: - extendable-error: 0.1.7 + '@changesets/types': 7.0.0 - '@changesets/get-dependents-graph@2.1.3': + '@changesets/cli@3.0.3': dependencies: - '@changesets/types': 6.1.0 - '@manypkg/get-packages': 1.1.3 - picocolors: 1.1.1 - semver: 7.7.2 + '@changesets/apply-release-plan': 8.1.1 + '@changesets/assemble-release-plan': 7.0.0 + '@changesets/changelog-git': 1.0.0 + '@changesets/config': 4.0.1 + '@changesets/errors': 1.0.0 + '@changesets/get-dependents-graph': 3.0.0 + '@changesets/git': 4.0.1 + '@changesets/pre': 3.0.0 + '@changesets/read': 1.0.1 + '@changesets/should-skip-package': 1.0.0 + '@changesets/types': 7.0.0 + '@changesets/write': 1.0.1 + '@clack/prompts': 1.8.1 + '@manypkg/get-packages': 3.1.0 + '@pnpm/deps.graph-sequencer': 1100.0.1 + cac: 7.0.0 + import-meta-resolve: 4.2.0 + launch-editor: 2.14.1 + package-manager-detector: 1.8.0 + semver: 7.8.5 + tinyexec: 1.3.1 - '@changesets/get-release-plan@4.0.13': + '@changesets/config@4.0.1': dependencies: - '@changesets/assemble-release-plan': 6.0.9 - '@changesets/config': 3.1.1 - '@changesets/pre': 2.0.2 - '@changesets/read': 0.6.5 - '@changesets/types': 6.1.0 - '@manypkg/get-packages': 1.1.3 + '@changesets/get-dependents-graph': 3.0.0 + '@changesets/should-skip-package': 1.0.0 + '@changesets/types': 7.0.0 + '@manypkg/get-packages': 3.1.0 + picomatch: 4.0.7 - '@changesets/get-version-range-type@0.4.0': {} + '@changesets/errors@1.0.0': {} - '@changesets/git@3.0.4': + '@changesets/format@0.1.2': dependencies: - '@changesets/errors': 0.2.0 - '@manypkg/get-packages': 1.1.3 - is-subdir: 1.2.0 - micromatch: 4.0.8 - spawndamnit: 3.0.1 + package-manager-detector: 1.8.0 + tinyexec: 1.3.1 - '@changesets/logger@0.1.1': + '@changesets/get-dependents-graph@3.0.0': dependencies: - picocolors: 1.1.1 + '@changesets/types': 7.0.0 + semver: 7.8.5 - '@changesets/parse@0.4.1': + '@changesets/git@4.0.1': dependencies: - '@changesets/types': 6.1.0 - js-yaml: 3.14.1 + '@changesets/errors': 1.0.0 + '@changesets/types': 7.0.0 + '@manypkg/get-packages': 3.1.0 + picomatch: 4.0.7 + tinyexec: 1.3.1 - '@changesets/pre@2.0.2': + '@changesets/parse@1.0.0': dependencies: - '@changesets/errors': 0.2.0 - '@changesets/types': 6.1.0 - '@manypkg/get-packages': 1.1.3 - fs-extra: 7.0.1 + '@changesets/types': 7.0.0 + yaml: 2.9.1 - '@changesets/read@0.6.5': + '@changesets/pre@3.0.0': dependencies: - '@changesets/git': 3.0.4 - '@changesets/logger': 0.1.1 - '@changesets/parse': 0.4.1 - '@changesets/types': 6.1.0 - fs-extra: 7.0.1 - p-filter: 2.1.0 - picocolors: 1.1.1 + '@changesets/errors': 1.0.0 + '@changesets/types': 7.0.0 + '@manypkg/get-packages': 3.1.0 - '@changesets/should-skip-package@0.1.2': + '@changesets/read@1.0.1': dependencies: - '@changesets/types': 6.1.0 - '@manypkg/get-packages': 1.1.3 + '@changesets/git': 4.0.1 + '@changesets/parse': 1.0.0 + '@changesets/types': 7.0.0 - '@changesets/types@4.1.0': {} + '@changesets/should-skip-package@1.0.0': + dependencies: + '@changesets/types': 7.0.0 - '@changesets/types@6.1.0': {} + '@changesets/types@7.0.0': {} - '@changesets/write@0.4.0': + '@changesets/write@1.0.1': dependencies: - '@changesets/types': 6.1.0 - fs-extra: 7.0.1 - human-id: 4.1.1 - prettier: 2.8.8 + '@changesets/format': 0.1.2 + '@changesets/types': 7.0.0 + human-id: 4.2.1 - '@commitlint/cli@19.8.1(@types/node@24.1.0)(typescript@5.8.3)': + '@clack/core@1.5.1': dependencies: - '@commitlint/format': 19.8.1 - '@commitlint/lint': 19.8.1 - '@commitlint/load': 19.8.1(@types/node@24.1.0)(typescript@5.8.3) - '@commitlint/read': 19.8.1 - '@commitlint/types': 19.8.1 - tinyexec: 1.0.1 - yargs: 17.7.2 + fast-wrap-ansi: 0.2.2 + sisteransi: 1.0.5 + + '@clack/prompts@1.8.1': + dependencies: + '@clack/core': 1.5.1 + fast-string-width: 3.0.2 + fast-wrap-ansi: 0.2.2 + sisteransi: 1.0.5 + + '@commitlint/cli@21.2.3(@types/node@26.6.2)(conventional-commits-parser@7.1.2)(typescript@7.0.2)': + dependencies: + '@commitlint/config-conventional': 21.2.3 + '@commitlint/format': 21.2.3 + '@commitlint/lint': 21.2.3 + '@commitlint/load': 21.2.3(@types/node@26.6.2)(typescript@7.0.2) + '@commitlint/read': 21.2.3(conventional-commits-parser@7.1.2) + '@commitlint/types': 21.2.3 + tinyexec: 1.3.1 + yargs: 18.1.0 transitivePeerDependencies: - '@types/node' + - conventional-commits-filter + - conventional-commits-parser - typescript - '@commitlint/config-conventional@19.8.1': + '@commitlint/config-conventional@21.2.3': dependencies: - '@commitlint/types': 19.8.1 - conventional-changelog-conventionalcommits: 7.0.2 + '@commitlint/types': 21.2.3 + conventional-changelog-conventionalcommits: 10.4.0 - '@commitlint/config-validator@19.8.1': + '@commitlint/config-validator@21.2.3': dependencies: - '@commitlint/types': 19.8.1 - ajv: 8.17.1 + '@commitlint/types': 21.2.3 + ajv: 8.20.0 - '@commitlint/ensure@19.8.1': + '@commitlint/ensure@21.2.3': dependencies: - '@commitlint/types': 19.8.1 - lodash.camelcase: 4.3.0 - lodash.kebabcase: 4.1.1 - lodash.snakecase: 4.1.1 - lodash.startcase: 4.4.0 - lodash.upperfirst: 4.3.1 + '@commitlint/types': 21.2.3 + es-toolkit: 1.52.0 - '@commitlint/execute-rule@19.8.1': {} + '@commitlint/execute-rule@21.0.1': {} - '@commitlint/format@19.8.1': + '@commitlint/format@21.2.3': dependencies: - '@commitlint/types': 19.8.1 - chalk: 5.4.1 + '@commitlint/types': 21.2.3 + picocolors: 1.1.1 - '@commitlint/is-ignored@19.8.1': + '@commitlint/is-ignored@21.2.3': dependencies: - '@commitlint/types': 19.8.1 - semver: 7.7.2 + '@commitlint/types': 21.2.3 + semver: 7.8.5 - '@commitlint/lint@19.8.1': + '@commitlint/lint@21.2.3': dependencies: - '@commitlint/is-ignored': 19.8.1 - '@commitlint/parse': 19.8.1 - '@commitlint/rules': 19.8.1 - '@commitlint/types': 19.8.1 + '@commitlint/is-ignored': 21.2.3 + '@commitlint/parse': 21.2.3 + '@commitlint/rules': 21.2.3 + '@commitlint/types': 21.2.3 - '@commitlint/load@19.8.1(@types/node@24.1.0)(typescript@5.8.3)': + '@commitlint/load@21.2.3(@types/node@26.6.2)(typescript@7.0.2)': dependencies: - '@commitlint/config-validator': 19.8.1 - '@commitlint/execute-rule': 19.8.1 - '@commitlint/resolve-extends': 19.8.1 - '@commitlint/types': 19.8.1 - chalk: 5.4.1 - cosmiconfig: 9.0.0(typescript@5.8.3) - cosmiconfig-typescript-loader: 6.1.0(@types/node@24.1.0)(cosmiconfig@9.0.0(typescript@5.8.3))(typescript@5.8.3) - lodash.isplainobject: 4.0.6 - lodash.merge: 4.6.2 - lodash.uniq: 4.5.0 + '@commitlint/config-validator': 21.2.3 + '@commitlint/execute-rule': 21.0.1 + '@commitlint/resolve-extends': 21.2.3 + '@commitlint/types': 21.2.3 + cosmiconfig: 9.0.2(typescript@7.0.2) + cosmiconfig-typescript-loader: 6.3.0(@types/node@26.6.2)(cosmiconfig@9.0.2(typescript@7.0.2))(typescript@7.0.2) + es-toolkit: 1.52.0 + is-plain-obj: 4.1.0 + picocolors: 1.1.1 transitivePeerDependencies: - '@types/node' - typescript - '@commitlint/message@19.8.1': {} + '@commitlint/message@21.2.0': {} - '@commitlint/parse@19.8.1': + '@commitlint/parse@21.2.3': dependencies: - '@commitlint/types': 19.8.1 - conventional-changelog-angular: 7.0.0 - conventional-commits-parser: 5.0.0 + '@commitlint/types': 21.2.3 + conventional-changelog-angular: 9.4.0 + conventional-commits-parser: 7.1.2 - '@commitlint/read@19.8.1': + '@commitlint/read@21.2.3(conventional-commits-parser@7.1.2)': dependencies: - '@commitlint/top-level': 19.8.1 - '@commitlint/types': 19.8.1 - git-raw-commits: 4.0.0 - minimist: 1.2.8 - tinyexec: 1.0.1 + '@commitlint/top-level': 21.2.0 + '@commitlint/types': 21.2.3 + '@conventional-changelog/git-client': 3.1.2(conventional-commits-parser@7.1.2) + tinyexec: 1.3.1 + transitivePeerDependencies: + - conventional-commits-filter + - conventional-commits-parser - '@commitlint/resolve-extends@19.8.1': + '@commitlint/resolve-extends@21.2.3': dependencies: - '@commitlint/config-validator': 19.8.1 - '@commitlint/types': 19.8.1 - global-directory: 4.0.1 - import-meta-resolve: 4.1.0 - lodash.mergewith: 4.6.2 + '@commitlint/config-validator': 21.2.3 + '@commitlint/types': 21.2.3 + es-toolkit: 1.52.0 + global-directory: 5.0.0 resolve-from: 5.0.0 - '@commitlint/rules@19.8.1': + '@commitlint/rules@21.2.3': dependencies: - '@commitlint/ensure': 19.8.1 - '@commitlint/message': 19.8.1 - '@commitlint/to-lines': 19.8.1 - '@commitlint/types': 19.8.1 + '@commitlint/ensure': 21.2.3 + '@commitlint/message': 21.2.0 + '@commitlint/to-lines': 21.0.1 + '@commitlint/types': 21.2.3 - '@commitlint/to-lines@19.8.1': {} + '@commitlint/to-lines@21.0.1': {} - '@commitlint/top-level@19.8.1': + '@commitlint/top-level@21.2.0': dependencies: - find-up: 7.0.0 + escalade: 3.2.0 - '@commitlint/types@19.8.1': + '@commitlint/types@21.2.3': dependencies: - '@types/conventional-commits-parser': 5.0.1 - chalk: 5.4.1 + conventional-commits-parser: 7.1.2 + picocolors: 1.1.1 - '@manypkg/find-root@1.1.0': + '@conventional-changelog/git-client@3.1.2(conventional-commits-parser@7.1.2)': dependencies: - '@babel/runtime': 7.27.6 - '@types/node': 12.20.55 - find-up: 4.1.0 - fs-extra: 8.1.0 + '@simple-libs/child-process-utils': 2.0.0 + '@simple-libs/stream-utils': 2.0.0 + semver: 7.8.5 + optionalDependencies: + conventional-commits-parser: 7.1.2 - '@manypkg/get-packages@1.1.3': - dependencies: - '@babel/runtime': 7.27.6 - '@changesets/types': 4.1.0 - '@manypkg/find-root': 1.1.0 - fs-extra: 8.1.0 - globby: 11.1.0 - read-yaml-file: 1.1.0 + '@conventional-changelog/template@1.4.0': {} - '@nodelib/fs.scandir@2.1.5': + '@manypkg/find-root@3.1.0': dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - - '@nodelib/fs.stat@2.0.5': {} + '@manypkg/tools': 2.1.2 - '@nodelib/fs.walk@1.2.8': + '@manypkg/get-packages@3.1.0': dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.19.1 + '@manypkg/find-root': 3.1.0 + '@manypkg/tools': 2.1.2 - '@types/conventional-commits-parser@5.0.1': + '@manypkg/tools@2.1.2': dependencies: - '@types/node': 24.1.0 + jju: 1.4.0 + tinyglobby: 0.2.17 + yaml: 2.9.1 - '@types/node@12.20.55': {} + '@pnpm/deps.graph-sequencer@1100.0.1': {} - '@types/node@24.1.0': + '@simple-libs/child-process-utils@2.0.0': dependencies: - undici-types: 7.8.0 + '@simple-libs/stream-utils': 2.0.0 - JSONStream@1.3.5: + '@simple-libs/stream-utils@2.0.0': {} + + '@types/node@26.6.2': dependencies: - jsonparse: 1.3.1 - through: 2.3.8 + undici-types: 8.9.0 + + '@typescript/typescript-aix-ppc64@7.0.2': + optional: true + + '@typescript/typescript-darwin-arm64@7.0.2': + optional: true + + '@typescript/typescript-darwin-x64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-x64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm@7.0.2': + optional: true + + '@typescript/typescript-linux-loong64@7.0.2': + optional: true + + '@typescript/typescript-linux-mips64el@7.0.2': + optional: true + + '@typescript/typescript-linux-ppc64@7.0.2': + optional: true + + '@typescript/typescript-linux-riscv64@7.0.2': + optional: true + + '@typescript/typescript-linux-s390x@7.0.2': + optional: true + + '@typescript/typescript-linux-x64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-sunos-x64@7.0.2': + optional: true + + '@typescript/typescript-win32-arm64@7.0.2': + optional: true - ajv@8.17.1: + '@typescript/typescript-win32-x64@7.0.2': + optional: true + + ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.0.6 + fast-uri: 3.1.8 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 all-contributors-cli@6.26.1: dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.29.7 async: 3.2.6 chalk: 4.1.2 didyoumean: 1.2.2 inquirer: 7.3.3 json-fixer: 1.6.15 - lodash: 4.17.21 + lodash: 4.18.1 node-fetch: 2.7.0 pify: 5.0.0 yargs: 15.4.1 @@ -1258,37 +1205,27 @@ snapshots: transitivePeerDependencies: - encoding - ansi-colors@4.1.3: {} - ansi-escapes@4.3.2: dependencies: type-fest: 0.21.3 ansi-regex@5.0.1: {} + ansi-regex@6.3.0: {} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 - argparse@1.0.10: - dependencies: - sprintf-js: 1.0.3 + ansi-styles@6.2.3: {} argparse@2.0.1: {} - array-ify@1.0.0: {} - - array-union@2.1.0: {} + argue-cli@3.2.0: {} async@3.2.6: {} - better-path-resolve@1.0.0: - dependencies: - is-windows: 1.0.2 - - braces@3.0.3: - dependencies: - fill-range: 7.1.1 + cac@7.0.0: {} callsites@3.1.0: {} @@ -1299,12 +1236,8 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - chalk@5.4.1: {} - chardet@0.7.0: {} - ci-info@3.9.0: {} - cli-cursor@3.1.0: dependencies: restore-cursor: 3.1.0 @@ -1317,11 +1250,11 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 6.2.0 - cliui@8.0.1: + cliui@9.0.1: dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 + string-width: 7.2.0 + strip-ansi: 7.2.0 + wrap-ansi: 9.0.2 color-convert@2.0.1: dependencies: @@ -1329,168 +1262,99 @@ snapshots: color-name@1.1.4: {} - compare-func@2.0.0: - dependencies: - array-ify: 1.0.0 - dot-prop: 5.3.0 - - conventional-changelog-angular@7.0.0: + conventional-changelog-angular@9.4.0: dependencies: - compare-func: 2.0.0 + '@conventional-changelog/template': 1.4.0 - conventional-changelog-conventionalcommits@7.0.2: + conventional-changelog-conventionalcommits@10.4.0: dependencies: - compare-func: 2.0.0 + '@conventional-changelog/template': 1.4.0 - conventional-commits-parser@5.0.0: + conventional-commits-parser@7.1.2: dependencies: - JSONStream: 1.3.5 - is-text-path: 2.0.0 - meow: 12.1.1 - split2: 4.2.0 + '@simple-libs/stream-utils': 2.0.0 + argue-cli: 3.2.0 - cosmiconfig-typescript-loader@6.1.0(@types/node@24.1.0)(cosmiconfig@9.0.0(typescript@5.8.3))(typescript@5.8.3): + cosmiconfig-typescript-loader@6.3.0(@types/node@26.6.2)(cosmiconfig@9.0.2(typescript@7.0.2))(typescript@7.0.2): dependencies: - '@types/node': 24.1.0 - cosmiconfig: 9.0.0(typescript@5.8.3) - jiti: 2.4.2 - typescript: 5.8.3 + '@types/node': 26.6.2 + cosmiconfig: 9.0.2(typescript@7.0.2) + jiti: 2.6.1 + typescript: 7.0.2 - cosmiconfig@9.0.0(typescript@5.8.3): + cosmiconfig@9.0.2(typescript@7.0.2): dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 - js-yaml: 4.1.0 + js-yaml: 4.3.2 parse-json: 5.2.0 optionalDependencies: - typescript: 5.8.3 - - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - dargs@8.1.0: {} + typescript: 7.0.2 decamelize@1.2.0: {} - detect-indent@6.1.0: {} - didyoumean@1.2.2: {} - dir-glob@3.0.1: - dependencies: - path-type: 4.0.0 - - dot-prop@5.3.0: - dependencies: - is-obj: 2.0.0 + emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} - enquirer@2.4.1: - dependencies: - ansi-colors: 4.1.3 - strip-ansi: 6.0.1 - env-paths@2.2.1: {} - error-ex@1.3.2: + error-ex@1.3.4: dependencies: is-arrayish: 0.2.1 + es-toolkit@1.52.0: {} + escalade@3.2.0: {} escape-string-regexp@1.0.5: {} - esprima@4.0.1: {} - - extendable-error@0.1.7: {} - external-editor@3.1.0: dependencies: chardet: 0.7.0 iconv-lite: 0.4.24 - tmp: 0.0.33 + tmp: 0.2.7 fast-deep-equal@3.1.3: {} - fast-glob@3.3.3: + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 + fast-string-truncated-width: 3.0.3 - fast-uri@3.0.6: {} + fast-uri@3.1.8: {} - fastq@1.19.1: + fast-wrap-ansi@0.2.2: dependencies: - reusify: 1.1.0 + fast-string-width: 3.0.2 + + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 figures@3.2.0: dependencies: escape-string-regexp: 1.0.5 - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - find-up@4.1.0: dependencies: locate-path: 5.0.0 path-exists: 4.0.0 - find-up@7.0.0: - dependencies: - locate-path: 7.2.0 - path-exists: 5.0.0 - unicorn-magic: 0.1.0 - - fs-extra@7.0.1: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 4.0.0 - universalify: 0.1.2 - - fs-extra@8.1.0: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 4.0.0 - universalify: 0.1.2 - get-caller-file@2.0.5: {} - git-raw-commits@4.0.0: - dependencies: - dargs: 8.1.0 - meow: 12.1.1 - split2: 4.2.0 - - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - - global-directory@4.0.1: - dependencies: - ini: 4.1.1 + get-east-asian-width@1.7.0: {} - globby@11.1.0: + global-directory@5.0.0: dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.3.3 - ignore: 5.3.2 - merge2: 1.4.1 - slash: 3.0.0 - - graceful-fs@4.2.11: {} + ini: 6.0.0 has-flag@4.0.0: {} - human-id@4.1.1: {} + human-id@4.2.1: {} husky@9.1.7: {} @@ -1498,16 +1362,14 @@ snapshots: dependencies: safer-buffer: 2.1.2 - ignore@5.3.2: {} - import-fresh@3.3.1: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 - import-meta-resolve@4.1.0: {} + import-meta-resolve@4.2.0: {} - ini@4.1.1: {} + ini@6.0.0: {} inquirer@7.3.3: dependencies: @@ -1517,7 +1379,7 @@ snapshots: cli-width: 3.0.0 external-editor: 3.1.0 figures: 3.2.0 - lodash: 4.17.21 + lodash: 4.18.1 mute-stream: 0.0.8 run-async: 2.4.1 rxjs: 6.6.7 @@ -1527,46 +1389,23 @@ snapshots: is-arrayish@0.2.1: {} - is-extglob@2.1.1: {} - is-fullwidth-code-point@3.0.0: {} - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - - is-number@7.0.0: {} - - is-obj@2.0.0: {} - - is-subdir@1.2.0: - dependencies: - better-path-resolve: 1.0.0 - - is-text-path@2.0.0: - dependencies: - text-extensions: 2.4.0 + is-plain-obj@4.1.0: {} - is-windows@1.0.2: {} + jiti@2.6.1: {} - isexe@2.0.0: {} - - jiti@2.4.2: {} + jju@1.4.0: {} js-tokens@4.0.0: {} - js-yaml@3.14.1: - dependencies: - argparse: 1.0.10 - esprima: 4.0.1 - - js-yaml@4.1.0: + js-yaml@4.3.2: dependencies: argparse: 2.0.1 json-fixer@1.6.15: dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.29.7 chalk: 4.1.2 pegjs: 0.10.0 @@ -1574,11 +1413,12 @@ snapshots: json-schema-traverse@1.0.0: {} - jsonfile@4.0.0: - optionalDependencies: - graceful-fs: 4.2.11 + jsonc-parser@3.3.1: {} - jsonparse@1.3.1: {} + launch-editor@2.14.1: + dependencies: + picocolors: 1.1.1 + shell-quote: 1.10.0 lines-and-columns@1.2.4: {} @@ -1586,45 +1426,10 @@ snapshots: dependencies: p-locate: 4.1.0 - locate-path@7.2.0: - dependencies: - p-locate: 6.0.0 - - lodash.camelcase@4.3.0: {} - - lodash.isplainobject@4.0.6: {} - - lodash.kebabcase@4.1.1: {} - - lodash.merge@4.6.2: {} - - lodash.mergewith@4.6.2: {} - - lodash.snakecase@4.1.1: {} - - lodash.startcase@4.4.0: {} - - lodash.uniq@4.5.0: {} - - lodash.upperfirst@4.3.1: {} - - lodash@4.17.21: {} - - meow@12.1.1: {} - - merge2@1.4.1: {} - - micromatch@4.0.8: - dependencies: - braces: 3.0.3 - picomatch: 2.3.1 + lodash@4.18.1: {} mimic-fn@2.1.0: {} - minimist@1.2.8: {} - - mri@1.2.0: {} - mute-stream@0.0.8: {} node-fetch@2.7.0: @@ -1635,37 +1440,17 @@ snapshots: dependencies: mimic-fn: 2.1.0 - os-tmpdir@1.0.2: {} - - outdent@0.5.0: {} - - p-filter@2.1.0: - dependencies: - p-map: 2.1.0 - p-limit@2.3.0: dependencies: p-try: 2.2.0 - p-limit@4.0.0: - dependencies: - yocto-queue: 1.2.1 - p-locate@4.1.0: dependencies: p-limit: 2.3.0 - p-locate@6.0.0: - dependencies: - p-limit: 4.0.0 - - p-map@2.1.0: {} - p-try@2.2.0: {} - package-manager-detector@0.2.11: - dependencies: - quansync: 0.2.10 + package-manager-detector@1.8.0: {} parent-module@1.0.1: dependencies: @@ -1673,43 +1458,25 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.27.1 - error-ex: 1.3.2 + '@babel/code-frame': 7.29.7 + error-ex: 1.3.4 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 path-exists@4.0.0: {} - path-exists@5.0.0: {} - - path-key@3.1.1: {} - - path-type@4.0.0: {} - pegjs@0.10.0: {} picocolors@1.1.1: {} - picomatch@2.3.1: {} - - pify@4.0.1: {} + picomatch@4.0.7: {} pify@5.0.0: {} - prettier@2.8.8: {} - - prettier@3.6.2: {} - - quansync@0.2.10: {} - - queue-microtask@1.2.3: {} + prettier@2.8.8: + optional: true - read-yaml-file@1.1.0: - dependencies: - graceful-fs: 4.2.11 - js-yaml: 3.14.1 - pify: 4.0.1 - strip-bom: 3.0.0 + prettier@3.9.8: {} require-directory@2.1.1: {} @@ -1726,44 +1493,23 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 - reusify@1.1.0: {} - run-async@2.4.1: {} - run-parallel@1.2.0: - dependencies: - queue-microtask: 1.2.3 - rxjs@6.6.7: dependencies: tslib: 1.14.1 safer-buffer@2.1.2: {} - semver@7.7.2: {} + semver@7.8.5: {} set-blocking@2.0.0: {} - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} + shell-quote@1.10.0: {} signal-exit@3.0.7: {} - signal-exit@4.1.0: {} - - slash@3.0.0: {} - - spawndamnit@3.0.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - - split2@4.2.0: {} - - sprintf-js@1.0.3: {} + sisteransi@1.0.5: {} string-width@4.2.3: dependencies: @@ -1771,31 +1517,39 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.7.0 + strip-ansi: 7.2.0 + + string-width@8.2.2: + dependencies: + get-east-asian-width: 1.7.0 + strip-ansi: 7.2.0 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 - strip-bom@3.0.0: {} + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.3.0 supports-color@7.2.0: dependencies: has-flag: 4.0.0 - term-size@2.2.1: {} - - text-extensions@2.4.0: {} - through@2.3.8: {} - tinyexec@1.0.1: {} + tinyexec@1.3.1: {} - tmp@0.0.33: + tinyglobby@0.2.17: dependencies: - os-tmpdir: 1.0.2 + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 - to-regex-range@5.0.1: - dependencies: - is-number: 7.0.0 + tmp@0.2.7: {} tr46@0.0.3: {} @@ -1803,13 +1557,30 @@ snapshots: type-fest@0.21.3: {} - typescript@5.8.3: {} - - undici-types@7.8.0: {} - - unicorn-magic@0.1.0: {} - - universalify@0.1.2: {} + typescript@7.0.2: + optionalDependencies: + '@typescript/typescript-aix-ppc64': 7.0.2 + '@typescript/typescript-darwin-arm64': 7.0.2 + '@typescript/typescript-darwin-x64': 7.0.2 + '@typescript/typescript-freebsd-arm64': 7.0.2 + '@typescript/typescript-freebsd-x64': 7.0.2 + '@typescript/typescript-linux-arm': 7.0.2 + '@typescript/typescript-linux-arm64': 7.0.2 + '@typescript/typescript-linux-loong64': 7.0.2 + '@typescript/typescript-linux-mips64el': 7.0.2 + '@typescript/typescript-linux-ppc64': 7.0.2 + '@typescript/typescript-linux-riscv64': 7.0.2 + '@typescript/typescript-linux-s390x': 7.0.2 + '@typescript/typescript-linux-x64': 7.0.2 + '@typescript/typescript-netbsd-arm64': 7.0.2 + '@typescript/typescript-netbsd-x64': 7.0.2 + '@typescript/typescript-openbsd-arm64': 7.0.2 + '@typescript/typescript-openbsd-x64': 7.0.2 + '@typescript/typescript-sunos-x64': 7.0.2 + '@typescript/typescript-win32-arm64': 7.0.2 + '@typescript/typescript-win32-x64': 7.0.2 + + undici-types@8.9.0: {} webidl-conversions@3.0.1: {} @@ -1820,32 +1591,30 @@ snapshots: which-module@2.0.1: {} - which@2.0.2: - dependencies: - isexe: 2.0.0 - wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - wrap-ansi@7.0.0: + wrap-ansi@9.0.2: dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.2.0 y18n@4.0.3: {} y18n@5.0.8: {} + yaml@2.9.1: {} + yargs-parser@18.1.3: dependencies: camelcase: 5.3.1 decamelize: 1.2.0 - yargs-parser@21.1.1: {} + yargs-parser@22.0.0: {} yargs@15.4.1: dependencies: @@ -1861,14 +1630,11 @@ snapshots: y18n: 4.0.3 yargs-parser: 18.1.3 - yargs@17.7.2: + yargs@18.1.0: dependencies: - cliui: 8.0.1 + cliui: 9.0.1 escalade: 3.2.0 get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 + string-width: 8.2.2 y18n: 5.0.8 - yargs-parser: 21.1.1 - - yocto-queue@1.2.1: {} + yargs-parser: 22.0.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..e9009c0 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +# all-contributors-cli still permits an obsolete tmp release through inquirer. +# The supported tmpNameSync API is unchanged; use the patched release. +overrides: + tmp: ^0.2.7 diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..c60021e --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,1583 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "1b0e6a07425a3e460666e88bf1c949ccc7bb0116ad562ce94a1eca60fe820725" + url: "https://pub.dev" + source: hosted + version: "103.0.0" + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: f6966125633a34f82d9be2ee895b755cff3554087b1d00a9eb884e5947f4bca9 + url: "https://pub.dev" + source: hosted + version: "1.3.77" + after_layout: + dependency: transitive + description: + name: after_layout + sha256: "95a1cb2ca1464f44f14769329fbf15987d20ab6c88f8fc5d359bd362be625f29" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "61c04d0c1bfed555c681ea079519933f071a5a026578ff73c4ff0df2d3462e5e" + url: "https://pub.dev" + source: hosted + version: "13.3.0" + ansi_styles: + dependency: transitive + description: + name: ansi_styles + sha256: "9c656cc12b3c27b17dd982b2cc5c0cfdfbdabd7bc8f3ae5e8542d9867b47ce8a" + url: "https://pub.dev" + source: hosted + version: "0.3.2+1" + antlr4: + dependency: transitive + description: + name: antlr4 + sha256: "752b4a6e4ad97953652a2b2bbf5377f46c94b579d3372b50080c7e5858234a05" + url: "https://pub.dev" + source: hosted + version: "4.13.2" + archive: + dependency: transitive + description: + name: archive + sha256: "6c5bcd986e06b94e3c40244af471750840a3d2341d1f9763a1100a14add517b4" + url: "https://pub.dev" + source: hosted + version: "4.3.0" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + build: + dependency: transitive + description: + name: build + sha256: "8a5c5761af8e31748bba3c82f68925ace40f3225c3eea25be9beb57eca7cd7a8" + url: "https://pub.dev" + source: hosted + version: "4.0.11" + build_config: + dependency: transitive + description: + name: build_config + sha256: d466ed2dc9c6cd1d169948879b84ee061eb5e22c64a7c6089879c6296d272a8d + url: "https://pub.dev" + source: hosted + version: "1.3.3" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: e1d40ef3f7934986d5da2271b1ba07794921ce263e44d622fb6c406d76589e33 + url: "https://pub.dev" + source: hosted + version: "4.1.6" + build_runner: + dependency: transitive + description: + name: build_runner + sha256: "894c243f6bc32015fec466ce30a6925bd537a77a426ee5bf481120477eb3de67" + url: "https://pub.dev" + source: hosted + version: "2.16.1" + build_verify: + dependency: transitive + description: + name: build_verify + sha256: "3b17b59b6d66f9d3e6014996f089902d56cec5760e051c353cc387b9da577652" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + build_version: + dependency: transitive + description: + name: build_version + sha256: "1063066ec338c18f0629d01077c9315f92fae3e7e0e06d0dc10e8aa3145d44f5" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: f87ea98192116f7093cb214551ce1929caae0681fdba282b3d8b4462adee7bb7 + url: "https://pub.dev" + source: hosted + version: "8.13.0" + cached_network_image: + dependency: transitive + description: + name: cached_network_image + sha256: "03d9cff3f68ded9e7c5588ebe7fc8650a669192685e59d2d2413d7813b286d6b" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: ff60d021d5a013490368cd9efdcbe35df4227fdb6c5971d15b4bc6e3d0172fe0 + url: "https://pub.dev" + source: hosted + version: "5.0.0" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: c8edf00f48d91bc469731e4fb095350b0162e18efe2cf0054cb40c62d1a1f70a + url: "https://pub.dev" + source: hosted + version: "2.0.0" + cel: + dependency: transitive + description: + name: cel + sha256: "72776d4cacd526f9846522e7a3ce8584c2071974d747e2ea695645faa2dcea1b" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + charcode: + dependency: transitive + description: + name: charcode + sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a + url: "https://pub.dev" + source: hosted + version: "1.4.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" + cli_completion: + dependency: transitive + description: + name: cli_completion + sha256: d61bbbfccabe02fdad54eeb275586e15e31abdeb35ec7a0d68832fae473cc298 + url: "https://pub.dev" + source: hosted + version: "0.6.0" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" + cli_launcher: + dependency: transitive + description: + name: cli_launcher + sha256: d2dd1baf7ae4ff1550e731dcb8fe669a5c652903e833f75359c178222c44214e + url: "https://pub.dev" + source: hosted + version: "0.3.4" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: "89b8801ba49f8b593fbf2d3e59efd0f566c46cd4ba6837ae2610c72b52a0f899" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + clock: + dependency: transitive + description: + name: clock + sha256: e51d50bca3217c9a9fa2b41a30e4a38971133f5f9ec7a3d57bae095007f1d28e + url: "https://pub.dev" + source: hosted + version: "1.1.3" + cloud_firestore: + dependency: transitive + description: + name: cloud_firestore + sha256: db785e4d0a995654781835b78a30b8bb203d967e123843bb918df0cdb6a7ec69 + url: "https://pub.dev" + source: hosted + version: "6.10.0" + cloud_firestore_platform_interface: + dependency: transitive + description: + name: cloud_firestore_platform_interface + sha256: ef985a8239846187de0eccce2092a940eaf281a1357c6325e1739ec0b6c230e2 + url: "https://pub.dev" + source: hosted + version: "8.0.7" + cloud_firestore_web: + dependency: transitive + description: + name: cloud_firestore_web + sha256: b30b3b44937345d50b195f85ef328f1d71c2a79965cb1645a479d029d45f636a + url: "https://pub.dev" + source: hosted + version: "5.7.3" + cloud_functions: + dependency: transitive + description: + name: cloud_functions + sha256: "60ab011bcf99c18f0db95c3c958d83da4b846547267fe69984c4faee0008a7c7" + url: "https://pub.dev" + source: hosted + version: "6.5.0" + cloud_functions_platform_interface: + dependency: transitive + description: + name: cloud_functions_platform_interface + sha256: "5f81c0a3e5e45942edd787ce44650db40ed8ac6dd81db7eab3ba6e68c71d05fa" + url: "https://pub.dev" + source: hosted + version: "6.0.7" + cloud_functions_web: + dependency: transitive + description: + name: cloud_functions_web + sha256: fca862c4babe8c17e9aa20dc1a521ab24dd04d1296491beb0000245f3e04b648 + url: "https://pub.dev" + source: hosted + version: "5.1.13" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: "828110d598123b5ea96c00c9f3c72105bf79f8ee36c20a39b26209ade421ec57" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: aa5932e94c6c39c2f9ec4e5e06dfdd11a9430a61f6c41b6ba75b28ce0c481baf + url: "https://pub.dev" + source: hosted + version: "4.12.0" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + connectivity_plus: + dependency: transitive + description: + name: connectivity_plus + sha256: "762c99f890ca8bf87f7337236f99edd42793843bc6c3631da294a76653a54bd0" + url: "https://pub.dev" + source: hosted + version: "7.3.1" + connectivity_plus_platform_interface: + dependency: transitive + description: + name: connectivity_plus_platform_interface + sha256: "3c09627c536d22fd24691a905cdd8b14520de69da52c7a97499c8be5284a32ed" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + conventional_commit: + dependency: transitive + description: + name: conventional_commit + sha256: c40b1b449ce2a63fa2ce852f35e3890b1e182f5951819934c0e4a66254bc0dc3 + url: "https://pub.dev" + source: hosted + version: "0.6.1+1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + coverage: + dependency: "direct dev" + description: + name: coverage + sha256: "956a3de0725ca232ad353565a8290d3357592bf4250f6f298a185e2d949c5d3d" + url: "https://pub.dev" + source: hosted + version: "1.15.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + cupertino_icons: + dependency: transitive + description: + name: cupertino_icons + sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" + url: "https://pub.dev" + source: hosted + version: "1.0.9" + cupertino_ui: + dependency: transitive + description: + name: cupertino_ui + sha256: c6747018d37d32feeeb858a1d7043831cb541460ebe1a890a995cdb8df8ea5a5 + url: "https://pub.dev" + source: hosted + version: "1.1.0" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "82ade9fc4273f29ed673e33166944465225b4f7fc5d4aaef48605cc751c18fc1" + url: "https://pub.dev" + source: hosted + version: "3.1.13" + dbus: + dependency: transitive + description: + name: dbus + sha256: a48d5da28e89bd02196e80d81ed8d7954923d00a0f4a68cc20b575038f023383 + url: "https://pub.dev" + source: hosted + version: "0.7.15" + device_info_plus: + dependency: transitive + description: + name: device_info_plus + sha256: "0891702f96b2e465fe567b7ec448380e6b1c14f60af552a8536d9f583b6b8442" + url: "https://pub.dev" + source: hosted + version: "13.2.0" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: "04b173a92e2d9161dfead145667037c8d834db725ce2e7b942bfe18fd2f45a46" + url: "https://pub.dev" + source: hosted + version: "8.1.0" + equatable: + dependency: transitive + description: + name: equatable + sha256: "3bce007a596ff8b3119c45d68aaef631272537c03d30e5d4534dd24bf4c5eaa2" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + fake_cloud_firestore: + dependency: transitive + description: + name: fake_cloud_firestore + sha256: "4276eadc5e2c36663fe393e98d14d4cc413ebe8ce0d02edd8cda57ef7a5a8369" + url: "https://pub.dev" + source: hosted + version: "4.3.0" + fake_firebase_security_rules: + dependency: transitive + description: + name: fake_firebase_security_rules + sha256: "983a1b3aec751af3122c1cfa79ebac0336a1589226a9683548b30457a5268720" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + 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: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + firebase_analytics: + dependency: transitive + description: + name: firebase_analytics + sha256: b82d2cabf4905a465ac95937037cbab4f2ca80f559cb04abd3da3f2abb9960bb + url: "https://pub.dev" + source: hosted + version: "12.6.0" + firebase_analytics_platform_interface: + dependency: transitive + description: + name: firebase_analytics_platform_interface + sha256: "5c3ab4b681c837afd71f0abf77c7485f51c7b645d4ae77b96f9ed54779ff4126" + url: "https://pub.dev" + source: hosted + version: "6.0.7" + firebase_analytics_web: + dependency: transitive + description: + name: firebase_analytics_web + sha256: "09eb4d0ce4c9a16efbc277ac709a90eb197d5512592215a73c6a302ab1ab6c3a" + url: "https://pub.dev" + source: hosted + version: "0.6.1+13" + firebase_auth: + dependency: transitive + description: + name: firebase_auth + sha256: "144fa22926b629e797ecc757db5d31b145aeedf6018160296dae945d98a98fb5" + url: "https://pub.dev" + source: hosted + version: "6.7.0" + firebase_auth_platform_interface: + dependency: transitive + description: + name: firebase_auth_platform_interface + sha256: "9959a6e7338df4f725707a71fd9f727d8dfafd7c46cbaf59fcf7c8e94fb92d11" + url: "https://pub.dev" + source: hosted + version: "9.1.0" + firebase_auth_web: + dependency: transitive + description: + name: firebase_auth_web + sha256: "11861819c667e9f47269fa1e7fca8d0e5229f80d4d3008d44b2108b8c93bfd94" + url: "https://pub.dev" + source: hosted + version: "6.3.0" + firebase_core: + dependency: transitive + description: + name: firebase_core + sha256: "5fbbaa2aeb08a52feee559f92a62f5a6b04c9bdff81c8f608636771cf0d7b680" + url: "https://pub.dev" + source: hosted + version: "4.15.0" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: "9bfbc85faca09346471ac56fbcee00757ebcfd85887c6f602764eff0001902d8" + url: "https://pub.dev" + source: hosted + version: "8.1.1" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: "8ed84f5001dbd7a0390e2818d46bc858ceebb970edededdfc8d6552c76fc2f6e" + url: "https://pub.dev" + source: hosted + version: "3.12.0" + firebase_crashlytics: + dependency: transitive + description: + name: firebase_crashlytics + sha256: f37fe0df162f08bd549114b784037c73a093c64d22625632f97dbbddf18669b9 + url: "https://pub.dev" + source: hosted + version: "5.4.0" + firebase_crashlytics_platform_interface: + dependency: transitive + description: + name: firebase_crashlytics_platform_interface + sha256: "8270dcdb1800db3e5f888ce30a2d5e8f92c379c461f022297c83b2c794aa1792" + url: "https://pub.dev" + source: hosted + version: "3.9.0" + firebase_messaging: + dependency: transitive + description: + name: firebase_messaging + sha256: "47a8142a0b897783103b01018ad69f123b819b0120483847bde30d1b2fa89b36" + url: "https://pub.dev" + source: hosted + version: "16.7.0" + firebase_messaging_platform_interface: + dependency: transitive + description: + name: firebase_messaging_platform_interface + sha256: f9707e185b8ce1155d58e7e29348245072d99981f899fa8d3214abb2652c4ccb + url: "https://pub.dev" + source: hosted + version: "4.10.0" + firebase_messaging_web: + dependency: transitive + description: + name: firebase_messaging_web + sha256: "311b16c75fc645c7a70c9ade38908abb12310dfce449fc3d3cee1771f196351e" + url: "https://pub.dev" + source: hosted + version: "4.2.5" + firebase_remote_config: + dependency: transitive + description: + name: firebase_remote_config + sha256: fa518445638fa3903e359ac544bd3243e72ada025728c8dbfa4b28ae939e8b38 + url: "https://pub.dev" + source: hosted + version: "6.7.0" + firebase_remote_config_platform_interface: + dependency: transitive + description: + name: firebase_remote_config_platform_interface + sha256: dc5a9713031f2ca744cb266d96a58609e929db19f3d91a1dd6bb8f8a0d77acad + url: "https://pub.dev" + source: hosted + version: "3.0.7" + firebase_remote_config_web: + dependency: transitive + description: + name: firebase_remote_config_web + sha256: "500abf695c370c76a486ad7480a6df7caf55d2d35ccc5c66b9fc9c1c47f2bc14" + url: "https://pub.dev" + source: hosted + version: "1.10.14" + firebase_storage: + dependency: transitive + description: + name: firebase_storage + sha256: f936f99ea1f49f644d5e83a495ed71bd89b040358e548f9dee14f10458d49ea0 + url: "https://pub.dev" + source: hosted + version: "13.6.0" + firebase_storage_platform_interface: + dependency: transitive + description: + name: firebase_storage_platform_interface + sha256: "249a8cfa965f0bfb5ed6b6e14fa9b63428ba3c71ffe73b0e360da8d2fde2ef92" + url: "https://pub.dev" + source: hosted + version: "6.0.7" + firebase_storage_web: + dependency: transitive + description: + name: firebase_storage_web + sha256: b8a2e1f8a013d192f7b6e09ad40ec50e13d0bc97d2a3462369b2ba3f368d9c78 + url: "https://pub.dev" + source: hosted + version: "3.11.13" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: f94fc6f5480977e3eb7b622ed488f671ba96c971bd0e274e6bd0f0ba9b044816 + url: "https://pub.dev" + source: hosted + version: "3.4.5" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_localizations: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_screenutil: + dependency: transitive + description: + name: flutter_screenutil + sha256: "8239210dd68bee6b0577aa4a090890342d04a136ce1c81f98ee513fc0ce891de" + url: "https://pub.dev" + source: hosted + version: "5.9.3" + flutter_spinkit: + dependency: transitive + description: + name: flutter_spinkit + sha256: "77850df57c00dc218bfe96071d576a8babec24cf58b2ed121c83cca4a2fdce7f" + url: "https://pub.dev" + source: hosted + version: "5.2.2" + flutter_test: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: "218aeb56050c714f62a3182775320dfa04602b55074873e24e31bbd39bda96fb" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + hooks: + dependency: transitive + description: + name: hooks + sha256: eaac480a35ec0814146c2c48d96aaa829e0e44a7662c88ae84c9edf4bc35651f + url: "https://pub.dev" + source: hosted + version: "2.2.0" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + intl: + dependency: transitive + description: + name: intl + sha256: "1ca20c894b1717686a2319b8548763d812bc0aabdac580420a44c5178c57a867" + url: "https://pub.dev" + source: hosted + version: "0.20.3" + io: + dependency: transitive + description: + name: io + sha256: "2635216ca6a737e60de577ffa1a48a0bec76ca8a62917cfc1bb88c14c570646f" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + jni: + dependency: transitive + description: + name: jni + sha256: f038e58b4dc2c9037f50e233175086337e0b305e356d28211bf55f21c504cbd3 + url: "https://pub.dev" + source: hosted + version: "1.0.3" + jni_flutter: + dependency: transitive + description: + name: jni_flutter + sha256: b2310cdd4c18c65c081ab141a41efa94aa26c65431803703ece51996f174f351 + url: "https://pub.dev" + source: hosted + version: "1.0.3" + jni_util: + dependency: transitive + description: + name: jni_util + sha256: "1ba86da04a5f2bf18fde2edb235587e70c5b0fc5bd4ba955f46b00942c3fc35f" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" + url: "https://pub.dev" + source: hosted + version: "4.12.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" + url: "https://pub.dev" + source: hosted + version: "6.1.0" + logger: + dependency: transitive + description: + name: logger + sha256: "2a0dc097e7b01d942475bdd552356db2d0f768b05540bd4b2b53f1840f2239a7" + url: "https://pub.dev" + source: hosted + version: "2.8.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + mask_text_input_formatter: + dependency: transitive + description: + name: mask_text_input_formatter + sha256: "978c58ec721c25621ceb468e633f4eef64b64d45424ac4540e0565d4f7c800cd" + url: "https://pub.dev" + source: hosted + version: "2.9.0" + mason: + dependency: transitive + description: + name: mason + sha256: ba60dcf99bb7363e46085388e9c910d18daec86567518c1669c446f737140f28 + url: "https://pub.dev" + source: hosted + version: "0.1.3" + mason_logger: + dependency: "direct dev" + description: + name: mason_logger + sha256: "495a2732647b1c861f75c8bcc36766b8af4c59999a6c8562d7e604cc6fba0fa3" + url: "https://pub.dev" + source: hosted + version: "0.3.6" + matcher: + dependency: transitive + description: + name: matcher + sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd" + url: "https://pub.dev" + source: hosted + version: "0.12.20" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + material_ui: + dependency: transitive + description: + name: material_ui + sha256: "6d056d60ce745335fcf1c726a4fc46a481a62229f202b65dbc2db5edbbabc7e2" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + melos: + dependency: "direct dev" + description: + name: melos + sha256: "1a67a089ae6ec7b75199bb909c11c249ede4400842107f988093c587d7cf8bb6" + url: "https://pub.dev" + source: hosted + version: "8.8.0" + meta: + dependency: transitive + description: + name: meta + sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9" + url: "https://pub.dev" + source: hosted + version: "1.19.0" + mime: + dependency: transitive + description: + name: mime + sha256: bd47de35f07e27267e69c8c8b22edf9473bfee170a60d60fcc93730c5144b7f6 + url: "https://pub.dev" + source: hosted + version: "2.1.0" + mock_exceptions: + dependency: transitive + description: + name: mock_exceptions + sha256: "6e3e623712d2c6106ffe9e14732912522b565ddaa82a8dcee6cd4441b5984056" + url: "https://pub.dev" + source: hosted + version: "0.8.2" + mockito: + dependency: transitive + description: + name: mockito + sha256: "6c1970612452260366064d4df80fdb3eece1ec5e52610ae34a0e51f2f3d8e64f" + url: "https://pub.dev" + source: hosted + version: "5.8.1" + mocktail: + dependency: transitive + description: + name: mocktail + sha256: "5e1bf53cc7baa8062a33b84424deb61513858ea05c601b8509e683815b5914aa" + url: "https://pub.dev" + source: hosted + version: "1.0.5" + more: + dependency: transitive + description: + name: more + sha256: e252628d2183cc09539b686abfbd9d8302675959b89a2a8146f5f4baca6ac5ba + url: "https://pub.dev" + source: hosted + version: "4.7.0" + mustache_template: + dependency: transitive + description: + name: mustache_template + sha256: "17a8de9072575bb1a3237b90ed1f756b2189995b334df2ad12b7cdd76b13f3ce" + url: "https://pub.dev" + source: hosted + version: "2.0.6" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + nm: + dependency: transitive + description: + name: nm + sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + objectid: + dependency: transitive + description: + name: objectid + sha256: f6cea8360862c78b1984cc8f497e484aa7f491282f7f4e4bd2f722bc85f93237 + url: "https://pub.dev" + source: hosted + version: "4.0.3" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: ad56fd53a78ff6b1472fa59ff2a4e8b8ccabafc586fc263a1dfad0b99b5553e3 + url: "https://pub.dev" + source: hosted + version: "9.6.0" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + package_info_plus: + dependency: transitive + description: + name: package_info_plus + sha256: "127e1751e37ffb2ff4658beeaca77bad0c27bf5f932bd3a501c2296926d4b481" + url: "https://pub.dev" + source: hosted + version: "10.2.1" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: db762cb2f4f25ee60fb6359773861b0f199e00b90d237bd85a76a1e806b46ef4 + url: "https://pub.dev" + source: hosted + version: "4.1.0" + path: + dependency: "direct dev" + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825 + url: "https://pub.dev" + source: hosted + version: "2.1.6" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" + url: "https://pub.dev" + source: hosted + version: "7.0.2" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "4177f68c237ea2128d1bee66ac17b2ce05ba3dbaafcbdd54c5d40a39d0b6b11c" + url: "https://pub.dev" + source: hosted + version: "1.5.3" + posix: + dependency: transitive + description: + name: posix + sha256: bc1bad54ad2b735816e31f8d4600cfde6c7839975085ddfbca48b6c9f7c4044e + url: "https://pub.dev" + source: hosted + version: "6.5.2" + process: + dependency: transitive + description: + name: process + sha256: "4242ba3508d37e01808bdf71ad1d5bb93a8d671bf2e7450e6b1b353fb0808891" + url: "https://pub.dev" + source: hosted + version: "5.0.6" + process_run: + dependency: transitive + description: + name: process_run + sha256: "19c6f9585e4662a2fd95a4f17361bad7bb1610957a15ec8a31171953a1b87da8" + url: "https://pub.dev" + source: hosted + version: "1.3.6" + prompts: + dependency: transitive + description: + name: prompts + sha256: "3773b845e85a849f01e793c4fc18a45d52d7783b4cb6c0569fad19f9d0a774a1" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + provider: + dependency: transitive + description: + name: provider + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" + url: "https://pub.dev" + source: hosted + version: "6.1.5+1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "261236774e8b1d69cfc6b9eabbc96c40f25e7a2d6b171f3385d4f65d5734fb24" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + pub_updater: + dependency: transitive + description: + name: pub_updater + sha256: "3aadeb8c68d15ef9d117b36474ddcc543b48ae1e64f7dd71407da26df66bf7c8" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + pubspec_parse: + dependency: "direct dev" + description: + name: pubspec_parse + sha256: c38b81cbf34450b67e0265d73433569d12e34782e30ed769c9cc99c9d5f2e796 + url: "https://pub.dev" + source: hosted + version: "1.6.0" + recase: + dependency: transitive + description: + name: recase + sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 + url: "https://pub.dev" + source: hosted + version: "4.1.0" + record_use: + dependency: transitive + description: + name: record_use + sha256: "1cb8564af8d43b464294411db9217f5ec04891c6f22ee2c32d73ae05e88a6bd2" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + rx: + dependency: transitive + description: + name: rx + sha256: "3c819c80915138089c517e0d78f462792c5a2de05189466ab38bee7b6a8a330f" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" + shared_preferences: + dependency: transitive + description: + name: shared_preferences + sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf + url: "https://pub.dev" + source: hosted + version: "2.5.5" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "1e12aafe408aa50da80edfd679a2a6bf63ba7ab37c7fa98286da459a757b3399" + url: "https://pub.dev" + source: hosted + version: "2.4.28" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "2ec3934efa51e46117f23031cc141b8fc878e8525b94ec1ea4f7f586cf1b47ea" + url: "https://pub.dev" + source: hosted + version: "2.5.7" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: a603f1fb984a7391ae5978d1b92bfaaa08b350dca5c825256f925818f7943bf5 + url: "https://pub.dev" + source: hosted + version: "4.2.4" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "14c2945847669b44089bb1222f66873d7ff7103c58911917f2a63c5a62327898" + url: "https://pub.dev" + source: hosted + version: "0.10.14" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: "54ad23e2a79c846b596b6c56cdd9c867ea58c288258402316b602f78df0cbfe0" + url: "https://pub.dev" + source: hosted + version: "2.4.4" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "805b27c6fa10d8883253795e644818fbd5c87ce665459db84fbe71326cbb5421" + url: "https://pub.dev" + source: hosted + version: "2.4.4" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "077e38e82c099534146a2fb31b608c2a3a4489a0f21d9af6b2f5c87b566a1ea0" + url: "https://pub.dev" + source: hosted + version: "2.5.13" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: dbdda396f975f74d611ffd3e6fb280ee3ee4cd2319b89278533f427e0140cf0b + url: "https://pub.dev" + source: hosted + version: "2.4.4" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: c9177b2f3150ed0647b90a94307c28452fe0a5401863044216aed83addd6c7c5 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "277654b3034d17ac6f9f1cb5595db011b1d5d41e8806866db28e0abaa101c490" + url: "https://pub.dev" + source: hosted + version: "1.12.2" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: a00e5f18bffc764f923e7dec1038527f7fe7a1791361a7117f0358193f13d53a + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "397b146f97613b83d84bdccb439bc8d0f3aebb6c1d14a9641e6f24201c490b2d" + url: "https://pub.dev" + source: hosted + version: "3.4.2" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test: + dependency: "direct dev" + description: + name: test + sha256: ca578dc12bb8b2f40b67b7d3bd2fac4f31c01a6ff7130a14e2597b919934507f + url: "https://pub.dev" + source: hosted + version: "1.31.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11" + url: "https://pub.dev" + source: hosted + version: "0.7.12" + test_core: + dependency: transitive + description: + name: test_core + sha256: d2e98ec12998368dc59ddd47ab709f2cd55acd6b66dc7db764455a44082f4bc5 + url: "https://pub.dev" + source: hosted + version: "0.6.18" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + universal_io: + dependency: transitive + description: + name: universal_io + sha256: f63cbc48103236abf48e345e07a03ce5757ea86285ed313a6a032596ed9301e2 + url: "https://pub.dev" + source: hosted + version: "2.3.1" + uuid: + dependency: transitive + description: + name: uuid + sha256: "9b129329f58692f6e6578329498a8fe9fbe98f090beb764ffbb8ee2eadd01dcd" + url: "https://pub.dev" + source: hosted + version: "4.6.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "92b9910f66ed1057fd4da7b040ae7c74cafacf885bdc81be496928d5049b032d" + url: "https://pub.dev" + source: hosted + version: "2.4.3" + visibility_detector: + dependency: transitive + description: + name: visibility_detector + sha256: dd5cc11e13494f432d15939c3aa8ae76844c42b723398643ce9addb88a5ed420 + url: "https://pub.dev" + source: hosted + version: "0.4.0+2" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "5f37239c4851efcef929cea7824e76df7f2f0970aef85d66bbc430afa40e72f0" + url: "https://pub.dev" + source: hosted + version: "15.3.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + win32: + dependency: transitive + description: + name: win32 + sha256: a0b93865d5644f11cf6a8c3f6db909f1ec168958b5805f6cc684adea957cd63d + url: "https://pub.dev" + source: hosted + version: "6.4.0" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "73b1d78920a9d6e03f8b4e43e612b87bf3152a0e5c5e5150267762b7c4116904" + url: "https://pub.dev" + source: hosted + version: "3.0.3" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4" + url: "https://pub.dev" + source: hosted + version: "7.0.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: f67cdd8e07d3c6329146aaef1ba043542b3134c12489f553ca9a7435d1068aea + url: "https://pub.dev" + source: hosted + version: "3.1.4" + yaml_edit: + dependency: transitive + description: + name: yaml_edit + sha256: "07c9e63ba42519745182b88ca12264a7ba2484d8239958778dfe4d44fe760488" + url: "https://pub.dev" + source: hosted + version: "2.2.4" +sdks: + dart: ">=3.12.0 <4.0.0" + flutter: ">=3.47.5" diff --git a/pubspec.yaml b/pubspec.yaml index 6879a77..3ac8e49 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,11 +1,82 @@ name: flutter_forge description: A place where Flutter packages are crafted and built. +publish_to: none environment: - sdk: '>=3.2.0 <4.0.0' + sdk: '>=3.12.0 <4.0.0' + flutter: '>=3.47.5' dev_dependencies: - flutter_lints: ^5.0.0 - melos: ^6.2.0 + test: ^1.31.1 + coverage: ^1.15.0 + flutter_lints: ^6.0.0 + melos: ^8.8.0 + path: ^1.9.1 pubspec_parse: ^1.3.0 mason_logger: ^0.3.1 + +workspace: + - packages/cli_core + - packages/connectivity_wrapper/example + - packages/connectivity_wrapper + - packages/dzod/example + - packages/dzod + - packages/html_rich_text/example + - packages/html_rich_text + - packages/morse_tap/example + - packages/morse_tap + - packages/nonstop_cli/bricks/flutter_app_for_mono_repo/hooks + - packages/nonstop_cli/bricks/flutter_package_for_mono_repo/hooks + - packages/nonstop_cli/bricks/flutter_plugin_for_mono_repo/hooks + - packages/nonstop_cli/bricks/flutter_project_with_mono_repo/hooks + - packages/nonstop_cli + - packages/ns_firebase_utils + - packages/ns_intl_phone_input/example + - packages/ns_intl_phone_input + - packages/ns_utils + - packages/timer_button/example + - packages/timer_button + - plugins/contact_permission/example + - plugins/contact_permission + - tools/readme_sync + +melos: + command: + version: + workspaceChangelog: false + fetchTags: true + updateGitTagRefs: false + linkToCommits: false + hooks: + preCommit: dart run melos run update_nonstop_cli_version && dart run melos run update_nonstop_cli_bundles + + scripts: + lint: + description: "Run format and analyze all packages" + # Check owned source once from the workspace root. Git omits ignored SDK + # and build artifacts; untracked source is included during development. + run: | + git ls-files -z --cached --others --exclude-standard -- '*.dart' \ + ':(exclude)**/__brick__/**' ':(exclude)**/coverage/**' ':(exclude)**/build/**' \ + | xargs -0 dart format --output=none --set-exit-if-changed && + dart analyze --fatal-infos . + fix: + description: "Apply Dart automated fixes to all packages" + exec: + command: dart fix --apply + concurrency: 5 + packageFilters: + dirExists: lib + test: + description: "Run every package test and require 100% line coverage" + run: python3 tools/coverage.py + test:coverage: + description: "Run every package test and require 100% line coverage" + run: python3 tools/coverage.py + update_nonstop_cli_version: + run: dart run tools/update_nonstop_cli_version.dart + update_nonstop_cli_bundles: + run: dart run tools/update_nonstop_cli_bundles.dart + sync:readme: + description: "Sync README sections across all packages" + run: dart run tools/readme_sync/readme_sync.dart diff --git a/test/tooling_test.dart b/test/tooling_test.dart new file mode 100644 index 0000000..0618158 --- /dev/null +++ b/test/tooling_test.dart @@ -0,0 +1,276 @@ +import 'dart:io'; + +import 'package:mason_logger/mason_logger.dart'; +import 'package:path/path.dart' as path; +import 'package:test/test.dart'; + +import '../tools/update_nonstop_cli_bundles.dart' as bundles; +import '../tools/update_nonstop_cli_version.dart' as version; + +void main() { + late Directory root; + late Logger logger; + + Future write(String relative, String content) async { + final file = File(path.join(root.path, relative)); + await file.parent.create(recursive: true); + await file.writeAsString(content); + return file; + } + + Future brick(String name) async { + await write('$name/brick.yaml', 'name: $name\n'); + await write('$name/__brick__/main.dart', 'void main() {}\n'); + await write('$name/hooks/pre_gen.dart', 'void run() {}\n'); + await write( + '$name/hooks/pubspec.yaml', + 'name: ${name}_hooks\nresolution: workspace\ndependencies:\n mason: ^0.1.2\n', + ); + return bundles.BundlePath( + input: path.join(root.path, name), + output: path.join(root.path, 'output'), + fileName: '${name}_bundle.dart', + ); + } + + setUp(() async { + root = await Directory.systemTemp.createTemp('tooling_test_'); + logger = Logger(level: Level.quiet); + final originalExitCode = exitCode; + addTearDown(() => exitCode = originalExitCode); + }); + + tearDown(() => root.delete(recursive: true)); + + test( + 'version command writes declared version and stages only changed output', + () async { + await write('pubspec.yaml', 'name: sample\nversion: 1.2.3+4\n'); + await Directory(path.join(root.path, 'lib')).create(); + final calls = >[]; + await version.main( + cliDirectory: root.path, + logger: logger, + runProcess: (executable, arguments) async { + calls.add([executable, ...arguments]); + return ProcessResult(1, arguments.first == 'diff' ? 1 : 0, '', ''); + }, + ); + expect(exitCode, 0); + final generated = File(path.join(root.path, 'lib/version.dart')); + expect( + await generated.readAsString(), + contains("const packageVersion = '1.2.3+4';"), + ); + expect(calls, [ + ['git', 'diff', '--quiet', generated.path], + ['git', 'add', generated.path], + ]); + + calls.clear(); + await version.main( + cliDirectory: root.path, + logger: logger, + runProcess: (executable, arguments) async { + calls.add([executable, ...arguments]); + return ProcessResult(1, 0, '', ''); + }, + ); + expect(exitCode, 0); + expect(calls, [ + ['git', 'diff', '--quiet', generated.path], + ]); + }, + ); + + test( + 'version command reports IO and process failures without exiting host', + () async { + await version.main(cliDirectory: root.path); + expect(exitCode, 1); + await write('pubspec.yaml', 'name: sample\nversion: 1.0.0\n'); + await Directory(path.join(root.path, 'lib')).create(); + await version.main( + cliDirectory: root.path, + logger: logger, + runProcess: (_, _) async => + throw const ProcessException('git', [], 'failed'), + ); + expect(exitCode, 1); + }, + ); + + test( + 'standalone brick staging preserves source and removes local caches', + () async { + final input = await brick('sample'); + await write('sample/hooks/.dart_tool/package_config.json', '{}'); + await write('sample/hooks/coverage/lcov.info', 'temporary coverage'); + await write('sample/hooks/test/hook_test.dart', 'test code'); + await write('sample/hooks/build/temp', 'temporary build'); + await Link( + path.join(input.input, '__brick__/linked.dart'), + ).create('main.dart'); + final sourceManifest = File(path.join(input.input, 'hooks/pubspec.yaml')); + final original = await sourceManifest.readAsString(); + final staged = await bundles.stageStandaloneBrick(Directory(input.input)); + addTearDown(() => staged.parent.delete(recursive: true)); + expect(await sourceManifest.readAsString(), original); + expect( + await File(path.join(staged.path, 'hooks/pubspec.yaml')).readAsString(), + allOf( + contains('mason: ^0.1.2'), + isNot(contains('resolution: workspace')), + ), + ); + expect( + await File( + path.join(staged.path, '__brick__/main.dart'), + ).readAsString(), + 'void main() {}\n', + ); + expect( + await Link(path.join(staged.path, '__brick__/linked.dart')).target(), + 'main.dart', + ); + for (final cache in ['.dart_tool', 'coverage', 'build', 'test']) { + expect( + Directory(path.join(staged.path, 'hooks', cache)).existsSync(), + isFalse, + ); + } + }, + ); + + test( + 'staging cleans its temporary directory if the source is missing', + () async { + Set stagedDirectories() => Directory.systemTemp + .listSync() + .whereType() + .where((dir) => path.basename(dir.path).startsWith('nonstop_bundle_')) + .map((dir) => dir.path) + .toSet(); + final before = stagedDirectories(); + await expectLater( + bundles.stageStandaloneBrick( + Directory(path.join(root.path, 'missing')), + ), + throwsA(isA()), + ); + expect(stagedDirectories(), before); + }, + ); + + test( + 'bundle command formats and commits each changed generated file', + () async { + final inputs = [await brick('first'), await brick('second')]; + final calls = >[]; + final stages = []; + await bundles.main( + bundles: inputs, + logger: logger, + runProcess: (executable, arguments) async { + calls.add([executable, ...arguments]); + if (executable == 'mason') { + stages.add(arguments[1]); + expect( + await File( + path.join(arguments[1], 'hooks/pubspec.yaml'), + ).readAsString(), + isNot(contains('resolution: workspace')), + ); + expect(arguments, [ + 'bundle', + arguments[1], + '-t', + 'dart', + '-o', + inputs.first.output, + ]); + } + return ProcessResult(1, arguments.first == 'diff' ? 1 : 0, '', ''); + }, + ); + expect(exitCode, 0); + expect(calls.first, ['dart', 'pub', 'global', 'activate', 'mason_cli']); + expect(calls.where((call) => call[0] == 'mason'), hasLength(2)); + expect( + calls.where((call) => call[0] == 'git' && call[1] == 'commit'), + hasLength(2), + ); + expect( + calls, + contains( + equals([ + 'dart', + 'format', + path.join(inputs.first.output, inputs.first.fileName), + ]), + ), + ); + for (final stage in stages) { + expect(Directory(stage).parent.existsSync(), isFalse); + } + expect(bundles.bundlePaths, hasLength(4)); + expect( + bundles.bundlePaths.map((bundle) => bundle.fileName).toSet(), + hasLength(4), + ); + }, + ); + + test('bundle command does not stage or commit unchanged output', () async { + final input = await brick('sample'); + final calls = >[]; + await bundles.main( + bundles: [input], + runProcess: (executable, arguments) async { + calls.add([executable, ...arguments]); + return ProcessResult(1, 0, '', ''); + }, + ); + expect(exitCode, 0); + expect(calls.where((call) => call[0] == 'git').map((call) => call[1]), [ + 'diff', + ]); + }); + + test('bundle failure returns error and removes the staged copy', () async { + final input = await brick('sample'); + String? staged; + await bundles.main( + bundles: [input], + logger: logger, + runProcess: (executable, arguments) async { + if (executable == 'mason') staged = arguments[1]; + return ProcessResult(1, executable == 'mason' ? 1 : 0, '', 'failed'); + }, + ); + expect(exitCode, 1); + expect(Directory(staged!).parent.existsSync(), isFalse); + expect( + await File(path.join(input.input, 'hooks/pubspec.yaml')).readAsString(), + contains('resolution: workspace'), + ); + }); + + test('thrown process failure removes staging and reports error', () async { + final input = await brick('sample'); + String? staged; + await bundles.main( + bundles: [input], + logger: logger, + runProcess: (executable, arguments) async { + if (executable == 'mason') { + staged = arguments[1]; + throw const ProcessException('mason', [], 'not available'); + } + return ProcessResult(1, 0, '', ''); + }, + ); + expect(exitCode, 1); + expect(Directory(staged!).parent.existsSync(), isFalse); + }); +} diff --git a/tools/coverage.py b/tools/coverage.py new file mode 100644 index 0000000..7ace99c --- /dev/null +++ b/tools/coverage.py @@ -0,0 +1,314 @@ +#!/usr/bin/env python3 +"""Run every real workspace package and enforce 100% executable Dart line coverage. + +Examples and Mason hooks are included. Mustache brick templates are validated by +nonstop_cli's template tests; they are not executable Dart packages before rendering. +All owned production libraries are imported by a temporary test so unused files +cannot silently disappear from the denominator. Empty reports never pass. +""" +from __future__ import annotations + +import argparse +import json +import os +from pathlib import Path +import re +import shutil +import signal +import subprocess +import sys +import time +from urllib.parse import quote + +ROOT = Path(__file__).resolve().parents[1] +RUNTIME_CRASH = b'Shell subprocess crashed with segmentation fault' + +# Run the installed Flutter tool through its test-runner injection point. The +# stock CLI cannot request truly unfiltered coverage: an empty package selection +# disables VM filters but also discards every line when formatting LCOV. Setting +# the collector's nullable libraryNames to null handles both stages correctly. +# This does not modify the SDK or coverage dependency. Function.apply forwards +# Flutter's entire runTests signature, including optional arguments, unchanged. +FLUTTER_COVERAGE_RUNNER = """\ +import 'dart:io'; +import 'package:flutter_tools/runner.dart' as runner; +import 'package:flutter_tools/src/cache.dart'; +import 'package:flutter_tools/src/commands/test.dart'; +import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:flutter_tools/src/test/coverage_collector.dart'; +import 'package:flutter_tools/src/test/runner.dart'; +import 'package:flutter_tools/src/hook_runner.dart'; +import 'package:flutter_tools/src/build_system/targets/hook_runner_native.dart'; +import 'package:flutter_tools/src/build_system/build_targets.dart'; +import 'package:flutter_tools/src/isolated/build_targets.dart'; +import 'package:flutter_tools/src/base/template.dart'; +import 'package:flutter_tools/src/isolated/mustache_template.dart'; + +class AllSourceTestRunner implements FlutterTestRunner { + @override + dynamic noSuchMethod(Invocation call) { + if (call.memberName != #runTests) return super.noSuchMethod(call); + final watcher = call.namedArguments[#watcher]; + if (watcher is! CoverageCollector) { + throw StateError('Expected coverage collector, got $watcher'); + } + watcher.libraryNames = null; + return Function.apply(const FlutterTestRunner().runTests, + call.positionalArguments, call.namedArguments); + } +} + +Future main(List args) async { + Cache.flutterRoot = Platform.environment['FLUTTER_ROOT']!; + await runner.run(args, () => [TestCommand( + testRunner: AllSourceTestRunner(), + nativeAssetsBuilder: globals.nativeAssetsBuilder)], + shutdownHooks: globals.shutdownHooks, + overrides: { + FlutterHookRunner: () => FlutterHookRunnerNative(), + BuildTargets: () => const BuildTargetsImpl(), + TemplateRenderer: () => const MustacheTemplateRenderer(), + }); +} +""" + + +def flutter_command(coverage: Path, env: dict) -> list[str]: + executable = shutil.which('flutter') + if executable is None: + raise RuntimeError('Flutter is not on PATH') + sdk = Path(env.get('FLUTTER_ROOT') or Path(executable).resolve().parent.parent) + config = sdk / 'packages/flutter_tools/.dart_tool/package_config.json' + if not config.exists(): + raise RuntimeError('Cannot locate Flutter tool package config; set FLUTTER_ROOT to the SDK directory') + env['FLUTTER_ROOT'] = str(sdk) + driver = coverage / 'flutter_coverage_runner.dart' + driver.write_text(FLUTTER_COVERAGE_RUNNER) + return [str(sdk / 'bin/cache/dart-sdk/bin/dart'), f'--packages={config}', str(driver), 'test'] + + +def discover(root: Path) -> list[dict]: + output = subprocess.check_output( + ['dart', 'pub', 'workspace', 'list', '--json'], cwd=root, text=True + ) + return json.loads(output)['packages'] + + +def sources(package: Path, root: Path) -> list[Path]: + if package == root: + candidates = list((package / 'tools').glob('*.dart')) + elif package.name == 'hooks' or package == root / 'tools/readme_sync': + candidates = list(package.glob('*.dart')) + list((package / 'commands').rglob('*.dart')) + else: + candidates = (list(package.glob('*.dart')) + list((package / 'lib').rglob('*.dart')) + + list((package / 'bin').rglob('*.dart')) + + list((package / 'tool').rglob('*.dart'))) + return sorted(path.resolve() for path in candidates) + + +def read_lcov(path: Path, package: Path, owned: set[Path]) -> dict[Path, dict[int, int]]: + result: dict[Path, dict[int, int]] = {} + current = None + for line in path.read_text().splitlines(): + if line.startswith('SF:'): + current = (package / line[3:]).resolve() + if current in owned: + result.setdefault(current, {}) + elif line.startswith('DA:') and current in owned: + number, hits, *_ = line[3:].split(',') + points = result[current] + points[int(number)] = points.get(int(number), 0) + int(hits) + return result + + +def declaration_only(source: str) -> bool: + """Recognize a deliberately narrow set of files with no executable lines. + + This is not an executable-line estimator. Unrecognized syntax fails closed: + it must have a VM coverage record. In particular, constructors, function + bodies, and non-const initializers cannot be excused as declaration-only. + """ + tokens = re.compile(r'''//[^\n]*|/\*.*?\*/|'(?:\\.|[^'\\])*'|"(?:\\.|[^"\\])*"''', re.S) + text = tokens.sub(lambda match: '' if match[0].startswith(('/',)) else "''", source) + text = re.sub(r"\b(?:import|export|part)\s+''[^;]*;", '', text) + text = re.sub(r'\bpart\s+of\s+[\w.]+\s*;', '', text) + text = re.sub(r'\blibrary(?:\s+[\w.]+)?\s*;', '', text) + text = re.sub(r'\btypedef\s+[^;]+;', '', text) + text = re.sub(r'\b(?:static\s+)?const\s+(?:[\w$]+(?:<[\w\s,<>?]+>)?\s+)?[\w$]+\s*=[^;]*;', '', text) + text = re.sub(r'\b(?:(?:abstract|final|base|interface|sealed)\s+)*class\s+\w+\s*\{\s*\}', '', text) + text = re.sub(r'\bextension(?:\s+\w+)?\s+on\s+[\w\s?<>.,]+\{\s*\}', '', text) + text = re.sub(r'\benum\s+\w+\s*\{\s*(?:\w+\s*(?:,\s*\w+\s*)*,?\s*)?\}', '', text) + return not text.strip() + + +def command(args: list[str], package: Path, log, env: dict, timeout_seconds: float = 600) -> bool: + """Bound each command and terminate its children on a timeout or VM crash. + + Flutter's runner can hang while finalizing a crashed coverage isolate. A + process group prevents a stalled compiler or test process surviving a failed + gate, and the log retains the original diagnostic rather than hiding it. + """ + log.write(('\n$ ' + ' '.join(args) + '\n').encode()) + log.flush() + output_offset = log.tell() + process = subprocess.Popen(args, cwd=package, stdout=log, stderr=subprocess.STDOUT, + env=env, start_new_session=True) + + def stop() -> None: + try: + os.killpg(process.pid, signal.SIGTERM) + except ProcessLookupError: + return + try: + process.wait(timeout=2) + except subprocess.TimeoutExpired: + os.killpg(process.pid, signal.SIGKILL) + process.wait() + + deadline = time.monotonic() + timeout_seconds + try: + with Path(log.name).open('rb') as output: + output.seek(output_offset) + tail = b'' + while process.poll() is None: + latest = tail + output.read() + if RUNTIME_CRASH in latest: + log.write(b'\n[coverage-gate] VM crash detected; terminating stalled runner.\n') + log.flush() + stop() + return False + tail = latest[-len(RUNTIME_CRASH):] + if time.monotonic() >= deadline: + log.write(f'\n[coverage-gate] Command timed out after {timeout_seconds:g}s.\n'.encode()) + log.flush() + stop() + return False + try: + process.wait(timeout=0.1) + except subprocess.TimeoutExpired: + pass + return process.returncode == 0 + except BaseException: + stop() + raise + + +def run_package(entry: dict, root: Path, report_only: bool = False) -> dict: + root = root.resolve() + package = Path(entry['path']).resolve() + name = entry['name'] + owned = sources(package, root) + summary = {'package': name, 'path': str(package.relative_to(root)), 'status': 'failed', 'hit': 0, 'lines': 0} + if not owned: + summary['reason'] = 'No production Dart sources discovered' + return summary + fixture = package / 'test/full_coverage_test.dart' + tests = sorted(p for p in (package / 'test').rglob('*_test.dart') if p != fixture) + if not tests: + summary['reason'] = 'No unit tests found' + return summary + coverage = package / 'coverage' + lcov = coverage / 'lcov.info' + if not report_only: + if coverage.exists(): + shutil.rmtree(coverage) + coverage.mkdir() + flutter = bool(re.search(r'^\s+sdk:\s*flutter\s*$', (package / 'pubspec.yaml').read_text(), re.M)) + if flutter: + # Incremental kernels left by earlier SDK/source revisions can + # execute correctly but omit source records from VM coverage. A + # fresh gate needs fresh compilation metadata as well as fresh LCOV. + test_cache = package / 'build/test_cache' + if test_cache.exists(): + shutil.rmtree(test_cache) + previous = fixture.read_bytes() if fixture.exists() else None + imports = [] + for index, source in enumerate(owned): + if re.search(r'^\s*part\s+of\b', source.read_text(), re.M): + continue + relative = (f'package:{name}/' + source.relative_to(package / 'lib').as_posix()) if source.is_relative_to(package / 'lib') else quote(os.path.relpath(source, fixture.parent), safe='/._-') + imports.append(f"import '{relative}' as source{index};") + framework = 'flutter_test/flutter_test.dart' if flutter else 'test/test.dart' + first_test = quote(os.path.relpath(tests[0], fixture.parent), safe='/._-') + imports.append(f"import '{first_test}' as original_tests;") + fixture.write_text('// Generated by tools/coverage.py; removed after the run.\n' + '// ignore_for_file: unused_import, directives_ordering\n' + f"import 'package:{framework}';\n" + '\n'.join(imports) + + ("\nvoid main() { TestWidgetsFlutterBinding.ensureInitialized(); original_tests.main(); }\n" if flutter else "\nvoid main() { original_tests.main(); }\n")) + env = os.environ.copy() + env['CI'] = 'true' + try: + with (coverage / 'test.log').open('wb') as log: + # VM package filters crash in Dart 3.13.4's SourceReport and omit + # file: libraries such as package-root entrypoints. Collect all + # loaded sources, then select owned paths in read_lcov instead. + args = flutter_command(coverage, env) + ['--no-pub', '--concurrency=1', '--coverage', '--reporter', 'expanded'] if flutter else ['dart', 'test', '--coverage=coverage/raw', '--reporter=expanded'] + args += [str(fixture.relative_to(package)), *[str(test.relative_to(package)) for test in tests[1:]]] + if not command(args, package, log, env): + summary['reason'] = f'Tests failed; see {coverage.relative_to(root)}/test.log' + return summary + if not flutter and not command(['dart', 'run', 'coverage:format_coverage', '--lcov', '--in=coverage/raw', '--out=coverage/lcov.info', '--report-on=.'], package, log, env): + summary['reason'] = 'Coverage conversion failed' + return summary + finally: + if previous is None: + fixture.unlink(missing_ok=True) + else: + fixture.write_bytes(previous) + if not lcov.exists(): + summary['reason'] = 'Coverage report missing' + return summary + records = read_lcov(lcov, package, set(owned)) + summary['lines'] = sum(len(lines) for lines in records.values()) + summary['hit'] = sum(hits > 0 for lines in records.values() for hits in lines.values()) + summary['uncovered'] = {str(file.relative_to(package)): [number for number, hits in lines.items() if hits == 0] + for file, lines in records.items() if any(hits == 0 for hits in lines.values())} + summary['sources'] = len(owned) + missing = [str(source.relative_to(package)) for source in owned + if source not in records and not declaration_only(source.read_text())] + if missing: + summary['missing_sources'] = missing + if summary['lines'] == 0: + summary['reason'] = 'Empty coverage report' + elif missing: + summary['reason'] = 'Production sources missing from coverage' + elif summary['hit'] != summary['lines']: + summary['reason'] = 'Uncovered production lines' + else: + summary['status'] = 'passed' + return summary + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--package', action='append', default=[], help='Workspace package name or path (repeatable)') + parser.add_argument('--report-only', action='store_true', help='Inspect existing reports; does not provide fresh verification') + args = parser.parse_args() + packages = discover(ROOT) + if args.package: + unknown = [selection for selection in args.package if not any(p['name'] == selection or str(Path(p['path']).relative_to(ROOT)) == selection for p in packages)] + if unknown: + parser.error('Unknown workspace packages: ' + ', '.join(unknown)) + packages = [p for p in packages if p['name'] in args.package or str(Path(p['path']).relative_to(ROOT)) in args.package] + if not packages: + parser.error('No matching workspace package') + results = [] + for entry in packages: + print(f"Testing {entry['name']}...", flush=True) + result = run_package(entry, ROOT, args.report_only) + results.append(result) + percentage = f"{100 * result['hit'] / result['lines']:.2f}%" if result['lines'] else 'no coverage' + print(f" {result['status'].upper()}: {result['hit']}/{result['lines']} ({percentage}) {result.get('reason', '')}", flush=True) + for source, lines in result.get('uncovered', {}).items(): + print(f" {source}: {', '.join(map(str, lines))}", flush=True) + for source in result.get('missing_sources', []): + print(f" {source}: no VM coverage record", flush=True) + destination = ROOT / 'coverage' + destination.mkdir(exist_ok=True) + report = destination / ('summary-existing.json' if args.report_only else 'summary.json') + report.write_text(json.dumps(results, indent=2) + '\n') + return int(any(result['status'] != 'passed' for result in results)) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/readme_sync/pubspec.yaml b/tools/readme_sync/pubspec.yaml index a4f4b4d..ce5774f 100644 --- a/tools/readme_sync/pubspec.yaml +++ b/tools/readme_sync/pubspec.yaml @@ -4,8 +4,13 @@ version: 1.0.0 publish_to: none environment: - sdk: ^3.0.0 + sdk: '>=3.6.0 <4.0.0' dependencies: yaml: ^3.1.0 path: ^1.8.0 + +dev_dependencies: + test: ^1.31.0 + +resolution: workspace diff --git a/tools/readme_sync/readme_sync.dart b/tools/readme_sync/readme_sync.dart index ac5cb8c..f7356d9 100644 --- a/tools/readme_sync/readme_sync.dart +++ b/tools/readme_sync/readme_sync.dart @@ -1,32 +1,40 @@ #!/usr/bin/env dart +// Command output is intentionally written to stdout. +// ignore_for_file: avoid_print import 'dart:io'; -import 'package:yaml/yaml.dart'; + import 'package:path/path.dart' as path; +import 'package:yaml/yaml.dart'; /// README Sync Tool /// /// Synchronizes standardized sections across all package READMEs. /// Uses HTML comment markers to identify managed sections. -void main(List args) async { - final config = Config.fromArgs(args); +Future main(List args) async { + exitCode = await runReadmeSync(args); +} + +/// Runs the command without terminating the host process. +Future runReadmeSync(List args, {String? scriptDir}) async { + final config = Config.fromArgs(args, scriptDir: scriptDir); if (config.showHelp) { printHelp(); - exit(0); + return 0; } - final syncer = ReadmeSyncer(config); - try { + final syncer = ReadmeSyncer(config); await syncer.run(); + return 0; } catch (e, stackTrace) { print('❌ Error: $e'); if (config.verbose) { print(stackTrace); } - exit(1); + return 1; } } @@ -49,9 +57,8 @@ class Config { required this.scriptDir, }); - factory Config.fromArgs(List args) { + factory Config.fromArgs(List args, {String? scriptDir}) { final scriptPath = Platform.script.toFilePath(); - final scriptDir = path.dirname(scriptPath); return Config( dryRun: args.contains('--dry-run') || args.contains('-d'), @@ -60,7 +67,7 @@ class Config { addMarkers: args.contains('--add-markers'), specificPackage: _extractPackageName(args), showHelp: args.contains('--help') || args.contains('-h'), - scriptDir: scriptDir, + scriptDir: scriptDir ?? path.dirname(scriptPath), ); } @@ -102,12 +109,12 @@ class ReadmeSyncer { // Filter packages if specific package requested var packagesToProcess = packages; if (config.specificPackage != null) { - packagesToProcess = packages - .where((p) => p.name == config.specificPackage) - .toList(); + packagesToProcess = + packages.where((p) => p.name == config.specificPackage).toList(); if (packagesToProcess.isEmpty) { - throw Exception('Package "${config.specificPackage}" not found in configuration'); + throw Exception( + 'Package "${config.specificPackage}" not found in configuration'); } } @@ -137,7 +144,7 @@ class ReadmeSyncer { } } - print('\n' + '━' * 50); + print('\n${'━' * 50}'); if (config.dryRun) { print('🔍 Dry run completed (no files modified)'); } else { @@ -151,7 +158,8 @@ class ReadmeSyncer { Future _loadConfigurations() async { // Load section templates - final sectionsFile = File(path.join(config.scriptDir, 'templates', 'sections.yaml')); + final sectionsFile = + File(path.join(config.scriptDir, 'templates', 'sections.yaml')); if (!sectionsFile.existsSync()) { throw Exception('Templates file not found: ${sectionsFile.path}'); } @@ -166,12 +174,16 @@ class ReadmeSyncer { sections[id] = SectionTemplate( id: id, content: (data['content'] as String).trim(), - variables: (data['variables'] as YamlList?)?.map((e) => e as String).toList() ?? [], + variables: (data['variables'] as YamlList?) + ?.map((e) => e as String) + .toList() ?? + [], ); } // Load package configurations - final packagesFile = File(path.join(config.scriptDir, 'config', 'packages.yaml')); + final packagesFile = + File(path.join(config.scriptDir, 'config', 'packages.yaml')); if (!packagesFile.existsSync()) { throw Exception('Packages config file not found: ${packagesFile.path}'); } @@ -232,7 +244,8 @@ class ReadmeSyncer { final renderedContent = _renderTemplate(template, package); // Replace section content - final newContent = _replaceSectionContent(content, sectionId, renderedContent); + final newContent = + _replaceSectionContent(content, sectionId, renderedContent); if (newContent != content) { content = newContent; @@ -294,20 +307,14 @@ class ReadmeSyncer { return content; } - String _replaceSectionContent(String readme, String sectionId, String newContent) { + String _replaceSectionContent( + String readme, String sectionId, String newContent) { final beginPrefix = ''; final beginIndex = readme.indexOf(beginPrefix); final endIndex = readme.indexOf(endMarker); - if (beginIndex == -1 || endIndex == -1) { - return readme; - } - - // Find the end of the BEGIN marker line (closing -->) - final beginLineEnd = readme.indexOf('-->', beginIndex) + 3; - final before = readme.substring(0, beginIndex) + _beginMarker(sectionId); final after = readme.substring(endIndex); @@ -349,7 +356,7 @@ class ReadmeSyncer { } } - print('\n' + '━' * 50); + print('\n${'━' * 50}'); if (totalIssues == 0) { print('✅ Validation passed: All markers are present'); } else { @@ -358,7 +365,8 @@ class ReadmeSyncer { } } - Future _addMarkersToPackages(List packagesToProcess) async { + Future _addMarkersToPackages( + List packagesToProcess) async { print('➕ Adding markers to READMEs...\n'); for (final package in packagesToProcess) { @@ -372,7 +380,8 @@ class ReadmeSyncer { print('📝 ${package.name}'); print(' ⚠️ Manual intervention required'); - print(' Add markers like: and '); + print( + ' Add markers like: and '); print(' Sections needed: ${package.sections.join(", ")}'); } diff --git a/tools/readme_sync/test/readme_sync_test.dart b/tools/readme_sync/test/readme_sync_test.dart new file mode 100644 index 0000000..ebbb65a --- /dev/null +++ b/tools/readme_sync/test/readme_sync_test.dart @@ -0,0 +1,234 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:path/path.dart' as path; +import 'package:test/test.dart'; +import 'package:yaml/yaml.dart'; + +import '../readme_sync.dart' as cli; + +const markerWarning = + 'auto-generated, do not edit. Run `melos sync:readme` to update'; + +Future> capture(Future Function() action) async { + final messages = []; + await runZoned(action, + zoneSpecification: ZoneSpecification(print: (_, __, ___, message) { + messages.add(message); + })); + return messages; +} + +void main() { + late Directory root; + late String scriptDir; + + Future write(String relative, String content) async { + final file = File(path.join(root.path, relative)); + await file.parent.create(recursive: true); + await file.writeAsString(content); + } + + String packageYaml(String name, {String sections = '[header]'}) => ''' + - name: $name + path: packages/$name + repo_path: packages/$name + import_path: $name/$name.dart + author_name: Test Author + github_username: test-author + sections: $sections +'''; + + Future configure({String? packages}) async { + await write('tools/readme_sync/templates/sections.yaml', ''' +sections: + header: + content: | + # {{package_name}} + {{repo_path}} {{import_path}} {{author_name}} {{github_username}} + variables: [package_name, author_name] + footer: + content: Footer +'''); + await write('tools/readme_sync/config/packages.yaml', + packages ?? 'packages:\n${packageYaml('demo')}'); + } + + Future run(List args) => + cli.runReadmeSync(args, scriptDir: scriptDir); + + setUp(() async { + root = await Directory.systemTemp.createTemp('readme_sync_test_'); + scriptDir = path.join(root.path, 'tools/readme_sync'); + await Directory(path.join(root.path, '.git')).create(); + await Directory(scriptDir).create(recursive: true); + }); + + tearDown(() async { + await root.delete(recursive: true); + }); + + test('flags, aliases, missing values, and default script directory parse', + () { + final flags = cli.Config.fromArgs([ + '--dry-run', + '--verbose', + '--validate', + '--add-markers', + '--package', + 'demo', + '--help', + ], scriptDir: scriptDir); + expect(flags.dryRun, isTrue); + expect(flags.verbose, isTrue); + expect(flags.validate, isTrue); + expect(flags.addMarkers, isTrue); + expect(flags.specificPackage, 'demo'); + expect(flags.showHelp, isTrue); + expect(flags.scriptDir, scriptDir); + final short = cli.Config.fromArgs(['-d', '-v', '-h']); + expect(short.dryRun && short.verbose && short.showHelp, isTrue); + expect(short.scriptDir, path.dirname(Platform.script.toFilePath())); + expect(cli.Config.fromArgs(['--package']).specificPackage, isNull); + expect(cli.Config.fromArgs([]).validate, isFalse); + }); + + test('entry point prints help without touching repository content', () async { + final oldExitCode = exitCode; + addTearDown(() => exitCode = oldExitCode); + final messages = await capture(() => cli.main(['--help'])); + expect(exitCode, 0); + expect(messages.single, contains('README Sync Tool')); + expect(messages.single, contains('--dry-run')); + }); + + test('missing repository and configuration failures report a failing status', + () async { + await Directory(path.join(root.path, '.git')).delete(); + var messages = await capture(() async => expect(await run([]), 1)); + expect(messages.single, contains('Could not find git repository root')); + await Directory(path.join(root.path, '.git')).create(); + messages = await capture(() async => expect(await run(['--verbose']), 1)); + expect(messages.join('\n'), contains('Templates file not found')); + expect(messages.last, contains('_loadConfigurations')); + await configure(); + await File(path.join(scriptDir, 'config/packages.yaml')).delete(); + messages = await capture(() async => expect(await run([]), 1)); + expect(messages.join('\n'), contains('Packages config file not found')); + }); + + test('sync renders variables, preserves unmanaged text, and is idempotent', + () async { + await configure(); + await write('packages/demo/README.md', + 'Before\n\nstale\n\nAfter\n'); + final file = File(path.join(root.path, 'packages/demo/README.md')); + var messages = await capture(() async => expect(await run(['-v']), 0)); + final expected = 'Before\n\n' + '# demo\npackages/demo demo/demo.dart Test Author test-author\n' + '\nAfter\n'; + expect(await file.readAsString(), expected); + expect(File('${file.path}.bak').existsSync(), isFalse); + expect(messages.join('\n'), contains('Loaded 2 section templates')); + expect(messages.join('\n'), contains('Updated: header')); + messages = await capture(() async => expect(await run([]), 0)); + expect(await file.readAsString(), expected); + expect(messages.join('\n'), contains('No changes needed')); + }); + + test('dry run previews changes without creating a backup or editing files', + () async { + await configure(); + const original = 'old'; + await write('packages/demo/README.md', original); + final messages = await capture(() async => expect(await run(['-d']), 0)); + final file = File(path.join(root.path, 'packages/demo/README.md')); + expect(await file.readAsString(), original); + expect(File('${file.path}.bak').existsSync(), isFalse); + expect(messages.join('\n'), contains('Would update README')); + expect(messages.join('\n'), contains('Dry run completed')); + }); + + test('package filtering includes plugins and rejects unknown names', + () async { + await configure( + packages: 'packages:\n${packageYaml('demo')}' + 'plugins:\n${packageYaml('plugin')}'); + await write('packages/plugin/README.md', + 'old'); + var messages = await capture( + () async => expect(await run(['--package', 'plugin']), 0)); + expect(messages.join('\n'), contains('Processing 1 package')); + expect(messages.join('\n'), isNot(contains('README.md not found'))); + expect( + await File(path.join(root.path, 'packages/plugin/README.md')) + .readAsString(), + contains('# plugin')); + messages = await capture( + () async => expect(await run(['--package', 'unknown']), 1)); + expect(messages.last, contains('Package "unknown" not found')); + }); + + test('sync reports missing files, missing markers, and unknown templates', + () async { + await configure( + packages: 'packages:\n${packageYaml('missing')}' + '${packageYaml('demo', sections: '[header, unknown]')}'); + await write('packages/demo/README.md', 'Unmanaged content'); + final messages = await capture(() async => expect(await run([]), 0)); + expect(messages.join('\n'), contains('Error processing missing')); + expect(messages.join('\n'), contains('Missing markers for: header')); + expect(messages.join('\n'), contains('Unknown section: unknown')); + expect(messages.join('\n'), contains('Errors: 1')); + expect( + await File(path.join(root.path, 'packages/demo/README.md')) + .readAsString(), + 'Unmanaged content'); + }); + + test('validation reports missing files and incomplete marker pairs', + () async { + await configure( + packages: 'packages:\n${packageYaml('missing')}' + '${packageYaml('demo', sections: '[header, footer]')}'); + await write('packages/demo/README.md', ''); + final messages = + await capture(() async => expect(await run(['--validate']), 0)); + expect(messages.join('\n'), contains('missing: README.md not found')); + expect(messages.join('\n'), contains('Missing markers for: header')); + expect(messages.join('\n'), contains('Missing markers for: footer')); + expect(messages.join('\n'), contains('Validation found 3 issue(s)')); + }); + + test('validation accepts managed sections and add-markers provides guidance', + () async { + await configure( + packages: 'plugins:\n${packageYaml('demo')}${packageYaml('missing')}'); + await write('packages/demo/README.md', + 'old'); + var messages = await capture( + () async => expect(await run(['--validate', '--package', 'demo']), 0)); + expect(messages.join('\n'), contains('demo: All markers present')); + expect(messages.join('\n'), contains('Validation passed')); + messages = + await capture(() async => expect(await run(['--add-markers']), 0)); + expect(messages.join('\n'), contains('Manual intervention required')); + expect(messages.join('\n'), contains('Sections needed: header')); + expect(messages.join('\n'), contains('missing: README.md not found')); + expect(messages.join('\n'), contains('')); + }); + + test('package configuration resolves paths and preserves metadata', () { + final yaml = (loadYaml('packages:\n${packageYaml('demo')}') + as YamlMap)['packages'] as YamlList; + final config = cli.PackageConfig.fromYaml(yaml.first as YamlMap, root.path); + expect(config.name, 'demo'); + expect(config.relativePath, 'packages/demo'); + expect(config.fullPath, path.join(root.path, 'packages/demo')); + expect(config.repoPath, 'packages/demo'); + expect(config.importPath, 'demo/demo.dart'); + expect(config.authorName, 'Test Author'); + expect(config.githubUsername, 'test-author'); + expect(config.sections, ['header']); + }); +} diff --git a/tools/tests/test_coverage.py b/tools/tests/test_coverage.py new file mode 100644 index 0000000..7700bdb --- /dev/null +++ b/tools/tests/test_coverage.py @@ -0,0 +1,158 @@ +import importlib.util +import os +from pathlib import Path +import sys +import tempfile +import time +import unittest +from unittest.mock import patch + +spec = importlib.util.spec_from_file_location('coverage_gate', Path(__file__).resolve().parents[1] / 'coverage.py') +gate = importlib.util.module_from_spec(spec) +spec.loader.exec_module(gate) + + +class CoverageGateTest(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory() + self.root = Path(self.temp.name).resolve() + self.package = self.root / 'packages/example' + (self.package / 'lib').mkdir(parents=True) + (self.package / 'lib/example.dart').write_text('int value() => 1;\n') + (self.package / 'test').mkdir() + (self.package / 'test/example_test.dart').write_text('void main() {}\n') + (self.package / 'coverage').mkdir() + self.entry = {'name': 'example', 'path': str(self.package)} + + def tearDown(self): + self.temp.cleanup() + + def report(self, text): + (self.package / 'coverage/lcov.info').write_text(text) + return gate.run_package(self.entry, self.root, report_only=True) + + def test_uncovered_line_fails_even_when_percentage_would_round_to_100(self): + text = 'SF:lib/example.dart\n' + ''.join(f'DA:{n},1\n' for n in range(1, 10001)) + 'DA:10001,0\nend_of_record\n' + result = self.report(text) + self.assertEqual(result['status'], 'failed') + self.assertEqual(result['uncovered'], {'lib/example.dart': [10001]}) + + def test_missing_tests_missing_report_and_empty_report_fail(self): + self.assertEqual(gate.run_package(self.entry, self.root, True)['reason'], 'Coverage report missing') + self.assertEqual(self.report('')['reason'], 'Empty coverage report') + (self.package / 'test/example_test.dart').unlink() + self.assertEqual(self.report('SF:lib/example.dart\nDA:1,1\n')['reason'], 'No unit tests found') + + def test_only_owned_sources_count_and_duplicate_records_merge(self): + result = self.report('SF:lib/example.dart\nDA:1,0\nend_of_record\nSF:lib/example.dart\nDA:1,2\nend_of_record\nSF:test/example_test.dart\nDA:1,0\nend_of_record\n') + self.assertEqual((result['status'], result['hit'], result['lines']), ('passed', 1, 1)) + + def test_missing_executable_library_cannot_silently_reduce_denominator(self): + (self.package / 'lib/missing.dart').write_text('int missing() => 1;') + result = self.report('SF:lib/example.dart\nDA:1,1\nend_of_record\n') + self.assertEqual(result['status'], 'failed') + self.assertEqual(result['missing_sources'], ['lib/missing.dart']) + + def test_only_verified_declarations_can_be_absent_from_vm_coverage(self): + declarations = """// Documentation +library constants; +import 'package:foo/foo.dart'; +export 'foo.dart' show Foo; +const Map values = {}; +class Keys { static const String name = 'name'; } +enum Mode { first, second, } +extension Empty on Function {} +""" + self.assertTrue(gate.declaration_only(declarations)) + for executable in ['void work() {}', 'int work() => 1;', 'final value = 1;', + 'class Value { const Value({this.number = 1}); final int number; }']: + self.assertFalse(gate.declaration_only(executable), executable) + (self.package / 'lib/constants.dart').write_text(declarations) + self.assertEqual(self.report('SF:lib/example.dart\nDA:1,1\nend_of_record\n')['status'], 'passed') + + def test_failed_test_run_restores_preexisting_generated_fixture(self): + (self.package / 'pubspec.yaml').write_text('name: example\n') + fixture = self.package / 'test/full_coverage_test.dart' + fixture.write_text('original contents') + with patch.object(gate, 'command', return_value=False) as run: + result = gate.run_package(self.entry, self.root) + self.assertEqual(result['status'], 'failed') + self.assertIn('Tests failed', result['reason']) + self.assertEqual(fixture.read_text(), 'original contents') + self.assertIn('test/full_coverage_test.dart', run.call_args.args[0]) + self.assertNotIn('test/example_test.dart', run.call_args.args[0]) + + def test_hooks_include_commands_but_not_tests_or_nested_templates(self): + hooks = self.root / 'hooks' + (hooks / 'commands').mkdir(parents=True) + (hooks / 'test').mkdir() + for relative in ['pre_gen.dart', 'commands/create.dart', 'test/example_test.dart']: + (hooks / relative).write_text('void main() {}') + self.assertEqual([p.relative_to(hooks).as_posix() for p in gate.sources(hooks, self.root)], ['commands/create.dart', 'pre_gen.dart']) + + def test_package_root_entrypoints_are_owned(self): + (self.package / 'main.dart').write_text('void main() {}') + (self.package / 'tool').mkdir() + (self.package / 'tool/verify.dart').write_text('void main() {}') + self.assertEqual([p.relative_to(self.package).as_posix() for p in gate.sources(self.package, self.root)], ['lib/example.dart', 'main.dart', 'tool/verify.dart']) + + def test_fixture_imports_all_sources_and_calls_real_tests(self): + (self.package / 'pubspec.yaml').write_text('name: example\ndependencies:\n flutter:\n sdk: flutter\n') + (self.package / 'lib/unused.dart').write_text('int unused() => 2;') + (self.package / 'main.dart').write_text('void main() {}') + cache = self.package / 'build/test_cache/stale.dill' + cache.parent.mkdir(parents=True) + cache.write_text('old kernel') + other_build = self.package / 'build/keep.txt' + other_build.write_text('unrelated artifact') + def inspect(args, package, log, env): + self.assertFalse(cache.exists()) + self.assertEqual(other_build.read_text(), 'unrelated artifact') + fixture = (package / 'test/full_coverage_test.dart').read_text() + self.assertIn("import 'package:example/unused.dart'", fixture) + self.assertIn("import '../main.dart'", fixture) + self.assertIn('original_tests.main()', fixture) + self.assertNotIn('--coverage-package=^$', args) + (package / 'coverage/lcov.info').write_text('SF:lib/example.dart\nDA:1,1\nend_of_record\nSF:lib/unused.dart\nDA:1,0\nend_of_record\nSF:main.dart\nDA:1,1\nend_of_record\n') + return True + with patch.object(gate, 'command', side_effect=inspect), patch.object(gate, 'flutter_command', return_value=['flutter', 'test']): + result = gate.run_package(self.entry, self.root) + self.assertEqual(result['status'], 'failed') + self.assertEqual(result['uncovered'], {'lib/unused.dart': [1]}) + self.assertFalse((self.package / 'test/full_coverage_test.dart').exists()) + + def test_flutter_driver_uses_installed_sdk_without_changing_it(self): + sdk = self.root / 'sdk' + config = sdk / 'packages/flutter_tools/.dart_tool/package_config.json' + config.parent.mkdir(parents=True) + config.write_text('{}') + env = {'FLUTTER_ROOT': str(sdk)} + with patch.object(gate.shutil, 'which', return_value=str(sdk / 'bin/flutter')): + args = gate.flutter_command(self.package / 'coverage', env) + self.assertEqual(args[0], str(sdk / 'bin/cache/dart-sdk/bin/dart')) + self.assertEqual(args[1], f'--packages={config}') + driver = Path(args[2]).read_text() + self.assertIn('watcher.libraryNames = null;', driver) + self.assertIn('Function.apply(const FlutterTestRunner().runTests', driver) + self.assertEqual(config.read_text(), '{}') + + def test_command_timeout_returns_failure_and_writes_diagnostic(self): + with (self.package / 'timeout.log').open('wb') as log: + started = time.monotonic() + result = gate.command([sys.executable, '-c', 'import time; time.sleep(30)'], self.package, log, os.environ.copy(), timeout_seconds=0.1) + self.assertFalse(result) + self.assertLess(time.monotonic() - started, 3) + self.assertIn('timed out', (self.package / 'timeout.log').read_text()) + + def test_command_detects_runtime_crash_without_waiting_for_timeout(self): + script = "import time; print('Shell subprocess crashed with segmentation fault', flush=True); time.sleep(30)" + with (self.package / 'crash.log').open('wb') as log: + started = time.monotonic() + result = gate.command([sys.executable, '-c', script], self.package, log, os.environ.copy(), timeout_seconds=10) + self.assertFalse(result) + self.assertLess(time.monotonic() - started, 3) + self.assertIn('VM crash detected', (self.package / 'crash.log').read_text()) + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/update_nonstop_cli_bundles.dart b/tools/update_nonstop_cli_bundles.dart index f089930..3ac474c 100644 --- a/tools/update_nonstop_cli_bundles.dart +++ b/tools/update_nonstop_cli_bundles.dart @@ -1,80 +1,96 @@ import 'dart:io'; import 'package:mason_logger/mason_logger.dart'; +import 'package:path/path.dart' as path; const _cliDirectory = 'packages/nonstop_cli'; final bundlePaths = [ - _GenBundlePath( + BundlePath( input: '$_cliDirectory/bricks/flutter_project_with_mono_repo', output: '$_cliDirectory/lib/commands/create', fileName: 'flutter_project_with_mono_repo_bundle.dart', ), - _GenBundlePath( + BundlePath( input: '$_cliDirectory/bricks/flutter_package_for_mono_repo', output: '$_cliDirectory/lib/commands/create', fileName: 'flutter_package_for_mono_repo_bundle.dart', ), - _GenBundlePath( + BundlePath( input: '$_cliDirectory/bricks/flutter_app_for_mono_repo', output: '$_cliDirectory/lib/commands/create', fileName: 'flutter_app_for_mono_repo_bundle.dart', ), - _GenBundlePath( + BundlePath( input: '$_cliDirectory/bricks/flutter_plugin_for_mono_repo', output: '$_cliDirectory/lib/commands/create', fileName: 'flutter_plugin_for_mono_repo_bundle.dart', ), ]; -class _GenBundlePath { +class BundlePath { final String input; final String output; final String fileName; - _GenBundlePath({ + BundlePath({ required this.input, required this.output, required this.fileName, }); } -void main() async { - final logger = Logger(); +typedef ProcessRunner = + Future Function(String executable, List arguments); + +Future main({ + List? bundles, + ProcessRunner runProcess = Process.run, + Logger? logger, +}) async { + logger ??= Logger(); + exitCode = 0; try { logger.info('Activating mason_cli'); - await Process.run('dart', ['pub', 'global', 'activate', 'mason_cli']); + await runProcess('dart', ['pub', 'global', 'activate', 'mason_cli']); int updateCount = 0; - for (final bundle in bundlePaths) { + for (final bundle in bundles ?? bundlePaths) { logger.info('Bundling ${bundle.fileName}'); - // Bundle the brick - final bundleResult = await Process.run( - 'mason', - ['bundle', bundle.input, '-t', 'dart', '-o', bundle.output], - ); + // Workspace membership is local to this repository. Bundle an isolated + // copy so generated projects resolve their hook dependencies standalone. + final staging = await stageStandaloneBrick(Directory(bundle.input)); + late final ProcessResult bundleResult; + try { + bundleResult = await runProcess('mason', [ + 'bundle', + staging.path, + '-t', + 'dart', + '-o', + bundle.output, + ]); + } finally { + await staging.parent.delete(recursive: true); + } if (bundleResult.exitCode != 0) { throw Exception('Failed to bundle ${bundle.fileName}'); } // Format the generated file final fullPath = '${bundle.output}/${bundle.fileName}'; - await Process.run('dart', ['format', fullPath]); + await runProcess('dart', ['format', fullPath]); // Check if file is modified - final isModified = - await Process.run('git', ['diff', '--quiet', fullPath]); + final isModified = await runProcess('git', ['diff', '--quiet', fullPath]); if (isModified.exitCode == 1) { logger.info('Committing changes to ${bundle.fileName}'); - await Process.run('git', ['add', fullPath]); - await Process.run( - 'git', - [ - 'commit', - '-m', - 'chore(nonstop_cli): update ${bundle.fileName} bundle' - ], - ); + await runProcess('git', ['add', fullPath]); + await runProcess('git', [ + 'commit', + '-m', + 'chore(nonstop_cli): update ${bundle.fileName} bundle', + ]); updateCount++; logger.info('Successfully updated bundles for ${bundle.fileName}'); } else { @@ -91,6 +107,59 @@ void main() async { } } catch (e) { logger.err('An error occurred while updating bundles $e'); - exit(1); + exitCode = 1; + } +} + +/// Copies a brick into a temporary directory for standalone packaging. +Future stageStandaloneBrick(Directory source) async { + final temporary = await Directory.systemTemp.createTemp('nonstop_bundle_'); + final staged = Directory( + path.join(temporary.path, path.basename(source.path)), + ); + try { + await staged.create(); + await for (final entry in source.list( + recursive: true, + followLinks: false, + )) { + final relative = path.relative(entry.path, from: source.path); + final segments = path.split(relative); + if ((segments.length >= 2 && + segments[0] == 'hooks' && + segments[1] == 'test') || + segments.any( + (segment) => + segment == '.dart_tool' || + segment == 'coverage' || + segment == 'build', + )) { + continue; + } + final destination = path.join(staged.path, relative); + if (entry is Directory) { + await Directory(destination).create(recursive: true); + } else if (entry is File) { + final file = File(destination); + await file.parent.create(recursive: true); + if (relative == path.join('hooks', 'pubspec.yaml')) { + final content = await entry.readAsString(); + await file.writeAsString( + content.replaceAll( + RegExp(r'^resolution:\s*workspace\s*$', multiLine: true), + '', + ), + ); + } else { + await entry.copy(destination); + } + } else if (entry is Link) { + await Link(destination).create(await entry.target()); + } + } + return staged; + } catch (_) { + await temporary.delete(recursive: true); + rethrow; } } diff --git a/tools/update_nonstop_cli_version.dart b/tools/update_nonstop_cli_version.dart index bb37f85..68acd66 100644 --- a/tools/update_nonstop_cli_version.dart +++ b/tools/update_nonstop_cli_version.dart @@ -5,13 +5,21 @@ import 'package:pubspec_parse/pubspec_parse.dart'; const _cliDirectory = 'packages/nonstop_cli'; -void main() async { - final logger = Logger(); +typedef ProcessRunner = + Future Function(String executable, List arguments); + +Future main({ + String cliDirectory = _cliDirectory, + ProcessRunner runProcess = Process.run, + Logger? logger, +}) async { + logger ??= Logger(); + exitCode = 0; logger.info('Starting version update process'); try { // Read the pubspec.yaml file - final pubspecFile = File('$_cliDirectory/pubspec.yaml'); + final pubspecFile = File('$cliDirectory/pubspec.yaml'); logger.info('Reading pubspec.yaml'); final pubspecContent = await pubspecFile.readAsString(); final pubspec = Pubspec.parse(pubspecContent); @@ -21,8 +29,8 @@ void main() async { logger.info('Current version: $version'); // Update the version.dart file - final versionFile = File('$_cliDirectory/lib/version.dart'); - logger.info('Updating $_cliDirectory/lib/version.dart'); + final versionFile = File('$cliDirectory/lib/version.dart'); + logger.info('Updating $cliDirectory/lib/version.dart'); await versionFile.writeAsString(''' // Generated code. Do not modify. // It is generated by the melos-pre-commit-version hook. @@ -30,22 +38,25 @@ void main() async { const packageVersion = '$version'; '''); -// Check if the file is modified - final isModified = - await Process.run('git', ['diff', '--quiet', versionFile.path]); + // Check if the file is modified + final isModified = await runProcess('git', [ + 'diff', + '--quiet', + versionFile.path, + ]); if (isModified.exitCode == 1) { logger.info('version.dart has been modified'); - await Process.run('git', ['add', versionFile.path]); + await runProcess('git', ['add', versionFile.path]); logger.info('${versionFile.path} has been staged'); logger.success( 'Successfully updated version.dart with ' - 'version $version in $_cliDirectory', + 'version $version in $cliDirectory', ); return; } logger.success('No changes detected'); } catch (e) { logger.err('An error occurred while updating the version $e'); - exit(1); + exitCode = 1; } }