Repository navigation
274 lines (248 loc) · 9.73 KB
/
Copy pathdart-package.yml
File metadata and controls
274 lines (248 loc) · 9.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# =============================================================================
# Reusable: verify a publishable Dart or Flutter package.
# =============================================================================
# The gate a published package actually needs — formatting, analysis, tests,
# the example, and the pub.dev score — rather than the app checks in
# flutter-android.yml, which builds an APK a package does not have.
#
# uses: CtrlAltDevelop/ci-workflows/.github/workflows/dart-package.yml@v1.1.0
# with:
# sdk: flutter
#
# Everything else has a default that suits a single-package repository with an
# `example/` beside it.
#
on:
workflow_call:
inputs:
sdk:
description: |
"flutter" for a package that depends on the Flutter SDK, "dart" for
one that does not. A Flutter package cannot be analysed by the Dart
SDK alone, and a pure Dart package gets a faster runner without it.
type: string
required: true
sdk-version:
description: |
Flutter version (e.g. "3.47.0") or Dart SDK version. "stable" tracks
the current release, which is what you want on a package whose users
are on whatever shipped last week.
type: string
default: "stable"
working-directory:
type: string
default: "."
runs-on:
description: |
Set to macos-latest when the package has golden tests: goldens are
rasterised, and a golden recorded on one platform fails on another
over pixels nobody changed.
type: string
default: "ubuntu-latest"
format:
description: "Fail on unformatted Dart"
type: boolean
default: true
analyze-example:
description: |
Analyze and test example/ when it exists. The example is not covered
by pana's static analysis, so it rots quietly without this.
type: boolean
default: true
lowest-resolution:
description: |
Also run the tests with every dependency pinned to the lowest version
the constraints allow. Off by default: `pub downgrade` floors the
test runner's own transitive packages too, which on some packages
fails for reasons that say nothing about your code.
type: boolean
default: false
coverage:
description: "Collect coverage and upload it as an artifact"
type: boolean
default: false
changelog:
description: |
Require CHANGELOG.md to carry an entry for the version in
pubspec.yaml. pana only checks that a changelog exists, so without
this a version bump ships with no note of what changed in it.
type: boolean
default: true
pana:
description: "Score the package the way pub.dev does"
type: boolean
default: true
pana-threshold:
description: |
Points the package may lose before the job fails. 0 means a perfect
score is the gate — worth holding on a package you publish.
type: number
default: 0
timeout-minutes:
type: number
default: 20
permissions: {}
jobs:
test:
name: Test (${{ matrix.resolution }})
runs-on: ${{ inputs.runs-on }}
timeout-minutes: ${{ inputs.timeout-minutes }}
permissions:
contents: read
strategy:
fail-fast: false
matrix:
# Floated constraints are only a real promise if both ends are run.
resolution: ${{ inputs.lowest-resolution && fromJSON('["highest","lowest"]') || fromJSON('["highest"]') }}
defaults:
run:
working-directory: ${{ inputs.working-directory }}
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: subosito/flutter-action@v2
if: inputs.sdk == 'flutter'
with:
flutter-version: ${{ inputs.sdk-version != 'stable' && inputs.sdk-version || '' }}
channel: stable
cache: true
- uses: dart-lang/setup-dart@v1
if: inputs.sdk != 'flutter'
with:
sdk: ${{ inputs.sdk-version }}
- run: ${{ inputs.sdk }} pub get
- name: Floor every dependency
if: matrix.resolution == 'lowest'
run: ${{ inputs.sdk }} pub downgrade
- name: Verify formatting
# Once per package, not once per resolution: formatting does not depend
# on which versions resolved.
if: inputs.format && matrix.resolution == 'highest'
run: dart format --output=none --set-exit-if-changed .
- name: Analyze
run: ${{ inputs.sdk }} analyze --fatal-infos
# `flutter test --coverage` takes no argument; `dart test` requires the
# output directory, so the flag is not spelled the same in both SDKs.
- name: Test
run: ${{ inputs.sdk }} test ${{ (inputs.coverage && matrix.resolution == 'highest') && (inputs.sdk == 'flutter' && '--coverage' || '--coverage=coverage') || '' }}
# Goldens write what they actually rendered next to what was expected;
# without them a red golden run tells you nothing you can act on.
- name: Upload failed goldens
uses: actions/upload-artifact@v4
if: failure()
with:
name: golden-failures-${{ matrix.resolution }}
path: ${{ inputs.working-directory }}/test/failures/
if-no-files-found: ignore
retention-days: 7
- uses: actions/upload-artifact@v4
if: inputs.coverage && matrix.resolution == 'highest'
with:
name: coverage
path: ${{ inputs.working-directory }}/coverage/
if-no-files-found: warn
retention-days: 7
example:
name: Example
if: inputs.analyze-example
runs-on: ${{ inputs.runs-on }}
timeout-minutes: ${{ inputs.timeout-minutes }}
permissions:
contents: read
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: subosito/flutter-action@v2
if: inputs.sdk == 'flutter'
with:
flutter-version: ${{ inputs.sdk-version != 'stable' && inputs.sdk-version || '' }}
channel: stable
cache: true
- uses: dart-lang/setup-dart@v1
if: inputs.sdk != 'flutter'
with:
sdk: ${{ inputs.sdk-version }}
# Skipped rather than failed when there is no example: a package without
# one is a fair choice, and the caller should not have to say so twice.
- name: Analyze and test the example
working-directory: ${{ inputs.working-directory }}
env:
SDK: ${{ inputs.sdk }}
run: |
if [ ! -f example/pubspec.yaml ]; then
echo "No example/ — nothing to check."
exit 0
fi
"$SDK" pub get
cd example
"$SDK" pub get
"$SDK" analyze --fatal-infos
# `test` on a package with no test/ exits non-zero; an example is
# allowed to be a demo and nothing more.
if [ -d test ]; then "$SDK" test; fi
score:
name: Score
if: inputs.pana
runs-on: ${{ inputs.runs-on }}
timeout-minutes: ${{ inputs.timeout-minutes }}
permissions:
contents: read
defaults:
run:
working-directory: ${{ inputs.working-directory }}
steps:
- uses: actions/checkout@v7
with:
# pana reads the repository's git metadata to check the pubspec's
# repository link, so it wants real history, not a shallow clone.
fetch-depth: 0
persist-credentials: false
- uses: subosito/flutter-action@v2
if: inputs.sdk == 'flutter'
with:
flutter-version: ${{ inputs.sdk-version != 'stable' && inputs.sdk-version || '' }}
channel: stable
cache: true
- uses: dart-lang/setup-dart@v1
if: inputs.sdk != 'flutter'
with:
sdk: ${{ inputs.sdk-version }}
- name: Changelog covers this version
if: inputs.changelog
run: |
version="$(grep -m1 '^version:' pubspec.yaml | sed 's/^version:[[:space:]]*//' | tr -d '"'"'"'"' | tr -d '[:space:]')"
if [ -z "$version" ]; then
echo "::error::pubspec.yaml has no version."
exit 1
fi
if [ ! -f CHANGELOG.md ]; then
echo "::error::No CHANGELOG.md."
exit 1
fi
# Matched loosely — `## 1.2.0`, `## [1.2.0]`, `# 1.2.0 - 2026-09-17`
# are all normal headings and all say the same thing.
if ! grep -qE "(^|[^0-9.])${version//./\\.}([^0-9.]|$)" CHANGELOG.md; then
echo "::error::CHANGELOG.md has no entry for $version."
exit 1
fi
echo "CHANGELOG.md covers $version."
- run: ${{ inputs.sdk }} pub get
# pana checks each `screenshots:` entry by running webpinfo, cwebp and
# gif2webp on it. A runner without them fails every screenshot with "No
# such file or directory" — and the job with exit code 127 — although
# nothing is wrong with the images. All three ship in the webp package.
- name: Install WebP tools
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y webp
# pana covers `pub publish --dry-run` and then some: publish readiness,
# documentation, declared platform support, static analysis and how far
# behind the dependencies are.
- name: Score with pana
run: |
dart pub global activate pana
pana --no-warning --exit-code-threshold ${{ inputs.pana-threshold }}
# No dependency audit here, for the same reason as flutter-android.yml:
# pub has no advisory command that exits non-zero. Trivy reads
# pubspec.lock in security.yml, and Dependabot watches the pub ecosystem.