6bf8bebf51
CI / Test and Build (push) Failing after 1s
CI / Migrate Dev DB (push) Has been skipped
CI / Migrate DB (push) Has been skipped
CodeQL / Analyze actions (push) Has been cancelled
CodeQL / Analyze javascript-typescript (push) Has been cancelled
CI / Detect Version (push) Has been cancelled
CI / Detect Desktop Changes (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/cron.Dockerfile, ubuntu-latest, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build AMD64 (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/cron.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/db.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/pii.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/realtime.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-8vcpu-ubuntu-2404-arm, ./docker/app.Dockerfile, linux-arm64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
Helm Chart / Lint, test, and validate chart (push) Has been cancelled
Helm Chart / Chart version bumped (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Build Dev ECR (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core) (push) Has been cancelled
CI / Promote Images (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Check Desktop Signing Secrets (push) Has been cancelled
CI / Desktop Release (push) Has been cancelled
CI / Create Desktop Prerelease (push) Has been cancelled
CI / Desktop Prerelease Build (push) Has been cancelled
CI / Publish Desktop Prerelease (push) Has been cancelled
CI / Prune Desktop Prereleases (push) Has been cancelled
Helm Chart / Install on kind and run helm test (push) Has been cancelled
172 lines
6.5 KiB
TypeScript
172 lines
6.5 KiB
TypeScript
import { spawnSync } from 'node:child_process'
|
|
import path from 'node:path'
|
|
import { truncate } from '@sim/utils/string'
|
|
import { EMAIL_SETUP, JOBS_SETUP, STORAGE_SETUP } from '../capability-config.ts'
|
|
import { promptCapabilitySetup, stageCapabilitySetupTransition } from '../capability-setup.ts'
|
|
import { resolveDatabase } from '../db.ts'
|
|
import type { Detection } from '../detect.ts'
|
|
import { ROOT, readEnvFile, reconcileEnvValues, writeEnvValues } from '../env-files.ts'
|
|
import { SetupError } from '../errors.ts'
|
|
import { pgProbe } from '../probes.ts'
|
|
import * as p from '../prompter.ts'
|
|
import { ensureRedis, resolveRedis } from '../redis.ts'
|
|
import {
|
|
chatFlagValues,
|
|
collectSecrets,
|
|
mothershipOverride,
|
|
promptCopilotKey,
|
|
promptLlmKeys,
|
|
promptSecurity,
|
|
promptSignInProviders,
|
|
promptUnlocks,
|
|
} from '../steps.ts'
|
|
import { glyph, theme } from '../theme.ts'
|
|
import { APP_URL } from '../urls.ts'
|
|
|
|
/**
|
|
* A migrate failure on a never-migrated database means setup failed — abort.
|
|
* On a database that already has applied migrations (a live but drifted dev
|
|
* DB), the failure is surfaced and the user decides whether to continue.
|
|
*/
|
|
async function runMigrations(dsn: string): Promise<void> {
|
|
const spin = p.spinner()
|
|
spin.start('Running database migrations…')
|
|
const result = spawnSync('bun', ['run', 'db:migrate'], {
|
|
cwd: path.join(ROOT, 'packages/db'),
|
|
encoding: 'utf8',
|
|
})
|
|
if (result.status === 0) {
|
|
spin.stop('Migrations applied')
|
|
return
|
|
}
|
|
spin.stop(`${glyph.fail} migrations failed`)
|
|
const error = truncate(`${result.stdout}\n${result.stderr}`.trim(), 2000)
|
|
const probe = await pgProbe(dsn)
|
|
const applied = probe.ok ? (probe.migrations?.applied ?? 0) : 0
|
|
if (applied === 0) {
|
|
throw new SetupError(`db:migrate failed on a fresh database:\n${error}`, [
|
|
`run it by hand to see the full output: ${theme.command('cd packages/db && bun run db:migrate')}`,
|
|
'check DATABASE_URL points at the database you expect',
|
|
])
|
|
}
|
|
p.log.warn(
|
|
`db:migrate failed, but this database already has ${applied} applied migrations — it may have schema drift (e.g. built with db:push).`
|
|
)
|
|
p.log.info(theme.muted(truncate(error, 600)))
|
|
const proceed = await p.confirm({
|
|
message: 'Continue setup without migrating? (doctor will keep flagging the drift)',
|
|
initialValue: true,
|
|
})
|
|
if (!proceed) throw new Error(`aborted: db:migrate failed:\n${error}`)
|
|
}
|
|
|
|
async function promptRedis(detection: Detection, existing?: string): Promise<string | null> {
|
|
const wants = await p.confirm({
|
|
message:
|
|
'Configure Redis? (powers live Chat status and table events; storage falls back to Postgres)',
|
|
initialValue: true,
|
|
})
|
|
if (!wants) return null
|
|
return resolveRedis(detection, existing)
|
|
}
|
|
|
|
export async function runDevMode(
|
|
detection: Detection,
|
|
quick: boolean
|
|
): Promise<{ startNow: boolean; script: string }> {
|
|
const sim = readEnvFile('sim')
|
|
const dsn = await resolveDatabase(detection, sim.vars.get('DATABASE_URL'))
|
|
const secrets = collectSecrets(sim)
|
|
|
|
const shared = {
|
|
DATABASE_URL: dsn,
|
|
BETTER_AUTH_SECRET: secrets.BETTER_AUTH_SECRET,
|
|
INTERNAL_API_SECRET: secrets.INTERNAL_API_SECRET,
|
|
BETTER_AUTH_URL: APP_URL,
|
|
NEXT_PUBLIC_APP_URL: APP_URL,
|
|
}
|
|
writeEnvValues('sim', {
|
|
...shared,
|
|
ENCRYPTION_KEY: secrets.ENCRYPTION_KEY,
|
|
API_ENCRYPTION_KEY: secrets.API_ENCRYPTION_KEY,
|
|
})
|
|
writeEnvValues('realtime', shared)
|
|
writeEnvValues('db', { DATABASE_URL: dsn })
|
|
p.log.step('Wrote apps/sim/.env, apps/realtime/.env, packages/db/.env (shared subset mirrored)')
|
|
await runMigrations(dsn)
|
|
|
|
const simAfter = readEnvFile('sim')
|
|
const values: Record<string, string> = {}
|
|
const remove = new Set<string>()
|
|
// Before the key is minted: a half-set override mints against one environment
|
|
// and validates against the other, and warning afterwards is too late — the
|
|
// bad key is already stored, and the next run offers to keep it.
|
|
Object.assign(values, mothershipOverride())
|
|
const copilotKey = await promptCopilotKey(simAfter.vars.get('COPILOT_API_KEY'))
|
|
if (copilotKey) values.COPILOT_API_KEY = copilotKey
|
|
Object.assign(values, chatFlagValues(copilotKey))
|
|
Object.assign(values, await promptLlmKeys(detection, !quick))
|
|
|
|
// Redis is set up in every mode, quick included. Storage falls back to
|
|
// PostgreSQL without it, but the pub/sub channels (live Chat task-status,
|
|
// table events) have no fallback — skipping it silently produces an install
|
|
// where live updates never arrive. Quick configures it with no questions at
|
|
// all; custom keeps the opt-out and the where-should-it-live ladder.
|
|
const redisUrl = quick
|
|
? await ensureRedis(detection, simAfter.vars.get('REDIS_URL'))
|
|
: await promptRedis(detection, simAfter.vars.get('REDIS_URL'))
|
|
if (redisUrl) {
|
|
values.REDIS_URL = redisUrl
|
|
writeEnvValues('realtime', { REDIS_URL: redisUrl })
|
|
}
|
|
|
|
if (!quick) {
|
|
const stagedVars = new Map(simAfter.vars)
|
|
for (const [key, value] of Object.entries(values)) stagedVars.set(key, value)
|
|
for (const setup of [JOBS_SETUP, STORAGE_SETUP, EMAIL_SETUP] as const) {
|
|
const transition = await promptCapabilitySetup(setup, stagedVars, {
|
|
containerized: false,
|
|
})
|
|
stageCapabilitySetupTransition(stagedVars, values, remove, transition)
|
|
}
|
|
Object.assign(values, await promptSignInProviders(stagedVars, APP_URL))
|
|
const security = await promptSecurity(simAfter.vars)
|
|
Object.assign(values, security.sim)
|
|
if (Object.keys(security.mirrorToRealtime).length > 0) {
|
|
writeEnvValues('realtime', security.mirrorToRealtime)
|
|
}
|
|
Object.assign(values, await promptUnlocks(simAfter.vars))
|
|
}
|
|
for (const key of Object.keys(values)) remove.delete(key)
|
|
if (remove.size > 0 || Object.keys(values).length > 0) {
|
|
reconcileEnvValues('sim', [...remove], values)
|
|
}
|
|
|
|
let script = 'dev:full'
|
|
if (detection.specs.hostMemGb < 16) {
|
|
script = await p.select({
|
|
message: `Low RAM detected (${detection.specs.hostMemGb}GB) — which dev server?`,
|
|
options: [
|
|
{
|
|
value: 'dev:full:capped',
|
|
label: 'Capped heap (recommended)',
|
|
hint: 'caps Node at 4GB — every integration still available',
|
|
},
|
|
{
|
|
value: 'dev:full',
|
|
label: 'Uncapped',
|
|
hint: 'lets the dev server take what it needs (~4GB typical)',
|
|
},
|
|
],
|
|
initialValue: 'dev:full:capped',
|
|
})
|
|
}
|
|
return {
|
|
startNow: await p.confirm({
|
|
message: `Start Sim now? (bun run ${script})`,
|
|
initialValue: true,
|
|
}),
|
|
script,
|
|
}
|
|
}
|