#!/usr/bin/env node /** * Zieht die sechs Kundenprojekte nach "WEBklar" um: sichert vorher die Webhooks aller * betroffenen Repos in eine JSON-Datei, ruft dann je Repo `reconcileProject` (Task 6) auf - * das verschiebt das Repo und traegt liegen gebliebene Pushes nach - und stellt danach eine * fehlende `.webklar-preview.json` fuer gehostete Projekte wieder her. * * Laeuft spaeter INNERHALB des Portal-Containers (nicht auf dem Host): nutzt server/config.js * und die dort konfigurierten Appwrite-/Gitea-Zugaenge direkt. * * Usage (im Container): node scripts/gitea-organisation-migrate.mjs */ import fs from 'node:fs/promises' import path from 'node:path' import { pathToFileURL } from 'node:url' import { config } from '../server/config.js' import { giteaFetch, splitRepo } from '../server/services/giteaClient.js' import { listDocuments, Query } from '../server/services/appwriteAdmin.js' import { reconcileProject } from '../server/services/projectAssignment.js' import { isHostedProject, ensureRepoPreviewConfig, getRepoInfo } from '../server/services/giteaAdmin.js' import { fetchPreviewConfig } from '../server/services/previewDeploy.js' export const CUSTOMER_REPOS = [ 'knso/emmas-schatzkiste', 'knso/Anelia-Wolf', 'ANDJ/Unger-Bus-Unternehmen', 'JUSN/D-ner-Nation', 'knso/videogen', 'knso/kfz', ] /** * Schreibt `hooks-.json` mit den aktuellen Webhooks aller uebergebenen Repos * (`{id, url, events}` je Hook - `url` kommt aus `config.url`, echtes Gitea traegt das Ziel * NIRGENDS sonst). Ein Repo, dessen Hooks nicht gelesen werden koennen, bekommt eine leere * Liste - die Sicherung soll nie an einem einzelnen kaputten Repo scheitern. */ async function backupHooks(repoFullNames, backupDir, now) { const backup = {} for (const repoFullName of repoFullNames) { const [owner, repo] = splitRepo(repoFullName) let hooks = [] try { const result = await giteaFetch(`/repos/${owner}/${repo}/hooks`) hooks = Array.isArray(result) ? result : [] } catch (err) { console.error(`[gitea-organisation-migrate] Webhooks von ${repoFullName} konnten nicht gesichert werden:`, err.message) } backup[repoFullName] = hooks.map((h) => ({ id: h.id, url: h.config?.url, events: h.events })) } await fs.mkdir(backupDir, { recursive: true }) const fileName = `hooks-${now.toISOString().replace(/[:.]/g, '-')}.json` await fs.writeFile(path.join(backupDir, fileName), JSON.stringify(backup, null, 2)) return fileName } export async function migrateRepos({ repoFullNames = CUSTOMER_REPOS, backupDir, now = new Date() } = {}) { await backupHooks(repoFullNames, backupDir, now) const results = [] for (const repoFullName of repoFullNames) { const docs = await listDocuments(config.collections.websiteProjects, [ Query.equal('repoFullName', repoFullName), Query.limit(1), ]) const doc = docs[0] if (!doc) { results.push({ from: repoFullName, to: repoFullName, backfilled: 0, warnings: ['kein Projekt-Eintrag'], previewConfigWritten: false, }) continue } let reconciled try { // Bewusst OHNE eigene Sperre aufgerufen - `reconcileProject` sperrt sich selbst // (Controller-Ruling): ein zweiter, gleichzeitiger Aufruf (z. B. aus dem Portal // waehrend der Migration) darf nicht doppelt umziehen. reconciled = await reconcileProject(doc.$id, { now }) } catch (err) { results.push({ from: repoFullName, to: repoFullName, backfilled: 0, warnings: [err.message], previewConfigWritten: false, }) continue } const to = reconciled.project.repoFullName let previewConfigWritten = false if (isHostedProject(reconciled.project)) { const branch = (await getRepoInfo(to))?.defaultBranch || 'main' const existingPreviewConfig = await fetchPreviewConfig(to, branch) if (!existingPreviewConfig) { await ensureRepoPreviewConfig(to, branch) previewConfigWritten = true } } results.push({ from: repoFullName, to, backfilled: reconciled.backfilled, warnings: reconciled.warnings, previewConfigWritten, }) } return results } async function main() { const results = await migrateRepos({ backupDir: '/app/upload-data/gitea-org' }) console.log(JSON.stringify(results, null, 2)) } if (import.meta.url === pathToFileURL(process.argv[1]).href) { main().catch((err) => { console.error(err.message) process.exit(1) }) }