Repository navigation
363 lines (327 loc) · 14.9 KB
/
Copy pathci.yml
File metadata and controls
363 lines (327 loc) · 14.9 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
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
name: CI
# While the repository is private, its Actions minutes are limited and Windows
# minutes count double. So every push runs everything on Ubuntu, and Windows
# joins on pull requests, and when the workflow is run by hand.
#
# The release workflow runs all of it (workflow_call), on Ubuntu and Windows,
# before it stages anything.
#
# Run by hand with "Update the visual baselines" ticked, it runs only the
# visual spec, on Windows, and uploads the images it takes
# (docs/development.md, Visual Tests).
on:
push:
pull_request:
workflow_dispatch:
inputs:
update-visual-baselines:
description: Update the visual baselines (runs only the visual spec, on Windows, and uploads its images)
type: boolean
default: false
workflow_call:
permissions:
contents: read
jobs:
house-rules:
name: House rules
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v7
with:
# Every commit, so every commit is checked.
fetch-depth: 0
persist-credentials: false
# No file and no commit message may name one of the organisation's
# private repositories. Naming them here would do just that, so the list
# is the PRIVATE_REPO_NAMES secret, one name per line, and this step never
# prints a name: only the files and commits it was found in. -w, so that
# "Electron-based" doesn't count as a name that it contains.
- name: No private repository names in files or commits
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
env:
PRIVATE_REPO_NAMES: ${{ secrets.PRIVATE_REPO_NAMES }}
run: |
if [ -z "$PRIVATE_REPO_NAMES" ]; then
echo "::error::The PRIVATE_REPO_NAMES secret isn't set, so there's nothing to check against."
exit 1
fi
# Listed first, so the step fails if git can't list them.
commits=$(git rev-list HEAD)
bad=0
n=0
while IFS= read -r name; do
name="${name%$'\r'}"
[ -n "$name" ] || continue
n=$((n + 1))
# git grep exits 1 when nothing matches, and more than 1 when it fails.
if files=$(git grep -liwF -e "$name" -- .); then
echo "::error::Private repository name $n is named in:"
printf '%s\n' "$files" | sed 's/^/ /'
bad=1
elif [ $? -ne 1 ]; then
echo "::error::git grep failed, so the files weren't checked."
exit 2
fi
for commit in $commits; do
if git log -1 --format='%s%n%b' "$commit" | grep -iwF -e "$name" > /dev/null; then
echo "::error::Private repository name $n is named in the message of $(git log -1 --format='%h' "$commit")."
bad=1
fi
done
done <<< "$PRIVATE_REPO_NAMES"
[ "$bad" = 0 ] && echo "None of the $n private repository names is named in any file or commit message."
exit "$bad"
# The kit's version is in four places, which must agree: package.json, its
# lock file, the demo's lock file (which records the kit it installs) and
# the README's badge (shields.io writes a hyphen as two).
- name: The kit's version agrees everywhere
run: |
node -e '
const fs = require("fs");
const read = (file) => JSON.parse(fs.readFileSync(file, "utf8"));
const badge = fs.readFileSync("README.md", "utf8").match(/img\.shields\.io\/badge\/version-(.+?)-[a-z]+\?/);
const found = {
"package.json": read("package.json").version,
"package-lock.json": read("package-lock.json").packages[""].version,
"demo/package-lock.json": read("demo/package-lock.json").packages["node_modules/@diamonddigitaldev/electron-kit"].version,
"README.md badge": badge && badge[1].replaceAll("--", "-"),
};
const want = found["package.json"];
let bad = false;
for (const [where, version] of Object.entries(found)) {
console.log(`${version === want ? "ok " : "NO "} ${where}: ${version}`);
if (version !== want) bad = true;
}
if (bad) { console.log(`::error::The kit is ${want} in package.json, but not everywhere above.`); process.exit(1); }
'
unit:
name: Unit and contract tests
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version: 24
cache: npm
cache-dependency-path: package-lock.json
# npm 12, as on the development machine: it only runs the install scripts package.json allows.
- name: Install npm 12
run: npm install -g npm@12
# Electron downloads its binary the first time it's asked for, not on
# install, and these tests never ask, so nothing large is downloaded.
- name: Install dependencies
run: |
npm --version
npm ci
- name: Run tests
run: npm test
electron:
name: Electron tests and packaged demo (${{ matrix.os }})
if: ${{ !inputs.update-visual-baselines }}
runs-on: ${{ matrix.os }}
# The suite runs twice (unpackaged, then against the packaged demo), and on
# Ubuntu that has taken 11 to 20 minutes: at 20, it was cancelled twice.
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
os: ${{ fromJSON(github.event_name == 'push' && '["ubuntu-latest"]' || '["ubuntu-latest", "windows-latest"]') }}
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version: 24
cache: npm
cache-dependency-path: |
package-lock.json
demo/package-lock.json
# npm 12, as on the development machine: it only runs the install scripts package.json allows.
- name: Install npm 12
run: npm install -g npm@12
# Windows runners install global packages outside Node's own folder, and
# setup-node puts Node's folder, with its bundled npm, first on PATH.
- name: Put npm 12 first on PATH
if: runner.os == 'Windows'
shell: pwsh
run: npm prefix -g >> $env:GITHUB_PATH
# The demo installs the kit from this checkout as a packed copy
# (demo/.npmrc). Electron downloads its binary the first time it's asked
# where it is, so that's done here.
- name: Install dependencies and Electron
run: |
npm --version
npm ci
npm ci --prefix demo
cd demo && node -e "console.log(require('electron'))"
# The libraries Electron needs, and Xvfb, the virtual display its windows
# open on. Playwright runs apt-get with sudo itself.
- name: Install Electron's system libraries and Xvfb
if: runner.os == 'Linux'
run: npx playwright install-deps chromium
# The unit job runs them on Ubuntu. On Windows they run here, since the
# updater's tests drive electron-updater's Windows updater, as File
# Converter's installed app does.
- name: Run the unit and contract tests
if: runner.os == 'Windows'
run: npm test
- name: Run the Electron tests
if: runner.os == 'Linux'
run: xvfb-run --auto-servernum --server-args="-screen 0 1280x1024x24" npx playwright test
- name: Run the Electron tests
if: runner.os == 'Windows'
run: npx playwright test
# The .rpm target (builder/base.json) needs rpmbuild, from the rpm package.
- name: Check for rpmbuild
if: runner.os == 'Linux'
run: |
if ! command -v rpmbuild > /dev/null; then
sudo apt-get update
sudo apt-get install -y --no-install-recommends rpm
fi
rpmbuild --version
# Nothing is published, and no token is passed to it.
- name: Build the demo
working-directory: demo
run: npx --no-install electron-builder ${{ runner.os == 'Windows' && '--win' || '--linux' }} --publish never
- name: Check the packaged demo carries the kit
working-directory: demo
shell: bash
run: |
ls -l dist
for ext in ${{ runner.os == 'Windows' && 'exe' || 'AppImage deb rpm' }}; do
ls dist/*.$ext > /dev/null
done
# builder/base.json's generateUpdatesFilesForAllChannels: a finished
# release (the demo is 0.0.0) carries every channel's file.
for channel in latest beta alpha; do
file=dist/$channel${{ runner.os == 'Linux' && '-linux' || '' }}.yml
test -f "$file" || { echo "::error::The build has no $file."; exit 1; }
grep -q "^version: 0.0.0$" "$file"
done
echo "The build carries every channel's update file."
# builder/base.json's maintainer, not package.json's author (a name and a web address).
- name: Check the .deb and .rpm name their maintainer
if: runner.os == 'Linux'
working-directory: demo
run: |
expected="Diamond Digital Development <will.knowles@diamonddigital.dev>"
deb=$(dpkg-deb -f dist/*.deb Maintainer)
rpm=$(rpm -qp --queryformat '%{PACKAGER}' dist/*.rpm)
echo "deb: $deb"
echo "rpm: $rpm"
test "$deb" = "$expected" && test "$rpm" = "$expected"
list=$(npx --no-install asar list dist/*-unpacked/resources/app.asar | sed 's#\\#/#g')
for file in package.json main/index.js main/accelerators.js main/channels.js main/info.js main/ipc.js main/menu.js main/shell.js main/store.js main/theme.js main/updater.js main/version.js preload.js css/kit.css page/theme.js page/kit.js; do
if ! printf '%s\n' "$list" | grep -x "/node_modules/@diamonddigitaldev/electron-kit/$file" > /dev/null; then
echo "::error::app.asar doesn't carry the kit's $file."
exit 1
fi
done
echo "app.asar carries the kit's files."
# The asking installer (builder/installer.js), installed silently with the
# choices its page would make, its registry read back after each install,
# then uninstalled: for one person, then (the runner is an administrator)
# for everyone, installed again over it.
- name: Install the demo and check what its installer adds
if: runner.os == 'Windows'
shell: pwsh
run: ./scripts/check-installer.ps1 -Setup (Get-ChildItem demo/dist/*.exe | Where-Object Name -notlike "*uninstaller*" | Select-Object -First 1).FullName
# builder/index.js's config(): the .desktop file is named for the appId
# and the running window matches it (desktopName, StartupWMClass), and it
# lists the demo's file types' MIME types, with %F so a file manager hands
# over every file as a path.
- name: Check the .desktop file in the .deb and .rpm
if: runner.os == 'Linux'
working-directory: demo
run: |
name=com.diamonddigitaldev.electronkitdemo
entry=usr/share/applications/$name.desktop
rpm -qlp dist/*.rpm | grep -x "/$entry"
dir=$(mktemp -d)
dpkg-deb -x dist/*.deb "$dir"
cat "$dir/$entry"
grep -x "MimeType=text/plain;text/markdown;" "$dir/$entry"
grep -E '^Exec=.* %F$' "$dir/$entry"
grep -x "StartupWMClass=$name" "$dir/$entry"
grep -x "Categories=Development;" "$dir/$entry"
node -e '
const asar = require("@electron/asar");
const pkg = JSON.parse(asar.extractFile(process.argv[1], "package.json").toString());
if (pkg.desktopName !== process.argv[2] + ".desktop") { console.log(`::error::The packed package.json has desktopName ${pkg.desktopName}.`); process.exit(1); }
' "$dir/opt/electron-kit Demo/resources/app.asar" "$name"
echo "The .desktop file and desktopName match the appId."
# The same tests, against the packaged app: on Windows the unpacked app
# the installer carries, on Linux the AppImage itself, which runs without
# FUSE by extracting itself first.
- name: Run the Electron tests against the packaged demo
if: runner.os == 'Linux'
run: |
export KIT_DEMO_EXECUTABLE=$(ls demo/dist/*.AppImage)
chmod +x "$KIT_DEMO_EXECUTABLE"
APPIMAGE_EXTRACT_AND_RUN=1 xvfb-run --auto-servernum --server-args="-screen 0 1280x1024x24" npx playwright test
- name: Run the Electron tests against the packaged demo
if: runner.os == 'Windows'
env:
KIT_DEMO_EXECUTABLE: demo/dist/win-unpacked/electron-kit Demo.exe
run: npx playwright test
# The only thing this job uploads: when a visual test fails, the image it
# expected, the one it took and the difference, so the failure can be
# seen. A workflow artifact is as private as the repository, and it's
# kept 3 days. Nothing else is uploaded.
- name: Upload the visual differences
if: failure() && runner.os == 'Windows'
uses: actions/upload-artifact@v7
with:
name: visual-differences-${{ github.run_attempt }}
path: |
test-results/**/*-actual.png
test-results/**/*-expected.png
test-results/**/*-diff.png
if-no-files-found: ignore
retention-days: 3
# Run by hand (update-visual-baselines): the visual spec takes its images
# afresh on a Windows runner, and they're uploaded as a workflow artifact,
# as private as the repository and kept 3 days, to be checked and committed
# (docs/development.md, Visual Tests). Nothing is committed from here.
visual-baselines:
name: Visual baselines (windows-latest)
if: ${{ inputs.update-visual-baselines }}
runs-on: windows-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version: 24
cache: npm
cache-dependency-path: |
package-lock.json
demo/package-lock.json
- name: Install npm 12
run: npm install -g npm@12
- name: Put npm 12 first on PATH
shell: pwsh
run: npm prefix -g >> $env:GITHUB_PATH
- name: Install dependencies and Electron
run: |
npm --version
npm ci
npm ci --prefix demo
cd demo && node -e "console.log(require('electron'))"
- name: Take the visual baselines
run: npx playwright test e2e/visual.spec.js --update-snapshots=all
- name: Upload the visual baselines
uses: actions/upload-artifact@v7
with:
name: visual-baselines
path: e2e/visual.spec.js-snapshots/
if-no-files-found: error
retention-days: 3