diff --git a/.github/workflows/admin-tests.yml b/.github/workflows/admin-tests.yml index 678241dc0..0bab3584a 100644 --- a/.github/workflows/admin-tests.yml +++ b/.github/workflows/admin-tests.yml @@ -27,7 +27,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "24" cache: "pnpm" - name: Install dependencies @@ -54,7 +54,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "24" cache: "pnpm" - name: Install dependencies @@ -120,7 +120,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "24" cache: "pnpm" - name: Install dependencies diff --git a/.github/workflows/ci-checks.yml b/.github/workflows/ci-checks.yml index 003a0f718..301ef138b 100644 --- a/.github/workflows/ci-checks.yml +++ b/.github/workflows/ci-checks.yml @@ -30,7 +30,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "24" cache: "pnpm" - name: Install dependencies @@ -53,14 +53,14 @@ jobs: echo "🧪 Running unit tests for admin login components..." # Run Vitest tests for common-ui library (AdminLogin components) echo "📦 Testing AdminLoginFormMolecule in common-ui..." - NODE_OPTIONS=--max-old-space-size=768 npx vitest run libs/common-ui/src/components/molecules/AdminLoginFormMolecule.test.tsx + NODE_OPTIONS=--max-old-space-size=2048 npx vitest run libs/common-ui/src/components/molecules/AdminLoginFormMolecule.test.tsx # Run Vitest tests for admin app (AdminLoginPage) echo "📦 Testing AdminLoginPage in apps/admin..." - NODE_OPTIONS=--max-old-space-size=768 npx vitest run apps/admin/src/app/admin/login/page.test.tsx + NODE_OPTIONS=--max-old-space-size=2048 npx vitest run apps/admin/src/app/admin/login/page.test.tsx continue-on-error: false env: - NODE_OPTIONS: "--max-old-space-size=768" + NODE_OPTIONS: "--max-old-space-size=2048" CI: "true" # Provide minimal env vars for tests NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.TEST_SUPABASE_URL || 'https://placeholder.supabase.co' }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index e5a4aad9b..5efea8549 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -52,7 +52,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "24" cache: "pnpm" - name: Install dependencies diff --git a/.github/workflows/deploy-main.yml b/.github/workflows/deploy-main.yml index 82cf41866..3ef12c191 100644 --- a/.github/workflows/deploy-main.yml +++ b/.github/workflows/deploy-main.yml @@ -130,7 +130,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "24" cache: "pnpm" - name: Install root dependencies @@ -168,13 +168,14 @@ jobs: - name: Deploy website run: | - set -euo pipefail + set +e + set -uo pipefail timeout 8m npx vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} for attempt in 1 2 3 4 5 6; do echo "Deploy website attempt ${attempt}/6" deploy_log="$(mktemp)" timeout 20m npx vercel deploy --prod --yes --token=${{ secrets.VERCEL_TOKEN }} 2>&1 | tee "$deploy_log" || true - if grep -Eq 'Failed to process project graph|Could not find ".modules.yaml"|ENOENT: no such file or directory|Error: Command "npx nx build|Error: Command "npm run build" exited with 1|Error: Command "npm install" exited with 1' "$deploy_log"; then + if grep -Eq 'Failed to process project graph|Could not find ".modules.yaml"|ENOENT: no such file or directory|Error: Command "npx nx build|Error: Command "npm run build" exited with 1|Error: Command "npm install" exited with 1|Error: The request timed out' "$deploy_log"; then echo "Deploy output indicates explicit build failure; failing deployment step." exit 1 fi @@ -189,7 +190,7 @@ jobs: fi cat "$inspect_log" - if grep -Eq 'Failed to process project graph|Could not find ".modules.yaml"|ENOENT: no such file or directory|Error: Command "npx nx build|Error: Command "npm run build" exited with 1|Error: Command "npm install" exited with 1' "$inspect_log"; then + if grep -Eq 'Failed to process project graph|Could not find ".modules.yaml"|ENOENT: no such file or directory|Error: Command "npx nx build|Error: Command "npm run build" exited with 1|Error: Command "npm install" exited with 1|Error: The request timed out' "$inspect_log"; then echo "Inspect indicates an explicit build failure; failing deployment step." exit 1 fi @@ -237,7 +238,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "24" cache: "pnpm" - name: Install root dependencies @@ -275,13 +276,14 @@ jobs: - name: Deploy admin run: | - set -euo pipefail + set +e + set -uo pipefail timeout 8m npx vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} for attempt in 1 2 3 4 5 6; do echo "Deploy admin attempt ${attempt}/6" deploy_log="$(mktemp)" timeout 20m npx vercel deploy --prod --yes --token=${{ secrets.VERCEL_TOKEN }} 2>&1 | tee "$deploy_log" || true - if grep -Eq 'Failed to process project graph|Could not find ".modules.yaml"|ENOENT: no such file or directory|Error: Command "npx nx build|Error: Command "npm run build" exited with 1|Error: Command "npm install" exited with 1' "$deploy_log"; then + if grep -Eq 'Failed to process project graph|Could not find ".modules.yaml"|ENOENT: no such file or directory|Error: Command "npx nx build|Error: Command "npm run build" exited with 1|Error: Command "npm install" exited with 1|Error: The request timed out' "$deploy_log"; then echo "Deploy output indicates explicit build failure; failing deployment step." exit 1 fi @@ -296,7 +298,7 @@ jobs: fi cat "$inspect_log" - if grep -Eq 'Failed to process project graph|Could not find ".modules.yaml"|ENOENT: no such file or directory|Error: Command "npx nx build|Error: Command "npm run build" exited with 1|Error: Command "npm install" exited with 1' "$inspect_log"; then + if grep -Eq 'Failed to process project graph|Could not find ".modules.yaml"|ENOENT: no such file or directory|Error: Command "npx nx build|Error: Command "npm run build" exited with 1|Error: Command "npm install" exited with 1|Error: The request timed out' "$inspect_log"; then echo "Inspect indicates an explicit build failure; failing deployment step." exit 1 fi diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 39a312206..69c7a7fe3 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -47,7 +47,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "24" cache: "pnpm" - name: Install dependencies diff --git a/.github/workflows/fix-and-resolve-secrets.yml b/.github/workflows/fix-and-resolve-secrets.yml index 12299effc..57cd6971c 100644 --- a/.github/workflows/fix-and-resolve-secrets.yml +++ b/.github/workflows/fix-and-resolve-secrets.yml @@ -46,7 +46,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "24" - name: Get Secret Scanning Alerts id: get-alerts diff --git a/.github/workflows/manual-deploy-parallel.yml b/.github/workflows/manual-deploy-parallel.yml index 15b3293f5..c5fca5632 100644 --- a/.github/workflows/manual-deploy-parallel.yml +++ b/.github/workflows/manual-deploy-parallel.yml @@ -57,14 +57,23 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "24" cache: "pnpm" - name: Enable Corepack run: corepack enable - name: Install root dependencies - run: pnpm install --no-frozen-lockfile + run: pnpm install --frozen-lockfile + + - name: Setup Vercel and Next.js Caching + uses: actions/cache@v4 + with: + path: | + apps/website/.next/cache + key: ${{ runner.os }}-website-next-cache-${{ hashFiles('pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-website-next-cache- - name: Validate Vercel token run: | @@ -79,113 +88,59 @@ jobs: curl -fsS -X PATCH \ -H "Authorization: Bearer ${{ secrets.VERCEL_TOKEN }}" \ -H "Content-Type: application/json" \ - -d '{"buildCommand":"npm run build","outputDirectory":"apps/website/.next"}' \ + -d '{"buildCommand":"pnpm --filter website build","outputDirectory":"apps/website/.next","installCommand":"echo skip"}' \ "https://api.vercel.com/v9/projects/${{ secrets.VERCEL_PROJECT_ID_WEBSITE }}" - - name: Validate build locally (optional build check) + - name: Validate and build (Local Vercel Build) if: ${{ inputs.validate_build_website }} - run: pnpm --dir apps/website run build + run: | + npx vercel@latest pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} + npx vercel@latest build --prod --token=${{ secrets.VERCEL_TOKEN }} env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_WEBSITE }} + NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }} NEXT_PUBLIC_APP_ENV: production NEXT_TELEMETRY_DISABLED: 1 NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }} NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }} - - name: Bypass Vercel Nx Plugin (website) - run: | - if [ -f nx.json ]; then - mv nx.json nx.json.bak - fi - - - name: Prepare website package manifest for Vercel build - run: | - node -e 'const fs=require("fs");const root=JSON.parse(fs.readFileSync("package.json","utf8"));const p="apps/website/package.json";const pkg=JSON.parse(fs.readFileSync(p,"utf8"));const internal=["@elzatona/common-ui","@elzatona/contexts","@elzatona/database","@elzatona/types","@elzatona/utilities"];const required=["@sentry/nextjs"];pkg.dependencies=pkg.dependencies||{};for(const name of internal){if(pkg.dependencies) delete pkg.dependencies[name]; if(pkg.devDependencies) delete pkg.devDependencies[name];}for(const name of required){if(!pkg.dependencies[name]){const v=(root.dependencies&&root.dependencies[name])||(root.devDependencies&&root.devDependencies[name]);if(v) pkg.dependencies[name]=v;}}fs.writeFileSync(p, JSON.stringify(pkg, null, 2)+"\n");' - - - name: Set root build script for website deployment - run: | - node -e 'const fs=require("fs");const p="package.json";const pkg=JSON.parse(fs.readFileSync(p,"utf8"));pkg.scripts=pkg.scripts||{};pkg.scripts.build="pnpm --dir apps/website build";fs.writeFileSync(p, JSON.stringify(pkg, null, 2)+"\n");' - - - name: Deploy website to Vercel + - name: Deploy website to Vercel (Prebuilt) run: | - set -euo pipefail - - ensure_website_ready() { - local deployment_url="$1" - - for ready_attempt in 1 2 3 4 5 6; do - ready_log="$(mktemp)" - if timeout 2m npx vercel inspect "${deployment_url}" --wait --timeout 2m --token=${{ secrets.VERCEL_TOKEN }} >"$ready_log" 2>&1; then - cat "$ready_log" - return 0 - fi - - cat "$ready_log" - echo "Deployment readiness check ${ready_attempt}/6 did not pass yet." - - if [ "$ready_attempt" -lt 6 ]; then - sleep 10 - fi - done - - return 1 - } + set +e # Explicitly disable exit-on-error to allow retry loop to handle failures + set -uo pipefail set_website_aliases() { local deployment_url="$1" echo "Setting website aliases for: ${deployment_url}" - timeout 3m npx vercel alias set "${deployment_url}" elzatona-web.com --token=${{ secrets.VERCEL_TOKEN }} + timeout 3m npx vercel@latest alias set "${deployment_url}" elzatona-web.com --token=${{ secrets.VERCEL_TOKEN }} # Keep www alias best-effort because some projects intentionally omit it. - if ! timeout 3m npx vercel alias set "${deployment_url}" www.elzatona-web.com --token=${{ secrets.VERCEL_TOKEN }}; then + if ! timeout 3m npx vercel@latest alias set "${deployment_url}" www.elzatona-web.com --token=${{ secrets.VERCEL_TOKEN }}; then echo "www.elzatona-web.com alias not set (continuing)." fi } - timeout 8m npx vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} for attempt in 1 2 3 4 5 6; do echo "Deploy website attempt ${attempt}/6" deploy_log="$(mktemp)" - timeout 20m npx vercel deploy --prod --yes --token=${{ secrets.VERCEL_TOKEN }} 2>&1 | tee "$deploy_log" || true - if grep -Eq 'Failed to process project graph|Could not find ".modules.yaml"|ENOENT: no such file or directory|Error: Command "npx nx build|Error: Command "npm run build" exited with 1|Error: Command "npm install" exited with 1' "$deploy_log"; then - echo "Deploy output indicates explicit build failure; failing deployment step." - exit 1 - fi - prod_url="$(grep -Eo 'https://[^ ]+\.vercel\.app' "$deploy_log" | tail -n 1 || true)" - if [ -n "$prod_url" ]; then - echo "Deploy command produced URL: $prod_url" - inspect_log="$(mktemp)" - if timeout 12m npx vercel inspect "$prod_url" --wait --timeout 12m --token=${{ secrets.VERCEL_TOKEN }} >"$inspect_log" 2>&1; then - cat "$inspect_log" + if timeout 15m npx vercel@latest deploy --prebuilt --prod --yes --token=${{ secrets.VERCEL_TOKEN }} 2>&1 | tee "$deploy_log"; then + prod_url=$(grep -Eo 'https://[^ ]+\.vercel\.app' "$deploy_log" | tail -n 1 || true) + if [ -z "$prod_url" ]; then + echo "Warning: Could not extract specific deployment URL; using production domain fallback." + # Fallback to the project's default production URL if extraction fails + prod_url="elzatona-web.vercel.app" + fi + echo "Website deployment is ready: $prod_url" set_website_aliases "$prod_url" - echo "Website deployment is ready in production: $prod_url" exit 0 - fi - cat "$inspect_log" - - if grep -Eq 'Failed to process project graph|Could not find ".modules.yaml"|ENOENT: no such file or directory|Error: Command "npx nx build|Error: Command "npm run build" exited with 1|Error: Command "npm install" exited with 1' "$inspect_log"; then - echo "Inspect indicates an explicit build failure; failing deployment step." - exit 1 - fi - echo "Inspect did not confirm readiness; probing URL reachability as transient fallback..." - for probe in 1 2 3 4 5 6; do - http_code="$(curl -sS -o /dev/null -w '%{http_code}' --max-time 20 "$prod_url" || true)" - if [ "$http_code" != "000" ]; then - echo "Website deployment URL is reachable (HTTP $http_code): $prod_url" - - if ensure_website_ready "$prod_url"; then - set_website_aliases "$prod_url" - echo "Website deployment is ready in production: $prod_url" - exit 0 - fi + fi - echo "Website URL is reachable but deployment is still not ready; continuing retries." - break - fi - echo "Website probe ${probe}/6 not reachable yet; retrying in 10s..." - sleep 10 - done - echo "Inspect and reachability probe did not confirm readiness; retrying deployment attempt..." + if grep -Eq 'Failed to process project graph|Could not find ".modules.yaml"|ENOENT: no such file or directory|Error: Command "npx nx build|Error: Command "npm run build" exited with 1|Error: Command "npm install" exited with 1|Error: The request timed out' "$deploy_log"; then + echo "Deploy output indicates explicit failure or timeout; failing deployment step." + exit 1 fi + if [ "$attempt" -eq 6 ]; then echo "Website deployment failed after 6 attempts" exit 1 @@ -197,6 +152,7 @@ jobs: env: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_WEBSITE }} + NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }} deploy-admin: name: Deploy Admin @@ -220,14 +176,23 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "24" cache: "pnpm" - name: Enable Corepack run: corepack enable - name: Install root dependencies - run: pnpm install --no-frozen-lockfile + run: pnpm install --frozen-lockfile + + - name: Setup Vercel and Next.js Caching + uses: actions/cache@v4 + with: + path: | + apps/admin/.next/cache + key: ${{ runner.os }}-next-cache-${{ hashFiles('pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-next-cache- - name: Validate Vercel token run: | @@ -242,71 +207,40 @@ jobs: curl -fsS -X PATCH \ -H "Authorization: Bearer ${{ secrets.VERCEL_TOKEN }}" \ -H "Content-Type: application/json" \ - -d '{"buildCommand":"npm run build","outputDirectory":".next"}' \ + -d '{"buildCommand":"pnpm --filter admin build","outputDirectory":"apps/admin/.next","installCommand":"echo skip"}' \ "https://api.vercel.com/v9/projects/${{ secrets.VERCEL_PROJECT_ID_ADMIN }}" - - name: Validate build locally (optional build check) + - name: Validate and build (Local Vercel Build) if: ${{ inputs.validate_build_admin }} - run: pnpm --dir apps/admin run build + run: | + npx vercel@latest pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} + npx vercel@latest build --prod --token=${{ secrets.VERCEL_TOKEN }} env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_ADMIN }} + NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }} NEXT_PUBLIC_APP_ENV: production NEXT_TELEMETRY_DISABLED: 1 NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }} NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }} - - name: Bypass Vercel Nx Plugin (admin) - run: | - if [ -f nx.json ]; then - mv nx.json nx.json.bak - fi - - - name: Prepare admin package manifest for Vercel build - run: | - node -e 'const fs=require("fs");const root=JSON.parse(fs.readFileSync("package.json","utf8"));const p="apps/admin/package.json";const pkg=JSON.parse(fs.readFileSync(p,"utf8"));const internal=["@elzatona/common-ui","@elzatona/contexts","@elzatona/database","@elzatona/types","@elzatona/utilities"];const required=["@sentry/nextjs"];pkg.dependencies=pkg.dependencies||{};for(const name of internal){if(pkg.dependencies) delete pkg.dependencies[name]; if(pkg.devDependencies) delete pkg.devDependencies[name];}for(const name of required){if(!pkg.dependencies[name]){const v=(root.dependencies&&root.dependencies[name])||(root.devDependencies&&root.devDependencies[name]);if(v) pkg.dependencies[name]=v;}}fs.writeFileSync(p, JSON.stringify(pkg, null, 2)+"\n");' - - - name: Set root build script for admin deployment - run: | - node -e 'const fs=require("fs");const p="package.json";const pkg=JSON.parse(fs.readFileSync(p,"utf8"));pkg.scripts=pkg.scripts||{};pkg.scripts.build="pnpm --dir apps/admin build";fs.writeFileSync(p, JSON.stringify(pkg, null, 2)+"\n");' - - - name: Deploy admin to Vercel + - name: Deploy admin to Vercel (Prebuilt) run: | - set -euo pipefail - timeout 8m npx vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} + set +e + set -uo pipefail for attempt in 1 2 3 4 5 6; do echo "Deploy admin attempt ${attempt}/6" deploy_log="$(mktemp)" - timeout 20m npx vercel deploy --prod --yes --token=${{ secrets.VERCEL_TOKEN }} 2>&1 | tee "$deploy_log" || true - if grep -Eq 'Failed to process project graph|Could not find ".modules.yaml"|ENOENT: no such file or directory|Error: Command "npx nx build|Error: Command "npm run build" exited with 1|Error: Command "npm install" exited with 1' "$deploy_log"; then - echo "Deploy output indicates explicit build failure; failing deployment step." - exit 1 + if timeout 15m npx vercel@latest deploy --prebuilt --prod --yes --token=${{ secrets.VERCEL_TOKEN }} 2>&1 | tee "$deploy_log"; then + echo "Admin deployment is ready in production." + exit 0 fi - prod_url="$(grep -Eo 'https://[^ ]+\.vercel\.app' "$deploy_log" | tail -n 1 || true)" - if [ -n "$prod_url" ]; then - echo "Deploy command produced URL: $prod_url" - inspect_log="$(mktemp)" - if timeout 12m npx vercel inspect "$prod_url" --wait --timeout 12m --token=${{ secrets.VERCEL_TOKEN }} >"$inspect_log" 2>&1; then - cat "$inspect_log" - echo "Admin deployment is ready in production: $prod_url" - exit 0 - fi - cat "$inspect_log" - if grep -Eq 'Failed to process project graph|Could not find ".modules.yaml"|ENOENT: no such file or directory|Error: Command "npx nx build|Error: Command "npm run build" exited with 1|Error: Command "npm install" exited with 1' "$inspect_log"; then - echo "Inspect indicates an explicit build failure; failing deployment step." - exit 1 - fi - echo "Inspect did not confirm readiness; probing URL reachability as transient fallback..." - for probe in 1 2 3 4 5 6; do - http_code="$(curl -sS -o /dev/null -w '%{http_code}' --max-time 20 "$prod_url" || true)" - if [ "$http_code" != "000" ]; then - echo "Admin deployment URL is reachable (HTTP $http_code): $prod_url" - exit 0 - fi - echo "Admin probe ${probe}/6 not reachable yet; retrying in 10s..." - sleep 10 - done - echo "Inspect and reachability probe did not confirm readiness; retrying deployment attempt..." + if grep -Eq 'Failed to process project graph|Could not find ".modules.yaml"|ENOENT: no such file or directory|Error: Command "npx nx build|Error: Command "npm run build" exited with 1|Error: Command "npm install" exited with 1|Error: The request timed out' "$deploy_log"; then + echo "Deploy output indicates explicit failure or timeout; failing deployment step." + exit 1 fi + if [ "$attempt" -eq 6 ]; then echo "Admin deployment failed after 6 attempts" exit 1 @@ -318,3 +252,4 @@ jobs: env: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_ADMIN }} + NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }} diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 81d916ea7..797820b32 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -32,7 +32,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "24" cache: "pnpm" - name: Install dependencies @@ -66,13 +66,20 @@ jobs: libs/common-ui/src/components/ui/dialog.test.tsx \ apps/website/src/app/lib/frontend-task-validator.test.ts \ apps/website/src/app/lib/frontend-task-validator-coverage.test.ts \ + libs/utilities/src/lib/api/questions-handler.test.ts \ + libs/utilities/src/lib/api/sanitize-server.test.ts \ + libs/utilities/src/lib/api/validation.test.ts \ + libs/utilities/src/lib/api/stats-utils.test.ts \ + libs/utilities/src/lib/api/__tests__/api-config.test.ts \ + apps/website/src/app/lib/network/routes/questions/unified/route.test.ts \ + libs/utilities/src/lib/api/__tests__/stats-utils.test.ts \ --coverage \ --coverage.provider=v8 \ --coverage.reporter=lcov \ --coverage.reporter=text-summary test -f coverage/lcov.info env: - NODE_OPTIONS: "--max-old-space-size=1536" + NODE_OPTIONS: "--max-old-space-size=2048" - name: SonarQube Scan uses: SonarSource/sonarqube-scan-action@v6 diff --git a/.husky/pre-commit b/.husky/pre-commit index 0e99e338f..c4a49c391 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -2,4 +2,4 @@ set -e -pnpm run check:staged +npm run check:staged diff --git a/apps/admin/network/routes/questions/unified/route.ts b/apps/admin/network/routes/questions/unified/route.ts index 70b05f613..e6aaba3d3 100644 --- a/apps/admin/network/routes/questions/unified/route.ts +++ b/apps/admin/network/routes/questions/unified/route.ts @@ -1,490 +1,19 @@ -import { NextRequest, NextResponse } from "next/server"; -import { createClient } from "@supabase/supabase-js"; +// v1.1 - Consolidated Admin Questions API Route +// Delegates to the shared @elzatona/utilities library. -// Type definitions for question relationships -interface QuestionTopic { - id?: string; - name?: string; - slug?: string; - difficulty?: string; - is_primary?: boolean; - order_index?: number; -} - -interface QuestionCategory { - id?: string; - name?: string; - slug?: string; - card_type?: string; - is_primary?: boolean; - order_index?: number; -} - -interface QuestionTopicRelation { - question_id: string; - topics: QuestionTopic | QuestionTopic[]; - is_primary: boolean; - order_index: number; -} - -interface QuestionCategoryRelation { - card_id: string; - categories: QuestionCategory | QuestionCategory[]; - is_primary: boolean; - order_index: number; -} - -// Get Supabase client - handle missing env vars gracefully -function getSupabaseClient() { - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; - const supabaseServiceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY; - - if (!supabaseUrl || !supabaseServiceRoleKey) { - console.error("❌ Supabase configuration missing:", { - hasUrl: !!supabaseUrl, - hasKey: !!supabaseServiceRoleKey, - }); - throw new Error( - "Supabase URL or Service Role Key is missing in environment variables.", - ); - } - - return createClient(supabaseUrl, supabaseServiceRoleKey); -} - -// Helper function to get question IDs for topic filtering -async function getQuestionIdsForTopics( - supabase: ReturnType, - topicNames: string[], -): Promise { - if (topicNames.length === 0) return []; - - // Get topic IDs by names - const { data: topicsData } = await supabase - .from("topics") - .select("id") - .in("name", topicNames); - - if (!topicsData || topicsData.length === 0) return []; - - const topicIds = topicsData.map((t: { id: string }) => t.id); - - // Get question IDs that have any of these topics - const { data: questionTopicData } = await supabase - .from("questions_topics") - .select("question_id") - .in("topic_id", topicIds); - - if (!questionTopicData || questionTopicData.length === 0) return []; - - return questionTopicData.map((qt: { question_id: string }) => qt.question_id); -} +import { NextRequest } from "next/server"; +import { questionsGetHandler, questionsPostHandler } from "@elzatona/utilities"; +/** + * GET /api/admin/questions/unified - Get questions with filters + */ export async function GET(request: NextRequest) { - try { - // Get Supabase client - const supabase = getSupabaseClient(); - - const { searchParams } = new URL(request.url); - const page = Number.parseInt(searchParams.get("page") || "1", 10); - const pageSize = Number.parseInt(searchParams.get("pageSize") || "10", 10); - const category = searchParams.get("category"); - const difficulty = searchParams.get("difficulty"); - const type = searchParams.get("type"); - const isActive = searchParams.get("isActive"); - const search = searchParams.get("search"); - const topic = searchParams.get("topic"); - const topics = searchParams.get("topics"); // comma-separated topic names - - console.log("🔍 API Parameters:", { - page, - pageSize, - category, - difficulty, - type, - isActive, - search, - topic, - topics, - }); - - // Build query using junction tables to get topics and categories - let query = supabase.from("questions").select(` - *, - learning_cards ( - id, - title, - type, - color, - icon - ) - `); - - // Apply filters - if (category && category !== "all" && category !== "undefined") { - // Check if category is a UUID or a name - const isUUID = - /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test( - category, - ); - - if (isUUID) { - // Direct UUID filtering - query = query.eq("category_id", category); - } else { - // Category name filtering - get UUID from name - const { data: categoryData } = await supabase - .from("categories") - .select("id") - .eq("name", category) - .eq("is_active", true) - .single(); - - if (categoryData) { - query = query.eq("category_id", categoryData.id); - } - } - } - - if (difficulty && difficulty !== "all") { - query = query.eq("difficulty", difficulty); - } - - if (type && type !== "all") { - query = query.eq("type", type); - } - - if (isActive !== null && isActive !== "all") { - query = query.eq("is_active", isActive === "true"); - } - - if (search) { - query = query.or(`title.ilike.%${search}%,content.ilike.%${search}%`); - } - - // Apply topic filtering using junction table - let topicQuestionIds: string[] = []; - if (topic && topic !== "all") { - topicQuestionIds = await getQuestionIdsForTopics( - supabase as ReturnType, - [topic], - ); - } else if (topics) { - const topicNames = topics - .split(",") - .map((t) => t.trim()) - .filter((t) => t); - topicQuestionIds = await getQuestionIdsForTopics( - supabase as ReturnType, - topicNames, - ); - } - - if (topicQuestionIds.length > 0) { - query = query.in("id", topicQuestionIds); - } else if ((topic && topic !== "all") || topics) { - // No questions found with these topics, return empty result - query = query.eq("id", "no-results"); - } - - // Get total count with same filters - let countQuery = supabase - .from("questions") - .select("*", { count: "exact", head: true }); - - // Apply same filters to count query - if (category && category !== "all" && category !== "undefined") { - // Check if category is a UUID or a name - const isUUID = - /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test( - category, - ); - - if (isUUID) { - // Direct UUID filtering - countQuery = countQuery.eq("category_id", category); - } else { - // Category name filtering - get UUID from name - const { data: categoryData } = await supabase - .from("categories") - .select("id") - .eq("name", category) - .eq("is_active", true) - .single(); - - if (categoryData) { - countQuery = countQuery.eq("category_id", categoryData.id); - } - } - } - - if (difficulty && difficulty !== "all") { - countQuery = countQuery.eq("difficulty", difficulty); - } - - if (type && type !== "all") { - countQuery = countQuery.eq("type", type); - } - - if (isActive !== null && isActive !== "all") { - countQuery = countQuery.eq("is_active", isActive === "true"); - } - - if (search) { - countQuery = countQuery.or( - `title.ilike.%${search}%,content.ilike.%${search}%`, - ); - } - - // Apply same topic filtering to count query - if (topicQuestionIds.length > 0) { - countQuery = countQuery.in("id", topicQuestionIds); - } else if ((topic && topic !== "all") || topics) { - // No questions found with these topics, return empty result - countQuery = countQuery.eq("id", "no-results"); - } - - const { count, error: countError } = await countQuery; - - if (countError) throw countError; - - // Apply pagination - const from = (page - 1) * pageSize; - const to = from + pageSize - 1; - query = query.range(from, to); - - // Execute query - const { data, error } = await query; - - if (error) throw error; - - // Get question IDs for batch fetching - const questionIds = data?.map((q) => q.id) || []; - - // Batch fetch all topics for these questions - const { data: allQuestionTopics } = await supabase - .from("questions_topics") - .select( - ` - question_id, - topic_id, - is_primary, - order_index, - topics ( - id, - name, - slug, - difficulty - ) - `, - ) - .in("question_id", questionIds); - - // Get unique learning card IDs - const learningCardIds = Array.from( - new Set(data?.map((q) => q.learning_card_id).filter(Boolean)), - ); - - // Fetch all categories for direct category_id lookups - const { data: allCategories } = await supabase - .from("categories") - .select("id, name, slug, card_type") - .eq("is_active", true); - - // Batch fetch all categories for these learning cards - const { data: allCardCategories } = await supabase - .from("card_categories") - .select( - ` - card_id, - category_id, - is_primary, - order_index, - categories ( - id, - name, - slug, - card_type - ) - `, - ) - .in("card_id", learningCardIds); - - // Transform data to match expected format with topics and categories - const transformedData = - data?.map((question) => { - // Get topics for this question - const questionTopics = - allQuestionTopics?.filter((qt) => qt.question_id === question.id) || - []; - const topics = questionTopics.map((qt: QuestionTopicRelation) => { - // Handle topics as array or single object - const topic = Array.isArray(qt.topics) ? qt.topics[0] : qt.topics; - return { - id: topic?.id, - name: topic?.name, - slug: topic?.slug, - difficulty: topic?.difficulty, - is_primary: qt.is_primary, - order_index: qt.order_index, - }; - }); - - // Get categories for this question's learning card - const cardCategories = - allCardCategories?.filter( - (cc) => cc.card_id === question.learning_card_id, - ) || []; - const categories = cardCategories.map( - (cc: QuestionCategoryRelation) => { - // Handle categories as array or single object - const category = Array.isArray(cc.categories) - ? cc.categories[0] - : cc.categories; - return { - id: category?.id, - name: category?.name, - slug: category?.slug, - card_type: category?.card_type, - is_primary: cc.is_primary, - order_index: cc.order_index, - }; - }, - ); - - // Get primary topic and category - const primaryTopic = topics.find((t) => t.is_primary) || topics[0]; - // Use the question's direct category_id instead of card categories - const directCategory = allCategories?.find( - (c) => c.id === question.category_id, - ); - const primaryCategory = - directCategory || - categories.find((c) => c.is_primary) || - categories[0]; - - return { - id: question.id, - title: question.title, - content: question.content, - type: question.type, - difficulty: question.difficulty, - points: question.points || 1, - options: question.options || [], - correct_answer: question.correct_answer, - explanation: question.explanation, - test_cases: question.test_cases || [], - hints: question.hints || [], - tags: question.tags || [], - stats: question.stats || {}, - metadata: question.metadata || {}, - // Legacy fields for backward compatibility - category: primaryCategory?.name || "Unknown", - category_id: primaryCategory?.id, - topic: primaryTopic?.name || "Unknown", - topic_id: primaryTopic?.id, - learning_card_id: question.learning_card_id, - learning_card: question.learning_cards - ? { - id: question.learning_cards.id, - title: question.learning_cards.title, - type: question.learning_cards.type, - color: question.learning_cards.color, - icon: question.learning_cards.icon, - } - : null, - // New fields with full relationship data - topics: topics, - categories: categories, - isActive: question.is_active, - createdAt: question.created_at, - updatedAt: question.updated_at, - }; - }) || []; - - return NextResponse.json({ - success: true, - data: transformedData, - pagination: { - page, - pageSize, - totalCount: count || 0, - totalPages: Math.ceil((count || 0) / pageSize), - }, - }); - } catch (error) { - const errorMessage = - error instanceof Error ? error.message : "Failed to fetch questions"; - console.error("Error fetching questions:", errorMessage); - return NextResponse.json( - { success: false, error: errorMessage }, - { status: 500 }, - ); - } + return questionsGetHandler(request); } +/** + * POST /api/admin/questions/unified - Create question + */ export async function POST(request: NextRequest) { - try { - // Get Supabase client - const supabase = getSupabaseClient(); - - const body = await request.json(); - - const { - title, - content, - type, - difficulty, - points, - options, - correct_answer, - explanation, - test_cases, - hints, - tags, - stats, - metadata, - category_id, - learning_card_id, - isActive = true, - } = body; - - const { data, error } = await supabase - .from("questions") - .insert({ - title, - content, - type, - difficulty, - points, - options, - correct_answer, - explanation, - test_cases, - hints, - tags, - stats, - metadata, - category_id, - learning_card_id, - is_active: isActive, - }) - .select() - .single(); - - if (error) throw error; - - return NextResponse.json({ - success: true, - data, - }); - } catch (error) { - const errorMessage = - error instanceof Error ? error.message : "Failed to create question"; - console.error("Error creating question:", errorMessage); - return NextResponse.json( - { success: false, error: errorMessage }, - { status: 500 }, - ); - } + return questionsPostHandler(request); } diff --git a/apps/admin/src/app/admin/content-management/hooks/useContentManagement.ts b/apps/admin/src/app/admin/content-management/hooks/useContentManagement.ts index ff52ad91c..3ea24fe40 100644 --- a/apps/admin/src/app/admin/content-management/hooks/useContentManagement.ts +++ b/apps/admin/src/app/admin/content-management/hooks/useContentManagement.ts @@ -11,10 +11,7 @@ import type { AdminUnifiedQuestion, Topic as AdminTopic, } from "@elzatona/types"; -import { - useLearningCardRepository, - usePlanRepository, -} from "@elzatona/database/client"; +import { useLearningCardRepository } from "@elzatona/database/client"; import { supabase } from "@elzatona/utilities"; import type { Topic as DatabaseTopic } from "@elzatona/database"; @@ -56,23 +53,6 @@ type DatabasePlanRecord = { updatedAt?: string | Date | null; }; -type DatabaseLearningCardRecord = { - id: string; - title: string; - description?: string | null; - content?: string | null; - color?: string | null; - icon?: string | null; - order_index?: number | null; - order?: number | null; - is_active?: boolean | null; - isPublished?: boolean | null; - created_at?: string | Date | null; - updated_at?: string | Date | null; - createdAt?: string | Date | null; - updatedAt?: string | Date | null; -}; - type PlanGenerationMetadata = { title: string; description: string; @@ -352,7 +332,7 @@ function buildTopicCategoryLookup( return lookup; } -function transformQuestion(q: any): AdminUnifiedQuestion { +function transformQuestion(q: Record): AdminUnifiedQuestion { const category_id = q.category_id ?? q.categoryId ?? getPrimaryNestedId(q.categories) ?? ""; const topic_id = @@ -371,8 +351,7 @@ function transformQuestion(q: any): AdminUnifiedQuestion { isActive: q.isActive ?? q.is_active ?? true, createdAt: q.createdAt ?? q.created_at ?? "", updatedAt: q.updatedAt ?? q.updated_at ?? "", - // Ensure nested objects are handled if needed - }; + } as AdminUnifiedQuestion; } export const contentManagementMappingTestUtils = { @@ -675,22 +654,6 @@ function normalizePlanStatus( return fallback; } -function normalizeLearningCard( - card: DatabaseLearningCardRecord, -): AdminLearningCard { - return { - id: card.id, - title: card.title, - description: card.description ?? card.content ?? "", - color: card.color ?? "#3B82F6", - icon: card.icon ?? "BookOpen", - order_index: card.order_index ?? card.order ?? 0, - is_active: card.is_active ?? card.isPublished ?? true, - created_at: toIsoDate(card.created_at ?? card.createdAt), - updated_at: toIsoDate(card.updated_at ?? card.updatedAt), - }; -} - function getErrorMessage(error: unknown, fallbackMessage: string): string { return error instanceof Error ? error.message : fallbackMessage; } @@ -798,7 +761,6 @@ async function loadLearningPlans(): Promise> { export function useContentManagement() { // Inject repositories const cardRepository = useLearningCardRepository(); - const planRepository = usePlanRepository(); // Transform database Topic to admin Topic const transformTopicToAdmin = ( @@ -960,10 +922,12 @@ export function useContentManagement() { topicsResult.error, ].filter((message): message is string => Boolean(message)); - const normalizedCards = cardsResult.data; + const normalizedCards = cardsResult.data || []; const { cards: canonicalCards, idsByKey } = buildCanonicalCards(normalizedCards); - const normalizedQuestions = questionsResult.data.map(transformQuestion); + const normalizedQuestions = (questionsResult.data || []).map((q) => + transformQuestion(q as unknown as Record), + ); const categoryCardLookup = buildCategoryCardLookup(normalizedQuestions); const topicCategoryLookup = buildTopicCategoryLookup(normalizedQuestions); @@ -1016,7 +980,7 @@ export function useContentManagement() { } finally { setLoading(false); } - }, [cardRepository, planRepository]); + }, []); useEffect(() => { fetchData(); @@ -1153,14 +1117,26 @@ export function useContentManagement() { setExpandedCards(newExpandedCards); setExpandedCategories(newExpandedCategories); setExpandedTopics(newExpandedTopics); - }, [debouncedSearchTerm, topics, categories, questions]); + }, [ + debouncedSearchTerm, + topics, + categories, + questions, + expandedCards, + expandedCategories, + expandedTopics, + ]); // Toggles const toggleCard = useCallback( (id: string) => setExpandedCards((prev) => { const next = new Set(prev); - next.has(id) ? next.delete(id) : next.add(id); + if (next.has(id)) { + next.delete(id); + } else { + next.add(id); + } return next; }), [], @@ -1170,7 +1146,11 @@ export function useContentManagement() { (id: string) => setExpandedCategories((prev) => { const next = new Set(prev); - next.has(id) ? next.delete(id) : next.add(id); + if (next.has(id)) { + next.delete(id); + } else { + next.add(id); + } return next; }), [], @@ -1180,7 +1160,11 @@ export function useContentManagement() { (id: string) => setExpandedTopics((prev) => { const next = new Set(prev); - next.has(id) ? next.delete(id) : next.add(id); + if (next.has(id)) { + next.delete(id); + } else { + next.add(id); + } return next; }), [], @@ -1190,7 +1174,11 @@ export function useContentManagement() { (id: string) => setExpandedPlans((prev) => { const next = new Set(prev); - next.has(id) ? next.delete(id) : next.add(id); + if (next.has(id)) { + next.delete(id); + } else { + next.add(id); + } return next; }), [], @@ -1200,7 +1188,11 @@ export function useContentManagement() { (id: string) => setExpandedPlanCards((prev) => { const next = new Set(prev); - next.has(id) ? next.delete(id) : next.add(id); + if (next.has(id)) { + next.delete(id); + } else { + next.add(id); + } return next; }), [], @@ -1210,7 +1202,11 @@ export function useContentManagement() { (id: string) => setExpandedPlanCategories((prev) => { const next = new Set(prev); - next.has(id) ? next.delete(id) : next.add(id); + if (next.has(id)) { + next.delete(id); + } else { + next.add(id); + } return next; }), [], @@ -1220,7 +1216,11 @@ export function useContentManagement() { (id: string) => setExpandedPlanTopics((prev) => { const next = new Set(prev); - next.has(id) ? next.delete(id) : next.add(id); + if (next.has(id)) { + next.delete(id); + } else { + next.add(id); + } return next; }), [], @@ -1262,7 +1262,11 @@ export function useContentManagement() { const toggleQuestionSelection = useCallback((id: string) => { setSelectedQuestions((prev) => { const next = new Set(prev); - next.has(id) ? next.delete(id) : next.add(id); + if (next.has(id)) { + next.delete(id); + } else { + next.add(id); + } return next; }); }, []); @@ -1392,7 +1396,7 @@ export function useContentManagement() { ); } }, - [planRepository], + [], ); const openDeleteCardModal = useCallback((card: AdminLearningCard) => { @@ -1429,7 +1433,11 @@ export function useContentManagement() { try { // ARCHITECTURAL: Fetch current plan cards using planRepository.getPlanCards(selectedPlanId) and available cards // Requires implementing new repository methods for plan-card relationships - const current: any = []; + const current: { + card_id: string; + order_index: number; + is_active: boolean; + }[] = []; setPlanCards(current || []); setAvailableCards(cards); } catch (err) { @@ -1541,7 +1549,7 @@ export function useContentManagement() { toast.error(err instanceof Error ? err.message : "Failed to add card"); } }, - [selectedPlanForCards, planCards, planRepository], + [selectedPlanForCards, planCards], ); const removeCardFromPlan = useCallback( @@ -1563,7 +1571,7 @@ export function useContentManagement() { ); } }, - [selectedPlanForCards, planRepository], + [selectedPlanForCards], ); const toggleCardActiveStatus = useCallback( @@ -1589,7 +1597,7 @@ export function useContentManagement() { ); } }, - [selectedPlanForCards, planRepository], + [selectedPlanForCards], ); const createSpacedRepetitionPlans = useCallback(async () => { @@ -1827,7 +1835,7 @@ export function useContentManagement() { ...cleanData } = data; - const payload: any = { + const payload: Record = { ...cleanData, topic_id: selectedTopicIdForNewQuestion, }; @@ -1880,7 +1888,7 @@ export function useContentManagement() { }, []); const submitCard = useCallback( - async (data: any) => { + async (data: Record) => { setIsSubmittingCard(true); try { const payload = { @@ -1902,7 +1910,7 @@ export function useContentManagement() { toast.success("Card updated"); } else { // Create - (payload as any).is_active = true; + (payload as Record).is_active = true; const { error: createError } = await supabase .from("learning_cards") .insert(payload); diff --git a/apps/admin/src/app/admin/content-management/page.integration.test.tsx b/apps/admin/src/app/admin/content-management/page.integration.test.tsx index f235f0c44..b7e36ccc5 100644 --- a/apps/admin/src/app/admin/content-management/page.integration.test.tsx +++ b/apps/admin/src/app/admin/content-management/page.integration.test.tsx @@ -30,45 +30,59 @@ vi.mock("@elzatona/contexts", () => ({ // Mock common-ui components to avoid complex rendering vi.mock("@elzatona/common-ui", () => ({ - Button: ({ children, onClick, ...props }: any) => ( - ), - Dialog: ({ children }: any) =>
{children}
, - DialogContent: ({ children }: any) => ( + Dialog: ({ children }: { children: React.ReactNode }) => ( +
{children}
+ ), + DialogContent: ({ children }: { children: React.ReactNode }) => (
{children}
), - DialogHeader: ({ children }: any) => ( + DialogHeader: ({ children }: { children: React.ReactNode }) => (
{children}
), - DialogTitle: ({ children }: any) => ( + DialogTitle: ({ children }: { children: React.ReactNode }) => (
{children}
), - DialogDescription: ({ children }: any) => ( + DialogDescription: ({ children }: { children: React.ReactNode }) => (
{children}
), - Input: ({ ...props }: any) => , - Label: ({ children, ...props }: any) => , - Textarea: ({ children, ...props }: any) => ( - + Input: ({ ...props }: Record) => ( + + ), + Label: ({ children, ...props }: Record) => ( + ), - Select: ({ children, value, onValueChange }: any) => ( + Textarea: ({ children, ...props }: Record) => ( + + ), + Select: ({ children, value, onValueChange }: Record) => ( ), - SelectTrigger: ({ children }: any) => <>{children}, - SelectValue: ({ children }: any) => <>{children}, - SelectContent: ({ children }: any) => <>{children}, - SelectItem: ({ children, value }: any) => ( - + SelectTrigger: ({ children }: { children: React.ReactNode }) => ( + <>{children} + ), + SelectValue: ({ children }: { children: React.ReactNode }) => <>{children}, + SelectContent: ({ children }: { children: React.ReactNode }) => ( + <>{children} ), - StatsSection: ({ stats }: any) => ( + SelectItem: ({ + children, + value, + }: { + children: React.ReactNode; + value: string; + }) => , + StatsSection: ({ stats }: { stats: Record }) => (
{stats && Object.entries(stats).map(([key, value]) => ( @@ -81,18 +95,18 @@ vi.mock("@elzatona/common-ui", () => ({ onSearchChange, filterCardType, onFilterChange, - }: any) => ( + }: Record) => (
onSearchChange(e.target.value)} + value={searchTerm as any} + onChange={(e) => (onSearchChange as any)(e.target.value)} placeholder="Search..." /> ); + 36| input = screen.getByLabelText(""); + | ^ + 37| expect(input).toHaveAttribute("type", "password"); + 38| }); + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[74/103]⎯ + + FAIL libs/common-ui/src/components/ui/select.test.tsx > Select Component > should render select trigger +TestingLibraryElementError: Unable to find an accessible element with the role "combobox" + +Here are the accessible roles: + + button: + + Name "Select option. Current: Select...": +  + + -------------------------------------------------- + +Ignored nodes: comments, script, style + + 
 +  +  +  + Select... +  +  +  +  +  + 
 + 
 + + ❯ Object.getElementError node_modules/.pnpm/@testing-library+dom@10.4.1/node_modules/@testing-library/dom/dist/config.js:37:19 + ❯ node_modules/.pnpm/@testing-library+dom@10.4.1/node_modules/@testing-library/dom/dist/query-helpers.js:76:38 + ❯ node_modules/.pnpm/@testing-library+dom@10.4.1/node_modules/@testing-library/dom/dist/query-helpers.js:52:17 + ❯ node_modules/.pnpm/@testing-library+dom@10.4.1/node_modules/@testing-library/dom/dist/query-helpers.js:95:19 + ❯ libs/common-ui/src/components/ui/select.test.tsx:28:28 + 26| , + 27| ); + 28| const trigger = screen.getByRole("combobox"); + | ^ + 29| expect(trigger).toBeInTheDocument(); + 30| }); + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[75/103]⎯ + + FAIL libs/common-ui/src/components/ui/select.test.tsx > Select Component > should display placeholder +TestingLibraryElementError: Unable to find an element with the text: Choose.... This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible. + +Ignored nodes: comments, script, style + + 
 +  +  +  + Select... +  +  +  +  +  + 
 + 
 + + ❯ Object.getElementError node_modules/.pnpm/@testing-library+dom@10.4.1/node_modules/@testing-library/dom/dist/config.js:37:19 + ❯ node_modules/.pnpm/@testing-library+dom@10.4.1/node_modules/@testing-library/dom/dist/query-helpers.js:76:38 + ❯ node_modules/.pnpm/@testing-library+dom@10.4.1/node_modules/@testing-library/dom/dist/query-helpers.js:52:17 + ❯ node_modules/.pnpm/@testing-library+dom@10.4.1/node_modules/@testing-library/dom/dist/query-helpers.js:95:19 + ❯ libs/common-ui/src/components/ui/select.test.tsx:43:19 + 41| , + 42| ); + 43| expect(screen.getByText("Choose...")).toBeInTheDocument(); + | ^ + 44| }); + 45| + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[76/103]⎯ + + FAIL libs/common-ui/src/components/ui/select.test.tsx > Select Component > should open dropdown when trigger is clicked +TestingLibraryElementError: Unable to find an accessible element with the role "combobox" + +Here are the accessible roles: + + button: + + Name "Select option. Current: Select...": +  + + -------------------------------------------------- + +Ignored nodes: comments, script, style + + 
 +  +  +  + Select... +  +  +  +  +  + 
 +  + + ❯ Object.getElementError node_modules/.pnpm/@testing-library+dom@10.4.1/node_modules/@testing-library/dom/dist/config.js:37:19 + ❯ node_modules/.pnpm/@testing-library+dom@10.4.1/node_modules/@testing-library/dom/dist/query-helpers.js:76:38 + ❯ node_modules/.pnpm/@testing-library+dom@10.4.1/node_modules/@testing-library/dom/dist/query-helpers.js:52:17 + ❯ node_modules/.pnpm/@testing-library+dom@10.4.1/node_modules/@testing-library/dom/dist/query-helpers.js:95:19 + ❯ libs/common-ui/src/components/ui/select.test.tsx:59:28 + 57| ); + 58| + 59| const trigger = screen.getByRole("combobox"); + | ^ + 60| fireEvent.click(trigger); + 61| + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[77/103]⎯ + + FAIL libs/common-ui/src/components/ui/select.test.tsx > Select Component > should call onValueChange when item is selected +TestingLibraryElementError: Unable to find an accessible element with the role "combobox" + +Here are the accessible roles: + + button: + + Name "Select option. Current: Select...": +  + + -------------------------------------------------- + +Ignored nodes: comments, script, style + + 
 +  +  +  + Select... +  +  +  +  +  + 
 +  + + ❯ Object.getElementError node_modules/.pnpm/@testing-library+dom@10.4.1/node_modules/@testing-library/dom/dist/config.js:37:19 + ❯ node_modules/.pnpm/@testing-library+dom@10.4.1/node_modules/@testing-library/dom/dist/query-helpers.js:76:38 + ❯ node_modules/.pnpm/@testing-library+dom@10.4.1/node_modules/@testing-library/dom/dist/query-helpers.js:52:17 + ❯ node_modules/.pnpm/@testing-library+dom@10.4.1/node_modules/@testing-library/dom/dist/query-helpers.js:95:19 + ❯ libs/common-ui/src/components/ui/select.test.tsx:81:28 + 79| ); + 80| + 81| const trigger = screen.getByRole("combobox"); + | ^ + 82| fireEvent.click(trigger); + 83| + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[78/103]⎯ + + FAIL libs/common-ui/src/components/ui/select.test.tsx > Select Component > should be disabled when disabled prop is true +TestingLibraryElementError: Unable to find an accessible element with the role "combobox" + +Here are the accessible roles: + + button: + + Name "Select option. Current: Select...": +  + + -------------------------------------------------- + +Ignored nodes: comments, script, style + + 
 +  +  +  + Select... +  +  +  +  +  + 
 +  + + ❯ Object.getElementError node_modules/.pnpm/@testing-library+dom@10.4.1/node_modules/@testing-library/dom/dist/config.js:37:19 + ❯ node_modules/.pnpm/@testing-library+dom@10.4.1/node_modules/@testing-library/dom/dist/query-helpers.js:76:38 + ❯ node_modules/.pnpm/@testing-library+dom@10.4.1/node_modules/@testing-library/dom/dist/query-helpers.js:52:17 + ❯ node_modules/.pnpm/@testing-library+dom@10.4.1/node_modules/@testing-library/dom/dist/query-helpers.js:95:19 + ❯ libs/common-ui/src/components/ui/select.test.tsx:104:28 + 102| ); + 103| + 104| const trigger = screen.getByRole("combobox"); + | ^ + 105| expect(trigger).toHaveAttribute("disabled"); + 106| }); + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[79/103]⎯ + + FAIL libs/common-ui/src/components/molecules/AdminMetricCard.test.tsx > AdminMetricCard > Loading State > should show loading skeleton when loading is true +Error: expect(received).toBeInTheDocument() + +received value must be an HTMLElement or an SVGElement. +Received has type: Null +Received has value: null + ❯ libs/common-ui/src/components/molecules/AdminMetricCard.test.tsx:87:53 + 85| // Check for loading skeleton (animate-pulse class) + 86| const card = screen.getByText("Total Questions").closest("div"); + 87| expect(card?.querySelector(".animate-pulse")).toBeInTheDocument(… + | ^ + 88| }); + 89| + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[80/103]⎯ + + FAIL libs/common-ui/src/components/molecules/LearningStyleSelector.test.tsx > LearningStyleSelector > should render both learning type cards +TestingLibraryElementError: Found multiple elements with the text: /Guided Learning/i + +Here are the matching elements: + +Ignored nodes: comments, script, style + + Guided Learning + + +Ignored nodes: comments, script, style + + Start  + Guided Learning + + +(If this is intentional, then use the `*AllBy*` variant of the query (like `queryAllByText`, `getAllByText`, or `findAllByText`)). + +Ignored nodes: comments, script, style + + 
 +  +  +  +  + How would you like to learn? +  +  + Choose your learning style to get the most personalized experience. + 

 + 
 +  +  +  +  +  +  +  +  +  +  + Guided Learning +  +  + Follow structured learning paths designed by experts. Perfect for beginners or those who prefer a guided approach. + 

 +  +  + Start  + Guided Learning +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + Free Style Learning +  +  + Create your own learning roadmap and explore topics at your own pace. Perfect for experienced learners. + 

 +  +  + Start  + Free Style Learning +  +  +  +  +  +  +  +  +  +  +  +  + + ❯ Object.getElementError node_modules/.pnpm/@testing-library+dom@10.4.1/node_modules/@testing-library/dom/dist/config.js:37:19 + ❯ getElementError node_modules/.pnpm/@testing-library+dom@10.4.1/node_modules/@testing-library/dom/dist/query-helpers.js:20:35 + ❯ getMultipleElementsFoundError node_modules/.pnpm/@testing-library+dom@10.4.1/node_modules/@testing-library/dom/dist/query-helpers.js:23:10 + ❯ node_modules/.pnpm/@testing-library+dom@10.4.1/node_modules/@testing-library/dom/dist/query-helpers.js:55:13 + ❯ node_modules/.pnpm/@testing-library+dom@10.4.1/node_modules/@testing-library/dom/dist/query-helpers.js:95:19 + ❯ libs/common-ui/src/components/molecules/LearningStyleSelector.test.tsx:26:19 + 24| />, + 25| ); + 26| expect(screen.getByText(/Guided Learning/i)).toBeInTheDocument(); + | ^ + 27| expect(screen.getByText(/Free Style Learning/i)).toBeInTheDocument… + 28| }); + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[81/103]⎯ + + FAIL libs/database/src/adapters/postgresql/__tests__/PostgreSQLPlanRepository.test.ts > PostgreSQLPlanRepository > Table Configuration > should use plan_cards as the main table +AssertionError: expected "spy" to be called with arguments: [ 'plan_cards' ] + +Received: + + 1st spy call: + + [ +- "plan_cards", ++ "learning_plans", + ] + + +Number of calls: 1 + + ❯ libs/database/src/adapters/postgresql/__tests__/PostgreSQLPlanRepository.test.ts:43:25 + 41| const clientSpy = vi.spyOn((repository as any).client, "from"); + 42| repository.findAll(); + 43| expect(clientSpy).toHaveBeenCalledWith("plan_cards"); + | ^ + 44| }); + 45| }); + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[82/103]⎯ + + FAIL libs/common-ui/src/admin/content-management/modals/QuestionFormModal.test.tsx > QuestionFormModal > renders create mode by default +Error: expect(element).toHaveClass("h-[100dvh]") + +Expected the element to have class: + h-[100dvh] +Received: + max-h-[90vh] rounded-xl bg-white dark:bg-gray-900 shadow-2xl flex flex-col fixed inset-x-0 top-[104px] sm:top-[120px] bottom-0 z-[201] m-0 h-[calc(100dvh-104px)] sm:h-[calc(100dvh-120px)] w-[100vw] max-h-none max-w-none overflow-hidden rounded-t-2xl border-0 p-0 + ❯ libs/common-ui/src/admin/content-management/modals/QuestionFormModal.test.tsx:34:53 + 32| ).toBeInTheDocument(); + 33| expect(screen.getByRole("dialog")).toHaveClass("z-[200]"); + 34| expect(container.querySelector("dialog > div")).toHaveClass("h-[10… + | ^ + 35| expect(container.querySelector("dialog > div")).toHaveClass("w-[10… + 36| }); + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[83/103]⎯ + + FAIL libs/common-ui/src/admin/content-management/modals/TopicQuestionsModal.test.tsx > TopicQuestionsModal > renders correctly with questions for the selected topic +TestingLibraryElementError: Unable to find an element with the text: Question 1. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible. + +Ignored nodes: comments, script, style + + 
 +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + Add Questions to Plan +  +  +  + Select questions from " + Test Topic + " to add to " + Test Plan + " + 

 + 
 +  +  +  +  +  +  + Select All +  +  +  +  + Deselect All +  +  +  +  + 1 +  of  + 2 +  selected +  +  +  +  +  +  + ); + 52| + 53| expect(screen.getByText("Question 1")).toBeInTheDocument(); + | ^ + 54| expect(screen.getByText("Question 2")).toBeInTheDocument(); + 55| expect(screen.queryByText("Question 3")).not.toBeInTheDocument(); + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[84/103]⎯ + + FAIL apps/website/src/app/features/guided-learning/utils/plan-helpers.test.ts > plan-helpers > getQuestionsRange > should return numeric range if totalQuestions exists +AssertionError: expected { min: 10, max: 20, …(2) } to be '10-20' // Object.is equality + +- Expected: +"10-20" + ++ Received: +{ + "label": "10-20 questions", + "max": 20, + "min": 10, + "type": "questions", +} + + ❯ apps/website/src/app/features/guided-learning/utils/plan-helpers.test.ts:73:40 + 71| it("should return numeric range if totalQuestions exists", () => { + 72| const plans = [{ totalQuestions: 10 }, { totalQuestions: 20 }]; + 73| expect(getQuestionsRange(plans)).toBe("10-20"); + | ^ + 74| }); + 75| + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[85/103]⎯ + + FAIL apps/website/src/app/features/guided-learning/utils/plan-helpers.test.ts > plan-helpers > getQuestionsRange > should return single number if only one plan has questions +AssertionError: expected { min: 15, max: 15, …(2) } to be '15-15' // Object.is equality + +- Expected: +"15-15" + ++ Received: +{ + "label": "15 questions", + "max": 15, + "min": 15, + "type": "questions", +} + + ❯ apps/website/src/app/features/guided-learning/utils/plan-helpers.test.ts:78:40 + 76| it("should return single number if only one plan has questions", (… + 77| const plans = [{ totalQuestions: 15 }]; + 78| expect(getQuestionsRange(plans)).toBe("15-15"); + | ^ + 79| }); + 80| + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[86/103]⎯ + + FAIL apps/website/src/app/features/guided-learning/utils/plan-helpers.test.ts > plan-helpers > getQuestionsRange > should fallback to duration hours if no questions +AssertionError: expected { Object (min, max, ...) } to be '5-10' // Object.is equality + +- Expected: +"5-10" + ++ Received: +{ + "label": "5-10h estimated", + "max": 10, + "min": 5, + "type": "hours", +} + + ❯ apps/website/src/app/features/guided-learning/utils/plan-helpers.test.ts:86:40 + 84| { duration: { totalHours: 10 } }, + 85| ]; + 86| expect(getQuestionsRange(plans)).toBe("5-10"); + | ^ + 87| }); + 88| + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[87/103]⎯ + + FAIL apps/website/src/app/features/guided-learning/utils/plan-helpers.test.ts > plan-helpers > getQuestionsRange > should return default text if no data +AssertionError: expected { min: 'Foundational', …(3) } to be 'Foundational to Expert' // Object.is equality + +- Expected: +"Foundational to Expert" + ++ Received: +{ + "label": "Foundational to Expert", + "max": "Expert", + "min": "Foundational", + "type": "descriptive", +} + + ❯ apps/website/src/app/features/guided-learning/utils/plan-helpers.test.ts:90:37 + 88| + 89| it("should return default text if no data", () => { + 90| expect(getQuestionsRange([])).toBe("Foundational to Expert"); + | ^ + 91| }); + 92| }); + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[88/103]⎯ + + + Snapshots 3 obsolete + ↳ libs/common-ui/src/common/ProgressTracker.test.tsx + · S-UT-SNAPSHOT: Progress Tracker Snapshot Tests should match progress tracker snapshot (challenge tracking) 1 + · S-UT-SNAPSHOT: Progress Tracker Snapshot Tests should match progress tracker snapshot (learning path tracking) 1 + · S-UT-SNAPSHOT: Progress Tracker Snapshot Tests should match progress tracker snapshot (question tracking) 1 + + Test Files 59 failed | 54 passed | 2 skipped (115) + Tests 76 failed | 522 passed | 6 skipped | 6 todo (610) + Start at 01:58:16 + Duration 24.36s (transform 4.00s, setup 24.46s, collect 21.54s, tests 9.20s, environment 44.00s, prepare 8.92s) + diff --git a/docs/admin/deployment-troubleshooting.md b/docs/admin/deployment-troubleshooting.md new file mode 100644 index 000000000..575c5159d --- /dev/null +++ b/docs/admin/deployment-troubleshooting.md @@ -0,0 +1,39 @@ +# Troubleshooting: Admin UI Changes "Skipped" in Production + +This document provides a reference for an issue where UI changes in the Admin application were successfully verified locally but failed to appear in the production environment (`https://elzatona-web.com/admin`). + +## Issue Description + +Changes made to the Content Management interface (specifically row alignment and modal positioning) were confirmed in local development and pushed to a PR branch, but after triggering a manual deployment, the live site remained on the old version. + +## Root Cause Discovery + +### 1. Silent Build Failures on Vercel + +Upon investigation using the Vercel CLI (`vercel ls`), it was discovered that many recent deployments were in an **Error** state. However, the GitHub Action workflow (`manual-deploy-parallel.yml`) was incorrectly reporting **Success** because it was only tracking the triggering of the deployment, not the remote build outcome. + +### 2. Destructive Workflow Patching + +The primary cause of the build failures was a "patching" step in the GitHub workflow that was intended to prepare the repository for Vercel. + +- **The specific bug**: The script was deleting internal workspace dependencies (like `@elzatona/common-ui`) from the `apps/admin/package.json` file before uploading to Vercel. +- **The consequence**: Since Next.js was configured to use `transpilePackages` for these libraries, it required the source code to be present and correctly linked via `pnpm`. By deleting the dependencies, the `pnpm install` on Vercel skipped linking those libraries, causing the `next build` command to fail with "module not found" errors. + +## The Solution + +### UI Fixes + +1. **Row Alignment**: Standardized all rows (Cards, Categories, Topics) to use `ml-auto` on the badge containers, ensuring consistent text-left/badge-right layout. +2. **Modal Synergy**: Keeping the admin navbar visible when editing questions to provide a visual anchor for the modal's top offset. + +### Deployment Fixes + +1. **Stop Manual Patching**: Removed the steps in `manual-deploy-parallel.yml` that deleted internal dependencies or modified `package.json` on the fly. +2. **Native Monorepo Support**: Switched the Vercel build command to `pnpm --filter admin build`. This allows Vercel's native `pnpm` workspace support to handle the library linking automatically. +3. **Syncing Vercel Settings**: Updated the workflow to explicitly set the `outputDirectory` to `apps/admin/.next` during the project configuration step. + +## Lessons Learned + +- **Avoid Destructive Edits**: Do not delete dependencies or rename critical configuration files (like `nx.json`) in the CI pipeline unless absolutely necessary. +- **Trust the Package Manager**: Modern tools like `pnpm` and deployment platforms like Vercel have native support for monorepos that is more reliable than manual scripting. +- **Monitor the Target**: Always check the logs of the actual deployment platform (Vercel) when changes appear to be skipped, as the CI pipeline (GitHub) may not always have the full picture. diff --git a/failed_job_log.txt b/failed_job_log.txt new file mode 100644 index 000000000..762d29408 --- /dev/null +++ b/failed_job_log.txt @@ -0,0 +1,415 @@ +Deploy Admin Set up job 2026-04-17T21:49:40.1792838Z Current runner version: '2.333.1' +Deploy Admin Set up job 2026-04-17T21:49:40.1814075Z ##[group]Runner Image Provisioner +Deploy Admin Set up job 2026-04-17T21:49:40.1814844Z Hosted Compute Agent +Deploy Admin Set up job 2026-04-17T21:49:40.1815275Z Version: 20260213.493 +Deploy Admin Set up job 2026-04-17T21:49:40.1815801Z Commit: 5c115507f6dd24b8de37d8bbe0bb4509d0cc0fa3 +Deploy Admin Set up job 2026-04-17T21:49:40.1816410Z Build Date: 2026-02-13T00:28:41Z +Deploy Admin Set up job 2026-04-17T21:49:40.1816947Z Worker ID: {9b823b10-5fdd-4177-ae5b-ee3693d7aefd} +Deploy Admin Set up job 2026-04-17T21:49:40.1817501Z Azure Region: westcentralus +Deploy Admin Set up job 2026-04-17T21:49:40.1818043Z ##[endgroup] +Deploy Admin Set up job 2026-04-17T21:49:40.1819093Z ##[group]Operating System +Deploy Admin Set up job 2026-04-17T21:49:40.1819629Z Ubuntu +Deploy Admin Set up job 2026-04-17T21:49:40.1820055Z 24.04.4 +Deploy Admin Set up job 2026-04-17T21:49:40.1820659Z LTS +Deploy Admin Set up job 2026-04-17T21:49:40.1821098Z ##[endgroup] +Deploy Admin Set up job 2026-04-17T21:49:40.1821550Z ##[group]Runner Image +Deploy Admin Set up job 2026-04-17T21:49:40.1822051Z Image: ubuntu-24.04 +Deploy Admin Set up job 2026-04-17T21:49:40.1822558Z Version: 20260413.86.1 +Deploy Admin Set up job 2026-04-17T21:49:40.1823421Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20260413.86/images/ubuntu/Ubuntu2404-Readme.md +Deploy Admin Set up job 2026-04-17T21:49:40.1824759Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20260413.86 +Deploy Admin Set up job 2026-04-17T21:49:40.1825557Z ##[endgroup] +Deploy Admin Set up job 2026-04-17T21:49:40.1826476Z ##[group]GITHUB_TOKEN Permissions +Deploy Admin Set up job 2026-04-17T21:49:40.1827967Z Contents: read +Deploy Admin Set up job 2026-04-17T21:49:40.1828500Z Metadata: read +Deploy Admin Set up job 2026-04-17T21:49:40.1828984Z ##[endgroup] +Deploy Admin Set up job 2026-04-17T21:49:40.1830946Z Secret source: Actions +Deploy Admin Set up job 2026-04-17T21:49:40.1831650Z Prepare workflow directory +Deploy Admin Set up job 2026-04-17T21:49:40.2093414Z Prepare all required actions +Deploy Admin Set up job 2026-04-17T21:49:40.2125691Z Getting action download info +Deploy Admin Set up job 2026-04-17T21:49:40.6277710Z Download action repository 'actions/checkout@v4' (SHA:34e114876b0b11c390a56381ad16ebd13914f8d5) +Deploy Admin Set up job 2026-04-17T21:49:40.7467332Z Download action repository 'pnpm/action-setup@v4' (SHA:b906affcce14559ad1aafd4ab0e942779e9f58b1) +Deploy Admin Set up job 2026-04-17T21:49:41.7547115Z Download action repository 'actions/setup-node@v4' (SHA:49933ea5288caeca8642d1e84afbd3f7d6820020) +Deploy Admin Set up job 2026-04-17T21:49:41.8884348Z Download action repository 'actions/cache@v4' (SHA:0057852bfaa89a56745cba8c7296529d2fc39830) +Deploy Admin Set up job 2026-04-17T21:49:42.2554390Z Complete job name: Deploy Admin +Deploy Admin Checkout repository 2026-04-17T21:49:42.3083483Z ##[group]Run actions/checkout@v4 +Deploy Admin Checkout repository 2026-04-17T21:49:42.3083959Z with: +Deploy Admin Checkout repository 2026-04-17T21:49:42.3084185Z ref: feat/optimize-ci-and-deploy-sync +Deploy Admin Checkout repository 2026-04-17T21:49:42.3084470Z repository: FoushWare/elzatona_web +Deploy Admin Checkout repository 2026-04-17T21:49:42.3084858Z token: *** +Deploy Admin Checkout repository 2026-04-17T21:49:42.3085065Z ssh-strict: true +Deploy Admin Checkout repository 2026-04-17T21:49:42.3085277Z ssh-user: git +Deploy Admin Checkout repository 2026-04-17T21:49:42.3085495Z persist-credentials: true +Deploy Admin Checkout repository 2026-04-17T21:49:42.3085728Z clean: true +Deploy Admin Checkout repository 2026-04-17T21:49:42.3085944Z sparse-checkout-cone-mode: true +Deploy Admin Checkout repository 2026-04-17T21:49:42.3086198Z fetch-depth: 1 +Deploy Admin Checkout repository 2026-04-17T21:49:42.3086396Z fetch-tags: false +Deploy Admin Checkout repository 2026-04-17T21:49:42.3086597Z show-progress: true +Deploy Admin Checkout repository 2026-04-17T21:49:42.3086796Z lfs: false +Deploy Admin Checkout repository 2026-04-17T21:49:42.3086977Z submodules: false +Deploy Admin Checkout repository 2026-04-17T21:49:42.3087173Z set-safe-directory: true +Deploy Admin Checkout repository 2026-04-17T21:49:42.3087641Z ##[endgroup] +Deploy Admin Checkout repository 2026-04-17T21:49:42.3924143Z Syncing repository: FoushWare/elzatona_web +Deploy Admin Checkout repository 2026-04-17T21:49:42.3925511Z ##[group]Getting Git version info +Deploy Admin Checkout repository 2026-04-17T21:49:42.3925984Z Working directory is '/home/runner/work/elzatona_web/elzatona_web' +Deploy Admin Checkout repository 2026-04-17T21:49:42.3926774Z [command]/usr/bin/git version +Deploy Admin Checkout repository 2026-04-17T21:49:42.4488056Z git version 2.53.0 +Deploy Admin Checkout repository 2026-04-17T21:49:42.4506709Z ##[endgroup] +Deploy Admin Checkout repository 2026-04-17T21:49:42.4518316Z Temporarily overriding HOME='/home/runner/work/_temp/10bbbbd5-1677-4e7b-aec4-c0c87085f301' before making global git config changes +Deploy Admin Checkout repository 2026-04-17T21:49:42.4519009Z Adding repository directory to the temporary git global config as a safe directory +Deploy Admin Checkout repository 2026-04-17T21:49:42.4529552Z [command]/usr/bin/git config --global --add safe.directory /home/runner/work/elzatona_web/elzatona_web +Deploy Admin Checkout repository 2026-04-17T21:49:42.4555557Z Deleting the contents of '/home/runner/work/elzatona_web/elzatona_web' +Deploy Admin Checkout repository 2026-04-17T21:49:42.4558329Z ##[group]Initializing the repository +Deploy Admin Checkout repository 2026-04-17T21:49:42.4561830Z [command]/usr/bin/git init /home/runner/work/elzatona_web/elzatona_web +Deploy Admin Checkout repository 2026-04-17T21:49:42.4638784Z hint: Using 'master' as the name for the initial branch. This default branch name +Deploy Admin Checkout repository 2026-04-17T21:49:42.4639579Z hint: will change to "main" in Git 3.0. To configure the initial branch name +Deploy Admin Checkout repository 2026-04-17T21:49:42.4640447Z hint: to use in all of your new repositories, which will suppress this warning, +Deploy Admin Checkout repository 2026-04-17T21:49:42.4641018Z hint: call: +Deploy Admin Checkout repository 2026-04-17T21:49:42.4641312Z hint: +Deploy Admin Checkout repository 2026-04-17T21:49:42.4641726Z hint: git config --global init.defaultBranch +Deploy Admin Checkout repository 2026-04-17T21:49:42.4642179Z hint: +Deploy Admin Checkout repository 2026-04-17T21:49:42.4642603Z hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and +Deploy Admin Checkout repository 2026-04-17T21:49:42.4643296Z hint: 'development'. The just-created branch can be renamed via this command: +Deploy Admin Checkout repository 2026-04-17T21:49:42.4643834Z hint: +Deploy Admin Checkout repository 2026-04-17T21:49:42.4644134Z hint: git branch -m +Deploy Admin Checkout repository 2026-04-17T21:49:42.4644473Z hint: +Deploy Admin Checkout repository 2026-04-17T21:49:42.4644932Z hint: Disable this message with "git config set advice.defaultBranchName false" +Deploy Admin Checkout repository 2026-04-17T21:49:42.4645824Z Initialized empty Git repository in /home/runner/work/elzatona_web/elzatona_web/.git/ +Deploy Admin Checkout repository 2026-04-17T21:49:42.4649534Z [command]/usr/bin/git remote add origin https://github.com/FoushWare/elzatona_web +Deploy Admin Checkout repository 2026-04-17T21:49:42.4674461Z ##[endgroup] +Deploy Admin Checkout repository 2026-04-17T21:49:42.4675063Z ##[group]Disabling automatic garbage collection +Deploy Admin Checkout repository 2026-04-17T21:49:42.4677852Z [command]/usr/bin/git config --local gc.auto 0 +Deploy Admin Checkout repository 2026-04-17T21:49:42.4701509Z ##[endgroup] +Deploy Admin Checkout repository 2026-04-17T21:49:42.4702064Z ##[group]Setting up auth +Deploy Admin Checkout repository 2026-04-17T21:49:42.4706624Z [command]/usr/bin/git config --local --name-only --get-regexp core\.sshCommand +Deploy Admin Checkout repository 2026-04-17T21:49:42.4729256Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'core\.sshCommand' && git config --local --unset-all 'core.sshCommand' || :" +Deploy Admin Checkout repository 2026-04-17T21:49:42.4978726Z [command]/usr/bin/git config --local --name-only --get-regexp http\.https\:\/\/github\.com\/\.extraheader +Deploy Admin Checkout repository 2026-04-17T21:49:42.5002821Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'http\.https\:\/\/github\.com\/\.extraheader' && git config --local --unset-all 'http.https://github.com/.extraheader' || :" +Deploy Admin Checkout repository 2026-04-17T21:49:42.5182110Z [command]/usr/bin/git config --local --name-only --get-regexp ^includeIf\.gitdir: +Deploy Admin Checkout repository 2026-04-17T21:49:42.5205656Z [command]/usr/bin/git submodule foreach --recursive git config --local --show-origin --name-only --get-regexp remote.origin.url +Deploy Admin Checkout repository 2026-04-17T21:49:42.5393859Z [command]/usr/bin/git config --local http.https://github.com/.extraheader AUTHORIZATION: basic *** +Deploy Admin Checkout repository 2026-04-17T21:49:42.5419826Z ##[endgroup] +Deploy Admin Checkout repository 2026-04-17T21:49:42.5420596Z ##[group]Fetching the repository +Deploy Admin Checkout repository 2026-04-17T21:49:42.5427568Z [command]/usr/bin/git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules --depth=1 origin +refs/heads/feat/optimize-ci-and-deploy-sync*:refs/remotes/origin/feat/optimize-ci-and-deploy-sync* +refs/tags/feat/optimize-ci-and-deploy-sync*:refs/tags/feat/optimize-ci-and-deploy-sync* +Deploy Admin Checkout repository 2026-04-17T21:49:45.3160847Z From https://github.com/FoushWare/elzatona_web +Deploy Admin Checkout repository 2026-04-17T21:49:45.3161764Z * [new branch] feat/optimize-ci-and-deploy-sync -> origin/feat/optimize-ci-and-deploy-sync +Deploy Admin Checkout repository 2026-04-17T21:49:45.3185167Z ##[endgroup] +Deploy Admin Checkout repository 2026-04-17T21:49:45.3185960Z ##[group]Determining the checkout info +Deploy Admin Checkout repository 2026-04-17T21:49:45.3191981Z [command]/usr/bin/git branch --list --remote origin/feat/optimize-ci-and-deploy-sync +Deploy Admin Checkout repository 2026-04-17T21:49:45.3211755Z origin/feat/optimize-ci-and-deploy-sync +Deploy Admin Checkout repository 2026-04-17T21:49:45.3215369Z ##[endgroup] +Deploy Admin Checkout repository 2026-04-17T21:49:45.3219364Z [command]/usr/bin/git sparse-checkout disable +Deploy Admin Checkout repository 2026-04-17T21:49:45.3746742Z [command]/usr/bin/git config --local --unset-all extensions.worktreeConfig +Deploy Admin Checkout repository 2026-04-17T21:49:45.3769738Z ##[group]Checking out the ref +Deploy Admin Checkout repository 2026-04-17T21:49:45.3773705Z [command]/usr/bin/git checkout --progress --force -B feat/optimize-ci-and-deploy-sync refs/remotes/origin/feat/optimize-ci-and-deploy-sync +Deploy Admin Checkout repository 2026-04-17T21:49:45.7538516Z Switched to a new branch 'feat/optimize-ci-and-deploy-sync' +Deploy Admin Checkout repository 2026-04-17T21:49:45.7539366Z branch 'feat/optimize-ci-and-deploy-sync' set up to track 'origin/feat/optimize-ci-and-deploy-sync'. +Deploy Admin Checkout repository 2026-04-17T21:49:45.7562655Z ##[endgroup] +Deploy Admin Checkout repository 2026-04-17T21:49:45.7598898Z [command]/usr/bin/git log -1 --format=%H +Deploy Admin Checkout repository 2026-04-17T21:49:45.7617639Z ceb93242f18483b5d1227a8efb9f8bd607a42fd4 +Deploy Admin Install pnpm 2026-04-17T21:49:45.7772976Z ##[group]Run pnpm/action-setup@v4 +Deploy Admin Install pnpm 2026-04-17T21:49:45.7827870Z with: +Deploy Admin Install pnpm 2026-04-17T21:49:45.7828084Z version: 10.33.0 +Deploy Admin Install pnpm 2026-04-17T21:49:45.7828261Z dest: ~/setup-pnpm +Deploy Admin Install pnpm 2026-04-17T21:49:45.7828444Z run_install: null +Deploy Admin Install pnpm 2026-04-17T21:49:45.7828626Z cache: false +Deploy Admin Install pnpm 2026-04-17T21:49:45.7828828Z cache_dependency_path: pnpm-lock.yaml +Deploy Admin Install pnpm 2026-04-17T21:49:45.7829089Z package_json_file: package.json +Deploy Admin Install pnpm 2026-04-17T21:49:45.7829316Z standalone: false +Deploy Admin Install pnpm 2026-04-17T21:49:45.7829490Z ##[endgroup] +Deploy Admin Install pnpm 2026-04-17T21:49:45.8909521Z ##[group]Running self-installer... +Deploy Admin Install pnpm 2026-04-17T21:49:46.4357716Z Progress: resolved 1, reused 0, downloaded 0, added 0 +Deploy Admin Install pnpm 2026-04-17T21:49:46.4443587Z Packages: +1 +Deploy Admin Install pnpm 2026-04-17T21:49:46.4444129Z + +Deploy Admin Install pnpm 2026-04-17T21:49:46.8629595Z Progress: resolved 1, reused 0, downloaded 1, added 1, done +Deploy Admin Install pnpm 2026-04-17T21:49:47.0262160Z +Deploy Admin Install pnpm 2026-04-17T21:49:47.0262649Z dependencies: +Deploy Admin Install pnpm 2026-04-17T21:49:47.0262909Z + pnpm 10.33.0 +Deploy Admin Install pnpm 2026-04-17T21:49:47.0263015Z +Deploy Admin Install pnpm 2026-04-17T21:49:47.0291355Z Done in 1s +Deploy Admin Install pnpm 2026-04-17T21:49:47.0438226Z ##[endgroup] +Deploy Admin Install pnpm 2026-04-17T21:49:47.0441259Z Installation Completed! +Deploy Admin Setup Node.js 2026-04-17T21:49:47.0566648Z ##[group]Run actions/setup-node@v4 +Deploy Admin Setup Node.js 2026-04-17T21:49:47.0566868Z with: +Deploy Admin Setup Node.js 2026-04-17T21:49:47.0567026Z node-version: 20 +Deploy Admin Setup Node.js 2026-04-17T21:49:47.0567191Z cache: pnpm +Deploy Admin Setup Node.js 2026-04-17T21:49:47.0567341Z always-auth: false +Deploy Admin Setup Node.js 2026-04-17T21:49:47.0567515Z check-latest: false +Deploy Admin Setup Node.js 2026-04-17T21:49:47.0567783Z token: *** +Deploy Admin Setup Node.js 2026-04-17T21:49:47.0567943Z env: +Deploy Admin Setup Node.js 2026-04-17T21:49:47.0568155Z PNPM_HOME: /home/runner/setup-pnpm/node_modules/.bin +Deploy Admin Setup Node.js 2026-04-17T21:49:47.0568413Z ##[endgroup] +Deploy Admin Setup Node.js 2026-04-17T21:49:47.3153221Z Found in cache @ /opt/hostedtoolcache/node/20.20.2/x64 +Deploy Admin Setup Node.js 2026-04-17T21:49:47.3159347Z ##[group]Environment details +Deploy Admin Setup Node.js 2026-04-17T21:49:47.9824514Z node: v20.20.2 +Deploy Admin Setup Node.js 2026-04-17T21:49:47.9824881Z npm: 10.8.2 +Deploy Admin Setup Node.js 2026-04-17T21:49:47.9825042Z yarn: 1.22.22 +Deploy Admin Setup Node.js 2026-04-17T21:49:47.9825952Z ##[endgroup] +Deploy Admin Setup Node.js 2026-04-17T21:49:47.9845294Z [command]/home/runner/setup-pnpm/node_modules/.bin/pnpm store path --silent +Deploy Admin Setup Node.js 2026-04-17T21:49:48.2512537Z /home/runner/setup-pnpm/node_modules/.bin/store/v10 +Deploy Admin Setup Node.js 2026-04-17T21:49:48.4660494Z Cache hit for: node-cache-Linux-x64-pnpm-c1fa4408a2f88a7a3398f75e9407cf077d6dc825f6d3648966b2ca686a6fa52f +Deploy Admin Setup Node.js 2026-04-17T21:49:49.6962787Z Received 33554432 of 436469304 (7.7%), 32.0 MBs/sec +Deploy Admin Setup Node.js 2026-04-17T21:49:50.7011151Z Received 192937984 of 436469304 (44.2%), 91.9 MBs/sec +Deploy Admin Setup Node.js 2026-04-17T21:49:51.6980388Z Received 377487360 of 436469304 (86.5%), 119.9 MBs/sec +Deploy Admin Setup Node.js 2026-04-17T21:49:52.3791755Z Received 436469304 of 436469304 (100.0%), 113.0 MBs/sec +Deploy Admin Setup Node.js 2026-04-17T21:49:52.3792562Z Cache Size: ~416 MB (436469304 B) +Deploy Admin Setup Node.js 2026-04-17T21:49:52.3820597Z [command]/usr/bin/tar -xf /home/runner/work/_temp/4753f2c7-5887-4950-a697-1e1831cb846d/cache.tzst -P -C /home/runner/work/elzatona_web/elzatona_web --use-compress-program unzstd +Deploy Admin Setup Node.js 2026-04-17T21:49:57.4183725Z Cache restored successfully +Deploy Admin Setup Node.js 2026-04-17T21:49:57.4359607Z Cache restored from key: node-cache-Linux-x64-pnpm-c1fa4408a2f88a7a3398f75e9407cf077d6dc825f6d3648966b2ca686a6fa52f +Deploy Admin Enable Corepack 2026-04-17T21:49:57.4576167Z ##[group]Run corepack enable +Deploy Admin Enable Corepack 2026-04-17T21:49:57.4576448Z corepack enable +Deploy Admin Enable Corepack 2026-04-17T21:49:57.4600917Z shell: /usr/bin/bash -e {0} +Deploy Admin Enable Corepack 2026-04-17T21:49:57.4601127Z env: +Deploy Admin Enable Corepack 2026-04-17T21:49:57.4601343Z PNPM_HOME: /home/runner/setup-pnpm/node_modules/.bin +Deploy Admin Enable Corepack 2026-04-17T21:49:57.4601595Z ##[endgroup] +Deploy Admin Install root dependencies 2026-04-17T21:49:57.5569423Z ##[group]Run pnpm install --frozen-lockfile +Deploy Admin Install root dependencies 2026-04-17T21:49:57.5569734Z pnpm install --frozen-lockfile +Deploy Admin Install root dependencies 2026-04-17T21:49:57.5588524Z shell: /usr/bin/bash -e {0} +Deploy Admin Install root dependencies 2026-04-17T21:49:57.5588730Z env: +Deploy Admin Install root dependencies 2026-04-17T21:49:57.5588938Z PNPM_HOME: /home/runner/setup-pnpm/node_modules/.bin +Deploy Admin Install root dependencies 2026-04-17T21:49:57.5589185Z ##[endgroup] +Deploy Admin Install root dependencies 2026-04-17T21:49:57.6308583Z ! Corepack is about to download https://registry.npmjs.org/pnpm/-/pnpm-10.33.0.tgz +Deploy Admin Install root dependencies 2026-04-17T21:49:58.8950414Z Scope: all 13 workspace projects +Deploy Admin Install root dependencies 2026-04-17T21:49:58.9895940Z Lockfile is up to date, resolution step is skipped +Deploy Admin Install root dependencies 2026-04-17T21:49:59.1134895Z Progress: resolved 1, reused 0, downloaded 0, added 0 +Deploy Admin Install root dependencies 2026-04-17T21:49:59.2622246Z Packages: +2431 +Deploy Admin Install root dependencies 2026-04-17T21:49:59.2622999Z ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +Deploy Admin Install root dependencies 2026-04-17T21:50:00.1144346Z Progress: resolved 2431, reused 360, downloaded 0, added 0 +Deploy Admin Install root dependencies 2026-04-17T21:50:01.1149684Z Progress: resolved 2431, reused 1085, downloaded 0, added 0 +Deploy Admin Install root dependencies 2026-04-17T21:50:02.1151068Z Progress: resolved 2431, reused 2282, downloaded 0, added 0 +Deploy Admin Install root dependencies 2026-04-17T21:50:03.1154314Z Progress: resolved 2431, reused 2417, downloaded 0, added 81 +Deploy Admin Install root dependencies 2026-04-17T21:50:04.1165302Z Progress: resolved 2431, reused 2417, downloaded 0, added 103 +Deploy Admin Install root dependencies 2026-04-17T21:50:05.1171370Z Progress: resolved 2431, reused 2417, downloaded 0, added 378 +Deploy Admin Install root dependencies 2026-04-17T21:50:06.1167251Z Progress: resolved 2431, reused 2417, downloaded 0, added 677 +Deploy Admin Install root dependencies 2026-04-17T21:50:07.1166003Z Progress: resolved 2431, reused 2417, downloaded 0, added 903 +Deploy Admin Install root dependencies 2026-04-17T21:50:08.1171558Z Progress: resolved 2431, reused 2417, downloaded 0, added 1043 +Deploy Admin Install root dependencies 2026-04-17T21:50:09.1177070Z Progress: resolved 2431, reused 2417, downloaded 0, added 1084 +Deploy Admin Install root dependencies 2026-04-17T21:50:10.1177952Z Progress: resolved 2431, reused 2417, downloaded 0, added 1202 +Deploy Admin Install root dependencies 2026-04-17T21:50:11.1183109Z Progress: resolved 2431, reused 2417, downloaded 0, added 1411 +Deploy Admin Install root dependencies 2026-04-17T21:50:12.1183918Z Progress: resolved 2431, reused 2417, downloaded 0, added 1861 +Deploy Admin Install root dependencies 2026-04-17T21:50:13.1187277Z Progress: resolved 2431, reused 2417, downloaded 0, added 1939 +Deploy Admin Install root dependencies 2026-04-17T21:50:14.2940851Z Progress: resolved 2431, reused 2417, downloaded 0, added 1940 +Deploy Admin Install root dependencies 2026-04-17T21:50:15.2234768Z Progress: resolved 2431, reused 2417, downloaded 0, added 2431, done +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3987436Z +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3988020Z dependencies: +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3988397Z + @daily-co/daily-js 0.84.0 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3988764Z + @heroicons/react 2.2.0 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3989104Z + @monaco-editor/react 4.7.0 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3989600Z + @radix-ui/react-checkbox 1.3.3 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3990022Z + @radix-ui/react-collapsible 1.1.12 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3990595Z + @radix-ui/react-dialog 1.1.15 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3990951Z + @radix-ui/react-label 2.1.8 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3991399Z + @radix-ui/react-popover 1.1.15 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3991784Z + @radix-ui/react-progress 1.1.8 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3992167Z + @radix-ui/react-scroll-area 1.2.10 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3992565Z + @radix-ui/react-select 2.2.6 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3992950Z + @radix-ui/react-separator 1.1.8 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3993329Z + @radix-ui/react-slot 1.2.4 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3993683Z + @radix-ui/react-switch 1.2.6 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3994020Z + @radix-ui/react-tabs 1.1.13 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3994322Z + @reactour/tour 3.8.0 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3994596Z + @sentry/nextjs 10.46.0 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3994939Z + @supabase/supabase-js 2.100.1 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3995311Z + @tanstack/react-query 5.95.2 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3995729Z + @tanstack/react-query-devtools 5.95.2 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3996149Z + @types/bcryptjs 2.4.6 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3996465Z + @types/dompurify 3.2.0 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3996778Z + @types/jsonwebtoken 9.0.10 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3997176Z + @types/react-syntax-highlighter 15.5.13 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3997576Z + @types/swagger-jsdoc 6.0.4 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3997952Z + @types/swagger-ui-react 5.18.0 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3998307Z + @vercel/speed-insights 1.3.1 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3998614Z + axios 1.15.0 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3998861Z + bcryptjs 3.0.3 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3999648Z + cheerio 1.2.0 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.3999984Z + class-variance-authority 0.7.1 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4000529Z + cloudinary 2.9.0 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4000825Z + clsx 2.1.1 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4001081Z + critters 0.0.23 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4001249Z + date-fns 4.1.0 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4001424Z + dompurify 3.4.0 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4001587Z + dotenv 17.3.1 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4001852Z + firebase 12.11.0 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4002054Z + html-to-image 1.11.13 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4002524Z + jotai 2.19.0 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4002741Z + jsonwebtoken 9.0.3 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4003000Z + lucide-react 0.542.0 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4003172Z + next 16.2.3 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4003346Z + next-auth 4.24.13 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4003520Z + node-cron 4.2.1 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4003697Z + node-fetch 2.7.0 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4003862Z + nuqs 2.8.9 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4004018Z + react 19.2.4 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4004290Z + react-day-picker 9.14.0 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4004578Z + react-dom 19.2.4 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4004783Z + react-markdown 10.1.0 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4005019Z + react-resizable-panels 2.1.9 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4005408Z + react-syntax-highlighter 16.1.1 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4005736Z + shiki 3.23.0 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4005989Z + sonner 2.0.7 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4006193Z + swagger-jsdoc 6.2.8 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4006386Z + swagger-ui-react 5.32.1 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4006581Z + tailwind-merge 3.5.0 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4006776Z + tailwindcss-animate 1.0.7 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4006985Z + validator 13.15.26 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4007152Z + xss 1.0.15 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4007311Z + zod 4.3.6 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4007407Z +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4007489Z devDependencies: +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4007677Z + @eslint/eslintrc 3.3.5 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4007971Z + @mermaid-js/mermaid-cli 11.12.0 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4008227Z + @nx/eslint 22.6.3 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4008397Z + @nx/jest 22.6.3 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4008564Z + @nx/next 22.6.3 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4008728Z + @nx/vite 22.6.3 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4008902Z + @playwright/test 1.58.2 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4009182Z + @swc-node/register 1.11.1 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4009477Z + @swc/core 1.15.21 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4009753Z + @testing-library/jest-dom 6.9.1 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4010090Z + @testing-library/react 16.3.2 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4010731Z + @testing-library/user-event 14.6.1 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4011062Z + @types/jest 30.0.0 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4011330Z + @types/node 20.19.37 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4011599Z + @types/react 19.2.14 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4011883Z + @types/react-dom 19.2.3 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4012198Z + @vitest/coverage-v8 3.2.4 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4012498Z + autoprefixer 10.4.27 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4012803Z + dotenv-cli 11.0.0 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4013001Z + eslint 9.39.4 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4013186Z + eslint-config-next 15.5.1 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4013380Z + glob 11.1.0 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4013536Z + husky 9.1.7 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4013775Z + jest 30.3.0 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4014052Z + jest-environment-jsdom 30.3.0 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4014346Z + js-yaml 3.14.2 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4014559Z + lint-staged 16.4.0 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4014756Z + minimatch 10.2.4 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4014917Z + msw 2.12.14 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4015080Z + null-loader 4.0.1 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4015242Z + nx 22.6.3 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4015404Z + oe-sonar-mcp 1.0.4 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4015571Z + prettier 3.8.1 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4015743Z + puppeteer 23.11.1 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4015907Z + semver 7.7.4 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4016083Z + tailwindcss 3.4.19 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4016245Z + ts-jest 29.4.6 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4016406Z + tsx 4.21.0 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4016602Z + typescript 5.9.3 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4016767Z + vercel 50.44.0 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4016968Z + vite 7.3.2 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4017146Z + vite-tsconfig-paths 6.1.1 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4017352Z + vitest 3.2.4 +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4017448Z +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4380704Z . prepare$ husky +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4799410Z . prepare: Done +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4925212Z ╭ Warning ─────────────────────────────────────────────────────────────────────╮ +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4926208Z │ │ +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4927435Z │ Ignored build scripts: @firebase/util@1.15.0, @parcel/watcher@2.5.6, │ +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4928354Z │ @scarf/scarf@1.4.0, @sentry/cli@2.58.5, @swc/core@1.15.21, │ +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4929220Z │ @tree-sitter-grammars/tree-sitter-yaml@0.7.1, core-js-pure@3.49.0, │ +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4930128Z │ esbuild@0.27.0, esbuild@0.27.4, esbuild@0.27.7, less@4.5.1, msw@2.12.14, │ +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4931465Z │ nx@22.6.3, protobufjs@7.5.4, puppeteer@23.11.1, sharp@0.34.5, │ +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4932338Z │ tree-sitter-json@0.24.8, tree-sitter@0.21.1, tree-sitter@0.22.4, │ +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4933186Z │ unrs-resolver@1.11.1. │ +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4934052Z │ Run "pnpm approve-builds" to pick which dependencies should be allowed │ +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4934962Z │ to run scripts. │ +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4935501Z │ │ +Deploy Admin Install root dependencies 2026-04-17T21:50:16.4935982Z ╰──────────────────────────────────────────────────────────────────────────────╯ +Deploy Admin Install root dependencies 2026-04-17T21:50:16.5128324Z Done in 17.8s using pnpm v10.33.0 +Deploy Admin Setup Nx and Vercel Caching 2026-04-17T21:50:16.5422878Z ##[group]Run actions/cache@v4 +Deploy Admin Setup Nx and Vercel Caching 2026-04-17T21:50:16.5423103Z with: +Deploy Admin Setup Nx and Vercel Caching 2026-04-17T21:50:16.5423278Z path: .nx/cache +Deploy Admin Setup Nx and Vercel Caching apps/admin/.next/cache +Deploy Admin Setup Nx and Vercel Caching +Deploy Admin Setup Nx and Vercel Caching 2026-04-17T21:50:16.5423605Z key: Linux-build-cache-ceb93242f18483b5d1227a8efb9f8bd607a42fd4 +Deploy Admin Setup Nx and Vercel Caching 2026-04-17T21:50:16.5423904Z restore-keys: Linux-build-cache- +Deploy Admin Setup Nx and Vercel Caching +Deploy Admin Setup Nx and Vercel Caching 2026-04-17T21:50:16.5424129Z enableCrossOsArchive: false +Deploy Admin Setup Nx and Vercel Caching 2026-04-17T21:50:16.5424333Z fail-on-cache-miss: false +Deploy Admin Setup Nx and Vercel Caching 2026-04-17T21:50:16.5424521Z lookup-only: false +Deploy Admin Setup Nx and Vercel Caching 2026-04-17T21:50:16.5424691Z save-always: false +Deploy Admin Setup Nx and Vercel Caching 2026-04-17T21:50:16.5424849Z env: +Deploy Admin Setup Nx and Vercel Caching 2026-04-17T21:50:16.5425047Z PNPM_HOME: /home/runner/setup-pnpm/node_modules/.bin +Deploy Admin Setup Nx and Vercel Caching 2026-04-17T21:50:16.5425294Z ##[endgroup] +Deploy Admin Setup Nx and Vercel Caching 2026-04-17T21:50:16.8307544Z Cache not found for input keys: Linux-build-cache-ceb93242f18483b5d1227a8efb9f8bd607a42fd4, Linux-build-cache- +Deploy Admin Validate Vercel token 2026-04-17T21:50:16.8371819Z ##[group]Run if [ -z "***" ]; then +Deploy Admin Validate Vercel token 2026-04-17T21:50:16.8372147Z if [ -z "***" ]; then +Deploy Admin Validate Vercel token 2026-04-17T21:50:16.8372394Z  echo "::error::Missing VERCEL_TOKEN secret" +Deploy Admin Validate Vercel token 2026-04-17T21:50:16.8372638Z  exit 1 +Deploy Admin Validate Vercel token 2026-04-17T21:50:16.8372803Z fi +Deploy Admin Validate Vercel token 2026-04-17T21:50:16.8372995Z echo "VERCEL_TOKEN is configured" +Deploy Admin Validate Vercel token 2026-04-17T21:50:16.8391384Z shell: /usr/bin/bash -e {0} +Deploy Admin Validate Vercel token 2026-04-17T21:50:16.8391582Z env: +Deploy Admin Validate Vercel token 2026-04-17T21:50:16.8391791Z PNPM_HOME: /home/runner/setup-pnpm/node_modules/.bin +Deploy Admin Validate Vercel token 2026-04-17T21:50:16.8392043Z ##[endgroup] +Deploy Admin Validate Vercel token 2026-04-17T21:50:16.8429593Z VERCEL_TOKEN is configured +Deploy Admin Configure admin Vercel project settings 2026-04-17T21:50:16.8449104Z ##[group]Run curl -fsS -X PATCH \ +Deploy Admin Configure admin Vercel project settings 2026-04-17T21:50:16.8449352Z curl -fsS -X PATCH \ +Deploy Admin Configure admin Vercel project settings 2026-04-17T21:50:16.8449788Z  -H "Authorization: ***" \ +Deploy Admin Configure admin Vercel project settings 2026-04-17T21:50:16.8450045Z  -H "Content-Type: application/json" \ +Deploy Admin Configure admin Vercel project settings 2026-04-17T21:50:16.8450619Z  -d '{"buildCommand":"pnpm --filter admin build","outputDirectory":"apps/admin/.next"}' \ +Deploy Admin Configure admin Vercel project settings 2026-04-17T21:50:16.8451100Z  "https://api.vercel.com/v9/projects/***" +Deploy Admin Configure admin Vercel project settings 2026-04-17T21:50:16.8467187Z shell: /usr/bin/bash -e {0} +Deploy Admin Configure admin Vercel project settings 2026-04-17T21:50:16.8467395Z env: +Deploy Admin Configure admin Vercel project settings 2026-04-17T21:50:16.8467597Z PNPM_HOME: /home/runner/setup-pnpm/node_modules/.bin +Deploy Admin Configure admin Vercel project settings 2026-04-17T21:50:16.8467847Z ##[endgroup] +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:17.2953978Z ##[group]Run npx vercel pull --yes --environment=production --token=*** +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:17.2954473Z npx vercel pull --yes --environment=production --token=*** +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:17.2954844Z npx vercel build --prod --token=*** +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:17.2973268Z shell: /usr/bin/bash -e {0} +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:17.2973479Z env: +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:17.2973693Z PNPM_HOME: /home/runner/setup-pnpm/node_modules/.bin +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:17.2974007Z VERCEL_ORG_ID: *** +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:17.2974249Z VERCEL_PROJECT_ID: *** +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:17.2974447Z NEXT_PUBLIC_APP_ENV: production +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:17.2974660Z NEXT_TELEMETRY_DISABLED: 1 +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:17.2974958Z NEXT_PUBLIC_SUPABASE_URL: *** +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:17.2976020Z NEXT_PUBLIC_SUPABASE_ANON_KEY: *** +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:17.2976253Z ##[endgroup] +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:18.3366033Z > NOTE: The Vercel CLI now collects telemetry regarding usage of the CLI. +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:18.3366846Z > This information is used to shape the CLI roadmap and prioritize features. +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:18.3367614Z > You can learn more, including how to opt-out if you'd not like to participate in this program, by visiting the following URL: +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:18.3368131Z > https://vercel.com/docs/cli/about-telemetry +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:18.5262325Z Retrieving project… +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:18.6645830Z > Downloading `production` Environment Variables for foushwares-projects/elzatona-admin +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:18.6646486Z Downloading +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:18.7739457Z Created .vercel/.env.production.local file [109ms] +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:18.7739852Z +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:18.7740030Z > Downloading project settings +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:18.7744116Z Downloaded project settings to ~/work/elzatona_web/elzatona_web/.vercel/project.json [0ms] +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:19.5798815Z WARNING! Build not running on Vercel. System environment variables will not be available. +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:19.8059668Z Warning: Detected "engines": { "node": ">=20.0.0" } in your `package.json` that will automatically upgrade when a new major Node.js Version is released. Learn More: https://vercel.link/node-version +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:19.8843823Z Detected `pnpm-lock.yaml` version 9 generated by pnpm@10.x with package.json#packageManager pnpm@10.33.0 +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:19.8876415Z Running "install" command: `npm install`... +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:28.6678258Z npm error Cannot read properties of null (reading 'package') +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:28.6679315Z npm error A complete log of this run can be found in: /home/runner/.npm/_logs/2026-04-17T21_50_19_938Z-debug-0.log +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:28.6877573Z Error: Command "npm install" exited with 1 +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:28.9340059Z ╭──────────────────────────────────────────────────────────────────────────────╮ +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:28.9341461Z │ │ +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:28.9342235Z │ Update available! v50.44.0 ≫ v51.6.1 │ +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:28.9343060Z │ Changelog: https://github.com/vercel/vercel/releases/tag/vercel%4051.6.1 │ +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:28.9343973Z │ Run `pnpm i vercel@latest` to update. │ +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:28.9344724Z │ │ +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:28.9345480Z │ The latest update may fix any errors that occurred. │ +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:28.9346191Z │ │ +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:28.9346921Z ╰──────────────────────────────────────────────────────────────────────────────╯ +Deploy Admin Validate and build (Local Vercel Build) 2026-04-17T21:50:28.9570448Z ##[error]Process completed with exit code 1. +Deploy Admin Post Install pnpm 2026-04-17T21:50:28.9829362Z Post job cleanup. +Deploy Admin Post Install pnpm 2026-04-17T21:50:29.1060031Z Pruning is unnecessary. +Deploy Admin Post Checkout repository 2026-04-17T21:50:29.1255154Z Post job cleanup. +Deploy Admin Post Checkout repository 2026-04-17T21:50:29.2100861Z [command]/usr/bin/git version +Deploy Admin Post Checkout repository 2026-04-17T21:50:29.2129638Z git version 2.53.0 +Deploy Admin Post Checkout repository 2026-04-17T21:50:29.2169350Z Temporarily overriding HOME='/home/runner/work/_temp/f09a2c0b-7fea-43c6-acc3-2541c6adc2e6' before making global git config changes +Deploy Admin Post Checkout repository 2026-04-17T21:50:29.2170822Z Adding repository directory to the temporary git global config as a safe directory +Deploy Admin Post Checkout repository 2026-04-17T21:50:29.2174495Z [command]/usr/bin/git config --global --add safe.directory /home/runner/work/elzatona_web/elzatona_web +Deploy Admin Post Checkout repository 2026-04-17T21:50:29.2367036Z [command]/usr/bin/git config --local --name-only --get-regexp core\.sshCommand +Deploy Admin Post Checkout repository 2026-04-17T21:50:29.2400726Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'core\.sshCommand' && git config --local --unset-all 'core.sshCommand' || :" +Deploy Admin Post Checkout repository 2026-04-17T21:50:29.2667384Z [command]/usr/bin/git config --local --name-only --get-regexp http\.https\:\/\/github\.com\/\.extraheader +Deploy Admin Post Checkout repository 2026-04-17T21:50:29.2690740Z http.https://github.com/.extraheader +Deploy Admin Post Checkout repository 2026-04-17T21:50:29.2702196Z [command]/usr/bin/git config --local --unset-all http.https://github.com/.extraheader +Deploy Admin Post Checkout repository 2026-04-17T21:50:29.2949472Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'http\.https\:\/\/github\.com\/\.extraheader' && git config --local --unset-all 'http.https://github.com/.extraheader' || :" +Deploy Admin Post Checkout repository 2026-04-17T21:50:29.3161130Z [command]/usr/bin/git config --local --name-only --get-regexp ^includeIf\.gitdir: +Deploy Admin Post Checkout repository 2026-04-17T21:50:29.3186091Z [command]/usr/bin/git submodule foreach --recursive git config --local --show-origin --name-only --get-regexp remote.origin.url +Deploy Admin Complete job 2026-04-17T21:50:29.3532055Z Cleaning up orphan processes +Deploy Admin Complete job 2026-04-17T21:50:29.3865757Z Terminate orphan process: pid (2261) (node) +Deploy Admin Complete job 2026-04-17T21:50:29.3892557Z Terminate orphan process: pid (2324) (node) +Deploy Admin Complete job 2026-04-17T21:50:29.3901920Z ##[warning]Node.js 20 actions are deprecated. The following actions are running on Node.js 20 and may not work as expected: actions/cache@v4, actions/checkout@v4, actions/setup-node@v4, pnpm/action-setup@v4. Actions will be forced to run with Node.js 24 by default starting June 2nd, 2026. Node.js 20 will be removed from the runner on September 16th, 2026. Please check if updated versions of these actions are available that support Node.js 24. To opt into Node.js 24 now, set the FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true environment variable on the runner or in your workflow file. Once Node.js 24 becomes the default, you can temporarily opt out by setting ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/ diff --git a/final_debug.txt b/final_debug.txt new file mode 100644 index 000000000..efb5597ec --- /dev/null +++ b/final_debug.txt @@ -0,0 +1,61 @@ + + RUN v3.2.4 /Users/a.fouad/S/New_elzatona + + ❯ libs/utilities/src/lib/auth-config.test.ts (9 tests | 3 failed) 5ms + ✓ authOptions > authorize > should find the authorize function 1ms + ✓ authOptions > authorize > should return null if credentials are missing 0ms + ✓ authOptions > authorize > should return null if email or password missing 0ms + × authOptions > authorize > should return developer user for valid dev credentials 1ms + → expected null not to be null + × authOptions > authorize > should return admin user for valid admin credentials 0ms + → expected null not to be null + × authOptions > authorize > should return guest user for valid guest credentials 0ms + → expected null not to be null + ✓ authOptions > authorize > should return null for invalid credentials 0ms + ✓ authOptions > callbacks > jwt callback should append user and account info 0ms + ✓ authOptions > callbacks > session callback should append token info to session user 0ms + +⎯⎯⎯⎯⎯⎯⎯ Failed Tests 3 ⎯⎯⎯⎯⎯⎯⎯ + + FAIL libs/utilities/src/lib/auth-config.test.ts > authOptions > authorize > should return developer user for valid dev credentials +AssertionError: expected null not to be null + ❯ libs/utilities/src/lib/auth-config.test.ts:31:26 + 29| password: "dev-access", + 30| }); + 31| expect(result).not.toBeNull(); + | ^ + 32| expect(result.id).toBe("dev-user-id"); + 33| expect(result.role).toBe("developer"); + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/3]⎯ + + FAIL libs/utilities/src/lib/auth-config.test.ts > authOptions > authorize > should return admin user for valid admin credentials +AssertionError: expected null not to be null + ❯ libs/utilities/src/lib/auth-config.test.ts:41:26 + 39| password: "admin-pass", + 40| }); + 41| expect(result).not.toBeNull(); + | ^ + 42| expect(result.role).toBe("admin"); + 43| }); + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/3]⎯ + + FAIL libs/utilities/src/lib/auth-config.test.ts > authOptions > authorize > should return guest user for valid guest credentials +AssertionError: expected null not to be null + ❯ libs/utilities/src/lib/auth-config.test.ts:50:26 + 48| password: "guest-pass", + 49| }); + 50| expect(result).not.toBeNull(); + | ^ + 51| expect(result.role).toBe("guest"); + 52| }); + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/3]⎯ + + + Test Files 1 failed (1) + Tests 3 failed | 6 passed (9) + Start at 21:04:44 + Duration 975ms (transform 44ms, setup 171ms, collect 19ms, tests 5ms, environment 315ms, prepare 50ms) + diff --git a/libs/common-ui/src/admin/content-management/LearningCardsManager.tsx b/libs/common-ui/src/admin/content-management/LearningCardsManager.tsx index 8dd50d82d..ced060158 100644 --- a/libs/common-ui/src/admin/content-management/LearningCardsManager.tsx +++ b/libs/common-ui/src/admin/content-management/LearningCardsManager.tsx @@ -118,7 +118,7 @@ const TopicNode: React.FC<{ id={`topic-${topic.id}`} className="border-l-2 border-gray-100 pl-4 py-2" > -
