package main // Frontend TypeScript checker. Runs tsc in noEmit mode using // tsconfig.json. Requires node on PATH; downloads the pinned TypeScript // release on first run (no npm). // // go run ./cmd/typecheck // go run ./cmd/typecheck -p tsconfig.json import ( "archive/tar" "compress/gzip" "flag" "fmt" "io" "net/http" "os" "os/exec" "path/filepath" "strings" ) const typescriptVersion = "5.8.3" func main() { tsconfig := flag.String("p", "tsconfig.json", "path to tsconfig.json") flag.Parse() if err := runTypecheck(*tsconfig); err != nil { fmt.Fprintf(os.Stderr, "Typecheck failed: %v\n", err) os.Exit(1) } } func runTypecheck(tsconfig string) error { node, err := exec.LookPath("node") if err != nil { return fmt.Errorf("node not found on PATH (required to run tsc): %w", err) } tsc, err := ensureTypeScript() if err != nil { return err } if _, err := os.Stat(tsconfig); err != nil { return fmt.Errorf("tsconfig not found: %s", tsconfig) } fmt.Printf("Typechecking with TypeScript %s...\n", typescriptVersion) cmd := exec.Command(node, tsc, "--noEmit", "-p", tsconfig) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { return err } fmt.Println("No type errors.") return nil } func ensureTypeScript() (string, error) { root, err := os.Getwd() if err != nil { return "", err } cacheDir := filepath.Join(root, "tools", ".cache", "typescript", typescriptVersion) tscPath := filepath.Join(cacheDir, "package", "lib", "tsc.js") if _, err := os.Stat(tscPath); err == nil { return tscPath, nil } fmt.Printf("Downloading TypeScript %s...\n", typescriptVersion) if err := downloadTypeScript(cacheDir); err != nil { return "", err } if _, err := os.Stat(tscPath); err != nil { return "", fmt.Errorf("tsc not found after download: %s", tscPath) } return tscPath, nil } func downloadTypeScript(destDir string) error { url := fmt.Sprintf("https://registry.npmjs.org/typescript/-/typescript-%s.tgz", typescriptVersion) resp, err := http.Get(url) if err != nil { return fmt.Errorf("download typescript: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("download typescript: HTTP %s", resp.Status) } if err := os.MkdirAll(destDir, 0o755); err != nil { return err } return extractTGZ(resp.Body, destDir) } func extractTGZ(r io.Reader, destDir string) error { gz, err := gzip.NewReader(r) if err != nil { return fmt.Errorf("read typescript archive: %w", err) } defer gz.Close() tr := tar.NewReader(gz) cleanDest := filepath.Clean(destDir) for { hdr, err := tr.Next() if err == io.EOF { return nil } if err != nil { return fmt.Errorf("read typescript archive: %w", err) } target := filepath.Join(destDir, filepath.FromSlash(hdr.Name)) cleanTarget := filepath.Clean(target) if cleanTarget != cleanDest && !strings.HasPrefix(cleanTarget, cleanDest+string(os.PathSeparator)) { return fmt.Errorf("invalid archive path: %s", hdr.Name) } switch hdr.Typeflag { case tar.TypeDir: if err := os.MkdirAll(target, 0o755); err != nil { return err } case tar.TypeReg: if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil { return err } f, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(hdr.Mode)&0o777|0o600) if err != nil { return err } if _, err := io.Copy(f, tr); err != nil { f.Close() return err } if err := f.Close(); err != nil { return err } } } }