Skip to content

delete: design skills #25

delete: design skills

delete: design skills #25

Workflow file for this run

name: CI
on:
push:
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
NODE_VERSION: "26.2.0"
PNPM_VERSION: "11.2.2"
jobs:
quality:
name: Quality Check
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
with:
version: ${{ env.PNPM_VERSION }}
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Takumi Guard
uses: flatt-security/setup-takumi-guard-npm@8f53b50568e4466f2d92504f349c05b9ffcb8b59 # v1
- name: Install dependencies
run: pnpm install --frozen-lockfile --prefer-offline
- name: Oxfmt Check
run: pnpm run fmt:check
- name: Oxlint Check
run: pnpm run lint
- name: TypeScript Check
run: pnpm exec tsc --noEmit
- name: Docker Compose Config Check
env:
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY: ci-anon-key
SUPABASE_SERVICE_ROLE_KEY: ci-service-role-key
CLOUDFLARE_TUNNEL_TOKEN: ci-cloudflare-token
run: |
docker compose -f compose.dev.yml config --quiet
docker compose --env-file .env.production.example -f compose.prod.yml -f compose.vps.yml config --quiet
docker compose --env-file .env.production.example -f compose.prod.yml -f compose.cloudflare.yml config --quiet
react-doctor:
name: React Doctor
runs-on: ubuntu-latest
env:
LANG: C.UTF-8
LC_ALL: C.UTF-8
NO_COLOR: "1"
FORCE_COLOR: "0"
permissions:
contents: read
issues: write
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: "24"
- name: Run React Doctor
id: run_doctor
run: |
OUTPUT=$(npx -y react-doctor@0.5.6 . --project 45th-homepage --verbose --fail-on none)
echo "$OUTPUT"
DIAG_PATH=$(echo "$OUTPUT" | grep "Full diagnostics written to" | awk '{print $NF}')
echo "diag_path=$DIAG_PATH" >> "$GITHUB_OUTPUT"
SCORE=$(echo "$OUTPUT" | grep -oE '[0-9]+ / 100' | head -n 1 | awk '{print $1}')
echo "score=$SCORE" >> "$GITHUB_OUTPUT"
- name: Comment React Doctor Report
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
if: ${{ always() && github.event_name == 'pull_request' }}
env:
DIAG_PATH: ${{ steps.run_doctor.outputs.diag_path }}
SCORE: ${{ steps.run_doctor.outputs.score }}
with:
script: |
const fs = require('fs');
const path = require('path');
const diagPath = process.env.DIAG_PATH;
const score = parseInt(process.env.SCORE || '0');
let comment = '## 🩺 React Doctor Report\n\n';
const getHealthColor = (s) => {
if (s >= 90) return '🟢';
if (s >= 70) return '🟠';
return '🔴';
};
comment += `### Health Score: ${getHealthColor(score)} **${score} / 100**\n\n`;
try {
if (!diagPath) throw new Error('No diagnostics path found');
const diagFile = path.join(diagPath, 'diagnostics.json');
if (!fs.existsSync(diagFile)) throw new Error('diagnostics.json not found');
const diagnostics = JSON.parse(fs.readFileSync(diagFile, 'utf8'));
if (diagnostics.length === 0) {
comment += '✅ **No issues found! Your codebase is in great health.**\n';
} else {
const categories = {};
diagnostics.forEach(d => {
const cat = d.category || 'Other';
if (!categories[cat]) categories[cat] = [];
categories[cat].push(d);
});
comment += '| Category | Count |\n| --- | --- |\n';
Object.keys(categories).forEach(cat => {
comment += `| ${cat} | ${categories[cat].length} |\n`;
});
comment += '\n<details>\n<summary>🔍 View Detailed Findings</summary>\n\n';
Object.keys(categories).sort().forEach(cat => {
comment += `#### ${cat}\n`;
comment += '| File | Line | Issue | Help |\n| --- | --- | --- | --- |\n';
categories[cat].forEach(d => {
const fileLink = `[${d.filePath}](/${context.repo.owner}/${context.repo.repo}/blob/${context.sha}/${d.filePath}#L${d.line})`;
comment += `| ${fileLink} | ${d.line} | ${d.message} | ${d.help || '-'} |\n`;
});
comment += '\n';
});
comment += '</details>\n';
}
} catch (err) {
comment += `⚠️ Could not parse detailed diagnostics: ${err.message}\n`;
}
const marker = '<!-- react-doctor-report -->';
const body = `${marker}\n${comment}`;
const { data: comments } = await github.rest.issues.listComments({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
});
const botComment = comments.find(c => c.user.type === 'Bot' && c.body.includes(marker));
if (botComment) {
await github.rest.issues.updateComment({
comment_id: botComment.id,
owner: context.repo.owner,
repo: context.repo.repo,
body: body
});
} else {
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: body
});
}
build:
name: Build
runs-on: ubuntu-latest
needs: [quality, react-doctor]
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
with:
version: ${{ env.PNPM_VERSION }}
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Takumi Guard
uses: flatt-security/setup-takumi-guard-npm@8f53b50568e4466f2d92504f349c05b9ffcb8b59 # v1
- name: Install dependencies
run: pnpm install --frozen-lockfile --prefer-offline
- name: Build
run: pnpm run build