Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 40 additions & 14 deletions cmd/generate-types/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -151,6 +171,12 @@ export interface IsolationConfig {
schema?: Record<string, any>;
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 {
Expand Down
46 changes: 46 additions & 0 deletions cmd/generate-types/main_test.go
Original file line number Diff line number Diff line change
@@ -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),
)
}
Loading