github.com/niko0xdev/gqlgen@v0.17.55-0.20240120102243-2ecff98c3e37/main.go (about)

     1  package main
     2  
     3  //go:generate go generate ./_examples/...
     4  
     5  import (
     6  	"bytes"
     7  	_ "embed"
     8  	"errors"
     9  	"fmt"
    10  	"html/template"
    11  	"io"
    12  	"io/fs"
    13  	"log"
    14  	"os"
    15  	"path/filepath"
    16  
    17  	"github.com/urfave/cli/v2"
    18  
    19  	"github.com/niko0xdev/gqlgen/api"
    20  	"github.com/niko0xdev/gqlgen/codegen/config"
    21  	"github.com/niko0xdev/gqlgen/graphql"
    22  	"github.com/niko0xdev/gqlgen/internal/code"
    23  	"github.com/niko0xdev/gqlgen/plugin/servergen"
    24  )
    25  
    26  //go:embed init-templates/schema.graphqls
    27  var schemaFileContent string
    28  
    29  //go:embed init-templates/gqlgen.yml.gotmpl
    30  var configFileTemplate string
    31  
    32  func getConfigFileContent(pkgName string) string {
    33  	var buf bytes.Buffer
    34  	if err := template.Must(template.New("gqlgen.yml").Parse(configFileTemplate)).Execute(&buf, pkgName); err != nil {
    35  		panic(err)
    36  	}
    37  	return buf.String()
    38  }
    39  
    40  func fileExists(filename string) bool {
    41  	_, err := os.Stat(filename)
    42  	return !errors.Is(err, fs.ErrNotExist)
    43  }
    44  
    45  // see Go source code:
    46  // https://github.com/golang/go/blob/f57ebed35132d02e5cf016f324853217fb545e91/src/cmd/go/internal/modload/init.go#L1283
    47  func findModuleRoot(dir string) (roots string) {
    48  	if dir == "" {
    49  		panic("dir not set")
    50  	}
    51  	dir = filepath.Clean(dir)
    52  
    53  	// Look for enclosing go.mod.
    54  	for {
    55  		if fi, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil && !fi.IsDir() {
    56  			return dir
    57  		}
    58  		d := filepath.Dir(dir)
    59  		if d == dir { // the parent of the root is itself, so we can go no further
    60  			break
    61  		}
    62  		dir = d
    63  	}
    64  	return ""
    65  }
    66  
    67  func initFile(filename, contents string) error {
    68  	if err := os.MkdirAll(filepath.Dir(filename), 0o755); err != nil {
    69  		return fmt.Errorf("unable to create directory for file '%s': %w\n", filename, err)
    70  	}
    71  	if err := os.WriteFile(filename, []byte(contents), 0o644); err != nil {
    72  		return fmt.Errorf("unable to write file '%s': %w\n", filename, err)
    73  	}
    74  
    75  	return nil
    76  }
    77  
    78  var initCmd = &cli.Command{
    79  	Name:  "init",
    80  	Usage: "create a new gqlgen project",
    81  	Flags: []cli.Flag{
    82  		&cli.BoolFlag{Name: "verbose, v", Usage: "show logs"},
    83  		&cli.StringFlag{Name: "config, c", Usage: "the config filename", Value: "gqlgen.yml"},
    84  		&cli.StringFlag{
    85  			Name:  "server",
    86  			Usage: "where to write the server stub to",
    87  			Value: "server.go",
    88  		},
    89  		&cli.StringFlag{
    90  			Name:  "schema",
    91  			Usage: "where to write the schema stub to",
    92  			Value: "graph/schema.graphqls",
    93  		},
    94  	},
    95  	Action: func(ctx *cli.Context) error {
    96  		configFilename := ctx.String("config")
    97  		serverFilename := ctx.String("server")
    98  		schemaFilename := ctx.String("schema")
    99  
   100  		cwd, err := os.Getwd()
   101  		if err != nil {
   102  			log.Println(err)
   103  			return fmt.Errorf("unable to determine current directory:%w", err)
   104  		}
   105  		pkgName := code.ImportPathForDir(cwd)
   106  		if pkgName == "" {
   107  			return fmt.Errorf(
   108  				"unable to determine import path for current directory, you probably need to run 'go mod init' first",
   109  			)
   110  		}
   111  		modRoot := findModuleRoot(cwd)
   112  		if modRoot == "" {
   113  			return fmt.Errorf("go.mod is missing. Please, do 'go mod init' first\n")
   114  		}
   115  
   116  		// check schema and config don't already exist
   117  		for _, filename := range []string{configFilename, schemaFilename, serverFilename} {
   118  			if fileExists(filename) {
   119  				return fmt.Errorf("%s already exists", filename)
   120  			}
   121  		}
   122  		_, err = config.LoadConfigFromDefaultLocations()
   123  		if err == nil {
   124  			return fmt.Errorf("gqlgen.yml already exists in a parent directory\n")
   125  		}
   126  
   127  		// create config
   128  		fmt.Println("Creating", configFilename)
   129  		if err := initFile(configFilename, getConfigFileContent(pkgName)); err != nil {
   130  			return err
   131  		}
   132  
   133  		// create schema
   134  		fmt.Println("Creating", schemaFilename)
   135  
   136  		if err := initFile(schemaFilename, schemaFileContent); err != nil {
   137  			return err
   138  		}
   139  
   140  		// create the package directory with a temporary file so that go recognises it as a package
   141  		// and autobinding doesn't error out
   142  		tmpPackageNameFile := "graph/model/_tmp_gqlgen_init.go"
   143  		if err := initFile(tmpPackageNameFile, "package model"); err != nil {
   144  			return err
   145  		}
   146  		defer os.Remove(tmpPackageNameFile)
   147  
   148  		var cfg *config.Config
   149  		if cfg, err = config.LoadConfig(configFilename); err != nil {
   150  			panic(err)
   151  		}
   152  
   153  		fmt.Println("Creating", serverFilename)
   154  		fmt.Println("Generating...")
   155  		if err := api.Generate(cfg, api.AddPlugin(servergen.New(serverFilename))); err != nil {
   156  			return err
   157  		}
   158  
   159  		fmt.Printf("\nExec \"go run ./%s\" to start GraphQL server\n", serverFilename)
   160  		return nil
   161  	},
   162  }
   163  
   164  var generateCmd = &cli.Command{
   165  	Name:  "generate",
   166  	Usage: "generate a graphql server based on schema",
   167  	Flags: []cli.Flag{
   168  		&cli.BoolFlag{Name: "verbose, v", Usage: "show logs"},
   169  		&cli.StringFlag{Name: "config, c", Usage: "the config filename"},
   170  	},
   171  	Action: func(ctx *cli.Context) error {
   172  		var cfg *config.Config
   173  		var err error
   174  		if configFilename := ctx.String("config"); configFilename != "" {
   175  			cfg, err = config.LoadConfig(configFilename)
   176  			if err != nil {
   177  				return err
   178  			}
   179  		} else {
   180  			cfg, err = config.LoadConfigFromDefaultLocations()
   181  			if errors.Is(err, fs.ErrNotExist) {
   182  				cfg, err = config.LoadDefaultConfig()
   183  			}
   184  
   185  			if err != nil {
   186  				return err
   187  			}
   188  		}
   189  
   190  		if err = api.Generate(cfg); err != nil {
   191  			return err
   192  		}
   193  		return nil
   194  	},
   195  }
   196  
   197  var versionCmd = &cli.Command{
   198  	Name:  "version",
   199  	Usage: "print the version string",
   200  	Action: func(ctx *cli.Context) error {
   201  		fmt.Println(graphql.GetVersion())
   202  		return nil
   203  	},
   204  }
   205  
   206  func main() {
   207  	app := cli.NewApp()
   208  	app.Name = "gqlgen"
   209  	app.Usage = generateCmd.Usage
   210  	app.Description = "This is a library for quickly creating strictly typed graphql servers in golang. See https://gqlgen.com/ for a getting started guide."
   211  	app.HideVersion = true
   212  	app.Flags = generateCmd.Flags
   213  	app.Version = graphql.GetVersion()
   214  	app.Before = func(context *cli.Context) error {
   215  		if context.Bool("verbose") {
   216  			log.SetFlags(0)
   217  		} else {
   218  			log.SetOutput(io.Discard)
   219  		}
   220  		return nil
   221  	}
   222  
   223  	app.Action = generateCmd.Action
   224  	app.Commands = []*cli.Command{
   225  		generateCmd,
   226  		initCmd,
   227  		versionCmd,
   228  	}
   229  
   230  	if err := app.Run(os.Args); err != nil {
   231  		fmt.Fprint(os.Stderr, err.Error()+"\n")
   232  		os.Exit(1)
   233  	}
   234  }