Skip to content

Commit a35b395

Browse files
authored
v2.19.0 (#273)
2 parents 94dc40b + 61ef2f8 commit a35b395

123 files changed

Lines changed: 5756 additions & 2688 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.

.env.local.example

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,13 @@ NEXTAUTH_URL=
1010
NEXTAUTH_SECRET=
1111

1212
# GitHub token that have read access to MingdaoSIG-Frontend repo
13-
NEXT_PUBLIC_GITHUB_TOKEN=
13+
GITHUB_TOKEN=
1414

1515
# Discord webhook URL for displaying login log
1616
NEXT_PUBLIC_WEBHOOK_LOGIN=
1717

1818
# Local Only
1919
# NODE_ENV=development,no-strict
2020

21-
# Production Only
2221
# FORCE_CONTAINERIZED=1
2322
# NODE_OPTIONS=--max-old-space-size=512

.githooks/post-merge

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#!/usr/bin/env bash
2+
3+
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
4+
# MDSIG Frontend — Post-Merge Hook
5+
# Syncs deps, cleans stale caches, and notifies about env changes.
6+
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
7+
8+
# Colors
9+
_CLI_GREEN='\033[0;32m'
10+
_CLI_YELLOW='\033[0;33m'
11+
_CLI_CYAN='\033[0;36m'
12+
_CLI_PURPLE='\033[0;35m'
13+
_CLI_RED='\033[0;31m'
14+
_CLI_BLUE='\033[0;34m'
15+
_CLI_GRAY='\033[0;90m'
16+
_CLI_NC='\033[0m'
17+
18+
# Symbols
19+
_CLI_CHECK="${_CLI_GREEN}[✓]${_CLI_NC}"
20+
_CLI_CROSS="${_CLI_RED}[✗]${_CLI_NC}"
21+
_CLI_WARN="${_CLI_YELLOW}[!]${_CLI_NC}"
22+
_CLI_INFO="${_CLI_CYAN}[i]${_CLI_NC}"
23+
24+
# Helpers
25+
log_info() { echo -e "$_CLI_INFO $1"; }
26+
log_success() { echo -e "$_CLI_CHECK $1"; }
27+
log_warn() { echo -e "$_CLI_WARN $1"; }
28+
log_error() { echo -e "$_CLI_CROSS $1"; }
29+
30+
print_divider() {
31+
local color="${1:-$_CLI_PURPLE}" label="${2:-}"
32+
local width="${COLUMNS:-$(tput cols 2>/dev/null || echo 80)}"
33+
if [[ -n "$label" ]]; then
34+
local padding=$((width - ${#label} - 6))
35+
local fill
36+
printf -v fill '%*s' "$padding" ''
37+
printf '%b━━━━[%s]%s%b\n' "$color" "$label" "${fill// /━}" "$_CLI_NC"
38+
else
39+
local fill
40+
printf -v fill '%*s' "$width" ''
41+
printf '%b%s%b\n' "$color" "${fill// /━}" "$_CLI_NC"
42+
fi
43+
}
44+
45+
# Get list of files changed in this merge
46+
changed_files() {
47+
git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD 2>/dev/null
48+
}
49+
50+
# ── Main ──────────────────────────────────────────────────────────
51+
52+
echo ""
53+
print_divider "$_CLI_PURPLE" "Post-Merge"
54+
echo ""
55+
56+
CHANGED=$(changed_files)
57+
58+
# 1. Run pnpm install only if lockfile changed
59+
if echo "$CHANGED" | grep -qE "(pnpm-lock\.yaml|package\.json)"; then
60+
log_info "Dependencies changed — running ${_CLI_CYAN}pnpm install${_CLI_NC}..."
61+
if pnpm install; then
62+
echo ""
63+
log_success "Dependencies updated"
64+
else
65+
echo ""
66+
log_error "pnpm install failed — run it manually"
67+
fi
68+
else
69+
log_success "Dependencies unchanged — skipping install"
70+
fi
71+
72+
echo ""
73+
74+
# 2. Clean .next cache when next.config.js changed
75+
if echo "$CHANGED" | grep -qE "next\.config\.(js|mjs|ts)"; then
76+
log_warn "next.config changed — cleaning ${_CLI_BLUE}.next${_CLI_NC} cache..."
77+
if [[ -d ".next" ]]; then
78+
rm -rf .next
79+
log_success "Cleared ${_CLI_BLUE}.next${_CLI_NC} cache"
80+
log_info "Run ${_CLI_CYAN}pnpm dev${_CLI_NC} or ${_CLI_CYAN}pnpm build${_CLI_NC} to rebuild"
81+
else
82+
log_info "No .next cache to clean"
83+
fi
84+
echo ""
85+
fi
86+
87+
# 3. Notify when .env.local.example changed
88+
if echo "$CHANGED" | grep -q ".env.local.example"; then
89+
echo ""
90+
print_divider "$_CLI_YELLOW" "Environment Update Required"
91+
echo ""
92+
log_warn "The ${_CLI_BLUE}.env.local.example${_CLI_NC} file was updated!"
93+
echo ""
94+
log_info "Review the changes and update your local ${_CLI_BLUE}.env.local${_CLI_NC}:"
95+
echo ""
96+
echo -e " ${_CLI_CYAN}git diff ORIG_HEAD HEAD -- .env.local.example${_CLI_NC}"
97+
echo ""
98+
# Show the actual diff inline for convenience
99+
diff_output=$(git diff ORIG_HEAD HEAD -- .env.local.example 2>/dev/null)
100+
if [[ -n "$diff_output" ]]; then
101+
log_info "Changes:"
102+
echo ""
103+
echo "$diff_output" | while IFS= read -r line; do
104+
if [[ "$line" == +* && "$line" != "+++"* ]]; then
105+
echo -e " ${_CLI_GREEN}${line}${_CLI_NC}"
106+
elif [[ "$line" == -* && "$line" != "---"* ]]; then
107+
echo -e " ${_CLI_RED}${line}${_CLI_NC}"
108+
fi
109+
done
110+
echo ""
111+
fi
112+
print_divider "$_CLI_YELLOW"
113+
echo ""
114+
fi
115+
116+
echo ""
117+
print_divider "$_CLI_PURPLE"
118+
echo ""

.githooks/pre-push

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
#!/usr/bin/env bash
2+
3+
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
4+
# MDSIG Frontend — Pre-Push Hook
5+
# Runs security scans, lint/format, type-check, and build before pushing.
6+
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
7+
8+
TOTAL_STEPS=8
9+
BUNDLE_SIZE_LIMIT_MB=50
10+
11+
# Colors
12+
_CLI_RED='\033[0;31m'
13+
_CLI_GREEN='\033[0;32m'
14+
_CLI_YELLOW='\033[0;33m'
15+
_CLI_BLUE='\033[0;34m'
16+
_CLI_PURPLE='\033[0;35m'
17+
_CLI_CYAN='\033[0;36m'
18+
_CLI_GRAY='\033[0;90m'
19+
_CLI_NC='\033[0m'
20+
21+
# Symbols
22+
_CLI_CHECK="${_CLI_GREEN}[✓]${_CLI_NC}"
23+
_CLI_CROSS="${_CLI_RED}[✗]${_CLI_NC}"
24+
_CLI_WARN="${_CLI_YELLOW}[!]${_CLI_NC}"
25+
_CLI_INFO="${_CLI_CYAN}[i]${_CLI_NC}"
26+
27+
# Helpers
28+
log_info() { echo -e "$_CLI_INFO $1"; }
29+
log_success() { echo -e "$_CLI_CHECK $1"; }
30+
log_warn() { echo -e "$_CLI_WARN $1"; }
31+
log_error() { echo -e "$_CLI_CROSS $1"; }
32+
33+
print_divider() {
34+
local color="${1:-$_CLI_PURPLE}" label="${2:-}"
35+
local width="${COLUMNS:-$(tput cols 2>/dev/null || echo 80)}"
36+
if [[ -n "$label" ]]; then
37+
local padding=$((width - ${#label} - 6))
38+
local fill
39+
printf -v fill '%*s' "$padding" ''
40+
printf '%b━━━━[%s]%s%b\n' "$color" "$label" "${fill// /━}" "$_CLI_NC"
41+
else
42+
local fill
43+
printf -v fill '%*s' "$width" ''
44+
printf '%b%s%b\n' "$color" "${fill// /━}" "$_CLI_NC"
45+
fi
46+
}
47+
48+
run_step() {
49+
local step_num="$1"
50+
local step_name="$2"
51+
local step_cmd="$3"
52+
53+
log_info "${_CLI_GRAY}[${step_num}/${TOTAL_STEPS}]${_CLI_NC} Running ${_CLI_CYAN}${step_name}${_CLI_NC}..."
54+
55+
if eval "$step_cmd"; then
56+
log_success "${_CLI_GRAY}[${step_num}/${TOTAL_STEPS}]${_CLI_NC} ${step_name}"
57+
return 0
58+
else
59+
echo ""
60+
log_error "${_CLI_GRAY}[${step_num}/${TOTAL_STEPS}]${_CLI_NC} ${step_name} ${_CLI_RED}failed${_CLI_NC}"
61+
echo ""
62+
log_info "Fix the errors above, then try pushing again."
63+
echo ""
64+
print_divider "$_CLI_RED"
65+
echo ""
66+
return 1
67+
fi
68+
}
69+
70+
# ── Check Functions ───────────────────────────────────────────────
71+
72+
check_env_secrets() {
73+
local failed=0
74+
75+
# Check for .env files being pushed
76+
local env_files
77+
env_files=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null | grep -E '\.env(\..*)?\.local$' || true)
78+
if [[ -z "$env_files" ]]; then
79+
env_files=$(git diff origin/HEAD --name-only --diff-filter=ACM 2>/dev/null | grep -E '\.env(\..*)?\.local$' || true)
80+
fi
81+
82+
if [[ -n "$env_files" ]]; then
83+
echo ""
84+
log_error "Secret files detected in changes:"
85+
while IFS= read -r f; do
86+
echo -e " ${_CLI_RED}${_CLI_NC} $f"
87+
done <<< "$env_files"
88+
echo ""
89+
log_info "Add these to ${_CLI_CYAN}.gitignore${_CLI_NC} or unstage them."
90+
failed=1
91+
fi
92+
93+
# Grep source files for hardcoded secret patterns
94+
local secret_patterns='(GOOGLE_CLIENT_SECRET|NEXTAUTH_SECRET|GITHUB_TOKEN|NEXT_PUBLIC_WEBHOOK_LOGIN|DISCORD_WEBHOOK)\s*=\s*["\x27][^"\x27]{8,}'
95+
local hits
96+
hits=$(git diff origin/HEAD -- '*.ts' '*.tsx' '*.js' '*.jsx' 2>/dev/null \
97+
| grep -E '^\+' \
98+
| grep -iE "$secret_patterns" || true)
99+
100+
if [[ -n "$hits" ]]; then
101+
echo ""
102+
log_error "Possible hardcoded secrets in diff:"
103+
echo -e " ${_CLI_GRAY}${hits}${_CLI_NC}"
104+
failed=1
105+
fi
106+
107+
return $failed
108+
}
109+
110+
check_bundle_size() {
111+
if [[ ! -d ".next" ]]; then
112+
log_warn "No .next directory found — skipping bundle size check"
113+
return 0
114+
fi
115+
116+
local size_kb
117+
size_kb=$(du -sk .next 2>/dev/null | cut -f1)
118+
local size_mb=$(( size_kb / 1024 ))
119+
local limit_kb=$(( BUNDLE_SIZE_LIMIT_MB * 1024 ))
120+
121+
if [[ "$size_kb" -gt "$limit_kb" ]]; then
122+
echo ""
123+
log_error "Bundle size ${_CLI_RED}${size_mb}MB${_CLI_NC} exceeds limit of ${_CLI_CYAN}${BUNDLE_SIZE_LIMIT_MB}MB${_CLI_NC}"
124+
echo ""
125+
log_info "Check for large imports or unoptimized assets."
126+
return 1
127+
fi
128+
129+
log_info " Bundle size: ${_CLI_CYAN}${size_mb}MB${_CLI_NC} ${_CLI_GRAY}(limit: ${BUNDLE_SIZE_LIMIT_MB}MB)${_CLI_NC}"
130+
return 0
131+
}
132+
133+
# ── Main ──────────────────────────────────────────────────────────
134+
135+
echo ""
136+
print_divider "$_CLI_PURPLE" "Pre-Push Checks"
137+
echo ""
138+
log_info "Verifying codebase before push..."
139+
echo ""
140+
141+
# 1. Secret detection with gitleaks
142+
if command -v gitleaks &> /dev/null; then
143+
run_step 1 "gitleaks (secret scan)" "gitleaks protect --staged --verbose" || exit 1
144+
else
145+
log_warn "${_CLI_GRAY}[1/${TOTAL_STEPS}]${_CLI_NC} gitleaks not installed — skipping"
146+
log_info "Install with: ${_CLI_CYAN}brew install gitleaks${_CLI_NC}"
147+
echo ""
148+
fi
149+
150+
# 2. Check for .env secrets in staged files
151+
run_step 2 "env secrets check" "check_env_secrets" || exit 1
152+
153+
# 3–7. Install, lint, type-check, build
154+
run_step 3 "pnpm install" "pnpm install" || exit 1
155+
run_step 4 "pnpm run check:fix" "pnpm run check:fix" || exit 1
156+
run_step 5 "pnpm run check:fix-unsafe" "pnpm run check:fix-unsafe" || exit 1
157+
run_step 6 "pnpm run types" "pnpm run types" || exit 1
158+
run_step 7 "pnpm build" "pnpm build" || exit 1
159+
160+
# 8. Bundle size check (after build)
161+
run_step 8 "bundle size check" "check_bundle_size" || exit 1
162+
163+
echo ""
164+
log_success "All pre-push checks passed"
165+
echo ""
166+
print_divider "$_CLI_PURPLE"
167+
echo ""

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,7 @@ next-env.d.ts
4040
# Other Package Manager (Remove if needed, just one PM one time)
4141
bun.lockb
4242
package-lock.json
43+
44+
/.claude/worktrees
45+
/.claude/.recent_work_last_update
46+
/.claude/RECENT_WORK.md

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@
2121
"[xml]": {
2222
"editor.defaultFormatter": "redhat.vscode-xml"
2323
},
24-
"css.lint.unknownAtRules": "ignore",
24+
"css.lint.unknownAtRules": "ignore"
2525
}

AGENTS.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# MDSIG Frontend — Agent Guide
2+
3+
MDSIG is a community sharing platform for Mingdao High School students and teachers to exchange ideas and insights.
4+
5+
## Commands
6+
7+
```bash
8+
pnpm install # Install dependencies
9+
pnpm dev # Dev server (DO NOT use for build verification)
10+
pnpm build # Production build (USE THIS for build verification)
11+
pnpm types # Type check only (no emit)
12+
pnpm check:fix # Biome auto-fix (safe fixes only)
13+
```
14+
15+
## Code Style Rules (Biome)
16+
17+
Run `pnpm check` after every change. All generated code **must** follow these rules (enforced by Biome via `biome.json`):
18+
19+
### Formatting
20+
21+
- Use **2 spaces** for indentation, never tabs
22+
- Keep lines within **80 characters**
23+
- Always end statements with a **semicolon**
24+
- Use **double quotes** for strings and JSX attributes
25+
- Always include **trailing commas** in JS/TS (but never in JSON)
26+
- Always wrap arrow function parameters in **parentheses**: `(x) => x`
27+
- Include spaces inside braces: `{ value }`
28+
29+
### Code You Must Write
30+
31+
- Always use **block statements** (`{}`) with `if`, `else`, `for`, `while` — no single-line bodies
32+
- Use **`for...of`** instead of `.forEach()` for iteration
33+
- Use **`console.error`**, **`console.warn`**, **`console.info`**, or **`console.assert`** only — never bare `console.log`
34+
- Keep Tailwind CSS class names **sorted** (Biome enforces `useSortedClasses`)
35+
36+
### Code You Must Avoid
37+
38+
- Do NOT leave **unused imports** or **unused variables** — remove them
39+
- Do NOT use **array index as React key** when a stable identifier is available
40+
- Do NOT leave **empty block statements** (`{}`) — add a comment or remove the block
41+
- Do NOT write **`else` after `return`/`break`/`continue`** — use early return instead
42+
- Avoid **`any`** type — use a specific type whenever possible
43+
44+
### Rules That Are NOT Enforced
45+
46+
These rules are intentionally **off** — do not "fix" code to satisfy them:
47+
48+
- `useImportType` — using `import` instead of `import type` is fine
49+
- `useExhaustiveDependencies` — React hook dependency arrays are not auto-checked
50+
- `noInferrableTypes` — explicit type annotations on inferred values are allowed
51+
- `noStaticElementInteractions`, `noSvgWithoutTitle`, `useKeyWithClickEvents` — a11y rules are off
52+
53+
## Docs
54+
55+
- [Architecture & project structure](docs/architecture.md)
56+
- [API patterns & authentication](docs/api.md)
57+
- [Code style & naming conventions](docs/code-style.md)
58+
- [TypeScript interfaces & permissions](docs/data-types.md)
59+
- [Feature implementation details](docs/features.md)

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
AGENTS.md

GEMINI.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
AGENTS.md

0 commit comments

Comments
 (0)