flutter-setup 2.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,814 @@
1
+ """CI/CD code generation for Flutter projects."""
2
+
3
+ import re
4
+ import subprocess
5
+ from pathlib import Path
6
+
7
+ from rich.console import Console
8
+
9
+ from .config import Config
10
+
11
+ console = Console()
12
+
13
+
14
+ class CicdGenerator:
15
+ """Generates CI/CD workflows and configuration for Flutter projects."""
16
+
17
+ def __init__(self, config: Config) -> None:
18
+ """Initialize CicdGenerator."""
19
+ self.config = config
20
+ self.project_path = config.project_path
21
+ self.project_name = config.project_name
22
+ self.package_name = config.package_name
23
+ self.platforms = [p.lower() for p in config.platforms]
24
+ self.flutter_channel = config.channel
25
+ self.flutter_version = self._get_current_flutter_version()
26
+
27
+ def _get_current_flutter_version(self) -> str:
28
+ """Get the current Flutter version from the installed Flutter SDK."""
29
+ try:
30
+ flutter_bin = self.config.flutter_location / "bin" / "flutter"
31
+ if not flutter_bin.exists():
32
+ # Fallback: try to find flutter in PATH
33
+ import shutil
34
+
35
+ flutter_path = shutil.which("flutter")
36
+ if not flutter_path:
37
+ console.print(
38
+ " ⚠️ Flutter not found, using 'stable' as default version"
39
+ )
40
+ return "stable"
41
+ flutter_bin = Path(flutter_path)
42
+
43
+ # Run flutter --version to get the version
44
+ result = subprocess.run(
45
+ [str(flutter_bin), "--version"],
46
+ capture_output=True,
47
+ text=True,
48
+ check=False,
49
+ )
50
+
51
+ # Parse version from output
52
+ # Output format: "Flutter 3.24.0 • channel stable • ..."
53
+ output = result.stdout or result.stderr or ""
54
+ match = re.search(r"Flutter\s+([\d.]+)", output)
55
+ if match:
56
+ version = match.group(1)
57
+ console.print(f" ℹ️ Detected Flutter version: {version}")
58
+ return version
59
+ else:
60
+ console.print(
61
+ " ⚠️ Could not parse Flutter version, using '3.38.0' as default"
62
+ )
63
+ return "3.38.0"
64
+ except Exception as e:
65
+ console.print(
66
+ f" ⚠️ Error detecting Flutter version: {e}, using '3.38.0' as default"
67
+ )
68
+ return "3.38.0"
69
+
70
+ def generate_cicd(self) -> None:
71
+ """Generate all CI/CD files."""
72
+ if self.config.dry_run:
73
+ console.print("[yellow]DRY RUN: Would generate CI/CD files[/yellow]")
74
+ return
75
+
76
+ console.print(" 🔧 Generating CI/CD workflows and configuration...")
77
+
78
+ # Create .github directory structure
79
+ github_dir = self.project_path / ".github"
80
+ workflows_dir = github_dir / "workflows"
81
+ workflows_dir.mkdir(parents=True, exist_ok=True)
82
+
83
+ # Generate workflows
84
+ self._generate_lint_workflow(workflows_dir)
85
+ self._generate_format_workflow(workflows_dir)
86
+ self._generate_test_workflow(workflows_dir)
87
+ self._generate_build_workflows(workflows_dir)
88
+
89
+ # Generate dependabot configuration
90
+ self._generate_dependabot(github_dir)
91
+
92
+ # Generate setup documentation
93
+ self._generate_setup_docs(github_dir)
94
+
95
+ console.print(" ✅ CI/CD workflows and configuration generated")
96
+
97
+ def _generate_lint_workflow(self, workflows_dir: Path) -> None:
98
+ """Generate lint workflow for flutter analyze."""
99
+ workflow_content = f"""name: Lint
100
+
101
+ on:
102
+ pull_request:
103
+ types: [opened, synchronize, reopened, ready_for_review]
104
+ push:
105
+ branches: ["main"]
106
+
107
+ concurrency:
108
+ group: lint-${{{{ github.ref }}}}
109
+ cancel-in-progress: true
110
+
111
+ jobs:
112
+ lint:
113
+ if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
114
+ runs-on: ubuntu-latest
115
+ steps:
116
+ - name: Checkout
117
+ uses: actions/checkout@v6
118
+
119
+ - name: Setup Flutter
120
+ uses: subosito/flutter-action@v2
121
+ with:
122
+ channel: ${{{{ secrets.FLUTTER_CHANNEL || 'stable' }}}}
123
+ version: ${{{{ secrets.FLUTTER_VERSION || '{self.flutter_version}' }}}}
124
+
125
+ - name: Cache pub
126
+ uses: actions/cache@v4
127
+ with:
128
+ path: ~/.pub-cache
129
+ key: pub-${{{{ runner.os }}}}-${{{{ hashFiles('**/pubspec.lock') }}}}
130
+
131
+ - name: Install dependencies
132
+ run: flutter pub get
133
+
134
+ - name: Analyze
135
+ run: flutter analyze
136
+
137
+ - name: Comment PR with results
138
+ if: github.event_name == 'pull_request' && failure()
139
+ uses: actions/github-script@v8
140
+ with:
141
+ script: |
142
+ github.rest.issues.createComment({{
143
+ issue_number: context.issue.number,
144
+ owner: context.repo.owner,
145
+ repo: context.repo.repo,
146
+ body: '❌ Linting failed. Please run `flutter analyze` locally and fix the issues.'
147
+ }})
148
+ """
149
+
150
+ workflow_file = workflows_dir / "lint.yml"
151
+ workflow_file.write_text(workflow_content)
152
+
153
+ def _generate_format_workflow(self, workflows_dir: Path) -> None:
154
+ """Generate format workflow for dart format."""
155
+ workflow_content = f"""name: Format
156
+
157
+ on:
158
+ pull_request:
159
+ types: [opened, synchronize, reopened, ready_for_review]
160
+ push:
161
+ branches: ["main"]
162
+
163
+ concurrency:
164
+ group: format-${{{{ github.ref }}}}
165
+ cancel-in-progress: true
166
+
167
+ jobs:
168
+ format:
169
+ if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
170
+ runs-on: ubuntu-latest
171
+ steps:
172
+ - name: Checkout
173
+ uses: actions/checkout@v6
174
+
175
+ - name: Setup Flutter
176
+ uses: subosito/flutter-action@v2
177
+ with:
178
+ channel: ${{{{ secrets.FLUTTER_CHANNEL || 'stable' }}}}
179
+ version: ${{{{ secrets.FLUTTER_VERSION || '{self.flutter_version}' }}}}
180
+
181
+ - name: Cache pub
182
+ uses: actions/cache@v4
183
+ with:
184
+ path: ~/.pub-cache
185
+ key: pub-${{{{ runner.os }}}}-${{{{ hashFiles('**/pubspec.lock') }}}}
186
+
187
+ - name: Install dependencies
188
+ run: flutter pub get
189
+
190
+ - name: Check formatting
191
+ run: dart format --set-exit-if-changed .
192
+
193
+ - name: Comment PR with results
194
+ if: github.event_name == 'pull_request' && failure()
195
+ uses: actions/github-script@v8
196
+ with:
197
+ script: |
198
+ github.rest.issues.createComment({{
199
+ issue_number: context.issue.number,
200
+ owner: context.repo.owner,
201
+ repo: context.repo.repo,
202
+ body: '❌ Code formatting check failed. Please run `dart format .` locally and commit the changes.'
203
+ }})
204
+ """
205
+
206
+ workflow_file = workflows_dir / "format.yml"
207
+ workflow_file.write_text(workflow_content)
208
+
209
+ def _generate_test_workflow(self, workflows_dir: Path) -> None:
210
+ """Generate test workflow."""
211
+ workflow_content = f"""name: Test
212
+
213
+ on:
214
+ pull_request:
215
+ types: [opened, synchronize, reopened, ready_for_review]
216
+ push:
217
+ branches: ["main"]
218
+
219
+ concurrency:
220
+ group: test-${{{{ github.ref }}}}
221
+ cancel-in-progress: true
222
+
223
+ jobs:
224
+ test:
225
+ if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
226
+ runs-on: ubuntu-latest
227
+ steps:
228
+ - name: Checkout
229
+ uses: actions/checkout@v6
230
+
231
+ - name: Setup Flutter
232
+ uses: subosito/flutter-action@v2
233
+ with:
234
+ channel: ${{{{ secrets.FLUTTER_CHANNEL || 'stable' }}}}
235
+ version: ${{{{ secrets.FLUTTER_VERSION || '{self.flutter_version}' }}}}
236
+
237
+ - name: Cache pub
238
+ uses: actions/cache@v4
239
+ with:
240
+ path: ~/.pub-cache
241
+ key: pub-${{{{ runner.os }}}}-${{{{ hashFiles('**/pubspec.lock') }}}}
242
+
243
+ - name: Install dependencies
244
+ run: flutter pub get
245
+
246
+ - name: Run unit tests
247
+ run: flutter test
248
+
249
+ - name: Upload coverage
250
+ if: github.event_name == 'pull_request'
251
+ uses: codecov/codecov-action@v5
252
+ with:
253
+ token: ${{ secrets.CODECOV_TOKEN }}
254
+ files: ./coverage/lcov.info
255
+ fail_ci_if_error: false
256
+ """
257
+
258
+ workflow_file = workflows_dir / "test.yml"
259
+ workflow_file.write_text(workflow_content)
260
+
261
+ def _generate_build_workflows(self, workflows_dir: Path) -> None:
262
+ """Generate build workflows for enabled platforms."""
263
+ if "ios" in self.platforms:
264
+ self._generate_ios_build_workflow(workflows_dir)
265
+ if "android" in self.platforms:
266
+ self._generate_android_build_workflow(workflows_dir)
267
+ if "web" in self.platforms:
268
+ self._generate_web_build_workflow(workflows_dir)
269
+ if "macos" in self.platforms:
270
+ self._generate_macos_build_workflow(workflows_dir)
271
+ if "linux" in self.platforms:
272
+ self._generate_linux_build_workflow(workflows_dir)
273
+ if "windows" in self.platforms:
274
+ self._generate_windows_build_workflow(workflows_dir)
275
+
276
+ def _generate_ios_build_workflow(self, workflows_dir: Path) -> None:
277
+ """Generate iOS build workflow."""
278
+ workflow_content = f"""name: Build iOS
279
+
280
+ on:
281
+ workflow_dispatch:
282
+ push:
283
+ tags:
284
+ - 'v*.*.*'
285
+ branches:
286
+ - main
287
+
288
+ jobs:
289
+ build-ios:
290
+ runs-on: macos-latest
291
+ steps:
292
+ - name: Checkout
293
+ uses: actions/checkout@v6
294
+
295
+ - name: Setup Flutter
296
+ uses: subosito/flutter-action@v2
297
+ with:
298
+ channel: ${{{{ secrets.FLUTTER_CHANNEL || 'stable' }}}}
299
+ version: ${{{{ secrets.FLUTTER_VERSION || '{self.flutter_version}' }}}}
300
+
301
+ - name: Cache pub
302
+ uses: actions/cache@v4
303
+ with:
304
+ path: ~/.pub-cache
305
+ key: pub-${{{{ runner.os }}}}-${{{{ hashFiles('**/pubspec.lock') }}}}
306
+
307
+ - name: Install dependencies
308
+ run: flutter pub get
309
+
310
+ - name: Build iOS
311
+ run: flutter build ios --release --no-codesign
312
+
313
+ - name: Upload artifacts
314
+ uses: actions/upload-artifact@v5
315
+ with:
316
+ name: ios-build
317
+ path: build/ios/iphoneos/Runner.app
318
+ if-no-files-found: ignore
319
+ """
320
+
321
+ workflow_file = workflows_dir / "build-ios.yml"
322
+ workflow_file.write_text(workflow_content)
323
+
324
+ def _generate_android_build_workflow(self, workflows_dir: Path) -> None:
325
+ """Generate Android build workflow."""
326
+ workflow_content = f"""name: Build Android
327
+
328
+ on:
329
+ workflow_dispatch:
330
+ push:
331
+ tags:
332
+ - 'v*.*.*'
333
+ branches:
334
+ - main
335
+
336
+ jobs:
337
+ build-android:
338
+ runs-on: ubuntu-latest
339
+ steps:
340
+ - name: Checkout
341
+ uses: actions/checkout@v6
342
+
343
+ - name: Setup Java
344
+ uses: actions/setup-java@v4
345
+ with:
346
+ distribution: 'temurin'
347
+ java-version: '17'
348
+
349
+ - name: Setup Flutter
350
+ uses: subosito/flutter-action@v2
351
+ with:
352
+ channel: ${{{{ secrets.FLUTTER_CHANNEL || 'stable' }}}}
353
+ version: ${{{{ secrets.FLUTTER_VERSION || '{self.flutter_version}' }}}}
354
+
355
+ - name: Cache pub
356
+ uses: actions/cache@v4
357
+ with:
358
+ path: ~/.pub-cache
359
+ key: pub-${{{{ runner.os }}}}-${{{{ hashFiles('**/pubspec.lock') }}}}
360
+
361
+ - name: Install dependencies
362
+ run: flutter pub get
363
+
364
+ - name: Build APK
365
+ run: flutter build apk --release
366
+
367
+ - name: Build AAB
368
+ run: flutter build appbundle --release
369
+
370
+ - name: Upload APK
371
+ uses: actions/upload-artifact@v5
372
+ with:
373
+ name: android-apk
374
+ path: build/app/outputs/flutter-apk/app-release.apk
375
+ if-no-files-found: ignore
376
+
377
+ - name: Upload AAB
378
+ uses: actions/upload-artifact@v5
379
+ with:
380
+ name: android-aab
381
+ path: build/app/outputs/bundle/release/app-release.aab
382
+ if-no-files-found: ignore
383
+ """
384
+
385
+ workflow_file = workflows_dir / "build-android.yml"
386
+ workflow_file.write_text(workflow_content)
387
+
388
+ def _generate_web_build_workflow(self, workflows_dir: Path) -> None:
389
+ """Generate Web build workflow."""
390
+ workflow_content = f"""name: Build Web
391
+
392
+ on:
393
+ workflow_dispatch:
394
+ push:
395
+ tags:
396
+ - 'v*.*.*'
397
+ branches:
398
+ - main
399
+
400
+ jobs:
401
+ build-web:
402
+ runs-on: ubuntu-latest
403
+ steps:
404
+ - name: Checkout
405
+ uses: actions/checkout@v6
406
+
407
+ - name: Setup Flutter
408
+ uses: subosito/flutter-action@v2
409
+ with:
410
+ channel: ${{{{ secrets.FLUTTER_CHANNEL || 'stable' }}}}
411
+ version: ${{{{ secrets.FLUTTER_VERSION || '{self.flutter_version}' }}}}
412
+
413
+ - name: Cache pub
414
+ uses: actions/cache@v4
415
+ with:
416
+ path: ~/.pub-cache
417
+ key: pub-${{{{ runner.os }}}}-${{{{ hashFiles('**/pubspec.lock') }}}}
418
+
419
+ - name: Install dependencies
420
+ run: flutter pub get
421
+
422
+ - name: Build Web
423
+ run: flutter build web --release
424
+
425
+ - name: Upload artifacts
426
+ uses: actions/upload-artifact@v5
427
+ with:
428
+ name: web-build
429
+ path: build/web
430
+ if-no-files-found: ignore
431
+ """
432
+
433
+ workflow_file = workflows_dir / "build-web.yml"
434
+ workflow_file.write_text(workflow_content)
435
+
436
+ def _generate_macos_build_workflow(self, workflows_dir: Path) -> None:
437
+ """Generate macOS build workflow."""
438
+ workflow_content = f"""name: Build macOS
439
+
440
+ on:
441
+ workflow_dispatch:
442
+ push:
443
+ tags:
444
+ - 'v*.*.*'
445
+ branches:
446
+ - main
447
+
448
+ jobs:
449
+ build-macos:
450
+ runs-on: macos-latest
451
+ steps:
452
+ - name: Checkout
453
+ uses: actions/checkout@v6
454
+
455
+ - name: Setup Flutter
456
+ uses: subosito/flutter-action@v2
457
+ with:
458
+ channel: ${{{{ secrets.FLUTTER_CHANNEL || 'stable' }}}}
459
+ version: ${{{{ secrets.FLUTTER_VERSION || '{self.flutter_version}' }}}}
460
+
461
+ - name: Cache pub
462
+ uses: actions/cache@v4
463
+ with:
464
+ path: ~/.pub-cache
465
+ key: pub-${{{{ runner.os }}}}-${{{{ hashFiles('**/pubspec.lock') }}}}
466
+
467
+ - name: Install dependencies
468
+ run: flutter pub get
469
+
470
+ - name: Build macOS
471
+ run: flutter build macos --release
472
+
473
+ - name: Upload artifacts
474
+ uses: actions/upload-artifact@v5
475
+ with:
476
+ name: macos-build
477
+ path: build/macos/Build/Products/Release
478
+ if-no-files-found: ignore
479
+ """
480
+
481
+ workflow_file = workflows_dir / "build-macos.yml"
482
+ workflow_file.write_text(workflow_content)
483
+
484
+ def _generate_linux_build_workflow(self, workflows_dir: Path) -> None:
485
+ """Generate Linux build workflow."""
486
+ workflow_content = f"""name: Build Linux
487
+
488
+ on:
489
+ workflow_dispatch:
490
+ push:
491
+ tags:
492
+ - 'v*.*.*'
493
+ branches:
494
+ - main
495
+
496
+ jobs:
497
+ build-linux:
498
+ runs-on: ubuntu-latest
499
+ steps:
500
+ - name: Checkout
501
+ uses: actions/checkout@v6
502
+
503
+ - name: Setup Flutter
504
+ uses: subosito/flutter-action@v2
505
+ with:
506
+ channel: ${{{{ secrets.FLUTTER_CHANNEL || 'stable' }}}}
507
+ version: ${{{{ secrets.FLUTTER_VERSION || '{self.flutter_version}' }}}}
508
+
509
+ - name: Cache pub
510
+ uses: actions/cache@v4
511
+ with:
512
+ path: ~/.pub-cache
513
+ key: pub-${{{{ runner.os }}}}-${{{{ hashFiles('**/pubspec.lock') }}}}
514
+
515
+ - name: Install Linux dependencies
516
+ run: |
517
+ sudo apt-get update
518
+ sudo apt-get install -y \\
519
+ libgtk-3-dev \\
520
+ libayatana-appindicator3-dev \\
521
+ clang \\
522
+ cmake \\
523
+ ninja-build \\
524
+ pkg-config \\
525
+ liblzma-dev
526
+
527
+ - name: Install dependencies
528
+ run: flutter pub get
529
+
530
+ - name: Build Linux
531
+ run: flutter build linux --release
532
+
533
+ - name: Upload artifacts
534
+ uses: actions/upload-artifact@v5
535
+ with:
536
+ name: linux-build
537
+ path: build/linux/x64/release/bundle
538
+ if-no-files-found: ignore
539
+ """
540
+
541
+ workflow_file = workflows_dir / "build-linux.yml"
542
+ workflow_file.write_text(workflow_content)
543
+
544
+ def _generate_windows_build_workflow(self, workflows_dir: Path) -> None:
545
+ """Generate Windows build workflow."""
546
+ workflow_content = f"""name: Build Windows
547
+
548
+ on:
549
+ workflow_dispatch:
550
+ push:
551
+ tags:
552
+ - 'v*.*.*'
553
+ branches:
554
+ - main
555
+
556
+ jobs:
557
+ build-windows:
558
+ runs-on: windows-latest
559
+ steps:
560
+ - name: Checkout
561
+ uses: actions/checkout@v6
562
+
563
+ - name: Setup Flutter
564
+ uses: subosito/flutter-action@v2
565
+ with:
566
+ channel: ${{{{ secrets.FLUTTER_CHANNEL || 'stable' }}}}
567
+ version: ${{{{ secrets.FLUTTER_VERSION || '{self.flutter_version}' }}}}
568
+
569
+ - name: Cache pub
570
+ uses: actions/cache@v4
571
+ with:
572
+ path: ~/.pub-cache
573
+ key: pub-${{{{ runner.os }}}}-${{{{ hashFiles('**/pubspec.lock') }}}}
574
+
575
+ - name: Install dependencies
576
+ run: flutter pub get
577
+
578
+ - name: Build Windows
579
+ run: flutter build windows --release
580
+
581
+ - name: Upload artifacts
582
+ uses: actions/upload-artifact@v5
583
+ with:
584
+ name: windows-build
585
+ path: build/windows/x64/runner/Release
586
+ if-no-files-found: ignore
587
+ """
588
+
589
+ workflow_file = workflows_dir / "build-windows.yml"
590
+ workflow_file.write_text(workflow_content)
591
+
592
+ def _generate_dependabot(self, github_dir: Path) -> None:
593
+ """Generate dependabot.yml configuration."""
594
+ dependabot_content = """version: 2
595
+ updates:
596
+ # Enable version updates for Flutter/Dart dependencies
597
+ - package-ecosystem: "pub"
598
+ directory: "/"
599
+ schedule:
600
+ interval: "weekly"
601
+ open-pull-requests-limit: 10
602
+ groups:
603
+ flutter-dependencies:
604
+ patterns:
605
+ - "flutter*"
606
+ - "dart*"
607
+ dev-dependencies:
608
+ patterns:
609
+ - "*_test"
610
+ - "*test*"
611
+ - "mockito"
612
+ - "build_runner"
613
+
614
+ # Enable version updates for GitHub Actions
615
+ - package-ecosystem: "github-actions"
616
+ directory: "/"
617
+ schedule:
618
+ interval: "weekly"
619
+ open-pull-requests-limit: 5
620
+ """
621
+
622
+ dependabot_file = github_dir / "dependabot.yml"
623
+ dependabot_file.write_text(dependabot_content)
624
+
625
+ def _generate_setup_docs(self, github_dir: Path) -> None:
626
+ """Generate CI/CD setup documentation."""
627
+ docs_content = f"""# CI/CD Setup Guide
628
+
629
+ This document provides instructions for setting up and configuring the CI/CD workflows for {self.project_name}.
630
+
631
+ ## Overview
632
+
633
+ The CI/CD workflows in this repository include:
634
+
635
+ - **Lint Workflow** (`lint.yml`): Runs `flutter analyze` on pull requests and pushes
636
+ - **Format Workflow** (`format.yml`): Checks code formatting with `dart format`
637
+ - **Test Workflow** (`test.yml`): Runs unit tests
638
+ - **Build Workflows**: Platform-specific build workflows for enabled platforms
639
+
640
+ ## Prerequisites
641
+
642
+ 1. **GitHub Actions enabled**: Ensure GitHub Actions are enabled in your repository settings
643
+ 2. **Flutter Configuration**: Set Flutter channel and version secrets (optional, defaults to `stable` channel)
644
+
645
+ ## Configuration
646
+
647
+ ### GitHub Secrets
648
+
649
+ Go to **Settings → Secrets → Actions** and create the following secrets:
650
+
651
+ | Secret | Value | Description | Required |
652
+ |--------|-------|-------------|----------|
653
+ | `FLUTTER_CHANNEL` | `stable`, `beta`, or `dev` | Flutter channel to use in workflows. Defaults to `stable` if not set. | No |
654
+ | `FLUTTER_VERSION` | Specific version (e.g., `3.24.0`) or empty | Flutter SDK version to use. If not set, defaults to the Flutter version that was installed when the project was created ({self.flutter_version}). Examples: `3.24.0`, `3.24.1`, `3.25.0`. Check [Flutter releases](https://docs.flutter.dev/release/archive) for available versions. | No |
655
+ | `CODECOV_TOKEN` | Codecov upload token | Token for uploading test coverage reports to Codecov. See [Codecov Token Setup](#codecov-token-setup) below for instructions. | No |
656
+
657
+ **Note**: If `FLUTTER_VERSION` is not set, the workflow will use the Flutter version that was installed when the project was created ({self.flutter_version}) as the default. Setting a specific version ensures consistent builds across all CI runs and allows you to override the default version.
658
+
659
+ ### Codecov Token Setup
660
+
661
+ To enable test coverage reporting in pull requests, you'll need to set up a Codecov token:
662
+
663
+ 1. **Sign in to Codecov**:
664
+ - Go to [codecov.io](https://codecov.io) and sign in with your GitHub account
665
+ - Grant Codecov access to your repository when prompted
666
+
667
+ 2. **Get your upload token**:
668
+ - Navigate to your repository on Codecov (or go to **Settings → Integrations**)
669
+ - Copy your repository's upload token (it will look like: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`)
670
+
671
+ 3. **Add the secret to GitHub**:
672
+ - Go to your repository on GitHub
673
+ - Navigate to **Settings → Secrets and variables → Actions**
674
+ - Click **New repository secret**
675
+ - Name: `CODECOV_TOKEN`
676
+ - Value: Paste your Codecov upload token
677
+ - Click **Add secret**
678
+
679
+ **Note**: The Codecov token is optional. The test workflow will still run tests without it, but coverage reports won't be uploaded to Codecov. If you don't plan to use Codecov, you can skip this step.
680
+
681
+ ### Additional Secrets (for App Store Deployment)
682
+
683
+ If you plan to deploy to app stores or use code signing, you'll need to configure additional secrets:
684
+
685
+ #### iOS App Store Connect (for TestFlight/App Store deployment)
686
+
687
+ 1. Go to [App Store Connect API Keys](https://appstoreconnect.apple.com/access/api)
688
+ 2. Create a new API key
689
+ 3. Download the `.p8` file (⚠️ only available once!)
690
+ 4. Convert to Base64:
691
+ ```bash
692
+ base64 -i AuthKey_ABC12345.p8 | pbcopy
693
+ ```
694
+ 5. Add the following secrets in **Settings → Secrets → Actions**:
695
+
696
+ | Secret | Description |
697
+ |--------|-------------|
698
+ | `APPLE_NOTARY_KEY_ID` | Key ID (example: ABC123DEFG) |
699
+ | `APPLE_NOTARY_ISSUER_ID` | Issuer ID (UUID on App Store Connect) |
700
+ | `APPLE_NOTARY_KEY_BASE64` | Base64 of the `.p8` key |
701
+
702
+ #### Google Play Console (for Play Store deployment)
703
+
704
+ 1. Go to [Google Play Console](https://play.google.com/console)
705
+ 2. Navigate to **Setup → API access**
706
+ 3. Create a service account
707
+ 4. Download the JSON key file
708
+ 5. Add the following secret in **Settings → Secrets → Actions**:
709
+
710
+ | Secret | Description |
711
+ |--------|-------------|
712
+ | `GOOGLE_PLAY_SERVICE_ACCOUNT_JSON` | Contents of the JSON key file |
713
+
714
+ ## Workflow Triggers
715
+
716
+ ### Automatic Triggers
717
+
718
+ - **Lint, Format, Test**: Run on pull requests and pushes to `main` branch
719
+ - **Build Workflows**: Run on pushes to `main` branch and version tags (`v*.*.*`)
720
+
721
+ ### Manual Triggers
722
+
723
+ All build workflows can be triggered manually via **Actions → [Workflow Name] → Run workflow**
724
+
725
+ ## Platform-Specific Notes
726
+
727
+ {self._generate_platform_notes()}
728
+
729
+ ## Troubleshooting
730
+
731
+ ### Common Issues
732
+
733
+ 1. **Workflow fails with "Flutter not found" or version error**
734
+ - Ensure the `FLUTTER_CHANNEL` secret is set correctly (`stable`, `beta`, or `dev`)
735
+ - If using `FLUTTER_VERSION`, verify the version exists for the specified channel
736
+ - Check [Flutter releases](https://docs.flutter.dev/release/archive) for valid version numbers
737
+ - If `FLUTTER_VERSION` is set incorrectly, remove it to use the latest version from the channel
738
+
739
+ 2. **Build fails on specific platform**
740
+ - Verify that the platform is enabled in your `pubspec.yaml`
741
+ - Check platform-specific dependencies are installed
742
+
743
+ 3. **Dependabot not creating PRs**
744
+ - Ensure Dependabot is enabled in repository settings
745
+ - Check that `dependabot.yml` is in the `.github` directory
746
+
747
+ ### Getting Help
748
+
749
+ - Check workflow logs in the **Actions** tab
750
+ - Review Flutter documentation for platform-specific requirements
751
+ - Consult GitHub Actions documentation for workflow syntax
752
+
753
+ ## Security Best Practices
754
+
755
+ 1. **Never commit secrets**: Always use GitHub Secrets for sensitive information
756
+ 2. **Review Dependabot PRs**: Regularly review and merge dependency updates
757
+ 3. **Limit workflow permissions**: Use minimal required permissions in workflows
758
+ 4. **Regular updates**: Keep GitHub Actions versions up to date
759
+
760
+ ## Next Steps
761
+
762
+ 1. Set up repository variables (if needed)
763
+ 2. Configure secrets (if deploying to app stores)
764
+ 3. Push your code to trigger workflows
765
+ 4. Monitor workflow runs in the **Actions** tab
766
+ """
767
+
768
+ docs_file = github_dir / "CI_CD_SETUP.md"
769
+ docs_file.write_text(docs_content)
770
+
771
+ def _generate_platform_notes(self) -> str:
772
+ """Generate platform-specific notes for the documentation."""
773
+ notes = []
774
+ if "ios" in self.platforms:
775
+ notes.append(
776
+ "### iOS\n"
777
+ "- Requires macOS runner\n"
778
+ "- Builds are unsigned by default\n"
779
+ "- For code signing, configure Apple Developer certificates"
780
+ )
781
+ if "android" in self.platforms:
782
+ notes.append(
783
+ "### Android\n"
784
+ "- Builds both APK and AAB formats\n"
785
+ "- Requires Java 17\n"
786
+ "- For Play Store deployment, configure Google Play Console API"
787
+ )
788
+ if "web" in self.platforms:
789
+ notes.append(
790
+ "### Web\n"
791
+ "- Builds optimized web assets\n"
792
+ "- Can be deployed to any static hosting service"
793
+ )
794
+ if "macos" in self.platforms:
795
+ notes.append(
796
+ "### macOS\n"
797
+ "- Requires macOS runner\n"
798
+ "- Builds unsigned .app bundles by default\n"
799
+ "- For distribution, configure code signing and notarization"
800
+ )
801
+ if "linux" in self.platforms:
802
+ notes.append(
803
+ "### Linux\n"
804
+ "- Installs required system dependencies automatically\n"
805
+ "- Builds bundle directory structure\n"
806
+ "- Can be packaged as .deb, .rpm, or AppImage"
807
+ )
808
+ if "windows" in self.platforms:
809
+ notes.append(
810
+ "### Windows\n"
811
+ "- Builds Windows executable\n"
812
+ "- Can be packaged as MSIX or installer"
813
+ )
814
+ return "\n\n".join(notes) if notes else "No platform-specific notes."