diff --git a/elements/forms/pipeline_group.go b/elements/forms/pipeline_group.go index a1bb450..6e6f4b6 100644 --- a/elements/forms/pipeline_group.go +++ b/elements/forms/pipeline_group.go @@ -2,6 +2,7 @@ package forms import ( Context "context" + "fmt" "github.com/vortex14/gotyphoon/elements/models/label" Errors "github.com/vortex14/gotyphoon/errors" @@ -83,7 +84,7 @@ func (g *PipelineGroup) Run(context Context.Context) error { logger.Warning(Errors.ForceSkipPipelines.Error()) default: errStack = err - logger.Error("Exit from group. Error: ", err.Error(), p.GetName()) + logger.Error(fmt.Sprintf("Pipeline name: %s ; Exit from group. Error: %s", p.GetName(), err.Error())) failedFlow = true } diff --git a/extensions/pipelines/http/emulator/rod/constuctor.go b/extensions/pipelines/http/emulator/rod/constuctor.go new file mode 100644 index 0000000..4d46e8b --- /dev/null +++ b/extensions/pipelines/http/emulator/rod/constuctor.go @@ -0,0 +1,83 @@ +package rod + +import ( + "bytes" + "context" + "fmt" + "github.com/PuerkitoBio/goquery" + "github.com/go-rod/rod" + "github.com/go-rod/rod/lib/devices" + net_http "github.com/vortex14/gotyphoon/extensions/pipelines/http/net-http" + "github.com/vortex14/gotyphoon/extensions/pipelines/text/html" + "os" + "time" + + "github.com/vortex14/gotyphoon/elements/forms" + "github.com/vortex14/gotyphoon/elements/models/label" + "github.com/vortex14/gotyphoon/interfaces" +) + +func CreateProxyRodRequestPipeline(opts *forms.Options, evopts *EventOptions) *HttpRodRequestPipeline { + + return &HttpRodRequestPipeline{ + BasePipeline: &forms.BasePipeline{ + MetaInfo: &label.MetaInfo{ + Name: "Rod http request", + }, + Options: opts, + Middlewares: []interfaces.MiddlewareInterface{ + net_http.ConstructorProxySettingMiddleware(true), + ConstructorRodProxyRequestMiddleware(true), + }, + }, + + Fn: func(context context.Context, task interfaces.TaskInterface, logger interfaces.LoggerInterface, + browser *rod.Browser) (error, context.Context) { + + logger.Info(fmt.Sprintf("RUN rod request proxy: %s , proxy_server: %s url: %s", task.GetProxyAddress(), task.GetProxyServerUrl(), task.GetFetcherUrl())) + + page := browser.DefaultDevice(devices.IPhoneX). + Timeout(time.Duration(task.GetFetcherTimeout()) * time.Second). + MustConnect(). + MustPage(task.GetFetcherUrl()) + + if evopts != nil { + evopts.Wait() + } + + logger.Debug("page opened") + page.MustWaitLoad() + logger.Debug("the page loaded") + context = NewPageCtx(context, page) + + body := page.MustHTML() + + doc, err := goquery.NewDocumentFromReader(bytes.NewBuffer([]byte(body))) + if err != nil { + return err, context + } + + context = html.NewHtmlCtx(context, doc) + context = NewBodyResponse(context, &body) + + page.MustClose() + + return nil, context + }, + Cn: func(err error, + context context.Context, + task interfaces.TaskInterface, + logger interfaces.LoggerInterface) { + + if task.GetSaveData("SKIP_CN") == "skip" { + return + } + + // Block current proxy + if net_http.MakeBlockRequest(logger, task) != nil { + logger.Error("Fatal exception. Impossible to block the proxy.") + os.Exit(1) + } + }, + } +} diff --git a/extensions/pipelines/http/emulator/rod/context.go b/extensions/pipelines/http/emulator/rod/context.go new file mode 100644 index 0000000..81a89ca --- /dev/null +++ b/extensions/pipelines/http/emulator/rod/context.go @@ -0,0 +1,52 @@ +package rod + +import ( + Context "context" + "github.com/go-rod/rod" + "github.com/go-rod/rod/lib/launcher" + + "github.com/vortex14/gotyphoon/ctx" +) + +const ( + BROWSER = "browser" + PAGE = "page" + LAUNCHER = "launcher" + RESPONSE = "response" +) + +func NewBrowserCtx(context Context.Context, browser *rod.Browser) Context.Context { + return ctx.Update(context, BROWSER, browser) +} + +func NewBodyResponse(context Context.Context, body *string) Context.Context { + return ctx.Update(context, RESPONSE, body) +} + +func GetPageResponse(context Context.Context) (bool, *string) { + body, ok := ctx.Get(context, RESPONSE).(*string) + return ok, body +} + +func GetBrowserCtx(context Context.Context) (bool, *rod.Browser) { + browser, ok := ctx.Get(context, BROWSER).(*rod.Browser) + return ok, browser +} + +func NewPageCtx(context Context.Context, page *rod.Page) Context.Context { + return ctx.Update(context, PAGE, page) +} + +func GetPageCtx(context Context.Context) (bool, *rod.Page) { + page, ok := ctx.Get(context, PAGE).(*rod.Page) + return ok, page +} + +func NewLauncherCtx(context Context.Context, launcher *launcher.Launcher) Context.Context { + return ctx.Update(context, LAUNCHER, launcher) +} + +func GetLauncherCtx(context Context.Context) (bool, *launcher.Launcher) { + launcher, ok := ctx.Get(context, LAUNCHER).(*launcher.Launcher) + return ok, launcher +} diff --git a/extensions/pipelines/http/emulator/rod/middleware.go b/extensions/pipelines/http/emulator/rod/middleware.go new file mode 100644 index 0000000..467d1e7 --- /dev/null +++ b/extensions/pipelines/http/emulator/rod/middleware.go @@ -0,0 +1,67 @@ +package rod + +import ( + "context" + "github.com/go-rod/rod" + "github.com/go-rod/rod/lib/launcher" + "github.com/vortex14/gotyphoon/elements/forms" + "github.com/vortex14/gotyphoon/elements/models/label" + "github.com/vortex14/gotyphoon/elements/models/task" + "github.com/vortex14/gotyphoon/extensions/middlewares" + "github.com/vortex14/gotyphoon/interfaces" +) + +func ConstructorRodProxyRequestMiddleware(required bool) interfaces.MiddlewareInterface { + return &middlewares.TaskMiddleware{ + Middleware: &forms.Middleware{ + MetaInfo: &label.MetaInfo{ + Required: required, + Name: "prepare for rod request", + }, + }, + Fn: func(context context.Context, task *task.TyphoonTask, + logger interfaces.LoggerInterface, reject func(err error), next func(ctx context.Context)) { + + url := launcher.New().Proxy(task.GetProxyAddress()).Delete("use-mock-keychain").MustLaunch() + browser := rod.New().ControlURL(url) + err := browser.Connect() + + if err != nil { + reject(err) + return + } + + context = NewBrowserCtx(context, browser) + + next(context) + + }, + } +} + +//func ConstructorRodProxyRequestMiddleware(required bool) interfaces.MiddlewareInterface { +// return &middlewares.TaskMiddleware{ +// Middleware: &forms.Middleware{ +// MetaInfo: &label.MetaInfo{ +// Required: required, +// Name: "set launcher for rod request", +// }, +// }, +// Fn: func(context context.Context, task *task.TyphoonTask, +// logger interfaces.LoggerInterface, reject func(err error), next func(ctx context.Context)) { +// +// sB, browser := GetBrowserCtx(context) +// +// if !sB { +// reject(Errors.New("rod browser not found")) +// return +// } +// +// url := launcher.New().Proxy(task.GetProxyAddress()).Delete("use-mock-keychain").MustLaunch() +// browser.ControlURL(url) +// +// next(context) +// +// }, +// } +//} diff --git a/extensions/pipelines/http/emulator/rod/options.go b/extensions/pipelines/http/emulator/rod/options.go new file mode 100644 index 0000000..b459ddb --- /dev/null +++ b/extensions/pipelines/http/emulator/rod/options.go @@ -0,0 +1,21 @@ +package rod + +import ( + "github.com/go-rod/rod" + "github.com/go-rod/rod/lib/proto" +) + +type EventOptions struct { + NetworkResponseReceived bool + Page *rod.Page +} + +func (e *EventOptions) Wait() { + + if e.NetworkResponseReceived { + er := proto.NetworkResponseReceived{} + wait := e.Page.WaitEvent(&er) + wait() + } + +} diff --git a/extensions/pipelines/http/emulator/rod/request.go b/extensions/pipelines/http/emulator/rod/request.go new file mode 100644 index 0000000..7ebb8ab --- /dev/null +++ b/extensions/pipelines/http/emulator/rod/request.go @@ -0,0 +1,102 @@ +package rod + +import ( + "context" + "fmt" + + "github.com/go-rod/rod" + "github.com/vortex14/gotyphoon/elements/forms" + "github.com/vortex14/gotyphoon/elements/models/task" + Errors "github.com/vortex14/gotyphoon/errors" + "github.com/vortex14/gotyphoon/extensions/pipelines" + "github.com/vortex14/gotyphoon/interfaces" + "github.com/vortex14/gotyphoon/log" +) + +type HttpRodRequestPipeline struct { + *forms.BasePipeline + *pipelines.TaskPipeline + + Fn func( + context context.Context, + task interfaces.TaskInterface, + logger interfaces.LoggerInterface, + + browser *rod.Browser, + + ) (error, context.Context) + + Cn func( + err error, + context context.Context, + task interfaces.TaskInterface, + logger interfaces.LoggerInterface, + ) +} + +func (t *HttpRodRequestPipeline) UnpackRequestCtx( + ctx context.Context, +) (bool, interfaces.TaskInterface, interfaces.LoggerInterface, *rod.Browser) { + okT, taskInstance := task.Get(ctx) + okL, logger := log.Get(ctx) + + okB, browser := GetBrowserCtx(ctx) + + if !okT || !okL || !okB { + return false, nil, nil, nil + } + + return okL && okT && okB, taskInstance, logger, browser +} + +func (t *HttpRodRequestPipeline) Run( + context context.Context, + reject func(pipeline interfaces.BasePipelineInterface, err error), + next func(ctx context.Context), +) { + + if t.Fn == nil { + reject(t, Errors.TaskPipelineRequiredHandler) + return + } + + t.SafeRun(func() error { + ok, taskInstance, logger, browser := t.UnpackRequestCtx(context) + + if !ok { + return fmt.Errorf("%s. taskInstance: %v, logger: %v, browser: %v", Errors.PipelineContexFailed, taskInstance, logger, browser) + } + + err, newContext := t.Fn(context, taskInstance, logger, browser) + if err != nil { + return err + } + next(newContext) + return err + + }, func(err error) { + reject(t, err) + _, logCtx := log.Get(context) + t.Cancel(context, logCtx, err) + }) + +} + +func (t *HttpRodRequestPipeline) Cancel( + context context.Context, + logger interfaces.LoggerInterface, + err error, +) { + + if t.Cn == nil { + return + } + + ok, taskInstance, logger := t.UnpackCtx(context) + if !ok { + return + } + + t.Cn(err, context, taskInstance, logger) + +} diff --git a/extensions/pipelines/http/emulator/rod/response.go b/extensions/pipelines/http/emulator/rod/response.go new file mode 100644 index 0000000..508cfed --- /dev/null +++ b/extensions/pipelines/http/emulator/rod/response.go @@ -0,0 +1,112 @@ +package rod + +import ( + "context" + "fmt" + "github.com/PuerkitoBio/goquery" + "github.com/vortex14/gotyphoon/extensions/pipelines/text/html" + + "github.com/go-rod/rod" + + "github.com/vortex14/gotyphoon/elements/forms" + "github.com/vortex14/gotyphoon/elements/models/task" + Errors "github.com/vortex14/gotyphoon/errors" + "github.com/vortex14/gotyphoon/extensions/pipelines" + "github.com/vortex14/gotyphoon/interfaces" + "github.com/vortex14/gotyphoon/log" +) + +type HttpRodResponsePipeline struct { + *forms.BasePipeline + *pipelines.TaskPipeline + + Fn func( + context context.Context, + task interfaces.TaskInterface, + logger interfaces.LoggerInterface, + + browser *rod.Browser, + page *rod.Page, + body *string, + doc *goquery.Document, + + ) (error, context.Context) + + Cn func( + err error, + context context.Context, + task interfaces.TaskInterface, + logger interfaces.LoggerInterface, + ) +} + +func (t *HttpRodResponsePipeline) UnpackResponseCtx( + ctx context.Context, +) (bool, interfaces.TaskInterface, interfaces.LoggerInterface, *rod.Browser, *rod.Page, *string, *goquery.Document) { + okT, taskInstance := task.Get(ctx) + okL, logger := log.Get(ctx) + + okB, browser := GetBrowserCtx(ctx) + okP, page := GetPageCtx(ctx) + okR, body := GetPageResponse(ctx) + okD, doc := html.GetHtmlDoc(ctx) + + if !okT || !okL || !okB || !okP || !okR || !okD { + return false, nil, nil, nil, nil, nil, nil + } + + return okL && okT && okB, taskInstance, logger, browser, page, body, doc +} + +func (t *HttpRodResponsePipeline) Run( + context context.Context, + reject func(pipeline interfaces.BasePipelineInterface, err error), + next func(ctx context.Context), +) { + + if t.Fn == nil { + reject(t, Errors.TaskPipelineRequiredHandler) + return + } + + t.SafeRun(func() error { + ok, taskInstance, logger, browser, page, body, doc := t.UnpackResponseCtx(context) + + if !ok { + return fmt.Errorf("%s. taskInstance: %v, logger: %v, browser: %v, page: %v, body: %v", + Errors.PipelineContexFailed, taskInstance, logger, browser, page, body) + } + + err, newContext := t.Fn(context, taskInstance, logger, browser, page, body, doc) + if err != nil { + return err + } + next(newContext) + return err + + }, func(err error) { + reject(t, err) + _, logCtx := log.Get(context) + t.Cancel(context, logCtx, err) + }) + +} + +func (t *HttpRodResponsePipeline) Cancel( + context context.Context, + logger interfaces.LoggerInterface, + err error, +) { + + if t.Cn == nil { + return + } + + ok, taskInstance, logger := t.UnpackCtx(context) + if !ok { + return + } + + t.Cn(err, context, taskInstance, logger) + +} diff --git a/extensions/pipelines/http/emulator/rod/test_test.go b/extensions/pipelines/http/emulator/rod/test_test.go new file mode 100644 index 0000000..b5553d1 --- /dev/null +++ b/extensions/pipelines/http/emulator/rod/test_test.go @@ -0,0 +1,68 @@ +package rod + +import ( + "context" + "github.com/PuerkitoBio/goquery" + "github.com/go-rod/rod" + . "github.com/smartystreets/goconvey/convey" + "github.com/vortex14/gotyphoon/elements/forms" + "github.com/vortex14/gotyphoon/elements/models/label" + Task "github.com/vortex14/gotyphoon/elements/models/task" + "github.com/vortex14/gotyphoon/extensions/data/fake" + "github.com/vortex14/gotyphoon/interfaces" + "github.com/vortex14/gotyphoon/log" + "testing" +) + +func init() { + log.InitD() +} + +func TestHttpRodRequestPipeline_Run(t *testing.T) { + + Convey("Create a rod pipeline", t, func() { + + g1 := forms.PipelineGroup{ + MetaInfo: &label.MetaInfo{ + Name: "Rod group", + }, + Stages: []interfaces.BasePipelineInterface{ + CreateProxyRodRequestPipeline(forms.GetCustomRetryOptions(1), nil), + &HttpRodResponsePipeline{ + BasePipeline: &forms.BasePipeline{ + Options: forms.GetNotRetribleOptions(), + MetaInfo: &label.MetaInfo{ + Name: "http response from rod emulator", + }, + }, + Fn: func(context context.Context, task interfaces.TaskInterface, logger interfaces.LoggerInterface, + browser *rod.Browser, page *rod.Page, body *string, doc *goquery.Document) (error, context.Context) { + + logger.Warning(doc.Html()) + + return nil, context + }, + Cn: func(err error, + context context.Context, + task interfaces.TaskInterface, + logger interfaces.LoggerInterface) { + + logger.Error("--- ", err.Error()) + }, + }, + }, + } + + newTask := fake.CreateDefaultTask() + + newTask.SetFetcherUrl("https://httpbin.org/ip") + newTask.SetFetcherMethod("GET") + newTask.Fetcher.Timeout = 60 + newTask.SetProxyServerUrl("http://localhost:8987") + ctxGroup := Task.NewTaskCtx(newTask) + + err := g1.Run(ctxGroup) + + So(err, ShouldBeNil) + }) +} diff --git a/extensions/pipelines/http/net-http/proxy.go b/extensions/pipelines/http/net-http/proxy.go index 85e0a2e..7d009f8 100644 --- a/extensions/pipelines/http/net-http/proxy.go +++ b/extensions/pipelines/http/net-http/proxy.go @@ -4,12 +4,11 @@ import ( "context" "fmt" "github.com/sirupsen/logrus" + "github.com/vortex14/gotyphoon/extensions/middlewares" "github.com/vortex14/gotyphoon/log" "net/http" "net/url" - "github.com/fatih/color" - "github.com/vortex14/gotyphoon/elements/forms" "github.com/vortex14/gotyphoon/elements/models/label" "github.com/vortex14/gotyphoon/elements/models/task" @@ -28,15 +27,15 @@ const ( ) func ConstructorProxySettingMiddleware(required bool) interfaces.MiddlewareInterface { - return &HttpMiddleware{ + return &middlewares.TaskMiddleware{ Middleware: &forms.Middleware{ MetaInfo: &label.MetaInfo{ - Required: required, - Name: NAMEProxyMiddleware, - Description: DescriptionProxyMiddleware, + Required: required, + Name: "get proxy for rod request", }, }, - Fn: func(context context.Context, task *task.TyphoonTask, request *http.Request, logger interfaces.LoggerInterface, reject func(err error), next func(ctx context.Context)) { + Fn: func(context context.Context, task *task.TyphoonTask, + logger interfaces.LoggerInterface, reject func(err error), next func(ctx context.Context)) { if len(task.GetProxyAddress()) > 0 { next(context) @@ -71,7 +70,7 @@ func ConstructorProxySettingMiddleware(required bool) interfaces.MiddlewareInter var proxyResponse models.Proxy err = utils.JsonLoad(&proxyResponse, *body) if err != nil { - color.Red("JsonLoad has Error: %s", err.Error()) + logger.Debug(fmt.Sprintf("JsonLoad has Error: %s", err.Error())) reject(err) return } @@ -82,6 +81,8 @@ func ConstructorProxySettingMiddleware(required bool) interfaces.MiddlewareInter task.SetUserAgent(proxyResponse.Agent) task.SetProxyAddress(proxyResponse.Proxy) + next(context) + }, } } diff --git a/extensions/pipelines/http/net-http/request.go b/extensions/pipelines/http/net-http/request.go index 4d83407..a7233b5 100644 --- a/extensions/pipelines/http/net-http/request.go +++ b/extensions/pipelines/http/net-http/request.go @@ -54,7 +54,7 @@ func FetchData(task *task.TyphoonTask) (error, *string) { func MakeBlockRequest(logger interfaces.LoggerInterface, task interfaces.TaskInterface) error { // Block current proxy proxy := task.GetProxyAddress() - logger.Error("block proxy: %s", proxy) + logger.Error(fmt.Sprintf("block proxy: %s", proxy)) urlSupported := fmt.Sprintf("%s/block?url=%s&proxy=%s&code=599", task.GetProxyServerUrl(), task.GetFetcherUrl(), proxy, diff --git a/go.mod b/go.mod index 3de0507..56d4e9e 100644 --- a/go.mod +++ b/go.mod @@ -31,6 +31,7 @@ require ( github.com/go-git/go-git/v5 v5.4.2 github.com/go-logfmt/logfmt v0.5.1 github.com/go-redis/redis/v8 v8.11.5 + github.com/go-rod/rod v0.111.0 github.com/gocarina/gocsv v0.0.0-20220531201732-5f969b02b902 github.com/goccy/go-graphviz v0.0.9 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 @@ -161,6 +162,9 @@ require ( github.com/xdg-go/scram v1.0.2 // indirect github.com/xdg-go/stringprep v1.0.2 // indirect github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect + github.com/ysmood/goob v0.4.0 // indirect + github.com/ysmood/gson v0.7.1 // indirect + github.com/ysmood/leakless v0.8.0 // indirect go.opencensus.io v0.23.0 // indirect go.uber.org/atomic v1.9.0 // indirect golang.org/x/image v0.0.0-20200119044424-58c23975cae1 // indirect diff --git a/go.sum b/go.sum index 5c2d7fd..99846a0 100644 --- a/go.sum +++ b/go.sum @@ -584,6 +584,8 @@ github.com/go-redis/redis/v8 v8.11.3 h1:GCjoYp8c+yQTJfc0n69iwSiHjvuAdruxl7elnZCx github.com/go-redis/redis/v8 v8.11.3/go.mod h1:xNJ9xDG09FsIPwh3bWdk+0oDWHbtF9rPN0F/oD9XeKc= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/go-rod/rod v0.111.0 h1:aMNNdz10GYPYec9z1WsFqwAdRYVsuufVTOrah7whG3I= +github.com/go-rod/rod v0.111.0/go.mod h1:GZDtmEs6RpF6kBRYpGCZXxXlKNneKVPiKOjaMbmVVjE= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -1462,6 +1464,14 @@ github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk= github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4= +github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ= +github.com/ysmood/goob v0.4.0/go.mod h1:u6yx7ZhS4Exf2MwciFr6nIM8knHQIE22lFpWHnfql18= +github.com/ysmood/got v0.31.3/go.mod h1:pE1l4LOwOBhQg6A/8IAatkGp7uZjnalzrZolnlhhMgY= +github.com/ysmood/gotrace v0.6.0/go.mod h1:TzhIG7nHDry5//eYZDYcTzuJLYQIkykJzCRIo4/dzQM= +github.com/ysmood/gson v0.7.1 h1:zKL2MTGtynxdBdlZjyGsvEOZ7dkxaY5TH6QhAbTgz0Q= +github.com/ysmood/gson v0.7.1/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg= +github.com/ysmood/leakless v0.8.0 h1:BzLrVoiwxikpgEQR0Lk8NyBN5Cit2b1z+u0mgL4ZJak= +github.com/ysmood/leakless v0.8.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/project.go b/project.go index 0a16992..f196a4a 100644 --- a/project.go +++ b/project.go @@ -549,7 +549,7 @@ func (p *Project) CreateSymbolicLink() error { env := &environment.Environment{} _, settings := env.GetSettings() - linkTyphoonPath := fmt.Sprintf("%s/pytyphoon/typhoon", settings.Path) + linkTyphoonPath := fmt.Sprintf("%s/typhoon", settings.Path) color.Yellow("TYPHOON_PATH=%s", settings.Path) directLink := filepath.Join(p.GetProjectPath(), "typhoon") color.Yellow(directLink)