diff --git a/app/src/components/detail/JobFields.tsx b/app/src/components/detail/JobFields.tsx index 07f2176..152b464 100644 --- a/app/src/components/detail/JobFields.tsx +++ b/app/src/components/detail/JobFields.tsx @@ -58,8 +58,16 @@ export function JobFields({ job, onSave, onDelete }: Props) { return (
- - + + {runnerLabel(runner)} · ) => Promise onDelete?: (name: string) => Promise } -/** Edit a program's catalog config (source identity), not how it's deployed. */ +type Obj = Record + +/** Edit a program's catalog config (its source identity), not how it's deployed. */ export function ProgramFields({ program, onSave, onDelete }: Props) { const m = program.manifest const [saving, setSaving] = useState(false) @@ -26,11 +33,24 @@ export function ProgramFields({ program, onSave, onDelete }: Props) { const [ref, setRef] = useState((m.ref as string) ?? "") const [deps, setDeps] = useState(((m.system_dependencies as string[]) ?? []).join(", ")) + const readVerb = (verb: string): string => { + if (verb === "build") { + const c = (m.build as { commands?: string[][] } | undefined)?.commands + return (c?.[0] ?? []).join(" ") + } + const cmds = (m.commands as Record | undefined) ?? {} + const key = verb === "type-check" && !cmds["type-check"] ? "type_check" : verb + return (cmds[key]?.[0] ?? []).join(" ") + } + const [cmds, setCmds] = useState>(() => + Object.fromEntries(VERBS.map((v) => [v, readVerb(v)])), + ) + const handleSave = async () => { setSaving(true) setSaved(false) try { - const config: Record = { ...m } + const config: Obj = { ...m } delete config.id config.description = description || undefined config.source = source || undefined @@ -39,10 +59,20 @@ export function ProgramFields({ program, onSave, onDelete }: Props) { config.version = version || undefined config.repo = repo || undefined config.ref = ref || undefined - config.system_dependencies = deps - .split(",") - .map((d) => d.trim()) - .filter(Boolean) + config.system_dependencies = deps.split(",").map((d) => d.trim()).filter(Boolean) + + const commands: Record = {} + let buildArgv: string[][] | null = null + for (const v of VERBS) { + const s = (cmds[v] ?? "").trim() + if (!s) continue + const argv: string[][] = [s.split(/\s+/)] + if (v === "build") buildArgv = argv + else commands[v] = argv + } + if (buildArgv) config.build = { ...((m.build as Obj) ?? {}), commands: buildArgv } + config.commands = Object.keys(commands).length ? commands : undefined + await onSave(program.id, config) setSaved(true) setTimeout(() => setSaved(false), 2000) @@ -53,9 +83,24 @@ export function ProgramFields({ program, onSave, onDelete }: Props) { return (
- - - + + + - + - - - - + + + + - {program.commands && Object.keys(program.commands).length > 0 && ( - -
- {Object.entries(program.commands).map(([verb, cmds]) => ( -
- {verb} - {cmds.map((a) => a.join(" ")).join(" && ")} -
- ))} -
-
- )} + +
+ {VERBS.map((verb) => ( +
+ {verb} + setCmds((c) => ({ ...c, [verb]: e.target.value }))} + placeholder={stack ? "(stack default)" : "—"} + className="flex-1 bg-black/30 border border-[var(--border)] rounded px-2 py-1 text-xs font-mono focus:outline-none focus:border-[var(--primary)]" + /> +
+ ))} +
+
- + {runnerLabel(runner)} · - - - - + + + + {envEditor} -
{children}
+
+ {children} + {hint &&

{hint}

} +
) } @@ -21,6 +32,7 @@ export function TextField({ placeholder, mono, width, + hint, }: { label: string value: string @@ -28,9 +40,10 @@ export function TextField({ placeholder?: string mono?: boolean width?: string + hint?: string }) { return ( - + onChange(e.target.value)}