Repository: zeromicro/go-zero
Pull request #1889 (2022-05-10 15:03:59)

feat: Add goctl quickstart
```
$ goctl quickstart -h                                                
quickly start a project

Usage:
  goctl quickstart [flags]

Flags:
  -h, --help                  help for quickstart
  -t, --service-type string   specify the service type, supported value: [mono, micro] (default "mono")
```


Patch:
diff --git a/readme-cn.md b/readme-cn.md
index fb984cc5..c6472149 100644
--- a/readme-cn.md
+++ b/readme-cn.md
@@ -1,4 +1,4 @@
-<img align="right" width="150px" src="https://gitee.com/kevwan/static/raw/master/doc/images/go-zero.png">
+<img align="right" width="150px" src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/go-zero.png">
 
 # go-zero
 
@@ -267,6 +267,8 @@ go-zero 已被许多公司用于生产部署，接入场景如在线教育、电
 >59. 费芮网络
 >60. 51CTO
 >61. 聿旌科技
+>62. 山东胜软科技股份有限公司
+>63. 上海芯果科技有限公司(好特卖)
 
 如果贵公司也已使用 go-zero，欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记，仅仅为了推广，不做其它用途。
 
diff --git a/tools/goctl/api/tsgen/components.tpl b/tools/goctl/api/tsgen/components.tpl
index 5a0cd9dd..aabe0194 100644
--- a/tools/goctl/api/tsgen/components.tpl
+++ b/tools/goctl/api/tsgen/components.tpl
@@ -1,3 +1,3 @@
-/ Code generated by goctl. DO NOT EDIT.
+// Code generated by goctl. DO NOT EDIT.
 
 {{.componentTypes}}
diff --git a/tools/goctl/cmd/root.go b/tools/goctl/cmd/root.go
index ccbb0353..d2d8717c 100644
--- a/tools/goctl/cmd/root.go
+++ b/tools/goctl/cmd/root.go
@@ -16,6 +16,7 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/kube"
 	"github.com/zeromicro/go-zero/tools/goctl/migrate"
 	"github.com/zeromicro/go-zero/tools/goctl/model"
+	"github.com/zeromicro/go-zero/tools/goctl/quickstart"
 	"github.com/zeromicro/go-zero/tools/goctl/rpc"
 	"github.com/zeromicro/go-zero/tools/goctl/tpl"
 	"github.com/zeromicro/go-zero/tools/goctl/upgrade"
