@brewnet/cli 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin-server-UODBPGWR.js +16 -0
- package/dist/app-manager-FIHPVUP7.js +51 -0
- package/dist/{boilerplate-manager-P6QYUU7Q.js → boilerplate-manager-WEFTHL2O.js} +3 -3
- package/dist/{chunk-DH2VK3YI.js → chunk-54WFZCU6.js} +2 -2
- package/dist/{chunk-4TJMJZMO.js → chunk-AXSHZEB3.js} +63 -1
- package/dist/{chunk-4TJMJZMO.js.map → chunk-AXSHZEB3.js.map} +1 -1
- package/dist/chunk-FZZ3HP2G.js +1151 -0
- package/dist/chunk-FZZ3HP2G.js.map +1 -0
- package/dist/chunk-JIPAYMOA.js +58 -0
- package/dist/chunk-JIPAYMOA.js.map +1 -0
- package/dist/{chunk-SIXBB6JU.js → chunk-Q6UUZR2V.js} +238 -1171
- package/dist/chunk-Q6UUZR2V.js.map +1 -0
- package/dist/{chunk-2VWMDHGI.js → chunk-YAYXULLO.js} +9 -61
- package/dist/chunk-YAYXULLO.js.map +1 -0
- package/dist/{chunk-JFPHGZ6Z.js → chunk-YXFDB5YX.js} +17 -2
- package/dist/chunk-YXFDB5YX.js.map +1 -0
- package/dist/{cloudflare-client-TFT6VCXF.js → cloudflare-client-F2TGQXGS.js} +2 -2
- package/dist/{compose-generator-O7GSIJ2S.js → compose-generator-OFJ2YWMB.js} +4 -2
- package/dist/compose-generator-OFJ2YWMB.js.map +1 -0
- package/dist/index.js +122 -168
- package/dist/index.js.map +1 -1
- package/dist/services/admin-daemon.js +6 -4
- package/dist/services/admin-daemon.js.map +1 -1
- package/package.json +1 -1
- package/dist/admin-server-DQVIEHV3.js +0 -14
- package/dist/chunk-2VWMDHGI.js.map +0 -1
- package/dist/chunk-JFPHGZ6Z.js.map +0 -1
- package/dist/chunk-SIXBB6JU.js.map +0 -1
- /package/dist/{admin-server-DQVIEHV3.js.map → admin-server-UODBPGWR.js.map} +0 -0
- /package/dist/{boilerplate-manager-P6QYUU7Q.js.map → app-manager-FIHPVUP7.js.map} +0 -0
- /package/dist/{cloudflare-client-TFT6VCXF.js.map → boilerplate-manager-WEFTHL2O.js.map} +0 -0
- /package/dist/{chunk-DH2VK3YI.js.map → chunk-54WFZCU6.js.map} +0 -0
- /package/dist/{compose-generator-O7GSIJ2S.js.map → cloudflare-client-F2TGQXGS.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/services/app-manager.ts","../src/services/gitea-client.ts"],"sourcesContent":["// packages/cli/src/services/app-manager.ts\nimport { existsSync, readFileSync, writeFileSync, readdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { randomBytes } from 'node:crypto';\nimport { execa } from 'execa';\nimport { GiteaClient } from './gitea-client.js';\nimport { readApps, addApp, updateApp, removeApp as registryRemoveApp, readDeployHistory, appendDeployHistory } from './app-registry.js';\nimport { getLastProject, loadState } from '../wizard/state.js';\nimport type { AppEntry, AppJob, AppJobStep, CreateAppOptions, DeployHistoryEntry, GitRepoEntry, AppGitInfo, DeploySettings } from '../types/app-entry.js';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst BREWNET_DIR = join(homedir(), '.brewnet');\nconst GITEA_TOKEN_PATH = join(BREWNET_DIR, 'gitea-token');\nconst GITEA_CONFIG_PATH = join(BREWNET_DIR, 'gitea-config.json');\nconst DEPLOY_HISTORY_PATH = join(BREWNET_DIR, 'deploy-history.json');\n\n// ---------------------------------------------------------------------------\n// In-memory job store (ephemeral — cleared on server restart)\n// ---------------------------------------------------------------------------\n\nconst jobs = new Map<string, AppJob>();\n\n// ---------------------------------------------------------------------------\n// Exported helpers (testable in isolation)\n// ---------------------------------------------------------------------------\n\nexport function resolveAppsJsonPath(): string {\n return join(BREWNET_DIR, 'apps.json');\n}\n\n// ---------------------------------------------------------------------------\n// Gitea config cache — persists confirmed baseUrl/username so resolveContext()\n// doesn't need to re-derive from wizardState on every call.\n// Written at: wizard completion (generate.ts), Named Tunnel setup (admin-server.ts).\n// ---------------------------------------------------------------------------\n\ninterface GiteaConfig {\n baseUrl: string;\n username: string;\n writtenAt: string;\n}\n\nexport function loadGiteaConfig(): GiteaConfig | null {\n if (!existsSync(GITEA_CONFIG_PATH)) return null;\n try {\n return JSON.parse(readFileSync(GITEA_CONFIG_PATH, 'utf-8')) as GiteaConfig;\n } catch {\n return null;\n }\n}\n\nexport function saveGiteaConfig(baseUrl: string, username: string): void {\n try {\n const config: GiteaConfig = { baseUrl, username, writtenAt: new Date().toISOString() };\n writeFileSync(GITEA_CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8');\n } catch (e) {\n console.warn('[app-manager] gitea-config.json 저장 실패:', e instanceof Error ? e.message : e);\n }\n}\n\n/** Parse a single KEY=VALUE line from a .env file. Returns '' if not found. */\nexport function readDotEnvValue(envPath: string, key: string): string {\n if (!existsSync(envPath)) return '';\n const lines = readFileSync(envPath, 'utf-8').split('\\n');\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed.startsWith(`${key}=`)) {\n return trimmed.slice(key.length + 1).trim();\n }\n }\n return '';\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nlet _boilerplateRegistered = false;\n\nexport async function listApps(): Promise<AppEntry[]> {\n const appsJson = resolveAppsJsonPath();\n const apps = readApps(appsJson);\n\n // Auto-register wizard boilerplates (once per process lifetime).\n // This bridges the gap between `brewnet init` (writes .brewnet-boilerplate.json)\n // and the Apps page (reads apps.json).\n if (_boilerplateRegistered) return apps;\n _boilerplateRegistered = true;\n try {\n const ctx = resolveContext();\n const bpPath = join(ctx.projectPath, '.brewnet-boilerplate.json');\n if (existsSync(bpPath)) {\n const raw = JSON.parse(readFileSync(bpPath, 'utf-8'));\n const bpMetas: Array<{ stackId: string; appDir: string; lang?: string; frameworkId?: string; status?: string; backendUrl?: string }> =\n Array.isArray(raw) ? raw : [raw];\n let changed = false;\n for (const bp of bpMetas) {\n if (!bp.stackId || !bp.appDir) continue;\n // Check if already registered by stackId or appDir\n const exists = apps.some((a) => a.appDir === bp.appDir || a.stackId === bp.stackId);\n if (!exists) {\n const port = bp.backendUrl ? parseInt(new URL(bp.backendUrl).port || '8080', 10) : 8080;\n const entry: AppEntry = {\n name: bp.stackId,\n mode: 'boilerplate',\n stackId: bp.stackId,\n appDir: bp.appDir,\n lang: bp.lang,\n framework: bp.frameworkId,\n port,\n status: (bp.status as AppEntry['status']) || 'running',\n createdAt: new Date().toISOString(),\n };\n apps.push(entry);\n changed = true;\n }\n }\n if (changed) {\n writeFileSync(appsJson, JSON.stringify(apps, null, 2), 'utf-8');\n }\n }\n } catch { /* non-critical — boilerplate auto-register is best-effort */ }\n\n return apps;\n}\n\nexport function getDeployHistory(appName?: string): DeployHistoryEntry[] {\n const entries = readDeployHistory(DEPLOY_HISTORY_PATH);\n if (!appName) return entries;\n return entries.filter((e) => e.appName === appName);\n}\n\nexport async function listGiteaRepos(): Promise<GitRepoEntry[]> {\n const ctx = resolveContext();\n const gitea = new GiteaClient({\n baseUrl: ctx.giteaBaseUrl,\n username: ctx.giteaUser,\n password: ctx.giteaPassword,\n tokenPath: GITEA_TOKEN_PATH,\n });\n await gitea.prepare(); // validate/refresh token before listing\n return gitea.listRepos();\n}\n\nexport function getDeploySettings(appName: string): DeploySettings {\n const apps = readApps(resolveAppsJsonPath());\n const app = apps.find((a) => a.name === appName);\n const settings = (app as (AppEntry & { deploySettings?: DeploySettings }) | undefined)?.deploySettings;\n return settings ?? { autoDeploy: false, deployBranch: 'main' };\n}\n\nexport function updateDeploySettings(appName: string, settings: Partial<DeploySettings>): void {\n const appsJson = resolveAppsJsonPath();\n const apps = readApps(appsJson);\n const app = apps.find((a) => a.name === appName);\n if (!app) throw new Error(`App \"${appName}\" not found`);\n const existing = (app as AppEntry & { deploySettings?: DeploySettings }).deploySettings\n ?? { autoDeploy: false, deployBranch: 'main' };\n (app as AppEntry & { deploySettings?: DeploySettings }).deploySettings = { ...existing, ...settings };\n updateApp(appsJson, appName, app as Partial<AppEntry>);\n}\n\nexport async function getAppGitInfo(appName: string): Promise<AppGitInfo> {\n const ctx = resolveContext();\n const apps = readApps(resolveAppsJsonPath());\n const app = apps.find((a) => a.name === appName);\n if (!app) throw new Error(`App \"${appName}\" not found`);\n\n const gitea = new GiteaClient({\n baseUrl: ctx.giteaBaseUrl,\n username: ctx.giteaUser,\n password: ctx.giteaPassword,\n tokenPath: GITEA_TOKEN_PATH,\n });\n\n let branch = 'main';\n let latestCommit: AppGitInfo['latestCommit'] = null;\n let cloneUrlSsh = `ssh://git@localhost:2222/${ctx.giteaUser}/${appName}.git`;\n\n try {\n const repo = await gitea.getRepo(appName);\n branch = repo.default_branch || 'main';\n cloneUrlSsh = repo.ssh_url || cloneUrlSsh;\n // Auto-fix repos created before visibility change (private → public)\n if (repo.private) {\n await gitea.makeRepoPublic(appName).catch((e) => {\n console.warn(`[app-manager] makeRepoPublic failed for ${appName}: ${e instanceof Error ? e.message : String(e)}`);\n });\n }\n latestCommit = await gitea.getLatestCommit(appName, branch);\n } catch (e) {\n console.warn(`[app-manager] getAppGitInfo Gitea call failed (${appName}): ${e instanceof Error ? e.message : String(e)}`);\n }\n\n return {\n giteaUrl: `${ctx.giteaDisplayUrl}/${ctx.giteaUser}/${appName}`,\n cloneUrlHttp: `${ctx.giteaBaseUrl}/${ctx.giteaUser}/${appName}.git`,\n cloneUrlSsh,\n localPath: app.appDir,\n branch,\n latestCommit,\n };\n}\n\nexport async function rollbackApp(appName: string, commitHash: string): Promise<string> {\n const job = newJob(appName, ['Checkout', 'Build & Start', 'Health check']);\n jobs.set(job.jobId, job);\n setImmediate(() => void _runRollback(job, appName, commitHash));\n return job.jobId;\n}\n\nasync function _runRollback(job: AppJob, appName: string, commitHash: string): Promise<void> {\n try {\n const apps = readApps(resolveAppsJsonPath());\n const app = apps.find((a) => a.name === appName);\n if (!app) throw new Error(`App \"${appName}\" not found`);\n\n const target = commitHash || 'HEAD~1';\n setStep(job, 0, 'running', `git checkout ${target.slice(0, 7)}`);\n await execa('git', ['checkout', target], { cwd: app.appDir });\n setStep(job, 0, 'done');\n\n await _injectQuickTunnelIfNeeded(app.appDir, appName, app.port);\n\n setStep(job, 1, 'running', 'docker compose up --build');\n await _dockerComposeUp(app.appDir, job);\n setStep(job, 1, 'done', 'containers started');\n\n setStep(job, 2, 'running');\n const healthUrl = _buildHealthUrl(app.appDir, app.port);\n setStep(job, 2, 'running', `polling ${healthUrl}`);\n await _pollHealth(healthUrl, 120_000, job);\n setStep(job, 2, 'done');\n\n updateApp(resolveAppsJsonPath(), appName, { status: 'running' });\n appendDeployHistory(DEPLOY_HISTORY_PATH, {\n appName,\n commitHash,\n commitMessage: `Rollback to ${commitHash.slice(0, 7)}`,\n status: 'success',\n deployedAt: new Date().toISOString(),\n });\n\n job.status = 'done';\n } catch (err) {\n job.status = 'failed';\n job.error = err instanceof Error ? err.message : String(err);\n for (const step of job.steps) {\n if (step.status === 'running' || step.status === 'pending') step.status = 'failed';\n }\n appendDeployHistory(DEPLOY_HISTORY_PATH, {\n appName,\n commitHash,\n commitMessage: `Rollback to ${commitHash.slice(0, 7)}`,\n status: 'failed',\n deployedAt: new Date().toISOString(),\n });\n }\n}\n\nexport async function getAppBranches(appName: string): Promise<string[]> {\n const ctx = resolveContext();\n const gitea = new GiteaClient({\n baseUrl: ctx.giteaBaseUrl,\n username: ctx.giteaUser,\n password: ctx.giteaPassword,\n tokenPath: GITEA_TOKEN_PATH,\n });\n return gitea.listBranches(appName);\n}\n\nexport async function setupWebhook(appName: string, webhookUrl: string): Promise<void> {\n const ctx = resolveContext();\n const settings = getDeploySettings(appName);\n const secret = settings.webhookSecret ?? randomBytes(16).toString('hex');\n\n const gitea = new GiteaClient({\n baseUrl: ctx.giteaBaseUrl,\n username: ctx.giteaUser,\n password: ctx.giteaPassword,\n tokenPath: GITEA_TOKEN_PATH,\n });\n\n await gitea.createWebhook(appName, webhookUrl, secret);\n updateDeploySettings(appName, { webhookSecret: secret });\n}\n\nexport async function deployApp(appName: string): Promise<string> {\n const job = newJob(appName, ['Pull', 'Build & Start', 'Health check']);\n jobs.set(job.jobId, job);\n setImmediate(() => void _runDeploy(job, appName));\n return job.jobId;\n}\n\nasync function _runDeploy(job: AppJob, appName: string): Promise<void> {\n const apps = readApps(resolveAppsJsonPath());\n const app = apps.find((a) => a.name === appName);\n if (!app) { job.status = 'failed'; job.error = `App \"${appName}\" not found`; return; }\n try {\n const settings = getDeploySettings(appName);\n\n setStep(job, 0, 'running');\n try {\n const ctx = resolveContext();\n const gitea = new GiteaClient({\n baseUrl: ctx.giteaBaseUrl,\n username: ctx.giteaUser,\n password: ctx.giteaPassword,\n tokenPath: GITEA_TOKEN_PATH,\n });\n await gitea.prepare();\n const repoExists = await gitea.repoExists(appName);\n if (!repoExists) {\n appendLog(job, '[pull] Gitea repo not found — recreating and pushing local code');\n const cloneUrl = await gitea.createRepo(appName, `Brewnet app: ${appName}`);\n const authedUrl = gitea.authedCloneUrl(cloneUrl);\n await execa('git', ['remote', 'add', 'brewnet', authedUrl], { cwd: app.appDir }).catch(() =>\n execa('git', ['remote', 'set-url', 'brewnet', authedUrl], { cwd: app.appDir }),\n );\n await execa('git', ['push', 'brewnet', 'HEAD:main', '--force'], { cwd: app.appDir });\n appendLog(job, '[pull] Gitea repo recreated and code pushed ✓');\n } else if (!existsSync(app.appDir)) {\n // appDir was deleted — re-clone from Gitea\n appendLog(job, '[pull] appDir missing — cloning from Gitea');\n const authedUrl = gitea.authedCloneUrl(`${ctx.giteaBaseUrl}/${ctx.giteaUser}/${appName}.git`);\n await execa('git', ['clone', authedUrl, app.appDir]);\n appendLog(job, '[pull] re-cloned from Gitea ✓');\n } else if (await gitea.repoIsEmpty(appName)) {\n // Repo exists but was never pushed to (e.g. created during app setup but push was skipped)\n appendLog(job, '[pull] Gitea repo is empty — pushing local code');\n // Boilerplates are cloned --depth 1; unshallow before pushing to empty Gitea repo.\n // reinitGit wipes .git, so remote must be added AFTER this step (same order as _createModeA).\n const isShallow = await execa('git', ['rev-parse', '--is-shallow-repository'], { cwd: app.appDir })\n .then((r) => r.stdout.trim() === 'true').catch(() => false);\n if (isShallow) {\n appendLog(job, '[pull] shallow clone detected — unshallowing');\n await execa('git', ['fetch', '--unshallow', 'origin'], { cwd: app.appDir }).catch(async () => {\n const { reinitGit } = await import('./boilerplate-manager.js');\n await reinitGit(app.appDir);\n });\n }\n const authedUrl = gitea.authedCloneUrl(`${ctx.giteaBaseUrl}/${ctx.giteaUser}/${appName}.git`);\n await execa('git', ['remote', 'add', 'brewnet', authedUrl], { cwd: app.appDir }).catch(() =>\n execa('git', ['remote', 'set-url', 'brewnet', authedUrl], { cwd: app.appDir }),\n );\n await execa('git', ['push', 'brewnet', 'HEAD:main', '--force'], { cwd: app.appDir });\n appendLog(job, '[pull] code pushed to Gitea ✓');\n } else {\n await execa('git', ['pull', 'brewnet', settings.deployBranch], { cwd: app.appDir }).catch((e) => {\n appendLog(job, `[pull] git pull failed (non-critical): ${e instanceof Error ? e.message : String(e)}`);\n });\n }\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n appendLog(job, `[pull] Gitea sync failed (non-critical): ${msg}`);\n console.warn(`[app-manager] Gitea sync failed for \"${appName}\": ${msg}`);\n }\n setStep(job, 0, 'done');\n\n // Check if deployable — must have docker-compose.yml (or auto-scaffold)\n const hasCompose = existsSync(join(app.appDir, 'docker-compose.yml')) || existsSync(join(app.appDir, 'compose.yml'));\n if (!hasCompose) {\n // Try auto-scaffold for known project types\n const projectType = _detectProjectType(app.appDir);\n if (projectType) {\n appendLog(job, `[scaffold] Detected ${projectType} project — generating Docker config`);\n _scaffoldDockerConfig(app.appDir, appName, app.port, job, projectType);\n } else {\n throw new Error(\n 'This project has no docker-compose.yml or Dockerfile. ' +\n 'Add a Dockerfile and docker-compose.yml to deploy, or use a Brewnet boilerplate.',\n );\n }\n }\n\n // Inject Traefik Quick Tunnel labels (idempotent — safe to call even if labels already present)\n await _injectQuickTunnelIfNeeded(app.appDir, appName, app.port);\n\n setStep(job, 1, 'running', 'docker compose up --build');\n await _dockerComposeUp(app.appDir, job);\n setStep(job, 1, 'done', 'containers started');\n\n setStep(job, 2, 'running');\n const healthUrlDeploy = _buildHealthUrl(app.appDir, app.port);\n setStep(job, 2, 'running', `polling ${healthUrlDeploy}`);\n await _pollHealth(healthUrlDeploy, 120_000, job);\n setStep(job, 2, 'done');\n\n updateApp(resolveAppsJsonPath(), appName, { status: 'running' });\n const headHash = await execa('git', ['rev-parse', 'HEAD'], { cwd: app.appDir })\n .then((r) => r.stdout.trim()).catch(() => '');\n const headMsg = await execa('git', ['log', '-1', '--format=%s'], { cwd: app.appDir })\n .then((r) => r.stdout.trim()).catch(() => 'Manual deploy');\n appendDeployHistory(DEPLOY_HISTORY_PATH, {\n appName,\n commitHash: headHash,\n commitMessage: headMsg,\n status: 'success',\n deployedAt: new Date().toISOString(),\n });\n\n job.status = 'done';\n } catch (err) {\n job.status = 'failed';\n job.error = err instanceof Error ? err.message : String(err);\n for (const step of job.steps) {\n if (step.status === 'running' || step.status === 'pending') step.status = 'failed';\n }\n const headHashFail = await execa('git', ['rev-parse', 'HEAD'], { cwd: app.appDir })\n .then((r) => r.stdout.trim()).catch(() => '');\n appendDeployHistory(DEPLOY_HISTORY_PATH, {\n appName,\n commitHash: headHashFail,\n commitMessage: 'Manual deploy',\n status: 'failed',\n deployedAt: new Date().toISOString(),\n });\n }\n}\n\nexport function getAppDir(appName: string): string | undefined {\n const apps = readApps(resolveAppsJsonPath());\n return apps.find((a) => a.name === appName)?.appDir;\n}\n\nexport function getJobStatus(jobId: string): AppJob | undefined {\n return jobs.get(jobId);\n}\n\n// ---------------------------------------------------------------------------\n// Internal: job helpers\n// ---------------------------------------------------------------------------\n\nfunction newJob(appName: string, stepLabels: string[]): AppJob {\n return {\n jobId: randomBytes(8).toString('hex'),\n appName,\n status: 'running',\n steps: stepLabels.map((label) => ({ label, status: 'pending' })),\n };\n}\n\nfunction setStep(job: AppJob, index: number, status: AppJobStep['status'], message?: string): void {\n const step = job.steps[index];\n if (step) { step.status = status; if (message) step.message = message; }\n}\n\n/** Append a log line to the job's rolling log buffer (max 200 lines). */\nfunction appendLog(job: AppJob, line: string): void {\n if (!job.logs) job.logs = [];\n job.logs.push(line);\n if (job.logs.length > 200) job.logs.splice(0, job.logs.length - 200);\n}\n\n// ---------------------------------------------------------------------------\n// Internal: read boilerplate meta from project\n// ---------------------------------------------------------------------------\n\ninterface BoilerplateMeta {\n stackId: string;\n appDir: string;\n backendUrl: string;\n port?: number;\n lang: string;\n frameworkId: string;\n status: string;\n}\n\nfunction readBoilerplateMeta(projectPath: string): BoilerplateMeta[] {\n const p = join(projectPath, '.brewnet-boilerplate.json');\n if (!existsSync(p)) return [];\n try {\n const raw = JSON.parse(readFileSync(p, 'utf-8'));\n return Array.isArray(raw) ? raw : [raw];\n } catch { return []; }\n}\n\n// ---------------------------------------------------------------------------\n// Internal: resolve wizard context\n// ---------------------------------------------------------------------------\n\ninterface AppContext {\n projectPath: string;\n giteaBaseUrl: string; // Internal URL for API calls — always http://localhost/git\n giteaDisplayUrl: string; // URL for display/links — external URL in Named Tunnel mode\n giteaUser: string;\n giteaPassword: string;\n}\n\nfunction resolveContext(): AppContext {\n // Read from persistent cache first — avoids fragile re-derivation from wizardState\n // on every call. Cache is written at wizard completion and Named Tunnel setup.\n const cached = loadGiteaConfig();\n\n const last = getLastProject();\n const state = loadState(last ?? '');\n const raw = (state as { projectPath?: string } | null)?.projectPath ?? process.cwd();\n const projectPath = raw.startsWith('~') ? join(homedir(), raw.slice(1)) : raw;\n\n const envPath = join(projectPath, '.env');\n const giteaUser =\n cached?.username ??\n (readDotEnvValue(envPath, 'GITEA_ADMIN_USER') ||\n (state?.admin as { username?: string } | undefined)?.username ||\n 'admin');\n\n // GITEA_ADMIN_PASSWORD is a Docker secret, NOT in .env — read from secrets file first\n const secretsPath = join(projectPath, 'secrets', 'admin_password');\n const secretsPassword = existsSync(secretsPath) ? readFileSync(secretsPath, 'utf-8').trim() : '';\n const giteaPassword =\n secretsPassword ||\n readDotEnvValue(envPath, 'GITEA_ADMIN_PASSWORD') ||\n (state?.admin as { password?: string } | undefined)?.password ||\n '';\n\n // Internal API URL: always local Traefik proxy — stable regardless of tunnel state.\n const giteaBaseUrl = 'http://localhost/git';\n\n // Display URL: use external subdomain in Named Tunnel mode so dashboard links open correctly.\n // In Named Tunnel mode, Gitea's ROOT_URL has no /git subpath, so local /git/ auth redirects\n // break in the browser. The external URL https://git.<zone>/ works correctly.\n const tunnelMode = state?.domain?.cloudflare?.tunnelMode ?? '';\n const zoneName = state?.domain?.cloudflare?.zoneName ?? '';\n const giteaDisplayUrl =\n tunnelMode === 'named' && zoneName ? `https://git.${zoneName}` : giteaBaseUrl;\n\n return { projectPath, giteaBaseUrl, giteaDisplayUrl, giteaUser, giteaPassword };\n}\n\n/** Inject Traefik Quick Tunnel labels if running in quick tunnel mode. */\nasync function _injectQuickTunnelIfNeeded(appDir: string, appName: string, port: number): Promise<void> {\n try {\n const last = getLastProject();\n const state = loadState(last ?? '');\n if (state?.domain?.cloudflare?.tunnelMode !== 'quick') return;\n const { injectTraefikForQuickTunnel } = await import('./boilerplate-manager.js');\n injectTraefikForQuickTunnel(appDir, appName, port);\n } catch (err) {\n // Log but don't fail — external access simply won't work\n console.error(`[Quick Tunnel] Failed to inject Traefik labels for ${appName}: ${err instanceof Error ? err.message : String(err)}`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Internal: simple health poll\n// ---------------------------------------------------------------------------\n\n/**\n * Detect project type from files in the directory.\n */\nfunction _detectProjectType(dir: string): 'nextjs' | 'nodejs' | 'python' | 'go' | 'rust' | 'java' | 'static' | null {\n try {\n if (existsSync(join(dir, 'next.config.ts')) || existsSync(join(dir, 'next.config.mjs')) || existsSync(join(dir, 'next.config.js'))) return 'nextjs';\n if (existsSync(join(dir, 'package.json'))) return 'nodejs';\n if (existsSync(join(dir, 'requirements.txt')) || existsSync(join(dir, 'pyproject.toml'))) return 'python';\n if (existsSync(join(dir, 'go.mod'))) return 'go';\n if (existsSync(join(dir, 'Cargo.toml'))) return 'rust';\n if (existsSync(join(dir, 'pom.xml')) || existsSync(join(dir, 'build.gradle')) || existsSync(join(dir, 'build.gradle.kts'))) return 'java';\n // Static HTML — fallback if index.html exists or any .html files\n if (existsSync(join(dir, 'index.html'))) return 'static';\n if (readdirSync(dir).some((f: string) => f.endsWith('.html'))) return 'static';\n } catch { /* ignore */ }\n return null;\n}\n\n/**\n * Generate Dockerfile + docker-compose.yml for projects that don't have them.\n */\nfunction _scaffoldDockerConfig(dir: string, _appName: string, port: number, job?: AppJob, detectedType?: string): void {\n const type = detectedType || _detectProjectType(dir);\n if (!type) throw new Error(`Cannot auto-detect project type in ${dir}. Add a Dockerfile and docker-compose.yml manually.`);\n\n if (job && !detectedType) appendLog(job, `[scaffold] Detected ${type} project — generating Docker config`);\n\n let dockerfile = '';\n\n switch (type) {\n case 'nextjs':\n // Use simple single-stage build — external Next.js projects may not\n // have output:'standalone' configured, and modifying their config\n // can break cached Docker layers. Just npm install + build + start.\n dockerfile = [\n 'FROM node:22-alpine',\n 'WORKDIR /app',\n 'COPY package.json package-lock.json* pnpm-lock.yaml* yarn.lock* ./',\n 'RUN npm install --legacy-peer-deps 2>/dev/null || yarn install 2>/dev/null || true',\n 'COPY . .',\n 'RUN npm run build',\n 'ENV PORT=3000 HOSTNAME=0.0.0.0',\n 'EXPOSE 3000',\n 'CMD [\"npm\", \"start\"]',\n ].join('\\n');\n break;\n case 'nodejs':\n dockerfile = [\n 'FROM node:22-alpine',\n 'WORKDIR /app',\n 'COPY package.json package-lock.json* pnpm-lock.yaml* yarn.lock* ./',\n 'RUN npm install --legacy-peer-deps || true',\n 'COPY . .',\n 'RUN npm run build 2>/dev/null || true',\n 'EXPOSE ' + port,\n 'CMD [\"npm\", \"start\"]',\n ].join('\\n');\n break;\n case 'python':\n dockerfile = [\n 'FROM python:3.13-slim',\n 'WORKDIR /app',\n 'COPY requirements.txt* pyproject.toml* ./',\n 'RUN pip install --no-cache-dir -r requirements.txt 2>/dev/null || pip install --no-cache-dir . 2>/dev/null || true',\n 'COPY . .',\n 'EXPOSE ' + port,\n 'CMD [\"python\", \"-m\", \"uvicorn\", \"main:app\", \"--host\", \"0.0.0.0\", \"--port\", \"' + port + '\"]',\n ].join('\\n');\n break;\n case 'go':\n dockerfile = [\n 'FROM golang:1.22-alpine AS builder',\n 'WORKDIR /app',\n 'COPY go.mod go.sum* ./',\n 'RUN go mod download',\n 'COPY . .',\n 'RUN CGO_ENABLED=0 go build -o server .',\n '',\n 'FROM alpine',\n 'WORKDIR /app',\n 'COPY --from=builder /app/server .',\n 'EXPOSE ' + port,\n 'CMD [\"./server\"]',\n ].join('\\n');\n break;\n case 'rust':\n dockerfile = [\n 'FROM rust:1.88 AS builder',\n 'WORKDIR /app',\n 'COPY . .',\n 'RUN cargo build --release',\n '',\n 'FROM debian:bookworm-slim',\n 'WORKDIR /app',\n 'COPY --from=builder /app/target/release/* /app/ 2>/dev/null || true',\n 'EXPOSE ' + port,\n 'CMD [\"./app\"]',\n ].join('\\n');\n break;\n case 'java':\n dockerfile = [\n 'FROM gradle:8.12-jdk21 AS builder',\n 'WORKDIR /app',\n 'COPY . .',\n 'RUN gradle build -x test 2>/dev/null || ./gradlew build -x test 2>/dev/null || mvn package -DskipTests 2>/dev/null || true',\n '',\n 'FROM eclipse-temurin:21-jre-alpine',\n 'WORKDIR /app',\n 'COPY --from=builder /app/build/libs/*.jar app.jar 2>/dev/null || true',\n 'COPY --from=builder /app/target/*.jar app.jar 2>/dev/null || true',\n 'EXPOSE ' + port,\n 'CMD [\"java\", \"-jar\", \"app.jar\"]',\n ].join('\\n');\n break;\n case 'static':\n dockerfile = [\n 'FROM nginx:1.27-alpine',\n 'COPY . /usr/share/nginx/html/',\n 'EXPOSE 80',\n ].join('\\n');\n break;\n }\n\n const internalPort = type === 'nextjs' ? 3000 : type === 'static' ? 80 : port;\n const compose = [\n 'services:',\n ' backend:',\n ' build: .',\n ' ports:',\n ` - \"${port}:${internalPort}\"`,\n ' restart: unless-stopped',\n ' healthcheck:',\n ` test: [\"CMD\", \"wget\", \"-q\", \"-O\", \"/dev/null\", \"http://127.0.0.1:${internalPort}/\"]`,\n ' interval: 10s',\n ' timeout: 5s',\n ' retries: 5',\n ].join('\\n');\n\n if (!existsSync(join(dir, 'Dockerfile'))) {\n writeFileSync(join(dir, 'Dockerfile'), dockerfile, 'utf-8');\n if (job) appendLog(job, '[scaffold] Generated Dockerfile');\n }\n writeFileSync(join(dir, 'docker-compose.yml'), compose, 'utf-8');\n if (job) appendLog(job, '[scaffold] Generated docker-compose.yml');\n\n // .dockerignore\n if (!existsSync(join(dir, '.dockerignore'))) {\n writeFileSync(join(dir, '.dockerignore'), 'node_modules\\n.next\\n.git\\n*.md\\n', 'utf-8');\n }\n}\n\n/**\n * Ensure a docker-compose.yml exists in `dir`.\n * If missing, auto-detect project type and scaffold Dockerfile + compose.\n */\nfunction ensureComposeFile(dir: string, appName: string, port: number, job?: AppJob): void {\n if (existsSync(join(dir, 'docker-compose.yml')) || existsSync(join(dir, 'compose.yml'))) return;\n _scaffoldDockerConfig(dir, appName, port, job);\n}\n\n/**\n * Resolve the backend health check port for a given app directory.\n * Reads BACKEND_PORT from .env (set by generateEnv).\n * Falls back to the provided port if .env is absent or has no BACKEND_PORT.\n * This ensures health checks always target the backend, not the frontend nginx.\n */\nfunction _resolveBackendPort(appDir: string, fallbackPort: number): number {\n const envPath = join(appDir, '.env');\n const val = readDotEnvValue(envPath, 'BACKEND_PORT');\n const parsed = val ? parseInt(val, 10) : NaN;\n return isNaN(parsed) ? fallbackPort : parsed;\n}\n\n/**\n * Detect Next.js basePath from next.config.ts/mjs/js in the app directory.\n * Returns the basePath string (e.g. '/apps/my-app') or '' if not set.\n * Next.js bakes basePath at build time — /health becomes /apps/my-app/health.\n */\nexport function detectBasePath(appDir: string): string {\n for (const name of ['next.config.ts', 'next.config.mjs', 'next.config.js']) {\n const p = join(appDir, name);\n if (existsSync(p)) {\n const content = readFileSync(p, 'utf-8');\n const match = content.match(/basePath\\s*:\\s*['\"`]([^'\"`]+)['\"`]/);\n if (match) return match[1]!;\n }\n }\n return '';\n}\n\n/**\n * Build the health check URL for an app.\n * - Brewnet boilerplates have /health endpoint → use /health\n * - Scaffolded/general projects → use / (root)\n * - Next.js with basePath → prefix accordingly\n */\nfunction _buildHealthUrl(appDir: string, fallbackPort: number): string {\n const healthPort = _resolveBackendPort(appDir, fallbackPort);\n const basePath = detectBasePath(appDir);\n // Check if this is a brewnet boilerplate (has .env.example with STACK_LANG)\n // or has an explicit /health route file\n const isBoilerplate = existsSync(join(appDir, '.env.example'))\n && readFileSync(join(appDir, '.env.example'), 'utf-8').includes('STACK_LANG');\n const hasHealthRoute = existsSync(join(appDir, 'src', 'app', 'health'))\n || existsSync(join(appDir, 'backend', 'src'));\n const healthPath = (isBoilerplate || hasHealthRoute) ? '/health' : '/';\n return `http://127.0.0.1:${healthPort}${basePath}${healthPath}`;\n}\n\nasync function _pollHealth(url: string, maxMs = 120_000, job?: AppJob): Promise<void> {\n const deadline = Date.now() + maxMs;\n let attempt = 0;\n while (Date.now() < deadline) {\n attempt++;\n try {\n const res = await fetch(url, { signal: AbortSignal.timeout(5000) });\n if (res.ok) {\n if (job) appendLog(job, `[health] ✓ ${url} → ${res.status} (attempt ${attempt})`);\n return;\n }\n if (job) appendLog(job, `[health] ${url} → ${res.status} (attempt ${attempt})`);\n } catch {\n if (job && attempt % 3 === 1) appendLog(job, `[health] waiting... ${url} (attempt ${attempt})`);\n }\n await new Promise((r) => setTimeout(r, 3000));\n }\n if (job) appendLog(job, `[health] ✗ timeout after ${maxMs / 1000}s`);\n throw new Error(`Health check timed out after ${maxMs / 1000}s: ${url}`);\n}\n\n/**\n * Run `docker compose up -d --build` with stdout/stderr streamed to job logs.\n */\nasync function _dockerComposeUp(cwd: string, job: AppJob): Promise<void> {\n appendLog(job, `[docker] $ docker compose up -d --build`);\n appendLog(job, `[docker] cwd: ${cwd}`);\n const proc = execa('docker', ['compose', 'up', '-d', '--build'], { cwd, reject: false });\n proc.stdout?.on('data', (chunk: Buffer) => {\n for (const line of chunk.toString().split('\\n').filter(Boolean)) {\n appendLog(job, `[docker] ${line}`);\n }\n });\n proc.stderr?.on('data', (chunk: Buffer) => {\n for (const line of chunk.toString().split('\\n').filter(Boolean)) {\n appendLog(job, `[docker] ${line}`);\n }\n });\n const result = await proc;\n if (result.exitCode !== 0) {\n appendLog(job, `[docker] ✗ exit code ${result.exitCode}`);\n throw new Error(`Command failed with exit code ${result.exitCode}: docker compose up -d --build\\n${result.stderr}`);\n }\n appendLog(job, `[docker] ✓ containers started`);\n}\n\n// ---------------------------------------------------------------------------\n// Public: createApp\n// ---------------------------------------------------------------------------\n\nexport async function createApp(opts: CreateAppOptions): Promise<string> {\n const job = newJob(opts.appName, ['Validating', 'Gitea setup', 'Gitea repo', 'Git push', 'Docker up', 'Health check']);\n jobs.set(job.jobId, job);\n\n // Run async — caller polls via getJobStatus\n setImmediate(() => void _runCreateApp(job, opts));\n\n return job.jobId;\n}\n\nasync function _runCreateApp(job: AppJob, opts: CreateAppOptions): Promise<void> {\n try {\n const ctx = resolveContext();\n const appsJson = resolveAppsJsonPath();\n\n // Step 0: Validating — mode-specific pre-checks\n setStep(job, 0, 'running');\n if (opts.mode === 'boilerplate') {\n if (!opts.stackId) throw new Error('stackId is required for boilerplate mode');\n const metas = readBoilerplateMeta(ctx.projectPath);\n const meta = metas.find((m) => m.stackId === opts.stackId);\n if (meta) {\n // Installed locally — use fast local copy path\n (opts as CreateAppOptions & { _meta?: unknown })._meta = meta;\n } else {\n // Not installed locally — fall back to fresh clone from catalog\n appendLog(job, `[info] Stack \"${opts.stackId}\" not installed locally — cloning fresh from catalog`);\n (opts as CreateAppOptions & { _resolvedStackId?: string })._resolvedStackId = opts.stackId;\n }\n } else if (opts.mode === 'git-clone') {\n if (!opts.gitUrl) throw new Error('gitUrl is required for Git Clone mode');\n } else if (opts.mode === 'new-project') {\n const { resolveStackId } = await import('../config/frameworks.js');\n const stackId = resolveStackId(opts.language ?? 'nodejs', opts.frameworkId ?? 'express');\n if (!stackId) throw new Error(`Unknown stack: ${opts.language}/${opts.frameworkId}`);\n (opts as CreateAppOptions & { _resolvedStackId?: string })._resolvedStackId = stackId;\n }\n setStep(job, 0, 'done');\n\n // Step 1: Gitea setup — ensure token exists, auto-fix mustChangePassword if needed\n setStep(job, 1, 'running');\n const gitea = new GiteaClient({\n baseUrl: ctx.giteaBaseUrl,\n username: ctx.giteaUser,\n password: ctx.giteaPassword,\n tokenPath: GITEA_TOKEN_PATH,\n });\n const giteaPrep = await gitea.prepare();\n setStep(job, 1, 'done', giteaPrep.message);\n\n if (opts.mode === 'boilerplate' && (opts as CreateAppOptions & { _meta?: unknown })._meta) {\n await _createModeA(job, opts, ctx, gitea, appsJson);\n } else if (opts.mode === 'git-clone') {\n await _createModeB(job, opts, ctx, gitea, appsJson);\n } else {\n // new-project OR boilerplate fallback (stack not installed locally)\n await _createModeC(job, opts, ctx, gitea, appsJson);\n }\n\n job.status = 'done';\n } catch (err) {\n job.status = 'failed';\n job.error = err instanceof Error ? err.message : String(err);\n for (const step of job.steps) {\n if (step.status === 'running' || step.status === 'pending') step.status = 'failed';\n }\n }\n}\n\nasync function _createModeA(\n job: AppJob,\n opts: CreateAppOptions,\n ctx: AppContext,\n gitea: GiteaClient,\n appsJson: string,\n): Promise<void> {\n // Step 0 already done in _runCreateApp — retrieve validated meta\n const meta = (opts as CreateAppOptions & { _meta?: BoilerplateMeta })._meta!;\n const port = opts.port ?? meta.port ?? parseInt(meta.backendUrl.split(':').pop() ?? '8080', 10);\n\n // Step 2: Gitea repo\n setStep(job, 2, 'running', `checking ${ctx.giteaUser}/${opts.appName}`);\n const alreadyExists = await gitea.repoExists(opts.appName);\n let cloneUrl: string;\n if (!alreadyExists) {\n setStep(job, 2, 'running', `creating ${ctx.giteaUser}/${opts.appName}`);\n cloneUrl = await gitea.createRepo(opts.appName, `Brewnet app: ${opts.appName}`);\n } else {\n cloneUrl = `${ctx.giteaBaseUrl}/${ctx.giteaUser}/${opts.appName}.git`;\n }\n setStep(job, 2, 'done');\n\n // Step 3: Git remote + push\n setStep(job, 3, 'running', `pushing HEAD:main → ${ctx.giteaUser}/${opts.appName}`);\n // Boilerplates are cloned --depth 1; Gitea rejects shallow pushes to empty repos.\n // Unshallow first (try origin), fall back to a fresh git init if origin is unreachable.\n const shallowCheck = await execa('git', ['rev-parse', '--is-shallow-repository'], { cwd: meta.appDir }).catch(() => ({ stdout: 'false' }));\n if (shallowCheck.stdout.trim() === 'true') {\n await execa('git', ['fetch', '--unshallow', 'origin'], { cwd: meta.appDir }).catch(async () => {\n const { reinitGit } = await import('./boilerplate-manager.js');\n await reinitGit(meta.appDir);\n });\n }\n const authedUrl = gitea.authedCloneUrl(cloneUrl);\n await execa('git', ['remote', 'add', 'brewnet', authedUrl], { cwd: meta.appDir }).catch(() => {\n return execa('git', ['remote', 'set-url', 'brewnet', authedUrl], { cwd: meta.appDir });\n });\n await execa('git', ['push', 'brewnet', 'HEAD:main', '--force'], { cwd: meta.appDir });\n setStep(job, 3, 'done');\n\n // Step 4: Docker up\n setStep(job, 4, 'running', 'docker compose up --build');\n ensureComposeFile(meta.appDir, opts.appName, port, job);\n await _injectQuickTunnelIfNeeded(meta.appDir, opts.appName, port);\n await _dockerComposeUp(meta.appDir, job);\n setStep(job, 4, 'done', 'containers started');\n\n // Step 5: Health check — accounts for Next.js basePath\n setStep(job, 5, 'running');\n const healthUrlA = _buildHealthUrl(meta.appDir, port);\n setStep(job, 5, 'running', `polling ${healthUrlA}`);\n await _pollHealth(healthUrlA, 120_000, job);\n setStep(job, 5, 'done');\n\n // Register\n addApp(appsJson, {\n name: opts.appName,\n mode: 'boilerplate',\n stackId: opts.stackId,\n appDir: meta.appDir,\n lang: meta.lang,\n framework: meta.frameworkId,\n port,\n giteaRepoUrl: `${ctx.giteaDisplayUrl}/${ctx.giteaUser}/${opts.appName}`,\n status: 'running',\n createdAt: new Date().toISOString(),\n });\n // Register Gitea webhook for auto-deploy (non-blocking)\n await setupWebhook(opts.appName, 'http://localhost:8088/api/deploy/hook').catch((e: unknown) => {\n console.warn('[webhook] registration failed (non-critical):', e instanceof Error ? e.message : String(e));\n });\n}\n\nasync function _createModeB(\n job: AppJob,\n opts: CreateAppOptions,\n ctx: AppContext,\n gitea: GiteaClient,\n appsJson: string,\n): Promise<void> {\n // Step 0 already done in _runCreateApp\n const port = opts.port ?? 8080;\n const appDir = join(ctx.projectPath, 'apps', opts.appName);\n\n // Step 2: Clone external repo + create Gitea repo\n setStep(job, 2, 'running', 'Cloning external repository...');\n const { reinitGit: reinitGitB } = await import('./boilerplate-manager.js');\n // Clean existing directory from a previous failed run\n const { rmSync } = await import('node:fs');\n if (existsSync(appDir)) {\n rmSync(appDir, { recursive: true, force: true });\n }\n const cloneArgs = ['clone', '--depth', '1'];\n if (opts.branch) cloneArgs.push('-b', opts.branch);\n cloneArgs.push(opts.gitUrl!, appDir);\n await execa('git', cloneArgs);\n appendLog(job, `[clone] ${opts.gitUrl} → ${opts.appName} ✓`);\n // Inject user-specified ports into .env so docker-compose picks them up\n // (prevents \"port already allocated\" when default 8080/3000 are in use)\n const envExPath = join(appDir, '.env.example');\n const envPath = join(appDir, '.env');\n if (existsSync(envExPath)) {\n const { findFreePort: findFreePortB } = await import('./boilerplate-manager.js');\n let envContent = readFileSync(envExPath, 'utf-8');\n envContent = envContent.replace(/^BACKEND_PORT=.*/m, `BACKEND_PORT=${port}`);\n // Start frontend port search AFTER the backend port to avoid collision\n const fePort = await findFreePortB(port + 1);\n envContent = envContent.replace(/^FRONTEND_PORT=.*/m, `FRONTEND_PORT=${fePort}`);\n writeFileSync(envPath, envContent, 'utf-8');\n } else if (existsSync(join(appDir, '.env'))) {\n let envContent = readFileSync(join(appDir, '.env'), 'utf-8');\n envContent = envContent.replace(/^BACKEND_PORT=.*/m, `BACKEND_PORT=${port}`);\n writeFileSync(join(appDir, '.env'), envContent, 'utf-8');\n }\n await reinitGitB(appDir);\n setStep(job, 2, 'running', `Creating Gitea repo ${ctx.giteaUser}/${opts.appName}…`);\n const alreadyExists = await gitea.repoExists(opts.appName);\n const cloneUrl = alreadyExists\n ? `${ctx.giteaBaseUrl}/${ctx.giteaUser}/${opts.appName}.git`\n : await gitea.createRepo(opts.appName, `Brewnet app: ${opts.appName}`);\n setStep(job, 2, 'done');\n\n setStep(job, 3, 'running');\n const authedUrl = gitea.authedCloneUrl(cloneUrl);\n await execa('git', ['remote', 'add', 'brewnet', authedUrl], { cwd: appDir });\n await execa('git', ['push', 'brewnet', 'HEAD:main', '--force'], { cwd: appDir });\n setStep(job, 3, 'done');\n\n // Git Clone mode: auto-scaffold docker-compose.yml if repo has none.\n // Handles static sites (nginx), plain Node.js/Python projects, etc.\n ensureComposeFile(appDir, opts.appName, port, job);\n\n const hasCompose = existsSync(join(appDir, 'docker-compose.yml')) || existsSync(join(appDir, 'compose.yml'));\n if (hasCompose) {\n setStep(job, 4, 'running', 'docker compose up --build');\n await _injectQuickTunnelIfNeeded(appDir, opts.appName, port);\n await _dockerComposeUp(appDir, job);\n setStep(job, 4, 'done', 'containers started');\n\n setStep(job, 5, 'running');\n const healthUrlB = _buildHealthUrl(appDir, port);\n setStep(job, 5, 'running', `polling ${healthUrlB}`);\n await _pollHealth(healthUrlB, 120_000, job);\n setStep(job, 5, 'done');\n } else {\n setStep(job, 4, 'done', 'skipped — no docker-compose.yml');\n setStep(job, 5, 'done', 'skipped — deploy separately');\n appendLog(job, '[clone] Gitea push completed — no docker-compose.yml, skipping Docker up');\n }\n\n addApp(appsJson, {\n name: opts.appName,\n mode: 'git-clone',\n sourceUrl: opts.gitUrl,\n appDir,\n port,\n giteaRepoUrl: `${ctx.giteaDisplayUrl}/${ctx.giteaUser}/${opts.appName}`,\n status: hasCompose ? 'running' : 'stopped',\n createdAt: new Date().toISOString(),\n });\n}\n\nasync function _createModeC(\n job: AppJob,\n opts: CreateAppOptions,\n ctx: AppContext,\n gitea: GiteaClient,\n appsJson: string,\n): Promise<void> {\n const { cloneStack, generateEnv, reinitGit, findFreePort } = await import('./boilerplate-manager.js');\n const { getStackById } = await import('../config/stacks.js');\n\n // Step 0 already done in _runCreateApp — retrieve resolved stackId\n const stackId = (opts as CreateAppOptions & { _resolvedStackId?: string })._resolvedStackId!;\n const requestedPort = opts.port ?? 8080;\n const appDir = join(ctx.projectPath, 'apps', opts.appName);\n\n // Clone and scaffold (visible as part of Gitea repo step context)\n await cloneStack(stackId, appDir);\n // Auto-detect free host port starting from the requested port to prevent\n // \"port already allocated\" errors when other apps occupy the requested port.\n const port = await findFreePort(requestedPort);\n const stackInfo = getStackById(stackId);\n const frontendPort = (stackInfo && !stackInfo.isUnified) ? await findFreePort(port + 1) : undefined;\n generateEnv(appDir, stackId, 'sqlite3', { hostPort: port, frontendPort });\n await reinitGit(appDir);\n\n setStep(job, 2, 'running');\n const cloneUrl = await gitea.createRepo(opts.appName, `Brewnet app: ${opts.appName}`);\n setStep(job, 2, 'done');\n\n setStep(job, 3, 'running');\n const authedUrl = gitea.authedCloneUrl(cloneUrl);\n await execa('git', ['remote', 'add', 'brewnet', authedUrl], { cwd: appDir });\n await execa('git', ['push', 'brewnet', 'HEAD:main', '--force'], { cwd: appDir });\n setStep(job, 3, 'done');\n\n setStep(job, 4, 'running', 'docker compose up --build');\n ensureComposeFile(appDir, opts.appName, port, job);\n await _injectQuickTunnelIfNeeded(appDir, opts.appName, port);\n await _dockerComposeUp(appDir, job);\n setStep(job, 4, 'done', 'containers started');\n\n setStep(job, 5, 'running');\n const healthUrlC = _buildHealthUrl(appDir, port);\n setStep(job, 5, 'running', `polling ${healthUrlC}`);\n await _pollHealth(healthUrlC, 120_000, job);\n setStep(job, 5, 'done');\n\n addApp(appsJson, {\n name: opts.appName,\n mode: opts.mode === 'boilerplate' ? 'boilerplate' : 'new-project',\n stackId,\n appDir,\n lang: opts.language ?? stackInfo?.language,\n framework: opts.frameworkId ?? stackInfo?.framework,\n port,\n giteaRepoUrl: `${ctx.giteaDisplayUrl}/${ctx.giteaUser}/${opts.appName}`,\n status: 'running',\n createdAt: new Date().toISOString(),\n });\n}\n\nexport async function startApp(appName: string): Promise<void> {\n const appsJson = resolveAppsJsonPath();\n const apps = readApps(appsJson);\n const app = apps.find((a) => a.name === appName);\n if (!app) throw new Error(`App \"${appName}\" not found`);\n await execa('docker', ['compose', 'up', '-d'], { cwd: app.appDir });\n updateApp(appsJson, appName, { status: 'running' });\n}\n\nexport async function stopApp(appName: string): Promise<void> {\n const appsJson = resolveAppsJsonPath();\n const apps = readApps(appsJson);\n const app = apps.find((a) => a.name === appName);\n if (!app) throw new Error(`App \"${appName}\" not found`);\n await execa('docker', ['compose', 'down'], { cwd: app.appDir }).catch((e: unknown) => {\n console.warn('[stopApp] docker compose down failed:', e instanceof Error ? e.message : String(e));\n });\n updateApp(appsJson, appName, { status: 'stopped' });\n}\n\nexport async function removeApp(appName: string): Promise<void> {\n const appsJson = resolveAppsJsonPath();\n const apps = readApps(appsJson);\n const app = apps.find((a) => a.name === appName);\n if (!app) throw new Error(`App \"${appName}\" not found`);\n await execa('docker', ['compose', 'down', '--volumes'], { cwd: app.appDir }).catch((e: unknown) => {\n console.warn('[removeApp] docker compose down failed:', e instanceof Error ? e.message : String(e));\n });\n registryRemoveApp(appsJson, appName);\n}\n","// packages/cli/src/services/gitea-client.ts\nimport { existsSync, readFileSync, writeFileSync, chmodSync, mkdirSync, unlinkSync } from 'node:fs';\nimport { dirname } from 'node:path';\nimport { execSync } from 'node:child_process';\nimport type { GitRepoEntry } from '../types/app-entry.js';\n\nexport interface GiteaClientConfig {\n /** Full base URL without trailing slash, e.g. \"http://localhost/git\" (via Traefik) */\n baseUrl: string;\n username: string;\n password: string;\n /** Path to persist the API token, e.g. ~/.brewnet/gitea-token */\n tokenPath: string;\n}\n\nexport class GiteaClient {\n private config: GiteaClientConfig;\n\n constructor(config: GiteaClientConfig) {\n this.config = config;\n }\n\n // ---------------------------------------------------------------------------\n // Token management\n // ---------------------------------------------------------------------------\n\n /**\n * Create a Gitea API token via Basic Auth.\n * If the admin account has mustChangePassword=true (403), auto-fixes via docker exec and retries.\n * Saves the token to tokenPath on success.\n */\n private async _createToken(): Promise<{ wasFixed: boolean }> {\n const { tokenPath, baseUrl, username, password } = this.config;\n const basic = Buffer.from(`${username}:${password}`).toString('base64');\n\n const makeRequest = () =>\n fetch(`${baseUrl}/api/v1/users/${username}/tokens`, {\n method: 'POST',\n headers: { Authorization: `Basic ${basic}`, 'Content-Type': 'application/json' },\n body: JSON.stringify({\n name: `brewnet-${Date.now()}`,\n scopes: ['write:repository', 'read:repository', 'write:user', 'read:user'],\n }),\n signal: AbortSignal.timeout(8000),\n });\n\n let res = await makeRequest();\n let wasFixed = false;\n\n if (!res.ok) {\n const body = await res.text();\n if (res.status === 403 && body.includes('must change')) {\n // Auto-fix: reset mustChangePassword via docker exec, then retry\n try {\n execSync(\n `docker exec -u git brewnet-gitea gitea admin user change-password` +\n ` --username ${username} --password ${password} --must-change-password=false`,\n { stdio: 'pipe' },\n );\n } catch (e) {\n const stderr = (e as { stderr?: Buffer }).stderr?.toString().trim() ?? String(e);\n throw new Error(\n `Gitea admin requires password change — auto-fix failed:\\n ${stderr}\\n` +\n ` Manual fix: docker exec -u git brewnet-gitea gitea admin user change-password` +\n ` --username ${username} --password <password> --must-change-password=false`,\n );\n }\n wasFixed = true;\n res = await makeRequest();\n if (!res.ok) {\n throw new Error(\n `Gitea token creation failed after auto-fix: ${res.status} ${await res.text()}`,\n );\n }\n } else {\n throw new Error(`Gitea token creation failed: ${res.status} ${body}`);\n }\n }\n\n const data = (await res.json()) as { sha1: string };\n mkdirSync(dirname(tokenPath), { recursive: true });\n writeFileSync(tokenPath, data.sha1, 'utf-8');\n chmodSync(tokenPath, 0o600);\n return { wasFixed };\n }\n\n /**\n * Explicit setup step — call once before any API operations.\n * Validates any cached token; deletes and re-creates if stale (401).\n * Returns what happened so the caller can surface it in job step logs.\n */\n async prepare(): Promise<{ autoFixed: boolean; message: string }> {\n const { tokenPath, baseUrl } = this.config;\n if (existsSync(tokenPath)) {\n // Validate cached token — it may be stale if Gitea was reset or re-installed\n const token = readFileSync(tokenPath, 'utf-8').trim();\n try {\n const check = await fetch(`${baseUrl}/api/v1/user`, {\n headers: { Authorization: `token ${token}` },\n signal: AbortSignal.timeout(8000),\n });\n if (check.status !== 401) {\n return { autoFixed: false, message: 'token cached' };\n }\n // Token is stale — delete and fall through to re-create\n unlinkSync(tokenPath);\n } catch {\n // Network error — assume token is still valid, let API calls fail naturally\n return { autoFixed: false, message: 'token cached (network check skipped)' };\n }\n }\n const { wasFixed } = await this._createToken();\n return {\n autoFixed: wasFixed,\n message: wasFixed\n ? 'mustChangePassword was set — auto-fixed via docker exec; token created'\n : 'token created',\n };\n }\n\n private async ensureToken(): Promise<string> {\n const { tokenPath } = this.config;\n if (existsSync(tokenPath)) {\n return readFileSync(tokenPath, 'utf-8').trim();\n }\n await this._createToken();\n return readFileSync(tokenPath, 'utf-8').trim();\n }\n\n private async authHeaders(): Promise<Record<string, string>> {\n return {\n Authorization: `token ${await this.ensureToken()}`,\n 'Content-Type': 'application/json',\n };\n }\n\n // ---------------------------------------------------------------------------\n // Repository operations\n // ---------------------------------------------------------------------------\n\n async repoExists(name: string): Promise<boolean> {\n const { baseUrl, username } = this.config;\n const res = await fetch(\n `${baseUrl}/api/v1/repos/${username}/${name}`,\n { headers: await this.authHeaders() },\n );\n return res.status === 200;\n }\n\n /** Returns true if the repo exists but has no commits (empty: true from Gitea API). */\n async repoIsEmpty(name: string): Promise<boolean> {\n const { baseUrl, username } = this.config;\n const res = await fetch(\n `${baseUrl}/api/v1/repos/${username}/${name}`,\n { headers: await this.authHeaders() },\n );\n if (res.status !== 200) return false;\n const data = await res.json() as { empty?: boolean };\n return data.empty === true;\n }\n\n /** Creates a private repo and returns the clone URL. */\n async createRepo(name: string, description = ''): Promise<string> {\n const { baseUrl } = this.config;\n const res = await fetch(`${baseUrl}/api/v1/user/repos`, {\n method: 'POST',\n headers: await this.authHeaders(),\n body: JSON.stringify({ name, description, private: false, auto_init: false }),\n });\n\n if (!res.ok) {\n const body = await res.text();\n // 409 \"repository already exists\" — previous partial creation left the repo.\n // Fetch the existing repo's clone_url and continue.\n if (res.status === 409) {\n const existing = await fetch(`${baseUrl}/api/v1/repos/${this.config.username}/${name}`, {\n headers: await this.authHeaders(),\n });\n if (existing.ok) {\n const data = (await existing.json()) as { clone_url: string };\n return data.clone_url;\n }\n }\n // Gitea 500 \"repository files already exist\" — DB record was deleted\n // (e.g. by uninstall) but bare git files remain on disk in the volume.\n // Delete the orphan files and retry once.\n if (res.status === 500 && body.includes('files already exist')) {\n await this.deleteRepo(name).catch(() => { /* may 404 — that's fine */ });\n const retry = await fetch(`${baseUrl}/api/v1/user/repos`, {\n method: 'POST',\n headers: await this.authHeaders(),\n body: JSON.stringify({ name, description, private: false, auto_init: false }),\n });\n if (!retry.ok) {\n throw new Error(`Gitea createRepo retry failed: ${retry.status} ${await retry.text()}`);\n }\n const retryData = (await retry.json()) as { clone_url: string };\n return retryData.clone_url;\n }\n throw new Error(`Gitea createRepo failed: ${res.status} ${body}`);\n }\n\n const data = (await res.json()) as { clone_url: string };\n return data.clone_url;\n }\n\n /** Patch a repo from private to public visibility. No-op if already public. */\n async makeRepoPublic(name: string): Promise<void> {\n const { baseUrl, username } = this.config;\n const res = await fetch(`${baseUrl}/api/v1/repos/${username}/${name}`, {\n method: 'PATCH',\n headers: await this.authHeaders(),\n body: JSON.stringify({ private: false }),\n });\n if (!res.ok) throw new Error(`Gitea makeRepoPublic failed: ${res.status} ${await res.text()}`);\n }\n\n async deleteRepo(name: string): Promise<void> {\n const { baseUrl, username } = this.config;\n await fetch(`${baseUrl}/api/v1/repos/${username}/${name}`, {\n method: 'DELETE',\n headers: await this.authHeaders(),\n });\n }\n\n /** Returns all repos accessible to the authenticated user. */\n async listRepos(): Promise<GitRepoEntry[]> {\n const { baseUrl } = this.config;\n const res = await fetch(`${baseUrl}/api/v1/user/repos`, {\n headers: await this.authHeaders(),\n signal: AbortSignal.timeout(8000),\n });\n if (!res.ok) {\n throw new Error(`Gitea listRepos failed: ${res.status} ${await res.text()}`);\n }\n return (await res.json()) as GitRepoEntry[];\n }\n\n /** Fetch a single repo's detail (includes default_branch, ssh_url). */\n async getRepo(name: string): Promise<{\n id: number; name: string; clone_url: string; ssh_url: string;\n html_url: string; description: string; private: boolean; default_branch: string;\n }> {\n const { baseUrl, username } = this.config;\n const res = await fetch(`${baseUrl}/api/v1/repos/${username}/${name}`, {\n headers: await this.authHeaders(),\n });\n if (!res.ok) throw new Error(`Gitea getRepo failed: ${res.status} ${await res.text()}`);\n return res.json() as Promise<{\n id: number; name: string; clone_url: string; ssh_url: string;\n html_url: string; description: string; private: boolean; default_branch: string;\n }>;\n }\n\n /** Get the latest commit on a branch. Returns null for empty repos. */\n async getLatestCommit(\n repoName: string,\n branch: string,\n ): Promise<{ hash: string; shortHash: string; message: string; date: string } | null> {\n const { baseUrl, username } = this.config;\n const res = await fetch(\n `${baseUrl}/api/v1/repos/${username}/${repoName}/commits?sha=${encodeURIComponent(branch)}&limit=1`,\n { headers: await this.authHeaders() },\n );\n if (!res.ok) return null;\n const commits = (await res.json()) as Array<{ sha: string; commit: { message: string; committer: { date: string } } }>;\n if (!commits.length) return null;\n const c = commits[0]!;\n return {\n hash: c.sha,\n shortHash: c.sha.slice(0, 7),\n message: c.commit.message.split('\\n')[0]!,\n date: c.commit.committer.date,\n };\n }\n\n /** Register a push webhook on the repo. */\n async createWebhook(repoName: string, webhookUrl: string, secret: string): Promise<void> {\n const { baseUrl, username } = this.config;\n const res = await fetch(`${baseUrl}/api/v1/repos/${username}/${repoName}/hooks`, {\n method: 'POST',\n headers: await this.authHeaders(),\n body: JSON.stringify({\n type: 'gitea',\n config: { url: webhookUrl, content_type: 'json', secret },\n events: ['push'],\n active: true,\n }),\n });\n if (!res.ok) throw new Error(`Gitea createWebhook failed: ${res.status} ${await res.text()}`);\n }\n\n /** Returns branch names for a repo. Falls back to empty array on error. */\n async listBranches(repoName: string): Promise<string[]> {\n const { baseUrl, username } = this.config;\n const res = await fetch(\n `${baseUrl}/api/v1/repos/${username}/${repoName}/branches?limit=50`,\n { headers: await this.authHeaders(), signal: AbortSignal.timeout(8000) },\n );\n if (!res.ok) return [];\n const data = await res.json() as Array<{ name: string }>;\n return data.map((b) => b.name);\n }\n\n /** URL suitable for git remote add — includes credentials in URL (stored in .git/config which is chmod 600). */\n authedCloneUrl(cloneUrl: string): string {\n const { username, password, tokenPath, baseUrl } = this.config;\n // Prefer cached API token over admin password — the token is verified to work\n // (prepare() always creates/validates it before any git operation).\n // Gitea accepts API tokens as HTTP Basic Auth password.\n const credential = existsSync(tokenPath)\n ? readFileSync(tokenPath, 'utf-8').trim()\n : password;\n const encUser = encodeURIComponent(username);\n const encCred = encodeURIComponent(credential);\n\n // Normalize to baseUrl — Gitea returns clone_url based on whatever host/path\n // it receives. Behind Traefik's strip-prefix middleware the URL lacks the /git\n // subpath (e.g. http://localhost/admin/repo.git instead of\n // http://localhost/git/admin/repo.git), causing git to hit the wrong service.\n // Extract the canonical user/repo.git tail and rebuild from baseUrl.\n const repoMatch = cloneUrl.match(/\\/([^/]+\\/[^/]+\\.git)$/);\n const normalizedUrl = repoMatch\n ? `${baseUrl.replace(/\\/$/, '')}/${repoMatch[1]}`\n : cloneUrl;\n\n return normalizedUrl.replace('http://', `http://${encUser}:${encCred}@`);\n }\n}\n"],"mappings":"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","names":["existsSync","readFileSync","writeFileSync","data","existsSync","readFileSync","writeFileSync","removeApp"]}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/services/app-registry.ts
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
5
|
+
import { dirname } from "path";
|
|
6
|
+
function readApps(appsJsonPath) {
|
|
7
|
+
if (!existsSync(appsJsonPath)) return [];
|
|
8
|
+
try {
|
|
9
|
+
return JSON.parse(readFileSync(appsJsonPath, "utf-8"));
|
|
10
|
+
} catch {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function writeApps(appsJsonPath, apps) {
|
|
15
|
+
mkdirSync(dirname(appsJsonPath), { recursive: true });
|
|
16
|
+
writeFileSync(appsJsonPath, JSON.stringify(apps, null, 2), "utf-8");
|
|
17
|
+
}
|
|
18
|
+
function addApp(appsJsonPath, entry) {
|
|
19
|
+
const apps = readApps(appsJsonPath);
|
|
20
|
+
if (apps.some((a) => a.name === entry.name)) {
|
|
21
|
+
throw new Error(`App "${entry.name}" already exists`);
|
|
22
|
+
}
|
|
23
|
+
writeApps(appsJsonPath, [...apps, entry]);
|
|
24
|
+
}
|
|
25
|
+
function updateApp(appsJsonPath, name, patch) {
|
|
26
|
+
const apps = readApps(appsJsonPath);
|
|
27
|
+
const idx = apps.findIndex((a) => a.name === name);
|
|
28
|
+
if (idx === -1) throw new Error(`App "${name}" not found`);
|
|
29
|
+
apps[idx] = { ...apps[idx], ...patch };
|
|
30
|
+
writeApps(appsJsonPath, apps);
|
|
31
|
+
}
|
|
32
|
+
function removeApp(appsJsonPath, name) {
|
|
33
|
+
const apps = readApps(appsJsonPath).filter((a) => a.name !== name);
|
|
34
|
+
writeApps(appsJsonPath, apps);
|
|
35
|
+
}
|
|
36
|
+
function readDeployHistory(historyJsonPath) {
|
|
37
|
+
if (!existsSync(historyJsonPath)) return [];
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(readFileSync(historyJsonPath, "utf-8"));
|
|
40
|
+
} catch {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function appendDeployHistory(historyJsonPath, entry) {
|
|
45
|
+
const entries = readDeployHistory(historyJsonPath);
|
|
46
|
+
mkdirSync(dirname(historyJsonPath), { recursive: true });
|
|
47
|
+
writeFileSync(historyJsonPath, JSON.stringify([...entries, entry], null, 2), "utf-8");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export {
|
|
51
|
+
readApps,
|
|
52
|
+
addApp,
|
|
53
|
+
updateApp,
|
|
54
|
+
removeApp,
|
|
55
|
+
readDeployHistory,
|
|
56
|
+
appendDeployHistory
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=chunk-JIPAYMOA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/services/app-registry.ts"],"sourcesContent":["// packages/cli/src/services/app-registry.ts\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';\nimport { dirname } from 'node:path';\nimport type { AppEntry, DeployHistoryEntry } from '../types/app-entry.js';\n\nexport function readApps(appsJsonPath: string): AppEntry[] {\n if (!existsSync(appsJsonPath)) return [];\n try {\n return JSON.parse(readFileSync(appsJsonPath, 'utf-8')) as AppEntry[];\n } catch {\n return [];\n }\n}\n\nexport function writeApps(appsJsonPath: string, apps: AppEntry[]): void {\n mkdirSync(dirname(appsJsonPath), { recursive: true });\n writeFileSync(appsJsonPath, JSON.stringify(apps, null, 2), 'utf-8');\n}\n\nexport function addApp(appsJsonPath: string, entry: AppEntry): void {\n const apps = readApps(appsJsonPath);\n if (apps.some((a) => a.name === entry.name)) {\n throw new Error(`App \"${entry.name}\" already exists`);\n }\n writeApps(appsJsonPath, [...apps, entry]);\n}\n\nexport function updateApp(appsJsonPath: string, name: string, patch: Partial<AppEntry>): void {\n const apps = readApps(appsJsonPath);\n const idx = apps.findIndex((a) => a.name === name);\n if (idx === -1) throw new Error(`App \"${name}\" not found`);\n apps[idx] = { ...apps[idx]!, ...patch };\n writeApps(appsJsonPath, apps);\n}\n\nexport function removeApp(appsJsonPath: string, name: string): void {\n const apps = readApps(appsJsonPath).filter((a) => a.name !== name);\n writeApps(appsJsonPath, apps);\n}\n\nexport function readDeployHistory(historyJsonPath: string): DeployHistoryEntry[] {\n if (!existsSync(historyJsonPath)) return [];\n try {\n return JSON.parse(readFileSync(historyJsonPath, 'utf-8')) as DeployHistoryEntry[];\n } catch {\n return [];\n }\n}\n\nexport function appendDeployHistory(historyJsonPath: string, entry: DeployHistoryEntry): void {\n const entries = readDeployHistory(historyJsonPath);\n mkdirSync(dirname(historyJsonPath), { recursive: true });\n writeFileSync(historyJsonPath, JSON.stringify([...entries, entry], null, 2), 'utf-8');\n}\n"],"mappings":";;;AACA,SAAS,YAAY,cAAc,eAAe,iBAAiB;AACnE,SAAS,eAAe;AAGjB,SAAS,SAAS,cAAkC;AACzD,MAAI,CAAC,WAAW,YAAY,EAAG,QAAO,CAAC;AACvC,MAAI;AACF,WAAO,KAAK,MAAM,aAAa,cAAc,OAAO,CAAC;AAAA,EACvD,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEO,SAAS,UAAU,cAAsB,MAAwB;AACtE,YAAU,QAAQ,YAAY,GAAG,EAAE,WAAW,KAAK,CAAC;AACpD,gBAAc,cAAc,KAAK,UAAU,MAAM,MAAM,CAAC,GAAG,OAAO;AACpE;AAEO,SAAS,OAAO,cAAsB,OAAuB;AAClE,QAAM,OAAO,SAAS,YAAY;AAClC,MAAI,KAAK,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM,IAAI,GAAG;AAC3C,UAAM,IAAI,MAAM,QAAQ,MAAM,IAAI,kBAAkB;AAAA,EACtD;AACA,YAAU,cAAc,CAAC,GAAG,MAAM,KAAK,CAAC;AAC1C;AAEO,SAAS,UAAU,cAAsB,MAAc,OAAgC;AAC5F,QAAM,OAAO,SAAS,YAAY;AAClC,QAAM,MAAM,KAAK,UAAU,CAAC,MAAM,EAAE,SAAS,IAAI;AACjD,MAAI,QAAQ,GAAI,OAAM,IAAI,MAAM,QAAQ,IAAI,aAAa;AACzD,OAAK,GAAG,IAAI,EAAE,GAAG,KAAK,GAAG,GAAI,GAAG,MAAM;AACtC,YAAU,cAAc,IAAI;AAC9B;AAEO,SAAS,UAAU,cAAsB,MAAoB;AAClE,QAAM,OAAO,SAAS,YAAY,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,IAAI;AACjE,YAAU,cAAc,IAAI;AAC9B;AAEO,SAAS,kBAAkB,iBAA+C;AAC/E,MAAI,CAAC,WAAW,eAAe,EAAG,QAAO,CAAC;AAC1C,MAAI;AACF,WAAO,KAAK,MAAM,aAAa,iBAAiB,OAAO,CAAC;AAAA,EAC1D,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEO,SAAS,oBAAoB,iBAAyB,OAAiC;AAC5F,QAAM,UAAU,kBAAkB,eAAe;AACjD,YAAU,QAAQ,eAAe,GAAG,EAAE,WAAW,KAAK,CAAC;AACvD,gBAAc,iBAAiB,KAAK,UAAU,CAAC,GAAG,SAAS,KAAK,GAAG,MAAM,CAAC,GAAG,OAAO;AACtF;","names":[]}
|