const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); const AdmZip = require('adm-zip'); const { execSync } = require('child_process'); const BUILD_DIR = path.join(__dirname, 'dist-build'); const REPOS = ['frontend', 'player']; const GITLAB_GROUP = 'vyla-entertainment'; const REPO_CONFIG = { 'frontend': { name: 'frontend', branch: 'main' }, 'player': { name: 'player', branch: 'main' }, }; function cleanBuildDir() { if (fs.existsSync(BUILD_DIR)) { fs.rmSync(BUILD_DIR, { recursive: true, force: true }); } fs.mkdirSync(BUILD_DIR, { recursive: true }); } function removeSensitiveFiles(dir) { if (!fs.existsSync(dir)) return; const gitDir = path.join(dir, '.git'); if (fs.existsSync(gitDir)) { fs.rmSync(gitDir, { recursive: true, force: true }); } const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { if (entry.name === '.git') { fs.rmSync(fullPath, { recursive: true, force: true }); } else { removeSensitiveFiles(fullPath); } } else { if ( entry.name.startsWith('.env') || entry.name.endsWith('.key') || entry.name.endsWith('.pem') || entry.name === 'credentials.json' || entry.name === '.gitlab-ci.yml' ) { fs.rmSync(fullPath, { force: true }); } } } } function clonePrivateRepo(repoConfig, targetDir) { if (fs.existsSync(targetDir)) { fs.rmSync(targetDir, { recursive: true, force: true }); } const token = process.env.GITLAB_TOKEN || process.env.VYLA_GITLAB_TOKEN; const repoUrl = `https://gitlab.com/${GITLAB_GROUP}/${repoConfig.name}.git`; try { if (token) { const authHeader = Buffer.from(`oauth2:${token.trim()}`).toString('base64'); execSync( `git -c "http.extraHeader=Authorization: Basic ${authHeader}" clone --depth 1 --branch ${repoConfig.branch} ${repoUrl} "${targetDir}"`, { stdio: 'inherit' } ); } else { execSync( `git clone --depth 1 --branch ${repoConfig.branch} ${repoUrl} "${targetDir}"`, { stdio: 'inherit' } ); } } finally { const gitFolder = path.join(targetDir, '.git'); if (fs.existsSync(gitFolder)) { fs.rmSync(gitFolder, { recursive: true, force: true }); } } } async function build() { cleanBuildDir(); for (const [key, config] of Object.entries(REPO_CONFIG)) { const targetDir = path.join(__dirname, key); console.log(`Cloning ${config.name}...`); clonePrivateRepo(config, targetDir); console.log(`Installing production dependencies for ${config.name}...`); execSync('npm install --omit=dev', { cwd: targetDir, stdio: 'inherit' }); removeSensitiveFiles(targetDir); } const zip = new AdmZip(); const configDir = path.join(__dirname, 'config'); if (fs.existsSync(configDir)) { removeSensitiveFiles(configDir); zip.addLocalFolder(configDir, 'config'); } for (const repo of REPOS) { const srcRepo = path.join(__dirname, repo); removeSensitiveFiles(srcRepo); zip.addLocalFolder(srcRepo, repo); } const zipPath = path.join(__dirname, 'update-payload.zip'); if (fs.existsSync(zipPath)) fs.rmSync(zipPath, { force: true }); zip.writeZip(zipPath); const verificationZip = new AdmZip(zipPath); const leakedEntries = verificationZip.getEntries().filter(e => e.entryName.includes('.git/') || e.entryName.includes('.git\\') || e.entryName.startsWith('.env') || e.entryName.includes('/.env') || e.entryName.endsWith('config/encrypted-credentials.json') ); if (leakedEntries.length > 0) { fs.rmSync(zipPath, { force: true }); throw new Error(`SECURITY ABORT: Archive contains forbidden files:\n${leakedEntries.map(e => e.entryName).join('\n')}`); } const hash = crypto.createHash('sha256'); hash.update(fs.readFileSync(zipPath)); const checksum = hash.digest('hex'); const manifest = { version: JSON.parse(fs.readFileSync('package.json', 'utf8')).version, sha256: checksum, timestamp: new Date().toISOString() }; fs.writeFileSync(path.join(__dirname, 'manifest.json'), JSON.stringify(manifest, null, 2), 'utf8'); for (const repo of REPOS) { fs.rmSync(path.join(__dirname, repo), { recursive: true, force: true }); } console.log('Build complete and verified clean.'); } build().catch(err => { console.error(err); process.exit(1); });