@@ -87,7 +88,8 @@ func supportGoStdFlag(args []string) []string {
 }
 
 func init() {
-	rootCmd.Version = fmt.Sprintf("%s %s/%s", version.BuildVersion, runtime.GOOS, runtime.GOARCH)
+	rootCmd.Version = fmt.Sprintf("%s %s/%s", version.BuildVersion,
+		runtime.GOOS, runtime.GOARCH)
 	rootCmd.AddCommand(api.Cmd)
 	rootCmd.AddCommand(bug.Cmd)
 	rootCmd.AddCommand(docker.Cmd)
@@ -95,6 +97,7 @@ func init() {
 	rootCmd.AddCommand(env.Cmd)
 	rootCmd.AddCommand(model.Cmd)
 	rootCmd.AddCommand(migrate.Cmd)
+	rootCmd.AddCommand(quickstart.Cmd)
 	rootCmd.AddCommand(rpc.Cmd)
 	rootCmd.AddCommand(tpl.Cmd)
 	rootCmd.AddCommand(upgrade.Cmd)
diff --git a/tools/goctl/go.sum b/tools/goctl/go.sum
index e7f3e3b8..6c39535c 100644
--- a/tools/goctl/go.sum
+++ b/tools/goctl/go.sum
@@ -66,6 +66,7 @@ github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:l
 github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -187,6 +188,7 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -285,6 +287,7 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
 github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
 github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -315,6 +318,7 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
 github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c=
 github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
 github.com/openzipkin/zipkin-go v0.3.0/go.mod h1:4c3sLeE8xjNqehmF5RpAFLPLJxXscc0R4l6Zg0P1tTQ=
+github.com/openzipkin/zipkin-go v0.4.0 h1:CtfRrOVZtbDj8rt1WXjklw0kqqJQwICrCKmlfUuBUUw=
 github.com/openzipkin/zipkin-go v0.4.0/go.mod h1:4c3sLeE8xjNqehmF5RpAFLPLJxXscc0R4l6Zg0P1tTQ=
 github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
@@ -327,19 +331,23 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
+github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
 github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
 github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
+github.com/prometheus/common v0.30.0 h1:JEkYlQnpzrzQFxi6gnukFPdQ+ac82oRhzMcIduJu/Ug=
 github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
 github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
 github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/rabbitmq/amqp091-go v1.1.0/go.mod h1:ogQDLSOACsLPsIq0NpbtiifNZi2YOz0VTJ0kHRghqbM=
 github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
@@ -363,6 +371,7 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
 github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
@@ -398,7 +407,9 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opentelemetry.io/otel v1.3.0 h1:APxLf0eiBwLl+SOXiJJCVYzA1OOJNyAoV8C5RNRyy7Y=
 go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs=
+go.opentelemetry.io/otel/exporters/jaeger v1.3.0 h1:HfydzioALdtcB26H5WHc4K47iTETJCdloL7VN579/L0=
 go.opentelemetry.io/otel/exporters/jaeger v1.3.0/go.mod h1:KoYHi1BtkUPncGSRtCe/eh1ijsnePhSkxwzz07vU0Fc=
+go.opentelemetry.io/otel/exporters/zipkin v1.3.0 h1:uOD28dZ7yIKITTcUS6MeAGNHYy3uhP7DTkhcJM6onlQ=
 go.opentelemetry.io/otel/exporters/zipkin v1.3.0/go.mod h1:LxGGfHIYbvsFnrJtBcazb0yG24xHdDGrT/H6RB9r3+8=
 go.opentelemetry.io/otel/sdk v1.3.0 h1:3278edCoH89MEJ0Ky8WQXVmDQv3FX4ZJ3Pp+9fJreAI=
 go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs=
@@ -635,6 +646,7 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
@@ -690,6 +702,7 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D
 google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20220228195345-15d65a4533f7 h1:ntPPoHzFW6Xp09ueznmahONZufyoSakK/piXnr2BU3I=
 google.golang.org/genproto v0.0.0-20220228195345-15d65a4533f7/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
@@ -707,6 +720,7 @@ google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTp
 google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
 google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
+google.golang.org/grpc v1.44.0 h1:weqSxi/TMs1SqFRMHCtBgXRs8k3X39QIDEZ0pRcttUg=
 google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@@ -720,6 +734,7 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
 google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/tools/goctl/model/sql/gen/gen.go b/tools/goctl/model/sql/gen/gen.go
index 29194225..ee55c0d7 100644
--- a/tools/goctl/model/sql/gen/gen.go
+++ b/tools/goctl/model/sql/gen/gen.go
@@ -65,7 +65,7 @@ func NewDefaultGenerator(dir string, cfg *config.Config, opt ...Option) (*defaul
 	}
 
 	dir = dirAbs
-	pkg := filepath.Base(dirAbs)
+	pkg := util.SafeString(filepath.Base(dirAbs))
 	err = pathx.MkdirIfNotExist(dir)
 	if err != nil {
 		return nil, err
diff --git a/tools/goctl/quickstart/cmd.go b/tools/goctl/quickstart/cmd.go
new file mode 100644
index 00000000..357b62a9
--- /dev/null
+++ b/tools/goctl/quickstart/cmd.go
@@ -0,0 +1,25 @@
+package quickstart
+
+import "github.com/spf13/cobra"
+
+const (
+	serviceTypeMono  = "mono"
+	serviceTypeMicro = "micro"
+)
+
+var (
+	varStringServiceType string
+
+	// Cmd describes the command to run.
+	Cmd = &cobra.Command{
+		Use:   "quickstart",
+		Short: "quickly start a project",
+		RunE:  run,
+	}
+)
+
+func init() {
+	Cmd.Flags().StringVarP(&varStringServiceType,
+		"service-type", "t", "mono",
+		"specify the service type, supported values: [mono, micro]")
+}
diff --git a/tools/goctl/quickstart/idl/api.yaml b/tools/goctl/quickstart/idl/api.yaml
new file mode 100644
index 00000000..b5ce1830
--- /dev/null
+++ b/tools/goctl/quickstart/idl/api.yaml
@@ -0,0 +1,3 @@
+Name: ping
+Host: 127.0.0.1
+Port: 8888
\ No newline at end of file
diff --git a/tools/goctl/quickstart/idl/apilogic.tpl b/tools/goctl/quickstart/idl/apilogic.tpl
new file mode 100644
index 00000000..d0363257
--- /dev/null
+++ b/tools/goctl/quickstart/idl/apilogic.tpl
@@ -0,0 +1,35 @@
+package logic
+
+import (
+    "context"
+
+    "github.com/zeromicro/go-zero/core/logx"
+    "greet/api/internal/svc"
+    "greet/api/internal/types"
+    {{if .callRPC}}"greet/rpc/greet"
+{{end}}
+)
+
+type PingLogic struct {
+    logx.Logger
+    ctx    context.Context
+    svcCtx *svc.ServiceContext
+}
+
+func NewPingLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PingLogic {
+    return &PingLogic{
+        Logger: logx.WithContext(ctx),
+        ctx:    ctx,
+        svcCtx: svcCtx,
+    }
+}
+
+func (l *PingLogic) Ping() (resp *types.Resp, err error) {
+    {{if .callRPC}}if _, err = l.svcCtx.GreetRpc.Ping(l.ctx, &greet.Placeholder{});err !=nil {
+        return
+    }{{end}}
+    resp = new(types.Resp)
+    resp.Msg = "pong"
+
+    return
+}
diff --git a/tools/goctl/quickstart/idl/greet.api b/tools/goctl/quickstart/idl/greet.api
new file mode 100644
index 00000000..a3091236
--- /dev/null
+++ b/tools/goctl/quickstart/idl/greet.api
@@ -0,0 +1,10 @@
+syntax = "v1"
+
+type resp {
+    msg string `json:"msg"`
+}
+
+service greet {
+    @handler ping
+    post /ping returns (resp)
+}
diff --git a/tools/goctl/quickstart/idl/greet.proto b/tools/goctl/quickstart/idl/greet.proto
new file mode 100644
index 00000000..a86caa70
--- /dev/null
+++ b/tools/goctl/quickstart/idl/greet.proto
@@ -0,0 +1,10 @@
+syntax = "proto3";
+
+package ping;
+option go_package = "./pb";
+
+message Placeholder{}
+
+service Greet {
+    rpc ping (Placeholder) returns (Placeholder);
+}
diff --git a/tools/goctl/quickstart/idl/svc.tpl b/tools/goctl/quickstart/idl/svc.tpl
new file mode 100644
index 00000000..e8c14ade
--- /dev/null
+++ b/tools/goctl/quickstart/idl/svc.tpl
@@ -0,0 +1,22 @@
+package svc
+
+import (
+    "github.com/zeromicro/go-zero/zrpc"
+    "greet/api/internal/config"
+    "greet/rpc/greet"
+)
+
+type ServiceContext struct {
+    Config   config.Config
+    GreetRpc greet.Greet
+}
+
+func NewServiceContext(c config.Config) *ServiceContext {
+    client := zrpc.MustNewClient(zrpc.RpcClientConf{
+        Target: "127.0.0.1:8080",
+    })
+    return &ServiceContext{
+        Config:   c,
+        GreetRpc: greet.NewGreet(client),
+    }
+}
diff --git a/tools/goctl/quickstart/micro.go b/tools/goctl/quickstart/micro.go
new file mode 100644
index 00000000..6122c2e3
--- /dev/null
+++ b/tools/goctl/quickstart/micro.go
@@ -0,0 +1,77 @@
+package quickstart
+
+import (
+	_ "embed"
+	"io/ioutil"
+	"path/filepath"
+
+	"github.com/zeromicro/go-zero/core/logx"
+	"github.com/zeromicro/go-zero/core/service"
+	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
+)
+
+const (
+	rpcEtcContent = `Name: greet.rpc
+ListenOn: 127.0.0.1:8080
+`
+	protoName = "greet.proto"
+)
+
+var (
+	//go:embed idl/greet.proto
+	protocContent string
+
+	zRPCWorkDir string
+)
+
+type serviceImpl struct {
+	starter func()
+}
+
+func (s serviceImpl) Start() {
+	s.starter()
+}
+
+func (s serviceImpl) Stop() {}
+
+func initRPCProto() error {
+	zRPCWorkDir = filepath.Join(projectDir, "rpc")
+	if err := pathx.MkdirIfNotExist(zRPCWorkDir); err != nil {
+		return err
+	}
+
+	protoFilename := filepath.Join(zRPCWorkDir, protoName)
+	rpcBytes := []byte(protocContent)
+	return ioutil.WriteFile(protoFilename, rpcBytes, 0666)
+}
+
+type micro struct{}
+
+func newMicroService() micro {
+	m := micro{}
+	m.mustStartRPCProject()
+	return m
+}
+
+func (m micro) mustStartRPCProject() {
+	logx.Must(initRPCProto())
+	log.Debug(">> Generating quickstart zRPC project...")
+	arg := "goctl rpc protoc " + protoName + " --go_out=. --go-grpc_out=. --zrpc_out=. --verbose"
+	execCommand(zRPCWorkDir, arg)
+	etcFile := filepath.Join(zRPCWorkDir, "etc", "greet.yaml")
+	logx.Must(ioutil.WriteFile(etcFile, []byte(rpcEtcContent), 0666))
+}
+
+func (m micro) start() {
+	mono := newMonoService(true)
+	goModTidy(projectDir)
+	sg := service.NewServiceGroup()
+	sg.Add(serviceImpl{func() {
+		log.Debug(">> Ready to start an zRPC server...")
+		goStart(zRPCWorkDir)
+	}})
+	sg.Add(serviceImpl{func() {
+		mono.start()
+	}})
+	sg.Start()
+}
diff --git a/tools/goctl/quickstart/mono.go b/tools/goctl/quickstart/mono.go
new file mode 100644
index 00000000..7a14227f
--- /dev/null
+++ b/tools/goctl/quickstart/mono.go
@@ -0,0 +1,79 @@
+package quickstart
+
+import (
+	_ "embed"
+	"io/ioutil"
+	"path/filepath"
+
+	"github.com/zeromicro/go-zero/core/logx"
+	"github.com/zeromicro/go-zero/tools/goctl/api/gogen"
+	"github.com/zeromicro/go-zero/tools/goctl/util"
+	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
+)
+
+var (
+	//go:embed idl/greet.api
+	apiContent string
+	//go:embed idl/svc.tpl
+	svcContent string
+	//go:embed idl/apilogic.tpl
+	apiLogicContent string
+	//go:embed idl/api.yaml
+	apiEtcContent string
+
+	apiWorkDir string
+)
+
+func initAPIFlags() error {
+	apiWorkDir = filepath.Join(projectDir, "api")
+	if err := pathx.MkdirIfNotExist(apiWorkDir); err != nil {
+		return err
+	}
+
+	apiFilename := filepath.Join(apiWorkDir, "greet.api")
+	apiBytes := []byte(apiContent)
+	if err := ioutil.WriteFile(apiFilename, apiBytes, 0666); err != nil {
+		return err
+	}
+
+	gogen.VarStringDir = apiWorkDir
+	gogen.VarStringAPI = apiFilename
+	return nil
+}
+
+type mono struct {
+	callRPC bool
+}
+
+func newMonoService(callRPC bool) mono {
+	m := mono{callRPC}
+	m.createAPIProject()
+	return m
+}
+
+func (m mono) createAPIProject() {
+	logx.Must(initAPIFlags())
+	log.Debug(">> Generating quickstart api project...")
+	logx.Must(gogen.GoCommand(nil, nil))
+	etcFile := filepath.Join(apiWorkDir, "etc", "greet.yaml")
+	logx.Must(ioutil.WriteFile(etcFile, []byte(apiEtcContent), 0666))
+	logicFile := filepath.Join(apiWorkDir, "internal", "logic", "pinglogic.go")
+
+	logx.Must(util.With("logic").Parse(apiLogicContent).SaveTo(map[string]bool{
+		"callRPC": m.callRPC,
+	}, logicFile, true))
+
+	if m.callRPC {
+		svcFile := filepath.Join(apiWorkDir, "internal", "svc", "servicecontext.go")
+		logx.Must(ioutil.WriteFile(svcFile, []byte(svcContent), 0666))
+	}
+}
+
+func (m mono) start() {
+	if !m.callRPC {
+		goModTidy(projectDir)
+	}
+	log.Debug(">> Ready to start an api server...")
+	log.Debug(">> Try to execute 'curl --request POST http://127.0.0.1:8888/ping' after service startup...")
+	goStart(apiWorkDir)
+}
diff --git a/tools/goctl/quickstart/quickstart.go b/tools/goctl/quickstart/quickstart.go
new file mode 100644
index 00000000..de4d2dcc
--- /dev/null
+++ b/tools/goctl/quickstart/quickstart.go
@@ -0,0 +1,79 @@
+package quickstart
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"github.com/spf13/cobra"
+	"github.com/zeromicro/go-zero/core/logx"
+	"github.com/zeromicro/go-zero/tools/goctl/util/console"
+	"github.com/zeromicro/go-zero/tools/goctl/util/ctx"
+	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
+)
+
+const baseDir = "greet"
+
+var (
+	log        = console.NewColorConsole(true)
+	projectDir string
+)
+
+func cleanWorkSpace(projectDir string) {
+	var command string
+	var breakeState bool
+	fmt.Printf("Detected that the %q already exists, do you clean up?"+
+		" [y: YES, n: NO]: ", projectDir)
+
+	for {
+		fmt.Scanln(&command)
+		switch command {
+		case "y":
+			log.Debug("Clean workspace...")
+			_ = os.RemoveAll(projectDir)
+			breakeState = true
+			break
+		case "n":
+			log.Error("User canceled")
+			os.Exit(1)
+		default:
+			fmt.Println("Invalid command, try again...")
+		}
+
+		if breakeState {
+			break
+		}
+	}
+}
+
+func initProject() {
+	wd, err := os.Getwd()
+	logx.Must(err)
+
+	projectDir = filepath.Join(wd, baseDir)
+	if exists := pathx.FileExists(projectDir); exists {
+		cleanWorkSpace(projectDir)
+	}
+
+	log.Must(pathx.MkdirIfNotExist(projectDir))
+	if hasGoMod, _ := ctx.IsGoMod(projectDir); hasGoMod {
+		return
+	}
+	if exitCode := execCommand(projectDir, "go mod init "+baseDir); exitCode != 0 {
+		log.Fatalln("Init process exit")
+	}
+}
+
+func run(_ *cobra.Command, _ []string) error {
+	initProject()
+	switch varStringServiceType {
+	case serviceTypeMono:
+		newMonoService(false).start()
+	case serviceTypeMicro:
+		newMicroService().start()
+	default:
+		return fmt.Errorf("invalid service type, expected %s | %s",
+			serviceTypeMono, serviceTypeMicro)
+	}
+	return nil
+}
diff --git a/tools/goctl/quickstart/run.go b/tools/goctl/quickstart/run.go
new file mode 100644
index 00000000..998ac1ba
--- /dev/null
+++ b/tools/goctl/quickstart/run.go
@@ -0,0 +1,35 @@
+package quickstart
+
+import (
+	"os"
+	"os/exec"
+	"runtime"
+
+	"github.com/zeromicro/go-zero/tools/goctl/vars"
+)
+
+func goStart(dir string) {
+	goproxy := "GOPROXY=https://goproxy.cn"
+	execCommand(dir, "go run .", goproxy)
+}
+
+func goModTidy(dir string) int {
+	goproxy := "GOPROXY=https://goproxy.cn"
+	log.Debug(">> go mod tidy")
+	return execCommand(dir, "go mod tidy", goproxy)
+}
+
+func execCommand(dir string, arg string, envArgs ...string) int {
+	cmd := exec.Command("sh", "-c", arg)
+	if runtime.GOOS == vars.OsWindows {
+		cmd = exec.Command("cmd.exe", "/c", arg)
+	}
+	env := append([]string(nil), os.Environ()...)
+	env = append(env, envArgs...)
+	cmd.Env = env
+	cmd.Dir = dir
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	_ = cmd.Run()
+	return cmd.ProcessState.ExitCode()
+}
diff --git a/tools/goctl/rpc/cli/zrpc.go b/tools/goctl/rpc/cli/zrpc.go
index 8610b4a5..23ff2cd2 100644
--- a/tools/goctl/rpc/cli/zrpc.go
+++ b/tools/goctl/rpc/cli/zrpc.go
@@ -20,7 +20,7 @@ var (
 
 // ZRPC generates grpc code directly by protoc and generates
 // zrpc code by goctl.
-func ZRPC(cmd *cobra.Command, args []string) error {
+func ZRPC(_ *cobra.Command, args []string) error {
 	protocArgs := wrapProtocCmd("protoc", args)
 	pwd, err := os.Getwd()
 	if err != nil {
diff --git a/tools/goctl/util/ctx/gomod.go b/tools/goctl/util/ctx/gomod.go
index 4c526b09..a5d67ba6 100644
--- a/tools/goctl/util/ctx/gomod.go
+++ b/tools/goctl/util/ctx/gomod.go
@@ -13,6 +13,10 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
 )
 
+const goModuleWithoutGoFiles = "command-line-arguments"
+
+var errInvalidGoMod = errors.New("invalid go module")
+
 // Module contains the relative data of go module,
 // which is the result of the command go list
 type Module struct {
@@ -23,6 +27,13 @@ type Module struct {
 	GoVersion string
 }
 
+func (m *Module) validate() error {
+	if m.Path == goModuleWithoutGoFiles || m.Dir == "" {
+		return errInvalidGoMod
+	}
+	return nil
+}
+
 // projectFromGoMod is used to find the go module and project file path
 // the workDir flag specifies which folder we need to detect based on
 // only valid for go mod project
@@ -43,6 +54,9 @@ func projectFromGoMod(workDir string) (*ProjectContext, error) {
 	if err != nil {
 		return nil, err
 	}
+	if err := m.validate(); err != nil {
+		return nil, err
+	}
 
 	var ret ProjectContext
 	ret.WorkDir = workDir


Test patch:
diff --git a/tools/goctl/rpc/generator/template_test.go b/tools/goctl/rpc/generator/template_test.go
index 75a74a72..7850ee60 100644
--- a/tools/goctl/rpc/generator/template_test.go
+++ b/tools/goctl/rpc/generator/template_test.go
@@ -12,13 +12,13 @@ import (
 
 func TestGenTemplates(t *testing.T) {
 	_ = Clean()
-	err := GenTemplates(nil)
+	err := GenTemplates()
 	assert.Nil(t, err)
 }
 
 func TestRevertTemplate(t *testing.T) {
 	_ = Clean()
-	err := GenTemplates(nil)
+	err := GenTemplates()
 	assert.Nil(t, err)
 	fp, err := pathx.GetTemplateDir(category)
 	if err != nil {
@@ -59,7 +59,7 @@ func TestRevertTemplate(t *testing.T) {
 
 func TestClean(t *testing.T) {
 	_ = Clean()
-	err := GenTemplates(nil)
+	err := GenTemplates()
 	assert.Nil(t, err)
 	fp, err := pathx.GetTemplateDir(category)
 	if err != nil {
@@ -78,7 +78,7 @@ func TestClean(t *testing.T) {
 
 func TestUpdate(t *testing.T) {
 	_ = Clean()
-	err := GenTemplates(nil)
+	err := GenTemplates()
 	assert.Nil(t, err)
 	fp, err := pathx.GetTemplateDir(category)
 	if err != nil {