Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: init node application #997

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 26 additions & 5 deletions cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@

// defaultSFNFile is the default serverless file name
const (
defaultSFNSourceFile = "app.go"
defaultSFNSourceTSFile = "app.ts"
defaultSFNTestSourceFile = "app_test.go"
defaultSFNCompliedFile = "sfn.yomo"
defaultSFNWASIFile = "sfn.wasm"
defaultSFNSourceFile = "app.go"
defaultSFNSourceTSFile = "app.ts"
defaultSFNTestSourceFile = "app_test.go"
defaultSFNTestSourceTSFile = "app_test.ts"
defaultSFNCompliedFile = "sfn.yomo"
defaultSFNWASIFile = "sfn.wasm"
)

// GetRootPath get root path
Expand Down Expand Up @@ -91,3 +92,23 @@
opts.Filename = f
return nil
}

// DefaultSFNSourceFile returns the default source file name for the given language
func DefaultSFNSourceFile(lang string) string {
switch lang {
case "node":
return defaultSFNSourceTSFile
default:
return defaultSFNSourceFile

Check warning on line 102 in cli/cli.go

View check run for this annotation

Codecov / codecov/patch

cli/cli.go#L97-L102

Added lines #L97 - L102 were not covered by tests
}
}

// DefaultSFNTestSourceFile returns the default test source file name
func DefaultSFNTestSourceFile(lang string) string {
switch lang {
case "node":
return defaultSFNTestSourceTSFile
default:
return defaultSFNTestSourceFile

Check warning on line 112 in cli/cli.go

View check run for this annotation

Codecov / codecov/patch

cli/cli.go#L107-L112

Added lines #L107 - L112 were not covered by tests
}
}
53 changes: 37 additions & 16 deletions cli/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,28 @@
package cli

import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/spf13/cobra"
"github.com/yomorun/yomo/cli/serverless/golang"
"github.com/yomorun/yomo/cli/serverless"
"github.com/yomorun/yomo/cli/template"
"github.com/yomorun/yomo/pkg/file"
"github.com/yomorun/yomo/pkg/log"
)

var name string
var lang string

