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 }