github.com/willdhorn/gqlgen@v0.0.0-20230430193005-695855bca25a/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/willdhorn/gqlgen/api" 16 "github.com/willdhorn/gqlgen/codegen/config" 17 "github.com/willdhorn/gqlgen/graphql" 18 "github.com/willdhorn/gqlgen/internal/code" 19 "github.com/willdhorn/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 }