cmd

package
v1.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 22, 2026 License: MIT Imports: 22 Imported by: 0

Documentation

Overview

Package cmd contains core TypeGo CLI commands.

Index

Constants

This section is empty.

Variables

View Source
var BuildCmd = &cobra.Command{
	Use:   "build [file]",
	Short: "Build and bundle a TypeScript file for production",
	Args:  cobra.ExactArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		filename := args[0]
		absPath, _ := filepath.Abs(filename)

		fmt.Printf("📦 Building %s...\n", absPath)

		res, _ := compiler.Compile(absPath, nil)

		tmpDir := ".typego_build_tmp"
		if err := os.MkdirAll(tmpDir, 0755); err != nil {
			fmt.Printf("Error creating temp dir: %v\n", err)
			os.Exit(1)
		}
		defer os.RemoveAll(tmpDir)

		fetcher, err := linker.NewFetcher()
		if err != nil {
			fmt.Printf("Failed to init fetcher: %v\n", err)
			os.Exit(1)
		}
		defer fetcher.Cleanup()

		virtualModules := make(map[string]string)
		var bindBlock string

		if res != nil {
			for _, imp := range res.Imports {
				if len(imp) > 3 && imp[:3] == "go:" {
					cleanImp := imp[3:]

					fmt.Printf("🔍 Inspecting %s...\n", cleanImp)
					_ = fetcher.Get(cleanImp)

					info, err := linker.Inspect(cleanImp, fetcher.TempDir)
					if err != nil {
						fmt.Printf("Warning: Could not inspect %s: %v\n", cleanImp, err)
						continue
					}

					bindBlock += linker.GenerateShim(info, "pkg_"+info.Name)

					var vmContent strings.Builder
					for _, fn := range info.Exports {

						vmContent.WriteString(fmt.Sprintf("export const %s = (globalThis as any)._go_hyper_%s.%s;\n", fn.Name, info.Name, fn.Name))
					}

					virtualModules[imp] = vmContent.String()
				}
			}
		}

		fmt.Println("🔨 Compiling binary (Pass 2)...")
		res, err = compiler.Compile(absPath, virtualModules)
		if err != nil {
			fmt.Printf("Build Error: %v\n", err)
			os.Exit(1)
		}

		var importBlock strings.Builder
		for _, imp := range res.Imports {
			if len(imp) > 3 && imp[:3] == "go:" {
				cleanImp := imp[3:]

				if cleanImp == "fmt" || cleanImp == "os" {
					continue
				}
				importBlock.WriteString(fmt.Sprintf("\t\"%s\"\n", cleanImp))
			}
		}

		shimContent := fmt.Sprintf(builder.ShimTemplate, importBlock.String(), fmt.Sprintf("%q", res.JS), bindBlock, MemoryLimit*1024*1024)

		shimPath := filepath.Join(tmpDir, "main.go")
		if err := os.WriteFile(shimPath, []byte(shimContent), 0644); err != nil {
			fmt.Printf("Error writing shim: %v\n", err)
			os.Exit(1)
		}

		goModContent := `module typego_app

go 1.23.6
`

		if err := os.WriteFile(filepath.Join(tmpDir, "go.mod"), []byte(goModContent), 0644); err != nil {
			fmt.Printf("Error writing go.mod: %v\n", err)
			os.Exit(1)
		}

		cwd, _ := os.Getwd()
		typegoRoot, isLocalDev := ecosystem.FindRepoRoot(cwd)

		if isLocalDev {
			fmt.Println("🔧 typego dev mode: using local source replacement at", typegoRoot)
			replaceCmd := exec.Command("go", "mod", "edit", "-replace", "github.com/repyh/typego="+typegoRoot)
			replaceCmd.Dir = tmpDir
			_ = replaceCmd.Run()
		}

		packages := []string{
			"github.com/repyh/typego/bridge/core",
			"github.com/repyh/typego/bridge/polyfills",
			"github.com/repyh/typego/engine",
			"github.com/repyh/typego/eventloop",
			"github.com/grafana/sobek",
		}
		for _, pkg := range packages {

			target := pkg
			if !isLocalDev {
				target = pkg + "@latest"
			}
			getCmd := exec.Command("go", "get", target)
			getCmd.Dir = tmpDir
			if isLocalDev {
				getCmd.Env = append(os.Environ(), "GOPROXY=off")
			} else {
				getCmd.Env = append(os.Environ(), "GOPROXY=direct")
			}
			_ = getCmd.Run()
		}

		outputName := buildOut
		if outputName == "" {
			outputName = "app.exe"
		}

		absOut, _ := filepath.Abs(outputName)

		fmt.Println("🧹 Resolving dependencies...")
		tidyCmd := exec.Command("go", "mod", "tidy")
		tidyCmd.Dir = tmpDir
		tidyCmd.Env = append(os.Environ(), "GOPROXY=direct")
		tidyCmd.Stdout = os.Stdout
		tidyCmd.Stderr = os.Stderr
		if err := tidyCmd.Run(); err != nil {
			fmt.Printf("go mod tidy failed: %v\n", err)
			os.Exit(1)
		}

		fmt.Println("🔨 Compiling binary...")
		buildCmd := exec.Command("go", "build", "-o", absOut, ".")
		buildCmd.Dir = tmpDir
		buildCmd.Stdout = os.Stdout
		buildCmd.Stderr = os.Stderr

		env := os.Environ()
		if buildTarget != "" {
			if target, ok := supportedTargets[buildTarget]; ok {
				fmt.Printf("🎯 Targeting %s/%s...\n", target.goos, target.goarch)
				env = append(env, "GOOS="+target.goos, "GOARCH="+target.goarch)

				env = append(env, "CGO_ENABLED=0")
			} else {
				fmt.Printf("Error: Unsupported target '%s'. Available targets:\n", buildTarget)
				for t := range supportedTargets {
					fmt.Printf("  - %s\n", t)
				}
				os.Exit(1)
			}
		}
		buildCmd.Env = env

		if err := buildCmd.Run(); err != nil {
			fmt.Printf("Compilation failed: %v\n", err)
			os.Exit(1)
		}

		fmt.Printf("✨ Binary created: %s\n", outputName)
	},
}
View Source
var DevCmd = &cobra.Command{
	Use:   "dev [file]",
	Short: "Start development server with hot-reload",
	Long: `Start a development server that watches for file changes and automatically
restarts. Provides colored output and compilation timing.`,
	Args: cobra.ExactArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		filename := args[0]
		cwd, _ := os.Getwd()

		if ecosystem.IsHandoffRequired(cwd) {
			binaryPath, _ := ecosystem.GetJITBinaryPath(cwd)

			handoff := exec.Command(binaryPath, os.Args[1:]...)
			handoff.Stdout = os.Stdout
			handoff.Stderr = os.Stderr
			handoff.Stdin = os.Stdin
			handoff.Env = append(os.Environ(), ecosystem.HandoffEnvVar+"=true")

			if err := handoff.Run(); err != nil {
				if exitErr, ok := err.(*exec.ExitError); ok {
					os.Exit(exitErr.ExitCode())
				}
				fmt.Printf("Handoff failed: %v\n", err)
				os.Exit(1)
			}
			os.Exit(0)
		}

		absPath, err := filepath.Abs(filename)
		if err != nil {
			printError("Failed to resolve path: %v", err)
			return
		}

		if _, err := os.Stat(absPath); os.IsNotExist(err) {
			printError("File not found: %s", filename)
			return
		}

		printBanner()
		printInfo("Watching %s", filepath.Base(filename))
		printInfo("Press Ctrl+C to stop")
		fmt.Println()

		sig := make(chan os.Signal, 1)
		signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)

		runDevProcess(absPath)

		lastMod := getDevLastMod(absPath)
		ticker := time.NewTicker(150 * time.Millisecond)
		defer ticker.Stop()

		for {
			select {
			case <-sig:
				fmt.Println()
				printWarning("Shutting down...")
				killDevProcess()
				printSuccess("Development server stopped")
				return
			case <-ticker.C:
				currentMod := getDevLastMod(absPath)
				if currentMod.After(lastMod) {
					fmt.Println()
					printInfo("Change detected, restarting...")
					killDevProcess()
					runDevProcess(absPath)
					lastMod = currentMod
				}
			}
		}
	},
}
View Source
var InitCmd = &cobra.Command{
	Use:   "init [name]",
	Short: "Initialize a new TypeGo project",
	Long: `Initialize a new TypeGo project with Go-first defaults.

By default, npm is NOT initialized. Use --npm flag if you need npm packages.
TypeScript type definitions are automatically generated.

Examples:
  typego init              # Initialize in current directory
  typego init my-app       # Create my-app directory and initialize
  typego init --npm        # Include npm/package.json setup`,
	Run: func(cmd *cobra.Command, args []string) {
		projectDir := "."
		if len(args) > 0 {
			projectDir = args[0]
			if err := os.MkdirAll(projectDir, 0755); err != nil {
				fmt.Printf("❌ Error creating project directory: %v\n", err)
				return
			}
		}

		fmt.Println("🚀 Creating TypeGo project...")
		fmt.Println()

		dirs := []string{
			filepath.Join(projectDir, "src"),
			filepath.Join(projectDir, ".typego", "types"),
		}
		for _, dir := range dirs {
			if err := os.MkdirAll(dir, 0755); err != nil {
				fmt.Printf("❌ Error creating directory %s: %v\n", dir, err)
				return
			}
		}

		files := []struct {
			path    string
			content string
			name    string
		}{
			{filepath.Join(projectDir, "src", "index.ts"), indexTemplate, "src/index.ts"},
			{filepath.Join(projectDir, "typego.modules.json"), modulesTemplate, "typego.modules.json"},
			{filepath.Join(projectDir, "tsconfig.json"), tsConfigTemplate, "tsconfig.json"},
		}

		fmt.Println("📁 Created project structure:")
		for _, f := range files {
			if _, err := os.Stat(f.path); os.IsNotExist(err) {
				if err := os.WriteFile(f.path, []byte(f.content), 0644); err != nil {
					fmt.Printf("❌ Error creating %s: %v\n", f.name, err)
					return
				}
				fmt.Printf("   ├── %s\n", f.name)
			} else {
				fmt.Printf("   ├── %s (exists, skipped)\n", f.name)
			}
		}

		fmt.Println("   └── .typego/types/go.d.ts")
		typesCmd := exec.Command("typego", "types")
		typesCmd.Dir = projectDir
		if err := typesCmd.Run(); err != nil {
			fmt.Println("   ⚠️  Could not auto-generate types. Run 'typego types' manually.")
		}

		if initWithNpm {
			fmt.Println()
			fmt.Println("📦 Initializing npm (--npm flag)...")
			if _, err := os.Stat(filepath.Join(projectDir, "package.json")); os.IsNotExist(err) {
				npmInit := exec.Command("npm", "init", "-y")
				npmInit.Dir = projectDir
				if err := npmInit.Run(); err != nil {
					fmt.Printf("   ⚠️  npm init failed: %v\n", err)
				} else {
					npmInstall := exec.Command("npm", "install", "-D", "@types/node")
					npmInstall.Dir = projectDir
					if err := npmInstall.Run(); err != nil {
						fmt.Printf("   ⚠️  failed to install @types/node: %v\n", err)
					} else {
						fmt.Println("   ✅ Installed @types/node")
					}
				}
			}
		}

		fmt.Println()
		fmt.Println("📋 Next steps:")
		if projectDir != "." {
			fmt.Printf("   1. cd %s\n", projectDir)
			fmt.Println("   2. typego run src/index.ts")
		} else {
			fmt.Println("   1. typego run src/index.ts")
		}
		fmt.Println()
		fmt.Println("💡 Tip: Add Go dependencies with 'typego add github.com/gin-gonic/gin'")
	},
}
View Source
var MemoryLimit uint64 = 128
View Source
var RunCmd = &cobra.Command{
	Use:   "run [file]",
	Short: "Run a TypeScript file",
	Long: `Run a TypeScript file using the TypeGo engine.

By default, uses interpreter mode for fast execution (<1s startup).
Use --compile to generate a standalone Go binary (slower, but more portable).`,
	Args: cobra.ExactArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		filename := args[0]
		cwd, _ := os.Getwd()

		if !compileMode && ecosystem.IsHandoffRequired(cwd) {
			binaryPath, _ := ecosystem.GetJITBinaryPath(cwd)

			handoff := exec.Command(binaryPath, os.Args[1:]...)
			handoff.Stdout = os.Stdout
			handoff.Stderr = os.Stderr
			handoff.Stdin = os.Stdin
			handoff.Env = append(os.Environ(), ecosystem.HandoffEnvVar+"=true")

			if err := handoff.Run(); err != nil {
				if exitErr, ok := err.(*exec.ExitError); ok {
					os.Exit(exitErr.ExitCode())
				}
				fmt.Printf("Handoff failed: %v\n", err)
				os.Exit(1)
			}
			os.Exit(0)
		}

		if compileMode {
			runStandalone(filename)
		} else {
			if err := runInterpreter(filename); err != nil {
				fmt.Printf("Error: %v\n", err)
				os.Exit(1)
			}
		}
	},
}
View Source
var TypesCmd = &cobra.Command{
	Use:   "types [file]",
	Short: "Sync and update TypeGo ambient definitions",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("Syncing TypeGo definitions...")

		dtsPath := filepath.Join(".typego", "types", "go.d.ts")
		if err := os.MkdirAll(filepath.Dir(dtsPath), 0755); err != nil {
			fmt.Printf("Error: %v\n", err)
			return
		}

		// Read existing (target) file first
		var currentContent []byte
		if existing, err := os.ReadFile(dtsPath); err == nil && len(existing) > 0 {
			currentContent = existing
		}

		newGlobal := string(core.GlobalTypes) + "\n" + intrinsics.IntrinsicTypes

		reGlobal := regexp.MustCompile(`(?s)^// TypeGo Type Definitions.*?// TypeGo Namespaces`)
		if reGlobal.Match(currentContent) {
			currentContent = reGlobal.ReplaceAll(currentContent, []byte(newGlobal))
		} else if len(currentContent) == 0 {
			currentContent = []byte(newGlobal)
		} else {

			currentContent = append([]byte(newGlobal+"\n"), currentContent...)
		}

		fetcher, err := linker.NewFetcher()
		if err != nil {
			fmt.Printf("Failed to init fetcher: %v\n", err)
			return
		}
		defer fetcher.Cleanup()

		fileSet := make(map[string]bool)
		if len(args) > 0 {

			absPath, _ := filepath.Abs(args[0])
			fileSet[absPath] = true
		} else {

			err := filepath.WalkDir(".", func(path string, d os.DirEntry, err error) error {
				if err != nil {
					return err
				}

				if d.IsDir() {
					name := d.Name()
					if name == "node_modules" || name == ".typego" || name == ".git" || name == ".gemini" {
						return filepath.SkipDir
					}
					return nil
				}

				if filepath.Ext(path) == ".ts" {
					absPath, _ := filepath.Abs(path)
					fileSet[absPath] = true
				}
				return nil
			})
			if err != nil {
				fmt.Printf("Error searching for TypeScript files: %v\n", err)
			}
		}

		goImports := make(map[string]bool)

		coreModules := []string{
			"go:fmt", "go:os", "go:net/url",
		}
		for _, mod := range coreModules {
			goImports[mod] = true
		}

		for file := range fileSet {
			fmt.Printf("🔍 Scanning %s...\n", filepath.Base(file))
			res, _ := compiler.Compile(file, nil)
			if res != nil {
				for _, imp := range res.Imports {
					if strings.HasPrefix(imp, "go:") || strings.HasPrefix(imp, "typego:") {
						goImports[imp] = true
					}
				}
			}
		}

		processed := make(map[string]bool)
		queue := []string{}
		for imp := range goImports {
			queue = append(queue, imp)
		}

		for len(queue) > 0 {
			imp := queue[0]
			queue = queue[1:]

			if processed[imp] {
				continue
			}
			processed[imp] = true

			if imp == "go:net/http" || imp == "go:sync" || imp == "typego:memory" || imp == "typego:worker" || imp == "go:memory" || imp == "go:crypto" {
				continue
			}

			fmt.Printf("📦 Generating types for %s...\n", imp)

			var pkgPath string
			if strings.HasPrefix(imp, "go:") {
				pkgPath = strings.TrimPrefix(imp, "go:")
			} else {
				continue
			}

			isInternal := strings.HasPrefix(pkgPath, "github.com/repyh/typego")
			if !isInternal {
				if err := fetcher.Get(pkgPath); err != nil {
					fmt.Printf("Warning: Failed to fetch %s: %v\n", pkgPath, err)
					continue
				}
			}

			info, err := linker.Inspect(pkgPath, fetcher.TempDir)
			if err != nil {
				fmt.Printf("Failed to inspect %s: %v\n", pkgPath, err)
				continue
			}

			for _, st := range info.Structs {
				for _, field := range st.Fields {
					if field.ImportPath != "" && field.ImportPath != pkgPath {
						newImp := "go:" + field.ImportPath
						if !processed[newImp] {
							queue = append(queue, newImp)
						}
					}
				}
				for _, embed := range st.Embeds {
					if embed.ImportPath != "" && embed.ImportPath != pkgPath {
						newImp := "go:" + embed.ImportPath
						if !processed[newImp] {
							queue = append(queue, newImp)
						}
					}
				}
			}

			info.ImportPath = pkgPath

			newTypeBlock := linker.GenerateTypes(info)

			pattern := fmt.Sprintf(`(?s)// MODULE: %s.*?// END: %s\n`, regexp.QuoteMeta(imp), regexp.QuoteMeta(imp))
			re := regexp.MustCompile(pattern)

			if re.Match(currentContent) {
				currentContent = re.ReplaceAll(currentContent, []byte(newTypeBlock))
			} else {
				currentContent = append(currentContent, []byte("\n"+newTypeBlock)...)
			}
		}

		currentContent = updateTypeBlock(currentContent, "go:net/http", string(bridge_net.HttpTypes))
		currentContent = updateTypeBlock(currentContent, "go:sync", string(bridge_sync.Types))
		currentContent = updateTypeBlock(currentContent, "go:memory", string(bridge_memory.Types))
		currentContent = updateTypeBlock(currentContent, "typego:memory", string(bridge_memory.Types))
		currentContent = updateTypeBlock(currentContent, "typego:worker", string(bridge_worker.Types))
		currentContent = updateTypeBlock(currentContent, "go:crypto", string(bridge_crypto.Types))

		if err := os.WriteFile(dtsPath, currentContent, 0644); err != nil {
			fmt.Printf("Error writing types: %v\n", err)
			return
		}

		fmt.Println("✅ Definitions synced to .typego/types/go.d.ts")
	},
}
View Source
var WatchCmd = &cobra.Command{
	Use:   "watch [file]",
	Short: "Run a file and restart on changes",
	Args:  cobra.ExactArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		filename := args[0]
		absPath, err := filepath.Abs(filename)
		if err != nil {
			fmt.Printf("Error: %v\n", err)
			return
		}

		fmt.Printf("👀 Watching %s...\n", filepath.Base(filename))

		sig := make(chan os.Signal, 1)
		signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)

		runProcess(absPath)

		lastMod := getLastMod(absPath)
		ticker := time.NewTicker(200 * time.Millisecond)
		defer ticker.Stop()

		for {
			select {
			case <-sig:
				fmt.Println("\nStopped watching.")
				killProcess()
				return
			case <-ticker.C:
				currentMod := getLastMod(absPath)
				if currentMod.After(lastMod) {
					fmt.Println("🔄 Change detected, restarting...")
					killProcess()
					runProcess(absPath)
					lastMod = currentMod
				}
			}
		}
	},
}

Functions

This section is empty.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL