Skip to content

Commit 42ea336

Browse files
Alex Steleaclaude
andauthored
Add GitHub Actions workflow for code quality checks (#196)
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 13ff47f commit 42ea336

607 files changed

Lines changed: 29879 additions & 32216 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/settings.local.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@
2121
"Bash(pnpm -w run build)",
2222
"Bash(pnpm -w run clean)",
2323
"Bash(./build-docker.sh:*)",
24-
"Bash(npx tsc:*)"
24+
"Bash(npx tsc:*)",
25+
"Bash(ls:*)",
26+
"Bash(npx yaml-lint:*)",
27+
"Bash(gh pr create:*)",
28+
"Bash(gh workflow:*)"
2529
],
2630
"deny": []
2731
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
name: Code Quality
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
- release/**
8+
push:
9+
branches:
10+
- main
11+
workflow_dispatch:
12+
13+
concurrency:
14+
group: ${{ github.workflow }}-${{ github.ref }}
15+
cancel-in-progress: true
16+
17+
permissions:
18+
contents: read
19+
pull-requests: write
20+
pages: write
21+
id-token: write
22+
23+
jobs:
24+
lint-test-build:
25+
name: Lint, Test, and Build
26+
runs-on: ubuntu-latest
27+
28+
services:
29+
redis:
30+
image: redis:7-alpine
31+
ports:
32+
- 6379:6379
33+
options: >-
34+
--health-cmd "redis-cli ping"
35+
--health-interval 10s
36+
--health-timeout 5s
37+
--health-retries 5
38+
39+
steps:
40+
- name: Checkout code
41+
uses: actions/checkout@v4
42+
with:
43+
fetch-depth: 0
44+
45+
- name: Setup pnpm
46+
uses: pnpm/action-setup@v4
47+
with:
48+
version: 9.15.0
49+
50+
- name: Setup Node.js
51+
uses: actions/setup-node@v4
52+
with:
53+
node-version: '20'
54+
cache: 'pnpm'
55+
56+
- name: Install dependencies
57+
run: pnpm install --frozen-lockfile
58+
59+
- name: Run lint
60+
run: pnpm check-types
61+
62+
- name: Run tests
63+
env:
64+
REDIS_URL: redis://localhost:6379
65+
DATABASE_URL: postgres://postgres:password@localhost:5432/radix-incentives
66+
TOKEN_PRICE_SERVICE_API_KEY: ${{ secrets.TOKEN_PRICE_SERVICE_API_KEY }}
67+
NODE_ENV: test
68+
SKIP_INTEGRATION_TESTS: true
69+
run: pnpm test:ci
70+
71+
- name: Build applications
72+
run: pnpm build --filter=workers --filter=streamer --filter=incentives --filter=admin
73+
env:
74+
DATABASE_URL: postgres://postgres:password@localhost:5432/radix-incentives
75+
WORKERS_API_BASE_URL: http://localhost:3003
76+
77+
- name: Upload test coverage
78+
if: always()
79+
uses: actions/upload-artifact@v4
80+
with:
81+
name: coverage
82+
path: packages/api/coverage
83+
retention-days: 7
84+
85+
- name: Coverage Report as Comment
86+
if: github.event_name == 'pull_request'
87+
run: |
88+
if [ -f packages/api/coverage/coverage-summary.json ]; then
89+
COVERAGE=$(cat packages/api/coverage/coverage-summary.json | jq -r '
90+
"## 📊 Test Coverage Report\n\n" +
91+
"| Metric | Coverage |\n" +
92+
"|--------|----------|\n" +
93+
"| **Lines** | " + (.total.lines.pct|tostring) + "% |\n" +
94+
"| **Functions** | " + (.total.functions.pct|tostring) + "% |\n" +
95+
"| **Branches** | " + (.total.branches.pct|tostring) + "% |\n" +
96+
"| **Statements** | " + (.total.statements.pct|tostring) + "% |\n\n" +
97+
"### Summary\n" +
98+
"- **Total Lines:** " + (.total.lines.covered|tostring) + "/" + (.total.lines.total|tostring) + "\n" +
99+
"- **Total Functions:** " + (.total.functions.covered|tostring) + "/" + (.total.functions.total|tostring) + "\n" +
100+
"- **Total Branches:** " + (.total.branches.covered|tostring) + "/" + (.total.branches.total|tostring) + "\n" +
101+
"- **Total Statements:** " + (.total.statements.covered|tostring) + "/" + (.total.statements.total|tostring) + "\n\n" +
102+
"📋 **[View Detailed HTML Coverage Report](https://radixdlt.github.io/radix-incentives/)**\n\n" +
103+
"*Coverage report generated by Vitest*"
104+
')
105+
106+
gh pr comment ${{ github.event.pull_request.number }} --body "$COVERAGE" || echo "Failed to post coverage comment"
107+
else
108+
echo "Coverage summary file not found"
109+
fi
110+
env:
111+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
112+
113+
deploy-coverage:
114+
name: Deploy Coverage Report
115+
needs: lint-test-build
116+
runs-on: ubuntu-latest
117+
if: github.event_name == 'pull_request'
118+
119+
environment:
120+
name: github-pages
121+
url: ${{ steps.deployment.outputs.page_url }}
122+
123+
steps:
124+
- name: Download coverage artifact
125+
uses: actions/download-artifact@v4
126+
with:
127+
name: coverage
128+
path: ./coverage
129+
130+
- name: Setup Pages
131+
uses: actions/configure-pages@v4
132+
133+
- name: Upload to GitHub Pages
134+
uses: actions/upload-pages-artifact@v3
135+
with:
136+
path: ./coverage
137+
138+
- name: Deploy to GitHub Pages
139+
id: deployment
140+
uses: actions/deploy-pages@v4

.husky/pre-commit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pnpm check-types

apps/admin/components.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@
1818
"hooks": "~/hooks"
1919
},
2020
"iconLibrary": "lucide"
21-
}
21+
}

apps/admin/next.config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful
33
* for Docker builds.
44
*/
5-
import "./src/env.js";
5+
import './src/env.js';
66

