Files
BrowserOS/packages/browseros-agent/scripts/build/server/config.ts
Nikhil 492f3fcdf2 feat(openclaw): prewarm ghcr image in vm (#887)
* 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
2026-04-30 11:18:11 -07:00

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',
},
}
}