Preview: Dev-Modus nur mit dev-Skript waehlbar

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-09-30 22:47:54 +00:00
1 parent a5cb668853
commit 04b45e4207
5 files changed
+159 -7

No files matched your search

+53 -4
View File
@@ -8,10 +8,11 @@ import crypto from 'node:crypto'
import express from 'express'
import { startFakeAppwrite } from './helpers/fakeAppwrite.js'
let aw, runner, gitea, app, base, tmp, cfg, envLib, logs
let devScript, aw, runner, gitea, app, base, tmp, cfg, envLib, logs
const key = crypto.randomBytes(32)
const SHA = 'c'.repeat(40)
const rs = { calls: [], busy: false, slow: false, result: 'static' }
const rs = { calls: [], busy: false, slow: false, result: 'static', failError: null }
const gs = { pkg: undefined, status: 200 } // pkg: undefined = Standardantwort, null = 404, Objekt = package.json
before(async () => {
const seed = [
@@ -34,6 +35,7 @@ before(async () => {
rs.calls.push({ method: req.method, url: req.url, body: raw ? JSON.parse(raw) : null })
res.setHeader('Content-Type', 'application/json')
if (req.method === 'POST' && req.url === '/jobs/build') return res.end(JSON.stringify({ runId: 'run-x' }))
if (req.method === 'GET' && req.url.startsWith('/jobs/') && rs.failError) return res.end(JSON.stringify({ state: 'failed', result: null, log: [], error: rs.failError }))
if (req.method === 'GET' && req.url.startsWith('/jobs/')) return res.end(JSON.stringify(rs.slow ? { state: 'running' } : { state: 'done', detected: { kind: 'static', pm: 'npm' }, sha: 'b'.repeat(40), log: ['ok'], error: null, result: rs.result }))
if (req.url.endsWith('/start')) return res.end(JSON.stringify({ state: 'starting' }))
res.end(JSON.stringify({ state: 'sleeping', ok: true }))
@@ -42,6 +44,11 @@ before(async () => {
await new Promise((r) => runner.listen(0, '127.0.0.1', r))
gitea = http.createServer((req, res) => {
res.setHeader('Content-Type', 'application/json')
if (req.url.startsWith('/api/v1/repos/') && req.url.includes('/contents/package.json') && gs.pkg !== undefined) {
if (gs.status !== 200) { res.statusCode = gs.status; return res.end(JSON.stringify({ message: 'x' })) }
if (gs.pkg === null) { res.statusCode = 404; return res.end(JSON.stringify({ message: 'not found' })) }
return res.end(JSON.stringify({ content: Buffer.from(JSON.stringify(gs.pkg)).toString('base64') }))
}
res.end(JSON.stringify({ commit: { id: 'b'.repeat(40) }, default_branch: 'main' }))
})
await new Promise((r) => gitea.listen(0, '127.0.0.1', r))
@@ -62,6 +69,7 @@ before(async () => {
cfg.previewRunner.logDir = path.join(tmp, 'logs')
envLib = await import('../server/services/previewEnv.js')
logs = await import('../server/services/previewLogs.js')
devScript = await import('../server/services/previewDevScript.js')
const pd = await import('../server/services/previewDeploy.js')
pd.runnerTiming.pollMs = 10
const { default: preview } = await import('../server/routes/admin/preview.js')
@@ -88,7 +96,7 @@ after(async () => {
await fs.rm(tmp, { recursive: true, force: true })
})
beforeEach(() => { rs.busy = false; rs.slow = false; rs.result = 'static'; rs.calls = []; cfg.previewRunner.enabled = true; process.env.PREVIEW_ENV_KEY = key.toString('base64') })
beforeEach(() => { gs.pkg = undefined; gs.status = 200; rs.failError = null; devScript.clearDevScriptCache(); rs.busy = false; rs.slow = false; rs.result = 'static'; rs.calls = []; cfg.previewRunner.enabled = true; process.env.PREVIEW_ENV_KEY = key.toString('base64') })
const call = (method, p, body, jwt = 'jwt-admin') =>
fetch(`${base}${p}`, {
@@ -108,7 +116,7 @@ test('GET: Form, leerer Modus = auto, 404 und keine Subdomain', async () => {
const r = await call('GET', '/pw/preview')
assert.equal(r.status, 200)
assert.deepEqual(await r.json(), {
mode: 'auto', state: 'online', detected: 'static', builtSha: SHA, builtAt: '', error: '', branch: 'main', envNames: [], logs: [],
mode: 'auto', state: 'online', detected: 'static', builtSha: SHA, builtAt: '', error: '', branch: 'main', envNames: [], logs: [], devAvailable: null,
})
assert.equal((await call('GET', '/gibtsnicht/preview')).status, 404)
const r2 = await call('GET', '/pnosub/preview')
@@ -293,3 +301,44 @@ test('Runner-Schutzschalter 503: rebuild und dev/start melden Server ausgelastet
assert.deepEqual(await r.json(), { error: 'Server ausgelastet, versuche es gleich noch mal' })
}
})
test('GET: devAvailable true/false/null', async () => {
gs.pkg = { scripts: { dev: 'vite' } }
assert.equal((await (await call('GET', '/pw/preview')).json()).devAvailable, true)
devScript.clearDevScriptCache()
gs.pkg = null
assert.equal((await (await call('GET', '/pw/preview')).json()).devAvailable, false)
devScript.clearDevScriptCache()
gs.pkg = {}; gs.status = 500
assert.equal((await (await call('GET', '/pw/preview')).json()).devAvailable, null)
})
test('PATCH dev ohne dev-Skript: 400, Modus bleibt, kein Neubau', async () => {
gs.pkg = null
const r = await call('PATCH', '/pr/preview', { mode: 'dev' })
assert.equal(r.status, 400)
assert.equal((await r.json()).error, 'Dieses Projekt hat kein dev-Skript. Reines HTML braucht keinen Dev-Server – Änderungen erscheinen nach jedem Push automatisch.')
assert.notEqual(aw.get('websiteProjects', 'pr').previewMode, 'dev')
assert.equal(rs.calls.filter((c) => c.url === '/jobs/build').length, 0)
devScript.clearDevScriptCache()
gs.pkg = { scripts: { build: 'x' } }
assert.equal((await call('PATCH', '/pr/preview', { mode: 'dev' })).status, 400)
})
test('PATCH dev erlaubt bei dev-Skript und bei Gitea-Fehler (null)', async () => {
gs.pkg = { scripts: { dev: 'vite' } }
assert.equal((await call('PATCH', '/pe/preview', { mode: 'dev' })).status, 200)
assert.equal(aw.get('websiteProjects', 'pe').previewMode, 'dev')
devScript.clearDevScriptCache()
gs.pkg = {}; gs.status = 500
assert.equal((await call('PATCH', '/pw/preview', { mode: 'dev' })).status, 200)
})
test('Verfolger: Kein dev-Skript => freundliche Meldung, Zustand failed', async () => {
rs.failError = 'Kein dev-Skript in package.json'
assert.equal((await call('PATCH', '/pe/preview', { mode: 'build' })).status, 200)
for (let i = 0; i < 100 && aw.get('websiteProjects', 'pe').previewState !== 'failed'; i++) await new Promise((r) => setTimeout(r, 20))
const d = aw.get('websiteProjects', 'pe')
assert.equal(d.previewState, 'failed')
assert.equal(d.previewError, 'Dev-Modus hier nicht möglich – statische Version ist online')
})