1 package main
2
3
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/99designs/gqlgen/api"
20 "github.com/99designs/gqlgen/codegen/config"
21 "github.com/99designs/gqlgen/graphql"
22 "github.com/99designs/gqlgen/internal/code"
23 "github.com/99designs/gqlgen/plugin/servergen"
24 )
25
26
27 var schemaFileContent string
28
29
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
46
47 func findModuleRoot(dir string) (roots string) {
48 if dir == "" {
49 panic("dir not set")
50 }
51 dir = filepath.Clean(dir)
52
53
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 {
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
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
128 fmt.Println("Creating", configFilename)
129 if err := initFile(configFilename, getConfigFileContent(pkgName)); err != nil {
130 return err
131 }
132
133
134 fmt.Println("Creating", schemaFilename)
135
136 if err := initFile(schemaFilename, schemaFileContent); err != nil {
137 return err
138 }
139
140
141
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.Version)
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.Version
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 }
235
View as plain text