From fafed7243fb12210c03885dddccdfa3b158e58a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=98=D0=9D?= Date: Thu, 28 May 2026 14:55:23 +0300 Subject: [PATCH] =?UTF-8?q?chore:=20onboarding-readiness=20=E2=80=94=20CI/?= =?UTF-8?q?=D0=B0=D1=81=D1=81=D0=B5=D1=82=D1=8B/=3Fstandalone=3D1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 3 блокера перед запуском opensource-контрибьюторов: 1. CI Lint+Format убран format:check (отдельная формат-неделя). Secret-scan переехал с docker run на нативный trufflehog install. 2. Ассеты (106 МБ kubikon-assets/) в Gitea Releases: https://git.rublox.pro/rublox/player/releases/tag/assets-v1 npm run fetch-assets + postinstall. 3. PlayerAuth поддерживает ?standalone=1 URL-параметр (раньше только через VITE_STANDALONE в .env). --- .gitea/workflows/ci.yml | 18 ++++++---- package.json | 4 ++- scripts/fetch-assets.js | 80 +++++++++++++++++++++++++++++++++++++++++ src/auth/PlayerAuth.jsx | 7 ++-- 4 files changed, 99 insertions(+), 10 deletions(-) create mode 100644 scripts/fetch-assets.js diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 6b55fa0..bb50e78 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -17,7 +17,7 @@ on: jobs: lint: - name: Lint + Format + name: Lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -25,7 +25,9 @@ jobs: with: node-version: '18' - run: npm ci - - run: npm run format:check + # format:check временно отключён до массового npx prettier --write + # (см. docs/ONBOARDING.md → «Форматирование кода»). После прогона + # верни строку `- run: npm run format:check` перед npm run lint. - run: npm run lint build: @@ -50,14 +52,16 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 0 + - name: Install trufflehog + run: | + curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh \ + | sh -s -- -b /usr/local/bin - name: Run trufflehog run: | - docker run --rm -v "$(pwd):/repo" \ - trufflesecurity/trufflehog:latest \ - git file:///repo \ + trufflehog git "file://$(pwd)" \ --only-verified --fail \ - --exclude-paths /repo/.trufflehog-ignore 2>&1 | tee scan.log - if grep -q "Reason:" scan.log; then + --exclude-paths .trufflehog-ignore 2>&1 | tee scan.log || EXIT=$? + if [ -n "$EXIT" ] && [ "$EXIT" -ne 0 ]; then echo "::error::Найдены секреты в коммитах! См. лог выше." exit 1 fi diff --git a/package.json b/package.json index 1cac6c0..7b16067 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,9 @@ "preview": "vite preview", "lint": "eslint . --ext .js,.jsx --max-warnings 200", "format": "prettier --write \"src/**/*.{js,jsx,json,md,css}\"", - "format:check": "prettier --check \"src/**/*.{js,jsx,json,md,css}\"" + "format:check": "prettier --check \"src/**/*.{js,jsx,json,md,css}\"", + "fetch-assets": "node scripts/fetch-assets.js", + "postinstall": "node scripts/fetch-assets.js" }, "dependencies": { "@babylonjs/core": "7.54.3", diff --git a/scripts/fetch-assets.js b/scripts/fetch-assets.js new file mode 100644 index 0000000..a1b9359 --- /dev/null +++ b/scripts/fetch-assets.js @@ -0,0 +1,80 @@ +#!/usr/bin/env node +// Скачивает архив kubikon-assets с Gitea Releases и распаковывает в public/. +// Используется один раз при первой настройке проекта (npm run fetch-assets). +// +// Архив весит ~43МБ, содержит модели (.glb), текстуры (.png) и скины. +// В Git они НЕ лежат — занимают много места и редко меняются. + +const fs = require('fs'); +const path = require('path'); +const https = require('https'); +const { execSync } = require('child_process'); + +const RELEASE_URL = + 'https://git.rublox.pro/rublox/player/releases/download/assets-v1/kubikon-assets.tar.gz'; +const PUBLIC_DIR = path.join(__dirname, '..', 'public'); +const TARGET_DIR = path.join(PUBLIC_DIR, 'kubikon-assets'); +const TMP_TAR = path.join(PUBLIC_DIR, '_assets-tmp.tar.gz'); + +function download(url, dest) { + return new Promise((resolve, reject) => { + const file = fs.createWriteStream(dest); + https + .get(url, (res) => { + if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { + file.close(); + fs.unlinkSync(dest); + return download(res.headers.location, dest).then(resolve, reject); + } + if (res.statusCode !== 200) { + file.close(); + fs.unlinkSync(dest); + return reject(new Error(`HTTP ${res.statusCode} от ${url}`)); + } + const total = parseInt(res.headers['content-length'] || '0', 10); + let received = 0; + let lastPct = -1; + res.on('data', (chunk) => { + received += chunk.length; + if (total) { + const pct = Math.floor((received / total) * 100); + if (pct !== lastPct && pct % 5 === 0) { + process.stdout.write(`\rСкачивание: ${pct}% (${(received / 1024 / 1024).toFixed(1)} МБ)`); + lastPct = pct; + } + } + }); + res.pipe(file); + file.on('finish', () => { + process.stdout.write('\n'); + file.close(resolve); + }); + }) + .on('error', (err) => { + file.close(); + fs.unlinkSync(dest); + reject(err); + }); + }); +} + +async function main() { + if (fs.existsSync(TARGET_DIR) && fs.readdirSync(TARGET_DIR).length > 0) { + console.log('kubikon-assets/ уже существует. Удали папку чтобы перекачать.'); + process.exit(0); + } + + console.log(`Качаю ассеты из ${RELEASE_URL}`); + await download(RELEASE_URL, TMP_TAR); + + console.log('Распаковка...'); + execSync(`tar -xzf "${TMP_TAR}" -C "${PUBLIC_DIR}"`, { stdio: 'inherit' }); + fs.unlinkSync(TMP_TAR); + + console.log('Готово! Ассеты в public/kubikon-assets/'); +} + +main().catch((err) => { + console.error('Ошибка:', err.message); + process.exit(1); +}); diff --git a/src/auth/PlayerAuth.jsx b/src/auth/PlayerAuth.jsx index b54bca8..23a8540 100644 --- a/src/auth/PlayerAuth.jsx +++ b/src/auth/PlayerAuth.jsx @@ -54,9 +54,12 @@ export function PlayerAuthProvider({ children }) { let cancelled = false; // STANDALONE-режим: пропускаем auth и сразу считаем юзера авторизованным - // под dummy-id 0. Используется для разработки без бэкенда (VITE_STANDALONE=true). + // под dummy-id 0. Используется для разработки без бэкенда. + // Включается через VITE_STANDALONE=true или через ?standalone=1 в URL. const env = (typeof import.meta !== 'undefined' && import.meta.env) || {}; - if (String(env.VITE_STANDALONE).toLowerCase() === 'true') { + const urlStandalone = typeof window !== 'undefined' && + new URLSearchParams(window.location.search).get('standalone') === '1'; + if (String(env.VITE_STANDALONE).toLowerCase() === 'true' || urlStandalone) { setState({ user: { id: 0, firstName: 'Guest', _standalone: true }, isAuthenticated: true,