// initCmd represents the init command
var initCmd = &cobra.Command{
Use: "init",
Short: "Initialize a YoMo Stream function",
Long: "Initialize a YoMo Stream function",
Run: func(cmd *cobra.Command, args []string) {
name := opts.Name

Check warning on line 40 in cli/init.go

View check run for this annotation

Codecov / codecov/patch

cli/init.go#L40

Added line #L40 was not covered by tests
if len(args) >= 1 && args[0] != "" {
name = args[0]
}
Expand All @@ -46,21 +49,39 @@

log.PendingStatusEvent(os.Stdout, "Initializing the Stream Function...")
name = strings.ReplaceAll(name, " ", "_")
// create app.go
fname := filepath.Join(name, defaultSFNSourceFile)
contentTmpl := golang.InitTmpl
if err := file.PutContents(fname, contentTmpl); err != nil {
log.FailureStatusEvent(os.Stdout, "Write stream function into app.go file failure with the error: %v", err)
filename := filepath.Join(name, DefaultSFNSourceFile(lang))
opts.Filename = filename
// serverless setup
err := serverless.Setup(&opts)
if err != nil {
log.FailureStatusEvent(os.Stdout, err.Error())

Check warning on line 57 in cli/init.go

View check run for this annotation

Codecov / codecov/patch

cli/init.go#L52-L57

Added lines #L52 - L57 were not covered by tests
return
}

// create app_test.go
testName := filepath.Join(name, defaultSFNTestSourceFile)
if err := file.PutContents(testName, golang.InitTestTmpl); err != nil {
log.FailureStatusEvent(os.Stdout, "Write unittest tmpl into app_test.go file failure with the error: %v", err)
// create app source file
fname := filepath.Join(name, DefaultSFNSourceFile(lang))
contentTmpl, err := template.GetContent("init", "", lang, false)
if err != nil {
log.FailureStatusEvent(os.Stdout, err.Error())

Check warning on line 64 in cli/init.go

View check run for this annotation

Codecov / codecov/patch

cli/init.go#L61-L64

Added lines #L61 - L64 were not covered by tests
return
}

if err := file.PutContents(fname, contentTmpl); err != nil {
log.FailureStatusEvent(os.Stdout, "Write stream function into %s file failure with the error: %v", fname, err)
return
}

Check warning on line 70 in cli/init.go

View check run for this annotation

Codecov / codecov/patch

cli/init.go#L67-L70

Added lines #L67 - L70 were not covered by tests
// create app test file
testName := filepath.Join(name, DefaultSFNTestSourceFile(lang))
testTmpl, err := template.GetContent("init", "", lang, true)
if err != nil {
if !errors.Is(err, template.ErrUnsupportedTest) {
log.FailureStatusEvent(os.Stdout, err.Error())
return
}
} else {
if err := file.PutContents(testName, testTmpl); err != nil {
log.FailureStatusEvent(os.Stdout, "Write unittest tmpl into %s file failure with the error: %v", testName, err)
return
}

Check warning on line 83 in cli/init.go

View check run for this annotation

Codecov / codecov/patch

cli/init.go#L72-L83

Added lines #L72 - L83 were not covered by tests
}
// create .env
fname = filepath.Join(name, ".env")
if err := file.PutContents(fname, []byte(fmt.Sprintf("YOMO_SFN_NAME=%s\nYOMO_SFN_ZIPPER=localhost:9000\n", name))); err != nil {
Expand All @@ -70,13 +91,13 @@

log.SuccessStatusEvent(os.Stdout, "Congratulations! You have initialized the stream function successfully.")
log.InfoStatusEvent(os.Stdout, "You can enjoy the YoMo Stream Function via the command: ")
log.InfoStatusEvent(os.Stdout, "\tStep 1: cd %s && yomo build", name)
log.InfoStatusEvent(os.Stdout, "\tStep 2: yomo run sfn.yomo")
log.InfoStatusEvent(os.Stdout, "\tcd %s && yomo run", name)

Check warning on line 94 in cli/init.go

View check run for this annotation

Codecov / codecov/patch

cli/init.go#L94

Added line #L94 was not covered by tests
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the yomo build command be deprecated? Migrate the wasi flag to yomo run command..

},
}

func init() {
rootCmd.AddCommand(initCmd)

initCmd.Flags().StringVarP(&name, "name", "n", "", "The name of Stream Function")
initCmd.Flags().StringVarP(&opts.Name, "name", "n", "", "The name of Stream Function")
initCmd.Flags().StringVarP(&lang, "lang", "l", "go", "The language of Stream Function, support go and node")

Check warning on line 102 in cli/init.go

View check run for this annotation

Codecov / codecov/patch

cli/init.go#L101-L102

Added lines #L101 - L102 were not covered by tests
}
5 changes: 5 additions & 0 deletions cli/serverless/exec/serverless.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
target string
}

// Setup sets up the serverless
func (s *ExecServerless) Setup(opts *serverless.Options) error {
return nil

Check warning on line 20 in cli/serverless/exec/serverless.go

View check run for this annotation

Codecov / codecov/patch

cli/serverless/exec/serverless.go#L19-L20

Added lines #L19 - L20 were not covered by tests
}

// Init initializes the serverless
func (s *ExecServerless) Init(opts *serverless.Options) error {
if !file.Exists(opts.Filename) {
Expand Down
5 changes: 5 additions & 0 deletions cli/serverless/golang/serverless.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
tempDir string
}

// Setup sets up the serverless
func (s *GolangServerless) Setup(opts *serverless.Options) error {
return nil

Check warning on line 34 in cli/serverless/golang/serverless.go

View check run for this annotation

Codecov / codecov/patch

cli/serverless/golang/serverless.go#L33-L34

Added lines #L33 - L34 were not covered by tests
}

// Init initializes the serverless
func (s *GolangServerless) Init(opts *serverless.Options) error {
s.opts = opts
Expand Down
6 changes: 0 additions & 6 deletions cli/serverless/golang/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,6 @@ import (
//go:embed templates/main.tmpl
var MainFuncTmpl []byte

//go:embed templates/init.tmpl
var InitTmpl []byte

//go:embed templates/init_test.tmpl
var InitTestTmpl []byte

//go:embed templates/wasi_main.tmpl
var WasiMainFuncTmpl []byte

Expand Down
37 changes: 37 additions & 0 deletions cli/serverless/nodejs/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,40 @@

return nil
}

// InitApp initializes the nodejs application
func (w *NodejsWrapper) InitApp() error {
// init
cmd := exec.Command(w.npmPath, "init")
if w.npmPath == "npm" {
cmd.Args = append(cmd.Args, "-y")
}
cmd.Dir = w.workDir
cmd.Stderr = os.Stderr
cmd.Env = os.Environ()

if err := cmd.Run(); err != nil {
return fmt.Errorf("run %s failed: %v", cmd.String(), err)
}
return nil

Check warning on line 160 in cli/serverless/nodejs/runtime.go

View check run for this annotation

Codecov / codecov/patch

cli/serverless/nodejs/runtime.go#L147-L160

Added lines #L147 - L160 were not covered by tests
}

// InstallDeps installs the yomo dependencies
func (w *NodejsWrapper) InstallDeps() error {
// @yomo/sfn
cmd := exec.Command(w.npmPath, "install", "@yomo/sfn")
cmd.Dir = w.workDir
cmd.Stderr = os.Stderr
cmd.Env = os.Environ()
err := cmd.Run()
if err != nil {
return fmt.Errorf("run %s failed: %v", cmd.String(), err)
}

Check warning on line 173 in cli/serverless/nodejs/runtime.go

View check run for this annotation

Codecov / codecov/patch

cli/serverless/nodejs/runtime.go#L164-L173

Added lines #L164 - L173 were not covered by tests
// devDependencies
cmd = exec.Command(w.npmPath, "install", "-D", "@types/node", "ts-node")
err = cmd.Run()
if err != nil {
return fmt.Errorf("run %s failed: %v", cmd.String(), err)
}
return nil

Check warning on line 180 in cli/serverless/nodejs/runtime.go

View check run for this annotation

Codecov / codecov/patch

cli/serverless/nodejs/runtime.go#L175-L180

Added lines #L175 - L180 were not covered by tests
}
30 changes: 30 additions & 0 deletions cli/serverless/nodejs/serverless.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@

import (
"os"
"path/filepath"

"github.com/yomorun/yomo/cli/serverless"
"github.com/yomorun/yomo/pkg/file"
"github.com/yomorun/yomo/pkg/log"
"github.com/yomorun/yomo/pkg/wrapper"
)

Expand All @@ -16,6 +19,33 @@
wrapper *NodejsWrapper
}

// Setup sets up the nodejs serverless
func (s *nodejsServerless) Setup(opts *serverless.Options) error {
wrapper, err := NewWrapper(opts.Name, opts.Filename)
if err != nil {
return err
}

Check warning on line 27 in cli/serverless/nodejs/serverless.go

View check run for this annotation

Codecov / codecov/patch

cli/serverless/nodejs/serverless.go#L23-L27

Added lines #L23 - L27 were not covered by tests
// init package.json
err = file.Mkdir(wrapper.workDir)
if err != nil {
log.FailureStatusEvent(os.Stdout, "Create work dir failed: %v", err)
return err
}
if !file.Exists(filepath.Join(wrapper.workDir, "package.json")) {
err = wrapper.InitApp()
if err != nil {
return err
}

Check warning on line 38 in cli/serverless/nodejs/serverless.go

View check run for this annotation

Codecov / codecov/patch

cli/serverless/nodejs/serverless.go#L29-L38

Added lines #L29 - L38 were not covered by tests
}
// install dependencies
err = wrapper.InstallDeps()
if err != nil {
return err
}

Check warning on line 44 in cli/serverless/nodejs/serverless.go

View check run for this annotation

Codecov / codecov/patch

cli/serverless/nodejs/serverless.go#L41-L44

Added lines #L41 - L44 were not covered by tests

return nil

Check warning on line 46 in cli/serverless/nodejs/serverless.go

View check run for this annotation

Codecov / codecov/patch

cli/serverless/nodejs/serverless.go#L46

Added line #L46 was not covered by tests
}

// Init initializes the nodejs serverless
func (s *nodejsServerless) Init(opts *serverless.Options) error {
wrapper, err := NewWrapper(opts.Name, opts.Filename)
Expand Down
17 changes: 17 additions & 0 deletions cli/serverless/serverless.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

// Serverless defines the interface for serverless
type Serverless interface {
// Setup sets up the serverless
Setup(opts *Options) error
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the difference between Setup(opts *Options) error and Init(opts *Options) error?

Should these two function be merged ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setup is used to configure the application environment and Init is used to build


// Init initializes the serverless
Init(opts *Options) error

Expand Down Expand Up @@ -69,3 +72,17 @@

return nil, fmt.Errorf(`serverless: unsupport "%s" source (forgotten import?)`, ext)
}

// Setup sets up the serverless
func Setup(opts *Options) error {
ext := filepath.Ext(opts.Filename)

driversMu.RLock()
s, ok := drivers[ext]
driversMu.RUnlock()
if ok {
return s.Setup(opts)
}

Check warning on line 85 in cli/serverless/serverless.go

View check run for this annotation

Codecov / codecov/patch

cli/serverless/serverless.go#L77-L85

Added lines #L77 - L85 were not covered by tests

return fmt.Errorf(`serverless: unsupport "%s" source (forgotten import?)`, ext)

Check warning on line 87 in cli/serverless/serverless.go

View check run for this annotation

Codecov / codecov/patch

cli/serverless/serverless.go#L87

Added line #L87 was not covered by tests
}
5 changes: 5 additions & 0 deletions cli/serverless/wasm/serverless.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
mu sync.Mutex
}

// Setup sets up the serverless
func (s *wasmServerless) Setup(opts *cli.Options) error {
return nil

Check warning on line 29 in cli/serverless/wasm/serverless.go

View check run for this annotation

Codecov / codecov/patch

cli/serverless/wasm/serverless.go#L28-L29

Added lines #L28 - L29 were not covered by tests
}

// Init initializes the serverless
func (s *wasmServerless) Init(opts *cli.Options) error {
runtime, err := NewRuntime(opts.Runtime)
Expand Down
36 changes: 36 additions & 0 deletions cli/template/go/init_llm_test.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package main

import (
"reflect"
"testing"

"github.com/yomorun/yomo/serverless/mock"
)

func TestHandler(t *testing.T) {
tests := []struct {
name string
ctx *mock.MockContext
// want is the expected result written by ctx.WriteLLMResult()
want string
}{
{
name: "get weather",
ctx: mock.NewArgumentsContext(`{"city":"New York","latitude":40.7128,"longitude":-74.0060}`, 0x33),
want: "The current weather in New York (40.712800,-74.006000) is sunny",
},
// TODO: add more test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Handler(tt.ctx)

records := tt.ctx.RecordsWritten()
got := records[0].LLMResult

if !reflect.DeepEqual(got, tt.want) {
t.Errorf("TestHandler got: %v, want: %v", got, tt.want)
}
})
}
}
40 changes: 40 additions & 0 deletions cli/template/go/init_normal.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package main

import (
"fmt"
"strings"

"github.com/yomorun/yomo/serverless"
)

// Init is an optional function invoked during the initialization phase of the
// sfn instance. It's designed for setup tasks like global variable
// initialization, establishing database connections, or loading models into
// GPU memory. If initialization fails, the sfn instance will halt and terminate.
// This function can be omitted if no initialization tasks are needed.
func Init() error {
return nil
}

// DataTags specifies the data tags to which this serverless function
// subscribes, essential for data reception. Upon receiving data with these
// tags, the Handler function is triggered.
func DataTags() []uint32 {
return []uint32{0x33}
}

// Handler orchestrates the core processing logic of this function.
// - ctx.Tag() identifies the tag of the incoming data.
// - ctx.Data() accesses the raw data.
// - ctx.Write() forwards processed data downstream.
func Handler(ctx serverless.Context) {
data := ctx.Data()
fmt.Printf("<< sfn received[%d Bytes]: %s\n", len(data), data)
output := strings.ToUpper(string(data))
err := ctx.Write(0x34, []byte(output))
if err != nil {
fmt.Printf(">> sfn write error: %v\n", err)
return
}
fmt.Printf(">> sfn written[%d Bytes]: %s\n", len(output), output)
}
Loading
Loading