mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
- PKCE verifier/state/pairing code now use generateSecureToken, generateRandomHex and generateShortId instead of hand-rolled randomBytes; the pairing loop's modulo was unbiased only because 256 % 32 == 0 - new sha256Base64Url in @sim/security/hash so both sides of the PKCE exchange derive the challenge from one implementation - isUsableSecret moved beside SECRET_KEYS so setup and doctor apply the same rule; doctor previously passed a key setup would replace - isTruthy narrowed to true/1, matching the app it claims to mirror — it accepted yes/on, so a flag could read on in doctor and off in the app - checkLive runs its five probes concurrently (~17s serial worst case) - detection overlaps the banner animation instead of queueing behind it - glyph.fail/glyph.warn at 13 sites that bypassed the constant; removed unused prompter exports, a dead ENV_PATHS re-export, and an unused export keyword
67 lines
2.1 KiB
TypeScript
67 lines
2.1 KiB
TypeScript
import { spawnSync } from 'node:child_process'
|
|
import { SetupError } from './errors.ts'
|
|
import { waitFor } from './probes.ts'
|
|
import * as p from './prompter.ts'
|
|
import { glyph, theme } from './theme.ts'
|
|
|
|
const INSTALL_HINTS = [
|
|
'install Docker Desktop: https://docker.com/products/docker-desktop',
|
|
`or OrbStack (lighter on macOS): ${theme.command('brew install orbstack')}`,
|
|
]
|
|
|
|
function daemonUp(): boolean {
|
|
return spawnSync('docker', ['info'], { stdio: 'ignore' }).status === 0
|
|
}
|
|
|
|
function installed(): boolean {
|
|
return spawnSync('which', ['docker'], { stdio: 'ignore' }).status === 0
|
|
}
|
|
|
|
/**
|
|
* Returns whether the Docker daemon is available, offering to launch Docker
|
|
* Desktop (macOS) when it's installed but stopped. Never installs anything.
|
|
* With required=true, unavailability is a SetupError instead of false.
|
|
*/
|
|
export async function ensureDocker(required: boolean): Promise<boolean> {
|
|
if (daemonUp()) return true
|
|
|
|
if (!installed()) {
|
|
if (required) throw new SetupError('Docker is not installed.', INSTALL_HINTS)
|
|
return false
|
|
}
|
|
|
|
if (process.platform !== 'darwin') {
|
|
if (required) {
|
|
throw new SetupError('Docker is installed but the daemon is not running.', [
|
|
`start it: ${theme.command('sudo systemctl start docker')} (Linux)`,
|
|
])
|
|
}
|
|
return false
|
|
}
|
|
|
|
const launch = await p.confirm({
|
|
message: 'Docker is installed but not running — start Docker Desktop now?',
|
|
initialValue: true,
|
|
})
|
|
if (!launch) {
|
|
if (required) {
|
|
throw new SetupError('Docker is required for this mode.', [
|
|
'start Docker Desktop, then re-run the wizard',
|
|
])
|
|
}
|
|
return false
|
|
}
|
|
|
|
spawnSync('open', ['-a', 'Docker'], { stdio: 'ignore' })
|
|
const spin = p.spinner()
|
|
spin.start('Waiting for the Docker daemon…')
|
|
const up = await waitFor(async () => daemonUp(), 90_000, 2000)
|
|
spin.stop(up ? 'Docker is running' : `${glyph.fail} daemon did not come up`)
|
|
if (!up) {
|
|
throw new SetupError('Docker Desktop did not start within 90s.', [
|
|
'first-ever launch needs a GUI license acceptance — open Docker Desktop manually once, then re-run',
|
|
])
|
|
}
|
|
return true
|
|
}
|