github.com/geneva/gqlgen@v0.17.7-0.20230801155730-7b9317164836/main.go (about)

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