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.
- flutter_setup/__init__.py +0 -0
- flutter_setup/bootstrap.py +904 -0
- flutter_setup/cicd_generator.py +814 -0
- flutter_setup/cli.py +662 -0
- flutter_setup/config.py +135 -0
- flutter_setup/config_manager.py +159 -0
- flutter_setup/core.py +204 -0
- flutter_setup/exceptions.py +37 -0
- flutter_setup/flutter_manager.py +579 -0
- flutter_setup/platform.py +15 -0
- flutter_setup/prerequisites.py +39 -0
- flutter_setup/prerequisites_linux.py +140 -0
- flutter_setup/prerequisites_macos.py +226 -0
- flutter_setup/project_creator.py +82 -0
- flutter_setup-2.1.0.dist-info/METADATA +365 -0
- flutter_setup-2.1.0.dist-info/RECORD +20 -0
- flutter_setup-2.1.0.dist-info/WHEEL +5 -0
- flutter_setup-2.1.0.dist-info/entry_points.txt +2 -0
- flutter_setup-2.1.0.dist-info/licenses/LICENSE +21 -0
- flutter_setup-2.1.0.dist-info/top_level.txt +1 -0
|
@@ -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."
|