mirror of
https://github.com/browseros-ai/BrowserOS.git
synced 2026-05-19 11:31:03 +00:00
* feat(openclaw): add gateway image inspection * feat(openclaw): pull gateway image from registry * refactor(vm): decouple readiness from image cache * refactor(openclaw): remove vm cache from runtime factory * feat(openclaw): detect current gateway image * feat(openclaw): prewarm vm runtime and reuse current gateway * feat(openclaw): prewarm runtime on server startup * refactor(vm): remove browseros image cache runtime * refactor(build-tools): remove openclaw tarball pipeline * chore: self-review fixes * fix(openclaw): suppress prewarm pull progress logs * fix(openclaw): address review feedback * fix(openclaw): resolve review findings * fix(dev): stop stale watch supervisors
119 lines
3.3 KiB
TypeScript
119 lines
3.3 KiB
TypeScript
import { existsSync, readFileSync } from 'node:fs'
|
|
import { join } from 'node:path'
|
|
|
|
import { parse } from 'dotenv'
|
|
|
|
import type { BuildConfig } from './types'
|
|
|
|
const REQUIRED_PROD_VARS = [
|
|
'BROWSEROS_CONFIG_URL',
|
|
'CODEGEN_SERVICE_URL',
|
|
'POSTHOG_API_KEY',
|
|
'SENTRY_DSN',
|
|
]
|
|
const INLINED_ENV_VARS = [
|
|
...REQUIRED_PROD_VARS,
|
|
'NODE_ENV',
|
|
'LOG_LEVEL',
|
|
] as const
|
|
const PROD_ENV_PATH = join('apps', 'server', '.env.production')
|
|
const PROD_ENV_TEMPLATE_PATH = join('apps', 'server', '.env.production.example')
|
|
|
|
function readServerVersion(rootDir: string): string {
|
|
const pkgPath = join(rootDir, 'apps/server/package.json')
|
|
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
|
|
return pkg.version
|
|
}
|
|
|
|
function pickEnv(name: string, fileEnv: Record<string, string>): string {
|
|
const value = process.env[name] ?? fileEnv[name]
|
|
if (!value || value.trim().length === 0) {
|
|
throw new Error(`Missing required environment variable: ${name}`)
|
|
}
|
|
return value
|
|
}
|
|
|
|
function loadProdEnv(rootDir: string): Record<string, string> {
|
|
const prodEnvPath = join(rootDir, PROD_ENV_PATH)
|
|
if (!existsSync(prodEnvPath)) {
|
|
const prodEnvTemplatePath = join(rootDir, PROD_ENV_TEMPLATE_PATH)
|
|
if (existsSync(prodEnvTemplatePath)) {
|
|
throw new Error(
|
|
`Missing ${PROD_ENV_PATH}. Create it from ${PROD_ENV_TEMPLATE_PATH} before running build:server.`,
|
|
)
|
|
}
|
|
throw new Error(
|
|
`Missing ${PROD_ENV_PATH}. The template file ${PROD_ENV_TEMPLATE_PATH} was not found.`,
|
|
)
|
|
}
|
|
return parse(readFileSync(prodEnvPath, 'utf-8'))
|
|
}
|
|
|
|
function buildInlineEnv(
|
|
fileEnv: Record<string, string>,
|
|
): Record<string, string> {
|
|
const inlineEnv: Record<string, string> = {}
|
|
for (const key of INLINED_ENV_VARS) {
|
|
const value = process.env[key] ?? fileEnv[key]
|
|
if (value !== undefined) {
|
|
inlineEnv[key] = value
|
|
}
|
|
}
|
|
return inlineEnv
|
|
}
|
|
|
|
function validateProductionEnv(envVars: Record<string, string>): void {
|
|
const missing = REQUIRED_PROD_VARS.filter((name) => {
|
|
const value = envVars[name]
|
|
return !value || value.trim().length === 0
|
|
})
|
|
if (missing.length > 0) {
|
|
throw new Error(
|
|
`Production build requires variables: ${missing.join(', ')} (set them in ${PROD_ENV_PATH} or process env).`,
|
|
)
|
|
}
|
|
}
|
|
|
|
export interface LoadBuildConfigOptions {
|
|
ci?: boolean
|
|
}
|
|
|
|
export function loadBuildConfig(
|
|
rootDir: string,
|
|
options: LoadBuildConfigOptions = {},
|
|
): BuildConfig {
|
|
const fileEnv = loadProdEnv(rootDir)
|
|
const envVars = buildInlineEnv(fileEnv)
|
|
if (!options.ci) {
|
|
validateProductionEnv(envVars)
|
|
}
|
|
|
|
const processEnv: NodeJS.ProcessEnv = {
|
|
PATH: process.env.PATH ?? '',
|
|
...fileEnv,
|
|
...process.env,
|
|
}
|
|
|
|
if (options.ci) {
|
|
return { version: readServerVersion(rootDir), envVars, processEnv }
|
|
}
|
|
|
|
return {
|
|
version: readServerVersion(rootDir),
|
|
envVars,
|
|
processEnv,
|
|
r2: {
|
|
accountId: pickEnv('R2_ACCOUNT_ID', fileEnv),
|
|
accessKeyId: pickEnv('R2_ACCESS_KEY_ID', fileEnv),
|
|
secretAccessKey: pickEnv('R2_SECRET_ACCESS_KEY', fileEnv),
|
|
bucket: pickEnv('R2_BUCKET', fileEnv),
|
|
downloadPrefix:
|
|
process.env.R2_DOWNLOAD_PREFIX ?? fileEnv.R2_DOWNLOAD_PREFIX ?? '',
|
|
uploadPrefix:
|
|
process.env.R2_UPLOAD_PREFIX ??
|
|
fileEnv.R2_UPLOAD_PREFIX ??
|
|
'server/prod-resources',
|
|
},
|
|
}
|
|
}
|