#!/usr/bin/env node /** * Preview 2.0: legt die Preview-Runner-Attribute an der Collection websiteProjects an. * Idempotent: vorhandene Attribute werden uebersprungen. * Usage: * node scripts/preview-runner-setup.mjs --check (nur melden, nichts anlegen) * node scripts/preview-runner-setup.mjs (fehlende Attribute anlegen) */ import { pathToFileURL } from 'node:url' import { config } from '../server/config.js' export const PREVIEW_RUNNER_ATTRIBUTES = [ { key: 'previewMode', type: 'string', size: 10, required: false }, { key: 'previewEnv', type: 'string', size: 20000, required: false }, { key: 'previewState', type: 'string', size: 20, required: false }, { key: 'previewDetected', type: 'string', size: 40, required: false }, { key: 'previewBuiltSha', type: 'string', size: 40, required: false }, { key: 'previewBranch', type: 'string', size: 100, required: false }, { key: 'previewBuiltAt', type: 'string', size: 40, required: false }, { key: 'previewError', type: 'string', size: 500, required: false }, ] /** Echter Appwrite-Client (REST) fuer die Collection websiteProjects. */ export function createAppwriteClient() { const { endpoint, projectId, databaseId, apiKey } = config.appwrite const collectionId = config.collections.websiteProjects const base = `${endpoint.replace(/\/$/, '')}/databases/${databaseId}/collections/${collectionId}` async function aw(path, { method = 'GET', body } = {}) { const response = await fetch(`${base}${path}`, { method, headers: { 'Content-Type': 'application/json', 'X-Appwrite-Project': projectId, 'X-Appwrite-Key': apiKey, }, body: body ? JSON.stringify(body) : undefined, }) const data = await response.json().catch(() => ({})) if (!response.ok) throw new Error(data.message || `Appwrite ${response.status}`) return data } return { async listAttributes() { // Ohne limit liefert Appwrite nur 25 Attribute - die Sammlung hat mehr const q = encodeURIComponent(JSON.stringify({ method: 'limit', values: [200] })) const data = await aw(`/attributes?queries[]=${q}`) return data.attributes || [] }, async createAttribute({ type, ...body }) { await aw(`/attributes/${type}`, { method: 'POST', body }) }, async waitForAttribute(key, maxSec = 30) { for (let i = 0; i < maxSec; i += 1) { const attr = await aw(`/attributes/${key}`).catch(() => null) if (attr?.status === 'available') return await new Promise((r) => setTimeout(r, 1000)) } throw new Error(`Attribut ${key} wurde nicht verfuegbar`) }, } } export async function main({ check = false, client, log = console.log } = {}) { const api = client || createAppwriteClient() const existing = new Set((await api.listAttributes()).map((a) => a.key)) const missing = PREVIEW_RUNNER_ATTRIBUTES.filter((a) => !existing.has(a.key)) for (const attr of PREVIEW_RUNNER_ATTRIBUTES) { if (existing.has(attr.key)) log(`= Attribut ${attr.key} (existiert)`) else if (check) log(`- Attribut ${attr.key} FEHLT`) } if (check) { log(missing.length ? `${missing.length} Attribut(e) fehlen.` : 'Alle Attribute vorhanden.') return { missing } } for (const attr of missing) { await api.createAttribute(attr) log(`+ Attribut ${attr.key}`) } for (const attr of missing) await api.waitForAttribute(attr.key) log('Fertig.') return { missing } } const invokedDirectly = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href if (invokedDirectly) { const check = process.argv.includes('--check') main({ check }) .then(({ missing }) => process.exit(check && missing.length ? 1 : 0)) .catch((e) => { console.error(e.message) process.exit(1) }) }