77
/** @type {import("next").NextConfig} */
88
const config = {
9-
output: "standalone",
9+
output: 'standalone',
1010
};
1111

1212
export default config;

apps/admin/postcss.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export default {
22
plugins: {
3-
"@tailwindcss/postcss": {},
3+
'@tailwindcss/postcss': {},
44
},
55
};

apps/admin/src/app/activities/[id]/page.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
'use client';
22

3-
import * as React from 'react';
3+
import { ArrowLeft } from 'lucide-react';
44
import Link from 'next/link';
55
import { useParams, useRouter } from 'next/navigation';
6-
import { ArrowLeft } from 'lucide-react';
6+
import * as React from 'react';
77
import { toast } from 'sonner';
88

9+
import type {
10+
Activity,
11+
ActivityCategory,
12+
Dapp,
13+
UpdateActivityInput,
14+
} from 'api/incentives';
915
import { Button } from '~/components/ui/button';
1016
import {
1117
Card,
@@ -16,15 +22,9 @@ import {
1622
} from '~/components/ui/card';
1723
import { Input } from '~/components/ui/input';
1824
import { Label } from '~/components/ui/label';
19-
import { Textarea } from '~/components/ui/textarea';
20-
import { Switch } from '~/components/ui/switch';
2125
import { Separator } from '~/components/ui/separator';
22-
import type {
23-
Activity,
24-
ActivityCategory,
25-
Dapp,
26-
UpdateActivityInput,
27-
} from 'api/incentives';
26+
import { Switch } from '~/components/ui/switch';
27+
import { Textarea } from '~/components/ui/textarea';
2828
import { api } from '~/trpc/react';
2929

3030
// Reusable form component (can be extracted later)

apps/admin/src/app/activities/page.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
'use client';
22

3-
import * as React from 'react';
3+
import { ArrowDown, ArrowUp, ArrowUpDown, Search } from 'lucide-react';
44
import { useRouter } from 'next/navigation';
5-
import { Search, ArrowUpDown, ArrowUp, ArrowDown } from 'lucide-react';
5+
import * as React from 'react';
66

7+
import { Badge } from '~/components/ui/badge';
8+
import { Input } from '~/components/ui/input';
9+
import { Separator } from '~/components/ui/separator';
710
import {
811
Table,
912
TableBody,
@@ -12,9 +15,6 @@ import {
1215
TableHeader,
1316
TableRow,
1417
} from '~/components/ui/table';
15-
import { Badge } from '~/components/ui/badge';
16-
import { Separator } from '~/components/ui/separator';
17-
import { Input } from '~/components/ui/input';
1818
import { api } from '~/trpc/react';
1919

2020
type SortField =

apps/admin/src/app/api/trpc/[trpc]/route.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
2-
import type { NextRequest } from "next/server";
1+
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
2+
import type { NextRequest } from 'next/server';
33

4-
import { env } from "~/env";
54
import {
65
adminAppRouter,
76
appRouter,
87
createDependencyLayer,
9-
} from "api/incentives";
10-
import { createTRPCContext } from "api/incentives";
11-
import { db } from "db/incentives";
8+
} from 'api/incentives';
9+
import { createTRPCContext } from 'api/incentives';
10+
import { db } from 'db/incentives';
11+
import { env } from '~/env';
1212

1313
/**
1414
* This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when
@@ -32,15 +32,15 @@ const createContext = async (req: NextRequest) => {
3232

3333
const handler = (req: NextRequest) =>
3434
fetchRequestHandler({
35-
endpoint: "/api/trpc",
35+
endpoint: '/api/trpc',
3636
req,
3737
router: adminAppRouter,
3838
createContext: () => createContext(req),
3939
onError:
40-
env.NODE_ENV === "development"
40+
env.NODE_ENV === 'development'
4141
? ({ path, error }) => {
4242
console.error(
43-
`❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`
43+
`❌ tRPC failed on ${path ?? '<no-path>'}: ${error.message}`,
4444
);
4545
}
4646
: undefined,

apps/admin/src/app/component-whitelist/page.tsx

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
"use client";
1+
'use client';
22

3-
import { useState, useRef } from "react";
4-
import { api } from "~/trpc/react";
5-
import { Button } from "~/components/ui/button";
3+
import { File, FileText, Loader2, Upload } from 'lucide-react';
4+
import { useRef, useState } from 'react';
5+
import { Alert, AlertDescription } from '~/components/ui/alert';
6+
import { Button } from '~/components/ui/button';
67
import {
78
Card,
89
CardContent,
910
CardDescription,
1011
CardHeader,
1112
CardTitle,
12-
} from "~/components/ui/card";
13-
import { Alert, AlertDescription } from "~/components/ui/alert";
14-
import { Loader2, Upload, FileText, File } from "lucide-react";
13+
} from '~/components/ui/card';
14+
import { api } from '~/trpc/react';
1515

1616
export default function ComponentWhitelistPage() {
1717
const [selectedFile, setSelectedFile] = useState<File | null>(null);
@@ -32,7 +32,7 @@ export default function ComponentWhitelistPage() {
3232
setIsUploading(false);
3333
setSelectedFile(null);
3434
if (fileInputRef.current) {
35-
fileInputRef.current.value = "";
35+
fileInputRef.current.value = '';
3636
}
3737
refetchStats();
3838
},
@@ -49,12 +49,12 @@ export default function ComponentWhitelistPage() {
4949
const file = event.target.files?.[0];
5050
if (file) {
5151
if (
52-
file.type !== "text/csv" &&
53-
!file.name.toLowerCase().endsWith(".csv")
52+
file.type !== 'text/csv' &&
53+
!file.name.toLowerCase().endsWith('.csv')
5454
) {
5555
setUploadResult({
5656
success: false,
57-
message: "Please select a CSV file",
57+
message: 'Please select a CSV file',
5858
});
5959
return;
6060
}
@@ -70,9 +70,9 @@ export default function ComponentWhitelistPage() {
7070
};
7171

7272
const handleFileInputKeyDown = (
73-
e: React.KeyboardEvent<HTMLButtonElement>
73+
e: React.KeyboardEvent<HTMLButtonElement>,
7474
) => {
75-
if (e.key === "Enter" || e.key === " ") {
75+
if (e.key === 'Enter' || e.key === ' ') {
7676
e.preventDefault();
7777
fileInputRef.current?.click();
7878
}
@@ -82,7 +82,7 @@ export default function ComponentWhitelistPage() {
8282
if (!selectedFile) {
8383
setUploadResult({
8484
success: false,
85-
message: "Please select a CSV file",
85+
message: 'Please select a CSV file',
8686
});
8787
return;
8888
}
@@ -96,7 +96,7 @@ export default function ComponentWhitelistPage() {
9696
} catch (error) {
9797
setUploadResult({
9898
success: false,
99-
message: "Failed to read CSV file",
99+
message: 'Failed to read CSV file',
100100
});
101101
setIsUploading(false);
102102
}
@@ -129,7 +129,7 @@ export default function ComponentWhitelistPage() {
129129
<CardContent>
130130
<div>
131131
<p className="text-2xl font-bold">
132-
{whitelistStats?.count?.toLocaleString() ?? "Loading..."}
132+
{whitelistStats?.count?.toLocaleString() ?? 'Loading...'}
133133
</p>
134134
<p className="text-muted-foreground">Components in whitelist</p>
135135
</div>
@@ -219,7 +219,7 @@ export default function ComponentWhitelistPage() {
219219
e.stopPropagation();
220220
setSelectedFile(null);
221221
if (fileInputRef.current) {
222-
fileInputRef.current.value = "";
222+
fileInputRef.current.value = '';
223223
}
224224
setUploadResult(null);
225225
}}
@@ -253,7 +253,7 @@ export default function ComponentWhitelistPage() {
253253

254254
{/* Results */}
255255
{uploadResult && (
256-
<Alert variant={uploadResult.success ? "default" : "destructive"}>
256+
<Alert variant={uploadResult.success ? 'default' : 'destructive'}>
257257
<AlertDescription>
258258
{uploadResult.message}
259259
{uploadResult.count && (

0 commit comments

Comments
 (0)