Files
go-omni/cmd/go-omni/main.go

215 lines
7.2 KiB
Go

package main
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"git.yuntuo.hk/k2safe/go-omni/cmd/go-omni/apigen"
"git.yuntuo.hk/k2safe/go-omni/cmd/go-omni/scaffold"
)
func main() {
if len(os.Args) < 2 {
usage()
os.Exit(2)
}
switch os.Args[1] {
case "api":
if err := runAPI(os.Args[2:]); err != nil {
fmt.Fprintln(os.Stderr, "go-omni api:", err)
os.Exit(1)
}
case "migrate":
if err := runMigrate(os.Args[2:]); err != nil {
fmt.Fprintln(os.Stderr, "go-omni migrate:", err)
os.Exit(1)
}
case "add":
if err := runAdd(os.Args[2:]); err != nil {
fmt.Fprintln(os.Stderr, "go-omni add:", err)
os.Exit(1)
}
case "new":
if err := runNew(os.Args[2:]); err != nil {
fmt.Fprintln(os.Stderr, "go-omni new:", err)
os.Exit(1)
}
case "help", "-h", "--help":
usage()
default:
fmt.Fprintf(os.Stderr, "unknown command %q\n", os.Args[1])
usage()
os.Exit(2)
}
}
func runNew(args []string) error {
options := scaffold.Options{}
var target string
var apiFile string
for index := 0; index < len(args); index++ {
arg := args[index]
switch {
case arg == "--api" || strings.HasPrefix(arg, "--api="):
_, value, inline := strings.Cut(arg, "=")
if !inline {
if index+1 >= len(args) || strings.HasPrefix(args[index+1], "-") {
return errors.New("--api requires a file")
}
index++
value = args[index]
}
if value == "" {
return errors.New("--api requires a file")
}
apiFile = value
case arg == "--log-output" || arg == "--log-format" || arg == "--storage" || strings.HasPrefix(arg, "--log-output=") || strings.HasPrefix(arg, "--log-format=") || strings.HasPrefix(arg, "--storage="):
name, value, hasValue := strings.Cut(arg, "=")
if !hasValue {
if index+1 >= len(args) || strings.HasPrefix(args[index+1], "--") {
return fmt.Errorf("%s requires a value", name)
}
index++
value = args[index]
}
if strings.TrimSpace(value) == "" {
return fmt.Errorf("%s requires a value", name)
}
switch name {
case "--log-output":
options.LogOutput = value
case "--log-format":
options.LogFormat = value
case "--storage":
options.Storage = value
}
case arg == "--force":
options.Force = true
case arg == "--database":
if index+1 >= len(args) || strings.TrimSpace(args[index+1]) == "" {
return errors.New("--database requires memory or mysql")
}
index++
options.Database = args[index]
case strings.HasPrefix(arg, "--database="):
options.Database = strings.TrimPrefix(arg, "--database=")
if strings.TrimSpace(options.Database) == "" {
return errors.New("--database requires memory or mysql")
}
case arg == "--module":
if index+1 >= len(args) {
return errors.New("--module requires a value")
}
index++
options.Module = args[index]
case strings.HasPrefix(arg, "--module="):
options.Module = strings.TrimPrefix(arg, "--module=")
case arg == "--framework-dir":
if index+1 >= len(args) {
return errors.New("--framework-dir requires a value")
}
index++
options.FrameworkDir = args[index]
case strings.HasPrefix(arg, "--framework-dir="):
options.FrameworkDir = strings.TrimPrefix(arg, "--framework-dir=")
case arg == "--framework-version":
if index+1 >= len(args) || strings.TrimSpace(args[index+1]) == "" {
return errors.New("--framework-version requires a value")
}
index++
options.FrameworkVersion = args[index]
case strings.HasPrefix(arg, "--framework-version="):
options.FrameworkVersion = strings.TrimPrefix(arg, "--framework-version=")
if strings.TrimSpace(options.FrameworkVersion) == "" {
return errors.New("--framework-version requires a value")
}
case strings.HasPrefix(arg, "-"):
return fmt.Errorf("unknown option %q", arg)
case target == "":
target = arg
default:
return fmt.Errorf("unexpected argument %q", arg)
}
}
if target == "" {
return errors.New("project directory is required")
}
if options.Module == "" && apiFile == "" {
options.Module = filepath.Base(filepath.Clean(target))
}
options.Dir = target
if options.Force {
return errors.New("managed projects do not accept --force; use api generate to update owned files")
}
o := apigen.Options{API: apiFile, Dir: target, Module: options.Module, FrameworkDir: options.FrameworkDir, FrameworkVersion: options.FrameworkVersion, LogOutput: options.LogOutput, LogFormat: options.LogFormat, Database: options.Database, Storage: options.Storage}
var err error
var out apigen.Output
if apiFile != "" {
out, err = apigen.Generate(o)
} else {
out, err = apigen.New(o)
}
printAPIDiagnostics(out.Diagnostics)
if err != nil {
return err
}
fmt.Printf("created API project %s; implement internal/logic operations; run with go run .\n", filepath.Clean(target))
return nil
}
func usage() {
fmt.Fprintln(os.Stderr, "GoOmni project tool")
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, "Usage:")
fmt.Fprintln(os.Stderr, " go-omni migrate [--dir migrations] [--apply] [--dsn-env GO_OMNI_DB_DSN]")
fmt.Fprintln(os.Stderr, " go-omni new [--module <module>] <directory>")
fmt.Fprintln(os.Stderr, " go-omni new --api <file.api> [--module <module>] <directory>")
fmt.Fprintln(os.Stderr, " go-omni api new [--module <module>] <directory> Create a runnable API project")
fmt.Fprintln(os.Stderr, " go-omni api generate [--api <file.api>] [--dir <project>] [--check|--dry-run]")
fmt.Fprintln(os.Stderr, " go-omni api inspect [--api <file.api>] Show routes, auth and business errors")
fmt.Fprintln(os.Stderr, " go-omni api export [--api <file.api>] Write OpenAPI JSON to stdout")
fmt.Fprintln(os.Stderr, " go-omni api validate --api <file.api> Validate definitions and OpenAPI")
fmt.Fprintln(os.Stderr, " go-omni add <name> [--dir <project>] Generate an isolated business module")
fmt.Fprintln(os.Stderr, " --framework-version <version> Pin a remote framework version (defaults to CLI version)")
fmt.Fprintln(os.Stderr, " --framework-dir <directory> Use a local framework checkout")
fmt.Fprintln(os.Stderr, " --database <none|memory|mysql> Defaults to none")
fmt.Fprintln(os.Stderr, " --log-output <stdout|stderr|file> Generate configured logging (default stderr)")
fmt.Fprintln(os.Stderr, " --log-format <json|text> Select structured log format (default json)")
fmt.Fprintln(os.Stderr, " --storage <none|local|s3|r2|oss> Select optional storage adapter (default none)")
}
func runAdd(args []string) error {
directory, name := ".", ""
for index := 0; index < len(args); index++ {
arg := args[index]
switch {
case arg == "--dir":
if index+1 >= len(args) {
return errors.New("--dir requires a project directory")
}
index++
directory = args[index]
case strings.HasPrefix(arg, "--dir="):
directory = strings.TrimPrefix(arg, "--dir=")
case strings.HasPrefix(arg, "-"):
return fmt.Errorf("unknown option %q", arg)
case name == "":
name = arg
default:
return fmt.Errorf("unexpected argument %q", arg)
}
}
if _, err := os.Stat(filepath.Join(directory, ".goOmni", "apiManifest.json")); err == nil {
return errors.New("managed API projects add modules through .api imports and api generate")
}
if err := scaffold.AddModule(directory, name); err != nil {
return err
}
fmt.Printf("created internal/%s; see its README.md for route registration\n", name)
return nil
}