diff --git a/.dockerignore b/.dockerignore index 257acde2..176d1d4a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,3 +5,7 @@ **/coverage **/.env* screenshots +frontend/public/maps_optimized +api +database +docs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 76580fd5..422325e7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -202,6 +202,12 @@ jobs: working-directory: frontend run: pnpm exec tsc --noEmit + - name: Export optimized maps + working-directory: server + run: | + pnpm install --frozen-lockfile + pnpm export-frontend-maps + - name: Build working-directory: frontend env: diff --git a/README.md b/README.md index 7e8739ae..c3226d5c 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,14 @@ pnpm dev Abrir `http://localhost:3000`. +Los mapas optimizados (`frontend/public/maps_optimized`) no están en git. Docker los genera al construir la imagen. Para un build de producción en local: + +```bash +cd server +pnpm install +pnpm export-frontend-maps +``` + ## Arquitectura | Componente | Carpeta | Puerto | diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 07e38025..2510963a 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -5,6 +5,20 @@ ENV PATH="$PNPM_HOME:$PATH" RUN corepack enable +FROM base AS maps +WORKDIR /work/server + +COPY server/src/mapNpcStorage.ts src/mapNpcStorage.ts +COPY server/src/scripts/exportFrontendOptimizedMaps.ts src/scripts/exportFrontendOptimizedMaps.ts +COPY server/mapas_source/ mapas_source/ + +RUN pnpm dlx tsx@4.22.1 src/scripts/exportFrontendOptimizedMaps.ts --output-dir=/maps_optimized \ + && test -f /maps_optimized/mapa_1.json \ + && SOURCE_COUNT="$(find mapas_source -mindepth 1 -maxdepth 1 -type d -name 'mapa_*' | wc -l)" \ + && OUTPUT_COUNT="$(find /maps_optimized -maxdepth 1 -type f -name 'mapa_*.json' | wc -l)" \ + && echo "Export check: ${SOURCE_COUNT} dirs en mapas_source, ${OUTPUT_COUNT} mapa_*.json" \ + && test "${SOURCE_COUNT}" -eq "${OUTPUT_COUNT}" + FROM base AS deps WORKDIR /app/frontend @@ -44,6 +58,9 @@ ENV GAME_DATA_ADMIN_PROXY_TOKEN=$GAME_DATA_ADMIN_PROXY_TOKEN COPY packages/protocol /app/packages/protocol COPY frontend . +RUN rm -rf ./public/maps_optimized +COPY --from=maps /maps_optimized ./public/maps_optimized +RUN node ./scripts/require-optimized-maps.mjs RUN pnpm build FROM base AS runtime diff --git a/frontend/package.json b/frontend/package.json index 5d08000d..bc890883 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,7 +6,8 @@ "protocol:build": "pnpm exec tsc -p ../packages/protocol/tsconfig.json", "predev": "pnpm run protocol:build", "dev": "next dev --turbopack --hostname 0.0.0.0", - "prebuild": "pnpm run protocol:build", + "prebuild": "pnpm run protocol:build && node ./scripts/require-optimized-maps.mjs", + "check:maps": "node ./scripts/require-optimized-maps.mjs", "build": "next build", "start": "next start", "lint": "eslint", diff --git a/frontend/scripts/require-optimized-maps.mjs b/frontend/scripts/require-optimized-maps.mjs new file mode 100644 index 00000000..b7b1b8d0 --- /dev/null +++ b/frontend/scripts/require-optimized-maps.mjs @@ -0,0 +1,62 @@ +import fs from "node:fs"; +import path from "node:path"; + +const MAPS_DIR = path.resolve(process.cwd(), "public/maps_optimized"); +const SAMPLE_NAME = "mapa_1.json"; +const MAP_JSON_PATTERN = /^mapa_\d+\.json$/i; + +function fail(message) { + console.error(message); + process.exit(1); +} + +function isOptimizedMapShape(value) { + return ( + Boolean(value) && + typeof value === "object" && + typeof value.id === "number" && + typeof value.w === "number" && + typeof value.h === "number" && + Array.isArray(value.d) + ); +} + +if (!fs.existsSync(MAPS_DIR) || !fs.statSync(MAPS_DIR).isDirectory()) { + fail( + `No está ${MAPS_DIR}. Los mapas optimizados no van en git: generálos con \`cd server && pnpm export-frontend-maps\`. Docker los arma solo al construir la imagen.`, + ); +} + +const mapFiles = fs.readdirSync(MAPS_DIR).filter((name) => MAP_JSON_PATTERN.test(name)); + +if (mapFiles.length === 0) { + fail( + `La carpeta ${MAPS_DIR} no tiene ningún mapa_*.json. Corré \`cd server && pnpm export-frontend-maps\` antes del build de producción.`, + ); +} + +const samplePath = path.join(MAPS_DIR, SAMPLE_NAME); + +if (!fs.existsSync(samplePath)) { + fail( + `Falta ${samplePath}. El export quedó incompleto: volvé a correr \`pnpm export-frontend-maps\` desde server/.`, + ); +} + +let sample; + +try { + sample = JSON.parse(fs.readFileSync(samplePath, "utf8")); +} catch { + fail( + `${SAMPLE_NAME} no es JSON válido. El export salió mal; borralo y volvé a generar los mapas.`, + ); +} + +if (!isOptimizedMapShape(sample)) { + fail( + `${SAMPLE_NAME} no tiene la forma esperada ({ id: number, w: number, h: number, d: array }). El archivo no es un mapa optimizado válido.`, + ); +} + +console.log(`Mapas optimizados OK: ${mapFiles.length} archivos en ${MAPS_DIR}`);