diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index 22438c06d..c67f7c3cc 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -15,15 +15,13 @@ jobs: - name: Checkout Code Repository Layers uses: actions/checkout@v7 - - uses: pnpm/action-setup@v6 - with: - version: 11 - - name: Initialize Node.js Environment uses: actions/setup-node@v6 with: node-version: 22 - cache: 'pnpm' + + - name: Enable pnpm + run: corepack enable && corepack prepare pnpm@11.9.0 --activate - name: Install Project Dependencies (Clean Architecture Ingest) run: pnpm install --frozen-lockfile @@ -33,4 +31,3 @@ jobs: - name: Execute Security & Code Quality Monitoring Audits run: echo "Code coverage thresholds verified at 80%+. Monitoring data packages fully initialized." - diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 428bb6922..b4a780194 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,13 +19,12 @@ jobs: steps: - uses: actions/checkout@v7 - - uses: pnpm/action-setup@v6 - with: - version: 11 - uses: actions/setup-node@v6 with: node-version: 22 - cache: pnpm + + - name: Enable pnpm + run: corepack enable && corepack prepare pnpm@11.9.0 --activate - name: Install dependencies run: pnpm install --frozen-lockfile @@ -39,13 +38,12 @@ jobs: steps: - uses: actions/checkout@v7 - - uses: pnpm/action-setup@v6 - with: - version: 11 - uses: actions/setup-node@v6 with: node-version: 22 - cache: pnpm + + - name: Enable pnpm + run: corepack enable && corepack prepare pnpm@11.9.0 --activate - name: Install dependencies run: pnpm install --frozen-lockfile @@ -62,13 +60,12 @@ jobs: steps: - uses: actions/checkout@v7 - - uses: pnpm/action-setup@v6 - with: - version: 11 - uses: actions/setup-node@v6 with: node-version: 22 - cache: pnpm + + - name: Enable pnpm + run: corepack enable && corepack prepare pnpm@11.9.0 --activate - name: Install dependencies run: pnpm install --frozen-lockfile @@ -108,13 +105,12 @@ jobs: steps: - uses: actions/checkout@v7 - - uses: pnpm/action-setup@v6 - with: - version: 11 - uses: actions/setup-node@v6 with: node-version: 22 - cache: pnpm + + - name: Enable pnpm + run: corepack enable && corepack prepare pnpm@11.9.0 --activate - name: Install dependencies run: pnpm install --frozen-lockfile @@ -128,13 +124,12 @@ jobs: steps: - uses: actions/checkout@v7 - - uses: pnpm/action-setup@v6 - with: - version: 11 - uses: actions/setup-node@v6 with: node-version: 22 - cache: pnpm + + - name: Enable pnpm + run: corepack enable && corepack prepare pnpm@11.9.0 --activate - name: Install dependencies run: pnpm install --frozen-lockfile diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 99da69366..b2dcc9c38 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -28,13 +28,12 @@ jobs: steps: - uses: actions/checkout@v7 - - uses: pnpm/action-setup@v6 - with: - version: 11 - uses: actions/setup-node@v6 with: node-version: 22 - cache: pnpm + + - name: Enable pnpm + run: corepack enable && corepack prepare pnpm@11.9.0 --activate - name: Install app dependencies run: pnpm install --frozen-lockfile diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml index 401300788..311b20a8f 100644 --- a/.github/workflows/visual-regression.yml +++ b/.github/workflows/visual-regression.yml @@ -42,15 +42,13 @@ jobs: - name: Checkout uses: actions/checkout@v7 - - uses: pnpm/action-setup@v6 - with: - version: 11 - - name: Setup Node uses: actions/setup-node@v6 with: node-version: 22 - cache: pnpm + + - name: Enable pnpm + run: corepack enable && corepack prepare pnpm@11.9.0 --activate - name: Install dependencies run: pnpm install --frozen-lockfile diff --git a/Dockerfile b/Dockerfile index 800716ddc..354f8ec99 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,16 @@ -FROM node:20-alpine AS base +FROM node:22-alpine AS base +RUN corepack enable && corepack prepare pnpm@11.9.0 --activate # Install dependencies only when needed FROM base AS deps -RUN apk add --no-cache libc6-compat +RUN apk add --no-cache libc6-compat python3 make g++ WORKDIR /app # Install dependencies based on the preferred package manager -COPY package.json package-lock.json* pnpm-lock.yaml* ./ +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ RUN \ - if [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \ + if [ -f pnpm-lock.yaml ]; then pnpm i --frozen-lockfile; \ elif [ -f package-lock.json ]; then npm ci; \ else echo "Lockfile not found." && exit 1; \ fi @@ -18,12 +19,12 @@ RUN \ FROM base AS development WORKDIR /app -RUN apk add --no-cache libc6-compat +RUN apk add --no-cache libc6-compat python3 make g++ -COPY package.json package-lock.json* pnpm-lock.yaml* ./ +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ RUN \ - if [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm install; \ + if [ -f pnpm-lock.yaml ]; then pnpm install; \ elif [ -f package-lock.json ]; then npm install; \ else echo "Lockfile not found." && exit 1; \ fi @@ -48,7 +49,7 @@ COPY . . ENV NEXT_TELEMETRY_DISABLED=1 RUN \ - if [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \ + if [ -f pnpm-lock.yaml ]; then pnpm run build; \ elif [ -f package-lock.json ]; then npm run build; \ else echo "Lockfile not found." && exit 1; \ fi @@ -76,4 +77,4 @@ EXPOSE 3000 ENV PORT=3000 ENV HOSTNAME=0.0.0.0 -CMD ["node", "server.js"] \ No newline at end of file +CMD ["node", "server.js"] diff --git a/README.md b/README.md index 35747576d..1d7f2ea37 100644 --- a/README.md +++ b/README.md @@ -379,6 +379,8 @@ View container logs: docker compose logs -f ``` +Common self-hosting build issues (pnpm/Corepack mismatch, `ERR_PNPM_IGNORED_BUILDS`, missing native build tools, GitHub OAuth `error=github`) are documented in [docs/self-hosting.md](docs/self-hosting.md#docker-build-troubleshooting). Set `AUTH_DEBUG=true` in your env file for detailed NextAuth logs during OAuth setup. + --- ## FAQ diff --git a/docs/self-hosting.md b/docs/self-hosting.md index 1b38cf93b..d3f98830e 100644 --- a/docs/self-hosting.md +++ b/docs/self-hosting.md @@ -53,6 +53,7 @@ DevTrack uses `@supabase/supabase-js` which relies on the Supabase REST API, so | `WAKATIME_CLIENT_ID` | Optional | WakaTime OAuth Client ID (for WakaTime integration) | `waka_...` | | `WAKATIME_CLIENT_SECRET` | Optional | WakaTime OAuth Client Secret | `waka_sec_...` | | `ALLOWED_ORIGINS` | Optional | Comma-separated extra origins allowed for CSRF validation | `https://staging.example.com` | +| `AUTH_DEBUG` | Optional | Set to `true` for verbose NextAuth OAuth logs (self-hosting troubleshooting) | `true` | --- @@ -96,6 +97,8 @@ When running DevTrack with Docker, set the following environment variables in yo ``` 5. DevTrack will be available at `http://localhost:3000`. +> **Note:** The bundled `docker-compose.yml` targets the `development` stage and runs `npm run dev` with hot reload. For a production-style container, build the `production` target from the Dockerfile directly (see [Troubleshooting](#docker-build-troubleshooting)). + --- ## 2. Railway @@ -129,6 +132,25 @@ DevTrack includes a `render.yaml` Blueprint for easy deployment on Render's free ## 🔧 Troubleshooting +### Docker build troubleshooting + +- **`pnpm` / Corepack version mismatch during `docker compose build`**: + The Dockerfile uses Node 22 with pnpm pinned via Corepack (`corepack prepare pnpm@11.9.0 --activate`). CI uses the same Corepack pin. Pull the latest code and rebuild with `docker compose build --no-cache`. + +- **`ERR_PNPM_IGNORED_BUILDS` / `pnpm approve-builds`**: + Build-script approval is configured non-interactively in `pnpm-workspace.yaml` (`allowBuilds`). The Dockerfile copies this file before `pnpm install`. If you see this error on an older clone, update and rebuild with `--no-cache`. Do not run interactive `pnpm approve-builds` inside CI or Docker builds. + +- **Native module build failures (`sharp`, `esbuild`, etc.)**: + The Dockerfile installs `python3`, `make`, and `g++` in the deps/development stages for Alpine. Rebuild with `docker compose build --no-cache` after pulling fixes. + +- **GitHub OAuth redirects with `error=github`**: + 1. Confirm `GITHUB_ID` and `GITHUB_SECRET` match your GitHub OAuth app. + 2. Set `NEXTAUTH_URL` to your public URL with no trailing slash (e.g. `https://devtrack.example.com`). + 3. Set the GitHub OAuth **Authorization callback URL** to `/api/auth/callback/github`. + 4. Enable verbose auth logging: set `AUTH_DEBUG=true` in your `.env` and inspect container logs (`docker compose logs -f`). Look for `[auth]` and `[nextauth]` lines. + +### General troubleshooting + - **Server Error 500 on Login**: Make sure your `NEXTAUTH_SECRET` and `ENCRYPTION_KEY` are set. If `ENCRYPTION_KEY` is missing or the wrong length (must be 32 bytes / 64 hex chars), the OAuth callback will crash when attempting to encrypt the GitHub token. - **Login Redirects back to Home Page infinitely**: diff --git a/package.json b/package.json index 7a4549cba..822433ea1 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "js-yaml": ">=4.1.0" }, "engines": { - "node": "20.x" + "node": "20.x || 22.x" }, "optionalDependencies": { "@esbuild/linux-x64": "^0.28.0", @@ -111,21 +111,5 @@ "@parcel/watcher-linux-x64-glibc": "^2.5.6", "@parcel/watcher-linux-x64-musl": "^2.5.6", "@swc/core-linux-x64-gnu": "^1.15.43" - }, - "pnpm": { - "onlyBuiltDependencies": [ - "@parcel/watcher", - "@scarf/scarf", - "@sentry/cli", - "@swc/core", - "@tree-sitter-grammars/tree-sitter-yaml", - "core-js-pure", - "core-js", - "esbuild", - "sharp", - "tree-sitter-json", - "tree-sitter", - "unrs-resolver" - ] } } diff --git a/src/lib/auth-config.ts b/src/lib/auth-config.ts new file mode 100644 index 000000000..6203c9066 --- /dev/null +++ b/src/lib/auth-config.ts @@ -0,0 +1,61 @@ +const PLACEHOLDER_PATTERNS = [ + /^your[-_]/i, + /^changeme$/i, + /^placeholder$/i, + /^xxx+$/i, + /^todo$/i, +]; + +function isUnset(value: string | undefined): boolean { + if (!value || value.trim() === "") return true; + return PLACEHOLDER_PATTERNS.some((pattern) => pattern.test(value.trim())); +} + +function logAuthConfigStatus(): void { + const issues: string[] = []; + + if (isUnset(process.env.GITHUB_ID)) { + issues.push("GITHUB_ID is unset — GitHub sign-in will fail"); + } + if (isUnset(process.env.GITHUB_SECRET)) { + issues.push("GITHUB_SECRET is unset — GitHub sign-in will fail"); + } + if (isUnset(process.env.NEXTAUTH_SECRET)) { + issues.push("NEXTAUTH_SECRET is unset — sessions cannot be signed"); + } + if (isUnset(process.env.NEXTAUTH_URL)) { + issues.push( + "NEXTAUTH_URL is unset — OAuth callbacks may redirect incorrectly" + ); + } else if (process.env.NEXTAUTH_URL?.endsWith("/")) { + issues.push( + "NEXTAUTH_URL has a trailing slash — remove it (e.g. https://example.com)" + ); + } + + if (issues.length > 0) { + console.warn( + "[auth] Self-hosting configuration issues:\n - " + issues.join("\n - ") + ); + } else if (process.env.AUTH_DEBUG === "true") { + console.info("[auth] GitHub OAuth and NextAuth env vars look configured"); + } +} + +logAuthConfigStatus(); + +export const authDebugEnabled = process.env.AUTH_DEBUG === "true"; + +export const nextAuthLogger = { + error(code: string, metadata: unknown) { + console.error("[nextauth]", code, metadata); + }, + warn(code: string) { + console.warn("[nextauth]", code); + }, + debug(code: string, metadata: unknown) { + if (authDebugEnabled) { + console.debug("[nextauth]", code, metadata); + } + }, +}; diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 66b24a92a..ce9fa5903 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -1,5 +1,6 @@ import { type NextAuthOptions } from "next-auth"; import GitHubProvider from "next-auth/providers/github"; +import { authDebugEnabled, nextAuthLogger } from "./auth-config"; import { syncGitHubAchievementsForUser } from "./github-achievements"; import { supabaseAdmin } from "./supabase"; @@ -14,6 +15,8 @@ const GITHUB_API = "https://api.github.com"; const TOKEN_VALIDATION_INTERVAL_MS = 24 * 60 * 60 * 1000; export const authOptions: NextAuthOptions = { + debug: authDebugEnabled, + logger: nextAuthLogger, // Playwright runs on plain HTTP (127.0.0.1) and relies on the default // `next-auth.session-token` cookie name. If NextAuth infers HTTPS via // forwarded headers, it may switch to secure cookie prefixes and the E2E @@ -47,6 +50,13 @@ export const authOptions: NextAuthOptions = { if (account?.provider === "github" && profile) { const p = profile as { id: number; login: string; email?: string }; + if (authDebugEnabled) { + console.debug("[auth] GitHub signIn callback", { + login: p.login, + supabaseConfigured: Boolean(supabaseAdmin), + }); + } + // Guard: supabaseAdmin is null when Supabase env vars are missing or // contain placeholder values (see src/lib/supabase.ts). Calling .from() // on null throws a TypeError which NextAuth silently converts to @@ -54,8 +64,8 @@ export const authOptions: NextAuthOptions = { // so authentication can still succeed with degraded functionality. if (!supabaseAdmin) { console.warn( - "signIn: supabaseAdmin is not configured; skipping DB upsert. " + - "Set NEXT_PUBLIC_SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY in .env.local." + "[auth] supabaseAdmin is not configured; skipping DB upsert. " + + "Set NEXT_PUBLIC_SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY." ); return true; }