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