diff --git a/next.config.js b/next.config.js index 3228a37..073b486 100644 --- a/next.config.js +++ b/next.config.js @@ -1,5 +1,6 @@ /** @type {import('next').NextConfig} */ const nextConfig = { + output: "standalone", reactStrictMode: true, typedRoutes: true, images: { diff --git a/ops/docker/Dockerfile.web b/ops/docker/Dockerfile.web index 72fe08d..46931cc 100644 --- a/ops/docker/Dockerfile.web +++ b/ops/docker/Dockerfile.web @@ -1,11 +1,16 @@ -FROM node:24-alpine +FROM node:24-alpine AS base +# Install libc6-compat for Alpine compatibility +RUN apk add --no-cache libc6-compat WORKDIR /app # Enable pnpm via corepack RUN corepack enable -# Copy dependency manifests first for layer caching +# ─── 1. Dependencies Stage ───────────────────────────────────── +FROM base AS deps + +# Copy dependency manifests COPY package.json pnpm-lock.yaml ./ # Configure workspace policy for native dependencies if required @@ -14,15 +19,46 @@ RUN printf "packages:\n - '.'\n\nallowBuilds:\n esbuild: true\n sharp: true\n # Install dependencies using frozen lockfile RUN pnpm install --frozen-lockfile -# Copy application source code +# ─── 2. Build Stage ─────────────────────────────────────────── +FROM base AS builder + +COPY --from=deps /app/node_modules ./node_modules COPY . . -# Build Next.js application +ENV NEXT_TELEMETRY_DISABLED=1 +ENV NODE_ENV=production + +# Build Next.js application with standalone output RUN pnpm build -EXPOSE 3000 +# ─── 3. Production Runner Stage ─────────────────────────────── +FROM node:24-alpine AS runner + +WORKDIR /app -ENV PORT=3000 ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +# Create a non-root system user for security +RUN addgroup --system --gid 1001 nodejs && \ + adduser --system --uid 1001 nextjs + +# Copy static assets and public directory +COPY --from=builder --chown=nextjs:nodejs /app/public ./public + +# Set correct permission for prerender cache +RUN mkdir .next && chown nextjs:nodejs .next + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +CMD ["node", "server.js"] -CMD ["pnpm", "start"] diff --git a/ops/docker/Dockerfile.worker b/ops/docker/Dockerfile.worker index cc957c0..45e0e7d 100644 --- a/ops/docker/Dockerfile.worker +++ b/ops/docker/Dockerfile.worker @@ -5,6 +5,11 @@ WORKDIR /app # Enable pnpm via corepack RUN corepack enable +# Install Supercronic cron runner (cached layer) +RUN wget -O /usr/local/bin/supercronic \ + https://github.com/aptible/supercronic/releases/download/v0.2.29/supercronic-linux-amd64 \ + && chmod +x /usr/local/bin/supercronic + # Copy dependency manifests first for layer caching COPY package.json pnpm-lock.yaml ./ @@ -14,19 +19,14 @@ RUN printf "packages:\n - '.'\n\nallowBuilds:\n esbuild: true\n sharp: true\n # Install dependencies using frozen lockfile RUN pnpm install --frozen-lockfile -# Copy application source code -COPY . . - -# Install Supercronic cron runner -RUN wget -O /usr/local/bin/supercronic \ - https://github.com/aptible/supercronic/releases/download/v0.2.29/supercronic-linux-amd64 \ - && chmod +x /usr/local/bin/supercronic - # Copy entrypoint script and initial leaderboard cron job definition COPY ops/docker/entrypoint.sh /usr/local/bin/entrypoint.sh COPY ops/cron/leaderboard.cron /etc/leaderboard.cron RUN chmod +x /usr/local/bin/entrypoint.sh +# Copy application source code +COPY . . + # Build-time argument for version identification ARG GIT_COMMIT_SHA=development ENV DEVIMPACT_VERSION=${GIT_COMMIT_SHA} diff --git a/package.json b/package.json index f1920f5..1451684 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,6 @@ "radix-ui": "^1.4.3", "react": "^19.2.4", "react-dom": "^19.2.4", - "react-icons": "^5.6.0", "recharts": "^2.12.7", "redis": "^5.12.1", "tailwind-merge": "^2.5.3" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 08376df..08d16af 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,7 +28,7 @@ importers: version: 1.25.0(react@19.2.7) next: specifier: ^16.4.1 - version: 16.4.1(@babel/core@7.29.7(supports-color@7.2.0))(@types/node@25.9.5)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 16.4.1(@babel/core@7.29.7)(@types/node@25.9.5)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -44,9 +44,6 @@ importers: react-dom: specifier: ^19.2.4 version: 19.2.7(react@19.2.7) - react-icons: - specifier: ^5.6.0 - version: 5.7.0(react@19.2.7) recharts: specifier: ^2.12.7 version: 2.15.4(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -98,13 +95,13 @@ importers: version: 17.4.2 eslint: specifier: ^9.17.0 - version: 9.39.5(jiti@1.21.7)(supports-color@7.2.0) + version: 9.39.5(jiti@1.21.7) eslint-config-next: specifier: ^16.2.2 - version: 16.2.10(@typescript-eslint/parser@8.64.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + version: 16.2.10(@typescript-eslint/parser@8.64.0(eslint@9.39.5(jiti@1.21.7))(typescript@6.0.3))(eslint@9.39.5(jiti@1.21.7))(typescript@6.0.3) eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0)) + version: 10.1.8(eslint@9.39.5(jiti@1.21.7)) husky: specifier: ^9.1.7 version: 9.1.7 @@ -610,105 +607,89 @@ packages: resolution: {integrity: sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==} cpu: [arm64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-arm@1.3.4': resolution: {integrity: sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==} cpu: [arm] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.3.4': resolution: {integrity: sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==} cpu: [ppc64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.3.4': resolution: {integrity: sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==} cpu: [riscv64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-s390x@1.3.4': resolution: {integrity: sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==} cpu: [s390x] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-x64@1.3.4': resolution: {integrity: sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==} cpu: [x64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.3.4': resolution: {integrity: sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==} cpu: [arm64] os: [linux] - libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.3.4': resolution: {integrity: sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==} cpu: [x64] os: [linux] - libc: [musl] '@img/sharp-linux-arm64@0.35.5': resolution: {integrity: sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==} engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] - libc: [glibc] '@img/sharp-linux-arm@0.35.5': resolution: {integrity: sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==} engines: {node: '>=20.9.0'} cpu: [arm] os: [linux] - libc: [glibc] '@img/sharp-linux-ppc64@0.35.5': resolution: {integrity: sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==} engines: {node: '>=20.9.0'} cpu: [ppc64] os: [linux] - libc: [glibc] '@img/sharp-linux-riscv64@0.35.5': resolution: {integrity: sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==} engines: {node: '>=20.9.0'} cpu: [riscv64] os: [linux] - libc: [glibc] '@img/sharp-linux-s390x@0.35.5': resolution: {integrity: sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==} engines: {node: '>=20.9.0'} cpu: [s390x] os: [linux] - libc: [glibc] '@img/sharp-linux-x64@0.35.5': resolution: {integrity: sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==} engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] - libc: [glibc] '@img/sharp-linuxmusl-arm64@0.35.5': resolution: {integrity: sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==} engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] - libc: [musl] '@img/sharp-linuxmusl-x64@0.35.5': resolution: {integrity: sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==} engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] - libc: [musl] '@img/sharp-wasm32@0.35.5': resolution: {integrity: sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==} @@ -782,28 +763,24 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@next/swc-linux-arm64-musl@16.4.1': resolution: {integrity: sha512-1SuJyDxW/PR9dHJat/ZRgcYLhuSZ+1psP4DlKb2tKIuXbjygqpLrJWRSq+onZQBv8LcmU67EIrmb6c3XTaJLjw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@next/swc-linux-x64-gnu@16.4.1': resolution: {integrity: sha512-Istng1hMGp9TGO4lUqeKLd70MPN/OOkucVTP2Nmj+gLkwBgmPqUw8CJYBCC7nbl/B/23bBCcjCI5Hsh6hazC1A==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@next/swc-linux-x64-musl@16.4.1': resolution: {integrity: sha512-aSC9wMNiZRRFVpPkPWBzRwLQxIGm/CiQcXyFF1XNcgodb+uMxdk7aSSGGpDXCbnvZdf4itjHxpOd5h7i7zaiYw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@next/swc-win32-arm64-msvc@16.4.1': resolution: {integrity: sha512-t/myvwFDlypMUjRvbBf8qLPyuaB0iXYYdBtwMt6DM6f+60wtGr4bSYholdPqNeV5VUXZhrpMwfza829kmhS3iA==} @@ -1625,42 +1602,36 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.2.15': resolution: {integrity: sha512-uN26XOnVR7rqQupG3V9oAG2EGGatWjcJHOeDOHUFCh4TBWmP2tHTFoVN13aYbwfm0aHqJAEmcwhAu6sooIjfqQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@rolldown/binding-linux-ppc64-gnu@1.2.15': resolution: {integrity: sha512-9nWkcdd+H+XhaA3fQIwpHnKIrfx/PQ6rO0pivr78Og07tXlUlctBBJoEYLPfhTmCR/QxEVdn6uPjHV5SymPhdw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-s390x-gnu@1.2.15': resolution: {integrity: sha512-Ggh0ciFjLwIusLSLz7LZYEaGdxOAUJXI+Qu5JCbhPJJHEt8vP+IjRATTJheiJsdzzTYgUiHUiw3X1Ea3ZpMBdA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.2.15': resolution: {integrity: sha512-FHg60rS6DBePAYZ9X9F67x+JObFdmgn6tupA1C/a3B+CufFmTD4mX+e8M75tCiAO3foR/6xwVBI0KQiI7sn/rw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-musl@1.2.15': resolution: {integrity: sha512-/cvV/7v5VGz/moiz0XKRj2dE8DzsKyhIu1KpOvJGPjRywg3yscON889UGvAe8zfiJLHup7VLjBduoGWi3HusWQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@rolldown/binding-openharmony-arm64@1.2.15': resolution: {integrity: sha512-MyzC5Hrv/sm+Z7TSGKP/j8001RO2xQm1zOpCNVL4j0cY/VhBZviludA2YFQ7i4lH6ZAXxQm0lcMdBzM+99mE7w==} @@ -3074,28 +3045,24 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.33.0: resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.33.0: resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.33.0: resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-arm64-msvc@1.33.0: resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} @@ -3556,11 +3523,6 @@ packages: peerDependencies: react: ^19.2.7 - react-icons@5.7.0: - resolution: {integrity: sha512-LBLy340Rzqy6+/yVhZKT3B/QpP1BZaesGqasf09HPOBzRarcDIFH0WwXlXQfE7q7ipxK4MSiC5DIBWURCny6fw==} - peerDependencies: - react: '*' - react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -4231,20 +4193,20 @@ snapshots: '@babel/compat-data@7.29.7': {} - '@babel/core@7.29.7(supports-color@7.2.0)': + '@babel/core@7.29.7': dependencies: '@babel/code-frame': 7.29.7 '@babel/generator': 7.29.7 '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) '@babel/helpers': 7.29.7 '@babel/parser': 7.29.7 '@babel/template': 7.29.7 - '@babel/traverse': 7.29.7(supports-color@7.2.0) + '@babel/traverse': 7.29.7 '@babel/types': 7.29.7 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.3(supports-color@7.2.0) + debug: 4.4.3 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -4269,19 +4231,19 @@ snapshots: '@babel/helper-globals@7.29.7': {} - '@babel/helper-module-imports@7.29.7(supports-color@7.2.0)': + '@babel/helper-module-imports@7.29.7': dependencies: - '@babel/traverse': 7.29.7(supports-color@7.2.0) + '@babel/traverse': 7.29.7 '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-module-imports': 7.29.7(supports-color@7.2.0) + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.7(supports-color@7.2.0) + '@babel/traverse': 7.29.7 transitivePeerDependencies: - supports-color @@ -4308,7 +4270,7 @@ snapshots: '@babel/parser': 7.29.7 '@babel/types': 7.29.7 - '@babel/traverse@7.29.7(supports-color@7.2.0)': + '@babel/traverse@7.29.7': dependencies: '@babel/code-frame': 7.29.7 '@babel/generator': 7.29.7 @@ -4316,7 +4278,7 @@ snapshots: '@babel/parser': 7.29.7 '@babel/template': 7.29.7 '@babel/types': 7.29.7 - debug: 4.4.3(supports-color@7.2.0) + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -4559,17 +4521,17 @@ snapshots: '@esbuild/win32-x64@0.18.20': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))': + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.5(jiti@1.21.7))': dependencies: - eslint: 9.39.5(jiti@1.21.7)(supports-color@7.2.0) + eslint: 9.39.5(jiti@1.21.7) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/config-array@0.21.2(supports-color@7.2.0)': + '@eslint/config-array@0.21.2': dependencies: '@eslint/object-schema': 2.1.7 - debug: 4.4.3(supports-color@7.2.0) + debug: 4.4.3 minimatch: 3.1.5 transitivePeerDependencies: - supports-color @@ -4582,10 +4544,10 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.3.6(supports-color@7.2.0)': + '@eslint/eslintrc@3.3.6': dependencies: ajv: 6.15.0 - debug: 4.4.3(supports-color@7.2.0) + debug: 4.4.3 espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 @@ -5775,15 +5737,15 @@ snapshots: dependencies: csstype: 3.2.3 - '@typescript-eslint/eslint-plugin@8.64.0(@typescript-eslint/parser@8.64.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.64.0(@typescript-eslint/parser@8.64.0(eslint@9.39.5(jiti@1.21.7))(typescript@6.0.3))(eslint@9.39.5(jiti@1.21.7))(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.64.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/parser': 8.64.0(eslint@9.39.5(jiti@1.21.7))(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.64.0 - '@typescript-eslint/type-utils': 8.64.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.64.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/type-utils': 8.64.0(eslint@9.39.5(jiti@1.21.7))(typescript@6.0.3) + '@typescript-eslint/utils': 8.64.0(eslint@9.39.5(jiti@1.21.7))(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.64.0 - eslint: 9.39.5(jiti@1.21.7)(supports-color@7.2.0) + eslint: 9.39.5(jiti@1.21.7) ignore: 7.0.6 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -5791,23 +5753,23 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.64.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/parser@8.64.0(eslint@9.39.5(jiti@1.21.7))(typescript@6.0.3)': dependencies: '@typescript-eslint/scope-manager': 8.64.0 '@typescript-eslint/types': 8.64.0 - '@typescript-eslint/typescript-estree': 8.64.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.64.0(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.64.0 - debug: 4.4.3(supports-color@7.2.0) - eslint: 9.39.5(jiti@1.21.7)(supports-color@7.2.0) + debug: 4.4.3 + eslint: 9.39.5(jiti@1.21.7) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.64.0(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/project-service@8.64.0(typescript@6.0.3)': dependencies: '@typescript-eslint/tsconfig-utils': 8.64.0(typescript@6.0.3) '@typescript-eslint/types': 8.64.0 - debug: 4.4.3(supports-color@7.2.0) + debug: 4.4.3 typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -5821,13 +5783,13 @@ snapshots: dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.64.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.64.0(eslint@9.39.5(jiti@1.21.7))(typescript@6.0.3)': dependencies: '@typescript-eslint/types': 8.64.0 - '@typescript-eslint/typescript-estree': 8.64.0(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.64.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - debug: 4.4.3(supports-color@7.2.0) - eslint: 9.39.5(jiti@1.21.7)(supports-color@7.2.0) + '@typescript-eslint/typescript-estree': 8.64.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.64.0(eslint@9.39.5(jiti@1.21.7))(typescript@6.0.3) + debug: 4.4.3 + eslint: 9.39.5(jiti@1.21.7) ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: @@ -5835,13 +5797,13 @@ snapshots: '@typescript-eslint/types@8.64.0': {} - '@typescript-eslint/typescript-estree@8.64.0(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.64.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.64.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/project-service': 8.64.0(typescript@6.0.3) '@typescript-eslint/tsconfig-utils': 8.64.0(typescript@6.0.3) '@typescript-eslint/types': 8.64.0 '@typescript-eslint/visitor-keys': 8.64.0 - debug: 4.4.3(supports-color@7.2.0) + debug: 4.4.3 minimatch: 10.2.5 semver: 7.8.5 tinyglobby: 0.2.17 @@ -5850,13 +5812,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.64.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/utils@8.64.0(eslint@9.39.5(jiti@1.21.7))(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0)) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.5(jiti@1.21.7)) '@typescript-eslint/scope-manager': 8.64.0 '@typescript-eslint/types': 8.64.0 - '@typescript-eslint/typescript-estree': 8.64.0(supports-color@7.2.0)(typescript@6.0.3) - eslint: 9.39.5(jiti@1.21.7)(supports-color@7.2.0) + '@typescript-eslint/typescript-estree': 8.64.0(typescript@6.0.3) + eslint: 9.39.5(jiti@1.21.7) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -6312,17 +6274,13 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 - debug@3.2.7(supports-color@7.2.0): + debug@3.2.7: dependencies: ms: 2.1.3 - optionalDependencies: - supports-color: 7.2.0 - debug@4.4.3(supports-color@7.2.0): + debug@4.4.3: dependencies: ms: 2.1.3 - optionalDependencies: - supports-color: 7.2.0 decimal.js-light@2.5.1: {} @@ -6531,18 +6489,18 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-next@16.2.10(@typescript-eslint/parser@8.64.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): + eslint-config-next@16.2.10(@typescript-eslint/parser@8.64.0(eslint@9.39.5(jiti@1.21.7))(typescript@6.0.3))(eslint@9.39.5(jiti@1.21.7))(typescript@6.0.3): dependencies: '@next/eslint-plugin-next': 16.2.10 - eslint: 9.39.5(jiti@1.21.7)(supports-color@7.2.0) - eslint-import-resolver-node: 0.3.10(supports-color@7.2.0) - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.64.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0) - eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0)) - eslint-plugin-react: 7.37.5(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0)) - eslint-plugin-react-hooks: 7.1.1(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0) + eslint: 9.39.5(jiti@1.21.7) + eslint-import-resolver-node: 0.3.10 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5(jiti@1.21.7)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.64.0(eslint@9.39.5(jiti@1.21.7))(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@1.21.7)) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.5(jiti@1.21.7)) + eslint-plugin-react: 7.37.5(eslint@9.39.5(jiti@1.21.7)) + eslint-plugin-react-hooks: 7.1.1(eslint@9.39.5(jiti@1.21.7)) globals: 16.4.0 - typescript-eslint: 8.64.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + typescript-eslint: 8.64.0(eslint@9.39.5(jiti@1.21.7))(typescript@6.0.3) optionalDependencies: typescript: 6.0.3 transitivePeerDependencies: @@ -6551,56 +6509,56 @@ snapshots: - eslint-plugin-import-x - supports-color - eslint-config-prettier@10.1.8(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0)): + eslint-config-prettier@10.1.8(eslint@9.39.5(jiti@1.21.7)): dependencies: - eslint: 9.39.5(jiti@1.21.7)(supports-color@7.2.0) + eslint: 9.39.5(jiti@1.21.7) - eslint-import-resolver-node@0.3.10(supports-color@7.2.0): + eslint-import-resolver-node@0.3.10: dependencies: - debug: 3.2.7(supports-color@7.2.0) + debug: 3.2.7 is-core-module: 2.16.2 resolve: 2.0.0-next.7 transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5(jiti@1.21.7)): dependencies: '@nolyfill/is-core-module': 1.0.39 - debug: 4.4.3(supports-color@7.2.0) - eslint: 9.39.5(jiti@1.21.7)(supports-color@7.2.0) + debug: 4.4.3 + eslint: 9.39.5(jiti@1.21.7) get-tsconfig: 4.14.0 is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.17 unrs-resolver: 1.12.2 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.64.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.64.0(eslint@9.39.5(jiti@1.21.7))(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@1.21.7)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.14.0(@typescript-eslint/parser@8.64.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10(supports-color@7.2.0))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0): + eslint-module-utils@2.14.0(@typescript-eslint/parser@8.64.0(eslint@9.39.5(jiti@1.21.7))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@1.21.7)): dependencies: - debug: 3.2.7(supports-color@7.2.0) + debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.64.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 9.39.5(jiti@1.21.7)(supports-color@7.2.0) - eslint-import-resolver-node: 0.3.10(supports-color@7.2.0) - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0) + '@typescript-eslint/parser': 8.64.0(eslint@9.39.5(jiti@1.21.7))(typescript@6.0.3) + eslint: 9.39.5(jiti@1.21.7) + eslint-import-resolver-node: 0.3.10 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5(jiti@1.21.7)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.64.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.64.0(eslint@9.39.5(jiti@1.21.7))(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@1.21.7)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 array.prototype.findlastindex: 1.2.6 array.prototype.flat: 1.3.3 array.prototype.flatmap: 1.3.3 - debug: 3.2.7(supports-color@7.2.0) + debug: 3.2.7 doctrine: 2.1.0 - eslint: 9.39.5(jiti@1.21.7)(supports-color@7.2.0) - eslint-import-resolver-node: 0.3.10(supports-color@7.2.0) - eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.64.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10(supports-color@7.2.0))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0) + eslint: 9.39.5(jiti@1.21.7) + eslint-import-resolver-node: 0.3.10 + eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.64.0(eslint@9.39.5(jiti@1.21.7))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@1.21.7)) hasown: 2.0.4 is-core-module: 2.16.2 is-glob: 4.0.3 @@ -6612,13 +6570,13 @@ snapshots: string.prototype.trimend: 1.0.10 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.64.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/parser': 8.64.0(eslint@9.39.5(jiti@1.21.7))(typescript@6.0.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0)): + eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.5(jiti@1.21.7)): dependencies: aria-query: 5.3.2 array-includes: 3.1.9 @@ -6628,7 +6586,7 @@ snapshots: axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - eslint: 9.39.5(jiti@1.21.7)(supports-color@7.2.0) + eslint: 9.39.5(jiti@1.21.7) hasown: 2.0.4 jsx-ast-utils: 3.3.5 language-tags: 1.0.9 @@ -6637,18 +6595,18 @@ snapshots: safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 - eslint-plugin-react-hooks@7.1.1(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0): + eslint-plugin-react-hooks@7.1.1(eslint@9.39.5(jiti@1.21.7)): dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) + '@babel/core': 7.29.7 '@babel/parser': 7.29.7 - eslint: 9.39.5(jiti@1.21.7)(supports-color@7.2.0) + eslint: 9.39.5(jiti@1.21.7) hermes-parser: 0.25.1 zod: 4.4.3 zod-validation-error: 4.0.2(zod@4.4.3) transitivePeerDependencies: - supports-color - eslint-plugin-react@7.37.5(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0)): + eslint-plugin-react@7.37.5(eslint@9.39.5(jiti@1.21.7)): dependencies: array-includes: 3.1.9 array.prototype.findlast: 1.2.5 @@ -6656,7 +6614,7 @@ snapshots: array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 es-iterator-helpers: 1.4.0 - eslint: 9.39.5(jiti@1.21.7)(supports-color@7.2.0) + eslint: 9.39.5(jiti@1.21.7) estraverse: 5.3.0 hasown: 2.0.4 jsx-ast-utils: 3.3.5 @@ -6681,14 +6639,14 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0): + eslint@9.39.5(jiti@1.21.7): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0)) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.5(jiti@1.21.7)) '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.21.2(supports-color@7.2.0) + '@eslint/config-array': 0.21.2 '@eslint/config-helpers': 0.4.2 '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.6(supports-color@7.2.0) + '@eslint/eslintrc': 3.3.6 '@eslint/js': 9.39.5 '@eslint/plugin-kit': 0.4.1 '@humanfs/node': 0.16.8 @@ -6698,7 +6656,7 @@ snapshots: ajv: 6.15.0 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.3(supports-color@7.2.0) + debug: 4.4.3 escape-string-regexp: 4.0.0 eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -7298,7 +7256,7 @@ snapshots: react: 19.2.7 react-dom: 19.2.7(react@19.2.7) - next@16.4.1(@babel/core@7.29.7(supports-color@7.2.0))(@types/node@25.9.5)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + next@16.4.1(@babel/core@7.29.7)(@types/node@25.9.5)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: '@next/env': 16.4.1 '@swc/helpers': 0.5.23 @@ -7307,7 +7265,7 @@ snapshots: postcss: 8.5.23 react: 19.2.7 react-dom: 19.2.7(react@19.2.7) - styled-jsx: 5.1.6(@babel/core@7.29.7(supports-color@7.2.0))(react@19.2.7) + styled-jsx: 5.1.6(@babel/core@7.29.7)(react@19.2.7) optionalDependencies: '@next/swc-darwin-arm64': 16.4.1 '@next/swc-darwin-x64': 16.4.1 @@ -7626,10 +7584,6 @@ snapshots: react: 19.2.7 scheduler: 0.27.0 - react-icons@5.7.0(react@19.2.7): - dependencies: - react: 19.2.7 - react-is@16.13.1: {} react-is@17.0.2: {} @@ -8013,12 +7967,12 @@ snapshots: strip-json-comments@3.1.1: {} - styled-jsx@5.1.6(@babel/core@7.29.7(supports-color@7.2.0))(react@19.2.7): + styled-jsx@5.1.6(@babel/core@7.29.7)(react@19.2.7): dependencies: client-only: 0.0.1 react: 19.2.7 optionalDependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) + '@babel/core': 7.29.7 sucrase@3.35.1: dependencies: @@ -8165,13 +8119,13 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.64.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): + typescript-eslint@8.64.0(eslint@9.39.5(jiti@1.21.7))(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.64.0(@typescript-eslint/parser@8.64.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/parser': 8.64.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.64.0(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.64.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 9.39.5(jiti@1.21.7)(supports-color@7.2.0) + '@typescript-eslint/eslint-plugin': 8.64.0(@typescript-eslint/parser@8.64.0(eslint@9.39.5(jiti@1.21.7))(typescript@6.0.3))(eslint@9.39.5(jiti@1.21.7))(typescript@6.0.3) + '@typescript-eslint/parser': 8.64.0(eslint@9.39.5(jiti@1.21.7))(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.64.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.64.0(eslint@9.39.5(jiti@1.21.7))(typescript@6.0.3) + eslint: 9.39.5(jiti@1.21.7) typescript: 6.0.3 transitivePeerDependencies: - supports-color diff --git a/scripts/calculate-next-country.ts b/scripts/calculate-next-country.ts index d3ca2eb..79125da 100644 --- a/scripts/calculate-next-country.ts +++ b/scripts/calculate-next-country.ts @@ -43,12 +43,16 @@ async function main() { logger.info(`DB: ${(process.env.DATABASE_URL ?? "").slice(0, 40)}...`); const db = getDatabaseStore(); - await db.initializeSchema(); - await db.seedCalculationCountries(); - // 1. Pick the next country to calculate + // 1. Pick and atomically claim the next country to calculate logger.info("Picking next country to calculate..."); - const next = await db.getNextCountryToCalculate(); + let next = await db.getNextCountryToCalculate(); + if (!next) { + // Attempt one-time seed if the calculation table is unseeded + await db.seedCalculationCountries(); + next = await db.getNextCountryToCalculate(); + } + if (!next) { logger.info("No countries to calculate. All are up-to-date or running."); process.exit(0); @@ -56,11 +60,7 @@ async function main() { activeCountrySlug = next.slug; - logger.info(`Selected: ${next.title} (${next.slug})`); - - // 2. Mark as running - await db.startCalculation(next.slug); - logger.info(`Marked '${next.slug}' as running.`); + logger.info(`Selected and claimed: ${next.title} (${next.slug})`); try { // 3. Run the calculation (same function used by the API endpoint) diff --git a/scripts/init-db.ts b/scripts/init-db.ts index 18d7406..f2d64ca 100644 --- a/scripts/init-db.ts +++ b/scripts/init-db.ts @@ -31,6 +31,10 @@ async function main() { await store.initializeSchema(); console.log(" ✓ Schema created (github_users table + indexes)"); + // Seed calculation countries + await store.seedCalculationCountries(); + console.log(" ✓ Seeded calculation countries"); + console.log("\nDone. Database is ready."); process.exit(0); } diff --git a/src/app/api/compare/route.ts b/src/app/api/compare/route.ts index 6fa709f..28aff35 100644 --- a/src/app/api/compare/route.ts +++ b/src/app/api/compare/route.ts @@ -27,7 +27,14 @@ export async function GET(request: Request) { const users = await compareUsers(usernames, selectedLanguages); const winnerData = calculateWinner(users); const insights = createComparisonInsights(users, locale); - return NextResponse.json({ success: true, users, ...winnerData, insights }); + return NextResponse.json( + { success: true, users, ...winnerData, insights }, + { + headers: { + "Cache-Control": "public, s-maxage=1800, stale-while-revalidate=86400", + }, + }, + ); } catch (error: unknown) { console.error("GitHub score error:", error); return formatApiErrorResponse(error); diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 7adb0f4..ba575c8 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -11,6 +11,8 @@ import { supportedLocales, } from "@/lib/i18n/core"; import { getMetadataBase, toAbsoluteUrl } from "@/lib/seo"; +import { AppHeader } from "@/components/layout/app-header"; +import { AppFooter } from "@/components/layout/app-footer"; import Providers from "./providers"; const themeInitScript = ` @@ -111,7 +113,13 @@ export default async function RootLayout({ children }: { children: ReactNode })
-