/** * Складывает артефакты electron-builder (win + mac + linux) в публичный репозиторий, * ветка `updates`, чтобы generic URL …/raw/branch/updates/ указывал на актуальные latest*.yml и установщики. * * Копирование **merge**: существующие файлы в ветке (другие ОС) не удаляются — обновляются только * те имена, которые пришли из переданных каталогов артефактов. * * Переменные окружения: * DND_UPDATES_SERVER — https://git.example.com (без слэша в конце) * UPDATES_REPO — owner/repo (публичный репозиторий) * DND_UPDATES_PUSH_TOKEN — PAT с правом push в UPDATES_REPO * ARTIFACT_WIN — каталог с файлами Windows (можно пустой / отсутствует — пропуск) * ARTIFACT_MAC — каталог с файлами macOS * ARTIFACT_LINUX — каталог с файлами Linux (AppImage и т.д.) * GIT_COMMIT_TAG — опционально, для сообщения коммита */ import { execFileSync } from 'node:child_process'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const ALLOWED_EXT = new Set(['.yml', '.yaml', '.exe', '.blockmap', '.zip', '.dmg', '.pkg', '.appimage']); function mustEnv(name) { const v = process.env[name]?.trim(); if (!v) throw new Error(`Missing env ${name}`); return v; } function optionalDir(name) { const v = process.env[name]?.trim(); return v && v.length > 0 ? v : ''; } function copyFlatReleaseFiles(fromDir, toDir) { if (!fromDir || !fs.existsSync(fromDir)) { console.warn(`[sync-update-feed] skip missing dir: ${fromDir || '(empty)'}`); return 0; } let n = 0; for (const name of fs.readdirSync(fromDir)) { const src = path.join(fromDir, name); if (!fs.statSync(src).isFile()) continue; const ext = path.extname(name).toLowerCase(); if (!ALLOWED_EXT.has(ext)) continue; fs.copyFileSync(src, path.join(toDir, name)); n += 1; } return n; } function runGit(args, cwd) { execFileSync('git', args, { cwd, stdio: 'inherit' }); } function main() { const server = mustEnv('DND_UPDATES_SERVER').replace(/\/+$/u, ''); const updatesRepo = mustEnv('UPDATES_REPO'); const token = mustEnv('DND_UPDATES_PUSH_TOKEN'); const winDir = optionalDir('ARTIFACT_WIN'); const macDir = optionalDir('ARTIFACT_MAC'); const linuxDir = optionalDir('ARTIFACT_LINUX'); const u = new URL(server); const host = u.host; const cloneUrl = `https://oauth2:${encodeURIComponent(token)}@${host}/${updatesRepo}.git`; const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'dnd-feed-')); const work = path.join(tmp, 'repo'); try { execFileSync('git', ['clone', '--depth', '1', '-b', 'updates', cloneUrl, work], { stdio: 'inherit' }); } catch { execFileSync('git', ['clone', '--depth', '1', cloneUrl, work], { stdio: 'inherit' }); runGit(['checkout', '-B', 'updates'], work); } runGit(['config', 'user.email', 'ci@gitea-actions.local'], work); runGit(['config', 'user.name', 'gitea-actions'], work); const copied = copyFlatReleaseFiles(winDir, work) + copyFlatReleaseFiles(macDir, work) + copyFlatReleaseFiles(linuxDir, work); if (copied === 0) { throw new Error( '[sync-update-feed] no release files copied (check ARTIFACT_WIN / ARTIFACT_MAC / ARTIFACT_LINUX)', ); } const tag = process.env.GIT_COMMIT_TAG?.trim() || 'ci'; runGit(['add', '-A'], work); const st = execFileSync('git', ['status', '--porcelain'], { cwd: work }).toString().trim(); if (st) { runGit(['commit', '-m', `update feed ${tag}`], work); runGit(['push', '-u', 'origin', 'updates'], work); } else { console.warn('[sync-update-feed] nothing to commit (identical artifacts?)'); } fs.rmSync(tmp, { recursive: true, force: true }); console.log(`[sync-update-feed] done (${String(copied)} file(s) copied)`); } main();