-
Notifications
You must be signed in to change notification settings - Fork 0
Add GitHub Actions CI with tests, linting, and security scanning #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add GitHub Actions CI with tests, linting, and security scanning #2
Conversation
📝 WalkthroughWalkthroughAdds GitHub automation (Dependabot, CI, CodeQL); extracts Electron auth into a dedicated context and updates frontend auth flows; stabilizes E2E tests by sharing API/Vite servers and applying migrations once; removes recurrence default fallbacks; several frontend lint, memoization, import, and small logic tweaks. Changes
Sequence Diagram(s)sequenceDiagram
participant Test as TestWorker
participant Fixture as GtdAppFixture
participant API as API Server
participant DB as Database
participant Vite as Vite Dev Server
Test->>Fixture: StartAsync()
alt API not started
Fixture->>API: acquire _apiServerLock & start API
API->>DB: apply migrations (guarded by _migrationLock)
DB-->>API: migrations applied
Fixture-->>API: release _apiServerLock
else API already started
Fixture-->>API: reuse shared API
end
alt Vite not started
Fixture->>Vite: acquire _viteServerLock & start Vite dev server
Vite-->>Fixture: return base URL
else
Fixture-->>Vite: reuse shared Vite
end
Fixture->>DB: ClearDatabaseAsync (guarded)
Fixture-->>Test: return readiness & BaseUrl
Test->>API: run E2E tests against shared servers
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Comment |
9931e4e to
d135f83
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In @.github/workflows/ci.yml:
- Around line 97-100: Remove the deprecated dotnet tool installation for
Microsoft.Playwright.CLI: delete the line that runs "dotnet tool install
--global Microsoft.Playwright.CLI" and keep the existing "playwright install
--with-deps chromium" step (or remove it if Playwright is already installed
after the project's "dotnet build"); ensure the workflow relies on the
Playwright CLI provided by the Microsoft.Playwright.MSTest.v4 package made
available after the "dotnet build" step so the CI uses the built-in "playwright"
command instead of installing the deprecated tool.
In @.github/workflows/codeql.yml:
- Around line 30-34: Update the GitHub Actions step that uses
actions/setup-dotnet@v4: change the dotnet-version value from '10.0.x' to a
currently available SDK (e.g., '9.0.x') so the C# matrix job can run; locate the
step named "Setup .NET" and update the dotnet-version input for the
actions/setup-dotnet@v4 action accordingly.
🧹 Nitpick comments (2)
.github/dependabot.yml (1)
1-28: Well-structured Dependabot configuration.The grouping of minor/patch updates for npm and NuGet is a good practice to reduce PR noise. Consider also adding a group for GitHub Actions updates for consistency:
# GitHub Actions - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" + groups: + actions-updates: + patterns: ["*"].github/workflows/ci.yml (1)
14-35: Consider adding NuGet caching for faster builds.The backend job could benefit from NuGet package caching to speed up dependency restoration:
- name: Setup .NET uses: actions/setup-dotnet@v4 with: dotnet-version: '10.0.x' + cache: true + cache-dependency-path: '**/packages.lock.json'Note: This requires
RestorePackagesWithLockFileto be enabled in your .NET projects, or you can useactions/cachewith the NuGet packages directory.
535c66a to
ef931fd
Compare
- CI workflow: backend build/tests, frontend lint/typecheck/tests, E2E tests - CodeQL security scanning for C# and TypeScript - Dependabot for automated dependency updates (npm, NuGet, Actions) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
CI/CD: - Add CI workflow with backend/frontend/E2E jobs - Add CodeQL security scanning for C# and TypeScript - Add Dependabot for npm, NuGet, and Actions updates Frontend: - Migrate from npm to pnpm (faster installs, stricter deps) - Fix ESLint errors: extract useElectronAuth hook, fix import order - Fix syncRules.test.ts to handle path reference in config Docs: - Remove outdated migration status from CLAUDE.md - Add pre-commit verification section - Update all npm commands to pnpm Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use built-in Playwright CLI from MSTest package instead of deprecated global tool - Add grouping for GitHub Actions in Dependabot config
- Add migration lock to prevent parallel test classes from racing - Skip migration if already applied (for parallel workers) - Disable CodeQL until Code Scanning is enabled in repo settings (kept as workflow_dispatch for future use)
d56def6 to
ec217b9
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@frontend/src/components/auth/ProtectedLayout.tsx`:
- Around line 40-51: The checkAuth function currently awaits
isElectronAuthenticated() without error handling, which can leave isLoading true
if the promise rejects; wrap the await in try/catch/finally inside checkAuth
(catch should setIsAuthenticated(false) or set an error state) and ensure
setIsLoading(false) is called in finally so loading never remains stuck; update
references to checkAuth, isElectronAuthenticated, setIsAuthenticated, and
setIsLoading accordingly.
In `@frontend/src/lib/powersync/syncRules.test.ts`:
- Around line 117-127: The current guard treats an empty string content as
absent and will also throw a generic TypeError if sync_rules is missing; update
the logic in the block that sets syncRulesText (look for parsedYaml.sync_rules,
syncRulesText, projectRoot, parseSyncRulesContent) to first check for a
non-empty content string (e.g., parsedYaml?.sync_rules?.content !== undefined &&
parsedYaml.sync_rules.content.trim() !== ''), use that when present, otherwise
if parsedYaml?.sync_rules?.path is present read the file with
fs.readFileSync(projectRoot + '/powersync/' + parsedYaml.sync_rules.path,
'utf-8'), and finally throw your specific Error('sync_rules must have either
content or path') when neither exists so missing sync_rules yields the custom
error rather than a TypeError.
- ElectronAuthPage: move early return after hooks (rules-of-hooks) - EditActionModal: add eslint-disable for legitimate state reset/init - RecurrenceEditor: add eslint-disable for controlled component sync
cbea61f to
96cd8ea
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
frontend/src/components/auth/ProtectedLayout.tsx (1)
40-55: Clear stale error state on successful auth checks.
If a previous check failed,errorcan remain visible even when a latercheckAuth()succeeds but returns unauthenticated. Consider resettingerroron success.✅ Proposed tweak
const checkAuth = useCallback(async () => { setIsLoading(true) try { const authenticated = await isElectronAuthenticated() setIsAuthenticated(authenticated) + setError(null) } catch (err) { setError(err instanceof Error ? err.message : 'Failed to check authentication') setIsAuthenticated(false) } finally { setIsLoading(false) } }, [])
- Fix set-state-in-effect errors with targeted suppressions for legitimate modal reset/init patterns in EditActionModal - Remove unnecessary optional chains on non-nullable values (electron.ts, syncRules.test.ts) - Remove redundant nullish coalescing where types guarantee values (fixedInterval, occurrenceCount, templatePriority have schema defaults) - Use .at(0) for proper undefined typing on array access (connector.ts) - Use 'key in' operator for proper object key narrowing (connector.ts) - Fix floating promise with void operator (ElectronAuthPage.tsx) - Remove dead code after exhaustive status checks (ElectronAuthPage.tsx) - Auto-fix import ordering (9 files) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
React Compiler: - Add babel-plugin-react-compiler and eslint-plugin-react-compiler - Configure Vite to use compiler babel plugin - Add compiler ESLint rules to catch violations - Fix ProcessPage.tsx: use ref patterns for intentional stale closures, add proper deps to effects and callbacks Docs: - Fix DOWNLOAD arrow direction in DATA_FLOW.md (was pointing wrong way) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The React Compiler changes in a2db170 added proper dependencies to effects in ProcessPage.tsx, but those dependencies (contexts, projects) were unstable - data.map() creates new arrays on every render. This caused effects to re-run excessively, which on slower CI runners led to navigation timeouts (10+ seconds to process all the re-renders). Fix: Add useMemo to useContexts and useProjects hooks to maintain stable array references when the underlying data hasn't changed. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Each test class was creating its own API server on a random port, but Vite was shared and configured with the first API's port. This could cause proxy mismatches when tests run in parallel. Fix: Share the API server instance like we share the DB container and Vite server. Each test just clears the database for isolation. Also adds RecurrencePatterns to the TRUNCATE statement. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@tests/OpenGTD.E2E/GtdAppFixture.cs`:
- Around line 120-198: The code can leak a partially-built WebApplication if an
exception occurs after builder.Build(); change EnsureApiServerAsync to build
into a local variable (e.g., var app = builder.Build()), perform migrations,
UseDeveloperExceptionPage, MapApiEndpoints, StartAsync and resolve the address
against that local app, and only after all steps succeed assign _sharedApp = app
and _sharedApiUrl = resolvedUrl; if any exception occurs while using the local
app, call app.DisposeAsync()/app.Dispose() (and set local to null) before
rethrowing so the partially-initialized instance is not orphaned; ensure locks
(_apiServerLock/_migrationLock) are still released in finally blocks.
🧹 Nitpick comments (3)
tests/OpenGTD.E2E/GtdAppFixture.cs (3)
238-243: Minor:GC.SuppressFinalizewithout a finalizer.
GC.SuppressFinalize(this)is called, but the class doesn't define a finalizer. This is harmless but technically unnecessary.
286-290: Minor TOCTOU race in port allocation.The port is allocated by binding to port 0, then released before Vite starts. Another process could theoretically claim the port in between. This is unlikely to cause issues in practice, but for robustness, consider using
--port 0with Vite (if supported) and parsing the actual port from stdout, or retrying on port conflicts.
315-331: Consider fail-fast if Vite process exits unexpectedly.The polling loop catches all exceptions during startup, which is reasonable for connection-refused errors. However, if the Vite process crashes during startup, the loop will wait the full 30 seconds before timing out.
Suggestion: Check for process exit in polling loop
for (var i = 0; i < 30; i++) // Max 30 seconds { await Task.Delay(100); + if (_viteProcess.HasExited) + { + throw new InvalidOperationException( + $"Vite dev server exited unexpectedly with code {_viteProcess.ExitCode}"); + } try {
The first tests in each class were failing because they hit an un-warmed React app. Vite HTTP responding doesn't mean the app is ready - the first real navigation triggers module loading, WASM initialization, and React app startup. Fix: After Vite is responding, do a warmup navigation with Playwright that actually loads the React app and waits for content to render. This ensures all initialization is complete before tests run. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Instead of hardcoding table names in TRUNCATE (which requires manual updates when adding new entities), query EF Core's model metadata for all table names dynamically. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@tests/OpenGTD.E2E/GtdAppFixture.cs`:
- Around line 216-224: The TRUNCATE SQL uses unqualified names from
GetTableName() which can mis-resolve across schemas; update the table name
construction in GtdAppFixture (the dbContext.Model.GetEntityTypes() pipeline) to
call e.GetSchema() and produce schema-qualified identifiers (e.g.
"schema"."table"), handling null/empty schema (use default schema or omit quotes
consistently) and then join those fully-qualified names into the TRUNCATE TABLE
... CASCADE statement before ExecuteSqlRawAsync.
- Around line 347-365: Move Playwright resource allocation into a safe disposal
pattern so partially-constructed resources aren't leaked: either use C# await
using for Playwright.CreateAsync(), playwright.Chromium.LaunchAsync(...) and
browser.NewPageAsync() (preferred) or allocate them inside the try block and
null-check them in the finally block before disposing/closing; update references
to Playwright.CreateAsync, playwright, browser, and page and ensure
playwright.DisposeAsync()/browser.CloseAsync()/page.CloseAsync() (or
DisposeAsync) are called only when the corresponding variable is non-null or via
await using so disposal always runs even if CreateAsync/LaunchAsync/NewPageAsync
throws.
♻️ Duplicate comments (1)
tests/OpenGTD.E2E/GtdAppFixture.cs (1)
160-193: Dispose partially-built API app on failure to avoid orphaned servers.If any exception occurs after
builder.Build()but before_sharedApiUrlis assigned, retries will leak the priorWebApplication. Consider building into a local variable, only assigning to_sharedApp/_sharedApiUrlafter success, and disposing on failure.🔧 Proposed fix
- _sharedApp = builder.Build(); + var app = builder.Build(); // Apply migrations once await _migrationLock.WaitAsync(); try { if (!_migrationApplied) { - using var scope = _sharedApp.Services.CreateScope(); + using var scope = app.Services.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService<GTDDbContext>(); await dbContext.Database.MigrateAsync(); _migrationApplied = true; } } finally { _migrationLock.Release(); } // For E2E tests, use developer exception page - _sharedApp.UseDeveloperExceptionPage(); + app.UseDeveloperExceptionPage(); // API endpoints only - frontend served by Vite dev server - _sharedApp.MapApiEndpoints(); + app.MapApiEndpoints(); - await _sharedApp.StartAsync(); + try + { + await app.StartAsync(); - var server = _sharedApp.Services.GetRequiredService<IServer>(); - var addresses = server.Features.Get<IServerAddressesFeature>()?.Addresses; - _sharedApiUrl = addresses?.FirstOrDefault() + var server = app.Services.GetRequiredService<IServer>(); + var addresses = server.Features.Get<IServerAddressesFeature>()?.Addresses; + var apiUrl = addresses?.FirstOrDefault() ?? throw new InvalidOperationException("Could not determine API server address"); - - return _sharedApiUrl; + _sharedApp = app; + _sharedApiUrl = apiUrl; + return apiUrl; + } + catch + { + await app.DisposeAsync(); + throw; + }
All test classes call DisposeAsync directly, and both methods were no-ops since shared resources are cleaned up at process exit. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1. EnsureApiServerAsync: Build into local variable, only assign to static fields after all steps succeed, dispose on failure 2. ClearDatabaseAsync: Use schema-qualified table names from EF Core model (e.GetSchema() + e.GetTableName()) 3. Playwright warmup: Use try/finally with null checks for proper disposal even if exceptions occur during initialization 4. Vite startup: Fail fast if process exits unexpectedly during polling loop instead of waiting full 30 second timeout Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Summary
Test plan
🤖 Generated with Claude Code
Summary by CodeRabbit
Chores
Bug Fixes / Reliability
User-facing
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.