Commit a53fab5
authored
Speed up CI/CD runs, scope the developer CLI, and harden workflow hygiene (#895)
### Summary & Motivation
A coordinated refresh of the CI/CD pipelines and developer CLI: faster
runs, scoped commands, better observability, and consistent workflow
conventions.
#### Test infrastructure
`BackOfficeEndpointBaseTest` and `EndpointBaseTest<TContext>` built a
fresh `WebApplicationFactory<Program>` per test instance. With
`Meziantou.Xunit.ParallelTestFramework`, every Account-API and
BackOffice test paid a full ASP.NET Core cold-start. Both bases now
share one host per test class via `IClassFixture<...>`, routing per-test
state (SqliteConnection, telemetry collector, MockStripeState,
`IEmailClient`) through `AsyncLocal`. New types:
`BackOfficeWebApplicationFactory` + `BackOfficeTestContext` (26 derived
classes) and `AccountWebApplicationFactory` + `AccountTestContext` (60
derived classes). `MockStripeState` becomes transient so per-request
resolution reads the per-test instance;
`TestServer.PreserveExecutionContext = true` keeps the AsyncLocal
flowing into request handling.
#### Single Code Style workflow
A new `code-style.yml` replaces the per-SCS code-style work in
`account.yml`, `main.yml`, `app-gateway.yml`. Four jobs:
- `detect-scope` — classifies the diff via inline `git diff`, outputs
the backend scope and format mode for the downstream jobs.
- `code-linting` — backend inspectcode + frontend build + frontend lint
(oxlint).
- `code-formatting` — backend cleanupcode + frontend format check
(oxfmt).
- `sonarcloud` — runs once per push (previously three times, once per
SCS), sequentially per slnf to keep test assemblies in separate
processes and avoid the `PortAllocation.Load()` race.
Backend lint and format are scoped to a single SCS via
`--self-contained-system <name>` when the diff touches only that SCS;
cross-cutting changes fall back to the full solution. Code coverage is
out of scope here — the dotCover path repeatedly exceeded 20 minutes
against the new IClassFixture infrastructure and is tracked separately.
#### AppGateway tests now run in CI
`AppGateway.Tests` existed in the solution but was never executed — the
slnf-scoped runs missed it, and `app-gateway.yml` had no test step. The
workflow now runs `dotnet test AppGateway.Tests/AppGateway.Tests.csproj
--no-build` with the same user-secret bootstrap the other workflows use.
#### Developer CLI: `--gateway/-g` scope flag
`build`, `test`, `format`, and `lint` accept `--gateway`/`-g` to scope
to AppGateway only (mutually exclusive with `-s`). `pp test -g` targets
`AppGateway.Tests/AppGateway.Tests.csproj`; lint/format scope JetBrains
tools to `AppGateway/**` + `AppGateway.Tests/**`. Shared logic in
`AppGatewayHelper`.
#### Faster format and lint
- `format` defaults to changed-only (`.cs` files diffed vs
`origin/main`); `--all-files` for full sweep. CI auto-flips to
`--all-files` when `application/dotnet-tools.json` changes, so JetBrains
tool upgrades catch latent drift.
- `lint --changed-only` is opt-in; CI lints full because inspectcode has
cross-file rules.
- Dropped the temp-`.slnf` workaround for `cleanupcode` — JetBrains
2026.1 accepts `.slnx` directly.
#### Workflow hygiene
- **Concurrency** on every top-level workflow with `cancel-in-progress:
true` (and `false` for reusable deploy workflows so mid-deploy
cancellations cannot leave a half-applied state).
- **Inspection results on failure**: `code-style.yml` and
`developer-cli.yml` dump `result.json` to the run console and
`$GITHUB_STEP_SUMMARY`. Replaces the brittle `grep | tee` exit-code
contract.
- Removed leftover SonarScanner steps and Java JDK setup from
`account.yml` and `main.yml` (centralized in `code-style.yml`).
- Removed stale `_preview-migrations.yml` trigger-path references (file
does not exist).
- YAML hygiene pass across all workflows: blank lines, canonical
argument order, trailing whitespace.
#### Database Plan reuses Build and Test artifacts
`build-and-test` uploads `application/**/bin` and `application/**/obj`
as a `<scs>-build` artifact (when staging is enabled).
`_migrate-database.yml` downloads it and restores NuGet, skipping the
redundant checkout + setup + build chain.
#### E2E test fix piggy-backed on this branch
Two e2e tests inherited from the recently-merged feature-flags work
failed on a default-seeded database (`feature-flag-flows.spec.ts:262`,
`user-management-flows.spec.ts:473`). Root cause: `account-overview` and
`compact-view` are registered with `isKillSwitchEnabled: true`, so the
startup reconciler creates them inactive (`EnabledAt = null`); the
configurable-flags handlers filter inactive rows out, the API returns
empty, and `FeaturesSection` / `PreferencesFeatureFlagsSection` render
`null`. The tests now activate the kill-switch flags via the back-office
activate endpoint during setup — test-only fix, no production code, no
seeder change.
#### CI benchmark
The biggest CI win comes from the **Database Staging migration-skip
gate** on `account.yml` / `main.yml` — PRs that do not change EF
migration files skip the Database Staging job entirely.
| Workflow | BEFORE | AFTER, no migrations | AFTER, with migrations | Δ
(no migrations) | Δ (with migrations) |
|---|---|---|---|---|---|
| Account | 9m00s | 2m49s | 6m17s | -69% | -30% |
| Main | 6m41s | 1m55s | 5m40s | -71% | -15% |
| AppGateway | 7m26s | 1m26s | 1m26s | -81% | -81% |
| Code Style | - | 4m48s | 4m48s | new | new |
| **Critical path per push** | **9m00s** | **4m48s** | **6m17s** |
**-47%** | **-30%** |
| Total CI minutes per push | 23m07s | 15m19s | 22m34s | -34% | -2% |
AppGateway's delta is large because `code-linting` and `code-formatting`
moved out of `app-gateway.yml` into the consolidated `code-style.yml`.
On the no-migrations path the critical path now sits on `Code Style`
(parallel lint/format/sonar jobs each paying setup); sharing the backend
build artifact across them is an obvious follow-up. On the
with-migrations path the critical path stays on `account.yml`.
Downstream projects with a substantial Main SCS will see a bigger lift
than this suggests — the slowest CI step used to be backend
`cleanupcode` running on every file; it now inspects only changed `.cs`
files. Main and Account now sit close in wall-clock despite Account
having materially more code, because the npm + dotnet restore + build
setup now dominates.
#### Test-step benchmark (isolated)
Test-host refactor measured in isolation (Code Style and Database
Staging disabled; 5 samples per side):
| | Run 1 | Run 2 | Run 3 | Run 4 | Run 5 | Mean | Stddev |
|---|---|---|---|---|---|---|---|
| AFTER (BackOffice + Account shared host) | 2m18s | 2m26s | 2m21s |
2m21s | 2m21s | **2m21s** | 2.9s |
| BEFORE (per-test host, baseline) | 3m46s | 3m51s | 3m38s | 3m41s |
3m54s | **3m46s** | 6.7s |
Δ -37% on the test step alone, stddev more than halves — the runs are
markedly more stable too.
### Checklist
- [x] I have added tests, or done manual regression tests
- [x] I have updated the documentation, if necessary124 files changed
Lines changed: 1354 additions & 732 deletions
File tree
- .claude/skills
- format
- lint
- .github/workflows
- application
- account
- Tests
- ArchitectureTests
- Authentication
- BackOffice
- BillingDrift
- Dashboard
- FeatureFlags
- Billing
- EmailAuthentication
- FeatureFlags
- Signups
- Subscriptions
- Domain
- Tenants
- BackOffice
- Users
- BackOffice
- Workers
- WebApp/tests/e2e
- developer-cli
- Commands
- Utilities
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
9 | | - | |
| 9 | + | |
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| 19 | + | |
19 | 20 | | |
20 | | - | |
| 21 | + | |
21 | 22 | | |
22 | 23 | | |
23 | 24 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
9 | | - | |
| 9 | + | |
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| 19 | + | |
19 | 20 | | |
20 | | - | |
| 21 | + | |
21 | 22 | | |
22 | 23 | | |
23 | 24 | | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
24 | 34 | | |
25 | 35 | | |
26 | 36 | | |
27 | | - | |
28 | | - | |
29 | | - | |
30 | | - | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
31 | 41 | | |
32 | 42 | | |
33 | 43 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
41 | 45 | | |
42 | 46 | | |
43 | 47 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
49 | 49 | | |
50 | 50 | | |
51 | 51 | | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
52 | 56 | | |
53 | 57 | | |
54 | 58 | | |
| |||
67 | 71 | | |
68 | 72 | | |
69 | 73 | | |
| 74 | + | |
70 | 75 | | |
71 | 76 | | |
72 | 77 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
35 | 39 | | |
36 | 40 | | |
37 | 41 | | |
| |||
68 | 72 | | |
69 | 73 | | |
70 | 74 | | |
71 | | - | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
72 | 85 | | |
73 | | - | |
| 86 | + | |
74 | 87 | | |
75 | 88 | | |
76 | 89 | | |
| |||
93 | 106 | | |
94 | 107 | | |
95 | 108 | | |
96 | | - | |
| 109 | + | |
97 | 110 | | |
98 | 111 | | |
99 | 112 | | |
| |||
102 | 115 | | |
103 | 116 | | |
104 | 117 | | |
105 | | - | |
| 118 | + | |
106 | 119 | | |
107 | 120 | | |
108 | 121 | | |
109 | 122 | | |
110 | | - | |
| 123 | + | |
111 | 124 | | |
112 | 125 | | |
113 | 126 | | |
| |||
122 | 135 | | |
123 | 136 | | |
124 | 137 | | |
125 | | - | |
| 138 | + | |
126 | 139 | | |
127 | | - | |
| 140 | + | |
128 | 141 | | |
129 | 142 | | |
130 | 143 | | |
131 | | - | |
| 144 | + | |
132 | 145 | | |
133 | 146 | | |
134 | 147 | | |
| |||
165 | 178 | | |
166 | 179 | | |
167 | 180 | | |
168 | | - | |
| 181 | + | |
169 | 182 | | |
170 | | - | |
| 183 | + | |
171 | 184 | | |
172 | | - | |
| 185 | + | |
173 | 186 | | |
174 | 187 | | |
175 | | - | |
| 188 | + | |
176 | 189 | | |
177 | 190 | | |
178 | 191 | | |
179 | 192 | | |
180 | 193 | | |
181 | | - | |
| 194 | + | |
182 | 195 | | |
183 | | - | |
| 196 | + | |
184 | 197 | | |
185 | 198 | | |
186 | 199 | | |
| |||
0 commit comments