Merge pull request #335 from trheyi/v0.10.3-dev
[add] migrate commands & studio
This commit is contained in:
commit
8bc61ad817
16 changed files with 121 additions and 620 deletions
2
Makefile
2
Makefile
|
|
@ -9,7 +9,7 @@ COMMIT := $(shell git log | head -n 1 | awk '{print substr($$2, 0, 12)}')
|
|||
NOW := $(shell date +"%FT%T%z")
|
||||
|
||||
# ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
|
||||
TESTFOLDER := $(shell $(GO) list ./... | grep -E 'api|model|flow|script|fs|i18n|connector|query|plugin|cert|crypto|task|schedule|runtime|helper|utils|widget|importer|store|widgets|engine|service' | grep -vE 'examples|tests*|config')
|
||||
TESTFOLDER := $(shell $(GO) list ./... | grep -vE 'examples|tests|share*')
|
||||
TESTTAGS ?= ""
|
||||
|
||||
# TESTWIDGETS := $(shell $(GO) list ./widgets/...)
|
||||
|
|
|
|||
16
cmd/run.go
16
cmd/run.go
|
|
@ -33,13 +33,17 @@ var runCmd = &cobra.Command{
|
|||
Boot()
|
||||
cfg := config.Conf
|
||||
cfg.Session.IsCLI = true
|
||||
engine.Load(cfg)
|
||||
if len(args) < 1 {
|
||||
fmt.Println(color.RedString(L("Not enough arguments")))
|
||||
fmt.Println(color.WhiteString(share.BUILDNAME + " help"))
|
||||
return
|
||||
}
|
||||
|
||||
err := engine.Load(cfg)
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString(L("Engine: %s"), err.Error()))
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
fmt.Println(color.GreenString(L("Run: %s"), name))
|
||||
pargs := []interface{}{}
|
||||
|
|
@ -48,7 +52,7 @@ var runCmd = &cobra.Command{
|
|||
continue
|
||||
}
|
||||
|
||||
// 解析参数
|
||||
// Parse the arguments
|
||||
if strings.HasPrefix(arg, "::") {
|
||||
arg := strings.TrimPrefix(arg, "::")
|
||||
var v interface{}
|
||||
|
|
@ -59,10 +63,12 @@ var runCmd = &cobra.Command{
|
|||
}
|
||||
pargs = append(pargs, v)
|
||||
fmt.Println(color.WhiteString("args[%d]: %s", i-1, arg))
|
||||
|
||||
} else if strings.HasPrefix(arg, "\\::") {
|
||||
arg := "::" + strings.TrimPrefix(arg, "\\::")
|
||||
pargs = append(pargs, arg)
|
||||
fmt.Println(color.WhiteString("args[%d]: %s", i-1, arg))
|
||||
|
||||
} else {
|
||||
pargs = append(pargs, arg)
|
||||
fmt.Println(color.WhiteString("args[%d]: %s", i-1, arg))
|
||||
|
|
@ -71,7 +77,11 @@ var runCmd = &cobra.Command{
|
|||
}
|
||||
|
||||
process := process.New(name, pargs...)
|
||||
res := process.Run()
|
||||
res, err := process.Exec()
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString(L("Process: %s"), err.Error()))
|
||||
}
|
||||
|
||||
fmt.Println(color.WhiteString("--------------------------------------"))
|
||||
fmt.Println(color.WhiteString(L("%s Response"), name))
|
||||
fmt.Println(color.WhiteString("--------------------------------------"))
|
||||
|
|
|
|||
21
cmd/start.go
21
cmd/start.go
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/yaoapp/gou/api"
|
||||
"github.com/yaoapp/gou/connector"
|
||||
"github.com/yaoapp/gou/fs"
|
||||
"github.com/yaoapp/gou/plugin"
|
||||
"github.com/yaoapp/gou/schedule"
|
||||
"github.com/yaoapp/gou/server/http"
|
||||
"github.com/yaoapp/gou/store"
|
||||
|
|
@ -24,6 +25,7 @@ import (
|
|||
"github.com/yaoapp/yao/service"
|
||||
"github.com/yaoapp/yao/setup"
|
||||
"github.com/yaoapp/yao/share"
|
||||
"github.com/yaoapp/yao/studio"
|
||||
)
|
||||
|
||||
var startDebug = false
|
||||
|
|
@ -35,6 +37,9 @@ var startCmd = &cobra.Command{
|
|||
Long: L("Start Engine"),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
defer share.SessionStop()
|
||||
defer plugin.KillAll()
|
||||
|
||||
// Setup
|
||||
if setup.Check() {
|
||||
go setup.Start()
|
||||
|
|
@ -111,14 +116,14 @@ var startCmd = &cobra.Command{
|
|||
if mode == "development" {
|
||||
|
||||
// Start Studio Server
|
||||
// go func() {
|
||||
// err := studio.Start(config.Conf)
|
||||
// if err != nil {
|
||||
// fmt.Println(color.RedString(L("Fatal: %s"), err.Error()))
|
||||
// os.Exit(2)
|
||||
// }
|
||||
// }()
|
||||
// defer studio.Stop()
|
||||
go func() {
|
||||
err := studio.Start(config.Conf)
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString(L("Fatal: %s"), err.Error()))
|
||||
os.Exit(2)
|
||||
}
|
||||
}()
|
||||
defer studio.Stop()
|
||||
|
||||
printApis(false)
|
||||
printTasks(false)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/yaoapp/yao/config"
|
||||
"github.com/yaoapp/yao/engine"
|
||||
"github.com/yaoapp/yao/share"
|
||||
"github.com/yaoapp/yao/studio"
|
||||
)
|
||||
|
||||
// RunCmd command
|
||||
|
|
@ -34,13 +35,23 @@ var RunCmd = &cobra.Command{
|
|||
Boot()
|
||||
cfg := config.Conf
|
||||
cfg.Session.IsCLI = true
|
||||
engine.Load(cfg)
|
||||
|
||||
if len(args) < 1 {
|
||||
fmt.Println(color.RedString(L("Not enough arguments")))
|
||||
fmt.Println(color.WhiteString(share.BUILDNAME + " help"))
|
||||
return
|
||||
}
|
||||
|
||||
err := engine.Load(cfg)
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString(L("Engine: %s"), err.Error()))
|
||||
}
|
||||
|
||||
err = studio.Load(cfg)
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString(L("Studio: %s"), err.Error()))
|
||||
}
|
||||
|
||||
name := strings.Split(args[0], ".")
|
||||
service := strings.Join(name[0:len(name)-1], ".")
|
||||
method := name[len(name)-1]
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ func init() {
|
|||
Conf = Load()
|
||||
return
|
||||
}
|
||||
|
||||
Conf = LoadFrom(filename)
|
||||
if Conf.Mode == "production" {
|
||||
Production()
|
||||
|
|
@ -56,14 +57,21 @@ func LoadFrom(envfile string) Config {
|
|||
return Load()
|
||||
}
|
||||
|
||||
// Load 加载配置
|
||||
// Load the config
|
||||
func Load() Config {
|
||||
cfg := Config{}
|
||||
if err := env.Parse(&cfg); err != nil {
|
||||
exception.New("Can't read config %s", 500, err.Error()).Throw()
|
||||
}
|
||||
|
||||
// Root path
|
||||
cfg.Root, _ = filepath.Abs(cfg.Root)
|
||||
|
||||
// App Root
|
||||
if cfg.AppSource == "" {
|
||||
cfg.AppSource = cfg.Root
|
||||
}
|
||||
|
||||
// Studio Secret
|
||||
if cfg.Studio.Secret == nil {
|
||||
v, err := crypto.Hash(crypto.HashTypes["SHA256"], uuid.New().String())
|
||||
|
|
@ -74,6 +82,20 @@ func Load() Config {
|
|||
cfg.Studio.Auto = true
|
||||
}
|
||||
|
||||
// DataRoot
|
||||
if cfg.DataRoot == "" {
|
||||
cfg.DataRoot = filepath.Join(cfg.Root, "data")
|
||||
if !filepath.IsAbs(cfg.DataRoot) {
|
||||
cfg.DataRoot, _ = filepath.Abs(cfg.DataRoot)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(cfg.DataRoot); errors.Is(err, os.ErrNotExist) {
|
||||
if err := os.MkdirAll(cfg.DataRoot, os.ModePerm); err != nil {
|
||||
exception.New("Can't create data root %s", 500, err.Error()).Throw()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
|
@ -56,11 +57,11 @@ func TestLoadFrom(t *testing.T) {
|
|||
assert.Equal(t, cfg.Root, root)
|
||||
assert.Equal(t, cfg.Mode, os.Getenv("YAO_ENV"))
|
||||
assert.Equal(t, cfg.Host, os.Getenv("YAO_HOST"))
|
||||
assert.Equal(t, cfg.Port, os.Getenv("YAO_PORT"))
|
||||
assert.Equal(t, fmt.Sprintf("%d", cfg.Port), os.Getenv("YAO_PORT"))
|
||||
assert.Equal(t, cfg.JWTSecret, os.Getenv("YAO_JWT_SECRET"))
|
||||
assert.Equal(t, cfg.Log, os.Getenv("YAO_LOG"))
|
||||
assert.Equal(t, cfg.LogMode, os.Getenv("YAO_LOG_MODE"))
|
||||
assert.Equal(t, cfg.DB.Driver, os.Getenv("YAO_DB_DRIVER"))
|
||||
assert.Equal(t, cfg.DB.Primary[0], os.Getenv("YAO_DB_PRIMARY"))
|
||||
assert.Equal(t, cfg.DB.Secondary[0], os.Getenv("YAO_DB_SECONDARY"))
|
||||
// assert.Equal(t, cfg.DB.Secondary[0], os.Getenv("YAO_DB_SECONDARY"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,20 +2,21 @@ package config
|
|||
|
||||
// Config 象传应用引擎配置
|
||||
type Config struct {
|
||||
Mode string `json:"mode,omitempty" env:"YAO_ENV" envDefault:"production"` // 象传引擎启动模式 production/development
|
||||
Root string `json:"root,omitempty" env:"YAO_ROOT" envDefault:"."` // 应用根目录
|
||||
Mode string `json:"mode,omitempty" env:"YAO_ENV" envDefault:"production"` // The start mode production/development
|
||||
AppSource string `json:"app,omitempty" env:"YAO_APP_SOURCE"` // The Application Source Root Path default same as Root
|
||||
Root string `json:"root,omitempty" env:"YAO_ROOT" envDefault:"."` // The Application Root Path
|
||||
Lang string `json:"lang,omitempty" env:"YAO_LANG" envDefault:"en-us"` // Default language setting
|
||||
TimeZone string `json:"timezone,omitempty" env:"YAO_TIMEZONE"` // Default TimeZone
|
||||
DataRoot string `json:"data_root,omitempty" env:"YAO_DATA_ROOT" envDefault:""` // DATA PATH
|
||||
DataRoot string `json:"data_root,omitempty" env:"YAO_DATA_ROOT" envDefault:""` // The data root path
|
||||
ExtensionRoot string `json:"extension_root,omitempty" env:"YAO_EXTENSION_ROOT" envDefault:""` // Plugin, Wasm root PATH, Default is <YAO_ROOT> (<YAO_ROOT>/plugins <YAO_ROOT>/wasms)
|
||||
Host string `json:"host,omitempty" env:"YAO_HOST" envDefault:"0.0.0.0"` // 服务监听地址
|
||||
Port int `json:"port,omitempty" env:"YAO_PORT" envDefault:"5099"` // 服务监听端口
|
||||
Cert string `json:"cert,omitempty" env:"YAO_CERT"` // HTTPS 证书文件地址
|
||||
Key string `json:"key,omitempty" env:"YAO_KEY"` // HTTPS 证书密钥地址
|
||||
Log string `json:"log,omitempty" env:"YAO_LOG"` // 服务日志地址
|
||||
LogMode string `json:"log_mode,omitempty" env:"YAO_LOG_MODE" envDefault:"TEXT"` // 服务日志模式 JSON|TEXT
|
||||
JWTSecret string `json:"jwt_secret,omitempty" env:"YAO_JWT_SECRET"` // JWT 密钥
|
||||
DB Database `json:"db,omitempty"` // 数据库配置
|
||||
Host string `json:"host,omitempty" env:"YAO_HOST" envDefault:"0.0.0.0"` // The server host
|
||||
Port int `json:"port,omitempty" env:"YAO_PORT" envDefault:"5099"` // The server port
|
||||
Cert string `json:"cert,omitempty" env:"YAO_CERT"` // The HTTPS certificate path
|
||||
Key string `json:"key,omitempty" env:"YAO_KEY"` // The HTTPS certificate key path
|
||||
Log string `json:"log,omitempty" env:"YAO_LOG"` // The log file path
|
||||
LogMode string `json:"log_mode,omitempty" env:"YAO_LOG_MODE" envDefault:"TEXT"` // The log mode TEXT|JSON
|
||||
JWTSecret string `json:"jwt_secret,omitempty" env:"YAO_JWT_SECRET"` // The JWT Secret
|
||||
DB Database `json:"db,omitempty"` // The database config
|
||||
AllowFrom []string `json:"allowfrom,omitempty" envSeparator:"|" env:"YAO_ALLOW_FROM"` // Domain list the separator is |
|
||||
Session Session `json:"session,omitempty"` // Session Config
|
||||
Studio Studio `json:"studio,omitempty"` // Studio config
|
||||
|
|
|
|||
230
engine/load.go
230
engine/load.go
|
|
@ -46,7 +46,7 @@ func Load(cfg config.Config) (err error) {
|
|||
os.Setenv("XGEN_BASE", adminRoot)
|
||||
|
||||
// load the application
|
||||
err = loadApp(cfg.Root)
|
||||
err = loadApp(cfg.AppSource)
|
||||
if err != nil {
|
||||
printErr(cfg.Mode, "Load Application", err)
|
||||
panic(err)
|
||||
|
|
@ -281,231 +281,3 @@ func printErr(mode, widget string, err error) {
|
|||
color.Red(message)
|
||||
}
|
||||
}
|
||||
|
||||
// // LoadDeprecated 根据配置加载 API, FLow, Model, Plugin
|
||||
// func LoadDeprecated(cfg config.Config) (err error) {
|
||||
// defer func() { err = exception.Catch(recover()) }()
|
||||
|
||||
// // // Load Runtime
|
||||
// // err = runtime.Load(cfg)
|
||||
// // if err != nil {
|
||||
// // printErr(cfg.Mode, "Runtime", err)
|
||||
// // }
|
||||
|
||||
// // // 加载应用信息
|
||||
// // // 第一步: 加载应用信息
|
||||
// // app.Load(cfg)
|
||||
|
||||
// // 加密密钥函数
|
||||
// // model.LoadCrypt(fmt.Sprintf(`{"key":"%s"}`, cfg.DB.AESKey), "AES")
|
||||
// // gou.LoadCrypt(`{}`, "PASSWORD")
|
||||
|
||||
// // Load Certs
|
||||
// err = cert.Load(cfg)
|
||||
// if err != nil {
|
||||
// printErr(cfg.Mode, "Cert", err)
|
||||
// }
|
||||
|
||||
// // Load connectors
|
||||
// err = connector.Load(cfg)
|
||||
// if err != nil {
|
||||
// printErr(cfg.Mode, "Connector", err)
|
||||
// }
|
||||
|
||||
// // Load FileSystem
|
||||
// err = fs.Load(cfg)
|
||||
// if err != nil {
|
||||
// printErr(cfg.Mode, "FileSystem", err)
|
||||
// }
|
||||
|
||||
// // Load i18n
|
||||
// err = i18n.Load(cfg)
|
||||
// if err != nil {
|
||||
// printErr(cfg.Mode, "i18n", err)
|
||||
// }
|
||||
|
||||
// // Load Studio development mode only
|
||||
// if cfg.Mode == "development" {
|
||||
// err = studio.Load(cfg)
|
||||
// if err != nil {
|
||||
// printErr(cfg.Mode, "Studio", err)
|
||||
// }
|
||||
// }
|
||||
|
||||
// // 第二步: 建立数据库 & 会话连接
|
||||
// err = share.DBConnect(cfg.DB) // 创建数据库连接
|
||||
// if err != nil {
|
||||
// printErr(cfg.Mode, "DB", err)
|
||||
// }
|
||||
|
||||
// // share.SessionConnect(cfg.Session) // 创建会话服务器链接
|
||||
|
||||
// // 加载应用引擎
|
||||
// if os.Getenv("YAO_DEV") != "" {
|
||||
// LoadEngine(filepath.Join(os.Getenv("YAO_DEV"), "/yao"))
|
||||
// } else {
|
||||
// LoadEngine()
|
||||
// }
|
||||
|
||||
// // 第三步: 加载数据分析引擎
|
||||
// query.Load(cfg) // 加载数据分析引擎
|
||||
|
||||
// // 第四步: 加载共享库 & JS 处理器
|
||||
// err = share.Load(cfg) // 加载共享库 lib
|
||||
// if err != nil {
|
||||
// printErr(cfg.Mode, "Lib", err)
|
||||
// }
|
||||
|
||||
// err = script.Load(cfg) // 加载JS处理器 script
|
||||
// if err != nil {
|
||||
// printErr(cfg.Mode, "Script", err)
|
||||
// }
|
||||
|
||||
// // 第五步: 加载数据模型等
|
||||
// err = model.Load(cfg) // 加载数据模型 model
|
||||
// if err != nil {
|
||||
// printErr(cfg.Mode, "Model", err)
|
||||
// }
|
||||
|
||||
// err = flow.Load(cfg) // 加载业务逻辑 Flow
|
||||
// if err != nil {
|
||||
// printErr(cfg.Mode, "Flow", err)
|
||||
// }
|
||||
|
||||
// err = store.Load(cfg) // Load stores
|
||||
// if err != nil {
|
||||
// printErr(cfg.Mode, "Store", err)
|
||||
// }
|
||||
|
||||
// err = plugin.Load(cfg) // 加载业务插件 plugin
|
||||
// if err != nil {
|
||||
// printErr(cfg.Mode, "Plugin", err)
|
||||
// }
|
||||
|
||||
// // // XGEN 1.0
|
||||
// // if share.App.XGen == "1.0" {
|
||||
|
||||
// // // SET XGEN_BASE
|
||||
// // // adminRoot := "yao"
|
||||
// // // if share.App.Optional != nil {
|
||||
// // // if root, has := share.App.Optional["adminRoot"]; has {
|
||||
// // // adminRoot = fmt.Sprintf("%v", root)
|
||||
// // // }
|
||||
// // // }
|
||||
// // // os.Setenv("XGEN_BASE", adminRoot)
|
||||
|
||||
// // // Load build-in widgets
|
||||
// // err = widgets.Load(cfg)
|
||||
// // if err != nil {
|
||||
// // printErr(cfg.Mode, "Widgets", err)
|
||||
// // }
|
||||
|
||||
// // delete(gou.APIs, "xiang.table")
|
||||
// // delete(gou.APIs, "xiang.page")
|
||||
// // delete(gou.APIs, "xiang.chart")
|
||||
// // delete(gou.APIs, "xiang.xiang")
|
||||
// // delete(gou.APIs, "xiang.user")
|
||||
// // delete(gou.APIs, "xiang.storage")
|
||||
|
||||
// // } else { // old version
|
||||
// // err = table.Load(cfg) // 加载数据表格 table
|
||||
// // if err != nil {
|
||||
// // printErr(cfg.Mode, "Table", err)
|
||||
// // }
|
||||
|
||||
// // err = chart.Load(cfg) // 加载分析图表 chart
|
||||
// // if err != nil {
|
||||
// // printErr(cfg.Mode, "Chart", err)
|
||||
// // }
|
||||
|
||||
// // err = page.Load(cfg) // 加载页面 page 忽略错误
|
||||
// // if err != nil {
|
||||
// // printErr(cfg.Mode, "Page", err)
|
||||
// // }
|
||||
// // }
|
||||
|
||||
// importer.Load(cfg) // 加载数据导入 imports
|
||||
|
||||
// err = api.Load(cfg) // 加载业务接口 API
|
||||
// if err != nil {
|
||||
// printErr(cfg.Mode, "API", err)
|
||||
// }
|
||||
|
||||
// err = socket.Load(cfg) // Load sockets
|
||||
// if err != nil {
|
||||
// printErr(cfg.Mode, "Socket", err)
|
||||
// }
|
||||
|
||||
// err = websocket.Load(cfg) // Load websockets (client)
|
||||
// if err != nil {
|
||||
// printErr(cfg.Mode, "WebSocket", err)
|
||||
// }
|
||||
|
||||
// err = task.Load(cfg) // Load tasks
|
||||
// if err != nil {
|
||||
// printErr(cfg.Mode, "Task", err)
|
||||
// }
|
||||
|
||||
// err = schedule.Load(cfg) // Load schedules
|
||||
// if err != nil {
|
||||
// printErr(cfg.Mode, "Schedule", err)
|
||||
// }
|
||||
|
||||
// err = widget.Load(cfg) // Load widgets
|
||||
// if err != nil {
|
||||
// printErr(cfg.Mode, "Widget", err)
|
||||
// }
|
||||
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// Reload 根据配置重新加载 API, FLow, Model, Plugin
|
||||
// func Reload(cfg config.Config) {
|
||||
// gou.APIs = map[string]*gou.API{}
|
||||
// gou.Models = map[string]*gou.Model{}
|
||||
// gou.Flows = map[string]*gou.Flow{}
|
||||
// gou.Plugins = map[string]*gou.Plugin{}
|
||||
// Load(cfg)
|
||||
// }
|
||||
|
||||
// LoadEngine 加载引擎的 API, Flow, Model 配置
|
||||
// func LoadEngine(from ...string) {
|
||||
// var scripts []share.Script
|
||||
// if len(from) > 0 {
|
||||
// scripts = share.GetFilesFS(from[0], ".json")
|
||||
// } else {
|
||||
// scripts = share.GetFilesBin("yao", ".json")
|
||||
// }
|
||||
|
||||
// if scripts == nil {
|
||||
// exception.New("读取文件失败", 500, from).Throw()
|
||||
// }
|
||||
|
||||
// if len(scripts) == 0 {
|
||||
// exception.New("读取文件失败, 未找到任何可执行脚本", 500, from).Throw()
|
||||
// }
|
||||
|
||||
// // 加载 API, Flow, Models, Table, Chart, Screens
|
||||
// for _, script := range scripts {
|
||||
// switch script.Type {
|
||||
// case "models":
|
||||
// gou.LoadModel(string(script.Content), "xiang."+script.Name)
|
||||
// break
|
||||
// case "flows":
|
||||
// gou.LoadFlow(string(script.Content), "xiang."+script.Name)
|
||||
// break
|
||||
// case "apis":
|
||||
// gou.LoadAPI(string(script.Content), "xiang."+script.Name)
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
|
||||
// // 加载数据应用
|
||||
// for _, script := range scripts {
|
||||
// switch script.Type {
|
||||
// case "tables":
|
||||
// table.LoadTable(string(script.Content), "xiang."+script.Name)
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
|
|
|||
44
fs/fs.go
44
fs/fs.go
|
|
@ -1,7 +1,6 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/yaoapp/gou/fs"
|
||||
|
|
@ -13,44 +12,11 @@ import (
|
|||
// Load system fs
|
||||
func Load(cfg config.Config) error {
|
||||
|
||||
root, err := filepath.Abs(cfg.Root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
scriptRoot := filepath.Join(cfg.AppSource, "scripts")
|
||||
dslDenyList := []string{scriptRoot, cfg.DataRoot}
|
||||
|
||||
dataRoot, err := Root(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scriptRoot := filepath.Join(root, "scripts")
|
||||
dslDenyList := []string{scriptRoot, dataRoot}
|
||||
|
||||
if _, err := os.Stat(dataRoot); os.IsNotExist(err) {
|
||||
err := os.MkdirAll(dataRoot, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fs.Register("system", system.New(dataRoot))
|
||||
// fs.Register("binary", system.New(root)) // Next
|
||||
fs.RootRegister("dsl", dsl.New(root).DenyAbs(dslDenyList...)) // DSL
|
||||
fs.RootRegister("script", system.New(scriptRoot)) // Script
|
||||
fs.Register("system", system.New(cfg.DataRoot))
|
||||
fs.RootRegister("dsl", dsl.New(cfg.AppSource).DenyAbs(dslDenyList...)) // DSL
|
||||
fs.RootRegister("script", system.New(scriptRoot)) // Script
|
||||
return nil
|
||||
}
|
||||
|
||||
// Root return data root
|
||||
func Root(cfg config.Config) (string, error) {
|
||||
root := cfg.DataRoot
|
||||
if root == "" {
|
||||
root = filepath.Join(cfg.Root, "data")
|
||||
}
|
||||
|
||||
root, err := filepath.Abs(root)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return root, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,7 @@ func TestLoad(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, size)
|
||||
|
||||
root, err := Root(config.Conf)
|
||||
assert.Nil(t, err)
|
||||
root := config.Conf.DataRoot
|
||||
|
||||
info, err := os.Stat(filepath.Join(root, "test.file"))
|
||||
assert.Nil(t, err)
|
||||
|
|
|
|||
|
|
@ -3,17 +3,11 @@ package runtime
|
|||
import (
|
||||
v8 "github.com/yaoapp/gou/runtime/v8"
|
||||
"github.com/yaoapp/yao/config"
|
||||
"github.com/yaoapp/yao/fs"
|
||||
)
|
||||
|
||||
// Start v8 runtime
|
||||
func Start(cfg config.Config) error {
|
||||
|
||||
dataRoot, err := fs.Root(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
option := &v8.Option{
|
||||
MinSize: cfg.Runtime.MinSize,
|
||||
MaxSize: cfg.Runtime.MaxSize,
|
||||
|
|
@ -21,10 +15,10 @@ func Start(cfg config.Config) error {
|
|||
HeapAvailableSize: cfg.Runtime.HeapAvailableSize,
|
||||
HeapSizeRelease: cfg.Runtime.HeapSizeRelease,
|
||||
Precompile: cfg.Runtime.Precompile,
|
||||
DataRoot: dataRoot,
|
||||
DataRoot: cfg.DataRoot,
|
||||
}
|
||||
|
||||
err = v8.Start(option)
|
||||
err := v8.Start(option)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,19 +52,6 @@ func Stop(srv *http.Server) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// StopWithContext stop with timeout
|
||||
// func StopWithContext(ctx context.Context, onComplete func()) {
|
||||
// shutdown <- true
|
||||
// select {
|
||||
// case <-ctx.Done():
|
||||
// log.Error("[STOP] canceled (%v)", ctx.Err())
|
||||
// onComplete()
|
||||
// case <-shutdownComplete:
|
||||
// share.SessionStop()
|
||||
// onComplete()
|
||||
// }
|
||||
// }
|
||||
|
||||
func prepare() error {
|
||||
|
||||
// Session server
|
||||
|
|
|
|||
253
service/watch.go
253
service/watch.go
|
|
@ -2,32 +2,15 @@ package service
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/yaoapp/gou/application"
|
||||
"github.com/yaoapp/gou/server/http"
|
||||
"github.com/yaoapp/kun/log"
|
||||
"github.com/yaoapp/yao/config"
|
||||
"github.com/yaoapp/yao/engine"
|
||||
"github.com/yaoapp/yao/share"
|
||||
)
|
||||
|
||||
var watchShutdown = make(chan bool, 1) // shutdown signal
|
||||
var watchReady = make(chan bool, 1) // ready signal
|
||||
var excludes = map[string]bool{"ui": true, "db": true, "data": true}
|
||||
var handlers = map[string]func(root string, file string, event string, cfg config.Config){
|
||||
"models": watchModel,
|
||||
}
|
||||
|
||||
// Watch the application code change for hot update
|
||||
func Watch(srv *http.Server, interrupt chan uint8) (err error) {
|
||||
|
||||
|
|
@ -65,239 +48,3 @@ func Watch(srv *http.Server, interrupt chan uint8) (err error) {
|
|||
|
||||
}, interrupt)
|
||||
}
|
||||
|
||||
// StopWatch stop watching the code change
|
||||
func StopWatch() {
|
||||
watchShutdown <- true
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
|
||||
func watchStart(cfg config.Config) error {
|
||||
|
||||
root := cfg.Root
|
||||
|
||||
// recive interrupt signal
|
||||
interrupt := make(chan os.Signal, 1)
|
||||
signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
|
||||
|
||||
shutdown := make(chan bool, 1)
|
||||
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer watcher.Close()
|
||||
|
||||
root, err = filepath.Abs(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dirs, err := ioutil.ReadDir(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add path
|
||||
for _, dir := range dirs {
|
||||
if !dir.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
name := dir.Name()
|
||||
if _, has := excludes[name]; has {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(name, ".") {
|
||||
continue
|
||||
}
|
||||
|
||||
filename := filepath.Join(root, name)
|
||||
err := watcher.Add(filename)
|
||||
if err != nil {
|
||||
log.Error("[Watch] %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(color.GreenString("[Watch] Watching %s", name))
|
||||
log.Info("[Watch] Watching: %s", filename)
|
||||
|
||||
// sub dir
|
||||
depth := 0
|
||||
err = filepath.WalkDir(filename, func(path string, d fs.DirEntry, err error) error {
|
||||
depth = depth + 1
|
||||
if depth == 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Info("[Watch] Watching: %s", path)
|
||||
err = watcher.Add(path)
|
||||
if err != nil {
|
||||
log.Error("[Watch] %s", err.Error())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Error("[Watch] %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// event handler
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-shutdown:
|
||||
log.Info("[Watch] The event handler exit")
|
||||
return
|
||||
|
||||
case event, ok := <-watcher.Events:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
relpath := strings.TrimPrefix(event.Name, root)
|
||||
if strings.HasPrefix(relpath, string(os.PathSeparator)) {
|
||||
relpath = strings.TrimPrefix(relpath, string(os.PathSeparator))
|
||||
}
|
||||
|
||||
pi := strings.Split(relpath, string(os.PathSeparator))
|
||||
widget := pi[0]
|
||||
|
||||
watchHanler := watchReload
|
||||
if handler, has := handlers[widget]; has {
|
||||
watchHanler = handler
|
||||
}
|
||||
|
||||
if _, has := excludes[widget]; !has {
|
||||
base := filepath.Base(event.Name)
|
||||
isdir := true
|
||||
if strings.HasSuffix(base, ".yao") || strings.HasSuffix(base, ".json") || strings.HasSuffix(base, ".js") {
|
||||
isdir = false
|
||||
}
|
||||
|
||||
events := strings.Split(event.Op.String(), "|")
|
||||
for _, eventType := range events {
|
||||
|
||||
// ADD / REMOVE Watching dir
|
||||
if isdir {
|
||||
switch eventType {
|
||||
case "CREATE":
|
||||
log.Info("[Watch] Watching: %s", event.Name)
|
||||
watcher.Add(event.Name)
|
||||
break
|
||||
case "REMOVE":
|
||||
log.Info("[Watch] Unwatching: %s", event.Name)
|
||||
watcher.Remove(event.Name)
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
log.Info("[Watch] %s %s", eventType, event.Name)
|
||||
watchHanler(root, event.Name, eventType, cfg)
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
case err, ok := <-watcher.Errors:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
fmt.Println(color.RedString("[Watch] %s", err.Error()))
|
||||
log.Error("[Watch] %s", err.Error())
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
fmt.Println(color.GreenString("[Watch] Started"))
|
||||
watchReady <- true
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-watchShutdown:
|
||||
shutdown <- true
|
||||
log.Info("[Watch] Stopped")
|
||||
fmt.Println(color.YellowString("[Watch] Stopped"))
|
||||
return nil
|
||||
|
||||
case <-interrupt:
|
||||
shutdown <- true
|
||||
log.Info("[Watch] Stopped")
|
||||
fmt.Println(color.YellowString("[Watch] Stopped"))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func watchModel(root string, file string, event string, cfg config.Config) {
|
||||
// name := share.SpecName(root, file)
|
||||
// switch event {
|
||||
// case "CREATE":
|
||||
// content, err := ioutil.ReadFile(file)
|
||||
// if err != nil {
|
||||
// fmt.Println(color.RedString("[Watch] Model: %s %s", name, err.Error()))
|
||||
// return
|
||||
// }
|
||||
// _, err = gou.LoadModelReturn(string(content), name)
|
||||
// if err != nil {
|
||||
// fmt.Println(color.RedString("[Watch] Model: %s %s", name, err.Error()))
|
||||
// return
|
||||
// }
|
||||
|
||||
// // mod.Migrate(true)
|
||||
// fmt.Println(color.GreenString("[Watch] Model: %s Created (Please run yao migrate manually)", name))
|
||||
// break
|
||||
|
||||
// case "WRITE":
|
||||
// content, err := ioutil.ReadFile(file)
|
||||
// if err != nil {
|
||||
// fmt.Println(color.RedString("[Watch] Model: %s %s", name, err.Error()))
|
||||
// return
|
||||
// }
|
||||
// _, err = gou.LoadModelReturn(string(content), name)
|
||||
// if err != nil {
|
||||
// fmt.Println(color.RedString("[Watch] Model: %s %s", name, err.Error()))
|
||||
// return
|
||||
// }
|
||||
// // mod.Migrate(false)
|
||||
// fmt.Println(color.GreenString("[Watch] Model: %s Reloaded (Please run yao migrate manually)", name))
|
||||
// break
|
||||
|
||||
// case "REMOVE", "RENAME":
|
||||
// delete(gou.Models, name)
|
||||
// fmt.Println(color.GreenString("[Watch] Model: %s Removed", name))
|
||||
// break
|
||||
// }
|
||||
}
|
||||
|
||||
func watchReload(root string, file string, event string, cfg config.Config) {
|
||||
|
||||
switch event {
|
||||
case "CREATE", "WRITE", "REMOVE":
|
||||
|
||||
err := share.DBClose()
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString("[Watch] Reload: %s", err.Error()))
|
||||
}
|
||||
|
||||
err = engine.Load(config.Conf) // 加载脚本等
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString("[Watch] Reload: %s", err.Error()))
|
||||
}
|
||||
|
||||
// Restart Server
|
||||
// ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
// defer cancel()
|
||||
|
||||
// Stop(func() {
|
||||
// go Start()
|
||||
// fmt.Println(color.GreenString("[Watch] Reload Completed"))
|
||||
// })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -206,6 +206,7 @@ func setRouter(router *gin.Engine) {
|
|||
script, err := v8.SelectRoot(service)
|
||||
if err != nil {
|
||||
throw(c, 500, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ctx, err := script.NewContext(fmt.Sprintf("%v", sid), nil)
|
||||
|
|
|
|||
|
|
@ -6,11 +6,10 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/yaoapp/gou/application"
|
||||
"github.com/yaoapp/gou/fs"
|
||||
"github.com/yaoapp/gou/fs/dsl"
|
||||
v8 "github.com/yaoapp/gou/runtime/v8"
|
||||
|
|
@ -93,56 +92,27 @@ func Stop() {
|
|||
|
||||
// Load studio config
|
||||
func Load(cfg config.Config) error {
|
||||
|
||||
err := loadDSL(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return loadScripts(cfg)
|
||||
return loadScripts()
|
||||
}
|
||||
|
||||
func loadDSL(cfg config.Config) error {
|
||||
|
||||
root, err := filepath.Abs(cfg.Root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scriptRoot := filepath.Join(root, "scripts")
|
||||
dataRoot := filepath.Join(root, "data")
|
||||
dslDenyList := []string{scriptRoot, dataRoot}
|
||||
dfs = dsl.New(root).DenyAbs(dslDenyList...)
|
||||
dslDenyList := []string{cfg.DataRoot}
|
||||
dfs = dsl.New(cfg.AppSource).DenyAbs(dslDenyList...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadScripts(cfg config.Config) error {
|
||||
root, err := filepath.Abs(cfg.Root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
studioRoot := filepath.Join(root, "studio")
|
||||
return loadScriptFrom(studioRoot)
|
||||
}
|
||||
|
||||
// Load script From dir
|
||||
func loadScriptFrom(dir string) error {
|
||||
|
||||
if share.DirNotExists(dir) {
|
||||
log.Warn("[Studio] Load %s does not exists", dir)
|
||||
return nil
|
||||
}
|
||||
|
||||
messages := []string{}
|
||||
err := share.Walk(dir, ".js", func(root, filename string) {
|
||||
name := share.SpecName(root, filename)
|
||||
_, err := v8.LoadRoot(filename, name)
|
||||
if err != nil {
|
||||
messages = append(messages, err.Error())
|
||||
func loadScripts() error {
|
||||
exts := []string{"*.js"}
|
||||
return application.App.Walk("studio", func(root, file string, isdir bool) error {
|
||||
if isdir {
|
||||
return nil
|
||||
}
|
||||
})
|
||||
|
||||
if len(messages) > 0 {
|
||||
return fmt.Errorf("[Studio] Load %s", strings.Join(messages, ";"))
|
||||
}
|
||||
return err
|
||||
_, err := v8.LoadRoot(file, share.ID(root, file))
|
||||
return err
|
||||
}, exts...)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,17 +12,21 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/yaoapp/yao/config"
|
||||
"github.com/yaoapp/yao/helper"
|
||||
"github.com/yaoapp/yao/test"
|
||||
)
|
||||
|
||||
type kv map[string]interface{}
|
||||
type arr []interface{}
|
||||
|
||||
func TestLoad(t *testing.T) {
|
||||
|
||||
test.Prepare(t, config.Conf)
|
||||
defer test.Clean()
|
||||
|
||||
err := Load(config.Conf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.NotNil(t, dfs)
|
||||
|
||||
// res, err := gou.Yao.Engine.RootCall(map[string]interface{}{}, "table", "Ping")
|
||||
// assert.Nil(t, err)
|
||||
|
|
@ -34,6 +38,10 @@ func TestLoad(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestStartStop(t *testing.T) {
|
||||
|
||||
test.Prepare(t, config.Conf)
|
||||
defer test.Clean()
|
||||
|
||||
var err error
|
||||
go func() { err = Start(config.Conf) }()
|
||||
if err != nil {
|
||||
|
|
@ -46,6 +54,10 @@ func TestStartStop(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestStartStopError(t *testing.T) {
|
||||
|
||||
test.Prepare(t, config.Conf)
|
||||
defer test.Clean()
|
||||
|
||||
var err error
|
||||
go func() { err = Start(config.Conf) }()
|
||||
if err != nil {
|
||||
|
|
@ -63,6 +75,9 @@ func TestStartStopError(t *testing.T) {
|
|||
|
||||
func TestAPI(t *testing.T) {
|
||||
|
||||
test.Prepare(t, config.Conf)
|
||||
defer test.Clean()
|
||||
|
||||
Load(config.Conf)
|
||||
|
||||
var err error
|
||||
|
|
@ -73,23 +88,23 @@ func TestAPI(t *testing.T) {
|
|||
defer Stop()
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
code, row := httpGet[kv]("/dsl/ReadFile?name=/models/user.json", t)
|
||||
code, row := httpGet[kv]("/dsl/ReadFile?name=/models/user.mod.yao", t)
|
||||
assert.Equal(t, 200, code)
|
||||
assert.Equal(t, "用户", row["name"])
|
||||
assert.Equal(t, "User", row["name"])
|
||||
|
||||
code, rows := httpGet[arr]("/dsl/ReadDir?name=/models", t)
|
||||
assert.Equal(t, 200, code)
|
||||
assert.Equal(t, 11, len(rows))
|
||||
assert.Equal(t, 8, len(rows))
|
||||
|
||||
code, rows = httpGet[arr]("/dsl/ReadDir?name=/models&recursive=1", t)
|
||||
assert.Equal(t, 200, code)
|
||||
assert.Equal(t, 12, len(rows))
|
||||
|
||||
code, length := httpPost[int]("/dsl/WriteFile?name=/models/foo.mod.json", []byte(`{"name":"foo"}`), t)
|
||||
code, length := httpPost[int]("/dsl/WriteFile?name=/models/foo.mod.yao", []byte(`{"name":"foo"}`), t)
|
||||
assert.Equal(t, 200, code)
|
||||
assert.Equal(t, 19, length)
|
||||
|
||||
code, _ = httpPost[kv]("/dsl/Remove?name=/models/foo.mod.json", nil, t)
|
||||
code, _ = httpPost[kv]("/dsl/Remove?name=/models/foo.mod.yao", nil, t)
|
||||
assert.Equal(t, 200, code)
|
||||
|
||||
code, _ = httpPost[kv]("/dsl/Mkdir?name=/models/bar", nil, t)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue