Preview: Schema-Skript und Runner-Konfiguration
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
def490ceca
commit
40a8aad3f9
3 files changed
+160
No files matched your search
@@ -0,0 +1,97 @@
|
|||||||
|
#!/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: '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)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -303,6 +303,12 @@ export const config = {
|
|||||||
sessionMaxAge: Number(process.env.PREVIEW_SESSION_MAX_AGE || 60 * 60 * 24 * 7),
|
sessionMaxAge: Number(process.env.PREVIEW_SESSION_MAX_AGE || 60 * 60 * 24 * 7),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
previewRunner: {
|
||||||
|
enabled: process.env.PREVIEW_RUNNER === 'on',
|
||||||
|
url: process.env.PREVIEW_RUNNER_URL || 'http://webklar-preview-runner:8080',
|
||||||
|
token: process.env.PREVIEW_RUNNER_TOKEN || '',
|
||||||
|
},
|
||||||
|
|
||||||
invoiceNinja: {
|
invoiceNinja: {
|
||||||
url: (process.env.INVOICE_NINJA_URL || 'https://invoice.webklar.com').replace(/\/$/, ''),
|
url: (process.env.INVOICE_NINJA_URL || 'https://invoice.webklar.com').replace(/\/$/, ''),
|
||||||
apiToken: process.env.INVOICE_NINJA_API_TOKEN || '',
|
apiToken: process.env.INVOICE_NINJA_API_TOKEN || '',
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { test } from 'node:test'
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import { PREVIEW_RUNNER_ATTRIBUTES, main } from '../scripts/preview-runner-setup.mjs'
|
||||||
|
import { config } from '../server/config.js'
|
||||||
|
|
||||||
|
function mockClient(existing = []) {
|
||||||
|
const created = []
|
||||||
|
return {
|
||||||
|
created,
|
||||||
|
async listAttributes() {
|
||||||
|
return existing.map((key) => ({ key }))
|
||||||
|
},
|
||||||
|
async createAttribute(attr) {
|
||||||
|
created.push(attr.key)
|
||||||
|
},
|
||||||
|
async waitForAttribute() {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test('Schema: genau die 7 Attribute mit Groessen, alle optional', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
PREVIEW_RUNNER_ATTRIBUTES.map((a) => a.key),
|
||||||
|
['previewMode', 'previewEnv', 'previewState', 'previewDetected', 'previewBuiltSha', 'previewBuiltAt', 'previewError']
|
||||||
|
)
|
||||||
|
assert.ok(PREVIEW_RUNNER_ATTRIBUTES.every((a) => a.required === false && a.type === 'string'))
|
||||||
|
const sizes = Object.fromEntries(PREVIEW_RUNNER_ATTRIBUTES.map((a) => [a.key, a.size]))
|
||||||
|
assert.deepEqual(sizes, {
|
||||||
|
previewMode: 10,
|
||||||
|
previewEnv: 20000,
|
||||||
|
previewState: 20,
|
||||||
|
previewDetected: 40,
|
||||||
|
previewBuiltSha: 40,
|
||||||
|
previewBuiltAt: 40,
|
||||||
|
previewError: 500,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('main legt nur fehlende Attribute an', async () => {
|
||||||
|
const client = mockClient(['previewMode'])
|
||||||
|
const result = await main({ check: false, client, log: () => {} })
|
||||||
|
assert.equal(client.created.length, 6)
|
||||||
|
assert.ok(!client.created.includes('previewMode'))
|
||||||
|
assert.equal(result.missing.length, 6)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('main --check legt nichts an und meldet Fehlende', async () => {
|
||||||
|
const client = mockClient(['previewMode'])
|
||||||
|
const result = await main({ check: true, client, log: () => {} })
|
||||||
|
assert.equal(client.created.length, 0)
|
||||||
|
assert.equal(result.missing.length, 6)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Konfiguration: previewRunner ist standardmaessig aus', () => {
|
||||||
|
assert.equal(config.previewRunner.enabled, false)
|
||||||
|
assert.equal(config.previewRunner.url, 'http://webklar-preview-runner:8080')
|
||||||
|
assert.equal(config.previewRunner.token, '')
|
||||||
|
})
|
||||||
Reference in new issue
Block a user