Refactor config command and fix default memory limit

Move defaults and normalization into KEYS metadata, validate config
file shape on load, and lower default container memory from 32G to 16G.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Chris Wanstrath 2026-03-20 10:24:57 -07:00
parent f7d876776b
commit e3e4419933
4 changed files with 41 additions and 22 deletions

View File

@ -45,3 +45,17 @@ sandlot rm <branch> # tear down session (container, worktree, local branch)
```
Use git directly for commits, pushes, merges, etc. The worktree is a normal git checkout.
### Configuration
```bash
sandlot config # show all options and current values
sandlot config memory # show current memory setting
sandlot config memory 32G # set container memory limit
```
Config is stored in `~/.config/sandlot/config.json`.
| Key | Default | Description |
|-----|---------|-------------|
| `memory` | `16G` | Container memory limit (e.g. 4G, 16G, 32G, 64G) |

View File

@ -1,38 +1,41 @@
import { die } from "../fmt.ts"
import * as config from "../config.ts"
const KEYS: Record<string, { description: string; validate: (v: string) => string | null }> = {
const KEYS: Record<string, { default: string; description: string; normalize: (v: string) => string; validate: (v: string) => string | null }> = {
memory: {
default: "16G",
description: "Container memory limit (e.g. 4G, 16G, 32G, 64G)",
validate: (v) => /^\d+[GMgm]$/.test(v) ? null : "Must be a number followed by G or M (e.g. 16G)",
normalize: (v) => v.toUpperCase(),
validate: (v) => /^[1-9]\d*[GMgm]$/.test(v) ? null : "Must be a number followed by G or M (e.g. 16G)",
},
}
export async function action(args: string[]) {
if (args.length === 0) {
const cfg = await config.load()
const defaults: Record<string, string> = { memory: "32G" }
for (const [key, meta] of Object.entries(KEYS)) {
const val = (cfg as any)[key]
const display = val ?? `${defaults[key]} (default)`
const val = cfg[key as keyof config.Config]
const display = val ?? `${meta.default} (default)`
console.log(`${key} = ${display}`)
console.log(` ${meta.description}`)
}
return
}
if (args.length === 1) {
const key = args[0]
const [key, ...rest] = args
if (!(key in KEYS)) die(`Unknown config key: ${key}\nAvailable keys: ${Object.keys(KEYS).join(", ")}`)
if (rest.length === 0) {
const val = await config.get(key as keyof config.Config)
const defaults: Record<string, string> = { memory: "32G" }
console.log(val ?? `${defaults[key]} (default)`)
console.log(val ?? `${KEYS[key].default} (default)`)
return
}
const [key, value] = args
if (!(key in KEYS)) die(`Unknown config key: ${key}\nAvailable keys: ${Object.keys(KEYS).join(", ")}`)
const error = KEYS[key].validate(value)
if (rest.length > 1) die(`Too many arguments. Usage: sandlot config ${key} <value>`)
const meta = KEYS[key]
const error = meta.validate(rest[0])
if (error) die(error)
await config.set(key as keyof config.Config, value.toUpperCase())
console.log(`${key} = ${value.toUpperCase()}`)
const normalized = meta.normalize(rest[0])
await config.set(key as keyof config.Config, normalized)
console.log(`${key} = ${normalized}`)
}

View File

@ -1,3 +1,4 @@
import { mkdir } from "fs/promises"
import { homedir } from "os"
import { dirname, join } from "path"
@ -8,15 +9,16 @@ export interface Config {
}
export async function load(): Promise<Config> {
try {
return await Bun.file(CONFIG_PATH).json()
} catch {
return {}
}
const file = Bun.file(CONFIG_PATH)
if (!(await file.exists())) return {}
const raw = await file.json()
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return {}
const cfg: Config = {}
if (typeof raw.memory === "string") cfg.memory = raw.memory
return cfg
}
export async function save(config: Config): Promise<void> {
const { mkdir } = await import("fs/promises")
await mkdir(dirname(CONFIG_PATH), { recursive: true })
await Bun.write(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n")
}

View File

@ -67,7 +67,7 @@ function hostMounts(home: string): { dev: boolean; code: boolean } {
/** Pull the image and start the container in detached mode. */
async function createContainer(home: string): Promise<void> {
const mounts = hostMounts(home)
const memory = (await getConfig("memory")) ?? "32G"
const memory = (await getConfig("memory")) ?? "16G"
const args = ["container", "run", "-d", "--name", CONTAINER_NAME, "-m", memory]
if (mounts.dev) args.push("--mount", `type=bind,source=${home}/dev,target=/host/dev,readonly`)
if (mounts.code) args.push("--mount", `type=bind,source=${home}/code,target=/host/code,readonly`)