+
{/* Left section: expandable content with description */} diff --git a/libs/common-ui/src/admin/content-management/modals/QuestionFormModal.tsx b/libs/common-ui/src/admin/content-management/modals/QuestionFormModal.tsx index 1b125697f..8069f2648 100644 --- a/libs/common-ui/src/admin/content-management/modals/QuestionFormModal.tsx +++ b/libs/common-ui/src/admin/content-management/modals/QuestionFormModal.tsx @@ -36,8 +36,10 @@ export const QuestionFormModal: React.FC = ({ readOnly = false, isLoading = false, }) => { - const initialData = - question || (topicId ? ({ topic_id: topicId } as any) : undefined); + let initialData = question || undefined; + if (!initialData && topicId) { + initialData = { topic_id: topicId } as any; + } let modalTitle = "Create New Question"; if (readOnly) { modalTitle = "View Question"; diff --git a/libs/common-ui/src/admin/content-management/modals/TopicFormModal.tsx b/libs/common-ui/src/admin/content-management/modals/TopicFormModal.tsx index 3f69ffb7b..f74032154 100644 --- a/libs/common-ui/src/admin/content-management/modals/TopicFormModal.tsx +++ b/libs/common-ui/src/admin/content-management/modals/TopicFormModal.tsx @@ -11,11 +11,6 @@ import { Button, Input, Textarea, - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, } from "../../../components/ui"; import { BookOpen, Search, Plus, Check, ChevronsUpDown } from "lucide-react"; import { @@ -94,6 +89,13 @@ export const TopicFormModal: React.FC = ({ onOpenChange(false); }; + let submitButtonText = "Create Topic"; + if (isSubmitting) { + submitButtonText = "Saving..."; + } else if (topic) { + submitButtonText = "Save Changes"; + } + return ( @@ -181,13 +183,15 @@ export const TopicFormModal: React.FC = ({ .includes(categorySearch.toLowerCase()), ) .map((category) => ( -
{ setCategoryId(category.id); setIsCategoryPopoverOpen(false); @@ -199,7 +203,7 @@ export const TopicFormModal: React.FC = ({ {categoryId === category.id && ( )} -
+ ))} {categorySearch.trim() && @@ -207,8 +211,9 @@ export const TopicFormModal: React.FC = ({ (c) => c.name.toLowerCase() === categorySearch.toLowerCase(), ) && ( -
{ if (onCreateCategory) { await onCreateCategory(categorySearch.trim()); @@ -222,7 +227,7 @@ export const TopicFormModal: React.FC = ({ > Create "{categorySearch}" -
+ )} {categories.length === 0 && !categorySearch && ( @@ -268,11 +273,7 @@ export const TopicFormModal: React.FC = ({ disabled={isSubmitting} className="bg-purple-600 hover:bg-purple-700 text-white" > - {isSubmitting - ? "Saving..." - : topic - ? "Save Changes" - : "Create Topic"} + {submitButtonText} diff --git a/libs/common-ui/src/admin/content-management/modals/TopicQuestionsModal.tsx b/libs/common-ui/src/admin/content-management/modals/TopicQuestionsModal.tsx index bb77e5033..635f3bcf8 100644 --- a/libs/common-ui/src/admin/content-management/modals/TopicQuestionsModal.tsx +++ b/libs/common-ui/src/admin/content-management/modals/TopicQuestionsModal.tsx @@ -46,6 +46,17 @@ export const TopicQuestionsModal: React.FC = ({ ? questions.filter((q) => q.topic_id === topic.id) : []; + const getDifficultyColor = (difficulty: string) => { + switch (difficulty) { + case "beginner": + return "bg-green-50 text-green-700 dark:bg-green-900 dark:text-green-300"; + case "intermediate": + return "bg-yellow-50 text-yellow-700 dark:bg-yellow-900 dark:text-yellow-300"; + default: + return "bg-red-50 text-red-700 dark:bg-red-900 dark:text-red-300"; + } + }; + return ( = ({
{question.difficulty} diff --git a/libs/common-ui/src/admin/editors/FrontendTaskEditor.tsx b/libs/common-ui/src/admin/editors/FrontendTaskEditor.tsx index 3faa7d971..e983a86b9 100644 --- a/libs/common-ui/src/admin/editors/FrontendTaskEditor.tsx +++ b/libs/common-ui/src/admin/editors/FrontendTaskEditor.tsx @@ -128,13 +128,10 @@ export default function FrontendTaskEditor({ handleMouseDown, showFileExplorer, setShowFileExplorer, - consoleOutput, - setConsoleOutput, showPreview, activeTab, setActiveTab, activeBrowserTab, - setActiveBrowserTab, } = editorState; // Simple form handlers @@ -169,11 +166,6 @@ export default function FrontendTaskEditor({ mode={mode} showFileExplorer={showFileExplorer} setShowFileExplorer={setShowFileExplorer} - showPreview={showPreview} - activeBrowserTab={activeBrowserTab} - setActiveBrowserTab={setActiveBrowserTab} - consoleOutput={consoleOutput} - setConsoleOutput={setConsoleOutput} />
); diff --git a/libs/common-ui/src/admin/editors/FrontendTaskEditorComponents.tsx b/libs/common-ui/src/admin/editors/FrontendTaskEditorComponents.tsx index eed0176b2..13544a02d 100644 --- a/libs/common-ui/src/admin/editors/FrontendTaskEditorComponents.tsx +++ b/libs/common-ui/src/admin/editors/FrontendTaskEditorComponents.tsx @@ -1,5 +1,6 @@ import React from "react"; -import { ArrowLeft, Sun, Moon, Monitor, Save, Code } from "lucide-react"; +import { ArrowLeft, Save, Code } from "lucide-react"; +import { ThemeToggle, TabHeader, InputGroup } from "./SharedEditorComponents"; interface HeaderProps { isDark: boolean; @@ -21,107 +22,55 @@ export const FrontendTaskEditorHeader: React.FC = ({ onCancel, }) => (

{mode === "create" - ? "Create Frontend Task" + ? "Create Task" : mode === "edit" - ? "Edit Frontend Task" - : "View Frontend Task"} + ? "Edit Task" + : "View Task"}

- React - - {formData.difficulty} - + React + + {formData.difficulty}
- {/* Theme Toggle */} -
- - - -
+ {mode !== "view" && ( )} @@ -142,173 +91,82 @@ interface MainContentProps { mode: "create" | "edit" | "view"; showFileExplorer: boolean; setShowFileExplorer: (show: boolean) => void; - showPreview: boolean; - activeBrowserTab: "browser" | "console"; - setActiveBrowserTab: (tab: "browser" | "console") => void; - consoleOutput: string[]; - setConsoleOutput: (output: string[]) => void; } -export const FrontendTaskEditorMainContent: React.FC = ({ - isDark, - leftPanelWidth, - rightPanelWidth, - handleMouseDown, - activeTab, - setActiveTab, - formData, - setFormData, - mode, - showFileExplorer, - setShowFileExplorer, - showPreview, - activeBrowserTab, - setActiveBrowserTab, - consoleOutput, - setConsoleOutput, -}) => ( -
- {/* Left Panel - Description */} -
- {/* Resize Handle */} -
handleMouseDown(e, "left")} - >
+export const FrontendTaskEditorMainContent: React.FC = ( + props, +) => { + const { + isDark, + leftPanelWidth, + rightPanelWidth, + handleMouseDown, + activeTab, + setActiveTab, + formData, + setFormData, + mode, + showFileExplorer, + setShowFileExplorer, + } = props; - {/* Tabs */} + return ( +
- - -
- - {/* Content */} -
- {activeTab === "description" && ( -
- {/* Task Metadata - Editable */} - {mode !== "view" && ( -
-
- - - setFormData((prev: any) => ({ - ...prev, - title: e.target.value, - })) - } - className={`w-full px-3 py-2 rounded-lg border transition-colors ${ - isDark - ? "bg-gray-700 border-gray-600 text-white" - : "bg-white border-gray-300 text-gray-900" - }`} - placeholder="Enter task title" - /> -
-
- )} -
- )} + +
+ {activeTab === "description" && mode !== "view" && ( + setFormData({ ...formData, title: v })} + isDark={isDark} + /> + )} +
+
handleMouseDown(e, "left")} + />
-
- {/* Middle Panel - Editor */} -
- {/* Editor Header */}
-
+
-
- - {/* Right Panel - Preview */} -
- {/* Resize Handle */} -
handleMouseDown(e, "right")} - >
- {/* Preview Header */}
-
- Eye - - Preview - +
handleMouseDown(e, "right")} + /> +
+ Preview
-
-); + ); +}; diff --git a/libs/common-ui/src/admin/editors/FrontendTaskEditorHooks.ts b/libs/common-ui/src/admin/editors/FrontendTaskEditorHooks.ts index 314382f79..9acb24e21 100644 --- a/libs/common-ui/src/admin/editors/FrontendTaskEditorHooks.ts +++ b/libs/common-ui/src/admin/editors/FrontendTaskEditorHooks.ts @@ -13,30 +13,11 @@ interface FileNode { isEntryPoint?: boolean; } -// Hook for theme management -export const useThemeManagement = () => { - const [theme, setTheme] = useState<"light" | "dark" | "system">("system"); - const [isDark, setIsDark] = useState(false); - - useEffect(() => { - const mediaQuery = globalThis.window.matchMedia( - "(prefers-color-scheme: dark)", - ); - const updateTheme = () => { - if (theme === "system") { - setIsDark(mediaQuery.matches); - } else { - setIsDark(theme === "dark"); - } - }; - - updateTheme(); - mediaQuery.addEventListener("change", updateTheme); - return () => mediaQuery.removeEventListener("change", updateTheme); - }, [theme]); - - return { theme, setTheme, isDark }; -}; +export { + useThemeManagement, + usePanelLayout, + useConsoleManagement, +} from "./SharedEditorHooks"; // Hook for form data management export const useFormDataManagement = (task?: AdminFrontendTask | null) => { @@ -79,74 +60,6 @@ export const useFormDataManagement = (task?: AdminFrontendTask | null) => { return { formData, setFormData }; }; -// Hook for panel layout management -export const usePanelLayout = () => { - const [leftPanelWidth, setLeftPanelWidth] = useState(25); - const [rightPanelWidth, setRightPanelWidth] = useState(25); - const [isResizing, setIsResizing] = useState(false); - const [resizeStartX, setResizeStartX] = useState(0); - - const handleMouseDown = (e: React.MouseEvent, _panel: "left" | "right") => { - e.preventDefault(); - setIsResizing(true); - setResizeStartX(e.clientX); - }; - - const handleMouseMove = useCallback( - (e: MouseEvent) => { - if (!isResizing) return; - - const deltaX = e.clientX - resizeStartX; - const containerWidth = window.innerWidth; - const deltaPercent = (deltaX / containerWidth) * 100; - - if (deltaPercent !== 0) { - setLeftPanelWidth((prev) => - Math.max(20, Math.min(60, prev + deltaPercent)), - ); - setRightPanelWidth((prev) => - Math.max(20, Math.min(60, prev - deltaPercent)), - ); - setResizeStartX(e.clientX); - } - }, - [isResizing, resizeStartX], - ); - - const handleMouseUp = useCallback(() => { - setIsResizing(false); - }, []); - - useEffect(() => { - if (isResizing) { - document.addEventListener("mousemove", handleMouseMove); - document.addEventListener("mouseup", handleMouseUp); - document.body.style.cursor = "col-resize"; - document.body.style.userSelect = "none"; - } else { - document.removeEventListener("mousemove", handleMouseMove); - document.removeEventListener("mouseup", handleMouseUp); - document.body.style.cursor = ""; - document.body.style.userSelect = ""; - } - - return () => { - document.removeEventListener("mousemove", handleMouseMove); - document.removeEventListener("mouseup", handleMouseUp); - document.body.style.cursor = ""; - document.body.style.userSelect = ""; - }; - }, [isResizing, resizeStartX, handleMouseMove, handleMouseUp]); - - return { - leftPanelWidth, - rightPanelWidth, - setLeftPanelWidth, - setRightPanelWidth, - handleMouseDown, - }; -}; - // Helper function to create file tree structure const createFileTree = (files: Array) => { return [ @@ -312,27 +225,3 @@ export const useFileManagement = (task?: AdminFrontendTask | null) => { setShowFileExplorer, }; }; - -// Hook for console management -export const useConsoleManagement = () => { - const [consoleOutput, setConsoleOutput] = useState([]); - const [showConsole] = useState(true); - - useEffect(() => { - const handleMessage = (event: MessageEvent) => { - // SECURITY: Verify message origin to prevent XSS - if (event.origin !== globalThis.window.location.origin) { - console.warn("Received message from untrusted origin:", event.origin); - return; - } - if (event.data.type === "console") { - setConsoleOutput((prev) => [...prev.slice(-19), event.data.message]); - } - }; - - window.addEventListener("message", handleMessage); - return () => window.removeEventListener("message", handleMessage); - }, []); - - return { consoleOutput, setConsoleOutput, showConsole }; -}; diff --git a/libs/common-ui/src/admin/editors/ProblemSolvingEditor.tsx b/libs/common-ui/src/admin/editors/ProblemSolvingEditor.tsx index d0ac119db..5a4e17033 100644 --- a/libs/common-ui/src/admin/editors/ProblemSolvingEditor.tsx +++ b/libs/common-ui/src/admin/editors/ProblemSolvingEditor.tsx @@ -113,17 +113,12 @@ export default function ProblemSolvingEditor({ setSolutionCode, activeTab, setActiveTab, - fileExplorerState, dynamicFieldsState, leftPanelWidth, rightPanelWidth, handleMouseDown, copied, setCopied, - showPreview, - setShowPreview, - activeBrowserTab, - setActiveBrowserTab, } = editorState; // Simple form handlers @@ -241,12 +236,6 @@ export default function ProblemSolvingEditor({ setActiveTab={(tab) => setActiveTab(tab as "starter" | "solution")} formData={formData} setFormData={setFormData} - starterCode={starterCode} - setStarterCode={setStarterCode} - solutionCode={solutionCode} - setSolutionCode={setSolutionCode} - showFileExplorer={fileExplorerState.showFileExplorer} - setShowFileExplorer={fileExplorerState.setShowFileExplorer} >
= ({ onCancel, }) => (
PS

{isEditing ? "Edit Problem" : "Create Problem"}

{formData.category} @@ -68,46 +60,12 @@ export const ProblemSolvingEditorHeader: React.FC = ({
- {/* Theme Toggle */} -
- - - -
- +
@@ -123,12 +81,6 @@ interface MainContentProps { setActiveTab: (tab: string) => void; formData: any; setFormData: (data: any) => void; - starterCode: string; - setStarterCode: (code: string) => void; - solutionCode: string; - setSolutionCode: (code: string) => void; - showFileExplorer: boolean; - setShowFileExplorer: (show: boolean) => void; children?: React.ReactNode; } @@ -141,244 +93,103 @@ export const ProblemSolvingEditorMainContent: React.FC = ({ setActiveTab, formData, setFormData, - starterCode, - setStarterCode, - solutionCode, - setSolutionCode, - showFileExplorer, - setShowFileExplorer, children, }) => (
- {/* Left Panel - Problem Details */}
-
-
- {/* Basic Information */} -
- - - setFormData({ ...formData, title: e.target.value }) - } - className={`w-full p-3 rounded-lg border transition-colors duration-300 ${ - isDark - ? "bg-gray-700 border-gray-600 text-white" - : "bg-white border-gray-300 text-gray-900" - }`} - placeholder="Enter problem title" - /> -
- -
- -