previewDeploy: node_build-Autodetect fuer App-Repos + Build-Output-Erkennung

- runDeploy: Repos mit package.json-build-Script ohne index.html werden
  automatisch als node_build gebaut (Expo/Vite/React zeigen sonst nur den
  Platzhalter); Build-Fehler fallen auf static+Platzhalter zurueck
- deployNodeBuild: npm-ci-Fallback auf npm install, Output-Erkennung
  dist/build/out/web-build, maxBuffer+Timeout
- updateWebsiteProject: bestehenden type/index aus .webklar-preview.json
  erhalten statt bei jedem Edit auf static zurueckzusetzen

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Claude
2026-07-20 13:37:34 +00:00
parent f3bde44b3d
commit 55991c9008
2 changed files with 80 additions and 7 deletions

View File

@@ -432,9 +432,20 @@ export async function updateWebsiteProject(projectId, { projectName, subdomain,
/* Default main */
}
// Deploy-Typ (z.B. node_build) und index-Unterordner aus der vorhandenen
// Repo-Config uebernehmen - sonst wuerde jedes Bearbeiten im Portal
// App-Projekte (Expo/Vite/React) wieder auf "static" zuruecksetzen.
let existingCfg = null
try {
existingCfg = await fetchPreviewConfig(repoFullName, branch)
} catch {
/* keine bestehende Config -> Defaults */
}
const previewConfig = {
enabled: hasWebsite,
type: 'static',
type: existingCfg?.type || 'static',
...(existingCfg?.index ? { index: existingCfg.index } : {}),
branch,
displayName: nextName,
templateName: doc.templateName || config.gitea.templateRepo,

View File

@@ -215,11 +215,60 @@ async function ensureIndexHtml(targetDir, displayName) {
return { placeholder: true }
}
/**
* Erkennt App-Repos, die einen Build-Schritt brauchen (Expo, Vite, React ...):
* package.json mit "build"-Script vorhanden, aber keine index.html im Webroot.
* Solche Repos wuerden als "static" nur den Platzhalter zeigen - deshalb wird
* der Deploy-Typ automatisch auf node_build angehoben (Langzeit-Fix, damit
* App-Projekte ohne manuelle Config-Pflege korrekt angezeigt werden).
*/
async function shouldAutoBuild(sourceDir, indexSubdir = '') {
try {
const pkg = JSON.parse(await fs.readFile(path.join(sourceDir, 'package.json'), 'utf8'))
if (!pkg?.scripts?.build) return false
} catch {
return false
}
const webroot = indexSubdir ? path.join(sourceDir, indexSubdir) : sourceDir
try {
await fs.access(path.join(webroot, 'index.html'))
return false // echte statische Startseite vorhanden -> kein Build noetig
} catch {
return true
}
}
/**
* npm ci + npm run build, dann das Build-Ausgabeverzeichnis deployen.
* Ausgabeverzeichnis wird erkannt (dist > build > out > web-build), weil
* Toolchains unterschiedliche Defaults haben (Expo/Vite: dist, CRA: build).
*/
async function deployNodeBuild(sourceDir, targetDir) {
await execFileAsync('npm', ['ci'], { cwd: sourceDir })
await execFileAsync('npm', ['run', 'build'], { cwd: sourceDir })
const distDir = path.join(sourceDir, 'dist')
await deployStatic(distDir, targetDir)
const execOpts = {
cwd: sourceDir,
maxBuffer: 64 * 1024 * 1024,
timeout: 15 * 60 * 1000,
env: { ...process.env, CI: '1' },
}
try {
await execFileAsync('npm', ['ci'], execOpts)
} catch {
// Ohne (passendes) package-lock.json schlaegt npm ci fehl -> npm install.
await execFileAsync('npm', ['install', '--no-audit', '--no-fund'], execOpts)
}
await execFileAsync('npm', ['run', 'build'], execOpts)
for (const dirName of ['dist', 'build', 'out', 'web-build']) {
const candidate = path.join(sourceDir, dirName)
try {
await fs.access(path.join(candidate, 'index.html'))
await deployStatic(candidate, targetDir)
return
} catch {
/* naechsten Kandidaten pruefen */
}
}
throw new Error('Build erzeugte kein Ausgabeverzeichnis mit index.html (dist/build/out/web-build)')
}
export function buildPreviewUrl(subdomain) {
@@ -310,8 +359,21 @@ export async function runDeploy(repoFullName, branch, previewConfig) {
await cloneRepo(cloneUrl, workDir, branch, config.gitea.apiToken)
if (previewConfig.type === 'node_build') {
await deployNodeBuild(workDir, targetDir)
let deployType = previewConfig.type
if (deployType !== 'node_build' && (await shouldAutoBuild(workDir, previewConfig.index || ''))) {
console.log(`[previewDeploy] ${repoFullName}: package.json mit build-Script, keine index.html -> node_build (auto)`)
deployType = 'node_build'
}
if (deployType === 'node_build') {
try {
await deployNodeBuild(workDir, targetDir)
} catch (err) {
// Build-Fehler duerfen die Subdomain nicht toeten: statisch deployen,
// ensureIndexHtml haelt sie mit Kandidat/Platzhalter erreichbar.
console.error(`[previewDeploy] node_build fuer ${repoFullName} fehlgeschlagen, Fallback auf static:`, err.message)
await deployStatic(workDir, targetDir, previewConfig.index || '')
}
} else {
await deployStatic(workDir, targetDir, previewConfig.index || '')
}