Compare commits
2
Commits
e93206a150
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d5c6b06f7 | ||
|
|
759f5f65a2 |
@@ -17,7 +17,19 @@ fun 的服务注册全在 `BindService`/`BindServiceForGen` 调用里。fun-cli
|
|||||||
(服务定义在 `package main` 也不怕)——执行后立即删除。生成由 fun 公开的
|
(服务定义在 `package main` 也不怕)——执行后立即删除。生成由 fun 公开的
|
||||||
`BindServiceForGen + SetOutput + GenCode` 完成,产物与内置生成器完全一致。
|
`BindServiceForGen + SetOutput + GenCode` 完成,产物与内置生成器完全一致。
|
||||||
|
|
||||||
## 三个命令
|
## 四个命令
|
||||||
|
|
||||||
|
### fun new —— 项目脚手架
|
||||||
|
|
||||||
|
```bash
|
||||||
|
fun new myapp # 生成 go.mod + hello world 服务
|
||||||
|
fun new github.com/me/myapp # 模块路径形式,目录取末段
|
||||||
|
fun new myapp -local ../fun # replace 指向本地 fun 源码(离线/未发布)
|
||||||
|
fun new myapp -port 9000 # 指定示例服务端口(默认 18080)
|
||||||
|
```
|
||||||
|
|
||||||
|
生成 `go.mod`、`main.go`(HelloSvc.Say 示例)、`README.md`、`.gitignore`,
|
||||||
|
自动 `go mod tidy` 后即可 `go run .` 启动。
|
||||||
|
|
||||||
### fun gen —— 生成客户端代码
|
### fun gen —— 生成客户端代码
|
||||||
|
|
||||||
|
|||||||
+8
-1
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
// fun-cli:fun 框架的配套命令行工具
|
// fun-cli:fun 框架的配套命令行工具
|
||||||
//
|
//
|
||||||
|
// fun new 脚手架:生成可运行的 fun 项目骨架
|
||||||
// fun gen [ts|go] 生成 TypeScript / Go 客户端
|
// fun gen [ts|go] 生成 TypeScript / Go 客户端
|
||||||
// fun run 热更新开发循环
|
// fun run 热更新开发循环
|
||||||
// fun docs 拉起 API 文档站(参数表单 + 在线发送测试)
|
// fun docs 拉起 API 文档站(参数表单 + 在线发送测试)
|
||||||
@@ -11,11 +12,14 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
const version = "1.0.0"
|
const version = "1.4.1"
|
||||||
|
|
||||||
const usage = `fun-cli ` + version + ` — fun 框架配套工具
|
const usage = `fun-cli ` + version + ` — fun 框架配套工具
|
||||||
|
|
||||||
用法:
|
用法:
|
||||||
|
fun new <项目名|模块路径> [-port 端口]
|
||||||
|
脚手架:生成 go.mod + 示例服务 + README,go mod tidy 后即可 go run。
|
||||||
|
|
||||||
fun gen [ts|go] [-o 目录] [-p 模块路径] [-from meta.json]
|
fun gen [ts|go] [-o 目录] [-p 模块路径] [-from meta.json]
|
||||||
生成客户端代码(fun 核心直接产出,与内置 GenCode 完全一致)。
|
生成客户端代码(fun 核心直接产出,与内置 GenCode 完全一致)。
|
||||||
扫描模块内 BindService/BindServiceForGen 调用,在对应包目录临时
|
扫描模块内 BindService/BindServiceForGen 调用,在对应包目录临时
|
||||||
@@ -30,6 +34,7 @@ const usage = `fun-cli ` + version + ` — fun 框架配套工具
|
|||||||
流式方法实时展示 NDJSON 行。默认自动 go run 拉起后端并反向代理 /cell。
|
流式方法实时展示 NDJSON 行。默认自动 go run 拉起后端并反向代理 /cell。
|
||||||
|
|
||||||
示例:
|
示例:
|
||||||
|
fun new myapp
|
||||||
fun gen ts -o frontend/src/api
|
fun gen ts -o frontend/src/api
|
||||||
fun run -p ./cmd/server
|
fun run -p ./cmd/server
|
||||||
fun docs -upstream http://127.0.0.1:9000 -p .
|
fun docs -upstream http://127.0.0.1:9000 -p .
|
||||||
@@ -44,6 +49,8 @@ func main() {
|
|||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
switch os.Args[1] {
|
switch os.Args[1] {
|
||||||
|
case "new":
|
||||||
|
cmdNew(os.Args[2:])
|
||||||
case "gen":
|
case "gen":
|
||||||
cmdGen(os.Args[2:])
|
cmdGen(os.Args[2:])
|
||||||
case "run":
|
case "run":
|
||||||
|
|||||||
+154
@@ -0,0 +1,154 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// fun new:脚手架——一条命令生成可运行的 fun 项目骨架
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var reModuleName = regexp.MustCompile(`^[a-zA-Z0-9][\w.\-/]*$`)
|
||||||
|
|
||||||
|
func cmdNew(args []string) {
|
||||||
|
// 手动解析:允许 flag 放任意位置(go flag 遇位置参数即停,不友好)
|
||||||
|
port, local, module := 18080, "", ""
|
||||||
|
for i := 0; i < len(args); i++ {
|
||||||
|
a := args[i]
|
||||||
|
if v, ok := strings.CutPrefix(a, "-port="); ok {
|
||||||
|
fmt.Sscanf(v, "%d", &port)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if v, ok := strings.CutPrefix(a, "-local="); ok {
|
||||||
|
local = v
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a == "-port" || a == "-local") && i+1 < len(args) {
|
||||||
|
i++
|
||||||
|
if a == "-port" {
|
||||||
|
fmt.Sscanf(args[i], "%d", &port)
|
||||||
|
} else {
|
||||||
|
local = args[i]
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(a, "-") {
|
||||||
|
fmt.Fprintln(os.Stderr, "fun: 未知参数:", a)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
if module != "" {
|
||||||
|
fmt.Fprintln(os.Stderr, "用法: fun new <项目名|模块路径> [-port 端口] [-local fun源码路径]")
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
module = a
|
||||||
|
}
|
||||||
|
if module == "" {
|
||||||
|
fmt.Fprintln(os.Stderr, "用法: fun new <项目名|模块路径> [-port 端口] [-local fun源码路径]")
|
||||||
|
fmt.Fprintln(os.Stderr, "示例: fun new myapp fun new github.com/me/myapp")
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
if !reModuleName.MatchString(module) || strings.Contains(module, "..") {
|
||||||
|
fmt.Fprintln(os.Stderr, "fun: 非法项目名:", module)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
// 模块路径形如 github.com/me/app 时,目录取最后一段
|
||||||
|
dir := module
|
||||||
|
if i := strings.LastIndex(module, "/"); i >= 0 {
|
||||||
|
dir = module[i+1:]
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(dir); err == nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "fun: 目录 %s 已存在\n", dir)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "fun: 创建目录失败:", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
gomod := fmt.Sprintf("module %s\n\ngo 1.22\n\nrequire github.com/cyi-cc/fun v1.4.1\n", module)
|
||||||
|
if local != "" {
|
||||||
|
abs, err := filepath.Abs(local)
|
||||||
|
if err != nil {
|
||||||
|
abs = local
|
||||||
|
}
|
||||||
|
gomod += fmt.Sprintf("\nreplace github.com/cyi-cc/fun => %s\n", filepath.ToSlash(abs))
|
||||||
|
}
|
||||||
|
files := map[string]string{
|
||||||
|
"go.mod": gomod,
|
||||||
|
"main.go": fmt.Sprintf(`package main
|
||||||
|
|
||||||
|
import "github.com/cyi-cc/fun"
|
||||||
|
|
||||||
|
type HelloDto struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type HelloSvc struct {
|
||||||
|
fun.Ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
// hello world:最小可调用的服务方法
|
||||||
|
func (s *HelloSvc) Say(dto HelloDto) (string, error) {
|
||||||
|
return "hello, " + dto.Name + "!", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
f := fun.New()
|
||||||
|
f.BindService(&HelloSvc{})
|
||||||
|
f.Start(%d)
|
||||||
|
}
|
||||||
|
`, port),
|
||||||
|
".gitignore": "/gen\n/log\nfun-docs-edits.json\n",
|
||||||
|
"README.md": fmt.Sprintf(`# %s
|
||||||
|
|
||||||
|
fun 框架项目。[文档](https://fungo.ink)
|
||||||
|
|
||||||
|
## 启动
|
||||||
|
|
||||||
|
`+"```bash"+`
|
||||||
|
go run .
|
||||||
|
`+"```"+`
|
||||||
|
|
||||||
|
## 工具链(fun-cli)
|
||||||
|
|
||||||
|
`+"```bash"+`
|
||||||
|
fun docs # API 文档站 + 在线调测(热更新)
|
||||||
|
fun run # 热更新开发循环
|
||||||
|
fun gen ts # 生成 TypeScript 客户端
|
||||||
|
`+"```"+`
|
||||||
|
`, module),
|
||||||
|
}
|
||||||
|
for name, body := range files {
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, name), []byte(body), 0o644); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "fun: 写入 %s 失败: %v\n", name, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tidy := exec.Command("go", "mod", "tidy")
|
||||||
|
tidy.Dir = dir
|
||||||
|
tidy.Stdout, tidy.Stderr = os.Stderr, os.Stderr
|
||||||
|
tidied := tidy.Run() == nil
|
||||||
|
|
||||||
|
next := "go run ."
|
||||||
|
if !tidied {
|
||||||
|
next = "go mod tidy && go run ."
|
||||||
|
}
|
||||||
|
fmt.Printf(`
|
||||||
|
已创建 %s(module %s)
|
||||||
|
─────────────────────────────────────────
|
||||||
|
%s/go.mod 依赖 github.com/cyi-cc/fun
|
||||||
|
%s/main.go HelloSvc.Say 示例服务
|
||||||
|
─────────────────────────────────────────
|
||||||
|
下一步:
|
||||||
|
cd %s
|
||||||
|
%s # 启动服务(:%d)
|
||||||
|
fun docs # 文档站在线调测
|
||||||
|
`, dir, module, dir, dir, dir, next, port)
|
||||||
|
if !tidied {
|
||||||
|
fmt.Println("\n ⚠ go mod tidy 失败(可能离线),进入目录后手动执行一次即可")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user