diff --git a/cmd/generate-types/main.go b/cmd/generate-types/main.go index b8769769..93a509df 100644 --- a/cmd/generate-types/main.go +++ b/cmd/generate-types/main.go @@ -8,30 +8,34 @@ import ( "strings" ) -func main() { - // Define the TypeScript types based on our Go contracts - typeDefinitions := generateTypeDefinitions() +// contractsRelPath is the location of the generated TypeScript file +// relative to the module root. +const contractsRelPath = "frontend/src/types/contracts.ts" + +// generateFileContent returns the full contents that should be written to +// contracts.ts (header + generated type definitions). Exposed so tests can +// verify the committed file matches the generator output and catch drift +// without writing to disk. +func generateFileContent() string { + return fmt.Sprintf(`// Generated TypeScript types from Go contracts +// DO NOT EDIT - This file is auto-generated by cmd/generate-types + +%s`, generateTypeDefinitions()) +} - // Create output directory if it doesn't exist - outputDir := "frontend/src/types" +func main() { + outputDir := filepath.Dir(contractsRelPath) if err := os.MkdirAll(outputDir, 0755); err != nil { fmt.Printf("Error creating output directory: %v\n", err) os.Exit(1) } - // Write the TypeScript file - outputFile := filepath.Join(outputDir, "contracts.ts") - content := fmt.Sprintf(`// Generated TypeScript types from Go contracts -// DO NOT EDIT - This file is auto-generated by cmd/generate-types - -%s`, typeDefinitions) - - if err := os.WriteFile(outputFile, []byte(content), 0600); err != nil { + if err := os.WriteFile(contractsRelPath, []byte(generateFileContent()), 0600); err != nil { fmt.Printf("Error writing TypeScript file: %v\n", err) os.Exit(1) } - fmt.Printf("Successfully generated TypeScript types: %s\n", outputFile) + fmt.Printf("Successfully generated TypeScript types: %s\n", contractsRelPath) } func generateTypeDefinitions() string { @@ -117,6 +121,7 @@ export interface HealthStatus { created: string; // ISO date string updated: string; // ISO date string isolation?: IsolationConfig; + isolation_defaults?: IsolationDefaults; // Resolved baseline values (read-only, used as placeholders) oauth_status?: 'authenticated' | 'expired' | 'error' | 'none'; // OAuth authentication status token_expires_at?: string; // ISO date string when OAuth token expires user_logged_out?: boolean; // True if user explicitly logged out (prevents auto-reconnection) @@ -135,12 +140,27 @@ export interface OAuthConfig { export interface IsolationConfig { enabled: boolean; image?: string; + network_mode?: string; + extra_args?: string[]; memory_limit?: string; cpu_limit?: string; working_dir?: string; timeout?: string; } +// IsolationDefaults reports the resolved baseline Docker isolation +// values the backend will apply when no per-server override is set. +// Populated on server-list / server-get responses; the Web UI uses these +// as placeholders so "empty = inherit" is discoverable instead of +// mysterious. Never sent back on PATCH requests. +export interface IsolationDefaults { + runtime_type?: string; + image?: string; + network_mode?: string; + extra_args?: string[]; + working_dir?: string; +} + `) // Tool types @@ -151,6 +171,12 @@ export interface IsolationConfig { schema?: Record; usage: number; last_used?: string; // ISO date string + // Mirrors contracts.Tool.Disabled on the Go side — present when an + // approval record exists for this tool. Absent means "enabled" (default). + disabled?: boolean; + // Tool-level quarantine status surfaced by the same approval record. + // Optional because non-quarantined tools simply omit the field. + approval_status?: string; } export interface SearchResult { diff --git a/cmd/generate-types/main_test.go b/cmd/generate-types/main_test.go new file mode 100644 index 00000000..80c401e6 --- /dev/null +++ b/cmd/generate-types/main_test.go @@ -0,0 +1,46 @@ +package main + +import ( + "os" + "path/filepath" + "testing" +) + +// TestContractsInSync fails when frontend/src/types/contracts.ts has drifted +// from what cmd/generate-types would produce today. Catches the failure mode +// where a contributor hand-edits contracts.ts (or hand-edits the generator's +// hardcoded string literals) without updating the other side: the next +// `make build` / `go run ./cmd/generate-types` silently reverts their work +// and leaves a dirty working tree. +// +// To fix a failure of this test: +// 1. Decide which side is correct (usually: the generator). +// 2. Run `go run ./cmd/generate-types` from the module root, OR update the +// string literals in main.go to match contracts.ts. +// 3. Commit both files in the same change. +func TestContractsInSync(t *testing.T) { + // cmd/generate-types tests run with cwd = the package directory. + // Walk up two levels to reach the module root. + contractsPath := filepath.Join("..", "..", contractsRelPath) + + committed, err := os.ReadFile(contractsPath) + if err != nil { + t.Fatalf("reading %s: %v", contractsPath, err) + } + + generated := []byte(generateFileContent()) + + if string(committed) == string(generated) { + return + } + + t.Fatalf( + "%s is out of sync with cmd/generate-types/main.go.\n"+ + "\nThe TypeScript string literals in main.go must produce a byte-identical\n"+ + "copy of contracts.ts. To fix: either run `go run ./cmd/generate-types`\n"+ + "from the module root (if the generator is the source of truth) or update\n"+ + "the string literals in main.go (if contracts.ts is the source of truth).\n"+ + "\ncommitted size: %d bytes\ngenerated size: %d bytes", + contractsRelPath, len(committed), len(generated), + ) +}