-
Notifications
You must be signed in to change notification settings - Fork 16
799 lines (742 loc) · 38 KB
/
Copy pathci.yaml
File metadata and controls
799 lines (742 loc) · 38 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
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
name: CI
on:
push:
pull_request:
jobs:
# Repo-wide em-dash guard. The script scans every tracked file (yaml,
# md, json, html, root config, AND the linted TS / TSX / JS / JSX /
# MTS / MJS source). The overlap with the ESLint rule
# `local/no-em-dash` on source files is the point: this job is one
# single check that succeeds iff the repo is clean end-to-end, so a
# future weakening of the rule's include globs cannot silently let an
# em dash slip through. The check is a single `git grep`, so the job
# finishes in a few seconds and does not consume Node / pnpm cache
# budget.
no_em_dash:
name: no em dashes
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
# The job runs `git grep` on the checkout but never pushes or
# talks to the remote, so the `GITHUB_TOKEN` Actions stashes
# in `.git/config` by default is unused. Disabling persistence
# keeps it from being shipped in any artifact / log capture
# downstream (`zizmor`'s `artipacked` lint). The matching
# `excessive-permissions` lint zizmor also emits is *not*
# addressed by an explicit `permissions: { contents: read }`
# block on this job: this is a public repo on GitHub's
# default-restrictive workflow-permissions setting, so the
# token already arrives as `contents: read` only, and the
# other jobs in this file follow the same pattern (only
# `coverage` carries a `permissions:` block, and only
# because Codecov OIDC + GitHub native coverage upload
# need scopes the default does not include). Adding the
# block here would break that pattern without granting any
# additional security.
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '24'
- name: Check no em dashes
run: node scripts/check-no-em-dash.mts
build:
name: typecheck · lint · test · build · ${{ matrix.os }} · node ${{ matrix.node }}
runs-on: ${{ matrix.os }}
# Force bash on every OS so `run:` steps that use POSIX redirection (e.g.
# `>> "$GITHUB_STEP_SUMMARY"`) work uniformly. windows-latest defaults to
# pwsh otherwise. Git Bash ships on the GitHub-hosted Windows image.
defaults:
run:
shell: bash
strategy:
fail-fast: false
matrix:
# Cross every Node entry below with all three GitHub-hosted OS images.
# The Node matrix exists to surface strip-types / transform-types /
# amaro behaviour shifts; running it on Linux + Windows + macOS also
# catches platform-specific regressions in path handling, child
# process spawning, and pnpm hardlink/symlink behaviour.
os: [ubuntu-latest, windows-latest, macos-latest]
# The 22.x line is pinned to the DoS-patched range (22.22.0+, see
# https://nodejs.org/en/blog/vulnerability/january-2026-dos-mitigation-async-hooks);
# earlier 22.x are out of support. The 24.x entries still cover
# strip-types / transform-types / amaro behaviour shifts.
node:
# Node 22 LTS DoS-patched range
- '22.22.0' # exact minimum supported version (DoS-patched)
- '>=22.22.0 <23' # latest 22.x range
- '>=24.0.0 <24.1.0' # Node 24 initial state; strip-types default-on / Stability: RC
- '>=24.1.0 <24.3.0' # amaro updates
# Major: experimental warning removed in Node 24
- '>=24.3.0 <24.5.0' # warning removed / amaro 1.1.0 / CJS TS loader refactor
- '>=24.5.0 <24.8.0' # amaro updates
- '>=24.8.0 <24.10.0' # amaro updates
- '>=24.10.0 <24.12.0' # amaro updates
# Major: type stripping marked Stable in Node 24 LTS
- '24.12.0' # exact boundary version where type stripping became stable
- '>=24.12.0 <25' # stable Node 24 range after 24.12.0
# Node 26 (Current). Follow the same floor + latest pattern as
# Node 24: 26.0.0 catches initial-release behaviour, the open
# range pulls in patch updates without manual bumps.
- '26.0.0' # exact 26.x floor (initial release)
- '>=26.0.0 <27' # latest 26.x
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
# action-setup@v6 bootstrap-installs pnpm v11 (per its committed
# lockfile) and only honours `packageManager` via pnpm's own
# `manage-package-manager-versions`. Force @pnpm/exe so bootstrap
# pnpm runs on its bundled Node regardless of which matrix Node
# we switch to next.
#
# Pinning `version: 11.13.1` instead doesn't work: action-setup
# compares it against the full `pnpm@11.13.1+sha512.xxx`
# packageManager string and refuses on mismatch
# (ERR_PNPM_BAD_PM_VERSION-style error).
#
# Do NOT pin pnpm 11.12.0 or 11.13.0: those two releases publish
# their per-platform npm packages (@pnpm/linux-x64, @pnpm/macos-
# arm64, @pnpm/win-x64, ...) WITHOUT the native binary (~1.9 kB
# metadata-only tarballs), so `standalone: true` here resolves
# @pnpm/exe, finds no binary, and leaves the placeholder that dies
# with `pnpm: 1: This: not found` on every OS. Fixed upstream in
# 11.13.1 (platform packages ship the binary again); 11.11.0 was
# the last good release before the gap. See pnpm/pnpm#12955
# (release-flow regression from pnpm/pnpm#12949, fixed in 11.13.1).
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
with:
standalone: true
# Turbo's local cache is at .turbo/cache. Keying on the commit SHA
# always writes a fresh entry; restore-keys falls back to the most
# recent prior cache for the same OS/node so unaffected task hashes
# hit immediately. node version is part of the key because turbo's
# hash currently does not factor it in, and we want to keep the
# matrix entries from clobbering each other if that ever changes.
- name: Cache Turborepo
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: .turbo/cache
key: turbo-${{ runner.os }}-node${{ matrix.node }}-${{ github.sha }}
restore-keys: |
turbo-${{ runner.os }}-node${{ matrix.node }}-
- name: Setup matrix Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: ${{ matrix.node }}
cache: pnpm
- name: Print resolved Node version
run: |
node --version
echo "**Resolved Node:** \`$(node --version)\` (matrix: \`${{ matrix.node }}\`)" >> "$GITHUB_STEP_SUMMARY"
- name: Install
# Belt-and-suspenders: pnpm-workspace.yaml's `allowBuilds`
# already gates which packages may run postinstall, and
# `playwright` is not in that list, so `pnpm install` won't
# invoke Playwright's browser-download script today. Setting
# `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1` here keeps that
# contract explicit so a future `allowBuilds` change can't
# silently re-enable a 150 MB postinstall download that the
# following cache + `playwright install` steps are meant to
# satisfy.
env:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
run: pnpm install
- name: Typecheck
run: pnpm typecheck
- name: Lint
run: pnpm lint
- name: Format check
run: pnpm format:check
# Playwright bundles its own browser binaries (~150 MB). Cache by
# OS and the lockfile hash so a `@playwright/test` upgrade busts
# the cache automatically. Cache locations are OS-specific:
# Linux uses ~/.cache/ms-playwright, macOS uses
# ~/Library/Caches/ms-playwright, Windows uses
# %USERPROFILE%/AppData/Local/ms-playwright. `actions/cache`
# expands `~` through Node's `os.homedir()` and only treats `/`
# as a path separator on Windows runners; `~\AppData\…` would
# not resolve, so all three patterns use POSIX separators.
# `actions/cache` silently skips paths that don't exist on the
# current runner, so listing all three is safe.
- name: Cache Playwright browsers
id: playwright-cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.cache/ms-playwright
~/Library/Caches/ms-playwright
~/AppData/Local/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
playwright-${{ runner.os }}-
# `--with-deps` installs apt packages that Chromium needs at
# runtime (libnss3, libxkbcommon0, etc.). It's Linux-only; the
# action no-ops it on macOS/Windows but warns; gating on runner.os
# keeps the log clean.
- name: Install Playwright (Linux)
if: runner.os == 'Linux'
run: pnpm --filter @arkor/e2e-studio exec playwright install --with-deps chromium
- name: Install Playwright (macOS/Windows)
if: runner.os != 'Linux'
run: pnpm --filter @arkor/e2e-studio exec playwright install chromium
- name: Test
env:
ARKOR_INTERNAL_SCAFFOLD_ARKOR_SPEC: file:${{ github.workspace }}/packages/arkor
run: pnpm test
# Upload Playwright's HTML report + traces / videos / screenshots
# for every non-cancelled outcome so failures retain their
# artefacts regardless of which step failed. `if: failure()`
# would only fire when a *prior* step had already failed at the
# time this step runs; because Upload sits before Build (line
# below), a Test green + Build red sequence used to lose the
# report entirely. `always() && !cancelled()` also uploads on
# fully-green runs; the report dir is small then, so the extra
# artefact cost is negligible relative to the debugging value of
# having traces for every run. The `!cancelled()` half ensures
# a manual cancel doesn't get stalled waiting for an artefact
# upload that the maintainer is no longer interested in.
# `if-no-files-found: ignore` covers the case where Turbo
# cancelled `@arkor/e2e-studio#test` before Playwright could
# write anything (see comment on the option below).
#
# `matrix.node` would disambiguate across the matrix but it
# contains `<`, `>`, `=`, which `actions/upload-artifact` rejects
# (NTFS-incompatible); `strategy.job-index` is the documented
# alphanumeric escape hatch.
- name: Upload Playwright report
if: ${{ always() && !cancelled() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: playwright-report-${{ runner.os }}-${{ strategy.job-index }}
path: |
e2e/studio/playwright-report
e2e/studio/test-results
# Turbo may cancel `@arkor/e2e-studio#test` when an earlier
# task fails (e.g. a unit test failure in `arkor:test`),
# leaving Playwright with no chance to write its report.
# `actions/upload-artifact@v7` defaults to failing the step
# when no files match, which would convert "test step
# failed for an unrelated reason" into "upload step also
# failed" and noise up the CI summary. `ignore` makes the
# upload silently no-op when there's nothing to publish.
if-no-files-found: ignore
- name: Build
run: pnpm build
# Cross-pm install matrix: exercises `arkor init --use-<pm>` and
# `create-arkor --use-<pm>` against every package manager the SDK accepts,
# crossed with OS and a curated set of Node versions known to shift
# spawn / pipe / strip-types behaviour (e.g. libuv 1.51 in Node 22.17
# changed macOS pipe-flush timing, the kind of thing that ate hours of
# debugging in eng-606). The build job above already runs `pnpm test`
# end-to-end, but its install assertions only cover the pms that a
# typical contributor has on PATH (npm + pnpm). The yarn / yarn-berry /
# bun lanes pull their runtimes in CI-only because they aren't a
# development prerequisite and we don't want `pnpm test` on a fresh
# checkout to require them.
#
# KNOWN MATRIX GAP (Windows × bun × `arkor init`): this combination is
# NOT covered by the install matrix. `arkor init --use-bun` on Windows
# currently fails to produce `bun.lock` due to a CLI-binary-axis
# divergence between `arkor init` and `create-arkor` (the two share
# `cli-internal/install.ts` but the spawn shape differs in a way that
# bun on Windows is sensitive to). The gate is implemented at the
# test-runner level in `e2e/cli/src/arkor-init.test.ts` via `it.skip`
# so vitest reports the case as skipped per CI run; the
# `create-arkor.test.ts` lane DOES run bun on Windows and covers bun
# itself + the create-arkor spawn shape. The `arkor init --use-bun`
# CLI flag stays exposed because the divergence is treated as a bug
# to fix, not a permanent constraint; until then, the
# docs/cli/init.mdx page and `docs/ja/cli/init.mdx` mirror carry an
# explicit Windows caveat on the `--use-bun` flag so users hitting it
# have signal that this is known. Re-enable the case (drop the
# `isBunOnWindows` arm in arkor-init.test.ts, drop the caveat in
# both docs/cli/init.mdx files, AND this comment block) when the
# spawn-shape divergence is fixed.
#
# Each runner sets `ARKOR_E2E_PM=<label>`; the e2e suites
# (e2e/cli/src/{arkor-init,create-arkor}.test.ts) gate their install
# cases on that env var so exactly one pm's install assertions run per
# matrix entry. The rest of the e2e suite runs unfiltered: setup
# (workspace install + CLI build + corepack/bun bootstrap) dominates
# each job's wall time, so the marginal cost of running every e2e test
# over the matrix pm/Node/OS triple is small and occasionally surfaces
# cross-axis interactions outside the install path (e.g. yarn-berry's
# PnP fs layout influencing scaffold or spawn assertions).
install-matrix:
name: install · ${{ matrix.id }} · ${{ matrix.os }} · node ${{ matrix.node }}
runs-on: ${{ matrix.os }}
# Gate the 147-job Cartesian fanout on the fast em-dash guard so a
# style regression in any tracked file (yaml, md, ts, ...) fails
# before the install matrix burns runner minutes provisioning
# corepack / yarn-berry / bun across every OS, Node, and pm cell.
needs: no_em_dash
defaults:
run:
shell: bash
strategy:
fail-fast: false
# Full Cartesian product: 3 OS × 7 Node × 7 pm = 147 jobs.
# This is intentional. `arkor init` and `create-arkor` are
# bootstrap-time entry points; every (OS, Node, pm) cell
# exercises a different combination of `child_process.spawn`
# shell semantics (`.cmd` shims on Windows, corepack
# provisioning, yarn-berry's PnP-vs-`node-modules` linker,
# pnpm 9 / 10 / 11 lockfile and `allowBuilds` schema
# divergence, bun's lockfile + spawn-shape quirks). The
# observed divergences this matrix has caught (bun on
# Windows missing `bun.lock` under `arkor init`'s spawn
# shape; pnpm 9's `packages:` requirement; yarn 4 PnP
# default on `CI=1`) were not reproducible from a smaller
# axis-folded matrix; a single missing combination would
# have masked the bug at review time. The runtime / queue-
# time cost is accepted for that reason.
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
# Curated Node versions: the supported Node lines, i.e.
# the active LTS lines (22, 24) plus Current (26, not
# yet LTS; promotes in Oct 2026). main's engines bump
# (`packages/arkor/package.json` → `>=22.22.0`) made the
# prior pre-22.22 entries (22.13/17/21) point at
# unsupported configurations, so they've been dropped
# along with the yarn × pre-22.22 excludes that masked
# the engines mismatch.
#
# Each Node line gets two entries: exact engines floor
# (so the lowest officially supported version is actually
# exercised, not just whatever 22.22+ patch setup-node
# happens to resolve) plus the live `<minor>` range. The
# build matrix above uses the same pattern.
node:
- '22.22.0' # exact engines floor (DoS-patched 22 LTS)
- '>=22.22.0 <23' # latest 22.x
- '24.0.0' # exact 24.x floor (initial release; type-stripping default-on / Stability: RC)
- '24.12.0' # exact boundary where type stripping became Stable
- '>=24.12.0 <25' # latest 24.x
- '26.0.0' # exact 26.x floor (initial release)
- '>=26.0.0 <27' # latest 26.x
id: [pnpm-9, pnpm-10, pnpm-11, npm, yarn, yarn-berry, bun]
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
# The prior `rolldownIncompat` bootstrap path (separate Node 24
# checkout to install + build CLIs before exercising them under
# a matrix Node where rolldown's native binding wouldn't load)
# was for the pre-22.22 Node entries that the engines bump
# removed from this matrix. Every supported Node now satisfies
# rolldown's `^20.19 || >=22.12` requirement, so the bootstrap
# path is no longer needed and the matrix uses a single Node
# setup throughout.
- name: Setup matrix Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: ${{ matrix.node }}
# Intentionally no `cache: pnpm`; at this point pnpm isn't on
# PATH yet (the install-matrix job uses Node's bundled corepack
# to provision pnpm in the *next* step instead of running
# `pnpm/action-setup` up-front), so setup-node's pnpm-aware
# cache lookup fails with `Unable to locate executable file:
# pnpm`. Each job re-fetches deps fresh; ~30 s/job extra is
# noise next to the 2–3 min setup cost, and skipping the cache
# avoids a class of cross-OS pitfalls (e.g. corepack writing
# shims into a system-owned bin dir on Windows).
# We deliberately use Node's bundled corepack instead of
# pnpm/action-setup. action-setup adds its pnpm to PATH in a way
# that shadows corepack's shim, which would defeat the matrix
# --activate below: every entry would end up running action-setup's
# pinned pnpm regardless of which version we asked corepack to
# activate. Workspace `pnpm install` still resolves to pnpm 11.13.1
# because corepack reads `packageManager` from the root
# package.json; only the matrix pm test runs in tmpdirs (no
# packageManager field there), so corepack falls back to the
# activated default.
- name: Enable corepack
run: |
# Some Node 22.x minors ship a corepack predating the
# npm-registry signing-key rotation, so a plain `corepack
# enable` followed by `pnpm install` blows up in
# `verifySignature`. Upgrade first to be safe across the
# matrix Nodes: `corepack@0.34.7` carries the
# post-rotation keys and its engines `^20.10.0 ||
# ^22.11.0 || >=24.0.0` cover every supported entry.
# `--force` is required on Windows to clobber the runner
# image's pre-seeded `C:\npm\prefix\yarn.cmd`. Pinned
# (not `@latest`) so an upstream corepack release can't
# change resolution behaviour or raise its supported-Node
# floor and start failing this matrix without any
# repository change, mirroring the same pinning policy
# applied to pnpm/yarn/bun below.
npm install -g --force corepack@0.34.7
corepack enable
echo "COREPACK_ENABLE_DOWNLOAD_PROMPT=0" >> "$GITHUB_ENV"
- name: Install workspace + build CLIs
run: |
pnpm install
pnpm --filter create-arkor build
pnpm --filter arkor build
# NOTE: an earlier "Pack arkor into a scaffold tarball" step
# used to run `pnpm pack` and expose the result as a workflow
# output, fed into the now-removed `ARKOR_INTERNAL_SCAFFOLD_ARKOR_SPEC`
# job env. With the per-case `file:../<tgz>` override (the
# only viable shape on Windows + pnpm-10 / bun) the real-
# install tests now pack their own tarball in `beforeAll`,
# so the workflow-level pack was paying its `pnpm pack` cost
# on every leg of the (very large) matrix without anything
# consuming the output. PR #99 round-39 Copilot review.
- name: Setup pm under test (${{ matrix.id }})
if: ${{ matrix.id != 'bun' }}
run: |
case "${{ matrix.id }}" in
pnpm-9) corepack prepare "pnpm@9.15.9" --activate ;;
pnpm-10) corepack prepare "pnpm@10.33.4" --activate ;;
pnpm-11) corepack prepare "pnpm@11.13.1" --activate ;;
npm) echo "Using bundled npm $(npm --version)" ;;
yarn) corepack prepare "yarn@1.22.22" --activate ;;
# yarn classic and berry both use the `yarn` npm package;
# only the version line differs (1.x vs 4.x).
yarn-berry) corepack prepare "yarn@4.14.1" --activate ;;
esac
# bun isn't on corepack's package list and its official `curl |
# bash` installer is POSIX-only, so on Windows it'd silently no-op.
# `oven-sh/setup-bun` handles all three OSes uniformly and adds
# bun to PATH via $GITHUB_PATH for us.
#
# Pinning the version (rather than `latest`) keeps the matrix
# reproducible in lockstep with the corepack-prepared
# pnpm-*/yarn versions above. Copilot review on PR #99 flagged
# `bun-version: latest` as a way for an upstream bun release to
# silently flip CI green/red without a repo change. Bump in a
# dedicated commit when intentionally rolling forward.
- name: Setup bun
if: ${{ matrix.id == 'bun' }}
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: '1.3.13'
- name: Confirm pm version
run: |
# Run from $RUNNER_TEMP, not the workspace cwd. corepack
# enforces the workspace's `packageManager: pnpm@11.13.1` and
# rejects `yarn --version` / `bun --version` with "This project
# is configured to use pnpm". Stepping out into a directory
# without a packageManager field lets corepack fall back to the
# version we just `--activate`d for the matrix pm, which is
# also more useful diagnostically (it prints the matrix
# version, not the workspace's pinned 11.13.1 for pnpm-N
# entries).
cd "$RUNNER_TEMP"
case "${{ matrix.id }}" in
pnpm-*) pnpm --version ;;
npm) npm --version ;;
yarn|yarn-berry) yarn --version ;;
bun) bun --version ;;
esac
# `ARKOR_INTERNAL_SCAFFOLD_ARKOR_SPEC` is deliberately NOT
# set at the job level. The real-install matrix tests
# (`runs real <pm> install + git commit` in
# `e2e/cli/src/{arkor-init,create-arkor}.test.ts`) pack
# their own `arkor-*.tgz` in `beforeAll` and pass a per-case
# `file:../<tgz>` override into `runCli` / `runCreateArkor`,
# which is required because pnpm 10 rejects absolute
# Windows-drive `file:` URIs (`file:D:\…`) and bun on Windows
# skips `bun.lock` generation for multi-segment relative
# `file:` paths. Setting a job-wide absolute fallback here
# would mask that requirement. A future test that performs
# a real install without overriding would silently pass on
# Linux/macOS and break only on Windows × pnpm-10. (PR #99
# round-39 Copilot review: that earlier fallback predated
# the per-case override and is now vestigial.)
#
# `YARN_NETWORK_CONCURRENCY=1` (yarn 1 optionalDeps
# extraction race) lives in e2e/cli/src/spawn-cli.ts, set
# per spawn so local `ARKOR_E2E_PM=yarn-berry pnpm --filter
# @arkor/e2e-cli test` runs work without extra env-var
# threading. The round-13 yarn-4 immutable-install fix lives
# in `packages/cli-internal/src/install.ts` itself (the SDK
# forwards `YARN_ENABLE_IMMUTABLE_INSTALLS=false` to the
# spawned `yarn install`), so real users running `arkor init
# --use-yarn` in their CI no longer fail on the missing
# initial lockfile.
- name: Run e2e suite under matrix pm
run: |
# Derive the e2e install-test gate label from the matrix id.
# yarn-berry and the pnpm-N entries share a common label
# across versions because the test only cares about which
# `--use-<flag>` to pass; the binary version is selected by
# corepack / setup-bun above.
#
# The label vocabulary lives in
# `e2e/cli/src/install-matrix.ts` (`InstallCaseLabel` /
# `INSTALL_CASES`). When adding or removing a matrix `id`
# here, update that file in lockstep; the e2e suite's gate
# function reads `ARKOR_E2E_PM` against the same label set.
case "${{ matrix.id }}" in
pnpm-*) PM_LABEL=pnpm ;;
yarn-berry) PM_LABEL=yarn-berry ;;
*) PM_LABEL="${{ matrix.id }}" ;;
esac
# We deliberately don't `-t`-filter to "real <pm> install":
# setup (workspace install + CLI build + corepack/bun) is the
# bulk of each job's wall time, so dropping the filter to run
# the full e2e suite is ~30 s/job extra and surfaces any
# cross-axis interactions we didn't anticipate (e.g. yarn-berry
# PnP affecting unrelated spawn paths). The install cases
# themselves are still pm-gated via `ARKOR_E2E_PM` so only one
# pm's install assertions run per job.
ARKOR_E2E_PM="$PM_LABEL" pnpm --filter @arkor/e2e-cli exec vitest run
coverage:
name: coverage · upload to Codecov
runs-on: ubuntu-latest
# The matrix above already exercises tests across Node versions; this
# job runs once on the newest LTS to keep coverage + Test Analytics
# uploads stable. Codecov uploads use OIDC (`use_oidc: true`); no
# CODECOV_TOKEN secret is stored in this repo. `id-token: write` is
# required for the runner to mint the OIDC JWT that codecov-action
# exchanges for an upload token. See
# https://docs.codecov.com/docs/github-tokenless-uploads.
#
# `code-quality: write` is required by GitHub's native code-coverage
# public preview (announced 2026-05-26): `actions/upload-code-coverage`
# POSTs the Cobertura XML through the GitHub API using the workflow's
# default GITHUB_TOKEN, and the token needs that scope to write to
# the code-quality API. Coverage uploads to GitHub run in parallel
# with the Codecov uploads below; Codecov stays authoritative while
# the GitHub feature is in preview, so both signals are available
# side-by-side. See
# https://github.blog/changelog/2026-05-26-code-coverage-in-pull-requests-is-now-in-public-preview/.
permissions:
id-token: write
code-quality: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
# Same reason as the build job; see comment there.
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
with:
standalone: true
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '24'
cache: pnpm
- name: Cache Turborepo
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: .turbo/cache
key: turbo-${{ runner.os }}-coverage-${{ github.sha }}
restore-keys: |
turbo-${{ runner.os }}-coverage-
- name: Install
# Belt-and-suspenders: pnpm-workspace.yaml's `allowBuilds`
# already gates which packages may run postinstall, and
# `playwright` is not in that list, so `pnpm install` won't
# invoke Playwright's browser-download script today. Setting
# `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1` here keeps that
# contract explicit so a future `allowBuilds` change can't
# silently re-enable a 150 MB postinstall download that the
# following cache + `playwright install` steps are meant to
# satisfy.
env:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
run: pnpm install
# Mirror the matrix job's Playwright cache so the coverage run
# also reuses the previously-downloaded browsers.
- name: Cache Playwright browsers
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
playwright-${{ runner.os }}-
- name: Install Playwright
run: pnpm --filter @arkor/e2e-studio exec playwright install --with-deps chromium
# Tests must keep going on failure so every package still produces
# its lcov.info / junit.xml; turbo's default `--continue=never` would
# cancel sibling tasks after the first failure and starve the upload
# steps below. Subsequent uploads gate on `!cancelled()` so they run
# even when tests fail; the job's final outcome still reflects the
# test result.
- name: Test with coverage
env:
ARKOR_INTERNAL_SCAFFOLD_ARKOR_SPEC: file:${{ github.workspace }}/packages/arkor
# `create-arkor`'s tsdown only emits sourcemaps when this is
# set; e2e/cli's c8 needs them to remap V8 coverage from the
# spawned scaffolder back to packages/create-arkor/src/**.
# Replaces the inline `CREATE_ARKOR_BUILD_SOURCEMAP=1 ...`
# that lived in e2e/cli's pretest:coverage hook before
# workspace deps + turbo's `^build` made that pretest
# redundant.
CREATE_ARKOR_BUILD_SOURCEMAP: "1"
run: pnpm exec turbo run test:coverage --continue
- name: Upload arkor coverage
if: ${{ !cancelled() }}
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
use_oidc: true
disable_search: true
files: ./packages/arkor/coverage/lcov.info
flags: arkor
fail_ci_if_error: false
- name: Upload create-arkor coverage
if: ${{ !cancelled() }}
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
use_oidc: true
disable_search: true
files: ./packages/create-arkor/coverage/lcov.info
flags: create-arkor
fail_ci_if_error: false
- name: Upload cli-internal coverage
if: ${{ !cancelled() }}
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
use_oidc: true
disable_search: true
files: ./packages/cli-internal/coverage/lcov.info
flags: cli-internal
fail_ci_if_error: false
- name: Upload studio-app coverage
if: ${{ !cancelled() }}
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
use_oidc: true
disable_search: true
files: ./packages/studio-app/coverage/lcov.info
flags: studio-app
fail_ci_if_error: false
# E2E coverage is collected via c8 wrapping vitest in e2e/cli; the
# spawned CLI children inherit NODE_V8_COVERAGE and their hits are
# remapped through tsdown's sourcemaps back into
# packages/{arkor,create-arkor}/src/**.
- name: Upload e2e-cli coverage
if: ${{ !cancelled() }}
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
use_oidc: true
disable_search: true
files: ./e2e/cli/coverage/lcov.info
flags: e2e-cli
fail_ci_if_error: false
# GitHub native code coverage (public preview, announced 2026-05-26).
# Uploads the Cobertura XML produced alongside lcov.info by each
# package's vitest v8 coverage reporter so GitHub can display
# per-PR coverage % directly on pull requests. e2e/cli is excluded:
# see the skip note below for the c8 / cobertura empty-output
# rationale.
# Codecov stays authoritative while the feature is in preview; once
# GitHub Coverage reaches GA we can decide whether to drop one path.
# `fail-on-error: false` mirrors the Codecov uploads' philosophy:
# an upload glitch must not turn a green test run red. The action
# itself already no-ops on `merge_group` events and fork PRs, so no
# extra `if:` gating is needed for those cases.
- name: Upload arkor coverage to GitHub
if: ${{ !cancelled() }}
uses: actions/upload-code-coverage@abb5995db9e0199b0e2bb9dbd136fce4cb1ec4d3 # v1.3.0
with:
file: ./packages/arkor/coverage/cobertura-coverage.xml
language: TypeScript
label: code-coverage/arkor
fail-on-error: false
- name: Upload create-arkor coverage to GitHub
if: ${{ !cancelled() }}
uses: actions/upload-code-coverage@abb5995db9e0199b0e2bb9dbd136fce4cb1ec4d3 # v1.3.0
with:
file: ./packages/create-arkor/coverage/cobertura-coverage.xml
language: TypeScript
label: code-coverage/create-arkor
fail-on-error: false
- name: Upload cli-internal coverage to GitHub
if: ${{ !cancelled() }}
uses: actions/upload-code-coverage@abb5995db9e0199b0e2bb9dbd136fce4cb1ec4d3 # v1.3.0
with:
file: ./packages/cli-internal/coverage/cobertura-coverage.xml
language: TypeScript
label: code-coverage/cli-internal
fail-on-error: false
- name: Upload studio-app coverage to GitHub
if: ${{ !cancelled() }}
uses: actions/upload-code-coverage@abb5995db9e0199b0e2bb9dbd136fce4cb1ec4d3 # v1.3.0
with:
file: ./packages/studio-app/coverage/cobertura-coverage.xml
language: TypeScript
label: code-coverage/studio-app
fail-on-error: false
# e2e/cli intentionally does NOT upload a Cobertura report to GitHub.
# c8's cobertura reporter currently emits an empty 402-byte shell
# here: the istanbul cobertura writer doesn't pick up c8's
# sourcemap-remapped coverage of the spawned CLI children the way
# the lcov reporter does. The Codecov upload above stays on the
# lcov path, which has produced usable signal historically; once
# the c8 ↔ cobertura gap is closed (or we switch e2e/cli off c8),
# re-add this upload alongside the others.
# Codecov Test Analytics: uploads JUnit XML produced by the vitest
# `junit` reporter. Tracks pass/fail history per test, surfaces flaky
# tests, and shows failure trends on the Codecov dashboard. See
# https://docs.codecov.com/docs/test-analytics.
- name: Upload arkor test results
if: ${{ !cancelled() }}
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
use_oidc: true
disable_search: true
report_type: test_results
files: ./packages/arkor/coverage/junit.xml
flags: arkor
fail_ci_if_error: false
- name: Upload create-arkor test results
if: ${{ !cancelled() }}
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
use_oidc: true
disable_search: true
report_type: test_results
files: ./packages/create-arkor/coverage/junit.xml
flags: create-arkor
fail_ci_if_error: false
- name: Upload cli-internal test results
if: ${{ !cancelled() }}
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
use_oidc: true
disable_search: true
report_type: test_results
files: ./packages/cli-internal/coverage/junit.xml
flags: cli-internal
fail_ci_if_error: false
- name: Upload studio-app test results
if: ${{ !cancelled() }}
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
use_oidc: true
disable_search: true
report_type: test_results
files: ./packages/studio-app/coverage/junit.xml
flags: studio-app
fail_ci_if_error: false
- name: Upload e2e-cli test results
if: ${{ !cancelled() }}
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
use_oidc: true
disable_search: true
report_type: test_results
files: ./e2e/cli/coverage/junit.xml
flags: e2e-cli
fail_ci_if_error: false
# Studio E2E reports JUnit XML produced by the Playwright junit
# reporter (see e2e/studio/playwright.config.ts). No lcov: browser
# tests don't collect server-side V8 coverage, so we only feed
# Test Analytics for pass/fail trend tracking.
- name: Upload e2e-studio test results
if: ${{ !cancelled() }}
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
use_oidc: true
disable_search: true
report_type: test_results
files: ./e2e/studio/coverage/junit.xml
flags: e2e-studio
fail_ci_if_error: false