-
Notifications
You must be signed in to change notification settings - Fork 4
300 lines (249 loc) · 10.9 KB
/
Copy pathci.yml
File metadata and controls
300 lines (249 loc) · 10.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
name: CI
on:
push:
branches:
- master
- release/*
pull_request:
paths-ignore:
- "**.md"
workflow_dispatch:
inputs:
release_type:
type: choice
options:
- Dry Run
- Release
# Runners resolve through repo/org `vars` (ArcBox pins paid Blacksmith labels there);
# forks without the vars fall back to GitHub-hosted labels with zero setup.
# Pull request and release-branch CI keeps latest-run cancellation. Every master SHA gets a unique
# group so a release-PR merge cannot be canceled or replaced before its exact SHA is tested.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.ref == 'refs/heads/master' && github.sha || 'latest' }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
permissions:
contents: read
env:
# Non-lint Node steps still need more than the default 4096 MB heap; lint sets its own limit.
NODE_OPTIONS: --max-old-space-size=6144
jobs:
typescript:
name: TypeScript
runs-on: ${{ vars.CI_RUNNER_LINUX || 'ubuntu-latest' }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- name: Setup Rust
id: setup-rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
rustflags: "" # keep build.rustflags; the -D warnings default belongs to the Rust job's clippy call
background: true
- parallel:
- uses: pnpm/action-setup@v6
with:
run_install: false
cache: true
- uses: actions/setup-node@v7
with:
node-version-file: .nvmrc
package-manager-cache: false
# Before `pnpm install`, so the hashFiles globs below cannot reach into node_modules.
#
# eslint validates a cached entry against that file's own content only, so a cross-file
# type change would leave an unchanged file's cached result in place — a type-aware rule
# such as no-floating-promises can start applying to a caller nobody edited, and `tsc`
# does not reject that code either. The key therefore covers every lint-visible input at
# once and there are deliberately no restore-keys: a prefix match would reintroduce
# exactly that hole. The cost is that this only hits on commits touching no lintable
# source (docs, Rust, workflows); a run that changes any of them re-lints from cold,
# which is the correct answer.
- name: Restore ESLint cache
uses: actions/cache@v6
with:
path: .eslintcache
key: eslint-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'pnpm-workspace.yaml', 'package.json', 'apps/*/package.json', 'packages/*/*/package.json', 'tsconfig*.json', 'apps/*/tsconfig.json', 'apps/*/tests/tsconfig.json', 'apps/*/e2e/tsconfig.json', 'packages/*/*/tsconfig.json', 'packages/*/*/tests/tsconfig.json') }}-${{ hashFiles('packages/*/*/src/**') }}-${{ github.sha }}
restore-keys: |
eslint-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'pnpm-workspace.yaml', 'package.json', 'apps/*/package.json', 'packages/*/*/package.json', 'tsconfig*.json', 'apps/*/tsconfig.json', 'apps/*/tests/tsconfig.json', 'apps/*/e2e/tsconfig.json', 'packages/*/*/tsconfig.json', 'packages/*/*/tests/tsconfig.json') }}-${{ hashFiles('packages/*/*/src/**') }}-
eslint-${{ runner.os }}-
- name: Install dependencies
run: pnpm install --frozen-lockfile
# After `pnpm install`, which could otherwise prune the restored node_modules/.cache dirs.
# `tsc --build` validates staleness from .tsbuildinfo content hashes, not timestamps
# (touching every source leaves a warm build sub-second), so restoring a stale build info
# is always safe — tsc rechecks exactly what changed. restore-keys is therefore sound here,
# unlike for the ESLint cache above.
- name: Restore TypeScript build info
uses: actions/cache@v6
with:
path: |
apps/*/node_modules/.cache/tsconfig*.tsbuildinfo
packages/*/*/node_modules/.cache/tsconfig*.tsbuildinfo
key: tsc-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'tsconfig*.json', 'apps/*/tsconfig.json', 'apps/*/tests/tsconfig.json', 'apps/*/e2e/tsconfig.json', 'packages/*/*/tsconfig.json', 'packages/*/*/tests/tsconfig.json') }}-${{ github.sha }}
restore-keys: |
tsc-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'tsconfig*.json', 'apps/*/tsconfig.json', 'apps/*/tests/tsconfig.json', 'apps/*/e2e/tsconfig.json', 'packages/*/*/tsconfig.json', 'packages/*/*/tests/tsconfig.json') }}-
tsc-${{ runner.os }}-
- wait: setup-rust
- name: Build PTY sidecar for interoperability tests
id: build-sidecar
run: cargo build --locked -p linkcode-pty
background: true
- name: Check formatting and imports
run: pnpm format:check
# Type-aware programs are duplicated per worker, so lint defaults to single-threaded.
- name: Lint
run: pnpm lint
- name: Typecheck
run: pnpm typecheck
- wait: build-sidecar
- name: Test
env:
LINKCODE_PTY_SIDECAR_PATH: ${{ github.workspace }}/target/debug/linkcode-pty
LINKCODE_REQUIRE_PTY_SIDECAR: "1"
run: |
pnpm test --exclude='apps/daemon/tests/integration/**'
pnpm test apps/daemon/tests/integration --no-file-parallelism
- name: Daemon process acceptance
env:
LINKCODE_PTY_SIDECAR_PATH: ${{ github.workspace }}/target/debug/linkcode-pty
run: |
pnpm -F @linkcode/daemon build
pnpm -F @linkcode/daemon e2e:startup
desktop:
name: Desktop App Entry
runs-on: ${{ vars.CI_RUNNER_LINUX || 'ubuntu-latest' }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- name: Setup Rust
id: setup-rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
rustflags: ""
background: true
- parallel:
- uses: pnpm/action-setup@v6
with:
run_install: false
cache: true
- uses: actions/setup-node@v7
with:
node-version-file: .nvmrc
package-manager-cache: false
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Install Electron system dependencies
run: |
pnpm -F @linkcode/desktop exec playwright-core install-deps chromium
sudo apt-get install --no-install-recommends -y openbox x11-utils
- name: Test unpackaged app entry
run: xvfb-run -a pnpm -F @linkcode/desktop e2e:unpackaged
- name: Test window state persistence
run: |
xvfb-run -a sh -c '
openbox >/tmp/linkcode-openbox.log 2>&1 &
wm_pid=$!
trap "kill $wm_pid 2>/dev/null || true" EXIT
# Maximize needs a managing WM; wait for the EWMH readiness marker instead
# of racing openbox startup.
for _ in $(seq 1 50); do
xprop -root _NET_SUPPORTING_WM_CHECK 2>/dev/null | grep -q "window id" && break
sleep 0.2
done
pnpm -F @linkcode/desktop e2e:window-bounds
'
- wait: setup-rust
- name: Test unsigned packaged app entry
run: xvfb-run -a pnpm -F @linkcode/desktop e2e:packaged
webview:
name: Webview Browser Entry
runs-on: ${{ vars.CI_RUNNER_LINUX || 'ubuntu-latest' }}
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v7
- parallel:
- uses: pnpm/action-setup@v6
with:
run_install: false
cache: true
- uses: actions/setup-node@v7
with:
node-version-file: .nvmrc
package-manager-cache: false
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Install Chromium
run: pnpm -F @linkcode/webview exec playwright-core install --with-deps chromium --only-shell
- name: Test production and mock browser entries
run: pnpm -F @linkcode/webview e2e:browser
# Never expose production telemetry configuration to pull-request code. The browser smoke
# above deliberately runs without it; push/manual builds separately verify the real bundle.
- name: Build production bundle with telemetry
if: ${{ github.event_name != 'pull_request' }}
env:
VITE_SENTRY_DSN: ${{ secrets.SENTRY_DSN_WEBVIEW }}
VITE_POSTHOG_PROJECT_TOKEN: ${{ secrets.POSTHOG_PROJECT_TOKEN }}
VITE_POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
run: pnpm -F @linkcode/webview build
mobile:
name: Mobile Native Bundles
runs-on: ${{ vars.CI_RUNNER_LINUX || 'ubuntu-latest' }}
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v7
- parallel:
- uses: pnpm/action-setup@v6
with:
run_install: false
cache: true
- uses: actions/setup-node@v7
with:
node-version-file: .nvmrc
package-manager-cache: false
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Export Android and iOS app entries
# SENTRY_AUTH_TOKEN intentionally stays in EAS: ordinary CI does not upload source maps.
env:
EXPO_PUBLIC_SENTRY_DSN: ${{ github.event_name != 'pull_request' && secrets.SENTRY_DSN_MOBILE || '' }}
EXPO_PUBLIC_POSTHOG_PROJECT_TOKEN: ${{ github.event_name != 'pull_request' && secrets.POSTHOG_PROJECT_TOKEN || '' }}
EXPO_PUBLIC_POSTHOG_HOST: ${{ github.event_name != 'pull_request' && secrets.POSTHOG_HOST || '' }}
run: pnpm -F @linkcode/mobile smoke:export
rust:
name: Rust
runs-on: ${{ vars.CI_RUNNER_LINUX || 'ubuntu-latest' }}
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v7
# rustfmt and clippy come from rust-toolchain.toml's components.
- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
rustflags: ""
- parallel:
- name: Format
run: cargo fmt --check
- name: Clippy
run: cargo clippy --all-targets --locked -- -D warnings
- name: Test
run: cargo test --locked
all-green:
name: All Green
runs-on: ${{ vars.CI_RUNNER_LINUX || 'ubuntu-latest' }}
timeout-minutes: 5
needs:
- typescript
- desktop
- webview
- mobile
- rust
if: always()
steps:
- name: Check required jobs
run: |
if [ '${{ needs.typescript.result }}' != 'success' ] || [ '${{ needs.desktop.result }}' != 'success' ] || [ '${{ needs.webview.result }}' != 'success' ] || [ '${{ needs.mobile.result }}' != 'success' ] || [ '${{ needs.rust.result }}' != 'success' ]; then
exit 1
fi